TUN/TAP设备浅析(三) -- TUN/TAP设备的应用

上一篇文章主要讲述了TUN/TAP设备的一些原理,你可能会好奇,TUN/TAP设备究竟有什么用处呢?所以这篇文章,我想用一些实际的例子来回答这个问题。

例子源自陈硕老师的博客,博文中关于TUN/TAP设备的使用非常典型,对原文感兴趣的同学可以查看这里:http://blog.csdn.net/solstice/article/details/6579232

背景:在一台 PC 机上模拟 TCP 客户端程序发起连接请求,同时在该 PC 上创建虚拟网卡 tun0,接收连接请求

并送至 faketcp 应用程序,用于模拟 TCP 服务器端进行响应。

网络的拓扑结构如下:

拓扑结构

具体做法是:在主机 atom 上通过打开 /dev/net/tun 设备来创建一个 tun0 虚拟网卡,然后把这个网卡的地址设为192.168.0.1/24,这样 faketcp 程序就扮演了192.168.0.0/24 这个网段上的所有机器。atom 发给192.168.0.2192.168.0.254IP 数据包都会发给 faketcp 程序,faketcp 程序可以模拟其中任何一个IPatomIP 数据包。

程序分成几步来实现。

第一步:实现 icmp echo 协议,这样就能 pingfaketcp 了:

faketcp.h:

#include <algorithm>  // std::swap#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <arpa/inet.h>  // inet_ntop
#include <net/if.h>struct SocketAddr
{uint32_t saddr, daddr;  // 源地址和目的地址uint16_t sport, dport;  // 源端口和目的端口bool operator==(const SocketAddr& rhs) const{return saddr == rhs.saddr && daddr == rhs.daddr && sport == rhs.sport && dport == rhs.dport;}bool operator<(const SocketAddr& rhs) const{return memcmp(this, &rhs, sizeof(rhs)) < 0;}
};int tun_alloc(char dev[IFNAMSIZ]);
uint16_t in_checksum(const void* buf, int len);void icmp_input(int fd, const void* input, const void* payload, int len);

faketcp.cc:

#include "faketcp.h"#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/if_tun.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h>
#include <sys/ioctl.h>int sethostaddr(const char* dev)
{struct ifreq ifr;bzero(&ifr, sizeof(ifr));strcpy(ifr.ifr_name, dev);struct sockaddr_in addr;bzero(&addr, sizeof addr);addr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.0.1", &addr.sin_addr);//addr.sin_addr.s_addr = htonl(0xc0a80001);bcopy(&addr, &ifr.ifr_addr, sizeof addr);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0)return sockfd;int err = 0;// ifconfig tun0 192.168.0.1if ((err = ioctl(sockfd, SIOCSIFADDR, (void *) &ifr)) < 0){perror("ioctl SIOCSIFADDR");goto done;}// ifup tun0 其实就是启动tun0if ((err = ioctl(sockfd, SIOCGIFFLAGS, (void *) &ifr)) < 0){perror("ioctl SIOCGIFFLAGS");goto done;}ifr.ifr_flags |= IFF_UP;if ((err = ioctl(sockfd, SIOCSIFFLAGS, (void *) &ifr)) < 0){perror("ioctl SIOCSIFFLAGS");goto done;}// ifconfig tun0 192.168.0.1/24 # 配置子网掩码inet_pton(AF_INET, "255.255.255.0", &addr.sin_addr);bcopy(&addr, &ifr.ifr_netmask, sizeof addr);if ((err = ioctl(sockfd, SIOCSIFNETMASK, (void *) &ifr)) < 0){perror("ioctl SIOCSIFNETMASK");goto done;}
done:close(sockfd);return err;
}int tun_alloc(char dev[IFNAMSIZ])
{struct ifreq ifr;int fd, err;if ((fd = open("/dev/net/tun", O_RDWR)) < 0){perror("open");return -1;}bzero(&ifr, sizeof(ifr));ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // tun设备不包含以太网头部,而tap包含,仅此而已if (*dev){strncpy(ifr.ifr_name, dev, IFNAMSIZ); }if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0){perror("ioctl TUNSETIFF");close(fd);return err;}strcpy(dev, ifr.ifr_name);if ((err = sethostaddr(dev)) < 0) // 设定地址等信息return err;return fd;
}uint16_t in_checksum(const void* buf, int len)
{assert(len % 2 == 0);const uint16_t* data = static_cast<const uint16_t*>(buf);int sum = 0;for (int i = 0; i < len; i+=2){sum += *data++;}while (sum >> 16)sum = (sum & 0xFFFF) + (sum >> 16);assert(sum <= 0xFFFF);return ~sum;
}void icmp_input(int fd, const void* input, const void* payload, int len)
{const struct iphdr* iphdr = static_cast<const struct iphdr*>(input); // ip头部const struct icmphdr* icmphdr = static_cast<const struct icmphdr*>(payload); // icmp头部// const int icmphdr_size = sizeof(*icmphdr);const int iphdr_len = iphdr->ihl*4;if (icmphdr->type == ICMP_ECHO){char source[INET_ADDRSTRLEN];char dest[INET_ADDRSTRLEN];inet_ntop(AF_INET, &iphdr->saddr, source, INET_ADDRSTRLEN);inet_ntop(AF_INET, &iphdr->daddr, dest, INET_ADDRSTRLEN);printf("%s > %s: ", source, dest);printf("ICMP echo request, id %d, seq %d, length %d\n",ntohs(icmphdr->un.echo.id),ntohs(icmphdr->un.echo.sequence),len - iphdr_len);union{unsigned char output[ETH_FRAME_LEN]; // 以太网头部struct{struct iphdr iphdr;struct icmphdr icmphdr;} out;};memcpy(output, input, len);out.icmphdr.type = ICMP_ECHOREPLY;out.icmphdr.checksum += ICMP_ECHO; // FIXME: not portablestd::swap(out.iphdr.saddr, out.iphdr.daddr);write(fd, output, len);}
}

