Linux 中的 Tun/Tap 介绍

TUN/TAP 设备

在计算机中TUN与TAP是操作系统内核中的虚拟网络设备。不同于硬件设备这些虚拟的网络设备全部用软件实现,但提供了与硬件设备完全相同的功能。

我们先了解一下物理设备的工作原理

所有主机物理网卡收到的数据包时,会先将其交给内核的 Network Stack 处理,然后通过 Socket API 通知给用户态的用户程序。

Linux 中 Tun/Tap 驱动程序为应用程序提供了两种交互方式:

  • 虚拟网络接口和字符设备/dev/net/tun。写入字符设备/dev/net/tun的数据会发送到虚拟网络接口中;
  • 发送到虚拟网络接口中的数据也会出现在该字符设备上;

我们再看下 tun 设备的工作原理

用户态应用往字符设备 /dev/tunX 写数据时,写入的数据都会出现在TUN虚拟设备上,当内核发送一个包给 TUN 虚拟设备时,通过读这个字符设备 /dev/tunX 同样可以拿到包的内容。

用户态应用程序写数据到 tun/tap 设备后进入内核态,内核态通过TCP协议复制到用户态,最后数据再次复制到内核态并通过物理网卡转发出去,期间共经历了三次用户态与内核态的复制操作,相比传统的一次复制操作来说,开销还是比较大的,因此性能会有一定的下降,这正是它的缺点。

TAP 设备与 TUN 设备工作方式完全相同,区别在于:

  • TUN 设备的 /dev/tunX 文件收发的是 IP 层数据包,只能工作在 IP 层,无法与物理网卡做 bridge,但是可以通过三层交换(如 ip_forward)与物理网卡连通。
  • TAP 设备的 /dev/tapX 文件收发的是 MAC 层数据包,拥有 MAC 层功能,可以与物理网卡做 bridge,支持 MAC 层广播

应用场景

tun/tap 的最主要应用场景就是vpn。
基实现原理就是用到隧道技术,将无法直接发送的包通先封成允许通过的合法数据包,然后经过隧道的方式传递给对方,对方收到数据包再解包成原始数据包,再继续传递下去,直到接收端收到,然后响应并原路返回。
以下图为例

  1. 应用进程(用户态)发起一个请求时,数据包并不是直接通过eth0网卡流出去,而是将请求数据包写入一个 TUN 字符设备,此时字符设备的数据会被发到虚拟网卡上(进入内核态)。根据TUN设置的特点,凡是写到这个设备的数据都可以在设备的另一端被应用程序读出的原理,应用程序客户端VPN(Port:28001)不断的从TUN 设备里将数据包读出来,然后再经过物理的网卡 eth0(IP1) 网卡流出,这一步就是一个普通的应用程序的客户端发起一个请求的过程。
  2. 流出的数据包通过 eth0 (IP2)被服务端VPN(Port:38001)接收到,然后再将收到的数据包以同样的方式写入 TUN 设备,此时进入内核态,经过 TCP/IP 协议栈,则再次将数据包经过物理网卡eth0(IP2)出去,经过交换同机或路由器直到最终到达目的主机(目的主机非本机)。
  3. 然后目的主机将响应按原来的线路返回给发起请求应用程序。

总结

  • 数据包在整个流程中,需要进行一些封包解包的操作, 这个操作由设备驱动完成
  • 如果数据包目的地不是VPN(Port:38001)当前所在主机的话,则需要向数据流向其它机器,此时务必修改IPTABLES的来源地址进行,即需要做SNAT,否则响应数据包将无法原路返回给客户端。

使用方法

我们先介绍一下在 Linux 中是如何对 TUN/TAP 虚拟设备进行操作创建管理和删除的,但对于如何充分利用这些设备必须通过编写程序代码来实现,在后面会给出网友整理出来的演示代码。

命令行输入 ip help 查看 ip 命令是否支持 tun/tap 工具,支持的话就会显示 tuntap 选项:

# ip help
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { link | addr | addrlabel | route | rule | neigh | ntable |
                   tunnel | tuntap | maddr | mroute | mrule | monitor | xfrm |
                   netns | l2tp | tcp_metrics | token }

不支持就请升级或下载最新的 iproute2 工具包,也可以使用类似的 tunctl 工具。

先查看一下 ip tuntap 的基本用法

# ip tuntap help
Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]
    [ mode { tun | tap } ] [ user USER ] [ group GROUP ]
    [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]

Where:    USER  := { STRING | NUMBER }
    GROUP := { STRING | NUMBER }
  1. 创建 tap/tun 设备:
# ip tuntap add dev tap0 mod tap # 创建 tap 
# ip tuntap add dev tun0 mod tun # 创建 tun

# ifconfig -a

新添加的虚拟网卡默认是 DOWN 状态.
对于 tun 类型的虚拟网卡,它的MAC地址全是 0,这个是正常的

  1. 激活虚拟网卡
# ip link set tun0 up
# ip link set tap0 up

对它的操作与普通网卡的命令是一样的

  1. 分配IP
# ip addr add 10.0.0.1/24 dev tun0

此时 PING 10.0.0.1 是可以通的。

# ping 10.0.0.1
PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.
64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=0.036 ms
64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=0.031 ms
64 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=0.037 ms
  1. 删除 tap/tun 设备:
# ip tuntap del dev tap0 mod tap # 删除 tap
# ip tuntap del dev tun0 mod tun # 删除 tun

代码演示

上面我们手动创建了虚拟网卡,但没有办法测试网卡的使用效果,下面是一段实现虚拟网卡读取的演示代码,代码摘自:https://segmentfault.com/a/1190000009249039

#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 devicen", nread);
    }
    return 0;
}

演示

#--------------------------第一个shell窗口----------------------
#将上面的程序保存成tun.c,然后编译
dev@debian:~$ gcc tun.c -o tun

#启动tun程序,程序会创建一个新的tun设备,
#程序会阻塞在这里,等着数据包过来
dev@debian:~$ sudo ./tun
Open tun/tap device tun1 for reading...
Read 84 bytes from tun/tap device
Read 84 bytes from tun/tap device
Read 84 bytes from tun/tap device
Read 84 bytes from tun/tap device

#--------------------------第二个shell窗口----------------------
#启动抓包程序,抓经过tun1的包
# tcpdump -i tun1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tun1, link-type RAW (Raw IP), capture size 262144 bytes
19:57:13.473101 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 1, length 64
19:57:14.480362 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 2, length 64
19:57:15.488246 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 3, length 64
19:57:16.496241 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 4, length 64

#--------------------------第三个shell窗口----------------------
#./tun启动之后,通过ip link命令就会发现系统多了一个tun设备,
#在我的测试环境中,多出来的设备名称叫tun1,在你的环境中可能叫tun0
#新的设备没有ip,我们先给tun1配上IP地址
dev@debian:~$ sudo ip addr add 192.168.3.11/24 dev tun1

#默认情况下,tun1没有起来,用下面的命令将tun1启动起来
dev@debian:~$ sudo ip link set tun1 up

#尝试ping一下192.168.3.0/24网段的IP,
#根据默认路由,该数据包会走tun1设备,
#由于我们的程序中收到数据包后,啥都没干,相当于把数据包丢弃了,
#所以这里的ping根本收不到返回包,
#但在前两个窗口中可以看到这里发出去的四个icmp echo请求包,
#说明数据包正确的发送到了应用程序里面,只是应用程序没有处理该包
dev@debian:~$ ping -c 4 192.168.3.12
PING 192.168.3.12 (192.168.3.12) 56(84) bytes of data.

--- 192.168.3.12 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3023ms

参考资料

IO多路复用机制详解(转)

服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

(1)同步阻塞IO(Blocking IO):即传统的IO模型。

