Linux tun虛擬網(wǎng)卡通信的使用解讀
什么是linux tun設(shè)備
Linux TUN 設(shè)備是一種虛擬網(wǎng)絡(luò)設(shè)備,用于在用戶空間和內(nèi)核空間之間建立數(shù)據(jù)通道,使用戶空間程序可以通過這個設(shè)備與內(nèi)核網(wǎng)絡(luò)棧進行交互。TUN 設(shè)備是一種通用的網(wǎng)絡(luò)隧道設(shè)備,常用于實現(xiàn)虛擬專用網(wǎng)絡(luò)(VPN)和其他網(wǎng)絡(luò)隧道技術(shù)。
TUN 設(shè)備的工作原理
將網(wǎng)絡(luò)數(shù)據(jù)包從用戶空間發(fā)送到內(nèi)核空間,或者從內(nèi)核空間發(fā)送到用戶空間??梢允瞻l(fā)第三層數(shù)據(jù)報文包,如IP封包,這使得用戶空間的應(yīng)用程序可以讀取和處理傳入的數(shù)據(jù)包,然后將數(shù)據(jù)包發(fā)送回TUN 設(shè)備,再由內(nèi)核負責將數(shù)據(jù)包發(fā)送到目標地址。
在使用 TUN 設(shè)備時,用戶空間程序通常會打開 TUN 設(shè)備文件,并像讀寫普通文件一樣對其進行讀寫操作。這樣,用戶空間程序就可以將網(wǎng)絡(luò)數(shù)據(jù)包發(fā)送到 TUN 設(shè)備或者從 TUN 設(shè)備讀取接收到的數(shù)據(jù)包。TUN 設(shè)備通常具有一個虛擬的 IP 地址,作為與內(nèi)核網(wǎng)絡(luò)棧進行交互的入口和出口
基本處理框架
- 創(chuàng)建 TUN 設(shè)備: 在 Linux 系統(tǒng)中,可以使用 ip 命令或者其他網(wǎng)絡(luò)管理工具來創(chuàng)建 TUN 設(shè)備。
- 用戶空間應(yīng)用程序與 TUN 設(shè)備交互: 用戶空間的應(yīng)用程序通常會打開 TUN 設(shè)備文件,這類似于打開普通文件。例如,應(yīng)用程序可以打開 /dev/net/tun 設(shè)備文件。
- 數(shù)據(jù)包傳輸: 當用戶空間的應(yīng)用程序向 TUN 設(shè)備文件寫入數(shù)據(jù)包時,數(shù)據(jù)包將被發(fā)送到內(nèi)核空間的 TUN 設(shè)備驅(qū)動程序。這個過程是由內(nèi)核的 TUN/TAP 驅(qū)動來完成的。
- 內(nèi)核處理: 內(nèi)核中的 TUN/TAP 驅(qū)動程序接收從用戶空間傳入的數(shù)據(jù)包。對于 TUN 設(shè)備,它會將數(shù)據(jù)包解析為 IP 數(shù)據(jù)包,并將其發(fā)送到內(nèi)核網(wǎng)絡(luò)棧進行進一步處理。
- 數(shù)據(jù)包處理: 在內(nèi)核網(wǎng)絡(luò)棧中,數(shù)據(jù)包將按照路由表和網(wǎng)絡(luò)配置進行處理。如果數(shù)據(jù)包的目標地址與本地網(wǎng)絡(luò)或者路由表匹配,那么數(shù)據(jù)包將被內(nèi)核轉(zhuǎn)發(fā)到目標地址。
- 接收數(shù)據(jù)包: 當內(nèi)核收到其他網(wǎng)絡(luò)設(shè)備傳入的數(shù)據(jù)包(如網(wǎng)絡(luò)接口收到的數(shù)據(jù)包),如果目標地址是 TUN 設(shè)備的 IP 地址,那么數(shù)據(jù)包將被傳遞給 TUN 設(shè)備驅(qū)動程序。
- 用戶空間讀?。?數(shù)據(jù)包通過 TUN 設(shè)備驅(qū)動程序傳遞到用戶空間的應(yīng)用程序打開的 TUN 設(shè)備文件。應(yīng)用程序可以讀取這些數(shù)據(jù)包并進行處理。
如下是框架流程圖:
linux tun設(shè)備可以用作什么技術(shù)
- VPN(Virtual Private Network): TUN 設(shè)備可用于構(gòu)建 VPN。通過將數(shù)據(jù)包從用戶空間發(fā)送到內(nèi)核空間,再通過TUN 設(shè)備進行加密、隧道封裝和傳輸,可以實現(xiàn)安全的遠程訪問和數(shù)據(jù)傳輸。
- 隧道技術(shù): TUN 設(shè)備也可用于其他隧道技術(shù),如隧道模式下的 IPv6-over-IPv4 和 IPv4-over-IPv6 隧道。它允許不同網(wǎng)絡(luò)之間通過隧道進行通信。
- 加密通信: TUN 設(shè)備可以用于實現(xiàn)端到端的加密通信,保護數(shù)據(jù)的安全性和隱私。
- 虛擬專用網(wǎng)絡(luò): 使用 TUN 設(shè)備,可以創(chuàng)建虛擬專用網(wǎng)絡(luò)(VPN)或虛擬局域網(wǎng)(VLAN),將不同的網(wǎng)絡(luò)或子網(wǎng)連接在一起。
- 網(wǎng)絡(luò)隔離: TUN 設(shè)備可以用于實現(xiàn)網(wǎng)絡(luò)隔離,將不同的應(yīng)用程序或服務(wù)隔離在不同的虛擬網(wǎng)絡(luò)中,增強網(wǎng)絡(luò)的安全性。
- 協(xié)議代理: TUN 設(shè)備還可以用作協(xié)議代理,允許用戶空間應(yīng)用程序處理特定的網(wǎng)絡(luò)協(xié)議,例如將 UDP 或 TCP 流量進行自定義處理。
- 網(wǎng)絡(luò)測試和仿真: 利用 TUN 設(shè)備,可以在用戶空間中模擬網(wǎng)絡(luò)環(huán)境,用于測試和仿真網(wǎng)絡(luò)應(yīng)用程序的性能和穩(wěn)定性。
本文今天要完成什么?
使用虛擬機ubuntu 自帶tun驅(qū)動完成:
- 虛擬驅(qū)動的啟動
- 應(yīng)用層發(fā)包給虛擬網(wǎng)卡驅(qū)動tun并寫入設(shè)備節(jié)點文件,應(yīng)用層完成讀取,加密,寫入設(shè)備節(jié)點并發(fā)送給協(xié)議棧
- 使用tcpdump工具抓取包驗證是否正確發(fā)送與接收
完成框架
- 利用已有的tun設(shè)備驅(qū)動,打開并配置ip,添加靜態(tài)路由表
- 寫兩個應(yīng)用層進程,分別完成數(shù)據(jù)發(fā)送到tun和數(shù)據(jù)接收處理加密并發(fā)回tun驅(qū)動
linux已經(jīng)自帶驅(qū)動:
ubuntu:/dev/net$ ls tun
應(yīng)用層代碼1:
基本邏輯:
- 打開虛擬設(shè)備網(wǎng)卡
- 添加靜態(tài)路由
- 阻塞等待數(shù)據(jù)的到來
- 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頭部長度,單位為字節(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");//啟動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ù)包,通過網(wǎng)絡(luò)接口 "tun0" 進行傳輸,而且這個目標地址被視為一個單獨的主機,而不是一個整個網(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頭部長度 + 應(yīng)用層數(shù)據(jù)長度 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ā)送來的信息 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數(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ù)包頭部,此處就可以進行數(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 頭部長度 + ICMP 數(shù)據(jù)長度 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)核會自動計算校驗和 ip_header->saddr = inet_addr("10.0.0.1"); // 源 IP 地址 ip_header->daddr = inet_addr("14.0.0.2"); // 目標 IP 地址 // 添加 ICMP 數(shù)據(jù) 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; // 校驗和高位字節(jié) icmp_data[3] = 0; // 校驗和低位字節(jié) icmp_data[4] = 0x12; // 標識符高位字節(jié) icmp_data[5] = 0x34; // 標識符低位字節(jié) icmp_data[6] = 0; // 序列號高位字節(jié) icmp_data[7] = 0; // 序列號低位字節(jié) // 計算 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; // 將數(shù)據(jù)包寫入TUN設(shè)備的設(shè)備節(jié)點 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代碼
基本邏輯:
負責給虛擬網(wǎng)卡驅(qū)動發(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è)置目標 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è)置目標 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)該問出這樣的問題,以前的文章都有提及!
驗證
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ā)來的數(shù)據(jù)包(兩個包),讀取完后完成數(shù)據(jù)包的發(fā)送
利用十六進制轉(zhuǎn)字符串驗證應(yīng)用代碼2發(fā)送給應(yīng)用代碼1的數(shù)據(jù)是否發(fā)送成功:
- 第一個包
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é)果:
- 第二個包
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é)果
驗證成功
ps:
- 完成的功能很少,希望這能拋磚引玉
- 代碼解釋不是特別詳細,代碼行中有很多注釋,希望能幫助到你
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
CentOS 7安裝Mysql并設(shè)置開機自啟動的方法
本篇文章主要介紹了CentOS 7安裝Mysql并設(shè)置開機自啟動的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-02-02telnet?Connection?refused端口不通如何處理
本文介紹了telnet命令的基本用途及排查telnet連接拒絕的處理思路,telnet主要用于測試網(wǎng)絡(luò)連接,如遇到連接問題,可能是由于防火墻未開放或目的主機服務(wù)未啟動,文章通過實際例子解釋了telnet命令的作用,并提供了解決網(wǎng)絡(luò)連接問題的方法2024-10-10