/* # proxyprotocol: Support for HAProxy's proxyprotocol # # Copyright (C) 2025 Yves Rutschle # # This program 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; either # version 2 of the License, or (at your option) any later # version. # # 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. # # The full text for the General Public License is here: # http://www.gnu.org/licenses/gpl.html */ #include "config.h" #ifdef HAVE_PROXYPROTOCOL #include #include "common.h" #include "log.h" /* Converts socket family to libproxyprotocol family */ static int family_to_pp(int af_family) { switch (af_family) { case AF_INET: return ADDR_FAMILY_INET; case AF_INET6: return ADDR_FAMILY_INET6; case AF_UNIX: return ADDR_FAMILY_UNIX; default: print_message(msg_int_error, "Unknown internal socket family %d\n", af_family); return -1; } } typedef char libpp_addr[108]; /* This is hardcoded in libproxyprotocol/proxy_protocol.h */ typedef enum { PEER, SOCK } sockpeer_t; /* Fills *addr, *host and *serv with the connection information corresponding * to fd. sockpeer indicates if if is filled with the remote, or local, part of * the socket. * *host is the IP address as string and *serv is the service (port) * */ static int get_info(int fd, sockpeer_t sockpeer, struct addrinfo* addr, libpp_addr* host, uint16_t* serv) { char serv_str[NI_MAXSERV]; int res; if (sockpeer == PEER) { res = getpeername(fd, addr->ai_addr, &addr->ai_addrlen); CHECK_RES_RETURN(res, "getpeername", -1); } else { res = getsockname(fd, addr->ai_addr, &addr->ai_addrlen); CHECK_RES_RETURN(res, "getsockname", -1); } res = getnameinfo(addr->ai_addr, addr->ai_addrlen, (char*)host, sizeof(*host), serv_str, sizeof(serv_str), NI_NUMERICHOST | NI_NUMERICSERV ); if (res != 0) { print_message(msg_system_error, "getnameinfo: %s\n", gai_strerror(res)); return -1; } *serv = atoi(serv_str); return 0; } /* Turns an IPv4 address string into an IPv6 address string (i.e. prepends * "::fff:", in place. Assumes the buffer is big enough, but won't go over len */ static void pp_ipv4_to_ipv6(char* addr, int len) { char tmp[108]; /* magic number from proxy_protocol.h */ int res = snprintf(tmp, sizeof(tmp), "::ffff:%s", addr); if (res >= sizeof(tmp)) { print_message(msg_int_error, "Error converting IPv4 address to IPv6 address"); return; } memcpy(addr, tmp, res); } /* Returns the address family (ip4 or ip6) of the server-side of a connection * */ static int get_serverside_family(struct connection* cnx) { struct sockaddr addr; socklen_t addrlen = sizeof(addr); int res = getpeername(cnx->q[1].fd, &addr, &addrlen); CHECK_RES_RETURN(res, "getpeername", -1); return addr.sa_family; } int pp_write_header(int pp_version, struct connection* cnx) { pp_info_t pp_info_in_v1 = { .transport_protocol = TRANSPORT_PROTOCOL_STREAM, }; uint16_t pp1_hdr_len; int32_t error; int src_family = AF_UNSPEC; int dst_family = AF_UNSPEC; struct sockaddr_storage ss; struct addrinfo addr; int res; addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); res = get_info(cnx->q[0].fd, PEER, &addr, &pp_info_in_v1.src_addr, &pp_info_in_v1.src_port); if (res == -1) return -1; src_family = addr.ai_addr->sa_family; res = get_info(cnx->q[0].fd, SOCK, &addr, &pp_info_in_v1.dst_addr, &pp_info_in_v1.dst_port ); if (res == -1) return -1; dst_family = get_serverside_family(cnx); pp_info_in_v1.address_family = family_to_pp(AF_INET); /* * If src IPv6/dst IPv4 or src IPv4/dst IPv6, * convert IPv4 to v4-mapped IPv6 (::ffff:a.b.c.d) * (refer to rfc4291, 2.2.3 for mixed-format) */ if (src_family != dst_family) { pp_ipv4_to_ipv6(pp_info_in_v1.dst_addr, sizeof(pp_info_in_v1.dst_addr)); pp_ipv4_to_ipv6(pp_info_in_v1.src_addr, sizeof(pp_info_in_v1.src_addr)); pp_info_in_v1.address_family = family_to_pp(AF_INET6); } uint8_t *pp1_hdr = pp_create_hdr(pp_version, &pp_info_in_v1, &pp1_hdr_len, &error); if (!pp1_hdr) { print_message(msg_system_error, "pp_create_hdr:%d:%s\n", error, pp_strerror(error)); return -1; } defer_write_before(&cnx->q[1], pp1_hdr, pp1_hdr_len); pp_info_clear(&pp_info_in_v1); free(pp1_hdr); return 0; } /* Logs the connection data in the pp header and return header length */ int pp_log_connection(char* buffer, ssize_t buffer_len) { pp_info_t pp_info; int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info); if (header_len > 0) { print_message(msg_connections, "client had proxyprotocol info: %s:%d to %s:%d not forwarded to server\n", pp_info.src_addr, pp_info.src_port, pp_info.dst_addr, pp_info.dst_port); } else { print_message(msg_connections, "no proxyprotocol data found on client side, forwarding as is\n"); header_len = 0; } return header_len; } int pp_header_len(char* buffer, int buffer_len) { pp_info_t pp_info; int header_len = pp_parse_hdr((uint8_t*)buffer, buffer_len, &pp_info); print_message(msg_probe_info, "proxyprotocol header %d bytes found\n", header_len); if (header_len < 0) header_len = 0; return header_len; } #endif /* HAVE_PROXYPROTOCOL */