概说《TCP/IP详解 卷2》第7章 域和协议

原文链接:https://mp.weixin.qq.com/s/pUlWfDqJgDUO5exhzfI08Q

本文要点

  • 引言

  • domain结构

  • protosw结构

  • IP的domain和protosw

  • pffindproto和pffindtype函数

  • pfctlinput函数

  • IP初始化

    • Internet传输分用

    • ip_init函数

  • sysctl系统调用

 

引言

    Net/3通过协议中使用的编址方法将协议分组,关联到一个域中,并用一个协议族常量来标识每个域。当前,一个域中的每个协议使用同类地址,并且每种地址只被一个域使用。所以,一个域能通过它的协议族或者地址族常量唯一标识。图1列出了我们讨论的协议和常量。

概说《TCP/IP详解 卷2》第7章 域和协议

图1 公共的协议和地址族常量

    PF_LOCAL和AF_LOCAL是支持同一主机上进程间通信的协议的主要标识,它们是POSIX.12标准的一部分。在Net/3之前,用PF_UNIX和AF_UNIX标识这些协议。

 

domain结构

    一个协议域由一个图2所示的domain结构来表示。

概说《TCP/IP详解 卷2》第7章 域和协议

图2 结构domain的定义

42~57 dom_family是一个地址族常量(例如AF_INET),它指示在此域中协议使用的编址方式。dom_name是此域的一个文本名称(例如"internet")。

    dom_init指向初始化此域的函数。dom_externalize和dom_dispose指向那些管理通过此域内通信路径发送的访问权限的函数。Unix域实现这个特性在进程间传递文件描述符。Interntet域不能实现访问权限。

    dom_protosw和dom_protswNPROTOSW指向一个protosw结构的数组的起始和结束。dom_next指向一个内核支持的域链表中的下一个域。domains指针是一个全局的、指向所有域的链表的头指针。

    dom_rtattach、dom_rtoffset和dom_maxrtkey保存此域的路由信息。

    图3显示了一个domains列表的例子。

概说《TCP/IP详解 卷2》第7章 域和协议

图3 domain列表

protosw结构

    Net/3为内核中的每个协议分配一个protosw结构并初始化,同时将在一个域中的所有协议的这个结构组织到一个数组中。每个domain结构引用到相应的protosw结构数组。一个内核可以通过提供多个protosw项为同一协议提供多个接口。protosw结构的定义如图4所示。

概说《TCP/IP详解 卷2》第7章 域和协议

图4 protosw结构的定义

57~61 此结构的前4个成员用来标识和表征协议。pr_type指示协议的通信语义。图5列出了pr_type可能的值和对应的Internet协议。

概说《TCP/IP详解 卷2》第7章 域和协议

图5 protosw结构的定义

    pr_domain指向相关的domain结构,pr_protocol为域中协议的编号,pr_flags标识协议的附加特征。图6列出了pr_flags的可能值。

概说《TCP/IP详解 卷2》第7章 域和协议

图6 pr_flags的值

    如果一个协议支持PR_ADDR,必须也支持PR_ATOMIC。PR_ADDR和PR_CONNREQUIRED是互斥的。

    当设置了PR_WANTRCVD,并当插口层将插口接收缓存的数据传递给一个进程时(即当在接收缓存中有更多可用空间时),它通知协议层。

    PR_RIGHTS指示访问权限控制报文能通过连接来传递。访问权限要求内核中的其他支持来确保在接收进程没有销毁报文时能完成正确的清除工作。仅Unix域支持访问权限,在那里它们用来在进程间传递描述符。

    图7所示的是协议类型、协议标志和协议语义间的关系。

概说《TCP/IP详解 卷2》第7章 域和协议

