免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1319 | 回复: 0
打印 上一主题 下一主题

不同情况下构造skb数据包的实现。 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-08-11 12:32 |只看该作者 |倒序浏览

在我这个网络接口的程序中(can0),其实难点就是怎样组包。怎样在原来数据包的基础加上自己的数据,怎样构造ip头,怎样构造udp头。
调试了两个星期,终于是调通了,在这个过程中,通过看内核源代码和自己组包的尝试,大概对组包的方法有了些了解,记录在此,留做备忘,也希望能给需要这方面信息的朋友一点帮助吧。
1,正常网卡收到数据包后的情况:
她的工作就是剥离mac头,然后给一些字段赋值,最后调用netif_rx将剥离mac头后的数据报(比如ip数据包)发送到上层协议。由协议栈处理。在此以ldd3中的snull为例,虽然snull跟硬件不相关,但这个过程都是类似的。
    struct sk_buff *skb;
    struct snull_priv *priv = netdev_priv(dev);
    skb = dev_alloc_skb(pkt->datalen + 2);
    if (!skb) {
        if (printk_ratelimit())
            printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
        priv->stats.rx_dropped++;
        goto out;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
    /* Write metadata, and then pass to the receive level */
    skb->dev = dev;
   skb->protocol = eth_type_trans(skb, dev);

    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    priv->stats.rx_packets++;
    priv->stats.rx_bytes += pkt->datalen;
    netif_rx(skb);
注意:上面代码中红色放大的地方是重要的。
因为此刻收到的数据包的格式如下:mac+ip+udp/udp+data
这时候的处理就是剥离mac头,然后需要更新的一些域值。这些都是在函数eth_type_trans函数里做的。需要注意的是skb->dev = dev;这条语句是很重要的,如果没有此语句,将会导致系统错误而死机(至少在我的板子上是这样的)。
注意:eth_type_trans()函数主要赋值的是:
skb->mac.raw,skb->protocol和skb->pkt_type。见下面的代码有无mac头的情况。
2,完全从一个字符串开始构造一个新的skb数据包。
以前只是看过如何修改数据包,自己构造数据包,这还是头一次,刚开始确实给我难住了,来来经过看内核代码和自己摸索,我自己写的代码如下:
/*假设:data是一个指向字符串的指针,data_len是data的长度*/
struct ipv6hdr *ipv6h;
struct udphdr *udph;
struct sk__buff * new_skb;
int length = data_len + sizeof(struct ipv6hdr) + sizeof(udphdr);
new_skb = dev_alloc_skb(length);
if(!new_skb)
    {
        printk("low memory...\n"):
        return -1;
    }
skb_reserve(new_skb,length);
memcpy(skb_push(new_skb,data_len),data,data_len);
new_skb->h.uh = udph = (struct udphdr *)skb_push(new_skb,sizeof(struct udphdr));
memcpy(udph,&udph_tmp,sizeof(struct udphdr)); //注意,此刻我的udph_tmp是在另一个过程中截获的数据包的udp头,如果完全是自己构造数据包,则需要自己填充udp数据头中的字段。
udph->len = .............. ; //此处需要给udph->len赋值。注意udph->len是__u16的。存储时是高低位互换的,所以你应该先将你要更新的数字编成16进制的数,然后高低位互换,在赋值给udh->len。
udplen = new_skb->len;
new_skb->nh.ipv6h = ipv6h = (struct ipv6hdr *)skb_push(new_skb,sizeof(struct ipv6hdr));
memcpy(ipv6h,&ipv6h_tmp,sizeof(struct ipv6hdr)); //同udp头注释。
ipb6h->payload_len = ..........; //此处同udph->len.需要注意的是,此处所指的长度并不包括ipv6头的长度,而是去掉ipv6头后的长度。
udph->check = 0;
udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph, udplen, 0));
///////////////注意,如果是ipv4,则还需要计算ip校验和,但此处是ipv6,不用计算ip检验和,所以此处没有ipv6头的校验。//////////////////////////
new_skb->mac.raw = new_skb->data; //因为无mac头
new_skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包
new_skb->pkt_type = PACKET_HOST; //表明是发往本机的包
new_skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。
netif_rx(new_skb);
3,当需要改变原有skb的数据域的情况。
此时,有两种办法:
可以先判断skb的tailroom,如果空间够大,则我们可以把需要添加的数据放在skb的tailroom里。如果tailroom不够大,则需要调用skb_copy_expand函数来扩充tailroom或者headroom。

例如我们需要在skb的后面加上一个16个字节的字符串,则代码类似如下:
if(skb_tailroom(skb)  16)
{
    nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb)+16,GFP_ATOMIC);
    if(!nskb)
    {
        printk("low memory....\n");        
        dev_kfree_skb(skb);
        return -1;
    }
   
    else
    {
        kfree_skb(skb); // 注意,如果此时是钩子函数钩出来的,则skb不能在这里释放,否则会造成死机。
        skb = nskb;
    }
   
    memcpy(skb_put(skb,16),ipbuf,16); //ipbuf为要加到skb后面的字符串
   
    udplen = skb->len - sizeof(struct ipv6hdr);
   
    udph->len += 0x1000; //换成十进制为 + 16
    ipv6h->payload_len += 0x1000;
   
    udph->check = 0;
    udph->check = csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, udplen, IPPROTO_UDP, csum_partial((char *)udph,udplen,0));   
   
    skb->mac.raw = new_skb->data; //因为无mac头
    skb->protocol = htons(ETH_P_IPV6); //表明包是ipv6数据包
    skb->pkt_type = PACKET_HOST; //表明是发往本机的包
    skb->dev = &can_control; //此处很重要,如果没有这条语句,则内核跑死。至少在我板子上是这样的。can_control是我的net_device结构体变量。
  netif_rx(skb);
}
注意:当调用skb_copy_expand或者修改了skb的数据域后,一定要更新udph->len和ipv6h->payload_len。否则上层应用(比如udp套接字)收到的数据包还是原来的数据包而不是修改后的数据包,因为udph->len的原因。
这三种情况下构造数据包的方法弄明白了,估计以后在要操作数据包应该就不会被难住了。
ps:今天比较开心,终于是把程序调通了,这两个星期的时间没白费,学了不少东西,休息两天,准备下一个任务。
返回到: 虚拟网络接口(can0)战斗总结


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/83905/showart_2024232.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP