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 }