计算UDP报文CRC校验的总结

概述

因公司项目需求,遇到需要发送带UDP/IP头数据包的功能,经过多次尝试顺利完成,博文记录以备忘。

环境信息

操作系统

ARM64平台的中标麒麟Kylin V10

工具

tcpdump、wireshark、vscode

原理

请查看大佬的博文

UDP伪包头定义(图片来源于参考链接)
在这里插入图片描述

调试过程

对着大佬的博文说明,用上白嫖党专业技能C++【Copy++】,欠缺的结构体和头文件运用专业技能补全。

//头文件
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <string.h>//结构体
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 headerstruct	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 headerstruct	in_addr ih_src;		//源地址struct	in_addr ih_dst;		//目标地址
};

因为ih_next、ih_prev这两个字段并没有使用,所以只要保证每个字段大小是4个字节,并且值为0即可。

经过一番测试,对比成功。

完整代码

为了方便项目使用,做了部分调整,请自行针对性修改

#include <netinet/udp.h>
#include <netinet/ip.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#ifndef IPHL
#define IPHL sizeof(struct ip)
#endif#ifndef UDPHL
#define UDPHL sizeof(struct udphdr)
#endifstruct 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 headerstruct 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的伪首部是什么?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/194115.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MQ - 消息系统

消息系统 1、消息系统的演变 在大型系统中&#xff0c;会需要和很多子系统做交互&#xff0c;也需要消息传递&#xff0c;在诸如此类系统中&#xff0c;你会找到源系统&#xff08;消息发送方&#xff09;和 目的系统&#xff08;消息接收方&#xff09;。为了在这样的消息系…

数据结构和算法-哈夫曼树以相关代码实现

文章目录 总览带权路径长度哈夫曼树的定义哈夫曼树的构造法1法2 哈夫曼编码英文字母频次总结实验内容&#xff1a; 哈夫曼树一、上机实验的问题和要求&#xff08;需求分析&#xff09;&#xff1a;二、程序设计的基本思想&#xff0c;原理和算法描述&#xff1a;三、调试和运行…

Matter学习笔记(3)——交互模型

一、简介 1.1 交互方式 交互模型层定义了客户端和服务器设备之间可以执行哪些交互。发起交互的节点称为发起者&#xff08;通常为客户端设备&#xff09;&#xff0c;作为交互的接收者的节点称为目标&#xff08;通常为服务器设备&#xff09;。 节点通过以下方式进行交互&a…

Spring Initial 脚手架国内镜像地址

官方的脚手架下载太慢了&#xff0c;并且现在没有了Java8的选项&#xff0c;所以找到国内的脚手架镜像地址&#xff0c;推荐给大家。 首先说官方的脚手架 官方的脚手架地址为&#xff1a; https://start.spring.io/ 但是可以看到&#xff0c;并没有了Java8的选项。 所以推荐…

3dMax拼图生成工具Puzzle2D使用教程

Puzzle2D for 3dsMax拼图生成工具使用教程 Puzzle2D简介&#xff1a; 2D拼图随机生成器&#xff08;英文&#xff1a;Puzzle2D&#xff09; &#xff0c;是一款由#沐风课堂#用MAXScript脚本语言开发的3dsMax建模小工具&#xff0c;可以随机创建2D可编辑样条线拼图图形。可批量…

【tensorflow学习-选择动作】 学习tensorflow代码调用过程

a actor.choose_action(s) def choose_action(self, s):s s[np.newaxis, :]return self.sess.run(self.action, {self.s: s}) # get probabilities for all actions输入&#xff1a;s 输出&#xff1a;self.sess.run(self.action, {self.s: s}) &#xff1a;a

解决:UnboundLocalError: local variable ‘js’ referenced before assignment

解决&#xff1a;UnboundLocalError: local variable ‘js’ referenced before assignment 文章目录 解决&#xff1a;UnboundLocalError: local variable js referenced before assignment背景报错问题报错翻译报错位置代码报错原因解决方法今天的分享就到此结束了 背景 在使…

实战案例:chatglm3 基础模型多轮对话微调

chatglm3 发布了&#xff0c;这次还发了base版本的模型&#xff0c;意味着我们可以基于这个base模型去自由地做SFT了。 本项目实现了基于base模型的SFT。 base模型 https://huggingface.co/THUDM/chatglm3-6b-base由于模型较大&#xff0c;建议离线下载后放在代码目录&#…

