mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-22 09:21:57 -05:00
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:
parent
709ee9144c
commit
35c74bafc0
4 changed files with 230 additions and 0 deletions
|
@ -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
107
Kernel/Net/ICMPv6.h
Normal 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>());
|
|
@ -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());
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue