From 7d923ee5702dfb3d4a96a541667b82206cf8b0e3 Mon Sep 17 00:00:00 2001 From: Luke Bakken Date: Mon, 15 Dec 2025 18:55:55 -0800 Subject: [PATCH] Return friendly error when AMQP 1.0 receiver has no source When a client attaches an AMQP 1.0 receiver link without specifying a source address, RabbitMQ crashes with a `function_clause` error and returns an Erlang stack trace to the client. This makes it difficult for developers to understand what went wrong. Previously, the client received: ``` SessionError: function_clause [{rabbit_amqp_session,ensure_source, [undefined,false,<<...>>,<<...>>, {user,<<...>>,[administrator],[...]}, <<...>>,<0.585.0>,[],[]], [{file,"rabbit_amqp_session.erl"},{line,2665}]}, {rabbit_amqp_session,handle_attach,2,...}, ...] ``` This change adds a new clause to `ensure_source/9` that catches the case where the source is `undefined`. Instead of crashing, the function now returns `{error, source_address_required}`, which `handle_attach/2` maps to an `amqp:invalid-field` error with a clear message. Now, the client receives: ``` LinkError: Attach refused: source_address_required condition: 'amqp:invalid-field', description: 'Attach refused: source_address_required' ``` The error condition `amqp:invalid-field` is appropriate here because the client failed to provide a required field in the attach frame, not because a resource was not found. Also added a test for an `undefined` receiver address. Fixes https://github.com/rabbitmq/rabbitmq-server/discussions/14300 --- deps/rabbit/src/rabbit_amqp_session.erl | 2 ++ deps/rabbit/test/amqp_address_SUITE.erl | 36 ++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/deps/rabbit/src/rabbit_amqp_session.erl b/deps/rabbit/src/rabbit_amqp_session.erl index f178c9a51c5d..6bc0075d1473 100644 --- a/deps/rabbit/src/rabbit_amqp_session.erl +++ b/deps/rabbit/src/rabbit_amqp_session.erl @@ -2719,6 +2719,8 @@ ensure_source(#'v1_0.source'{ _ -> exit_not_implemented("Dynamic source not supported: ~tp", [Source0]) end; +ensure_source(undefined, _, _, _, _, _, _, _, _) -> + {error, source_required}; ensure_source(Source = #'v1_0.source'{dynamic = true}, _, _, _, _, _, _, _, _) -> exit_not_implemented("Dynamic source not supported: ~tp", [Source]); ensure_source(Source0 = #'v1_0.source'{address = Address, diff --git a/deps/rabbit/test/amqp_address_SUITE.erl b/deps/rabbit/test/amqp_address_SUITE.erl index 50adfa8a9344..a08c15b5526a 100644 --- a/deps/rabbit/test/amqp_address_SUITE.erl +++ b/deps/rabbit/test/amqp_address_SUITE.erl @@ -65,7 +65,8 @@ common_tests() -> target_per_message_exchange_absent_settled, target_per_message_exchange_absent_unsettled, target_bad_address, - source_bad_address + source_bad_address, + receiver_address_undefined ]. init_per_suite(Config) -> @@ -420,6 +421,39 @@ target_per_message_unset_to_address(Config) -> ok = amqp10_client:end_session(Session), ok = amqp10_client:close_connection(Connection). +%% Test v2 target address +%% receiver address undefined +receiver_address_undefined(Config) -> + QName = atom_to_binary(?FUNCTION_NAME), + Addr = rabbitmq_amqp_address:queue(QName), + Init = {_, LinkPair = #link_pair{session = Session}} = init(Config), + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + + AttachArgs = #{name => <<"receiver">>, + role => {receiver, #{address => undefined, + durable => none}, self()}, + snd_settle_mode => settled, + rcv_settle_mode => first, + filter => #{}, + properties => #{}, + raw_mode => false}, + {ok, Receiver} = amqp10_client:attach_link(Session, AttachArgs), + + Expected = {amqp10_event, {link, Receiver, {detached, #'v1_0.error'{ + condition = ?V_1_0_AMQP_ERROR_INVALID_FIELD, + description = {utf8, <<"Attach refused: {bad_address,undefined}">>}}}}}, + Got = receive + {amqp10_event, {link, _, {detached, _}}}=Event -> + Event + after ?TIMEOUT -> + Reason = {missing_event, ?LINE}, + flush(Reason), + ct:fail(Reason) + end, + ?assertMatch(Got, Expected), + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + ok = cleanup(Init). + bad_v2_addresses() -> [ %% valid v1, but bad v2 target addresses