Linux虚拟网络之tun(一)基本使用

来源:互联网 发布:edius mac 编辑:程序博客网 时间:2024/05/20 04:14

TUN/TAP 设备是一种让用户态程序向内核协议栈注入数据的设备,一个工作在三层,一个工作在二层。理论知识可以看:

  • 虚拟网卡 TUN/TAP 驱动程序设计原理。
  • TAP/TUN摘要
  • TAP/TUN浅析(一)
  • TAP/TUN(二)

本文只讲怎么用,直接上代码:

#include <fcntl.h>#include <sys/socket.h>#include <linux/ip.h>#include <linux/if.h>#include <linux/if_tun.h>#include <arpa/inet.h>#include <string.h>#include <sys/ioctl.h>#include <unistd.h>#include <iostream>#include <errno.h>#include <linux/if_ether.h>#include <linux/if_packet.h>#include <thread>using namespace std;void task(char* dev, int net_addr){    int           tun_fd;    struct ifreq        ifr;    tun_fd = open("/dev/net/tun", O_RDWR);    if(0 > tun_fd)    {        cout<<"open tun file error!" << endl;        return;    }    memset(&ifr, 0, sizeof(ifr));    ifr.ifr_flags = IFF_TUN | IFF_NO_PI;    strncpy(ifr.ifr_ifrn.ifrn_name, dev, IFNAMSIZ);    if(0 > ioctl(tun_fd, TUNSETIFF, &ifr))    {        string err_str = strerror(errno);        cout << "Failed to set TUN device name: " << err_str << endl;        close(tun_fd);        return;    }    // Bring up the interface    int sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));    if(0 > ioctl(sock, SIOCGIFFLAGS, &ifr))    {        string err_str = strerror(errno);        cout << "Failed to bring up socket: " << err_str << endl;        close(tun_fd);        return;    }    ifr.ifr_flags |= IFF_UP | IFF_RUNNING | IFF_PROMISC;    if(0 > ioctl(sock, SIOCSIFFLAGS, &ifr))    {        string err_str = strerror(errno);        cout << "Failed to set socket flags: " << err_str << endl;        close(tun_fd);        return;    }    ifr.ifr_addr.sa_family                                 = AF_INET;    ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr = net_addr;    if(0 > ioctl(sock, SIOCSIFADDR, &ifr))    {        string err_str = strerror(errno);        cout << "Failed to set socket address: " << err_str << endl;        close(tun_fd);        return;    }    ifr.ifr_netmask.sa_family                                 = AF_INET;    ((struct sockaddr_in *)&ifr.ifr_netmask)->sin_addr.s_addr = inet_addr("255.255.255.0");    if(0 > ioctl(sock, SIOCSIFNETMASK, &ifr))    {        string err_str = strerror(errno);        cout << "Failed to set socket netmask: " << err_str << endl;        close(tun_fd);        return;    }    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0)    {        string err_str = strerror(errno);        cout << "Failed to get socket index: " << err_str << endl;        close (tun_fd);        return;    }    struct sockaddr_ll addr;    memset (&addr, 0, sizeof (addr));    addr.sll_family   = AF_PACKET;    addr.sll_ifindex  = ifr.ifr_ifindex;    addr.sll_protocol = htons(ETH_P_ALL);    printf("addr.sll_ifindex  %d" ,addr.sll_ifindex);    if (-1 == bind(sock, (struct sockaddr *)&addr, sizeof(addr)))    {        string err_str = strerror(errno);        printf("binding socket %d with if %s error: %s",                sock, dev, strerror(errno));        close (tun_fd);        return;    }    int N_bytes;    unsigned char msg[8000];    in_addr ip_addr;    ip_addr.s_addr = net_addr;    int j = 500;    while(j-- > 0)    {        N_bytes = read(tun_fd, msg, 8000);        if (N_bytes > 0)        {            printf("%s recv msg len: %3d, msg: ", inet_ntoa(ip_addr), N_bytes);            for (int i = 0; i < 20 && i < N_bytes; i++) printf("%2x ", msg[i]);            cout << endl;        }    }    while(true)    {        N_bytes = recv (sock, msg, 8000, 0);        if (N_bytes > 0)        {            printf("%s sock recv msg len: %3d, msg: ", inet_ntoa(ip_addr), N_bytes);            for (int i = 0; i < 20 && i < N_bytes; i++) printf("%2x ", msg[i]);            cout << endl;        }    }}int main(){    char dev[IFNAMSIZ] = "tun0";    char dev2[IFNAMSIZ] = "tun1";    thread t(task, dev, inet_addr("172.23.1.25"));    thread t2(task, dev2, inet_addr("172.23.1.24"));    t.join();    t2.join();}

1、上面代码建立了两个tun设备(tun0和tun1),每个设备有自己的地址。
2、task中演示了两种访问tun设备的方式 – 文件方式和socket方式(为了让socket能收发所有的包,用了SOCK_RAW,这里可以根据自己的需要建立其他协议类型的socket)。
3、假设有个目标地址是 10.0.0.10,执行下面的命令,程序就可以打印所接收到的包了。

ping 10.0.0.10 -I tun0

curl可以如下使用:

sudo curl --interface tun_srsue -O target_url

4、如果使用tun方式,收到的包都是ip包;如果使用tap方式,收到的是以太网帧。
5、跟netlink其实有点像,很多场景下可以互换。不过从使用方便程度看,tun占很大优势,因为不用编写内核驱动。跟netlink的性能没有对比测试,有空了测测。

说到使用方便性,在go语言中体现更明显。目前为止还没有哪个netlink库能很方便的使用,但是tun库是有的,代码如下:

package mainimport (    "log"    "github.com/songgao/water")func main() {    ifce, err := water.New(water.Config{        DeviceType: water.TUN,    })    if err != nil {        log.Fatal(err)    }    log.Printf("Interface Name: %s\n", ifce.Name())    packet := make([]byte, 2000)    for {        n, err := ifce.Read(packet)        if err != nil {            log.Fatal(err)        }        log.Printf("Packet Received: % x\n", packet[:n])    }}
0 0
原创粉丝点击