OSG编程指南:专栏内容介绍及目录

1、专栏介绍 OpenSceneGraph&#xff08;OSG&#xff09;场景图形系统是一个基于工业标准 OpenGL 的软件接口&#xff0c;它让程序员能够更加快速、便捷地创建高性能、跨平台的交互式图形程序。本专栏基于 OSG 3.6.5版本进行源码的编写及扩展&#xff0c;也通用于其他OSG版本的…

OpenTelemetry系列 - 第2篇 Java端接入OpenTelemetry

目录 一、架构说明二、方式1 - 自动化2.1 opentelemetry-javaagent.jar&#xff08;Java8 &#xff09;2.2 使用opentelemetry-javaagent.jar完成自动注入2.3 配置opentelemetry-javaagent.jar2.4 使用注解&#xff08;WithSpan, SpanAttribute&#xff09;2.5.1 代码集成WithS…

【栈和队列(2)】

文章目录 前言队列队列方法队列模拟实现循环队列练习1 队列实现栈 前言 队列和栈是相反的&#xff0c;栈是先进后出&#xff0c;队列是先进先出&#xff0c;相当于排队打饭&#xff0c;排第一的是最先打到饭出去的。 队列 队列&#xff1a;只允许在一端进行插入数据操作&…

20、Resnet 为什么这么重要

&#xff08;本文已加入“计算机视觉入门与调优”专栏&#xff0c;点击专栏查看更多文章信息&#xff09; resnet 这一网络的重要性&#xff0c;上一节大概介绍了一下&#xff0c;可以从以下两个方面来有所体现&#xff1a;第一是 resnet 广泛的作为其他神经网络的 back bone&…

Redis集合对象

一. 编码 集合对象的编码可以是intset或者hashtable。 intset编码的集合对象使用整数集合作为底层实现&#xff0c;集合对象包含的所有元素都保存在整数集合里面。 127.0.0.1:6379> sadd numbers 1 3 5 (integer) 3 127.0.0.1:6379> object encoding numbers "ints…

使用凌鲨进行内网穿透

为了方便在本地进行开发和调试工作&#xff0c;有时候需要安全地连接内网或Kubernetes集群中的服务。 在net proxy server中可以限制访问用户&#xff0c;也可以设置端口转发的密码。 使用 连接端口转发服务 列出可转发端口 可转发端口是服务端设置的&#xff0c;不会暴露真…

锁表的原因及解决办法

引言 作为开发人员&#xff0c;我们经常会和数据库打交道。 当我们对数据库进行修改操作的时候&#xff0c;例如添加字段&#xff0c;更新记录等&#xff0c;没有正确评估该表在这一时刻的使用频率&#xff0c;直接进行修改&#xff0c;致使修改操作长时间无法响应&#xff0…

2023年【起重机司机(限桥式起重机)】报名考试及起重机司机(限桥式起重机)考试资料

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【起重机司机(限桥式起重机)】报名考试及起重机司机(限桥式起重机)考试资料&#xff0c;包含起重机司机(限桥式起重机)报名考试答案和解析及起重机司机(限桥式起重机)考试资料练习。安全生产模拟考试一点通结合…

正是阶段高等数学复习--函数极限的计算

之前在预备阶段中函数极限的解决方式分三步&#xff0c;第一步观察形式并确定用什么方式来解决&#xff0c;第二步化简&#xff0c;化简方式一共有7种&#xff0c;分别是最重要的三种&#xff08;等价替换、拆分极限存在的项、计算非零因子&#xff09;以及次重要的4种&#xf…

BurpSuite 请求/响应解密插件开发

BurpSuite 请求/响应解密插件开发 本文主要记录如何利用burp官方的新版API即MontoyaApi 写一个请求/响应的解密插件。背景下面是主要的操作步骤&#xff1a;根据上述操作做完之后&#xff0c;生成&#xff0c;然后在burp中加载插件&#xff0c;然后通关抓包看效果&#xff0c;具…

DevEco Studio 调整开发工具中的字体大小与行高

我们打开编辑器 选择 左上角 File 下的 Settings 将左侧菜单栏 编辑 展开 我们在编辑下面 选择 Font 然后 如下图指向的两个位置 我们可以调整它的字体大小和行高 设置好之后 右下角 点击 Apply 应用 然后点击 OK即可 当然 你按着 Ctrl 然后鼠标滚动 也可以像浏览器那样 拉…