BSD net源码分析(2-2)

来源:互联网 发布:python init 函数 编辑:程序博客网 时间:2024/06/05 16:51

二、以太网接口接收数据帧
当接口接收到发送给该接口的单播地址和广播地址的帧,帧可用时,触发一个中断,并且内核调用驱动函数函数leintr()。
leintr()函数内部根据硬件寄存器的值,判断是什么中断,如果是LE_RINT状态,调用lerint处理接收的数据,如果是LE_TINT,调用lexint做输出数据处理。
其他的状态,程序会更新接口的统计,并调用lereset重设接口寄存器的值和状态。
以太网接口接收数据中断,触发调用函数lerint(),该函数主要处理接收到的数据,并丢弃错误的数据,同时更新统计值,有效数据通过调用函数leread交到enther_input函数做后续处理。
leread的输入参数包括数据内容buf,长度len。以太网的头部(ether_header结构存放)存放再buf的首部。
leread获取到以太网的数据的头部,判断ether_type类型,并要区别802和以太网包的不同格式,获取数据的长度和内容。判断是否广播数据和多播数据,并置flag,如果给接口有bpf回调,同时调用。
最后leread调用m_devget函数获取一个mbuf,并把数据放入mbuf,但以太网的首部未放入,而时通过ether_input函数的参数带入。
/******************************************************/
/*
* Pull packet off interface.  Off is nonzero if packet
* has trailing header; m_devget will then force this header
* information to be at the front, but we still have to drop
* the type and length which are at the front of any trailer data.
*/
m = m_devget((char *)(et + 1), len, off, &le->sc_if, 0);
if (m == 0)
return;
m->m_flags |= flags;
ether_input(&le->sc_if, et, m);
/******************************************************/
ether_input主要完成对广播和多播包的识别以及链路层数据的分发处理。
(1)通过以太网地址识别包的类型,并置mbuf的m_flags标记。
(2)通过以太网头部的ether_type分发不同类型的数据包。
对于ETHERTYPE_IP包,调用schednetisr(NETISR_IP)软中断,并选择ipintrp队列输入数据。
对ETHERTYPE_ARP包,调用schednetisr(NETISR_ARP)软中断,并选择arpintrp队列输入数据。
对ETHERTYPE_NS包,调用schednetisr(NETISR_NS)软中断,并选择nsintrp队列输入数据。
对其他的包可能时ISO封装的802包做相应的处理。
函数的最后根据选择的队列,将数据入队。

三、以太网接口数据输出
当网络层协议调用接口ifnet结构体中的if_output时,开始输出。所有以太网设备的if_output都指向ether_output函数,该函数封装以太网的头部,并将数据输入到接口的发送队列。
(1)验证接口状态:主要是接口状态的校验,判断接口是否启用。
/******************************************************/
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
senderr(ENETDOWN);
ifp->if_lastchange = time;
/*****************************************************/
(2)输出路由处理:包括主机路由和网关路由的处理,避免ARP泛滥处理
/*****************************************************/
if (rt = rt0) {
if ((rt->rt_flags & RTF_UP) == 0) {
if (rt0 = rt = rtalloc1(dst, 1))
rt->rt_refcnt–;
else
senderr(EHOSTUNREACH);
}
if (rt->rt_flags & RTF_GATEWAY) {
if (rt->rt_gwroute == 0)
goto lookup;
if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) {
rtfree(rt); rt = rt0;
lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1);
if ((rt = rt->rt_gwroute) == 0)
senderr(EHOSTUNREACH);
}
}
if (rt->rt_flags & RTF_REJECT)
if (rt->rt_rmx.rmx_expire == 0 ||
time.tv_sec < rt->rt_rmx.rmx_expire)
senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
}
/*****************************************************/
rt0 上层协议(IP ip_output)找到的路由信息,该值也可能为空,此时将跳过路由验证部分直接进入分组处理。
如果输入的路由无效,参考路由表,获取一条有效的路由,rt0 = rt = rtalloc1(dst, 1)。
如果下一跳是网关,找到一条到网关的路由。
当路由的结果是RTF_REJECT,表示对方不准备响应一个ARP,此时丢弃改分组。

(3)分组的按不同的协议处理:
IP分组处理:
/***************************************************/
case AF_INET:
if (!arpresolve(ac, rt, m, dst, edst))
return (0);    /* if not yet resolved */
/* If broadcasting on a simplex interface, loopback a copy */
if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX))
mcopy = m_copy(m, 0, (int)M_COPYALL);
off = m->m_pkthdr.len – m->m_len;
type = ETHERTYPE_IP;
break;
/***************************************************/
如果dst_safamily地址族是AF_INET,表示该分组携带IP协议数据。
首 先,通过arpresolv函数查找IP地址到以太网地址的对应。如果ARP缓存中存在地址对应,返回1,否则,该IP分组将被ARP程序接管,直道 ARP知道获取到具体的物理地址后,通过in_arpintr调用ether_output重新发送分组。ARP具体的实现,后面会有详细介绍。
如果数据是广播分组且接口是单向的,需要复制一份分组,并在后面将分组输入到接口的接收队列。

