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.1
  if ((err = ioctl(sockfd, SIOCSIFADDR, (void *) &ifr)) < 0)
  {
    perror("ioctl SIOCSIFADDR");
    goto done;
  }
  // ifup tun0 其实就是启动tun0
  if ((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 portable
    std::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; // SYN
      out.tcphdr.ack = 1; // ACK
      response = 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); // 连接到目的ip

    uint32_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++; // 源端口在不断加1
        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]; // ip
    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); // 注意到这里的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • 1、第八章 Samba服务器2、第八章 NFS服务器3、第十章 Linux下DNS服务器配站点,域名解析概念命令:...
    哈熝少主阅读 3,633评论 0 10
  • 个人认为,Goodboy1881先生的TCP /IP 协议详解学习博客系列博客是一部非常精彩的学习笔记,这虽然只是...
    贰零壹柒_fc10阅读 5,019评论 0 8
  • 1.这篇文章不是本人原创的,只是个人为了对这部分知识做一个整理和系统的输出而编辑成的,在此郑重地向本文所引用文章的...
    SOMCENT阅读 12,977评论 6 174
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,903评论 1 13