Archive

Posts Tagged ‘ip’

ip分片与重组

December 19th, 2014 No comments

最近在做udp pump, 突然手贱的想要知道一个udp包的最大负载的是多少.
于是写了一个简单的程序来测试了一下, client不断的发送payload size不断增加的包, 然后在server端收数据包. 当然了由于IP的最大payload只有65535-sizeof(IP Header).所以Udp的最大负载也就是只有大约65535-20-8 = 65507个. 如果超过这个大小以后,不是收不到, 而是client根本发不出去这个数据包

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import socket
import random
import string
import sys
import hashlib

def sendData(udp, ip,port, size):
    data = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(size))
    md5 = hashlib.new('md5')
    md5.update(data)
    udp.getsockname()
    udp.sendto(data,(ip,port))
    print "%s :: sending %08d bytes to %s:%d   --> %s" % ( udp.getsockname(), len(data), ip,port, md5.hexdigest())


def run(ip,port,begin,inc,end):
    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP)
    udp.bind(("0.0.0.0",0))
    sendData(udp, ip, port, 65507)
    sendData(udp, ip, port, 65508)

用上面的代码发送可以很轻松的验证,发送65507个字节的时候是OK的,但是65508的数据就会得到Message Too Long的错误.

接下来呢, 我做了一件错误的事情,然后导致了后来一系列的事情的发生.
我在server端使用tcpdump来查看网络数据包, udp server listen在6666端口.所以我使用命令来查看数据包

sudo tcpdump -i any -nnn -vvv "port 6666"

注意我的 tcpdump的filter是”port 6666″,也就是采用过滤端口的方式.这个也就是我想当然的认为ip fragment以后的每一个udp packet的port应该和原始数据是一致.

拿到tcpdump的数据以后我下了一大跳

17:06:09.875525 IP (tos 0x0, ttl 61, id 28605, offset 0, flags [+], proto UDP (17), length 1500)
    192.168.80.233.52099 > 10.15.10.50.6666: UDP, length 65507

请看IP的段显示的数据大小为1500, 这个很容易理解,毕竟大部分的网络的MTU都是1500,这里也不例外.但是UDP段显示的length是65507, 这个也说得过去, 我发送的数据就是这么大的. 但是问题来了,为啥只有这么一个数据包. 于是我猜测抓包显示的数据错掉了.我把抓包数据存下来以后发现文件大小也就是1500多一点大.那么很明显这个数据包里面的数据真的只有1500 bytes. 这里的事情被我认为是非常诡异的, 因为我发送数据的时候对数据做了一个md5,udpserver收到数据以后也会做一个md5. 这两个md5是一模一样的,那么很明显证明了server端收到的数据和client发送的数据是一样的.
但是抓的包却只有1500 bytes啊,剩下的数据死到哪里去了呢?
我一度怀疑tcpdump有bug,于是换成了wireshark(tshark). 但是得到的结果是一样,见鬼了. 于是乎一阵google以后找到了使用raw socket来抓数据包的代码在这里.感谢作者的无私分享. 用这个程序跑了一下,只抓udp的包. 从抓包数据看,端口为6666的仍旧只有1500字节的ip数据, 但是很奇怪的是那些非6666端口的其他数据包里面的数据和我发送的数据十分相似–随机的字符数字组合. 然后我发现了一个东西,也是这个东西让我明白了这到底是怎么回事.

IP Header
   |-IP Version        : 4
   |-IP Header Length  : 5 DWORDS or 20 Bytes
   |-Type Of Service   : 0
   |-IP Total Length   : 1500  Bytes(Size of Packet)
   |-Identification    : 63208
   |-Dont Fragment Field   : 0
   |-More Fragment Field   : 0
   |-TTL      : 62
   |-Protocol : 17
   |-Checksum : 35759

这个让我感觉很奇妙的东西就是Identification. 因为抓包数据中的所有包头都含有相同的Identification. 这个不免让我觉得其实IP分片以后实际上是靠Identification来决定是不是同一个上层数据包的内容的. 于是我查看了一下ip分片的实现

/* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
{
        struct inet_frag_queue *q;
        struct ip4_create_arg arg;
        unsigned int hash;

        arg.iph = iph;
        arg.user = user;

        read_lock(&ip4_frags.lock);
        hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);

        q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
        if (q == NULL)
                goto out_nomem;

        return container_of(q, struct ipq, q);

out_nomem:
        LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
        return NULL;
}

上面的ip_find是从ip_local_deliver => ip_defrag => ip_find这个调用链过来的. 我们可以看到这里使用ip header里面的id, source addr, dest addr和protocol来做一个hash, 远没有port啥事.
到这里已经很清楚了, udp发的数据的最大负载是65535-20 -8 = 65507. 但是到了ip层的话还是需要被分片的, 否则过不了交换机. ip分片的依据的ip header中的id, 当然还需要由frags来指明fragment的offset之类的信息.
这个时候再回头来想想当初为何就没有想到, port是udp/tcp层的东西, ip层是没有这个玩意儿的. 那么分片自然就是和port无关的了.

Categories: programming Tags: , , ,