Skip to content

Commit

Permalink
Merge 4d6d213 into fc5947e
Browse files Browse the repository at this point in the history
  • Loading branch information
weiss committed Dec 10, 2021
2 parents fc5947e + 4d6d213 commit 8a1da0c
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -13,6 +13,6 @@ p1_utils is an application containing ProcessOne modules and tools that are leve
* `p1_time_compat` is a module to ease support and migration of Erlang
time management function from Erlang R16/R17 to Erlang R18.
* `p1_http` is an http client which provides a common API for inets / lhttpc / ibrowse
* `p1_proxy_protocol` decodes HAproxy protocol (v1 and v2) headers.

If you have `rebar` binary, you can generate `p1_utils` documentation by running `rebar3 edoc`.
187 changes: 187 additions & 0 deletions src/p1_proxy_protocol.erl
@@ -0,0 +1,187 @@
%%%----------------------------------------------------------------------
%%% File : p1_proxy_protocol.erl
%%% Author : Paweł Chmielowski <pawel@process-one.net>
%%% Purpose :
%%% Created : 27 Nov 2018 by Paweł Chmielowski <pawel@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2021 ProcessOne, SARL. All Rights Reserved.
%%%
%%% Licensed under the Apache License, Version 2.0 (the "License");
%%% you may not use this file except in compliance with the License.
%%% You may obtain a copy of the License at
%%%
%%% http://www.apache.org/licenses/LICENSE-2.0
%%%
%%% Unless required by applicable law or agreed to in writing, software
%%% distributed under the License is distributed on an "AS IS" BASIS,
%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%%% See the License for the specific language governing permissions and
%%% limitations under the License.
%%%
%%%----------------------------------------------------------------------
-module(p1_proxy_protocol).
-author("pawel@process-one.net").

%% API
-export([decode/3]).

-spec decode(gen_tcp | ssl, inet:socket(), integer())
-> {{inet:ip_address(), inet:port_number()},
{inet:ip_address(), inet:port_number()}}
| {error, atom()}
| {undefined, undefined}.
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,
_/binary>> ->
{{{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,
_/binary>> ->
{{{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.

0 comments on commit 8a1da0c

Please sign in to comment.