跟我一起学TCP/IP
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
CIDR(Classless Interdomain Routing)
本书的重点不是路由协议,作者对OSPF和BGP只进行了简单概述,我们也不进行深入研究了
互联网一开始设计的路由架构是基于网络的,将网络分为3类,分别是Class A、Class B、Class C
每个网络要求一个路由表项
回忆一下地址分类
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的顺序进行的
本书的重点不是路由协议,作者对OSPF和BGP只进行了简单概述,我们也不进行深入研究了
互联网一开始设计的路由架构是基于网络的,将网络分为3类,分别是Class A、Class B、Class C
每个网络要求一个路由表项
回忆一下地址分类
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 次。
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
第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格式
字段说明:
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,结构如下:
为了计算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"
另一个终端抓包
编程Note:
如果使用raw socket发送UDP数据报,需要应用程序构造完整的UDP数据报(包含UDP header)所以需要计算UDP校验和(参考UNP第28章)
UDP是一个面向数据报的传输层协议,应用程序的每个sendto调用都会产生一个udp数据报(封装在一个ip数据报中)
应用调用sendto发送udp数据报,需要考虑生成的ip数据报的大小限制:
1. path MTU:生成的ip数据报的总长度不能超过path MTU,否则会导致分片
2. IP Maximum Datagram Size:IPv4实现定义该值,表示可以处理的最大ip数据报大小,该值的可移植大小为576字节(包含ip header)
UDP格式
字段说明:
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,结构如下:
为了计算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)
如果使用raw socket发送UDP数据报,需要应用程序构造完整的UDP数据报(包含UDP header)所以需要计算UDP校验和(参考UNP第28章)
上次由 723937936@qq.com 在 2023-03-15 7:02,总共编辑 2 次。
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
IP分片
当IP模块发送IP数据报时,IP模块会获取外出接口的MTU或特定socket的PMTU,如果要发送的IP数据报的总长度超过MTU或PMTU,则IP模块可能会执行分片操作或返回EMSGSIZE错误(可以通过socket option来改变行为,下文有说明)
无论发送主机还是中间的路由器都可能会执行分片操作,但只有分片到达最终的目的主机才会执行重组操作
回忆IP数据报格式: 其中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
在另一个终端运行tcpdump
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这两个分片,只打印了IP头里的协议字段为1(表示icmp),因为这个分片里不包含icmp消息的头
3、4这两个分片的id为10526
5、6这两个分片的id为58106
以太网的MTU为1500字节,所以最大的IP数据报总长度为1500字节(20+8+1472)时不需要分片,当发送1501字节的IP数据报时会执行分片
PMTU发现
ICMP unreachable - need to frag 错误消息格式: MTU of next-hop network:指示通往下一跳网络那个接口的MTU值
实验网络结构: 我们从host1主机上发送一个udp数据报到host2主机
首先在host1主机上配置一条host-route,网关是192.168.0.1
其次修改host1主机接口的MTU为2000(这样可以发送超过1500字节的IP数据报)
在一个终端执行pmtu(代码:https://gitee.com/q723937936/tcpip/blob/master/pmtu.cpp)
在另一个终端执行tcpdump
观察tcpdump输出(我标了序号):
第1个IP数据报设置了DF标志(表示不允许中间路由器执行分片操作),该IP数据报的总长度为1501字节
第2个IP数据报显示路由器给主机host1发送了一个ICMP错误消息:unreachable - need to frag (mtu 1500),该错误消息报告了下一跳网络的接口MTU为1500
最后一行是触发该ICMP错误的那个IP数据报(存放在ICMP错误消息的数据部分,共28个字节)
pmtu程序使用setsockopt设置了IP_MTU_DISCOVER选项,值为IP_PMTUDISC_PROBE
IP_MTU_DISCOVER选项有4个可能值,分别为:
IP_PMTUDISC_WANT - 发送主机IP模块执行pmtu发现,并根据ptmu的值和上层发送的数据长度来决定是否进行分片,如未进行分片,则IP数据报的DF置位,如执行了分片,则所有分片都不设置DF。这是linux上默认行为
IP_PMTUDISC_DONT - 发送主机IP模块不执行pmtu发现,使用本机的接口MTU和上层应用发送数据的长度来决定是否进行分片,无论是否执行了分片,外出的IP数据报的DF都不设置,也就是给中间路由器执行分片的机会
IP_PMTUDISC_DO - 发送主机的IP模块会执行pmtu发现,如果上层发送的数据报大于该pmtu则返回EMSGSIZE错误,否则外出的IP数据报DF置位
IP_PMTUDISC_PROBE - 发送主机的IP模块会执行pmtu发现,但是发送IP数据报时忽略该pmtu,如果上层发送的数据报大于本机的接口MTU则返回EMSGSIZE错误,否则外出的IP数据报DF置位
对于已连接的UDP socket,如果发送主机执行pmtu发现,可以使用IP_MTU socket option来获取ptmu的值
IP_PMTUDISC_PROBE这个选项值是给应用层发送超过pmtu大小(且DF置位)的IP数据报的能力,pmtu程序使用这个值
当IP模块发送IP数据报时,IP模块会获取外出接口的MTU或特定socket的PMTU,如果要发送的IP数据报的总长度超过MTU或PMTU,则IP模块可能会执行分片操作或返回EMSGSIZE错误(可以通过socket option来改变行为,下文有说明)
无论发送主机还是中间的路由器都可能会执行分片操作,但只有分片到达最终的目的主机才会执行重组操作
回忆IP数据报格式: 其中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
代码: 全选
$ 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
第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这两个分片,只打印了IP头里的协议字段为1(表示icmp),因为这个分片里不包含icmp消息的头
3、4这两个分片的id为10526
5、6这两个分片的id为58106
以太网的MTU为1500字节,所以最大的IP数据报总长度为1500字节(20+8+1472)时不需要分片,当发送1501字节的IP数据报时会执行分片
PMTU发现
ICMP unreachable - need to frag 错误消息格式: MTU of next-hop network:指示通往下一跳网络那个接口的MTU值
实验网络结构: 我们从host1主机上发送一个udp数据报到host2主机
首先在host1主机上配置一条host-route,网关是192.168.0.1
代码: 全选
$ sudo route add -host 192.168.0.3 gw 192.168.0.1 dev enp0s3
代码: 全选
$ sudo ifconfig enp0s3 mtu 2000
$ ifconfig enp0s3
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 2000
inet 192.168.0.6 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 fe80::1a8b:c9c0:743f:9226 prefixlen 64 scopeid 0x20<link>
ether 08:00:27:c2:f3:77 txqueuelen 1000 (Ethernet)
RX packets 891271 bytes 272337548 (272.3 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 417697 bytes 108466468 (108.4 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
代码: 全选
$ ./pmtu 192.168.0.3 8888 1473
代码: 全选
$ sudo tcpdump -i enp0s3 -n -v udp or icmp
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
1. 00:06:15.560808 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 1501)
192.168.0.6.34991 > 192.168.0.3.8888: UDP, length 1473
2. 00:06:15.565712 IP (tos 0xc0, ttl 64, id 18149, offset 0, flags [none], proto ICMP (1), length 576)
192.168.0.1 > 192.168.0.6: ICMP 192.168.0.3 unreachable - need to frag (mtu 1500), length 556
IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 1501)
192.168.0.6.34991 > 192.168.0.3.8888: UDP, length 1473
第1个IP数据报设置了DF标志(表示不允许中间路由器执行分片操作),该IP数据报的总长度为1501字节
第2个IP数据报显示路由器给主机host1发送了一个ICMP错误消息:unreachable - need to frag (mtu 1500),该错误消息报告了下一跳网络的接口MTU为1500
最后一行是触发该ICMP错误的那个IP数据报(存放在ICMP错误消息的数据部分,共28个字节)
pmtu程序使用setsockopt设置了IP_MTU_DISCOVER选项,值为IP_PMTUDISC_PROBE
IP_MTU_DISCOVER选项有4个可能值,分别为:
IP_PMTUDISC_WANT - 发送主机IP模块执行pmtu发现,并根据ptmu的值和上层发送的数据长度来决定是否进行分片,如未进行分片,则IP数据报的DF置位,如执行了分片,则所有分片都不设置DF。这是linux上默认行为
IP_PMTUDISC_DONT - 发送主机IP模块不执行pmtu发现,使用本机的接口MTU和上层应用发送数据的长度来决定是否进行分片,无论是否执行了分片,外出的IP数据报的DF都不设置,也就是给中间路由器执行分片的机会
IP_PMTUDISC_DO - 发送主机的IP模块会执行pmtu发现,如果上层发送的数据报大于该pmtu则返回EMSGSIZE错误,否则外出的IP数据报DF置位
IP_PMTUDISC_PROBE - 发送主机的IP模块会执行pmtu发现,但是发送IP数据报时忽略该pmtu,如果上层发送的数据报大于本机的接口MTU则返回EMSGSIZE错误,否则外出的IP数据报DF置位
对于已连接的UDP socket,如果发送主机执行pmtu发现,可以使用IP_MTU socket option来获取ptmu的值
IP_PMTUDISC_PROBE这个选项值是给应用层发送超过pmtu大小(且DF置位)的IP数据报的能力,pmtu程序使用这个值
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
UDP与ARP的交互
实验:
从ubuntu18.04主机192.168.0.6,向安卓手机192.168.0.4发送8192字节的udp数据
发送前确保arp cache没有缓存安卓手机的mac地址
sock程序地址:https://github.com/unpbook/unpv13e
在一个终端运行sock
在另一个终端运行tcpdump
观察tcpdump输出,linux每秒发送一次arp请求,直到arp应答返回才发送6个IP分片
Maximum UDP Datagram Size
IP数据报的总长度字段是两个字节,也就是理论上最大的IP数据报长度为65535字节
下面的实验验证在Ubuntu18.04上loopback接口可以发送的UDP数据报的最大大小
在一个终端运行sock
在另一个终端运行tcpdump
从上面的实验,可以看出Ubuntu18.04上可以发送的最大UDP数据报为65507字节(生成的IP数据报大小为65507+20+8=65535)
从tcpdump的输出看IP数据报没有分片,查看loopback的接口MTU为65536,因此IP模块不会执行分片操作
下面的实验验证在Ubuntu18.04上以太网接口可以发送的UDP数据报的最大大小
在一个终端运行sock
在另一个终端运行tcpdump
从上面的输出,Ubuntu18.04是支持发送65535字节的IP数据报(tcpdump没有输出所有的分片)
另外,安卓手机在30秒后回送了一个ICMP ip reassembly time exceeded错误消息,这说明安卓手机没有收到所有分片(什么原因?),因此重组分片超时了
上面的例子重试了几次,大部分时候是可以收到ICMP 192.168.0.4 udp port 8888 unreachable,这说明安卓手机成功接收了完整的IP数据报,tcpdump输出如下
前面提到过主机的IP实现不要求接收超过576字节的IP数据报,由上面的实验可知linux上无此限制。
但是当应用使用UDP时,我们仍然要考虑MTU,应用发送的数据大小最好不超过1500-20-8=1472字节,以避免IP分片
另外应用可以通过SO_RCVBUF和SO_SNDBUF这两个socket选项来设置发送和接收buffer,这两个buffer也会影响应用可以发送和接收的UDP数据报大小,这两个buffer的默认值保存在如下文件
send buffer:/proc/sys/net/core/wmem_max
recv buffer:/proc/sys/net/core/rmem_max
在Ubuntu18.04上他们的大小如下:
ICMP Source Quench Error
该消息是一种简单的流量控制方法,现在已经废弃
实验:
从ubuntu18.04主机192.168.0.6,向安卓手机192.168.0.4发送8192字节的udp数据
发送前确保arp cache没有缓存安卓手机的mac地址
sock程序地址:https://github.com/unpbook/unpv13e
在一个终端运行sock
代码: 全选
$ arp -n
Address HWtype HWaddress Flags Mask Iface
192.168.0.3 ether 90:9c:4a:c0:be:d0 C enp0s3
192.168.0.1 ether 2c:61:04:ba:ff:fa C enp0s3
$ sock -u -i -n1 -w8192 192.168.0.4 8888
代码: 全选
$ sudo tcpdump -i enp0s3 -n 'host 192.168.0.4 and (icmp or udp or arp)'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
20:35:42.865074 ARP, Request who-has 192.168.0.4 tell 192.168.0.6, length 28
20:35:43.893040 ARP, Request who-has 192.168.0.4 tell 192.168.0.6, length 28
20:35:44.002977 ARP, Reply 192.168.0.4 is-at a4:45:19:6b:e4:d8, length 46
20:35:44.003021 IP 192.168.0.6.42718 > 192.168.0.4.8888: UDP, bad length 8192 > 1472
20:35:44.003057 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:35:44.003071 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:35:44.003086 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:35:44.003100 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:35:44.003115 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:35:44.026525 IP 192.168.0.4 > 192.168.0.6: ICMP 192.168.0.4 udp port 8888 unreachable, length 556
Maximum UDP Datagram Size
IP数据报的总长度字段是两个字节,也就是理论上最大的IP数据报长度为65535字节
下面的实验验证在Ubuntu18.04上loopback接口可以发送的UDP数据报的最大大小
在一个终端运行sock
代码: 全选
$ sock -u -i -n1 -w65507 127.0.0.1 8888
$ sock -u -i -n1 -w65508 127.0.0.1 8888
write returned -1, expected 65508: Message too long
代码: 全选
$ sudo tcpdump -n -i lo udp or icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
21:54:26.510097 IP 127.0.0.1.42965 > 127.0.0.1.8888: UDP, length 65507
21:54:26.510107 IP 127.0.0.1 > 127.0.0.1: ICMP 127.0.0.1 udp port 8888 unreachable, length 556
从tcpdump的输出看IP数据报没有分片,查看loopback的接口MTU为65536,因此IP模块不会执行分片操作
代码: 全选
$ ifconfig lo
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 210 bytes 84038 (84.0 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 210 bytes 84038 (84.0 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
在一个终端运行sock
代码: 全选
$ sock -u -i -n1 -w65507 192.168.0.4 8888
$ sock -u -i -n1 -w65508 192.168.0.4 8888
write returned -1, expected 65508: Message too long
代码: 全选
sudo tcpdump -i enp0s3 -n 'host 192.168.0.4 and (udp or icmp)'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
20:59:41.462183 IP 192.168.0.6.33612 > 192.168.0.4.8888: UDP, bad length 65507 > 1472
20:59:41.462212 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462217 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462221 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462225 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462229 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462233 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462635 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462684 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462685 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462686 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462687 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462785 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462867 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.462869 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463017 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463025 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463026 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463027 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463028 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463028 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463042 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463146 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463377 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463384 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463385 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463386 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463387 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463388 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
20:59:41.463389 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:00:12.037916 IP 192.168.0.4 > 192.168.0.6: ICMP ip reassembly time exceeded, length 556
另外,安卓手机在30秒后回送了一个ICMP ip reassembly time exceeded错误消息,这说明安卓手机没有收到所有分片(什么原因?),因此重组分片超时了
上面的例子重试了几次,大部分时候是可以收到ICMP 192.168.0.4 udp port 8888 unreachable,这说明安卓手机成功接收了完整的IP数据报,tcpdump输出如下
代码: 全选
sudo tcpdump -i enp0s3 -n 'host 192.168.0.4 and (udp or icmp)'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
21:24:48.212063 IP 192.168.0.6.47475 > 192.168.0.4.8888: UDP, bad length 65507 > 1472
21:24:48.212100 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212104 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212110 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212115 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212121 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212125 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.212790 IP 192.168.0.6 > 192.168.0.4: ip-proto-17
21:24:48.324355 IP 192.168.0.4 > 192.168.0.6: ICMP 192.168.0.4 udp port 8888 unreachable, length 556
但是当应用使用UDP时,我们仍然要考虑MTU,应用发送的数据大小最好不超过1500-20-8=1472字节,以避免IP分片
另外应用可以通过SO_RCVBUF和SO_SNDBUF这两个socket选项来设置发送和接收buffer,这两个buffer也会影响应用可以发送和接收的UDP数据报大小,这两个buffer的默认值保存在如下文件
send buffer:/proc/sys/net/core/wmem_max
recv buffer:/proc/sys/net/core/rmem_max
在Ubuntu18.04上他们的大小如下:
代码: 全选
$ cat /proc/sys/net/core/wmem_max
212992
$ cat /proc/sys/net/core/rmem_max
212992
该消息是一种简单的流量控制方法,现在已经废弃
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
UDP Server Design
获取客户端IP地址和端口号
UDP Server 可以通过如下两个函数获取客户端IP地址和端口号
获取目的IP地址
linux上不支持IP_RECVDSTADDR socket选项,替代的选项是IP_PKTINFO,使能该选项后可以通过recvmsg接收辅助数据,辅助数据内存放的是如下结构:
上述结构中的ipi_addr就是目的IP地址
UDP Input Queue
UDP输入队列,就是receive buffer,该buffer的大小可以通过SO_RCVBUF设置
服务端
客户端
linux上的SO_RCVBUF选项有最小值限制,命令指定的接收buffer大小是256,实际linux设置的是2304
当客户端连续发送9个UDP数据报时,服务端只收到了4个UDP数据报,说明服务端的接收队列溢出了,后面接收的5个UDP数据报被丢弃了
sock程序地址:https://gitee.com/q723937936/unpv13e
Restricting Local IP Address
默认情况下,多个进程不能同时bind相同IP地址和相同端口号的地址结构
相同IP地址指:IP地址完全相同或其中一个IP地址为wildcard
要解除该限制,可以使用SO_REUSEADDR或SO_REUSEPORT选项
Restricting Foreign IP Address
UDP server也可以调用connect函数,只有connect指定的远程客户端才能向该UDP server发送UDP数据报
connect(2)手册有如下说明:
使能SO_REUSEADDR或SO_REUSEPORT选项的UDP server,可以启动多个实例
SO_REUSEADDR:使用该选项,则数据报递送给哪个实例可能不确定(我测试是递送给后启动的实例)
SO_REUSEPORT:使用该选项,多个实例之间会负载均衡
获取客户端IP地址和端口号
UDP Server 可以通过如下两个函数获取客户端IP地址和端口号
代码: 全选
recvfrom
recvmsg
linux上不支持IP_RECVDSTADDR socket选项,替代的选项是IP_PKTINFO,使能该选项后可以通过recvmsg接收辅助数据,辅助数据内存放的是如下结构:
代码: 全选
struct in_pktinfo {
unsigned int ipi_ifindex; /* Interface index */
struct in_addr ipi_spec_dst; /* Local address */
struct in_addr ipi_addr; /* Header Destination address */
};
UDP Input Queue
UDP输入队列,就是receive buffer,该buffer的大小可以通过SO_RCVBUF设置
服务端
代码: 全选
$ sock -s -u -v -E -R256 -r256 -P30000 6666
SO_RCVBUF = 2304
from 192.168.0.3, to 192.168.0.6: 1
from 192.168.0.3, to 192.168.0.6: 2
from 192.168.0.3, to 192.168.0.6: 3
from 192.168.0.3, to 192.168.0.6: 4
代码: 全选
$ nc -u 192.168.0.6 6666
1
2
3
4
5
6
7
8
9
当客户端连续发送9个UDP数据报时,服务端只收到了4个UDP数据报,说明服务端的接收队列溢出了,后面接收的5个UDP数据报被丢弃了
sock程序地址:https://gitee.com/q723937936/unpv13e
Restricting Local IP Address
默认情况下,多个进程不能同时bind相同IP地址和相同端口号的地址结构
相同IP地址指:IP地址完全相同或其中一个IP地址为wildcard
要解除该限制,可以使用SO_REUSEADDR或SO_REUSEPORT选项
Restricting Foreign IP Address
UDP server也可以调用connect函数,只有connect指定的远程客户端才能向该UDP server发送UDP数据报
connect(2)手册有如下说明:
多个UDP Server实例If the socket sockfd is of type SOCK_DGRAM, then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received.
使能SO_REUSEADDR或SO_REUSEPORT选项的UDP server,可以启动多个实例
SO_REUSEADDR:使用该选项,则数据报递送给哪个实例可能不确定(我测试是递送给后启动的实例)
SO_REUSEPORT:使用该选项,多个实例之间会负载均衡
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
第十二章:广播和多播
有三种IP地址:
单播(unicast)
广播(broadcast)
多播(multicast)
对应的以太网地址也有三种:
单播(unicast)
广播(broadcast)
多播(multicast)
单播是一对一
广播是一对所有
多播最灵活,介于单播与广播之间,是一对多
广播和多播只适用于UDP协议
广播和多播的能力并不是由UDP模块提供的,而是由接口卡、链路层、网络层三个模块共同提供的
接口卡可以接收单播帧、广播帧、多播帧
默认情况下接口卡只能接收单播帧和广播帧,只有接口卡加入多播组才能接收多播帧
另外还可以将接口卡配置成混杂模式(promiscuous mode),该模式可以接收所有帧
以太网多播地址:最高字节的最低位为1;多播地址可以有很多,每个多播地址标识一个多播组(局域网内的一部分主机)
以太网广播地址:0xFFFFFFFFFFFF;该地址可以认为是一个特殊的多播地址,标识局域网内的所有主机(所有主机在一个多播组里)
广播的缺点是局域网内的所有主机都能收到广播帧,即使该主机对这个广播帧不感兴趣,比如:
以前上学时,老师用的一个网络教学软件控制教室里的所有电脑,假如教室里有50台电脑(都开机了),但是只有30个学生打开了网络教学软件(受控端),老师的每个操作都会发送一个UDP数据报,50台电脑都能收到这个UDP数据报,但是有20台电脑没有打开网络教学软件(受控端),这些UDP数据报要经过完整的协议栈,直到UDP模块才被丢弃(不产生ICMP-port unreachable错误,因为目的地址是广播地址),浪费了主机的处理能力。
多播的目的就是为了解决广播的缺点,多播采用自注册机制,只有打开网络教学软件的主机会将自己注册(加入)到(约定好的)同一个多播组,老师的操作对应的UDP数据报只会发送给这个组内的主机,其他主机不受影响。
网络层的广播
不像链路层的广播地址只有一个(0xFFFFFFFFFFFF),网络层的广播地址是针对(子)网络的,不同(子)网络有不同的广播地址
受限的广播(limited broadcast):也称为local broadcast,IP地址为255.255.255.255,目的地址为255.255.255.255的IP数据报会被广播到局域网内的所有主机,但是路由器绝对不会转发
面向网络的广播(net-directed broadcast):hostid全1的IP地址,路由器通常默认不会转发(为了防止DOS攻击)
面向子网的广播(subnet-directed broadcast):hostid全1的IP地址,路由器通常默认不会转发(为了防止DOS攻击)
上面后两种统称为directed broadcast
如果使用CIDR技术,实际上directed broadcast可以统一为面向域的广播(domain-directed broadcast)
广播还有个用途是服务发现(Service discovery):
比如局域网某个主机部署了一个UDP服务,局域网内的其他主机可以广播一个UDP数据报到指定端口,预期只有一个主机会响应一个应答,由此发现(获得)服务所在主机的IP地址(DHCP是一个例子)
另外ARP也利用了链路层广播来查询目标主机的硬件地址
一个例子
在linux主机上ping广播地址,先查看本机ip地址为192.168.0.6,arp cache显示还有另一台macos主机(192.168.0.3)和一个路由器(192.168.0.1)
从上面的输出看,只有192.168.0.3发送了应答,这是因为linux默认不会响应ICMP-echo request,可以通过如下命令修改默认配置:
再次ping广播地址:
可以看到linux主机192.168.0.6也发送了应答(广播包含子网内的所有主机,也包括自己)
编程说明
进程要想发送广播数据报,必须要使能SO_BROADCAST选项
开发一个udpecho程序和一个udpcli程序,测试UDP数据报被广播到所有主机
分别在192.168.0.3和192.168.0.6主机上运行udpecho命令
然后在192.168.0.6主机上运行udpcli命令,广播UDP数据报
代码参见:https://gitee.com/q723937936/tcpip/tree/master
UNP 第20章详细描述了广播
有三种IP地址:
单播(unicast)
广播(broadcast)
多播(multicast)
对应的以太网地址也有三种:
单播(unicast)
广播(broadcast)
多播(multicast)
单播是一对一
广播是一对所有
多播最灵活,介于单播与广播之间,是一对多
广播和多播只适用于UDP协议
广播和多播的能力并不是由UDP模块提供的,而是由接口卡、链路层、网络层三个模块共同提供的
接口卡可以接收单播帧、广播帧、多播帧
默认情况下接口卡只能接收单播帧和广播帧,只有接口卡加入多播组才能接收多播帧
另外还可以将接口卡配置成混杂模式(promiscuous mode),该模式可以接收所有帧
以太网多播地址:最高字节的最低位为1;多播地址可以有很多,每个多播地址标识一个多播组(局域网内的一部分主机)
以太网广播地址:0xFFFFFFFFFFFF;该地址可以认为是一个特殊的多播地址,标识局域网内的所有主机(所有主机在一个多播组里)
广播的缺点是局域网内的所有主机都能收到广播帧,即使该主机对这个广播帧不感兴趣,比如:
以前上学时,老师用的一个网络教学软件控制教室里的所有电脑,假如教室里有50台电脑(都开机了),但是只有30个学生打开了网络教学软件(受控端),老师的每个操作都会发送一个UDP数据报,50台电脑都能收到这个UDP数据报,但是有20台电脑没有打开网络教学软件(受控端),这些UDP数据报要经过完整的协议栈,直到UDP模块才被丢弃(不产生ICMP-port unreachable错误,因为目的地址是广播地址),浪费了主机的处理能力。
多播的目的就是为了解决广播的缺点,多播采用自注册机制,只有打开网络教学软件的主机会将自己注册(加入)到(约定好的)同一个多播组,老师的操作对应的UDP数据报只会发送给这个组内的主机,其他主机不受影响。
网络层的广播
不像链路层的广播地址只有一个(0xFFFFFFFFFFFF),网络层的广播地址是针对(子)网络的,不同(子)网络有不同的广播地址
受限的广播(limited broadcast):也称为local broadcast,IP地址为255.255.255.255,目的地址为255.255.255.255的IP数据报会被广播到局域网内的所有主机,但是路由器绝对不会转发
面向网络的广播(net-directed broadcast):hostid全1的IP地址,路由器通常默认不会转发(为了防止DOS攻击)
面向子网的广播(subnet-directed broadcast):hostid全1的IP地址,路由器通常默认不会转发(为了防止DOS攻击)
上面后两种统称为directed broadcast
如果使用CIDR技术,实际上directed broadcast可以统一为面向域的广播(domain-directed broadcast)
广播还有个用途是服务发现(Service discovery):
比如局域网某个主机部署了一个UDP服务,局域网内的其他主机可以广播一个UDP数据报到指定端口,预期只有一个主机会响应一个应答,由此发现(获得)服务所在主机的IP地址(DHCP是一个例子)
另外ARP也利用了链路层广播来查询目标主机的硬件地址
一个例子
在linux主机上ping广播地址,先查看本机ip地址为192.168.0.6,arp cache显示还有另一台macos主机(192.168.0.3)和一个路由器(192.168.0.1)
代码: 全选
$ ifconfig enp0s3 | grep inet
inet 192.168.0.6 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 fe80::1a8b:c9c0:743f:9226 prefixlen 64 scopeid 0x20<link>
$ arp -n
Address HWtype HWaddress Flags Mask Iface
192.168.0.3 ether 90:9c:4a:c0:be:d0 C enp0s3
192.168.0.1 ether 2c:61:04:ba:ff:fa C enp0s3
$ ping -b 192.168.0.255
WARNING: pinging broadcast address
PING 192.168.0.255 (192.168.0.255) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.449 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.705 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.560 ms
代码: 全选
$ cat /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
1
$ sudo bash -c 'echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts'
$ cat /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
0
代码: 全选
$ ping -b 192.168.0.255
WARNING: pinging broadcast address
PING 192.168.0.255 (192.168.0.255) 56(84) bytes of data.
64 bytes from 192.168.0.6: icmp_seq=1 ttl=64 time=0.059 ms
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=0.300 ms (DUP!)
64 bytes from 192.168.0.6: icmp_seq=2 ttl=64 time=0.026 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=0.276 ms (DUP!)
64 bytes from 192.168.0.6: icmp_seq=3 ttl=64 time=0.063 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=0.654 ms (DUP!)
编程说明
进程要想发送广播数据报,必须要使能SO_BROADCAST选项
开发一个udpecho程序和一个udpcli程序,测试UDP数据报被广播到所有主机
分别在192.168.0.3和192.168.0.6主机上运行udpecho命令
代码: 全选
$ ./udpecho 8888
代码: 全选
$ ./udpcli -b 192.168.0.255 8888
hello
from 192.168.0.6: hello
from 192.168.0.3: hello
how are you
from 192.168.0.6: how are you
from 192.168.0.3: how are you
UNP 第20章详细描述了广播
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
IP多播组地址
类D地址称为IP多播组地址,如下图
从上图可知IP多播组地址范围为:224.0.0.0-239.255.255.255,每个IP多播组地址标识一个多播组
整个32位称为IP多播组地址,低28位称为多播组ID,在不引起歧义的情况下,我们可以交换使用这两个术语
IP多播组地址到以太网多播组地址的转换:
前面说过,以太网多播组地址范围是:01:00:5E:00:00:00-01:00:5E:7F:FF:FF,也就是高25位是固定的,只有低23位可以用来映射
IP多播组地址的高4位也是固定的,要把IP多播组地址的低28位(也就是多播组ID)映射到以太网地址的低23位,必须忽略多播组ID的高5位,也就是IP多播组地址到以太网多播组地址的映射不是一一对应的,32个IP多播组地址映射到一个以太网多播组地址
从编程的角度看,我们其实并不关心IP多播组地址到以太网多播组地址是如何转换的,在接口加入多播组时是指定IP多播组地址,内核会将该IP多播组地址转换成相应的以太网多播组地址,并通知接口卡接收目的地址为该以太网多播组地址的帧
IP数据报的可路由性(routable)
目的地址为单播地址的IP数据报,路由器会转发
目的地址为255.255.255.255的IP数据报,路由器不会转发
目的地址为子网广播地址的IP数据报,路由器默认不会转发
目的地址为多播地址的IP数据报,分两种情况,如下:
IANA保留的一些well known多播组地址:
224.0.0.1 - all nodes
224.0.0.2 - all routers
224.0.0.4 - dvmrp
224.0.0.9 - ripv2
224.0.0.22 -igmp
224.0.0.251 - mDNS
查看接口加入的多播组
从输出看,enp0s3接口加入了224.0.0.251和224.0.0.1多播组
编程说明
加入多播组是通过IP_ADD_MEMBERSHIP选项来实现的,我修改udpecho服务,添加-m选项指定要加入的多播组
udpcli程序不需要做任何修改
分别在192.168.0.3和192.168.0.6两台主机上运行udpecho:
在192.168.0.6上运行udpcli:
再次查看接口加入的多播组:
上述输出表明,enp0s3接口已经加入224.0.0.88多播组
代码地址:https://gitee.com/q723937936/tcpip/tree/master
UNP第21章有多播的详细描述
类D地址称为IP多播组地址,如下图
从上图可知IP多播组地址范围为:224.0.0.0-239.255.255.255,每个IP多播组地址标识一个多播组
整个32位称为IP多播组地址,低28位称为多播组ID,在不引起歧义的情况下,我们可以交换使用这两个术语
IP多播组地址到以太网多播组地址的转换:
前面说过,以太网多播组地址范围是:01:00:5E:00:00:00-01:00:5E:7F:FF:FF,也就是高25位是固定的,只有低23位可以用来映射
IP多播组地址的高4位也是固定的,要把IP多播组地址的低28位(也就是多播组ID)映射到以太网地址的低23位,必须忽略多播组ID的高5位,也就是IP多播组地址到以太网多播组地址的映射不是一一对应的,32个IP多播组地址映射到一个以太网多播组地址
从编程的角度看,我们其实并不关心IP多播组地址到以太网多播组地址是如何转换的,在接口加入多播组时是指定IP多播组地址,内核会将该IP多播组地址转换成相应的以太网多播组地址,并通知接口卡接收目的地址为该以太网多播组地址的帧
IP数据报的可路由性(routable)
目的地址为单播地址的IP数据报,路由器会转发
目的地址为255.255.255.255的IP数据报,路由器不会转发
目的地址为子网广播地址的IP数据报,路由器默认不会转发
目的地址为多播地址的IP数据报,分两种情况,如下:
- 目的地址在224.0.0.0-224.0.0.255范围的IP数据报,路由器不会转发
- 目的地址在其他范围的IP数据报,路由器会转发
IANA保留的一些well known多播组地址:
224.0.0.1 - all nodes
224.0.0.2 - all routers
224.0.0.4 - dvmrp
224.0.0.9 - ripv2
224.0.0.22 -igmp
224.0.0.251 - mDNS
查看接口加入的多播组
代码: 全选
$ netstat -g -n
IPv6/IPv4 Group Memberships
Interface RefCnt Group
--------------- ------ ---------------------
lo 1 224.0.0.251
lo 1 224.0.0.1
enp0s3 1 224.0.0.251
enp0s3 1 224.0.0.1
lo 1 ff02::fb
lo 1 ff02::1
lo 1 ff01::1
enp0s3 1 ff02::fb
enp0s3 1 ff02::1:ff3f:9226
enp0s3 1 ff02::1
enp0s3 1 ff01::1
编程说明
加入多播组是通过IP_ADD_MEMBERSHIP选项来实现的,我修改udpecho服务,添加-m选项指定要加入的多播组
udpcli程序不需要做任何修改
分别在192.168.0.3和192.168.0.6两台主机上运行udpecho:
代码: 全选
$ ./udpecho -m 224.0.0.88 8888
代码: 全选
$ ./udpcli 224.0.0.88 8888
hi there
from 192.168.0.6: hi there
from 192.168.0.3: hi there
代码: 全选
$ netstat -g -n
IPv6/IPv4 Group Memberships
Interface RefCnt Group
--------------- ------ ---------------------
lo 1 224.0.0.251
lo 1 224.0.0.1
enp0s3 1 224.0.0.88
enp0s3 1 224.0.0.251
enp0s3 1 224.0.0.1
lo 1 ff02::fb
lo 1 ff02::1
lo 1 ff01::1
enp0s3 1 ff02::fb
enp0s3 1 ff02::1:ff3f:9226
enp0s3 1 ff02::1
enp0s3 1 ff01::1
代码地址:https://gitee.com/q723937936/tcpip/tree/master
UNP第21章有多播的详细描述
-
- 帖子: 23
- 注册时间: 2023-02-26 9:59
- 系统: ubuntu
Re: 跟我一起学TCP/IP
IGMP
我们前面学习过目的地址在224.0.0.0-224.0.0.255范围内的IP多播数据报,路由器不会转发,目的地址在其他范围的IP多播数据报会被路由器转发
路由器在转发IP多播数据报时,要么转发到与路由器相连的网络里的主机,要么转发到下一跳路由器,这就要求路由器的路由表里有相关的route entries
那么路由器如何知道向自己的路由表里添加哪些route entries呢?答案就是IGMP协议,IGMP协议是一个多播组发现协议
IGMP格式
上图所示为IGMPv1版本的协议格式
字段说明:
4-bit IGMP version: 1
4-bit IGMP type:1-query;2-reply
16-bit checksum:计算方法同UDP的checksum计算方法
32-bit group address:主机报告的组地址
路由器发现多播组的基本思路是:
1. 路由器向子网内多播一个IGMP数据报查询消息
2. 子网内的主机自动响应一个或多个IGMP应答
3. 路由器收到IGMP应答,在路由表里添加通往相应多播组地址的一个route
上述过程是由一个实现了DVMRP协议的multicast routing daemon实施的,实现DVMRP协议的路由器称为多播路由器
IGMP的query和reply消息类似ICMP的echo query和echo reply消息,query是由应用进程发起的,reply是内核自动产生的
ICMP echo query是ping程序发送的
IGMP query是multicast routing daemon程序发送的
IGMP详细操作过程
网络内的主机主动报告多播组:当一个接口加入多播组时,内核先后发送两个IGMP(type=2)多播数据报,目的地址是该多播组地址
多播路由器定时查询网络内的多播组:多播路由器定时发送IGMP(type=1)多播数据报,目的地址224.0.0.1(因为网络内所有nodes都默认加入这个组,所以所有node都能收到这条IGMP查询数据报)
网络内的主机收到IGMP查询消息后,查看内核维护的多播组表(netstat -gn),每行(224.0.0.1除外)产生一个IGMP reply,目的地址是该多播组地址
从上面的描述可知:多播路由器的接口必须能接收所有多播帧
TTL字段说明
默认情况下,进程发送的IP多播数据报的TTL字段设置为1,要想发送的IP多播数据报可以被多播路由器转发,发送进程必须显式设置TLL字段,因为IP单播数据报和IP多播数据报的TTL字段是分别维护的,所以设置TTL也使用不同的socket选项
IP单播数据报的TTL使用IP_TTL选项
IP多播数据报的TTL使用IP_MULTICAST_TTL选项
另外IP多播数据报和IP广播数据报一样,不会触发任何ICMP错误消息
观察主机报告IGMP消息
在一个终端执行udpecho
在另一个终端执行tcpdump
从tcpdump的输出,看到4条igmp v3记录,前两条是加入224.0.0.88多播组时报告的,后两条是离开224.0.0.88多播组时报告的
在IGMPv1版本协议操作里,离开多播组是不报告的IGMP消息的
发送igmp查询消息
开发一个igmpquery程序,发送igmp查询消息
示例1:
在一个终端执行igmpquery
在一个终端执行tcpdump
从上面的输出看到,当发送IGMPv1版本的查询消息时,内核也发送v1版本的应答
示例2:
在一个终端执行udpecho加入多播组224.0.0.88,然后执行igmpquery
在另一个终端执行tcpdump
前两条的输出是udpecho加入多播组224.0.0.88时报告的,紧接着是igmp query消息,随后有两台主机报告了两个多播组
我们前面学习过目的地址在224.0.0.0-224.0.0.255范围内的IP多播数据报,路由器不会转发,目的地址在其他范围的IP多播数据报会被路由器转发
路由器在转发IP多播数据报时,要么转发到与路由器相连的网络里的主机,要么转发到下一跳路由器,这就要求路由器的路由表里有相关的route entries
那么路由器如何知道向自己的路由表里添加哪些route entries呢?答案就是IGMP协议,IGMP协议是一个多播组发现协议
IGMP格式
上图所示为IGMPv1版本的协议格式
字段说明:
4-bit IGMP version: 1
4-bit IGMP type:1-query;2-reply
16-bit checksum:计算方法同UDP的checksum计算方法
32-bit group address:主机报告的组地址
路由器发现多播组的基本思路是:
1. 路由器向子网内多播一个IGMP数据报查询消息
2. 子网内的主机自动响应一个或多个IGMP应答
3. 路由器收到IGMP应答,在路由表里添加通往相应多播组地址的一个route
上述过程是由一个实现了DVMRP协议的multicast routing daemon实施的,实现DVMRP协议的路由器称为多播路由器
IGMP的query和reply消息类似ICMP的echo query和echo reply消息,query是由应用进程发起的,reply是内核自动产生的
ICMP echo query是ping程序发送的
IGMP query是multicast routing daemon程序发送的
IGMP详细操作过程
网络内的主机主动报告多播组:当一个接口加入多播组时,内核先后发送两个IGMP(type=2)多播数据报,目的地址是该多播组地址
多播路由器定时查询网络内的多播组:多播路由器定时发送IGMP(type=1)多播数据报,目的地址224.0.0.1(因为网络内所有nodes都默认加入这个组,所以所有node都能收到这条IGMP查询数据报)
网络内的主机收到IGMP查询消息后,查看内核维护的多播组表(netstat -gn),每行(224.0.0.1除外)产生一个IGMP reply,目的地址是该多播组地址
从上面的描述可知:多播路由器的接口必须能接收所有多播帧
TTL字段说明
默认情况下,进程发送的IP多播数据报的TTL字段设置为1,要想发送的IP多播数据报可以被多播路由器转发,发送进程必须显式设置TLL字段,因为IP单播数据报和IP多播数据报的TTL字段是分别维护的,所以设置TTL也使用不同的socket选项
IP单播数据报的TTL使用IP_TTL选项
IP多播数据报的TTL使用IP_MULTICAST_TTL选项
另外IP多播数据报和IP广播数据报一样,不会触发任何ICMP错误消息
观察主机报告IGMP消息
在一个终端执行udpecho
代码: 全选
$ ./udpecho -m 224.0.0.88 8888
^C
$
代码: 全选
$ sudo tcpdump -n -v igmp
tcpdump: listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
10:07:07.004416 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [DF], proto IGMP (2), length 40, options (RA))
192.168.0.6 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 224.0.0.88 to_ex, 0 source(s)]
10:07:07.638984 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [DF], proto IGMP (2), length 40, options (RA))
192.168.0.6 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 224.0.0.88 to_ex, 0 source(s)]
10:07:11.099510 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [DF], proto IGMP (2), length 40, options (RA))
192.168.0.6 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 224.0.0.88 to_in, 0 source(s)]
10:07:11.699645 IP (tos 0xc0, ttl 1, id 0, offset 0, flags [DF], proto IGMP (2), length 40, options (RA))
192.168.0.6 > 224.0.0.22: igmp v3 report, 1 group record(s) [gaddr 224.0.0.88 to_in, 0 source(s)]
在IGMPv1版本协议操作里,离开多播组是不报告的IGMP消息的
发送igmp查询消息
开发一个igmpquery程序,发送igmp查询消息
示例1:
在一个终端执行igmpquery
代码: 全选
$ sudo ./igmpquery
代码: 全选
$ sudo tcpdump -n igmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
11:05:13.087660 IP 192.168.0.6 > 224.0.0.1: igmp query v1
11:05:13.764182 IP 192.168.0.3 > 224.0.0.251: igmp v1 report 224.0.0.251
示例2:
在一个终端执行udpecho加入多播组224.0.0.88,然后执行igmpquery
代码: 全选
$ ./udpecho -m 224.0.0.88 8888 &
$ sudo ./igmpquery
代码: 全选
$ sudo tcpdump -n igmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
11:09:16.507421 IP 192.168.0.6 > 224.0.0.88: igmp v1 report 224.0.0.88
11:09:21.333221 IP 192.168.0.6 > 224.0.0.88: igmp v1 report 224.0.0.88
11:09:25.510708 IP 192.168.0.6 > 224.0.0.1: igmp query v1
11:09:26.072933 IP 192.168.0.6 > 224.0.0.251: igmp v1 report 224.0.0.251
11:09:29.288262 IP 192.168.0.6 > 224.0.0.88: igmp v1 report 224.0.0.88
11:09:34.027950 IP 192.168.0.3 > 224.0.0.251: igmp v1 report 224.0.0.251