Linux tun虛擬網(wǎng)卡通信的使用解讀
什么是linux tun設(shè)備
Linux TUN 設(shè)備是一種虛擬網(wǎng)絡(luò)設(shè)備,用于在用戶(hù)空間和內(nèi)核空間之間建立數(shù)據(jù)通道,使用戶(hù)空間程序可以通過(guò)這個(gè)設(shè)備與內(nèi)核網(wǎng)絡(luò)棧進(jìn)行交互。TUN 設(shè)備是一種通用的網(wǎng)絡(luò)隧道設(shè)備,常用于實(shí)現(xiàn)虛擬專(zhuān)用網(wǎng)絡(luò)(VPN)和其他網(wǎng)絡(luò)隧道技術(shù)。
TUN 設(shè)備的工作原理
將網(wǎng)絡(luò)數(shù)據(jù)包從用戶(hù)空間發(fā)送到內(nèi)核空間,或者從內(nèi)核空間發(fā)送到用戶(hù)空間??梢允瞻l(fā)第三層數(shù)據(jù)報(bào)文包,如IP封包,這使得用戶(hù)空間的應(yīng)用程序可以讀取和處理傳入的數(shù)據(jù)包,然后將數(shù)據(jù)包發(fā)送回TUN 設(shè)備,再由內(nèi)核負(fù)責(zé)將數(shù)據(jù)包發(fā)送到目標(biāo)地址。
在使用 TUN 設(shè)備時(shí),用戶(hù)空間程序通常會(huì)打開(kāi) TUN 設(shè)備文件,并像讀寫(xiě)普通文件一樣對(duì)其進(jìn)行讀寫(xiě)操作。這樣,用戶(hù)空間程序就可以將網(wǎng)絡(luò)數(shù)據(jù)包發(fā)送到 TUN 設(shè)備或者從 TUN 設(shè)備讀取接收到的數(shù)據(jù)包。TUN 設(shè)備通常具有一個(gè)虛擬的 IP 地址,作為與內(nèi)核網(wǎng)絡(luò)棧進(jìn)行交互的入口和出口
基本處理框架
- 創(chuàng)建 TUN 設(shè)備: 在 Linux 系統(tǒng)中,可以使用 ip 命令或者其他網(wǎng)絡(luò)管理工具來(lái)創(chuàng)建 TUN 設(shè)備。
- 用戶(hù)空間應(yīng)用程序與 TUN 設(shè)備交互: 用戶(hù)空間的應(yīng)用程序通常會(huì)打開(kāi) TUN 設(shè)備文件,這類(lèi)似于打開(kāi)普通文件。例如,應(yīng)用程序可以打開(kāi) /dev/net/tun 設(shè)備文件。
- 數(shù)據(jù)包傳輸: 當(dāng)用戶(hù)空間的應(yīng)用程序向 TUN 設(shè)備文件寫(xiě)入數(shù)據(jù)包時(shí),數(shù)據(jù)包將被發(fā)送到內(nèi)核空間的 TUN 設(shè)備驅(qū)動(dòng)程序。這個(gè)過(guò)程是由內(nèi)核的 TUN/TAP 驅(qū)動(dòng)來(lái)完成的。
- 內(nèi)核處理: 內(nèi)核中的 TUN/TAP 驅(qū)動(dòng)程序接收從用戶(hù)空間傳入的數(shù)據(jù)包。對(duì)于 TUN 設(shè)備,它會(huì)將數(shù)據(jù)包解析為 IP 數(shù)據(jù)包,并將其發(fā)送到內(nèi)核網(wǎng)絡(luò)棧進(jìn)行進(jìn)一步處理。
- 數(shù)據(jù)包處理: 在內(nèi)核網(wǎng)絡(luò)棧中,數(shù)據(jù)包將按照路由表和網(wǎng)絡(luò)配置進(jìn)行處理。如果數(shù)據(jù)包的目標(biāo)地址與本地網(wǎng)絡(luò)或者路由表匹配,那么數(shù)據(jù)包將被內(nèi)核轉(zhuǎn)發(fā)到目標(biāo)地址。
- 接收數(shù)據(jù)包: 當(dāng)內(nèi)核收到其他網(wǎng)絡(luò)設(shè)備傳入的數(shù)據(jù)包(如網(wǎng)絡(luò)接口收到的數(shù)據(jù)包),如果目標(biāo)地址是 TUN 設(shè)備的 IP 地址,那么數(shù)據(jù)包將被傳遞給 TUN 設(shè)備驅(qū)動(dòng)程序。
- 用戶(hù)空間讀?。?數(shù)據(jù)包通過(guò) TUN 設(shè)備驅(qū)動(dòng)程序傳遞到用戶(hù)空間的應(yīng)用程序打開(kāi)的 TUN 設(shè)備文件。應(yīng)用程序可以讀取這些數(shù)據(jù)包并進(jìn)行處理。
如下是框架流程圖:

