%%%---------------------------------------------------------------------- %%% File : p1_proxy_protocol.erl %%% Author : Paweł Chmielowski %%% Purpose : %%% Created : 27 Nov 2018 by Paweł Chmielowski %%% %%% %%% Copyright (C) 2002-2025 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 <> -> {{{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 <> -> {{{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, <>, true, Timeout); {ok, Other} -> read_until_rn(SockMod, Socket, <>, 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, <>} -> read_until_rn(SockMod, Socket, <>, true, Timeout); {ok, Other} -> read_until_rn(SockMod, Socket, <>, false, Timeout); {error, _} = Err -> Err end.