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 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 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.
|
- [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`.
|
- [ ] 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 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.
|
- [ ] 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
|
[ 2%] Building user-mode applications
|
||||||
Building app: arp
|
Building app: arp
|
||||||
Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/arp (214 bytes)
|
||||||
@@ -38,12 +35,9 @@ Building app: sh
|
|||||||
1 warning generated.
|
1 warning generated.
|
||||||
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
||||||
[ 2%] Built target apps
|
[ 2%] Built target apps
|
||||||
[ 5%] Generating CPIO initial ramdisk
|
|
||||||
Generated initrd: 24768 bytes
|
|
||||||
[ 5%] Built target initrd
|
[ 5%] Built target initrd
|
||||||
[ 7%] Building C object src/CMakeFiles/kernel.dir/kernel.c.o
|
[ 7%] Building C object src/CMakeFiles/kernel.dir/tcp.c.o
|
||||||
[ 10%] Building C object src/CMakeFiles/kernel.dir/dhcp.c.o
|
[ 10%] Linking C executable ../bin/kernel
|
||||||
[ 13%] Linking C executable ../bin/kernel
|
|
||||||
[ 97%] Built target kernel
|
[ 97%] Built target kernel
|
||||||
[100%] Generating bootable ISO image
|
[100%] Generating bootable ISO image
|
||||||
xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project.
|
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 current: stdio file, overwriteable
|
||||||
Media status : is blank
|
Media status : is blank
|
||||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
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
|
xorriso : UPDATE : 581 files added in 1 seconds
|
||||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||||
xorriso : UPDATE : 586 files added in 1 seconds
|
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 : 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.
|
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
|
||||||
ISO image produced: 6009 sectors
|
ISO image produced: 6027 sectors
|
||||||
Written to medium : 6009 sectors at LBA 0
|
Written to medium : 6027 sectors at LBA 0
|
||||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
||||||
|
|
||||||
[100%] Built target iso
|
[100%] Built target iso
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ add_executable(kernel
|
|||||||
ipv4.c
|
ipv4.c
|
||||||
arp.c
|
arp.c
|
||||||
dhcp.c
|
dhcp.c
|
||||||
|
udp.c
|
||||||
|
tcp.c
|
||||||
env.c
|
env.c
|
||||||
keyboard.c
|
keyboard.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
#include "ipv4.h"
|
#include "ipv4.h"
|
||||||
#include "arp.h"
|
#include "arp.h"
|
||||||
#include "dhcp.h"
|
#include "dhcp.h"
|
||||||
|
#include "udp.h"
|
||||||
|
#include "tcp.h"
|
||||||
#include "framebuffer.h"
|
#include "framebuffer.h"
|
||||||
|
|
||||||
/* Global framebuffer info, parsed from multiboot2 tags. */
|
/* Global framebuffer info, parsed from multiboot2 tags. */
|
||||||
@@ -431,6 +433,12 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
dhcp_init();
|
dhcp_init();
|
||||||
offset_print("DHCP subsystem initialized\n");
|
offset_print("DHCP subsystem initialized\n");
|
||||||
|
|
||||||
|
udp_init();
|
||||||
|
offset_print("UDP stack initialized\n");
|
||||||
|
|
||||||
|
tcp_init();
|
||||||
|
offset_print("TCP stack initialized\n");
|
||||||
|
|
||||||
init_drivers();
|
init_drivers();
|
||||||
EARLY_PRINT("DRV ");
|
EARLY_PRINT("DRV ");
|
||||||
offset_print("Drivers initialized\n");
|
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