前言
前面我们初步认识了什么是tun设备及基础的工作原理与接收文件的设备文件(节点)、虚拟网卡的启动、添加路由表等操作,为什么进一步理解tun设备与协议栈的通信理解,这次我们将应用层控制tun设备发送ping,通过read读取到,后经过手动组装icmp网络包write到协议栈,模拟远程主机回复给我们的 icmp reply,顺便熟悉下icmp 网络包的协议。
模拟ping发送与接收有什么作用
网络测试和调试:TUN设备可以用于模拟网络环境,以测试和调试网络应用程序和协议。通过模拟Ping发送和接收,您可以检查网络连接是否正常工作,诊断延迟问题,以及测试网络拓扑。
完成事项
在ubuntu 设中使用ping指定网卡命令使得tun设备发送ping包,tun read到后,定义组装icmp reply、调用系统调用write到协议栈,通过tcpdump抓包,验证是否模拟成功
作用
- 提高tun 设备的发送、接收与read与write的理解。
- 了解ping的协议,ip header、icmp header、icmp date的定义,赋值,校验和等,协议字段的不同,导致的无法被识别为目标request 的reply,为的ping外网做技术储备。
操作列表
- 用于读取tun设备节点并处理数据的应用层程序,再模拟发送icmp reply包
- 通过ping命令使得tun设备发送icmp包
- tcpdump工具抓取流经tun网卡的包
验证与代码流程
- 用于读取tun设备节点并处理数据的应用层程序,再模拟发送icmp reply包
#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>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/ip_icmp.h>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");printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name);return fd;
}int tun_fd, nread;char buffer[1500];char buffer7[1000];
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");
uint16_t calculate_checksum(uint16_t *data, int length) {uint32_t sum = 0;while (length > 1) {sum += *data++;length -= 2;}if (length > 0){sum += *((uint8_t *)data);}while (sum >> 16) {sum = (sum & 0xFFFF) + (sum >> 16);}return (uint16_t)(~sum);
}memset(buffer7,0,sizeof(buffer7));// 检查数据包是否为 ICMP Echo 请求struct iphdr *ip_header = (struct iphdr *)buffer7;if( buffer[20]==0x08){struct icmphdr *icmp_header = (struct icmphdr *)(buffer7 + sizeof(struct iphdr));printf("--------是icmp request包-----------\n");printf("--------ID:%02X,%02X,seq num:%02X,%02X-----------\n",buffer[24],buffer[25],buffer[26],buffer[27]);unsigned char id = buffer[24];unsigned char se = buffer[26];unsigned char id1 = buffer[25];unsigned char se1 = buffer[27];unsigned short ID = ((unsigned short)id << 8 )|id1;unsigned short seq = ((unsigned short)se << 8 )|se1;printf("----------ID:%04X,seq:%04X--------------\n",ID,seq);// Manually construct IP headerip_header->version = 4;ip_header->ihl = 5; // Header length in 32-bit words (5 for no options)ip_header->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr) + 56);ip_header->id = htons(12345); // Customize the ID if neededip_header->ttl = 64; // Time to Liveip_header->protocol = IPPROTO_ICMP;ip_header->saddr = inet_addr("192.168.100.137"); // Source IP addressip_header->daddr = inet_addr("10.0.0.1"); // Destination IP addressip_header->check = 0; // Checksum (set to 0 for now, will be calculated later)// Create ICMP header for Echo Replyicmp_header->type = ICMP_ECHOREPLY;icmp_header->code = 0;icmp_header->checksum = 0;icmp_header->un.echo.id = htons(ID); // Customize the ID if neededicmp_header->un.echo.sequence = htons(seq);// Customize the sequence number if neededchar *data = buffer7 + sizeof(struct iphdr) + sizeof(struct icmphdr);memcpy(data,&(buffer[28]),8);memcpy(data+8,&(buffer[36]),48);icmp_header->checksum = calculate_checksum((uint16_t *)icmp_header, sizeof(struct icmphdr) + 56);printf("------------sizeof(struct icmphdr) + sizeof(reply_data):%d--------------------\n",(sizeof(struct icmphdr) + 56));// 发送 ICMP Echo Reply 数据包到 TUN 设备ssize_t bytes_written=write(tun_fd, buffer7, (sizeof(struct icmphdr) + 56+sizeof(struct iphdr)));if (bytes_written < 0) {perror("Failed to write to TUN device");break;}printf("---------ICMP Echo Request sent :%d Bytes-------------\n",bytes_written);}
}close(tun_fd);return 0;
}
编译并执行结果:
编译:
$ gcc net_device_user1.c -o net_device_user1
net_device_user1.c: In function ‘main’:
net_device_user1.c:143:28: warning: format ‘%zd’ expects argument of type ‘signed size_t’, but argument 2 has type ‘int’ [-Wformat=]printf("Read %zd bytes from tun/tap device\n", nread);~~^%d
net_device_user1.c:401:82: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]printf("------------sizeof(struct icmphdr) + sizeof(reply_data):%d--------------------\n",(sizeof(struct icmphdr) + 56));~^%ld
net_device_user1.c:409:59: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘ssize_t {aka long int}’ [-Wformat=]printf("---------ICMP Echo Request sent :%d Bytes-------------\n",bytes_written);
执行结果:
$ sudo ./net_device_user1
SIOCADDRT: File exists
Open tun/tap device: tun0 for reading...
- 通过ping命令使得tun设备发送icmp包
$ ping -I tun0 192.168.100.137
PING 192.168.100.137 (192.168.100.137) from 10.0.0.1 tun0: 56(84) bytes of data.
ping后,应用程序会有新的日志log
$ sudo ./net_device_user1
SIOCADDRT: File exists
Open tun/tap device: tun0 for reading...
Read 84 bytes from tun/tap device
45 00 00 54 2B FFFFFF8B 40 00 40 01 FFFFFFDF FFFFFFEB 0A 00 00 01
FFFFFFC0 FFFFFFA8 64 FFFFFF89 08 00 21 FFFFFF9D 53 46 00 01 FFFFFFC2 2D FFFFFFF8 64
00 00 00 00 FFFFFFFC FFFFFFB5 0D 00 00 00 00 00 10 11 12 13
14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23
24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33
34 35 36 37
--------是icmp request包-----------
--------ID:53,46,seq num:00,01-----------
----------ID:5346,seq:0001--------------
------------sizeof(struct icmphdr) + sizeof(reply_data):64--------------------
---------ICMP Echo Request sent :84 Bytes-------------
- tcpdump工具抓取流经tun网卡的包
sudo tcpdump -i tun0 -w tcpdump.pcap
tcpdump: listening on tun0, link-type RAW (Raw IP), capture size 262144 bytes
tcpdump: pcap_loop: The interface went down
16 packets captured
18 packets received by filter
0 packets dropped by kernel
打开tcpdump包:
正常被协议栈识别
PS
处理icmp网络包的应用层有很多细节问题,在代码中也打印了出来,我们可以利用wireshark的二进制来确定协议与打印出来的二进制之间的关系