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

@ -111,6 +111,10 @@
#cmakedefine01 ICMP_DEBUG
#endif
#ifndef ICMPV6_DEBUG
#cmakedefine01 ICMPV6_DEBUG
#endif
#ifndef INTEL_GRAPHICS_DEBUG
#cmakedefine01 INTEL_GRAPHICS_DEBUG
#endif

107
Kernel/Net/ICMPv6.h Normal file
View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2024, kleines Filmröllchen <filmroellchen@serenityos.org>
* Copyright (c) 2024, sdomi <ja@sdomi.pl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/MACAddress.h>
#include <Kernel/Net/IP/IPv6.h>
// https://www.rfc-editor.org/rfc/rfc4443
// Section 2.1
struct ICMPv6Type {
enum {
DestinationUnreachable = 1,
PacketTooBig = 2,
TimeExceeded = 3,
ParameterProblem = 4,
EchoRequest = 128,
EchoReply = 129,
NeighborSolicitation = 135,
NeighborAdvertisement = 136,
};
};
class [[gnu::packed]] ICMPv6Header {
public:
ICMPv6Header() = default;
~ICMPv6Header() = default;
u8 type() const { return m_type; }
void set_type(u8 type) { m_type = type; }
u8 code() const { return m_code; }
void set_code(u8 code) { m_code = code; }
u16 checksum() const { return m_checksum; }
void set_checksum(u16 checksum) { m_checksum = checksum; }
void* payload() { return &m_payload[0]; }
void const* payload() const { return &m_payload[0]; }
private:
u8 m_type { 0 };
u8 m_code { 0 };
NetworkOrdered<u16> m_checksum { 0 };
u8 m_payload[0];
};
static_assert(AssertSize<ICMPv6Header, 4>());
class [[gnu::packed]] ICMPv6Echo {
public:
void* payload() { return &m_payload[0]; }
void const* payload() const { return &m_payload[0]; }
ICMPv6Header header;
NetworkOrdered<u16> identifier;
NetworkOrdered<u16> sequence_number;
private:
u8 m_payload[0];
};
static_assert(AssertSize<ICMPv6Echo, 8>());
// https://www.rfc-editor.org/rfc/rfc2461
// Section 4.3
struct [[gnu::packed]] ICMPv6NeighborSolicitation {
ICMPv6Header header;
u32 reserved;
IPv6Address target_address;
};
static_assert(AssertSize<ICMPv6NeighborSolicitation, 6 * 32 / 8>());
// Section 4.4
class [[gnu::packed]] ICMPv6NeighborAdvertisement {
public:
ICMPv6Header header;
NetworkOrdered<u32> flags;
IPv6Address target_address;
bool override() const { return (flags >> 29) & 1; }
void set_override(bool override) { flags = (flags & ~(1 << 29)) | (override << 29); }
bool solicited() const { return (flags >> 30) & 1; }
void set_solicited(bool solicited) { flags = (flags & ~(1 << 30)) | (solicited << 30); }
bool router() const { return flags >> 31; }
void set_router(bool router) { flags = (flags & ~(1 << 31)) | (router << 31); }
};
static_assert(AssertSize<ICMPv6NeighborAdvertisement, 6 * 32 / 8>());
// Section 4.6.1
struct [[gnu::packed]] ICMPv6OptionLinkLayerAddress {
u8 type; // default: Target link-layer address
u8 length; // multiplied by 8, rounded up
MACAddress address;
};
static_assert(AssertSize<ICMPv6OptionLinkLayerAddress, 8>());

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());

View file

@ -75,6 +75,7 @@ set(HTML_SCRIPT_DEBUG ON)
set(HTTPJOB_DEBUG ON)
set(HUNKS_DEBUG ON)
set(ICMP_DEBUG ON)
set(ICMPV6_DEBUG ON)
set(ICO_DEBUG ON)
set(IDL_DEBUG ON)
set(ILBM_DEBUG ON)