Net/Kernel: Add basic ICMPv6 support

This commit adds the minimum amount of code to make Serenity reply to
ICMPv6 ping packets. This includes basic Network Discovery Protocol
support, as well as a basic ICMPv6 Echo Request/Response handler.

This change also introduces a new debug flag, ICMPV6_DEBUG.

Co-Authored-By: kleines Filmröllchen <filmroellchen@serenityos.org>
This commit is contained in:
sdomi
2024-09-09 20:54:11 +02:00
committed by Tim Schumacher
parent 709ee9144c
commit 35c74bafc0
4 changed files with 230 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
#include <Kernel/Net/EtherType.h>
#include <Kernel/Net/EthernetFrameHeader.h>
#include <Kernel/Net/ICMP.h>
#include <Kernel/Net/ICMPv6.h>
#include <Kernel/Net/IP/ARP.h>
#include <Kernel/Net/IP/IP.h>
#include <Kernel/Net/IP/IPv4.h>
@@ -33,6 +34,7 @@ static void handle_arp(EthernetFrameHeader const&, size_t frame_size);
static void handle_ipv4(EthernetFrameHeader const&, size_t frame_size, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter);
static void handle_icmp(EthernetFrameHeader const&, IPv4Packet const&, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter);
static void handle_ipv6(EthernetFrameHeader const&, size_t frame_size, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter);
static void handle_icmpv6(EthernetFrameHeader const&, IPv6PacketHeader const&, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter);
static void handle_udp(IPv4Packet const&, UnixDateTime const& packet_timestamp);
static void handle_tcp(IPv4Packet const&, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter);
static void send_delayed_tcp_ack(TCPSocket& socket);
@@ -265,12 +267,128 @@ void handle_ipv6(EthernetFrameHeader const& eth, size_t frame_size, UnixDateTime
case TransportProtocol::TCP:
dbgln_if(IPV6_DEBUG, "handle_ipv6: TODO: got TCP packet, what to do with it?");
break;
case TransportProtocol::ICMPv6:
return handle_icmpv6(eth, packet, packet_timestamp, adapter);
default:
dbgln_if(IPV6_DEBUG, "handle_ipv6: Unhandled protocol {:#02x}", packet.next_header());
break;
}
}
void handle_icmpv6(EthernetFrameHeader const& eth, IPv6PacketHeader const& ipv6_packet, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter)
{
// TODO: Hand ICMPv6 packets to listening user sockets, once those exist.
// TODO: pass through packet_timestamp to raw sockets (see above)
(void)packet_timestamp;
auto& icmpv6_header = *static_cast<ICMPv6Header const*>(ipv6_packet.payload());
auto const ipv6_payload_offset = adapter->ipv6_payload_offset();
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: source={}, destination={}, type={:#02x}, code={:#02x}", ipv6_packet.source().to_string(), ipv6_packet.destination().to_string(), icmpv6_header.type(), icmpv6_header.code());
RefPtr<PacketWithTimestamp> packet;
size_t icmp_packet_size = ipv6_packet.payload_size();
if (icmpv6_header.type() == ICMPv6Type::NeighborSolicitation) {
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: got neighbor solicitation");
if (icmp_packet_size < sizeof(ICMPv6NeighborSolicitation)) {
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: Neighbor solicitation packet too small, ignoring.");
return;
}
auto& request = *bit_cast<ICMPv6NeighborSolicitation const*>(&icmpv6_header);
if (request.target_address != adapter->ipv6_address()) {
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: Got a packet, but not for us. Dropping.");
return;
}
struct [[gnu::packed]] advertisement_with_option {
ICMPv6NeighborAdvertisement base;
ICMPv6OptionLinkLayerAddress option;
};
icmp_packet_size = sizeof(advertisement_with_option);
packet = adapter->acquire_packet_buffer(ipv6_payload_offset + icmp_packet_size);
if (!packet) {
dbgln("Could not allocate packet buffer while sending ICMPv6 packet");
return;
}
adapter->fill_in_ipv6_header(*packet, adapter->ipv6_address(), eth.source(), ipv6_packet.source(), TransportProtocol::ICMPv6, icmp_packet_size, 255);
memset(packet->buffer->data() + ipv6_payload_offset, 0, icmp_packet_size);
auto& response = *(advertisement_with_option*)(packet->buffer->data() + ipv6_payload_offset);
response.base.header.set_type(ICMPv6Type::NeighborAdvertisement);
response.base.set_solicited(1);
response.base.set_override(1);
response.base.target_address = adapter->ipv6_address();
response.option.type = 2;
response.option.length = 1;
response.option.address = adapter->mac_address();
IPv6PseudoHeader header;
header.source_address = adapter->ipv6_address();
header.target_address = ipv6_packet.source();
header.packet_length = icmp_packet_size;
header.next_header = TransportProtocol::ICMPv6;
InternetChecksum checksum;
checksum.add({ &header, sizeof(IPv6PseudoHeader) });
checksum.add({ &response, sizeof(advertisement_with_option) });
response.base.header.set_checksum(checksum.finish());
} else if (icmpv6_header.type() == ICMPv6Type::EchoRequest) {
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: got echo request");
if (icmp_packet_size < sizeof(ICMPv6Echo)) {
dbgln("handle_icmp6: echo request packet too small, ignoring.");
return;
}
auto& request = *bit_cast<ICMPv6Echo const*>(&icmpv6_header);
auto ipv6_payload_offset = adapter->ipv6_payload_offset();
packet = adapter->acquire_packet_buffer(ipv6_payload_offset + icmp_packet_size);
if (!packet) {
dbgln("Could not allocate packet buffer while sending ICMPv6 packet");
return;
}
adapter->fill_in_ipv6_header(*packet, adapter->ipv6_address(), eth.source(), ipv6_packet.source(), TransportProtocol::ICMPv6, icmp_packet_size, 64);
memset(packet->buffer->data() + ipv6_payload_offset, 0, sizeof(ICMPv6Echo));
auto& response = *(ICMPv6Echo*)(packet->buffer->data() + ipv6_payload_offset);
response.header.set_type(ICMPv6Type::EchoReply);
response.identifier = request.identifier;
response.sequence_number = request.sequence_number;
IPv6PseudoHeader header;
header.source_address = adapter->ipv6_address();
header.target_address = ipv6_packet.source();
header.packet_length = icmp_packet_size;
header.next_header = TransportProtocol::ICMPv6;
if (icmp_packet_size > sizeof(ICMPv6Echo))
memcpy(response.payload(), request.payload(), icmp_packet_size - sizeof(ICMPv6Echo));
InternetChecksum checksum;
checksum.add({ &header, sizeof(IPv6PseudoHeader) });
checksum.add({ &response, icmp_packet_size });
response.header.set_checksum(checksum.finish());
} else {
dbgln_if(ICMPV6_DEBUG, "handle_icmp6: got unknown ICMPv6 type {:#02x}", icmpv6_header.type());
return;
}
adapter->send_packet(packet->bytes());
adapter->release_packet_buffer(*packet);
return;
}
void handle_icmp(EthernetFrameHeader const& eth, IPv4Packet const& ipv4_packet, UnixDateTime const& packet_timestamp, RefPtr<NetworkAdapter> adapter)
{
auto& icmp_header = *static_cast<ICMPHeader const*>(ipv4_packet.payload());