图7 协议特征和举例

    图7不包括标志PR_WANTRCVD和PR_RIGHTS。对于可靠的面向连接的协议,PR_WANTRCVD总是被设置。

    为了理解一个protosw项的通信语义,必须一起考虑PRxxx标志和pr_type。在图7中,我们用两列“是否有记录边界”和“是否可靠”来描述由pr_type隐式指示的语义。图7显示了可靠协议的三种类型:

  • 面向连接的字节流协议,如TCP。这些协议用SOCK_STREAM标识。

  • 有记录边界的面向连接的流协议用SOCK_SEQPACKET标识。在此协议类型中,PR_ATOMIC指示记录是否由每个输出请求隐式地指定,或显示通过在输出中设置标志MSG_EOR来指定。

  • 第三种可靠协议提供一个有隐式记录边界的面向连接服务,它由SOCK_RDM标识。

    两种不可靠协议显示在图7中:

  • 一个运输层数据报协议,如UDP,它包括复用和检验和,由SOCK_DGRAM指定。

  • 一个网络层数据报协议,如ICMP,它由SOCK_RAW指定。

62~68 接着的5个成员是函数指针,用来提供从其它协议对此协议的访问。pr_input处理来自下层协议输入的数据,pr_output处理来自上层协议输出的数据,pr_ctlinput处理来自下层的控制信息,而pr_ctloutput处理来自上层的控制信息。pr_usrreq处理来自进程的所有通信请求。如图8所示。

概说《TCP/IP详解 卷2》第7章 域和协议

图8 一个协议的5个主要入口点

69~75 剩下的5个成员是协议的实用函数。pr_init处理初始化。pr_fasttimo和pr_slowtimo分别每200ms和500ms被调用来执行周期性的协议函数,如更新重传定时器。pr_drain在内存缺乏时被m_reclaim调用。它请求协议释放尽可能多的内存。pr_sysctl为sysctl(8)命令提供一个接口,一种修改系统范围参数的方式,如允许转发分组和UDP检验和计算。

 

IP的domain和protosw

    内核声明所有协议的domain和protosw结构,并进行静态初始化。对于Internet协议,inetsw数组包含protosw结构。图9总结了在数组inetsw中的协议信息。图10显示了Internet协议的数组定义和domain结构定义。

概说《TCP/IP详解 卷2》第7章 域和协议

图9 Internet域协议

概说《TCP/IP详解 卷2》第7章 域和协议

图10 Internet的domain和protows结构

39~77 在inetsw数组中的3个protosw结构提供对IP的访问。第一个:inetsw[0],标识IP的管理函数并且只能由内核访问。其他两项:inetsw[3]和inetsw[6],除了pr_protocol值以外它们是一样的,都提供到IP的一个原始接口。inetsw[3]处理接收到的任何未识别协议的分组。inetsw[6]是默认的原始协议,当没有找到其他可以匹配的项时,这个结构由函数pffindproto返回。

    原始接口允许一个进程发送和接收不涉及运输层的IP分组。原始接口的一个用途是实现内核外的传输协议。另一个用户就是作为诊断工具,如traceroute,它使用原始IP接口直接访问IP。图11总结了IP protosw结构。

概说《TCP/IP详解 卷2》第7章 域和协议

图11 IP protosw的条目

78~81 Internet协议的domain结构显示在图10底部。Internet域使用AF_INET网络编址,文本名称"internet",没有初始化和控制报文函数。它的protosw结构在inetsw数组中。

    Internet协议的路由初始化函数是rn_inithead。一个IP地址的最大有效位为32,并且一个Internet选路键的大小为一个16字节的socketaddr_in结构。

    在概说《TCP/IP详解 卷2》第3章 接口层图19中的系统初始化期间,内核调用domaininit来链接结构domain和protosw。domaininit显示如图12所示。

概说《TCP/IP详解 卷2》第7章 域和协议

图12 函数domaininit

 

37~42 ADDDOMAIN宏声明并链接一个domain结构。例如,ADDDOMAIN(unix)展开为:

    extern struct domain unixdomain;

    unixdomain.dom_next = domains;

    domains = &unixdomain

43~54 domaininit通过调用ADDDOMAIN为每个支持的域结构构造域列表。

    图13显示了链接的结构domain和protosw,它们配置在内核中来支持Internet、Unix和OSI协议族。

概说《TCP/IP详解 卷2》第7章 域和协议

图13 初始化后的domain链表和protosw数组

55~61 两个嵌套的for循环查找内核中的每个域和协议,并且若定义了初始化函数dom_init和pr_init,则调用它们。对于Internet协议,调用下面函数(图10):ip_init、udp_init、tcp_init、igmp_init和rip_init。