显式输出分组处理:
/********************************************************/
case AF_UNSPEC:
eh = (struct ether_header *)dst->sa_data;
bcopy((caddr_t)eh->ether_dhost, (caddr_t)edst, sizeof (edst));
type = eh->ether_type;
break;
/*********************************************************/
地址族是AF_UNSPEC,表示某些显式的分组输出,由调用者显式的指定目标的物理地址,类别字段为接口首部的地址类别。

除了以上的两种类型的分组外,还有ISO和AF_NS类别的分组,在这里不详细介绍。

如果之前有拷贝分组,在对不同类型的分组处理完后调用
if (mcopy)
(void) looutput(ifp, mcopy, dst, rt);
将分组输入到接口输入队列,具体的实现在loop接口代码分析时详细解析。

(4)帧构造:构造以太网的数据帧。
/***************************************************/
/*
* Add local net header.  If no space in first mbuf,
* allocate another.
*/
M_PREPEND(m, sizeof (struct ether_header), M_DONTWAIT);
if (m == 0)
senderr(ENOBUFS);
eh = mtod(m, struct ether_header *);
type = htons((u_short)type);
bcopy((caddr_t)&type,(caddr_t)&eh->ether_type,
sizeof(eh->ether_type));
bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst));
bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost,
sizeof(eh->ether_shost));
/******************************************************/
构造帧主要的工作是构造以太网的帧首部。

(5)分组入接口输出队列
/******************************************************/
s = splimp();
/*
* Queue message on interface, and start output if interface
* not yet active.
*/
if (IF_QFULL(&ifp->if_snd)) {
IF_DROP(&ifp->if_snd);
splx(s);
senderr(ENOBUFS);
}
IF_ENQUEUE(&ifp->if_snd, m);
if ((ifp->if_flags & IFF_OACTIVE) == 0)
(*ifp->if_start)(ifp);
splx(s);
ifp->if_obytes += len + sizeof (struct ether_header);
if (m->m_flags & M_MCAST)
ifp->if_omcasts++;
return (error);
/******************************************************/
最后部分是将分组入输出队列,如果队列满了就丢弃该分组,并返回没有缓存的错误码。
入队后,判断接口是否激活,如果未激活,需要调用if_start实例启动接口的输出。
函数的末尾更新接口统计信息并返回。

if_start指向具体设备的驱动函数,如lestart函数。
lestart首先检查接口是后工作,如果不工作直接返回。
/*********************************************/
tmd = &le->sc_r2->ler2_tmd[le->sc_tmd];
do {
if (tmd->tmd1 & LE_OWN) {
le->sc_xown2++;
return (0);
}
IF_DEQUEUE(&le->sc_if.if_snd, m);
if (m == 0)
return (0);
len = leput(le->sc_r2->ler2_tbuf[le->sc_tmd], m);
#if NBPFILTER > 0
/*
* If bpf is listening on this interface, let it
* see the packet before we commit it to the wire.
*/
if (ifp->if_bpf)
bpf_tap(ifp->if_bpf, le->sc_r2->ler2_tbuf[le->sc_tmd],
len);
#endif

tmd->tmd3 = 0;
tmd->tmd2 = -len;
tmd->tmd1 = LE_OWN | LE_STP | LE_ENP;
if (++le->sc_tmd == LETBUF) {
le->sc_tmd = 0;
tmd = le->sc_r2->ler2_tmd;
} else
tmd++;
} while (++le->sc_txcnt < LETBUF);
le->sc_if.if_flags |= IFF_OACTIVE;
return (0);
/*********************************************/
数据退队:一旦开始数据输出,函数会循环的从输出队列中退队并写入硬件缓存中。
bpf输出:如果接口注册了bpf,此时也会调用bpf_tap扔出数据。
输出结束:当输出队列为空或者接口发送的数据包等于接口的最大输出缓存时,会跳出循环,并将接口置为输出激活状态。

到此,完成了一个数据分组输出的全过程

四、以太网接口的配置
系统提供了系统调用ioctl函数为一个进程访问一个设备的标准系统所不支持的特性。

int ioctl(int fd, unsigned long com,…);
fd 是一个描述符,通常是一个设备和网络连接,com 指示ioctl函数所要执行的命令类型,第三个参数是可变的,由第二个参数决定他的内容。
进程通过指定接口的fd,访问接口的特性。以下是网络接口所使用的几个命令以及对应的最终的调用函数。
命令        第三个参数       函数       说明
SIOCGIFCONF   struct ifconf *   ifconf    获取接口配置清单
SIOCGIFFLAGS  struct ifreq *    ifioctl   获得接口标志
SIOCGIFMETRIC struct ifreq *    ifioctl   获得接口度量
SIOCSIFFLAGS  struct ifreq *    ifioctl   设置接口标志
SIOCSIFMETRIC struct ifreq *    ifioctl   设置接口度量
下面具体介绍一下不同命令的具体处理。

