前面我们初步认识了什么是tun设备及基础的工作原理与接收文件的设备文件(节点)、虚拟网卡的启动、添加路由表等操作,为什么进一步理解tun设备与协议栈的通信理解,这次我们将应用层控制tun设备发送ping,通过read读取到,后经过手动组装icmp网络包write到协议栈,模拟远程主机回复给我们的 icmp reply,顺便熟悉下icmp 网络包的协议。
网络测试和调试:TUN设备可以用于模拟网络环境,以测试和调试网络应用程序和协议。通过模拟Ping发送和接收,您可以检查网络连接是否正常工作,诊断延迟问题,以及测试网络拓扑。
在ubuntu 设中使用ping指定网卡命令使得tun设备发送ping包,tun read到后,定义组装icmp reply、调用系统调用write到协议栈,通过tcpdump抓包,验证是否模拟成功
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 header
ip_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 needed
ip_header->ttl = 64; // Time to Live
ip_header->protocol = IPPROTO_ICMP;
ip_header->saddr = inet_addr("192.168.100.137"); // Source IP address
ip_header->daddr = inet_addr("10.0.0.1"); // Destination IP address
ip_header->check = 0; // Checksum (set to 0 for now, will be calculated later)
// Create ICMP header for Echo Reply
icmp_header->type = ICMP_ECHOREPLY;
icmp_header->code = 0;
icmp_header->checksum = 0;
icmp_header->un.echo.id = htons(ID); // Customize the ID if needed
icmp_header->un.echo.sequence = htons(seq);// Customize the sequence number if needed
char *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 -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-------------
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包:
正常被协议栈识别
处理icmp网络包的应用层有很多细节问题,在代码中也打印了出来,我们可以利用wireshark的二进制来确定协议与打印出来的二进制之间的关系