如何从NF移动数据包

我有一个利用netfilter钩子的内核模块。 目标是将数据包转发到另一个目的地。 正如我可以看到设计来自外部的数据包与设置为我的服务器的daddr IP通过NF_INET_PRE_ROUTING,然后假设排队等候本地应用程序。 在NF_INET_PRE_ROUTING上,我更改特定的数据包(检测我自己的协议),并用远程服务器IP和saddr替换daddr与我的服务器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=%dn", 
    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);

亚历克斯,我有完全相同的问题,你试图从内核发送mangled 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。


我无法找到任何方式以一种或多或少的正确方式编程转发数据包。 我发现的唯一方法(似乎是非常流行的解决方案)是手动修改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 %dn", 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 packetn");
#endif
        return NF_STOLEN;
    } else {
#if DEBUG > 0
printk(KERN_INFO "[HTTP] Sending failedn");
#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);

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


使用内核挂钩来完成此操作的方法是手动修改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
链接地址: http://www.djcxy.com/p/84817.html

上一篇: how to move packet from NF

下一篇: Are there any good explanations of kernel schedulers?