]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Add a module which implements the HAProxy PROXY v2 protocol.
authorPeter Powell <petpow@saberuk.com>
Thu, 26 Jul 2018 11:13:13 +0000 (12:13 +0100)
committerPeter Powell <petpow@saberuk.com>
Thu, 26 Jul 2018 11:35:22 +0000 (12:35 +0100)
docs/conf/modules.conf.example
include/inspsocket.h
include/users.h
src/modules/m_haproxy.cpp [new file with mode: 0644]
src/users.cpp

index 1b35c4a1e548c167e3f437b85242457a98a00ea0..c8fd94c06c3ba64721608926c75aa2aba6ed8944 100644 (file)
 # must be in one of your oper class blocks.
 #<module name="globalload">
 
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# HAProxy module: Adds support for the HAProxy PROXY v2 protocol. To
+# use this module specify hook="haproxy" in the <bind> tag that HAProxy
+# has been configured to connect to.
+#<module name="haproxy">
+
 #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 # HELPOP module: Provides the /HELPOP command
 #<module name="helpop">
index addb761d119037c510eed2021972fc52ba9a513f..5bdbfd6524ec5bc395880b99dd4f9a147d078ebd 100644 (file)
@@ -297,6 +297,12 @@ class CoreExport StreamSocket : public EventHandler
        /** Called when the socket gets an error from socket engine or IO hook */
        virtual void OnError(BufferedSocketError e) = 0;
 
+       /** Called when the endpoint addresses are changed.
+        * @param local The new local endpoint.
+        * @param remote The new remote endpoint.
+        */
+       virtual void OnSetEndPoint(const irc::sockets::sockaddrs& server, const irc::sockets::sockaddrs& remote) { }
+
        /** Send the given data out the socket, either now or when writes unblock
         */
        void WriteData(const std::string& data);
index 12a311980254766fa007651664ef1fedb6b00bbc..88858b160f9352a6f78d0fa3de0847b1e9541b43 100644 (file)
@@ -736,6 +736,7 @@ class CoreExport UserIOHandler : public StreamSocket
        LocalUser* const user;
        UserIOHandler(LocalUser* me) : user(me) {}
        void OnDataReady() CXX11_OVERRIDE;
