Linux下虚拟网络接口(VNI)设计与使用

1.功能概述

  在 Linux 中实现下图中的虚拟网络设备接口模块(VNI),在 IP 模块和以太网接口之间串接一个虚拟的 vni0 接口。如下图所示:
在这里插入图片描述

发送数据

  将 Linux 内核 IP 模块送下来的 IP 分组封装一个 VNI 头部和一个以太网帧头部,然后发给以太接口。发送数据时,直接从 IP 层取走报文,再被 VNI 模块打上 VNI 头部,通过以太网口发送出去。

接收数据

  将以太接口收到的 VNI 分组的 VNI 头部去掉,然后将 IP 分组上交给 Linux 内核的 IP 模块。发出去的数据被更改了格式,不会被识别为 IP 数据包,只能被主机识别为以太网帧,需要从数据链路层捕获。

数据分组

  VNI 分组位于以太帧头部与 IP 头部之间,由特定数字-+2 字节分组序号(初始值=0)组成。

统计打印

  利用 Ping 命令发出 100 个 ICMP 个报文,统计 VNI 模块发送和接收的分组个
数,每分钟定时打印以下信息。
  发送端:1)当前的发送分组总数;2)每分钟内的发送速率(pps,即每秒
的发送分组个数);
  接收端:1)当前的接收分组总数;2)每分钟内的接收速率(pps,即每秒
的接收分组个数)。

2.设计原理

2.1 Linux内核编程

2.1.1 内核模块

  模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。
  模块就是整个内核的一部分。内核模块可以在它所认为适当的时候,插入到内核或者从内核中删除,而且还不影响内核的正常运行,这样能够更好的适应于用户的需求。

//必要的头文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
//模块许可证声明(必须)
MODULE_LICENSE("GPL");
//模块加载函数(必须)
static int hello_init(void) {
 printk(KERN_ALERT "Hello World enter/n");
 return 0; }
//模块卸载函数(必须)
static void hello_exit(void) {
 printk(KERN_ALERT "Hello World exit/n");
}
//模块的注册
module_init(hello_init);
module_exit(hello_exit);
//声明模块的作者(可选)
MODULE_AUTHOR("Ming");
//声明模块的描述(可选)
MODULE_DESCRIPTION("This is a simple example!/n");
//声明模块的别名(可选)
MODULE_ALIAS("example");

2.1.2 Makefile

  要把一个普通的.c 文件变成我们所需要的内核文件。一般我们理解,应该是应用几条 Linux 下的命令就可以搞定(如 gcc,g++……),这里的理解是对的,我们就是需要几个命令就 OK。但是我们知道,编译这个需要敲打的命令过于多,要输入内核版本的号,路径,和编写模块的路径与信息。如果每次都输入这么多,那肯定是太麻烦。这时我们就想到了 Makefile 文件,通过它来管理一个庞大的项目是再好不过的。

KERNEL_VER = $(shell uname -r)
# the file to compile
obj-m += vni.o
# specify flags for the module compilation
EXTRA_CFLAGS = -g -O1
build: kernel_modules
kernel_modules:
 make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules
clean:
 make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean

2.2 Linux内核网络关键结构体

sk_buff
  sk_buff 结构体非常重要,它定义于 include/linux/skbuff.h 文件中,含义为“套接字缓冲区”,用于在 Linux 网络子系统中的各层之间传递数据,是 Linux 网络子系统数据传递的“中枢神经”。当发送数据包时,Linux 内核的网络处理模块必须建立一个包含要传输的数据包的sk_buff,然后将 sk_buff 递交给下层,各层在sk_buff 中添加不同的协议头直至交给网络设备发送。同样地,当网络设备从网络媒介上接收到数据包后,它必须将接收到的数据转换为 sk_buff 数据结构并传递给上层,各层剥去相应的协议头直至交给用户。
net_device
  网络设备接口层的主要功能是为千变万化的网络设备定义统一、抽象的数据结构 net_device 结构体,以不变应万变,实现多种硬件在软件层次上的统一。
  net_device 结构体在内核中指代一个网络设备,它定义于include/linux/netdevice.h 文件中,网络设备驱动程序只需通过填充 net_device 的具体成员并注册 net_device 即可实现硬件操作函数与内核的挂接。
  net_device 是一个巨大的结构体,定义于 include/linux/netdevice.h 中,包含网络设备的属性描述和操作接口,下面介绍其中的一些关键成员。

