Linux tun虚拟网卡通信初识

news2024/11/25 16:21:23

什么是linux tun设备

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

TUN 设备的工作原理

  1. 将网络数据包从用户空间发送到内核空间,或者从内核空间发送到用户空间。可以收发第三层数据报文包,如IP封包,这使得用户空间的应用程序可以读取和处理传入的数据包,然后将数据包发送回TUN 设备,再由内核负责将数据包发送到目标地址。
  2. 在使用 TUN 设备时,用户空间程序通常会打开 TUN 设备文件,并像读写普通文件一样对其进行读写操作。这样,用户空间程序就可以将网络数据包发送到 TUN 设备或者从 TUN 设备读取接收到的数据包。TUN 设备通常具有一个虚拟的 IP 地址,作为与内核网络栈进行交互的入口和出口

基本处理框架

  1. 创建 TUN 设备: 在 Linux 系统中,可以使用 ip 命令或者其他网络管理工具来创建 TUN 设备。
  2. 用户空间应用程序与 TUN 设备交互: 用户空间的应用程序通常会打开 TUN 设备文件,这类似于打开普通文件。例如,应用程序可以打开 /dev/net/tun 设备文件。
  3. 数据包传输: 当用户空间的应用程序向 TUN 设备文件写入数据包时,数据包将被发送到内核空间的 TUN 设备驱动程序。这个过程是由内核的 TUN/TAP 驱动来完成的。
  4. 内核处理: 内核中的 TUN/TAP 驱动程序接收从用户空间传入的数据包。对于 TUN 设备,它会将数据包解析为 IP 数据包,并将其发送到内核网络栈进行进一步处理。
  5. 数据包处理: 在内核网络栈中,数据包将按照路由表和网络配置进行处理。如果数据包的目标地址与本地网络或者路由表匹配,那么数据包将被内核转发到目标地址。
  6. 接收数据包: 当内核收到其他网络设备传入的数据包(如网络接口收到的数据包),如果目标地址是 TUN 设备的 IP 地址,那么数据包将被传递给 TUN 设备驱动程序。
  7. 用户空间读取: 数据包通过 TUN 设备驱动程序传递到用户空间的应用程序打开的 TUN 设备文件。应用程序可以读取这些数据包并进行处理。

如下是框架流程图:
在这里插入图片描述

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

  1. VPN(Virtual Private Network): TUN 设备可用于构建 VPN。通过将数据包从用户空间发送到内核空间,再通过TUN 设备进行加密、隧道封装和传输,可以实现安全的远程访问和数据传输。
  2. 隧道技术: TUN 设备也可用于其他隧道技术,如隧道模式下的 IPv6-over-IPv4 和 IPv4-over-IPv6 隧道。它允许不同网络之间通过隧道进行通信。
  3. 加密通信: TUN 设备可以用于实现端到端的加密通信,保护数据的安全性和隐私。
  4. 虚拟专用网络: 使用 TUN 设备,可以创建虚拟专用网络(VPN)或虚拟局域网(VLAN),将不同的网络或子网连接在一起。
  5. 网络隔离: TUN 设备可以用于实现网络隔离,将不同的应用程序或服务隔离在不同的虚拟网络中,增强网络的安全性。
  6. 协议代理: TUN 设备还可以用作协议代理,允许用户空间应用程序处理特定的网络协议,例如将 UDP 或 TCP 流量进行自定义处理。
  7. 网络测试和仿真: 利用 TUN 设备,可以在用户空间中模拟网络环境,用于测试和仿真网络应用程序的性能和稳定性。

本文今天要完成什么?

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

  1. 虚拟驱动的启动
  2. 应用层发包给虚拟网卡驱动tun并写入设备节点文件,应用层完成读取,加密,写入设备节点并发送给协议栈
  3. 使用tcpdump工具抓取包验证是否正确发送与接收

完成框架

  1. 利用已有的tun设备驱动,打开并配置ip,添加静态路由表
  2. 写两个应用层进程,分别完成数据发送到tun和数据接收处理加密并发回tun驱动

linux已经自带驱动:

ubuntu:/dev/net$ ls 
tun

