On Mon, 2015-05-04 at 08:47 +0200, Michael Kerrisk (man-pages) wrote:
> Eric,
> 
> On 4 May 2015 at 06:34, Eric Dumazet <[email protected]> wrote:
> > From: Eric Dumazet <[email protected]>
> >
> > This patch allows a server application to get the TCP SYN headers for
> > its passive connections.  This is useful if the server is doing
> > fingerprinting of clients based on SYN packet contents.
> >
> > Two socket options are added: TCP_SAVE_SYN and TCP_SAVED_SYN.
> >
> > The first is used on a socket to enable saving the SYN headers
> > for child connections. This can be set before or after the listen()
> > call.
> >
> > The latter is used to retrieve the SYN headers for passive connections,
> > if the parent listener has enabled TCP_SAVE_SYN.
> >
> > TCP_SAVED_SYN is read once, it frees the saved SYN headers.
> >
> > The data returned in TCP_SAVED_SYN are network (IPv4/IPv6) and TCP
> > headers.
> 
> This description is a little thin, so I'm unclear on one or two
> points. TCP_SAVE_SYN is clearly applied to the listening socket. But
> what about TCP_SAVED_SYN? Is that applied to the connected socket
> returned by accept()?
> 
> The highly similar naming of these two seems unfortunate. At the very
> least, it makes for easy confusion in conversations about the two
> options. It would be better to have names that were more distinct.
> Perhaps the latter could be TCP_CONN_SYN or TCP_CONN_SAVED_SYN, for
> example?
> 
> Thanks,


TCP_CONN_SYN is rather confusing, because of the analogy with connect().

Maybe I can send the test Neal wrote 3 years ago, part of our automated
non regression tests we run here.

Let me know if you need more information, thanks !

/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 * Author: [email protected] (Neal Cardwell)
 *
 * A basic test for the TCP_SAVE_SYN and TCP_SAVED_SYN socket options.
 */

#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>

#ifndef TCP_SAVE_SYN
#define TCP_SAVE_SYN    27
#endif

#ifndef TCP_SAVED_SYN
#define TCP_SAVED_SYN   28
#endif

typedef char bool;
typedef enum bool_t {
        false = 0,
        true = 1,
} bool_t;

static void fail(const char *msg)
{
        fprintf(stderr, "%s\n", msg);
        exit(1);
}

static void fail_perror(const char *msg)
{
        perror(msg);
        exit(1);
}

/*
 * Once every ~5000 connections, connect fails with ENOBUFS. This is
 * not even in the manpage, and seem to be transient. For now, just retry.
 */
static void connect_reliably(int fd, struct sockaddr *daddr, int dlen)
{
        int ret, max_runs = 5;

        do {
                ret = connect(fd, daddr, dlen);
        } while (ret == -1 && errno == ENOBUFS && --max_runs);

        if (ret)
                fail_perror("reliable connect");
}

/* Get and validate the saved SYN. */
static void read_saved_syn(int fd, int address_family)
{
        unsigned char syn[500];
        socklen_t syn_len = sizeof(syn);

        memset(syn, 0, sizeof(syn));

        /* Read the saved SYN. */
        if (getsockopt(fd, IPPROTO_TCP, TCP_SAVED_SYN, syn, &syn_len) != 0)
                fail_perror("first getsockopt TCP_SAVED_SYN failed");

        /* Check the length and first byte of the SYN. */
        if (address_family == AF_INET) {
                assert(syn_len == 60);
                assert(syn[0] >> 4 == 0x4);     /* IPv4 */
        } else if (address_family == AF_INET6) {
                assert(syn_len == 80);
                assert(syn[0] >> 4 == 0x6);     /* IPv6 */
        } else {
                assert(!"bad address family");
        }

        /* Check the last few bytes of the SYN, which will be TCP options. */
        assert(syn[syn_len-4] == 0x01);  /* TCP option: kind = NOP */
        assert(syn[syn_len-3] == 0x03);  /* TCP option: kind = window scale */
        assert(syn[syn_len-2] == 0x03);  /* TCP option: length = 3 */
        assert(syn[syn_len-1] == 0x06 || syn[syn_len-1] == 0x07);  /* TCP 
option: window scale = 6 or 7 */

        /* If we try TCP_SAVED_SYN again it should succeed with 0 length. */
        if (getsockopt(fd, IPPROTO_TCP, TCP_SAVED_SYN, syn, &syn_len) != 0)
                fail("repeated getsockopt TCP_SAVED_SYN failed");
        assert(syn_len == 0);
}

