tap/tun
创建tun设备
ip tuntap add dev tun0 mod tun
配置ip地址
ifconfig tun0 192.168.0.254 up
为接口添加路由
route add -host 192.168.0.1 dev tun0
删除tun设备
ip tuntap del dev tun0 mod tun
通过用户应用程序使用tun(先删除通过ip tuntap命令添加的tun设备)
+----------------------------------------------------------------+
| |
| +--------------------+ +--------------------+ |
| | User Application A | | User Application B |<-----+ |
| +--------------------+ +--------------------+ | |
| | 1 | 5 | |
|...............|......................|...................|.....|
| ↓ ↓ | |
| +----------+ +----------+ | |
| | socket A | | socket B | | |
| +----------+ +----------+ | |
| | 2 | 6 | |
|.................|.................|......................|.....|
| ↓ ↓ | |
| +------------------------+ 4 | |
| | Newwork Protocol Stack | | |
| +------------------------+ | |
| | 7 | 3 | |
|................|...................|.....................|.....|
| ↓ ↓ | |
| +----------------+ +----------------+ | |
| | eth0 | | tun0 | | |
| +----------------+ +----------------+ | |
| 192.168.80.134 | | 192.168.1.134 | |
| | 8 +---------------------+ |
| | |
+----------------|-----------------------------------------------+
↓
Physical Network
tun0是连着内核协议栈和用户应用B(通过open该设备)的管道,当协议栈往tun0发送数据时,用户应用B能收到对应的数据。
User Application A:
能够触发协议栈往tun0发送数据(通过设备文件实现内核态和用户态通信)就行,这里直接使用ping
User Application B:
打开tun设备,接收协议栈发往tun0的数据
/*tun.c*/
#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>
#include<stdlib.h>
#include<stdio.h>
int tun_alloc(int flags)
{
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 ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) {
close(fd);
return err;
}
printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name);
return fd;
}
int main()
{
int tun_fd, nread;
char buffer[1500];
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
* IFF_NO_PI - Do not provide packet information
*/
tun_fd = tun_alloc(IFF_TUN | IFF_NO_PI);
if (tun_fd < 0) {
perror("Allocating interface");
exit(1);
}
while (1) {
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);
}
return 0;
}
步骤:
1.运行tun,打开字符设备文件
./tun
2.配置tun设备的IP地址192.168.1.134和接口状态,并在tun接口抓包
ip addr add 192.168.1.134/24 dev tun0
ip link set tun0 up
tcpdump -i tun0
3.触发数据发往tun0
ping 192.168.1.100(协议栈查路由,发现该icmp req包发往tun0)
通过抓包程序和应用B的打印,可以看到数据发送到了tun的另一端应用B,但是B没做处理,包到此终结。
如果修改应用B,使其返回icmp rep,则完成了一个ICMP应答功能。
#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>
#include<stdlib.h>
#include<stdio.h>
int tun_alloc(char *dev, int flags)
{
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;
}
printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name);
// 一旦设备开启成功,系统会给设备分配一个名称,对于tun设备,一般为tunX,X为从0开始的编号
strcpy(dev, ifr.ifr_name);
return fd;
}
int main()
{
int tun_fd, nread;
char buffer[1500];
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);
}
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);
//构造icmp rep包
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;
}
veth-pair
veth-pair 就是一对的虚拟设备接口,和 tap/tun 设备不同的是,它都是成对出现的。一端连着协议栈,一端彼此相连着。
创建一对 veth-pair veth0 veth1
ip link add veth0 type veth peer name veth1
给veth-pair分别配置ip地址
ip addr add 10.1.1.1/24 dev veth0
ip addr add 10.1.1.2/24 dev veth1
设置状态up
ip link set veth0 up
ip link set veth1 up
veth-pair连通性
[root@ ~]# ping -I veth0 10.1.1.2 -c 2
PING 10.1.1.2 (10.1.1.2) from 10.1.1.1 veth0: 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.021 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.033 ms
如果ping不通,是由于ARP没学到,Ubuntu 系统内核中一些 ARP 相关的默认配置限制所导致,需要修改一下配置项:
echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter
抓包
veth0:
tcpdump -nnt -i veth0
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 1, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 2, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 3, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 4, length 64
veth1:
tcpdump -nnt -i veth1
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 19, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 20, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 21, length 64
IP 10.1.1.2 > 10.1.1.1: ICMP echo request, id 8499, seq 22, length 64
都只有echo request包
在loopback抓包:
tcpdump -nnt -i lo
IP 10.1.1.1 > 10.1.1.2: ICMP echo reply, id 8499, seq 79, length 64
IP 10.1.1.1 > 10.1.1.2: ICMP echo reply, id 8499, seq 80, length 64
IP 10.1.1.1 > 10.1.1.2: ICMP echo reply, id 8499, seq 81, length 64
IP 10.1.1.1 > 10.1.1.2: ICMP echo reply, id 8499, seq 82, length 64
其实这里 echo reply 走的是 localback 口
整个ping流程
由于 veth0 连着 veth1,所以 ICMP request 直接发给 veth1。
veth1 收到请求后,交给另一端的协议栈。
协议栈看本地有 10.1.1.3 这个 IP,于是构造 ICMP reply 包,查看路由表,发现回给 10.1.1.0 网段的数据包应该走 localback 口,于是将 reply 包交给 lo 口(会优先查看路由表的 0 号表,ip route show table 0 查看)。
lo 收到协议栈的 reply 包后,啥都没干,转手又回给协议栈。
协议栈收到 reply 包之后,发现有 socket 在等待包,于是将包给 socket。
等待在用户态的 ping 程序发现 socket 返回,于是就收到 ICMP 的 reply 包。
cloud_dev_veth_pair_ping.jpg
删除veth-pair设备
ip link del veth0