12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007 |
- %% Copyright (c) 2018-2020, Loïc Hoguin <essen@ninenines.eu>
- %%
- %% Permission to use, copy, modify, and/or distribute this software for any
- %% purpose with or without fee is hereby granted, provided that the above
- %% copyright notice and this permission notice appear in all copies.
- %%
- %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- -module(ranch_proxy_header).
- -export([parse/1]).
- -export([header/1]).
- -export([header/2]).
- -export([to_connection_info/1]).
- -type proxy_info() :: #{
- %% Mandatory part.
- version := 1 | 2,
- command := local | proxy,
- transport_family => undefined | ipv4 | ipv6 | unix,
- transport_protocol => undefined | stream | dgram,
- %% Addresses.
- src_address => inet:ip_address() | binary(),
- src_port => inet:port_number(),
- dest_address => inet:ip_address() | binary(),
- dest_port => inet:port_number(),
- %% Extra TLV-encoded data.
- alpn => binary(), %% US-ASCII.
- authority => binary(), %% UTF-8.
- ssl => #{
- client := [ssl | cert_conn | cert_sess],
- verified := boolean(),
- version => binary(), %% US-ASCII.
- cipher => binary(), %% US-ASCII.
- sig_alg => binary(), %% US-ASCII.
- key_alg => binary(), %% US-ASCII.
- cn => binary() %% UTF-8.
- },
- netns => binary(), %% US-ASCII.
- %% Unknown TLVs can't be parsed so the raw data is given.
- raw_tlvs => [{0..255, binary()}]
- }.
- -export_type([proxy_info/0]).
- -type build_opts() :: #{
- checksum => crc32c,
- padding => pos_integer() %% >= 3
- }.
- %% Parsing.
- -spec parse(Data) -> {ok, proxy_info(), Data} | {error, atom()} when Data::binary().
- parse(<<"\r\n\r\n\0\r\nQUIT\n", Rest/bits>>) ->
- parse_v2(Rest);
- parse(<<"PROXY ", Rest/bits>>) ->
- parse_v1(Rest);
- parse(_) ->
- {error, 'The PROXY protocol header signature was not recognized. (PP 2.1, PP 2.2)'}.
- -ifdef(TEST).
- parse_unrecognized_header_test() ->
- {error, _} = parse(<<"GET / HTTP/1.1\r\n">>),
- ok.
- -endif.
- %% Human-readable header format (Version 1).
- parse_v1(<<"TCP4 ", Rest/bits>>) ->
- parse_v1(Rest, ipv4);
- parse_v1(<<"TCP6 ", Rest/bits>>) ->
- parse_v1(Rest, ipv6);
- parse_v1(<<"UNKNOWN\r\n", Rest/bits>>) ->
- {ok, #{
- version => 1,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- }, Rest};
- parse_v1(<<"UNKNOWN ", Rest0/bits>>) ->
- case binary:split(Rest0, <<"\r\n">>) of
- [_, Rest] ->
- {ok, #{
- version => 1,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- }, Rest};
- [_] ->
- {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'}
- end;
- parse_v1(_) ->
- {error, 'The INET protocol and family string was not recognized. (PP 2.1)'}.
- parse_v1(Rest0, Family) ->
- try
- {ok, SrcAddr, Rest1} = parse_ip(Rest0, Family),
- {ok, DestAddr, Rest2} = parse_ip(Rest1, Family),
- {ok, SrcPort, Rest3} = parse_port(Rest2, $\s),
- {ok, DestPort, Rest4} = parse_port(Rest3, $\r),
- <<"\n", Rest/bits>> = Rest4,
- {ok, #{
- version => 1,
- command => proxy,
- transport_family => Family,
- transport_protocol => stream,
- src_address => SrcAddr,
- src_port => SrcPort,
- dest_address => DestAddr,
- dest_port => DestPort
- }, Rest}
- catch
- throw:parse_ipv4_error ->
- {error, 'Failed to parse an IPv4 address in the PROXY protocol header line. (PP 2.1)'};
- throw:parse_ipv6_error ->
- {error, 'Failed to parse an IPv6 address in the PROXY protocol header line. (PP 2.1)'};
- throw:parse_port_error ->
- {error, 'Failed to parse a port number in the PROXY protocol header line. (PP 2.1)'};
- _:_ ->
- {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'}
- end.
- parse_ip(<<Addr:7/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:8/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:9/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:10/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:11/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:12/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:13/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:14/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(<<Addr:15/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
- parse_ip(Data, ipv6) ->
- [Addr, Rest] = binary:split(Data, <<$\s>>),
- parse_ipv6(Addr, Rest).
- parse_ipv4(Addr0, Rest) ->
- case inet:parse_ipv4strict_address(binary_to_list(Addr0)) of
- {ok, Addr} -> {ok, Addr, Rest};
- {error, einval} -> throw(parse_ipv4_error)
- end.
- parse_ipv6(Addr0, Rest) ->
- case inet:parse_ipv6strict_address(binary_to_list(Addr0)) of
- {ok, Addr} -> {ok, Addr, Rest};
- {error, einval} -> throw(parse_ipv6_error)
- end.
- parse_port(<<Port:1/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
- parse_port(<<Port:2/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
- parse_port(<<Port:3/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
- parse_port(<<Port:4/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
- parse_port(<<Port:5/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
- parse_port(Port0, Rest) ->
- try binary_to_integer(Port0) of
- Port when Port > 0, Port =< 65535 ->
- {ok, Port, Rest};
- _ ->
- throw(parse_port_error)
- catch _:_ ->
- throw(parse_port_error)
- end.
- -ifdef(TEST).
- parse_v1_test() ->
- %% Examples taken from the PROXY protocol header specification.
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := ipv4,
- transport_protocol := stream,
- src_address := {255, 255, 255, 255},
- src_port := 65535,
- dest_address := {255, 255, 255, 255},
- dest_port := 65535
- }, <<>>} = parse(<<"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := ipv6,
- transport_protocol := stream,
- src_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535},
- src_port := 65535,
- dest_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535},
- dest_port := 65535
- }, <<>>} = parse(<<"PROXY TCP6 "
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff "
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := undefined,
- transport_protocol := undefined
- }, <<>>} = parse(<<"PROXY UNKNOWN\r\n">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := undefined,
- transport_protocol := undefined
- }, <<>>} = parse(<<"PROXY UNKNOWN "
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff "
- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := ipv4,
- transport_protocol := stream,
- src_address := {192, 168, 0, 1},
- src_port := 56324,
- dest_address := {192, 168, 0, 11},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r\nHost: 192.168.0.11\r\n\r\n">>} = parse(<<
- "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n"
- "GET / HTTP/1.1\r\n"
- "Host: 192.168.0.11\r\n"
- "\r\n">>),
- %% Test cases taken from tomciopp/proxy_protocol.
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := ipv4,
- transport_protocol := stream,
- src_address := {192, 168, 0, 1},
- src_port := 56324,
- dest_address := {192, 168, 0, 11},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r">>} = parse(<<
- "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>),
- {error, _} = parse(<<"PROXY TCP4 192.1638.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>),
- {error, _} = parse(<<"PROXY TCP4 192.168.0.1 192.168.0.11 1111111 443\r\nGET / HTTP/1.1\r">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := ipv6,
- transport_protocol := stream,
- src_address := {8193, 3512, 0, 66, 0, 35374, 880, 29492},
- src_port := 4124,
- dest_address := {8193, 3512, 0, 66, 0, 35374, 880, 29493},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY TCP6 "
- "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
- "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>),
- {error, _} = parse(<<"PROXY TCP6 "
- "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
- "2001:0db8:00;0:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>),
- {error, _} = parse(<<"PROXY TCP6 "
- "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
- "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 foo\r\nGET / HTTP/1.1\r">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := undefined,
- transport_protocol := undefined
- }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN 4124 443\r\nGET / HTTP/1.1\r">>),
- {ok, #{
- version := 1,
- command := proxy,
- transport_family := undefined,
- transport_protocol := undefined
- }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN\r\nGET / HTTP/1.1\r">>),
- ok.
- -endif.
- %% Binary header format (version 2).
- %% LOCAL.
- parse_v2(<<2:4, 0:4, _:8, Len:16, Rest0/bits>>) ->
- case Rest0 of
- <<_:Len/binary, Rest/bits>> ->
- {ok, #{
- version => 2,
- command => local
- }, Rest};
- _ ->
- {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'}
- end;
- %% PROXY.
- parse_v2(<<2:4, 1:4, Family:4, Protocol:4, Len:16, Rest/bits>>)
- when Family =< 3, Protocol =< 2 ->
- case Rest of
- <<Header:Len/binary, _/bits>> ->
- parse_v2(Rest, Len, parse_family(Family), parse_protocol(Protocol),
- <<Family:4, Protocol:4, Len:16, Header:Len/binary>>);
- _ ->
- {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'}
- end;
- %% Errors.
- parse_v2(<<Version:4, _/bits>>) when Version =/= 2 ->
- {error, 'Invalid version in the PROXY protocol binary header. (PP 2.2)'};
- parse_v2(<<_:4, Command:4, _/bits>>) when Command > 1 ->
- {error, 'Invalid command in the PROXY protocol binary header. (PP 2.2)'};
- parse_v2(<<_:8, Family:4, _/bits>>) when Family > 3 ->
- {error, 'Invalid address family in the PROXY protocol binary header. (PP 2.2)'};
- parse_v2(<<_:12, Protocol:4, _/bits>>) when Protocol > 2 ->
- {error, 'Invalid transport protocol in the PROXY protocol binary header. (PP 2.2)'}.
- parse_family(0) -> undefined;
- parse_family(1) -> ipv4;
- parse_family(2) -> ipv6;
- parse_family(3) -> unix.
- parse_protocol(0) -> undefined;
- parse_protocol(1) -> stream;
- parse_protocol(2) -> dgram.
- parse_v2(Data, Len, Family, Protocol, _)
- when Family =:= undefined; Protocol =:= undefined ->
- <<_:Len/binary, Rest/bits>> = Data,
- {ok, #{
- version => 2,
- command => proxy,
- %% In case only one value was undefined, we set both explicitly.
- %% It doesn't make sense to have only one known value.
- transport_family => undefined,
- transport_protocol => undefined
- }, Rest};
- parse_v2(<<
- S1, S2, S3, S4,
- D1, D2, D3, D4,
- SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv4, Protocol, Header)
- when Len >= 12 ->
- parse_tlv(Rest, Len - 12, #{
- version => 2,
- command => proxy,
- transport_family => Family,
- transport_protocol => Protocol,
- src_address => {S1, S2, S3, S4},
- src_port => SrcPort,
- dest_address => {D1, D2, D3, D4},
- dest_port => DestPort
- }, Header);
- parse_v2(<<
- S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16,
- D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16,
- SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv6, Protocol, Header)
- when Len >= 36 ->
- parse_tlv(Rest, Len - 36, #{
- version => 2,
- command => proxy,
- transport_family => Family,
- transport_protocol => Protocol,
- src_address => {S1, S2, S3, S4, S5, S6, S7, S8},
- src_port => SrcPort,
- dest_address => {D1, D2, D3, D4, D5, D6, D7, D8},
- dest_port => DestPort
- }, Header);
- parse_v2(<<SrcAddr0:108/binary, DestAddr0:108/binary, Rest/bits>>,
- Len, Family=unix, Protocol, Header)
- when Len >= 216 ->
- try
- [SrcAddr, _] = binary:split(SrcAddr0, <<0>>),
- true = byte_size(SrcAddr) > 0,
- [DestAddr, _] = binary:split(DestAddr0, <<0>>),
- true = byte_size(DestAddr) > 0,
- parse_tlv(Rest, Len - 216, #{
- version => 2,
- command => proxy,
- transport_family => Family,
- transport_protocol => Protocol,
- src_address => SrcAddr,
- dest_address => DestAddr
- }, Header)
- catch _:_ ->
- {error, 'Invalid UNIX address in PROXY protocol binary header. (PP 2.2)'}
- end;
- parse_v2(_, _, _, _, _) ->
- {error, 'Invalid length in the PROXY protocol binary header. (PP 2.2)'}.
- -ifdef(TEST).
- parse_v2_test() ->
- %% Test cases taken from tomciopp/proxy_protocol.
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := ipv4,
- transport_protocol := stream,
- src_address := {127, 0, 0, 1},
- src_port := 444,
- dest_address := {192, 168, 0, 1},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
- 33, %% Version and command.
- 17, %% Family and protocol.
- 0, 12, %% Length.
- 127, 0, 0, 1, %% Source address.
- 192, 168, 0, 1, %% Destination address.
- 1, 188, %% Source port.
- 1, 187, %% Destination port.
- "GET / HTTP/1.1\r\n">>),
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := ipv4,
- transport_protocol := dgram,
- src_address := {127, 0, 0, 1},
- src_port := 444,
- dest_address := {192, 168, 0, 1},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
- 33, %% Version and command.
- 18, %% Family and protocol.
- 0, 12, %% Length.
- 127, 0, 0, 1, %% Source address.
- 192, 168, 0, 1, %% Destination address.
- 1, 188, %% Source port.
- 1, 187, %% Destination port.
- "GET / HTTP/1.1\r\n">>),
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := ipv6,
- transport_protocol := stream,
- src_address := {5532, 4240, 1, 0, 0, 0, 0, 0},
- src_port := 444,
- dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
- 33, %% Version and command.
- 33, %% Family and protocol.
- 0, 36, %% Length.
- 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address.
- 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address.
- 1, 188, %% Source port.
- 1, 187, %% Destination port.
- "GET / HTTP/1.1\r\n">>),
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := ipv6,
- transport_protocol := dgram,
- src_address := {5532, 4240, 1, 0, 0, 0, 0, 0},
- src_port := 444,
- dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0},
- dest_port := 443
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
- 33, %% Version and command.
- 34, %% Family and protocol.
- 0, 36, %% Length.
- 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address.
- 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address.
- 1, 188, %% Source port.
- 1, 187, %% Destination port.
- "GET / HTTP/1.1\r\n">>),
- Path = <<"/var/pgsql_sock">>,
- Len = byte_size(Path),
- Padding = 8 * (108 - Len),
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := unix,
- transport_protocol := stream,
- src_address := Path,
- dest_address := Path
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10,
- 33,
- 49,
- 0, 216,
- Path/binary, 0:Padding,
- Path/binary, 0:Padding,
- "GET / HTTP/1.1\r\n">>),
- {ok, #{
- version := 2,
- command := proxy,
- transport_family := unix,
- transport_protocol := dgram,
- src_address := Path,
- dest_address := Path
- }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10,
- 33,
- 50,
- 0, 216,
- Path/binary, 0:Padding,
- Path/binary, 0:Padding,
- "GET / HTTP/1.1\r\n">>),
- ok.
- parse_v2_regression_test() ->
- %% Real packet received from AWS. We confirm that the CRC32C
- %% check succeeds only (in other words that ok is returned).
- {ok, _, <<>>} = parse(<<
- 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 84,
- 172, 31, 7, 113, 172, 31, 10, 31, 200, 242, 0, 80, 3, 0, 4,
- 232, 214, 137, 45, 234, 0, 23, 1, 118, 112, 99, 101, 45, 48,
- 56, 100, 50, 98, 102, 49, 53, 102, 97, 99, 53, 48, 48, 49, 99,
- 57, 4, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>),
- ok.
- -endif.
- parse_tlv(Rest, 0, Info, _) ->
- {ok, Info, Rest};
- %% PP2_TYPE_ALPN.
- parse_tlv(<<16#1, TLVLen:16, ALPN:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
- parse_tlv(Rest, Len - TLVLen - 3, Info#{alpn => ALPN}, Header);
- %% PP2_TYPE_AUTHORITY.
- parse_tlv(<<16#2, TLVLen:16, Authority:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
- parse_tlv(Rest, Len - TLVLen - 3, Info#{authority => Authority}, Header);
- %% PP2_TYPE_CRC32C.
- parse_tlv(<<16#3, TLVLen:16, CRC32C:32, Rest/bits>>, Len0, Info, Header) when TLVLen =:= 4 ->
- Len = Len0 - TLVLen - 3,
- BeforeLen = byte_size(Header) - Len - TLVLen,
- <<Before:BeforeLen/binary, _:32, After:Len/binary>> = Header,
- %% The initial CRC is ranch_crc32c:crc32c(<<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>).
- case ranch_crc32c:crc32c(2900412422, [Before, <<0:32>>, After]) of
- CRC32C ->
- parse_tlv(Rest, Len, Info, Header);
- _ ->
- {error, 'Failed CRC32C verification in PROXY protocol binary header. (PP 2.2)'}
- end;
- %% PP2_TYPE_NOOP.
- parse_tlv(<<16#4, TLVLen:16, _:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
- parse_tlv(Rest, Len - TLVLen - 3, Info, Header);
- %% PP2_TYPE_SSL.
- parse_tlv(<<16#20, TLVLen:16, Client, Verify:32, Rest0/bits>>, Len, Info, Header) ->
- SubsLen = TLVLen - 5,
- case Rest0 of
- <<Subs:SubsLen/binary, Rest/bits>> ->
- SSL0 = #{
- client => parse_client(<<Client>>),
- verified => Verify =:= 0
- },
- case parse_ssl_tlv(Subs, SubsLen, SSL0) of
- {ok, SSL, <<>>} ->
- parse_tlv(Rest, Len - TLVLen - 3, Info#{ssl => SSL}, Header);
- Error={error, _} ->
- Error
- end;
- _ ->
- {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}
- end;
- %% PP2_TYPE_NETNS.
- parse_tlv(<<16#30, TLVLen:16, NetNS:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
- parse_tlv(Rest, Len - TLVLen - 3, Info#{netns => NetNS}, Header);
- %% Unknown TLV.
- parse_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
- RawTLVs = maps:get(raw_tlvs, Info, []),
- parse_tlv(Rest, Len - TLVLen - 3, Info#{raw_tlvs => [{TLVType, TLVValue}|RawTLVs]}, Header);
- %% Invalid TLV length.
- parse_tlv(_, _, _, _) ->
- {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}.
- parse_client(<<_:5, ClientCertSess:1, ClientCertConn:1, ClientSSL:1>>) ->
- Client0 = case ClientCertSess of
- 0 -> [];
- 1 -> [cert_sess]
- end,
- Client1 = case ClientCertConn of
- 0 -> Client0;
- 1 -> [cert_conn|Client0]
- end,
- case ClientSSL of
- 0 -> Client1;
- 1 -> [ssl|Client1]
- end.
- parse_ssl_tlv(Rest, 0, Info) ->
- {ok, Info, Rest};
- %% Valid TLVs.
- parse_ssl_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info) ->
- case ssl_subtype(TLVType) of
- undefined ->
- {error, 'Invalid TLV subtype for PP2_TYPE_SSL in PROXY protocol binary header. (PP 2.2)'};
- Type ->
- parse_ssl_tlv(Rest, Len - TLVLen - 3, Info#{Type => TLVValue})
- end;
- %% Invalid TLV length.
- parse_ssl_tlv(_, _, _) ->
- {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}.
- ssl_subtype(16#21) -> version;
- ssl_subtype(16#22) -> cn;
- ssl_subtype(16#23) -> cipher;
- ssl_subtype(16#24) -> sig_alg;
- ssl_subtype(16#25) -> key_alg;
- ssl_subtype(_) -> undefined.
- %% Building.
- -spec header(proxy_info()) -> iodata().
- header(ProxyInfo) ->
- header(ProxyInfo, #{}).
- -spec header(proxy_info(), build_opts()) -> iodata().
- header(#{version := 2, command := local}, _) ->
- <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 0:28>>;
- header(#{version := 2, command := proxy,
- transport_family := Family,
- transport_protocol := Protocol}, _)
- when Family =:= undefined; Protocol =:= undefined ->
- <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4, 0:24>>;
- header(ProxyInfo=#{version := 2, command := proxy,
- transport_family := Family,
- transport_protocol := Protocol}, Opts) ->
- Addresses = addresses(ProxyInfo),
- TLVs = tlvs(ProxyInfo, Opts),
- ExtraLen = case Opts of
- #{checksum := crc32c} -> 7;
- _ -> 0
- end,
- Len = iolist_size(Addresses) + iolist_size(TLVs) + ExtraLen,
- Header = [
- <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>,
- <<(family(Family)):4, (protocol(Protocol)):4>>,
- <<Len:16>>,
- Addresses,
- TLVs
- ],
- case Opts of
- #{checksum := crc32c} ->
- CRC32C = ranch_crc32c:crc32c([Header, <<16#3, 4:16, 0:32>>]),
- [Header, <<16#3, 4:16, CRC32C:32>>];
- _ ->
- Header
- end;
- header(#{version := 1, command := proxy,
- transport_family := undefined,
- transport_protocol := undefined}, _) ->
- <<"PROXY UNKNOWN\r\n">>;
- header(#{version := 1, command := proxy,
- transport_family := Family0,
- transport_protocol := stream,
- src_address := SrcAddress, src_port := SrcPort,
- dest_address := DestAddress, dest_port := DestPort}, _)
- when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
- [
- <<"PROXY ">>,
- case Family0 of
- ipv4 when tuple_size(SrcAddress) =:= 4, tuple_size(DestAddress) =:= 4 ->
- [<<"TCP4 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)];
- ipv6 when tuple_size(SrcAddress) =:= 8, tuple_size(DestAddress) =:= 8 ->
- [<<"TCP6 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)]
- end,
- $\s,
- integer_to_binary(SrcPort),
- $\s,
- integer_to_binary(DestPort),
- $\r, $\n
- ].
- family(ipv4) -> 1;
- family(ipv6) -> 2;
- family(unix) -> 3.
- protocol(stream) -> 1;
- protocol(dgram) -> 2.
- addresses(#{transport_family := ipv4,
- src_address := {S1, S2, S3, S4}, src_port := SrcPort,
- dest_address := {D1, D2, D3, D4}, dest_port := DestPort})
- when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
- <<S1, S2, S3, S4, D1, D2, D3, D4, SrcPort:16, DestPort:16>>;
- addresses(#{transport_family := ipv6,
- src_address := {S1, S2, S3, S4, S5, S6, S7, S8}, src_port := SrcPort,
- dest_address := {D1, D2, D3, D4, D5, D6, D7, D8}, dest_port := DestPort})
- when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
- <<
- S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16,
- D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16,
- SrcPort:16, DestPort:16
- >>;
- addresses(#{transport_family := unix,
- src_address := SrcAddress, dest_address := DestAddress})
- when byte_size(SrcAddress) =< 108, byte_size(DestAddress) =< 108 ->
- SrcPadding = 8 * (108 - byte_size(SrcAddress)),
- DestPadding = 8 * (108 - byte_size(DestAddress)),
- <<
- SrcAddress/binary, 0:SrcPadding,
- DestAddress/binary, 0:DestPadding
- >>.
- tlvs(ProxyInfo, Opts) ->
- [
- binary_tlv(ProxyInfo, alpn, 16#1),
- binary_tlv(ProxyInfo, authority, 16#2),
- ssl_tlv(ProxyInfo),
- binary_tlv(ProxyInfo, netns, 16#30),
- raw_tlvs(ProxyInfo),
- noop_tlv(Opts)
- ].
- binary_tlv(Info, Key, Type) ->
- case Info of
- #{Key := Bin} ->
- Len = byte_size(Bin),
- <<Type, Len:16, Bin/binary>>;
- _ ->
- <<>>
- end.
- noop_tlv(#{padding := Len0}) when Len0 >= 3 ->
- Len = Len0 - 3,
- <<16#4, Len:16, 0:Len/unit:8>>;
- noop_tlv(_) ->
- <<>>.
- ssl_tlv(#{ssl := Info=#{client := Client0, verified := Verify0}}) ->
- Client = client(Client0, 0),
- Verify = if
- Verify0 -> 0;
- not Verify0 -> 1
- end,
- TLVs = [
- binary_tlv(Info, version, 16#21),
- binary_tlv(Info, cn, 16#22),
- binary_tlv(Info, cipher, 16#23),
- binary_tlv(Info, sig_alg, 16#24),
- binary_tlv(Info, key_alg, 16#25)
- ],
- Len = iolist_size(TLVs) + 5,
- [<<16#20, Len:16, Client, Verify:32>>, TLVs];
- ssl_tlv(_) ->
- <<>>.
- client([], Client) -> Client;
- client([ssl|Tail], Client) -> client(Tail, Client bor 16#1);
- client([cert_conn|Tail], Client) -> client(Tail, Client bor 16#2);
- client([cert_sess|Tail], Client) -> client(Tail, Client bor 16#4).
- raw_tlvs(Info) ->
- [begin
- Len = byte_size(Bin),
- <<Type, Len:16, Bin/binary>>
- end || {Type, Bin} <- maps:get(raw_tlvs, Info, [])].
- -ifdef(TEST).
- v1_test() ->
- Test1 = #{
- version => 1,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- },
- {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
- Test2 = #{
- version => 1,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
- Test3 = #{
- version => 1,
- command => proxy,
- transport_family => ipv6,
- transport_protocol => stream,
- src_address => {1, 2, 3, 4, 5, 6, 7, 8},
- src_port => 1234,
- dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8},
- dest_port => 23456
- },
- {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
- ok.
- v2_test() ->
- Test0 = #{
- version => 2,
- command => local
- },
- {ok, Test0, <<>>} = parse(iolist_to_binary(header(Test0))),
- Test1 = #{
- version => 2,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- },
- {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
- Test2 = #{
- version => 2,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
- Test3 = #{
- version => 2,
- command => proxy,
- transport_family => ipv6,
- transport_protocol => stream,
- src_address => {1, 2, 3, 4, 5, 6, 7, 8},
- src_port => 1234,
- dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8},
- dest_port => 23456
- },
- {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
- Test4 = #{
- version => 2,
- command => proxy,
- transport_family => unix,
- transport_protocol => dgram,
- src_address => <<"/run/source.sock">>,
- dest_address => <<"/run/destination.sock">>
- },
- {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))),
- ok.
- v2_tlvs_test() ->
- Common = #{
- version => 2,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- Test1 = Common#{alpn => <<"h2">>},
- {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
- Test2 = Common#{authority => <<"internal.example.org">>},
- {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
- Test3 = Common#{netns => <<"/var/run/netns/example">>},
- {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
- Test4 = Common#{ssl => #{
- client => [ssl, cert_conn, cert_sess],
- verified => true,
- version => <<"TLSv1.3">>,
- cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
- sig_alg => <<"SHA256">>,
- key_alg => <<"RSA2048">>,
- cn => <<"example.com">>
- }},
- {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))),
- %% Note that the raw_tlvs order is not relevant and therefore
- %% the parser does not reverse the list it builds.
- Test5In = Common#{raw_tlvs => RawTLVs=[
- %% The only custom TLV I am aware of is defined at:
- %% https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol
- {16#ea, <<16#1, "instance-id">>},
- %% This TLV is entirely fictional.
- {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>}
- ]},
- Test5Out = Test5In#{raw_tlvs => lists:reverse(RawTLVs)},
- {ok, Test5Out, <<>>} = parse(iolist_to_binary(header(Test5In))),
- ok.
- v2_checksum_test() ->
- Test = #{
- version => 2,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{checksum => crc32c}))),
- ok.
- v2_padding_test() ->
- Test = #{
- version => 2,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))),
- ok.
- -endif.
- %% Helper to convert proxy_info() to ssl:connection_info().
- %%
- %% Because there isn't a lot of fields common to both types
- %% this only ends up returning the keys protocol, selected_cipher_suite
- %% and sni_hostname *at most*.
- -spec to_connection_info(proxy_info()) -> ssl:connection_info().
- to_connection_info(ProxyInfo=#{ssl := SSL}) ->
- ConnInfo0 = case ProxyInfo of
- #{authority := Authority} ->
- [{sni_hostname, Authority}];
- _ ->
- []
- end,
- ConnInfo = case SSL of
- #{cipher := Cipher} ->
- case ssl:str_to_suite(binary_to_list(Cipher)) of
- {error, {not_recognized, _}} ->
- ConnInfo0;
- CipherInfo ->
- [{selected_cipher_suite, CipherInfo}|ConnInfo0]
- end;
- _ ->
- ConnInfo0
- end,
- %% https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html
- case SSL of
- #{version := <<"TLSv1.3">>} -> [{protocol, 'tlsv1.3'}|ConnInfo];
- #{version := <<"TLSv1.2">>} -> [{protocol, 'tlsv1.2'}|ConnInfo];
- #{version := <<"TLSv1.1">>} -> [{protocol, 'tlsv1.1'}|ConnInfo];
- #{version := <<"TLSv1">>} -> [{protocol, tlsv1}|ConnInfo];
- #{version := <<"SSLv3">>} -> [{protocol, sslv3}|ConnInfo];
- #{version := <<"SSLv2">>} -> [{protocol, sslv2}|ConnInfo];
- %% <<"unknown">>, unsupported or missing version.
- _ -> ConnInfo
- end;
- %% No SSL/TLS information available.
- to_connection_info(_) ->
- [].
- -ifdef(TEST).
- to_connection_info_test() ->
- Common = #{
- version => 2,
- command => proxy,
- transport_family => ipv4,
- transport_protocol => stream,
- src_address => {127, 0, 0, 1},
- src_port => 1234,
- dest_address => {10, 11, 12, 13},
- dest_port => 23456
- },
- %% Version 1.
- [] = to_connection_info(#{
- version => 1,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- }),
- [] = to_connection_info(Common#{version => 1}),
- %% Version 2, no ssl data.
- [] = to_connection_info(#{
- version => 2,
- command => local
- }),
- [] = to_connection_info(#{
- version => 2,
- command => proxy,
- transport_family => undefined,
- transport_protocol => undefined
- }),
- [] = to_connection_info(Common),
- [] = to_connection_info(#{
- version => 2,
- command => proxy,
- transport_family => unix,
- transport_protocol => dgram,
- src_address => <<"/run/source.sock">>,
- dest_address => <<"/run/destination.sock">>
- }),
- [] = to_connection_info(Common#{netns => <<"/var/run/netns/example">>}),
- [] = to_connection_info(Common#{raw_tlvs => [
- {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>}
- ]}),
- %% Version 2, with ssl-related data.
- [] = to_connection_info(Common#{alpn => <<"h2">>}),
- %% The authority alone is not enough to deduce that this is SNI.
- [] = to_connection_info(Common#{authority => <<"internal.example.org">>}),
- [
- {protocol, 'tlsv1.3'},
- {selected_cipher_suite, #{
- cipher := aes_128_gcm,
- key_exchange := ecdhe_rsa,
- mac := aead,
- prf := sha256
- }}
- ] = to_connection_info(Common#{ssl => #{
- client => [ssl, cert_conn, cert_sess],
- verified => true,
- version => <<"TLSv1.3">>,
- cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
- sig_alg => <<"SHA256">>,
- key_alg => <<"RSA2048">>,
- cn => <<"example.com">>
- }}),
- [
- {protocol, 'tlsv1.3'},
- {selected_cipher_suite, #{
- cipher := aes_128_gcm,
- key_exchange := ecdhe_rsa,
- mac := aead,
- prf := sha256
- }},
- {sni_hostname, <<"internal.example.org">>}
- ] = to_connection_info(Common#{authority => <<"internal.example.org">>, ssl => #{
- client => [ssl, cert_conn, cert_sess],
- verified => true,
- version => <<"TLSv1.3">>,
- cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
- sig_alg => <<"SHA256">>,
- key_alg => <<"RSA2048">>,
- cn => <<"example.com">>
- }}),
- ok.
- -endif.
|