62~65 在domaininit中计算这些参数,用来控制mbuf中分组的格式,以避免对数据额外复制。在协议初始化期间设置了max_linkhdr和max_protohdr。这里,domaininit将max_linkhdr强制设置为一个下限16。16字节用于给带有4字节边界的14字节以太网首部留出空间。图14、15列出了这些参数和典型的取值。

概说《TCP/IP详解 卷2》第7章 域和协议

图14 用来减少协议数据复制的参数

概说《TCP/IP详解 卷2》第7章 域和协议

图15 mbuf和相关的最大首部长度

    max_protohdr是一个软限制,估计预期的协议首部大小。在Internet域中,IP和TCP首部长度通常为20字节,但都可以达到60字节。长度超过max_protohdr的代价是花时间将数据向后移动,以留出比预期的协议首部更大的空间。

66~68 domaininit通过调用timeout启动pfslowtimo和pffashtimo。第3个参数指明何时内核应该调用这个函数,在这里是一个时间滴答内。两个函数显示在图16中。

概说《TCP/IP详解 卷2》第7章 域和协议

图16 函数pfslowtimo和pffasttimo

153~176 这两个相近的函数用两个for循环分别为每个协议调用pr_slowtimo和pr_fashtimo,前提是如果定义了这两个函数。这两个函数每500ms和200ms通过调用timeout调度自己一次。

 

pffindproto和pffindtype函数

    如图17所示,函数pffindproto和pffindtype通过编号(例如IPPROTO_TCP)或者类型(例如SOCK_STREAM)来查找一个协议。当进程创建一个插口时,调用这两个函数来查找相应的protosw项。

图17 函数pffindproto和pffindtype

69~84 pffindtype线性搜索domains,查找指定的族,然后在域内搜索第一个指定类型的协议。

85~107 pffindproto搜索domains,查找指定的族、类型和协议。如果没有找到一个protocol、type匹配项,并且type为SOCK_RAW,而此域有一个默认的原始协议(pr_protocol为0),则pffindproto选择默认的原始协议而不是完全失败。例如,一个调用如下:

    pffindproto( PF_INET,27, SOCK_RAW )

它返回一个指向inetsw[6]的指针,默认的原始IP协议,因为Net/3不包括对协议27的支持,通过访问原始IP,一个进程可以使用内核来管理IP分组的发送和接口,从而自己实现协议27服务。

    两个函数都返回一个所选协议的protosw结构的指针;或者没找到则返回一个空指针。

    当一个应用程序进行下面的调用时:

    socket(PF_INET, SOCK_STREAM, 0);  //TCP

    pffindtype被调用如下:

    pffindtype(PF_INET, SOCK_STREAM);

    图9显示pffindtype会返回一个指向inetsw[2]的指针,因为TCP是此数组中第个SOCK_STREAM协议。同样,

    socket(PF_INET, SOCK_DGRAM, 0); //UDP

    会调用

    pffindtype(PF_INET, SOCK_DGRAM);

    它返回一个指向inetsw[1]的UDP指针。

 

pfctlinput函数

    函数pfctlinput给每个域中的每个协议发送一个控制请求,如图18所示。当可能影响每个协议的事件发生时,使用这个函数,例如一个接口被关闭,或路由表发生改变。当一个ICMP重定向报文到达时,ICMP调用pfctlinput,因为重定向会影响所有的Internet协议(例如TCP和UDP)。

概说《TCP/IP详解 卷2》第7章 域和协议

图18 函数pfctlinput

142~152 两个嵌套的for循环查找每个域中的每个协议。pfctlinput通过调用每个协议的pr_ctlinput函数来发送由cmd指定的协议控制命令。对于UDP,调用udp_ctlinput;而对于TCP,调用tcp_ctlinput。

 

IP初始化

    Internet域没有一个初始化函数,但单个Internet协议有。现在我们仅查看IP初始化函数ip_init。在讨论代码之前,先了解一下数组ip_protox。

