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 数据包。
3.2 VNI接口实现
??VNI 分组头部字段用于在 IP 分组上插入头部信息。
static struct VNI_ethhdr{
unsigned char student[4];
unsigned short vnid;
};
??VNI 统计信息结构体,统计 VNI 模块收发数据情况。
static struct VNI_states{
uint16_t vni_tx_packets;
uint16_t vni_rx_packets;
};
??网络接口 net_device:VNI,向内核注册 vni0 设备。
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[] ={
{
.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发送至内核。
4.实际测试
4.1环境
- 主机:两台电脑上的相同 linux 虚拟机
- 操作系统:6.04.1-Ubuntu Linux
- 内核版本:4.15.0-72-generic
- 抓包软件: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>
#define NETLINK_TEST 30
#define MSG_LEN 125
#define USER_PORT 100
struct sock *nlsk = NULL;
char *vni_msg = "this is vni module";
struct timer_list vni_timer;
static int cnt = 0;
struct timeval oldtv;
static struct net_device *vni_dev = NULL;
static struct VNI_ethhdr {
unsigned char student[4];
unsigned short vnid;
};
static struct VNI_states {
uint16_t vni_tx_packets;
uint16_t vni_rx_packets;
};
struct VNI_states vni_states;
static int nl_cnt;
unsigned char eth_rcv[256];
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;
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if (!nl_skb) {
printk("netlink alloc failure\n");
return -1;
}
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;
}
static void timer_handle(struct timer_list *tls)
{
unsigned char buf[128];
if (++cnt >= 5) {
nl_cnt++;
printk("vni:%s,cnt:%d", vni_msg, nl_cnt);
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);
}
static void VNI_Reader(const unsigned char *buf)
{
int i = 0;
struct sk_buff *new_skb;
struct net_device *dev = NULL;
struct iphdr *iphdr = NULL;
struct ethhdr *eth = NULL;
unsigned char data[51];
new_skb = dev_alloc_skb(128);
skb_reserve(new_skb, 80);
new_skb->len = 0;
memcpy(data, buf + 40, 50);
skb_push(new_skb, 50);
memcpy(new_skb->data, data, 50);
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);
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");
new_skb->dev = dev;
new_skb->protocol = htons(0x0800);
new_skb->ip_summed = CHECKSUM_UNNECESSARY;
dev = dev_get_by_name(&init_net, "vni0");
new_skb->dev = dev;
vni_states.vni_rx_packets++;
netif_rx(new_skb);
}
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++) {
eth_rcv[i] = umsg[i];
}
VNI_Reader(eth_rcv);
}
}
}
static unsigned int
VNI_HookLocalIN(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
int ret = 0;
int i = 0;
struct net_device *dev = NULL;
struct ethhdr *eth = eth_hdr(skb);
struct iphdr *iphdr = ip_hdr(skb);
printk("ipsaddr:%08x,daddr:%08x", iphdr->saddr, iphdr->daddr);
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]);
if (iphdr->protocol == 0xf4f0) {
printk("F4f0 LOCAL IN\n");
memmove(skb->data, skb->data + 6, 14);
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) {
printk("not icmp");
return NF_ACCEPT;
}
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);
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);
p = skb_push(skb, 6);
vni = (struct VNI_ethhdr *)p;
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);
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;
}
static struct nf_hook_ops VNI_hooks[] = {
{
.hook = VNI_HookLocalIN,
.pf = PF_BRIDGE,
.hooknum = NF_BR_PRE_ROUTING,
.priority = NF_BR_PRI_FIRST,
},
{
.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,
};
static const struct net_device_ops vni_dev_ops = {
};
static int __init VNI_init(void)
{
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_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);
nf_register_net_hooks(&init_net, VNI_hooks, ARRAY_SIZE(VNI_hooks));
return 0;
}
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");
}
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;
#define SNAP_LEN 1518
#define ETHERNET_HEAD_SIZE 14
#define IP_HEAD_SIZE(packet) ((((struct ip *)(packet + ETHERNET_HEAD_SIZE))->ip_hlv & 0x0f) * 4)
#define ETHERNET_ADDR_LEN 6
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++) {
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;
memcpy(eth_skb, packet, 90);
for (k = 0; k < 90; k++) {
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];
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;
saddr.nl_pid = 100;
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;
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;
printf("send kernel:%s\n", umsg);
if (pcap_findalldevs(&alldevs, errbuf) == -1) {
printf("no device !\n");
}
dev = alldevs->name;
dev = "ens33";
printf("eth:%s\n", dev);
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) {
int id = 0;
printf("this is sniffer\n");
pcap_loop(pcap, -1, ethernet_callback, (u_char *)&id);
pcap_close(pcap);
} else {
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);
}
printf("from kernel:%s\n", u_info.msg);
strcpy(nl_data, u_info.msg);
strncpy(temp, nl_data, 4);
nl_time = atoi(temp);
strncpy(temp, nl_data + 5, 4);
tx_packets = atoi(temp);
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)
obj-m += vni.o
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
|