本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。

前面两篇文章已经介绍过 tap/tun 的原理和配置工具。这篇文章通过一个编程示例来深入了解 tap/tun 的程序结构。

01 准备工作

首先通过 modinfo tun 查看系统内核是否支持 tap/tun 设备驱动。

[root@by ~]# modinfo tun
filename: /lib/modules/3.10.0-862.14.4.el7.x86_64/kernel/drivers/net/tun.ko.xz
alias: devname:net/tun
alias: char-major-10-200
license: GPL
author: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
description: Universal TUN/TAP device driver
retpoline: Y
rhelversion: 7.5
srcversion: 50878D5D5A0138445B25AA8
depends:
intree: Y
vermagic: 3.10.0-862.14.4.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: E4:A1:B6:8F:46:8A:CA:5C:22:84:50:53:18:FD:9D:AD:72:4B:13:03
sig_hashalgo: sha256

在 linux 2.4 及之后的内核版本中,tun/tap 驱动是默认编译进内核中的。

如果你的系统不支持,请先选择手动编译内核或者升级内核。编译时开启下面的选项即可:

Device Drivers => Network device support => Universal TUN/TAP device driver support

tap/tun 也支持编译成模块,如果编译成模块,需要手动加载它:

[root@localhost ~]# modprobe tun
[root@localhost ~]# lsmod | grep tun
tun 31665 0

关于以上的详细步骤,网上有很多教程,这里就不再赘述了。

https://blog.csdn.net/lishuhuakai/article/details/70305543

上面只是加载了 tap/tun 模块,要完成 tap/tun 的编码,还需要有设备文件,运行命令:

mknod /dev/net/tun c 10 200 # c表示为字符设备,10和200分别是主设备号和次设备号

这样在 /dev/net 下就创建了一个名为 tun 的文件。

02 编程示例

2.1 启动设备

使用 tap/tun 设备,需要先进行一些初始化工作,如下代码所示:

int tun_alloc(char *dev, int flags)
{
assert(dev != NULL); struct ifreq ifr;
int fd, err; char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev, O_RDWR)) < 0) {
return fd;
} memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = flags; if (*dev != '\0') {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) {
close(fd);
return err;
} // 一旦设备开启成功,系统会给设备分配一个名称,对于tun设备,一般为tunX,X为从0开始的编号;
// 对于tap设备,一般为tapX
strcpy(dev, ifr.ifr_name); return fd;
}

首先打开字符设备文件 /dev/net/tun,然后用 ioctl 注册设备的工作模式,是 tap 还是 tun。这个模式由结构体 struct ifreq 的属性 ifr_flags 来定义,它有以下表示:

  • IFF_TUN: 表示创建一个 tun 设备。
  • IFF_TAP: 表示创建一个 tap 设备。
  • IFF_NO_PI: 表示不包含包头信息,默认的,每个数据包传到用户空间时,都会包含一个附加的包头来保存包信息,这个表示不加包头。
  • IFF_ONE_QUEUE:表示采用单一队列模式。

还是有一个属性是 ifr_name,表示设备的名字,它可以由用户自己指定,也可以由系统自动分配,比如 tapXtunX,X 从 0 开始编号。

ioctl 完了之后,文件描述符 fd 就和设备建立起了关联,之后就可以根据 fd 进行 read 和 write 操作了。

2.2 写一个 ICMP 的调用函数

为了测试上面的程序,我们写一个简单的 ICMP echo 程序。我们会使用 tun 设备,然后给 tunX 接口发送一个 ping 包,程序简单响应这个包,完成 ICMP 的 request 和 reply 的功能。

如下代码所示:

int main()
{
int tun_fd, nread;
char buffer[4096];
char tun_name[IFNAMSIZ]; tun_name[0] = '\0'; /* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
* IFF_NO_PI - Do not provide packet information
*/
tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI); if (tun_fd < 0) {
perror("Allocating interface");
exit(1);
} printf("Open tun/tap device: %s for reading...\n", tun_name); while (1) {
unsigned char ip[4];
// 收包
nread = read(tun_fd, buffer, sizeof(buffer));
if (nread < 0) {
perror("Reading from interface");
close(tun_fd);
exit(1);
} printf("Read %d bytes from tun/tap device\n", nread); // 简单对收到的包调换一下顺序
memcpy(ip, &buffer[12], 4);
memcpy(&buffer[12], &buffer[16], 4);
memcpy(&buffer[16], ip, 4); buffer[20] = 0;
*((unsigned short *)&buffer[22]) += 8; // 发包
nread = write(tun_fd, buffer, nread); printf("Write %d bytes to tun/tap device, that's %s\n", nread, buffer);
}
return 0;
}

下面测试一下。

