IP 分片与重组

IP:分片与重组

1. 引言
为了与互联网上的任意计算机通信,每个应用TCP/IP的计算机必须具有分片和重组的代码,一个设计良好的应用软件,会生成足够小的数据报,
因此主机并不需要经常执行分片任务.

2. 数据报的分片
分片发生在选择路由之后,以及放入接口队列之前.IP把数据报长度与MTU进行比较,确定是否需要分片.
如需分片,IP首先生成多个数据报,并将每个数据报中的分片位置1,将源数据报中的数据按顺序分片,并将它们装入这些数据报中.
它还在同一源数据报产生的所有数据报片中将MF位置为1,末尾片除外.IP一边分片,一边将它们传递给网络接口发送出去.

2.1 为一个数据报片再次分片
对于MF未置1的片,和上边说的没区别,除了最后一个片外,其它全置1.但对于MF为1的源片,再次分片后的所有分片MF全置1

3. 分片的实现

/* ipputp.c – ipputp */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipputp  –  发送一个数据包到一个接口的输出队列
 *————————————————————————
 */
int
ipputp(unsigned ifn, IPaddr nh, struct ep *pep)
{
    struct    netif    *pni = &nif[ifn];        /* 接口指针*/
    struct    ip    *pip;                /* IP报指针*/
    int        hlen, maxdlen, tosend, offset, offindg;

    if (pni->ni_state == NIS_DOWN) {            /* 接口关闭则返回*/
        freebuf(pep);
        return SYSERR;
    }
    pip = (struct ip *)pep->ep_data;            /* IP报指向*/
    if (pip->ip_len <= pni->ni_mtu) {        /* IP报长度小于等于接口MTU,则直接发送数据报*/
        pep->ep_nexthop = nh;
        pip->ip_cksum = 0;
        iph2net(pip);
        pep->ep_order &= ~EPO_IP;
        pip->ip_cksum = cksum((WORD *)pip, IP_HLEN(pip));
        return netwrite(pni, pep, EP_HLEN+net2hs(pip->ip_len));
    }
    /* 否则,我们必须分片 */

    if (pip->ip_fragoff & IP_DF) {            /* 能否分片,不能分片则报错并返回*/
        IpFragFails++;
        icmp(ICT_DESTUR, ICC_FNADF, pip->ip_src, pep, 0);
        return OK;
    }
    maxdlen = (pni->ni_mtu – IP_HLEN(pip)) &~ 7;    /* 最大长度为MTU减去IP头长度,并增加到8的倍数*/
    offset = 0;                    /* 偏移量*/
    offindg = (pip->ip_fragoff & IP_FRAGOFF)<<3;    /* 偏移量,保存控制位*/
    tosend = pip->ip_len – IP_HLEN(pip);        /* 要发送的IP报长度,不包括IP报头*/

    while (tosend > maxdlen) {            /* 仅当剩余的数据大于可发送的最大数据量时才发送*/
        if (ipfsend(pni,nh,pep,offset,maxdlen,offindg) != OK) {    /* 生成并发送分片*/
            IpOutDiscards++;
            freebuf(pep);
            return SYSERR;
        }
        IpFragCreates++;
        tosend -= maxdlen;
        offset += maxdlen;
        offindg += maxdlen;            /* */
    }
    IpFragOKs++;
    IpFragCreates++;
    hlen = ipfhcopy(pep, pep, offindg);        /* 首部拷贝处理,返回首部长度*/
    pip = (struct ip *)pep->ep_data;            /* 取IP数据报*/
    /*处理最后一个分片,当最后剩余的数据小于等于可发送的最大数据量 */
    memcpy(&pep->ep_data[hlen], &pep->ep_data[IP_HLEN(pip)+offset],
        tosend);                    /* 修改源报,使之变为最后一个报片*/
    /*非末尾的数据报片再次分片时,保证MF全为1 */
    pip->ip_fragoff = (pip->ip_fragoff & IP_MF)|(offindg>>3);
    pip->ip_len = tosend + hlen;
    pip->ip_cksum = 0;
    iph2net(pip);
    pep->ep_order &= ~EPO_IP;
    pip->ip_cksum = cksum((WORD *)pip, hlen);
    pep->ep_nexthop = nh;
    return netwrite(pni, pep, EP_HLEN+net2hs(pip->ip_len));
}

