一直关注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地址发送。
==》参考连接:https://blog.csdn.net/jiejiemcu/article/details/88406088
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));
}
}
}
}
}