#include #include #include #include #include #include #include #include #include #include #include // The shell command blocks the normal input pump, so we must poll Ctrl-C // ourselves while listening. extern void poll_keyboard_for_ctrl_c(void); typedef struct __attribute__((packed)) eth_hdr { uint8 dst[6]; uint8 src[6]; uint16 ethertype_be; } eth_hdr; typedef struct __attribute__((packed)) arp_pkt { uint16 htype_be; uint16 ptype_be; uint8 hlen; uint8 plen; uint16 oper_be; uint8 sha[6]; uint8 spa[4]; uint8 tha[6]; uint8 tpa[4]; } arp_pkt; typedef struct __attribute__((packed)) ipv4_hdr { uint8 ver_ihl; uint8 dscp_ecn; uint16 total_len_be; uint16 id_be; uint16 flags_frag_off_be; uint8 ttl; uint8 proto; uint16 hdr_checksum_be; uint8 src[4]; uint8 dst[4]; } ipv4_hdr; typedef struct __attribute__((packed)) udp_hdr { uint16 src_port_be; uint16 dst_port_be; uint16 len_be; uint16 checksum_be; } udp_hdr; typedef struct __attribute__((packed)) tcp_hdr { uint16 src_port_be; uint16 dst_port_be; uint32 seq_be; uint32 ack_be; uint8 data_off; // high 4 bits = data offset uint8 flags; uint16 window_be; uint16 checksum_be; uint16 urg_ptr_be; } tcp_hdr; typedef struct __attribute__((packed)) icmp_echo_hdr { uint8 type; uint8 code; uint16 checksum_be; uint16 id_be; uint16 seq_be; } icmp_echo_hdr; typedef struct arp_cache_entry { uint8 ip[4]; uint8 mac[6]; uint8 valid; uint32 last_seen_tick; } arp_cache_entry; typedef struct udp_rx_slot { uint8 valid; net_udp_rx_packet pkt; } udp_rx_slot; typedef struct icmp_echo_reply_slot { uint8 valid; uint8 src_ip[4]; uint16 id; uint16 seq; uint32 payload_len; } icmp_echo_reply_slot; typedef enum tcp_state { TCP_CLOSED = 0, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_FIN_WAIT } tcp_state; typedef struct tcp_conn { tcp_state state; uint8 local_ip[4]; uint16 local_port; uint8 remote_ip[4]; uint16 remote_port; uint32 seq; uint32 ack; uint16 listen_port; uint8 listening; int retx_timer_id; uint8 retx_active; uint8 retx_count; uint8 last_flags; uint32 last_seq; uint32 last_ack; uint32 last_end_seq; uint32 last_payload_len; uint8 last_payload[NET_TCP_MAX_PAYLOAD]; } tcp_conn; typedef struct tcp_rx_slot { uint8 valid; net_tcp_rx_packet pkt; } tcp_rx_slot; typedef struct net_timer_slot { uint8 active; uint32 expiry_tick; uint32 interval_ticks; net_timer_cb cb; void* ctx; } net_timer_slot; // UDP socket: binds a port to a dedicated RX queue #define SOCKET_RXQ_SIZE 4 #define MAX_SOCKETS 8 // ARP cache timing (ms) #define ARP_CACHE_TTL_MS 30000u #define ARP_CACHE_REFRESH_MS 8000u #define ARP_RETRY_BACKOFF_MS 200u // TCP flags #define TCP_FLAG_FIN 0x01u #define TCP_FLAG_SYN 0x02u #define TCP_FLAG_RST 0x04u #define TCP_FLAG_PSH 0x08u #define TCP_FLAG_ACK 0x10u // TCP retransmit policy (minimal reliability) #define TCP_RETX_INTERVAL_MS 400u #define TCP_RETX_MAX_ATTEMPTS 4u /* * ABI-INVARIANT: DNS UDP payload cap for the resolver. * * Why: RFC 1035 limits UDP DNS messages to 512 bytes without EDNS. * Invariant: The resolver uses fixed buffers sized to this limit. * Breakage if changed: * - Increasing: raises stack usage in low-RAM paths. * - Decreasing: rejects valid replies and can break common lookups. * ABI-sensitive: No (internal kernel API). * Disk-format-sensitive: No. * Security-critical: Yes (bounds all DNS parse operations). */ #define NET_DNS_MAX_MESSAGE 512u /* * ABI-INVARIANT: Maximum DNS name length (RFC 1035). * * Why: DNS names are limited to 253 characters (labels + dots). * Invariant: Encoded queries must fit within NET_DNS_MAX_MESSAGE. * Breakage if changed: May allow malformed names to overflow buffers. * ABI-sensitive: No (internal kernel API). * Disk-format-sensitive: No. * Security-critical: Yes (input validation boundary). */ #define NET_DNS_MAX_NAME 253u /* * ABI-INVARIANT: Maximum DNS label length (RFC 1035). * * Why: A single label is limited to 63 bytes. * Invariant: Resolver rejects labels longer than this. * Breakage if changed: Risks encoding invalid queries. * ABI-sensitive: No (internal kernel API). * Disk-format-sensitive: No. * Security-critical: Yes (input validation boundary). */ #define NET_DNS_MAX_LABEL 63u /* * ABI-INVARIANT: DNS service port (UDP). * * Why: Standard DNS service port defined by RFC 1035. * Invariant: Resolver emits queries to this port on the configured nameserver. * Breakage if changed: Queries will target the wrong service. * ABI-sensitive: No (internal kernel API). * Disk-format-sensitive: No. * Security-critical: Yes (limits protocol expectations). */ #define NET_DNS_PORT 53u typedef struct udp_socket { uint8 bound; uint16 port; udp_rx_slot rxq[SOCKET_RXQ_SIZE]; uint32 rxq_head; uint32 rxq_tail; uint32 dropped_count; } udp_socket; static struct { net_config cfg; int inited; uint8 mac[6]; const netdev* dev; arp_cache_entry arp_cache[4]; net_ip_stats ip_stats; udp_rx_slot udp_rxq[8]; net_udp_stats udp_stats; icmp_echo_reply_slot icmp_rxq[4]; net_icmp_stats icmp_stats; tcp_conn tcp; net_tcp_stats tcp_stats; tcp_rx_slot tcp_rxq[8]; uint32 tcp_rxq_head; uint32 tcp_rxq_tail; udp_socket sockets[MAX_SOCKETS]; net_timer_slot timers[8]; uint32 last_timer_tick; uint32 last_arp_age_tick; } g_net = { .cfg = { .local_ip = {10, 0, 2, 15}, .gateway_ip = {10, 0, 2, 2}, .netmask = {255, 255, 255, 0}, .dns_ip = {10, 0, 2, 3}, }, }; static uint16 checksum16_add_bytes(uint32 sum, const uint8* data, uint32 len); static int tcp_send_segment(const uint8 src_ip[4], uint16 src_port, const uint8 dst_ip[4], uint16 dst_port, uint32 seq, uint32 ack, uint8 flags, const uint8* payload, uint32 payload_len, int arp_spins); static void tcp_retx_timer_cb(void* ctx); static void net_log(const char* msg); static int dns_get_server_ip(uint8 out_ip[4]); static int dns_encode_name(const char* name, uint8* out, uint32 out_cap, uint32* out_len); static int dns_skip_name(const uint8* msg, uint32 len, uint32* io_off); void net_config_get(net_config* out) { if (!out) return; *out = g_net.cfg; } static uint16 tcp_checksum16_ex(const uint8 src_ip[4], const uint8 dst_ip[4], const uint8* tcp_bytes, uint32 tcp_hdr_len, const uint8* payload, uint32 payload_len) { uint32 sum = 0; if (!src_ip || !dst_ip || !tcp_bytes) return 0; if (tcp_hdr_len < (uint32)sizeof(tcp_hdr) || tcp_hdr_len > 60u) return 0; uint8 pseudo[12]; for (int i = 0; i < 4; i++) pseudo[i] = src_ip[i]; for (int i = 0; i < 4; i++) pseudo[4 + i] = dst_ip[i]; pseudo[8] = 0; pseudo[9] = 6; // TCP uint16 tcp_len = (uint16)(tcp_hdr_len + payload_len); pseudo[10] = (uint8)(tcp_len >> 8); pseudo[11] = (uint8)(tcp_len & 0xFF); sum = checksum16_add_bytes(sum, pseudo, 12u); // TCP header (including options) with checksum field zeroed uint8 hdr_bytes[60]; memcpy(hdr_bytes, tcp_bytes, tcp_hdr_len); hdr_bytes[16] = 0; hdr_bytes[17] = 0; sum = checksum16_add_bytes(sum, hdr_bytes, tcp_hdr_len); if (payload_len && payload) { sum = checksum16_add_bytes(sum, payload, payload_len); } uint16 csum = (uint16)(~sum); if (csum == 0) csum = 0xFFFF; return csum; } static uint16 tcp_checksum16(const uint8 src_ip[4], const uint8 dst_ip[4], const tcp_hdr* tcp, const uint8* payload, uint32 payload_len) { return tcp_checksum16_ex(src_ip, dst_ip, (const uint8*)tcp, (uint32)sizeof(tcp_hdr), payload, payload_len); } // Retransmit helpers (minimal reliability for SYN/SYN-ACK/DATA/FIN). // Stores the last outbound segment and retries it a few times on timeout. static void tcp_retx_cancel(void) { if (g_net.tcp.retx_timer_id >= 0) { net_timer_cancel(g_net.tcp.retx_timer_id); } g_net.tcp.retx_timer_id = -1; g_net.tcp.retx_active = 0; g_net.tcp.retx_count = 0; g_net.tcp.last_flags = 0; g_net.tcp.last_payload_len = 0; } static void tcp_retx_arm(uint8 flags, uint32 seq, uint32 ack, const uint8* payload, uint32 payload_len) { if (payload_len > NET_TCP_MAX_PAYLOAD) return; tcp_retx_cancel(); g_net.tcp.last_flags = flags; g_net.tcp.last_seq = seq; g_net.tcp.last_ack = ack; g_net.tcp.last_payload_len = payload_len; g_net.tcp.last_end_seq = seq + payload_len; if (flags & TCP_FLAG_SYN) g_net.tcp.last_end_seq += 1u; if (flags & TCP_FLAG_FIN) g_net.tcp.last_end_seq += 1u; if (payload_len > 0) { memcpy(g_net.tcp.last_payload, payload, payload_len); } g_net.tcp.retx_count = 0; g_net.tcp.retx_active = 1; g_net.tcp.retx_timer_id = net_timer_start(net_timer_ticks_from_ms(TCP_RETX_INTERVAL_MS), net_timer_ticks_from_ms(TCP_RETX_INTERVAL_MS), tcp_retx_timer_cb, NULL); } static void tcp_retx_ack_received(uint32 ack) { if (!g_net.tcp.retx_active) return; if (ack >= g_net.tcp.last_end_seq) { tcp_retx_cancel(); } } static void tcp_retx_timer_cb(void* ctx) { (void)ctx; if (!g_net.tcp.retx_active) return; if (g_net.tcp.state == TCP_CLOSED) { tcp_retx_cancel(); return; } if (g_net.tcp.retx_count >= TCP_RETX_MAX_ATTEMPTS) { net_log("[NETSTACK] TCP retransmit limit reached\n"); tcp_retx_cancel(); g_net.tcp.state = TCP_CLOSED; return; } if (g_net.tcp.local_port == 0 || g_net.tcp.remote_port == 0) return; (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.last_seq, g_net.tcp.last_ack, g_net.tcp.last_flags, g_net.tcp.last_payload_len ? g_net.tcp.last_payload : NULL, g_net.tcp.last_payload_len, 800000); g_net.tcp.retx_count++; } int net_config_set(const net_config* in) { if (!in) return -1; g_net.cfg = *in; return 0; } void net_config_set_defaults(void) { g_net.cfg.local_ip[0] = 10; g_net.cfg.local_ip[1] = 0; g_net.cfg.local_ip[2] = 2; g_net.cfg.local_ip[3] = 15; g_net.cfg.gateway_ip[0] = 10; g_net.cfg.gateway_ip[1] = 0; g_net.cfg.gateway_ip[2] = 2; g_net.cfg.gateway_ip[3] = 2; g_net.cfg.netmask[0] = 255; g_net.cfg.netmask[1] = 255; g_net.cfg.netmask[2] = 255; g_net.cfg.netmask[3] = 0; g_net.cfg.dns_ip[0] = 10; g_net.cfg.dns_ip[1] = 0; g_net.cfg.dns_ip[2] = 2; g_net.cfg.dns_ip[3] = 3; } void net_get_local_ip(uint8 out_ip[4]) { if (!out_ip) return; out_ip[0] = g_net.cfg.local_ip[0]; out_ip[1] = g_net.cfg.local_ip[1]; out_ip[2] = g_net.cfg.local_ip[2]; out_ip[3] = g_net.cfg.local_ip[3]; } static uint16 be16(uint16 x) { return (uint16)((x >> 8) | (x << 8)); } static uint32 bswap32(uint32 x) { return ((x >> 24) & 0x000000FFu) | ((x >> 8) & 0x0000FF00u) | ((x << 8) & 0x00FF0000u) | ((x << 24) & 0xFF000000u); } static uint32 be32(uint32 x) { return bswap32(x); } static void net_log(const char* msg) { if (!msg) return; serial_write(SERIAL_COM1, msg, (int)strlen(msg)); } static uint32 net_get_ticks(void) { return sched_get_tick_count(); } static uint32 net_get_hz(void) { return sched_get_tick_hz(); } static uint32 net_ticks_from_ms(uint32 ms) { uint32 hz = net_get_hz(); if (hz == 0) hz = 100; // ceil(ms * hz / 1000) uint32 ticks = (ms * hz + 999u) / 1000u; if (ticks == 0) ticks = 1; return ticks; } uint32 net_timer_get_ticks(void) { return net_get_ticks(); } uint32 net_timer_get_hz(void) { return net_get_hz(); } uint32 net_timer_ticks_from_ms(uint32 ms) { return net_ticks_from_ms(ms); } int net_timer_start(uint32 delay_ticks, uint32 interval_ticks, net_timer_cb cb, void* ctx) { if (!cb) return -1; if (delay_ticks == 0) delay_ticks = 1; for (int i = 0; i < (int)(sizeof(g_net.timers) / sizeof(g_net.timers[0])); i++) { if (!g_net.timers[i].active) { g_net.timers[i].active = 1; g_net.timers[i].expiry_tick = net_get_ticks() + delay_ticks; g_net.timers[i].interval_ticks = interval_ticks; g_net.timers[i].cb = cb; g_net.timers[i].ctx = ctx; return i; } } return -2; } void net_timer_cancel(int timer_id) { if (timer_id < 0 || timer_id >= (int)(sizeof(g_net.timers) / sizeof(g_net.timers[0]))) return; g_net.timers[timer_id].active = 0; } static void net_timer_run(void) { uint32 now = net_get_ticks(); if (now == g_net.last_timer_tick) return; g_net.last_timer_tick = now; for (int i = 0; i < (int)(sizeof(g_net.timers) / sizeof(g_net.timers[0])); i++) { net_timer_slot* t = &g_net.timers[i]; if (!t->active) continue; if ((int32)(now - t->expiry_tick) >= 0) { t->cb(t->ctx); if (t->interval_ticks > 0) { t->expiry_tick = now + t->interval_ticks; } else { t->active = 0; } } } } static int ipv4_eq(const uint8 a[4], const uint8 b[4]) { return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; } static void print_ipv4_bytes(const uint8 ip[4]) { printf("%d.%d.%d.%d", (int)ip[0], (int)ip[1], (int)ip[2], (int)ip[3]); } static uint16 ipv4_checksum16(const void* data, uint32 len) { const uint8* b = (const uint8*)data; uint32 sum = 0; for (uint32 i = 0; i + 1 < len; i += 2) { sum += ((uint16)b[i] << 8) | (uint16)b[i + 1]; } if (len & 1u) { sum += ((uint16)b[len - 1] << 8); } while (sum >> 16) { sum = (sum & 0xFFFFu) + (sum >> 16); } return (uint16)(~sum); } static uint16 checksum16_add_bytes(uint32 sum, const uint8* data, uint32 len) { // Adds network-order 16-bit words; pads odd trailing byte with zero. uint32 i = 0; while (i + 1u < len) { uint16 w = (uint16)(((uint16)data[i] << 8) | (uint16)data[i + 1u]); sum += (uint32)w; i += 2u; } if (i < len) { uint16 w = (uint16)((uint16)data[i] << 8); sum += (uint32)w; } // Fold to 16-bit (may still overflow once, fold again). while (sum >> 16) sum = (sum & 0xFFFFu) + (sum >> 16); return (uint16)sum; } static uint16 udp_checksum16(const uint8 src_ip[4], const uint8 dst_ip[4], const udp_hdr* udp, const uint8* payload, uint32 payload_len) { // UDP checksum includes pseudoheader + UDP header + payload. // Returned value is in host order (caller be16()s it). if (!src_ip || !dst_ip || !udp) return 0; uint32 sum = 0; // Pseudoheader sum = checksum16_add_bytes(sum, src_ip, 4u); sum = checksum16_add_bytes(sum, dst_ip, 4u); { uint8 ph[4]; ph[0] = 0; ph[1] = 17; // protocol ph[2] = (uint8)((uint16)be16(udp->len_be) >> 8); ph[3] = (uint8)((uint16)be16(udp->len_be) & 0xFF); sum = checksum16_add_bytes(sum, ph, 4u); } // UDP header with checksum field as 0 { uint8 hdr_bytes[8]; hdr_bytes[0] = (uint8)((uint16)be16(udp->src_port_be) >> 8); hdr_bytes[1] = (uint8)((uint16)be16(udp->src_port_be) & 0xFF); hdr_bytes[2] = (uint8)((uint16)be16(udp->dst_port_be) >> 8); hdr_bytes[3] = (uint8)((uint16)be16(udp->dst_port_be) & 0xFF); hdr_bytes[4] = (uint8)((uint16)be16(udp->len_be) >> 8); hdr_bytes[5] = (uint8)((uint16)be16(udp->len_be) & 0xFF); hdr_bytes[6] = 0; hdr_bytes[7] = 0; sum = checksum16_add_bytes(sum, hdr_bytes, 8u); } if (payload_len && payload) { sum = checksum16_add_bytes(sum, payload, payload_len); } uint16 csum = (uint16)(~sum); if (csum == 0) csum = 0xFFFF; return csum; } static int arp_cache_lookup(const uint8 ip[4], uint8 out_mac[6]) { uint32 now = net_get_ticks(); uint32 ttl_ticks = net_ticks_from_ms(ARP_CACHE_TTL_MS); for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (!g_net.arp_cache[i].valid) continue; if (ttl_ticks != 0 && (int32)(now - g_net.arp_cache[i].last_seen_tick) > (int32)ttl_ticks) { g_net.arp_cache[i].valid = 0; continue; } if (ipv4_eq(g_net.arp_cache[i].ip, ip)) { for (int j = 0; j < 6; j++) out_mac[j] = g_net.arp_cache[i].mac[j]; return 1; } } return 0; } static void arp_cache_update(const uint8 ip[4], const uint8 mac[6]) { uint32 now = net_get_ticks(); // Update in place if present. for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (g_net.arp_cache[i].valid && ipv4_eq(g_net.arp_cache[i].ip, ip)) { for (int j = 0; j < 6; j++) g_net.arp_cache[i].mac[j] = mac[j]; g_net.arp_cache[i].last_seen_tick = now; return; } } // Insert into first free slot. for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (!g_net.arp_cache[i].valid) { for (int j = 0; j < 4; j++) g_net.arp_cache[i].ip[j] = ip[j]; for (int j = 0; j < 6; j++) g_net.arp_cache[i].mac[j] = mac[j]; g_net.arp_cache[i].valid = 1; g_net.arp_cache[i].last_seen_tick = now; return; } } // Cache full: overwrite slot 0. for (int j = 0; j < 4; j++) g_net.arp_cache[0].ip[j] = ip[j]; for (int j = 0; j < 6; j++) g_net.arp_cache[0].mac[j] = mac[j]; g_net.arp_cache[0].valid = 1; g_net.arp_cache[0].last_seen_tick = now; } static int ipv4_is_zero(const uint8 ip[4]); static int arp_send_request_silent(const uint8 sender_ip[4], const uint8 target_ip[4]); static void arp_age_timer_cb(void* ctx) { (void)ctx; uint32 now = net_get_ticks(); uint32 ttl_ticks = net_ticks_from_ms(ARP_CACHE_TTL_MS); uint32 refresh_ticks = net_ticks_from_ms(ARP_CACHE_REFRESH_MS); for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (!g_net.arp_cache[i].valid) continue; if (ttl_ticks != 0 && (int32)(now - g_net.arp_cache[i].last_seen_tick) > (int32)ttl_ticks) { g_net.arp_cache[i].valid = 0; net_log("[NETSTACK] ARP entry expired\n"); continue; } } // Refresh gateway MAC periodically if present. if (!ipv4_is_zero(g_net.cfg.gateway_ip)) { for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (!g_net.arp_cache[i].valid) continue; if (ipv4_eq(g_net.arp_cache[i].ip, g_net.cfg.gateway_ip)) { if (refresh_ticks != 0 && (int32)(now - g_net.arp_cache[i].last_seen_tick) >= (int32)refresh_ticks) { (void)arp_send_request_silent(g_net.cfg.local_ip, g_net.cfg.gateway_ip); } break; } } } } static int arp_send_reply_silent(const uint8 target_mac[6], const uint8 sender_ip[4], const uint8 sender_mac[6], const uint8 target_ip[4]); static int arp_resolve(const uint8 sender_ip[4], const uint8 target_ip[4], uint8 out_mac[6], int arp_spins); static uint32 ipv4_to_u32(const uint8 ip[4]) { return ((uint32)ip[0] << 24) | ((uint32)ip[1] << 16) | ((uint32)ip[2] << 8) | (uint32)ip[3]; } static int ipv4_is_zero(const uint8 ip[4]) { return ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0; } static int ipv4_is_same_subnet(const uint8 a[4], const uint8 b[4], const uint8 mask[4]) { uint32 au = ipv4_to_u32(a); uint32 bu = ipv4_to_u32(b); uint32 mu = ipv4_to_u32(mask); return ((au & mu) == (bu & mu)); } static void ipv4_choose_next_hop(const uint8 src_ip[4], const uint8 dst_ip[4], uint8 out_next_hop_ip[4]) { // Minimal routing: if dst is not on-link, send to gateway (L2) but keep IP dst unchanged. // If gateway is unset, fall back to direct ARP for dst. if (!src_ip || !dst_ip || !out_next_hop_ip) return; /* * ABI-INVARIANT: QEMU slirp DNS next-hop behavior. * * Why: In common slirp setups the synthetic DNS IP (10.0.2.3) is exposed * as a virtual service and may not answer ARP directly. Packets must * still be sent to the default gateway MAC at L2. * Invariant: When destination equals configured dns_ip and a gateway is * configured, choose gateway as ARP target. * Breakage if changed: DNS lookups can fail with repeated ARP retries even * though IP configuration is correct. * ABI-sensitive: No. * Disk-format-sensitive: No. * Security-critical: No. */ if (!ipv4_is_zero(g_net.cfg.gateway_ip) && ipv4_eq(dst_ip, g_net.cfg.dns_ip)) { for (int i = 0; i < 4; i++) out_next_hop_ip[i] = g_net.cfg.gateway_ip[i]; return; } if (!ipv4_is_zero(g_net.cfg.gateway_ip) && !ipv4_is_same_subnet(src_ip, dst_ip, g_net.cfg.netmask)) { for (int i = 0; i < 4; i++) out_next_hop_ip[i] = g_net.cfg.gateway_ip[i]; return; } for (int i = 0; i < 4; i++) out_next_hop_ip[i] = dst_ip[i]; } static int parse_ipv4_str(const char* s, uint8 out[4]) { if (!s || !out) return -1; for (int part = 0; part < 4; part++) { if (*s < '0' || *s > '9') return -1; int v = 0; while (*s >= '0' && *s <= '9') { v = (v * 10) + (*s - '0'); if (v > 255) return -1; s++; } out[part] = (uint8)v; if (part != 3) { if (*s != '.') return -1; s++; } } return (*s == '\0') ? 0 : -1; } static int dns_get_server_ip(uint8 out_ip[4]) { if (!out_ip) return -1; char buf[256]; int n = vfs_read_file(0, "/etc/resolv.conf", buf, (int)sizeof(buf) - 1); if (n > 0) { buf[n] = '\0'; char* line = buf; while (line && *line) { char* next = strchr(line, '\n'); if (next) { *next = '\0'; next++; } while (*line == ' ' || *line == '\t' || *line == '\r') line++; if (*line == '#' || *line == '\0') { line = next; continue; } const char* key = "nameserver"; int match = 1; for (int i = 0; key[i]; i++) { if (line[i] != key[i]) { match = 0; break; } } if (match && (line[10] == ' ' || line[10] == '\t')) { const char* ip_str = line + 10; while (*ip_str == ' ' || *ip_str == '\t') ip_str++; if (parse_ipv4_str(ip_str, out_ip) == 0) return 0; } line = next; } } // Fallback to netcfg DNS if resolv.conf is missing or invalid. if (g_net.cfg.dns_ip[0] || g_net.cfg.dns_ip[1] || g_net.cfg.dns_ip[2] || g_net.cfg.dns_ip[3]) { for (int i = 0; i < 4; i++) out_ip[i] = g_net.cfg.dns_ip[i]; return 0; } return -1; } static int dns_encode_name(const char* name, uint8* out, uint32 out_cap, uint32* out_len) { if (!name || !out || !out_len) return -1; uint32 len = 0; const char* p = name; if (*p == '\0') return -2; if (strlen(name) > NET_DNS_MAX_NAME) return -3; while (*p) { const char* label = p; uint32 label_len = 0; while (*p && *p != '.') { label_len++; p++; } if (label_len == 0 || label_len > NET_DNS_MAX_LABEL) return -4; if (len + 1 + label_len + 1 > out_cap) return -5; out[len++] = (uint8)label_len; for (uint32 i = 0; i < label_len; i++) { out[len++] = (uint8)label[i]; } if (*p == '.') p++; } if (len + 1 > out_cap) return -6; out[len++] = 0; *out_len = len; return 0; } static int dns_skip_name(const uint8* msg, uint32 len, uint32* io_off) { if (!msg || !io_off) return -1; uint32 off = *io_off; while (off < len) { uint8 b = msg[off]; if (b == 0) { *io_off = off + 1u; return 0; } if ((b & 0xC0u) == 0xC0u) { if (off + 1u >= len) return -2; *io_off = off + 2u; return 0; } if (b > NET_DNS_MAX_LABEL) return -3; off++; if (off + b > len) return -4; off += b; } return -5; } int net_dns_resolve(const char* name, uint8 out_ip[4], int timeout_spins) { if (!name || !out_ip) return -1; if (net_init_e1000_default() != 0) return -2; uint8 dns_ip[4]; if (dns_get_server_ip(dns_ip) != 0) return -3; uint8 local_ip[4]; net_get_local_ip(local_ip); int socket_id = -1; uint16 base_port = (uint16)(40000u + (net_get_ticks() % 20000u)); for (int attempt = 0; attempt < 16; attempt++) { uint16 port = (uint16)(base_port + attempt); socket_id = net_udp_bind(port); if (socket_id >= 0) break; } if (socket_id < 0) return -4; uint8 msg[NET_DNS_MAX_MESSAGE]; memset(msg, 0, sizeof(msg)); uint16 tx_id = (uint16)(net_get_ticks() & 0xFFFFu); msg[0] = (uint8)(tx_id >> 8); msg[1] = (uint8)(tx_id & 0xFFu); msg[2] = 0x01; // recursion desired msg[3] = 0x00; msg[4] = 0x00; msg[5] = 0x01; // QDCOUNT uint32 off = 12u; uint32 name_len = 0; int rc = dns_encode_name(name, msg + off, (uint32)sizeof(msg) - off, &name_len); if (rc != 0) { net_udp_close(socket_id); return -5; } off += name_len; if (off + 4u > (uint32)sizeof(msg)) { net_udp_close(socket_id); return -6; } msg[off++] = 0x00; msg[off++] = 0x01; // QTYPE = A msg[off++] = 0x00; msg[off++] = 0x01; // QCLASS = IN rc = net_udp_send_socket(socket_id, local_ip, dns_ip, (uint16)NET_DNS_PORT, msg, off, 800000); if (rc != 0) { net_udp_close(socket_id); return -7; } if (timeout_spins <= 0) timeout_spins = 2000000; uint32 ui_ticks = 0; for (int spin = 0; spin < timeout_spins; spin++) { if ((spin & 0x1FFF) == 0) { watchdog_kick("net-dns-wait"); // Keep the GUI alive while resolver waits in a blocking syscall. if (tile_is_tiling_active() && (++ui_ticks & 0x1F) == 0) { tile_render_once(); } } (void)net_poll(local_ip, 32); net_udp_rx_packet pkt; int got = net_udp_recv_socket(socket_id, &pkt); if (got <= 0) continue; if (!ipv4_eq(pkt.src_ip, dns_ip) || pkt.payload_len < 12u) continue; const uint8* rx = pkt.payload; uint32 rx_len = pkt.payload_len; uint16 rx_id = (uint16)((rx[0] << 8) | rx[1]); if (rx_id != tx_id) continue; uint16 flags = (uint16)((rx[2] << 8) | rx[3]); if ((flags & 0x8000u) == 0) continue; // not a response if ((flags & 0x000Fu) != 0) { net_udp_close(socket_id); return -8; } uint16 qdcount = (uint16)((rx[4] << 8) | rx[5]); uint16 ancount = (uint16)((rx[6] << 8) | rx[7]); uint32 roff = 12u; for (uint16 q = 0; q < qdcount; q++) { if (dns_skip_name(rx, rx_len, &roff) != 0) { net_udp_close(socket_id); return -9; } if (roff + 4u > rx_len) { net_udp_close(socket_id); return -9; } roff += 4u; } for (uint16 a = 0; a < ancount; a++) { if (dns_skip_name(rx, rx_len, &roff) != 0) { net_udp_close(socket_id); return -10; } if (roff + 10u > rx_len) { net_udp_close(socket_id); return -10; } uint16 type = (uint16)((rx[roff] << 8) | rx[roff + 1u]); uint16 class = (uint16)((rx[roff + 2u] << 8) | rx[roff + 3u]); uint16 rdlen = (uint16)((rx[roff + 8u] << 8) | rx[roff + 9u]); roff += 10u; if (roff + rdlen > rx_len) { net_udp_close(socket_id); return -11; } if (type == 1u && class == 1u && rdlen == 4u) { for (int i = 0; i < 4; i++) out_ip[i] = rx[roff + (uint32)i]; net_udp_close(socket_id); return 0; } roff += rdlen; } } net_udp_close(socket_id); return -12; } static void udp_rxq_clear(void) { for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { g_net.udp_rxq[i].valid = 0; } g_net.udp_stats.udp_rx_enqueued = 0; g_net.udp_stats.udp_rx_dropped = 0; g_net.udp_stats.udp_rx_truncated = 0; g_net.udp_stats.udp_rx_bad_checksum = 0; g_net.udp_stats.udp_tx_checksums = 0; } static int tcp_rxq_enqueue(const uint8 src_ip[4], uint16 src_port, const uint8 dst_ip[4], uint16 dst_port, const uint8* payload, uint32 payload_len) { if (payload_len > NET_TCP_MAX_PAYLOAD) { // Never truncate TCP payloads; caller keeps ACK unchanged on enqueue // failure so the peer retransmits a segment we can fully buffer. g_net.tcp_stats.tcp_rx_dropped++; return -1; } uint32 cap = (uint32)(sizeof(g_net.tcp_rxq) / sizeof(g_net.tcp_rxq[0])); tcp_rx_slot* s = &g_net.tcp_rxq[g_net.tcp_rxq_tail]; if (s->valid) { g_net.tcp_stats.tcp_rx_dropped++; return -1; } s->valid = 1; for (int j = 0; j < 4; j++) s->pkt.src_ip[j] = src_ip[j]; for (int j = 0; j < 4; j++) s->pkt.dst_ip[j] = dst_ip[j]; s->pkt.src_port = src_port; s->pkt.dst_port = dst_port; s->pkt.payload_len = payload_len; if (payload_len != 0u) { memcpy(s->pkt.payload, payload, payload_len); } g_net.tcp_rxq_tail = (g_net.tcp_rxq_tail + 1u) % cap; g_net.tcp_stats.tcp_rx_enqueued++; return 0; } static void icmp_rxq_clear(void) { for (int i = 0; i < (int)(sizeof(g_net.icmp_rxq) / sizeof(g_net.icmp_rxq[0])); i++) { g_net.icmp_rxq[i].valid = 0; } g_net.icmp_stats.echo_req_rx = 0; g_net.icmp_stats.echo_rep_rx = 0; g_net.icmp_stats.echo_rep_tx = 0; g_net.icmp_stats.echo_rep_dropped = 0; g_net.icmp_stats.dest_unreach_rx = 0; g_net.icmp_stats.time_exceeded_rx = 0; g_net.icmp_stats.frag_needed_rx = 0; } static void tcp_rxq_clear(void) { for (int i = 0; i < (int)(sizeof(g_net.tcp_rxq) / sizeof(g_net.tcp_rxq[0])); i++) { g_net.tcp_rxq[i].valid = 0; } g_net.tcp_rxq_head = 0u; g_net.tcp_rxq_tail = 0u; g_net.tcp_stats.tcp_rx_enqueued = 0; g_net.tcp_stats.tcp_rx_dropped = 0; } static void icmp_rxq_enqueue_reply(const uint8 src_ip[4], uint16 id, uint16 seq, uint32 payload_len) { for (int i = 0; i < (int)(sizeof(g_net.icmp_rxq) / sizeof(g_net.icmp_rxq[0])); i++) { if (!g_net.icmp_rxq[i].valid) { g_net.icmp_rxq[i].valid = 1; for (int j = 0; j < 4; j++) g_net.icmp_rxq[i].src_ip[j] = src_ip[j]; g_net.icmp_rxq[i].id = id; g_net.icmp_rxq[i].seq = seq; g_net.icmp_rxq[i].payload_len = payload_len; return; } } g_net.icmp_stats.echo_rep_dropped++; } static int icmp_rxq_dequeue_match(uint16 id, uint16 seq, uint8 out_src_ip[4], uint32* out_payload_len) { for (int i = 0; i < (int)(sizeof(g_net.icmp_rxq) / sizeof(g_net.icmp_rxq[0])); i++) { if (g_net.icmp_rxq[i].valid && g_net.icmp_rxq[i].id == id && g_net.icmp_rxq[i].seq == seq) { if (out_src_ip) { for (int j = 0; j < 4; j++) out_src_ip[j] = g_net.icmp_rxq[i].src_ip[j]; } if (out_payload_len) { *out_payload_len = g_net.icmp_rxq[i].payload_len; } g_net.icmp_rxq[i].valid = 0; return 1; } } return 0; } static int udp_rxq_enqueue(const uint8 src_ip[4], uint16 src_port, const uint8 dst_ip[4], uint16 dst_port, const uint8* payload, uint32 payload_len) { // First, try to route to a bound socket for (int sock_idx = 0; sock_idx < MAX_SOCKETS; sock_idx++) { udp_socket* sock = &g_net.sockets[sock_idx]; if (sock->bound && sock->port == dst_port) { uint32 tail_idx = sock->rxq_tail; if (!sock->rxq[tail_idx].valid) { udp_rx_slot* s = &sock->rxq[tail_idx]; s->valid = 1; for (int j = 0; j < 4; j++) s->pkt.src_ip[j] = src_ip[j]; for (int j = 0; j < 4; j++) s->pkt.dst_ip[j] = dst_ip[j]; s->pkt.src_port = src_port; s->pkt.dst_port = dst_port; uint32 copy_len = payload_len; if (copy_len > NET_UDP_MAX_PAYLOAD) { copy_len = NET_UDP_MAX_PAYLOAD; g_net.udp_stats.udp_rx_truncated++; } s->pkt.payload_len = copy_len; if (copy_len != 0u) { memcpy(s->pkt.payload, payload, copy_len); } sock->rxq_tail = (sock->rxq_tail + 1) % SOCKET_RXQ_SIZE; g_net.udp_stats.udp_rx_enqueued++; return 0; } else { // Socket queue full sock->dropped_count++; g_net.udp_stats.udp_rx_dropped++; return -1; } } } // No socket bound to this port; use global queue for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { if (!g_net.udp_rxq[i].valid) { udp_rx_slot* s = &g_net.udp_rxq[i]; s->valid = 1; for (int j = 0; j < 4; j++) s->pkt.src_ip[j] = src_ip[j]; for (int j = 0; j < 4; j++) s->pkt.dst_ip[j] = dst_ip[j]; s->pkt.src_port = src_port; s->pkt.dst_port = dst_port; uint32 copy_len = payload_len; if (copy_len > NET_UDP_MAX_PAYLOAD) { copy_len = NET_UDP_MAX_PAYLOAD; g_net.udp_stats.udp_rx_truncated++; } s->pkt.payload_len = copy_len; if (copy_len != 0u) { memcpy(s->pkt.payload, payload, copy_len); } g_net.udp_stats.udp_rx_enqueued++; return 0; } } g_net.udp_stats.udp_rx_dropped++; return -1; } int net_init_e1000_default(void) { static netdev e1000_dev; e1000_dev.send_frame = e1000_send_frame; e1000_dev.rx_poll_frame = e1000_rx_poll_frame; e1000_dev.get_mac = e1000_get_mac; if (e1000_init() != 0) return -10; return net_init(&e1000_dev); } int net_init(const netdev* dev) { if (g_net.inited) return 0; if (!dev || !dev->send_frame || !dev->rx_poll_frame || !dev->get_mac) return -1; if (dev->get_mac(g_net.mac) != 0) return -2; g_net.dev = dev; udp_rxq_clear(); icmp_rxq_clear(); tcp_rxq_clear(); g_net.ip_stats.ipv4_rx_fragments = 0; g_net.ip_stats.ipv4_rx_frag_dropped = 0; memset(&g_net.tcp, 0, sizeof(g_net.tcp)); g_net.tcp.retx_timer_id = -1; memset(&g_net.tcp_stats, 0, sizeof(g_net.tcp_stats)); memset(g_net.timers, 0, sizeof(g_net.timers)); g_net.last_timer_tick = 0; (void)net_timer_start(net_ticks_from_ms(1000), net_ticks_from_ms(1000), arp_age_timer_cb, NULL); g_net.inited = 1; return 0; } int net_is_inited(void) { return g_net.inited ? 1 : 0; } net_udp_stats net_udp_get_stats(void) { return g_net.udp_stats; } net_ip_stats net_ip_get_stats(void) { return g_net.ip_stats; } net_icmp_stats net_icmp_get_stats(void) { return g_net.icmp_stats; } net_tcp_stats net_tcp_get_stats(void) { return g_net.tcp_stats; } int net_tcp_listen(uint16 local_port) { if (local_port == 0) return -1; if (net_init_e1000_default() != 0) return -2; g_net.tcp.listening = 1; g_net.tcp.listen_port = local_port; g_net.tcp.state = TCP_CLOSED; tcp_retx_cancel(); tcp_rxq_clear(); return 0; } int net_tcp_close(void) { if (g_net.tcp.state == TCP_ESTABLISHED) { uint8 src_ip[4]; net_get_local_ip(src_ip); (void)tcp_send_segment(src_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_FIN | TCP_FLAG_ACK, NULL, 0, 800000); tcp_retx_arm((uint8)(TCP_FLAG_FIN | TCP_FLAG_ACK), g_net.tcp.seq, g_net.tcp.ack, NULL, 0); g_net.tcp.seq += 1; g_net.tcp_stats.tcp_fin_tx++; g_net.tcp.state = TCP_FIN_WAIT; } g_net.tcp.listening = 0; return 0; } int net_tcp_is_closed(void) { return (g_net.tcp.state == TCP_CLOSED) ? 1 : 0; } int net_tcp_recv(net_tcp_rx_packet* out) { if (!out) return -1; uint32 cap = (uint32)(sizeof(g_net.tcp_rxq) / sizeof(g_net.tcp_rxq[0])); for (uint32 n = 0; n < cap; n++) { uint32 idx = (g_net.tcp_rxq_head + n) % cap; if (g_net.tcp_rxq[idx].valid) { *out = g_net.tcp_rxq[idx].pkt; g_net.tcp_rxq[idx].valid = 0; g_net.tcp_rxq_head = (idx + 1u) % cap; return 1; } } // Queue is empty: perform one poll pass and try once more. // This avoids enqueueing bursts before draining already-queued packets. if (g_net.inited) { uint8 local_ip[4]; net_get_local_ip(local_ip); (void)net_poll(local_ip, 8); for (uint32 n = 0; n < cap; n++) { uint32 idx = (g_net.tcp_rxq_head + n) % cap; if (g_net.tcp_rxq[idx].valid) { *out = g_net.tcp_rxq[idx].pkt; g_net.tcp_rxq[idx].valid = 0; g_net.tcp_rxq_head = (idx + 1u) % cap; return 1; } } } return 0; } uint32 net_tcp_queue_count(void) { uint32 count = 0; for (int i = 0; i < (int)(sizeof(g_net.tcp_rxq) / sizeof(g_net.tcp_rxq[0])); i++) { if (g_net.tcp_rxq[i].valid) count++; } return count; } int net_get_mac(uint8 out_mac[6]) { if (!out_mac) return -1; if (!g_net.inited) return -2; for (int i = 0; i < 6; i++) out_mac[i] = g_net.mac[i]; return 0; } uint32 net_get_arp_cache(net_arp_entry* out, uint32 out_cap) { if (!out || out_cap == 0u) return 0; uint32 written = 0; for (int i = 0; i < (int)(sizeof(g_net.arp_cache) / sizeof(g_net.arp_cache[0])); i++) { if (written >= out_cap) break; out[written].valid = g_net.arp_cache[i].valid; for (int j = 0; j < 4; j++) out[written].ip[j] = g_net.arp_cache[i].ip[j]; for (int j = 0; j < 6; j++) out[written].mac[j] = g_net.arp_cache[i].mac[j]; written++; } return written; } uint32 net_udp_queue_total(void) { if (!g_net.inited) return 0; uint32 count = 0; for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { if (g_net.udp_rxq[i].valid) count++; } return count; } static int icmp_send_echo_reply_direct(const uint8 dst_mac[6], const uint8 local_ip[4], const uint8 dst_ip[4], uint16 id, uint16 seq, const uint8* payload, uint32 payload_len) { uint8 frame[1600]; uint32 off = 0; eth_hdr* eh = (eth_hdr*)(frame + off); for (int i = 0; i < 6; i++) eh->dst[i] = dst_mac[i]; for (int i = 0; i < 6; i++) eh->src[i] = g_net.mac[i]; eh->ethertype_be = be16(0x0800u); off += (uint32)sizeof(eth_hdr); ipv4_hdr* ip = (ipv4_hdr*)(frame + off); memset(ip, 0, sizeof(*ip)); ip->ver_ihl = 0x45; ip->ttl = 64; ip->proto = 1; for (int i = 0; i < 4; i++) ip->src[i] = local_ip[i]; for (int i = 0; i < 4; i++) ip->dst[i] = dst_ip[i]; uint16 ip_total_len = (uint16)(sizeof(ipv4_hdr) + sizeof(icmp_echo_hdr) + payload_len); ip->total_len_be = be16(ip_total_len); static uint16 ip_id = 0x9000; ip->id_be = be16(ip_id++); ip->flags_frag_off_be = be16(0x4000u); ip->hdr_checksum_be = 0; ip->hdr_checksum_be = be16(ipv4_checksum16(ip, (uint32)sizeof(*ip))); off += (uint32)sizeof(ipv4_hdr); icmp_echo_hdr* ic = (icmp_echo_hdr*)(frame + off); ic->type = 0; ic->code = 0; ic->checksum_be = 0; ic->id_be = be16(id); ic->seq_be = be16(seq); off += (uint32)sizeof(icmp_echo_hdr); if (payload_len != 0u) { memcpy(frame + off, payload, payload_len); off += payload_len; } uint16 csum = ipv4_checksum16((const void*)(frame + sizeof(eth_hdr) + sizeof(ipv4_hdr)), (uint32)sizeof(icmp_echo_hdr) + payload_len); ic->checksum_be = be16(csum); if (off < 60u) { memset(frame + off, 0, 60u - off); off = 60u; } return g_net.dev->send_frame(frame, off); } static int icmp_send_echo_request(const uint8 local_ip[4], const uint8 dst_ip[4], const uint8 dst_mac[6], uint16 id, uint16 seq) { // Small fixed payload (helps validate checksums/length). uint8 payload[32]; for (uint32 i = 0; i < (uint32)sizeof(payload); i++) payload[i] = (uint8)('A' + (i % 26u)); uint8 frame[1600]; uint32 off = 0; eth_hdr* eh = (eth_hdr*)(frame + off); for (int i = 0; i < 6; i++) eh->dst[i] = dst_mac[i]; for (int i = 0; i < 6; i++) eh->src[i] = g_net.mac[i]; eh->ethertype_be = be16(0x0800u); off += (uint32)sizeof(eth_hdr); ipv4_hdr* ip = (ipv4_hdr*)(frame + off); memset(ip, 0, sizeof(*ip)); ip->ver_ihl = 0x45; ip->ttl = 64; ip->proto = 1; for (int i = 0; i < 4; i++) ip->src[i] = local_ip[i]; for (int i = 0; i < 4; i++) ip->dst[i] = dst_ip[i]; uint16 ip_total_len = (uint16)(sizeof(ipv4_hdr) + sizeof(icmp_echo_hdr) + sizeof(payload)); ip->total_len_be = be16(ip_total_len); static uint16 ip_id = 0x1000; ip->id_be = be16(ip_id++); ip->flags_frag_off_be = be16(0x4000u); ip->hdr_checksum_be = 0; ip->hdr_checksum_be = be16(ipv4_checksum16(ip, (uint32)sizeof(*ip))); off += (uint32)sizeof(ipv4_hdr); icmp_echo_hdr* ic = (icmp_echo_hdr*)(frame + off); ic->type = 8; ic->code = 0; ic->checksum_be = 0; ic->id_be = be16(id); ic->seq_be = be16(seq); off += (uint32)sizeof(icmp_echo_hdr); memcpy(frame + off, payload, (uint32)sizeof(payload)); off += (uint32)sizeof(payload); uint16 csum = ipv4_checksum16((const void*)(frame + sizeof(eth_hdr) + sizeof(ipv4_hdr)), (uint32)sizeof(icmp_echo_hdr) + (uint32)sizeof(payload)); ic->checksum_be = be16(csum); if (off < 60u) { memset(frame + off, 0, 60u - off); off = 60u; } return g_net.dev->send_frame(frame, off); } uint32 net_udp_queue_count(uint16 local_port) { if (!g_net.inited) return 0; if (local_port == 0) return 0; uint32 count = 0; for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { if (g_net.udp_rxq[i].valid && g_net.udp_rxq[i].pkt.dst_port == local_port) { count++; } } return count; } uint32 net_udp_queue_clear(uint16 local_port) { if (!g_net.inited) return 0; if (local_port == 0) return 0; uint32 cleared = 0; for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { if (g_net.udp_rxq[i].valid && g_net.udp_rxq[i].pkt.dst_port == local_port) { g_net.udp_rxq[i].valid = 0; cleared++; } } return cleared; } // --- UDP Socket API --- int net_udp_bind(uint16 port) { if (!g_net.inited) return -1; if (port == 0) return -2; // Check if already bound for (int i = 0; i < MAX_SOCKETS; i++) { if (g_net.sockets[i].bound && g_net.sockets[i].port == port) { return -1; // Already bound } } // Find free slot for (int i = 0; i < MAX_SOCKETS; i++) { if (!g_net.sockets[i].bound) { g_net.sockets[i].bound = 1; g_net.sockets[i].port = port; g_net.sockets[i].rxq_head = 0; g_net.sockets[i].rxq_tail = 0; g_net.sockets[i].dropped_count = 0; for (int j = 0; j < SOCKET_RXQ_SIZE; j++) { g_net.sockets[i].rxq[j].valid = 0; } return i; // socket_id } } return -1; // No free slots } int net_udp_close(int socket_id) { if (socket_id < 0 || socket_id >= MAX_SOCKETS) return -1; if (!g_net.sockets[socket_id].bound) return -1; g_net.sockets[socket_id].bound = 0; g_net.sockets[socket_id].port = 0; return 0; } int net_udp_send_socket(int socket_id, const uint8 src_ip[4], const uint8 dst_ip[4], uint16 dst_port, const uint8* payload, uint32 payload_len, int arp_spins) { if (socket_id < 0 || socket_id >= MAX_SOCKETS) return -1; if (!g_net.sockets[socket_id].bound) return -1; uint16 src_port = g_net.sockets[socket_id].port; return net_udp_send(src_ip, src_port, dst_ip, dst_port, payload, payload_len, arp_spins); } int net_udp_recv_socket(int socket_id, net_udp_rx_packet* out) { if (socket_id < 0 || socket_id >= MAX_SOCKETS) return -1; if (!g_net.sockets[socket_id].bound) return -1; if (!out) return -2; udp_socket* sock = &g_net.sockets[socket_id]; uint32 idx = sock->rxq_head; if (sock->rxq[idx].valid) { *out = sock->rxq[idx].pkt; sock->rxq[idx].valid = 0; sock->rxq_head = (sock->rxq_head + 1) % SOCKET_RXQ_SIZE; return 1; } return 0; // No packet } uint32 net_udp_socket_queue_count(int socket_id) { if (socket_id < 0 || socket_id >= MAX_SOCKETS) return 0; if (!g_net.sockets[socket_id].bound) return 0; uint32 count = 0; for (int i = 0; i < SOCKET_RXQ_SIZE; i++) { if (g_net.sockets[socket_id].rxq[i].valid) count++; } return count; } uint32 net_get_sockets(net_socket_info* out, uint32 out_cap) { if (!out || out_cap == 0) return 0; uint32 written = 0; for (int i = 0; i < MAX_SOCKETS && written < out_cap; i++) { if (g_net.sockets[i].bound) { out[written].bound = 1; out[written].port = g_net.sockets[i].port; out[written].queued = net_udp_socket_queue_count(i); out[written].dropped = g_net.sockets[i].dropped_count; written++; } } return written; } int net_udp_recv(uint16 local_port, net_udp_rx_packet* out) { if (!g_net.inited || !g_net.dev) return -1; if (!out) return -2; if (local_port == 0) return -3; for (int i = 0; i < (int)(sizeof(g_net.udp_rxq) / sizeof(g_net.udp_rxq[0])); i++) { if (g_net.udp_rxq[i].valid && g_net.udp_rxq[i].pkt.dst_port == local_port) { *out = g_net.udp_rxq[i].pkt; g_net.udp_rxq[i].valid = 0; return 1; } } return 0; } int net_poll(const uint8 local_ip[4], uint32 budget_frames) { if (!local_ip) return -1; if (!g_net.inited || !g_net.dev) { if (net_init_e1000_default() != 0) return -2; } net_timer_run(); if (budget_frames == 0u) budget_frames = 64u; uint8 frame[1600]; uint32 len = 0; uint32 processed = 0; for (uint32 i = 0; i < budget_frames; i++) { int got = g_net.dev->rx_poll_frame(frame, (uint32)sizeof(frame), &len, 1); if (got < 0) return -3; if (got == 0) break; processed++; // ARP: learn and respond to requests for our IP. if (len >= (uint32)(sizeof(eth_hdr) + sizeof(arp_pkt))) { eth_hdr* eh0 = (eth_hdr*)frame; if (be16(eh0->ethertype_be) == 0x0806u) { arp_pkt* a0 = (arp_pkt*)(frame + sizeof(eth_hdr)); uint16 oper0 = be16(a0->oper_be); if (oper0 == 1u) { arp_cache_update(a0->spa, a0->sha); if (ipv4_eq(a0->tpa, local_ip)) { (void)arp_send_reply_silent(a0->sha, local_ip, g_net.mac, a0->spa); } } else if (oper0 == 2u) { arp_cache_update(a0->spa, a0->sha); } continue; } } // IPv4: basic handling (UDP enqueue + ICMP echo). if (len < (uint32)(sizeof(eth_hdr) + sizeof(ipv4_hdr))) continue; eth_hdr* eh = (eth_hdr*)frame; if (be16(eh->ethertype_be) != 0x0800u) continue; ipv4_hdr* ip = (ipv4_hdr*)(frame + sizeof(eth_hdr)); uint8 ver = (uint8)(ip->ver_ihl >> 4); uint8 ihl_words = (uint8)(ip->ver_ihl & 0x0Fu); if (ver != 4 || ihl_words < 5) continue; uint32 ip_hdr_len = (uint32)ihl_words * 4u; uint16 frag = be16(ip->flags_frag_off_be); uint16 frag_off = (uint16)(frag & 0x1FFFu); uint16 frag_flags = (uint16)(frag & 0xE000u); if (frag_off != 0 || (frag_flags & 0x2000u)) { g_net.ip_stats.ipv4_rx_fragments++; g_net.ip_stats.ipv4_rx_frag_dropped++; net_log("[NETSTACK] Dropping IPv4 fragment\n"); continue; } if (!ipv4_eq(ip->dst, local_ip)) continue; // Opportunistically learn IP->MAC mapping from IPv4 frames. arp_cache_update(ip->src, eh->src); if (ip->proto == 1) { // ICMP echo support for debugging (ping). if (len < (uint32)sizeof(eth_hdr) + ip_hdr_len + (uint32)sizeof(icmp_echo_hdr)) continue; icmp_echo_hdr* ic = (icmp_echo_hdr*)(frame + sizeof(eth_hdr) + ip_hdr_len); uint16 id = be16(ic->id_be); uint16 seq = be16(ic->seq_be); // Use IPv4 total length to compute payload length (bounds-checked against frame len). uint32 ip_total_len = (uint32)be16(ip->total_len_be); if (ip_total_len < ip_hdr_len + (uint32)sizeof(icmp_echo_hdr)) continue; uint32 icmp_total_len = ip_total_len - ip_hdr_len; uint32 icmp_payload_len = icmp_total_len - (uint32)sizeof(icmp_echo_hdr); uint32 icmp_payload_off = (uint32)sizeof(eth_hdr) + ip_hdr_len + (uint32)sizeof(icmp_echo_hdr); if (icmp_payload_off > len) continue; if (icmp_payload_off + icmp_payload_len > len) { if (len > icmp_payload_off) icmp_payload_len = len - icmp_payload_off; else icmp_payload_len = 0; } if (ic->type == 8 && ic->code == 0) { // Echo request to our IP: reply directly back to sender MAC. g_net.icmp_stats.echo_req_rx++; int rc = icmp_send_echo_reply_direct(eh->src, ip->dst, ip->src, id, seq, frame + icmp_payload_off, icmp_payload_len); if (rc == 0) g_net.icmp_stats.echo_rep_tx++; } else if (ic->type == 0 && ic->code == 0) { // Echo reply: enqueue for ping. g_net.icmp_stats.echo_rep_rx++; icmp_rxq_enqueue_reply(ip->src, id, seq, icmp_payload_len); } else if (ic->type == 3) { // Destination unreachable g_net.icmp_stats.dest_unreach_rx++; if (ic->code == 4) { g_net.icmp_stats.frag_needed_rx++; net_log("[NETSTACK] ICMP frag needed\n"); } } else if (ic->type == 11) { // Time exceeded g_net.icmp_stats.time_exceeded_rx++; net_log("[NETSTACK] ICMP time exceeded\n"); } continue; } // TCP handling (minimal client state machine). if (ip->proto == 6) { if (len < (uint32)sizeof(eth_hdr) + ip_hdr_len + (uint32)sizeof(tcp_hdr)) continue; tcp_hdr* tcp = (tcp_hdr*)(frame + sizeof(eth_hdr) + ip_hdr_len); uint32 tcp_hdr_len = (uint32)((tcp->data_off >> 4) & 0x0Fu) * 4u; if (tcp_hdr_len < (uint32)sizeof(tcp_hdr)) continue; uint16 src_port = be16(tcp->src_port_be); uint16 dst_port = be16(tcp->dst_port_be); if (dst_port == 0 || src_port == 0) continue; uint32 payload_off = (uint32)sizeof(eth_hdr) + ip_hdr_len + tcp_hdr_len; uint32 payload_len = 0; uint32 ip_total_len = (uint32)be16(ip->total_len_be); if (ip_total_len >= ip_hdr_len + tcp_hdr_len) { payload_len = ip_total_len - ip_hdr_len - tcp_hdr_len; } if (payload_off > len) continue; if (payload_off + payload_len > len) { if (len > payload_off) payload_len = len - payload_off; else payload_len = 0; } // Verify TCP checksum. if (tcp->checksum_be != 0) { uint16 saved = tcp->checksum_be; uint16 want = tcp_checksum16_ex(ip->src, ip->dst, (const uint8*)tcp, tcp_hdr_len, frame + payload_off, payload_len); if (be16(saved) != want) { continue; } } // Passive listen: SYN on listen port. if (g_net.tcp.listening && g_net.tcp.state == TCP_CLOSED && dst_port == g_net.tcp.listen_port && (tcp->flags & TCP_FLAG_SYN)) { g_net.tcp_stats.tcp_listen_syn_rx++; for (int i = 0; i < 4; i++) g_net.tcp.local_ip[i] = ip->dst[i]; for (int i = 0; i < 4; i++) g_net.tcp.remote_ip[i] = ip->src[i]; g_net.tcp.local_port = dst_port; g_net.tcp.remote_port = src_port; g_net.tcp.seq = net_get_ticks(); g_net.tcp.ack = be32(tcp->seq_be) + 1; g_net.tcp.state = TCP_SYN_RECEIVED; (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_SYN | TCP_FLAG_ACK, NULL, 0, 800000); tcp_retx_arm((uint8)(TCP_FLAG_SYN | TCP_FLAG_ACK), g_net.tcp.seq, g_net.tcp.ack, NULL, 0); g_net.tcp_stats.tcp_syn_sent++; continue; } // Match against current connection only. if (g_net.tcp.state != TCP_CLOSED && dst_port == g_net.tcp.local_port && src_port == g_net.tcp.remote_port && ipv4_eq(ip->src, g_net.tcp.remote_ip)) { uint32 seq = be32(tcp->seq_be); uint32 ack = be32(tcp->ack_be); uint8 flags = tcp->flags; if (flags & TCP_FLAG_RST) { g_net.tcp_stats.tcp_rst_rx++; g_net.tcp.state = TCP_CLOSED; tcp_retx_cancel(); continue; } if (g_net.tcp.state == TCP_SYN_SENT) { if ((flags & (TCP_FLAG_SYN | TCP_FLAG_ACK)) == (TCP_FLAG_SYN | TCP_FLAG_ACK) && ack == g_net.tcp.seq + 1) { g_net.tcp_stats.tcp_synack_rx++; tcp_retx_cancel(); g_net.tcp.seq += 1; g_net.tcp.ack = seq + 1; (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_ACK, NULL, 0, 800000); g_net.tcp_stats.tcp_ack_tx++; g_net.tcp.state = TCP_ESTABLISHED; g_net.tcp_stats.tcp_conn_established++; } continue; } if (g_net.tcp.state == TCP_SYN_RECEIVED) { if ((flags & TCP_FLAG_ACK) && ack == g_net.tcp.seq + 1) { tcp_retx_cancel(); g_net.tcp.seq += 1; g_net.tcp.state = TCP_ESTABLISHED; g_net.tcp_stats.tcp_conn_established++; } continue; } if (g_net.tcp.state == TCP_ESTABLISHED || g_net.tcp.state == TCP_FIN_WAIT) { if (flags & TCP_FLAG_ACK) { tcp_retx_ack_received(ack); } if (payload_len > 0) { g_net.tcp_stats.tcp_data_rx++; // Reliability invariant: only advance ACK for payload that is // in-order and successfully enqueued. If RX queue is full, // keep ACK unchanged so the peer retransmits. if (seq == g_net.tcp.ack) { int enq_rc = tcp_rxq_enqueue(ip->src, src_port, ip->dst, dst_port, frame + payload_off, payload_len); if (enq_rc == 0) { g_net.tcp.ack += payload_len; } } (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_ACK, NULL, 0, 800000); g_net.tcp_stats.tcp_ack_tx++; } if (flags & TCP_FLAG_FIN) { // Accept FIN only when it is in-order relative to current ACK. uint32 fin_seq = seq + payload_len; if (fin_seq == g_net.tcp.ack) { g_net.tcp_stats.tcp_fin_rx++; g_net.tcp.ack += 1u; (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_ACK, NULL, 0, 800000); g_net.tcp_stats.tcp_ack_tx++; g_net.tcp.state = TCP_CLOSED; tcp_retx_cancel(); } else { // Duplicate/out-of-order FIN: send current ACK only. (void)tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_ACK, NULL, 0, 800000); g_net.tcp_stats.tcp_ack_tx++; } } else if (g_net.tcp.state == TCP_FIN_WAIT && (flags & TCP_FLAG_ACK)) { g_net.tcp.state = TCP_CLOSED; tcp_retx_cancel(); } } } continue; } // UDP receive enqueue. if (ip->proto != 17) continue; if (len < (uint32)sizeof(eth_hdr) + ip_hdr_len + (uint32)sizeof(udp_hdr)) continue; udp_hdr* udp = (udp_hdr*)(frame + sizeof(eth_hdr) + ip_hdr_len); uint16 src_port = be16(udp->src_port_be); uint16 dst_port = be16(udp->dst_port_be); if (dst_port == 0 || src_port == 0) continue; uint32 udp_total_len = (uint32)be16(udp->len_be); if (udp_total_len < (uint32)sizeof(udp_hdr)) continue; uint32 payload_off = (uint32)sizeof(eth_hdr) + ip_hdr_len + (uint32)sizeof(udp_hdr); uint32 payload_len = udp_total_len - (uint32)sizeof(udp_hdr); if (payload_off > len) continue; if (payload_off + payload_len > len) { if (len > payload_off) payload_len = len - payload_off; else payload_len = 0; } // Verify UDP checksum if present (0 means "no checksum" for IPv4). if (udp->checksum_be != 0) { uint16 saved = udp->checksum_be; // Compute against the truncated payload we actually have. // This is best-effort; frames truncated by NIC/driver will fail checksum and be dropped. uint16 want = udp_checksum16(ip->src, ip->dst, udp, frame + payload_off, payload_len); if (be16(saved) != want) { g_net.udp_stats.udp_rx_bad_checksum++; continue; } } (void)udp_rxq_enqueue(ip->src, src_port, ip->dst, dst_port, frame + payload_off, payload_len); } return (int)processed; } int net_icmp_ping(const uint8 local_ip[4], const uint8 dst_ip[4], int count, int timeout_spins) { if (!local_ip || !dst_ip) return -1; if (count <= 0) count = 4; if (timeout_spins <= 0) timeout_spins = 8000000; if (net_init_e1000_default() != 0) return -2; // Stable ID so we can match replies. const uint16 id = 0xE100; int replies = 0; g_user_interrupt = 0; for (int i = 0; i < count; i++) { watchdog_kick("net-ping"); poll_keyboard_for_ctrl_c(); if (g_user_interrupt) { g_user_interrupt = 0; break; } uint16 seq = (uint16)(i + 1); uint8 dst_mac[6]; uint8 next_hop_ip[4]; ipv4_choose_next_hop(local_ip, dst_ip, next_hop_ip); int rc = arp_resolve(local_ip, next_hop_ip, dst_mac, 800000); if (rc != 0) { printf("%cPING%c ", 255, 255, 255, 255, 255, 255); print_ipv4_bytes(dst_ip); printf(": arp failed (%d)\n", rc); continue; } uint32 send_tick = sched_get_tick_count(); rc = icmp_send_echo_request(local_ip, dst_ip, dst_mac, id, seq); if (rc != 0) { printf("%cPING%c ", 255, 255, 255, 255, 255, 255); print_ipv4_bytes(dst_ip); printf(": tx failed (%d)\n", rc); continue; } int got_reply = 0; for (int spin = 0; spin < timeout_spins; spin++) { if ((spin & 0x3FFF) == 0) { watchdog_kick("net-ping-wait"); poll_keyboard_for_ctrl_c(); if (g_user_interrupt) { g_user_interrupt = 0; return replies; } } (void)net_poll(local_ip, 64); uint8 src_ip[4]; uint32 payload_len = 0; int found = icmp_rxq_dequeue_match(id, seq, src_ip, &payload_len); if (found == 1) { uint32 now_tick = sched_get_tick_count(); uint32 rtt_ticks = now_tick - send_tick; printf("%cPING%c ", 0, 255, 0, 255, 255, 255); print_ipv4_bytes(dst_ip); printf(": seq=%d len=%d time=%d ticks\n", (int)seq, (int)payload_len, (int)rtt_ticks); replies++; got_reply = 1; break; } } if (!got_reply) { printf("%cPING%c ", 255, 255, 255, 255, 255, 255); print_ipv4_bytes(dst_ip); printf(": seq=%d timeout\n", (int)seq); } // Small delay between pings. sleep(1); } return replies; } static int arp_send_request_silent(const uint8 sender_ip[4], const uint8 target_ip[4]) { uint8 frame[64]; memset(frame, 0, sizeof(frame)); // Ethernet header for (int i = 0; i < 6; i++) frame[i] = 0xFF; for (int i = 0; i < 6; i++) frame[6 + i] = g_net.mac[i]; frame[12] = 0x08; frame[13] = 0x06; arp_pkt* a = (arp_pkt*)(frame + sizeof(eth_hdr)); a->htype_be = be16(1u); a->ptype_be = be16(0x0800u); a->hlen = 6; a->plen = 4; a->oper_be = be16(1u); for (int i = 0; i < 6; i++) a->sha[i] = g_net.mac[i]; for (int i = 0; i < 4; i++) a->spa[i] = sender_ip[i]; for (int i = 0; i < 6; i++) a->tha[i] = 0; for (int i = 0; i < 4; i++) a->tpa[i] = target_ip[i]; uint32 total_len = (uint32)(sizeof(eth_hdr) + sizeof(arp_pkt)); if (total_len < 60u) total_len = 60u; return g_net.dev->send_frame(frame, total_len); } static int arp_poll_reply_mac(const uint8 target_ip[4], uint8 out_mac[6], int spin_limit) { if (spin_limit <= 0) spin_limit = 12000000; uint8 frame[1600]; uint32 len = 0; for (int spin = 0; spin < spin_limit; spin++) { if ((spin & 0x3FFF) == 0) { watchdog_kick("net-arp-wait"); } int got = g_net.dev->rx_poll_frame(frame, (uint32)sizeof(frame), &len, 1); if (got < 0) return -2; if (got == 0) continue; if (len >= (uint32)(sizeof(eth_hdr) + sizeof(arp_pkt))) { eth_hdr* eh = (eth_hdr*)frame; uint16 et = be16(eh->ethertype_be); if (et == 0x0806u) { arp_pkt* a = (arp_pkt*)(frame + sizeof(eth_hdr)); uint16 oper = be16(a->oper_be); if (oper == 2u) { arp_cache_update(a->spa, a->sha); if (ipv4_eq(a->spa, target_ip)) { for (int i = 0; i < 6; i++) out_mac[i] = a->sha[i]; return 0; } } } } } return -3; } static int arp_resolve(const uint8 sender_ip[4], const uint8 target_ip[4], uint8 out_mac[6], int arp_spins) { if (arp_cache_lookup(target_ip, out_mac)) return 0; int base_spins = arp_spins; if (base_spins <= 0) base_spins = 12000000; int per_try_spins = base_spins / 3; if (per_try_spins < 100000) per_try_spins = 100000; for (int attempt = 0; attempt < 3; attempt++) { int rc = arp_send_request_silent(sender_ip, target_ip); if (rc != 0) return -100 + rc; rc = arp_poll_reply_mac(target_ip, out_mac, per_try_spins); if (rc == 0) { arp_cache_update(target_ip, out_mac); return 0; } if (attempt < 2) { net_log("[NETSTACK] ARP retry\n"); sched_sleep_us(ARP_RETRY_BACKOFF_MS * 1000u); } } return -203; } static int arp_send_reply_silent(const uint8 target_mac[6], const uint8 sender_ip[4], const uint8 sender_mac[6], const uint8 target_ip[4]) { uint8 frame[64]; memset(frame, 0, sizeof(frame)); // Ethernet header for (int i = 0; i < 6; i++) frame[i] = target_mac[i]; for (int i = 0; i < 6; i++) frame[6 + i] = sender_mac[i]; frame[12] = 0x08; frame[13] = 0x06; arp_pkt* a = (arp_pkt*)(frame + sizeof(eth_hdr)); a->htype_be = be16(1u); a->ptype_be = be16(0x0800u); a->hlen = 6; a->plen = 4; a->oper_be = be16(2u); // Reply: "sender" is us; "target" is requester for (int i = 0; i < 6; i++) a->sha[i] = sender_mac[i]; for (int i = 0; i < 4; i++) a->spa[i] = sender_ip[i]; for (int i = 0; i < 6; i++) a->tha[i] = target_mac[i]; for (int i = 0; i < 4; i++) a->tpa[i] = target_ip[i]; uint32 total_len = (uint32)(sizeof(eth_hdr) + sizeof(arp_pkt)); if (total_len < 60u) total_len = 60u; return g_net.dev->send_frame(frame, total_len); } int net_arp_test_send(const uint8 sender_ip[4], const uint8 target_ip[4], int rx_spins) { if (!sender_ip || !target_ip) return -1; if (net_init_e1000_default() != 0) return -2; int rc = arp_send_request_silent(sender_ip, target_ip); if (rc != 0) return rc; printf("ARP who-has "); print_ipv4_bytes(target_ip); printf(" tell "); print_ipv4_bytes(sender_ip); printf(" (TX ok)\n"); uint8 mac[6]; rc = arp_poll_reply_mac(target_ip, mac, rx_spins); if (rc == 0) { printf("%cARP reply received.\n", 0, 255, 0); return 0; } return rc; } static int tcp_send_segment(const uint8 src_ip[4], uint16 src_port, const uint8 dst_ip[4], uint16 dst_port, uint32 seq, uint32 ack, uint8 flags, const uint8* payload, uint32 payload_len, int arp_spins) { if (!src_ip || !dst_ip) return -1; if (payload_len != 0u && !payload) return -1; if (payload_len > 1400u) return -2; if (src_port == 0 || dst_port == 0) return -3; uint8 dst_mac[6]; uint8 next_hop_ip[4]; ipv4_choose_next_hop(src_ip, dst_ip, next_hop_ip); int rc = arp_resolve(src_ip, next_hop_ip, dst_mac, arp_spins); if (rc != 0) return rc; uint8 frame[1600]; uint32 off = 0; eth_hdr* eh = (eth_hdr*)(frame + off); for (int i = 0; i < 6; i++) eh->dst[i] = dst_mac[i]; for (int i = 0; i < 6; i++) eh->src[i] = g_net.mac[i]; eh->ethertype_be = be16(0x0800u); off += (uint32)sizeof(eth_hdr); ipv4_hdr* ip = (ipv4_hdr*)(frame + off); memset(ip, 0, sizeof(*ip)); ip->ver_ihl = 0x45; ip->ttl = 64; ip->proto = 6; for (int i = 0; i < 4; i++) ip->src[i] = src_ip[i]; for (int i = 0; i < 4; i++) ip->dst[i] = dst_ip[i]; uint16 ip_total_len = (uint16)(sizeof(ipv4_hdr) + sizeof(tcp_hdr) + payload_len); ip->total_len_be = be16(ip_total_len); static uint16 ip_id = 0x6000; ip->id_be = be16(ip_id++); ip->flags_frag_off_be = be16(0x4000u); ip->hdr_checksum_be = 0; ip->hdr_checksum_be = be16(ipv4_checksum16(ip, (uint32)sizeof(*ip))); off += (uint32)sizeof(ipv4_hdr); tcp_hdr* tcp = (tcp_hdr*)(frame + off); memset(tcp, 0, sizeof(*tcp)); tcp->src_port_be = be16(src_port); tcp->dst_port_be = be16(dst_port); tcp->seq_be = be32(seq); tcp->ack_be = be32(ack); tcp->data_off = (uint8)(5u << 4); tcp->flags = flags; tcp->window_be = be16(4096u); tcp->urg_ptr_be = 0; tcp->checksum_be = 0; tcp->checksum_be = be16(tcp_checksum16(src_ip, dst_ip, tcp, payload, payload_len)); off += (uint32)sizeof(tcp_hdr); if (payload_len) { memcpy(frame + off, payload, payload_len); off += payload_len; } return g_net.dev->send_frame(frame, off); } int net_tcp_send(const uint8 local_ip[4], uint16 local_port, const uint8 dst_ip[4], uint16 dst_port, const uint8* payload, uint32 payload_len, int timeout_spins) { if (!local_ip || !dst_ip) return -1; if (payload_len != 0u && !payload) return -1; if (dst_port == 0) return -2; if (net_init_e1000_default() != 0) return -3; if (payload_len > NET_TCP_MAX_PAYLOAD) return -4; if (local_port == 0) { local_port = (uint16)(40000u + (net_get_ticks() % 20000u)); } // Initialize connection state. memset(&g_net.tcp, 0, sizeof(g_net.tcp)); for (int i = 0; i < 4; i++) g_net.tcp.local_ip[i] = local_ip[i]; for (int i = 0; i < 4; i++) g_net.tcp.remote_ip[i] = dst_ip[i]; g_net.tcp.local_port = local_port; g_net.tcp.remote_port = dst_port; g_net.tcp.seq = net_get_ticks(); g_net.tcp.ack = 0; g_net.tcp.state = TCP_SYN_SENT; g_net.tcp_stats.tcp_syn_sent++; int rc = tcp_send_segment(local_ip, local_port, dst_ip, dst_port, g_net.tcp.seq, 0, TCP_FLAG_SYN, NULL, 0, timeout_spins); tcp_retx_arm(TCP_FLAG_SYN, g_net.tcp.seq, 0, NULL, 0); if (rc != 0) return rc; // Wait for SYN-ACK if (timeout_spins <= 0) timeout_spins = 12000000; for (int spin = 0; spin < timeout_spins; spin++) { if ((spin & 0x1FFF) == 0) watchdog_kick("net-tcp-wait"); (void)net_poll(local_ip, 16); if (g_net.tcp.state == TCP_ESTABLISHED) break; } if (g_net.tcp.state != TCP_ESTABLISHED) return -5; // Send payload if any. if (payload_len > 0) { rc = tcp_send_segment(local_ip, local_port, dst_ip, dst_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_PSH | TCP_FLAG_ACK, payload, payload_len, timeout_spins); tcp_retx_arm((uint8)(TCP_FLAG_PSH | TCP_FLAG_ACK), g_net.tcp.seq, g_net.tcp.ack, payload, payload_len); if (rc != 0) return rc; g_net.tcp.seq += payload_len; g_net.tcp_stats.tcp_data_tx++; } // Send FIN rc = tcp_send_segment(local_ip, local_port, dst_ip, dst_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_FIN | TCP_FLAG_ACK, NULL, 0, timeout_spins); tcp_retx_arm((uint8)(TCP_FLAG_FIN | TCP_FLAG_ACK), g_net.tcp.seq, g_net.tcp.ack, NULL, 0); if (rc != 0) return rc; g_net.tcp.seq += 1; g_net.tcp.state = TCP_FIN_WAIT; g_net.tcp_stats.tcp_fin_tx++; return (int)payload_len; } int net_tcp_connect(const uint8 local_ip[4], uint16 local_port, const uint8 dst_ip[4], uint16 dst_port, int timeout_spins) { if (!local_ip || !dst_ip) return -1; if (dst_port == 0) return -2; if (net_init_e1000_default() != 0) return -3; if (local_port == 0) { local_port = (uint16)(40000u + (net_get_ticks() % 20000u)); } // Initialize connection state. memset(&g_net.tcp, 0, sizeof(g_net.tcp)); for (int i = 0; i < 4; i++) g_net.tcp.local_ip[i] = local_ip[i]; for (int i = 0; i < 4; i++) g_net.tcp.remote_ip[i] = dst_ip[i]; g_net.tcp.local_port = local_port; g_net.tcp.remote_port = dst_port; g_net.tcp.seq = net_get_ticks(); g_net.tcp.ack = 0; g_net.tcp.state = TCP_SYN_SENT; g_net.tcp.listening = 0; tcp_retx_cancel(); tcp_rxq_clear(); g_net.tcp_stats.tcp_syn_sent++; int rc = tcp_send_segment(local_ip, local_port, dst_ip, dst_port, g_net.tcp.seq, 0, TCP_FLAG_SYN, NULL, 0, timeout_spins); tcp_retx_arm(TCP_FLAG_SYN, g_net.tcp.seq, 0, NULL, 0); if (rc != 0) return rc; if (timeout_spins <= 0) timeout_spins = 2000000; uint32 ui_ticks = 0; for (int spin = 0; spin < timeout_spins; spin++) { if ((spin & 0x1FFF) == 0) { watchdog_kick("net-tcp-connect"); // Keep the GUI alive while connect waits in a blocking syscall. if (tile_is_tiling_active() && (++ui_ticks & 0x1F) == 0) { tile_render_once(); } } (void)net_poll(local_ip, 16); if (g_net.tcp.state == TCP_ESTABLISHED) break; } if (g_net.tcp.state != TCP_ESTABLISHED) return -5; return 0; } int net_tcp_send_current(const uint8* payload, uint32 payload_len) { if (payload_len != 0u && !payload) return -1; if (!g_net.inited || !g_net.dev) return -2; if (g_net.tcp.state != TCP_ESTABLISHED) return -3; if (g_net.tcp.local_port == 0 || g_net.tcp.remote_port == 0) return -4; if (payload_len > NET_TCP_MAX_PAYLOAD) return -5; if (payload_len == 0u) return 0; int rc = tcp_send_segment(g_net.tcp.local_ip, g_net.tcp.local_port, g_net.tcp.remote_ip, g_net.tcp.remote_port, g_net.tcp.seq, g_net.tcp.ack, TCP_FLAG_PSH | TCP_FLAG_ACK, payload, payload_len, 800000); tcp_retx_arm((uint8)(TCP_FLAG_PSH | TCP_FLAG_ACK), g_net.tcp.seq, g_net.tcp.ack, payload, payload_len); if (rc != 0) return rc; g_net.tcp.seq += payload_len; g_net.tcp_stats.tcp_data_tx++; return (int)payload_len; } int net_udp_send(const uint8 src_ip[4], uint16 src_port, const uint8 dst_ip[4], uint16 dst_port, const uint8* payload, uint32 payload_len, int arp_spins) { if (!src_ip || !dst_ip) return -1; if (payload_len != 0u && !payload) return -1; if (payload_len > 1400u) return -2; if (src_port == 0 || dst_port == 0) return -3; if (net_init_e1000_default() != 0) return -4; uint8 dst_mac[6]; uint8 next_hop_ip[4]; ipv4_choose_next_hop(src_ip, dst_ip, next_hop_ip); int rc = arp_resolve(src_ip, next_hop_ip, dst_mac, arp_spins); if (rc != 0) return rc; uint8 frame[1600]; uint32 off = 0; eth_hdr* eh = (eth_hdr*)(frame + off); for (int i = 0; i < 6; i++) eh->dst[i] = dst_mac[i]; for (int i = 0; i < 6; i++) eh->src[i] = g_net.mac[i]; eh->ethertype_be = be16(0x0800u); off += (uint32)sizeof(eth_hdr); ipv4_hdr* ip = (ipv4_hdr*)(frame + off); memset(ip, 0, sizeof(*ip)); ip->ver_ihl = 0x45; ip->ttl = 64; ip->proto = 17; for (int i = 0; i < 4; i++) ip->src[i] = src_ip[i]; for (int i = 0; i < 4; i++) ip->dst[i] = dst_ip[i]; uint16 ip_total_len = (uint16)(sizeof(ipv4_hdr) + sizeof(udp_hdr) + payload_len); ip->total_len_be = be16(ip_total_len); static uint16 ip_id = 1; ip->id_be = be16(ip_id++); ip->flags_frag_off_be = be16(0x4000u); ip->hdr_checksum_be = 0; ip->hdr_checksum_be = be16(ipv4_checksum16(ip, (uint32)sizeof(*ip))); off += (uint32)sizeof(ipv4_hdr); udp_hdr* udp = (udp_hdr*)(frame + off); udp->src_port_be = be16(src_port); udp->dst_port_be = be16(dst_port); udp->len_be = be16((uint16)(sizeof(udp_hdr) + payload_len)); udp->checksum_be = 0; off += (uint32)sizeof(udp_hdr); const uint8* frame_payload = (const uint8*)(frame + off); memcpy(frame + off, payload, payload_len); off += payload_len; { uint16 csum = udp_checksum16(src_ip, dst_ip, udp, frame_payload, payload_len); udp->checksum_be = be16(csum); g_net.udp_stats.udp_tx_checksums++; } if (off < 60u) { memset(frame + off, 0, 60u - off); off = 60u; } rc = g_net.dev->send_frame(frame, off); if (rc != 0) return -300 + rc; return 0; } int net_udp_listen(const uint8 local_ip[4], uint16 local_port, int max_packets, int spin_limit) { if (!local_ip) return -1; if (local_port == 0) return -2; // max_packets == 0 => unlimited // spin_limit == 0 => unlimited if (max_packets < 0) max_packets = 1; if (spin_limit < 0) spin_limit = 12000000; if (net_init_e1000_default() != 0) return -3; int printed = 0; g_user_interrupt = 0; // The shell command blocks the normal input pump, so we must poll Ctrl-C // ourselves while listening. const int unlimited_packets = (max_packets == 0); const int unlimited_spins = (spin_limit == 0); uint32 spin = 0; // UI refresh pacing: keep the tile display updating while we block. uint32 ui_ticks = 0; // Batch polling helps amortize overhead while still staying responsive. const uint32 poll_budget = 64u; for (;;) { watchdog_kick("net-udp-listen"); // Keep UI rendering while shell is blocked. if (tile_is_tiling_active()) { if ((ui_ticks++ & 0x1F) == 0) { tile_render_once(); } } // Detect Ctrl-C while the normal UI loop is blocked. poll_keyboard_for_ctrl_c(); if (g_user_interrupt) { g_user_interrupt = 0; break; } if (!unlimited_packets && printed >= max_packets) break; if (!unlimited_spins && spin >= (uint32)spin_limit) break; int did_work = 0; spin++; int rc = net_poll(local_ip, poll_budget); if (rc < 0) return -4; if (rc > 0) did_work = 1; // Drain any queued UDP packets for our port. for (;;) { net_udp_rx_packet pkt; int gotp = net_udp_recv(local_port, &pkt); if (gotp < 0) return -5; if (gotp == 0) break; printf("%cUDP RX ", 0, 255, 0); print_ipv4_bytes(pkt.src_ip); printf(":%d -> ", (int)pkt.src_port); print_ipv4_bytes(pkt.dst_ip); printf(":%d (%d bytes): ", (int)local_port, (int)pkt.payload_len); char ascii[260]; uint32 show_len = pkt.payload_len; if (show_len > 256u) show_len = 256u; for (uint32 i = 0; i < show_len; i++) { char c = (char)pkt.payload[i]; if (c < 32 || c > 126) c = '.'; ascii[i] = c; } ascii[show_len] = 0; printf("%s", ascii); if (pkt.payload_len > show_len) { printf("..."); } printf("\n"); if (tile_is_tiling_active()) { tile_render_once(); } printed++; did_work = 1; if (!unlimited_packets && printed >= max_packets) break; } // If we saw nothing, sleep briefly to keep the host/VM responsive. if (!did_work) { // Use a simple busy delay here (not HLT-based) because some shell // contexts may have interrupts temporarily disabled. sleep(1); } } return printed; } int net_udp_echo(const uint8 local_ip[4], uint16 local_port, int max_packets, int spin_limit) { if (!local_ip) return -1; if (local_port == 0) return -2; // max_packets == 0 => unlimited // spin_limit == 0 => unlimited if (max_packets < 0) max_packets = 1; if (spin_limit < 0) spin_limit = 12000000; if (net_init_e1000_default() != 0) return -3; int printed = 0; g_user_interrupt = 0; // The shell command blocks the normal input pump, so we must poll Ctrl-C // ourselves while listening. const int unlimited_packets = (max_packets == 0); const int unlimited_spins = (spin_limit == 0); uint32 spin = 0; // UI refresh pacing: keep the tile display updating while we block. uint32 ui_ticks = 0; const uint32 poll_budget = 64u; for (;;) { watchdog_kick("net-udp-echo"); // Keep UI rendering while shell is blocked. if (tile_is_tiling_active()) { if ((ui_ticks++ & 0x1F) == 0) { tile_render_once(); } } // Detect Ctrl-C while the normal UI loop is blocked. poll_keyboard_for_ctrl_c(); if (g_user_interrupt) { g_user_interrupt = 0; break; } if (!unlimited_packets && printed >= max_packets) break; if (!unlimited_spins && spin >= (uint32)spin_limit) break; int did_work = 0; spin++; int rc = net_poll(local_ip, poll_budget); if (rc < 0) return -4; if (rc > 0) did_work = 1; // Drain any queued UDP packets for our port, print and echo. for (;;) { net_udp_rx_packet pkt; int gotp = net_udp_recv(local_port, &pkt); if (gotp < 0) return -5; if (gotp == 0) break; printf("%cUDP RX ", 0, 255, 0); print_ipv4_bytes(pkt.src_ip); printf(":%d -> ", (int)pkt.src_port); print_ipv4_bytes(pkt.dst_ip); printf(":%d (%d bytes): ", (int)local_port, (int)pkt.payload_len); char ascii[260]; uint32 show_len = pkt.payload_len; if (show_len > 256u) show_len = 256u; for (uint32 i = 0; i < show_len; i++) { char c = (char)pkt.payload[i]; if (c < 32 || c > 126) c = '.'; ascii[i] = c; } ascii[show_len] = 0; printf("%s", ascii); if (pkt.payload_len > show_len) { printf("..."); } printf("\n"); if (tile_is_tiling_active()) { tile_render_once(); } // Echo payload back to sender. Note payload was already truncated to NET_UDP_MAX_PAYLOAD. if (pkt.payload_len == 0u) { static const uint8 empty_msg[1] = { '\n' }; (void)net_udp_send(local_ip, local_port, pkt.src_ip, pkt.src_port, empty_msg, 1u, 800000); } else { (void)net_udp_send(local_ip, local_port, pkt.src_ip, pkt.src_port, pkt.payload, pkt.payload_len, 800000); } printed++; did_work = 1; if (!unlimited_packets && printed >= max_packets) break; } // If we saw nothing, sleep briefly to keep the host/VM responsive. if (!did_work) { // Use a simple busy delay here (not HLT-based) because some shell // contexts may have interrupts temporarily disabled. sleep(1); } } return printed; }