]> git.ipfire.org Git - thirdparty/squid.git/blob - src/proxyp/Parser.cc
Log PROXY protocol v2 TLVs; fix PROXY protocol parsing bugs (#342)
[thirdparty/squid.git] / src / proxyp / Parser.cc
1 /*
2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9 #include "squid.h"
10 #include "parser/BinaryTokenizer.h"
11 #include "parser/Tokenizer.h"
12 #include "proxyp/Elements.h"
13 #include "proxyp/Header.h"
14 #include "proxyp/Parser.h"
15 #include "sbuf/Stream.h"
16
17 #include <algorithm>
18
19 #if HAVE_SYS_SOCKET_H
20 #include <sys/socket.h>
21 #endif
22 #if HAVE_NETINET_IN_H
23 #include <netinet/in.h>
24 #endif
25 #if HAVE_NETINET_IP_H
26 #include <netinet/ip.h>
27 #endif
28
29 namespace ProxyProtocol {
30 namespace One {
31 /// magic octet prefix for PROXY protocol version 1
32 static const SBuf Magic("PROXY", 5);
33 /// extracts PROXY protocol v1 header from the given buffer
34 static Parsed Parse(const SBuf &buf);
35
36 static void ExtractIp(Parser::Tokenizer &tok, Ip::Address &addr);
37 static void ExtractPort(Parser::Tokenizer &tok, Ip::Address &addr, const bool trailingSpace);
38 static void ParseAddresses(Parser::Tokenizer &tok, Header::Pointer &header);
39 }
40
41 namespace Two {
42 /// magic octet prefix for PROXY protocol version 2
43 static const SBuf Magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
44 /// extracts PROXY protocol v2 header from the given buffer
45 static Parsed Parse(const SBuf &buf);
46
47 static void ParseAddresses(const uint8_t family, Parser::BinaryTokenizer &tok, Header::Pointer &header);
48 static void ParseTLVs(Parser::BinaryTokenizer &tok, Header::Pointer &header);
49 }
50 }
51
52 void
53 ProxyProtocol::One::ExtractIp(Parser::Tokenizer &tok, Ip::Address &addr)
54 {
55 static const auto ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG;
56
57 SBuf ip;
58
59 if (!tok.prefix(ip, ipChars))
60 throw TexcHere("PROXY/1.0 error: malformed IP address");
61
62 if (!tok.skip(' '))
63 throw TexcHere("PROXY/1.0 error: garbage after IP address");
64
65 if (!addr.GetHostByName(ip.c_str()))
66 throw TexcHere("PROXY/1.0 error: invalid IP address");
67
68 }
69
70 void
71 ProxyProtocol::One::ExtractPort(Parser::Tokenizer &tok, Ip::Address &addr, const bool trailingSpace)
72 {
73 int64_t port = -1;
74
75 if (!tok.int64(port, 10, false))
76 throw TexcHere("PROXY/1.0 error: malformed port");
77
78 if (trailingSpace && !tok.skip(' '))
79 throw TexcHere("PROXY/1.0 error: garbage after port");
80
81 if (port > std::numeric_limits<uint16_t>::max())
82 throw TexcHere("PROXY/1.0 error: invalid port");
83
84 addr.port(static_cast<uint16_t>(port));
85 }
86
87 void
88 ProxyProtocol::One::ParseAddresses(Parser::Tokenizer &tok, Header::Pointer &header)
89 {
90 static const CharacterSet addressFamilies("Address family", "46");
91 SBuf parsedAddressFamily;
92
93 if (!tok.prefix(parsedAddressFamily, addressFamilies, 1))
94 throw TexcHere("PROXY/1.0 error: missing or invalid IP address family");
95
96 if (!tok.skip(' '))
97 throw TexcHere("PROXY/1.0 error: missing SP after the IP address family");
98
99 // parse: src-IP SP dst-IP SP src-port SP dst-port
100 ExtractIp(tok, header->sourceAddress);
101 ExtractIp(tok, header->destinationAddress);
102
103 if (header->addressFamily() != parsedAddressFamily)
104 throw TexcHere("PROXY/1.0 error: declared and/or actual IP address families mismatch");
105
106 ExtractPort(tok, header->sourceAddress, true);
107 ExtractPort(tok, header->destinationAddress, false);
108 }
109
110 /// parses PROXY protocol v1 header from the buffer
111 ProxyProtocol::Parsed
112 ProxyProtocol::One::Parse(const SBuf &buf)
113 {
114 Parser::Tokenizer tok(buf);
115
116 static const SBuf::size_type maxHeaderLength = 107; // including CRLF
117 static const auto maxInteriorLength = maxHeaderLength - Magic.length() - 2;
118 static const auto interiorChars = CharacterSet::CR.complement().rename("non-CR");
119 SBuf interior;
120
121 if (!(tok.prefix(interior, interiorChars, maxInteriorLength) &&
122 tok.skip('\r') &&
123 tok.skip('\n'))) {
124 if (tok.atEnd())
125 throw Parser::BinaryTokenizer::InsufficientInput();
126 // "empty interior", "too-long interior", or "missing LF after CR"
127 throw TexcHere("PROXY/1.0 error: malformed header");
128 }
129 // extracted all PROXY protocol bytes
130
131 static const SBuf v1("1.0");
132 Header::Pointer header = new Header(v1, Two::cmdProxy);
133
134 Parser::Tokenizer interiorTok(interior);
135
136 if (!interiorTok.skip(' '))
137 throw TexcHere("PROXY/1.0 error: missing SP after the magic sequence");
138
139 static const SBuf protoUnknown("UNKNOWN");
140 static const SBuf protoTcp("TCP");
141
142 if (interiorTok.skip(protoTcp))
143 ParseAddresses(interiorTok, header);
144 else if (interiorTok.skip(protoUnknown))
145 header->ignoreAddresses();
146 else
147 throw TexcHere("PROXY/1.0 error: invalid INET protocol or family");
148
149 return Parsed(header, tok.parsedSize());
150 }
151
152 void
153 ProxyProtocol::Two::ParseAddresses(const uint8_t family, Parser::BinaryTokenizer &tok, Header::Pointer &header)
154 {
155 switch (family) {
156
157 case afInet: {
158 header->sourceAddress = tok.inet4("src_addr IPv4");
159 header->destinationAddress = tok.inet4("dst_addr IPv4");
160 header->sourceAddress.port(tok.uint16("src_port"));
161 header->destinationAddress.port(tok.uint16("dst_port"));
162 break;
163 }
164
165 case afInet6: {
166 header->sourceAddress = tok.inet6("src_addr IPv6");
167 header->destinationAddress = tok.inet6("dst_addr IPv6");
168 header->sourceAddress.port(tok.uint16("src_port"));
169 header->destinationAddress.port(tok.uint16("dst_port"));
170 break;
171 }
172
173 case afUnix: { // TODO: add support
174 // the address block length is 216 bytes
175 tok.skip(216, "unix_addr");
176 break;
177 }
178
179 default: {
180 // unreachable code: we have checked family validity already
181 Must(false);
182 break;
183 }
184 }
185 }
186
187 void
188 ProxyProtocol::Two::ParseTLVs(Parser::BinaryTokenizer &tok, Header::Pointer &header) {
189 while (!tok.atEnd()) {
190 const auto type = tok.uint8("pp2_tlv::type");
191 header->tlvs.emplace_back(type, tok.pstring16("pp2_tlv::value"));
192 }
193 }
194
195 ProxyProtocol::Parsed
196 ProxyProtocol::Two::Parse(const SBuf &buf)
197 {
198 Parser::BinaryTokenizer tokHeader(buf, true);
199
200 const auto versionAndCommand = tokHeader.uint8("version and command");
201
202 const auto version = (versionAndCommand & 0xF0) >> 4;
203 if (version != 2) // version == 2 is mandatory
204 throw TexcHere(ToSBuf("PROXY/2.0 error: invalid version ", version));
205
206 const auto command = (versionAndCommand & 0x0F);
207 if (command > cmdProxy)
208 throw TexcHere(ToSBuf("PROXY/2.0 error: invalid command ", command));
209
210 const auto familyAndProto = tokHeader.uint8("family and proto");
211
212 const auto family = (familyAndProto & 0xF0) >> 4;
213 if (family > afUnix)
214 throw TexcHere(ToSBuf("PROXY/2.0 error: invalid address family ", family));
215
216 const auto proto = (familyAndProto & 0x0F);
217 if (proto > tpDgram)
218 throw TexcHere(ToSBuf("PROXY/2.0 error: invalid transport protocol ", proto));
219
220 const auto rawHeader = tokHeader.pstring16("header");
221
222 static const SBuf v2("2.0");
223 Header::Pointer header = new Header(v2, Two::Command(command));
224
225 if (proto == tpUnspecified || family == afUnspecified) {
226 header->ignoreAddresses();
227 // discard address block and TLVs because we cannot tell
228 // how to parse such addresses and where the TLVs start.
229 } else {
230 Parser::BinaryTokenizer leftoverTok(rawHeader);
231 ParseAddresses(family, leftoverTok, header);
232 // TODO: parse TLVs for LOCAL connections
233 if (header->hasForwardedAddresses())
234 ParseTLVs(leftoverTok, header);
235 }
236
237 return Parsed(header, tokHeader.parsed());
238 }
239
240 ProxyProtocol::Parsed
241 ProxyProtocol::Parse(const SBuf &buf)
242 {
243 Parser::Tokenizer magicTok(buf);
244
245 const auto parser =
246 magicTok.skip(Two::Magic) ? &Two::Parse :
247 magicTok.skip(One::Magic) ? &One::Parse :
248 nullptr;
249
250 if (parser) {
251 const auto parsed = (parser)(magicTok.remaining());
252 return Parsed(parsed.header, magicTok.parsedSize() + parsed.size);
253 }
254
255 // detect and terminate other protocols
256 if (buf.length() >= Two::Magic.length()) {
257 // PROXY/1.0 magic is shorter, so we know that
258 // the input does not start with any PROXY magic
259 throw TexcHere("PROXY protocol error: invalid magic");
260 }
261
262 // TODO: detect short non-magic prefixes earlier to avoid
263 // waiting for more data which may never come
264
265 // not enough bytes to parse magic yet
266 throw Parser::BinaryTokenizer::InsufficientInput();
267 }
268
269 ProxyProtocol::Parsed::Parsed(const Header::Pointer &parsedHeader, const size_t parsedSize):
270 header(parsedHeader),
271 size(parsedSize)
272 {
273 assert(bool(parsedHeader));
274 }
275