November 3, 2022

BSD TCP/IP for Kyu - Socket

Before a connect() or bind() call can be made for a TCP connection, a socket needs to be created by a call like this.
    sockfd = socket ( AF_INET, SOCK_STREAM, 0 );
The last argument is "protocol" and is provided in case some protocol family (i.e. domain) had multiple protocols. For AF_INET there is only one option, so an argument of 0 will suffice and get the one and only default.

The second argument is "type", which is SOCK_STREAM for a TCP socket, but could be SOCK_DGRAM for UDP.

The system call is handled by sys_socket() in kern/uipc_syscalls.c.

The call creates a "struct filedesc" object which can be indexed by fd by the process making the call. A pointer to the socket is created by these calls:

error = socreate(uap->domain, &so, uap->type, uap->protocol);
fp->f_data = (caddr_t)so;
So the f_data field holds a pointer to the "struct socket" object.

Our interest now shifts to the routine socreate() in kern/uipc_socket.c

socreate

In our case, we have the call:
socreate ( AF_INET, &so, SOCK_STREAM, 0 ))
This routine is in kern/uipc_socket.c and is fairly short.

It does a lookup to find the protocol (it needs the "struct protosw".
It does a malloc for the "struct socket" and initializes it.

Then it makes this call:

error = (*prp->pr_usrreq)(so, PRU_ATTACH, (struct mbuf *)0, (struct mbuf *)proto, (struct mbuf *)0);
Which boils down to (since "proto" is 0):
tcp_usrreq (so, PRU_ATTACH, 0, 0, 0);
The function tcp_usrreq() is in tcp_usrreq.c This is a big function, but it is a switch/case on "req", which here is PRU_ATTACH
inp = sotoinpcb(so);
error = tcp_attach(so);
tp = sototcpcb(so);
Here we have:
#define sotoinpcb(so)   ((struct inpcb *)(so)->so_pcb)
#define sototcpcb(so)   (intotcpcb(sotoinpcb(so)))
#define intotcpcb(ip)   ((struct tcpcb *)(ip)->inp_ppcb)
So the real action is in tcp_attach(so), which is also in tcp_usrreq.c

What are the inpcb and tppcb?

These are the heart of the issue after all.

This will make a call (via pointers in protosw) to tcp_usrreq to perform a PRU_ATTACH.

struct inpcb {
        LIST_ENTRY(inpcb) inp_hash; 
        CIRCLEQ_ENTRY(inpcb) inp_queue;
        struct    inpcbtable *inp_table;
        int       inp_state;            /* bind/connect state */
        u_int16_t inp_fport;            /* foreign port */
        u_int16_t inp_lport;            /* local port */
        struct    socket *inp_socket;   /* back pointer to socket */
        caddr_t   inp_ppcb;             /* pointer to per-protocol pcb */
        struct    route inp_route;      /* placeholder for routing entry */
        int       inp_flags;            /* generic IP/datagram flags */
        struct    ip inp_ip;            /* header prototype; should have more */
        struct    mbuf *inp_options;    /* IP options */
        struct    ip_moptions *inp_moptions; /* IP multicast options */
        int       inp_errormtu;         /* MTU of last xmit status = EMSGSIZE */
};
#define inp_faddr       inp_ip.ip_dst
#define inp_laddr       inp_ip.ip_src
The struct tcpcb is a huge thing and can be found in netinet/tcp_var.h

So, what goes on in tcp_attach()?

tcp_attach() is basically:
	error = in_pcballoc(so, &tcb);
	inp = sotoinpcb(so);
        tp = tcp_newtcpcb(inp);
The routine in_pcballoc() is in in_pcb.c -- It does a MALLOC for the "struct inpcb", initializes it, and adds it to the tcb list.

sotoinpcb() is a macro that extracts a single field from a structure (why?).

#define sotoinpcb(so)           ((struct inpcb *)(so)->so_pcb)
The routine tcp_newtcpcb is in tcp_subr.c -- It does a malloc for a tcpcb, initializes it, then sets a link to it in the inpcb.


Have any comments? Questions? Drop me a line!

Kyu / [email protected]