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 }