3.1 发送一个数据报片

/* ipfsend.c – ipfsend */

#include <conf.h>
#include <kernel.h>
#include <network.h>

int ipfhcopy(struct ep *, struct ep *, unsigned);

/*————————————————————————
 *  ipfsend –  发送一个IP数据报的分片
 *————————————————————————
 */
int
ipfsend(struct netif *pni, IPaddr nexthop, struct ep *pep,
    unsigned offset, unsigned maxdlen, unsigned offindg)
{
    struct    ep    *pepnew;
    struct    ip    *pip, *pipnew;
    int        hlen, len;

    pepnew = (struct ep *)getbuf(Net.netpool);        /* 申请一个新帧的缓冲区*/
    if (pepnew == (struct ep *)SYSERR)
        return SYSERR;
    pepnew->ep_order = ~0;
    hlen = ipfhcopy(pepnew, pep, offindg);    /* 复制头 */

    pip = (struct ip *)pep->ep_data;        /* 源IP报的指向*/
    pipnew = (struct ip *)pepnew->ep_data;    /* 新生成的IP报*/
    pipnew->ip_fragoff = IP_MF | (offindg>>3);
    pipnew->ip_len = len = maxdlen + hlen;
    pipnew->ip_cksum = 0;

    iph2net(pipnew);
    pepnew->ep_order &= ~EPO_IP;        /* 清除字节顺序*/
    pipnew->ip_cksum = cksum((WORD *)pipnew, hlen);

    memcpy(&pepnew->ep_data[hlen],        /* 复制数据*/
        &pep->ep_data[IP_HLEN(pip)+offset], maxdlen);
    pepnew->ep_nexthop = nexthop;

    return netwrite(pni, pepnew, EP_HLEN+len);    /* 发送并返回 */
}

3.2 复制数据报首部

/* ipfhcopy.c – ipfhcopy */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  ipfhcopy –  copy the hardware, IP header, and options for a fragment
 *————————————————————————
 */
int
ipfhcopy(struct ep *pepto, struct ep *pepfrom, unsigned offindg)
{
    struct    ip    *pipfrom = (struct ip *)pepfrom->ep_data;    /* 旧IP报*/
    unsigned    i, maxhlen, olen, otype;
    unsigned    hlen = (IP_MINHLEN<<2);        /* IP头最小长度*4 ,就是以字节为单位了*/

    if (offindg == 0) {    /* 偏移为0,说明是第一个分片,复制帧的头和IP头到新的帧*/
        memcpy(pepto, pepfrom, EP_HLEN+IP_HLEN(pipfrom));
        return IP_HLEN(pipfrom);
    }
    /* 以下就说明不是第一个IP分片了*/
    memcpy(pepto, pepfrom, EP_HLEN+hlen);        /* 复制帧的头和除了IP选项外的报头*/

    /*复制选项 */

    maxhlen = IP_HLEN(pipfrom);        /* 包括选项的IP头的长度*/
    i = hlen;                /* 不包括选项的IP头长度*/
    while (i < maxhlen) {            /* 最小头比最大头小,说明有IP选项*/
        otype = pepfrom->ep_data[i];    /* IP选项*/
        olen = pepfrom->ep_data[++i];    /* 选项长度*/
        if (otype & IPO_COPY) {        /* 如果复制位为1*/
            memcpy(&pepto->ep_data[hlen],
                &pepfrom->ep_data[i-1], olen);        /* 复制这个选项到所有新的帧*/
            hlen += olen;
        } else if (otype == IPO_NOP || otype == IPO_EOOP) {    /* 选项结束或没有操作*/
            pepto->ep_data[hlen++] = otype;
            olen = 1;
        }
        i += olen-1;
        
        if (otype == IPO_EOOP)
            break;
    }
    /* pad to a multiple of 4 octets */
    while (hlen % 4)            /* 填充到4字节的整数倍*/
        pepto->ep_data[hlen++] = IPO_NOP;
    return hlen;
}