2.3内核定时器

  内核定时器是内核用来控制在未来某个时间点(基于 jiffies)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 kernel/timer.c 文件中。被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中。

struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
……
struct tvec_base *base;
/* ... */
};

  其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。当一个定时器被注册到内核之后,entry字段用来连接该定时器到一个内核链表中。

2.4 netlink 套接字

  Netlink 用于在内核模块与在用户地址空间中的进程之间传递消息的。Netlink 是一种面向数据报的消息系统,目前在 Linux 内核中有非常多应用可用于通信,包括路由、IPSEC、防火墙、netfilter 日志等。Netlink 具有以下特点:消息具有较强的扩展能力,用户可以自定义消息格式,且提供了基于事件的信号机制,允许大数据传输;支持全双工传输,允许内核主动发起传输通信;支持单
播与组播两种通信方式。
  Netlink 机制包含用户态接口与内核态接口, 其中用户态沿用标准的 socket接口,内核态则提供了专用接口。用户程序通过 Netlink 机制与内核进行通信, 流程如下图所示。
  用户态 Netlink 使用流程与常用 BSD Socket 一样,首先使用 socket 函数创建套接字,然后使用 bind 函数绑定地址,封装并使用 sendmsg 向内核发送消息,接着使用 recvmsg 接收消息,最后通过 close 函数关闭套接字,释放资源。内核态处理过程类似,发送消息时还可以根据需要选择单播或者多播。
在这里插入图片描述

2.5 netfilter

  Netfilter 是 Linux 引入的一个子系统,它作为一个通用的、抽象的框架,提供一整套的 hook 函数的管理机制,使得诸如数据包过滤、网络地址转换(NAT)和基于协议类型的连接跟踪成为了可能,其中 HOOK 机制是 Netfilter 的核心。 Netfilter 的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK,而在每个检测点上登记了一些处理函数进行处理。处理完后根据返回不同值,决定数据包是继续传输还被丢弃。
  NF_ACCEPT:继续正常传输数据报。这个返回值告诉 Netfilter 到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段  NF_DROP:丢弃该数据报,不再传输。
  NF_STOLEN:模块接管该数据报,告诉 Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且 Netfilter 应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的 sk_buff 数据结构仍然有效,只是回调函数从 Netfilter 获取了该数据包的所有权。
  NF_QUEUE:对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
  NF_REPEAT:再次调用该回调函数,应当谨慎使用这个值,以免造成死循环

2.6 libpcap抓包

  libpcap(Packet Capture Library),即数据包捕获函数库,是 Unix/Linux 平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的 API 接口,为底层网络监测提供了一个可移植的框架。著名的软件TCPDUMP就是在libpcap的基础上开发而成的。libpcap 主要由两部份组成:网络分接口(Network Tap)和数据过滤器(Packet Filter)。
  libpcap 可以实现以下功能:

  • 数据包捕获:捕获流经网卡的原始数据包
  • 自定义数据包发送:任何构造格式的原始数据包
  • 流量采集与统计:网络采集的中流量信息
  • 规则过滤:提供自带规则过滤功能,按需要选择过滤规则

3.VNI模块设计

3.1总体设计

  VNI 模块具备捕获 IP 层发往数据链路层的数据包和捕获数据链路层发送至该网卡的数据包功能。VNI 模块结构如下图所示。
  VNI 模块利用 netfilter 捕获 IP 层发往以太网接口的数据包。应用层提供 ping
命令产生 icmp 数据包,再 netfilter 的 NF_INET_LOCAL_OUT 挂接点捕获此类
icmp 数据包。
VNI 模块结构图

3.2 VNI接口实现

  VNI 分组头部字段用于在 IP 分组上插入头部信息。

//VNI 结构体
static struct VNI_ethhdr{
 //学号字段信息:0101
 unsigned char student[4];
 //VNI 分组 程序中设置为 ABCD 便于测试观察
 unsigned short vnid;
};

  VNI 统计信息结构体,统计 VNI 模块收发数据情况。

