2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
6 * This file is part of InspIRCd. InspIRCd is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, version 2.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 // The family for TCP over IPv4.
26 PP2_FAMILY_IPV4 = 0x11,
28 // The length of the PP2_FAMILY_IPV4 endpoints.
29 PP2_FAMILY_IPV4_LENGTH = 12,
31 // The family for TCP over IPv6.
32 PP2_FAMILY_IPV6 = 0x21,
34 // The length of the PP2_FAMILY_IPV6 endpoints.
35 PP2_FAMILY_IPV6_LENGTH = 36,
37 // The family for UNIX sockets.
38 PP2_FAMILY_UNIX = 0x31,
40 // The length of the PP2_FAMILY_UNIX endpoints.
41 PP2_FAMILY_UNIX_LENGTH = 216,
43 // The bitmask we apply to extract the command.
44 PP2_COMMAND_MASK = 0x0F,
46 // The length of the PROXY protocol header.
47 PP2_HEADER_LENGTH = 16,
49 // The length of the PROXY protocol signature.
50 PP2_SIGNATURE_LENGTH = 12,
52 // The PROXY protocol version we support.
55 // The bitmask we apply to extract the protocol version.
56 PP2_VERSION_MASK = 0xF0
61 // We are waiting for the PROXY header section.
62 HPS_WAITING_FOR_HEADER,
64 // We are waiting for the PROXY address section.
65 HPS_WAITING_FOR_ADDRESS,
67 // The client is fully connected.
82 // The signature used to identify the HAProxy protocol.
83 uint8_t signature[PP2_SIGNATURE_LENGTH];
85 // The version of the PROXY protocol and command being sent.
86 uint8_t version_command;
88 // The family for the address.
91 // The length of the address section.
95 class HAProxyHookProvider : public IOHookProvider
98 HAProxyHookProvider(Module* mod)
99 : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true)
103 void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE;
105 void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
107 // We don't need to implement this.
111 // The signature for a HAProxy PROXY protocol header.
112 static const char proxy_signature[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
114 class HAProxyHook : public IOHookMiddle
117 // The length of the address section.
118 uint16_t address_length;
120 // The endpoint the client is connecting from.
121 irc::sockets::sockaddrs client;
123 // The command sent by the proxy server.
124 HAProxyCommand command;
126 // The endpoint the client is connected to.
127 irc::sockets::sockaddrs server;
129 // The current state of the PROXY parser.
132 int ReadProxyAddress(StreamSocket* sock)
134 // Block until we have the entire address.
135 std::string& recvq = GetRecvQ();
136 if (recvq.length() < address_length)
142 // Skip the address completely.
143 recvq.erase(0, address_length);
147 // Store the endpoint information.
148 switch (client.family())
151 memcpy(&client.in4.sin_addr.s_addr, &recvq[0], 4);
152 memcpy(&server.in4.sin_addr.s_addr, &recvq[4], 8);
153 memcpy(&client.in4.sin_port, &recvq[8], 2);
154 memcpy(&server.in4.sin_port, &recvq[10], 2);
158 memcpy(client.in6.sin6_addr.s6_addr, &recvq[0], 16);
159 memcpy(server.in6.sin6_addr.s6_addr, &recvq[16], 16);
160 memcpy(&client.in6.sin6_port, &recvq[32], 2);
161 memcpy(&server.in6.sin6_port, &recvq[34], 2);
165 memcpy(client.un.sun_path, &recvq[0], 108);
166 memcpy(client.un.sun_path, &recvq[108], 108);
170 sock->OnSetEndPoint(server, client);
172 // XXX: HAProxy's PROXY v2 specification defines Type-Length-Values that
173 // could appear here but as of 2018-07-25 it does not send anything. We
174 // should revisit this in the future to see if they actually send them.
175 recvq.erase(0, address_length);
179 state = HPS_CONNECTED;
183 int ReadProxyHeader(StreamSocket* sock)
185 // Block until we have a header.
186 std::string& recvq = GetRecvQ();
187 if (recvq.length() < PP2_HEADER_LENGTH)
191 HAProxyHeader header;
192 memcpy(&header, recvq.c_str(), PP2_HEADER_LENGTH);
193 recvq.erase(0, PP2_HEADER_LENGTH);
195 // Check we are actually parsing a HAProxy header.
196 if (memcmp(&header.signature, proxy_signature, PP2_SIGNATURE_LENGTH) != 0)
198 // If we've reached this point the proxy server did not send a proxy information.
199 sock->SetError("Invalid HAProxy PROXY signature");
203 // We only support this version of the protocol.
204 const uint8_t version = (header.version_command & PP2_VERSION_MASK);
205 if (version != PP2_VERSION)
207 sock->SetError("Unsupported HAProxy PROXY protocol version");
211 // We only support the LOCAL and PROXY commands.
212 command = static_cast<HAProxyCommand>(header.version_command & PP2_COMMAND_MASK);
216 // Intentionally left blank.
220 // Check the protocol support and initialise the sockaddrs.
221 uint16_t shortest_length;
222 switch (header.family)
224 case PP2_FAMILY_IPV4: // TCP over IPv4.
225 client.sa.sa_family = server.sa.sa_family = AF_INET;
226 shortest_length = PP2_FAMILY_IPV4_LENGTH;
229 case PP2_FAMILY_IPV6: // TCP over IPv6.
230 client.sa.sa_family = server.sa.sa_family = AF_INET6;
231 shortest_length = PP2_FAMILY_IPV6_LENGTH;
234 case PP2_FAMILY_UNIX: // UNIX stream.
235 client.sa.sa_family = server.sa.sa_family = AF_UNIX;
236 shortest_length = PP2_FAMILY_UNIX_LENGTH;
239 default: // Unknown protocol.
240 sock->SetError("Invalid HAProxy PROXY protocol type");
244 // Check that the length can actually contain the addresses.
245 address_length = ntohs(header.length);
246 if (address_length < shortest_length)
248 sock->SetError("Truncated HAProxy PROXY address section");
254 sock->SetError("Unsupported HAProxy PROXY command");
258 state = HPS_WAITING_FOR_ADDRESS;
259 return ReadProxyAddress(sock);
263 HAProxyHook(IOHookProvider* Prov, StreamSocket* sock)
265 , state(HPS_WAITING_FOR_HEADER)
267 sock->AddIOHook(this);
270 int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) CXX11_OVERRIDE
272 // We don't need to implement this.
273 GetSendQ().moveall(uppersendq);
277 int OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) CXX11_OVERRIDE
281 case HPS_WAITING_FOR_HEADER:
282 return ReadProxyHeader(sock);
284 case HPS_WAITING_FOR_ADDRESS:
285 return ReadProxyAddress(sock);
288 std::string& recvq = GetRecvQ();
289 destrecvq.append(recvq);
294 // We should never reach this point.
298 void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE
300 // We don't need to implement this.
304 void HAProxyHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
306 new HAProxyHook(this, sock);
309 class ModuleHAProxy : public Module
312 reference<HAProxyHookProvider> hookprov;
316 : hookprov(new HAProxyHookProvider(this))
320 Version GetVersion() CXX11_OVERRIDE
322 return Version("Provides support for the HAProxy PROXY protocol", VF_VENDOR);
326 MODULE_INIT(ModuleHAProxy)