【问题标题】:how to move packet from NF_INET_PRE_ROUTING to NF_INET_POST_ROUTING?如何将数据包从 NF_INET_PRE_ROUTING 移动到 NF_INET_POST_ROUTING?
【发布时间】:2014-03-28 01:15:30
【问题描述】:

我有一个使用 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 #include #include #include #include #include #include #include #include #include #include #define 调试 1 静态结构 nf_hook_ops nfho; 静态__be32 srv_addr = 0x620aa8c0; 静态 __be32 cli_addr = 0x630aa8c0; 静态__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 *)){ 结构 iphdr *ip_header; 结构 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); #如果调试> 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] 在 hooknum=%d\n 中从 %d.%d.%d.%d:%d 收到一个数据包到 %d.%d.%d.%d:%d", 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), 钩号); #万一 if(ip_header->saddr == cli_addr && tcp_header->dest == ntohs(80)){ ip_header->daddr = rem_addr; ip_header->saddr = srv_addr; ip_header->检查= 0; ip_send_check(ip_header); tcp_header->检查= 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); 返回 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->检查= 0; ip_send_check(ip_header); tcp_header->检查= 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); 返回 NF_STOP; } 返回 NF_ACCEPT; } 静态 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); #如果调试> 0 printk(KERN_INFO "[HTTP] 协议模块成功插入内核。\n"); #万一 返回0; } 静态无效__exit cleanup_main(无效){ nf_unregister_hook(&nfho); #如果调试> 0 printk(KERN_INFO "[HTTP] 成功卸载协议模块。\n"); #万一 } 模块初始化(初始化主); module_exit(cleanup_main); MODULE_LICENSE("GPL v3"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC);

【问题讨论】:

  • 你不能用 iptables 达到同样的效果吗?
  • @rakib 不,因为iptables 只路由数据包,它不会改变它们。也许您的意思是 netfilter_queue 用户空间库?我可以使用它,但我首先在内核模块中寻找解决方案。

标签: c sockets tcp linux-kernel netfilter


【解决方案1】:

我找不到任何以或多或少正确的方式以编程方式转发数据包的方法。 我发现的唯一方法(似乎是非常流行的解决方案)是手动修改skb_buff 中的所有相关字段并通过dev_queue_xmit 传输更改的数据包。这种方式不好,因为它没有实现为数据包找到一个好的路由。 F.e.如果相邻网络包含许多实际上可用于数据包路由的节点,则似乎无法从内核模块找到正确的路由(或者我不知道这种方式)。此外,内核 TCP/IP 堆栈的源代码显示存在 ip_forward 函数,该函数在内核模块的任何部分都不可用,我尝试重现该函数最终将 TCP/IP 堆栈的一半拖到模块中。此函数可能是编程数据包转发的理想选择,因为它只需要几个参数,并且所有参数都会自行更改数据包的所有需要​​部分。

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

#include #include #include #include #include #include #include "my_mod.h" #define DRIVER_AUTHOR "AlexKey" #define DRIVER_DESC "HTTP 数据包操作" #define 调试 1 静态结构 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 *)){ 结构 iphdr *ip_header; 结构 tcphdr *tcp_header; 结构 ethhdr *eth_header; u32 萨德尔,爸爸; u16 源,目标; /* 获取所有标题 */ 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); /* 如果数据包源或目标不是 80,则数据包不适合我们:) */ if(tcp_header->source != ntohs(80) && tcp_header->dest != ntohs(80)) 返回 NF_ACCEPT; #如果调试> 0 printk(KERN_INFO "[HTTP] Got packet on %d from %d\n", htons(tcp_header->dest), htons(tcp_header->source)); #万一 saddr = ip_header->saddr; daddr = ip_header->daddr; 源 = tcp_header->源; dest = tcp_header->dest; /* 在链路层标头中将发送者 mac 更改为我们的以太网 mac 和目标mac到发件人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); /* 设置新的链路层头到套接字缓冲区 */ skb->data = (unsigned char *)eth_header; skb->len += ETH_HLEN; /* 设置为输出数据包 */ skb->pkt_type = PACKET_OUTGOING; /*交换IP头发送者和目标地址*/ memcpy(&ip_header->saddr, &daddr, sizeof(u32)); memcpy(&ip_header->daddr, &saddr, sizeof(u32)); /* 如果传输成功则报告它被盗 如果失败则丢弃它 */ 如果(dev_queue_xmit(skb)==NET_XMIT_SUCCESS){ #如果调试> 0 printk(KERN_INFO "[HTTP] 成功发送数据包\n"); #万一 返回 NF_STOLEN; } 别的 { #如果调试> 0 printk(KERN_INFO "[HTTP] 发送失败\n"); #万一 返回 NF_DROP; } } 静态 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); #如果调试> 0 printk(KERN_INFO "[HTTP] 协议模块成功插入内核。\n"); #万一 返回0; } 静态无效__exit cleanup_main(无效){ nf_unregister_hook(&nfho); #如果调试> 0 printk(KERN_INFO "[HTTP] 成功卸载协议模块。\n"); #万一 } 模块初始化(初始化主); module_exit(cleanup_main); MODULE_LICENSE("GPL v3"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC);

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

【讨论】:

    【解决方案2】:

    Alex,我遇到了与您尝试从内核发送损坏的 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 并在完成 skb 后返回 NF_STOLEN 以清理它。

    【讨论】:

    • 是的,我知道我可以在内核模块中使用套接字。此外,我可以将整个应用程序编写为内核模块。但这不是理性的方法。这会产生额外的开销,而这正是我试图通过仅使用内核现有函数来避免的。因此,虽然这绝对是可行的方法,但它并不是我想要的。
    • 另一方面,我发现IP堆栈已经有一个发送数据包的功能,唯一的问题是它没有被导出,因此不能从其他模块使用。我想尝试使用导出的这个函数来编译内核,如果它有效,请提交我的内核补丁。 (然后祈祷它被接受哈哈)
    • 在跟踪内核源代码后,我也遇到了那个函数,以为我终于找到了答案,结果却失望了。就我而言,我必须在内核模块中做所有事情,所以我真的别无选择,只能使用套接字。
    【解决方案3】:

    使用内核挂钩的方法是手动修改 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
    

    【讨论】:

      猜你喜欢
      • 2018-08-10
      • 2012-05-27
      • 2016-12-11
      • 1970-01-01
      • 2019-02-27
      • 2019-02-26
      • 2022-07-29
      • 1970-01-01
      • 2021-08-21
      相关资源
      最近更新 更多