]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_haproxy.cpp
Add a module which implements the HAProxy PROXY v2 protocol.
[user/henk/code/inspircd.git] / src / modules / m_haproxy.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
5  *
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.
9  *
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
13  * details.
14  *
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/>.
17  */
18
19
20 #include "inspircd.h"
21 #include "iohook.h"
22
23 enum
24 {
25         // The family for TCP over IPv4.
26         PP2_FAMILY_IPV4 = 0x11,
27
28         // The length of the PP2_FAMILY_IPV4 endpoints.
29         PP2_FAMILY_IPV4_LENGTH = 12,
30
31         // The family for TCP over IPv6.
32         PP2_FAMILY_IPV6 = 0x21,
33
34         // The length of the PP2_FAMILY_IPV6 endpoints.
35         PP2_FAMILY_IPV6_LENGTH = 36,
36
37         // The family for UNIX sockets.
38         PP2_FAMILY_UNIX = 0x31,
39
40         // The length of the PP2_FAMILY_UNIX endpoints.
41         PP2_FAMILY_UNIX_LENGTH = 216,
42
43         // The bitmask we apply to extract the command.
44         PP2_COMMAND_MASK = 0x0F,
45
46         // The length of the PROXY protocol header.
47         PP2_HEADER_LENGTH = 16,
48
49         // The length of the PROXY protocol signature.
50         PP2_SIGNATURE_LENGTH = 12,
51
52         // The PROXY protocol version we support.
53         PP2_VERSION = 0x20,
54
55         // The bitmask we apply to extract the protocol version.
56         PP2_VERSION_MASK = 0xF0
57 };
58
59 enum HAProxyState
60 {
61         // We are waiting for the PROXY header section.
62         HPS_WAITING_FOR_HEADER,
63
64         // We are waiting for the PROXY address section.
65         HPS_WAITING_FOR_ADDRESS,
66
67         // The client is fully connected.
68         HPS_CONNECTED
69 };
70
71 enum HAProxyCommand
72 {
73         // LOCAL command.
74         HPC_LOCAL = 0x00,
75
76         // PROXY command.
77         HPC_PROXY = 0x01
78 };
79
80 struct HAProxyHeader
81 {
82         // The signature used to identify the HAProxy protocol.
83         uint8_t signature[PP2_SIGNATURE_LENGTH];
84
85         // The version of the PROXY protocol and command being sent.
86         uint8_t version_command;
87
88         // The family for the address.
89         uint8_t family;
90
91         // The length of the address section.
92         uint16_t length;
93 };
94
95 class HAProxyHookProvider : public IOHookProvider
96 {
97  public:
98         HAProxyHookProvider(Module* mod)
99                 : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true)
100         {
101         }
102
103         void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE;
104
105         void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
106         {
107                 // We don't need to implement this.
108         }
109 };
110
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";
113
114 class HAProxyHook : public IOHookMiddle
115 {
116  private:
117         // The length of the address section.
118         uint16_t address_length;
119
120         // The endpoint the client is connecting from.
121         irc::sockets::sockaddrs client;
122
123         // The command sent by the proxy server.
124         HAProxyCommand command;
125
126         // The endpoint the client is connected to.
127         irc::sockets::sockaddrs server;
128
129         // The current state of the PROXY parser.
130         HAProxyState state;
131
132         int ReadProxyAddress(StreamSocket* sock)
133         {
134                 // Block until we have the entire address.
135                 std::string& recvq = GetRecvQ();
136                 if (recvq.length() < address_length)
137                         return 0;
138
139                 switch (command)
140                 {
141                         case HPC_LOCAL:
142                                 // Skip the address completely.
143                                 recvq.erase(0, address_length);
144                                 break;
145
146                         case HPC_PROXY:
147                                 // Store the endpoint information.
148                                 switch (client.family())
149                                 {
150                                         case AF_INET:
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);
155                                                 break;
156
157                                         case AF_INET6:
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);
162                                                 break;
163
164                                         case AF_UNIX:
165                                                 memcpy(client.un.sun_path, &recvq[0], 108);
166                                                 memcpy(client.un.sun_path, &recvq[108], 108);
167                                                 break;
168                                 }
169
170                                 sock->OnSetEndPoint(server, client);
171
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);
176                 }
177
178                 // We're done!
179                 state = HPS_CONNECTED;
180                 return 1;
181         }
182
183         int ReadProxyHeader(StreamSocket* sock)
184         {
185                 // Block until we have a header.
186                 std::string& recvq = GetRecvQ();
187                 if (recvq.length() < PP2_HEADER_LENGTH)
188                         return 0;
189
190                 // Read the header.
191                 HAProxyHeader header;
192                 memcpy(&header, recvq.c_str(), PP2_HEADER_LENGTH);
193                 recvq.erase(0, PP2_HEADER_LENGTH);
194
195                 // Check we are actually parsing a HAProxy header.
196                 if (memcmp(&header.signature, proxy_signature, PP2_SIGNATURE_LENGTH) != 0)
197                 {
198                         // If we've reached this point the proxy server did not send a proxy information.
199                         sock->SetError("Invalid HAProxy PROXY signature");
200                         return -1;
201                 }
202
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)
206                 {
207                         sock->SetError("Unsupported HAProxy PROXY protocol version");
208                         return -1;
209                 }
210
211                 // We only support the LOCAL and PROXY commands.
212                 command = static_cast<HAProxyCommand>(header.version_command & PP2_COMMAND_MASK);
213                 switch (command)
214                 {
215                         case HPC_LOCAL:
216                                 // Intentionally left blank.
217                                 break;
218
219                         case HPC_PROXY:
220                                 // Check the protocol support and initialise the sockaddrs.
221                                 uint16_t shortest_length;
222                                 switch (header.family)
223                                 {
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;
227                                                 break;
228
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;
232                                                 break;
233
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;
237                                                 break;
238
239                                         default: // Unknown protocol.
240                                                 sock->SetError("Invalid HAProxy PROXY protocol type");
241                                                 return -1;
242                                 }
243
244                                 // Check that the length can actually contain the addresses.
245                                 address_length = ntohs(header.length);
246                                 if (address_length < shortest_length)
247                                 {
248                                         sock->SetError("Truncated HAProxy PROXY address section");
249                                         return -1;
250                                 }
251                                 break;
252
253                         default:
254                                 sock->SetError("Unsupported HAProxy PROXY command");
255                                 return -1;
256                 }
257
258                 state = HPS_WAITING_FOR_ADDRESS;
259                 return ReadProxyAddress(sock);
260         }
261
262  public:
263         HAProxyHook(IOHookProvider* Prov, StreamSocket* sock)
264                 : IOHookMiddle(Prov)
265                 , state(HPS_WAITING_FOR_HEADER)
266         {
267                 sock->AddIOHook(this);
268         }
269
270         int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) CXX11_OVERRIDE
271         {
272                 // We don't need to implement this.
273                 GetSendQ().moveall(uppersendq);
274                 return 1;
275         }
276
277         int OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) CXX11_OVERRIDE
278         {
279                 switch (state)
280                 {
281                         case HPS_WAITING_FOR_HEADER:
282                                 return ReadProxyHeader(sock);
283
284                         case HPS_WAITING_FOR_ADDRESS:
285                                 return ReadProxyAddress(sock);
286
287                         case HPS_CONNECTED:
288                                 std::string& recvq = GetRecvQ();
289                                 destrecvq.append(recvq);
290                                 recvq.clear();
291                                 return 1;
292                 }
293
294                 // We should never reach this point.
295                 return -1;
296         }
297
298         void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE
299         {
300                 // We don't need to implement this.
301         }
302 };
303
304 void HAProxyHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
305 {
306         new HAProxyHook(this, sock);
307 }
308
309 class ModuleHAProxy : public Module
310 {
311  private:
312         reference<HAProxyHookProvider> hookprov;
313
314  public:
315         ModuleHAProxy()
316                 : hookprov(new HAProxyHookProvider(this))
317         {
318         }
319
320         Version GetVersion() CXX11_OVERRIDE
321         {
322                 return Version("Provides support for the HAProxy PROXY protocol", VF_VENDOR);
323         }
324 };
325
326 MODULE_INIT(ModuleHAProxy)