如何制作数据包的副本?

时间:2013-12-30 05:30:06

标签: linux linux-kernel packet kernel-module netfilter

我想在Net Filter挂钩处制作数据包的副本(并将其发送到由我制作的队列)。 skb_copy会为我工作吗?我还必须在数据包之前添加seq no,skb_reserve会这样做吗? 我编写了以下代码来捕获数据包

  unsigned int hook_func(unsigned int hooknum,
                         struct sk_buff **skb,
                         const struct net_device *in,
                         const struct net_device *out,
                         int (*okfn)(struct sk_buff *))
{
    if (strcmp(in->name, drop_if) == 0) {
        printk("Dropped packet on %s...\n", drop_if);

        return NF_DROP;
    } else {
        return NF_ACCEPT;
    }
}

/* Initialisation routine */
int init_module()
{
    /* Fill in our hook structure */
    nfho.hook     = hook_func;         /* Handler function */
    nfho.hooknum  = NF_IP_PRE_ROUTING; /* First hook for IPv4 */
    nfho.pf       = PF_INET;
    nfho.priority = NF_IP_PRI_FIRST;   /* Make our function first */

    nf_register_hook(&nfho);

    return 0;
}

2 个答案:

答案 0 :(得分:1)

我同意Rachit Jain,除非你有充分的理由在内核空间中这样做,我建议你使用libpcap在用户空间中这样做。

无论如何,如果你只想复制数据包然后修改一些数据,我建议你分配一个有足够空间的新skb来复制你收到的skb中已有的数据+足够的空间来添加标题。

这是我曾经使用的代码,它不会从现有的skb进行任何复制,但它对您有用。我在这里制作一种特殊的ICMP回音信息

int sendICMPEcho(unsigned char *msg, unsigned int length, 
                        __be32 source, __be32 dest)
{
    struct ethhdr *eth;
    struct iphdr *iph;
    struct icmphdr *icmph;
    struct sk_buff *newPacket;
    unsigned char *data;
    unsigned int skbSize = length + sizeof(struct icmphdr)
                + sizeof(struct iphdr)
                + sizeof(struct ethhdr);
    /* Allocate the skb */
    newPacket = alloc_skb(skbSize, GFP_ATOMIC);
    if(newPacket == NULL)
        return SEND_FAIL_MEMORY;

    /* Reserve the headers area */
    skb_reserve(newPacket, sizeof(struct icmphdr)
                 + sizeof(struct iphdr) 
                + sizeof(struct ethhdr));   

    /* Extend the data area from 0 to the message length */
    data = skb_put(newPacket, length);
    /* Copy the data from the message buffer to the newPacket */
    memcpy(data, msg, length);

    /************** ICMP HEADER***************/ 
    /* skb_push - pushing the icmp header in the packet data */
    icmph = (struct icmphdr *) skb_push(newPacket,
                        sizeof(struct icmphdr));
    /*set ICMP header here */
    icmph->type = ICMP_ECHO;
    icmph->code = 100; /* Our magic number */
    icmph->un.echo.id = 0;
    icmph->un.echo.sequence = htons(sendCounter);
    icmph->checksum= 0;
    icmph->checksum = in_cksum((unsigned short *)icmph, 
                sizeof(struct icmphdr) + length);
    /************** END ICMP HEADER**************/

    /************** IP HEADER ***************/
    iph = (struct iphdr *) skb_push(newPacket,
                        sizeof(struct iphdr));
    /* set IP header here */
    iph->ihl = 5;/* 5 * 32(bits) */
    iph->version = 4;
    iph->tos = 255; /* Just a magic number - remove it */
    iph->tot_len = htons( sizeof(struct iphdr) 
                + sizeof(struct icmphdr)
                + length);
    iph->id = 0;
    iph->frag_off = 0; /* No fragementation */
    iph->ttl = 65;
    iph->protocol =  IPPROTO_ICMP;
    iph->saddr = source;
    iph->daddr = dest;
    iph->check = 0;
    iph->check = in_cksum((unsigned short *)iph, sizeof(struct iphdr));
    /************** END IP HEADER ***************/

    /*WARNING: THE CODE BELOW SHOULD BE REPLACED BY SOMETHING MORE ROBUST
        THAT USES THE KERNEL ROUTING!
        AND USES IP_LOCAL_OUT INSTEAD OF WHAT WE ARE DOING */
    /* Set up the net-device for the new packet */
/* In my code, there was a function findDeviceByIp that does the routing and return which net_device to use for transmission*/
    newPacket->dev = findDeviceByIP(source);
    if(newPacket->dev == NULL)
    {   
        kfree_skb(newPacket);
        return SEND_DEV_FAIL;
    }

    /************** ETH HEADER ***************/
    eth = (struct ethhdr *) skb_push(newPacket, sizeof(struct ethhdr));
    if(strcmp(newPacket->dev->name, "wlan0") == 0)
        memcpy(eth->h_dest, wifiMAC, 6);
    else if(strcmp(newPacket->dev->name, "eth0") == 0)
        memcpy(eth->h_dest, etherMAC, 6);
    else
    {
        kfree_skb(newPacket);
        return SEND_FAIL_SEND;
    }
    memcpy(eth->h_source, newPacket->dev->dev_addr, 6);
    eth->h_proto = htons(ETH_P_IP);
    /************** END ETH HEADER ***************/

    dev_queue_xmit(newPacket);/* Transmite the packet */
    /* END OF THE WARNING AREA */

    ++sendCounter;
    return SEND_SUCCESS;
}

答案 1 :(得分:0)

Linux内核提供了很多辅助函数来处理skb。这取决于您想要使用哪个用例。

skb_clone ==>复制skbuff标头并递增数据缓冲区的引用计数器。如果您只想修改skbuff标题,那么可以使用skb_clone

skb_copy ==>复制skbuff标头,数据缓冲区以及片段。当您有兴趣修改主缓冲区和片段缓冲区中的数据时使用

pskb_copy ==>复制skbuff标头+只有数据缓冲区而不是片段,因此如果你想修改除片段缓冲区之外的skb,那么你可以使用这个。这也是争论的余地。

更好地阅读linux内核(net / core / skbuff.c)提供的辅助函数,以便有效地执行skb操作并避免任何陷阱。