IP 碎片重组

内核中的IP重组函数.
struct sk_buff * ip_defrag(struct sk_buff * skb, u32 user)
{
......
//如果内核范围超出限制
if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
ip_evictor(); //回收内存到限制之内
......
if ((qp = ip_find(iph, user)) != NULL) { //查找或者创建一个队列头
......
ip_frag_queue(qp, skb); //收集IP碎片
if (qp->last_in == (FIRST_IN|LAST_IN) && qp->meat == qp->len) //IP碎片收集完毕
ret = ip_frag_reasm(qp, dev); //把收集的碎片重组,主要是更新头结点
......
return ret; //返回新重组后的数据报头
}
......
return NULL; //队列头创建失败
}
我们看到主要有三个函数最重要,我们一个一个看.
首先看一下数据结构
struct ipq {
struct hlist_node list; //hash list
......
struct sk_buff *fragments; // linked list of received fragments
int len; // 应该接收数据长度
int meat; // 实际接收数据长度
......
};
static inline struct ipq * ip_find(struct iphdr * iph, u32 user)
{
......
//根据ip中的信息,计算出一个hash值
hash = ipqhashfn(id, saddr, daddr, protocol);
//在hash表中查找相关队列头
hlist_for_each_entry(qp, n, &ipq_hash[hash], list) {
if(......) //找到匹配
return qp;
}
......
//没找到,创建一个然后放入hash表中
return ip_frag_create(iph, user);
}
//好好看看,不太难
static void ip_frag_queue(struct ipq * qp, struct sk_buff * skb)
{
struct sk_buff * prev, * next;
int flags, offset;
int ihl, end;
......
offset = ntohs(skb->nh.iph->frag_off);
flags = offset & ~IP_OFFSET; // 获取标志位
offset &= IP_OFFSET;
offset <<= 3; // 确定偏移位置
ihl = skb->nh.iph->ihl * 4; // ip 头长度
end = offset + skb->len - ihl; // 确定这个IP包数据在完整包中的结束点 if ((flags & IP_MF) == 0) { //是最后一个IP包吗
......
qp->last_in |= LAST_IN; //也许没有受到全部的包,但这个报一定是那最后一个,所以加标志
......
} else {
......
if (end > qp->len) {
.......
qp->len = end; //记录最后的位置
}
}
.......
//找到这个数据报在全部数据报中的位置
prev = NULL;
for(next = qp->fragments; next != NULL; next = next->next) {
if (FRAG_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
} if (prev) { //有上个位置的包
//计算复盖的长度
int i = (FRAG_CB(prev)->offset + prev->len) - offset; if (i > 0) { //有复盖
offset += i; //调整这个包的偏移
.......
}
}
//有下个位置的包
while (next && FRAG_CB(next)->offset < end) {
int i = end - FRAG_CB(next)->offset; //计算复盖的长度
// 没有复盖全部的next包
if (i < next->len) {
......
FRAG_CB(next)->offset += i; //调整next的偏移
qp->meat -= i; //调整实际数据长度
......
} else {
struct sk_buff * free_it = next;
......
frag_kfree_skb(free_it, NULL); //释放掉她
}
}
//记录这个偏移
FRAG_CB(skb)->offset = offset;
//插入这个数据包
skb->next = next;
if (prev)
prev->next = skb;
else
qp->fragments = skb;
......
qp->meat += skb->len; //记录实际接收数据的长度
......
}
//重组一个新包,并不实际拷贝数据,因为包已经被连接好 skb->netx->next->next
static struct sk_buff * ip_frag_reasm(struct ipq * qp, struct net_device * dev)
{
......
struct sk_buff * fp, * head = qp->fragments;
......
ihlen = head->nh.iph->ihl * 4; //IP头长度
len = ihlen + qp->len; //头加数据的总长度
......
skb_shinfo(head)->frag_list = head->next; //指向头下的一个数据包
skb_push(head, head->data - head->nh.raw); //调整指针
......
for (fp = head->next; fp; fp = fp->next) { //长度收集
head->data_len += fp->len;
head->len += fp->len;
.......
head->truesize += fp->truesize;
atomic_sub(fp->truesize, &ip_frag_mem); //使用内存调整
}
head->next = NULL; //next NULL
.......
qp->fragments = NULL; // set NULL
return head;
.......
}
IP 分片
ip_output->ip_finish_output 中
......
if (skb->len > dst_mtu(skb->dst) && !skb_is_gso(skb)) //包长度超过mtu 且没有开启 gso (gso参考: http://lwn.net/Articles/189970/)
return ip_fragment(skb, ip_finish_output2); //分片
else
return ip_finish_output2(skb); int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
   struct iphdr *iph;
  int raw = 0;
  int ptr;
  struct net_device *dev;
    struct sk_buff *skb2;
unsigned int mtu, hlen, left, len, ll_rs;
int offset;
__be16 not_last_frag;
struct rtable *rt = (struct rtable*)skb->dst; //路由项
int err = 0;
dev = rt->u.dst.dev;
iph = skb->nh.iph; //获取ip头 //设置了不分片标志
if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(dst_mtu(&rt->u.dst)));
kfree_skb(skb);
return -EMSGSIZE;
}
hlen = iph->ihl * 4; //ip头长度
mtu = dst_mtu(&rt->u.dst) - hlen; //没有ip头的mtu大小
IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE; if (skb_shinfo(skb)->frag_list) { //如果skb已经被分片了
struct sk_buff *frag;
int first_len = skb_pagelen(skb); //返回skb数据长度
//数据长度大于mtu 或 没有8字节对齐 或 ip头中标志了分片 或 skb被cloned
if (first_len - hlen > mtu || ((first_len - hlen) & 7) || (iph->frag_off & htons(IP_MF|IP_OFFSET)) || skb_cloned(skb))
goto slow_path; //进入慢速路径 //循环每一个碎片
for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
//长度检查
if (frag->len > mtu || ((frag->len & 7) && frag->next) || skb_headroom(frag) < hlen)
goto slow_path; /* Partially cloned skb? */
if (skb_shared(frag)) //碎片被共享
goto slow_path; BUG_ON(frag->sk);
if (skb->sk) { //skb持有 sock结构
sock_hold(skb->sk);
frag->sk = skb->sk;
frag->destructor = sock_wfree;
skb->truesize -= frag->truesize; //长度减少
}
}
/* Everything is OK. Generate! */
err = 0;
offset = 0;
frag = skb_shinfo(skb)->frag_list; //碎片头
skb_shinfo(skb)->frag_list = NULL; //skb 碎片头指针清空
skb->data_len = first_len - skb_headlen(skb);
skb->len = first_len; //更新长度
iph->tot_len = htons(first_len);
iph->frag_off = htons(IP_MF); //设置标志
ip_send_check(iph); //计算skb的校验和 for (;;) {
/* Prepare header of the next frame, before previous one went down. */
if (frag) { //碎片
frag->ip_summed = CHECKSUM_NONE;
frag->h.raw = frag->data; //传输头
frag->nh.raw = __skb_push(frag, hlen); //留出网络头空间
memcpy(frag->nh.raw, iph, hlen); //copy skb中的ip头到这个碎片中当作网络头
iph = frag->nh.iph;
iph->tot_len = htons(frag->len); //更新长度
ip_copy_metadata(frag, skb); //copy skb中的一些其他字段的值到碎片
if (offset == 0) //第一个碎片
ip_options_fragment(frag); //构建选项 offset += skb->len - hlen; //更新偏移
iph->frag_off = htons(offset>>3); //ip头中保存偏移
if (frag->next != NULL) //还有下一个碎片
iph->frag_off |= htons(IP_MF); /* Ready, complete checksum */
ip_send_check(iph); //计算校验和
}
err = output(skb);//发送数据包 if (!err)
IP_INC_STATS(IPSTATS_MIB_FRAGCREATES); if (err || !frag)
break; //循环到下一个
skb = frag;
frag = skb->next;
skb->next = NULL; }
if (err == 0) { //正确
IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
return 0;
} while (frag) { //发送不正确
skb = frag->next;
kfree_skb(frag);
frag = skb;
}
IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
return err;
}
slow_path: //慢速路径
left = skb->len - hlen; //ip头后面数据长度
ptr = raw + hlen; // 0 + ip头长度
#ifdef CONFIG_BRIDGE_NETFILTER
/* for bridged IP traffic encapsulated inside f.e. a vlan header,
* we need to make room for the encapsulating header */
ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, nf_bridge_pad(skb));
mtu -= nf_bridge_pad(skb);
#else
ll_rs = LL_RESERVED_SPACE(rt->u.dst.dev); //预留头空间
#endif /* Fragment the datagram. */
offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3; //碎片偏移
not_last_frag = iph->frag_off & htons(IP_MF); //是否是最后一个碎片 while(left > 0) {
len = left;
/* IF: it doesn't fit, use 'mtu' - the data space left */
if (len > mtu) //数据包长度 > mtu
len = mtu; /* IF: we are not sending upto and including the packet end then align the next start on an eight byte boundary */
if (len < left) {
len &= ~7; //8 字节对齐
}
//分配碎片
if ((skb2 = alloc_skb(len + hlen + ll_rs, GFP_ATOMIC)) == NULL) {
NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");
err = -ENOMEM;
goto fail; }
ip_copy_metadata(skb2, skb); //copy skb中的其他数据到skb2
skb_reserve(skb2, ll_rs); //skb2->data += ll_rs 保留空间
skb_put(skb2, len + hlen); //设置skb长度
skb2->nh.raw = skb2->data; //指向网络头
skb2->h.raw = skb2->data + hlen; //指向传输头 if (skb->sk) //skb属于一个 sock
skb_set_owner_w(skb2, skb->sk); memcpy(skb2->nh.raw, skb->data, hlen); //copy网络头 if (skb_copy_bits(skb, ptr, skb2->h.raw, len))//copy网络头后面的数据 长度是len,不超过mtu
BUG(); left -= len; //总长度减少
iph = skb2->nh.iph; //获取ip头
iph->frag_off = htons((offset >> 3)); //设置碎片偏移 if (offset == 0) //第一个碎片,构建ip选项
ip_options_fragment(skb); if (left > 0 || not_last_frag) //还有碎片需要处理
iph->frag_off |= htons(IP_MF); ptr += len;//更新copy位置
offset += len;//偏移增加
iph->tot_len = htons(len + hlen); //更新 ip头中长度
ip_send_check(iph); //计算校验和
err = output(skb2); //发送数据包
if (err)
goto fail; IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
}
kfree_skb(skb);
IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
return err;
fail: kfree_skb(skb);
IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);
return err;
}

  

