| /* |
| * 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; |
| } |