关闭

UDP包分片倒序

1843人阅读 评论(0) 收藏 举报
前段时间在测试一款设备的时候发现,由其发出来的UDP包(大于1500Bytes),会被分片(fragment)。

  这是本在意料之中的,但意外的是发出来的分片包是乱序的--包尾先发出来,包头后发出来。问题的关键在于防火墙拦截这种乱序的UDP包,即通信没法正常开展。

  因为该设备使用的是本来就是通用的LINUX系统内核,怀疑是内核编译的问题。于是在运行REDHEAD的PC机上再进行测试,发现情况依旧。但是在另一台同样是REDHEAD的PC机上测试,却能得到正序的包。原来是否倒序与LINUX的内核版本有关,2.4.x系列版本是倒序,而2.6.x系列是正序的。

  对2.4.x的核进行跟踪,可以发现其执行过程为

  |      sys_write               fs/read_write.c
| sock_writev net/socket.c
| sock_sendmsg net/socket.c
| inet_sendmsg net/ipv4/af_inet.c
| udp_sendmsg net/ipv4/udp.c
| ip_build_xmit net/ipv4/ip_output.c
| ip_build_xmit_slow net/ipv4/ip_output.c
| output_maybe_reroute net/ipv4/ip_output.c
| ip_output net/ipv4/ip_output.c
| ip_finish_output net/ipv4/ip_output.c
| dev_queue_xmit net/dev.c
| --------------------------------------------
| el3_start_xmit driver/net/3c309.c
V

  事实上在进入output_maybe_reroute函数处理的时候,已经是倒序的了。

通过查看ip_build_xmit_slow函数的注释,有这样的一段话:

/* Note that the fragment at the highest offset is sent first,
* so the getfrag routine can fill in the TCP/UDP checksum header
* field in the last fragment it sends... actually it also helps
* the reassemblers, they can put most packets in at the head of
* the fragment queue, and they know the total size in advance. This
* last feature will measurably improve the Linux fragment handler one
* day. */

  因此倒序是必然的。这个函数的功能是把大包(超过1500Bytes)分拆成小包,并倒序传入output_maybe_reroute,最终实现IP包发出。

为什么要倒序呢,这主要是出于效率的考虑。因为通过要在IP包头带上整个包的checksum,当包被分片时,发送接口必须等到读取了整个包的内容,才 能计算出最终的checksum。如果分片包按正序发送,则发送接口必须先把所有分片包缓存起来,在收到整个包的时候计算其checksum,然后逐一发 出。但是如果的倒序发包,那么就可以做到来一分片包只简单的计算该段的checksum,然后马上发出;直到收到最后一个分片时,算出整个包的 checksum,然后把此值放到IP头上发出。显然后者比前者少了缓冲的过程,性能自然要好些。

  但是接下来却又发现一个奇怪的现象,那就是另一款同样是使用2.4.x内核的设备,在发UDP大包时,却不存在倒序现象。

通过对比分析,发现关键在于ip_build_xmit_slow函数中的

err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
skb->dst->dev, output_maybe_reroute);

其中的NF_HOOK宏定义为:



#define NF_HOOK(pf, hook, skb, indev, outdev, okfn) /
(list_empty(&nf_hooks[(pf)][(hook)]) /
? (okfn)(skb) /
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))


  通常情况下,是调用okfn函数(在这里对应的是output_maybe_reroute)进行处理,第一款设备和

第一台测试PC就是这种情况。但是在内核配置为使用NETFILTER功能的时候,就不一样了,实际上这时将会调用nf_hook_slow函数进行处理。简言之,这个函数会把上层发来的包先缓存起来,并按正序发出去。因此第二款测试设备不存在乱序的情况。

既然找到的根本原因,接下来就好办了。最初是想参考2.6.x内核修改的,但是查看代码后发现,这两个版本相差实在太大了,甚至于根本上没有使用ip_build_xmit_slow函数。那就按照NETFILTER方式修改吧,先把包缓存起来,再按顺序发送出去即是。自己操刀改了些代码,测试通过。

static int

ip_build_xmit_slow(struct sock *sk,
int getfrag (const void *,
char *,
unsigned int,
unsigned int),
const void

*frag,
unsigned length,
struct ipcm_cookie *ipc,
struct rtable *rt,
int flags)
{
unsigned int fraglen, maxfraglen, fragheaderlen;
int err;
int offset, mf;
int mtu;
u16 id;

int hh_len = (rt->u.dst.dev->hard_header_len + 15+16)&~15;
int nfrags=0;
struct ip_options *opt = ipc->opt;
int df = 0;

struct sk_buff *skb_buf[64]; /* 64 is more than 65535/1500. */
int skb_buf_idx = 0;

mtu = rt->u.dst.pmtu;
if (ip_dont_fragment(sk, &rt->u.dst))
df = htons(IP_DF);

length -= sizeof(struct iphdr);

if (opt) {
fragheaderlen = sizeof(struct iphdr) + opt->optlen;
maxfraglen = ((mtu-sizeof(struct iphdr)-opt->optlen) & ~7) + fragheaderlen;
} else {
fragheaderlen = sizeof(struct iphdr);

/*
* Fragheaderlen is the size of 'overhead' on each buffer. Now work
* out the size of the frames to send.
*/

maxfraglen = ((mtu-sizeof(struct iphdr)) & ~7) + fragheaderlen;
}

if (length + fragheaderlen > 0xFFFF) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
return -EMSGSIZE;
}

