DIY TCP/IP TCP模块的实现2

上一篇:DIY TCP/IP TCP模块的实现1
9.4 TCP连接的初始化
TCP是面向连接的协议,面向连接即有状态的协议。通信双方通过三步握手,达到连接建立的状态,然后基于这种状态交互TCP数据帧。断开连接是通信双方通过四步握手达到断开连接的状态。9.1节提到<源IP地址,源端口,目标IP地址,目标端口>四元组可以标识通信双方建立的TCP连接。所以定义TCP连接的数据结构应包括四元组信息,另外为保存下节将实现的TCP握手过程中得到的序列号,以及通信双方的TCP options解析出的window size,SACK等信息,将这些信息都加入TCP连接数据结构中。
TCP连接数据结构是TCP模块的核心数据结构,TCP模块的代码实现,连接的建立和维护,TCP连接状态机,三步握手,四步握手,滑动窗口的实现,都是基于本节定义的TCP连接数据结构实现。本节介绍TCP连接数据结构的定义,初始化,为DIY TCP/IP的初始化新增运行时参数,来指定TCP的端口号,用于初始化一条TCP连接。
测试方法与9.4节基本相同,不同点是主机A上运行的DIY TCP/IP除了指定虚拟IP地址之外,还要指定TCP的端口号,记为PX。主机C上运行的iperf TCP client端的参数改为iperf -c <虚拟IP地址> -i 1 -t 43200 -p PX。测试目标是TCP模块能够正确接收主机C发出的TCP SYN数据帧,根据目标端口号找到对应的TCP连接,并打印输出TCP SYN的数据帧头部信息。
TCP连接数据结构是TCP模块的内部数据结构,仅在TCP模块内部使用,定义在tcp.c文件中。

  typedef struct _tcp_conn {
          /* remote port */
          unsigned short r_port;
          /* local port */
          unsigned short l_port;
          /* remote ip */
          unsigned char r_ip[4];
          /* local ip */
          unsigned char l_ip[4];
          /* remote max segment size */
          unsigned short r_mss;
          /* local mss */
          unsigned short l_mss;
          /* remote window size */
          unsigned short r_wnd_sz;
          /* local receive window size */
          unsigned short l_wnd_sz;
          /* remote sack */
          unsigned char r_sack;
          /* remote window scale */
          unsigned char r_shift_cn;
          /* local window scale */
      	  unsigned char l_shift_cn;
          /* remote tcp header flag */
          unsigned short r_hflag;
          /* local tcp header flag */
          unsigned short l_hflag;
          /* time echo reply */
          unsigned int time_echo;
          /* rx: ack sequence number */
          unsigned int ack_seq;
          /* tx: next sequence number */
          unsigned int send_seq;
          /* connection state */
          tcp_conn_state_t state;
  } tcp_conn_t;

通过typedef将TCP连接数据结构定义为tcp_conn_t类型,结构体中以”r_”开头的成员表示remote端的信息,以”l_”开头的成员表示local端的信息。逐个介绍每个成员的意义,r_port,l_port,r_ip,l_ip即四元组,分别代表,源端口,目标端口,源IP和目标IP。收到TCP SYN后从TCP头部解析出的源端口即TCP SYN发送方的端口,对于TCP模块来讲是remote端口,所以这里的remote和local刚好相反的对应到TCP SYN的源和目标端。r_mss和l_mss是remote和local端的TCP MSS。r_wnd_sz和l_wnd_sz是remote端和local端的接收缓存大小。r_shift_cn和l_shift_cn对应remote和local端的window scale数值,r_sack代表remote端是否支持SACK。r_hflag和l_hflag是remote端和local端TCP头部中的flags字段的值。time_echo保存remote端TCP options中的timestamp数值。next_seq是希望收到的下一个TCP数据帧的序列号,send_seq是l_local端发出的TCP数据帧的序列号。state是TCP连接状态数据结构。
TCP连接状态数据结构也是TCP模块的内部数据结构,定义在tcp.c中。

  typedef enum tcp_conn_state {
          STATE_CLOSED = 0,
          STATE_LISTEN,
          STATE_SYN_SENT,
          STATE_SYN_RECEIVED,
          STATE_ESTABLISHED,
          STATE_CLOSE_WAIT,
          STATE_LAST_ACK,
          STATE_FIN_WAIT1,
          STATE_FIN_WAIT2,
       	  STATE_CLOSING,
          STATE_TIME_WAIT,
  } tcp_conn_state_t;