4. 数据报的重组

4.1 数据结构

/* ipreass.h */

/* Internet Protocol (IP)  reassembly support */

#define IP_FQSIZE    10    /* 分片队列的数大数量 */
#define IP_MAXNF    10    /* 分片/数据报 的最大数量 */
#define    IP_FTTL        60    /* 生存时间(秒)*/

/* ipf_state flags */

#define    IPFF_VALID    1    /* 内容是有效的 */
#define    IPFF_BOGUS    2    /* 丢弃        */
#define    IPFF_FREE    3    /* 这个队列可以自由分配    */

struct    ipfq    {
    char    ipf_state;        /* 队列状态,值为上边 3种        */
    IPaddr    ipf_src;        /* 源IP地址 */
    short    ipf_id;            /* 数据报ID */
    int    ipf_ttl;        /* 数据报重组的生存时间 */
    int    ipf_q;            /* 分片存储的链表 */
};

extern    int    ipfmutex;        /* 互斥 mutex for ipfqt[] */
extern    struct    ipfq    ipfqt[];    /* IP分片队列表        */

int ipfsend(struct netif *, IPaddr, struct ep *, unsigned, unsigned,
    unsigned);
int ipfhcopy(struct ep *, struct ep *, unsigned);

4.2 互斥操作

为了何证进程在数据报片的链表时不会互相干扰,重组程序代码使用了一个互斥信号量ipfmutex.在ipreass.h中

4.3 在链表中加入一个数据报片

/* ipreass.c – ipreass */

#include <conf.h>
#include <kernel.h>
#include <network.h>

int ipfadd(struct ipfq *, struct ep *);
struct    ep    *ipfjoin(struct ipfq *);

/*————————————————————————
 *  ipreass  –  reassemble an IP datagram, if necessary
 *    returns packet, if complete; 0 otherwise
 *  IP数据报重组,如果报片到齐则重组该包,并返回完整的数据报,否则返回0
 *————————————————————————
 */
struct ep *
ipreass(struct ep *pep)
{
    struct    ep    *pep2;
    struct    ip    *pip;
    int        firstfree;
    int        i;

    pip = (struct ip *)pep->ep_data;        /* 得到IP数据报的*/

    wait(ipfmutex);                /* 互斥*/

    if ((pip->ip_fragoff & (IP_FRAGOFF|IP_MF)) == 0) {    /*如果不是分片,返回当前帧*/
        signal(ipfmutex);
        return pep;
    }
    IpReasmReqds++;
    firstfree = -1;
    /* 以下情况为是分片 */
    for (i=0; i<IP_FQSIZE; ++i) {
        struct    ipfq    *piq = &ipfqt[i];

        if (piq->ipf_state == IPFF_FREE) {        /* 队列未使用,则继续*/
            if (firstfree == -1)
                firstfree = i;
            continue;
        }
        if (piq->ipf_id != pip->ip_id)            /* 队列ID不等于分片ID,则继续*/
            continue;
        if (piq->ipf_src != pip->ip_src)            /* 源地址不同,则继续*/
            continue;
        /* 找到匹配 */
        if (ipfadd(piq, pep) == 0) {            /* 把该分片加入匹配的队列*/
            signal(ipfmutex);
            return 0;
        }
        pep2 = ipfjoin(piq);                /* 检查是否可以重组数据报*/
        signal(ipfmutex);
        return pep2;
        
    }
    /* 没有匹配 */

    if (firstfree < 0) {                    /* 检查是否有空闲队列*/
        /* no room– drop */                /* 没有空闲则丢弃*/
        freebuf(pep);
        signal(ipfmutex);
        return 0;
    }
    ipfqt[firstfree].ipf_q = newq(IP_FQSIZE, QF_WAIT);    /* 分配一个空闲表项*/
    if (ipfqt[firstfree].ipf_q < 0) {
        freebuf(pep);
        signal(ipfmutex);
        return 0;
    }
    ipfqt[firstfree].ipf_src = pip->ip_src;            /* 填充*/
    ipfqt[firstfree].ipf_id = pip->ip_id;
    ipfqt[firstfree].ipf_ttl = IP_FTTL;
    ipfqt[firstfree].ipf_state = IPFF_VALID;
    ipfadd(&ipfqt[firstfree], pep);
    signal(ipfmutex);
    return 0;
}

