Net/3 的接口层位于协议栈的最底部,负责和设备驱动交互,并为上层应用屏蔽物理层的差异。
接口层为上层提供 best effort 的服务。
ifnet
每个 ifnet 对应了一个网络设备,由于网络设备可能有多个地址,因此 ifnet 中的 ifaddr 也是是一个单向链表:
ifnet 结构
ifnet 的定义如下:
struct ifnet {
char *if_name; /* name, e.g. ``en'' or ``lo'' */ (1)
struct ifnet *if_next; /* all struct ifnets are chained */
struct ifaddr *if_addrlist; /* linked list of addresses per if */ (2)
int if_pcount; /* number of promiscuous listeners */
caddr_t if_bpf; /* packet filter structure */
u_short if_index; /* numeric abbreviation for this if */
short if_unit; /* sub-unit for lower level driver */
short if_timer; /* time 'til if_watchdog called */ (3)
short if_flags; /* up/down, broadcast, etc. */
struct if_data {
/* generic interface information */
u_char ifi_type; /* ethernet, tokenring, etc */
u_char ifi_addrlen; /* media address length */
u_char ifi_hdrlen; /* media header length */
u_long ifi_mtu; /* maximum transmission unit */
u_long ifi_metric; /* routing metric (external only) */
u_long ifi_baudrate; /* linespeed */
/* volatile statistics */
u_long ifi_ipackets; /* packets received on interface */
u_long ifi_ierrors; /* input errors on interface */
u_long ifi_opackets; /* packets sent on interface */
u_long ifi_oerrors; /* output errors on interface */
u_long ifi_collisions; /* collisions on csma interfaces */
u_long ifi_ibytes; /* total number of octets received */
u_long ifi_obytes; /* total number of octets sent */
u_long ifi_imcasts; /* packets received via multicast */
u_long ifi_omcasts; /* packets sent via multicast */
u_long ifi_iqdrops; /* dropped on input, this interface */
u_long ifi_noproto; /* destined for unsupported protocol */
struct timeval ifi_lastchange; /* last updated */
} if_data;
/* procedure handles */
int(*if_init) /* init routine */
__P((int));
int(*if_output) /* output routine (enqueue) */
__P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *));
int(*if_start) /* initiate output routine */
__P((struct ifnet *));
int(*if_done) /* output complete routine */
__P((struct ifnet *)); /* (XXX not used; fake prototype) */
int(*if_ioctl) /* ioctl routine */
__P((struct ifnet *, u_long, caddr_t));
int(*if_reset)
__P((int)); /* new autoconfig will permit removal */
int(*if_watchdog) /* timer routine */
__P((int));
struct ifqueue {
struct mbuf *ifq_head;
struct mbuf *ifq_tail;
int ifq_len;
int ifq_maxlen;
int ifq_drops;
} if_snd; /* output queue * (4)
};
#define if_mtu if_data.ifi_mtu (5)
#define if_type if_data.ifi_type (6)
#define if_addrlen if_data.ifi_addrlen
#define if_hdrlen if_data.ifi_hdrlen
#define if_metric if_data.ifi_metric (7)
#define if_baudrate if_data.ifi_baudrate
#define if_ipackets if_data.ifi_ipackets
#define if_ierrors if_data.ifi_ierrors
#define if_opackets if_data.ifi_opackets
#define if_oerrors if_data.ifi_oerrors
#define if_collisions if_data.ifi_collisions
#define if_ibytes if_data.ifi_ibytes
#define if_obytes if_data.ifi_obytes
#define if_imcasts if_data.ifi_imcasts
#define if_omcasts if_data.ifi_omcasts
#define if_iqdrops if_data.ifi_iqdrops
#define if_noproto if_data.ifi_noproto
#define if_lastchange if_data.ifi_lastchange
-
现在假设有两个以太网接口,则:
两个以太网接口的 if_name 均为 ens。
第一个以太网接口的 if_unit 为 0,第二个以太网接口的 if_unit 为 1.
因此在 ifconfig 的显示中,第一个以太网接口显示为 ens0,第二个以太网接口显示为 ens1。 if_index 在内核中唯一地标示了接口。
if_addr 链表。
以秒为单位记录时间,内核为此接口调用 if_watchdog 后归零。此函数用于收集接口统计或者是复位硬件。
接口分组输出队列。
mtu。对于以太网时 1500。
if_type 表明了接口支持的硬件地址类型:
Table 1. if_types.h if_type 作用 IFT_ETHER
CSMA/CD 以太网
IFT_IOS88023
IEEE 802.3 以太网
IFT_ISO88025
IEEE 802.5 令牌环网
IFT_LOOP
环回接口
if_metric 通常为零。更大的值不利于通过从接口。
在 Net/3 中注释 /* XXX */ 用于提醒读者这段代码时易混淆的。包括不明确的副作用,或者一个更难问题的解决方案。 |
if_flags
if_flags 的详细定义如下:
#define IFF_UP 0x1 /* interface is up */
#define IFF_BROADCAST 0x2 /* broadcast address valid */
#define IFF_DEBUG 0x4 /* turn on debugging */
#define IFF_LOOPBACK 0x8 /* is a loopback net */
#define IFF_POINTOPOINT 0x10 /* interface is point-to-point link */
#define IFF_NOTRAILERS 0x20 /* obsolete: avoid use of trailers */
#define IFF_RUNNING 0x40 /* resources allocated */
#define IFF_NOARP 0x80 /* no address resolution protocol */
#define IFF_PROMISC 0x100 /* receive all packets */
#define IFF_ALLMULTI 0x200 /* receive all multicast packets */
#define IFF_OACTIVE 0x400 /* transmission in progress */
#define IFF_SIMPLEX 0x800 /* can't hear own transmissions */
#define IFF_LINK0 0x1000 /* per link layer defined bit */
#define IFF_LINK1 0x2000 /* per link layer defined bit */
#define IFF_LINK2 0x4000 /* per link layer defined bit */
#define IFF_ALTPHYS IFF_LINK2 /* use alternate physical connection */
#define IFF_MULTICAST 0x8000 /* supports multicast */
在现代 Linux 中,需要区别 IFF_UP 和 IFF_RUNNING。IFF_UP 代表这个端口是可用的,而 IFF_RUNNING 代表这个端口是可用的且已经连接了网线。一个有意思的内容是 docker 网卡只设置了 IFF_UP 而没有设置 IFF_RUNNING。 |
ifaddr 结构
ifaddr 结构的定义要简洁很多:
struct ifaddr {
struct sockaddr *ifa_addr; /* address of interface */
struct sockaddr *ifa_dstaddr; /* other end of p-to-p link */
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
struct sockaddr *ifa_netmask; /* used to determine subnet */
struct ifnet *ifa_ifp; /* back-pointer to interface */
struct ifaddr *ifa_next; /* next address for interface */
void (*ifa_rtrequest)(); /* check or clean routes (+ or -)'d */
u_short ifa_flags; /* mostly rt_flags for cloning */
short ifa_refcnt; /* extra to malloc for link info */ (1)
int ifa_metric; /* cost of going out this interface */
#ifdef notdef
struct rtentry *ifa_rt; /* XXXX for ROUTETOIF ????? */
#endif
};
引用计数
在 ifnet-ifaddr 中曾经提到过 ifaddr 是一个链表结构。
sockaddr 结构及其特化
一个接口的地址信息不仅包括了主机地址。Net/3 使用通用的 sockaddr 保存主机地址、广播地址和网络掩码。通过使用一个通用结构,将硬件和协议专用地址细节相对接口层隐藏起来。
下面时 sockaddr 的定义:
/*
* Structure used by kernel to store most
* addresses.
*/
struct sockaddr {
u_char sa_len; /* total length */ (1)
u_char sa_family; /* address family */ (2)
char sa_data[14]; /* actually longer; address value */ (3)
};
结构体总大小。
地址族类型:
sa_family 协议 AF_INET
Internet
AF_ISO, AF_OSI
OSI
AF_UNIX
Unix 域
AF_ROUTE
路由表
AF_LINK
数据链路
AF_UNSPEC
如果地址长度大于 14B,则 sa_data 最高可以拓展到 253 字节(255 - sa_len - sa_family)。
在 C 语言中,如果最后一个成员为 buffer 对象,则可以将其视为不定长对象。
sockaddr 本是是一个通用结构。此后,每个协议对其进行特化,创建自己需要的 sockaddr 对象。
网络初始化
下面演示了内核启动时网络初始化相关的步骤:
void main(void *framep) {
// ...
cpu_startup(); (1)
// ...
/* Attach pseudo-devices. */
for (pdev = pdevinit; pdev->pdev_attach != NULL; pdev++) (2)
(*pdev->pdev_attach)(pdev->pdev_count);
/*
* Initialize protocols. Block reception of incoming packets
* until everything is ready.
*/
s = splimp();
ifinit(); (3)
domaininit(); (4)
splx(s);
// ...
/* The scheduler is an infinite loop. */
scheduler();
/* NOTREACHED */
}
查找并初始化所有硬件设备。
pdevinit 中包含了 loopback 这样的伪接口。其完全由软件实现。
初始化网络接口。
初始化协议。
环回初始化
下面是环回接口的初始化过程:
#define LOMTU (1024 + 512)
void loopattach(int n) {
struct ifnet *ifp = &loif;
ifp->if_name = "lo";
ifp->if_mtu = LOMTU; (1)
ifp->if_flags = IFF_LOOPBACK | IFF_MULTICAST; (2)
ifp->if_ioctl = loioctl;
ifp->if_output = looutput;
ifp->if_type = IFT_LOOP;
ifp->if_hdrlen = 0; (3)
ifp->if_addrlen = 0; (4)
if_attach(ifp);
bpfattach(&ifp->if_bpf, ifp, DLT_NULL, sizeof(u_int)); (5)
}
将 mtu 设置为 1536B。
环回接口允许多播。
loopback 没有链路首部。
loopback 没有硬件地址。
向 bfp 注册 loopback 接口。
if_attach
if_attach 源码如下:
struct ifaddr **ifnet_addrs = NULL;
void if_attach(struct ifnet *ifp) {
unsigned socksize, ifasize;
int namelen, unitlen, masklen, ether_output();
char workbuf[12], *unitname;
register struct ifnet **p = &ifnet;
register struct sockaddr_dl *sdl;
register struct ifaddr *ifa;
static int if_indexlim = 8;
extern void link_rtrequest();
while (*p)
p = &((*p)->if_next);
*p = ifp;
ifp->if_index = ++if_index;
if (ifnet_addrs == NULL || if_index >= if_indexlim) { (1)
unsigned n = (if_indexlim <<= 1) * sizeof(ifa);
struct ifaddr **q = (struct ifaddr **)malloc(n, M_IFADDR, M_WAITOK);
if (ifnet_addrs) {
bcopy((caddr_t)ifnet_addrs, (caddr_t)q, n / 2);
free((caddr_t)ifnet_addrs, M_IFADDR);
}
ifnet_addrs = q;
}
/*
* create a Link Level name for this device
*/
unitname = sprint_d((u_int)ifp->if_unit, workbuf, sizeof(workbuf)); (2)
namelen = strlen(ifp->if_name);
unitlen = strlen(unitname);
#define _offsetof(t, m) ((int)((caddr_t) & ((t *)0)->m))
masklen = _offsetof(struct sockaddr_dl, sdl_data[0]) +
unitlen + namelen;
socksize = masklen + ifp->if_addrlen;
#define ROUNDUP(a) (1 + (((a)-1) | (sizeof(long) - 1)))
socksize = ROUNDUP(socksize);
if (socksize < sizeof(*sdl))
socksize = sizeof(*sdl);
ifasize = sizeof(*ifa) + 2 * socksize;
if (ifa = (struct ifaddr *)malloc(ifasize, M_IFADDR, M_WAITOK)) { (3)
bzero((caddr_t)ifa, ifasize);
sdl = (struct sockaddr_dl *)(ifa + 1);
sdl->sdl_len = socksize;
sdl->sdl_family = AF_LINK;
bcopy(ifp->if_name, sdl->sdl_data, namelen); (4)
bcopy(unitname, namelen + (caddr_t)sdl->sdl_data, unitlen);
sdl->sdl_nlen = (namelen += unitlen);
sdl->sdl_index = ifp->if_index;
sdl->sdl_type = ifp->if_type;
ifnet_addrs[if_index - 1] = ifa;
ifa->ifa_ifp = ifp;
ifa->ifa_next = ifp->if_addrlist;
ifa->ifa_rtrequest = link_rtrequest; (5)
ifp->if_addrlist = ifa;
ifa->ifa_addr = (struct sockaddr *)sdl;
sdl = (struct sockaddr_dl *)(socksize + (caddr_t)sdl);
ifa->ifa_netmask = (struct sockaddr *)sdl;
sdl->sdl_len = masklen;
while (namelen != 0)
sdl->sdl_data[--namelen] = 0xff;
}
/* XXX -- Temporary fix before changing 10 ethernet drivers */
if (ifp->if_output == ether_output)
ether_ifattach(ifp);
}
如果 ifnet_addrs 没有初始化,则对其进行初始化。如果容量不足,则分配二倍大小的 buffer,并将旧数据复制到新数组中。
这里的 malloc 和 free 并不是 libc 中的函数。而是内核专用的。 将接口的 unit 值转为字符串。然后计算 if_name + unit 串的长度。
如果分配内存,忽略代码。
依次拼接 if_name 和 unit,并将其放在 sdl_data 中,将长度放在 sdl_nlen 中。接口的索引放在 sdl_index 中。
以太网使用 arp_rtrequest 取代 link_rtrequest,loopback 使用 loop_rtrequest。
ifinit
接口结构被初始化并链接在一起后,main 函数调用 ifinit:
void ifinit() {
register struct ifnet *ifp;
for (ifp = ifnet; ifp; ifp = ifp->if_next)
if (ifp->if_snd.ifq_maxlen == 0) (1)
ifp->if_snd.ifq_maxlen = ifqmaxlen;
if_slowtimo(0); (2)
}
如果接口的 maxlen 没有初始化,则设置为 50。
启动接口的 watchdog 定时器。如果定时器到期,内核回调用这个接口的监视定时器函数。
以太网输入
以太网处理输入数据帧的流程如下:
以太网接受发送向它的单播地址和以太网广播地址的帧。当一个完整的帧可用时,接口会产生一个中断,然后内核调用 leintr。
leintr 会调用 leread 借助 m_devget 将数据读到 mbuf 中。