如何使用netfilter钩子在内核空间中回显数据包?

时间:2012-10-25 14:40:17

标签: c linux linux-kernel netfilter

我想在内核空间中回显一个数据包。我在这台机器上使用端口6000运行一个echo服务器。 现在,客户端在另一台将数据发送到echo服务器的计算机上运行。现在,我想要做的是从内核空间回送数据包。我不想用数据包打扰服务器,它将从内核空间静默回显。我在下面提出我的代码:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netdevice.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/mm.h>
#include <linux/err.h>
#include <linux/crypto.h>
#include <linux/init.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/route.h>
#include <net/checksum.h>
#include <linux/netfilter_ipv4.h>

#define IP_HDR_LEN 20
#define UDP_HDR_LEN 8
#define TOT_HDR_LEN 28

static unsigned int pkt_echo_begin(unsigned int hooknum,
                        struct sk_buff *skb,
                        const struct net_device *in,
                        const struct net_device *out,
                        int (*okfn)(struct sk_buff *));

static struct nf_hook_ops pkt_echo_ops __read_mostly = {
    .pf = NFPROTO_IPV4,
    .priority = 1,
    .hooknum = NF_INET_PRE_ROUTING,
    .hook = pkt_echo_begin,
};

static int __init pkt_echo_init(void)
{
    printk(KERN_ALERT "\npkt_echo module started ...");
    return nf_register_hook(&pkt_echo_ops);
}

static void __exit pkt_echo_exit(void)
{
    nf_unregister_hook(&pkt_echo_ops);
    printk(KERN_ALERT "pkt_echo module stopped ...");
}

static unsigned int pkt_echo_begin (unsigned int hooknum,
                        struct sk_buff *skb,
                        const struct net_device *in,
                        const struct net_device *out,
                        int (*okfn)(struct sk_buff *))
{
    struct iphdr *iph;
    struct udphdr *udph;
    unsigned char *data;

    unsigned char *temp;

    __u16 dst_port, src_port;
    int data_len;



    if (skb) {
        iph = (struct iphdr *) skb_header_pointer (skb, 0, 0, NULL);

        if (iph && iph->protocol &&(iph->protocol == IPPROTO_UDP)) {
            udph = (struct udphdr *) skb_header_pointer (skb, IP_HDR_LEN, 0, NULL);
            src_port = ntohs (udph->source);
            dst_port = ntohs (udph->dest);

            if (dst_port == 6000) {
                printk(KERN_ALERT "\nUDP packet goes in");

                data = (unsigned char *) skb_header_pointer (skb, TOT_HDR_LEN, 0, NULL);
                data_len = skb->len - TOT_HDR_LEN;

                struct sk_buff *newskb;
                struct iphdr *newiph;
                struct udphdr *newudph;
                unsigned char *newdata;
                unsigned int newdata_len;

                newskb = skb_copy(skb, GFP_ATOMIC);
                newiph = (struct iphdr *) skb_header_pointer (newskb, 0, 0, NULL);
                newudph = (struct udphdr *) skb_header_pointer (newskb, IP_HDR_LEN, 0, NULL);
                newdata = (unsigned char *) skb_header_pointer (newskb, TOT_HDR_LEN, 0, NULL);

                newiph->saddr = iph->daddr;
                newiph->daddr = iph->saddr;

                newudph->source = udph->dest;
                newudph->dest = udph->source;

                struct sk_buff *tempskb;

                tempskb = skb_copy(skb, GFP_ATOMIC);

                *tempskb = *skb;
                *skb = *newskb;
                *newskb = *tempskb;

                kfree_skb(newskb);

            }
        }
    }
    return NF_ACCEPT;
}


module_init(pkt_echo_init);
module_exit(pkt_echo_exit);

MODULE_AUTHOR("Rifat Rahman Ovi: <rifatrahmanovi@gmail.com>");
MODULE_DESCRIPTION("Echoing a packet from kernel space.");
MODULE_LICENSE("GPL");

现在我不明白需要什么。我在PRE_ROUTING钩子中捕获了数据包。 然后我创建了一个新的skb,从旧的skb填充它,然后改变了 源地址(saddr),目标地址(daddr),源端口(源)和目标 port(dest),以便可以从PRE_ROUTING挂钩转发数据包。作为路由 在数据包从钩子传递之后做出决定,我寻找要转发到其原始源的数据包。但由于某种原因,它不是那样做的。收到数据包,一切都被更改,但数据包似乎没有倒退。我不明白缺少什么,以及使其发挥作用所需要的东西。更具体地说,从PRE_ROUTING钩子发送数据包到网络需要什么?

2 个答案:

答案 0 :(得分:5)

很多都不见了。

首先,您使用的网络过滤器钩子是路由前捕获传入数据包,因此除非你使用一些内核函数来发送你已经建立了包,返回NF_ACCEPT只会让你改变了数据包(或没” t)继续前进(这是本地系统,而不是它) 阅读dev_queue_xmit(struct sk_buff *)之类的函数,但请注意,在使用此函数之前,您的SKB必须具有链接层标头,因为此函数实际上将您的数据包排队到队列中以立即发送到NIC,这是您的工作设置链接层地址。

其次,请记住,在更改IP标头地址后,您必须重新计算数据包的校验和,否则您的数据包将在另一端被丢弃。

第三,请注意,在内核空间中尝试执行的操作在很大程度上被认为是非常糟糕的做法。 内核模块存在是有原因的,这不是其中之一,netfilter是一个很好的工具,但它并不是真的应该用于发送正常流量。

修改

阅读您的最新评论;我建议您阅读有关libPCap库的内容,它应该能够很好地为您服务,并且仍然可以将您的工作放在正确的位置,即用户空间。

答案 1 :(得分:1)

除了上一个答案之外,另一种可用于从netfilter回调中回显UDP数据包的技术是:

使用以下命令将数据包作为新的UDP数据包发回:

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)

在net / socket.c中

或使用netpoll库,如答案here中所述。

可以使用NF_DROP删除原始数据包。

在netfilter回调中,在“中断上下文”中运行可以发送数据包,但是无法接收它们(因为每次尝试等待都会导致内核崩溃)。 出于这个原因,我提出的解决方案使用UDP,但无法使用TCP(TCP握手要求必须接收ACK消息)。

无论如何,正如已经说过的那样,在内核空间中做这种事情是很糟糕的,应该只用于学习目的。