+       void OnSetEndPoint(const irc::sockets::sockaddrs& local, const irc::sockets::sockaddrs& remote) CXX11_OVERRIDE;
        void OnError(BufferedSocketError error) CXX11_OVERRIDE;
 
        /** Adds to the user's write buffer.
diff --git a/src/modules/m_haproxy.cpp b/src/modules/m_haproxy.cpp
new file mode 100644 (file)
index 0000000..8272c29
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2018 Peter Powell <petpow@saberuk.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "iohook.h"
+
+enum
+{
+       // The family for TCP over IPv4.
+       PP2_FAMILY_IPV4 = 0x11,
+
+       // The length of the PP2_FAMILY_IPV4 endpoints.
+       PP2_FAMILY_IPV4_LENGTH = 12,
+
+       // The family for TCP over IPv6.
+       PP2_FAMILY_IPV6 = 0x21,
+
+       // The length of the PP2_FAMILY_IPV6 endpoints.
+       PP2_FAMILY_IPV6_LENGTH = 36,
+
+       // The family for UNIX sockets.
+       PP2_FAMILY_UNIX = 0x31,
+
+       // The length of the PP2_FAMILY_UNIX endpoints.
+       PP2_FAMILY_UNIX_LENGTH = 216,
+
+       // The bitmask we apply to extract the command.
+       PP2_COMMAND_MASK = 0x0F,
+
+       // The length of the PROXY protocol header.
+       PP2_HEADER_LENGTH = 16,
+
+       // The length of the PROXY protocol signature.
+       PP2_SIGNATURE_LENGTH = 12,
+
+       // The PROXY protocol version we support.
+       PP2_VERSION = 0x20,
+
+       // The bitmask we apply to extract the protocol version.
+       PP2_VERSION_MASK = 0xF0
+};
+
+enum HAProxyState
+{
+       // We are waiting for the PROXY header section.
+       HPS_WAITING_FOR_HEADER,
+
+       // We are waiting for the PROXY address section.
+       HPS_WAITING_FOR_ADDRESS,
+
+       // The client is fully connected.
+       HPS_CONNECTED
+};
+
+enum HAProxyCommand
+{
+       // LOCAL command.
+       HPC_LOCAL = 0x00,
+
+       // PROXY command.
+       HPC_PROXY = 0x01
+};
+
+struct HAProxyHeader
+{
+       // The signature used to identify the HAProxy protocol.
+       uint8_t signature[PP2_SIGNATURE_LENGTH];
+
+       // The version of the PROXY protocol and command being sent.
+       uint8_t version_command;
+
+       // The family for the address.
+       uint8_t family;
+
+       // The length of the address section.
+       uint16_t length;
+};
+
+class HAProxyHookProvider : public IOHookProvider
+{
+ public:
+       HAProxyHookProvider(Module* mod)
+               : IOHookProvider(mod, "haproxy", IOHookProvider::IOH_UNKNOWN, true)
+       {
+       }
+
+       void OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE;
+
+       void OnConnect(StreamSocket* sock) CXX11_OVERRIDE
+       {
+               // We don't need to implement this.
+       }
+};
+
+// The signature for a HAProxy PROXY protocol header.
+static const char proxy_signature[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
+
+class HAProxyHook : public IOHookMiddle
+{
+ private:
+       // The length of the address section.
+       uint16_t address_length;
+
+       // The endpoint the client is connecting from.
+       irc::sockets::sockaddrs client;
+
+       // The command sent by the proxy server.
+       HAProxyCommand command;
+
+       // The endpoint the client is connected to.
+       irc::sockets::sockaddrs server;
+
+       // The current state of the PROXY parser.
+       HAProxyState state;
+
+       int ReadProxyAddress(StreamSocket* sock)
+       {
+               // Block until we have the entire address.
+               std::string& recvq = GetRecvQ();
+               if (recvq.length() < address_length)
+                       return 0;
+
+               switch (command)
+               {
+                       case HPC_LOCAL:
+                               // Skip the address completely.
+                               recvq.erase(0, address_length);
+                               break;
+
+                       case HPC_PROXY:
+                               // Store the endpoint information.
+                               switch (client.family())
+                               {
+                                       case AF_INET:
+                                               memcpy(&client.in4.sin_addr.s_addr, &recvq[0], 4);
+                                               memcpy(&server.in4.sin_addr.s_addr, &recvq[4], 8);
+                                               memcpy(&client.in4.sin_port, &recvq[8], 2);
+                                               memcpy(&server.in4.sin_port, &recvq[10], 2);
+                                               break;
+
+                                       case AF_INET6:
+                                               memcpy(client.in6.sin6_addr.s6_addr, &recvq[0], 16);
+                                               memcpy(server.in6.sin6_addr.s6_addr, &recvq[16], 16);
+                                               memcpy(&client.in6.sin6_port, &recvq[32], 2);
+                                               memcpy(&server.in6.sin6_port, &recvq[34], 2);
+                                               break;
+
+                                       case AF_UNIX:
+                                               memcpy(client.un.sun_path, &recvq[0], 108);
+                                               memcpy(client.un.sun_path, &recvq[108], 108);
+                                               break;
+                               }
+
+                               sock->OnSetEndPoint(server, client);
+
+                               // XXX: HAProxy's PROXY v2 specification defines Type-Length-Values that
+                               // could appear here but as of 2018-07-25 it does not send anything. We
+                               // should revisit this in the future to see if they actually send them.
+                               recvq.erase(0, address_length);
+               }
+
+               // We're done!
+               state = HPS_CONNECTED;
+               return 1;
+       }
+
+       int ReadProxyHeader(StreamSocket* sock)
+       {
+               // Block until we have a header.
+               std::string& recvq = GetRecvQ();
+               if (recvq.length() < PP2_HEADER_LENGTH)
+                       return 0;
+
+               // Read the header.
+               HAProxyHeader header;
+               memcpy(&header, recvq.c_str(), PP2_HEADER_LENGTH);
+               recvq.erase(0, PP2_HEADER_LENGTH);
+
+               // Check we are actually parsing a HAProxy header.
+               if (memcmp(&header.signature, proxy_signature, PP2_SIGNATURE_LENGTH) != 0)
+               {
+                       // If we've reached this point the proxy server did not send a proxy information.
+                       sock->SetError("Invalid HAProxy PROXY signature");
+                       return -1;
+               }
+
+               // We only support this version of the protocol.
+               const uint8_t version = (header.version_command & PP2_VERSION_MASK);
+               if (version != PP2_VERSION)
+               {
+                       sock->SetError("Unsupported HAProxy PROXY protocol version");
+                       return -1;
+               }
+
+               // We only support the LOCAL and PROXY commands.
+               command = static_cast<HAProxyCommand>(header.version_command & PP2_COMMAND_MASK);
+               switch (command)
+               {
+                       case HPC_LOCAL:
+                               // Intentionally left blank.
+                               break;
+
+                       case HPC_PROXY:
+                               // Check the protocol support and initialise the sockaddrs.
+                               uint16_t shortest_length;
+                               switch (header.family)
+                               {
+                                       case PP2_FAMILY_IPV4: // TCP over IPv4.
+                                               client.sa.sa_family = server.sa.sa_family = AF_INET;
+                                               shortest_length = PP2_FAMILY_IPV4_LENGTH;
+                                               break;
+
+                                       case PP2_FAMILY_IPV6: // TCP over IPv6.
+                                               client.sa.sa_family = server.sa.sa_family = AF_INET6;
+                                               shortest_length = PP2_FAMILY_IPV6_LENGTH;
+                                               break;
+
+                                       case PP2_FAMILY_UNIX: // UNIX stream.
+                                               client.sa.sa_family = server.sa.sa_family = AF_UNIX;
+                                               shortest_length = PP2_FAMILY_UNIX_LENGTH;
+                                               break;
+
+                                       default: // Unknown protocol.
+                                               sock->SetError("Invalid HAProxy PROXY protocol type");
+                                               return -1;
+                               }
+
+                               // Check that the length can actually contain the addresses.
+                               address_length = ntohs(header.length);
+                               if (address_length < shortest_length)
+                               {
+                                       sock->SetError("Truncated HAProxy PROXY address section");
+                                       return -1;
+                               }
+                               break;
+
+                       default:
+                               sock->SetError("Unsupported HAProxy PROXY command");
+                               return -1;
+               }
+
+               state = HPS_WAITING_FOR_ADDRESS;
+               return ReadProxyAddress(sock);
+       }
+
+ public:
+       HAProxyHook(IOHookProvider* Prov, StreamSocket* sock)
+               : IOHookMiddle(Prov)
+               , state(HPS_WAITING_FOR_HEADER)
+       {
+               sock->AddIOHook(this);
+       }
+
+       int OnStreamSocketWrite(StreamSocket* sock, StreamSocket::SendQueue& uppersendq) CXX11_OVERRIDE
+       {
+               // We don't need to implement this.
+               GetSendQ().moveall(uppersendq);
+               return 1;
+       }
+
+       int OnStreamSocketRead(StreamSocket* sock, std::string& destrecvq) CXX11_OVERRIDE
+       {
+               switch (state)
+               {
+                       case HPS_WAITING_FOR_HEADER:
+                               return ReadProxyHeader(sock);
+
+                       case HPS_WAITING_FOR_ADDRESS:
+                               return ReadProxyAddress(sock);
+
+                       case HPS_CONNECTED:
+                               std::string& recvq = GetRecvQ();
+                               destrecvq.append(recvq);
+                               recvq.clear();
+                               return 1;
+               }
+
+               // We should never reach this point.
+               return -1;
+       }
+
+       void OnStreamSocketClose(StreamSocket* sock) CXX11_OVERRIDE
+       {
+               // We don't need to implement this.
+       }
+};
+
+void HAProxyHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
+{
+       new HAProxyHook(this, sock);
+}
+
+class ModuleHAProxy : public Module
+{
+ private:
+       reference<HAProxyHookProvider> hookprov;
+
+ public:
+       ModuleHAProxy()
+               : hookprov(new HAProxyHookProvider(this))
+       {
+       }
+
+       Version GetVersion() CXX11_OVERRIDE
+       {
+               return Version("Provides support for the HAProxy PROXY protocol", VF_VENDOR);
+       }
+};
+
+MODULE_INIT(ModuleHAProxy)
index 04a8f959a0160b7eebe3155a6490834350645195..442770aca3513118291fbbb64300fb46e106d6ba 100644 (file)
@@ -315,6 +315,12 @@ void UserIOHandler::AddWriteBuf(const std::string &data)
        WriteData(data);
 }
 
+void UserIOHandler::OnSetEndPoint(const irc::sockets::sockaddrs& server, const irc::sockets::sockaddrs& client)
+{
+       memcpy(&user->server_sa, &server, sizeof(irc::sockets::sockaddrs));
+       user->SetClientIP(client);
+}
+
 void UserIOHandler::OnError(BufferedSocketError)
 {
        ServerInstance->Users->QuitUser(user, getError());