TCP连接状态定义为枚举类型,从STATE_CLOSED到STATE_TIME_WAIT共11个数值,分别代表TCP连接在不同阶段的状态。TCP连接的状态转换在9.5节实现三步握手时详细介绍,这里先引入数据结构的定义。本节只用到SYN_CLOSED和STATE_LISTEN两个状态,分别代表TCP连接处于关闭状态,和被动监听连接的状态。
DIY TCP/IP的TCP模块将所有的TCP连接存放在一个tcp_conn_t类型的数组中,做为TCP模块的内部静态变量使用。TCP模块新增的接口函数有tcp_init,tcp_deinit用于初始化TCP连接数组,和释放连接数组占用的内存。tcp_conn_open,tcp_conn_close用于打开和关闭TCP连接。新增的接口函数声明在tcp.h中暴露给其他模块使用,函数实现在tcp.c中,先来看函数声明,再介绍每个函数的具体实现。

 #ifndef _TCP_H_
 #define _TCP_H_
…
 
 int tcppkt_recv(void *pkt, unsigned int sz);
 int tcp_init();
 void tcp_deinit();
 int tcp_conn_open(unsigned short port);
 int tcp_conn_close(int idx);
 #endif

line 2到line4略去的内容与9.3节一样,高亮显示的部分是本节新增的接口函数的声明。tcp_init没有入参,返回值为0,表示成功初始化TCP连接数组。tcp_deinit没有入参也没有返回值,这两个函数在初始化模块init.c中被调用。tcp_conn_open入参为TCP port,返回值是成功创建的TCP连接在连接数组中的下标,失败时返回负值,tcp_conn_close入参为TCP连接数组的下标,成功时返回0,失败时返回负值。

 static tcp_conn_t *tcp_connections = NULL;
 static int tcp_conn_num = 0;
…
 int tcp_init()
 {
     int ret = 0;
     if (tcp_connections == NULL) {
         tcp_connections = (tcp_conn_t *)malloc(sizeof(tcp_conn_t));
         if (tcp_connections == NULL) {
             log_printf(ERROR, "No memory for TCP module\n");
             return -1;
         }
         memset(tcp_connections, 0, sizeof(tcp_conn_t));
         tcp_conn_num = 1;
     }
     return ret;
 }
 
 void tcp_deinit()
 {
     if (tcp_connections == NULL)
         return;
     log_printf(INFO, "Destroy TCP Connections\n");
     /* TBD: flush packets in rcv window */
     free(tcp_connections);
 }

Line 1-2: 定义TCP模块的静态变量TCP连接数组,和数组中的连接数目。line82-136略之间略去的部分是tcp_conn_open和tcp_conn_close的实现,稍后介绍。
Line 6-17: TCP模块的初始化函数,目前只用于初始化TCP连接数组,初始化只申请存放一个TCP连接的内存空间,连接数目也初始化为1,通过tcp_conn_open打开新的TCP连接时,扩展连接数组tcp_connections占用的内存空间和数组中的连接数目。将TCP连接的各个成员值初始化为0,tcp_conn_t的state成员值也为0,对应STATE_CLOSED状态。
Line 19-26: TCP模块的销毁函数,目前是用于释放TCP连接数组占用的内存空间,TBD(to be done)的注释部分用于扩展处理各个TCP连接的接收缓存中尚未处理的TCP数据帧。

 int tcp_conn_open(unsigned short port)
 {
     int ret = 0, i;
     tcp_conn_t *conn = NULL;
 
     if (tcp_connections == NULL) {
         log_printf(ERROR, "TCP module not initialized\n");
         return -1;
     }
     for (i = 0; i < tcp_conn_num; i ++) {
         if (tcp_connections[i].l_port == port) {
             ret = i;
             goto out;
         }
     }
     for (i = 0; i < tcp_conn_num; i ++) {
         if (tcp_connections[i].state == STATE_CLOSED) {
             conn = &tcp_connections[i];
             ret = i;
         }
     }
     if (conn == NULL) {
         tcp_connections = realloc(tcp_connections,
             (tcp_conn_num + 1) * sizeof(tcp_conn_t));
         if (tcp_connections == NULL) {
             log_printf(ERROR, "No memory for TCP connection\n");
             ret = -1;
             goto out;
         }
         tcp_conn_num += 1;
         conn = &tcp_connections[i];
         memset(conn, 0, sizeof(tcp_conn_t));
         ret = i;
     }
     conn->state = STATE_LISTEN;
     conn->l_port = port;
     conn->l_wnd_sz = 65535;
     conn->l_shift_cn = 8;
     conn->l_mss = get_ifmtu(DEFAULT_IFNAME)
                 - sizeof(iphdr_t) - sizeof(tcphdr_t);
     log_printf(INFO, "TCP listen on port: %u\n", conn->l_port);
 out:
     return ret;
 }