icmpecho.cc:

#include "faketcp.h"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <linux/if_ether.h>int main()
{char ifname[IFNAMSIZ] = "tun%d";int fd = tun_alloc(ifname); // tun_alloc函数主要用于开启if (fd < 0){fprintf(stderr, "tunnel interface allocation failed\n");exit(1);}printf("allocted tunnel interface %s\n", ifname);sleep(1);for (;;){union{unsigned char buf[ETH_FRAME_LEN]; // 以太网头部struct iphdr iphdr;   // ip头部};const int iphdr_size = sizeof iphdr; // ip头部默认是20字节int nread = read(fd, buf, sizeof(buf));if (nread < 0){perror("read");close(fd);exit(1);}printf("read %d bytes from tunnel interface %s.\n", nread, ifname);const int iphdr_len = iphdr.ihl*4;if (nread >= iphdr_size&& iphdr.version == 4&& iphdr_len >= iphdr_size&& iphdr_len <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr_len) == 0){const void* payload = buf + iphdr_len;if (iphdr.protocol == IPPROTO_ICMP)  // icmp协议{icmp_input(fd, buf, payload, nread);}}else{printf("bad packet\n");for (int i = 0; i < nread; ++i){if (i % 4 == 0) printf("\n");printf("%02x ", buf[i]);}printf("\n");}}return 0;
}

运行方法,打开3个命令行窗口:

  1. 在第1个窗口运行sudo ./icmpecho,程序显示:
allocted tunnel interface tun0
  1. 在第2个窗口运行:
$ sudo ifconfig tun0 192.168.0.1/24 # 设定ip地址$ sudo tcpdump -i tun0  # 用tcpdump抓取通过接口tun0的数据包
  1. 在第3个窗口运行:
$ ping 192.168.0.2$ ping 192.168.0.3$ ping 192.168.0.234

发现每个192.168.0.XIP 都能 ping 通。

第二步:实现拒接 TCP 连接的功能,即在收到SYN TCP segment的时候发送RST segment

rejectall.cc:

#include "faketcp.h"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_ether.h>void tcp_input(int fd, const void* input, const void* payload, int tot_len)
{const struct iphdr* iphdr = static_cast<const struct iphdr*>(input); // ip头部const struct tcphdr* tcphdr = static_cast<const struct tcphdr*>(payload); // tcp头部const int iphdr_len = iphdr->ihl*4; // ip头部的大小const int tcp_seg_len = tot_len - iphdr_len; // tcp报文的大小const int tcphdr_size = sizeof(*tcphdr);if (tcp_seg_len >= tcphdr_size&& tcp_seg_len >= tcphdr->doff*4){const int tcphdr_len = tcphdr->doff*4;if (tcphdr->syn) // 收到了SYN分节{char source[INET_ADDRSTRLEN];char dest[INET_ADDRSTRLEN];inet_ntop(AF_INET, &iphdr->saddr, source, INET_ADDRSTRLEN); // 将ip转化为可读的字符串inet_ntop(AF_INET, &iphdr->daddr, dest, INET_ADDRSTRLEN);printf("IP %s.%d > %s.%d: ",source, ntohs(tcphdr->source), dest, ntohs(tcphdr->dest));printf("Flags [S], seq %u, win %d, length %d\n",ntohl(tcphdr->seq), // 序列号ntohs(tcphdr->window), // 窗口大小tot_len - iphdr_len - tcphdr_len);union{unsigned char output[ETH_FRAME_LEN];struct{struct iphdr iphdr;struct tcphdr tcphdr;} out;};assert(sizeof(out) == sizeof(struct iphdr) + sizeof(struct tcphdr));int output_len = sizeof(out);bzero(&out, output_len + 4);memcpy(output, input, sizeof(struct iphdr));out.iphdr.tot_len = htons(output_len);std::swap(out.iphdr.saddr, out.iphdr.daddr);out.iphdr.check = 0;out.iphdr.check = in_checksum(output, sizeof(struct iphdr));out.tcphdr.source = tcphdr->dest;  // 源地址和目的地址对调out.tcphdr.dest = tcphdr->source;out.tcphdr.seq = 0;out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1); // 确认序列号out.tcphdr.doff = sizeof(struct tcphdr) / 4;out.tcphdr.ack = 1;out.tcphdr.rst = 1; // 注意这里的RST分节out.tcphdr.window = 0;unsigned char* pseudo = output + output_len;pseudo[0] = 0;pseudo[1] = IPPROTO_TCP;pseudo[2] = 0;pseudo[3] = sizeof(struct tcphdr);out.tcphdr.check = in_checksum(&out.iphdr.saddr, sizeof(struct tcphdr)+12);write(fd, output, output_len);}}
}int main()
{char ifname[IFNAMSIZ] = "tun%d";int fd = tun_alloc(ifname);if (fd < 0){fprintf(stderr, "tunnel interface allocation failed\n");exit(1);}printf("allocted tunnel interface %s\n", ifname);sleep(1);for (;;){union{unsigned char buf[ETH_FRAME_LEN];struct iphdr iphdr;};const int iphdr_size = sizeof iphdr;int nread = read(fd, buf, sizeof(buf));if (nread < 0){perror("read");close(fd);exit(1);}printf("read %d bytes from tunnel interface %s.\n", nread, ifname);const int iphdr_len = iphdr.ihl*4;if (nread >= iphdr_size&& iphdr.version == 4&& iphdr_len >= iphdr_size&& iphdr_len <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr_len) == 0){const void* payload = buf + iphdr_len;if (iphdr.protocol == IPPROTO_ICMP){icmp_input(fd, buf, payload, nread);}else if (iphdr.protocol == IPPROTO_TCP){tcp_input(fd, buf, payload, nread);}}else{printf("bad packet\n");for (int i = 0; i < nread; ++i){if (i % 4 == 0) printf("\n");printf("%02x ", buf[i]);}printf("\n");}}return 0;
}

运行方法,打开3个命令行窗口,头两个窗口的操作与前面相同,运行的faketcp 程序是 ./rejectall

  1. 在第3个窗口运行

$ nc 192.168.0.2  2000$ nc 192.168.0.2  3333$ nc 192.168.0.7  5555

发现向其中任意一个 IP 发起的 TCP 连接都被拒接了。

第三步:实现接受 TCP 连接的功能,即在接收到SYN TCP segment的时候发回 SYN + ACK。这个程序同时处理了连接断开的情况,即在收到FIN segment的时候发回 FIN + ACK。

acceptall.cc:

#include "faketcp.h"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_ether.h>void tcp_input(int fd, const void* input, const void* payload, int tot_len)
{const struct iphdr* iphdr = static_cast<const struct iphdr*>(input);const struct tcphdr* tcphdr = static_cast<const struct tcphdr*>(payload);const int iphdr_len = iphdr->ihl*4;const int tcp_seg_len = tot_len - iphdr_len;const int tcphdr_size = sizeof(*tcphdr);if (tcp_seg_len >= tcphdr_size&& tcp_seg_len >= tcphdr->doff*4){const int tcphdr_len = tcphdr->doff*4;char source[INET_ADDRSTRLEN];char dest[INET_ADDRSTRLEN];inet_ntop(AF_INET, &iphdr->saddr, source, INET_ADDRSTRLEN);inet_ntop(AF_INET, &iphdr->daddr, dest, INET_ADDRSTRLEN);printf("IP %s.%d > %s.%d: ",source, ntohs(tcphdr->source), dest, ntohs(tcphdr->dest));printf("Flags [%c], seq %u, win %d, length %d\n",tcphdr->syn ? 'S' : (tcphdr->fin ? 'F' : '.'),ntohl(tcphdr->seq),ntohs(tcphdr->window),tot_len - iphdr_len - tcphdr_len);union{unsigned char output[ETH_FRAME_LEN];struct{struct iphdr iphdr;struct tcphdr tcphdr;} out;};assert(sizeof(out) == sizeof(struct iphdr) + sizeof(struct tcphdr));int output_len = sizeof(out);bzero(&out, output_len + 4);memcpy(output, input, sizeof(struct iphdr));out.iphdr.tot_len = htons(output_len);std::swap(out.iphdr.saddr, out.iphdr.daddr);out.iphdr.check = 0;out.iphdr.check = in_checksum(output, sizeof(struct iphdr));out.tcphdr.source = tcphdr->dest;out.tcphdr.dest = tcphdr->source;out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1);out.tcphdr.doff = sizeof(struct tcphdr) / 4;out.tcphdr.window = htons(5000);bool response = false;if (tcphdr->syn){out.tcphdr.seq = htonl(123456); // 序列号随机产生out.tcphdr.syn = 1; // SYNout.tcphdr.ack = 1; // ACKresponse = true;}else if (tcphdr->fin) // 对于对方发送的FIN也需要接收是吧!{out.tcphdr.seq = htonl(123457);out.tcphdr.fin = 1;out.tcphdr.ack = 1;response = true;}unsigned char* pseudo = output + output_len;pseudo[0] = 0;pseudo[1] = IPPROTO_TCP;pseudo[2] = 0;pseudo[3] = sizeof(struct tcphdr);out.tcphdr.check = in_checksum(&out.iphdr.saddr, sizeof(struct tcphdr)+12);if (response){write(fd, output, output_len);}}
}int main()
{char ifname[IFNAMSIZ] = "tun%d";int fd = tun_alloc(ifname);if (fd < 0){fprintf(stderr, "tunnel interface allocation failed\n");exit(1);}printf("allocted tunnel interface %s\n", ifname);sleep(1);for (;;){union{unsigned char buf[ETH_FRAME_LEN];struct iphdr iphdr;};const int iphdr_size = sizeof iphdr;int nread = read(fd, buf, sizeof(buf));if (nread < 0){perror("read");close(fd);exit(1);}printf("read %d bytes from tunnel interface %s.\n", nread, ifname);const int iphdr_len = iphdr.ihl*4;if (nread >= iphdr_size&& iphdr.version == 4&& iphdr_len >= iphdr_size&& iphdr_len <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr_len) == 0){const void* payload = buf + iphdr_len;if (iphdr.protocol == IPPROTO_ICMP){icmp_input(fd, buf, payload, nread);}else if (iphdr.protocol == IPPROTO_TCP){tcp_input(fd, buf, payload, nread);}}else{printf("bad packet\n");for (int i = 0; i < nread; ++i){if (i % 4 == 0) printf("\n");printf("%02x ", buf[i]);}printf("\n");}}return 0;
}