//VNI 统计收发统计信息
static struct VNI_states{
 //发送分组统计
 uint16_t vni_tx_packets;
 //接收分组统计
 uint16_t vni_rx_packets;
};

  网络接口 net_device:VNI,向内核注册 vni0 设备。

 // 分配一个 net_device 结构体
 vni_dev = alloc_netdev(0, "vni%d", 'e', ether_setup);
 //设置参数
 vni_dev->netdev_ops = &vni_dev_ops;
 vni_dev->flags |= IFF_NOARP;
 vni_dev->features |= 0x4;
 //注册
 register_netdev(vni_dev);

  netfilter 钩子挂接结构:目前程序中用到的是 IP 层的 NF_INET_LOCAL_OUT挂接点,捕获主机发出去的数据包。

static struct nf_hook_ops VNI_hooks[] ={
 //netfilter-iptables IP 层 HOOK
 {
 .hook = VNI_HookLocalOUT,
 .pf = PF_INET,
 .hooknum = NF_INET_LOCAL_OUT,
 .priority = NF_IP_PRI_FILTER - 1,
 }
};

3.3 VNI模块实现

  VNI 模块程序由内核态和用户态两部分组成。程序运行流程如下图所示。
  内核态程序:首先模块加载 netlink,建立套接字实现用户态和内核的通信;注册定时器,实现定时输出 NVI 模块收发分组的统计信息;注册 vni0 虚拟接口;建立 netfilter 钩子挂接点,实现捕获 IP 层发出数据包的功能。
  获取到发出的 ICMP 数据包,在以太网头和 IP 头之间添加 VNI 头部(6 个字节),然后直接调用 dev_queue_xmit(skb)通过以太接口输出。获取到用户态发来的 vni 数据包时,取消数据包头的 VNI 分组,恢复原始数据包,然后调用netif_rx(skb)向 IP 层提交数据包,使得通过 vni0 口可以看见数据包内容。
  用户态程序:首先创建 netlink 套接字,建立于内核端的通信;然后开启两进程,一个负责接收内核发来的 vni 模块收发分组情况,计算分组收发速率并打印输出;一个负责通过 libpcap 捕获数据链路层的 vni 数据包,然后通过 netlink发送至内核。
VNI运行流程图

4.实际测试

4.1环境

  1. 主机:两台电脑上的相同 linux 虚拟机
  2. 操作系统:6.04.1-Ubuntu Linux
  3. 内核版本:4.15.0-72-generic
  4. 抓包软件:Wireshark

4.2 VNI发送端

ping 命令信息:
  通过 ping 命令向局域网内任一台主机发送 icmp 请求,根据下图显示的结果,发出 icmp 报文长度为 84 字节。
在这里插入图片描述
发送端网络配置信息:
  发送端网络配置信息如下图所示,虚拟的 vni0 接口成功创建,以太网口 ens33存在。
在这里插入图片描述
发送端以太网口抓包信息
  根据 Wireshark 抓包信息,如下图所示,协议类型已经被成功修改为 0xf4f0类型.并且以太网头部的后 6 个字节被修改为 00 01 02 04 ab cd。为了便于观察测试,把 vni 分组序号自己修改为 ab cd 了。
在这里插入图片描述
VNI 统计信息打印
  从下图 的 VNI 打印结果来看,VNI 模块收发情况几乎一致。与发送端Wireshark 显示速率相比较,统计信息符合实际情况。
  由于发出的数据包被修改为了广播类型,导致发出去的主机也可以再次接收到该数据包。
在这里插入图片描述

4.3 VNI 接收端

  接收端网络接口配置信息,如下图所示。
在这里插入图片描述
接收端 ens33 口数据包
  接收端的 ens33 口接收到的报文仍然是 VNI(0xf4f0)报文,如下图所示。
在这里插入图片描述
接收端 libpcap 捕获数据包
  下图显示的是,接收端通过 libpcap 捕获的 vni 数据包内容。
在这里插入图片描述
接收端 VNI0 口-数据包
  从下图可以看出,通过 VNI 模块的接口成功将原始的 vni 报文还原为 icmp报文。
在这里插入图片描述
VNI 统计信息打印
  用户端通过 netlink 套接字定时打印 VNI 模块收发报文情况。从下图看出接收端主要是接收 vni 分组,测试程序中存在大量的调试输出变量信息,导致程序运行有点受影响。接收分组速率为 0.91 个,与发送端发送速率大致相等。(两边统计信息截图不在同一时刻)