tcp_conn_open用于打开一个TCP连接,DIY TCP/IP的虚拟IP地址本身就是局域网中不存在的IP地址,与虚拟IP地址对应的端口号也是不存在的,所以可以指定需要的端口号,不必担心与Linux kernel中已经使用的端口号发生冲突。
Line 1-41: 首先判断连接数组tcp_connections是否已经初始化,不为空时继续执行。遍历tcp_connections连接数组,查找指定的端口号是否已经存在,如果存在,直接返回对应数组下标。如果指定端口号不存在,再次遍历连接数组,查找数组中可以使用的TCP连接,即处于STATE_CLOSED状态的连接,如果找到,保存数组下标做为返回值。如果没有找到,则说明连接数组中已经没有空闲连接,通过realloc扩展连接数组,扩展的增量为1。代码执行到line120行时,已经确定了空闲连接的下标,初始化新增连接的状态为STATE_LISTEN监听指定的TCP端口号,初始化l_wnd_sz为最大值65535,l_shift_cn为8,即设置接收缓存为65535 << 8 = 16M。通过utils模块的新增函数get_ifmtu获取指定网络接口的MTU数值,一般情况下网络接口的MTU为1500,MTU减去IP头部的20个字节,和TCP头部不带任何选项时的20个字节,得到TCP连接本地端的MSS,最后打印新增连接的监听端口号,返回TCP连接在连接数组中的下标值。

 int tcp_conn_close(int idx)
 {
     int ret = 0;
 
     if (idx >= tcp_conn_num && idx < 0)
         ret = -1;
     tcp_connections[idx].state = STATE_CLOSED;
     /* TBD: close TCP connection, flush packets in rcv window */
     return ret;
 }

tcp_conn_close判定下标值合法后,将对应连接状态设置为STATE_CLOSED关闭状态,使TCP连接可以循环使用。TBD (to be done)的注释部分,用于扩展处理TCP连接的接收缓存中尚未处理的TCP数据帧。
本节除了新增接口函数之外,由于引入了TCP连接数据结构,TCP数据帧的接收和TCP头部选项内容的解析函数都在9.3节的基础上对做了相应的修改。先来看tcppkt_recv函数的修改:

 int tcppkt_recv(void *pkt, unsigned int sz)
 {
     int ret = 0;
     iphdr_t *ippkt = NULL;
     tcp_pseudo_hdr_t pseudo_hdr;
     unsigned short tcppkt_len = 0;
     tcphdr_t *tcppkt = NULL;
     tcp_conn_t *conn = NULL;
 
     if (pkt == NULL || sz == 0) {
         log_printf(ERROR, "TCP receive packet failed,"
                 "invalid parameters\n");
         ret = -1;
         goto out;
     }
     ippkt = (iphdr_t *)pkt;
     tcppkt_len  = NTOHS(ippkt->total_len) - sizeof(iphdr_t);
     tcppkt = strip_header(ippkt, sizeof(iphdr_t));
     /* build tcp pesudo header */
     memset(&pseudo_hdr, 0, sizeof(pseudo_hdr));
     memcpy(pseudo_hdr.src_ip, ippkt->src_ip, sizeof(ippkt->src_ip));
     memcpy(pseudo_hdr.dst_ip, ippkt->dst_ip, sizeof(ippkt->dst_ip));
     pseudo_hdr.proto = ippkt->proto;
     pseudo_hdr.len = HSTON(tcppkt_len);
     /* tcp checksum validation */
     if (tcp_cksum(&pseudo_hdr, tcppkt, tcppkt_len)) {
         log_printf(ERROR, "Invalid TCP checksum\n");
         ret = -1;
         goto out;
     }
     /* TBD: find established tcp connection */
     /* find listenning tcp connection */
     conn = tcp_find_listen_conn(NTOHS(tcppkt->dst_port));
     if (conn == NULL) {
         log_printf(ERROR, "%u port not available\n",
             NTOHS(tcppkt->dst_port));
         ret = -1;
         goto out;
         
     }
     if (tcp_parse_options(conn, tcppkt, tcppkt_len)) {
         log_printf(ERROR, "Failed to parse TCP options\n");
         ret = -1;
         goto out;
     }
     memcpy(conn->r_ip, ippkt->src_ip, sizeof(ippkt->src_ip));
     memcpy(conn->l_ip, ippkt->dst_ip, sizeof(ippkt->dst_ip));
 out:
     return ret;
 }

