虚拟网卡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




  • 0
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用golang实现tun虚拟网卡读写的示例代码: ```go package main import ( "fmt" "net" "os" "golang.org/x/sys/unix" ) func main() { // 打开tun虚拟网卡设备 fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0) if err != nil { fmt.Printf("Error opening tun device: %v\n", err) return } // 设置tun虚拟网卡的名称和类型 ifr := &unix.Ifreq{} copy(ifr.IfrnName[:], []byte("mytun")) ifr.IfruFlags = unix.IFF_TUN | unix.IFF_NO_PI _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(ifr))) if errno != 0 { fmt.Printf("Error setting tun device: %v\n", errno) return } // 获取tun虚拟网卡的IP地址和掩码 addr := &unix.Ifreq{} copy(addr.IfrnName[:], []byte("mytun")) _, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.SIOCGIFADDR, uintptr(unsafe.Pointer(addr))) if errno != 0 { fmt.Printf("Error getting tun device address: %v\n", errno) return } ip := net.IPv4(addr.IfruAddr[0], addr.IfruAddr[1], addr.IfruAddr[2], addr.IfruAddr[3]) mask := &unix.Ifreq{} copy(mask.IfrnName[:], []byte("mytun")) _, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.SIOCGIFNETMASK, uintptr(unsafe.Pointer(mask))) if errno != 0 { fmt.Printf("Error getting tun device netmask: %v\n", errno) return } netmask := net.IPv4(mask.IfruAddr[0], mask.IfruAddr[1], mask.IfruAddr[2], mask.IfruAddr[3]) fmt.Printf("Tun device IP is %s, netmask is %s\n", ip.String(), netmask.String()) // 读取tun虚拟网卡的数据包 buf := make([]byte, 1500) n, err := unix.Read(fd, buf) if err != nil { fmt.Printf("Error reading from tun device: %v\n", err) return } fmt.Printf("Received %d bytes from tun device: %v\n", n, buf[:n]) // 向tun虚拟网卡发送数据包 n, err = unix.Write(fd, []byte("hello world")) if err != nil { fmt.Printf("Error writing to tun device: %v\n", err) return } fmt.Printf("Sent %d bytes to tun device\n", n) } ``` 在以上示例代码中,我们首先使用`unix.Open()`函数打开tun虚拟网卡设备,然后使用`unix.Syscall()`函数调用`ioctl()`系统调用来设置tun虚拟网卡的名称和类型,以及获取tun虚拟网卡的IP地址和掩码。然后,我们使用`unix.Read()`函数从tun虚拟网卡读取数据包,并使用`unix.Write()`函数向tun虚拟网卡发送数据包。 需要注意的是,以上示例代码仅用于演示如何使用golang实现tun虚拟网卡读写,并不能直接运行。完整的实现应该考虑更多的细节和错误处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值