PCAP parsing evaluation - C source

20 Aug 2024

/*
* parse.h
*
* Copyright (c) 2024 Fabian Bißmann
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

typedef struct {
  uint32_t magic;
  uint16_t major_version;
  uint16_t minor_version;
  uint32_t reserved1;
  uint32_t reserved2;
  uint32_t snap_len;
  uint32_t link_type;
} pcap_header;

typedef struct {
  uint32_t timestamp_s;
  uint32_t timestamp_ms;
  uint32_t cap_len;
  uint32_t orig_len;
} pcap_record;

typedef struct {
  uint16_t sll_family;
  uint16_t sll_protocol;
  uint32_t sll_ifindex;
  uint16_t sll_hatype;
  unsigned char sll_pkttype;
  unsigned char sll_halen;
  unsigned char sll_addr[8];
} sockaddr_ll;

typedef struct {
  unsigned char version;
  unsigned char dsfield;
  uint16_t len;
  uint16_t id;
  uint16_t flags;
  uint16_t ttl;
  unsigned char proto;
  uint16_t checksum;
  uint32_t s_addr;
  uint32_t d_addr;
} ipv4;

typedef struct {
  uint8_t type;
  uint8_t code;
  uint16_t checksum;
  uint16_t identifier;
  uint16_t sequence_nr;
} icmp_echo_reply;

// parser functions
int parse_pcap_header(pcap_header *pkg_struct, FILE *fp);
void parse_pcap_record(pcap_record *pkg_struct, FILE *fp);
int parse_sockaddr_ll(sockaddr_ll *pkg_struct, unsigned char *data);
int parse_ipv4(ipv4 *pkg_struct, unsigned char *data);
int parse_icmp_echo_reply(icmp_echo_reply *pkg_struct, unsigned char *data);

// print functions
void print_pcap_head(pcap_header *pkg_struct);
void print_pcap_record_meta(pcap_record *pkg_struct);
void print_sll(sockaddr_ll *pkg_struct);
void print_ipv4(ipv4 *pkg_struct);
void print_icmp4(icmp_echo_reply *pkg_struct);

// util functions
void hex_dump(unsigned char *buf, uint32_t cap_len);
void fmt_mac_address(unsigned char *buf, unsigned char *hex_data);
void fmt_pcap_timestamp_s(unsigned char *buf, uint32_t timestamp_s);
/*
* parse.c
*
* Copyright (c) 2024 Fabian Bißmann
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>

#include "parse.h"

int parse_pcap_header(pcap_header *pkg_struct, FILE *fp) {
  fread(&(*pkg_struct).magic, 4, 1, fp);
  fread(&(*pkg_struct).major_version, 2, 1, fp);
  fread(&(*pkg_struct).minor_version, 2, 1, fp);
  fread(&(*pkg_struct).reserved1, 4, 1, fp);
  fread(&(*pkg_struct).reserved2, 4, 1, fp);
  fread(&(*pkg_struct).snap_len, 4, 1, fp);
  fread(&(*pkg_struct).link_type, 4, 1, fp);

  pkg_struct->snap_len = __builtin_bswap32(pkg_struct->snap_len);

  if ((pkg_struct->magic == 0xa1b2c3d4) ||
      (pkg_struct->magic == 0xa1b23c4d))
    return true;
  return false;
}

void parse_pcap_record(pcap_record *pkg_struct, FILE *fp) {
  fread(&(*pkg_struct).timestamp_s, 4, 1, fp);
  fread(&(*pkg_struct).timestamp_ms, 4, 1, fp);
  fread(&(*pkg_struct).cap_len, 4, 1, fp);
  fread(&(*pkg_struct).orig_len, 4, 1, fp);
}

int parse_sockaddr_ll(sockaddr_ll *pkg_struct, unsigned char *data) {
  char *data_pos = data;

  memcpy(&(*pkg_struct).sll_family, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).sll_protocol, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).sll_ifindex, data_pos, 4);
  data_pos += 4;
  memcpy(&(*pkg_struct).sll_hatype, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).sll_pkttype, data_pos, 1);
  data_pos ++;
  memcpy(&(*pkg_struct).sll_halen, data_pos, 1);
  data_pos ++;
  memcpy(&(*pkg_struct).sll_addr, data_pos, 8);

  pkg_struct->sll_family = __builtin_bswap16(pkg_struct->sll_family);
  pkg_struct->sll_protocol = __builtin_bswap16(pkg_struct->sll_protocol);
  pkg_struct->sll_ifindex = __builtin_bswap32(pkg_struct->sll_ifindex);
  pkg_struct->sll_hatype = __builtin_bswap16(pkg_struct->sll_hatype);

  return 20;
}

int parse_ipv4(ipv4 *pkg_struct, unsigned char *data) {
  char *data_pos = data;

  memcpy(&(*pkg_struct).version, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).dsfield, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).len, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).id, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).flags, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).ttl, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).proto, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).checksum, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).s_addr, data_pos, 4);
  data_pos += 4;
  memcpy(&(*pkg_struct).d_addr, data_pos, 4);

  pkg_struct->len = __builtin_bswap16(pkg_struct->len);
  pkg_struct->id = __builtin_bswap16(pkg_struct->id);
  pkg_struct->flags = __builtin_bswap16(pkg_struct->flags);
  pkg_struct->checksum = __builtin_bswap16(pkg_struct->checksum);
  pkg_struct->d_addr = __builtin_bswap32(pkg_struct->d_addr);
  pkg_struct->s_addr = __builtin_bswap32(pkg_struct->s_addr);

  return 20;
}

int parse_icmp_echo_reply(icmp_echo_reply *pkg_struct, unsigned char *data) {
  char *data_pos = data;

  memcpy(&(*pkg_struct).type, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).code, data_pos, 1);
  data_pos++;
  memcpy(&(*pkg_struct).checksum, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).identifier, data_pos, 2);
  data_pos += 2;
  memcpy(&(*pkg_struct).sequence_nr, data_pos, 2);

  pkg_struct->checksum = __builtin_bswap16(pkg_struct->checksum);
  pkg_struct->identifier = __builtin_bswap16(pkg_struct->identifier);
  pkg_struct->sequence_nr = __builtin_bswap16(pkg_struct->sequence_nr);

  return 8;
}

void fmt_mac_address(unsigned char *buf, unsigned char *hex_data) {
  snprintf(buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", hex_data[0], hex_data[1],
      hex_data[2], hex_data[3], hex_data[4], hex_data[5]);
}

void fmt_pcap_timestamp_s(unsigned char *buf, uint32_t timestamp_s) {
  time_t ts_s = timestamp_s; 
  struct tm ts = *localtime(&ts_s);
  if (!strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &ts))
    puts("error while converting timestamp.");
}

void print_pcap_head(pcap_header *pkg_struct) {
  printf("##### parsed pcap header #####\n");
  printf("magic | major | minor | r1 | r2 | snap_len | link_type\n");
  printf("%x | %x | %x | %x | %x | %x | %x\n",
      pkg_struct->magic,
      pkg_struct->major_version,
      pkg_struct->minor_version,
      pkg_struct->reserved1,
      pkg_struct->reserved2,
      pkg_struct->snap_len,
      pkg_struct->link_type);
  printf("##### parsed pcap header #####\n");
}

void print_pcap_record_meta(pcap_record *pkg_struct) {
  printf("#### begin pcap_record #####\n");
  printf("s, cap_len: %i, %i\n", pkg_struct->timestamp_s, pkg_struct->cap_len);
}

void hex_dump(unsigned char *buf, uint32_t cap_len) {
  printf("##### hex dump #####\n");
  for(int i=0; i<cap_len; i++) {
    printf("%02x", buf[i]);
    if ((i+1) % 4 == 0) {
      if ((i+1) % 32 == 0) {
        printf("\n");
      }
      else {
        printf(" ");
      }
    }
  }
  printf("\n");
  printf("##### hex dump #####\n");
}

void print_sll(sockaddr_ll *pkg_struct) {
  printf("##### sll data #####\n");
  printf("family | protocol | ifindex | hatype | pkttype | halen\n");
  printf("%x | %x | %x | %x | %x | %x\n", pkg_struct->sll_family,
                                          pkg_struct->sll_protocol,
                                          pkg_struct->sll_ifindex,
                                          pkg_struct->sll_hatype,
                                          pkg_struct->sll_pkttype,
                                          pkg_struct->sll_halen);
  unsigned char mac_address[19] = {0};
  fmt_mac_address(mac_address, pkg_struct->sll_addr);
  printf("addr: %s\n", mac_address);
  printf("##### sll data #####\n");
}

void print_ipv4(ipv4 *pkg_struct) {
  printf("##### ipv4 data #####\n");
  printf("version | dsfield | len | id | flags | ttl | proto | checksum | s_addr | d_addr\n");
  printf("%x | %x | %x | %x | %x | %x | %x | %x | %x | %x\n",
      pkg_struct->version,
      pkg_struct->dsfield,
      pkg_struct->len,
      pkg_struct->id,
      pkg_struct->flags,
      pkg_struct->ttl,
      pkg_struct->proto,
      pkg_struct->checksum,
      pkg_struct->s_addr,
      pkg_struct->d_addr);
  printf("##### ipv4 data #####\n");
}

void print_icmp4(icmp_echo_reply *pkg_struct) {
  // type: 8 = echo, 0 = echo reply
  printf("##### icmp echo reply data #####\n");
  printf("type | code | checksum |  identifier | sequence number\n");
  printf("%x | %x | %x | %x | %x\n", pkg_struct->type, 
                                     pkg_struct->code,
                                     pkg_struct->checksum,
                                     pkg_struct->identifier,
                                     pkg_struct->sequence_nr);
  printf("##### icmp echo reply data #####\n");
}

static FILE *fp;
void free_all() {
  fclose(fp);
}

int main() {
  atexit(free_all);
  fp = fopen("./dump.pcap", "r");
  char ch;

  pcap_header pkg_head;
  pcap_record pkg_record;
  if (!parse_pcap_header(&pkg_head, fp))
    exit(EXIT_FAILURE);

  parse_pcap_record(&pkg_record, fp);
  print_pcap_head(&pkg_head);
  print_pcap_record_meta(&pkg_record);

  // alloc char array with cap_len
  unsigned char *packet = malloc(sizeof(char)*pkg_record.cap_len);
  unsigned char *packet_orig = packet;
  fread(packet, pkg_record.cap_len, 1, fp);
  hex_dump(packet, pkg_record.cap_len);

  unsigned char buf[20] = {0};
  fmt_pcap_timestamp_s(buf, pkg_record.timestamp_s);
  puts(buf);

  int bytes_parsed = 0;
  int bytes_read;
  sockaddr_ll pkg_sockaddr_ll;
  ipv4 pck_ipv4;
  icmp_echo_reply pkg_icmp_echo;

  bytes_read = parse_sockaddr_ll(&pkg_sockaddr_ll, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);

  print_sll(&pkg_sockaddr_ll);

  bytes_read = parse_ipv4(&pck_ipv4, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);
  print_ipv4(&pck_ipv4);

  bytes_read = parse_icmp_echo_reply(&pkg_icmp_echo, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);
  print_icmp4(&pkg_icmp_echo);

  printf("\n\n\n");
  puts("New packet:");
  parse_pcap_record(&pkg_record, fp);
  print_pcap_record_meta(&pkg_record);
  free(packet_orig);
  packet = malloc(sizeof(char)*pkg_record.cap_len);
  packet_orig = packet;

  fread(packet, pkg_record.cap_len, 1, fp);
  hex_dump(packet, pkg_record.cap_len);

  memset(buf, 0, sizeof(buf));
  fmt_pcap_timestamp_s(buf, pkg_record.timestamp_s);
  puts(buf);

  bytes_parsed = 0;
  bytes_read = parse_sockaddr_ll(&pkg_sockaddr_ll, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);

  print_sll(&pkg_sockaddr_ll);

  bytes_read = parse_ipv4(&pck_ipv4, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);
  print_ipv4(&pck_ipv4);

  bytes_read = parse_icmp_echo_reply(&pkg_icmp_echo, packet);
  bytes_parsed += bytes_read;
  if (bytes_parsed < pkg_record.cap_len)
    packet = &packet[bytes_read];
  else
    exit(EXIT_FAILURE);
  print_icmp4(&pkg_icmp_echo);

  exit(0);
}