因公司项目需求,遇到需要发送带UDP/IP头数据包的功能,经过多次尝试顺利完成,博文记录以备忘。
ARM64平台的中标麒麟Kylin V10
tcpdump、wireshark、vscode
请查看大佬的博文
对着大佬的博文说明,用上白嫖党专业技能C++【Copy++】,欠缺的结构体和头文件运用专业技能补全。
//头文件
#include
#include
#include
//结构体
struct ipovly {
caddr_t ih_next, ih_prev; /* for protocol sequence q's */
u_char ih_x1; /* (unused) */
u_char ih_pr; //协议域
short ih_len; //这个相当于IP头部,len = data Len + udp HeaderLen + ip header
struct in_addr ih_src; //源地址
struct in_addr ih_dst; //目标地址
};
补全后的代码,经过测试,发现死活不对,后面通过打印结构体大小,发现了端倪,罪魁祸首就是 caddr_t 这个数据类型,IP报文头只有20个字节,但是这个结构体直接32个字节,将UDP的数据都覆盖了部分。这就导致生成的CRC总是不对。
经过层层头文件穿透,caddr_t 定义如下
//sys/types.h
......省略部分
typedef char *__caddr_t;
......省略部分
#ifdef __USE_MISC
# ifndef __daddr_t_defined
typedef __daddr_t daddr_t;
typedef __caddr_t caddr_t;
# define __daddr_t_defined
# endif
#endif
发现char * 数据类型在64位操作系统下是占用8个字节;在32位操作系统下是占用4个字节,故而导致数据被覆盖。因为struct ipovly这个结构不是同一个博文定义的,不排除是复制有差异。
后面将结构体调整如下
struct ipovly {
unsigned int ih_next, ih_prev; /* for protocol sequence q's */
u_char ih_x1; /* (unused) */
u_char ih_pr; //协议域
short ih_len; //这个相当于IP头部,len = data Len + udp HeaderLen + ip header
struct in_addr ih_src; //源地址
struct in_addr ih_dst; //目标地址
};
因为ih_next、ih_prev这两个字段并没有使用,所以只要保证每个字段大小是4个字节,并且值为0即可。
经过一番测试,对比成功。
为了方便项目使用,做了部分调整,请自行针对性修改
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef IPHL
#define IPHL sizeof(struct ip)
#endif
#ifndef UDPHL
#define UDPHL sizeof(struct udphdr)
#endif
struct ipovly
{
unsigned int ih_next, ih_prev; /* for protocol sequence q's */
u_char ih_x1; /* (unused) */
u_char ih_pr; // 协议域
short ih_len; // 这个相当于IP头部,len = data Len + udp HeaderLen + ip header
struct in_addr ih_src, ih_dst; /* source and dest address */
};
unsigned short my_cksum(unsigned short *addr, int len)
{
register int sum = 0;
register unsigned short *w = addr;
register int nleft = len;
int c = 0;
while (nleft > 1)
{
sum += *w++;
nleft -= 2;
}
if (nleft == 1)
{
unsigned char a = 0;
memcpy(&a, w, 1);
sum += a;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return ~sum;
}
short udpipgen(
unsigned char *frame,
const char *saddr,
const char *daddr,
unsigned short sport,
unsigned short dport,
unsigned short msglen)
{
struct ip *ip = (struct ip *)(frame);
struct udphdr *udp = (struct udphdr *)(frame + IPHL);
struct ipovly *io = (struct ipovly *)frame; /*Only for UDP crc*/
struct in_addr s_addr, d_addr;
memset(&s_addr, 0, sizeof(struct in_addr));
memset(&d_addr, 0, sizeof(struct in_addr));
inet_aton(saddr, &s_addr);
inet_aton(daddr, &d_addr);
if (ip == NULL || udp == NULL || io == NULL || saddr == NULL || daddr == NULL ||
s_addr.s_addr == 0 || d_addr.s_addr == 0)
{
return 0;
}
io->ih_next = io->ih_prev = 0;
io->ih_dst.s_addr = d_addr.s_addr;
io->ih_src.s_addr = s_addr.s_addr;
io->ih_x1 = 0;
io->ih_pr = IPPROTO_UDP;
io->ih_len = htons(UDPHL + msglen);
udp->uh_sport = htons(sport);
udp->uh_dport = htons(dport);
udp->uh_ulen = io->ih_len;
udp->uh_sum = my_cksum((unsigned short *)ip, UDPHL + IPHL + msglen);
memset(io, 0x00, sizeof(struct ipovly));
ip->ip_v = IPVERSION;
ip->ip_hl = IPHL >> 2;
ip->ip_tos = 0;
ip->ip_len = htons(UDPHL + IPHL + msglen);
ip->ip_id = htons(0x78D6); //反向验证的时候,注意该字段是否和Wireshark的数据包一致
ip->ip_off = 0; //反向验证的时候,注意该字段是否和Wireshark的数据包一致
ip->ip_ttl = 1; //反向验证的时候,注意该字段是否和Wireshark的数据包一致
ip->ip_p = IPPROTO_UDP;
ip->ip_src.s_addr = s_addr.s_addr;
ip->ip_dst.s_addr = d_addr.s_addr;
ip->ip_sum = my_cksum((unsigned short *)ip, IPHL);
return 1;
}
/**
* 正向使用程序(通过数据生成对应的IP/UDP包数据)样例
* int main(...)
* {
* //需要发送的内层数据
* char data[100] = {...};
* //包含IP头、UDP头、内层数据的数组(以下称帧缓冲区)
* char frame[20+8+sizeof(data)] = {0};
* memset(frame,0,sizeof(frame));
* //内层数据复制到帧缓冲区,因为UDP计算CRC的时候,需要带上数据。IP包头计算CRC只需要包头数据
* memcpy(frame+28,data,sizeof(data)); //28 = IP包头20个字节 + UDP包头8个字节
* //udpipgen函数会自动填充对应的结构体字段
* short ret = udpipgen(frame, "192.168.10.1", "192.168.10.119", 10254, 10252, sizeof(data));
* if (ret == 1) { //TODO 生成成功的后续操作 }
* }
*
*/
/**
* 因为是反向验证程序(从Wireshark捕捉的UDP数据验证CRC),所以该程度的具体功能如下
* 1. 读取一包Wireshark/tcpdump捕捉到的UDP数据包
* 2. 根据IP/UDP解释对应的数据包
* 3. 然后清除对应结构的CRC校验字段
* 4. 调用udpipgen函数,生成CRC
* 5. 控制台打印然后比对原来的CRC和新生成CRC,是否一致
*/
int main(int argc, char *argv[])
{
FILE *fp;
//注意这个缓冲区大小,如果解释大于1024字节的数据会出问题,请自行修改
unsigned char buff[1024] = {0};
if (argc < 2)
{
printf("Usage: %s package\n", argv[0]);
exit(0);
}
//argv[1]: 表示Wireshark抓的数据包,只解释一包,批量请自行修改
fp = fopen(argv[1], "rb");
if (fp == NULL)
{
perror("open file failure!!!");
exit(-1);
}
else
{
// fseek(fp,18,SEEK_SET);
size_t nbytes = fread(buff, 1, 1024, fp);
if (nbytes < 0)
{
perror("read file error!!");
fclose(fp);
fp = NULL;
exit(-1);
}
else
{
unsigned char dup[nbytes] = {0};
//复制数据帧
memcpy(dup, buff, nbytes);
struct ip *ip = (struct ip *)(buff);
struct udphdr *udp = (struct udphdr *)(buff + IPHL);
unsigned short ip_sum = ip->ip_sum;
unsigned short udp_sum = udp->uh_sum;
printf("ip sum: %x, udp sum: %x\n", ip_sum, udp_sum);
struct ip *ip2 = (struct ip *)(dup);
struct udphdr *udp2 = (struct udphdr *)(dup + IPHL);
printf("ip2 sum: %x, udp2 sum: %x\n", ip2->ip_sum, udp2->uh_sum);
//重置复制帧的CRC字段
ip2->ip_sum = 0;
udp2->uh_sum = 0;
char saddr[256] = {0};
char daddr[256] = {0};
inet_ntop(AF_INET, (void *)&ip->ip_src.s_addr, saddr, 256);
printf("ip_src.s_addr : %s ,%d ", saddr, ntohs(udp->uh_sport));
memset(daddr, 0, 256);
inet_ntop(AF_INET, (void *)&ip->ip_dst.s_addr, daddr, 256);
printf("ip_dst.s_addr : %s ,%d\n", daddr, ntohs(udp->uh_dport));
// udp->len = UDP Header长度(8个字节) + 数据长度,因为Wireshark捕捉的数据包都是网络字节序的,所以需要转换一下
printf("data length : %d\n", ntohs(udp->len) - 8);
memset((void *)ip2, 0, sizeof(struct ip));
// 调用函数,计算IP结构、UDP结构的CRC字段
udpipgen(dup, saddr, daddr, ntohs(udp->uh_sport), ntohs(udp->uh_dport), ntohs(udp->uh_ulen) - 8);
printf("Calc ip sum: %x, udp sum: %x\n", ip2->ip_sum, udp2->uh_sum);
fclose(fp);
fp = NULL;
}
}
}
将生成的数据通过UDP发送出去,利用Wireshark/tcpdump抓包,然后通过Wireshark打开
右键呼出菜单,设置自动校验,通过Wireshark的自动校验来验证CRC生成是否正确
手动组UDP/TCP包时计算 CRC 校验碰到的问题。
C语言数据类型在不同位数平台下的字节长度
TCP/IP详解V2(三)之TCP协议
UDP的伪首部是什么?