跟我一起学TCP/IP

Web、Mail、Ftp、DNS、Proxy、VPN、Samba、LDAP 等基础网络服务
723937936@qq.com
帖子: 51
注册时间: 2023-02-26 9:59
系统: ubuntu

Re: 跟我一起学TCP/IP

#16

帖子 723937936@qq.com » 2023-03-11 8:26

CIDR(Classless Interdomain Routing)

本书的重点不是路由协议,作者对OSPF和BGP只进行了简单概述,我们也不进行深入研究了

互联网一开始设计的路由架构是基于网络的,将网络分为3类,分别是Class A、Class B、Class C
每个网络要求一个路由表项

回忆一下地址分类
Screen Shot 2023-02-28 at 9.35.06 PM.png
A类IP地址的netid有7位,共2^7=128个网络,因此要求128个路由表项
B类IP地址的netid有14位,共2^14=16384个网络,要求16384个路由表项
C类IP地址的netid有21位,共2^21=2097152个网络,要求2097152个路由表项

对于A类和B类网络,路由表项的规模尚可接受,但是对于C类网络,这个路由表项的数目过大,无论从维护还是性能考虑都是不可接受的

CIDR是一种新的路由技术,他不基于网络,而是基于域(domain),也就是说他没有网络的概念了,无论物理网络的拓扑结构是什么样,都可以使用这种新的路由技术

CIDR虽然基于域而不是网络,但是他的思想是一样的,类似进程使用多级页表来索引虚拟地址空间的思想

页表思想介绍:
对于4GB的虚拟地址空间:
如果采用一级页表,那么对于4KB大小的页,就需要2^20=1048576个页表项
如果采用两级页表,那么第一级页表项只需要2^10=1024个(第一级页表索引10位,第二级页表索引10位,页内偏移12位)
我们可以看出采用二级页表节省的是第一级页表项的个数

对于路由表项来说,节省的是Internet Routing table entries

Internet router 大概是指连接各自治系统的路由器

IP地址分类就类似只采用一级页表,特别是对于C类网络(hostid占8位,可以理解为页大小,netid占21位(可以理解为页表索引)),最初引入CIDR就是为了减少索引C类网络的路由表项数目

CIDR也是采用多级页表思想,且级数不固定

任意连续的IP地址范围都可以组成一个域,以书上的例子194.0.0.0-195.255.255.255这个地址范围,为了方便观察,我们转换成32位的二进制
11000010 00000000 00000000 00000000
11000011 11111111 11111111 11111111
我们观察到这个地址范围内所有的地址的前7位都相同,因此所有以1100001开头的IP地址都在这个域内,所以只需要一个路由表项就可以将IP数据报转发到这个域内,剩下的25位还可以继续划分成不同的子域,比如继续使用额外的7位组成一个子域,那么第一个子域的范围如下
11000010 00000000 00000000 00000000 (194.0.0.0)
11000010 00000011 11111111 11111111 (194.3.255.255)
子域还可以继续划分子子域,划分的级数没有限制

标识域的范围也是使用一个32位的mask,称为domain mask

要识别一个IP地址属于哪个域,只需要将IP地址和domain mask进行与操作就得到了域号
通往某个域的路由表项类似: domain-number gateway domain-mask

在IP模块执行IP routing时,会选择最长匹配(longest match),也就是选择mask_one_bit_count(mask)最大的那条route
假如将路由表里所有路由条目按mask_one_bit_count(mask)从大到小排序,那么第一个匹配的条目就是最佳匹配(best match)的route

兼容性:
CIDR路由条目与先前的host-route、net-route、default-route是兼容的,因为host-route对应的mask是全1(这个域中只有一台主机),default-route对应的mask全0,所以匹配顺序是按host-route、net-route、default-route的顺序进行的
上次由 723937936@qq.com 在 2023-03-12 22:12,总共编辑 1 次。
723937936@qq.com
帖子: 51
注册时间: 2023-02-26 9:59
系统: ubuntu

Re: 跟我一起学TCP/IP

#17

帖子 723937936@qq.com » 2023-03-12 21:12

第11章:UDP

UDP是一个面向数据报的传输层协议,应用程序的每个sendto调用都会产生一个udp数据报(封装在一个ip数据报中)
应用调用sendto发送udp数据报,需要考虑生成的ip数据报的大小限制:
1. path MTU:生成的ip数据报的总长度不能超过path MTU,否则会导致分片
2. IP Maximum Datagram Size:IPv4实现定义该值,表示可以处理的最大ip数据报大小,该值的可移植大小为576字节(包含ip header)

