Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

parse IP and TCP header (especially common tcp header options)of packets captured by libpcap

I want to use libpcap to capture IP packet, and the I want to parse the IP header and tcp header. `

there are IP header and TCP header structures in <netinet/ip.h> and <netinet/tcp.h> IP header is relatively easier to parse, but for TCP header,since there are tcp options, the common options are MSS, SACK(selective acknowledgement), timestamp, window scaling and NOP.

I want to have a function parse_pkt():

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

so after calling the function, if I want to know the source ip address, sequence number, and MSS,

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

are there any similar source codes/snippets which can satisfy my requirements? thanks!

like image 364
user1944267 Avatar asked May 13 '13 10:05

user1944267


People also ask

What are TCP header options?

This option field is used in the SYN packet when the client establishes a connection with the server. “Kind=2” takes 1 bytes space, “length=4” takes 1-byte space and “MSS” takes 2-byte space. Thus, the total space consumed (length = 4) by the MSS option is 4 bytes. Maximum Segment Size (MSS)

What is packet parsing?

Abstract:Packet parsing is the first step in processing of packets in devices such as switches and routers. In this paper, we present a totally new program control unit as well as... Metadata. Abstract: Packet parsing is the first step in processing of packets in devices such as switches and routers.

Does TCP header include IP?

A TCP header always has one or more IP packets attached.

What is HLEN in TCP header?

The HLEN value is 8, which means the total number of bytes in the header is 8 × 4, or 32 bytes. The first 20 bytes are the base header the next 12 The first 20 bytes are the base header, the next 12 bytes are the options. In an IPv4 packet, the value of HLEN is 5, and the value of the total length field is 0x0028.


1 Answers

(First example below) Here's something that I have in the works (in C++11). This is for UDP packets, but you could adapt it for TCP packets by adding the related struct and Net::load() template like the one below.

(Second example below) You didn't specify a target language in the question, but if you're looking for C, then you might use #pragma pack on the structures and then cast the pointer+offset as a pointer to the struct and then call ntohs/ntohl on the appropriate fields. Doing that is probably the fastest solution, but it relies on #pragma pack, which is not standard.

C++11 style

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;
  }
}

Client code in packet capture handler:

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);

alternative (C style):

(Sorry for any errors. I typed this from memory, and didn't try to compile or run--but I think you will understand the basic idea):

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)

client code in packet handler:

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 Header Notes

according to netinet/tcp.h, the TCP header is roughly:

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;

Load this struct into memory using whichever method you prefer and don't forget to fix the byte ordering (ntohs/ntohl) for multi-byte integer types as above.

The TCP options follow, which cannot be loaded into a struct like this. See the section about TCP options at this link. For MSS, you will want to parse each option until you find option with kind == 2. Building on the C example above:

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;
} 
like image 53
15 revs, 3 users 96% Avatar answered Oct 21 '22 04:10

15 revs, 3 users 96%