原文链接:https://mp.weixin.qq.com/s/Lt5625Q7wPRdmTqu9ie8wg
本文要点
-
引言
-
IP地址简介
-
接口与地址
-
sockaddr_in结构
-
in_ifaddr结构
-
-
地址指派
-
ifioctl函数
-
in_control函数
-
前提条件
-
SIOCSIFADDR命令
-
in_ifinit函数
-
SIOCSIFNETMASK命令
-
SIOCSIFDSTADDR命令
-
获取接口信息
-
每个接口多个IP地址
-
附加IP地址:SIOCAIFADDR
-
删除IP地址:SIOCDIFADDR
-
-
接口ioctl处理
-
leioctl函数
-
slioctl函数
-
loioctl函数
-
引言
本文讨论Net/3如何管理IP地址信息。从in_ifaddr和sockaddr_in结构开始,它们是基于通用ifaddr和sockaddr结构;然后介绍IP地址的指派和几个查询接口数据结构与维护IP地址的实用函数。
IP地址简介
在IP模型中,地址是指派给一个系统中的网络接口而不是系统本身。在系统有多个接口的情况下,系统有多重初始地址,并有多个IP地址。
IP地址定义分为五类。A、B和C类地址支持单播通信;D类地址支持IP多播,在一个多播通信中,一个单独的源方发送一个数据报给多个目标方。E类地址是试验用的,接收的E类地址分组被不参与试验的主机丢弃。
注意IP多播和硬件多播的区别。硬件多播的特点是数据链路硬件将帧传输给多个硬件接口。有些网络硬件,如以太网,支持数据链路多播,其它硬件可能不支持。
IP多播是一个在IP系统内实现的软件特性,将分组传输给多个可能在Internet中任何位置的IP地址。
在IP网络的子网划分中,我们会看到每个网络接口有一个相关的子网掩码,它是判断一个分组是否到达后的目的地或还需要转发的关键。通常,当提及一个IP地址的网络部分时,我们包括任何可能定义的子网。当需要区分网络与子网时,需要明确指出来。
环回网络,127.0.0.0,是一个特殊的A类网络。这种格式的地址是不会出现在一个主机的外部的。发送到这个网络的分组被环回并被这个主机接收。
通常以点分十进制数表示法来显示一个IP地址。图1列出了每类IP地址的范围。
图1 不同IP地址类的范围
对于我们的有些例子,子网字段不按一个字节对齐,一个网络/子网/主机在一个B类网络中分为16/11/5。点分十进制数表示法很难表示这样的地址,因此我们还是用方块图来说明IP地址的内容。我们用三个部分显示每个地址:网络、子网和主机。每个部分的阴影指示它的内容。图2用我们网络示例中的主机sun的以太网接口来说明块表示法和点分十进制表示法。
图2 可选的IP地址表示法
在一个Internet上的系统通常能划分为两类:主机和路由器。一个主机通常有一个网络接口,并且是一个IP分组的源或目标方。一个路由器有多个网络接口,当分组向它的目标方移动时将分组从一个网络转发到下一个网络。为执行这个功能,路由器用各种专用路由协议来交换关于网络拓扑信息。
如果一个有多个网络接口的系统不在网络接口间路由分组,仍然叫一个主机。一个系统可能既是一个主机又是一个路由器。这种情况经常发生在当一个路由器提供运输层服务(如用于配置Telnet访问,或用于网络管理的SNMP时)。当区分一个主机和路由器间的意义并不重要时,我们使用术语系统。
不谨慎地配置一个路由器会干扰网络的正常运转,因此一个系统必须默认为一个主机来操作,并且必须显式地由一个管理员来配置作为一个路由器操作。在Net/3中,如果全局整数ipforwarding不为0,则一个系统作为一个路由器;如果ipforwarding为0(默认),则系统作为一个主机。
接口与地址
本文讨论的所有接口与地址结构配置如图3所示。
图3中显示了有一个接口例子:以太网接口、SLIP接口和环回接口。它们都有一个链路层地址作为地址列表中的第一个结点。显示的以太网接口有两个 IP地址,SLIP接口有一个IP地址,并且环回接口有一个IP地址和一个OSI地址。
注意所有的IP地址被链接到in_ifaddr列表中,并且所有的链路层地址能从ifnet_addr数组访问。
图3中没有画出每个ifaddr结构中的指针ifa_ifp。这些指针指回接口ifnet结构。
图3 接口和地址数据结构
1. sockaddr_in结构
在概说《TCP/IP详解 卷2》第3章 接口层中介绍了通用sockaddr和ifaddr结构。现在我们介绍IP专用的结构:sockaddr_in和in_ifaddr。
图4 结构sockaddr_in
68~70 由于历史原因,Net/3以网络字节序列将Internet地址存放在一个in_addr结构中,这个结构只有一个成员s_addr,它包含这个地址。虽然这是多余和混乱的,但在Net/3中一直保持这种组织方式。
106~112 sin_len总是16(结构sockaddr_in的大小),并且sin_family为AF_INET。sin_port是一个网络字节序列的16bit值,用来分用运输层报文。sin_addr标识一个32bit的Internet地址。
图4显示了sockaddr_in的成员sin_port、sin_addr和sin_zero覆盖sockaddr的成员sa_data。在Internet域中,sin_zero未用,但必须由全0字节组成,将它追加到sockaddr_in结构后面,以得到与一个sockaddr结构一样的长度。
通常,当一个Internet地址存储在一个u_long中时,它以主机字节序列存储,以便地址的压缩和位操作,但在图5的in_addr结构中,s_addr是一个值得注意的例外。
图5 一个sockaddr_in结构的组织
2. in_ifaddr结构
图6显示了为Internet协议定义的接口地址结构。对于每个指派给一个接口的IP地址,分配一个in_ifaddr结构,并添加到接口地址列表中和IP地址全局列表中(图3)。
图6 结构in_ifaddr
41~45 in_ifaddr开始是一个通用接口地址结构ia_ifa,跟着是IP专用成员。两个宏ia_ifp和ia_flags简化了对存储在通用结构ifaddr结构中的接口指针和接口地址标志的访问。if_next用于所有Internet地址的链接起来。这个列表独立于每个接口关联的链路层ifaddr结构列表,并且通过全局列表in_ifaddr来访问。
46~54 其余成员(除了ia_multiaddrs)显示如图7中,它显示了在我们的B类网络例子中sun的三个接口的相应值。u_long类型变量地址以按主机字节序列存储;in_addr和sockaddr_dl类型变量地址按网络字节序列存储。
55~56 结构in_ifaddr的最后一个成员指向一个in_multi结构的列表,其中每项包含与此接口有关的一个IP多播地址。
图7 sun上的以太网、PPP和环回in_ifaddr结构
地址指派
接口在系统初始化期间被识别时初始化,但是在Internet协议能通过这个接口进行通信前,必须指派一个IP地址。一旦Net/3内核运行,程序ifconfig就配置这些接口,ifconfig通过在某个插口上的ioctl系统调用来发送配置命令。
图8显示了本文将讨论的ioctl命令。命令相关地址必须是此命令指定插口所支持的地址族类(即不能通过一个UDP插口配置一个OSI地址)。对于IP地址,ioctl命令在一个UDP插口上发送。
图8 接口ioctl命令
获得地址信息的命令从SIOCG开始,设置地址信息的命令从SIOCS开始。SIOC代表socket ioctl,G代表get,S代表set。
图8中命令修改一个接口相关地址信息的,由于地址是特定协议使用的,因此命令处理是与协议相关的。图9显示了与这些命令关联的ioctl相关函数。
图9 ioctl相关函数
1. ifioctl函数
如图9所示,ifioctl将协议无关的ioctl命令传递给此插口关联协议的pr_usrreq函数。将控制交给udp_usrreq,并且又立即传给in_control,在in_control中进行大部分的处理。如果在一个TCP插口上发送同样的命令,控制最后也会到达in_control。图10再次显示了ifioctl函数中的default代码,第一次出现在概说《TCP/IP详解 卷2》第4章 接图17中。
图10 函数ifioctl:特定协议的命令
447~454 函数将图8中的ioctl命令的所有相关数据传给与请求插口相关联的协议的用户请求函数。对于UDP插口,调用udp_usrreq。udp_usrreq函数的细节后续章节讨论,现在我们仅查看udp_usrreq中的PRU_CONTROL代码:
if (req==PRU_CONTROL){
return (in_control(so, (int) m, (caddr_t)
addr, (struct ifnet *) control));
}
2. in_control函数
图9显示了通过soo_ioctl中的default或者ifioctl中的与协议相关的情况,控制能到达in_control。在这两种情况中,udp_usrreq调用in_control,并返回in_control的返回值。in_control函数如图11所示。
图11 函数in_control
132~145 so指向这个ioctl命令(第二个参数cmd标识)指定的插口。第三个参数data指向命令所用或返回的数据,图8第二列。最后一个参数ifp为空(来自soo_ioctl的无接口ioctl)或指向结构ifreq或in_aliasreq中命名的接口(来自ifioctl的接口ioctl)。in_control初始化ifr和ifra来访问作为一个ifreq或in_aliasreq结构的data。
146~152 如果ifp指向一个ifnet结构,这个for循环找到与此接口关联的Internet地址列表中的第一个地址。如果发现一个地址,ia指向它的in_ifaddr结构;否则ia为空。
若ifp为空,cmd就不会匹配第一个switch中的任何情况,或第二个switch中任何非默认情况。在第二个switch中的default情况中,当ifp为空时,返回EOPNOTSUPP。
153~330 in_control中的第一个switch确保在处理命令之前每个命令的前提条件都满足。在后面会单独说明各种情况。
如果在第二个switch中的default情况被执行,ifp指向一个接口结构;并且如果接口有一个if_ioctl函数,则in_control将ioctl命令传给这个接口进行设备的特定处理。
331~332 我们会看到这个switch语句中的很多情况都直接返回了。如果控制落到switch语句外,则in_control返回0.第二个switch中有几个case执行了跳出语句。
我们按照下面的顺序查看这个接口ioctl命令:
-
指派一个地址、网络掩码或目标地址
-
指派一个广播地址
-
取回一个地址、网络掩码、目标地址或广播地址
-
给一个接口指派多播地址
-
删除一个地址
对于每组命令,在第一个switch中进行前提条件处理,然后在第二个switch中处理命令。
3. 前提条件
图12显示了对SIOCSIFADDR、SIOCSIFNETMASK和SIOCSIFDSTADDR的前提条件检验。
图12 函数in_control:地址指派
a. 仅用于超级用户
116~172 如果这个插口不是由一个超级用户进程创建的,这些命令被禁止,并且in_control返回EPERM。如果此请求没有关联的接口,内核调用panic。由于如果在ifioctl不能找到一个接口,它就返回,因此panic从来不会被调用。
b. 分配结构
173~191 如果ia为空,命令请求创建一个新的地址。in_control分配一个in_ifaddr结构,用bzero初始化,并将它链接到系统的in_ifaddr列表中和此接口的if_addrlist列表中。
c. 初始化结构
192~206 初始化in_ifaddr结构。首先,在此结构中ifaddr部分的通用指针被初始化为指向结构in_ifaddr中的结构sockaddr_in。必须时,此函数还初始化结构ia_sockmask和ia_boradaddr。图13说明了初始化后的in_ifaddr。
图13 被in_control初始化后的in_ifaddr结构
202~206 最后,in_control建立从in_ifaddr到此接口的ifnet结构的回指指针。
4. 地址指派:SIOCSIFADDR
前提条件处理代码保证ia指向一个要被SIOCSIFADDR命令修改的in_ifaddr结构。图14显示了in_control第二switch中处理这个命令的执行代码。
图14 函数in_control:地址指派
159~261in_ifinit完成所有工作。IP地址包含在ifreq结构(ifr_addr)里传递给in_ifinit。
5. in_ifinit函数
in_ifinit的主要步骤是:
-
将地址复制到此结构并将此变化通知硬件
-
忽略原地址配置的任何路由
-
为这个地址建立一个子网掩码
-
建立一个默认路由到连接的网络
-
将此接口加入到所有主机组
从图15开始分三部分讨论这段代码。
图15 函数in_ifinit:地址指派和路由初始化
353~357 in_ifinit的四个参数为:ifp,指向接口结构的指针;ia,指向要改变的in_ifaddr结构的指针;sin,指向请求的IP地址指针;scrub,指示这个接口如果存在路由应该被忽略。i保存主机字节序列的IP地址。
a. 指派地址并通知硬件
358~374 in_control将原来的地址保存在oldaddr中,万一发生差错时,必须恢复它。如果接口定义if_ioctl函数,则in_control调用它。相同接口的三个函数leioctl、slioctl和loioctl在本文后面讨论。如果发生差错,恢复原来的地址,并且in_control返回。
b. 以太网配置
375~378 对于以太网设备,arp_rtrequest作为链路层路由函数被选择,并且设置RTF_CLONING标志。arp_rtrequest在原著21章讨论,RTF_CLONING在原著19章讨论。
c. 忽略原来的路由
379~384 如果调用者要求已存在的路由被清除,原地址被重新连接到ifa_addr,同时in_ifscrub找到并废除任何基于老地址的路由。if_ifscrub返回后,新地址被恢复。
in_ifinit显示在图16中的部分是构造网络和子网掩码。
图16 函数in_ifinit:网络和子网掩码
d. 构造网络掩码和默认子网掩码
385~400 根据地址是一个A类、B类或C类地址,在ia_netmask中构造一个尝试性网络掩码。如果这个地址没有子网掩码,ia_subnetmask和ia_sockmask被初始化为ia_netmask中的尝试性掩码。
如果指定了一个子网,in_ifinit将这个尝试性网络掩码和这个和这个已存在的子网掩码进行逻辑与运算得到一个新的网络掩码。这个操作可以会清除该尝试性网络掩码的一些1bit。在这种情况下,网络掩码比所考虑的地址类型期望的更少一些1bit。
一个接口默认配置不划分子网,即网络和子网的掩码相同。但是通过一个显示请求(SIOCSINNETMASK或者SICOCAIIADDR)用来允许子网划分。
e. 构造网络和子网数量
401~403 网络和子网数量通过网络和子网掩码从新地址中获得。函数in_socktrim通过查找掩码中包含1bit的最后一个字节来设置in_sockmask的长度。
图17显示了in_ifinit的最后一分部,它为接口添加了一个路由,并加入所有主机多播组。
图17 函数in_ifinit:路由和多播组
f. 为主机或网络建立路由
404~422 下一步是为新地址所指定的网络创建一个路由。in_control从接口将路由度量复制到结构in_ifaddr中。如果接口支持广播,则构造广播地址;如果支持环回地址,把目的地址强制分配给环回接口的地址;如果一个点对点接口没有指向给链路另一端的IP地址,则in_control在试图为这个无效地址建立路由前返回。
in_ifinit将flags初始化为RTF_UP,并与环回和点对点接口的RTF_HOST进行逻辑或。rtinit为此接口给这个网络(不设置RTF_HOST)或主机(设置RTF_HOST)安装一个路由。若rtinit安装成功,则设置ia_flags中的标志IFA_ROUTE,指示已给此地址安装一个路由。
g. 加入所有主机组
423~433 最后,一个有多播功能的接口当它被初始化时必须加入所有主机多播组。in_addmulti完成此工作,原著12章讨论。
6. SIOCSIFNETMASK命令:网络掩码指派
图18显示了网络掩码命令的处理。
图18 函数in_control:网络掩码指派
262~265 in_control从ifreq结构中获取网络掩码,并将它以网络字节序列保存在ia_sockmask中,以主机字节序列保存在ia_subnetmask中。
7. SIOCSIFDSTADDR命令:目的地址指派
对于点对点接口,在链路另一端的系统地址用SIOCSIFDSTADDR命令指定。图12显示了图19中的代码的前提条件处理。
图19 函数in_control:目的地址指派
236~245 只有点对点网络才有目的地址,因此对于其它网络,in_control返回EINVAL。将当前目的地址保存在oldaddr后,代码设置新地址,并且通过函数if_ioctl通知硬件。如果发生差错,则恢复原地址。
246~253 如果地址原来有一个关联的路由,首先调用rtinit删除这个路由,并再次调用rtinit为新地址安装一个新路由。
8. 获取接口信息
图20显示了命令SIOCSIFBRDADDR的前提条件处理,它同将接口信息返回给调用进程的ioctl命令一样。
图20 函数in_control:前提条件处理
207~217 广播地址只能通过一个超级用户进程创建的插口来设置。命令SIOCSIFBRDADDR和4个SIOCGxxx命令仅当已经为此接口定义了一个地址时才起作用。在这种情况下,ia不会为空。如果ia为空,返回EADDRNOTAVAIL。
这5个命令的处理显示在图21中。
图21 函数in_control:处理
220~235 将单播地址、广播地址、目的地址或者网络掩码复制到ifreq结构。只有网络接口支持广播,广播地址才有效;并且只有点对点接口,目的地址才有效。
254~258 仅当接口支持广播,才从结构ifreq复制广播地址。
9. 每个接口多个IP地址
SIOCGxxx和SIOCSxxx命令只操作与一个接口关联的第一个IP地址,在in_control开头的循环找到的第一个地址(图23)。为支持每个接口的多个IP地址,必须用SIOCAIFADDR命令指派和配置其它地址。实际上,SIOCAIFADDR能完成所有的SIOCGxxx和SIOCSxxx命令能完成的操作。程序ifconfig使用SIOCAIFADDR来配置一个接口的所有地址信息。
Net/3的ifconfig程序的-alias选项将存放在一个in_aliasreq中的其它地址的相关信息传递给内核,如图22所示。
图22 结构in_aliasreq
59~65 注意,不像结构ifreq,在结构in_aliasreq中没有定义联合。在一个单独的ioctl调用中可能为SIOCAIFADDR指定地址、广播地址和掩码。
SIOCAIFADDR增加一个新地址或者修改一个已存在地址的相关信息。SIOCDIFADDR删除匹配的IP地址的in_ifaddr结构。图23显示了命令SIOCAIFADDR和SIOCDIFADDR的前提条件处理,它假设在in_control开头的循环已经将ia设置为指向与ifra_name指定的接口关联的第一个IP地址。
图23 函数in_control:添加和删除地址
154~165 因为SIOCDADDR代码只查看*ifra的前两个成员,图23所示的代码用于处理SIOCAIFADDR(当ifra指向一个in_aliasreq结构时)和SIOCDIFADDR(当ifra指向一个ifreq结构时)。结构in_aliasreq和ifreq的前两个成员是一样的。
对于这两个命令,in_control开头的循环启动for循环不断查找与ifra->ifra_addr指定的IP地址相同的in_ifaddr结构。对于删除命令,如果没找到则返回EADDRNOTAVAIL。
在这个处理删除命令的循环和检验后,控制落到图12中讨论的代码之处。对于添加命令,图12中的若找不到一个与in_aliasreq结构中地址匹配的地址,就分配一个新in_ifaddr结构。
10. 附加IP地址:SIOCAIFADDR
此时ia指向一个新的in_ifaddr结构或者包含与请求地址匹配的IP地址的旧in_ifaddr结构。SIOCAIFADDR的处理如图24所示。
图24 in_control函数:SIOCAIFADDR处理
266~277 因为SIOCAIFADDR能创建一个新地址或者修改一个已存在地址的相关信息,标志maskIsNew和hostIsNew跟踪变化的情况。这样,在这个函数结束时,如果必要,能更新路由。
代码在默认方式下取一个新的IP地址指派给接口(hostIsNew初始为1)。如果新地址长度为0,in_control将当前地址复制到请求中,并将hostIsNew修改为0。如果长度不是0,并且新地址与老地址匹配,则这个请求不包含一个新地址,并且hostIsNew被设置为0。
278~284 如果在这个请求中指定一个网络掩码,则任何使用当前地址的路由被忽略,并且in_control安装此新掩码。
285~290 如果接口是一个点对点接口,并且此请求包含一个新目的地址,则in_scrub忽略任何使用此地址的路由,新目的地址被安装,并且maskIsNew被设置为1,以强制调用in_ifinit来重新配置接口。
291~297 如果配置了一个新地址或指派了一个新掩码,则in_ifinit作适当的修改来支持新地配置。注意,in_ifinit的最后一个参数为0。这表示已注意到这一点,不必刷新所有路由。最后,如果接口支持广播,则从in_aliasreq结构复制广播地址。
11. 删除IP地址:SIOCDIFADDR
命令SIOCAIFADDR从一个接口删除IP地址,如图25所示。记住,ia指向要被删除的in_ifaddr结构。
图25 in_control函数:删除地址
298~323 前提条件处理代码将ia指向要删除的地址。in_ifscrub删除任何与此地址关联的路由。第一个if删除接口地址列表的结构。第二个if删除Internet地址列表(in_ifaddr)的结构。
324~325 IFAFREE只在引用计数降到0时才释放此结构。
接口ioctl处理
当一个地址被分配给接口时的专用ioctl处理,对于我们的每个例子接口,这个处理分别在函数leioctl、slioctl和loioctl中。
in_ifinit通过命令SIOCSIFADDR和命令SIOCAIFADDR来调用。in_ifinit总是通过接口的if_ioctl函数发送SIOCSIFADDR命令。
1. leioctl函数
图26显示了SIOCSIFADDR命令的处理。
图26 函数leioctl
614~637 在处理命令前,data转换为一个ifaddr结构指针,并且ifp->if_unit为此请求选择相应的le_softc结构。
leinit将接口标志为启动并初始化硬件。对于Internet地址,IP地址保存在arpcom结构中,并且为此地址发送一个免费ARP。
627~677 对于未识别命令,返回EINVAL。
2. slioctl函数
slioctl(图27)为SLIP设备驱动器处理SIOCSIFADDR和SIOCSIFDSTADDR。
图27 函数slioctl
663~672 对于这两个命令,如果地址不是一个IP地址,则返回EAFNOSUPPORT。SIOCSADDR命令设置为IFF_UP。
688~693 对于未识别命令,返回EINVAL。
3. loioctl函数
loioctl和它的SIOCSIFADDR命令的实现如图28所示。
图28 函数loioctl
135~151 对于Internet地址,loioctl设置IFF_UP,并立即返回。
167~171 对于未识别命令,返回EINVAL。
更多最新文章尽在公众号:大白爱爬山,欢迎关注!