/* Open server and client socket and test TCP_SAVE_SYN and TCP_SAVED_SYN. */
static void do_test(struct sockaddr *srv_addr, uint16_t *srv_port, int srv_len,
                    struct sockaddr *cli_addr, uint16_t *cli_port, int cli_len,
                    bool get_saved_syn)
{
        int fd_listen = -1, fd_accept = -1, fd_connect = -1;
        int one = 1;

        fd_listen = socket(srv_addr->sa_family, SOCK_STREAM, 0);
        if (fd_listen == -1)
                fail_perror("open fd_listen");

        if (setsockopt(fd_listen, SOL_SOCKET, SO_REUSEADDR,
                       &one, sizeof(one)) < 0)
                fail_perror("setsockopt SO_REUSEADDR");

        if (bind(fd_listen, srv_addr, srv_len))
                fail_perror("bind fd_listen");

        if (getsockname(fd_listen, srv_addr, (socklen_t *)&srv_len))
                fail_perror("getsockname fd_listen");

        *cli_port = *srv_port;

        if (setsockopt(fd_listen, IPPROTO_TCP, TCP_SAVE_SYN,
                       &one, sizeof(one)) < 0)
                fail_perror("setsockopt TCP_SAVE_SYN");

        if (listen(fd_listen, 1))
                fail_perror("listen fd_listen");

        fd_connect = socket(cli_addr->sa_family, SOCK_STREAM, 0);
        if (fd_connect == -1)
                fail_perror("open fd_connect");

        connect_reliably(fd_connect, cli_addr, cli_len);

        fd_accept = accept(fd_listen, NULL, 0);
        if (fd_accept == -1)
                fail_perror("accept fd_listen");

        if (get_saved_syn) {
                read_saved_syn(fd_accept, cli_addr->sa_family);
        }

        if (close(fd_listen))
                fail_perror("close fd_listen");

        if (close(fd_accept))
                fail_perror("close fd_accept");

        if (close(fd_connect))
                fail_perror("close fd_connect");
}


static void test_ipv4(bool get_saved_syn)
{
        struct sockaddr_in srv_addr = {
                .sin_family             = AF_INET,
                .sin_addr.s_addr        = INADDR_ANY,
                .sin_port               = 0,
        };
        struct sockaddr_in cli_addr = {
                .sin_family             = AF_INET,
                .sin_addr.s_addr        = htonl(INADDR_LOOPBACK),
        };

        printf("   testing IPv4 ...\n");
        do_test((struct sockaddr *)&srv_addr, &srv_addr.sin_port,
                sizeof(srv_addr),
                (struct sockaddr *)&cli_addr, &cli_addr.sin_port,
                sizeof(cli_addr),
                get_saved_syn);
}

static void test_ipv6(bool get_saved_syn)
{
        struct sockaddr_in6 srv_addr = {
                .sin6_family            = AF_INET6,
                .sin6_addr              = IN6ADDR_ANY_INIT,
                .sin6_port              = 0,
        };
        struct sockaddr_in6 cli_addr = {
                .sin6_family            = AF_INET6,
                .sin6_addr              = IN6ADDR_LOOPBACK_INIT,
        };

        printf("   testing IPv6 ...\n");
        do_test((struct sockaddr *)&srv_addr, &srv_addr.sin6_port,
                sizeof(srv_addr),
                (struct sockaddr *)&cli_addr, &cli_addr.sin6_port,
                sizeof(cli_addr),
                get_saved_syn);
}

static void test_ipv4_mapped_ipv6(bool get_saved_syn)
{
        struct sockaddr_in6 srv_addr = {
                .sin6_family            = AF_INET6,
                .sin6_addr              = IN6ADDR_ANY_INIT,
                .sin6_port              = 0,
        };
        struct sockaddr_in cli_addr = {
                .sin_family             = AF_INET,
                .sin_addr.s_addr        = htonl(INADDR_LOOPBACK),
        };

        printf("   testing IPv4-mapped-IPv6 (srv=AF_INET6, cli=AF_INET)...\n");
        do_test((struct sockaddr *)&srv_addr, &srv_addr.sin6_port,
                sizeof(srv_addr),
                (struct sockaddr *)&cli_addr, &cli_addr.sin_port,
                sizeof(cli_addr),
                get_saved_syn);
}

static void test_all_address_families(bool get_saved_syn)
{
        test_ipv4(get_saved_syn);
        test_ipv6(get_saved_syn);
        test_ipv4_mapped_ipv6(get_saved_syn);
}

static void run_all_tests(void)
{
        bool get_saved_syn;

        /* Test normal behavior, when we ask the kernel to record the
         * SYN and then read it using the TCP_SAVED_SYN getsockopt().
         */
        printf("test: reading the saved SYN...\n");
        get_saved_syn = true;
        test_all_address_families(get_saved_syn);

        /* Test behavior when we ask the kernel to record the SYN and
         * then never actually use the TCP_SAVED_SYN getsockopt() to
         * extract the saved SYN.
         */
        printf("test: not reading the saved SYN...\n");
        get_saved_syn = false;
        test_all_address_families(get_saved_syn);
}

static int usage(const char *executable_path)
{
        fprintf(stderr, "usage: %s\n", executable_path);
        return 1;
}

int main(int argc, char **argv)
{
        if (getuid() != 0 || getgid() != 0)
                fail("must run as root\n");

        if (argc != 1)
                return usage(argv[0]);

        system("sysctl net.ipv4.tcp_timestamps=1");
        system("sysctl net.ipv4.tcp_sack=1");
        system("sysctl net.ipv4.tcp_window_scaling=1");

        run_all_tests();

        printf("OK. All tests passed.\n");
        return 0;
}


--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to