ranch_proxy_header.erl 31 KB

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