nodereply_test.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2024-2025 Marek Küthe <m.k@mk16.de> 2 // 3 // SPDX-License-Identifier: GPL-3.0-or-later 4 5 #include <gtest/gtest.h> 6 #include <stdexcept> 7 #include <tins/tins.h> 8 #include "nodereply.hpp" 9 10 using namespace crazytrace; 11 12 TEST(NodeReplyTest, SimpleInit) 13 { 14 const NodeReply reply(NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST); 15 EXPECT_EQ(reply.get_type(), 16 NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST); 17 } 18 19 TEST(NodeReplyTest, NoReply) 20 { 21 NodeReply reply(NodeReplyType::NOREPLY); 22 EXPECT_EQ(reply.get_type(), NodeReplyType::NOREPLY); 23 24 try 25 { 26 [[maybe_unused]] const std::string reply_packet = reply.to_packet(); 27 FAIL(); 28 } 29 catch (const std::exception& e) 30 { 31 SUCCEED(); 32 EXPECT_EQ(std::string(e.what()), 33 "Attempt to create a packet, although there is no reply."); 34 } 35 36 try 37 { 38 reply.icmp_echo_reply(56, 1, {}); 39 FAIL(); 40 } 41 catch (const std::exception& e) 42 { 43 SUCCEED(); 44 EXPECT_EQ(std::string(e.what()), 45 "NodeReply has no type that would require ICMP echo reply " 46 "information."); 47 } 48 49 try 50 { 51 reply.udp_response({}, 33434, 45834); 52 FAIL(); 53 } 54 catch (const std::exception& e) 55 { 56 SUCCEED(); 57 EXPECT_EQ(std::string(e.what()), 58 "NodeReply has no type that would require UDP information."); 59 } 60 61 try 62 { 63 reply.packet_reassembly(Tins::IPv6Address("fd00::")); 64 FAIL(); 65 } 66 catch (const std::exception& e) 67 { 68 SUCCEED(); 69 EXPECT_EQ(std::string(e.what()), 70 "NodeReply has no type that would require original " 71 "destionation address information."); 72 } 73 74 std::ostringstream test_output; 75 test_output << reply; 76 EXPECT_EQ(test_output.str(), "NOREPLY"); 77 } 78 79 TEST(NodeReplyTest, IcmpEchoReply) 80 { 81 const Tins::HWAddress<6> source_mac("52:54:00:b2:fa:7f"); 82 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7e"); 83 const Tins::IPv6Address source_address("fd00::1"); 84 const Tins::IPv6Address destination_address("fd00::2"); 85 constexpr int hoplimit = 55; 86 constexpr int icmp_identifier = 56; 87 constexpr int icmp_sequence = 1; 88 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 89 90 /* Reply */ 91 NodeReply reply(NodeReplyType::ICMP_ECHO_REPLY, 92 destination_mac, 93 destination_address, 94 source_mac, 95 source_address); 96 reply.set_hoplimit(hoplimit); 97 reply.icmp_echo_reply(icmp_identifier, icmp_sequence, payload); 98 const std::string actual_packet = reply.to_packet(); 99 100 /* Expected packet */ 101 Tins::EthernetII packet = Tins::EthernetII(destination_mac, source_mac) / 102 Tins::IPv6(destination_address, source_address) / 103 Tins::ICMPv6(Tins::ICMPv6::Types::ECHO_REPLY); 104 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 105 inner_ipv6.hop_limit(hoplimit); 106 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 107 inner_icmpv6.identifier(icmp_identifier); 108 inner_icmpv6.sequence(icmp_sequence); 109 inner_icmpv6.inner_pdu(Tins::RawPDU(payload)); 110 111 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 112 const std::string expected_packet(serialized_packet.begin(), 113 serialized_packet.end()); 114 115 /* Tests */ 116 try 117 { 118 reply.udp_response({}, 33434, 45834); 119 FAIL(); 120 } 121 catch (const std::exception& e) 122 { 123 SUCCEED(); 124 EXPECT_EQ(std::string(e.what()), 125 "NodeReply has no type that would require UDP information."); 126 } 127 128 try 129 { 130 reply.packet_reassembly(Tins::IPv6Address("fd00::")); 131 FAIL(); 132 } 133 catch (const std::exception& e) 134 { 135 SUCCEED(); 136 EXPECT_EQ(std::string(e.what()), 137 "NodeReply has no type that would require original " 138 "destionation address information."); 139 } 140 141 std::ostringstream test_output; 142 test_output << reply; 143 EXPECT_EQ(test_output.str(), 144 "REPLY ICMP_ECHO_REPLY: fd00::1 (52:54:00:b2:fa:7f) -> fd00::2 " 145 "(52:54:00:b2:fa:7e) Hoplimit=55: ID=56 SEQ=1 Payload: 08 04 05 " 146 "09 ff 00 00 00 00 00"); 147 148 EXPECT_EQ(actual_packet, expected_packet); 149 EXPECT_EQ(reply.get_type(), NodeReplyType::ICMP_ECHO_REPLY); 150 } 151 152 TEST(NodeReplyTest, IcmpTimeExceededIcmpEchoRequest) 153 { 154 const Tins::HWAddress<6> source_mac("52:54:00:b2:fa:7f"); 155 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7e"); 156 const Tins::IPv6Address source_address("fd00::1"); 157 const Tins::IPv6Address destination_address("fd00::2"); 158 const Tins::IPv6Address original_destination_address("fd00::3"); 159 constexpr int hoplimit = 55; 160 constexpr int icmp_identifier = 56; 161 constexpr int icmp_sequence = 1; 162 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 163 164 /* Reply */ 165 NodeReply reply(NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST, 166 destination_mac, 167 destination_address, 168 source_mac, 169 source_address); 170 reply.set_hoplimit(hoplimit); 171 reply.icmp_echo_reply(icmp_identifier, icmp_sequence, payload); 172 reply.packet_reassembly(original_destination_address); 173 const std::string actual_packet = reply.to_packet(); 174 175 /* Expected packet */ 176 Tins::IPv6 embedded_packet = 177 Tins::IPv6(original_destination_address, destination_address) / 178 Tins::ICMPv6(Tins::ICMPv6::Types::ECHO_REQUEST); 179 embedded_packet.hop_limit(1); 180 Tins::ICMPv6& embedded_inner_icmpv6 = 181 embedded_packet.rfind_pdu<Tins::ICMPv6>(); 182 embedded_inner_icmpv6.identifier(icmp_identifier); 183 embedded_inner_icmpv6.sequence(icmp_sequence); 184 embedded_inner_icmpv6.inner_pdu(Tins::RawPDU(payload)); 185 const Tins::PDU::serialization_type serialized_embedded_packet = 186 embedded_packet.serialize(); 187 188 Tins::EthernetII packet = Tins::EthernetII(destination_mac, source_mac) / 189 Tins::IPv6(destination_address, source_address) / 190 Tins::ICMPv6(Tins::ICMPv6::Types::TIME_EXCEEDED); 191 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 192 inner_ipv6.hop_limit(hoplimit); 193 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 194 inner_icmpv6.inner_pdu(Tins::RawPDU(serialized_embedded_packet)); 195 196 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 197 const std::string expected_packet(serialized_packet.begin(), 198 serialized_packet.end()); 199 200 /* Tests */ 201 try 202 { 203 reply.udp_response({}, 33434, 45834); 204 FAIL(); 205 } 206 catch (const std::exception& e) 207 { 208 SUCCEED(); 209 EXPECT_EQ(std::string(e.what()), 210 "NodeReply has no type that would require UDP information."); 211 } 212 213 std::ostringstream test_output; 214 test_output << reply; 215 EXPECT_EQ(test_output.str(), 216 "REPLY ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST: fd00::1 " 217 "(52:54:00:b2:fa:7f) -> fd00::2 (52:54:00:b2:fa:7e) Hoplimit=55 " 218 "REQUEST_ADDRESS=fd00::3 LENGTH=10"); 219 220 EXPECT_EQ(actual_packet, expected_packet); 221 EXPECT_EQ(reply.get_type(), 222 NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST); 223 } 224 225 TEST(NodeReplyTest, IcmpPortUnreachable) 226 { 227 const Tins::HWAddress<6> source_mac("52:54:00:b2:fa:7f"); 228 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7e"); 229 const Tins::IPv6Address source_address("fd00::1"); 230 const Tins::IPv6Address destination_address("fd00::2"); 231 const Tins::IPv6Address original_destination_address("fd00::3"); 232 constexpr int hoplimit = 55; 233 constexpr int udp_sport = 46432; 234 constexpr int udp_dport = 34344; 235 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 236 237 /* Reply */ 238 NodeReply reply(NodeReplyType::ICMP_PORT_UNREACHABLE, 239 destination_mac, 240 destination_address, 241 source_mac, 242 source_address); 243 reply.set_hoplimit(hoplimit); 244 reply.udp_response(payload, udp_dport, udp_sport); 245 reply.packet_reassembly(original_destination_address); 246 const std::string actual_packet = reply.to_packet(); 247 248 /* Expected packet */ 249 Tins::IPv6 embedded_packet = 250 Tins::IPv6(original_destination_address, destination_address) / 251 Tins::UDP(udp_dport, udp_sport); 252 embedded_packet.hop_limit(1); 253 Tins::UDP& embedded_inner_udp = embedded_packet.rfind_pdu<Tins::UDP>(); 254 embedded_inner_udp.inner_pdu(Tins::RawPDU(payload)); 255 const Tins::PDU::serialization_type serialized_embedded_packet = 256 embedded_packet.serialize(); 257 258 Tins::EthernetII packet = 259 Tins::EthernetII(destination_mac, source_mac) / 260 Tins::IPv6(destination_address, source_address) / 261 Tins::ICMPv6(Tins::ICMPv6::Types::DEST_UNREACHABLE); 262 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 263 inner_ipv6.hop_limit(hoplimit); 264 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 265 inner_icmpv6.code(4); 266 inner_icmpv6.inner_pdu(Tins::RawPDU(serialized_embedded_packet)); 267 268 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 269 const std::string expected_packet(serialized_packet.begin(), 270 serialized_packet.end()); 271 272 /* Tests */ 273 try 274 { 275 reply.icmp_echo_reply(78, 1, {}); 276 FAIL(); 277 } 278 catch (const std::exception& e) 279 { 280 SUCCEED(); 281 EXPECT_EQ(std::string(e.what()), 282 "NodeReply has no type that would require ICMP echo reply " 283 "information."); 284 } 285 286 std::ostringstream test_output; 287 test_output << reply; 288 EXPECT_EQ(test_output.str(), 289 "REPLY ICMP_PORT_UNREACHABLE: fd00::1 (52:54:00:b2:fa:7f) -> " 290 "fd00::2 (52:54:00:b2:fa:7e) Hoplimit=55: DPORT=34344 " 291 "SPORT=46432 REQUEST_ADDRESS=fd00::3 LENGTH=10"); 292 293 EXPECT_EQ(actual_packet, expected_packet); 294 EXPECT_EQ(reply.get_type(), NodeReplyType::ICMP_PORT_UNREACHABLE); 295 } 296 297 TEST(NodeReplyTest, IcmpTimeExceededUdp) 298 { 299 const Tins::HWAddress<6> source_mac("52:54:00:b2:fa:7f"); 300 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7e"); 301 const Tins::IPv6Address source_address("fd00::1"); 302 const Tins::IPv6Address destination_address("fd00::2"); 303 const Tins::IPv6Address original_destination_address("fd00::3"); 304 constexpr int hoplimit = 55; 305 constexpr int udp_sport = 46432; 306 constexpr int udp_dport = 34344; 307 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 308 309 /* Reply */ 310 NodeReply reply(NodeReplyType::ICMP_TIME_EXCEEDED_UDP, 311 destination_mac, 312 destination_address, 313 source_mac, 314 source_address); 315 reply.set_hoplimit(hoplimit); 316 reply.udp_response(payload, udp_dport, udp_sport); 317 reply.packet_reassembly(original_destination_address); 318 const std::string actual_packet = reply.to_packet(); 319 320 /* Expected packet */ 321 Tins::IPv6 embedded_packet = 322 Tins::IPv6(original_destination_address, destination_address) / 323 Tins::UDP(udp_dport, udp_sport); 324 embedded_packet.hop_limit(1); 325 Tins::UDP& embedded_inner_udp = embedded_packet.rfind_pdu<Tins::UDP>(); 326 embedded_inner_udp.inner_pdu(Tins::RawPDU(payload)); 327 const Tins::PDU::serialization_type serialized_embedded_packet = 328 embedded_packet.serialize(); 329 330 Tins::EthernetII packet = Tins::EthernetII(destination_mac, source_mac) / 331 Tins::IPv6(destination_address, source_address) / 332 Tins::ICMPv6(Tins::ICMPv6::Types::TIME_EXCEEDED); 333 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 334 inner_ipv6.hop_limit(hoplimit); 335 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 336 inner_icmpv6.inner_pdu(Tins::RawPDU(serialized_embedded_packet)); 337 338 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 339 const std::string expected_packet(serialized_packet.begin(), 340 serialized_packet.end()); 341 342 /* Tests */ 343 try 344 { 345 reply.icmp_echo_reply(78, 1, {}); 346 FAIL(); 347 } 348 catch (const std::exception& e) 349 { 350 SUCCEED(); 351 EXPECT_EQ(std::string(e.what()), 352 "NodeReply has no type that would require ICMP echo reply " 353 "information."); 354 } 355 356 std::ostringstream test_output; 357 test_output << reply; 358 EXPECT_EQ( 359 test_output.str(), 360 "REPLY ICMP_TIME_EXCEEDED_UDP: fd00::1 (52:54:00:b2:fa:7f) -> fd00::2 " 361 "(52:54:00:b2:fa:7e) Hoplimit=55 REQUEST_ADDRESS=fd00::3 LENGTH=10"); 362 363 EXPECT_EQ(actual_packet, expected_packet); 364 EXPECT_EQ(reply.get_type(), NodeReplyType::ICMP_TIME_EXCEEDED_UDP); 365 } 366 367 TEST(NodeReplyTest, IcmpNdp) 368 { 369 const Tins::HWAddress<6> source_mac("52:54:00:b2:fa:7f"); 370 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7e"); 371 const Tins::IPv6Address source_address("fd00::1"); 372 const Tins::IPv6Address destination_address("fd00::2"); 373 374 /* Reply */ 375 NodeReply reply(NodeReplyType::ICMP_NDP, 376 destination_mac, 377 destination_address, 378 source_mac, 379 source_address); 380 const std::string actual_packet = reply.to_packet(); 381 382 /* Expected packet */ 383 Tins::EthernetII packet = 384 Tins::EthernetII(destination_mac, source_mac) / 385 Tins::IPv6(destination_address, source_address) / 386 Tins::ICMPv6(Tins::ICMPv6::Types::NEIGHBOUR_ADVERT); 387 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 388 inner_ipv6.hop_limit(255); 389 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 390 inner_icmpv6.target_addr(source_address); 391 inner_icmpv6.solicited(Tins::small_uint<1>(1)); 392 inner_icmpv6.router(Tins::small_uint<1>(1)); 393 inner_icmpv6.override(Tins::small_uint<1>(1)); 394 const Tins::ICMPv6::option address_option( 395 Tins::ICMPv6::OptionTypes::TARGET_ADDRESS, 396 source_mac.size(), 397 &(*source_mac.begin())); 398 inner_icmpv6.add_option(address_option); 399 400 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 401 const std::string expected_packet(serialized_packet.begin(), 402 serialized_packet.end()); 403 404 /* Tests */ 405 try 406 { 407 reply.set_hoplimit(120); 408 FAIL(); 409 } 410 catch (const std::exception& e) 411 { 412 SUCCEED(); 413 EXPECT_EQ(std::string(e.what()), 414 "ICMP NDP responses always have a hop limit of 255."); 415 } 416 417 try 418 { 419 reply.icmp_echo_reply(3, 5, {}); 420 FAIL(); 421 } 422 catch (const std::exception& e) 423 { 424 SUCCEED(); 425 EXPECT_EQ(std::string(e.what()), 426 "NodeReply has no type that would require ICMP echo reply " 427 "information."); 428 } 429 430 try 431 { 432 reply.udp_response({}, 33434, 45834); 433 FAIL(); 434 } 435 catch (const std::exception& e) 436 { 437 SUCCEED(); 438 EXPECT_EQ(std::string(e.what()), 439 "NodeReply has no type that would require UDP information."); 440 } 441 442 try 443 { 444 reply.packet_reassembly(Tins::IPv6Address("fd00::")); 445 FAIL(); 446 } 447 catch (const std::exception& e) 448 { 449 SUCCEED(); 450 EXPECT_EQ(std::string(e.what()), 451 "NodeReply has no type that would require original " 452 "destionation address information."); 453 } 454 455 std::ostringstream test_output; 456 test_output << reply; 457 EXPECT_EQ(test_output.str(), 458 "REPLY ICMP_NDP: fd00::1 (52:54:00:b2:fa:7f) -> fd00::2 " 459 "(52:54:00:b2:fa:7e)"); 460 461 EXPECT_EQ(actual_packet, expected_packet); 462 EXPECT_EQ(reply.get_type(), NodeReplyType::ICMP_NDP); 463 }