C语言 - 将结构体写入文件 (.pcap)

10

我正在尝试编写一个可以在Wireshark中使用的.pcap文件。为此,我有一些包含各种数据类型的结构体需要写入文件(见代码)。

所以,我创建了结构体实例,填充了数据,使用FILE *fp = fopen("test.pcap","w"),然后我不确定如何正确地将其写入文件。我相信我应该使用memcpy,但我不确定最佳方法是什么。过去,我大多数情况下都依赖于C++库来完成这项工作。有什么建议吗?

typedef struct pcap_hdr_s {
        uint32_t magic_number;   /* magic number */
        uint16_t version_major;  /* major version number */
        uint16_t version_minor;  /* minor version number */
        int32_t  thiszone;       /* GMT to local correction */
        uint32_t sigfigs;        /* accuracy of timestamps */
        uint32_t snaplen;        /* max length of captured packets, in octets */
        uint32_t network;        /* data link type */
} pcap_hdr_t;

typedef struct pcaprec_hdr_s {
   uint32_t ts_sec;         /* timestamp seconds */
   uint32_t ts_usec;        /* timestamp microseconds */
   uint32_t incl_len;       /* number of octets of packet saved in file */
   uint32_t orig_len;       /* actual length of packet */
} pcaprec_hdr_t;

typedef struct ethernet_hdr_s {
   uint8_t dst[6];    /* destination host address */
   uint8_t src[6];    /* source host address */
   uint16_t type;     /* IP? ARP? RARP? etc */
} ethernet_hdr_t;

typedef struct ip_hdr_s {
   uint8_t  ip_hl:4, /* both fields are 4 bits */
            ip_v:4;
   uint8_t        ip_tos;
   uint16_t       ip_len;
   uint16_t       ip_id;
   uint16_t       ip_off;
   uint8_t        ip_ttl;
   uint8_t        ip_p;
   uint16_t       ip_sum;
   uint32_t ip_src;
   uint32_t ip_dst;
}ip_hdr_t;

typedef struct udp_header
{
  uint16_t src;
  uint16_t dst;
  uint16_t length;
  uint16_t checksum;
} udp_header_t;

小心字节顺序--我认为捕获的数据包总是使用网络字节顺序,但您可能需要检查标题的字节顺序。如果文件格式使用“magic_number”字段来确定标题的字节顺序,则可能不需要担心。 - tomlogic
魔数让你可以使用大端或小端编写,甚至可以在每个数据包之后来回切换。魔数还可以提醒你文件传输不当所造成的损坏。 - Seth
1
魔数并不能让你按大端或小端顺序写出数据包数据;你必须按照它在网络上出现的顺序来写,对于以太网、IP、TCP和UDP多字节整数字段而言,这个顺序是大端。它确实可以让你按照文件和记录头中的字段在本地字节顺序下写出,但最好还是让libpcap/WinPcap来完成这项工作。不幸的是,例如在Windows和UN*X之间以ASCII模式FTP文件并不会影响魔数。 - user862787
3个回答

16
使用libpcap或WinPcap - pcap_open_dead()获取一个“虚假”的pcap_t,用于与pcap_dump_open()一起使用以指定链路层头类型(对于以太网,请使用DLT_EN10MB)和快照长度(使用65535),pcap_dump_open()打开要写入的文件,pcap_dump()写出数据包,pcap_dump_close()关闭文件。比直接使用fopen()fwrite()fclose()(这是libpcap/WinPcap在“底层”使用的内容)要容易得多。

是的,你必须正确获取数据包中的字节顺序。字节顺序取决于协议;对于以太网头中的type字段以及IP、TCP和UDP头中的所有多字节字段,它们必须按照大端序排列。(pcap文件中的魔数与此无关-它仅指示文件头和每个数据包记录头中字段的字节顺序,而不是数据包中字段的字节顺序,以及由于在Linux中实现方式的原因,在Linux USB捕获的数据包开头的元数据。数据包数据应该看起来完全像“在线”上一样。)


1
你能分享一些“更多的代码”吗?我的意思是写一个例子吗? - Kyslik

4
使用fwrite()函数。你需要检查一下,但我认为.pcap文件是以二进制模式写入的。
例子:
pcaprec_hdr_t pcaprec_hdr;
// fill pcaprec_hdr with valid info

FILE* pFile = NULL;
pFile = fopen ("myfile.pcap" , "wb"); // open for writing in binary mode

fwrite (&pcaprec_hdr, 1, sizeof(pcaprec_hdr_t) , pFile);

fclose(pFile);

1
-1 是因为忘记编写文件头并且不仅仅是说“使用libpcap/WinPcap,他们已经知道如何编写文件头和记录头”。 - user862787
你假设他们已经知道了。但是他们真的知道吗?将这个答案选为官方答案表明了另外一件事情。 - karlphillip
2
你认为他们已经知道了。但是他们真的知道吗?是的,他们知道。他们是该文件格式的第一个实现者。 - user862787
我遇到了一些问题,你能否编辑你的答案并提供“完整”的示例?当我在Wireshark中打开我的pcap文件时,出现错误提示:“捕获文件似乎在数据包中间被截断。”而且我只能看到“帧”行。谢谢。 - Kyslik

4
这是我的理解,盖伊·哈里斯所建议的是。因此,根据Kyslik的请求,我们有:

以下是需要翻译的内容:

#include <libpcap/pcap.h>

/* Ethernet/IP/SCTP INIT chunk */
static const unsigned char pkt1[82] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ........ */
  0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x45, 0x00, /* ......E. */
  0x00, 0x44, 0x55, 0xb1, 0x00, 0x00, 0x40, 0x84, /* .DU...@. */
  0x26, 0x83, 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, /* &....... */
  0x00, 0x01, 0x00, 0x01, 0x1f, 0x90, 0x00, 0x00, /* ........ */
  0x00, 0x00, 0x68, 0xe5, 0x88, 0x1b, 0x01, 0x00, /* ..h..... */
  0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, /* .$...... */
  0xa0, 0x00, 0x00, 0x04, 0xff, 0xff, 0x00, 0x00, /* ........ */
  0x16, 0x2e, 0x80, 0x00, 0x00, 0x04, 0xc0, 0x00, /* ........ */
  0x00, 0x04, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x05, /* ........ */
  0x00, 0x00                                      /* .. */
};


int main(int argc, char *argv[]) {

  pcap_t *handle = pcap_open_dead(DLT_EN10MB, 1 << 16);
  pcap_dumper_t *dumper = pcap_dump_open(handle, "/tmp/pktcap/cap.pcap");

  struct pcap_pkthdr pcap_hdr;
  pcap_hdr.caplen = sizeof(pkt1);
  pcap_hdr.len = pcap_hdr.caplen;

  pcap_dump((u_char *)dumper, &pcap_hdr, pkt1);
  pcap_dump_close(dumper);

  return 0;
}

我不得不将 pcap_pkthdr pcap_hdr; 修改为 struct pcap_pkthdr pcap_hdr;,以便在 gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609 上编译通过。 - Jaakko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接