第4章 接口以太网.docx
《第4章 接口以太网.docx》由会员分享,可在线阅读,更多相关《第4章 接口以太网.docx(23页珍藏版)》请在冰豆网上搜索。
第4章接口以太网
第4章接口:
以太网
4.1引言
在第3章我们讨论了所有接口要用到的数据结构及对这些数据结构的初始化。
在本章,我们说明以太网设备驱动程序在初始化后是如何接收和传输帧的。
本章的第二部分是介绍配置网络设备的通用ioctl命令。
第5章是SLIP和环回驱动程序。
我们不准备查看整个以太网驱动程序的源代码,因为它有大约1,000行C代码(其中有一半是一个特定接口卡的硬件细节),但我们要研究设备无关的以太网代码部分,及驱动程序是如何和内核其他部分交互的。
如果读者对一个驱动程序的源代码感兴趣,Net/3版本包括很多不同接口的源代码。
要想研究接口的技术规范要求能理解设备专用的命令。
图4.1所示的是Net/3提供的各种驱动程序,包括在本章我们要讨论的LANCE驱动程序。
网络设备驱动程序通过ifnet结构(图3.6)中的7个函数指针来访问。
图4.2列出了指向我们的三个例子驱动程序的指针项。
输入函数不包含在图4.2中,因为它们是网络设备中断驱动的。
中断服务例程的配置是硬件相关的并且超出了本书的范围。
我们要识别处理设备中断的函数,但不是这些函数被调用的机制。
设备
文件
DECDEUNA接口
vax/if/if_de.c
3Com以太网接口
vax/if/if_ec.c
ExcelanEXOS204接口
vax/if/if_ex.c
Interlan以太网通信控制器
vax/if/if_il.c
InterlanNP100以太网通信控制器
vax/if/if_ix.c
DigitalQ-BUStoNI适配器
vax/if/if_qe.c
CMCENP-20以太网控制器
tahoe/if/if_enp.c
ExcelanEXOS202(VME)&203(QBUS)
tahoe/if/if_ex.c
ACCVERSAbus以太网控制器
tahoe/if/if_ace.c
AMD7990LANCE接口
hp300/dev/if_le.c
NE2000以太网
i386/isa/if_ne.c
WesternDigital8003以太网适配器
i386/isa/if_we.c
图4.1Net/3中可用的以太网驱动程序
ifnet
以太网
SLIP
环回
说明
if_init
leinit
硬件初始化
if_output
ether_output
slouput
looutput
接收并对传输的帧进行排队
if_start
lestart
开始传输帧
if_done
输出完成(未用)
if_ioctl
leioctl
slioctl
lcioctl
处理来自一个进程的ioctl命令
if_reset
lereset
把设备复位到已知的状态
if_watchdog
监视设备故障或收集统计信息
图4.2例子驱动程序的接口函数
只有函数if_output和if_ioctl被一致地调用。
而if_init、if_done和if_reset从来不被调用或仅从设备专用代码调用(例如leinit直接被leioctl调用)。
函数if_start仅被函数ether_output调用。
4.2代码介绍
以太网设备驱动程序和通用接口ioctl的代码包含在两个头文件和三个C文件中,它们列于图4.3中。
文件
说明
net/if_ether.h
以太网结构
net/if.h
Ioctl命令定义
net/if_thersubr.c
通用以太网函数
hp300/dev/if_le.c
LANCE以太网驱动程序
net/if.c
ioctl处理
图4.3在本章讨论的文件
全局变量
显示在图4.4中的全局变量包括协议输入队列、LANCE接口结构和以太网广播地址。
变量
数据类型
说明
arpintrq
structifqueue
ARP输入队列
clnlintrq
structifqueue
CLNP输入队列
ipintrq
structifqueue
IP输入队列
le_softc
structle_softc[]
LANCE以太网接口
etherbraodcastaddr
u_char[]
以太网广播地址
图4.4本章介绍的全局变量
le_softc是一个数组,因为这里可以有多个以太网接口。
统计
结构ifnet中为每个接口收集的统计如图4.5所示。
ifnet成员
说明
用于
SNMP
if_collisions
在CSMA接口的冲突数
if_ibytes
接收到的字节总数
if_ierrors
接收到的有输入差错分组数
if_imcasts
接收到的多播分组数
if_ipackets
在接口接收到的分组数
if_iqdrops
被此接口丢失的输入分组数
if_lastchange
上一次改变统计的时间
if_noproto
指定为不支持协议的分组数
if_obytes
发送的字节总数
if_oerrors
接口上输出的差错数
if_omcasts
发送的多播分组数
if_opackets
接口上发送的分组数
if_snd.ifq_drops
在输出期间丢失的分组数
if_snd.ifq_len
输出队列中的分组数。
图4.5结构ifnet中维护的统计
图4.6显示了一些netstat命令的输出例子,它包括一些ifnet结构中的统计信息。
第一列包含显示为一个字符串的if_name和if_unit。
若接口是关闭的(不设置IFF_UP),一个星号显示在这个名字的旁边。
在图4.6中,sl0、sl2和sl3是关闭的。
图4.6接口统计的样本
第二列显示的是if_mtu。
在表头“Network”和“Address”底下的输出依赖于地址的类型。
对于链路层地址,显示了结构sockaddr_dl的sdl_data的内容。
对于IP地址显示了子网和单播地址。
其余的列是if_ipackets,if_ierrors,if_opackets,if_oerrors和if_collisions。
在输出中冲突的分组大约有3%(942,798/23,234,729=3%)。
这个机器的SLIP输出队列从未满过,因为SLIP接口的输出没有差错。
在传输中LANCE硬件检测到12个以太网的输出差错。
其中一些差错可能被视为冲突。
硬件检测出814个以太网的输入差错,例如分组太短或错误的检验和。
SNMP变量
图4.7所示的是SNMP接口表(ifTable)中的一个接口项对象(ifEntry),它包含在每个接口的ifnet结构中。
ISODESNMP代理从if_type获得ifSpeed并为ifAdminStatus维护一个内部变量。
代理的ifLastChange基于结构ifnet中的if_lastchange但与代理的启动时间相关,而不是与系统的启动时间相关。
代理为ifSpecific返回一个空变量。
接口表,索引=
SNMP变量
ifnet成员
说明
ifIndex
if_index
唯一地标识接口
ifDescr
if_name
接口的文本名称
ifType
if_type
接口的类型(例如以太网、SLIP等等)
ifMtu
if_mtu
接口的MTU(字节)
ifSpeed
(看正文)
接口的正常速率(每秒比特)
ifPhysAddress
ac_enaddr
媒体地址(来自结构arpcom)
ifAdminStatus
(看正文)
接口的期望状态(IFF_UP标志)
ifOperStatus
if_flags
接口的操作状态(IFF_UP标志)
ifLastChange
(看正文)
上一次统计改变时间
ifInOctets
if_ibytes
输入的字节总数
ifInUcastPkts
if_ipackets–
if_imcasts
输入的单播分组数
ifInNUcastPkts
if_imcasts
输入的广播或多播分组数
ifInDiscards
if_iqdrops
因为实现的限制而丢弃的分组数
ifInErrors
if_ierrors
差错的分组数
ifInUnknownProtos
if_noproto
指定为未知协议的分组数
ifOutOctets
if_obytes
输出字节数
ifOutUcastPkts
if_opackets–
if_omcasts
输出的单播分组数
ifOutNUcastPkts
if_omcasts
输出的广播或多播分组数
ifOUtDiscards
if_snd.ifq_drops
因为实现的限制而丢失的输出分组数
ifOutErrors
if_oerrors
因为差错而丢失的输出分组数
ifOutQLen
if_snd.ifq_len
输出队列长度
ifSpecific
n/a
媒体专用信息的SNMP对象ID(未实现)
图4.7接口表ifTable的变量
4.3以太网接口
Net/3以太网设备驱动程序都遵循同样的设计。
对于大多数Unix设备驱动程序来说,都是这样,因为写一个新接口卡的驱动程序总是在一个已有的驱动程序的基础上修改而来的。
在本节,我们简要地概述一下以太网的标准和一个以太网驱动程序的设计。
我们用LANCE驱动程序来说明这个设计。
图4.8说明了一个IP分组的以太网封装。
图4.8一个IP分组的以太网封装
以太网帧包括48bit的目的地址和源地址,接下来是一个16bit的类型字段,它标识这个帧所携带的数据的格式。
对于IP分组,类型是0x0800(2048)。
帧的最后是一个32bit的CRC(循环冗余检验),它用来检查帧中的差错。
我们所讨论的最初的以太网组帧的标准在1982年由Digital设备公司、Intel公司及施乐公司发布,并作为今天在TCP/IP网络中最常用的格式。
另一个可选的格式是IEEE(电气电子工程师协会)规定的802.2和802.3标准。
更多的IEEE标准详见[Stallings1987]。
我们用48bit的以太网地址作为硬件地址。
IP地址到硬件地址之间的转换用ARP协议(RFC826[Plummer1982]),这个协议在第21章讨论,而硬件地址到IP地址的转换用RARP协议(RFC903[Finlaysonetal.1984])。
以太网地址有两种类型:
单播和多播。
一个单播地址描述一个单一的以太网接口,而一个多播地址描述一组以太网接口。
一个以太网广播是一个所有接口都接收的多播。
以太网单播地址由设备的厂商分配,也有一些设备的地址允许用软件改变。
一些DECNET协议要求标识一个多接口主机的硬件地址,因此DECNET必须能改变一个设备的以太网单播地址。
图4.9列举了以太网接口的数据结构和函数。
在图中有:
用一个椭圆标识的一个函数(leintr)、用一个方框标识的数据结构(le_softc[0])、le_softc及用圆角方框标识的一组函数(ARP协议)。
图4.9左上角显示的是OSI无连接网络层(clnl)协议、IP和ARP的输入队列。
对于clnlintrq我们不打算讲更多,将它包含进来是为了强调ether_input要将以太网帧分路到多个协议队列中。
在技术上,OSI使用无连接网络协议(CLNP而不是CLNL),但我们使用的是Net/3中的术语。
CLNP的官方标准是ISO8473。
[Stallings1993]对这个标准进行了概述。
接口结构le_softc在图4.9的中间。
我们感兴趣的是这个结构中的ifnet和arpcom,其他是LANCE硬件的专用部分。
我们在图3.6显示了结构ifnet,在图3.26显示了结构arpcom。
leintr函数
我们从以太网帧的接收开始。
现在,我们假设硬件已初始化并且系统已完成配置,当接口产生一个中断时,leintr被调用。
在正常操作中,一个以太网接口接收发送到它的单播地址和以太网广播地址的帧。
当一个完整的帧可用,接口就产生一个中断并且内核调用leintr。
图4.9以太网设备驱动程序
在第12章,我们会看见可能要配置多个以太网接口来接收以太网多播帧(不同于广播)。
有些接口可以配置为运行在混杂方式(promiscuousmode),在这种方式下接口接收所有出现在网络上的帧。
在卷1中讨论的tcpdump程序可以使用BPF(BSD分组过滤程序)来利用这种特性。
leintr检测硬件并且如果有一个帧到达就调用leread把这个帧从接口转移到一个mbuf链中(用m_devget)。
如果硬件报告一个帧已传输完或发现一个差错(如一个有错误的检验和),则leintr更新相应的接口统计,复位这个硬件,并调用lestart来传输另一个帧。
所有以太网设备驱动程序将它们接收到的帧传给ether_input做进一步的处理。
设备驱动程序构造的mbuf链不包括以太网首部,以太网首部作为一个独立的参数传递给ether_input。
结构ether_header显示在图4.10中。
38-42以太网CRC并不总可用。
它由接口硬件来计算与检验,接口硬件丢弃到达的CRC差错帧。
以太网设备驱动程序负责ether_type的网络和主机字节序间的转换。
在驱动程序外它总是主机字节序。
图4.10结构ether_header
leread函数
函数leread(图4.11)的开始是由leintr传给它的一个连续的内存缓冲区并且构造了一个ther_header结构和一个mbuf链。
这个链表存储来自以太网帧的数据。
leread还将输入帧传给BPF。
图4.11函数leread
528-539函数leintr给leread传了三个参数:
unit,它标识接收到此帧的特定接口卡;buf,它指向接收到的帧;len,它是帧的字节数(包括首部和CRC)。
函数将et指向这个缓存的开始,并且将以太网字节序转换成主机字节序,来构造结构ether_header。
540-551将len减去以太网首部和CRC的大小得到数据的字节数。
短分组(Runtpacket)是一个长度太短的非法以太网帧,它被记录、统计并被丢弃。
552-557接下来,目的地址被检测,判断是不是以太网广播或多播地址。
以太网广播地址是一个以太网多播地址的特例;它的每一比特都被设置了。
etherbroadcastaddr是一个数组,定义如下:
u_charetherbroadcastaddr[6]={0xff,0xff,0xff,0xff,0xff,0xff};
这是C语言中定义一个48bit值的方便的方法。
这个技术仅在我们假设字符是8bit值时才起作用——ANSIC并不保证这一点。
bcmp比较etherbroadcastaddr和ether_dhost,若相同,则设置标志M_BCAST。
一个以太网多播地址由这个地址的首字节的低位比特来标识,如图4.12所示。
图4.12检测一个以太网多播地址
在第12章我们会看到并不是所有以太网多播帧都是IP多播数据报并且IP必须进一步检测这个分组。
如果这个地址的多播比特被置位,在mbuf首部中设置M_MCAST。
检测的顺序是重要的:
首先ether_input将整个48bit地址和以太网广播地址比较,若不同则检测这个标识以太网多播地址的首字节的低位比特(习题4.1)。
558-573如果接口带有BPF,调用bpf_tap把这个帧直接传给BPF。
我们会看见对于SLIP和环回接口,要构造一个特定的BPF帧,因为这些网络没有一个链路层首部(不像以太网)。
当一个接口带有BPF,它可以配置为运行在混淆模式并且接收网络上出现的所有以太网帧,而不是通常硬件接收帧的子集。
如果分组发送给一个不与此接口地址匹配的单播地址,则被leread丢弃。
574-585m_devget(2.6节)将数据从传给leread的缓存中复制到一个它分配的mbuf链中。
传给m_devget的第一个参数指向以太网首部后的第一个字节,它是此帧中的第一个数据字节。
如果m_devget内存用完,leread立即返回。
另外广播和多播标志被设置在链表中的第一个mbuf中,ether_input处理这个分组。
ether_input函数
函数ether_input显示在图4.13中,它检查结构ether_header来判断接收到的数据的类型并将接收到的分组加入到队列中等待处理。
图4.13函数ether_input
广播和多播的识别
196-209传给ether_input的参数有:
ifp,一个指向接收此分组的接口的ifnet结构的指针;eh,一个指向接收分组的以太网首部的指针;m,一个指向接收分组的指针(不包括以太网首部)。
任何到达不工作接口的分组将被丢弃。
可能没有为接口配置一个协议地址,或者可能被程序ifconfig(8)(6.6节)显式地将接口禁用了。
210-218变量time是一个全局的timeval结构,内核用它维护当前时间和日期,它是从Unix新纪元(1970年1月1日00:
00:
00,协调通用时间[UTC])开始的秒和微秒数。
在[ItanoandRamsey1993]中可以找到对UTC的简要讨论。
我们在Net/3源代码中会经常遇到结构timeval:
structtimeval{
longtv_sec;/*seconds*/
longtv_usec;/*andmicroseconds*/
};
ether_input用当前时间更新if_lastchange并且把if_ibytes加上输入分组的长度(分组长度加上14字节的以太网首部)。
然后,ether_input再次用leread去判断分组是否为一个广播或多播分组。
有些内核编译时可能没有包括BPF代码,因此测试必须在ether_input中进行。
链路层分用
219-227ether_input根据以太网类型字段来跳转。
对于一个IP分组,schednetisr调度一个IP软件中断并且选择IP输入队列,ipintrq。
对于一个ARP分组,调度ARP软件中断并选择arpintrq。
一个isr是一个服务例程中断。
在原先的BSD版本,当处于网络中断级别时,ARP分组通过调用arpinput立即被处理。
通过分组排队,它们可以在软件中断级别被处理。
如果要处理其他以太网类型,一个内核程序员应在此增加其他情况的处理。
或者,一个进程能用BPF接收其他以太网类型。
例如,在Net/3中,RARP服务通常用BPF实现。
228-307默认情况处理不识别以太网类型或按802.3标准(例如OSI无连接传输)封装的分组。
以太网type字段和802.3length字段在一个以太网帧中占用同一位置。
两种封装能够分辨出来,因为一个以太网封装的类型范围和802.3封装的长度范围是不同的(图4.14)。
我们跳过OSI代码,在[Stallings1993]中有对OSI链路层协议的说明。
范围
说明
0–1500
IEEE802.3length字段
1501–65535
以太网type字段:
2048
IP分组
2045
ARP分组
图4.14以太网type和802.3length字段
有很多其他以太网类型值分配给各种协议;我们没有在图4.14中显示。
在RFC1700[ReynoldsandPostel1994]中有一个有更多通用类型的列表。
分组排队
308-315最后,ether_input把分组放置到选择的队列中,若队列为空则丢弃此分组。
我们在图7.23和21.16中会看到IP和ARP队列的默认限制为每个50个(ipqmaxlen)分组。
当ether_input返回时,设备驱动程序通知硬件它已准备接收下一分组,这时下一分组可能已存在于设备中。
当schednetisr调度的软件中断发生时处理分组输入队列(1.12节)。
准确地说,调用ipintr来处理IP输入队列中的分组,调用arpintr来处理ARP输入队列中的分组。
ether_output函数
我们现在查看以太网帧的输出,当一个网络层协议,如IP,调用此接口ifnet结构中指定的函数if_output开始处理输出。
所有以太网设备的if_output是ether_output(图4.2)。
ether_output用14字节以太网首部封装一个以太网帧的数据部分,并将它放置到接口的发送队列中。
这个函数比较大,我们分4个部分来说明:
验证,
特定协议处理,
构造帧,
接口排队。
图4.15包括这个函数的第一个部分。
49-64ether_output的参数有:
ifp,它指向输出接口的ifnet结构;m0,要发送的分组;dst,分组的目的地址;rt0,路由信息。
65-67在ether_output中多次调用宏senderr。
#definesenderr(e){error=(e);gotobad;}
senderr保存差错码并跳到函数的尾部bad,在那里分组被丢弃并且ether_output返回error。
如果接口启动并在运行,ether_output更新接口的上次更改时间。
否则,返回ENETDOWN。
主机路由
68-74rt0指向ip_output找到的路由项并传递给ether_output。
如果从BPF调用ether_output,rt0可以为空,在这种情况控制转给图4.16中的代码。
否则,验证路由。
如果路由无效,参考路由表并且当路由不能被找到时返回EHOSTUNREACH。
这时,rt0和rt指向一个到下一跳目的地的有效路由。
图4.15函数ether_output:
验证
网关路由
75-85如果分组的下一跳是一个网关(而不是最终目的),找到一个到此网关的路由,并且rt指向它。
如果不能发现一个网关路由,则返回EHOSTUNREACH。
这时,rt指向下一跳目的地的路由。
下一跳可能是一个网关或最终目的地址。
避免ARP泛洪
86-90当目的方不准备响应ARP请求时,ARP代码设置标志RTF_REJECT来丢弃到达目的方的分组。
这描述在图21.24中。
ether_output根据此分组的目的地址继续处理。
因为以太网设备仅响应以太网地址,要发送一个分组,ether_output必须发现下一跳目的地的IP地址所对应的以太网地址。
ARP协议(21章)用来实现这个转换。
图4.16显示了驱动程序是如何访问ARP协议的。
图4.16函数ether_output:
网络协议处理
IP输出
91-101ether_output根据目的地址中的sa_family进行跳转。
我们在图4.16中仅显示了caseAF_INET、AF_ISO和AF_UNSPEC而略过了AF_ISO的代码。
caseAF_INET调用arpresolve来决定与目的IP地址相对应的以太网地址。
如果以太网地址已存在于ARP高速缓存中,则arpresolve返回1并且ether_output继续执行。
否则,这个IP分组由ARP控制,并且ARP判断地址