linux tun設(shè)備可以用作什么技術(shù)
- VPN(Virtual Private Network): TUN 設(shè)備可用于構(gòu)建 VPN。通過(guò)將數(shù)據(jù)包從用戶(hù)空間發(fā)送到內(nèi)核空間,再通過(guò)TUN 設(shè)備進(jìn)行加密、隧道封裝和傳輸,可以實(shí)現(xiàn)安全的遠(yuǎn)程訪(fǎng)問(wèn)和數(shù)據(jù)傳輸。
- 隧道技術(shù): TUN 設(shè)備也可用于其他隧道技術(shù),如隧道模式下的 IPv6-over-IPv4 和 IPv4-over-IPv6 隧道。它允許不同網(wǎng)絡(luò)之間通過(guò)隧道進(jìn)行通信。
- 加密通信: TUN 設(shè)備可以用于實(shí)現(xiàn)端到端的加密通信,保護(hù)數(shù)據(jù)的安全性和隱私。
- 虛擬專(zhuān)用網(wǎng)絡(luò): 使用 TUN 設(shè)備,可以創(chuàng)建虛擬專(zhuān)用網(wǎng)絡(luò)(VPN)或虛擬局域網(wǎng)(VLAN),將不同的網(wǎng)絡(luò)或子網(wǎng)連接在一起。
- 網(wǎng)絡(luò)隔離: TUN 設(shè)備可以用于實(shí)現(xiàn)網(wǎng)絡(luò)隔離,將不同的應(yīng)用程序或服務(wù)隔離在不同的虛擬網(wǎng)絡(luò)中,增強(qiáng)網(wǎng)絡(luò)的安全性。
- 協(xié)議代理: TUN 設(shè)備還可以用作協(xié)議代理,允許用戶(hù)空間應(yīng)用程序處理特定的網(wǎng)絡(luò)協(xié)議,例如將 UDP 或 TCP 流量進(jìn)行自定義處理。
- 網(wǎng)絡(luò)測(cè)試和仿真: 利用 TUN 設(shè)備,可以在用戶(hù)空間中模擬網(wǎng)絡(luò)環(huán)境,用于測(cè)試和仿真網(wǎng)絡(luò)應(yīng)用程序的性能和穩(wěn)定性。
本文今天要完成什么?
使用虛擬機(jī)ubuntu 自帶tun驅(qū)動(dòng)完成:
- 虛擬驅(qū)動(dòng)的啟動(dòng)
- 應(yīng)用層發(fā)包給虛擬網(wǎng)卡驅(qū)動(dòng)tun并寫(xiě)入設(shè)備節(jié)點(diǎn)文件,應(yīng)用層完成讀取,加密,寫(xiě)入設(shè)備節(jié)點(diǎn)并發(fā)送給協(xié)議棧
- 使用tcpdump工具抓取包驗(yàn)證是否正確發(fā)送與接收
完成框架
- 利用已有的tun設(shè)備驅(qū)動(dòng),打開(kāi)并配置ip,添加靜態(tài)路由表
- 寫(xiě)兩個(gè)應(yīng)用層進(jìn)程,分別完成數(shù)據(jù)發(fā)送到tun和數(shù)據(jù)接收處理加密并發(fā)回tun驅(qū)動(dòng)
linux已經(jīng)自帶驅(qū)動(dòng):
ubuntu:/dev/net$ ls tun
應(yīng)用層代碼1:
基本邏輯:
- 打開(kāi)虛擬設(shè)備網(wǎng)卡
- 添加靜態(tài)路由
- 阻塞等待數(shù)據(jù)的到來(lái)
- read后,再進(jìn)行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頭部長(zhǎng)度,單位為字節(jié)
#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");//啟動(dòng)tun虛擬網(wǎng)卡
system("sudo route add -net 10.0.0.2 netmask 255.255.255.255 dev tun0");//將所有發(fā)送到 10.0.0.2 的數(shù)據(jù)包,通過(guò)網(wǎng)絡(luò)接口 "tun0" 進(jìn)行傳輸,而且這個(gè)目標(biāo)地址被視為一個(gè)單獨(dú)的主機(jī),而不是一個(gè)整個(gè)網(wǎng)絡(luò)。
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頭部長(zhǎng)度 + 應(yīng)用層數(shù)據(jù)長(zhǎng)度
tun_fd = tun_alloc(IFF_TUN | IFF_NO_PI);
if (tun_fd < 0)
{
perror("Allocating interface");
exit(1);
}
while (1) {
//發(fā)送數(shù)據(jù)包到TUN/TAP設(shè)備
memset(buffer,0,sizeof(buffer));
//讀取協(xié)議棧發(fā)送來(lái)的信息
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);
// 以十六進(jìn)制格式輸出IP數(shù)據(jù)包
for (int i = 0; i < nread; i++) {
printf("%02X ", buffer[i]);
if ((i + 1) % 16 == 0) {
printf("\n");
}
}
printf("\n");
// 構(gòu)造 IP 數(shù)據(jù)包頭部,此處就可以進(jìn)行數(shù)據(jù)加密,具體的功能未完成,可自行加密處理
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 頭部長(zhǎng)度 + ICMP 數(shù)據(jù)長(zhǎng)度
ip_header->id = 0;
ip_header->frag_off = 0;
ip_header->ttl = 64;
ip_header->protocol = 1;//IPPROTO_ICMPCMP 協(xié)議
ip_header->checksum = 0; // 留空,內(nèi)核會(huì)自動(dòng)計(jì)算校驗(yàn)和
ip_header->saddr = inet_addr("10.0.0.1"); // 源 IP 地址
ip_header->daddr = inet_addr("14.0.0.2"); // 目標(biāo) IP 地址
// 添加 ICMP 數(shù)據(jù)
char *icmp_data = buffer1 + IP_HEADER_LENGTH;
icmp_data[0] = 8; // ICMP 類(lèi)型為 8(Echo Request)
icmp_data[1] = 0; // ICMP 代碼為 0
icmp_data[2] = 0; // 校驗(yàn)和高位字節(jié)
icmp_data[3] = 0; // 校驗(yàn)和低位字節(jié)
icmp_data[4] = 0x12; // 標(biāo)識(shí)符高位字節(jié)
icmp_data[5] = 0x34; // 標(biāo)識(shí)符低位字節(jié)
icmp_data[6] = 0; // 序列號(hào)高位字節(jié)
icmp_data[7] = 0; // 序列號(hào)低位字節(jié)
// 計(jì)算 ICMP 校驗(yàn)和
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;
// 將數(shù)據(jù)包寫(xiě)入TUN設(shè)備的設(shè)備節(jié)點(diǎn)
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;
}應(yīng)用層2代碼
基本邏輯:
負(fù)責(zé)給虛擬網(wǎng)卡驅(qū)動(dòng)發(fā)送應(yīng)用層數(shù)據(jù)包
#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()
{
// 創(chuàng)建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("Error creating socket");
//close(tun_fd);
exit(1);
}
sleep(5);
// 設(shè)置目標(biāo) 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));
// 模擬發(fā)送數(shù)據(jù)到 TUN 設(shè)備
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);
// 設(shè)置目標(biāo) 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));
// 模擬發(fā)送數(shù)據(jù)到 TUN 設(shè)備
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);
}應(yīng)用層如何編譯
如果您經(jīng)常閱讀我的文章,就不應(yīng)該問(wèn)出這樣的問(wèn)題,以前的文章都有提及!
驗(yàn)證
root權(quán)限執(zhí)行應(yīng)用層代碼1與結(jié)果(有刪減):
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權(quán)限執(zhí)行應(yīng)用層代碼2與結(jié)果:
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

