/src/pdns/pdns/proxy-protocol.cc
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * This file is part of PowerDNS or dnsdist. |
3 | | * Copyright -- PowerDNS.COM B.V. and its contributors |
4 | | * |
5 | | * This program is free software; you can redistribute it and/or modify |
6 | | * it under the terms of version 2 of the GNU General Public License as |
7 | | * published by the Free Software Foundation. |
8 | | * |
9 | | * In addition, for the avoidance of any doubt, permission is granted to |
10 | | * link this program with OpenSSL and to (re)distribute the binaries |
11 | | * produced as the result of such linking. |
12 | | * |
13 | | * This program is distributed in the hope that it will be useful, |
14 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | * GNU General Public License for more details. |
17 | | * |
18 | | * You should have received a copy of the GNU General Public License |
19 | | * along with this program; if not, write to the Free Software |
20 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
21 | | */ |
22 | | |
23 | | #include "proxy-protocol.hh" |
24 | | |
25 | | // TODO: maybe use structs instead of explicitly working byte by byte, like https://github.com/dovecot/core/blob/master/src/lib-master/master-service-haproxy.c |
26 | | |
27 | | #define PROXYMAGIC "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" |
28 | | #define PROXYMAGICLEN sizeof(PROXYMAGIC)-1 |
29 | | |
30 | | static const string proxymagic(PROXYMAGIC, PROXYMAGICLEN); |
31 | | |
32 | | static void makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen, size_t additionalSize, std::string& out) |
33 | 0 | { |
34 | 0 | const uint8_t versioncommand = (0x20 | command); |
35 | 0 | const size_t totalSize = proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + additionalSize; |
36 | 0 | if (out.capacity() < totalSize) { |
37 | 0 | out.reserve(totalSize); |
38 | 0 | } |
39 | |
|
40 | 0 | out.append(proxymagic); |
41 | |
|
42 | 0 | out.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand)); |
43 | 0 | out.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol)); |
44 | |
|
45 | 0 | out.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen)); |
46 | 0 | } |
47 | | |
48 | | std::string makeLocalProxyHeader() |
49 | 0 | { |
50 | 0 | std::string out; |
51 | 0 | makeSimpleHeader(0x00, 0, 0, 0, out); |
52 | 0 | return out; |
53 | 0 | } |
54 | | |
55 | | std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values) |
56 | 0 | { |
57 | 0 | if (source.sin4.sin_family != destination.sin4.sin_family) { |
58 | 0 | throw std::runtime_error("The PROXY destination and source addresses must be of the same family"); |
59 | 0 | } |
60 | | |
61 | 0 | const uint8_t command = 0x01; |
62 | 0 | const uint8_t protocol = (source.isIPv4() ? 0x10 : 0x20) | (tcp ? 0x01 : 0x02); |
63 | 0 | const size_t addrSize = source.isIPv4() ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr); |
64 | 0 | const uint16_t sourcePort = source.sin4.sin_port; |
65 | 0 | const uint16_t destinationPort = destination.sin4.sin_port; |
66 | |
|
67 | 0 | size_t valuesSize = 0; |
68 | 0 | for (const auto& value : values) { |
69 | 0 | if (value.content.size() > std::numeric_limits<uint16_t>::max()) { |
70 | 0 | throw std::runtime_error("The size of proxy protocol values is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to add a value of size " + std::to_string(value.content.size())); |
71 | 0 | } |
72 | 0 | valuesSize += sizeof(uint8_t) + sizeof(uint8_t) * 2 + value.content.size(); |
73 | 0 | if (valuesSize > std::numeric_limits<uint16_t>::max()) { |
74 | 0 | throw std::runtime_error("The total size of proxy protocol values is limited to " + std::to_string(std::numeric_limits<uint16_t>::max())); |
75 | 0 | } |
76 | 0 | } |
77 | | |
78 | | /* size of the data that will come _after_ the minimal proxy protocol header */ |
79 | 0 | size_t additionalDataSize = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize; |
80 | 0 | if (additionalDataSize > std::numeric_limits<uint16_t>::max()) { |
81 | 0 | throw std::runtime_error("The size of a proxy protocol header is limited to " + std::to_string(std::numeric_limits<uint16_t>::max()) + ", trying to send one of size " + std::to_string(additionalDataSize)); |
82 | 0 | } |
83 | | |
84 | 0 | const uint16_t contentlen = htons(static_cast<uint16_t>(additionalDataSize)); |
85 | 0 | std::string ret; |
86 | 0 | makeSimpleHeader(command, protocol, contentlen, additionalDataSize, ret); |
87 | | |
88 | | // We already established source and destination sin_family equivalence |
89 | 0 | if (source.isIPv4()) { |
90 | 0 | assert(addrSize == sizeof(source.sin4.sin_addr.s_addr)); |
91 | 0 | ret.append(reinterpret_cast<const char*>(&source.sin4.sin_addr.s_addr), addrSize); |
92 | 0 | assert(addrSize == sizeof(destination.sin4.sin_addr.s_addr)); |
93 | 0 | ret.append(reinterpret_cast<const char*>(&destination.sin4.sin_addr.s_addr), addrSize); |
94 | 0 | } |
95 | 0 | else { |
96 | 0 | assert(addrSize == sizeof(source.sin6.sin6_addr.s6_addr)); |
97 | 0 | ret.append(reinterpret_cast<const char*>(&source.sin6.sin6_addr.s6_addr), addrSize); |
98 | 0 | assert(addrSize == sizeof(destination.sin6.sin6_addr.s6_addr)); |
99 | 0 | ret.append(reinterpret_cast<const char*>(&destination.sin6.sin6_addr.s6_addr), addrSize); |
100 | 0 | } |
101 | | |
102 | 0 | ret.append(reinterpret_cast<const char*>(&sourcePort), sizeof(sourcePort)); |
103 | 0 | ret.append(reinterpret_cast<const char*>(&destinationPort), sizeof(destinationPort)); |
104 | |
|
105 | 0 | for (const auto& value : values) { |
106 | 0 | uint16_t contentSize = htons(static_cast<uint16_t>(value.content.size())); |
107 | 0 | ret.append(reinterpret_cast<const char*>(&value.type), sizeof(value.type)); |
108 | 0 | ret.append(reinterpret_cast<const char*>(&contentSize), sizeof(contentSize)); |
109 | 0 | ret.append(reinterpret_cast<const char*>(value.content.data()), value.content.size()); |
110 | 0 | } |
111 | |
|
112 | 0 | return ret; |
113 | 0 | } |
114 | | |
115 | | /* returns: number of bytes consumed (positive) after successful parse |
116 | | or number of bytes missing (negative) |
117 | | or unfixable parse error (0)*/ |
118 | | template<typename Container> ssize_t isProxyHeaderComplete(const Container& header, bool* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut) |
119 | 376 | { |
120 | 376 | static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr); |
121 | 376 | static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr); |
122 | 376 | size_t addrSize = 0; |
123 | 376 | uint8_t versioncommand; |
124 | 376 | uint8_t protocol; |
125 | | |
126 | 376 | if (header.size() < s_proxyProtocolMinimumHeaderSize) { |
127 | | // this is too short to be a complete proxy header |
128 | 11 | return -(s_proxyProtocolMinimumHeaderSize - header.size()); |
129 | 11 | } |
130 | | |
131 | 365 | if (std::memcmp(&header.at(0), &proxymagic.at(0), proxymagic.size()) != 0) { |
132 | | // wrong magic, can not be a proxy header |
133 | 119 | return 0; |
134 | 119 | } |
135 | | |
136 | 246 | versioncommand = header.at(12); |
137 | | /* check version */ |
138 | 246 | if (!(versioncommand & 0x20)) { |
139 | 1 | return 0; |
140 | 1 | } |
141 | | |
142 | | /* remove the version to get the command */ |
143 | 245 | uint8_t command = versioncommand & ~0x20; |
144 | | |
145 | 245 | if (command == 0x01) { |
146 | 37 | protocol = header.at(13); |
147 | 37 | if ((protocol & 0xf) == 1) { |
148 | 23 | if (tcp) { |
149 | 23 | *tcp = true; |
150 | 23 | } |
151 | 23 | } else if ((protocol & 0xf) == 2) { |
152 | 9 | if (tcp) { |
153 | 9 | *tcp = false; |
154 | 9 | } |
155 | 9 | } else { |
156 | 5 | return 0; |
157 | 5 | } |
158 | | |
159 | 32 | protocol = protocol >> 4; |
160 | | |
161 | 32 | if (protocol == 1) { |
162 | 10 | if (protocolOut) { |
163 | 10 | *protocolOut = 4; |
164 | 10 | } |
165 | 10 | addrSize = addr4Size; // IPv4 |
166 | 22 | } else if (protocol == 2) { |
167 | 17 | if (protocolOut) { |
168 | 17 | *protocolOut = 6; |
169 | 17 | } |
170 | 17 | addrSize = addr6Size; // IPv6 |
171 | 17 | } else { |
172 | | // invalid protocol |
173 | 5 | return 0; |
174 | 5 | } |
175 | | |
176 | 27 | if (addrSizeOut) { |
177 | 27 | *addrSizeOut = addrSize; |
178 | 27 | } |
179 | | |
180 | 27 | if (proxy) { |
181 | 27 | *proxy = true; |
182 | 27 | } |
183 | 27 | } |
184 | 208 | else if (command == 0x00) { |
185 | 201 | if (proxy) { |
186 | 201 | *proxy = false; |
187 | 201 | } |
188 | 201 | } |
189 | 7 | else { |
190 | | /* unsupported command */ |
191 | 7 | return 0; |
192 | 7 | } |
193 | | |
194 | 228 | uint16_t contentlen = (static_cast<uint8_t>(header.at(14)) << 8) + static_cast<uint8_t>(header.at(15)); |
195 | 228 | uint16_t expectedlen = 0; |
196 | 228 | if (command != 0x00) { |
197 | 27 | expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port); |
198 | 27 | } |
199 | | |
200 | 228 | if (contentlen < expectedlen) { |
201 | 7 | return 0; |
202 | 7 | } |
203 | | |
204 | 221 | if (header.size() < s_proxyProtocolMinimumHeaderSize + contentlen) { |
205 | 46 | return -((s_proxyProtocolMinimumHeaderSize + contentlen) - header.size()); |
206 | 46 | } |
207 | | |
208 | 175 | return s_proxyProtocolMinimumHeaderSize + contentlen; |
209 | 221 | } long isProxyHeaderComplete<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool*, bool*, unsigned long*, unsigned char*) Line | Count | Source | 119 | 376 | { | 120 | 376 | static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr); | 121 | 376 | static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr); | 122 | 376 | size_t addrSize = 0; | 123 | 376 | uint8_t versioncommand; | 124 | 376 | uint8_t protocol; | 125 | | | 126 | 376 | if (header.size() < s_proxyProtocolMinimumHeaderSize) { | 127 | | // this is too short to be a complete proxy header | 128 | 11 | return -(s_proxyProtocolMinimumHeaderSize - header.size()); | 129 | 11 | } | 130 | | | 131 | 365 | if (std::memcmp(&header.at(0), &proxymagic.at(0), proxymagic.size()) != 0) { | 132 | | // wrong magic, can not be a proxy header | 133 | 119 | return 0; | 134 | 119 | } | 135 | | | 136 | 246 | versioncommand = header.at(12); | 137 | | /* check version */ | 138 | 246 | if (!(versioncommand & 0x20)) { | 139 | 1 | return 0; | 140 | 1 | } | 141 | | | 142 | | /* remove the version to get the command */ | 143 | 245 | uint8_t command = versioncommand & ~0x20; | 144 | | | 145 | 245 | if (command == 0x01) { | 146 | 37 | protocol = header.at(13); | 147 | 37 | if ((protocol & 0xf) == 1) { | 148 | 23 | if (tcp) { | 149 | 23 | *tcp = true; | 150 | 23 | } | 151 | 23 | } else if ((protocol & 0xf) == 2) { | 152 | 9 | if (tcp) { | 153 | 9 | *tcp = false; | 154 | 9 | } | 155 | 9 | } else { | 156 | 5 | return 0; | 157 | 5 | } | 158 | | | 159 | 32 | protocol = protocol >> 4; | 160 | | | 161 | 32 | if (protocol == 1) { | 162 | 10 | if (protocolOut) { | 163 | 10 | *protocolOut = 4; | 164 | 10 | } | 165 | 10 | addrSize = addr4Size; // IPv4 | 166 | 22 | } else if (protocol == 2) { | 167 | 17 | if (protocolOut) { | 168 | 17 | *protocolOut = 6; | 169 | 17 | } | 170 | 17 | addrSize = addr6Size; // IPv6 | 171 | 17 | } else { | 172 | | // invalid protocol | 173 | 5 | return 0; | 174 | 5 | } | 175 | | | 176 | 27 | if (addrSizeOut) { | 177 | 27 | *addrSizeOut = addrSize; | 178 | 27 | } | 179 | | | 180 | 27 | if (proxy) { | 181 | 27 | *proxy = true; | 182 | 27 | } | 183 | 27 | } | 184 | 208 | else if (command == 0x00) { | 185 | 201 | if (proxy) { | 186 | 201 | *proxy = false; | 187 | 201 | } | 188 | 201 | } | 189 | 7 | else { | 190 | | /* unsupported command */ | 191 | 7 | return 0; | 192 | 7 | } | 193 | | | 194 | 228 | uint16_t contentlen = (static_cast<uint8_t>(header.at(14)) << 8) + static_cast<uint8_t>(header.at(15)); | 195 | 228 | uint16_t expectedlen = 0; | 196 | 228 | if (command != 0x00) { | 197 | 27 | expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port); | 198 | 27 | } | 199 | | | 200 | 228 | if (contentlen < expectedlen) { | 201 | 7 | return 0; | 202 | 7 | } | 203 | | | 204 | 221 | if (header.size() < s_proxyProtocolMinimumHeaderSize + contentlen) { | 205 | 46 | return -((s_proxyProtocolMinimumHeaderSize + contentlen) - header.size()); | 206 | 46 | } | 207 | | | 208 | 175 | return s_proxyProtocolMinimumHeaderSize + contentlen; | 209 | 221 | } |
Unexecuted instantiation: long isProxyHeaderComplete<std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > > >(std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > > const&, bool*, bool*, unsigned long*, unsigned char*) |
210 | | |
211 | | /* returns: number of bytes consumed (positive) after successful parse |
212 | | or number of bytes missing (negative) |
213 | | or unfixable parse error (0)*/ |
214 | | template<typename Container> ssize_t parseProxyHeader(const Container& header, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values) |
215 | 376 | { |
216 | 376 | size_t addrSize = 0; |
217 | 376 | uint8_t protocol = 0; |
218 | 376 | ssize_t got = isProxyHeaderComplete(header, &proxy, &tcp, &addrSize, &protocol); |
219 | 376 | if (got <= 0) { |
220 | 201 | return got; |
221 | 201 | } |
222 | | |
223 | 175 | size_t pos = s_proxyProtocolMinimumHeaderSize; |
224 | | |
225 | 175 | if (proxy) { |
226 | 11 | source = makeComboAddressFromRaw(protocol, reinterpret_cast<const char*>(&header.at(pos)), addrSize); |
227 | 11 | pos = pos + addrSize; |
228 | 11 | destination = makeComboAddressFromRaw(protocol, reinterpret_cast<const char*>(&header.at(pos)), addrSize); |
229 | 11 | pos = pos + addrSize; |
230 | 11 | source.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); |
231 | 11 | pos = pos + sizeof(uint16_t); |
232 | 11 | destination.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); |
233 | 11 | pos = pos + sizeof(uint16_t); |
234 | 11 | } |
235 | | |
236 | 175 | size_t remaining = got - pos; |
237 | 42.3k | while (remaining >= (sizeof(uint8_t) + sizeof(uint16_t))) { |
238 | | /* we still have TLV values to parse */ |
239 | 42.2k | uint8_t type = static_cast<uint8_t>(header.at(pos)); |
240 | 42.2k | pos += sizeof(uint8_t); |
241 | 42.2k | uint16_t len = (static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos + 1)); |
242 | 42.2k | pos += sizeof(uint16_t); |
243 | | |
244 | 42.2k | if (len > 0) { |
245 | 1.25k | if (len > (got - pos)) { |
246 | 63 | return 0; |
247 | 63 | } |
248 | | |
249 | 1.19k | values.push_back({std::string(reinterpret_cast<const char*>(&header.at(pos)), len), type}); |
250 | 1.19k | pos += len; |
251 | 1.19k | } |
252 | 41.0k | else { |
253 | 41.0k | values.push_back({"", type}); |
254 | 41.0k | } |
255 | | |
256 | 42.2k | remaining = got - pos; |
257 | 42.2k | } |
258 | | |
259 | 112 | return pos; |
260 | 175 | } long parseProxyHeader<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool&, ComboAddress&, ComboAddress&, bool&, std::__1::vector<ProxyProtocolValue, std::__1::allocator<ProxyProtocolValue> >&) Line | Count | Source | 215 | 376 | { | 216 | 376 | size_t addrSize = 0; | 217 | 376 | uint8_t protocol = 0; | 218 | 376 | ssize_t got = isProxyHeaderComplete(header, &proxy, &tcp, &addrSize, &protocol); | 219 | 376 | if (got <= 0) { | 220 | 201 | return got; | 221 | 201 | } | 222 | | | 223 | 175 | size_t pos = s_proxyProtocolMinimumHeaderSize; | 224 | | | 225 | 175 | if (proxy) { | 226 | 11 | source = makeComboAddressFromRaw(protocol, reinterpret_cast<const char*>(&header.at(pos)), addrSize); | 227 | 11 | pos = pos + addrSize; | 228 | 11 | destination = makeComboAddressFromRaw(protocol, reinterpret_cast<const char*>(&header.at(pos)), addrSize); | 229 | 11 | pos = pos + addrSize; | 230 | 11 | source.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); | 231 | 11 | pos = pos + sizeof(uint16_t); | 232 | 11 | destination.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); | 233 | 11 | pos = pos + sizeof(uint16_t); | 234 | 11 | } | 235 | | | 236 | 175 | size_t remaining = got - pos; | 237 | 42.3k | while (remaining >= (sizeof(uint8_t) + sizeof(uint16_t))) { | 238 | | /* we still have TLV values to parse */ | 239 | 42.2k | uint8_t type = static_cast<uint8_t>(header.at(pos)); | 240 | 42.2k | pos += sizeof(uint8_t); | 241 | 42.2k | uint16_t len = (static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos + 1)); | 242 | 42.2k | pos += sizeof(uint16_t); | 243 | | | 244 | 42.2k | if (len > 0) { | 245 | 1.25k | if (len > (got - pos)) { | 246 | 63 | return 0; | 247 | 63 | } | 248 | | | 249 | 1.19k | values.push_back({std::string(reinterpret_cast<const char*>(&header.at(pos)), len), type}); | 250 | 1.19k | pos += len; | 251 | 1.19k | } | 252 | 41.0k | else { | 253 | 41.0k | values.push_back({"", type}); | 254 | 41.0k | } | 255 | | | 256 | 42.2k | remaining = got - pos; | 257 | 42.2k | } | 258 | | | 259 | 112 | return pos; | 260 | 175 | } |
Unexecuted instantiation: long parseProxyHeader<std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > > >(std::__1::vector<unsigned char, noinit_adaptor<std::__1::allocator<unsigned char> > > const&, bool&, ComboAddress&, ComboAddress&, bool&, std::__1::vector<ProxyProtocolValue, std::__1::allocator<ProxyProtocolValue> >&) |
261 | | |
262 | | #include "noinitvector.hh" |
263 | | template ssize_t isProxyHeaderComplete<std::string>(const std::string& header, bool* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut); |
264 | | template ssize_t isProxyHeaderComplete<PacketBuffer>(const PacketBuffer& header, bool* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut); |
265 | | template ssize_t parseProxyHeader<std::string>(const std::string& header, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values); |
266 | | template ssize_t parseProxyHeader<PacketBuffer>(const PacketBuffer& header, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values); |