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.2 ~ 192.168.0.254的 IP 數據包都會發給 faketcp 程序,faketcp 程序可以模擬其中任何一個IP 給atom 發 IP 數據包。
程序分成幾步來實現。
第一步:實現 icmp echo 協議,這樣就能 ping 通 faketcp 了:
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個命令行窗口:
發現每個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); // 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。
發現向其中任意一個 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個命令行窗口。
文中代碼目錄連接:https://github.com/chenshuo/recipes/tree/master/faketcp
作者:Yihulee
鏈接:https://www.jianshu.com/p/14f9340d940d
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的TUN/TAP设备浅析(三) -- TUN/TAP设备的应用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 买房还可以再等等?房贷利率连续下调7个月
- 下一篇: 怎么申请招行经典白 招行经典白金申请条件