如何从NF移动数据包
我有一个利用netfilter钩子的内核模块。 目标是将数据包转发到另一个目的地。 正如我可以看到设计来自外部的数据包与设置为我的服务器的daddr
IP通过NF_INET_PRE_ROUTING,然后假设排队等候本地应用程序。 在NF_INET_PRE_ROUTING上,我更改特定的数据包(检测我自己的协议),并用远程服务器IP和saddr
替换daddr
与我的服务器IP。 我想从内核模块本身做到这一点,但无法找到将现有数据包移动到另一个路由点( NF_INET_FORWARD
或NF_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