应用层实现udp协议栈

首先简单看一下网络的ios模型

 由上图可以清晰看出,主机A往主机B发送数据的过程,应用层把数据组织好后,经过传输层时,加上TCP头,经过网络层时,加上ip头,经过数据链路层时,加上以太头,经过网卡转换为数字信号,传递到主机B,主机B在通过层层拆包,最后到达应用层。

而对于用户来说,一般只需要组织好应用层的数据,下层的数据由内核协议栈来负责封装和解析,现在,尝试着跳过内核协议栈,自己实现一个简单的udp协议的解析和封装过程。

首先,要借助netmap工具,从网卡上把源数据捞出来,这个工具可以在github上下载,安装过程很简单,安装完毕后 insmod netmap.ko 把设备安装上,可以在/dev目录下看到一个netmap设备,这样我们就可以把网卡的数据拦截下来进行操作了。

下面是一个udp的首部格式

 根据上图,创建一个udp的结构体

struct udphdr
{
    unsigned short sprot;
    unsigned short dport;
    unsigned short length;
    unsigned short check;
}; //8byte

ip头的首部格式

 ip头的结构体

struct iphdr {
    unsigned char hdrlen:4,
                    version:4;
    unsigned char tos;
    unsigned short totlen;
    unsigned short id;
    unsigned short flag_offset;
    unsigned char ttl;
    unsigned char type;
    unsigned short check;
    unsigned int sip;
    unsigned int dip;
}; //20byte

以太头格式

 以太头结构体

struct ethhdr {
    unsigned char h_dst[ETH_ADDR_LENGTH];
    unsigned char h_str[ETH_ADDR_LENGTH];
    unsigned short h_proto;
}//14byte

最后组织成一个udp的pkt

struct udppkt{
    struct ethhdr eh;//14
    struct iphdr ip;//20
    struct udphdr udp;//8

    unsigned char data[0];//存储应用层数据
};//42

下面我们来接收一下服务器udp的包

#include <stdio.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <net/netmap_user.h>

#pragma pack(1) //按一字节对齐,不然解析会失败
#define NETMAP_WITH_LIBS

#define ETH_ADDR_LENGTH     6
#define PROTO_IP            0x0800
#define PROTO_ARP           0x0806
#define PROTO_UDP           17
#define PROTO_TCP           6
#define PROTO_ICMP          1

int main()
{
    struct nm_pkthdr h;
    struct nm_desc *nm = nm_open("netmap:eth33",NULL,0,NULL);//拦截eth33网卡数据
    if (nm == NULL) return -1;

    struct pollfd pfd = {0};
    pfd.fd = nm->fd;
    pfd.events = POLLIN;

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

        if(pfd.events & POLLIN) {
            unsigned char* stream = nm_nextpkt(nm,&h);
            struct ethhdr* eh =(struct ethhdr*)stream;
            if(ntohs(eh->h_proto) == PROTO_IP){
                struct udppkt *udp = (struct udppkt*)stream;
                if(udp->ip.type == PROTO_UDP)
                {
                    int udplen = ntohs(udp->udp.length);
                    udp->data[udplen-8] = '\0';//减去头部字节
                    printf("udp -->%s\n",udp->data);
                }
            }   
        }
    }
}

编译通过之后,执行一下,发现还是无法接收到数据,把程序停掉后先ping一下服务器,在运行程序,现在就可以正常接收到数据了,然后在过一段时间,又无法接收到数据了。

 这是为什么呢,我们看一下路由表

 这时候是没有192.168.32.145这个路由信息的,那是因为我们还没有实现arp协议,停掉程序后,用内核协议栈的arp协议把服务器添加进路由表后,才能正常接收到数据

 那为什么过一段时间之后,又无法收到数据了呢,因为这是一个动态的ip,路由器隔一段时间会广播arp包,收不到145的回包,便把他从路由表里去掉了。

接下来,我们完善一下arp协议,先看看arp的报文格式

 根据上图写出结构体

struct arphdr {

	unsigned short h_type;
	unsigned short h_proto;

	unsigned char h_addrlen;
	unsigned char h_protolen;

	unsigned short oper;

	unsigned char smac[ETH_ADDR_LENGTH];
	unsigned int sip;

	unsigned char dmac[ETH_ADDR_LENGTH];
	unsigned int dip;
};//28

然后检测到eh->hproto类型为PROTO_ARP时,返回一个arp包

void echo_arp_pkt(struct arppkt *arp, struct arppkt *arp_rt, char *mac) {

	memcpy(arp_rt, arp, sizeof(struct arppkt));

	memcpy(arp_rt->eh.h_dst, arp->eh.h_src, ETH_ADDR_LENGTH);
	str2mac(arp_rt->eh.h_src, mac);
	arp_rt->eh.h_proto = arp->eh.h_proto;

	arp_rt->arp.h_addrlen = 6;
	arp_rt->arp.h_protolen = 4;
	arp_rt->arp.oper = htons(2);
	
	str2mac(arp_rt->arp.smac, mac);//把字符串转为mac
	arp_rt->arp.sip = arp->arp.dip;
	
	memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ADDR_LENGTH);
	arp_rt->arp.dip = arp->arp.sip;

}



.......
else if (ntohs(eh->h_proto) ==  PROTO_ARP) {

				struct arppkt *arp = (struct arppkt *)stream;

				struct arppkt arp_rt;

				if (arp->arp.dip == inet_addr("192.168.32.145")) { //确认是本机地址在回包,不然会形成arp攻击

					echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");//组织arp包

					nm_inject(nmr, &arp_rt, sizeof(arp_rt));

					printf("arp ret\n");
				
				}

这样就可以和服务器保持一个长时间的通信了。这时还会发现,客户端ping不通服务器了,那是因为ping用到的是icmp协议,这里我们还没实现。

-----------------------------------------------------

附上icmp实现的效果图

 

        当然,这只是一个简单的udp包解析的过程,还有许多协议没有实现,后续可以试着实现一下tcp协议栈。

        网络协议栈是由多个协议栈组成的,构成了一个复杂的网络环境,实现了一个强大的数据收发功能,这个艰巨的工作还是交给内核去完成吧。

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值