【问题标题】:Read TCP Options Fields读取 TCP 选项字段
【发布时间】:2017-03-12 17:16:51
【问题描述】:

我正在链接 linux/tcp.h 并且我正在尝试读取 TCP 选项,但我似乎无法找到这样做的方法。我已经在线阅读了一些内容,根据一些在线资源,我必须迭代所有“剩余数据包”,直到我点击我想要的选项? (现在我将尝试专注于“MSS”选项)。谁能给我一个代码示例?

struct iphdr *iph = ((struct iphdr *) full_packet);
    fprintf(stdout, "IP{v=%u; ihl=%u; tos=%u; tot_len=%u; id=%u; ttl=%u; protocol=%u; "
        ,iph->version, iph->ihl*4, iph->tos, ntohs(iph->tot_len), ntohs(iph->id), iph->ttl, iph->protocol);

    if (iph->protocol == 6){

        struct tcphdr *tcp = ((struct tcphdr *) (full_packet + (iph->ihl << 2)));
        fprintf(stdout, "TCP{sport=%u; dport=%u; seq=%u; ack_seq=%u; flags=u%ua%up%ur%us%uf%u; window=%u; urg=%u}\n",
            ntohs(tcp->source), ntohs(tcp->dest), ntohl(tcp->seq), ntohl(tcp->ack_seq)
            ,tcp->urg, tcp->ack, tcp->psh, tcp->rst, tcp->syn, tcp->fin, ntohs(tcp->window), tcp->urg_ptr);\
    }

到目前为止,我在读取/解析 IP/TCP 数据方面是什么

谢谢!

顶...任何帮助表示赞赏!

进度更新(感谢@WillisBlackburn):

struct iphdr *iph = ((struct iphdr *) full_packet);

fprintf(stdout, "IP{v=%u; ihl=%u; tos=%u; tot_len=%u; id=%u; ttl=%u; protocol=%u; "
    ,iph->version, iph->ihl*4, iph->tos, ntohs(iph->tot_len), ntohs(iph->id), iph->ttl, iph->protocol);

if (iph->protocol == 6){

    struct tcphdr *tcp = ((struct tcphdr *) (full_packet + (iph->ihl << 2)));

    uint8_t *p = (uint8_t *)tcp + 20;
    uint8_t *end = (uint8_t *)tcp + tcp->doff * 4;
    uint16_t mss = 0; 
    printf("\nThe offset is %d\n", tcp->doff);
    printf("Let's check what's at location p: %u is supposed to be less than %d\n",(*(uint8_t *)tcp + 20), (uint8_t)end);
    while (p < end) {
        uint8_t kind = *p++;
        if (kind == 0) {
            printf("The kind is 0?\n");
            break;
        }
        if (kind == 1) {
            // No-op option with no length.
            continue;
        }
        uint8_t size = *p++;
        if (kind == 2) {
            mss = ntohs(*(uint16_t *)p);
            printf("The MSS value is: %d\n", mss);
        }
        p += (size - 2);
    }

    fprintf(stdout, "TCP{sport=%u; dport=%u; seq=%u; ack_seq=%u; flags=u%ua%up%ur%us%uf%u; window=%u; urg=%u}\n",
        ntohs(tcp->source), ntohs(tcp->dest), ntohl(tcp->seq), ntohl(tcp->ack_seq)
        ,tcp->urg, tcp->ack, tcp->psh, tcp->rst, tcp->syn, tcp->fin, ntohs(tcp->window), tcp->urg_ptr);

}

更多进展:

发现了一些很酷的读取 TCP 选项的实现;但是,我不太确定在我的代码中实现它。人们有什么帮助吗?这是我发现的:

https://github.com/multipath-tcp/mptcp/blob/214e17c446d98e238c3bc8a3177990eae6a7059b/net/ipv4/tcp_input.c#L3674-L3789

https://github.com/multipath-tcp/mptcp/blob/02bc9ec8da25e0e71f9d1fec02be4632a0846092/include/net/tcp.h#L170-L210

