/ tests / nodereply_test.cpp
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  }