在这里插入图片描述

5.程序源码

vni.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_ether.h>
#include <linux/netfilter_bridge/ebtables.h>
#include <linux/types.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/netlink.h>
#include <linux/netlink.h>
#include <net/sock.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/igmp.h>
#include <linux/ctype.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <linux/string.h>

#include <linux/jiffies.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/kernel.h>

//netlink
#define NETLINK_TEST     30
#define MSG_LEN            125
#define USER_PORT        100

//netlink
struct sock *nlsk = NULL;
char *vni_msg = "this is vni module";

//vni-timer
struct timer_list vni_timer;
static int cnt = 0;
struct timeval oldtv;

//netdev
static struct net_device *vni_dev = NULL;

//VNI结构体
static struct VNI_ethhdr {
    //学号字段信息:0124
    unsigned char student[4];
    //VNI分组 程序中设置为 ABCD 便于测试观察
    unsigned short vnid;
};

//VNI统计收发统计信息
static struct VNI_states {
    //发送分组统计
    uint16_t vni_tx_packets;
    //接收分组统计
    uint16_t vni_rx_packets;
};

struct VNI_states vni_states;

//netlink消息发送统计、
static int nl_cnt;

//以太网数据包
unsigned char eth_rcv[256];

//mac地址信息
unsigned char smac[6] = {0x00, 0x0c, 0x29, 0x39, 0x92, 0x50};
unsigned char dmac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

/*
*********************************************
*
* 向用户空间发送数据
*
*********************************************
*/
int send_usrmsg(char *pbuf, uint16_t len)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    /* 创建sk_buff 空间 */
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if (!nl_skb) {
        printk("netlink alloc failure\n");
        return -1;
    }

    /* 设置netlink消息头部 */
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if (nlh == NULL) {
        printk("nlmsg_put failaure \n");
        nlmsg_free(nl_skb);
        return -1;
    }

    /* 拷贝数据发送 */
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

    return ret;
}

/*
*********************************************
*
* VNI定时器,定时60s打印统计信息
* 发送至用户空间
*
*********************************************
*/
static void timer_handle(struct timer_list *tls)
{
    unsigned char buf[128];
    //统计频率 2*cnt.如2*5=10s,每10s统计一次
    if (++cnt >= 5) {
        //发送消息数增加
        nl_cnt++;

        //10 s到
        printk("vni:%s,cnt:%d", vni_msg, nl_cnt);

        //id*10代表经过的时间
        sprintf(buf, "%4d %4d %4d", nl_cnt, vni_states.vni_tx_packets,
                vni_states.vni_rx_packets);

        send_usrmsg(buf, strlen(buf));
        cnt = 0;
    }

    mod_timer(&vni_timer, jiffies + 2 * HZ);
}
/*
*********************************************
*
*去掉数据包的VNI头部
*
*********************************************
*/
static void VNI_Reader(const unsigned char *buf)
{
    int i = 0;
    struct sk_buff *new_skb;
    //网络设备
    struct net_device *dev = NULL;
    //IP头
    struct iphdr *iphdr = NULL;

    //以太网头
    struct ethhdr *eth = NULL;
    //数据包数据
    unsigned char data[51];
    //关闭打印调试信息,避免不必要的运行负担
    //打印数据包头部信息
    // printk("VNI-process");
    // for(i=0; i< 40; i++){
    // printk("%02x-",buf[i]);
    // if ((i + 1) % 16 == 0){
    // printk(" ");
    // }
    // }
    // 构造一个sk_buff
    new_skb = dev_alloc_skb(128);
    skb_reserve(new_skb, 80); /* align IP on 16B boundary */
    new_skb->len =  0;

    //数据包内容14+6+20
    memcpy(data, buf + 40, 50);
    skb_push(new_skb, 50);
    memcpy(new_skb->data, data, 50);

    //数据包IP头
    iphdr = skb_push(new_skb, 20);
    skb_reset_network_header(new_skb);

    memcpy(new_skb->data, buf + 20, 20);

    iphdr->version = 4;
    iphdr->ihl = 5;
    iphdr->tos = 0;
    iphdr->tot_len = htons(0x46);

    //数据包ethernet头
    eth = skb_push(new_skb, 14);
    skb_reset_mac_header(new_skb);

    memcpy(eth->h_source, buf + 6, 6);
    memcpy(eth->h_dest, buf, 6);
    eth->h_proto = htons(0x0800);
    printk("4");
    /* Write metadata, and then pass to the receive level */
    new_skb->dev = dev;
    new_skb->protocol = htons(0x0800);
    new_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */

    dev = dev_get_by_name(&init_net, "vni0");
    new_skb->dev = dev;
    //接收分组记录
    vni_states.vni_rx_packets++;
    //提交数据至VNI0口
    netif_rx(new_skb);
}

