首页 文章

如何将数据包从NF_INET_PRE_ROUTING移动到NF_INET_POST_ROUTING?

提问于
浏览
2

我有一个使用netfilter钩子的内核模块 . 目标是将数据包转发到另一个目的地 . 正如我所看到的设计来自外部的数据包 daddr 设置为我的服务器IP通过NF_INET_PRE_ROUTING然后假设排队等待本地应用程序 . 在NF_INET_PRE_ROUTING上,我更改了特定的数据包(检测我自己的协议),并将 daddr 替换为远程服务器IP和 saddr 替换为我的服务器IP . 我想从内核模块本身做到这一点,但无法找到将现有数据包移动到另一个路由点( NF_INET_FORWARDNF_INET_LOCAL_OUT 或甚至 NF_INET_POST_ROUTING )或创建新数据包并将其插入TCP / IP堆栈的方法,就像它从服务器本身发送 . 目前,数据包只是在第一次挂钩后才进入黑洞 . 我不认为它会以某种方式进入任何其他钩子 . 我怎么能这样做?

我当前的代码(测试远程服务器与客户端相同的代码):

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <net/route.h>

#define DEBUG 1

static struct nf_hook_ops nfho;

static __be32 srv_addr = 0x620aa8c0;
static __be32 cli_addr = 0x630aa8c0;
static __be32 rem_addr = 0x630aa8c0;

static 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 *)){
    struct iphdr *ip_header;
    struct tcphdr *tcp_header;

    ip_header = (struct iphdr *)skb_network_header(skb);
    skb_set_transport_header(skb, ip_header->ihl * 4);
    tcp_header = (struct tcphdr *)skb_transport_header(skb);

#if DEBUG > 0
if(tcp_header->dest == ntohs(80) || tcp_header->source == ntohs(80))//(ip_header->saddr == cli_addr || ip_header->saddr == srv_addr || ip_header->saddr == rem_addr) && 
printk(KERN_INFO "[HTTP] Got a packet to %d.%d.%d.%d:%d from %d.%d.%d.%d:%d in hooknum=%d\n", 
    ip_header->daddr & 0x000000FF,
    (ip_header->daddr & 0x0000FF00) >> 8,
    (ip_header->daddr & 0x00FF0000) >> 16,
    (ip_header->daddr & 0xFF000000) >> 24,
    ntohs(tcp_header->dest), 
    ip_header->saddr & 0x000000FF,
    (ip_header->saddr & 0x0000FF00) >> 8,
    (ip_header->saddr & 0x00FF0000) >> 16,
    (ip_header->saddr & 0xFF000000) >> 24,
    ntohs(tcp_header->source),
    hooknum);
#endif

    if(ip_header->saddr == cli_addr && tcp_header->dest == ntohs(80)){
        ip_header->daddr = rem_addr;
        ip_header->saddr = srv_addr;
        ip_header->check = 0;
        ip_send_check(ip_header);
        tcp_header->check = 0;
        tcp_header->check = tcp_v4_check(skb->len - 4*ip_header->ihl, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcp_header, skb->len - 4*ip_header->ihl,0));

        okfn(skb);
        return NF_STOP;
    }
    if(ip_header->saddr == rem_addr && tcp_header->source == ntohs(80)){
        ip_header->daddr = cli_addr;
        ip_header->saddr = srv_addr;
        ip_header->check = 0;
        ip_send_check(ip_header);
        tcp_header->check = 0;
        tcp_header->check = tcp_v4_check(skb->len - 4*ip_header->ihl, ip_header->saddr, ip_header->daddr, csum_partial((char *)tcp_header, skb->len - 4*ip_header->ihl,0));

        okfn(skb);
        return NF_STOP;
    }
    return NF_ACCEPT;
}




static int __init init_main(void) {
    nfho.hook = hook_func;
    nfho.hooknum = 0;
    nfho.pf = PF_INET;
    nfho.priority = NF_IP_PRI_FIRST;
    nf_register_hook(&nfho);
#if DEBUG > 0
    printk(KERN_INFO "[HTTP] Successfully inserted protocol module into kernel.\n");
#endif
    return 0;
}

