【问题标题】:parse IP and TCP header (especially common tcp header options)of packets captured by libpcap解析 libpcap 捕获的数据包的 IP 和 TCP 标头(尤其是常见的 tcp 标头选项)
【发布时间】:2013-05-07 08:33:42
【问题描述】:

我想用libpcap来捕获IP包,我想解析IP头和tcp头。 `

<netinet/ip.h><netinet/tcp.h>中有IP头和TCP头结构 IP 头相对容易解析,但是对于 TCP 头,由于有 tcp 选项, 常见的选项是 MSS、SACK(选择性确认)、时间戳、窗口缩放和 NOP。

我想要一个函数 parse_pkt():

struct tcphdr tcp_hdr;
struct ip ip_hdr;
parse_pkt(u_char *pcap_packet, struct ip* p_ip, struct tcp* p_tcp);

所以调用函数后,如果我想知道源ip地址、序列号和MSS,

scr_ip = ip_hdr.src_ip
seq = tcp_hdr.seq
mss = tcp_hdr.mss

有没有类似的源代码/sn-ps 可以满足我的要求? 谢谢!

【问题讨论】:

    标签: networking tcp ip libpcap


    【解决方案1】:

    (下面的第一个示例)这是我正在开发的东西(在 C++11 中)。这适用于 UDP 数据包,但您可以通过添加相关结构和Net::load() 模板来使其适用于 TCP 数据包,如下所示。

    (下面的第二个示例)您没有在问题中指定目标语言,但如果您正在寻找 C,那么您可以在结构上使用 #pragma pack,然后将指针+偏移量转换为指向结构,然后在适当的字段上调用 ​​ntohs/ntohl。这样做可能是最快的解决方案,但它依赖于#pragma pack,这不是标准的。

    C++11 风格

    net.h:

    namespace Net {
      using addr_t = uint32_t;
      using port_t = uint16_t;
    
      struct ether_header_t {
        uint8_t  dst_addr[6];
        uint8_t  src_addr[6];
        uint16_t llc_len;
      };
    
      struct ip_header_t {
        uint8_t  ver_ihl;  // 4 bits version and 4 bits internet header length
        uint8_t  tos;
        uint16_t total_length;
        uint16_t id;
        uint16_t flags_fo; // 3 bits flags and 13 bits fragment-offset
        uint8_t  ttl;
        uint8_t  protocol;
        uint16_t checksum;
        addr_t   src_addr;
        addr_t   dst_addr;
    
        uint8_t ihl() const;
        size_t size() const;
      };
    
      class udp_header_t {
      public:
        port_t   src_port;
        port_t   dst_port;
        uint16_t length;
        uint16_t checksum;
      };
    
      template< typename T >
      T load( std::istream& stream, bool ntoh = true );
      template<>
      ip_header_t  load( std::istream& stream, bool ntoh );
      template<>
      udp_header_t load( std::istream& stream, bool ntoh );
    
      std::string to_string( const addr_t& addr );
    }
    

    net.cpp:

    namespace Net {
    
      uint8_t ip_header_t::ihl() const {
        return (ver_ihl & 0x0F);
      }
    
      size_t ip_header_t::size() const {
        return ihl() * sizeof(uint32_t);
      }
    
      template<>
      ip_header_t load( std::istream& stream, bool ntoh ) {
        ip_header_t header;
        stream.read((char*)&header.ver_ihl,      sizeof(header.ver_ihl));
        stream.read((char*)&header.tos,          sizeof(header.tos));
        stream.read((char*)&header.total_length, sizeof(header.total_length));
        stream.read((char*)&header.id,           sizeof(header.id));
        stream.read((char*)&header.flags_fo,     sizeof(header.flags_fo));
        stream.read((char*)&header.ttl,          sizeof(header.ttl));
        stream.read((char*)&header.protocol,     sizeof(header.protocol));
        stream.read((char*)&header.checksum,     sizeof(header.checksum));
        stream.read((char*)&header.src_addr,     sizeof(header.src_addr));
        stream.read((char*)&header.dst_addr,     sizeof(header.dst_addr));
        if( ntoh ) {
          header.total_length = ntohs(header.total_length);
          header.id =           ntohs(header.id);
          header.flags_fo =     ntohs(header.flags_fo);
          header.checksum =     ntohs(header.checksum);
          header.src_addr =     ntohl(header.src_addr);
          header.dst_addr =     ntohl(header.dst_addr);
        }
        return header;
      }
    
      template<>
      udp_header_t load( std::istream& stream, bool ntoh ) {
        udp_header_t header;
        stream.read((char*)&header.src_port, sizeof(header.src_port));
        stream.read((char*)&header.dst_port, sizeof(header.dst_port));
        stream.read((char*)&header.length,   sizeof(header.length));
        stream.read((char*)&header.checksum, sizeof(header.checksum));
        if( ntoh ) {
          header.src_port = ntohs(header.src_port);
          header.dst_port = ntohs(header.dst_port);
          header.length   = ntohs(header.length);
          header.checksum = ntohs(header.checksum);
        }
        return header;
      }
    }
    

    数据包捕获处理程序中的客户端代码:

    using std::chrono::seconds;
    using std::chrono::microseconds;
    using clock = std::chrono::system_clock;
    using Net::ether_header_t;
    using Net::ip_header_t;
    using Net::udp_header_t;
    
    auto packet_time = clock::time_point(seconds(header->ts.tv_sec) + microseconds(header->ts.tv_usec));
    std::istringstream stream(std::string((char*)packet, header->caplen));
    stream.seekg(sizeof(ether_header_t), std::ios_base::beg);
    auto ip_header = Net::load<ip_header_t>(stream);
    if( ip_header.size() > 20 ) { 
      stream.seekg(ip_header.size() + sizeof(ether_header_t), std::ios_base::beg);
    }
    auto udp_header = Net::load<udp_header_t>(stream);
    

    另类(C风格):

    (抱歉有任何错误。我是凭记忆输入的,没有尝试编译或运行——但我想你会理解基本思想):

    net.h:

    typedef uint32_t addr_t;
    typedef uint16_t port_t;
    
    #pragma pack(push, 1)
    typedef struct {
      uint8_t  dst_addr[6];
      uint8_t  src_addr[6];
      uint16_t llc_len;
    } ether_header_t;
    
    typedef struct {
      uint8_t  ver_ihl;  // 4 bits version and 4 bits internet header length
      uint8_t  tos;
      uint16_t total_length;
      uint16_t id;
      uint16_t flags_fo; // 3 bits flags and 13 bits fragment-offset
      uint8_t  ttl;
      uint8_t  protocol;
      uint16_t checksum;
      addr_t   src_addr;
      addr_t   dst_addr;
    } ip_header_t;
    
    typedef struct {
      port_t   src_port;
      port_t   dst_port;
      uint16_t length;
      uint16_t checksum;
    } udp_header_t;
    #pragma pack(pop)
    

    数据包处理程序中的客户端代码:

    ip_header_t   ip_header =  (ip_header_t)*(packet + sizeof(ether_header_t));
    ip_header.total_length = ntohs(ip_header.total_length);
    ip_header.id           = ntohs(ip_header.id);
    ip_header.flags_fo     = ntohs(ip_header.flags_fo);
    ip_header.checksum     = ntohs(ip_header.checksum);
    ip_header.src_addr     = ntohl(ip_header.src_addr);
    ip_header.dst_addr     = ntohl(ip_header.dst_addr);
    int ip_size = 4 * (ip_header.ver_ihl & 0x0F);
    
    udp_header_t udp_header = (udp_header_t)*(packet + ip_size + sizeof(ether_header_t));
    udp_header.src_port = ntohs(udp_header.src_port);
    udp_header.dst_port = ntohs(udp_header.dst_port);
    udp_header.length   = ntohs(udp_header.length);
    udp_header.checksum = ntohs(udp_header.checksum);
    

    TCP 标头注释

    根据netinet/tcp.h,TCP头大致为:

    typedef struct {
      uint16_t src_port;
      uint16_t dst_port;
      uint32_t seq;
      uint32_t ack;
      uint8_t  data_offset;  // 4 bits
      uint8_t  flags;
      uint16_t window_size;
      uint16_t checksum;
      uint16_t urgent_p;
    } tcp_header_t;
    

    使用您喜欢的任何方法将此结构加载到内存中,并且不要忘记如上所述修复多字节整数类型的字节顺序(ntohs/ntohl)。

    TCP 选项如下,不能加载到这样的结构中。请参阅link 中有关 TCP 选项的部分。对于 MSS,您需要解析每个选项,直到找到 kind == 2 的选项。以上面的 C 示例为基础:

    typedef struct {
      uint8_t kind;
      uint8_t size;
    } tcp_option_t;
    
    uint16_t mss;
    uint8_t* opt = (uint8_t*)(packet + ip_size + sizeof(ether_header_t) + sizeof(tcp_header_t))
    while( *opt != 0 ) {
      tcp_option_t* _opt = (tcp_option_t*)opt;
      if( _opt->kind == 1 /* NOP */ ) {
         ++opt;  // NOP is one byte;
         continue;
      }
      if( _opt->kind == 2 /* MSS */ ) {
        mss = ntohs((uint16_t)*(opt + sizeof(opt)));
      }
      opt += _opt->size;
    } 
    

    【讨论】:

    • 他有兴趣解析TCP头选项,这个选项比其他字段更乱(当然有规则,但不像其他字段那么容易)。
    • 它并没有你想象的那么混乱。查看我的答案的底部。
    • 我认为你犯了一个错误,++opt 实际上会使其 +2。
    • 没有错。 opt 是一个指针。 opt++ 增加指针,continue 开始下一次迭代。
    • 如果是NOP,地址应该加1,但是++opt会让它加2,因为tcp_option_t是2字节
    猜你喜欢
    • 1970-01-01
    • 2021-12-25
    • 2018-04-13
    • 2016-12-29
    • 2011-07-16
    • 2013-06-12
    • 2011-01-05
    • 2013-03-04
    • 2014-03-12
    相关资源
    最近更新 更多