mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-24 17:29:28 +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"}}},
|
||||
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.14"}}},
|
||||
{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"}}},
|
||||
{jiffy, ".*", {git, "https://github.com/davisp/jiffy", {tag, "0.14.8"}}},
|
||||
{p1_oauth2, ".*", {git, "https://github.com/processone/p1_oauth2", {tag, "0.6.3"}}},
|
||||
|
@ -69,7 +69,8 @@
|
||||
default_host,
|
||||
custom_headers,
|
||||
trail = <<>>,
|
||||
addr_re
|
||||
addr_re,
|
||||
sock_peer_name = none
|
||||
}).
|
||||
|
||||
-define(XHTML_DOCTYPE,
|
||||
@ -143,6 +144,7 @@ init({SockMod, Socket}, Opts) ->
|
||||
true -> [{[], ejabberd_xmlrpc}];
|
||||
false -> []
|
||||
end,
|
||||
SockPeer = proplists:get_value(sock_peer_name, Opts, none),
|
||||
DefinedHandlers = proplists:get_value(request_handlers, Opts, []),
|
||||
RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++
|
||||
Admin ++ Bind ++ XMLRPC,
|
||||
@ -159,6 +161,7 @@ init({SockMod, Socket}, Opts) ->
|
||||
custom_headers = CustomHeaders,
|
||||
options = Opts,
|
||||
request_handlers = RequestHandlers,
|
||||
sock_peer_name = SockPeer,
|
||||
addr_re = RE},
|
||||
try receive_headers(State) of
|
||||
V -> V
|
||||
@ -463,6 +466,7 @@ process_request(#state{request_method = Method,
|
||||
request_version = Version,
|
||||
sockmod = SockMod,
|
||||
socket = Socket,
|
||||
sock_peer_name = SockPeer,
|
||||
options = Options,
|
||||
request_host = Host,
|
||||
request_port = Port,
|
||||
@ -481,13 +485,17 @@ process_request(#state{request_method = Method,
|
||||
{State2, false} ->
|
||||
{State2, make_bad_request(State)};
|
||||
{State2, {LPath, LQuery, Data}} ->
|
||||
PeerName =
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end,
|
||||
PeerName = case SockPeer of
|
||||
none ->
|
||||
case SockMod of
|
||||
gen_tcp ->
|
||||
inet:peername(Socket);
|
||||
_ ->
|
||||
SockMod:peername(Socket)
|
||||
end;
|
||||
{_, Peer} ->
|
||||
{ok, Peer}
|
||||
end,
|
||||
IPHere = case PeerName of
|
||||
{ok, V} -> V;
|
||||
{error, _} = E -> throw(E)
|
||||
|
@ -204,26 +204,49 @@ accept(ListenSocket, Module, Opts, Sup, Interval) ->
|
||||
NewInterval = check_rate_limit(Interval),
|
||||
case gen_tcp:accept(ListenSocket) of
|
||||
{ok, 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]);
|
||||
case proplists:get_value(use_proxy_protocol, Opts, false) of
|
||||
true ->
|
||||
case proxy_protocol:decode(gen_tcp, Socket, 10000) of
|
||||
{error, Err} ->
|
||||
?ERROR_MSG("(~w) Proxy protocol parsing failed: ~s",
|
||||
[ListenSocket, inet:format_error(Err)]),
|
||||
gen_tcp:close(Socket);
|
||||
{{Addr, Port}, {PAddr, PPort}} = SP ->
|
||||
Opts2 = [{sock_peer_name, SP} | Opts],
|
||||
Receiver = case start_connection(Module, Socket, Opts2, Sup) of
|
||||
{ok, RecvPid} ->
|
||||
RecvPid;
|
||||
_ ->
|
||||
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,
|
||||
accept(ListenSocket, Module, Opts, Sup, NewInterval);
|
||||
{error, Reason} ->
|
||||
?ERROR_MSG("(~w) Failed TCP accept: ~s",
|
||||
[ListenSocket, inet:format_error(Reason)]),
|
||||
[ListenSocket, inet:format_error(Reason)]),
|
||||
accept(ListenSocket, Module, Opts, Sup, NewInterval)
|
||||
end.
|
||||
|
||||
@ -665,7 +688,9 @@ listen_opt_type(max_fsm_queue) ->
|
||||
listen_opt_type(shaper) ->
|
||||
fun acl:shaper_rules_validator/1;
|
||||
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() ->
|
||||
[module, port,
|
||||
@ -675,6 +700,7 @@ listen_options() ->
|
||||
{inet6, false},
|
||||
{accept_interval, 0},
|
||||
{backlog, 5},
|
||||
{use_proxy_protocol, false},
|
||||
{supervisor, true}].
|
||||
|
||||
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