Line 8: 对比9.3节,将data_offset和data_len局部变量移除,新增tcp_conn_t *指针,保存查找到的TCP连接的地址,没有高亮显示的代码与9.3节一致。
Line 31-40: TBD(to be done)的注释部分用于扩展在已经建立TCP连接的情况下,处理收到TCP数据帧的情况,TCP数据帧的处理放在滑动窗口的实现一节介绍。本节默认的情况是没有建立TCP连接,首先根据TCP头部的目标端口号,查找是否存在TCP连接正在监听目标端口号,这里不用比较目标IP地址,是因为IP模块在接收TCP数据帧时,已经判断过,目标IP地址是DIY TCP/IP的虚拟IP地址,所以只需根据目标端口号查找被动监听的TCP连接,如果没有找到监听目标端口号的连接,则丢弃该TCP数据帧。
Line 41-50: 执行到这里,说明找到了监听目标端口的TCP连接,9.3节的tcp_prase_options函数只是解析了TCP SYN头部的选项字段,并打印输出,避免重复解析选项字段的值,将解析过的选项如MSS,SACK等保存到TCP连接中备用。tcp_prase_options基于9.3节也做了相应的修改,tcp_parse_options函数返回成功时,将IP头部的源IP地址和目标IP地址保存在TCP连接中。

 static tcp_conn_t *tcp_find_listen_conn(unsigned short port)
 {
     int i;
     tcp_conn_t *conn = NULL;
 
     if (tcp_connections == NULL)
         return NULL;
     for (i = 0; i < tcp_conn_num; i ++) {
         if (tcp_connections[i].l_port == port &&
             tcp_connections[i].state == STATE_LISTEN) {
             conn = &tcp_connections[i];
             break;
         }
     }
     return conn;
 }

