mirror of
https://github.com/SerenityOS/serenity
synced 2026-05-09 16:42:10 +02: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:
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user