int    ipfmutex;
struct    ipfq    ipfqt[IP_FQSIZE];

4.4 溢出时的丢弃

/* ipfadd.c – ipfadd */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

/*————————————————————————
 *  ipfadd  –  增加一个分片到一个IP碎片队列
 *————————————————————————
 */
Bool
ipfadd(struct ipfq *iq, struct ep *pep)
{
    struct    ip    *pip;
    int        fragoff;

    if (iq->ipf_state != IPFF_VALID) {            /* 分片队列无效,则丢弃*/
        freebuf(pep);
        return FALSE;
    }
    pip = (struct ip *)pep->ep_data;                /* 得到IP数据报的*/
    fragoff = pip->ip_fragoff & IP_FRAGOFF;            /* 得到偏移量*/
    /* -fragoff用作关键值,越大越靠前 */
    if (enq(iq->ipf_q, pep, -fragoff) < 0) {            /* 举出丢弃并释放内存*/
        /* overflow– free all frags and drop */
        freebuf(pep);
        IpReasmFails++;
        while (pep = (struct ep *)deq(iq->ipf_q)) {    /* 从队列删除帧并释放帧*/
            freebuf(pep);
            IpReasmFails++;
        }
        freeq(iq->ipf_q);                /* 释放队列*/
        iq->ipf_state = IPFF_BOGUS;
        return FALSE;
    }
    iq->ipf_ttl = IP_FTTL;        /* 重置生存时间 */
    return TRUE;
}

4.5 测试一个完整的数据据报

/* ipfjoin.c – ipfjoin */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

struct    ep    *ipfcons(struct ipfq *);

/*————————————————————————
 *  ipfjoin  –  join fragments, if all collected
 *————————————————————————
 */
struct ep *
ipfjoin(struct ipfq *iq)
{
    struct    ep    *pep;
    struct    ip     *pip = 0;
    int        off, packoff;

    if (iq->ipf_state == IPFF_BOGUS)
        return 0;
    /* see if we have the whole datagram */
    /* 看我们是否有整个的数据包 */

    off = 0;
    while (pep=(struct ep *)seeq(iq->ipf_q)) {        /* 取出帧*/
        pip = (struct ip *)pep->ep_data;            /* 取出IP报*/
        packoff =  (pip->ip_fragoff & IP_FRAGOFF)<<3;
        if (off < packoff) {                /* 偏移大于0*/
            while(seeq(iq->ipf_q))            /* 一个不满足,说没没全到*/
                /*empty*/;
            return 0;
        }
        off = packoff + pip->ip_len – IP_HLEN(pip);    /*计算总长度,不含头*/
    }
    /* 这里利用off来测试,首先ipfjoin查看当前数据报片的偏移量是否与off值相符。
       如果当前数据报片的偏移量超过了off的值,那么必定有尚未到达的数据报片,
       因此ipfjoin返回0。如果偏移量与off值一致,那么ipfjoin通过将off值加上当前
       数据报片长度减去首部计算出下一个数据报片的偏移量。*/
    if (off > MAXLRGBUF) {        /* 超过缓冲区则丢弃 – too big for us to handle */
        while (pep = (struct ep *)deq(iq->ipf_q))
            freebuf(pep);
        freeq(iq->ipf_q);
        iq->ipf_state = IPFF_FREE;
        return 0;
    }
    if (pip == 0 || (pip->ip_fragoff & IP_MF) == 0)        /* 没有IP报或没有更多的分片*/
        return ipfcons(iq);                /* 收集数据报片并重建完整的数据报 */

    return 0;
}

4.6 将数据报片组装成完整的数据报

/* ipfcons.c – ipfcons */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 *  ipfcons  –  从IP碎片队列构建一个分组
 *————————————————————————
 */
