请不要裸操作sk_buff,请使用sk_buff的API

写这篇文章,我只是为了嘲笑我自己。我其实什么都不懂。

我总是觉得sk_buff的API太难用了,但也可能是我不会用才会这么认为…无论如何,我一直不喜欢这个东西。

为了证明TCP/UDP的校验和的脆弱性,我想演示一个效果,即交换一下TCP的payload的两个uint16值,它的校验和保持不变,所以我们必须为每一个文件单独再生成一个MD5文件。

我的代码是这么写的:

unsigned short *pstart, tmp;
...
tcph = (struct tcphdr *)((char *)iph + iph->ihl*4);

pstart = (unsigned short *)((char *)tcph + tcph->doff*4);
tmp = *pstart;
*pstart = *(pstart + 1);
*(pstart + 1) = tmp;

如果这个代码部署在中间节点,那OK没问题,但是如果是部署在数据的发送节点,那么pstart并没有指向payload的开始处。发生了什么?

我在玩TCP换头操作的时候,曾经天真的用memmove来操作skb(一下一个panic),我当然懂memmove的细节,知道它如何处理overlap,但我不知道skb的data可能并不连续。

难道一个skb表示的数据包还能不连续?数据上看,协议头和payload显然是分离的。

如果不了解skb的运作方式,还真的想不通,但事实上这就是不用API,非要裸操作skb的后果。

下面的代码无论部署在哪里都是对的:

pstart = (unsigned short *)((char *)tcph + tcph->doff*4);

if (skb_shinfo(skb)->nr_frags) {
     
	skb_frag_t *frag;
	frag = &skb_shinfo(skb)->frags[0];
	buf = kmap_atomic(skb_frag_page(frag));
} else {
     
	buf = (char *)((char *)tcph + tcph->doff*4);
}
pstart = (unsigned short *)buf;
tmp = *pstart;
*pstart = *(pstart + 1);
*(pstart + 1) = tmp;

if (skb_shinfo(skb)->nr_frags) {
     
	kunmap_atomic(buf);
}

很简单是吧, 要用skb的frag而不是直接假设skb的data是连续的!

这里面的理由很容易理解。

当我们调用send的时候,其参数是一个用户态的buffer,当这个buffer需要被封装到skb中被发出的时候,一般的理解是协议栈会将这个buffer拷贝到内核态为skb分配的data中,这明显是低效的。显然正确的做法是直接将skb的payload指针指向这块用户态的buffer,这就是本地始发的数据包为什么使用frag的原因。

sk_buff非常复杂,你会用下面这些API吗:

  • skb_reset_network_header
  • skb_set_transport_header
  • pskb_expand_head

反正我一直都不会用,所以我一般裸操作skb的各个字段,我自诩太了解skb了,所以当我要修改数据报文payload的时候,我会选择手工设置skb的各个字段,直到我碰到了frag,frag_list,gro,gso…

涉及到GSO/GRO就更加复杂了。

我承认我最害怕天黑,我承认我错了,我会努力学习API的用法而不是炫技秀花活儿,这些花活儿显然没有任何意义。

幸运的是,经过两周的历练,我已经会用这些API了。


浙江温州皮鞋湿,下雨进水不会胖。

你可能感兴趣的:(sk_buff,skb)