IP 碎片重组的更多相关文章

  1. IP分片重组的分析和常见碎片攻击 v0.2

    IP分片重组的分析和常见碎片攻击 v0.2http://www.nsfocus.net/index.php?act=magazine&do=view&mid=584 作者:yawl ( ...

  2. IP碎片原理:攻击和防护

    为了加深理解IP协议和一些DoS攻击手段大家有必要看看以下内容,也许对你理解这个概念有所帮助.先来看看IP碎片是如何产生的吧.         一.IP碎片是如何产生的       链路层具有最大传输 ...

  3. ip分片重组 ip_defrag

    在ip_local_deliver中,如果检测到是分片包,则需要进行分片重组: ip_local_deliver |-->ip_is_fragment //判断是否为分片包 |-->ip_ ...

  4. PROC 文件系统调节参数介绍(netstat -us)

    转自:http://www.cnblogs.com/super-king/p/3296333.html /proc/net/* snmp文件 Ip: ip项 Forwarding        : 是 ...

  5. proc 文件系统调节参数介绍

    /proc/net/* snmp文件 Ip: ip项 Forwarding        : 是否开启ip_forward,1开启,2关闭 DefaultTTL       : IP默认ttl. In ...

  6. Linux协议栈函数调用流程

    普通网络驱动程序中必须要调用的函数是eth_type_trans(略),然后向上递交sk_buff时调用netif_rx()(net/core/dev.c).其函数中主要几行 __skb_queue_ ...

  7. 014_IP专项研究监控

    一.数据demo cat /proc/net/snmp Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagr ...

  8. Libnids(Library Network Intrusion Detection System) .

    Libnids(Library Network Intrusion Detection System)是一个网络入侵检测开发的专业编程接口.它实现了基于网络的入侵检测系统的基本框架,并提供了一些基本的 ...

  9. Libnids读书笔记 (转)

    一.当日工作(或学习)内容及进展情况(以条目式陈述,必要时配图说明) Libnids读书笔记: Libnids(Library Network Intusion Detection System)网络 ...

随机推荐

  1. 万网免费主机wordpress快速建站教程-域名绑定及备案

    进入主机管理界面,点击管理 点击域名绑定,绑定域名项选择已有域名,选择已购买的域名,点击一键解析域名,点击添加,即可完成域名解析工作. 由于没有备案,备案状态显示为未备案,点击旁边的备案链接,跳转至阿 ...

  2. MVC小系列(四)【向RouteData里扔数据】

    向RouteData里扔数据 当Url做路由之后,QueryString里当然是不可能再存你的信息了,而信息包括控制器,action,参数都会存储在RouteData里,而一般这里的信息都是通过前一个 ...

  3. 保留关键字 (Transact-SQL)

    https://msdn.microsoft.com/zh-cn/library/ms189822(v=sql.120).aspx Microsoft SQL Server 将保留关键字用于定义.操作 ...

  4. addLoadEvent函数

    首先是addLoadEvent函数的代码清单: function addLoadEvent(func){    var oldonload=window.onload;    if(typeof wi ...

  5. JSON.parse 函数应用 (复制备忘)

    JSON.parse 函数 JSON.parse 函数 (JavaScript) 将 JavaScript 对象表示法 (JSON) 字符串转换为对象. 语法 JSON.parse(text [, r ...

  6. HttpUtility.HtmlEncode

    HttpUtility.HtmlEncode用来防止站点受到恶意脚本注入的攻击 public string Welcome(string name, int numTimes = 1) {     r ...

  7. ASP.NET获取上传图片的大小

    1.采用客户端javascript可以取得图片大小 <input id="FileUpload" type="file" size="30&qu ...

  8. std::string stringf(const char* format, ...)

    std::string stringf(const char* format, ...){ va_list arg_list; va_start(arg_list, format); // SUSv2 ...

  9. 为 DataGridView 控件添加行号

    虽然好像不经常用到,不过还是记下来防止以后用到 /// <summary> /// 为 DataGridView 控件添加行号 /// </summary> /// <p ...

  10. ruby环境sass编译中文出现Syntax error: Invalid GBK character错误解决方法

    sass文件编译时候使用ruby环境,无论是界面化的koala工具还是命令行模式的都无法通过,真是令人烦恼. 容易出现中文注释时候无法编译通过,或者出现乱码,找了几天的解决方法终于解决了. 这个问题的 ...