(2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

(3)IO多路复用(IO Multiplexing):即经典的反应器Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构。

(4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。不经常用。

高性能I/O设计模式Reactor和Proactor:https://blog.csdn.net/xiongping_/article/details/45152333

select、poll、epoll之间的区别(搜狗面试)

转自:https://blog.csdn.net/baixiaoshi/article/details/48708347

批量结束linux进程

这里 php timer.php 进程是使用swoole来搞的一个crond服务,用来定时采集一些数据

用ps显示的有以下进程

root 6583 0.0 0.2 219676 4872 ? Ss 10:46 0:00 php-fpm: master process (/usr/local/php/etc/php-fpm.conf)
www 7076 2.7 1.0 304928 19920 ? S 11:32 0:05 php-fpm: pool www
www 7077 3.0 1.4 312964 26940 ? S 11:32 0:05 php-fpm: pool www
www 7117 2.5 1.2 310668 23724 ? S 11:35 0:01 php-fpm: pool www
root 7121 0.0 0.2 331188 4500 ? Ssl 11:36 0:00 php timer.php
root 7122 0.0 0.2 257012 4168 ? S 11:36 0:00 php timer.php
root 7124 0.0 0.2 242940 5060 ? S 11:36 0:00 php timer.php
root 7125 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7126 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7127 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7128 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7129 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7130 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 7131 0.0 0.2 243052 4504 ? S 11:36 0:00 php timer.php
root 25224 0.0 0.0 0 0 ? S< 2016 0:00 [kcopyd]
root 25225 0.0 0.0 0 0 ? S< 2016 0:00 [bioset]
root 25226 0.0 0.0 0 0 ? S< 2016 0:00 [dm-thin]
root 25227 0.0 0.0 0 0 ? S< 2016 0:00 [bioset]
root 25235 0.0 0.0 0 0 ? S< 2016 0:00 [xfsalloc]
root 25236 0.0 0.0 0 0 ? S< 2016 0:00 [xfs_mru_cache]
root 25804 0.0 0.0 0 0 ? S< 2016 0:00 [kworker/0:0H]
root 26091 0.0 0.0 0 0 ? S< 2016 0:47 [kworker/0:1H]

单独结束一个进程的时候,可以使用 kill -9 进程ID,而如果有许多个同命令的进程的话,则可以同样使用此命令进行批量结束

kill `ps -ef|grep 'php timer.php'| grep -v grep|awk '{print $2}'`

其中

ps -ef|grep ‘php timer.php’| grep -v grep|awk ‘{print $2}’

是用来过滤所有相关进程id

如何用十条命令在一分钟内检查Linux服务器性能

如果你的Linux服务器突然负载暴增,报警短信快发爆你的手机,如何在最短时间内找出Linux性能问题所在?来看Netflix性能工程团队的这篇博文,看它们通过十条命令在一分钟内对机器性能问题进行诊断。
概述

通过执行以下命令,可以在1分钟内对系统资源使用情况有个大致的了解。

  • uptime
  • dmesg | tail
  • vmstat 1
  • mpstat -P ALL 1
  • pidstat 1
  • iostat -xz 1
  • free -m
  • sar -n DEV 1
  • sar -n TCP,ETCP 1
  • top
  • gtop
  • pstree 查看进程树之间派生的关系
  • trap 捕捉进行接收到的信号标识,如 SIGKILL/SIGTERM

其中一些命令需要安装sysstat包,有一些由procps包提供。这些命令的输出,有助于快速定位性能瓶颈,检查出所有资源(CPU、内存、磁盘IO等)的利用率(utilization)、饱和度(saturation)和错误(error)度量,也就是所谓的USE方法。 Continue reading

Linux 之 /etc/profile、~/.bash_profile 等几个文件的执行过程

在登录Linux时要执行文件的过程如下:
在刚登录Linux时,首先启动 /etc/profile 文件,然后再启动用户目录下的 ~/.bash_profile、 ~/.bash_login或 ~/.profile文件中的其中一个,

执行的顺序为:~/.bash_profile~/.bash_login~/.profile

如果 ~/.bash_profile文件存在的话,一般还会执行 ~/.bashrc文件。

因为在 ~/.bash_profile文件中一般会有下面的代码:

[shell]if [ -f ~/.bashrc ] ; then
. ./bashrc
fi[/shell]

~/.bashrc中,一般还会有以下代码:

[shell]if [ -f /etc/bashrc ] ; then
. /etc/bashrc
fi[/shell]

所以,~/.bashrc会调用 /etc/bashrc文件。最后,在退出shell时,还会执行 ~/.bash_logout文件。 Continue reading

ps和pstree

ps和pstree是用于系统分析的基本命令。ps有3中不同风格的命令选项,UNIX风格、BSD风格和GNU风格。这里我们只介绍UNIX风格选项。

ps命令可以显示当前运行的进程列表。top命令也可以显示进程信息,但ps可以提供更加详细的内容。使用相应选项可以影响进程显示的数量。ps -A命令可以列出所有进程及其相应的进程ID(PID),当我们使用如pmap或renice等工具时会用到此PID。 Continue reading

关于Linux操作系统源代码查看工具的介绍

a、Windows系统可以用Source Insight,Linux系统可以用Source Navigator。

b、vim或emacs编辑器,配合cscope、ctags、etags等交叉索引工具。

c、vim或emacs编辑器,配合grep、egrep等文本搜索工具,不过最好要对源代码目录结构有所熟悉

d、LXR,以网页的形式通过浏览器浏览,安装复杂(debian下安装容易,请版面搜寻lxr)

e、GNU global,可以在命令行用,也可以生成hypertext,类似lxr,但更省事。

Source Insight3.5(含Key)下载

FreeBSD/Linux检测硬盘坏道

Linux检测硬盘坏道

badblocks

功能说明:检查磁盘装置中损坏的区块。

语法:badblocks [-svw][-b ][-o ][磁盘装置][磁盘区块数][启始区块]

补充说明:执行指令时须指定所要检查的磁盘装置,及此装置的磁盘区块数。

参数:

-b 指定磁盘的区块大小,单位为字节。

-o 将检查的结果写入指定的输出文件。

-s 在检查时显示进度。

-v 执行时显示详细的信息。

-w 在检查时,执行写入测试。

[磁盘装置] 指定要检查的磁盘装置。

[磁盘区块数] 指定磁盘装置的区块总数。

[启始区块] 指定要从哪个区块开始检查。

badblocks 检测磁盘坏块

1)$badblocks -s //显示进度 -v //显示执行详细情况 /dev/sda1

2)读写方式检测 未挂载的磁盘设备或分区

