nodecontainer_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 <memory> 7 #include <sstream> 8 #include <stdexcept> 9 #include <cstdint> 10 #include <tins/tins.h> 11 #include "nodecontainer.hpp" 12 #include "nodeinfo.hpp" 13 14 using namespace crazytrace; 15 16 class NodeContainerTest : public testing::Test 17 { 18 protected: 19 // cppcheck-suppress duplInheritedMember 20 static void SetUpTestSuite() 21 { 22 testing::Test::SetUpTestSuite(); 23 24 /* Container 1 */ 25 container1 = std::make_unique<NodeContainer>(); 26 27 c1_child_node1 = std::make_shared<NodeInfo>(); 28 c1_child_node1->set_hoplimit(20); 29 c1_child_node1->set_mac_address( 30 Tins::HWAddress<6>("52:54:00:b2:fa:7f")); 31 c1_child_node1->add_address(Tins::IPv6Address("fd00::11")); 32 c1_child_node1->add_address(Tins::IPv6Address("fd00::12")); 33 container1->add_node(c1_child_node1); 34 35 c1_child_node2 = std::make_shared<NodeInfo>(); 36 c1_child_node2->set_hoplimit(30); 37 c1_child_node2->set_mac_address( 38 Tins::HWAddress<6>("52:54:00:b2:fa:7e")); 39 c1_child_node2->add_address(Tins::IPv6Address("fd00::21")); 40 container1->add_node(c1_child_node2); 41 42 c1_child_node3 = std::make_shared<NodeInfo>(); 43 c1_child_node3->set_mac_address( 44 Tins::HWAddress<6>("52:54:00:b2:fa:7d")); 45 c1_child_node3->add_address(Tins::IPv6Address("fd00::3")); 46 47 c1_child_node3_child1 = std::make_shared<NodeInfo>(); 48 c1_child_node3_child1->add_address(Tins::IPv6Address("fd00::3:1")); 49 c1_child_node3->add_node(c1_child_node3_child1); 50 51 c1_child_node3_child2 = std::make_shared<NodeInfo>(); 52 c1_child_node3_child2->add_address(Tins::IPv6Address("fd00::3:2")); 53 54 c1_child_node3_child2_child1 = std::make_shared<NodeInfo>(); 55 c1_child_node3_child2_child1->add_address( 56 Tins::IPv6Address("fd00::3:2:1")); 57 c1_child_node3_child2->add_node(c1_child_node3_child2_child1); 58 59 c1_child_node3->add_node(c1_child_node3_child2); 60 container1->add_node(c1_child_node3); 61 62 /* Container 2 */ 63 container2 = std::make_unique<NodeContainer>(); 64 65 c2_child_node1 = std::make_shared<NodeInfo>(); 66 c2_child_node1->set_mac_address( 67 Tins::HWAddress<6>("52:54:00:b2:fa:7f")); 68 c2_child_node1->add_address(Tins::IPv6Address("fd01::")); 69 container2->add_node(c2_child_node1); 70 71 /* Container 3 */ 72 container3 = std::make_unique<NodeContainer>(); 73 } 74 75 Tins::EthernetII 76 create_echo_request(const Tins::HWAddress<6>& source_mac, 77 const Tins::HWAddress<6>& destination_mac, 78 const Tins::IPv6Address& source_address, 79 const Tins::IPv6Address& destination_address, 80 const uint8_t hoplimit, 81 const uint16_t icmp_identifier, 82 const uint16_t icmp_sequence, 83 const Tins::RawPDU::payload_type& payload) 84 { 85 Tins::EthernetII packet = 86 Tins::EthernetII(destination_mac, source_mac) / 87 Tins::IPv6(destination_address, source_address) / 88 Tins::ICMPv6(Tins::ICMPv6::Types::ECHO_REQUEST); 89 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 90 inner_ipv6.hop_limit(hoplimit); 91 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 92 inner_icmpv6.identifier(icmp_identifier); 93 inner_icmpv6.sequence(icmp_sequence); 94 inner_icmpv6.inner_pdu(Tins::RawPDU(payload)); 95 96 const Tins::PDU::serialization_type serialized_packet = 97 packet.serialize(); 98 if (serialized_packet.size() > std::numeric_limits<uint32_t>::max()) 99 throw std::invalid_argument( 100 "serialized_packet.size() > " 101 "std::numeric_limits<uint32_t>::max()"); 102 const Tins::EthernetII final_packet( 103 serialized_packet.data(), 104 static_cast<uint32_t>(serialized_packet.size())); 105 106 return final_packet; 107 } 108 109 Tins::EthernetII 110 create_udp_request(const Tins::HWAddress<6>& source_mac, 111 const Tins::HWAddress<6>& destination_mac, 112 const Tins::IPv6Address& source_address, 113 const Tins::IPv6Address& destination_address, 114 const uint8_t hoplimit, 115 const uint16_t udp_dport, 116 const uint16_t udp_sport, 117 const Tins::RawPDU::payload_type& payload) 118 { 119 Tins::EthernetII packet = 120 Tins::EthernetII(destination_mac, source_mac) / 121 Tins::IPv6(destination_address, source_address) / 122 Tins::UDP(udp_dport, udp_sport); 123 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 124 inner_ipv6.hop_limit(hoplimit); 125 Tins::UDP& inner_udp = inner_ipv6.rfind_pdu<Tins::UDP>(); 126 inner_udp.inner_pdu(Tins::RawPDU(payload)); 127 128 const Tins::PDU::serialization_type serialized_packet = 129 packet.serialize(); 130 if (serialized_packet.size() > std::numeric_limits<uint32_t>::max()) 131 throw std::invalid_argument( 132 "serialized_packet.size() > " 133 "std::numeric_limits<uint32_t>::max()"); 134 const Tins::EthernetII final_packet( 135 serialized_packet.data(), 136 static_cast<uint32_t>(serialized_packet.size())); 137 138 return final_packet; 139 } 140 141 Tins::EthernetII 142 create_ndp_request(const Tins::HWAddress<6>& source_mac, 143 const Tins::IPv6Address& source_address, 144 const Tins::IPv6Address& target_address, 145 const uint8_t hoplimit) 146 { 147 Tins::EthernetII packet = 148 Tins::EthernetII(Tins::HWAddress<6>("33:33:ff:48:b2:ae"), 149 source_mac) / 150 Tins::IPv6(Tins::IPv6Address("ff02::1:ff48:b2ae"), 151 source_address) / 152 Tins::ICMPv6(Tins::ICMPv6::Types::NEIGHBOUR_SOLICIT); 153 Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>(); 154 inner_ipv6.hop_limit(hoplimit); 155 Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>(); 156 inner_icmpv6.target_addr(target_address); 157 158 const Tins::PDU::serialization_type serialized_packet = 159 packet.serialize(); 160 if (serialized_packet.size() > std::numeric_limits<uint32_t>::max()) 161 throw std::invalid_argument( 162 "serialized_packet.size() > " 163 "std::numeric_limits<uint32_t>::max()"); 164 const Tins::EthernetII final_packet( 165 serialized_packet.data(), 166 static_cast<uint32_t>(serialized_packet.size())); 167 168 return final_packet; 169 } 170 171 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 172 static std::unique_ptr<NodeContainer> container1; 173 static std::shared_ptr<NodeInfo> c1_child_node1; 174 static std::shared_ptr<NodeInfo> c1_child_node2; 175 static std::shared_ptr<NodeInfo> c1_child_node3; 176 static std::shared_ptr<NodeInfo> c1_child_node3_child1; 177 static std::shared_ptr<NodeInfo> c1_child_node3_child2; 178 static std::shared_ptr<NodeInfo> c1_child_node3_child2_child1; 179 static std::unique_ptr<NodeContainer> container2; 180 static std::shared_ptr<NodeInfo> c2_child_node1; 181 static std::unique_ptr<NodeContainer> container3; 182 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 183 }; 184 185 // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 186 std::unique_ptr<NodeContainer> NodeContainerTest::container1; 187 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node1; 188 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node2; 189 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node3; 190 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node3_child1; 191 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node3_child2; 192 std::shared_ptr<NodeInfo> NodeContainerTest::c1_child_node3_child2_child1; 193 std::unique_ptr<NodeContainer> NodeContainerTest::container2; 194 std::shared_ptr<NodeInfo> NodeContainerTest::c2_child_node1; 195 std::unique_ptr<NodeContainer> NodeContainerTest::container3; 196 197 // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 198 199 TEST_F(NodeContainerTest, Print) 200 { 201 std::ostringstream test_output; 202 203 container1->print(test_output); 204 EXPECT_EQ( 205 test_output.str(), 206 "NodeContainer: 3 childnodes\nChilds:\n\tNodeInfo: hoplimit=20 " 207 "fd00::11 fd00::12 52:54:00:b2:fa:7f\n\tNodeInfo: hoplimit=30 fd00::21 " 208 "52:54:00:b2:fa:7e\n\tNodeInfo: hoplimit=64 fd00::3 " 209 "52:54:00:b2:fa:7d\n\tChilds:\n\t\tNodeInfo: hoplimit=64 " 210 "fd00::3:1\n\t\tNodeInfo: hoplimit=64 " 211 "fd00::3:2\n\t\tChilds:\n\t\t\tNodeInfo: hoplimit=64 fd00::3:2:1\n"); 212 test_output.str(""); 213 214 container2->print(test_output); 215 EXPECT_EQ(test_output.str(), 216 "NodeContainer: 1 childnodes\nChilds:\n\tNodeInfo: hoplimit=64 " 217 "fd01:: 52:54:00:b2:fa:7f\n"); 218 test_output.str(""); 219 220 container3->print(test_output); 221 EXPECT_EQ(test_output.str(), "NodeContainer: 0 childnodes\n"); 222 } 223 224 TEST_F(NodeContainerTest, Output) 225 { 226 std::ostringstream test_output; 227 228 test_output << *container1; 229 EXPECT_EQ(test_output.str(), "NodeContainer: 3 childnodes"); 230 test_output.str(""); 231 232 test_output << *container2; 233 EXPECT_EQ(test_output.str(), "NodeContainer: 1 childnodes"); 234 test_output.str(""); 235 236 test_output << *container3; 237 EXPECT_EQ(test_output.str(), "NodeContainer: 0 childnodes"); 238 } 239 240 TEST_F(NodeContainerTest, MaxDepth) 241 { 242 EXPECT_EQ(container1->max_depth(), 3); 243 EXPECT_EQ(container2->max_depth(), 1); 244 EXPECT_EQ(container3->max_depth(), 0); 245 } 246 247 TEST_F(NodeContainerTest, Comparison) 248 { 249 NodeContainer container; 250 251 auto c1_child_node1 = std::make_shared<NodeInfo>(); 252 c1_child_node1->set_hoplimit(20); 253 c1_child_node1->set_mac_address(Tins::HWAddress<6>("52:54:00:b2:fa:7f")); 254 c1_child_node1->add_address(Tins::IPv6Address("fd00::11")); 255 c1_child_node1->add_address(Tins::IPv6Address("fd00::12")); 256 container.add_node(c1_child_node1); 257 258 auto c1_child_node2 = std::make_shared<NodeInfo>(); 259 c1_child_node2->set_hoplimit(30); 260 c1_child_node2->set_mac_address(Tins::HWAddress<6>("52:54:00:b2:fa:7e")); 261 c1_child_node2->add_address(Tins::IPv6Address("fd00::21")); 262 container.add_node(c1_child_node2); 263 264 auto c1_child_node3 = std::make_shared<NodeInfo>(); 265 c1_child_node3->set_mac_address(Tins::HWAddress<6>("52:54:00:b2:fa:7d")); 266 c1_child_node3->add_address(Tins::IPv6Address("fd00::3")); 267 268 auto c1_child_node3_child1 = std::make_shared<NodeInfo>(); 269 c1_child_node3_child1->add_address(Tins::IPv6Address("fd00::3:1")); 270 c1_child_node3->add_node(c1_child_node3_child1); 271 272 auto c1_child_node3_child2 = std::make_shared<NodeInfo>(); 273 c1_child_node3_child2->add_address(Tins::IPv6Address("fd00::3:2")); 274 275 auto c1_child_node3_child2_child1 = std::make_shared<NodeInfo>(); 276 c1_child_node3_child2_child1->add_address(Tins::IPv6Address("fd00::3:2:1")); 277 c1_child_node3_child2->add_node(c1_child_node3_child2_child1); 278 279 c1_child_node3->add_node(c1_child_node3_child2); 280 container.add_node(c1_child_node3); 281 282 EXPECT_EQ(*container1, container); 283 EXPECT_NE(*container2, container); 284 EXPECT_NE(*container3, container); 285 } 286 287 TEST_F(NodeContainerTest, GetReplyForEchoRequest) 288 { 289 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 290 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 291 const Tins::IPv6Address source_address("fd01::1"); 292 const Tins::IPv6Address destination_address("fd00::3:2:1"); 293 constexpr int hoplimit = 55; 294 constexpr int icmp_identifier = 56; 295 constexpr int icmp_sequence = 1; 296 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 297 298 const Tins::EthernetII request_packet = 299 create_echo_request(source_mac, 300 destination_mac, 301 source_address, 302 destination_address, 303 hoplimit, 304 icmp_identifier, 305 icmp_sequence, 306 payload); 307 308 const NodeRequest echo_request(request_packet); 309 const NodeReply echo_reply = container1->get_reply(echo_request); 310 311 NodeReply expected_reply(NodeReplyType::ICMP_ECHO_REPLY, 312 source_mac, 313 source_address, 314 destination_mac, 315 destination_address); 316 expected_reply.icmp_echo_reply(icmp_identifier, icmp_sequence, payload); 317 expected_reply.set_hoplimit(62); 318 EXPECT_EQ(echo_reply, expected_reply); 319 } 320 321 TEST_F(NodeContainerTest, GetReplyTimeExceededForEchoRequest) 322 { 323 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 324 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 325 const Tins::IPv6Address source_address("fd01::1"); 326 const Tins::IPv6Address destination_address("fd00::3:2:1"); 327 constexpr int hoplimit = 1; 328 constexpr int icmp_identifier = 56; 329 constexpr int icmp_sequence = 1; 330 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 331 332 const Tins::EthernetII request_packet = 333 create_echo_request(source_mac, 334 destination_mac, 335 source_address, 336 destination_address, 337 hoplimit, 338 icmp_identifier, 339 icmp_sequence, 340 payload); 341 342 const NodeRequest echo_request(request_packet); 343 const NodeReply echo_reply = container1->get_reply(echo_request); 344 345 const Tins::IPv6Address time_exceeded_hop("fd00::3"); 346 347 NodeReply expected_reply( 348 NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST, 349 source_mac, 350 source_address, 351 destination_mac, 352 time_exceeded_hop); 353 expected_reply.icmp_echo_reply(icmp_identifier, icmp_sequence, payload); 354 expected_reply.packet_reassembly(destination_address); 355 expected_reply.set_hoplimit(64); 356 EXPECT_EQ(echo_reply, expected_reply); 357 } 358 359 TEST_F(NodeContainerTest, GetReplyForUdpRequest) 360 { 361 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 362 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 363 const Tins::IPv6Address source_address("fd01::1"); 364 const Tins::IPv6Address destination_address("fd00::3"); 365 constexpr int hoplimit = 5; 366 constexpr int udp_dport = 33434; 367 constexpr int udp_sport = 16383; 368 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 369 370 const Tins::EthernetII request_packet = 371 create_udp_request(source_mac, 372 destination_mac, 373 source_address, 374 destination_address, 375 hoplimit, 376 udp_dport, 377 udp_sport, 378 payload); 379 380 const NodeRequest echo_request(request_packet); 381 const NodeReply echo_reply = container1->get_reply(echo_request); 382 383 NodeReply expected_reply(NodeReplyType::ICMP_PORT_UNREACHABLE, 384 source_mac, 385 source_address, 386 destination_mac, 387 destination_address); 388 expected_reply.udp_response(payload, udp_dport, udp_sport); 389 expected_reply.packet_reassembly(destination_address); 390 expected_reply.set_hoplimit(64); 391 EXPECT_EQ(echo_reply, expected_reply); 392 } 393 394 TEST_F(NodeContainerTest, GetTimeExceededReplyForUdpRequest) 395 { 396 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 397 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 398 const Tins::IPv6Address source_address("fd01::1"); 399 const Tins::IPv6Address destination_address("fd00::3:2:1"); 400 constexpr int hoplimit = 1; 401 constexpr int udp_dport = 33434; 402 constexpr int udp_sport = 16383; 403 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 404 405 const Tins::EthernetII request_packet = 406 create_udp_request(source_mac, 407 destination_mac, 408 source_address, 409 destination_address, 410 hoplimit, 411 udp_dport, 412 udp_sport, 413 payload); 414 415 const NodeRequest echo_request(request_packet); 416 const NodeReply echo_reply = container1->get_reply(echo_request); 417 418 const Tins::IPv6Address time_exceeded_hop("fd00::3"); 419 420 NodeReply expected_reply(NodeReplyType::ICMP_TIME_EXCEEDED_UDP, 421 source_mac, 422 source_address, 423 destination_mac, 424 time_exceeded_hop); 425 expected_reply.udp_response(payload, udp_dport, udp_sport); 426 expected_reply.packet_reassembly(destination_address); 427 expected_reply.set_hoplimit(64); 428 EXPECT_EQ(echo_reply, expected_reply); 429 } 430 431 TEST_F(NodeContainerTest, GetNoReplyForNonExistAddress) 432 { 433 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 434 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 435 const Tins::IPv6Address source_address("fd01::1"); 436 const Tins::IPv6Address destination_address("fd00::3:2:1"); 437 constexpr int hoplimit = 1; 438 constexpr int udp_dport = 33434; 439 constexpr int udp_sport = 16383; 440 const Tins::RawPDU::payload_type payload = {8, 4, 5, 9, 255, 0, 0, 0, 0, 0}; 441 442 const Tins::EthernetII request_packet = 443 create_udp_request(source_mac, 444 destination_mac, 445 source_address, 446 destination_address, 447 hoplimit, 448 udp_dport, 449 udp_sport, 450 payload); 451 452 const NodeRequest echo_request(request_packet); 453 const NodeReply echo_reply = container2->get_reply(echo_request); 454 455 const NodeReply expected_reply(NodeReplyType::NOREPLY); 456 EXPECT_EQ(echo_reply, expected_reply); 457 } 458 459 TEST_F(NodeContainerTest, GetNoReplyForUnknownPacket) 460 { 461 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 462 const Tins::HWAddress<6> destination_mac("52:54:00:b2:fa:7d"); 463 const Tins::IPv4Address source_address("10.10.10.10"); 464 const Tins::IPv4Address destination_address("10.10.10.11"); 465 466 Tins::EthernetII packet = Tins::EthernetII(destination_mac, source_mac) / 467 Tins::IP(destination_address, source_address); 468 469 const Tins::PDU::serialization_type serialized_packet = packet.serialize(); 470 ASSERT_LE(serialized_packet.size(), std::numeric_limits<uint32_t>::max()); 471 const Tins::EthernetII final_packet( 472 serialized_packet.data(), 473 static_cast<uint32_t>(serialized_packet.size())); 474 475 const NodeRequest echo_request(final_packet); 476 const NodeReply echo_reply = container1->get_reply(echo_request); 477 478 const NodeReply expected_reply(NodeReplyType::NOREPLY); 479 EXPECT_EQ(echo_reply, expected_reply); 480 } 481 482 TEST_F(NodeContainerTest, GetReplyForNdpRequest) 483 { 484 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 485 const Tins::HWAddress<6> target_mac("52:54:00:b2:fa:7d"); 486 const Tins::IPv6Address source_address("fd01::1"); 487 const Tins::IPv6Address target_address("fd00::3"); 488 constexpr int hoplimit = 5; 489 490 const Tins::EthernetII request_packet = create_ndp_request( 491 source_mac, source_address, target_address, hoplimit); 492 493 const NodeRequest echo_request(request_packet); 494 const NodeReply echo_reply = container1->get_reply(echo_request); 495 496 const NodeReply expected_reply(NodeReplyType::ICMP_NDP, 497 source_mac, 498 source_address, 499 target_mac, 500 target_address); 501 EXPECT_EQ(echo_reply, expected_reply); 502 } 503 504 TEST_F(NodeContainerTest, GetNoReplyForNdpRequestDueToHoplimit) 505 { 506 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 507 const Tins::IPv6Address source_address("fd01::1"); 508 const Tins::IPv6Address target_address("fd00::3"); 509 constexpr int hoplimit = 0; 510 511 const Tins::EthernetII request_packet = create_ndp_request( 512 source_mac, source_address, target_address, hoplimit); 513 514 const NodeRequest echo_request(request_packet); 515 const NodeReply echo_reply = container1->get_reply(echo_request); 516 517 const NodeReply expected_reply(NodeReplyType::NOREPLY); 518 EXPECT_EQ(echo_reply, expected_reply); 519 } 520 521 TEST_F(NodeContainerTest, GetNoReplyForNdpRequestDueToNonExistTarget) 522 { 523 const Tins::HWAddress<6> source_mac("52:54:01:b2:fa:7f"); 524 const Tins::IPv6Address source_address("fd01::1"); 525 const Tins::IPv6Address target_address("fd00::3"); 526 constexpr int hoplimit = 5; 527 528 const Tins::EthernetII request_packet = create_ndp_request( 529 source_mac, source_address, target_address, hoplimit); 530 531 const NodeRequest echo_request(request_packet); 532 const NodeReply echo_reply = container2->get_reply(echo_request); 533 534 const NodeReply expected_reply(NodeReplyType::NOREPLY); 535 EXPECT_EQ(echo_reply, expected_reply); 536 }