运行方法,打开3个命令行窗口,步骤与前面相同,运行的 faketcp 程序是 ./acceptall

这次会发现 nc 能和192.168.0.X中的每一个 IP 每一个 PORT 都能连通。还可以在第4个窗口中运行 netstat -tpn,以确认连接确实建立起来了。

如果在 nc 中输入数据,数据会堆积在操作系统中,表现为netstat 显示的发送队列 (Send-Q)的长度增加。

第四步:在第三步接受TCP连接的基础上,实现接收数据,即在收到包含 payload 数据的 TCP segment时发回ACK

discardall.cc:

#include "faketcp.h"#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_ether.h>void tcp_input(int fd, const void* input, const void* payload, int tot_len)
{const struct iphdr* iphdr = static_cast<const struct iphdr*>(input);const struct tcphdr* tcphdr = static_cast<const struct tcphdr*>(payload);const int iphdr_len = iphdr->ihl*4;const int tcp_seg_len = tot_len - iphdr_len;const int tcphdr_size = sizeof(*tcphdr);if (tcp_seg_len >= tcphdr_size&& tcp_seg_len >= tcphdr->doff*4){const int tcphdr_len = tcphdr->doff*4;const int payload_len = tot_len - iphdr_len - tcphdr_len;char source[INET_ADDRSTRLEN];char dest[INET_ADDRSTRLEN];inet_ntop(AF_INET, &iphdr->saddr, source, INET_ADDRSTRLEN); // 将ip地址变得可读inet_ntop(AF_INET, &iphdr->daddr, dest, INET_ADDRSTRLEN);printf("IP %s.%d > %s.%d: ",source, ntohs(tcphdr->source), dest, ntohs(tcphdr->dest));printf("Flags [%c], seq %u, win %d, length %d\n",tcphdr->syn ? 'S' : (tcphdr->fin ? 'F' : '.'),ntohl(tcphdr->seq),ntohs(tcphdr->window),payload_len);union{unsigned char output[ETH_FRAME_LEN];struct{struct iphdr iphdr;struct tcphdr tcphdr;} out;};assert(sizeof(out) == sizeof(struct iphdr) + sizeof(struct tcphdr));int output_len = sizeof(out);bzero(&out, output_len + 4);memcpy(output, input, sizeof(struct iphdr));out.iphdr.tot_len = htons(output_len);std::swap(out.iphdr.saddr, out.iphdr.daddr);out.iphdr.check = 0;out.iphdr.check = in_checksum(output, sizeof(struct iphdr));out.tcphdr.source = tcphdr->dest; // 目的地址和源地址倒换out.tcphdr.dest = tcphdr->source;out.tcphdr.doff = sizeof(struct tcphdr) / 4;out.tcphdr.window = htons(5000);bool response = false;if (tcphdr->syn){out.tcphdr.seq = htonl(123456);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1);out.tcphdr.syn = 1;out.tcphdr.ack = 1;response = true;}else if (tcphdr->fin){out.tcphdr.seq = htonl(123457);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1);out.tcphdr.fin = 1;out.tcphdr.ack = 1;response = true;}else if (payload_len > 0){out.tcphdr.seq = htonl(123457);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+payload_len); // 确认的序列号out.tcphdr.ack = 1; // ack,不发送数据,仅发送确认号response = true;}unsigned char* pseudo = output + output_len;pseudo[0] = 0;pseudo[1] = IPPROTO_TCP;pseudo[2] = 0;pseudo[3] = sizeof(struct tcphdr);out.tcphdr.check = in_checksum(&out.iphdr.saddr, sizeof(struct tcphdr)+12);if (response){write(fd, output, output_len);}}
}int main()
{char ifname[IFNAMSIZ] = "tun%d";int fd = tun_alloc(ifname);if (fd < 0){fprintf(stderr, "tunnel interface allocation failed\n");exit(1);}printf("allocted tunnel interface %s\n", ifname);sleep(1);for (;;){union{unsigned char buf[ETH_FRAME_LEN];struct iphdr iphdr;};const int iphdr_size = sizeof iphdr;int nread = read(fd, buf, sizeof(buf));if (nread < 0){perror("read");close(fd);exit(1);}printf("read %d bytes from tunnel interface %s.\n", nread, ifname);const int iphdr_len = iphdr.ihl*4;if (nread >= iphdr_size&& iphdr.version == 4&& iphdr_len >= iphdr_size&& iphdr_len <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr_len) == 0){const void* payload = buf + iphdr_len;if (iphdr.protocol == IPPROTO_ICMP){icmp_input(fd, buf, payload, nread);}else if (iphdr.protocol == IPPROTO_TCP){tcp_input(fd, buf, payload, nread);}}else{printf("bad packet\n");for (int i = 0; i < nread; ++i){if (i % 4 == 0) printf("\n");printf("%02x ", buf[i]);}printf("\n");}}return 0;
}

