虛擬網絡卡TUN/TAP裝置使用例項
文章出處: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
#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