/*
*********************************************
*
* netlink数据处理回调函数
*
*********************************************
*/
static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    unsigned char *umsg = NULL;

    int i = 0;
    if (skb->len >= nlmsg_total_size(0)) {
        nlh = nlmsg_hdr(skb);
        umsg = NLMSG_DATA(nlh);
        if (umsg) {
            printk("msg:%d", nlh->nlmsg_len);
            for (i = 0; i < 90; i++) {
                //printk("%02x-",umsg[i]);
                eth_rcv[i] = umsg[i];
                // if ((i + 1) % 16 == 0)
                // {
                // printk(" ");
                // }
            }
            //数据包处理,去掉VNI头部
            VNI_Reader(eth_rcv);
        }
    }
}

/*
*********************************************
*
* 进入本地数据的钩子处理函数
* 如果能从内核抓取以太网数据包,可直接调用此函数
* 处理,但没有实现netfilter 二层抓包
* 尝试利用 netfilter-bridge,但始终没有成功
*
*********************************************
*/
static unsigned int
VNI_HookLocalIN(void *priv, struct sk_buff *skb,
                const struct nf_hook_state *state)
{
    //从eth0获取数据
    int ret = 0;
    int i = 0;
    struct net_device *dev = NULL;

    struct ethhdr *eth = eth_hdr(skb);
    struct iphdr *iphdr = ip_hdr(skb);
    //打印ip地址
    printk("ipsaddr:%08x,daddr:%08x", iphdr->saddr, iphdr->daddr);

    //打印mac地址
    printk("dmac:");
    for (i = 0; i < ETH_ALEN - 1; i++) {
        printk("%02x-", eth->h_dest[i]);
    }
    printk("%02x", eth->h_dest[ETH_ALEN - 1]);
    printk("smac");
    for (i = 0; i < ETH_ALEN - 1; i++) {
        printk("%02x-", eth->h_source[i]);
    }
    printk("%02x", eth->h_source[ETH_ALEN - 1]);


    //检测到vni分组
    if (iphdr->protocol == 0xf4f0) {
        printk("F4f0 LOCAL IN\n");
        //去掉VNI分组*/
        memmove(skb->data, skb->data + 6, 14);
        //mac_header指向当前位置
        skb->mac_header += 6;
        skb_pull(skb, 6);

        dev = dev_get_by_name(&init_net, "vni0");
        skb->dev = dev;
        //向上提交数据
        netif_rx(skb);
        ret = NF_STOLEN;
    } else {
        ret = NF_ACCEPT;
    }

    return ret;
}
/*
*********************************************
*
* 本地发出数据的钩子处理函数
*
*********************************************
*/

