Packages
amqp_client
3.8.21
4.2.1
4.2.0
retired
4.1.6
4.1.5
retired
4.1.5-rc.2
retired
4.1.5-rc.1
retired
4.1.5-1
4.0.3
4.0.3-rc.1
3.13.0-rc.2
3.13.0-rc.1
3.12.14
3.12.13
3.12.12
3.12.11
3.12.10
3.12.9
3.12.8
3.12.7
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.12.0-rc.4
3.11.28
3.11.27
3.11.26
3.11.25
3.11.24
3.11.23
3.11.22
3.11.21
3.11.20
3.11.19
3.11.18
3.11.17
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.11.0-rc.2
3.11.0-rc.1
3.11.0-1
3.10.20
3.10.19
3.10.18
3.10.17
3.10.16
3.10.15
3.10.14
3.10.13
3.10.12
3.10.11
3.10.10
3.10.9
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.10.0-rc.6
3.10.0-rc.5
3.9.29
3.9.28
3.9.27
3.9.26
3.9.25
3.9.24
3.9.23
3.9.22
3.9.21
3.9.20
3.9.19
3.9.18
3.9.17
3.9.16
3.9.15
3.9.11
3.9.8
3.9.5
3.9.4
3.9.1
3.8.35
3.8.34
3.8.33
3.8.32
3.8.31
3.8.30
3.8.25
3.8.21
3.8.19
3.8.14
3.8.11
3.8.10
3.8.10-rc.1
3.8.9
3.8.7
3.8.6
3.8.6-rc.2
3.8.6-rc.1
3.8.5
3.8.5-rc.2
3.8.5-rc.1
3.8.4
3.8.4-rc.3
3.8.4-rc.1
3.8.3
3.8.3-rc.2
3.8.3-rc.1
3.8.2
3.8.2-rc.1
3.8.1
3.8.1-rc.1
3.8.0
3.8.0-rc.3
3.8.0-rc.2
3.8.0-rc.1
3.7.28
3.7.27
3.7.27-rc.2
3.7.27-rc.1
3.7.26
3.7.25
3.7.25-rc.1
3.7.24
3.7.24-rc.2
3.7.24-rc.1
3.7.23
3.7.23-rc.1
3.7.22
3.7.22-rc.2
3.7.22-rc.1
3.7.21
3.7.20
3.7.20-rc.2
3.7.20-rc.1
3.7.19
3.7.18
3.7.18-rc.1
3.7.17
3.7.17-rc.3
3.7.17-rc.2
3.7.15
3.7.14
3.7.14-rc.2
3.7.14-rc.1
3.7.13
3.7.13-rc.2
3.7.13-rc.1
3.7.12
3.7.12-rc.2
3.7.12-rc.1
3.7.11
3.7.11-rc.2
3.7.11-rc.1
3.7.10-rc.4
3.7.10-rc.3
3.7.10-rc.2
3.7.10-rc.1
3.7.9
3.7.9-rc.3
3.7.9-rc.2
3.7.8
3.7.8-rc.4
3.7.8-rc.3
3.7.8-rc.2
3.7.8-rc.1
3.7.7
3.7.7-rc.2
3.7.7-rc.1
3.7.6
3.7.6-rc.2
3.7.6-rc.1
3.7.5
3.7.5-rc.1
3.7.4
3.7.4-rc.4
3.7.4-rc.3
3.7.4-rc.2
3.7.4-rc.1
3.7.3
3.7.3-rc.2
3.7.3-rc.1
3.7.2
3.7.1
3.7.0-rc.2
3.6.16
3.6.16-rc.1
3.6.15
3.6.15-rc.1
3.6.14
3.6.13
3.6.12
3.6.11
3.6.10
3.6.9
3.6.8
3.6.7
3.6.7-pre.1
3.5.6
3.5.0
3.4.0
3.3.5
3.0.2
0.0.0-rc.1
RabbitMQ AMQP Client
Current section
Files
Jump to
Current section
Files
src/amqp_network_connection.erl
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates. All rights reserved.
%%
%% @private
-module(amqp_network_connection).
-include("amqp_client_internal.hrl").
-behaviour(amqp_gen_connection).
-export([init/0, terminate/2, connect/4, do/2, open_channel_args/1, i/2,
info_keys/0, handle_message/2, closing/3, channels_terminated/1]).
-define(RABBIT_TCP_OPTS, [binary, {packet, 0}, {active,false}, {nodelay, true}]).
-define(SOCKET_CLOSING_TIMEOUT, 1000).
-define(HANDSHAKE_RECEIVE_TIMEOUT, 60000).
-define(TCP_MAX_PACKET_SIZE, (16#4000000 + ?EMPTY_FRAME_SIZE - 1)).
-record(state, {sock,
name,
heartbeat,
writer0,
frame_max,
type_sup,
closing_reason, %% undefined | Reason
waiting_socket_close = false}).
-define(INFO_KEYS, [type, heartbeat, frame_max, sock, name]).
%%---------------------------------------------------------------------------
init() ->
{ok, #state{}}.
open_channel_args(#state{sock = Sock, frame_max = FrameMax}) ->
[Sock, FrameMax].
do(#'connection.close_ok'{} = CloseOk, State) ->
erlang:send_after(?SOCKET_CLOSING_TIMEOUT, self(), socket_closing_timeout),
do2(CloseOk, State);
do(Method, State) ->
do2(Method, State).
do2(Method, #state{writer0 = Writer}) ->
%% Catching because it expects the {channel_exit, _, _} message on error
catch rabbit_writer:send_command_sync(Writer, Method).
handle_message(socket_closing_timeout,
State = #state{closing_reason = Reason}) ->
{stop, {socket_closing_timeout, Reason}, State};
handle_message(socket_closed, State = #state{waiting_socket_close = true,
closing_reason = Reason}) ->
{stop, {shutdown, Reason}, State};
handle_message(socket_closed, State = #state{waiting_socket_close = false}) ->
{stop, socket_closed_unexpectedly, State};
handle_message({socket_error, _} = SocketError, State) ->
{stop, SocketError, State};
handle_message({channel_exit, 0, Reason}, State) ->
{stop, {channel0_died, Reason}, State};
handle_message(heartbeat_timeout, State) ->
{stop, heartbeat_timeout, State};
handle_message(closing_timeout, State = #state{closing_reason = Reason}) ->
{stop, Reason, State};
handle_message({'EXIT', Pid, Reason}, State) ->
{stop, rabbit_misc:format("stopping because dependent process ~p died: ~p", [Pid, Reason]), State};
%% see http://erlang.org/pipermail/erlang-bugs/2012-June/002933.html
handle_message({Ref, {error, Reason}},
State = #state{waiting_socket_close = Waiting,
closing_reason = CloseReason})
when is_reference(Ref) ->
{stop, case {Reason, Waiting} of
{closed, true} -> {shutdown, CloseReason};
{closed, false} -> socket_closed_unexpectedly;
{_, _} -> {socket_error, Reason}
end, State}.
closing(_ChannelCloseType, {server_initiated_close, _, _} = Reason, State) ->
{ok, State#state{waiting_socket_close = true,
closing_reason = Reason}};
closing(_ChannelCloseType, Reason, State) ->
{ok, State#state{closing_reason = Reason}}.
channels_terminated(State = #state{closing_reason =
{server_initiated_close, _, _}}) ->
{ok, State#state{waiting_socket_close = true}};
channels_terminated(State) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
i(type, _State) -> network;
i(heartbeat, State) -> State#state.heartbeat;
i(frame_max, State) -> State#state.frame_max;
i(sock, State) -> State#state.sock;
i(name, State) -> State#state.name;
i(Item, _State) -> throw({bad_argument, Item}).
info_keys() ->
?INFO_KEYS.
%%---------------------------------------------------------------------------
%% Handshake
%%---------------------------------------------------------------------------
connect(AmqpParams = #amqp_params_network{host = Host}, SIF, TypeSup, State) ->
case gethostaddr(Host) of
{error, Reason} -> {error, Reason};
AF -> do_connect(AF, AmqpParams, SIF, State#state{type_sup = TypeSup})
end.
do_connect({Addr, Family},
AmqpParams = #amqp_params_network{ssl_options = none,
port = Port,
connection_timeout = Timeout,
socket_options = ExtraOpts},
SIF, State) ->
ok = obtain(),
case gen_tcp:connect(Addr, Port,
[Family | ?RABBIT_TCP_OPTS] ++ ExtraOpts,
Timeout) of
{ok, Sock} -> try_handshake(AmqpParams, SIF,
State#state{sock = Sock});
{error, _} = E -> E
end;
do_connect({Addr, Family},
AmqpParams = #amqp_params_network{ssl_options = SslOpts0,
port = Port,
connection_timeout = Timeout,
socket_options = ExtraOpts},
SIF, State) ->
{ok, GlobalSslOpts} = application:get_env(amqp_client, ssl_options),
app_utils:start_applications([asn1, crypto, public_key, ssl]),
ok = obtain(),
case gen_tcp:connect(Addr, Port,
[Family | ?RABBIT_TCP_OPTS] ++ ExtraOpts,
Timeout) of
{ok, Sock} ->
SslOpts = rabbit_ssl_options:fix(
orddict:to_list(
orddict:merge(fun (_, _A, B) -> B end,
orddict:from_list(GlobalSslOpts),
orddict:from_list(SslOpts0)))),
case ssl:connect(Sock, SslOpts, Timeout) of
{ok, SslSock} ->
try_handshake(AmqpParams, SIF,
State#state{sock = SslSock});
{error, _} = E ->
E
end;
{error, _} = E ->
E
end.
inet_address_preference() ->
case application:get_env(amqp_client, prefer_ipv6) of
{ok, true} -> [inet6, inet];
{ok, false} -> [inet, inet6]
end.
gethostaddr(Host) ->
resolve_address(Host, inet_address_preference()).
resolve_address(Host, [Family | Remaining]) ->
case inet:getaddr(Host, Family) of
{ok, IP} -> {IP, Family};
_ -> resolve_address(Host, Remaining)
end;
resolve_address(_Host, []) ->
{error, unknown_host}.
try_handshake(AmqpParams, SIF, State = #state{sock = Sock}) ->
Name = case rabbit_net:connection_string(Sock, outbound) of
{ok, Str} -> list_to_binary(Str);
{error, _} -> <<"unknown">>
end,
try handshake(AmqpParams, SIF,
State#state{name = <<"client ", Name/binary>>}) of
Return -> Return
catch exit:Reason -> {error, Reason}
end.
handshake(AmqpParams, SIF, State0 = #state{sock = Sock}) ->
case rabbit_net:send(Sock, ?PROTOCOL_HEADER) of
ok ->
case start_infrastructure(SIF, State0) of
{ok, ChMgr, State1} ->
network_handshake(AmqpParams, {ChMgr, State1});
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end.
start_infrastructure(SIF, State = #state{sock = Sock, name = Name}) ->
case SIF(Sock, Name) of
{ok, ChMgr, Writer} ->
{ok, ChMgr, State#state{writer0 = Writer}};
{error, Reason} ->
{error, Reason}
end.
network_handshake(AmqpParams = #amqp_params_network{virtual_host = VHost},
{ChMgr, State0}) ->
Start = #'connection.start'{server_properties = ServerProperties,
mechanisms = Mechanisms} =
handshake_recv('connection.start'),
ok = check_version(Start),
case login(AmqpParams, Mechanisms, State0) of
{closing, #amqp_error{}, _Error} = Err ->
do(#'connection.close_ok'{}, State0),
Err;
Tune ->
{TuneOk, ChannelMax, State1} = tune(Tune, AmqpParams, State0),
do2(TuneOk, State1),
do2(#'connection.open'{virtual_host = VHost}, State1),
Params = {ServerProperties, ChannelMax, ChMgr, State1},
case handshake_recv('connection.open_ok') of
#'connection.open_ok'{} -> {ok, Params};
{closing, #amqp_error{} = AmqpError, Error} -> {closing, Params,
AmqpError, Error}
end
end.
check_version(#'connection.start'{version_major = ?PROTOCOL_VERSION_MAJOR,
version_minor = ?PROTOCOL_VERSION_MINOR}) ->
ok;
check_version(#'connection.start'{version_major = 8,
version_minor = 0}) ->
exit({protocol_version_mismatch, 0, 8});
check_version(#'connection.start'{version_major = Major,
version_minor = Minor}) ->
exit({protocol_version_mismatch, Major, Minor}).
tune(#'connection.tune'{channel_max = ServerChannelMax,
frame_max = ServerFrameMax,
heartbeat = ServerHeartbeat},
#amqp_params_network{channel_max = ClientChannelMax,
frame_max = ClientFrameMax,
heartbeat = ClientHeartbeat}, State) ->
[ChannelMax, Heartbeat, FrameMax] =
lists:zipwith(fun (Client, Server) when Client =:= 0; Server =:= 0 ->
lists:max([Client, Server]);
(Client, Server) ->
lists:min([Client, Server])
end,
[ClientChannelMax, ClientHeartbeat, ClientFrameMax],
[ServerChannelMax, ServerHeartbeat, ServerFrameMax]),
%% If we attempt to recv > 64Mb, inet_drv will return enomem, so
%% we cap the max negotiated frame size accordingly. Note that
%% since we receive the frame header separately, we can actually
%% cope with frame sizes of 64M + ?EMPTY_FRAME_SIZE - 1.
CappedFrameMax = case FrameMax of
0 -> ?TCP_MAX_PACKET_SIZE;
_ -> lists:min([FrameMax, ?TCP_MAX_PACKET_SIZE])
end,
NewState = State#state{heartbeat = Heartbeat, frame_max = CappedFrameMax},
start_heartbeat(NewState),
{#'connection.tune_ok'{channel_max = ChannelMax,
frame_max = CappedFrameMax,
heartbeat = Heartbeat}, ChannelMax, NewState}.
start_heartbeat(#state{sock = Sock,
name = Name,
heartbeat = Heartbeat,
type_sup = Sup}) ->
Frame = rabbit_binary_generator:build_heartbeat_frame(),
SendFun = fun () -> catch rabbit_net:send(Sock, Frame) end,
Connection = self(),
ReceiveFun = fun () -> Connection ! heartbeat_timeout end,
rabbit_heartbeat:start(
Sup, Sock, Name, Heartbeat, SendFun, Heartbeat, ReceiveFun).
login(Params = #amqp_params_network{auth_mechanisms = ClientMechanisms,
client_properties = UserProps},
ServerMechanismsStr, State) ->
ServerMechanisms = string:tokens(binary_to_list(ServerMechanismsStr), " "),
case [{N, S, F} || F <- ClientMechanisms,
{N, S} <- [F(none, Params, init)],
lists:member(binary_to_list(N), ServerMechanisms)] of
[{Name, MState0, Mech}|_] ->
{Resp, MState1} = Mech(none, Params, MState0),
StartOk = #'connection.start_ok'{
client_properties = client_properties(UserProps),
mechanism = Name,
response = Resp},
do2(StartOk, State),
login_loop(Mech, MState1, Params, State);
[] ->
exit({no_suitable_auth_mechanism, ServerMechanisms})
end.
login_loop(Mech, MState0, Params, State) ->
case handshake_recv('connection.tune') of
Tune = #'connection.tune'{} ->
Tune;
#'connection.secure'{challenge = Challenge} ->
{Resp, MState1} = Mech(Challenge, Params, MState0),
do2(#'connection.secure_ok'{response = Resp}, State),
login_loop(Mech, MState1, Params, State);
#'connection.close'{reply_code = ?ACCESS_REFUSED,
reply_text = ExplanationBin} ->
Explanation = binary_to_list(ExplanationBin),
{closing,
#amqp_error{name = access_refused,
explanation = Explanation},
{error, {auth_failure, Explanation}}}
end.
client_properties(UserProperties) ->
{ok, Vsn} = application:get_key(amqp_client, vsn),
Default = [{<<"product">>, longstr, <<"RabbitMQ">>},
{<<"version">>, longstr, list_to_binary(Vsn)},
{<<"platform">>, longstr, <<"Erlang">>},
{<<"copyright">>, longstr,
<<"Copyright (c) 2007-2021 VMware, Inc. or its affiliates.">>},
{<<"information">>, longstr,
<<"Licensed under the MPL. "
"See https://www.rabbitmq.com/">>},
{<<"capabilities">>, table, ?CLIENT_CAPABILITIES}],
lists:foldl(fun({K, _, _} = Tuple, Acc) ->
lists:keystore(K, 1, Acc, Tuple)
end, Default, UserProperties).
handshake_recv(Expecting) ->
receive
{'$gen_cast', {method, Method, none, noflow}} ->
case {Expecting, element(1, Method)} of
{E, M} when E =:= M ->
Method;
{'connection.tune', 'connection.secure'} ->
Method;
{'connection.tune', 'connection.close'} ->
Method;
{'connection.open_ok', 'connection.close'} ->
exit(get_reason(Method));
{'connection.open_ok', _} ->
{closing,
#amqp_error{name = command_invalid,
explanation = "was expecting "
"connection.open_ok"},
{error, {unexpected_method, Method,
{expecting, Expecting}}}};
_ ->
throw({unexpected_method, Method,
{expecting, Expecting}})
end;
socket_closed ->
case Expecting of
'connection.tune' -> exit({auth_failure, "Disconnected"});
'connection.open_ok' -> exit(access_refused);
_ -> exit({socket_closed_unexpectedly,
Expecting})
end;
{socket_error, _} = SocketError ->
exit({SocketError, {expecting, Expecting}});
{refused, Version} ->
exit({server_refused_connection, Version});
{malformed_header, All} ->
exit({server_sent_malformed_header, All});
heartbeat_timeout ->
exit(heartbeat_timeout);
Other ->
throw({handshake_recv_unexpected_message, Other})
after ?HANDSHAKE_RECEIVE_TIMEOUT ->
case Expecting of
'connection.open_ok' ->
{closing,
#amqp_error{name = internal_error,
explanation = "handshake timed out waiting "
"connection.open_ok"},
{error, handshake_receive_timed_out}};
_ ->
exit(handshake_receive_timed_out)
end
end.
obtain() ->
case code:is_loaded(file_handle_cache) of
false -> ok;
_ -> file_handle_cache:obtain()
end.
get_reason(#'connection.close'{reply_code = ErrCode}) ->
?PROTOCOL:amqp_exception(ErrCode).