应用层代码1:
基本逻辑:

  1. 打开虚拟设备网卡
  2. 添加静态路由
  3. 阻塞等待数据的到来
  4. read后,再进行wirite操作
#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

完成的功能很少,希望这能抛砖引玉
代码解释不是特别详细,代码行中有很多注释,希望能帮助到你

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/849515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

适配器模式-java实现

意图 复用已经存在的接口&#xff0c;与所需接口不一致的类。即将一个类&#xff08;通常是旧系统中的功能类&#xff09;&#xff0c;通过适配器转化成另一个接口的实现。&#xff08;简单来说&#xff0c;就是复用旧系统的功能&#xff0c;去实现新的接口&#xff09; 我们举…

Python Web开发 Jinja2模板引擎

在之前的文章中&#xff0c;简单介绍了Python Web开发框架Flask&#xff0c;知道了如何写个Hello World&#xff0c;但是距离用Flask开发真正的项目&#xff0c;还有段距离&#xff0c;现在我们目标更靠近一些 —— 学习下Jinja2模板。 模板的作用 模板是用来做什么的呢&…

C 语言高级3--函数指针回调函数,预处理,动态库的封装

目录 1.函数指针和回调函数 1.1 函数指针 1.1.1 函数类型 1.1.2 函数指针(指向函数的指针) 1.1.3 函数指针数组 1.1.4 函数指针做函数参数(回调函数) 2.预处理 2.1 预处理的基本概念 2.2 文件包含指令(#include) 2.2.1 文件包含处理 2.2.2 #incude<>和#include&q…

数据结构:栈的实现(C实现)

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》 文章目录 前言一、栈的实现思路1. 结构的定义2. 初始化栈(StackInit)3. 入栈(StackPush)4. 出栈(StackPop)5. 获取栈顶元素(StackTop)6. 检查栈是否为空(StackEmpty)7. 销毁栈(StackDestroy) 二、…

踩坑 视觉SLAM 十四讲第二版 ch13 编译及运行问题

一、安装Geset 库 sudo apt-get install libgtest-dev cd /usr/src/gtest sudo mkdir build cd build sudo cmake .. //一定要以sudo的方式运行&#xff0c;否则没有写入权限 sudo make //这个也一样要以sudo的方式 sudo cp libgtest*.a /usr/local/lib //将生成…

python表白代码大全可复制,python表白代码大全简单

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python表白代码大全可复制&#xff0c;python表白程序代码完整版&#xff0c;现在让我们一起来看看吧&#xff01; 今天是20230520&#xff0c;有人说&#xff1a;5代表的是人生五味&#xff0c;酸甜苦辣咸&#xff1b;…

设备管理平台:采用以可靠性为中心的维护策略的优势

在如今的工业领域&#xff0c;以可靠性为中心的维护策略正逐渐成为企业数字化转型的核心。无论是混合还是离散自动化应用&#xff0c;优化维护和工作流程实践已经成为提高利润、降低停机时间、增强运营和生产性能的不可或缺的一环。在这个过程中&#xff0c;设备管理系统与物联…

Harbor部署--使用 Harbor 安装包

一、Harbor安装准备条件 这里以 harbor 2.8.3 版本为例 1.1 硬件要求 Harbor 安装对硬件资源CPU、内存和硬盘的要求如下表&#xff1a; 资源 最小要求 推荐配置 CPU 2 CPU 4 CPU Mem 4 GB 8 GB Disk 40 GB 160 GB 使用如下命令分别查看服务器的物理CPU和逻辑CPU个数…

LCD1602相关

一.概述 LCD1602是一种工业字符型液晶&#xff0c;能够同时显示16*02即32字符&#xff08;16列2行&#xff09; 二.引脚接口说明表 第 1 脚 : VSS 为电源地 第 2 脚 : VDD 接 5V 正电源 第 3 脚 : VL 为液晶显示器对比度调整端 , 接正电源时对比度最弱&#xff…

第三章 图论 No.8最近公共祖先lca, tarjan与次小生成树

文章目录 lcaTarjan板子题&#xff1a;1172. 祖孙询问lca或tarjan&#xff1a;1171. 距离356. 次小生成树352. 闇の連鎖 lca O ( m l o g n ) O(mlogn) O(mlogn)&#xff0c;n为节点数量&#xff0c;m为询问次数&#xff0c;lca是一种在线处理询问的算法 自己也是自己的祖先 倍…

XML方式AOP快速入门XML方式AOP配置详解

目录 1.XML方式AOP快速入门 1&#xff1a;导入AOP相关坐标 2&#xff1a;准备目标类&#xff0c;准备增强类&#xff0c;并配置给Spring管理 3&#xff1a;配置切点表达式&#xff08;那些方法要被增强&#xff09; 4&#xff1a;配置织入&#xff08;切点被哪些方法增强&…

漫画算法做题笔记

诸神缄默不语-个人CSDN博文目录 哦这是我三年前写的&#xff0c;我现在Java语法都快忘光了…… 反正之前的博文也发一下好了。这个因为我当年是用有道云笔记而不是直接用CSDN编辑器写的&#xff0c;所以后面有些内容写乱了&#xff0c;因为我现在猛的一看有点看不懂&#xff0…

一文读懂|RDMA原理

什么是DMA DMA全称为Direct Memory Access&#xff0c;即直接内存访问。意思是外设对内存的读写过程可以不用CPU参与而直接进行。我们先来看一下没有DMA的时候&#xff1a; 无DMA控制器时I/O设备和内存间的数据路径 假设I/O设备为一个普通网卡&#xff0c;为了从内存拿到需要…

事务的隔离级别与Spring事务的传播机制

目录 事务的隔离性 事务的隔离级别 读未提交 读已提交 可重复读 串行化 Spring事务的传播机制 支持当前事务 不支持当前事务 嵌套事务 事务的隔离性 事务的隔离性是事务的四大特性之一&#xff0c;数据库允许多个事务并发操作数据&#xff0c;为了尽可能地避免并发操…

【JAVA】-【IO流】

文章目录 FileReader读入数据的基本操作FileReader中使用reader()FileWrite写出数据的操作使用FileInputStream、FileOutputStream操作图片缓冲流&#xff08;字节型&#xff09;实现非文本文件的复制 复制文本文件也可以使用字节流&#xff0c;但是不要在内存中读出来&#xf…

【Matlab】Elman神经网络遗传算法(Elman-GA)函数极值寻优——非线性函数求极值

往期博客&#x1f449; 【Matlab】BP神经网络遗传算法(BP-GA)函数极值寻优——非线性函数求极值 【Matlab】GRNN神经网络遗传算法(GRNN-GA)函数极值寻优——非线性函数求极值 【Matlab】RBF神经网络遗传算法(RBF-GA)函数极值寻优——非线性函数求极值 本篇博客将主要介绍Elman神…

【Spring Boot】Spring Boot项目的创建和文件配置

目录 一、为什么要学Spring Boot 1、Spring Boot的优点 二、创建Spring Boot项目 1、创建项目之前的准备工作 2、创建Spring Boot项目 3、项目目录的介绍 4、安装Spring Boot快速添加依赖的插件 5、在项目中写一个helloworld 三、Spring Boot的配置文件 1、配置文件的…

opencv基础48-绘制图像轮廓并切割示例-cv2.drawContours()

绘制图像轮廓&#xff1a;drawContours函数 在 OpenCV 中&#xff0c;可以使用函数 cv2.drawContours()绘制图像轮廓。该函数的语法格式是&#xff1a; imagecv2.drawContours( image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]…

mousedown拖拽功能(vue3+ts)

因为项目有rem适配&#xff0c;使用第三方插件无法处理适配问题&#xff0c;所有只能自己写拖拽功能了 拖拽一般都会想到按下&#xff0c;移动&#xff0c;放开&#xff0c;但是本人亲测&#xff0c;就在div绑定一个按下事件就行了&#xff08;在事件里面写另外两个事件&#x…

前端架构师的具体职责范围(合集)

前端架构师的具体职责范围1 职责&#xff1a; 1、前端技术选型、架构搭建、制定前端开发规范&#xff0c;并编制相关文档 2、负责搭建前端框架、通用组件方案制定、性能优化相关工作; 3、维护和升级本地开发环境&#xff0c;提高开发效率&#xff0c;提升开发质量; 4、推动…