1. Internet传输分用

    一个网络层协议(比如IP)必须分用数据报,并将它们传递到相应的运输层协议。为了完成这些,相应的protosw结构必须通过一个在数据报中出现的协议编号得到。对于Internet协议,这由数组ip_protox来完成,如图19所示。

概说《TCP/IP详解 卷2》第7章 域和协议

图19 ip_protox将协议编号映射到inetsw中的一项

    数组ip_protox的下标来自IP首部的协议值(ip_p)。被选项是inetsw数组中处理此数据报的协议的下标。例如,一个协议编号为6的数据报由inetsw[2]处理,协议为TCP。内核在协议初始化时构造ip_protox数组,如图20所示。

 

 

2. ip_init函数

概说《TCP/IP详解 卷2》第7章 域和协议

图20 函数ip_init

    domaininit(图12)在系统初始化期间调用ip_init。

71~78 pffindproto返回一个指向原始协议(inetsw[3],图11)的指针。如果找不到原始协议,Net/3就调用panic,因为这是内核要求。如果找不到原始协议,内核一定被配置错误了。IP将一个未知传输协议的到达分组传递给此协议,在那里它们由内核外部的一个进程来处理。

79~85 接着两个循环初始化数组ip_protox。第一个循环将数组中的每项设置为pr,即默认协议的下标(图19中为3)。第二个循环检查inetsw中的每个协议,并且将ip_protox中的匹配项设置为引用相应的inetsw项。为此,每个protosw结构中的pr_protocol必须是期望出现在输入数据报中的协议编号。

86~92 ip_init初始化IP重装队列ipq,用系统时钟植入ip_id,并将IP输入队列(ipintrq)的最大长度设置为50(ipqmaxlen)。ip_id用系统时钟设置,为数据报标识符提供一个随机起点。最后,ip_init分配一个两维数组ip_ifmatrix,统计系统接口之间路由的分组数。

 

sysctl系统调用

    系统调用sysctl访问并修改Net/3系统范围参数。系统管理员通过程序sysctl(8)修改这些参数。每个参数由一个分层的整数列表来标识,并有一个相应的类型。此系统调用的原型为:

    int sysctl(int *name, u_int namelen, 

                  void *old, size_t *oldlenp, 

                  void *new, size_t newlen);

    *name指向一个包含namelen个整数的数组。*old指向在此范围内返回的旧值,*new指向在此范围内传递的新值。

    图21总结了关于联网名称组织。

概说《TCP/IP详解 卷2》第7章 域和协议

图21 sysctl的名称组织

    在图21中,IP转发标志的全名为:CTL_NET、PE_INET、0、IPCTL_FORWARDING,用4个整数存储在一个数组中。

    每层的sysctl命名方案通过不同的函数处理。图22显示了处理这些Internet参数的函数。

概说《TCP/IP详解 卷2》第7章 域和协议

图22 处理Internet参数的sysctl函数

    顶层名称由sysctl处理。网络层名称由net_sysctl处理,它根据族和协议将控制转给协议的protosw项指定的pr_sysctl函数。

    图23所示的是函数net_sysctl。

概说《TCP/IP详解 卷2》第7章 域和协议

概说《TCP/IP详解 卷2》第7章 域和协议

图23 函数net_sysctl

108~119 net_sysctl的参数除了增加了p外,同系统调用sysctl一样,p指向当前进程结构。

120~134 在名称接下来的两个整数被认为是在结构domain和protosw中指定的协议族和协议编号成员的值。如果没有指定族,则返回0。如果指定了族,for循环在域列表中查找一个匹配的族。如果没找到,那么返回ENOPROTOOPT。

135~141 如果找到匹配域,第二个for循环查找第一个定义了函数pr_sysctl的匹配协议。当找到匹配项,将请求传递给此协议的pr_sysctl函数。注意,把name+2指向的整数传递给下一级。如果没有找到匹配的协议,那么返回ENOPROTOOPT。

    图24所示的是为Internet协议定义的pr_sysctl函数。

概说《TCP/IP详解 卷2》第7章 域和协议

图24 Internet协议族的pr_sysctl函数

 

更多最新文章尽在公众号:大白爱爬山,欢迎关注!

概说《TCP/IP详解 卷2》第7章 域和协议