UDP格式
Screen Shot 2023-03-12 at 11.22.52 AM.png
Screen Shot 2023-03-12 at 11.23.05 AM.png
字段说明:
source port number:标识发送进程
destination port number:标识接收进程
UDP length:UDP数据报总长度(包含header和data),该字段最小值8,也就是允许data的长度为0
checksum:包括 UDP pseudo header、UDP header和data的校验和(可选,默认使能,填0表示发送者未计算校验和)

ones' complement

要计算UDP checksum需要理解是什么ones' complement,要理解什么是ones' complement,先理解什么是complement?
complement:我翻译成补数(在计算机中一般翻译成补码,但是我认为翻译成补数更具一般性)
理解补数最好的例子是时钟,我们以24时制为例,在24时制里,比如11+13=24,我们说11和13相对24互为补数,我们称24为模数

时钟的算术运算:
举例说明:
比如现在23点,再过2个小时,是1点,也就是:
23+2=1
我们脑子里是这样计算的,23+2=25,然后25-24=1
再举一个例子,比如现在是2点,再过24小时,还是2点,也就是:
2+24=2
也就是一个数加上他的模数等于自己
还有0点和24点也表示一个值

下面假如一天只有15小时,比如3+12=15,我们称3和12相对15互为补数
下面的例子用二进制做补数运算

比如:
3+12=15
转换为二进制:
0011+1100=1111
像这种模数为全1的情况有一个专门的名字,称为一补数(ones complement),其实叫什么名字不重要,运算方法完全一样

同样,我们计算13+5=3,我们是这样计算的,13+5=18,然后18-15=3
换算成二进制再计算一遍:
1101+0101=10010,然后10010-1111=0011,结果为3
发生进位后要减去模数的操作也可以如下计算:
10010-1111=10010-(10000-1)=10010-10000+1=0010+1=0011
这种计算的技巧是:将进位直接加到最低位上
另外相对1111互补的两个数对应位正好相反,这是一补数的特点

UDP checksum

UDP checksum是计算16-bit word的和(按补数方法计算)
16-bit word的模数是0xffff

计算UDP校验和算法:

1. 将所有16-bit word相加(按补数方法计算)
2. 将得到的结果取补数就是UDP的校验和

具体实现参考UNP的源代码:
https://github.com/unpbook/unpv13e/blob ... in_cksum.c

UDP pseudo header

UDP校验和的计算要包含一些额外的内容,称为UDP pseudo header,结构如下:
Screen Shot 2023-03-12 at 6.14.00 PM.png
为了计算UDP校验和,需要先构造上图所示结构(此结构只用于计算校验和,并不会发送此结构)
因为计算校验和要求数据总长度必须是偶数,所以如果UDP length字段为奇数,则需要在数据末尾添加一个字节('\0'),(上图中的两个16-bit UDP length字段的值不要加1)
计算校验和之前,上述结构中的16-bit UDP checksum字段必须为0
上面提到如果UDP数据报里的checksum字段为0表示发送者没有计算校验和,但是如果发送者计算的校验和恰巧为0,那么就在checksum字段填0xffff(这种情况接收端不需要做特殊处理,因为0x0000和0xffff在补数的世界里是等价物,想象前面举的24时制的例子:0点和24点是一样的)

接收端检查校验和算法:

1. 将所有16-bit word相加(按补数方法计算)
2. 如果得到的结果为0xffff,则表示检查正确

接收主机的UDP模块同样需要在接收的UDP数据报前面添加UDP pseudo header,以及(如果UDP数据报长度不是偶数)在尾部添加'\0'

为什么接收端计算出的结果是0xffff?(假设接收到的UDP数据报没有损坏)

发送端计算校验和的最后一个步骤是求补数,假设发送端第一步计算的结果为0x0001,那么第二步得到的校验和就是0xfffe(对应的补数)
那么接收端计算的结果就是0x0001+0xfffe(接收端在第一步计算时会连带checksum字段一起相加),所以结果为0xffff

一句话就是互补的两个数相加等于模数,这是补数的定义

前面提到如果发送端计算的校验和恰巧为0x0000,需要在checksum字段填0xffff,这种情况对于接收端不需要做特殊处理,原因是
在补数的世界里,0和模数是等价物,也就是
0x0000+0xffff=0xffff
0xffff+0xffff=0xffff(想象一下24点再过24小时还是24点)


接收主机的UDP模块如果检测到UDP数据报的校验和错误,则默默丢弃该UDP数据报,并不会回送ICMP错误消息给发送者(IP模块如果检测到IP数据报头的校验和错误,也会默默丢弃该IP数据报)

注意:
1. 即使UDP checksum检查正确,也不表示UDP数据报内容没有变化,因为这个校验和算法比较简陋,并不能检查出所有错误
2. 说UDP是一个不可靠的协议,并不是指UDP的checksum无法检测所有错误,而是指可达性,也就是UDP数据报能否送达目的主机。就checksum来说,TCP也使用相同checksum算法,同样无法检测TCP segment的所有错误,但是TCP是保证到达目的主机或无法送达时报告错误(有确认机制)。
如果需要确保内容正确,应该使用密码算法(比如哈希算法、消息认证码、数字签名等)
网络层:IP+密码算法=IPsec
传输层:TCP+密码算法=TLS