/*
* Start at the end of the frame by handling the remainder.
*/

offset = length - (length % (maxfraglen - fragheaderlen));

/*
* Amount of memory to allocate for final fragment.
*/

fraglen = length - offset + fragheaderlen;

if (length-offset==0) {
fraglen = maxfraglen;
offset -= maxfraglen-fragheaderlen;
}

/*
* The last fragment will not have MF (more fragments) set.
*/

mf = 0;

/*
* Don't fragment packets for path mtu discovery.
*/

if (offset > 0 && sk->protinfo.af_inet.pmtudisc==IP_PMTUDISC_DO) {
ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
return -EMSGSIZE;
}
if (flags&MSG_PROBE)
goto out;

/*
* Begin outputting the bytes.
*/

id = sk->protinfo.af_inet.id++;

do {
char *data;
struct sk_buff * skb;

/*
* Get the memory we require with some space left for alignment.
*/
if (!(flags & MSG_DONTWAIT) nfrags == 0) {
skb = sock_alloc_send_skb(sk, fraglen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
} else {
/* On a non-blocking write, we check for send buffer
* usage on the first fragment only.
*/
skb = sock_wmalloc(sk, fraglen + hh_len + 15, 1,
sk->allocation);
if (!skb)
err = -ENOBUFS;
}
if (skb == NULL)
goto error;

/*
* Fill in the control structures
*/

skb->priority = sk->priority;
skb->dst = dst_clone(&rt->u.dst);
skb_reserve(skb, hh_len);

/*
* Find where to start putting bytes.
*/

data = skb_put(skb, fraglen);
skb->nh.iph = (struct iphdr *)data;

/*
* Only write IP header onto non-raw packets
*/

{
struct iphdr *iph = (struct iphdr *)data;

iph->version = 4;
iph->ihl = 5;
if (opt) {
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt,
ipc->addr, rt, offset);
}
iph->tos = sk->protinfo.af_inet.tos;
iph->tot_len = htons(fraglen - fragheaderlen + iph->ihl*4);
iph->frag_off = htons(offset>>3)mfdf;
iph->id = id;
if (!mf) {
if (offset !df) {
/* Select an unpredictable ident only
* for packets without DF or having
* been fragmented.
*/
__ip_select_ident(iph, &rt->u.dst);
id = iph->id;
}

/*
* Any further fragments will have MF set.
*/
mf = htons(IP_MF);
}
if (rt->rt_type == RTN_MULTICAST)
iph->ttl = sk->protinfo.af_inet.mc_ttl;
else
iph->ttl = sk->protinfo.af_inet.ttl;
iph->protocol = sk->protocol;
iph->check = 0;
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
data += iph->ihl*4;
}

/*
* User data callback
*/

if (getfrag(frag, data, offset, fraglen-fragheaderlen)) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}

offset -= (maxfraglen-fragheaderlen);
fraglen = maxfraglen;

nfrags++;

/*
* Put the skbs into the skb_buf first,
* send the datas later.
*/

skb_buf[skb_buf_idx] = skb;
skb_buf_idx++;
} while (offset >= 0);

/*
* Now send the datas, the fragment at the lowest offset is sent first.
*/

while (--skb_buf_idx >= 0) {
err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb_buf[skb_buf_idx], NULL,
skb_buf[skb_buf_idx]->dst->dev, output_maybe_reroute);
if (err) {
if (err > 0)
err = sk->protinfo.af_inet.recverr ? net_xmit_errno(err) : 0;
if (err)
goto error;
}
}

if (nfrags>1)
ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
out:
return 0;

error:
/*
* Error happen, we should free skbs that stored in skb_buf here.
*/

while (--skb_buf_idx >=0)
kfree_skb(skb_buf[skb_buf_idx]);

IP_INC_STATS(IpOutDiscards);
if (nfrags>1)
ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
return err;
}
3.1415926535897932384626433832

   PS:在写BLOG以后,我一直遵循这样一个习惯:原创的新帖,结尾处标上圆周率,每次增加一位。这是我能记住的最后一位圆周率了,算是一个阶段的结束 吧,以后发文就要先查查圆周率了。作为纪念吧,希望写一篇还算有点技术含量的文章,因此一直拖着,迟迟没动笔。这个文章来源于近段时间处理的一个工程问 题,现在总算写完了--可惜排版难看了点。

 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:73771次
    • 积分:674
    • 等级:
    • 排名:千里之外
    • 原创:13篇
    • 转载:0篇
    • 译文:0篇
    • 评论:17条
    文章分类
    最新评论
    我的博客