Implement IPv4 stack and ip app (AI)

- Created src/ipv4.h: IPv4 header struct, protocol numbers, checksum,
  address conversion (ipv4_aton/ntoa), send/receive/routing API,
  protocol handler registration
- Created src/ipv4.c: packet construction with header checksum,
  simple routing (direct subnet + gateway), incoming packet validation
  and dispatch to registered protocol handlers
- Created apps/ip/ip.c: displays network interface config from
  /sys/net (MAC, link, IP, netmask, gateway); supports 'ip set'
  to configure interface via sysfs writes
- Added ipv4.c to kernel build, kernel calls ipv4_init() at boot
- Tested: clean boot, IPv4 initialized, ip app in CPIO
This commit is contained in:
AI
2026-02-24 07:28:33 +00:00
parent 35bafdcad9
commit 1825448528
7 changed files with 659 additions and 5 deletions

View File

@@ -71,7 +71,7 @@ Once a task is completed, it should be checked off.
- [x] Create a network driver for the NE2000 NIC.
- [x] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T.
- [x] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`.
- [ ] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys`
- [x] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys`
- [ ] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys`
- [ ] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
- [ ] Create a UDP and TCP stack.

202
apps/ip/ip.c Normal file
View File

@@ -0,0 +1,202 @@
/**
* @file ip.c
* @brief Display and configure IPv4 network configuration.
*
* Reads network interface information from /sys/net and displays
* the current IPv4 configuration for all interfaces.
*
* Usage:
* ip - Show all interfaces
* ip set <iface> <ip> <netmask> <gateway> - Configure an interface
*
* Examples:
* ip
* ip set eth1 192.168.1.100 255.255.255.0 192.168.1.1
*/
#include "syscalls.h"
/**
* Read the contents of a sysfs file into buf.
* Returns number of bytes read, or -1 on failure.
*/
static int32_t read_sysfs(const char *path, char *buf, uint32_t size) {
int32_t fd = open(path, 0);
if (fd < 0) return -1;
int32_t n = read(fd, buf, size - 1);
close(fd);
if (n > 0) {
buf[n] = '\0';
/* Strip trailing newline */
for (int32_t i = n - 1; i >= 0; i--) {
if (buf[i] == '\n' || buf[i] == '\r') buf[i] = '\0';
else break;
}
} else {
buf[0] = '\0';
}
return n;
}
/**
* Build a sysfs path: /sys/net/<iface>/<file>
*/
static void build_path(char *out, uint32_t out_size,
const char *iface, const char *file) {
/* Manual string concatenation */
uint32_t pos = 0;
const char *prefix = "/sys/net/";
while (*prefix && pos < out_size - 1) out[pos++] = *prefix++;
while (*iface && pos < out_size - 1) out[pos++] = *iface++;
if (pos < out_size - 1) out[pos++] = '/';
while (*file && pos < out_size - 1) out[pos++] = *file++;
out[pos] = '\0';
}
/**
* Show info for one interface.
*/
static void show_iface(const char *name) {
char path[128];
char val[64];
puts(name);
puts(":\n");
/* MAC address */
build_path(path, sizeof(path), name, "mac");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" MAC: ");
puts(val);
puts("\n");
}
/* Link status */
build_path(path, sizeof(path), name, "link");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Link: ");
puts(val);
puts("\n");
}
/* IP address */
build_path(path, sizeof(path), name, "ip");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" IP: ");
puts(val);
puts("\n");
}
/* Netmask */
build_path(path, sizeof(path), name, "netmask");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Netmask: ");
puts(val);
puts("\n");
}
/* Gateway */
build_path(path, sizeof(path), name, "gateway");
if (read_sysfs(path, val, sizeof(val)) > 0) {
puts(" Gateway: ");
puts(val);
puts("\n");
}
}
/**
* Write a value to a sysfs file.
*/
static int32_t write_sysfs(const char *path, const char *value) {
int32_t fd = open(path, 0);
if (fd < 0) return -1;
int32_t n = write(fd, value, strlen(value));
close(fd);
return n;
}
int main(void) {
char arg1[64];
/* Check if we have a subcommand */
if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') {
/* No arguments — show all interfaces */
char name[128];
uint32_t idx = 0;
int found = 0;
while (readdir("/sys/net", idx, name) >= 0) {
show_iface(name);
found = 1;
idx++;
}
if (!found) {
puts("No network interfaces found.\n");
}
return 0;
}
/* Check for "set" subcommand */
if (strcmp(arg1, "set") == 0) {
char iface_name[32], ip[32], netmask[32], gateway[32];
char path[128];
if (getenv("ARG2", iface_name, sizeof(iface_name)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG3", ip, sizeof(ip)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG4", netmask, sizeof(netmask)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
if (getenv("ARG5", gateway, sizeof(gateway)) < 0) {
puts("Usage: ip set <iface> <ip> <netmask> <gateway>\n");
return 1;
}
/* Write IP */
build_path(path, sizeof(path), iface_name, "ip");
if (write_sysfs(path, ip) < 0) {
puts("Failed to set IP address\n");
return 1;
}
/* Write netmask */
build_path(path, sizeof(path), iface_name, "netmask");
if (write_sysfs(path, netmask) < 0) {
puts("Failed to set netmask\n");
return 1;
}
/* Write gateway */
build_path(path, sizeof(path), iface_name, "gateway");
if (write_sysfs(path, gateway) < 0) {
puts("Failed to set gateway\n");
return 1;
}
puts("Configured ");
puts(iface_name);
puts(": ");
puts(ip);
puts(" / ");
puts(netmask);
puts(" gw ");
puts(gateway);
puts("\n");
return 0;
}
puts("Unknown command: ");
puts(arg1);
puts("\nUsage: ip [set <iface> <ip> <netmask> <gateway>]\n");
return 1;
}

View File

@@ -1,3 +1,6 @@
-- Configuring done (0.1s)
-- Generating done (0.2s)
-- Build files have been written to: /workspaces/claude-os/build
[ 2%] Building user-mode applications
Building app: cat
Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes)
@@ -11,6 +14,8 @@ Building app: fork-test
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
Building app: hello-world
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
Building app: ip
Built: /workspaces/claude-os/build/apps_bin/ip (3695 bytes)
Building app: ls
Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes)
Building app: mkfs.fat32
@@ -29,7 +34,12 @@ Building app: sh
1 warning generated.
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
[ 2%] Built target apps
[ 5%] Generating CPIO initial ramdisk
Generated initrd: 24100 bytes
[ 5%] Built target initrd
[ 8%] Building C object src/CMakeFiles/kernel.dir/kernel.c.o
[ 11%] Building C object src/CMakeFiles/kernel.dir/ipv4.c.o
[ 13%] Linking C executable ../bin/kernel
[ 97%] Built target kernel
[100%] Generating bootable ISO image
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
@@ -38,14 +48,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
Media current: stdio file, overwriteable
Media status : is blank
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
Added to ISO image: directory '/'='/tmp/grub.KhfgHk'
Added to ISO image: directory '/'='/tmp/grub.JjepBC'
xorriso : UPDATE : 581 files added in 1 seconds
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
xorriso : UPDATE : 586 files added in 1 seconds
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
ISO image produced: 5967 sectors
Written to medium : 5967 sectors at LBA 0
xorriso : UPDATE : 0.27% done
ISO image produced: 5974 sectors
Written to medium : 5974 sectors at LBA 0
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
[100%] Built target iso