它似乎比 WillisBlackburn 的复杂一些,并且可能会解决那种太大的问题? (200+ 一些数据包,而不是我们期望的 MSS 的“2”(+ TCP 数据包中的其他选项)。

不胜感激!!!

如果你想谈论它,我将在频道#adamc 中的 chat.freenode.net (niven.freenode.net) 中讨论!

最新更新:

所以,我基于 MTCP,但这里是代码:(添加的用户测试):

const unsigned char *ptr;
const struct tcphdr *th = ((struct tcphdr *) (full_packet + (iph->ihl << 2)));
int length = (th->doff * 4) - sizeof(struct tcphdr);

ptr = (const unsigned char *)(th + 1);

while(length > 0){
  printf("Got in the while loop\n");
  int opcode = *ptr++;
  int opsize;

  switch(opcode) {
    case 0:
      printf("Got the initial val (EOL)\n");
      return;
    case 1:
      printf("Got the NOP val as well!\n");
      length--;
      continue;
    default:
      printf("Entered default.\n");
      opsize = *ptr++;
      printf("Does stuff after setting the OPSize.\n");
      if(opsize < 2) {
        printf("OPSize = %d < 2;Length = %d\n", opsize, length);
        return;
      }
      if(opsize > length) {
        printf("OPSize = %d > Length = %d\n", opsize, length);
        return;
      }
      switch(opcode) {
        printf("Switching the OPCode\n");
        case 2: //TCP MSS
          if(opsize == 4 && th->syn) {
            printf("MSS: %d\n", ptr);
          }
        case 3: //TCP Window
          if(opsize==3 && th->syn) {
            uint8_t wscale = *(uint8_t *)ptr;
            if(wscale>14) {
              printf("Illgal wscale value: %d\n", wscale);
            }
            printf("WSCALE: %d\n", *(uint8_t*)ptr);
          }
          break;
        case 8: //Timestamp
          if(opsize==10) {
            printf("Timestamp is present!!!\n");
          }
          break;
        case 4: //TCP SACK
          if((opsize >= (2+8)) && !((opsize-2)%8)) {
            printf("SACK val: %d\n", ((ptr-2)- (unsigned char *)th));
          }

        default:
          printf("Entered default of switching OPCode\n");
          return;
      }
      ptr += opsize-2;
      length -= opsize;
  }
}

所以现在我的问题是: 当我提出请求时,它会给我以下信息:

IP{v=4; ihl=20; tos=0; tot_len=64; id=48952; ttl=61; protocol=6; Got in the while loop
Entered default.
Does stuff after setting the OPSize.
OPSize = 127 > Length = 12

所以基本上,它进入了while循环,进入了默认值(在opsizes之间切换)等等;但是,OPSize 是 127(大于长度),所以我相信我在获取尺寸方面做错了。任何帮助将不胜感激!

【问题讨论】:

  • 是用户空间程序吗?为什么需要选项?你从哪里得到的包?根据en.wikipedia.org/wiki/… 选项只是在 tcp 标头的固定字段之后,但它们没有固定的结构。请检查struct tcphdr 的“数据偏移”字段doff 字段,增加指向数据包的指针并解析它们(或要求解析给你full_packet 的东西)。示例:lxr.free-electrons.com/source/net/netfilter/… - TCPOPT_MSS
  • @osgx 是的,它是一个用户空间程序。我正在阅读防火墙中的选项,并通过他们的排队系统从 netfilter 获取数据包。我仍然对那个示例感到有些困惑。您还有其他更易于阅读的示例或与上述示例特别适用的示例吗?
  • 我没有其他示例,并且我发现了这一示例中唯一令人困惑的点 - 指针增量为 op = skb_header_pointer(skb, par-&gt;thoff + sizeof(*th), optlen, _opt); - 将其替换为 const u_int8_t *op = ((u_int8_t *) full_packet ) + tcphdr.doff*4 - sizeof(struct tcphdr)。宏定义在 include/net/tcp.h - lxr.free-electrons.com/source/include/net/tcp.h#L175 TCPOPT_MSS 2 TCPOLEN_MSS 4

标签: c linux networking tcp


【解决方案1】:

有两种可能的答案:如何获取 TCP 连接的 MSS(最大段大小)以及如何解析 TCP 标头。

如果您只想知道连接的 MSS 是多少,那么您可以在打开的套接字上使用 getsockopt 函数。将文件描述符、IPPROTO_TCP 作为级别、TCP_INFO 作为选项、将保存输出的struct tcp_info(来自 tcp.h)的地址和sizeof (struct tcp_info) 传递给它。内核将填写struct tcp_info 的字段,您可以从tcpi_snd_msstcpi_rcv_mss 获取MSS。

如果你真的想解析 TCP header 本身,那么你必须了解 header 的布局。您使用的struct tcphdr 不包括选项,这些选项位于struct tcphdr 中的字段之后。每个选项字段都是一个字节,用于标识选项种类,可选地后跟第二个字节,指定选项的大小(包括种类和大小字节),然后是附加数据。

可能根本没有任何选择。您必须首先查看 TCP 标头的数据偏移字段 (doff)。它是 32 位字。 struct tcphdr 定义的标准标头的大小为 20 字节,因此只有在 doff 大于 5(乘以 4 字节 = 20 字节)时才会出现选项。

假设有选项,您可以像这样阅读它们。请注意,MSS 的选项种类为 2,选项种类 0 表示选项列表的结尾,但前提是选项列表的结尾与 doff 中的数据的开头不一致。选项类型 0 和 1(无操作)是单个字节。其他选项种类在种类后面有一个大小字节,并指定选项字段的大小(包括种类和大小字段)。

uint8_t *p = (uint8_t *)tcp + 20; // or sizeof (struct tcphdr)
uint8_t *end = (uint8_t *)tcp + tcp->doff * 4;
uint16_t mss = 0; 
while (p < end) {
    uint8_t kind = *p++;
    if (kind == 0) {
        break;
    }
    if (kind == 1) {
        // No-op option with no length.
        continue;
    }
    uint8_t size = *p++;
    if (kind == 2) {
        mss = ntohs(*(uint16_t *)p);
    }
    p += (size - 2);
}

我没有对此进行测试,但应该非常接近。

我找不到任何文档可以确认只有 TCP 选项类型 0 和 1 不包含大小字节。 TCP 的规范是RFC 793,它(在撰写本文时)已有 35 年历史,仅提及选项种类 0、1 和 2。它表明大小字节是否存在取决于选项种类。但这意味着为了正确解析标头,您的程序必须知道all the possible options it might encounter。例如,如果头的选项段包含字节 16、2、4、1 和 0,这是否意味着类型 16 的选项,后跟大小字节 2,然后是选项类型 4(因为大小2 表示选项仅由种类和大小组成)?或者它是否意味着没有大小字节的类型 16 的选项,然后是类型 2 (MSS) 的选项,然后是大小字节 4?除非您知道选项类型 16“Skeeter”是否应该跟一个大小字节,否则您无法知道。

【讨论】:

  • 可以通过使用一些标准的 linux tcp.h 标头在代码中减少魔术常量吗?
  • 有什么建议吗?我查看了 tcp.h,但里面并没有太多关于选项的内容。
  • th_off 和doff 选项一样吗?它说它没有 th_off 但我假设它是一样的?
  • 是的——我正在查看 netinet/tcp.h 中结构的 BSD 版本。
  • 您每次都在看同一个数据包吗?你能发布它的十六进制转储吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
  • 2019-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多