2.3 给 tap/tun 设备配置 IP 地址

编译:

[root@localhost coding]# gcc -o taptun taptun.c
[root@localhost coding]# ./taptun
Open tun/tap device: tun0 for reading...

开另一个终端,查看生成了 tun0 接口:

[root@localhost coding]# ip a
6: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN qlen 500
link/none

tun0 接口配置 IP 并启用,比如 10.1.1.2/24

[root@localhost ~]# ip a a 10.1.1.2/24 dev tun0
[root@localhost ~]# ip l s tun0 up

再开一个终端,用 tcpdumptun0 的包。

[root@localhost ~]# tcpdump -nnt -i tun0

然后在第二个终端 ping 一下 10.1.1.0/24 网段的 IP,比如 10.1.1.3,看到:

[root@localhost ~]# ping -c 4 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.133 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.188 ms
64 bytes from 10.1.1.3: icmp_seq=3 ttl=64 time=0.092 ms
64 bytes from 10.1.1.3: icmp_seq=4 ttl=64 time=0.110 ms --- 10.1.1.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3290ms
rtt min/avg/max/mdev = 0.092/0.130/0.188/0.038 ms

由于 tun0 接口建好之后,会生成一条到本网段 10.1.1.0/24 的默认路由,根据默认路由,数据包会走 tun0 口,所以能 ping 通,可以用 route -n 查看。

再看 tcpdump 抓包终端,成功显示 ICMP 的 request 包和 reply 包。

[root@localhost ~]# tcpdump -nnt -i tun0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 3250, seq 1, length 64
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 3250, seq 1, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 3250, seq 2, length 64
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 3250, seq 2, length 64

再看程序 taptun.c 的输出:

[root@localhost coding]# ./taptun
Open tun/tap device: tun0 for reading...
Read 48 bytes from tun/tap device
Write 48 bytes to tun/tap device
Read 48 bytes from tun/tap device
Write 48 bytes to tun/tap device

ok,以上便验证了程序的正确性。

03 总结

通过这个小例子,让我们知道了基于 tap/tun 编程的流程,对 tap/tun 又加深了一层理解。

使用 tap/tun 设备需要包含头文件 #include <linux/if_tun.h>,以下是完整代码。

/******************************************************************************
* File Name: taptun.c
* Author: 公众号: CloudDeveloper
* Created Time: 2019年02月23日 星期六 21时28分24秒
*****************************************************************************/ #include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <linux/if_tun.h> int tun_alloc(char *dev, int flags)
{
assert(dev != NULL); struct ifreq ifr;
int fd, err; char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev, O_RDWR)) < 0) {
return fd;
} memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = flags; if (*dev != '\0') {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) {
close(fd);
return err;
} // 一旦设备开启成功,系统会给设备分配一个名称,对于tun设备,一般为tunX,X为从0开始的编号;
// 对于tap设备,一般为tapX
strcpy(dev, ifr.ifr_name); return fd;
} int main()
{
int tun_fd, nread;
char buffer[4096];
char tun_name[IFNAMSIZ]; tun_name[0] = '\0'; /* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
* IFF_NO_PI - Do not provide packet information
*/
tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI); if (tun_fd < 0) {
perror("Allocating interface");
exit(1);
} printf("Open tun/tap device: %s for reading...\n", tun_name); while (1) {
unsigned char ip[4];
// 收包
nread = read(tun_fd, buffer, sizeof(buffer));
if (nread < 0) {
perror("Reading from interface");
close(tun_fd);
exit(1);
} printf("Read %d bytes from tun/tap device\n", nread); // 简单对收到的包调换一下顺序
memcpy(ip, &buffer[12], 4);
memcpy(&buffer[12], &buffer[16], 4);
memcpy(&buffer[16], ip, 4); buffer[20] = 0;
*((unsigned short *)&buffer[22]) += 8; // 发包
nread = write(tun_fd, buffer, nread); printf("Write %d bytes to tun/tap device, that's %s\n", nread, buffer);
}
return 0;
}

我的公众号 「Linux云计算网络」(id: cloud_dev) ,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,分享的内容包括但不限于 Linux、网络、云计算虚拟化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++编程技术等内容,欢迎大家关注。