static unsigned int
VNI_HookLocalOUT(void *priv, struct sk_buff *skb,
                 const struct nf_hook_state *state)
{
    int nret = 1;
    struct net_device *dev = NULL;
    unsigned short type = 0xf4f0;
    unsigned char *p = NULL;

    unsigned char iprotocol, ipttl;
    unsigned short ipid, iptotlen;
    unsigned int ipsaddr, ipdaddr;

    struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);

    struct iphdr *iph = ip_hdr(nskb);

    struct VNI_ethhdr *vni;

    struct ethhdr *eth;

    ipsaddr = iph->saddr;
    ipdaddr = iph->daddr;
    iprotocol = iph->protocol;
    printk("ip:%02x", iprotocol);

    ipid = iph->id;
    printk("id:%02x", ipid);

    iptotlen = iph->tot_len;
    printk("len:%04x", iptotlen);

    ipttl = iph->ttl;
    printk("ipsaddr:%08x,daddr:%08x", ipsaddr, ipdaddr);

    if (iph->protocol != IPPROTO_ICMP) {
        //not icmp
        printk("not icmp");
        return NF_ACCEPT;
    }
    /*添加VNI分组*/
    printk("LOCAL OUT");


    if (skb_cow_head(skb, 6) < 0) {
        printk("fail");
    }

    eth = (struct ethhdr *)skb_mac_header(skb);
    iph = (struct iphdr *)skb_network_header(skb);
    skb->ip_summed = CHECKSUM_UNNECESSARY;

    skb_reserve(skb, 12);
    skb_pull(skb, 34);

    //添加IP头部

    iph = (struct iphdr *)skb_push(skb, 20);
    skb_reset_network_header(skb);

    iph->version = 4;
    iph->protocol = iprotocol;
    iph->tos = 0;
    iph->tot_len =
        iph->frag_off = 0;
    iph->id = ipid;
    iph->ttl = ipttl;
    iph->tot_len = iptotlen;
    iph->saddr = ipsaddr;
    iph->daddr = ipdaddr;
    iph->check = 0;
    iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

    //添加VNI头部
    p = skb_push(skb, 6);
    vni = (struct VNI_ethhdr *)p;
    //memmove(skb->data,skb->data+6, ETH_HLEN);

    vni->student[0] = 0x00;
    vni->student[1] = 0x01;
    vni->student[2] = 0x02;
    vni->student[3] = 0x04;
    vni->vnid = htons(0xABCD);

    eth = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr));
    skb_reset_mac_header(skb);

    memcpy(eth->h_dest, dmac, 6);
    memcpy(eth->h_source, smac, 6);
    eth->h_proto = __constant_htons(type);

    //skb->mac_header = skb->data;
    dev = dev_get_by_name(&init_net, "ens33");
    skb->dev = dev;

    if (dev_queue_xmit(skb) < 0) {
        printk("error");
        goto out;
    }

    //发送分组统计
    vni_states.vni_tx_packets++;
    nret = 0;

out:
    if (nret != 0 && skb != NULL) {
        kfree_skb(skb);
        dev_put(dev);
    }

    return NF_STOLEN;
}

/*
*********************************************
*
* nf钩子挂接结构
*
*********************************************
*/
static struct nf_hook_ops VNI_hooks[] = {
    //netfilter-bridge 二层HOOK
    {
        .hook = VNI_HookLocalIN,
        .pf = PF_BRIDGE,
        .hooknum = NF_BR_PRE_ROUTING,
        .priority = NF_BR_PRI_FIRST,
    },
    //netfilter-iptables IP层HOOK
    {
        .hook = VNI_HookLocalOUT,
        .pf = PF_INET,
        .hooknum = NF_INET_LOCAL_OUT,
        .priority = NF_IP_PRI_FILTER - 1,
    }
};


struct netlink_kernel_cfg cfg = {
    .input  = netlink_rcv_msg, /* set recv callback */
};

static const struct net_device_ops vni_dev_ops = {
    // .ndo_start_xmit = virt_net_send_packet,
};

/*
*********************************************
* *****VNI模块加载函数
*********************************************
*/
static int __init VNI_init(void)
{
    /*
    *********************************************
    * netlink通信接口建立
    *********************************************
    */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if (nlsk == NULL) {
        printk("netlink_kernel_create error !\n");
        return -1;
    }

    /*
     *********************************************
     * 内核定时器
     *********************************************
     */
    timer_setup(&vni_timer, timer_handle, 0);
    do_gettimeofday(&oldtv);
    vni_timer.expires = jiffies + 2 * HZ;
    add_timer(&vni_timer);

    /*
     *********************************************
     **VNI接口建立
     *********************************************
     */
    // 分配一个net_device结构体
    vni_dev = alloc_netdev(0, "vni%d", 'e', ether_setup);
    //设置
    vni_dev->netdev_ops = &vni_dev_ops;
    vni_dev->flags           |= IFF_NOARP;
    vni_dev->features        |= 0x4;
    //注册
    register_netdev(vni_dev);

    /*
     *********************************************
     * netfilter 钩子函数注册
     *********************************************
     */
    nf_register_net_hooks(&init_net, VNI_hooks, ARRAY_SIZE(VNI_hooks));

    return 0;
}

