读书人

在内核中结构一个数据包让指定的网卡

发布时间: 2012-07-23 09:42:19 作者: rapoo

在内核中构造一个数据包,让指定的网卡来接收。
网上在内核中构造一个数据包然后通过一个网络接口发送出去的例子有很多,但是在内核中构造一个数据包,让网络接口来接收的方法的文章不多。
文章参考了: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 (root@localhost.localdomain)");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 *)){// DEBUGprintk(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;}

读书人网 >操作系统

热点推荐