| /* | |
| * Copyright (c) 2013 Patrick McHardy <kaber@trash.net> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License version 2 as | |
| * published by the Free Software Foundation. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <stdlib.h> | |
| #include <stdbool.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <getopt.h> | |
| #include <sys/types.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <pcap/pcap.h> | |
| #include <netinet/ip.h> | |
| #include <netinet/tcp.h> | |
| static const char *iface = "lo"; | |
| static uint16_t port; | |
| static const char *chain = "SYNPROXY"; | |
| static int parse_packet(const char *host, const uint8_t *data) | |
| { | |
| const struct iphdr *iph = (void *)data + 14; | |
| const struct tcphdr *th = (void *)iph + iph->ihl * 4; | |
| int length; | |
| uint8_t *ptr; | |
| if (!th->syn || !th->ack) | |
| return 0; | |
| printf("-A %s -d %s -p tcp --dport %u " | |
| "-m state --state UNTRACKED,INVALID " | |
| "-j SYNPROXY ", chain, host, port); | |
| /* ECE && !CWR */ | |
| if (th->res2 == 0x1) | |
| printf("--ecn "); | |
| length = th->doff * 4 - sizeof(*th); | |
| ptr = (uint8_t *)(th + 1); | |
| while (length > 0) { | |
| int opcode = *ptr++; | |
| int opsize; | |
| switch (opcode) { | |
| case TCPOPT_EOL: | |
| return 1; | |
| case TCPOPT_NOP: | |
| length--; | |
| continue; | |
| default: | |
| opsize = *ptr++; | |
| if (opsize < 2) | |
| return 1; | |
| if (opsize > length) | |
| return 1; | |
| switch (opcode) { | |
| case TCPOPT_MAXSEG: | |
| if (opsize == TCPOLEN_MAXSEG) | |
| printf("--mss %u ", ntohs(*(uint16_t *)ptr)); | |
| break; | |
| case TCPOPT_WINDOW: | |
| if (opsize == TCPOLEN_WINDOW) | |
| printf("--wscale %u ", *ptr); | |
| break; | |
| case TCPOPT_TIMESTAMP: | |
| if (opsize == TCPOLEN_TIMESTAMP) | |
| printf("--timestamp "); | |
| break; | |
| case TCPOPT_SACK_PERMITTED: | |
| if (opsize == TCPOLEN_SACK_PERMITTED) | |
| printf("--sack-perm "); | |
| break; | |
| } | |
| ptr += opsize - 2; | |
| length -= opsize; | |
| } | |
| } | |
| printf("\n"); | |
| return 1; | |
| } | |
| static void probe_host(const char *host) | |
| { | |
| struct sockaddr_in sin; | |
| char pcap_errbuf[PCAP_ERRBUF_SIZE]; | |
| struct pcap_pkthdr pkthdr; | |
| const uint8_t *data; | |
| struct bpf_program fp; | |
| pcap_t *ph; | |
| int fd; | |
| ph = pcap_create(iface, pcap_errbuf); | |
| if (ph == NULL) { | |
| perror("pcap_create"); | |
| goto err1; | |
| } | |
| if (pcap_setnonblock(ph, 1, pcap_errbuf) == -1) { | |
| perror("pcap_setnonblock"); | |
| goto err2; | |
| } | |
| if (pcap_setfilter(ph, &fp) == -1) { | |
| pcap_perror(ph, "pcap_setfilter"); | |
| goto err2; | |
| } | |
| if (pcap_activate(ph) != 0) { | |
| pcap_perror(ph, "pcap_activate"); | |
| goto err2; | |
| } | |
| if (pcap_compile(ph, &fp, "src host 127.0.0.1 and tcp and src port 80", | |
| 1, PCAP_NETMASK_UNKNOWN) == -1) { | |
| pcap_perror(ph, "pcap_compile"); | |
| goto err2; | |
| } | |
| fd = socket(AF_INET, SOCK_STREAM, 0); | |
| if (fd < 0) { | |
| perror("socket"); | |
| goto err3; | |
| } | |
| memset(&sin, 0, sizeof(sin)); | |
| sin.sin_family = AF_INET; | |
| sin.sin_port = htons(port); | |
| sin.sin_addr.s_addr = inet_addr(host); | |
| if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { | |
| perror("connect"); | |
| goto err4; | |
| } | |
| for (;;) { | |
| data = pcap_next(ph, &pkthdr); | |
| if (data == NULL) | |
| break; | |
| if (parse_packet(host, data)) | |
| break; | |
| } | |
| close(fd); | |
| err4: | |
| close(fd); | |
| err3: | |
| pcap_freecode(&fp); | |
| err2: | |
| pcap_close(ph); | |
| err1: | |
| return; | |
| } | |
| enum { | |
| OPT_HELP = 'h', | |
| OPT_IFACE = 'i', | |
| OPT_PORT = 'p', | |
| OPT_CHAIN = 'c', | |
| }; | |
| static const struct option options[] = { | |
| { .name = "help", .has_arg = false, .val = OPT_HELP }, | |
| { .name = "iface", .has_arg = true, .val = OPT_IFACE }, | |
| { .name = "port" , .has_arg = true, .val = OPT_PORT }, | |
| { .name = "chain", .has_arg = true, .val = OPT_CHAIN }, | |
| { } | |
| }; | |
| static void print_help(const char *name) | |
| { | |
| printf("%s [ options ] address...\n" | |
| "\n" | |
| "Options:\n" | |
| " -i/--iface Outbound interface\n" | |
| " -p/--port Port number to probe\n" | |
| " -c/--chain Chain name to use for rules\n" | |
| " -h/--help Show this help\n", | |
| name); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int optidx = 0, c; | |
| for (;;) { | |
| c = getopt_long(argc, argv, "hi:p:c:", options, &optidx); | |
| if (c == -1) | |
| break; | |
| switch (c) { | |
| case OPT_IFACE: | |
| iface = optarg; | |
| break; | |
| case OPT_PORT: | |
| port = atoi(optarg); | |
| break; | |
| case OPT_CHAIN: | |
| chain = optarg; | |
| break; | |
| case OPT_HELP: | |
| print_help(argv[0]); | |
| exit(0); | |
| case '?': | |
| print_help(argv[0]); | |
| exit(1); | |
| } | |
| } | |
| argc -= optind; | |
| argv += optind; | |
| while (argc > 0) { | |
| probe_host(*argv); | |
| argc--; | |
| argv++; | |
| } | |
| return 0; | |
| } |