/*
*********************************************
* *****VNI模块卸载函数
*********************************************
*/
static void __exit VNI_exit(void)
{
    nf_unregister_net_hooks(&init_net, VNI_hooks, ARRAY_SIZE(VNI_hooks));

    if (nlsk) {
        netlink_kernel_release(nlsk);
        nlsk = NULL;
    }

    del_timer(&vni_timer);

    unregister_netdev(vni_dev);
    free_netdev(vni_dev);
    printk("vni_exit!\n");
}

/*
 *********************************************
 * VNI模块注册
 *********************************************
 */
module_init(VNI_init);
module_exit(VNI_exit);

MODULE_LICENSE("GPL V2");
MODULE_AUTHOR("Ming");
MODULE_DESCRIPTION("vni example");

user.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

#include<stdio.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<pcap.h>

#define PROMISC 1

#define NETLINK_TEST    30
#define MSG_LEN            125
#define MAX_PLOAD        125

/*过滤条件*/
char filter_exp[] = "ether[12:2]=0xf4f0";

/*抓包设备名称*/
char *dev;

/*最大抓包长度 :Ethernet 1500字节 + 以太网帧头部14字节 + 以太网帧尾部4字节*/
#define SNAP_LEN 1518

/*ethernet head are exactly 14 bytes*/
#define ETHERNET_HEAD_SIZE 14

/*ip头部字节数宏  取hlv低四位即头部长度*单位4bytes  然后强转为ip结构体*/
//#define IP_HEAD_SIZE(ipheader) ((ipheader->ip_hlv & 0x0f) * 4)
#define IP_HEAD_SIZE(packet)  ((((struct ip *)(packet + ETHERNET_HEAD_SIZE))->ip_hlv & 0x0f) * 4)

/*ethernet address are 6 bytes*/
#define ETHERNET_ADDR_LEN 6
/*Ethernet HEADER*/

struct ethernet {
    u_char ether_dhost[ETHERNET_ADDR_LEN];
    u_char ether_shost[ETHERNET_ADDR_LEN];
    u_short ether_type;
};

typedef struct _user_msg_info {
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int skfd;
int ret;
user_msg_info u_info;
socklen_t len;
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl saddr, daddr;

void ethernet_callback(u_char *arg, const struct pcap_pkthdr *pcap_pkt,
                       const u_char *packet)
{
    unsigned char eth_skb[256];
    unsigned char *umsg = NULL;
    struct ethernet *ethheader;
    struct ip *ipptr;
    u_short protocol;
    u_int *id = (u_int *)arg;

    printf("---------------Device : %s------------------\n", dev);
    printf("---------------Filter: %s-------------------\n", filter_exp);
    printf("-----------------Analyze Info---------------\n");
    printf("Id: %d\n", ++(*id));
    printf("Packet length: %d\n", pcap_pkt->len);
    printf("Number of bytes: %d\n", pcap_pkt->caplen);

    int k;
    for (k = 0; k < 90; k++) {
        /*表示以16进制的格式输出整数类型的数值,
        输出域宽为2,右对齐,不足的用字符0替代*/
        printf(" %02x", packet[k]);
        if ((k + 1) % 16 == 0) {
            printf("\n");
        }
    }
    printf("\n\n");

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));

    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    memcpy(eth_skb, packet, 90);

    for (k = 0; k < 90; k++) {
        /*表示以16进制的格式输出整数类型的数值,
        输出域宽为2,右对齐,不足的用字符0替代*/
        printf(" %02x", eth_skb[k]);
        if ((k + 1) % 16 == 0) {
            printf("\n");
        }
    }
    umsg = eth_skb;

    memcpy(NLMSG_DATA(nlh), eth_skb, 90);
    nlh->nlmsg_len = NLMSG_LENGTH(MAX_PLOAD);
    ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr,
                 sizeof(struct sockaddr_nl));
    if (!ret) {
        perror("sendto error\n");
        close(skfd);
        exit(-1);
    }

    printf("\nsend kernel:%s\n", umsg);

    ethheader = (struct ethernet *)packet;
    printf("\n---------------Data Link Layer-----------\n");

    printf("Mac Src Address: ");
    int i;
    for (i = 0; i < ETHERNET_ADDR_LEN; i++) {
        if (ETHERNET_ADDR_LEN - 1 == i) {
            printf("%02x\n", ethheader->ether_shost[i]);
        } else {
            printf("%02x:", ethheader->ether_shost[i]);
        }
    }

    printf("Mac Dst Address: ");
    int j;
    for (j = 0; j < ETHERNET_ADDR_LEN; j++) {
        if (ETHERNET_ADDR_LEN - 1 == j) {
            printf("%02x\n", ethheader->ether_dhost[j]);
        } else {
            printf("%02x:", ethheader->ether_dhost[j]);
        }
    }

    protocol = ntohs(ethheader->ether_type);

    printf("eth-proto:%04x\n", protocol);


    printf("---------------------Done--------------------\n\n\n");
}



