diff --git a/Kernel/ICMP.h b/Kernel/ICMP.h new file mode 100644 index 0000000000..0e21b58b5d --- /dev/null +++ b/Kernel/ICMP.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +struct ICMPType { +enum { + EchoReply = 0, + EchoRequest = 8, +}; +}; + +class [[gnu::packed]] ICMPHeader { +public: + ICMPHeader() { } + ~ICMPHeader() { } + + byte type() const { return m_type; } + void set_type(byte b) { m_type = b; } + + byte code() const { return m_code; } + void set_code(byte b) { m_code = b; } + + word checksum() const { return ntohs(m_checksum); } + void set_checksum(word w) { m_checksum = htons(w); } + + const void* payload() const { return &m_payload[0]; } + void* payload() { return &m_payload[0]; } + +private: + byte m_type { 0 }; + byte m_code { 0 }; + word m_checksum { 0 }; + dword m_rest_of_header { 0 }; + byte m_payload[0]; +}; + +static_assert(sizeof(ICMPHeader) == 8); + +struct [[gnu::packed]] IPv4ICMPPacket { + IPv4Packet ipv4_packet; + ICMPHeader icmp_header; +}; + +struct [[gnu::packed]] ICMPEchoPacket { + ICMPHeader header; + NetworkOrdered identifier; + NetworkOrdered sequence_number; + byte payload[]; +}; diff --git a/Kernel/IPv4Packet.h b/Kernel/IPv4Packet.h new file mode 100644 index 0000000000..2ff5769002 --- /dev/null +++ b/Kernel/IPv4Packet.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +struct IPv4Protocol { +enum { + ICMP = 1, +}; +}; + +class [[gnu::packed]] IPv4Packet { +public: + byte version() const { return (m_version_and_ihl >> 4) & 0xf; } + void set_version(byte version) { m_version_and_ihl = (m_version_and_ihl & 0x0f) | (version << 4); } + + byte internet_header_length() const { return m_version_and_ihl & 0xf; } + void set_internet_header_length(byte ihl) { m_version_and_ihl = (m_version_and_ihl & 0xf0) | (ihl & 0x0f); } + + word length() const { return ntohs(m_length); } + void set_length(word length) { m_length = htons(length); } + + word ident() const { return ntohs(m_ident); } + void set_ident(word ident) { m_ident = htons(ident); } + + byte ttl() const { return m_ttl; } + void set_ttl(byte ttl) { m_ttl = ttl; } + + byte protocol() const { return m_protocol; } + void set_protocol(byte protocol) { m_protocol = protocol; } + + word checksum() const { return ntohs(m_checksum); } + void set_checksum(word checksum) { m_checksum = htons(checksum); } + + const IPv4Address& source() const { return m_source; } + void set_source(const IPv4Address& address) { m_source = address; } + + const IPv4Address& destination() const { return m_destination; } + void set_destination(const IPv4Address& address) { m_destination = address; } + + void* payload() { return m_payload; } + const void* payload() const { return m_payload; } + +private: + byte m_version_and_ihl; + byte m_dscp_and_ecn; + word m_length; + word m_ident; + word m_flags_and_fragment; + byte m_ttl; + byte m_protocol; + word m_checksum; + IPv4Address m_source; + IPv4Address m_destination; + byte m_payload[0]; +}; + +static_assert(sizeof(IPv4Packet) == 20); diff --git a/Kernel/NetworkAdapter.cpp b/Kernel/NetworkAdapter.cpp index 75d32c7763..bac685520d 100644 --- a/Kernel/NetworkAdapter.cpp +++ b/Kernel/NetworkAdapter.cpp @@ -24,6 +24,19 @@ void NetworkAdapter::send(const MACAddress& destination, const ARPPacket& packet kfree(eth); } +void NetworkAdapter::send_ipv4(const MACAddress& destination, const void* packet, size_t packet_size) +{ + size_t size_in_bytes = sizeof(EthernetFrameHeader) + packet_size + sizeof(EthernetFrameCheckSequence); + auto* eth = (EthernetFrameHeader*)kmalloc(size_in_bytes); + eth->set_source(mac_address()); + eth->set_destination(destination); + eth->set_ether_type(EtherType::IPv4); + memcpy(eth->payload(), packet, packet_size); + send_raw((byte*)eth, size_in_bytes); + kfree(eth); +} + + void NetworkAdapter::did_receive(const byte* data, int length) { InterruptDisabler disabler; diff --git a/Kernel/NetworkAdapter.h b/Kernel/NetworkAdapter.h index 01c69b9a25..dcf9ec47c2 100644 --- a/Kernel/NetworkAdapter.h +++ b/Kernel/NetworkAdapter.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include class NetworkAdapter { public: @@ -18,6 +20,7 @@ public: void set_ipv4_address(const IPv4Address&); void send(const MACAddress&, const ARPPacket&); + void send_ipv4(const MACAddress&, const void*, size_t); ByteBuffer dequeue_packet(); diff --git a/Kernel/NetworkOrdered.h b/Kernel/NetworkOrdered.h new file mode 100644 index 0000000000..4905a82b99 --- /dev/null +++ b/Kernel/NetworkOrdered.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +template +T convert_between_host_and_network(T host_value) +{ + if constexpr (sizeof(T) == 4) { + auto* s = (byte*)&host_value; + return (dword)(s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]); + } + if constexpr (sizeof(T) == 2) { + auto* s = (byte*)&host_value; + return (word)(s[0] << 8 | s[1]); + } + if constexpr (sizeof(T) == 1) + return host_value; +} + +template +class [[gnu::packed]] NetworkOrdered { +public: + NetworkOrdered() + : m_network_value(0) + { + } + + NetworkOrdered(const T& host_value) + : m_network_value(convert_between_host_and_network(host_value)) + { + } + + NetworkOrdered(const NetworkOrdered& other) + : m_network_value(other.m_network_value) + { + } + + NetworkOrdered& operator=(const NetworkOrdered& other) + { + m_network_value = other.m_network_value; + return *this; + } + + operator T() const { return convert_between_host_and_network(m_network_value); } + +private: + T m_network_value; +}; diff --git a/Kernel/NetworkTask.cpp b/Kernel/NetworkTask.cpp index d2e6ad66ab..41a2d08ca7 100644 --- a/Kernel/NetworkTask.cpp +++ b/Kernel/NetworkTask.cpp @@ -1,12 +1,18 @@ #include #include #include +#include +#include #include #include #include +//#define ETHERNET_DEBUG +//#define IPV4_DEBUG + static void handle_arp(const EthernetFrameHeader&, int frame_size); static void handle_ipv4(const EthernetFrameHeader&, int frame_size); +static void handle_icmp(const EthernetFrameHeader&, int frame_size); Lockable>& arp_table() { @@ -35,12 +41,14 @@ void NetworkTask_main() continue; } auto& eth = *(const EthernetFrameHeader*)packet.pointer(); +#ifdef ETHERNET_DEBUG kprintf("NetworkTask: From %s to %s, ether_type=%w, packet_length=%u\n", eth.source().to_string().characters(), eth.destination().to_string().characters(), eth.ether_type(), packet.size() ); +#endif switch (eth.ether_type()) { case EtherType::ARP: @@ -123,5 +131,61 @@ void handle_arp(const EthernetFrameHeader& eth, int frame_size) void handle_ipv4(const EthernetFrameHeader& eth, int frame_size) { + constexpr int minimum_ipv4_frame_size = sizeof(EthernetFrameHeader) + sizeof(IPv4Packet) + sizeof(EthernetFrameCheckSequence); + if (frame_size < minimum_ipv4_frame_size) { + kprintf("handle_ipv4: Frame too small (%d, need %d)\n", frame_size, minimum_ipv4_frame_size); + return; + } + auto& packet = *static_cast(eth.payload()); +#ifdef IPV4_DEBUG + kprintf("handle_ipv4: source=%s, target=%s\n", + packet.source().to_string().characters(), + packet.destination().to_string().characters() + ); +#endif + + switch (packet.protocol()) { + case IPv4Protocol::ICMP: + return handle_icmp(eth, frame_size); + default: + kprintf("handle_ipv4: Unhandled protocol %u\n", packet.protocol()); + break; + } +} + +void handle_icmp(const EthernetFrameHeader& eth, int frame_size) +{ + auto& ipv4_packet = *static_cast(eth.payload()); + auto& icmp_header = *static_cast(ipv4_packet.payload()); + kprintf("handle_icmp: type=%b, code=%b\n", icmp_header.type(), icmp_header.code()); + + auto& e1000 = *E1000NetworkAdapter::the(); + if (ipv4_packet.destination() == e1000.ipv4_address()) { + // It's for me! + if (icmp_header.type() == ICMPType::EchoRequest) { + auto& request = reinterpret_cast(icmp_header); + kprintf("ICMP echo request: id=%u, seq=%u\n", (int)request.identifier, (int)request.sequence_number); + byte* response_buffer = (byte*)kmalloc(ipv4_packet.length()); + memset(response_buffer, 0, ipv4_packet.length()); + struct [[gnu::packed]] EchoResponse { + IPv4Packet ipv4; + ICMPEchoPacket icmp_echo; + }; + auto& response = *(EchoResponse*)response_buffer; + response.ipv4.set_version(4); + response.ipv4.set_internet_header_length(5); + response.ipv4.set_source(e1000.ipv4_address()); + response.ipv4.set_destination(ipv4_packet.source()); + response.ipv4.set_protocol(IPv4Protocol::ICMP); + response.ipv4.set_length(sizeof(IPv4ICMPPacket)); + response.icmp_echo.header.set_type(ICMPType::EchoReply); + response.icmp_echo.header.set_code(0); + response.icmp_echo.identifier = request.identifier; + response.icmp_echo.sequence_number = request.sequence_number; + memcpy(response.icmp_echo.payload, request.payload, ipv4_packet.length() - sizeof(ICMPEchoPacket)); + e1000.send_ipv4(eth.source(), &response, ipv4_packet.length()); + kfree(response_buffer); + } + } }