原文链接:https://mp.weixin.qq.com/s/ckh1n9tgKwhNytyRa0l47A
本文要点
引言
多播数据结构
以太网多播地址
ether_multi结构
in_multi结构
ip_moptions结构
多播插口选项与TTL值
ip_setmoptions函数
加入一个IP多播组
in_addmulti函数
slioctl和loioctl函数
leioctl函数
ether_addmulti函数
离开一个IP多播组
in_delmulti函数
ether_delmulti函数
ip_getmoptions函数
多播输入处理:ipintr函数
多播输出处理:ip_output函数
小结
引言
在概说《TCP/IP详解 卷2》第8章 IP:网际协议中,D类地址IP(224.0.0.0到时239.255.255.255)用于识别接口组;由于这个原因,D类地址被称为多播组(multicast group)。具有D类目的地址的数据报被提交给互联网内所有加入相应多播组的各个接口。
Internet上利用多播的实验性应用程序包括:音频和视频会议应用程序、资源发现工具和共享白板等。
多播组的成员由于接口加入或者离开组而动态地变化,这是根据系统上运行的进程的请求决定的。因为多播组成员与接口有关,所以多接口主机可能针对每个接口,都有不同的多个播组成员关系表。我们称一个特定接口上的组成员看作为一对{接口,多播组}。
单个网络上的组成员利用IGMP协议(原著第13章)在系统之间通信。多播路由器用多播选路协议(原著第14章),如基于距离向量多播路由选择协议传播成员信息(DVMRP)。标准IP路由器可能支持多播选路,或者用专用路由器处理多播选路。
如以太网、令牌等网络直接支持硬件多播。在Net/3中,如果某个接口支持多播,那么在接口的ifnet结构中的if_flags标志的IFF_MULTICAST比特就被置位。本文将以以太网为例说明硬件支持的IP多播。
如果本地网络不支持硬件级多播,那么在某个特定接口上就得不到IP多播业务。对于多播主机的要求分为三个级别:
0级:主机不能发送或接收IP多播
1级:主机能发送但不能接收IP多播
在向某个IP多播组发送数据之前,并不要求主机加入该组。多播数据报的发送方式与单播一样,除了多播数据报目的地址是IP多播组之外。网络驱动器必须能够识别出这个地址,把它在本地网络上多播数据报。
2级:主机能接收和发送IP多播
为了接收IP多播,主机必须能够加入或离开多播组,而且必须支持IGMP,能够在至少一个接口上交换组成员信息。多接口主机必须支持在它的接口的一个子网上的多播。
和UDP、TCP端口号一样,互联网号授权机构IANA维护着一个注册的IP多播组表。图1列出了一些知名多播组。
图1 一些注册的IP多播组
前256个组(从224.0.0.0到224.255.255.255)是为实现IP单播和多播选路机制的协议预留。不管发给其中任意一个组的数据报内IP首部的TTL值如何变化,多播路由器都不会把它转发出本地网络。
对于符合2级的系统,要求其在系统初始化时,在所有的多播接口上加入224.0.0.1组,并且保持为该组成员,直到系统关闭。
单播或者多播路由器可能会加入224.0.0.2进行互相通信。ICMP路由器请求报文和路由器报告报文可能被分别发往224.0.0.1和224.0.0.2,而不是受限的广播地址(255.255.255.255)。224.0.0.4组支持在实现的DVMRP的多播路由器之间通信。
除了前256个组外,其它组(224.0.1.0~239.255.255)或者被分配给多播应用程序协议,或者仍然没有被分配。
多播数据结构
1. 以太网多播地址
IP多播的高效实现要求IP充分利用硬件级多播,因为如果没有硬件级多播,就不得不在网络上广播每个多播IP数据报,而每台主机也不得不检查每个数据报,把那些不是给它的丢掉。硬件在数据报到达IP层之前,就把没用的过滤掉了。
为了保证硬件级过滤器能正常工作,网络接口必须把IP多播组目的地址转换成网络硬件识别的链路级多播地址。在点到点网络上,如SLIP和环回接口,必须明确给出地址映射,因为只能有一个目的地址。在其它网络上,如以太网,也需要有一个明确地完成映射地址的函数。以太网的标准映射适用于任何使用802.3寻址方式的网络。
对于以太网单播和多播地址区别:高位字节的最低位,1表示它是多播地址,0表示单播地址。单播以太网地址由接口制造商分配,多播地址由网络协议动态分配
因为以太网支持多种协议,所以要采取措施分配多播地址,避免冲突。IEEE管理以太网多播地址分配。IEEE把一块以太网多播地址分给IANA以支持IP多播,块的地址都是以01:00:5e开头;以00:00:5e开头的以太网单播也分配给IANA。
图2显示了一个D类IP地址构造出一个以太网多播地址。
图2 IP和以太网地址之间的映射
图2显示的映射是一个多到一的映射。在构造以太网地址时,没有使用D类地址的高位9比特。32bit的IP多播组映射到一个以太网多播地址。图3显示了Net/3中实现的这个映射宏。
图3 ETHER_IP_MULTICAST宏
a. IP到以太网多播映射
61~71 ETHER_MAP_IPMULTICAST实现图2所示的映射。ipaddr指向D类多播地址,enaddr构造匹配的以太网地址,用6字节的数组表示。该以太网多播地址的前3个字节是0x01,0x00,0x5e,后面跟着一个0比特,然后是D类IP地址的低23位。
2. ether_multi结构
Net/3为每个以太网接口维护一个该硬件接收的以太网多播地址范围表。这个表定义了该设备要实现的多播过滤。因为大多数以太网设备能选择地接收的地址是有限的,所以IP层必须要准备丢弃那些通过了硬件过滤的数据报。地址范围被保存在ether_multi结构中,如图4所示。
图4 ether_multi结构
a. 以太网多播地址
147~153 enm_addrlo和enm_addrhi指定需要被接收的以太网多播地址的范围。当enm_addrlo和enm_addrhi相同时,就指定一个以太网地址。ether_multi的完整列表在每个以太网接口的arpcom结构中。
enm_ac指回相关接口的arpcom结构,enm_refcount跟踪对ether_multi结构的使用。enm_next把单个接口的ether_multi结构做成链表。图5显示出,有三个ether_multi结构的链表附在le_softc[0]上,这是我们以太网接口示例的ifnet结构。
图5 有三个ether_multi结构的LANCE接口
在图5中,我们看到:
接口已经加入了三个组。很有可能是224.0.0.1(所有组)、224.0.0.2(所有路由器)和224.0.1.2(SGI-dogfight)。因为以太网到IP地址的映射是一到多的,所以只看到以太网多播地址的结构,无法确定确切的IP多播地址。
有了enm_ac后向指针,就很容易找到链表的开始,释放某个ether_multi结构,无需再实现双向链表。
ether_multi只适用于以太网设备,其它多播设备可能有其它实现。
图6中的ETHER_LOOKUP_MULTI宏,搜索某个ether_multi结构,找到地址范围。
图6 ETHER_LOOKUP_MULTI宏
b. 以太网多播查找
166~177 addrlo和addrhi指定搜索的范围,ac指向包含了要搜索链表的arpcom结构。for循环完成线性搜索,在表的最后结束,或者当enm_addrlo和enm_addrhi都分别与和所提供的addlo和addrhi匹配时结束。当循环终止时,enm为空或者指向某个匹配的ether_multi结构。
3. in_multi结构
ether_multi数据结构并不专用于IP;它们必须支持所有内核支持的任意协议族的多播活动。在网络层,IP维护着一个与接口相关的IP多播组表
为了实现方便,把这个IP多播表附在与该接口有关的in_ifaddr结构中;in_ifaddr结构中包含了该接口的单播地址,多播表与单播地址它们除了都与同一个接口相关以外,就没有其它关系。
图7中的in_multi结构描述了每个IP多播对{接口,组}。
图7 in_multi结构
a. IP多播地址
111~118 inm_addr是一个D类多播地址,如224.0.0.1所有主机组。inm_ifp指回相关接口的ifnet结构,而inm_ia指回接口的in_ifaddr结构。
只有当系统中的某个进程通知内核,它要在某个特定的{接口,组}对上接收多播数据报时,才存在一个in_multi结构。由于可能会有多个进程要求接收发往同一个对上的数据报,所以inm_refcount跟踪对该{接口,组}对的引用次数。当没有进程对某个特定的对感兴趣时,inm_refcount就变成0,in_multi结构就被释放掉。这个动作可能会引起相关的ether_multi结构也被释放,如果此时ether_multi的引用计数也变成了0。
inm_timer是第13章描述的IGMP协议实现的一部分,最后,inm_next指向表中的下一个in_multi结构。
图8用接口示例le_softc[0]显示了接口,即它的单播地址和它的IP多播组表之间的关系。
图8 le接口的一个IP多播组
为了清楚起见,图8已经省略了对应的ether_multi结构(图29)。如果系统有两个以太网网卡,第二个可能由le_softc[1]管理,还可能有它自己的附在arpcom结构的多播组表。IN_LOOKUP_MULTI宏(图9)搜索IP多播表寻找某个特定多播组。
图9 IN_LOOKUP_MULTI宏
b. IP多播查找
131~146 IN_LOOKUP_MULTI在与接口ifp相关的多播组表中查找多播组addr。IFP_TO_IA搜索Internet地址链表in_ifaddr,寻找与接口ifp相关的in_ifaddr结构。如果IFP_TO_IA找到一个接口,则for循环搜索它的IP多播表。循环结束后,inm为空或者指向匹配的in_multi结构。
4. ip_moptions结构
运输层通过ip_moptions结构包含的多播选项控制多播输出处理。例如,UDP调用ip_output是:
error=ip_output(m, inp->inp_options,
&inp->route,
inp->inp_socket->so_options
&(SO_DONTROUTE|SO_BROADCAST),
inp->inp_moptions);
inp指向某个Internet协议控制块(PCB),并且UDP为每个由进程创建的socket关联一个PCB。在PCB内,inp_moptons是指向某个ip_moptions结构的指针。对于每个输出的数据报,都可以会给ip_output传入一个不同的ip_moptions结构。图10是ip_moptions结构的定义。
图10 ip_moptions结构
100~106 ip_output通过imo_multicast_ifp指向的接口对输出的多播数据报进行选路。如果imo_multicast_ifp为空,就选择目的站多播组的默认接口。
imo_multicast_ttl为外出的多播数据报指定初始的TTL。默认为1,把多播数据报保留在本地网络内。
如果imo_multicast_loop是0,就是不回送数据报,也不把数据报提交给正在发送的接口,即使该接口是多播组的成员。如果imo_multicast_loop是1,并且如果正在发送的接口是多播组成员,就把多播数据报回送给该接口。
最后,整数imo_num_memberships和数组imo_membership维护与该结构相关的{接口,组}对。所有对该表的改变都转告给IP,由IP在所连到的本地网络上宣布成员的变化。imo_membership数组的每个入口都是指向一个in_multi结构的指针,该in_multi结构附在适当接口的in_ifaddr结构上。
多播插口选项与TTL值
图11显示了几个IP级的插口选项,提供对ip_moptions结构的进程级访问。
图11 多播插口选项
在概说《TCP/IP详解 卷2》第8章 IP:网际协议图25中看到了ip_ctloutput函数的整体结构。图12显示了改变和检索多播选项有关的语句。
图12 ip_ctloutput函数:多播选项
486~491 所有的多播选项都由ip_setmoptions和ip_getmoptions函数处理。
539~549 ip_getmoptions和ip_setmoptions,该结构与发布ioctl命令的那个插口关联。
多播TTL值难以理解,因为它们有两个作用。TTL值的基本作用,如IP分组一样,是限制分组在互联网内的生存期,避免它有网络中无限循环。第二个作用是,把分组限制在管理边界所指定的互联网的某个区域内。管理区域是由一些主机相关的词语指定的,如“这个结点”,“这个公司”,“这个州”等,并与分组开始的地方有关。与多播分组有关的区域叫做它的辖域。
RFC1122的标准实现把生存期和辖域这两个概念合并在IP首部的一个TTL值里。当TTL的值变成0时,除了丢弃该分组外,多播路由器还给每个接口关联了一个TTL阈值,限制在接口上的多播传输。一个要在该接口上传输的分组必须具有大于或等于该接口阈值的TTL。由于这个原因,多播分组可能在它的TTL变为0之前就被丢弃了。阈值是管理员在配置多播路由器时分配的,这些值确定了多播分组的辖域。管理员使用的阈值策略以及数据报的源站与多播接口之间的距离定义多播数据报的初始TTL值。
图13显示了多种应用程序推荐的TTL值和推荐的阈值。
图13 IP多播数据报的TTL值
第一栏是IP首部中的ip_ttl初始值,第二栏是应用程序专用阈值。第三栏是与该TTL值相关的推荐的辖域。
例如,一个要与本地结点外的网络通信的接口,阈值要被配置成32。所有开始时TTL为32或者小于32的数据报到达该接口时,TTL都小于32,所以它们在被转发到外部网络之前被丢弃了,即使它们的TTL值大于0。
ip_setmoptions函数
ip_setmoptions函数块包括一个用来处理各选项的switch语句,如图14所示,下面将分几个部分来讨论该函数。
图14 ip_setmoptions函数
650~664 第一个参数,optname,指明正在改变哪个多播参数。第二个参数,imop,是一个指向某个ip_motions结构的指针。如果*imop不空,ip_setmoptions修改它所指向的结构。否则,ip_setmoptions分配一个新的ip_moptions结构,并把它的地址保存在*imop里。如果没有内存了,则返回ENOBUFS。后面的所有错误都通知error,error在函数的最后返回。第三个参数,m,指向存放要改变选项数据的mbuf。
a. 构造默认值
665~679 当分配一个新的ip_moptions结构时,ip_setmoptions把默认的多播接口指针初始化为空,把默认TTL初始化为1,使多播数据报能回送,并清除组成员表。有了这些默认值后,ip_output查询路由表选择一个输出的接口,多播被限制在本地网络中,并且如果输出的接口是目的多播组的成员,则系统将接收它自己的多播发送。
b. 进程选项
680~860 ip_setmoptions体由一个switch语句组成,其中对每种选项都有一个case语句。default情况把error设成EOPNOTSUPP。
c. 如果默认值是OK,丢弃结构
861~872 switch语句之后,ip_setmoptions检查ip_moptions结构。如果所有多播选项与它们对应的默认值匹配,就不再需要该结构,将其释放;最后返回0或者错误码。
1. 选择一个明确的多播接口:IP_MULTICAST_IF
当optname是IP_MULTICAST_IF时,传给ip_setmoptions的mbuf中就包含了多播接口的单播地址,该地址指定了在这个插口上发送的多播所使用的特定接口。图15是这个选项的处理程序。
图15 ip_setmoptions函数:选择多播输出接口
a. 验证
681~698 如果没有提供mbuf,或者mbuf中的数据不是一个in_addr结构的大小,则ip_setmoptions将返回一个EINVAL差错;否则把数据复制到addr。如果接口地址是INADDR_ANY,则丢弃所有前面选定的接口。对后面用这个ip_moptions结构的多播,将根据它们的目的多播组进行选路,而不再通过一个明确命名的接口(图35)。
b. 选择默认接口
699~710 如果addr不为INADDR_ANY,就由INADDR_TO_FIND_IFP找到匹配接口的位置。如果找不到匹配或者接口不支持多播,就将返回EADDRNOTAVAIL。否则,ip_moptions结构中输出请求的多播接口设置为ifp。
2. 选择明确的多播TTL:IP_MULTICAST_TTL
当optname是IP_MULTICAST_TTL时,缓存中有一个字节指定输出多播的IP TTL。这个TTL由ip_output在每个发往相关插口的多播数据报中插入。图16显示了该选项的处理程序。
图16 ip_setmoptions:选择明确的多播TTL
711~720 如果缓存中有一个字节的数据,就把它复制到imo_multicast_ttl。否则返回错误码EINVAL。
3. 选择多播环回:IP_MULTICAST_LOOP
通常,多播应用程序有两种形式:
一个系统内一个发送方和多个远程接收方的应用程序。这种配置中,只有一个本地进程向多播组发送数据报,所以无需回送输出的多播。
一个系统内的多个发送方和接收方。必须回送数据报,确保每个进程接收到系统其它发送方的传送。
IP_MULTICAST_LOOP选项(图17)为ip_moptions结构选择回送策略。
图17 ip_setmoptions函数:选择多播环回
a. 验证和选择环回策略
721~732 如果m为空,或者没有1字节数据,或者该字节不是0或者1,就返回错误码EINVAL。否则,把该字节复制到imo_multicast_loop。0指明不要把数据报回送,1允许环回机制。
图18显示了多播数据报的最大辖域值之间的关系:imo_multicast_ttl和imo_multicast_loop。
图18 环回和TTL对多播辖域的影响
图18显示了根据发送的环回策略,指定的TTL值接收多播分组的接口的设置。如果硬件接收自己的发送,则不管理采用什么环回策略,都接收分组。数据报可能通过选路穿过该网络,并到达与系统相连的其它接口。如果发送系统本身是一个多播路由器,输出的分组可能被转发到其它接口,但是只有一个接口接受它们进行输入处理。
加入一个IP多播组
除了内核自动加入的IP所有主机组外,其它组成员是由进程明确发出请求产生的。加入或离开多播组选项比其它选项使用地更多。必须修改接口的in_multi表以及链路层多播结构,例如以太网中讨论的ether_multi。
当optname是IP_ADDMEMBERSHIP时,mbuf中的数据是一个如图19所示的ip_mreq结构。
图19 ip_mreq结构
148~151 imr_multiaddr指定多播组,imr_interface用相关的单播IP地址指定接口。ip_mreq结构指定{接口,组}对表示成员的变化。
图20显示了加入和离开以太网接口例子相关的多播组时,所调用的函数。
图20 加入和离开一个多播组
我们从ip_setmoptions(图21)的IP_ADD_MEMBERSHIP情况开始,在这里修改ip_moptions结构。然后我们跟踪请求通过IP层、以太网驱动程序,一直到物理设备。
图21 ip_setmoptions函数:加入一个多播组
a. 验证
733~746 ip_setmoptions从验证该请求开始。如果没有传给mbuf,或者缓存的大小不对,或者结构的地址(imr_multiaddr)不是一个多播组地址,则ip_setmoptions将返回ENIVAL。mreq指向有效的ip_req地址。
b. 找到接口
747~774 如果接口的单播地址(imr_interface)是INADDR_ANY,则ip_setmoptions必须找到指定组的默认接口。该多播组构造一个route结构,作为目的地址,并传给rtalloc,由rtalloc为多播组找到一个路由。如果没有找到可用路由,则请求失败,产生EADDRNOTAVAIL。如果找到路由,则在ifp中保存指向路由外出接口的指针,此时不再需要路由,将其释放。
如果imr_interface不是INADDR_ANY,则请求一个明确的接口。INADDR_TO_IFP宏用请求的单播地址搜索接口。如果没有找到接口或者它不支持多播,则请求失败,产生错误EADDRNOTAVAIL。
c. 已经是成员了?
775~792 对请求做最后检查是检查imo_membership数组,看看所选接口是否已经是请求组的成员。如果for循环找到一个匹配,或者成员数组已满,则将返回EADDRINUSE或ETOOMANYREFS,并终止对这个选项的处理。
d. 加入多播组
793~803 此时,请求似乎是合理的了。in_addmulti安排IP开始接收该组的多播数据报。in_addmulti返回的指针指向一个新的或者已存在的in_multi结构,该结构位于接口的多播组表中。这个结构保存在成员数组中,并把数组的大小加1。
1. in_addmulti函数
in_addmulti(图22)和in_delmulti维护接口已加入多播组的表。加入请求或者在接口表中增加一个新的in_multi结构,或者增加对某个已存在结构的引用次数。
图22 in_addmulti函数:前半部分
a. 已经是一个成员了
469~487 ip_setmoptions已经证实ap指向一个D类多播地址,ifp指向一个能够多播的接口。IN_LOOKUP_MULTI(图9)确定接口是否已经是该组的一个成员。如果是,则in_addmulti更新引用计数后返回。
如果接口还不是该组成员,则执行图23中的程序。
图23 in_addmulti函数:后半部分
b. 更新in_multi表
487~509 如果接口还是不是成员,则in_addmulti分配并初始化一个新的in_multi结构,把该结构插到接口的in_ifaddr结构中的ia_multiaddr表的前端。
c. 更新接口,通告变化
510~530 如果接口驱动程序已经定义了一个ip_ioctl函数,则in_addmulti构造一个包含了该组地址的ifreq结构(概说《TCP/IP详解 卷2》第4章 接口:以太网图18),并把SIOCADDMULTI请求传给接口。如果接口拒绝该请求,则把in_multi结构从链表中断开并释放掉。最后,in_addmulti调用igmp_joingroup,把成员变化信息传播给其它主机。
in_addrmulti返回一个指针,该指针指向in_multi结构,或者如果出错则为空。
2. slioctl和loioctl函数:多播处理
SLIP和环回接口的多播组处理很简单:除了检查差错外,不做其它事情。图24显示了SLIP处理。
图24 slioctl函数:多播处理
673~687 不管请求为空还是不适用于AF_INET协议族,都回返EAFNOSUPPORT。
图25显示了环回处理。
图25 lioctl函数:多播处理
152~166 环回接口的处理和图24中的SLIP的程序一样。不管请求为空还是不适用于AF_INET协议族,都返回EAFNOSUPPORT。
3. leioctl函数:多播处理
图26是leioctl函数处理多播组相关的case语句。
图26 leioctl函数:多播处理
657~671 leioctl把增加和删除请求直接传给ether_addmulti或者ether_delmulti函数。如果请求改变了该物理硬件接收的IP多播地址集,则两个函数都返回ENETRESET。如果发生这种情况,则leioctl调用lereset,用新的多播接收表重新初始化该硬件。
4. ether_addmulti函数
所有以太网驱动程序都调用ether_addmulti函数处理SIOCADDMULTI请求。这个函数把IP D类地址映射到合适的以太网多播地址(图2)上,并更新ether_multi表。图27是ether_multi的前半部分。
图27 ether_multi函数:前半部分
a. 初始化地址范围
366~399 首先,ether_addmulti初始化addrlo和addrhi中的多播地址范围。如果所请求的地址来自AF_UNSPEC族,ether_addmulti假定该地址是一个明确的以太网多播地址,并把它复制到addrlo和addrhi中。如果地址属于AF_INET族,并且是INADDR_ANY(0.0.0.0),ether_addmulti把addrlo初始化成ether_ipmulticast_min,把addrhi初始化成ether_ipmulticast_max。这两个以太网地址常量定义为:
u_char ether_ipmulticast_min[6] =
{0x01, 0x00, 0x5e, 0x00, 0x00, 0x00};
u_char ether_ipmulticast_max[6] =
{0x01, 0x00, 0x5e, 0x7f, 0xff, 0xff};
IP多播路由器必须监听所有IP多播。把组指定为INADDR_ANY,被认为是请求加入所有IP多播组。在这种情况下,所选择的以太网地址范围跨越了分配给IANA的整个IP多播地址块。
ETHER_MAP_IP_MULTICAST把其它特定的IP多播组映射到合适的以太网多播地址。当发生EAFNOSUPPORT错误时,将拒绝对其它地址族的请求。
ether_addmulti的第二部分,显示如图28所示,证实地址范围,并且如果该地址是新的,就把它加入表中。
图28 ether_addmulti函数:后半部分
b. 已经在接收
400~418 ether_addmulti检查高地址和低地址是多播比特位,保证它们是真正的以太网多播地址。ETHER_LOOKUP_MULTI确定硬件是否已经对指定的地址开始监听。如果是,则增加匹配的ether_multi结构中的引用计数,并用ether_addmulti返回0.
c. 更新ether_multi表
419~441 如果这是一个新的地址范围,则分配并初始化一个新的ether_multi结构,把它链到接口arpcom结构(图5)中的ac_multiaddrs表上。如果ether_addmulti返回ENETRESET,则调用它的设备驱动程序就知道多播表被改变了,必须更新硬件接收过滤器。
图29显示了在LANCE以太网接口加入所有主机后,ip_moptions、in_multi和ether_multi结构之间的关系。
图29 多播数据结构的整体图
离开一个IP多播组
通常情况下,离开一个多播组的步骤是加入一个多播组的步骤的反序。更新ip_moptions结构中的成员表、IP接口的in_multi表和设备的ether_multi表。首先,我们回到ip_setmoptions中的IP_DROP_MEMBERSHIP情况语句,如图30所示。
图30 ip_setmoptions函数:离开一个多播组
a. 验证
804~830 存储器缓存中必然包含一个ip_mreq结构,其中的imr_multiaddr必须是一个多播组,而且必须有一个接口与单播地址imr_interface相关。如果这些条件都不满足,则将返回EINVAL和EADDRNOTAVAIL错误信息,继续到swithc语句的最后进行处理。
b. 删除成员引用
831~856 for循环用请求的{接口,组}对在组成员表中寻找一个in_multi结构。如果没找到,则将返回EADDRNOTAVAIL错误信息。如果找到了,则in_delmulti更新in_multi表,并且第二个for循环把成员数组中不用的in_multi结构删去,把后面的in_multi结构向前移动。数组的大小被相应更新。
1. in_delmulti函数
因为可能会有多个进程接收多播数据报,所以调用in_delmulti(图31)的结果是,当对in_multi结构没有引用时,只离开指定的多播组。
图31 in_delmulti函数
534~567 in_delmulti一开始就减少了in_multi结构的引用计数,如果该计数非零,则返回。如果为零,则表明在指定的{接口,组}对上,没有其它进程等待多播数据报。调用igmp_leavegroup,但该函数不做任何事情。
for循环遍历in_multi结构的链表,找到匹配的结构。把匹配的in_multi结构从链表上断开,in_delmulti向接口发布SIOCDELMULTI请求,以便更新所有设备专用的数据结构。对以太网来说,这意味着更新ether_multi表。最后释放in_multi结构。
2. ether_delmulti函数
当IP释放与某个以太网设备相关的in_multi结构时,该设备也可能释放匹配ether_multi结构。我们说“可能”是因为IP忽略其它监听IP多播的软件。当ether_multi结构的引用计数变成0时,就释放该结构。图32是ether_delmulti函数。
图32 ether_delmulti函数
445~479 ether_delmulti函数用ether_addmulti函数采用的同一方法初始化addrlo和addrhi数组。
a. 寻找ether_multi结构
480~494 ETHER_LOOKUP_MULTI寻找匹配的ether_multi结构。如果没有找到,则返回ENXIO。如果找到匹配的结构,则把引用计数减1。如果此时引用计数非零,则返回。
b. 删除ether_multi结构
495~511 for循环搜索ether_multi表,寻找匹配的地址范围,并从链表中断开匹配的结构,将它释放。最后更新链表长度,返回ENETRESET,使设备驱动程序可以更新它的硬件接收过滤器
ip_getmoptions函数
ip_getmoptions获取当前的选项设置,如图33所示。
图33 ip_getmoptions函数
876~914 ip_getmoptions的三个参数:optname,要取得的选项;imo,ip_moptions结构;mp,一个指向mbuf的指针。m_get分配一个mbuf存放该选项数据。这三个选项的指针(分别是addr、ttl和loop)初始化为指向mbuf的数据域,而mbuf的长度被设成选项数据的长度。
对IP_MULTICAST_IF,返回IFP_TO_IA发现的单播地址,或者如果没有明确的多播接口,则返回 INADDR_ANY。
对IP_MULTICAST_TTL,返回imo_multicast_ttl,或者没有选择明确的TTL,则返回1。
对IP_MULTICAST_LOOP,返回imo_multicast_loop,或者如果没有选择明确的多播环回策略,则返回1.
最后,如果不识别该选项,则返回EOPNOTSUPP。
多播输入处理:ipintr函数
在概说《TCP/IP详解 卷2》第4章 接口:以太网中,我们看到ether_input检测到达的以太网多播分组,在把一个IP分组放到IP输入队列之前(ipintrq),把mbuf首部的M_MCAST标志们置位。ipintr函数按顺序处理每个分组。我们在ipintr中省略的多播处理程序如图34所示。
图34 ipintr函数:多播输入处理
214~245 如果目的地址不是一个IP多播组,则跳过整个这部分代码。如果地址是一个多播组,并且系统被配置成IP多播路由器(ip_mrouter),就把ip_id转换成网络字节序列,并把分组传给ip_mforward。如果出现错误或者分组是通过一个多播隧道到达的,则ip_mforward返回一个非零值。分组被丢弃,且ips_cantforward加1。
如果ip_mforward返回0,则把ip_id转换回主机字节序列,由ipintr继续处理分组。
如果ip指向一个IGMP分组,则接受该分组,并在ours处(概说《TCP/IP详解 卷2》第10章 IP的分片和重装图8)继续执行执行。不管到达接口的每个目的组或者组成员是什么,多播路由器必须接收所有IGMP分组。IGMP分组中有组成员变化的信息。
246~257 根据系统是否被配置成多播路由器来确定是否执行图35中的其余程序。IN_LOOKUP_MULTI搜索接口加入的多播组表。如果没有找到匹配,则丢弃该分组。当硬件过滤器接受不需要的分组时,或者当与接口相关的多播组与分组中的目的多播地址映射到同一个以太网地址时,才出现这种情况。
如果接收该分组,就继续执行ipintr的ours标号处的语句。
多播输出处理:ip_output函数
在概说《TCP/IP详解 卷2》第8章 IP:网际协议讨论ip_output时,推迟了对ip_output的mp参数和多播处理程序的讨论。在ip_output中,如果mp指向一个ip_moptions结构,它就覆盖多播输出处理的默认值。ip_output中省略的程序在图35和图36中显示。ip指向输出分组,m指向该分组的mbuf,ifp指向路由表为目的多播组的接口。
图35 ip_output函数:默认和源地址
图36 ip_output函数:环回、转发和发送
a. 建立默认值
129~155 只有分组是到一个多播组时,才执行图35中的程序。此时,ip_output把mbuf中的M_MCAST置位,并把dst重设成最终目的地址,因为ip_output可能曾把它设成下一跳路由器。
如果传递了一个ip_moptions结构,则相应地改变ip_ttl和ifp。否则,把ip_ttl设置默认值1,避免多播分组到达某个远程网络。查询路由表或者ip_moptions结构所得到的接口必须支持多播。如果不支持,则ip_output丢弃该分组,并返回ENETNUREACH。
b. 选择源地址
156~167 如果没有指定源地址,则由for循环找到与输出接口相关的单播地址,并填入IP首部的ip_src。
与单播分组不同,如果系统被设置成一个多播路由器,则必须在一个以上的接口上发送输出的多播分组。即使系统不是一个多播路由器,输出的接口也可能是目的多播组的一个成员,也会需要接收该分组。最后,我们需要考虑一下多播环回策略和环回接口本身 ,有三个问题:
是否要在输出的接口上接收该分组?
是否向其它接口转发该分组?
是否在出去的接口发送该分组?
图36显示了ip_output中解决这三个问题的程序。
c. 是否环回?
168~176 如果IN_LOOKUP_MULTI确定输出的接口是目的多播组的成员,而且imo_multicast_loop非零,则分组被 ip_mloopback放到输出接口上排列,等待输入。在这种情况下,不考虑转发原始分组,因为在输入过程中如果需要,分组的复制会被转发的。
d. 是否转发?
178~197 如果分组不是环回的,但系统被配置成一个多播路由器,并且分组符合转发的条件,则ip_mforward向其它多播接口分发该分组的备份。如果ip_mforward没有返回0,则ip_output丢弃该分组,不发送它。
为了避免ip_mforward和ip_output之间的无限循环,ip_mforward在调用ip_output之前,总是把IP_FORWARDING打开。在本系统上产生的数据报是符合转发条件的,因为运输层不打开IF_FORWARDING。
e. 是否发送?
198~209 TTL是0的分组可能被环回,但从不转发它们(ip_mforward丢弃它们),也从不被发送。如果TTL是0或者如果输出接口是环回接口,则ip_output丢弃该分组,因为TTL超时,或者分组已经被ip_mloopback环回了。
f. 发送分组
210~211 到这个时候,分组应该已经从物理上在输出接口上被发送了。sendit(概说《TCP/IP详解 卷2》第8章 IP:网际协议图20)处的程序在把分组传给接口的if_output函数调用之前可能已经把它分片了。
ip_mloopback依靠looutput完成它的工作。ip_mloopback传递的looutput不是指向环回接口的指针,而是指向输出多播接口的指针。图37显示了ip_mloopback函数。
图38 ip_mloopback函数
929~956 仅仅复制分组是不够的;必须看起来分组已经被输出接口接收了,所以ip_mloopback把ip_len和ip_off转换成网络字节序列,并计算分组的检验和。looutput把分组放到IP输入队列。
小结
本文讨论了一个主机如何处理IP多播数据报。我们看到,在IP的D类地址和以太网多播地址的格式以及它们之间的映射关系。
同时,讨论了in_multi和ether_multi结构,每个IP多播接口都维护一个它自己的组成员表,而每个以太网接口都维护一个以太网多播地址。
在输入处理中,只在到达接口的是目的多播组成员时,该IP多播才被接受下来。尽管如果系统被配置成多播路由器,它们也可能被继续转发到其它接口。
被配置成多播路由器的系统必须接受所有接口上的所有多播分组。只要为INADDR_ANY地址发布SIOCADDMULTI命令,就可以迅速做到这一点。
ip_moptions结构是多播输出处理的基础。它控制对输出接口的选择、TTL值以及环回策略。它也控制对in_multi结构的引用计数,从而决定接口加入或者离开某个IP多播组的时机。
更多最新文章尽在公众号:大白爱爬山,欢迎关注!