ranch_proxy_header.erl (28070B)
1 %% Copyright (c) 2018, Loïc Hoguin <essen@ninenines.eu> 2 %% 3 %% Permission to use, copy, modify, and/or distribute this software for any 4 %% purpose with or without fee is hereby granted, provided that the above 5 %% copyright notice and this permission notice appear in all copies. 6 %% 7 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 -module(ranch_proxy_header). 16 17 -export([parse/1]). 18 -export([header/1]). 19 -export([header/2]). 20 21 -type proxy_info() :: #{ 22 %% Mandatory part. 23 version := 1 | 2, 24 command := local | proxy, 25 transport_family => undefined | ipv4 | ipv6 | unix, 26 transport_protocol => undefined | stream | dgram, 27 %% Addresses. 28 src_address => inet:ip_address() | binary(), 29 src_port => inet:port_number(), 30 dest_address => inet:ip_address() | binary(), 31 dest_port => inet:port_number(), 32 %% Extra TLV-encoded data. 33 alpn => binary(), %% US-ASCII. 34 authority => binary(), %% UTF-8. 35 ssl => #{ 36 client := [ssl | cert_conn | cert_sess], 37 verified := boolean(), 38 version => binary(), %% US-ASCII. 39 cipher => binary(), %% US-ASCII. 40 sig_alg => binary(), %% US-ASCII. 41 key_alg => binary(), %% US-ASCII. 42 cn => binary() %% UTF-8. 43 }, 44 netns => binary(), %% US-ASCII. 45 %% Unknown TLVs can't be parsed so the raw data is given. 46 raw_tlvs => [{0..255, binary()}] 47 }. 48 -export_type([proxy_info/0]). 49 50 -type build_opts() :: #{ 51 checksum => crc32c, 52 padding => pos_integer() %% >= 3 53 }. 54 55 %% Parsing. 56 57 -spec parse(Data) -> {ok, proxy_info(), Data} | {error, atom()} when Data::binary(). 58 parse(<<"\r\n\r\n\0\r\nQUIT\n", Rest/bits>>) -> 59 parse_v2(Rest); 60 parse(<<"PROXY ", Rest/bits>>) -> 61 parse_v1(Rest); 62 parse(_) -> 63 {error, 'The PROXY protocol header signature was not recognized. (PP 2.1, PP 2.2)'}. 64 65 -ifdef(TEST). 66 parse_unrecognized_header_test() -> 67 {error, _} = parse(<<"GET / HTTP/1.1\r\n">>), 68 ok. 69 -endif. 70 71 %% Human-readable header format (Version 1). 72 parse_v1(<<"TCP4 ", Rest/bits>>) -> 73 parse_v1(Rest, ipv4); 74 parse_v1(<<"TCP6 ", Rest/bits>>) -> 75 parse_v1(Rest, ipv6); 76 parse_v1(<<"UNKNOWN\r\n", Rest/bits>>) -> 77 {ok, #{ 78 version => 1, 79 command => proxy, 80 transport_family => undefined, 81 transport_protocol => undefined 82 }, Rest}; 83 parse_v1(<<"UNKNOWN ", Rest0/bits>>) -> 84 case binary:split(Rest0, <<"\r\n">>) of 85 [_, Rest] -> 86 {ok, #{ 87 version => 1, 88 command => proxy, 89 transport_family => undefined, 90 transport_protocol => undefined 91 }, Rest}; 92 [_] -> 93 {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'} 94 end; 95 parse_v1(_) -> 96 {error, 'The INET protocol and family string was not recognized. (PP 2.1)'}. 97 98 parse_v1(Rest0, Family) -> 99 try 100 {ok, SrcAddr, Rest1} = parse_ip(Rest0, Family), 101 {ok, DestAddr, Rest2} = parse_ip(Rest1, Family), 102 {ok, SrcPort, Rest3} = parse_port(Rest2, $\s), 103 {ok, DestPort, Rest4} = parse_port(Rest3, $\r), 104 <<"\n", Rest/bits>> = Rest4, 105 {ok, #{ 106 version => 1, 107 command => proxy, 108 transport_family => Family, 109 transport_protocol => stream, 110 src_address => SrcAddr, 111 src_port => SrcPort, 112 dest_address => DestAddr, 113 dest_port => DestPort 114 }, Rest} 115 catch 116 throw:parse_ipv4_error -> 117 {error, 'Failed to parse an IPv4 address in the PROXY protocol header line. (PP 2.1)'}; 118 throw:parse_ipv6_error -> 119 {error, 'Failed to parse an IPv6 address in the PROXY protocol header line. (PP 2.1)'}; 120 throw:parse_port_error -> 121 {error, 'Failed to parse a port number in the PROXY protocol header line. (PP 2.1)'}; 122 _:_ -> 123 {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'} 124 end. 125 126 parse_ip(<<Addr:7/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 127 parse_ip(<<Addr:8/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 128 parse_ip(<<Addr:9/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 129 parse_ip(<<Addr:10/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 130 parse_ip(<<Addr:11/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 131 parse_ip(<<Addr:12/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 132 parse_ip(<<Addr:13/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 133 parse_ip(<<Addr:14/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 134 parse_ip(<<Addr:15/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest); 135 parse_ip(Data, ipv6) -> 136 [Addr, Rest] = binary:split(Data, <<$\s>>), 137 parse_ipv6(Addr, Rest). 138 139 parse_ipv4(Addr0, Rest) -> 140 case inet:parse_ipv4strict_address(binary_to_list(Addr0)) of 141 {ok, Addr} -> {ok, Addr, Rest}; 142 {error, einval} -> throw(parse_ipv4_error) 143 end. 144 145 parse_ipv6(Addr0, Rest) -> 146 case inet:parse_ipv6strict_address(binary_to_list(Addr0)) of 147 {ok, Addr} -> {ok, Addr, Rest}; 148 {error, einval} -> throw(parse_ipv6_error) 149 end. 150 151 parse_port(<<Port:1/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest); 152 parse_port(<<Port:2/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest); 153 parse_port(<<Port:3/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest); 154 parse_port(<<Port:4/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest); 155 parse_port(<<Port:5/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest); 156 157 parse_port(Port0, Rest) -> 158 try binary_to_integer(Port0) of 159 Port when Port > 0, Port =< 65535 -> 160 {ok, Port, Rest}; 161 _ -> 162 throw(parse_port_error) 163 catch _:_ -> 164 throw(parse_port_error) 165 end. 166 167 -ifdef(TEST). 168 parse_v1_test() -> 169 %% Examples taken from the PROXY protocol header specification. 170 {ok, #{ 171 version := 1, 172 command := proxy, 173 transport_family := ipv4, 174 transport_protocol := stream, 175 src_address := {255, 255, 255, 255}, 176 src_port := 65535, 177 dest_address := {255, 255, 255, 255}, 178 dest_port := 65535 179 }, <<>>} = parse(<<"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n">>), 180 {ok, #{ 181 version := 1, 182 command := proxy, 183 transport_family := ipv6, 184 transport_protocol := stream, 185 src_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, 186 src_port := 65535, 187 dest_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535}, 188 dest_port := 65535 189 }, <<>>} = parse(<<"PROXY TCP6 " 190 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff " 191 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>), 192 {ok, #{ 193 version := 1, 194 command := proxy, 195 transport_family := undefined, 196 transport_protocol := undefined 197 }, <<>>} = parse(<<"PROXY UNKNOWN\r\n">>), 198 {ok, #{ 199 version := 1, 200 command := proxy, 201 transport_family := undefined, 202 transport_protocol := undefined 203 }, <<>>} = parse(<<"PROXY UNKNOWN " 204 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff " 205 "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>), 206 {ok, #{ 207 version := 1, 208 command := proxy, 209 transport_family := ipv4, 210 transport_protocol := stream, 211 src_address := {192, 168, 0, 1}, 212 src_port := 56324, 213 dest_address := {192, 168, 0, 11}, 214 dest_port := 443 215 }, <<"GET / HTTP/1.1\r\nHost: 192.168.0.11\r\n\r\n">>} = parse(<< 216 "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n" 217 "GET / HTTP/1.1\r\n" 218 "Host: 192.168.0.11\r\n" 219 "\r\n">>), 220 %% Test cases taken from tomciopp/proxy_protocol. 221 {ok, #{ 222 version := 1, 223 command := proxy, 224 transport_family := ipv4, 225 transport_protocol := stream, 226 src_address := {192, 168, 0, 1}, 227 src_port := 56324, 228 dest_address := {192, 168, 0, 11}, 229 dest_port := 443 230 }, <<"GET / HTTP/1.1\r">>} = parse(<< 231 "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>), 232 {error, _} = parse(<<"PROXY TCP4 192.1638.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>), 233 {error, _} = parse(<<"PROXY TCP4 192.168.0.1 192.168.0.11 1111111 443\r\nGET / HTTP/1.1\r">>), 234 {ok, #{ 235 version := 1, 236 command := proxy, 237 transport_family := ipv6, 238 transport_protocol := stream, 239 src_address := {8193, 3512, 0, 66, 0, 35374, 880, 29492}, 240 src_port := 4124, 241 dest_address := {8193, 3512, 0, 66, 0, 35374, 880, 29493}, 242 dest_port := 443 243 }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY TCP6 " 244 "2001:0db8:0000:0042:0000:8a2e:0370:7334 " 245 "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>), 246 {error, _} = parse(<<"PROXY TCP6 " 247 "2001:0db8:0000:0042:0000:8a2e:0370:7334 " 248 "2001:0db8:00;0:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>), 249 {error, _} = parse(<<"PROXY TCP6 " 250 "2001:0db8:0000:0042:0000:8a2e:0370:7334 " 251 "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 foo\r\nGET / HTTP/1.1\r">>), 252 {ok, #{ 253 version := 1, 254 command := proxy, 255 transport_family := undefined, 256 transport_protocol := undefined 257 }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN 4124 443\r\nGET / HTTP/1.1\r">>), 258 {ok, #{ 259 version := 1, 260 command := proxy, 261 transport_family := undefined, 262 transport_protocol := undefined 263 }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN\r\nGET / HTTP/1.1\r">>), 264 ok. 265 -endif. 266 267 %% Binary header format (version 2). 268 269 %% LOCAL. 270 parse_v2(<<2:4, 0:4, _:8, Len:16, Rest0/bits>>) -> 271 case Rest0 of 272 <<_:Len/binary, Rest/bits>> -> 273 {ok, #{ 274 version => 2, 275 command => local 276 }, Rest}; 277 _ -> 278 {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'} 279 end; 280 %% PROXY. 281 parse_v2(<<2:4, 1:4, Family:4, Protocol:4, Len:16, Rest/bits>>) 282 when Family =< 3, Protocol =< 2 -> 283 case Rest of 284 <<Header:Len/binary, _/bits>> -> 285 parse_v2(Rest, Len, parse_family(Family), parse_protocol(Protocol), 286 <<Family:4, Protocol:4, Len:16, Header:Len/binary>>); 287 _ -> 288 {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'} 289 end; 290 %% Errors. 291 parse_v2(<<Version:4, _/bits>>) when Version =/= 2 -> 292 {error, 'Invalid version in the PROXY protocol binary header. (PP 2.2)'}; 293 parse_v2(<<_:4, Command:4, _/bits>>) when Command > 1 -> 294 {error, 'Invalid command in the PROXY protocol binary header. (PP 2.2)'}; 295 parse_v2(<<_:8, Family:4, _/bits>>) when Family > 3 -> 296 {error, 'Invalid address family in the PROXY protocol binary header. (PP 2.2)'}; 297 parse_v2(<<_:12, Protocol:4, _/bits>>) when Protocol > 2 -> 298 {error, 'Invalid transport protocol in the PROXY protocol binary header. (PP 2.2)'}. 299 300 parse_family(0) -> undefined; 301 parse_family(1) -> ipv4; 302 parse_family(2) -> ipv6; 303 parse_family(3) -> unix. 304 305 parse_protocol(0) -> undefined; 306 parse_protocol(1) -> stream; 307 parse_protocol(2) -> dgram. 308 309 parse_v2(Data, Len, Family, Protocol, _) 310 when Family =:= undefined; Protocol =:= undefined -> 311 <<_:Len/binary, Rest/bits>> = Data, 312 {ok, #{ 313 version => 2, 314 command => proxy, 315 %% In case only one value was undefined, we set both explicitly. 316 %% It doesn't make sense to have only one known value. 317 transport_family => undefined, 318 transport_protocol => undefined 319 }, Rest}; 320 parse_v2(<< 321 S1, S2, S3, S4, 322 D1, D2, D3, D4, 323 SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv4, Protocol, Header) 324 when Len >= 12 -> 325 parse_tlv(Rest, Len - 12, #{ 326 version => 2, 327 command => proxy, 328 transport_family => Family, 329 transport_protocol => Protocol, 330 src_address => {S1, S2, S3, S4}, 331 src_port => SrcPort, 332 dest_address => {D1, D2, D3, D4}, 333 dest_port => DestPort 334 }, Header); 335 parse_v2(<< 336 S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16, 337 D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16, 338 SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv6, Protocol, Header) 339 when Len >= 36 -> 340 parse_tlv(Rest, Len - 36, #{ 341 version => 2, 342 command => proxy, 343 transport_family => Family, 344 transport_protocol => Protocol, 345 src_address => {S1, S2, S3, S4, S5, S6, S7, S8}, 346 src_port => SrcPort, 347 dest_address => {D1, D2, D3, D4, D5, D6, D7, D8}, 348 dest_port => DestPort 349 }, Header); 350 parse_v2(<<SrcAddr0:108/binary, DestAddr0:108/binary, Rest/bits>>, 351 Len, Family=unix, Protocol, Header) 352 when Len >= 216 -> 353 try 354 [SrcAddr, _] = binary:split(SrcAddr0, <<0>>), 355 true = byte_size(SrcAddr) > 0, 356 [DestAddr, _] = binary:split(DestAddr0, <<0>>), 357 true = byte_size(DestAddr) > 0, 358 parse_tlv(Rest, Len - 216, #{ 359 version => 2, 360 command => proxy, 361 transport_family => Family, 362 transport_protocol => Protocol, 363 src_address => SrcAddr, 364 dest_address => DestAddr 365 }, Header) 366 catch _:_ -> 367 {error, 'Invalid UNIX address in PROXY protocol binary header. (PP 2.2)'} 368 end; 369 parse_v2(_, _, _, _, _) -> 370 {error, 'Invalid length in the PROXY protocol binary header. (PP 2.2)'}. 371 372 -ifdef(TEST). 373 parse_v2_test() -> 374 %% Test cases taken from tomciopp/proxy_protocol. 375 {ok, #{ 376 version := 2, 377 command := proxy, 378 transport_family := ipv4, 379 transport_protocol := stream, 380 src_address := {127, 0, 0, 1}, 381 src_port := 444, 382 dest_address := {192, 168, 0, 1}, 383 dest_port := 443 384 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 385 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 386 33, %% Version and command. 387 17, %% Family and protocol. 388 0, 12, %% Length. 389 127, 0, 0, 1, %% Source address. 390 192, 168, 0, 1, %% Destination address. 391 1, 188, %% Source port. 392 1, 187, %% Destination port. 393 "GET / HTTP/1.1\r\n">>), 394 {ok, #{ 395 version := 2, 396 command := proxy, 397 transport_family := ipv4, 398 transport_protocol := dgram, 399 src_address := {127, 0, 0, 1}, 400 src_port := 444, 401 dest_address := {192, 168, 0, 1}, 402 dest_port := 443 403 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 404 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 405 33, %% Version and command. 406 18, %% Family and protocol. 407 0, 12, %% Length. 408 127, 0, 0, 1, %% Source address. 409 192, 168, 0, 1, %% Destination address. 410 1, 188, %% Source port. 411 1, 187, %% Destination port. 412 "GET / HTTP/1.1\r\n">>), 413 {ok, #{ 414 version := 2, 415 command := proxy, 416 transport_family := ipv6, 417 transport_protocol := stream, 418 src_address := {5532, 4240, 1, 0, 0, 0, 0, 0}, 419 src_port := 444, 420 dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0}, 421 dest_port := 443 422 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 423 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 424 33, %% Version and command. 425 33, %% Family and protocol. 426 0, 36, %% Length. 427 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address. 428 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address. 429 1, 188, %% Source port. 430 1, 187, %% Destination port. 431 "GET / HTTP/1.1\r\n">>), 432 {ok, #{ 433 version := 2, 434 command := proxy, 435 transport_family := ipv6, 436 transport_protocol := dgram, 437 src_address := {5532, 4240, 1, 0, 0, 0, 0, 0}, 438 src_port := 444, 439 dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0}, 440 dest_port := 443 441 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 442 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature. 443 33, %% Version and command. 444 34, %% Family and protocol. 445 0, 36, %% Length. 446 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address. 447 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address. 448 1, 188, %% Source port. 449 1, 187, %% Destination port. 450 "GET / HTTP/1.1\r\n">>), 451 Path = <<"/var/pgsql_sock">>, 452 Len = byte_size(Path), 453 Padding = 8 * (108 - Len), 454 {ok, #{ 455 version := 2, 456 command := proxy, 457 transport_family := unix, 458 transport_protocol := stream, 459 src_address := Path, 460 dest_address := Path 461 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 462 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 463 33, 464 49, 465 0, 216, 466 Path/binary, 0:Padding, 467 Path/binary, 0:Padding, 468 "GET / HTTP/1.1\r\n">>), 469 {ok, #{ 470 version := 2, 471 command := proxy, 472 transport_family := unix, 473 transport_protocol := dgram, 474 src_address := Path, 475 dest_address := Path 476 }, <<"GET / HTTP/1.1\r\n">>} = parse(<< 477 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 478 33, 479 50, 480 0, 216, 481 Path/binary, 0:Padding, 482 Path/binary, 0:Padding, 483 "GET / HTTP/1.1\r\n">>), 484 ok. 485 486 parse_v2_regression_test() -> 487 %% Real packet received from AWS. We confirm that the CRC32C 488 %% check succeeds only (in other words that ok is returned). 489 {ok, _, <<>>} = parse(<< 490 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 84, 491 172, 31, 7, 113, 172, 31, 10, 31, 200, 242, 0, 80, 3, 0, 4, 492 232, 214, 137, 45, 234, 0, 23, 1, 118, 112, 99, 101, 45, 48, 493 56, 100, 50, 98, 102, 49, 53, 102, 97, 99, 53, 48, 48, 49, 99, 494 57, 4, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 495 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>), 496 ok. 497 -endif. 498 499 parse_tlv(Rest, 0, Info, _) -> 500 {ok, Info, Rest}; 501 %% PP2_TYPE_ALPN. 502 parse_tlv(<<16#1, TLVLen:16, ALPN:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> 503 parse_tlv(Rest, Len - TLVLen - 3, Info#{alpn => ALPN}, Header); 504 %% PP2_TYPE_AUTHORITY. 505 parse_tlv(<<16#2, TLVLen:16, Authority:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> 506 parse_tlv(Rest, Len - TLVLen - 3, Info#{authority => Authority}, Header); 507 %% PP2_TYPE_CRC32C. 508 parse_tlv(<<16#3, TLVLen:16, CRC32C:32, Rest/bits>>, Len0, Info, Header) when TLVLen =:= 4 -> 509 Len = Len0 - TLVLen - 3, 510 BeforeLen = byte_size(Header) - Len - TLVLen, 511 <<Before:BeforeLen/binary, _:32, After:Len/binary>> = Header, 512 %% The initial CRC is ranch_crc32c:crc32c(<<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>). 513 case ranch_crc32c:crc32c(2900412422, [Before, <<0:32>>, After]) of 514 CRC32C -> 515 parse_tlv(Rest, Len, Info, Header); 516 _ -> 517 {error, 'Failed CRC32C verification in PROXY protocol binary header. (PP 2.2)'} 518 end; 519 %% PP2_TYPE_NOOP. 520 parse_tlv(<<16#4, TLVLen:16, _:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> 521 parse_tlv(Rest, Len - TLVLen - 3, Info, Header); 522 %% PP2_TYPE_SSL. 523 parse_tlv(<<16#20, TLVLen:16, Client, Verify:32, Rest0/bits>>, Len, Info, Header) -> 524 SubsLen = TLVLen - 5, 525 case Rest0 of 526 <<Subs:SubsLen/binary, Rest/bits>> -> 527 SSL0 = #{ 528 client => parse_client(<<Client>>), 529 verified => Verify =:= 0 530 }, 531 case parse_ssl_tlv(Subs, SubsLen, SSL0) of 532 {ok, SSL, <<>>} -> 533 parse_tlv(Rest, Len - TLVLen - 3, Info#{ssl => SSL}, Header); 534 Error={error, _} -> 535 Error 536 end; 537 _ -> 538 {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'} 539 end; 540 %% PP2_TYPE_NETNS. 541 parse_tlv(<<16#30, TLVLen:16, NetNS:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> 542 parse_tlv(Rest, Len - TLVLen - 3, Info#{netns => NetNS}, Header); 543 %% Unknown TLV. 544 parse_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info, Header) -> 545 RawTLVs = maps:get(raw_tlvs, Info, []), 546 parse_tlv(Rest, Len - TLVLen - 3, Info#{raw_tlvs => [{TLVType, TLVValue}|RawTLVs]}, Header); 547 %% Invalid TLV length. 548 parse_tlv(_, _, _, _) -> 549 {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}. 550 551 parse_client(<<_:5, ClientCertSess:1, ClientCertConn:1, ClientSSL:1>>) -> 552 Client0 = case ClientCertSess of 553 0 -> []; 554 1 -> [cert_sess] 555 end, 556 Client1 = case ClientCertConn of 557 0 -> Client0; 558 1 -> [cert_conn|Client0] 559 end, 560 case ClientSSL of 561 0 -> Client1; 562 1 -> [ssl|Client1] 563 end. 564 565 parse_ssl_tlv(Rest, 0, Info) -> 566 {ok, Info, Rest}; 567 %% Valid TLVs. 568 parse_ssl_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info) -> 569 case ssl_subtype(TLVType) of 570 undefined -> 571 {error, 'Invalid TLV subtype for PP2_TYPE_SSL in PROXY protocol binary header. (PP 2.2)'}; 572 Type -> 573 parse_ssl_tlv(Rest, Len - TLVLen - 3, Info#{Type => TLVValue}) 574 end; 575 %% Invalid TLV length. 576 parse_ssl_tlv(_, _, _) -> 577 {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}. 578 579 ssl_subtype(16#21) -> version; 580 ssl_subtype(16#22) -> cn; 581 ssl_subtype(16#23) -> cipher; 582 ssl_subtype(16#24) -> sig_alg; 583 ssl_subtype(16#25) -> key_alg; 584 ssl_subtype(_) -> undefined. 585 586 %% Building. 587 588 -spec header(proxy_info()) -> iodata(). 589 header(ProxyInfo) -> 590 header(ProxyInfo, #{}). 591 592 -spec header(proxy_info(), build_opts()) -> iodata(). 593 header(#{version := 2, command := local}, _) -> 594 <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 0:28>>; 595 header(#{version := 2, command := proxy, 596 transport_family := Family, 597 transport_protocol := Protocol}, _) 598 when Family =:= undefined; Protocol =:= undefined -> 599 <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4, 0:24>>; 600 header(ProxyInfo=#{version := 2, command := proxy, 601 transport_family := Family, 602 transport_protocol := Protocol}, Opts) -> 603 Addresses = addresses(ProxyInfo), 604 TLVs = tlvs(ProxyInfo, Opts), 605 ExtraLen = case Opts of 606 #{checksum := crc32c} -> 7; 607 _ -> 0 608 end, 609 Len = iolist_size(Addresses) + iolist_size(TLVs) + ExtraLen, 610 Header = [ 611 <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>, 612 <<(family(Family)):4, (protocol(Protocol)):4>>, 613 <<Len:16>>, 614 Addresses, 615 TLVs 616 ], 617 case Opts of 618 #{checksum := crc32c} -> 619 CRC32C = ranch_crc32c:crc32c([Header, <<16#3, 4:16, 0:32>>]), 620 [Header, <<16#3, 4:16, CRC32C:32>>]; 621 _ -> 622 Header 623 end; 624 header(#{version := 1, command := proxy, 625 transport_family := undefined, 626 transport_protocol := undefined}, _) -> 627 <<"PROXY UNKNOWN\r\n">>; 628 header(#{version := 1, command := proxy, 629 transport_family := Family0, 630 transport_protocol := stream, 631 src_address := SrcAddress, src_port := SrcPort, 632 dest_address := DestAddress, dest_port := DestPort}, _) 633 when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> 634 [ 635 <<"PROXY ">>, 636 case Family0 of 637 ipv4 when tuple_size(SrcAddress) =:= 4, tuple_size(DestAddress) =:= 4 -> 638 [<<"TCP4 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)]; 639 ipv6 when tuple_size(SrcAddress) =:= 8, tuple_size(DestAddress) =:= 8 -> 640 [<<"TCP6 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)] 641 end, 642 $\s, 643 integer_to_binary(SrcPort), 644 $\s, 645 integer_to_binary(DestPort), 646 $\r, $\n 647 ]. 648 649 family(ipv4) -> 1; 650 family(ipv6) -> 2; 651 family(unix) -> 3. 652 653 protocol(stream) -> 1; 654 protocol(dgram) -> 2. 655 656 addresses(#{transport_family := ipv4, 657 src_address := {S1, S2, S3, S4}, src_port := SrcPort, 658 dest_address := {D1, D2, D3, D4}, dest_port := DestPort}) 659 when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> 660 <<S1, S2, S3, S4, D1, D2, D3, D4, SrcPort:16, DestPort:16>>; 661 addresses(#{transport_family := ipv6, 662 src_address := {S1, S2, S3, S4, S5, S6, S7, S8}, src_port := SrcPort, 663 dest_address := {D1, D2, D3, D4, D5, D6, D7, D8}, dest_port := DestPort}) 664 when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 -> 665 << 666 S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16, 667 D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16, 668 SrcPort:16, DestPort:16 669 >>; 670 addresses(#{transport_family := unix, 671 src_address := SrcAddress, dest_address := DestAddress}) 672 when byte_size(SrcAddress) =< 108, byte_size(DestAddress) =< 108 -> 673 SrcPadding = 8 * (108 - byte_size(SrcAddress)), 674 DestPadding = 8 * (108 - byte_size(DestAddress)), 675 << 676 SrcAddress/binary, 0:SrcPadding, 677 DestAddress/binary, 0:DestPadding 678 >>. 679 680 tlvs(ProxyInfo, Opts) -> 681 [ 682 binary_tlv(ProxyInfo, alpn, 16#1), 683 binary_tlv(ProxyInfo, authority, 16#2), 684 ssl_tlv(ProxyInfo), 685 binary_tlv(ProxyInfo, netns, 16#30), 686 raw_tlvs(ProxyInfo), 687 noop_tlv(Opts) 688 ]. 689 690 binary_tlv(Info, Key, Type) -> 691 case Info of 692 #{Key := Bin} -> 693 Len = byte_size(Bin), 694 <<Type, Len:16, Bin/binary>>; 695 _ -> 696 <<>> 697 end. 698 699 noop_tlv(#{padding := Len0}) when Len0 >= 3 -> 700 Len = Len0 - 3, 701 <<16#4, Len:16, 0:Len/unit:8>>; 702 noop_tlv(_) -> 703 <<>>. 704 705 ssl_tlv(#{ssl := Info=#{client := Client0, verified := Verify0}}) -> 706 Client = client(Client0, 0), 707 Verify = if 708 Verify0 -> 0; 709 not Verify0 -> 1 710 end, 711 TLVs = [ 712 binary_tlv(Info, version, 16#21), 713 binary_tlv(Info, cn, 16#22), 714 binary_tlv(Info, cipher, 16#23), 715 binary_tlv(Info, sig_alg, 16#24), 716 binary_tlv(Info, key_alg, 16#25) 717 ], 718 Len = iolist_size(TLVs) + 5, 719 [<<16#20, Len:16, Client, Verify:32>>, TLVs]; 720 ssl_tlv(_) -> 721 <<>>. 722 723 client([], Client) -> Client; 724 client([ssl|Tail], Client) -> client(Tail, Client bor 16#1); 725 client([cert_conn|Tail], Client) -> client(Tail, Client bor 16#2); 726 client([cert_sess|Tail], Client) -> client(Tail, Client bor 16#4). 727 728 raw_tlvs(Info) -> 729 [begin 730 Len = byte_size(Bin), 731 <<Type, Len:16, Bin/binary>> 732 end || {Type, Bin} <- maps:get(raw_tlvs, Info, [])]. 733 734 -ifdef(TEST). 735 v1_test() -> 736 Test1 = #{ 737 version => 1, 738 command => proxy, 739 transport_family => undefined, 740 transport_protocol => undefined 741 }, 742 {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), 743 Test2 = #{ 744 version => 1, 745 command => proxy, 746 transport_family => ipv4, 747 transport_protocol => stream, 748 src_address => {127, 0, 0, 1}, 749 src_port => 1234, 750 dest_address => {10, 11, 12, 13}, 751 dest_port => 23456 752 }, 753 {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), 754 Test3 = #{ 755 version => 1, 756 command => proxy, 757 transport_family => ipv6, 758 transport_protocol => stream, 759 src_address => {1, 2, 3, 4, 5, 6, 7, 8}, 760 src_port => 1234, 761 dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8}, 762 dest_port => 23456 763 }, 764 {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), 765 ok. 766 767 v2_test() -> 768 Test0 = #{ 769 version => 2, 770 command => local 771 }, 772 {ok, Test0, <<>>} = parse(iolist_to_binary(header(Test0))), 773 Test1 = #{ 774 version => 2, 775 command => proxy, 776 transport_family => undefined, 777 transport_protocol => undefined 778 }, 779 {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), 780 Test2 = #{ 781 version => 2, 782 command => proxy, 783 transport_family => ipv4, 784 transport_protocol => stream, 785 src_address => {127, 0, 0, 1}, 786 src_port => 1234, 787 dest_address => {10, 11, 12, 13}, 788 dest_port => 23456 789 }, 790 {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), 791 Test3 = #{ 792 version => 2, 793 command => proxy, 794 transport_family => ipv6, 795 transport_protocol => stream, 796 src_address => {1, 2, 3, 4, 5, 6, 7, 8}, 797 src_port => 1234, 798 dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8}, 799 dest_port => 23456 800 }, 801 {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), 802 Test4 = #{ 803 version => 2, 804 command => proxy, 805 transport_family => unix, 806 transport_protocol => dgram, 807 src_address => <<"/run/source.sock">>, 808 dest_address => <<"/run/destination.sock">> 809 }, 810 {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))), 811 ok. 812 813 v2_tlvs_test() -> 814 Common = #{ 815 version => 2, 816 command => proxy, 817 transport_family => ipv4, 818 transport_protocol => stream, 819 src_address => {127, 0, 0, 1}, 820 src_port => 1234, 821 dest_address => {10, 11, 12, 13}, 822 dest_port => 23456 823 }, 824 Test1 = Common#{alpn => <<"h2">>}, 825 {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))), 826 Test2 = Common#{authority => <<"internal.example.org">>}, 827 {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))), 828 Test3 = Common#{netns => <<"/var/run/netns/example">>}, 829 {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))), 830 Test4 = Common#{ssl => #{ 831 client => [ssl, cert_conn, cert_sess], 832 verified => true, 833 version => <<"TLSv1.3">>, %% Note that I'm not sure this example value is correct. 834 cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>, 835 sig_alg => <<"SHA256">>, 836 key_alg => <<"RSA2048">>, 837 cn => <<"example.com">> 838 }}, 839 {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))), 840 %% Note that the raw_tlvs order is not relevant and therefore 841 %% the parser does not reverse the list it builds. 842 Test5In = Common#{raw_tlvs => RawTLVs=[ 843 %% The only custom TLV I am aware of is defined at: 844 %% https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol 845 {16#ea, <<16#1, "instance-id">>}, 846 %% This TLV is entirely fictional. 847 {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>} 848 ]}, 849 Test5Out = Test5In#{raw_tlvs => lists:reverse(RawTLVs)}, 850 {ok, Test5Out, <<>>} = parse(iolist_to_binary(header(Test5In))), 851 ok. 852 853 v2_checksum_test() -> 854 Test = #{ 855 version => 2, 856 command => proxy, 857 transport_family => ipv4, 858 transport_protocol => stream, 859 src_address => {127, 0, 0, 1}, 860 src_port => 1234, 861 dest_address => {10, 11, 12, 13}, 862 dest_port => 23456 863 }, 864 {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{checksum => crc32c}))), 865 ok. 866 867 v2_padding_test() -> 868 Test = #{ 869 version => 2, 870 command => proxy, 871 transport_family => ipv4, 872 transport_protocol => stream, 873 src_address => {127, 0, 0, 1}, 874 src_port => 1234, 875 dest_address => {10, 11, 12, 13}, 876 dest_port => 23456 877 }, 878 {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))), 879 ok. 880 -endif.