运行方法,打开3个命令行窗口,步骤与前面相同,运行的faketcp程序是./acceptall

这次会发现 nc 能和192.168.0.X中的每一个IP 每一个PORT 都能连通,数据也能发出去。还可以在第4个窗口中运行netstat -tpn,以确认连接确实建立起来了,并且发送队列的长度为0;

这一步已经解决了前面的问题2,扮演任意 TCP 服务端。

第五步:解决前面的问题1,扮演客户端向atom 发起任意多的连接。

connectmany.cc:

#include "faketcp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <linux/if_ether.h>void tcp_input(int fd, const void* input, const void* payload, int tot_len, bool passive)
{const struct iphdr* iphdr = static_cast<const struct iphdr*>(input);const struct tcphdr* tcphdr = static_cast<const struct tcphdr*>(payload);const int iphdr_len = iphdr->ihl*4;const int tcp_seg_len = tot_len - iphdr_len;const int tcphdr_size = sizeof(*tcphdr);if (tcp_seg_len >= tcphdr_size&& tcp_seg_len >= tcphdr->doff*4){const int tcphdr_len = tcphdr->doff*4;const int payload_len = tot_len - iphdr_len - tcphdr_len;char source[INET_ADDRSTRLEN];char dest[INET_ADDRSTRLEN];inet_ntop(AF_INET, &iphdr->saddr, source, INET_ADDRSTRLEN);inet_ntop(AF_INET, &iphdr->daddr, dest, INET_ADDRSTRLEN);printf("IP %s.%d > %s.%d: ",source, ntohs(tcphdr->source), dest, ntohs(tcphdr->dest));printf("Flags [%c], seq %u, win %d, length %d\n",tcphdr->syn ? 'S' : (tcphdr->fin ? 'F' : '.'),ntohl(tcphdr->seq),ntohs(tcphdr->window),payload_len);union{unsigned char output[ETH_FRAME_LEN];struct{struct iphdr iphdr;struct tcphdr tcphdr;} out;};assert(sizeof(out) == sizeof(struct iphdr) + sizeof(struct tcphdr));int output_len = sizeof(out);bzero(&out, output_len + 4);memcpy(output, input, sizeof(struct iphdr));out.iphdr.tot_len = htons(output_len);std::swap(out.iphdr.saddr, out.iphdr.daddr);out.iphdr.check = 0;out.iphdr.check = in_checksum(output, sizeof(struct iphdr));out.tcphdr.source = tcphdr->dest;out.tcphdr.dest = tcphdr->source;out.tcphdr.doff = sizeof(struct tcphdr) / 4;out.tcphdr.window = htons(5000);bool response = false;if (tcphdr->syn) // 对方发起连接,或者对方发送了确认的syn和ack{out.tcphdr.seq = htonl(passive ? 123456 : 123457);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1);if (passive) // passive==true表示被动接收连接,表示对方连过来{out.tcphdr.syn = 1;}// 否则的话,表示自己主动发送的连接,接收到了对方的syn和ack,我们只需要发送一个ack即可out.tcphdr.ack = 1; response = true;}else if (tcphdr->fin) // 对方关闭连接{out.tcphdr.seq = htonl(123457);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1);out.tcphdr.fin = 1;out.tcphdr.ack = 1;response = true;}else if (payload_len > 0){out.tcphdr.seq = htonl(123457);out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+payload_len);out.tcphdr.ack = 1;response = true;}unsigned char* pseudo = output + output_len;pseudo[0] = 0;pseudo[1] = IPPROTO_TCP;pseudo[2] = 0;pseudo[3] = sizeof(struct tcphdr);out.tcphdr.check = in_checksum(&out.iphdr.saddr, sizeof(struct tcphdr)+12);if (response){write(fd, output, output_len);}}
}// connect_one 发起一个tcp连接?
bool connect_one(int fd, uint32_t daddr, int dport, uint32_t saddr, int sport)
{{union{unsigned char output[ETH_FRAME_LEN];struct{struct iphdr iphdr;struct tcphdr tcphdr;} out;};bzero(&out, (sizeof out)+4);out.iphdr.version = IPVERSION;out.iphdr.ihl = sizeof(out.iphdr)/4;out.iphdr.tos = 0;out.iphdr.tot_len = htons(sizeof(out));out.iphdr.id = 55564;out.iphdr.frag_off |= htons(IP_DF);out.iphdr.ttl = IPDEFTTL;out.iphdr.protocol = IPPROTO_TCP;out.iphdr.saddr = saddr;out.iphdr.daddr = daddr;out.iphdr.check = in_checksum(output, sizeof(struct iphdr));out.tcphdr.source = sport; // 端口号out.tcphdr.dest = dport;out.tcphdr.seq = htonl(123456);out.tcphdr.ack_seq = 0;out.tcphdr.doff = sizeof(out.tcphdr)/4;out.tcphdr.syn = 1;       // 主动发起连接out.tcphdr.window = htons(4096);unsigned char* pseudo = output + sizeof out;pseudo[0] = 0;pseudo[1] = IPPROTO_TCP;pseudo[2] = 0;pseudo[3] = sizeof(struct tcphdr);out.tcphdr.check = in_checksum(&out.iphdr.saddr, sizeof(struct tcphdr)+12);write(fd, output, sizeof out); // 发送连接}union{unsigned char buf[ETH_FRAME_LEN];struct iphdr iphdr;};const int iphdr_size = sizeof iphdr;int nread = read(fd, buf, sizeof(buf)); // 接收到回复之后if (nread < 0){perror("read");close(fd);exit(1);}// printf("read %d bytes from tunnel interface %s.\n", nread, ifname);if (nread >= iphdr_size&& iphdr.version == 4&& iphdr.ihl*4 >= iphdr_size&& iphdr.ihl*4 <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr.ihl*4) == 0){const void* payload = buf + iphdr.ihl*4;if (iphdr.protocol == IPPROTO_ICMP){icmp_input(fd, buf, payload, nread);}else if (iphdr.protocol == IPPROTO_TCP) // tcp 报文{tcp_input(fd, buf, payload, nread, false); // 注意到这里的false,表示是自己主动发起连接}}return true;
}void connect_many(int fd, const char* ipstr, int port, int count)
{uint32_t destip;inet_pton(AF_INET, ipstr, &destip); // 连接到目的ipuint32_t srcip = ntohl(destip)+1;int srcport = 1024; // 端口从1024开始for (int i = 0; i < count; ++i){connect_one(fd, destip, htons(port), htonl(srcip), htons(srcport));srcport++; // 源端口在不断加1if (srcport > 0xFFFF){srcport = 1024;srcip++;}}
}void usage()
{
}int main(int argc, char* argv[])
{if (argc < 4){usage();return 0;}char ifname[IFNAMSIZ] = "tun%d";int fd = tun_alloc(ifname);if (fd < 0){fprintf(stderr, "tunnel interface allocation failed\n");exit(1);}const char* ip = argv[1]; // ipint port = atoi(argv[2]); // 端口int count = atoi(argv[3]); // 数量printf("allocted tunnel interface %s\n", ifname);printf("press enter key to start connecting %s:%d\n", ip, port);getchar();connect_many(fd, ip, port, count); // 发起连接for (;;){union{unsigned char buf[ETH_FRAME_LEN];struct iphdr iphdr;};const int iphdr_size = sizeof iphdr;int nread = read(fd, buf, sizeof(buf));if (nread < 0){perror("read");close(fd);exit(1);}printf("read %d bytes from tunnel interface %s.\n", nread, ifname);const int iphdr_len = iphdr.ihl*4;if (nread >= iphdr_size&& iphdr.version == 4&& iphdr_len >= iphdr_size&& iphdr_len <= nread&& iphdr.tot_len == htons(nread)&& in_checksum(buf, iphdr_len) == 0){const void* payload = buf + iphdr_len;if (iphdr.protocol == IPPROTO_ICMP){icmp_input(fd, buf, payload, nread);}else if (iphdr.protocol == IPPROTO_TCP){tcp_input(fd, buf, payload, nread, true); // 注意到这里的true,表示是被动接收连接}}else{printf("bad packet\n");for (int i = 0; i < nread; ++i){if (i % 4 == 0) printf("\n");printf("%02x ", buf[i]);}printf("\n");}}return 0;
}

这一步的运行方法与前面不同,打开4个命令行窗口。

  1. 在第1个窗口运行sudo ./connectmany 192.168.0.1 2007 1000,表示将向192.168.0.1:2007 发起1000个并发连接。程序显示:
allocated tunnel interface tun0
press enter key to start connecting 192.168.0.1  2007
  1. 在第二个窗口运行
$ sudo ifconfig tun0 192.168.0.1/24$ sudo tcpdump -i tun0
  1. 在第3个窗口运行一个能接收并发TCP 连接的服务程序,可以是httpd, 也可以是muduoechodiscard 示例,程序应listen 2007端口。
  2. 回到第1个窗口敲回车,然后在第4个窗口中用netstat -tpn来观察并发连接。

文中代码目录连接:https://github.com/chenshuo/recipes/tree/master/faketcp



作者:Yihulee
链接:https://www.jianshu.com/p/14f9340d940d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439203.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云计算底层技术-虚拟网络设备(Bridge,VLAN)

openstack底层技术-各种虚拟网络设备一(Bridge,VLAN) openstack底层技术-各种虚拟网络设备二(tun/tap,veth) Linux BridgeBridge与netfilterVLAN VLAN设备原理及配置VLAN在openstack中的应用IBM网站上有一篇高质量文章Linux 上的基础网络设备详解。本文会参考文章部分内容&…

【Codeforce-911D】逆序对

题干&#xff1a; Problem - D - Codeforces 解题报告&#xff1a; 不难发现&#xff0c;假设n的倒序排列(n,n-1,...,3,2,1)的逆序对是x&#xff0c;则对n的任意一个逆序对数为y的排列做翻转&#xff0c;新生成的排列的逆序对数位x-y。 因此这题作为奇偶性&#xff0c;其实只…

【LeetCode-面试题 17.09 - medium】第 k 个数

力扣 解题报告&#xff1a; 法一&#xff1a;优先队列做bfs。 法二&#xff1a;看成三个有序链表&#xff0c;做三路归并即可。 注意这里归并&#xff0c;如果多个指针最小值&#xff0c;那么这些指针都需要

10分钟精通SharePoint-验证方式

简介 说到身份验证大家应该不陌生&#xff0c;访问任何平台或系统都需要身份验证&#xff0c;SharePoint也不例外&#xff0c;用户身份验证可根据身份验证提供程序验证用户的身份&#xff0c;身份验证提供程序包含用户平局切可以确认用户正确提交这些平均的目录或数据库。用于…

【18周-钻石】能量供应

思路&#xff1a; 总纲是贪心&#xff0c;按终点排序&#xff0c;然后优先往右边排能量塔。具体实现的时候需要单点更新&#xff0c;区间查询&#xff0c;所以用树状数组set维护一下&#xff0c;就OK了。 代码&#xff1a;&#xff08;不知道为啥有一个样例RE了&#xff09; …

【LeetCode-2421(hard)】好路径的数目

解题报告&#xff1a; 提供两种解法&#xff1a;1、并查集。2、启发式合并。3、树分治&#xff08;点分治&#xff0c;但是校招应该不会问到这个难度吧&#xff0c;&#xff0c;不复习了就&#xff09; 解题思路1&#xff1a; 两种思考方式可以想到这个解法。 1、【由特解到…

分布式事务解决方案框架(LCN)

事务概念 事务特性(ACID) 原子性&#xff08;A&#xff09; 所谓的原子性就是说&#xff0c;在整个事务中的所有操作&#xff0c;要么全部完成&#xff0c;要么全部不做&#xff0c;没有中间状态。对于事务在执行中发生错误&#xff0c;所有的操作都会被回滚&#xff0c;整个…

C#分布式事务解决方案-TransactionScope

引用一下别人的导读&#xff1a; 在实际开发工作中&#xff0c;执行一个事件&#xff0c;然后调用另一接口插入数据&#xff0c;如果处理逻辑出现异常&#xff0c;那么之前插入的数据将成为垃圾数据&#xff0c; 我们所希望的是能够在整个这个方法定义为一个事务&#xff0c;Tr…

【19周-星耀】FASTER!FASTER!FASTER!

题目&#xff1a; 解题报告&#xff1a; 看数据范围&#xff0c;应该是个n^3的dp。 但是刚开始觉得二维就可以写。 但是G了&#xff0c;因为无法根据定义的状态判断从[i]到[i1]的速度是多少。我这直接默认是d[i]了&#xff0c;但是显然不一定是。 然后感觉欸&#xff0c;我是…

[转载]使用消息队列实现分布式事务-公认较为理想的分布式事务解决方案

前阵子从支付宝转账1万块钱到余额宝&#xff0c;这是日常生活的一件普通小事&#xff0c;但作为互联网研发人员的职业病&#xff0c;我就思考支付宝扣除1万之后&#xff0c;如果系统挂掉怎么办&#xff0c;这时余额宝账户并没有增加1万&#xff0c;数据就会出现不一致状况了。 …

【LeetCode-6195. hard】对字母串可执行的最大删除数

题干:力扣 解题报告&#xff1a; 刚开始觉得直接贪心选有短则短&#xff0c;但是发现不行&#xff0c;不能贪心有短的可以操作的则选短的。 三个方式想到倒着递推。一是直接记住这个特例&#xff0c;正推还倒推不能互相转换的特例。 二是因为dp[i]代表前i个的最大次数&#x…

【洛谷 P2034】选择数字(单调队列优化dp)

题目链接&#xff1a;选择数字 - 洛谷 解题报告&#xff1a; 思路1&#xff1a; 参考代码&#xff1a; #include<cstdio> #include<iostream> #include<deque> using namespace std; const long long Maxn10000020,inf0x3f3f3f3f; long long a[Maxn],s[Max…

了解Entity Framework中事务处理

Entity Framework 6以前&#xff0c;框架本身并没有提供显式的事务处理方案&#xff0c;在EF6中提供了事务处理的API。 所有版本的EF&#xff0c;只要你调用SaveChanges方法进行插入、修改或删除&#xff0c;EF框架会自动将该操作进行事务包装。这种方法无法对事务进行显式的控…

【LeetCode-769. medium】最多能完成排序的块

力扣 解题报告&#xff1a; 注意这种【根据一个要求&#xff0c;将数组分成多个区间】类模型的问题&#xff08;比如汽车加油站、加法表达式求和&#xff09;&#xff0c;套路就这三步&#xff1a; 1、初始化 2、for循环或者while&#xff0c;里面三步 2.1 更新 2.2 如果符合…

SQL Server Profiler工具

一、SQL Profiler工具简介 SQL Profiler是一个图形界面和一组系统存储过程&#xff0c;其作用如下&#xff1a; 图形化监视SQL Server查询&#xff1b;在后台收集查询信息&#xff1b;分析性能&#xff1b;诊断像死锁之类的问题&#xff1b;调试T-SQL语句&#xff1b;模拟重放…

【LeetCode856. medium】括号的分数

856. 括号的分数 解题报告&#xff1a; 括号问题考虑栈。 本来是想用栈做&#xff0c;但是发现其实得分是相加还是乘2&#xff0c;和括号的层级有关系。 比如第三层的括号之间就是相加&#xff0c;第二层的括号就是把第三层的分数和乘2。记得算完第二层的分之后&#xff0c;…

SharePoint Permission中6个表的关联关系**

1、UserInfo&#xff1a;定义所有user&#xff0c;对应的类为SPUser&#xff0c;以site collection为单位&#xff0c;tp_ID字段标识user的id值。 2、Groups&#xff1a;定义所有组&#xff0c;对应的类为SPGroup&#xff0c;以site collection为单位&#xff0c;ID字段表示组…

【LeetCode2434. medium】使用机器人打印字典序最小的字符串

2434. 使用机器人打印字典序最小的字符串 解题报告&#xff1a; 首先进行题意转换&#xff0c;其实上述两种操作描述的就是一个栈。 为什么要进行题意转换呢&#xff1f;因为你看题目说有两种操作&#xff0c;但是却不问最少的操作数&#xff0c;说明说不定这两种操作可以合并…

天猫精灵方糖拆解报告和芯片详解

折腾&#xff1a; 【记录】天猫精灵方糖拆解过程 后&#xff0c;下面详细整理关于芯片的信息。 总体截图&#xff1a; 各个单元&#xff1a; MEDIATEK ARM MT8516AAAA 1812-BZASH BET02027 ACMQPQ8K 【整理】SoC CPU MEDIATEK MT8516详解 SAMSUNG 810 K9F1G08U0F SC…

【LeetCode 904-medium】水果成篮

1、 这题做法多种多样&#xff0c;看能不能找到五种做法。 其实就是这道题&#xff1a; 力扣 力扣题我的AC代码&#xff1a;&#xff08;法1&#xff1a;边遍历边维护答案&#xff0c;如果遇到f[i]是第三个数就抛弃 除了f[i-1]的另一个数&#xff09; class Solution { pu…