抓包示例:

一个终端发送udp数据报,data="hello\n"

代码: 全选

$ nc -u -p 7777 192.168.0.3 8888
hello

另一个终端抓包

代码: 全选

$ sudo ./udpdump
IP 192.168.0.6.7777 > 192.168.0.3.8888: UDP, length 6 (UDP cksum=8179)
编程Note:

如果使用raw socket发送UDP数据报,需要应用程序构造完整的UDP数据报(包含UDP header)所以需要计算UDP校验和(参考UNP第28章)
上次由 723937936@qq.com 在 2023-03-15 7:02,总共编辑 2 次。
723937936@qq.com
帖子: 51
注册时间: 2023-02-26 9:59
系统: ubuntu

Re: 跟我一起学TCP/IP

#18

帖子 723937936@qq.com » 2023-03-15 6:28

IP分片

当IP模块发送IP数据报时,IP模块会获取外出接口的MTU或特定socket的PMTU,如果要发送的IP数据报的总长度超过MTU或PMTU,则IP模块可能会执行分片操作或返回EMSGSIZE错误(可以通过socket option来改变行为,下文有说明)

无论发送主机还是中间的路由器都可能会执行分片操作,但只有分片到达最终的目的主机才会执行重组操作

回忆IP数据报格式:
Screen Shot 2023-02-27 at 10.01.38 PM.png
其中16-bit identification、3-bit flags、13-bit fragment offset与IP分片有关

16-bit identification:用于标识同一个IP数据报的不同分片,目的主机根据此id来判断一个IP数据报有哪些分片
3-bit flags:最低位是MF(more fragments flag),中间位是DF(dont fragment flag),最高位保留
13-bit fragment offset:分片包含的数据的偏移,单位8字节

分片被认为是不好的,是因为一个分片丢失,整个IP数据报都要重传

观察分片

在一个终端执行ping

代码: 全选

$ ping -c 1 -s 1472 192.168.0.3
$ ping -c 1 -s 1473 192.168.0.3
在另一个终端运行tcpdump

代码: 全选

$ sudo tcpdump -i enp0s3 -n -v icmp
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
1. 23:28:43.900886 IP (tos 0x0, ttl 64, id 9008, offset 0, flags [DF], proto ICMP (1), length 1500)
    192.168.0.6 > 192.168.0.3: ICMP echo request, id 12521, seq 1, length 1480
2. 23:28:43.901048 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto ICMP (1), length 1500)
    192.168.0.3 > 192.168.0.6: ICMP echo reply, id 12521, seq 1, length 1480

3. 23:28:50.439809 IP (tos 0x0, ttl 64, id 10526, offset 0, flags [+], proto ICMP (1), length 1500)
    192.168.0.6 > 192.168.0.3: ICMP echo request, id 12522, seq 1, length 1480
4. 23:28:50.439829 IP (tos 0x0, ttl 64, id 10526, offset 1480, flags [none], proto ICMP (1), length 21)
    192.168.0.6 > 192.168.0.3: ip-proto-1

5. 23:28:50.440038 IP (tos 0x0, ttl 64, id 58106, offset 0, flags [+], proto ICMP (1), length 1500)
    192.168.0.3 > 192.168.0.6: ICMP echo reply, id 12522, seq 1, length 1480
6. 23:28:50.440038 IP (tos 0x0, ttl 64, id 58106, offset 1480, flags [none], proto ICMP (1), length 21)
    192.168.0.3 > 192.168.0.6: ip-proto-1
tcpdump的输出(我标了序号)
第1个IP数据报(echo request)设置了DF标志,IP数据报的总长度为1500字节(包含20字节的IP头,8字节的icmp头,1472字节的数据)
第2个IP数据报(echo reply)设置了DF标志,IP数据报的总长度为1500字节(包含20字节的IP头,8字节的icmp头,1472字节的数据)
第3个IP数据报(echo request)设置了MF表示(+),这个分片的长度为1500字节(包含20字节的IP头,8字节的icmp头,1472字节的数据)
第4个IP数据报(echo request)没有设置flags(最后一个分片),这个分片的长度为21字节(包含20字节的IP头,1字节的数据)
第5个IP数据报(echo reply)设置了MF表示(+),这个分片的长度为1500字节(包含20字节的IP头,8字节的icmp头,1472字节的数据)
第6个IP数据报(echo reply)没有设置flags(最后一个分片),这个分片的长度为21字节(包含20字节的IP头,1字节的数据)
另外:
4、6这两个分片,只