tcp_find_listen_conn入参是目标端口,成功时返回指向TCP连接数组中查找到的TCP连接的指针,失败时返回空。函数体遍历tcp_connections数组,查找与入参端口号相等的TCP连接,且TCP连接处于STATE_LISTEN监听状态。

 static int tcp_parse_options(tcp_conn_t *conn, 
             tcphdr_t *tcppkt, unsigned short pkt_len)
 {
     int ret = 0;
     unsigned char *popt = NULL;
     unsigned short MSS = 0;
     unsigned char SACK = 0;
     unsigned short data_len = 0;
     unsigned short data_offset = 0;
     unsigned short opt_sz = 0;
     unsigned char shift_count = 0;
     unsigned int TSval = 0, TSecr = 0;
     tcphdr_flags_t flags;
 
     if (conn == NULL || tcppkt == NULL || pkt_len ==0 )
         return -1;
 
     flags.v = NTOHS(tcppkt->flags_len);
     data_offset = flags.b.hdr_len << 2;
     data_len = pkt_len - data_offset;
     opt_sz = data_offset - sizeof(tcphdr_t);
     
     if ((popt = get_opt(tcppkt->options,
                 opt_sz, TCP_OPT_MSS))) {
         MSS = NTOHS(*(unsigned short *)popt);
     }
     if ((popt = get_opt(tcppkt->options,
                 opt_sz, TCP_OPT_SACK))) {
         SACK = 1;
     }
     if ((popt = get_opt(tcppkt->options,
                 opt_sz, TCP_OPT_WSCALE))) {
         shift_count = *(unsigned char *)popt;
     }
     if ((popt = get_opt(tcppkt->options,
                 opt_sz, TCP_OPT_TIMESTAMP))) {
         TSval = NTOHL(*(unsigned int *)popt);
         TSecr = NTOHL(*((unsigned int *)&popt[4]));
     }
     conn->r_port = NTOHS(tcppkt->src_port);
     conn->l_port = NTOHS(tcppkt->dst_port);
     conn->r_wnd_sz = NTOHS(tcppkt->wnd_sz);
     conn->r_mss = MSS;
     conn->r_sack = SACK;
     conn->r_shift_cn = shift_count;
     conn->time_echo = TSval;
     conn->next_seq = NTOHL(tcppkt->seq_num) + data_len;
 
     log_printf(INFO, "%u -> %u: Seq=%u Win=%u Len=%u MSS=%u "
             "SACK_PERM=%u TSval=%u TSecr=%u\n",
             conn->r_port, conn->l_port,
             NTOHL(tcppkt->seq_num), conn->r_wnd_sz,
             data_len, MSS, SACK,
             conn->time_echo, TSecr);
     return ret;
 }

Line 4-39: tcp_parse_options入参为conn,指向查找到的TCP连接的指针,tcppkt指向接收到的TCP数据帧的首字节地址,pkt_len为TCP数据帧的长度,包括TCP头部的长度和TCP数据部分的长度。tcp_prase_options的参数不同于9.3节,选项字段的长度opt_sz,TCP数据部分的长度data_len,都放在tcp_parse_options函数内部计算,与9.3节tcppkt_recv函数中的计算方法一致。另外添加了对TIMESTAMP,Window Scale选项的解析。
Line 40-47: 初始化TCP连接的各个成员,从TCP数据帧头部获取源端口值,目标端口值,赋值给TCP连接的r_port (remote port )和l_port (local port)。TCP数据帧头部的window size赋值给r_wnd_sz,保存remote端接收缓存的大小。r_mss, r_sack, r_shift_cn分别保存remote端的Max Segment Size,是否支持SACK,Window Scale的数值,r_wnd_sz << r_shift_cn可以计算出remote端扩展后的接收缓存大小。time_echo保存接收到的TCP数据帧的时间戳,回复对应的TCP ACK时,用于填充TCP ACK头部的timestamp选项的timestamp echo reply字段。conn->next_seq字段的值是希望收到的下一个TCP数据帧的Sequence Number,是当前收到的TCP数据帧的Sequence Number加上,当前收到的TCP数据帧的数据部分的长度。
Line 49-56: 打印输出接收到的TCP数据帧的头部信息,和解析后的选项值的信息。
本节的最后一处修改是DIY TCP/IP的初始化模块init.c,添加TCP模块接口函数的调用,高亮显示的部分是本节的新增代码,其余代码与init模块最近一次在7.2节的修改一致。

 int main(int argc, char *argv[])
 {
  int c, ret = 0;
  unsigned char ip_cfg = 0;
  unsigned char fake_ip[4];
  net_device_t *ndev = NULL;
  unsigned char *tcp_port = NULL;
 
  if (argc <= 1) {
      log_printf(WARNING, "No option configured for DIY User Space TCP/IP stack\n");
      goto skip_option;
  }
 
  for (;;) {
      c = getopt(argc, argv, "i:p:h");
      if (c < 0) {
          if (optind <= (argc - 1)) {
              log_printf(ERROR, "invalid option '%s' found\n", argv[optind]);
              ret = -1;
              goto out;
          }
          break;
      }
      switch (c) {
          case 'i':
              ip_cfg = 1;
              dotip2rawbyte(optarg, fake_ip, sizeof(fake_ip));
              break;
          case 'p':
              tcp_port = optarg;
              break;
          case 'h':
              print_help();
              goto out;
          default:
              log_printf(ERROR, "invalid option: %s\n", optarg);
              ret = -1;
              goto out;
      }
  }
 
 skip_option:
  signal(SIGINT, signal_handler);
 
  ndev = netdev_init(DEFAULT_IFNAME);
  if (ndev == NULL) {
      ret = -1;
      goto out;
  }
  if (ip_cfg)
      netdev_set_ipaddr(ndev, fake_ip, sizeof(fake_ip));
  pdbuf_init();
  arp_init();
 tcp_init();
  if (tcp_port)
      tcp_conn_open((unsigned short)atoi(tcp_port));
  netdev_start_loop(ndev);
 out:
  if (ndev)
      netdev_deinit(ndev);
  arp_deinit();
  tcp_deinit();
  pdbuf_deinit();
  return ret;
 }
 

