]>
Commit | Line | Data |
---|---|---|
463ec5fd PD |
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 | ||
463ec5fd PD |
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 string proxymagic(PROXYMAGIC, PROXYMAGICLEN); | |
31 | ||
8c73c703 RG |
32 | static std::string makeSimpleHeader(uint8_t command, uint8_t protocol, uint16_t contentLen) |
33 | { | |
34 | std::string ret; | |
35 | const uint8_t versioncommand = (0x20 | command); | |
36 | ||
37 | ret.reserve(proxymagic.size() + sizeof(versioncommand) + sizeof(protocol) + sizeof(contentLen) + contentLen); | |
38 | ||
39 | ret.append(proxymagic); | |
40 | ||
41 | ret.append(reinterpret_cast<const char*>(&versioncommand), sizeof(versioncommand)); | |
42 | ret.append(reinterpret_cast<const char*>(&protocol), sizeof(protocol)); | |
43 | ||
44 | ret.append(reinterpret_cast<const char*>(&contentLen), sizeof(contentLen)); | |
45 | ||
46 | return ret; | |
47 | } | |
48 | ||
49 | std::string makeLocalProxyHeader() | |
50 | { | |
51 | return makeSimpleHeader(0x00, 0, 0); | |
52 | } | |
53 | ||
bde73d5b | 54 | std::string makeProxyHeader(bool tcp, const ComboAddress& source, const ComboAddress& destination, const std::vector<ProxyProtocolValue>& values) |
463ec5fd PD |
55 | { |
56 | if (source.sin4.sin_family != destination.sin4.sin_family) { | |
57 | throw std::runtime_error("The PROXY destination and source addresses must be of the same family"); | |
58 | } | |
59 | ||
8c73c703 | 60 | const uint8_t command = 0x01; |
463ec5fd PD |
61 | const uint8_t protocol = (source.isIPv4() ? 0x10 : 0x20) | (tcp ? 0x01 : 0x02); |
62 | const size_t addrSize = source.isIPv4() ? sizeof(source.sin4.sin_addr.s_addr) : sizeof(source.sin6.sin6_addr.s6_addr); | |
63 | const uint16_t sourcePort = source.sin4.sin_port; | |
64 | const uint16_t destinationPort = destination.sin4.sin_port; | |
8a79a6c4 RG |
65 | |
66 | size_t valuesSize = 0; | |
67 | for (const auto& value : values) { | |
68 | if (value.content.size() > std::numeric_limits<uint16_t>::max()) { | |
69 | 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())); | |
70 | } | |
71 | valuesSize += sizeof(uint8_t) + sizeof(uint8_t) * 2 + value.content.size(); | |
72 | } | |
73 | ||
1088cc4f RG |
74 | size_t total = (addrSize * 2) + sizeof(sourcePort) + sizeof(destinationPort) + valuesSize; |
75 | if (total > std::numeric_limits<uint16_t>::max()) { | |
76 | 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(total)); | |
77 | } | |
463ec5fd | 78 | |
1088cc4f | 79 | const uint16_t contentlen = htons(static_cast<uint16_t>(total)); |
8c73c703 | 80 | std::string ret = makeSimpleHeader(command, protocol, contentlen); |
463ec5fd PD |
81 | |
82 | // We already established source and destination sin_family equivalence | |
83 | if (source.isIPv4()) { | |
84 | assert(addrSize == sizeof(source.sin4.sin_addr.s_addr)); | |
85 | ret.append(reinterpret_cast<const char*>(&source.sin4.sin_addr.s_addr), addrSize); | |
86 | assert(addrSize == sizeof(destination.sin4.sin_addr.s_addr)); | |
87 | ret.append(reinterpret_cast<const char*>(&destination.sin4.sin_addr.s_addr), addrSize); | |
88 | } | |
89 | else { | |
90 | assert(addrSize == sizeof(source.sin6.sin6_addr.s6_addr)); | |
91 | ret.append(reinterpret_cast<const char*>(&source.sin6.sin6_addr.s6_addr), addrSize); | |
92 | assert(addrSize == sizeof(destination.sin6.sin6_addr.s6_addr)); | |
93 | ret.append(reinterpret_cast<const char*>(&destination.sin6.sin6_addr.s6_addr), addrSize); | |
94 | } | |
95 | ||
96 | ret.append(reinterpret_cast<const char*>(&sourcePort), sizeof(sourcePort)); | |
97 | ret.append(reinterpret_cast<const char*>(&destinationPort), sizeof(destinationPort)); | |
98 | ||
8a79a6c4 RG |
99 | for (const auto& value : values) { |
100 | uint16_t contentSize = htons(static_cast<uint16_t>(value.content.size())); | |
101 | ret.append(reinterpret_cast<const char*>(&value.type), sizeof(value.type)); | |
102 | ret.append(reinterpret_cast<const char*>(&contentSize), sizeof(contentSize)); | |
103 | ret.append(reinterpret_cast<const char*>(value.content.data()), value.content.size()); | |
104 | } | |
105 | ||
463ec5fd PD |
106 | return ret; |
107 | } | |
108 | ||
109 | /* returns: number of bytes consumed (positive) after successful parse | |
110 | or number of bytes missing (negative) | |
111 | or unfixable parse error (0)*/ | |
8c73c703 | 112 | ssize_t isProxyHeaderComplete(const std::string& header, bool* proxy, bool* tcp, size_t* addrSizeOut, uint8_t* protocolOut) |
463ec5fd | 113 | { |
bde73d5b RG |
114 | static const size_t addr4Size = sizeof(ComboAddress::sin4.sin_addr.s_addr); |
115 | static const size_t addr6Size = sizeof(ComboAddress::sin6.sin6_addr.s6_addr); | |
8c73c703 | 116 | size_t addrSize = 0; |
463ec5fd PD |
117 | uint8_t versioncommand; |
118 | uint8_t protocol; | |
119 | ||
bde73d5b | 120 | if (header.size() < s_proxyProtocolMinimumHeaderSize) { |
463ec5fd | 121 | // this is too short to be a complete proxy header |
bde73d5b | 122 | return -(s_proxyProtocolMinimumHeaderSize - header.size()); |
463ec5fd PD |
123 | } |
124 | ||
bde73d5b | 125 | if (header.compare(0, proxymagic.size(), proxymagic) != 0) { |
463ec5fd PD |
126 | // wrong magic, can not be a proxy header |
127 | return 0; | |
128 | } | |
129 | ||
130 | versioncommand = header.at(12); | |
8c73c703 RG |
131 | /* check version */ |
132 | if (!(versioncommand & 0x20)) { | |
463ec5fd PD |
133 | return 0; |
134 | } | |
135 | ||
8c73c703 RG |
136 | /* remove the version to get the command */ |
137 | uint8_t command = versioncommand & ~0x20; | |
138 | ||
139 | if (command == 0x01) { | |
140 | protocol = header.at(13); | |
141 | if ((protocol & 0xf) == 1) { | |
142 | if (tcp) { | |
143 | *tcp = true; | |
144 | } | |
145 | } else if ((protocol & 0xf) == 2) { | |
146 | if (tcp) { | |
147 | *tcp = false; | |
148 | } | |
149 | } else { | |
150 | return 0; | |
bde73d5b | 151 | } |
8c73c703 RG |
152 | |
153 | protocol = protocol >> 4; | |
154 | ||
155 | if (protocol == 1) { | |
156 | if (protocolOut) { | |
157 | *protocolOut = 4; | |
158 | } | |
159 | addrSize = addr4Size; // IPv4 | |
160 | } else if (protocol == 2) { | |
161 | if (protocolOut) { | |
162 | *protocolOut = 6; | |
163 | } | |
164 | addrSize = addr6Size; // IPv6 | |
165 | } else { | |
166 | // invalid protocol | |
167 | return 0; | |
bde73d5b | 168 | } |
463ec5fd | 169 | |
8c73c703 RG |
170 | if (addrSizeOut) { |
171 | *addrSizeOut = addrSize; | |
172 | } | |
463ec5fd | 173 | |
8c73c703 RG |
174 | if (proxy) { |
175 | *proxy = true; | |
bde73d5b | 176 | } |
8c73c703 RG |
177 | } |
178 | else if (command == 0x00) { | |
179 | if (proxy) { | |
180 | *proxy = false; | |
bde73d5b | 181 | } |
463ec5fd | 182 | } |
8c73c703 RG |
183 | else { |
184 | /* unsupported command */ | |
185 | return 0; | |
bde73d5b RG |
186 | } |
187 | ||
9a6a9e15 | 188 | uint16_t contentlen = (static_cast<uint8_t>(header.at(14)) << 8) + static_cast<uint8_t>(header.at(15)); |
8c73c703 RG |
189 | uint16_t expectedlen = 0; |
190 | if (command != 0x00) { | |
191 | expectedlen = (addrSize * 2) + sizeof(ComboAddress::sin4.sin_port) + sizeof(ComboAddress::sin4.sin_port); | |
192 | } | |
463ec5fd | 193 | |
bde73d5b | 194 | if (contentlen < expectedlen) { |
463ec5fd PD |
195 | return 0; |
196 | } | |
197 | ||
bde73d5b RG |
198 | if (header.size() < s_proxyProtocolMinimumHeaderSize + contentlen) { |
199 | return -((s_proxyProtocolMinimumHeaderSize + contentlen) - header.size()); | |
200 | } | |
201 | ||
202 | return s_proxyProtocolMinimumHeaderSize + contentlen; | |
203 | } | |
204 | ||
205 | /* returns: number of bytes consumed (positive) after successful parse | |
206 | or number of bytes missing (negative) | |
207 | or unfixable parse error (0)*/ | |
8c73c703 | 208 | ssize_t parseProxyHeader(const std::string& header, bool& proxy, ComboAddress& source, ComboAddress& destination, bool& tcp, std::vector<ProxyProtocolValue>& values) |
bde73d5b RG |
209 | { |
210 | size_t addrSize = 0; | |
211 | uint8_t protocol = 0; | |
8c73c703 | 212 | ssize_t got = isProxyHeaderComplete(header, &proxy, &tcp, &addrSize, &protocol); |
bde73d5b RG |
213 | if (got <= 0) { |
214 | return got; | |
463ec5fd PD |
215 | } |
216 | ||
bde73d5b | 217 | size_t pos = s_proxyProtocolMinimumHeaderSize; |
463ec5fd | 218 | |
8c73c703 RG |
219 | if (proxy) { |
220 | source = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize); | |
221 | pos = pos + addrSize; | |
222 | destination = makeComboAddressFromRaw(protocol, &header.at(pos), addrSize); | |
223 | pos = pos + addrSize; | |
9a6a9e15 | 224 | source.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); |
8c73c703 | 225 | pos = pos + sizeof(uint16_t); |
9a6a9e15 | 226 | destination.setPort((static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos+1))); |
8c73c703 RG |
227 | pos = pos + sizeof(uint16_t); |
228 | } | |
463ec5fd | 229 | |
8a79a6c4 RG |
230 | size_t remaining = got - pos; |
231 | while (remaining >= (sizeof(uint8_t) + sizeof(uint16_t))) { | |
232 | /* we still have TLV values to parse */ | |
233 | uint8_t type = static_cast<uint8_t>(header.at(pos)); | |
234 | pos += sizeof(uint8_t); | |
9a6a9e15 | 235 | uint16_t len = (static_cast<uint8_t>(header.at(pos)) << 8) + static_cast<uint8_t>(header.at(pos + 1)); |
8a79a6c4 RG |
236 | pos += sizeof(uint16_t); |
237 | ||
238 | if (len > 0) { | |
239 | if (len > (got - pos)) { | |
240 | return 0; | |
241 | } | |
242 | ||
243 | values.push_back({ std::string(&header.at(pos), len), type }); | |
244 | pos += len; | |
245 | } | |
246 | else { | |
247 | values.push_back({ std::string(), type }); | |
248 | } | |
249 | ||
250 | remaining = got - pos; | |
251 | } | |
252 | ||
463ec5fd PD |
253 | return pos; |
254 | } |