struct ep *
ipfcons(struct ipfq *iq)
{
    struct    ep    *pep, *peptmp;
    struct    ip    *pip;
    int        off, seq;

    pep = (struct ep *)getbuf(Net.lrgpool);            /* 申请缓存空间*/
    if (pep == (struct ep *)SYSERR) {            /* 申请失败则丢弃该报*/
        while (peptmp = (struct ep *)deq(iq->ipf_q)) {
            IpReasmFails++;
            freebuf(peptmp);
        }
        freeq(iq->ipf_q);                /* 释放队列*/
        iq->ipf_state = IPFF_FREE;
        return 0;
    }
    /*  复制帧和IP报头 */

    peptmp = (struct ep *)deq(iq->ipf_q);            /* 取出一个分片*/
    pip = (struct ip *)peptmp->ep_data;            /* 得到IP报*/
    off = IP_HLEN(pip);                    /* 得到IP头长度*/
    seq = 0;
    memcpy(pep, peptmp, EP_HLEN+off);            /* 复制IP报头到pep*/

    /* 复制数据部分 */
    while (peptmp != 0) {
        int dlen, doff;

        pip = (struct ip *)peptmp->ep_data;        /* 取IP报*/
        doff = IP_HLEN(pip) + seq
            – ((pip->ip_fragoff&IP_FRAGOFF)<<3);
        dlen = pip->ip_len – doff;
        memcpy(pep->ep_data+off, peptmp->ep_data+doff, dlen);
        off += dlen;
        seq += dlen;
        freebuf(peptmp);
        peptmp = (struct ep *)deq(iq->ipf_q);
    }

    /* 修复分组的头 */
    pip = (struct ip *)pep->ep_data;                /* 取出IP报*/
    pip->ip_len = off;                    /* 修复长度*/
    pip->ip_fragoff = 0;                    /* 修复偏移量*/

    /* 释放资源 */
    freeq(iq->ipf_q);
    iq->ipf_state = IPFF_FREE;
    IpReasmOKs++;
    return pep;
}

5. 数据报片链表的维护管理

/* ipftimer.c – ipftimer */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipftimer – 更新生存周期并删除过期的碎片
 *————————————————————————
 */
void
ipftimer(int gran)
{
    struct    ep    *pep;
    struct    ip    *pip;
    int        i;

    wait(ipfmutex);                        /* 申请互斥量 */
    for (i=0; i<IP_FQSIZE; ++i) {                /* 遍历队列*/
        struct ipfq *iq = &ipfqt[i];

        if (iq->ipf_state == IPFF_FREE)            /* 空闲则继续*/
            continue;
        iq->ipf_ttl -= gran;                /* 非空闲则ttl-1*/
        if (iq->ipf_ttl <= 0) {                /* 生存周期到达则*/
            if (iq->ipf_state == IPFF_BOGUS) {    /* 如果为丢弃状态,则继续*/
                /* resources already gone */
                iq->ipf_state = IPFF_FREE;
                continue;
            }
            if (pep = (struct ep *)deq(iq->ipf_q)) {    /* 取分片*/
                IpReasmFails++;
                pip = (struct ip *)pep->ep_data;    /* 取IP报*/
                icmp(ICT_TIMEX, ICC_FTIMEX,    /* 向源站报错*/
                    pip->ip_src, pep, 0);
            }
            while (pep = (struct ep *)deq(iq->ipf_q)) {    /* 释放资源*/
                IpReasmFails++;
                freebuf(pep);
            }
            freeq(iq->ipf_q);            /* 释放队列*/
            iq->ipf_state = IPFF_FREE;
        }
    }
    signal(ipfmutex);                    /* 互斥解锁*/
}

6. 初始化

/* ipfinit.c – ipfinit */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * ipfinit  –  initialize IP fragment queue data structures
 *————————————————————————
 */
void
ipfinit(void)
{
    int    i;

    ipfmutex = screate(1);            /* 不多说了,简单,太~~~,这个看不明白就不用看了. 🙂 */
    for (i=0; i<IP_FQSIZE; ++i)
        ipfqt[i].ipf_state = IPFF_FREE;
}
 

IP 分片与重组》有一个想法

回复 猪八戒的博客 取消回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注