IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 用户态协议栈TCP/IP的实现 -> 正文阅读

[网络协议]用户态协议栈TCP/IP的实现

在上一篇文章中,知道了为什么要有用户态协议栈,它具备什么功能。

今天来讲述,如何实现一个用户态协议栈。
它的原理就是, 将TCP/UDP到达网卡经解析后的数据,存储起来, 然后存储的位置通过映射的方法,直接到达应用程序处理。

在这里插入图片描述

网络协议栈

在这里插入图片描述
请问,网卡是属于哪一层协议栈?
先解释一下,
物理层 传输的是 物理信号, 即 光电信号。
数据链路层 , 对应的是 数字信号。

而从物理层的 光电信号 转化为 数据链路层的 数字信号, 靠的是 网卡。

网卡, 也可将 数字信号转为 光电信号。

所以,网卡并不属于网络协议栈里的任何一层。

UDP数据包的结构图

在这里插入图片描述
它是一层一层的,往外包的。

以太网协议

在这里插入图片描述
mac地址是以太网产物
mac地址有什么用?
mac地址,在局域网内有用。出了局域网之后,mac的地址就无效了。

//定义一个以太网的协议头
struct ethhdr {
	unsigned char h_dest[ETH_ALEN];    // mac 目的地址,固定6个字节
	unsigned char h_source[ETH_ALEN];  // 源地址, 固定6个字节
	unsigned short h_proto;            // 类型 
};

这个以太网协议头这个结构体,大小为14字节。

IP协议

在这里插入图片描述
IP地址是网络层产物

// IP协议头
struct iphdr {
	unsigned char version;   // 版本号  ipv4 /ipv6
	unsigned char tos;		 // 8bit 的 服务类型
	unsigned short tot_len;  
	unsigned short id;       // 标识, 每个包都有自己的id,identidy
	unsigned short flag_off; // 3bit的标志, 13bit的偏移量
	unsigned char ttl;		 // 生命周期
	unsigned char protocol;	 // 协议
	unsigned short check;	 // 校验和
	unsigned int saddr;		 // 源ip地址
	unsigned int daddr;      // 目的地址
};

什么时候选择有符号的char,啥时候选择无符号char.
如果有用来计算的话,就选择有符号的char型。
如果没有,只是单纯的表示值。那么就选择无符号的char。

ARP协议

在这里插入图片描述

// 28字节的 ARP请求/应答  ARP协议头
struct arphdr {
	unsigned short h_type;			// 硬件类型
	unsigned short h_proto;	        // 协议类型
	unsigned char h_addrlen;        // 硬件地址长度
	unsigned char protolen;			// 协议地址长度
	unsigned short oper;			// op 操作
	unsigned char smac[ETH_ALEN];  // 发送端以太网地址
	unsigned int sip;			   // 发送端IP地址
	unsigned char dmac[ETH_ALEN];  // 目的以太网地址
	unsigned int dip;			   // 目的IP地址
};

// 一个ARP的数据包
struct arppkt {
	struct ethhdr eh;  	// 以太网头
	struct arphdr arp;  // ARP请求/应答 ARP协议头
};

ICMP协议

在这里插入图片描述

// ICMP协议头
struct icmphdr {
	unsigned char type;
	unsigned char code;
	unsigned short check;
	unsigned short identifier;
	unsigned short seq;
	unsigned char data[32];
};

//一个ICMP的数据包
struct icmppkt {
	struct ethhdr eh;  // 以太网
	struct iphdr ip;   // IP头
	struct icmphdr icmp;  // ICMP协议头
};

用户态协议栈的实现

以下是使用了netmap 框架来实现的一个,用户态协议栈



#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <sys/poll.h>
#include <arpa/inet.h>


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


#define ETH_ALEN	6
#define PROTO_IP	0x0800
#define PROTO_ARP	0x0806

#define PROTO_UDP	17
#define PROTO_ICMP	1
#define PROTO_IGMP	2

// 14字节的 以太网 协议头
struct ethhdr {
	unsigned char h_dest[ETH_ALEN];    // mac 目的地址,固定6个字节
	unsigned char h_source[ETH_ALEN];  // 源地址, 固定6个字节
	unsigned short h_proto;            // 类型 
};

