/ src / nodereply.cpp
nodereply.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 "nodereply.hpp"
  6  
  7  using namespace crazytrace;
  8  
  9  crazytrace::NodeReply::NodeReply(NodeReplyType type) :
 10      _type(type),
 11      _hoplimit(0),
 12      _icmp_identifier(0),
 13      _icmp_sequence(0),
 14      _udp_dport(0),
 15      _udp_sport(0)
 16  {
 17  }
 18  
 19  crazytrace::NodeReply::NodeReply(NodeReplyType type,
 20                                   Tins::HWAddress<6> destination_mac,
 21                                   Tins::IPv6Address destination_address,
 22                                   Tins::HWAddress<6> source_mac,
 23                                   Tins::IPv6Address source_address) :
 24      _type(type),
 25      _destination_mac(destination_mac),
 26      _destination_address(destination_address),
 27      _source_mac(source_mac),
 28      _source_address(source_address),
 29      _hoplimit(0),
 30      _icmp_identifier(0),
 31      _icmp_sequence(0),
 32      _udp_dport(0),
 33      _udp_sport(0)
 34  {
 35  }
 36  
 37  void crazytrace::NodeReply::set_hoplimit(uint8_t hoplimit)
 38  {
 39      if (this->_type == NodeReplyType::ICMP_NDP)
 40          throw std::runtime_error(
 41              "ICMP NDP responses always have a hop limit of 255.");
 42  
 43      this->_hoplimit = hoplimit;
 44  }
 45  
 46  void crazytrace::NodeReply::icmp_echo_reply(
 47      uint16_t icmp_identifier,
 48      uint16_t icmp_sequence,
 49      const Tins::RawPDU::payload_type& payload)
 50  {
 51      if (this->_type != NodeReplyType::ICMP_ECHO_REPLY &&
 52          this->_type != NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST)
 53          throw std::runtime_error("NodeReply has no type that would require "
 54                                   "ICMP echo reply information.");
 55  
 56      this->_icmp_identifier = icmp_identifier;
 57      this->_icmp_sequence = icmp_sequence;
 58      this->_payload = payload;
 59  }
 60  
 61  void crazytrace::NodeReply::udp_response(
 62      const Tins::RawPDU::payload_type& payload,
 63      uint16_t udp_dport,
 64      uint16_t udp_sport)
 65  {
 66      if (this->_type != NodeReplyType::ICMP_PORT_UNREACHABLE &&
 67          this->_type != NodeReplyType::ICMP_TIME_EXCEEDED_UDP)
 68          throw std::runtime_error(
 69              "NodeReply has no type that would require UDP information.");
 70      this->_payload = payload;
 71      this->_udp_dport = udp_dport;
 72      this->_udp_sport = udp_sport;
 73  }
 74  
 75  void crazytrace::NodeReply::packet_reassembly(
 76      Tins::IPv6Address original_destination_address)
 77  {
 78      if (this->_type != NodeReplyType::ICMP_PORT_UNREACHABLE &&
 79          this->_type != NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST &&
 80          this->_type != NodeReplyType::ICMP_TIME_EXCEEDED_UDP)
 81          throw std::runtime_error("NodeReply has no type that would require "
 82                                   "original destionation address information.");
 83      this->_original_destination_address = original_destination_address;
 84  }
 85  
 86  std::string crazytrace::NodeReply::to_packet() const
 87  {
 88      switch (this->_type)
 89      {
 90          case NodeReplyType::ICMP_ECHO_REPLY:
 91          {
 92              Tins::EthernetII packet =
 93                  Tins::EthernetII(this->_destination_mac, this->_source_mac) /
 94                  Tins::IPv6(this->_destination_address, this->_source_address) /
 95                  Tins::ICMPv6(Tins::ICMPv6::Types::ECHO_REPLY);
 96              Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>();
 97              inner_ipv6.hop_limit(this->_hoplimit);
 98              Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>();
 99              inner_icmpv6.identifier(this->_icmp_identifier);
100              inner_icmpv6.sequence(this->_icmp_sequence);
101              inner_icmpv6.inner_pdu(Tins::RawPDU(this->_payload));
102  
103              Tins::PDU::serialization_type serialized_packet =
104                  packet.serialize();
105              const std::string raw_packet(serialized_packet.begin(),
106                                           serialized_packet.end());
107              return raw_packet;
108          }
109          case NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST:
110          case NodeReplyType::ICMP_TIME_EXCEEDED_UDP:
111          case NodeReplyType::ICMP_PORT_UNREACHABLE:
112          {
113              /* Recreation of the receiving packet */
114              Tins::IPv6 receiving_ipv6(this->_original_destination_address,
115                                        this->_destination_address);
116              receiving_ipv6.hop_limit(1);
117              switch (this->_type)
118              {
119                  case NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST:
120                  {
121                      Tins::ICMPv6 receiving_icmpv6(
122                          Tins::ICMPv6::Types::ECHO_REQUEST);
123                      receiving_icmpv6.identifier(this->_icmp_identifier);
124                      receiving_icmpv6.sequence(this->_icmp_sequence);
125                      receiving_icmpv6.inner_pdu(Tins::RawPDU(this->_payload));
126                      receiving_ipv6.inner_pdu(receiving_icmpv6);
127                      break;
128                  }
129                  case NodeReplyType::ICMP_TIME_EXCEEDED_UDP:
130                  case NodeReplyType::ICMP_PORT_UNREACHABLE:
131                  {
132                      Tins::UDP receiving_udp(this->_udp_dport, this->_udp_sport);
133                      receiving_udp.inner_pdu(Tins::RawPDU(this->_payload));
134                      receiving_ipv6.inner_pdu(receiving_udp);
135                      break;
136                  }
137                  default:
138                      /* Fatal error */
139                      break;
140              }
141              Tins::PDU::serialization_type serialized_receiving_packet =
142                  receiving_ipv6.serialize();
143              if (serialized_receiving_packet.size() > 1000)
144                  serialized_receiving_packet.resize(1000);
145  
146              switch (this->_type)
147              {
148                  case NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST:
149                  case NodeReplyType::ICMP_TIME_EXCEEDED_UDP:
150                  {
151                      Tins::EthernetII packet =
152                          Tins::EthernetII(this->_destination_mac,
153                                           this->_source_mac) /
154                          Tins::IPv6(this->_destination_address,
155                                     this->_source_address) /
156                          Tins::ICMPv6(Tins::ICMPv6::Types::TIME_EXCEEDED);
157                      Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>();
158                      inner_ipv6.hop_limit(this->_hoplimit);
159                      Tins::ICMPv6& inner_icmpv6 =
160                          inner_ipv6.rfind_pdu<Tins::ICMPv6>();
161                      inner_icmpv6.inner_pdu(
162                          Tins::RawPDU(serialized_receiving_packet));
163  
164                      const Tins::PDU::serialization_type serialized_packet =
165                          packet.serialize();
166                      const std::string raw_packet(serialized_packet.begin(),
167                                                   serialized_packet.end());
168                      return raw_packet;
169                  }
170                  case NodeReplyType::ICMP_PORT_UNREACHABLE:
171                  {
172                      Tins::EthernetII packet =
173                          Tins::EthernetII(this->_destination_mac,
174                                           this->_source_mac) /
175                          Tins::IPv6(this->_destination_address,
176                                     this->_source_address) /
177                          Tins::ICMPv6(Tins::ICMPv6::Types::DEST_UNREACHABLE);
178                      Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>();
179                      inner_ipv6.hop_limit(this->_hoplimit);
180                      Tins::ICMPv6& inner_icmpv6 =
181                          inner_ipv6.rfind_pdu<Tins::ICMPv6>();
182                      inner_icmpv6.code(4);
183                      inner_icmpv6.inner_pdu(
184                          Tins::RawPDU(serialized_receiving_packet));
185  
186                      const Tins::PDU::serialization_type serialized_packet =
187                          packet.serialize();
188                      const std::string raw_packet(serialized_packet.begin(),
189                                                   serialized_packet.end());
190                      return raw_packet;
191                  }
192                  default:
193                      [[unlikely]] throw std::runtime_error(
194                          "Attempt to create a packet, but the packet type has "
195                          "suddenly changed. As a result, no response could be "
196                          "generated.");
197              }
198          }
199          case NodeReplyType::ICMP_NDP:
200          {
201              Tins::EthernetII packet =
202                  Tins::EthernetII(this->_destination_mac, this->_source_mac) /
203                  Tins::IPv6(this->_destination_address, this->_source_address) /
204                  Tins::ICMPv6(Tins::ICMPv6::Types::NEIGHBOUR_ADVERT);
205              Tins::IPv6& inner_ipv6 = packet.rfind_pdu<Tins::IPv6>();
206              inner_ipv6.hop_limit(255);
207              Tins::ICMPv6& inner_icmpv6 = inner_ipv6.rfind_pdu<Tins::ICMPv6>();
208              inner_icmpv6.target_addr(this->_source_address);
209              inner_icmpv6.solicited(Tins::small_uint<1>(1));
210              inner_icmpv6.router(Tins::small_uint<1>(1));
211              inner_icmpv6.override(Tins::small_uint<1>(1));
212              const Tins::ICMPv6::option address_option(
213                  Tins::ICMPv6::OptionTypes::TARGET_ADDRESS,
214                  this->_source_mac.size(),
215                  this->_source_mac.begin());
216              inner_icmpv6.add_option(address_option);
217  
218              const Tins::PDU::serialization_type serialized_packet =
219                  packet.serialize();
220              const std::string raw_packet(serialized_packet.begin(),
221                                           serialized_packet.end());
222              return raw_packet;
223          }
224          default:
225              [[unlikely]] throw std::runtime_error(
226                  "Attempt to create a packet, although there is no reply.");
227      }
228  }
229  
230  NodeReplyType crazytrace::NodeReply::get_type() const noexcept
231  {
232      return this->_type;
233  }
234  
235  bool crazytrace::NodeReply::operator==(const NodeReply& other) const
236  {
237      return this->_type == other._type &&
238             this->_destination_mac == other._destination_mac &&
239             this->_destination_address == other._destination_address &&
240             this->_source_mac == other._source_mac &&
241             this->_source_address == other._source_address &&
242             this->_hoplimit == other._hoplimit &&
243             this->_icmp_identifier == other._icmp_identifier &&
244             this->_icmp_sequence == other._icmp_sequence &&
245             this->_payload == other._payload &&
246             this->_udp_dport == other._udp_dport &&
247             this->_udp_sport == other._udp_sport &&
248             this->_original_destination_address ==
249                 other._original_destination_address;
250  }
251  
252  std::ostream& crazytrace::operator<<(std::ostream& os,
253                                       NodeReply const & nodereply)
254  {
255      if (nodereply._type == NodeReplyType::NOREPLY)
256      {
257          os << "NOREPLY";
258          return os;
259      }
260  
261      std::string type_string;
262      switch (nodereply._type)
263      {
264          case NodeReplyType::ICMP_ECHO_REPLY:
265              type_string = "ICMP_ECHO_REPLY";
266              break;
267          case NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST:
268              type_string = "ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST";
269              break;
270          case NodeReplyType::ICMP_PORT_UNREACHABLE:
271              type_string = "ICMP_PORT_UNREACHABLE";
272              break;
273          case NodeReplyType::ICMP_TIME_EXCEEDED_UDP:
274              type_string = "ICMP_TIME_EXCEEDED_UDP";
275              break;
276          case NodeReplyType::ICMP_NDP:
277              type_string = "ICMP_NDP";
278              break;
279          default:
280              type_string = "UNKNOWN";
281              break;
282      }
283      os << "REPLY " << type_string << ": " << nodereply._source_address << " ("
284         << nodereply._source_mac << ") -> " << nodereply._destination_address
285         << " (" << nodereply._destination_mac << ")";
286  
287      switch (nodereply._type)
288      {
289          case NodeReplyType::ICMP_ECHO_REPLY:
290          {
291              os << " Hoplimit=" << static_cast<unsigned>(nodereply._hoplimit)
292                 << ": ID=" << nodereply._icmp_identifier
293                 << " SEQ=" << nodereply._icmp_sequence << " Payload:";
294              for (const auto& byte : nodereply._payload)
295              {
296                  os << std::format(" {:02x}", static_cast<int>(byte));
297              }
298              break;
299          }
300          case NodeReplyType::ICMP_PORT_UNREACHABLE:
301              os << " Hoplimit=" << static_cast<unsigned>(nodereply._hoplimit)
302                 << ": DPORT=" << nodereply._udp_dport
303                 << " SPORT=" << nodereply._udp_sport
304                 << " REQUEST_ADDRESS=" << nodereply._original_destination_address
305                 << " LENGTH=" << nodereply._payload.size();
306              break;
307          case NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST:
308          case NodeReplyType::ICMP_TIME_EXCEEDED_UDP:
309              os << " Hoplimit=" << static_cast<unsigned>(nodereply._hoplimit)
310                 << " REQUEST_ADDRESS=" << nodereply._original_destination_address
311                 << " LENGTH=" << nodereply._payload.size();
312              break;
313          default:
314              break;
315      }
316  
317      return os;
318  }