$badblocks -s //显示进度 -w //以写去检测 -v //显示执行详细情况 /dev/sda2

========================= Continue reading

Linux性能测试工具Lmbench介绍和使用说明

Linux性能测试工具Lmbench是一套简易可移植的,符合ANSI/C标准为UNIX/POSIX而制定的微型测评工具。一般来说,它衡量两个关键特征:反应时间和带宽。Lmbench旨在使系统开发者深入了解关键操作的基础成本。

1、Lmbench的使用与介绍

Linux性能测试工具Lmbench是一套简易可移植的,符合ANSI/C标准为UNIX/POSIX而制定的微型测评工具。一般来说,它衡量两个关键特征:反应时间和带宽。Lmbench旨在使系统开发者深入了解关键操作的基础成本。其官方网站是:http://www.bitmover.com/lmbench/。
2、Lmbench主要功能
带宽测评工具反应时间测评工具其他读取缓存文件
拷贝内存
读内存
写内存
管道
TCP上下文切换
网络:连接的建立,管道,TCP,UDP和RPChotpotato
文件系统的建立和删除
进程创建
信号处理
上层的系统调用
内存读入反应时间处理器时钟比率计算 Continue reading

device 0 has different MAC address than expected 的解决办法

今天克隆了一份vm(centos),发现重启网卡的时候提示”device 0 has different MAC address than expected…”之类的错误,手动修改mac地址也不行.后来找到一种解决办法如下:

删除 HWADDR 一行,然后执行ifconfig和service network restart命令.然后用ifconfig命令查看就会发现已经可以正常使用了.

不过在eth0文件里HWADDR这一行系统并没有自动添加上的.