diff --git a/tools/pdivert/pdivert.c b/tools/pdivert/pdivert.c new file mode 100644 index 0000000..554ddf4 --- /dev/null +++ b/tools/pdivert/pdivert.c @@ -0,0 +1,107 @@ +/* + * pdivert — PF-on-Linux divert(4) relay (ISC 2026) + * + * Mirrors the classic BSD divert(8) workflow against the in-module + * PF_DIVERT socket family: + * + * pdivert -p [-f] [-q] + * -p port bind the divert socket to (matches pf rule port) + * -f flip the last ICMP payload byte (demo rewrite) + * -q quiet (no hexdump) + * + * Packets pulled from the socket are sent straight back (optionally + * mutated), reproducing "receive -> rewrite -> reinject". + * + * The kernel side (module/pfsrc/divert_sock.c) re-injects per OpenBSD + * ip_divert.c semantics using the metadata captured at divert time. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PF_DIVERT +#define PF_DIVERT 32 /* BSD PF_DIVERT; in-module family */ +#endif +#ifndef IPPROTO_DIVERT +#define IPPROTO_DIVERT 254 +#endif + +int +main(int argc, char **argv) +{ + int fd, ch, port = 0, flip = 0, quiet = 0; + unsigned char buf[65536]; + + while ((ch = getopt(argc, argv, "p:fq")) != -1) { + switch (ch) { + case 'p': + port = atoi(optarg); + break; + case 'f': + flip = 1; + break; + case 'q': + quiet = 1; + break; + default: + fprintf(stderr, "usage: pdivert -p port [-fq]\n"); + return 2; + } + } + if (port <= 0 || port > 65535) { + fprintf(stderr, "pdivert: -p required\n"); + return 2; + } + + if ((fd = socket(PF_DIVERT, SOCK_RAW, IPPROTO_DIVERT)) == -1) { + perror("socket(PF_DIVERT)"); + return 1; + } + { + struct sockaddr_in sa; + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; /* family ignored by bind; port only */ + sa.sin_port = htons((uint16_t)port); + if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + perror("bind"); + return 1; + } + } + if (!quiet) + fprintf(stderr, "pdivert: listening on divert port %d\n", port); + + for (;;) { + ssize_t n = recv(fd, buf, sizeof(buf), 0); + + if (n <= 0) { + if (n < 0 && errno != EAGAIN && errno != EINTR) + perror("recv"); + if (n == 0) + break; + continue; + } + if (!quiet) { + char src[64] = "?", dst[64] = "?"; + + if (n >= 20 && (buf[0] >> 4) == 4) { + inet_ntop(AF_INET, buf + 12, src, sizeof(src)); + inet_ntop(AF_INET, buf + 16, dst, sizeof(dst)); + } + fprintf(stderr, "pdivert: got %zd bytes %s -> %s\n", + n, src, dst); + } + if (flip && n > 0) + buf[n - 1] ^= 0xff; + if (send(fd, buf, (size_t)n, 0) == -1) + perror("send"); + } + close(fd); + return 0; +}