如何从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
