linux tap设备是个二层网络设备,收发的数据包包含mac头的信息,除了做不可描述方面的事情外,也可以用于模拟嵌入式网络设备的收发情况,用于调试网络功能。
源代码在gitee
https://gitee.com/hl1200/tap2http.git
本机安装ubuntu服务器版本,已经带有tun的驱动。
创建一个tap设备:
fd = open("/dev/net/tun", O_RDWR);
printf("fd is %d \n", fd);
if(fd == -1) {
perror("tapdev: tapdev_init: open");
exit(1);
}
设置tap设备
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
if (ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) {
perror(cmdbuf);
exit(1);
}
设置tap0的ip
snprintf(cmdbuf, sizeof(cmdbuf), "ifconfig tap0 inet %d.%d.%d.%d",
192, 168, 30, 1);
system(cmdbuf);
这样就创建了一个tap0网卡。在主机上发送到192.168.30.2/255的所有数据包都在fd中接收到。
当客户端访问服务器时,客户端会发数据包,数据包以mac头开始
struct ethernet_hdr {
u8 et_dest[6]; /* Destination node */
u8 et_src[6]; /* Source node */
u16 et_protlen; /* Protocol or length */};
在开始时,客户端不知道目标的mac地址,所以要发送arp广播包,向对应ip的服务器获取mac地址
struct arp_hdr {
u16 ar_hrd; /* Format of hardware address */
# define ARP_ETHER 1 /* Ethernet hardware address */
u16 ar_pro; /* Format of protocol address */
u8 ar_hln; /* Length of hardware address */
# define ARP_HLEN 6
u8 ar_pln; /* Length of protocol address */
# define ARP_PLEN 4
u16 ar_op; /* Operation */
# define ARPOP_REQUEST 1 /* Request to resolve address */
# define ARPOP_REPLY 2 /* Response to previous request */
# define RARPOP_REQUEST 3 /* Request to resolve address */
# define RARPOP_REPLY 4 /* Response to previous request */
/*
* The remaining fields are variable in size, according to
* the sizes above, and are defined as appropriate for
* specific hardware/protocol combinations.
*/
u8 ar_data[0];
#define ar_sha ar_data[0]
#define ar_spa ar_data[ARP_HLEN]
#define ar_tha ar_data[ARP_HLEN + ARP_PLEN]
#define ar_tpa ar_data[ARP_HLEN + ARP_PLEN + ARP_HLEN]
#if 0
u8 ar_sha[]; /* Sender hardware address */
u8 ar_spa[]; /* Sender protocol address */
u8 ar_tha[]; /* Target hardware address */
u8 ar_tpa[]; /* Target protocol address */
#endif /* 0 */
};
收到arp报文后,把op改为reply,源地址目标地址相应填上,发回去就行了。
取得服务器mac后,客户端就可以发送ip包了。
这是一个接收的ip包
mac头
32 0c 36 7c 00 72 目标mac地址
7a a5 bf 66 20 5d 源mac地址
08 00 协议类型,0x08 00是ip协议
ip头
45 4是ipv4,5是5*32bit,20byte长
00 服务类型,一般全为0,不用理会
00 3c 包长度,包括包头
60 f4 标识符,拆包用
40 00 标记片偏移,拆包用
40 生存时间
06 协议类型,06是tcp协议
1c 73 首部校验和
c0 a8 1e 01 源ip
c0 a8 1e 03 目标ip
20字节ip头到此为止
tcp数据
d9 f2 源端口
00 50 目标端口,80端口是http
e8 e9 3a a6 数据序列号
00 00 00 00 数据确认序列号,这个是第一次握手,没有。
a0 02 前4位1011说明数据离包头偏移40byte,后六位000010,SYN位1,是个同步包。
fa f0 窗口
9e fb 校验和
00 00 紧急指针
以下都是tcp的选项,为了简化,可以直接不要,这样
mac头就6+6+2=14 byte
ip头 20 byte
tcp头 20byte
最短的tcp数据包就 54byte
02 04 05 b4 mss大小
04 02 SACK permitted
08 0a 9e 22 54 c9 00 00 00 00 TSOPT
01 03 03 No_Operation
07 填充
如果用结构体来表示整个tcp包,注意一下位对齐,用了usigned int类型后,mac头14byte,下面可能会有空byte,最后整个结构体发出去,接收就可能错位。
接着就是tcp的三次握手,这里只做服务端,基本就是
- C->S,flag = S,client_sqn = a,client_ack =0
- S->C, flag = S|A server_sqn = b, server_ack = a+1
- C->S, flag = A client_sqn = a+1, client_ack = b+1
建立起tcp连接以后,可以传输http数据 - 客户端先发get报文,
C->S, flag = P|A client_sqn = a+1, client = b+1, http_len = n - 服务端发ack表明收到,然虎发响应报文。