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

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

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

#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 main

import (
    "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])
    }
}
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的示例代码,使用golang实现tun虚拟网卡的读写: ```go package main import ( "fmt" "net" "os" "syscall" ) const ( MTU = 1500 IFNAME = "tun0" IFPREFIX = "/dev/net/tun" ) func main() { // 打开tun设备 fd, err := syscall.Open(IFPREFIX, os.O_RDWR, 0) if err != nil { fmt.Println("Open tun device failed:", err) return } defer syscall.Close(fd) // 设置tun设备 ifr := &syscall.Ifreq{} copy(ifr.Name[:], IFNAME) ifr.Flags = syscall.IFF_TUN | syscall.IFF_NO_PI _, _, err = syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(ifr))) if err != 0 { fmt.Println("Set tun device failed:", err) return } // 获取tun设备的IP地址和子网掩码 iface, err := net.InterfaceByName(IFNAME) if err != nil { fmt.Println("Get interface failed:", err) return } addrs, err := iface.Addrs() if err != nil { fmt.Println("Get address failed:", err) return } for _, addr := range addrs { ip, _, err := net.ParseCIDR(addr.String()) if err == nil && ip.To4() != nil { fmt.Println("IP Address:", ip) fmt.Println("Subnet Mask:", net.IPv4Mask(255, 255, 255, 0)) break } } // 读取数据 buf := make([]byte, MTU) for { n, err := syscall.Read(fd, buf) if err != nil { fmt.Println("Read from tun device failed:", err) return } fmt.Println("Read:", buf[:n]) } // 写入数据 data := []byte("Hello, world!") n, err := syscall.Write(fd, data) if err != nil { fmt.Println("Write to tun device failed:", err) return } fmt.Println("Write:", n) } ``` 该代码首先打开tun设备,并设置为tun模式。然后获取tun设备的IP地址和子网掩码,并开始读取数据。在读取到数据后,将数据打印出来。最后,该代码将Hello, world!写入tun设备。 需要注意的是,该代码需要root权限才能运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值