利用 Linux tap/tun 虚拟设备写一个 ICMP echo 程序的更多相关文章

  1. 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度。 2)输出字符串中第一个出现字母a的位置。 3)在字符串的第3个字符后面插入子串“hello”,输出新字符串。 4)将字符串“hello”替换为“me”,输出新字符串。 5)以字符“m”为分隔符,将字符串分离,并输出分离后的字符串。 */

    namespace test4 {/* 4.写一个控制台应用程序,接收一个长度大于3的字符串,完成下列功能: 1)输出字符串的长度. 2)输出字符串中第一个出现字母a的位置. 3)在字符串的第3个字符 ...

  2. python学习(10)字典学习,写一个三级菜单程序

    学习了字典的应用.按老师的要求写一个三级菜单程序. 三级菜单程序需求如下: 1.深圳市的区--街道--社区---小区4级 2.建立一个字典,把各级区域都装进字典里 3.用户可以从1级进入2级再进入3级 ...

  3. 利用windows.h头文件写一个简单的C语言倒计时

    今天写一个简单的倒计时函数 代码如下: #include<stdio.h> #include<windows.h> int main() { int i; printf(&qu ...

  4. 手把手教你写一个RN小程序!

    时间过得真快,眨眼已经快3年了! 1.我的第一个App 还记得我14年初写的第一个iOS小程序,当时是给别人写的一个单机的相册,也是我开发的第一个完整的app,虽然功能挺少,但是耐不住心中的激动啊,现 ...

  5. [C#学习笔记1]用csc.exe和记事本写一个C#应用程序

    csc.exe是C#的命令行编译器(CSharpCompiler),可以编译C#源程序成可执行程序.它与Visual Studio等IDE(Integrated Development Environ ...

  6. 自己写一个chrome扩展程序 - 右键菜单扩展

    最近在学习Spring,心想dotnet如何实现类似形式呢.于是想认真学习Casetle组件,发现没有书籍!而spring的书多得很.于是只好找网上教程了.发现系统的文章不多.Terrylee好多文章 ...

  7. 用c-free 5写一个入门的程序

    本文记录了在windows系统中使用C-FREE 5新建一个Hello HoverTree程序的步骤. 安装好C-Free 5之后,打开.新建一个工程: 附C-Free 5下载:http://hove ...

  8. Qt 利用XML文档,写一个程序集合 四

    接上一篇https://www.cnblogs.com/DreamDog/p/9214067.html 启动外部程序 这里简单了,直接上代码吧 connect(button,&MPushBut ...

  9. Qt 利用XML文档,写一个程序集合 二

    接上一篇文章https://www.cnblogs.com/DreamDog/p/9213915.html XML文档的读写 一个根节点,下面每一个子节点代表一个子程序,内容为子程序名字,图标路径,e ...

随机推荐

  1. nginx简介与配置

    nginx简介 nginx(发音同engine x)是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like协议下发行. nginx由俄罗斯的程序 ...

  2. sublime text2 安装及使用教程

    1.下载安装包地址:https://www.sublimetext.com/2 2.安装,一直点下一步就好,将下列选项打钩,这样文件右键就可以直接用sublime text2打开 3.新建一个html ...

  3. 对TIMIT数据进行格式转换(SPHERE2WAV(RIFF))

    首先,转换sph2pipe工具所在文件夹(此工具为LDC所提供的SPHERE音频文件转换工具) cd '/home/dream/Research/kaldi-master/tools/sph2pipe ...

  4. java下载Excel模板(工具类)

    一次文件下载记录 一次不成熟的文件下载操作记录,希望能对需要的人有所帮助. 1.前端代码 $("#downloadModel").click(function(){ var mod ...

  5. Jmeter+ant集成接口测试报告

    一.jdk1.8下载及环境配置 1.1 下载地址 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-download ...

  6. 2019.03.12 codeforces739E. Gosha is hunting(dp凸优化)

    传送门 题意:nnn个物品,有aaa个XXX道具和bbb个YYY道具,XXX道具移走第iii个物品概率为pip_ipi​,YYY道具移走第iii个道具概率为uiu_iui​. 对于每个物品每种道具最多 ...

  7. 数据结构C语言顺序表

    #include <stdio.h> #include <stdlib.h> typedef int EmenType; typedef struct Node { int d ...

  8. Unity3D编辑器扩展(五)——常用特性(Attribute)以及Selection类

    前面写了四篇关于编辑器的: Unity3D编辑器扩展(一)——定义自己的菜单按钮 Unity3D编辑器扩展(二)——定义自己的窗口 Unity3D编辑器扩展(三)——使用GUI绘制窗口 Unity3D ...

  9. SFTP文件服务器的搭建

    由于公司项目的需要,需要自己搭建一个SFTP文件服务器,来实现不同IP服务器之间文件的传输: 应用的场景:由于需要缓解服务器的压力,需要对服务进分离,分别放置在不同IP服务器上: 首先提供一个SFTP ...

  10. Exp2 后门原理与实践 20154320 李超

    目录- 基础问题回答- 基础知识- 实验过程- 实验心得体会 基础知识问答 1. 例举你能想到的一个后门进入到你系统中的可能方式?从不安全的网站上下载的程序可能存在后门. 2. 例举你知道的后门如何启 ...