From 09c5439c02f31e9875083e51966dad535af005a9 Mon Sep 17 00:00:00 2001 From: Peter Powell Date: Thu, 26 Jul 2018 12:13:13 +0100 Subject: [PATCH] Add a module which implements the HAProxy PROXY v2 protocol. --- docs/conf/modules.conf.example | 6 + include/inspsocket.h | 6 + include/users.h | 1 + src/modules/m_haproxy.cpp | 326 +++++++++++++++++++++++++++++++++ src/users.cpp | 6 + 5 files changed, 345 insertions(+) create mode 100644 src/modules/m_haproxy.cpp diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 1b35c4a1e..c8fd94c06 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -864,6 +864,12 @@ # must be in one of your oper class blocks. # +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# HAProxy module: Adds support for the HAProxy PROXY v2 protocol. To +# use this module specify hook="haproxy" in the tag that HAProxy +# has been configured to connect to. +# + #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # HELPOP module: Provides the /HELPOP command # diff --git a/include/inspsocket.h b/include/inspsocket.h index addb761d1..5bdbfd652 100644 --- a/include/inspsocket.h +++ b/include/inspsocket.h @@ -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); diff --git a/include/users.h b/include/users.h index 12a311980..88858b160 100644 --- a/include/users.h +++ b/include/users.h @@ -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 index 000000000..8272c2980 --- /dev/null +++ b/src/modules/m_haproxy.cpp @@ -0,0 +1,326 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2018 Peter Powell + * + * 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 . + */ + + +#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(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 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) diff --git a/src/users.cpp b/src/users.cpp index 04a8f959a..442770aca 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -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()); -- 2.39.2