#include #include #include #include #include "packeteer/summarize.hpp" namespace { // Ethernet + IPv4 + UDP + DNS query for "example.com", assembled the // same way the real capture path hands bytes to summarize_packet: one // contiguous frame, no struct-casting. std::vector ethernet_ipv4_udp_dns_frame() { std::vector dns = { 0x12, 0x9d, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0, 0x00, 0x01, 0x00, 0x01, }; std::vector udp(8, 0); udp[0] = 0xD4; udp[1] = 0x31; // src port 54321 udp[2] = 0x00; udp[3] = 0x35; // dst port 53 std::uint16_t udp_len = static_cast(8 + dns.size()); udp[4] = static_cast(udp_len >> 8); udp[5] = static_cast(udp_len & 0xFF); std::vector ip(20, 0); ip[0] = 0x45; ip[8] = 64; // ttl ip[9] = packeteer::net::kProtoUdp; // proto ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; // src 10.0.0.1 ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; // dst 10.0.0.2 std::vector eth = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac 0x08, 0x00, // ethertype IPv4 }; std::vector frame = eth; frame.insert(frame.end(), ip.begin(), ip.end()); frame.insert(frame.end(), udp.begin(), udp.end()); frame.insert(frame.end(), dns.begin(), dns.end()); return frame; } // Ethernet + IPv4 + TCP + an HTTP GET request. This is the only test // exercising L7Registry's TCP-payload path with a real registered // dissector - DNS only ever runs over UDP, so summarize_packet's TCP // branch calling into l7_summarize() was otherwise unverified. std::vector ethernet_ipv4_tcp_http_frame() { std::string_view request = "GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n"; std::vector http(request.begin(), request.end()); std::vector tcp(20, 0); tcp[0] = 0xC3; tcp[1] = 0x50; // src port 50000 tcp[2] = 0x00; tcp[3] = 0x50; // dst port 80 tcp[12] = 5 << 4; // data_offset = 5 (20-byte header) tcp[13] = 0x18; // PSH | ACK std::vector ip(20, 0); ip[0] = 0x45; ip[8] = 64; // ttl ip[9] = packeteer::net::kProtoTcp; // proto ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; // src 10.0.0.1 ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; // dst 10.0.0.2 std::vector eth = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac 0x08, 0x00, // ethertype IPv4 }; std::vector frame = eth; frame.insert(frame.end(), ip.begin(), ip.end()); frame.insert(frame.end(), tcp.begin(), tcp.end()); frame.insert(frame.end(), http.begin(), http.end()); return frame; } // Ethernet + IPv6 + a Hop-by-Hop Options extension header + TCP. Proves // walk_ipv6_extension_headers() is actually wired into summarize_packet's // IPv6 branch, not just unit-tested in isolation - without it, this // packet's TCP layer (and any L7 behind it) would be silently invisible. std::vector ethernet_ipv6_hopbyhop_tcp_frame() { std::vector tcp(20, 0); tcp[0] = 0x00; tcp[1] = 0x50; // src port 80 tcp[2] = 0x00; tcp[3] = 0x51; // dst port 81 tcp[12] = 5 << 4; // data_offset = 5 tcp[13] = 0x02; // SYN std::vector hop_by_hop = { static_cast(packeteer::net::kProtoTcp), 0x00, // hdr_ext_len = 0 -> total length (0+1)*8 = 8 bytes 0, 0, 0, 0, 0, 0, // option padding }; std::vector ip6(40, 0); ip6[0] = 0x60; // version 6 std::uint16_t payload_len = static_cast(hop_by_hop.size() + tcp.size()); ip6[4] = static_cast(payload_len >> 8); ip6[5] = static_cast(payload_len & 0xFF); ip6[6] = packeteer::net::kNextHeaderHopByHop; ip6[7] = 64; // hop_limit ip6[23] = 0x01; // src = ::1 ip6[39] = 0x01; // dst = ::1 std::vector eth = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, // dst mac 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // src mac 0x86, 0xDD, // ethertype IPv6 }; std::vector frame = eth; frame.insert(frame.end(), ip6.begin(), ip6.end()); frame.insert(frame.end(), hop_by_hop.begin(), hop_by_hop.end()); frame.insert(frame.end(), tcp.begin(), tcp.end()); return frame; } } // namespace TEST_CASE("summarize_packet walks a Hop-by-Hop extension header to reach TCP") { auto line = packeteer::summarize_packet(ethernet_ipv6_hopbyhop_tcp_frame(), DLT_EN10MB); CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x86dd" " | IPv6 ::1 -> ::1 ttl=64 proto=6" " | TCP 80 -> 81 [S] seq=0 ack=0 win=0"); } TEST_CASE("summarize_packet decodes a full Ethernet/IPv4/TCP/HTTP frame end to end") { auto line = packeteer::summarize_packet(ethernet_ipv4_tcp_http_frame(), DLT_EN10MB); CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0800" " | IPv4 10.0.0.1 -> 10.0.0.2 ttl=64 proto=6" " | TCP 50000 -> 80 [AP] seq=0 ack=0 win=0" " | HTTP GET /index.html Host: example.com"); } TEST_CASE("summarize_packet decodes a full Ethernet/IPv4/UDP/DNS frame end to end") { auto line = packeteer::summarize_packet(ethernet_ipv4_udp_dns_frame(), DLT_EN10MB); CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0800" " | IPv4 10.0.0.1 -> 10.0.0.2 ttl=64 proto=17" " | UDP 54321 -> 53 len=37" " | DNS query id=4765 example.com type=1"); } TEST_CASE("summarize_packet on DLT_RAW skips the Ethernet line entirely") { auto frame = ethernet_ipv4_udp_dns_frame(); std::vector raw(frame.begin() + packeteer::net::kEthernetHeaderLen, frame.end()); auto line = packeteer::summarize_packet(raw, DLT_RAW); CHECK(line.substr(0, 3) == "RAW"); CHECK(line.find("ETH") == std::string::npos); CHECK(line.find("IPv4 10.0.0.1 -> 10.0.0.2") != std::string::npos); } TEST_CASE("summarize_packet reports a truncated Ethernet frame without decoding further") { std::vector bytes(10, 0); // shorter than the 14-byte header auto line = packeteer::summarize_packet(bytes, DLT_EN10MB); CHECK(line == "[10 bytes] truncated ethernet frame"); } TEST_CASE("summarize_packet stops after the Ethernet line for an unhandled ethertype") { std::vector bytes = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x88, 0xCC, // LLDP, not IPv4/IPv6/ARP }; auto line = packeteer::summarize_packet(bytes, DLT_EN10MB); CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x88cc"); } TEST_CASE("summarize_packet decodes an ARP request end to end") { std::vector bytes = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x08, 0x06, // ARP 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 10, 0, 0, 1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10, 0, 0, 2, }; auto line = packeteer::summarize_packet(bytes, DLT_EN10MB); CHECK(line == "ETH aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 ethertype=0x0806 | " "ARP who-has 10.0.0.2 tell 10.0.0.1 (aa:bb:cc:dd:ee:ff)"); } TEST_CASE("hex_dump_lines produces one line per 16 bytes, with the right byte count") { std::vector bytes(20, 0); for (std::size_t i = 0; i < bytes.size(); ++i) bytes[i] = static_cast(i); auto lines = packeteer::hex_dump_lines(bytes); REQUIRE(lines.size() == 2); CHECK(lines[0].substr(0, 6) == "000000"); CHECK(lines[1].substr(0, 6) == "000010"); CHECK(lines[0].find("00 01 02 03") != std::string::npos); CHECK(lines[0].find('|') != std::string::npos); }