int main(int argc, char **argv)
{

    char *umsg = "hello netlink!! this is from user\n";

    pcap_t *pcap;
    char errbuf[PCAP_ERRBUF_SIZE];
    struct pcap_pkthdr hdr;
    pcap_if_t *alldevs;

    struct bpf_program bpf_p;
    bpf_u_int32 net;
    bpf_u_int32 mask;

    int nl_time;
    int rx_packets, tx_packets;
    unsigned char nl_data[16];
    unsigned char temp[4];


    /* 创建NETLINK socket */
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if (skfd == -1) {
        perror("create socket error\n");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = 100;  //端口号(port ID)
    saddr.nl_groups = 0;
    if (bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0; // to kernel
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    printf("send kernel:%s\n", umsg);

    /*find the device to capture packet*/
    if (pcap_findalldevs(&alldevs, errbuf) == -1) {
        printf("no device !\n");
    }

    /*默认取第一个网络设备*/
    dev = alldevs->name;
    //选择“ens33”接口
    dev = "ens33";
    //dev = "br0";
    printf("eth:%s\n", dev);
    /*open the device*/
    pcap = pcap_open_live(dev, SNAP_LEN, PROMISC, 2000, errbuf);
    if (pcap == NULL) {
        printf("open error!\n");
        return 0;
    }

    if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
        printf("Could not get netmask for device!\n");
        net = 0;
        mask = 0;
    }

    if (pcap_compile(pcap, &bpf_p, filter_exp, 0, net) == -1) {
        printf("Could not parse filter\n");
        return 0;
    }
    if (pcap_setfilter(pcap, &bpf_p) == -1) {
        printf("Could not install filter\n");
        return 0;
    }

    pid_t child_pid;
    /* 创建一个子进程 */
    child_pid = fork();

    if (child_pid == 0) {
        //发送0xf4f0数据包
        int id = 0;
        printf("this is sniffer\n");
        /*capture the packet until occure error*/
        pcap_loop(pcap, -1, ethernet_callback, (u_char *)&id);

        pcap_close(pcap);
    } else {
        //接收netlink 消息
        while (1) {
            memset(&u_info, 0, sizeof(u_info));
            len = sizeof(struct sockaddr_nl);
            ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0,
                           (struct sockaddr *)&daddr, &len);
            if (!ret) {
                perror("recv form kernel error\n");
                close(skfd);
                exit(-1);
            }
            //打印VNI模块发送 or 接收的信息
            printf("from kernel:%s\n", u_info.msg);
            strcpy(nl_data, u_info.msg);

            //VNI模块运行时间 nltime*10=秒数
            strncpy(temp, nl_data, 4);
            nl_time = atoi(temp);

            //VNI模块发送分组
            strncpy(temp, nl_data + 5, 4);
            tx_packets = atoi(temp);

            //VNI模块接收分组
            strncpy(temp, nl_data + 10, 4);
            rx_packets = atoi(temp);

            printf("\n---------------VNI发送情况------------------\n");
            printf("\nvni tx:%d packets\n", tx_packets);
            printf("vni tx rate:%.2f pps\n", (float)(tx_packets * 1.0) / (10 * nl_time));
            printf("\n---------------VNI接收情况------------------\n");
            printf("\nvni rx:%d packets\n", rx_packets);
            printf("vni rx rate:%.2f pps\n", (float)(rx_packets * 1.0) / (10 * nl_time));
            sleep(5);
        }
        close(skfd);

        free((void *)nlh);
    }

    return 0;
}

Makefile

KERNEL_VER = $(shell uname -r)

# the file to compile
obj-m += vni.o

# specify flags for the module compilation
EXTRA_CFLAGS = -g -O1

build: kernel_modules

kernel_modules:
	make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(KERNEL_VER)/build M=$(PWD) clean

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我楚狂声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值