分別抓到了應(yīng)用層發(fā)來(lái)的數(shù)據(jù)包(兩個(gè)包),讀取完后完成數(shù)據(jù)包的發(fā)送
利用十六進(jìn)制轉(zhuǎn)字符串驗(yàn)證應(yīng)用代碼2發(fā)送給應(yīng)用代碼1的數(shù)據(jù)是否發(fā)送成功:
- 第一個(gè)包
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數(shù)據(jù)轉(zhuǎn)化結(jié)果:

- 第二個(gè)包
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

結(jié)果
驗(yàn)證成功
ps:
- 完成的功能很少,希望這能拋磚引玉
- 代碼解釋不是特別詳細(xì),代碼行中有很多注釋?zhuān)M軒椭侥?/li>
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Linux進(jìn)程池實(shí)現(xiàn)的詳細(xì)指南
如果你了解過(guò)STL的底層設(shè)計(jì),你會(huì)發(fā)現(xiàn)在其中會(huì)有一個(gè)叫做內(nèi)存池的設(shè)計(jì),其作用就是先申請(qǐng)出一片空間,如果后續(xù)你需要對(duì)你的容器進(jìn)行擴(kuò)容,所擴(kuò)展的空間就從內(nèi)存池里取的,本文給大家介紹了Linux進(jìn)程池實(shí)現(xiàn)的詳細(xì)指南,需要的朋友可以參考下2024-11-11
Linux下進(jìn)程數(shù)量的限制pid_max的配置方法
這篇文章主要介紹了Linux下進(jìn)程數(shù)量的限制pid_max的方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-07-07
Linux進(jìn)程使用文件進(jìn)行通信的常用方法
在Linux系統(tǒng)中,進(jìn)程間通信(IPC)是構(gòu)建復(fù)雜應(yīng)用的關(guān)鍵技術(shù),使用文件進(jìn)行通信是一種簡(jiǎn)單而有效的方法,特別適合需要持久化數(shù)據(jù)或跨系統(tǒng)邊界的場(chǎng)景,本文將介紹幾種主要的文件通信技術(shù)及其應(yīng)用場(chǎng)景,需要的朋友可以參考下2025-11-11
Linux的/etc/hosts文件的基礎(chǔ)知識(shí)和在實(shí)用指南
在 Linux 和 Unix 系統(tǒng)中,/etc/hosts 文件扮演著一個(gè)至關(guān)重要的角色,在網(wǎng)絡(luò)和系統(tǒng)管理中,了解如何正確使用這個(gè)文件能夠大大提升效率和靈活性,本文將深入探討 /etc/hosts 文件的用途、結(jié)構(gòu)和實(shí)際應(yīng)用,需要的朋友可以參考下2026-02-02
CentOS6.5安裝glibc-2.14后locale出現(xiàn)No?such?file?or?directory解
大家好,本篇文章主要講了CentOS6.5安裝glibc-2.14后locale出現(xiàn)No?such?file?or?directory解決辦法,感興趣的同學(xué)趕快來(lái)看看吧,希望對(duì)你有幫助2021-11-11