struct iphdr {
	unsigned char version;   // 版本号  ipv4 /ipv6
	unsigned char tos;		 // 8位的 服务类型
	unsigned short tot_len;  // 
	unsigned short id;       // 标识, 每个包都有自己的id,identidy
	unsigned short flag_off; // 三位的标志, 13位的偏移量
	unsigned char ttl;		 // 生命周期
	unsigned char protocol;	 // 协议
	unsigned short check;	 // 校验和
	unsigned int saddr;		 // 源ip地址
	unsigned int daddr;      // 目的地址
};

// UPD协议头  
struct udphdr {
	unsigned short source;  //源端口    
	unsigned short dest;	//目的端口
	unsigned short len;		//UDP长度
	unsigned short check;	//UDP校验
};

// 一个UDP数据包,  sizeof(udppkt) = 40 个字节,默认是以4个字节
struct udppkt {
	struct ethhdr eh;  // 以太网头
	struct iphdr ip;   // IP头
	struct udphdr udp; // UDP头
	unsigned char body[128]; //用户数据
};

// 28字节的 ARP请求/应答  ARP协议头
struct arphdr {
	unsigned short h_type;			// 硬件类型
	unsigned short h_proto;	        // 协议类型
	unsigned char h_addrlen;        // 硬件地址长度
	unsigned char protolen;			// 协议地址长度
	unsigned short oper;			// op 操作
	unsigned char smac[ETH_ALEN];  // 发送端以太网地址
	unsigned int sip;			   // 发送端IP地址
	unsigned char dmac[ETH_ALEN];  // 目的以太网地址
	unsigned int dip;			   // 目的IP地址
};

// 一个ARP的数据包
struct arppkt {
	struct ethhdr eh;  	// 以太网头
	struct arphdr arp;  // ARP请求/应答 ARP协议头
};

// ICMP协议头
struct icmphdr {
	unsigned char type;
	unsigned char code;
	unsigned short check;
	unsigned short identifier;
	unsigned short seq;
	unsigned char data[32];
};

//一个ICMP的数据包
struct icmppkt {
	struct ethhdr eh;  // 以太网
	struct iphdr ip;   // IP头
	struct icmphdr icmp;  // ICMP协议头
};

void print_mac(unsigned char *mac) {
	int i = 0;
	for (i = 0;i < ETH_ALEN-1;i ++) {
		printf("%02x:", mac[i]);
	}
	printf("%02x", mac[i]);
}

void print_ip(unsigned char *ip) {
	int i = 0;

	for (i = 0;i < 3;i ++) {
		printf("%d.", ip[i]);
	}
	printf("%d", ip[i]);
}


void print_arp(struct arppkt *arp) {
	print_mac(arp->eh.h_dest);
	printf(" ");

	print_mac(arp->eh.h_source);
	printf(" ");

	printf("0x%04x ", ntohs(arp->eh.h_proto));
	printf("  ");
	
}

int str2mac(char *mac, char *str) {

	char *p = str;
	unsigned char value = 0x0;
	int i = 0;

	while (p != '\0') {
		
		if (*p == ':') {
			mac[i++] = value;
			value = 0x0;
		} else {
			
			unsigned char temp = *p;
			if (temp <= '9' && temp >= '0') {
				temp -= '0';  
			} else if (temp <= 'f' && temp >= 'a') {
				temp -= 'a';
				temp += 10;
			} else if (temp <= 'F' && temp >= 'A') {
				temp -= 'A';
				temp += 10;
			} else {	
				break;
			}
			value <<= 4;
			value |= temp;
		}
		p ++;
	}

	mac[i] = value;

	return 0;
}

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

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

	memcpy(arp_rt->eh.h_dest, arp->eh.h_source, ETH_ALEN);
	str2mac(arp_rt->eh.h_source, hmac);
	arp_rt->eh.h_proto = arp->eh.h_proto;

	arp_rt->arp.h_addrlen = 6;
	arp_rt->arp.protolen = 4;
	arp_rt->arp.oper = htons(2);
	
	str2mac(arp_rt->arp.smac, hmac);
	arp_rt->arp.sip = arp->arp.dip;
	
	memcpy(arp_rt->arp.dmac, arp->arp.smac, ETH_ALEN);
	arp_rt->arp.dip = arp->arp.sip;

}


