1.中心环节
IP是整个协议软件的中心环节,不要把IP软件简单的分割成输入和输出两部分,因为输入和输出的界限很模糊.以下把重点放在网关上.
2. IP软件设计思想
* 统一的输入队列以及统一的选路过程
* 独立的IP进程
* 本地主机接口
3. IP软件结构和数据报流程
4. IP软件结构和数据报流程
4.1 选择传入数据报的策略
挑选数据报并为其选择路由的IP程序段实现了一个重要策略:它决定了数据报来源的相对优先级.
公平分配优先权,例用策略为循环法(round-robin)
/* ipgetp.c – ipgetp */
#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>
static int ifnext = NI_LOCAL; //接口索引
/*————————————————————————
* ipgetp — choose next IP input queue and extract a packet
*————————————————————————
*/
struct ep *
ipgetp(int *pifnum)
{
struct ep *pep; //以太帧结构体
int i;
recvclr(); /* 确保没有旧分组正在等待 */
while (TRUE) {
for (i=0; i < Net.nif; ++i, ++ifnext) { //遍历每个接口
if (ifnext >= Net.nif) //等于或大于最大接口数
ifnext = 0;
if (nif[ifnext].ni_state == NIS_DOWN) //此接口关闭
continue;
if (pep = NIGET(ifnext)) { /*NIGET宏,提取并返回第一个数据*/
*pifnum = ifnext; //返回接口的序号
return pep; //返回以太帧的结构体指针
}
}
ifnext = receive(); //如没有数据则阻塞,当有数据到达时,返回数据报到达的接口指向
}
/* can't reach here */
}
4.2 IP使用的常量定义
/* ip.h – IP_HLEN, IP_CLASS{A,B,C,D,E} */
/* 互联网协议(IP)的常量和数据报格式 */
#define IP_ALEN 4 /* 以字节为单位的IP地址的长度(字节) */
typedef unsigned long IPaddr; /* 互联网地址 */
#if BYTE_ORDER == BIG_ENDIAN
#define IP_CLASSA(x) (((x) & 0x80000000) == 0) /* A类的IP */
#define IP_CLASSB(x) (((x) & 0xc0000000) == 0x80000000) /* B类的IP */
#define IP_CLASSC(x) (((x) & 0xe0000000) == 0xc0000000) /* C类的IP */
#define IP_CLASSD(x) (((x) & 0xf0000000) == 0xe0000000) /* D类的IP */
#define IP_CLASSE(x) (((x) & 0xf8000000) == 0xf0000000) /* E类的IP */
#else /* BYTE_ORDER */
#define IP_CLASSA(x) (((x) & 0x80) == 0x00) /* A类的IP地址 */
#define IP_CLASSB(x) (((x) & 0xc0) == 0x80) /* B类的IP地址 */
#define IP_CLASSC(x) (((x) & 0xe0) == 0xc0) /* C类的IP地址 */
#define IP_CLASSD(x) (((x) & 0xf0) == 0xe0) /* D类的IP地址 */
#define IP_CLASSE(x) (((x) & 0xf8) == 0xf0) /* E类的IP地址 */
#endif /* BYTE_ORDER */
/* 部分分配协议编号 */
#define IPT_ICMP 1 /* ICMP数据包协议类型 */
#define IPT_IGMP 2 /* IGMP数据包协议类型 */
#define IPT_TCP 6 /* TCP数据包协议类型 */
#define IPT_EGP 8 /* EGP数据包协议类型 */
#define IPT_UDP 17 /* UDP数据包的协议类型 */
#define IPT_OSPF 89 /* OSPF数据包协议类型 */
struct ip {
u_char ip_verlen; /* IP版本及头长度 (in longs)*/
u_char ip_tos; /* 服务类型 */
u_short ip_len; /* 包总长度(字节) */
short ip_id; /* 数据包的id */
short ip_fragoff; /* 碎片偏移(8字节的) */
u_char ip_ttl; /* 生存时间,在网关的跳 */
u_char ip_proto; /* IP协议(见IPT_ *)*/
short ip_cksum; /* 头校验和 */
IPaddr ip_src; /* 源IP地址 */
IPaddr ip_dst; /* 目的地IP地址 */
u_char ip_data[1]; /* 可变长度的数据 */
};
#define IP_VERSION 4 /* 当前版本值 */
#define IP_MINHLEN 5 /* 最小的IP头长度 (in longs) */
#define IP_TTL 255 /* 生存时间初始值 */
#define IP_MF 0x2000 /* 更多的碎片位 */
#define IP_DF 0x4000 /* 不分片位 */
#define IP_FRAGOFF 0x1fff /* 碎片偏移 fragment offset mask*/
#define IP_PREC 0xe0 /* 优先的服务 precedence portion of TOS*/
/* IP优先值 */
#define IPP_NETCTL 0xe0 /* 网络控制 */
#define IPP_INCTL 0xc0 /* Internet控制 */
#define IPP_CRIT 0xa0 /* 关键 critical */
#define IPP_FLASHO 0x80 /* flash over-ride */
#define IPP_FLASH 0x60 /* flash */
#define IPP_IMMED 0x40 /* immediate */
#define IPP_PRIO 0x20 /* 优先 */
#define IPP_NORMAL 0x00 /* 正常 */
/*宏来计算一个数据报的报头的长度(字节) */
#define IP_HLEN(pip) ((pip->ip_verlen & 0xf)<<2)
#define IPMHLEN 20 /* 最小的IP头长度(字节) */
/* IP选项 */
#define IPO_COPY 0x80 /* 分片复制 copy on fragment mask */
#define IPO_CLASS 0x60 /* 选项类 option class */
#define IPO_NUM 0x17 /* 选项编号option number */
#define IPO_EOOP 0x00 /* 选项结束 */
#define IPO_NOP 0x01 /* 没有操作 no operation */
#define IPO_SEC 0x82 /* DoD security/compartmentalization */
#define IPO_LSRCRT 0x83 /* 松散源路由 loose source routing */
#define IPO_SSRCRT 0x89 /* 严格的源路由strict source routing */
#define IPO_RECRT 0x07 /* 记录路线 */
#define IPO_STRID 0x88 /* 流编号 */
#define IPO_TIME 0x44 /* 互联网时间戳 */
#define IP_MAXLEN BPMAXB-EP_HLEN /* IP数据报的最大长度 */
/*IP进程的信息 */
extern PROCESS ipproc();
#define IPSTK 512 /* IP过程堆栈大小 */
#define IPPRI 100 /* IP运行在高优先级 */
#define IPNAM "ip" /* IP过程的名字 */
#define IPARGC 0 /* count of args to IP */
extern IPaddr ip_maskall; /* = 255.255.255.255 */
extern IPaddr ip_anyaddr; /* = 0.0.0.0 */
extern IPaddr ip_loopback; /* = 127.0.0.1 */
extern int ippid, gateway;
struct ip *iph2net(struct ip *), *ipnet2h(struct ip *);
unsigned short cksum(WORD *, unsigned);
int ipsend(IPaddr, struct ep *, unsigned, u_char, u_char, u_char);
int ipputp(unsigned, IPaddr, struct ep *);
Bool isbrc(IPaddr);
4.3 IP进程的结构 (IP进程允许被阻塞)
/* ipproc.c – ipproc */
#include <conf.h>
#include <kernel.h>
#include <network.h>
struct ep *ipgetp(int *);
struct route *rtget(IPaddr, Bool);
/*————————————————————————
* ipproc – 处理一个从网络中来的IP数据包
*————————————————————————
*/
PROCESS
ipproc(void)
{
struct ep *pep;
struct ip *pip;
struct route *prt;
Bool nonlocal;
int ifnum;
ippid = getpid(); /* 得到进程的ID */
signal(Net.sema); /* 信号进行初始化*/
while (TRUE) {
pep = ipgetp(&ifnum); /* 选取一个以太网数据报*/
pip = (struct ip *)pep->ep_data; /* 得到此报包含的更高级报地址*/
if ((pip->ip_verlen>>4) != IP_VERSION) { /* 判断是否为IP数据报*/
IpInHdrErrors++;
freebuf(pep);
continue;
}
if (IP_CLASSE(pip->ip_dst)) { /* 判断是否为E类地址*/
IpInAddrErrors++;
freebuf(pep);
continue;
}
if (ifnum != NI_LOCAL) { /* 此报不是本机产生的*/
if (cksum((WORD *)pip, IP_HLEN(pip))) { /* 计算检验和*/
IpInHdrErrors++;
freebuf(pep);
continue;
}
ipnet2h(pip);
pep->ep_order |= EPO_IP;
}
prt = rtget(pip->ip_dst, (ifnum == NI_LOCAL)); /* 选择路由*/
if (prt == NULL) { /* 如果路由不存在*/
if (gateway) { /* 如网关存在发送ICMP不可达*/
iph2net(pip);
pep->ep_order &= ~EPO_IP;
icmp(ICT_DESTUR, ICC_NETUR,
pip->ip_src, pep, 0);
} else {
IpOutNoRoutes++;
freebuf(pep);
}
continue;
}
nonlocal = ifnum != NI_LOCAL && prt->rt_ifnum != NI_LOCAL; /* 不是本地地址*/
if (!gateway && nonlocal) { /* 不是网关也不是本地地址*/
IpInAddrErrors++;
freebuf(pep);
rtfree(prt);
continue;
}
if (nonlocal)
IpForwDatagrams++;
/* 如果我们发件人,填写在源IP */
if (ifnum == NI_LOCAL) { /* 如果网络接口为本地*/
if (pip->ip_src == ip_anyaddr)
if (prt->rt_ifnum == NI_LOCAL) /* 路由的接口为本地*/
pip->ip_src = pip->ip_dst;
else
pip->ip_src =
nif[prt->rt_ifnum].ni_ip;
} else if (–(pip->ip_ttl) == 0 && /* ttl 为零,且路由接口不为本地*/
prt->rt_ifnum != NI_LOCAL) {
IpInHdrErrors++;
iph2net(pip);
pep->ep_order &= ~EPO_IP;
icmp(ICT_TIMEX, ICC_TIMEX, pip->ip_src, pep, 0); /*发送ICMP超时报文*/
rtfree(prt);
continue;
}
ipdbc(ifnum, pep, prt); /* 处理定向广播 */
ipredirect(pep, ifnum, prt); /* 做重定向,如果需要的话*/
if (prt->rt_metric != 0)
ipputp(prt->rt_ifnum, prt->rt_gw, pep);
else
ipputp(prt->rt_ifnum, pip->ip_dst, pep);
rtfree(prt);
}
}
int ippid, gateway, bsdbrc;
4.4 校验和的计算
/* cksum.c – cksum */
/*————————————————————————
* cksum – Return 16-bit ones complement of 16-bit ones complement sum
*————————————————————————
*/
unsigned short
cksum(buf, nbytes)
unsigned short *buf;
int nbytes;
{
unsigned long sum;
unsigned short tmp;
int nwords;
nwords = nbytes / 2; /* 将字节转成字,也就是16个的个数 */
for (sum=0; nwords>0; nwords–) /* 将16位为单位做累加*/
sum += *buf++;
if (nbytes & 1) { /* 如长度不是16位的倍数则补齐*/
tmp = *(unsigned char *)buf;
sum += tmp;
}
sum = (sum >> 16) + (sum & 0xffff); /* 高位低位相加 */
sum += (sum >> 16); /* 上一步溢出时,将溢出位也加到sum中 */
return (unsigned short)~sum; /* 返回sum的反码*/
}
4.5 处理定向广播
主要做了两件事,复制一个副本,然后正常发送
/* ipdbc.c – ipdbc */
#include <conf.h>
#include <kernel.h>
#include <network.h>
struct route *rtget(IPaddr, Bool);
/*————————————————————————
* ipdbc – handle IP directed broadcast copying
*————————————————————————
*/
void
ipdbc(unsigned ifnum, struct ep *pep, struct route *prt)
{
struct ip *pip = (struct ip *)pep->ep_data;
struct ep *pep2;
struct route *prt2;
int len;
if (prt->rt_ifnum != NI_LOCAL)
return; /* 不是我们 */
if (!isbrc(pip->ip_dst))
return; /* 不是广播 */
prt2 = rtget(pip->ip_dst, RTF_LOCAL); /* 选择一个路由 */
if (prt2 == NULL)
return;
if (prt2->rt_ifnum == ifnum) { /* 没有针对接口 */
rtfree(prt2);
return;
}
/* 定向广播,建立一个副本 */
/* len = ether header + IP packet */
len = EP_HLEN + pip->ip_len;
if (len > EP_MAXLEN) /* 为副本分配缓冲区*/
pep2 = (struct ep *)getbuf(Net.lrgpool);
else
pep2 = (struct ep *)getbuf(Net.netpool);
if (pep2 == (struct ep *)SYSERR) {
rtfree(prt2);
return;
}
memcpy(pep2, pep, len); /* 复制副本*/
/* 发送一份拷贝到网络 */
ipputp(prt2->rt_ifnum, pip->ip_dst, pep2);
rtfree(prt2);
return; /* continue; "pep" goes locally in IP */
}
4.6 识别一个广播地址
/* isbrc.c – isbrc */
#include <conf.h>
#include <kernel.h>
#include <sleep.h>
#include <network.h>
/*————————————————————————
* isbrc – Is "dest" a broadcast address?
*————————————————————————
*/
Bool
isbrc(IPaddr dest)
{
int inum;
/* 所有的0和全1的是广播地址 */
if (dest == ip_anyaddr || dest == ip_maskall) /* 如果为全0 或 全1 则返回真*/
return TRUE;
/* 检查真正的广播地址和BSD风格的网和子网 */
for (inum=0; inum < Net.nif; ++inum)
if (dest == nif[inum].ni_brc ||
dest == nif[inum].ni_nbrc ||
dest == nif[inum].ni_subnet ||
dest == nif[inum].ni_net)
return TRUE;
return FALSE;
}
5. IP首部中的字节顺序
/* iph2net.c – iph2net */
#include <conf.h>
#include <kernel.h>
#include <network.h>
/*————————————————————————
* iph2net – iph2net – 转换成一个IP数据包头从主机到网络字节顺序
*————————————————————————
*/
struct ip *
iph2net(struct ip *pip)
{
/* 注:不包括IP选项 */
pip->ip_len = hs2net(pip->ip_len);
pip->ip_id = hs2net(pip->ip_id);
pip->ip_fragoff = hs2net(pip->ip_fragoff);
return pip;
}
/* ipnet2h.c – ipnet2h */
#include <conf.h>
#include <kernel.h>
#include <network.h>
/*————————————————————————
* ipnet2h – 转换成一个IP数据包头从网络到主机字节顺序
*————————————————————————
*/
struct ip *
ipnet2h(struct ip *pip)
{
/* 注:不包括IP选项 */
pip->ip_len = net2hs(pip->ip_len);
pip->ip_id = net2hs(pip->ip_id);
pip->ip_fragoff = net2hs(pip->ip_fragoff);
return pip;
}
6. 向IP发送数据报
6.1 发送本地生成的数据报
/* ipsend.c – ipsend */
#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>
static ipackid = 1; /* 静态变量 标识*/
/*————————————————————————
* ipsend – 发送一个IP数据报到指定地址
*————————————————————————
*/
int
ipsend(IPaddr faddr, struct ep *pep, unsigned datalen,
u_char proto, u_char ptos, u_char ttl)
{
struct ip *pip = (struct ip *) pep->ep_data;
pep->ep_type = EPT_IP; /* 类型为网际协议*/
pep->ep_order |= EPO_IP|EPO_NET; /* 网络层 协议等级为1*/
pip->ip_verlen = (IP_VERSION<<4) | IP_MINHLEN; /* 协议版本 最小的IP头长度*/
pip->ip_tos = ptos; /* 设置服务类型与优先级*/
pip->ip_len = datalen+IP_HLEN(pip); /* IP分组的长度*/
pip->ip_id = ipackid++; /* 分组的标识*/
pip->ip_fragoff = 0; /* 分片的偏移*/
pip->ip_ttl = ttl; /* 生存时间*/
pip->ip_proto = proto; /* 协议类型*/
pip->ip_dst = faddr; /* 目标地址*/
/*
* 特殊情况的ICMP协议,因此源匹配目的地
* on multi-homed hosts. 多穴主机??
*/
if (pip->ip_proto != IPT_ICMP) /* 不是ICMP分组*/
pip->ip_src = ip_anyaddr; /* IP的源地址为任意地址*/
if (enq(nif[NI_LOCAL].ni_ipinq, pep, 0) < 0) { /* 将分组放入本地主机接口队列中*/
freebuf(pep);
IpOutDiscards++;
}
send(ippid, NI_LOCAL); /* IP数据报阻塞并等待,发送报文给IP进程*/
IpOutRequests++;
return OK;
}
/* 特殊的IP地址 */
IPaddr ip_anyaddr = 0;
#if BYTE_ORDER == LITTLE_ENDIAN
IPaddr ip_loopback = 0x0100007F; /* 127.0.0.1 */
#else /* BYTE_ORDER */
IPaddr ip_loopback = 0x7F000001; /* 127.0.0.1 */
#endif /* BYTE_ORDER */
6.2 发送传入数据报
/* ip_in.c – ip_in */
#include <conf.h>
#include <kernel.h>
#include <network.h>
#include <proc.h>
/*————————————————————————
* ip_in – IP input function
*————————————————————————
*/
int
ip_in(struct netif *pni, struct ep *pep)
{
struct ip *pip = (struct ip *)pep->ep_data;
IpInReceives++;
if (enq(pni->ni_ipinq, pep, pip->ip_tos & IP_PREC) < 0) {
IpInDiscards++; /* 如队列满则记录溢出差错并丢则报文*/
freebuf(pep);
}
send(ippid, (pni-&nif[0])); /* 向IP进程发送消息*/
return OK;
}
7. 表格的维护
/* slowtimer.c – slowtimer */
#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <sleep.h>
#include <date.h>
#include <network.h>
#define STGRAN 1 /* 定时器粒度(延迟秒) */
void arptimer(unsigned), ipftimer(unsigned), rttimer(unsigned);
#ifdef OSPF
void ospftimer(unsigned);
#endif /*OSPF*/
/*————————————————————————
* slowtimer – 处理长期定期维护网络表
*————————————————————————
*/
PROCESS
slowtimer(void)
{
unsigned long lasttime, now; /* prev and current times (secs)*/
int delay; /* 几秒钟内实际延迟 */
signal(Net.sema);
gettime(&lasttime);
while (1) {
sleep(STGRAN); /* 延时*/
gettime(&now);
delay = now – lasttime;
if (delay <= 0 || delay > 4*STGRAN)
delay = STGRAN; /* 时钟复位-likely clock reset */
lasttime = now;
arptimer(delay);
ipftimer(delay);
rttimer(delay);
#ifdef OSPF
ospftimer(delay);
#endif /* OSPF */
}
}