View File

@@ -28,6 +28,7 @@ add_executable(kernel
ne2000.c
e3c509.c
ethernet.c
ipv4.c
env.c
keyboard.c
interrupts.S

279
src/ipv4.c Normal file
View File

@@ -0,0 +1,279 @@
/**
* @file ipv4.c
* @brief IPv4 network layer implementation.
*
* Handles IPv4 packet construction, parsing, checksum calculation,
* and routing to the appropriate Ethernet interface.
*
* Incoming Ethernet frames with EtherType 0x0800 are passed to
* ipv4_receive(), which validates the header and dispatches to
* registered protocol handlers (ICMP, UDP, TCP, etc.).
*/
#include "ipv4.h"
#include "ethernet.h"
#include <string.h>
/* Debug print helpers */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/* ================================================================
* Global state
* ================================================================ */
/** Maximum number of registered protocol handlers. */
#define MAX_PROTO_HANDLERS 16
/** Protocol handler entry. */
typedef struct {
uint8_t protocol;
ipv4_proto_handler_t handler;
} proto_handler_t;
/** Registered protocol handlers. */
static proto_handler_t proto_handlers[MAX_PROTO_HANDLERS];
static uint32_t proto_handler_count = 0;
/** Global IP identification counter. */
static uint16_t ip_id_counter = 1;
/* ================================================================
* Checksum
* ================================================================ */
uint16_t ipv4_checksum(const void *data, uint32_t len) {
const uint16_t *words = (const uint16_t *)data;
uint32_t sum = 0;
while (len > 1) {
sum += *words++;
len -= 2;
}
/* Add left-over byte, if any */
if (len == 1) {
sum += *(const uint8_t *)words;
}
/* Fold 32-bit sum into 16 bits */
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (uint16_t)(~sum);
}
/* ================================================================
* Address conversion helpers
* ================================================================ */
uint32_t ipv4_aton(const char *str) {
uint32_t octets[4] = {0};
int idx = 0;
for (const char *p = str; *p && idx < 4; p++) {
if (*p == '.') {
idx++;
} else if (*p >= '0' && *p <= '9') {
octets[idx] = octets[idx] * 10 + (uint32_t)(*p - '0');
} else {
break;
}
}
return (octets[0] << 24) | (octets[1] << 16) |
(octets[2] << 8) | octets[3];
}
char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size) {
if (size < 16) { buf[0] = '\0'; return buf; }
int pos = 0;
for (int i = 0; i < 4; i++) {
if (i > 0) buf[pos++] = '.';
uint8_t octet = (uint8_t)((ip >> (24 - i * 8)) & 0xFF);
if (octet >= 100) {
buf[pos++] = (char)('0' + octet / 100);
buf[pos++] = (char)('0' + (octet % 100) / 10);
buf[pos++] = (char)('0' + octet % 10);
} else if (octet >= 10) {
buf[pos++] = (char)('0' + octet / 10);
buf[pos++] = (char)('0' + octet % 10);
} else {
buf[pos++] = (char)('0' + octet);
}
}
buf[pos] = '\0';
return buf;
}
/* ================================================================
* Routing
* ================================================================ */
/**
* Find the Ethernet interface for a given destination IP.
*
* Simple routing: check if dst is on a directly-connected subnet.
* If not, use the first interface with a configured gateway.
* Fallback: first interface.
*
* @param dst_ip Destination IP (host byte order).
* @return Interface index, or -1 if no interfaces.
*/
static int ipv4_route(uint32_t dst_ip) {
uint32_t count = ethernet_get_iface_count();
if (count == 0) return -1;
/* Check for directly-connected subnet */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (!iface || !iface->active) continue;
if (iface->ip_addr == 0 || iface->netmask == 0) continue;
if ((dst_ip & iface->netmask) == (iface->ip_addr & iface->netmask)) {
return (int)i;
}
}
/* Use first interface with a gateway */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (!iface || !iface->active) continue;
if (iface->gateway != 0) return (int)i;
}
/* Fallback: first active interface */
for (uint32_t i = 0; i < count; i++) {
eth_iface_t *iface = ethernet_get_iface(i);
if (iface && iface->active) return (int)i;
}
return -1;
}
/* ================================================================
* Send
* ================================================================ */
int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len) {
if (len > IPV4_MTU) return -1;
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (!iface || !iface->active) return -1;
/* Build IPv4 packet on stack */
uint8_t packet[ETH_MTU];
ipv4_header_t *hdr = (ipv4_header_t *)packet;
hdr->ihl_version = 0x45; /* IPv4, IHL=5 (20 bytes) */
hdr->tos = 0;
hdr->total_length = htons((uint16_t)(IPV4_HLEN + len));
hdr->identification = htons(ip_id_counter++);
hdr->flags_fragoff = htons(IPV4_FLAG_DF); /* Don't fragment */
hdr->ttl = 64;
hdr->protocol = protocol;
hdr->checksum = 0;
hdr->src_ip = htonl(iface->ip_addr);
hdr->dst_ip = htonl(dst_ip);
/* Compute header checksum */
hdr->checksum = ipv4_checksum(hdr, IPV4_HLEN);
/* Copy payload */
memcpy(packet + IPV4_HLEN, payload, len);
/* Determine destination MAC.
* For now, use broadcast (FF:FF:FF:FF:FF:FF) — the ARP subsystem
* will override this once implemented. */
uint8_t dst_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
/* TODO: ARP lookup for dst_ip (or gateway if not on-link) */
return ethernet_send(iface, dst_mac, ETHERTYPE_IPV4, packet,
IPV4_HLEN + len);
}
int ipv4_send(uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len) {
int iface_idx = ipv4_route(dst_ip);
if (iface_idx < 0) return -1;
return ipv4_send_iface((uint32_t)iface_idx, dst_ip, protocol, payload, len);
}
/* ================================================================
* Receive
* ================================================================ */
void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx) {
if (len < IPV4_HLEN) return;
const ipv4_header_t *hdr = (const ipv4_header_t *)data;
/* Verify version */
if ((hdr->ihl_version >> 4) != 4) return;
/* Verify header length */
uint32_t ihl = (uint32_t)(hdr->ihl_version & 0x0F) * 4;
if (ihl < IPV4_HLEN || ihl > len) return;
/* Verify checksum */
if (ipv4_checksum(data, ihl) != 0) return;
uint16_t total_len = ntohs(hdr->total_length);
if (total_len > len) return;
uint32_t src_ip = ntohl(hdr->src_ip);
uint32_t dst_ip = ntohl(hdr->dst_ip);
const uint8_t *payload = (const uint8_t *)data + ihl;
uint32_t payload_len = total_len - ihl;
/* Check if this packet is for us */
eth_iface_t *iface = ethernet_get_iface(iface_idx);
if (iface && iface->ip_addr != 0) {
if (dst_ip != iface->ip_addr &&
dst_ip != 0xFFFFFFFF && /* broadcast */
(dst_ip & ~iface->netmask) != ~iface->netmask) {
return; /* Not for us */
}
}
/* Dispatch to registered protocol handler */
for (uint32_t i = 0; i < proto_handler_count; i++) {
if (proto_handlers[i].protocol == hdr->protocol) {
proto_handlers[i].handler(src_ip, dst_ip, payload,
payload_len, iface_idx);
return;
}
}
/* No handler registered for this protocol */
}
/* ================================================================
* Protocol registration
* ================================================================ */
int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler) {
if (proto_handler_count >= MAX_PROTO_HANDLERS) return -1;
proto_handlers[proto_handler_count].protocol = protocol;
proto_handlers[proto_handler_count].handler = handler;
proto_handler_count++;
return 0;
}
/* ================================================================
* Initialization
* ================================================================ */
void ipv4_init(void) {
memset(proto_handlers, 0, sizeof(proto_handlers));
proto_handler_count = 0;
ip_id_counter = 1;
offset_print(" IPv4: initialized\n");
}

