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;
}