文章参考了:http://bbs.chinaunix.net/thread-1931661-1-1.html,谢谢该文的作者以及回帖者。
本人经历的项目中有个很奇怪的需求。主机有2个网络接口,要求从一个网络接口接收数据,内核对收到的数据进行修改等操作,然后由另外一个网络接口来接收这些修改后的数据。最基本的想法就是调用netif_rx函数来实现,但是本人以前一直没有成功的调用netif_rx函数,调用时出现死机,直到遇到上面那篇文章。
参考代码如下:
// eth1是客户端的网关,eth0是进行模拟接收的适配器。 #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/netfilter.h> #include <linux/skbuff.h> #include <linux/ip.h> #include <linux/inet.h> #include <linux/netdevice.h> #include <linux/inetdevice.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <linux/netfilter_ipv4.h> #include <linux/param.h> #include <linux/completion.h> // for DECLARE_COMPLETION() #include <linux/sched.h> // for daemonize() and set_current_state() #include <linux/delay.h> #include <linux/in.h> #include <linux/udp.h> #include <linux/tcp.h> #include <linux/spinlock.h> #include <linux/timer.h> #include <linux/etherdevice.h> #include <net/ip.h> #include <net/tcp.h> #include <net/udp.h> #include <net/dst.h> // MODULE_DESCRIPTION("My kernel module"); MODULE_AUTHOR("root ([email protected])"); MODULE_LICENSE("Dual BSD/GPL"); // // 配置参数 unsigned int ETH0= 0; unsigned int MASK0= 0; unsigned int ETH1= 0; unsigned int MASK1= 0; // 设备 struct net_device *dev0 =0; struct net_device *dev1 =0; struct in_device *indev0=0; struct in_device *indev1=0; struct in_ifaddr *ifr0 =0; struct in_ifaddr *ifr1 =0; // 钩子处理函数 unsigned int sendip(unsigned int hook, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { // DEBUG printk(KERN_EMERG "send_ip \n"); return NF_ACCEPT; } // 钩子处理函数 unsigned int getip(unsigned int hook, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { int ret; struct ethhdr *peth=NULL; struct iphdr *pip=NULL; struct iphdr *p=NULL; struct sk_buff *skb; if(pskb==NULL) return NF_DROP; if((*pskb)==NULL) return NF_DROP; // (*pskb)->len是IP包的长度(包括IP头) if(memcmp(in->name,"eth0",4)==0) { }else { if(pip->daddr == ETH0) { // 让eth0来接收 skb = alloc_skb((*pskb)->len + 2 + ETH_HLEN,GFP_ATOMIC); // 新的IP包 skb_reserve(skb,2); skb_put(skb,(*pskb)->len + ETH_HLEN); memcpy(skb->data,(*pskb)->data - ETH_HLEN,(*pskb)->len + ETH_HLEN); skb->mac.raw = skb->data; skb->mac_len = ETH_HLEN; skb->len = (*pskb)->len + ETH_HLEN; skb->ip_summed = CHECKSUM_NONE; skb->pkt_type = PACKET_HOST; skb->protocol = __constant_htons(ETH_P_IP); skb->dev = dev0; // 关键,以前没有成功就是下面的参数没有设置。 skb_shinfo(skb)->nr_frags=0; skb_shinfo(skb)->frag_list=NULL; skb_shinfo(skb)->frags[0].page=NULL; skb_pull(skb,ETH_HLEN); skb_reset_network_header(skb); ret=netif_rx(skb); dev0->last_rx = jiffies; //printk(KERN_EMERG "ret=%d , %d \n",ret,gi++); return NF_STOLEN; } } return NF_ACCEPT; } struct nf_hook_ops pre_ops = { .hook = getip, .pf = PF_INET, .hooknum = NF_IP_PRE_ROUTING, .priority = NF_IP_PRI_FIRST, }; struct nf_hook_ops post_ops = { .hook = sendip, .pf = PF_INET, .hooknum = NF_IP_LOCAL_OUT, .priority = NF_IP_PRI_FIRST, }; static int gatekernel_init_module(void) { printk(KERN_EMERG "Module Init.\n"); // 初始化设备对象 dev0 = dev_get_by_name("eth0"); if(!dev0) { printk( KERN_EMERG "Counld not get dev0.\n"); return -1; } ETH0 = inet_select_addr(dev0,0,RT_SCOPE_UNIVERSE); indev0 = (struct in_device*)dev0->ip_ptr; ifr0= indev0->ifa_list; MASK0 = ifr0->ifa_mask; dev1 = dev_get_by_name("eth1"); if(!dev1) { printk( KERN_EMERG "Counld not get dev1.\n"); return -1; } ETH1 = inet_select_addr(dev1,0,RT_SCOPE_UNIVERSE); indev1 = (struct in_device*)dev1->ip_ptr; ifr1= indev1->ifa_list; MASK1 = ifr1->ifa_mask; nf_register_hook(&pre_ops); nf_register_hook(&post_ops); return 0; } // 反初始化 static void gatekernel_exit_module(void) { // 取消钩子函数 nf_unregister_hook(&pre_ops); nf_unregister_hook(&post_ops); printk(KERN_EMERG "bye.\n"); } module_init(gatekernel_init_module); module_exit(gatekernel_exit_module);
原帖的内容是:
正在做一个项目 里面需要有这样一个步骤
1.在网络层将数据包截住并修改
2.重新把修改后的数据包交付给协议栈底端 让它重新经过协议栈
开始阶段 为了实现简单 用ICMP报文作为拦截对象 且截住的数据包不做过多修改 只将协议头部若干指针修改 让它的报文头状态变为好像刚刚从网卡到来的时候一样
实现的时候 用的是netfilter的PRE_ROUTING钩子 检查每一个数据包 如果是ICMP报文且类型为8 即ECHO报文 就将报文截获 复制一个 修改头部信息 然后重新调用netif_rx 让协议栈接收 原来的报文使用NF_STOLEN处理
注册在PRE_ROUTING钩子处的回调函数如下
static unsigned int pre_routing_hook(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, \ const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *skb; struct sk_buff *new_skb; //unsigned char src_ip[4]; //unsigned char dst_ip[4]; // struct icmphdr *icmp_hdr; int icmp_hdr_off; // struct iphdr *ip_hdr; int ip_hdr_off; struct ethhdr *eth_hdr; struct net_device *NIC = NULL; const char nic[5] = "eth0"; // printk(KERN_INFO "inside the filter\n"); int index; int rx_ret; skb = skb_copy(*pskb, GFP_ATOMIC); NIC = dev_get_by_name(nic); if (!NIC) { return NF_ACCEPT; } if (skb->nh.iph->protocol == IPPROTO_ICMP) { // get ip header ip_hdr = (struct iphdr *)skb->nh.iph; // calcutate ip header length ip_hdr_off = ip_hdr->ihl << 2; // get icmp header icmp_hdr = (struct icmphdr *)(skb->data + ip_hdr_off); // only modify icmp request // #define ICMP_ECHO 8 /* Echo Request */ // in <linux/icmp/h> if (icmp_hdr->type == ICMP_ECHO) { new_skb = alloc_skb(skb->len + 5 + ETH_HLEN, GFP_ATOMIC); skb_reserve(new_skb, 2); skb_put(new_skb, skb->len + ETH_HLEN); memcpy(new_skb->data, skb->data - ETH_HLEN, skb->len + ETH_HLEN); new_skb->mac.raw = new_skb->data; // commented //skb_pull(new_skb, ETH_HLEN); new_skb->mac_len = ETH_HLEN; new_skb->len = skb->len + ETH_HLEN; new_skb->ip_summed = CHECKSUM_UNNECESSARY; new_skb->pkt_type = PACKET_HOST; new_skb->protocol = htons(ETH_P_IP); new_skb->dev = NIC; skb_shinfo(new_skb)->nr_frags = 0; skb_shinfo(new_skb)->frag_list = NULL; skb_shinfo(new_skb)->frags[0].page = NULL; rx_ret = netif_rx(new_skb); NIC->last_rx = jiffies; kfree_skb(skb); return NF_STOLEN; } } kfree_skb(skb); return NF_ACCEPT; }