158
src/ipv4.h Normal file
View File

@@ -0,0 +1,158 @@
/**
* @file ipv4.h
* @brief IPv4 network layer.
*
* Provides IPv4 packet construction, parsing, and routing.
* Sits on top of the Ethernet subsystem and provides the foundation
* for higher-level protocols (ICMP, UDP, TCP).
*
* The IPv4 stack maintains a simple routing table: each Ethernet
* interface has an IP address, netmask, and default gateway.
* Outbound packets are routed to the appropriate interface.
*
* Inbound packets are dispatched to registered protocol handlers
* based on the IP protocol field.
*/
#ifndef IPV4_H
#define IPV4_H
#include <stdint.h>
/* ================================================================
* IPv4 header (20 bytes minimum, no options)
* ================================================================ */
/** IPv4 header structure (network byte order in memory). */
typedef struct __attribute__((packed)) ipv4_header {
uint8_t ihl_version; /**< Version (4) and IHL (5 for no options). */
uint8_t tos; /**< Type of Service. */
uint16_t total_length; /**< Total datagram length (header + data). */
uint16_t identification; /**< Fragment identification. */
uint16_t flags_fragoff; /**< Flags (3 bits) + Fragment Offset (13 bits). */
uint8_t ttl; /**< Time to Live. */
uint8_t protocol; /**< Upper-layer protocol number. */
uint16_t checksum; /**< Header checksum. */
uint32_t src_ip; /**< Source IP address (network byte order). */
uint32_t dst_ip; /**< Destination IP address (network byte order). */
} ipv4_header_t;
/** IPv4 header size (no options). */
#define IPV4_HLEN 20
/** Maximum IPv4 payload over Ethernet (1500 - 20). */
#define IPV4_MTU 1480
/* ================================================================
* Protocol numbers
* ================================================================ */
#define IP_PROTO_ICMP 1
#define IP_PROTO_TCP 6
#define IP_PROTO_UDP 17
/* ================================================================
* IPv4 flags
* ================================================================ */
#define IPV4_FLAG_DF 0x4000 /**< Don't Fragment. */
#define IPV4_FLAG_MF 0x2000 /**< More Fragments. */
/* ================================================================
* Protocol handler callback
* ================================================================ */
/**
* Callback for handling incoming IPv4 packets of a specific protocol.
*
* @param src_ip Source IP address (host byte order).
* @param dst_ip Destination IP address (host byte order).
* @param payload Protocol payload (past IPv4 header).
* @param len Payload length.
* @param iface_idx Ethernet interface index the packet arrived on.
*/
typedef void (*ipv4_proto_handler_t)(uint32_t src_ip, uint32_t dst_ip,
const void *payload, uint32_t len,
uint32_t iface_idx);
/* ================================================================
* Public API
* ================================================================ */
/**
* Initialize the IPv4 subsystem.
*/
void ipv4_init(void);
/**
* Send an IPv4 packet.
*
* Automatically routes to the correct Ethernet interface based on
* the destination IP and configured routes.
*
* @param dst_ip Destination IP (host byte order).
* @param protocol IP protocol number (e.g., IP_PROTO_UDP).
* @param payload Packet payload.
* @param len Payload length (max IPV4_MTU).
* @return 0 on success, -1 on failure.
*/
int ipv4_send(uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len);
/**
* Send an IPv4 packet through a specific interface.
*
* @param iface_idx Ethernet interface index.
* @param dst_ip Destination IP (host byte order).
* @param protocol IP protocol number.
* @param payload Packet payload.
* @param len Payload length.
* @return 0 on success, -1 on failure.
*/
int ipv4_send_iface(uint32_t iface_idx, uint32_t dst_ip, uint8_t protocol,
const void *payload, uint32_t len);
/**
* Process an incoming IPv4 packet (called from Ethernet layer).
*
* @param data Raw IPv4 packet (header + payload).
* @param len Total packet length.
* @param iface_idx Interface index the packet arrived on.
*/
void ipv4_receive(const void *data, uint32_t len, uint32_t iface_idx);
/**
* Register a protocol handler.
*
* @param protocol IP protocol number.
* @param handler Callback function.
* @return 0 on success, -1 if table is full.
*/
int ipv4_register_proto(uint8_t protocol, ipv4_proto_handler_t handler);
/**
* Compute the Internet checksum over a buffer.
*
* @param data Buffer.
* @param len Length in bytes.
* @return Checksum in network byte order.
*/
uint16_t ipv4_checksum(const void *data, uint32_t len);
/**
* Convert an IPv4 address string "A.B.C.D" to a uint32_t (host byte order).
*
* @param str Dotted-decimal string.
* @return IPv4 address, or 0 on failure.
*/
uint32_t ipv4_aton(const char *str);
/**
* Format an IPv4 address (host byte order) into a dotted-decimal string.
*
* @param ip IPv4 address.
* @param buf Output buffer (at least 16 bytes).
* @param size Buffer size.
* @return Pointer to buf.
*/
char *ipv4_ntoa(uint32_t ip, char *buf, uint32_t size);
#endif /* IPV4_H */

View File

@@ -23,6 +23,7 @@
#include "fat32.h"
#include "keyboard.h"
#include "ethernet.h"
#include "ipv4.h"
#include "framebuffer.h"
/* Global framebuffer info, parsed from multiboot2 tags. */
@@ -419,6 +420,9 @@ void kernel_main(uint32_t magic, uint32_t addr) {
ethernet_init();
offset_print("Ethernet subsystem initialized\n");
ipv4_init();
offset_print("IPv4 stack initialized\n");
init_drivers();
EARLY_PRINT("DRV ");
offset_print("Drivers initialized\n");