在内核中构造一个数据包,让指定的网卡来接收。

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


原帖的内容是:
[color=brown]正在做一个项目 里面需要有这样一个步骤

1.在网络层将数据包截住并修改
2.重新把修改后的数据包交付给协议栈底端 让它重新经过协议栈

开始阶段 为了实现简单 用ICMP报文作为拦截对象 且截住的数据包不做过多修改 只将协议头部若干指针修改 让它的报文头状态变为好像刚刚从网卡到来的时候一样

实现的时候 用的是netfilter的PRE_ROUTING钩子 检查每一个数据包 如果是ICMP报文且类型为8 即ECHO报文 就将报文截获 复制一个 修改头部信息 然后重新调用netif_rx 让协议栈接收 原来的报文使用NF_STOLEN处理

注册在PRE_ROUTING钩子处的回调函数如下 [/color]


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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值