-
TCP/IP卷一:12---链路层之(以太网帧、帧校验序列/循环冗余校验、MTU/MTU路径)
2019-07-15 19:54:33一、以太网帧 所有的以太网(802.3)帧都基于一个共同的格式。...以太网帧开始是一个前导字段,接收器电路用它确定一个帧的到达时间,并确定编码位 (称为时钟恢复)之间的时间量。由于以太网是一个异步的局域网...一、以太网帧
- 所有的以太网(802.3)帧都基于一个共同的格式。在原有规范的基础上,帧格式已被改进以支持额外功能
以太网帧格式
- 下图显示了当前的以太网帧格式,以及它与IEEE提出的一个相对新的术语IEEE分组(一个在其他标准中经常使用的术语)的关系
- 以太网帧开始是一个前导字段,接收器电路用它确定一个帧的到达时间,并确定编码位 (称为时钟恢复)之间的时间量。由于以太网是一个异步的局域网(即每个以太网接口卡中不 保持精确的时钟同步),从一个接口到另一个接口的编码位之间的间隔可能不同。前导是一 个公认的模式(典型值为0xAA),在发现帧起始分隔符(SFD)时,接收器使用它“恢复时钟”
- SFD的固定值为0xAB
- 这个基本的帧格式包括48位(6字节)的目的地址(DST)和源地址(SRC)字段。这些地址有时也采用其他名称,例如“MAC地址”、 “链路层地址”、 “802地址”、 “硬件地址” 或“物理地址”。以太网帧的目的地址也允许寻址到多个站点(称为“广播”或“组播”)。广播功能用于ARP协议,组播功能用于ICMPv6协议,以 实现网络层地址和链路层地址之间的转换
- 源地址后面紧跟着一个类型字段,或一个长度字段。在多数情况下,它用于确定头 部后面的数据类型。TCP/IP网络使用的常见值包括IPv4 (0x0800)、 IPv6 (0x86DD)和 ARP (0x0806)。0x8100表示一个Q标签帧(可携带一个“虚拟局域网”或802.1q标准 回 VLAN ID)。一个以太网帧的基本大小是1518字节,但最近的标准将该值扩大到2000字节
- 注意:最初的IEEE (802.3)规范将长度/类型字段作为长度字段而不是类型字段使用。困此,这个字段被重载(可用于多个目的)。关键是看字段值。目前,如果字段值大于或等于1536,则该字段表示类型,它是由标准分配的超过1536的值。如果字 段值等于或小于1500,则该字段表示长度。 [ETHERTYPES]给出了类型的完整列表
- 在上述字段之后,[802.3-2008]提供了多种标签包含由其他IEEE标准定义的备种协议字段。其中,最常见的是那些由802.1p和802.1q使用的标签,它提供虚拟局域网和一些服务质量(Qos)指示符
- 注意:当前的[802.3-2008]标准采用修改后的802.3帧格式,提供最大为482字节 的“标签”,它携带在每个以太网帧中。这些较大的帧称为信封帧,长度最大可能达到2000字节。包含802.1p/q标签的帧称为Q标签帧,也是信封帧。但是,并非 所有信封帧必然是Q标签帧
- 在这些讨论过的字段之后,是帧的数据区或有效载荷部分。这里是放高层PDU(例如IP数据报)的地方。传统上,以太网的有效载荷一直是1500字节,它代表以太网的MTU。目 前,大多数系统为以太网使用1500字节的MTU,虽然在必要时它也可设置为一个较小的值。有效载荷有时被填充(添加)数个0,以确保帧总体长度符合最小长度要求
二、帧校验序列/循环冗余校验
- 在以太网帧格式中,有效载荷区域之后的最后字段提供了对帧完整性的检查。循环冗余校验(CRC)字段位于尾部,有32位,有时称之为IEEE/ANSI标准的CRC32 [802.3- 2008]。要使用一个″位CRC检测数据传输 错误,被检查的消息首先需要追加″位0形成一个扩展消息。然后,扩展消息(使用 模2除洼)除以一个(乃十1)位的值,这个 作为除数的值称为生成多项式。放置在消息 的CRC字段中的值是这次除法计算中余数 的二进制反码(商被丢弃)。生成多项式已 被标准化为一系列不同的″值。以太网使用 ″=32,CRC32的生成多项式是33位的二 进制数100000100110000010001Ⅱ0110110111。 为了理解如何使用(mod 2 )二进制除法计算 余数,我们看一个CRC4的简单例子。国际 电信联盟(ITU)将CRC4的生成多项式值 标准化为10011,这是在G.704 [G704]标 准中规定的。如果我们要发送16位的消息 1001111000101111,首先开始进行下图所示的(mod2)二进制除法
- 在该图中,我们看到这个除法的余数 是4位的值ⅡⅡ。通常,该余数的反码 (0000)将放置在帧的CRC或帧校验序列 (FCS)字段中。在接收到数据之后,接收方 执行相同的除法计算出余数,并判断该值与FCS字段的值是否匹配。如果两者不匹配,帧可能在传输过程中受损,通常被丢弃。CRC功 能可用于提示信息受损,因为位模式的任何改变极可能导致余数的改变
三、帧大小
- 以太网帧有最小和最大尺寸
最小尺寸
- 最小的帧是64字节,要求数据区(有效载荷)长度(无标签)最小为48字节
- 当有效载荷较小时,填充字节(值为0)被添加到有效载荷尾部,以确保达到最小长度
最大尺寸
- 传统以太网的最大帧长度是1518字节(包括4字节CRC和14字节头部)。选择这个值 出于一种折中:如果一个帧中包括一个错误(接收到不正确的CRC校验),只需重发1.5kB 以修复该问题
- 另一方面,MTU大小限制为1500字节。为了发送一个更大的消息,则需要多个帧(例如,对于TCP/IP网络常用的较大尺寸64KB,需要至少44个帧)
- 由多个以太网帧构成一个更大的上层PDU的后果是,每个帧都贡献了一个固定开销(14字节的头部和4字节的CRC)。更糟的是,为了允许以太网硬件接收电路正确恢复来 自网络的数据,并为其他站提供将自已的流量与已有流量区分开的机会,以太网帧在网络中 不能无缝地压缩在一起。Ethernet Ⅱ规范除了在帧开始处定义了7字节前导和1字节SFD之 外,还指定了12字节的包间距(IPG)时间( 10Mb/s为9.6us,100Mb/s为960ns,1000Mb/s为 96ns,10000Mb/s为9.6ns)。因此, Ethernet Ⅱ的每帧效率最多为1500/(12 + 8+14+ 1500+4)= 0.975293,约98%。一种提高效率的方式是,在以太网中传输大量数据时,尽量使帧尺寸更大 一些。这可采用以太网巨型帧吓]来实现,它是一种非标准的以太网扩展(主要在1000Mb/s以 太网交换机中使用),通常允许帧尺寸高达9000字节。有些环境使用的帧称为超级巨型帧,它 们通常超过9000字节。在使用巨型帧时要谨慎,这些较大的帧无法与较小的1518字节的帧 互操作,因为它们无法由大多数传统以太网设备处理
四、MTU和MTU路径
- 我们从最上面的帧格式图可以看到,在很多链路层网络(例如以太网)中,携带高层协议PDU的帧大小是有限制的。以太网有效载荷的字节数通常被限制为1500,PPP通常采用相同大小以保持与以太网兼容。链路层的这种特征被称为最大传输单元(MTU)
- 大多数的分组网络(例如以太网)都有固定的上限。大多数的流类型网络(串行链路)提供可设置的上限,它可被帧协议(例如PPP)所使用。如果IP需要发送一个数据报,并且这个数据报比链路层MTU大,则IP通过分片将数据报分解成较小的部分,使每个分片都小于MTU(我们会在后面Internet协议文章和UDP文章中讨论IP分片)
- 路径MTU:
- 当同一网络中的两台主机之间通信时,本地链路的MTU在会话期间对数据报大小有直接影响。当两台主机之间跨越多个网络通信时,每条链路可能有不同大小的MTU
- 在包含所有链路的整个网络路径上,最小的MTU称为路径MTU
- 任何两台主机之间的路径MTU不会永远不变,这取决于当时使用的路径。如果网络中的路由器或链路故障,MTU可能改变。另外,路径通常不对称(主机A到B路径可能不是 B到A的反向路径),路径MTU不需要在两个方向上相同
- [RFCl191]规定了IPv4路径MTU发现(PMTUD)机制,[RFC1981]描述了用于IPv6 的相应机制。 [RFC4821]描述了一个补充方案,以解决这些机制中的一些间题。PMTUD用于确定某个时间的路径MTU,它在IPv6实现中是需要的。在后面的文章中,针对前面描述的ICMP和IP分片,我们将观察这个机制如何运行。我们在讨论TCP和UDP时,也会讨论它对传输性能的影响
-
CRC校验
2012-10-05 09:22:21在学TCP/IP中,关于Ethernet帧结构中的最后一部分帧校验字段FCS(4B),在编程通信程序时,我们需对数据链路层通信Ethernet帧进行校验,即对帧校验字段FCS进行校验。FCS采用32位CRC校验。校验的范围包括目的地址、源...在学TCP/IP中,关于Ethernet帧结构中的最后一部分帧校验字段FCS(4B),在编程通信程序时,我们需对数据链路层通信Ethernet帧进行校验,即对帧校验字段FCS进行校验。FCS采用32位CRC校验。校验的范围包括目的地址、源地址字段、类型字段、数据字段。在接受段进行校验,如果发现错误,帧将被丢弃。
下面是关于CRC的循序渐进的知识:
循环冗余码校验(CRC=cyclic redundancy check),是一个信息字段和校验字段的长度可以任意选定的差错校验码。
原理:任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码1010111对应的多项式为x6+x4+x2+x+1,而多项式为x5+x3+x2+x+1对应的代码101111。发过来看:x8+ x2+x1+1= 1*x8+0*x7+ 0*x6+0*x5+0*x4+1*x2+1*x1+1*1,所得到的代码是100000111。
CRC码集选择的原则:若设码字长度为N,信息字段为K位,校验字段为R位(N=K+R),则对于CRC码集中的任一码字,存在且仅存在一个R次多项式g(x),使得V(x)=A(x)g(x)=xRm(x)+r(x);其中: m(x)为K次信息多项式, r(x)为R-1次校验多项式,g(x)称为生成多项式:g(x)=g0+g1x1+ g2x2+...+g(R-1)x(R-1)+gRxR。发送方通过指定的g(x)产生CRC码字,接收方则通过该g(x)来验证收到的CRC码字。
CRC校验码软件生成方法:借助于多项式除法,其余数为校验字段。例如:信息字段代码为: 1011001;对应m(x)=x6+x4+x3+1 。假设生成多项式为:g(x)=x4+x3+1;则对应g(x)的代码为: 11001 。x4m(x)=x10+x8+x7+x4 对应的代码记为:10110010000;x4m(x)/ g(x)即采用多项式除法(如何运算见下段): 得余数为: 1010 (即校验字段为:1010)。发送方:发出的传输字段为: 1 0 1 1 0 0 1 1010(信息字段 校验字段)。接收方:使用相同的生成码进行校验:接收到的字段/生成码(二进制除法)如果能够除尽,则正确。
给出余数(1010)的计算步骤:除法没有数学上的含义,而是采用计算机的模二除法,即,除数和被除数做异或运算。进行异或运算时除数和被除数最高位对齐,按位异或。
1011001 0000
-11001
--------------------------
=01111010000
1111010000
-11001
-------------------------
=0011110000
11110000
-11001
--------------------------
=00111000
111000
- 11001
-------------------
= 001010
以下是8位CRC校验为例:
关于CRC校验实现的方法有很多,简单起见,采用8位CRC校验,其生成多项式为G(x)= x8+ x2+x1+1。例:一个实现CRC-8的硬件电路:由8个移位寄存器和3个异或单元组成。
计算过程如下:
(1)开始编码解码前所有寄存器清0。
(2)输入位作为最左边异或或操作的输入之一,8个寄存器上的移位操作同时进行,均为左移一位。
(3)每次移位时寄存器R7的输出作为所有3个异或操作的输入之一,寄存器R0的输出作为中间异或操作的输入之一,寄存器R1的输出作为最左边异或操作的输入之一。
(4)各个异或操作的结果作为它左边那个寄存器的输入位。
(5)重复以上操作,每输入一位就做一次移位操作,直到输入了所有要计算的数据为止。这时,这个寄存器组中的数据就是CRC-8的结果。
以1010为例,将1010后补8个0所得的比特序列(即101000000000)当做输入值,8个移位寄存器组中的值为00110110。该结果与101000000000除以100000111(G(x)= x8+ x2+x1+1)所得的余数相同。
对上面计算过程分析可以得到,当寄存器R7的移出位为1时,寄存器组才和00000111进行XOR运算;移出位为0时,不做运算。每次寄存器中的数据左移后就需要从输入数据中读入一位新的数据,如果读入的新数据为1,则需要把寄存器R0置为1,然后再判断寄存器是否需要与00000111进行XOR运算。
思路:
//register8是一个8位的寄存器,把register8中的值置为0,在原始数据后添加8个0(因为校验字段为8位,得到的是x8 m(x))
代码:While(数据未处理完) { if(register8首位是1) { register8中的数据左移1位; if(从输入中读入的新数据为1) { 将register8的最低位置1; } register8= register8 XOR 00000111 } else { register8中的数据左移1位; if(从输入中读入的新数据为1) { 将register8的最低位置1; } } }
代码:void checkCRC(int &chCurrByte, int chNextByte) { // CRC循环:每次调用进行8次循环,处理一个字节的数据。 for (int nMask = 0x80; nMask > 0; nMask >>= 1) { if ((chCurrByte & 0x80) != 0) // 首位为1:移位,并进行异或运算 { chCurrByte <<= 1; // 移一位 if ( (chNextByte & nMask) != 0) // 补一位 { chCurrByte |= 1; } chCurrByte ^= 7; // 首位已经移出,仅对低8位进行异或运算,7的二进制为0000,0111 } else // 首位为0,只移位,不进行异或运算 { chCurrByte <<= 1; // 移一位 if ( (chNextByte & nMask) != 0) // 补一位 { chCurrByte |= 1; } } } }
前面介绍的串行方法,对于任意长度的生成多项式G(x)都是适用。如果发送的数据块很长的话,这种方法就不太合适,效率太低。为了提高效率,可以一次处理8,16或32位。
Ethernet一般是对一帧数据进行CRC校验,而字节是帧的基本单位,所以最常用的CRC校验算法是按字节查表的快速算法:本字节后的CRC码,等于上一节CRC右移8位和本字节之和再与上一字节余式CRC码的低8位左移8位相加所求的CRC码。如果把8位二进制序列数的CRC(共256个)全部计算出来放在一个表中,编码时只要从表中查找对应的值进行处理即可。
下面给出网上搜到的参数表生成程序:代码:#include <stdio.h> unsigned long int crc32_table[256]; unsigned long int ulPolynomial = 0x04c11db7; unsigned long int Reflect(unsigned long int ref, char ch) { unsigned long int value(0); // 交换bit0和bit7,bit1和bit6,类推 for(int i = 1; i < (ch + 1); i++) { if(ref & 1) value |= 1 << (ch - i); ref >>= 1; } return value; } init_crc32_table() { unsigned long int crc,temp; // 256个值 for(int i = 0; i <= 0xFF; i++) { temp=Reflect(i, 8); crc32_table[i]= temp<< 24; for (int j = 0; j < 8; j++){ unsigned long int t1,t2; unsigned long int flag=crc32_table[i]&0x80000000; t1=(crc32_table[i] << 1); if(flag==0) t2=0; else t2=ulPolynomial; crc32_table[i] =t1^t2 ; } crc=crc32_table[i]; crc32_table[i] = Reflect(crc32_table[i], 32); } }
代码:unsigned long crc_32_tab[256]= {0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535 , 0x9e6495a3,0x0edb8832,…, 0x5a05df1b, 0x2d02ef8d };//事先计算出的参数表,共有256项,未全部列出。 unsigned long GenerateCRC32(char xdata * DataBuf,unsigned long len) { unsigned long oldcrc32; unsigned long crc32; unsigned long oldcrc; unsigned int charcnt; char c,t; oldcrc32 = 0x00000000; //初值为0 charcnt=0; while(len--) { t= (oldcrc32 >> 24)&0xFF; //要移出的字节的值 oldcrc=crc_32_tab[t]; //根据移出的字节的值查表 c=DataBuf[charcnt]; //新移进来的字节值 oldcrc32= (oldcrc32 << 8) | c; //将新移进来的字节值添在寄存器末字节中 oldcrc32=oldcrc32^oldcrc; //将寄存器与查出的值进行xor运算 charcnt++; } crc32=oldcrc32; return crc32; }
-
Ethernet帧的解析
2014-12-12 12:13:401.按Ethernet V2.0格式封装Ethernet帧,源地址来自本机MAC地址,目的地址为随意编写的有效MAC地址,类型字段为IP协议对应值,数据字段来自文本文件(见附件),帧校验字段采用8位CRC校验。 2.输出每个帧的各字段... -
TCP/IP帧,校验的计算方式
2019-08-03 15:43:431.把校验和字段置为0; 2.对IP头部中的每16bit进行二进制求和; 3.如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值; 4.将该16bit的值取反,存入校验和字段...1.IP报头的checksum
1.把校验和字段置为0;
2.对IP头部中的每16bit进行二进制求和;
3.如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
4.将该16bit的值取反,存入校验和字段。
2.UDP/TCP报头的checksum
UDP/TCP报头中的校验和的计算比较复杂的,要用到 UDP/TCP伪首部:先要填充伪首部各个字段,然后再将UDP/TCP报头以后(包括报头)的数据附加到伪首部的后面,再对位首部使用上述校验和计算,所得到的值才是UDP/TCP报头部分的校验和。 位首部可以用如下的结构体表示:typedef struct{
ULONG sourceip; //源IP地址
ULONG destip; //目的IP地址
BYTE mbz; //置空(0)
BYTE ptcl; //协议类型
USHORT plen; //TCP/UDP数据包的长度(即从TCP/UDP报头算起到数据包结束的长度 单位:字节)
}Psd_Header; 注:1.对于UDP协议,如果不想计算的话,可以把校验和的两个字节置0,根据RFC的规定,这样上层UDP协议就不会计算校验和了2.sniffer查看校验和时,最好在中间节点。你在本机抓的时候,你的协议驱动在NDIS架构中层次太高,与标准协议栈是平级的,此时校验和处未被计算填充,还是垃圾填充数据。你在网络其它节点抓的时候,报文已经经过网卡的处理,校验和被正确设置了。3.参考测试代码1
USHORT checksum(USHORT *buffer,int size) { unsigned long cksum=0; while(size>1) { cksum+=*buffer++; size-=sizeof(USHORT); } if(size) { cksum+=*(UCHAR *)buffer; } //将32位数转换成16 while (cksum>>16) cksum=(cksum>>16)+(cksum & 0xffff); return (USHORT) (~cksum); }
4.参考测试代码2
#include "stm32f4xx.h" #include "usart.h" #include "delay.h" #include <string.h> #define LWIP_MAKE_U16(a, b) ((a << 8) | b) //((b << 8) | a) #define LWIP_hton_U16(a) ((((a)>>8)&0x00ff) |(((a)<<8)&0xff00)) #define LWIP_hton_U32(a) \ ((u32)((a>>0) & 0xff) << 24) | \ ((u32)((a>>8) & 0xff) << 16) | \ ((u32)((a>>16) & 0xff) << 8) | \ (u32)((a>>24) & 0xff) #define IP4_ADDR( a,b,c,d) \ ((u32)((a) & 0xff) << 24) | \ ((u32)((b) & 0xff) << 16) | \ ((u32)((c) & 0xff) << 8) | \ (u32)((d) & 0xff) #define FOLD_U32T(u) (((u) >> 16) + ((u) & 0x0000ffffUL)) #define SWAP_BYTES_IN_WORD(w) (((w) & 0xff) << 8) | (((w) & 0xff00) >> 8) u16 lwip_standard_chksum(void *dataptr, int len) { u8 *pb = (u8 *)dataptr; u16 *ps, t = 0; u32 sum = 0; int odd = ((u32)pb & 1); /* Get aligned to u16_t */ if (odd && len > 0) { ((u8 *)&t)[1] = *pb++; len--; } /* Add the bulk of the data */ ps = (u16 *)(void *)pb; while (len > 1) { sum += *ps++; len -= 2; } /* Consume left-over byte, if any */ if (len > 0) { ((u8 *)&t)[0] = *(u8 *)ps; } /* Add end bytes */ sum += t; /* Fold 32-bit sum to 16 bits calling this twice is propably faster than if statements... */ sum = FOLD_U32T(sum); sum = FOLD_U32T(sum); /* Swap if alignment was odd */ if (odd) { sum = SWAP_BYTES_IN_WORD(sum); } return (u16)sum; } typedef struct udp_pcb { //IP u8 headIP; u8 priority; u16 lenIP; u16 identifyflag; u16 flags ; u8 ttl; u8 proto; u16 checkIP; u32 srcIP; u32 destIP; //UDP u16 srcPORT; u16 destPORT; u16 lenUDP; u16 checkUDP; }udp_pcb_def; udp_pcb_def udata; void UDP_data(u8* retbuf,u16* retlen,u16 srcPort,u16 destPort,u8* srcIpbuf,u8* destIpbuf,u8 *dbuf,u16 dlen) { u32 chk_sum_IP = 0; u32 chk_sum_UDP = 0; u32 chk_sum = 0; u16 chk_sumxxx = 0;//测试 u16 srcPORT = srcPort; u16 destPORT = destPort; u16 lenUDP = 8+dlen; u8 proto = 0x11;//udp u32 srcIP = IP4_ADDR(srcIpbuf[0],srcIpbuf[1],srcIpbuf[2],srcIpbuf[3]);//高低位对调 u32 destIP = IP4_ADDR(destIpbuf[0],destIpbuf[1],destIpbuf[2],destIpbuf[3]);//高低位对调 u8 * databuf = dbuf; u16 datalen = dlen; u8 headIP = 0x45;//ipv4 20B u8 priority = 0x00;//优先级 u16 lenIP = 20+8+dlen; static u16 identifyflag = 0x0001;//++ u16 flags = 0x0000; u8 ttl = 0x40;//生命 //~~~~~~~~~~~~~~~~~~~IP头 chk_sum=0; chk_sumxxx = LWIP_MAKE_U16(headIP, priority);//0x4500 chk_sum +=chk_sumxxx; chk_sumxxx = lenIP; chk_sum += chk_sumxxx; chk_sumxxx = identifyflag; chk_sum += chk_sumxxx; chk_sumxxx = flags; chk_sum += chk_sumxxx; chk_sumxxx = LWIP_MAKE_U16(ttl, proto);//0x4011 chk_sum += chk_sumxxx; chk_sumxxx = (srcIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = (srcIP >> 16); chk_sum += chk_sumxxx; chk_sumxxx = (destIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = (destIP >> 16); chk_sum += chk_sumxxx; chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = ~chk_sum; chk_sum_IP=chk_sum; //~~~~~~~~~~~~~~~~~~~UDP头+data chk_sum=0; chk_sumxxx = (srcPORT); chk_sum += chk_sumxxx; chk_sumxxx = (destPORT); chk_sum += chk_sumxxx; chk_sumxxx = (lenUDP); chk_sum += chk_sumxxx; chk_sumxxx = lwip_standard_chksum(databuf, datalen);//求和完的结果会 高低位对调 chk_sumxxx = SWAP_BYTES_IN_WORD(chk_sumxxx); chk_sum += chk_sumxxx; //~~~~~~~~~~~~~~~~~~~伪UDP头 chk_sumxxx = (srcIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = (srcIP >> 16); chk_sum += chk_sumxxx; chk_sumxxx = (destIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = (destIP >> 16); chk_sum += chk_sumxxx; chk_sumxxx = (lenUDP); chk_sum += chk_sumxxx; chk_sumxxx = (proto); chk_sum += chk_sumxxx; chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = ~chk_sum; chk_sum_UDP=chk_sum; //~~~~~~~~~~~~~~~~~~~整理数据 //IP udata.headIP = headIP; udata.priority = priority; udata.lenIP = LWIP_hton_U16(lenIP); udata.identifyflag = LWIP_hton_U16(identifyflag); udata.flags = LWIP_hton_U16(flags); udata.ttl = ttl; udata.proto = proto; udata.checkIP = LWIP_hton_U16(chk_sum_IP); udata.srcIP = LWIP_hton_U32(srcIP); udata.destIP = LWIP_hton_U32(destIP); //UDP udata.srcPORT = LWIP_hton_U16(srcPORT); udata.destPORT = LWIP_hton_U16(destPORT); udata.lenUDP = LWIP_hton_U16(lenUDP); udata.checkUDP = LWIP_hton_U16(chk_sum_UDP); memcpy(&(retbuf[0]),(u8*)(&udata),sizeof(udp_pcb_def)); //retbuf = (u8*)(&udata); //*retlen = sizeof(udp_pcb_def); return; } int main(void) { //IP_data(); u8 ipsrcbuf[4]; ipsrcbuf[0]=10; ipsrcbuf[1]=50; ipsrcbuf[2]=133; ipsrcbuf[3]=37; u8 ipdesbuf[4]; ipdesbuf[0]=114; ipdesbuf[1]=55; ipdesbuf[2]=168; ipdesbuf[3]=54; u8 sdatabuf[] = "from minh"; u8 retbuf[(sizeof(udp_pcb_def)+sizeof(sdatabuf))]; u16 retlen=0; UDP_data(retbuf,&retlen,1501,1501,ipsrcbuf,ipdesbuf,sdatabuf,sizeof(sdatabuf)-1); memcpy(&(retbuf[sizeof(udp_pcb_def)]),sdatabuf,sizeof(sdatabuf)-1); } ^DPPPI: 1,"10.50.143.226","0.0.0.0","219.150.32.132","123.150.150.150" int i,n=0; String[] words; Scanner sc=new Scanner(System.in); System.out.println ("请输入一串数字,以逗号隔开:"); String str=sc.next(); for(i=0;i<str.length();i++){ if(str.charAt(i)==',') n++; } words=str.trim().split(","); for(i=0; i<str.length()-n; i++) { System.out.print(words[i]+"\t"); } /* void IP_data(void){ u32 chk_sum = 0; u16 chk_sumxxx = 0; u8 headIP1 = 0x45;//ipv4 20B u8 headIP2 = 0x00;//优先级 u16 lenIP = 0x0026; u16 lenflag = 0x0001;//++ u16 lenflagx = 0x0000; u8 ttl = 0x40;//生命 u8 proto = 0x11;//udp u32 srcIP = 0x0a328525;//高低位对调 u32 destIP = 0x7237a836;//高低位对调 chk_sumxxx = LWIP_CHANGE_U16(LWIP_MAKE_U16(headIP1, headIP2));//0x4500 chk_sum +=chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(lenIP); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(lenflag); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(lenflagx); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(LWIP_MAKE_U16(ttl, proto));//0x4011 chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(srcIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(srcIP >> 16); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(destIP & 0xFFFF); chk_sum += chk_sumxxx; chk_sumxxx = LWIP_CHANGE_U16(destIP >> 16); chk_sum += chk_sumxxx; //chk_sumxxx = (chk_sum >> 16); //chk_sum += chk_sumxxx; //chk_sumxxx = (chk_sum & 0xFFFF); //chk_sum += chk_sumxxx; chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = (chk_sum >> 16) + (chk_sum & 0xFFFF); chk_sum = ~chk_sum; } */
-
以太网帧结构以及CRC校验
2017-05-27 14:25:57MAC帧报文结构 ...以太帧由一个32位冗余校验码结尾。它用于检验数据传输是否出现损坏。 帧结构图:说一下各个字段的作用: 前同步码:第一个字段是7个字节的前同步码,1和0交替,作用是用来使接收端的适配器在接MAC帧报文结构
在以太网链路上的数据包称作以太帧。以太帧起始部分由前导码和帧开始符组成。后面紧跟着一个以太网报头,以MAC地址说明目的地址和源地址。帧的中部是该帧负载的包含其他协议报头的数据包(例如IP协议)。以太帧由一个32位冗余校验码结尾。它用于检验数据传输是否出现损坏。
帧结构图:
说一下各个字段的作用:
- 前同步码:第一个字段是7个字节的前同步码,1和0交替,作用是用来使接收端的适配器在接收MAC帧时能够迅速调整时钟频率,使它和发送端的频率相同。
- 帧开始定界符:第二个字段是1个字节的帧开始定界符,前六位1和0交替,最后的两个连续1表示告诉接收端适配器:“帧信息要来了,你准备接收把。
- MAC 目的地址:第三个字段是6字节(MAC地址占48位,如:FF,FF,FF,FF,FF),发送方的网卡(MAC)地址,用处是当网卡接收到一个数据帧时,首先会检查该帧的目的地址,是否与当前适配器的物理地址相同,如果相同则会进一步处理,不同则直接丢弃。
- 源MAC地址:发送端的MAC地址同样占6个字节。
- 类型:该字段在网络协议栈分解中及其重要,考虑当PDU(协议数据单元)来到某一层时,它需要将PDU交付给上层,而上层协议众多,所以在处理数据的时候,必须要一个字段标识我这个交付给谁。如,该字段为0x0800时,表示将有效载荷交付给IP协议,为0x0806交付给ARP,0X8035交付给RARP。
- 数据:数据也叫有效载荷,除过当前层协议需要使用的字段外,即需要交付给上层的数据,以太网帧数据长度规定最小为46字节,最大为1500字节,如果有不到46字节时,会用填充字节填充到最小长度。最大值也叫最大传输单元(MTU),我们可以再 Linux输入 ifconfig 可以看到有一项MTU:1500。
- 帧检验序列FCS(使用CRC校验法):检测该帧是否出现差错。
无效的MAC帧
当出现下列情况之一即为无效的MAC帧:
- 帧的长度不是8的倍数。
- 检验序列检验出差错。
- 帧长度数据字段不在46-1500之间。
CRC校验步骤
(1)、在发送端先把数组按照一定划分大小划分为组,假设每组K个比特,要传输的数据记位M,发送方要做的就是在数据M后面添加用于差错检测的 n 位冗余码,然后构成一个帧发送出去,也就是说此时发送的数据在原来基础上曾家了n位冗余码。
(2)、n 位冗余码怎么来的?
首先在原数据M后面添加n个0相当于左移n位,此时数据长度变为原来的每组K个比特加n即(k+n)位。然后用该序列除以在计算之前规定的一个长度为(n+1)位的除数P,根据二进制的模2 运算,计算出余数R。这个余数R就会作为冗余码拼接在原数据后面发送出去。模 2 运算:加法不进位,减法和加法一样,比如:
1111+1010 = 0101;(3)、接收方把收到的每一个帧都处于同样的余数,然后检查得到的余数R:
- 若余数R = 0,则判定这个帧没有错,接受。
- 若余数R != 0,则判定这个帧有差错。(只能检测出该帧出现错误,无法定位出错位置)。注意:该检测只能保证无比特差错,而不能保证可靠传输。
-
以太网数据帧的格式以及每个字段的作用
2020-03-24 22:19:57目的地址 源地址 类型 数据 帧校验序列 前导码(7字节):使接收器建立比特同步 其实定界符SFD(1字节):指示一帧的开始 目的地址DA(6字节):指出要接收该帧的工作站 源地址SA(6字节):指示发送该帧的工作站... -
CRC校验关键点说明(内附C语言CRC校验库)
2019-03-22 17:59:36CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,... -
CRC校验原理及步骤
2017-06-22 11:59:11CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,... -
关于CRC校验
2017-06-19 13:09:54crc即循环冗余校验码,是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面... -
计算机网络导论 数据链路层 协议 HDLC PPP Ethernet
2020-07-05 20:45:29HDLC 面向位协议 正常相应模式:一主 多从 异步平衡模式:点到点连接 信息帧I-frame:用户信息字段存上层 ...帧校验字段:CRC 控制字段 P/F 为1 意味轮询/终止 管理帧 00 RR 准备接受 10 RNR 不准备接受 01 -
数据校验--CRC校验
2018-12-11 16:05:27CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。 循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式... -
循环冗余校验(CRC校验)
2017-06-28 16:12:24循环冗余校验码:是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。循 环冗余检查是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也... -
以太网数据帧的报尾封装字段是什么_「计算机网络」第三章:数据链路层
2020-12-31 13:27:483.1 数据链路层的基本概念3.1.1 数据链路层的简单模型数据链路层不关心物理层解决的问题,只关心帧头帧尾和校验。3.1.1 数据链路层的信道类型->点到点信道:这种信道使用一对一的点对点通信方式。->广播信道...