From e6929438a06234d1e9c7ad89260dc9370f070a32 Mon Sep 17 00:00:00 2001 From: AI Date: Tue, 24 Feb 2026 07:43:45 +0000 Subject: [PATCH] Implement UDP and TCP stack (AI) --- README.md | 2 +- build.log | 16 +- src/CMakeLists.txt | 2 + src/kernel.c | 8 + src/tcp.c | 512 +++++++++++++++++++++++++++++++++++++++++++++ src/tcp.h | 169 +++++++++++++++ src/udp.c | 180 ++++++++++++++++ src/udp.h | 128 ++++++++++++ 8 files changed, 1005 insertions(+), 12 deletions(-) create mode 100644 src/tcp.c create mode 100644 src/tcp.h create mode 100644 src/udp.c create mode 100644 src/udp.h diff --git a/README.md b/README.md index ff5d9f1..5ddac5a 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Once a task is completed, it should be checked off. - [x] Create a IPv4 stack. Create the `ip` app that shows curernt IPv4 configuration. It should read this information from `/sys` - [x] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys` - [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information. -- [ ] Create a UDP and TCP stack. +- [x] Create a UDP and TCP stack. - [ ] Implement a simple version of `ftp` and `wget`. - [ ] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode. - [ ] Create a simple game of pool. It should use graphics mode to render the game. diff --git a/build.log b/build.log index 6a56e07..d77619b 100644 --- a/build.log +++ b/build.log @@ -1,6 +1,3 @@ --- 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: arp Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes) @@ -38,12 +35,9 @@ 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: 24768 bytes [ 5%] Built target initrd -[ 7%] Building C object src/CMakeFiles/kernel.dir/kernel.c.o -[ 10%] Building C object src/CMakeFiles/kernel.dir/dhcp.c.o -[ 13%] Linking C executable ../bin/kernel +[ 7%] Building C object src/CMakeFiles/kernel.dir/tcp.c.o +[ 10%] Linking C executable ../bin/kernel [ 97%] Built target kernel [100%] Generating bootable ISO image xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. @@ -52,14 +46,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.dJBlkG' +Added to ISO image: directory '/'='/tmp/grub.onhBjG' 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: 6009 sectors -Written to medium : 6009 sectors at LBA 0 +ISO image produced: 6027 sectors +Written to medium : 6027 sectors at LBA 0 Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. [100%] Built target iso diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4e10de..9c1d680 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,6 +31,8 @@ add_executable(kernel ipv4.c arp.c dhcp.c + udp.c + tcp.c env.c keyboard.c interrupts.S diff --git a/src/kernel.c b/src/kernel.c index 8014e43..d8ccb69 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -26,6 +26,8 @@ #include "ipv4.h" #include "arp.h" #include "dhcp.h" +#include "udp.h" +#include "tcp.h" #include "framebuffer.h" /* Global framebuffer info, parsed from multiboot2 tags. */ @@ -431,6 +433,12 @@ void kernel_main(uint32_t magic, uint32_t addr) { dhcp_init(); offset_print("DHCP subsystem initialized\n"); + udp_init(); + offset_print("UDP stack initialized\n"); + + tcp_init(); + offset_print("TCP stack initialized\n"); + init_drivers(); EARLY_PRINT("DRV "); offset_print("Drivers initialized\n"); diff --git a/src/tcp.c b/src/tcp.c new file mode 100644 index 0000000..50e8319 --- /dev/null +++ b/src/tcp.c @@ -0,0 +1,512 @@ +/** + * @file tcp.c + * @brief Transmission Control Protocol (TCP) implementation. + * + * Minimal TCP state machine supporting: + * - Active open (connect) + * - Data transfer (send/recv) + * - Connection teardown (close with FIN) + * + * Limitations: + * - No retransmission timer (single-attempt) + * - No congestion control + * - No passive open (listen/accept) yet + * - No out-of-order segment handling + * - Window size is static + */ + +#include "tcp.h" +#include "ipv4.h" +#include "ethernet.h" +#include "kmalloc.h" +#include "string.h" +#include "vga.h" + +/* ================================================================ + * Internal state + * ================================================================ */ + +static tcp_socket_t tcp_sockets[TCP_MAX_SOCKETS]; +static uint16_t tcp_next_port = 49152; /* Ephemeral port start. */ + +/* Simple pseudo-random ISN based on a counter. */ +static uint32_t tcp_isn_counter = 0x12345678; + +static uint32_t tcp_generate_isn(void) { + tcp_isn_counter = tcp_isn_counter * 1103515245 + 12345; + return tcp_isn_counter; +} + +/* ================================================================ + * TCP pseudo-header checksum + * ================================================================ */ + +/** + * Compute TCP checksum including the pseudo-header. + */ +static uint16_t tcp_checksum(uint32_t src_ip, uint32_t dst_ip, + const void *tcp_seg, uint32_t tcp_len) +{ + uint32_t sum = 0; + + /* Pseudo-header */ + sum += (src_ip >> 16) & 0xFFFF; + sum += src_ip & 0xFFFF; + sum += (dst_ip >> 16) & 0xFFFF; + sum += dst_ip & 0xFFFF; + sum += htons(6); /* IP_PROTO_TCP */ + sum += htons(tcp_len); + + /* TCP segment */ + const uint16_t *p = (const uint16_t *)tcp_seg; + uint32_t rem = tcp_len; + while (rem > 1) { + sum += *p++; + rem -= 2; + } + if (rem == 1) { + sum += *((const uint8_t *)p); + } + + /* Fold carries */ + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + return (uint16_t)(~sum); +} + +/* ================================================================ + * Send a TCP segment + * ================================================================ */ + +static int tcp_send_segment(tcp_socket_t *sock, uint8_t flags, + const void *data, uint32_t data_len) +{ + uint32_t total_len = TCP_HLEN + data_len; + uint8_t buf[TCP_HLEN + TCP_MSS]; + + if (total_len > sizeof(buf)) return -1; + + /* Build TCP header */ + tcp_header_t *hdr = (tcp_header_t *)buf; + hdr->src_port = htons(sock->local_port); + hdr->dst_port = htons(sock->remote_port); + hdr->seq_num = htonl(sock->snd_nxt); + hdr->ack_num = (flags & TCP_ACK) ? htonl(sock->rcv_nxt) : 0; + hdr->data_offset = (TCP_HLEN / 4) << 4; /* 5 words, no options */ + hdr->flags = flags; + hdr->window = htons(TCP_RX_BUF_SIZE - sock->rx_count); + hdr->checksum = 0; + hdr->urgent = 0; + + /* Copy payload */ + if (data && data_len > 0) { + memcpy(buf + TCP_HLEN, data, data_len); + } + + /* Compute local IP from first ethernet interface. */ + eth_iface_t *iface = ethernet_get_iface(0); + uint32_t src_ip = iface ? iface->ip_addr : 0; + + /* Compute checksum over the whole segment. */ + hdr->checksum = tcp_checksum(htonl(src_ip), htonl(sock->remote_ip), + buf, total_len); + + /* Advance SND.NXT for SYN, FIN, and data. */ + if (flags & TCP_SYN) + sock->snd_nxt++; + if (flags & TCP_FIN) + sock->snd_nxt++; + sock->snd_nxt += data_len; + + /* Send via IPv4. */ + return ipv4_send(sock->remote_ip, IP_PROTO_TCP, buf, total_len); +} + +/* ================================================================ + * Send a RST in response to an unexpected segment + * ================================================================ */ + +static void tcp_send_rst(uint32_t src_ip, uint32_t dst_ip, + const tcp_header_t *in_hdr, uint32_t seg_len) +{ + uint8_t buf[TCP_HLEN]; + tcp_header_t *hdr = (tcp_header_t *)buf; + + memset(buf, 0, TCP_HLEN); + hdr->src_port = in_hdr->dst_port; /* Already in network byte order. */ + hdr->dst_port = in_hdr->src_port; + hdr->data_offset = (TCP_HLEN / 4) << 4; + + if (in_hdr->flags & TCP_ACK) { + hdr->seq_num = in_hdr->ack_num; + hdr->flags = TCP_RST; + } else { + hdr->seq_num = 0; + hdr->ack_num = htonl(ntohl(in_hdr->seq_num) + seg_len); + hdr->flags = TCP_RST | TCP_ACK; + } + + hdr->window = 0; + hdr->checksum = tcp_checksum(htonl(dst_ip), htonl(src_ip), + buf, TCP_HLEN); + + ipv4_send(src_ip, IP_PROTO_TCP, buf, TCP_HLEN); +} + +/* ================================================================ + * Find a socket matching an incoming segment + * ================================================================ */ + +static tcp_socket_t *tcp_find_socket(uint32_t remote_ip, + uint16_t remote_port, + uint16_t local_port) +{ + for (int i = 0; i < TCP_MAX_SOCKETS; i++) { + tcp_socket_t *s = &tcp_sockets[i]; + if (!s->active) continue; + if (s->local_port != local_port) continue; + if (s->state == TCP_STATE_LISTEN) return s; + if (s->remote_port == remote_port && s->remote_ip == remote_ip) + return s; + } + return NULL; +} + +/* ================================================================ + * Ring buffer helpers + * ================================================================ */ + +static void rx_buf_write(tcp_socket_t *sock, const uint8_t *data, uint32_t len) { + for (uint32_t i = 0; i < len && sock->rx_count < TCP_RX_BUF_SIZE; i++) { + sock->rx_buf[sock->rx_head] = data[i]; + sock->rx_head = (sock->rx_head + 1) % TCP_RX_BUF_SIZE; + sock->rx_count++; + } +} + +static uint32_t rx_buf_read(tcp_socket_t *sock, uint8_t *buf, uint32_t len) { + uint32_t n = 0; + while (n < len && sock->rx_count > 0) { + buf[n++] = sock->rx_buf[sock->rx_tail]; + sock->rx_tail = (sock->rx_tail + 1) % TCP_RX_BUF_SIZE; + sock->rx_count--; + } + return n; +} + +/* ================================================================ + * TCP input processing + * ================================================================ */ + +void tcp_receive(uint32_t src_ip_ho, uint32_t dst_ip_ho, + const void *data, uint32_t len, uint32_t iface_idx) +{ + (void)iface_idx; + + if (len < TCP_HLEN) return; + + const tcp_header_t *hdr = (const tcp_header_t *)data; + uint32_t hdr_len = ((hdr->data_offset >> 4) & 0x0F) * 4; + if (hdr_len < TCP_HLEN || hdr_len > len) return; + + uint16_t src_port = ntohs(hdr->src_port); + uint16_t dst_port = ntohs(hdr->dst_port); + uint32_t seq = ntohl(hdr->seq_num); + uint32_t ack = ntohl(hdr->ack_num); + uint8_t flags = hdr->flags; + const uint8_t *payload = (const uint8_t *)data + hdr_len; + uint32_t payload_len = len - hdr_len; + + /* Compute segment length (SYN and FIN count as 1 byte each). */ + uint32_t seg_len = payload_len; + if (flags & TCP_SYN) seg_len++; + if (flags & TCP_FIN) seg_len++; + + /* Find matching socket. */ + tcp_socket_t *sock = tcp_find_socket(src_ip_ho, src_port, dst_port); + + if (!sock) { + /* No socket found — send RST if not already RST. */ + if (!(flags & TCP_RST)) { + tcp_send_rst(src_ip_ho, dst_ip_ho, hdr, seg_len); + } + return; + } + + /* Handle RST. */ + if (flags & TCP_RST) { + sock->state = TCP_STATE_CLOSED; + sock->active = 0; + return; + } + + switch (sock->state) { + case TCP_STATE_SYN_SENT: + /* Expecting SYN+ACK */ + if ((flags & (TCP_SYN | TCP_ACK)) == (TCP_SYN | TCP_ACK)) { + if (ack == sock->snd_nxt) { + sock->rcv_nxt = seq + 1; + sock->snd_una = ack; + sock->state = TCP_STATE_ESTABLISHED; + /* Send ACK */ + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } + } else if (flags & TCP_SYN) { + /* Simultaneous open — simplified handling. */ + sock->rcv_nxt = seq + 1; + sock->state = TCP_STATE_SYN_RECEIVED; + tcp_send_segment(sock, TCP_SYN | TCP_ACK, NULL, 0); + } + break; + + case TCP_STATE_SYN_RECEIVED: + if (flags & TCP_ACK) { + if (ack == sock->snd_nxt) { + sock->snd_una = ack; + sock->state = TCP_STATE_ESTABLISHED; + } + } + break; + + case TCP_STATE_ESTABLISHED: + /* Check sequence number. */ + if (seq != sock->rcv_nxt) { + /* Out-of-order — send duplicate ACK. */ + tcp_send_segment(sock, TCP_ACK, NULL, 0); + break; + } + + /* Update SND.UNA if ACK present. */ + if (flags & TCP_ACK) { + sock->snd_una = ack; + } + + /* Process payload. */ + if (payload_len > 0) { + rx_buf_write(sock, payload, payload_len); + sock->rcv_nxt += payload_len; + } + + /* Handle FIN. */ + if (flags & TCP_FIN) { + sock->rcv_nxt++; + sock->state = TCP_STATE_CLOSE_WAIT; + /* ACK the FIN. */ + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } else if (payload_len > 0) { + /* ACK the data. */ + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } + break; + + case TCP_STATE_FIN_WAIT_1: + if (flags & TCP_ACK) { + sock->snd_una = ack; + } + if ((flags & TCP_FIN) && (flags & TCP_ACK) && ack == sock->snd_nxt) { + /* FIN+ACK: simultaneous close shortcut. */ + sock->rcv_nxt++; + sock->state = TCP_STATE_TIME_WAIT; + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } else if (flags & TCP_FIN) { + sock->rcv_nxt++; + sock->state = TCP_STATE_CLOSING; + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } else if ((flags & TCP_ACK) && ack == sock->snd_nxt) { + /* Our FIN was ACKed. */ + sock->state = TCP_STATE_FIN_WAIT_2; + } + /* Accept any data in FIN_WAIT_1 */ + if (payload_len > 0 && seq == sock->rcv_nxt) { + rx_buf_write(sock, payload, payload_len); + sock->rcv_nxt += payload_len; + } + break; + + case TCP_STATE_FIN_WAIT_2: + if (payload_len > 0 && seq == sock->rcv_nxt) { + rx_buf_write(sock, payload, payload_len); + sock->rcv_nxt += payload_len; + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } + if (flags & TCP_FIN) { + sock->rcv_nxt++; + sock->state = TCP_STATE_TIME_WAIT; + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } + break; + + case TCP_STATE_CLOSE_WAIT: + /* Waiting for application to close. Accept ACKs. */ + if (flags & TCP_ACK) { + sock->snd_una = ack; + } + break; + + case TCP_STATE_CLOSING: + if ((flags & TCP_ACK) && ack == sock->snd_nxt) { + sock->state = TCP_STATE_TIME_WAIT; + } + break; + + case TCP_STATE_LAST_ACK: + if ((flags & TCP_ACK) && ack == sock->snd_nxt) { + sock->state = TCP_STATE_CLOSED; + sock->active = 0; + } + break; + + case TCP_STATE_TIME_WAIT: + /* In a real OS we'd wait 2*MSL. Here we just ACK and stay. */ + if (flags & TCP_FIN) { + tcp_send_segment(sock, TCP_ACK, NULL, 0); + } + /* Immediately transition to CLOSED (no timer). */ + sock->state = TCP_STATE_CLOSED; + sock->active = 0; + break; + + default: + break; + } +} + +/* ================================================================ + * Public API + * ================================================================ */ + +void tcp_init(void) +{ + memset(tcp_sockets, 0, sizeof(tcp_sockets)); + ipv4_register_proto(IP_PROTO_TCP, tcp_receive); + vga_puts("[TCP] Initialized ("); + vga_put_dec(TCP_MAX_SOCKETS); + vga_puts(" sockets)\n"); +} + +int tcp_socket_create(void) +{ + for (int i = 0; i < TCP_MAX_SOCKETS; i++) { + if (!tcp_sockets[i].active) { + memset(&tcp_sockets[i], 0, sizeof(tcp_socket_t)); + tcp_sockets[i].active = 1; + tcp_sockets[i].state = TCP_STATE_CLOSED; + return i; + } + } + return -1; +} + +int tcp_connect(int sockfd, uint32_t remote_ip, uint16_t remote_port) +{ + if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1; + tcp_socket_t *sock = &tcp_sockets[sockfd]; + if (!sock->active || sock->state != TCP_STATE_CLOSED) return -1; + + /* Assign ephemeral local port. */ + sock->local_port = tcp_next_port++; + if (tcp_next_port == 0) tcp_next_port = 49152; + sock->remote_ip = remote_ip; + sock->remote_port = remote_port; + + /* Generate ISN. */ + sock->snd_nxt = tcp_generate_isn(); + sock->snd_una = sock->snd_nxt; + + /* Send SYN. */ + sock->state = TCP_STATE_SYN_SENT; + return tcp_send_segment(sock, TCP_SYN, NULL, 0); +} + +int tcp_send(int sockfd, const void *data, uint32_t len) +{ + if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1; + tcp_socket_t *sock = &tcp_sockets[sockfd]; + if (!sock->active || sock->state != TCP_STATE_ESTABLISHED) return -1; + + /* Send in MSS-sized chunks. */ + uint32_t sent = 0; + const uint8_t *p = (const uint8_t *)data; + while (sent < len) { + uint32_t chunk = len - sent; + if (chunk > TCP_MSS) chunk = TCP_MSS; + if (tcp_send_segment(sock, TCP_ACK | TCP_PSH, p + sent, chunk) < 0) + break; + sent += chunk; + } + return (int)sent; +} + +int tcp_recv(int sockfd, void *buf, uint32_t bufsize) +{ + if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return -1; + tcp_socket_t *sock = &tcp_sockets[sockfd]; + if (!sock->active) return -1; + + /* If connection is closed with data still in buffer, return it. */ + if (sock->rx_count == 0) { + if (sock->state == TCP_STATE_CLOSE_WAIT || + sock->state == TCP_STATE_CLOSED) { + return -1; /* EOF / connection closed. */ + } + return 0; /* No data available yet. */ + } + + return (int)rx_buf_read(sock, (uint8_t *)buf, bufsize); +} + +void tcp_close(int sockfd) +{ + if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return; + tcp_socket_t *sock = &tcp_sockets[sockfd]; + if (!sock->active) return; + + switch (sock->state) { + case TCP_STATE_ESTABLISHED: + sock->state = TCP_STATE_FIN_WAIT_1; + tcp_send_segment(sock, TCP_FIN | TCP_ACK, NULL, 0); + break; + + case TCP_STATE_CLOSE_WAIT: + sock->state = TCP_STATE_LAST_ACK; + tcp_send_segment(sock, TCP_FIN | TCP_ACK, NULL, 0); + break; + + case TCP_STATE_SYN_SENT: + case TCP_STATE_SYN_RECEIVED: + sock->state = TCP_STATE_CLOSED; + sock->active = 0; + break; + + default: + /* Already closing or closed — force close. */ + sock->state = TCP_STATE_CLOSED; + sock->active = 0; + break; + } +} + +uint8_t tcp_get_state(int sockfd) +{ + if (sockfd < 0 || sockfd >= TCP_MAX_SOCKETS) return TCP_STATE_CLOSED; + return tcp_sockets[sockfd].state; +} + +const char *tcp_state_name(uint8_t state) +{ + switch (state) { + case TCP_STATE_CLOSED: return "CLOSED"; + case TCP_STATE_LISTEN: return "LISTEN"; + case TCP_STATE_SYN_SENT: return "SYN_SENT"; + case TCP_STATE_SYN_RECEIVED: return "SYN_RECEIVED"; + case TCP_STATE_ESTABLISHED: return "ESTABLISHED"; + case TCP_STATE_FIN_WAIT_1: return "FIN_WAIT_1"; + case TCP_STATE_FIN_WAIT_2: return "FIN_WAIT_2"; + case TCP_STATE_CLOSE_WAIT: return "CLOSE_WAIT"; + case TCP_STATE_CLOSING: return "CLOSING"; + case TCP_STATE_LAST_ACK: return "LAST_ACK"; + case TCP_STATE_TIME_WAIT: return "TIME_WAIT"; + default: return "UNKNOWN"; + } +} diff --git a/src/tcp.h b/src/tcp.h new file mode 100644 index 0000000..22ded53 --- /dev/null +++ b/src/tcp.h @@ -0,0 +1,169 @@ +/** + * @file tcp.h + * @brief Transmission Control Protocol (TCP) subsystem. + * + * Provides a minimal TCP implementation supporting active open + * (client connections). Implements the core TCP state machine + * for connection setup (SYN/SYN-ACK/ACK), data transfer, and + * connection teardown (FIN/ACK). + * + * Built on top of the IPv4 stack. + */ + +#ifndef TCP_H +#define TCP_H + +#include + +/** Maximum number of TCP sockets. */ +#define TCP_MAX_SOCKETS 8 + +/** Maximum TCP segment payload. */ +#define TCP_MSS 1460 /* ETH_MTU(1500) - IP(20) - TCP(20) */ + +/** TCP header size (no options). */ +#define TCP_HLEN 20 + +/** TCP receive buffer size per socket. */ +#define TCP_RX_BUF_SIZE 4096 + +/** TCP send buffer size per socket. */ +#define TCP_TX_BUF_SIZE 4096 + +/* ================================================================ + * TCP flags + * ================================================================ */ +#define TCP_FIN 0x01 +#define TCP_SYN 0x02 +#define TCP_RST 0x04 +#define TCP_PSH 0x08 +#define TCP_ACK 0x10 +#define TCP_URG 0x20 + +/* ================================================================ + * TCP states (RFC 793) + * ================================================================ */ +#define TCP_STATE_CLOSED 0 +#define TCP_STATE_LISTEN 1 +#define TCP_STATE_SYN_SENT 2 +#define TCP_STATE_SYN_RECEIVED 3 +#define TCP_STATE_ESTABLISHED 4 +#define TCP_STATE_FIN_WAIT_1 5 +#define TCP_STATE_FIN_WAIT_2 6 +#define TCP_STATE_CLOSE_WAIT 7 +#define TCP_STATE_CLOSING 8 +#define TCP_STATE_LAST_ACK 9 +#define TCP_STATE_TIME_WAIT 10 + +/* ================================================================ + * TCP header + * ================================================================ */ + +typedef struct __attribute__((packed)) tcp_header { + uint16_t src_port; /**< Source port. */ + uint16_t dst_port; /**< Destination port. */ + uint32_t seq_num; /**< Sequence number. */ + uint32_t ack_num; /**< Acknowledgment number. */ + uint8_t data_offset; /**< Data offset (upper 4 bits) + reserved. */ + uint8_t flags; /**< TCP flags. */ + uint16_t window; /**< Window size. */ + uint16_t checksum; /**< Checksum. */ + uint16_t urgent; /**< Urgent pointer. */ +} tcp_header_t; + +/* ================================================================ + * TCP socket + * ================================================================ */ + +typedef struct tcp_socket { + uint8_t active; /**< 1 if in use. */ + uint8_t state; /**< TCP_STATE_*. */ + uint16_t local_port; /**< Local port (host byte order). */ + uint16_t remote_port; /**< Remote port (host byte order). */ + uint32_t remote_ip; /**< Remote IP (host byte order). */ + + /* Sequence numbers */ + uint32_t snd_una; /**< Send unacknowledged. */ + uint32_t snd_nxt; /**< Send next. */ + uint32_t rcv_nxt; /**< Receive next expected. */ + + /* Receive buffer (ring buffer) */ + uint8_t rx_buf[TCP_RX_BUF_SIZE]; + uint32_t rx_head; /**< Write position. */ + uint32_t rx_tail; /**< Read position. */ + uint32_t rx_count; /**< Bytes available. */ +} tcp_socket_t; + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize the TCP subsystem. + */ +void tcp_init(void); + +/** + * Create a TCP socket. + * @return Socket index (>= 0), or -1 on failure. + */ +int tcp_socket_create(void); + +/** + * Connect to a remote host (active open). + * Sends SYN and transitions to SYN_SENT state. + * + * @param sockfd Socket index. + * @param remote_ip Remote IP (host byte order). + * @param remote_port Remote port (host byte order). + * @return 0 on success (SYN sent), -1 on failure. + */ +int tcp_connect(int sockfd, uint32_t remote_ip, uint16_t remote_port); + +/** + * Send data on an established connection. + * + * @param sockfd Socket index. + * @param data Data to send. + * @param len Data length. + * @return Number of bytes sent, or -1 on failure. + */ +int tcp_send(int sockfd, const void *data, uint32_t len); + +/** + * Receive data from an established connection (non-blocking). + * + * @param sockfd Socket index. + * @param buf Buffer. + * @param bufsize Buffer size. + * @return Number of bytes received, 0 if no data, -1 on error/closed. + */ +int tcp_recv(int sockfd, void *buf, uint32_t bufsize); + +/** + * Close a TCP connection. + * + * @param sockfd Socket index. + */ +void tcp_close(int sockfd); + +/** + * Get the state of a TCP socket. + * + * @param sockfd Socket index. + * @return TCP state constant, or TCP_STATE_CLOSED for invalid sockets. + */ +uint8_t tcp_get_state(int sockfd); + +/** + * Get the state name as a string. + */ +const char *tcp_state_name(uint8_t state); + +/** + * Process an incoming TCP segment (called from IPv4 layer). + */ +void tcp_receive(uint32_t src_ip, uint32_t dst_ip, + const void *data, uint32_t len, uint32_t iface_idx); + +#endif /* TCP_H */ diff --git a/src/udp.c b/src/udp.c new file mode 100644 index 0000000..451deef --- /dev/null +++ b/src/udp.c @@ -0,0 +1,180 @@ +/** + * @file udp.c + * @brief UDP subsystem implementation. + * + * Provides a simple socket-like interface for sending and receiving + * UDP datagrams. Registers with the IPv4 stack as protocol handler 17. + */ + +#include "udp.h" +#include "ipv4.h" +#include "ethernet.h" +#include + +/* Debug print helpers */ +extern void offset_print(const char *str); +extern void print_hex(uint32_t val); + +/* ================================================================ + * Global state + * ================================================================ */ + +/** UDP socket table. */ +static udp_socket_t sockets[UDP_MAX_SOCKETS]; + +/* ================================================================ + * Internal helpers + * ================================================================ */ + +/** + * Find a socket bound to the given local port. + */ +static udp_socket_t *find_socket_by_port(uint16_t port) { + for (int i = 0; i < UDP_MAX_SOCKETS; i++) { + if (sockets[i].active && sockets[i].bound && + sockets[i].local_port == port) { + return &sockets[i]; + } + } + return NULL; +} + +/* ================================================================ + * Public API + * ================================================================ */ + +int udp_socket_create(void) { + for (int i = 0; i < UDP_MAX_SOCKETS; i++) { + if (!sockets[i].active) { + memset(&sockets[i], 0, sizeof(udp_socket_t)); + sockets[i].active = 1; + return i; + } + } + return -1; +} + +int udp_bind(int sockfd, uint16_t port) { + if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1; + if (!sockets[sockfd].active) return -1; + + /* Check port not already used */ + if (find_socket_by_port(port)) return -1; + + sockets[sockfd].local_port = port; + sockets[sockfd].bound = 1; + return 0; +} + +int udp_sendto(int sockfd, uint32_t dst_ip, uint16_t dst_port, + const void *data, uint32_t len) { + if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1; + if (!sockets[sockfd].active) return -1; + if (len > UDP_MAX_PAYLOAD) return -1; + + /* Build UDP datagram */ + uint8_t pkt[UDP_HLEN + UDP_MAX_PAYLOAD]; + udp_header_t *hdr = (udp_header_t *)pkt; + + hdr->src_port = htons(sockets[sockfd].local_port); + hdr->dst_port = htons(dst_port); + hdr->length = htons((uint16_t)(UDP_HLEN + len)); + hdr->checksum = 0; /* Optional in IPv4 */ + + memcpy(pkt + UDP_HLEN, data, len); + + /* Send via IPv4 */ + int ret = ipv4_send(dst_ip, IP_PROTO_UDP, pkt, UDP_HLEN + len); + return (ret == 0) ? (int)len : -1; +} + +int udp_recvfrom(int sockfd, void *buf, uint32_t bufsize, + uint32_t *src_ip, uint16_t *src_port) { + if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return -1; + if (!sockets[sockfd].active) return -1; + + udp_socket_t *sock = &sockets[sockfd]; + + /* Scan the receive queue for the oldest entry */ + for (int i = 0; i < UDP_RX_QUEUE_SIZE; i++) { + if (sock->rx_queue[i].used) { + udp_rx_entry_t *entry = &sock->rx_queue[i]; + + uint32_t copy_len = entry->len; + if (copy_len > bufsize) copy_len = bufsize; + + memcpy(buf, entry->data, copy_len); + if (src_ip) *src_ip = entry->src_ip; + if (src_port) *src_port = entry->src_port; + + entry->used = 0; /* Free the slot */ + return (int)copy_len; + } + } + + return 0; /* No data available */ +} + +void udp_close(int sockfd) { + if (sockfd < 0 || sockfd >= UDP_MAX_SOCKETS) return; + memset(&sockets[sockfd], 0, sizeof(udp_socket_t)); +} + +/* ================================================================ + * IPv4 protocol handler + * ================================================================ */ + +void udp_receive(uint32_t src_ip, uint32_t dst_ip, + const void *data, uint32_t len, uint32_t iface_idx) { + (void)dst_ip; + (void)iface_idx; + + if (len < UDP_HLEN) return; + + const udp_header_t *hdr = (const udp_header_t *)data; + uint16_t dst_port = ntohs(hdr->dst_port); + uint16_t src_port_val = ntohs(hdr->src_port); + uint16_t udp_len = ntohs(hdr->length); + + if (udp_len > len) return; + uint32_t payload_len = udp_len - UDP_HLEN; + + /* Find socket bound to this port */ + udp_socket_t *sock = find_socket_by_port(dst_port); + if (!sock) return; + + /* Enqueue the datagram */ + for (int i = 0; i < UDP_RX_QUEUE_SIZE; i++) { + if (!sock->rx_queue[i].used) { + udp_rx_entry_t *entry = &sock->rx_queue[i]; + entry->src_ip = src_ip; + entry->src_port = src_port_val; + entry->len = (uint16_t)(payload_len > UDP_RX_BUF_SIZE ? + UDP_RX_BUF_SIZE : payload_len); + memcpy(entry->data, (const uint8_t *)data + UDP_HLEN, + entry->len); + entry->used = 1; + return; + } + } + /* Queue full — drop packet */ +} + +/* ================================================================ + * Initialization + * ================================================================ */ + +/** + * IPv4 callback wrapper for UDP. + */ +static void udp_ipv4_handler(uint32_t src_ip, uint32_t dst_ip, + const void *payload, uint32_t len, + uint32_t iface_idx) { + udp_receive(src_ip, dst_ip, payload, len, iface_idx); +} + +void udp_init(void) { + memset(sockets, 0, sizeof(sockets)); + ipv4_register_proto(IP_PROTO_UDP, udp_ipv4_handler); + offset_print(" UDP: initialized\n"); +} diff --git a/src/udp.h b/src/udp.h new file mode 100644 index 0000000..b30bd2e --- /dev/null +++ b/src/udp.h @@ -0,0 +1,128 @@ +/** + * @file udp.h + * @brief User Datagram Protocol (UDP) subsystem. + * + * Provides a simple UDP socket interface built on top of the IPv4 stack. + * Supports connectionless datagram communication. + */ + +#ifndef UDP_H +#define UDP_H + +#include + +/** Maximum number of UDP sockets. */ +#define UDP_MAX_SOCKETS 16 + +/** Maximum UDP payload size. */ +#define UDP_MAX_PAYLOAD 1472 /* ETH_MTU(1500) - IP(20) - UDP(8) */ + +/** UDP header size. */ +#define UDP_HLEN 8 + +/* ================================================================ + * UDP header + * ================================================================ */ + +typedef struct __attribute__((packed)) udp_header { + uint16_t src_port; /**< Source port (network byte order). */ + uint16_t dst_port; /**< Destination port (network byte order). */ + uint16_t length; /**< Total length (header + payload, NBO). */ + uint16_t checksum; /**< Checksum (0 = not computed). */ +} udp_header_t; + +/* ================================================================ + * UDP socket + * ================================================================ */ + +/** Maximum number of queued received datagrams per socket. */ +#define UDP_RX_QUEUE_SIZE 8 + +/** Maximum datagram size in the receive queue. */ +#define UDP_RX_BUF_SIZE 1500 + +/** Received datagram in the queue. */ +typedef struct udp_rx_entry { + uint32_t src_ip; /**< Source IP (host byte order). */ + uint16_t src_port; /**< Source port (host byte order). */ + uint16_t len; /**< Payload length. */ + uint8_t data[UDP_RX_BUF_SIZE]; /**< Payload data. */ + uint8_t used; /**< 1 if slot is occupied. */ +} udp_rx_entry_t; + +/** + * UDP socket state. + */ +typedef struct udp_socket { + uint16_t local_port; /**< Bound local port (host byte order). */ + uint8_t active; /**< 1 if socket is in use. */ + uint8_t bound; /**< 1 if bound to a port. */ + udp_rx_entry_t rx_queue[UDP_RX_QUEUE_SIZE]; /**< Receive queue. */ + uint32_t rx_head; /**< Next slot to write. */ +} udp_socket_t; + +/* ================================================================ + * Public API + * ================================================================ */ + +/** + * Initialize the UDP subsystem. + * Registers as an IPv4 protocol handler. + */ +void udp_init(void); + +/** + * Create a UDP socket. + * @return Socket index (>= 0) on success, -1 on failure. + */ +int udp_socket_create(void); + +/** + * Bind a socket to a local port. + * + * @param sockfd Socket index. + * @param port Local port number (host byte order). + * @return 0 on success, -1 on failure. + */ +int udp_bind(int sockfd, uint16_t port); + +/** + * Send a UDP datagram. + * + * @param sockfd Socket index. + * @param dst_ip Destination IP (host byte order). + * @param dst_port Destination port (host byte order). + * @param data Payload data. + * @param len Payload length (max UDP_MAX_PAYLOAD). + * @return Number of bytes sent, or -1 on failure. + */ +int udp_sendto(int sockfd, uint32_t dst_ip, uint16_t dst_port, + const void *data, uint32_t len); + +/** + * Receive a UDP datagram (non-blocking). + * + * @param sockfd Socket index. + * @param buf Buffer for payload. + * @param bufsize Buffer size. + * @param src_ip Output: source IP (host byte order, can be NULL). + * @param src_port Output: source port (host byte order, can be NULL). + * @return Number of bytes received, 0 if no data, -1 on error. + */ +int udp_recvfrom(int sockfd, void *buf, uint32_t bufsize, + uint32_t *src_ip, uint16_t *src_port); + +/** + * Close a UDP socket. + * + * @param sockfd Socket index. + */ +void udp_close(int sockfd); + +/** + * Process an incoming UDP datagram (called from IPv4 layer). + */ +void udp_receive(uint32_t src_ip, uint32_t dst_ip, + const void *data, uint32_t len, uint32_t iface_idx); + +#endif /* UDP_H */