一、网卡的作用
将物理层的光纤双绞线传输过来的模拟信号转换成数字信号,然后交给数据链路层处理,因此工作在在七层协议模型中的物理层和数据链路层之间。
实现网络协议栈需要获取最原始的以太网的数据的方式。用户态协议栈的优点:减少系统调用和CPU上下文切换的次数,提高网络工作性能,比如处理C10M问题时。
二、网卡原始数据的获取
1.raw sock()
2.旁路:netmap、dpdk的方式
3.hook:bpf/ebpf、netfilter上
三、 netmap的功能
1.将网卡旁路出来,将网卡数据mmap到内存,直接操作内存数据即可。
2.使用:编译之后进行 insmod netmap.ko 就会多出个字符设备/dev/netmap,可以通过dev/netmap 字符设备 操作网卡。
采用旁路的方式实现网络协议栈(数据链路层、网络层、传输层)
四、主要结构体定义
以 实现 UDP 协议栈为例:用客户端发报文,测试收到UDP报文 实现简易的用户态协议栈。
如果实现TCP版本,需要实现 三次握手
来建立连接,发送数据到客户端,还有滑动窗口
、定时器
等等。。。。
以太网协议结构体:
struct ethhdr {
unsigned char h_dest[ETH_ALEN];
unsigned char h_source[ETH_ALEN];
unsigned short h_proto;
};
ipheader: 一个UDP包长度小于64K,而16位总长度如果超过了MTU 就会进行IP分片,16位标识用以标记被分片的UDP包,13位片偏移表示具体的位置。
struct iphdr {
unsigned char version;
unsigned char tos;
unsigned short tot_len;
unsigned short id;
unsigned short flag_off;
unsigned char ttl;
unsigned char protocol;
unsigned short check;
unsigned int saddr;
unsigned int daddr;
};
每一层都包含上一层协议的类型。
udpheader:
struct udphdr {
unsigned short source;
unsigned short dest;
unsigned short len;
unsigned short check;
};
struct udppkt {
struct ethhdr eh;
struct iphdr ip;
struct udphdr udp;
unsigned char body[0];//柔性数组:数组长度可扩展。
};
五、添加ARP和ICMP协议
如果只有以上三个协议,会出现两个问题
1.之后不能接收数据,使用 ARP -a 命令在ARP表中查不到 IP 地址了,刚开始能正常解析数据是因为:虚拟机刚开机的时候,用xshell 连虚拟机,宿主机发送过 arp 请求,而虚拟机的内核协议栈此时还没被 netmap 接管,回应了 arp 请求,宿主机就将虚拟机的信息暂时的添加到arp表中,当动态arp记录失效,udp 包不知道发给谁,就会先发arp请求。
解决方法:在宿主机 arp 表中添加静态的 arp 记录或实现 ARP 协议把 client 的 MAC 地址放到以太网头里面发送出去。
2.同理之后也不能 ping 通,需要实现 ICMP 协议。
具体实现:
server.c
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); //将eth0网卡映射到内存
if (nmr == NULL) {
return -1;
}
pfd.fd = nmr->fd;
pfd.events = POLLIN;
while (1) {
int ret = poll(&pfd, 1, -1);
if (ret < 0) continue;
//netmap帮我们封装成了IO的形式,若网卡有数据来了,IO可读,所以可用poll管理
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);
struct udppkt udp_rt;
echo_udp_pkt(udp, &udp_rt);
nm_inject(nmr, &udp_rt, sizeof(struct udppkt)); //发送包
} 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.0.26")) {
echo_arp_pkt(arp, &arp_rt, "00:50:56:33:1c:ca");
nm_inject(nmr, &arp_rt, sizeof(struct arppkt));
}
}
}
}
}