static void __exit cleanup_main(void) {
    nf_unregister_hook(&nfho);
#if DEBUG > 0
    printk(KERN_INFO "[HTTP] Successfully unloaded protocol module.\n");
#endif
}

module_init(init_main);
module_exit(cleanup_main);

MODULE_LICENSE("GPL v3");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

3 回答

  • 0

    亚历克斯,我遇到了你试图从内核发送损坏的skb的完全相同的问题 . 我经历了相同的思考过程,但找不到能够正确处理传出数据包路由的优雅解决方案 . 直到我发现我也可以在内核中使用套接字 .

    使用 socket.h 中的 sock_create 在内核模块中创建一个原始套接字,如下所示:

    struct socket *mySock;
    if ( sock_create(PF_INET, SOCK_RAW, IPPROTO_RAW, &mySock) != 0 )
    {
        /* Error creating socket */
    }
    

    修改IP标头后,您可以使用函数使用 sock_sendmsg 发送skb:

    int sock_send(struct socket *sock, struct sockaddr_in *addr, struct iovec *iov, int iovlen, int totalLen)
    {
        struct msghdr msg;
        mm_segment_t oldfs;
        int size = 0;
    
        if (sock == NULL || sock->sk == NULL)
        {
            return 0;
        }
    
        msg.msg_flags = 0;
        msg.msg_name = addr;
        msg.msg_namelen = sizeof(struct sockaddr_in);
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
        msg.msg_iov = iov;
        msg.msg_iovlen = iovlen;
    
        /* Set to kernel data segment since sock_sendmsg expects user space pointers */
        oldfs = get_fs();
        set_fs(KERNEL_DS);
    
        size = sock_sendmsg(sock, &msg, totalLen);
        set_fs(oldfs);
        return size;
    }
    

    请记住 IPPROTO_RAW 套接字,您必须自己创建IP标头,但是您已经在skb中有一个 . 现在,您只需创建并填充 struct iovec 数组并将其传递给 sock_send .

    对于 struct sockaddr_in *addr ,使用与IP标头相同的目标地址:

    struct sockaddr_in addr = { .sin_family = PF_INET,
                                .sin_port   = 0,  /* 0 for RAW socket */
                                .sin_addr   = { .s_addr = dstAddr } };
    

    记得返回 NF_DROP 或释放skb并返回 NF_STOLEN 以便在完成skb后将其清理干净 .

  • 1

    我找不到任何方式以一种或多或少的正确方式以编程方式转发数据包 . 我找到的唯一方法(似乎是非常流行的解决方案)是手动修改 skb_buff 中的所有相关字段,并通过 dev_queue_xmit 发送更改的数据包 . 这种方式似乎不可能从内核模块中找到合适的路径(或者我不知道这样的方式) . 内核TCP / IP堆栈的源代码也显示了 ip_forward 函数的存在,该函数不能从内核模块的任何部分获得,我试图重现该函数最终将一半的TCP / IP堆栈拖入模块 . 这个函数可能是程序化数据包转发的理想选择,因为它只需要几个参数,并且所有参数都会自行改变所有需要的数据包部分 .

    无论如何 . 我自己的固定代码现在看起来像这样:

    #include <linux/types.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/netfilter_ipv4.h>
    #include <linux/ip.h>
    #include <linux/tcp.h>
    #include "my_mod.h"
    
    
    #define DRIVER_AUTHOR "AlexKey"
    #define DRIVER_DESC "HTTP packets manipulations"
    
    #define DEBUG 1
    
    static struct nf_hook_ops nfho;
    
    static 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 *)){
        struct iphdr *ip_header;
        struct tcphdr *tcp_header;
        struct ethhdr *eth_header;
    
        u32 saddr, daddr;
        u16 source, dest;
    
        /* Get all the headers */
        eth_header = (struct ethhdr *)skb_mac_header(skb);
        ip_header = (struct iphdr *)skb_network_header(skb);
        skb_set_transport_header(skb, ip_header->ihl * 4);
        tcp_header = (struct tcphdr *)skb_transport_header(skb);
    
        /* If the packet source or dest are not 80 then the packet is not for us :) */
        if(tcp_header->source != ntohs(80) && tcp_header->dest != ntohs(80))
            return NF_ACCEPT;
    
    #if DEBUG > 0
    printk(KERN_INFO "[HTTP] Got packet on %d from %d\n", htons(tcp_header->dest), htons(tcp_header->source));
    #endif
    
        saddr = ip_header->saddr;
        daddr = ip_header->daddr;
    
        source = tcp_header->source;
        dest = tcp_header->dest;
    
        /* In link layer header change sender mac to our ethernet mac
            and destination mac to sender mac :) ping-pong */
        memcpy(eth_header->h_dest,eth_header->h_source,ETH_ALEN);
        memcpy(eth_header->h_source,skb->dev->dev_addr,ETH_ALEN);
    
        /* Set new link layer headers to socket buffer */
        skb->data = (unsigned char *)eth_header;
        skb->len += ETH_HLEN;
    
        /* Setting it as outgoing packet */
        skb->pkt_type = PACKET_OUTGOING;
    
        /* Swap the IP headers sender and destination addresses */
        memcpy(&ip_header->saddr, &daddr, sizeof(u32));
        memcpy(&ip_header->daddr, &saddr, sizeof(u32));
    
    
        /* If transmission suceeds then report it stolen
            if it fails then drop it */
        if(dev_queue_xmit(skb)==NET_XMIT_SUCCESS){
    #if DEBUG > 0
    printk(KERN_INFO "[HTTP] Successfully sent packet\n");
    #endif
            return NF_STOLEN;
        } else {
    #if DEBUG > 0
    printk(KERN_INFO "[HTTP] Sending failed\n");
    #endif
            return NF_DROP;
        }
    
    }
    
    
    static int __init init_main(void) {
        nfho.hook = hook_func;
        nfho.hooknum = 0;
        nfho.pf = PF_INET;
        nfho.priority = NF_IP_PRI_FIRST;
        nf_register_hook(&nfho);
    
    #if DEBUG > 0
        printk(KERN_INFO "[HTTP] Successfully inserted protocol module into kernel.\n");
    #endif
        return 0;
    }
    
    static void __exit cleanup_main(void) {
        nf_unregister_hook(&nfho);
    #if DEBUG > 0
        printk(KERN_INFO "[HTTP] Successfully unloaded protocol module.\n");
    #endif
    }
    
    module_init(init_main);
    module_exit(cleanup_main);
    
    MODULE_LICENSE("GPL v3");
    MODULE_AUTHOR(DRIVER_AUTHOR);
    MODULE_DESCRIPTION(DRIVER_DESC);
    

    我很想听到对此代码的任何修改 .

  • 0

    使用内核钩子执行此操作的方法是手动修改skb_buff中的所有相关字段,并通过dev_queue_xmit传输更改的数据包 . 当您尝试创建一个“凭空”到目的地的数据包时,您需要小心路由 . 假设从用户空间的角度正确设置了路由,那么启用数据包的所有操作就是在dev_queue_xmit()之前使用ip_route_output() . 例如:

    struct rtable *rt;
    struct net *nt;
    
    // do the packet mangling, headers copying here
    
    skb->dev = new_dev;  // new_dev is the iface through which to reach the dest
    nt = dev_net(skb->dev);
    rt = ip_route_output(nt, ip_header->daddr, ip_header->saddr, 
        RT_TOS(ip_header->tos), skb->dev_ifindex);
    
    skb_dst_set(skb, &(rt->dst));
    
    return NF_ACCEPT; // pass the mangled packet on, business as usual
    

相关问题