Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > Linux tun虚拟网卡通信

Linux tun虚拟网卡通信的使用解读

作者:wifi chicken

这篇文章主要介绍了Linux tun虚拟网卡通信的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

什么是linux tun设备

Linux TUN 设备是一种虚拟网络设备,用于在用户空间和内核空间之间建立数据通道,使用户空间程序可以通过这个设备与内核网络栈进行交互。TUN 设备是一种通用的网络隧道设备,常用于实现虚拟专用网络(VPN)和其他网络隧道技术。

TUN 设备的工作原理

将网络数据包从用户空间发送到内核空间,或者从内核空间发送到用户空间。可以收发第三层数据报文包,如IP封包,这使得用户空间的应用程序可以读取和处理传入的数据包,然后将数据包发送回TUN 设备,再由内核负责将数据包发送到目标地址。

在使用 TUN 设备时,用户空间程序通常会打开 TUN 设备文件,并像读写普通文件一样对其进行读写操作。这样,用户空间程序就可以将网络数据包发送到 TUN 设备或者从 TUN 设备读取接收到的数据包。TUN 设备通常具有一个虚拟的 IP 地址,作为与内核网络栈进行交互的入口和出口

基本处理框架

如下是框架流程图:

linux tun设备可以用作什么技术

本文今天要完成什么?

使用虚拟机ubuntu 自带tun驱动完成:

完成框架

linux已经自带驱动:

ubuntu:/dev/net$ ls 
tun

应用层代码1:

基本逻辑:

#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>
#include <netinet/in.h>
#include <arpa/inet.h>

#define IP_VERSION 4
#define IP_HEADER_LENGTH 20 // IPv4头部长度,单位为字节
#define DEST_IP "10.0.0.2"

struct iphdr {
    unsigned char  ihl_version;
    unsigned char  tos;
    unsigned short total_length;
    unsigned short id;
    unsigned short frag_off;
    unsigned char  ttl;
    unsigned char  protocol;
    unsigned short checksum;
    unsigned int   saddr;
    unsigned int   daddr;
};
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;
    }
    system("sudo ifconfig tun0 10.0.0.1 up");//启动tun虚拟网卡
    system("sudo route add -net 10.0.0.2 netmask 255.255.255.255 dev tun0");//将所有发送到 10.0.0.2 的数据包,通过网络接口 "tun0" 进行传输,而且这个目标地址被视为一个单独的主机,而不是一个整个网络。
    system("sudo route add -net 192.168.6.1 netmask 255.255.255.255 dev tun0");
    printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name);

    return fd;
}

int main()
{

            int tun_fd, nread;
            char buffer[1500];
            char buffer1[IP_HEADER_LENGTH + 100]; // IP头部长度 + 应用层数据长度        
            tun_fd = tun_alloc(IFF_TUN | IFF_NO_PI);
            if (tun_fd < 0) 
            {
            perror("Allocating interface");
            exit(1);
            }
while (1) {
            //发送数据包到TUN/TAP设备
            memset(buffer,0,sizeof(buffer));
            //读取协议栈发送来的信息
            nread = read(tun_fd, buffer, sizeof(buffer));
            if (nread < 0) {
            close(tun_fd);
            exit(1);
            }
            printf("Read %zd bytes from tun/tap device\n", nread);
            // 以十六进制格式输出IP数据包
            for (int i = 0; i < nread; i++) {
            printf("%02X ", buffer[i]);
            if ((i + 1) % 16 == 0) {
            printf("\n");
            }
            }
            printf("\n");
            // 构造 IP 数据包头部,此处就可以进行数据加密,具体的功能未完成,可自行加密处理
            struct iphdr *ip_header = (struct iphdr *)buffer1;
            ip_header->ihl_version = (IP_VERSION << 4) | (IP_HEADER_LENGTH / 4);
            ip_header->tos = 0;
            ip_header->total_length = htons(IP_HEADER_LENGTH + 8); // IP 头部长度 + ICMP 数据长度
            ip_header->id = 0;
            ip_header->frag_off = 0;
            ip_header->ttl = 64;
            ip_header->protocol = 1;//IPPROTO_ICMPCMP 协议
            ip_header->checksum = 0; // 留空,内核会自动计算校验和
            ip_header->saddr = inet_addr("10.0.0.1"); // 源 IP 地址
            ip_header->daddr = inet_addr("14.0.0.2"); // 目标 IP 地址

            // 添加 ICMP 数据
            char *icmp_data = buffer1 + IP_HEADER_LENGTH;
            icmp_data[0] = 8; // ICMP 类型为 8(Echo Request)
            icmp_data[1] = 0; // ICMP 代码为 0
            icmp_data[2] = 0; // 校验和高位字节
            icmp_data[3] = 0; // 校验和低位字节
            icmp_data[4] = 0x12; // 标识符高位字节
            icmp_data[5] = 0x34; // 标识符低位字节
            icmp_data[6] = 0; // 序列号高位字节
            icmp_data[7] = 0; // 序列号低位字节

            // 计算 ICMP 校验和
            unsigned short checksum = 0;
            for (int i = 0; i < 8; i += 2) {
            checksum += (icmp_data[i] << 8) | icmp_data[i + 1];
            }
            checksum = (checksum >> 16) + (checksum & 0xFFFF);
            checksum = ~checksum;
            icmp_data[2] = (checksum >> 8) & 0xFF;
            icmp_data[3] = checksum & 0xFF;
            // 将数据包写入TUN设备的设备节点
            ssize_t num_bytes_sent = write(tun_fd, buffer1, IP_HEADER_LENGTH + 8);

            if (num_bytes_sent < 0) {
            perror("write");
            close(tun_fd);
            return -1;
            }
            printf("Sent %zd bytes to TUN device.\n", num_bytes_sent);
            }
            close(tun_fd);
            return 0;
}

应用层2代码

基本逻辑:

负责给虚拟网卡驱动发送应用层数据包

#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>
#include <netinet/in.h>
#include <arpa/inet.h>

#define DEST_IP "10.0.0.2"
#define DEST_IP1 "192.168.6.1"


int main()
{

// 创建套接字
            int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
            if (sockfd < 0) {
            perror("Error creating socket");
            //close(tun_fd);
            exit(1);
            }
            sleep(5);
            // 设置目标 IP 地址和端口
            printf("------------start-------------------\n");
            struct sockaddr_in dest_addr;
            memset(&dest_addr, 0, sizeof(dest_addr));
            dest_addr.sin_family = AF_INET;
            dest_addr.sin_port = htons(12345);
            inet_pton(AF_INET, DEST_IP, &(dest_addr.sin_addr));

            // 模拟发送数据到 TUN 设备
            const char* message = "Hello, TUN device!!!!!";

            int sockfd1 = socket(AF_INET, SOCK_DGRAM, 0);
            if (sockfd1 < 0) {
            perror("Error creating socket");
            //close(tun_fd);
            exit(1);
            }
            sleep(5);
            // 设置目标 IP 地址和端口
            printf("------------start-------------------\n");
            struct sockaddr_in dest_addr1;
            memset(&dest_addr1, 0, sizeof(dest_addr1));
            dest_addr1.sin_family = AF_INET;
            dest_addr1.sin_port = htons(1234);
            inet_pton(AF_INET, DEST_IP1, &(dest_addr1.sin_addr));

            // 模拟发送数据到 TUN 设备
            const char* message1 = "tun tunt tunt!!!!!!";

            while(1)
            {
            sendto(sockfd1, message1, strlen(message1), 0, (struct sockaddr*)&dest_addr1, sizeof(dest_addr1));        
               sleep(5);
               sendto(sockfd, message, strlen(message), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
            }
            close(sockfd);
            close(sockfd1);
  
}

应用层如何编译

如果您经常阅读我的文章,就不应该问出这样的问题,以前的文章都有提及!

验证

root权限执行应用层代码1结果(有删减):

sudo ./net_device_user1
RTNETLINK answers: File exists
Open tun/tap device: tun0 for reading...

Read 50 bytes from tun/tap device
45 00 00 32 03 FFFFFF9E 40 00 40 11 23 1B 0A 00 00 01 
0A 00 00 02 FFFFFF86 0D 30 39 00 1E 05 27 48 65 6C 6C 
6F 2C 20 54 55 4E 20 64 65 76 69 63 65 21 21 21 
21 21 
Sent 28 bytes to TUN device.
Read 47 bytes from tun/tap device
45 00 00 2F 03 FFFFFF9F 40 00 40 11 23 1D 0A 00 00 01 
0A 00 00 02 FFFFFFC0 3E 04 FFFFFFD2 00 1B FFFFFFF3 FFFFFFDE 74 75 6E 20 
74 75 6E 74 20 74 75 6E 74 21 21 21 21 21 21 
Sent 28 bytes to TUN device.

root权限执行应用层代码2结果

sudo ./net_device.o
------------start-------------------

抓取tupdump包

sudo tcpdump -i tun0 -w tcpdump_30.pcap
tcpdump: listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
tcpdump: pcap_loop: The interface went down
16 packets captured
16 packets received by filter
0 packets dropped by kernel

分别抓到了应用层发来的数据包(两个包),读取完后完成数据包的发送

利用十六进制转字符串验证应用代码2发送给应用代码1的数据是否发送成功:

Read 50 bytes from tun/tap device
45 00 00 32 03 FFFFFF9E 40 00 40 11 23 1B 0A 00 00 01 
0A 00 00 02 FFFFFF86 0D 30 39 00 1E 05 27 48 65 6C 6C 
6F 2C 20 54 55 4E 20 64 65 76 69 63 65 21 21 21 
21 21

其中的data数据转化结果:

Read 47 bytes from tun/tap device
45 00 00 2F 03 FFFFFF9F 40 00 40 11 23 1D 0A 00 00 01 
0A 00 00 02 FFFFFFC0 3E 04 FFFFFFD2 00 1B FFFFFFF3 FFFFFFDE 74 75 6E 20 
74 75 6E 74 20 74 75 6E 74 21 21 21 21 21 21 

结果

验证成功

ps:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文