(一)ifioctl函数,ioctl通过将cmd传给ifioctl实现接口的特性访问。
(1)SIOCGIFCONF,OSIOCGIFCONF
通过调用函数ifconf返回一个可变长的ifreq结构的表。
/**********************************/
switch (cmd) {

case SIOCGIFCONF:
case OSIOCGIFCONF:
return (ifconf(cmd, data));
}
/**********************************/
(2)其他命令
传入的参数是一个指向ifreq的指针。ifunit通过接口名查找接口的ifnet指针,如果未找到,返回ENXIO。
/*****************************************/
ifr = (struct ifreq *)data;
ifp = ifunit(ifr->ifr_name);
if (ifp == 0)
return (ENXIO);
/*****************************************/

SIOCGIFFFLAGS 获取接口的标记,并通过传入的指针携带结果返回。
SIOCGIFMETRIC 获取接口的度量,通过传入的地址带回结果
/*****************************************/
case SIOCGIFFLAGS:
ifr->ifr_flags = ifp->if_flags;
break;
case SIOCGIFMETRIC:
ifr->ifr_metric = ifp->if_metric;
break;
/*****************************************/

SIOCSIFFLAGS 配置接口的标记
SIOCSIFMETRIC 配置接口的度量
接口的配置需要超级权限的用户才能执行,通过suser函数判断是否超级用户。
对SIOCSIFFLAGS 有些标记不能被进程配置,通过忽略标志IFF_CANTCHAGE。
表达式(ifp->if_flags&IFF_CANTCHANGE)清除能被进程改变的接口标志,而表达式(fr->ifr_flags&~IFF_CANTCHANGE)清除在请求中不被进程改变的标志。
最后通过ifp_ioctl指针调用指向设备专用的ioctl函数设置标记。根据不同的标记对接口做不同的操作,这里不详细介绍。
/**************************************************/
case SIOCSIFFLAGS:
if (error = suser(p->p_ucred, &p->p_acflag))
return (error);
if (ifp->if_flags & IFF_UP && (ifr->ifr_flags & IFF_UP) == 0) {
int s = splimp();
if_down(ifp);
splx(s);
}
if (ifr->ifr_flags & IFF_UP && (ifp->if_flags & IFF_UP) == 0) {
int s = splimp();
if_up(ifp);
splx(s);
}
ifp->if_flags = (ifp->if_flags & IFF_CANTCHANGE) |
(ifr->ifr_flags &~ IFF_CANTCHANGE);
if (ifp->if_ioctl)
(void) (*ifp->if_ioctl)(ifp, cmd, data);
break;

case SIOCSIFMETRIC:
if (error = suser(p->p_ucred, &p->p_acflag))
return (error);
ifp->if_metric = ifr->ifr_metric;
break;
/**************************************************/

如果接口ioctl命令不能被识别, ifioctl把命令发送给与所请求插口关联的协议的用户要求函数。
/************************************************/
default:
if (so->so_proto == 0)
return (EOPNOTSUPP);
return ((*so->so_proto->pr_usrreq)(so, PRU_CONTROL,
cmd, data, ifp));
/*************************************************/

(2)ifconf函数,为进程提供一个标准的方法发现一个系统中的接口和配置地址。
该函数的返回结果采用两个结构存储内容
/**************************************************/
/*
* Structure used in SIOCGIFCONF request.
* Used to retrieve interface configuration
* for machine (useful for programs which
* must know all networks accessible).
*/
struct    ifconf {
int    ifc_len;        /* size of associated buffer */
union {
caddr_t    ifcu_buf;
struct    ifreq *ifcu_req;
} ifc_ifcu;
#define    ifc_buf    ifc_ifcu.ifcu_buf    /* buffer address */
#define    ifc_req    ifc_ifcu.ifcu_req    /* array of structures returned */
};
/*
* Interface request structure used for socket
* ioctl’s.  All interface ioctl’s must have parameter
* definitions which begin with ifr_name.  The
* remainder may be interface specific.
*/
struct    ifreq {
#define    IFNAMSIZ    16
char    ifr_name[IFNAMSIZ];        /* if name, e.g. “en0″ */
union {
struct    sockaddr ifru_addr;
struct    sockaddr ifru_dstaddr;
struct    sockaddr ifru_broadaddr;
short    ifru_flags;
int    ifru_metric;
caddr_t    ifru_data;
} ifr_ifru;
#define    ifr_addr    ifr_ifru.ifru_addr    /* address */
#define    ifr_dstaddr    ifr_ifru.ifru_dstaddr    /* other end of p-to-p link */
#define    ifr_broadaddr    ifr_ifru.ifru_broadaddr    /* broadcast address */
#define    ifr_flags    ifr_ifru.ifru_flags    /* flags */
#define    ifr_metric    ifr_ifru.ifru_metric    /* metric */
#define    ifr_data    ifr_ifru.ifru_data    /* for use by interface */
};
/**************************************************/
调用时,ifconf的成员ifc_len表示ifc_buf的长度,函数返回时,内容用ifreq结构填充。
ifreq的成员ifru_data的长度是可变的,他存放不同类型接口的地址sockaddr,不同类型的地址长度不一样。

到此,关于以太网接口的所有函数都以及粗略的做了介绍。

原创粉丝点击