void echo_udp_pkt(struct udppkt *udp, struct udppkt *udp_rt) {

	memcpy(udp_rt, udp, sizeof(struct udppkt));

	memcpy(udp_rt->eh.h_dest, udp->eh.h_source, ETH_ALEN);
	memcpy(udp_rt->eh.h_source, udp->eh.h_dest, ETH_ALEN);

	udp_rt->ip.saddr = udp->ip.daddr;
	udp_rt->ip.daddr = udp->ip.saddr;

	udp_rt->udp.source = udp->udp.dest;
	udp_rt->udp.dest = udp->udp.source;

}

unsigned short in_cksum(unsigned short *addr, int len)
{
	register int nleft = len;
	register unsigned short *w = addr;
	register int sum = 0;
	unsigned short answer = 0;

	while (nleft > 1)  {
		sum += *w++;
		nleft -= 2;
	}

	if (nleft == 1) {
		*(u_char *)(&answer) = *(u_char *)w ;
		sum += answer;
	}

	sum = (sum >> 16) + (sum & 0xffff);	
	sum += (sum >> 16);			
	answer = ~sum;
	
	return (answer);

}


void echo_icmp_pkt(struct icmppkt *icmp, struct icmppkt *icmp_rt) {

	memcpy(icmp_rt, icmp, sizeof(struct icmppkt));

	icmp_rt->icmp.type = 0x0; //
	icmp_rt->icmp.code = 0x0; //
	icmp_rt->icmp.check = 0x0;

	icmp_rt->ip.saddr = icmp->ip.daddr;
	icmp_rt->ip.daddr = icmp->ip.saddr;

	memcpy(icmp_rt->eh.h_dest, icmp->eh.h_source, ETH_ALEN);
	memcpy(icmp_rt->eh.h_source, icmp->eh.h_dest, ETH_ALEN);

	icmp_rt->icmp.check = in_cksum((unsigned short*)&icmp_rt->icmp, sizeof(struct icmphdr));
	
}


int main() {
	
	struct ethhdr *eh;
	struct pollfd pfd = {0};
	struct nm_pkthdr h;
	unsigned char *stream = NULL;

	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) {
		// 为什么要选择poll?
		// 少于1024 的用select,poll, 大于 用epoll
		// select\poll 底层原理都是差不多的,但是api使用,poll更简单一些
		int ret = poll(&pfd, 1, -1);
		if (ret < 0) continue;
		
		if (pfd.revents & POLLIN) {
			stream = nm_nextpkt(nmr, &h); //拿到一个数据包
			eh = (struct ethhdr*)stream;

			if (ntohs(eh->h_proto) == PROTO_IP) {

				struct udppkt *udp = (struct udppkt*)stream;
				if (udp->ip.protocol == PROTO_UDP) {

					struct in_addr addr;
					addr.s_addr = udp->ip.saddr;

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

					udp->body[udp_length-8] = '\0';
					printf("udp --> %s\n", udp->body);
#if 1
					struct udppkt udp_rt;
					echo_udp_pkt(udp, &udp_rt);
					nm_inject(nmr, &udp_rt, sizeof(struct udppkt));
#endif
				} else if (udp->ip.protocol == PROTO_ICMP) {
					
					struct icmppkt *icmp = (struct icmppkt*)stream;

					printf("icmp ---------- --> %d, %x\n", icmp->icmp.type, icmp->icmp.check);
					if (icmp->icmp.type == 0x08) {
						struct icmppkt icmp_rt = {0};
						echo_icmp_pkt(icmp, &icmp_rt);

						//printf("icmp check %x\n", icmp_rt.icmp.check);
						// 往共享内存中写入待发送的数据包数据。数据包经过共享内存拷贝到网卡,然后发送出去
						nm_inject(nmr, &icmp_rt, sizeof(struct icmppkt));
					}
					
				} else if (udp->ip.protocol == PROTO_IGMP) {

				} else {
					printf("other ip packet");
				}
				
			}  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.2.217")) {
					echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");
					nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
				}
			}
		} 
	}
}



  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-07-10 11:41:25  更:2021-07-10 11:42:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 17:53:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码