mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
Add support for proxy protocol
This add support for version 1 and 2 of protocol specified in http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt To enable it you need add option use_proxy_protocol: true to listener.
This commit is contained in:
parent
9139ea86fb
commit
6845896d12
@ -24,7 +24,7 @@
|
|||||||
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}},
|
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.26"}}},
|
||||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
||||||
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
|
{fast_xml, ".*", {git, "https://github.com/processone/fast_xml", {tag, "1.1.34"}}},
|
||||||
{xmpp, ".*", {git, "https://github.com/processone/xmpp", {tag, "1.2.6"}}},
|
{xmpp, ".*", {git, "https://github.com/processone/xmpp", "2756f13fd2670b99a41c5a8d558b90528283ff6f"}},
|
||||||
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
|
{fast_yaml, ".*", {git, "https://github.com/processone/fast_yaml", {tag, "1.0.17"}}},
|
||||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||||
|
@ -69,7 +69,8 @@
|
|||||||
default_host,
|
default_host,
|
||||||
custom_headers,
|
custom_headers,
|
||||||
trail = <<>>,
|
trail = <<>>,
|
||||||
addr_re
|
addr_re,
|
||||||
|
sock_peer_name = none
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-define(XHTML_DOCTYPE,
|
-define(XHTML_DOCTYPE,
|
||||||
@ -143,6 +144,7 @@ init({SockMod, Socket}, Opts) ->
|
|||||||
true -> [{[], ejabberd_xmlrpc}];
|
true -> [{[], ejabberd_xmlrpc}];
|
||||||
false -> []
|
false -> []
|
||||||
end,
|
end,
|
||||||
|
SockPeer = proplists:get_value(sock_peer_name, Opts, none),
|
||||||
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
||||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||||
Admin ++ Bind ++ XMLRPC,
|
Admin ++ Bind ++ XMLRPC,
|
||||||
@ -159,6 +161,7 @@ init({SockMod, Socket}, Opts) ->
|
|||||||
custom_headers = CustomHeaders,
|
custom_headers = CustomHeaders,
|
||||||
options = Opts,
|
options = Opts,
|
||||||
request_handlers = RequestHandlers,
|
request_handlers = RequestHandlers,
|
||||||
|
sock_peer_name = SockPeer,
|
||||||
addr_re = RE},
|
addr_re = RE},
|
||||||
try receive_headers(State) of
|
try receive_headers(State) of
|
||||||
V -> V
|
V -> V
|
||||||
@ -463,6 +466,7 @@ process_request(#state{request_method = Method,
|
|||||||
request_version = Version,
|
request_version = Version,
|
||||||
sockmod = SockMod,
|
sockmod = SockMod,
|
||||||
socket = Socket,
|
socket = Socket,
|
||||||
|
sock_peer_name = SockPeer,
|
||||||
options = Options,
|
options = Options,
|
||||||
request_host = Host,
|
request_host = Host,
|
||||||
request_port = Port,
|
request_port = Port,
|
||||||
@ -481,13 +485,17 @@ process_request(#state{request_method = Method,
|
|||||||
{State2, false} ->
|
{State2, false} ->
|
||||||
{State2, make_bad_request(State)};
|
{State2, make_bad_request(State)};
|
||||||
{State2, {LPath, LQuery, Data}} ->
|
{State2, {LPath, LQuery, Data}} ->
|
||||||
PeerName =
|
PeerName = case SockPeer of
|
||||||
case SockMod of
|
none ->
|
||||||
gen_tcp ->
|
case SockMod of
|
||||||
inet:peername(Socket);
|
gen_tcp ->
|
||||||
_ ->
|
inet:peername(Socket);
|
||||||
SockMod:peername(Socket)
|
_ ->
|
||||||
end,
|
SockMod:peername(Socket)
|
||||||
|
end;
|
||||||
|
{_, Peer} ->
|
||||||
|
{ok, Peer}
|
||||||
|
end,
|
||||||
IPHere = case PeerName of
|
IPHere = case PeerName of
|
||||||
{ok, V} -> V;
|
{ok, V} -> V;
|
||||||
{error, _} = E -> throw(E)
|
{error, _} = E -> throw(E)
|
||||||
|
@ -204,26 +204,49 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
|
|||||||
NewInterval = check_rate_limit(Interval),
|
NewInterval = check_rate_limit(Interval),
|
||||||
case gen_tcp:accept(ListenSocket) of
|
case gen_tcp:accept(ListenSocket) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
case {inet:sockname(Socket), inet:peername(Socket)} of
|
case proplists:get_value(use_proxy_protocol, Opts, false) of
|
||||||
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
|
true ->
|
||||||
Receiver = case start_connection(Module, Socket, Opts, Sup) of
|
case proxy_protocol:decode(gen_tcp, Socket, 10000) of
|
||||||
{ok, RecvPid} ->
|
{error, Err} ->
|
||||||
RecvPid;
|
?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
|
||||||
_ ->
|
[ListenSocket, inet:format_error(Err)]),
|
||||||
gen_tcp:close(Socket),
|
gen_tcp:close(Socket);
|
||||||
none
|
{{Addr, Port}, {PAddr, PPort}} = SP ->
|
||||||
end,
|
Opts2 = [{sock_peer_name, SP} | Opts],
|
||||||
?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
|
Receiver = case start_connection(Module, Socket, Opts2, Sup) of
|
||||||
[Receiver,
|
{ok, RecvPid} ->
|
||||||
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
RecvPid;
|
||||||
PPort, inet_parse:ntoa(Addr), Port]);
|
_ ->
|
||||||
|
gen_tcp:close(Socket),
|
||||||
|
none
|
||||||
|
end,
|
||||||
|
?INFO_MSG("(~p) Accepted proxied connection ~s:~p -> ~s:~p",
|
||||||
|
[Receiver,
|
||||||
|
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
||||||
|
PPort, inet_parse:ntoa(Addr), Port])
|
||||||
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
gen_tcp:close(Socket)
|
case {inet:sockname(Socket), inet:peername(Socket)} of
|
||||||
|
{{ok, {Addr, Port}}, {ok, {PAddr, PPort}}} ->
|
||||||
|
Receiver = case start_connection(Module, Socket, Opts, Sup) of
|
||||||
|
{ok, RecvPid} ->
|
||||||
|
RecvPid;
|
||||||
|
_ ->
|
||||||
|
gen_tcp:close(Socket),
|
||||||
|
none
|
||||||
|
end,
|
||||||
|
?INFO_MSG("(~p) Accepted connection ~s:~p -> ~s:~p",
|
||||||
|
[Receiver,
|
||||||
|
ejabberd_config:may_hide_data(inet_parse:ntoa(PAddr)),
|
||||||
|
PPort, inet_parse:ntoa(Addr), Port]);
|
||||||
|
_ ->
|
||||||
|
gen_tcp:close(Socket)
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
accept(ListenSocket, Module, Opts, Sup, NewInterval);
|
accept(ListenSocket, Module, Opts, Sup, NewInterval);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?ERROR_MSG("(~w) Failed TCP accept: ~s",
|
?ERROR_MSG("(~w) Failed TCP accept: ~s",
|
||||||
[ListenSocket, inet:format_error(Reason)]),
|
[ListenSocket, inet:format_error(Reason)]),
|
||||||
accept(ListenSocket, Module, Opts, Sup, NewInterval)
|
accept(ListenSocket, Module, Opts, Sup, NewInterval)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -665,7 +688,9 @@ listen_opt_type(max_fsm_queue) ->
|
|||||||
listen_opt_type(shaper) ->
|
listen_opt_type(shaper) ->
|
||||||
fun acl:shaper_rules_validator/1;
|
fun acl:shaper_rules_validator/1;
|
||||||
listen_opt_type(access) ->
|
listen_opt_type(access) ->
|
||||||
fun acl:access_rules_validator/1.
|
fun acl:access_rules_validator/1;
|
||||||
|
listen_opt_type(use_proxy_protocol) ->
|
||||||
|
fun(B) when is_boolean(B) -> B end.
|
||||||
|
|
||||||
listen_options() ->
|
listen_options() ->
|
||||||
[module, port,
|
[module, port,
|
||||||
@ -675,6 +700,7 @@ listen_options() ->
|
|||||||
{inet6, false},
|
{inet6, false},
|
||||||
{accept_interval, 0},
|
{accept_interval, 0},
|
||||||
{backlog, 5},
|
{backlog, 5},
|
||||||
|
{use_proxy_protocol, false},
|
||||||
{supervisor, true}].
|
{supervisor, true}].
|
||||||
|
|
||||||
opt_type(listen) -> fun validate_cfg/1;
|
opt_type(listen) -> fun validate_cfg/1;
|
||||||
|
182
src/proxy_protocol.erl
Normal file
182
src/proxy_protocol.erl
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% File : ejabberd_http.erl
|
||||||
|
%%% Author : Paweł Chmielowski <pawel@process-one.net>
|
||||||
|
%%% Purpose :
|
||||||
|
%%% Created : 27 Nov 2018 by Paweł Chmielowski <pawel@process-one.net>
|
||||||
|
%%%
|
||||||
|
%%%
|
||||||
|
%%% ejabberd, Copyright (C) 2002-2018 ProcessOne
|
||||||
|
%%%
|
||||||
|
%%% This program is free software; you can redistribute it and/or
|
||||||
|
%%% modify it under the terms of the GNU General Public License as
|
||||||
|
%%% published by the Free Software Foundation; either version 2 of the
|
||||||
|
%%% License, or (at your option) any later version.
|
||||||
|
%%%
|
||||||
|
%%% This program is distributed in the hope that it will be useful,
|
||||||
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
%%% General Public License for more details.
|
||||||
|
%%%
|
||||||
|
%%% You should have received a copy of the GNU General Public License along
|
||||||
|
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
%%%
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
-module(proxy_protocol).
|
||||||
|
-author("pawel@process-one.net").
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([decode/3]).
|
||||||
|
|
||||||
|
decode(SockMod, Socket, Timeout) ->
|
||||||
|
V = SockMod:recv(Socket, 6, Timeout),
|
||||||
|
case V of
|
||||||
|
{ok, <<"PROXY ">>} ->
|
||||||
|
decode_v1(SockMod, Socket, Timeout);
|
||||||
|
{ok, <<16#0d, 16#0a, 16#0d, 16#0a, 16#00, 16#0d>>} ->
|
||||||
|
decode_v2(SockMod, Socket, Timeout);
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
end.
|
||||||
|
|
||||||
|
decode_v1(SockMod, Socket, Timeout) ->
|
||||||
|
case read_until_rn(SockMod, Socket, <<>>, false, Timeout) of
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err;
|
||||||
|
Val ->
|
||||||
|
case binary:split(Val, <<" ">>, [global]) of
|
||||||
|
[<<"TCP4">>, SAddr, DAddr, SPort, DPort] ->
|
||||||
|
try {inet_parse:ipv4strict_address(binary_to_list(SAddr)),
|
||||||
|
inet_parse:ipv4strict_address(binary_to_list(DAddr)),
|
||||||
|
binary_to_integer(SPort),
|
||||||
|
binary_to_integer(DPort)}
|
||||||
|
of
|
||||||
|
{{ok, DA}, {ok, SA}, DP, SP} ->
|
||||||
|
{{SA, SP}, {DA, DP}};
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
catch
|
||||||
|
error:badarg ->
|
||||||
|
{error, eproto}
|
||||||
|
end;
|
||||||
|
[<<"TCP6">>, SAddr, DAddr, SPort, DPort] ->
|
||||||
|
try {inet_parse:ipv6strict_address(binary_to_list(SAddr)),
|
||||||
|
inet_parse:ipv6strict_address(binary_to_list(DAddr)),
|
||||||
|
binary_to_integer(SPort),
|
||||||
|
binary_to_integer(DPort)}
|
||||||
|
of
|
||||||
|
{{ok, DA}, {ok, SA}, DP, SP} ->
|
||||||
|
{{SA, SP}, {DA, DP}};
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
catch
|
||||||
|
error:badarg ->
|
||||||
|
{error, eproto}
|
||||||
|
end;
|
||||||
|
[<<"UNKNOWN">> | _] ->
|
||||||
|
{undefined, undefined}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
decode_v2(SockMod, Socket, Timeout) ->
|
||||||
|
case SockMod:recv(Socket, 10, Timeout) of
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err;
|
||||||
|
{ok, <<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a,
|
||||||
|
2:4, Command:4, Transport:8, AddrLen:16/big-unsigned-integer>>} ->
|
||||||
|
case SockMod:recv(Socket, AddrLen, Timeout) of
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err;
|
||||||
|
{ok, Data} ->
|
||||||
|
case Command of
|
||||||
|
0 ->
|
||||||
|
case {inet:sockname(Socket), inet:peername(Socket)} of
|
||||||
|
{{ok, SA}, {ok, DA}} ->
|
||||||
|
{SA, DA};
|
||||||
|
{{error, _} = E, _} ->
|
||||||
|
E;
|
||||||
|
{_, {error, _} = E} ->
|
||||||
|
E
|
||||||
|
end;
|
||||||
|
1 ->
|
||||||
|
case Transport of
|
||||||
|
% UNSPEC or UNIX
|
||||||
|
V when V == 0; V == 16#31; V == 16#32 ->
|
||||||
|
{{unknown, unknown}, {unknown, unknown}};
|
||||||
|
% IPV4 over TCP or UDP
|
||||||
|
V when V == 16#11; V == 16#12 ->
|
||||||
|
case Data of
|
||||||
|
<<D1:8, D2:8, D3:8, D4:8,
|
||||||
|
S1:8, S2:8, S3:8, S4:8,
|
||||||
|
DP:16/big-unsigned-integer,
|
||||||
|
SP:16/big-unsigned-integer>> ->
|
||||||
|
{{{S1, S2, S3, S4}, SP},
|
||||||
|
{{D1, D2, D3, D4}, DP}};
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
end;
|
||||||
|
% IPV6 over TCP or UDP
|
||||||
|
V when V == 16#21; V == 16#22 ->
|
||||||
|
case Data of
|
||||||
|
<<D1:16/big-unsigned-integer,
|
||||||
|
D2:16/big-unsigned-integer,
|
||||||
|
D3:16/big-unsigned-integer,
|
||||||
|
D4:16/big-unsigned-integer,
|
||||||
|
D5:16/big-unsigned-integer,
|
||||||
|
D6:16/big-unsigned-integer,
|
||||||
|
D7:16/big-unsigned-integer,
|
||||||
|
D8:16/big-unsigned-integer,
|
||||||
|
S1:16/big-unsigned-integer,
|
||||||
|
S2:16/big-unsigned-integer,
|
||||||
|
S3:16/big-unsigned-integer,
|
||||||
|
S4:16/big-unsigned-integer,
|
||||||
|
S5:16/big-unsigned-integer,
|
||||||
|
S6:16/big-unsigned-integer,
|
||||||
|
S7:16/big-unsigned-integer,
|
||||||
|
S8:16/big-unsigned-integer,
|
||||||
|
DP:16/big-unsigned-integer,
|
||||||
|
SP:16/big-unsigned-integer>> ->
|
||||||
|
{{{S1, S2, S3, S4, S5, S6, S7, S8}, SP},
|
||||||
|
{{D1, D2, D3, D4, D5, D6, D7, D8}, DP}};
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
<<16#0a, 16#51, 16#55, 16#49, 16#54, 16#0a, _/binary>> ->
|
||||||
|
{error, eproto};
|
||||||
|
_ ->
|
||||||
|
{error, eproto}
|
||||||
|
end.
|
||||||
|
|
||||||
|
read_until_rn(_SockMod, _Socket, Data, _, _) when size(Data) > 107 ->
|
||||||
|
{error, eproto};
|
||||||
|
read_until_rn(SockMod, Socket, Data, true, Timeout) ->
|
||||||
|
case SockMod:recv(Socket, 1, Timeout) of
|
||||||
|
{ok, <<"\n">>} ->
|
||||||
|
Data;
|
||||||
|
{ok, <<"\r">>} ->
|
||||||
|
read_until_rn(SockMod, Socket, <<Data/binary, "\r">>,
|
||||||
|
true, Timeout);
|
||||||
|
{ok, Other} ->
|
||||||
|
read_until_rn(SockMod, Socket, <<Data/binary, "\r", Other/binary>>,
|
||||||
|
false, Timeout);
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
read_until_rn(SockMod, Socket, Data, false, Timeout) ->
|
||||||
|
case SockMod:recv(Socket, 2, Timeout) of
|
||||||
|
{ok, <<"\r\n">>} ->
|
||||||
|
Data;
|
||||||
|
{ok, <<Byte:8, "\r">>} ->
|
||||||
|
read_until_rn(SockMod, Socket, <<Data/binary, Byte:8>>,
|
||||||
|
true, Timeout);
|
||||||
|
{ok, Other} ->
|
||||||
|
read_until_rn(SockMod, Socket, <<Data/binary, Other/binary>>,
|
||||||
|
false, Timeout);
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err
|
||||||
|
end.
|
Loading…
Reference in New Issue
Block a user