IP 总体结构 详解

Posted by 老沙
6月 24 2010

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 */
    }
}
 

Trackback URL for this entry