Line 7: 新增unsigned char *类型的指针tcp_port,指向getopt获取到的-p选项的参数值,做为初始化TCP连接的端口号。
Line 54-56: 调用tcp_init初始化TCP模块,按照本节tcp_init的实现,只是初始化了TCP连接数组。通过C的库函数atoi将tcp_port指向的字符串的值,转换为整型值,传入tcp_conn_open时再强制转换为无符号16位整型值,初始化一条TCP连接,监听tcp_port,等待remote端发起TCP连接请求。tcp_port的数值只要在16位无符号整型值的数值范围内即可,tcp_port端口号是与DIY TCP/IP的虚拟IP地址相关的,实际上虚拟IP地址和tcp_port端口号对于运行DIY TCP/IP的主机A上的linux kernel 而言都是不存在的,所以不必担心指定的端口号与Linux kernel中正在使用的端口号发生冲突。
Line 62: 销毁DIY TCP/IP时,调用tcp_deinit,释放TCP模块占用的内存,按照本节的实现,tcp_deinit释放TCP连接数组占用的内存。
验证本节代码实现之前,再来看一下utils模块新增的两个函数,get_ifmtu和get_currenttime。get_ifmtu已经在tcp_conn_open函数中被用到,get_currenttime本节暂未使用,该函数将在9.7节发送TCP数据帧时用到。utils模块的函数实现都在utils.c文件中。

 /* ifreq: /usr/include/net/if.h */
 int get_ifmtu(char *ifname)
 {
         int fd = 0;
         int ifmtu = -1;
         struct ifreq ifr;
         unsigned int ifname_sz;
 
         if (ifname == NULL)
                 return ifmtu;
 
         if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
                 printf("Failed to create socket fd: %s (%d)\n",
                                 strerror(errno), errno);
                 goto out;
         }
         memset(&ifr, 0, sizeof(ifr));
         strncpy(ifr.ifr_name, ifname, MIN(strlen(ifname) + 1, sizeof(ifr.ifr_name)));
         if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
                 printf("Failed to get interface: %s mtu %s (%d)\n",
                                 ifr.ifr_name, strerror(errno), errno);
                 goto out;
         }
         ifmtu = ifr.ifr_mtu;
 out:
         if (fd)
                 close(fd);
         return ifmtu;
 }

get_ifmtu与7.2节中获取硬件地址的函数get_hw_addr一致,都是通过ioctl系统调用获取指定网络设备ifname的参数,命令字为SIOCGIFMTU。7.2节中已经详细介绍了get_hw_addr的实现,get_ifmtu仅仅是命令字不同。

 /* get current time in usec */
 unsigned int get_current_time(void)
 {
 #define MAX_UINT32_VAL (0xFFFFFFFFU)
 
         int ret;
         unsigned long usec = 0;
         struct timespec now;
 
         if(clock_gettime(CLOCK_REALTIME, &now) < 0) {
                 printf("Failed to get clock time, %s (%d)\n", strerror(errno), errno);
                 return MAX_UINT32_VAL;
         }
 
         usec = now.tv_sec * 1000000;
         usec += (now.tv_nsec / 1000);
 
         /* adjust unsigned long timestamp to the scope of unsigned int */
         if (usec > MAX_UINT32_VAL)
                 usec %= MAX_UINT32_VAL;
         return (unsigned long)usec;
 }

