/ src / nodecontainer.cpp
nodecontainer.cpp
  1  // SPDX-FileCopyrightText: Copyright (C) 2024-2026 Marek Küthe <m.k@mk16.de>
  2  //
  3  // SPDX-License-Identifier: GPL-3.0-or-later
  4  
  5  #include "nodecontainer.hpp"
  6  
  7  using namespace crazytrace;
  8  
  9  NodeReply crazytrace::NodeContainer::get_reply(const NodeRequest& request)
 10  {
 11      switch (request.get_type())
 12      {
 13          case NodeRequestType::ICMP_ECHO_REQUEST:
 14          case NodeRequestType::UDP:
 15          {
 16              const std::vector<std::shared_ptr<NodeInfo>> route =
 17                  this->get_route_to(request.get_destination_address());
 18              if (route.empty())
 19              {
 20                  return NodeReply(NodeReplyType::NOREPLY);
 21              }
 22  
 23              const Tins::HWAddress<6> source_mac =
 24                  route.back()->get_mac_address();
 25  
 26              if (const int hoplimit = request.get_hoplimit();
 27                  static_cast<std::size_t>(hoplimit) >= route.size())
 28              {
 29                  /* target reached */
 30                  const std::shared_ptr<NodeInfo>& reached_node = route.at(0);
 31  
 32                  // Both variables undergo a value check during initialization so
 33                  // that neither is greater than 255. It is therefore safe to
 34                  // convert them into an int.
 35                  const int reply_hoplimit = reached_node->get_hoplimit() -
 36                                             static_cast<int>(route.size()) + 1;
 37                  if (reply_hoplimit <= 0)
 38                      return NodeReply(NodeReplyType::NOREPLY);
 39  
 40                  switch (request.get_type())
 41                  {
 42                      case NodeRequestType::ICMP_ECHO_REQUEST:
 43                      {
 44                          NodeReply reply(NodeReplyType::ICMP_ECHO_REPLY,
 45                                          request.get_source_mac(),
 46                                          request.get_source_address(),
 47                                          source_mac,
 48                                          reached_node->get_address());
 49                          reply.icmp_echo_reply(request.get_icmp_identifier(),
 50                                                request.get_icmp_sequence(),
 51                                                request.get_payload());
 52                          reply.set_hoplimit(
 53                              static_cast<uint8_t>(reply_hoplimit));
 54                          return reply;
 55                      }
 56                      case NodeRequestType::UDP:
 57                      {
 58                          NodeReply reply(NodeReplyType::ICMP_PORT_UNREACHABLE,
 59                                          request.get_source_mac(),
 60                                          request.get_source_address(),
 61                                          source_mac,
 62                                          reached_node->get_address());
 63                          reply.udp_response(request.get_payload(),
 64                                             request.get_udp_dport(),
 65                                             request.get_udp_sport());
 66                          reply.packet_reassembly(
 67                              request.get_destination_address());
 68                          reply.set_hoplimit(
 69                              static_cast<uint8_t>(reply_hoplimit));
 70                          return reply;
 71                      }
 72                      default:
 73                          [[unlikely]] throw std::runtime_error(
 74                              "An impossible error has occurred. A reply was "
 75                              "requested for ICMP_ECHO_REQUEST or UDP, but the "
 76                              "request has suddenly changed.");
 77                          break;
 78                  }
 79              }
 80              else
 81              {
 82                  /* hoplimit exceeded */
 83                  const unsigned int reached_node_number =
 84                      static_cast<unsigned int>(route.size()) -
 85                      request.get_hoplimit();
 86                  const std::shared_ptr<NodeInfo>& reached_node =
 87                      route.at(reached_node_number);
 88  
 89                  const int reply_hoplimit =
 90                      reached_node->get_hoplimit() - hoplimit + 1;
 91                  if (reply_hoplimit <= 0)
 92                      return NodeReply(NodeReplyType::NOREPLY);
 93  
 94                  switch (request.get_type())
 95                  {
 96                      case NodeRequestType::ICMP_ECHO_REQUEST:
 97                      {
 98                          NodeReply reply(
 99                              NodeReplyType::ICMP_TIME_EXCEEDED_ICMP_ECHO_REQUEST,
100                              request.get_source_mac(),
101                              request.get_source_address(),
102                              source_mac,
103                              reached_node->get_address());
104                          reply.icmp_echo_reply(request.get_icmp_identifier(),
105                                                request.get_icmp_sequence(),
106                                                request.get_payload());
107                          reply.packet_reassembly(
108                              request.get_destination_address());
109                          reply.set_hoplimit(
110                              static_cast<uint8_t>(reply_hoplimit));
111                          return reply;
112                      }
113                      case NodeRequestType::UDP:
114                      {
115                          NodeReply reply(NodeReplyType::ICMP_TIME_EXCEEDED_UDP,
116                                          request.get_source_mac(),
117                                          request.get_source_address(),
118                                          source_mac,
119                                          reached_node->get_address());
120                          reply.udp_response(request.get_payload(),
121                                             request.get_udp_dport(),
122                                             request.get_udp_sport());
123                          reply.packet_reassembly(
124                              request.get_destination_address());
125                          reply.set_hoplimit(
126                              static_cast<uint8_t>(reply_hoplimit));
127                          return reply;
128                      }
129                      default:
130                          [[unlikely]] throw std::runtime_error(
131                              "An impossible error has occurred. A reply was "
132                              "requested for ICMP_ECHO_REQUEST or UDP, but the "
133                              "request has suddenly changed.");
134                          break;
135                  }
136              }
137              break;
138          }
139          case NodeRequestType::ICMP_NDP:
140          {
141              /* Only the first level of nodes can have MAC addresses, as all
142                 other child nodes are "hidden" behind them and routing to them
143                 should be simulated. */
144              if (request.get_hoplimit() < 1)
145                  return NodeReply(NodeReplyType::NOREPLY);
146  
147              if (const auto found_node = std::ranges::find_if(
148                      this->_nodes,
149                      [&](const std::shared_ptr<NodeInfo>& node)
150                      {
151                          return node->has_address(
152                              request.get_destination_address());
153                      });
154                  found_node != this->_nodes.end())
155              {
156                  /* We have found a node with the corresponding MAC address.
157                      Other nodes with the same MAC address must not exist. */
158                  NodeReply reply(NodeReplyType::ICMP_NDP,
159                                  request.get_source_mac(),
160                                  request.get_source_address(),
161                                  (*found_node)->get_mac_address(),
162                                  request.get_destination_address());
163                  return reply;
164              }
165              return NodeReply(NodeReplyType::NOREPLY);
166          }
167          default:
168              return NodeReply(NodeReplyType::NOREPLY);
169      }
170  
171      return NodeReply(NodeReplyType::NOREPLY);
172  }
173  
174  void crazytrace::NodeContainer::add_node(std::shared_ptr<NodeInfo> node)
175  {
176      this->_nodes.push_back(node);
177  }
178  
179  std::size_t crazytrace::NodeContainer::max_depth() const
180  {
181      std::size_t max = 0;
182      for (const auto& node : this->_nodes)
183      {
184          const std::size_t node_depth = node->max_depth();
185          max = std::max(max, node_depth);
186      }
187      return max;
188  }
189  
190  std::vector<std::shared_ptr<NodeInfo>> crazytrace::NodeContainer::get_route_to(
191      const Tins::IPv6Address& destination_address) const
192  {
193      for (const auto& node : this->_nodes)
194      {
195          if (node->has_address(destination_address))
196          {
197              std::vector<std::shared_ptr<NodeInfo>> result;
198              result.push_back(node);
199              return result;
200          }
201  
202          std::vector<std::shared_ptr<NodeInfo>> result =
203              node->get_route_to(destination_address);
204          if (!result.empty())
205          {
206              result.push_back(node);
207              return result;
208          }
209      }
210  
211      return {};
212  }
213  
214  void crazytrace::NodeContainer::print(std::ostream& os) const
215  {
216      os << *this << std::endl;
217      if (!this->_nodes.empty())
218      {
219          os << "Childs:" << std::endl;
220          for (const auto& node : this->_nodes)
221          {
222              node->print(os, 1);
223          }
224      }
225  }
226  
227  bool crazytrace::NodeContainer::operator==(const NodeContainer& other) const
228  {
229      return std::ranges::equal(this->_nodes, // flawfinder: ignore
230                                other._nodes,
231                                [](const auto& a, const auto& b)
232                                {
233                                    return *a == *b;
234                                });
235  }
236  
237  std::ostream& crazytrace::operator<<(std::ostream& os,
238                                       const NodeContainer& nodecontainer)
239  {
240      os << "NodeContainer: " << nodecontainer._nodes.size() << " childnodes";
241      return os;
242  }