Detailed implementation of UDP state protocol stack

Keywords: network Network Protocol udp

1 Preface

First, we need to answer a question: why should we learn to implement the user state protocol stack? From a technical point of view, it is mainly because the user state network protocol stack is more efficient. The second is that the user state protocol stack can be customized. More efficient is mainly aimed at copying the network card data to the protocol stack. If the user state protocol stack is adopted, a zero copy process can be carried out, that is, it can be completed by using mmap technology. The implementation of UDP user state protocol here is mainly to deepen the understanding of the protocol stack and understand the working principle of the kernel protocol stack.

2 network protocol format

2.1 Ethernet protocol

Ethernet protocol is divided into three parts: protocol header, data and CRC verification. Source address and destination address refer to the hardware address (also known as MAC address) of the network card. The types mainly include IP protocol, ARP protocol and RARP protocol. The protocol definition comes from RFC894, as shown in the figure below.

Header structure definition:

#define ETH_HDR_LEN             6

#define IP_PROTO                0x0800
#define ARP_PROTO               0x0806
#define RARP_PROTO              0x0835

typedef struct _eth_hdr {
    unsigned char src_mac[ETH_HDR_LEN];
    unsigned char dst_mac[ETH_HDR_LEN];
    unsigned short proto;
}eth_hdr;

2.2 IP protocol

IP protocol is complex, including many contents. It mainly defines the source IP address and destination IP address. The default is the header length of 20 bytes. When there is an option, the header length will be extended. See RFC791 for specific definitions:
Header structure definition:

typedef struct  _ip_header
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned char hdr_len:4,
                  version:4;
#else
    unsigned char version:4,
                  hdr_len:4;
#endif
    unsigned char tos_type;
    unsigned short pkt_len;
    unsigned short mark;
    unsigned short flag_offset;
    unsigned char ttl;
    unsigned char proto;
    unsigned short hdr_checksum;
    unsigned int src_ip;
    unsigned int dst_ip;
    unsigned int opt[0];
}ip_header;

2.3 UDP protocol

UDP protocol is the simplest protocol defined. It basically does not provide any guarantee. It mainly defines the source port and destination port, which also makes UDP more flexible in the user layer. See RFC768 for details:

#define PROTO_UDP	17
typedef struct  _udp_header
{
    unsigned short src_port;
    unsigned short dst_port;

    unsigned short length;
    unsigned short checksum;
}udp_header;

typedef struct  _udp_pkt
{
    eth_header eth;
    ip_header ip;
    udp_header udp;
    
    unsigned char data[0];
}udp_pkt;

2.4 ARP Protocol

The role of ARP protocol is to obtain the MAC address through the IP address. The main workflow is that the ARP process broadcasts and sends an ARP request packet on the local area network. The main contents are A's own IP address, MAC address and IP address of inquiry. Then the queried object can reply to its own IP and MAC addresses, establish an ARP table, and then directly look up the table. See RFC 826 for details:

typedef struct  _arp_header
{
    unsigned short hw_type;
    unsigned short proto;
    unsigned char hw_len;
    unsigned char addr_len;
    unsigned short op;

    unsigned short src_mac[ETH_MAC_LEN];
    unsigned int src_ip;
    unsigned short dst_mac[ETH_MAC_LEN];
    unsigned int dst_ip;
}arp_header;

typedef struct  _arp_pkt
{
    eth_header eth;
    arp_header arp;

    unsigned char data[0];
}arp_pkt;

be careful:
There is an arp attack problem, that is, for arp spoofing, use the principle of arp broadcasting, and then send the wrong ip and mac to each other, resulting in the disorder of the arp table and the unavailability of the whole network.

2.5 ICMP Protocol

ICMP Protocol is a connectionless protocol for transmitting error report control information. See RFC792 for details:

Structure definition:

typedef struct  _icmp_header
{
    unsigned char type;

    unsigned char code;
    unsigned short checksum;
    unsigned short identifier;
    unsigned short seq;
    unsigned char data[32];
}icmp_header;

typedef struct  _icmp_pkt
{
    eth_header eth;
    ip_header ip;
    icmp_header icmp;

    unsigned char data[0];
}icmp_pkt;

3 implementation of UDP user protocol stack

Using netmap to realize a simple UDP protocol parsing process, using netmap interface: nm_open open the network card; nm_nextpkt obtains network data; nm_inject sends network data. Using poll to obtain the network data receiving status, according to the strong conversion layer by layer of the protocol, the user data is finally obtained, and the data transmission is also a process of layer by layer encapsulation. The whole is relatively simple. Network programming needs to consider network byte order and user byte order, and subsequently improve the implementation of ARP table lookup and ICMP ping functions.

#include<stdio.h>
#include <poll.h>
#include <arpa/inet.h>
#define NETMAP_WITH_LIBS
#include <net/netmap_user.h> 
#pragma pack(1)

int main() 
{
    eth_header *eth = NULL;
    struct pollfd pfd = {0};
    struct nm_pkthdr nmh;
    unsigned char *buffer = NULL;

    //netmap: open eth0 network card
    struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
	if (nmr == NULL) 
    {
		return -1;
	}

    pfd.fd = nmr->fd;
	pfd.events = POLLIN;

    while(1) 
    {
        int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;

        if (pfd.revents & POLLIN) 
        {
            buffer = nm_nextpkt(nmr, &nmh);
            eth = (eth_header *)buffer;

            if(ntohs(eth->proto) == PROTO_IP)
            {
                udp_pkt *udp = (udp_pkt *)buffer;
                if(udp->ip.proto == PROTO_UDP)
                {
                    struct in_addr addr;
					addr.s_addr = udp->ip.src_ip;

					int udp_length = ntohs(udp->udp.length);
					printf("%s:%d:length:%d, ip_len:%d --> ", inet_ntoa(addr), udp->udp.src_port, 
						udp_length, ntohs(udp->ip.pkt_len));

					udp->data[udp_length-8] = '\0';
					printf("udp buffer: %s\n", udp->data);

                    udp_pkt udp_rt;
					echo_udp_pkt(udp, &udp_rt);
					nm_inject(nmr, &udp_rt, sizeof(udp_pkt));
                }

            }
            
        }
    }

    return 0;
}

Note:
The structure will be aligned, resulting in an error in calculating the size of the last packet. Here, you need to specify #pragma pack(1)

Posted by ailgup on Sun, 05 Dec 2021 10:41:57 -0800