get_current_time获取当前系统时间,通过librt的库函数clock_gettime实现,获取指定clock_id的时间。这里指定的clock_id是CLOCK_REALTIME,即wall-time,是系统的真实时间。clock_gettime的返回值存放在timespec结构体中,精度是纳秒,将其转换成微妙存放在无符号长整型变量usec中。TCP的timestamp选项存放本地时间戳的字段是4个字节,usec超过32位无符号整型的最大值时,取2^32 – 1的余数,做为返回值。
9.4节已经完成了TCP数据帧头部校验和的检验,能正确接收TCP数据帧,并解析出主机C上运行的iperf客户端发出的TCP SYN的目标端口是iperf TCP server的默认目标端口5001。本节测试在9.4节接收TCP数据帧的基础上,查找TCP SYN目标端口号对应的TCP连接,并打印TCP SYN数据帧的信息。测试方法与9.4节一致,主机A上运行DIY TCP/IP,指定虚拟IP地址,和TCP端口号,命令行为./tcp_ip_stack -i <虚拟IP地址A> -p <端口号P>,与主机A处于同一局域网的Android手机,主机C上运行iperf client端,命令行为iperf -c <虚拟IP地址A> -i 1 -t 43200 -p <端口号P>。
Android手机主机C,运行iperf的截屏如下
DIY TCP/IP Android Iperf Runtime Log
主机C运行iperf TCP客户端,指定TCP Server的IP地址是192.168.1.7,端口号为7000。iperf客户端没有打印任何Log,说明该IP地址在局域网中不存在。
同一时间段,运行在主机A上的tcpdump的结果如下:
DIY TCP/IP Tcpdump log
主机A上tcpdump捕获eth0网络接口的数据帧,与DIY TCP/IP中pcap_open_live指定的网络接口一致。-s 0指定存放数据包的缓存为最大值65535,-w指定将捕获的数据帧存入tcp_recv.pcap文件。从tcpdump的结果来看,一共捕获到了11个数据帧。
同一时间段,运行在主机A上的DIY TCP/IP的结果如下:
DIY TCP/IP Runtime log
从运行结果可以看出,DIY TCP/IP的TCP模块一共收到了4个TCP数据帧,说明TCP头部校验和检验正确,并查找到了监听目标端口号的TCP连接。打印结果显示收到的TCP数据帧的源端口号是47031,目标端口号是7000。4个TCP数据帧的Sequence Number都一样,说明后三个TCP数据帧是重传的,Window Size是65535,Window Scale是8 (1 << 8),数据部分的长度为0,MSS是1460,支持SACK选项,第一个TCP数据帧的时间戳是4770872,后面三个TCP数据帧的时间戳是不断增长的,间隔为100ms,200ms,400ms指数级增长。来看主机A上tcpdump的结果,对比DIY TCP/IP的运行结果。
主机A上tcpdump的结果在wireshark中打开如下
DIY TCP/IP Iperf TCP Connect
从wireshark的分析中可以看到,主机C(192.168.1.150)首先发出了ARP Reuqest,然后主机A上运行的的DIY TCP/IP正确回复了ARP Reply,建立了虚拟IP地址192.168.1.7与eth0硬件地址的映射。主机C又发出了4个TCP SYN数据帧,其中有3帧是黑色背景,红色前景显示,说明3个TCP SYN是重传的。通过Sequence number也可以看出这四个TCP SYN的序号一样,MSS为1460,支持SACK选项。
这里TCP数据帧的Sequence number被wireshark友好的解析为0,将第一个TCP数据帧展开,来看Sequence number是否与DIY TCP/IP的打印输出一致。
DIY TCP/IP TCP SYN Port 7000
展开后的TCP数据帧的头部可以看到,sequence number 为0,后面括号中的内容是relative sequence number,这是wireshark软件解析的结果,说明该序号是相对值。在右边的raw byte显示栏可以看到sequence number的值为0x2b0ab8df(722122975十进制), TCP数据帧携带的数据长度为0,源端口,目标端口,Window Size 和Window Scale以及TCP头部中各个选项的值都与DIY TCP/IP的输出结果一致。
下一篇:DIY TCP/IP TCP模块的实现3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值