ITCHCPP 1.6.1
High-Performance NASDAQ TotalView-ITCH 5.0 Parser & Market-Data Platform
Loading...
Searching...
No Matches
pcap.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstring>
5#include <fstream>
6#include <utility>
7
8namespace itch::transport {
9
10namespace {
11
12// Capture-file record fields follow the file's own byte order; network protocol
13// headers (Ethernet/IP/UDP) are always big-endian. These helpers read either.
14template <typename UintType>
15auto read_le_or_be(std::span<const std::byte> data, std::size_t offset, bool swapped) -> UintType {
16 UintType value {};
17 std::memcpy(&value, data.data() + offset, sizeof(UintType));
18 return swapped ? utils::swap_bytes(value) : value;
19}
20
21template <typename UintType>
22auto read_net(std::span<const std::byte> data, std::size_t offset) -> UintType {
23 UintType value {};
24 std::memcpy(&value, data.data() + offset, sizeof(UintType));
25 return utils::from_big_endian(value);
26}
27
28// Link-layer types we know how to unwrap (subset of the tcpdump/pcap registry).
29constexpr std::uint32_t LINKTYPE_ETHERNET = 1;
30constexpr std::uint32_t LINKTYPE_RAW = 101;
31constexpr std::uint32_t LINKTYPE_LINUX_SLL = 113;
32
33constexpr std::uint16_t ETHERTYPE_IPV4 = 0x0800;
34constexpr std::uint16_t ETHERTYPE_IPV6 = 0x86DD;
35constexpr std::uint16_t ETHERTYPE_VLAN = 0x8100;
36
37constexpr std::uint8_t IP_PROTOCOL_UDP = 17;
38
39constexpr std::size_t ETHERNET_HEADER_SIZE = 14;
40constexpr std::size_t VLAN_TAG_SIZE = 4;
41constexpr std::size_t LINUX_SLL_HEADER_SIZE = 16;
42constexpr std::size_t IPV6_HEADER_SIZE = 40;
43constexpr std::size_t UDP_HEADER_SIZE = 8;
44
45constexpr std::uint32_t PCAP_MAGIC = 0xA1B2C3D4;
46constexpr std::uint32_t PCAP_MAGIC_SWAPPED = 0xD4C3B2A1;
47constexpr std::uint32_t PCAP_MAGIC_NANO = 0xA1B23C4D;
48constexpr std::uint32_t PCAP_MAGIC_NANO_SWP = 0x4D3CB2A1;
49constexpr std::uint32_t PCAPNG_BLOCK_SHB = 0x0A0D0D0A;
50
51// Strips a frame's link-layer header down to the network (IP) layer, or
52// returns std::nullopt if the frame is too short or its link type isn't one
53// we know how to unwrap.
54auto strip_link_layer(std::span<const std::byte> frame, std::uint32_t link_type)
55 -> std::optional<std::span<const std::byte>> {
56 if (link_type == LINKTYPE_ETHERNET) {
57 if (frame.size() < ETHERNET_HEADER_SIZE) {
58 return std::nullopt;
59 }
60 std::size_t header = ETHERNET_HEADER_SIZE;
61 auto ethertype = read_net<std::uint16_t>(frame, 12);
62 while (ethertype == ETHERTYPE_VLAN && frame.size() >= header + VLAN_TAG_SIZE) {
63 ethertype = read_net<std::uint16_t>(frame, header + 2);
64 header += VLAN_TAG_SIZE;
65 }
66 if (ethertype != ETHERTYPE_IPV4 && ethertype != ETHERTYPE_IPV6) {
67 return std::nullopt;
68 }
69 return frame.subspan(header);
70 }
71 if (link_type == LINKTYPE_LINUX_SLL) {
72 if (frame.size() < LINUX_SLL_HEADER_SIZE) {
73 return std::nullopt;
74 }
75 return frame.subspan(LINUX_SLL_HEADER_SIZE);
76 }
77 if (link_type != LINKTYPE_RAW) {
78 return std::nullopt; // Unsupported link layer.
79 }
80 return frame;
81}
82
83} // namespace
84
85PcapReader::PcapReader(MessageCallback callback) : m_mold {std::move(callback)} {}
86
87auto PcapReader::read(std::span<const std::byte> capture) -> bool {
88 if (capture.size() < sizeof(std::uint32_t)) {
89 return false;
90 }
91 std::uint32_t magic {};
92 std::memcpy(&magic, capture.data(), sizeof(magic));
93
94 if (magic == PCAP_MAGIC || magic == PCAP_MAGIC_NANO) {
95 return read_classic_pcap(capture, /*swapped=*/false);
96 }
97 if (magic == PCAP_MAGIC_SWAPPED || magic == PCAP_MAGIC_NANO_SWP) {
98 return read_classic_pcap(capture, /*swapped=*/true);
99 }
100 if (magic == PCAPNG_BLOCK_SHB) {
101 return read_pcapng(capture);
102 }
103 return false;
104}
105
106auto PcapReader::read_file(const std::string& path) -> bool {
107 std::ifstream file {path, std::ios::binary};
108 if (!file) {
109 return false;
110 }
111 std::vector<char> raw {std::istreambuf_iterator<char> {file}, {}};
112 std::vector<std::byte> bytes(raw.size());
113 std::memcpy(bytes.data(), raw.data(), raw.size());
114 return read(std::span<const std::byte> {bytes});
115}
116
117auto PcapReader::read_classic_pcap(std::span<const std::byte> capture, bool swapped) -> bool {
118 constexpr std::size_t GLOBAL_HEADER_SIZE = 24;
119 constexpr std::size_t NETWORK_OFFSET = 20;
120 constexpr std::size_t RECORD_HEADER_SIZE = 16;
121 constexpr std::size_t INCL_LEN_OFFSET = 8;
122
123 if (capture.size() < GLOBAL_HEADER_SIZE) {
124 return false;
125 }
126 const auto link_type = read_le_or_be<std::uint32_t>(capture, NETWORK_OFFSET, swapped);
127
128 std::size_t offset = GLOBAL_HEADER_SIZE;
129 while (offset + RECORD_HEADER_SIZE <= capture.size()) {
130 const auto incl_len =
131 read_le_or_be<std::uint32_t>(capture, offset + INCL_LEN_OFFSET, swapped);
132 offset += RECORD_HEADER_SIZE;
133 if (offset + incl_len > capture.size()) {
134 break; // Truncated final record.
135 }
136 handle_frame(capture.subspan(offset, incl_len), link_type);
137 offset += incl_len;
138 }
139 return true;
140}
141
142auto PcapReader::read_pcapng(std::span<const std::byte> capture) -> bool {
143 constexpr std::size_t BLOCK_HEADER_SIZE = 8; // Type + total length.
144 constexpr std::size_t BOM_OFFSET = 8; // Byte-order magic in the SHB.
145 constexpr std::uint32_t BOM_LITTLE = 0x1A2B3C4D;
146 constexpr std::uint32_t BLOCK_IDB = 0x00000001;
147 constexpr std::uint32_t BLOCK_SPB = 0x00000003;
148 constexpr std::uint32_t BLOCK_EPB = 0x00000006;
149
150 bool swapped = false;
151 std::vector<std::uint32_t> interface_link_types;
152
153 std::size_t offset = 0;
154 while (offset + BLOCK_HEADER_SIZE <= capture.size()) {
155 std::uint32_t block_type {};
156 std::memcpy(&block_type, capture.data() + offset, sizeof(block_type));
157 // The SHB's byte-order magic tells us how to read this section. Until we
158 // have read it, block lengths are interpreted in host order, which is
159 // correct for the SHB type/length fields we need to bootstrap from.
160 const bool block_swapped = (block_type == PCAPNG_BLOCK_SHB) ? false : swapped;
161
162 const auto total_length =
163 read_le_or_be<std::uint32_t>(capture, offset + sizeof(std::uint32_t), block_swapped);
164 if (total_length < BLOCK_HEADER_SIZE + sizeof(std::uint32_t) ||
165 offset + total_length > capture.size()) {
166 break;
167 }
168
169 if (block_type == PCAPNG_BLOCK_SHB) {
170 const auto bom = read_net<std::uint32_t>(capture, offset + BOM_OFFSET);
171 // If the byte-order magic reads as the canonical value in network
172 // (big-endian) interpretation, the file is big-endian; otherwise it
173 // is little-endian. Compare against both forms.
174 std::uint32_t bom_native {};
175 std::memcpy(&bom_native, capture.data() + offset + BOM_OFFSET, sizeof(bom_native));
176 swapped = (bom_native != BOM_LITTLE);
177 (void)bom;
178 interface_link_types.clear();
179 } else if (block_type == BLOCK_IDB) {
180 // LinkType is the first 2-byte field of the IDB body.
181 const auto link_type =
182 read_le_or_be<std::uint16_t>(capture, offset + BLOCK_HEADER_SIZE, swapped);
183 interface_link_types.push_back(link_type);
184 } else if (block_type == BLOCK_EPB) {
185 constexpr std::size_t EPB_IFACE_OFFSET = BLOCK_HEADER_SIZE;
186 constexpr std::size_t EPB_CAPLEN_OFFSET = BLOCK_HEADER_SIZE + 12;
187 constexpr std::size_t EPB_DATA_OFFSET = BLOCK_HEADER_SIZE + 20;
188 // The block must be large enough to hold its fixed header fields
189 // (interface id, two timestamp words, captured/packet length) plus
190 // the trailing repeated total-length word before those fields can
191 // be read; total_length alone (checked above) only guarantees the
192 // block fits in the buffer, not that it's this shape.
193 if (total_length >= EPB_DATA_OFFSET + sizeof(std::uint32_t)) {
194 const auto iface =
195 read_le_or_be<std::uint32_t>(capture, offset + EPB_IFACE_OFFSET, swapped);
196 const auto cap_len =
197 read_le_or_be<std::uint32_t>(capture, offset + EPB_CAPLEN_OFFSET, swapped);
198 if (offset + EPB_DATA_OFFSET + cap_len <= capture.size() &&
199 iface < interface_link_types.size()) {
200 handle_frame(
201 capture.subspan(offset + EPB_DATA_OFFSET, cap_len),
202 interface_link_types[iface]
203 );
204 }
205 }
206 } else if (block_type == BLOCK_SPB) {
207 constexpr std::size_t SPB_DATA_OFFSET = BLOCK_HEADER_SIZE + 4;
208 // The SPB has no captured length, so derive it from the block size.
209 if (total_length >= SPB_DATA_OFFSET + sizeof(std::uint32_t) &&
210 !interface_link_types.empty()) {
211 const std::size_t data_len = total_length - SPB_DATA_OFFSET - sizeof(std::uint32_t);
212 handle_frame(
213 capture.subspan(offset + SPB_DATA_OFFSET, data_len),
214 interface_link_types.front()
215 );
216 }
217 }
218
219 offset += total_length;
220 }
221 return true;
222}
223
224auto PcapReader::handle_frame(std::span<const std::byte> frame, std::uint32_t link_type) -> void {
225 const auto network = strip_link_layer(frame, link_type);
226 if (!network.has_value()) {
227 return;
228 }
229 const auto payload = extract_udp_payload(*network);
230 if (!payload.has_value()) {
231 return;
232 }
233 ++m_udp_datagrams;
234 m_mold.decode_packet(*payload);
235}
236
237auto PcapReader::extract_udp_payload(std::span<const std::byte> network
238) const -> std::optional<std::span<const std::byte>> {
239 if (network.empty()) {
240 return std::nullopt;
241 }
242
243 // Determine IP version and locate the UDP header.
244 const auto version = static_cast<std::uint8_t>(network[0]) >> 4;
245 std::uint8_t protocol {0};
246 std::size_t ip_header_len {0};
247 if (version == 4) {
248 ip_header_len = static_cast<std::size_t>(static_cast<std::uint8_t>(network[0]) & 0x0F) * 4U;
249 if (network.size() < ip_header_len || ip_header_len < 20) {
250 return std::nullopt;
251 }
252 protocol = static_cast<std::uint8_t>(network[9]);
253 } else if (version == 6) {
254 if (network.size() < IPV6_HEADER_SIZE) {
255 return std::nullopt;
256 }
257 protocol = static_cast<std::uint8_t>(network[6]);
258 ip_header_len = IPV6_HEADER_SIZE;
259 } else {
260 return std::nullopt;
261 }
262 if (protocol != IP_PROTOCOL_UDP) {
263 return std::nullopt;
264 }
265
266 const std::span<const std::byte> udp = network.subspan(ip_header_len);
267 if (udp.size() < UDP_HEADER_SIZE) {
268 return std::nullopt;
269 }
270 const auto dst_port = read_net<std::uint16_t>(udp, 2);
271 if (m_port_filter.has_value() && dst_port != *m_port_filter) {
272 return std::nullopt;
273 }
274 const auto udp_length = read_net<std::uint16_t>(udp, 4);
275 std::size_t payload_len = udp.size() - UDP_HEADER_SIZE;
276 if (udp_length >= UDP_HEADER_SIZE) {
277 payload_len = std::min<std::size_t>(payload_len, udp_length - UDP_HEADER_SIZE);
278 }
279 return udp.subspan(UDP_HEADER_SIZE, payload_len);
280}
281
282} // namespace itch::transport
PcapReader(MessageCallback callback)
Constructs a reader that forwards each decoded ITCH message to callback.
Definition pcap.cpp:85
auto read_file(const std::string &path) -> bool
Reads and decodes a capture file from disk.
Definition pcap.cpp:106
auto read(std::span< const std::byte > capture) -> bool
Decodes an in-memory capture buffer.
Definition pcap.cpp:87
std::function< void(const Message &)> MessageCallback
The signature for the callback function used in streaming parse methods.
Definition parser.hpp:40
In-house reader for classic pcap and pcapng capture files carrying MoldUDP64/ITCH traffic.