/* * Copyright (C) 2024 Nikos Mavrogiannopoulos * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* Unit test for proxy protocol v2 IPv6 address block parsing. */ #include #include #include #include #include #include #include #include #define UNDER_TEST #define force_read_timeout(fd, buf, count, time) read(fd, buf, count) #include "../src/worker-proxyproto.c" /* Proxy protocol v2 signature (12 bytes) */ static const uint8_t PP2_SIG[] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; /* * Build a proxy protocol v2 packet with an IPv6 address block into buf[]. * Returns the total number of bytes written. */ static size_t build_v2_ipv6_packet(uint8_t *buf, size_t bufsz, const struct in6_addr *src_addr, uint16_t src_port, const struct in6_addr *dst_addr, uint16_t dst_port) { size_t pos = 0; uint16_t sp, dp, plen; memcpy(buf + pos, PP2_SIG, 12); pos += 12; buf[pos++] = 0x21; /* ver=2, PROXY command */ buf[pos++] = 0x21; /* AF_INET6 (0x2) | TCP (0x1) */ size_t len_offset = pos; buf[pos++] = 0; buf[pos++] = 0; size_t payload_start = pos; /* IPv6 address block (36 bytes): * src IP (16 bytes) * dst IP (16 bytes) * src port (2 bytes, network order) * dst port (2 bytes, network order) */ memcpy(buf + pos, src_addr, 16); pos += 16; memcpy(buf + pos, dst_addr, 16); pos += 16; sp = htons(src_port); dp = htons(dst_port); memcpy(buf + pos, &sp, 2); pos += 2; memcpy(buf + pos, &dp, 2); pos += 2; plen = (uint16_t)(pos - payload_start); buf[len_offset] = (plen >> 8) & 0xff; buf[len_offset + 1] = plen & 0xff; assert(pos <= bufsz); return pos; } /* * Feed pkt into a pipe and call parse_proxy_proto_header. * ws->conn_type must be set by the caller. */ static int run_parse(struct worker_st *ws, const uint8_t *pkt, size_t pkt_len) { int fds[2]; int ret; if (pipe(fds) < 0) { perror("pipe"); return -1; } /* write all bytes; pipe buffer is large enough for our small packets */ if (write(fds[1], pkt, pkt_len) != (ssize_t)pkt_len) { perror("write"); close(fds[0]); close(fds[1]); return -1; } close(fds[1]); ret = parse_proxy_proto_header(ws, fds[0]); close(fds[0]); return ret; } int main(void) { uint8_t pkt[256]; size_t pkt_len; struct worker_st ws = { 0 }; int ret; /* ------------------------------------------------------------------ */ /* IPv6 proxy protocol v2 — both remote and local addr correct */ /* */ /* Reproducer for the bug where sa->sin6_family was assigned before sa */ /* was redirected to &ws->our_addr, leaving our_addr.sin6_family == 0. */ /* ------------------------------------------------------------------ */ { struct in6_addr src6, dst6; struct sockaddr_in6 *rem6, *loc6; memset(&ws, 0, sizeof(ws)); ws.conn_type = SOCK_TYPE_TCP; inet_pton(AF_INET6, "2001:db8::1", &src6); inet_pton(AF_INET6, "2001:db8::2", &dst6); pkt_len = build_v2_ipv6_packet(pkt, sizeof(pkt), &src6, 1234, &dst6, 443); ret = run_parse(&ws, pkt, pkt_len); if (ret != 0) { fprintf(stderr, "parse failed (%d)\n", ret); return 1; } if (ws.remote_addr_len != sizeof(struct sockaddr_in6)) { fprintf(stderr, "remote_addr_len wrong (%u)\n", ws.remote_addr_len); return 1; } if (ws.our_addr_len != sizeof(struct sockaddr_in6)) { fprintf(stderr, "our_addr_len wrong (%u)\n", ws.our_addr_len); return 1; } rem6 = (void *)&ws.remote_addr; loc6 = (void *)&ws.our_addr; if (rem6->sin6_family != AF_INET6) { fprintf(stderr, "remote sin6_family wrong: %d\n", rem6->sin6_family); return 1; } /* This check catches the bug: sin6_family was written to * remote_addr a second time instead of to our_addr. */ if (loc6->sin6_family != AF_INET6) { fprintf(stderr, "local sin6_family wrong: %d (expected %d)\n", loc6->sin6_family, AF_INET6); return 1; } if (memcmp(&rem6->sin6_addr, &src6, 16) != 0) { fprintf(stderr, "remote address mismatch\n"); return 1; } if (memcmp(&loc6->sin6_addr, &dst6, 16) != 0) { fprintf(stderr, "local address mismatch\n"); return 1; } if (ntohs(rem6->sin6_port) != 1234) { fprintf(stderr, "remote port wrong: %u\n", ntohs(rem6->sin6_port)); return 1; } if (ntohs(loc6->sin6_port) != 443) { fprintf(stderr, "local port wrong: %u\n", ntohs(loc6->sin6_port)); return 1; } } return 0; }