UDP用户态协议栈详细实现

UDP用户态协议栈详细实现

  • 1 前言
  • 2 网络协议格式
    • 2.1 以太网协议
    • 2.2 IP协议
    • 2.3 UDP协议
    • 2.4 ARP协议
    • 2.5 ICMP协议
  • 3 UDP用户协议栈实现

1 前言

首先需要回答一个问题,为什么要学习实现用户态协议栈,从技术角度分析,主要是由于用户态的网络协议栈更高效,第二个是用户态协议栈可以实现定制。更高效主要是针对,网卡数据拷贝到协议栈这部分,如果采用用户态协议栈就可以进行一个零拷贝的过程,即利用mmap技术完成。这里实现UDP用户态协议主要是对于协议栈的理解的加深,了解内核协议栈的工作原理。

2 网络协议格式

2.1 以太网协议

以太网协议分为三个部分,协议头,数据和CRC校验。源地址和目的地址是指网卡的硬件地址(也叫MAC地址),类型主要包括IP协议,ARP协议和RARP协议。协议定义来自于RFC894,具体如下图。
在这里插入图片描述
头结构定义:

#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协议

IP协议比较复杂,包括内容比较多,主要是定义了源ip地址和目的ip地址。默认是20字节的头部长度,当存在选项时头部长度将进行扩展。具体定义见RFC791:
UDP用户态协议栈详细实现_第1张图片头结构定义:

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协议

udp协议是定义最为简单的协议,协议基本不提供什么保障,主要定义了源端口和目的端口,也使得UDP在用户层可以进行更为灵活的设计。详细见RFC768:
UDP用户态协议栈详细实现_第2张图片

#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协议

ARP协议的作用是通过IP地址获取MAC地址,主要工作流程是ARP进程在本局域网上广播发送一个ARP请求分组,主要内容是A自己的IP地址、MAC地址,询问的IP地址。然后被询问对象既可以回复自身的ip和mac地址,建立一个arp表,后面可以直接查表。具体见RFC 826:
UDP用户态协议栈详细实现_第3张图片

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;

注意:
这里存在一个arp攻击问题,就是对于arp欺骗,利用arp广播的原理,然后发送错误的ip和mac个对方造成arp表错乱整个网络无法使用。

2.5 ICMP协议

ICMP协议是一种面向无连接的协议,用于传输出错报告控制信息。具体见RFC792:
UDP用户态协议栈详细实现_第4张图片
结构定义:

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 UDP用户协议栈实现

利用netmap实现一个简单的UDP协议的解析过程,用到netmap接口:nm_open打开网卡;nm_nextpkt获取网络数据;nm_inject发送网络数据。利用poll进行网络数据接收状态的获取,根据协议一层一层的强转,最终得到用户数据,数据发送也是一层层封装的过程。整体比较简单。网络编程需要考虑网络字节序和用户字节序,后续完善ARP查表和ICMP ping功能的实现。

#include
#include 
#include 
#define NETMAP_WITH_LIBS
#include  
#pragma pack(1)

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

    //netmap:打开eth0网卡
    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;
}

注意点:
结构体会进行对齐,造成最后数据包的大小计算错误,这里需要指定#pragma pack(1)

你可能感兴趣的:(Linux网络编程,udp,网络协议,网络)