Reading TCP Options in Go

In Go http.Server allows us to set ConnContext, a custom function to modify the contetx used for a new connection. We can get the socket file descriptor from the connection through net.TCPConn.Control.

server := http.Server{
    ConnContext: func(ctx context.Context, c net.Conn) context.Context {
        // In case the syscall fails, return early
        sock, err := c.(*net.TCPConn).SyscallConn()
        if err != nil {
            return ctx
        }

        sock.Control(func(fd uintptr) {
            ctx = context.WithValue(ctx, "info", netutils.Parse(fd))
        })

        return ctx
    },
}

To then extract the tcp options given a socket file descriptor we will use getsocktopt. Let’s first create a small wrapper around our C code.

package netutils

// #include "parse.h"
import "C"

type TCPOptions struct {
	Mss    int
	Window int
	Scale  int
	TTL    int
}

func Parse(fd uintptr) Options {
    var i C.struct_info_t = C.get_info(C.int(fd))

	return Options{
		Mss:    uint32(i.tcp.tcpi_rcv_mss),
		Window: uint32(i.tcp.tcpi_rcv_space),
		Scale:  uint32(i.tcp.tcpi_rcv_wscale),
 		TTL:    int(i.ip.ttl),
	}
}

And now the C code:

#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>

struct tcp_info_t {
  uint8_t  tcpi_rcv_wscale;
  uint32_t tcpi_rcv_mss;
  uint32_t tcpi_rcv_space;
};

struct ip_info_t {
    uint8_t ttl;
};

struct info_t {
    struct tcp_info_t tcp;
    struct ip_info_t  ip;
};

static inline struct info_t get_info(int fd) {
    struct info_t info;

    // TCP option information
    struct tcp_info ti;
    socklen_t tisize = sizeof(ti);
    getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &tisize);

    info.tcp.tcpi_rcv_wscale = ti.tcpi_rcv_wscale;
    info.tcp.tcpi_rcv_mss = ti.tcpi_rcv_mss;
    info.tcp.tcpi_rcv_space = ti.tcpi_rcv_space;

    // IP TTL
    socklen_t optlen = sizeof(int);
    getsockopt(fd, IPPROTO_IP, IP_TTL, &info.ip.ttl, &optlen);

    return info;
}