计算UDP报文CRC校验的总结

news2025/1/18 11:04:55

概述

因公司项目需求,遇到需要发送带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 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 <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)
#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的伪首部是什么?

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

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

相关文章

2023年【G1工业锅炉司炉】考试试题及G1工业锅炉司炉模拟考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【G1工业锅炉司炉】考试试题及G1工业锅炉司炉模拟考试题库&#xff0c;包含G1工业锅炉司炉考试试题答案和解析及G1工业锅炉司炉模拟考试题库练习。安全生产模拟考试一点通结合国家G1工业锅炉司炉考试最新大纲及…

【开源】基于Vue和SpringBoot的校园二手交易系统

项目编号&#xff1a; S 009 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S009&#xff0c;文末获取源码。} 项目编号&#xff1a;S009&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 二手商品档案管理模…

Docker容器(一)概述

一、虚拟化概述 1.1引⼊虚拟化技术的必要性 服务器只有5%的时间是在⼯作的&#xff1b;在其它时间服务器都处于“休眠”状态. 虚拟化前 每台主机⼀个操作系统; 软硬件紧密结合; 在同⼀个主机上运⾏多个应⽤程序通常会遭遇冲突; 系统的资源利⽤率低; 硬件成本⾼昂⽽且不够灵活…

TIA西门子博途V19安装教程及注意事项

TIA西门子博途V19安装教程及注意事项 前提条件: TIA Portal V19需要.Net Framework 3.5环境,所以在安装TIA V19之前要先安装它。 如下图所示,否则可能会提示报错信息: 大家可以在控制面板中的程序和功能中检查是否已经安装,如果没有,可以参考以下步骤自行安装:

OpenWrt作为旁路由(网关)配置

目录 背景前提条件环境操作步骤物理层连接设置与主路由同一网段禁用IPv6取消LAN接口桥接防火墙配置 背景 本文简介如何配置OpenWrt&#xff0c;使其作为旁路由&#xff08;网关&#xff09;运行。 旁路由大概有以下这几种工作方式&#xff1a; 主路由开DHCP&#xff0c;网关未…

十、FreeRTOS之FreeRTOS任务调度

本节需要掌握的内容如下&#xff1a; 1&#xff0c;开启任务调度器&#xff08;熟悉&#xff09; 2&#xff0c;启动第一个任务&#xff08;熟悉&#xff09; 3&#xff0c;任务切换&#xff08;掌握&#xff09; 一&#xff0c;开启任务调度器&#xff08;熟悉&#xff09…

java高校实验室排课学生考勤系统springboot+vue

随着各高校办学规模的迅速扩大,学科专业的不断拓宽,传统的实验教学和实验室管理方法已经不能适应学校管理的要求,特别是化学实验室的管理,化学实验室仪器药品繁杂多样,管理任务繁重,目前主要使用人工记录方法管理,使用不便,效率低下,而且容易疏漏.时间一长将产生大量的文件和数…

【C++】异常处理 ⑦ ( 异常类的继承层次结构 | 抛出 / 捕获 多个类型异常对象 | 抛出子类异常对象 / 捕获并处理 父类异常对象 )

文章目录 一、抛出 / 捕获 多个类型异常对象1、抛出 / 捕获 多个类型异常对象2、操作弊端3、完整代码示例 二、异常类的继承层次结构1、抛出子类异常对象 / 捕获并处理 父类异常对象2、完整代码示例 - 抛出子类异常对象 / 捕获并处理 父类异常对象 自定义的 异常类 , 可能存在 …

Java数据结构 之 包装类简单认识泛类

生命不息&#xff0c;奋斗不止 目录 1. 什么是包装类&#xff1f; 1.1 装箱和拆箱 1.2 自动装箱和自动拆箱 2. 什么是泛型 3. 引出泛型 3.1 语法 4 泛型类的使用 4.1 语法 4.2 示例 4.3 类型推导(Type Inference) 5. 裸类型(Raw Type) &#xff08;了解&#xff09…

〖大前端 - 基础入门三大核心之JS篇㊻〗- JS + CSS实现动画

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…

JSP格式化标签 parseDate将指定时间格式字符串转为真正的date对象

格式化标签最后一个就是 parseDate 作用 将一个日期/时间格式字符串 转为 真正的date时间类型 有点无语 这种 东西应该都是在java中去做的 而不是在java中 这个建议也是做个了解即可 作用不是那么大 基本语法如下 这里 我们 直接编写代码如下 <% page contentType"…

智能优化算法应用:基于天牛须算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于天牛须算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于天牛须算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.天牛须算法4.实验参数设定5.算法结果6.参考文献7.…

2024年AMC8美国初中数学竞赛最后一个月复习指南(附资料)

还有一个半月的时间&#xff0c;2024年AMC8&#xff08;大家默认都直接叫这个比赛的英文名&#xff0c;而不叫中文名美国数学竞赛或美国初中数学竞赛了&#xff09;就要开始了。 有志于在2024年AMC8的比赛中拿到奖项的孩子已经在“磨拳霍霍”了。那么最后一个半月的时间该如何…

智能优化算法应用:基于热交换算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于热交换算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于热交换算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.热交换算法4.实验参数设定5.算法结果6.参考文献7.…

Java线程池的使用和最佳实践

第1章&#xff1a;引言 处理并发问题时&#xff0c;如果每次都新建线程&#xff0c;那系统的压力得有多大&#xff1f;这时候&#xff0c;线程池就像一个英雄一样出现了&#xff0c;它帮我们有效地管理线程&#xff0c;提高资源利用率&#xff0c;降低开销。那么&#xff0c;为…

还搞不懂什么是参数,超参数吗?三分钟快速了解参数与超参数的概念和区别!!!

文章目录 前言一、参数是什么&#xff1f;二、超参数是什么三&#xff0c;常使用的超参数有哪些 前言 参数是模型中可被学习和调整的参数&#xff0c;通过训练数据进行学习和优化&#xff1b; 而超参数则是手动设置的参数&#xff0c;用于控制模型的行为和性能&#xff0c;超…

探秘Python FastAPI、Sanic、Tornado 与Golang Gin性能之战!

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Python和Golang作为两种流行的编程语言&#xff0c;都拥有强大的异步框架&#xff0c;为开发者提供了在构建高性能应用时的选择。在Python阵营中&#xff0c;FastAPI、Sanic、Tornado等框架因其异步特性和高效的…

viple模拟器使用(四):unity模拟器中实现两距离局部最优迷宫算法

名字解读 两距离&#xff1a;指的是左侧距离和右侧距离 局部最优&#xff1a;对当前状态来说最好的选择&#xff0c;至于整体能不能达到最优&#xff0c;是无法确定的。 从节点1到节点5&#xff0c;一共有3条路 第1条路线&#xff1a;1→2→4→5&#xff0c;对应的花销是&…

LeetCode刷题---反转链表

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏&#xff1a;http://t.csdnimg.cn/ZxuNL http://t.csdnimg.cn/c9twt 前言&#xff1a;这个专栏主要讲述递归递归、搜索与回溯算法&#xff0c;所以下面题目主要也是这些算法做的 我讲述…

Linux 基础认识

文章目录 前言Linux历史window历史Linux地位发行版本 前言 建议只看概述 Linux历史 概述&#xff1a; 由一个研究生受Minix操作系统启发编写的&#xff0c;因为功能实用&#xff0c;代码开源被世界人接收和开发 &#xff0c;最终正式发布 。 详情&#xff1a; 1991年10月5日…