TUN模块用例–模拟ICMP_ECHOREPLY包(ping反馈)
使用TUN模块模拟ICMP_ECHOREPLY包,也就是ping命令的回包功能。
由于TUN设备虚拟三层网络设备,因此在read()读取TUN数据时获得的时带有IP头部的数据,write()写入回包的数据要从新计算IP校验码和ICMP校验码。
IP头部校验
//IP头部校验
uint16_t ip_checksum(uint16_t *buf, int size)
{
uint32_t sum = 0;
while(size >1)
{
sum += *(buf++);
size -= 2;
}
while(sum >> 16)
{
sum = (sum >> 16) + (sum & 0xffff);
}
return ~sum;
}
ICMP头部校验码
//ICMP头部校验
uint16_t icmp_checksum(unsigned char *buffer, int len)
{
uint32_t checksum = 0;
uint16_t *data = (u_int16_t *)buffer;
while(len > 1)
{
checksum += *data++;
len -= 2;
}
if(1 == len)
{
u_int16_t tmp = *data;
tmp &= 0xff00;
checksum += tmp;
}
checksum = (checksum >> 16) + (checksum & 0x0000ffff);
checksum += checksum >> 16;
checksum = ~checksum;
return checksum;
}
TUN设备创建并设置属性
int initTun(char *aDev, uint32_t aLocalAddr, uint32_t aNetMask)
{
int tunFd;
struct ifreq ifr;
struct sockaddr_in si;
int err = 0, sockSetip = 0;
/* 打开tun设备 */
if ((tunFd = open("/dev/net/tun", O_RDWR)) < 0)
{
printf("Open tun file error!\n");
return tunFd;
}
/* 设置tun设备名称,默认设备名格式为'tun+数字' */
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TUN | IFF_NO_PI ;//| IFF_NO_PI 告诉内核不需要提供报文信息,即不需要其它字节,否则会提供额外四字节(2字节标识,2字节协议)
strncpy(ifr.ifr_ifrn.ifrn_name, aDev, IFNAMSIZ);
if ((err = ioctl(tunFd, TUNSETIFF, &ifr)) < 0)
{
printf("ioctl TUNSETIFF ERR\n");
close(tunFd);
return err;
}
/* 设置虚拟网卡属性,需要借用socket+ioctl,打开一个socket */
sockSetip = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
if (sockSetip < 0)
{
printf("Failed to open sockSetip\n");
}
/* 获取socket中的ifr属性 */
if ((err = ioctl(sockSetip, SIOCGIFFLAGS, &ifr)) < 0)
{
printf("ioctl SIOCGIFFLAGS ERR\n");
close(sockSetip);
close(tunFd);
return err;
}
/* 虚拟网卡模式设置 */
ifr.ifr_flags |= IFF_UP | IFF_RUNNING | IFF_PROMISC;
if ((err = ioctl(sockSetip, SIOCSIFFLAGS, &ifr)) < 0)
{
printf("ioctl SIOCSIFFLAGS ERR\n");
close(sockSetip);
close(tunFd);
return err;
}
/* 设置虚拟网卡地址 */
memset(&si, 0, sizeof(struct sockaddr_in));
si.sin_family = AF_INET;
si.sin_addr.s_addr = aLocalAddr;
memcpy(&ifr.ifr_addr, &si, sizeof(struct sockaddr_in));
if ((err = ioctl(sockSetip, SIOCSIFADDR, &ifr)) < 0)
{
printf("Failed to set ip address\n");
close(sockSetip);
close(tunFd);
return err;
}
si.sin_addr.s_addr = aNetMask;
memcpy(&ifr.ifr_netmask, &si, sizeof(struct sockaddr_in));
if ((err = ioctl(sockSetip, SIOCSIFNETMASK, &ifr)) < 0)
{
printf("Failed to set netmask\n");
close(sockSetip);
close(tunFd);
return err;
}
close(sockSetip);
return tunFd;
}
主函数
int main()
{
char readBuf[WBUFF_SIZE];
memset(readBuf, 0, WBUFF_SIZE);
int tFd, ret;
char mDev[IFNAMSIZ] = "TEST0";
tFd = initTun(mDev, inet_addr("192.168.10.1"), inet_addr("255.255.255.252"));
if (tFd < 0)
{
printf("Failed to open tun\n");
return -1;
}
while (1)
{
ret = read(tFd, readBuf, WBUFF_SIZE);
if (ret > 48)
{
printf("read %d bytes\n", ret);
printfHex(readBuf, ret);
/* 修改IP头 */
struct iphdr *iph = (struct iphdr *)readBuf;
unsigned int iptemp;
iptemp = iph->daddr;
iph->daddr = iph->saddr;
iph->saddr = iptemp;
uint32_t iphSize = iph->ihl * 4;
iph->check = 0;
iph->check = ip_checksum((uint16_t *)iph, iphSize);
/* 修改ICMP头 */
struct icmphdr *icmph = (struct icmphdr *)(readBuf + iphSize);
icmph->type = ICMP_ECHOREPLY;
icmph->code = 0x00;
icmph->checksum = 0x0000;
icmph->checksum = icmp_checksum((unsigned char *)icmph, ret - iphSize);
/* 返回Ping包 */
ret = write(tFd, readBuf, ret);
if (ret > 0)
{
printf("write %d bytes\n", ret);
printfHex(readBuf, ret);
}
}
}
close(tFd);
return 0;
}
效果
补充引用的头文件
#include <sys/ioctl.h>
#include <linux/ip.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/if_ether.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/icmp.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>