虚拟网卡TUN/TAP设备使用实例
2013-08-21 16:45
330 查看
文章出处:http://blog.csdn.net/solstice/article/details/6579232
转载渊源:这篇文章源自陈硕老师的博客,原文讨论的主题是在绕开操作系统协议栈的情况下,对tcp并发连接数的支持情况;因为其中对TUN / TAP设备的使用非常典型,而且讲解清晰,所以特部分转载过来作为资料留存;
IBM developerworks上有一篇文章对tun / tap设备进行了详细的介绍,也是一篇非常好的参考资料,另附连接如下:http://www.ibm.com/developerworks/cn/linux/l-tuntap/
背景:在一台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.2 ~ 192.168.0.254的IP packet都会发给faketcp程序,faketcp程序可以模拟其中任何一个IP给atom发IP packet;
程序分成几步来实现。
第一步:实现icmp echo协议,这样就能ping通faketcp了;
icmpecho.cc
faketcp.cc
运行方法,打开3个命令行窗口:
1. 在第1个窗口运行 sudo ./icmpecho,程序显示:
allocted tunnel interface tun0
2. 在第2个窗口运行:
$ sudo ifconfig tun0 192.168.0.1/24
$ sudo tcpdump -i tun0
3. 在第3个窗口运行:
$ ping 192.168.0.2
$ ping 192.168.0.3
$ ping 192.168.0.234
发现每个192.168.0.X 的IP都能ping通;
第二步:实现拒接TCP连接的功能,即在收到SYN TCP segment的时候发送RST segment。
rejectall.cc
运行方法,打开3个命令行窗口,头两个窗口的操作与前面相同,运行的faketcp程序是 ./rejectall
3. 在第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
运行方法,打开3个命令行窗口,步骤与前面相同,运行的faketcp程序是 ./acceptall。这次会发现 nc 能和192.168.0.X中的每一个IP 每一个PORT都能连通。还可以在第4个窗口中运行 netstat -tpn,以确认连接确实建立起来了。如果在nc中输入数据,数据会堆积在操作系统中,表现为netstat 显示的发送队列 (Send-Q)的长度增加;
第四步:在第三步接受TCP连接的基础上,实现接收数据,即在收到包含 payload 数据的 TCP segment时发回ACK。
discardall.cc
运行方法,打开3个命令行窗口,步骤与前面相同,运行的faketcp程序是./acceptall。这次会发现nc 能和192.168.0.X中的每一个IP 每一个PORT都能连通,数据也能发出去。还可以在第4个窗口中运行netstat -tpn,以确认连接确实建立起来了,并且发送队列的长度为0;
这一步已经解决了前面的问题2,扮演任意TCP服务端。
第五步:解决前面的问题1,扮演客户端向atom发起任意多的连接。
connectmany.cc
这一步的运行方法与前面不同,打开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
2. 在第二个窗口运行
$ sudo ifconfig tun0 192.168.0.1/24
$ sudo tcpdump -i tun0
3. 在第3个窗口运行一个能接收并发TCP连接的服务程序,可以是httpd, 也可以是muduo的echo 或discard示例,程序应listen 2007端口。
4. 回到第1个窗口敲回车,然后在第4个窗口中用netstat -tpn来观察并发连接。
文中代码目录连接:https://github.com/chenshuo/recipes/tree/master/faketcp
转载渊源:这篇文章源自陈硕老师的博客,原文讨论的主题是在绕开操作系统协议栈的情况下,对tcp并发连接数的支持情况;因为其中对TUN / TAP设备的使用非常典型,而且讲解清晰,所以特部分转载过来作为资料留存;
IBM developerworks上有一篇文章对tun / tap设备进行了详细的介绍,也是一篇非常好的参考资料,另附连接如下:http://www.ibm.com/developerworks/cn/linux/l-tuntap/
背景:在一台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.2 ~ 192.168.0.254的IP packet都会发给faketcp程序,faketcp程序可以模拟其中任何一个IP给atom发IP packet;
程序分成几步来实现。
第一步:实现icmp echo协议,这样就能ping通faketcp了;
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); 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 { 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; }
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 tun_alloc(char *dev) { struct ifreq ifr; int fd, err; if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { perror("open"); return -1; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; if (*dev) { strncpy(ifr.ifr_name, dev, IFNAMSIZ); } if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { perror("ioctl"); close(fd); return err; } strcpy(dev, ifr.ifr_name); 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); const struct icmphdr* icmphdr = static_cast<const struct icmphdr*>(payload); // 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 portable std::swap(out.iphdr.saddr, out.iphdr.daddr); write(fd, output, len); } }
运行方法,打开3个命令行窗口:
1. 在第1个窗口运行 sudo ./icmpecho,程序显示:
allocted tunnel interface tun0
2. 在第2个窗口运行:
$ sudo ifconfig tun0 192.168.0.1/24
$ sudo tcpdump -i tun0
3. 在第3个窗口运行:
$ ping 192.168.0.2
$ ping 192.168.0.3
$ ping 192.168.0.234
发现每个192.168.0.X 的IP都能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); 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; if (tcphdr->syn) { 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 [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; 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
3. 在第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; out.tcphdr.ack = 1; response = true; } else if (tcphdr->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); 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; 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) { out.tcphdr.seq = htonl(passive ? 123456 : 123457); out.tcphdr.ack_seq = htonl(ntohl(tcphdr->seq)+1); if (passive) { 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; 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); } } } 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_input(fd, buf, payload, nread, false); } } return true; } void connect_many(int fd, const char* ipstr, int port, int count) { uint32_t destip; inet_pton(AF_INET, ipstr, &destip); uint32_t srcip = ntohl(destip)+1; int srcport = 1024; for (int i = 0; i < count; ++i) { connect_one(fd, destip, htons(port), htonl(srcip), htons(srcport)); srcport++; if (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]; int 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); } } 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
2. 在第二个窗口运行
$ sudo ifconfig tun0 192.168.0.1/24
$ sudo tcpdump -i tun0
3. 在第3个窗口运行一个能接收并发TCP连接的服务程序,可以是httpd, 也可以是muduo的echo 或discard示例,程序应listen 2007端口。
4. 回到第1个窗口敲回车,然后在第4个窗口中用netstat -tpn来观察并发连接。
文中代码目录连接:https://github.com/chenshuo/recipes/tree/master/faketcp