Implement UDP and TCP stack (AI)
This commit is contained in:
@@ -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.
|
||||
|
||||
16
build.log
16
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
|
||||
|
||||
@@ -31,6 +31,8 @@ add_executable(kernel
|
||||
ipv4.c
|
||||
arp.c
|
||||
dhcp.c
|
||||
udp.c
|
||||
tcp.c
|
||||
env.c
|
||||
keyboard.c
|
||||
interrupts.S
|
||||
|
||||
@@ -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");
|
||||
|
||||
512
src/tcp.c
Normal file
512
src/tcp.c
Normal file
@@ -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";
|
||||
}
|
||||
}
|
||||
169
src/tcp.h
Normal file
169
src/tcp.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
/** 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 */
|
||||
180
src/udp.c
Normal file
180
src/udp.c
Normal file
@@ -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 <string.h>
|
||||
|
||||
/* 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");
|
||||
}
|
||||
128
src/udp.h
Normal file
128
src/udp.h
Normal file
@@ -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 <stdint.h>
|
||||
|
||||
/** 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 */
|
||||
Reference in New Issue
Block a user