LWIP的IP协议笔记
IP协议简介
IP协议是TCP/IP协议族的基石,它为上层提供无连接、不可靠的服务
无连接:指IP通信双方都不长久的维持对方的任何信息。这表示上层协议每次发送数据,都需要明确指出对方的IP地址
不可靠:指IP协议不能把IP数据报准确到达接收端,只是会尽最大努力。一旦发送失败,就通知上层协议,而不会试图重发
描述:总路线主机H1 到 主机H2
目的地址IP2 就是主机H2的地址
源地址IP1 就是主机H1的地址
传输 路径 | 网络层 写入IP数据报首部地址 | 数据链路层 写入MAC帧首部地址 | ||
源地址 | 目的地址 | 源地址 | 目的地址 | |
H1->R1 | IP1 | IP2 | HA1 | HA3 |
R1->R2 | IP1 | IP2 | HA4 | HA5 |
R2->H2 | IP1 | IP2 | HA6 | HA2 |
为什么需要MAC地址和IP地址?
①数据传输过程中,源IP地址和目标IP不变
②路由器根据目标IP地址的网络号进行路由
IP地址分为A、B、C、D、E类地址
总结:
MAC地址用来数据转发,而IP地址用来确认路标
mac的作用则是实现直连的两个设备之间的通信,而IP则负责在没有智联的两个网络之间进行通信传输;
IP分片
MTU最大1500字节;
当数据传输大于1500字节的时候就需要数据分片
一、抓取 IP协议的内容
Wireshark抓包工具
可以看到每个数据包的所有bit
并可以分析数据
二、IP协议字段解析
version | 版本 |
header length | 头⻓度 |
differentiated service field | 差分服务区域 |
total length | 总⻓度 |
identification | 标识位 |
flags | 标志位 |
fragment offset | 偏移量 |
time to live | ⽣存周期 |
Protocol | 上层协议 |
Header checksum | 校验和 |
Source | 源IP地址 |
destination | ⽬的IP地址 |
数据包框图
三、IP协议处理分片
3.1、MTU是什么最⼤传输单元,
默认是1500字节
指的是⼀个数据包从⽹卡发出时,最⼤的total length的⻓度
如果⼤于1500字节就会进⾏分⽚
3.2、理解identification 、flags 、fragment offset 这些字段 的作用
1,identification 字段:
⽤来标识⼀个分⽚ ,例如:4000字节(含首部的4000字节)需要分⽚,分成1500, 1500, 1040,三⽚ ,这些数据包的identification字段都是⼀致的;与下图3一致(这里和课程差异一点)
多出来的60 是多的三个包控制段3*20
2,flags 字段:
Reserved bit | 保留位,暂时没作⽤ |
Don’t fragment | DM位,该位置1,说明该数据包不能被分⽚(⽤来测试接⼝的MTU⼤⼩的) |
More fragment | FM位,如果是最后⼀个分⽚,就⽤0来表示,不是最后⼀个分⽚就⽤1来表示 |
3,fragment offset 字段:
fragment offset也叫偏移量,偏移的计算是payload部分payload指的是去掉⼆层头部和三层头部后的内容1500字节的分⽚,指的是IP+payload的⼤⼩
加上E2,整个数据包抓包看到的⼤⼩是1514字节 ,这点不用理会
下图3的数据包,是首部20字节加数据3980的4000字节
888:标识符
0/1:尾部识别
185/370:是整个大包的数据偏移量 185是从3980数据的第185个开始的字节
185=(1500-20)/8 mtu=1500 首部=20字节 除以8=是等于8倍的压缩量;注释1
注释1:这个数据只有13bit 通过压缩 8倍 13bit+3bit=16bit 可描述偏移65535个字节
这里是内存堆 payload+偏移量 指向分片的数据段
四、分片原理
调用分片原理的主函数实现在源文件:\Middlewares\lwip\src\core\ipv4\ip4.c 826行
err_t ip4_output_if_src(struct pbuf *p, const ip4_addr_t *src, const ip4_addr_t *dest,
u8_t ttl, u8_t tos,
u8_t proto, struct netif *netif)
条件:用户数据大小:3960,pbuf:内存堆,TCP模式
本来用户数据是3980,至少20字节作为首部,但是多了各层添加头部,所以真正的数据是3960
不要看34 54的分片结构框图,我都被搞蒙。答:这里的54和分包的结构不一样,这是未分包时候的;
PS:以下的4000是 20字节头部+数据的4000
1),申请pbuf
54字节由来:20字节IP首部 20字节TCP经过各层的首部 14字节是选项字节和源/目标地址
下图是申请的内存堆大小
2),递交给传输层
TCP首部:20,调用pbuf_add_header(p, TCP_HLEN)函数偏移payload指针
3),递交给网络层
IP首部:20,调用pbuf_add_header(p, IP_HLEN)函数偏移payload指针
4),分片ip4_frag()函数
err_t
ip4_frag(struct pbuf *p, struct netif *netif, const ip4_addr_t *dest)
{
struct pbuf *rambuf;
#if !LWIP_NETIF_TX_SINGLE_PBUF
struct pbuf *newpbuf;
u16_t newpbuflen = 0;
u16_t left_to_copy;
#endif
struct ip_hdr *original_iphdr;
struct ip_hdr *iphdr;
/* 第一步:nfb = (1500 - 20)/8 = 185 */
const u16_t nfb = (u16_t)((netif->mtu - IP_HLEN) / 8);
u16_t left, fragsize;
u16_t ofo;
int last;
/* poff = 20 */
u16_t poff = IP_HLEN;
u16_t tmp;
int mf_set;
/* 第二步:获取IP首部信息 */
original_iphdr = (struct ip_hdr *)p->payload;
iphdr = original_iphdr;
/* 第三步:判断首部获取成功 */
if (IPH_HL_BYTES(iphdr) != IP_HLEN) {
/* ip4_frag() does not support IP options */
return ERR_VAL;
} /
* 第四步:tmp = 保存偏移量的字段数值 = 0 */
tmp = lwip_ntohs(IPH_OFFSET(iphdr));
/* 第五步:ofo = 0 & 0x1fffU = 0 */
ofo = tmp & IP_OFFMASK;
/* 第六步:mf_set = 0 & 0x2000U = 0 */ */
mf_set = tmp & IP_MF;
/* 第七步:left = p->tot_len - 20
1:4000 - 20 = 3980 */
left = (u16_t)(p->tot_len - IP_HLEN);
/* 第八步:left > 0 ? */
while (left) {
/* 第九步:分片大小fragsize = left < (nfb * 8) ? left : (nfb * 8)
1:3980 < 1480 ? 3980 : 1480 fragsize = 1480
2:2500 < 1480 ? 2500 :1480 fragsize = 1480
3:1020 < 1480 ? 1020 :1480 fragsize = 1020 */
fragsize = LWIP_MIN(left, (u16_t)(nfb * 8));
/* 第十步:申请pbuf内存---用来描述分片 */
rambuf = pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);
if (rambuf == NULL) {
goto memerr;
} /
* 第十一步:把IP首部复制到rambuf当中 */
SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);
/* iphdr指向rambuf->payload */
iphdr = (struct ip_hdr *)rambuf->payload;
/* left_to_copy = 分片大小fragsize
1:left_to_copy = 1480
2:left_to_copy = 1480
3:left_to_copy = 1020 */
left_to_copy = fragsize;
while (left_to_copy) {
struct pbuf_custom_ref *pcr;
/* plen = p->len - poff
1:4000 - 20 = 3980 plen = 3980
2:4000 - 1500 = 2500 plen = 2500
3:4000 - 2980 = 1020 plen = 1020 */
u16_t plen = (u16_t)(p->len - poff);
/* 新的pbuf大小 newpbuflen = left_to_copy < plen ? left_to_copy : plen
1:newpbuflen = 1480
2:newpbuflen = 1480
3:newpbuflen = 1020 */
newpbuflen = LWIP_MIN(left_to_copy, plen);
/* 是否为空 */
if (!newpbuflen) {
poff = 0;
p = p->next;
continue;
} /
* 申请一个新的结构pbuf_custom_ref */
pcr = ip_frag_alloc_pbuf_custom_ref();
if (pcr == NULL) {
pbuf_free(rambuf);
goto memerr;
} /
* 镜像pbuf(p),尽管我们可能不需要它的全部。 */
newpbuf = pbuf_alloced_custom(PBUF_RAW, newpbuflen, PBUF_REF, &pcr->pc,
(u8_t *)p->payload + poff, newpbuflen);
if (newpbuf == NULL) {
ip_frag_free_pbuf_custom_ref(pcr);
pbuf_free(rambuf);
goto memerr;
} p
buf_ref(p);
pcr->original = p;
pcr->pc.custom_free_function = ipfrag_free_pbuf_custom;
/* rambuf与newpbuf链接 */
pbuf_cat(rambuf, newpbuf);
/* 1:left_to_copy = 1480 - 1480 = 0
2:left_to_copy = 1480 - 1480 = 0
3:left_to_copy = 1020 - 1020 = 0 */
left_to_copy = (u16_t)(left_to_copy - newpbuflen);
if (left_to_copy) {
poff = 0;
p = p->next;
}
} /
* 1:poff = 20 + 1480 = 1500
2:poff = 1500 + 1480 = 2980
3:poff = 2980 + 1020 = 4000 */
poff = (u16_t)(poff + newpbuflen);
/* 1:last = (3980 <= 1500 - 20 ) = 0
2:last = (2500 <= 1500 - 20 ) = 0
3:last = (1020 <= 1500 - 20 ) = 1 */
last = (left <= netif->mtu - IP_HLEN);
/* 设置新的偏移量和MF标志 */
tmp = (IP_OFFMASK & (ofo));
if (!last || mf_set) {
/* 最后一个片段有MF设置 */
tmp = tmp | IP_MF;
} /
* 设置该分片的IP首部信息 */
IPH_OFFSET_SET(iphdr, lwip_htons(tmp));
IPH_LEN_SET(iphdr, lwip_htons((u16_t)(fragsize + IP_HLEN)));
IPH_CHKSUM_SET(iphdr, 0);
/* 发送该分片IP数据包 */
netif->output(netif, rambuf, dest);
IPFRAG_STATS_INC(ip_frag.xmit);
/* 释放内存 */
pbuf_free(rambuf);
/* 1:left = 3980 - 1480 = 2500
2:left = 2500 - 1480 = 1020
3:left = 1020 - 1020 = 0 */
left = (u16_t)(left - fragsize);
/* 1:ofo = 0 + 185 = 185
2:ofo = 185 + 185 = 370
3:ofo = 370 + 185 = 555 */
ofo = (u16_t)(ofo + nfb);
} M
IB2_STATS_INC(mib2.ipfragoks);
return ERR_OK;
memerr:
MIB2_STATS_INC(mib2.ipfragfails);
return ERR_MEM;
}
分片函数的效果图
总
传输层递交示意图(已经添加了IP首部,该数据包处于IP层(网络层))4000字节=头+数据