一直关注tcp/ip的底层协议栈,终于有了大概的了解,通过ntytcp了解tcp相关的底层实现,发现除了tcp协议栈相关的东西,还有udp,arp,icmp(ping命令相关),以及通过netmap对网卡数据进行接收的相关基础知识点,幸好,听过king老师相关的课程,这里就简单的做一些整理。
1:概念介绍:
1:arp:地址解析协议(ip层/数据链路层)
维护了计算机ip和mac地址(物理地址)的对应关系。 (因为网络通信时实际传输的是“帧”,帧里面是有目标主机的MAC地址的)
==》在TCP/IP模型中,ARP协议属于IP层;在OSI模型中,ARP协议属于链路层。
==》局域网内维护ip和mac地址的对应关系
==》如果是外网,路由器网关作为中转,网关直连的网卡上请求对方网关的MAC地址
,然后网关通过IP层查询目标主机,获取mac地址发送。
2:icmp:网络控制消息协议(网络层)
为了辅助IP 协议,交换各种各样的控制信息而被制造出来的。 属于网络层协议(IP,icmp)
主要功能:差错通知和信息查询
例如:ping命令,traceroute命令
3:udp:用户数据报协议(传输层)
UDP是基于IP的简单协议,不可靠的协议。
udp传输速度快,可以用在下载,游戏方向。
用户可以自己实现可靠的数据传输,通过增加确认和重传机制,实现udp的可靠传输。
2:协议数据结构
1:根据协议栈分析数据的结构:
根据图形理解:
发送数据时,其实就是应用层我们的数据外层以此包上各个协议栈的数据。
而接受数据时,其实就是从链路层开始一层层解析最外层的数据,获得最后特定协议特定数据模块
2:以太帧的结构
以太帧起始部分由前同步码和帧开始定界符组成,后面紧跟着一个以太网报头,以 MAC 地址说明目的地址和源地址。以太帧的中部是该帧负载的包含其他协议报头的数据包,如 IP 协议。
以太帧由一个 32 位冗余校验码结尾,用于检验数据传输是否出现损坏。以太帧结构如图所示。
注意:其中的数据块长度最小为 46 字节,最大为 1500 字节。
3:arp协议的分析
arp属于网络层,就是给arp数据块加上网络层的arp头信息,再加上数据链路层头信息。
arp报文格式:
相关代码结构定义如下:
#define ETH_ALEN 6 //以太网协议头的定义 struct ethhdr { unsigned char h_dest[ETH_ALEN]; unsigned char h_source[ETH_ALEN]; unsigned short h_proto; //arp 为0x0806 ip数据包为 0x0800 }; //arp协议头的定义 struct arphdr { unsigned short h_type; //目标网卡的硬件类型 1为以太网地址 unsigned short h_proto; //协议类型 0x0800表示ip协议 unsigned char h_addrlen; //硬件地址长度 如以太网地址长度为6,对应硬件类型 unsigned char protolen; //协议地址长度 如arp,ip协议,值是4 unsigned short oper; // arp请求:1 arp应答:2 rarp请求:3 rarp应答4 unsigned char smac[ETH_ALEN]; //源mac地址 unsigned int sip; //源ip地址 unsigned char dmac[ETH_ALEN]; //目标mac地址 unsigned int dip; //目标ip地址 }; //arp的整体包, struct arppkt { struct ethhdr eh; struct arphdr arp; };
如果用wireshark抓包分析,参考网络上的图:
4:ICMP协议:控制报文协议
ICMP 协议用于在 IP 主机和路由器之间传递控制消息,描述网络是否通畅、主机是否可达、路由器是否可用等网络状态。
icmp协议位于传输层:
struct ethhdr { unsigned char h_dest[ETH_ALEN]; //目的mac地址 unsigned char h_source[ETH_ALEN]; //源mac地址 unsigned short h_proto; //上一层协议类型,如0x0800代表上一层是IP协议,0x0806为arp }; struct iphdr { unsigned char version; //4 位IP版本号+4位首部长度 unsigned char tos; //8位服务类型TOS unsigned short tot_len; //16位IP包总长度(字节) unsigned short id; //16位标识, 用于辅助IP包的拆装 unsigned short flag_off; //3位标志位+13位偏移位, 也是用于IP包的拆装 unsigned char ttl; //8位IP包生存时间 TTL unsigned char protocol; //8位协议 (TCP, UDP 或其他) unsigned short check; //16位IP首部校验和,最初置零,等所有包头都填写正确后,计算并替换. unsigned int saddr; //32位源IP地址 unsigned int daddr; //32位目的IP地址 }; struct icmphdr { unsigned char type; //标识ICMP报文的类型,分为两种查询报文和差错报文 unsigned char code; //标识对应ICMP报文的代码,于type共同表示报文类型 unsigned short check; //ICMP报文数据部分在内的整个ICMP数据报的校验和 从TYPE开始,直到最后一位用户数据,如果为字节数为奇数则补充一位 unsigned short identifier; //识别号(一般用进程号作为识别号), 用于匹配ECHO和ECHO REPLY包 unsigned short seq; //报文序列号, 用于标记ECHO报文顺序 unsigned char data[32]; //时间戳 }; //icmp协议包的定义 struct icmppkt { struct ethhdr eh; struct iphdr ip; struct icmphdr icmp; };
5:UDP协议:用户数据报协议
#define ETH_ALEN 6 struct ethhdr { unsigned char h_dest[ETH_ALEN]; unsigned char h_source[ETH_ALEN]; unsigned short h_proto; }; 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; }; //共8个字节 struct udphdr { unsigned short source; // 源端口号16bit unsigned short dest; // 目的端口号16bit unsigned short len; // 数据包长度16bit unsigned short check; // 校验和16bit }; //udp数据包 struct udppkt { struct ethhdr eh; struct iphdr ip; struct udphdr udp; unsigned char body[128]; };
3:测试抓包
1:arp抓包测试:
2:icmp抓包测试:
3:udp抓包测试:
4:tcp/ip抓包测试:
4:编码实现
用netmap和相关的自定义协议包实现对arp,udp,icmp报文的接受。
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)); } #define PROTO_IP 0x0800 #define PROTO_ARP 0x0806 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) { 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; //ip头中检验udp协议和icmp协议 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)); } } } } }