diff --git a/Makefile b/Makefile index 993aa303..df2388dd 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ else BOOTFS := /media/bootfs endif -.PHONY: all shared user kernel clean raspi virt run debug dump prepare-fs help install +.PHONY: all modules kernel shared user tools clean raspi virt run debug dump prepare-fs help install docs all: kshared modules kernel shared user tools libs docs @echo "Build complete." @@ -51,7 +51,10 @@ clean: $(MAKE) -C modules $@ $(MAKE) -C libs $@ @echo "removing images" - $(RM) kernel.img kernel.elf dump + $(RM) -f kernel.img kernel.elf disk.img dump trace serial.log gdb_net.log screenlog.0 dump.dtb + $(RM) -f logs/*.log fs/kernel.elf + $(RM) -rf fs/redos/tools fs/redos/system fs/redos/user fs/redos/docs .cache + @find ./user ./tools ./modules ./kernel ./shared ./libs -type f \( -name dump -o -name '*.dump' -o -name '*.trace' -o -name '*.log' -o -name '*.tmp' \) -exec rm -f {} + raspi: $(MAKE) LOAD_ADDR=0x80000 XHCI_CTX_SIZE=64 QEMU=true MODE=raspi all diff --git a/common.mk b/common.mk index 4e6aaace..afb7eebb 100644 --- a/common.mk +++ b/common.mk @@ -15,7 +15,7 @@ COMMON_FLAGS ?= -ffreestanding -nostdlib -fno-exceptions -fno-unwind-tables \ -fno-asynchronous-unwind-tables -g -O0 -Wall -Wextra \ -Wno-unused-parameter -Wno-address-of-packed-member \ -Werror \ - -Wno-unused-function + -Wno-unused-function -Wno-missing-field-initializers ifeq ($(ARCH), aarch64-none-elf-) COMMON_FLAGS += -mcpu=cortex-a72 -Wno-error=sized-deallocation diff --git a/kernel/exceptions/exception_handler.c b/kernel/exceptions/exception_handler.c index 102feb16..976673ad 100644 --- a/kernel/exceptions/exception_handler.c +++ b/kernel/exceptions/exception_handler.c @@ -6,8 +6,10 @@ #include "theme/theme.h" #include "std/string.h" #include "sysregs.h" +#include "process/syscall.h" static bool panic_triggered = false; +static bool panic_skip_trace = false; void set_exception_vectors(){ extern char exception_vectors[]; @@ -27,7 +29,11 @@ void handle_exception(const char* type, uint64_t info) { char buf[STRING_MAX_LEN];//no heap to avoid corruption const char *fmt = "%s \r\nESR_EL1: %llx\r\nELR_EL1: %llx\r\nFAR_EL1: %llx"; string_format_buf(buf, sizeof(buf), fmt,type,esr,elr,far); + + bool old_panic_skip_trace = panic_skip_trace; + panic_skip_trace = syscall_depth > 0; panic(buf, info); + panic_skip_trace = old_panic_skip_trace; } void fiq_el1_handler(){ handle_exception("FIQ EXCEPTION\r\n", 0); } @@ -61,6 +67,10 @@ void panic(const char* msg, uint64_t info) { uart_raw_puts("Additional info: "); uart_puthex(info); uart_raw_puts("\r\n"); + if (!old_panic_triggered && !panic_skip_trace) { + uart_raw_puts("Backtrace:\r\n"); + trace(); + } uart_raw_puts("System Halted\r\n"); if (!old_panic_triggered){ char buf[STRING_MAX_LEN]; diff --git a/kernel/exceptions/timer.c b/kernel/exceptions/timer.c index ca996028..a5af89f4 100644 --- a/kernel/exceptions/timer.c +++ b/kernel/exceptions/timer.c @@ -1,4 +1,5 @@ #include "timer.h" +#include "irq.h" #include "math/math.h" #define TIMER_SLEW_MAX_PPM 500 @@ -9,7 +10,9 @@ static int g_sync = 0; static uint64_t g_wall_base_mono_us = 0; static int64_t g_wall_base_unix_us = 0; static int32_t g_freq_ppm = 0; +static int64_t g_freq_frac = 0; static int64_t g_slew_rem_us = 0; +static uint64_t g_slew_frac = 0; static int32_t g_tz_offset_min = 0; @@ -51,7 +54,9 @@ void timer_init(uint64_t msecs) { g_wall_base_mono_us = timer_now_usec(); g_wall_base_unix_us = 0; g_freq_ppm = 0; + g_freq_frac = 0; g_slew_rem_us = 0; + g_slew_frac = 0; g_sync = 0; } @@ -87,7 +92,9 @@ uint64_t timer_now() { uint64_t timer_now_msec() { uint64_t ticks = timer_now(); uint64_t freq = rd_cntfrq_el0(); - return (ticks * 1000) / freq; + uint64_t q = ticks / freq; + uint64_t r = ticks % freq; + return q * 1000ULL + (r * 1000ULL) / freq; } uint64_t timer_now_usec(void) { @@ -110,17 +117,25 @@ static int64_t wall_advance_to(uint64_t mono_now_us) { int64_t dt = (int64_t)dt_u; int64_t base = g_wall_base_unix_us; - int64_t adj = dt +(dt * (int64_t)g_freq_ppm)/1000000LL; - base += adj; - - int64_t max_slew = (dt * (int64_t)TIMER_SLEW_MAX_PPM) / 1000000LL; - if (max_slew < 1)max_slew = 1; + int64_t dt_sec = dt / 1000000LL; + int64_t dt_rem = dt % 1000000LL; + int64_t freq_num = dt_rem * (int64_t)g_freq_ppm + g_freq_frac; + int64_t freq_adj = dt_sec * (int64_t)g_freq_ppm + freq_num / 1000000LL; + g_freq_frac = freq_num % 1000000LL; + base += dt + freq_adj; if (g_slew_rem_us) { - int64_t apply = clamp_i64(g_slew_rem_us, -max_slew, max_slew); - g_slew_rem_us -= apply; - base += apply; - } + uint64_t slew_num = (dt_u % 1000000ULL) * (uint64_t)TIMER_SLEW_MAX_PPM + g_slew_frac; + int64_t max_slew = (int64_t)((dt_u / 1000000ULL) * (uint64_t)TIMER_SLEW_MAX_PPM + slew_num / 1000000ULL); + g_slew_frac = slew_num % 1000000ULL; + + if (max_slew) { + int64_t apply = clamp_i64(g_slew_rem_us, -max_slew, max_slew); + g_slew_rem_us -= apply; + base += apply; + if (!g_slew_rem_us) g_slew_frac = 0; + } + } else g_slew_frac = 0; g_wall_base_mono_us = mono_now_us; g_wall_base_unix_us = base; @@ -131,44 +146,65 @@ static int64_t wall_advance_to(uint64_t mono_now_us) { } uint64_t timer_wall_time_us(void) { - return (uint64_t)wall_advance_to(timer_now_usec()); + irq_flags_t irq = irq_save_disable(); + uint64_t us = (uint64_t)wall_advance_to(timer_now_usec()); + irq_restore(irq); + return us; } uint64_t timer_unix_time_us(void) { - if (!g_sync) return 0; + irq_flags_t irq = irq_save_disable(); + if (!g_sync) { + irq_restore(irq); + return 0; + } int64_t u = wall_advance_to( timer_now_usec()); + irq_restore(irq); if (u < 0) return 0; return (uint64_t)u; } void timer_sync_set_unix_us(uint64_t unix_us) { + irq_flags_t irq = irq_save_disable(); uint64_t now_us = timer_now_usec(); g_wall_base_mono_us = now_us; g_wall_base_unix_us= (int64_t)unix_us; + g_freq_frac = 0; g_slew_rem_us = 0; + g_slew_frac = 0; g_sync = 1; + irq_restore(irq); } void timer_sync_slew_us(int64_t delta_us){ + irq_flags_t irq = irq_save_disable(); + wall_advance_to(timer_now_usec()); const int64_t cap = 60LL * 1000000LL; - int64_t v = g_slew_rem_us + delta_us; - g_slew_rem_us = clamp_i64(v, -cap, cap); + g_slew_rem_us = clamp_i64(delta_us, -cap, cap); + g_slew_frac = 0; + irq_restore(irq); } void timer_sync_set_freq_ppm(int32_t ppm) { + irq_flags_t irq = irq_save_disable(); + wall_advance_to(timer_now_usec()); g_freq_ppm = clamp_i64((int32_t)ppm, -TIMER_FREQ_MAX_PPM, TIMER_FREQ_MAX_PPM); + g_freq_frac = 0; + irq_restore(irq); } int32_t timer_sync_get_freq_ppm(void) { - return g_freq_ppm; -} - -void timer_apply_sntp_sample_us(uint64_t server_unix_us) { - timer_sync_set_unix_us(server_unix_us); + irq_flags_t irq = irq_save_disable(); + int32_t ppm = g_freq_ppm; + irq_restore(irq); + return ppm; } int timer_is_synchronised(void) { - return g_sync; + irq_flags_t irq = irq_save_disable(); + int sync = g_sync; + irq_restore(irq); + return sync; } uint64_t timer_unix_time_ms(void) { @@ -194,10 +230,18 @@ uint64_t timer_local_time_ms(void){ } int timer_set_manual_unix_time_ms(uint64_t unix_ms){ - if (g_sync) return -1; + irq_flags_t irq = irq_save_disable(); + if (g_sync) { + irq_restore(irq); + return -1; + } uint64_t now_us = timer_now_usec(); g_wall_base_mono_us = now_us; g_wall_base_unix_us = (int64_t)(unix_ms * 1000ULL); + g_freq_frac = 0; + g_slew_rem_us = 0; + g_slew_frac = 0; + irq_restore(irq); return 0; } @@ -224,10 +268,9 @@ static void civil_from_days(int64_t z, int64_t* y, unsigned* m, unsigned* d){ *y = y_full; *m = mm; *d = dd; } -static void fmt2u(uint64_t v, char out[3]){ //maybe move this in a helper file +static inline void fmt2u(uint32_t v, char out[2]){ //maybe move this in a helper file out[0] = (char)('0' + (v/10u)%10u); out[1] = (char)('0' + (v%10u)); - out[2] = '\0'; } void timer_unix_ms_to_datetime(uint64_t unix_ms, int use_local, DateTime* out){ @@ -281,28 +324,18 @@ int timer_now_datetime(DateTime* out, int use_local){ void timer_datetime_to_string(const DateTime* dt, char* buf, uint32_t buflen){ if (!dt || !buf || buflen < 20) return; - uint16_t Y = dt->year; - buf[0] = (char)('0' + (Y/1000)%10); - buf[1] = (char)('0' + (Y/100)%10); - buf[2] = (char)('0' + (Y/10)%10); - buf[3] = (char)('0' + (Y%10)); + fmt2u(dt->year/100, buf); + fmt2u(dt->year, buf + 2); buf[4] = '-'; - char mm[3], dd[3], HH[3], MM[3], SS[3]; - fmt2u(dt->month, mm); - fmt2u(dt->day, dd); - fmt2u(dt->hour, HH); - fmt2u(dt->minute, MM); - fmt2u(dt->second, SS); - - buf[5] = mm[0]; buf[6] = mm[1]; + fmt2u(dt->month, buf + 5); buf[7] = '-'; - buf[8] = dd[0]; buf[9] = dd[1]; + fmt2u(dt->day, buf + 8); buf[10] = ' '; - buf[11] = HH[0]; buf[12] = HH[1]; + fmt2u(dt->hour, buf + 11); buf[13] = ':'; - buf[14] = MM[0]; buf[15] = MM[1]; + fmt2u(dt->minute, buf + 14); buf[16] = ':'; - buf[17] = SS[0]; buf[18] = SS[1]; + fmt2u(dt->second, buf + 17); buf[19] = '\0'; } diff --git a/kernel/exceptions/timer.h b/kernel/exceptions/timer.h index 077b7a01..c70ba177 100644 --- a/kernel/exceptions/timer.h +++ b/kernel/exceptions/timer.h @@ -23,8 +23,6 @@ uint64_t timer_now_usec(void); uint64_t timer_wall_time_us(void); uint64_t timer_unix_time_us(void); -void timer_apply_sntp_sample_us(uint64_t server_unix_us); - void timer_sync_set_unix_us(uint64_t unix_us); void timer_sync_slew_us(int64_t delta_us); void timer_sync_set_freq_ppm(int32_t ppm); diff --git a/kernel/fw/fw_cfg.c b/kernel/fw/fw_cfg.c index 6257d8d4..5ec2a23d 100644 --- a/kernel/fw/fw_cfg.c +++ b/kernel/fw/fw_cfg.c @@ -2,6 +2,7 @@ #include "console/kio.h" #include "std/memory_access.h" #include "memory/mmu.h" +#include "memory/addr.h" #include "async.h" #include "sysregs.h" #include "std/string.h" @@ -44,7 +45,6 @@ void fw_cfg_dma_operation(void* dest, uint32_t size, uint32_t ctrl) { }; write64(PHYS_TO_VIRT(FW_CFG_DMA), __builtin_bswap64(pt_va_to_pa(&access))); - __asm__("isb"); if (!wait(&access.control, __builtin_bswap32(~0x1), false, 2000)){ @@ -71,7 +71,7 @@ bool fw_find_file(const char* search, struct fw_cfg_file *file) { if (!fw_cfg_check()) return false; - + u32 count = 0; fw_cfg_dma_read(&count, sizeof(count), FW_LIST_DIRECTORY); diff --git a/kernel/memory/addr.c b/kernel/memory/addr.c index e7bde8dc..f8905d46 100644 --- a/kernel/memory/addr.c +++ b/kernel/memory/addr.c @@ -1,4 +1,5 @@ #include "addr.h" +#include "va_layout.h" #include "va_layout.h" diff --git a/kernel/memory/page_allocator.c b/kernel/memory/page_allocator.c index 78919279..d9a85c20 100644 --- a/kernel/memory/page_allocator.c +++ b/kernel/memory/page_allocator.c @@ -186,6 +186,7 @@ void setup_page(uintptr_t address, uint8_t attributes){ } paddr_t palloc_inner(uint64_t size, uint8_t level, uint8_t attributes, bool full, bool map) { + if (!size) return 0; if (!alloc_max_page) page_alloc_init(); if (!page_alloc_high_va) page_alloc_enable_high_va(); uint64_t page_count = count_pages(size,PAGE_SIZE); @@ -263,10 +264,10 @@ paddr_t palloc_inner(uint64_t size, uint8_t level, uint8_t attributes, bool full uint64_t inv = ~mem_bitmap[i]; uint64_t bit = __builtin_ctzll(inv); - if (bit > (64 - page_count)){ - continue; - } - while (bit < 64) { + uint64_t max_bit = 64 - page_count; + if (bit > max_bit) continue; + + while (bit <= max_bit) { bool found = true; for (uint64_t b = bit; b < bit + page_count; b++){ if ((mem_bitmap[i] >> b) & 1ull){ @@ -277,7 +278,7 @@ paddr_t palloc_inner(uint64_t size, uint8_t level, uint8_t attributes, bool full } if (found) break; } - if (bit >= 64) continue; + if (bit > max_bit) continue; uintptr_t first_address = 0; mem_page* prev_page = 0; diff --git a/kernel/networking/application_layer/csocket_http_client.c b/kernel/networking/application_layer/csocket_http_client.c new file mode 100644 index 00000000..f2924878 --- /dev/null +++ b/kernel/networking/application_layer/csocket_http_client.c @@ -0,0 +1,481 @@ +#include "csocket_http_client.h" +#include "csocket_http_internal.h" +#include "networking/transport_layer/csocket.h" +#include "networking/net_logger/net_logger.h" +#include "http.h" +#include "std/string.h" +#include "std/memory.h" +#include "net/socket_types.h" +#include "data/format/url.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/transport_layer/socket_endpoint.h" +#include "process/scheduler.h" +#include "alloc/allocate.h" + +typedef struct HTTPClientOrigin { + string domain; + net_l4_endpoint ep; + bool valid; +} HTTPClientOrigin; + +typedef struct HTTPClient { + socket_handle_t sock; + SocketOptions log_opts; + SocketOptions tcp_opts; + HTTPClientPolicy policy; + HTTPClientOrigin origin; +} HTTPClient; + +static HTTPResponseMsg http_client_error_response(int64_t err) { + HTTPResponseMsg resp = {0}; + resp.status_code = (HttpError)err; + return resp; +} + +static HTTPResponseMsg http_client_receive_response(HTTPClient* cli, HTTPMethod request_method) { + HTTPResponseMsg resp = {0}; + string buf = (string){0}; + char tmp[512]; + + while (1) { + int32_t hdr_end = -1; + int32_t head_result = http_socket_recv_head(cli->sock, &buf, cli->policy.common.max_header_bytes, cli->policy.common.header_idle_timeout_ms, cli->policy.common.header_total_timeout_ms, &hdr_end); + if (head_result < 0) { + string_free(buf); + if (cli->sock) http_socket_close(&cli->sock, true); + return http_client_error_response(head_result == SOCK_ERR_WOULDBLOCK ? SOCK_ERR_PROTO : head_result); + } + + int32_t status_line_end = strindex((char*)buf.data, "\r\n"); + if (status_line_end <= 0) { + string_free(buf); + if (cli->sock) http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_PROTO); + } + + HTTPStatusLine status_line = {0}; + if (http_parse_status_line(buf.data, status_line_end, &status_line) != HTTP_PARSE_OK) { + string_free(buf); + if (cli->sock) http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_PROTO); + } + + resp.status_code = (HttpError)status_line.status_code; + if (status_line.reason_len) { + resp.reason = (string){0}; + string_append_bytes(&resp.reason, buf.data + status_line.reason_off, status_line.reason_len); + } + + HTTPParseResult header_result = http_header_parse( + (char*)buf.data + status_line_end + 2, + (uint32_t)hdr_end - (uint32_t)(status_line_end + 2), + &cli->policy.common, + &resp.headers_common, + &resp.extra_headers, + &resp.extra_header_count); + + if (header_result != HTTP_PARSE_OK) { + http_response_free(&resp); + string_free(buf); + if (cli->sock) (void)http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_PROTO); + } + + uint32_t body_start = (uint32_t)hdr_end + 4; + uint32_t have = buf.length > body_start ? buf.length - body_start : 0; + uint32_t code = (uint32_t)resp.status_code; + bool no_body = request_method == HTTP_METHOD_HEAD || code == 204 || code == 304; + if (code >= 100 && code < 200) { + HTTPResponseMsg info = resp; + resp = (HTTPResponseMsg){0}; + if (info.reason.mem_length) string_free(info.reason); + http_headers_common_free(&info.headers_common); + http_headers_extra_free(info.extra_headers, info.extra_header_count); + + if (have) memmove(buf.data, buf.data + body_start, have); + if (buf.data) buf.data[have] = 0; + buf.length = have; + continue; + } + + if (no_body) { + } else if (resp.headers_common.framing.chunked) { + HTTPChunkedDecoder dec; + http_chunked_decoder_init(&dec, &cli->policy.common); + uint32_t used = 0; + HTTPParseResult chunk_result = have ? http_chunked_decoder_feed(&dec, buf.data + body_start, have, &used) : HTTP_PARSE_INCOMPLETE; + uint32_t body_start_ms = (uint32_t)get_time(); + HTTPSocketTimeoutState body_timeout = {body_start_ms, body_start_ms}; + + while (chunk_result == HTTP_PARSE_INCOMPLETE) { + int64_t r = http_socket_recv_wait(cli->sock, tmp, sizeof(tmp), cli->policy.common.body_idle_timeout_ms, cli->policy.common.body_total_timeout_ms, &body_timeout); + if (r <= 0) break; + chunk_result = http_chunked_decoder_feed(&dec, tmp, (uint32_t)r, &used); + } + + if (chunk_result != HTTP_PARSE_OK) { + http_chunked_decoder_free(&dec); + http_response_free(&resp); + string_free(buf); + if (cli->sock) (void)http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_PROTO); + } + + string decoded = dec.body; + dec.body = (string){0}; + http_chunked_decoder_free(&dec); + if (decoded.length) { + resp.body = decoded; + } else if (decoded.mem_length) string_free(decoded); + } else { + uint32_t need = resp.headers_common.framing.has_content_length ? resp.headers_common.fields.content_length : 0; + + if (resp.headers_common.framing.has_content_length) { + if (need > cli->policy.common.max_body_bytes) { + http_response_free(&resp); + string_free(buf); + if (cli->sock) (void)http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_PROTO); + } + + if (need) { + char* body_copy = (char*)zalloc(need); + if (!body_copy) { + http_response_free(&resp); + string_free(buf); + if (cli->sock) (void)http_socket_close(&cli->sock, true); + return http_client_error_response(SOCK_ERR_SYS); + } + + uint32_t copied = have < need ? have : need; + if (copied) memcpy(body_copy, buf.data + body_start, copied); + + if (copied < need) { + int64_t receive_result = http_socket_recv_exact(cli->sock, body_copy + copied, need - copied, cli->policy.common.body_idle_timeout_ms, cli->policy.common.body_total_timeout_ms); + if (receive_result < 0) { + release(body_copy); + http_response_free(&resp); + string_free(buf); + if (cli->sock) (void)http_socket_close(&cli->sock, true); + return http_client_error_response(receive_result == SOCK_ERR_WOULDBLOCK ? SOCK_ERR_PROTO : receive_result); + } + } + + resp.body = (string){body_copy, need, need}; + } + } else if (cli->policy.allow_close_delimited) { + string body = (string){0}; + uint32_t max_body = cli->policy.common.max_body_bytes; + bool bad_body = have > max_body; + int64_t body_err = SOCK_ERR_PROTO; + if (!bad_body && have) { + string_append_bytes(&body, buf.data + body_start, have); + bad_body = body.length != have; + } + + uint32_t body_start_ms = (uint32_t)get_time(); + HTTPSocketTimeoutState body_timeout = {body_start_ms, body_start_ms}; + while (!bad_body) { + int64_t r = http_socket_recv_wait(cli->sock, tmp, sizeof(tmp), cli->policy.common.body_idle_timeout_ms, cli->policy.common.body_total_timeout_ms, &body_timeout); + if (r < 0) { + body_err = r == SOCK_ERR_WOULDBLOCK ? SOCK_ERR_PROTO : r; + bad_body = true; + break; + } + if (r == 0) break; + if ((uint32_t)r > max_body - body.length) { + bad_body = true; + break; + } + uint32_t expected = body.length + (uint32_t)r; + string_append_bytes(&body, tmp, (uint32_t)r); + if (body.length != expected) bad_body = true; + } + + if (bad_body) { + string_free(body); + http_response_free(&resp); + string_free(buf); + if (cli->sock) http_socket_close(&cli->sock, true); + return http_client_error_response(body_err); + } + if (body.length) { + resp.body = body; + } else if (body.mem_length) string_free(body); + } + } + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_CLIENT; + ev.action = NETLOG_ACT_HTTP_RECV_RESPONSE; + ev.pid = get_current_proc_pid(); + ev.u0 = (uint32_t)resp.status_code; + ev.u1 = (uint32_t)resp.body.length; + http_socket_fill_log_endpoints(cli->sock, &ev); + netlog_socket_event(&cli->log_opts, &ev); + + string_free(buf); + return resp; + } +} + +static int32_t http_client_connect_endpoint_origin(HTTPClient* cli, const net_l4_endpoint* dst, const char* host_name) { + if (!cli->sock) { + SocketOptions connect_options = cli->tcp_opts; + connect_options.flags &= ~SOCK_OPT_NONBLOCK; + cli->sock = create_socket(PROTO_TCP, &connect_options); + } + if (!cli->sock) return SOCK_ERR_SYS; + int32_t r = connect_socket(cli->sock, dst); + if (r >= 0 && (r = set_socket_option(cli->sock, SOCK_OPT_NONBLOCK, &(uint32_t){1}, sizeof(uint32_t))) >= 0) { + HTTPClientOrigin next = {0}; + next.valid = true; + if (dst) next.ep = *dst; + if (host_name) next.domain = string_from_literal(host_name); + if (cli->origin.domain.mem_length) string_free(cli->origin.domain); + cli->origin = next; + } else { + (void)http_socket_close(&cli->sock, true); + } + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_CLIENT; + ev.action = NETLOG_ACT_CONNECT; + ev.pid = get_current_proc_pid(); + if (dst) ev.dst_ep = *dst; + ev.i0 = r; + + if (cli->sock) { + http_socket_fill_log_endpoints(cli->sock, &ev); + if (ev.remote_ep.ver) ev.dst_ep = ev.remote_ep; + } + + netlog_socket_event(&cli->log_opts, &ev); + return r; +} + +static int32_t http_client_connect_domain_origin(HTTPClient* cli, const char* host, uint16_t port) { + if (!host || !port) return SOCK_ERR_INVAL; + net_l4_endpoint ep = socket_endpoint_select(host, port, (ip_version_t)0, DNS_USE_BOTH, 3000); + if (!ep.ver) return SOCK_ERR_DNS; + return http_client_connect_endpoint_origin(cli, &ep, host); +} + +http_client_handle_t http_client_create(const SocketOptions* extra, const HTTPClientPolicyOptions* http_options) { + HTTPClient* cli = (HTTPClient*)zalloc(sizeof(*cli)); + if (!cli) return NULL; + + cli->policy = http_client_policy_from_options(http_options); + if (extra) { + cli->log_opts = *extra; + cli->tcp_opts = *extra; + cli->tcp_opts.flags &= ~SOCK_OPT_DEBUG; + } + + return cli; +} + +void http_client_destroy(http_client_handle_t h) { + if (!h) return; + HTTPClient* cli = (HTTPClient*)h; + if (cli->sock) (void)http_socket_close(&cli->sock, true); + if (cli->origin.domain.mem_length) string_free(cli->origin.domain); + release(cli); +} + +int32_t http_client_set_options(http_client_handle_t h, const HTTPClientPolicyOptions* http_options) { + if (!h) return (int32_t)SOCK_ERR_INVAL; + HTTPClient* cli = (HTTPClient*)h; + cli->policy = http_client_policy_from_options(http_options); + return SOCK_OK; +} + +int32_t http_client_connect_endpoint(http_client_handle_t h, const net_l4_endpoint* dst) { + if (!h || !dst) return (int32_t)SOCK_ERR_INVAL; + HTTPClient* cli = (HTTPClient*)h; + return http_client_connect_endpoint_origin(cli, dst, NULL); +} + +int32_t http_client_connect_domain(http_client_handle_t h, const char* host, uint16_t port) { + if (!h || !host) return (int32_t)SOCK_ERR_INVAL; + HTTPClient* cli = (HTTPClient*)h; + return http_client_connect_domain_origin(cli, host, port); +} + +HTTPResponseMsg http_client_send_request(http_client_handle_t h, const HTTPRequestMsg* req) { + HTTPResponseMsg empty = {0}; + if (!h || !req) { + empty.status_code = (HttpError)SOCK_ERR_INVAL; + return empty; + } + + HTTPClient* cli = (HTTPClient*)h; + HTTPRequestMsg curr = *req; + curr.path.mem_length = 0; + curr.headers_common.fields.host.mem_length = 0; + + HTTPResponseMsg resp = {0}; + uint32_t redirects = cli->policy.follow_redirects ? cli->policy.max_redirects : 0; + for (uint32_t i = 0;; i++) { + if (!cli->sock) { + resp = http_client_error_response(SOCK_ERR_STATE); + break; + } + + if (!curr.host_override && curr.version != HTTP_VERSION_10 && !curr.headers_common.fields.host.length && cli->origin.valid) { + if (cli->origin.domain.data) { + uint16_t host_port = cli->origin.ep.port; + bool ipv6 = memchr(cli->origin.domain.data, ':', cli->origin.domain.length) != 0; + if (host_port && host_port != 80) { + if (ipv6) curr.headers_common.fields.host = string_format("[%.*s]:%i", (int)cli->origin.domain.length, cli->origin.domain.data, (int)host_port); + else curr.headers_common.fields.host = string_format("%.*s:%i", (int)cli->origin.domain.length, cli->origin.domain.data, (int)host_port); + } else { + if (ipv6) curr.headers_common.fields.host = string_format("[%.*s]", (int)cli->origin.domain.length, cli->origin.domain.data); + else curr.headers_common.fields.host = string_from_literal_length(cli->origin.domain.data, cli->origin.domain.length); + } + } else if (cli->origin.ep.ver) { + char ip[48]; + bool ipv6 = false; + uint16_t ep_port = 0; + net_ep_split(&cli->origin.ep, ip, sizeof(ip), &ipv6, &ep_port); + uint16_t host_port = ep_port; + if (host_port && host_port != 80) { + if (ipv6) curr.headers_common.fields.host = string_format("[%s]:%i", ip, (int)host_port); + else curr.headers_common.fields.host = string_format("%s:%i", ip, (int)host_port); + } else { + if (ipv6) curr.headers_common.fields.host = string_format("[%s]", ip); + else curr.headers_common.fields.host = string_from_literal(ip); + } + } + } + + string out = http_request_builder(&curr); + if (!out.data || !out.length) { + if (out.mem_length) string_free(out); + resp = http_client_error_response(SOCK_ERR_SYS); + break; + } + uint32_t out_len = out.length; + + uint32_t send_start_ms = (uint32_t)get_time(); + HTTPSocketTimeoutState send_timeout = {send_start_ms, send_start_ms}; + int64_t sent = http_socket_send_all(cli->sock, out.data, out_len, 0, HTTP_SOCKET_WRITE_IDLE_TIMEOUT_MS, HTTP_SOCKET_WRITE_TOTAL_TIMEOUT_MS, &send_timeout); + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_CLIENT; + ev.action = NETLOG_ACT_HTTP_SEND_REQUEST; + ev.pid = get_current_proc_pid(); + ev.u0 = out_len; + ev.i0 = sent; + http_socket_fill_log_endpoints(cli->sock, &ev); + + char pathbuf[128]; + if (curr.path.length && curr.path.data) { + uint32_t n = curr.path.length; + if (n > sizeof(pathbuf) - 1) n = sizeof(pathbuf) - 1; + memcpy(pathbuf, curr.path.data, n); + pathbuf[n] = 0; + ev.s0 = pathbuf; + } + + netlog_socket_event(&cli->log_opts, &ev); + string_free(out); + + if (sent < 0) { + if (cli->sock) http_socket_close(&cli->sock, true); + resp = http_client_error_response(sent); + break; + } + + resp = http_client_receive_response(cli, curr.method); + uint32_t code = (uint32_t)resp.status_code; + bool redirect = code == HTTP_MOVED_PERMANENTLY || code == HTTP_FOUND || code == HTTP_SEE_OTHER || code == HTTP_TEMPORARY_REDIRECT || code == HTTP_PERMANENT_REDIRECT; + if (!redirect || i >= redirects || !resp.headers_common.fields.location.length) break; + + ParsedURL url = parse_url(resp.headers_common.fields.location.data, resp.headers_common.fields.location.length); + if (!url.ok) break; + + bool absolute = url.scheme.size || url.host.size; + if (url.scheme.size) { + if (url.scheme.size == 5 && strncmp_case((const char*)url.scheme.ptr, "https", true, 5) == 0) break; + if (!(url.scheme.size == 4 && strncmp_case((const char*)url.scheme.ptr, "http", true, 4) == 0)) break; + } + if (absolute && (!url.host.ptr || !url.host.size)) break; + + string next_path = url_request_path(&url, &curr.path); + if (!next_path.length) break; + + if (curr.path.mem_length) string_free(curr.path); + curr.path = next_path; + + if (absolute) { + uint16_t next_port = url.port ? url.port : 80; + string next_domain = string_from_literal_length((const char*)url.host.ptr, url.host.size); + if (!curr.host_override) { + if (curr.headers_common.fields.host.mem_length) string_free(curr.headers_common.fields.host); + curr.headers_common.fields.host = (string){0}; + } + + int32_t rr = cli->sock ? http_socket_close(&cli->sock, false) : SOCK_OK; + if (rr >= 0) rr = next_domain.data ? http_client_connect_domain_origin(cli, next_domain.data, next_port) : SOCK_ERR_SYS; + if (next_domain.mem_length) string_free(next_domain); + if (rr < 0) { + http_response_free(&resp); + resp = http_client_error_response(rr); + break; + } + } else { + net_l4_endpoint reconnect_ep = cli->origin.ep; + string reconnect_domain = {0}; + if (cli->origin.domain.data) reconnect_domain = string_from_literal(cli->origin.domain.data); + + int32_t rr = cli->sock ? http_socket_close(&cli->sock, false) : SOCK_OK; + if (rr >= 0 && cli->origin.valid) { + if (reconnect_domain.data) rr = http_client_connect_domain_origin(cli, reconnect_domain.data, reconnect_ep.port); + else rr = http_client_connect_endpoint_origin(cli, &reconnect_ep, NULL); + } else if (rr >= 0) rr = SOCK_ERR_STATE; + if (reconnect_domain.mem_length) string_free(reconnect_domain); + if (rr < 0) { + http_response_free(&resp); + resp = http_client_error_response(rr); + break; + } + } + + if (code == HTTP_SEE_OTHER && curr.method != HTTP_METHOD_HEAD) { + curr.method = HTTP_METHOD_GET; + curr.body = (string){0}; + curr.headers_common.framing.chunked = 0; + curr.headers_common.framing.has_content_length = 0; + curr.headers_common.fields.content_length = 0; + } + + http_response_free(&resp); + } + + if (curr.path.mem_length) string_free(curr.path); + if (curr.headers_common.fields.host.mem_length) string_free(curr.headers_common.fields.host); + return resp; +} + +int32_t http_client_close(http_client_handle_t h) { + if (!h) return (int32_t)SOCK_ERR_INVAL; + + HTTPClient* cli = (HTTPClient*)h; + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_CLIENT; + ev.action = NETLOG_ACT_CLOSE; + ev.pid = get_current_proc_pid(); + + if (cli->sock) http_socket_fill_log_endpoints(cli->sock, &ev); + + int32_t r = cli->sock ? http_socket_close(&cli->sock, false) : SOCK_ERR_STATE; + ev.i0 = r; + netlog_socket_event(&cli->log_opts, &ev); + + if (!cli->sock){ + if (cli->origin.domain.mem_length) string_free(cli->origin.domain); + cli->origin = (HTTPClientOrigin){0}; + } + return r; +} diff --git a/kernel/networking/application_layer/csocket_http_client.cpp b/kernel/networking/application_layer/csocket_http_client.cpp deleted file mode 100644 index 2fe47744..00000000 --- a/kernel/networking/application_layer/csocket_http_client.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "csocket_http_client.h" -#include "socket_http_client.hpp" -#include "networking/transport_layer/socket.hpp" -#include "networking/transport_layer/socket_tcp.hpp" - - -extern "C" { - -http_client_handle_t http_client_create(uint16_t pid, const SocketExtraOptions* extra) { - HTTPClient* cli = new HTTPClient(pid, extra); - if (!cli) return nullptr; - return reinterpret_cast(cli); -} - -void http_client_destroy(http_client_handle_t h) { - if (!h) return; - HTTPClient* cli = reinterpret_cast(h); - delete cli; -} - -int32_t http_client_connect_ex(http_client_handle_t h, uint8_t dst_kind, const void* dst, uint16_t port) { - if (!h || !dst) return (int32_t)SOCK_ERR_INVAL; - HTTPClient *cli = reinterpret_cast(h); - return cli->connect(static_cast(dst_kind), dst, port); -} - -HTTPResponseMsg http_client_send_request(http_client_handle_t h, const HTTPRequestMsg *req) { - HTTPResponseMsg empty{}; - if (!h || !req) { - empty.status_code = (HttpError)SOCK_ERR_INVAL; - return empty; - } - HTTPClient *cli = reinterpret_cast(h); - return cli->send_request(*req); -} - -int32_t http_client_close(http_client_handle_t h) { - if (!h) return (int32_t)SOCK_ERR_INVAL; - HTTPClient *cli = reinterpret_cast(h); - return cli->close(); -} -} diff --git a/kernel/networking/application_layer/csocket_http_client.h b/kernel/networking/application_layer/csocket_http_client.h index 30bd1088..5c541ab9 100644 --- a/kernel/networking/application_layer/csocket_http_client.h +++ b/kernel/networking/application_layer/csocket_http_client.h @@ -8,10 +8,12 @@ extern "C" { typedef void* http_client_handle_t; -http_client_handle_t http_client_create(uint16_t pid, const SocketExtraOptions* extra); +http_client_handle_t http_client_create(const SocketOptions* extra, const HTTPClientPolicyOptions *options); +int32_t http_client_set_options(http_client_handle_t h, const HTTPClientPolicyOptions *options); void http_client_destroy(http_client_handle_t h); -int32_t http_client_connect_ex(http_client_handle_t h, uint8_t dst_kind, const void *dst, uint16_t port); +int32_t http_client_connect_endpoint(http_client_handle_t h, const net_l4_endpoint *dst); +int32_t http_client_connect_domain(http_client_handle_t h, const char *host, uint16_t port); HTTPResponseMsg http_client_send_request(http_client_handle_t h, const HTTPRequestMsg *req); diff --git a/kernel/networking/application_layer/csocket_http_internal.c b/kernel/networking/application_layer/csocket_http_internal.c new file mode 100644 index 00000000..eafd6b8d --- /dev/null +++ b/kernel/networking/application_layer/csocket_http_internal.c @@ -0,0 +1,136 @@ +#include "csocket_http_internal.h" +#include "http.h" +#include "net/socket_types.h" +#include "syscalls/syscalls.h" + +int32_t http_socket_close(socket_handle_t *socket, bool abort) { + if (!socket || !*socket) return SOCK_ERR_STATE; + + socket_handle_t handle = *socket; + if (abort) { + SocketLinger linger = {.enabled = 1, .timeout_ms = 0}; + set_socket_option(handle, SOCK_OPT_LINGER, &linger, sizeof(linger)); + } else { + uint32_t nonblock = 0; + set_socket_option(handle, SOCK_OPT_NONBLOCK, &nonblock, sizeof(nonblock)); + } + + int32_t close_result = close_socket(handle); + if (close_result != SOCK_ERR_WOULDBLOCK) *socket = 0; + return close_result; +} + +int64_t http_socket_recv_wait(socket_handle_t socket, + void *buffer, + uint32_t length, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + HTTPSocketTimeoutState *timeout) { + while (1) { + uint32_t now = (uint32_t)get_time(); + if ((idle_timeout_ms && now - timeout->last_progress_ms >= idle_timeout_ms)|| (total_timeout_ms && now - timeout->start_ms >= total_timeout_ms)) return SOCK_ERR_WOULDBLOCK; + + int64_t result = receive_from_socket(socket, buffer, length, NULL); + now = (uint32_t)get_time(); + if (result == SOCK_ERR_WOULDBLOCK) { + if ((idle_timeout_ms && now - timeout->last_progress_ms >= idle_timeout_ms) || (total_timeout_ms && now - timeout->start_ms >= total_timeout_ms)) return SOCK_ERR_WOULDBLOCK; + msleep(2); + continue; + } + if (result > 0) timeout->last_progress_ms = now; + return result; + } +} + +int32_t http_socket_recv_head(socket_handle_t socket, + string *buffer, + uint32_t max_header_bytes, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + int32_t *header_end) { + *header_end = find_crlfcrlf(buffer->data, buffer->length); + if (*header_end >= 0) return (uint32_t)*header_end + 4 <= max_header_bytes ? SOCK_OK : SOCK_ERR_PROTO; + if (buffer->length >= max_header_bytes) return SOCK_ERR_PROTO; + + uint32_t now = (uint32_t)get_time(); + HTTPSocketTimeoutState timeout = {now, now}; + char temporary[2048]; + + while (*header_end < 0) { + uint32_t available = max_header_bytes - buffer->length; + uint32_t request = available < sizeof(temporary) ? available : sizeof(temporary); + int64_t result = http_socket_recv_wait(socket, temporary, request, idle_timeout_ms, total_timeout_ms, &timeout); + if (result < 0) return (int32_t)result; + if (result == 0) return SOCK_ERR_PROTO; + + uint32_t scan = buffer->length > 3 ? buffer->length - 3 : 0; + uint32_t expected = buffer->length + (uint32_t)result; + string_append_bytes(buffer, temporary, (uint32_t)result); + if (buffer->length != expected) return SOCK_ERR_SYS; + + *header_end = find_crlfcrlf(buffer->data + scan, buffer->length - scan); + if (*header_end >= 0) { + *header_end += scan; + return (uint32_t)*header_end + 4 <= max_header_bytes ? SOCK_OK : SOCK_ERR_PROTO; + } + if (buffer->length >= max_header_bytes) return SOCK_ERR_PROTO; + } + + return SOCK_OK; +} + +int64_t http_socket_recv_exact(socket_handle_t socket, void *buffer, uint32_t length, uint32_t idle_timeout_ms, uint32_t total_timeout_ms) { + uint32_t now = (uint32_t)get_time(); + HTTPSocketTimeoutState timeout = {now, now}; + uint32_t received = 0; + + while (received < length) { + int64_t result = http_socket_recv_wait(socket, (uint8_t*)buffer + received, length - received, idle_timeout_ms, total_timeout_ms, &timeout); + if (result < 0) return result; + if (result == 0) return SOCK_ERR_PROTO; + if ((uint64_t)result > length - received) return SOCK_ERR_STATE; + received += (uint32_t)result; + } + + return received; +} + +int64_t http_socket_send_all(socket_handle_t socket, + const void *buffer, + uint32_t length, + uint32_t max_chunk, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + HTTPSocketTimeoutState *timeout) { + uint32_t sent = 0; + while (sent < length) { + uint32_t now = (uint32_t)get_time(); + if ((idle_timeout_ms && now - timeout->last_progress_ms >= idle_timeout_ms) || (total_timeout_ms && now - timeout->start_ms >= total_timeout_ms)) return SOCK_ERR_WOULDBLOCK; + + uint32_t request = length - sent; + if (max_chunk && request > max_chunk) request = max_chunk; + + int64_t result = send_on_socket(socket, (const uint8_t*)buffer + sent, request); + now = (uint32_t)get_time(); + if (result == SOCK_ERR_WOULDBLOCK || result == 0) { + if ((idle_timeout_ms && now - timeout->last_progress_ms >= idle_timeout_ms) || (total_timeout_ms && now - timeout->start_ms >= total_timeout_ms)) return SOCK_ERR_WOULDBLOCK; + msleep(2); + continue; + } + if (result < 0) return result; + if ((uint64_t)result > request) return SOCK_ERR_STATE; + + sent += (uint32_t)result; + timeout->last_progress_ms = now; + } + + return sent; +} + +void http_socket_fill_log_endpoints(socket_handle_t socket, netlog_socket_event_t *event) { + uint32_t local_port = 0; + uint32_t length = sizeof(local_port); + if (get_socket_option(socket, SOCK_GET_LOCAL_PORT, &local_port, &length) == SOCK_OK) event->local_port = local_port; + length = sizeof(event->remote_ep); + get_socket_option(socket, SOCK_GET_REMOTE_ENDPOINT, &event->remote_ep, &length); +} diff --git a/kernel/networking/application_layer/csocket_http_internal.h b/kernel/networking/application_layer/csocket_http_internal.h new file mode 100644 index 00000000..ef1c39f6 --- /dev/null +++ b/kernel/networking/application_layer/csocket_http_internal.h @@ -0,0 +1,44 @@ +#pragma once + +#include "networking/transport_layer/csocket.h" +#include "networking/net_logger/net_logger.h" +#include "std/string.h" + +#define HTTP_SOCKET_WRITE_IDLE_TIMEOUT_MS 3000 +#define HTTP_SOCKET_WRITE_TOTAL_TIMEOUT_MS 30000 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct HTTPSocketTimeoutState { + uint32_t start_ms; + uint32_t last_progress_ms; +} HTTPSocketTimeoutState; + +int32_t http_socket_close(socket_handle_t *socket, bool abort); +int64_t http_socket_recv_wait(socket_handle_t socket, + void *buffer, + uint32_t length, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + HTTPSocketTimeoutState *timeout); +int32_t http_socket_recv_head(socket_handle_t socket, + string *buffer, + uint32_t max_header_bytes, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + int32_t *header_end); +int64_t http_socket_recv_exact(socket_handle_t socket, void *buffer, uint32_t length, uint32_t idle_timeout_ms, uint32_t total_timeout_ms); +int64_t http_socket_send_all(socket_handle_t socket, + const void *buffer, + uint32_t length, + uint32_t max_chunk, + uint32_t idle_timeout_ms, + uint32_t total_timeout_ms, + HTTPSocketTimeoutState *timeout); +void http_socket_fill_log_endpoints(socket_handle_t socket, netlog_socket_event_t *event); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/application_layer/csocket_http_server.c b/kernel/networking/application_layer/csocket_http_server.c new file mode 100644 index 00000000..80225917 --- /dev/null +++ b/kernel/networking/application_layer/csocket_http_server.c @@ -0,0 +1,544 @@ +#include "csocket_http_server.h" +#include "csocket_http_internal.h" +#include "networking/transport_layer/csocket.h" +#include "networking/net_logger/net_logger.h" +#include "http.h" +#include "std/string.h" +#include "std/memory.h" +#include "net/socket_types.h" +#include "syscalls/syscalls.h" +#include "process/scheduler.h" +#include "alloc/allocate.h" + +typedef struct HTTPConnection { + socket_handle_t client; + string carry_buf; + uint32_t request_count; + HTTPMethod current_method; + bool close_after_response; + bool send_keep_alive_header; +} HTTPConnection; + +typedef struct HTTPServer { + socket_handle_t sock; + SocketOptions log_opts; + SocketOptions tcp_opts; + HTTPServerPolicy policy; +} HTTPServer; + +http_server_handle_t http_server_create(const SocketOptions* extra, const HTTPServerPolicyOptions* http_options) { + HTTPServer* srv = (HTTPServer*)zalloc(sizeof(*srv)); + if (!srv) return NULL; + + srv->policy = http_server_policy_from_options(http_options); + if (extra) { + srv->log_opts = *extra; + srv->tcp_opts = *extra; + srv->tcp_opts.flags &= ~SOCK_OPT_DEBUG; + } + + srv->tcp_opts.flags |= SOCK_OPT_NONBLOCK | SOCK_OPT_REUSEADDR; + + return srv; +} + +void http_server_destroy(http_server_handle_t h) { + if (!h) return; + HTTPServer* srv = (HTTPServer*)h; + if (srv->sock) (void)http_socket_close(&srv->sock, true); + release(srv); +} + +int32_t http_server_set_options(http_server_handle_t h, const HTTPServerPolicyOptions* http_options) { + if (!h) return (int32_t)SOCK_ERR_INVAL; + HTTPServer* srv = (HTTPServer*)h; + srv->policy = http_server_policy_from_options(http_options); + return SOCK_OK; +} + +int32_t http_server_bind(http_server_handle_t h, const SockBindSpec* spec, uint16_t port) { + if (!h || !spec) return (int32_t)SOCK_ERR_INVAL; + + HTTPServer* srv = (HTTPServer*)h; + uint16_t p = port; + if (!srv->sock) srv->sock = create_socket(PROTO_TCP, &srv->tcp_opts); + if (!srv->sock) return SOCK_ERR_SYS; + int32_t r = bind_socket(srv->sock, spec, p); + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_BIND; + ev.pid = get_current_proc_pid(); + ev.u0 = p; + ev.i0 = r; + netlog_socket_event(&srv->log_opts, &ev); + return r; +} + +int32_t http_server_listen(http_server_handle_t h, int backlog) { + if (!h) return (int32_t)SOCK_ERR_INVAL; + + HTTPServer* srv = (HTTPServer*)h; + int32_t b = backlog; + int32_t r = srv->sock ? listen_on(srv->sock, b) : SOCK_ERR_STATE; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_LISTEN; + ev.pid = get_current_proc_pid(); + ev.u0 = (uint32_t)b; + ev.i0 = r; + netlog_socket_event(&srv->log_opts, &ev); + return r; +} + +http_connection_handle_t http_server_accept(http_server_handle_t h) { + if (!h) return NULL; + + HTTPServer* srv = (HTTPServer*)h; + if (!srv->sock) return NULL; + socket_handle_t child = accept_on_socket(srv->sock); + if (!child) return NULL; + //TODO for now almost everything is using SOCK_OPT_NONBLOCK for compatibilit + //in many app protocols it should be blocking, while for others such as HTTP, it should depend on the given options (blocking by default) + if (set_socket_option(child, SOCK_OPT_NONBLOCK,&(uint32_t){1}, sizeof(uint32_t)) != SOCK_OK) { + http_socket_close(&child, true); + return NULL; + } + + HTTPConnection* conn = (HTTPConnection*)zalloc(sizeof(HTTPConnection)); + if (!conn) { + http_socket_close(&child, true); + return NULL; + } + + conn->client = child; + conn->carry_buf = (string){0}; + conn->request_count = 0; + conn->current_method = HTTP_METHOD_GET; + conn->close_after_response = true; + conn->send_keep_alive_header = false; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_ACCEPT; + ev.pid = get_current_proc_pid(); + ev.i0 = (int64_t)child; + http_socket_fill_log_endpoints(child, &ev); + netlog_socket_event(&srv->log_opts, &ev); + return conn; +} + +HTTPRequestMsg http_server_recv_request(http_server_handle_t h, http_connection_handle_t c) { + HTTPRequestMsg req = {0}; + if (!h || !c) return req; + + HTTPServer* srv = (HTTPServer*)h; + HTTPConnection* conn = (HTTPConnection*)c; + if (!conn->client) return req; + + string buf = conn->carry_buf; + conn->carry_buf = (string){0}; + + char tmp[2048]; + int32_t hdr_end = -1; + uint32_t consumed = 0; + bool bad_request = false; + HttpError reject_status = HTTP_BAD_REQUEST; + char* body_copy = NULL; + + int32_t head_result = http_socket_recv_head(conn->client, &buf, srv->policy.common.max_header_bytes, srv->policy.common.header_idle_timeout_ms, srv->policy.common.header_total_timeout_ms, &hdr_end); + if (head_result < 0) { + bool header_too_large = (hdr_end >= 0 && (uint32_t)hdr_end + 4 > srv->policy.common.max_header_bytes) || (hdr_end < 0 && buf.length >= srv->policy.common.max_header_bytes); + if (header_too_large) { + bad_request = true; + reject_status = HTTP_HEADER_FIELDS_TOO_LARGE; + } else { + string_free(buf); + if (conn->client) http_socket_close(&conn->client, true); + return req; + } + } + + if (!bad_request && (!buf.data || hdr_end < 2)) { + bad_request = true; + reject_status = HTTP_BAD_REQUEST; + } + + if (!bad_request) { + uint32_t line_end = 0; + while (line_end + 1u < (uint32_t)hdr_end) { + if (buf.data[line_end] == '\r' && buf.data[line_end + 1] == '\n') break; + line_end++; + } + + if (line_end > srv->policy.common.max_start_line) { + bad_request = true; + reject_status = HTTP_URI_TOO_LONG; + } + + HTTPRequestLine line = {0}; + HTTPParseResult line_result = HTTP_PARSE_OK; + if (!bad_request) { + line_result = http_parse_request_line(buf.data, line_end, &line); + if (line_result != HTTP_PARSE_OK) { + bad_request = true; + reject_status = http_parse_result_status(line_result); + } else if (line.target_off > line_end || line.target_len > line_end - line.target_off) { + bad_request = true; + reject_status = HTTP_BAD_REQUEST; + } else if (!http_method_allowed(srv->policy.allowed_methods, line.method)) { + bad_request = true; + reject_status = line.method == HTTP_METHOD_UNKNOWN ? HTTP_NOT_IMPLEMENTED : HTTP_METHOD_NOT_ALLOWED; + } + } + + if (!bad_request) { + const char* target = buf.data + line.target_off; + uint32_t target_len = line.target_len; + req.method = line.method; + req.version = line.version; + req.path = (string){0}; + uint32_t expepted_len = target_len; + + uint32_t scheme_len = 0; + if (target_len >= 7 && memcmp(target, "http://", 7) == 0) scheme_len = 7; + else if (target_len >= 8 && memcmp(target, "https://", 8) == 0) scheme_len = 8; + + if (scheme_len) { + if (!srv->policy.allow_absolute_uri) { + bad_request = true; + reject_status = HTTP_BAD_REQUEST; + } else { + const char* authority = target + scheme_len; + uint32_t remaining = target_len - scheme_len; + const char* slash = memchr(authority, '/', remaining); + const char* query = memchr(authority, '?', remaining); + if (query && (!slash || query < slash)) { + uint32_t query_len = target_len - (uint32_t)(query - target); + expepted_len = query_len + 1; + string_append_bytes(&req.path, "/", 1); + if (req.path.length == 1) string_append_bytes(&req.path, query, query_len); + } else if (slash) { + expepted_len = target_len - (uint32_t)(slash - target); + string_append_bytes(&req.path, slash, expepted_len); + } else { + expepted_len = 1; + string_append_bytes(&req.path, "/", 1); + } + } + } else string_append_bytes(&req.path, target, target_len); + + if (!bad_request && req.path.length != expepted_len) { + bad_request = true; + reject_status = HTTP_INTERNAL_SERVER_ERROR; + } + if (!bad_request && (!req.path.length || req.path.length > srv->policy.common.max_path_len)) { + bad_request = true; + reject_status = req.path.length > srv->policy.common.max_path_len ? HTTP_URI_TOO_LONG : HTTP_BAD_REQUEST; + } + } + + if (!bad_request) { + HTTPParseResult header_result = http_header_parse( + (char*)buf.data + line_end + 2, + (uint32_t)hdr_end - (line_end + 2), + &srv->policy.common, + &req.headers_common, + &req.extra_headers, + &req.extra_header_count); + + if (header_result != HTTP_PARSE_OK) { + bad_request = true; + reject_status = http_parse_result_status(header_result); + } else if (srv->policy.require_host_http11 && line.version == HTTP_VERSION_11 && !req.headers_common.fields.host.length) { + bad_request = true; + reject_status = HTTP_BAD_REQUEST; + } else { + conn->request_count++; + conn->current_method = req.method; + conn->close_after_response = true; + conn->send_keep_alive_header = false; + if (srv->policy.allow_keep_alive) { + if (line.version == HTTP_VERSION_11) conn->close_after_response = req.headers_common.framing.connection_close != 0; + else if (line.version == HTTP_VERSION_10 && req.headers_common.framing.connection_keep_alive) { + conn->close_after_response = false; + conn->send_keep_alive_header = true; + } + } + if (srv->policy.max_keepalive_requests && conn->request_count >= srv->policy.max_keepalive_requests) conn->close_after_response = true; + } + } + + uint32_t body_start = hdr_end + 4; + uint32_t have = buf.length > body_start ? buf.length - body_start : 0; + + if (!bad_request && req.headers_common.framing.expect_continue) { + if (req.headers_common.framing.has_content_length && req.headers_common.fields.content_length > srv->policy.common.max_body_bytes) { + bad_request = true; + reject_status = HTTP_PAYLOAD_TOO_LARGE; + } else { + HTTPResponseMsg cont = {0}; + cont.status_code = HTTP_CONTINUE; + http_server_send_response(srv, conn, &cont); + } + } + uint32_t need = req.headers_common.framing.has_content_length ? req.headers_common.fields.content_length : 0; + consumed = body_start; + + if (!bad_request && req.headers_common.framing.chunked) { + HTTPChunkedDecoder dec; + http_chunked_decoder_init(&dec, &srv->policy.common); + + uint32_t used = 0; + HTTPParseResult chunk_result = have ? http_chunked_decoder_feed(&dec, buf.data + body_start, have, &used) : HTTP_PARSE_INCOMPLETE; + uint32_t body_start_ms = (uint32_t)get_time(); + HTTPSocketTimeoutState body_timeout = {body_start_ms, body_start_ms}; + consumed = body_start + used; + + while (chunk_result == HTTP_PARSE_INCOMPLETE) { + int64_t r = http_socket_recv_wait(conn->client, tmp, sizeof(tmp), srv->policy.common.body_idle_timeout_ms, srv->policy.common.body_total_timeout_ms, &body_timeout); + if (r <= 0) break; + used = 0; + chunk_result = http_chunked_decoder_feed(&dec, tmp, (uint32_t)r, &used); + consumed = buf.length; + if (chunk_result == HTTP_PARSE_OK && used < (uint32_t)r && !conn->carry_buf.length) { + conn->carry_buf = (string){0}; + uint32_t extra = (uint32_t)r - used; + string_append_bytes(&conn->carry_buf, tmp + used, extra); + if (conn->carry_buf.length != extra) conn->close_after_response = true; + } + } + + if (chunk_result != HTTP_PARSE_OK) { + bad_request = true; + reject_status = http_parse_result_status(chunk_result); + } else { + string decoded = dec.body; + dec.body = (string){0}; + if (decoded.length) { + req.body = decoded; + } else if (decoded.mem_length) string_free(decoded); + } + http_chunked_decoder_free(&dec); + } else { + if (!bad_request && need > srv->policy.common.max_body_bytes) { + bad_request = true; + reject_status = HTTP_PAYLOAD_TOO_LARGE; + } + + if (!bad_request && need > 0) { + body_copy = (char*)zalloc(need); + if (!body_copy) { + bad_request = true; + reject_status = HTTP_INTERNAL_SERVER_ERROR; + } else { + uint32_t copied = have < need ? have : need; + if (copied) memcpy(body_copy, buf.data + body_start, copied); + + if (copied < need) { + int64_t receive_result = http_socket_recv_exact(conn->client, body_copy + copied, need - copied, srv->policy.common.body_idle_timeout_ms, srv->policy.common.body_total_timeout_ms); + if (receive_result < 0) bad_request = true; + } + } + } + + if (!bad_request) consumed = body_start + need; + if (body_copy && !bad_request) { + req.body = (string){body_copy, need, need}; + } + } + } + + if (bad_request) { + if (body_copy) release(body_copy); + const char* reason = http_status_reason(reject_status); + string body = srv->policy.send_error_body ? string_format("%s\n", reason) : (string){0}; + HTTPResponseMsg res = {0}; + HTTPHeader allow_header = {0}; + string allow_value = {0}; + static char allow_key[] = "Allow"; + + res.status_code = reject_status; + res.headers_common.fields.content_length = body.length; + res.headers_common.framing.has_content_length = 1; + if (srv->policy.error_content_type) res.headers_common.fields.content_type = string_from_literal(srv->policy.error_content_type); + res.body = body; + + if (reject_status == HTTP_METHOD_NOT_ALLOWED) { + allow_value = http_methods_allow_header(srv->policy.allowed_methods); + allow_header.key = (string){allow_key, sizeof(allow_key) - 1, 0}; + allow_header.value = allow_value; + res.extra_headers = &allow_header; + res.extra_header_count = 1; + } + + conn->close_after_response = true; + http_server_send_response(srv, conn, &res); + if (conn->client) (void)http_socket_close(&conn->client, true); + + http_request_free(&req); + http_headers_common_free(&res.headers_common); + if (allow_value.mem_length) string_free(allow_value); + if (body.mem_length) string_free(body); + string_free(buf); + return (HTTPRequestMsg){0}; + } + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_HTTP_RECV_REQUEST; + ev.pid = get_current_proc_pid(); + ev.u0 = (uint32_t)req.method; + ev.u1 = (uint32_t)req.path.length; + ev.i0 = (int64_t)req.body.length; + http_socket_fill_log_endpoints(conn->client, &ev); + + char pathbuf[128]; + if (req.path.length && req.path.data) { + uint32_t n = req.path.length; + if (n > sizeof(pathbuf) - 1) n = sizeof(pathbuf) - 1; + memcpy(pathbuf, req.path.data, n); + pathbuf[n] = 0; + ev.s0 = pathbuf; + } + + netlog_socket_event(&srv->log_opts, &ev); + + if (consumed < buf.length && !conn->carry_buf.length) { + conn->carry_buf = (string){0}; + uint32_t extra = buf.length - consumed; + string_append_bytes(&conn->carry_buf, buf.data + consumed, extra); + if (conn->carry_buf.length != extra) conn->close_after_response = true; + } + + string_free(buf); + return req; +} + +int32_t http_server_send_response(http_server_handle_t h, http_connection_handle_t c, const HTTPResponseMsg* res) { + if (!h || !c || !res) return (int32_t)SOCK_ERR_INVAL; + + HTTPServer* srv = (HTTPServer*)h; + HTTPConnection* conn = (HTTPConnection*)c; + if (!conn->client) return SOCK_ERR_STATE; + + uint32_t code = (uint32_t)res->status_code; + bool informational = code >= 100 && code < 200; + bool suppress_body = informational || conn->current_method == HTTP_METHOD_HEAD || code == 204 || code == 304; + bool send_chunked = suppress_body ? false : res->headers_common.framing.chunked; + HTTPResponseMsg head = *res; + bool explicit_close = http_header_value_has_token(res->headers_common.fields.connection.data, res->headers_common.fields.connection.length, "close", 5); + bool explicit_keep_alive = http_header_value_has_token(res->headers_common.fields.connection.data, res->headers_common.fields.connection.length, "keep-alive", 10); + bool close_after_send = !informational && (conn->close_after_response || explicit_close); + static char conn_close_data[] = "close"; + static char conn_keep_alive_data[] = "keep-alive"; + const string conn_close = {conn_close_data, sizeof(conn_close_data) - 1, 0}; + const string conn_keep_alive = {conn_keep_alive_data, sizeof(conn_keep_alive_data) - 1, 0}; + + if (!informational) { + if (close_after_send) head.headers_common.fields.connection = conn_close; + else if (!res->headers_common.fields.connection.length && conn->send_keep_alive_header) head.headers_common.fields.connection = conn_keep_alive; + else if (explicit_keep_alive) head.headers_common.fields.connection = res->headers_common.fields.connection; + } + + if (suppress_body) { + head.body = (string){0}; + head.headers_common.framing.chunked = 0; + } + + uint32_t body_len = (!send_chunked && !suppress_body && res->body.data && res->body.length) ? (uint32_t)res->body.length : 0; + if (!send_chunked) { + head.body = (string){0}; + if (body_len && !head.headers_common.framing.has_content_length) { + head.headers_common.fields.content_length = body_len; + head.headers_common.framing.has_content_length = 1; + } + } + string out = http_response_builder(&head); + if (!out.data || !out.length) return SOCK_ERR_SYS; + uint32_t out_len = out.length + body_len; + int64_t sent = 0; + uint32_t start_ms = (uint32_t)get_time(); + HTTPSocketTimeoutState send_timeout = {start_ms, start_ms}; + const uint8_t* first_ptr = (const uint8_t*)out.data; + uint32_t first_len = out.length; + uint32_t first_body_len = 0; + uint32_t body_off = 0; + uint8_t* combo = NULL; + + if (body_len && out.length < 1460) { + first_body_len = body_len; + uint32_t room = 1460 - out.length; + if (first_body_len > room) first_body_len = room; + + if (first_body_len) { + combo = (uint8_t*)zalloc(out.length + first_body_len); + if (combo) { + memcpy(combo, out.data, out.length); + memcpy(combo + out.length, (const void*)res->body.data, first_body_len); + + first_ptr = combo; + first_len = out.length + first_body_len; + } else first_body_len = 0; + } + } + + sent = http_socket_send_all(conn->client, first_ptr, first_len, 0, HTTP_SOCKET_WRITE_IDLE_TIMEOUT_MS, HTTP_SOCKET_WRITE_TOTAL_TIMEOUT_MS, &send_timeout); + + if (sent >= 0) body_off = first_body_len; + if (sent >= 0 && body_len > body_off) { + const uint8_t* body = (const uint8_t*)res->body.data; + int64_t body_sent = http_socket_send_all(conn->client, body + body_off, body_len - body_off, 16384, HTTP_SOCKET_WRITE_IDLE_TIMEOUT_MS, HTTP_SOCKET_WRITE_TOTAL_TIMEOUT_MS, &send_timeout); + if (body_sent < 0) sent = body_sent; + else body_off += (uint32_t)body_sent; + } + + if (sent >= 0) sent = (int64_t)out.length + body_off; + if (combo) release(combo); + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_HTTP_SEND_RESPONSE; + ev.pid = get_current_proc_pid(); + ev.u0 = code; + ev.u1 = out_len; + ev.i0 = sent; + http_socket_fill_log_endpoints(conn->client, &ev); + netlog_socket_event(&srv->log_opts, &ev); + + string_free(out); + if (sent < 0 && conn->client) http_socket_close(&conn->client, true); + if (sent >= 0 && close_after_send && conn->client) { + int32_t close_result = http_socket_close(&conn->client, false); + if (close_result < 0) return close_result; + } + return sent < 0 ? (int32_t)sent : SOCK_OK; +} + +int32_t http_connection_close(http_connection_handle_t c) { + if (!c) return (int32_t)SOCK_ERR_INVAL; + HTTPConnection* conn = (HTTPConnection*)c; + int32_t result = conn->client ? http_socket_close(&conn->client, false) : SOCK_OK; + if (result < 0) return result; + if (conn->carry_buf.mem_length) string_free(conn->carry_buf); + release(conn); + return SOCK_OK; +} + +int32_t http_server_close(http_server_handle_t h) { + if (!h) return (int32_t)SOCK_ERR_INVAL; + + HTTPServer* srv = (HTTPServer*)h; + int32_t r = srv->sock ? SOCK_OK : SOCK_ERR_STATE; + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_HTTP_SERVER; + ev.action = NETLOG_ACT_CLOSE; + ev.pid = get_current_proc_pid(); + ev.i0 = r; + if (srv->sock) http_socket_fill_log_endpoints(srv->sock, &ev); + if (srv->sock) r = http_socket_close(&srv->sock, false); + ev.i0 = r; + netlog_socket_event(&srv->log_opts, &ev); + return r; +} diff --git a/kernel/networking/application_layer/csocket_http_server.cpp b/kernel/networking/application_layer/csocket_http_server.cpp deleted file mode 100644 index afe75eba..00000000 --- a/kernel/networking/application_layer/csocket_http_server.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "csocket_http_server.h" -#include "socket_http_server.hpp" -#include "networking/transport_layer/socket_tcp.hpp" -#include "networking/transport_layer/socket.hpp" - - -extern "C" { - -http_server_handle_t http_server_create(uint16_t pid, const SocketExtraOptions* extra) { - HTTPServer* srv = new HTTPServer(pid, extra); - if (!srv) return nullptr; - return reinterpret_cast(srv); -} - -void http_server_destroy(http_server_handle_t h) { - if (!h) return; - HTTPServer* srv = reinterpret_cast(h); - delete srv; -} - -int32_t http_server_bind(http_server_handle_t h, const SockBindSpec *spec, uint16_t port) { - if (!h || !spec) return (int32_t)SOCK_ERR_INVAL; - HTTPServer* srv = reinterpret_cast(h); - return srv->bind(*spec, port); -} - -int32_t http_server_listen(http_server_handle_t h, int backlog) { - if (!h) return (int32_t)SOCK_ERR_INVAL; - HTTPServer* srv = reinterpret_cast(h); - return srv->listen(backlog); -} - -http_connection_handle_t http_server_accept(http_server_handle_t h) { - if (!h) return nullptr; - HTTPServer* srv = reinterpret_cast(h); - TCPSocket* cli = srv->accept(); - return reinterpret_cast(cli); -} - -HTTPRequestMsg http_server_recv_request(http_server_handle_t h, http_connection_handle_t c) { - HTTPRequestMsg empty{}; - if (!h || !c) return empty; - HTTPServer* srv = reinterpret_cast(h); - TCPSocket* conn = reinterpret_cast(c); - return srv->recv_request(conn); -} - -int32_t http_server_send_response(http_server_handle_t h, http_connection_handle_t c, const HTTPResponseMsg *res) { - if (!h || !c || !res) return (int32_t)SOCK_ERR_INVAL; - HTTPServer* srv = reinterpret_cast(h); - TCPSocket* conn = reinterpret_cast(c); - return srv->send_response(conn, *res); -} - -int32_t http_connection_close(http_connection_handle_t c) { - if (!c) return (int32_t)SOCK_ERR_INVAL; - TCPSocket* conn = reinterpret_cast(c); - delete conn; - return (int32_t)SOCK_OK; -} - -int32_t http_server_close(http_server_handle_t h) { - if (!h) return (int32_t)SOCK_ERR_INVAL; - HTTPServer* srv = reinterpret_cast(h); - return srv->close(); -} - -} diff --git a/kernel/networking/application_layer/csocket_http_server.h b/kernel/networking/application_layer/csocket_http_server.h index 0b673473..59a7d81f 100644 --- a/kernel/networking/application_layer/csocket_http_server.h +++ b/kernel/networking/application_layer/csocket_http_server.h @@ -10,7 +10,8 @@ extern "C" { typedef void* http_server_handle_t; typedef void* http_connection_handle_t; -http_server_handle_t http_server_create(uint16_t pid, const SocketExtraOptions* extra); +http_server_handle_t http_server_create(const SocketOptions* extra, const HTTPServerPolicyOptions *options); +int32_t http_server_set_options(http_server_handle_t srv, const HTTPServerPolicyOptions *options); void http_server_destroy(http_server_handle_t srv); int32_t http_server_bind(http_server_handle_t srv, const struct SockBindSpec *spec, uint16_t port); diff --git a/kernel/networking/application_layer/dhcp.c b/kernel/networking/application_layer/dhcp.c index c161823a..bdc7cc76 100644 --- a/kernel/networking/application_layer/dhcp.c +++ b/kernel/networking/application_layer/dhcp.c @@ -1,9 +1,7 @@ #include "dhcp.h" #include "std/memory.h" -#include "networking/transport_layer/udp.h" -#include "networking/internet_layer/ipv4.h" +#include "networking/link_layer/link_utils.h" #include "types.h" -#include "syscalls/syscalls.h" static size_t dhcp_options_write_param_req_list(uint8_t *opt, size_t idx) { static const uint8_t prl[] = { 1, 3, 6, 15, 42, 26, 51, 58, 59, 119 }; @@ -15,59 +13,58 @@ static size_t dhcp_options_write_param_req_list(uint8_t *opt, size_t idx) { return idx; } -sizedptr dhcp_build_packet(const dhcp_request *req, uint8_t msg_type, uint32_t xid, dhcp_req_kind kind, bool broadcast) { +uint32_t dhcp_build_packet(const dhcp_request *req, uint8_t msg_type, uint32_t xid, dhcp_req_kind kind, bool broadcast, dhcp_packet *out) { + if (!req || !out) return 0; - dhcp_packet p; - memset(&p, 0, sizeof(p)); + memset(out, 0, sizeof(*out)); size_t idx = 0; - p.op = 1; - p.htype = 1; - p.hlen = 6; - p.hops = 0; - p.xid = xid; - p.secs = 0; - p.flags = broadcast ? bswap16(0x8000) : 0; - p.ciaddr = 0; - p.yiaddr = 0; - p.siaddr = 0; - p.giaddr = 0; - memcpy(p.chaddr, req->mac, 6); + out->op = 1; + out->htype = 1; + out->hlen = MAC_ADDR_LEN; + out->xid = xid; + out->flags = broadcast ? bswap16(0x8000) : 0; + mac_copy(out->chaddr, req->mac); - if (msg_type == DHCPINFORM) p.ciaddr = req->offered_ip; - if (msg_type == DHCPREQUEST && (kind == DHCPK_RENEW || kind == DHCPK_REBIND)) p.ciaddr = req->offered_ip; + if (msg_type == DHCPINFORM) out->ciaddr = req->offered_ip; + if (msg_type == DHCPREQUEST && (kind == DHCPK_RENEW || kind == DHCPK_REBIND)) out->ciaddr = req->offered_ip; - p.options[idx++] = DHCP_MAGIC_COOKIE_0; - p.options[idx++] = DHCP_MAGIC_COOKIE_1; - p.options[idx++] = DHCP_MAGIC_COOKIE_2; - p.options[idx++] = DHCP_MAGIC_COOKIE_3; + out->options[idx++] = DHCP_MAGIC_COOKIE_0; + out->options[idx++] = DHCP_MAGIC_COOKIE_1; + out->options[idx++] = DHCP_MAGIC_COOKIE_2; + out->options[idx++] = DHCP_MAGIC_COOKIE_3; - p.options[idx++] = 53; - p.options[idx++] = 1; - p.options[idx++] = msg_type; + out->options[idx++] = 53; + out->options[idx++] = 1; + out->options[idx++] = msg_type; if (msg_type == DHCPREQUEST && kind == DHCPK_SELECT) { - p.options[idx++] = 50; - p.options[idx++] = 4; - memcpy(&p.options[idx], &req->offered_ip, 4); + out->options[idx++] = 50; + out->options[idx++] = 4; + memcpy(&out->options[idx], &req->offered_ip, 4); idx += 4; if (req->server_ip) { - p.options[idx++] = 54; - p.options[idx++] = 4; - memcpy(&p.options[idx], &req->server_ip, 4); + out->options[idx++] = 54; + out->options[idx++] = 4; + memcpy(&out->options[idx], &req->server_ip, 4); idx += 4; } + } else if (msg_type == DHCPDECLINE && kind == DHCPK_DECLINE) { + out->options[idx++] = 50; + out->options[idx++] = 4; + memcpy(&out->options[idx], &req->offered_ip, 4); + idx += 4; + out->options[idx++] = 54; + out->options[idx++] = 4; + memcpy(&out->options[idx], &req->server_ip, 4); + idx += 4; } - idx = dhcp_options_write_param_req_list(p.options, idx); - - p.options[idx++] = 255; + if (msg_type != DHCPDECLINE) idx = dhcp_options_write_param_req_list(out->options, idx); - size_t dhcp_len = sizeof(dhcp_packet) - (sizeof(p.options) - idx); - uintptr_t buf = (uintptr_t)malloc(dhcp_len); + out->options[idx++] = 255; - memcpy((void*)buf, &p, dhcp_len); - return (sizedptr){ .ptr = buf, .size = (uint32_t)dhcp_len }; + return (uint32_t)(sizeof(*out) - (sizeof(out->options) - idx)); } dhcp_packet* dhcp_parse_frame_payload(uintptr_t frame_ptr) { @@ -88,6 +85,7 @@ uint16_t dhcp_parse_option_bounded(const dhcp_packet *p, uint32_t payload_len, u const uint8_t *opt = p->options; uint32_t i= 4; uint32_t max = payload_len - header_sz; + if (max > sizeof(p->options)) max = sizeof(p->options); while (i < max) { uint8_t code = opt[i++]; if (code == 0) continue; diff --git a/kernel/networking/application_layer/dhcp.h b/kernel/networking/application_layer/dhcp.h index 2483c077..e1a9fca6 100644 --- a/kernel/networking/application_layer/dhcp.h +++ b/kernel/networking/application_layer/dhcp.h @@ -46,7 +46,7 @@ typedef struct __attribute__((packed)) { } dhcp_packet; typedef struct { - uint8_t mac[6]; + uint8_t mac[MAC_ADDR_LEN]; uint32_t server_ip; uint32_t offered_ip; } dhcp_request; @@ -56,10 +56,11 @@ typedef enum { DHCPK_RENEW = 1, DHCPK_REBIND = 2, DHCPK_INFORM = 3, - DHCPK_DISCOVER = 4 + DHCPK_DISCOVER = 4, + DHCPK_DECLINE = 5 } dhcp_req_kind; -sizedptr dhcp_build_packet(const dhcp_request *req, uint8_t msg_type, uint32_t xid, dhcp_req_kind kind, bool broadcast); +uint32_t dhcp_build_packet(const dhcp_request *req, uint8_t msg_type, uint32_t xid, dhcp_req_kind kind, bool broadcast, dhcp_packet *out); dhcp_packet* dhcp_parse_frame_payload(uintptr_t frame_ptr); diff --git a/kernel/networking/application_layer/dhcp_daemon.c b/kernel/networking/application_layer/dhcp_daemon.c index 5b4d6ece..7b428b1f 100644 --- a/kernel/networking/application_layer/dhcp_daemon.c +++ b/kernel/networking/application_layer/dhcp_daemon.c @@ -1,23 +1,22 @@ #include "dhcp_daemon.h" -#include "console/kio.h" #include "std/memory.h" -#include "process/scheduler.h" +#include "kernel_processes/kprocess_loader.h" +#include "exceptions/irq.h" #include "random/random.h" #include "networking/application_layer/dhcp.h" -#include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_utils.h" #include "net/network_types.h" #include "networking/link_layer/arp.h" -#include "networking/transport_layer/udp.h" +#include "networking/link_layer/link_utils.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #include "networking/transport_layer/trans_utils.h" #include "types.h" #include "networking/interface_manager.h" #include "networking/network.h" -#include "networking/internet_layer/ipv4_route.h" #include "syscalls/syscalls.h" typedef enum { @@ -29,33 +28,53 @@ typedef enum { DHCP_S_REBINDING } dhcp_state_t; +typedef enum { + DHCP_APPLY_FAILED = 0, + DHCP_APPLY_OK, + DHCP_APPLY_CONFLICT +} dhcp_apply_result_t; + typedef struct { uint8_t ifindex; - uint8_t l3_id; + l3_id_t l3_id; + uint32_t seen_epoch; + uint32_t seen_generation; + ipv4_cfg_t mode; dhcp_state_t state; - uint32_t t1_left_ms; - uint32_t t2_left_ms; - uint32_t lease_left_ms; + uint64_t t1_left_ms; + uint64_t t2_left_ms; + uint64_t lease_left_ms; + uint32_t last_tick_ms; uint32_t last_xid; uint32_t trans_xid; uint32_t server_ip_net; - uint8_t mac[6]; + uint8_t mac[MAC_ADDR_LEN]; bool mac_ok; bool needs_inform; socket_handle_t sock; - uint32_t retry_left_ms; + uint64_t retry_left_ms; + uint32_t rx_fast_left_ms; uint32_t backoff_ms; + uint32_t force_renew_seen; } dhcp_if_state_t; -static volatile bool g_force_renew = false; -static uint16_t g_pid_dhcpd = 0xFFFF; -static dhcp_if_state_t g_if[MAX_L2_INTERFACES * MAX_IPV4_PER_INTERFACE]; +static volatile uint32_t g_force_renew_generation; +static volatile uint8_t g_dhcp_running; +static volatile uint8_t g_dhcp_pending; +static volatile uint8_t g_dhcp_rekick; +static dhcp_if_state_t g_if[MAX_IPV4_L3_INTERFACES]; static int g_if_count = 0; -uint16_t dhcp_get_pid() { return g_pid_dhcpd; } -bool dhcp_is_running() { return g_pid_dhcpd != 0xFFFF; } -void dhcp_set_pid(uint16_t p){ g_pid_dhcpd = p; } -void dhcp_force_renew() { g_force_renew = true; } +#define DHCP_ACTIVE_TICK_MS 100 +#define DHCP_IDLE_TICK_MS 5000 + +void dhcp_force_renew() { + irq_flags_t irq = irq_save_disable(); + g_force_renew_generation++; + if (!g_force_renew_generation) g_force_renew_generation = 1; + irq_restore(irq); + dhcp_daemon_kick(); +} static uint32_t dhcp_next_backoff_ms(dhcp_if_state_t* st) { if (st->backoff_ms == 0) st->backoff_ms = 4000; @@ -73,31 +92,16 @@ static uint32_t dhcp_next_backoff_ms(dhcp_if_state_t* st) { return (uint32_t)val; } -static void dhcp_reset_backoff(dhcp_if_state_t* st) { - st->backoff_ms = 0; - st->retry_left_ms = 0; -} - -static bool find_state(uint8_t l3_id, int* idx) { - for (int i = 0; i < g_if_count; i++) if (g_if[i].l3_id == l3_id) { *idx = i; return true; } - return false; -} - -static void remove_state_at(int i) { - if (g_if[i].sock) { - socket_close_udp(g_if[i].sock); - socket_destroy_udp(g_if[i].sock); - g_if[i].sock = 0; - } - if (i < g_if_count - 1) g_if[i] = g_if[g_if_count - 1]; - g_if_count--; -} - static void ensure_inventory() { for (int i = 0; i < g_if_count;) { - uint8_t l3id = g_if[i].l3_id; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3id); - if (!v4 || v4->mode == IPV4_CFG_DISABLED || !v4->l2 || !v4->l2->is_up) { remove_state_at(i); continue; } + dhcp_if_state_t* st = &g_if[i]; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); + if (!ipv4_l3_is_active(v4) || v4->generation != st->seen_generation) { + if (st->sock) close_socket(st->sock); + if (i < g_if_count - 1) g_if[i] = g_if[g_if_count - 1]; + g_if_count--; + continue; + } i++; } uint8_t n = l2_interface_count(); @@ -106,117 +110,171 @@ static void ensure_inventory() { if (!l2 || !l2->is_up) continue; for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - int idx; - if (find_state(v4->l3_id, &idx)) continue; - dhcp_if_state_t st; - memset(&st, 0, sizeof(st)); - st.ifindex = l2->ifindex; - st.l3_id = v4->l3_id; - st.state = DHCP_S_INIT; - st.t1_left_ms = 0; - st.t2_left_ms = 0; - st.lease_left_ms = 0; - st.last_xid = 0; - st.trans_xid = 0; - st.server_ip_net = 0; - const uint8_t* m = network_get_mac(st.ifindex); - if (m) { memcpy(st.mac, m, 6); st.mac_ok = true; } - st.needs_inform = (v4->mode == IPV4_CFG_STATIC && v4->ip != 0); - st.sock = udp_socket_create(SOCK_ROLE_SERVER, g_pid_dhcpd, NULL); - SockBindSpec spec; - memset(&spec, 0, sizeof(spec)); - spec.kind = BIND_L3; - spec.l3_id = st.l3_id; - if (socket_bind_udp_ex(st.sock, &spec, 68) != SOCK_OK) { - socket_destroy_udp(st.sock); - continue; + if (!ipv4_l3_is_active(v4)) continue; + bool found = false; + for (int i = 0; i < g_if_count; i++) { + if (g_if[i].l3_id == v4->l3_id) { + found = true; + break; + } + } + if (found || g_if_count >= MAX_IPV4_L3_INTERFACES) continue; + dhcp_if_state_t* st = &g_if[g_if_count++]; + memset(st, 0, sizeof(*st)); + st->ifindex = l2->ifindex; + st->l3_id = v4->l3_id; + st->seen_epoch = v4->epoch; + st->seen_generation = v4->generation; + st->mode = v4->mode; + st->needs_inform = v4->mode == IPV4_CFG_STATIC && v4->ip != 0; + st->force_renew_seen = g_force_renew_generation; + st->last_tick_ms = (uint32_t)get_time(); + if (v4->mode == IPV4_CFG_DHCP && v4->ip && v4->runtime_opts_v4.lease && v4->runtime_opts_v4.lease_start_time) { + uint32_t age_ms = (uint32_t)get_time() - v4->runtime_opts_v4.lease_start_time; + uint64_t lease_ms = v4->runtime_opts_v4.lease == UINT32_MAX ? UINT64_MAX : (uint64_t)v4->runtime_opts_v4.lease * 1000; + uint64_t t1_ms = v4->runtime_opts_v4.t1 == UINT32_MAX ? UINT64_MAX : (uint64_t)v4->runtime_opts_v4.t1 * 1000; + uint64_t t2_ms = v4->runtime_opts_v4.t2 == UINT32_MAX ? UINT64_MAX : (uint64_t)v4->runtime_opts_v4.t2 * 1000; + + if (lease_ms == UINT64_MAX || age_ms < lease_ms) { + st->state = DHCP_S_BOUND; + st->lease_left_ms = lease_ms == UINT64_MAX ? UINT64_MAX : lease_ms - age_ms; + st->t1_left_ms = t1_ms == UINT64_MAX ? UINT64_MAX : (age_ms < t1_ms ? t1_ms - age_ms : 0); + st->t2_left_ms = t2_ms == UINT64_MAX ? UINT64_MAX : (age_ms < t2_ms ? t2_ms - age_ms : 0); + if (v4->runtime_opts_v4.server_ip) st->server_ip_net = bswap32(v4->runtime_opts_v4.server_ip); + if (g_force_renew_generation) st->force_renew_seen = g_force_renew_generation - 1; + else st->force_renew_seen = UINT32_MAX; + } + } + + const uint8_t* m = network_get_mac(st->ifindex); + if (m) { + mac_copy(st->mac, m); + st->mac_ok = true; } - st.retry_left_ms = 0; - st.backoff_ms = 0; - g_if[g_if_count++] = st; } } -} + + for (int i = 0; i < g_if_count; i++) { + dhcp_if_state_t* st = &g_if[i]; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); + if (!ipv4_l3_is_active(v4)) continue; -static bool packet_mac_matches(const dhcp_packet *p, const uint8_t mac[6]) { - if (!mac) return true; - for (int i = 0; i < 6; i++) if (p->chaddr[i] != mac[i]) return false; - return true; + if (st->mode != v4->mode) { + st->mode = v4->mode; + st->state = DHCP_S_INIT; + st->t1_left_ms = 0; + st->t2_left_ms = 0; + st->lease_left_ms = 0; + st->last_xid = 0; + st->trans_xid = 0; + st->server_ip_net = 0; + st->retry_left_ms = 0; + st->rx_fast_left_ms = 0; + st->backoff_ms = 0; + st->last_tick_ms = get_time(); + st->needs_inform = v4->mode == IPV4_CFG_STATIC && v4->ip != 0; + } else if (st->seen_epoch != v4->epoch && v4->mode == IPV4_CFG_STATIC && v4->ip) st->needs_inform = true; + st->seen_epoch = v4->epoch; + st->seen_generation = v4->generation; + + bool needs_socket = v4->mode == IPV4_CFG_DHCP || st->needs_inform; + if (needs_socket && !st->sock) { + st->sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_NONBLOCK}); + if (st->sock) { + SockBindSpec spec = {.kind = BIND_L3, .ver = IP_VER4, .l3_id = st->l3_id}; + if (set_socket_option(st->sock, SOCK_OPT_BROADCAST_ALLOWED, NULL, 0) != SOCK_OK || bind_socket(st->sock, &spec, 68) != SOCK_OK) { + close_socket(st->sock); + st->sock = 0; + } + } + } else if (!needs_socket && st->sock) { + close_socket(st->sock); + st->sock = 0; + } + } } -static bool udp_wait_for_type_on(socket_handle_t sock, uint8_t wanted, uint32_t expect_xid, const uint8_t mac[6], dhcp_packet **outp, sizedptr *outsp, uint32_t timeout_ms) { - uint32_t waited = 0; - while(waited < timeout_ms){ - uint8_t buf[1024]; +static uint32_t udp_wait_for_type_on(socket_handle_t sock, uint8_t wanted, uint32_t expect_xid, const uint8_t mac[MAC_ADDR_LEN], dhcp_packet* out, uint32_t timeout_ms) { + if (!out) return 0; + + uint64_t start_ms = get_time(); + while (get_time() - start_ms < timeout_ms){ net_l4_endpoint src; memset(&src, 0, sizeof(src)); - int64_t r = socket_recvfrom_udp_ex(sock, buf, sizeof(buf), &src); + int64_t r = receive_from_socket(sock, out, sizeof(*out), &src); if (r > 0) { - if (src.port != 67) { continue; } - if ((size_t)r < sizeof(dhcp_packet) - sizeof(((dhcp_packet*)0)->options) + 4) { continue; } - dhcp_packet *p = (dhcp_packet*)buf; - - if (p->htype != 1) { continue; } - if (p->hlen != 6) { continue; } - if (!dhcp_has_valid_cookie(p)) { continue; } - if (expect_xid && p->xid != expect_xid) { continue; } - if (mac && !packet_mac_matches(p, mac)) { continue; } - uint16_t idx = dhcp_parse_option_bounded(p, (uint32_t)r, 53); - if (idx == UINT16_MAX) { continue; } - uint8_t len = p->options[idx+1]; - if (len < 1) { continue; } - if (p->options[idx+2] != wanted) { continue; } - uintptr_t copy = (uintptr_t)malloc((uint32_t)r); - memcpy((void*)copy, buf, (size_t)r); - if (outp) *outp= (dhcp_packet*)copy; - if (outsp) *outsp = (sizedptr){ copy, (uint32_t)r }; - return true; - } else { - msleep(50); - waited += 50; + if (src.port != 67) continue; + if ((size_t)r < sizeof(dhcp_packet) - sizeof(out->options) + 4) continue; + if (out->htype != 1 || out->hlen != MAC_ADDR_LEN) continue; + if (!dhcp_has_valid_cookie(out)) continue; + if (expect_xid && out->xid != expect_xid) continue; + if (mac && !mac_equal(out->chaddr, mac)) continue; + uint16_t idx = dhcp_parse_option_bounded(out, (uint32_t)r, 53); + if (idx == UINT16_MAX || out->options[idx+1] < 1) continue; + if (out->options[idx+2] != wanted) continue; + return (uint32_t)r; } + msleep(50); } - return false; + return 0; } -static bool udp_wait_for_ack_or_nak(socket_handle_t sock, uint32_t expect_xid, const uint8_t mac[6], dhcp_packet **outp, sizedptr *outsp, uint32_t timeout_ms, uint8_t *out_msg_type) { - uint32_t waited = 0; - while (waited < timeout_ms) { - uint8_t buf[1024]; +static uint32_t udp_wait_for_ack_or_nak(socket_handle_t sock, uint32_t expect_xid, const uint8_t mac[MAC_ADDR_LEN], uint32_t expect_server_net, dhcp_packet* out, uint32_t timeout_ms, uint8_t *out_msg_type) { + if (!out) return 0; + + uint64_t start_ms = get_time(); + while (get_time() - start_ms < timeout_ms) { net_l4_endpoint src; memset(&src, 0, sizeof(src)); - int64_t r = socket_recvfrom_udp_ex(sock, buf, sizeof(buf), &src); + int64_t r = receive_from_socket(sock, out, sizeof(*out), &src); if (r > 0) { - if (src.port != 67) { continue; } - if ((size_t)r < sizeof(dhcp_packet) - sizeof(((dhcp_packet*)0)->options) + 4) { continue; } - dhcp_packet* p = (dhcp_packet*)buf; - if (p->htype != 1 || p->hlen != 6) { continue; } - if (!dhcp_has_valid_cookie(p)) { continue; } - if (expect_xid && p->xid != expect_xid) { continue; } - if (mac && !packet_mac_matches(p, mac)) { continue; } - uint16_t idx = dhcp_parse_option_bounded(p, (uint32_t)r, 53); - if (idx == UINT16_MAX || p->options[idx+1] < 1) { continue; } - uint8_t mtype = p->options[idx+2]; - if (mtype != DHCPACK && mtype != DHCPNAK) { continue; } - uintptr_t copy = (uintptr_t)malloc((uint32_t)r); - memcpy((void*)copy, buf, (size_t)r); - if (outp) *outp = (dhcp_packet*)copy; - if (outsp) *outsp = (sizedptr){ copy, (uint32_t)r }; + if (src.port != 67) continue; + if ((size_t)r < sizeof(dhcp_packet) - sizeof(out->options) + 4) continue; + if (out->htype != 1 || out->hlen != MAC_ADDR_LEN) continue; + if (!dhcp_has_valid_cookie(out)) continue; + if (expect_xid && out->xid != expect_xid) continue; + if (mac && !mac_equal(out->chaddr, mac)) continue; + uint16_t idx = dhcp_parse_option_bounded(out, (uint32_t)r, 53); + if (idx == UINT16_MAX || out->options[idx+1] < 1) continue; + uint8_t mtype = out->options[idx+2]; + if (mtype != DHCPACK && mtype != DHCPNAK) continue; + if (expect_server_net) { + uint16_t sid = dhcp_parse_option_bounded(out, r, 54); + if (sid == UINT16_MAX || out->options[sid + 1] < 4) continue; + uint32_t server_net; + memcpy(&server_net, &out->options[sid+2], 4); + if (server_net != expect_server_net) continue; + } if (out_msg_type) *out_msg_type = mtype; - return true; - } else { - msleep(50); - waited += 50; + return (uint32_t)r; } + msleep(50); } - return false; + return 0; +} + +static void dhcp_send_decline_for(dhcp_if_state_t* st, uint32_t offered_ip_net) { + if (!st || !st->sock || !st->server_ip_net || !offered_ip_net) return; + + dhcp_request req; + memset(&req, 0, sizeof(req)); + if (st->mac_ok) mac_copy(req.mac, st->mac); + req.offered_ip = offered_ip_net; + req.server_ip = st->server_ip_net; + + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPDECLINE, st->last_xid, DHCPK_DECLINE, true, &pkt); + if (!pkt_len) return; + + uint32_t bcast = IPV4_LIMITED_BROADCAST; + net_l4_endpoint dst; + make_ep(&bcast, 67, IP_VER4, &dst); + send_to_socket(st->sock, &dst, &pkt, pkt_len); } -static void apply_offer_to_l3(uint8_t ifindex, uint8_t l3_id, dhcp_packet *p, sizedptr sp, uint32_t xid, dhcp_if_state_t* st) { +static dhcp_apply_result_t apply_offer_to_l3(l3_id_t l3_id, dhcp_packet *p, uint32_t payload_len, uint32_t xid, dhcp_if_state_t* st) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); - if (!v4) return; + if (!v4) return DHCP_APPLY_FAILED; net_runtime_opts_t rt_local; memset(&rt_local, 0, sizeof(rt_local)); uint32_t yi_net = p->yiaddr; @@ -225,110 +283,98 @@ static void apply_offer_to_l3(uint8_t ifindex, uint8_t l3_id, dhcp_packet *p, si uint8_t len; uint32_t mask_host = 0; - idx = dhcp_parse_option_bounded(p, sp.size, 1); - if (idx != UINT16_MAX && (len = p->options[idx+1]) >= 4) { - uint32_t mask_net; - memcpy(&mask_net, &p->options[idx+2], 4); - mask_host = bswap32(mask_net); + idx = dhcp_parse_option_bounded(p, payload_len, 1); + if (idx != UINT16_MAX && p->options[idx+1] >= 4) { + mask_host = rd_be32(&p->options[idx+2]); } uint32_t gw_host = 0; - idx = dhcp_parse_option_bounded(p, sp.size, 3); - if (idx != UINT16_MAX && (len = p->options[idx+1]) >= 4) { - uint32_t gw_net; - memcpy(&gw_net, &p->options[idx+2], 4); - gw_host = bswap32(gw_net); + idx = dhcp_parse_option_bounded(p, payload_len, 3); + if (idx != UINT16_MAX && p->options[idx+1] >= 4) { + gw_host = rd_be32(&p->options[idx+2]); } - idx = dhcp_parse_option_bounded(p, sp.size, 6); + idx = dhcp_parse_option_bounded(p, payload_len, 6); if (idx != UINT16_MAX) { len = p->options[idx+1]; for (int i = 0; i < 2 && (i*4 + 4) <= len; i++) { - uint32_t dns_net; - memcpy(&dns_net, &p->options[idx+2 + i*4], 4); - rt_local.dns[i] = bswap32(dns_net); + rt_local.dns[i] = rd_be32(&p->options[idx+2 + i*4]); } } - idx = dhcp_parse_option_bounded(p, sp.size, 42); + idx = dhcp_parse_option_bounded(p, payload_len, 42); if (idx != UINT16_MAX) { len = p->options[idx+1]; for (int i = 0; i < 2 && (i*4 + 4) <= len; i++) { - uint32_t ntp_net; - memcpy(&ntp_net, &p->options[idx+2 + i*4], 4); - rt_local.ntp[i] = bswap32(ntp_net); + rt_local.ntp[i] = rd_be32(&p->options[idx+2 + i*4]); } } - idx = dhcp_parse_option_bounded(p, sp.size, 26); + idx = dhcp_parse_option_bounded(p, payload_len, 26); if (idx != UINT16_MAX && p->options[idx+1] == 2) { - uint16_t mtu_net; - memcpy(&mtu_net, &p->options[idx+2], 2); - rt_local.mtu = bswap16(mtu_net); + uint16_t mtu = rd_be16(&p->options[idx+2]); + if (mtu >= 68) rt_local.mtu = mtu; } uint32_t lease_s = 0; - idx = dhcp_parse_option_bounded(p, sp.size, 51); + idx = dhcp_parse_option_bounded(p, payload_len, 51); if (idx != UINT16_MAX && p->options[idx+1] >= 4) { - uint32_t lease_net; - memcpy(&lease_net, &p->options[idx+2], 4); - lease_s = bswap32(lease_net); + lease_s = rd_be32(&p->options[idx+2]); rt_local.lease = lease_s; } uint32_t t1_s = 0; - idx = dhcp_parse_option_bounded(p, sp.size, 58); + idx = dhcp_parse_option_bounded(p, payload_len, 58); if (idx != UINT16_MAX && p->options[idx+1] >= 4) { - uint32_t t1_net; - memcpy(&t1_net, &p->options[idx+2], 4); - t1_s = bswap32(t1_net); + t1_s = rd_be32(&p->options[idx+2]); } else { if (lease_s) t1_s = lease_s / 2; } - rt_local.t1 = t1_s; - uint32_t t2_s = 0; - idx = dhcp_parse_option_bounded(p, sp.size, 59); + idx = dhcp_parse_option_bounded(p, payload_len, 59); if (idx != UINT16_MAX && p->options[idx+1] >= 4) { - uint32_t t2_net; - memcpy(&t2_net, &p->options[idx+2], 4); - t2_s = bswap32(t2_net); + t2_s = rd_be32(&p->options[idx+2]); } else { if (lease_s) t2_s = (lease_s / 8) * 7; } + if (lease_s != UINT32_MAX) { + if (!lease_s) return DHCP_APPLY_FAILED; + if (!t1_s || t1_s >= lease_s) t1_s = lease_s / 2; + if (!t2_s || t2_s <= t1_s || t2_s >= lease_s) t2_s = (lease_s / 8) * 7; + if (t2_s <= t1_s) t2_s = t1_s + ((lease_s - t1_s) / 2); + if (t2_s >= lease_s) t2_s = lease_s - 1; + } + rt_local.t1 = t1_s; rt_local.t2 = t2_s; - idx = dhcp_parse_option_bounded(p, sp.size, 54); + idx = dhcp_parse_option_bounded(p, payload_len, 54); if (idx != UINT16_MAX && p->options[idx+1] >= 4) { memcpy(&st->server_ip_net, &p->options[idx+2], 4); } + if (st->server_ip_net) rt_local.server_ip = bswap32(st->server_ip_net); if (rt_local.dns[0] == 0 && gw_host != 0) rt_local.dns[0] = gw_host; rt_local.xid = (uint16_t)xid; + rt_local.lease_start_time = get_time(); - l3_ipv4_update(l3_id, ip_host, mask_host, gw_host, IPV4_CFG_DHCP, &rt_local); - - l3_ipv4_interface_t* v4u = l3_ipv4_find_by_id(l3_id); - if (v4u) { - if (!v4u->routing_table) { - v4u->routing_table = ipv4_rt_create(); - } else { - ipv4_rt_clear(v4u->routing_table); - } - if (v4u->routing_table) { - if (ip_host && mask_host) { - uint32_t net = ip_host & mask_host; - ipv4_rt_add_in(v4u->routing_table, net, mask_host, 0, 10); - } - if (gw_host) { - ipv4_rt_add_in(v4u->routing_table, 0, 0, gw_host, 11); - } - } + if (ip_host != v4->ip && !arp_dad_ipv4_on(st->ifindex, ip_host)) { + dhcp_send_decline_for(st, yi_net); + return DHCP_APPLY_CONFLICT; } - st->t1_left_ms = t1_s * 1000; - st->t2_left_ms = t2_s * 1000; - st->lease_left_ms = lease_s * 1000; + if (!l3_ipv4_update(l3_id, ip_host, mask_host, gw_host, IPV4_CFG_DHCP, &rt_local)) return DHCP_APPLY_FAILED; + + st->t1_left_ms = (uint64_t)t1_s * 1000; + st->t2_left_ms = (uint64_t)t2_s * 1000; + st->lease_left_ms = (uint64_t)lease_s * 1000; + st->force_renew_seen = g_force_renew_generation; + st->last_tick_ms = get_time(); + l3_ipv4_interface_t* updated = l3_ipv4_find_by_id(l3_id); + if (updated) { + st->seen_epoch = updated->epoch; + st->seen_generation = updated->generation; + } + return DHCP_APPLY_OK; } static void dhcp_send_discover_for(dhcp_if_state_t* st) { @@ -338,90 +384,113 @@ static void dhcp_send_discover_for(dhcp_if_state_t* st) { st->trans_xid = xid; dhcp_request req; memset(&req, 0, sizeof(req)); - if (st->mac_ok) memcpy(req.mac, st->mac, 6); - sizedptr pkt = dhcp_build_packet(&req, DHCPDISCOVER, xid, DHCPK_DISCOVER, true); - uint32_t bcast = 0xFFFFFFFFu; + if (st->mac_ok) mac_copy(req.mac, st->mac); + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPDISCOVER, xid, DHCPK_DISCOVER, true, &pkt); + if (!pkt_len) return; + uint32_t bcast = IPV4_LIMITED_BROADCAST; net_l4_endpoint dst; - make_ep(bcast, 67, IP_VER4, &dst); - socket_sendto_udp_ex(st->sock, 0, &dst, 0, (const void*)pkt.ptr, pkt.size); - free_sized((void*)pkt.ptr, pkt.size); + make_ep(&bcast, 67, IP_VER4, &dst); + send_to_socket(st->sock, &dst, &pkt, pkt_len); } -static void dhcp_send_request_select_for(dhcp_if_state_t* st, const dhcp_request* base) { - sizedptr pkt = dhcp_build_packet(base, DHCPREQUEST, st->trans_xid, DHCPK_SELECT, true); - uint32_t dip = 0xFFFFFFFFu; - net_l4_endpoint dst; - make_ep(dip, 67, IP_VER4, &dst); - socket_sendto_udp_ex(st->sock, 0, &dst, 0, (const void*)pkt.ptr, pkt.size); - free_sized((void*)pkt.ptr, pkt.size); -} - -static void dhcp_send_renew_for(dhcp_if_state_t* st) { +static void dhcp_send_renew_for(dhcp_if_state_t* st, bool new_transaction) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); if (!v4) return; dhcp_request req; memset(&req, 0, sizeof(req)); - if (st->mac_ok) memcpy(req.mac, st->mac, 6); - uint32_t ip_net = bswap32(v4->ip); - req.offered_ip = ip_net; + if (st->mac_ok) mac_copy(req.mac, st->mac); + req.offered_ip = bswap32(v4->ip); req.server_ip = st->server_ip_net; - rng_t rng; - rng_init_random(&rng); - st->trans_xid = rng_next32(&rng); - sizedptr pkt = dhcp_build_packet(&req, DHCPREQUEST, st->trans_xid, DHCPK_RENEW, st->server_ip_net == 0); - uint32_t dip = st->server_ip_net ? st->server_ip_net : 0xFFFFFFFFu; + + if (new_transaction || !st->trans_xid) { + rng_t rng; + rng_init_random(&rng); + st->trans_xid = rng_next32(&rng); + } + + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPREQUEST, st->trans_xid, DHCPK_RENEW, false, &pkt); + if (!pkt_len) return; + uint32_t dip = st->server_ip_net ? bswap32(st->server_ip_net) : IPV4_LIMITED_BROADCAST; net_l4_endpoint dst; - make_ep(dip, 67, IP_VER4, &dst); - socket_sendto_udp_ex(st->sock, 0, &dst, 0, (const void*)pkt.ptr, pkt.size); - free_sized((void*)pkt.ptr, pkt.size); + make_ep(&dip, 67, IP_VER4, &dst); + if (send_to_socket(st->sock, &dst, &pkt, pkt_len) == pkt_len) st->rx_fast_left_ms = 1000; } -static void dhcp_send_rebind_for(dhcp_if_state_t* st) { +static void dhcp_send_rebind_for(dhcp_if_state_t* st, bool new_transaction) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); if (!v4) return; dhcp_request req; memset(&req, 0, sizeof(req)); - if (st->mac_ok) memcpy(req.mac, st->mac, 6); - uint32_t ip_net = bswap32(v4->ip); - req.offered_ip = ip_net; - req.server_ip = 0; - rng_t rng; - rng_init_random(&rng); - st->trans_xid = rng_next32(&rng); - sizedptr pkt = dhcp_build_packet(&req, DHCPREQUEST, st->trans_xid, DHCPK_REBIND, true); - uint32_t dip = 0xFFFFFFFFu; + if (st->mac_ok) mac_copy(req.mac, st->mac); + req.offered_ip = bswap32(v4->ip); + if (new_transaction || !st->trans_xid) { + rng_t rng; + rng_init_random(&rng); + st->trans_xid = rng_next32(&rng); + } + + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPREQUEST, st->trans_xid, DHCPK_REBIND, true, &pkt); + if (!pkt_len) return; + uint32_t dip = IPV4_LIMITED_BROADCAST; net_l4_endpoint dst; - make_ep(dip, 67, IP_VER4, &dst); - socket_sendto_udp_ex(st->sock, 0, &dst, 0, (const void*)pkt.ptr, pkt.size); - free_sized((void*)pkt.ptr, pkt.size); + make_ep(&dip, 67, IP_VER4, &dst); + if (send_to_socket(st->sock, &dst, &pkt, pkt_len) == pkt_len) st->rx_fast_left_ms = 1000; } -static void dhcp_send_inform_for(dhcp_if_state_t* st) { +static uint32_t dhcp_send_inform_for(dhcp_if_state_t* st) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); - if (!v4 || !v4->ip) return; + if (!v4 || !v4->ip) return 0; dhcp_request req; memset(&req, 0, sizeof(req)); - if (st->mac_ok) memcpy(req.mac, st->mac, 6); + if (st->mac_ok) mac_copy(req.mac, st->mac); uint32_t ip_net = bswap32(v4->ip); req.offered_ip = ip_net; req.server_ip = 0; rng_t rng; rng_init_random(&rng); uint32_t xid = rng_next32(&rng); - sizedptr pkt = dhcp_build_packet(&req, DHCPINFORM, xid, DHCPK_INFORM, true); - uint32_t dip = 0xFFFFFFFFu; + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPINFORM, xid, DHCPK_INFORM, true, &pkt); + if (!pkt_len) return 0; + uint32_t dip = IPV4_LIMITED_BROADCAST; net_l4_endpoint dst; - make_ep(dip, 67, IP_VER4, &dst); - socket_sendto_udp_ex(st->sock, 0, &dst, 0, (const void*)pkt.ptr, pkt.size); - free_sized((void*)pkt.ptr, pkt.size); + make_ep(&dip, 67, IP_VER4, &dst); + if (send_to_socket(st->sock, &dst, &pkt, pkt_len) != pkt_len) return 0; + return xid; } -static void schedule_retry(dhcp_if_state_t* st) { +static void dhcp_drop_lease_and_retry(dhcp_if_state_t* st) { + l3_ipv4_update(st->l3_id, 0, 0, 0, IPV4_CFG_DHCP, NULL); + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); + if (v4) { + st->seen_epoch = v4->epoch; + st->seen_generation = v4->generation; + } + st->t1_left_ms = 0; + st->t2_left_ms = 0; + st->lease_left_ms = 0; + st->server_ip_net = 0; + st->state = DHCP_S_INIT; + st->rx_fast_left_ms = 0; st->retry_left_ms = dhcp_next_backoff_ms(st); } +static void dhcp_finish_ack(dhcp_if_state_t* st, dhcp_apply_result_t applied) { + if (applied == DHCP_APPLY_OK) { + st->state = DHCP_S_BOUND; + st->backoff_ms = 0; + st->retry_left_ms = 0; + st->rx_fast_left_ms = 0; + } else { + dhcp_drop_lease_and_retry(st); + if (applied == DHCP_APPLY_CONFLICT && st->retry_left_ms < 10000) st->retry_left_ms = 10000; + } +} + static void fsm_once_for(dhcp_if_state_t* st) { - dhcp_state_t old = st->state; switch (st->state) { case DHCP_S_INIT: { if (st->retry_left_ms != 0) break; @@ -430,127 +499,163 @@ static void fsm_once_for(dhcp_if_state_t* st) { st->state = DHCP_S_SELECTING; } break; case DHCP_S_SELECTING: { - dhcp_packet* offer = NULL; sizedptr sp = (sizedptr){0,0}; - if (!udp_wait_for_type_on(st->sock, DHCPOFFER, st->last_xid, st->mac_ok ? st->mac : NULL, &offer, &sp, 5000)) { + dhcp_packet offer; + uint32_t offer_len = udp_wait_for_type_on(st->sock, DHCPOFFER, st->last_xid, st->mac_ok ? st->mac : NULL, &offer, 5000); + if (!offer_len) { st->state = DHCP_S_INIT; - schedule_retry(st); + st->retry_left_ms = dhcp_next_backoff_ms(st); break; } dhcp_request req; memset(&req, 0, sizeof(req)); - if (st->mac_ok) memcpy(req.mac, st->mac, 6); - uint16_t idx54 = dhcp_parse_option_bounded(offer, sp.size, 54); - if (idx54 != UINT16_MAX && offer->options[idx54+1] >= 4) memcpy(&st->server_ip_net, &offer->options[idx54+2], 4); - memcpy(&req.offered_ip, &offer->yiaddr, 4); + if (st->mac_ok) mac_copy(req.mac, st->mac); + uint16_t idx54 = dhcp_parse_option_bounded(&offer, offer_len, 54); + if (idx54 == UINT16_MAX || offer.options[idx54+1] < 4 || !offer.yiaddr) { + st->state = DHCP_S_INIT; + st->retry_left_ms = dhcp_next_backoff_ms(st); + break; + } + memcpy(&st->server_ip_net, &offer.options[idx54+2], 4); + memcpy(&req.offered_ip, &offer.yiaddr, 4); req.server_ip = st->server_ip_net; - free_sized((void*)sp.ptr, sp.size); - dhcp_send_request_select_for(st, &req); + dhcp_packet pkt; + uint32_t pkt_len = dhcp_build_packet(&req, DHCPREQUEST, st->trans_xid, DHCPK_SELECT, true, &pkt); + if (!pkt_len) { + st->state = DHCP_S_INIT; + st->retry_left_ms = dhcp_next_backoff_ms(st); + break; + } + + uint32_t dip = IPV4_LIMITED_BROADCAST; + net_l4_endpoint dst; + make_ep(&dip, 67, IP_VER4, &dst); + send_to_socket(st->sock, &dst, &pkt, pkt_len); st->state = DHCP_S_REQUESTING; - dhcp_reset_backoff(st); + st->backoff_ms = 0; + st->retry_left_ms = 0; + st->rx_fast_left_ms = 0; } break; case DHCP_S_REQUESTING: { - dhcp_packet* resp = NULL; sizedptr sp = (sizedptr){0,0}; uint8_t mtype = 0; - if (!udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, &resp, &sp, 5000, &mtype)) { + dhcp_packet resp; + uint8_t mtype = 0; + uint32_t resp_len = udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, st->server_ip_net, &resp, 5000, &mtype); + if (!resp_len) { st->state = DHCP_S_INIT; - schedule_retry(st); + st->retry_left_ms = dhcp_next_backoff_ms(st); + } else if (mtype == DHCPACK) { + dhcp_apply_result_t applied = apply_offer_to_l3(st->l3_id, &resp, resp_len, st->last_xid, st); + dhcp_finish_ack(st, applied); } else { - if (mtype == DHCPACK) { - apply_offer_to_l3(st->ifindex, st->l3_id, resp, sp, st->last_xid, st); - free_sized((void*)sp.ptr, sp.size); - st->state = DHCP_S_BOUND; - dhcp_reset_backoff(st); - } else { - free_sized((void*)sp.ptr, sp.size); - l3_ipv4_update(st->l3_id, 0, 0, 0, IPV4_CFG_DHCP, NULL); - st->t1_left_ms = 0; - st->t2_left_ms = 0; - st->lease_left_ms = 0; - st->server_ip_net = 0; - st->state = DHCP_S_INIT; - schedule_retry(st); - } + dhcp_drop_lease_and_retry(st); } } break; case DHCP_S_BOUND: { - if (g_force_renew) { - g_force_renew = false; - dhcp_send_renew_for(st); + if (!st->lease_left_ms) { + dhcp_drop_lease_and_retry(st); + break; + } + + if (st->force_renew_seen != g_force_renew_generation) { + st->force_renew_seen = g_force_renew_generation; + dhcp_send_renew_for(st, true); st->last_xid = st->trans_xid; + st->retry_left_ms = st->t2_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; st->state = DHCP_S_RENEWING; - } else if (st->t2_left_ms == 0 && st->lease_left_ms != 0) { - dhcp_send_rebind_for(st); + } else if (!st->t2_left_ms) { + dhcp_send_rebind_for(st, true); st->last_xid = st->trans_xid; + st->retry_left_ms = st->lease_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; st->state = DHCP_S_REBINDING; - } else if (st->t1_left_ms == 0 && st->lease_left_ms != 0) { - dhcp_send_renew_for(st); + } else if (!st->t1_left_ms) { + dhcp_send_renew_for(st, true); st->last_xid = st->trans_xid; + st->retry_left_ms = st->t2_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; st->state = DHCP_S_RENEWING; } } break; case DHCP_S_RENEWING: { - dhcp_packet* p = NULL; sizedptr sp = (sizedptr){0,0}; uint8_t mtype = 0; - if (udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, &p, &sp, 2000, &mtype)) { - if (mtype == DHCPACK) { - apply_offer_to_l3(st->ifindex, st->l3_id, p, sp, st->last_xid, st); - free_sized((void*)sp.ptr, sp.size); - st->state = DHCP_S_BOUND; - dhcp_reset_backoff(st); - } else { - free_sized((void*)sp.ptr, sp.size); - l3_ipv4_update(st->l3_id, 0, 0, 0, IPV4_CFG_DHCP, NULL); - st->t1_left_ms = 0; - st->t2_left_ms = 0; - st->lease_left_ms = 0; - st->server_ip_net = 0; - st->state = DHCP_S_INIT; - schedule_retry(st); - } - } else { - dhcp_send_rebind_for(st); + if (!st->lease_left_ms) { + dhcp_drop_lease_and_retry(st); + break; + } + if (!st->t2_left_ms) { + dhcp_send_rebind_for(st, true); st->last_xid = st->trans_xid; + st->retry_left_ms = st->lease_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; st->state = DHCP_S_REBINDING; + break; + } + + dhcp_packet p; + uint8_t mtype = 0; + uint32_t payload_len = udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, st->server_ip_net, &p, 200, &mtype); + if (payload_len) { + if (mtype == DHCPACK) { + dhcp_apply_result_t applied = apply_offer_to_l3(st->l3_id, &p, payload_len, st->last_xid, st); + dhcp_finish_ack(st, applied); + } else dhcp_drop_lease_and_retry(st); + break; + } + + if (!st->retry_left_ms) { + dhcp_send_renew_for(st, false); + st->last_xid = st->trans_xid; + st->retry_left_ms = st->t2_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; } } break; case DHCP_S_REBINDING: { - dhcp_packet* p = NULL; sizedptr sp = (sizedptr){0,0}; uint8_t mtype = 0; - if (udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, &p, &sp, 2000, &mtype)) { + if (!st->lease_left_ms) { + dhcp_drop_lease_and_retry(st); + break; + } + + dhcp_packet p; + uint8_t mtype = 0; + uint32_t payload_len = udp_wait_for_ack_or_nak(st->sock, st->last_xid, st->mac_ok ? st->mac : NULL, 0, &p, 200, &mtype); + if (payload_len) { if (mtype == DHCPACK) { - apply_offer_to_l3(st->ifindex, st->l3_id, p, sp, st->last_xid, st); - free_sized((void*)sp.ptr, sp.size); - st->state = DHCP_S_BOUND; - dhcp_reset_backoff(st); + dhcp_apply_result_t applied = apply_offer_to_l3(st->l3_id, &p, payload_len, st->last_xid, st); + dhcp_finish_ack(st, applied); } else { - free_sized((void*)sp.ptr, sp.size); - l3_ipv4_update(st->l3_id, 0, 0, 0, IPV4_CFG_DHCP, NULL); - st->t1_left_ms = 0; - st->t2_left_ms = 0; - st->lease_left_ms = 0; - st->server_ip_net = 0; - st->state = DHCP_S_INIT; - schedule_retry(st); + dhcp_drop_lease_and_retry(st); } - } else { - l3_ipv4_update(st->l3_id, 0, 0, 0, IPV4_CFG_DHCP, NULL); - st->t1_left_ms = 0; - st->t2_left_ms = 0; - st->lease_left_ms = 0; - st->server_ip_net = 0; - st->state = DHCP_S_INIT; - schedule_retry(st); + break; + } + + if (!st->retry_left_ms) { + dhcp_send_rebind_for(st, false); + st->last_xid = st->trans_xid; + st->retry_left_ms = st->lease_left_ms / 2; + if (st->retry_left_ms < 60000) st->retry_left_ms = 60000; } } break; } - if (old != st->state) kprintf("[DHCP] ifx=%i l3=%i state %i -> %i", st->ifindex, st->l3_id, old, st->state); } static void tick_timers() { + uint32_t now_ms = get_time(); for (int i = 0; i < g_if_count; i++) { - if (g_if[i].state == DHCP_S_BOUND) { - if (g_if[i].t1_left_ms > 100) g_if[i].t1_left_ms -= 100; else if (g_if[i].t1_left_ms) g_if[i].t1_left_ms = 0; - if (g_if[i].t2_left_ms > 100) g_if[i].t2_left_ms -= 100; else if (g_if[i].t2_left_ms) g_if[i].t2_left_ms = 0; - if (g_if[i].lease_left_ms > 100) g_if[i].lease_left_ms -= 100; else if (g_if[i].lease_left_ms) g_if[i].lease_left_ms = 0; + uint32_t elapsed_ms = now_ms - g_if[i].last_tick_ms; + g_if[i].last_tick_ms = now_ms; + if (!elapsed_ms) continue; + + if (g_if[i].state == DHCP_S_BOUND || g_if[i].state == DHCP_S_RENEWING || g_if[i].state == DHCP_S_REBINDING) { + if (g_if[i].t1_left_ms > elapsed_ms) g_if[i].t1_left_ms -= elapsed_ms; + else g_if[i].t1_left_ms = 0; + if (g_if[i].t2_left_ms > elapsed_ms) g_if[i].t2_left_ms -= elapsed_ms; + else g_if[i].t2_left_ms = 0; + if (g_if[i].lease_left_ms > elapsed_ms) g_if[i].lease_left_ms -= elapsed_ms; + else g_if[i].lease_left_ms = 0; } - if (g_if[i].retry_left_ms > 100) g_if[i].retry_left_ms -= 100; else if (g_if[i].retry_left_ms) g_if[i].retry_left_ms = 0; + if (g_if[i].retry_left_ms > elapsed_ms) g_if[i].retry_left_ms -= elapsed_ms; + else g_if[i].retry_left_ms = 0; + if (g_if[i].rx_fast_left_ms > elapsed_ms) g_if[i].rx_fast_left_ms -= elapsed_ms; + else g_if[i].rx_fast_left_ms = 0; } } @@ -558,26 +663,169 @@ static void maybe_send_inform() { for (int i = 0; i < g_if_count; i++) { if (!g_if[i].needs_inform) continue; l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(g_if[i].l3_id); - if (!v4 || v4->mode != IPV4_CFG_STATIC || !v4->ip) { g_if[i].needs_inform = false; continue; } - dhcp_send_inform_for(&g_if[i]); + if (!v4 || v4->mode != IPV4_CFG_STATIC || !v4->ip) { + g_if[i].needs_inform = false; + continue; + } + + uint32_t xid = dhcp_send_inform_for(&g_if[i]); + if (xid) { + dhcp_packet ack; + uint32_t ack_len = udp_wait_for_type_on(g_if[i].sock, DHCPACK, xid, g_if[i].mac_ok ? g_if[i].mac : NULL, &ack, 5000); + if (ack_len) { + net_runtime_opts_t rt = v4->runtime_opts_v4; + uint16_t idx = dhcp_parse_option_bounded(&ack, ack_len, 6); + if (idx != UINT16_MAX) { + uint8_t len = ack.options[idx+1]; + rt.dns[0] = 0; + rt.dns[1] = 0; + for (int d = 0; d < 2 && d * 4 + 4 <= len; d++) { + rt.dns[d] = rd_be32(&ack.options[idx + 2 + d*4]); + } + } + + idx = dhcp_parse_option_bounded(&ack, ack_len, 42); + if (idx != UINT16_MAX) { + uint8_t len = ack.options[idx+1]; + rt.ntp[0] = 0; + rt.ntp[1] = 0; + for (int n = 0; n < 2 && n * 4 + 4 <= len; n++) { + rt.ntp[n] = rd_be32(&ack.options[idx + 2 + n*4]); + } + } + + idx = dhcp_parse_option_bounded(&ack, ack_len, 26); + if (idx != UINT16_MAX && ack.options[idx + 1] == 2) { + uint16_t mtu = rd_be16(&ack.options[idx + 2]); + if (mtu >= 68) rt.mtu = mtu; + } + l3_ipv4_update(v4->l3_id, v4->ip, v4->mask, v4->gw, IPV4_CFG_STATIC, &rt); + } + } + g_if[i].needs_inform = false; + if (g_if[i].sock) { + close_socket(g_if[i].sock); + g_if[i].sock = 0; + } } } int dhcp_daemon_entry(int argc, char* argv[]) { (void)argc; (void)argv; - g_pid_dhcpd = (uint16_t)get_current_proc_pid(); - dhcp_set_pid(g_pid_dhcpd); + + irq_flags_t irq = irq_save_disable(); + g_dhcp_pending = 0; + g_dhcp_running = 1; + g_dhcp_rekick = 0; + irq_restore(irq); + for (;;) { + irq = irq_save_disable(); + g_dhcp_rekick = 0; + irq_restore(irq); + ensure_inventory(); - if (g_if_count == 0) { msleep(250); continue; } + tick_timers(); + for (int i = 0; i < g_if_count; i++) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(g_if[i].l3_id); - if (!v4) continue; - if (v4->mode == IPV4_CFG_DHCP) fsm_once_for(&g_if[i]); + if (!v4 || v4->mode != IPV4_CFG_DHCP || !g_if[i].sock) continue; + fsm_once_for(&g_if[i]); } maybe_send_inform(); - msleep(100); - tick_timers(); + + bool active_work = false; + uint32_t sleep_ms = DHCP_IDLE_TICK_MS; + if (g_dhcp_rekick) sleep_ms = DHCP_ACTIVE_TICK_MS; + for (int i = 0; i < g_if_count; i++) { + dhcp_if_state_t* st = &g_if[i]; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(st->l3_id); + if (!ipv4_l3_is_active(v4)) continue; + if (st->needs_inform) { + active_work = true; + sleep_ms = DHCP_ACTIVE_TICK_MS; + break; + } + if (v4->mode != IPV4_CFG_DHCP) continue; + active_work = true; + + if (st->force_renew_seen != g_force_renew_generation || st->state == DHCP_S_SELECTING || st->state == DHCP_S_REQUESTING || ((st->state == DHCP_S_RENEWING || st->state == DHCP_S_REBINDING) && st->rx_fast_left_ms)) { + sleep_ms = DHCP_ACTIVE_TICK_MS; + break; + } + + uint64_t next = 0; + if (st->state == DHCP_S_INIT) next = st->retry_left_ms; + else if (st->state == DHCP_S_BOUND) { + next = st->lease_left_ms; + if (st->t1_left_ms < next) next = st->t1_left_ms; + if (st->t2_left_ms < next) next = st->t2_left_ms; + } else if (st->state == DHCP_S_RENEWING) { + next = st->retry_left_ms; + if (!next || st->t2_left_ms < next) next = st->t2_left_ms; + if (!next || st->lease_left_ms < next) next = st->lease_left_ms; + } else if (st->state == DHCP_S_REBINDING) { + next = st->retry_left_ms; + if (!next || st->lease_left_ms < next) next = st->lease_left_ms; + } + if (!next) { + sleep_ms = DHCP_ACTIVE_TICK_MS; + break; + } + uint64_t wait = next > DHCP_IDLE_TICK_MS ? DHCP_IDLE_TICK_MS : next; + if (wait < sleep_ms) sleep_ms = wait; + } + if (!active_work) break; + msleep(sleep_ms); + } + + irq = irq_save_disable(); + g_dhcp_running = 0; + bool rekick = g_dhcp_rekick != 0; + g_dhcp_rekick = 0; + irq_restore(irq); + if (rekick) dhcp_daemon_kick(); + return 0; +} + +void dhcp_daemon_kick(void) { + irq_flags_t irq = irq_save_disable(); + if (g_dhcp_running || g_dhcp_pending) { + g_dhcp_rekick = 1; + irq_restore(irq); + return; + } + irq_restore(irq); + + bool config_work = false; + uint8_t n = l2_interface_count(); + for (uint8_t ix = 0; ix < n && !config_work; ix++) { + l2_interface_t* l2 = l2_interface_at(ix); + if (!l2 || !l2->is_up) continue; + for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[s]; + if (!ipv4_l3_is_active(v4)) continue; + if (v4->mode == IPV4_CFG_DHCP || (v4->mode == IPV4_CFG_STATIC && v4->ip)) { + config_work = true; + break; + } + } + } + if (!config_work) return; + + irq = irq_save_disable(); + if (g_dhcp_running || g_dhcp_pending) { + g_dhcp_rekick = 1; + irq_restore(irq); + return; + } + g_dhcp_pending = 1; + irq_restore(irq); + + if (!create_kernel_process("dhcp_daemon", dhcp_daemon_entry, 0, 0)) { + irq = irq_save_disable(); + g_dhcp_pending = 0; + irq_restore(irq); } } diff --git a/kernel/networking/application_layer/dhcp_daemon.h b/kernel/networking/application_layer/dhcp_daemon.h index 9b1f281a..1914e6a5 100644 --- a/kernel/networking/application_layer/dhcp_daemon.h +++ b/kernel/networking/application_layer/dhcp_daemon.h @@ -7,13 +7,7 @@ extern "C" { int dhcp_daemon_entry(int argc, char* argv[]); -uint16_t dhcp_get_pid(); - -bool dhcp_is_running(); -void dhcp_set_pid(uint16_t pid); - -void dhcp_notify_link_up(); -void dhcp_notify_link_down(); +void dhcp_daemon_kick(void); void dhcp_force_renew(); #ifdef __cplusplus diff --git a/kernel/networking/application_layer/dhcpv6.c b/kernel/networking/application_layer/dhcpv6.c index 339ad12e..c060ed9e 100644 --- a/kernel/networking/application_layer/dhcpv6.c +++ b/kernel/networking/application_layer/dhcpv6.c @@ -2,20 +2,20 @@ #include "std/memory.h" #include "std/string.h" +#include "networking/link_layer/link_utils.h" +#include "networking/internet_layer/ipv6_utils.h" -static void opt_append(uint8_t*b, uint32_t cap, uint32_t*off, uint16_t code, const void*data, uint16_t len){ - if (!b || !off) return; - if (*off + 4u + len > cap) return; +static bool opt_append(uint8_t* b, uint32_t cap, uint32_t* off, uint16_t code, const void* data, uint16_t len){ + if (!b || !off) return false; + if (*off > cap || cap - *off < 4u + len) return false; - uint16_t c = bswap16(code); - uint16_t l = bswap16(len); - - memcpy(b + *off + 0, &c, 2); - memcpy(b + *off + 2, &l, 2); + wr_be16(b + *off, code); + wr_be16(b + *off + 2, len); if (len && data) memcpy(b + *off + 4, data, len); *off += 4u + len; + return true; } uint32_t dhcpv6_make_xid24(uint32_t r32){ @@ -24,12 +24,12 @@ uint32_t dhcpv6_make_xid24(uint32_t r32){ return x; } -uint32_t dhcpv6_iaid_from_mac(const uint8_t mac[6]){ +uint32_t dhcpv6_iaid_from_mac(const uint8_t mac[MAC_ADDR_LEN]){ if (!mac) return 0; return ((uint32_t)mac[2] << 24) | ((uint32_t)mac[3] << 16) | ((uint32_t)mac[4] << 8) | (uint32_t)mac[5]; } -bool dhcpv6_build_message(uint8_t*out, uint32_t out_cap, uint32_t*out_len, const net_runtime_opts_v6_t*rt, const uint8_t mac[6], uint8_t msg_type, dhcpv6_req_kind kind, uint32_t xid24, bool want_address) { +bool dhcpv6_build_message(uint8_t*out, uint32_t out_cap, uint32_t*out_len, const net_runtime_opts_v6_t*rt, const uint8_t mac[MAC_ADDR_LEN], uint8_t msg_type, uint32_t xid24, uint16_t elapsed_cs, const uint8_t ia_addr[16]) { if (!out || !out_len) return false; if (out_cap < 4) return false; @@ -44,133 +44,84 @@ bool dhcpv6_build_message(uint8_t*out, uint32_t out_cap, uint32_t*out_len, const uint32_t off = 4; uint8_t duid[10]; - uint16_t duid_type = bswap16(3); - uint16_t hw_type = bswap16(1); - - memcpy(duid + 0, &duid_type, 2); - memcpy(duid + 2, &hw_type, 2); + wr_be16(duid, 3); + wr_be16(duid + 2, 1); - if (mac) memcpy(duid + 4, mac, 6); - else memset(duid + 4, 0, 6); + if (mac) mac_copy(duid + 4, mac); + else mac_clear(duid + 4); - opt_append(out, out_cap, &off, DHCPV6_OPT_CLIENTID, duid, 10); + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_CLIENTID, duid, 10)) return false; - if (rt && rt->server_id_len && rt->server_id_len <= DHCPV6_MAX_SERVER_ID) opt_append(out, out_cap, &off, DHCPV6_OPT_SERVERID, rt->server_id, rt->server_id_len); + if (msg_type == DHCPV6_MSG_REQUEST || msg_type == DHCPV6_MSG_RENEW || msg_type == DHCPV6_MSG_RELEASE || msg_type == DHCPV6_MSG_DECLINE) { + if (!rt || !rt->server_id_len || rt->server_id_len > DHCPV6_MAX_SERVER_ID) return false; + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_SERVERID, rt->server_id, rt->server_id_len)) return false; + } - uint8_t elapsed[2] = {0, 0}; - opt_append(out, out_cap, &off, DHCPV6_OPT_ELAPSED, elapsed, 2); + uint16_t elapsed = bswap16(elapsed_cs); + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_ELAPSED, &elapsed, 2)) return false; if (msg_type == DHCPV6_MSG_SOLICIT || msg_type == DHCPV6_MSG_REQUEST || msg_type == DHCPV6_MSG_RENEW || - msg_type == DHCPV6_MSG_REBIND || msg_type == DHCPV6_MSG_CONFIRM || msg_type == DHCPV6_MSG_RELEASE || - msg_type == DHCPV6_MSG_INFORMATION_REQUEST || msg_type == DHCPV6_MSG_DECLINE){ - - uint16_t oro[2]; - uint16_t n = 0; + msg_type == DHCPV6_MSG_REBIND || msg_type == DHCPV6_MSG_INFORMATION_REQUEST){ + uint16_t oro[4]; + uint16_t oro_len = 0; - oro[n++] = bswap16(DHCPV6_OPT_DNS_SERVERS); - oro[n++] = bswap16(DHCPV6_OPT_NTP_SERVER); + oro[oro_len++] = bswap16(DHCPV6_OPT_DNS_SERVERS); + oro[oro_len++] = bswap16(DHCPV6_OPT_NTP_SERVER); - opt_append(out, out_cap, &off, DHCPV6_OPT_ORO, oro, (uint16_t)(n * 2)); + if (msg_type == DHCPV6_MSG_SOLICIT) oro[oro_len++] = bswap16(DHCPV6_OPT_SOL_MAX_RT); + if (msg_type == DHCPV6_MSG_INFORMATION_REQUEST) { + oro[oro_len++] = bswap16(DHCPV6_OPT_INF_MAX_RT); + if (oro_len < 4) oro[oro_len++] = bswap16(DHCPV6_OPT_INFORMATION_REFRESH_TIME); + } + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_ORO, oro, oro_len * 2)) return false; } - if (want_address) { + if (msg_type == DHCPV6_MSG_SOLICIT || msg_type == DHCPV6_MSG_REQUEST || msg_type == DHCPV6_MSG_CONFIRM || msg_type == DHCPV6_MSG_RENEW || msg_type == DHCPV6_MSG_REBIND || msg_type == DHCPV6_MSG_RELEASE || msg_type == DHCPV6_MSG_DECLINE) { uint32_t iaid = rt ? rt->iaid : 0; - uint32_t iaid_pd = iaid ? (iaid ^ 0xA5A5A5A5u) : 0; + if (!iaid) return false; - if (kind == DHCPV6K_SELECT){ - uint8_t iana[12]; + uint8_t payload[40]; - uint32_t iaid_net = bswap32(iaid); - uint32_t t1_net = 0; - uint32_t t2_net = 0; + wr_be32(payload, iaid); + wr_be32(payload + 4, 0); + wr_be32(payload + 8, 0); + uint16_t ia_len = 12; - memcpy(iana + 0, &iaid_net, 4); - memcpy(iana + 4, &t1_net, 4); - memcpy(iana + 8, &t2_net, 4); + if (ia_addr && !ipv6_is_unspecified(ia_addr)) { + wr_be16(payload + 12, DHCPV6_OPT_IAADDR); + wr_be16(payload + 14, 24); + ipv6_cpy(payload + 16, ia_addr); + wr_be32(payload + 32, 0); + wr_be32(payload + 36, 0); + ia_len = 40; + } else if (msg_type == DHCPV6_MSG_CONFIRM || msg_type == DHCPV6_MSG_RENEW || msg_type == DHCPV6_MSG_REBIND || msg_type == DHCPV6_MSG_RELEASE || msg_type == DHCPV6_MSG_DECLINE) return false; - opt_append(out, out_cap, &off, DHCPV6_OPT_IA_NA, iana, 12); + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_IA_NA, payload, ia_len)) return false; - if (rt && rt->pd_prefix_len){ + if (rt->pd_prefix_len) { + uint32_t pd_iaid = iaid ^ 0xA5A5A5A5u; + if (msg_type == DHCPV6_MSG_SOLICIT) { uint8_t iapd[12]; - - uint32_t pd_iaid_net = bswap32(iaid_pd); - - memcpy(iapd + 0, &pd_iaid_net, 4); - memcpy(iapd + 4, &t1_net, 4); - memcpy(iapd + 8, &t2_net, 4); - - opt_append(out, out_cap, &off, DHCPV6_OPT_IA_PD, iapd, 12); - } - } else { - if (rt && rt->lease){ - uint8_t payload[40]; - uint8_t addr[16]; - - memset(addr, 0, 16); - - uint32_t iaid_net = bswap32(iaid); - uint32_t t1_net = 0; - uint32_t t2_net = 0; - - memcpy(payload + 0, &iaid_net, 4); - memcpy(payload + 4, &t1_net, 4); - memcpy(payload + 8, &t2_net, 4); - - uint16_t code_net = bswap16(DHCPV6_OPT_IAADDR); - uint16_t len_net = bswap16(24); - - memcpy(payload + 12, &code_net, 2); - memcpy(payload + 14, &len_net, 2); - memcpy(payload + 16, addr, 16); - - uint32_t pref_net = bswap32(rt->t1); - uint32_t valid_net = bswap32(rt->lease); - - memcpy(payload + 32, &pref_net, 4); - memcpy(payload + 36, &valid_net, 4); - - opt_append(out, out_cap, &off, DHCPV6_OPT_IA_NA, payload, 40); + wr_be32(iapd, pd_iaid); + wr_be32(iapd + 4, 0); + wr_be32(iapd + 8, 0); + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_IA_PD, iapd, 12)) return false; } else { - uint8_t iana[12]; - - uint32_t iaid_net = bswap32(iaid); - uint32_t t1_net = 0; - uint32_t t2_net = 0; - - memcpy(iana + 0, &iaid_net, 4); - memcpy(iana + 4, &t1_net, 4); - memcpy(iana + 8, &t2_net, 4); - - opt_append(out, out_cap, &off, DHCPV6_OPT_IA_NA, iana, 12); - } - - if (rt && rt->pd_prefix_len){ - uint8_t payload[41]; - - uint32_t iaid_net = bswap32(iaid_pd); - uint32_t t1_net = 0; - uint32_t t2_net = 0; - - memcpy(payload + 0, &iaid_net, 4); - memcpy(payload + 4, &t1_net, 4); - memcpy(payload + 8, &t2_net, 4); - - uint16_t code_net = bswap16(DHCPV6_OPT_IAPREFIX); - uint16_t len_net = bswap16(25); - - memcpy(payload + 12, &code_net, 2); - memcpy(payload + 14, &len_net, 2); - - uint32_t pref_net = bswap32(rt->pd_preferred_lft); - uint32_t valid_net = bswap32(rt->pd_valid_lft); - - memcpy(payload + 16, &pref_net, 4); - memcpy(payload + 20, &valid_net, 4); - - payload[24] = rt->pd_prefix_len; - memcpy(payload + 25, rt->pd_prefix, 16); - - opt_append(out, out_cap, &off, DHCPV6_OPT_IA_PD, payload, 41); + uint8_t iapd[41]; + wr_be32(iapd, pd_iaid); + wr_be32(iapd + 4, 0); + wr_be32(iapd + 8, 0); + wr_be16(iapd + 12, DHCPV6_OPT_IAPREFIX); + wr_be16(iapd + 14, 25); + uint32_t preferred = rt->pd_preferred_lft; + uint32_t valid = rt->pd_valid_lft; + wr_be32(iapd + 16, preferred); + wr_be32(iapd + 20, valid); + + iapd[24] = rt->pd_prefix_len; + ipv6_cpy(iapd + 25, rt->pd_prefix); + + if (!opt_append(out, out_cap, &off, DHCPV6_OPT_IA_PD, iapd, 41)) return false; } } } @@ -180,140 +131,152 @@ bool dhcpv6_build_message(uint8_t*out, uint32_t out_cap, uint32_t*out_len, const static bool parse_opts(const uint8_t*opt, uint32_t opt_len, uint32_t expect_iaid, dhcpv6_parsed_t*out){ uint32_t off = 0; - bool got_addr = false; - - while (off + 4 <= opt_len) { - uint16_t code_net; - uint16_t len_net; - - memcpy(&code_net, opt + off + 0, 2); - memcpy(&len_net, opt + off + 2, 2); - uint16_t code = bswap16(code_net); - uint16_t len = bswap16(len_net); + while (off < opt_len) { + if (off + 4 > opt_len) return false; + uint16_t code = rd_be16(opt + off); + uint16_t len = rd_be16(opt + off + 2); off += 4; - if (off + len >opt_len) break; + if (off + len >opt_len) return false; + const uint8_t* data = opt + off; - if (code == DHCPV6_OPT_SERVERID && len && len <= DHCPV6_MAX_SERVER_ID){ - memcpy(out->server_id, opt + off, len); + if (code == DHCPV6_OPT_SERVERID) { + if (!len || len > DHCPV6_MAX_SERVER_ID || out->has_server_id) return false; + memcpy(out->server_id, data, len); out->server_id_len = len; out->has_server_id = true; - } - - if (code == DHCPV6_OPT_DNS_SERVERS && len >= 16){ + } else if (code == DHCPV6_OPT_CLIENTID) { + if (!len || len > DHCPV6_MAX_CLIENT_ID || out->has_client_id) return false; + memcpy(out->client_id, data, len); + out->client_id_len = len; + out->has_client_id = true; + } else if (code == DHCPV6_OPT_STATUS_CODE) { + if (out->has_status || len < 2) return false; + out->status_code = rd_be16(data); + out->has_status = true; + } else if (code == DHCPV6_OPT_DNS_SERVERS) { + if (len < 16 || (len % 16) != 0) return false; int n = (int)(len / 16); if (n > 2) n = 2; - for (int i = 0; i < n; i++) - memcpy(out->dns[i], opt + off + (uint32_t)i * 16u, 16); + for (int i = 0; i < n; i++) ipv6_cpy(out->dns[i], data + (uint32_t)i * 16u); out->has_dns = true; - } - - if (code == DHCPV6_OPT_NTP_SERVER && len >= 16){ - int n = (int)(len / 16); - if (n > 2) n = 2; - - for (int i = 0; i < n; i++) - memcpy(out->ntp[i], opt + off + (uint32_t)i * 16u, 16); - - out->has_ntp = true; - } - - if (code == DHCPV6_OPT_IA_NA && len >= 12){ - uint32_t iaid_net; - uint32_t t1_net; - uint32_t t2_net; - - memcpy(&iaid_net, opt + off + 0, 4); - memcpy(&t1_net, opt + off + 4, 4); - memcpy(&t2_net, opt + off + 8, 4); - - uint32_t iaid = bswap32(iaid_net); - if (expect_iaid && iaid != expect_iaid){ off += len; continue; } - - out->t1 = bswap32(t1_net); - out->t2 = bswap32(t2_net); - - uint32_t sub = off + 12; - uint32_t end = off + len; - - while (sub + 4 <= end){ - uint16_t sc_net; - uint16_t sl_net; - - memcpy(&sc_net, opt + sub + 0, 2); - memcpy(&sl_net, opt + sub + 2, 2); - - uint16_t sc = bswap16(sc_net); - uint16_t sl = bswap16(sl_net); - - sub += 4; - if (sub + sl > end) break; - - if (sc == DHCPV6_OPT_IAADDR && sl >= 24){ - memcpy(out->addr, opt + sub + 0, 16); - - uint32_t pref_net; - uint32_t valid_net; - - memcpy(&pref_net, opt + sub + 16, 4); - memcpy(&valid_net, opt + sub + 20, 4); - - out->preferred_lft = bswap32(pref_net); - out->valid_lft = bswap32(valid_net); - out->has_addr = true; - got_addr = true; - break; + } else if (code == DHCPV6_OPT_NTP_SERVER) { + uint32_t ntp_off = 0; + uint8_t count = 0; + while (ntp_off < len) { + if (ntp_off + 4 > len) return false; + uint16_t sc = rd_be16(data + ntp_off); + uint16_t sl = rd_be16(data + ntp_off + 2); + ntp_off += 4; + if (ntp_off + sl > len) return false; + if (sc == DHCPV6_NTP_SUBOPT_SERVER_ADDR && sl == 16 && count < 2) { + ipv6_cpy(out->ntp[count], data + ntp_off); + count++; } - - sub += sl; + ntp_off += sl; } - } - - if (code == DHCPV6_OPT_IA_PD && len >= 12){ - uint32_t sub = off + 12; - uint32_t end = off + len; - - while (sub + 4 <= end){ - uint16_t sc_net; - uint16_t sl_net; - - memcpy(&sc_net, opt + sub + 0, 2); - memcpy(&sl_net, opt + sub + 2, 2); - - uint16_t sc = bswap16(sc_net); - uint16_t sl = bswap16(sl_net); - - sub += 4; - if (sub + sl > end) break; - - if (sc == DHCPV6_OPT_IAPREFIX && sl >= 25){ - uint32_t pref_net; - uint32_t valid_net; - - memcpy(&pref_net, opt + sub + 0, 4); - memcpy(&valid_net, opt + sub + 4, 4); - - out->pd_preferred_lft = bswap32(pref_net); - out->pd_valid_lft = bswap32(valid_net); - out->pd_prefix_len = opt[sub + 8]; - - memcpy(out->pd_prefix, opt + sub + 9, 16); - - out->has_pd = true; - break; + out->has_ntp = count != 0; + } else if (code == DHCPV6_OPT_SOL_MAX_RT || code == DHCPV6_OPT_INF_MAX_RT || code == DHCPV6_OPT_INFORMATION_REFRESH_TIME) { + if (len != 4) return false; + uint32_t value = rd_be32(data); + if (code == DHCPV6_OPT_INFORMATION_REFRESH_TIME) { + out->has_info_refresh_time = true; + out->info_refresh_time = value; + } else if (value >= 60 && value <= 86400) { + if (code == DHCPV6_OPT_SOL_MAX_RT) { + out->has_sol_max_rt = true; + out->sol_max_rt = value; + } else { + out->has_inf_max_rt = true; + out->inf_max_rt = value; + } + } + } else if (code == DHCPV6_OPT_IA_NA) { + if (len < 12) return false; + uint32_t iaid = rd_be32(data); + if (!expect_iaid || iaid == expect_iaid) { + uint32_t t1 = rd_be32(data + 4); + uint32_t t2 = rd_be32(data + 8); + if (t1 && t2 && t1 > t2) { + off += len; + continue; + } + out->has_ia_na = true; + out->t1 = t1; + out->t2 = t2; + + uint32_t sub = 12; + while (sub < len) { + if (sub + 4 > len) return false; + uint16_t sc = rd_be16(data + sub); + uint16_t sl = rd_be16(data + sub + 2); + + sub += 4; + if (sub + sl > len) return false; + + if (sc == DHCPV6_OPT_STATUS_CODE) { + if (out->has_ia_status || sl < 2) return false; + out->ia_status_code = rd_be16(data + sub); + out->has_ia_status = true; + } else if (sc == DHCPV6_OPT_IAADDR && sl >= 24 && !out->has_addr) { + uint32_t preferred = rd_be32(data + sub + 16); + uint32_t valid = rd_be32(data + sub + 20); + if (preferred <= valid && !ipv6_is_unspecified(data + sub)) { + if (valid) { + ipv6_cpy(out->addr, data + sub); + out->preferred_lft = preferred; + out->valid_lft = valid; + out->has_addr = true; + } else if (!out->has_expired_addr) { + ipv6_cpy(out->expired_addr, data + sub); + out->has_expired_addr = true; + } + } + } + sub += sl; + } + } + } else if (code == DHCPV6_OPT_IA_PD){ + if (len < 12) return false; + uint32_t iaid = rd_be32(data); + uint32_t expect_pd_iaid = expect_iaid ? (expect_iaid ^ 0xA5A5A5A5u) : 0; + + if (!expect_pd_iaid || iaid == expect_pd_iaid) { + uint32_t sub = 12; + while (sub < len) { + if (sub + 4 > len) return false; + uint16_t sc = rd_be16(data + sub); + uint16_t sl = rd_be16(data + sub + 2); + + sub += 4; + if (sub + sl > len) return false; + + if (sc == DHCPV6_OPT_IAPREFIX && sl >= 25 && !out->has_pd) { + uint32_t preferred = rd_be32(data + sub); + uint32_t valid = rd_be32(data + sub + 4); + uint8_t prefix_len = data[sub + 8]; + + if (preferred <= valid && prefix_len <= 128 && valid) { + out->pd_preferred_lft = preferred; + out->pd_valid_lft = valid; + out->pd_prefix_len = prefix_len; + ipv6_cpy(out->pd_prefix, data + sub + 9); + + out->has_pd = true; + } + } + sub += sl; } - - sub += sl; } } off += len; } - return got_addr || out->has_pd || out->has_server_id; + return true; } bool dhcpv6_parse_message(const uint8_t*msg, uint32_t msg_len, uint32_t expect_xid24, uint32_t expect_iaid, dhcpv6_parsed_t*out){ diff --git a/kernel/networking/application_layer/dhcpv6.h b/kernel/networking/application_layer/dhcpv6.h index 9fb690a1..348cd3df 100644 --- a/kernel/networking/application_layer/dhcpv6.h +++ b/kernel/networking/application_layer/dhcpv6.h @@ -2,6 +2,7 @@ #include "types.h" #include "networking/interface_manager.h" +#include "networking/link_layer/link_utils.h" #ifdef __cplusplus extern "C" { @@ -34,18 +35,19 @@ enum { #define DHCPV6_OPT_NTP_SERVER 56 #define DHCPV6_OPT_IA_PD 25 #define DHCPV6_OPT_IAPREFIX 26 +#define DHCPV6_OPT_INFORMATION_REFRESH_TIME 32 +#define DHCPV6_OPT_SOL_MAX_RT 82 +#define DHCPV6_OPT_INF_MAX_RT 83 + +#define DHCPV6_NTP_SUBOPT_SERVER_ADDR 1 #define DHCPV6_MAX_SERVER_ID 128 +#define DHCPV6_MAX_CLIENT_ID 128 #define DHCPV6_MAX_MSG 512 -typedef enum { - DHCPV6K_SELECT = 0, - DHCPV6K_RENEW = 1, - DHCPV6K_REBIND = 2, - DHCPV6K_CONFIRM = 3, - DHCPV6K_RELEASE = 4, - DHCPV6K_DECLINE = 5 -} dhcpv6_req_kind; +#define DHCPV6_STATUS_SUCCESS 0 +#define DHCPV6_STATUS_NO_BINDING 3 +#define DHCPV6_STATUS_NOT_ON_LINK 4 typedef struct { uint8_t msg_type; @@ -55,8 +57,20 @@ typedef struct { uint16_t server_id_len; uint8_t server_id[DHCPV6_MAX_SERVER_ID]; + bool has_client_id; + uint16_t client_id_len; + uint8_t client_id[DHCPV6_MAX_CLIENT_ID]; + + bool has_status; + uint16_t status_code; + bool has_ia_status; + uint16_t ia_status_code; + + bool has_ia_na; bool has_addr; uint8_t addr[16]; + bool has_expired_addr; + uint8_t expired_addr[16]; uint32_t preferred_lft; uint32_t valid_lft; @@ -74,14 +88,20 @@ typedef struct { uint8_t pd_prefix_len; uint32_t pd_preferred_lft; uint32_t pd_valid_lft; + + bool has_sol_max_rt; + uint32_t sol_max_rt; + bool has_inf_max_rt; + uint32_t inf_max_rt; + bool has_info_refresh_time; + uint32_t info_refresh_time; } dhcpv6_parsed_t; uint32_t dhcpv6_make_xid24(uint32_t r32); -void dhcpv6_duid_ll_from_mac(uint8_t out_duid[10], const uint8_t mac[6]); -uint32_t dhcpv6_iaid_from_mac(const uint8_t mac[6]); +uint32_t dhcpv6_iaid_from_mac(const uint8_t mac[MAC_ADDR_LEN]); -bool dhcpv6_build_message(uint8_t* out, uint32_t out_cap, uint32_t* out_len, const net_runtime_opts_v6_t* rt, const uint8_t mac[6], uint8_t type, dhcpv6_req_kind kind, uint32_t xid24, bool want_address); +bool dhcpv6_build_message(uint8_t* out, uint32_t out_cap, uint32_t* out_len, const net_runtime_opts_v6_t* rt, const uint8_t mac[MAC_ADDR_LEN], uint8_t type, uint32_t xid24, uint16_t elapsed_cs, const uint8_t ia_addr[16]); bool dhcpv6_parse_message(const uint8_t *msg, uint32_t msg_len, uint32_t expect_xid24, uint32_t expect_iaid, dhcpv6_parsed_t *out); diff --git a/kernel/networking/application_layer/dhcpv6_daemon.c b/kernel/networking/application_layer/dhcpv6_daemon.c index 46333545..18777b2d 100644 --- a/kernel/networking/application_layer/dhcpv6_daemon.c +++ b/kernel/networking/application_layer/dhcpv6_daemon.c @@ -1,22 +1,23 @@ #include "dhcpv6_daemon.h" #include "std/memory.h" -#include "std/string.h" #include "syscalls/syscalls.h" -#include "process/scheduler.h" +#include "kernel_processes/kprocess_loader.h" +#include "exceptions/irq.h" #include "math/rng.h" +#include "random/random.h" #include "data/struct/linked_list.h" #include "networking/interface_manager.h" +#include "networking/link_layer/link_utils.h" #include "networking/network.h" #include "networking/application_layer/dhcpv6.h" -#include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv6_utils.h" -#include "networking/transport_layer/csocket_udp.h" - +#include "networking/transport_layer/csocket.h" +//TODO add dns cleaner (dhcpv6, rdnss) enum { DHCPV6_S_INIT = 0, DHCPV6_S_SOLICIT = 1, @@ -26,18 +27,21 @@ enum { DHCPV6_S_REBINDING = 5, DHCPV6_S_CONFIRMING = 6, DHCPV6_S_RELEASING = 7, - DHCPV6_S_DECLINING = 8 + DHCPV6_S_DECLINING = 8, + DHCPV6_S_INFO = 9 }; typedef struct { uint8_t ifindex; - uint8_t target_l3_id; - uint8_t bound_linklocal_l3_id; + l3_id_t target_l3_id; + l3_id_t bound_linklocal_l3_id; + uint32_t target_generation; + uint32_t bound_linklocal_generation; uint8_t last_gateway[16]; uint8_t last_gateway_ok; - uint8_t mac[6]; + uint8_t mac[MAC_ADDR_LEN]; uint8_t mac_ok; socket_handle_t sock; @@ -47,19 +51,52 @@ typedef struct { uint32_t retry_left_ms; uint32_t backoff_ms; - uint32_t t1_left_ms; - uint32_t t2_left_ms; - uint32_t lease_left_ms; - uint8_t last_state; + uint64_t t1_left_ms; + uint64_t t2_left_ms; + uint64_t lease_left_ms; + uint32_t last_tick_ms; + + uint32_t rx_fast_left_ms; + uint32_t sol_max_rt_ms; + uint32_t inf_max_rt_ms; + uint64_t exchange_elapsed_ms; + uint64_t info_refresh_left_ms; + + uint8_t offered_addr[16]; + uint8_t action_addr[16]; + uint8_t has_offer; + uint8_t has_action_addr; + uint8_t tx_tries; uint8_t done; } dhcpv6_bind_t; -#define DHCPV6_MAX_INFOREQ_TX 3 -#define DHCPV6_MAX_REQUEST_TX 3 -#define DHCPV6_MAX_OTHER_TX 5 - -static uint16_t g_dhcpv6_pid = 0xFFFF; +#define DHCPV6_SOL_TIMEOUT_MS 1000 +#define DHCPV6_SOL_MAX_RT_MS 3600000 +#define DHCPV6_REQ_TIMEOUT_MS 1000 +#define DHCPV6_REQ_MAX_RT_MS 30000 +#define DHCPV6_REQ_MAX_RC 10 +#define DHCPV6_CNF_TIMEOUT_MS 1000 +#define DHCPV6_CNF_MAX_RT_MS 4000 +#define DHCPV6_CNF_MAX_RD_MS 10000 +#define DHCPV6_REN_TIMEOUT_MS 10000 +#define DHCPV6_REN_MAX_RT_MS 600000 +#define DHCPV6_REB_TIMEOUT_MS 10000 +#define DHCPV6_REB_MAX_RT_MS 600000 +#define DHCPV6_INF_TIMEOUT_MS 1000 +#define DHCPV6_INF_MAX_RT_MS 3600000 +#define DHCPV6_REL_TIMEOUT_MS 1000 +#define DHCPV6_REL_MAX_RC 4 +#define DHCPV6_DEC_TIMEOUT_MS 1000 +#define DHCPV6_DEC_MAX_RC 4 +#define DHCPV6_IRT_DEFAULT_SEC 86400 +#define DHCPV6_IRT_MINIMUM_SEC 600 +#define DHCPV6_ACTIVE_TICK_MS 100 +#define DHCPV6_IDLE_TICK_MS 5000 + +static volatile uint8_t g_dhcpv6_running; +static volatile uint8_t g_dhcpv6_pending; +static volatile uint8_t g_dhcpv6_rekick; static rng_t g_dhcpv6_rng; static linked_list_t* g_dhcpv6_binds = NULL; @@ -67,87 +104,101 @@ static volatile bool g_force_renew_all = false; static volatile bool g_force_rebind_all = false; static volatile bool g_force_confirm_all = false; -static uint64_t g_force_release_mask = 0; -static uint64_t g_force_decline_mask = 0; - -uint16_t dhcpv6_get_pid() { return g_dhcpv6_pid; } -bool dhcpv6_is_running() { return g_dhcpv6_pid != 0xFFFF; } -void dhcpv6_set_pid(uint16_t pid) { g_dhcpv6_pid = pid; } - -void dhcpv6_force_renew_all() { g_force_renew_all = true; } -void dhcpv6_force_rebind_all() { g_force_rebind_all = true; } -void dhcpv6_force_confirm_all() { g_force_confirm_all = true; } +static uint32_t g_force_release[MAX_IPV6_L3_INTERFACES]; +static uint32_t g_force_decline[MAX_IPV6_L3_INTERFACES]; -static int l3id_to_bit(uint8_t l3_id) { - if (!l3_id) return -1; - if ((l3_id & 0x08) == 0) return -1; - - uint8_t ifx = (uint8_t)((l3_id >> 4) & 0x0F); - uint8_t sl = (uint8_t)(l3_id & 0x03); - int idx = ((int)(ifx - 1) * MAX_IPV6_PER_INTERFACE) + (int)sl; - - if (idx < 0 || idx >= 64) return -1; - return idx; +void dhcpv6_force_renew_all() { + irq_flags_t irq = irq_save_disable(); + g_force_renew_all = true; + irq_restore(irq); + dhcpv6_daemon_kick(); } - -void dhcpv6_force_release_l3(uint8_t l3_id) { - int b = l3id_to_bit(l3_id); - if (b < 0) return; - g_force_release_mask |= (1ull << (uint64_t)b); +void dhcpv6_force_rebind_all() { + irq_flags_t irq = irq_save_disable(); + g_force_rebind_all = true; + irq_restore(irq); + dhcpv6_daemon_kick(); } - -void dhcpv6_force_decline_l3(uint8_t l3_id) { - int b = l3id_to_bit(l3_id); - if (b < 0) return; - g_force_decline_mask |= (1ull << (uint64_t)b); +void dhcpv6_force_confirm_all() { + irq_flags_t irq = irq_save_disable(); + g_force_confirm_all = true; + irq_restore(irq); + dhcpv6_daemon_kick(); } -static void mcast_servers(uint8_t out_ip[16]) { - memset(out_ip, 0, 16); - out_ip[0] = 0xFF; - out_ip[1] = 0x02; - out_ip[14] = 0x01; - out_ip[15] = 0x02; +void dhcpv6_force_release_l3(l3_id_t l3_id) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!v6 || v6->cfg != IPV6_CFG_DHCPV6) return; + uint32_t index = (uint32_t)l3_id - MAX_IPV4_L3_INTERFACES - 1; + if (index >= MAX_IPV6_L3_INTERFACES) return; + irq_flags_t irq = irq_save_disable(); + g_force_release[index] = v6->generation; + irq_restore(irq); + dhcpv6_daemon_kick(); } -static uint32_t next_backoff_ms(dhcpv6_bind_t* b) { - if (!b) return 4000; - - if (!b->backoff_ms) b->backoff_ms = 4000; - else { - uint64_t d = (uint64_t)b->backoff_ms * 2u; - if (d > 64000u) d = 64000u; - b->backoff_ms = (uint32_t)d; - } - - uint32_t j = (uint32_t)(rng_next32(&g_dhcpv6_rng) % 2000u); - int64_t v = (int64_t)b->backoff_ms + (int64_t)((int32_t)j - 1000); - if (v < 1000) v = 1000; - - return (uint32_t)v; +void dhcpv6_force_decline_l3(l3_id_t l3_id) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!v6 || v6->cfg != IPV6_CFG_DHCPV6) return; + uint32_t index = (uint32_t)l3_id - MAX_IPV4_L3_INTERFACES - 1; + if (index >= MAX_IPV6_L3_INTERFACES) return; + irq_flags_t irq = irq_save_disable(); + g_force_decline[index] = v6->generation; + irq_restore(irq); + dhcpv6_daemon_kick(); } static void reset_backoff(dhcpv6_bind_t* b) { if (!b) return; b->backoff_ms = 0; b->retry_left_ms = 0; + b->rx_fast_left_ms = 0; + b->exchange_elapsed_ms = 0; + b->tx_tries = 0; +} + +static void start_exchange(l3_ipv6_interface_t* v6, dhcpv6_bind_t* b, uint8_t state) { + if (!v6 || !b) return; + v6->dhcpv6_state = state; + b->xid24 = dhcpv6_make_xid24(rng_next32(&g_dhcpv6_rng)); + reset_backoff(b); + + if (state == DHCPV6_S_SOLICIT || state == DHCPV6_S_CONFIRMING || state == DHCPV6_S_INFO) b->retry_left_ms = rng_next32(&g_dhcpv6_rng) % 1000; + if (state == DHCPV6_S_SOLICIT) { + b->has_offer = 0; + memset(b->offered_addr, 0, sizeof(b->offered_addr)); + v6->runtime_opts_v6.server_id_len = 0; + } } static void reset_lease_state(l3_ipv6_interface_t* v6, dhcpv6_bind_t* b) { if (v6) { + if (v6->cfg == IPV6_CFG_DHCPV6) { + if (!ipv6_is_unspecified(v6->ip) || v6->prefix_len || !ipv6_is_unspecified(v6->gateway)) { + l3_ipv6_update(v6->l3_id, (const uint8_t[16]){0}, 0, v6->gateway, IPV6_CFG_DHCPV6, v6->kind); + if (b) b->target_generation = v6->generation; + } + } v6->dhcpv6_state = DHCPV6_S_INIT; v6->runtime_opts_v6.server_id_len = 0; v6->runtime_opts_v6.lease = 0; v6->runtime_opts_v6.lease_start_time = 0; + v6->runtime_opts_v6.t1 = 0; + v6->runtime_opts_v6.t2 = 0; + memset(v6->runtime_opts_v6.ntp, 0, sizeof(v6->runtime_opts_v6.ntp)); + memset(v6->runtime_opts_v6.pd_prefix, 0, sizeof(v6->runtime_opts_v6.pd_prefix)); + v6->runtime_opts_v6.pd_prefix_len = 0; + v6->runtime_opts_v6.pd_preferred_lft = 0; + v6->runtime_opts_v6.pd_valid_lft = 0; } if (b) { b->t1_left_ms = 0; b->t2_left_ms = 0; b->lease_left_ms = 0; b->xid24 = 0; - b->last_state = 0xFF; - b->tx_tries = 0; reset_backoff(b); + b->has_offer = 0; + b->has_action_addr = 0; } } @@ -178,31 +229,27 @@ static void ensure_binds() { if (keep) { bool stateful = (t->cfg == IPV6_CFG_DHCPV6); - bool stateless = (t->cfg == IPV6_CFG_SLAAC && t->dhcpv6_stateless); + bool stateless = ((t->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && t->dhcpv6_stateless); if (!stateful && !stateless) keep = false; + else if (t->generation != b->target_generation) { + if (stateless && t->l2 == l2) b->target_generation = t->generation; + else keep = false; + } } - if (keep) if (!t->l2 || !t->l2->is_up) keep = false; + if (keep && (!t->l2 || !t->l2->is_up)) keep = false; l3_ipv6_interface_t* llv6 = NULL; if (keep) { llv6 = l3_ipv6_find_by_id(b->bound_linklocal_l3_id); - if (!llv6) keep = false; + if (!llv6 || llv6->generation != b->bound_linklocal_generation || !ipv6_l3_is_ready(llv6) || !ipv6_is_linklocal(llv6->ip)) keep = false; } - if (keep) if (llv6->cfg == IPV6_CFG_DISABLE) keep = false; - if (keep) if (llv6->dad_state != IPV6_DAD_OK) keep = false; - if (keep) if (!ipv6_is_linklocal(llv6->ip)) keep = false; - if (!keep) { - if (t) reset_lease_state(t, b); + if (t && t->generation == b->target_generation && t->cfg == IPV6_CFG_DHCPV6) reset_lease_state(t, b); dhcpv6_bind_t* rb = (dhcpv6_bind_t*)linked_list_remove(g_dhcpv6_binds, it); if (rb) { - if (rb->sock) { - socket_close_udp(rb->sock); - socket_destroy_udp(rb->sock); - rb->sock = 0; - } + if (rb->sock) close_socket(rb->sock); free_sized(rb, sizeof(*rb)); } } @@ -235,41 +282,39 @@ static void ensure_binds() { target = v6; break; } - if (v6->cfg == IPV6_CFG_SLAAC && v6->dhcpv6_stateless) { - target = v6; - break; - } + if (!target && (v6->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && v6->dhcpv6_stateless) target = v6; } if (!target) continue; - uint8_t ll_l3 = 0; - bool ll_ok = false; - + l3_ipv6_interface_t* llv6 = NULL; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (v6->dad_state != IPV6_DAD_OK) continue; + if (!ipv6_l3_is_ready(v6)) continue; if (!ipv6_is_linklocal(v6->ip)) continue; - ll_l3 = v6->l3_id; - ll_ok = true; + llv6 = v6; break; } - if (!ll_ok) continue; + if (!llv6) continue; - dhcpv6_bind_t* b = (dhcpv6_bind_t*)malloc(sizeof(*b)); + dhcpv6_bind_t* b = (dhcpv6_bind_t*)zalloc(sizeof(*b)); if (!b) continue; - memset(b, 0, sizeof(*b)); - b->ifindex = l2->ifindex; b->target_l3_id = target->l3_id; - b->bound_linklocal_l3_id = ll_l3; + b->bound_linklocal_l3_id = llv6->l3_id; + b->target_generation = target->generation; + b->bound_linklocal_generation = llv6->generation; + b->last_tick_ms = (uint32_t)get_time(); + b->sol_max_rt_ms = DHCPV6_SOL_MAX_RT_MS; + b->inf_max_rt_ms = DHCPV6_INF_MAX_RT_MS; const uint8_t* mac = network_get_mac(b->ifindex); - if (mac) { memcpy(b->mac, mac, 6); b->mac_ok = 1; } + if (mac) { + mac_copy(b->mac, mac); + b->mac_ok = 1; + } - b->sock = udp_socket_create(SOCKET_SERVER, g_dhcpv6_pid, NULL); + b->sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_NONBLOCK}); if (!b->sock) { free_sized(b, sizeof(*b)); continue; @@ -281,534 +326,580 @@ static void ensure_binds() { spec.ver = IP_VER6; spec.l3_id = b->bound_linklocal_l3_id; - if (socket_bind_udp_ex(b->sock, &spec, DHCPV6_CLIENT_PORT) != SOCK_OK) { - socket_destroy_udp(b->sock); - b->sock = 0; + if (bind_socket(b->sock, &spec, DHCPV6_CLIENT_PORT) != SOCK_OK) { + close_socket(b->sock); free_sized(b, sizeof(*b)); continue; } - uint8_t m[16]; - mcast_servers(m); - (void)l2_ipv6_mcast_join(b->ifindex, m); - linked_list_push_front(g_dhcpv6_binds, b); } } -static void fsm_once(dhcpv6_bind_t* b, uint32_t tick_ms) { +static void fsm_once(dhcpv6_bind_t* b, uint32_t tick_ms, bool force_renew, bool force_rebind, bool force_confirm) { if (!b || !b->mac_ok || !b->sock) return; if (b->done) return; l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(b->target_l3_id); - if (!v6) return; + if (!v6 || v6->generation != b->target_generation) return; + l3_ipv6_interface_t* llv6 = l3_ipv6_find_by_id(b->bound_linklocal_l3_id); + if (!llv6 || llv6->generation != b->bound_linklocal_generation) return; bool stateful = (v6->cfg == IPV6_CFG_DHCPV6); - bool stateless = (v6->cfg == IPV6_CFG_SLAAC && v6->dhcpv6_stateless); + bool stateless = ((v6->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && v6->dhcpv6_stateless); if (!stateful && !stateless) return; if (!(v6->kind & IPV6_ADDRK_GLOBAL)) return; - if (stateless && v6->dhcpv6_stateless_done) return; + if (stateless && v6->dhcpv6_state == DHCPV6_S_INFO && !b->xid24) start_exchange(v6, b, DHCPV6_S_INFO); + if (stateless && v6->dhcpv6_stateless_done) { + if (b->info_refresh_left_ms != UINT64_MAX) { + if (b->info_refresh_left_ms > tick_ms) b->info_refresh_left_ms -= tick_ms; + else b->info_refresh_left_ms = 0; + } + if (b->info_refresh_left_ms) return; + v6->dhcpv6_stateless_done = 0; + } - if (v6->runtime_opts_v6.lease != 0 && v6->runtime_opts_v6.lease_start_time != 0 && !ipv6_is_unspecified(v6->ip) && v6->dhcpv6_state == DHCPV6_S_INIT) { - uint32_t now_s = get_time(); - uint32_t start_s = v6->runtime_opts_v6.lease_start_time; - uint32_t elapsed_s = (now_s >= start_s) ? (now_s - start_s) : 0; + if (!v6->runtime_opts_v6.iaid) v6->runtime_opts_v6.iaid = dhcpv6_iaid_from_mac(b->mac); + if (!v6->runtime_opts_v6.iaid) v6->runtime_opts_v6.iaid = rng_next32(&g_dhcpv6_rng); + if (v6->runtime_opts_v6.lease && v6->runtime_opts_v6.lease_start_time && !ipv6_is_unspecified(v6->ip) && + (v6->dhcpv6_state == DHCPV6_S_INIT || v6->dhcpv6_state == DHCPV6_S_BOUND || v6->dhcpv6_state == DHCPV6_S_RENEWING || v6->dhcpv6_state == DHCPV6_S_REBINDING || v6->dhcpv6_state == DHCPV6_S_CONFIRMING)) { + uint64_t age_ms = get_time() - v6->runtime_opts_v6.lease_start_time; uint32_t lease_s = v6->runtime_opts_v6.lease; - if (elapsed_s >= lease_s) { - v6->runtime_opts_v6.lease = 0; - v6->runtime_opts_v6.lease_start_time = 0; - v6->dhcpv6_state = DHCPV6_S_INIT; - } else { - uint32_t left_s = lease_s - elapsed_s; - b->lease_left_ms = left_s * 1000u; + uint32_t t1_s = v6->runtime_opts_v6.t1; + uint32_t t2_s = v6->runtime_opts_v6.t2; - uint32_t t1_s = v6->runtime_opts_v6.t1; - uint32_t t2_s = v6->runtime_opts_v6.t2; + if (!t1_s) t1_s = lease_s == UINT32_MAX ? UINT32_MAX : lease_s / 2; + if (!t2_s) t2_s = lease_s == UINT32_MAX ? UINT32_MAX : (uint64_t)lease_s * 4 / 5; - if (!t1_s) t1_s = lease_s / 2; - if (!t2_s) t2_s = (lease_s / 8) * 7; + uint64_t lease_ms = lease_s == UINT32_MAX ? UINT64_MAX : (uint64_t)lease_s * 1000; + uint64_t t1_ms = t1_s == UINT32_MAX ? UINT64_MAX : (uint64_t)t1_s * 1000; + uint64_t t2_ms = t2_s == UINT32_MAX ? UINT64_MAX : (uint64_t)t2_s * 1000; - if (elapsed_s >= t1_s) b->t1_left_ms = 0; - else b->t1_left_ms = (t1_s - elapsed_s) * 1000u; + if (lease_ms != UINT64_MAX && age_ms >= lease_ms) reset_lease_state(v6, b); + else { + if (lease_ms == UINT64_MAX) b->lease_left_ms = UINT64_MAX; + else b->lease_left_ms = lease_ms - age_ms; - if (elapsed_s >= t2_s) b->t2_left_ms = 0; - else b->t2_left_ms = (t2_s - elapsed_s) * 1000u; + if (t1_ms == UINT64_MAX) b->t1_left_ms = UINT64_MAX; + else if (age_ms >= t1_ms) b->t1_left_ms = 0; + else b->t1_left_ms = t1_ms - age_ms; - v6->dhcpv6_state = DHCPV6_S_BOUND; - reset_backoff(b); + if (t2_ms == UINT64_MAX) b->t2_left_ms = UINT64_MAX; + else if (age_ms >= t2_ms) b->t2_left_ms = 0; + else b->t2_left_ms = t2_ms - age_ms; + + if (v6->dhcpv6_state == DHCPV6_S_INIT) v6->dhcpv6_state = DHCPV6_S_BOUND; } } + uint8_t state = v6->dhcpv6_state; if (b->retry_left_ms > tick_ms) b->retry_left_ms -= tick_ms; else b->retry_left_ms = 0; + if (b->rx_fast_left_ms > tick_ms) b->rx_fast_left_ms -= tick_ms; + else b->rx_fast_left_ms = 0; + if (state != DHCPV6_S_INIT && state != DHCPV6_S_BOUND) b->exchange_elapsed_ms += tick_ms; - if (v6->dhcpv6_state == DHCPV6_S_BOUND) { - if (b->t1_left_ms > tick_ms) b->t1_left_ms -= tick_ms; - else b->t1_left_ms = 0; - if (b->t2_left_ms > tick_ms) b->t2_left_ms -= tick_ms; - else b->t2_left_ms = 0; - if (b->lease_left_ms > tick_ms) b->lease_left_ms -= tick_ms; - else b->lease_left_ms = 0; - } - - if (!v6->runtime_opts_v6.iaid) v6->runtime_opts_v6.iaid = dhcpv6_iaid_from_mac(b->mac); - if (!v6->runtime_opts_v6.iaid) v6->runtime_opts_v6.iaid = rng_next32(&g_dhcpv6_rng); - - int bit = l3id_to_bit(v6->l3_id); + uint32_t force_index = (uint32_t)v6->l3_id - MAX_IPV4_L3_INTERFACES - 1; bool do_release = false; bool do_decline = false; + if (force_index < MAX_IPV6_L3_INTERFACES) { + irq_flags_t irq = irq_save_disable(); + do_release = g_force_release[force_index] == v6->generation; + do_decline = g_force_decline[force_index] == v6->generation; + g_force_release[force_index] = 0; + g_force_decline[force_index] = 0; + irq_restore(irq); + } - if (bit >= 0) { - uint64_t m = (1ull << (uint64_t)bit); - - if (g_force_release_mask & m) { - g_force_release_mask &= ~m; - do_release = true; + if (stateful && do_release) { + if (v6->runtime_opts_v6.server_id_len && !ipv6_is_unspecified(v6->ip)) { + ipv6_cpy(b->action_addr, v6->ip); + b->has_action_addr = 1; + l3_ipv6_update(v6->l3_id, (const uint8_t[16]){0}, 0, v6->gateway, IPV6_CFG_DHCPV6, v6->kind); + b->target_generation = v6->generation; + start_exchange(v6, b, DHCPV6_S_RELEASING); + } else { + reset_lease_state(v6, b); + b->done = 1; + return; } - - if (g_force_decline_mask & m) { - g_force_decline_mask &= ~m; - do_decline = true; + } else if (stateful && do_decline) { + if (v6->runtime_opts_v6.server_id_len && !ipv6_is_unspecified(v6->ip)) { + ipv6_cpy(b->action_addr, v6->ip); + b->has_action_addr = 1; + l3_ipv6_update(v6->l3_id, (const uint8_t[16]){0}, 0, v6->gateway, IPV6_CFG_DHCPV6, v6->kind); + b->target_generation = v6->generation; + start_exchange(v6, b, DHCPV6_S_DECLINING); + } else { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); } - } - - if (do_release) { - v6->dhcpv6_state = DHCPV6_S_RELEASING; - b->retry_left_ms = 0; - reset_backoff(b); - } else if (do_decline) { - v6->dhcpv6_state = DHCPV6_S_DECLINING; - b->retry_left_ms = 0; - reset_backoff(b); - } - - if (g_force_confirm_all) { - v6->dhcpv6_state = DHCPV6_S_CONFIRMING; - b->retry_left_ms = 0; - reset_backoff(b); - } else if (g_force_rebind_all) { - v6->dhcpv6_state = DHCPV6_S_REBINDING; - b->retry_left_ms = 0; - reset_backoff(b); - } else if (g_force_renew_all) { - v6->dhcpv6_state = DHCPV6_S_RENEWING; - b->retry_left_ms = 0; - reset_backoff(b); - } + } else if (stateful && force_confirm && !ipv6_is_unspecified(v6->ip)) start_exchange(v6, b, DHCPV6_S_CONFIRMING); + else if (stateful && force_rebind && !ipv6_is_unspecified(v6->ip)) start_exchange(v6, b, DHCPV6_S_REBINDING); + else if (stateful && force_renew && v6->runtime_opts_v6.server_id_len && !ipv6_is_unspecified(v6->ip)) start_exchange(v6, b, DHCPV6_S_RENEWING); - if (v6->gateway[0] || v6->gateway[1]) { + if (stateful && !ipv6_is_unspecified(v6->ip) && !ipv6_is_unspecified(v6->gateway)) { if (!b->last_gateway_ok) { ipv6_cpy(b->last_gateway, v6->gateway); b->last_gateway_ok = 1; } else if (ipv6_cmp(b->last_gateway, v6->gateway) != 0) { ipv6_cpy(b->last_gateway, v6->gateway); - v6->dhcpv6_state = DHCPV6_S_CONFIRMING; - b->retry_left_ms = 0; - reset_backoff(b); + start_exchange(v6, b, DHCPV6_S_CONFIRMING); } } - if (v6->dhcpv6_state == DHCPV6_S_INIT) { - if (stateless) { - uint8_t zero16[16] = {0}; - int has_dns = (memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); + if (stateful && v6->runtime_opts_v6.lease && !b->lease_left_ms) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + } + + if (b->xid24) { + for (int packets = 0; packets < 4; packets++) { + uint8_t rx[DHCPV6_MAX_MSG]; + net_l4_endpoint src; + memset(&src, 0, sizeof(src)); + int64_t r = receive_from_socket(b->sock, rx, sizeof(rx), &src); + if (r <= 0) break; + if (src.port != DHCPV6_SERVER_PORT || r < 4) continue; + + dhcpv6_parsed_t p; + if (!dhcpv6_parse_message(rx, r, b->xid24, v6->runtime_opts_v6.iaid, &p)) continue; + if (!p.has_server_id || !p.has_client_id || p.client_id_len != 10) continue; + + uint8_t duid[10]; + wr_be16(duid, 3); + wr_be16(duid + 2, 1); + mac_copy(duid + 4, b->mac); + if (memcmp(duid, p.client_id, sizeof(duid)) != 0) continue; + + if (p.has_sol_max_rt) b->sol_max_rt_ms = p.sol_max_rt * 1000; + if (p.has_inf_max_rt) b->inf_max_rt_ms = p.inf_max_rt * 1000; + + state = v6->dhcpv6_state; + bool success = (!p.has_status || p.status_code == DHCPV6_STATUS_SUCCESS) && (!p.has_ia_status || p.ia_status_code == DHCPV6_STATUS_SUCCESS); + bool no_binding = (p.has_status && p.status_code == DHCPV6_STATUS_NO_BINDING) || (p.has_ia_status && p.ia_status_code == DHCPV6_STATUS_NO_BINDING); + bool not_on_link = (p.has_status && p.status_code == DHCPV6_STATUS_NOT_ON_LINK) || (p.has_ia_status && p.ia_status_code == DHCPV6_STATUS_NOT_ON_LINK); + + if (state == DHCPV6_S_SOLICIT) { + if (p.msg_type != DHCPV6_MSG_ADVERTISE || !success || !p.has_ia_na || !p.has_addr || !p.valid_lft) continue; + v6->runtime_opts_v6.server_id_len = p.server_id_len; + memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); + if (p.has_pd) { + ipv6_cpy(v6->runtime_opts_v6.pd_prefix, p.pd_prefix); + v6->runtime_opts_v6.pd_prefix_len = p.pd_prefix_len; + v6->runtime_opts_v6.pd_preferred_lft = p.pd_preferred_lft; + v6->runtime_opts_v6.pd_valid_lft = p.pd_valid_lft; + } + ipv6_cpy(b->offered_addr, p.addr); + b->has_offer = 1; + start_exchange(v6, b, DHCPV6_S_REQUEST); + return; + } + + if (p.msg_type != DHCPV6_MSG_REPLY) continue; + if (state == DHCPV6_S_REQUEST || state == DHCPV6_S_RENEWING || state == DHCPV6_S_RELEASING || state == DHCPV6_S_DECLINING) { + + if (!v6->runtime_opts_v6.server_id_len || p.server_id_len != v6->runtime_opts_v6.server_id_len || memcmp(p.server_id, v6->runtime_opts_v6.server_id, p.server_id_len) != 0) continue; + } - if (has_dns) { + if (state == DHCPV6_S_INFO) { + if (!stateless || !success) continue; + v6->runtime_opts_v6.server_id_len = p.server_id_len; + memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); + memset(v6->runtime_opts_v6.ntp, 0, sizeof(v6->runtime_opts_v6.ntp)); + if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); + if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); + + uint32_t refresh_s = p.has_info_refresh_time ? p.info_refresh_time : DHCPV6_IRT_DEFAULT_SEC; + if (refresh_s != UINT32_MAX && refresh_s < DHCPV6_IRT_MINIMUM_SEC) refresh_s = DHCPV6_IRT_MINIMUM_SEC; + if (refresh_s == UINT32_MAX) b->info_refresh_left_ms = UINT64_MAX; + else b->info_refresh_left_ms = (uint64_t)refresh_s * 1000; v6->dhcpv6_stateless_done = 1; - b->retry_left_ms = 0; + v6->dhcpv6_state = DHCPV6_S_INIT; + b->xid24 = 0; reset_backoff(b); return; } - v6->dhcpv6_state = DHCPV6_S_SOLICIT; - b->retry_left_ms = 0; - reset_backoff(b); - return; - } + if (state == DHCPV6_S_CONFIRMING) { + if (!p.has_status) continue; + if (p.status_code == DHCPV6_STATUS_SUCCESS) { + v6->dhcpv6_state = DHCPV6_S_BOUND; + b->xid24 = 0; + reset_backoff(b); + return; + } + if (p.status_code == DHCPV6_STATUS_NOT_ON_LINK) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + return; + } + continue; + } - v6->dhcpv6_state = DHCPV6_S_SOLICIT; - b->retry_left_ms = 0; - reset_backoff(b); - return; - } + if (state == DHCPV6_S_RELEASING) { + if (!success && !no_binding) continue; + reset_lease_state(v6, b); + b->done = 1; + return; + } + if (state == DHCPV6_S_DECLINING) { + if (!success && !no_binding) continue; + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + return; + } + if (state == DHCPV6_S_REQUEST && not_on_link) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + return; + } + if (state == DHCPV6_S_REQUEST && p.has_expired_addr) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + return; + } + if ((state == DHCPV6_S_RENEWING || state == DHCPV6_S_REBINDING) && p.has_expired_addr && !ipv6_is_unspecified(v6->ip) && ipv6_cmp(p.expired_addr, v6->ip) == 0) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + return; + } + if ((state == DHCPV6_S_RENEWING || state == DHCPV6_S_REBINDING) && no_binding) { + v6->runtime_opts_v6.server_id_len = p.server_id_len; + memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); + if (!ipv6_is_unspecified(v6->ip)) { + ipv6_cpy(b->offered_addr, v6->ip); + b->has_offer = 1; + } + start_exchange(v6, b, DHCPV6_S_REQUEST); + return; + } + if (!success) continue; + if (state != DHCPV6_S_REQUEST && state != DHCPV6_S_RENEWING && state != DHCPV6_S_REBINDING) continue; + if (!p.has_ia_na || !p.has_addr || !p.valid_lft) continue; + + uint32_t lease_s = p.valid_lft; + uint32_t preferred_s = p.preferred_lft ? p.preferred_lft : lease_s; + uint32_t t1_s = p.t1; + uint32_t t2_s = p.t2; + if (!t1_s) t1_s = preferred_s == UINT32_MAX ? UINT32_MAX : preferred_s / 2; + if (!t2_s) t2_s = preferred_s == UINT32_MAX ? UINT32_MAX : (uint64_t)preferred_s * 4 / 5; + if (t1_s && t2_s && t1_s > t2_s) continue; + + uint8_t gw[16]; + + if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) ipv6_cpy(gw, v6->gateway); + else memset(gw, 0, 16); + + if (!l3_ipv6_update(v6->l3_id, p.addr, 128, gw, IPV6_CFG_DHCPV6, v6->kind)) continue; + b->target_generation = v6->generation; + + v6->runtime_opts_v6.server_id_len = p.server_id_len; + memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); + + memset(v6->runtime_opts_v6.ntp, 0, sizeof(v6->runtime_opts_v6.ntp)); + if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); + if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); + if (p.has_pd) { + ipv6_cpy(v6->runtime_opts_v6.pd_prefix, p.pd_prefix); + v6->runtime_opts_v6.pd_prefix_len = p.pd_prefix_len; + v6->runtime_opts_v6.pd_preferred_lft = p.pd_preferred_lft; + v6->runtime_opts_v6.pd_valid_lft = p.pd_valid_lft; + } - if (v6->dhcpv6_state == DHCPV6_S_BOUND) { - if (!b->lease_left_ms && v6->runtime_opts_v6.lease) { - v6->dhcpv6_state = DHCPV6_S_SOLICIT; - v6->runtime_opts_v6.server_id_len = 0; - reset_backoff(b); - return; - } + v6->runtime_opts_v6.t1 = t1_s; + v6->runtime_opts_v6.t2 = t2_s; + v6->runtime_opts_v6.lease = lease_s; + v6->runtime_opts_v6.lease_start_time = get_time(); - if (!b->t2_left_ms && b->lease_left_ms) { - v6->dhcpv6_state = DHCPV6_S_REBINDING; - b->retry_left_ms = 0; - reset_backoff(b); - return; - } + b->t1_left_ms = t1_s == UINT32_MAX ? UINT64_MAX : (uint64_t)t1_s * 1000; + b->t2_left_ms = t2_s == UINT32_MAX ? UINT64_MAX : (uint64_t)t2_s * 1000; + b->lease_left_ms = lease_s == UINT32_MAX ? UINT64_MAX : (uint64_t)lease_s * 1000; + b->has_offer = 0; + b->has_action_addr = 0; - if (!b->t1_left_ms && b->lease_left_ms) { - v6->dhcpv6_state = DHCPV6_S_RENEWING; - b->retry_left_ms = 0; + v6->dhcpv6_state = DHCPV6_S_BOUND; + b->xid24 = 0; reset_backoff(b); return; } - - return; } - if (b->retry_left_ms) return; - if (b->last_state != (uint8_t)v6->dhcpv6_state) { - b->last_state = (uint8_t)v6->dhcpv6_state; - b->tx_tries = 0; + if (b->done) return; + if (v6->dhcpv6_state == DHCPV6_S_INIT) { + if (stateless) start_exchange(v6, b, DHCPV6_S_INFO); + else start_exchange(v6, b, DHCPV6_S_SOLICIT); } - - uint8_t type_peek = DHCPV6_MSG_SOLICIT; - - if (stateless) type_peek = DHCPV6_MSG_INFORMATION_REQUEST; - else if (v6->dhcpv6_state == DHCPV6_S_SOLICIT) type_peek = DHCPV6_MSG_SOLICIT; - else if (v6->dhcpv6_state == DHCPV6_S_REQUEST) type_peek = DHCPV6_MSG_REQUEST; - else if (v6->dhcpv6_state == DHCPV6_S_RENEWING) type_peek = DHCPV6_MSG_RENEW; - else if (v6->dhcpv6_state == DHCPV6_S_REBINDING) type_peek = DHCPV6_MSG_REBIND; - else if (v6->dhcpv6_state == DHCPV6_S_CONFIRMING) type_peek = DHCPV6_MSG_CONFIRM; - else if (v6->dhcpv6_state == DHCPV6_S_RELEASING) type_peek = DHCPV6_MSG_RELEASE; - else if (v6->dhcpv6_state == DHCPV6_S_DECLINING) type_peek = DHCPV6_MSG_DECLINE; - - uint8_t lim = DHCPV6_MAX_OTHER_TX; - if (type_peek == DHCPV6_MSG_INFORMATION_REQUEST) lim = DHCPV6_MAX_INFOREQ_TX; - else if (type_peek == DHCPV6_MSG_REQUEST) lim = DHCPV6_MAX_REQUEST_TX; - - if (b->tx_tries >= lim) { - uint8_t zero16[16] = {0}; - int has_dns =(memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); - - if (!has_dns) { - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) { - memcpy(v6->runtime_opts_v6.dns[0], v6->gateway, 16); - } - } - - if (stateless){ - v6->dhcpv6_stateless_done = 1; - v6->dhcpv6_state = DHCPV6_S_INIT; - b->retry_left_ms = 0; + if (v6->dhcpv6_state == DHCPV6_S_BOUND) { + if (!b->t2_left_ms && b->lease_left_ms) start_exchange(v6, b, DHCPV6_S_REBINDING); + else if (!b->t1_left_ms && b->lease_left_ms && v6->runtime_opts_v6.server_id_len) start_exchange(v6, b, DHCPV6_S_RENEWING); + else return; + } + if (v6->dhcpv6_state == DHCPV6_S_RENEWING && !b->t2_left_ms && b->lease_left_ms) start_exchange(v6, b, DHCPV6_S_REBINDING); + + state = v6->dhcpv6_state; + uint8_t max_tries = 0; + uint32_t max_duration = 0; + if (state == DHCPV6_S_REQUEST) max_tries = DHCPV6_REQ_MAX_RC; + else if (state == DHCPV6_S_CONFIRMING) max_duration = DHCPV6_CNF_MAX_RD_MS; + else if (state == DHCPV6_S_RELEASING) max_tries = DHCPV6_REL_MAX_RC; + else if (state == DHCPV6_S_DECLINING) max_tries = DHCPV6_DEC_MAX_RC; + + if ((!b->retry_left_ms && max_tries && b->tx_tries >= max_tries) || (max_duration && b->exchange_elapsed_ms >= max_duration)) { + if (state == DHCPV6_S_REQUEST) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); + } else if (state == DHCPV6_S_CONFIRMING) { + v6->dhcpv6_state = DHCPV6_S_BOUND; + b->xid24 = 0; reset_backoff(b); - return; + } else if (state == DHCPV6_S_RELEASING) { + reset_lease_state(v6, b); + b->done = 1; + } else if (state == DHCPV6_S_DECLINING) { + reset_lease_state(v6, b); + start_exchange(v6, b, DHCPV6_S_SOLICIT); } - - b->done = 1; - v6->dhcpv6_state = DHCPV6_S_INIT; - reset_backoff(b); return; } + if (b->retry_left_ms) return; + + uint8_t type = 0; + if (state == DHCPV6_S_SOLICIT) type = DHCPV6_MSG_SOLICIT; + else if (state == DHCPV6_S_REQUEST) type = DHCPV6_MSG_REQUEST; + else if (state == DHCPV6_S_RENEWING) type = DHCPV6_MSG_RENEW; + else if (state == DHCPV6_S_REBINDING) type = DHCPV6_MSG_REBIND; + else if (state == DHCPV6_S_CONFIRMING) type = DHCPV6_MSG_CONFIRM; + else if (state == DHCPV6_S_RELEASING) type = DHCPV6_MSG_RELEASE; + else if (state == DHCPV6_S_DECLINING) type = DHCPV6_MSG_DECLINE; + else if (state == DHCPV6_S_INFO) type = DHCPV6_MSG_INFORMATION_REQUEST; + if (!type) return; + + const uint8_t* ia_addr = NULL; + if (state == DHCPV6_S_REQUEST && b->has_offer) ia_addr = b->offered_addr; + else if ((state == DHCPV6_S_RENEWING || state == DHCPV6_S_REBINDING || state == DHCPV6_S_CONFIRMING) && !ipv6_is_unspecified(v6->ip)) ia_addr = v6->ip; + else if ((state == DHCPV6_S_RELEASING || state == DHCPV6_S_DECLINING) && b->has_action_addr) ia_addr = b->action_addr; + + uint64_t elapsed_cs = b->exchange_elapsed_ms / 10; + uint16_t elapsed = elapsed_cs > UINT16_MAX ? UINT16_MAX : elapsed_cs; uint8_t msg[DHCPV6_MAX_MSG]; uint32_t msg_len = 0; - - b->xid24 = dhcpv6_make_xid24(rng_next32(&g_dhcpv6_rng)); - - uint8_t type = DHCPV6_MSG_SOLICIT; - dhcpv6_req_kind kind = DHCPV6K_SELECT; - - if (stateless) { - type = DHCPV6_MSG_INFORMATION_REQUEST; - kind = DHCPV6K_SELECT; - } else if (v6->dhcpv6_state == DHCPV6_S_SOLICIT) { - type = DHCPV6_MSG_SOLICIT; - kind = DHCPV6K_SELECT; - } else if (v6->dhcpv6_state == DHCPV6_S_REQUEST) { - type = DHCPV6_MSG_REQUEST; - kind = DHCPV6K_SELECT; - } else if (v6->dhcpv6_state == DHCPV6_S_RENEWING) { - type = DHCPV6_MSG_RENEW; - kind = DHCPV6K_RENEW; - } else if (v6->dhcpv6_state == DHCPV6_S_REBINDING) { - type = DHCPV6_MSG_REBIND; - kind = DHCPV6K_REBIND; - } else if (v6->dhcpv6_state == DHCPV6_S_CONFIRMING) { - type = DHCPV6_MSG_CONFIRM; - kind = DHCPV6K_CONFIRM; - } else if (v6->dhcpv6_state == DHCPV6_S_RELEASING) { - type = DHCPV6_MSG_RELEASE; - kind = DHCPV6K_RELEASE; - } else if (v6->dhcpv6_state == DHCPV6_S_DECLINING) { - type = DHCPV6_MSG_DECLINE; - kind = DHCPV6K_DECLINE; - } else { - type = DHCPV6_MSG_SOLICIT; - kind = DHCPV6K_SELECT; - v6->dhcpv6_state = DHCPV6_S_SOLICIT; - } - bool want_addr = !stateless; - - if (!dhcpv6_build_message(msg, sizeof(msg), &msg_len, &v6->runtime_opts_v6, b->mac, type, kind, b->xid24, want_addr)) { - b->retry_left_ms = next_backoff_ms(b); + if (!dhcpv6_build_message(msg, sizeof(msg), &msg_len, &v6->runtime_opts_v6, b->mac, type, b->xid24, elapsed, ia_addr)) { + b->retry_left_ms = 1000; return; } net_l4_endpoint dst; memset(&dst, 0, sizeof(dst)); dst.ver = IP_VER6; - mcast_servers(dst.ip); + ipv6_make_multicast(2, IPV6_MCAST_DHCPV6_SERVERS, NULL, dst.ip); dst.port = DHCPV6_SERVER_PORT; - - (void)socket_sendto_udp_ex(b->sock, DST_ENDPOINT, &dst, 0, (const void*)msg, (uint64_t)msg_len); + if (send_to_socket(b->sock, &dst, msg, msg_len) == msg_len) b->rx_fast_left_ms = 1000; b->tx_tries++; - uint8_t rx[DHCPV6_MAX_MSG]; - uint32_t rx_len = 0; - - net_l4_endpoint src; - memset(&src, 0, sizeof(src)); - - bool got = false; - uint32_t waited = 0; - - while (waited < 250) { - int64_t r = socket_recvfrom_udp_ex(b->sock, rx, sizeof(rx), &src); - if (r > 0) { - if (src.port != DHCPV6_SERVER_PORT) { - msleep(50); - waited += 50; - continue; - } - rx_len = (uint32_t)r; - got = true; - break; - } - msleep(50); - waited += 50; + uint32_t initial_rt = 0; + uint32_t max_rt = 0; + if (state == DHCPV6_S_SOLICIT) { + initial_rt = DHCPV6_SOL_TIMEOUT_MS; + max_rt = b->sol_max_rt_ms ? b->sol_max_rt_ms : DHCPV6_SOL_MAX_RT_MS; + } else if (state == DHCPV6_S_REQUEST) { + initial_rt = DHCPV6_REQ_TIMEOUT_MS; + max_rt = DHCPV6_REQ_MAX_RT_MS; + } else if (state == DHCPV6_S_CONFIRMING) { + initial_rt = DHCPV6_CNF_TIMEOUT_MS; + max_rt = DHCPV6_CNF_MAX_RT_MS; + } else if (state == DHCPV6_S_RENEWING) { + initial_rt = DHCPV6_REN_TIMEOUT_MS; + max_rt = DHCPV6_REN_MAX_RT_MS; + } else if (state == DHCPV6_S_REBINDING) { + initial_rt = DHCPV6_REB_TIMEOUT_MS; + max_rt = DHCPV6_REB_MAX_RT_MS; + } else if (state == DHCPV6_S_INFO) { + initial_rt = DHCPV6_INF_TIMEOUT_MS; + max_rt = b->inf_max_rt_ms ? b->inf_max_rt_ms : DHCPV6_INF_MAX_RT_MS; + } else if (state == DHCPV6_S_RELEASING) initial_rt = DHCPV6_REL_TIMEOUT_MS; + else if (state == DHCPV6_S_DECLINING) initial_rt = DHCPV6_DEC_TIMEOUT_MS; + + uint32_t base = initial_rt; + if (b->backoff_ms) base = b->backoff_ms > UINT32_MAX / 2 ? UINT32_MAX : b->backoff_ms * 2; + + int32_t jitter = rng_next32(&g_dhcpv6_rng) % 20001; + jitter -= 10000; + int64_t next = base; + next += next * jitter / 100000; + if (next < 1) next = 1; + + if (max_rt && next > max_rt) { + jitter = rng_next32(&g_dhcpv6_rng) % 20001; + jitter -= 10000; + next = max_rt; + next += next * jitter / 100000; + if (next < 1) next = 1; } + if (next > UINT32_MAX) next = UINT32_MAX; + b->backoff_ms = next; + b->retry_left_ms = b->backoff_ms; +} - if (got && rx_len >= 4) { - uint32_t rid24 = ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | (uint32_t)rx[3]; - uint32_t expect = dhcpv6_make_xid24(b->xid24); - - if (rid24 == (expect & 0x00FFFFFFu)) { - dhcpv6_parsed_t p; - - if (dhcpv6_parse_message(rx, rx_len, expect, v6->runtime_opts_v6.iaid, &p)) { - if (p.msg_type == DHCPV6_MSG_ADVERTISE && v6->dhcpv6_state == DHCPV6_S_SOLICIT) { - if (p.has_server_id) { - v6->runtime_opts_v6.server_id_len = p.server_id_len; - if (p.server_id_len) memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); - } - - if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); - if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); - uint8_t zero16[16] = {0}; - int has_dns = (memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); - - if (!has_dns) { - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) { - memcpy(v6->runtime_opts_v6.dns[0], v6->gateway, 16); - } - } - - if (p.has_pd) { - ipv6_cpy(v6->runtime_opts_v6.pd_prefix, p.pd_prefix); - v6->runtime_opts_v6.pd_prefix_len = p.pd_prefix_len; - v6->runtime_opts_v6.pd_preferred_lft = p.pd_preferred_lft; - v6->runtime_opts_v6.pd_valid_lft = p.pd_valid_lft; - } - - if (p.t1) v6->runtime_opts_v6.t1 = p.t1; - if (p.t2) v6->runtime_opts_v6.t2 = p.t2; - - if (p.has_addr) { - uint8_t gw[16]; +int dhcpv6_daemon_entry(int argc, char* argv[]) { + (void)argc; + (void)argv; - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); + irq_flags_t irq = irq_save_disable(); + g_dhcpv6_pending = 0; + g_dhcpv6_running = 1; + g_dhcpv6_rekick = 0; + irq_restore(irq); - (void)l3_ipv6_update(v6->l3_id, p.addr, 128, gw, IPV6_CFG_DHCPV6, v6->kind); + rng_init_random(&g_dhcpv6_rng); - uint32_t lease_s = p.valid_lft; - v6->runtime_opts_v6.lease = lease_s; - v6->runtime_opts_v6.lease_start_time = get_time(); + for (;;) { + irq = irq_save_disable(); + bool force_renew = g_force_renew_all; + bool force_rebind = g_force_rebind_all; + bool force_confirm = g_force_confirm_all; + g_force_renew_all = false; + g_force_rebind_all = false; + g_force_confirm_all = false; + g_dhcpv6_rekick = 0; + irq_restore(irq); - uint32_t t1_s = v6->runtime_opts_v6.t1; - uint32_t t2_s = v6->runtime_opts_v6.t2; + ensure_binds(); - if (!t1_s) t1_s = lease_s / 2; - if (!t2_s) t2_s = (lease_s / 8) * 7; + if (g_dhcpv6_binds) { + for (linked_list_node_t* it = g_dhcpv6_binds->head; it; it = it->next) { + dhcpv6_bind_t* b = (dhcpv6_bind_t*)it->data; + if (!b) continue; + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - b->last_tick_ms; + b->last_tick_ms = now_ms; + fsm_once(b, elapsed_ms, force_renew, force_rebind, force_confirm); + } + } - b->t1_left_ms = t1_s * 1000u; - b->t2_left_ms = t2_s * 1000u; - b->lease_left_ms = lease_s * 1000u; - } - v6->dhcpv6_state = DHCPV6_S_REQUEST; - b->retry_left_ms = 0; - reset_backoff(b); - } else if (p.msg_type == DHCPV6_MSG_REPLY) { - if (stateless) { - if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); - if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); - - uint8_t zero16[16] = {0}; - int has_dns = (memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); - - if (!has_dns) { - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) { - memcpy(v6->runtime_opts_v6.dns[0], v6->gateway, 16); - } - } - - v6->dhcpv6_stateless_done = 1; - reset_backoff(b); - return; - } - if (v6->dhcpv6_state == DHCPV6_S_REQUEST || v6->dhcpv6_state == DHCPV6_S_RENEWING || v6->dhcpv6_state == DHCPV6_S_REBINDING || v6->dhcpv6_state == DHCPV6_S_CONFIRMING) { - if (p.has_server_id) { - v6->runtime_opts_v6.server_id_len = p.server_id_len; - if (p.server_id_len) memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); - } - - if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); - if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); - uint8_t zero16[16] = {0}; - int has_dns = (memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); - - if (!has_dns) { - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) { - memcpy(v6->runtime_opts_v6.dns[0], v6->gateway, 16); - } - } - - if (p.has_pd) { - ipv6_cpy(v6->runtime_opts_v6.pd_prefix, p.pd_prefix); - v6->runtime_opts_v6.pd_prefix_len = p.pd_prefix_len; - v6->runtime_opts_v6.pd_preferred_lft = p.pd_preferred_lft; - v6->runtime_opts_v6.pd_valid_lft = p.pd_valid_lft; - } - - if (p.t1) v6->runtime_opts_v6.t1 = p.t1; - if (p.t2) v6->runtime_opts_v6.t2 = p.t2; - - if (p.has_addr) { - uint8_t gw[16]; - - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); - - (void)l3_ipv6_update(v6->l3_id, p.addr, 128, gw, IPV6_CFG_DHCPV6, v6->kind); - - uint32_t lease_s = p.valid_lft; - v6->runtime_opts_v6.lease = lease_s; - v6->runtime_opts_v6.lease_start_time = get_time(); - - uint32_t t1_s = v6->runtime_opts_v6.t1; - uint32_t t2_s = v6->runtime_opts_v6.t2; - - if (!t1_s) t1_s = lease_s / 2; - if (!t2_s) t2_s = (lease_s / 8) * 7; - - b->t1_left_ms = t1_s * 1000u; - b->t2_left_ms = t2_s * 1000u; - b->lease_left_ms = lease_s * 1000u; - } - - v6->dhcpv6_state = DHCPV6_S_BOUND; - reset_backoff(b); - } else if (v6->dhcpv6_state == DHCPV6_S_SOLICIT) { - if (p.has_server_id) { - v6->runtime_opts_v6.server_id_len = p.server_id_len; - if (p.server_id_len) memcpy(v6->runtime_opts_v6.server_id, p.server_id, p.server_id_len); - } - - if (p.has_dns) memcpy(v6->runtime_opts_v6.dns, p.dns, sizeof(v6->runtime_opts_v6.dns)); - if (p.has_ntp) memcpy(v6->runtime_opts_v6.ntp, p.ntp, sizeof(v6->runtime_opts_v6.ntp)); - uint8_t zero16[16] = {0}; - int has_dns = (memcmp(v6->runtime_opts_v6.dns[0], zero16, 16) != 0) || (memcmp(v6->runtime_opts_v6.dns[1], zero16, 16) != 0); - - if (!has_dns) { - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) { - memcpy(v6->runtime_opts_v6.dns[0], v6->gateway, 16); - } - } - - if (p.has_pd) { - ipv6_cpy(v6->runtime_opts_v6.pd_prefix, p.pd_prefix); - v6->runtime_opts_v6.pd_prefix_len = p.pd_prefix_len; - v6->runtime_opts_v6.pd_preferred_lft = p.pd_preferred_lft; - v6->runtime_opts_v6.pd_valid_lft = p.pd_valid_lft; - } - - if (p.t1) v6->runtime_opts_v6.t1 = p.t1; - if (p.t2) v6->runtime_opts_v6.t2 = p.t2; - - if (p.has_addr) { - uint8_t gw[16]; - - if (!ipv6_is_unspecified(v6->gateway) && !ipv6_is_multicast(v6->gateway)) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); - - (void)l3_ipv6_update(v6->l3_id, p.addr, 128, gw, IPV6_CFG_DHCPV6, v6->kind); - - uint32_t lease_s = p.valid_lft; - v6->runtime_opts_v6.lease = lease_s; - v6->runtime_opts_v6.lease_start_time = get_time(); - - uint32_t t1_s = v6->runtime_opts_v6.t1; - uint32_t t2_s = v6->runtime_opts_v6.t2; - - if (!t1_s) t1_s = lease_s / 2; - if (!t2_s) t2_s = (lease_s / 8) * 7; - - b->t1_left_ms = t1_s * 1000u; - b->t2_left_ms = t2_s * 1000u; - b->lease_left_ms = lease_s * 1000u; - } - - v6->dhcpv6_state = DHCPV6_S_BOUND; - reset_backoff(b); - } else if (v6->dhcpv6_state == DHCPV6_S_RELEASING || v6->dhcpv6_state == DHCPV6_S_DECLINING) { - v6->runtime_opts_v6.lease = 0; - v6->runtime_opts_v6.lease_start_time = 0; - - b->t1_left_ms = 0; - b->t2_left_ms = 0; - b->lease_left_ms = 0; - - v6->dhcpv6_state = DHCPV6_S_INIT; - reset_backoff(b); - } + bool active_work = g_force_renew_all || g_force_rebind_all || g_force_confirm_all; + for (uint32_t i = 0; i < MAX_IPV6_L3_INTERFACES && !active_work; i++) active_work = g_force_release[i] || g_force_decline[i]; + if (!active_work && g_dhcpv6_binds) { + for (linked_list_node_t* it = g_dhcpv6_binds->head; it; it = it->next) { + dhcpv6_bind_t* b = (dhcpv6_bind_t*)it->data; + if (!b || b->done || !b->sock) continue; + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(b->target_l3_id); + if (!v6 || !v6->l2 || !v6->l2->is_up) continue; + + bool stateful = v6->cfg == IPV6_CFG_DHCPV6; + bool stateless = (v6->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && v6->dhcpv6_stateless; + if (stateful || (stateless && (!v6->dhcpv6_stateless_done || b->info_refresh_left_ms != UINT64_MAX))) { + active_work = true; + break; } } } - } - - b->retry_left_ms = next_backoff_ms(b); -} + if (!active_work) break; -int dhcpv6_daemon_entry(int argc, char* argv[]) { - (void)argc; - (void)argv; + uint32_t sleep_ms = g_dhcpv6_rekick ? DHCPV6_ACTIVE_TICK_MS : DHCPV6_IDLE_TICK_MS; + if (g_dhcpv6_binds) { + for (linked_list_node_t* it = g_dhcpv6_binds->head; it; it = it->next) { + dhcpv6_bind_t* b = (dhcpv6_bind_t*)it->data; + if (!b || b->done || !b->sock) continue; + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(b->target_l3_id); + if (!v6 || !v6->l2 || !v6->l2->is_up) continue; + + uint32_t wait_ms = DHCPV6_IDLE_TICK_MS; + uint8_t state = v6->dhcpv6_state; + if (state != DHCPV6_S_INIT && state != DHCPV6_S_BOUND) { + if (b->rx_fast_left_ms) wait_ms = b->rx_fast_left_ms < DHCPV6_ACTIVE_TICK_MS ? b->rx_fast_left_ms : DHCPV6_ACTIVE_TICK_MS; + else if (b->retry_left_ms < wait_ms) wait_ms = b->retry_left_ms; + if (!b->retry_left_ms) wait_ms = DHCPV6_ACTIVE_TICK_MS; + if (state == DHCPV6_S_RENEWING && b->t2_left_ms < wait_ms) wait_ms = b->t2_left_ms; + if (b->lease_left_ms < wait_ms) wait_ms = b->lease_left_ms; + } else if (state == DHCPV6_S_BOUND) { + uint64_t next = b->lease_left_ms; + if (b->t1_left_ms < next) next = b->t1_left_ms; + if (b->t2_left_ms < next) next = b->t2_left_ms; + if (next != UINT64_MAX && next < wait_ms) wait_ms = next; + } else wait_ms = DHCPV6_ACTIVE_TICK_MS; + + bool stateless = (v6->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && v6->dhcpv6_stateless; + if (stateless && v6->dhcpv6_stateless_done && b->info_refresh_left_ms != UINT64_MAX && b->info_refresh_left_ms < wait_ms) wait_ms = b->info_refresh_left_ms; + if (!wait_ms) wait_ms = DHCPV6_ACTIVE_TICK_MS; + if (wait_ms < sleep_ms) sleep_ms = wait_ms; + } + } + msleep(sleep_ms); + } - g_dhcpv6_pid = (uint16_t)get_current_proc_pid(); - dhcpv6_set_pid(g_dhcpv6_pid); + if (g_dhcpv6_binds) { + while (g_dhcpv6_binds->head) { + dhcpv6_bind_t* b = (dhcpv6_bind_t*)linked_list_pop_front(g_dhcpv6_binds); + if (!b) continue; + if (b->sock) close_socket(b->sock); + free_sized(b, sizeof(*b)); + } + linked_list_destroy(g_dhcpv6_binds); + g_dhcpv6_binds = NULL; + } - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&g_dhcpv6_rng, virt_timer); + irq = irq_save_disable(); + g_dhcpv6_running = 0; + bool rekick = g_dhcpv6_rekick != 0; + g_dhcpv6_rekick = 0; + irq_restore(irq); + if (rekick) dhcpv6_daemon_kick(); + return 0; +} - const uint32_t tick_ms = 250; +void dhcpv6_daemon_kick(void) { + irq_flags_t irq = irq_save_disable(); + if (g_dhcpv6_running || g_dhcpv6_pending) { + g_dhcpv6_rekick = 1; + irq_restore(irq); + return; + } + irq_restore(irq); - for (;;) { - ensure_binds(); + bool config_work = false; + uint8_t n = l2_interface_count(); + for (uint8_t i = 0; i < n && !config_work; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2 || !l2->is_up) continue; - if (g_dhcpv6_binds) { - for (linked_list_node_t* it = g_dhcpv6_binds->head; it; it = it->next) { - dhcpv6_bind_t* b = (dhcpv6_bind_t*)it->data; - if (b) fsm_once(b, tick_ms); + bool has_linklocal = false; + for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[s]; + if (ipv6_l3_is_ready(v6) && ipv6_is_linklocal(v6->ip)) { + has_linklocal = true; + break; } } + if (!has_linklocal) continue; - if (g_force_renew_all) g_force_renew_all = false; - if (g_force_rebind_all) g_force_rebind_all = false; - if (g_force_confirm_all) g_force_confirm_all = false; + for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[s]; + if (!v6 || !(v6->kind & IPV6_ADDRK_GLOBAL)) continue; + if (v6->cfg == IPV6_CFG_DHCPV6 || ((v6->cfg & IPV6_CFG_STATELESS) == IPV6_CFG_STATELESS && v6->dhcpv6_stateless && !v6->dhcpv6_stateless_done)) { + config_work = true; + break; + } + } + } + bool forced_work = g_force_renew_all || g_force_rebind_all || g_force_confirm_all; + for (uint32_t i = 0; i < MAX_IPV6_L3_INTERFACES && !forced_work; i++) forced_work = g_force_release[i] || g_force_decline[i]; + if (!config_work && !forced_work) return; + + irq = irq_save_disable(); + if (g_dhcpv6_running || g_dhcpv6_pending) { + g_dhcpv6_rekick = 1; + irq_restore(irq); + return; + } + g_dhcpv6_pending = 1; + irq_restore(irq); - msleep(tick_ms); + if (!create_kernel_process("dhcpv6_daemon", dhcpv6_daemon_entry, 0, 0)) { + irq = irq_save_disable(); + g_dhcpv6_pending = 0; + irq_restore(irq); } } \ No newline at end of file diff --git a/kernel/networking/application_layer/dhcpv6_daemon.h b/kernel/networking/application_layer/dhcpv6_daemon.h index 6a2c7675..4c34864a 100644 --- a/kernel/networking/application_layer/dhcpv6_daemon.h +++ b/kernel/networking/application_layer/dhcpv6_daemon.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" +#include "net/network_types.h" #ifdef __cplusplus extern "C" { @@ -8,16 +9,14 @@ extern "C" { int dhcpv6_daemon_entry(int argc, char* argv[]); -uint16_t dhcpv6_get_pid(); -bool dhcpv6_is_running(); -void dhcpv6_set_pid(uint16_t pid); +void dhcpv6_daemon_kick(void); void dhcpv6_force_renew_all(); void dhcpv6_force_rebind_all(); void dhcpv6_force_confirm_all(); -void dhcpv6_force_release_l3(uint8_t l3_id); -void dhcpv6_force_decline_l3(uint8_t l3_id); +void dhcpv6_force_release_l3(l3_id_t l3_id); +void dhcpv6_force_decline_l3(l3_id_t l3_id); #ifdef __cplusplus } diff --git a/kernel/networking/application_layer/dns/dns.c b/kernel/networking/application_layer/dns/dns.c index d9262a63..28962393 100644 --- a/kernel/networking/application_layer/dns/dns.c +++ b/kernel/networking/application_layer/dns/dns.c @@ -1,545 +1,298 @@ #include "dns.h" #include "dns_mdns.h" #include "dns_cache.h" -#include "std/std.h" -#include "math/math.h" -#include "process/scheduler.h" +#include "std/memory.h" #include "types.h" -#include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" #include "networking/interface_manager.h" -#include "dns_daemon.h" #include "syscalls/syscalls.h" #include "networking/transport_layer/trans_utils.h" +#include "random/random.h" #define MDNS_TIMEOUT_A_MS 500u #define MDNS_TIMEOUT_AAAA_MS 300u +#define DNS_MAX_CNAME_DEPTH 4 +#define DNS_QUERY_RECORDS 8 -static bool dns_is_local_name(const char* hostname) { - if (!hostname) return false; - uint32_t nlen = strlen(hostname); - if (nlen < 6u) return false; - if (strncmp(hostname +(nlen - 6u), ".local", 6) != 0)return false; - return true; -} +static dns_result_t perform_dns_query_once(socket_handle_t sock, const net_l4_endpoint *dns_srv, const char *name, dns_qtype_t qtype, uint32_t timeout_ms, dns_record_t *out_records, uint32_t max_records, uint32_t *out_count) { + if (out_count) *out_count = 0; + if (!sock) return DNS_ERR_SOCKET; + if (!dns_srv) return DNS_ERR_NO_DNS; + if (!name) return DNS_ERR_FORMAT; + if (!out_records && max_records) return DNS_ERR_FORMAT; -static dns_result_t dns_write_qname(uint8_t* buf, uint32_t buf_len, uint32_t* offset, const char* name) { - if (!buf || !offset || !name) return DNS_ERR_FORMAT; - uint32_t off = *offset; - if (off >= buf_len) return DNS_ERR_FORMAT; - uint32_t label_len = 0; - uint32_t label_pos = off; - buf[off++] = 0; - for (const char* p = name; *p; ++p) { - char c = *p; - if (c =='.') { - if (!label_len || label_len > 63u) return DNS_ERR_FORMAT; - buf[label_pos] = (uint8_t)label_len; - label_len = 0; - label_pos = off; - if (off >= buf_len) return DNS_ERR_FORMAT; - buf[off++] = 0; - continue; - } - if (label_len >= 63u) return DNS_ERR_FORMAT; - if (off >= buf_len) return DNS_ERR_FORMAT; - buf[off++] = (uint8_t)c; - label_len++; - } - if (!label_len || label_len > 63u) return DNS_ERR_FORMAT; - buf[label_pos] = (uint8_t)label_len; - if (off >= buf_len) return DNS_ERR_FORMAT; - buf[off++]= 0; - *offset = off; - return DNS_OK; -} - -static uint32_t skip_dns_name(const uint8_t* message, uint32_t message_len, uint32_t offset){ - if (offset >= message_len) return message_len + 1; - uint32_t cursor = offset; - while (cursor < message_len) { - uint8_t len = message[cursor++]; - if (len == 0) break; - if ((len & 0xC0) == 0xC0) { - if (cursor >= message_len) return message_len + 1; - cursor++; - break; - } - cursor += len; - if (cursor > message_len) return message_len + 1; - } - return cursor; -} - -static dns_result_t parse_dns_a_record(uint8_t* buffer, uint32_t buffer_len, uint16_t message_id, uint32_t* out_ip, uint32_t* out_ttl_s){ - if (buffer_len < 12) return DNS_ERR_FORMAT; - if (rd_be16(buffer+0) != message_id) return DNS_ERR_FORMAT; - uint16_t flags = rd_be16(buffer+2); - uint16_t question_count = rd_be16(buffer+4); - uint16_t answer_count = rd_be16(buffer+6); - if ((flags & 0x000F) == 3) return DNS_ERR_NXDOMAIN; - uint32_t offset = 12; - for (uint16_t i = 0; i < question_count; ++i){ - offset = skip_dns_name(buffer, buffer_len, offset); - if (offset + 4 > buffer_len) return DNS_ERR_FORMAT; - offset += 4; - } - for (uint16_t i = 0; i < answer_count; ++i){ - offset = skip_dns_name(buffer, buffer_len, offset); - if (offset + 10 > buffer_len) return DNS_ERR_FORMAT; - uint16_t type = rd_be16(buffer+offset+0); - uint16_t klass = rd_be16(buffer+offset+2); - uint32_t ttl_s = rd_be32(buffer+offset+4); - uint16_t rdlength = rd_be16(buffer+offset+8); - offset += 10; - if (offset + rdlength > buffer_len) return DNS_ERR_FORMAT; - if (type == 1 && klass == 1 && rdlength == 4){ - uint32_t ip_host = rd_be32(buffer+offset); - *out_ip = ip_host; - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; - } - offset += rdlength; - } - return DNS_ERR_NO_ANSWER; -} - -static dns_result_t parse_dns_aaaa_record(uint8_t* buffer, uint32_t buffer_len, uint16_t message_id, uint8_t out_ipv6[16], uint32_t* out_ttl_s){ - if (buffer_len < 12) return DNS_ERR_FORMAT; - if (rd_be16(buffer+0) != message_id) return DNS_ERR_FORMAT; - uint16_t flags = rd_be16(buffer+2); - uint16_t question_count = rd_be16(buffer+4); - uint16_t answer_count = rd_be16(buffer+6); - if ((flags & 0x000F) == 3) return DNS_ERR_NXDOMAIN; - uint32_t offset = 12; - for (uint16_t i = 0; i < question_count; ++i){ - offset = skip_dns_name(buffer, buffer_len, offset); - if (offset + 4 > buffer_len) return DNS_ERR_FORMAT; - offset += 4; - } - for (uint16_t i = 0; i < answer_count; ++i){ - offset = skip_dns_name(buffer, buffer_len, offset); - if (offset + 10 > buffer_len) return DNS_ERR_FORMAT; - uint16_t type = rd_be16(buffer+offset+0); - uint16_t klass = rd_be16(buffer+offset+2); - uint32_t ttl_s = rd_be32(buffer+offset+4); - uint16_t rdlength = rd_be16(buffer+offset+8); - offset += 10; - if (offset + rdlength > buffer_len) return DNS_ERR_FORMAT; - if (type == 28 && klass == 1 && rdlength == 16){ - memcpy(out_ipv6, buffer+offset, 16); - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; - } - offset += rdlength; - } - return DNS_ERR_NO_ANSWER; -} - -static dns_result_t perform_dns_query_once_a(socket_handle_t sock, const net_l4_endpoint* dns_srv, const char* name, uint32_t timeout_ms, uint32_t* out_ip, uint32_t* out_ttl_s){ uint8_t request_buffer[512]; - memset(request_buffer,0,sizeof(request_buffer)); rng_t rng; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&rng, virt_timer); + rng_init_random(&rng); uint16_t message_id = (uint16_t)(rng_next32(&rng) & 0xFFFF); - wr_be16(request_buffer+0, message_id); - wr_be16(request_buffer+2, 0x0100); - wr_be16(request_buffer+4, 1); - uint32_t offset = 12; - dns_result_t qnr=dns_write_qname(request_buffer, sizeof(request_buffer), &offset, name); - if (qnr != DNS_OK) return qnr; - - if (offset+ 4 > sizeof(request_buffer)) return DNS_ERR_FORMAT; - - wr_be16(request_buffer+offset+0, 1); - wr_be16(request_buffer+offset+2, 1); - offset += 4; + uint32_t request_len = dns_wire_build_query(request_buffer, sizeof(request_buffer), message_id, name, qtype, false); + if (!request_len) return DNS_ERR_FORMAT; net_l4_endpoint dst = *dns_srv; dst.port = 53; - int64_t sent = socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, request_buffer, offset); - if (sent < 0) return DNS_ERR_SEND; + int64_t sent = send_to_socket(sock, &dst, (void*)request_buffer, request_len); + if (sent != request_len) return DNS_ERR_SEND; uint32_t waited_ms = 0; while (waited_ms < timeout_ms){ uint8_t response_buffer[512]; net_l4_endpoint source; - int64_t received = socket_recvfrom_udp_ex(sock, response_buffer, sizeof(response_buffer), &source); - bool ok_src = false; - if (received > 0 && source.port == 53 && source.ver == dst.ver) { - if (dst.ver == IP_VER4) ok_src = (*(uint32_t*)source.ip == *(uint32_t*)dst.ip); - else if (dst.ver == IP_VER6) ok_src = (memcmp(source.ip, dst.ip, 16) == 0); - } - if (ok_src){ - uint32_t ip_host; - uint32_t ttl_s = 0; - dns_result_t pr = parse_dns_a_record(response_buffer, (uint32_t)received, message_id, &ip_host, &ttl_s); - if(pr == DNS_OK){ - *out_ip = ip_host; - if(out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; + int64_t received = receive_from_socket(sock, response_buffer, sizeof(response_buffer), &source); + if (received > 0 && net_ep_equal(&source, &dst)) { + uint32_t received_len = received; + dns_record_t parsed[DNS_QUERY_RECORDS]; + uint32_t parsed_count = 0; + uint16_t flags = 0; + if (!dns_wire_parse_records(response_buffer, received_len, true, message_id, parsed, DNS_QUERY_RECORDS, &parsed_count, &flags)|| + (flags & (DNS_FLAG_QR | DNS_OPCODE_MASK | DNS_FLAG_TC)) != DNS_FLAG_QR) { + msleep(50); + waited_ms += 50; + continue; + } + if ((flags & DNS_RCODE_MASK) == DNS_RCODE_NXDOMAIN) return DNS_ERR_NXDOMAIN; + if (flags & DNS_RCODE_MASK) return DNS_ERR_NO_ANSWER; + + uint32_t count = 0; + for (uint32_t i = 0; i < parsed_count; i++) { + if ((parsed[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if (qtype != DNS_TYPE_ANY && parsed[i].type != qtype && parsed[i].type != DNS_TYPE_CNAME) continue; + if (count < max_records) out_records[count] = parsed[i]; + count++; } - if (pr == DNS_ERR_NXDOMAIN) return pr; - } - msleep(50); - waited_ms += 50; - } - return DNS_ERR_TIMEOUT; -} - -static dns_result_t perform_dns_query_once_aaaa(socket_handle_t sock, const net_l4_endpoint* dns_srv, const char* name, uint32_t timeout_ms, uint8_t out_ipv6[16], uint32_t* out_ttl_s){ - uint8_t request_buffer[512]; - memset(request_buffer, 0, sizeof(request_buffer)); - rng_t rng; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&rng, virt_timer); - uint16_t message_id = (uint16_t)(rng_next32(&rng) & 0xFFFF); - wr_be16(request_buffer+0, message_id); - wr_be16(request_buffer+2, 0x0100); - wr_be16(request_buffer+4, 1); - uint32_t offset = 12; - dns_result_t qnr=dns_write_qname(request_buffer, sizeof(request_buffer), &offset, name); - if (qnr != DNS_OK) return qnr; - - if (offset+ 4 > sizeof(request_buffer)) return DNS_ERR_FORMAT; - - wr_be16(request_buffer+offset+0, 28); - wr_be16(request_buffer+offset+2, 1); - offset += 4; - - net_l4_endpoint dst = *dns_srv; - dst.port = 53; - - int64_t sent = socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, request_buffer, offset); - if (sent < 0) return DNS_ERR_SEND; - uint32_t waited_ms = 0; - while (waited_ms < timeout_ms){ - uint8_t response_buffer[512]; - net_l4_endpoint source; - int64_t received = socket_recvfrom_udp_ex(sock, response_buffer, sizeof(response_buffer), &source); - bool ok_src = false; - if (received > 0 && source.port == 53 && source.ver == dst.ver) { - if (dst.ver == IP_VER4) ok_src = (*(uint32_t*)source.ip == *(uint32_t*)dst.ip); - else if (dst.ver == IP_VER6) ok_src = (memcmp(source.ip, dst.ip, 16) == 0); - } - if (ok_src){ - uint32_t ttl_s = 0; - dns_result_t pr = parse_dns_aaaa_record(response_buffer, (uint32_t)received, message_id, out_ipv6, &ttl_s); - if (pr == DNS_OK){ - if (out_ttl_s) *out_ttl_s = ttl_s; + if (count) { + if (out_count) *out_count = count < max_records ? count : max_records; return DNS_OK; } - if (pr == DNS_ERR_NXDOMAIN) return pr; - } else { - msleep(50); - waited_ms += 50; + return DNS_ERR_NO_ANSWER; } + msleep(50); + waited_ms += 50; } return DNS_ERR_TIMEOUT; } -static bool pick_dns_on_l3(uint8_t l3_id, net_l4_endpoint* out_primary, net_l4_endpoint* out_secondary){ - if (l3_ipv4_find_by_id(l3_id)) { +static bool pick_dns(l3_id_t l3_id, net_l4_endpoint* out_primary, net_l4_endpoint* out_secondary) { + if (l3_id) { l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); - if (!v4) return false; - uint32_t p = v4->runtime_opts_v4.dns[0]; - uint32_t s = v4->runtime_opts_v4.dns[1]; - if (out_primary) { - memset(out_primary, 0, sizeof(*out_primary)); - out_primary->ver = IP_VER4; - memcpy(out_primary->ip, &p, 4); - } - if (out_secondary) { - memset(out_secondary, 0, sizeof(*out_secondary)); - out_secondary->ver = IP_VER4; - memcpy(out_secondary->ip, &s, 4); + if (v4) { + uint32_t primary = v4->runtime_opts_v4.dns[0]; + uint32_t secondary = v4->runtime_opts_v4.dns[1]; + if (out_primary) make_ep(&primary, 0, IP_VER4, out_primary); + if (out_secondary) make_ep(&secondary, 0, IP_VER4, out_secondary); + return primary || secondary; } - return (p != 0) || (s != 0); - } - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); - if (!v6) return false; - static const uint8_t z[16] = {0}; - const uint8_t* p6 = v6->runtime_opts_v6.dns[0]; - const uint8_t* s6 = v6->runtime_opts_v6.dns[1]; - bool hp = memcmp(p6, z, 16) != 0; - bool hs = memcmp(s6, z, 16) != 0; - if (out_primary) { - memset(out_primary, 0, sizeof(*out_primary)); - out_primary->ver = IP_VER6; - if (hp) memcpy(out_primary->ip, p6, 16); - } - if (out_secondary) { - memset(out_secondary, 0, sizeof(*out_secondary)); - out_secondary->ver = IP_VER6; - if (hs) memcpy(out_secondary->ip, s6, 16); + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!v6) return false; + bool have_primary = !ipv6_is_unspecified(v6->runtime_opts_v6.dns[0]); + bool have_secondary = !ipv6_is_unspecified(v6->runtime_opts_v6.dns[1]); + if (out_primary) make_ep(have_primary ? v6->runtime_opts_v6.dns[0] : NULL, 0, IP_VER6, out_primary); + if (out_secondary) make_ep(have_secondary ? v6->runtime_opts_v6.dns[1] : NULL, 0, IP_VER6, out_secondary); + return have_primary || have_secondary; } - return hp || hs; -} -static bool pick_dns_first_iface(uint8_t* out_l3, net_l4_endpoint* out_primary, net_l4_endpoint* out_secondary){ - uint8_t n = l2_interface_count(); - for (uint8_t i = 0; i < n; ++i){ + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { l2_interface_t* l2 = l2_interface_at(i); if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s){ + for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++){ l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4 || v4->mode == IPV4_CFG_DISABLED) continue; - - uint32_t p = v4->runtime_opts_v4.dns[0]; - uint32_t q = v4->runtime_opts_v4.dns[1]; - if (p || q){ - if (out_l3) *out_l3 = v4->l3_id; - if (out_primary) { - memset(out_primary, 0, sizeof(*out_primary)); - out_primary->ver = IP_VER4; - memcpy(out_primary->ip, &p, 4); - } - if (out_secondary) { - memset(out_secondary, 0, sizeof(*out_secondary)); - out_secondary->ver = IP_VER4; - memcpy(out_secondary->ip, &q, 4); - } - return true; - } + if (!ipv4_l3_is_active(v4)) continue; + + uint32_t primary = v4->runtime_opts_v4.dns[0]; + uint32_t secondary = v4->runtime_opts_v4.dns[1]; + if (!primary && !secondary) continue; + if (out_primary) make_ep(&primary, 0, IP_VER4, out_primary); + if (out_secondary) make_ep(&secondary, 0, IP_VER4, out_secondary); + return true; } for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6 || v6->cfg == IPV6_CFG_DISABLE) continue; - static const uint8_t z[16] = {0}; - bool hp = memcmp(v6->runtime_opts_v6.dns[0], z, 16) != 0; - bool hq = memcmp(v6->runtime_opts_v6.dns[1], z, 16) != 0; - if (hp || hq){ - if (out_l3) *out_l3 = v6->l3_id; - if (out_primary) { - memset(out_primary, 0, sizeof(*out_primary)); - out_primary->ver = IP_VER6; - if (hp) memcpy(out_primary->ip, v6->runtime_opts_v6.dns[0], 16); - } - if (out_secondary) { - memset(out_secondary, 0, sizeof(*out_secondary)); - out_secondary->ver = IP_VER6; - if (hq) memcpy(out_secondary->ip, v6->runtime_opts_v6.dns[1], 16); - } - return true; - } + if (!ipv6_l3_is_ready(v6)) continue; + bool have_primary = !ipv6_is_unspecified(v6->runtime_opts_v6.dns[0]); + bool have_secondary = !ipv6_is_unspecified(v6->runtime_opts_v6.dns[1]); + if (!have_primary && !have_secondary) continue; + if (out_primary) make_ep(have_primary ? v6->runtime_opts_v6.dns[0] : NULL, 0, IP_VER6, out_primary); + if (out_secondary) make_ep(have_secondary ? v6->runtime_opts_v6.dns[1] : NULL, 0, IP_VER6, out_secondary); + return true; } } return false; } -static bool dns_srv_is_zero(const net_l4_endpoint* e){ - if (!e) return true; - if (e->ver == IP_VER4) return *(const uint32_t*)e->ip == 0; - if (e->ver == IP_VER6) return ipv6_is_unspecified(e->ip); - return true; -} +static dns_result_t dns_query_selected(l3_id_t l3_id, const net_l4_endpoint* primary, const net_l4_endpoint* secondary, dns_server_sel_t which, const char* hostname, dns_qtype_t qtype, uint32_t timeout_ms, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count) { + if (out_count) *out_count = 0; + bool have_primary = primary && ((primary->ver == IP_VER4 && rd_be32(primary->ip)) || (primary->ver == IP_VER6 && !ipv6_is_unspecified(primary->ip))); + bool have_secondary = secondary && ((secondary->ver == IP_VER4 && rd_be32(secondary->ip)) || (secondary->ver == IP_VER6 && !ipv6_is_unspecified(secondary->ip))); + if (which == DNS_USE_PRIMARY && !have_primary) return DNS_ERR_NO_DNS; + if (which == DNS_USE_SECONDARY && !have_secondary) return DNS_ERR_NO_DNS; + if (which == DNS_USE_BOTH && !have_primary && !have_secondary) return DNS_ERR_NO_DNS; + socket_handle_t sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_NONBLOCK}); + if (!sock) return DNS_ERR_SOCKET; + if (l3_id) { + SockBindSpec spec = {.kind = BIND_L3, .l3_id = l3_id}; + if (bind_socket(sock, &spec, 0) != SOCK_OK) { + close_socket(sock); + return DNS_ERR_SOCKET; + } + } -static dns_result_t query_with_selection_a(const net_l4_endpoint* primary, const net_l4_endpoint* secondary, dns_server_sel_t which, const char* hostname, uint32_t timeout_ms, uint32_t* out_ip){ - if (which == DNS_USE_PRIMARY && dns_srv_is_zero(primary)) return DNS_ERR_NO_DNS; - if (which == DNS_USE_SECONDARY && dns_srv_is_zero(secondary)) return DNS_ERR_NO_DNS; - if (which == DNS_USE_BOTH && dns_srv_is_zero(primary) && dns_srv_is_zero(secondary)) return DNS_ERR_NO_DNS; - socket_handle_t sock = dns_socket_handle(); - if (sock == 0) return DNS_ERR_SOCKET; dns_result_t res = DNS_ERR_NO_DNS; - uint32_t ttl_s = 0; - if (which == DNS_USE_PRIMARY) res = perform_dns_query_once_a(sock, primary, hostname, timeout_ms, out_ip, &ttl_s); - else if (which == DNS_USE_SECONDARY) res = perform_dns_query_once_a(sock, secondary, hostname, timeout_ms, out_ip, &ttl_s); + if (which == DNS_USE_PRIMARY) res = perform_dns_query_once(sock, primary, hostname, qtype, timeout_ms, out_records, max_records, out_count); + else if (which == DNS_USE_SECONDARY) res = perform_dns_query_once(sock, secondary, hostname, qtype, timeout_ms, out_records, max_records, out_count); else { - const net_l4_endpoint* first = !dns_srv_is_zero(primary) ? primary : secondary; - const net_l4_endpoint* second = !dns_srv_is_zero(secondary) ? secondary : primary; - res = perform_dns_query_once_a(sock, first, hostname, timeout_ms, out_ip, &ttl_s); - if (res != DNS_OK && second && first != second) res = perform_dns_query_once_a(sock, second, hostname, timeout_ms, out_ip, &ttl_s); - } - if (res == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - uint8_t addr[16]; - memset(addr, 0, 16); - wr_be32(addr, *out_ip); - dns_cache_put_ip(hostname, 1, addr, ttl_ms); + const net_l4_endpoint* first = have_primary ? primary : secondary; + const net_l4_endpoint* second = have_secondary ? secondary : primary; + + res = perform_dns_query_once(sock, first, hostname, qtype, timeout_ms, out_records, max_records, out_count); + if (res != DNS_OK && second && first != second) res = perform_dns_query_once(sock, second, hostname, qtype, timeout_ms, out_records, max_records, out_count); } + + close_socket(sock); return res; } -static dns_result_t query_with_selection_aaaa(const net_l4_endpoint* primary, const net_l4_endpoint* secondary, dns_server_sel_t which, const char* hostname, uint32_t timeout_ms, uint8_t out_ipv6[16]){ - if (which == DNS_USE_PRIMARY && dns_srv_is_zero(primary)) return DNS_ERR_NO_DNS; - if (which == DNS_USE_SECONDARY && dns_srv_is_zero(secondary)) return DNS_ERR_NO_DNS; - if (which == DNS_USE_BOTH && dns_srv_is_zero(primary) && dns_srv_is_zero(secondary)) return DNS_ERR_NO_DNS; - socket_handle_t sock = dns_socket_handle(); - if (sock == 0) return DNS_ERR_SOCKET; - dns_result_t res = DNS_ERR_NO_DNS; - uint32_t ttl_s = 0; - if (which == DNS_USE_PRIMARY) res = perform_dns_query_once_aaaa(sock, primary, hostname, timeout_ms, out_ipv6, &ttl_s); - else if (which == DNS_USE_SECONDARY) res = perform_dns_query_once_aaaa(sock, secondary, hostname, timeout_ms, out_ipv6, &ttl_s); - else { - const net_l4_endpoint* first = !dns_srv_is_zero(primary) ? primary : secondary; - const net_l4_endpoint* second = !dns_srv_is_zero(secondary) ? secondary : primary; - res = perform_dns_query_once_aaaa(sock, first, hostname, timeout_ms, out_ipv6, &ttl_s); - if (res != DNS_OK && second && first != second) res = perform_dns_query_once_aaaa(sock, second, hostname, timeout_ms, out_ipv6, &ttl_s); - } - if (res == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 28, out_ipv6, ttl_ms); - } - return res; +dns_result_t dns_query(const char* hostname, dns_qtype_t qtype, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count, dns_server_sel_t which, uint32_t timeout_ms) { + if (out_count) *out_count = 0; + if (!hostname) return DNS_ERR_FORMAT; + if (!out_records && max_records) return DNS_ERR_FORMAT; + if (dns_wire_is_local_name(hostname)) return mdns_query(0, hostname, qtype, timeout_ms, out_records, max_records, out_count); + + net_l4_endpoint p, s; + if (!pick_dns(0, &p, &s)) return DNS_ERR_NO_DNS; + return dns_query_selected(0, &p, &s, which, hostname, qtype, timeout_ms, out_records, max_records, out_count); } -dns_result_t dns_resolve_a(const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms){ - if (!hostname || !out_ip) return DNS_ERR_FORMAT; - uint8_t cached[16]; - if (dns_cache_get_ip(hostname, 1, cached)) { - *out_ip = rd_be32(cached); - return DNS_OK; - } +dns_result_t dns_query_on_l3(l3_id_t l3_id, const char* hostname, dns_qtype_t qtype, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count, dns_server_sel_t which, uint32_t timeout_ms) { + if (out_count) *out_count = 0; + if (!hostname) return DNS_ERR_FORMAT; + if (!out_records && max_records) return DNS_ERR_FORMAT; + if (!l3_id) return DNS_ERR_NO_DNS; + if (dns_wire_is_local_name(hostname))return mdns_query(l3_id, hostname, qtype, timeout_ms, out_records, max_records, out_count); + net_l4_endpoint p,s; + if (!pick_dns(l3_id, &p, &s)) return DNS_ERR_NO_DNS; + return dns_query_selected(l3_id, &p, &s, which, hostname, qtype, timeout_ms, out_records, max_records, out_count); +} - bool is_local = dns_is_local_name(hostname); - - if (is_local){ - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_a(hostname, timeout_ms > MDNS_TIMEOUT_A_MS ? MDNS_TIMEOUT_A_MS : timeout_ms, out_ip, &ttl_s); - if (mr == DNS_OK) { - uint8_t a[16]; - memset(a, 0, 16); - wr_be32(a, *out_ip); - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 1, a, ttl_ms); - } - return mr; - } +static dns_result_t dns_resolve_ip_common(uint8_t use_l3, l3_id_t l3_id, const char* hostname, dns_qtype_t qtype, uint8_t out_addr[16], dns_server_sel_t which, uint32_t timeout_ms, uint32_t *out_ttl_s) { + if (!hostname || !out_addr) return DNS_ERR_FORMAT; + + char current[DNS_WIRE_MAX_NAME]; + if (!dns_wire_name_normalize(hostname, current, sizeof(current))) return DNS_ERR_FORMAT; + + uint32_t alias_ttl_s = UINT32_MAX; + uint32_t depth = 0; + while (depth <= DNS_MAX_CNAME_DEPTH) { + dns_record_t records[DNS_QUERY_RECORDS]; + uint32_t count = 0; + dns_result_t res; + if (use_l3) res = dns_query_on_l3(l3_id, current, qtype, records, DNS_QUERY_RECORDS, &count, which, timeout_ms); + else res = dns_query(current, qtype, records, DNS_QUERY_RECORDS, &count, which, timeout_ms); + if (res != DNS_OK) return res; + + for (;;) { + for (uint32_t i = 0; i < count; i++) { + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN || records[i].type != qtype) continue; + if (!dns_wire_name_equals(records[i].name, current)) continue; + if (qtype == DNS_TYPE_A) memcpy(out_addr, records[i].addr, 4); + else ipv6_cpy(out_addr, records[i].addr); + + uint32_t ttl_s = records[i].ttl_s; + uint32_t ttl_ms = ttl_s > (UINT32_MAX - 1) / 1000 ? UINT32_MAX - 1 : ttl_s * 1000; + dns_cache_put_ip(use_l3 ? l3_id : 0, current, qtype, out_addr, ttl_ms); + if (!dns_wire_name_equals(hostname, current)) { + if (ttl_s > alias_ttl_s) ttl_s = alias_ttl_s; + uint32_t alias_ms = ttl_s > (UINT32_MAX - 1) / 1000 ? UINT32_MAX - 1 : ttl_s * 1000; + dns_cache_put_ip(use_l3 ? l3_id : 0, hostname, qtype, out_addr, alias_ms); + } + if (out_ttl_s) *out_ttl_s = ttl_s; + return DNS_OK; + } - dns_result_t res = DNS_ERR_NO_DNS; - uint8_t l3 = 0; - net_l4_endpoint p, s; - if (pick_dns_first_iface(&l3, &p, &s)) res = query_with_selection_a(&p, &s, which, hostname, timeout_ms, out_ip); - - if (res != DNS_OK && is_local){ - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_a(hostname, timeout_ms > MDNS_TIMEOUT_A_MS ? MDNS_TIMEOUT_A_MS : timeout_ms, out_ip, &ttl_s); - if (mr == DNS_OK) { - uint8_t a[16]; - memset(a,0, 16); - wr_be32(a, *out_ip); - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 1, a, ttl_ms); - return DNS_OK; + char cname[DNS_WIRE_MAX_NAME]; + cname[0] = 0; + uint32_t cname_ttl = 0; + for (uint32_t i = 0; i < count; i++) { + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN || records[i].type != DNS_TYPE_CNAME) continue; + if (!records[i].target[0] || !dns_wire_name_equals(records[i].name, current)) continue; + strncpy(cname, records[i].target, sizeof(cname)); + cname[sizeof(cname) - 1] = 0; + cname_ttl = records[i].ttl_s; + break; + } + if (!cname[0]) return DNS_ERR_NO_ANSWER; + if (dns_wire_name_equals(cname, current) || depth == DNS_MAX_CNAME_DEPTH) return DNS_ERR_FORMAT; + if (cname_ttl < alias_ttl_s) alias_ttl_s = cname_ttl; + if (!dns_wire_name_normalize(cname, current, sizeof(current))) return DNS_ERR_FORMAT; + depth++; + bool have_next = false; + for (uint32_t i = 0; i < count; i++) { + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if ((records[i].type == qtype || records[i].type == DNS_TYPE_CNAME) && dns_wire_name_equals(records[i].name, current)) { + have_next = true; + break; + } + } + if (!have_next) break; } } - - return res; + return DNS_ERR_NO_ANSWER; } -dns_result_t dns_resolve_a_on_l3(uint8_t l3_id, const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms){ +dns_result_t dns_resolve_a(const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms) { if (!hostname || !out_ip) return DNS_ERR_FORMAT; uint8_t cached[16]; - if (dns_cache_get_ip(hostname, 1, cached)) { + if (dns_cache_get_ip(0, hostname, DNS_TYPE_A, cached)) { *out_ip = rd_be32(cached); return DNS_OK; } - bool is_local = dns_is_local_name(hostname); - - if (is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_a(hostname, timeout_ms > MDNS_TIMEOUT_A_MS ? MDNS_TIMEOUT_A_MS : timeout_ms, out_ip, &ttl_s); - if (mr == DNS_OK) { - uint8_t a[16]; - memset(a, 0, 16); - wr_be32(a, *out_ip); - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 1, a, ttl_ms); - } - return mr; - } - - dns_result_t res = DNS_ERR_NO_DNS; - net_l4_endpoint p, s; - - if (pick_dns_on_l3(l3_id, &p, &s)) res = query_with_selection_a(&p, &s, which, hostname, timeout_ms, out_ip); - - if (res != DNS_OK && is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_a(hostname, timeout_ms > MDNS_TIMEOUT_A_MS ? MDNS_TIMEOUT_A_MS : timeout_ms, out_ip, &ttl_s); - if (mr == DNS_OK) { - uint8_t a[16]; - memset(a, 0, 16); - wr_be32(a, *out_ip); - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 1, a, ttl_ms); - return DNS_OK; - } - } + uint8_t addr[16]; + uint32_t ttl_s = 0; + if (dns_wire_is_local_name(hostname) && timeout_ms > MDNS_TIMEOUT_A_MS) timeout_ms = MDNS_TIMEOUT_A_MS; + dns_result_t res = dns_resolve_ip_common(0, 0, hostname, DNS_TYPE_A, addr, which, timeout_ms, &ttl_s); + if (res != DNS_OK) return res; - return res; + *out_ip = rd_be32(addr); + return DNS_OK; } -dns_result_t dns_resolve_aaaa(const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms){ - if (!hostname || !out_ipv6) return DNS_ERR_FORMAT; - if (dns_cache_get_ip(hostname, 28, out_ipv6)) return DNS_OK; - - bool is_local = dns_is_local_name(hostname); - - if (is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_aaaa(hostname, timeout_ms > MDNS_TIMEOUT_AAAA_MS ? MDNS_TIMEOUT_AAAA_MS : timeout_ms, out_ipv6, &ttl_s); - if (mr == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 28, out_ipv6, ttl_ms); - } - return mr; +dns_result_t dns_resolve_a_on_l3(l3_id_t l3_id, const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms) { + if (!l3_id || !hostname || !out_ip) return DNS_ERR_FORMAT; + uint8_t cached[16]; + if (dns_cache_get_ip(l3_id, hostname, DNS_TYPE_A, cached)) { + *out_ip = rd_be32(cached); + return DNS_OK; } - dns_result_t res = DNS_ERR_NO_DNS; - uint8_t l3 = 0; - net_l4_endpoint p, s; - if (pick_dns_first_iface(&l3, &p, &s)) res = query_with_selection_aaaa(&p, &s, which, hostname, timeout_ms, out_ipv6); - - if (res != DNS_OK && is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_aaaa(hostname, timeout_ms > MDNS_TIMEOUT_AAAA_MS ? MDNS_TIMEOUT_AAAA_MS : timeout_ms, out_ipv6, &ttl_s); - if (mr == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 28, out_ipv6, ttl_ms); - return DNS_OK; - } - } + uint8_t addr[16]; + uint32_t ttl_s = 0; + if (dns_wire_is_local_name(hostname) && timeout_ms > MDNS_TIMEOUT_A_MS) timeout_ms = MDNS_TIMEOUT_A_MS; + dns_result_t res = dns_resolve_ip_common(1, l3_id, hostname, DNS_TYPE_A, addr, which, timeout_ms, &ttl_s); + if (res != DNS_OK) return res; - return res; + *out_ip = rd_be32(addr); + return DNS_OK; } -dns_result_t dns_resolve_aaaa_on_l3(uint8_t l3_id, const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms){ +dns_result_t dns_resolve_aaaa(const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms) { if (!hostname || !out_ipv6) return DNS_ERR_FORMAT; - if (dns_cache_get_ip(hostname, 28, out_ipv6)) return DNS_OK; - bool is_local = dns_is_local_name(hostname); - if (is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_aaaa(hostname, timeout_ms > MDNS_TIMEOUT_AAAA_MS ? MDNS_TIMEOUT_AAAA_MS : timeout_ms, out_ipv6, &ttl_s); - if (mr == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 28, out_ipv6, ttl_ms); - } - return mr; - } + if (dns_cache_get_ip(0, hostname, DNS_TYPE_AAAA, out_ipv6)) return DNS_OK; - dns_result_t res = DNS_ERR_NO_DNS; - net_l4_endpoint p, s; - if (pick_dns_on_l3(l3_id, &p, &s)) res = query_with_selection_aaaa(&p, &s, which, hostname, timeout_ms, out_ipv6); - - if (res != DNS_OK && is_local) { - uint32_t ttl_s = 0; - dns_result_t mr = mdns_resolve_aaaa(hostname, timeout_ms > MDNS_TIMEOUT_AAAA_MS ? MDNS_TIMEOUT_AAAA_MS : timeout_ms, out_ipv6, &ttl_s); - if (mr == DNS_OK) { - uint32_t ttl_ms = ttl_s > (0xFFFFFFFFu / 1000u) ? 0xFFFFFFFFu : ttl_s * 1000u; - dns_cache_put_ip(hostname, 28, out_ipv6, ttl_ms); - return DNS_OK; - } - } + uint32_t ttl_s = 0; + if (dns_wire_is_local_name(hostname) && timeout_ms > MDNS_TIMEOUT_AAAA_MS) timeout_ms = MDNS_TIMEOUT_AAAA_MS; + return dns_resolve_ip_common(0, 0, hostname, DNS_TYPE_AAAA, out_ipv6, which, timeout_ms, &ttl_s); +} - return res; +dns_result_t dns_resolve_aaaa_on_l3(l3_id_t l3_id, const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms) { + if (!l3_id || !hostname || !out_ipv6) return DNS_ERR_FORMAT; + if (dns_cache_get_ip(l3_id, hostname, DNS_TYPE_AAAA, out_ipv6)) return DNS_OK; + uint32_t ttl_s = 0; + if (dns_wire_is_local_name(hostname) && timeout_ms > MDNS_TIMEOUT_AAAA_MS) timeout_ms = MDNS_TIMEOUT_AAAA_MS; + return dns_resolve_ip_common(1, l3_id, hostname, DNS_TYPE_AAAA, out_ipv6, which, timeout_ms, &ttl_s); } diff --git a/kernel/networking/application_layer/dns/dns.h b/kernel/networking/application_layer/dns/dns.h index f20ddfe3..4e0d331e 100644 --- a/kernel/networking/application_layer/dns/dns.h +++ b/kernel/networking/application_layer/dns/dns.h @@ -1,5 +1,6 @@ #pragma once -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" +#include "networking/application_layer/dns/dns_wire.h" #ifdef __cplusplus extern "C" { #endif @@ -20,12 +21,14 @@ typedef enum { DNS_USE_BOTH = 2 } dns_server_sel_t; +typedef uint16_t dns_qtype_t; + +dns_result_t dns_query(const char* hostname, dns_qtype_t qtype, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count, dns_server_sel_t which, uint32_t timeout_ms); +dns_result_t dns_query_on_l3(l3_id_t l3_id, const char* hostname, dns_qtype_t qtype, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count, dns_server_sel_t which, uint32_t timeout_ms); dns_result_t dns_resolve_a(const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms); -dns_result_t dns_resolve_a_on_l3(uint8_t l3_id, const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms); +dns_result_t dns_resolve_a_on_l3(l3_id_t l3_id, const char* hostname, uint32_t* out_ip, dns_server_sel_t which, uint32_t timeout_ms); dns_result_t dns_resolve_aaaa(const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms); -dns_result_t dns_resolve_aaaa_on_l3(uint8_t l3_id, const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms); - -void dns_cache_tick(uint32_t ms); +dns_result_t dns_resolve_aaaa_on_l3(l3_id_t l3_id, const char* hostname, uint8_t out_ipv6[16], dns_server_sel_t which, uint32_t timeout_ms); #ifdef __cplusplus } diff --git a/kernel/networking/application_layer/dns/dns_cache.c b/kernel/networking/application_layer/dns/dns_cache.c index f08fca98..51ba83a1 100644 --- a/kernel/networking/application_layer/dns/dns_cache.c +++ b/kernel/networking/application_layer/dns/dns_cache.c @@ -1,12 +1,18 @@ #include "dns_cache.h" -#include "std/std.h" +#include "dns_wire.h" +#include "networking/interface_manager.h" +#include "std/memory.h" +#include "std/string.h" +#include "syscalls/syscalls.h" typedef struct { uint8_t in_use; - uint8_t rr_type; + uint16_t rr_type; + l3_id_t l3_id; + uint32_t l3_epoch; uint32_t name_len; char name[128]; - uint32_t ttl_ms; + uint64_t expire_ms; uint8_t addr[16]; } dns_cache_entry_t; @@ -20,80 +26,116 @@ static void dns_cache_ensure_init(void) { uint8_t a[16]; memset(a, 0, sizeof(a)); wr_be32(a, 0x7F000001u); - dns_cache_put_ip("localhost", 1, a, 0xFFFFFFFFu); + dns_cache_put_ip(0, "localhost", DNS_TYPE_A, a, 0xFFFFFFFFu); uint8_t v6[16]; memset(v6, 0, sizeof(v6)); v6[15] = 1; - dns_cache_put_ip("localhost", 28, v6, 0xFFFFFFFFu); + dns_cache_put_ip(0, "localhost", DNS_TYPE_AAAA, v6, 0xFFFFFFFFu); } -void dns_cache_put_ip(const char* name, uint8_t rr_type,const uint8_t addr[16], uint32_t ttl_ms) { +void dns_cache_put_ip(l3_id_t l3_id, const char* name, uint16_t rr_type,const uint8_t addr[16], uint32_t ttl_ms) { if (!name || !addr) return; - uint32_t nlen = strlen(name); + uint32_t epoch = l3_epoch(l3_id); + if (l3_id && !epoch) return; + dns_cache_ensure_init(); + char norm[128]; + if (!dns_wire_name_normalize(name, norm, sizeof(norm))) return; + uint32_t nlen = strlen(norm); if (!nlen) return; - if (nlen >= 128) return; if (!ttl_ms) return; + uint32_t addr_len = rr_type == DNS_TYPE_A ? 4 : 16; - if (nlen == 9u&& strncmp(name, "localhost", 9) == 0 && (rr_type == 1 || rr_type == 28))ttl_ms = 0xFFFFFFFFu; - + if (nlen == 9u&& memcmp(norm, "localhost", 9) == 0 && (rr_type == DNS_TYPE_A || rr_type == DNS_TYPE_AAAA)) { + for (int i = 0; i < 32; i++) { + if (g_dns_cache[i].in_use && g_dns_cache[i].rr_type == rr_type && g_dns_cache[i].l3_id == l3_id && g_dns_cache[i].l3_epoch == epoch && g_dns_cache[i].name_len == nlen && !memcmp(g_dns_cache[i].name, norm, nlen)) return; + } + ttl_ms = UINT32_MAX; + } int free_i = -1; + int evict_i = -1; + uint64_t evict_expire = UINT64_MAX; + uint64_t now = get_time(); for (int i = 0; i < 32; i++) { - if (!g_dns_cache[i].in_use) { + if (!g_dns_cache[i].in_use || (g_dns_cache[i].expire_ms != UINT64_MAX && now >= g_dns_cache[i].expire_ms)) { if (free_i < 0) free_i = i; continue; } + if (g_dns_cache[i].expire_ms < evict_expire) { + evict_expire = g_dns_cache[i].expire_ms; + evict_i = i; + } + if (g_dns_cache[i].l3_id != l3_id || g_dns_cache[i].l3_epoch != epoch) continue; if (g_dns_cache[i].rr_type != rr_type) continue; if (g_dns_cache[i].name_len != nlen) continue; - if (strncmp(g_dns_cache[i].name, name, (int)nlen) != 0) continue; - memcpy(g_dns_cache[i].addr, addr, 16); - g_dns_cache[i].ttl_ms = ttl_ms; + if (memcmp(g_dns_cache[i].name, norm, nlen) != 0) continue; + memset(g_dns_cache[i].addr, 0, sizeof(g_dns_cache[i].addr)); + memcpy(g_dns_cache[i].addr, addr, addr_len); + g_dns_cache[i].expire_ms = ttl_ms == 0xFFFFFFFFu ? UINT64_MAX : now + ttl_ms; return; } int idx = free_i; - if (idx < 0) idx = 0; + if (idx < 0) idx = evict_i; + if (idx < 0) return; memset(&g_dns_cache[idx], 0, sizeof(g_dns_cache[idx])); g_dns_cache[idx].in_use = 1; g_dns_cache[idx].rr_type = rr_type; + g_dns_cache[idx].l3_id = l3_id; + g_dns_cache[idx].l3_epoch = epoch; g_dns_cache[idx].name_len = nlen; - memcpy(g_dns_cache[idx].name, name, nlen); + memcpy(g_dns_cache[idx].name, norm, nlen); g_dns_cache[idx].name[nlen] = 0; - g_dns_cache[idx].ttl_ms = ttl_ms; - memcpy(g_dns_cache[idx].addr, addr, 16); + g_dns_cache[idx].expire_ms = ttl_ms == 0xFFFFFFFFu ? UINT64_MAX : now + ttl_ms; + memcpy(g_dns_cache[idx].addr, addr, addr_len); } -bool dns_cache_get_ip(const char* name, uint8_t rr_type, uint8_t out_addr[16]) { +void dns_cache_remove_ip(l3_id_t l3_id, const char* name, uint16_t rr_type) { + if (!name) return; + uint32_t epoch = l3_epoch(l3_id); + if (l3_id && !epoch) return; dns_cache_ensure_init(); - if (!name || !out_addr) return false; - uint32_t nlen = strlen(name); - if (!nlen) return false; - if (nlen >= 128) return false; + char norm[128]; + if (!dns_wire_name_normalize(name, norm, sizeof(norm))) return; + uint32_t nlen = strlen(norm); + if (nlen == 9 && memcmp(norm, "localhost", 9) == 0 && (rr_type == DNS_TYPE_A || rr_type == DNS_TYPE_AAAA)) return; for (int i = 0; i < 32; i++) { if (!g_dns_cache[i].in_use) continue; + if (g_dns_cache[i].l3_id != l3_id || g_dns_cache[i].l3_epoch != epoch) continue; if (g_dns_cache[i].rr_type != rr_type) continue; - if (g_dns_cache[i].ttl_ms == 0) continue; if (g_dns_cache[i].name_len != nlen) continue; - if (strncmp(g_dns_cache[i].name, name, (int)nlen) != 0) continue; - memcpy(out_addr, g_dns_cache[i].addr, 16); - return true; + if (memcmp(g_dns_cache[i].name, norm, nlen) != 0) continue; + memset(&g_dns_cache[i], 0, sizeof(g_dns_cache[i])); + return; } - return false; } -void dns_cache_tick(uint32_t ms) { +bool dns_cache_get_ip(l3_id_t l3_id, const char* name, uint16_t rr_type, uint8_t out_addr[16]) { + if (!name || !out_addr) return false; + uint32_t epoch = l3_epoch(l3_id); + if (l3_id && !epoch) return false; dns_cache_ensure_init(); + char norm[128]; + if (!dns_wire_name_normalize(name, norm, sizeof(norm))) return false; + uint32_t nlen = strlen(norm); + if (!nlen) return false; + if (nlen == 9 && !memcmp(norm, "localhost", 9)) { + l3_id = 0; + epoch = 0; + } + uint64_t now = get_time(); for (int i = 0; i < 32; i++) { if (!g_dns_cache[i].in_use) continue; - if (!g_dns_cache[i].ttl_ms) { - g_dns_cache[i].in_use = 0; - continue; - } - if (g_dns_cache[i].ttl_ms == 0xFFFFFFFFu) continue; - if (g_dns_cache[i].ttl_ms <= ms) { + if (g_dns_cache[i].expire_ms != UINT64_MAX && now >= g_dns_cache[i].expire_ms) { memset(&g_dns_cache[i], 0, sizeof(g_dns_cache[i])); - } else { - g_dns_cache[i].ttl_ms -= ms; + continue; } + if (g_dns_cache[i].l3_id != l3_id || g_dns_cache[i].l3_epoch != epoch) continue; + if (g_dns_cache[i].rr_type != rr_type) continue; + if (g_dns_cache[i].name_len != nlen) continue; + if (memcmp(g_dns_cache[i].name, norm, nlen) != 0) continue; + memcpy(out_addr, g_dns_cache[i].addr, 16); + return true; } + return false; } diff --git a/kernel/networking/application_layer/dns/dns_cache.h b/kernel/networking/application_layer/dns/dns_cache.h index 7c3c6fef..b7192d22 100644 --- a/kernel/networking/application_layer/dns/dns_cache.h +++ b/kernel/networking/application_layer/dns/dns_cache.h @@ -1,13 +1,14 @@ #pragma once #include "types.h" +#include "net/network_types.h" #ifdef __cplusplus extern "C" { #endif -bool dns_cache_get_ip(const char* name, uint8_t rr_type, uint8_t out_addr[16]); -void dns_cache_put_ip(const char* name, uint8_t rr_type,const uint8_t addr[16], uint32_t ttl_ms); -void dns_cache_tick(uint32_t ms); +bool dns_cache_get_ip(l3_id_t l3_id, const char* name, uint16_t rr_type, uint8_t out_addr[16]); +void dns_cache_put_ip(l3_id_t l3_id, const char* name, uint16_t rr_type,const uint8_t addr[16], uint32_t ttl_ms); +void dns_cache_remove_ip(l3_id_t l3_id, const char* name, uint16_t rr_type); #ifdef __cplusplus } diff --git a/kernel/networking/application_layer/dns/dns_daemon.c b/kernel/networking/application_layer/dns/dns_daemon.c index f0e55f19..801abc6f 100644 --- a/kernel/networking/application_layer/dns/dns_daemon.c +++ b/kernel/networking/application_layer/dns/dns_daemon.c @@ -1,85 +1,222 @@ #include "dns_daemon.h" -#include "mdns_responder.h" -#include "dns_cache.h" -#include "dns_sd.h" -#include "process/scheduler.h" +#include "mdns_internal.h" +#include "kernel_processes/kprocess_loader.h" +#include "exceptions/irq.h" #include "syscalls/syscalls.h" #include "net/socket_types.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" +#include "networking/interface_manager.h" #include "std/memory.h" -#include "net/socket_types.h" - -static uint16_t g_pid_dnsd = 0xFFFF; -static socket_handle_t g_sock = 0; - -static socket_handle_t g_sock_mdns4 = 0; -static socket_handle_t g_sock_mdns6 = 0; - -uint16_t dns_get_pid(void){ return g_pid_dnsd; } -bool dns_is_running(void){ return g_pid_dnsd != 0xFFFF; } -void dns_set_pid(uint16_t p){ g_pid_dnsd = p; } -socket_handle_t dns_socket_handle(void){ return g_sock; } - -socket_handle_t mdns_socket_handle_v4(void){ return g_sock_mdns4; } -socket_handle_t mdns_socket_handle_v6(void){ return g_sock_mdns6; } - -static socket_handle_t mdns_create_socket(ip_version_t ver, const void* group) { - SocketExtraOptions opt; - memset(&opt, 0, sizeof(opt)); - opt.flags = SOCK_OPT_MCAST_JOIN | SOCK_OPT_TTL; - opt.ttl = 255; - opt.mcast_ver = ver; - if(ver == IP_VER4) memcpy(opt.mcast_group, group, 4); - else memcpy(opt.mcast_group, group, 16); - - socket_handle_t s = udp_socket_create(SOCK_ROLE_SERVER, g_pid_dnsd, &opt); - if(!s) return 0; - - SockBindSpec spec; - memset(&spec, 0, sizeof(spec)); - spec.kind = BIND_ANY; - if(socket_bind_udp_ex(s, &spec, DNS_SD_MDNS_PORT) != SOCK_OK){ - socket_destroy_udp(s); - return 0; +static mdns_tx_target_t g_mdns[MAX_L3_INTERFACES]; +static uint16_t g_mdns_count = 0; +static volatile uint8_t g_dns_daemon_running; +static volatile uint8_t g_dns_daemon_pending; +static volatile uint8_t g_dns_dirty; + +static uint32_t mdns_sync(const uint8_t* group4, const uint8_t* group6) { + uint32_t changed = 0; + uint16_t out = 0; + for (uint16_t i = 0; i < g_mdns_count; i++) { + bool valid = false; + if (g_mdns[i].sock && g_mdns[i].ver == IP_VER4) { + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(g_mdns[i].l3_id); + valid = ipv4_l3_is_ready(v4) && !v4->is_localhost && v4->generation == g_mdns[i].l3_generation; + if (valid) { + valid = false; + for (uint8_t j = 0; j < v4->l2->ipv4_mcast_count; j++) { + if (v4->l2->ipv4_mcast[j] == DNS_MDNS_GROUP_V4) { + valid = true; + break; + } + } + } + } else if (g_mdns[i].sock && g_mdns[i].ver == IP_VER6) { + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(g_mdns[i].l3_id); + valid = ipv6_l3_is_ready(v6) && !v6->is_localhost && v6->generation == g_mdns[i].l3_generation; + if (valid) { + valid = false; + for (uint8_t j = 0; j < v6->l2->ipv6_mcast_count; j++) { + if (ipv6_cmp(v6->l2->ipv6_mcast[j], group6) == 0) { + valid = true; + break; + } + } + } + } + if (!valid) { + if (g_mdns[i].sock) close_socket(g_mdns[i].sock); + if (g_mdns[i].ifindex && g_mdns[i].ifindex <= MAX_L2_INTERFACES) changed |= 1u << (g_mdns[i].ifindex - 1u); + continue; + } + if (out != i) g_mdns[out] = g_mdns[i]; + out++; } + g_mdns_count = out; + + uint8_t n_if = l2_interface_count(); + for (uint8_t i = 0; i < n_if && g_mdns_count < MAX_L3_INTERFACES; i++) { + l2_interface_t *l2 = l2_interface_at(i); + if (!l2 || !l2->is_up) continue; + + for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE && g_mdns_count < MAX_L3_INTERFACES; j++) { + l3_ipv4_interface_t *v4 = l2->l3_v4[j]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost) continue; + bool have_socket = false; + for (uint16_t k = 0; k < g_mdns_count; k++) { + if (g_mdns[k].sock && g_mdns[k].ver == IP_VER4 && g_mdns[k].l3_id == v4->l3_id && g_mdns[k].l3_generation == v4->generation) { + have_socket = true; + break; + } + } + if (have_socket) continue; + + socket_handle_t s = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_TTL | SOCK_OPT_NONBLOCK, .ttl = 255}); + if (!s) continue; + + SockBindSpec spec = {.kind = BIND_L3, .ver = IP_VER4, .l3_id = v4->l3_id}; + net_l4_endpoint group; + make_ep(group4, DNS_MDNS_PORT, IP_VER4, &group); + + if (bind_socket(s, &spec, DNS_MDNS_PORT) != SOCK_OK || set_socket_option(s, SOCK_OPT_MCAST_JOIN, &group, sizeof(group)) != SOCK_OK) { + close_socket(s); + continue; + } + + g_mdns[g_mdns_count].sock = s; + g_mdns[g_mdns_count].ver = IP_VER4; + g_mdns[g_mdns_count].ifindex = l2->ifindex; + g_mdns[g_mdns_count].l3_id = v4->l3_id; + g_mdns[g_mdns_count].l3_generation = v4->generation; + memcpy(g_mdns[g_mdns_count].mcast_ip, group4, 4); + g_mdns_count++; + if (l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES) changed |= 1u << (l2->ifindex - 1u); + } - return s; + for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE && g_mdns_count < MAX_L3_INTERFACES; j++) { + l3_ipv6_interface_t *v6 = l2->l3_v6[j]; + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) continue; + bool have_socket = false; + for (uint16_t k = 0; k < g_mdns_count; k++) { + if (g_mdns[k].sock && g_mdns[k].ver == IP_VER6 && g_mdns[k].l3_id == v6->l3_id && g_mdns[k].l3_generation == v6->generation) { + have_socket = true; + break; + } + } + if (have_socket) continue; + + socket_handle_t s = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_TTL | SOCK_OPT_NONBLOCK, .ttl = 255}); + if (!s) continue; + + SockBindSpec spec = {.kind = BIND_L3, .ver = IP_VER6, .l3_id = v6->l3_id}; + net_l4_endpoint group; + make_ep(group6, DNS_MDNS_PORT, IP_VER6, &group); + if (bind_socket(s, &spec, DNS_MDNS_PORT) != SOCK_OK || set_socket_option(s, SOCK_OPT_MCAST_JOIN, &group, sizeof(group)) != SOCK_OK) { + close_socket(s); + continue; + } + + g_mdns[g_mdns_count].sock = s; + g_mdns[g_mdns_count].ver = IP_VER6; + g_mdns[g_mdns_count].ifindex = l2->ifindex; + g_mdns[g_mdns_count].l3_id = v6->l3_id; + g_mdns[g_mdns_count].l3_generation = v6->generation; + ipv6_cpy(g_mdns[g_mdns_count].mcast_ip, group6); + g_mdns_count++; + if (l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES) changed |= 1u << (l2->ifindex-1); + } + } + return changed; } -int dns_deamon_entry(int argc, char* argv[]){ +static int dns_deamon_entry(int argc, char* argv[]) { (void)argc; (void)argv; - dns_set_pid(get_current_proc_pid()); - g_sock = udp_socket_create(SOCK_ROLE_CLIENT, g_pid_dnsd, NULL); - uint32_t mdns_v4 = IPV4_MCAST_MDNS; + irq_flags_t irq = irq_save_disable(); + g_dns_daemon_running = 1; + g_dns_daemon_pending = 0; + g_dns_dirty = 0; + irq_restore(irq); + + uint32_t mdns_v4 = DNS_MDNS_GROUP_V4; + uint8_t mdns_v4_addr[4]; uint8_t mdns_v6[16]; + memcpy(mdns_v4_addr, &mdns_v4, 4); ipv6_make_multicast(0x02, IPV6_MCAST_MDNS, 0, mdns_v6); - g_sock_mdns4 = mdns_create_socket(IP_VER4, &mdns_v4); - g_sock_mdns6 = mdns_create_socket(IP_VER6, mdns_v6); + uint32_t changed = mdns_sync(mdns_v4_addr, mdns_v6); + if (changed) mdns_reprobe(changed); + + const uint32_t tick_ms = 100; + const uint32_t sync_ms = 1000; + uint64_t last_socket_sync_ms = (uint32_t)get_time(); + while (mdns_has_work()) { + irq = irq_save_disable(); + bool dirty = g_dns_dirty != 0; + g_dns_dirty = 0; + irq_restore(irq); + + uint32_t now_ms = (uint32_t)get_time(); + if (dirty || now_ms - last_socket_sync_ms >= sync_ms) { + last_socket_sync_ms = now_ms; + changed = mdns_sync(mdns_v4_addr, mdns_v6); + if (changed) mdns_reprobe(changed); + } - uint32_t tick_ms = 100; - for(;;) { - dns_cache_tick(tick_ms); uint8_t buf[900]; net_l4_endpoint src; - if (g_sock_mdns4) { - memset(&src, 0, sizeof(src)); - int64_t r4 = socket_recvfrom_udp_ex(g_sock_mdns4, buf, sizeof(buf), &src); - if(r4 > 0) mdns_responder_handle_query(g_sock_mdns4, IP_VER4, (const uint8_t*)&mdns_v4, buf, (uint32_t)r4, &src); + for (uint16_t sidx = 0; sidx < g_mdns_count; sidx++) { + socket_handle_t s = g_mdns[sidx].sock; + for (int i = 0; i < 64; i++) { + int64_t r = receive_from_socket(s, buf, sizeof(buf), &src); + if (r == SOCK_ERR_WOULDBLOCK) break; + if (r < 0) break; + if (!r) continue; + mdns_rx(s, g_mdns[sidx].l3_id, g_mdns[sidx].ver, g_mdns[sidx].mcast_ip, buf, (uint32_t)r, &src); + } } - if (g_sock_mdns6) { - memset(&src, 0, sizeof(src)); - int64_t r6 = socket_recvfrom_udp_ex(g_sock_mdns6, buf, sizeof(buf), &src); - if(r6 > 0) mdns_responder_handle_query(g_sock_mdns6, IP_VER6, mdns_v6, buf, (uint32_t)r6, &src); - } - - mdns_responder_tick(g_sock_mdns4,g_sock_mdns6,(const uint8_t*)&mdns_v4,mdns_v6); + mdns_responder_tick_multi(g_mdns, g_mdns_count); + if (!mdns_has_work()) break; msleep(tick_ms); } - return 1; -} \ No newline at end of file + + for (uint16_t i = 0; i < g_mdns_count; i++) close_socket(g_mdns[i].sock); + g_mdns_count = 0; + + irq = irq_save_disable(); + g_dns_daemon_running = 0; + g_dns_dirty = 0; + irq_restore(irq); + dns_daemon_kick(); + return 0; +} + +void dns_daemon_kick(void) { + if (!mdns_has_work()) return; + + irq_flags_t irq = irq_save_disable(); + if (g_dns_daemon_running) { + g_dns_dirty = 1; + irq_restore(irq); + return; + } + + if (g_dns_daemon_pending) { + irq_restore(irq); + return; + } + + g_dns_daemon_pending = 1; + irq_restore(irq); + + if (!create_kernel_process("dns_daemon", dns_deamon_entry, 0, 0)) { + irq = irq_save_disable(); + g_dns_daemon_pending = 0; + irq_restore(irq); + } +} diff --git a/kernel/networking/application_layer/dns/dns_daemon.h b/kernel/networking/application_layer/dns/dns_daemon.h index ca386c53..d760a106 100644 --- a/kernel/networking/application_layer/dns/dns_daemon.h +++ b/kernel/networking/application_layer/dns/dns_daemon.h @@ -1,21 +1,10 @@ #pragma once -#include "networking/transport_layer/csocket_udp.h" - -#define IPV4_MCAST_MDNS 0xE00000FBu #ifdef __cplusplus extern "C" { #endif -bool dns_is_running(void); -void dns_set_pid(uint16_t p); -socket_handle_t dns_socket_handle(void); - -socket_handle_t mdns_socket_handle_v4(void); -socket_handle_t mdns_socket_handle_v6(void); - -uint16_t dns_get_pid(void); -int dns_deamon_entry(int argc, char* argv[]); +void dns_daemon_kick(void); #ifdef __cplusplus } #endif diff --git a/kernel/networking/application_layer/dns/dns_mdns.c b/kernel/networking/application_layer/dns/dns_mdns.c index d4a29226..2ef6dc5d 100644 --- a/kernel/networking/application_layer/dns/dns_mdns.c +++ b/kernel/networking/application_layer/dns/dns_mdns.c @@ -1,246 +1,200 @@ #include "dns_mdns.h" -#include "dns_daemon.h" +#include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "std/std.h" +#include "networking/interface_manager.h" +#include "std/memory.h" #include "networking/transport_layer/trans_utils.h" -#define MDNS_PORT 5353 - -static uint32_t skip_dns_name(const uint8_t* message, uint32_t message_len, uint32_t offset) { - if (offset >= message_len) return message_len + 1; - uint32_t cursor = offset; - while (cursor < message_len) { - uint8_t len = message[cursor]; - cursor++; - if (len == 0) break; - if ((len & 0xC0) == 0xC0) { - if (cursor >= message_len) return message_len + 1; - cursor++; - break; - } - cursor +=len; - if (cursor > message_len) return message_len + 1; - } - return cursor; -} +#define MDNS_QUERY_TARGETS_MAX (MAX_L2_INTERFACES * 2) -static bool read_dns_name(const uint8_t* message, uint32_t message_len, uint32_t offset, char* out, uint32_t out_cap, uint32_t* consumed) { - if (!message) return false; - if (!out) return false; - if (!out_cap) return false; - if (offset >= message_len) return false; - - uint32_t cur = offset; - uint32_t out_len = 0; - uint32_t consumed_local = 0; - uint8_t jumped = 0; - uint32_t jumps = 0; - - for (;;){ - if (cur >= message_len) return false; - - uint8_t len = message[cur]; - if (len == 0) { - if (!jumped) consumed_local = cur - offset + 1; - if (out_len >= out_cap) return false; - out[out_len] = 0; - if (consumed) *consumed = consumed_local; - return true; - } - - if ((len & 0xC0) == 0xC0) { - if (cur + 1 >= message_len) return false; - uint16_t ptr = (uint16_t)(((uint16_t)(len & 0x3F) << 8) | (uint16_t)message[cur + 1]); - if (ptr >= message_len) return false; - if (!jumped) consumed_local = cur - offset + 2; - cur = ptr; - jumped = 1; - jumps++; - if (jumps> 16) return false; - continue; - } - - cur++; - if (cur + len > message_len) return false; - - if (out_len) { - if (out_len + 1 >= out_cap) return false; - out[out_len++] = '.'; +typedef struct { + socket_handle_t sock; + l3_id_t l3_id; + net_l4_endpoint dst; +} mdns_query_target_t; +static bool mdns_open_query_target(l3_id_t l3_id, mdns_query_target_t* target) { + if (!l3_id || !target) return false; + + ip_version_t ver; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); + if (ipv4_l3_is_ready(v4) && !v4->is_localhost) ver = IP_VER4; + else { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) return false; + if (!ipv6_is_linklocal(v6->ip)) { + if (!v6->l2) return false; + for (uint8_t i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* cand = v6->l2->l3_v6[i]; + if (!ipv6_l3_is_ready(cand) || cand->is_localhost || !ipv6_is_linklocal(cand->ip)) continue; + v6 = cand; + break; + } + if (!ipv6_is_linklocal(v6->ip)) return false; + l3_id = v6->l3_id; } - - if (out_len + len >= out_cap) return false; - memcpy(out + out_len, message + cur, len); - out_len += len; - cur += len; - - if (!jumped) consumed_local = cur - offset; - } -} - -static dns_result_t parse_mdns_ip_record(const uint8_t* buffer, uint32_t buffer_len, const char* name, uint16_t qtype, uint8_t* out_rdata, uint32_t out_len, uint32_t* out_ttl_s) { - if (!buffer) return DNS_ERR_FORMAT; - if (buffer_len < 12) return DNS_ERR_FORMAT; - if (!name) return DNS_ERR_FORMAT; - if (!out_rdata) return DNS_ERR_FORMAT; - if (!out_len) return DNS_ERR_FORMAT; - - uint16_t qd = rd_be16(buffer + 4); - uint16_t an = rd_be16(buffer + 6); - uint16_t ns = rd_be16(buffer + 8); - uint16_t ar = rd_be16(buffer + 10); - - uint32_t offset = 12; - for (uint16_t i = 0; i < qd; ++i) { - offset = skip_dns_name(buffer, buffer_len, offset); - if (offset+4 > buffer_len) return DNS_ERR_FORMAT; - offset += 4; + ver = IP_VER6; } - uint32_t total = (uint32_t)an + (uint32_t)ns + (uint32_t)ar; - uint32_t name_len = (uint32_t)strlen(name); - - for (uint32_t i = 0; i < total; ++i) { - char rrname[256]; - uint32_t consumed = 0; - if (!read_dns_name(buffer, buffer_len, offset, rrname, sizeof(rrname), &consumed)) return DNS_ERR_FORMAT; - offset += consumed; - - if (offset + 10 > buffer_len) return DNS_ERR_FORMAT; - - uint16_t type = rd_be16(buffer + offset + 0); - uint16_t klass = rd_be16(buffer + offset + 2); - uint32_t ttl_s = rd_be32(buffer + offset + 4); - uint16_t rdlen = rd_be16(buffer + offset + 8); - offset += 10; - - if (offset + rdlen > buffer_len) return DNS_ERR_FORMAT; - - if (type == qtype && (klass & 0x7FFFu) == 1u){ - uint32_t rrname_len = (uint32_t)strlen(rrname); - if (rrname_len == name_len&& strncmp(rrname, name, (int)name_len) == 0 && rdlen == out_len) { - memcpy(out_rdata, buffer + offset, out_len); - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; - } - } + socket_handle_t sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_TTL | SOCK_OPT_NONBLOCK, .ttl = 255}); + if (!sock) return false; - offset += rdlen; + SockBindSpec spec = {.kind = BIND_L3, .ver = ver, .l3_id = l3_id}; + if (bind_socket(sock, &spec, 0) != SOCK_OK) { + close_socket(sock); + return false; } - return DNS_ERR_NO_ANSWER; -} - -static bool dns_write_qname(uint8_t* buf, uint32_t buf_len, uint32_t*inout_off, const char* name) { - if (!buf || !inout_off || !name) return false; - uint32_t off = *inout_off; - if (off >= buf_len) return false; - uint32_t label_len = 0; - uint32_t label_pos = off; - buf[off++] = 0; - for (const char* p = name; *p; ++p) { - char c = *p; - if (c =='.') { - if (!label_len || label_len > 63u) return false; - buf[label_pos] = (uint8_t)label_len; - label_len = 0; - label_pos = off; - if (off >= buf_len) return false; - buf[off++] = 0; - continue; - } - if (label_len >= 63u) return false; - if (off >= buf_len) return false; - buf[off++]= (uint8_t)c; - label_len++; + target->sock = sock; + target->l3_id = l3_id; + if (ver == IP_VER4) { + uint32_t group = DNS_MDNS_GROUP_V4; + make_ep(&group, DNS_MDNS_PORT, IP_VER4, &target->dst); + } else { + uint8_t group6[16]; + ipv6_make_multicast(0x02, IPV6_MCAST_MDNS, 0, group6); + make_ep(group6, DNS_MDNS_PORT, IP_VER6, &target->dst); } - if (!label_len || label_len > 63u) return false; - buf[label_pos] = (uint8_t)label_len; - if (off >= buf_len) return false; - buf[off++] = 0; - *inout_off = off; return true; } -static dns_result_t perform_mdns_query_once(socket_handle_t sock, const net_l4_endpoint* dst, const char* name, uint16_t qtype, uint32_t timeout_ms, uint8_t* out_rdata, uint32_t out_len, uint32_t* out_ttl_s) { - if (!sock) return DNS_ERR_NO_DNS; - if (!dst) return DNS_ERR_NO_DNS; +dns_result_t mdns_query(l3_id_t l3_id, const char* name, dns_qtype_t qtype, uint32_t timeout_ms, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count) { + if (out_count) *out_count = 0; if (!name) return DNS_ERR_FORMAT; - if (!out_rdata) return DNS_ERR_FORMAT; - if (!out_len) return DNS_ERR_FORMAT; + if (!out_records && max_records) return DNS_ERR_FORMAT; uint8_t request_buffer[512]; - memset(request_buffer, 0, sizeof(request_buffer)); - - wr_be16(request_buffer + 0, 0); - wr_be16(request_buffer + 2, 0x0000); - wr_be16(request_buffer + 4, 1); + uint32_t request_len = dns_wire_build_query(request_buffer, sizeof(request_buffer), 0, name, qtype, false); + if (!request_len) return DNS_ERR_FORMAT; + + mdns_query_target_t targets[MDNS_QUERY_TARGETS_MAX]; + uint32_t target_count = 0; + if (l3_id) { + if (mdns_open_query_target(l3_id, &targets[0])) target_count = 1; + } else { + uint8_t n_if = l2_interface_count(); + for (uint8_t i = 0; i < n_if && target_count < MDNS_QUERY_TARGETS_MAX; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2 || !l2->is_up) continue; + + for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[j]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost) continue; + if (mdns_open_query_target(v4->l3_id, &targets[target_count])) target_count++; + break; + } - uint32_t offset = 12; - if (!dns_write_qname(request_buffer, (uint32_t)sizeof(request_buffer), &offset, name)) return DNS_ERR_FORMAT; - if (offset + 4 > (uint32_t)sizeof(request_buffer)) return DNS_ERR_FORMAT; - wr_be16(request_buffer + offset + 0, qtype); - wr_be16(request_buffer + offset + 2, 0x0001); - offset += 4; + if (target_count >= MDNS_QUERY_TARGETS_MAX) break; + for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE; j++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[j]; + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) continue; + if (mdns_open_query_target(v6->l3_id, &targets[target_count])) target_count++; + break; + } + } + } + if (!target_count) return DNS_ERR_NO_DNS; - int64_t sent = socket_sendto_udp_ex(sock, DST_ENDPOINT, dst, 0, request_buffer, offset); - if (sent < 0) return DNS_ERR_SEND; + bool sent_any = false; + for (uint32_t i = 0; i < target_count; i++) if (send_to_socket(targets[i].sock, &targets[i].dst, request_buffer, request_len) >= 0) sent_any = true; + if (!sent_any) { + for (uint32_t i = 0; i < target_count; i++) close_socket(targets[i].sock); + return DNS_ERR_SEND; + } + uint32_t found = 0; + bool matched = false; uint32_t waited_ms = 0; while (waited_ms < timeout_ms) { - uint8_t response_buffer[512]; - net_l4_endpoint source; - int64_t received = socket_recvfrom_udp_ex(sock, response_buffer, sizeof(response_buffer), &source); - if (received > 0 && source.port == MDNS_PORT){ - uint32_t ttl_s = 0; - dns_result_t pr = parse_mdns_ip_record(response_buffer, (uint32_t)received, name, qtype, out_rdata, out_len, &ttl_s); - if (pr == DNS_OK){ - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; + for (uint32_t t = 0; t < target_count; t++) { + for (uint32_t rx = 0; rx < 16; rx++) { + uint8_t response_buffer[1024]; + net_l4_endpoint source; + int64_t received = receive_from_socket(targets[t].sock, response_buffer, sizeof(response_buffer), &source); + if (received == SOCK_ERR_WOULDBLOCK) break; + if (received <= 0) break; + if (source.port != DNS_MDNS_PORT) continue; + + bool source_on_l3 = false; + if (source.ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(targets[t].l3_id); + if (ipv4_l3_is_ready(v4) && v4->mask) { + uint32_t ip = 0; + memcpy(&ip, source.ip, 4); + source_on_l3 = ipv4_same_subnet(v4->ip, ip, v4->mask); + } + } else if (source.ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(targets[t].l3_id); + if (ipv6_l3_is_ready(v6)) source_on_l3 = ipv6_is_linklocal(source.ip) || (v6->prefix_len && ipv6_common_prefix_len(v6->ip, source.ip) >= v6->prefix_len); + } + + if (!source_on_l3) continue; + + uint32_t received_len = received; + dns_record_t records[12]; + uint32_t count = 0; + uint16_t flags = 0; + if (dns_wire_parse_records(response_buffer, received_len, false, 0, records, N_ARR(records), &count, &flags) && (flags & DNS_FLAG_QR) && !(flags & (DNS_OPCODE_MASK | DNS_RCODE_MASK))) { + for (uint32_t i = 0; i < count; i++) { + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if (qtype != DNS_TYPE_ANY && records[i].type != qtype) continue; + if (!dns_wire_name_equals(records[i].name, name)) continue; + matched = true; + if (max_records && found < max_records) { + bool duplicate = false; + for (uint32_t j = 0; j < found; j++) { + if (!dns_wire_record_equal(&records[i], &out_records[j])) continue; + duplicate = true; + break; + } + if (!duplicate) out_records[found++] = records[i]; + } + } + } } } + if (max_records && found >= max_records) break; msleep(20); waited_ms += 20; } - return DNS_ERR_TIMEOUT; + for (uint32_t i = 0; i < target_count; i++) close_socket(targets[i].sock); + if (out_count) *out_count = found; + return matched ? DNS_OK : DNS_ERR_TIMEOUT; } dns_result_t mdns_resolve_a(const char* name, uint32_t timeout_ms, uint32_t* out_ip, uint32_t* out_ttl_s) { - socket_handle_t sock = mdns_socket_handle_v4(); - if (!sock) return DNS_ERR_NO_DNS; + if (!out_ip) return DNS_ERR_FORMAT; - uint32_t group = 0xE00000FBu; - net_l4_endpoint dst; - make_ep(group, MDNS_PORT, IP_VER4, &dst); - - uint8_t rdata[4]; - uint32_t ttl_s = 0; + dns_record_t records[4]; + uint32_t count = 0; - dns_result_t r = perform_mdns_query_once(sock, &dst, name, 1, timeout_ms, rdata, 4, &ttl_s); + dns_result_t r = mdns_query(0, name, DNS_TYPE_A, timeout_ms, records, 4, &count); if (r != DNS_OK) return r; - uint32_t ip; - memcpy(&ip, rdata, 4); - if (out_ip) *out_ip = rd_be32((uint8_t*)&ip); - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; + for (uint32_t i = 0; i < count; i++) { + if (records[i].type != DNS_TYPE_A) continue; + *out_ip = rd_be32(records[i].addr); + if (out_ttl_s) *out_ttl_s = records[i].ttl_s; + return DNS_OK; + } + + return DNS_ERR_NO_ANSWER; } dns_result_t mdns_resolve_aaaa(const char* name, uint32_t timeout_ms, uint8_t out_ipv6[16], uint32_t* out_ttl_s) { - socket_handle_t sock = mdns_socket_handle_v6(); - if (!sock) return DNS_ERR_NO_DNS; - - net_l4_endpoint dst; - memset(&dst, 0, sizeof(dst)); - dst.ver = IP_VER6; - ipv6_make_multicast(0x02, IPV6_MCAST_MDNS, 0, dst.ip); - dst.port = MDNS_PORT; - - uint32_t ttl_s = 0; - dns_result_t r = perform_mdns_query_once(sock, &dst, name, 28, timeout_ms, out_ipv6, 16, &ttl_s); + if (!out_ipv6) return DNS_ERR_FORMAT; + dns_record_t records[4]; + uint32_t count = 0; + dns_result_t r = mdns_query(0, name, DNS_TYPE_AAAA, timeout_ms, records, 4, &count); if (r != DNS_OK) return r; - if (out_ttl_s) *out_ttl_s = ttl_s; - return DNS_OK; + for (uint32_t i = 0; i < count; i++) { + if (records[i].type != DNS_TYPE_AAAA) continue; + ipv6_cpy(out_ipv6, records[i].addr); + if (out_ttl_s) *out_ttl_s = records[i].ttl_s; + return DNS_OK; + } + + return DNS_ERR_NO_ANSWER; } diff --git a/kernel/networking/application_layer/dns/dns_mdns.h b/kernel/networking/application_layer/dns/dns_mdns.h index e5a84ea5..71aa282a 100644 --- a/kernel/networking/application_layer/dns/dns_mdns.h +++ b/kernel/networking/application_layer/dns/dns_mdns.h @@ -2,5 +2,6 @@ #include "dns.h" #include "types.h" +dns_result_t mdns_query(l3_id_t l3_id, const char* name, dns_qtype_t qtype, uint32_t timeout_ms, dns_record_t* out_records, uint32_t max_records, uint32_t* out_count); dns_result_t mdns_resolve_a(const char* name, uint32_t timeout_ms, uint32_t* out_ip, uint32_t* out_ttl_s); dns_result_t mdns_resolve_aaaa(const char* name, uint32_t timeout_ms, uint8_t out_ipv6[16], uint32_t* out_ttl_s); \ No newline at end of file diff --git a/kernel/networking/application_layer/dns/dns_sd.c b/kernel/networking/application_layer/dns/dns_sd.c index 5b53aac2..3c59eb87 100644 --- a/kernel/networking/application_layer/dns/dns_sd.c +++ b/kernel/networking/application_layer/dns/dns_sd.c @@ -1,125 +1,56 @@ #include "dns_sd.h" -#include "std/std.h" +#include "std/string.h" +#include "std/memory.h" -uint32_t dns_sd_encode_qname(uint8_t* out, uint32_t cap, uint32_t off, const char* name) { - if(!out) return 0; - if(!cap) return 0; - if(off >= cap) return 0; - if(!name) return 0; - - uint32_t idx = off; - uint32_t lab_len = 0; - uint32_t lab_pos = idx; - - out[idx] = 0; - idx++; - - while (*name) { - if(*name == '.') { - if(lab_len > 63) return 0; - out[lab_pos] = (uint8_t)lab_len; - lab_len = 0; - lab_pos = idx; - if(idx >= cap) return 0; - out[idx] = 0; - idx++; - name++; - continue; - } - - if(idx >= cap) return 0; - out[idx] = (uint8_t)(*name); - idx++; - name++; - lab_len++; - if(lab_len > 63) return 0; - } - - if(lab_len > 63) return 0; - if(lab_pos >= cap) return 0; - out[lab_pos] = (uint8_t)lab_len; - if(idx >= cap) return 0; - out[idx] = 0; - idx++; - return idx; -} - -uint32_t dns_sd_put_u16(uint8_t* out, uint32_t cap, uint32_t off, uint16_t v) { - if(!out) return 0; - if(off + 2 > cap) return 0; - uint16_t t = be16(v); - memcpy(out + off, &t, 2); - return off + 2; -} - -uint32_t dns_sd_put_u32(uint8_t* out, uint32_t cap, uint32_t off, uint32_t v) { - if(!out) return 0; - if(off + 4 > cap) return 0; - uint32_t t = be32(v); - memcpy(out + off, &t, 4); - return off + 4; -} - -uint32_t dns_sd_add_rr_ptr(uint8_t* out, uint32_t cap, uint32_t off, const char* name, uint16_t rrclass, uint32_t ttl_s, const char* target) { - off = dns_sd_encode_qname(out, cap, off, name); - if(!off) return 0; - - off = dns_sd_put_u16(out, cap, off, DNS_SD_TYPE_PTR); +uint32_t dns_sd_add_rr_ptr(uint8_t* out, uint32_t cap, uint32_t off, const char* name, uint16_t rrclass, uint32_t ttl_s, const char *target) { + if (!dns_wire_write_name(out, cap, &off, name)) return 0; + off = dns_wire_put_u16(out, cap, off, DNS_TYPE_PTR); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, rrclass); + off = dns_wire_put_u16(out, cap, off, rrclass); if(!off) return 0; - off = dns_sd_put_u32(out, cap, off, ttl_s); + off = dns_wire_put_u32(out, cap, off, ttl_s); if(!off) return 0; uint32_t rdlen_pos = off; - off = dns_sd_put_u16(out, cap,off, 0); + off = dns_wire_put_u16(out, cap,off, 0); if(!off) return 0; uint32_t r0 = off; - off = dns_sd_encode_qname(out, cap, off, target); - if(!off) return 0; + if(!dns_wire_write_name(out, cap, &off, target)) return 0; uint16_t rdlen = (uint16_t)(off - r0); - uint16_t rdbe = be16(rdlen); - memcpy(out + rdlen_pos, &rdbe, 2); + wr_be16(out + rdlen_pos, rdlen); return off; } uint32_t dns_sd_add_rr_a(uint8_t* out, uint32_t cap, uint32_t off, const char* name, uint16_t rrclass, uint32_t ttl_s, uint32_t ip) { - off = dns_sd_encode_qname(out, cap, off, name); - if(!off) return 0; + if (!dns_wire_write_name(out, cap, &off, name)) return 0; - off = dns_sd_put_u16(out, cap,off,DNS_SD_TYPE_A); + off = dns_wire_put_u16(out, cap,off,DNS_TYPE_A); if(!off) return 0; - off = dns_sd_put_u16(out,cap, off, rrclass); + off = dns_wire_put_u16(out,cap, off, rrclass); if(!off) return 0; - off = dns_sd_put_u32(out, cap, off, ttl_s); + off = dns_wire_put_u32(out, cap, off, ttl_s); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, 4); + off = dns_wire_put_u16(out, cap, off, 4); if(!off) return 0; - if(off + 4 > cap) return 0; - out[off + 0] = (uint8_t)(ip >> 24); - out[off + 1] = (uint8_t)(ip >> 16); - out[off + 2] = (uint8_t)(ip >> 8); - out[off + 3] = (uint8_t)(ip); - return off + 4; + return dns_wire_put_u32(out, cap, off, ip); } uint32_t dns_sd_add_rr_aaaa(uint8_t* out, uint32_t cap, uint32_t off, const char* name, uint16_t rrclass,uint32_t ttl_s, const uint8_t ip6[16]) { - off = dns_sd_encode_qname(out, cap, off, name); - if(!off) return 0; + if (!dns_wire_write_name(out, cap, &off, name)) return 0; - off = dns_sd_put_u16(out, cap, off, DNS_SD_TYPE_AAAA); + off = dns_wire_put_u16(out, cap, off, DNS_TYPE_AAAA); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, rrclass); + off = dns_wire_put_u16(out, cap, off, rrclass); if(!off) return 0; - off = dns_sd_put_u32(out, cap, off, ttl_s); + off = dns_wire_put_u32(out, cap, off, ttl_s); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, 16); + off = dns_wire_put_u16(out, cap, off, 16); if(!off) return 0; - if(off + 16 > cap) return 0; + if(off > cap || cap - off < 16) return 0; memcpy(out + off, ip6, 16); return off + 16; } @@ -131,35 +62,32 @@ uint32_t dns_sd_add_rr_srv(uint8_t* out, uint32_t cap, uint32_t off, const char* if(!name) return 0; if(!target) return 0; - off = dns_sd_encode_qname(out, cap, off, name); - if(!off) return 0; + if (!dns_wire_write_name(out, cap, &off, name)) return 0; - off = dns_sd_put_u16(out, cap, off, DNS_SD_TYPE_SRV); + off = dns_wire_put_u16(out, cap, off, DNS_TYPE_SRV); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, rrclass); + off = dns_wire_put_u16(out, cap, off, rrclass); if(!off) return 0; - off = dns_sd_put_u32(out, cap, off, ttl_s); + off = dns_wire_put_u32(out, cap, off, ttl_s); if(!off) return 0; uint32_t rdlen_pos = off; - off = dns_sd_put_u16(out, cap, off, 0); + off = dns_wire_put_u16(out, cap, off, 0); if(!off) return 0; uint32_t rdata_start = off; - off = dns_sd_put_u16(out, cap, off, priority); + off = dns_wire_put_u16(out, cap, off, priority); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, weight); + off = dns_wire_put_u16(out, cap, off, weight); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, port); + off = dns_wire_put_u16(out, cap, off, port); if(!off) return 0; - off = dns_sd_encode_qname(out, cap, off, target); - if(!off) return 0; + if (!dns_wire_write_name(out, cap, &off, target)) return 0; - uint16_t rdlen = (uint16_t)(off - rdata_start); - uint16_t t = be16(rdlen); - memcpy(out + rdlen_pos, &t, 2); + uint16_t rdlen = off - rdata_start; + wr_be16(out + rdlen_pos, rdlen); return off; } @@ -169,18 +97,17 @@ uint32_t dns_sd_add_rr_txt(uint8_t *out, uint32_t cap, uint32_t off, const char if(off >= cap) return 0; if(!name) return 0; - off = dns_sd_encode_qname(out, cap, off, name); - if(!off) return 0; + if (!dns_wire_write_name(out, cap, &off, name)) return 0; - off = dns_sd_put_u16(out, cap, off, DNS_SD_TYPE_TXT); + off = dns_wire_put_u16(out, cap, off, DNS_TYPE_TXT); if(!off) return 0; - off = dns_sd_put_u16(out, cap, off, rrclass); + off = dns_wire_put_u16(out, cap, off, rrclass); if(!off) return 0; - off = dns_sd_put_u32(out, cap, off, ttl_s); + off = dns_wire_put_u32(out, cap, off, ttl_s); if(!off) return 0; uint32_t rdlen_pos = off; - off = dns_sd_put_u16(out, cap, off, 0); + off = dns_wire_put_u16(out, cap, off, 0); if(!off) return 0; uint32_t rdata_start = off; @@ -188,7 +115,7 @@ uint32_t dns_sd_add_rr_txt(uint8_t *out, uint32_t cap, uint32_t off, const char if(txt && txt[0]){ const char* p = txt; while(*p) { - while(*p == ' ' || *p == '\t' || *p == ';' || *p == '\n' || *p == '\r') p++; + while(is_whitespace(*p) || *p == ';') p++; if(!*p) break; const char* start = p; @@ -197,18 +124,40 @@ uint32_t dns_sd_add_rr_txt(uint8_t *out, uint32_t cap, uint32_t off, const char uint32_t len = (uint32_t)(p - start); if(len > 255) len = 255; - if(off + 1 + len > cap) return 0; + if(off > cap || cap - off < 1 + len) return 0; out[off] = (uint8_t)len; off++; memcpy(out + off, start,len); off += len; - while(*p == ' ' || *p == '\t' || *p == ';' || *p == '\n' || *p == '\r') p++; + while(is_whitespace(*p) || *p == ';') p++; } } + if (off == rdata_start) { + if (off >= cap) return 0; + out[off++] = 0; + } + uint16_t rdlen = (uint16_t)(off - rdata_start); - uint16_t t = be16(rdlen); - memcpy(out + rdlen_pos, &t, 2); + wr_be16(out + rdlen_pos, rdlen); return off; } + +uint32_t dns_sd_add_record(uint8_t *out, uint32_t cap, uint32_t off, const dns_record_t *record, uint16_t rrclass, uint32_t ttl_s) { + if (!record) return 0; + switch (record->type) { + case DNS_TYPE_A: + return dns_sd_add_rr_a(out, cap, off, record->name, rrclass, ttl_s, rd_be32(record->addr)); + case DNS_TYPE_AAAA: + return dns_sd_add_rr_aaaa(out, cap, off, record->name, rrclass, ttl_s, record->addr); + case DNS_TYPE_PTR: + return dns_sd_add_rr_ptr(out, cap, off, record->name, rrclass, ttl_s, record->target); + case DNS_TYPE_SRV: + return dns_sd_add_rr_srv(out, cap, off, record->name, rrclass, ttl_s, record->priority, record->weight, record->port, record->target); + case DNS_TYPE_TXT: + return dns_sd_add_rr_txt(out, cap, off, record->name, rrclass, ttl_s, record->txt); + default: + return 0; + } +} diff --git a/kernel/networking/application_layer/dns/dns_sd.h b/kernel/networking/application_layer/dns/dns_sd.h index 312a432b..6f4c7de5 100644 --- a/kernel/networking/application_layer/dns/dns_sd.h +++ b/kernel/networking/application_layer/dns/dns_sd.h @@ -1,36 +1,20 @@ #pragma once #include "types.h" +#include "dns_wire.h" #ifdef __cplusplus extern "C" { #endif -#define DNS_SD_MDNS_PORT 5353 - -#define DNS_SD_TYPE_A 1 -#define DNS_SD_TYPE_PTR 12 -#define DNS_SD_TYPE_TXT 16 -#define DNS_SD_TYPE_SRV 33 -#define DNS_SD_TYPE_AAAA 28 -#define DNS_SD_TYPE_ANY 255 - -#define DNS_SD_CLASS_IN 1 - -#define DNS_SD_FLAG_QR 0x8000 -#define DNS_SD_FLAG_AA 0x0400 - #define DNS_SD_DOMAIN_LOCAL "local" #define DNS_SD_ENUM_SERVICES "_services._dns-sd._udp.local" -uint32_t dns_sd_encode_qname(uint8_t *out, uint32_t cap, uint32_t off, const char *name); -uint32_t dns_sd_put_u16(uint8_t *out, uint32_t cap, uint32_t off, uint16_t v); -uint32_t dns_sd_put_u32(uint8_t *out, uint32_t cap, uint32_t off, uint32_t v); - uint32_t dns_sd_add_rr_ptr(uint8_t *out, uint32_t cap, uint32_t off, const char *name, uint16_t rrclass, uint32_t ttl_s, const char *target); uint32_t dns_sd_add_rr_a(uint8_t *out, uint32_t cap, uint32_t off, const char *name, uint16_t rrclass, uint32_t ttl_s, uint32_t ip); uint32_t dns_sd_add_rr_aaaa(uint8_t *out, uint32_t cap, uint32_t off, const char *name, uint16_t rrclass, uint32_t ttl_s, const uint8_t ip6[16]); uint32_t dns_sd_add_rr_srv(uint8_t *out, uint32_t cap, uint32_t off, const char *name, uint16_t rrclass, uint32_t ttl_s, uint16_t priority, uint16_t weight, uint16_t port, const char *target); uint32_t dns_sd_add_rr_txt(uint8_t *out, uint32_t cap, uint32_t off, const char *name, uint16_t rrclass, uint32_t ttl_s, const char *txt); +uint32_t dns_sd_add_record(uint8_t *out, uint32_t cap, uint32_t off, const dns_record_t *record, uint16_t rrclass, uint32_t ttl_s); #ifdef __cplusplus } diff --git a/kernel/networking/application_layer/dns/dns_wire.c b/kernel/networking/application_layer/dns/dns_wire.c new file mode 100644 index 00000000..35f1057c --- /dev/null +++ b/kernel/networking/application_layer/dns/dns_wire.c @@ -0,0 +1,431 @@ +#include "dns_wire.h" +#include "std/string.h" +#include "std/memory.h" + +uint32_t dns_wire_put_u16(uint8_t *out, uint32_t cap, uint32_t off, uint16_t v) { + if (!out) return 0; + if (off > cap || cap - off < 2) return 0; + wr_be16(out + off, v); + return off + 2; +} + +uint32_t dns_wire_put_u32(uint8_t *out, uint32_t cap, uint32_t off, uint32_t v) { + if (!out) return 0; + if (off > cap || cap - off < 4) return 0; + wr_be32(out + off, v); + return off + 4; +} + +bool dns_wire_name_normalize(const char *name, char *out, uint32_t out_cap) { + if (!name) return false; + if (!out) return false; + if (!out_cap) return false; + + uint32_t in_len = strlen(name); + while (in_len && name[in_len - 1] == '.') in_len--; + if (!in_len) return false; + if (in_len >= out_cap) return false; + + uint32_t label_len = 0; + for (uint32_t i = 0; i < in_len; i++) { + char c = tolower(name[i]); + if (c == '.') { + if (!label_len) return false; + label_len = 0; + out[i] = '.'; + continue; + } + if (c <= 32) return false; + label_len++; + if (label_len > 63) return false; + out[i] = c; + } + + if (!label_len) return false; + out[in_len] = 0; + return true; +} + +bool dns_wire_name_equals(const char *a, const char *b) { + if (!a || !b) return false; + uint32_t alen = strlen(a); + uint32_t blen = strlen(b); + while (alen && a[alen - 1] == '.') alen--; + while (blen && b[blen - 1] == '.') blen--; + if (!alen || alen != blen || alen >= DNS_WIRE_MAX_NAME) return false; + + uint32_t label_len = 0; + for (uint32_t i = 0; i < alen; i++) { + char c = tolower(a[i]); + if (c != tolower(b[i])) return false; + if (c == '.') { + if (!label_len) return false; + label_len = 0; + } else if (c <= 32 || ++label_len > 63) return false; + } + return label_len != 0; +} + +bool dns_wire_is_local_name(const char *name) { + char n[DNS_WIRE_MAX_NAME]; + if (!dns_wire_name_normalize(name, n, sizeof(n))) return false; + return strend(n, ".local") == 0; +} + +bool dns_wire_write_name(uint8_t *out, uint32_t cap, uint32_t *off, const char *name) { + if (!out) return false; + if (!off) return false; + if (!name) return false; + if (*off >= cap) return false; + + uint32_t in_len = strlen(name); + while (in_len && name[in_len - 1] == '.') in_len--; + if (!in_len) return false; + if (in_len >= DNS_WIRE_MAX_NAME) return false; + + uint32_t idx = *off; + uint32_t wire_len = 1; + uint32_t pos = 0; + while (pos < in_len) { + uint32_t start = pos; + uint32_t len = 0; + while (pos < in_len && name[pos] != '.') { + char c = name[pos]; + if (c <= 32) return false; + len++; + pos++; + } + if (!len || len > 63) return false; + wire_len += 1 + len; + if (wire_len > 255) return false; + if (idx > cap || cap - idx < 1 + len) return false; + + out[idx++] = len; + memcpy(out + idx, name + start, len); + idx += len; + + if (pos < in_len) { + if (name[pos] != '.') return false; + pos++; + if (pos == in_len) return false; + } + } + + if (idx >= cap) return false; + out[idx++] = 0; + *off = idx; + return true; +} + +bool dns_wire_read_name(const uint8_t *msg, uint32_t msg_len, uint32_t off, char *out, uint32_t out_cap, uint32_t *out_next) { + if (!msg) return false; + bool skip = !out && !out_cap; + if (!skip && !out) return false; + if (!skip && !out_cap) return false; + if (off >= msg_len) return false; + + uint32_t idx = off; + uint32_t out_idx = 0; + uint32_t jumps = 0; + bool jumped = false; + + while (true) { + if (idx >= msg_len) return false; + uint8_t c = msg[idx]; + + if ((c & 0xC0) == 0xC0) { + if (idx + 1 >= msg_len) return false; + uint16_t ptr = ((c & 0x3F) << 8) | msg[idx+1]; + if (ptr >= msg_len) return false; + if (!jumped) { + if (out_next) *out_next = idx + 2; + jumped = true; + } + idx = ptr; + jumps++; + if (jumps > 16) return false; + continue; + } + + if (c & 0xC0) return false; + + idx++; + if (!c) { + if (!jumped && out_next) *out_next = idx; + if (skip) return true; + if (!out_idx) { + if (out_cap < 2) return false; + out[0] = '.'; + out[1] = 0; + return true; + } + if (out_idx >= out_cap) return false; + out[out_idx] = 0; + return true; + } + + uint32_t label_len = c; + if (label_len > 63) return false; + if (label_len > msg_len - idx) return false; + + if (!skip) { + if (out_idx) { + if (out_idx + 1 >= out_cap) return false; + out[out_idx++] = '.'; + } + + if (out_idx + label_len >= out_cap) return false; + memcpy(out + out_idx, msg + idx, label_len); + out_idx += label_len; + } + idx += label_len; + } +} + +bool dns_wire_read_rr(const uint8_t *msg, uint32_t msg_len, uint32_t off, dns_section_t section, dns_rr_view_t *rr, uint32_t *out_next) { + if (!rr) return false; + memset(rr, 0, sizeof(*rr)); + if (!dns_wire_read_name(msg, msg_len, off, rr->name, sizeof(rr->name), &off)) return false; + if (off > msg_len || msg_len - off < 10) return false; + + rr->type = rd_be16(msg + off); + rr->rrclass = rd_be16(msg + off + 2); + rr->ttl_s = rd_be32(msg + off + 4); + rr->rdlen = rd_be16(msg + off + 8); + rr->rdata_off = off + 10; + rr->section = section; + if (rr->rdlen > msg_len - rr->rdata_off) return false; + if (out_next) *out_next = rr->rdata_off + rr->rdlen; + return true; +} + +bool dns_wire_parse_rdata(const uint8_t *msg, uint32_t msg_len, const dns_rr_view_t *rr, dns_record_t *out) { + if (!msg) return false; + if (!rr) return false; + if (!out) return false; + if (rr->rdata_off > msg_len || rr->rdlen > msg_len - rr->rdata_off) return false; + + memset(out, 0, sizeof(*out)); + strncpy(out->name, rr->name, sizeof(out->name)); + out->type = rr->type; + out->rrclass = rr->rrclass; + out->ttl_s = rr->ttl_s; + out->section = rr->section; + + const uint8_t *rdata = msg + rr->rdata_off; + if (rr->type == DNS_TYPE_A) { + if (rr->rdlen != 4)return false; + memcpy(out->addr, rdata, 4); + return true; + } + + if (rr->type == DNS_TYPE_AAAA) { + if (rr->rdlen != 16)return false; + memcpy(out->addr, rdata, 16); + return true; + } + + if (rr->type == DNS_TYPE_CNAME || rr->type == DNS_TYPE_PTR || rr->type == DNS_TYPE_NS) { + uint32_t next = 0; + if (!rr->rdlen)return false; + if (!dns_wire_read_name(msg, msg_len, rr->rdata_off, out->target, sizeof(out->target), &next)) return false; + return next == rr->rdata_off + rr->rdlen; + } + + if (rr->type == DNS_TYPE_TXT) { + uint32_t idx = 0; + uint32_t out_idx = 0; + out->txt[0] = 0; + while (idx < rr->rdlen) { + uint8_t len = rdata[idx++]; + if (idx + len > rr->rdlen) return false; + if (len) { + if (out_idx && out_idx + 1 < sizeof(out->txt)) out->txt[out_idx++] = ';'; + uint32_t copy = len; + if (out_idx + copy >= sizeof(out->txt)) copy = sizeof(out->txt) - out_idx - 1; + memcpy(out->txt + out_idx, rdata + idx, copy); + out_idx += copy; + } + idx += len; + } + out->txt[out_idx] = 0; + return true; + } + + if (rr->type == DNS_TYPE_SRV) { + if (rr->rdlen < 7) return false; + out->priority = rd_be16(rdata); + out->weight = rd_be16(rdata + 2); + out->port = rd_be16(rdata + 4); + uint32_t next = 0; + if (!dns_wire_read_name(msg, msg_len, rr->rdata_off + 6, out->target, sizeof(out->target), &next)) return false; + return next == rr->rdata_off + rr->rdlen; + } + + return true; +} + +bool dns_wire_record_equal(const dns_record_t *a, const dns_record_t *b) { + if (!a || !b) return false; + if (a->type != b->type) return false; + if ((a->rrclass & DNS_CLASS_MASK) != (b->rrclass & DNS_CLASS_MASK)) return false; + if (!dns_wire_name_equals(a->name, b->name)) return false; + + switch (a->type) { + case DNS_TYPE_A: + return memcmp(a->addr, b->addr, 4) == 0; + case DNS_TYPE_AAAA: + return memcmp(a->addr, b->addr, 16) == 0; + case DNS_TYPE_CNAME: + case DNS_TYPE_NS: + case DNS_TYPE_PTR: + return dns_wire_name_equals(a->target, b->target); + case DNS_TYPE_SRV: + return a->priority == b->priority && a->weight == b->weight && a->port == b->port && dns_wire_name_equals(a->target, b->target); + case DNS_TYPE_TXT: + return strcmp(a->txt, b->txt) == 0; + default: + return false; + } +} + +uint32_t dns_wire_write_record_rdata(const dns_record_t *record, uint8_t *out, uint32_t cap) { + if (!record |!out) return 0; + + if (record->type == DNS_TYPE_A) { + if (cap < 4) return 0; + memcpy(out, record->addr, 4); + return 4; + } + if (record->type == DNS_TYPE_AAAA) { + if (cap < 16) return 0; + memcpy(out, record->addr, 16); + return 16; + } + if (record->type == DNS_TYPE_TXT) { + uint32_t off = 0; + const char *txt = record->txt; + while (*txt) { + while (is_whitespace(*txt) || *txt == ';') txt++; + if (!*txt) break; + + const char *start = txt; + while (*txt && *txt != ';' && *txt != '\n' && *txt != '\r') txt++; + uint32_t len = (uint32_t)(txt - start); + if (len > 255) len = 255; + if (off > cap || cap - off < 1 + len) return 0; + out[off++] = (uint8_t)len; + memcpy(out + off, start, len); + off += len; + } + if (!off) { + if (!cap) return 0; + out[0] = 0; + return 1; + } + return off; + } + if (record->type == DNS_TYPE_SRV) { + if (cap < 7) return 0; + char target[DNS_WIRE_MAX_NAME]; + if (!dns_wire_name_normalize(record->target, target, sizeof(target))) return 0; + wr_be16(out, record->priority); + wr_be16(out + 2, record->weight); + wr_be16(out + 4, record->port); + uint32_t off = 6; + if (!dns_wire_write_name(out, cap, &off, target)) return 0; + return off; + } + if (record->type == DNS_TYPE_PTR || record->type == DNS_TYPE_CNAME || record->type == DNS_TYPE_NS) { + char target[DNS_WIRE_MAX_NAME]; + if (!dns_wire_name_normalize(record->target, target, sizeof(target))) return 0; + uint32_t off = 0; + if (!dns_wire_write_name(out, cap, &off, target)) return 0; + return off; + } + return 0; +} + +int dns_wire_record_cmp(const dns_record_t *a, const dns_record_t *b) { + if (!a || !b) { + if (a) return 1; + if (b) return -1; + return 0; + } + + uint16_t ac = a->rrclass & DNS_CLASS_MASK; + uint16_t bc = b->rrclass & DNS_CLASS_MASK; + if (ac != bc) return ac > bc ? 1 : -1; + if (a->type != b->type) return a->type > b->type ? 1 : -1; + + if (a->type == DNS_TYPE_A || a->type == DNS_TYPE_AAAA) { + uint32_t n = a->type == DNS_TYPE_A ? 4 : 16; + int32_t cmp = memcmp(a->addr, b->addr, n); + if (cmp > 0) return 1; + if (cmp < 0) return -1; + return 0; + } + + uint8_t ar[DNS_WIRE_MAX_NAME + 8]; + uint8_t br[DNS_WIRE_MAX_NAME + 8]; + uint32_t alen = dns_wire_write_record_rdata(a, ar, sizeof(ar)); + uint32_t blen = dns_wire_write_record_rdata(b, br, sizeof(br)); + uint32_t common = alen < blen ? alen : blen; + int cmp = memcmp(ar, br, common); + if (cmp) return cmp > 0 ? 1 : -1; + if (alen == blen) return 0; + return alen > blen ? 1 : -1; +} + +bool dns_wire_parse_records(const uint8_t *msg, uint32_t msg_len, bool check_id, uint16_t message_id, dns_record_t *out, uint32_t out_cap, uint32_t *out_count, uint16_t *out_flags) { + if (out_count) *out_count = 0; + if (!msg) return false; + if (msg_len < 12) return false; + if (check_id && rd_be16(msg) != message_id) return false; + + uint16_t flags = rd_be16(msg+2); + uint16_t qd = rd_be16(msg+4); + uint16_t an = rd_be16(msg+6); + uint16_t ns = rd_be16(msg + 8); + uint16_t ar = rd_be16(msg + 10); + if (out_flags) *out_flags = flags; + + uint32_t off = 12; + for (uint16_t i = 0; i < qd; i++) { + if (!dns_wire_read_name(msg, msg_len, off, NULL, 0, &off)) return false; + if (off > msg_len || msg_len - off < 4) return false; + off += 4; + } + + uint32_t count = 0; + const uint32_t section_count[] = {an, ns, ar}; + const dns_section_t section[] = {DNS_SECTION_ANSWER, DNS_SECTION_AUTHORITY, DNS_SECTION_ADDITIONAL}; + + for (uint32_t s = 0; s < N_ARR(section_count); s++) { + for (uint32_t i = 0; i < section_count[s]; i++) { + dns_rr_view_t rr; + if (!dns_wire_read_rr(msg, msg_len, off, section[s], &rr, &off)) return false; + if (out && count < out_cap && dns_wire_parse_rdata(msg, msg_len, &rr, &out[count])) count++; + } + } + + if (out_count) *out_count = count; + return true; +} + +uint32_t dns_wire_build_query(uint8_t *out, uint32_t cap, uint16_t message_id, const char *name,uint16_t qtype, bool mdns_qu) { + if (!out) return 0; + if (cap < 12) return 0; + + memset(out, 0, 12); + wr_be16(out, message_id); + wr_be16(out + 2, message_id ? DNS_FLAG_RD : 0); + wr_be16(out + 4,1); + + uint32_t off = 12; + if (!dns_wire_write_name(out, cap, &off, name)) return 0; + if (off > cap || cap - off < 4) return 0; + wr_be16(out + off, qtype); + wr_be16(out + off + 2, mdns_qu? DNS_CLASS_UNICAST_RESPONSE | DNS_CLASS_IN : DNS_CLASS_IN); + return off + 4; +} diff --git a/kernel/networking/application_layer/dns/dns_wire.h b/kernel/networking/application_layer/dns/dns_wire.h new file mode 100644 index 00000000..54a28ea9 --- /dev/null +++ b/kernel/networking/application_layer/dns/dns_wire.h @@ -0,0 +1,93 @@ +#pragma once +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DNS_WIRE_MAX_NAME 256 +#define DNS_WIRE_MAX_TXT 256 + +#define DNS_CLASS_IN 1 +#define DNS_CLASS_ANY 255 +#define DNS_CLASS_CACHE_FLUSH 0x8000 +#define DNS_CLASS_UNICAST_RESPONSE 0x8000 +#define DNS_CLASS_MASK 0x7FFF + +#define DNS_FLAG_QR 0x8000 +#define DNS_FLAG_TC 0x0200 +#define DNS_FLAG_AA 0x0400 +#define DNS_FLAG_RD 0x0100 +#define DNS_OPCODE_MASK 0x7800 + +#define DNS_RCODE_MASK 0x000F +#define DNS_RCODE_NXDOMAIN 3 + +#define DNS_MDNS_PORT 5353 +#define DNS_MDNS_GROUP_V4 0xE00000FB + +#define DNS_TYPE_A 1 +#define DNS_TYPE_NS 2 +#define DNS_TYPE_CNAME 5 +#define DNS_TYPE_SOA 6 +#define DNS_TYPE_PTR 12 +#define DNS_TYPE_MX 15 +#define DNS_TYPE_TXT 16 +#define DNS_TYPE_AAAA 28 +#define DNS_TYPE_SRV 33 +#define DNS_TYPE_OPT 41 +#define DNS_TYPE_DS 43 +#define DNS_TYPE_NAPTR 35 +#define DNS_TYPE_CAA 257 +#define DNS_TYPE_ANY 255 + +//TODO add type handling for SOA OPT MX NS NAPTR DS CAA when needed + +typedef enum { + DNS_SECTION_ANSWER = 0, + DNS_SECTION_AUTHORITY = 1, + DNS_SECTION_ADDITIONAL = 2 +} dns_section_t; + +typedef struct { + char name[DNS_WIRE_MAX_NAME]; + uint16_t type; + uint16_t rrclass; + uint32_t ttl_s; + uint16_t rdlen; + uint32_t rdata_off; + dns_section_t section; +} dns_rr_view_t; + +typedef struct { + char name[DNS_WIRE_MAX_NAME]; + uint16_t type; + uint16_t rrclass; + uint32_t ttl_s; + dns_section_t section; + uint8_t addr[16]; + char target[DNS_WIRE_MAX_NAME]; + char txt[DNS_WIRE_MAX_TXT]; + uint16_t priority; + uint16_t weight; + uint16_t port; +} dns_record_t; + +uint32_t dns_wire_put_u16(uint8_t *out, uint32_t cap, uint32_t off, uint16_t v); +uint32_t dns_wire_put_u32(uint8_t *out, uint32_t cap, uint32_t off, uint32_t v); +bool dns_wire_write_name(uint8_t *out, uint32_t cap, uint32_t *off, const char *name); +bool dns_wire_read_name(const uint8_t *msg, uint32_t msg_len, uint32_t off, char *out, uint32_t out_cap, uint32_t *out_next); +bool dns_wire_name_normalize(const char *name, char *out, uint32_t out_cap); +bool dns_wire_name_equals(const char *a, const char *b); +bool dns_wire_is_local_name(const char *name); +bool dns_wire_read_rr(const uint8_t *msg, uint32_t msg_len, uint32_t off, dns_section_t section, dns_rr_view_t *rr, uint32_t *out_next); +bool dns_wire_parse_rdata(const uint8_t *msg, uint32_t msg_len, const dns_rr_view_t *rr, dns_record_t *out); +bool dns_wire_record_equal(const dns_record_t *a, const dns_record_t *b); +uint32_t dns_wire_write_record_rdata(const dns_record_t *record, uint8_t *out, uint32_t cap); +int dns_wire_record_cmp(const dns_record_t *a, const dns_record_t *b); +bool dns_wire_parse_records(const uint8_t *msg, uint32_t msg_len, bool check_id, uint16_t message_id, dns_record_t *out, uint32_t out_cap, uint32_t *out_count, uint16_t *out_flags); +uint32_t dns_wire_build_query(uint8_t *out, uint32_t cap, uint16_t message_id, const char *name,uint16_t qtype, bool mdns_qu); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/application_layer/dns/mdns_internal.h b/kernel/networking/application_layer/dns/mdns_internal.h new file mode 100644 index 00000000..1a98c579 --- /dev/null +++ b/kernel/networking/application_layer/dns/mdns_internal.h @@ -0,0 +1,82 @@ +#pragma once +#include "mdns_responder.h" +#include "dns_wire.h" +#include "networking/interface_manager.h" + +#define MDNS_TTL_S 120 +#define MDNS_MAX_SERVICES 8 +#define MDNS_FLUSH_CLASS (DNS_CLASS_CACHE_FLUSH | DNS_CLASS_IN) +#define MDNS_GOODBYE_BURST 1 +#define MDNS_PROBE_RETRY_MS 5000 +#define MDNS_HOST_NAME "RedactedOS" + +typedef struct { + bool ready; + uint8_t sent; + uint64_t next_ms; +} mdns_probe_t; + +typedef struct { + mdns_probe_t probe; + uint8_t announce_left; + uint64_t last_tx_ms; +} mdns_link_state_t; + +typedef struct { + bool used; + bool active; + uint8_t goodbye_left; + uint64_t last_tx_ms; + char instance[64]; + char service[32]; + char proto[8]; + char txt[128]; + uint16_t port; + bool advertised; + bool retire; + uint16_t name_index; + uint16_t old_name_index; + mdns_link_state_t link[MAX_L2_INTERFACES]; +} mdns_service_t; + +typedef struct { + uint8_t* out; + uint32_t cap; + uint32_t off; + uint16_t an; + uint16_t ar; +} mdns_pkt_t; + +typedef struct { + socket_handle_t sock; + ip_version_t ver; + uint8_t ifindex; + l3_id_t l3_id; + uint32_t l3_generation; + uint8_t mcast_ip[16]; +} mdns_tx_target_t; + +extern char g_mdns_fqdn[72]; +extern uint16_t g_mdns_host_name_index; +extern uint16_t g_mdns_old_host_name_index; +extern bool g_mdns_host_advertised; +extern uint8_t g_mdns_host_goodbye_left; +extern mdns_link_state_t g_mdns_host_link[MAX_L2_INTERFACES]; +extern mdns_service_t g_mdns_services[MDNS_MAX_SERVICES]; + +bool mdns_has_work(void); +void mdns_reprobe(uint32_t l2_mask); +void mdns_responder_tick_multi(const mdns_tx_target_t *targets, uint32_t target_count); +void mdns_rx(socket_handle_t sock, l3_id_t l3_id, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len, const net_l4_endpoint *src); + +bool mdns_send(socket_handle_t sock, const net_l4_endpoint *src, bool unicast, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len); +l2_interface_t* mdns_l2(l3_id_t l3_id); +void mdns_instance_name(char* out, uint32_t out_cap, const mdns_service_t* s, uint16_t name_index); +void mdns_record(dns_record_t* r, const char* name, uint16_t type); +uint32_t mdns_host_records(l2_interface_t* l2, dns_record_t* out, uint32_t cap); +void mdns_service_records(const mdns_service_t* s, uint16_t name_index, dns_record_t out[2]); +void mdns_cache_host(l2_interface_t* l2); +void mdns_probe_start(mdns_probe_t* probe, uint32_t delay_ms); +bool mdns_pkt_begin(mdns_pkt_t *p, uint8_t *out, uint32_t cap, uint16_t flags); +bool mdns_pkt_add(mdns_pkt_t* p, bool additional, const dns_record_t* record, uint16_t rrclass, uint32_t ttl_s); +bool mdns_send_mcast(socket_handle_t sock, ip_version_t ver, const uint8_t* mcast_ip, uint32_t l2_slot, const uint8_t* pkt, uint32_t pkt_len, uint64_t now); diff --git a/kernel/networking/application_layer/dns/mdns_responder.c b/kernel/networking/application_layer/dns/mdns_responder.c index ba57b962..d9119abd 100644 --- a/kernel/networking/application_layer/dns/mdns_responder.c +++ b/kernel/networking/application_layer/dns/mdns_responder.c @@ -1,692 +1,392 @@ -#include "mdns_responder.h" +#include "mdns_internal.h" +#include "dns_daemon.h" #include "dns_sd.h" #include "dns_cache.h" +#include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #include "networking/interface_manager.h" -#include "std/std.h" +#include "data/hash.h" +#include "math/rng.h" +#include "random/random.h" #include "std/string.h" +#include "std/memory.h" #include "syscalls/syscalls.h" -#define MDNS_TTL_S 120 -#define MDNS_ANNOUNCE_BURST 3 -#define MDNS_GOODBYE_BURST 3 -#define MDNS_ANNOUNCE_INTERVAL_MS 250 -#define MDNS_KEEPALIVE_MS 60000 -#define MDNS_MAX_SERVICES 8 -#define MDNS_CACHE_MAX 48 +#define MDNS_ANNOUNCE_BURST 2 +#define MDNS_ANNOUNCE_INTERVAL_MS 1000 +#define MDNS_RECENT_RR_MAX 64 +#define MDNS_PROBE_COUNT 3 +#define MDNS_PROBE_INTERVAL_MS 250 typedef struct { bool used; - bool active; - uint8_t announce_left; - uint8_t goodbye_left; - uint64_t last_tx_ms; - char instance[64]; - char service[32]; - char proto[8]; - char txt[128]; - uint16_t port; -} mdns_service_t; - -typedef struct { - uint16_t rrtype; - uint16_t rrclass; - uint32_t ttl_s; - uint16_t rdlen; -} mdns_rr_hdr_t; - -typedef struct { - uint8_t type; - uint16_t rrtype; - uint16_t port; - uint64_t expire_ms; - char name[256]; - char target[256]; - char txt[256]; -} mdns_cache_entry_t; - -typedef struct { - uint8_t *out; - uint32_t cap; - uint32_t off; - uint32_t an_pos; - uint32_t ar_pos; - uint16_t an; - uint16_t ar; -} mdns_pkt_t; - -static uint32_t g_mdns_ipv4 = 0; -static uint8_t g_mdns_ipv6[16]; -static uint8_t g_mdns_ifindex = 0; -static char g_mdns_fqdn[72]; - -static uint64_t g_mdns_last_refresh_ms = 0; -static uint64_t g_mdns_last_keepalive_ms = 0; -static uint8_t g_mdns_host_announce_left = 0; -static uint64_t g_mdns_host_last_tx_ms = 0; - -static mdns_service_t g_mdns_services[MDNS_MAX_SERVICES]; -static mdns_cache_entry_t g_mdns_cache[MDNS_CACHE_MAX]; - - -static bool mdns_read_name(const uint8_t *msg, uint32_t msg_len, uint32_t off, char *out, uint32_t out_cap, uint32_t *out_next) { - if (!msg) return false; - if (!msg_len) return false; - if (off >= msg_len) return false; - if (!out) return false; - if (!out_cap) return false; - - uint32_t idx = off; - uint32_t out_idx = 0; - uint32_t jumps = 0; - bool jumped = false; - - while (true) { - if (idx >= msg_len) return false; - - uint8_t c = msg[idx]; - if ((c & 0xC0) == 0xC0) { - if (idx + 1 >= msg_len) return false; - uint16_t ptr = (uint16_t)(((uint16_t)(c & 0x3F) << 8) | msg[idx + 1]); - if (ptr >= msg_len) return false; - if (!jumped) { - if (out_next) *out_next = idx + 2; - jumped = true; - } - idx = ptr; - jumps++; - if (jumps > 16) return false; - continue; - } - - if (c == 0) { - if (!jumped) { - if (out_next) *out_next = idx + 1; - } - if (out_idx == 0) { - if (out_cap < 2) return false; - out[0] = '.'; - out[1] = 0; - return true; - } - if (out_idx >= out_cap) return false; - out[out_idx] = 0; - return true; - } - - uint32_t lab_len = c; - idx++; - if (idx + lab_len > msg_len) return false; - - if (out_idx) { - if (out_idx + 1 >= out_cap) return false; - out[out_idx] = '.'; - out_idx++; - } - - if (out_idx + lab_len >= out_cap) return false; - memcpy(out + out_idx, msg + idx, lab_len); - out_idx += lab_len; - idx += lab_len; - } -} - -static void mdns_send(socket_handle_t sock, const net_l4_endpoint *src, bool unicast, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len) { - if (!sock) return; - if (!pkt) return; - if (!pkt_len) return; - + uint8_t l2_slot; + ip_version_t ver; + uint64_t key; + uint64_t last_ms; +} mdns_recent_rr_t; + +char g_mdns_fqdn[72] = MDNS_HOST_NAME ".local"; +uint16_t g_mdns_host_name_index; +uint16_t g_mdns_old_host_name_index; +bool g_mdns_host_advertised; +uint8_t g_mdns_host_goodbye_left; +mdns_link_state_t g_mdns_host_link[MAX_L2_INTERFACES]; +mdns_service_t g_mdns_services[MDNS_MAX_SERVICES]; + +static rng_t g_mdns_rng; +static bool g_mdns_rng_ready; +static mdns_recent_rr_t g_mdns_recent_rr[MDNS_RECENT_RR_MAX]; + +bool mdns_send(socket_handle_t sock, const net_l4_endpoint *src, bool unicast, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len) { net_l4_endpoint dst; memset(&dst, 0, sizeof(dst)); if (unicast && src) { dst = *src; - if (!dst.port) dst.port = DNS_SD_MDNS_PORT; - socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, (void*)pkt, pkt_len); - return; + if (!dst.port) dst.port = DNS_MDNS_PORT; + } else { + dst.ver = ver; + if (ver == IP_VER4) memcpy(dst.ip, mcast_ip, 4); + else ipv6_cpy(dst.ip, mcast_ip); + dst.port = DNS_MDNS_PORT; } - - dst.ver = ver; - if (ver == IP_VER4) memcpy(dst.ip, mcast_ip, 4); - else memcpy(dst.ip, mcast_ip, 16); - dst.port = DNS_SD_MDNS_PORT; - socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, (void*)pkt, pkt_len); + return send_to_socket(sock, &dst, pkt, pkt_len) == pkt_len; } -static bool mdns_pick_identity(uint32_t *out_v4, uint8_t out_v6[16], uint8_t *out_ifindex, uint8_t out_ifid[8]) { - if (!out_v4) return false; - if (!out_v6) return false; - if (!out_ifindex) return false; - if (!out_ifid) return false; - - uint32_t v4 = 0; - uint8_t v6_best[16]; - uint8_t v6_fallback[16]; - uint8_t ifid_best[8]; - uint8_t ifid_fallback[8]; - uint8_t if_best = 0; - uint8_t if_fallback = 0; - - memset(v6_best, 0, sizeof(v6_best)); - memset(v6_fallback, 0, sizeof(v6_fallback)); - memset(ifid_best, 0, sizeof(ifid_best)); - memset(ifid_fallback, 0, sizeof(ifid_fallback)); - - uint8_t c = l2_interface_count(); - for (uint8_t i = 0; i < c; i++) { - l2_interface_t *l2 = l2_interface_at(i); - if (!l2)continue; - if (!l2->is_up) continue; - - if (!v4) { - for (uint8_t j = 0; j < l2->ipv4_count; j++) { - l3_ipv4_interface_t *a = l2->l3_v4[j]; - if (!a) continue; - if (a->is_localhost) continue; - if (!a->ip) continue; - v4 = a->ip; - break; - } - } - - for (uint8_t j = 0; j < l2->ipv6_count; j++) { - l3_ipv6_interface_t *a = l2->l3_v6[j]; - if (!a) continue; - if (a->is_localhost) continue; - if (!a->ip[0]) continue; - - bool is_lla = (a->ip[0] == 0xFE && (a->ip[1] & 0xC0) == 0x80); - if (!is_lla && !if_best) { - memcpy(v6_best, a->ip, 16); - memcpy(ifid_best, a->interface_id, 8); - if_best = l2->ifindex; - } - - if (!if_fallback) { - memcpy(v6_fallback, a->ip, 16); - memcpy(ifid_fallback,a->interface_id, 8); - if_fallback = l2->ifindex; - } - } - } - - if (if_best) { - *out_v4 = v4; - memcpy(out_v6, v6_best, 16); - *out_ifindex = if_best; - memcpy(out_ifid, ifid_best, 8); - return true; - } - - if (if_fallback) { - *out_v4 = v4; - memcpy(out_v6, v6_fallback, 16); - *out_ifindex = if_fallback; - memcpy(out_ifid, ifid_fallback, 8); - return true; - } - - if (v4) { - *out_v4 = v4; - memset(out_v6, 0, 16); - *out_ifindex = 0; - memset(out_ifid, 0, 8); - return true; +static uint32_t mdns_probe_jitter(void) { + if (!g_mdns_rng_ready) { + rng_init_random(&g_mdns_rng); + g_mdns_rng_ready = true; } + return rng_between32(&g_mdns_rng, 0, MDNS_PROBE_INTERVAL_MS + 1); +} - return false; +void mdns_probe_start(mdns_probe_t* probe, uint32_t delay_ms) { + probe->ready = false; + probe->sent = 0; + probe->next_ms = get_time() + delay_ms; } -static void mdns_refresh_identity(void) { - uint64_t now = get_time(); - if (g_mdns_last_refresh_ms && (now - g_mdns_last_refresh_ms) < 1000) return; - g_mdns_last_refresh_ms = now; - - uint32_t v4 = 0; - uint8_t v6[16]; - uint8_t ifid[8]; - uint8_t ifindex = 0; - memset(v6, 0, sizeof(v6)); - memset(ifid, 0, sizeof(ifid)); - - if (!mdns_pick_identity(&v4, v6, &ifindex, ifid)) return; - - bool changed = false; - if (g_mdns_ipv4 != v4) changed = true; - if (memcmp(g_mdns_ipv6, v6, 16) != 0) changed = true; - if (g_mdns_ifindex != ifindex) changed = true; - - g_mdns_ipv4 = v4; - memcpy(g_mdns_ipv6, v6, 16); - g_mdns_ifindex = ifindex; - - if (!g_mdns_fqdn[0]) { - char host[64]; - string_format_buf(host, sizeof(host),"redactedos-%02x%02x%02x%02x%02x%02x%02x%02x", ifid[0],ifid[1],ifid[2],ifid[3],ifid[4],ifid[5], ifid[6], ifid[7]); - string_format_buf(g_mdns_fqdn, sizeof(g_mdns_fqdn), "%s.local", host); - changed = true; +void mdns_instance_name(char* out, uint32_t out_cap, const mdns_service_t* s, uint16_t name_index) { + char label[64]; + if (name_index < 2) strncpy(label, s->instance, sizeof(label)); + else { + char suffix[12]; + string_format_buf(suffix, sizeof(suffix), "-%u", (uint32_t)name_index); + uint32_t suffix_len = strlen(suffix); + uint32_t base_len = strlen(s->instance); + if (base_len + suffix_len > 63) base_len = 63 - suffix_len; + memcpy(label, s->instance, base_len); + memcpy(label + base_len, suffix, suffix_len + 1); } - if (changed) { - g_mdns_host_announce_left = MDNS_ANNOUNCE_BURST; - g_mdns_host_last_tx_ms= 0; - for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { - if (!g_mdns_services[i].used) continue; - if (!g_mdns_services[i].active) continue; - g_mdns_services[i].announce_left = MDNS_ANNOUNCE_BURST; - g_mdns_services[i].last_tx_ms = 0; - } - } + string_format_buf(out, out_cap, "%s._%s._%s.local", label, s->service, s->proto); } +l2_interface_t* mdns_l2(l3_id_t l3_id) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); + if (ipv4_l3_is_ready(v4)) return v4->l2; -static void mdns_make_service_type(char *out, uint32_t out_cap, const char *service, const char *proto) { - if (!out) return; - if (!out_cap) return; - if (!service) return; - if (!proto) return; - string_format_buf(out, out_cap, "_%s._%s.local", service, proto); + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + return ipv6_l3_is_ready(v6) ? v6->l2 : NULL; } - -static bool mdns_pkt_begin(mdns_pkt_t *p, uint8_t *out, uint32_t cap, uint16_t flags) { - if (!p) return false; - if (!out) return false; +bool mdns_pkt_begin(mdns_pkt_t *p, uint8_t *out, uint32_t cap, uint16_t flags) { if (cap < 12) return false; + memset(out, 0, 12); memset(p, 0, sizeof(*p)); p->out = out; p->cap = cap; - p->off = 0; - - p->off = dns_sd_put_u16(out, cap, p->off, 0); - if (!p->off) return false; - p->off = dns_sd_put_u16(out, cap, p->off, flags); - if (!p->off) return false; - p->off = dns_sd_put_u16(out, cap, p->off, 0); - if (!p->off) return false; - - p->an_pos = p->off; - p->off = dns_sd_put_u16(out, cap, p->off, 0); - if (!p->off) return false; - - p->off = dns_sd_put_u16(out, cap, p->off, 0); - if (!p->off) return false; - - p->ar_pos = p->off; - p->off = dns_sd_put_u16(out, cap, p->off, 0); - if (!p->off) return false; - + p->off = 12; + wr_be16(out+2, flags); return true; } -static void mdns_pkt_commit(mdns_pkt_t *p) { - if (!p) return; - uint16_t anbe = be16(p->an); - uint16_t arbe = be16(p->ar); - memcpy(p->out + p->an_pos, &anbe, 2); - memcpy(p->out + p->ar_pos, &arbe, 2); -} - -static bool mdns_pkt_add_ptr(mdns_pkt_t *p, bool additional, const char *name, uint16_t rrclass, uint32_t ttl_s, const char *target) { - if (!p) return false; - uint32_t n = dns_sd_add_rr_ptr(p->out, p->cap, p->off, name, rrclass, ttl_s, target); - if (!n) return false; - p->off = n; - if (additional) p->ar++; - else p->an++; +bool mdns_pkt_add(mdns_pkt_t *p, bool additional, const dns_record_t* record, uint16_t rrclass, uint32_t ttl_s) { + uint32_t off = dns_sd_add_record(p->out, p->cap, p->off, record, rrclass, ttl_s); + if (!off) return false; + p->off = off; + if (additional) { + p->ar++; + wr_be16(p->out + 10, p->ar); + } else { + p->an++; + wr_be16(p->out + 6, p->an); + } return true; } -static bool mdns_pkt_add_a(mdns_pkt_t *p, bool additional, const char *name, uint16_t rrclass, uint32_t ttl_s, uint32_t ip) { - if (!p) return false; - uint32_t n = dns_sd_add_rr_a(p->out, p->cap, p->off, name, rrclass, ttl_s, ip); - if (!n) return false; - p->off = n; - if (additional) p->ar++; - else p->an++; - return true; -} +void mdns_cache_host(l2_interface_t* l2) { + uint32_t ttl_ms = MDNS_TTL_S * 1000; + for (uint8_t i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[i]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost) continue; + uint8_t ip[16] = {0}; + memcpy(ip, &v4->ip, 4); + dns_cache_put_ip(v4->l3_id, g_mdns_fqdn, DNS_TYPE_A, ip, ttl_ms); + dns_cache_put_ip(0, g_mdns_fqdn, DNS_TYPE_A, ip, ttl_ms); + } -static bool mdns_pkt_add_aaaa(mdns_pkt_t *p, bool additional, const char *name, uint16_t rrclass, uint32_t ttl_s, const uint8_t ip6[16]) { - if (!p) return false; - uint32_t n = dns_sd_add_rr_aaaa(p->out, p->cap, p->off, name, rrclass, ttl_s, ip6); - if (!n) return false; - p->off = n; - if (additional) p->ar++; - else p->an++; - return true; + for (uint8_t i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) continue; + dns_cache_put_ip(v6->l3_id, g_mdns_fqdn, DNS_TYPE_AAAA, v6->ip, ttl_ms); + dns_cache_put_ip(0, g_mdns_fqdn, DNS_TYPE_AAAA, v6->ip, ttl_ms); + } } -static bool mdns_pkt_add_srv(mdns_pkt_t *p, bool additional, const char *name, uint16_t rrclass, uint32_t ttl_s, uint16_t port, const char *target) { - if (!p) return false; - uint32_t n = dns_sd_add_rr_srv(p->out, p->cap, p->off, name, rrclass, ttl_s, 0, 0, port, target); - if (!n) return false; - p->off = n; - if (additional) p->ar++; - else p->an++; +static bool mdns_label_ok(const char *s, uint32_t max_len) { + if (!s) return false; + uint32_t len = strlen(s); + if (!len || len >= max_len || len > 63) return false; + for (uint32_t i = 0; i < len; i++) { + char c = s[i]; + if (!is_alnum(c) && c != '-') return false; + } return true; } -static bool mdns_pkt_add_txt(mdns_pkt_t *p, bool additional, const char *name, uint16_t rrclass, uint32_t ttl_s, const char *txt) { - if (!p) return false; - uint32_t n = dns_sd_add_rr_txt(p->out, p->cap, p->off, name, rrclass, ttl_s, txt); - if (!n) return false; - p->off = n; - if (additional) p->ar++; - else p->an++; +static bool mdns_instance_ok(const char *s) { + if (!s) return false; + uint32_t len = strlen(s); + if (!len || len >= 64 || len > 63) return false; + for (uint32_t i = 0; i < len; i++) { + char c = s[i]; + if (c < 32 || c == '.') return false; + } return true; } +void mdns_record(dns_record_t* r, const char* name, uint16_t type) { + memset(r, 0, sizeof(*r)); + strncpy(r->name, name, sizeof(r->name)); + r->type = type; + r->rrclass = DNS_CLASS_IN; + r->ttl_s = MDNS_TTL_S; +} -static void mdns_cache_put_ptr(const char *name, const char *target, uint32_t ttl_s) { - if (!name) return; - if (!target) return; +uint32_t mdns_host_records(l2_interface_t* l2, dns_record_t* out, uint32_t cap) { + if (!cap) return 0; - uint64_t now = get_time(); - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (!e->type) continue; - if (e->rrtype != DNS_SD_TYPE_PTR) continue; - if (strncmp(e->name, name, 256) != 0) continue; - strncpy(e->target, target, sizeof(e->target)); - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; + uint32_t count = 0; + for (uint8_t i = 0; i < MAX_IPV4_PER_INTERFACE && count < cap; i++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[i]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost) continue; + mdns_record(&out[count], g_mdns_fqdn, DNS_TYPE_A); + wr_be32(out[count].addr, v4->ip); + count++; } - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (e->type) continue; - memset(e, 0, sizeof(*e)); - e->type = 1; - e->rrtype = DNS_SD_TYPE_PTR; - strncpy(e->name, name, sizeof(e->name)); - strncpy(e->target, target, sizeof(e->target)); - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; + for (uint8_t i = 0; i < MAX_IPV6_PER_INTERFACE && count < cap; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) continue; + mdns_record(&out[count], g_mdns_fqdn, DNS_TYPE_AAAA); + ipv6_cpy(out[count].addr, v6->ip); + count++; } + return count; } -static void mdns_cache_put_srv(const char *name, uint16_t port, const char *target, uint32_t ttl_s) { - if (!name) return; - if (!target) return; - - uint64_t now = get_time(); - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (!e->type) continue; - if (e->rrtype != DNS_SD_TYPE_SRV) continue; - if (strncmp(e->name, name, 256) != 0) continue; - e->port = port; - strncpy(e->target, target, sizeof(e->target)); - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; - } +static int mdns_add_host_records(mdns_pkt_t* p, bool additional, l2_interface_t* l2, const char* name, uint16_t rrclass, uint32_t ttl_s) { + if (!l2->is_up) return 0; - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (e->type) continue; - memset(e, 0, sizeof(*e)); - e->type = 1; - e->rrtype = DNS_SD_TYPE_SRV; - e->port = port; - strncpy(e->name, name, sizeof(e->name)); - strncpy(e->target, target, sizeof(e->target)); - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; + dns_record_t records[8]; + uint32_t count = mdns_host_records(l2, records, N_ARR(records)); + for (uint32_t i = 0; i < count; i++) { + strncpy(records[i].name, name, sizeof(records[i].name)); + if (!mdns_pkt_add(p, additional, &records[i], rrclass, ttl_s)) return -1; } + return count; } -static void mdns_cache_put_txt(const char *name, const char *txt, uint32_t ttl_s) { - if (!name) return; +void mdns_service_records(const mdns_service_t* s, uint16_t name_index, dns_record_t out[2]) { + char inst[256]; + mdns_instance_name(inst, sizeof(inst), s, name_index); - uint64_t now = get_time(); - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (!e->type) continue; - if (e->rrtype != DNS_SD_TYPE_TXT) continue; - if (strncmp(e->name, name, 256) != 0) continue; - if (txt) strncpy(e->txt, txt, sizeof(e->txt)); - else e->txt[0] = 0; - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; - } + mdns_record(&out[0], inst, DNS_TYPE_TXT); + strncpy(out[0].txt, s->txt, sizeof(out[0].txt)); - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (e->type) continue; - memset(e, 0, sizeof(*e)); - e->type = 1; - e->rrtype = DNS_SD_TYPE_TXT; - strncpy(e->name, name, sizeof(e->name)); - if (txt) strncpy(e->txt, txt, sizeof(e->txt)); - e->expire_ms = now + (uint64_t)ttl_s * 1000; - return; - } + mdns_record(&out[1], inst, DNS_TYPE_SRV); + out[1].port = s->port; + strncpy(out[1].target, g_mdns_fqdn, sizeof(out[1].target)); } -static bool mdns_parse_ipv4_ptr_qname(const char *name, uint32_t *out_ip) { - if (!name) return false; - if (!out_ip) return false; - - uint32_t oct[4]; - memset(oct, 0, sizeof(oct)); - - const char *p = name; - for (int i = 0; i < 4; i++) { - uint32_t v = 0; - uint32_t digits = 0; - while (*p >= '0' && *p <= '9') { - v = v * 10 + (uint32_t)(*p - '0'); - p++; - digits++; - if (digits > 3) return false; +static bool mdns_recent(const dns_record_t* r, ip_version_t ver, uint32_t l2_slot, uint64_t now, bool mark) { + if (l2_slot >= MAX_L2_INTERFACES) return false; + + char name[DNS_WIRE_MAX_NAME]; + if (!dns_wire_name_normalize(r->name, name, sizeof(name))) return false; + + uint8_t key_data[DNS_WIRE_MAX_NAME*2 + 20]; + uint32_t off = 0; + if (!dns_wire_write_name(key_data, sizeof(key_data), &off, name)) return false; + off = dns_wire_put_u16(key_data, sizeof(key_data), off, r->type); + if (!off) return false; + off = dns_wire_put_u16(key_data, sizeof(key_data), off, r->rrclass & DNS_CLASS_MASK); + if (!off) return false; + off = dns_wire_put_u32(key_data, sizeof(key_data), off, r->ttl_s); + if (!off) return false; + + uint32_t rdata_len = dns_wire_write_record_rdata(r, key_data + off, sizeof(key_data) - off); + if (!rdata_len) return false; + uint64_t key = hash_map_fnv1a64(key_data, off + rdata_len); + + int empty = -1; + uint32_t oldest = 0; + + for (uint32_t i = 0; i < MDNS_RECENT_RR_MAX; i++) { + mdns_recent_rr_t* e = &g_mdns_recent_rr[i]; + if (!e->used) { + if (empty < 0) empty = (int)i; + continue; } - if (!digits) return false; - if (v > 255) return false; - oct[i] = v; - if (i < 3) { - if (*p != '.') return false; - p++; + if (e->l2_slot == l2_slot && e->ver == ver && e->key == key) { + bool recent = (now - e->last_ms) < MDNS_ANNOUNCE_INTERVAL_MS; + if (mark) e->last_ms = now; + return recent; } + if (g_mdns_recent_rr[oldest].used && e->last_ms < g_mdns_recent_rr[oldest].last_ms) oldest = i; } - if (*p != '.') return false; - p++; - - if (strncmp(p, "in-addr.arpa", 12) != 0) return false; - - uint32_t a = oct[3]; - uint32_t b = oct[2]; - uint32_t c = oct[1]; - uint32_t d = oct[0]; - - *out_ip = (uint32_t)((a & 255u) | ((b & 255u) << 8) | ((c & 255u) << 16) | ((d & 255u) << 24)); - return true; -} - -static void mdns_parse_txt(const uint8_t *rdata, uint16_t rdlen, char *out, uint32_t out_cap) { - if (!out) return; - if (!out_cap) return; - - out[0] = 0; - if (!rdata) return; - if (!rdlen) return; - - uint32_t idx = 0; - uint32_t out_idx = 0; - - while (idx < rdlen) { - uint8_t len = rdata[idx]; - idx++; - if (idx + len > rdlen) break; - - if (len) { - if (out_idx) { - if (out_idx + 1 >= out_cap) break; - out[out_idx] = ';'; - out_idx++; - } - uint32_t copy = len; - if (out_idx + copy >= out_cap) copy = out_cap - out_idx - 1; - memcpy(out + out_idx, rdata + idx, copy); - out_idx += copy; - } - - idx += len; + if (mark) { + mdns_recent_rr_t* e = &g_mdns_recent_rr[empty >= 0 ? (uint32_t)empty : oldest]; + e->used = true; + e->l2_slot = (uint8_t)l2_slot; + e->ver = ver; + e->key = key; + e->last_ms = now; } - - if (out_idx >= out_cap) out_idx = out_cap - 1; - out[out_idx] = 0; + return false; } -static void mdns_cache_from_packet(const uint8_t *pkt, uint32_t pkt_len) { - if (!pkt) return; - if (pkt_len < 12) return; - - uint16_t flags = be16(*(const uint16_t *)(pkt + 2)); - if (!(flags & DNS_SD_FLAG_QR)) return; +bool mdns_send_mcast(socket_handle_t sock, ip_version_t ver, const uint8_t* mcast_ip, uint32_t l2_slot, const uint8_t* pkt, uint32_t pkt_len, uint64_t now) { + if (l2_slot >= MAX_L2_INTERFACES) return mdns_send(sock, NULL, false, ver, mcast_ip, pkt, pkt_len); - uint16_t qd = be16(*(const uint16_t *)(pkt + 4)); - uint16_t an = be16(*(const uint16_t *)(pkt + 6)); - uint16_t ns = be16(*(const uint16_t *)(pkt + 8)); - uint16_t ar = be16(*(const uint16_t *)(pkt + 10)); - - uint32_t off = 12; + dns_record_t records[48]; + uint32_t count = 0; + uint16_t flags = 0; + if (!dns_wire_parse_records(pkt, pkt_len, false, 0, records, N_ARR(records), &count, &flags)) return false; - for (uint16_t i = 0; i < qd; i++) { - char qname[256]; - uint32_t next = 0; - if (!mdns_read_name(pkt, pkt_len, off, qname, sizeof(qname), &next)) return; - if (next + 4 > pkt_len) return; - off = next + 4; - if (off > pkt_len) return; - } - - uint32_t rr_total = (uint32_t)an + (uint32_t)ns + (uint32_t)ar; - for (uint32_t i = 0; i < rr_total; i++) { - char name[256]; - uint32_t next = 0; - if (!mdns_read_name(pkt, pkt_len, off, name, sizeof(name), &next)) return; - if (next + 10 > pkt_len) return; - - mdns_rr_hdr_t h; - h.rrtype = be16(*(const uint16_t *)(pkt + next)); - h.rrclass = be16(*(const uint16_t *)(pkt + next + 2)); - h.ttl_s = be32(*(const uint32_t *)(pkt + next + 4)); - h.rdlen = be16(*(const uint16_t *)(pkt + next + 8)); - - uint32_t rdata = next + 10; - if (rdata + h.rdlen > pkt_len) return; - - if (h.rrtype == DNS_SD_TYPE_A) { - if (h.rdlen == 4) { - uint8_t ip4[16]; - memset(ip4, 0, sizeof(ip4)); - memcpy(ip4, pkt + rdata, 4); - dns_cache_put_ip(name, DNS_SD_TYPE_A, ip4, h.ttl_s * 1000); - } - } else if (h.rrtype == DNS_SD_TYPE_AAAA) { - if (h.rdlen == 16) { - dns_cache_put_ip(name, DNS_SD_TYPE_AAAA, pkt + rdata, h.ttl_s * 1000); - } - } else if (h.rrtype == DNS_SD_TYPE_PTR) { - char target[256]; - uint32_t tnext = 0; - if (mdns_read_name(pkt, pkt_len, rdata, target, sizeof(target), &tnext)) { - mdns_cache_put_ptr(name, target, h.ttl_s); - } - } else if (h.rrtype == DNS_SD_TYPE_SRV) { - if (h.rdlen >= 6) { - uint16_t port = be16(*(const uint16_t *)(pkt + rdata + 4)); - char target[256]; - uint32_t tnext = 0; - if (mdns_read_name(pkt, pkt_len, rdata + 6, target, sizeof(target), &tnext)) { - mdns_cache_put_srv(name, port, target, h.ttl_s); - } - } - } else if (h.rrtype == DNS_SD_TYPE_TXT) { - char txt[256]; - mdns_parse_txt(pkt + rdata, h.rdlen, txt, sizeof(txt)); - mdns_cache_put_txt(name, txt, h.ttl_s); - } - - off = rdata + h.rdlen; - if (off > pkt_len) return; + uint8_t filtered[1500]; + mdns_pkt_t p; + uint64_t added = 0; + if (!mdns_pkt_begin(&p, filtered, sizeof(filtered), flags)) return false; + for (uint32_t i = 0; i < count; i++) { + if (mdns_recent(&records[i], ver, l2_slot, now, false)) continue; + bool additional = records[i].section == DNS_SECTION_ADDITIONAL; + if (!mdns_pkt_add(&p, additional, &records[i], records[i].rrclass, records[i].ttl_s)) return false; + added |= 1ULL << i; } + if (!added || !mdns_send(sock, NULL, false, ver, mcast_ip, filtered, p.off)) return false; + for (uint32_t i = 0; i < count; i++) if (added & (1ULL << i)) mdns_recent(&records[i], ver, l2_slot, now, true); + return true; } -static bool mdns_add_host_additionals(mdns_pkt_t *p) { - if (!p) return false; - - uint16_t rrclass = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - - if (g_mdns_ipv4) { - if (!mdns_pkt_add_a(p, true, g_mdns_fqdn, rrclass, MDNS_TTL_S, g_mdns_ipv4)) return false; - } +static bool mdns_probe_packet(uint8_t* out, uint32_t cap, const char* name, const dns_record_t* records, uint32_t count, uint32_t* out_len) { + if (cap < 12 || !count) return false; + memset(out, 0, 12); + wr_be16(out + 4, 1); + wr_be16(out + 8, (uint16_t)count); - uint8_t ip6[16]; - memcpy(ip6, g_mdns_ipv6, 16); - if (!ip6[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6); - if (ip6[0]) { - if (!mdns_pkt_add_aaaa(p, true, g_mdns_fqdn, rrclass, MDNS_TTL_S, ip6)) return false; + uint32_t off = 12; + if (!dns_wire_write_name(out, cap, &off, name)) return false; + off = dns_wire_put_u16(out, cap, off, DNS_TYPE_ANY); + if (!off) return false; + off = dns_wire_put_u16(out, cap, off, DNS_CLASS_IN); + if (!off) return false; + + for (uint32_t i = 0; i < count; i++) { + off = dns_sd_add_record(out, cap, off, &records[i], DNS_CLASS_IN, MDNS_TTL_S); + if (!off) return false; } + *out_len = off; return true; } -static bool mdns_add_service_records(mdns_pkt_t *p, const mdns_service_t *s, uint32_t ttl_s, bool goodbye) { - if (!p) return false; - if (!s) return false; - - char type[128]; - char inst[256]; - mdns_make_service_type(type, sizeof(type), s->service, s->proto); - inst[0] = 0; - if (!s->instance[0] || !s->service[0] || !s->proto[0]) return false; - string_format_buf(inst, sizeof(inst), "%s._%s._%s.local", s->instance, s->service, s->proto); - - uint16_t ptr_class = DNS_SD_CLASS_IN; - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - - uint32_t ttl = goodbye ? 0 : ttl_s; - - if (!mdns_pkt_add_ptr(p, false, DNS_SD_ENUM_SERVICES, ptr_class, ttl, type)) return false; - if (!mdns_pkt_add_ptr(p, false, type, ptr_class, ttl, inst)) return false; - - if (!goodbye) { - if (!mdns_pkt_add_srv(p, true, inst, flush_class, ttl_s, s->port, g_mdns_fqdn)) return false; - if (!mdns_pkt_add_txt(p, true, inst, flush_class, ttl_s, s->txt)) return false; - if (!mdns_add_host_additionals(p)) return false; - } else { - if (!mdns_pkt_add_srv(p, true, inst, flush_class, 0, s->port, g_mdns_fqdn)) return false; - if (!mdns_pkt_add_txt(p, true, inst, flush_class, 0, s->txt)) return false; - } - - return true; +bool mdns_has_work(void) { + if (g_mdns_host_goodbye_left) return true; + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) if (g_mdns_services[i].used && (g_mdns_services[i].active || g_mdns_services[i].goodbye_left)) return true; + return false; } bool mdns_register_service(const char *instance, const char *service, const char *proto, uint16_t port, const char *txt) { - if (!instance) return false; - if (!service) return false; + if (!port) return false; + if (!mdns_instance_ok(instance)) return false; + if (!mdns_label_ok(service, 32)) return false; if (!proto) return false; + if (strcmp_case(proto, "tcp", true) != 0 && strcmp_case(proto, "udp", true) != 0) return false; + if (txt) { + uint32_t txt_len = strlen(txt); + if (txt_len >= 128) return false; + for (uint32_t i = 0; i < txt_len; i++) { + char c = txt[i]; + if (!is_printable(c)) return false; + } + } + + uint32_t link_mask = 0; + uint8_t n_if = l2_interface_count(); + for (uint8_t i = 0; i < n_if; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2 || !l2->is_up || !l2->ifindex || l2->ifindex > MAX_L2_INTERFACES) continue; + + bool ready = false; + for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE && !ready; j++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[j]; + if (ipv4_l3_is_ready(v4) && !v4->is_localhost) ready = true; + } + for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE && !ready; j++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[j]; + if (ipv6_l3_is_ready(v6) && !v6->is_localhost) ready = true; + } + if (ready) link_mask |= 1u << (l2->ifindex-1); + } for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { mdns_service_t *s = &g_mdns_services[i]; if (!s->used) continue; if (strncmp(s->instance, instance, (int)sizeof(s->instance)) != 0) continue; if (strncmp(s->service, service, (int)sizeof(s->service)) != 0) continue; - if (strncmp(s->proto, proto, (int)sizeof(s->proto)) != 0) continue; + if (strncmp_case(s->proto, proto, true, (int)sizeof(s->proto)) != 0) continue; + + bool was_active = s->active; + bool rdata_changed = s->port != port; + if (txt) rdata_changed |= strcmp(s->txt, txt) != 0; + else rdata_changed |= s->txt[0] != 0; s->active = true; s->port = port; if (txt) strncpy(s->txt, txt, sizeof(s->txt)); else s->txt[0] = 0; - s->announce_left = MDNS_ANNOUNCE_BURST; + + bool cancelled_goodbye = false; + if (!was_active && s->goodbye_left) { + s->goodbye_left = 0; + s->retire = false; + cancelled_goodbye = true; + } + + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + if (!(link_mask & (1u << slot))) continue; + mdns_link_state_t* link = &s->link[slot]; + if (!cancelled_goodbye && !was_active && !s->advertised) mdns_probe_start(&link->probe, mdns_probe_jitter()); + else if (was_active && rdata_changed && !link->probe.ready) mdns_probe_start(&link->probe, mdns_probe_jitter()); + link->announce_left = link->probe.ready && g_mdns_host_link[slot].probe.ready ? MDNS_ANNOUNCE_BURST : 0; + link->last_tx_ms = 0; + + mdns_probe_t* host_probe = &g_mdns_host_link[slot].probe; + if (!host_probe->ready && !host_probe->sent && !host_probe->next_ms) mdns_probe_start(host_probe, mdns_probe_jitter()); + } + s->goodbye_left = 0; s->last_tx_ms = 0; + g_mdns_host_goodbye_left = 0; + dns_daemon_kick(); return true; } @@ -704,9 +404,17 @@ bool mdns_register_service(const char *instance, const char *service, const char strncpy(s->proto, proto, sizeof(s->proto)); if (txt) strncpy(s->txt, txt, sizeof(s->txt)); - s->announce_left = MDNS_ANNOUNCE_BURST; + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + if (!(link_mask & (1u << slot))) continue; + mdns_probe_start(&s->link[slot].probe, mdns_probe_jitter()); + + mdns_probe_t* host_probe = &g_mdns_host_link[slot].probe; + if (!host_probe->ready && !host_probe->sent && !host_probe->next_ms) mdns_probe_start(host_probe, mdns_probe_jitter()); + } s->goodbye_left = 0; s->last_tx_ms = 0; + g_mdns_host_goodbye_left = 0; + dns_daemon_kick(); return true; } @@ -714,9 +422,10 @@ bool mdns_register_service(const char *instance, const char *service, const char } bool mdns_deregister_service(const char *instance, const char *service, const char *proto) { - if (!instance) return false; - if (!service) return false; + if (!mdns_instance_ok(instance)) return false; + if (!mdns_label_ok(service, 32)) return false; if (!proto) return false; + if (strcmp_case(proto, "tcp", true) != 0 && strcmp_case(proto, "udp", true) != 0) return false; for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { mdns_service_t *s = &g_mdns_services[i]; @@ -724,277 +433,277 @@ bool mdns_deregister_service(const char *instance, const char *service, const ch if (!s->active) continue; if (strncmp(s->instance, instance, (int)sizeof(s->instance)) != 0) continue; if (strncmp(s->service, service, (int)sizeof(s->service)) != 0) continue; - if (strncmp(s->proto, proto, (int)sizeof(s->proto)) != 0) continue; + if (strncmp_case(s->proto, proto, true, (int)sizeof(s->proto)) != 0) continue; s->active = false; - s->announce_left = 0; - s->goodbye_left = MDNS_GOODBYE_BURST; s->last_tx_ms = 0; + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + s->link[slot].announce_left = 0; + s->link[slot].last_tx_ms = 0; + } + + if (s->advertised) { + s->old_name_index = s->name_index; + s->goodbye_left = MDNS_GOODBYE_BURST; + s->retire = true; + } else memset(s, 0, sizeof(*s)); + + bool active = false; + for (uint32_t j = 0; j < MDNS_MAX_SERVICES; j++) { + if (!g_mdns_services[j].used || !g_mdns_services[j].active) continue; + active = true; + break; + } + if (!active) { + if (g_mdns_host_advertised) { + g_mdns_old_host_name_index = g_mdns_host_name_index; + g_mdns_host_goodbye_left = MDNS_GOODBYE_BURST; + } + memset(g_mdns_host_link, 0, sizeof(g_mdns_host_link)); + } + dns_daemon_kick(); return true; } return false; } -void mdns_responder_tick(socket_handle_t sock4, socket_handle_t sock6, const uint8_t mcast_v4[4], const uint8_t mcast_v6[16]) { - mdns_refresh_identity(); - uint64_t now = get_time(); - for (uint32_t i = 0; i < MDNS_CACHE_MAX; i++) { - mdns_cache_entry_t *e = &g_mdns_cache[i]; - if (!e->type) continue; - if (now < e->expire_ms) continue; - memset(e, 0, sizeof(*e)); +void mdns_reprobe(uint32_t l2_mask) { + for (uint32_t i = 0; i < MDNS_RECENT_RR_MAX; i++) { + mdns_recent_rr_t *e = &g_mdns_recent_rr[i]; + if (e->used && e->l2_slot < MAX_L2_INTERFACES && (l2_mask & (1u << e->l2_slot))) memset(e, 0, sizeof(*e)); } - if (!g_mdns_last_keepalive_ms) g_mdns_last_keepalive_ms = now; - if ((now - g_mdns_last_keepalive_ms) >= MDNS_KEEPALIVE_MS) { - g_mdns_last_keepalive_ms = now; - g_mdns_host_announce_left = 1; - g_mdns_host_last_tx_ms = 0; + uint32_t delay_ms = mdns_probe_jitter(); + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + if (!(l2_mask & (1u << slot))) continue; + + bool active = false; + g_mdns_host_link[slot].announce_left = 0; + g_mdns_host_link[slot].last_tx_ms = 0; for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { - if (!g_mdns_services[i].used) continue; - if (!g_mdns_services[i].active) continue; - g_mdns_services[i].announce_left = 1; - g_mdns_services[i].last_tx_ms = 0; + mdns_service_t* s = &g_mdns_services[i]; + if (!s->used || !s->active) continue; + active = true; + mdns_probe_start(&s->link[slot].probe, delay_ms); + s->link[slot].announce_left = 0; + s->link[slot].last_tx_ms = 0; } + if (active) mdns_probe_start(&g_mdns_host_link[slot].probe, delay_ms); + else memset(&g_mdns_host_link[slot], 0, sizeof(g_mdns_host_link[slot])); } +} - if (g_mdns_host_announce_left) { - if (!g_mdns_host_last_tx_ms || (now - g_mdns_host_last_tx_ms) >= MDNS_ANNOUNCE_INTERVAL_MS) { - g_mdns_host_last_tx_ms = now; - - uint8_t pkt[900]; - mdns_pkt_t p; - if (mdns_pkt_begin(&p, pkt, sizeof(pkt), (uint16_t)(DNS_SD_FLAG_QR | DNS_SD_FLAG_AA))) { - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (g_mdns_ipv4) mdns_pkt_add_a(&p, false, g_mdns_fqdn, flush_class, MDNS_TTL_S, g_mdns_ipv4); - - uint8_t ip6[16]; - memcpy(ip6, g_mdns_ipv6, 16); - if (!ip6[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6); - if (ip6[0]) mdns_pkt_add_aaaa(&p, false, g_mdns_fqdn, flush_class, MDNS_TTL_S, ip6); - - mdns_pkt_commit(&p); - - if (sock4 && mcast_v4) mdns_send(sock4, 0, false, IP_VER4, mcast_v4, pkt, p.off); - if (sock6 && mcast_v6) mdns_send(sock6, 0, false, IP_VER6, mcast_v6, pkt, p.off); - - uint32_t ttl_ms = MDNS_TTL_S * 1000; +void mdns_responder_tick_multi(const mdns_tx_target_t *targets, uint32_t target_count) { + if (!target_count) return; - if (g_mdns_ipv4) { - uint8_t ip4[16]; - memset(ip4, 0, sizeof(ip4)); - memcpy(ip4, &g_mdns_ipv4, 4); - dns_cache_put_ip(g_mdns_fqdn, DNS_SD_TYPE_A, ip4, ttl_ms); + uint64_t now = get_time(); + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_link_state_t* host = &g_mdns_host_link[slot]; + if (!host->probe.next_ms || host->probe.ready || now < host->probe.next_ms) continue; + + if (host->probe.sent < MDNS_PROBE_COUNT) { + bool sent = false; + for (uint32_t t = 0; t < target_count; t++) { + if (targets[t].ifindex != slot + 1) continue; + l2_interface_t* l2 = mdns_l2(targets[t].l3_id); + if (!l2) continue; + + dns_record_t records[8]; + uint32_t count = mdns_host_records(l2, records, N_ARR(records)); + if (!count) continue; + + uint8_t pkt[900]; + uint32_t pkt_len = 0; + if (!mdns_probe_packet(pkt, sizeof(pkt), g_mdns_fqdn, records, count, &pkt_len)) continue; + if (mdns_send(targets[t].sock, NULL, false, targets[t].ver, targets[t].mcast_ip, pkt, pkt_len)) sent = true; + } + if (sent) { + host->probe.sent++; + host->probe.next_ms = now + MDNS_PROBE_INTERVAL_MS; + } + } else { + host->probe.ready = true; + host->announce_left = MDNS_ANNOUNCE_BURST; + host->last_tx_ms = 0; + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t *s = &g_mdns_services[i]; + if (s->used && s->active && s->link[slot].probe.ready) { + s->link[slot].announce_left = MDNS_ANNOUNCE_BURST; + s->link[slot].last_tx_ms = 0; } - - uint8_t ip6_cache[16]; - memcpy(ip6_cache, g_mdns_ipv6, 16); - if (!ip6_cache[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6_cache); - if (ip6_cache[0]) dns_cache_put_ip(g_mdns_fqdn, DNS_SD_TYPE_AAAA, ip6_cache, ttl_ms); } - - g_mdns_host_announce_left--; } } for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { mdns_service_t *s = &g_mdns_services[i]; - if (!s->used) continue; - - bool do_goodbye = false; - if (s->goodbye_left) do_goodbye = true; - if (!do_goodbye && !s->active) continue; - if (!do_goodbye && !s->announce_left) continue; - - uint32_t interval = MDNS_ANNOUNCE_INTERVAL_MS; - if (!s->last_tx_ms || (now - s->last_tx_ms) >= interval) { - s->last_tx_ms = now; - - uint8_t pkt[900]; - mdns_pkt_t p; - if (mdns_pkt_begin(&p, pkt, sizeof(pkt), (uint16_t)(DNS_SD_FLAG_QR | DNS_SD_FLAG_AA))) { - mdns_add_service_records(&p, s, MDNS_TTL_S, do_goodbye); - mdns_pkt_commit(&p); - - if (sock4 && mcast_v4) mdns_send(sock4, 0, false, IP_VER4, mcast_v4, pkt, p.off); - if (sock6 && mcast_v6) mdns_send(sock6, 0, false, IP_VER6, mcast_v6, pkt, p.off); - } - - if (do_goodbye) { - if (s->goodbye_left) s->goodbye_left--; - if (!s->goodbye_left) { - memset(s, 0, sizeof(*s)); + if (!s->used || !s->active) continue; + + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_link_state_t* link = &s->link[slot]; + if (link->probe.ready || !link->probe.next_ms || now < link->probe.next_ms) continue; + + if (link->probe.sent < MDNS_PROBE_COUNT) { + dns_record_t records[2]; + mdns_service_records(s, s->name_index, records); + bool sent = false; + + uint8_t pkt[900]; + uint32_t pkt_len = 0; + if (mdns_probe_packet(pkt, sizeof(pkt), records[0].name, records, 2, &pkt_len)) { + for (uint32_t t = 0; t < target_count; t++) { + if (targets[t].ifindex != slot + 1) continue; + if (mdns_send(targets[t].sock, NULL, false, targets[t].ver, targets[t].mcast_ip, pkt, pkt_len)) sent = true; + } + } + if (sent) { + link->probe.sent++; + link->probe.next_ms = now + MDNS_PROBE_INTERVAL_MS; } } else { - if (s->announce_left) s->announce_left--; + link->probe.ready = true; + if (g_mdns_host_link[slot].probe.ready) { + link->announce_left = MDNS_ANNOUNCE_BURST; + link->last_tx_ms = 0; + } } } } -} -void mdns_responder_handle_query(socket_handle_t sock, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len, const net_l4_endpoint *src) { - if (!sock) return; - if (!mcast_ip) return; - if (!pkt) return; - if (pkt_len < 12) return; + if (g_mdns_host_goodbye_left) { + char old_name[72]; + if (g_mdns_old_host_name_index < 2) string_format_buf(old_name, sizeof(old_name), "%s.local", MDNS_HOST_NAME); + else string_format_buf(old_name, sizeof(old_name), "%s-%u.local", MDNS_HOST_NAME, (uint32_t)g_mdns_old_host_name_index); + bool sent = false; + for (uint32_t t = 0; t < target_count; t++) { + l2_interface_t* l2 = mdns_l2(targets[t].l3_id); + if (!l2) continue; - mdns_refresh_identity(); + uint8_t pkt[900]; + mdns_pkt_t p; + if (!mdns_pkt_begin(&p, pkt, sizeof(pkt), DNS_FLAG_QR | DNS_FLAG_AA)) continue; + int added = mdns_add_host_records(&p, false, l2, old_name, MDNS_FLUSH_CLASS, 0); + if (added <= 0) continue; + uint32_t slot = l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES ? (uint32_t)l2->ifindex - 1u : UINT32_MAX; + if (mdns_send_mcast(targets[t].sock, targets[t].ver, targets[t].mcast_ip, slot, pkt, p.off, now)) sent = true; + } - uint16_t flags = be16(*(const uint16_t *)(pkt + 2)); - if (flags & DNS_SD_FLAG_QR) { - mdns_cache_from_packet(pkt, pkt_len); - return; + if (sent) { + g_mdns_host_goodbye_left--; + if (!g_mdns_host_goodbye_left) { + g_mdns_host_advertised = false; + memset(g_mdns_host_link, 0, sizeof(g_mdns_host_link)); + } + } } - uint16_t qd = be16(*(const uint16_t *)(pkt + 4)); - if (!qd) return; - - bool unicast_any = false; + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_link_state_t* host = &g_mdns_host_link[slot]; + if (!host->probe.ready || !host->announce_left) continue; + if (host->last_tx_ms && (now - host->last_tx_ms) < MDNS_ANNOUNCE_INTERVAL_MS) continue; - uint8_t out[1500]; - mdns_pkt_t p; - if (!mdns_pkt_begin(&p, out, sizeof(out), (uint16_t)(DNS_SD_FLAG_QR | DNS_SD_FLAG_AA))) return; + bool sent = false; + for (uint32_t t = 0; t < target_count; t++) { + if (targets[t].ifindex != slot + 1) continue; + l2_interface_t* l2 = mdns_l2(targets[t].l3_id); + if (!l2) continue; - uint32_t qoff = 12; - - for (uint16_t qi = 0; qi < qd; qi++) { - char qname[256]; - uint32_t next = 0; - if (!mdns_read_name(pkt, pkt_len, qoff, qname, sizeof(qname), &next)) return; - if (next + 4 > pkt_len) return; - - uint16_t qtype = be16(*(const uint16_t *)(pkt + next)); - uint16_t qclass = be16(*(const uint16_t *)(pkt + next + 2)); - if ((qclass & 0x8000u) != 0) unicast_any = true; - - uint32_t ipq = 0; - if (qtype == DNS_SD_TYPE_PTR && g_mdns_ipv4 && mdns_parse_ipv4_ptr_qname(qname, &ipq) && ipq == g_mdns_ipv4) { - uint16_t ptr_class = DNS_SD_CLASS_IN; - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - - if (!mdns_pkt_add_ptr(&p, false, qname, ptr_class, MDNS_TTL_S, g_mdns_fqdn)) return; - if (!mdns_pkt_add_a(&p, true, g_mdns_fqdn, flush_class, MDNS_TTL_S, g_mdns_ipv4)) return; - - uint8_t ip6[16]; - memcpy(ip6, g_mdns_ipv6, 16); - if (!ip6[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6); - if (ip6[0]) { - if (!mdns_pkt_add_aaaa(&p, true, g_mdns_fqdn, flush_class, MDNS_TTL_S, ip6)) return; + uint8_t pkt[900]; + mdns_pkt_t p; + if (!mdns_pkt_begin(&p, pkt, sizeof(pkt), DNS_FLAG_QR | DNS_FLAG_AA)) continue; + int added = mdns_add_host_records(&p, false, l2, g_mdns_fqdn, MDNS_FLUSH_CLASS, MDNS_TTL_S); + if (added <= 0) continue; + if (mdns_send_mcast(targets[t].sock, targets[t].ver, targets[t].mcast_ip, slot, pkt, p.off, now)) { + sent = true; + mdns_cache_host(l2); } } - - if (qtype == DNS_SD_TYPE_A || qtype == DNS_SD_TYPE_ANY) { - if (strncmp(qname, g_mdns_fqdn, 256) == 0 && g_mdns_ipv4) { - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (!mdns_pkt_add_a(&p, false, g_mdns_fqdn, flush_class, MDNS_TTL_S, g_mdns_ipv4)) return; - } + if (sent) { + g_mdns_host_advertised = true; + host->last_tx_ms = now; + host->announce_left--; } + } - if (qtype == DNS_SD_TYPE_AAAA || qtype == DNS_SD_TYPE_ANY) { - if (strncmp(qname, g_mdns_fqdn, 256) == 0) { - uint8_t ip6[16]; - memcpy(ip6, g_mdns_ipv6, 16); - if (!ip6[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6); - if (ip6[0]) { - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (!mdns_pkt_add_aaaa(&p, false, g_mdns_fqdn, flush_class, MDNS_TTL_S, ip6)) return; - } + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t *s = &g_mdns_services[i]; + if (!s->used) continue; + if (s->goodbye_left && s->last_tx_ms && (now - s->last_tx_ms) < MDNS_ANNOUNCE_INTERVAL_MS) continue; + + bool other_type = false; + if (s->goodbye_left && s->retire) { + for (uint32_t j = 0; j < MDNS_MAX_SERVICES; j++) { + if (j == i) continue; + const mdns_service_t* other = &g_mdns_services[j]; + if (!other->used || !other->active) continue; + if (strncmp(other->service, s->service, (int)sizeof(other->service)) != 0) continue; + if (strncmp_case(other->proto, s->proto, true, (int)sizeof(other->proto)) != 0) continue; + other_type = true; + break; } } + bool add_enum = !s->goodbye_left || (s->retire && !other_type); + uint16_t name_index = s->goodbye_left ? s->old_name_index : s->name_index; + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_link_state_t* link = NULL; + if (s->goodbye_left) { + if (slot) break; + } else { + if (!s->active) break; + link = &s->link[slot]; + if (!link->probe.ready || !g_mdns_host_link[slot].probe.ready || !link->announce_left) continue; + if (link->last_tx_ms && (now - link->last_tx_ms) < MDNS_ANNOUNCE_INTERVAL_MS) continue; + } - if ((qtype == DNS_SD_TYPE_PTR || qtype == DNS_SD_TYPE_ANY) && - strncmp(qname, DNS_SD_ENUM_SERVICES, 256) == 0) { - uint16_t ptr_class = DNS_SD_CLASS_IN; + bool sent = false; + for (uint32_t t = 0; t < target_count; t++) { + if (!s->goodbye_left && targets[t].ifindex != slot + 1) continue; + l2_interface_t* l2 = mdns_l2(targets[t].l3_id); + if (!l2) continue; + uint32_t l2_slot = l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES ? (uint32_t)l2->ifindex - 1u : UINT32_MAX; - for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { - if (!g_mdns_services[i].used) continue; - if (!g_mdns_services[i].active) continue; + uint8_t pkt[900]; + mdns_pkt_t p; + if (!mdns_pkt_begin(&p, pkt, sizeof(pkt), DNS_FLAG_QR | DNS_FLAG_AA)) continue; char type[128]; - mdns_make_service_type(type,sizeof(type), g_mdns_services[i].service, g_mdns_services[i].proto); - - bool seen = false; - for (uint32_t j = 0; j < i; j++) { - if (!g_mdns_services[j].used) continue; - if (!g_mdns_services[j].active) continue; - - char type2[128]; - mdns_make_service_type(type2,sizeof(type2),g_mdns_services[j].service, g_mdns_services[j].proto); - if (strncmp(type2, type, 128) == 0) { - seen = true; - break; - } + string_format_buf(type, sizeof(type), "_%s._%s.local", s->service, s->proto); + dns_record_t unique[2]; + mdns_service_records(s, name_index, unique); + if (!unique[0].name[0] || !s->service[0] || !s->proto[0]) continue; + + uint32_t ttl = s->goodbye_left ? 0 : MDNS_TTL_S; + dns_record_t record; + if (add_enum) { + mdns_record(&record, DNS_SD_ENUM_SERVICES, DNS_TYPE_PTR); + strncpy(record.target, type, sizeof(record.target)); + if (!mdns_pkt_add(&p, false, &record, DNS_CLASS_IN, ttl)) continue; } - if (seen) continue; - if (!mdns_pkt_add_ptr(&p, false, DNS_SD_ENUM_SERVICES, ptr_class, MDNS_TTL_S, type)) return; - } - } - - bool need_host_add = false; - for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { - if (!g_mdns_services[i].used) continue; - if (!g_mdns_services[i].active) continue; - - mdns_service_t *s = &g_mdns_services[i]; - - char type[128]; - char inst[256]; - mdns_make_service_type(type, sizeof(type), s->service, s->proto); - inst[0] = 0; - if (!s->instance[0] || !s->service[0] || !s->proto[0]) continue; - string_format_buf(inst, sizeof(inst), "%s._%s._%s.local", s->instance, s->service, s->proto); - - if ((qtype == DNS_SD_TYPE_PTR || qtype == DNS_SD_TYPE_ANY) && - strncmp(qname, type, 256) == 0) { - uint16_t ptr_class = DNS_SD_CLASS_IN; - if (!mdns_pkt_add_ptr(&p, false, type, ptr_class, MDNS_TTL_S, inst)) return; - - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (!mdns_pkt_add_srv(&p, true, inst, flush_class, MDNS_TTL_S, s->port, g_mdns_fqdn)) return; - if (!mdns_pkt_add_txt(&p, true, inst, flush_class, MDNS_TTL_S, s->txt)) return; - need_host_add = true; + mdns_record(&record, type, DNS_TYPE_PTR); + strncpy(record.target, unique[0].name, sizeof(record.target)); + if (!mdns_pkt_add(&p, false, &record, DNS_CLASS_IN, ttl)) continue; + if (!mdns_pkt_add(&p, true, &unique[1], MDNS_FLUSH_CLASS, ttl)) continue; + if (!mdns_pkt_add(&p, true, &unique[0], MDNS_FLUSH_CLASS, ttl)) continue; + if (!s->goodbye_left && mdns_add_host_records(&p, true, l2, g_mdns_fqdn, MDNS_FLUSH_CLASS, MDNS_TTL_S) < 0) continue; + if (mdns_send_mcast(targets[t].sock, targets[t].ver, targets[t].mcast_ip, l2_slot, pkt, p.off, now)) sent = true; } + if (!sent) continue; - if ((qtype == DNS_SD_TYPE_SRV || qtype == DNS_SD_TYPE_ANY) && - strncmp(qname, inst, 256) == 0) { - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (!mdns_pkt_add_srv(&p, false, inst, flush_class, MDNS_TTL_S, s->port, g_mdns_fqdn)) return; - need_host_add = true; - } - - if ((qtype == DNS_SD_TYPE_TXT || qtype == DNS_SD_TYPE_ANY) && - strncmp(qname, inst, 256) == 0) { - uint16_t flush_class = (uint16_t)(0x8000u | DNS_SD_CLASS_IN); - if (!mdns_pkt_add_txt(&p, false, inst, flush_class, MDNS_TTL_S, s->txt)) return; + if (s->goodbye_left) { + s->last_tx_ms = now; + s->goodbye_left--; + if (!s->goodbye_left) { + if (s->retire) memset(s, 0, sizeof(*s)); + else s->last_tx_ms = 0; + } + break; + } else { + s->advertised = true; + link->last_tx_ms = now; + link->announce_left--; } } - - if (need_host_add) { - if (!mdns_add_host_additionals(&p)) return; - } - - qoff = next + 4; - if (qoff > pkt_len) return; } - - if (!p.an && !p.ar) return; - - mdns_pkt_commit(&p); - mdns_send(sock, src, unicast_any, ver, mcast_ip, out, p.off); - - uint32_t ttl_ms = MDNS_TTL_S * 1000; - - if (g_mdns_ipv4) { - uint8_t ip4[16]; - memset(ip4, 0, sizeof(ip4)); - memcpy(ip4, &g_mdns_ipv4, 4); - dns_cache_put_ip(g_mdns_fqdn, DNS_SD_TYPE_A, ip4, ttl_ms); - } - - uint8_t ip6[16]; - memcpy(ip6, g_mdns_ipv6, 16); - if (!ip6[0] && g_mdns_ifindex) ipv6_make_lla_from_mac(g_mdns_ifindex, ip6); - if (ip6[0]) dns_cache_put_ip(g_mdns_fqdn, DNS_SD_TYPE_AAAA, ip6, ttl_ms); -} \ No newline at end of file +} diff --git a/kernel/networking/application_layer/dns/mdns_responder.h b/kernel/networking/application_layer/dns/mdns_responder.h index be14dd07..94a25583 100644 --- a/kernel/networking/application_layer/dns/mdns_responder.h +++ b/kernel/networking/application_layer/dns/mdns_responder.h @@ -1,5 +1,5 @@ #pragma once -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #include "net/network_types.h" #ifdef __cplusplus @@ -9,9 +9,6 @@ extern "C" { bool mdns_register_service(const char *instance, const char *service, const char *proto, uint16_t port, const char *txt); bool mdns_deregister_service(const char *instance, const char *service, const char *proto); -void mdns_responder_tick(socket_handle_t sock4, socket_handle_t sock6, const uint8_t mcast_v4[4], const uint8_t mcast_v6[16]); -void mdns_responder_handle_query(socket_handle_t sock, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len, const net_l4_endpoint *src); - #ifdef __cplusplus } #endif diff --git a/kernel/networking/application_layer/dns/mdns_rx.c b/kernel/networking/application_layer/dns/mdns_rx.c new file mode 100644 index 00000000..d0db5873 --- /dev/null +++ b/kernel/networking/application_layer/dns/mdns_rx.c @@ -0,0 +1,483 @@ +#include "mdns_internal.h" + +#include "dns_cache.h" +#include "dns_sd.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "data/hash.h" +#include "std/memory.h" +#include "std/string.h" +#include "syscalls/syscalls.h" + +#define MDNS_QUERY_DEDUP_MAX 8 +#define MDNS_QUERY_DEDUP_MS 250 +#define MDNS_PROBE_TIE_RETRY_MS 1000 + +typedef struct { + bool used; + uint8_t l2_slot; + ip_version_t ver; + uint16_t port; + uint64_t hash; + uint64_t last_ms; + uint8_t ip[16]; +} mdns_query_dedup_t; + +static mdns_query_dedup_t g_mdns_query_dedup[MDNS_QUERY_DEDUP_MAX]; +static uint8_t g_mdns_query_dedup_next = 0; + +static int mdns_probe_compare(const dns_record_t* own, uint32_t own_count, const dns_record_t* records, uint32_t record_count, const char* name) { + const dns_record_t* lhs[8]; + const dns_record_t* rhs[8]; + uint32_t lc = 0; + uint32_t rc = 0; + + for (uint32_t i = 0; i < own_count && lc < N_ARR(lhs); i++) lhs[lc++] = &own[i]; + for (uint32_t i = 0; i < record_count && rc < N_ARR(rhs); i++) { + if (records[i].section != DNS_SECTION_AUTHORITY) continue; + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if (!dns_wire_name_equals(records[i].name, name)) continue; + rhs[rc++] = &records[i]; + } + if (!rc) return 0; + + for (uint32_t i = 1; i < lc; i++) { + const dns_record_t* cur = lhs[i]; + uint32_t j = i; + for (; j && dns_wire_record_cmp(lhs[j - 1], cur) > 0; j--) lhs[j] = lhs[j - 1]; + lhs[j] = cur; + } + for (uint32_t i = 1; i < rc; i++) { + const dns_record_t* cur = rhs[i]; + uint32_t j = i; + for (; j && dns_wire_record_cmp(rhs[j - 1], cur) > 0; j--) rhs[j] = rhs[j - 1]; + rhs[j] = cur; + } + for (uint32_t i = 0; i < lc && i < rc; i++) { + int cmp = dns_wire_record_cmp(lhs[i], rhs[i]); + if (cmp) return cmp; + } + if (lc == rc) return 0; + return lc > rc ? 1 : -1; +} + +static int mdns_add_answer(mdns_pkt_t* p, bool additional, const dns_record_t* record, const dns_record_t* known, uint32_t known_count, uint16_t rrclass, uint32_t ttl_s) { + for (uint32_t i = 0; i < known_count; i++) { + if (known[i].section != DNS_SECTION_ANSWER) continue; + if (known[i].ttl_s < record->ttl_s / 2) continue; + if (dns_wire_record_equal(&known[i], record)) return 0; + } + return mdns_pkt_add(p, additional, record, rrclass, ttl_s) ? 1 : -1; +} + +static void mdns_host_conflict(void) { + if (g_mdns_host_advertised && !g_mdns_host_goodbye_left) { + g_mdns_old_host_name_index = g_mdns_host_name_index; + g_mdns_host_goodbye_left = MDNS_GOODBYE_BURST; + } + + g_mdns_host_advertised = false; + if (g_mdns_host_name_index < 2 || g_mdns_host_name_index == UINT16_MAX) g_mdns_host_name_index = 2; + else g_mdns_host_name_index++; + string_format_buf(g_mdns_fqdn, sizeof(g_mdns_fqdn), "%s-%u.local", MDNS_HOST_NAME, (uint32_t)g_mdns_host_name_index); + + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_probe_start(&g_mdns_host_link[slot].probe, MDNS_PROBE_RETRY_MS); + g_mdns_host_link[slot].announce_left = 0; + g_mdns_host_link[slot].last_tx_ms = 0; + + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t* s = &g_mdns_services[i]; + if (!s->used || !s->active) continue; + if (!s->link[slot].probe.ready) mdns_probe_start(&s->link[slot].probe, MDNS_PROBE_RETRY_MS); + s->link[slot].announce_left = 0; + s->link[slot].last_tx_ms = 0; + } + } +} + +static void mdns_service_conflict(mdns_service_t* s) { + if (s->advertised && !s->goodbye_left) { + s->old_name_index = s->name_index; + s->goodbye_left = MDNS_GOODBYE_BURST; + s->retire = false; + } + + s->advertised = false; + if (s->name_index < 2 || s->name_index == UINT16_MAX) s->name_index = 2; + else s->name_index++; + for (uint32_t slot = 0; slot < MAX_L2_INTERFACES; slot++) { + mdns_probe_start(&s->link[slot].probe, MDNS_PROBE_RETRY_MS); + s->link[slot].announce_left = 0; + s->link[slot].last_tx_ms = 0; + } +} + +static bool mdns_has_conflict(const dns_record_t* records, uint32_t count, const dns_record_t* own, uint32_t own_count) { + for (uint32_t i = 0; i < count; i++) { + if ((records[i].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + bool own_type = false; + bool same = false; + for (uint32_t j = 0; j < own_count; j++) { + if (records[i].type != own[j].type) continue; + if (!dns_wire_name_equals(records[i].name, own[j].name)) continue; + own_type = true; + if (dns_wire_record_equal(&records[i], &own[j])) { + same = true; + break; + } + } + if (own_type && !same) return true; + } + return false; +} + +static void mdns_rx_response(l2_interface_t* l2, l3_id_t l3_id, dns_record_t* records, uint32_t count) { + if (!l2->ifindex || l2->ifindex > MAX_L2_INTERFACES) return; + uint32_t l2_slot = (uint32_t)l2->ifindex - 1; + + bool host_name = false; + for (uint32_t i = 0; i < count; i++) { + dns_record_t* r = &records[i]; + if ((r->rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if (dns_wire_name_equals(r->name, g_mdns_fqdn)) host_name = true; + if (r->type == DNS_TYPE_A || r->type == DNS_TYPE_AAAA) { + uint32_t ttl_ms = r->ttl_s > (UINT32_MAX-1) / 1000 ? UINT32_MAX - 1 : r->ttl_s * 1000; + if (!r->ttl_s) dns_cache_remove_ip(l3_id, r->name, r->type); + else dns_cache_put_ip(l3_id, r->name, r->type, r->addr, ttl_ms); + } + } + + if (g_mdns_host_link[l2_slot].probe.sent && !g_mdns_host_link[l2_slot].probe.ready && host_name) { + mdns_host_conflict(); + return; + } + + if (g_mdns_host_link[l2_slot].probe.ready) { + dns_record_t own[8]; + uint32_t own_count = mdns_host_records(l2, own, N_ARR(own)); + if (own_count && mdns_has_conflict(records, count, own, own_count)) { + mdns_host_conflict(); + return; + } + } + + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t* s = &g_mdns_services[i]; + if (!s->used || !s->active) continue; + + char inst[256]; + mdns_instance_name(inst, sizeof(inst), s, s->name_index); + if (s->link[l2_slot].probe.sent && !s->link[l2_slot].probe.ready) { + bool seen = false; + for (uint32_t j = 0; j < count; j++) { + if ((records[j].rrclass & DNS_CLASS_MASK) != DNS_CLASS_IN) continue; + if (!dns_wire_name_equals(records[j].name, inst)) continue; + seen = true; + break; + } + if (seen) { + mdns_service_conflict(s); + continue; + } + } + if (!s->link[l2_slot].probe.ready) continue; + + dns_record_t own[2]; + mdns_service_records(s, s->name_index, own); + if (mdns_has_conflict(records, count, own, 2)) mdns_service_conflict(s); + } +} + +static void mdns_rx_probe(l2_interface_t* l2, const uint8_t* pkt, uint32_t pkt_len, uint16_t qd, const dns_record_t* records, uint32_t count) { + if (!l2->ifindex || l2->ifindex > MAX_L2_INTERFACES) return; + uint32_t l2_slot = (uint32_t)l2->ifindex - 1; + + uint32_t off = 12; + for (uint16_t qi = 0; qi < qd; qi++) { + char qname[256]; + uint32_t next = 0; + if (!dns_wire_read_name(pkt, pkt_len, off, qname, sizeof(qname), &next)) break; + if (next + 4 > pkt_len) break; + + uint16_t qtype = rd_be16(pkt + next); + uint16_t qclass = rd_be16(pkt + next + 2) & DNS_CLASS_MASK; + off = next + 4; + if (qtype != DNS_TYPE_ANY || (qclass != DNS_CLASS_IN && qclass != DNS_CLASS_ANY)) continue; + + if (!g_mdns_host_link[l2_slot].probe.ready && g_mdns_host_link[l2_slot].probe.sent && dns_wire_name_equals(qname, g_mdns_fqdn)) { + dns_record_t own[8]; + uint32_t own_count = mdns_host_records(l2, own, N_ARR(own)); + if (own_count && mdns_probe_compare(own, own_count, records, count, g_mdns_fqdn) < 0) { + mdns_probe_start(&g_mdns_host_link[l2_slot].probe, MDNS_PROBE_TIE_RETRY_MS); + g_mdns_host_link[l2_slot].announce_left = 0; + g_mdns_host_link[l2_slot].last_tx_ms = 0; + } + } + + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t* s = &g_mdns_services[i]; + if (!s->used || !s->active || s->link[l2_slot].probe.ready || !s->link[l2_slot].probe.sent) continue; + + char inst[256]; + mdns_instance_name(inst, sizeof(inst), s, s->name_index); + if (!dns_wire_name_equals(qname, inst)) continue; + + dns_record_t own[2]; + mdns_service_records(s, s->name_index, own); + if (mdns_probe_compare(own, 2, records, count, inst) < 0) { + mdns_probe_start(&s->link[l2_slot].probe, MDNS_PROBE_TIE_RETRY_MS); + s->link[l2_slot].announce_left = 0; + s->link[l2_slot].last_tx_ms = 0; + } + } + } +} + +void mdns_rx(socket_handle_t sock, l3_id_t l3_id, ip_version_t ver, const uint8_t *mcast_ip, const uint8_t *pkt, uint32_t pkt_len, const net_l4_endpoint *src) { + if (pkt_len < 12) return; + + l2_interface_t* l2 = mdns_l2(l3_id); + if (!l2) return; + if (!l2->ifindex || l2->ifindex > MAX_L2_INTERFACES) return; + uint32_t l2_slot = (uint32_t)l2->ifindex - 1; + + dns_record_t records[48]; + uint32_t record_count = 0; + uint16_t flags = 0; + if (!dns_wire_parse_records(pkt, pkt_len, false, 0, records, N_ARR(records), &record_count, &flags)) return; + if (flags & (DNS_OPCODE_MASK | DNS_RCODE_MASK)) return; + + if (flags & DNS_FLAG_QR) { + if (src->port != DNS_MDNS_PORT) return; + mdns_rx_response(l2, l3_id, records, record_count); + return; + } + + uint16_t qd = rd_be16(pkt + 4); + if (!qd) return; + + mdns_rx_probe(l2, pkt, pkt_len, qd, records, record_count); + + uint64_t now = get_time(); + uint64_t hash = hash_map_fnv1a64(pkt, pkt_len); + for (uint32_t i = 0; i < MDNS_QUERY_DEDUP_MAX; i++) { + mdns_query_dedup_t *e = &g_mdns_query_dedup[i]; + if (!e->used || e->l2_slot != l2_slot || e->ver != ver || e->port != src->port || e->hash != hash) continue; + if (memcmp(e->ip, src->ip, ver == IP_VER4 ? 4 : 16) != 0) continue; + if (now - e->last_ms >= MDNS_QUERY_DEDUP_MS) continue; + e->last_ms = now; + return; + } + + mdns_query_dedup_t *dedup = &g_mdns_query_dedup[g_mdns_query_dedup_next++ % MDNS_QUERY_DEDUP_MAX]; + memset(dedup, 0, sizeof(*dedup)); + dedup->used = true; + dedup->l2_slot = (uint8_t)l2_slot; + dedup->ver = ver; + dedup->port = src->port; + dedup->hash = hash; + dedup->last_ms = now; + memcpy(dedup->ip, src->ip, ver == IP_VER4 ? 4 : 16); + + bool legacy = src->port && src->port != DNS_MDNS_PORT; + bool want_unicast = false; + bool need_mcast = false; + uint32_t ttl = legacy ? 10 : MDNS_TTL_S; + uint16_t unique_class = legacy ? DNS_CLASS_IN : MDNS_FLUSH_CLASS; + const dns_record_t* known = legacy ? NULL : records; + uint32_t known_count = legacy ? 0 : record_count; + + uint8_t out[1500]; + mdns_pkt_t p; + uint16_t reply_flags = DNS_FLAG_QR | DNS_FLAG_AA; + if (legacy) reply_flags |= rd_be16(pkt + 2) & DNS_FLAG_RD; + if (!mdns_pkt_begin(&p, out, sizeof(out), reply_flags)) return; + if (legacy) { + uint32_t qend = 12; + for (uint16_t i = 0; i < qd; i++) { + if (!dns_wire_read_name(pkt, pkt_len, qend, NULL, 0, &qend)) return; + if (qend + 4 > pkt_len) return; + qend += 4; + } + if (qend > sizeof(out)) return; + memcpy(out + 12, pkt + 12, qend - 12); + p.off = qend; + wr_be16(out, rd_be16(pkt)); + wr_be16(out + 4, qd); + } + + dns_record_t host_records[8]; + uint32_t host_count = g_mdns_host_link[l2_slot].probe.ready ? mdns_host_records(l2, host_records, N_ARR(host_records)) : 0; + bool has_host = false; + uint32_t service_bits = 0; + uint32_t qoff = 12; + + for (uint16_t qi = 0; qi < qd; qi++) { + char qname[256]; + uint32_t next = 0; + if (!dns_wire_read_name(pkt, pkt_len, qoff, qname, sizeof(qname), &next)) return; + if (next + 4 > pkt_len) return; + + uint16_t qtype = rd_be16(pkt + next); + uint16_t qclass = rd_be16(pkt + next + 2); + qoff = next + 4; + if ((qclass & DNS_CLASS_MASK) != DNS_CLASS_IN && (qclass & DNS_CLASS_MASK) != DNS_CLASS_ANY) continue; + bool qu = (qclass & DNS_CLASS_UNICAST_RESPONSE) != 0; + uint16_t records_before = p.an + p.ar; + + if (g_mdns_host_link[l2_slot].probe.ready && (qtype == DNS_TYPE_PTR || qtype == DNS_TYPE_ANY)) { + char norm[DNS_WIRE_MAX_NAME]; + uint32_t ipq = 0; + bool valid_reverse = dns_wire_name_normalize(qname, norm, sizeof(norm)); + if (valid_reverse) { + uint8_t ip[4]; + const char *p = norm; + for (int i = 3; i >= 0; i--) { + const char *label = p; + while (*label && *label != '.') label++; + uint32_t v = 0; + if (*label != '.' || label - p > 3 || !parse_uint32_dec_len(p, label - p, &v) || v > 255) { + valid_reverse = false; + break; + } + ip[i] = v; + p = label + 1; + } + if (valid_reverse) { + if (strcmp(p, "in-addr.arpa") != 0) valid_reverse = false; + else ipq = rd_be32(ip); + } + } + + if (valid_reverse) { + for (uint8_t i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[i]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost || v4->ip != ipq) continue; + + dns_record_t ptr; + mdns_record(&ptr, qname, DNS_TYPE_PTR); + strncpy(ptr.target, g_mdns_fqdn, sizeof(ptr.target)); + int ans = mdns_add_answer(&p, false, &ptr, known, known_count, DNS_CLASS_IN, ttl); + if (ans < 0) return; + if (ans) { + has_host = true; + for (uint32_t h = 0; h < host_count; h++) { + int added = mdns_add_answer(&p, true, &host_records[h], known, known_count, unique_class, ttl); + if (added < 0) return; + has_host |= added != 0; + } + } + break; + } + } + } + + if (g_mdns_host_link[l2_slot].probe.ready && dns_wire_name_equals(qname, g_mdns_fqdn)) { + for (uint32_t h = 0; h < host_count; h++) { + if (qtype != DNS_TYPE_ANY && qtype != host_records[h].type) continue; + int added = mdns_add_answer(&p, false, &host_records[h], known, known_count, unique_class, ttl); + if (added < 0) return; + has_host |= added != 0; + } + } + + if ((qtype == DNS_TYPE_PTR || qtype == DNS_TYPE_ANY) && dns_wire_name_equals(qname, DNS_SD_ENUM_SERVICES)) { + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t* s = &g_mdns_services[i]; + if (!(s->used && s->active && s->link[l2_slot].probe.ready && g_mdns_host_link[l2_slot].probe.ready)) continue; + + char type[128]; + string_format_buf(type, sizeof(type), "_%s._%s.local", s->service, s->proto); + bool seen = false; + for (uint32_t j = 0; j < i; j++) { + if (!(g_mdns_services[j].used && g_mdns_services[j].active && g_mdns_services[j].link[l2_slot].probe.ready && g_mdns_host_link[l2_slot].probe.ready)) continue; + char type2[128]; + string_format_buf(type2, sizeof(type2), "_%s._%s.local", g_mdns_services[j].service, g_mdns_services[j].proto); + if (dns_wire_name_equals(type2, type)) { + seen = true; + break; + } + } + if (seen) continue; + + dns_record_t ptr; + mdns_record(&ptr, DNS_SD_ENUM_SERVICES, DNS_TYPE_PTR); + strncpy(ptr.target, type, sizeof(ptr.target)); + int added = mdns_add_answer(&p, false, &ptr, known, known_count, DNS_CLASS_IN, ttl); + if (added < 0) return; + if (added) service_bits |= 1u << i; + } + } + + bool need_host_add = false; + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) { + mdns_service_t* s = &g_mdns_services[i]; + if (!(s->used && s->active && s->link[l2_slot].probe.ready && g_mdns_host_link[l2_slot].probe.ready)) continue; + + char type[128]; + string_format_buf(type, sizeof(type), "_%s._%s.local", s->service, s->proto); + dns_record_t unique[2]; + + mdns_service_records(s, s->name_index, unique); + + if ((qtype == DNS_TYPE_PTR || qtype == DNS_TYPE_ANY) && dns_wire_name_equals(qname, type)) { + dns_record_t ptr; + mdns_record(&ptr, type, DNS_TYPE_PTR); + strncpy(ptr.target, unique[0].name, sizeof(ptr.target)); + int added = mdns_add_answer(&p, false, &ptr, known, known_count, DNS_CLASS_IN, ttl); + if (added < 0) return; + if (added) { + added = mdns_add_answer(&p, true, &unique[1], known, known_count, unique_class, ttl); + if (added < 0) return; + if (added) { + service_bits |= 1u << i; + need_host_add = true; + } + added = mdns_add_answer(&p, true, &unique[0], known, known_count, unique_class, ttl); + if (added < 0) return; + if (added) service_bits |= 1u << i; + } + } + + if ((qtype == DNS_TYPE_SRV || qtype == DNS_TYPE_ANY) && dns_wire_name_equals(qname, unique[0].name)) { + int added = mdns_add_answer(&p, false, &unique[1], known, known_count, unique_class, ttl); + if (added < 0) return; + if (added) { + service_bits |= 1u << i; + need_host_add = true; + } + } + + if ((qtype == DNS_TYPE_TXT || qtype == DNS_TYPE_ANY) && dns_wire_name_equals(qname, unique[0].name)) { + int added = mdns_add_answer(&p, false, &unique[0], known, known_count, unique_class, ttl); + if (added < 0) return; + if (added) service_bits |= 1u << i; + } + } + + if (need_host_add) { + for (uint32_t h = 0; h < host_count; h++) { + int added = mdns_add_answer(&p, true, &host_records[h], known, known_count, unique_class, ttl); + if (added < 0) return; + has_host |= added != 0; + } + } + + if (!legacy && p.an + p.ar != records_before) { + if (qu) want_unicast = true; + else need_mcast = true; + } + } + + if (!p.an && !p.ar) return; + bool sent; + if (legacy || (want_unicast && !need_mcast)) sent = mdns_send(sock, src, true, ver, mcast_ip, out, p.off); + else sent = mdns_send_mcast(sock, ver, mcast_ip, l2_slot, out, p.off, now); + if (!sent) return; + + if (has_host) { + g_mdns_host_advertised = true; + mdns_cache_host(l2); + } + for (uint32_t i = 0; i < MDNS_MAX_SERVICES; i++) if ((service_bits & (1u << i)) && g_mdns_services[i].used) g_mdns_services[i].advertised = true; +} diff --git a/kernel/networking/application_layer/http.c b/kernel/networking/application_layer/http.c index eebf2698..3cd85e2c 100644 --- a/kernel/networking/application_layer/http.c +++ b/kernel/networking/application_layer/http.c @@ -1,76 +1,356 @@ #include "http.h" #include "std/string.h" #include "std/memory.h" -#include "syscalls/syscalls.h" -string http_header_builder(const HTTPHeadersCommon *C, const HTTPHeader *H, uint32_t N){ - string out = string_repeat('\0', 0); +HTTPPolicy http_default_policy(void) { + HTTPPolicy p = { + .max_start_line = HTTP_DEFAULT_MAX_START_LINE, + .max_header_bytes = HTTP_DEFAULT_MAX_HEADER_BYTES, + .max_header_count = HTTP_DEFAULT_MAX_HEADER_COUNT, + .max_header_key_len = HTTP_DEFAULT_MAX_HEADER_KEY_LEN, + .max_header_value_len = HTTP_DEFAULT_MAX_HEADER_VALUE_LEN, + .max_path_len = HTTP_DEFAULT_MAX_PATH_LEN, + .max_body_bytes = HTTP_DEFAULT_MAX_BODY_BYTES, + .header_idle_timeout_ms = HTTP_DEFAULT_HEADER_IDLE_TIMEOUT_MS, + .header_total_timeout_ms = HTTP_DEFAULT_HEADER_TOTAL_TIMEOUT_MS, + .body_idle_timeout_ms = HTTP_DEFAULT_BODY_IDLE_TIMEOUT_MS, + .body_total_timeout_ms = HTTP_DEFAULT_BODY_TOTAL_TIMEOUT_MS, + .allow_chunked = true, + }; + return p; +} - if (C->type.length){ - string_append_bytes(&out, "Content-Type: ", 14); - string_append_bytes(&out, C->type.data, C->type.length); - string_append_bytes(&out, "\r\n", 2); - } +HTTPPolicy http_policy_apply_options(HTTPPolicy p, const HTTPPolicyOptions *options) { + if (!options) return p; + + uint32_t f = options->flags; + if (f & HTTP_POLICY_OPT_MAX_START_LINE) p.max_start_line = options->value.max_start_line; + if (f & HTTP_POLICY_OPT_MAX_HEADER_BYTES) p.max_header_bytes = options->value.max_header_bytes; + if (f & HTTP_POLICY_OPT_MAX_HEADER_COUNT) p.max_header_count = options->value.max_header_count; + if (f & HTTP_POLICY_OPT_MAX_HEADER_KEY_LEN) p.max_header_key_len = options->value.max_header_key_len; + if (f & HTTP_POLICY_OPT_MAX_HEADER_VALUE_LEN) p.max_header_value_len = options->value.max_header_value_len; + if (f & HTTP_POLICY_OPT_MAX_PATH_LEN) p.max_path_len = options->value.max_path_len; + if (f & HTTP_POLICY_OPT_MAX_BODY_BYTES) p.max_body_bytes = options->value.max_body_bytes; + if (f & HTTP_POLICY_OPT_HEADER_IDLE_TIMEOUT_MS) p.header_idle_timeout_ms = options->value.header_idle_timeout_ms; + if (f & HTTP_POLICY_OPT_HEADER_TOTAL_TIMEOUT_MS) p.header_total_timeout_ms = options->value.header_total_timeout_ms; + if (f & HTTP_POLICY_OPT_BODY_IDLE_TIMEOUT_MS) p.body_idle_timeout_ms = options->value.body_idle_timeout_ms; + if (f & HTTP_POLICY_OPT_BODY_TOTAL_TIMEOUT_MS) p.body_total_timeout_ms = options->value.body_total_timeout_ms; + if (f & HTTP_POLICY_OPT_ALLOW_CHUNKED) p.allow_chunked = options->value.allow_chunked; + return p; +} + +HTTPServerPolicy http_server_policy_from_options(const HTTPServerPolicyOptions *options) { + HTTPServerPolicy p = { + .common = http_default_policy(), + .max_keepalive_requests = HTTP_DEFAULT_MAX_KEEPALIVE_REQUESTS, + .allowed_methods = HTTP_METHOD_MASK_ALL, + .error_content_type = "text/plain", + .allow_keep_alive = true, + .allow_absolute_uri = true, + .require_host_http11 = true, + .send_error_body = true + }; + + if (!options) return p; + + HTTPPolicyOptions common = { + .value = options->value.common, + .flags = options->common_flags + }; + p.common = http_policy_apply_options(p.common, &common); + + uint32_t f = options->flags; + if (f & HTTP_SERVER_OPT_MAX_KEEPALIVE_REQUESTS) p.max_keepalive_requests = options->value.max_keepalive_requests; + if (f & HTTP_SERVER_OPT_ALLOWED_METHODS) p.allowed_methods = options->value.allowed_methods; + if (f & HTTP_SERVER_OPT_ERROR_CONTENT_TYPE) p.error_content_type = options->value.error_content_type; + if (f & HTTP_SERVER_OPT_ALLOW_KEEP_ALIVE) p.allow_keep_alive = options->value.allow_keep_alive; + if (f & HTTP_SERVER_OPT_ALLOW_ABSOLUTE_URI) p.allow_absolute_uri = options->value.allow_absolute_uri; + if (f & HTTP_SERVER_OPT_REQUIRE_HOST_HTTP11) p.require_host_http11 = options->value.require_host_http11; + if (f & HTTP_SERVER_OPT_SEND_ERROR_BODY) p.send_error_body = options->value.send_error_body; + return p; +} + +HTTPClientPolicy http_client_policy_from_options(const HTTPClientPolicyOptions *options) { + HTTPClientPolicy p = { + .common = http_default_policy(), + .max_redirects = HTTP_DEFAULT_MAX_REDIRECTS, + .follow_redirects = false, + .allow_close_delimited = true + }; + + if (!options) return p; + + HTTPPolicyOptions common = { + .value = options->value.common, + .flags = options->common_flags + }; + p.common = http_policy_apply_options(p.common, &common); - string tmp = string_format("Content-Length: %i\r\n", (int)C->length); - string_append_bytes(&out, tmp.data, tmp.length); - string_free(tmp); + uint32_t f = options->flags; + if (f & HTTP_CLIENT_OPT_MAX_REDIRECTS) p.max_redirects = options->value.max_redirects; + if (f & HTTP_CLIENT_OPT_FOLLOW_REDIRECTS) p.follow_redirects = options->value.follow_redirects; + if (f & HTTP_CLIENT_OPT_ALLOW_CLOSE_DELIMITED) p.allow_close_delimited = options->value.allow_close_delimited; + return p; +} - if (C->date.length){ - string_append_bytes(&out, "Date: ", 6); - string_append_bytes(&out, C->date.data, C->date.length); - string_append_bytes(&out, "\r\n", 2); +const char* http_method_name(HTTPMethod method) { + switch (method) { + case HTTP_METHOD_GET: return "GET"; + case HTTP_METHOD_POST: return "POST"; + case HTTP_METHOD_PUT: return "PUT"; + case HTTP_METHOD_DELETE: return "DELETE"; + case HTTP_METHOD_HEAD: return "HEAD"; + case HTTP_METHOD_OPTIONS: return "OPTIONS"; + case HTTP_METHOD_UNKNOWN: return "UNKNOWN"; + default: return "UNKNOWN"; } +} + +bool http_method_allowed(uint32_t mask, HTTPMethod method) { + if ((uint32_t)method >= 32) return false; + return (mask & (1u << method)) != 0; +} - if (C->host.length){ - string_append_bytes(&out, "Host: ", 6); - bool has_colon = str_has_char(C->host.data, C->host.length, ':') >= 0; - bool has_lb = str_has_char(C->host.data, C->host.length, '[') >= 0; - bool has_rb = str_has_char(C->host.data, C->host.length, ']') >= 0; - if (has_colon && !has_lb && !has_rb){ - string_append_bytes(&out, "[", 1); - string_append_bytes(&out, C->host.data, C->host.length); - string_append_bytes(&out, "]", 1); - } else { - string_append_bytes(&out, C->host.data, C->host.length); +string http_methods_allow_header(uint32_t mask) { + HTTPMethod methods[] = { + HTTP_METHOD_GET, + HTTP_METHOD_HEAD, + HTTP_METHOD_POST, + HTTP_METHOD_PUT, + HTTP_METHOD_DELETE, + HTTP_METHOD_OPTIONS + }; + + char buf[48]; + uint32_t len = 0; + for (uint32_t i = 0; i < N_ARR(methods); i++) { + if (!http_method_allowed(mask, methods[i])) continue; + if (len) { + buf[len++] = ','; + buf[len++] = ' '; } - string_append_bytes(&out, "\r\n", 2); - } else { - string_append_bytes(&out, "Host: RedactedOS_0.1\r\n", 22); + const char *name = http_method_name(methods[i]); + uint32_t n = (uint32_t)strlen(name); + memcpy(buf + len, name, n); + len += n; + } + if (!len) return (string){0}; + buf[len] = 0; + return string_from_literal_length(buf, len); +} + +const char* http_status_reason(HttpError status) { + switch (status) { + case HTTP_CONTINUE: return "Continue"; + case HTTP_OK: return "OK"; + case HTTP_PARTIAL_CONTENT: return "Partial Content"; + case HTTP_MOVED_PERMANENTLY: return "Moved Permanently"; + case HTTP_FOUND: return "Found"; + case HTTP_SEE_OTHER: return "See Other"; + case HTTP_TEMPORARY_REDIRECT: return "Temporary Redirect"; + case HTTP_PERMANENT_REDIRECT: return "Permanent Redirect"; + case HTTP_BAD_REQUEST: return "Bad Request"; + case HTTP_UNAUTHORIZED: return "Unauthorized"; + case HTTP_FORBIDDEN: return "Forbidden"; + case HTTP_NOT_FOUND: return "Not Found"; + case HTTP_METHOD_NOT_ALLOWED: return "Method Not Allowed"; + case HTTP_PAYLOAD_TOO_LARGE: return "Payload Too Large"; + case HTTP_URI_TOO_LONG: return "URI Too Long"; + case HTTP_RANGE_NOT_SATISFIABLE: return "Range Not Satisfiable"; + case HTTP_EXPECTATION_FAILED: return "Expectation Failed"; + case HTTP_HEADER_FIELDS_TOO_LARGE: return "Request Header Fields Too Large"; + case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error"; + case HTTP_NOT_IMPLEMENTED: return "Not Implemented"; + case HTTP_SERVICE_UNAVAILABLE: return "Service Unavailable"; + case HTTP_VERSION_NOT_SUPPORTED: return "HTTP Version Not Supported"; + default: return "Error"; + } +} + +HttpError http_parse_result_status(HTTPParseResult result) { + switch (result) { + case HTTP_PARSE_TOO_LARGE: + case HTTP_PARSE_TOO_MANY_HEADERS: + return HTTP_HEADER_FIELDS_TOO_LARGE; + case HTTP_PARSE_BAD_CONTENT_LENGTH: + case HTTP_PARSE_BAD_FORMAT: + case HTTP_PARSE_MISSING_HOST: + return HTTP_BAD_REQUEST; + case HTTP_PARSE_UNSUPPORTED_TRANSFER: + return HTTP_NOT_IMPLEMENTED; + case HTTP_PARSE_UNSUPPORTED_VERSION: + return HTTP_VERSION_NOT_SUPPORTED; + case HTTP_PARSE_PAYLOAD_TOO_LARGE: + return HTTP_PAYLOAD_TOO_LARGE; + case HTTP_PARSE_INCOMPLETE: + return HTTP_BAD_REQUEST; + default: + return HTTP_BAD_REQUEST; + } +} + +bool http_header_value_has_token(const char *buf, uint32_t len, const char *token, uint32_t token_len) { + if (!buf || !token || !token_len) return false; + + uint32_t pos = 0; + while (pos < len) { + while (pos < len && (is_whitespace(buf[pos]) || buf[pos] == ',')) pos++; + uint32_t start = pos; + while (pos < len && buf[pos] != ',') pos++; + uint32_t end = pos; + while (end > start && is_whitespace(buf[end - 1])) end--; + if (end - start == token_len && strncmp_case(buf + start, token, true, token_len) == 0) return true; + if (pos < len) pos++; } - if (C->connection.length){ - string_append_bytes(&out, "Connection: ", 12); - string_append_bytes(&out, C->connection.data, C->connection.length); - string_append_bytes(&out, "\r\n", 2); + return false; +} + +HTTPParseResult http_parse_request_line(const char *buf, uint32_t len, HTTPRequestLine *out) { + if (!buf || !out || !len) return HTTP_PARSE_BAD_FORMAT; + *out = (HTTPRequestLine){0}; + + uint32_t i = 0; + while (i < len && buf[i] != ' ') i++; + if (i == len) return HTTP_PARSE_BAD_FORMAT; + uint32_t mlen = i; + if (!mlen) return HTTP_PARSE_BAD_FORMAT; + + if (mlen == 3 && memcmp(buf, "GET", 3) == 0) out->method = HTTP_METHOD_GET; + else if (mlen == 4 && memcmp(buf, "POST", 4) == 0) out->method = HTTP_METHOD_POST; + else if (mlen == 3 && memcmp(buf, "PUT", 3) == 0) out->method = HTTP_METHOD_PUT; + else if (mlen == 6 && memcmp(buf, "DELETE", 6) == 0) out->method = HTTP_METHOD_DELETE; + else if (mlen == 4 && memcmp(buf, "HEAD", 4) == 0) out->method = HTTP_METHOD_HEAD; + else if (mlen == 7 && memcmp(buf, "OPTIONS", 7) == 0) out->method = HTTP_METHOD_OPTIONS; + else { + static const char method_tchar[] = "!#$%&'*+-.^_`|~"; + for (uint32_t j = 0; j < mlen; j++) if (!is_alnum( buf[j]) && !memchr(method_tchar, buf[j], sizeof(method_tchar)-1)) return HTTP_PARSE_BAD_FORMAT; + out->method = HTTP_METHOD_UNKNOWN; } - if (C->keep_alive.length){ - string_append_bytes(&out, "Keep-Alive: ", 12); - string_append_bytes(&out, C->keep_alive.data, C->keep_alive.length); - string_append_bytes(&out, "\r\n", 2); + i++; + while (i < len && buf[i] == ' ')i++; + uint32_t target_off = i; + const char *space = memchr(buf + i, ' ', len - i); + if (!space) return HTTP_PARSE_BAD_FORMAT; + i = (uint32_t)(space - buf); + uint32_t target_len = i - target_off; + if (!target_len) return HTTP_PARSE_BAD_FORMAT; + for (uint32_t j = target_off; j < i; j++) { + unsigned char c = (unsigned char)buf[j]; + if (c <= 0x20 || c == 0x7F) return HTTP_PARSE_BAD_FORMAT; } - for (uint32_t i = 0; i < N; i++){ - const HTTPHeader *hdr = &H[i]; - string_append_bytes(&out, hdr->key.data, hdr->key.length); - string_append_bytes(&out, ": ", 2); - string_append_bytes(&out, hdr->value.data, hdr->value.length); - string_append_bytes(&out, "\r\n", 2); + i++; + while (i < len && buf[i] == ' ') i++; + uint32_t version_len = len - i; + if (version_len != 8) return HTTP_PARSE_UNSUPPORTED_VERSION; + if (memcmp(buf + i, "HTTP/1.0", 8) == 0) out->version = HTTP_VERSION_10; + else if (memcmp(buf + i, "HTTP/1.1", 8) == 0) out->version = HTTP_VERSION_11; + else return HTTP_PARSE_UNSUPPORTED_VERSION; + + out->target_off = target_off; + out->target_len = target_len; + return HTTP_PARSE_OK; +} + +HTTPParseResult http_parse_status_line(const char *buf, uint32_t len, HTTPStatusLine *out) { + if (!buf || !out || len < 12) return HTTP_PARSE_BAD_FORMAT; + *out = (HTTPStatusLine){0}; + + if (memcmp(buf, "HTTP/1.0", 8) == 0) out->version = HTTP_VERSION_10; + else if (memcmp(buf, "HTTP/1.1", 8) == 0) out->version = HTTP_VERSION_11; + else return HTTP_PARSE_UNSUPPORTED_VERSION; + + if (buf[8] != ' ') return HTTP_PARSE_BAD_FORMAT; + uint32_t code = 0; + if (!parse_uint32_dec_len(buf + 9, 3, &code) || code < 100) return HTTP_PARSE_BAD_FORMAT; + out->status_code = code; + + uint32_t i = 12; + while (i < len && buf[i] == ' ') i++; + out->reason_off = i; + out->reason_len = len - i; + return HTTP_PARSE_OK; +} + +static bool http_append_bytes(string *out, const void *data, uint32_t len){ + if (!out || (len && !data) || (out->length && !out->data)) return false; + if (!len) return true; + if (out->length >= UINT32_MAX - len) return false; + uint32_t expected = out->length + len; + string_append_bytes(out, data, len); + return out->length == expected; +} + +static bool http_append_field(string *out, const char *key, uint32_t key_len, const char *val, uint32_t val_len) { + if (!out || !key || !key_len || (val_len && !val)) return false; + return http_append_bytes(out, key, key_len) && http_append_bytes(out, ": ", 2)&& + http_append_bytes(out, val, val_len) && http_append_bytes(out, "\r\n", 2); +} + +string http_header_builder(const HTTPHeadersCommon *C, const HTTPHeader *H, uint32_t N, HTTPHeaderBuildKind kind, HTTPMethod method, uint32_t status_code) { + string out = {0}; + bool ok = true; + + if (C) { + bool request = kind == HTTP_HEADER_BUILD_REQUEST; + bool response = kind == HTTP_HEADER_BUILD_RESPONSE; + bool informational = response && status_code >= 100 && status_code < 200; + + if (C->fields.content_type.length && !informational) ok = ok && http_append_field(&out,"Content-Type", 12, C->fields.content_type.data, C->fields.content_type.length); + + if (!informational) { + if (C->framing.chunked) { + ok = ok && http_append_bytes(&out, "Transfer-Encoding: chunked\r\n", 28); + } else if (C->framing.has_content_length || C->fields.content_length || response || method == HTTP_METHOD_POST || method == HTTP_METHOD_PUT) { + char tmp[60]; + uint32_t n = string_format_buf(tmp, sizeof(tmp), "Content-Length: %u\r\n", C->fields.content_length); + ok = ok && n && n < sizeof(tmp) && http_append_bytes(&out, tmp, n); + } + } + + if (response && C->fields.location.length) ok = ok && http_append_field(&out, "Location", 8, C->fields.location.data, C->fields.location.length); + if (request && C->fields.range.length) ok = ok && http_append_field(&out, "Range", 5, C->fields.range.data, C->fields.range.length); + if (response && C->fields.content_range.length) ok = ok && http_append_field(&out, "Content-Range", 13, C->fields.content_range.data, C->fields.content_range.length); + if (request && C->fields.expect.length) ok = ok && http_append_field(&out, "Expect", 6, C->fields.expect.data, C->fields.expect.length); + + if (request && C->fields.host.length){ + const char *colon = memchr(C->fields.host.data, ':', C->fields.host.length); + bool ipv6 = colon && memchr(colon + 1, ':', C->fields.host.length - (colon - C->fields.host.data) - 1); + bool has_lb = memchr(C->fields.host.data, '[', C->fields.host.length) != 0; + bool has_rb = memchr(C->fields.host.data, ']', C->fields.host.length) != 0; + bool brackets = ipv6 && !has_lb && !has_rb; + + ok = ok && http_append_bytes(&out, "Host: ", 6); + if (brackets) ok = ok && http_append_bytes(&out, "[", 1); + ok = ok && http_append_bytes(&out, C->fields.host.data, C->fields.host.length); + if (brackets) ok = ok && http_append_bytes(&out, "]", 1); + ok = ok && http_append_bytes(&out, "\r\n", 2); + } + + if (C->fields.connection.length) ok = ok && http_append_field(&out, "Connection", 10, C->fields.connection.data, C->fields.connection.length); } - string_append_bytes(&out, "\r\n", 2); + for (uint32_t i = 0; ok && H && i < N; i++) ok = http_append_field(&out, H[i].key.data, H[i].key.length, H[i].value.data, H[i].value.length); + + if (ok) ok = http_append_bytes(&out, "\r\n", 2); + if (!ok) { + if (out.mem_length) string_free(out); + return (string){0}; + } return out; } void http_headers_common_free(HTTPHeadersCommon *C){ if (!C) return; - if (C->type.mem_length) string_free(C->type); - if (C->date.mem_length) string_free(C->date); - if (C->connection.mem_length) string_free(C->connection); - if (C->keep_alive.mem_length) string_free(C->keep_alive); - if (C->host.mem_length) string_free(C->host); - if (C->content_type.mem_length) string_free(C->content_type); + if (C->fields.content_type.mem_length) string_free(C->fields.content_type); + if (C->fields.connection.mem_length) string_free(C->fields.connection); + if (C->fields.host.mem_length) string_free(C->fields.host); + if (C->fields.expect.mem_length) string_free(C->fields.expect); + if (C->fields.range.mem_length) string_free(C->fields.range); + if (C->fields.location.mem_length) string_free(C->fields.location); + if (C->fields.content_range.mem_length) string_free(C->fields.content_range); *C = (HTTPHeadersCommon){0}; } @@ -80,88 +360,255 @@ void http_headers_extra_free(HTTPHeader *extra, uint32_t extra_count){ if (extra[i].key.mem_length) string_free(extra[i].key); if (extra[i].value.mem_length) string_free(extra[i].value); } - free_sized(extra, extra_count * sizeof(HTTPHeader)); + release(extra); } -void http_header_parser(const char *buf, uint32_t len, - HTTPHeadersCommon *C, - HTTPHeader **out_extra, - uint32_t *out_extra_count) -{ - *C = (HTTPHeadersCommon){0}; +void http_request_free(HTTPRequestMsg *req) { + if (!req) return; + if (req->path.mem_length) string_free(req->path); + http_headers_common_free(&req->headers_common); + http_headers_extra_free(req->extra_headers, req->extra_header_count); + if (req->body.mem_length) string_free(req->body); + *req = (HTTPRequestMsg){0}; +} - uint32_t max_lines = 0; - for (uint32_t i = 0; i + 1 < len; i++){ - if (buf[i]=='\r' && buf[i+1]=='\n') max_lines++; +void http_response_free(HTTPResponseMsg *res) { + if (!res) return; + if (res->reason.mem_length) string_free(res->reason); + http_headers_common_free(&res->headers_common); + http_headers_extra_free(res->extra_headers, res->extra_header_count); + if (res->body.mem_length) string_free(res->body); + *res = (HTTPResponseMsg){0}; +} + +static bool http_cpy_field(string *out, const char *src, uint32_t len) { + if (!out) return false; + if (!len) { + static char empty[] = ""; + *out = (string){empty, 0, 0}; + return true; } + *out = string_from_literal_length(src, len); + return out->data && out->length == len; +} + +HTTPParseResult http_header_parse(const char *buf, uint32_t len, const HTTPPolicy *policy, HTTPHeadersCommon *C, HTTPHeader **out_extra, uint32_t *out_extra_count){ + HTTPPolicy p = policy ? *policy : http_default_policy(); + if (!buf || !C || !out_extra || !out_extra_count) return HTTP_PARSE_BAD_FORMAT; + if (len > p.max_header_bytes) return HTTP_PARSE_TOO_LARGE; + + *C = (HTTPHeadersCommon){0}; + *out_extra = NULL; + *out_extra_count = 0; HTTPHeader *extras = NULL; - if (max_lines){ - extras = (HTTPHeader*)(uintptr_t)malloc(sizeof(*extras) * max_lines); - if (!extras){ - *out_extra = NULL; - *out_extra_count = 0; - return; - } - } + uint32_t extra_cap = 0; + HTTPParseResult result = HTTP_PARSE_OK; uint32_t extra_i = 0; + uint32_t header_i = 0; uint32_t pos = 0; + bool seen_host = false; - char key_tmp[64]; - - while (pos + 1 < len){ + while (pos < len){ uint32_t eol = pos; - while (eol + 1 < len && !(buf[eol]=='\r' && buf[eol+1]=='\n')) eol++; + while (eol < len && buf[eol] && buf[eol] != '\r' && buf[eol] != '\n') eol++; + bool has_crlf = false; + if (eol < len) { + if (buf[eol] == 0 || buf[eol] == '\n') result = HTTP_PARSE_BAD_FORMAT; + else if (eol + 1 >= len || buf[eol+1] != '\n') result = HTTP_PARSE_BAD_FORMAT; + else has_crlf = true; + } + if (result != HTTP_PARSE_OK) break; + + if (eol == pos) break; - if (eol == pos){ - pos += 2; + header_i++; + if (header_i > p.max_header_count) { + result = HTTP_PARSE_TOO_MANY_HEADERS; break; } uint32_t sep = pos; while (sep < eol && buf[sep] != ':') sep++; - - if (sep == eol){ - pos = eol + 2; - continue; + if (sep == eol || sep == pos) { + result = HTTP_PARSE_BAD_FORMAT; + break; } uint32_t key_len = sep - pos; uint32_t val_start = sep + 1; - while (val_start < eol && (buf[val_start]==' ' || buf[val_start]=='\t')) val_start++; + while (val_start < eol && is_whitespace(buf[val_start])) val_start++; + uint32_t val_end = eol; + while (val_end > val_start && is_whitespace(buf[val_end - 1])) val_end--; + uint32_t val_len = val_end - val_start; + bool handled = false; + + if (key_len > p.max_header_key_len || val_len > p.max_header_value_len) { + result = HTTP_PARSE_TOO_LARGE; + break; + } - uint32_t val_len = eol - val_start; + if (key_len == 14 && strncmp_case(buf + pos, "content-length", true, key_len) == 0){ + uint32_t parsed = 0; + bool ok = parse_uint32_dec_len(buf + val_start, val_len, &parsed); - uint32_t copy_len = (key_len < sizeof(key_tmp)-1) ? key_len : (sizeof(key_tmp)-1); - for (uint32_t i = 0; i < copy_len; i++){ - key_tmp[i] = buf[pos + i]; - } - key_tmp[copy_len] = '\0'; + if (!ok || (C->framing.has_content_length && C->fields.content_length != parsed)) { + C->bad_content_length = 1; + result = HTTP_PARSE_BAD_CONTENT_LENGTH; + } else { + C->framing.has_content_length = 1; + C->fields.content_length = parsed; + } + handled = true; + } else if (key_len == 12 && strncmp_case(buf + pos, "content-type", true, key_len) == 0){ + if (C->fields.content_type.mem_length) string_free(C->fields.content_type); + if (!http_cpy_field(&C->fields.content_type, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + handled = true; + } else if (key_len == 10 && strncmp_case(buf + pos, "connection", true, key_len) == 0){ + if (C->fields.connection.mem_length) string_free(C->fields.connection); + if (!http_cpy_field(&C->fields.connection, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + C->framing.connection_close = http_header_value_has_token(buf + val_start, val_len, "close", 5) ? 1 : 0; + C->framing.connection_keep_alive = http_header_value_has_token(buf + val_start, val_len, "keep-alive", 10) ? 1 : 0; + handled = true; + } else if (key_len == 4 && strncmp_case(buf + pos, "host", true, key_len) == 0) { + if (seen_host) result = HTTP_PARSE_BAD_FORMAT; + else { + seen_host = 1; + if (!http_cpy_field(&C->fields.host, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + } + handled = true; + } else if (key_len == 6 && strncmp_case(buf + pos, "expect", true, key_len) == 0){ + if (C->fields.expect.mem_length) string_free(C->fields.expect); + if (!http_cpy_field(&C->fields.expect, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + C->framing.expect_continue = http_header_value_has_token(buf + val_start, val_len, "100-continue", 12) ? 1 : 0; + handled = true; + } else if (key_len == 5 && strncmp_case(buf + pos, "range", true, key_len) == 0){ + if (C->fields.range.mem_length) string_free(C->fields.range); + if (!http_cpy_field(&C->fields.range, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + C->range.has = 1; + C->range.invalid = 1; + if (val_len > 6 && strncmp_case(buf + val_start, "bytes=", true, 6) == 0) { + uint32_t rp = val_start + 6; + uint32_t rend = val_start + val_len; + uint64_t start = 0; + uint64_t end = 0; + uint32_t start_pos = rp; + while (rp < rend && is_digit(buf[rp])) rp++; + uint32_t start_len = rp - start_pos; + if (rp < rend && buf[rp] == '-') { + rp++; + uint32_t end_pos = rp; + while (rp < rend && is_digit(buf[rp])) rp++; + uint32_t end_len = rp - end_pos; + bool has_start = start_len != 0; + bool has_end = end_len != 0; + bool valid = rp == rend && (has_start || has_end); + if (has_start && !parse_uint64_dec_len(buf + start_pos, start_len, &start)) valid = false; + if (has_end && !parse_uint64_dec_len(buf + end_pos, end_len, &end)) valid = false; + if (valid && (!has_start || !has_end || end >= start)) { + C->range.invalid = 0; + C->range.has_start = has_start ? 1 : 0; + C->range.has_end = has_end ? 1 : 0; + C->range.start = start; + C->range.end = end; + } + } + } + handled = true; + } else if (key_len == 8 && strncmp_case(buf + pos, "location", true, key_len) == 0) { + if (C->fields.location.mem_length) string_free(C->fields.location); + if (!http_cpy_field(&C->fields.location, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + handled = true; + } else if (key_len == 13 && strncmp_case(buf + pos, "content-range", true, key_len) == 0) { + if (C->fields.content_range.mem_length) string_free(C->fields.content_range); + if (!http_cpy_field(&C->fields.content_range, buf + val_start, val_len)) result = HTTP_PARSE_TOO_LARGE; + handled = true; + } else if (key_len == 17 && strncmp_case(buf + pos, "transfer-encoding", true, key_len) == 0){ + if (C->framing.chunked) { + result = HTTP_PARSE_BAD_FORMAT; + break; + } + uint32_t te_pos = val_start; + uint32_t te_end = val_start + val_len; + uint32_t count = 0; + bool chunked = false; + + while (te_pos < te_end) { + while (te_pos < te_end && is_whitespace(buf[te_pos])) te_pos++; + if (te_pos >= te_end) break; + + uint32_t token_start = te_pos; + if (te_end - te_pos <= 24) { + while (te_pos < te_end && buf[te_pos] != ',' && buf[te_pos] != ';' && !is_whitespace(buf[te_pos])) te_pos++; + } else te_pos += (uint32_t)memcspn(buf + te_pos, te_end - te_pos, ",; \t\n\r", 6); + uint32_t token_len = te_pos - token_start; + if (!token_len) { + result = HTTP_PARSE_BAD_FORMAT; + break; + } + + while (te_pos < te_end && is_whitespace(buf[te_pos])) te_pos++; + if (te_pos < te_end && buf[te_pos] == ';') { + result = HTTP_PARSE_UNSUPPORTED_TRANSFER; + break; + } + if (token_len != 7 || strncmp_case(buf + token_start, "chunked", true, 7) != 0) { + result = HTTP_PARSE_UNSUPPORTED_TRANSFER; + break; + } + + count++; + chunked = true; + if (te_pos < te_end) { + if (buf[te_pos] != ',') { + result = HTTP_PARSE_BAD_FORMAT; + break; + } + te_pos++; + while (te_pos < te_end && is_whitespace(buf[te_pos])) te_pos++; + if (te_pos == te_end) { + result = HTTP_PARSE_BAD_FORMAT; + break; + } + } + } - if (copy_len == 14 && strcmp_case(key_tmp, "content-length", true) == 0){ - C->length = (uint32_t)parse_int_u64(buf + val_start, val_len); - } - else if (copy_len == 12 && strcmp_case(key_tmp, "content-type", true) == 0){ - C->type = string_from_literal_length((char*)(buf + val_start), val_len); - } - else if (copy_len == 4 && strcmp_case(key_tmp, "date", true) == 0){ - C->date = string_from_literal_length((char*)(buf + val_start), val_len); + if (result == HTTP_PARSE_OK && count != 1) result = HTTP_PARSE_UNSUPPORTED_TRANSFER; + else if (result == HTTP_PARSE_OK && chunked && !p.allow_chunked) result = HTTP_PARSE_UNSUPPORTED_TRANSFER; + else if (result == HTTP_PARSE_OK) C->framing.chunked = chunked ? 1 : 0; + handled = true; } - else if (copy_len == 10 && strcmp_case(key_tmp, "connection", true) == 0){ - C->connection = string_from_literal_length((char*)(buf + val_start), val_len); - } - else if (copy_len == 10 && strcmp_case(key_tmp, "keep-alive", true) == 0){ - C->keep_alive = string_from_literal_length((char*)(buf + val_start), val_len); - } - else if (copy_len == 4 && strcmp_case(key_tmp, "host", true) == 0){ - C->host = string_from_literal_length((char*)(buf + val_start), val_len); - } - else { - string key = string_from_literal_length((char*)(buf + pos), key_len); - string value = string_from_literal_length((char*)(buf + val_start), val_len); - if (extras && extra_i < max_lines){ + if (result != HTTP_PARSE_OK) break; + if (!handled) { + string key = (string){0}; + string value = (string){0}; + if (!http_cpy_field(&key, buf + pos, key_len) || !http_cpy_field(&value, buf + val_start, val_len)) { + string_free(key); + string_free(value); + result = HTTP_PARSE_TOO_LARGE; + break; + } + + if (extra_i < p.max_header_count) { + if (extra_i == extra_cap) { + uint32_t new_cap = extra_cap ? extra_cap * 2 : 4; + if (new_cap > p.max_header_count) new_cap = p.max_header_count; + HTTPHeader *grown = (HTTPHeader*)zalloc(sizeof(*grown) * new_cap); + if (!grown) { + if (key.mem_length) string_free(key); + if (value.mem_length) string_free(value); + result = HTTP_PARSE_TOO_LARGE; + break; + } + if (extras) { + memcpy(grown, extras, sizeof(*grown) * extra_i); + release(extras); + } + extras = grown; + extra_cap = new_cap; + } extras[extra_i++] = (HTTPHeader){ key, value }; } else { if (key.mem_length) string_free(key); @@ -169,100 +616,240 @@ void http_header_parser(const char *buf, uint32_t len, } } + if (!has_crlf) break; pos = eol + 2; } - if (!extras || extra_i == 0){ - if (extras) free_sized(extras, sizeof(*extras) * max_lines); + if (result == HTTP_PARSE_OK && C->framing.chunked && C->framing.has_content_length) result = HTTP_PARSE_BAD_FORMAT; + + if (result != HTTP_PARSE_OK) { + http_headers_extra_free(extras, extra_i); + http_headers_common_free(C); *out_extra = NULL; *out_extra_count = 0; - return; + return result; } - if (extra_i == max_lines){ - *out_extra = extras; - *out_extra_count = extra_i; - return; + if (!extras || extra_i == 0){ + if (extras) release(extras); + *out_extra = NULL; + *out_extra_count = 0; + return result; } - HTTPHeader *shr = (HTTPHeader*)(uintptr_t)malloc(sizeof(*shr) * extra_i); - if (shr){ - memcpy(shr, extras, sizeof(*shr) * extra_i); - free_sized(extras, sizeof(*extras) * max_lines); - *out_extra = shr; - *out_extra_count = extra_i; - return; - } + *out_extra = extras; + *out_extra_count = extra_i; + return result; +} + +static bool http_append_chunked_body(string *out, const void *ptr, uint32_t len) { + if (!out || (len && !ptr)) return false; - for (uint32_t i = 0; i < extra_i; i++){ - if (extras[i].key.mem_length) string_free(extras[i].key); - if (extras[i].value.mem_length) string_free(extras[i].value); + if (len) { + char hex[16]; + uint32_t n = u64_to_base(hex, len, 16, 0); + if (!http_append_bytes(out, hex, n) || + !http_append_bytes(out, "\r\n", 2) || + !http_append_bytes(out, ptr, len) || + !http_append_bytes(out, "\r\n", 2)) return false; } - free_sized(extras, sizeof(*extras) * max_lines); - *out_extra = NULL; - *out_extra_count = 0; + + return http_append_bytes(out, "0\r\n\r\n", 5); } string http_request_builder(const HTTPRequestMsg *R){ - static const char *Mnames[] = { "GET", "POST", "PUT", "DELETE" }; - string out = string_format("%s ", Mnames[R->method]); + if (!R) return (string){0}; + HTTPHeadersCommon common = R->headers_common; + if (R->host_override) common.fields.host = R->host_override[0] ? (string){(char*)R->host_override, (uint32_t)strlen(R->host_override), 0} : (string){0}; + if (!common.framing.chunked && (R->body.length || R->method == HTTP_METHOD_POST || R->method == HTTP_METHOD_PUT)) { + common.fields.content_length = R->body.length; + common.framing.has_content_length = 1; + } - string_append_bytes(&out, R->path.data, R->path.length); - string_append_bytes(&out, " HTTP/1.1\r\n", 11); + HTTPVersion version = R->version == HTTP_VERSION_10 ? HTTP_VERSION_10 : HTTP_VERSION_11; + string out = string_format("%s ", http_method_name(R->method)); + if (!out.data) return out; - string hdrs = http_header_builder(&R->headers_common, R->extra_headers, R->extra_header_count); - string_append_bytes(&out, hdrs.data, hdrs.length); - string_free(hdrs); + bool ok = http_append_bytes(&out, R->path.data, R->path.length) && + http_append_bytes(&out, version == HTTP_VERSION_10 ? " HTTP/1.0\r\n" : " HTTP/1.1\r\n", 11); - if (R->body.ptr && R->body.size){ - string body = string_from_literal_length((char*)R->body.ptr, R->body.size); - string_append_bytes(&out, body.data, body.length); - string_free(body); + if (ok) { + string hdrs = http_header_builder(&common, R->extra_headers, R->extra_header_count, HTTP_HEADER_BUILD_REQUEST, R->method, 0); + ok = hdrs.data && http_append_bytes(&out, hdrs.data, hdrs.length); + if (hdrs.mem_length) string_free(hdrs); + } + if (ok) { + if (common.framing.chunked) ok = http_append_chunked_body(&out, R->body.data, R->body.length); + else if (R->body.length) ok = http_append_bytes(&out, R->body.data, R->body.length); + } + if (!ok) { + if (out.mem_length) string_free(out); + return (string){0}; } - return out; } string http_response_builder(const HTTPResponseMsg *R){ - string out = string_format("HTTP/1.1 %i ", (int)R->status_code); - string_append_bytes(&out, R->reason.data, R->reason.length); - string_append_bytes(&out, "\r\n", 2); + if (!R) return (string){0}; + HTTPHeadersCommon common = R->headers_common; + bool informational = R->status_code >= 100 && R->status_code < 200; + if (!informational && !common.framing.chunked) { + if (!common.framing.has_content_length && !common.fields.content_length) common.fields.content_length = R->body.length; + common.framing.has_content_length = 1; + } - string hdrs = http_header_builder(&R->headers_common, R->extra_headers, R->extra_header_count); - string_append_bytes(&out, hdrs.data, hdrs.length); - string_free(hdrs); + string out = string_format("HTTP/1.1 %i ", R->status_code); + if (!out.data) return out; + const char *reason = http_status_reason(R->status_code); + bool ok = R->reason.length ? http_append_bytes(&out, R->reason.data, R->reason.length) : http_append_bytes(&out, reason, strlen(reason)); + if (ok) ok = http_append_bytes(&out, "\r\n", 2); - if (R->body.ptr && R->body.size){ - string_append_bytes(&out, (char*)R->body.ptr, (uint32_t)R->body.size); + if (ok) { + string hdrs = http_header_builder(&common, R->extra_headers, R->extra_header_count, HTTP_HEADER_BUILD_RESPONSE, HTTP_METHOD_GET, R->status_code); + ok = hdrs.data && http_append_bytes(&out, hdrs.data, hdrs.length); + if (hdrs.mem_length) string_free(hdrs); + } + if (ok && !informational) { + if (common.framing.chunked) ok = http_append_chunked_body(&out, R->body.data, R->body.length); + else if (R->body.length) ok = http_append_bytes(&out, R->body.data, R->body.length); + } + if (!ok) { + if (out.mem_length) string_free(out); + return (string){0}; } return out; } int find_crlfcrlf(const char *data, uint32_t len){ - for (uint32_t i = 0; i + 3 < len; i++){ - if (data[i]=='\r' && data[i+1]=='\n' && data[i+2]=='\r' && data[i+3]=='\n') return (int)i; - } - return -1; + const char *p = memmem(data, len, "\r\n\r\n", 4); + return p ? (int)(p-data) : -1; } -sizedptr http_get_payload(sizedptr header){ - if (!header.ptr || header.size < 4) return (sizedptr){0}; +void http_chunked_decoder_init(HTTPChunkedDecoder *dec, const HTTPPolicy *policy){ + if (!dec) return; + *dec = (HTTPChunkedDecoder){0}; + dec->policy = policy ? *policy : http_default_policy(); + dec->stage = HTTP_CHUNK_STAGE_SIZE; + dec->line = (string){0}; + dec->body = (string){0}; + dec->trailers_buf = (string){0}; +} - int start = find_crlfcrlf((char*)header.ptr, header.size); - if (start < 0) return (sizedptr){0}; +HTTPParseResult http_chunked_decoder_feed(HTTPChunkedDecoder *dec, const char *buf, uint32_t len, uint32_t *out_used) { + if (out_used) *out_used = 0; + if (!dec || !buf) return HTTP_PARSE_BAD_FORMAT; + if (dec->stage == HTTP_CHUNK_STAGE_DONE) return HTTP_PARSE_OK; + + uint32_t i = 0; + while (i < len) { + if (dec->stage == HTTP_CHUNK_STAGE_SIZE) { + uint32_t take = 1; + if (len - i > 1) { + const char* nl = memchr(buf + i, '\n', len - i); + take = nl ? (uint32_t)(nl - (buf + i)) + 1 : len - i; + } + if (dec->line.length > dec->policy.max_header_value_len || take > dec->policy.max_header_value_len - dec->line.length) return HTTP_PARSE_TOO_LARGE; + uint32_t expected = dec->line.length + take; + string_append_bytes(&dec->line, buf+i, take); + if (dec->line.length != expected) return HTTP_PARSE_TOO_LARGE; + i += take; + if (dec->line.length >= 2 && dec->line.data[dec->line.length - 2] == '\r' && dec->line.data[dec->line.length - 1] == '\n') { + uint64_t chunk_len = 0; + HTTPParseResult r = HTTP_PARSE_OK; + uint32_t line_end = dec->line.length - 2; + uint32_t scan = 0; + bool saw_digit = false; + while (scan < line_end && is_whitespace(dec->line.data[scan])) scan++; + if (scan >= line_end) r = HTTP_PARSE_BAD_FORMAT; + while (r == HTTP_PARSE_OK && scan < line_end) { + char c = dec->line.data[scan]; + if (c == ';') break; + if (is_whitespace(c)) { + while (scan < line_end && is_whitespace(dec->line.data[scan])) scan++; + if (scan < line_end && dec->line.data[scan] != ';') r = HTTP_PARSE_BAD_FORMAT; + break; + } + + int digit = hex_val(c); + if (digit < 0) { + r = HTTP_PARSE_BAD_FORMAT; + break; + } + saw_digit = true; + chunk_len = chunk_len * 16 + (uint64_t)digit; + if (chunk_len > dec->policy.max_body_bytes) { + r = HTTP_PARSE_PAYLOAD_TOO_LARGE; + break; + } + scan++; + } + dec->line.length = 0; + dec->line.data[0] = 0; + if (!saw_digit && r == HTTP_PARSE_OK) r = HTTP_PARSE_BAD_FORMAT; + if (r != HTTP_PARSE_OK) return r; + if (dec->body_total + chunk_len > dec->policy.max_body_bytes) return HTTP_PARSE_PAYLOAD_TOO_LARGE; + dec->chunk_size = chunk_len; + dec->chunk_read = 0; + dec->crlf_seen = 0; + dec->stage = chunk_len ? HTTP_CHUNK_STAGE_DATA : HTTP_CHUNK_STAGE_TRAILERS; + } + } else if (dec->stage == HTTP_CHUNK_STAGE_DATA) { + uint64_t need64 = dec->chunk_size - dec->chunk_read; + uint32_t avail = len - i; + uint32_t take = need64 < avail ? (uint32_t)need64: avail; + if (take) { + uint32_t expected = dec->body.length + take; + string_append_bytes(&dec->body, buf + i, take); + if (dec->body.length != expected) return HTTP_PARSE_TOO_LARGE; + dec->chunk_read += take; + dec->body_total += take; + i += take; + } + if (dec->chunk_read == dec->chunk_size) dec->stage = HTTP_CHUNK_STAGE_DATA_CRLF; + } else if (dec->stage == HTTP_CHUNK_STAGE_DATA_CRLF) { + char expected = dec->crlf_seen ? '\n' : '\r'; + if (buf[i] != expected) return HTTP_PARSE_BAD_FORMAT; + dec->crlf_seen++; + i++; + if (dec->crlf_seen == 2) { + dec->crlf_seen = 0; + dec->stage = HTTP_CHUNK_STAGE_SIZE; + } + } else if (dec->stage == HTTP_CHUNK_STAGE_TRAILERS) { + uint32_t take = 1; + if (len - i > 1) { + const char* nl = memchr(buf + i, '\n', len - i); + take = nl ? (nl - (buf + i)) + 1 : len - i; + } + if (dec->trailers_buf.length > dec->policy.max_header_bytes || take > dec->policy.max_header_bytes - dec->trailers_buf.length) return HTTP_PARSE_TOO_LARGE; + uint32_t scan = dec->trailers_buf.length > 3 ? dec->trailers_buf.length - 3 : 0; + uint32_t expected = dec->trailers_buf.length + take; + string_append_bytes(&dec->trailers_buf, buf + i, take); + if (dec->trailers_buf.length != expected) return HTTP_PARSE_TOO_LARGE; + i += take; + if (dec->trailers_buf.length == 2 && dec->trailers_buf.data[0] == '\r' && dec->trailers_buf.data[1] == '\n') { + dec->stage = HTTP_CHUNK_STAGE_DONE; + if (out_used) *out_used = i; + return HTTP_PARSE_OK; + } - return (sizedptr){ - header.ptr + (uint32_t)(start + 4), - header.size - (uint32_t)(start + 4) - }; -} + if (find_crlfcrlf(dec->trailers_buf.data + scan, dec->trailers_buf.length - scan) >= 0) { + dec->stage = HTTP_CHUNK_STAGE_DONE; + if (out_used) *out_used = i; + return HTTP_PARSE_OK; + } -string http_get_chunked_payload(sizedptr chunk){ - if (chunk.ptr && chunk.size > 0){ - int sizetrm = strindex((char*)chunk.ptr, "\r\n"); - uint64_t chunk_size = parse_hex_u64((char*)chunk.ptr, sizetrm); - return string_from_literal_length((char*)(chunk.ptr + sizetrm + 2), (uint32_t)chunk_size); + } } - return (string){0}; + if (out_used) *out_used = i; + return dec->stage == HTTP_CHUNK_STAGE_DONE ? HTTP_PARSE_OK : HTTP_PARSE_INCOMPLETE; +} + +void http_chunked_decoder_free(HTTPChunkedDecoder *dec) { + if (!dec) return; + if (dec->line.mem_length) string_free(dec->line); + if (dec->body.mem_length) string_free(dec->body); + if (dec->trailers_buf.mem_length) string_free(dec->trailers_buf); + *dec = (HTTPChunkedDecoder){0}; } diff --git a/kernel/networking/application_layer/http.h b/kernel/networking/application_layer/http.h index 06163a76..9a4616cb 100644 --- a/kernel/networking/application_layer/http.h +++ b/kernel/networking/application_layer/http.h @@ -7,47 +7,239 @@ extern "C" { #endif +#define HTTP_DEFAULT_MAX_START_LINE 2048 +#define HTTP_DEFAULT_MAX_HEADER_BYTES (16 * 1024) +#define HTTP_DEFAULT_MAX_HEADER_COUNT 64 +#define HTTP_DEFAULT_MAX_HEADER_KEY_LEN 64 +#define HTTP_DEFAULT_MAX_HEADER_VALUE_LEN 4096 +#define HTTP_DEFAULT_MAX_PATH_LEN 2048 +#define HTTP_DEFAULT_MAX_BODY_BYTES (1024*1024) +#define HTTP_DEFAULT_HEADER_IDLE_TIMEOUT_MS 1000 +#define HTTP_DEFAULT_HEADER_TOTAL_TIMEOUT_MS 15000 +#define HTTP_DEFAULT_BODY_IDLE_TIMEOUT_MS 3000 +#define HTTP_DEFAULT_BODY_TOTAL_TIMEOUT_MS 20000 +#define HTTP_DEFAULT_MAX_KEEPALIVE_REQUESTS 16 +#define HTTP_DEFAULT_MAX_REDIRECTS 5 + typedef enum { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_PUT, - HTTP_METHOD_DELETE + HTTP_METHOD_DELETE, + HTTP_METHOD_HEAD, + HTTP_METHOD_OPTIONS, + HTTP_METHOD_UNKNOWN } HTTPMethod; +#define HTTP_METHOD_MASK_GET (1u << HTTP_METHOD_GET) +#define HTTP_METHOD_MASK_POST (1u << HTTP_METHOD_POST) +#define HTTP_METHOD_MASK_PUT (1u << HTTP_METHOD_PUT) +#define HTTP_METHOD_MASK_DELETE (1u << HTTP_METHOD_DELETE) +#define HTTP_METHOD_MASK_HEAD (1u << HTTP_METHOD_HEAD) +#define HTTP_METHOD_MASK_OPTIONS (1u << HTTP_METHOD_OPTIONS) +#define HTTP_METHOD_MASK_ALL (HTTP_METHOD_MASK_GET | HTTP_METHOD_MASK_POST | HTTP_METHOD_MASK_PUT | HTTP_METHOD_MASK_DELETE | HTTP_METHOD_MASK_HEAD | HTTP_METHOD_MASK_OPTIONS) + +typedef enum { + HTTP_VERSION_UNKNOWN, + HTTP_VERSION_10, + HTTP_VERSION_11 +} HTTPVersion; + +typedef enum { + HTTP_PARSE_OK, + HTTP_PARSE_BAD_FORMAT, + HTTP_PARSE_TOO_LARGE, + HTTP_PARSE_TOO_MANY_HEADERS, + HTTP_PARSE_BAD_CONTENT_LENGTH, + HTTP_PARSE_UNSUPPORTED_TRANSFER, + HTTP_PARSE_MISSING_HOST, + HTTP_PARSE_UNSUPPORTED_VERSION, + HTTP_PARSE_PAYLOAD_TOO_LARGE, + HTTP_PARSE_INCOMPLETE +} HTTPParseResult; + typedef enum { + HTTP_CONTINUE = 100, HTTP_OK = 200, + HTTP_PARTIAL_CONTENT = 206, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_FOUND = 302, + HTTP_SEE_OTHER = 303, + HTTP_TEMPORARY_REDIRECT = 307, + HTTP_PERMANENT_REDIRECT = 308, HTTP_BAD_REQUEST = 400, HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, HTTP_NOT_FOUND = 404, + HTTP_METHOD_NOT_ALLOWED = 405, + HTTP_PAYLOAD_TOO_LARGE = 413, + HTTP_URI_TOO_LONG = 414, + HTTP_RANGE_NOT_SATISFIABLE = 416, + HTTP_EXPECTATION_FAILED = 417, + HTTP_HEADER_FIELDS_TOO_LARGE = 431, HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_NOT_IMPLEMENTED = 501, HTTP_SERVICE_UNAVAILABLE = 503, - HTTP_DEBUG = 800, + HTTP_VERSION_NOT_SUPPORTED = 505, } HttpError; +typedef enum { + HTTP_HEADER_BUILD_REQUEST, + HTTP_HEADER_BUILD_RESPONSE +} HTTPHeaderBuildKind; + +typedef enum { + HTTP_CHUNK_STAGE_SIZE, + HTTP_CHUNK_STAGE_DATA, + HTTP_CHUNK_STAGE_DATA_CRLF, + HTTP_CHUNK_STAGE_TRAILERS, + HTTP_CHUNK_STAGE_DONE +} HTTPChunkStage; + +typedef enum { + HTTP_POLICY_OPT_MAX_START_LINE = 1u << 0, + HTTP_POLICY_OPT_MAX_HEADER_BYTES = 1u << 1, + HTTP_POLICY_OPT_MAX_HEADER_COUNT = 1u << 2, + HTTP_POLICY_OPT_MAX_HEADER_KEY_LEN = 1u << 3, + HTTP_POLICY_OPT_MAX_HEADER_VALUE_LEN = 1u << 4, + HTTP_POLICY_OPT_MAX_PATH_LEN = 1u << 5, + HTTP_POLICY_OPT_MAX_BODY_BYTES = 1u << 6, + HTTP_POLICY_OPT_HEADER_IDLE_TIMEOUT_MS = 1u << 7, + HTTP_POLICY_OPT_HEADER_TOTAL_TIMEOUT_MS = 1u << 8, + HTTP_POLICY_OPT_BODY_IDLE_TIMEOUT_MS = 1u << 9, + HTTP_POLICY_OPT_BODY_TOTAL_TIMEOUT_MS = 1u << 10, + HTTP_POLICY_OPT_ALLOW_CHUNKED = 1u << 11 +} HTTPPolicyOptionFlag; + +typedef enum { + HTTP_SERVER_OPT_MAX_KEEPALIVE_REQUESTS = 1u << 0, + HTTP_SERVER_OPT_ALLOWED_METHODS = 1u << 1, + HTTP_SERVER_OPT_ERROR_CONTENT_TYPE = 1u << 2, + HTTP_SERVER_OPT_ALLOW_KEEP_ALIVE = 1u << 3, + HTTP_SERVER_OPT_ALLOW_ABSOLUTE_URI = 1u << 4, + HTTP_SERVER_OPT_REQUIRE_HOST_HTTP11 = 1u << 5, + HTTP_SERVER_OPT_SEND_ERROR_BODY = 1u << 6 +} HTTPServerPolicyOptionFlag; + +typedef enum { + HTTP_CLIENT_OPT_MAX_REDIRECTS = 1u << 0, + HTTP_CLIENT_OPT_FOLLOW_REDIRECTS = 1u << 1, + HTTP_CLIENT_OPT_ALLOW_CLOSE_DELIMITED = 1u << 2 +} HTTPClientPolicyOptionFlag; + +typedef struct { + uint32_t max_start_line; + uint32_t max_header_bytes; + uint32_t max_header_count; + uint32_t max_header_key_len; + uint32_t max_header_value_len; + uint32_t max_path_len; + uint32_t max_body_bytes; + uint32_t header_idle_timeout_ms; + uint32_t header_total_timeout_ms; + uint32_t body_idle_timeout_ms; + uint32_t body_total_timeout_ms; + bool allow_chunked; +} HTTPPolicy; + +typedef struct { + HTTPPolicy value; + uint32_t flags; +} HTTPPolicyOptions; + +typedef struct { + HTTPPolicy common; + uint32_t max_keepalive_requests; + uint32_t allowed_methods; + const char *error_content_type; + bool allow_keep_alive; + bool allow_absolute_uri; + bool require_host_http11; + bool send_error_body; +} HTTPServerPolicy; + +typedef struct { + HTTPServerPolicy value; + uint32_t flags; + uint32_t common_flags; +} HTTPServerPolicyOptions; + +typedef struct { + HTTPPolicy common; + uint32_t max_redirects; + bool follow_redirects; + bool allow_close_delimited; +} HTTPClientPolicy; + +typedef struct { + HTTPClientPolicy value; + uint32_t flags; + uint32_t common_flags; +} HTTPClientPolicyOptions; + +typedef struct { + HTTPMethod method; + HTTPVersion version; + uint32_t target_off; + uint32_t target_len; +} HTTPRequestLine; + +typedef struct { + HTTPVersion version; + uint32_t status_code; + uint32_t reason_off; + uint32_t reason_len; +} HTTPStatusLine; + typedef struct { string key; string value; } HTTPHeader; typedef struct { - uint32_t length; - string type; - string date; + string content_type; string connection; - string keep_alive; string host; - string content_type; + string expect; + string range; + string location; + string content_range; + uint32_t content_length; +} HTTPHeaderFields; + +typedef struct { + uint8_t has_content_length; + uint8_t chunked; + uint8_t connection_close; + uint8_t connection_keep_alive; + uint8_t expect_continue; +} HTTPMessageFraming; + +typedef struct { + uint8_t has; + uint8_t invalid; + uint8_t has_start; + uint8_t has_end; + uint64_t start; + uint64_t end; +} HTTPRangeSpec; + +typedef struct { + HTTPHeaderFields fields; + HTTPMessageFraming framing; + HTTPRangeSpec range; + uint8_t bad_content_length; } HTTPHeadersCommon; typedef struct { HTTPMethod method; + HTTPVersion version; string path; + const char *host_override; HTTPHeadersCommon headers_common; HTTPHeader *extra_headers; uint32_t extra_header_count; - sizedptr body; + string body; } HTTPRequestMsg; typedef struct { @@ -56,20 +248,54 @@ typedef struct { HTTPHeadersCommon headers_common; HTTPHeader *extra_headers; uint32_t extra_header_count; - sizedptr body; + string body; } HTTPResponseMsg; +typedef struct { + HTTPPolicy policy; + HTTPChunkStage stage; + uint64_t chunk_size; + uint64_t chunk_read; + uint32_t body_total; + uint8_t crlf_seen; + string line; + string body; + string trailers_buf; +} HTTPChunkedDecoder; + +HTTPPolicy http_default_policy(void); +HTTPPolicy http_policy_apply_options(HTTPPolicy base, const HTTPPolicyOptions *options); +HTTPServerPolicy http_server_policy_from_options(const HTTPServerPolicyOptions *options); +HTTPClientPolicy http_client_policy_from_options(const HTTPClientPolicyOptions *options); +const char* http_method_name(HTTPMethod method); +bool http_method_allowed(uint32_t mask, HTTPMethod method); +string http_methods_allow_header(uint32_t mask); +const char* http_status_reason(HttpError status); +HttpError http_parse_result_status(HTTPParseResult result); +bool http_header_value_has_token(const char *buf, uint32_t len, const char *token, uint32_t token_len); +HTTPParseResult http_parse_request_line(const char *buf, uint32_t len, HTTPRequestLine *out); +HTTPParseResult http_parse_status_line(const char *buf, uint32_t len, HTTPStatusLine *out); string http_header_builder(const HTTPHeadersCommon *common, const HTTPHeader *extra, - uint32_t extra_count); + uint32_t extra_count, + HTTPHeaderBuildKind kind, + HTTPMethod method, + uint32_t status_code); -void http_header_parser(const char *buf, uint32_t len, +HTTPParseResult http_header_parse(const char *buf, uint32_t len, + const HTTPPolicy *policy, HTTPHeadersCommon *out_common, HTTPHeader **out_extra, uint32_t *out_extra_count); +void http_chunked_decoder_init(HTTPChunkedDecoder *dec, const HTTPPolicy *policy); +HTTPParseResult http_chunked_decoder_feed(HTTPChunkedDecoder *dec, const char *buf, uint32_t len, uint32_t *out_used); +void http_chunked_decoder_free(HTTPChunkedDecoder *dec); + void http_headers_common_free(HTTPHeadersCommon *common); void http_headers_extra_free(HTTPHeader *extra, uint32_t extra_count); +void http_request_free(HTTPRequestMsg *req); +void http_response_free(HTTPResponseMsg *res); string http_request_builder(const HTTPRequestMsg *req); @@ -77,10 +303,6 @@ string http_response_builder(const HTTPResponseMsg *res); int find_crlfcrlf(const char *data, uint32_t len); -sizedptr http_get_payload(sizedptr header); - -string http_get_chunked_payload(sizedptr chunk); - #ifdef __cplusplus } #endif diff --git a/kernel/networking/application_layer/http_webserver.c b/kernel/networking/application_layer/http_webserver.c new file mode 100644 index 00000000..b6301d4f --- /dev/null +++ b/kernel/networking/application_layer/http_webserver.c @@ -0,0 +1,345 @@ +#include "http_webserver.h" +#include "networking/firewall.h" +#include "filesystem/filesystem.h" +#include "networking/application_layer/dns/mdns_responder.h" +#include "data/format/url.h" +#include "std/memory.h" +#include "std/string.h" +#include "syscalls/syscalls.h" +#include "process/scheduler.h" + +typedef struct { + const HTTPRoute *route; + const HTTPRoute *method_route; + uint32_t methods; +} HTTPWebRouteMatch; + +static HTTPWebRouteMatch http_web_find_route(const HTTPWebServerConfig *config, string path, HTTPMethod method) { + HTTPWebRouteMatch match = {0}; + if (!config || (!config->routes && config->route_count)) return match; + + uint32_t path_len = url_path_len(path); + uint32_t best_prefix_len = 0; + uint32_t exact_methods = 0; + uint32_t prefix_methods = 0; + bool exact = false; + + for (uint32_t i = 0; i < config->route_count; i++) { + const HTTPRoute *route = &config->routes[i]; + if (!route->path || !path.data) continue; + + uint32_t route_len = (uint32_t)strlen(route->path); + bool prefix = (route->flags & HTTP_ROUTE_PREFIX) != 0; + if (path_len < route_len || (!prefix && path_len != route_len)) continue; + if (memcmp(path.data, route->path, route_len) != 0) continue; + + uint32_t methods = route->methods; + if ((methods & HTTP_METHOD_MASK_GET) && (config->head_as_get || (route->flags & HTTP_ROUTE_HEAD_AS_GET))) methods |= HTTP_METHOD_MASK_HEAD; + bool allowed = http_method_allowed(methods, method); + + if (!prefix) { + if (!exact) { + match.route = NULL; + match.method_route = NULL; + } + exact = true; + exact_methods |= methods; + if (!match.route) match.route = route; + if (allowed && !match.method_route) match.method_route = route; + } else if (!exact) { + if (route_len > best_prefix_len || !match.route) { + match.route = route; + match.method_route = NULL; + prefix_methods = 0; + best_prefix_len = route_len; + } + if (route_len == best_prefix_len) { + prefix_methods |= methods; + if (allowed && !match.method_route) match.method_route = route; + } + } + } + + match.methods = exact ? exact_methods : prefix_methods; + return match; +} + +static HTTPHeader *http_web_build_file_headers(const HTTPWebFile *file, string content_range, HTTPHeader local[8], uint32_t *out_count, char cache_buf[60]) { + static char cache_name[] = "Cache-Control"; + static char accept_ranges_name[] = "Accept-Ranges"; + static char accept_ranges_value[] = "bytes"; + static char content_range_name[] = "Content-Range"; + + *out_count = 0; + uint32_t base_count = file->headers ? file->header_count : 0; + uint32_t total = base_count+1; + + if (file->cache_max_age_sec) total++; + if (content_range.length) total++; + HTTPHeader *headers = total <= 8 ? local : (HTTPHeader*)zalloc(sizeof(HTTPHeader) * total); + if (!headers) return NULL; + uint32_t n = 0; + for (uint32_t i = 0; i < base_count; i++) headers[n++] = file->headers[i]; + headers[n++] = (HTTPHeader){{accept_ranges_name, sizeof(accept_ranges_name) - 1, 0}, {accept_ranges_value, sizeof(accept_ranges_value) - 1, 0}}; + if (file->cache_max_age_sec) { + uint32_t len = string_format_buf(cache_buf, 60, "public, max-age=%u", file->cache_max_age_sec); + headers[n++] = (HTTPHeader){{cache_name, sizeof(cache_name) - 1, 0}, {cache_buf, len, 0}}; + } + + if (content_range.length) headers[n++] = (HTTPHeader){{content_range_name, sizeof(content_range_name) - 1, 0}, content_range}; + + *out_count = n; + return headers; +} + +int32_t http_web_send(HTTPWebContext *ctx, const HTTPWebResponse *response) { + if (!ctx || !response) return SOCK_ERR_INVAL; + static char close_data[] = "close"; + const char *type = response->content_type ? response->content_type : "application/octet-stream"; + HTTPResponseMsg res = {0}; + res.status_code = response->status; + res.headers_common.fields.content_length = response->body_len; + res.headers_common.framing.has_content_length = 1; + res.headers_common.fields.content_type = (string){(char*)type, (uint32_t)strlen(type), 0}; + if (ctx->close_after_response) res.headers_common.fields.connection = (string){close_data, sizeof(close_data) - 1, 0}; + if (response->body && response->body_len) res.body = (string){(char*)response->body, response->body_len, 0}; + res.extra_headers = (HTTPHeader*)response->headers; + res.extra_header_count = response->header_count; + return http_server_send_response(ctx->server, ctx->conn, &res); +} + +static int32_t http_web_send_allow(HTTPWebContext *ctx, HttpError status, uint32_t methods) { + methods |= HTTP_METHOD_MASK_OPTIONS; + string allow = http_methods_allow_header(methods); + string body = status == HTTP_METHOD_NOT_ALLOWED ? string_format("%s\n", http_status_reason(status)) : (string){0}; + static char allow_name[] = "Allow"; + static char text_plain[] = "text/plain"; + HTTPHeader allow_header = {{allow_name, sizeof(allow_name)-1, 0}, allow}; + HTTPResponseMsg res = {0}; + res.status_code = status; + res.headers_common.fields.content_length = body.length; + res.headers_common.framing.has_content_length = 1; + res.headers_common.fields.content_type = (string){text_plain, sizeof(text_plain) - 1, 0}; + res.extra_headers = &allow_header; + res.extra_header_count = 1; + res.body = body; + int32_t rc = http_server_send_response(ctx->server, ctx->conn, &res); + string_free(allow); + string_free(body); + return rc; +} + +static int32_t http_web_send_file(HTTPWebContext *ctx, const HTTPWebFile *web_file) { + if (!ctx || !web_file || !web_file->fs_path) return SOCK_ERR_INVAL; + + fs_stat st = {0}; + if (!get_stat(kernel_fs(), web_file->fs_path, &st) || st.type != entry_file) { + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_NOT_FOUND, "Not Found\n"); + return http_web_send(ctx, &response); + } + if (st.size > UINT32_MAX || (web_file->max_bytes && st.size > web_file->max_bytes)) { + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_PAYLOAD_TOO_LARGE, "Payload Too Large\n"); + return http_web_send(ctx, &response); + } + + uint64_t file_size = st.size; + uint64_t range_start = 0; + uint64_t range_end = file_size ? file_size - 1 : 0; + uint64_t range_len = file_size; + bool partial = ctx->request->headers_common.range.has; + bool not_satisfiable = false; + + if (partial) { + const HTTPRangeSpec *in = &ctx->request->headers_common.range; + + if (in->invalid || file_size == 0 || (!in->has_start && !in->has_end)) not_satisfiable = true; + else if (in->has_start) { + range_start = in->start; + range_end = in->has_end && in->end < file_size ? in->end : file_size - 1; + not_satisfiable = range_start >= file_size || range_end < range_start; + } else { + uint64_t suffix = in->end; + not_satisfiable = suffix == 0; + if (!not_satisfiable) { + range_start = suffix >= file_size ? 0 : file_size - suffix; + range_end = file_size - 1; + } + } + + range_len = not_satisfiable ? 0 : range_end - range_start + 1; + } + + char content_range_buf[64]; + string content_range = {0}; + if (not_satisfiable) { + //print("aaa %s %llu", web_file->fs_path, file_size); + content_range.length = string_format_buf(content_range_buf, sizeof(content_range_buf), "bytes */%llu", file_size); + content_range.data = content_range_buf; + + HTTPHeader local[8]; + uint32_t total = 0; + char cache_buf[60]; + HTTPHeader *headers = http_web_build_file_headers(web_file, content_range, local, &total, cache_buf); + if (!headers) { + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error\n"); + return http_web_send(ctx, &response); + } + + HTTPResponseMsg res = {0}; + res.status_code = HTTP_RANGE_NOT_SATISFIABLE; + res.headers_common.fields.content_length = 0; + res.headers_common.framing.has_content_length = 1; + res.extra_headers = headers; + res.extra_header_count = total; + + int32_t rc = http_server_send_response(ctx->server, ctx->conn, &res); + if (headers != local) release(headers); + return rc; + } + + if (partial) { + //print("%s %llu-%llu %llu", web_file->fs_path, range_start, range_end, file_size); + content_range.length = string_format_buf(content_range_buf, sizeof(content_range_buf), "bytes %llu-%llu/%llu", range_start, range_end, file_size); + content_range.data = content_range_buf; + } + + uint32_t read_len = (uint32_t)range_len; + uint8_t *buf = NULL; + bool head = ctx->request->method == HTTP_METHOD_HEAD; + if (read_len && !head) { + buf = zalloc(file_size); + if (!buf) { + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error\n"); + return http_web_send(ctx, &response); + } + //TODO a range request allocates and reads the whole file even for a few bytes; read_range() or similar would help + if (simple_read(kernel_fs(), web_file->fs_path, buf, file_size) != file_size) { + release(buf); + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error\n"); + return http_web_send(ctx, &response); + } + } + + HTTPHeader local[8]; + uint32_t total = 0; + char cache_buf[60]; + HTTPHeader *headers = http_web_build_file_headers(web_file, content_range, local, &total, cache_buf); + if (!headers) { + if (buf) release(buf); + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error\n"); + return http_web_send(ctx, &response); + } + + HTTPWebResponse response = { + .status = partial ? HTTP_PARTIAL_CONTENT : HTTP_OK, + .content_type = web_file->content_type ? web_file->content_type : "application/octet-stream", + .body = buf ? buf + range_start : NULL, + .body_len = read_len, + .headers = headers, + .header_count = total + }; + + int32_t rc = http_web_send(ctx, &response); + if (headers != local) release(headers); + if (buf) release(buf); + return rc; +} + +int32_t http_webserver_run(const HTTPWebServerConfig *config) { + if (!config || !config->port || (!config->routes && config->route_count)) return SOCK_ERR_INVAL; + + HTTPServerPolicyOptions local_policy = config->policy_options ? *config->policy_options : (HTTPServerPolicyOptions){0}; + if (!(local_policy.flags & HTTP_SERVER_OPT_ALLOWED_METHODS)) { + uint32_t methods = HTTP_METHOD_MASK_OPTIONS; + for (uint32_t i = 0; i < config->route_count; i++) { + methods |= config->routes[i].methods; + if ((config->routes[i].methods & HTTP_METHOD_MASK_GET) && (config->head_as_get || (config->routes[i].flags & HTTP_ROUTE_HEAD_AS_GET))) methods |= HTTP_METHOD_MASK_HEAD; + } + local_policy.flags |= HTTP_SERVER_OPT_ALLOWED_METHODS; + local_policy.value.allowed_methods = methods; + } + + http_server_handle_t srv = http_server_create(config->socket_options, &local_policy); + if (!srv) return SOCK_ERR_SYS; + + struct SockBindSpec spec = {0}; + spec.kind = BIND_ANY; + int32_t rc = http_server_bind(srv, &spec, config->port); + if (rc < 0) { + http_server_destroy(srv); + return rc; + } + + rc = http_server_listen(srv, config->backlog > 0 ? config->backlog : 8); + if (rc < 0) { + http_server_close(srv); + http_server_destroy(srv); + return rc; + } + + if (config->firewall_allow) { + NetCtrlFirewallRule rule = { + .action = NET_CTRL_FIREWALL_ALLOW, + .direction = NET_CTRL_FIREWALL_IN, + .protocol = PROTO_TCP, + .port_from = config->port, + .port_to = config->port + }; + uint16_t pid = get_current_proc_pid(); + rc = pid ? firewall_add_rule(&rule, pid) : SOCK_ERR_INVAL; + if (rc < 0) { + http_server_close(srv); + http_server_destroy(srv); + return rc; + } + } + + if (config->mdns_instance && config->mdns_type && config->mdns_proto) mdns_register_service(config->mdns_instance, config->mdns_type, config->mdns_proto, config->port, config->mdns_txt); + + while (1) { + http_connection_handle_t conn = http_server_accept(srv); + if (!conn) { + msleep(10); + continue; + } + + while (2) { + HTTPRequestMsg req = http_server_recv_request(srv, conn); + if (!req.path.length) { + http_connection_close(conn); + break; + } + + HTTPWebRouteMatch match = http_web_find_route(config, req.path, req.method); + const HTTPRoute *route = match.method_route ? match.method_route : match.route; + HTTPWebContext ctx = { + .server = srv, + .conn = conn, + .request = &req, + .route = route, + .config = config, + .user = route && route->user ? route->user : config->user, + .close_after_response = config->close_each_response + }; + + if (req.method == HTTP_METHOD_OPTIONS && (match.route || config->options_for_any_path)) rc = http_web_send_allow(&ctx, HTTP_OK, match.methods); + else if (match.method_route) { + if (match.method_route->kind == HTTP_ROUTE_STATIC) rc = http_web_send(&ctx, &match.method_route->as.response); + else if (match.method_route->kind == HTTP_ROUTE_FILE) rc = http_web_send_file(&ctx, &match.method_route->as.file); + else if (match.method_route->kind == HTTP_ROUTE_HANDLER && match.method_route->as.handler) rc = match.method_route->as.handler(&ctx); + else rc = SOCK_ERR_INVAL; + } else if (match.route) rc = http_web_send_allow(&ctx, HTTP_METHOD_NOT_ALLOWED, match.methods); + else if (config->not_found.body || config->not_found.body_len || config->not_found.content_type) rc = http_web_send(&ctx, &config->not_found); + else { + HTTPWebResponse response = HTTP_WEB_TEXT_RESPONSE(HTTP_NOT_FOUND, "Not Found\n"); + rc = http_web_send(&ctx, &response); + } + + http_request_free(&req); + if (config->close_each_response || rc < 0) { + http_connection_close(conn); + break; + } + } + } +} diff --git a/kernel/networking/application_layer/http_webserver.h b/kernel/networking/application_layer/http_webserver.h new file mode 100644 index 00000000..39081cac --- /dev/null +++ b/kernel/networking/application_layer/http_webserver.h @@ -0,0 +1,95 @@ +#pragma once + +#include "http.h" +#include "csocket_http_server.h" +#include "net/socket_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct HTTPWebContext HTTPWebContext; +typedef int32_t (*HTTPWebHandler)(HTTPWebContext *ctx); + +typedef enum { + HTTP_ROUTE_EXACT = 0, + HTTP_ROUTE_PREFIX = 1u << 0, + HTTP_ROUTE_HEAD_AS_GET = 1u << 1 +} HTTPRouteFlags; + +typedef enum { + HTTP_ROUTE_HANDLER, + HTTP_ROUTE_STATIC, + HTTP_ROUTE_FILE +} HTTPRouteKind; + +typedef struct { + HttpError status; + const char *content_type; + const void *body; + uint32_t body_len; + const HTTPHeader *headers; + uint32_t header_count; +} HTTPWebResponse; + +typedef struct { + const char *fs_path; + const char *content_type; + uint32_t max_bytes; + uint32_t cache_max_age_sec; + const HTTPHeader *headers; + uint32_t header_count; +} HTTPWebFile; + +typedef struct { + const char *path; + uint32_t methods; + uint32_t flags; + HTTPRouteKind kind; + void *user; + union { + HTTPWebHandler handler; + HTTPWebResponse response; + HTTPWebFile file; + } as; +} HTTPRoute; + +typedef struct { + uint16_t port; + int backlog; + const SocketOptions *socket_options; + const HTTPServerPolicyOptions *policy_options; + const HTTPRoute *routes; + uint32_t route_count; + HTTPWebResponse not_found; + void *user; + const char *mdns_instance; + const char *mdns_type; + const char *mdns_proto; + const char *mdns_txt; + bool close_each_response; + bool head_as_get; + bool options_for_any_path; + bool firewall_allow; +} HTTPWebServerConfig; + +struct HTTPWebContext { + http_server_handle_t server; + http_connection_handle_t conn; + HTTPRequestMsg *request; + const HTTPRoute *route; + const HTTPWebServerConfig *config; + void *user; + bool close_after_response; +}; + +#define HTTP_WEB_RESPONSE(code_, type_, body_, body_len_) (HTTPWebResponse){code_, type_, body_, body_len_, NULL, 0} +#define HTTP_WEB_TEXT_RESPONSE(code_, text_) (HTTPWebResponse){code_, "text/plain", text_, sizeof(text_) - 1, NULL, 0} +#define HTTP_WEB_HTML_RESPONSE(code_, html_) (HTTPWebResponse){code_, "text/html", html_, sizeof(html_) - 1, NULL, 0} + +int32_t http_webserver_run(const HTTPWebServerConfig *config); +int32_t http_web_send(HTTPWebContext *ctx, const HTTPWebResponse *response); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/application_layer/ntp.c b/kernel/networking/application_layer/ntp.c index 60406243..b221d6d0 100644 --- a/kernel/networking/application_layer/ntp.c +++ b/kernel/networking/application_layer/ntp.c @@ -1,15 +1,13 @@ #include "ntp.h" #include "exceptions/timer.h" #include "std/memory.h" -#include "networking/internet_layer/ipv4.h" -#include "process/scheduler.h" -#include "console/kio.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" #include "math/math.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #include "networking/transport_layer/trans_utils.h" -#include "syscalls/syscalls.h" - #define NTP_PORT 123 #define NTP_VN 4 #define NTP_MODE_CLIENT 3 @@ -18,11 +16,34 @@ #define NTP_STEP_US 128000LL #define NTP_FREQ_MAX_PPM 500 +#define NTP_FREQ_MIN_INTERVAL_US 32000000ULL #define NTP_PHI_PPM 15 #define NTP_REFID_DENY 0x44454E59u //DENY +#define NTP_REFID_RATE 0x52415445u #define NTP_REFID_RSTR 0x52535452u //RSTR +#define NTP_RATE_MIN_SEC 64 +#define NTP_RATE_MAX_SHIFT 7 +#define NTP_MAX_DISP_US 16000000ULL +#define NTP_MAX_CANDIDATES 16 +#define NTP_MAX_RULES MAX_L2_INTERFACES * NTP_MAX_CANDIDATES +#define NTP_CLOCK_GUARD_US 86400ULL * 1000000ULL +#define NTP_RECOVERY_FRESH_US 120 * 1000000ULL +#define NTP_RECOVERY_AGREEMENT_US 2000000ULL +#define NTP_RECOVERY_MAX_RTT_US 1000000ULL +#define NTP_RECOVERY_MAX_DISTANCE_US 1000000ULL + +typedef struct { + int64_t offset_us; + uint64_t delay_us; + uint64_t dispersion_us; + uint64_t mono_time_us; + uint64_t server_time_us; + uint64_t root_delay_us; + uint64_t root_dispersion_us; +} ntp_sample_t; + typedef struct __attribute__((packed)) { uint8_t li_vn_mode; uint8_t stratum; @@ -38,32 +59,54 @@ typedef struct __attribute__((packed)) { } ntp_packet_t; typedef struct { - uint32_t ip_host; + net_l4_endpoint endpoint; + l3_id_t l3_id; + uint32_t l3_epoch; +} ntp_server_t; + +typedef struct { + ntp_server_t server; uint64_t last_sample_mono_us; + uint64_t last_server_time_us; int64_t last_filt_offset_us; - int32_t freq_ppm; ntp_sample_t filt[NTP_FILTER_N]; uint8_t count; uint64_t best_delay_us; - uint64_t best_disp_us; uint64_t jitter_us; uint64_t root_distance_us; int64_t best_offset_us; - uint64_t root_delay_us; - uint64_t root_disp_us; + uint64_t best_sample_mono_us; + uint64_t best_server_time_us; + uint64_t suspect_mono_us; + uint64_t suspect_server_us; + uint64_t suspect_distance_us; + uint8_t suspect_votes; + bool suspect_valid; } ntp_peer_t; -static ntp_peer_t g_peers[2]; -static uint64_t g_last_ref_unix_us = 0; +typedef struct { + net_l4_endpoint endpoint; + l3_id_t l3_id; + uint64_t rate_until_mono_us; + uint64_t last_seen_mono_us; + uint8_t rate_backoff; + bool denied; + bool used; +} ntp_rule_t; -static uint32_t g_best_ip_host = 0; -static uint64_t g_best_delay_us = 0; -static uint64_t g_best_disp_us = 0; -static uint64_t g_best_jitter_us = 0; -static uint64_t g_best_root_distance_us = 0; -static int64_t g_best_offset_us = 0; -static uint8_t g_best_count = 0; -static uint8_t g_max_count = 0; +typedef struct { + socket_handle_t sock; + uint64_t t1_us; + uint64_t orig_ntp64_be; + bool active; +} ntp_query_t; + +//https://www.rfc-editor.org/info/rfc5905/ +//TODO full RFC 5905 clock filter and multisource select +static ntp_peer_t g_peers[NTP_MAX_CANDIDATES]; +static ntp_rule_t g_rules[NTP_MAX_RULES]; +static uint32_t g_failed_l2_mask; +static uint8_t g_selected_l2_ifindex; static uint64_t unix_us_to_ntp64_be(uint64_t unix_us){ uint64_t sec = unix_us / 1000000ULL; @@ -77,16 +120,15 @@ static uint64_t ntp64_be_to_unix_us(uint64_t ntp_be){ uint64_t ntp = be64(ntp_be); uint64_t sec = ntp >> 32; uint64_t frac = ntp & 0xffffffffULL; - if (sec < NTP_UNIX_EPOCH_DELTA) return 0; + if (sec < NTP_UNIX_EPOCH_DELTA) sec += 1ULL << 32; sec -= NTP_UNIX_EPOCH_DELTA; return sec * 1000000ULL + ((frac * 1000000ULL) >> 32); } -static uint64_t ntp_short_be_to_us_signed(uint32_t v_be){ - int32_t v = be32(v_be); - if (v <= 0) return 0; - uint64_t us = ((uint64_t)v * 1000000ULL) / 65536ULL; - return us; +static uint64_t ntp_root_delay_be_to_us(uint32_t v_be, uint8_t vn){ + uint32_t v = be32(v_be); + if (vn == 3 && (v & 0x80000000u)) return 0; + return ((uint64_t)v * 1000000ULL) / 65536ULL; } static inline uint64_t ntp_local_time_us(void){ @@ -95,257 +137,473 @@ static inline uint64_t ntp_local_time_us(void){ return timer_wall_time_us(); } -static ntp_result_t ntp_send_query(socket_handle_t sock, uint32_t server_ip_host, uint64_t* t1_us_out, uint64_t* tx_ntp64_be_out){ +static ntp_result_t ntp_send_query(socket_handle_t sock, const ntp_server_t* server, uint64_t* t1_us_out, uint64_t* tx_ntp64_be_out){ ntp_packet_t p; memset(&p, 0, sizeof(p)); - p.li_vn_mode = (0u << 6) | ((uint8_t)NTP_VN << 3)| (uint8_t)NTP_MODE_CLIENT; + p.li_vn_mode = ((uint8_t)NTP_VN << 3) | (uint8_t)NTP_MODE_CLIENT; p.poll = 6; p.precision = -20; - if (g_last_ref_unix_us) p.refTs = unix_us_to_ntp64_be(g_last_ref_unix_us); - uint64_t t1_us = ntp_local_time_us(); uint64_t tx_be = unix_us_to_ntp64_be(t1_us); p.txTs = tx_be; - - net_l4_endpoint dst; - make_ep(server_ip_host, NTP_PORT, IP_VER4, &dst); - int64_t sent = socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, &p, sizeof(p)); - if (sent < 0) return NTP_ERR_SEND; + int64_t sent = send_to_socket(sock, &server->endpoint, &p, sizeof(p)); + if (sent != (int64_t)sizeof(p)) return NTP_ERR_SEND; *t1_us_out = t1_us; *tx_ntp64_be_out = tx_be; return NTP_OK; } -static int ntp_valid_server_response(const ntp_packet_t* r, uint64_t expected_orig_ntp64_be, ntp_result_t* kod_out){ +static bool ntp_valid_server_response(const ntp_packet_t* r, uint64_t expected_orig_ntp64_be, bool* kod, uint32_t* kod_refid){ + *kod = 0; + *kod_refid = 0; if (!r) return 0; - uint8_t li = (uint8_t)(r->li_vn_mode >> 6); - if (li == 3) return 0; if ((r->li_vn_mode & 0x7) != NTP_MODE_SERVER) return 0; uint8_t vn = (r->li_vn_mode >> 3) & 0x7; if (vn < 3 || vn > 4) return 0; + if (r->origTs != expected_orig_ntp64_be) return false; if (r->stratum== 0) { - if (kod_out) { - uint32_t rid = be32(r->refId); - if (rid == NTP_REFID_DENY || rid == NTP_REFID_RSTR) *kod_out = NTP_ERR_KOD; - else *kod_out = NTP_ERR_FORMAT; - } + *kod = true; + *kod_refid = be32(r->refId); return 0; } + uint8_t li = (uint8_t)(r->li_vn_mode >> 6); + if (li == 3) return 0; + if (r->stratum >= 16) return 0; - if (r->origTs != expected_orig_ntp64_be) return 0; if (r->recvTs == 0 || r->txTs == 0) return 0; uint64_t rx = ntp64_be_to_unix_us(r->recvTs); uint64_t tx = ntp64_be_to_unix_us(r->txTs); if (tx < rx) return 0; - (void)kod_out; + uint64_t ref = r->refTs ? ntp64_be_to_unix_us(r->refTs) : 0; + if (ref > tx) return 0; + + uint64_t root_delay_us = ntp_root_delay_be_to_us(r->rootDelay, vn); + uint64_t root_disp_us = ((uint64_t)be32(r->rootDispersion) * 1000000ULL) / 65536ULL; + uint64_t root_distance_us = root_delay_us / 2ULL + root_disp_us; + if (root_distance_us >= NTP_MAX_DISP_US) return 0; return 1; } -static void discover_servers(uint32_t* s0, uint32_t* s1){ - *s0 = 0; - *s1 = 0; +static bool ntp_server_l3_valid(const ntp_server_t* server) { + if (server->endpoint.ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(server->l3_id); + return ipv4_l3_is_ready(v4) && v4->epoch == server->l3_epoch; + } + + if (server->endpoint.ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(server->l3_id); + return ipv6_l3_is_ready(v6) && v6->epoch == server->l3_epoch; + } + + return false; +} + +static void ntp_server_add(ntp_server_t* out, uint32_t* count, const void* ip, ip_version_t ver, l3_id_t l3_id, uint32_t l3_epoch) { + if (*count >= NTP_MAX_CANDIDATES) return; + ntp_server_t candidate = {0}; + make_ep(ip, NTP_PORT, ver, &candidate.endpoint); + candidate.l3_id = l3_id; + candidate.l3_epoch = l3_epoch; + for (uint32_t i = 0; i < *count; i++) { + if (out[i].l3_id != candidate.l3_id || out[i].l3_epoch != candidate.l3_epoch) continue; + if (net_ep_equal(&out[i].endpoint, &candidate.endpoint)) return; + } + out[*count] = candidate; + (*count)++; +} + +static uint32_t discover_servers(ntp_server_t out[NTP_MAX_CANDIDATES], uint32_t skip_l2_mask) { + ntp_server_t fallback[NTP_MAX_CANDIDATES] = {0}; + uint32_t fallback_count = 0; + uint8_t fallback_l2_ifindex = 0; uint8_t l2n = l2_interface_count(); - for (uint8_t i = 0; i < l2n && (*s0 == 0 || *s1 == 0); i++) { + for (uint8_t i = 0; i < l2n; i++) { l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE && (*s0 == 0 || *s1 == 0); s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - const net_runtime_opts_t* rt =&v4->runtime_opts_v4; - if (!rt) continue; - uint32_t c0 = rt->ntp[0]; - uint32_t c1 = rt->ntp[1]; - if (c0 && c0 != *s0 && c0 != *s1) { - if (!*s0) *s0 = c0; - else if (!*s1) *s1 = c0; + if (!l2 || !l2->is_up) continue; + if (l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES && (skip_l2_mask & (1u << (l2->ifindex - 1)))) continue; + + ntp_server_t v4[NTP_MAX_CANDIDATES] = {0}; + ntp_server_t v6[NTP_MAX_CANDIDATES] = {0}; + uint32_t v4n = 0; + uint32_t v6n = 0; + for (int s = 0; s < (int)N_ARR(l2->l3_v4) && v4n < NTP_MAX_CANDIDATES; s++) { + l3_ipv4_interface_t* ip = l2->l3_v4[s]; + if (!ipv4_l3_is_ready(ip) || ip->is_localhost) continue; + for (uint32_t n = 0; n < N_ARR(ip->runtime_opts_v4.ntp) && v4n < NTP_MAX_CANDIDATES; n++) { + uint32_t server = ip->runtime_opts_v4.ntp[n]; + if (server) ntp_server_add(v4, &v4n, &server, IP_VER4, ip->l3_id, ip->epoch); } - if (c1 && c1 != *s0 && c1 != *s1) { - if (!*s0) *s0 = c1; - else if (!*s1) *s1 = c1; + } + + for (int s = 0; s < (int)N_ARR(l2->l3_v6) && v6n < NTP_MAX_CANDIDATES; s++) { + l3_ipv6_interface_t* ip = l2->l3_v6[s]; + if (!ipv6_l3_is_ready(ip) || ip->is_localhost || ipv6_is_linklocal(ip->ip)) continue; + for (uint32_t n = 0; n < N_ARR(ip->runtime_opts_v6.ntp) && v6n < NTP_MAX_CANDIDATES; n++) { + const uint8_t* server = ip->runtime_opts_v6.ntp[n]; + if (ipv6_is_unspecified(server) || ipv6_is_multicast(server)) continue; + l3_id_t src_l3 = ip->l3_id; + uint32_t src_epoch = ip->epoch; + if (ipv6_is_linklocal(server)) { + src_l3 = 0; + src_epoch = 0; + for (int k = 0; k < (int)N_ARR(l2->l3_v6); k++) { + l3_ipv6_interface_t* candidate = l2->l3_v6[k]; + if (!ipv6_l3_is_ready(candidate) || !ipv6_is_linklocal(candidate->ip)) continue; + src_l3 = candidate->l3_id; + src_epoch = candidate->epoch; + break; + } + } + if (src_l3) ntp_server_add(v6, &v6n, server, IP_VER6, src_l3, src_epoch); } } + + if (v4n && v6n) { + uint32_t n = 0; + uint32_t v4i = 0; + uint32_t v6i = 0; + while (n < NTP_MAX_CANDIDATES && (v4i < v4n || v6i < v6n)) { + if (v4i < v4n) { + out[n++] = v4[v4i]; + v4i++; + } + if (n < NTP_MAX_CANDIDATES && v6i < v6n) { + out[n++] = v6[v6i]; + v6i++; + } + } + g_selected_l2_ifindex = l2->ifindex; + return n; + } + + if (!fallback_count) { + fallback_l2_ifindex = l2->ifindex; + if (v4n) for (uint32_t n = 0; n < v4n && fallback_count < NTP_MAX_CANDIDATES; n++) fallback[fallback_count++] = v4[n]; + else if (v6n) for (uint32_t n = 0; n < v6n && fallback_count < NTP_MAX_CANDIDATES; n++) fallback[fallback_count++] = v6[n]; + } } + + for (uint32_t i = 0; i < fallback_count; i++) out[i] = fallback[i]; + if (fallback_count) g_selected_l2_ifindex = fallback_l2_ifindex; + + return fallback_count; } -static void discipline_apply(ntp_peer_t* best_peer, uint64_t server_unix_us_at_t4, uint64_t mono_now_us){ +static ntp_rule_t* ntp_rule_for(const ntp_server_t* server, uint64_t now_us) { + ntp_rule_t* empty = 0; + ntp_rule_t* evict = 0; + ntp_rule_t* old_denied = 0; + for (uint32_t i = 0; i < NTP_MAX_RULES; i++) { + ntp_rule_t* r = &g_rules[i]; + if (r->used && r->l3_id == server->l3_id && net_ep_equal(&r->endpoint, &server->endpoint)) { + r->last_seen_mono_us = now_us; + return r; + } + if (!r->used && !empty) empty = r; + if (!r->used) continue; + if (!r->denied && now_us < r->rate_until_mono_us) continue; + int selected = 0; + for (uint32_t j = 0; j < NTP_MAX_CANDIDATES; j++) { + if (g_peers[j].server.l3_id == r->l3_id && net_ep_equal(&r->endpoint, &g_peers[j].server.endpoint)) { + selected = 1; + break; + } + } + if (selected) continue; + if (r->denied) { + if (!old_denied || r->last_seen_mono_us < old_denied->last_seen_mono_us) old_denied = r; + } else if (!evict || r->last_seen_mono_us < evict->last_seen_mono_us) evict = r; + } + ntp_rule_t* r = empty ? empty : (evict ? evict : old_denied); + if (!r) return NULL; + memset(r, 0, sizeof(*r)); + r->used = true; + r->endpoint = server->endpoint; + r->l3_id = server->l3_id; + r->last_seen_mono_us = now_us; + return r; +} + +static bool ntp_clock_outlier(uint64_t server_us) { + if (!timer_is_synchronised()) return false; + uint64_t wall_us = timer_unix_time_us(); + if (!wall_us) return false; + uint64_t diff = server_us > wall_us ? server_us - wall_us : wall_us - server_us; + return diff > NTP_CLOCK_GUARD_US; +} + +static void ntp_peer_reset(ntp_peer_t* peer) { + memset(peer->filt, 0, sizeof(peer->filt)); + peer->count = 0; + peer->best_delay_us = 0; + peer->jitter_us = 0; + peer->root_distance_us = 0; + peer->best_offset_us = 0; + peer->best_sample_mono_us = 0; + peer->best_server_time_us = 0; + peer->last_sample_mono_us = 0; + peer->last_server_time_us = 0; + peer->last_filt_offset_us = 0; + peer->suspect_valid = false; + peer->suspect_votes = 0; +} + +static void discipline_apply(ntp_peer_t* best_peer, uint64_t server_unix_us_at_t4){ if (!best_peer) return; + if (!best_peer->best_sample_mono_us || best_peer->best_sample_mono_us <= best_peer->last_sample_mono_us) return; int64_t off = best_peer->best_offset_us; - g_last_ref_unix_us = server_unix_us_at_t4; + uint64_t sample_mono_us = best_peer->best_sample_mono_us; + uint64_t sample_server_us = best_peer->best_server_time_us; if (!timer_is_synchronised()) { timer_sync_set_unix_us(server_unix_us_at_t4); timer_sync_set_freq_ppm(0); - best_peer->last_sample_mono_us = mono_now_us; - best_peer->last_filt_offset_us = off; - best_peer->freq_ppm = 0; + for (uint32_t i = 0; i < NTP_MAX_CANDIDATES; i++) ntp_peer_reset(&g_peers[i]); return; } if (abs_i64(off) > NTP_STEP_US) { timer_sync_set_unix_us(server_unix_us_at_t4); - timer_sync_set_freq_ppm(0); - - best_peer->count = 0; - best_peer->best_delay_us = 0; - best_peer->best_disp_us = 0; - best_peer->jitter_us = 0; - best_peer->root_distance_us = 0; - best_peer->best_offset_us = 0; - best_peer->last_sample_mono_us = mono_now_us; - best_peer->last_filt_offset_us = 0; - best_peer->freq_ppm = 0; - + for (uint32_t i = 0; i < NTP_MAX_CANDIDATES; i++) ntp_peer_reset(&g_peers[i]); return; } timer_sync_slew_us(off); - if (best_peer->last_sample_mono_us != 0) { - uint64_t dt = mono_now_us - best_peer->last_sample_mono_us; - if (dt >= 1000000ULL) { + if (best_peer->last_sample_mono_us != 0 && sample_server_us > best_peer->last_server_time_us) { + uint64_t dt = sample_mono_us - best_peer->last_sample_mono_us; + if (dt >= NTP_FREQ_MIN_INTERVAL_US) { int64_t thr = (int64_t)best_peer->jitter_us * 4LL + 2000LL; if (thr < 2000) thr = 2000; if (abs_i64(off) <= thr && abs_i64(best_peer->last_filt_offset_us) <= thr) { - int64_t d_off = off - best_peer->last_filt_offset_us; - int64_t ppm_est = (d_off * 1000000LL) / (int64_t)dt; + int64_t server_dt = (int64_t)(sample_server_us - best_peer->last_server_time_us); + int64_t ppm_est = ((server_dt - (int64_t)dt) * 1000000LL) / (int64_t)dt; if (ppm_est > NTP_FREQ_MAX_PPM) ppm_est = NTP_FREQ_MAX_PPM; if (ppm_est < -NTP_FREQ_MAX_PPM) ppm_est = -NTP_FREQ_MAX_PPM; - int32_t est = (int32_t)ppm_est; - best_peer->freq_ppm = (int32_t)((best_peer->freq_ppm * 7 + est) / 8); - if (best_peer->freq_ppm > NTP_FREQ_MAX_PPM) best_peer->freq_ppm = NTP_FREQ_MAX_PPM; - if (best_peer->freq_ppm < -NTP_FREQ_MAX_PPM) best_peer->freq_ppm = -NTP_FREQ_MAX_PPM; - timer_sync_set_freq_ppm(best_peer->freq_ppm); + int32_t current = timer_sync_get_freq_ppm(); + int32_t freq_ppm = (int32_t)(((int64_t)current * 7LL + ppm_est) / 8LL); + timer_sync_set_freq_ppm(freq_ppm); } } } - best_peer->last_sample_mono_us = mono_now_us; + best_peer->last_sample_mono_us = sample_mono_us; + best_peer->last_server_time_us = sample_server_us; best_peer->last_filt_offset_us = off; } ntp_result_t ntp_poll_once(uint32_t timeout_ms){ - uint32_t s0 = 0; - uint32_t s1 = 0; - - discover_servers(&s0, &s1); - if (s0 == 0 && s1 == 0) return NTP_ERR_NO_SERVER; + ntp_server_t servers[NTP_MAX_CANDIDATES] = {0}; + uint32_t server_count = discover_servers(servers, g_failed_l2_mask); - socket_handle_t sock = udp_socket_create(0, (uint32_t)get_current_proc_pid(), NULL); - if (sock == 0) return NTP_ERR_SOCKET; - - uint64_t t1_0 = 0, t1_1 = 0; - uint64_t o0 = 0, o1 = 0; - - if (s0) { - if (ntp_send_query(sock, s0, &t1_0, &o0) != NTP_OK) t1_0 = 0; + if (!server_count && g_failed_l2_mask) { + g_failed_l2_mask = 0; + server_count = discover_servers(servers, 0); } - if (s1 && s1 != s0) { - if (ntp_send_query(sock, s1, &t1_1, &o1) != NTP_OK) t1_1 = 0; + if (!server_count) { + g_selected_l2_ifindex = 0; + return NTP_ERR_NO_SERVER; } - uint32_t waited = 0; - ntp_result_t best_err = NTP_ERR_TIMEOUT; + ntp_peer_t next[NTP_MAX_CANDIDATES]; + memset(next, 0, sizeof(next)); + for (uint32_t i = 0; i < server_count; i++) { + for (uint32_t j = 0; j < NTP_MAX_CANDIDATES; j++) { + if (!g_peers[j].server.l3_id || g_peers[j].server.l3_id != servers[i].l3_id || g_peers[j].server.l3_epoch != servers[i].l3_epoch) continue; + if (!net_ep_equal(&g_peers[j].server.endpoint, &servers[i].endpoint)) continue; + next[i] = g_peers[j]; + break; + } + next[i].server = servers[i]; + } + memcpy(g_peers, next, sizeof(g_peers)); + + ntp_result_t best_err = NTP_ERR_KOD; + + uint32_t tried = 0; + uint32_t normal_samples = 0; + bool new_suspect = false; + bool valid_response = false; + bool peer_suppressed = false; + + + while (true) { + ntp_query_t query[NTP_MAX_CANDIDATES]; + memset(query, 0, sizeof(query)); + uint32_t active = 0; + uint64_t mono_now_us = timer_now_usec(); + + for (uint32_t i = 0; i < server_count && active < 2; i++) { + if (tried & (1u << i)) continue; + ntp_peer_t* peer = &g_peers[i]; + ntp_rule_t* rule = ntp_rule_for(&peer->server, mono_now_us); + if (!rule || rule->denied || mono_now_us < rule->rate_until_mono_us) { + peer_suppressed = true; + continue; + } + tried |= 1u << i; + if (!ntp_server_l3_valid(&peer->server)) { + best_err = NTP_ERR_NO_SERVER; + continue; + } - uint64_t mono_now_us = timer_now_usec(); + socket_handle_t sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_NONBLOCK}); + if (!sock) { + best_err = NTP_ERR_SOCKET; + continue; + } + SockBindSpec spec = {.kind = BIND_L3, .ver = peer->server.endpoint.ver, .l3_id = peer->server.l3_id}; + if (bind_socket(sock, &spec, 0) != SOCK_OK) { + close_socket(sock); + best_err = NTP_ERR_SOCKET; + continue; + } + if (!ntp_server_l3_valid(&peer->server)) { + close_socket(sock); + best_err = NTP_ERR_NO_SERVER; + continue; + } + if (ntp_send_query(sock, &peer->server, &query[i].t1_us, &query[i].orig_ntp64_be) != NTP_OK) { + close_socket(sock); + best_err = NTP_ERR_SEND; + continue; + } + query[i].sock = sock; + query[i].active = true; + active++; + } + + if (!active) break; + best_err = NTP_ERR_TIMEOUT; - while (waited < timeout_ms) { - uint8_t buf[96]; - net_l4_endpoint src; - int64_t n = socket_recvfrom_udp_ex(sock, buf, sizeof(buf), &src); + uint64_t wait_start_us = timer_now_usec(); + while (active && timer_now_usec() - wait_start_us < (uint64_t)timeout_ms * 1000ULL) { + bool progress = false; + for (uint32_t i = 0; i < server_count; i++) { + if (!query[i].active) continue; + uint8_t buf[96]; + net_l4_endpoint src; + int64_t n = receive_from_socket(query[i].sock, buf, sizeof(buf), &src); - if (n >= (int64_t)sizeof(ntp_packet_t) && src.ver == IP_VER4 && src.port == NTP_PORT) { - uint32_t rip = 0; - memcpy(&rip, src.ip, 4); - if (rip == s0 || rip == s1) { + if (n >= 0) progress = true; + if (n < (int64_t)sizeof(ntp_packet_t)) continue; uint64_t t4_us = ntp_local_time_us(); uint64_t mono_sample_us = timer_now_usec(); + if (!net_ep_equal(&src,&g_peers[i].server.endpoint)) continue; + if (!ntp_server_l3_valid(&g_peers[i].server)) { + close_socket(query[i].sock); + query[i].sock = 0; + query[i].active = false; + active--; + best_err = NTP_ERR_NO_SERVER; + continue; + } + const ntp_packet_t* r = (const ntp_packet_t*)buf; - uint64_t t1_us = (rip == s0) ? t1_0 : t1_1; - uint64_t orig_be = (rip == s0) ? o0 : o1; - if (t1_us == 0) continue; - - ntp_result_t kod = NTP_OK; - if (!ntp_valid_server_response(r, orig_be, &kod)) { - if (kod == NTP_ERR_KOD) { - uint32_t refid = be32(r->refId); - if (refid == NTP_REFID_DENY || refid == NTP_REFID_RSTR) best_err = NTP_ERR_KOD; + bool kod = false; + uint32_t kod_refid = 0; + if (!ntp_valid_server_response(r, query[i].orig_ntp64_be, &kod, &kod_refid)) { + if (kod) { + ntp_rule_t* rule = ntp_rule_for(&g_peers[i].server, mono_sample_us); + if (rule) { + if (kod_refid == NTP_REFID_DENY || kod_refid == NTP_REFID_RSTR) { + rule->denied = true; + } else if (kod_refid == NTP_REFID_RATE) { + peer_suppressed = true; + uint64_t rate_sec = (uint64_t)NTP_RATE_MIN_SEC << rule->rate_backoff; + if (rule->rate_backoff < NTP_RATE_MAX_SHIFT) rule->rate_backoff++; + rule->rate_until_mono_us = mono_sample_us + rate_sec * 1000000ULL; + } + } + best_err = NTP_ERR_KOD; + close_socket(query[i].sock); + query[i].sock = 0; + query[i].active = false; + active--; } continue; } - uint64_t T2 = ntp64_be_to_unix_us(r->recvTs); - uint64_t T3 = ntp64_be_to_unix_us(r->txTs); - if (T2 == 0 || T3 == 0) continue; - - int64_t rtt = (int64_t)(t4_us - t1_us) - (int64_t)(T3 - T2); + uint64_t t2_us = ntp64_be_to_unix_us(r->recvTs); + uint64_t t3_us = ntp64_be_to_unix_us(r->txTs); + int64_t t1 = (int64_t)query[i].t1_us; + int64_t t2 = (int64_t)t2_us; + int64_t t3 = (int64_t)t3_us; + int64_t t4 = (int64_t)t4_us; + int64_t rtt = (t4 - t1) - (t3 - t2); + if (rtt < -2000) continue; if (rtt < 0) rtt = 0; - int64_t off = ((int64_t)(T2 - t1_us) + (int64_t)(T3 - t4_us)) / 2; - uint64_t server_unix_us = (uint64_t)((int64_t)t4_us + off); + int64_t off = ((t2 - t1) + (t3 - t4)) / 2; + int64_t server_time = t4 + off; const uint64_t year2000 = 946684800ULL*1000000ULL; - bool ok_range = false; - if (timer_is_synchronised()) { - uint64_t now_wall_us = timer_unix_time_us(); - uint64_t plus1d = 86400ULL * 1000000ULL; - ok_range = (server_unix_us >= year2000) && (now_wall_us == 0 || server_unix_us <= now_wall_us + plus1d); - } else { - ok_range = (server_unix_us >= year2000); - } - if (!ok_range) continue; - - ntp_peer_t* p = NULL; - for (uint32_t i = 0; i < 2; i++) { - if (g_peers[i].ip_host== rip) { - p = &g_peers[i]; - break; - } + if (server_time < (int64_t)year2000) continue; + + uint64_t server_unix_us = (uint64_t)server_time; + valid_response = true; + ntp_peer_t* p = &g_peers[i]; + uint8_t vn = (r->li_vn_mode >> 3) & 0x7; + uint64_t root_delay_us = ntp_root_delay_be_to_us(r->rootDelay, vn); + uint64_t root_disp_us = ((uint64_t) be32(r->rootDispersion) * 1000000ULL) / 65536ULL; + uint64_t distance_us = root_delay_us / 2ULL + root_disp_us + (uint64_t)rtt / 2ULL + 1000ULL; + ntp_rule_t* rule = ntp_rule_for(&p->server, mono_sample_us); + if (rule) { + rule->rate_backoff = 0; + rule->rate_until_mono_us = 0; } - if (!p) { - for (uint32_t i = 0; i < 2; i++) { - if (g_peers[i].ip_host == 0) { - memset(&g_peers[i],0, sizeof(g_peers[i])); - g_peers[i].ip_host = rip; - p = &g_peers[i]; - break; + if (ntp_clock_outlier(server_unix_us)) { + if ((uint64_t)rtt <= NTP_RECOVERY_MAX_RTT_US && distance_us <= NTP_RECOVERY_MAX_DISTANCE_US) { + uint8_t votes = 1; + if (p->suspect_valid && mono_sample_us > p->suspect_mono_us + 1000000ULL && mono_sample_us - p->suspect_mono_us <= NTP_RECOVERY_FRESH_US) { + uint64_t predicted = p->suspect_server_us + mono_sample_us - p->suspect_mono_us; + uint64_t diff = predicted > server_unix_us ? predicted - server_unix_us : server_unix_us - predicted; + if (diff <= NTP_RECOVERY_AGREEMENT_US) votes = 2; } + p->suspect_mono_us = mono_sample_us; + p->suspect_server_us = server_unix_us; + p->suspect_distance_us = distance_us; + p->suspect_votes = votes; + p->suspect_valid = true; + new_suspect = true; + } else { + p->suspect_valid = 0; + p->suspect_votes = 0; } - } - if (!p) p = &g_peers[0]; - - p->root_delay_us = ntp_short_be_to_us_signed(r->rootDelay); - p->root_disp_us = ntp_short_be_to_us_signed(r->rootDispersion); + best_err = NTP_ERR_FORMAT; + } else { + p->suspect_valid = 0; + p->suspect_votes = 0; + ntp_sample_t s; + s.offset_us = off; + s.delay_us = (uint64_t)rtt; + s.dispersion_us = 1000; + s.mono_time_us = mono_sample_us; + s.server_time_us = server_unix_us; + s.root_delay_us = root_delay_us; + s.root_dispersion_us = root_disp_us; + + for (int j = (int)NTP_FILTER_N - 1; j > 0; j--) p->filt[j] = p->filt[j - 1]; + p->filt[0] = s; + if (p->count < NTP_FILTER_N) p->count++; - ntp_sample_t s; - s.offset_us = off; - s.delay_us = (uint64_t)rtt; - s.dispersion_us = (uint64_t)rtt / 2ULL+1000; - s.mono_time_us = mono_sample_us; - - for (int i = (int)NTP_FILTER_N - 1; i > 0; i--) p->filt[i] = p->filt[i - 1]; - p->filt[0] = s; - if (p->count < NTP_FILTER_N) p->count++; - if (p->count > g_max_count) g_max_count = p->count; - - if (p->count) { uint8_t best = 0; - uint64_t best_delay = (uint64_t)-1; - uint64_t best_disp = (uint64_t)-1; + uint64_t best_delay = UINT64_MAX; + uint64_t best_disp = UINT64_MAX; - for (uint8_t i = 0; i < p->count; i++) { - uint64_t age_us = mono_sample_us -p->filt[i].mono_time_us; + for (uint8_t j = 0; j < p->count; j++) { + uint64_t age_us = mono_sample_us -p->filt[j].mono_time_us; uint64_t grow = (age_us * (uint64_t)NTP_PHI_PPM) / 1000000ULL; - uint64_t disp = p->filt[i].dispersion_us + grow; - uint64_t delay = p->filt[i].delay_us; + uint64_t disp = p->filt[j].dispersion_us + grow; + uint64_t delay = p->filt[j].delay_us; if (delay < best_delay || (delay == best_delay && disp < best_disp)) { - best = i; + best = j; best_delay = delay; best_disp = disp; } @@ -353,45 +611,91 @@ ntp_result_t ntp_poll_once(uint32_t timeout_ms){ int64_t best_off = p->filt[best].offset_us; uint64_t sumsq = 0; - for (uint8_t i = 0; i < p->count; i++) { - int64_t d = p->filt[i].offset_us - best_off; + for (uint8_t j = 0; j < p->count; j++) { + int64_t d = p->filt[j].offset_us - best_off; uint64_t a = (uint64_t)abs_i64(d); - sumsq += a * a; + if (a > UINT32_MAX) a = UINT32_MAX; + if (UINT64_MAX - sumsq < a * a) sumsq = UINT64_MAX; + else sumsq += a * a; } - uint64_t jitter = 0; - jitter = sqrt_u64(sumsq / (uint64_t)p->count); - p->best_offset_us = best_off; + p->best_sample_mono_us = p->filt[best].mono_time_us; + p->best_server_time_us = p->filt[best].server_time_us; p->best_delay_us = best_delay; - p->best_disp_us = best_disp; - p->jitter_us = jitter; + uint64_t jitter_n = p->count > 1 ? (uint64_t)p->count - 1ULL : 1ULL; + p->jitter_us = sqrt_u64(sumsq / jitter_n); - uint64_t root_dist = 0; - root_dist += p->root_disp_us; - root_dist += p->root_delay_us / 2ULL; + uint64_t root_dist = p->filt[best].root_dispersion_us; + root_dist += p->filt[best].root_delay_us / 2ULL; root_dist += best_delay / 2ULL; root_dist += best_disp; - root_dist += jitter; + root_dist += p->jitter_us; p->root_distance_us = root_dist; + normal_samples |= 1u << i; + best_err = NTP_OK; } + close_socket(query[i].sock); + query[i].sock = 0; + query[i].active = false; + active--; + progress = true; + } - best_err =NTP_OK; + if (!active) break; + if (!progress) { + uint64_t elapsed_us = timer_now_usec() - wait_start_us; + uint64_t timeout_us = (uint64_t)timeout_ms * 1000ULL; + if (elapsed_us >= timeout_us) break; + uint32_t step = (uint32_t)((timeout_us - elapsed_us + 999ULL) / 1000ULL); + if (step > 50) step = 50; + if (step) msleep(step); } - } else { - msleep(50); - waited += 50; } - if (best_err == NTP_OK && waited >= (timeout_ms / 2)) break; + for (uint32_t i = 0; i < server_count; i++) if (query[i].sock) close_socket(query[i].sock); + if (normal_samples && !new_suspect) break; } - socket_destroy_udp(sock); + uint64_t recovery_time_us = 0; + bool recovered = 0; + uint64_t now_us = timer_now_usec(); + if (new_suspect && !normal_samples) { + for (uint32_t i = 0; i < server_count; i++) { + ntp_peer_t* a = &g_peers[i]; + if (!a->suspect_valid || a->suspect_votes < 2 || now_us < a->suspect_mono_us || now_us - a->suspect_mono_us > NTP_RECOVERY_FRESH_US) continue; + ntp_rule_t* ar = ntp_rule_for(&a->server, now_us); + if (!ar || ar->denied || now_us < ar->rate_until_mono_us) continue; + uint64_t a_now = a->suspect_server_us + now_us - a->suspect_mono_us; + if (!ntp_clock_outlier(a_now)) continue; + for (uint32_t j = i+1; j < server_count; j++) { + ntp_peer_t* b = &g_peers[j]; + if (!b->suspect_valid || b->suspect_votes < 2 || now_us < b->suspect_mono_us || now_us - b->suspect_mono_us > NTP_RECOVERY_FRESH_US) continue; + ntp_rule_t* br = ntp_rule_for(&b->server, now_us); + if (!br || br->denied || now_us < br->rate_until_mono_us) continue; + if (a->server.endpoint.ver != b->server.endpoint.ver || net_ep_equal(&a->server.endpoint, &b->server.endpoint)) continue; + uint64_t b_now = b->suspect_server_us + now_us - b->suspect_mono_us; + if (!ntp_clock_outlier(b_now)) continue; + uint64_t diff = a_now > b_now ? a_now - b_now : b_now - a_now; + if (diff > NTP_RECOVERY_AGREEMENT_US) continue; + recovery_time_us = a->suspect_distance_us <= b->suspect_distance_us ? a_now : b_now; + recovered = 1; + break; + } + if (recovered)break; + } + } + + if (recovered) { + timer_sync_set_freq_ppm(0); + timer_sync_set_unix_us(recovery_time_us); + for (uint32_t i = 0; i < NTP_MAX_CANDIDATES; i++) ntp_peer_reset(&g_peers[i]); + return NTP_OK; + } ntp_peer_t* best = NULL; - for (uint32_t i = 0; i < 2; i++) { - if (g_peers[i].ip_host == 0) continue; - if (g_peers[i].count == 0) continue; + for (uint32_t i = 0; i < server_count; i++) { + if (!(normal_samples & (1u << i)) || !g_peers[i].count) continue; if (!best) best = &g_peers[i]; else { uint64_t da = g_peers[i].root_distance_us; @@ -400,23 +704,27 @@ ntp_result_t ntp_poll_once(uint32_t timeout_ms){ } } - if (!best) return best_err; - - g_best_ip_host = best->ip_host; - g_best_offset_us = best->best_offset_us; - g_best_delay_us = best->best_delay_us; - g_best_disp_us = best->best_disp_us; - g_best_jitter_us = best->jitter_us; - g_best_root_distance_us = best->root_distance_us; - g_best_count = best->count; + if (!best) { + if (!peer_suppressed && !valid_response && g_selected_l2_ifindex && g_selected_l2_ifindex <= MAX_L2_INTERFACES) g_failed_l2_mask |= 1u << (g_selected_l2_ifindex - 1); + return best_err; + } - mono_now_us = timer_now_usec(); uint64_t t4_us = ntp_local_time_us(); uint64_t server_unix_us = (uint64_t)((int64_t)t4_us + best->best_offset_us); - discipline_apply(best, server_unix_us, mono_now_us); + discipline_apply(best, server_unix_us); return NTP_OK; } -uint8_t ntp_max_filter_count(void){ return g_max_count; } \ No newline at end of file +uint8_t ntp_max_filter_count(void) { + uint64_t now = timer_now_usec(); + uint8_t max_count = 0; + for (uint32_t i = 0; i < NTP_MAX_CANDIDATES; i++) { + if (!g_peers[i].server.l3_id) continue; + ntp_rule_t* rule = ntp_rule_for(&g_peers[i].server, now); + if (!rule || rule->denied || now < rule->rate_until_mono_us) continue; + if (g_peers[i].count > max_count) max_count = g_peers[i].count; + } + return max_count; +} diff --git a/kernel/networking/application_layer/ntp.h b/kernel/networking/application_layer/ntp.h index 3ef25f48..3c2ff53e 100644 --- a/kernel/networking/application_layer/ntp.h +++ b/kernel/networking/application_layer/ntp.h @@ -16,13 +16,6 @@ typedef enum { NTP_ERR_KOD } ntp_result_t; -typedef struct { - int64_t offset_us; - uint64_t delay_us; - uint64_t dispersion_us; - uint64_t mono_time_us; -} ntp_sample_t; - #define NTP_FILTER_N 8 ntp_result_t ntp_poll_once(uint32_t timeout_ms); diff --git a/kernel/networking/application_layer/ntp_daemon.c b/kernel/networking/application_layer/ntp_daemon.c index 827bf1e1..b5bc55e6 100644 --- a/kernel/networking/application_layer/ntp_daemon.c +++ b/kernel/networking/application_layer/ntp_daemon.c @@ -1,74 +1,38 @@ #include "ntp_daemon.h" #include "ntp.h" -#include "exceptions/timer.h" #include "process/scheduler.h" -#include "networking/internet_layer/ipv4.h" -#include "networking/internet_layer/ipv4_utils.h" -#include "networking/interface_manager.h" #include "syscalls/syscalls.h" static uint16_t g_pid_ntp = 0xFFFF; -static socket_handle_t g_sock = 0; - -uint16_t ntp_get_pid(void){ return g_pid_ntp; } bool ntp_is_running(void){ return g_pid_ntp != 0xFFFF; } -void ntp_set_pid(uint16_t p){ g_pid_ntp = p; } -socket_handle_t ntp_socket_handle(void){ return g_sock; } -#define NTP_POLL_INTERVAL_MS 60000u +#define NTP_POLL_INTERVAL_MS 64000u #define NTP_QUERY_TIMEOUT_MS 1200u -#define NTP_BOOTSTRAP_MAX_RETRY 8u -#define NTP_WARMUP_INTERVAL_MS 400u - -static bool any_ipv4_configured_nonlocal(void){ - uint8_t n = l2_interface_count(); - for (uint8_t i = 0; i < n; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2 || !l2->is_up) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (!v4->ip) continue; - if (v4->is_localhost) continue; - if (ipv4_is_loopback(v4->ip)) continue; - return true; - } - } - return false; -} +#define NTP_WARMUP_INTERVAL_MS 2000u +#define NTP_NO_SERVER_INTERVAL_MS 10000u +#define NTP_RETRY_MIN_MS 16000u +#define NTP_RETRY_MAX_MS 64000u int ntp_daemon_entry(int argc, char* argv[]){ (void)argc; (void)argv; g_pid_ntp = get_current_proc_pid(); - g_sock = udp_socket_create(0, g_pid_ntp, NULL); - ntp_set_pid(get_current_proc_pid()); - uint32_t attempts = 0; - while (attempts < NTP_BOOTSTRAP_MAX_RETRY) { - if (!any_ipv4_configured_nonlocal()) { - msleep(500); - continue; - } - ntp_result_t r = ntp_poll_once(NTP_QUERY_TIMEOUT_MS); - if (r == NTP_OK) break; - attempts++; - uint32_t backoff_ms = (1<<(attempts <= 4 ? attempts : 4)) * 250u; - uint32_t jitter = backoff_ms / 10u; - msleep(backoff_ms + (jitter / 2u)); - } - - while (any_ipv4_configured_nonlocal() && ntp_max_filter_count() < NTP_FILTER_N) { - ntp_poll_once(NTP_QUERY_TIMEOUT_MS); - msleep(NTP_WARMUP_INTERVAL_MS); - } + uint32_t retry_ms = NTP_RETRY_MIN_MS; for (;;) { - ntp_poll_once(NTP_QUERY_TIMEOUT_MS); - msleep(NTP_POLL_INTERVAL_MS); + ntp_result_t r = ntp_poll_once(NTP_QUERY_TIMEOUT_MS); + if (r == NTP_OK) { + retry_ms = NTP_RETRY_MIN_MS; + msleep(ntp_max_filter_count() < NTP_FILTER_N ? NTP_WARMUP_INTERVAL_MS : NTP_POLL_INTERVAL_MS); + } else if (r == NTP_ERR_NO_SERVER) { + retry_ms = NTP_RETRY_MIN_MS; + msleep(NTP_NO_SERVER_INTERVAL_MS); + } else { + msleep(retry_ms); + if (retry_ms < NTP_RETRY_MAX_MS / 2) retry_ms *= 2; + else retry_ms = NTP_RETRY_MAX_MS; + } } - - return 1; } diff --git a/kernel/networking/application_layer/ntp_daemon.h b/kernel/networking/application_layer/ntp_daemon.h index ffec242e..3a3efc1b 100644 --- a/kernel/networking/application_layer/ntp_daemon.h +++ b/kernel/networking/application_layer/ntp_daemon.h @@ -1,16 +1,13 @@ #pragma once #include "types.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #ifdef __cplusplus extern "C" { #endif -uint16_t ntp_get_pid(void); bool ntp_is_running(void); -void ntp_set_pid(uint16_t p); -socket_handle_t ntp_socket_handle(void); int ntp_daemon_entry(int argc, char* argv[]); diff --git a/kernel/networking/application_layer/sntp.c b/kernel/networking/application_layer/sntp.c deleted file mode 100644 index 9f17b3e9..00000000 --- a/kernel/networking/application_layer/sntp.c +++ /dev/null @@ -1,169 +0,0 @@ -#include "sntp.h" //deprecated, use ntp -#include "exceptions/timer.h" -#include "std/memory.h" -#include "networking/internet_layer/ipv4.h" -#include "process/scheduler.h" -#include "console/kio.h" -#include "types.h" -#include "networking/transport_layer/csocket_udp.h" -#include "syscalls/syscalls.h" -#include "networking/transport_layer/trans_utils.h" - -#define NTP_PORT 123 -#define NTP_UNIX_EPOCH_DELTA 2208988800UL - -typedef struct __attribute__((packed)) { - uint8_t li_vn_mode; - uint8_t stratum; - uint8_t poll; - int8_t precision; - uint32_t rootDelay; - uint32_t rootDispersion; - uint32_t refId; - uint64_t refTs; - uint64_t origTs; - uint64_t recvTs; - uint64_t txTs; -} ntp_packet_t; - -static uint64_t unix_us_to_ntp64_be(uint64_t unix_us){ - uint64_t sec = unix_us / 1000000ULL; - uint64_t frac = ((unix_us % 1000000ULL) << 32) / 1000000ULL; - sec += NTP_UNIX_EPOCH_DELTA; - uint64_t ntp = (sec << 32) | (frac & 0xffffffffULL); - return be64(ntp); -} - -static uint64_t ntp64_be_to_unix_us(uint64_t ntp_be){ - uint64_t ntp = be64(ntp_be); - uint64_t sec = ntp >> 32; - uint64_t frac = ntp & 0xffffffffULL; - if (sec < NTP_UNIX_EPOCH_DELTA) return 0; - sec -= NTP_UNIX_EPOCH_DELTA; - return sec * 1000000ULL + ((frac * 1000000ULL) >> 32); -} - -static sntp_result_t sntp_send_query(socket_handle_t sock, uint32_t server_ip_host, uint64_t* t1_us_out) { - ntp_packet_t p; - memset(&p, 0, sizeof(p)); - p.li_vn_mode = (0u<<6) | (4u<<3) | 3u; - uint64_t t1_us = timer_wall_time_us(); - p.txTs = unix_us_to_ntp64_be(t1_us); - net_l4_endpoint dst; - make_ep(server_ip_host, NTP_PORT, IP_VER4, &dst); - int64_t sent = socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, &p, sizeof(p)); - if (sent < 0) return SNTP_ERR_SEND; - *t1_us_out = t1_us; - return SNTP_OK; -} - -sntp_result_t sntp_poll_once(uint32_t timeout_ms){ - uint32_t s0 = 0; - uint32_t s1 = 0; - - uint8_t l2n = l2_interface_count(); - for (uint8_t i = 0; i < l2n && (s0 == 0 || s1 == 0); i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE && (s0 == 0 || s1 == 0); s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - const net_runtime_opts_t* rt = &v4->runtime_opts_v4; - if (!rt) continue; - uint32_t c0 = rt->ntp[0]; - uint32_t c1 = rt->ntp[1]; - if (c0 && c0 != s0 && c0 != s1){ - if (!s0) s0 = c0; - else if (!s1) s1 = c0; - } - if (c1 && c1 != s0 && c1 != s1){ - if (!s0) s0 = c1; - else if (!s1) s1 = c1; - } - } - } - - if (s0 == 0 && s1 == 0) return SNTP_ERR_NO_SERVER; - - socket_handle_t sock = udp_socket_create(0, (uint32_t)get_current_proc_pid(), NULL); - if (sock == 0) return SNTP_ERR_SOCKET; - - uint64_t t1_0 = 0, t1_1 = 0; - - if (s0) { - if (sntp_send_query(sock, s0, &t1_0) != SNTP_OK) t1_0 = 0; - } - if (s1 && s1 != s0) { - if (sntp_send_query(sock, s1, &t1_1) != SNTP_OK) t1_1 = 0; - } - - uint32_t waited = 0; - uint64_t best_server_unix_us = 0; - uint64_t best_rtt_us = (uint64_t)-1; - - while (waited < timeout_ms){ - uint8_t buf[96]; - net_l4_endpoint src; - int64_t n = socket_recvfrom_udp_ex(sock, buf, sizeof(buf), &src); - - if (n >= (int64_t)sizeof(ntp_packet_t) && src.ver == IP_VER4 && src.port == NTP_PORT){ - uint32_t rip = 0; - memcpy(&rip, src.ip, 4); - if (rip == s0 || rip == s1){ - ntp_packet_t* r = (ntp_packet_t*)buf; - uint64_t t4_us = timer_wall_time_us(); - - uint64_t T1 = ntp64_be_to_unix_us(r->origTs); - uint64_t T2 = ntp64_be_to_unix_us(r->recvTs); - uint64_t T3 = ntp64_be_to_unix_us(r->txTs); - - if (T1 != 0 && T3 != 0) { - uint64_t t1_us = (rip == s0) ? t1_0 : t1_1; - uint64_t d = (T1 > t1_us) ? (T1 - t1_us) : (t1_us - T1); - if (t1_us != 0 && d <= 1000000ULL) { - int64_t rtt = (int64_t)(t4_us - t1_us) - (int64_t)(T3 - T2); - if (rtt < 0) rtt = 0; - int64_t off = ((int64_t)(T2 - t1_us) + (int64_t)(T3 - t4_us)) / 2; - - uint64_t server_unix_us = (uint64_t)((int64_t)t4_us + off); - - const uint64_t year2000 = 946684800ULL * 1000000ULL; - bool ok_range = false; - - if (timer_is_synchronised()) { - uint64_t now_wall_ms = timer_unix_time_ms(); - uint64_t now_wall_us = now_wall_ms ? (now_wall_ms * 1000ULL) : 0; - const uint64_t plus1d = 86400ULL * 1000000ULL; - ok_range = (server_unix_us >= year2000) &&(now_wall_us == 0 || server_unix_us <= now_wall_us + plus1d); - } else { - ok_range = (server_unix_us >= year2000); - } - if (ok_range) { - if ((uint64_t)rtt < best_rtt_us){ - best_rtt_us = (uint64_t)rtt; - best_server_unix_us = server_unix_us; - } - } - } - } - } - } else { - msleep(50); - waited += 50; - } - - if (best_server_unix_us != 0 && waited >= (timeout_ms / 2)) break; - } - - socket_destroy_udp(sock); - - if (best_server_unix_us == 0) return SNTP_ERR_TIMEOUT; - - //uint64_t sec = best_server_unix_us / 1000000ULL; - //uint64_t frac = ((best_server_unix_us % 1000000ULL) << 32) / 1000000ULL; - //uint64_t ntp64 = ((sec + NTP_UNIX_EPOCH_DELTA) << 32) | frac; - - timer_apply_sntp_sample_us(best_server_unix_us); - return SNTP_OK; -} diff --git a/kernel/networking/application_layer/sntp.h b/kernel/networking/application_layer/sntp.h deleted file mode 100644 index 1816b682..00000000 --- a/kernel/networking/application_layer/sntp.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once//deprecated, use ntp -#include "types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - SNTP_OK = 0, - SNTP_ERR_NO_CFG, - SNTP_ERR_NO_SERVER, - SNTP_ERR_SOCKET, - SNTP_ERR_SEND, - SNTP_ERR_TIMEOUT, - SNTP_ERR_FORMAT -} sntp_result_t; - -sntp_result_t sntp_poll_once(uint32_t timeout_ms); - -#ifdef __cplusplus -} -#endif diff --git a/kernel/networking/application_layer/sntp_daemon.c b/kernel/networking/application_layer/sntp_daemon.c deleted file mode 100644 index 21a7adca..00000000 --- a/kernel/networking/application_layer/sntp_daemon.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "sntp_daemon.h"//deprecated, use ntp -#include "sntp.h" -#include "exceptions/timer.h" -#include "process/scheduler.h" -#include "networking/internet_layer/ipv4.h" -#include "networking/internet_layer/ipv4_utils.h" - -#include "networking/interface_manager.h" -#include "syscalls/syscalls.h" - - -static uint16_t g_pid_sntp = 0xFFFF; -static socket_handle_t g_sock = 0; - -uint16_t sntp_get_pid(void){ return g_pid_sntp; } -bool sntp_is_running(void){ return g_pid_sntp != 0xFFFF; } -void sntp_set_pid(uint16_t p){ g_pid_sntp = p; } -socket_handle_t sntp_socket_handle(void){ return g_sock; } - -#define SNTP_POLL_INTERVAL_MS (10u * 60u * 1000u) -#define SNTP_QUERY_TIMEOUT_MS 1200u -#define SNTP_BOOTSTRAP_MAX_RETRY 5u - -static bool any_ipv4_configured_nonlocal(void){ - uint8_t n = l2_interface_count(); - for (uint8_t i = 0; i < n; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2 || !l2->is_up) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (!v4->ip) continue; - if (v4->is_localhost) continue; - if (ipv4_is_loopback(v4->ip)) continue; - return true; - } - } - return false; -} - -int sntp_daemon_entry(int argc, char* argv[]){ - (void)argc; (void)argv; - g_pid_sntp = (uint16_t)get_current_proc_pid(); - g_sock = udp_socket_create(0, g_pid_sntp, NULL); - sntp_set_pid(get_current_proc_pid()); - uint32_t attempts = 0; - while (attempts < SNTP_BOOTSTRAP_MAX_RETRY){ - if (!any_ipv4_configured_nonlocal()){ - msleep(500); - continue; - } - sntp_result_t r = sntp_poll_once(SNTP_QUERY_TIMEOUT_MS); - if (r == SNTP_OK){ - break; - } - attempts++; - uint32_t backoff_ms = (1u << (attempts <= 4 ? attempts : 4)) * 250u; - uint32_t jitter = backoff_ms / 10u; - msleep(backoff_ms + (jitter / 2u)); - } - for(;;){ - sntp_poll_once(SNTP_QUERY_TIMEOUT_MS); - msleep(SNTP_POLL_INTERVAL_MS); - } - return 1; -} diff --git a/kernel/networking/application_layer/sntp_daemon.h b/kernel/networking/application_layer/sntp_daemon.h deleted file mode 100644 index 8ea56ac1..00000000 --- a/kernel/networking/application_layer/sntp_daemon.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once//deprecated, use ntp -#include "types.h" -#include "networking/transport_layer/csocket_udp.h" - -#ifdef __cplusplus -extern "C" { -#endif - -uint16_t sntp_get_pid(void); -bool sntp_is_running(void); -void sntp_set_pid(uint16_t p); -socket_handle_t sntp_socket_handle(void); -int sntp_daemon_entry(int argc, char* argv[]); - -#ifdef __cplusplus -} -#endif diff --git a/kernel/networking/application_layer/socket_http_client.hpp b/kernel/networking/application_layer/socket_http_client.hpp deleted file mode 100644 index be410252..00000000 --- a/kernel/networking/application_layer/socket_http_client.hpp +++ /dev/null @@ -1,226 +0,0 @@ -#pragma once -#include "console/kio.h" -#include "networking/transport_layer/socket_tcp.hpp" -#include "http.h" -#include "std/std.h" -#include "net/socket_types.h" - -class HTTPClient { -private: - uint16_t pid; - TCPSocket* sock; - SocketExtraOptions log_opts; - SocketExtraOptions* tcp_extra; - -public: - explicit HTTPClient(uint16_t pid_, const SocketExtraOptions* extra) : pid(pid_), sock(nullptr), log_opts{}, tcp_extra(nullptr) { - if (extra) log_opts = *extra; - - const SocketExtraOptions* tcp_ptr = extra; - if (extra && (log_opts.flags & SOCK_OPT_DEBUG)) { - tcp_extra = (SocketExtraOptions*)malloc(sizeof(SocketExtraOptions)); - if (tcp_extra) { - *tcp_extra = *extra; - tcp_extra->flags &= ~SOCK_OPT_DEBUG; - tcp_ptr = tcp_extra; - } - } - - sock = (TCPSocket*)malloc(sizeof(TCPSocket)); - if (sock) new (sock) TCPSocket(SOCK_ROLE_CLIENT, pid, tcp_ptr); - } - - ~HTTPClient() {close();} - - int32_t connect(SockDstKind kind, const void* dst, uint16_t port) { - uint16_t p = port; - int32_t r = sock ? sock->connect(kind, dst, p) : SOCK_ERR_STATE; - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_CLIENT; - ev.action = NETLOG_ACT_CONNECT; - ev.pid = pid; - ev.dst_kind = kind; - ev.u0 = p; - if (kind == DST_DOMAIN) ev.s0 = (const char*)dst; - if (kind == DST_ENDPOINT && dst) ev.dst_ep = *(const net_l4_endpoint*)dst; - ev.i0 = r; - - if (sock) { - ev.local_port = sock->get_local_port(); - ev.remote_ep = sock->get_remote_ep(); - if (ev.remote_ep.ver) ev.dst_ep = ev.remote_ep; - } - - netlog_socket_event(&log_opts, &ev); - return r; - } - - HTTPResponseMsg send_request(const HTTPRequestMsg& req) { - HTTPResponseMsg resp{}; - if (!sock) { - resp.status_code = (HttpError)SOCK_ERR_STATE; - return resp; - } - - string out = http_request_builder(&req); - uint32_t out_len = out.length; - - uint32_t off = 0; - int64_t sent = 0; - while (off < out_len) { - int64_t r = sock->send(out.data + off, out_len - off); - if (r == TCP_WOULDBLOCK) { - msleep(5); - continue; - } - if (r < 0) { - sent = r; - break; - } - off += (uint32_t)r; - } - if (sent >= 0) sent = (int64_t)off; - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_CLIENT; - ev.action = NETLOG_ACT_HTTP_SEND_REQUEST; - ev.pid = pid; - ev.u0 = out_len; - ev.i0 = sent; - ev.local_port = sock->get_local_port(); - ev.remote_ep = sock->get_remote_ep(); - - char pathbuf[128]; - if (req.path.length && req.path.data) { - uint32_t n = req.path.length; - if (n > sizeof(pathbuf) - 1) n = sizeof(pathbuf) - 1; - memcpy(pathbuf, req.path.data, n); - pathbuf[n] = 0; - ev.s0 = pathbuf; - } - - netlog_socket_event(&log_opts, &ev); - string_free(out); - - if (sent < 0) { - resp.status_code = (HttpError)sent; - return resp; - } - - string buf = string_repeat('\0', 0); - char tmp[512]; - int hdr_end = -1; - - while (hdr_end < 0) { - int64_t r = sock->recv(tmp, sizeof(tmp)); - if (r == TCP_WOULDBLOCK) { - msleep(10); - continue; - } - if (r < 0) { - string_free(buf); - resp.status_code = (HttpError)r; - return resp; - } - if (r == 0) { - string_free(buf); - resp.status_code = (HttpError)SOCK_ERR_PROTO; - return resp; - } - string_append_bytes(&buf, tmp, (uint32_t)r); - hdr_end = find_crlfcrlf(buf.data, buf.length); - } - - uint32_t i = 0; - while (i < (uint32_t)hdr_end && buf.data[i] != ' ') i++; - uint32_t code = 0, j = i+1; - while (j < (uint32_t)hdr_end && buf.data[j] >= '0' && buf.data[j] <= '9') { - code = code*10 + (buf.data[j]-'0'); - ++j; - } - resp.status_code = (HttpError)code; - while (j < (uint32_t)hdr_end && buf.data[j]==' ') ++j; - if (j < (uint32_t)hdr_end) { - uint32_t rlen = hdr_end - j; - resp.reason = string_repeat('\0', 0); - string_append_bytes(&resp.reason, buf.data+j, rlen); - } - - HTTPHeader *extras = nullptr; - uint32_t extra_count = 0; - int status_line_end = strindex((char*)buf.data, "\r\n"); - http_header_parser( - (char*)buf.data + status_line_end + 2, - buf.length - (uint32_t)(status_line_end + 2), - &resp.headers_common, - &extras, - &extra_count); - resp.extra_headers = extras; - resp.extra_header_count = extra_count; - - uint32_t body_start = hdr_end + 4; - uint32_t have = (buf.length > body_start) ? buf.length - body_start : 0; - - uint32_t need = resp.headers_common.length; - if (need > 0) { - while (have < need) { - int64_t r = sock->recv(tmp, sizeof(tmp)); - if (r == TCP_WOULDBLOCK) { msleep(10); continue; } - if (r < 0) break; - if (r == 0) break; - string_append_bytes(&buf, tmp, (uint32_t)r); - have += (uint32_t)r; - } - } - if (have > 0) { - char *body_copy = (char*)malloc(have); - if (body_copy) { - memcpy(body_copy, buf.data + body_start, have); - resp.body.ptr = (uintptr_t)body_copy; - resp.body.size = have; - } - } - - netlog_socket_event_t ev1{}; - ev1.comp = NETLOG_COMP_HTTP_CLIENT; - ev1.action = NETLOG_ACT_HTTP_RECV_RESPONSE; - ev1.pid = pid; - ev1.u0 = (uint32_t)resp.status_code; - ev1.u1 = (uint32_t)resp.body.size; - ev1.local_port = sock->get_local_port(); - ev1.remote_ep = sock->get_remote_ep(); - netlog_socket_event(&log_opts, &ev1); - - string_free(buf); - return resp; - } - - int32_t close() { - int32_t r = SOCK_ERR_STATE; - if (sock) r = sock->close(); - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_CLIENT; - ev.action = NETLOG_ACT_CLOSE; - ev.pid = pid; - ev.i0 = r; - - if (sock) { - ev.local_port = sock->get_local_port(); - ev.remote_ep = sock->get_remote_ep(); - } - - netlog_socket_event(&log_opts, &ev); - - if (sock) sock->~TCPSocket(); - if (sock) free_sized(sock, sizeof(TCPSocket)); - sock = nullptr; - - if (tcp_extra) free_sized(tcp_extra, sizeof(SocketExtraOptions)); - tcp_extra = nullptr; - - log_opts.flags &= ~SOCK_OPT_DEBUG; - return r; - } -}; diff --git a/kernel/networking/application_layer/socket_http_server.hpp b/kernel/networking/application_layer/socket_http_server.hpp deleted file mode 100644 index 9e888288..00000000 --- a/kernel/networking/application_layer/socket_http_server.hpp +++ /dev/null @@ -1,270 +0,0 @@ -#pragma once -#include "console/kio.h" -#include "networking/transport_layer/socket_tcp.hpp" -#include "http.h" -#include "std/std.h" -#include "net/socket_types.h" - - -class HTTPServer { -private: - uint16_t pid; - TCPSocket* sock; - SocketExtraOptions log_opts; - SocketExtraOptions* tcp_extra; - -public: - explicit HTTPServer(uint16_t pid_, const SocketExtraOptions* extra) : pid(pid_), sock(nullptr), log_opts{}, tcp_extra(nullptr) { - if (extra) log_opts = *extra; - - const SocketExtraOptions* tcp_ptr = extra; - if (extra && (log_opts.flags & SOCK_OPT_DEBUG)) { - tcp_extra = (SocketExtraOptions*)malloc(sizeof(SocketExtraOptions)); - if (tcp_extra) { - *tcp_extra = *extra; - tcp_extra->flags &= ~SOCK_OPT_DEBUG; - tcp_ptr = tcp_extra; - } - } - - sock = (TCPSocket*)malloc(sizeof(TCPSocket)); - if (sock) new (sock) TCPSocket(SOCK_ROLE_SERVER, pid, tcp_ptr); - } - - ~HTTPServer() { close(); } - - int32_t bind(const SockBindSpec& spec, uint16_t port) { - uint16_t p = port; - int32_t r = sock ? sock->bind(spec, p) : SOCK_ERR_STATE; - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_BIND; - ev.pid = pid; - ev.u0 = p; - ev.i0 = r; - netlog_socket_event(&log_opts, &ev); - return r; - } - - int32_t listen(int backlog = 4) { - int b = backlog; - int32_t r = sock ? sock->listen(b) : SOCK_ERR_STATE; - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_LISTEN; - ev.pid = pid; - ev.u0 = (uint32_t)b; - ev.i0 = r; - netlog_socket_event(&log_opts, &ev); - return r; - } - - TCPSocket* accept() { - TCPSocket* c = sock ? sock->accept() : nullptr; - if (c) { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_ACCEPT; - ev.pid = pid; - ev.i0 = (int64_t)(uintptr_t)c; - ev.local_port = c->get_local_port(); - ev.remote_ep = c->get_remote_ep(); - netlog_socket_event(&log_opts, &ev); - } - return c; - } - - HTTPRequestMsg recv_request(TCPSocket* client) { - HTTPRequestMsg req{}; - if (!client) return req; - - string buf = string_repeat('\0', 0); - char tmp[512]; - int hdr_end = -1; - - while (hdr_end < 0) { - int64_t r = client->recv(tmp, sizeof(tmp)); - if (r == TCP_WOULDBLOCK) { - msleep(10); - continue; - } - if (r <= 0) { - string_free(buf); - return req; - } - string_append_bytes(&buf, tmp, (uint32_t)r); - hdr_end = find_crlfcrlf(buf.data, buf.length); - } - - uint32_t line_end = 0; - while (line_end + 1u < (uint32_t)hdr_end) { - if (buf.data[line_end] == '\r' && buf.data[line_end + 1u] == '\n') - break; - ++line_end; - } - - uint32_t p = 0; - while (p + 1u < line_end && buf.data[p] == '\r' && buf.data[p + 1u] == '\n') - p += 2; - - uint32_t i = p; - while (i < line_end && buf.data[i] != ' ') ++i; - - const char* method_tok = buf.data + p; - uint32_t mlen = i > p ? (i - p) : 0; - - if (mlen == 3 && memcmp(method_tok, "GET", 3) == 0) req.method = HTTP_METHOD_GET; - else if (mlen == 4 && memcmp(method_tok, "POST", 4) == 0) req.method = HTTP_METHOD_POST; - else if (mlen == 3 && memcmp(method_tok, "PUT", 3) == 0) req.method = HTTP_METHOD_PUT; - else if (mlen == 6 && memcmp(method_tok, "DELETE", 6) == 0) req.method = HTTP_METHOD_DELETE; - else req.method = HTTP_METHOD_GET; - - uint32_t j = (i < line_end) ? (i + 1u) : line_end; - uint32_t path_start = j; - while (j < line_end && buf.data[j] != ' ') ++j; - req.path = string_repeat('\0', 0); - string_append_bytes(&req.path, buf.data + path_start, j - path_start); - - if (req.path.length >= 7 && memcmp(req.path.data, "http://", 7) == 0) { - uint32_t k = 7; - while (k < req.path.length && req.path.data[k] != '/') ++k; - if (k < req.path.length) { - string newp = string_repeat('\0', 0); - string_append_bytes(&newp, req.path.data + k, req.path.length - k); - string_free(req.path); - req.path = newp; - } - } else if (req.path.length >= 8 && memcmp(req.path.data, "https://", 8) == 0) { - uint32_t k = 8; - while (k < req.path.length && req.path.data[k] != '/') ++k; - if (k < req.path.length) { - string newp = string_repeat('\0', 0); - string_append_bytes(&newp, req.path.data + k, req.path.length - k); - string_free(req.path); - req.path = newp; - } - } - - int status_line_end = (int)line_end; - http_header_parser( - (char*)buf.data + status_line_end + 2, - (uint32_t)hdr_end - (uint32_t)(status_line_end + 2), - &req.headers_common, - &req.extra_headers, - &req.extra_header_count - ); - - uint32_t body_start = hdr_end + 4; - uint32_t have = buf.length > body_start ? buf.length - body_start : 0; - uint32_t need = req.headers_common.length; - - if (need > 0) { - while (have < need) { - int64_t r = client->recv(tmp, sizeof(tmp)); - if (r == TCP_WOULDBLOCK) { - msleep(10); - continue; - } - if (r < 0) break; - if (r == 0) break; - string_append_bytes(&buf, tmp, (uint32_t)r); - have += (uint32_t)r; - } - } - - if (have > 0) { - char* body_copy = (char*)malloc(have); - if (body_copy) { - memcpy(body_copy, buf.data + body_start, have); - req.body.ptr = (uintptr_t)body_copy; - req.body.size = have; - } - } - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_HTTP_RECV_REQUEST; - ev.pid = pid; - ev.u0 = (uint32_t)req.method; - ev.u1 = (uint32_t)req.path.length; - ev.i0 = (int64_t)req.body.size; - ev.local_port = client->get_local_port(); - ev.remote_ep = client->get_remote_ep(); - - char pathbuf[128]; - if (req.path.length && req.path.data) { - uint32_t n = req.path.length; - if (n > sizeof(pathbuf) - 1) n = sizeof(pathbuf) - 1; - memcpy(pathbuf, req.path.data, n); - pathbuf[n] = 0; - ev.s0 = pathbuf; - } - - netlog_socket_event(&log_opts, &ev); - - string_free(buf); - return req; - } - - int32_t send_response(TCPSocket* client, const HTTPResponseMsg& res) { - if (!client) return SOCK_ERR_STATE; - uint32_t code = (uint32_t)res.status_code; - string out = http_response_builder(&res); - uint32_t out_len = out.length; - uint32_t off = 0; - int64_t sent = 0; - while (off < out_len) { - int64_t r = client->send(out.data + off, out_len - off); - if (r == TCP_WOULDBLOCK) { - msleep(5); - continue; - } - if (r < 0) { - sent = r; - break; - } - off += (uint32_t)r; - } - if (sent >= 0) sent = (int64_t)off; - - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_HTTP_SEND_RESPONSE; - ev.pid = pid; - ev.u0 = code; - ev.u1 = out_len; - ev.i0 = sent; - ev.local_port = client->get_local_port(); - ev.remote_ep = client->get_remote_ep(); - netlog_socket_event(&log_opts, &ev); - - string_free(out); - return sent < 0 ? (int32_t)sent : SOCK_OK; - } - - int32_t close() { - int32_t r = sock ? SOCK_OK : SOCK_ERR_STATE; - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_HTTP_SERVER; - ev.action = NETLOG_ACT_CLOSE; - ev.pid = pid; - ev.i0 = r; - if (sock) { - ev.local_port = sock->get_local_port(); - ev.remote_ep = sock->get_remote_ep(); - } - netlog_socket_event(&log_opts, &ev); - - if (sock) sock->~TCPSocket(); - if (sock) free_sized(sock, sizeof(TCPSocket)); - sock = nullptr; - - if (tcp_extra) free_sized(tcp_extra, sizeof(SocketExtraOptions)); - tcp_extra = nullptr; - - log_opts.flags &= ~SOCK_OPT_DEBUG; - return r; - } -}; \ No newline at end of file diff --git a/kernel/networking/application_layer/ssdp.c b/kernel/networking/application_layer/ssdp.c index 96fa265b..0ef616a3 100644 --- a/kernel/networking/application_layer/ssdp.c +++ b/kernel/networking/application_layer/ssdp.c @@ -1,22 +1,28 @@ #include "ssdp.h" -#include "syscalls/syscalls.h" #include "networking/application_layer/http.h" -#include "std/std.h" +#include "std/string.h" +#include "std/memory.h" uint32_t ssdp_parse_mx_ms(const char* buf, int len){ - const char* p = memmem(buf, len, "\r\nmx:", 5); - if (!p) p = memmem(buf, len, "\nmx:", 4); - if (!p) return 1000; - - int i = (int)(p - buf); - while (i < len && buf[i] != ':') ++i; - if (i >= len) return 1000; - ++i; + if (!buf || len <= 0) return 1000; + int i = 0; + for (; i + 4 <= len; i++) { + if (buf[i] == '\n' && strncmp_case(buf + i + 1, "mx:", 1, 3) == 0) break; + } + if (i + 4 > len) return 1000; + i += 4; while (i < len && (buf[i] == ' ' || buf[i] == '\t')) ++i; + uint32_t start = (uint32_t)i; + while (i < len && is_digit(buf[i])) ++i; + if ((uint32_t)i == start) return 1000; + uint32_t mx_len = i - start; uint32_t v = 0; - parse_uint32_dec(buf, &v); + if (mx_len >= 16 || !parse_uint32_dec_len(buf + start, mx_len, &v)) return 1000; + while (i < len && (buf[i] == ' ' || buf[i] == '\t')) i++; + if (i < len && buf[i] != '\r' && buf[i] != '\n') return 1000; + if (i < len && buf[i] == '\r' && (i + 1 == len || buf[i + 1] != '\n')) return 1000; if (v == 0) v = 1; if (v > 5) v = 5; @@ -31,18 +37,19 @@ bool ssdp_is_msearch(const char* buf, int len) { } string ssdp_build_search_response(void) { - HTTPHeader extra[5]; - extra[0] = (HTTPHeader){ string_from_literal("CACHE-CONTROL"), string_from_literal("max-age=60")}; - extra[1] = (HTTPHeader){ string_from_literal("EXT"), string_from_literal("")}; - extra[2] = (HTTPHeader){ string_from_literal("SERVER"), string_from_literal("RedactedOS/1.0 UPnP/1.1")}; - extra[3] = (HTTPHeader){ string_from_literal("ST"), string_from_literal("ssdp:all")}; - extra[4] = (HTTPHeader){ string_from_literal("USN"), string_from_literal("uuid:redacted-os::upnp:rootdevice")}; + HTTPHeader extra[] = { + {{"CACHE-CONTROL", 13, 0}, {"max-age=60", 10, 0}}, + {{"EXT", 3, 0}, {"", 0, 0}}, + {{"SERVER", 6, 0}, {"RedactedOS/1.0 UPnP/1.1", 23, 0}}, + {{"ST", 2, 0}, {"ssdp:all", 8, 0}}, + {{"USN", 3, 0}, {"uuid:redacted-os::upnp:rootdevice", 33, 0}} + }; HTTPResponseMsg R = (HTTPResponseMsg){0}; R.status_code = HTTP_OK; - R.reason = string_from_literal("OK"); + R.reason = (string){"OK", 2, 0}; R.extra_headers = extra; - R.extra_header_count = 5; + R.extra_header_count = N_ARR(extra); return http_response_builder(&R); } @@ -51,19 +58,29 @@ string ssdp_build_notify(bool alive, bool v6) { const char* nts = alive ? "ssdp:alive" : "ssdp:byebye"; string out = string_from_literal("NOTIFY * HTTP/1.1\r\n"); - - HTTPHeader extra[6]; - extra[0] = (HTTPHeader){ string_from_literal("HOST"), string_from_literal(host)}; - extra[1] = (HTTPHeader){ string_from_literal("NT"), string_from_literal("upnp:rootdevice")}; - extra[2] = (HTTPHeader){ string_from_literal("NTS"), string_from_literal(nts)}; - extra[3] = (HTTPHeader){ string_from_literal("USN"), string_from_literal("uuid:redacted-os::upnp:rootdevice")}; - extra[4] = (HTTPHeader){ string_from_literal("CACHE-CONTROL"), string_from_literal("max-age=1800")}; - extra[5] = (HTTPHeader){ string_from_literal("SERVER"), string_from_literal("RedactedOS/1.0 UPnP/1.1")}; + if (!out.data) return out; + HTTPHeader extra[] = { + {{"HOST", 4, 0}, {(char*)host, (uint32_t)strlen(host), 0}}, + {{"NT", 2, 0}, {"upnp:rootdevice", 15, 0}}, + {{"NTS", 3, 0}, {(char*)nts, (uint32_t)strlen(nts), 0}}, + {{"USN", 3, 0}, {"uuid:redacted-os::upnp:rootdevice", 33, 0}}, + {{"CACHE-CONTROL", 13, 0}, {"max-age=1800", 12, 0}}, + {{"SERVER", 6, 0}, {"RedactedOS/1.0 UPnP/1.1", 23, 0}} + }; HTTPHeadersCommon c = (HTTPHeadersCommon){0}; - string hdrs = http_header_builder(&c, extra, 6); + string hdrs = http_header_builder(&c, extra, N_ARR(extra), HTTP_HEADER_BUILD_REQUEST, HTTP_METHOD_GET, 0); + if (!hdrs.data || hdrs.length > UINT32_MAX - out.length) { + string_free(out); + string_free(hdrs); + return (string){0}; + } + uint32_t total = out.length + hdrs.length; string_append_bytes(&out, hdrs.data, hdrs.length); string_free(hdrs); - string_append_bytes(&out, "\r\n", 2); + if (out.length != total) { + string_free(out); + return (string){0}; + } return out; } diff --git a/kernel/networking/application_layer/ssdp_daemon.c b/kernel/networking/application_layer/ssdp_daemon.c index 8771dcd5..37e79584 100644 --- a/kernel/networking/application_layer/ssdp_daemon.c +++ b/kernel/networking/application_layer/ssdp_daemon.c @@ -1,15 +1,17 @@ #include "ssdp_daemon.h" -#include "process/scheduler.h" #include "std/memory.h" #include "std/string.h" #include "syscalls/syscalls.h" #include "net/network_types.h" -#include "networking/transport_layer/csocket_udp.h" +#include "net/socket_types.h" +#include "networking/transport_layer/csocket.h" +#include "networking/transport_layer/trans_utils.h" #include "networking/application_layer/ssdp.h" +#include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "math/math.h" -#include "networking/transport_layer/trans_utils.h" +#include "networking/interface_manager.h" +#include "random/random.h" //at the moment it's a very basic version. it's a protocol still in use but only in few cases //it;s used in some printers, upnp, local video streaming and various other things @@ -17,30 +19,40 @@ typedef struct { uint8_t used; + socket_handle_t sock; uint32_t due_ms; net_l4_endpoint dst; } ssdp_pending_t; +typedef struct { + socket_handle_t sock; + ip_version_t ver; +} ssdp_socket_entry_t; + static rng_t ssdp_rng; static uint32_t ssdp_uptime_ms = 0; static uint32_t ssdp_host_v4 = IPV4_MCAST_SSDP; static uint8_t ssdp_host_v6[16]; +static ssdp_socket_entry_t ssdp_sockets[MAX_L3_INTERFACES]; +static uint16_t ssdp_socket_count = 0; #define SSDP_MAX_PENDING 64 #define SSDP_RATE_WINDOW_MS 1000 #define SSDP_RATE_MAX 20 #define SSDP_NOTIFY_INTERVAL_MS 300000 +#define SSDP_RECV_BURST 8 static ssdp_pending_t ssdp_pending[SSDP_MAX_PENDING]; static uint32_t ssdp_rate_window_ms = 0; static uint32_t ssdp_rate_count = 0; -static void ssdp_schedule_response(const net_l4_endpoint* src, uint32_t mx_ms) { - if (!src) return; +static void ssdp_schedule_response(socket_handle_t sock, const net_l4_endpoint* src, uint32_t mx_ms) { + if (!sock || !src) return; for (int i = 0; i < SSDP_MAX_PENDING; ++i) { if (!ssdp_pending[i].used) { ssdp_pending[i].used = 1; + ssdp_pending[i].sock = sock; ssdp_pending[i].dst = *src; ssdp_pending[i].due_ms = ssdp_uptime_ms + rng_between32(&ssdp_rng, 0, mx_ms); return; @@ -48,70 +60,103 @@ static void ssdp_schedule_response(const net_l4_endpoint* src, uint32_t mx_ms) { } } -static void ssdp_send_notify(socket_handle_t s4, socket_handle_t s6, bool alive) { - if (s4) { - string msg = ssdp_build_notify(alive, false); - net_l4_endpoint dst; - make_ep(ssdp_host_v4, 1900, IP_VER4, &dst); - (void)socket_sendto_udp_ex(s4, DST_ENDPOINT, &dst, 0, msg.data, msg.length); - string_free(msg); - } +static void ssdp_send_notify(bool alive) { + string msg[2] = {{0}}; + net_l4_endpoint dst[2]; + make_ep(&ssdp_host_v4, 1900, IP_VER4, &dst[0]); + make_ep(ssdp_host_v6, 1900, IP_VER6, &dst[1]); - if (s6) { - string msg = ssdp_build_notify(alive, true); - net_l4_endpoint dst = (net_l4_endpoint){0}; - dst.ver = IP_VER6; - memcpy(dst.ip, ssdp_host_v6, 16); - dst.port = 1900; - (void)socket_sendto_udp_ex(s6, DST_ENDPOINT, &dst, 0, msg.data, msg.length); - string_free(msg); + for (uint16_t i = 0; i < ssdp_socket_count; i++) { + socket_handle_t s = ssdp_sockets[i].sock; + if (!s) continue; + + bool v6 = ssdp_sockets[i].ver == IP_VER6; + if (!msg[v6].data) msg[v6] = ssdp_build_notify(alive, v6); + if (!msg[v6].data || !msg[v6].length) continue; + (void)send_to_socket(s, &dst[v6], msg[v6].data, msg[v6].length); } + string_free(msg[0]); + string_free(msg[1]); } int ssdp_daemon_entry(int argc, char* argv[]) { (void)argc; (void)argv; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&ssdp_rng, virt_timer); + rng_init_random(&ssdp_rng); ipv6_make_multicast(0x02, IPV6_MCAST_SSDP, NULL, ssdp_host_v6); - SocketExtraOptions opt4 = (SocketExtraOptions){0}; - opt4.flags = SOCK_OPT_MCAST_JOIN; - opt4.mcast_ver = IP_VER4; - memcpy(opt4.mcast_group, &ssdp_host_v4, 4); + uint8_t n_if = l2_interface_count(); + for (uint8_t i = 0; i < n_if && ssdp_socket_count < MAX_L3_INTERFACES; i++) { + l2_interface_t *l2 = l2_interface_at(i); + if (!l2 || !l2->is_up) continue; + + for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE && ssdp_socket_count < MAX_L3_INTERFACES; j++) { + l3_ipv4_interface_t *v4 = l2->l3_v4[j]; + if (!ipv4_l3_is_ready(v4) || v4->is_localhost) continue; + + socket_handle_t s = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_TTL | SOCK_OPT_NONBLOCK, .ttl = 2}); + if (!s) continue; + + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + spec.kind = BIND_L3; + spec.ver = IP_VER4; + spec.l3_id = v4->l3_id; + + net_l4_endpoint group; + memset(&group, 0, sizeof(group)); + group.ver = IP_VER4; + group.port = 1900; + memcpy(group.ip, &ssdp_host_v4, 4); + + if (bind_socket(s, &spec, 1900) != SOCK_OK || set_socket_option(s, SOCK_OPT_MCAST_JOIN, &group, sizeof(group)) != SOCK_OK) { + close_socket(s); + continue; + } - SocketExtraOptions opt6 = (SocketExtraOptions){0}; - opt6.flags = SOCK_OPT_MCAST_JOIN; - opt6.mcast_ver = IP_VER6; - memcpy(opt6.mcast_group, ssdp_host_v6, 16); + ssdp_sockets[ssdp_socket_count].sock = s; + ssdp_sockets[ssdp_socket_count].ver = IP_VER4; + ssdp_socket_count++; + } - uint16_t pid = get_current_proc_pid(); + for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE && ssdp_socket_count < MAX_L3_INTERFACES; j++) { + l3_ipv6_interface_t *v6 = l2->l3_v6[j]; + if (!ipv6_l3_is_ready(v6) || v6->is_localhost) continue; - socket_handle_t s4 = udp_socket_create(SOCK_ROLE_SERVER, pid, &opt4); - socket_handle_t s6 = udp_socket_create(SOCK_ROLE_SERVER, pid, &opt6); + socket_handle_t s = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_TTL | SOCK_OPT_NONBLOCK, .ttl = 2}); + if (!s) continue; - struct SockBindSpec spec = (struct SockBindSpec){0}; - spec.kind = BIND_ANY; - if (s4 && socket_bind_udp_ex(s4, &spec, 1900) < 0) { - socket_close_udp(s4); - socket_destroy_udp(s4); - s4 = 0; - } - if (s6 && socket_bind_udp_ex(s6, &spec, 1900) < 0) { - socket_close_udp(s6); - socket_destroy_udp(s6); - s6 = 0; + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + spec.kind = BIND_L3; + spec.ver = IP_VER6; + spec.l3_id = v6->l3_id; + + net_l4_endpoint group; + memset(&group, 0, sizeof(group)); + group.ver = IP_VER6; + group.port = 1900; + ipv6_cpy(group.ip, ssdp_host_v6); + + if (bind_socket(s, &spec, 1900) != SOCK_OK || set_socket_option(s, SOCK_OPT_MCAST_JOIN, &group, sizeof(group)) != SOCK_OK) { + close_socket(s); + continue; + } + + ssdp_sockets[ssdp_socket_count].sock = s; + ssdp_sockets[ssdp_socket_count].ver = IP_VER6; + ssdp_socket_count++; + } } - if (!s4 && !s6) return 1; + if (!ssdp_socket_count) return 1; - ssdp_send_notify(s4, s6, true); + ssdp_send_notify(true); msleep(100); - ssdp_send_notify(s4, s6, true); + ssdp_send_notify(true); msleep(100); - ssdp_send_notify(s4, s6, true); + ssdp_send_notify(true); uint32_t notify_ms = 0; const uint32_t tick_ms = 50; @@ -120,25 +165,21 @@ int ssdp_daemon_entry(int argc, char* argv[]) { notify_ms += tick_ms; if (notify_ms >= SSDP_NOTIFY_INTERVAL_MS) { notify_ms = 0; - ssdp_send_notify(s4, s6, true); + ssdp_send_notify(true); } char buf[2048]; net_l4_endpoint src = (net_l4_endpoint){0}; - if (s4) { - int64_t r4 = socket_recvfrom_udp_ex(s4, buf, sizeof(buf) - 1, &src); - if (r4 > 0) { - buf[r4] = 0; - if (ssdp_is_msearch(buf, (int)r4)) ssdp_schedule_response(&src, ssdp_parse_mx_ms(buf, (int)r4)); - } - } - - if (s6) { - int64_t r6 = socket_recvfrom_udp_ex(s6, buf, sizeof(buf) - 1, &src); - if (r6 > 0) { - buf[r6] = 0; - if (ssdp_is_msearch(buf, (int)r6)) ssdp_schedule_response(&src, ssdp_parse_mx_ms(buf, (int)r6)); + for (uint16_t sidx = 0; sidx < ssdp_socket_count; sidx++) { + socket_handle_t s = ssdp_sockets[sidx].sock; + for (int i = 0; i < SSDP_RECV_BURST; ++i) { + int64_t r = receive_from_socket(s, buf, sizeof(buf) - 1, &src); + if (r == SOCK_ERR_WOULDBLOCK) break; + if (r < 0) break; + if (!r) continue; + buf[r] = 0; + if (ssdp_is_msearch(buf, (int)r)) ssdp_schedule_response(s, &src, ssdp_parse_mx_ms(buf, (int)r)); } } @@ -158,8 +199,7 @@ int ssdp_daemon_entry(int argc, char* argv[]) { ssdp_pending[i].used = 0; string resp = ssdp_build_search_response(); - socket_handle_t sock = (ssdp_pending[i].dst.ver == IP_VER6) ? s6 : s4; - if (sock) (void)socket_sendto_udp_ex(sock, DST_ENDPOINT, &ssdp_pending[i].dst, 0, resp.data, resp.length); + if (ssdp_pending[i].sock) (void)send_to_socket(ssdp_pending[i].sock, &ssdp_pending[i].dst, (void*)resp.data, resp.length); string_free(resp); break; } diff --git a/kernel/networking/drivers/loopback/loopback_driver.cpp b/kernel/networking/drivers/loopback/loopback_driver.cpp index 7314e651..0495d402 100644 --- a/kernel/networking/drivers/loopback/loopback_driver.cpp +++ b/kernel/networking/drivers/loopback/loopback_driver.cpp @@ -1,12 +1,10 @@ #include "loopback_driver.hpp" #include "std/memory.h" -#include "memory/page_allocator.h" LoopbackDriver::LoopbackDriver(){ - memory_page = 0; rx_head = 0; rx_tail = 0; - verbose = false; + memset(rxq, 0, sizeof(rxq)); hw_name[0]='l'; hw_name[1]='o'; hw_name[2]='o'; hw_name[3]='p'; hw_name[4]='b'; hw_name[5]='a'; hw_name[6]='c'; hw_name[7]='k'; hw_name[8]=0; } @@ -15,38 +13,26 @@ LoopbackDriver::~LoopbackDriver(){} bool LoopbackDriver::init_at(uint64_t pci_addr, uint32_t irq_base_vector){ (void)pci_addr; (void)irq_base_vector; - if (!memory_page) { - memory_page = palloc(PAGE_SIZE, MEM_PRIV_KERNEL, MEM_RW, true); - if (!memory_page) return false; - } return true; } -sizedptr LoopbackDriver::allocate_packet(size_t size){ - if (!size) return (sizedptr){0,0}; - if (!memory_page && !init_at(0, 0)) return (sizedptr){0,0}; - void* p = kalloc(memory_page, size, ALIGN_16B, MEM_PRIV_KERNEL); - return (sizedptr){(uintptr_t)p, (uint32_t)size}; -} - -sizedptr LoopbackDriver::handle_receive_packet(){ - if (rx_head == rx_tail) return (sizedptr){0,0}; - sizedptr p = rxq[rx_head]; +netpkt_t* LoopbackDriver::handle_receive_packet(){ + if (rx_head == rx_tail) return nullptr; + netpkt_t* p = rxq[rx_head]; + rxq[rx_head] = nullptr; rx_head = (uint16_t)((rx_head + 1) & 255); return p; } -void LoopbackDriver::handle_sent_packet(){} +void LoopbackDriver::enable_verbose(){} -void LoopbackDriver::enable_verbose(){ verbose = true; } - -bool LoopbackDriver::send_packet(sizedptr packet){ - if (!packet.ptr || !packet.size) return false; +netdev_tx_result_t LoopbackDriver::send_packet(netpkt_t* packet){ + if (!packet || !netpkt_len(packet)) return NETDEV_TX_DROP; uint16_t next = (uint16_t)((rx_tail + 1) & 255); - if (next == rx_head)return false; + if (next == rx_head)return NETDEV_TX_BUSY; rxq[rx_tail] = packet; rx_tail = next; - return true; + return NETDEV_TX_OK; } void LoopbackDriver::get_mac(uint8_t out_mac[6]) const{ diff --git a/kernel/networking/drivers/loopback/loopback_driver.hpp b/kernel/networking/drivers/loopback/loopback_driver.hpp index c110301b..5e7076a8 100644 --- a/kernel/networking/drivers/loopback/loopback_driver.hpp +++ b/kernel/networking/drivers/loopback/loopback_driver.hpp @@ -9,11 +9,9 @@ class LoopbackDriver : public NetDriver { ~LoopbackDriver() override; bool init_at(uint64_t pci_addr, uint32_t irq_base_vector) override; - sizedptr allocate_packet(size_t size) override; - sizedptr handle_receive_packet() override; - void handle_sent_packet() override; + netpkt_t* handle_receive_packet() override; void enable_verbose() override; - bool send_packet(sizedptr packet) override; + netdev_tx_result_t send_packet(netpkt_t* packet) override; void get_mac(uint8_t out_mac[6]) const override; uint16_t get_mtu() const override; uint16_t get_header_size() const override; @@ -22,10 +20,8 @@ class LoopbackDriver : public NetDriver { uint8_t get_duplex() const override; private: - void* memory_page; - sizedptr rxq[256]; + netpkt_t* rxq[256]; uint16_t rx_head; uint16_t rx_tail; - bool verbose; char hw_name[16]; }; \ No newline at end of file diff --git a/kernel/networking/drivers/net_bus.cpp b/kernel/networking/drivers/net_bus.cpp index 511513fb..18273ff8 100644 --- a/kernel/networking/drivers/net_bus.cpp +++ b/kernel/networking/drivers/net_bus.cpp @@ -17,7 +17,7 @@ typedef struct { uint8_t mac[6]; uint16_t mtu; uint16_t header_size; - uint8_t kind; + NetDevKind kind; uint32_t speed_mbps; uint8_t duplex; } net_nic_desc_t; @@ -49,18 +49,14 @@ static void add_loopback(){ if (g_lo_added) return; if (g_count >= MAX_L2_INTERFACES) return; net_nic_desc_t* d = &g_nics[g_count++]; - d->drv = nullptr; - - memset(d->ifname, 0, sizeof(d->ifname)); - memset(d->hw_ifname, 0, sizeof(d->hw_ifname)); - memset(d->mac, 0, sizeof(d->mac)); + memset(d, 0, sizeof(*d)); strncpy(d->ifname, "lo0", sizeof(d->ifname)); strncpy(d->hw_ifname, "loopback", sizeof(d->hw_ifname)); d->mtu = 65535; d->header_size = 0; - d->kind = NET_IFK_LOCALHOST; + d->kind = NET_DEV_LOOPBACK; d->speed_mbps = 0xFFFFFFFFu; d->duplex = LINK_DUPLEX_UNKNOWN; g_lo_added = true; @@ -107,10 +103,10 @@ int net_bus_init(){ (unsigned)i, ven, dev, cls, sub, infos[i].prog_if, (uintptr_t)infos[i].addr); const char* if_prefix = "net"; - uint8_t kind = NET_IFK_OTHER; + NetDevKind kind = NET_DEV_OTHER; if (sub == 0x00) { if_prefix = "eth"; - kind = NET_IFK_ETH; + kind = NET_DEV_ETH; } bool matched = false; @@ -209,8 +205,8 @@ uint16_t net_bus_get_header_size(int idx){ return g_nics[idx].header_size; } -uint8_t net_bus_get_kind(int idx){ - if (idx < 0 || (size_t)idx >= g_count) return NET_IFK_UNKNOWN; +NetDevKind net_bus_get_kind(int idx){ + if (idx < 0 || (size_t)idx >= g_count) return NET_DEV_UNKNOWN; return g_nics[idx].kind; } diff --git a/kernel/networking/drivers/net_bus.hpp b/kernel/networking/drivers/net_bus.hpp index 32f45502..72ed5c44 100644 --- a/kernel/networking/drivers/net_bus.hpp +++ b/kernel/networking/drivers/net_bus.hpp @@ -11,6 +11,6 @@ const char* net_bus_hw_ifname(int idx); void net_bus_get_mac(int idx, uint8_t out_mac[6]); uint16_t net_bus_get_mtu(int idx); uint16_t net_bus_get_header_size(int idx); -uint8_t net_bus_get_kind(int idx); +NetDevKind net_bus_get_kind(int idx); uint32_t net_bus_get_speed_mbps(int idx); uint8_t net_bus_get_duplex(int idx); \ No newline at end of file diff --git a/kernel/networking/drivers/net_driver.hpp b/kernel/networking/drivers/net_driver.hpp index 2d083da1..1a206d41 100644 --- a/kernel/networking/drivers/net_driver.hpp +++ b/kernel/networking/drivers/net_driver.hpp @@ -1,17 +1,25 @@ #pragma once #include "types.h" #include "net/network_types.h" +#include "networking/netpkt.h" + +typedef enum { + NETDEV_TX_OK = 0, + NETDEV_TX_BUSY = 1, + NETDEV_TX_DROP = 2 +} netdev_tx_result_t; class NetDriver { public: NetDriver() = default; virtual ~NetDriver() = default; virtual bool init_at(uint64_t pci_addr, uint32_t irq_base_vector) = 0; - virtual sizedptr allocate_packet(size_t size) = 0; - virtual sizedptr handle_receive_packet() = 0; - virtual void handle_sent_packet() = 0; + virtual netpkt_t* handle_receive_packet() = 0; + virtual void handle_sent_packet() {} + virtual void complete_rx_batch(){} + virtual void complete_tx_batch(){} virtual void enable_verbose() = 0; - virtual bool send_packet(sizedptr packet) = 0; + virtual netdev_tx_result_t send_packet(netpkt_t* packet) = 0; virtual void get_mac(uint8_t out_mac[6]) const = 0; virtual uint16_t get_mtu() const = 0; virtual uint16_t get_header_size() const = 0; diff --git a/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.cpp b/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.cpp index 24b2cee9..01948387 100644 --- a/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.cpp +++ b/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.cpp @@ -1,10 +1,10 @@ #include "virtio_net_pci.hpp" #include "console/kio.h" #include "pci.h" -#include "syscalls/syscalls.h" #include "memory/page_allocator.h" #include "std/memory.h" #include "networking/network.h" +#include "networking/link_layer/eth.h" #include "sysregs.h" #include "exceptions/irq.h" @@ -12,10 +12,7 @@ #define TRANSMIT_QUEUE 1 #define CONTROL_QUEUE 2 -constexpr uint32_t RX_BUF_SIZE = PAGE_SIZE; -constexpr uint16_t RX_CHAIN_SEGS = 4; - -void* g_rx_pool = nullptr; +constexpr uint32_t RX_BUF_SIZE = 2048; #define kprintfv(fmt, ...) \ ({ \ @@ -42,9 +39,28 @@ typedef struct __attribute__((packed)) virtio_net_ctrl_ack_t { #define VIRTIO_NET_CTRL_MAC_TABLE_SET 0 +void virtio_net_rx_free(void* ctx, uintptr_t base) { + VirtioNetDriver* driver = (VirtioNetDriver*)ctx; + if (!driver || !driver->rx_qsz || !driver->rx_avail || !driver->rx_pool) return; + if (base < (uintptr_t)driver->rx_pool) return; + + uintptr_t off = base - (uintptr_t)driver->rx_pool; + if (off % RX_BUF_SIZE) return; + + uint32_t desc_index = (uint32_t)(off/RX_BUF_SIZE); + if (desc_index >= driver->rx_qsz) return; + + irq_flags_t flags = irq_save_disable(); + uint16_t aidx = driver->rx_avail->idx; + driver->rx_avail->ring[aidx % driver->rx_qsz] = (uint16_t)desc_index; + asm volatile ("dmb ishst" ::: "memory"); + driver->rx_avail->idx = (uint16_t)(aidx + 1); + driver->rx_notify_pending = true; + irq_restore(flags); +} bool virtio_net_ctrl_send(virtio_device* dev, uint8_t cls, uint8_t cmd, const void* payload, uint32_t payload_len) { - if (!dev) return false; + if (!dev || (payload_len && !payload)) return false; virtio_net_ctrl_hdr_t hdr; hdr.cls= cls; @@ -84,7 +100,6 @@ VirtioNetDriver::~VirtioNetDriver(){} bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { verbose = false; - mrg_rxbuf = false; ctrl_vq = false; ctrl_rx = false; header_size = sizeof(virtio_net_hdr_t); @@ -93,10 +108,20 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { duplex = LINK_DUPLEX_UNKNOWN; last_used_receive_idx = 0; last_used_sent_idx = 0; + rx_used_batch_end = 0; rx_desc = nullptr; rx_avail = nullptr; rx_used = nullptr; rx_qsz = 0; + rx_pool = nullptr; + rx_notify_pending = false; + tx_desc = nullptr; + tx_avail = nullptr; + tx_used = nullptr; + tx_qsz = 0; + tx_avail_shadow_idx = 0; + tx_pending = nullptr; + tx_free_head = UINT16_MAX; memset(&vnp_net_dev, 0, sizeof(vnp_net_dev)); kprintfv("[virtio-net] probing pci_addr=%x",(uintptr_t)addr); @@ -128,9 +153,11 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { net_feature_mask |= (1ULL << VIRTIO_NET_F_MAC); net_feature_mask |= (1ULL << VIRTIO_NET_F_STATUS); net_feature_mask |= (1ULL << VIRTIO_NET_F_MTU); - net_feature_mask |= (1ULL << VIRTIO_NET_F_MRG_RXBUF); net_feature_mask |= (1ULL << VIRTIO_NET_F_CTRL_VQ); net_feature_mask |= (1ULL << VIRTIO_NET_F_CTRL_RX); + net_feature_mask |= (1ULL << VIRTIO_NET_F_CTRL_RX_EXTRA); + net_feature_mask |= (1ULL << VIRTIO_NET_F_SPEED_DUPLEX); + //TODO evaluate MRG_RXBUF CSUM TSO GSO GRO USO virtio_set_feature_mask(net_feature_mask); if (!virtio_init_device(&vnp_net_dev)){ @@ -139,8 +166,12 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { } kprintfv("[virtio-net] common_cfg=%x device_cfg=%x", (uintptr_t)vnp_net_dev.common_cfg,(uintptr_t)vnp_net_dev.device_cfg); - mrg_rxbuf = (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_MRG_RXBUF)) != 0; - header_size = mrg_rxbuf ? sizeof(virtio_net_hdr_mrg_rxbuf_t) : sizeof(virtio_net_hdr_t); + if (!(vnp_net_dev.negotiated_features & (1ULL << VIRTIO_F_VERSION_1))) { + kprintf("[virtio-net][err] device did not accept VIRTIO_F_VERSION_1"); + vnp_net_dev.common_cfg->device_status |= VIRTIO_STATUS_FAILED; + return false; + } + header_size = sizeof(virtio_net_hdr_t); ctrl_vq = (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_CTRL_VQ)) != 0; ctrl_rx = (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_CTRL_RX)) != 0; @@ -160,37 +191,24 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { if (!rx_qsz || !rx_desc || !rx_avail || !rx_used) return false; kprintfv("[virtio-net] RX qsz=%u",rx_qsz); - if (!g_rx_pool){ - g_rx_pool = palloc((uint64_t)rx_qsz * RX_BUF_SIZE, MEM_PRIV_KERNEL, MEM_RW, true); - kprintfv("[virtio-net] rx_pool=%x",(uintptr_t)g_rx_pool); - if (!g_rx_pool) return false; - } - - uint16_t chain_count = (uint16_t)(rx_qsz / RX_CHAIN_SEGS); - if (!chain_count) return false; + rx_pool = palloc((uint64_t)rx_qsz * RX_BUF_SIZE, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + kprintfv("[virtio-net] rx_pool=%x", (uintptr_t)rx_pool); + if (!rx_pool) return false; - memset((void*)rx_desc, 0, 16ULL * rx_qsz); - rx_avail->flags = 0; + //re enable used buffer notifications when network irqs are used + rx_avail->flags = VIRTQ_AVAIL_F_NO_INTERRUPT; rx_avail->idx = 0; rx_used->flags = 0; rx_used->idx = 0; - for (uint16_t c = 0; c < chain_count; c++) { - uint16_t head = (uint16_t)(c * RX_CHAIN_SEGS); - - for (uint16_t s = 0; s < RX_CHAIN_SEGS; s++){ - uint16_t di = (uint16_t)(head + s); - void* buf = (void*)((uintptr_t)g_rx_pool + (uintptr_t)di * (uintptr_t)RX_BUF_SIZE); + for (uint16_t di = 0; di < rx_qsz; di++) { + void* buf = (void*)((uintptr_t)rx_pool + (uintptr_t)di * (uintptr_t)RX_BUF_SIZE); + rx_desc[di].addr = VIRT_TO_PHYS((uintptr_t)buf); + rx_desc[di].len = RX_BUF_SIZE; + rx_desc[di].flags = VIRTQ_DESC_F_WRITE; + rx_desc[di].next = 0; - rx_desc[di].addr = VIRT_TO_PHYS((uintptr_t)buf); - rx_desc[di].len = RX_BUF_SIZE; - rx_desc[di].flags = (uint16_t)(VIRTQ_DESC_F_WRITE | ((s + 1 < RX_CHAIN_SEGS) ? VIRTQ_DESC_F_NEXT : 0)); - rx_desc[di].next = (uint16_t)(di + 1); - } - - rx_desc[head + (RX_CHAIN_SEGS - 1)].next = 0; - - rx_avail->ring[rx_avail->idx % rx_qsz] = head; + rx_avail->ring[rx_avail->idx % rx_qsz] = di; rx_avail->idx++; } @@ -200,11 +218,29 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { vnp_net_dev.common_cfg->queue_msix_vector = 0; kprintfv("[virtio-net] RX vector=%u",vnp_net_dev.common_cfg->queue_msix_vector); if (vnp_net_dev.common_cfg->queue_msix_vector != 0) return false; - virtio_notify(&vnp_net_dev); if (TRANSMIT_QUEUE >= vnp_net_dev.num_queues) return false; if (!vnp_net_dev.queues[TRANSMIT_QUEUE].valid) return false; + tx_qsz = vnp_net_dev.queues[TRANSMIT_QUEUE].size; + tx_desc = vnp_net_dev.queues[TRANSMIT_QUEUE].desc; + tx_avail = vnp_net_dev.queues[TRANSMIT_QUEUE].driver; + tx_used = vnp_net_dev.queues[TRANSMIT_QUEUE].device; + if (!tx_qsz || !tx_desc || !tx_avail || !tx_used) return false; + + tx_pending = (netpkt_t**)palloc((uint64_t)tx_qsz * sizeof(netpkt_t*), MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + if (!tx_pending) return false; + + //re enable used buffer notifications when network irqs are used + tx_avail->flags = VIRTQ_AVAIL_F_NO_INTERRUPT; + tx_avail->idx = 0; + tx_used->flags = 0; + tx_used->idx = 0; + last_used_sent_idx = 0; + tx_avail_shadow_idx = 0; + tx_free_head = 0; + for (uint16_t i = 0; i < tx_qsz; i++) tx_desc[i].next = (i + 1 < tx_qsz) ? (i + 1) : UINT16_MAX; + select_queue(&vnp_net_dev, TRANSMIT_QUEUE); vnp_net_dev.common_cfg->queue_msix_vector = 1; kprintfv("[virtio-net] TX vector=%u",vnp_net_dev.common_cfg->queue_msix_vector); @@ -215,7 +251,6 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { vnp_net_dev.common_cfg->queue_msix_vector = 0xFFFF; } - if (ctrl_vq && ctrl_rx) (void)sync_multicast((const uint8_t*)0, 0); kprintfv("[virtio-net] negotiated ctrl_vq=%u ctrl_rx=%u", (unsigned)ctrl_vq, (unsigned)ctrl_rx); volatile virtio_net_config* cfg = (volatile virtio_net_config*)vnp_net_dev.device_cfg; @@ -224,15 +259,21 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { get_mac(mac); - uint16_t dev_mtu = cfg->mtu; - if (dev_mtu != 0 && dev_mtu != 0xFFFF && dev_mtu >= 576) mtu = dev_mtu; else mtu = 1500; + if (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_MTU)) { + uint16_t dev_mtu = cfg->mtu; + if (dev_mtu != 0 && dev_mtu != 0xFFFF && dev_mtu >= 576) mtu = dev_mtu; + } + uint16_t rx_mtu_cap = (uint16_t)(RX_BUF_SIZE - header_size - sizeof(eth_hdr_t) - ETH_VLAN_TAG_LEN); + if (mtu > rx_mtu_cap) mtu = rx_mtu_cap; - speed_mbps = cfg->speed; - switch (cfg->duplex) { - case 0: duplex = LINK_DUPLEX_HALF; break; - case 1: duplex = LINK_DUPLEX_FULL; break; - default: duplex = LINK_DUPLEX_UNKNOWN; break; + if (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_SPEED_DUPLEX)) { + speed_mbps = cfg->speed; + switch (cfg->duplex) { + case 0: duplex = LINK_DUPLEX_HALF; break; + case 1: duplex = LINK_DUPLEX_FULL; break; + default: duplex = LINK_DUPLEX_UNKNOWN; break; + } } hw_name[0] = 'v'; hw_name[1] = 'i'; hw_name[2] = 'r'; hw_name[3] = 't'; hw_name[4] = 'i'; hw_name[5] = 'o'; hw_name[6] = 0; @@ -249,7 +290,10 @@ bool VirtioNetDriver::init_at(uint64_t addr, uint32_t irq_base_vector) { } vnp_net_dev.common_cfg->device_status |= VIRTIO_STATUS_DRIVER_OK; - select_queue(&vnp_net_dev, RECEIVE_QUEUE); + asm volatile ("dsb sy" ::: "memory"); + + virtio_notify_queue(&vnp_net_dev, RECEIVE_QUEUE); + if (ctrl_vq && ctrl_rx) (void)sync_multicast((const uint8_t*)0, 0); return true; } @@ -282,150 +326,124 @@ uint8_t VirtioNetDriver::get_duplex() const { } } -sizedptr VirtioNetDriver::allocate_packet(size_t size){ - size_t total = size + (size_t)header_size; - return (sizedptr){(uintptr_t)kalloc(vnp_net_dev.memory_page, total, ALIGN_64B, MEM_PRIV_KERNEL), total}; -} +netpkt_t* VirtioNetDriver::handle_receive_packet(){ + if (!rx_qsz || !rx_desc || !rx_used || !rx_avail || !rx_pool) return nullptr; -sizedptr VirtioNetDriver::handle_receive_packet(){ - uint32_t desc_index = 0; - uint32_t total_len = 0; - uint16_t num_buffers = 1; + for (uint16_t attempt = 0; attempt < rx_qsz; attempt++) { + if (last_used_receive_idx == rx_used_batch_end) { + rx_used_batch_end = rx_used->idx; + asm volatile ("dmb ishld" ::: "memory"); + if (last_used_receive_idx == rx_used_batch_end) return nullptr; + } - disable_interrupt(); - select_queue(&vnp_net_dev, RECEIVE_QUEUE); - volatile virtq_used* used = rx_used; - volatile virtq_desc* desc = rx_desc; - volatile virtq_avail* avail = rx_avail; - uint16_t qsz = rx_qsz; - if (!qsz || !used || !desc || !avail) { - enable_interrupt(); - return (sizedptr){0,0}; + uint16_t used_ring_index = (uint16_t)(last_used_receive_idx % rx_qsz); + volatile virtq_used_elem* e = &rx_used->ring[used_ring_index]; + uint32_t desc_index = e->id; + uint32_t total_len = e->len; + last_used_receive_idx++; + + if (desc_index >= rx_qsz) continue; + uintptr_t buf = (uintptr_t)PHYS_TO_VIRT_P((void*)rx_desc[desc_index].addr); + if (total_len <= (uint32_t)header_size || total_len > rx_desc[desc_index].len) { + virtio_net_rx_free(this, buf); + continue; + } + + uint32_t payload_len = total_len - (uint32_t)header_size; + netpkt_t* pkt = netpkt_wrap(buf, RX_BUF_SIZE, header_size, payload_len, virtio_net_rx_free, this); + if (!pkt) { + virtio_net_rx_free(this, buf); + return nullptr; + } + + return pkt; } + return nullptr; +} + +void VirtioNetDriver::complete_rx_batch() { + irq_flags_t flags = irq_save_disable(); + bool pending = rx_notify_pending; + rx_notify_pending = false; + irq_restore(flags); + if (!pending) return; + asm volatile ("dmb ishst" ::: "memory"); + virtio_notify_queue(&vnp_net_dev, RECEIVE_QUEUE); +} + +void VirtioNetDriver::handle_sent_packet(){ + if (TRANSMIT_QUEUE >= vnp_net_dev.num_queues) return; + if (!tx_qsz || !tx_desc || !tx_used || !tx_pending) return; + + uint16_t used_end = tx_used->idx; asm volatile ("dmb ishld" ::: "memory"); - uint16_t new_idx = used->idx; - if (new_idx == last_used_receive_idx) { - enable_interrupt(); - return (sizedptr){0,0}; - } + while (last_used_sent_idx != used_end) { + uint16_t ring_index = (uint16_t)(last_used_sent_idx % tx_qsz); + uint32_t used_id = tx_used->ring[ring_index].id; + last_used_sent_idx++; - uint16_t used_ring_index = (uint16_t)(last_used_receive_idx % qsz); - volatile virtq_used_elem* e = &used->ring[used_ring_index]; - last_used_receive_idx++; - desc_index = e->id; - total_len = e->len; - - if (desc_index >= qsz || total_len <= (uint32_t)header_size){ - uint16_t aidx = avail->idx; - avail->ring[aidx % qsz] = (uint16_t)(desc_index % qsz); - asm volatile ("dmb ishst" ::: "memory"); - avail->idx = (uint16_t)(aidx + 1); - asm volatile ("dmb ishst" ::: "memory"); - select_queue(&vnp_net_dev, RECEIVE_QUEUE); - virtio_notify(&vnp_net_dev); - enable_interrupt(); - return (sizedptr){0,0}; - } + if (used_id >= tx_qsz) continue; + uint16_t desc_index = (uint16_t)used_id; - volatile uint8_t* first_buf = (volatile uint8_t*)PHYS_TO_VIRT_P((void*)(uintptr_t)desc[desc_index].addr); - if (mrg_rxbuf) { - virtio_net_hdr_mrg_rxbuf_t* h = (virtio_net_hdr_mrg_rxbuf_t*)(uintptr_t)first_buf; - num_buffers = h->num_buffers; - if (num_buffers == 0) num_buffers = 1; - if (num_buffers > RX_CHAIN_SEGS) { - uint16_t aidx = avail->idx; - avail->ring[aidx % qsz] = (uint16_t)desc_index; - asm volatile ("dmb ishst" ::: "memory"); - avail->idx = (uint16_t)(aidx + 1); - asm volatile ("dmb ishst" ::: "memory"); - select_queue(&vnp_net_dev, RECEIVE_QUEUE); - virtio_notify(&vnp_net_dev); - enable_interrupt(); - return (sizedptr){0,0}; - } - } + netpkt_t* packet = tx_pending[desc_index]; + if (!packet) continue; + tx_pending[desc_index] = nullptr; + tx_desc[desc_index].addr = 0; + tx_desc[desc_index].len = 0; + tx_desc[desc_index].flags = 0; + tx_desc[desc_index].next = tx_free_head; + tx_free_head = desc_index; - enable_interrupt(); - - uint32_t payload_len = total_len - (uint32_t)header_size; - void* out_buf = kalloc(vnp_net_dev.memory_page, payload_len, ALIGN_64B, MEM_PRIV_KERNEL); - if (!out_buf){ - disable_interrupt(); - uint16_t aidx = avail->idx; - avail->ring[aidx % qsz] = (uint16_t)desc_index; - asm volatile ("dmb ishst" ::: "memory"); - avail->idx = (uint16_t)(aidx + 1); - asm volatile ("dmb ishst" ::: "memory"); - select_queue(&vnp_net_dev, RECEIVE_QUEUE); - virtio_notify(&vnp_net_dev); - enable_interrupt(); - return (sizedptr){0,0}; + netpkt_unref(packet); } +} - uint32_t written = 0; - uint32_t remaining = payload_len; - uint16_t di = (uint16_t)desc_index; - for (uint16_t bi = 0; bi < num_buffers && remaining; bi++) { - volatile uint8_t* buf = (volatile uint8_t*)PHYS_TO_VIRT_P((void*)(uintptr_t)desc[di].addr); - uint32_t cap = desc[di].len; - uint32_t off = (bi == 0) ? (uint32_t)header_size : 0; - if (cap <= off) break; - - uint32_t chunk = cap - off; - if (chunk > remaining) chunk = remaining; - memcpy((uint8_t*)out_buf + written, (const void*)((uintptr_t)buf + off), chunk); - written += chunk; - remaining -= chunk; - - if (bi + 1 < num_buffers) { - if (!(desc[di].flags & VIRTQ_DESC_F_NEXT)) break; - di = desc[di].next; - } - } +netdev_tx_result_t VirtioNetDriver::send_packet(netpkt_t* packet){ + if (!packet || !netpkt_len(packet)) return NETDEV_TX_DROP; + if (!tx_qsz || !tx_desc || !tx_avail || !tx_used || !tx_pending) return NETDEV_TX_DROP; - disable_interrupt(); - uint16_t aidx = avail->idx; - avail->ring[aidx % qsz] = (uint16_t)desc_index; - asm volatile ("dmb ishst" ::: "memory"); - avail->idx = (uint16_t)(aidx + 1); - asm volatile ("dmb ishst" ::: "memory"); - select_queue(&vnp_net_dev, RECEIVE_QUEUE); - virtio_notify(&vnp_net_dev); - enable_interrupt(); + if (tx_free_head == UINT16_MAX) { + kprintfv("[virtio-net] tx queue full len=%u", (unsigned)netpkt_len(packet)); + return NETDEV_TX_BUSY; + } - if (remaining != 0) { - kfree(out_buf, payload_len); - return (sizedptr){0,0}; + uint16_t desc_index = tx_free_head; + tx_free_head = tx_desc[desc_index].next; + if (netpkt_headroom(packet) < header_size) { + tx_desc[desc_index].next = tx_free_head; + tx_free_head = desc_index; + return NETDEV_TX_DROP; } - return (sizedptr){ (uintptr_t)out_buf, payload_len }; -} + void* hdr = netpkt_push(packet, header_size); + if (!hdr) { + tx_desc[desc_index].next = tx_free_head; + tx_free_head = desc_index; + return NETDEV_TX_DROP; + } + memset(hdr, 0, (size_t)header_size); -void VirtioNetDriver::handle_sent_packet(){ - if (TRANSMIT_QUEUE >= vnp_net_dev.num_queues) return; - if (!vnp_net_dev.queues[TRANSMIT_QUEUE].device) return; - last_used_sent_idx = vnp_net_dev.queues[TRANSMIT_QUEUE].device->idx; -} + tx_pending[desc_index] = packet; + tx_desc[desc_index].addr = VIRT_TO_PHYS(netpkt_data(packet)); + tx_desc[desc_index].len = netpkt_len(packet); + tx_desc[desc_index].flags = 0; + tx_desc[desc_index].next = 0; -bool VirtioNetDriver::send_packet(sizedptr packet){ - if (!packet.ptr || !packet.size) return false; + uint16_t avail_idx = tx_avail_shadow_idx; + tx_avail->ring[avail_idx % tx_qsz] = desc_index; + tx_avail_shadow_idx = (uint16_t)(avail_idx + 1); - disable_interrupt(); - select_queue(&vnp_net_dev, TRANSMIT_QUEUE); + kprintfv("[virtio-net] tx queued desc=%u len=%u", desc_index,(unsigned)netpkt_len(packet)); + return NETDEV_TX_OK; +} - if ((size_t)header_size <= packet.size) memset((void*)packet.ptr, 0, (size_t)header_size); - if (mrg_rxbuf) ((virtio_net_hdr_mrg_rxbuf_t*)packet.ptr)->num_buffers = 0; - virtio_buf b; - b.addr = packet.ptr; - b.len = (uint32_t)packet.size; - b.flags = 0; - bool ok = virtio_send_nd(&vnp_net_dev, &b, 1); - enable_interrupt(); - - kprintfv("[virtio-net] tx queued len=%u",(unsigned)packet.size); - if (ok) kfree((void*)packet.ptr, packet.size); - return ok; +void VirtioNetDriver::complete_tx_batch() { + if (tx_avail->idx == tx_avail_shadow_idx) return; + asm volatile ("dmb ishst" ::: "memory"); + tx_avail->idx = tx_avail_shadow_idx; + asm volatile ("dmb ishst" ::: "memory"); + virtio_notify_queue(&vnp_net_dev, TRANSMIT_QUEUE); } bool VirtioNetDriver::sync_multicast(const uint8_t* macs, uint32_t count) { @@ -433,8 +451,6 @@ bool VirtioNetDriver::sync_multicast(const uint8_t* macs, uint32_t count) { if (!ctrl_rx) return true; if (!macs && count) return false; - disable_interrupt(); - bool ok = true; uint8_t v0 = 0; @@ -443,13 +459,14 @@ bool VirtioNetDriver::sync_multicast(const uint8_t* macs, uint32_t count) { ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_PROMISC, &v0, 1); ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_ALLMULTI, &v0, 1); - if (count == 0) ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_NOMULTI, &v1, 1); - else ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_NOMULTI, &v0, 1); + if (vnp_net_dev.negotiated_features & (1ULL << VIRTIO_NET_F_CTRL_RX_EXTRA)) { + if (count == 0) ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_NOMULTI, &v1, 1); + else ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_RX, VIRTIO_NET_CTRL_RX_NOMULTI, &v0, 1); + } uint32_t payload_len = 8u + count * 6u; uint8_t* payload = (uint8_t*)kalloc(vnp_net_dev.memory_page, payload_len, ALIGN_16B, MEM_PRIV_KERNEL); if (!payload) { - enable_interrupt(); return false; } kprintfv("[virtio-net] sync_multicast ctrl_vq=%u ctrl_rx=%u count=%u",(unsigned)ctrl_vq, (unsigned)ctrl_rx, (unsigned)count); @@ -462,7 +479,6 @@ bool VirtioNetDriver::sync_multicast(const uint8_t* macs, uint32_t count) { ok = ok && virtio_net_ctrl_send(&vnp_net_dev, VIRTIO_NET_CTRL_MAC, VIRTIO_NET_CTRL_MAC_TABLE_SET, payload, payload_len); kfree(payload, payload_len); - enable_interrupt(); return ok; } diff --git a/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.hpp b/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.hpp index 19b8b4fb..2eb9d7a7 100644 --- a/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.hpp +++ b/kernel/networking/drivers/virtio_net_pci/virtio_net_pci.hpp @@ -4,7 +4,6 @@ #include "virtio/virtio_pci.h" #include "std/memory.h" #include "networking/link_layer/nic_types.h" -#define VIRTIO_F_VERSION_1 32 #define VIRTIO_NET_F_CSUM 0 #define VIRTIO_NET_F_GUEST_CSUM 1 @@ -24,6 +23,8 @@ #define VIRTIO_NET_F_STATUS 16 #define VIRTIO_NET_F_CTRL_VQ 17 #define VIRTIO_NET_F_CTRL_RX 18 +#define VIRTIO_NET_F_CTRL_RX_EXTRA 20 +#define VIRTIO_NET_F_SPEED_DUPLEX 63 typedef struct __attribute__((packed)) virtio_net_hdr_t { uint8_t flags; @@ -32,12 +33,8 @@ typedef struct __attribute__((packed)) virtio_net_hdr_t { uint16_t gso_size; uint16_t csum_start; uint16_t csum_offset; -} virtio_net_hdr_t; - -typedef struct __attribute__((packed)) virtio_net_hdr_mrg_rxbuf_t { - virtio_net_hdr_t hdr; uint16_t num_buffers; -} virtio_net_hdr_mrg_rxbuf_t; +} virtio_net_hdr_t; typedef struct __attribute__((packed)) virtio_net_config { uint8_t mac[6]; @@ -56,7 +53,7 @@ class VirtioNetDriver : public NetDriver { VirtioNetDriver(); ~VirtioNetDriver(); - bool init_at(uint64_t pci_addr, uint32_t irq_base_vector); + bool init_at(uint64_t pci_addr, uint32_t irq_base_vector) override; void get_mac(uint8_t out_mac[6]) const override; uint16_t get_mtu() const override; uint16_t get_header_size() const override; @@ -67,21 +64,33 @@ class VirtioNetDriver : public NetDriver { uint8_t get_duplex() const override; bool sync_multicast(const uint8_t* macs, uint32_t count) override; - sizedptr allocate_packet(size_t size) override; - sizedptr handle_receive_packet() override; + netpkt_t* handle_receive_packet() override; void handle_sent_packet() override; - bool send_packet(sizedptr packet) override; + void complete_rx_batch() override; + void complete_tx_batch() override; + netdev_tx_result_t send_packet(netpkt_t* packet) override; private: + friend void virtio_net_rx_free(void* ctx, uintptr_t base); virtio_device vnp_net_dev = {}; volatile virtq_desc* rx_desc = nullptr; volatile virtq_avail* rx_avail = nullptr; volatile virtq_used* rx_used = nullptr; uint16_t rx_qsz = 0; + uint16_t rx_used_batch_end = 0; + void* rx_pool = nullptr; + bool rx_notify_pending = false; + + volatile virtq_desc* tx_desc = nullptr; + volatile virtq_avail* tx_avail = nullptr; + volatile virtq_used* tx_used = nullptr; + uint16_t tx_qsz = 0; + uint16_t tx_avail_shadow_idx = 0; + netpkt_t** tx_pending = nullptr; + uint16_t tx_free_head = UINT16_MAX; bool verbose = false; - bool mrg_rxbuf = false; bool ctrl_vq = false; bool ctrl_rx = false; diff --git a/kernel/networking/firewall.c b/kernel/networking/firewall.c new file mode 100644 index 00000000..de5bcba2 --- /dev/null +++ b/kernel/networking/firewall.c @@ -0,0 +1,207 @@ +#include "firewall.h" +#include "exceptions/irq.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "data/struct/queue.h" +#include "std/memory.h" +#include "net/socket_types.h" + +typedef struct firewall_rule_entry { + NetCtrlFirewallRule rule; + uint16_t owner_pid; +} firewall_rule_entry_t; +static CQueue rules_in; +static CQueue rules_out; +static uint32_t next_id = 1; +static volatile bool enabled = true; +static uint8_t default_in = NET_CTRL_FIREWALL_DENY; +static uint8_t default_out = NET_CTRL_FIREWALL_ALLOW; + +bool firewall_allows(protocol_t protocol, NetCtrlFirewallDirection direction, const net_l4_endpoint* remote, uint16_t local_port, bool related) { + if (protocol != PROTO_TCP && protocol != PROTO_UDP) return true; + if (!remote || (direction != NET_CTRL_FIREWALL_IN && direction != NET_CTRL_FIREWALL_OUT)) return false; + + uint32_t remote_v4 = 0; + if (remote->ver == IP_VER4) { + memcpy(&remote_v4, remote->ip, sizeof(remote_v4)); + if (ipv4_is_loopback(remote_v4)) return true; + } else if (remote->ver == IP_VER6) { + if (ipv6_is_loopback(remote->ip)) return true; + } else return false; + + if (related || !enabled) return true; + + irq_flags_t irq = irq_save_disable(); + if (!enabled) { + irq_restore(irq); + return true; + } + + CQueue* rules = direction == NET_CTRL_FIREWALL_IN ? &rules_in : &rules_out; + uint16_t port = direction == NET_CTRL_FIREWALL_IN ? local_port : remote->port; + uint8_t action = 0; + uint64_t index = rules->tail; + for (uint64_t i = 0; i < rules->length; i++) { + const firewall_rule_entry_t* entry = (const firewall_rule_entry_t*)((const uint8_t*)rules->buffer + index * rules->elem_size); + const NetCtrlFirewallRule* rule = &entry->rule; + if (++index == rules->capacity) index = 0; + if (rule->protocol != PROTO_NONE && rule->protocol != protocol) continue; + + if (rule->ip_version) { + if (remote->ver != rule->ip_version) continue; + if (rule->prefix_len) { + if (rule->ip_version == IP_VER4) { + uint32_t rule_v4 = 0; + memcpy(&rule_v4, rule->address, sizeof(rule_v4)); + uint32_t mask = rule->prefix_len == 32 ? UINT32_MAX : UINT32_MAX << (32 - rule->prefix_len); + if ((rule_v4 & mask) != (remote_v4 & mask)) continue; + } else { + uint8_t bytes = rule->prefix_len >> 3; + uint8_t bits = rule->prefix_len & 7; + if (bytes && memcmp(rule->address, remote->ip, bytes) != 0) continue; + if (bits) { + uint8_t mask = (uint8_t)(0xFFu << (8 - bits)); + if ((rule->address[bytes] & mask) != (remote->ip[bytes] & mask)) continue; + } + } + } + } + + if (rule->port_from && (port < rule->port_from || port > rule->port_to)) continue; + + action = rule->action; + break; + } + + uint8_t fallback = direction == NET_CTRL_FIREWALL_IN ? default_in : default_out; + irq_restore(irq); + return action ? action == NET_CTRL_FIREWALL_ALLOW : fallback == NET_CTRL_FIREWALL_ALLOW; +} + +uint32_t firewall_get_snapshot(NetCtrlFirewallState* state, NetCtrlFirewallRule* out, uint32_t max_rules) { + irq_flags_t irq = irq_save_disable(); + uint64_t total64 = rules_in.length + rules_out.length; + uint32_t total = total64 > UINT32_MAX ? UINT32_MAX : (uint32_t)total64; + if (state) *state = (NetCtrlFirewallState){ .enabled = enabled, .default_in = default_in, .default_out = default_out, .rule_count = total }; + if (!out || !max_rules) { + irq_restore(irq); + return total; + } + + uint32_t count = 0; + uint64_t in_i = 0, out_i = 0; + while (count < max_rules && (in_i < rules_in.length || out_i < rules_out.length)) { + const firewall_rule_entry_t* in_entry = NULL; + const firewall_rule_entry_t* out_entry = NULL; + if (in_i < rules_in.length) { + uint64_t index = (rules_in.tail + in_i) % rules_in.capacity; + in_entry = (const firewall_rule_entry_t*)((const uint8_t*)rules_in.buffer + index * rules_in.elem_size); + } + if (out_i < rules_out.length) { + uint64_t index = (rules_out.tail + out_i) % rules_out.capacity; + out_entry = (const firewall_rule_entry_t*)((const uint8_t*)rules_out.buffer + index * rules_out.elem_size); + } + bool take_in = in_entry && (!out_entry || in_entry->rule.id < out_entry->rule.id); + out[count++] = (take_in ? in_entry : out_entry)->rule; + if (take_in) in_i++; + else out_i++; + } + if (state) state->rule_count = count; + irq_restore(irq); + return count; +} + +int32_t firewall_add_rule(const NetCtrlFirewallRule* input, uint16_t owner_pid) { + if (!input || (input->action != NET_CTRL_FIREWALL_ALLOW && input->action != NET_CTRL_FIREWALL_DENY) || + (input->direction != NET_CTRL_FIREWALL_IN && input->direction != NET_CTRL_FIREWALL_OUT) || + (input->protocol != PROTO_NONE && input->protocol != PROTO_TCP && input->protocol != PROTO_UDP) || + (input->ip_version && input->ip_version != IP_VER4 && input->ip_version != IP_VER6) || + (input->ip_version == IP_VER4 && input->prefix_len > 32) || (input->ip_version == IP_VER6 && input->prefix_len > 128) || + (!input->ip_version && input->prefix_len) || ((!input->port_from) != (!input->port_to)) || + (input->port_from && input->port_from > input->port_to)) return SOCK_ERR_INVAL; + //the longest if statement in the world + + firewall_rule_entry_t entry = { .rule = *input, .owner_pid = owner_pid }; + irq_flags_t irq = irq_save_disable(); + CQueue* rules = input->direction == NET_CTRL_FIREWALL_IN ? &rules_in : &rules_out; + if (!rules->elem_size)cqueue_init(rules, 0, sizeof(firewall_rule_entry_t), NULL, NULL); + entry.rule.id = next_id++; + if (!entry.rule.id) entry.rule.id = next_id++; + if (!cqueue_enqueue(rules, &entry)) { + irq_restore(irq); + return SOCK_ERR_SYS; + } + irq_restore(irq); + return SOCK_OK; +} + +static void firewall_remove_at(CQueue* rules, uint64_t offset) { + for (uint64_t i = offset; i + 1 < rules->length; i++) { + uint64_t dst = (rules->tail + i) % rules->capacity; + uint64_t src = (rules->tail + i + 1) % rules->capacity; + memcpy((uint8_t*)rules->buffer + dst * rules->elem_size, (uint8_t*)rules->buffer + src * rules->elem_size, rules->elem_size); + } + rules->head = (rules->head + rules->capacity - 1) % rules->capacity; + memset((uint8_t*)rules->buffer + rules->head * rules->elem_size, 0, rules->elem_size); + rules->length = rules->length - 1; +} + +int32_t firewall_remove_rule(uint32_t id) { + if (!id) return SOCK_ERR_INVAL; + irq_flags_t irq = irq_save_disable(); + CQueue* lists[2] = {&rules_in, &rules_out}; + for (uint32_t l = 0; l < 2; l++) { + CQueue* rules = lists[l]; + for (uint64_t i = 0; i < rules->length; i++) { + uint64_t index = (rules->tail + i) % rules->capacity; + firewall_rule_entry_t* entry = (firewall_rule_entry_t*)((uint8_t*)rules->buffer + index * rules->elem_size); + if (entry->rule.id != id) continue; + firewall_remove_at(rules, i); + irq_restore(irq); + return SOCK_OK; + } + } + irq_restore(irq); + return SOCK_ERR_NOT_FOUND; +} + +void firewall_cleanup_process(uint16_t pid) { + if (!pid) return; + irq_flags_t irq = irq_save_disable(); + CQueue* lists[2] = {&rules_in, &rules_out}; + for (uint32_t l = 0; l < 2; l++) { + CQueue* rules = lists[l]; + for (uint64_t i = 0; i < rules->length;) { + uint64_t index = (rules->tail + i) % rules->capacity; + firewall_rule_entry_t* entry = (firewall_rule_entry_t*)((uint8_t*)rules->buffer + index * rules->elem_size); + if (entry->owner_pid != pid) { + i++; + continue; + } + firewall_remove_at(rules, i); + } + } + irq_restore(irq); +} + +void firewall_clear_rules(void) { + irq_flags_t irq = irq_save_disable(); + cqueue_clear(&rules_in); + cqueue_clear(&rules_out); + irq_restore(irq); +} + +void firewall_set_enabled(bool value) { + irq_flags_t irq = irq_save_disable(); + enabled = value; + irq_restore(irq); +} + +int32_t firewall_set_default(NetCtrlFirewallDirection direction, NetCtrlFirewallAction action) { + if ((direction != NET_CTRL_FIREWALL_IN && direction != NET_CTRL_FIREWALL_OUT) || (action != NET_CTRL_FIREWALL_ALLOW && action != NET_CTRL_FIREWALL_DENY)) return SOCK_ERR_INVAL; + irq_flags_t irq = irq_save_disable(); + if (direction == NET_CTRL_FIREWALL_IN) default_in = action; + else default_out = action; + irq_restore(irq); + return SOCK_OK; +} \ No newline at end of file diff --git a/kernel/networking/firewall.h b/kernel/networking/firewall.h new file mode 100644 index 00000000..07eeadb6 --- /dev/null +++ b/kernel/networking/firewall.h @@ -0,0 +1,22 @@ +#pragma once + +#include "types.h" +#include "net/net_ctrl.h" +#include "net/network_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool firewall_allows(protocol_t protocol, NetCtrlFirewallDirection direction, const net_l4_endpoint* remote, uint16_t local_port, bool related); +uint32_t firewall_get_snapshot(NetCtrlFirewallState* state, NetCtrlFirewallRule* out, uint32_t max_rules); +int32_t firewall_add_rule(const NetCtrlFirewallRule* rule, uint16_t owner_pid); +int32_t firewall_remove_rule(uint32_t id); +void firewall_cleanup_process(uint16_t pid); +void firewall_clear_rules(void); +void firewall_set_enabled(bool value); +int32_t firewall_set_default(NetCtrlFirewallDirection direction, NetCtrlFirewallAction action); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/interface_manager.c b/kernel/networking/interface_manager.c index 3858c095..1cc311c6 100644 --- a/kernel/networking/interface_manager.c +++ b/kernel/networking/interface_manager.c @@ -1,43 +1,56 @@ #include "interface_manager.h" #include "std/memory.h" #include "networking/link_layer/arp.h" +#include "networking/link_layer/link_utils.h" #include "networking/link_layer/ndp.h" #include "networking/internet_layer/ipv4_route.h" #include "networking/internet_layer/ipv6_route.h" -#include "networking/port_manager.h" -#include "process/scheduler.h" -#include "memory/page_allocator.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" #include "networking/internet_layer/igmp.h" #include "networking/internet_layer/mld.h" #include "networking/link_layer/nic_types.h" #include "networking/network.h" - -static void* g_kmem_page_v4 = NULL; -static void* g_kmem_page_v6 = NULL; +#include "networking/transport_layer/tcp.h" +#include "networking/application_layer/dhcpv6_daemon.h" +#include "networking/application_layer/dhcp_daemon.h" +#include "networking/application_layer/dns/dns_daemon.h" +#include "networking/transport_layer/socket_core.h" +#include "alloc/allocate.h" //TODO: add network settings +#define L2_MCAST_STACK_MACS 16 + static l2_interface_t g_l2[MAX_L2_INTERFACES]; static uint8_t g_l2_used[MAX_L2_INTERFACES]; static uint8_t g_l2_count = 0; +static uint32_t g_l3_epoch_seq = 1; +static uint32_t g_l3_generation_seq = 1; +static uint32_t g_l2_generation_seq = 1; typedef struct { l3_ipv4_interface_t node; bool used; - uint8_t slot_in_l2; } v4_slot_t; typedef struct { l3_ipv6_interface_t node; bool used; - uint8_t slot_in_l2; } v6_slot_t; -#define V4_POOL_SIZE (MAX_L2_INTERFACES * MAX_IPV4_PER_INTERFACE) -#define V6_POOL_SIZE (MAX_L2_INTERFACES * MAX_IPV6_PER_INTERFACE) +static v4_slot_t g_v4[MAX_IPV4_L3_INTERFACES]; +static v6_slot_t g_v6[MAX_IPV6_L3_INTERFACES]; + +static uint32_t next_l3_epoch(void) { + g_l3_epoch_seq++; + if (!g_l3_epoch_seq) g_l3_epoch_seq = 1; + return g_l3_epoch_seq; +} -static v4_slot_t g_v4[V4_POOL_SIZE]; -static v6_slot_t g_v6[V6_POOL_SIZE]; +static uint32_t next_l3_generation(void) { + g_l3_generation_seq++; + if (!g_l3_generation_seq) g_l3_generation_seq = 1; + return g_l3_generation_seq; +} static inline int l2_slot_from_ifindex(uint8_t ifindex){ if (!ifindex) return -1; @@ -48,7 +61,7 @@ static inline int l2_slot_from_ifindex(uint8_t ifindex){ } static bool v4_has_dhcp_on_l2(uint8_t ifindex){ - for (int i = 0; i < V4_POOL_SIZE; i++){ + for (int i = 0; i < MAX_IPV4_L3_INTERFACES; i++){ if (!g_v4[i].used) continue; l3_ipv4_interface_t *x = &g_v4[i].node; if (!x->l2) continue; @@ -60,7 +73,7 @@ static bool v4_has_dhcp_on_l2(uint8_t ifindex){ static bool l2_has_active_v4(l2_interface_t* itf) { if (!itf) return false; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { + for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { l3_ipv4_interface_t* v4 = itf->l3_v4[s]; if (!v4) continue; if (v4->mode == IPV4_CFG_DISABLED) continue; @@ -71,7 +84,7 @@ static bool l2_has_active_v4(l2_interface_t* itf) { static bool l2_has_active_v6(l2_interface_t* itf) { if (!itf) return false; - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { + for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { l3_ipv6_interface_t* v6 = itf->l3_v6[s]; if (!v6) continue; if (v6->cfg == IPV6_CFG_DISABLE) continue; @@ -80,7 +93,61 @@ static bool l2_has_active_v6(l2_interface_t* itf) { return false; } -uint8_t l2_interface_create(const char *name, void *driver_ctx, uint16_t base_metric, uint8_t kind){ +static bool l2_sync_multicast_filters(l2_interface_t* itf) { + if (!itf || itf->link_kind == NET_LINK_LOOPBACK) return false; + + uint8_t physical = l2_physical_ifindex(itf); + l2_interface_t *parent = l2_interface_find_by_index(physical); + if (!parent || parent->link_kind == NET_LINK_LOOPBACK) return false; + if (!parent->is_up) { + if (itf != parent) return true; + return network_sync_multicast(physical, NULL, 0); + } + + uint32_t max_macs = 0; + for (int i = 0; i < MAX_L2_INTERFACES; i++) { + if (!g_l2_used[i]) continue; + l2_interface_t *cur = &g_l2[i]; + if (!cur->is_up || l2_physical_ifindex(cur) != physical) continue; + max_macs += cur->ipv4_mcast_count + cur->ipv6_mcast_count; + } + + uint8_t stack_macs[L2_MCAST_STACK_MACS][MAC_ADDR_LEN]; + uint8_t (*macs)[MAC_ADDR_LEN] = stack_macs; + if (max_macs > L2_MCAST_STACK_MACS) { + macs = zalloc(max_macs * sizeof(*macs)); + if (!macs) return false; + } + + uint32_t count = 0; + for (int i = 0; i < MAX_L2_INTERFACES; i++) { + if (!g_l2_used[i]) continue; + l2_interface_t *cur = &g_l2[i]; + if (!cur->is_up || l2_physical_ifindex(cur) != physical) continue; + + for (uint8_t family = 0; family < 2; family++) { + uint8_t n = family ? cur->ipv6_mcast_count : cur->ipv4_mcast_count; + for (uint8_t j = 0; j < n; j++) { + uint8_t mac[MAC_ADDR_LEN]; + if (family) ipv6_multicast_mac(cur->ipv6_mcast[j], mac); + else ipv4_mcast_to_mac(cur->ipv4_mcast[j], mac); + + uint32_t k = 0; + for (; k < count; k++) if (mac_equal(macs[k], mac)) break; + if (k == count) { + mac_copy(macs[count], mac); + count++; + } + } + } + } + + bool ok = network_sync_multicast(physical, count ? macs[0] : NULL, count); + if (macs != stack_macs) release(macs); + return ok; +} + +uint8_t l2_interface_create(const char *name, uint8_t nic_id, uint16_t base_metric, NetDevKind dev_kind) { int slot = -1; for (int i=0;i<(int)MAX_L2_INTERFACES;i++) if (!g_l2_used[i]) { slot=i; @@ -91,37 +158,73 @@ uint8_t l2_interface_create(const char *name, void *driver_ctx, uint16_t base_me l2_interface_t* itf = &g_l2[slot]; memset(itf, 0, sizeof(*itf)); itf->ifindex = (uint8_t)(slot + 1); + g_l2_generation_seq++; + if (!g_l2_generation_seq) g_l2_generation_seq = 1; + itf->generation = g_l2_generation_seq; + itf->nic_id = nic_id; - int i = 0; - if (name) { - while (name[i] && i < 15) { - itf->name[i] = name[i]; - i++; - } - } - itf->name[i] = 0; + if (name) strncpy(itf->name, name, sizeof(itf->name)); - itf->driver_context = driver_ctx; itf->base_metric = base_metric; - itf->kind = kind; - if (kind != NET_IFK_LOCALHOST) { - itf->arp_table = arp_table_create(); - itf->nd_table = ndp_table_create(); - } else { - itf->arp_table = NULL; - itf->nd_table = NULL; - } + itf->dev_kind = dev_kind; + itf->link_kind = dev_kind == NET_DEV_LOOPBACK ? NET_LINK_LOOPBACK : NET_LINK_DIRECT; + + itf->ipv4_mcast[0] = IPV4_MCAST_ALL_HOSTS; + itf->ipv4_mcast_ref[0] = 1; + itf->ipv4_mcast_count = 1; + + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + ipv6_cpy(itf->ipv6_mcast[0], all_nodes); + itf->ipv6_mcast_ref[0] = 1; + itf->ipv6_mcast_count = 1; g_l2_used[slot] = 1; g_l2_count += 1; return itf->ifindex; } +l2_interface_t *l2_vlan_find(uint8_t parent_ifindex, uint16_t vlan_id) { + if (!parent_ifindex || !vlan_id || vlan_id > 4094) return NULL; + for (int i = 0; i < MAX_L2_INTERFACES; i++) { + l2_interface_t *itf = &g_l2[i]; + if (g_l2_used[i] && itf->link_kind == NET_LINK_VLAN && itf->parent_ifindex == parent_ifindex && itf->vlan_id == vlan_id) return itf; + } + return NULL; +} + +uint8_t l2_vlan_create(uint8_t parent_ifindex, uint16_t vlan_id, const char *name) { + l2_interface_t *parent = l2_interface_find_by_index(parent_ifindex); + if (!parent || parent->dev_kind != NET_DEV_ETH || parent->link_kind != NET_LINK_DIRECT || !vlan_id || vlan_id > 4094 || !name) return 0; + + size_t len = strlen_max(name, 16); + if (!len || len >= 16 || l2_vlan_find(parent_ifindex, vlan_id)) return 0; + for (int i = 0; i < MAX_L2_INTERFACES; i++) if (g_l2_used[i] && strcmp(g_l2[i].name, name) == 0) return 0; + + uint8_t ix = l2_interface_create(name, parent->nic_id, parent->base_metric, parent->dev_kind); + l2_interface_t *vlan = l2_interface_find_by_index(ix); + if (!vlan) return 0; + vlan->link_kind = NET_LINK_VLAN; + vlan->parent_ifindex = parent_ifindex; + vlan->vlan_id = vlan_id; + return ix; +} + +bool l2_vlan_destroy(uint8_t ifindex) { + l2_interface_t *vlan = l2_interface_find_by_index(ifindex); + if (!vlan || vlan->link_kind != NET_LINK_VLAN) return false; + return l2_interface_destroy(ifindex); +} + bool l2_interface_destroy(uint8_t ifindex){ int slot = l2_slot_from_ifindex(ifindex); if (slot < 0) return false; l2_interface_t* itf = &g_l2[slot]; if (itf->ipv4_count || itf->ipv6_count) return false; + if (itf->link_kind != NET_LINK_VLAN) for (int i = 0; i < MAX_L2_INTERFACES; i++) { + if (g_l2_used[i] && g_l2[i].parent_ifindex == ifindex) return false; + } + if (itf->is_up && !l2_interface_set_up(ifindex, false)) return false; if (itf->arp_table) { arp_table_destroy((arp_table_t*)itf->arp_table); @@ -132,6 +235,11 @@ bool l2_interface_destroy(uint8_t ifindex){ itf->nd_table = NULL; } + ipv6_rt_onlink_clear(ifindex); + socket_core_l2_deleted(ifindex, itf->generation); + igmp_l2_deleted(ifindex); + mld_l2_deleted(ifindex); + memset(&g_l2[slot], 0, sizeof(l2_interface_t)); g_l2_used[slot] = 0; if (g_l2_count) g_l2_count -= 1; @@ -156,160 +264,227 @@ l2_interface_t* l2_interface_at(uint8_t idx) { return 0; } +bool l2_prepare_arp(uint8_t ifindex) { + l2_interface_t *l2 = l2_interface_find_by_index(ifindex); + if (!l2) return false; + if (l2->link_kind == NET_LINK_LOOPBACK || l2->arp_table) return true; + l2->arp_table = arp_table_create(); + return l2->arp_table != NULL; +} + +bool l2_prepare_ndp(uint8_t ifindex) { + l2_interface_t *l2 = l2_interface_find_by_index(ifindex); + if (!l2) return false; + if (l2->link_kind == NET_LINK_LOOPBACK || l2->nd_table) return true; + l2->nd_table = ndp_table_create(); + return l2->nd_table != NULL; +} + bool l2_interface_set_up(uint8_t ifindex, bool up) { l2_interface_t* itf = l2_interface_find_by_index(ifindex); if (!itf) return false; + if (itf->is_up == up) return true; itf->is_up = up; - return true; -} - -static bool l2_sync_multicast_filters(l2_interface_t* itf) { - if (!itf) return false; - uint8_t macs[(MAX_IPV4_MCAST_PER_INTERFACE + MAX_IPV6_MCAST_PER_INTERFACE) * 6]; - uint32_t count = 0; - - for (int i = 0; i < (int)itf->ipv4_mcast_count; ++i) { - uint8_t m[6]; - ipv4_mcast_to_mac(itf->ipv4_mcast[i], m); - bool exists = false; - for (uint32_t j = 0; j < count; ++j) { - if (memcmp(&macs[j * 6], m, 6) == 0) { - exists = true; - break; - } + if (itf->link_kind != NET_LINK_LOOPBACK) { + if (!l2_sync_multicast_filters(itf)) { + itf->is_up = !up; + return false; } - if (!exists) { - memcpy(&macs[count * 6], m, 6); - count++; + ndp_link_state_changed(ifindex, up); + if (itf->link_kind == NET_LINK_DIRECT) for (int i = 0; i < MAX_L2_INTERFACES; i++) { + l2_interface_t* child = &g_l2[i]; + if (!g_l2_used[i] || child->link_kind != NET_LINK_VLAN || child->parent_ifindex != ifindex || !child->is_up) continue; + ndp_link_state_changed(child->ifindex, up); } } - - for (int i = 0; i < (int)itf->ipv6_mcast_count; ++i) { - uint8_t m[6]; - ipv6_multicast_mac(itf->ipv6_mcast[i], m); - bool exists = false; - for (uint32_t j = 0; j < count; ++j) { - if (memcmp(&macs[j*6], m, 6) == 0) { - exists = true; - break; - } - } - if (!exists) { - memcpy(&macs[count * 6], m, 6); - count++; - } + if (up) { + dhcp_daemon_kick(); + dhcpv6_daemon_kick(); } + dns_daemon_kick(); + return true; +} - return network_sync_multicast(itf->ifindex, macs, count); +bool l2_interface_is_operational(const l2_interface_t* l2) { + if (!l2 || !l2->is_up) return false; + uint8_t parent_ifindex = l2->parent_ifindex; + if (!parent_ifindex) return true; + l2_interface_t* parent = l2_interface_find_by_index(parent_ifindex); + return parent && parent->is_up; } -static int find_ipv4_group_index(l2_interface_t* itf, uint32_t group) { - for (int i = 0; i < (int)itf->ipv4_mcast_count; ++i) if (itf->ipv4_mcast[i] == group) return i; - return -1; +bool l2_interface_set_metric(uint8_t ifindex, uint16_t metric) { + l2_interface_t* itf = l2_interface_find_by_index(ifindex); + if (!itf) return false; + if (itf->base_metric == metric) return true; + itf->base_metric = metric; + for (int i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + l3_ipv4_interface_t* v4 = itf->l3_v4[i]; + if (!v4 || !v4->routing_table) continue; + ipv4_rt_sync_basics((ipv4_rt_table_t*)v4->routing_table, v4->ip, v4->mask, v4->gw, itf->base_metric); + } + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = itf->l3_v6[i]; + if (!v6 || !v6->routing_table) continue; + ipv6_rt_sync_basics((ipv6_rt_table_t*)v6->routing_table, v6->ip, v6->prefix_len, v6->gateway, itf->base_metric); + } + return true; } + bool l2_ipv4_mcast_join(uint8_t ifindex, uint32_t group) { l2_interface_t* itf = l2_interface_find_by_index(ifindex); if (!itf) return false; if (!ipv4_is_multicast(group)) return false; - if (find_ipv4_group_index(itf, group) >= 0) return true; + int idx = -1; + for (int i = 0; i < (int)itf->ipv4_mcast_count; i++) { + if (itf->ipv4_mcast[i] != group) continue; + idx = i; + break; + } + if (idx >= 0) { + if (itf->ipv4_mcast_ref[idx] < 0xFFFFu) itf->ipv4_mcast_ref[idx] += 1; + return true; + } if (itf->ipv4_mcast_count >= MAX_IPV4_MCAST_PER_INTERFACE) return false; - itf->ipv4_mcast[itf->ipv4_mcast_count++] = group; - if (itf->kind != NET_IFK_LOCALHOST) (void)l2_sync_multicast_filters(itf); - if (itf->kind != NET_IFK_LOCALHOST && l2_has_active_v4(itf)) (void)igmp_send_join(ifindex, group); + itf->ipv4_mcast[itf->ipv4_mcast_count] = group; + itf->ipv4_mcast_ref[itf->ipv4_mcast_count] = 1; + itf->ipv4_mcast_count += 1; + if (itf->link_kind != NET_LINK_LOOPBACK && itf->is_up && !l2_sync_multicast_filters(itf)) { + itf->ipv4_mcast_count--; + itf->ipv4_mcast_ref[itf->ipv4_mcast_count] = 0; + return false; + } + if (itf->link_kind != NET_LINK_LOOPBACK && l2_has_active_v4(itf)) (void)igmp_send_join(ifindex, group); return true; } bool l2_ipv4_mcast_leave(uint8_t ifindex, uint32_t group) { l2_interface_t* itf = l2_interface_find_by_index(ifindex); if (!itf) return false; - int idx = find_ipv4_group_index(itf, group); + int idx = -1; + for (int i = 0; i < (int)itf->ipv4_mcast_count; i++) { + if (itf->ipv4_mcast[i] != group) continue; + idx = i; + break; + } if (idx < 0) return true; - for (int i = idx + 1; i < (int)itf->ipv4_mcast_count; ++i) itf->ipv4_mcast[i-1] = itf->ipv4_mcast[i]; + if (group == IPV4_MCAST_ALL_HOSTS && itf->ipv4_mcast_ref[idx] <= 1) return true; + if (itf->ipv4_mcast_ref[idx] > 1) { + itf->ipv4_mcast_ref[idx] -= 1; + return true; + } + for (int i = idx + 1; i < (int)itf->ipv4_mcast_count; i++) { + itf->ipv4_mcast[i-1] = itf->ipv4_mcast[i]; + itf->ipv4_mcast_ref[i-1] = itf->ipv4_mcast_ref[i]; + } if (itf->ipv4_mcast_count) itf->ipv4_mcast_count -= 1; - if (itf->kind != NET_IFK_LOCALHOST) (void)l2_sync_multicast_filters(itf); - if (itf->kind != NET_IFK_LOCALHOST && l2_has_active_v4(itf)) (void)igmp_send_leave(ifindex, group); + if (itf->ipv4_mcast_count < MAX_IPV4_MCAST_PER_INTERFACE) itf->ipv4_mcast_ref[itf->ipv4_mcast_count] = 0; + if (itf->link_kind != NET_LINK_LOOPBACK && itf->is_up && !l2_sync_multicast_filters(itf)) { + for (int i = (int)itf->ipv4_mcast_count; i > idx; i--) { + itf->ipv4_mcast[i] = itf->ipv4_mcast[i-1]; + itf->ipv4_mcast_ref[i] = itf->ipv4_mcast_ref[i-1]; + } + itf->ipv4_mcast[idx] = group; + itf->ipv4_mcast_ref[idx] = 1; + itf->ipv4_mcast_count++; + return false; + } + if (itf->link_kind != NET_LINK_LOOPBACK && l2_has_active_v4(itf)) (void)igmp_send_leave(ifindex, group); return true; } -static int find_ipv6_group_index(l2_interface_t* itf, const uint8_t group[16]) { - for (int i = 0; i < (int)itf->ipv6_mcast_count; ++i) if (ipv6_cmp(itf->ipv6_mcast[i], group) == 0) return i; - return -1; -} bool l2_ipv6_mcast_join(uint8_t ifindex, const uint8_t group[16]) { l2_interface_t* itf = l2_interface_find_by_index(ifindex); if (!itf || !group) return false; if (!ipv6_is_multicast(group)) return false; - if (find_ipv6_group_index(itf, group) >= 0) return true; + int idx = -1; + for (int i = 0; i < (int)itf->ipv6_mcast_count; i++) { + if (ipv6_cmp(itf->ipv6_mcast[i], group) != 0) continue; + idx = i; + break; + } + if (idx >= 0) { + if (itf->ipv6_mcast_ref[idx] < 0xFFFFu) itf->ipv6_mcast_ref[idx] += 1; + return true; + } if (itf->ipv6_mcast_count >= MAX_IPV6_MCAST_PER_INTERFACE) return false; ipv6_cpy(itf->ipv6_mcast[itf->ipv6_mcast_count], group); + itf->ipv6_mcast_ref[itf->ipv6_mcast_count] = 1; itf->ipv6_mcast_count += 1; - if (itf->kind != NET_IFK_LOCALHOST) (void)l2_sync_multicast_filters(itf); - if (itf->kind != NET_IFK_LOCALHOST && l2_has_active_v6(itf)) (void)mld_send_join(ifindex, group); + if (itf->link_kind != NET_LINK_LOOPBACK && itf->is_up && !l2_sync_multicast_filters(itf)) { + itf->ipv6_mcast_count--; + itf->ipv6_mcast_ref[itf->ipv6_mcast_count] = 0; + return false; + } + if (itf->link_kind != NET_LINK_LOOPBACK && l2_has_active_v6(itf)) (void)mld_send_join(ifindex, group); return true; } bool l2_ipv6_mcast_leave(uint8_t ifindex, const uint8_t group[16]) { l2_interface_t* itf = l2_interface_find_by_index(ifindex); if (!itf || !group) return false; - int idx = find_ipv6_group_index(itf, group); + int idx = -1; + for (int i = 0; i < (int)itf->ipv6_mcast_count; i++) { + if (ipv6_cmp(itf->ipv6_mcast[i], group) != 0) continue; + idx = i; + break; + } if (idx < 0) return true; - if (itf->kind != NET_IFK_LOCALHOST && l2_has_active_v6(itf)) (void)mld_send_leave(ifindex, group); - for (int i = idx + 1; i < (int)itf->ipv6_mcast_count; ++i) ipv6_cpy(itf->ipv6_mcast[i-1], itf->ipv6_mcast[i]); - if (itf->ipv6_mcast_count) itf->ipv6_mcast_count -= 1; - if (itf->kind != NET_IFK_LOCALHOST) (void)l2_sync_multicast_filters(itf); - return true; -} -static bool v4_ip_exists_anywhere(uint32_t ip){ - for (int i=0;il2 || x->l2->ifindex != ifindex) continue; - if (x->mode == IPV4_CFG_DISABLED) continue; - uint32_t m = (x->mask==0)?mask:((mask==0)?x->mask:((x->mask < mask)?x->mask:mask)); - if (ipv4_net(ip, m) == ipv4_net(x->ip, m)) return true; + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + if (ipv6_cmp(group, all_nodes) == 0 && itf->ipv6_mcast_ref[idx] <= 1) return true; + if (itf->ipv6_mcast_ref[idx] > 1) { + itf->ipv6_mcast_ref[idx] -= 1; + return true; } - return false; + for (int i = idx + 1; i < (int)itf->ipv6_mcast_count; i++) { + ipv6_cpy(itf->ipv6_mcast[i-1], itf->ipv6_mcast[i]); + itf->ipv6_mcast_ref[i-1] = itf->ipv6_mcast_ref[i]; + } + if (itf->ipv6_mcast_count) itf->ipv6_mcast_count -= 1; + if (itf->ipv6_mcast_count < MAX_IPV6_MCAST_PER_INTERFACE) itf->ipv6_mcast_ref[itf->ipv6_mcast_count] = 0; + if (itf->link_kind != NET_LINK_LOOPBACK && itf->is_up && !l2_sync_multicast_filters(itf)) { + for (int i = (int)itf->ipv6_mcast_count; i > idx; i--) { + ipv6_cpy(itf->ipv6_mcast[i], itf->ipv6_mcast[i-1]); + itf->ipv6_mcast_ref[i] = itf->ipv6_mcast_ref[i-1]; + } + ipv6_cpy(itf->ipv6_mcast[idx], group); + itf->ipv6_mcast_ref[idx] = 1; + itf->ipv6_mcast_count++; + return false; + } + if (itf->link_kind != NET_LINK_LOOPBACK && l2_has_active_v6(itf)) (void)mld_send_leave(ifindex, group); + return true; } static bool v6_ip_exists_anywhere(const uint8_t ip[16]){ if (ipv6_is_unspecified(ip)) return false; - for (int i=0;il2 || x->l2->ifindex != ifindex) continue; if (x->cfg == IPV6_CFG_DISABLE) continue; if (ipv6_is_unspecified(x->ip)) continue; uint8_t minp = (x->prefix_len < prefix_len) ? x->prefix_len : prefix_len; - int eq = 1; - int fb = minp/8, rb = minp%8; - for (int b=0;bip[b]) {eq=0;break;} } - if (eq && rb){ - uint8_t m=(uint8_t)(0xFF<<(8-rb)); - if ( (ip[fb]&m) != (x->ip[fb]&m) ) eq=0; - } - if (eq) return true; + if (ipv6_common_prefix_len(ip, x->ip) >= minp) return true; } return false; } -uint8_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *runtime_opts){ +l3_id_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *runtime_opts) { l2_interface_t *l2 = l2_interface_find_by_index(ifindex); if (!l2) return 0; + bool had_active_v4 = l2_has_active_v4(l2); if (mode == IPV4_CFG_DHCP) { if (v4_has_dhcp_on_l2(ifindex)) { return 0; @@ -318,16 +493,17 @@ uint8_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, ui if (mode == IPV4_CFG_STATIC){ if (ipv4_is_unspecified(ip)) return 0; if (!ipv4_mask_is_contiguous(mask)) return 0; - if (ipv4_is_loopback(ip) && (l2->kind != NET_IFK_LOCALHOST)) return 0; + if (ipv4_is_loopback(ip) && (l2->link_kind != NET_LINK_LOOPBACK)) return 0; if (ipv4_is_multicast(ip)) return 0; if (ipv4_is_reserved_special(ip)) { - if (!(ipv4_is_loopback(ip) && l2->kind == NET_IFK_LOCALHOST)) return 0; + if (!(ipv4_is_loopback(ip) && l2->link_kind == NET_LINK_LOOPBACK)) return 0; } if (ipv4_is_network_address(ip, mask)) return 0; if (ipv4_is_broadcast_address(ip, mask)) return 0; - if (v4_ip_exists_anywhere(ip)) return 0; - if (v4_overlap_intra_l2(ifindex, ip, mask)) return 0; + if (l3_ipv4_find_by_ip(ip)) return 0; } + if (mode != IPV4_CFG_DISABLED && l2->link_kind != NET_LINK_LOOPBACK && !l2_prepare_arp(ifindex)) return 0; + if (mode == IPV4_CFG_STATIC && l2->link_kind != NET_LINK_LOOPBACK && !arp_dad_ipv4_on(ifindex, ip)) return 0; if (l2->ipv4_count >= MAX_IPV4_PER_INTERFACE) return 0; int loc = -1; @@ -336,17 +512,14 @@ uint8_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, ui break; } int g = -1; - for (int i=0;il2 = l2; n->mode = mode; n->ip = (mode==IPV4_CFG_STATIC) ? ip : 0; @@ -357,73 +530,78 @@ uint8_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, ui memset(&n->runtime_opts_v4, 0, sizeof(n->runtime_opts_v4)); if (runtime_opts) n->runtime_opts_v4 = *runtime_opts; - n->routing_table = NULL; - if (l2->kind != NET_IFK_LOCALHOST) { - n->routing_table = ipv4_rt_create(); + n->is_localhost = (l2->link_kind == NET_LINK_LOOPBACK); + n->l3_id = (l3_id_t)(g + 1); + if (!n->is_localhost && mode != IPV4_CFG_DISABLED) { + n->routing_table = ipv4_rt_create(n->l3_id); if (!n->routing_table) { - g_v4[g].used = false; memset(&g_v4[g], 0, sizeof(g_v4[g])); return 0; } ipv4_rt_ensure_basics((ipv4_rt_table_t*)n->routing_table, n->ip, n->mask, n->gw, l2->base_metric); } - n->is_localhost = (l2->kind == NET_IFK_LOCALHOST); - n->l3_id = make_l3_id_v4(l2->ifindex, (uint8_t)loc); + n->epoch = next_l3_epoch(); + n->generation = next_l3_generation(); l2->l3_v4[loc] = n; + g_v4[g].used = true; l2->ipv4_count++; - if (!g_kmem_page_v4) g_kmem_page_v4 = palloc(PAGE_SIZE*1, MEM_PRIV_KERNEL, MEM_RW|MEM_NORM, false); - if (!g_kmem_page_v4) return NULL; - n->port_manager = (port_manager_t*)kalloc(g_kmem_page_v4, sizeof(port_manager_t), ALIGN_16B, MEM_PRIV_KERNEL); - if (!n->port_manager) { - l2->l3_v4[loc] = NULL; - if (l2->ipv4_count) l2->ipv4_count--; - if (n->routing_table) { - ipv4_rt_destroy((ipv4_rt_table_t*)n->routing_table); - n->routing_table = NULL; - } - g_v4[g].used = false; - memset(&g_v4[g], 0, sizeof(g_v4[g])); - return 0; + if (!had_active_v4 && l2->link_kind != NET_LINK_LOOPBACK && l2_has_active_v4(l2)) { + for (int i = 0; i < (int)l2->ipv4_mcast_count; i++) igmp_send_join(l2->ifindex, l2->ipv4_mcast[i]); } - port_manager_init(n->port_manager); - - if (n->mode != IPV4_CFG_DISABLED && n->ip && l2->kind != NET_IFK_LOCALHOST) (void)l2_ipv4_mcast_join(ifindex, IPV4_MCAST_ALL_HOSTS); + dhcp_daemon_kick(); + dns_daemon_kick(); return n->l3_id; } -bool l3_ipv4_update(uint8_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *runtime_opts){ +bool l3_ipv4_update(l3_id_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *runtime_opts) { l3_ipv4_interface_t *n = l3_ipv4_find_by_id(l3_id); if (!n) return false; l2_interface_t *l2 = n->l2; if (!l2) return false; + uint16_t old_effective_mtu = l3_ipv4_effective_mtu(n); + bool had_active_v4 = l2_has_active_v4(l2); if (mode == IPV4_CFG_DHCP && n->mode != IPV4_CFG_DHCP) { if (v4_has_dhcp_on_l2(l2->ifindex)) return false; } if (mode == IPV4_CFG_STATIC){ if (ipv4_is_unspecified(ip)) return false; if (!ipv4_mask_is_contiguous(mask)) return false; - if (ipv4_is_loopback(ip)&& (l2->kind != NET_IFK_LOCALHOST)) return false; + if (ipv4_is_loopback(ip)&& (l2->link_kind != NET_LINK_LOOPBACK)) return false; if (ipv4_is_multicast(ip)) return false; if (ipv4_is_reserved_special(ip)) { - if (!(ipv4_is_loopback(ip) && l2->kind == NET_IFK_LOCALHOST)) return false; + if (!(ipv4_is_loopback(ip) && l2->link_kind == NET_LINK_LOOPBACK)) return false; } if (ipv4_is_network_address(ip, mask)) return false; if (ipv4_is_broadcast_address(ip, mask)) return false; - if (ip != n->ip && v4_ip_exists_anywhere(ip)) return false; - for (int i = 0; i < V4_POOL_SIZE; i++){ - if (!g_v4[i].used) continue; - l3_ipv4_interface_t *x = &g_v4[i].node; - if (x==n) continue; - if (!x->l2 || x->l2->ifindex != l2->ifindex) continue; - if (x->mode == IPV4_CFG_DISABLED) continue; - uint32_t m = (x->mask < mask) ? x->mask : mask; - if (ipv4_net(ip, m) == ipv4_net(x->ip, m)) return false; - } } + if ((mode == IPV4_CFG_STATIC || mode == IPV4_CFG_DHCP) && ip != n->ip && ip && l3_ipv4_find_by_ip(ip)) return false; + if (mode != IPV4_CFG_DISABLED && l2->link_kind != NET_LINK_LOOPBACK && !l2_prepare_arp(l2->ifindex)) return false; + if (mode == IPV4_CFG_STATIC && ip != n->ip && l2->link_kind != NET_LINK_LOOPBACK && !arp_dad_ipv4_on(l2->ifindex, ip)) return false; + + uint32_t old_ip = n->ip; + uint32_t old_mask = n->mask; + bool old_active = n->mode != IPV4_CFG_DISABLED && n->ip != 0; + bool new_active = mode != IPV4_CFG_DISABLED && (mode == IPV4_CFG_STATIC || mode == IPV4_CFG_DHCP) && ip != 0; + bool identity_changed = old_ip != (new_active ? ip : 0) || old_active != new_active; + bool needs_route = !n->is_localhost && mode != IPV4_CFG_DISABLED; + ipv4_rt_table_t *new_rt = NULL; + if (needs_route && !n->routing_table) { + new_rt = ipv4_rt_create(n->l3_id); + if (!new_rt) return false; + } + + bool l3_changed = n->mode != mode || n->gw != gw; + if (mode == IPV4_CFG_STATIC || mode == IPV4_CFG_DHCP) { + if (n->ip != ip || n->mask != mask) l3_changed = true; + } else if (n->ip || n->mask) { + l3_changed = true; + } + + if (identity_changed) tcp_l3_retire(n->l3_id, n->generation); n->mode = mode; @@ -434,7 +612,6 @@ bool l3_ipv4_update(uint8_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4 n->mask = mask; n->gw = gw; n->broadcast = ipv4_broadcast_calc(ip, mask); - if (n->ip && l2->kind != NET_IFK_LOCALHOST) (void)l2_ipv4_mcast_join(l2->ifindex, IPV4_MCAST_ALL_HOSTS); } else { n->ip = 0; n->mask = 0; @@ -442,40 +619,58 @@ bool l3_ipv4_update(uint8_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4 n->broadcast = 0; } - if (l2->kind != NET_IFK_LOCALHOST) { - if (!n->routing_table) n->routing_table = ipv4_rt_create(); - if (n->routing_table) ipv4_rt_sync_basics((ipv4_rt_table_t*)n->routing_table, n->ip, n->mask, n->gw, l2->base_metric); - } else { - if (n->routing_table) { - ipv4_rt_destroy((ipv4_rt_table_t*)n->routing_table); - n->routing_table = NULL; + uint16_t new_effective_mtu = l3_ipv4_effective_mtu(n); + if (new_effective_mtu != old_effective_mtu) l3_changed = true; + + if (needs_route) { + if (new_rt) n->routing_table = new_rt; + if (old_ip && old_mask) { + uint32_t old_net = old_ip & old_mask; + uint32_t new_net = (n->ip && n->mask) ? (n->ip & n->mask) : 0; + if (!n->ip || !n->mask || old_mask != n->mask || old_net != new_net) { + ipv4_rt_del_in((ipv4_rt_table_t*)n->routing_table, old_net, old_mask); + } } + ipv4_rt_sync_basics((ipv4_rt_table_t*)n->routing_table, n->ip, n->mask, n->gw, l2->base_metric); + } else if (n->routing_table) { + ipv4_rt_destroy((ipv4_rt_table_t*)n->routing_table); + n->routing_table = NULL; + } + if (!had_active_v4 && l2->link_kind != NET_LINK_LOOPBACK && l2_has_active_v4(l2)) { + for (int i = 0; i < (int)l2->ipv4_mcast_count; i++) igmp_send_join(l2->ifindex, l2->ipv4_mcast[i]); + } + + if (l3_changed) { + n->epoch = next_l3_epoch(); + if (identity_changed) n->generation = next_l3_generation(); } + if (!identity_changed && new_effective_mtu && old_effective_mtu && new_effective_mtu < old_effective_mtu) tcp_l3_mtu_reduce(n->l3_id, n->generation, new_effective_mtu); + dhcp_daemon_kick(); + if (identity_changed) dns_daemon_kick(); return true; } -bool l3_ipv4_remove_from_interface(uint8_t l3_id){ +bool l3_ipv4_remove_from_interface(l3_id_t l3_id) { l3_ipv4_interface_t *n = l3_ipv4_find_by_id(l3_id); if (!n) return false; l2_interface_t *l2 = n->l2; if (!l2) return false; - if (l2->ipv4_count <= 1) return false; + if (l2->ipv4_count <= 1 && l2->link_kind != NET_LINK_VLAN) return false; int g = -1; - for (int i=0;iport_manager) { - kfree(n->port_manager, sizeof(port_manager_t)); - n->port_manager = NULL; - } + tcp_l3_retire(n->l3_id, n->generation); + socket_core_l3_deleted(n->l3_id); - uint8_t slot = l3_slot_from_id(l3_id); - if (slot < MAX_IPV4_PER_INTERFACE && l2->l3_v4[slot] == n){ + for (int slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + if (l2->l3_v4[slot] != n) continue; l2->l3_v4[slot] = NULL; if (l2->ipv4_count) l2->ipv4_count--; + break; } if (n->routing_table) { @@ -485,26 +680,38 @@ bool l3_ipv4_remove_from_interface(uint8_t l3_id){ g_v4[g].used = false; memset(&g_v4[g], 0, sizeof(g_v4[g])); + dhcp_daemon_kick(); + dns_daemon_kick(); return true; } -l3_ipv4_interface_t* l3_ipv4_find_by_id(uint8_t l3_id){ - if (!l3_id || l3_is_v6_from_id(l3_id)) return NULL; - uint8_t ifx = l3_ifindex_from_id(l3_id); - uint8_t loc = l3_slot_from_id(l3_id); - l2_interface_t *l2 = l2_interface_find_by_index(ifx); - if (!l2) return NULL; - if (loc >= MAX_IPV4_PER_INTERFACE) return NULL; - return l2->l3_v4[loc]; +l3_ipv4_interface_t* l3_ipv4_find_by_id(l3_id_t l3_id) { + if (!l3_id || l3_id > MAX_IPV4_L3_INTERFACES) return NULL; + v4_slot_t *slot = &g_v4[l3_id - 1]; + return slot->used ? &slot->node : NULL; } l3_ipv4_interface_t* l3_ipv4_find_by_ip(uint32_t ip){ - for (int i=0;il2) return 0; + uint16_t device_mtu = network_get_device_mtu(l3->l2->ifindex); + if (!device_mtu) return 0; + uint16_t l3_mtu = l3->runtime_opts_v4.mtu; + return l3_mtu && l3_mtu < device_mtu ? l3_mtu : device_mtu; +} + +l3_id_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind) { l2_interface_t *l2 = l2_interface_find_by_index(ifindex); if (!l2) return 0; + if (l2->link_kind != NET_LINK_LOOPBACK && cfg != IPV6_CFG_DISABLE) { + uint16_t device_mtu = network_get_device_mtu(ifindex); + if (device_mtu && device_mtu < 1280) return 0; + } + bool had_active_v6 = l2_has_active_v6(l2); + uint8_t pre_mcast_count = l2->ipv6_mcast_count; if (prefix_len > 128) return 0; int placeholder_ll = 0; @@ -532,7 +739,7 @@ uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t if (!ipv6_is_linklocal(ip)) return 0; } if (!ipv6_is_unspecified(ip) && !placeholder_ll && v6_ip_exists_anywhere(ip)) return 0; - for (int i=0;iifindex != ifindex) continue; if (ipv6_is_linklocal(g_v6[i].node.ip) && g_v6[i].node.cfg != IPV6_CFG_DISABLE) return 0; @@ -545,18 +752,18 @@ uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t } if (!ipv6_is_unspecified(ip)){ if (ipv6_is_multicast(ip)) return 0; - if (is_loop && (l2->kind != NET_IFK_LOCALHOST)) return 0; + if (is_loop && (l2->link_kind != NET_LINK_LOOPBACK)) return 0; if (!is_loop){ if (ipv6_is_ula(ip)) return 0; if (!placeholder_gua){ if (v6_ip_exists_anywhere(ip)) return 0; - if (v6_overlap_intra_l2(ifindex, ip, prefix_len)) return 0; + if (v6_overlap_intra_l2(ifindex, ip, prefix_len, NULL)) return 0; } } } if (!is_loop){ bool has_lla=false; - for (int i=0;il2 || x->l2->ifindex != ifindex) continue; @@ -577,21 +784,18 @@ uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t } int g = -1; - for (int i=0;ilink_kind != NET_LINK_LOOPBACK && !l2_prepare_ndp(ifindex)) return 0; - g_v6[g].used = true; - g_v6[g].slot_in_l2 = (uint8_t)loc; - + memset(&g_v6[g], 0, sizeof(g_v6[g])); l3_ipv6_interface_t *n = &g_v6[g].node; - memset(n, 0, sizeof(*n)); n->l2 = l2; n->cfg = cfg; n->kind = kind; - n->mtu = 1500; uint8_t final_ip[16]; ipv6_cpy(final_ip, ip); @@ -603,7 +807,7 @@ uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t ipv6_cpy(n->ip, final_ip); n->prefix_len = prefix_len; ipv6_cpy(n->gateway, gw); - n->is_localhost = (l2->kind == NET_IFK_LOCALHOST); + n->is_localhost = (l2->link_kind == NET_LINK_LOOPBACK); n->valid_lifetime = 0; n->preferred_lifetime = 0; n->timestamp_created = 0; @@ -625,58 +829,61 @@ uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t } } - n->l3_id = make_l3_id_v6(l2->ifindex, (uint8_t)loc); + n->l3_id = (l3_id_t)(MAX_IPV4_L3_INTERFACES + g + 1); + if (!n->is_localhost && cfg != IPV6_CFG_DISABLE) { + n->routing_table = ipv6_rt_create(n->l3_id); + if (!n->routing_table) { + memset(&g_v6[g], 0, sizeof(g_v6[g])); + return 0; + } + ipv6_rt_ensure_basics((ipv6_rt_table_t*)n->routing_table, n->ip, n->prefix_len, n->gateway, l2->base_metric); + } + + n->epoch = next_l3_epoch(); + n->generation = next_l3_generation(); l2->l3_v6[loc] = n; + g_v6[g].used = true; l2->ipv6_count++; - if (!g_kmem_page_v6) g_kmem_page_v6 = palloc(PAGE_SIZE*1, MEM_PRIV_KERNEL, MEM_RW|MEM_NORM, false); - if (!g_kmem_page_v6) return NULL; - - n->port_manager = (port_manager_t*)kalloc(g_kmem_page_v6, sizeof(port_manager_t), ALIGN_16B, MEM_PRIV_KERNEL); - if (!n->port_manager){ - l2->l3_v6[loc] = NULL; - if (l2->ipv6_count) l2->ipv6_count--; - g_v6[g].used = false; - memset(&g_v6[g], 0, sizeof(g_v6[g])); - return 0; - } - port_manager_init(n->port_manager); - if (cfg == IPV6_CFG_DHCPV6){ - uint8_t m[16]; - ipv6_make_multicast(2, IPV6_MCAST_DHCPV6_SERVERS, NULL, m); - (void)l2_ipv6_mcast_join(ifindex, m); - } - n->routing_table = NULL; - if (!n->is_localhost) { - n->routing_table = ipv6_rt_create(); - if (n->routing_table){ - ipv6_rt_ensure_basics((ipv6_rt_table_t*)n->routing_table, n->ip, n->prefix_len, n->gateway, l2->base_metric); - } - + if (!n->is_localhost && n->cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(n->ip) && !ipv6_is_placeholder_gua(n->ip)) { uint8_t m[16]; - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, m); - (void)l2_ipv6_mcast_join(ifindex, m); - - if (n->cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(n->ip) && !ipv6_is_placeholder_gua(n->ip)) { - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, n->ip, m); - (void)l2_ipv6_mcast_join(ifindex, m); + ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, n->ip, m); + if (!l2_ipv6_mcast_join(ifindex, m)) { + if (n->routing_table) ipv6_rt_destroy((ipv6_rt_table_t*)n->routing_table); + l2->l3_v6[loc] = NULL; + l2->ipv6_count--; + memset(&g_v6[g], 0, sizeof(g_v6[g])); + return 0; } } + if (!had_active_v6 && l2->link_kind != NET_LINK_LOOPBACK && l2_has_active_v6(l2)) { + for (int i = 0; i < (int)pre_mcast_count && i < (int)l2->ipv6_mcast_count; i++) mld_send_join(l2->ifindex, l2->ipv6_mcast[i]); + } + if (n->dad_requested) ndp_daemon_kick(); + if (n->cfg & IPV6_CFG_DHCPV6) dhcpv6_daemon_kick(); + dns_daemon_kick(); return n->l3_id; } -bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind){ +bool l3_ipv6_update(l3_id_t l3_id, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind) { l3_ipv6_interface_t *n = l3_ipv6_find_by_id(l3_id); if (!n) return false; l2_interface_t *l2 = n->l2; if (!l2) return false; + if (l2->link_kind != NET_LINK_LOOPBACK && cfg != IPV6_CFG_DISABLE) { + uint16_t device_mtu = network_get_device_mtu(l2->ifindex); + if (device_mtu && device_mtu < 1280) return false; + } + bool had_active_v6 = l2_has_active_v6(l2); + uint8_t pre_mcast_count = l2->ipv6_mcast_count; if (prefix_len > 128) return false; if (kind == n->kind && cfg == n->cfg && prefix_len == n->prefix_len && ipv6_cmp(ip, n->ip) == 0 && ipv6_cmp(gw, n->gateway) == 0) return true; + bool l3_changed = kind != n->kind || cfg != n->cfg || prefix_len != n->prefix_len || ipv6_cmp(ip, n->ip) != 0; if ((n->kind & IPV6_ADDRK_LINK_LOCAL) && cfg == IPV6_CFG_DISABLE){ - for (int i=0;il2 || x->l2->ifindex != l2->ifindex) continue; @@ -689,7 +896,7 @@ bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, con if (!ipv6_is_linklocal(ip)) return false; } if (!ipv6_is_unspecified(ip) && ipv6_cmp(ip, n->ip)!=0 && v6_ip_exists_anywhere(ip)) return false; - for (int i=0;ikind != NET_IFK_LOCALHOST)) return false; - if (ipv6_cmp(ip,n->ip)!=0 && v6_ip_exists_anywhere(ip)) return false; - if (v6_overlap_intra_l2(l2->ifindex, ip, prefix_len)){ - for (int i=0;il2 || x->l2->ifindex != l2->ifindex) continue; - if (ipv6_is_unspecified(x->ip)) continue; - uint8_t minp = (x->prefix_len < prefix_len) ? x->prefix_len : prefix_len; - int eq = 1; - int fb=minp/8, rb=minp%8; - for (int b=0;bip[b]) {eq=0;break;} } - if (eq && rb){ - uint8_t m=(uint8_t)(0xFF<<(8-rb)); - if ( (ip[fb]&m) != (x->ip[fb]&m) ) eq=0; - } - if (eq) return false; - } + if (ipv6_is_loopback(ip) && (l2->link_kind != NET_LINK_LOOPBACK)) return false; + if (!ipv6_is_placeholder_gua(ip)) { + if (ipv6_cmp(ip,n->ip)!=0 && v6_ip_exists_anywhere(ip)) return false; + if (v6_overlap_intra_l2(l2->ifindex, ip, prefix_len, n)) return false; } } } else { @@ -728,53 +920,45 @@ bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, con } uint8_t old_ip[16]; + uint8_t old_gateway[16]; ipv6_cpy(old_ip, n->ip); + ipv6_cpy(old_gateway, n->gateway); + uint8_t old_prefix_len = n->prefix_len; + ipv6_cfg_t old_cfg = n->cfg; + bool old_active = old_cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(old_ip); + bool new_active = cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(ip); + bool identity_changed = ipv6_cmp(old_ip, ip) != 0 || old_active != new_active || kind != n->kind; + if (cfg != IPV6_CFG_DISABLE && l2->link_kind != NET_LINK_LOOPBACK && !l2_prepare_ndp(l2->ifindex)) return false; + bool needs_route = !n->is_localhost && cfg != IPV6_CFG_DISABLE; + ipv6_rt_table_t *new_rt = NULL; + if (needs_route && !n->routing_table) { + new_rt = ipv6_rt_create(n->l3_id); + if (!new_rt) return false; + } - n->cfg = cfg; - n->kind = kind; - uint8_t m[16]; - - ipv6_make_multicast(2, IPV6_MCAST_DHCPV6_SERVERS, NULL, m); - (void)l2_ipv6_mcast_leave(l2->ifindex, m); - - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, m); - (void)l2_ipv6_mcast_leave(l2->ifindex, m); - - ipv6_make_multicast(2, IPV6_MCAST_ALL_ROUTERS, NULL, m); - (void)l2_ipv6_mcast_leave(l2->ifindex, m); - - if (!ipv6_is_unspecified(old_ip) && !ipv6_is_placeholder_gua(old_ip)) { - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, old_ip, m); - (void)l2_ipv6_mcast_leave(l2->ifindex, m); + bool old_has_sn = !n->is_localhost && old_cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(old_ip) && !ipv6_is_placeholder_gua(old_ip); + bool new_has_sn = !n->is_localhost && cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(ip) && !ipv6_is_placeholder_gua(ip); + uint8_t old_sn[16] = {0}, new_sn[16] = {0}; + if (old_has_sn) ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, old_ip, old_sn); + if (new_has_sn) ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, ip, new_sn); + bool same_sn = old_has_sn && new_has_sn && ipv6_cmp(old_sn, new_sn) == 0; + if (new_has_sn && !same_sn && !l2_ipv6_mcast_join(l2->ifindex, new_sn)) { + if (new_rt) ipv6_rt_destroy(new_rt); + return false; } + if (ipv6_cmp(old_gateway, gw) != 0) l3_changed = true; + if (identity_changed) tcp_l3_retire(n->l3_id, n->generation); + + n->cfg = cfg; + n->kind = kind; ipv6_cpy(n->ip, ip); n->prefix_len = prefix_len; ipv6_cpy(n->gateway, gw); - if (!n->is_localhost) { - if (cfg != IPV6_CFG_DISABLE) { - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, m); - (void)l2_ipv6_mcast_join(l2->ifindex, m); - } - - if (cfg == IPV6_CFG_DHCPV6){ - ipv6_make_multicast(2, IPV6_MCAST_DHCPV6_SERVERS, NULL, m); - (void)l2_ipv6_mcast_join(l2->ifindex, m); - } - - if (cfg == IPV6_CFG_SLAAC){ - ipv6_make_multicast(2, IPV6_MCAST_ALL_ROUTERS, NULL, m); - (void)l2_ipv6_mcast_join(l2->ifindex, m); - } - - if (cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(n->ip) && !ipv6_is_placeholder_gua(n->ip)) { - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, n->ip, m); - (void)l2_ipv6_mcast_join(l2->ifindex, m); - } - } + if (old_has_sn && !same_sn) (void)l2_ipv6_mcast_leave(l2->ifindex, old_sn); - if (ipv6_cmp(old_ip, n->ip) != 0) { + if (ipv6_cmp(old_ip, n->ip) != 0 || (old_cfg == IPV6_CFG_DISABLE) != (n->cfg == IPV6_CFG_DISABLE)) { n->dad_state = IPV6_DAD_NONE; n->dad_timer_ms = 0; n->dad_probes_sent = 0; @@ -782,7 +966,7 @@ bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, con if (n->is_localhost) { n->dad_requested = 0; n->dad_state = IPV6_DAD_OK; - } else if (ipv6_is_unspecified(n->ip) || ipv6_is_multicast(n->ip) || n->cfg == IPV6_CFG_DISABLE) { + } else if (ipv6_is_unspecified(n->ip) || ipv6_is_multicast(n->ip) || ipv6_is_placeholder_gua(n->ip) || n->cfg == IPV6_CFG_DISABLE) { n->dad_requested = 0; } else { n->dad_requested = 1; @@ -790,56 +974,75 @@ bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, con } if (!n->is_localhost) { - if (n->dad_requested && !ipv6_is_placeholder_gua(n->ip)) { - uint8_t sn[16]; - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, n->ip, sn); - (void)l2_ipv6_mcast_join(l2->ifindex, sn); - } - - if (!n->routing_table) n->routing_table = ipv6_rt_create(); - if (n->routing_table){ + if (needs_route) { + if (new_rt) n->routing_table = new_rt; + if (old_prefix_len && !ipv6_is_unspecified(old_ip)) { + uint8_t old_net[16]; + uint8_t new_net[16]; + ipv6_prefix_network(old_ip, old_prefix_len, old_net); + if (n->prefix_len && !ipv6_is_unspecified(n->ip)) ipv6_prefix_network(n->ip, n->prefix_len, new_net); + if (!n->prefix_len || ipv6_is_unspecified(n->ip) || old_prefix_len != n->prefix_len || ipv6_cmp(old_net, new_net) != 0) { + ipv6_rt_del_in((ipv6_rt_table_t*)n->routing_table, old_net, old_prefix_len); + } + } ipv6_rt_sync_basics((ipv6_rt_table_t*)n->routing_table, n->ip, n->prefix_len, n->gateway, l2->base_metric); - } - } else { - if (n->routing_table) { + } else if (n->routing_table) { ipv6_rt_destroy((ipv6_rt_table_t*)n->routing_table); n->routing_table = NULL; } + } else if (n->routing_table) { + ipv6_rt_destroy((ipv6_rt_table_t*)n->routing_table); + n->routing_table = NULL; + } + if (!had_active_v6 && l2->link_kind != NET_LINK_LOOPBACK && l2_has_active_v6(l2)) { + for (int i = 0; i < (int)pre_mcast_count && i < (int)l2->ipv6_mcast_count; i++) mld_send_join(l2->ifindex, l2->ipv6_mcast[i]); } + if (l3_changed) { + n->epoch = next_l3_epoch(); + if (identity_changed) n->generation = next_l3_generation(); + } + if (n->dad_requested) ndp_daemon_kick(); + if (n->cfg & IPV6_CFG_DHCPV6) dhcpv6_daemon_kick(); + if (identity_changed) dns_daemon_kick(); return true; } -bool l3_ipv6_remove_from_interface(uint8_t l3_id){ +bool l3_ipv6_remove_from_interface(l3_id_t l3_id) { l3_ipv6_interface_t *n = l3_ipv6_find_by_id(l3_id); if (!n) return false; l2_interface_t *l2 = n->l2; if (!l2) return false; if ((n->kind & IPV6_ADDRK_LINK_LOCAL)){ - for (int i=0;il2 || x->l2->ifindex != l2->ifindex) continue; if ((x->kind & IPV6_ADDRK_GLOBAL) && x->cfg != IPV6_CFG_DISABLE) return false; } } - if (l2->ipv6_count <= 1) return false; + if (l2->ipv6_count <= 1 && l2->link_kind != NET_LINK_VLAN) return false; int g = -1; - for (int i=0;iport_manager) { - kfree(n->port_manager, sizeof(port_manager_t)); - n->port_manager = NULL; + tcp_l3_retire(n->l3_id, n->generation); + socket_core_l3_deleted(n->l3_id); + + if (!n->is_localhost && n->cfg != IPV6_CFG_DISABLE && !ipv6_is_unspecified(n->ip) && !ipv6_is_placeholder_gua(n->ip)) { + uint8_t sn[16]; + ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, n->ip, sn); + (void)l2_ipv6_mcast_leave(l2->ifindex, sn); } - uint8_t slot = l3_slot_from_id(l3_id); - if (slot < MAX_IPV6_PER_INTERFACE && l2->l3_v6[slot] == n){ + for (int slot = 0; slot < MAX_IPV6_PER_INTERFACE; slot++) { + if (l2->l3_v6[slot] != n) continue; l2->l3_v6[slot] = NULL; if (l2->ipv6_count) l2->ipv6_count--; + break; } if (n->routing_table){ @@ -849,59 +1052,77 @@ bool l3_ipv6_remove_from_interface(uint8_t l3_id){ g_v6[g].used = false; memset(&g_v6[g], 0, sizeof(g_v6[g])); + dns_daemon_kick(); return true; } -bool l3_ipv6_set_enabled(uint8_t l3_id, bool enable){ - l3_ipv6_interface_t *n = l3_ipv6_find_by_id(l3_id); - if (!n) return false; - if (enable){ - return true; - } else { - if ((n->kind & IPV6_ADDRK_LINK_LOCAL)){ - l2_interface_t *l2 = n->l2; - for (int i=0;il2 || x->l2->ifindex != l2->ifindex) continue; - if ((x->kind & IPV6_ADDRK_GLOBAL) && x->cfg != IPV6_CFG_DISABLE) return false; - } - } - n->cfg = IPV6_CFG_DISABLE; - n->dad_state = IPV6_DAD_NONE; - n->dad_probes_sent = 0; - n->dad_timer_ms = 0; - return true; - } -} - -l3_ipv6_interface_t* l3_ipv6_find_by_id(uint8_t l3_id){ - if (!l3_id || !l3_is_v6_from_id(l3_id)) return NULL; - uint8_t ifx = l3_ifindex_from_id(l3_id); - uint8_t loc = l3_slot_from_id(l3_id); - l2_interface_t *l2 = l2_interface_find_by_index(ifx); - if (!l2) return NULL; - if (loc >= MAX_IPV6_PER_INTERFACE) return NULL; - return l2->l3_v6[loc]; +l3_ipv6_interface_t* l3_ipv6_find_by_id(l3_id_t l3_id) { + if (l3_id <= MAX_IPV4_L3_INTERFACES || l3_id > MAX_L3_INTERFACES) return NULL; + v6_slot_t *slot = &g_v6[l3_id - MAX_IPV4_L3_INTERFACES - 1]; + return slot->used ? &slot->node : NULL; } l3_ipv6_interface_t* l3_ipv6_find_by_ip(const uint8_t ip[16]){ - for (int i=0;il2) return 0; + uint16_t device_mtu = network_get_device_mtu(l3->l2->ifindex); + if (!device_mtu) return 0; + uint16_t mtu = device_mtu; + if (l3->l2->ipv6_link_mtu && l3->l2->ipv6_link_mtu < mtu) mtu = l3->l2->ipv6_link_mtu; + return mtu; +} + +uint32_t l3_epoch(l3_id_t l3_id) { + if (!l3_id) return 0; + if (l3_id <= MAX_IPV4_L3_INTERFACES) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); + if (v4) return v4->epoch; + return 0; + } + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (v6) return v6->epoch; + return 0; +} + +bool l2_ipv6_set_link_mtu(uint8_t ifindex, uint16_t mtu) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || mtu < 1280) return false; + + uint16_t device_mtu = network_get_device_mtu(ifindex); + if (!device_mtu || mtu > device_mtu) return false; + if (l2->ipv6_link_mtu == mtu) return true; + + uint16_t old_mtu[MAX_IPV6_PER_INTERFACE] = {0}; + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) if (l2->l3_v6[i]) old_mtu[i] = l3_ipv6_effective_mtu(l2->l3_v6[i]); + + l2->ipv6_link_mtu = mtu; + uint32_t epoch = next_l3_epoch(); + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6) continue; + uint16_t new_mtu = l3_ipv6_effective_mtu(v6); + v6->epoch = epoch; + if (new_mtu && old_mtu[i] && new_mtu < old_mtu[i]) tcp_l3_mtu_reduce(v6->l3_id, v6->generation, new_mtu); + } + return true; +} + void l3_init_localhost_ipv4(void){ l2_interface_t *lo = NULL; for (int i=0;i<(int)MAX_L2_INTERFACES;i++){ if (!g_l2_used[i]) continue; - if (g_l2[i].kind == NET_IFK_LOCALHOST) { + if (g_l2[i].link_kind == NET_LINK_LOOPBACK) { lo = &g_l2[i]; break; } } if (!lo) return; - for (int i=0;iifindex, loop6, 128, zero16, IPV6_CFG_STATIC, IPV6_ADDRK_GLOBAL); - - uint8_t multi[16]; - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, loop6, multi); - (void)l2_ipv6_mcast_join(lo->ifindex, multi); - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, loop6, multi); - (void)l2_ipv6_mcast_join(lo->ifindex, multi); + (void)l3_ipv6_add_to_interface(lo->ifindex, loop6, 128, (const uint8_t[16]){0}, IPV6_CFG_STATIC, IPV6_ADDRK_GLOBAL); } //TODO: add autoconfig settings/policy @@ -940,22 +1154,22 @@ void ifmgr_autoconfig_l2(uint8_t ifindex){ l2_interface_t *l2 = l2_interface_find_by_index(ifindex); if (!l2) return; - if (l2->kind == NET_IFK_LOCALHOST) return; + if (l2->link_kind == NET_LINK_LOOPBACK) return; - if (l2->ipv4_count == 0){ + if (!l2->suppress_auto_v4 && l2->ipv4_count == 0){ (void)l3_ipv4_add_to_interface(ifindex, 0, 0, 0, IPV4_CFG_DHCP, NULL); } bool has_lla=false; bool has_gua=false; - for (int i=0;il2 || x->l2->ifindex != ifindex) continue; - if (!has_lla) if (ipv6_is_linklocal(x->ip) && x->cfg != IPV6_CFG_DISABLE) has_lla=true; + if (!has_lla && ipv6_is_linklocal(x->ip) && x->cfg != IPV6_CFG_DISABLE) has_lla = true; if (!has_gua) { if ((x->kind == IPV6_ADDRK_GLOBAL) && x->cfg != IPV6_CFG_DISABLE) has_gua=true; @@ -966,22 +1180,17 @@ void ifmgr_autoconfig_l2(uint8_t ifindex){ } if (!has_lla){ uint8_t lla[16]; - uint8_t zero16[16] = {0}; ipv6_make_lla_from_mac(ifindex, lla); - (void)l3_ipv6_add_to_interface(ifindex, lla, 64, zero16, IPV6_CFG_SLAAC, IPV6_ADDRK_LINK_LOCAL); + (void)l3_ipv6_add_to_interface(ifindex, lla, 64, (const uint8_t[16]){0}, IPV6_CFG_SLAAC, IPV6_ADDRK_LINK_LOCAL); - uint8_t m[16]; - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, lla, m); - (void)l2_ipv6_mcast_join(ifindex, m); } - if (!has_gua) { + if (!has_gua && !l2->suppress_auto_v6) { uint8_t ph[16]; - uint8_t zero16[16]={0}; ipv6_make_placeholder_gua(ph); - (void)l3_ipv6_add_to_interface(ifindex, ph, 64, zero16, IPV6_CFG_SLAAC, IPV6_ADDRK_GLOBAL); + (void)l3_ipv6_add_to_interface(ifindex, ph, 64, (const uint8_t[16]){0}, IPV6_CFG_STATELESS, IPV6_ADDRK_GLOBAL); } } @@ -992,90 +1201,32 @@ void ifmgr_autoconfig_all_l2(void){ } } -ip_resolution_result_t resolve_ipv4_to_interface(uint32_t dst_ip){ - ip_resolution_result_t r; r.found=false; r.ipv4=NULL; r.ipv6=NULL; r.l2=NULL; - int best_plen = -1; - for (int i=0;il2) continue; - if (x->mode == IPV4_CFG_DISABLED) continue; - uint32_t m = x->mask; - if (m==0){ - if (x->ip == dst_ip && best_plen < 32){ best_plen = 32; r.found=true; r.ipv4=x; r.l2=x->l2; } - continue; - } - if (ipv4_net(dst_ip, m) == ipv4_net(x->ip, m)){ - int plen=0; uint32_t tmp=m; - while (tmp){ plen += (tmp & 1u); tmp >>= 1; } - if (plen > best_plen){ best_plen = plen; r.found=true; r.ipv4=x; r.l2=x->l2; } - } - } +ip_resolution_result_t resolve_ipv4_to_interface(uint32_t dst_ip) { + ip_resolution_result_t r = {0}; + l3_id_t chosen = 0; + if (!ipv4_rt_pick_best_l3(dst_ip, 0, &chosen)) return r; + + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(chosen); + if (!ipv4_l3_is_ready(v4)) return r; + + r.found = true; + r.ipv4 = v4; + r.l2 = v4->l2; return r; } ip_resolution_result_t resolve_ipv6_to_interface(const uint8_t dst_ip[16]) { - ip_resolution_result_t r; - r.found = false; - r.ipv4 = NULL; - r.ipv6 = NULL; - r.l2= NULL; + ip_resolution_result_t r = {0}; + if (!dst_ip) return r; - int dst_is_ll = ipv6_is_linklocal(dst_ip); - int best_pl = -1; - uint16_t best_cost = 0x7FFF; + l3_id_t chosen = 0; + if (!ipv6_rt_pick_best_l3(dst_ip, 0, &chosen)) return r; - for (int i = 0; i < V6_POOL_SIZE; i++) { - if (!g_v6[i].used) continue; - l3_ipv6_interface_t *x = &g_v6[i].node; - if (!x->l2) continue; - if (x->cfg == IPV6_CFG_DISABLE) continue; - if (ipv6_is_unspecified(x->ip)) continue; - - int src_is_ll = ipv6_is_linklocal(x->ip); - - if (dst_is_ll != src_is_ll) - continue; - - int pl_conn = -1; - int match = ipv6_common_prefix_len(dst_ip, x->ip); - if (match >= x->prefix_len) pl_conn = x->prefix_len; - - int pl_tab = -1; - uint16_t met_tab = 0x7FFF; - uint8_t via[16] = {0}; - - if (x->routing_table) { - int out_pl = -1; - int out_met = 0x7FFF; - if (ipv6_rt_lookup_in((const ipv6_rt_table_t*)x->routing_table,dst_ip, via, &out_pl, &out_met)) - { - pl_tab = out_pl; - met_tab = out_met; - } - } - - int cand_pl = pl_conn; - uint16_t cand_cost = x->l2->base_metric; - - if (pl_tab > cand_pl || (pl_tab == cand_pl && (x->l2->base_metric + met_tab) < cand_cost)) { - cand_pl = pl_tab; - cand_cost = x->l2->base_metric + met_tab; - } - - if (cand_pl > best_pl || (cand_pl == best_pl && cand_cost < best_cost)) { - best_pl = cand_pl; - best_cost = cand_cost; - r.found = true; - r.ipv6 = x; - r.l2 = x->l2; - } - } - if (best_pl < 0) { - r.found = false; - r.ipv6 = NULL; - r.l2 = NULL; - } + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(chosen); + if (!ipv6_l3_is_ready(v6)) return r; + r.found = true; + r.ipv6 = v6; + r.l2 = v6->l2; return r; } \ No newline at end of file diff --git a/kernel/networking/interface_manager.h b/kernel/networking/interface_manager.h index 7786b16d..9e63ad52 100644 --- a/kernel/networking/interface_manager.h +++ b/kernel/networking/interface_manager.h @@ -1,42 +1,22 @@ #pragma once #include "types.h" -#include "networking/port_manager.h" +#include "net/interface_types.h" +#include "net/network_types.h" +#include "networking/link_layer/nic_types.h" #ifdef __cplusplus extern "C" { #endif -#define MAX_L2_INTERFACES 15 +#define MAX_L2_INTERFACES 32 #define MAX_IPV4_PER_INTERFACE 4 #define MAX_IPV6_PER_INTERFACE 4 -#define MAX_IPV4_MCAST_PER_INTERFACE 12 -#define MAX_IPV6_MCAST_PER_INTERFACE 12 - -typedef enum { - IPV4_CFG_DISABLED = -1, - IPV4_CFG_DHCP = 0, - IPV4_CFG_STATIC = 1 -} ipv4_cfg_t; - -typedef enum { - IPV6_DAD_NONE = 0, - IPV6_DAD_IN_PROGRESS = 1, - IPV6_DAD_FAILED = 2, - IPV6_DAD_OK = 3 -} ipv6_dad_state_t; - -typedef enum { - IPV6_ADDRK_GLOBAL = 0x01, - IPV6_ADDRK_LINK_LOCAL = 0x02 -} ipv6_addr_kind_t; - -typedef enum { - IPV6_CFG_DISABLE = -1, - IPV6_CFG_STATIC = 0x01, - IPV6_CFG_SLAAC = 0x02, - IPV6_CFG_DHCPV6 = 0x04 -} ipv6_cfg_t; +#define MAX_L3_INTERFACES (MAX_L2_INTERFACES * (MAX_IPV4_PER_INTERFACE + MAX_IPV6_PER_INTERFACE)) +#define MAX_IPV4_L3_INTERFACES (MAX_L2_INTERFACES * MAX_IPV4_PER_INTERFACE) +#define MAX_IPV6_L3_INTERFACES (MAX_L2_INTERFACES * MAX_IPV6_PER_INTERFACE) +#define MAX_IPV4_MCAST_PER_INTERFACE 16 +#define MAX_IPV6_MCAST_PER_INTERFACE 32 struct l2_interface; struct l3_ipv4_interface; @@ -56,11 +36,19 @@ typedef struct net_runtime_opts { typedef struct l2_interface { uint8_t ifindex; + uint8_t nic_id; + uint8_t parent_ifindex; + uint16_t vlan_id; + uint32_t generation; char name[16]; bool is_up; + bool suppress_auto_v4; + bool suppress_auto_v6; uint16_t base_metric; - uint8_t kind; - void *driver_context; + NetDevKind dev_kind; + NetLinkKind link_kind; + uint8_t ipv6_default_hop_limit; + uint16_t ipv6_link_mtu; void *arp_table; void *nd_table; struct l3_ipv4_interface *l3_v4[MAX_IPV4_PER_INTERFACE]; @@ -68,13 +56,17 @@ typedef struct l2_interface { uint8_t ipv4_count; uint8_t ipv6_count; uint32_t ipv4_mcast[MAX_IPV4_MCAST_PER_INTERFACE]; + uint16_t ipv4_mcast_ref[MAX_IPV4_MCAST_PER_INTERFACE]; uint8_t ipv4_mcast_count; uint8_t ipv6_mcast[MAX_IPV6_MCAST_PER_INTERFACE][16]; + uint16_t ipv6_mcast_ref[MAX_IPV6_MCAST_PER_INTERFACE]; uint8_t ipv6_mcast_count; } l2_interface_t; typedef struct l3_ipv4_interface { - uint8_t l3_id; + l3_id_t l3_id; + uint32_t epoch; + uint32_t generation; uint32_t ip; uint32_t mask; uint32_t gw; @@ -83,7 +75,6 @@ typedef struct l3_ipv4_interface { bool is_localhost; net_runtime_opts_t runtime_opts_v4; void *routing_table; - port_manager_t *port_manager; l2_interface_t *l2; } l3_ipv4_interface_t; @@ -97,7 +88,7 @@ typedef struct net_runtime_opts_v6 { uint32_t iaid; uint32_t lease; - uint32_t lease_start_time; + uint64_t lease_start_time; uint16_t server_id_len; uint8_t server_id[128]; @@ -109,8 +100,9 @@ typedef struct net_runtime_opts_v6 { } net_runtime_opts_v6_t; typedef struct l3_ipv6_interface { - uint8_t l3_id; - uint16_t mtu; + l3_id_t l3_id; + uint32_t epoch; + uint32_t generation; uint8_t ip[16]; uint8_t prefix_len; @@ -128,11 +120,9 @@ typedef struct l3_ipv6_interface { uint8_t dad_probes_sent; uint32_t dad_timer_ms; void *routing_table; - port_manager_t *port_manager; l2_interface_t *l2; uint8_t ra_has; uint8_t ra_autonomous; - uint8_t ra_is_default; uint8_t ra_flags; uint8_t dhcpv6_stateless; uint8_t dhcpv6_stateless_done; @@ -149,30 +139,40 @@ typedef struct ip_resolution_result { l2_interface_t *l2; } ip_resolution_result_t; -uint8_t l2_interface_create(const char *name, void *driver_ctx, uint16_t base_metric, uint8_t kind); +uint8_t l2_interface_create(const char *name, uint8_t nic_id, uint16_t base_metric, NetDevKind dev_kind); bool l2_interface_destroy(uint8_t ifindex); +uint8_t l2_vlan_create(uint8_t parent_ifindex, uint16_t vlan_id, const char *name); +l2_interface_t *l2_vlan_find(uint8_t parent_ifindex, uint16_t vlan_id); +bool l2_vlan_destroy(uint8_t ifindex); l2_interface_t *l2_interface_find_by_index(uint8_t ifindex); uint8_t l2_interface_count(void); l2_interface_t *l2_interface_at(uint8_t idx); bool l2_interface_set_up(uint8_t ifindex, bool up); +bool l2_interface_set_metric(uint8_t ifindex, uint16_t metric); +bool l2_interface_is_operational(const l2_interface_t* l2); +bool l2_prepare_arp(uint8_t ifindex); +bool l2_prepare_ndp(uint8_t ifindex); bool l2_ipv4_mcast_join(uint8_t ifindex, uint32_t group); bool l2_ipv4_mcast_leave(uint8_t ifindex, uint32_t group); bool l2_ipv6_mcast_join(uint8_t ifindex, const uint8_t group[16]); bool l2_ipv6_mcast_leave(uint8_t ifindex, const uint8_t group[16]); -uint8_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *rt); -bool l3_ipv4_update(uint8_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *rt); -bool l3_ipv4_remove_from_interface(uint8_t l3_id); -l3_ipv4_interface_t *l3_ipv4_find_by_id(uint8_t l3_id); +l3_id_t l3_ipv4_add_to_interface(uint8_t ifindex, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *rt); +bool l3_ipv4_update(l3_id_t l3_id, uint32_t ip, uint32_t mask, uint32_t gw, ipv4_cfg_t mode, net_runtime_opts_t *rt); +bool l3_ipv4_remove_from_interface(l3_id_t l3_id); +l3_ipv4_interface_t *l3_ipv4_find_by_id(l3_id_t l3_id); l3_ipv4_interface_t *l3_ipv4_find_by_ip(uint32_t ip); +uint16_t l3_ipv4_effective_mtu(const l3_ipv4_interface_t *l3); -uint8_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind); -bool l3_ipv6_update(uint8_t l3_id, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind); -bool l3_ipv6_remove_from_interface(uint8_t l3_id); -bool l3_ipv6_set_enabled(uint8_t l3_id, bool enable); -l3_ipv6_interface_t *l3_ipv6_find_by_id(uint8_t l3_id); +l3_id_t l3_ipv6_add_to_interface(uint8_t ifindex, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind); +bool l3_ipv6_update(l3_id_t l3_id, const uint8_t ip[16], uint8_t prefix_len, const uint8_t gw[16], ipv6_cfg_t cfg, uint8_t kind); +bool l3_ipv6_remove_from_interface(l3_id_t l3_id); +l3_ipv6_interface_t *l3_ipv6_find_by_id(l3_id_t l3_id); l3_ipv6_interface_t *l3_ipv6_find_by_ip(const uint8_t ip[16]); +uint16_t l3_ipv6_effective_mtu(const l3_ipv6_interface_t *l3); +uint32_t l3_epoch(l3_id_t l3_id); +bool l2_ipv6_set_link_mtu(uint8_t ifindex, uint16_t mtu); void l3_init_localhost_ipv4(void); void l3_init_localhost_ipv6(void); @@ -183,20 +183,11 @@ void ifmgr_autoconfig_l2(uint8_t ifindex); ip_resolution_result_t resolve_ipv4_to_interface(uint32_t dst_ip); ip_resolution_result_t resolve_ipv6_to_interface(const uint8_t dst_ip[16]); -static inline port_manager_t* ifmgr_pm_v4(uint8_t l3_id){ - l3_ipv4_interface_t* n = l3_ipv4_find_by_id(l3_id); - return n ? n->port_manager : NULL; +static inline uint8_t l2_physical_ifindex(const l2_interface_t *l2) { + if (!l2) return 0; + if (l2->parent_ifindex) return l2->parent_ifindex; + return l2->ifindex; } -static inline port_manager_t* ifmgr_pm_v6(uint8_t l3_id){ - l3_ipv6_interface_t* n = l3_ipv6_find_by_id(l3_id); - return n ? n->port_manager : NULL; -} - -static inline uint8_t make_l3_id_v4(uint8_t ifindex, uint8_t local_slot){ return (uint8_t)((ifindex<<4) | (local_slot & 0x03)); } -static inline uint8_t make_l3_id_v6(uint8_t ifindex, uint8_t local_slot){ return (uint8_t)((ifindex<<4) | 0x08 | (local_slot & 0x03)); } -static inline uint8_t l3_ifindex_from_id(uint8_t l3_id){ return (uint8_t)((l3_id >> 4) & 0x0F); } -static inline uint8_t l3_is_v6_from_id(uint8_t l3_id){ return (uint8_t)((l3_id & 0x08) ? 1 : 0); } -static inline uint8_t l3_slot_from_id(uint8_t l3_id){ return (uint8_t)(l3_id & 0x03); } #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/icmp.c b/kernel/networking/internet_layer/icmp.c index fc4a91fc..27ffd001 100644 --- a/kernel/networking/internet_layer/icmp.c +++ b/kernel/networking/internet_layer/icmp.c @@ -1,247 +1,154 @@ #include "networking/internet_layer/icmp.h" #include "net/checksums.h" #include "std/std.h" -#include "console/kio.h" #include "networking/interface_manager.h" #include "networking/internet_layer/ipv4.h" -#include "syscalls/syscalls.h" - -#define MAX_PENDING 16 - -typedef struct { - bool in_use; - uint16_t id; - uint16_t seq; - bool received; - uint8_t rx_type; - uint8_t rx_code; - uint32_t start_ms; - uint32_t end_ms; - uint32_t rx_src_ip; -} ping_slot_t; - -static ping_slot_t g_pending[MAX_PENDING] = {0}; - -static int alloc_slot(uint16_t id, uint16_t seq) { - for (int i = 0; i < MAX_PENDING; i++) { - if (!g_pending[i].in_use) { - g_pending[i].in_use = true; - g_pending[i].id = id; - g_pending[i].seq = seq; - g_pending[i].received = false; - g_pending[i].rx_type = 0xFF; - g_pending[i].rx_code = 0xFF; - g_pending[i].start_ms = (uint32_t)get_time(); - g_pending[i].end_ms = 0; - g_pending[i].rx_src_ip = 0; - return i; - } +#include "networking/transport_layer/csocket_raw.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/transport_layer/tcp.h" +#include "networking/internet_layer/pmtu.h" +#include "networking/internet_layer/ipv4_utils.h" + +void icmp_send_port_unreachable(l3_id_t l3_id, uint32_t remote_ip, const uint8_t *ip_header, uint8_t ip_header_len, const netpkt_t *l4pkt, uint32_t l4_off, uint32_t l4_len) { + if (!l3_id || !remote_ip || !ip_header || !l4pkt) return; + if (ip_header_len < sizeof(ipv4_hdr_t) || ip_header_len > 60) return; + if (ipv4_is_unspecified(remote_ip) || ipv4_is_multicast(remote_ip) || ipv4_is_limited_broadcast(remote_ip)) return; + + l3_ipv4_interface_t *l3 = l3_ipv4_find_by_id(l3_id); + if (!ipv4_l3_is_ready(l3)) return; + if (l3->mask && ipv4_is_directed_broadcast(l3->ip, l3->mask, remote_ip)) return; + + uint32_t quote_l4 = l4_len < 8 ? l4_len : 8; + uint32_t icmp_len = 8u + ip_header_len + quote_l4; + uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(ipv4_hdr_t); + netpkt_t *reply = netpkt_alloc(icmp_len, headroom, 0); + if (!reply) return; + uint8_t *out = (uint8_t*)netpkt_put(reply, icmp_len); + if (!out) { + netpkt_unref(reply); + return; } - return -1; -} -static void mark_received(uint16_t id, uint16_t seq, uint8_t type, uint8_t code, uint32_t src_ip) { - for (int i = 0; i < MAX_PENDING; i++) { - if (g_pending[i].in_use && g_pending[i].id == id && g_pending[i].seq == seq) { - g_pending[i].received = true; - g_pending[i].rx_type = type; - g_pending[i].rx_code = code; - g_pending[i].end_ms = (uint32_t)get_time(); - g_pending[i].rx_src_ip = src_ip; - return; - } + memset(out, 0, 8); + out[0] = ICMP_DEST_UNREACH; + out[1] = 3; + memcpy(out + 8, ip_header, ip_header_len); + if (quote_l4 && !netpkt_copyout(l4pkt, l4_off, out + 8 + ip_header_len, quote_l4)) { + netpkt_unref(reply); + return; } -} + wr_be16(out + 2u, checksum16(out, icmp_len)); -static uintptr_t build_echo(uint16_t id, uint16_t seq, const uint8_t* payload, uint32_t pay_len, uint32_t* out_total_len) { - uint32_t len = 8 + (pay_len > 56 ? 56 : pay_len); - *out_total_len = len; - uintptr_t buf = (uintptr_t)malloc(8 + 56); - if (!buf) return 0; - - icmp_packet *pkt = (icmp_packet*)buf; - pkt->type = ICMP_ECHO_REQUEST; - pkt->code = 0; - pkt->id = bswap16(id); - pkt->seq = bswap16(seq); - - memset(pkt->payload, 0, 56); - if (payload && pay_len) memcpy(pkt->payload, payload, (pay_len > 56 ? 56 : pay_len)); - pkt->checksum = 0; - pkt->checksum = checksum16((uint16_t*)pkt, (len+1)/2); - return buf; + ip_tx_opts_t tx = {.target = {.l3_id = l3_id}, .scope = IP_TX_BOUND_L3}; + ipv4_send_packet(remote_ip, PROTO_ICMP, reply, &tx, IP_TTL_DEFAULT, 0, 0); } -bool icmp_ping(uint32_t dst_ip, uint16_t id, uint16_t seq, uint32_t timeout_ms, const void* tx_opts_or_null, uint32_t ttl, ping_result_t* out) { - int slot = alloc_slot(id, seq); - if (slot < 0) { - if (out) { - out->rtt_ms = 0; - out->status = PING_UNKNOWN_ERROR; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - out->responder_ip = 0; - } - return false; - } - uint32_t tot_len = 0; - uintptr_t buf = build_echo(id, seq, NULL, 32, &tot_len); - if (!buf) { - if (out) { - out->rtt_ms = 0; - out->status = PING_UNKNOWN_ERROR; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - out->responder_ip = 0; - } - g_pending[slot].in_use = false; - return false; - } +static void icmp_note_frag_needed(uint8_t ifindex, const uint8_t* raw, uint32_t len) { + if (!raw || len < 8 + sizeof(ipv4_hdr_t)) return; - uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(ipv4_hdr_t); - netpkt_t* pkt = netpkt_alloc(tot_len, headroom, 0); - if (!pkt) { - free_sized((void*)buf, 8 + 56); - g_pending[slot].in_use = false; - return false; - } - void* p = netpkt_put(pkt, tot_len); - if (!p) { - netpkt_unref(pkt); - free_sized((void*)buf, 8 + 56); - g_pending[slot].in_use = false; - return false; - } - memcpy(p, (const void*)buf, tot_len); - free_sized((void*)buf, 8 + 56); - ipv4_send_packet(dst_ip, 1, pkt, (const ipv4_tx_opts_t*)tx_opts_or_null, (uint8_t)ttl, 0); - - uint32_t start = (uint32_t)get_time(); - for (;;) { - if (g_pending[slot].received) { - if (out) { - out->icmp_type = g_pending[slot].rx_type; - out->icmp_code = g_pending[slot].rx_code; - out->responder_ip = g_pending[slot].rx_src_ip; - switch (g_pending[slot].rx_type) { - case ICMP_ECHO_REPLY: out->status = PING_OK; break; - case ICMP_DEST_UNREACH: - switch (g_pending[slot].rx_code) { - case 0: out->status = PING_NET_UNREACH; break; - case 1: out->status = PING_HOST_UNREACH; break; - case 2: out->status = PING_PROTO_UNREACH; break; - case 3: out->status = PING_PORT_UNREACH; break; - case 4: out->status = PING_FRAG_NEEDED; break; - case 5: out->status = PING_SRC_ROUTE_FAILED; break; - case 13: out->status = PING_ADMIN_PROHIBITED; break; - default: out->status = PING_UNKNOWN_ERROR; break; - } - break; - case ICMP_TIME_EXCEEDED: out->status = PING_TTL_EXPIRED; break; - case ICMP_PARAM_PROBLEM: out->status = PING_PARAM_PROBLEM; break; - case ICMP_REDIRECT: out->status = PING_REDIRECT; break; - default: out->status = PING_UNKNOWN_ERROR; break; - } + const uint8_t* quoted = raw + 8; + uint8_t version_ihl = quoted[0]; + if ((version_ihl >> 4) != 4) return; - if (g_pending[slot].end_ms >= g_pending[slot].start_ms) out->rtt_ms = g_pending[slot].end_ms - g_pending[slot].start_ms; - else out->rtt_ms = 0; - } - bool ok = (g_pending[slot].rx_type == ICMP_ECHO_REPLY); - g_pending[slot].in_use = false; - return ok; - } + uint32_t ihl = (uint32_t)(version_ihl & 0x0F) * 4; + if (ihl < sizeof(ipv4_hdr_t) || 8 + ihl > len) return; - uint32_t now = (uint32_t)get_time(); - if (now - start >= timeout_ms) break; - msleep(5); - } + ipv4_hdr_t inner; + memcpy(&inner, quoted, sizeof(inner)); + uint32_t local_ip = bswap32(inner.src_ip); + uint32_t remote_ip = bswap32(inner.dst_ip); + l3_ipv4_interface_t* l3 = l3_ipv4_find_by_ip(local_ip); + if (!ipv4_l3_is_ready(l3) || !l3->l2 || l3->l2->ifindex != ifindex) return; + if (!remote_ip || ipv4_is_multicast(remote_ip) || ipv4_is_limited_broadcast(remote_ip)) return; + + uint16_t mtu = rd_be16(raw + 6); + if (!mtu) { + static const uint16_t plateau[] = {65535, 32000, 17914, 8166, 4352, 2002, 1492, 1006, 508, 296, 68}; + uint16_t original_len = bswap16(inner.total_length); + if (original_len < ihl) return; - if (out) { - out->rtt_ms = 0; - out->status = PING_TIMEOUT; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - out->responder_ip = 0; + mtu = 68; + for (int i = 0; i < (int)N_ARR(plateau); i++) { + if (plateau[i] >= original_len) continue; + mtu = plateau[i]; + break; + } } - g_pending[slot].in_use = false; - return false; + if (mtu < 68) return; + + uint16_t base_mtu = l3_ipv4_effective_mtu(l3); + if (!base_mtu || mtu >= base_mtu) return; + uint16_t path_mtu = pmtu_note(l3->l3_id, l3->epoch, IP_VER4, &remote_ip, mtu); + if (!path_mtu || inner.protocol != PROTO_TCP || len < 8 + ihl + 4) return; + + const uint8_t* tcp = quoted + ihl; + tcp_pmtu_update(l3->l3_id, IP_VER4, &local_ip, &remote_ip, rd_be16(tcp), rd_be16(tcp + 2), path_mtu); } -void icmp_input(uintptr_t ptr, uint32_t len, uint32_t src_ip, uint32_t dst_ip) { - if (len < 8) return; +void icmp_input(uint8_t ifindex, netpkt_t* pkt, uint32_t src_ip, uint32_t dst_ip) { + if (!pkt) return; - icmp_packet* pkt = (icmp_packet*)ptr; - uint16_t recv_ck = pkt->checksum; - pkt->checksum = 0; - uint16_t calc = checksum16((uint16_t*)pkt, (len+1)/2); - pkt->checksum = recv_ck; - if (calc != recv_ck) return; + uint32_t len = netpkt_len(pkt); + if (len < 8) { + netpkt_unref(pkt); + return; + } - uint8_t type = pkt->type; - uint8_t code = pkt->code; - uint16_t id = bswap16(pkt->id); - uint16_t sq = bswap16(pkt->seq); - uint32_t pay = len - 8; - if (pay > 56) pay = 56; + const uint8_t* raw = (const uint8_t*)netpkt_data(pkt); + uint8_t hdr[8]; + if (!netpkt_copyout(pkt, 0, hdr, sizeof(hdr))) { + netpkt_unref(pkt); + return; + } + if (checksum16(raw, len) != 0) { + netpkt_unref(pkt); + return; + } - if (type == ICMP_ECHO_REQUEST) { - uintptr_t buf = (uintptr_t)malloc(8 + 56); - if (!buf) return; - icmp_packet *rp = (icmp_packet*)buf; - rp->type = ICMP_ECHO_REPLY; - rp->code = 0; - rp->id = bswap16(id); - rp->seq = bswap16(sq); - memset(rp->payload, 0, 56); - if (pay) memcpy(rp->payload, pkt->payload, pay); - rp->checksum = 0; - uint32_t rlen = 8 + pay; - rp->checksum = checksum16((uint16_t*)rp, (rlen+1)/2); + socket_raw_input_v4(PROTO_ICMP, ifindex, src_ip, dst_ip, pkt); + uint8_t type = hdr[0]; + if (type == ICMP_ECHO_REQUEST) { l3_ipv4_interface_t* l3 = l3_ipv4_find_by_ip(dst_ip); if (l3 && l3->l2) { - ipv4_tx_opts_t o = {.index = l3->l3_id, .scope = IP_TX_BOUND_L3}; + ip_tx_opts_t o = {.target = {.l3_id = l3->l3_id}, .scope = IP_TX_BOUND_L3}; uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(ipv4_hdr_t); - netpkt_t* pkt = netpkt_alloc(rlen, headroom, 0); - if (pkt) { - void* p = netpkt_put(pkt, rlen); - if (p) { - memcpy(p, (const void*)buf, rlen); - ipv4_send_packet(src_ip, 1, pkt, &o, IP_TTL_DEFAULT, 0); + netpkt_t* reply = netpkt_alloc(len, headroom, 0); + if (reply) { + void* out = netpkt_put(reply, len); + if (out) { + memcpy(out, raw, len); + icmp_echo_hdr_t* rp = (icmp_echo_hdr_t*)out; + rp->type = ICMP_ECHO_REPLY; + rp->code = 0; + rp->checksum = 0; + rp->checksum = bswap16(checksum16(out, len)); + ipv4_send_packet(src_ip, PROTO_ICMP, reply, &o, IP_TTL_DEFAULT, 0, 0); } else { - netpkt_unref(pkt); + netpkt_unref(reply); } } } - free_sized((void*)buf, 8 + 56); + netpkt_unref(pkt); return; } if (type == ICMP_ECHO_REPLY) { - mark_received(id, sq, type, code, src_ip); + netpkt_unref(pkt); return; } - if (type == ICMP_TIME_EXCEEDED || type == ICMP_DEST_UNREACH || type == ICMP_PARAM_PROBLEM || type == ICMP_REDIRECT) { - if (pay >= 28) { - const uint8_t *ip = pkt->payload; - uint8_t ihl = (uint8_t)(ip[0] & 0x0F); - uint32_t iphdr = (uint32_t)ihl * 4; - - if (pay >= iphdr + 8) { - uint8_t proto = ip[9]; - if (proto == 1) { - const uint8_t *ic = pkt->payload + iphdr; - uint8_t t = ic[0]; - if (t == ICMP_ECHO_REQUEST || t == ICMP_ECHO_REPLY) { - uint16_t iid = (uint16_t)((ic[4] << 8) | ic[5]); - uint16_t isq = (uint16_t)((ic[6] << 8) | ic[7]); - mark_received(iid, isq, type, code, src_ip); - } - } - } + if (type == ICMP_DEST_UNREACH) { + if (hdr[1] == 4) icmp_note_frag_needed(ifindex, raw, len); + netpkt_unref(pkt); + return; } + + if (type == ICMP_TIME_EXCEEDED || type == ICMP_PARAM_PROBLEM || type == ICMP_REDIRECT) { + netpkt_unref(pkt); return; } + + netpkt_unref(pkt); } diff --git a/kernel/networking/internet_layer/icmp.h b/kernel/networking/internet_layer/icmp.h index 08ff1223..71cf8163 100644 --- a/kernel/networking/internet_layer/icmp.h +++ b/kernel/networking/internet_layer/icmp.h @@ -1,33 +1,20 @@ #pragma once #include "types.h" #include "net/network_types.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { #endif -#define ICMP_ECHO_REPLY 0 -#define ICMP_DEST_UNREACH 3 -#define ICMP_REDIRECT 5 -#define ICMP_ECHO_REQUEST 8 -#define ICMP_TIME_EXCEEDED 11 -#define ICMP_PARAM_PROBLEM 12 - typedef enum { - PING_OK = 0, - PING_TIMEOUT = 1, - PING_NET_UNREACH = 2, - PING_HOST_UNREACH = 3, - PING_PROTO_UNREACH = 4, - PING_PORT_UNREACH = 5, - PING_FRAG_NEEDED = 6, - PING_SRC_ROUTE_FAILED = 7, - PING_ADMIN_PROHIBITED = 8, - PING_TTL_EXPIRED = 9, - PING_PARAM_PROBLEM = 10, - PING_REDIRECT = 11, - PING_UNKNOWN_ERROR = 255 -} ping_status_t; + ICMP_ECHO_REPLY = 0, + ICMP_DEST_UNREACH = 3, + ICMP_REDIRECT = 5, + ICMP_ECHO_REQUEST = 8, + ICMP_TIME_EXCEEDED = 11, + ICMP_PARAM_PROBLEM = 12 +} icmp_type_t; typedef struct __attribute__((packed)) { uint8_t type; @@ -35,20 +22,10 @@ typedef struct __attribute__((packed)) { uint16_t checksum; uint16_t id; uint16_t seq; - uint8_t payload[56]; -} icmp_packet; - -typedef struct { - uint32_t rtt_ms; - uint8_t status; - uint8_t icmp_type; - uint8_t icmp_code; - uint8_t _pad; - uint32_t responder_ip; -} ping_result_t; +} icmp_echo_hdr_t; -bool icmp_ping(uint32_t dst_ip, uint16_t id, uint16_t seq, uint32_t timeout_ms, const void *tx_opts_or_null, uint32_t ttl, ping_result_t *out); -void icmp_input(uintptr_t ptr, uint32_t len, uint32_t src_ip, uint32_t dst_ip); +void icmp_input(uint8_t ifindex, netpkt_t* pkt, uint32_t src_ip, uint32_t dst_ip); +void icmp_send_port_unreachable(l3_id_t l3_id, uint32_t remote_ip, const uint8_t *ip_header, uint8_t ip_header_len, const netpkt_t *l4pkt, uint32_t l4_off, uint32_t l4_len); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/icmpv6.c b/kernel/networking/internet_layer/icmpv6.c index 4b1ca415..d22c21a3 100644 --- a/kernel/networking/internet_layer/icmpv6.c +++ b/kernel/networking/internet_layer/icmpv6.c @@ -5,11 +5,14 @@ #include "networking/internet_layer/ipv6_utils.h" #include "networking/internet_layer/ipv6_route.h" #include "networking/link_layer/eth.h" +#include "networking/network.h" #include "networking/link_layer/ndp.h" #include "networking/internet_layer/mld.h" -#include "syscalls/syscalls.h" - -#define MAX_PENDING 16 +#include "networking/transport_layer/csocket_raw.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/transport_layer/tcp.h" +#include "networking/internet_layer/pmtu.h" +#include "networking/interface_manager.h" typedef struct __attribute__((packed)) { icmpv6_hdr_t hdr; @@ -17,51 +20,6 @@ typedef struct __attribute__((packed)) { uint16_t seq; } icmpv6_echo_t; -typedef struct { - bool in_use; - uint16_t id; - uint16_t seq; - bool received; - uint8_t rx_type; - uint8_t rx_code; - uint32_t start_ms; - uint32_t end_ms; - uint8_t rx_src_ip[16]; -} ping6_slot_t; - -static ping6_slot_t g_pending[MAX_PENDING] = {0}; - -static int alloc_slot(uint16_t id, uint16_t seq) { - for (int i = 0; i < MAX_PENDING; i++) { - if (!g_pending[i].in_use) { - g_pending[i].in_use = true; - g_pending[i].id = id; - g_pending[i].seq = seq; - g_pending[i].received = false; - g_pending[i].rx_type = 0xFF; - g_pending[i].rx_code = 0xFF; - g_pending[i].start_ms = (uint32_t)get_time(); - g_pending[i].end_ms = 0; - memset(g_pending[i].rx_src_ip, 0, 16); - return i; - } - } - return -1; -} - -static void mark_received(uint16_t id, uint16_t seq, uint8_t type, uint8_t code, const uint8_t src_ip[16]) { - for (int i = 0; i < MAX_PENDING; i++) { - if (g_pending[i].in_use && g_pending[i].id == id && g_pending[i].seq == seq) { - g_pending[i].received = true; - g_pending[i].rx_type = type; - g_pending[i].rx_code = code; - g_pending[i].end_ms = (uint32_t)get_time(); - if (src_ip) memcpy(g_pending[i].rx_src_ip, src_ip, 16); - return; - } - } -} - bool icmpv6_send_on_l2(uint8_t ifindex, const uint8_t dst_ip[16], const uint8_t src_ip[16], const uint8_t dst_mac[6], const void *icmp, uint32_t icmp_len, uint8_t hop_limit) { if (!ifindex || !dst_ip || !src_ip || !dst_mac || !icmp || !icmp_len) return false; @@ -74,241 +32,167 @@ bool icmpv6_send_on_l2(uint8_t ifindex, const uint8_t dst_ip[16], const uint8_t return false; } - ipv6_hdr_t *ip6 = (ipv6_hdr_t*)buf; - ip6->ver_tc_fl = bswap32((uint32_t)(6u << 28)); - ip6->payload_len = bswap16((uint16_t)icmp_len); - ip6->next_header = 58; - ip6->hop_limit = hop_limit ? hop_limit : 64; - memcpy(ip6->src, src_ip, 16); - memcpy(ip6->dst, dst_ip, 16); + ipv6_hdr_t ip6; + ip6.ver_tc_fl = bswap32((uint32_t)(6u << 28)); + ip6.payload_len = bswap16((uint16_t)icmp_len); + ip6.next_header = PROTO_ICMPV6; + ip6.hop_limit = hop_limit ? hop_limit : 64; + ipv6_cpy(ip6.src, src_ip); + ipv6_cpy(ip6.dst, dst_ip); + memcpy(buf, &ip6, sizeof(ip6)); memcpy((void*)((uintptr_t)buf + sizeof(ipv6_hdr_t)), icmp, icmp_len); return eth_send_frame_on(ifindex, ETHERTYPE_IPV6, dst_mac, pkt); } -static bool icmpv6_send_echo_reply(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t *icmp, uint32_t icmp_len, const uint8_t src_mac[6], uint8_t hop_limit) { - if (!dst_ip || !icmp || icmp_len < sizeof(icmpv6_echo_t)) return false; - - uintptr_t buf = (uintptr_t)malloc(icmp_len); - if (!buf) return false; - - memcpy((void*)buf, icmp, icmp_len); - - icmpv6_echo_t *e = (icmpv6_echo_t*)buf; - e->hdr.type = ICMPV6_ECHO_REPLY; - e->hdr.code = 0; - e->hdr.checksum = 0; - - ipv6_tx_plan_t plan; - if (!ipv6_build_tx_plan(dst_ip, 0 ,0, 0, &plan)) { - free_sized((void*)buf, icmp_len); - return false; - } - - e->hdr.checksum = bswap16(checksum16_pipv6(dst_ip, src_ip, 58, (const uint8_t*)buf, icmp_len)); - - icmpv6_send_on_l2(ifindex, src_ip, dst_ip, src_mac, (const void*)buf, icmp_len, hop_limit ? hop_limit : 64); - - free_sized((void*)buf, icmp_len); - return true; -} - -static bool icmpv6_send_echo_request(const uint8_t dst_ip[16], uint16_t id, uint16_t seq, const void *payload, uint32_t payload_len, const void *tx_opts_or_null, uint8_t hop_limit) { - if (!dst_ip) return false; +static bool icmpv6_send_echo_reply(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t *icmp, uint32_t icmp_len, const uint8_t src_mac[6], uint8_t hop_limit) { + if (!ifindex || !src_ip || !dst_ip || !src_mac || !icmp || icmp_len < sizeof(icmpv6_echo_t)) return false; - uint32_t len = (uint32_t)sizeof(icmpv6_echo_t) + payload_len; - uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(ipv6_hdr_t); - netpkt_t* pkt = netpkt_alloc(len, headroom, 0); + uint32_t total = (uint32_t)sizeof(ipv6_hdr_t) + icmp_len; + netpkt_t* pkt = netpkt_alloc(total, (uint32_t)sizeof(eth_hdr_t), 0); if (!pkt) return false; - void* buf = netpkt_put(pkt, len); + + uint8_t* buf = (uint8_t*)netpkt_put(pkt, total); if (!buf) { netpkt_unref(pkt); return false; } - icmpv6_echo_t *e = (icmpv6_echo_t*)buf; - e->hdr.type = ICMPV6_ECHO_REQUEST; + ipv6_hdr_t* ip6 = (ipv6_hdr_t*)buf; + ip6->ver_tc_fl = bswap32((uint32_t)(6u << 28)); + ip6->payload_len = bswap16((uint16_t)icmp_len); + ip6->next_header = PROTO_ICMPV6; + ip6->hop_limit = hop_limit ? hop_limit : 64; + ipv6_cpy(ip6->src, dst_ip); + ipv6_cpy(ip6->dst, src_ip); + + icmpv6_echo_t* e = (icmpv6_echo_t*)(buf + sizeof(*ip6)); + memcpy(e, icmp, icmp_len); + e->hdr.type = ICMPV6_ECHO_REPLY; e->hdr.code = 0; e->hdr.checksum = 0; - e->id = bswap16(id); - e->seq = bswap16(seq); - if (payload_len) memcpy((void*)((uintptr_t)buf + sizeof(icmpv6_echo_t)), payload, payload_len); + e->hdr.checksum = bswap16(checksum16_pipv6(dst_ip, src_ip, PROTO_ICMPV6, (const uint8_t*)e, icmp_len)); - ipv6_tx_plan_t plan; - if (!ipv6_build_tx_plan(dst_ip, tx_opts_or_null, 0, 0, &plan)) { - netpkt_unref(pkt); - return false; + uint16_t mtu = network_get_device_mtu(ifindex); + if (mtu && total > mtu) { + l3_ipv6_interface_t* l3 = l3_ipv6_find_by_ip(dst_ip); + if (!ipv6_l3_is_ready(l3) || !l3->l2 || l3->l2->ifindex != ifindex || !netpkt_pull(pkt, sizeof(ipv6_hdr_t))) { + netpkt_unref(pkt); + return false; + } + ip_tx_opts_t tx = {.target = {.l3_id = l3->l3_id},.scope = IP_TX_BOUND_L3}; + return ipv6_send_packet(src_ip, PROTO_ICMPV6, pkt, &tx, hop_limit, 0, 0); } - e->hdr.checksum = bswap16(checksum16_pipv6(plan.src_ip, dst_ip, 58, (const uint8_t*)buf, len)); - ipv6_send_packet(dst_ip, 58, pkt, (const ipv6_tx_opts_t*)tx_opts_or_null, hop_limit ? hop_limit : 64, 0); - return true; + return eth_send_frame_on(ifindex, ETHERTYPE_IPV6, src_mac, pkt); } -bool icmpv6_ping(const uint8_t dst_ip[16], uint16_t id, uint16_t seq, uint32_t timeout_ms, const void *tx_opts_or_null, uint8_t hop_limit, ping6_result_t *out) { - int slot = alloc_slot(id, seq); - if (slot < 0) { - if (out) { - out->rtt_ms = 0; - out->status = PING_UNKNOWN_ERROR; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - memset(out->responder_ip, 0, 16); - } - return false; +void icmpv6_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, bool router_alert, const uint8_t src_mac[6], netpkt_t* pkt) { + if (!ifindex || !src_ip || !dst_ip || !pkt || netpkt_len(pkt) < sizeof(icmpv6_hdr_t)) { + if (pkt) netpkt_unref(pkt); + return; } - uint8_t payload[32]; - memset(payload, 0, sizeof(payload)); - - if (!icmpv6_send_echo_request(dst_ip, id, seq, payload, sizeof(payload), tx_opts_or_null, hop_limit)) { - if (out) { - out->rtt_ms = 0; - out->status = PING_UNKNOWN_ERROR; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - memset(out->responder_ip, 0, 16); - } - g_pending[slot].in_use =false; - return false; + const uint8_t *icmp = (const uint8_t*)netpkt_data(pkt); + uint32_t icmp_len = netpkt_len(pkt); + icmpv6_hdr_t hdr; + if (!netpkt_copyout(pkt, 0, &hdr, sizeof(hdr))) { + netpkt_unref(pkt); + return; } - - uint32_t start = (uint32_t)get_time(); - for (;;) { - if (g_pending[slot].received) { - if (out) { - out->icmp_type = g_pending[slot].rx_type; - out->icmp_code = g_pending[slot].rx_code; - memcpy(out->responder_ip, g_pending[slot].rx_src_ip, 16); - - switch (g_pending[slot].rx_type) { - case ICMPV6_ECHO_REPLY: - out->status = PING_OK; - break; - case ICMPV6_DEST_UNREACH: - switch (g_pending[slot].rx_code) { - case 0: out->status = PING_NET_UNREACH; break; - case 1: out->status = PING_ADMIN_PROHIBITED; break; - case 2: out->status = PING_ADMIN_PROHIBITED; break; - case 3: out->status = PING_HOST_UNREACH; break; - case 4: out->status = PING_PORT_UNREACH; break; - default: out->status = PING_UNKNOWN_ERROR; break; - } - break; - case ICMPV6_PACKET_TOO_BIG: - out->status = PING_FRAG_NEEDED; - break; - case ICMPV6_TIME_EXCEEDED: - out->status = PING_TTL_EXPIRED; - break; - case ICMPV6_PARAM_PROBLEM: - out->status = PING_PARAM_PROBLEM; - break; - default: - out->status = PING_UNKNOWN_ERROR; - break; - } - - if (g_pending[slot].end_ms >= g_pending[slot].start_ms) out->rtt_ms = g_pending[slot].end_ms - g_pending[slot].start_ms; - else out->rtt_ms = 0; - } - - bool ok = (g_pending[slot].rx_type == ICMPV6_ECHO_REPLY); - g_pending[slot].in_use = false; - return ok; - } - - uint32_t now = (uint32_t)get_time(); - if (now - start >= timeout_ms) break; - msleep(5); + const icmpv6_hdr_t *h = &hdr; + if (h->code != 0 && (h->type == ICMPV6_ECHO_REQUEST || h->type == ICMPV6_ECHO_REPLY)) { + netpkt_unref(pkt); + return; } - if (out) { - out->rtt_ms = 0; - out->status = PING_TIMEOUT; - out->icmp_type = 0xFF; - out->icmp_code = 0xFF; - memset(out->responder_ip, 0, 16); + uint16_t calc = bswap16(checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, icmp, icmp_len)); + if (calc != 0) { + netpkt_unref(pkt); + return; } - g_pending[slot].in_use = false; - return false; -} - -static bool extract_echo_id_seq_from_error(const uint8_t *icmp, uint32_t icmp_len, uint16_t *out_id, uint16_t *out_seq) {//b - if (!icmp || icmp_len < 8u + (uint32_t)sizeof(ipv6_hdr_t) + (uint32_t)sizeof(icmpv6_echo_t)) return false; - - const ipv6_hdr_t *inner = (const ipv6_hdr_t*)(icmp + 8); - uint32_t v = bswap32(inner->ver_tc_fl); - if ((v >>28) != 6) return false; - if (inner->next_header != 58) return false; - - const uint8_t *inner_icmp = (const uint8_t*)(inner + 1); - if ((uintptr_t)inner_icmp + sizeof(icmpv6_echo_t)>(uintptr_t)icmp + icmp_len) return false; - - const icmpv6_echo_t *e = (const icmpv6_echo_t*)inner_icmp; - if (e->hdr.type != ICMPV6_ECHO_REQUEST) return false; - - if (out_id) *out_id = bswap16(e->id); - if (out_seq) *out_seq = bswap16(e->seq); - return true; -} - -void icmpv6_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, const uint8_t src_mac[6], const uint8_t *icmp, uint32_t icmp_len) { - if (!ifindex || !src_ip || !dst_ip || !icmp || icmp_len < sizeof(icmpv6_hdr_t)) return; - - const icmpv6_hdr_t *h = (const icmpv6_hdr_t*)icmp; - if (h->code != 0 && (h->type == ICMPV6_ECHO_REQUEST || h->type == ICMPV6_ECHO_REPLY)) return; - - uint16_t calc = bswap16(checksum16_pipv6(src_ip, dst_ip, 58, icmp, icmp_len)); - if (calc != 0) return; - - if ((h->type == 133 || h->type == 134 || h->type == 135 || h->type == 136 || h->type == 137) && hop_limit != 255) return; - if (h->type == 130 || h->type == 131 || h->type == 132 || h->type == 143) { - mld_input((uint8_t)ifindex, src_ip, dst_ip, icmp, icmp_len); + if ((h->type == ICMPV6_ROUTER_SOLICIT || h->type == ICMPV6_ROUTER_ADVERT || h->type == ICMPV6_NEIGHBOR_SOLICIT || h->type == ICMPV6_NEIGHBOR_ADVERT || h->type == ICMPV6_REDIRECT) && hop_limit != 255) { + netpkt_unref(pkt); return; } + socket_raw_input_v6(ifindex, src_ip, dst_ip, pkt); + if (h->type == ICMPV6_MLD_QUERY || h->type == ICMPV6_MLD_REPORT || h->type == ICMPV6_MLD_DONE || h->type == ICMPV6_MLDV2_REPORT) { + mld_input(ifindex, src_ip, dst_ip, hop_limit, router_alert, pkt); + netpkt_unref(pkt); + return; + } if (h->type == ICMPV6_ECHO_REQUEST) { icmpv6_send_echo_reply(ifindex, src_ip, dst_ip, icmp, icmp_len, src_mac, hop_limit); + netpkt_unref(pkt); return; } if (h->type == ICMPV6_ECHO_REPLY) { - if (icmp_len < sizeof(icmpv6_echo_t)) return; - const icmpv6_echo_t *e = (const icmpv6_echo_t*)icmp; - mark_received(bswap16(e->id), bswap16(e->seq), h->type, h->code, src_ip); + netpkt_unref(pkt); return; } - if (h->type == 133 || h->type == 134 || h->type == 135 || h->type == 136 || h->type == 137) { - ndp_input(ifindex, src_ip, dst_ip, src_mac, icmp, icmp_len); + if (h->type == ICMPV6_ROUTER_SOLICIT || h->type == ICMPV6_ROUTER_ADVERT || h->type == ICMPV6_NEIGHBOR_SOLICIT || h->type == ICMPV6_NEIGHBOR_ADVERT || h->type == ICMPV6_REDIRECT) { + ndp_input(ifindex, src_ip, dst_ip, src_mac, pkt); + netpkt_unref(pkt); return; } if (h->type == ICMPV6_PACKET_TOO_BIG) { + if (h->code || icmp_len < 8 + sizeof(ipv6_hdr_t)) { + netpkt_unref(pkt); + return; + } - if (icmp_len >= 8u + (uint32_t)sizeof(ipv6_hdr_t)) { - uint32_t mtu = bswap32(*(const uint32_t *)(icmp + 4)); - const ipv6_hdr_t *inner = (const ipv6_hdr_t *)(icmp + 8); - uint32_t v = bswap32(inner->ver_tc_fl); + uint32_t reported = rd_be32(icmp + 4); + if (reported < 1280 || reported > UINT16_MAX) { + netpkt_unref(pkt); + return; + } - if ((v >> 28) == 6 && mtu >= 1280u && mtu <= 65535u) - ipv6_pmtu_note(inner->dst, (uint16_t)mtu); + ipv6_hdr_t inner; + memcpy(&inner, icmp + 8, sizeof(inner)); + if ((bswap32(inner.ver_tc_fl) >> 28) != 6 || ipv6_is_multicast(inner.dst)) { + netpkt_unref(pkt); + return; + } + + l3_ipv6_interface_t* l3 = l3_ipv6_find_by_ip(inner.src); + if (!ipv6_l3_is_ready(l3) || !l3->l2 || l3->l2->ifindex != ifindex) { + netpkt_unref(pkt); + return; + } - uint16_t id = 0, seq = 0; - if (extract_echo_id_seq_from_error(icmp, icmp_len, &id, &seq)) - mark_received(id, seq, h->type, h->code, src_ip); + uint16_t mtu = (uint16_t)reported; + uint16_t base_mtu = l3_ipv6_effective_mtu(l3); + if (!base_mtu || mtu >= base_mtu) { + netpkt_unref(pkt); + return; } + + uint16_t path_mtu = pmtu_note(l3->l3_id, l3->epoch, IP_VER6, inner.dst, mtu); + if (path_mtu) { + uint32_t l4_off = 8 + (uint32_t)sizeof(ipv6_hdr_t); + uint32_t l4_len = icmp_len - l4_off; + uint8_t inner_nh = inner.next_header; + uint8_t ports[4]; + if (ipv6_skip_ext_headers(pkt, &inner_nh, &l4_off, &l4_len, false, NULL) && inner_nh == PROTO_TCP && l4_len >= sizeof(ports) && + netpkt_copyout(pkt, l4_off, ports, sizeof(ports))) tcp_pmtu_update(l3->l3_id, IP_VER6, inner.src, inner.dst, rd_be16(ports), rd_be16(ports + 2), path_mtu); + } + + netpkt_unref(pkt); return; } if (h->type == ICMPV6_DEST_UNREACH || h->type == ICMPV6_TIME_EXCEEDED || h->type == ICMPV6_PARAM_PROBLEM) { - uint16_t id = 0, seq = 0; - if (extract_echo_id_seq_from_error(icmp, icmp_len, &id, &seq)) mark_received(id, seq, h->type, h->code, src_ip); + netpkt_unref(pkt); return; } + + netpkt_unref(pkt); } \ No newline at end of file diff --git a/kernel/networking/internet_layer/icmpv6.h b/kernel/networking/internet_layer/icmpv6.h index 58c54ca4..1f7f949e 100644 --- a/kernel/networking/internet_layer/icmpv6.h +++ b/kernel/networking/internet_layer/icmpv6.h @@ -2,18 +2,31 @@ #include "types.h" #include "net/network_types.h" -#include "networking/internet_layer/icmp.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { #endif -#define ICMPV6_DEST_UNREACH 1 -#define ICMPV6_PACKET_TOO_BIG 2 -#define ICMPV6_TIME_EXCEEDED 3 -#define ICMPV6_PARAM_PROBLEM 4 -#define ICMPV6_ECHO_REQUEST 128 -#define ICMPV6_ECHO_REPLY 129 +typedef enum { + ICMPV6_DEST_UNREACH = 1, + ICMPV6_PACKET_TOO_BIG = 2, + ICMPV6_TIME_EXCEEDED = 3, + ICMPV6_PARAM_PROBLEM = 4, + ICMPV6_ECHO_REQUEST = 128, + ICMPV6_ECHO_REPLY = 129, + ICMPV6_MLD_QUERY = 130, + ICMPV6_MLD_REPORT = 131, + ICMPV6_MLD_DONE = 132, + ICMPV6_ROUTER_SOLICIT = 133, + ICMPV6_ROUTER_ADVERT = 134, + ICMPV6_NEIGHBOR_SOLICIT = 135, + ICMPV6_NEIGHBOR_ADVERT = 136, + ICMPV6_REDIRECT = 137, + ICMPV6_MLDV2_REPORT = 143 +} icmpv6_type_t; + +#define ICMPV6_CODE_PORT_UNREACH 4 typedef struct __attribute__((packed)) { uint8_t type; @@ -21,17 +34,7 @@ typedef struct __attribute__((packed)) { uint16_t checksum; } icmpv6_hdr_t; -typedef struct { - uint32_t rtt_ms; - uint8_t status; - uint8_t icmp_type; - uint8_t icmp_code; - uint8_t _pad; - uint8_t responder_ip[16]; -} ping6_result_t; - -void icmpv6_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, const uint8_t src_mac[6], const uint8_t *icmp, uint32_t icmp_len); -bool icmpv6_ping(const uint8_t dst_ip[16], uint16_t id, uint16_t seq, uint32_t timeout_ms, const void *tx_opts_or_null, uint8_t hop_limit, ping6_result_t *out); +void icmpv6_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, bool router_alert, const uint8_t src_mac[6], netpkt_t* pkt); bool icmpv6_send_on_l2(uint8_t ifindex, const uint8_t dst_ip[16], const uint8_t src_ip[16], const uint8_t dst_mac[6], const void *icmp, uint32_t icmp_len, uint8_t hop_limit); #ifdef __cplusplus diff --git a/kernel/networking/internet_layer/igmp.c b/kernel/networking/internet_layer/igmp.c index 42833c45..fa3b14c6 100644 --- a/kernel/networking/internet_layer/igmp.c +++ b/kernel/networking/internet_layer/igmp.c @@ -1,17 +1,39 @@ #include "igmp.h" #include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_route.h" #include "networking/internet_layer/ipv4_utils.h" +#include "networking/link_layer/eth.h" #include "net/checksums.h" #include "networking/interface_manager.h" +#include "networking/transport_layer/csocket_raw.h" #include "kernel_processes/kprocess_loader.h" #include "math/rng.h" +#include "random/random.h" #include "std/memory.h" #include "std/string.h" #include "syscalls/syscalls.h" #define IGMP_TYPE_QUERY 0x11 +#define IGMP_TYPE_V1_REPORT 0x12 #define IGMP_TYPE_V2_REPORT 0x16 #define IGMP_TYPE_V2_LEAVE 0x17 +#define IGMP_TYPE_V3_REPORT 0x22 + +#define IGMPV3_RTYPE_MODE_IS_INCLUDE 1 +#define IGMPV3_RTYPE_MODE_IS_EXCLUDE 2 +#define IGMPV3_RTYPE_CHANGE_TO_INCLUDE 3 +#define IGMPV3_RTYPE_CHANGE_TO_EXCLUDE 4 + +#define IGMPV3_ALL_ROUTERS 0xE0000016 +#define IGMP_V3_UNSOLICITED_INTERVAL_MS 1000 +#define IGMP_UNSOLICITED_REPORT_INTERVAL_MS 10000u +#define IGMP_DEFAULT_QUERY_INTERVAL_MS 125000u +#define IGMP_DEFAULT_ROBUSTNESS 2 +#define IPV4_OPT_ROUTER_ALERT 0x94 + +#define IGMP_REPORT_JOIN 1 +#define IGMP_REPORT_LEAVE 2 +#define IGMP_MAX_QUERY_SOURCES 32 typedef struct __attribute__((packed)) igmp_hdr_t { uint8_t type; @@ -20,53 +42,155 @@ typedef struct __attribute__((packed)) igmp_hdr_t { uint32_t group; } igmp_hdr_t; +typedef struct __attribute__((packed)) { + uint8_t type; + uint8_t reserved1; + uint16_t checksum; + uint16_t reserved2; + uint16_t num_records; +} igmpv3_report_hdr_t; + +typedef struct __attribute__((packed)) { + uint8_t record_type; + uint8_t aux_len; + uint16_t num_sources; + uint32_t group; +} igmpv3_record_t; + typedef struct { uint8_t used; uint8_t ifindex; uint32_t group; - uint32_t refresh_ms; uint32_t query_due_ms; + uint32_t unsolicited_due_ms; uint8_t query_pending; + uint8_t query_source_count; + uint8_t unsolicited_left; + uint8_t unsolicited_kind; + uint8_t last_reporter; + uint32_t query_sources[IGMP_MAX_QUERY_SOURCES]; } igmp_state_t; +typedef struct { + uint32_t v1_until_ms; + uint32_t v2_until_ms; +} igmp_if_state_t; + static volatile int igmp_daemon_running = 0; static volatile int igmp_daemon_pending = 0; -static uint32_t igmp_uptime_ms = 0; static rng_t igmp_rng; static int igmp_rng_inited = 0; #define IGMP_MAX_TRACK 64 -#define IGMP_REFRESH_PERIOD_MS 60000 static igmp_state_t igmp_states[IGMP_MAX_TRACK]; +static igmp_if_state_t igmp_if_states[MAX_L2_INTERFACES]; + +static void igmp_daemon_kick(void); +static uint8_t igmp_compat_mode(uint8_t ifindex, uint32_t now_ms) { + if (!ifindex || ifindex > MAX_L2_INTERFACES) return 3; + igmp_if_state_t* s = &igmp_if_states[ifindex - 1]; + if (s->v1_until_ms && (int32_t)(s->v1_until_ms - now_ms) <= 0) s->v1_until_ms = 0; + if (s->v2_until_ms && (int32_t)(s->v2_until_ms - now_ms) <= 0) s->v2_until_ms = 0; + if (s->v1_until_ms) return 1; + if (s->v2_until_ms) return 2; + return 3; +} + +static bool igmp_send_message(uint8_t ifindex, uint32_t dst, const void* msg, uint32_t msg_len) { + if (!msg || !msg_len) return false; + + ip_tx_opts_t tx = {.target = {.ifindex = ifindex}, .scope = IP_TX_BOUND_L2}; + ipv4_tx_plan_t plan; + if (!ipv4_build_tx_plan(dst, &tx, &plan)) return false; + + l3_ipv4_interface_t* l3 = l3_ipv4_find_by_id(plan.l3_id); + if (!ipv4_l3_is_ready(l3)) return false; -static bool send_igmp(uint8_t ifindex, uint32_t dst, uint8_t type, uint32_t group) { - uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(ipv4_hdr_t); - netpkt_t* pkt = netpkt_alloc(sizeof(igmp_hdr_t),headroom, 0); + const uint32_t ip_header_len = sizeof(ipv4_hdr_t) + 4; + uint32_t total_len = ip_header_len + msg_len; + if (total_len > UINT16_MAX || l3_ipv4_effective_mtu(l3) < total_len) return false; + netpkt_t* pkt = netpkt_alloc(total_len, sizeof(eth_hdr_t), 0); if (!pkt) return false; - igmp_hdr_t* h = (igmp_hdr_t*)netpkt_put(pkt, sizeof(igmp_hdr_t)); - if (!h) { + uint8_t* igmp = (uint8_t*)netpkt_put(pkt, total_len); + if (!igmp) { netpkt_unref(pkt); return false; } - h->type = type; - h->max_resp_time = 0; - h->group = bswap32(group); - h->checksum = 0; - h->checksum = checksum16((const uint16_t*)h, sizeof(igmp_hdr_t)/2); + ipv4_hdr_t ip; + ip.version_ihl = (uint8_t)((IP_VER4 << 4) | (IP_IHL_NOOPTS + 1)); + ip.dscp_ecn = 0xC0; + ip.total_length = bswap16((uint16_t)total_len); + ip.identification = 0; + ip.flags_frag_offset = 0; + ip.ttl = 1; + ip.protocol = PROTO_IGMP; + ip.header_checksum = 0; + ip.src_ip = bswap32(plan.src_ip); + ip.dst_ip = bswap32(dst); + memcpy(igmp, &ip, sizeof(ip)); + + const uint32_t opt_off = sizeof(ipv4_hdr_t); + igmp[opt_off] = IPV4_OPT_ROUTER_ALERT; + igmp[opt_off+1] = 4; + igmp[opt_off+2] = 0; + igmp[opt_off+3] = 0; + ip.header_checksum = bswap16(checksum16(igmp, ip_header_len)); + memcpy(igmp, &ip, sizeof(ip)); + memcpy(igmp + ip_header_len, msg, msg_len); + + uint8_t dst_mac[MAC_ADDR_LEN]; + ipv4_mcast_to_mac(dst, dst_mac); + return eth_send_frame_on(ifindex, ETHERTYPE_IPV4, dst_mac, pkt); +} - ipv4_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L2; - tx.index = ifindex; +static bool igmp_send_v12(uint8_t ifindex, uint32_t dst, uint8_t type, uint32_t group) { + igmp_hdr_t msg; + msg.type = type; + msg.max_resp_time = 0; + msg.checksum = 0; + msg.group = bswap32(group); + msg.checksum = bswap16(checksum16(&msg, sizeof(msg))); + return igmp_send_message(ifindex, dst, &msg, sizeof(msg)); +} - ipv4_send_packet(dst, 2, pkt, &tx, 1, 0); - return true; +static bool igmp_send_v3(uint8_t ifindex, uint32_t group, uint8_t record_type, const uint32_t* sources, uint8_t source_count) { + igmpv3_report_hdr_t hdr = {0}; + igmpv3_record_t record = {0}; + uint8_t msg[sizeof(hdr) + sizeof(record) + IGMP_MAX_QUERY_SOURCES * sizeof(uint32_t)]; + uint32_t msg_len = sizeof(hdr) + sizeof(record) + source_count * sizeof(uint32_t); + + hdr.type = IGMP_TYPE_V3_REPORT; + hdr.num_records = bswap16(1); + record.record_type = record_type; + record.num_sources = bswap16(source_count); + record.group = bswap32(group); + + memcpy(msg, &hdr, sizeof(hdr)); + memcpy(msg + sizeof(hdr), &record, sizeof(record)); + for (uint8_t i = 0; i < source_count; i++) wr_be32(msg + sizeof(hdr) + sizeof(record) + i * sizeof(uint32_t), sources[i]); + hdr.checksum = bswap16(checksum16(msg, msg_len)); + memcpy(msg, &hdr, sizeof(hdr)); + return igmp_send_message(ifindex, IGMPV3_ALL_ROUTERS, msg, msg_len); +} + +static bool igmp_send_state_change(uint8_t ifindex, uint32_t group, uint8_t kind) { + uint8_t mode = igmp_compat_mode(ifindex, get_time()); + if (kind == IGMP_REPORT_JOIN) { + if (mode == 1) return igmp_send_v12(ifindex, group, IGMP_TYPE_V1_REPORT, group); + if (mode == 2) return igmp_send_v12(ifindex, group, IGMP_TYPE_V2_REPORT, group); + return igmp_send_v3(ifindex, group, IGMPV3_RTYPE_CHANGE_TO_EXCLUDE, 0, 0); + } + + if (mode == 1) return true; + if (mode == 2) return igmp_send_v12(ifindex, IPV4_MCAST_ALL_ROUTERS, IGMP_TYPE_V2_LEAVE, group); + return igmp_send_v3(ifindex, group, IGMPV3_RTYPE_CHANGE_TO_INCLUDE, 0, 0); } static igmp_state_t* igmp_find_state(uint8_t ifindex, uint32_t group) { - for (int i = 0; i < IGMP_MAX_TRACK; ++i) { + for (int i = 0; i < (int)N_ARR(igmp_states); i++) { igmp_state_t* s = &igmp_states[i]; if (!s->used) continue; if (s->ifindex == ifindex &&s->group == group) return s; @@ -77,14 +201,18 @@ static igmp_state_t* igmp_find_state(uint8_t ifindex, uint32_t group) { static igmp_state_t* igmp_get_state(uint8_t ifindex, uint32_t group) { igmp_state_t* s = igmp_find_state(ifindex, group); if (s) return s; - for (int i = 0; i < IGMP_MAX_TRACK; ++i) { + for (int i = 0; i < (int)N_ARR(igmp_states); i++) { if (!igmp_states[i].used) { igmp_states[i].used = 1; igmp_states[i].ifindex = ifindex; igmp_states[i].group = group; - igmp_states[i].refresh_ms = 0; igmp_states[i].query_due_ms = 0; + igmp_states[i].unsolicited_due_ms = 0; igmp_states[i].query_pending = 0; + igmp_states[i].query_source_count = 0; + igmp_states[i].unsolicited_left = 0; + igmp_states[i].unsolicited_kind = 0; + igmp_states[i].last_reporter = 0; return &igmp_states[i]; } } @@ -92,11 +220,10 @@ static igmp_state_t* igmp_get_state(uint8_t ifindex, uint32_t group) { } static int igmp_has_pending_timers(void) { - for (int i = 0; i < IGMP_MAX_TRACK; ++i) { + for (int i = 0; i < (int)N_ARR(igmp_states); i++) { igmp_state_t* s = &igmp_states[i]; if (!s->used) continue; - if (s->query_pending) return 1; - if (s->refresh_ms < IGMP_REFRESH_PERIOD_MS) return 1; + if (s->query_pending || s->unsolicited_left) return 1; } return 0; } @@ -109,25 +236,23 @@ static int igmp_daemon_entry(int argc, char* argv[]) { igmp_daemon_running = 1; if (!igmp_rng_inited) { - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&igmp_rng, virt_timer); + rng_init_random(&igmp_rng); igmp_rng_inited = 1; } const uint32_t tick_ms = 100; while (igmp_has_pending_timers()) { - igmp_uptime_ms += tick_ms; + uint32_t now_ms = get_time(); - for (int i = 0; i < IGMP_MAX_TRACK; ++i) { + for (int i = 0; i < (int)N_ARR(igmp_states); i++) { igmp_state_t* s = &igmp_states[i]; if (!s->used) continue; l2_interface_t* l2 = l2_interface_find_by_index(s->ifindex); bool still_joined = false; if (l2) { - for (int j = 0; j < (int)l2->ipv4_mcast_count; ++j) { + for (int j = 0; j < (int)l2->ipv4_mcast_count; j++) { if (l2->ipv4_mcast[j] == s->group) { still_joined = true; break; @@ -135,25 +260,41 @@ static int igmp_daemon_entry(int argc, char* argv[]) { } } if (!still_joined) { - s->used = 0; - continue; + s->query_pending = 0; + if (s->unsolicited_kind == IGMP_REPORT_JOIN) s->unsolicited_left = 0; + //continue; } - s->refresh_ms+= tick_ms; - if (s->refresh_ms>= IGMP_REFRESH_PERIOD_MS) { - s->refresh_ms = 0; - (void)send_igmp(s->ifindex, s->group, IGMP_TYPE_V2_REPORT, s->group); + if (s->query_pending && (int32_t)(now_ms - s->query_due_ms) >= 0) { + s->query_pending = 0; + uint8_t mode = igmp_compat_mode(s->ifindex, now_ms); + bool sent; + if (mode == 1) sent = igmp_send_v12(s->ifindex, s->group, IGMP_TYPE_V1_REPORT, s->group); + else if (mode == 2) sent = igmp_send_v12(s->ifindex, s->group, IGMP_TYPE_V2_REPORT, s->group); + else if (s->query_source_count) sent = igmp_send_v3(s->ifindex, s->group, IGMPV3_RTYPE_MODE_IS_INCLUDE, s->query_sources, s->query_source_count); + else sent = igmp_send_v3(s->ifindex, s->group, IGMPV3_RTYPE_MODE_IS_EXCLUDE, 0, 0); + s->query_source_count = 0; + if (sent && mode != 3) s->last_reporter = 1; } - if (s->query_pending && igmp_uptime_ms >= s->query_due_ms) { - s->query_pending = 0; - (void)send_igmp(s->ifindex, s->group, IGMP_TYPE_V2_REPORT, s->group); + if (s->unsolicited_left && (int32_t)(now_ms - s->unsolicited_due_ms) >= 0) { + bool sent = igmp_send_state_change(s->ifindex, s->group, s->unsolicited_kind); + uint8_t mode = igmp_compat_mode(s->ifindex, now_ms); + if (sent && s->unsolicited_kind == IGMP_REPORT_JOIN && mode != 3) s->last_reporter = 1; + s->unsolicited_left--; + if (s->unsolicited_left) { + uint32_t interval = mode == 3 ? IGMP_V3_UNSOLICITED_INTERVAL_MS : IGMP_UNSOLICITED_REPORT_INTERVAL_MS; + s->unsolicited_due_ms = now_ms + rng_between32(&igmp_rng, 1, interval+1); + } } + + if (!still_joined && !s->query_pending && !s->unsolicited_left) memset(s, 0, sizeof(*s)); } msleep(tick_ms); } igmp_daemon_running = 0; + igmp_daemon_kick(); return 0; } @@ -166,72 +307,279 @@ static void igmp_daemon_kick(void) { bool igmp_send_join(uint8_t ifindex, uint32_t group) { if (!ipv4_is_multicast(group)) return false; + if (group == IPV4_MCAST_ALL_HOSTS) return true; + + if (!igmp_rng_inited) { + rng_init_random(&igmp_rng); + igmp_rng_inited = 1; + } + igmp_state_t* s = igmp_get_state(ifindex, group); - if (s) s->refresh_ms = 0; + if (!s) return false; + + bool ok = igmp_send_state_change(ifindex, group, IGMP_REPORT_JOIN); + uint8_t mode = igmp_compat_mode(ifindex, get_time()); + if (ok && mode != 3) s->last_reporter = 1; + s->unsolicited_kind = IGMP_REPORT_JOIN; + s->unsolicited_left = IGMP_DEFAULT_ROBUSTNESS - 1; + uint32_t interval = mode == 3 ? IGMP_V3_UNSOLICITED_INTERVAL_MS : IGMP_UNSOLICITED_REPORT_INTERVAL_MS; + s->unsolicited_due_ms = get_time() + rng_between32(&igmp_rng, 1, interval + 1); igmp_daemon_kick(); - return send_igmp(ifindex, group, IGMP_TYPE_V2_REPORT, group); + return ok; } bool igmp_send_leave(uint8_t ifindex, uint32_t group) { if (!ipv4_is_multicast(group)) return false; + if (group == IPV4_MCAST_ALL_HOSTS) return true; igmp_state_t* s = igmp_find_state(ifindex, group); - if (s) s->used = 0; + uint8_t mode = igmp_compat_mode(ifindex, get_time()); + if (mode == 1) { + if (s) memset(s, 0, sizeof(*s)); + return true; + } + if (mode == 2) { + bool send_leave = !s || s->last_reporter; + if (s) memset(s, 0, sizeof(*s)); + if (!send_leave) return true; + return igmp_send_state_change(ifindex, group, IGMP_REPORT_LEAVE); + } + + if (!s) s = igmp_get_state(ifindex, group); + if (!s) return false; + bool ok = igmp_send_state_change(ifindex, group, IGMP_REPORT_LEAVE); + s->query_pending = 0; + s->unsolicited_kind = IGMP_REPORT_LEAVE; + s->unsolicited_left = IGMP_DEFAULT_ROBUSTNESS - 1; + s->unsolicited_due_ms = get_time() + rng_between32(&igmp_rng, 1, IGMP_V3_UNSOLICITED_INTERVAL_MS + 1); igmp_daemon_kick(); - return send_igmp(ifindex, IPV4_MCAST_ALL_ROUTERS, IGMP_TYPE_V2_LEAVE, group); + return ok; } -static void schedule_report(uint8_t ifindex, uint32_t group, uint32_t max_resp_ds) { - if (!ipv4_is_multicast(group)) return; +static bool igmp_dest_assigned(uint8_t ifindex, uint32_t dst) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) return false; + for (int i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[i]; + if (!ipv4_l3_is_active(v4)) continue; + if (v4->ip == dst) return 1; + } + for (int i = 0; i < (int)l2->ipv4_mcast_count; i++) if (l2->ipv4_mcast[i] == dst) return true; + return false; +} + +static void schedule_report(uint8_t ifindex, uint32_t group, uint32_t max_ms, const uint32_t* sources, uint16_t source_count) { + if (!ipv4_is_multicast(group) || group == IPV4_MCAST_ALL_HOSTS) return; + if (source_count > IGMP_MAX_QUERY_SOURCES) return; + if (!igmp_rng_inited) { + rng_init_random(&igmp_rng); + igmp_rng_inited = 1; + } + igmp_state_t* s = igmp_get_state(ifindex, group); - if (!s) return; - uint32_t max_ms = (uint32_t)max_resp_ds * 100; - if (max_ms == 0) max_ms = 100; - uint32_t delay = rng_between32(&igmp_rng, 0, max_ms); - uint32_t due = igmp_uptime_ms + delay; - if (!s->query_pending || due < s->query_due_ms) { + + if (!s->query_pending) { + s->query_source_count = 0; + for (uint16_t i = 0; i < source_count; i++) s->query_sources[s->query_source_count++] = sources[i]; + } else if (!source_count || !s->query_source_count) s->query_source_count = 0; + else { + uint32_t merged[IGMP_MAX_QUERY_SOURCES]; + uint8_t merged_count = s->query_source_count; + for (uint8_t i = 0; i < merged_count; i++) merged[i] = s->query_sources[i]; + for (uint16_t i = 0; i < source_count; i++) { + bool exists = false; + for (uint8_t j = 0; j < merged_count; j++) { + if (merged[j] != sources[i]) continue; + exists = true; + break; + } + if (exists) continue; + if (merged_count >= IGMP_MAX_QUERY_SOURCES) return; + merged[merged_count++] = sources[i]; + } + for (uint8_t i = 0; i < merged_count; i++) s->query_sources[i] = merged[i]; + s->query_source_count = merged_count; + } + uint32_t delay = max_ms ? rng_between32(&igmp_rng, 0, max_ms +1) : 0; + uint32_t now_ms = get_time(); + uint32_t due = now_ms + delay; + if (!s->query_pending || (int32_t)(due - s->query_due_ms) < 0) { s->query_pending = 1; s->query_due_ms = due; } igmp_daemon_kick(); } -void igmp_input(uint8_t ifindex, uint32_t src, uint32_t dst, const void* l4, uint32_t l4_len) { - if (!l4 || l4_len < sizeof(igmp_hdr_t)) return; - const igmp_hdr_t* h = (const igmp_hdr_t*)l4; - uint16_t saved = h->checksum; - igmp_hdr_t tmp; - memcpy(&tmp, h, sizeof(tmp)); - tmp.checksum = 0; - if (checksum16((const uint16_t*)&tmp, sizeof(tmp) / 2) != saved) return; +void igmp_input(uint8_t ifindex, uint32_t src, uint32_t dst, const uint8_t* ip_header, uint8_t ip_header_len, netpkt_t* pkt) { + if (!pkt) return; + uint32_t l4_len = netpkt_len(pkt); + if (l4_len < sizeof(igmp_hdr_t)) { + netpkt_unref(pkt); + return; + } + const uint8_t* p = (const uint8_t*)netpkt_data(pkt); + uint8_t hdr[12]; + if (!p || !netpkt_copyout(pkt, 0, hdr, sizeof(igmp_hdr_t))) { + netpkt_unref(pkt); + return; + } + if (checksum16(p, l4_len) != 0) { + netpkt_unref(pkt); + return; + } + + socket_raw_input_v4(PROTO_IGMP, ifindex, src, dst, pkt); - uint8_t type = h->type; - uint32_t group = bswap32(h->group); + ipv4_hdr_t ip = {0}; + if (ip_header && ip_header_len >= sizeof(ip)) memcpy(&ip, ip_header, sizeof(ip)); - uint32_t max_resp_ds = (uint32_t)h->max_resp_time; + uint8_t type = hdr[0]; + uint32_t group = rd_be32(hdr + 4); - if (type != IGMP_TYPE_QUERY) return; + if (type == IGMP_TYPE_V1_REPORT || type == IGMP_TYPE_V2_REPORT) { + if (ip.ttl == 1 && igmp_compat_mode(ifindex, get_time()) != 3) { + igmp_state_t* s = igmp_find_state(ifindex, group); + if (s) { + s->query_pending = 0; + s->unsolicited_left = 0; + s->last_reporter = 0; + } + } + netpkt_unref(pkt); + return; + } + + if (type != IGMP_TYPE_QUERY || ip.ttl != 1) { + netpkt_unref(pkt); + return; + } - if (group == 0) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return; - for (int i = 0; i < (int)l2->ipv4_mcast_count; ++i) { - uint32_t g = l2->ipv4_mcast[i]; - if (ipv4_is_multicast(g)) schedule_report(ifindex, g, max_resp_ds); + uint8_t version; + uint32_t max_ms; + uint16_t source_count = 0; + uint32_t sources[IGMP_MAX_QUERY_SOURCES]; + if (l4_len == sizeof(igmp_hdr_t)) { + if (hdr[1] == 0) { + version = 1; + max_ms = 10000; + } else { + version = 2; + max_ms = hdr[1] * 100; + } + } else if (l4_len >= sizeof(hdr)) { + version = 3; + if (!netpkt_copyout(pkt, 0, hdr, sizeof(hdr))) { + netpkt_unref(pkt); + return; + } + source_count = rd_be16(hdr + 10); + uint32_t expected = sizeof(hdr) + source_count * 4; + if (expected != l4_len || source_count > IGMP_MAX_QUERY_SOURCES) { + netpkt_unref(pkt); + return; + } + for (uint16_t i = 0; i < source_count; i++) { + uint8_t raw[4]; + if (!netpkt_copyout(pkt, sizeof(hdr) + i * 4, raw, sizeof(raw))) { + netpkt_unref(pkt); + return; + } + sources[i] = rd_be32(raw); } + uint8_t code = hdr[1]; + if (code < 128) max_ms = code * 100; + else { + uint32_t exp = (code >> 4) & 0x07; + uint32_t mant = code & 0x0F; + max_ms = ((mant | 0x10) << (exp + 3)) * 100; + } + } else { + netpkt_unref(pkt); return; } - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return; + if (version > 1) { + bool router_alert = false; + if (ip_header && ip_header_len > sizeof(ipv4_hdr_t)) { + uint32_t pos = sizeof(ipv4_hdr_t); + while (pos < ip_header_len) { + uint8_t opt = ip_header[pos]; + if (opt == 0) break; + if (opt == 1) { + pos++; + continue; + } + if (ip_header_len - pos < 2) break; - for (int i = 0; i < (int)l2->ipv4_mcast_count; ++i) { - if (l2->ipv4_mcast[i] == group) { - schedule_report(ifindex, group, max_resp_ds); + uint8_t len = ip_header[pos+1]; + if (len < 2 || len > ip_header_len - pos) break; + if (opt == IPV4_OPT_ROUTER_ALERT && len == 4 && !ip_header[pos + 2] && !ip_header[pos + 3]) { + router_alert = true; + break; + } + pos += len; + } + } + if (!router_alert) { + netpkt_unref(pkt); return; } } - (void)src; - (void)dst; -} \ No newline at end of file + if (group && !ipv4_is_multicast(group)) { + netpkt_unref(pkt); + return; + } + if (!group) { + if (source_count || (dst != IPV4_MCAST_ALL_HOSTS && !igmp_dest_assigned(ifindex, dst))) { + netpkt_unref(pkt); + return; + } + if (version < 3 && ifindex && ifindex <= MAX_L2_INTERFACES) { + igmp_if_state_t* s = &igmp_if_states[ifindex-1]; + uint32_t now_ms = get_time(); + uint8_t old_mode = igmp_compat_mode(ifindex, now_ms); + uint32_t until = now_ms + IGMP_DEFAULT_ROBUSTNESS * IGMP_DEFAULT_QUERY_INTERVAL_MS + max_ms; + + if (version == 1) s->v1_until_ms = until; + else if (version == 2) s->v2_until_ms = until; + if (igmp_compat_mode(ifindex, now_ms) != old_mode) { + for (int i = 0; i < (int)N_ARR(igmp_states); i++) { + igmp_state_t* state = &igmp_states[i]; + if (!state->used || state->ifindex != ifindex) continue; + state->query_pending = 0; + state->query_source_count = 0; + state->unsolicited_left = 0; + } + } + } + } else if (dst != group && !igmp_dest_assigned(ifindex, dst)) { + netpkt_unref(pkt); + return; + } + + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) { + netpkt_unref(pkt); + return; + } + + if (!group) { + + for (int i = 0; i < (int)l2->ipv4_mcast_count; i++) schedule_report(ifindex, l2->ipv4_mcast[i], max_ms, 0, 0); + } else { + for (int i = 0; i < (int)l2->ipv4_mcast_count; i++) { + if (l2->ipv4_mcast[i] != group) continue; + schedule_report(ifindex, group, max_ms, source_count ? sources : 0, source_count); + break; + } + } + + netpkt_unref(pkt); +} + +void igmp_l2_deleted(uint8_t ifindex) { + if (!ifindex || ifindex > MAX_L2_INTERFACES) return; + memset(&igmp_if_states[ifindex-1], 0, sizeof(igmp_if_states[0])); + for (int i = 0; i < IGMP_MAX_TRACK; i++) if (igmp_states[i].used && igmp_states[i].ifindex == ifindex) memset(&igmp_states[i], 0, sizeof(igmp_states[i])); +} diff --git a/kernel/networking/internet_layer/igmp.h b/kernel/networking/internet_layer/igmp.h index ae56c209..874db21d 100644 --- a/kernel/networking/internet_layer/igmp.h +++ b/kernel/networking/internet_layer/igmp.h @@ -1,13 +1,15 @@ #pragma once #include "types.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { #endif bool igmp_send_join(uint8_t ifindex, uint32_t group); +void igmp_l2_deleted(uint8_t ifindex); bool igmp_send_leave(uint8_t ifindex, uint32_t group); -void igmp_input(uint8_t ifindex, uint32_t src, uint32_t dst, const void* l4, uint32_t l4_len); +void igmp_input(uint8_t ifindex, uint32_t src, uint32_t dst, const uint8_t* ip_header, uint8_t ip_header_len, netpkt_t* pkt); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv4.c b/kernel/networking/internet_layer/ipv4.c index a8e7edf5..c03e0c65 100644 --- a/kernel/networking/internet_layer/ipv4.c +++ b/kernel/networking/internet_layer/ipv4.c @@ -1,6 +1,7 @@ #include "ipv4.h" #include "ipv4_route.h" #include "networking/link_layer/arp.h" +#include "networking/link_layer/link_utils.h" #include "networking/internet_layer/icmp.h" #include "networking/internet_layer/igmp.h" #include "std/memory.h" @@ -12,479 +13,473 @@ #include "ipv4_utils.h" #include "net/network_types.h" #include "networking/link_layer/nic_types.h" +#include "networking/net_fragbuf.h" +#include "networking/internet_layer/pmtu.h" +#include "networking/interface_manager.h" +#include "networking/network.h" static uint16_t g_ip_ident = 1; -static l3_ipv4_interface_t* best_v4_on_l2_for_dst(l2_interface_t* l2, uint32_t dst) { - l3_ipv4_interface_t* best = NULL; - uint32_t best_pl = -1; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (!v4->ip) continue; - uint32_t m = v4->mask; - if (m && (ipv4_net(dst, m) == ipv4_net(v4->ip, m))) { - uint32_t pl = ipv4_prefix_len(m); - if (pl > best_pl) { - best_pl = pl; - best = v4; - } - } else if (!best) { - best = v4; - } - } - return best; +#define IPV4_REASS_SLOTS 8 +typedef struct { + uint8_t used; + uint8_t ifindex; + uint8_t first_header_len; + uint16_t ident; + uint8_t proto; + uint32_t src; + uint32_t dst; + uint32_t last_update_ms; + uint8_t first_header[60]; + net_fragbuf_t frag; +} ipv4_reass_slot_t; + +static ipv4_reass_slot_t g_ipv4_reass[IPV4_REASS_SLOTS]; + +static void ipv4_reass_free(ipv4_reass_slot_t *s) { + if (!s) return; + net_fragbuf_free(&s->frag); + s->used = 0; + s->first_header_len = 0; } -static bool lookup_route_in_tables(uint32_t dst, uint32_t* out_nh, uint8_t* out_ifx, uint32_t* out_src) { - int best_pl = -1; - int best_metric = 0x7FFF; - uint32_t best_nh = 0; - uint8_t best_ifx = 0; - uint32_t best_src = 0; - - uint8_t cnt = l2_interface_count(); - for (uint8_t i = 0; i < cnt; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (!v4->routing_table) continue; - - uint32_t nh = 0; int pl = -1; int metric = 0; - if (ipv4_rt_lookup_in(v4->routing_table, dst, &nh, &pl, &metric)) { - if (pl > best_pl || (pl == best_pl && metric < best_metric)) { - best_pl = pl; - best_metric = metric; - best_nh = nh ? nh : dst; - best_ifx = l2->ifindex; - best_src = v4->ip; - } - } - } +bool ipv4_send_packet(uint32_t dst_ip, uint8_t proto, netpkt_t* pkt, const ip_tx_opts_t* opts, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute) { + if (!pkt) return false; + uint32_t seg_len = netpkt_len(pkt); + if (!seg_len || seg_len > UINT16_MAX - sizeof(ipv4_hdr_t)) { + netpkt_unref(pkt); + return false; } - if (best_pl >= 0) { - if (out_nh) *out_nh = best_nh; - if (out_ifx) *out_ifx = best_ifx; - if (out_src) *out_src = best_src; - return true; - } - return false; -} - -static bool pick_broadcast_bound_l3(uint8_t l3_id, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); - if (!v4 || !v4->l2) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - - if (out_ifx) *out_ifx = v4->l2->ifindex; - if (out_src) *out_src = v4->ip; - if (!v4->ip && v4->mode == IPV4_CFG_DHCP && out_src) *out_src = 0; - if (out_nh) *out_nh = 0xFFFFFFFFu; - return true; -} + uint8_t ifx = 0; + uint32_t src_ip = 0; + uint32_t nh = dst_ip; + l3_ipv4_interface_t* src_v4 = NULL; + bool limited = ipv4_is_limited_broadcast(dst_ip); + bool multicast = ipv4_is_multicast(dst_ip); -static bool pick_broadcast_bound_l2(uint8_t ifindex, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return false; + if (limited) { + if (!opts || opts->scope != IP_TX_BOUND_L3) { + netpkt_unref(pkt); + return false; + } - l3_ipv4_interface_t* chosen = NULL; - l3_ipv4_interface_t* dhcp = NULL; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->ip) { chosen = v4; break; } - if (!v4->ip && v4->mode == IPV4_CFG_DHCP && !dhcp) dhcp = v4; - } - if (!chosen) chosen = dhcp; + src_v4 = l3_ipv4_find_by_id(opts->target.l3_id); + if (!ipv4_l3_is_active(src_v4)) { + netpkt_unref(pkt); + return false; + } - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) *out_src = (chosen && chosen->ip) ? chosen->ip : 0; - if (out_nh) *out_nh = 0xFFFFFFFFu; - return true; -} + ifx = src_v4->l2->ifindex; + src_ip = src_v4->ip; + nh = IPV4_LIMITED_BROADCAST; + } else { + ipv4_tx_plan_t plan; + if (!ipv4_build_tx_plan(dst_ip, opts, &plan)) { + netpkt_unref(pkt); + return false; + } -static bool pick_broadcast_global(uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - l3_ipv4_interface_t* static_cand = NULL; - l3_ipv4_interface_t* dhcp_cand = NULL; - l2_interface_t* l2_s = NULL; - l2_interface_t* l2_d = NULL; - - uint8_t n = l2_interface_count(); - for (uint8_t i = 0; i < n; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; s++) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->ip && !static_cand) { static_cand = v4; l2_s = l2; } - if (!v4->ip && v4->mode == IPV4_CFG_DHCP && !dhcp_cand) { dhcp_cand = v4; l2_d = l2; } + src_v4 = l3_ipv4_find_by_id(plan.l3_id); + if (!ipv4_l3_is_ready(src_v4)) { + netpkt_unref(pkt); + return false; } - } - l3_ipv4_interface_t* pick = static_cand ? static_cand : dhcp_cand; - l2_interface_t* l2 = static_cand ? l2_s : l2_d; - if (!l2) return false; + ifx = src_v4->l2->ifindex; + src_ip = plan.src_ip; - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) *out_src = (pick && pick->ip) ? pick->ip : 0; - if (out_nh) *out_nh = 0xFFFFFFFFu; - return true; -} + uint32_t route_nh = 0; + bool have_nh = false; -static bool pick_route_global(uint32_t dst, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - if (ipv4_is_limited_broadcast(dst)) return pick_broadcast_global(out_ifx, out_src, out_nh); - - ip_resolution_result_t r = resolve_ipv4_to_interface(dst); - if (r.found && r.ipv4 && r.l2) { - uint32_t m = r.ipv4->mask; - if (m && (ipv4_net(dst, m) == ipv4_net(r.ipv4->ip, m))) { - if (out_ifx) *out_ifx = r.l2->ifindex; - if (out_src) *out_src = r.ipv4->ip; - if (out_nh) *out_nh = dst; - return true; - } - if (r.ipv4->gw) { - if (out_ifx) *out_ifx = r.l2->ifindex; - if (out_src) *out_src = r.ipv4->ip; - if (out_nh) *out_nh = r.ipv4->gw; - return true; - } - if (r.ipv4->routing_table) { - uint32_t nh = 0; int pl = -1; int metric = 0; - if (ipv4_rt_lookup_in(r.ipv4->routing_table, dst, &nh, &pl, &metric)) { - if (out_ifx) *out_ifx = r.l2->ifindex; - if (out_src) *out_src = r.ipv4->ip; - if (out_nh) *out_nh = nh ? nh : dst; - return true; + if (dontroute) { + if (!ipv4_tx_plan_onlink(&plan, dst_ip)) { + netpkt_unref(pkt); + return false; } + nh = dst_ip; + have_nh = true; } - } - return lookup_route_in_tables(dst, out_nh, out_ifx, out_src); -} - -static bool pick_route_bound_l3(uint8_t l3_id, uint32_t dst, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - if (ipv4_is_limited_broadcast(dst)) return pick_broadcast_bound_l3(l3_id, out_ifx, out_src, out_nh); - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); - if (!v4 || !v4->l2) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - if (!v4->ip) return false; - - uint32_t m = v4->mask; - if (m && (ipv4_net(dst, m) == ipv4_net(v4->ip, m))) { - if (out_ifx) *out_ifx = v4->l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = dst; - return true; - } - if (v4->gw) { - if (out_ifx) *out_ifx = v4->l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = v4->gw; - return true; - } - if (v4->routing_table) { - uint32_t nh = 0; int pl = -1; int metric = 0; - if (ipv4_rt_lookup_in(v4->routing_table, dst, &nh, &pl, &metric)) { - if (out_ifx) *out_ifx = v4->l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = nh ? nh : dst; - return true; + if (!have_nh && src_v4->routing_table && ipv4_rt_lookup_in(src_v4->routing_table, dst_ip, &route_nh, NULL, NULL)) { + nh = route_nh ? route_nh : dst_ip; + have_nh = true; } - } - return false; -} - -static bool pick_route_bound_l2(uint8_t ifindex, uint32_t dst, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - if (ipv4_is_limited_broadcast(dst)) return pick_broadcast_bound_l2(ifindex, out_ifx, out_src, out_nh); - - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return false; - - l3_ipv4_interface_t* v4 = best_v4_on_l2_for_dst(l2, dst); - if (!v4) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - - uint32_t m = v4->mask; - if (m && (ipv4_net(dst, m) == ipv4_net(v4->ip, m))) { - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = dst; - return true; - } - if (v4->gw) { - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = v4->gw; - return true; - } - if (v4->routing_table) { - uint32_t nh = 0; int pl = -1; int metric = 0; - if (ipv4_rt_lookup_in(v4->routing_table, dst, &nh, &pl, &metric)) { - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) *out_src = v4->ip; - if (out_nh) *out_nh = nh ? nh : dst; - return true; + if (!have_nh && src_v4->mask && ipv4_net(dst_ip, src_v4->mask) == ipv4_net(src_v4->ip, src_v4->mask)) { + nh = dst_ip; + have_nh = true; + } + if (!have_nh && src_v4->gw) { + nh = src_v4->gw; + have_nh = true; + } + if (!have_nh) { + netpkt_unref(pkt); + return false; } } - return false; -} -static bool pick_route(uint32_t dst, const ipv4_tx_opts_t* opts, uint8_t* out_ifx, uint32_t* out_src, uint32_t* out_nh) { - if (opts) { - if (opts->scope == IP_TX_BOUND_L3) return pick_route_bound_l3(opts->index, dst, out_ifx, out_src, out_nh); - if (opts->scope == IP_TX_BOUND_L2) return pick_route_bound_l2(opts->index, dst, out_ifx, out_src, out_nh); - return pick_route_global(dst, out_ifx, out_src, out_nh); + l2_interface_t* l2 = src_v4->l2; + uint8_t dst_mac[MAC_ADDR_LEN]; + bool need_arp = false; + bool is_dbcast = src_v4->mask && nh == dst_ip && ipv4_broadcast_calc(src_v4->ip, src_v4->mask) == dst_ip; + if (proto == PROTO_TCP && (multicast || limited || is_dbcast)) { + netpkt_unref(pkt); + return false; } - return pick_route_global(dst, out_ifx, out_src, out_nh); -} + if (limited || is_dbcast) mac_set_broadcast(dst_mac); + else if (multicast) ipv4_mcast_to_mac(dst_ip, dst_mac); + else if (l2->link_kind == NET_LINK_LOOPBACK) mac_clear(dst_mac); + else need_arp = true; -void ipv4_send_packet(uint32_t dst_ip, uint8_t proto, netpkt_t* pkt, const ipv4_tx_opts_t* opts, uint8_t ttl, uint8_t dontfrag) { - if (!pkt || !netpkt_len(pkt)) { - if (pkt) netpkt_unref(pkt); - return; + uint16_t mtu = l3_ipv4_effective_mtu(src_v4); + if (proto != PROTO_TCP && !multicast && !limited && !is_dbcast) { + uint16_t path_mtu = pmtu_get(src_v4->l3_id, src_v4->epoch, IP_VER4, &dst_ip); + if (path_mtu && path_mtu < mtu) mtu = path_mtu; } + uint32_t hdr_len = IP_IHL_NOOPTS * 4; + uint32_t total = hdr_len + seg_len; - uint8_t ifx = 0; - uint32_t src_ip = 0; - uint32_t nh = 0; - if (!pick_route(dst_ip, opts, &ifx, &src_ip, &nh)) { + if (mtu < hdr_len + 8) { netpkt_unref(pkt); - return; + return false; } - uint8_t dst_mac[6]; - bool is_dbcast = false; - l2_interface_t* l2 = l2_interface_find_by_index(ifx); - if (l2) { - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4 || v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->mask && ipv4_broadcast_calc(v4->ip, v4->mask) == dst_ip) { is_dbcast = true; break; } - } - } - - if (is_dbcast) { - memset(dst_mac, 0xFF, 6); - } else if (ipv4_is_multicast(dst_ip)) { - ipv4_mcast_to_mac(dst_ip, dst_mac); - } else { - if (l2 && l2->kind == NET_IFK_LOCALHOST) { - memset(dst_mac, 0, 6); - } else if (!arp_resolve_on(ifx, nh, dst_mac, 1000)) { + if (total <= (uint32_t)mtu) { + void* hdrp = netpkt_push(pkt, hdr_len); + if (!hdrp) { netpkt_unref(pkt); - return; + return false; } - } - uint16_t mtu = 1500; - if (l2) { - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->ip != src_ip) continue; - if (v4->runtime_opts_v4.mtu) mtu = v4->runtime_opts_v4.mtu; - break; - } + ipv4_hdr_t ip; + ip.version_ihl = (uint8_t)((IP_VER4 << 4) | IP_IHL_NOOPTS); + ip.dscp_ecn = 0; + ip.total_length = bswap16((uint16_t)total); + ip.identification = bswap16(g_ip_ident++); + uint16_t ff = 0; + if (dontfrag) ff |= 0x4000; + ip.flags_frag_offset = bswap16(ff); + ip.ttl = ttl ? ttl : IP_TTL_DEFAULT; + ip.protocol = proto; + ip.header_checksum = 0; + ip.src_ip = bswap32(src_ip); + ip.dst_ip = bswap32(dst_ip); + ip.header_checksum = bswap16(checksum16(&ip, hdr_len)); + memcpy(hdrp, &ip, sizeof(ip)); + + if (need_arp) return arp_send_or_queue_on(ifx, nh, pkt); + return eth_send_frame_on(ifx, ETHERTYPE_IPV4, dst_mac, pkt); } - uint32_t hdr_len = IP_IHL_NOOPTS * 4; - uint32_t seg_len = netpkt_len(pkt); - void* hdrp = netpkt_push(pkt, hdr_len); - if (!hdrp) { + if (dontfrag) { netpkt_unref(pkt); - return; + return false; } - uint32_t total = hdr_len + seg_len; - if (dontfrag && total > (uint32_t)mtu) { + uint32_t max_chunk = ((uint32_t)mtu - hdr_len) / 8 * 8; + if (!max_chunk) { netpkt_unref(pkt); - return; + return false; } - ipv4_hdr_t* ip = (ipv4_hdr_t*)hdrp; - ip->version_ihl = (uint8_t)((IP_VERSION_4 << 4) | IP_IHL_NOOPTS); - ip->dscp_ecn = 0; - ip->total_length = bswap16((uint16_t)total); - ip->identification = bswap16(g_ip_ident++); - uint16_t ff = 0; - if (dontfrag) ff |= 0x4000u; - ip->flags_frag_offset = bswap16(ff); - ip->ttl = ttl ? ttl : IP_TTL_DEFAULT; - ip->protocol = proto; - ip->header_checksum = 0; - ip->src_ip = bswap32(src_ip); - ip->dst_ip = bswap32(dst_ip); - ip->header_checksum = checksum16((const uint16_t*)ip, hdr_len / 2); - - eth_send_frame_on(ifx, ETHERTYPE_IPV4, dst_mac, pkt); -} -void ipv4_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { - if (!pkt) return; - uint32_t ip_len = netpkt_len(pkt); - uintptr_t ip_ptr = netpkt_data(pkt); - if (ip_len < sizeof(ipv4_hdr_t)) return; + uint16_t ident = g_ip_ident++; + ipv4_hdr_t ip; + ip.version_ihl = (uint8_t)((IP_VER4 << 4) | IP_IHL_NOOPTS); + ip.dscp_ecn = 0; + ip.identification = bswap16(ident); + ip.ttl = ttl ? ttl : IP_TTL_DEFAULT; + ip.protocol = proto; + ip.src_ip = bswap32(src_ip); + ip.dst_ip = bswap32(dst_ip); + + uint32_t off = 0; + const uint8_t* data = (const uint8_t*)netpkt_data(pkt); + bool ok = true; + + while (off < seg_len) { + uint32_t remain = seg_len - off; + uint32_t chunk = remain > max_chunk ? max_chunk : remain; + uint8_t more = off + chunk < seg_len ? 1 : 0; + uint32_t frame_len = hdr_len + chunk; + + netpkt_t* fpkt = netpkt_alloc(frame_len, sizeof(eth_hdr_t), 0); + if (!fpkt) { + ok = false; + break; + } - ipv4_hdr_t* ip = (ipv4_hdr_t*)ip_ptr; - uint8_t ver = (uint8_t)(ip->version_ihl >> 4); - uint8_t ihl = (uint8_t)(ip->version_ihl & 0x0F); - if (ver != IP_VERSION_4) return; - if (ihl < IP_IHL_NOOPTS) return; + void* buf = netpkt_put(fpkt, frame_len); + if (!buf) { + netpkt_unref(fpkt); + ok = false; + break; + } - uint32_t hdr_len = (uint32_t)ihl * 4; - if (ip_len < hdr_len) return; + ip.total_length = bswap16((uint16_t)frame_len); + uint16_t ff = (uint16_t)((off / 8) & 0x1FFF); + if (more) ff |= 0x2000; + ip.flags_frag_offset = bswap16(ff); + ip.header_checksum = 0; + ip.header_checksum = bswap16(checksum16(&ip, hdr_len)); - uint16_t saved = ip->header_checksum; - ip->header_checksum = 0; - if (checksum16((const uint16_t*)ip, hdr_len / 2) != saved) { - ip->header_checksum = saved; - return; - } - ip->header_checksum = saved; + memcpy(buf, &ip, sizeof(ip)); + memcpy((uint8_t*)buf + hdr_len, data + off, chunk); - uint16_t ip_totlen = bswap16(ip->total_length); - if (ip_totlen < hdr_len) return; - if (ip_len < ip_totlen) return; - (void)netpkt_trim(pkt, ip_totlen); - ip_len = ip_totlen; + if (need_arp) { + if (!arp_send_or_queue_on(ifx, nh, fpkt)) ok = false; + } else if (!eth_send_frame_on(ifx, ETHERTYPE_IPV4, dst_mac, fpkt)) ok = false; - uintptr_t l4 = ip_ptr + hdr_len; - uint32_t l4_len = (uint32_t)ip_totlen - hdr_len; + off += chunk; + } - uint32_t src = bswap32(ip->src_ip); - uint32_t dst = bswap32(ip->dst_ip); + netpkt_unref(pkt); + return ok && off == seg_len; +} - if (ifindex && src) { - uint8_t mac_old[6]; - bool had = arp_table_get_for_l2((uint8_t)ifindex, src, mac_old); - if (!had || memcmp(mac_old, src_mac, 6) != 0) { - arp_table_put_for_l2((uint8_t)ifindex, src, src_mac, 180000, false); - } else { - arp_table_put_for_l2((uint8_t)ifindex, src, mac_old, 180000, false); - } +static bool ipv4_deliver_transport(uint8_t proto, uint32_t src, uint32_t dst, l3_id_t l3_id, netpkt_t *l4pkt, bool include_shared, udp_input_result_t *udp_result, bool* checksum_done) { + if (proto == PROTO_TCP) { + tcp_input(IP_VER4, &src, &dst, l3_id, l4pkt); + return true; } + if (proto == PROTO_UDP) { + udp_input_result_t result = udp_input(IP_VER4, &src, &dst, l3_id, l4pkt, include_shared, checksum_done); + if (udp_result) *udp_result = result; + return true; + } + return false; +} - uint8_t proto = ip->protocol; - - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); +static void ipv4_deliver_l4(uint8_t ifindex, netpkt_t* pkt, uint32_t l4_off, uint32_t l4_len, uint8_t proto, uint32_t src, uint32_t dst, const uint8_t *ip_header, uint8_t ip_header_len) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (!l2) return; l3_ipv4_interface_t* cand[MAX_IPV4_PER_INTERFACE]; int ccount = 0; for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; + if (!ipv4_l3_is_active(v4)) continue; cand[ccount++] = v4; } - if (ccount == 0) return; + if (ccount == 0) return; + bool checksum_done = false; if (ipv4_is_multicast(dst)) { + if (proto == PROTO_TCP) return; bool joined = false; - for (int i = 0; i < (int)l2->ipv4_mcast_count; ++i) if (l2->ipv4_mcast[i] == dst) { - joined = true; - break; + for (int i = 0; i < (int)l2->ipv4_mcast_count; ++i) { + if (l2->ipv4_mcast[i] == dst) { + joined = true; + break; + } } if (!joined) return; + if (proto == PROTO_IGMP) { + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (l4pkt) igmp_input(ifindex, src, dst, ip_header, ip_header_len, l4pkt); + return; + } + + bool include_shared = true; for (int i = 0; i < ccount; ++i) { - uint8_t l3id = cand[i]->l3_id; - switch (proto) { - case 2: igmp_input((uint8_t)ifindex, src, dst, (const void*)l4, l4_len); break; - case 6: tcp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - default: break; - } + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) continue; + l3_id_t l3id = cand[i]->l3_id; + if (!ipv4_deliver_transport(proto, src, dst, l3id, l4pkt, include_shared, NULL, &checksum_done)) netpkt_unref(l4pkt); + include_shared = false; } return; } - - if (dst == 0xFFFFFFFFu) { - if (ccount == 1) { - uint8_t l3id = cand[0]->l3_id; - switch (proto) { - case 1: icmp_input(l4, l4_len, src, dst); break; - case 6: tcp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - default: break; - } - return; - } else { - for (int i = 0; i < ccount; ++i) { - uint8_t l3id = cand[i]->l3_id; - switch (proto) { - case 1: icmp_input(l4, l4_len, src, dst); break; - case 6: tcp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - default: break; - } + if (dst == IPV4_LIMITED_BROADCAST) { + if (proto == PROTO_TCP) return; + if (proto == PROTO_IGMP || proto == PROTO_ICMP) { + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (l4pkt) { + if (proto == PROTO_IGMP) igmp_input(ifindex, src, dst, ip_header, ip_header_len, l4pkt); + else icmp_input(ifindex, l4pkt, src, dst); } return; } + bool include_shared = true; + for (int i = 0; i < ccount; ++i) { + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) continue; + l3_id_t l3id = cand[i]->l3_id; + if (!ipv4_deliver_transport(proto, src, dst, l3id, l4pkt, include_shared, NULL, &checksum_done)) netpkt_unref(l4pkt); + include_shared = false; + } + return; } int match_count = 0; - uint8_t match_l3id = 0; + l3_id_t match_l3id = 0; for (int i = 0; i < ccount; ++i) { if (cand[i]->ip && cand[i]->ip == dst) { match_count++; - match_l3id = cand[i]->l3_id; + if (match_count == 1) match_l3id = cand[i]->l3_id; } } + if (match_count == 1) { - switch (proto) { - case 1: icmp_input(l4, l4_len, src, dst); break; - case 6: tcp_input(IP_VER4, &src, &dst, match_l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, match_l3id, l4, l4_len); break; - default: break; + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) return; + if (proto == PROTO_ICMP) icmp_input(ifindex, l4pkt, src, dst); + else if (proto == PROTO_IGMP) igmp_input(ifindex, src, dst, ip_header, ip_header_len, l4pkt); + else { + udp_input_result_t result = UDP_INPUT_DROP; + if (!ipv4_deliver_transport(proto, src, dst, match_l3id, l4pkt, true, &result, &checksum_done)) netpkt_unref(l4pkt); + else if (proto == PROTO_UDP && result == UDP_INPUT_NO_LISTENER && ip_header) icmp_send_port_unreachable(match_l3id, src, ip_header, ip_header_len, pkt, l4_off, l4_len); } return; } + if (match_count > 1) { - for (int i = 0; i < ccount; ++i) { - if (cand[i]->ip == dst) { - uint8_t l3id = cand[i]->l3_id; - switch (proto) { - case 1: icmp_input(l4, l4_len, src, dst); break; - case 6: tcp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - default: break; - } + if (proto == PROTO_IGMP || proto == PROTO_ICMP) { + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (l4pkt) { + if (proto == PROTO_IGMP) igmp_input(ifindex, src, dst, ip_header, ip_header_len, l4pkt); + else icmp_input(ifindex, l4pkt, src, dst); } + return; + } + bool include_shared = true; + for (int i = 0; i < ccount; ++i) { + if (cand[i]->ip != dst) continue; + + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) continue; + l3_id_t l3id = cand[i]->l3_id; + if (!ipv4_deliver_transport(proto, src, dst, l3id, l4pkt, include_shared, NULL, &checksum_done)) netpkt_unref(l4pkt); + include_shared = false; } return; } - int any_dbcast = 0; - for (uint8_t i = 0, n = l2_interface_count(); i < n; ++i) { - l2_interface_t* l2x = l2_interface_at(i); - if (!l2x) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2x->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->mask && ipv4_broadcast_calc(v4->ip, v4->mask) == dst) { - any_dbcast = 1; - uint8_t l3id = v4->l3_id; - switch (proto) { - case 1: icmp_input(l4, l4_len, src, dst); break; - case 6: tcp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - case 17: udp_input(IP_VER4, &src, &dst, l3id, l4, l4_len); break; - default: break; - } + if (proto == PROTO_TCP) return; + bool include_shared = true; + for (int i = 0; i < ccount; ++i) { + l3_ipv4_interface_t* v4 = cand[i]; + if (!v4 || !v4->ip || !v4->mask) continue; + if (v4->is_localhost) continue; + if (ipv4_broadcast_calc(v4->ip, v4->mask) != dst) continue; + + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) { + if (proto != PROTO_UDP) return; + continue; + } + if (proto == PROTO_ICMP) icmp_input(ifindex, l4pkt, src, dst); + else if (!ipv4_deliver_transport(proto, src, dst, v4->l3_id, l4pkt, include_shared, NULL, &checksum_done)) netpkt_unref(l4pkt); + if (proto != PROTO_UDP) return; + include_shared = false; + } +} + +void ipv4_input(uint8_t ifindex, netpkt_t* pkt, const uint8_t src_mac[MAC_ADDR_LEN]) { + if (!pkt) return; + uint32_t ip_len = netpkt_len(pkt); + if (ip_len < sizeof(ipv4_hdr_t)) return; + + uint8_t hdr_copy[60]; + if (!netpkt_copyout(pkt, 0, hdr_copy, sizeof(ipv4_hdr_t))) return; + uint8_t ver = (uint8_t)(hdr_copy[0] >> 4); + uint8_t ihl = (uint8_t)(hdr_copy[0] & 0x0F); + if (ver != IP_VER4 || ihl < IP_IHL_NOOPTS) return; + + uint32_t hdr_len = (uint32_t)ihl * 4; + if (ip_len < hdr_len) return; + if (hdr_len > sizeof(ipv4_hdr_t) && !netpkt_copyout(pkt, sizeof(ipv4_hdr_t), hdr_copy + sizeof(ipv4_hdr_t), hdr_len - sizeof(ipv4_hdr_t))) return; + if (checksum16(hdr_copy, hdr_len) != 0) return; + ipv4_hdr_t ip; + memcpy(&ip, hdr_copy, sizeof(ip)); + + uint16_t ip_totlen = bswap16(ip.total_length); + if (ip_totlen < hdr_len) return; + if (ip_len < ip_totlen) return; + (void)netpkt_trim(pkt, ip_totlen); + + uint32_t src = bswap32(ip.src_ip); + uint32_t dst = bswap32(ip.dst_ip); + + if (ipv4_is_loopback(src)) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || l2->link_kind != NET_LINK_LOOPBACK) return; + } + + uint8_t proto = ip.protocol; + uint32_t l4_len = (uint32_t)ip_totlen - hdr_len; + uint16_t ff = bswap16(ip.flags_frag_offset); + if (ff & 0x8000u) return; + uint32_t off = (uint32_t)(ff & 0x1FFF) * 8; + uint8_t more = (ff & 0x2000) ? 1 : 0; + if ((ff & 0x4000u) && (off || more)) return; + + if (off || more) { + if (!l4_len) return; + if (more && (l4_len & 7)) return; + if (off + l4_len > NET_FRAGBUF_DEFAULT_MAX_LEN) return; + + uint32_t now = (uint32_t)get_time(); + ipv4_reass_slot_t* slot = NULL; + uint16_t ident = bswap16(ip.identification); + + for (int i = 0; i < IPV4_REASS_SLOTS; i++) { + ipv4_reass_slot_t* s = &g_ipv4_reass[i]; + if (!s->used) continue; + if (now - s->last_update_ms > 60000) { + ipv4_reass_free(s); + continue; + } + if (s->ifindex == ifindex && s->ident == ident && s->proto == proto && s->src == src && s->dst == dst) slot = s; + } + + if (!slot) { + for (int i = 0; i < IPV4_REASS_SLOTS; i++) { + if (g_ipv4_reass[i].used) continue; + slot = &g_ipv4_reass[i]; + slot->used = 1; + net_fragbuf_init(&slot->frag); + slot->ifindex = ifindex; + slot->ident = ident; + slot->proto = proto; + slot->src = src; + slot->dst = dst; + slot->last_update_ms = now; + break; } } + + if (!slot) return; + if (!net_fragbuf_add(&slot->frag, pkt, hdr_len, off, l4_len, more)) { + ipv4_reass_free(slot); + return; + } + + if (!off) { + slot->first_header_len = (uint8_t)hdr_len; + memcpy(slot->first_header, hdr_copy, hdr_len); + } + + slot->last_update_ms = now; + + if (!net_fragbuf_complete(&slot->frag)) return; + + netpkt_t* reassembled = net_fragbuf_take_packet(&slot->frag); + if (!reassembled) { + ipv4_reass_free(slot); + return; + } + + uint8_t first_header[60]; + uint8_t first_header_len = slot->first_header_len; + if (first_header_len) memcpy(first_header, slot->first_header, first_header_len); + ipv4_reass_free(slot); + ipv4_deliver_l4(ifindex, reassembled, 0, netpkt_len(reassembled), proto, src, dst, first_header_len ? first_header : NULL, first_header_len); + netpkt_unref(reassembled); + return; } - if (any_dbcast) return; - return; + ipv4_deliver_l4(ifindex, pkt, hdr_len, l4_len, proto, src, dst, hdr_copy, (uint8_t)hdr_len); } diff --git a/kernel/networking/internet_layer/ipv4.h b/kernel/networking/internet_layer/ipv4.h index 54638eac..b8e269b2 100644 --- a/kernel/networking/internet_layer/ipv4.h +++ b/kernel/networking/internet_layer/ipv4.h @@ -4,11 +4,9 @@ #include "networking/link_layer/eth.h" #include "net/network_types.h" #include "net/checksums.h" -#include "networking/interface_manager.h" #include "networking/netpkt.h" #define IP_IHL_NOOPTS 5 -#define IP_VERSION_4 4 #define IP_TTL_DEFAULT 64 #ifdef __cplusplus @@ -28,11 +26,9 @@ typedef struct __attribute__((packed)) ipv4_hdr_t { uint32_t dst_ip; } ipv4_hdr_t; -typedef ip_tx_scope_t ipv4_tx_scope_t; -typedef ip_tx_opts_t ipv4_tx_opts_t; -void ipv4_send_packet(uint32_t dst_ip, uint8_t proto, netpkt_t* pkt, const ipv4_tx_opts_t* opts, uint8_t ttl, uint8_t dontfrag); -void ipv4_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]); +bool ipv4_send_packet(uint32_t dst_ip, uint8_t proto, netpkt_t* pkt, const ip_tx_opts_t* opts, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute); +void ipv4_input(uint8_t ifindex, netpkt_t* pkt, const uint8_t src_mac[MAC_ADDR_LEN]); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv4_route.c b/kernel/networking/internet_layer/ipv4_route.c index 71b67e0a..948bfb1e 100644 --- a/kernel/networking/internet_layer/ipv4_route.c +++ b/kernel/networking/internet_layer/ipv4_route.c @@ -1,124 +1,124 @@ #include "ipv4_route.h" #include "std/memory.h" #include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4_utils.h" #include "syscalls/syscalls.h" -static bool v4_l3_ok_for_tx(l3_ipv4_interface_t* v4){ - if (!v4 || !v4->l2) return false; - if (!v4->l2->is_up) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - if (v4->is_localhost) return false; - if (!v4->ip) return false; - if (!v4->port_manager) return false; - return true; +struct ipv4_rt_table { + l3_id_t owner_l3_id; + uint32_t epoch; + ipv4_rt_entry_t e[IPV4_RT_PER_IF_MAX]; + int len; +}; + +static void ipv4_rt_bump(ipv4_rt_table_t* t) { + if (!t) return; + t->epoch++; + if (!t->epoch) t->epoch = 1; } -static bool l3_allowed(uint8_t id, const uint8_t* allowed, int n){ - if (!allowed || n <= 0) return true; - for (int i = 0; i < n; ++i) if (allowed[i] == id) return true; - return false; +bool ipv4_tx_plan_valid(const ipv4_tx_plan_t* plan) { + if (!plan || !plan->l3_id) return false; + + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(plan->l3_id); + if (!ipv4_l3_is_ready(v4)) return false; + if (v4->epoch != plan->l3_epoch) return false; + return v4->ip == plan->src_ip; +} + +bool ipv4_tx_plan_onlink(const ipv4_tx_plan_t* plan, uint32_t dst) { + if (!ipv4_tx_plan_valid(plan)) return false; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(plan->l3_id); + if (ipv4_is_loopback(dst)) return v4->is_localhost; + if (ipv4_is_multicast(dst) || ipv4_is_limited_broadcast(dst) || dst == v4->broadcast) return true; + return v4->mask && (dst & v4->mask) == (v4->ip & v4->mask); } -bool ipv4_build_tx_plan(uint32_t dst, const ip_tx_opts_t* hint, const uint8_t* allowed_l3, int allowed_n, ipv4_tx_plan_t* out){ +bool ipv4_build_tx_plan(uint32_t dst, const ip_tx_opts_t* hint, ipv4_tx_plan_t* out) { if (!out) return false; out->l3_id = 0; + out->l3_epoch = 0; out->src_ip = 0; - out->fixed_opts.scope = IP_TX_AUTO; - out->fixed_opts.index = 0; if (hint && hint->scope == IP_TX_BOUND_L3) { - uint8_t id = hint->index; - if (!l3_allowed(id, allowed_l3, allowed_n)) return false; + l3_id_t id = hint->target.l3_id; l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (!v4_l3_ok_for_tx(v4)) return false; + if (!ipv4_l3_is_ready(v4) || ipv4_is_loopback(dst) != v4->is_localhost) return false; out->l3_id = id; + out->l3_epoch = v4->epoch; out->src_ip = v4->ip; - out->fixed_opts.scope = IP_TX_BOUND_L3; - out->fixed_opts.index = id; return true; } - uint8_t cand[64]; - int n = 0; - + uint8_t ifindex = 0; if (hint && hint->scope == IP_TX_BOUND_L2) { - l2_interface_t* l2 = l2_interface_find_by_index(hint->index); + l2_interface_t* l2 = l2_interface_find_by_index(hint->target.ifindex); if (!l2 || !l2->is_up) return false; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE && n < (int)sizeof(cand); ++s){ - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4_l3_ok_for_tx(v4)) continue; - if (!l3_allowed(v4->l3_id, allowed_l3, allowed_n)) continue; - cand[n++] = v4->l3_id; - } - } else { - uint8_t cnt = l2_interface_count(); - for (uint8_t i = 0; i < cnt && n < (int)sizeof(cand); ++i){ - l2_interface_t* l2 = l2_interface_at(i); + ifindex = l2->ifindex; + } + + l3_id_t chosen = 0; + if (!ipv4_rt_pick_best_l3(dst, ifindex, &chosen)) { + uint8_t first = ifindex ? ifindex : 1; + uint8_t last = ifindex ? ifindex : MAX_L2_INTERFACES; + for (uint8_t ix = first; ix <= last && !chosen; ix++) { + l2_interface_t* l2 = l2_interface_find_by_index(ix); if (!l2 || !l2->is_up) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE && n < (int)sizeof(cand); ++s){ - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4_l3_ok_for_tx(v4)) continue; - if (!l3_allowed(v4->l3_id, allowed_l3, allowed_n)) continue; - cand[n++] = v4->l3_id; + for (int slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[slot]; + if (!ipv4_l3_is_ready(v4) || ipv4_is_loopback(dst) != v4->is_localhost) continue; + chosen = v4->l3_id; + break; } } + if (!chosen) return false; } - if (n == 0) return false; - - uint8_t chosen = 0; - if (!ipv4_rt_pick_best_l3_in(cand, n, dst, &chosen)) chosen = cand[0]; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(chosen); - if (!v4_l3_ok_for_tx(v4)) return false; + if (!ipv4_l3_is_ready(v4) || ipv4_is_loopback(dst) != v4->is_localhost) return false; out->l3_id = chosen; + out->l3_epoch = v4->epoch; out->src_ip = v4->ip; - out->fixed_opts.scope = IP_TX_BOUND_L3; - out->fixed_opts.index = chosen; return true; } -struct ipv4_rt_table { - ipv4_rt_entry_t e[IPV4_RT_PER_IF_MAX]; - int len; -}; -static int prefix_len(uint32_t m) { - int n = 0; - while (m & 0x80000000u) { n++; m <<= 1; } - return n; -} - -ipv4_rt_table_t* ipv4_rt_create(void) { - ipv4_rt_table_t* t = (ipv4_rt_table_t*)malloc(sizeof(ipv4_rt_table_t)); +ipv4_rt_table_t* ipv4_rt_create(l3_id_t owner_l3_id) { + ipv4_rt_table_t* t = (ipv4_rt_table_t*)zalloc(sizeof(ipv4_rt_table_t)); if (!t) return 0; - memset(t, 0, sizeof(*t)); + t->owner_l3_id = owner_l3_id; + t->epoch = 1; return t; } void ipv4_rt_destroy(ipv4_rt_table_t* t) { if (!t) return; - free_sized(t, sizeof(*t)); + release(t); } void ipv4_rt_clear(ipv4_rt_table_t* t) { if (!t) return; + bool changed = t->len != 0; t->len = 0; memset(t->e, 0, sizeof(t->e)); + if (changed) ipv4_rt_bump(t); } bool ipv4_rt_add_in(ipv4_rt_table_t* t, uint32_t network, uint32_t mask, uint32_t gateway, uint16_t metric) { if (!t) return false; for (int i = 0; i < t->len; i++) { if (t->e[i].network == network && t->e[i].mask == mask) { + if (t->e[i].gateway == gateway && t->e[i].metric == metric) return true; t->e[i].gateway = gateway; t->e[i].metric = metric; + ipv4_rt_bump(t); return true; } } if (t->len >= IPV4_RT_PER_IF_MAX) return false; t->e[t->len++] = (ipv4_rt_entry_t){ network, mask, gateway, metric }; + ipv4_rt_bump(t); return true; } @@ -128,12 +128,27 @@ bool ipv4_rt_del_in(ipv4_rt_table_t* t, uint32_t network, uint32_t mask) { if (t->e[i].network == network && t->e[i].mask == mask) { t->e[i] = t->e[--t->len]; memset(&t->e[t->len], 0, sizeof(t->e[0])); + ipv4_rt_bump(t); return true; } } return false; } +int ipv4_rt_count(const ipv4_rt_table_t* t) { + return t ? t->len : 0; +} + +bool ipv4_rt_get(const ipv4_rt_table_t* t, int index, ipv4_rt_entry_t* out) { + if (!t || !out || index < 0 || index >= t->len) return false; + *out = t->e[index]; + return true; +} + +uint32_t ipv4_rt_epoch(const ipv4_rt_table_t* t) { + return t ? t->epoch : 0; +} + bool ipv4_rt_lookup_in(const ipv4_rt_table_t* t, uint32_t dst, uint32_t* next_hop, int* out_prefix_len, int* out_metric) { if (!t) return false; int best_pl = -1; @@ -144,7 +159,7 @@ bool ipv4_rt_lookup_in(const ipv4_rt_table_t* t, uint32_t dst, uint32_t* next_ho uint32_t net = t->e[i].network; uint32_t mask = t->e[i].mask; if (mask == 0 || ((dst & mask) == net)) { - int pl = prefix_len(mask); + int pl = ipv4_prefix_len(mask); int met = t->e[i].metric; if (pl > best_pl || (pl == best_pl && met < best_metric)) { best_pl = pl; @@ -184,39 +199,53 @@ void ipv4_rt_sync_basics(ipv4_rt_table_t* t, uint32_t ip, uint32_t mask, uint32_ (void)ipv4_rt_add_in(t, net, mask, 0, base_metric); } } -bool ipv4_rt_pick_best_l3_in(const uint8_t* l3_ids, int n_ids, uint32_t dst, uint8_t* out_l3){ + +bool ipv4_rt_pick_best_l3(uint32_t dst, uint8_t ifindex, l3_id_t* out_l3){ int best_pl = -1; int best_cost = 0x7FFFFFFF; - uint8_t best_l3 = 0; - for (int i=0;il2) continue; - if (x->mode == IPV4_CFG_DISABLED) continue; - int l2base = (int)x->l2->base_metric; - int pl_conn = -1; - if (x->mask){ - uint32_t netx = x->ip & x->mask; - if ((dst & x->mask) == netx) pl_conn = prefix_len(x->mask); - } - int pl_tab = -1, met_tab = 0x7FFF; - if (x->routing_table){ - int out_pl = -1, out_met = 0x7FFF; - uint32_t nh; - if (ipv4_rt_lookup_in((const ipv4_rt_table_t*)x->routing_table, dst, &nh, &out_pl, &out_met)){ - pl_tab = out_pl; - met_tab = out_met; + l3_id_t best_l3 = 0; + bool dst_loopback = ipv4_is_loopback(dst); + uint8_t first = ifindex ? ifindex : 1; + uint8_t last = ifindex ? ifindex : MAX_L2_INTERFACES; + + for (uint8_t ix = first; ix <= last; ix++) { + l2_interface_t* l2 = l2_interface_find_by_index(ix); + if (!l2 || !l2->is_up) continue; + + for (int slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + l3_ipv4_interface_t* x = l2->l3_v4[slot]; + if (!ipv4_l3_is_ready(x) || dst_loopback != x->is_localhost) continue; + + int pl_conn = -1; + if (x->mask){ + uint32_t netx = x->ip & x->mask; + if ((dst & x->mask) == netx) pl_conn = ipv4_prefix_len(x->mask); + } + + int pl_tab = -1; + int met_tab = 0x7FFF; + if (x->routing_table){ + const ipv4_rt_table_t* rt = (const ipv4_rt_table_t*)x->routing_table; + if (rt->owner_l3_id && rt->owner_l3_id != x->l3_id) continue; + int out_pl = -1; + int out_met = 0x7FFF; + if (ipv4_rt_lookup_in(rt, dst, NULL, &out_pl, &out_met)) { + pl_tab = out_pl; + met_tab = out_met; + } + } + int cand_pl = pl_conn; + int cand_cost = l2->base_metric; + if (pl_tab > cand_pl || (pl_tab == cand_pl && met_tab < cand_cost)) { + cand_pl = pl_tab; + cand_cost = met_tab; + } + if (cand_pl > best_pl || (cand_pl == best_pl && cand_cost < best_cost) || + (cand_pl == best_pl && cand_cost == best_cost && x->l3_id < best_l3)) { + best_pl = cand_pl; + best_cost = cand_cost; + best_l3 = x->l3_id; } - } - int cand_pl = pl_conn; - int cand_cost = l2base; - if (pl_tab > cand_pl || (pl_tab == cand_pl && (l2base + met_tab) < cand_cost)){ - cand_pl = pl_tab; - cand_cost = l2base + met_tab; - } - if (cand_pl > best_pl || (cand_pl == best_pl && cand_cost < best_cost) || (cand_pl == best_pl && cand_cost == best_cost && l3_ids[i] < best_l3)){ - best_pl = cand_pl; - best_cost = cand_cost; - best_l3 = l3_ids[i]; } } if (best_pl < 0) return false; diff --git a/kernel/networking/internet_layer/ipv4_route.h b/kernel/networking/internet_layer/ipv4_route.h index 20b3c9c3..31655389 100644 --- a/kernel/networking/internet_layer/ipv4_route.h +++ b/kernel/networking/internet_layer/ipv4_route.h @@ -17,12 +17,15 @@ typedef struct { typedef struct ipv4_rt_table ipv4_rt_table_t; -ipv4_rt_table_t* ipv4_rt_create(void); +ipv4_rt_table_t* ipv4_rt_create(l3_id_t owner_l3_id); void ipv4_rt_destroy(ipv4_rt_table_t* t); void ipv4_rt_clear(ipv4_rt_table_t* t); bool ipv4_rt_add_in(ipv4_rt_table_t* t, uint32_t network, uint32_t mask, uint32_t gateway, uint16_t metric); bool ipv4_rt_del_in(ipv4_rt_table_t* t, uint32_t network, uint32_t mask); +int ipv4_rt_count(const ipv4_rt_table_t* t); +bool ipv4_rt_get(const ipv4_rt_table_t* t, int index, ipv4_rt_entry_t* out); +uint32_t ipv4_rt_epoch(const ipv4_rt_table_t* t); bool ipv4_rt_lookup_in(const ipv4_rt_table_t* t, uint32_t dst, uint32_t *next_hop, int* out_prefix_len, int* out_metric); @@ -30,14 +33,16 @@ void ipv4_rt_ensure_basics(ipv4_rt_table_t* t, uint32_t ip, uint32_t mask, uint3 void ipv4_rt_sync_basics(ipv4_rt_table_t* t, uint32_t ip, uint32_t mask, uint32_t gw, uint16_t base_metric); typedef struct { - uint8_t l3_id; + l3_id_t l3_id; + uint32_t l3_epoch; uint32_t src_ip; - ip_tx_opts_t fixed_opts; } ipv4_tx_plan_t; -bool ipv4_build_tx_plan(uint32_t dst, const ip_tx_opts_t* hint, const uint8_t* allowed_l3, int allowed_n, ipv4_tx_plan_t* out); +bool ipv4_tx_plan_valid(const ipv4_tx_plan_t* plan); +bool ipv4_tx_plan_onlink(const ipv4_tx_plan_t* plan, uint32_t dst); +bool ipv4_build_tx_plan(uint32_t dst, const ip_tx_opts_t* hint, ipv4_tx_plan_t* out); -bool ipv4_rt_pick_best_l3_in(const uint8_t* l3_ids, int n_ids, uint32_t dst, uint8_t* out_l3); +bool ipv4_rt_pick_best_l3(uint32_t dst, uint8_t ifindex, l3_id_t* out_l3); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv4_utils.c b/kernel/networking/internet_layer/ipv4_utils.c index cc7825da..88f29fc8 100644 --- a/kernel/networking/internet_layer/ipv4_utils.c +++ b/kernel/networking/internet_layer/ipv4_utils.c @@ -1,19 +1,5 @@ #include "ipv4_utils.h" - -static char* u8_to_str(uint8_t val, char* out) { - if (val >= 100) { - *out++ = '0' + (val / 100); - val %= 100; - *out++ = '0' + (val / 10); - *out++ = '0' + (val % 10); - } else if (val >= 10) { - *out++ = '0' + (val / 10); - *out++ = '0' + (val % 10); - } else { - *out++ = '0' + val; - } - return out; -} +#include "std/string.h" bool ipv4_is_unspecified(uint32_t ip) { return ip == 0; } bool ipv4_is_loopback(uint32_t ip) { return (ip & 0xFF000000u) == 0x7F000000u; } @@ -59,15 +45,26 @@ bool ipv4_is_unicast_global(uint32_t ip) { return true; } +bool ipv4_l3_is_active(l3_ipv4_interface_t *v4) { + if (!v4 || !v4->l2) return false; + if (!l2_interface_is_operational(v4->l2)) return false; + if (v4->mode == IPV4_CFG_DISABLED) return false; + return true; +} + +bool ipv4_l3_is_ready(l3_ipv4_interface_t *v4) { + if (!ipv4_l3_is_active(v4)) return false; + if (ipv4_is_unspecified(v4->ip)) return false; + return true; +} + bool ipv4_mask_is_contiguous(uint32_t mask) { if (mask == 0) return true; - return ((mask | (mask - 1u)) == 0xFFFFFFFFu); + return ((mask | (mask - 1u)) == IPV4_LIMITED_BROADCAST); } int ipv4_prefix_len(uint32_t mask) { - int n = 0; - while (mask & 0x80000000u) { n++; mask <<= 1; } - return n; + return mask == UINT32_MAX ? 32 : __builtin_clz(~mask); } uint32_t ipv4_net(uint32_t ip, uint32_t mask) { return ip & mask; } @@ -75,21 +72,21 @@ uint32_t ipv4_broadcast_calc(uint32_t ip, uint32_t mask) { return (mask == 0) ? bool ipv4_is_network_address(uint32_t ip, uint32_t mask) { if (!ipv4_mask_is_contiguous(mask)) return false; - if (mask == 0 || mask == 0xFFFFFFFFu) return false; + if (mask == 0 || mask == IPV4_LIMITED_BROADCAST) return false; return (ip & ~mask) == 0; } bool ipv4_is_broadcast_address(uint32_t ip, uint32_t mask) { if (!ipv4_mask_is_contiguous(mask)) return false; - if (mask == 0 || mask == 0xFFFFFFFFu) return false; + if (mask == 0 || mask == IPV4_LIMITED_BROADCAST) return false; return (ip & ~mask) == ~mask; } -bool ipv4_is_limited_broadcast(uint32_t ip) { return ip == 0xFFFFFFFFu; } +bool ipv4_is_limited_broadcast(uint32_t ip) { return ip == IPV4_LIMITED_BROADCAST; } bool ipv4_is_directed_broadcast(uint32_t ip, uint32_t mask, uint32_t dst) { if (!ipv4_mask_is_contiguous(mask)) return false; - if (mask == 0 || mask == 0xFFFFFFFFu) return false; + if (mask == 0 || mask == IPV4_LIMITED_BROADCAST) return false; return ipv4_broadcast_calc(ip, mask) == dst; } @@ -99,46 +96,32 @@ bool ipv4_same_subnet(uint32_t a, uint32_t b, uint32_t mask) { } void ipv4_to_string(uint32_t ip, char* buf) { - uint8_t a = (uint8_t)(ip >> 24); - uint8_t b = (uint8_t)(ip >> 16); - uint8_t c = (uint8_t)(ip >> 8); - uint8_t d = (uint8_t)(ip); - char* p = buf; - p = u8_to_str(a, p); *p++ = '.'; - p = u8_to_str(b, p); *p++ = '.'; - p = u8_to_str(c, p); *p++ = '.'; - p = u8_to_str(d, p); - *p = '\0'; + if (!buf) return; + string_format_buf(buf, 16, "%u.%u.%u.%u", ip >> 24, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); } bool ipv4_parse(const char* s, uint32_t* out) { if (!s || !out) return false; - uint32_t ip = 0, v = 0; - int oct = 0, digits = 0; + uint32_t ip = 0; const char* p = s; - while (*p) { - if (*p == '.') { - if (digits == 0 || v > 255 || oct >= 3) return false; - ip = (ip << 8) | (v & 0xFF); - v = 0; - digits = 0; - oct++; - } else if (*p >= '0' && *p <= '9') { - v = v * 10 + (uint32_t)(*p - '0'); - if (v > 255) return false; - digits++; + for (uint32_t oct = 0; oct < 4; oct++) { + if (!is_digit(*p)) return false; + char* end = 0; + uint64_t v = strtoul(p, &end, 10); + if (v > 255) return false; + ip = (ip << 8) | (uint32_t)v; + if (oct == 3) { + if (*end) return false; } else { - return false; + if (*end != '.') return false; + p = end + 1; } - ++p; } - if (oct != 3 || digits == 0 || v > 255) return false; - ip = (ip << 8) | (v & 0xFF); *out = ip; return true; } -void ipv4_mcast_to_mac(uint32_t group, uint8_t out_mac[6]) { +void ipv4_mcast_to_mac(uint32_t group, uint8_t out_mac[MAC_ADDR_LEN]) { if (!out_mac) return; out_mac[0] = 0x01; out_mac[1] = 0x00; diff --git a/kernel/networking/internet_layer/ipv4_utils.h b/kernel/networking/internet_layer/ipv4_utils.h index 6fe4da8c..4718f347 100644 --- a/kernel/networking/internet_layer/ipv4_utils.h +++ b/kernel/networking/internet_layer/ipv4_utils.h @@ -1,7 +1,11 @@ #pragma once #include "types.h" +#include "networking/link_layer/link_utils.h" +#include "networking/interface_manager.h" + #define IPV4_MCAST_ALL_HOSTS 0xE0000001u #define IPV4_MCAST_ALL_ROUTERS 0xE0000002u +#define IPV4_LIMITED_BROADCAST UINT32_MAX #ifdef __cplusplus extern "C" { @@ -19,6 +23,9 @@ bool ipv4_is_reserved(uint32_t ip); bool ipv4_is_reserved_special(uint32_t ip); bool ipv4_is_unicast_global(uint32_t ip); +bool ipv4_l3_is_active(l3_ipv4_interface_t *v4); +bool ipv4_l3_is_ready(l3_ipv4_interface_t *v4); + bool ipv4_mask_is_contiguous(uint32_t mask); int ipv4_prefix_len(uint32_t mask); @@ -34,7 +41,7 @@ bool ipv4_same_subnet(uint32_t a, uint32_t b, uint32_t mask); void ipv4_to_string(uint32_t ip, char* buf); bool ipv4_parse(const char* s, uint32_t* out); -void ipv4_mcast_to_mac(uint32_t group, uint8_t out_mac[6]); +void ipv4_mcast_to_mac(uint32_t group, uint8_t out_mac[MAC_ADDR_LEN]); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv6.c b/kernel/networking/internet_layer/ipv6.c index 05b2cd6a..ce256896 100644 --- a/kernel/networking/internet_layer/ipv6.c +++ b/kernel/networking/internet_layer/ipv6.c @@ -3,6 +3,7 @@ #include "std/memory.h" #include "std/string.h" #include "networking/link_layer/eth.h" +#include "networking/link_layer/link_utils.h" #include "networking/interface_manager.h" #include "networking/link_layer/ndp.h" #include "networking/transport_layer/tcp.h" @@ -13,20 +14,14 @@ #include "networking/internet_layer/ipv6_route.h" #include "networking/internet_layer/icmpv6.h" #include "math/rng.h" +#include "random/random.h" #include "net/checksums.h" #include "networking/link_layer/nic_types.h" +#include "networking/net_fragbuf.h" +#include "networking/internet_layer/pmtu.h" #define IPV6_MIN_MTU 1280u -#define PMTU_CACHE_SIZE 16 #define REASS_SLOTS 8 - -typedef struct { - uint8_t used; - uint8_t dst[16]; - uint16_t mtu; - uint32_t timestamp_ms; -} pmtu_entry_t; - typedef struct { uint8_t used; uint8_t ifindex; @@ -35,21 +30,14 @@ typedef struct { uint8_t dst[16]; uint8_t next_header; - uint32_t first_rx_ms; uint32_t last_update_ms; - uint32_t total_len; - uint8_t have_last; uint8_t have_first; uint8_t first_src_mac[6]; - uint8_t _pad0[1]; uint16_t first_pkt_len; - uint8_t _pad1[2]; uint8_t first_pkt[1280]; - - uint8_t *buf; - uint8_t bitmap[2048]; + net_fragbuf_t frag; } reass_slot_t; typedef struct __attribute__((packed)) { @@ -59,64 +47,31 @@ typedef struct __attribute__((packed)) { uint32_t identification; } ipv6_frag_hdr_t; -static pmtu_entry_t g_pmtu[PMTU_CACHE_SIZE] = {0}; static reass_slot_t g_reass[REASS_SLOTS] = {0}; - -uint16_t ipv6_pmtu_get(const uint8_t dst[16]) { - if (!dst) return 0; - uint32_t now = (uint32_t)get_time(); - for (int i = 0; i < PMTU_CACHE_SIZE; i++) { - if (!g_pmtu[i].used) continue; - if (ipv6_cmp(g_pmtu[i].dst, dst) != 0) continue; - if (now - g_pmtu[i].timestamp_ms > 600000u) { - g_pmtu[i].used = 0; - continue; - } - return g_pmtu[i].mtu; - } - return 0; -} - -void ipv6_pmtu_note(const uint8_t dst[16], uint16_t mtu) { - if (!dst) return; - if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU; - uint32_t now = (uint32_t)get_time(); - - int free_i = -1; - int lru_i = 0; - uint32_t lru_t = 0xFFFFFFFFu; - - for (int i = 0; i < PMTU_CACHE_SIZE; i++) { - if (g_pmtu[i].used) { - if (ipv6_cmp(g_pmtu[i].dst, dst) == 0) { - g_pmtu[i].mtu = mtu; - g_pmtu[i].timestamp_ms = now; - return; - } - if (g_pmtu[i].timestamp_ms < lru_t) { - lru_t = g_pmtu[i].timestamp_ms; - lru_i = i; - } - } else if (free_i < 0) free_i = i; - } - - int idx = (free_i >= 0) ? free_i : lru_i; - g_pmtu[idx].used = 1; - ipv6_cpy(g_pmtu[idx].dst, dst); - g_pmtu[idx].mtu = mtu; - g_pmtu[idx].timestamp_ms = now; -} +static uint8_t g_reass_active; static void reass_free(reass_slot_t *s) { if (!s) return; - if (s->buf) free_sized(s->buf, 2048u * 8u); - memset(s, 0, sizeof(*s)); + net_fragbuf_free(&s->frag); + if (s->used && g_reass_active) g_reass_active--; + s->used = 0; + s->have_first = 0; + s->first_pkt_len = 0; +} + +static bool ipv6_error_quote(netpkt_t* pkt, const ipv6_hdr_t* ip6, uint32_t l4_off, uint32_t l4_len, uint8_t* out, uint32_t cap, uint32_t* out_len) { + if (!pkt || !ip6 || !out || !out_len || cap < sizeof(*ip6)) return false; + uint32_t len = l4_off + l4_len; + if (len > cap) len = cap; + if (!netpkt_copyout(pkt, 0, out, len)) return false; + *out_len = len; + return true; } static void icmpv6_send_error(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t dst_mac[6], uint8_t type, uint8_t code, uint32_t param32, const uint8_t *invoking, uint32_t invoking_len) { if (!ifindex || !src_ip || !dst_ip || !dst_mac || !invoking || !invoking_len) return; - uint32_t max_invoke = 1280u; + uint32_t max_invoke = IPV6_MIN_MTU - (uint32_t)sizeof(ipv6_hdr_t); uint32_t base = (uint32_t)sizeof(icmpv6_hdr_t) + 4u; if (base >= max_invoke) return; @@ -124,416 +79,282 @@ static void icmpv6_send_error(uint8_t ifindex, const uint8_t src_ip[16], const u if (copy > max_invoke - base) copy = max_invoke - base; uint32_t icmp_len = base + copy; - uint8_t *buf = (uint8_t*)malloc(icmp_len); - if (!buf) return; + uint32_t total = (uint32_t)sizeof(ipv6_hdr_t) + icmp_len; + netpkt_t *pkt = netpkt_alloc( total, (uint32_t)sizeof(eth_hdr_t), 0); + if (!pkt) return; + + uint8_t *buf = (uint8_t*)netpkt_put(pkt, total); + if (!buf) { + netpkt_unref(pkt); + return; + } + + ipv6_hdr_t* ip6 = (ipv6_hdr_t*)buf; + ip6->ver_tc_fl = bswap32((uint32_t)(6u << 28)); + ip6->payload_len = bswap16((uint16_t)icmp_len); + ip6->next_header = PROTO_ICMPV6; + ip6->hop_limit = 64; + ipv6_cpy(ip6->src, src_ip); + ipv6_cpy(ip6->dst, dst_ip); - icmpv6_hdr_t *h = (icmpv6_hdr_t*)buf; + uint8_t* icmp = buf + sizeof(*ip6); + icmpv6_hdr_t *h = (icmpv6_hdr_t*)icmp; h->type = type; h->code = code; h->checksum = 0; + wr_be32(icmp + sizeof(icmpv6_hdr_t), param32); + memcpy(icmp + base, invoking, copy); + h->checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, icmp, icmp_len)); - *(uint32_t*)(buf + sizeof(icmpv6_hdr_t)) = bswap32(param32); - - memcpy(buf + base, invoking, copy); - - h->checksum =bswap16(checksum16_pipv6(src_ip, dst_ip, 58, buf, icmp_len)); - - icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, buf, icmp_len, 64); - - free_sized(buf, icmp_len); + eth_send_frame_on(ifindex, ETHERTYPE_IPV6, dst_mac, pkt); } -static l3_ipv6_interface_t* best_v6_on_l2_for_dst(l2_interface_t* l2, const uint8_t dst[16]) { - l3_ipv6_interface_t* best = NULL; - int best_cmp = -1; - int best_cost = 0x7FFFFFFF; - int dst_is_ll = (ipv6_is_linklocal(dst) || ipv6_is_linkscope_mcast(dst)) ? 1 : 0; +static void ipv6_deliver_transport(netpkt_t *pkt, uint32_t l4_off, uint32_t l4_len, uint8_t proto, const uint8_t src[16], const uint8_t dst[16], l3_id_t l3_id, bool include_shared, udp_input_result_t *udp_result, bool* checksum_done) { + if (proto != PROTO_TCP && proto != PROTO_UDP) return; + if (proto == PROTO_TCP && ipv6_is_multicast(dst)) return; - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (ipv6_is_unspecified(v6->ip)) continue; - if (v6->dad_state != IPV6_DAD_OK) continue; - - int v6_is_ll = ipv6_is_linklocal(v6->ip) ? 1 : 0; - if (v6_is_ll != dst_is_ll) continue; - - int cmp = ipv6_common_prefix_len(dst, v6->ip); - int cost = (int)l2->base_metric; - if (cmp > best_cmp || (cmp == best_cmp && cost < best_cost)) { - best_cmp = cmp; - best_cost = cost; - best = v6; - } - } - return best; -} - -static bool pick_route_bound_l3(uint8_t l3_id, const uint8_t dst[16], uint8_t* out_ifx, uint8_t out_src[16], uint8_t out_nh[16]) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); - if (!v6 || !v6->l2) return false; - if (v6->cfg == IPV6_CFG_DISABLE) return false; - if (ipv6_is_unspecified(v6->ip)) return false; - if (v6->dad_state != IPV6_DAD_OK) return false; + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (!l4pkt) return; - int dst_is_ll = (ipv6_is_linklocal(dst) || ipv6_is_linkscope_mcast(dst)) ? 1 : 0; - int src_is_ll = ipv6_is_linklocal(v6->ip) ? 1 : 0; - if (dst_is_ll != src_is_ll) return false; - - if (out_ifx) *out_ifx = v6->l2->ifindex; - if (out_src) ipv6_cpy(out_src, v6->ip); - - uint8_t nh[16]; - ipv6_cpy(nh, dst); - - if (!dst_is_ll && v6->prefix_len && ipv6_common_prefix_len(dst, v6->ip) < v6->prefix_len) { - uint8_t via[16] = {0}; - int pl = -1,met = 0x7FFF; - - if (v6->routing_table && - ipv6_rt_lookup_in((const ipv6_rt_table_t*)v6->routing_table, dst, via, &pl, &met)) - { - if (!ipv6_is_unspecified(via)) ipv6_cpy(nh, via); - } else if (!ipv6_is_unspecified(v6->gateway) && ipv6_is_linklocal(v6->gateway)) { - ipv6_cpy(nh, v6->gateway); - } + if (proto == PROTO_TCP) tcp_input(IP_VER6, src, dst, l3_id, l4pkt); + else { + udp_input_result_t result = udp_input(IP_VER6, src, dst, l3_id, l4pkt, include_shared, checksum_done); + if (udp_result) *udp_result = result; } - - if (out_nh) ipv6_cpy(out_nh, nh); - return true; } -static bool pick_route_bound_l2(uint8_t ifindex, const uint8_t dst[16], uint8_t* out_ifx, uint8_t out_src[16], uint8_t out_nh[16]) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return false; - - l3_ipv6_interface_t* v6 = best_v6_on_l2_for_dst(l2, dst); - if (!v6) return false; - - if (out_ifx) *out_ifx = l2->ifindex; - if (out_src) ipv6_cpy(out_src, v6->ip); - - uint8_t nh[16]; - ipv6_cpy(nh, dst); - - if (!ipv6_is_linklocal(dst) && v6->prefix_len && ipv6_common_prefix_len(dst, v6->ip) < v6->prefix_len) { - uint8_t via[16] = {0}; - int pl = -1; - int met =0x7FFF; - - if (v6->routing_table && ipv6_rt_lookup_in((const ipv6_rt_table_t*)v6->routing_table, dst, via, &pl, &met)) { - if (!ipv6_is_unspecified(via)) ipv6_cpy(nh, via); - } else if (!ipv6_is_unspecified(v6->gateway) && ipv6_is_linklocal(v6->gateway))ipv6_cpy(nh, v6->gateway); +bool ipv6_send_packet(const uint8_t dst[16], uint8_t next_header, netpkt_t* pkt, const ip_tx_opts_t* opts, uint8_t hop_limit, uint8_t dontfrag, uint8_t dontroute) { + if (!dst || !pkt) { + if (pkt) netpkt_unref(pkt); + return false; } - - if (out_nh) ipv6_cpy(out_nh, nh); - return true; -} - -static bool pick_route_global(const uint8_t dst[16], uint8_t* out_ifx, uint8_t out_src[16], uint8_t out_nh[16]) { - int dst_is_ll = ipv6_is_linklocal(dst) ? 1 : 0; - - ip_resolution_result_t r = resolve_ipv6_to_interface(dst); - if (r.found && r.ipv6 && r.l2) { - if (r.ipv6->cfg != IPV6_CFG_DISABLE && - !ipv6_is_unspecified(r.ipv6->ip) && - r.ipv6->dad_state == IPV6_DAD_OK) - { - int src_is_ll = ipv6_is_linklocal(r.ipv6->ip) ? 1 : 0; - if (src_is_ll == dst_is_ll) { - if (out_ifx) *out_ifx = r.l2->ifindex; - if (out_src) ipv6_cpy(out_src, r.ipv6->ip); - - uint8_t nh[16]; - ipv6_cpy(nh, dst); - - if (!dst_is_ll && r.ipv6->prefix_len && ipv6_common_prefix_len(dst, r.ipv6->ip) < r.ipv6->prefix_len) { - uint8_t via[16] = {0}; - int pl = -1; - int met = 0x7FFF; - - if (r.ipv6->routing_table && ipv6_rt_lookup_in((const ipv6_rt_table_t*)r.ipv6->routing_table, dst, via, &pl, &met)) { - if (!ipv6_is_unspecified(via)) ipv6_cpy(nh, via); - } else if (!ipv6_is_unspecified(r.ipv6->gateway) && ipv6_is_linklocal(r.ipv6->gateway)) { - ipv6_cpy(nh, r.ipv6->gateway); - } - } - - if (out_nh) ipv6_cpy(out_nh, nh); - return true; - } - } + if (next_header == PROTO_TCP && ipv6_is_multicast(dst)) { + netpkt_unref(pkt); + return false; } - - uint8_t best_ifx = 0; - uint8_t best_src[16] ={0}; - uint8_t best_nh[16] ={0}; - int best_pl = -1; - int best_cost = 0x7FFFFFFF; - - uint8_t n = l2_interface_count(); - for (uint8_t i = 0; i < n; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - - for (int s = 0; s< MAX_IPV6_PER_INTERFACE; s++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (ipv6_is_unspecified(v6->ip)) continue; - if (v6->dad_state != IPV6_DAD_OK) continue; - - int src_is_ll = ipv6_is_linklocal(v6->ip) ? 1 : 0; - if (src_is_ll != dst_is_ll) continue; - - int pl_conn = -1; - if (!dst_is_ll && v6->prefix_len && ipv6_common_prefix_len(dst, v6->ip) >= v6->prefix_len) pl_conn = v6->prefix_len; - if (dst_is_ll) pl_conn = 128; - - int pl_tab = -1; - int met_tab = 0x7FFF; - uint8_t via[16] = {0}; - - if (!dst_is_ll && v6->routing_table) { - int out_pl = -1; - int out_met = 0x7FFF; - if(ipv6_rt_lookup_in((const ipv6_rt_table_t*)v6->routing_table, dst, via, &out_pl, &out_met)) { - pl_tab = out_pl; - met_tab = out_met; - } - } - - int cand_pl = pl_conn; - int cand_cost = (int)l2->base_metric; - uint8_t cand_nh[16]; - ipv6_cpy(cand_nh, dst); - - if (pl_tab > cand_pl || (pl_tab == cand_pl && ((int)l2->base_metric + met_tab) < cand_cost)) { - cand_pl =pl_tab; - cand_cost = (int)l2->base_metric + met_tab; - if (!ipv6_is_unspecified(via)) ipv6_cpy(cand_nh, via); - } else if (cand_pl < 0) { - if (!ipv6_is_unspecified(v6->gateway) && ipv6_is_linklocal(v6->gateway)) ipv6_cpy(cand_nh, v6->gateway); - } - - if (cand_pl > best_pl || (cand_pl == best_pl && cand_cost < best_cost)) { - best_pl = cand_pl; - best_cost = cand_cost; - best_ifx = l2->ifindex; - ipv6_cpy(best_src, v6->ip); - ipv6_cpy(best_nh, cand_nh); - } - } + uint32_t seg_len = netpkt_len(pkt); + if (!seg_len || seg_len > UINT16_MAX) { + netpkt_unref(pkt); + return false; } - if (best_pl < 0) return false; - if (out_ifx) *out_ifx = best_ifx; - if (out_src) ipv6_cpy(out_src, best_src); - if (out_nh) ipv6_cpy(out_nh, best_nh); - return true; -} - -static bool pick_route(const uint8_t dst[16], const ipv6_tx_opts_t* opts, uint8_t* out_ifx, uint8_t out_src[16], uint8_t out_nh[16]) { - if (opts) { - if (opts->scope == IP_TX_BOUND_L3) return pick_route_bound_l3(opts->index, dst, out_ifx, out_src, out_nh); - if (opts->scope == IP_TX_BOUND_L2) return pick_route_bound_l2(opts->index, dst, out_ifx, out_src, out_nh); + ipv6_tx_plan_t plan; + if (!ipv6_build_tx_plan(dst, opts, &plan)) { + netpkt_unref(pkt); + return false; } - return pick_route_global(dst, out_ifx, out_src, out_nh); -} -void ipv6_send_packet(const uint8_t dst[16], uint8_t next_header, netpkt_t* pkt, const ipv6_tx_opts_t* opts, uint8_t hop_limit, uint8_t dontfrag) { - if (!dst || !pkt || !netpkt_len(pkt)) { - if (pkt) netpkt_unref(pkt); - return; + l3_ipv6_interface_t* src_v6 = l3_ipv6_find_by_id(plan.l3_id); + if (!ipv6_l3_is_ready(src_v6)) { + netpkt_unref(pkt); + return false; } - uint8_t ifx = 0; - uint8_t src[16] = {0}; - uint8_t nh[16] = {0}; - - l3_ipv6_interface_t* src_v6 = NULL; + uint8_t ifx = src_v6->l2->ifindex; + uint8_t src[16]; + uint8_t nh[16]; + ipv6_cpy(src, plan.src_ip); + ipv6_cpy(nh, dst); + bool linklocal = ipv6_is_linklocal(dst); + bool multicast = ipv6_is_multicast(dst); - if (!pick_route(dst, opts, &ifx, src, nh)) { + if (ipv6_is_linklocal(src) && !linklocal && !multicast) { netpkt_unref(pkt); - return; + return false; } - if (!ipv6_is_unspecified(src)) { - l2_interface_t* l2 = l2_interface_find_by_index(ifx); - if (!l2) { - netpkt_unref(pkt); - return; - } - - int ok = 0; - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (ipv6_cmp(v6->ip, src) != 0) continue; - if (v6->cfg == IPV6_CFG_DISABLE) { + if (!linklocal && !multicast) { + if (dontroute) { + if (!ipv6_tx_plan_onlink(&plan, dst)) { netpkt_unref(pkt); - return; + return false; } - if (v6->dad_state != IPV6_DAD_OK) { + } else { + if (!ipv6_next_hop_for_l3(plan.l3_id, dst, nh)) { netpkt_unref(pkt); - return; + return false; } - ok = 1; - src_v6 = v6; - break; - } - if (!ok) { - netpkt_unref(pkt); - return; + if (ipv6_is_linklocal(nh)) ndp_note_default_router(ifx, nh); } } - if (ipv6_is_linklocal(src) && !ipv6_is_linklocal(dst) && !ipv6_is_multicast(dst)) { - netpkt_unref(pkt); - return; - } - + l2_interface_t* l2 = src_v6->l2; uint8_t dst_mac[6]; - l2_interface_t* l2 = l2_interface_find_by_index(ifx); - if (ipv6_is_multicast(dst)) ipv6_multicast_mac(dst, dst_mac); - else if (l2 && l2->kind == NET_IFK_LOCALHOST) memset(dst_mac, 0, 6); - else if (!ndp_resolve_on(ifx, nh, dst_mac, 200)) { + bool need_ndp = false; + if (multicast) ipv6_multicast_mac(dst, dst_mac); + else if (l2 && l2->link_kind == NET_LINK_LOOPBACK) mac_clear(dst_mac); + else need_ndp = true; + + uint16_t mtu = l3_ipv6_effective_mtu(src_v6); + if (mtu < IPV6_MIN_MTU) { netpkt_unref(pkt); - return; + return false; + } + if (next_header != PROTO_TCP) { + uint16_t path_mtu = pmtu_get(src_v6->l3_id, src_v6->epoch, IP_VER6, dst); + if (path_mtu && path_mtu < mtu) mtu = path_mtu; } - - uint16_t mtu = 1500; - - if (!src_v6 && opts && opts->scope == IP_TX_BOUND_L3) src_v6 = l3_ipv6_find_by_id(opts->index); - if (src_v6 && src_v6->mtu) mtu = src_v6->mtu; - - uint16_t pmtu = ipv6_pmtu_get(dst); - if (pmtu && pmtu ver_tc_fl = bswap32((uint32_t)(6u << 28)); - ip6->payload_len = bswap16((uint16_t)seg_len); - ip6->next_header = next_header; - ip6->hop_limit = hop_limit ? hop_limit : 64; - memcpy(ip6->src, src, 16); - memcpy(ip6->dst, dst, 16); + ipv6_hdr_t ip6; + ip6.ver_tc_fl = bswap32((uint32_t)(6u << 28)); + ip6.payload_len = bswap16((uint16_t)seg_len); + ip6.next_header = next_header; + ip6.hop_limit = hop_limit ? hop_limit : (src_v6->l2->ipv6_default_hop_limit ? src_v6->l2->ipv6_default_hop_limit : 64); + ipv6_cpy(ip6.src, src); + ipv6_cpy(ip6.dst, dst); + memcpy(hdrp, &ip6, sizeof(ip6)); - eth_send_frame_on(ifx, ETHERTYPE_IPV6, dst_mac, pkt); - return; + if (need_ndp) return ndp_send_or_queue_on(ifx, nh, pkt); + return eth_send_frame_on(ifx, ETHERTYPE_IPV6, dst_mac, pkt); } - if (dontfrag) { + uint32_t frag_hdr_len = sizeof(ipv6_frag_hdr_t); + if (dontfrag || (uint32_t)mtu < hdr_len + frag_hdr_len + 8u) { netpkt_unref(pkt); - return; - } - - uint32_t frag_hdr_len = (uint32_t)sizeof(ipv6_frag_hdr_t); - if ((uint32_t)mtu < hdr_len + frag_hdr_len + 8u) { - netpkt_unref(pkt); - return; + return false; } uint32_t max_chunk = (uint32_t)mtu - hdr_len - frag_hdr_len; max_chunk = (max_chunk / 8u) * 8u; if (max_chunk == 0) { netpkt_unref(pkt); - return; + return false; } rng_t rng; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&rng, virt_timer); + rng_init_random(&rng); uint32_t ident = rng_next32(&rng); + ipv6_hdr_t ip6; + ip6.ver_tc_fl = bswap32((uint32_t)(6u << 28)); + ip6.next_header = IPV6_NH_FRAGMENT; + ip6.hop_limit = hop_limit ? hop_limit : (src_v6->l2->ipv6_default_hop_limit ? src_v6->l2->ipv6_default_hop_limit : 64); + ipv6_cpy(ip6.src, src); + ipv6_cpy(ip6.dst, dst); + uint32_t off = 0; const uint8_t* data = (const uint8_t*)netpkt_data(pkt); - uint32_t data_len = seg_len; + bool ok = true; - while (off < data_len) { - uint32_t remain = data_len - off; + while (off < seg_len) { + uint32_t remain = seg_len - off; uint32_t chunk = (remain > max_chunk)? max_chunk : remain; - uint8_t more = (off + chunk < data_len) ? 1u : 0u; + uint8_t more = off + chunk < seg_len ? 1u : 0; uint32_t payload_len = frag_hdr_len + chunk; uint32_t frame_len = hdr_len + payload_len; netpkt_t* fpkt = netpkt_alloc(frame_len, (uint32_t)sizeof(eth_hdr_t), 0); - if (!fpkt) break; + if (!fpkt) { + ok = false; + break; + } void* buf = netpkt_put(fpkt, frame_len); if (!buf) { netpkt_unref(fpkt); + ok = false; break; } - ipv6_hdr_t* ip6 = (ipv6_hdr_t*)buf; - ip6->ver_tc_fl = bswap32((uint32_t)(6u << 28)); - ip6->payload_len = bswap16((uint16_t)payload_len); - ip6->next_header = 44; - ip6->hop_limit = hop_limit ? hop_limit : 64; - memcpy(ip6->src, src, 16); - memcpy(ip6->dst, dst, 16); - - ipv6_frag_hdr_t* fh = (ipv6_frag_hdr_t*)((uintptr_t)buf + hdr_len); - fh->next_header = next_header; - fh->reserved = 0; + ip6.payload_len = bswap16((uint16_t)payload_len); + memcpy(buf, &ip6, sizeof(ip6)); + ipv6_frag_hdr_t fh; + fh.next_header = next_header; + fh.reserved = 0; uint16_t off_flags = (uint16_t)(((off / 8u) & 0x1FFFu) << 3); if (more) off_flags |= 0x0001u; - fh->offset_flags = bswap16(off_flags); - fh->identification = bswap32(ident); + fh.offset_flags = bswap16(off_flags); + fh.identification = bswap32(ident); + memcpy((uint8_t*)buf + hdr_len, &fh, sizeof(fh)); - memcpy((uint8_t*)(fh + 1), data + off, chunk); + memcpy((uint8_t*)buf + hdr_len + sizeof(fh), data + off, chunk); - eth_send_frame_on(ifx, ETHERTYPE_IPV6, dst_mac, fpkt); + if (need_ndp) { if (!ndp_send_or_queue_on(ifx, nh, fpkt)) ok = false; } + else if (!eth_send_frame_on(ifx, ETHERTYPE_IPV6, dst_mac, fpkt)) ok = false; off += chunk; } netpkt_unref(pkt); + return ok && off == seg_len; } -static bool ipv6_skip_ext_headers(uint8_t* nh, uintptr_t* l4, uint32_t* l4_len) { - if (!nh || !l4 || !l4_len) return false; +bool ipv6_skip_ext_headers(const netpkt_t* pkt, uint8_t* nh, uint32_t* l4_off, uint32_t* l4_len, bool stop_at_fragment, bool* router_alert) { + if (!pkt || !nh || !l4_off || !l4_len) return false; + if (router_alert) *router_alert = false; + uint32_t total_len = netpkt_len(pkt); + bool router_alert_seen = false; for(;;) { + uint8_t ext[2]; + if (*l4_off > total_len || *l4_len > total_len - *l4_off) return false; uint8_t h = *nh; - if (h == 44) return true; - - if (h == 0 ||h == 43 || h == 60) { - if (*l4_len < 2) return false; - const uint8_t* p = (const uint8_t*)(*l4); - uint8_t next = p[0]; - uint8_t hlen = p[1]; - uint32_t bytes = (uint32_t)(hlen + 1u)*8; + + if (h == IPV6_NH_FRAGMENT) { + if (stop_at_fragment) return true; + uint8_t frag[4]; + if (*l4_len < 8 || !netpkt_copyout(pkt, *l4_off, frag, sizeof(frag))) return false; + if (rd_be16(frag + 2) & 0xFFF8u) return false; + *nh = frag[0]; + *l4_off += 8; + *l4_len -= 8; + continue; + } + + if (h == IPV6_NH_HOP_BY_HOP || h == IPV6_NH_ROUTING || h == IPV6_NH_DEST_OPTS) { + if (*l4_len < sizeof(ext)) return false; + if (!netpkt_copyout(pkt, *l4_off, ext, sizeof(ext))) return false; + uint32_t bytes = ((uint32_t)ext[1] + 1u)*8; if (bytes > *l4_len) return false; - *nh = next; - *l4 += bytes; + + if (h == IPV6_NH_HOP_BY_HOP) { + uint32_t pos = 2; + while (pos < bytes) { + uint8_t type; + if (!netpkt_copyout(pkt, *l4_off + pos, &type, 1)) return false; + if (!type) { + pos++; + continue; + } + + uint8_t opt[2]; + if (bytes - pos < sizeof(opt) || !netpkt_copyout(pkt, *l4_off + pos, opt, sizeof(opt))) return false; + uint32_t opt_len = (uint32_t)opt[1]+2; + if (opt_len > bytes - pos) return false; + if (type == 5) { + if (router_alert_seen || opt[1] != 2) return false; + router_alert_seen = true; + uint8_t value[2]; + if (!netpkt_copyout(pkt, *l4_off + pos + 2, value, sizeof(value))) return false; + if (router_alert && !value[0] && !value[1]) *router_alert = true; + } + pos += opt_len; + } + } + + *nh = ext[0]; + *l4_off += bytes; *l4_len -= bytes; continue; } - if (h == 51) { - if (*l4_len < 2) return false; - const uint8_t* p = (const uint8_t*)(*l4); - uint8_t next = p[0]; - uint8_t plen = p[1]; - uint32_t bytes = ((uint32_t)plen + 2u)*4; + if (h == IPV6_NH_AH) { + if (*l4_len < sizeof(ext)) return false; + if (!netpkt_copyout(pkt, *l4_off, ext, sizeof(ext))) return false; + uint32_t bytes = ((uint32_t)ext[1] + 2u)*4; if (bytes > *l4_len) return false; - *nh = next; - *l4 += bytes; + *nh = ext[0]; + *l4_off += bytes; *l4_len -= bytes; continue; } @@ -542,27 +363,32 @@ static bool ipv6_skip_ext_headers(uint8_t* nh, uintptr_t* l4, uint32_t* l4_len) } } -void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { +void ipv6_input(uint8_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { if (!pkt) return; + bool checksum_done = false; uint32_t ip_len = netpkt_len(pkt); - uintptr_t ip_ptr = netpkt_data(pkt); if (ip_len < sizeof(ipv6_hdr_t)) return; - ipv6_hdr_t* ip6 = (ipv6_hdr_t*)ip_ptr; + ipv6_hdr_t ip6_; + ipv6_hdr_t* ip6 = &ip6_; + if (!netpkt_copyout(pkt, 0, ip6, sizeof(*ip6))) return; uint32_t v = bswap32(ip6->ver_tc_fl); if ((v >> 28) != 6) return; - uint32_t now = (uint32_t)get_time(); - for (int i = 0; i < REASS_SLOTS; i++) { - reass_slot_t *s = &g_reass[i]; - if (!s->used) continue; - if (now - s->first_rx_ms < 60000u) continue; + if (ipv6_is_loopback(ip6->src)) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || l2->link_kind != NET_LINK_LOOPBACK) return; + } - if (s->have_first && s->first_pkt_len) { - icmpv6_send_error(s->ifindex, s->dst, s->src, s->first_src_mac, 3, 1, 0, s->first_pkt, s->first_pkt_len); + if (g_reass_active) { + uint32_t now = (uint32_t)get_time(); + for (int i = 0; i < REASS_SLOTS; i++) { + reass_slot_t *s = &g_reass[i]; + if (!s->used || now - s->last_update_ms < 60000u) continue; + if (s->have_first && s->first_pkt_len) + icmpv6_send_error(s->ifindex, s->dst, s->src, s->first_src_mac, 3, 1, 0, s->first_pkt, s->first_pkt_len); + reass_free(s); } - - reass_free(s); } uint16_t payload_len = bswap16(ip6->payload_len); @@ -573,15 +399,14 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { if (ipv6_is_linklocal(ip6->src) && !ipv6_is_linklocal(ip6->dst) && !ipv6_is_multicast(ip6->dst) && - ip6->next_header != 58){ + ip6->next_header != PROTO_ICMPV6){ bool dst_is_local = false; - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (l2) { for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (!ipv6_l3_is_active(v6) || ipv6_is_placeholder_gua(v6->ip)) continue; if (ipv6_cmp(v6->ip, ip6->dst) == 0) { dst_is_local = true; break; @@ -592,66 +417,61 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { if (!dst_is_local) return; } - uintptr_t l4 = ip_ptr + sizeof(ipv6_hdr_t); + uint32_t l4_off = (uint32_t)sizeof(ipv6_hdr_t); uint32_t l4_len = (uint32_t)payload_len; if (ipv6_is_linklocal(ip6->dst) && !ipv6_is_unspecified(ip6->src) && !ipv6_is_linklocal(ip6->src)) return; - if (ifindex && !ipv6_is_unspecified(ip6->src) && src_mac) ndp_table_put_for_l2((uint8_t)ifindex, ip6->src, src_mac, 180000, false); - uint8_t nh = ip6->next_header; + bool router_alert = false; + + if (!ipv6_skip_ext_headers(pkt, &nh, &l4_off, &l4_len, true, &router_alert)) return; - if (!ipv6_skip_ext_headers(&nh, &l4, &l4_len)) return; + uint8_t inner_nh = 0; + uint32_t ident = 0, off = 0, frag_off = 0, frag_len = 0; + uint8_t more = 0; - if (nh == 44) {//b + if (nh == IPV6_NH_FRAGMENT) { if (l4_len < sizeof(ipv6_frag_hdr_t)) return; - const ipv6_frag_hdr_t* fh = (const ipv6_frag_hdr_t*)l4; - uint8_t inner_nh = fh->next_header; - uint16_t off_flags = bswap16(fh->offset_flags); - uint32_t ident = bswap32(fh->identification); + ipv6_frag_hdr_t fh; + if (!netpkt_copyout(pkt, l4_off, &fh, sizeof(fh))) return; + inner_nh = fh.next_header; + uint16_t off_flags = bswap16(fh.offset_flags); + ident = bswap32(fh.identification); + + off = ((uint32_t)(off_flags >> 3) & 0x1FFFu) * 8u; + more = (off_flags & 0x0001u) ? 1u : 0u; - uint32_t off = ((uint32_t)(off_flags >> 3) & 0x1FFFu) * 8u; - uint8_t more = (off_flags & 0x0001u) ? 1u : 0u; + frag_off = l4_off + (uint32_t)sizeof(ipv6_frag_hdr_t); + frag_len = l4_len-(uint32_t)sizeof(ipv6_frag_hdr_t); - const uint8_t* frag = (const uint8_t*)(fh + 1); - uint32_t frag_len = l4_len-(uint32_t)sizeof(ipv6_frag_hdr_t); + if (!off && !more) { + nh = inner_nh; + l4_off = frag_off; + l4_len = frag_len; + if (!ipv6_skip_ext_headers(pkt, &nh, &l4_off, &l4_len, false, NULL)) return; + } + } + if (nh == IPV6_NH_FRAGMENT) { if (more && (frag_len & 7u)) { uint8_t invoke_buf[sizeof(ipv6_hdr_t) + sizeof(ipv6_frag_hdr_t) + 8]; - uint32_t inv_len = (uint32_t)sizeof(ipv6_hdr_t) + l4_len; - const uint8_t *inv =(const uint8_t*) ip6; - if (inv_len > sizeof(invoke_buf)) { - memcpy(invoke_buf, ip6, sizeof(ipv6_hdr_t)); - uint32_t cpy = l4_len; - uint32_t max = (uint32_t)sizeof(ipv6_frag_hdr_t) + 8u; - if (cpy > max) cpy = max; - memcpy(invoke_buf + sizeof(ipv6_hdr_t), (void*)l4, cpy); - inv = invoke_buf; - inv_len = (uint32_t)sizeof(invoke_buf); - } - icmpv6_send_error((uint8_t)ifindex, ip6->dst, ip6->src, src_mac, 4, 0, 4u, inv, inv_len); + uint32_t inv_len = 0; + if (!ipv6_error_quote(pkt, ip6, l4_off, l4_len, invoke_buf, sizeof(invoke_buf), &inv_len)) return; + icmpv6_send_error(ifindex, ip6->dst, ip6->src, src_mac, 4, 0, 4u, invoke_buf, inv_len); return; } if (off + frag_len > 65535u) { uint8_t invoke_buf[sizeof(ipv6_hdr_t) + sizeof(ipv6_frag_hdr_t) + 8]; - uint32_t inv_len = (uint32_t)sizeof(ipv6_hdr_t) + l4_len; - const uint8_t *inv = (const uint8_t*)ip6; - if (inv_len > sizeof(invoke_buf)) { - memcpy(invoke_buf, ip6, sizeof(ipv6_hdr_t)); - uint32_t cpy = l4_len; - uint32_t max = (uint32_t)sizeof(ipv6_frag_hdr_t) + 8u; - if (cpy > max) cpy = max; - memcpy(invoke_buf + sizeof(ipv6_hdr_t), (void*)l4, cpy); - inv = invoke_buf; - inv_len = (uint32_t)sizeof(invoke_buf); - } - icmpv6_send_error((uint8_t)ifindex, ip6->dst, ip6->src, src_mac, 4, 0, 42u, inv, inv_len); + uint32_t inv_len = 0; + if (!ipv6_error_quote(pkt, ip6, l4_off, l4_len, invoke_buf, sizeof(invoke_buf), &inv_len))return; + icmpv6_send_error(ifindex, ip6->dst, ip6->src, src_mac, 4, 0, l4_off + 2, invoke_buf, inv_len); return; } - if (off + frag_len > 2048u * 8u) return; + if (off + frag_len > NET_FRAGBUF_DEFAULT_MAX_LEN) return; reass_slot_t* s = NULL; uint32_t now = (uint32_t)get_time(); @@ -659,14 +479,13 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { for (int i = 0; i < REASS_SLOTS; i++) { reass_slot_t *t = &g_reass[i]; if (!t->used) continue; - if (t->ifindex != (uint8_t)ifindex) continue; + if (t->ifindex != ifindex) continue; if (t->ident != ident) continue; if (t->next_header != inner_nh) continue; if (ipv6_cmp(t->src, ip6->src) != 0) continue; if (ipv6_cmp(t->dst, ip6->dst) != 0) continue; if (now - t->last_update_ms > 60000u) { - if (t->buf) free_sized(t->buf, 2048u * 8u); - memset(t, 0, sizeof(*t)); + reass_free(t); continue; } s = t; @@ -678,155 +497,106 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { reass_slot_t *t = &g_reass[i]; if (t->used) continue; - t->buf = (uint8_t*)malloc(2048u * 8u); - if (!t->buf) return; - t->used = 1; - t->ifindex = (uint8_t)ifindex; + g_reass_active++; + t->ifindex = ifindex; t->ident = ident; ipv6_cpy(t->src, ip6->src); ipv6_cpy(t->dst, ip6->dst); t->next_header = inner_nh; - t->first_rx_ms = now; t->last_update_ms = now; - t->total_len = 0; - t->have_last = 0; - t->have_first = 0; - memset(t->first_src_mac, 0, 6); - t->first_pkt_len = 0; - memset(t->first_pkt, 0, sizeof(t->first_pkt)); - memset(t->bitmap, 0, sizeof(t->bitmap)); + net_fragbuf_init(&t->frag); s = t; break; } } if (!s) return; - int overlap = 0; - uint32_t start = off / 8u; - uint32_t end = (off + frag_len + 7u) / 8u; - if (end > sizeof(s->bitmap)) end = sizeof(s->bitmap); - for (uint32_t i = start; i < end; i++) { - if (s->bitmap[i]) { - overlap = 1; - break; - } - } - if (overlap) { - reass_free(s); - return; - } int has_ulh = 0; if (off == 0) { - uint32_t ulh_off = 0; + uint32_t ulh_off = frag_off; + uint32_t ulh_len = frag_len; uint8_t nh = inner_nh; - int ok = 1; - - while (nh == 0 || nh == 43 || nh == 60 || nh == 51) { - const uint8_t *p = frag + ulh_off; - uint32_t avail = frag_len - ulh_off; - if (avail < 2) { ok = 0; break; } - - uint32_t hlen = 0; - if (nh == 0 || nh == 43 || nh == 60) hlen = ((uint32_t)p[1] + 1u) * 8u; - else hlen = ((uint32_t)p[1] + 2u) * 4u; - - if (hlen > avail) { ok = 0; break; } - - nh = p[0]; - ulh_off += hlen; - } - - if (ok) { + if (ipv6_skip_ext_headers(pkt, &nh, &ulh_off, &ulh_len, true, NULL)) { uint32_t need = 1; - if (nh == 6) need = 20; - else if (nh == 17) need = 8; - else if (nh == 58) need = 4; - if (frag_len - ulh_off >= need) has_ulh = 1; + if (nh == PROTO_TCP) need = 20; + else if (nh == PROTO_UDP) need = 8; + else if (nh == PROTO_ICMPV6) need = 4; + if (ulh_len >= need) has_ulh = 1; } } if (off == 0 && !has_ulh) { uint8_t invoke_buf[sizeof(ipv6_hdr_t) + sizeof(ipv6_frag_hdr_t) + 64]; - uint32_t inv_len = (uint32_t)sizeof(ipv6_hdr_t) + l4_len; - const uint8_t *inv = (const uint8_t*)ip6; - if (inv_len > sizeof(invoke_buf)) { - memcpy(invoke_buf, ip6, sizeof(ipv6_hdr_t)); - uint32_t cpy = l4_len; - uint32_t max = (uint32_t)sizeof(ipv6_frag_hdr_t) + 64u; - if (cpy > max) cpy = max; - memcpy(invoke_buf + sizeof(ipv6_hdr_t), (void*)l4, cpy); - inv = invoke_buf; - inv_len = (uint32_t)sizeof(invoke_buf); + uint32_t inv_len = 0; + if (!ipv6_error_quote(pkt, ip6, l4_off, l4_len, invoke_buf, sizeof(invoke_buf), &inv_len)) { + reass_free(s); + return; } - icmpv6_send_error((uint8_t)ifindex, ip6->dst, ip6->src, src_mac, 4, 3, 0u, inv, inv_len); + icmpv6_send_error(ifindex, ip6->dst, ip6->src, src_mac, 4, 3, 0u, invoke_buf, inv_len); reass_free(s); return; } if (off == 0 && !s->have_first) { - uint32_t inv_len = (uint32_t)sizeof(ipv6_hdr_t) + l4_len; - if (inv_len > sizeof(s->first_pkt)) inv_len = sizeof(s->first_pkt); - memcpy(s->first_pkt, ip6, inv_len); + uint32_t inv_len = 0; + if (!ipv6_error_quote(pkt, ip6, l4_off, l4_len, s->first_pkt, sizeof(s->first_pkt), &inv_len)) { + reass_free(s); + return; + } s->first_pkt_len = (uint16_t)inv_len; - memcpy(s->first_src_mac, src_mac, 6); + mac_copy(s->first_src_mac, src_mac); s->have_first = 1; } - memcpy(s->buf + off, frag, frag_len); + if (!net_fragbuf_add(&s->frag, pkt, frag_off, off, frag_len, more)) { + reass_free(s); + return; + } + s->last_update_ms = now; - start = off / 8u; - end = (off + frag_len + 7u) / 8u; - if (end > sizeof(s->bitmap)) end = sizeof(s->bitmap); - for (uint32_t i = start; i < end; i++) s->bitmap[i] = 1; + if (!net_fragbuf_complete(&s->frag)) return; - s->last_update_ms = (uint32_t)get_time(); + uint32_t payload_off = 0; + uint32_t payload_size = s->frag.total_len; - if (!more) { - s->have_last = 1; - s->total_len = off + frag_len; + netpkt_t* reassembled = net_fragbuf_take_packet(&s->frag); + if (!reassembled) { + reass_free(s); + return; } - int complete = 0; - if (s->have_last) { - uint32_t needed = (s->total_len + 7u) / 8u; - if (needed <= sizeof(s->bitmap)) { - complete = 1; - for (uint32_t i = 0; i < needed; i++) if (!s->bitmap[i]) { - complete = 0; - break; - } - } + if (!ipv6_skip_ext_headers(reassembled, &inner_nh, &payload_off, &payload_size, false, NULL)) { + netpkt_unref(reassembled); + reass_free(s); + return; } - if (!complete) return; - - uintptr_t payload_ptr = (uintptr_t)s->buf; - uint32_t payload_size = s->total_len; - if (!ipv6_skip_ext_headers(&inner_nh, &payload_ptr, &payload_size)) { + if (inner_nh == PROTO_ICMPV6) { + netpkt_t* l4pkt = netpkt_view(reassembled, payload_off, payload_size); + if (l4pkt) icmpv6_input(ifindex, ip6->src, ip6->dst, ip6->hop_limit, router_alert, src_mac, l4pkt); + netpkt_unref(reassembled); reass_free(s); return; } - if (inner_nh == 58) { - icmpv6_input(ifindex, ip6->src, ip6->dst, ip6->hop_limit, src_mac, (const uint8_t*)payload_ptr, payload_size); + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) { + netpkt_unref(reassembled); reass_free(s); return; } - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); - if (!l2) { reass_free(s); return; } - l3_ipv6_interface_t* cand[MAX_IPV6_PER_INTERFACE]; int ccount = 0; for (int x = 0; x < MAX_IPV6_PER_INTERFACE; x++) { l3_ipv6_interface_t* v6 = l2->l3_v6[x]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (!ipv6_l3_is_active(v6) || ipv6_is_placeholder_gua(v6->ip)) continue; cand[ccount++] = v6; } if (ccount == 0) { + netpkt_unref(reassembled); reass_free(s); return; } @@ -840,23 +610,26 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { } } if (!joined) { + netpkt_unref(reassembled); reass_free(s); return; } + bool include_shared = true; for (int i = 0; i < ccount; i++) { l3_ipv6_interface_t* v6 = cand[i]; - if (!ipv6_is_linklocal(v6->ip) && ipv6_is_linklocal(ip6->dst)) continue; - if (inner_nh == 17) udp_input(IP_VER6, ip6->src, ip6->dst, v6->l3_id, payload_ptr, payload_size); - else if (inner_nh == 6) tcp_input(IP_VER6, ip6->src, ip6->dst, v6->l3_id, payload_ptr, payload_size); + if (!ipv6_is_linklocal(v6->ip) && ipv6_is_linkscope_mcast(ip6->dst)) continue; + ipv6_deliver_transport(reassembled, payload_off, payload_size, inner_nh, ip6->src, ip6->dst, v6->l3_id, include_shared, NULL, &checksum_done); + include_shared = false; } + netpkt_unref(reassembled); reass_free(s); return; } int match_count = 0; - uint8_t match_l3id = 0; + l3_id_t match_l3id = 0; for (int i = 0; i < ccount; i++) { if (ipv6_cmp(cand[i]->ip, ip6->dst) == 0){ match_count++; @@ -865,28 +638,31 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { } if (match_count >= 1) { - if (inner_nh == 6) tcp_input(IP_VER6, ip6->src, ip6->dst, match_l3id, payload_ptr, payload_size); - else if (inner_nh == 17) udp_input(IP_VER6, ip6->src, ip6->dst, match_l3id, payload_ptr, payload_size); + udp_input_result_t result = UDP_INPUT_DROP; + ipv6_deliver_transport(reassembled, payload_off, payload_size, inner_nh, ip6->src, ip6->dst, match_l3id, true, &result, &checksum_done); + if (inner_nh == PROTO_UDP && result == UDP_INPUT_NO_LISTENER && s->have_first && s->first_pkt_len && + !ipv6_is_unspecified(ip6->src) && !ipv6_is_multicast(ip6->src)) + icmpv6_send_error(ifindex, ip6->dst, ip6->src, s->first_src_mac, ICMPV6_DEST_UNREACH, ICMPV6_CODE_PORT_UNREACH, 0, s->first_pkt, s->first_pkt_len); } - + netpkt_unref(reassembled); reass_free(s); return; } - if (nh == 58) { - icmpv6_input(ifindex, ip6->src, ip6->dst, ip6->hop_limit, src_mac, (const uint8_t*)l4, l4_len); + if (nh == PROTO_ICMPV6) { + netpkt_t* l4pkt = netpkt_view(pkt, l4_off, l4_len); + if (l4pkt) icmpv6_input(ifindex, ip6->src, ip6->dst, ip6->hop_limit, router_alert, src_mac, l4pkt); return; } - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (!l2) return; l3_ipv6_interface_t* cand[MAX_IPV6_PER_INTERFACE]; int ccount = 0; for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (!ipv6_l3_is_active(v6) || ipv6_is_placeholder_gua(v6->ip)) continue; cand[ccount++] = v6; } if (ccount == 0) return; @@ -901,27 +677,20 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { } if (!joined) return; + bool include_shared = true; for (int i = 0; i < ccount; i++) { l3_ipv6_interface_t* v6 = cand[i]; - if (!ipv6_is_linklocal(v6->ip) && ipv6_is_linklocal(ip6->dst)) + if (!ipv6_is_linklocal(v6->ip) && ipv6_is_linkscope_mcast(ip6->dst)) continue; - switch (ip6->next_header) { - case 17: - udp_input(IP_VER6, ip6->src, ip6->dst, v6->l3_id, l4, l4_len); - break; - case 6: - tcp_input(IP_VER6, ip6->src, ip6->dst, v6->l3_id, l4, l4_len); - break; - default: - break; - } + ipv6_deliver_transport(pkt, l4_off, l4_len, nh, ip6->src, ip6->dst, v6->l3_id, include_shared, NULL, &checksum_done); + include_shared = false; } return; } int match_count = 0; - uint8_t match_l3id = 0; + l3_id_t match_l3id = 0; for (int i = 0; i < ccount; ++i) { if (ipv6_cmp(cand[i]->ip, ip6->dst) == 0) { match_count++; @@ -930,15 +699,14 @@ void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]) { } if (match_count >= 1) { - switch (ip6->next_header) { - case 6: - tcp_input(IP_VER6, ip6->src, ip6->dst, match_l3id, l4, l4_len); - break; - case 17: - udp_input(IP_VER6, ip6->src, ip6->dst, match_l3id, l4, l4_len); - break; - default: - break; + udp_input_result_t result = UDP_INPUT_DROP; + ipv6_deliver_transport(pkt, l4_off, l4_len, nh, ip6->src, ip6->dst, match_l3id, true, &result, &checksum_done); + if (nh == PROTO_UDP && result == UDP_INPUT_NO_LISTENER && + !ipv6_is_unspecified(ip6->src) && !ipv6_is_multicast(ip6->src)) { + uint8_t invoke_buf[IPV6_MIN_MTU - sizeof(ipv6_hdr_t) - sizeof(icmpv6_hdr_t) - 4]; + uint32_t inv_len = 0; + if (ipv6_error_quote(pkt, ip6, l4_off, l4_len, invoke_buf, sizeof(invoke_buf), &inv_len)) + icmpv6_send_error(ifindex, ip6->dst, ip6->src, src_mac, ICMPV6_DEST_UNREACH, ICMPV6_CODE_PORT_UNREACH, 0, invoke_buf, inv_len); } return; } diff --git a/kernel/networking/internet_layer/ipv6.h b/kernel/networking/internet_layer/ipv6.h index a8e8fd36..d66211c4 100644 --- a/kernel/networking/internet_layer/ipv6.h +++ b/kernel/networking/internet_layer/ipv6.h @@ -8,6 +8,16 @@ extern "C" { #endif +typedef enum { + IPV6_NH_HOP_BY_HOP = 0, + IPV6_NH_ROUTING = 43, + IPV6_NH_FRAGMENT = 44, + IPV6_NH_ESP = 50, + IPV6_NH_AH = 51, + IPV6_NH_NO_NEXT = 59, + IPV6_NH_DEST_OPTS = 60 +} ipv6_next_header_t; + typedef struct __attribute__((packed)) { uint32_t ver_tc_fl; uint16_t payload_len; @@ -17,15 +27,9 @@ typedef struct __attribute__((packed)) { uint8_t dst[16]; } ipv6_hdr_t; -typedef ip_tx_scope_t ipv6_tx_scope_t; -typedef ip_tx_opts_t ipv6_tx_opts_t; - -void ipv6_send_packet(const uint8_t dst[16], uint8_t next_header, netpkt_t* pkt, const ipv6_tx_opts_t* opts, uint8_t hop_limit, uint8_t dontfrag); -void ipv6_input(uint16_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]); - -uint16_t ipv6_pmtu_get(const uint8_t dst[16]); -void ipv6_pmtu_note(const uint8_t dst[16], uint16_t mtu); - +bool ipv6_send_packet(const uint8_t dst[16], uint8_t next_header, netpkt_t* pkt, const ip_tx_opts_t* opts, uint8_t hop_limit, uint8_t dontfrag, uint8_t dontroute); +bool ipv6_skip_ext_headers(const netpkt_t* pkt, uint8_t* nh, uint32_t* l4_off, uint32_t* l4_len, bool stop_at_fragment, bool* router_alert); +void ipv6_input(uint8_t ifindex, netpkt_t* pkt, const uint8_t src_mac[6]); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv6_route.c b/kernel/networking/internet_layer/ipv6_route.c index 201bcea3..c835a55f 100644 --- a/kernel/networking/internet_layer/ipv6_route.c +++ b/kernel/networking/internet_layer/ipv6_route.c @@ -1,137 +1,326 @@ #include "ipv6_route.h" #include "std/memory.h" -#include "std/string.h" #include "networking/internet_layer/ipv6_utils.h" #include "networking/interface_manager.h" #include "syscalls/syscalls.h" +#include "exceptions/irq.h" + +#define IPV6_REDIRECT_MAX 32 +#define IPV6_ONLINK_PREFIX_MAX 32 +#define IPV6_ONLINK_PREFIX_INIT 4 + +typedef struct { + uint8_t prefix[16]; + uint64_t valid_until_ms; + uint8_t prefix_len; +} ipv6_onlink_prefix_t; + +typedef struct { + uint64_t next_expiry_ms; + uint8_t capacity; + uint8_t count; + ipv6_onlink_prefix_t entries[]; +} ipv6_onlink_table_t; + +static ipv6_onlink_table_t* g_onlink[MAX_L2_INTERFACES]; + +struct ipv6_rt_table { + l3_id_t owner_l3_id; + uint32_t epoch; + ipv6_rt_entry_t e[IPV6_RT_PER_IF_MAX]; + int len; +}; + +typedef struct { + uint8_t used; + uint8_t ifindex; + l3_id_t l3_id; + uint32_t l3_epoch; + uint32_t route_epoch; + uint8_t dst[16]; + uint8_t target[16]; +} ipv6_redirect_entry_t; + +static ipv6_redirect_entry_t g_redirects[IPV6_REDIRECT_MAX] = {0}; +static uint8_t g_redirect_rr = 0; + +static void ipv6_rt_bump(ipv6_rt_table_t* t) { + if (!t) return; + t->epoch++; + if (!t->epoch) t->epoch = 1; +} + +static void onlink_bump(uint8_t ifindex) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) return; + + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (v6 && v6->routing_table) ipv6_rt_bump((ipv6_rt_table_t*)v6->routing_table); + } +} + +static void onlink_refresh_expiry(ipv6_onlink_table_t* table) { + uint64_t next = UINT64_MAX; + for (uint8_t i = 0; i < table->count; i++) { + uint64_t valid = table->entries[i].valid_until_ms; + + if (valid < next) next = valid; + } + table->next_expiry_ms = next; +} + +static void onlink_expire(uint8_t ifindex, uint64_t now_ms) { + ipv6_onlink_table_t* table = g_onlink[ifindex-1]; + if (!table || now_ms < table->next_expiry_ms) return; + + bool changed = false; + for (uint8_t i = 0; i < table->count;){ + ipv6_onlink_prefix_t* entry = &table->entries[i]; + if (entry->valid_until_ms != UINT64_MAX && now_ms >= entry->valid_until_ms) { + table->count--; + if (i != table->count) table->entries[i] = table->entries[table->count]; + changed = true; + } else i++; + } + if (changed) { + onlink_refresh_expiry(table); + onlink_bump(ifindex); + } +} + +bool ipv6_rt_onlink_update(uint8_t ifindex, const uint8_t prefix[16], uint8_t prefix_len, uint32_t valid_lifetime_s) { + if (!ifindex || ifindex > MAX_L2_INTERFACES || !prefix || prefix_len > 128) return false; + + uint8_t network[16]; + ipv6_prefix_network(prefix, prefix_len, network); + ipv6_onlink_table_t* spare = NULL; + + for (;;) { + uint64_t now_ms = get_time(); + irq_flags_t irq = irq_save_disable(); + onlink_expire(ifindex, now_ms); + ipv6_onlink_table_t* table = g_onlink[ifindex - 1]; + + int found = -1; + if (table) { + for (uint8_t i = 0; i < table->count; i++) { + const ipv6_onlink_prefix_t* entry = &table->entries[i]; + if (entry->prefix_len == prefix_len && ipv6_cmp(entry->prefix, network) == 0) { + found = i; + break; + } + } + } + + if (!valid_lifetime_s) { + ipv6_onlink_table_t* retired = NULL; + if (found >= 0) { + bool refresh = table->entries[found].valid_until_ms == table->next_expiry_ms; + table->count--; + if ((uint8_t)found != table->count) table->entries[found] = table->entries[table->count]; + if (!table->count) { + g_onlink[ifindex - 1] = NULL; + retired = table; + } else if (refresh) onlink_refresh_expiry(table); + onlink_bump(ifindex); + } + irq_restore(irq); + release(retired); + release(spare); + return true; + } + + ipv6_onlink_table_t* retired = NULL; + if (found < 0 && (!table || table->count == table->capacity)) { + uint8_t capacity = table ? table->capacity : 0; + if (capacity >= IPV6_ONLINK_PREFIX_MAX) { + irq_restore(irq); + release(spare); + return false; + } + uint8_t needed = capacity ? capacity * 2 : IPV6_ONLINK_PREFIX_INIT; + if (needed > IPV6_ONLINK_PREFIX_MAX) needed = IPV6_ONLINK_PREFIX_MAX; + if (!spare || spare->capacity < needed) { + irq_restore(irq); + release(spare); + spare = zalloc(sizeof(spare->entries[0]) * needed + sizeof(*spare)); + if (!spare) return false; + spare->capacity = needed; + continue; + } + + spare->next_expiry_ms = table ? table->next_expiry_ms : UINT64_MAX; + if (table) { + spare->count = table->count; + memcpy(spare->entries, table->entries, sizeof(table->entries[0]) * table->count); + } + g_onlink[ifindex-1] = spare; + retired = table; + table = spare; + spare = NULL; + } + + uint64_t valid_until_ms = UINT64_MAX; + if (valid_lifetime_s != UINT32_MAX) valid_until_ms = now_ms + (uint64_t)valid_lifetime_s * 1000; + ipv6_onlink_prefix_t* entry; + if (found >= 0) entry = &table->entries[found]; + else { + entry = &table->entries[table->count++]; + entry->prefix_len = prefix_len; + ipv6_cpy(entry->prefix, network); + entry->valid_until_ms = UINT64_MAX; + } + uint64_t previous = entry->valid_until_ms; + entry->valid_until_ms = valid_until_ms; + if (previous == table->next_expiry_ms && valid_until_ms > previous) onlink_refresh_expiry(table); + else if (valid_until_ms < table->next_expiry_ms) table->next_expiry_ms = valid_until_ms; + if (found < 0) onlink_bump(ifindex); + irq_restore(irq); + release(retired); + release(spare); + return true; + } +} + +void ipv6_rt_onlink_clear(uint8_t ifindex) { + if (!ifindex || ifindex > MAX_L2_INTERFACES) return; + irq_flags_t irq = irq_save_disable(); + ipv6_onlink_table_t* retired = g_onlink[ifindex - 1]; + if (retired) { + g_onlink[ifindex - 1] = NULL; + onlink_bump(ifindex); + } + irq_restore(irq); + release(retired); +} static bool v6_l3_ok_for_tx(l3_ipv6_interface_t* v6, int dst_is_ll, int dst_is_loop) { - if (!v6 || !v6->l2) return false; - if (!v6->l2->is_up) return false; - if (v6->cfg == IPV6_CFG_DISABLE) return false; + if (!ipv6_l3_is_ready(v6)) return false; if (v6->is_localhost && !dst_is_loop) return false; - if (ipv6_is_unspecified(v6->ip)) return false; - if (v6->dad_state != IPV6_DAD_OK)return false; - if (!v6->port_manager) return false; int src_is_ll = ipv6_is_linklocal(v6->ip) ? 1 : 0; if (src_is_ll != dst_is_ll) return false; return true; } -static bool l3_allowed(uint8_t id, const uint8_t* allowed, int n) { - if (!allowed || n <= 0) return true; - for (int i = 0; i < n; ++i) if (allowed[i] == id) return true; +bool ipv6_tx_plan_valid(const ipv6_tx_plan_t* plan) { + if (!plan || !plan->l3_id) return false; + + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(plan->l3_id); + if (!ipv6_l3_is_ready(v6)) return false; + if (v6->epoch != plan->l3_epoch) return false; + return ipv6_cmp(v6->ip, plan->src_ip) == 0; +} + +bool ipv6_tx_plan_onlink(const ipv6_tx_plan_t* plan, const uint8_t dst[16]) { + if (!dst || !ipv6_tx_plan_valid(plan)) return false; + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(plan->l3_id); + if (ipv6_is_loopback(dst)) return v6->is_localhost; + if (ipv6_is_linklocal(dst) || ipv6_is_linkscope_mcast(dst) || ipv6_is_multicast(dst)) return true; + if (!(v6->ra_has && (v6->cfg & IPV6_CFG_SLAAC)) && v6->prefix_len && ipv6_prefix_matches(v6->ip, dst, v6->prefix_len)) return true; + if (v6->routing_table) { + uint8_t via[16] = {0}; + if (ipv6_rt_lookup_in((const ipv6_rt_table_t*)v6->routing_table, dst, via, NULL, NULL) && ipv6_is_unspecified(via)) return true; + } return false; } -bool ipv6_build_tx_plan(const uint8_t dst[16], const ip_tx_opts_t* hint, const uint8_t* allowed_l3, int allowed_n, ipv6_tx_plan_t* out) { +bool ipv6_build_tx_plan(const uint8_t dst[16], const ip_tx_opts_t* hint, ipv6_tx_plan_t* out) { if (!dst || !out) return false; memset(out, 0, sizeof(*out)); - out->fixed_opts.scope = IP_TX_AUTO; - out->fixed_opts.index = 0; int dst_is_ll = (ipv6_is_linklocal(dst) || ipv6_is_linkscope_mcast(dst)) ? 1 : 0; int dst_is_loop = ipv6_is_loopback(dst) ? 1 : 0; if (hint && hint->scope == IP_TX_BOUND_L3) { - uint8_t id = hint->index; - if (!l3_allowed(id, allowed_l3, allowed_n)) return false; + l3_id_t id = hint->target.l3_id; l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); if (!v6_l3_ok_for_tx(v6, dst_is_ll, dst_is_loop)) return false; out->l3_id = id; - memcpy(out->src_ip, v6->ip, 16); - out->fixed_opts.scope = IP_TX_BOUND_L3; - out->fixed_opts.index = id; + out->l3_epoch = v6->epoch; + ipv6_cpy(out->src_ip, v6->ip); return true; } - uint8_t cand[64]; - int n = 0; - + uint8_t ifindex = 0; if (hint && hint->scope == IP_TX_BOUND_L2) { - l2_interface_t* l2 = l2_interface_find_by_index(hint->index); + l2_interface_t* l2 = l2_interface_find_by_index(hint->target.ifindex); if (!l2 || !l2->is_up) return false; - for (int s = 0; s < MAX_IPV6_PER_INTERFACE && n < (int)sizeof(cand); ++s){ - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6_l3_ok_for_tx(v6, dst_is_ll, dst_is_loop)) continue; - if (!l3_allowed(v6->l3_id, allowed_l3, allowed_n)) continue; - cand[n++] = v6->l3_id; - } - } else { - uint8_t cnt = l2_interface_count(); - for (uint8_t i = 0; i < cnt && n < (int)sizeof(cand); ++i){ - l2_interface_t* l2 = l2_interface_at(i); + ifindex = l2->ifindex; + } + + l3_id_t chosen = 0; + if (!ipv6_rt_pick_best_l3(dst, ifindex, &chosen)) { + uint8_t first = ifindex ? ifindex : 1; + uint8_t last = ifindex ? ifindex : MAX_L2_INTERFACES; + for (uint8_t ix = first; ix <= last && !chosen; ix++) { + l2_interface_t* l2 = l2_interface_find_by_index(ix); if (!l2 || !l2->is_up) continue; - for (int s = 0; s < MAX_IPV6_PER_INTERFACE && n < (int)sizeof(cand); ++s){ - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!v6_l3_ok_for_tx(v6, dst_is_ll, dst_is_loop)) continue; - if (!l3_allowed(v6->l3_id, allowed_l3, allowed_n)) continue; - cand[n++] = v6->l3_id; + for (int slot = 0; slot < MAX_IPV6_PER_INTERFACE; slot++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[slot]; + if (!v6_l3_ok_for_tx(v6, dst_is_ll, dst_is_loop)) continue; + chosen = v6->l3_id; + break; } } + if (!chosen) return false; } - if (n == 0) return false; - - uint8_t chosen = 0; - if (!ipv6_rt_pick_best_l3_in(cand, n, dst, &chosen)) chosen = cand[0]; - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen); if (!v6_l3_ok_for_tx(v6, dst_is_ll, dst_is_loop)) return false; out->l3_id = chosen; - memcpy(out->src_ip, v6->ip, 16); - out->fixed_opts.scope = IP_TX_BOUND_L3; - out->fixed_opts.index = chosen; + out->l3_epoch = v6->epoch; + ipv6_cpy(out->src_ip, v6->ip); return true; } -struct ipv6_rt_table { - ipv6_rt_entry_t e[IPV6_RT_PER_IF_MAX]; - int len; -}; - -ipv6_rt_table_t* ipv6_rt_create(void) { - ipv6_rt_table_t* t = malloc(sizeof(*t)); +ipv6_rt_table_t* ipv6_rt_create(l3_id_t owner_l3_id) { + ipv6_rt_table_t* t = zalloc(sizeof(*t)); if (!t) return 0; - memset(t, 0, sizeof(*t)); + t->owner_l3_id = owner_l3_id; + t->epoch = 1; return t; } void ipv6_rt_destroy(ipv6_rt_table_t* t) { if (!t) return; - free_sized(t, sizeof(*t)); + release(t); } void ipv6_rt_clear(ipv6_rt_table_t* t) { if (!t) return; - + bool changed = t->len != 0; t->len = 0; memset(t->e, 0, sizeof(t->e)); + if (changed) ipv6_rt_bump(t); } bool ipv6_rt_add_in(ipv6_rt_table_t* t, const uint8_t net[16], uint8_t plen, const uint8_t gw[16], uint16_t metric) { if (!t) return false; for (int i = 0; i < t->len; i++) { - if (t->e[i].prefix_len == plen && memcmp(t->e[i].network, net, 16) == 0) { - memcpy(t->e[i].gateway, gw, 16); + if (t->e[i].prefix_len == plen && ipv6_cmp(t->e[i].network, net) == 0) { + if (ipv6_cmp(t->e[i].gateway, gw) == 0 && t->e[i].metric == metric) return true; + ipv6_cpy(t->e[i].gateway, gw); t->e[i].metric = metric; + ipv6_rt_bump(t); return true; } } if (t->len >= IPV6_RT_PER_IF_MAX) return false; - memcpy(t->e[t->len].network, net, 16); - memcpy(t->e[t->len].gateway, gw, 16); + ipv6_cpy(t->e[t->len].network, net); + ipv6_cpy(t->e[t->len].gateway, gw); t->e[t->len].prefix_len = plen; t->e[t->len].metric = metric; t->len++; + ipv6_rt_bump(t); return true; } @@ -140,9 +329,11 @@ bool ipv6_rt_del_in(ipv6_rt_table_t* t, const uint8_t net[16], uint8_t plen) { if (!t) return false; for (int i = 0; i < t->len; i++) { - if (t->e[i].prefix_len == plen && memcmp(t->e[i].network, net, 16) == 0) { - t->e[i] = t->e[--t->len]; + if (t->e[i].prefix_len == plen && ipv6_cmp(t->e[i].network, net) == 0) { + t->len--; + t->e[i] = t->e[t->len]; memset(&t->e[t->len], 0, sizeof(t->e[0])); + ipv6_rt_bump(t); return true; } } @@ -150,6 +341,20 @@ bool ipv6_rt_del_in(ipv6_rt_table_t* t, const uint8_t net[16], uint8_t plen) { return false; } +int ipv6_rt_count(const ipv6_rt_table_t* t) { + return t ? t->len : 0; +} + +bool ipv6_rt_get(const ipv6_rt_table_t* t, int index, ipv6_rt_entry_t* out) { + if (!t || !out || index < 0 || index >= t->len) return false; + *out = t->e[index]; + return true; +} + +uint32_t ipv6_rt_epoch(const ipv6_rt_table_t* t) { + return t ? t->epoch : 0; +} + bool ipv6_rt_lookup_in(const ipv6_rt_table_t* t, const uint8_t dst[16], uint8_t next_hop[16], int* out_pl, int* out_metric) { if (!t) return false; @@ -158,156 +363,267 @@ bool ipv6_rt_lookup_in(const ipv6_rt_table_t* t, const uint8_t dst[16], uint8_t uint8_t best_gw[16] = {0}; for (int i = 0; i < t->len; i++) { - bool match = false; - - if (t->e[i].prefix_len == 0) { - match = true; - } else { - int plen = t->e[i].prefix_len; - int fb = plen / 8; - int rb = plen % 8; - - match = true; - for (int j = 0; j < fb; j++) { - if (dst[j] != t->e[i].network[j]) { - match = false; - break; - } - } + const ipv6_rt_entry_t *entry = &t->e[i]; + int pl = entry->prefix_len; + int met = entry->metric; + + if (pl < best_pl || (pl == best_pl && met >= best_metric)) continue; + if (!ipv6_prefix_matches(dst, entry->network, entry->prefix_len)) continue; + best_pl = pl; + best_metric = met; + ipv6_cpy(best_gw, entry->gateway); + } - if (match && rb) { - uint8_t m = (uint8_t)(0xFF << (8 - rb)); - if ((dst[fb] & m) != (t->e[i].network[fb] & m)) match = false; + l3_ipv6_interface_t* owner = l3_ipv6_find_by_id(t->owner_l3_id); + l2_interface_t* l2 = owner ? owner->l2 : NULL; + if (l2 && !ipv6_is_unspecified(dst) && !ipv6_is_loopback(dst) && !ipv6_is_linklocal(dst) && !ipv6_is_multicast(dst)) { + int pl = -1; + uint8_t ifindex = l2->ifindex; + if (ifindex && ifindex <= MAX_L2_INTERFACES) { + irq_flags_t irq = irq_save_disable(); + onlink_expire(ifindex, get_time()); + const ipv6_onlink_table_t* table = g_onlink[ifindex - 1]; + if (table) { + for (uint8_t i = 0; i < table->count; i++) { + const ipv6_onlink_prefix_t* entry = &table->entries[i]; + uint8_t plen = entry->prefix_len; + if (plen <= pl || !ipv6_prefix_matches(dst, entry->prefix, plen)) continue; + pl = plen; + if (pl == 128) break; + } } + irq_restore(irq); } - - if (!match) continue; - - int pl = t->e[i].prefix_len; - int met = t->e[i].metric; - - if (pl > best_pl || (pl == best_pl && met < best_metric)) { + int met = l2->base_metric; + if (pl >= 0 && (pl > best_pl || (pl == best_pl && met < best_metric))) { best_pl = pl; best_metric = met; - memcpy(best_gw, t->e[i].gateway, 16); + memset(best_gw, 0, sizeof(best_gw)); } } if (best_pl < 0) return false; - if (next_hop) memcpy(next_hop, best_gw, 16); + if (next_hop) ipv6_cpy(next_hop, best_gw); if (out_pl) *out_pl =best_pl; if (out_metric) *out_metric = best_metric; return true; } -void ipv6_rt_ensure_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric) { - if (!t) return; +bool ipv6_next_hop_for_l3(l3_id_t l3_id, const uint8_t dst[16], uint8_t next_hop[16]) { + l3_ipv6_interface_t* src_v6 = l3_ipv6_find_by_id(l3_id); + if (!ipv6_l3_is_ready(src_v6)) return false; + ipv6_rt_table_t* rt = (ipv6_rt_table_t*)src_v6->routing_table; + + if (rt) { + bool checked_expiry = false; + uint32_t route_epoch = 0; + + for (int i = 0; i < IPV6_REDIRECT_MAX; i++) { + ipv6_redirect_entry_t* e = &g_redirects[i]; + if (!e->used || e->l3_id != src_v6->l3_id || ipv6_cmp(e->dst, dst) != 0) continue; + + if (!checked_expiry) { + if (src_v6->l2) { + uint8_t ifindex = src_v6->l2->ifindex; + if (ifindex && ifindex <= MAX_L2_INTERFACES) { + irq_flags_t irq = irq_save_disable(); + onlink_expire(ifindex, get_time()); + irq_restore(irq); + } + } + route_epoch = ipv6_rt_epoch(rt); + checked_expiry = true; + } + if (e->l3_epoch != src_v6->epoch || e->route_epoch != route_epoch) { + memset(e, 0, sizeof(*e)); + continue; + } - if (ip && plen &&!ipv6_is_unspecified(ip)) { - uint8_t net[16]; - ipv6_cpy(net, ip); + ipv6_cpy(next_hop, e->target); + return true; + } + } - if (plen < 128) { - int fb = plen / 8; - int rb = plen % 8; + uint8_t via[16] = {0}; + if (rt && ipv6_rt_lookup_in(rt, dst, via, NULL, NULL)) { + if (!ipv6_is_unspecified(via)) ipv6_cpy(next_hop, via); + else ipv6_cpy(next_hop, dst); + } else if (!(src_v6->ra_has && (src_v6->cfg & IPV6_CFG_SLAAC)) && src_v6->prefix_len && ipv6_prefix_matches(dst, src_v6->ip, src_v6->prefix_len)) ipv6_cpy(next_hop, dst); + else if (!ipv6_is_unspecified(src_v6->gateway) && ipv6_is_linklocal(src_v6->gateway)) ipv6_cpy(next_hop, src_v6->gateway); + else return false; - for (int i = fb + (rb > 0); i < 16; i++) net[i] = 0; + return true; +} - if (rb) { - uint8_t m = (uint8_t)(0xFF <<(8 - rb)); - net[fb] &=m; - } +bool ipv6_redirect_update(l3_id_t l3_id, const uint8_t router[16], const uint8_t dst[16], const uint8_t target[16]) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!ipv6_l3_is_ready(v6) || !v6->routing_table || !router || !dst || !target) return false; + + uint8_t current_next_hop[16]; + if (!ipv6_next_hop_for_l3(l3_id, dst, current_next_hop) || ipv6_cmp(current_next_hop, router) != 0) return false; + + int slot = -1; + for (int i = 0; i < IPV6_REDIRECT_MAX; i++) { + ipv6_redirect_entry_t* e = &g_redirects[i]; + if (e->used && e->l3_id == l3_id && ipv6_cmp(e->dst, dst) == 0) { + slot = i; + break; } + if (!e->used && slot < 0) slot = i; + } - ipv6_rt_add_in(t, net, plen, (const uint8_t[16]){0}, base_metric); + if (slot < 0) { + slot = g_redirect_rr; + g_redirect_rr = (uint8_t)((g_redirect_rr + 1) % IPV6_REDIRECT_MAX); } - if (gw && !ipv6_is_unspecified(gw)) { - uint8_t z[16] = {0}; - ipv6_rt_add_in(t, z, 0, gw, (uint16_t)(base_metric + 1)); + ipv6_redirect_entry_t* e = &g_redirects[slot]; + e->used = 0; + e->ifindex = v6->l2->ifindex; + e->l3_id = l3_id; + e->l3_epoch = v6->epoch; + e->route_epoch = ipv6_rt_epoch((const ipv6_rt_table_t*)v6->routing_table); + ipv6_cpy(e->dst, dst); + ipv6_cpy(e->target, target); + e->used = 1; + return true; +} + +bool ipv6_redirect_is_router(uint8_t ifindex, const uint8_t target[16]) { + if (!ifindex || ifindex > MAX_L2_INTERFACES || !target) return false; + bool checked_expiry = false; + + for (int i = 0; i < IPV6_REDIRECT_MAX; i++) { + ipv6_redirect_entry_t* e = &g_redirects[i]; + if (!e->used || e->ifindex != ifindex || ipv6_cmp(e->target, target) != 0) continue; + if (!checked_expiry) { + irq_flags_t irq = irq_save_disable(); + onlink_expire(ifindex, get_time()); + irq_restore(irq); + checked_expiry = true; + } + + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(e->l3_id); + if (!ipv6_l3_is_ready(v6) || !v6->routing_table || v6->l2->ifindex != ifindex || + e->l3_epoch != v6->epoch || e->route_epoch != ipv6_rt_epoch((const ipv6_rt_table_t*)v6->routing_table)) { + memset(e, 0, sizeof(*e)); + continue; + } + + if (ipv6_cmp(e->dst, e->target) != 0) return true; } + + return false; } -void ipv6_rt_sync_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric) { - if (!t) return; +void ipv6_redirect_invalidate_router(uint8_t ifindex, const uint8_t target[16]) { + if (!ifindex || !target) return; - uint8_t z[16] = {0}; + for (int i = 0; i < IPV6_REDIRECT_MAX; i++) { + ipv6_redirect_entry_t* e = &g_redirects[i]; + if (!e->used || e->ifindex != ifindex || ipv6_cmp(e->target, target) != 0) continue; + if (ipv6_cmp(e->dst, e->target) != 0) memset(e, 0, sizeof(*e)); + } +} + +void ipv6_redirect_invalidate(uint8_t ifindex, const uint8_t next_hop[16]) { + if (!ifindex) return; - if (gw && !ipv6_is_unspecified(gw)) ipv6_rt_add_in(t, z, 0,gw, (uint16_t)(base_metric + 1)); - else ipv6_rt_del_in(t, z, 0); + for (int i = 0; i < IPV6_REDIRECT_MAX; i++) { + ipv6_redirect_entry_t* e = &g_redirects[i]; + if (!e->used || e->ifindex != ifindex) continue; + if (!next_hop || ipv6_cmp(e->target, next_hop) == 0) memset(e, 0, sizeof(*e)); + } +} - if (ip && plen && !ipv6_is_unspecified(ip)) { +void ipv6_rt_ensure_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric) { + if (!t) return; + + if (ip && plen && !ipv6_is_unspecified(ip) && !ipv6_is_placeholder_gua(ip)) { uint8_t net[16]; - ipv6_cpy(net, ip); + ipv6_prefix_network(ip, plen, net); + ipv6_rt_add_in(t, net, plen, (const uint8_t[16]){0}, base_metric); + } + + if (gw && !ipv6_is_unspecified(gw)) { + ipv6_rt_add_in(t, (const uint8_t[16]){0}, 0, gw, (uint16_t)(base_metric + 1)); + } +} - if (plen < 128) { - int fb = plen / 8; - int rb = plen % 8; +void ipv6_rt_sync_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric) { + if (!t) return; - for (int i = fb + (rb > 0); i < 16; i++)net[i] = 0; + if (gw && !ipv6_is_unspecified(gw)) ipv6_rt_add_in(t, (const uint8_t[16]){0}, 0,gw, (uint16_t)(base_metric + 1)); + else ipv6_rt_del_in(t, (const uint8_t[16]){0}, 0); - if (rb) { - uint8_t m = (uint8_t)(0xFF << (8 - rb)); - net[fb] &= m; - } - } - - ipv6_rt_add_in(t, net, plen, (const uint8_t[16]) {0}, base_metric); + if (ip && plen && !ipv6_is_unspecified(ip) && !ipv6_is_placeholder_gua(ip)) { + uint8_t net[16]; + ipv6_prefix_network(ip, plen, net); + l3_ipv6_interface_t* owner = l3_ipv6_find_by_id(t->owner_l3_id); + if (owner && owner->ra_has && (owner->cfg & IPV6_CFG_SLAAC)) ipv6_rt_del_in(t, net, plen); + else ipv6_rt_add_in(t, net, plen, (const uint8_t[16]) {0}, base_metric); } } -bool ipv6_rt_pick_best_l3_in(const uint8_t* l3_ids, int n_ids, const uint8_t dst[16], uint8_t* out_l3) { +bool ipv6_rt_pick_best_l3(const uint8_t dst[16], uint8_t ifindex, l3_id_t* out_l3) { + if (!dst) return false; + int best_pl = -1; int best_cost = 0x7FFFFFFF; - uint8_t best_l3 = 0; + l3_id_t best_l3 = 0; + int dst_is_ll = (ipv6_is_linklocal(dst) || ipv6_is_linkscope_mcast(dst)) ? 1 : 0; + int dst_is_loop = ipv6_is_loopback(dst) ? 1 : 0; + uint8_t first = ifindex ? ifindex : 1; + uint8_t last = ifindex ? ifindex : MAX_L2_INTERFACES; - for (int i = 0; i < n_ids; i++) { - l3_ipv6_interface_t* x = l3_ipv6_find_by_id(l3_ids[i]); - if (!x || !x->l2)continue; - if (x->cfg == IPV6_CFG_DISABLE) continue; - if (ipv6_is_unspecified(x->ip)) continue; + for (uint8_t ix = first; ix <= last; ix++) { + l2_interface_t* l2 = l2_interface_find_by_index(ix); + if (!l2 || !l2->is_up) continue; - int l2base = x->l2->base_metric; + for (int slot = 0; slot < MAX_IPV6_PER_INTERFACE; slot++) { + l3_ipv6_interface_t* x = l2->l3_v6[slot]; + if (!v6_l3_ok_for_tx(x, dst_is_ll, dst_is_loop)) continue; - int pl_conn = -1; - if (x->prefix_len) { - int pl = ipv6_common_prefix_len(dst, x->ip); - if (pl >= x->prefix_len) pl_conn = x->prefix_len; - } + int pl_conn = -1; + if (!(x->ra_has && (x->cfg & IPV6_CFG_SLAAC)) && x->prefix_len) { + if (ipv6_prefix_matches(dst, x->ip, x->prefix_len)) pl_conn = x->prefix_len; + } - int pl_tab = -1; - int met_tab = 0x7FFF; + int pl_tab = -1; + int met_tab = 0x7FFF; - if (x->routing_table) { - uint8_t via[16] = {0}; - int out_pl = -1; - int out_met = 0x7FFF; + if (x->routing_table) { + const ipv6_rt_table_t* rt = (const ipv6_rt_table_t*)x->routing_table; + if (rt->owner_l3_id && rt->owner_l3_id != x->l3_id) continue; + int out_pl = -1; + int out_met = 0x7FFF; - if (ipv6_rt_lookup_in((const ipv6_rt_table_t*)x->routing_table, dst, via, &out_pl, &out_met)) { - pl_tab = out_pl; - met_tab = out_met; + if (ipv6_rt_lookup_in(rt, dst, NULL, &out_pl, &out_met)) { + pl_tab = out_pl; + met_tab = out_met; + } } - } - int cand_pl = pl_conn; - int cand_cost = l2base; + int cand_pl = pl_conn; + int cand_cost = l2->base_metric; - if (pl_tab > cand_pl || (pl_tab == cand_pl && l2base + met_tab < cand_cost)) { - cand_pl = pl_tab; - cand_cost = l2base + met_tab; - } + if (pl_tab > cand_pl || (pl_tab == cand_pl && met_tab < cand_cost)) { + cand_pl = pl_tab; + cand_cost = met_tab; + } - if (cand_pl > best_pl || (cand_pl == best_pl && cand_cost best_pl || (cand_pl == best_pl && cand_cost < best_cost) || + (cand_pl == best_pl && cand_cost == best_cost && x->l3_id < best_l3)) { + best_pl = cand_pl; + best_cost = cand_cost; + best_l3 = x->l3_id; + } } } if (best_pl < 0) return false; - if (out_l3) *out_l3 = best_l3; return true; } \ No newline at end of file diff --git a/kernel/networking/internet_layer/ipv6_route.h b/kernel/networking/internet_layer/ipv6_route.h index 3b632955..1e5f1c4a 100644 --- a/kernel/networking/internet_layer/ipv6_route.h +++ b/kernel/networking/internet_layer/ipv6_route.h @@ -17,26 +17,39 @@ typedef struct { typedef struct ipv6_rt_table ipv6_rt_table_t; -ipv6_rt_table_t* ipv6_rt_create(void); +ipv6_rt_table_t* ipv6_rt_create(l3_id_t owner_l3_id); void ipv6_rt_destroy(ipv6_rt_table_t* t); void ipv6_rt_clear(ipv6_rt_table_t* t); bool ipv6_rt_add_in(ipv6_rt_table_t* t, const uint8_t net[16], uint8_t plen, const uint8_t gw[16], uint16_t metric); bool ipv6_rt_del_in(ipv6_rt_table_t* t, const uint8_t net[16], uint8_t plen); +int ipv6_rt_count(const ipv6_rt_table_t* t); +bool ipv6_rt_get(const ipv6_rt_table_t* t, int index, ipv6_rt_entry_t* out); +uint32_t ipv6_rt_epoch(const ipv6_rt_table_t* t); bool ipv6_rt_lookup_in(const ipv6_rt_table_t* t, const uint8_t dst[16], uint8_t next_hop[16], int* out_prefix_len, int* out_metric); +bool ipv6_next_hop_for_l3(l3_id_t l3_id, const uint8_t dst[16], uint8_t next_hop[16]); +bool ipv6_redirect_update(l3_id_t l3_id, const uint8_t router[16], const uint8_t dst[16], const uint8_t target[16]); +bool ipv6_redirect_is_router(uint8_t ifindex, const uint8_t target[16]); +void ipv6_redirect_invalidate_router(uint8_t ifindex, const uint8_t target[16]); +void ipv6_redirect_invalidate(uint8_t ifindex, const uint8_t next_hop[16]); +bool ipv6_rt_onlink_update(uint8_t ifindex, const uint8_t prefix[16], uint8_t prefix_len, uint32_t valid_lifetime_s); +void ipv6_rt_onlink_clear(uint8_t ifindex); + void ipv6_rt_ensure_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric); void ipv6_rt_sync_basics(ipv6_rt_table_t* t, const uint8_t ip[16], uint8_t plen, const uint8_t gw[16], uint16_t base_metric); -bool ipv6_rt_pick_best_l3_in(const uint8_t* l3_ids, int n_ids, const uint8_t dst[16], uint8_t* out_l3); +bool ipv6_rt_pick_best_l3(const uint8_t dst[16], uint8_t ifindex, l3_id_t* out_l3); typedef struct { - uint8_t l3_id; + l3_id_t l3_id; + uint32_t l3_epoch; uint8_t src_ip[16]; - ip_tx_opts_t fixed_opts; } ipv6_tx_plan_t; -bool ipv6_build_tx_plan(const uint8_t dst[16], const ip_tx_opts_t* hint, const uint8_t* allowed_l3, int allowed_n, ipv6_tx_plan_t* out); +bool ipv6_tx_plan_valid(const ipv6_tx_plan_t* plan); +bool ipv6_tx_plan_onlink(const ipv6_tx_plan_t* plan, const uint8_t dst[16]); +bool ipv6_build_tx_plan(const uint8_t dst[16], const ip_tx_opts_t* hint, ipv6_tx_plan_t* out); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/ipv6_utils.c b/kernel/networking/internet_layer/ipv6_utils.c index 3b1a9143..7a0a276a 100644 --- a/kernel/networking/internet_layer/ipv6_utils.c +++ b/kernel/networking/internet_layer/ipv6_utils.c @@ -1,6 +1,7 @@ #include "ipv6_utils.h" -#include "std/std.h" +#include "std/memory.h" #include "networking/network.h" +#include "networking/interface_manager.h" bool ipv6_is_unspecified(const uint8_t ip[16]) { for (int i = 0; i < 16; i++) if (ip[i] != 0) return false; @@ -18,6 +19,29 @@ bool ipv6_is_ula(const uint8_t ip[16]) { return (ip[0] & 0xFE) == 0xFC; } bool ipv6_is_linklocal(const uint8_t ip[16]) { return ip[0] == 0xFE && (ip[1] & 0xC0) == 0x80; } +bool ipv6_l3_is_active(l3_ipv6_interface_t *v6) { + if (!v6 || !v6->l2) return false; + if (!l2_interface_is_operational(v6->l2)) return false; + if (v6->cfg == IPV6_CFG_DISABLE) return false; + return true; +} + +bool ipv6_l3_is_ready(l3_ipv6_interface_t *v6) { + if (!ipv6_l3_is_active(v6)) return false; + if (ipv6_is_unspecified(v6->ip) || ipv6_is_placeholder_gua(v6->ip)) return false; + if (v6->dad_state != IPV6_DAD_OK) return false; + return true; +} + +bool ipv6_l3_is_tcp_usable(l3_ipv6_interface_t *v6) { + if (!ipv6_l3_is_active(v6)) return false; + if (v6->is_localhost) return false; + if (ipv6_is_unspecified(v6->ip) || ipv6_is_placeholder_gua(v6->ip)) return false; + if (v6->dad_state == IPV6_DAD_FAILED) return false; + if (!(v6->kind & IPV6_ADDRK_LINK_LOCAL) && v6->dad_state != IPV6_DAD_OK) return false; + return true; +} + int ipv6_cmp(const uint8_t a[16], const uint8_t b[16]) { for (int i = 0; i < 16; i++) if (a[i] != b[i]) return (int)a[i] - (int)b[i]; return 0; @@ -26,17 +50,31 @@ int ipv6_cmp(const uint8_t a[16], const uint8_t b[16]) { void ipv6_cpy(uint8_t dst[16], const uint8_t src[16]) { memcpy(dst, src, 16); } int ipv6_common_prefix_len(const uint8_t a[16], const uint8_t b[16]) { - int bits = 0; for (int i = 0; i < 16; i++) { uint8_t x = (uint8_t)(a[i] ^ b[i]); - if (x == 0) { bits += 8; continue; } - for (int bpos = 7; bpos >= 0; --bpos) { - if (x & (1u << bpos)) return bits + (7 - bpos); - } + if (x) return i * 8 + __builtin_clz((uint32_t)x) - 24; } return 128; } +void ipv6_prefix_network(const uint8_t ip[16], uint8_t prefix_len, uint8_t out[16]) { + if (!out) return; + if (!ip) { + memset(out, 0, 16); + return; + } + + if (prefix_len > 128) prefix_len = 128; + ipv6_cpy(out, ip); + + if (prefix_len == 128) return; + + int fb = prefix_len / 8; + int rb = prefix_len % 8; + for (int i = fb + (rb > 0); i < 16; i++) out[i] = 0; + if (rb) out[fb] &= (uint8_t)(0xFF << (8 - rb)); +} + void ipv6_make_multicast(uint8_t scope, ipv6_mcast_kind_t kind, const uint8_t unicast[16], uint8_t out[16]) { memset(out, 0, 16); out[0] = 0xFF; @@ -56,10 +94,7 @@ void ipv6_make_multicast(uint8_t scope, ipv6_mcast_kind_t kind, const uint8_t un out[15] = 0x0c; break; case IPV6_MCAST_DHCPV6_SERVERS: - out[11] = 0x00; - out[12] = 0x01; - out[13] = 0x00; - out[14] = 0x00; + out[13] = 0x01; out[15] = 0x02; break; case IPV6_MCAST_MLDV2_ROUTERS: @@ -80,12 +115,6 @@ void ipv6_make_multicast(uint8_t scope, ipv6_mcast_kind_t kind, const uint8_t un } } -static int hexval(int c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} bool ipv6_parse(const char* s, uint8_t out[16]) { if (!s || !out) return false; @@ -103,7 +132,7 @@ bool ipv6_parse(const char* s, uint8_t out[16]) { if (wi >= 8) return false; int val = 0, cnt = 0, hv; - while ((hv = hexval(*p)) >= 0) { + while ((hv = hex_val(*p)) >= 0) { val = (val << 4) | hv; cnt++; if (cnt > 4) return false; @@ -237,4 +266,10 @@ void ipv6_make_lla_from_mac(uint8_t ifindex, uint8_t out[16]) { out[13] = mac[3]; out[14] = mac[4]; out[15] = mac[5]; + + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (l2 && l2->link_kind == NET_LINK_VLAN) { + out[14] ^= (uint8_t)(l2->vlan_id >> 8); + out[15] ^= (uint8_t)l2->vlan_id; + } } \ No newline at end of file diff --git a/kernel/networking/internet_layer/ipv6_utils.h b/kernel/networking/internet_layer/ipv6_utils.h index 69ee0724..eb50ba7f 100644 --- a/kernel/networking/internet_layer/ipv6_utils.h +++ b/kernel/networking/internet_layer/ipv6_utils.h @@ -1,5 +1,7 @@ #pragma once #include "types.h" +#include "net/interface_types.h" +#include "networking/interface_manager.h" #ifdef __cplusplus extern "C" { @@ -23,9 +25,14 @@ bool ipv6_is_linklocal(const uint8_t ip[16]); int ipv6_cmp(const uint8_t a[16], const uint8_t b[16]); void ipv6_cpy(uint8_t dst[16], const uint8_t src[16]); int ipv6_common_prefix_len(const uint8_t a[16], const uint8_t b[16]); +void ipv6_prefix_network(const uint8_t ip[16], uint8_t prefix_len, uint8_t out[16]); void ipv6_make_multicast(uint8_t scope, ipv6_mcast_kind_t kind, const uint8_t unicast[16], uint8_t out[16]); void ipv6_to_string(const uint8_t ip[16], char* buf, int buflen); bool ipv6_parse(const char* s, uint8_t out[16]); + +bool ipv6_l3_is_active(l3_ipv6_interface_t *v6); +bool ipv6_l3_is_ready(l3_ipv6_interface_t *v6); +bool ipv6_l3_is_tcp_usable(l3_ipv6_interface_t *v6); void ipv6_multicast_mac(const uint8_t ip[16], uint8_t mac[6]); void ipv6_make_lla_from_mac(uint8_t ifindex, uint8_t out[16]); @@ -50,6 +57,18 @@ static inline bool ipv6_is_linkscope_mcast(const uint8_t ip[16]){ return scope == 0x02; } +static inline bool ipv6_prefix_matches(const uint8_t a[16], const uint8_t b[16], uint8_t prefix_len) { + if (prefix_len > 128) return false; + + const uint8_t full_bytes = prefix_len / 8; + const uint8_t remaining_bits = prefix_len % 8; + for (uint8_t i = 0; i < full_bytes; i++) if (a[i] != b[i]) return false; + + if (remaining_bits == 0) return true; + const uint8_t mask = (uint8_t)(0xFF << (8 - remaining_bits)); + return (a[full_bytes] & mask) == (b[full_bytes] & mask); +} + #ifdef __cplusplus } #endif diff --git a/kernel/networking/internet_layer/mld.c b/kernel/networking/internet_layer/mld.c index 901a69b4..8e17a610 100644 --- a/kernel/networking/internet_layer/mld.c +++ b/kernel/networking/internet_layer/mld.c @@ -2,173 +2,163 @@ #include "kernel_processes/kprocess_loader.h" #include "math/rng.h" +#include "random/random.h" #include "networking/interface_manager.h" #include "net/checksums.h" #include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv6_utils.h" +#include "networking/internet_layer/icmpv6.h" #include "networking/link_layer/eth.h" +#include "networking/link_layer/nic_types.h" #include "std/memory.h" #include "syscalls/syscalls.h" -#define MLD_TYPE_QUERY 130 -#define MLD_TYPE_REPORT_V1 131 -#define MLD_TYPE_DONE_V1 132 -#define MLD_TYPE_REPORT_V2 143 - #define MLDV2_RTYPE_MODE_IS_INCLUDE 1 #define MLDV2_RTYPE_MODE_IS_EXCLUDE 2 #define MLDV2_RTYPE_CHANGE_TO_INCLUDE 3 #define MLDV2_RTYPE_CHANGE_TO_EXCLUDE 4 -#define MLDV2_RTYPE_ALLOW_NEW_SOURCES 5 -#define MLDV2_RTYPE_BLOCK_OLD_SOURCES 6 + +#define MLD_V2_UNSOLICITED_INTERVAL_MS 1000 +#define MLD_V1_UNSOLICITED_INTERVAL_MS 10000 +#define MLD_DEFAULT_QUERY_INTERVAL_MS 125000u +#define MLD_DEFAULT_ROBUSTNESS 2 + +#define MLD_REPORT_JOIN 1 +#define MLD_REPORT_LEAVE 2 +#define MLD_MAX_TRACK 64 +#define MLD_MAX_QUERY_SOURCES 32 typedef struct { uint8_t used; uint8_t ifindex; uint8_t group[16]; - uint32_t refresh_ms; uint32_t query_due_ms; + uint32_t change_due_ms; uint8_t query_pending; + uint8_t query_source_count; + uint8_t change_left; + uint8_t change_kind; + uint8_t last_reporter; + uint8_t query_sources[MLD_MAX_QUERY_SOURCES][16]; } mld_state_t; static volatile int mld_daemon_running = 0; static volatile int mld_daemon_pending = 0; -static uint32_t mld_uptime_ms = 0; static rng_t mld_rng; static int mld_rng_inited = 0; - -#define MLD_MAX_TRACK 64 -#define MLD_REFRESH_PERIOD_MS 60000 - static mld_state_t mld_states[MLD_MAX_TRACK]; +static uint32_t mld_v1_until_ms[MAX_L2_INTERFACES]; -static bool mld_pick_src_ip(uint8_t ifindex, uint8_t out_src_ip[16]); -static mld_state_t* mld_find_state(uint8_t ifindex, const uint8_t group[16]); +static void mld_daemon_kick(void); -static int mld_is_our_src(uint8_t ifindex, const uint8_t src_ip[16]) { - uint8_t my_ip[16]; - if(!mld_pick_src_ip(ifindex, my_ip)) return 0; - return (ipv6_cmp(my_ip, src_ip) == 0); +static bool mld_v1_mode(uint8_t ifindex, uint32_t now_ms) { + if (!ifindex || ifindex > MAX_L2_INTERFACES) return false; + uint32_t* until = &mld_v1_until_ms[ifindex - 1]; + if (!*until || (int32_t)(*until - now_ms) <= 0) *until = 0; + return *until != 0; } -static void mld_suppress_pending(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t group[16]) { - if(mld_is_our_src(ifindex, src_ip)) return; - - mld_state_t* s = mld_find_state(ifindex, group); - if(!s) return; - if(!s->query_pending) return; - - s->query_pending = 0; - s->query_due_ms = 0; +void mld_l2_deleted(uint8_t ifindex) { + if (!ifindex || ifindex > MAX_L2_INTERFACES) return; + mld_v1_until_ms[ifindex - 1] = 0; + for (int i = 0; i < MLD_MAX_TRACK; i++) if (mld_states[i].used && mld_states[i].ifindex == ifindex) memset(&mld_states[i], 0, sizeof(mld_states[i])); } -static bool mld_pick_src_ip(uint8_t ifindex, uint8_t out_src_ip[16]) { - l2_interface_t *l2; - l3_ipv6_interface_t *best; - l3_ipv6_interface_t *v6; - uint8_t i; - - l2 = l2_interface_find_by_index(ifindex); - if(!l2) return false; - - best = NULL; - for(i = 0; i < l2->ipv6_count; i++) { - v6 = l2->l3_v6[i]; - if(!v6) continue; - if(ipv6_is_unspecified(v6->ip)) continue; - if(ipv6_is_linklocal(v6->ip)) { - - - memcpy(out_src_ip, v6->ip, 16); - return true; - } - if(!best) best = v6; +static bool mld_dest_assigned(uint8_t ifindex, const uint8_t dst[16]) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) return false; + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!ipv6_l3_is_ready(v6)) continue; + if (ipv6_cmp(v6->ip, dst) == 0) return true; } - - if(!best) return false; - memcpy(out_src_ip, best->ip, 16); - return true; + for (int i = 0; i < (int)l2->ipv6_mcast_count; i++) if (ipv6_cmp(l2->ipv6_mcast[i], dst) == 0) return true; + return false; } -static bool mld_send_report(uint8_t ifindex, const uint8_t group[16], uint8_t record_type) { - uint8_t src_ip[16]; - uint8_t dst_ip[16]; +static bool mld_send_message(uint8_t ifindex, const uint8_t dst_ip[16], uint8_t* icmp, uint32_t icmp_len) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2) return false; + uint8_t src_ip[16] = {0}; uint8_t dst_mac[6]; - uint8_t icmp[28]; - - if(!mld_pick_src_ip(ifindex, src_ip)) return false; - - ipv6_make_multicast(2, IPV6_MCAST_MLDV2_ROUTERS, NULL, dst_ip); + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!ipv6_l3_is_ready(v6) || !ipv6_is_linklocal(v6->ip)) continue; + ipv6_cpy(src_ip, v6->ip); + break; + } ipv6_multicast_mac(dst_ip, dst_mac); - memset(icmp, 0, sizeof(icmp)); - icmp[0] = MLD_TYPE_REPORT_V2; - icmp[6] = 0; - icmp[7] = 1; - - icmp[8] = record_type; - icmp[9] = 0; - icmp[10] = 0; - icmp[11] = 0; - memcpy(icmp + 12, group, 16); - - uint16_t csum = checksum16_pipv6(src_ip, dst_ip, 58, icmp, sizeof(icmp)); - icmp[2] = (uint8_t)(csum >> 8); - icmp[3] = (uint8_t)(csum & 0xFF); - - uint8_t hbh[8]; - hbh[0] = 58; - hbh[1] = 0; - hbh[2] = 5; - hbh[3] = 2; - hbh[4] = 0; - hbh[5] = 0; - hbh[6] = 0; - hbh[7] = 0; - - uint32_t payload_len = (uint32_t)sizeof(hbh) + (uint32_t)sizeof(icmp); + wr_be16(icmp+2, 0); + wr_be16(icmp + 2, checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, icmp, icmp_len)); + const uint8_t hbh[8] = {PROTO_ICMPV6,0,5,2,0,0,1,0}; + + uint32_t payload_len = (uint32_t)sizeof(hbh) + icmp_len; uint32_t total = (uint32_t)sizeof(ipv6_hdr_t) + payload_len; uint32_t headroom = (((uint32_t)sizeof(eth_hdr_t) + 7u) & ~7u); netpkt_t* pkt = netpkt_alloc(total, headroom, 0); if(!pkt) return false; - ipv6_hdr_t* ip6 = (ipv6_hdr_t*)netpkt_put(pkt, (uint32_t)sizeof(ipv6_hdr_t)); - if(!ip6) { + void* buf = netpkt_put(pkt, total); + if(!buf) { netpkt_unref(pkt); return false; } - ((uint8_t*)&ip6->ver_tc_fl)[0] = 0x60; - ((uint8_t*)&ip6->ver_tc_fl)[1] = 0x00; - ((uint8_t*)&ip6->ver_tc_fl)[2] = 0x00; - ((uint8_t*)&ip6->ver_tc_fl)[3] = 0x00; - - ip6->payload_len = bswap16((uint16_t)payload_len); - ip6->next_header = 0; - ip6->hop_limit = 1; - memcpy(ip6->src, src_ip, 16); - memcpy(ip6->dst, dst_ip, 16); + ipv6_hdr_t ip6; + ip6.ver_tc_fl = bswap32((uint32_t)(6 << 28)); - uint8_t* hb = (uint8_t*)netpkt_put(pkt, (uint32_t)sizeof(hbh)); - if(!hb) { - netpkt_unref(pkt); - return false; - } - memcpy(hb, hbh, sizeof(hbh)); + ip6.payload_len = bswap16((uint16_t)payload_len); + ip6.next_header = IPV6_NH_HOP_BY_HOP; + ip6.hop_limit = 1; + ipv6_cpy(ip6.src, src_ip); + ipv6_cpy(ip6.dst, dst_ip); + memcpy(buf, &ip6, sizeof(ip6)); - uint8_t* icmp_p = (uint8_t*)netpkt_put(pkt, (uint32_t)sizeof(icmp)); - if(!icmp_p) { - netpkt_unref(pkt); - return false; - } - memcpy(icmp_p, icmp, sizeof(icmp)); + memcpy(buf + sizeof(ip6), hbh, sizeof(hbh)); + memcpy(buf + sizeof(ip6) + sizeof(hbh), icmp, icmp_len); return eth_send_frame_on(ifindex, ETHERTYPE_IPV6, dst_mac, pkt); } +static bool mld_send_v1(uint8_t ifindex, const uint8_t group[16], bool done) { + uint8_t msg[24]; + uint8_t dst[16]; + memset(msg, 0, sizeof(msg)); + msg[0] = done ? ICMPV6_MLD_DONE : ICMPV6_MLD_REPORT; + ipv6_cpy(msg + 8, group); + + if (done) ipv6_make_multicast(2, IPV6_MCAST_ALL_ROUTERS, NULL, dst); + else ipv6_cpy(dst, group); + return mld_send_message(ifindex, dst, msg, sizeof(msg)); +} + +static bool mld_send_v2(uint8_t ifindex, const uint8_t group[16], uint8_t record_type, const uint8_t sources[][16], uint8_t source_count) { + uint8_t msg[28 + MLD_MAX_QUERY_SOURCES*16]; + uint8_t dst[16]; + uint32_t msg_len = 28 + (uint32_t)source_count * 16; + memset(msg, 0, msg_len); + msg[0] = ICMPV6_MLDV2_REPORT; + wr_be16(msg + 6, 1); + msg[8] = record_type; + wr_be16(msg + 10, source_count); + ipv6_cpy(msg + 12, group); + for (uint8_t i = 0; i < source_count; i++) ipv6_cpy(msg + 28 + (uint32_t)i * 16, sources[i]); + ipv6_make_multicast(2, IPV6_MCAST_MLDV2_ROUTERS, NULL, dst); + return mld_send_message(ifindex, dst, msg, msg_len); +} + +static bool mld_send_state_change(uint8_t ifindex, const uint8_t group[16], uint8_t kind) { + if (mld_v1_mode(ifindex, get_time())) { + if (kind == MLD_REPORT_JOIN) return mld_send_v1(ifindex, group, false); + return mld_send_v1(ifindex, group, true); + } + return mld_send_v2(ifindex, group, kind == MLD_REPORT_JOIN ? MLDV2_RTYPE_CHANGE_TO_EXCLUDE : MLDV2_RTYPE_CHANGE_TO_INCLUDE, NULL, 0); +} + static mld_state_t* mld_find_state(uint8_t ifindex, const uint8_t group[16]) { - for(int i = 0; i < MLD_MAX_TRACK; i++) { + for(int i = 0; i < (int)N_ARR(mld_states); i++) { mld_state_t* s = &mld_states[i]; if(!s->used) continue; if(s->ifindex != ifindex) continue; @@ -181,42 +171,27 @@ static mld_state_t* mld_get_state(uint8_t ifindex, const uint8_t group[16]) { mld_state_t* s = mld_find_state(ifindex, group); if(s) return s; - for(int i = 0; i < MLD_MAX_TRACK; i++) { - if(!mld_states[i].used) { - mld_states[i].used = 1; - mld_states[i].ifindex = ifindex; - ipv6_cpy(mld_states[i].group, group); - mld_states[i].refresh_ms = 0; - mld_states[i].query_due_ms = 0; - mld_states[i].query_pending = 0; - return &mld_states[i]; - } + for(int i = 0; i < (int)N_ARR(mld_states); i++) { + s = &mld_states[i]; + if (s->used) continue; + memset(s, 0, sizeof(*s)); + s->used = 1; + s->ifindex = ifindex; + ipv6_cpy(s->group, group); + return s; } - return NULL; } static int mld_has_pending_timers(void) { - for(int i = 0; i < MLD_MAX_TRACK; i++) { + for(int i = 0; i < (int)N_ARR(mld_states); i++) { mld_state_t* s =&mld_states[i]; if(!s->used) continue; - if(s->query_pending) return 1; - if(s->refresh_ms < MLD_REFRESH_PERIOD_MS) return 1; + if (s->query_pending || s->change_left) return 1; } return 0; } -static int mld_is_still_joined(uint8_t ifindex, const uint8_t group[16]) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if(!l2) return 0; - - for(int i = 0; i < (int)l2->ipv6_mcast_count; i++) { - if(ipv6_cmp(l2->ipv6_mcast[i], group) == 0) return 1; - } - - return 0; -} - static int mld_daemon_entry(int argc, char* argv[]) { (void)argc; (void)argv; @@ -224,43 +199,63 @@ static int mld_daemon_entry(int argc, char* argv[]) { mld_daemon_pending = 0; mld_daemon_running = 1; - if(! mld_rng_inited) { - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&mld_rng, virt_timer); + if (!mld_rng_inited) { + rng_init_random(&mld_rng); mld_rng_inited = 1; } const uint32_t tick_ms = 100; while(mld_has_pending_timers()) { - mld_uptime_ms += tick_ms; + uint32_t now_ms = get_time(); - for(int i = 0; i < MLD_MAX_TRACK; i++) { + for(int i = 0; i < (int)N_ARR(mld_states); i++) { mld_state_t* s = &mld_states[i]; if(!s->used) continue; - if(!mld_is_still_joined(s->ifindex, s->group)) { - s->used = 0; - continue; + bool joined = false; + l2_interface_t* l2 = l2_interface_find_by_index(s->ifindex); + if (l2) { + for (int j = 0; j < (int)l2->ipv6_mcast_count; j++) { + if (ipv6_cmp(l2->ipv6_mcast[j], s->group) != 0) continue; + joined = true; + break; + } } - - s->refresh_ms += tick_ms; - if(s->refresh_ms >= MLD_REFRESH_PERIOD_MS) { - s->refresh_ms = 0; - (void)mld_send_report(s->ifindex, s->group, MLDV2_RTYPE_MODE_IS_EXCLUDE); + if (!joined) { + s->query_pending = 0; + if (s->change_kind == MLD_REPORT_JOIN) s->change_left = 0; } - if(s->query_pending && mld_uptime_ms >= s->query_due_ms) { + if (s->query_pending && (int32_t)(now_ms - s->query_due_ms) >= 0) { s->query_pending = 0; - (void)mld_send_report(s->ifindex, s->group, MLDV2_RTYPE_MODE_IS_EXCLUDE); + bool v1 = mld_v1_mode(s->ifindex, now_ms); + bool sent; + if (v1) sent = mld_send_v1(s->ifindex, s->group, false); + else if (s->query_source_count) sent = mld_send_v2(s->ifindex, s->group, MLDV2_RTYPE_MODE_IS_INCLUDE, s->query_sources, s->query_source_count); + else sent = mld_send_v2(s->ifindex, s->group, MLDV2_RTYPE_MODE_IS_EXCLUDE, NULL, 0); + s->query_source_count = 0; + if (sent && v1) s->last_reporter = 1; + } + + if (s->change_left && (int32_t)(now_ms - s->change_due_ms) >= 0) { + bool sent = mld_send_state_change(s->ifindex, s->group, s->change_kind); + if (sent && s->change_kind == MLD_REPORT_JOIN && mld_v1_mode(s->ifindex, now_ms)) s->last_reporter = 1; + s->change_left--; + if (s->change_left) { + uint32_t interval = mld_v1_mode(s->ifindex, get_time()) ? MLD_V1_UNSOLICITED_INTERVAL_MS : MLD_V2_UNSOLICITED_INTERVAL_MS; + s->change_due_ms = now_ms + rng_between32(&mld_rng, 1, interval + 1); + } } + + if (!joined && !s->query_pending && !s->change_left) memset(s, 0, sizeof(*s)); } msleep(tick_ms); } mld_daemon_running = 0; + mld_daemon_kick(); return 0; } @@ -272,45 +267,112 @@ static void mld_daemon_kick(void) { } bool mld_send_join(uint8_t ifindex, const uint8_t group[16]) { - if(!ipv6_is_multicast(group)) return false; + if (!group || !ipv6_is_multicast(group)) return false; + if ((group[1] & 0x0F) < 2) return true; + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + if (ipv6_cmp(group, all_nodes) == 0) return true; + + if (!mld_rng_inited) { + rng_init_random(&mld_rng); + mld_rng_inited = 1; + } mld_state_t* s = mld_get_state(ifindex, group); - if(s) s->refresh_ms = 0; + if (!s) return false; + + bool ok = mld_send_state_change(ifindex, group, MLD_REPORT_JOIN); + if (ok && mld_v1_mode(ifindex, get_time())) s->last_reporter = 1; + s->change_kind = MLD_REPORT_JOIN; + s->change_left = MLD_DEFAULT_ROBUSTNESS - 1; + uint32_t interval = mld_v1_mode(ifindex, get_time()) ? MLD_V1_UNSOLICITED_INTERVAL_MS : MLD_V2_UNSOLICITED_INTERVAL_MS; + s->change_due_ms = get_time() + rng_between32(&mld_rng, 1, interval + 1); mld_daemon_kick(); - return mld_send_report(ifindex, group, MLDV2_RTYPE_MODE_IS_EXCLUDE); + return ok; } bool mld_send_leave(uint8_t ifindex, const uint8_t group[16]) { - if(!ipv6_is_multicast(group)) return false; + if (!group || !ipv6_is_multicast(group)) return false; + if ((group[1] & 0x0F) < 2) return true; + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + if (ipv6_cmp(group, all_nodes) == 0) return true; mld_state_t* s = mld_find_state(ifindex, group); - if(s) s->used = 0; + bool v1 = mld_v1_mode(ifindex, get_time()); + if (v1) { + bool send_done = !s || s->last_reporter; + if (s) memset(s, 0, sizeof(*s)); + if (!send_done) return true; + return mld_send_state_change(ifindex, group, MLD_REPORT_LEAVE); + } + + if (!s) s = mld_get_state(ifindex, group); + if (!s) return false; + + bool ok = mld_send_state_change(ifindex, group, MLD_REPORT_LEAVE); + s->query_pending = 0; + s->change_kind = MLD_REPORT_LEAVE; + s->change_left = MLD_DEFAULT_ROBUSTNESS - 1; + s->change_due_ms = get_time() + rng_between32(&mld_rng, 1, MLD_V2_UNSOLICITED_INTERVAL_MS + 1); mld_daemon_kick(); - return mld_send_report(ifindex, group, MLDV2_RTYPE_MODE_IS_INCLUDE); + return ok; } -static void schedule_report(uint8_t ifindex, const uint8_t group[16], uint16_t max_resp_ms) { - if(!ipv6_is_multicast(group)) return; +void mld_resend_memberships(uint8_t ifindex) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || l2->link_kind == NET_LINK_LOOPBACK) return; + + for (int i = 0; i < (int)l2->ipv6_mcast_count; i++) mld_send_join(ifindex, l2->ipv6_mcast[i]); +} - if(!mld_rng_inited) { - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&mld_rng, virt_timer); +static void mld_schedule_report(uint8_t ifindex, const uint8_t group[16], uint32_t max_ms, const uint8_t sources[][16], uint16_t source_count, bool v1_query) { + if ((group[1] & 0x0F) < 2) return; + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + if (ipv6_cmp(group, all_nodes) == 0) return; + + if (!mld_rng_inited) { + rng_init_random(&mld_rng); mld_rng_inited = 1; } mld_state_t* s = mld_get_state(ifindex, group); if(!s) return; - uint32_t max_ms = (uint32_t)max_resp_ms; - if(max_ms == 0) max_ms = 100; + if (!s->query_pending) { + s->query_source_count = 0; + for (uint16_t i = 0; i < source_count; i++) { + ipv6_cpy(s->query_sources[s->query_source_count], sources[i]); + s->query_source_count++; + } + } else if (!source_count || !s->query_source_count) s->query_source_count = 0; + else { + uint8_t merged[MLD_MAX_QUERY_SOURCES][16]; + uint8_t merged_count = s->query_source_count; + for (uint8_t i = 0; i < merged_count; i++) ipv6_cpy(merged[i], s->query_sources[i]); + for (uint16_t i = 0; i < source_count; i++) { + bool exists = false; + for (uint8_t j = 0; j < merged_count; j++) { + if (ipv6_cmp(merged[j], sources[i]) != 0) continue; + exists = true; + break; + } + if (exists) continue; + if (merged_count >= N_ARR(merged)) return; + ipv6_cpy(merged[merged_count], sources[i]); + merged_count++; + } + for (uint8_t i = 0; i < merged_count; i++) ipv6_cpy(s->query_sources[i], merged[i]); + s->query_source_count = merged_count; + } - uint32_t delay = rng_between32(&mld_rng, 0, max_ms); - uint32_t due = mld_uptime_ms + delay; + uint32_t delay = max_ms ? rng_between32(&mld_rng, v1_query ? 0 : 1, max_ms +1) : 0; + uint32_t due = get_time() + delay; - if(!s->query_pending || due < s->query_due_ms) { + if (!s->query_pending || (int32_t)(due - s->query_due_ms) < 0) { s->query_pending = 1; s->query_due_ms = due; } @@ -318,79 +380,99 @@ static void schedule_report(uint8_t ifindex, const uint8_t group[16], uint16_t m mld_daemon_kick(); } -void mld_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const void* l4, uint32_t l4_len) { - if(!ifindex || !src_ip || !dst_ip || !l4) return; - if(l4_len < 8) return; - - const uint8_t* p = (const uint8_t*)l4; - uint8_t type = p[0]; - - if(type == MLD_TYPE_REPORT_V2) { - if(l4_len < 8) return; - uint16_t nrec = (uint16_t)((uint16_t)p[6] << 8) | (uint16_t)p[7]; - uint32_t off = 8; - - for(uint16_t i = 0; i < nrec; i++) { - if(off + 20u > l4_len) break; - - uint8_t rtype = p[off + 0]; - uint8_t aux_words = p[off + 1]; - uint16_t nsrc = (uint16_t)((uint16_t)p[off + 2] << 8) | (uint16_t)p[off + 3]; - const uint8_t* group = p + off + 4; - off += 20; - - uint32_t src_bytes = (uint32_t)nsrc * 16u; - if(off + src_bytes > l4_len) break; - off += src_bytes; - - uint32_t aux_bytes = (uint32_t)aux_words * 4u; - if(off + aux_bytes > l4_len) break; - off += aux_bytes; - - int interest = 0; - if(rtype == MLDV2_RTYPE_MODE_IS_EXCLUDE || rtype == MLDV2_RTYPE_CHANGE_TO_EXCLUDE || rtype == MLDV2_RTYPE_ALLOW_NEW_SOURCES) { - interest = 1; - } else if((rtype == MLDV2_RTYPE_MODE_IS_INCLUDE || rtype == MLDV2_RTYPE_CHANGE_TO_INCLUDE) && nsrc) { - interest = 1; - } - if(!interest) continue; - - mld_suppress_pending(ifindex, src_ip, group); +void mld_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, bool router_alert, netpkt_t* pkt) { + if(!ifindex || ifindex > MAX_L2_INTERFACES || !src_ip || !dst_ip || !pkt) return; + uint32_t l4_len = netpkt_len(pkt); + if (l4_len < 4 || hop_limit != 1 || !router_alert || !ipv6_is_linklocal(src_ip)) return; + + uint8_t type = 0; + if (!netpkt_copyout(pkt, 0, &type, 1)) return; + + if (type == ICMPV6_MLD_REPORT) { + if (l4_len < 24) return; + uint8_t msg[24]; + if (!netpkt_copyout(pkt, 0, msg, sizeof(msg))) return; + const uint8_t* group = msg + 8; + if (!mld_v1_mode(ifindex, get_time()) || !ipv6_is_multicast(group)) return; + if (ipv6_cmp(dst_ip, group) != 0 && !mld_dest_assigned(ifindex, dst_ip)) return; + mld_state_t* s = mld_find_state(ifindex, group); + if (s) { + s->query_pending = 0; + s->change_left = 0; + s->last_reporter = 0; } return; } - if(type == MLD_TYPE_REPORT_V1) { - if(l4_len < 24) return; - uint8_t group[16]; - memcpy(group, p + 8, 16); - if(ipv6_is_multicast(group)) mld_suppress_pending(ifindex, src_ip, group); - return; - } - - if(type != MLD_TYPE_QUERY) return; - if(l4_len < 24) return; + if (type != ICMPV6_MLD_QUERY) return; + if (l4_len != 24 && l4_len < 28) return; - uint16_t max_resp_ms = (uint16_t)((uint16_t)p[4] << 8) | (uint16_t)p[5]; + uint8_t query[28]; + if (!netpkt_copyout(pkt, 0, query, l4_len == 24 ? 24 : sizeof(query))) return; + uint16_t max_resp_code = rd_be16(query + 4); uint8_t group[16]; - memcpy(group, p + 8, 16); + ipv6_cpy(group, query + 8); + uint16_t source_count = 0; + uint8_t sources[MLD_MAX_QUERY_SOURCES][16]; + uint32_t max_ms = 0; + bool v1 = l4_len == 24; + + if (v1) max_ms = max_resp_code; + else { + source_count = rd_be16(query+26); + uint32_t source_bytes = (uint32_t)source_count * 16; + uint32_t expected = (uint32_t)sizeof(query) + source_bytes; + if (expected > l4_len || source_count > N_ARR(sources)) return; + for (uint16_t i = 0; i < source_count; i++) { + if (!netpkt_copyout(pkt, sizeof(query) + (uint32_t)i * 16, sources[i], 16)) return; + if (ipv6_is_unspecified(sources[i]) || ipv6_is_multicast(sources[i])) return; + } + if (max_resp_code < 0x8000) max_ms = max_resp_code; + else { + uint32_t exp = ((uint32_t)max_resp_code >> 12) & 0x07; + uint32_t mant = (uint32_t)max_resp_code & 0x0FFF; + max_ms = (mant | 0x1000) << (exp + 3); + } + } + + bool general = ipv6_is_unspecified(group); + if (!general && !ipv6_is_multicast(group)) return; + if (general) { + if (source_count) return; + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, 0, all_nodes); + if (ipv6_cmp(dst_ip, all_nodes) != 0 && !mld_dest_assigned(ifindex, dst_ip)) return; + } else if (ipv6_cmp(dst_ip, group) != 0 && !mld_dest_assigned(ifindex, dst_ip)) return; + + if (v1 && general) { + uint32_t now_ms = get_time(); + bool was_v1 = mld_v1_mode(ifindex, now_ms); + uint64_t interval = (uint64_t)MLD_DEFAULT_ROBUSTNESS*MLD_DEFAULT_QUERY_INTERVAL_MS + max_ms; + if (interval > 0x7FFFFFFF) interval = 0x7FFFFFFF; + mld_v1_until_ms[ifindex - 1] = now_ms + (uint32_t)interval; + if (!was_v1) { + for (int i = 0; i < (int)N_ARR(mld_states); i++) { + mld_state_t* pending = &mld_states[i]; + if (!pending->used || pending->ifindex != ifindex) continue; + pending->query_pending = 0; + pending->query_source_count = 0; + pending->change_left = 0; + } + } + } l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if(!l2) return; - if(ipv6_is_unspecified(group)) { - for(int i = 0; i < (int)l2->ipv6_mcast_count; i++) { - const uint8_t* g = l2->ipv6_mcast[i]; - if(ipv6_is_multicast(g)) schedule_report(ifindex, g, max_resp_ms); - } + if (general) { + for(int i = 0; i < (int)l2->ipv6_mcast_count; i++) mld_schedule_report(ifindex, l2->ipv6_mcast[i], max_ms, 0, 0, v1); return; } for(int i = 0; i < (int)l2->ipv6_mcast_count; i++) { - if(ipv6_cmp(l2->ipv6_mcast[i], group) == 0) { - schedule_report(ifindex, group, max_resp_ms); - return; - } + if(ipv6_cmp(l2->ipv6_mcast[i], group) != 0) continue; + mld_schedule_report(ifindex, group, max_ms, source_count ? sources : NULL, source_count, v1); + return; } } diff --git a/kernel/networking/internet_layer/mld.h b/kernel/networking/internet_layer/mld.h index 35269e92..392761b3 100644 --- a/kernel/networking/internet_layer/mld.h +++ b/kernel/networking/internet_layer/mld.h @@ -9,8 +9,10 @@ extern "C" { #endif bool mld_send_join(uint8_t ifindex, const uint8_t group[16]); +void mld_l2_deleted(uint8_t ifindex); bool mld_send_leave(uint8_t ifindex, const uint8_t group[16]); -void mld_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const void* l4, uint32_t l4_len); +void mld_resend_memberships(uint8_t ifindex); +void mld_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], uint8_t hop_limit, bool router_alert, netpkt_t* pkt); #ifdef __cplusplus } diff --git a/kernel/networking/internet_layer/pmtu.c b/kernel/networking/internet_layer/pmtu.c new file mode 100644 index 00000000..812955ec --- /dev/null +++ b/kernel/networking/internet_layer/pmtu.c @@ -0,0 +1,129 @@ +#include "pmtu.h" +#include "std/memory.h" +#include "syscalls/syscalls.h" +#include "exceptions/irq.h" + +#define PMTU_CACHE_MAX_AGE_MS 600000u +#define PMTU_CACHE_MAX_ENTRIES 128u + +typedef struct { + uint8_t dst[16]; + uint32_t l3_epoch; + uint32_t timestamp_ms; + l3_id_t l3_id; + uint16_t mtu; + ip_version_t ver; + bool used; +} pmtu_entry_t; + +static pmtu_entry_t g_pmtu[PMTU_CACHE_MAX_ENTRIES]; +static uint16_t g_pmtu_used; +static uint16_t g_pmtu_end; +static uint16_t g_pmtu_recent = UINT16_MAX; + +uint16_t pmtu_get(l3_id_t l3_id, uint32_t l3_epoch, ip_version_t ver, const void* dst) { + if (!l3_id || !l3_epoch || !dst || (ver != IP_VER4 && ver != IP_VER6)) return 0; + + uint32_t dst_len = ver == IP_VER6 ? 16 : 4; + irq_flags_t irq = irq_save_disable(); + + if (!g_pmtu_used) { + irq_restore(irq); + return 0; + } + + uint32_t now = (uint32_t)get_time(); + if (g_pmtu_recent < PMTU_CACHE_MAX_ENTRIES) { + pmtu_entry_t* e = &g_pmtu[g_pmtu_recent]; + if (e->used && e->l3_id == l3_id && e->l3_epoch == l3_epoch && e->ver == ver && + now - e->timestamp_ms < PMTU_CACHE_MAX_AGE_MS && !memcmp(e->dst, dst, dst_len)) { + uint16_t mtu = e->mtu; + irq_restore(irq); + return mtu; + } + } + + uint32_t limit = g_pmtu_end; + for (uint32_t i = 0; i < limit; i++) { + pmtu_entry_t* e = &g_pmtu[i]; + if (!e->used) continue; + if (now - e->timestamp_ms >= PMTU_CACHE_MAX_AGE_MS || (e->l3_id == l3_id && e->l3_epoch != l3_epoch)) { + memset(e, 0, sizeof(*e)); + g_pmtu_used--; + if (i+1 == g_pmtu_end) { + while (g_pmtu_end && !g_pmtu[g_pmtu_end - 1].used) g_pmtu_end--; + } + continue; + } + if (e->l3_id != l3_id || e->ver != ver || memcmp(e->dst, dst, dst_len) != 0) continue; + + uint16_t mtu = e->mtu; + g_pmtu_recent = (uint16_t)i; + irq_restore(irq); + return mtu; + } + + irq_restore(irq); + return 0; +} + +uint16_t pmtu_note(l3_id_t l3_id, uint32_t l3_epoch, ip_version_t ver, const void* dst, uint16_t mtu) { + if (!l3_id || !l3_epoch || !dst || !mtu || (ver != IP_VER4 && ver != IP_VER6)) return 0; + + uint32_t dst_len = ver == IP_VER6 ? 16 : 4; + uint32_t now = (uint32_t)get_time(); + int free_slot = -1; + int oldest = -1; + uint32_t oldest_age = 0; + irq_flags_t irq = irq_save_disable(); + + for (uint32_t i = 0; i < PMTU_CACHE_MAX_ENTRIES; i++) { + pmtu_entry_t* e = &g_pmtu[i]; + if (!e->used) { + if (free_slot < 0) free_slot = (int)i; + continue; + } + if (now - e->timestamp_ms >= PMTU_CACHE_MAX_AGE_MS || (e->l3_id == l3_id && e->l3_epoch != l3_epoch)) { + memset(e, 0, sizeof(*e)); + g_pmtu_used--; + if (free_slot < 0) free_slot = (int)i; + continue; + } + if (e->l3_id == l3_id && e->ver == ver && memcmp(e->dst, dst, dst_len) == 0) { + if (mtu < e->mtu) e->mtu = mtu; + e->timestamp_ms = now; + mtu = e->mtu; + g_pmtu_recent = (uint16_t)i; + irq_restore(irq); + return mtu; + } + + uint32_t age = now - e->timestamp_ms; + if (oldest < 0 || age > oldest_age) { + oldest = (int)i; + oldest_age = age; + } + } + + while (g_pmtu_end && !g_pmtu[g_pmtu_end - 1].used) g_pmtu_end--; + int slot = free_slot >= 0 ? free_slot : oldest; + if (slot < 0) { + irq_restore(irq); + return 0; + } + + pmtu_entry_t* e = &g_pmtu[slot]; + if (!e->used) g_pmtu_used++; + if ((uint32_t)slot >= g_pmtu_end) g_pmtu_end = (uint16_t)(slot + 1); + memset(e, 0, sizeof(*e)); + e->used = true; + e->ver = ver; + e->l3_id = l3_id; + e->l3_epoch = l3_epoch; + e->mtu = mtu; + e->timestamp_ms = now; + memcpy(e->dst, dst, dst_len); + g_pmtu_recent = (uint16_t)slot; + irq_restore(irq); + return mtu; +} diff --git a/kernel/networking/internet_layer/pmtu.h b/kernel/networking/internet_layer/pmtu.h new file mode 100644 index 00000000..962163ae --- /dev/null +++ b/kernel/networking/internet_layer/pmtu.h @@ -0,0 +1,14 @@ +#pragma once +#include "types.h" +#include "net/network_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +uint16_t pmtu_get(l3_id_t l3_id, uint32_t l3_epoch, ip_version_t ver, const void* dst); +uint16_t pmtu_note(l3_id_t l3_id, uint32_t l3_epoch, ip_version_t ver, const void* dst, uint16_t mtu); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/link_layer/arp.c b/kernel/networking/link_layer/arp.c index a1534b3c..6f175f48 100644 --- a/kernel/networking/link_layer/arp.c +++ b/kernel/networking/link_layer/arp.c @@ -1,196 +1,433 @@ #include "arp.h" #include "eth.h" +#include "link_utils.h" #include "std/memory.h" -#include "std/string.h" #include "networking/network.h" +#include "networking/interface_manager.h" #include "process/scheduler.h" -#include "console/kio.h" +#include "kernel_processes/kprocess_loader.h" +#include "exceptions/irq.h" #include "networking/internet_layer/ipv4.h" #include "syscalls/syscalls.h" #include "networking/internet_layer/ipv4_utils.h" +#define ARP_REACHABLE_MS 180000 +#define ARP_STALE_MS 60000 +#define ARP_RETRANS_MS 1000 +#define ARP_MAX_PROBES 3 +#define ARP_DAD_PROBES 3 +#define ARP_DAD_WAIT_MS 150 +#define ARP_ANNOUNCE_INTERVAL_MS 2000u +#define ARP_DEFEND_INTERVAL_MS 10000u -typedef struct arp_table { +typedef struct { + uint32_t ip; + uint32_t last_conflict_ms; + uint8_t used; +} arp_defense_t; + +struct arp_table { arp_entry_t entries[ARP_TABLE_MAX]; - uint8_t init; -} arp_table_t; + uint32_t dad_ip; + uint32_t announce_ip; + uint32_t announce_timer_ms; + uint8_t dad_conflict; + uint8_t announce_left; + arp_defense_t defense[MAX_IPV4_PER_INTERFACE]; +}; -static uint16_t g_arp_pid = 0xFFFF; +static volatile uint8_t g_arp_daemon_running; +static volatile uint8_t g_arp_daemon_pending; -static inline arp_table_t* l2_arp(uint8_t ifindex){ - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - return l2 ? (arp_table_t*)l2->arp_table : 0; +static void arp_daemon_kick(void); + +static void arp_entry_clear(arp_entry_t* e) { + if (!e) return; + if (e->pending) { + for (int i = 0; i < e->pending_len; i++) { + if (e->pending[i]) netpkt_unref(e->pending[i]); + } + release(e->pending); + } + memset(e, 0, sizeof(*e)); + e->state = ARP_STATE_UNUSED; } arp_table_t* arp_table_create(void){ - arp_table_t* t = (arp_table_t*)malloc(sizeof(arp_table_t)); + arp_table_t* t = (arp_table_t*)zalloc(sizeof(arp_table_t)); if (!t) return 0; - memset(t, 0, sizeof(*t)); - t->init = 1; arp_table_init_static_defaults(t); return t; } void arp_table_destroy(arp_table_t* t){ - if (t) free_sized(t, sizeof(*t)); + if (!t) return; + for (int i = 0; i < (int)N_ARR(t->entries); i++) arp_entry_clear(&t->entries[i]); + release(t); } void arp_table_init_static_defaults(arp_table_t* t){ if (!t) return; - t->entries[0].ip = 0xFFFFFFFFu; - t->entries[0].mac[0] = 0xFF; - t->entries[0].mac[1] = 0xFF; - t->entries[0].mac[2] = 0xFF; - t->entries[0].mac[3] = 0xFF; - t->entries[0].mac[4] = 0xFF; - t->entries[0].mac[5] = 0xFF; + t->entries[0].ip = IPV4_LIMITED_BROADCAST; + mac_set_broadcast(t->entries[0].mac); t->entries[0].ttl_ms = 0; + t->entries[0].state = ARP_STATE_REACHABLE; t->entries[0].static_entry = 1; } static int arp_find_slot(arp_table_t* t, uint32_t ip){ if (!t) return -1; - for (int i=0;ientries[i].ip == ip) return i; + for (int i = 0; i < (int)N_ARR(t->entries); i++) { + if (t->entries[i].state == ARP_STATE_UNUSED) continue; + if (t->entries[i].ip == ip) return i; + } return -1; } -static int arp_find_free(arp_table_t* t){ +static int arp_find_replacement(arp_table_t* t) { + int best = -1; + uint32_t best_ttl = UINT32_MAX; if (!t) return -1; - for (int i=0;ientries[i].ip == 0) return i; - return -1; + + for (int i = 0; i < (int)N_ARR(t->entries); i++) { + arp_entry_t* e = &t->entries[i]; + if (e->static_entry) continue; + if (e->pending_len) continue; + if (e->state == ARP_STATE_UNUSED) return i; + if (e->ttl_ms < best_ttl) { + best_ttl = e->ttl_ms; + best = i; + } + } + + return best; } -void arp_table_put_for_l2(uint8_t ifindex, uint32_t ip, const uint8_t mac[6], uint32_t ttl_ms, bool is_static){ - arp_table_t* t = l2_arp(ifindex); - if (!t) return; +void arp_table_put_for_l2(uint8_t ifindex, uint32_t ip, const uint8_t mac[MAC_ADDR_LEN], uint32_t ttl_ms, bool is_static){ + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !ip || !mac) return; int idx = arp_find_slot(t, ip); - if (idx < 0) idx = arp_find_free(t); - if (idx < 0) idx = 0; - t->entries[idx].ip = ip; - memcpy(t->entries[idx].mac, mac, 6); - t->entries[idx].ttl_ms = is_static ? 0 : ttl_ms; - t->entries[idx].static_entry = is_static ? 1 : 0; -} - -bool arp_table_get_for_l2(uint8_t ifindex, uint32_t ip, uint8_t mac_out[6]){ - arp_table_t* t = l2_arp(ifindex); - if (!t) return false; - for (int i=0;ientries[i].ip == ip){ - memcpy(mac_out, t->entries[i].mac, 6); - return true; + if (idx < 0) idx = arp_find_replacement(t); + if (idx < 0) return; + + arp_entry_t* e = &t->entries[idx]; + if (e->state != ARP_STATE_UNUSED && e->ip == ip && e->static_entry && !is_static) return; + if (e->state != ARP_STATE_UNUSED && e->ip != ip) arp_entry_clear(e); + + e->ip = ip; + mac_copy(e->mac, mac); + e->ttl_ms = is_static ? 0 : (ttl_ms ? ttl_ms : ARP_REACHABLE_MS); + e->timer_ms = 0; + e->state = ARP_STATE_REACHABLE; + e->probes_sent = 0; + e->static_entry = is_static ? 1 : 0; + + if (e->pending) { + for (int i = 0; i < e->pending_len; i++) { + netpkt_t* pkt = e->pending[i]; + e->pending[i] = 0; + if (pkt) (void)eth_send_frame_on(ifindex, ETHERTYPE_IPV4, e->mac, pkt); } + release(e->pending); + e->pending = 0; + e->pending_len = 0; + e->pending_bytes = 0; } - return false; + if (!is_static) arp_daemon_kick(); } -void arp_table_tick_for_l2(uint8_t ifindex, uint32_t ms){ - arp_table_t* t = l2_arp(ifindex); - if (!t) return; - for (int i=0;ientries[i].ip == 0 || t->entries[i].static_entry) continue; - if (t->entries[i].ttl_ms <= ms){ - memset(&t->entries[i], 0, sizeof(arp_entry_t)); - } else { - t->entries[i].ttl_ms -= ms; +static void arp_table_learn_for_l2(uint8_t ifindex, uint32_t ip, const uint8_t mac[MAC_ADDR_LEN], bool confirmed, bool allow_create) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !ip || !mac) return; + + int idx = arp_find_slot(t, ip); + if (idx < 0 && !allow_create) return; + if (idx < 0) idx = arp_find_replacement(t); + if (idx < 0) return; + + arp_entry_t* e = &t->entries[idx]; + if (e->state != ARP_STATE_UNUSED && e->ip == ip && e->static_entry) return; + if (e->state != ARP_STATE_UNUSED && e->ip != ip) arp_entry_clear(e); + + e->ip = ip; + mac_copy(e->mac, mac); + e->timer_ms = 0; + e->state = confirmed ? ARP_STATE_REACHABLE : ARP_STATE_STALE; + e->ttl_ms = confirmed ? ARP_REACHABLE_MS : ARP_STALE_MS; + e->probes_sent = 0; + e->static_entry = 0; + + if (e->pending) { + for (int i = 0; i < e->pending_len; i++) { + netpkt_t* pkt = e->pending[i]; + e->pending[i] = 0; + if (pkt) eth_send_frame_on(ifindex, ETHERTYPE_IPV4, e->mac, pkt); } + release(e->pending); + e->pending = 0; + e->pending_len = 0; + e->pending_bytes = 0; + } + arp_daemon_kick(); +} + +bool arp_table_get_for_l2(uint8_t ifindex, uint32_t ip, uint8_t mac_out[MAC_ADDR_LEN]){ + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !mac_out) return false; + + for (int i = 0; i < (int)N_ARR(t->entries); i++){ + arp_entry_t* e = &t->entries[i]; + if (e->state == ARP_STATE_UNUSED || e->state == ARP_STATE_INCOMPLETE) continue; + if (e->ip != ip) continue; + mac_copy(mac_out, e->mac); + return true; } + return false; +} + +bool arp_table_delete_for_l2(uint8_t ifindex, uint32_t ip) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !ip) return false; + int idx = arp_find_slot(t, ip); + if (idx < 0) return false; + arp_entry_clear(&t->entries[idx]); + return true; } -void arp_tick_all(uint32_t ms){ - for (uint8_t i=1;i<=MAX_L2_INTERFACES;i++){ - l2_interface_t* l2 = l2_interface_find_by_index(i); - if (!l2) continue; - if (!l2->arp_table) continue; - arp_table_tick_for_l2(i, ms); +uint32_t arp_table_dump_for_l2(uint8_t ifindex, arp_entry_t* out, uint32_t out_cap) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !out || !out_cap) return 0; + uint32_t n = 0; + for (int i = 0; i < (int)N_ARR(t->entries) && n < out_cap; i++) { + arp_entry_t* e = &t->entries[i]; + if (e->state == ARP_STATE_UNUSED) continue; + out[n++] = *e; } + return n; } static uint32_t pick_spa_for_l2(uint8_t ifindex, uint32_t target_ip){ l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (!l2) return 0; - for (int s=0;sl3_v4); s++) { l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->ip && v4->mask){ - uint32_t a = v4->ip & v4->mask; - uint32_t b = target_ip & v4->mask; - if (a == b) return v4->ip; + if (!ipv4_l3_is_ready(v4)) continue; + if (!fallback) fallback = v4->ip; + if (v4->mask && (v4->ip & v4->mask) == (target_ip & v4->mask)) return v4->ip; + } + return fallback; +} + +static void arp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t) return; + if (t->announce_left) { + if (t->announce_timer_ms > ms) t->announce_timer_ms -= ms; + else { + t->announce_timer_ms = 0; + if (arp_send_request_on(ifindex, t->announce_ip, t->announce_ip)) { + t->announce_left--; + if (t->announce_left) t->announce_timer_ms = ARP_ANNOUNCE_INTERVAL_MS; + } else { + t->announce_left = 0; + t->announce_ip = 0; + } + if (!t->announce_left) t->announce_ip = 0; } } - for (int s=0;sl3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (v4->ip) return v4->ip; + + for (int i = 0; i < (int)N_ARR(t->entries); i++){ + arp_entry_t* e = &t->entries[i]; + if (e->state == ARP_STATE_UNUSED || e->static_entry) continue; + + if (e->state == ARP_STATE_INCOMPLETE) { + if (e->timer_ms > ms) { + e->timer_ms -= ms; + continue; + } + + if (e->probes_sent >= ARP_MAX_PROBES) { + arp_entry_clear(e); + continue; + } + + e->timer_ms = ARP_RETRANS_MS; + e->probes_sent++; + arp_send_request_on(ifindex, pick_spa_for_l2(ifindex, e->ip), e->ip); + continue; + } + + if (e->ttl_ms > ms) { + e->ttl_ms -= ms; + continue; + } + + if (e->state == ARP_STATE_REACHABLE) { + e->state = ARP_STATE_STALE; + e->ttl_ms = ARP_STALE_MS; + continue; + } + + arp_entry_clear(e); } - return 0; } -bool arp_resolve_on(uint8_t ifindex, uint32_t ip, uint8_t mac_out[6], uint32_t timeout_ms){ - if (ip == 0xFFFFFFFFu){ - memset(mac_out, 0xFF, 6); - return true; +bool arp_send_or_queue_on(uint8_t ifindex, uint32_t ip, netpkt_t* pkt) { + if (!pkt || !netpkt_len(pkt)) { + if (pkt) netpkt_unref(pkt); + return false; } - if (arp_table_get_for_l2(ifindex, ip, mac_out)) return true; - arp_send_request_on(ifindex, ip); - uint32_t waited = 0; - const uint32_t POLL_MS = 100; - while (waited < timeout_ms) { - arp_table_tick_for_l2(ifindex, POLL_MS); - if (arp_table_get_for_l2(ifindex, ip, mac_out)) return true; - msleep(POLL_MS); - waited += POLL_MS; + + uint8_t mac[MAC_ADDR_LEN]; + if (ip == IPV4_LIMITED_BROADCAST) { + mac_set_broadcast(mac); + return eth_send_frame_on(ifindex, ETHERTYPE_IPV4, mac, pkt); } - return false; + + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !ip) { + netpkt_unref(pkt); + return false; + } + + int idx = arp_find_slot(t, ip); + if (idx >= 0 && t->entries[idx].state != ARP_STATE_INCOMPLETE) { + mac_copy(mac, t->entries[idx].mac); + return eth_send_frame_on(ifindex, ETHERTYPE_IPV4, mac, pkt); + } + if (idx < 0) idx = arp_find_replacement(t); + if (idx < 0) { + netpkt_unref(pkt); + return false; + } + + arp_entry_t* e = &t->entries[idx]; + if (e->state != ARP_STATE_UNUSED && e->ip != ip) arp_entry_clear(e); + + uint32_t len = netpkt_len(pkt); + if (e->pending_len >= ARP_PENDING_MAX || e->pending_bytes + len > ARP_PENDING_MAX_BYTES) { + netpkt_unref(pkt); + return false; + } + + if (!e->pending) { + e->pending = (netpkt_t**)zalloc(sizeof(netpkt_t*) * ARP_PENDING_MAX); + if (!e->pending) { + netpkt_unref(pkt); + return false; + } + } + + e->ip = ip; + e->state = ARP_STATE_INCOMPLETE; + e->ttl_ms = ARP_REACHABLE_MS; + e->static_entry = 0; + e->pending[e->pending_len] = pkt; + e->pending_len++; + e->pending_bytes += len; + + if (!e->probes_sent || !e->timer_ms) { + e->timer_ms = ARP_RETRANS_MS; + e->probes_sent++; + arp_send_request_on(ifindex, pick_spa_for_l2(ifindex, ip), ip); + } + arp_daemon_kick(); + + return true; } -void arp_send_request_on(uint8_t ifindex, uint32_t target_ip){ +bool arp_send_request_on(uint8_t ifindex, uint32_t sender_ip, uint32_t target_ip){ const uint8_t* local_mac = network_get_mac(ifindex); - if (!local_mac) return; - uint32_t spa = pick_spa_for_l2(ifindex, target_ip); - uint8_t dst_mac[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; + if (!local_mac || !target_ip) return false; + uint8_t dst_mac[MAC_ADDR_LEN]; + mac_set_broadcast(dst_mac); arp_hdr_t hdr; memset(&hdr, 0, sizeof(hdr)); hdr.htype = bswap16(1); hdr.ptype = bswap16(ETHERTYPE_IPV4); - hdr.hlen = 6; + hdr.hlen = MAC_ADDR_LEN; hdr.plen = 4; hdr.opcode = bswap16(ARP_OPCODE_REQUEST); - memcpy(hdr.sender_mac, local_mac, 6); - hdr.sender_ip = bswap32(spa); + mac_copy(hdr.sender_mac, local_mac); + hdr.sender_ip = bswap32(sender_ip); hdr.target_ip = bswap32(target_ip); netpkt_t* pkt = netpkt_alloc((uint32_t)sizeof(hdr), (uint32_t)sizeof(eth_hdr_t), 0); - if (!pkt) return; + if (!pkt) return false; void* p = netpkt_put(pkt, (uint32_t)sizeof(hdr)); if (!p) { netpkt_unref(pkt); - return; + return false; } memcpy(p, &hdr, sizeof(hdr)); - (void)eth_send_frame_on(ifindex, ETHERTYPE_ARP, dst_mac, pkt); + return eth_send_frame_on(ifindex, ETHERTYPE_ARP, dst_mac, pkt); +} + +bool arp_dad_ipv4_on(uint8_t ifindex, uint32_t ip) { + uint8_t mac[MAC_ADDR_LEN]; + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t || !ip || ip == IPV4_LIMITED_BROADCAST || ipv4_is_multicast(ip)) return false; + if (arp_table_get_for_l2(ifindex, ip, mac)) return false; + + t->dad_ip = ip; + t->dad_conflict = 0; + + for (int i = 0; i < ARP_DAD_PROBES; i++) { + if (!arp_send_request_on(ifindex, 0, ip)) { + t->dad_ip = 0; + return false; + } + uint32_t start = (uint32_t)get_time(); + while ((uint32_t)get_time() - start < ARP_DAD_WAIT_MS) { + if (t->dad_conflict || arp_table_get_for_l2(ifindex, ip, mac)) { + t->dad_ip = 0; + t->dad_conflict = 0; + return false; + } + msleep(10); + } + } + + bool ok = !t->dad_conflict && !arp_table_get_for_l2(ifindex, ip, mac); + t->dad_ip = 0; + t->dad_conflict = 0; + if (!ok) return false; + + if (arp_send_request_on(ifindex, ip, ip)) { + t->announce_ip = ip; + t->announce_left = 1; + t->announce_timer_ms = ARP_ANNOUNCE_INTERVAL_MS; + arp_daemon_kick(); + } + return true; } static bool l2_has_ip(uint8_t ifindex, uint32_t ip){ l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (!l2) return false; - for (int s=0;sl3_v4); s++){ l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; + if (!ipv4_l3_is_ready(v4)) continue; if (v4->ip == ip) return true; } return false; } -static void arp_send_reply_on(uint8_t ifindex, const arp_hdr_t* in_arp, const uint8_t in_src_mac[6]){ +static void arp_send_reply_on(uint8_t ifindex, const arp_hdr_t* in_arp, const uint8_t in_src_mac[MAC_ADDR_LEN]){ const uint8_t* local_mac = network_get_mac(ifindex); if (!local_mac) return; - uint32_t spa = pick_spa_for_l2(ifindex, bswap32(in_arp->sender_ip)); - if (!spa) return; + uint32_t spa = bswap32(in_arp->target_ip); + if (!l2_has_ip(ifindex, spa)) return; arp_hdr_t reply = *in_arp; - memcpy(reply.target_mac, in_arp->sender_mac, 6); - memcpy(reply.sender_mac, local_mac, 6); + mac_copy(reply.target_mac, in_arp->sender_mac); + mac_copy(reply.sender_mac, local_mac); reply.target_ip = in_arp->sender_ip; reply.sender_ip = bswap32(spa); reply.opcode = bswap16(ARP_OPCODE_REPLY); @@ -205,45 +442,132 @@ static void arp_send_reply_on(uint8_t ifindex, const arp_hdr_t* in_arp, const ui (void)eth_send_frame_on(ifindex, ETHERTYPE_ARP, in_src_mac, pkt); } -void arp_input(uint16_t ifindex, netpkt_t* pkt) { - if (!pkt) return; - uint32_t frame_len = netpkt_len(pkt); - uintptr_t frame_ptr = netpkt_data(pkt); - if (frame_len < (uint32_t)sizeof(eth_hdr_t) + (uint32_t)sizeof(arp_hdr_t)) return; +void arp_input(uint8_t ifindex, const uint8_t src_mac[MAC_ADDR_LEN], netpkt_t* pkt) { + if (!pkt || !src_mac) return; + if (netpkt_len(pkt) < (uint32_t)sizeof(arp_hdr_t)) return; + + arp_hdr_t hdr; + if (!netpkt_copyout(pkt, 0, &hdr, sizeof(hdr))) return; + if (bswap16(hdr.htype) != 1) return; + if (bswap16(hdr.ptype) != ETHERTYPE_IPV4) return; + if (hdr.hlen != MAC_ADDR_LEN || hdr.plen != 4) return; - const eth_hdr_t* eth = (const eth_hdr_t*)frame_ptr; - const uint8_t* src_mac = eth->src_mac; - const arp_hdr_t* hdr = (const arp_hdr_t*)(frame_ptr + sizeof(eth_hdr_t)); - uint16_t op = bswap16(hdr->opcode); - uint32_t sender_ip = bswap32(hdr->sender_ip); - uint32_t target_ip = bswap32(hdr->target_ip); + uint16_t op = bswap16(hdr.opcode); + if (op != ARP_OPCODE_REQUEST && op != ARP_OPCODE_REPLY) return; + if (!mac_equal(hdr.sender_mac, src_mac) || !mac_is_unicast(hdr.sender_mac)) return; - arp_table_put_for_l2((uint8_t)ifindex, sender_ip, hdr->sender_mac, 180000, false); + uint32_t sender_ip = bswap32(hdr.sender_ip); + uint32_t target_ip = bswap32(hdr.target_ip); - if (op == ARP_OPCODE_REQUEST) { - char tbuf[16], abuf[16]; - ipv4_to_string(target_ip, tbuf); + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t) return; + + const uint8_t* local_mac = network_get_mac(ifindex); + bool from_us = local_mac && mac_equal(src_mac, local_mac); + if (!from_us && t->dad_ip && (sender_ip == t->dad_ip || (!sender_ip && target_ip == t->dad_ip))) t->dad_conflict = 1; - bool has = l2_has_ip((uint8_t)ifindex, target_ip); + bool target_is_local = l2_has_ip(ifindex, target_ip); + bool sender_is_local = sender_ip && l2_has_ip(ifindex, sender_ip); + if (!from_us && sender_is_local) { + uint32_t now = (uint32_t)get_time(); + int slot = -1; + int oldest = -1; + uint32_t oldest_age = 0; - uint32_t spa_guess = pick_spa_for_l2((uint8_t)ifindex, sender_ip); - ipv4_to_string(spa_guess, abuf); - if (has || (spa_guess == target_ip)) { - arp_send_reply_on((uint8_t)ifindex, hdr, src_mac); + for (int i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + arp_defense_t* d = &t->defense[i]; + if (d->used && d->ip == sender_ip) { + slot = i; + break; + } + if (!d->used && slot < 0) slot = i; + if (!d->used) continue; + uint32_t age = now - d->last_conflict_ms; + if (age >= oldest_age) { + oldest_age = age; + oldest = i; + } } + + if (slot < 0) slot = oldest; + arp_defense_t* d = &t->defense[slot]; + bool defend = !d->used || d->ip != sender_ip || now - d->last_conflict_ms >= ARP_DEFEND_INTERVAL_MS; + d->used = 1; + d->ip = sender_ip; + d->last_conflict_ms = now; + if (defend) arp_send_request_on(ifindex, sender_ip, sender_ip); + } + + bool garp = sender_ip && sender_ip == target_ip; + bool sender_is_usable = sender_ip != 0 && sender_ip != IPV4_LIMITED_BROADCAST && !ipv4_is_multicast(sender_ip) && !sender_is_local; + if (sender_is_usable) { + bool target_mac_is_us = local_mac && mac_equal(hdr.target_mac, local_mac); + bool confirmed = op == ARP_OPCODE_REPLY && target_is_local && target_mac_is_us && !garp; + arp_table_learn_for_l2(ifindex, sender_ip, hdr.sender_mac, confirmed, !garp && target_is_local); } + + if (op == ARP_OPCODE_REQUEST && target_is_local) arp_send_reply_on(ifindex, &hdr, src_mac); } -void arp_set_pid(uint16_t pid){ g_arp_pid = pid; } -uint16_t arp_get_pid(void){ return g_arp_pid; } +static bool arp_has_timed_work(void) { + for (uint8_t ifindex = 1; ifindex <= MAX_L2_INTERFACES; ifindex++) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + arp_table_t* t = l2 ? (arp_table_t*)l2->arp_table : NULL; + if (!t) continue; + if (t->announce_left) return true; -int arp_daemon_entry(int argc, char* argv[]){ + for (int i = 0; i < (int)N_ARR(t->entries); i++) { + arp_entry_t* e = &t->entries[i]; + if (e->state != ARP_STATE_UNUSED && !e->static_entry) return true; + } + } + return false; +} + +static int arp_daemon_entry(int argc, char* argv[]){ (void)argc; (void)argv; - arp_set_pid(get_current_proc_pid()); - const uint32_t tick_ms = 10000; - while (1){ - arp_tick_all(tick_ms); - msleep(tick_ms); + + irq_flags_t irq = irq_save_disable(); + g_arp_daemon_pending = 0; + g_arp_daemon_running = 1; + irq_restore(irq); + + uint32_t last_ms = (uint32_t)get_time(); + while (arp_has_timed_work()) { + msleep(1000); + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - last_ms; + last_ms = now_ms; + for (uint8_t ifindex = 1; ifindex <= MAX_L2_INTERFACES; ifindex++) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || !l2->arp_table) continue; + arp_table_tick_for_l2(ifindex, elapsed_ms); + } + } + + irq = irq_save_disable(); + g_arp_daemon_running = 0; + irq_restore(irq); + arp_daemon_kick(); + return 0; +} + +static void arp_daemon_kick(void) { + if (!arp_has_timed_work()) return; + + irq_flags_t irq = irq_save_disable(); + if (g_arp_daemon_running || g_arp_daemon_pending) { + irq_restore(irq); + return; + } + g_arp_daemon_pending = 1; + irq_restore(irq); + + if (!create_kernel_process("arp_daemon", arp_daemon_entry, 0, 0)) { + irq = irq_save_disable(); + g_arp_daemon_pending = 0; + irq_restore(irq); } } diff --git a/kernel/networking/link_layer/arp.h b/kernel/networking/link_layer/arp.h index badfb6ee..2c68d643 100644 --- a/kernel/networking/link_layer/arp.h +++ b/kernel/networking/link_layer/arp.h @@ -1,33 +1,48 @@ #pragma once #include "types.h" -#include "networking/interface_manager.h" #include "networking/netpkt.h" +#include "networking/link_layer/link_utils.h" #ifdef __cplusplus extern "C" { #endif #define ARP_TABLE_MAX 64 +#define ARP_PENDING_MAX 8 +#define ARP_PENDING_MAX_BYTES 32768 #define ARP_OPCODE_REQUEST 1 #define ARP_OPCODE_REPLY 2 +typedef enum { + ARP_STATE_UNUSED = 0, + ARP_STATE_INCOMPLETE = 1, + ARP_STATE_REACHABLE = 2, + ARP_STATE_STALE = 3 +} arp_state_t; + typedef struct __attribute__((packed)) arp_hdr_t { uint16_t htype; uint16_t ptype; uint8_t hlen; uint8_t plen; uint16_t opcode;//1=request, 2=reply - uint8_t sender_mac[6]; + uint8_t sender_mac[MAC_ADDR_LEN]; uint32_t sender_ip; - uint8_t target_mac[6]; + uint8_t target_mac[MAC_ADDR_LEN]; uint32_t target_ip; } arp_hdr_t; typedef struct arp_entry { uint32_t ip; - uint8_t mac[6]; + uint8_t mac[MAC_ADDR_LEN]; uint32_t ttl_ms; - uint8_t static_entry;//1 static, 0 dynamic + uint32_t timer_ms; + uint32_t pending_bytes; + netpkt_t** pending; + uint8_t pending_len; + uint8_t state; + uint8_t probes_sent; + uint8_t static_entry;//1 static, 0 dynamic } arp_entry_t; typedef struct arp_table arp_table_t; @@ -36,19 +51,15 @@ arp_table_t* arp_table_create(void); void arp_table_destroy(arp_table_t* t); void arp_table_init_static_defaults(arp_table_t* t); -void arp_table_put_for_l2(uint8_t ifindex, uint32_t ip, const uint8_t mac[6], uint32_t ttl_ms, bool is_static); -bool arp_table_get_for_l2(uint8_t ifindex, uint32_t ip, uint8_t mac_out[6]); -void arp_table_tick_for_l2(uint8_t ifindex, uint32_t ms); -void arp_tick_all(uint32_t ms); - -bool arp_resolve_on(uint8_t ifindex, uint32_t ip, uint8_t mac_out[6], uint32_t timeout_ms); -void arp_send_request_on(uint8_t ifindex, uint32_t target_ip); - -void arp_input(uint16_t ifindex, netpkt_t* pkt); +void arp_table_put_for_l2(uint8_t ifindex, uint32_t ip, const uint8_t mac[MAC_ADDR_LEN], uint32_t ttl_ms, bool is_static); +bool arp_table_get_for_l2(uint8_t ifindex, uint32_t ip, uint8_t mac_out[MAC_ADDR_LEN]); +bool arp_table_delete_for_l2(uint8_t ifindex, uint32_t ip); +uint32_t arp_table_dump_for_l2(uint8_t ifindex, arp_entry_t* out, uint32_t out_cap); +bool arp_send_or_queue_on(uint8_t ifindex, uint32_t ip, netpkt_t* pkt); +bool arp_send_request_on(uint8_t ifindex, uint32_t sender_ip, uint32_t target_ip); +bool arp_dad_ipv4_on(uint8_t ifindex, uint32_t ip); -void arp_set_pid(uint16_t pid); -uint16_t arp_get_pid(void); -int arp_daemon_entry(int argc, char* argv[]); +void arp_input(uint8_t ifindex, const uint8_t src_mac[MAC_ADDR_LEN], netpkt_t* pkt); #ifdef __cplusplus } diff --git a/kernel/networking/link_layer/eth.c b/kernel/networking/link_layer/eth.c index e50400fc..3cc1cdc2 100644 --- a/kernel/networking/link_layer/eth.c +++ b/kernel/networking/link_layer/eth.c @@ -1,81 +1,104 @@ #include "eth.h" -#include "std/memory.h" +#include "networking/interface_manager.h" #include "networking/network.h" #include "arp.h" #include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv6.h" -#include "console/kio.h" -#include "syscalls/syscalls.h" -uintptr_t create_eth_packet(uintptr_t p, const uint8_t src_mac[6], const uint8_t dst_mac[6], uint16_t type) { - eth_hdr_t* eth =(eth_hdr_t*)p; - memcpy(eth->dst_mac, dst_mac, 6); - memcpy(eth->src_mac, src_mac, 6); - eth->ethertype = bswap16(type); - - return p + (uint32_t)sizeof(eth_hdr_t); +uint16_t eth_parse_type(const netpkt_t* pkt){ + uint16_t type = 0; + if (!pkt || !netpkt_copyout(pkt, 12u, &type, sizeof(type))) return 0; + return rd_be16(&type); } -uint16_t eth_parse_type(uintptr_t ptr){ - const eth_hdr_t* eth = (const eth_hdr_t*)ptr; - return bswap16(eth->ethertype); +bool eth_parse_vlan(const netpkt_t* pkt, uint16_t* vlan_id, uint16_t* inner_type) { + uint8_t tag[ETH_VLAN_TAG_LEN]; + if (!pkt || !vlan_id || !inner_type || netpkt_len(pkt) < sizeof(eth_hdr_t) + sizeof(tag)) return false; + if (!netpkt_copyout(pkt, sizeof(eth_hdr_t), tag, sizeof(tag))) return false; + *vlan_id = rd_be16(tag) & 0x0FFF; + *inner_type = rd_be16(tag + 2); + return true; } -const uint8_t* eth_src(uintptr_t ptr){ - const eth_hdr_t* eth = (const eth_hdr_t*)ptr; - return eth->src_mac; -} +bool eth_send_frame_on(uint8_t ifindex, uint16_t ethertype, const uint8_t dst_mac[MAC_ADDR_LEN], netpkt_t* pkt){ + l2_interface_t* itf = l2_interface_find_by_index(ifindex); + if (!pkt || !itf || !itf->is_up || !dst_mac) { + if (pkt) netpkt_unref(pkt); + return false; + } -const uint8_t* eth_dst(uintptr_t ptr){ - const eth_hdr_t* eth = (const eth_hdr_t*)ptr; - return eth->dst_mac; -} + bool tagged = itf->link_kind == NET_LINK_VLAN; + if (tagged) { + l2_interface_t *parent = l2_interface_find_by_index(itf->parent_ifindex); + if (!parent || parent->link_kind != NET_LINK_DIRECT || !parent->is_up) { + netpkt_unref(pkt); + return false; + } + } -bool eth_send_frame_on(uint16_t ifindex, uint16_t ethertype, const uint8_t dst_mac[6], netpkt_t* pkt){ const uint8_t* src_mac = network_get_mac(ifindex); - if (!src_mac || !dst_mac || !pkt) { - if (pkt) netpkt_unref(pkt); + if (!src_mac) { + netpkt_unref(pkt); + return false; + } + uint32_t head_size = sizeof(eth_hdr_t) + (tagged ? ETH_VLAN_TAG_LEN : 0); + uint32_t driver_headroom = network_get_header_size(ifindex); + if (!netpkt_ensure_headroom(pkt, head_size + driver_headroom)) { + netpkt_unref(pkt); return false; } - void* hdrp = netpkt_push(pkt, (uint32_t)sizeof(eth_hdr_t)); + uint8_t* hdrp = netpkt_push(pkt, head_size); if (!hdrp) { netpkt_unref(pkt); return false; } - (void)create_eth_packet((uintptr_t)hdrp, src_mac, dst_mac, ethertype); + mac_copy(hdrp, dst_mac); + mac_copy(hdrp + MAC_ADDR_LEN, src_mac); + if (tagged) { + wr_be16(hdrp + 12, ETHERTYPE_VLAN1Q); + wr_be16(hdrp + 14, itf->vlan_id); + wr_be16(hdrp + 16, ethertype); + } else wr_be16(hdrp + 12, ethertype); - bool ok = (net_tx_frame_on(ifindex, netpkt_data(pkt), netpkt_len(pkt)) == 0); - netpkt_unref(pkt); + bool ok = (net_tx_packet_on(ifindex, pkt) == 0); + if (!ok) netpkt_unref(pkt); return ok; } -void eth_input(uint16_t ifindex, netpkt_t* pkt) { - if (!pkt) return; +void eth_input(uint8_t ifindex, netpkt_t* pkt) { + l2_interface_t* input = l2_interface_find_by_index(ifindex); + if (!pkt || !input || !input->is_up) return; - uint32_t frame_len = netpkt_len(pkt); - uintptr_t frame_ptr = netpkt_data(pkt); - if (frame_len < sizeof(eth_hdr_t)) return; + eth_hdr_t eth; + if (!netpkt_copyout(pkt, 0, ð, sizeof(eth))) return; - uint16_t type = eth_parse_type(frame_ptr); - const uint8_t* src_mac = eth_src(frame_ptr); + uint16_t type = bswap16(eth.ethertype); + l2_interface_t *logical = input; + uint32_t header_size = sizeof(eth_hdr_t); + if (type == ETHERTYPE_VLAN1Q) { + if (input->dev_kind != NET_DEV_ETH || input->link_kind != NET_LINK_DIRECT) return; + uint16_t vid = 0; + if (!eth_parse_vlan(pkt, &vid, &type) || vid == 4095) return; + logical = vid ? l2_vlan_find(ifindex, vid) : input; + if (!logical || !logical->is_up) return; + header_size += ETH_VLAN_TAG_LEN; + } + + if (!netpkt_pull(pkt, header_size)) return; switch (type) { case ETHERTYPE_ARP: - arp_input(ifindex, pkt); + arp_input(logical->ifindex, eth.src_mac, pkt); break; case ETHERTYPE_IPV4: - if (!netpkt_pull(pkt, (uint32_t)sizeof(eth_hdr_t))) break; - ipv4_input(ifindex, pkt, src_mac); + ipv4_input(logical->ifindex, pkt, eth.src_mac); break; case ETHERTYPE_IPV6: - if (!netpkt_pull(pkt, (uint32_t)sizeof(eth_hdr_t))) break; - ipv6_input(ifindex, pkt, src_mac); - break; - case ETHERTYPE_VLAN1Q: //TODO vlan + ipv6_input(logical->ifindex, pkt, eth.src_mac); break; - case ETHERTYPE_VLAN1AD: //TODO vlan + case ETHERTYPE_VLAN1AD: break; default: break; diff --git a/kernel/networking/link_layer/eth.h b/kernel/networking/link_layer/eth.h index 38dc465b..e489267d 100644 --- a/kernel/networking/link_layer/eth.h +++ b/kernel/networking/link_layer/eth.h @@ -2,6 +2,7 @@ #include "types.h" #include "net/network_types.h" #include "networking/netpkt.h" +#include "networking/link_layer/link_utils.h" #ifdef __cplusplus extern "C" { @@ -12,20 +13,19 @@ extern "C" { #define ETHERTYPE_VLAN1Q 0x8100 #define ETHERTYPE_VLAN1AD 0x88A8 #define ETHERTYPE_IPV6 0x86DD +#define ETH_VLAN_TAG_LEN 4 typedef struct __attribute__((packed)) eth_hdr_t { - uint8_t dst_mac[6]; - uint8_t src_mac[6]; + uint8_t dst_mac[MAC_ADDR_LEN]; + uint8_t src_mac[MAC_ADDR_LEN]; uint16_t ethertype; } eth_hdr_t; -uint16_t eth_parse_type(uintptr_t frame_ptr); -const uint8_t* eth_src(uintptr_t frame_ptr); -const uint8_t* eth_dst(uintptr_t frame_ptr); +uint16_t eth_parse_type(const netpkt_t* pkt); +bool eth_parse_vlan(const netpkt_t* pkt, uint16_t* vlan_id, uint16_t* inner_type); +bool eth_send_frame_on(uint8_t ifindex, uint16_t ethertype, const uint8_t dst_mac[MAC_ADDR_LEN], netpkt_t* pkt); -bool eth_send_frame_on(uint16_t ifindex, uint16_t ethertype, const uint8_t dst_mac[6], netpkt_t* pkt); - -void eth_input(uint16_t ifindex, netpkt_t* pkt); +void eth_input(uint8_t ifindex, netpkt_t* pkt); #ifdef __cplusplus } diff --git a/kernel/networking/link_layer/link_utils.c b/kernel/networking/link_layer/link_utils.c index 09d6af3e..98e4523a 100644 --- a/kernel/networking/link_layer/link_utils.c +++ b/kernel/networking/link_layer/link_utils.c @@ -1,13 +1,41 @@ #include "link_utils.h" +#include "std/memory.h" -void mac_to_string(const uint8_t mac[6], char out[18]){ +void mac_copy(uint8_t dst[MAC_ADDR_LEN], const uint8_t src[MAC_ADDR_LEN]){ + if (!dst || !src) return; + memcpy(dst, src, MAC_ADDR_LEN); +} + +bool mac_equal(const uint8_t a[MAC_ADDR_LEN], const uint8_t b[MAC_ADDR_LEN]){ + if (!a || !b) return false; + return memcmp(a, b, MAC_ADDR_LEN) == 0; +} + +bool mac_is_unicast(const uint8_t mac[MAC_ADDR_LEN]) { + if (!mac || (mac[0] & 1)) return false; + for (int i = 0; i < MAC_ADDR_LEN; i++) if (mac[i]) return 1; + return false; +} + +void mac_clear(uint8_t mac[MAC_ADDR_LEN]){ + if (!mac) return; + memset(mac, 0, MAC_ADDR_LEN); +} + +void mac_set_broadcast(uint8_t mac[MAC_ADDR_LEN]){ + if (!mac) return; + memset(mac, 0xFF, MAC_ADDR_LEN); +} + +void mac_to_string(const uint8_t mac[MAC_ADDR_LEN], char out[18]){ + if (!out) return; static const char HEX[] = "0123456789abcdef"; int p = 0; - for (int i = 0; i < 6; ++i) { + for (int i = 0; i < MAC_ADDR_LEN; ++i) { uint8_t b = mac ? mac[i] : 0; out[p++] = HEX[b >> 4]; out[p++] = HEX[b & 0x0F]; - if (i != 5) out[p++] = ':'; + if (i != MAC_ADDR_LEN - 1) out[p++] = ':'; } out[p] = 0; } diff --git a/kernel/networking/link_layer/link_utils.h b/kernel/networking/link_layer/link_utils.h index 78d12204..3948f74d 100644 --- a/kernel/networking/link_layer/link_utils.h +++ b/kernel/networking/link_layer/link_utils.h @@ -6,7 +6,14 @@ extern "C" { #endif -void mac_to_string(const uint8_t mac[6], char out[18]); +#define MAC_ADDR_LEN 6 + +void mac_copy(uint8_t dst[MAC_ADDR_LEN], const uint8_t src[MAC_ADDR_LEN]); +bool mac_equal(const uint8_t a[MAC_ADDR_LEN], const uint8_t b[MAC_ADDR_LEN]); +bool mac_is_unicast(const uint8_t mac[MAC_ADDR_LEN]); +void mac_clear(uint8_t mac[MAC_ADDR_LEN]); +void mac_set_broadcast(uint8_t mac[MAC_ADDR_LEN]); +void mac_to_string(const uint8_t mac[MAC_ADDR_LEN], char out[18]); #ifdef __cplusplus } diff --git a/kernel/networking/link_layer/ndp.c b/kernel/networking/link_layer/ndp.c index dfa878d8..b3ee5691 100644 --- a/kernel/networking/link_layer/ndp.c +++ b/kernel/networking/link_layer/ndp.c @@ -1,25 +1,62 @@ #include "ndp.h" +#include "eth.h" +#include "link_utils.h" #include "networking/internet_layer/icmpv6.h" #include "std/memory.h" -#include "std/string.h" #include "networking/interface_manager.h" +#include "networking/application_layer/dhcpv6_daemon.h" +#include "networking/application_layer/dns/dns_daemon.h" #include "networking/internet_layer/ipv6_utils.h" +#include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv6_route.h" +#include "networking/internet_layer/mld.h" #include "net/checksums.h" #include "syscalls/syscalls.h" #include "networking/network.h" -#include "process/scheduler.h" +#include "kernel_processes/kprocess_loader.h" +#include "exceptions/irq.h" #include "math/rng.h" +#include "random/random.h" + +typedef struct { + uint8_t ip[16]; + uint32_t lifetime_ms; + uint32_t last_probe_ms; + int8_t preference; + uint8_t failed; + uint8_t used; +} ndp_default_router_t; + +#define NDP_DEFAULT_ROUTER_MAX 8 typedef struct { ndp_entry_t entries[NDP_TABLE_MAX]; - uint8_t init; + ndp_default_router_t routers[NDP_DEFAULT_ROUTER_MAX]; + uint32_t base_reachable_time_ms; + uint32_t reachable_time_ms; + uint32_t retrans_timer_ms; + uint32_t reachable_recalc_ms; + uint32_t rs_timer_ms; + uint8_t router_rr; + uint8_t rs_tries; } ndp_table_impl_t; -static uint32_t g_ndp_reachable_time_ms = 30000; -static uint32_t g_ndp_retrans_timer_ms = 1000; -static uint8_t g_ndp_max_probes = 3; -static volatile uint16_t g_ndp_pid = 0xFFFF; +#define NDP_DEFAULT_REACHABLE_TIME_MS 30000u +#define NDP_DEFAULT_RETRANS_TIMER_MS 1000u +#define NDP_REACHABLE_RECALC_MS 7200000u +#define NDP_DELAY_FIRST_PROBE_TIME_MS 5000u +#define NDP_MAX_PROBES 3u +#define NDP_ROUTER_REPROBE_INTERVAL_MS 60000u +#define NDP_MAX_RA_REACHABLE_TIME_MS 3600000u + +enum { + NDP_OPT_SOURCE_LLADDR = 1, + NDP_OPT_TARGET_LLADDR = 2, + NDP_OPT_PREFIX_INFO = 3, + NDP_OPT_REDIRECTED_HEADER = 4, + NDP_OPT_MTU = 5, + NDP_OPT_RDNSS = 25 +}; static rng_t g_rng; @@ -35,6 +72,13 @@ typedef struct __attribute__((packed)) { uint8_t target[16]; } icmpv6_na_t; +typedef struct __attribute__((packed)) { + icmpv6_hdr_t hdr; + uint32_t reserved; + uint8_t target[16]; + uint8_t destination[16]; +} icmpv6_redirect_t; + typedef struct __attribute__((packed)) { uint8_t type; uint8_t length; @@ -68,8 +112,105 @@ typedef struct __attribute__((packed)) { uint32_t mtu; } ndp_opt_mtu_t; -static uint8_t g_rs_tries[MAX_L2_INTERFACES]; -static uint32_t g_rs_timer_ms[MAX_L2_INTERFACES]; +typedef struct { + uint8_t type; + uint32_t off; + uint32_t size; +} ndp_opt_t; + +static volatile uint8_t g_ndp_daemon_running; +static volatile uint8_t g_ndp_daemon_pending; + +static bool ndp_read_opt(netpkt_t* pkt, uint32_t* off, uint32_t* len, ndp_opt_t* opt) { + if (*len < 2) return 0; + + uint8_t hdr[2]; + if (!netpkt_copyout(pkt, *off, hdr, sizeof(hdr)) || !hdr[1]) return 0; + + uint32_t size = (uint32_t)hdr[1]*8; + if (size > *len) return 0; + + opt->type = hdr[0]; + opt->off = *off; + opt->size = size; + *off += size; + *len -= size; + return true; +} + +static bool ndp_read_lladdr_option(netpkt_t * pkt, uint32_t off, uint32_t len, uint8_t type, uint8_t mac[6], bool* found) { + *found = false; + + while (len) { + ndp_opt_t opt; + if (!ndp_read_opt(pkt, &off, &len, &opt)) return false; + + if (opt.type == type) { + if (opt.size != sizeof(icmpv6_opt_lladdr_t)) return false; + if (!*found) { + icmpv6_opt_lladdr_t lladdr; + if (!netpkt_copyout(pkt, opt.off, &lladdr, sizeof(lladdr))) return false; + mac_copy(mac, lladdr.mac); + *found = true; + } + } + } + + return true; +} + +static bool ndp_read_redirect_options(netpkt_t* pkt, uint32_t off, uint32_t len, const uint8_t local_ip[16], const uint8_t destination[16], uint8_t mac[6], bool* found) { + *found = false; + + while (len) { + ndp_opt_t opt; + if (!ndp_read_opt(pkt, &off, &len, &opt)) return false; + + if (opt.type == NDP_OPT_TARGET_LLADDR) { + if (opt.size != sizeof(icmpv6_opt_lladdr_t)) return false; + if (!*found) { + icmpv6_opt_lladdr_t lladdr; + if (!netpkt_copyout(pkt, opt.off, &lladdr, sizeof(lladdr)) || !mac_is_unicast(lladdr.mac)) return false; + mac_copy(mac, lladdr.mac); + *found = true; + } + } else if (opt.type == NDP_OPT_REDIRECTED_HEADER) { + if (opt.size < 8 + sizeof(ipv6_hdr_t)) return false; + ipv6_hdr_t quoted; + if (!netpkt_copyout(pkt, opt.off + 8, "ed, sizeof(quoted))) return false; + if ((bswap32(quoted.ver_tc_fl) >> 28) != 6) return false; + if (ipv6_cmp(quoted.src, local_ip) != 0 || ipv6_cmp(quoted.dst, destination) != 0) return false; + } + } + + return true; +} + +static void ndp_flush_pending(uint8_t ifindex, ndp_entry_t* e) { + if (!e || !e->pending) return; + + for (int i = 0; i < e->pending_len; i++) { + netpkt_t* pkt = e->pending[i]; + e->pending[i] = 0; + if (pkt) eth_send_frame_on(ifindex, ETHERTYPE_IPV6, e->mac, pkt); + } + + release(e->pending); + e->pending = 0; + e->pending_len = 0; + e->pending_bytes = 0; +} + +static void ndp_mark_neighbor_observed(ndp_table_impl_t* t, ndp_entry_t *e, bool solicited) { + if (!e) return; + if (solicited) { + e->state = NDP_STATE_REACHABLE; + e->timer_ms = t && t->reachable_time_ms ? t->reachable_time_ms : NDP_DEFAULT_REACHABLE_TIME_MS; + } else { + e->state = NDP_STATE_STALE; + e->timer_ms = 0; + } +} static void make_random_iid(uint8_t out_iid[8]) { uint64_t x = 0; @@ -90,9 +231,15 @@ static void make_random_iid(uint8_t out_iid[8]) { static void handle_dad_failed(l3_ipv6_interface_t* v6) { if (!v6) return; + if (v6->cfg == IPV6_CFG_DHCPV6) { + v6->dad_requested = 0; + v6->dad_state = IPV6_DAD_NONE; + dhcpv6_force_decline_l3(v6->l3_id); + return; + } + uint8_t iid[8]; uint8_t new_ip[16]; - uint8_t zero16[16] = {0}; make_random_iid(iid); @@ -102,7 +249,7 @@ static void handle_dad_failed(l3_ipv6_interface_t* v6) { memset(new_ip + 2, 0, 6); memcpy(new_ip + 8, iid, 8); - (void)l3_ipv6_update(v6->l3_id, new_ip, 64, zero16, v6->cfg, v6->kind); + (void)l3_ipv6_update(v6->l3_id, new_ip, 64, (const uint8_t[16]){0}, v6->cfg, v6->kind); (void)ndp_request_dad_on(v6->l2 ? v6->l2->ifindex : 0, new_ip); return; } @@ -116,7 +263,7 @@ static void handle_dad_failed(l3_ipv6_interface_t* v6) { return; } - if (memcmp(v6->prefix, zero16, 16) != 0) ipv6_cpy(new_ip, v6->prefix); + if (!ipv6_is_unspecified(v6->prefix)) ipv6_cpy(new_ip, v6->prefix); else { ipv6_cpy(new_ip, v6->ip); memset(new_ip + 8, 0, 8); @@ -128,6 +275,69 @@ static void handle_dad_failed(l3_ipv6_interface_t* v6) { (void)ndp_request_dad_on(v6->l2 ? v6->l2->ifindex : 0, new_ip); } +static int ndp_find_slot(ndp_table_impl_t* t, const uint8_t ip[16]) { + if (!t) return -1; + + for (int i = 0; i < NDP_TABLE_MAX; i++) { + if (!t->entries[i].ttl_ms) continue; + if (ipv6_cmp(t->entries[i].ip, ip) == 0) return i; + } + + return -1; +} + +static int ndp_find_free(ndp_table_impl_t* t) { + if (!t) return -1; + for (int i = 0; i < NDP_TABLE_MAX; i++) if (t->entries[i].ttl_ms == 0 && t->entries[i].state == NDP_STATE_UNUSED) return i; + return -1; +} + +static bool ndp_select_default_router(ndp_table_impl_t* t, const uint8_t current[16], uint8_t out[16]) { + if (!out) return false; + memset(out, 0, 16); + if (!t) return false; + + int best = -1; + int8_t best_pref = -2; + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + ndp_default_router_t* r = &t->routers[i]; + if (!r->used || !r->lifetime_ms || r->failed) continue; + + int neighbor = ndp_find_slot(t, r->ip); + if (neighbor >= 0 && t->entries[neighbor].state == NDP_STATE_INCOMPLETE) continue; + + bool is_current = current && !ipv6_is_unspecified(current) && ipv6_cmp(r->ip, current) == 0; + if (r->preference > best_pref || (r->preference == best_pref && is_current)) { + best = i; + best_pref = r->preference; + } + } + + if (best < 0) { + int8_t fallback_pref = -2; + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + ndp_default_router_t* r = &t->routers[i]; + if (!r->used || !r->lifetime_ms) continue; + if (r->preference > fallback_pref) fallback_pref = r->preference; + } + + if (fallback_pref > -2) { + for (int n = 0; n < NDP_DEFAULT_ROUTER_MAX; n++) { + int i = (t->router_rr + n) % NDP_DEFAULT_ROUTER_MAX; + ndp_default_router_t* r = &t->routers[i]; + if (!r->used || !r->lifetime_ms || r->preference != fallback_pref) continue; + best = i; + t->router_rr = (uint8_t)(((i)+ 1) % NDP_DEFAULT_ROUTER_MAX); + break; + } + } + } + + if (best < 0) return false; + ipv6_cpy(out, t->routers[best].ip); + return true; +} + static void handle_lifetimes(uint32_t now_ms, l3_ipv6_interface_t* v6) { if (!v6) return; if (ipv6_is_placeholder_gua(v6->ip)) return; @@ -135,7 +345,7 @@ static void handle_lifetimes(uint32_t now_ms, l3_ipv6_interface_t* v6) { if (ipv6_is_linklocal(v6->ip)) return; if (!v6->ra_last_update_ms) return; - uint32_t elapsed_ms = now_ms >= v6->ra_last_update_ms ? now_ms - v6->ra_last_update_ms : 0; + uint32_t elapsed_ms = now_ms - v6->ra_last_update_ms; if (v6->preferred_lifetime && v6->preferred_lifetime != 0xFFFFFFFFu) { uint64_t pref_ms = (uint64_t)v6->preferred_lifetime * 1000ull; @@ -146,7 +356,7 @@ static void handle_lifetimes(uint32_t now_ms, l3_ipv6_interface_t* v6) { uint64_t valid_ms = (uint64_t)v6->valid_lifetime * 1000ull; if ((uint64_t)elapsed_ms >= valid_ms) { - if (!l3_ipv6_remove_from_interface(v6->l3_id)) (void)l3_ipv6_set_enabled(v6->l3_id, false); + if (!l3_ipv6_remove_from_interface(v6->l3_id)) (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, v6->gateway, IPV6_CFG_DISABLE, v6->kind); } } @@ -155,69 +365,62 @@ static void apply_ra_policy(uint32_t now_ms, l2_interface_t* l2) { uint8_t ifx = l2->ifindex; if (!ifx || ifx > MAX_L2_INTERFACES) return; + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; int has_lla_ok = 0; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (!ipv6_l3_is_ready(v6)) continue; if (!ipv6_is_linklocal(v6->ip)) continue; - if (v6->dad_state == IPV6_DAD_OK) { - has_lla_ok = 1; - break; - } + has_lla_ok = 1; + break; } if (!has_lla_ok) return; - uint8_t zero16[16] = {0}; + uint8_t default_router[16] = {0}; + (void)ndp_select_default_router(t, NULL, default_router); for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (!ipv6_l3_is_active(v6)) continue; if (!(v6->kind & IPV6_ADDRK_GLOBAL)) continue; if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; - if (!v6->ra_has)continue; - if (memcmp(v6->prefix, zero16, 16) == 0) continue; + if (!v6->ra_has) continue; + if (ipv6_is_unspecified(v6->prefix)) continue; uint8_t m = (v6->ra_flags & RA_FLAG_M) ? 1u : 0u; uint8_t o = (v6->ra_flags & RA_FLAG_O) ? 1u : 0u; - if (!v6->ra_autonomous) { - if (m) { - uint8_t gw[16]; - - if (v6->ra_is_default) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); - - v6->dhcpv6_stateless = 0; - v6->dhcpv6_stateless_done = 0; + if (m) { + v6->dhcpv6_stateless = 0; + v6->dhcpv6_stateless_done = 0; - if (v6->cfg != IPV6_CFG_DHCPV6 || ipv6_is_placeholder_gua(v6->ip)) { - uint8_t z[16] = {0}; - (void)l3_ipv6_update(v6->l3_id, z, 0, gw, IPV6_CFG_DHCPV6, v6->kind); - } else { - (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, gw, IPV6_CFG_DHCPV6, v6->kind); - } + if (v6->cfg != IPV6_CFG_DHCPV6 || ipv6_is_placeholder_gua(v6->ip)) { + (void)l3_ipv6_update(v6->l3_id, (const uint8_t[16]){0}, 0, default_router, IPV6_CFG_DHCPV6, v6->kind); } else { - v6->dhcpv6_stateless = o ? 1 : 0; - v6->dhcpv6_stateless_done = 0; + (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, default_router, IPV6_CFG_DHCPV6, v6->kind); } - continue; } - v6->dhcpv6_stateless = o ? 1 : 0; - v6->dhcpv6_stateless_done = 0; + if (!v6->ra_autonomous) { + uint8_t stateless = o ? 1 : 0; + if (v6->dhcpv6_stateless != stateless) { + v6->dhcpv6_stateless = stateless; + if (stateless) v6->dhcpv6_stateless_done = 0; + } + continue; + } + uint8_t stateless = o ? 1 : 0; + if (v6->dhcpv6_stateless != stateless) { + v6->dhcpv6_stateless = stateless; + if (stateless) v6->dhcpv6_stateless_done = 0; + } - if (v6->cfg != IPV6_CFG_SLAAC) { + ipv6_cfg_t ra_cfg = o ? IPV6_CFG_STATELESS : IPV6_CFG_SLAAC; + if (v6->cfg != ra_cfg) { uint8_t ph[16]; - uint8_t gw[16]; ipv6_make_placeholder_gua(ph); - - if (v6->ra_is_default) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); - - (void)l3_ipv6_update(v6->l3_id, ph, 64, gw, IPV6_CFG_SLAAC, v6->kind); + (void)l3_ipv6_update(v6->l3_id, ph, 64, default_router, ra_cfg, v6->kind); } if (ipv6_is_placeholder_gua(v6->ip)) { @@ -228,7 +431,7 @@ static void apply_ra_policy(uint32_t now_ms, l2_interface_t* l2) { ipv6_cpy(ip, v6->prefix); memcpy(ip + 8, iid, 8); - (void)l3_ipv6_update(v6->l3_id, ip, 64, v6->gateway, IPV6_CFG_SLAAC, v6->kind); + (void)l3_ipv6_update(v6->l3_id, ip, 64, v6->gateway, ra_cfg, v6->kind); v6->timestamp_created = now_ms; memcpy(v6->interface_id, ip + 8, 8); @@ -237,38 +440,35 @@ static void apply_ra_policy(uint32_t now_ms, l2_interface_t* l2) { continue; } - uint8_t gw[16]; - if (v6->ra_is_default) ipv6_cpy(gw, v6->gateway); - else memset(gw, 0, 16); - - (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, gw, IPV6_CFG_SLAAC, v6->kind); + (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, default_router, ra_cfg, v6->kind); v6->timestamp_created = now_ms; memcpy(v6->interface_id, v6->ip + 8, 8); } + dhcpv6_daemon_kick(); } -static void ndp_on_ra(uint8_t ifindex, const uint8_t router_ip[16], uint16_t router_lifetime, const uint8_t prefix[16], uint8_t prefix_len, uint32_t valid_lft, uint32_t preferred_lft, uint8_t autonomous, uint8_t ra_flags) { +static void ndp_on_ra(uint8_t ifindex, const uint8_t prefix[16], uint8_t prefix_len, uint32_t valid_lft, uint32_t preferred_lft, bool autonomous, uint8_t ra_flags) { if (!ifindex) return; if (prefix_len != 64) return; if (ipv6_is_unspecified(prefix) || ipv6_is_multicast(prefix) || ipv6_is_linklocal(prefix)) return; l2_interface_t* l2 = l2_interface_find_by_index(ifindex); if (!l2) return; + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; uint32_t now_ms = get_time(); - uint8_t zero16[16] = {0}; l3_ipv6_interface_t* slot = NULL; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; if (!v6) continue; if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (!(v6->kind == IPV6_ADDRK_GLOBAL)) continue; + if (v6->kind != IPV6_ADDRK_GLOBAL) continue; if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; - if (memcmp(v6->prefix, zero16, 16) != 0) { - if (ipv6_common_prefix_len(v6->prefix, prefix) >=64) { + if (!ipv6_is_unspecified(v6->prefix)) { + if (ipv6_common_prefix_len(v6->prefix, prefix) >= 64) { slot = v6; break; } @@ -291,7 +491,10 @@ static void ndp_on_ra(uint8_t ifindex, const uint8_t router_ip[16], uint16_t rou uint8_t ph[16]; ipv6_make_placeholder_gua(ph); - uint8_t id = l3_ipv6_add_to_interface(ifindex, ph, 64, zero16, IPV6_CFG_SLAAC, IPV6_ADDRK_GLOBAL); + ipv6_cfg_t cfg = IPV6_CFG_SLAAC; + if (ra_flags & RA_FLAG_M) cfg = IPV6_CFG_DHCPV6; + else if (ra_flags & RA_FLAG_O) cfg = IPV6_CFG_STATELESS; + l3_id_t id = l3_ipv6_add_to_interface(ifindex, ph, 64, (const uint8_t[16]){0}, cfg, IPV6_ADDRK_GLOBAL); if (!id) return; slot = l3_ipv6_find_by_id(id); @@ -300,121 +503,155 @@ static void ndp_on_ra(uint8_t ifindex, const uint8_t router_ip[16], uint16_t rou slot->ra_has = 1; slot->ra_autonomous = autonomous ? 1 : 0; - slot->ra_is_default = router_lifetime != 0; slot->ra_last_update_ms = now_ms; slot->ra_flags = ra_flags; ipv6_cpy(slot->prefix, prefix); - if (slot->ra_is_default && router_ip) ipv6_cpy(slot->gateway, router_ip); - else ipv6_cpy(slot->gateway, zero16); - slot->valid_lifetime = valid_lft; slot->preferred_lifetime = preferred_lft; - if (memcmp(slot->ip, zero16, 16) == 0) slot->timestamp_created = now_ms; + uint8_t gw[16] = {0}; + (void)ndp_select_default_router(t, slot->gateway, gw); + l3_ipv6_update(slot->l3_id, slot->ip, slot->prefix_len, gw, slot->cfg, slot->kind); + apply_ra_policy(now_ms, l2); - if(!ipv6_is_placeholder_gua(slot->ip) && !ipv6_is_unspecified(slot->ip)) memcpy(slot->interface_id, slot->ip + 8, 8); + if (ipv6_is_unspecified(slot->ip)) slot->timestamp_created = now_ms; + + if (!ipv6_is_placeholder_gua(slot->ip) && !ipv6_is_unspecified(slot->ip)) memcpy(slot->interface_id, slot->ip + 8, 8); +} + +static void ndp_entry_clear(ndp_entry_t* e) { + if (!e) return; + if (e->pending) { + for (int i = 0; i < e->pending_len; i++) if (e->pending[i]) netpkt_unref(e->pending[i]); + release(e->pending); + } + memset(e, 0, sizeof(*e)); + e->state = NDP_STATE_UNUSED; } ndp_table_t* ndp_table_create(void) { - ndp_table_impl_t* t = (ndp_table_impl_t*)malloc(sizeof(ndp_table_impl_t)); + ndp_table_impl_t* t = (ndp_table_impl_t*)zalloc(sizeof(ndp_table_impl_t)); if (!t) return 0; - memset(t, 0, sizeof(*t)); - t->init = 1; + t->base_reachable_time_ms = NDP_DEFAULT_REACHABLE_TIME_MS; + t->reachable_time_ms = NDP_DEFAULT_REACHABLE_TIME_MS; + t->retrans_timer_ms = NDP_DEFAULT_RETRANS_TIMER_MS; + t->reachable_recalc_ms = NDP_REACHABLE_RECALC_MS; return (ndp_table_t*)t; } void ndp_table_destroy(ndp_table_t* t) { if (!t) return; - free_sized(t, sizeof(ndp_table_impl_t)); -} - -static ndp_table_impl_t* l2_ndp(uint8_t ifindex) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return 0; - if (!l2->nd_table) l2->nd_table = ndp_table_create(); - return (ndp_table_impl_t*)l2->nd_table; + ndp_table_impl_t* impl = (ndp_table_impl_t*)t; + for (int i = 0; i < NDP_TABLE_MAX; i++) ndp_entry_clear(&impl->entries[i]); + release(t); } -static int ndp_find_slot(ndp_table_impl_t* t, const uint8_t ip[16]) { +static int ndp_find_replacement(ndp_table_impl_t* t) { + uint32_t best_ttl = 0xFFFFFFFFu; + int best = -1; if (!t) return -1; for (int i = 0; i < NDP_TABLE_MAX; i++) { - if (!t->entries[i].ttl_ms) continue; - if (memcmp(t->entries[i].ip, ip, 16) == 0) return i; + ndp_entry_t* e = &t->entries[i]; + if (e->pending_len) continue; + if (e->state == NDP_STATE_UNUSED || e->ttl_ms == 0) return i; + if (e->static_entry) continue; + if (e->ttl_ms < best_ttl) { + best_ttl = e->ttl_ms; + best = i; + } } - return -1; + return best; } -static int ndp_find_free(ndp_table_impl_t* t) { - if (!t) return -1; +static void ndp_sync_default_routes(uint8_t ifindex, ndp_table_impl_t* t) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + if (!l2 || !t) return; - for (int i = 0; i < NDP_TABLE_MAX; i++) if (t->entries[i].ttl_ms == 0 && t->entries[i].state == NDP_STATE_UNUSED) return i; + const uint8_t* current = NULL; + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6 || v6->is_localhost || !(v6->kind & IPV6_ADDRK_GLOBAL)) continue; + if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; + current = v6->gateway; + break; + } - return -1; + uint8_t selected[16] = {0}; + ndp_select_default_router(t, current, selected); + + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6 || v6->is_localhost || !(v6->kind & IPV6_ADDRK_GLOBAL)) continue; + if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; + if (ipv6_cmp(v6->gateway, selected) == 0) continue; + (void)l3_ipv6_update(v6->l3_id, v6->ip, v6->prefix_len, selected, v6->cfg, v6->kind); + } } -static void ndp_entry_clear(ndp_entry_t* e) { - memset(e, 0, sizeof(*e)); - e->state = NDP_STATE_UNUSED; +static void ndp_neighbor_failed(uint8_t ifindex, ndp_table_impl_t *t, ndp_entry_t *e) { + if (!t || !e) return; + + ipv6_redirect_invalidate(ifindex, e->ip); + if (e->is_router) { + bool changed = false; + for (int r = 0; r < NDP_DEFAULT_ROUTER_MAX; r++) { + if (!t->routers[r].used || ipv6_cmp(t->routers[r].ip, e->ip) != 0) continue; + if (!t->routers[r].failed) { + t->routers[r].failed = 1; + changed = true; + } + break; + } + if (changed) ndp_sync_default_routes(ifindex, t); + } + ndp_entry_clear(e); } -void ndp_table_put_for_l2(uint8_t ifindex, const uint8_t ip[16], const uint8_t mac[6], uint32_t ttl_ms, bool router) { - ndp_table_impl_t* t = l2_ndp(ifindex); +void ndp_table_put_for_l2(uint8_t ifindex, const uint8_t ip[16], const uint8_t mac[6], uint32_t ttl_ms, bool router, bool is_static) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; if (!t) return; int idx = ndp_find_slot(t, ip); if (idx < 0) idx = ndp_find_free(t); - if (idx < 0) { - uint32_t best_ttl = 0xFFFFFFFFu; - int best_i = -1; - - for (int i = 0; i < NDP_TABLE_MAX; i++) { - ndp_entry_t* e = &t->entries[i]; - if (e->state == NDP_STATE_UNUSED || e->ttl_ms == 0) { - best_i = i; - break; - } - - if (e->is_router && e->router_lifetime_ms) continue; - - if (e->ttl_ms < best_ttl) { - best_ttl = e->ttl_ms; - best_i = i; - } - } - - if (best_i < 0) best_i = 0; - idx = best_i; - } + if (idx < 0) idx = ndp_find_replacement(t); + if (idx < 0) return; ndp_entry_t* e = &t->entries[idx]; - memcpy(e->ip, ip, 16); + if (e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, ip) == 0 && e->static_entry && !is_static) return; + if (e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, ip) != 0) ndp_entry_clear(e); + ipv6_cpy(e->ip, ip); if (mac) { - memcpy(e->mac, mac, 6); + mac_copy(e->mac, mac); e->state = NDP_STATE_REACHABLE; - e->timer_ms = g_ndp_reachable_time_ms; + e->timer_ms = t->reachable_time_ms; } - if (ttl_ms == 0) { - ttl_ms = g_ndp_reachable_time_ms * 4; - if (ttl_ms == 0) ttl_ms = 1; + if (is_static) ttl_ms = UINT32_MAX; + else if (ttl_ms == 0) { + ttl_ms = t->reachable_time_ms * 4; } e->ttl_ms = ttl_ms; e->is_router = router ? 1 : 0; - e->router_lifetime_ms = router ? ttl_ms : 0; + e->static_entry = is_static ? 1 : 0; e->probes_sent = 0; + + ndp_flush_pending(ifindex, e); + if (!is_static) ndp_daemon_kick(); } -static bool ndp_table_get_for_l2(uint8_t ifindex, const uint8_t ip[16], uint8_t mac_out[6]) { - ndp_table_impl_t* t = l2_ndp(ifindex); +bool ndp_table_get_for_l2(uint8_t ifindex, const uint8_t ip[16], uint8_t mac_out[6]) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; if (!t) return false; for (int i = 0; i < NDP_TABLE_MAX; i++) { @@ -422,19 +659,57 @@ static bool ndp_table_get_for_l2(uint8_t ifindex, const uint8_t ip[16], uint8_t if (!e->ttl_ms) continue; if (e->state == NDP_STATE_UNUSED) continue; if (e->state == NDP_STATE_INCOMPLETE) continue; - if (memcmp(e->ip, ip, 16) != 0) continue; + if (ipv6_cmp(e->ip, ip) != 0) continue; - memcpy(mac_out, e->mac, 6); + mac_copy(mac_out, e->mac); + if (e->state == NDP_STATE_STALE) { + e->state = NDP_STATE_DELAY; + e->timer_ms = NDP_DELAY_FIRST_PROBE_TIME_MS; + ndp_daemon_kick(); + } return true; } return false; } +bool ndp_table_delete_for_l2(uint8_t ifindex, const uint8_t ip[16]) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t || !ip) return false; + int idx = ndp_find_slot(t, ip); + if (idx < 0) return false; + ndp_entry_clear(&t->entries[idx]); + return true; +} + +uint32_t ndp_table_dump_for_l2(uint8_t ifindex, ndp_entry_t* out, uint32_t out_cap) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t || !out || !out_cap) return 0; + uint32_t n = 0; + for (int i = 0; i < NDP_TABLE_MAX && n < out_cap; i++) { + ndp_entry_t* e = &t->entries[i]; + if (e->state == NDP_STATE_UNUSED) continue; + out[n++] = *e; + } + return n; +} + +uint32_t ndp_default_router_lifetime_for_l2(uint8_t ifindex, const uint8_t ip[16]) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t || !ip) return 0; + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) if (t->routers[i].used && ipv6_cmp(t->routers[i].ip, ip) == 0) return t->routers[i].lifetime_ms; + return 0; +} + static bool ndp_send_na_on(uint8_t ifindex, const uint8_t dst_ip[16], const uint8_t src_ip[16], const uint8_t target_ip[16], const uint8_t dst_mac_in[6], const uint8_t my_mac[6], uint8_t solicited) { + if (!my_mac) return false; + if (!ipv6_is_multicast(dst_ip) && !dst_mac_in) return false; + uint32_t plen = (uint32_t)(sizeof(icmpv6_na_t) + sizeof(icmpv6_opt_lladdr_t)); - uintptr_t buf = (uintptr_t)malloc(plen); - if (!buf) return false; + uint8_t buf[sizeof(icmpv6_na_t) + sizeof(icmpv6_opt_lladdr_t)] = {0}; icmpv6_na_t* na = (icmpv6_na_t*)buf; na->hdr.type = 136; @@ -446,30 +721,29 @@ static bool ndp_send_na_on(uint8_t ifindex, const uint8_t dst_ip[16], const uint flags |= (1u << 29); na->flags = bswap32(flags); - memcpy(na->target, target_ip, 16); + ipv6_cpy(na->target, target_ip); icmpv6_opt_lladdr_t* opt = (icmpv6_opt_lladdr_t*)(buf + sizeof(icmpv6_na_t)); opt->type = 2; opt->length = 1; - memcpy(opt->mac, my_mac, 6); + mac_copy(opt->mac, my_mac); - na->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, 58, (const uint8_t*)buf, plen)); + na->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, buf, plen)); uint8_t dst_mac[6]; if (ipv6_is_multicast(dst_ip)) ipv6_multicast_mac(dst_ip, dst_mac); - else memcpy(dst_mac, dst_mac_in, 6); - - bool ok = icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, (const void*)buf, plen, 255); + else mac_copy(dst_mac, dst_mac_in); - free_sized((void*)buf, plen); - return ok; + return icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, buf, plen, 255); } -static void ndp_send_ns_on(uint8_t ifindex, const uint8_t target_ip[16], const uint8_t src_ip[16]) { +static void ndp_send_ns_on(uint8_t ifindex, const uint8_t target_ip[16], const uint8_t src_ip[16], const uint8_t unicast_mac[6]) { bool dad = ipv6_is_unspecified(src_ip); - uint32_t plen = (uint32_t)sizeof(icmpv6_ns_t) + (dad ? 0u : (uint32_t)sizeof(icmpv6_opt_lladdr_t)); - uintptr_t buf = (uintptr_t)malloc(plen); - if (!buf) return; + const uint8_t* mac = dad ? NULL : network_get_mac(ifindex); + if (!dad && !mac) return; + + uint32_t plen = (uint32_t)sizeof(icmpv6_ns_t) + (mac ? (uint32_t)sizeof(icmpv6_opt_lladdr_t) : 0u); + uint8_t buf[sizeof(icmpv6_ns_t) + sizeof(icmpv6_opt_lladdr_t)] = {0}; icmpv6_ns_t* ns = (icmpv6_ns_t*)buf; ns->hdr.type = 135; @@ -477,28 +751,26 @@ static void ndp_send_ns_on(uint8_t ifindex, const uint8_t target_ip[16], const u ns->hdr.checksum = 0; ns->rsv = 0; - memcpy(ns->target, target_ip, 16); + ipv6_cpy(ns->target, target_ip); - if (!dad) { + if (mac) { icmpv6_opt_lladdr_t* opt = (icmpv6_opt_lladdr_t*)(buf + sizeof(icmpv6_ns_t)); opt->type = 1; opt->length = 1; - - const uint8_t* mac = network_get_mac(ifindex); - if (mac) memcpy(opt->mac, mac, 6); - else memset(opt->mac, 0, 6); + mac_copy(opt->mac, mac); } uint8_t dst_ip[16]; - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, target_ip, dst_ip); + if (unicast_mac) ipv6_cpy(dst_ip, target_ip); + else ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, target_ip, dst_ip); - ns->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, 58, (const uint8_t*)buf, plen)); + ns->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, buf, plen)); uint8_t dst_mac[6]; - ipv6_multicast_mac(dst_ip, dst_mac); + if (unicast_mac) mac_copy(dst_mac, unicast_mac); + else ipv6_multicast_mac(dst_ip, dst_mac); - icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, (const void*)buf, plen, 255); - free_sized((void*)buf, plen); + icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, buf, plen, 255); } static void ndp_send_rs_on(uint8_t ifindex) { @@ -527,61 +799,134 @@ static void ndp_send_rs_on(uint8_t ifindex) { uint32_t reserved; } icmpv6_rs_t; - uint32_t plen = (uint32_t)(sizeof(icmpv6_rs_t) + sizeof(icmpv6_opt_lladdr_t)); - uintptr_t buf = (uintptr_t)malloc(plen); - if (!buf) return; + const uint8_t* mac = ipv6_is_unspecified(src_ip) ? NULL : network_get_mac(ifindex); + uint32_t plen = (uint32_t)sizeof(icmpv6_rs_t) + (mac ? (uint32_t)sizeof(icmpv6_opt_lladdr_t) : 0u); + uint8_t buf[sizeof(icmpv6_rs_t) + sizeof(icmpv6_opt_lladdr_t)] = {0}; icmpv6_rs_t* rs = (icmpv6_rs_t*)buf; rs->hdr.type = 133; rs->hdr.code = 0; rs->hdr.checksum = 0; - rs->reserved =0; - - icmpv6_opt_lladdr_t* opt = (icmpv6_opt_lladdr_t*)(buf + sizeof(icmpv6_rs_t)); - opt->type = 1; - opt->length = 1; + rs->reserved = 0; - const uint8_t* mac = network_get_mac(ifindex); - if (mac) memcpy(opt->mac, mac, 6); - else memset(opt->mac, 0, 6); + if (mac) { + icmpv6_opt_lladdr_t* opt = (icmpv6_opt_lladdr_t*)(buf + sizeof(icmpv6_rs_t)); + opt->type = 1; + opt->length = 1; + mac_copy(opt->mac, mac); + } - rs->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, 58, (const uint8_t*)buf, plen)); + rs->hdr.checksum = bswap16(checksum16_pipv6(src_ip, dst_ip, PROTO_ICMPV6, buf, plen)); uint8_t dst_mac[6]; ipv6_multicast_mac(dst_ip, dst_mac); - icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, (const void*)buf, plen, 255); - free_sized((void*)buf, plen); + icmpv6_send_on_l2(ifindex, dst_ip, src_ip, dst_mac, buf, plen, 255); } static void ndp_send_probe(uint8_t ifindex, ndp_entry_t* e) { uint8_t src_ip[16] = {0}; l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (l2) { + if (e && e->pending && e->pending_len && e->pending[0] && netpkt_len(e->pending[0]) >= sizeof(ipv6_hdr_t)) { + ipv6_hdr_t ip6; + if (netpkt_copyout(e->pending[0], 0, &ip6, sizeof(ip6)) && (bswap32(ip6.ver_tc_fl) >> 28) == 6 && !ipv6_is_unspecified(ip6.src) && !ipv6_is_multicast(ip6.src)) ipv6_cpy(src_ip, ip6.src); + } + + if (l2 && ipv6_is_unspecified(src_ip)) { + int best = -1; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (v6->dad_state!= IPV6_DAD_OK) continue; + if (!ipv6_l3_is_ready(v6)) continue; - if (ipv6_is_linklocal(v6->ip)) { - memcpy(src_ip, v6->ip, 16); + if (ipv6_is_linklocal(e->ip)) { + if (!ipv6_is_linklocal(v6->ip)) continue; + ipv6_cpy(src_ip, v6->ip); break; } - if (ipv6_is_unspecified(src_ip) && !ipv6_is_unspecified(v6->ip)) - memcpy(src_ip, v6->ip, 16); + if (ipv6_is_linklocal(v6->ip)) continue; + int common = ipv6_common_prefix_len(v6->ip, e->ip); + if (common <= best) continue; + best = common; + ipv6_cpy(src_ip, v6->ip); } } - ndp_send_ns_on(ifindex, e->ip, src_ip); + ndp_send_ns_on(ifindex, e->ip, src_ip, e->state == NDP_STATE_PROBE ? e->mac : NULL); +} + +void ndp_note_default_router(uint8_t ifindex, const uint8_t router[16]) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t || !router) return; + + int selected = -1; + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + if (!t->routers[i].used || ipv6_cmp(t->routers[i].ip, router) != 0) continue; + selected = i; + break; + } + if (selected < 0) return; + + int8_t selected_pref = t->routers[selected].preference; + uint32_t now = (uint32_t)get_time(); + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + ndp_default_router_t* r = &t->routers[i]; + if (!r->used || !r->lifetime_ms || !r->failed || r->preference <= selected_pref) continue; + if (r->last_probe_ms && now - r->last_probe_ms < NDP_ROUTER_REPROBE_INTERVAL_MS) continue; + + int idx = ndp_find_slot(t, r->ip); + if (idx < 0) idx = ndp_find_free(t); + if (idx < 0) idx = ndp_find_replacement(t); + if (idx < 0) continue; + + ndp_entry_t* e = &t->entries[idx]; + bool existing = e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, r->ip) == 0; + if (existing && e->static_entry) continue; + if (!existing) { + if (e->state != NDP_STATE_UNUSED) ndp_entry_clear(e); + ipv6_cpy(e->ip, r->ip); + e->state = NDP_STATE_INCOMPLETE; + mac_clear(e->mac); + } else if (e->state != NDP_STATE_INCOMPLETE && mac_is_unicast(e->mac)) e->state = NDP_STATE_PROBE; + else e->state = NDP_STATE_INCOMPLETE; + + r->last_probe_ms = now ? now : 1; + e->is_router = 1; + e->ttl_ms = t->reachable_time_ms*4; + e->probes_sent = NDP_MAX_PROBES; + e->timer_ms = t->retrans_timer_ms; + ndp_send_probe(ifindex, e); + ndp_daemon_kick(); + } } static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { - ndp_table_impl_t* t = l2_ndp(ifindex); + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; if (!t) return; + if (t->reachable_recalc_ms <= ms) { + uint32_t low = t->base_reachable_time_ms / 2; + uint32_t high = t->base_reachable_time_ms + t->base_reachable_time_ms / 2; + t->reachable_time_ms = rng_between32(&g_rng, low, high+1); + if (!t->reachable_time_ms) t->reachable_time_ms = 1; + t->reachable_recalc_ms = NDP_REACHABLE_RECALC_MS; + } else t->reachable_recalc_ms -= ms; + + bool routers_changed = false; + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + ndp_default_router_t* r = &t->routers[i]; + if (!r->used) continue; + if (r->lifetime_ms <= ms) { + ipv6_redirect_invalidate(ifindex, r->ip); + memset(r, 0, sizeof(*r)); + routers_changed = true; + } else r->lifetime_ms -= ms; + } + if (routers_changed) ndp_sync_default_routes(ifindex, t); + for (int i = 0; i < NDP_TABLE_MAX; i++) { ndp_entry_t* e =&t->entries[i]; @@ -590,6 +935,7 @@ static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { continue; } + if (e->static_entry) continue; if (e->ttl_ms <= ms) { ndp_entry_clear(e); continue; @@ -597,15 +943,6 @@ static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { e->ttl_ms -= ms; - if (e->is_router && e->router_lifetime_ms) { - if (e->router_lifetime_ms <= ms) { - e->is_router = 0; - e->router_lifetime_ms = 0; - } else { - e->router_lifetime_ms -= ms; - } - } - if (e->timer_ms) { if (e->timer_ms <= ms)e->timer_ms = 0; else e->timer_ms -= ms; @@ -614,12 +951,12 @@ static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { switch (e->state) { case NDP_STATE_INCOMPLETE: if (e->timer_ms == 0) { - if (e->probes_sent < g_ndp_max_probes) { + if (e->probes_sent < NDP_MAX_PROBES) { e->probes_sent++; - e->timer_ms = g_ndp_retrans_timer_ms; + e->timer_ms = t->retrans_timer_ms; ndp_send_probe(ifindex, e); } else { - ndp_entry_clear(e); + ndp_neighbor_failed(ifindex, t, e); } } break; @@ -632,18 +969,20 @@ static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { if (e->timer_ms == 0) { e->state = NDP_STATE_PROBE; e->probes_sent = 0; - e->timer_ms = g_ndp_retrans_timer_ms; + e->timer_ms = t->retrans_timer_ms; ndp_send_probe(ifindex, e); } break; case NDP_STATE_PROBE: if (e->timer_ms == 0) { - if (e->probes_sent < g_ndp_max_probes) { + if (e->probes_sent < NDP_MAX_PROBES) { e->probes_sent++; - e->timer_ms = g_ndp_retrans_timer_ms; + e->timer_ms = t->retrans_timer_ms; ndp_send_probe(ifindex, e); - } else ndp_entry_clear(e); + } else { + ndp_neighbor_failed(ifindex, t, e); + } } break; @@ -653,89 +992,84 @@ static void ndp_table_tick_for_l2(uint8_t ifindex, uint32_t ms) { } } -static void ndp_tick_all(uint32_t ms) { - uint8_t n = l2_interface_count(); - - for (uint8_t i = 0; i < n; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - if (!l2->is_up) continue; - - ndp_table_tick_for_l2(l2->ifindex, ms); +bool ndp_send_or_queue_on(uint8_t ifindex, const uint8_t next_hop[16], netpkt_t* pkt) { + if (!next_hop || !pkt || !netpkt_len(pkt)) { + if (pkt) netpkt_unref(pkt); + return false; } -} -bool ndp_resolve_on(uint16_t ifindex, const uint8_t next_hop[16], uint8_t out_mac[6], uint32_t timeout_ms) { + uint8_t out_mac[6]; if (ipv6_is_multicast(next_hop)) { ipv6_multicast_mac(next_hop, out_mac); - return true; + return eth_send_frame_on(ifindex, ETHERTYPE_IPV6, out_mac, pkt); } - if (ndp_table_get_for_l2((uint8_t)ifindex, next_hop, out_mac)) return true; + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t) { + netpkt_unref(pkt); + return false; + } - ndp_table_impl_t* t = l2_ndp((uint8_t)ifindex); - if (t) { - int idx = ndp_find_slot(t, next_hop); - if (idx >= 0) { - ndp_entry_t* e = &t->entries[idx]; - if (e->ttl_ms && e->is_router && e->state != NDP_STATE_UNUSED && e->state != NDP_STATE_INCOMPLETE) { - memcpy(out_mac, e->mac, 6); - return true; + int idx = ndp_find_slot(t, next_hop); + if (idx >= 0) { + ndp_entry_t* e = &t->entries[idx]; + if (e->state != NDP_STATE_UNUSED && e->state != NDP_STATE_INCOMPLETE) { + mac_copy(out_mac, e->mac); + if (e->state == NDP_STATE_STALE) { + e->state = NDP_STATE_DELAY; + e->timer_ms = NDP_DELAY_FIRST_PROBE_TIME_MS; + ndp_daemon_kick(); } + return eth_send_frame_on(ifindex, ETHERTYPE_IPV6, out_mac, pkt); } } + if (idx < 0) idx = ndp_find_free(t); + if (idx < 0) idx = ndp_find_replacement(t); + if (idx < 0) { + netpkt_unref(pkt); + return false; + } - uint8_t src_ip[16] = {0}; - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); - - if (l2) { - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (v6->dad_state != IPV6_DAD_OK) continue; - - if (ipv6_is_linklocal(v6->ip)) { - memcpy(src_ip, v6->ip, 16); - break; - } + ndp_entry_t* e = &t->entries[idx]; + if (e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, next_hop) != 0) ndp_entry_clear(e); - if (ipv6_is_unspecified(src_ip) && !ipv6_is_unspecified(v6->ip)) - memcpy(src_ip, v6->ip, 16); - } + uint32_t len = netpkt_len(pkt); + if (e->pending_len >= NDP_PENDING_MAX || e->pending_bytes + len > NDP_PENDING_MAX_BYTES) { + netpkt_unref(pkt); + return false; } - t = l2_ndp((uint8_t)ifindex); - if (t) { - int idx = ndp_find_slot(t, next_hop); - if (idx < 0) idx = ndp_find_free(t); - - if (idx >= 0) { - ndp_entry_t* e = &t->entries[idx]; - memcpy(e->ip, next_hop, 16); - memset(e->mac, 0, 6); - e->ttl_ms = g_ndp_reachable_time_ms * 4; - e->is_router = 0; - e->router_lifetime_ms = 0; - e->state = NDP_STATE_INCOMPLETE; - e->timer_ms = g_ndp_retrans_timer_ms; - e->probes_sent = 0; + if (!e->pending) { + e->pending = (netpkt_t**)zalloc(sizeof(netpkt_t*) * NDP_PENDING_MAX); + if (!e->pending) { + netpkt_unref(pkt); + return false; } } - ndp_send_ns_on((uint8_t)ifindex, next_hop, src_ip); - - uint32_t waited = 0; - const uint32_t poll = 50; - - while (waited < timeout_ms) { - ndp_table_tick_for_l2((uint8_t)ifindex, poll); - if (ndp_table_get_for_l2((uint8_t)ifindex, next_hop, out_mac)) return true; - msleep(poll); - waited += poll; + ipv6_cpy(e->ip, next_hop); + mac_clear(e->mac); + e->ttl_ms = t->reachable_time_ms * 4; + e->is_router = ipv6_redirect_is_router(ifindex, next_hop) ? 1 : 0; + for (int r = 0; !e->is_router && r < NDP_DEFAULT_ROUTER_MAX; r++) { + if (!t->routers[r].used || ipv6_cmp(t->routers[r].ip, next_hop) != 0) continue; + e->is_router = 1; + break; } + e->state = NDP_STATE_INCOMPLETE; + e->pending[e->pending_len] = pkt; + e->pending_len++; + e->pending_bytes += len; + + if (!e->probes_sent || !e->timer_ms) { + e->timer_ms = t->retrans_timer_ms; + e->probes_sent++; + ndp_send_probe(ifindex, e); + } + ndp_daemon_kick(); - return false; + return true; } bool ndp_request_dad_on(uint8_t ifindex, const uint8_t ip[16]) { @@ -756,10 +1090,7 @@ bool ndp_request_dad_on(uint8_t ifindex, const uint8_t ip[16]) { v6->dad_timer_ms = 0; v6->dad_probes_sent = 0; v6->dad_requested = 1; - - uint8_t sn[16]; - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, v6->ip, sn); - (void)l2_ipv6_mcast_join(ifindex, sn); + ndp_daemon_kick(); return true; } @@ -767,29 +1098,50 @@ bool ndp_request_dad_on(uint8_t ifindex, const uint8_t ip[16]) { return false; } -void ndp_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t src_mac[6], const uint8_t* icmp, uint32_t icmp_len) { - if (!ifindex || !src_ip || !dst_ip || !icmp || icmp_len < sizeof(icmpv6_hdr_t)) return; +void ndp_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t src_mac[6], netpkt_t* pkt) { + if (!ifindex || ifindex > MAX_L2_INTERFACES || !src_ip || !dst_ip || !pkt || netpkt_len(pkt) < sizeof(icmpv6_hdr_t)) return; - const icmpv6_hdr_t* h = (const icmpv6_hdr_t*)icmp; + uint8_t ifx = ifindex; + uint32_t icmp_len = netpkt_len(pkt); + icmpv6_hdr_t hdr; + if (!netpkt_copyout(pkt, 0, &hdr, sizeof(hdr))) return; + const icmpv6_hdr_t* h = &hdr; if (h->code != 0) return; - if (h->type == 135) { + if (h->type == ICMPV6_NEIGHBOR_SOLICIT) { if (icmp_len < sizeof(icmpv6_ns_t)) return; - const icmpv6_ns_t* ns = (const icmpv6_ns_t*)icmp; - if (ipv6_is_multicast(ns->target)) return; + icmpv6_ns_t ns; + if (!netpkt_copyout(pkt, 0, &ns, sizeof(ns))) return; + if (ipv6_is_multicast(ns.target)) return; + + uint8_t slla[6]; + bool has_slla = false; + uint32_t opt_off = (uint32_t)sizeof(icmpv6_ns_t); + uint32_t opt_len = icmp_len - opt_off; + if (!ndp_read_lladdr_option(pkt, opt_off, opt_len, NDP_OPT_SOURCE_LLADDR, slla, &has_slla)) return; + + bool dad = ipv6_is_unspecified(src_ip); + if (dad) { + uint8_t solicited_node[16]; + ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, ns.target, solicited_node); + if (ipv6_cmp(dst_ip, solicited_node) != 0 || has_slla) return; + } + if (has_slla && !mac_is_unicast(slla)) has_slla = false; - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); + l2_interface_t* l2 = l2_interface_find_by_index(ifx); if (!l2) return; + if (dad && src_mac) { + const uint8_t* local_mac = network_get_mac(ifx); + if (local_mac && mac_equal(src_mac, local_mac)) return; + } - l3_ipv6_interface_t* self = 0; + l3_ipv6_interface_t* self = NULL; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - - if (ipv6_cmp(v6->ip, ns->target) == 0) { + if (!v6 || v6->cfg == IPV6_CFG_DISABLE) continue; + if (ipv6_cmp(v6->ip, ns.target) == 0) { self = v6; break; } @@ -797,8 +1149,8 @@ void ndp_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[ if (!self) return; - if (ipv6_is_unspecified(src_ip)) { - if (self->dad_state == IPV6_DAD_IN_PROGRESS || self->dad_requested) { + if (self->dad_state != IPV6_DAD_OK) { + if (dad && (self->dad_state == IPV6_DAD_IN_PROGRESS || self->dad_requested)) { self->dad_state = IPV6_DAD_FAILED; self->dad_timer_ms = 0; self->dad_probes_sent = 0; @@ -807,335 +1159,486 @@ void ndp_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[ return; } - if (self->dad_state != IPV6_DAD_OK) return; - - ndp_table_put_for_l2((uint8_t)ifindex, src_ip, src_mac, 180000, false); + const uint8_t* my_mac = network_get_mac(ifx); + if (!my_mac) return; - uint8_t src_my[16] = {0}; + if (dad) { + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + (void)ndp_send_na_on(ifx, all_nodes, self->ip, ns.target, NULL, my_mac, 0); + return; + } + bool src_is_local = false; for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (v6->dad_state != IPV6_DAD_OK) continue; - - if (ipv6_cmp(v6->ip, ns->target) == 0) { - ipv6_cpy(src_my, v6->ip); + if (!v6 || v6->cfg == IPV6_CFG_DISABLE) continue; + if (ipv6_cmp(v6->ip, src_ip) == 0) { + src_is_local = true; break; } } - if (ipv6_is_unspecified(src_my)) return; - - const uint8_t* my_mac = network_get_mac((uint8_t)ifindex); - if (!my_mac) return; + if (!src_is_local && has_slla) { + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; + if (t) { + int idx = ndp_find_slot(t, src_ip); + if (idx < 0) idx = ndp_find_free(t); + if (idx < 0) idx = ndp_find_replacement(t); + + if (idx >= 0) { + ndp_entry_t* e = &t->entries[idx]; + if (!(e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, src_ip) == 0 && e->static_entry)) { + if (e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, src_ip) != 0) ndp_entry_clear(e); + ipv6_cpy(e->ip, src_ip); + if (!mac_equal(e->mac, slla)) { + mac_copy(e->mac, slla); + e->state = NDP_STATE_STALE; + e->timer_ms = 0; + } else if (e->state == NDP_STATE_UNUSED || e->state == NDP_STATE_INCOMPLETE) { + e->state = NDP_STATE_STALE; + e->timer_ms = 0; + } + e->ttl_ms = t->reachable_time_ms * 4; + e->probes_sent = 0; + ndp_flush_pending(ifx, e); + } + } + } + } - ndp_send_na_on((uint8_t)ifindex, src_ip, src_my, ns->target, src_mac, my_mac, 1); + const uint8_t* reply_mac = has_slla ? slla : src_mac; + (void)ndp_send_na_on(ifx, src_ip, self->ip, ns.target, reply_mac, my_mac, 1); return; } - if (h->type == 136) { + if (h->type == ICMPV6_NEIGHBOR_ADVERT) { if (icmp_len < sizeof(icmpv6_na_t)) return; - const icmpv6_na_t* na = (const icmpv6_na_t*)icmp; - if (ipv6_is_multicast(na->target)) return; + icmpv6_na_t na; + if (!netpkt_copyout(pkt, 0, &na, sizeof(na))) return; + if (ipv6_is_multicast(na.target)) return; - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); + uint32_t f = bswap32(na.flags); + bool router = ((f >> 31) & 1u) != 0; + bool solicited = ((f >> 30) & 1u) != 0; + bool override = ((f >> 29) & 1u) != 0; + if (solicited && ipv6_is_multicast(dst_ip)) return; - if (l2) { - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (ipv6_cmp(v6->ip, na->target) != 0) continue; + uint8_t tlla[6]; + bool has_tlla = false; + uint32_t opt_off = (uint32_t)sizeof(icmpv6_na_t); + uint32_t opt_len = icmp_len - opt_off; + if (!ndp_read_lladdr_option(pkt, opt_off, opt_len, NDP_OPT_TARGET_LLADDR, tlla, &has_tlla)) return; + if (has_tlla && !mac_is_unicast(tlla)) has_tlla = false; - if (v6->dad_state == IPV6_DAD_IN_PROGRESS || v6->dad_requested) { - v6->dad_state = IPV6_DAD_FAILED; - v6->dad_requested = 0; - v6->dad_timer_ms = 0; - v6->dad_probes_sent = 0; - return; - } + l2_interface_t* l2 = l2_interface_find_by_index(ifx); + if (!l2) return; + + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6) continue; + if (ipv6_cmp(v6->ip, na.target) != 0) continue; + + if (v6->dad_state == IPV6_DAD_IN_PROGRESS || v6->dad_requested) { + v6->dad_state = IPV6_DAD_FAILED; + v6->dad_requested = 0; + v6->dad_timer_ms = 0; + v6->dad_probes_sent = 0; + return; } } if (ipv6_is_unspecified(src_ip)) return; + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6) continue; + if (v6->cfg == IPV6_CFG_DISABLE) continue; + if (ipv6_cmp(v6->ip, src_ip) == 0 || ipv6_cmp(v6->ip, na.target) == 0) return; + } - uint32_t f = bswap32(na->flags); - uint8_t router = (uint8_t)((f >> 31) & 1u); - uint8_t solicited = (uint8_t)((f >> 30) & 1u); - uint8_t override = (uint8_t)((f >> 29) & 1u); - - ndp_table_impl_t* t = l2_ndp((uint8_t)ifindex); + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; if (!t) return; - int idx = ndp_find_slot(t, na->target); - if (idx < 0) idx = ndp_find_free(t); + int idx = ndp_find_slot(t, na.target); + if (idx < 0) return; - if (idx < 0) { - uint32_t best_ttl = 0xFFFFFFFFu; - int best_i = -1; - - for (int i = 0; i < NDP_TABLE_MAX; i++) { - ndp_entry_t* e = &t->entries[i]; - if (e->state == NDP_STATE_UNUSED || e->ttl_ms == 0) { - best_i = i; - break; - } - - if (e->is_router && e->router_lifetime_ms) continue; + ndp_entry_t* e = &t->entries[idx]; + bool was_router = e->is_router != 0; - if (e->ttl_ms < best_ttl) { - best_ttl = e->ttl_ms; - best_i = i; + if (e->static_entry) { + e->is_router = router ? 1 : 0; + } else if (e->state == NDP_STATE_INCOMPLETE) { + if (!has_tlla) return; + mac_copy(e->mac, tlla); + e->ttl_ms = t->reachable_time_ms * 4; + e->is_router = router; + e->probes_sent = 0; + ndp_mark_neighbor_observed(t, e, solicited); + ndp_flush_pending(ifx, e); + } else { + bool mac_changed = has_tlla && !mac_equal(e->mac, tlla); + if (!override && mac_changed) { + if (e->state == NDP_STATE_REACHABLE) { + e->state = NDP_STATE_STALE; + e->timer_ms = 0; } + return; } - if (best_i < 0) best_i = 0; - idx = best_i; - } - - ndp_entry_t* e = &t->entries[idx]; + if (mac_changed) { + mac_copy(e->mac, tlla); + e->ttl_ms = t->reachable_time_ms * 4; + } - uint8_t old_mac[6]; - memcpy(old_mac, e->mac, 6); + if (solicited) ndp_mark_neighbor_observed(t, e, true); + else if (mac_changed) ndp_mark_neighbor_observed(t, e, false); - if (e->ttl_ms == 0 && e->state == NDP_STATE_UNUSED) { - memcpy(e->ip, na->target, 16); - memcpy(e->mac, src_mac, 6); - e->ttl_ms = g_ndp_reachable_time_ms * 4; + e->is_router = router; e->probes_sent = 0; - e->is_router = router ? 1 : 0; - e->router_lifetime_ms = e->is_router ? e->ttl_ms : 0; + } - if (solicited) { - e->state = NDP_STATE_REACHABLE; - e->timer_ms = g_ndp_reachable_time_ms; - } else { - e->state = NDP_STATE_STALE; - e->timer_ms = 0; - } - } else { - int mac_changed = memcmp(old_mac, src_mac, 6) != 0; + if (was_router && !router) ipv6_redirect_invalidate_router(ifx, na.target); - if (e->state == NDP_STATE_INCOMPLETE) { - memcpy(e->mac, src_mac, 6); - e->ttl_ms = g_ndp_reachable_time_ms * 4; + bool routers_changed = false; + for (int r = 0; r < NDP_DEFAULT_ROUTER_MAX; r++) { + ndp_default_router_t* def = &t->routers[r]; + if (!def->used || ipv6_cmp(def->ip, na.target) != 0) continue; - if (solicited) { - e->state = NDP_STATE_REACHABLE; - e->timer_ms = g_ndp_reachable_time_ms; - } else { - e->state = NDP_STATE_STALE; - e->timer_ms = 0; - } - } else { - if (!mac_changed) { - if (solicited) { - e->state = NDP_STATE_REACHABLE; - e->timer_ms = g_ndp_reachable_time_ms; - } - } else { - if (override) { - memcpy(e->mac, src_mac, 6); - e->ttl_ms = g_ndp_reachable_time_ms * 4; - - if (solicited) { - e->state = NDP_STATE_REACHABLE; - e->timer_ms = g_ndp_reachable_time_ms; - } else { - e->state = NDP_STATE_STALE; - e->timer_ms = 0; - } - } else { - e->state = NDP_STATE_STALE; - e->timer_ms = 0; - } - } + if (router && solicited && def->failed) { + def->failed = 0; + routers_changed = true; + } else if (was_router && !router) { + memset(def, 0, sizeof(*def)); + routers_changed = true; } + break; + } + if (routers_changed) ndp_sync_default_routes(ifx, t); + return; + } - if (router) e->is_router = 1; - if (!e->is_router) e->router_lifetime_ms = 0; - if (e->is_router && !e->router_lifetime_ms) e->router_lifetime_ms = e->ttl_ms; + if (h->type == ICMPV6_REDIRECT) { + if (icmp_len < sizeof(icmpv6_redirect_t) || !ipv6_is_linklocal(src_ip)) return; + + icmpv6_redirect_t redirect; + if (!netpkt_copyout(pkt, 0, &redirect, sizeof(redirect))) return; + if (ipv6_is_unspecified(redirect.destination) || ipv6_is_loopback(redirect.destination) || ipv6_is_multicast(redirect.destination)) return; + if (ipv6_is_unspecified(redirect.target) || ipv6_is_multicast(redirect.target)) return; + if (!ipv6_is_linklocal(redirect.target) && ipv6_cmp(redirect.target, redirect.destination) != 0) return; + + l3_ipv6_interface_t* local = l3_ipv6_find_by_ip(dst_ip); + if (!ipv6_l3_is_ready(local) || local->l2->ifindex != ifx) return; + l2_interface_t* l2 = local->l2; + + uint8_t tlla[6]; + bool has_tlla = false; + uint32_t opt_off = (uint32_t)sizeof(icmpv6_redirect_t); + uint32_t opt_len = icmp_len - opt_off; + + if (!ndp_read_redirect_options(pkt, opt_off, opt_len, local->ip, redirect.destination, tlla, &has_tlla)) return; + if (!ipv6_redirect_update(local->l3_id, src_ip, redirect.destination, redirect.target)) return; + + bool target_router = ipv6_cmp(redirect.target, redirect.destination) != 0; + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; + if (!t) return; + int idx = ndp_find_slot(t, redirect.target); + if (!has_tlla) { + if (idx >= 0 && target_router) t->entries[idx].is_router = 1; + return; } + if (idx < 0) idx = ndp_find_free(t); + if (idx < 0) idx = ndp_find_replacement(t); + if (idx < 0) return; + ndp_entry_t* e = &t->entries[idx]; + bool existing = e->state != NDP_STATE_UNUSED && ipv6_cmp(e->ip, redirect.target) == 0; + if (existing && e->static_entry) { + if (target_router) e->is_router = 1; + return; + } + if (!existing) { + if (e->state != NDP_STATE_UNUSED) ndp_entry_clear(e); + ipv6_cpy(e->ip, redirect.target); + } + + if (target_router) e->is_router = 1; + if (!existing || !mac_equal(e->mac, tlla)) { + mac_copy(e->mac, tlla); + ndp_mark_neighbor_observed(t, e, false); + } + e->ttl_ms = t->reachable_time_ms*4; e->probes_sent = 0; + ndp_flush_pending(ifx, e); + ndp_daemon_kick(); return; } - if (h->type == 134) { + if (h->type == ICMPV6_ROUTER_ADVERT) { if (icmp_len < sizeof(icmpv6_ra_t)) return; + if (!ipv6_is_linklocal(src_ip)) return; - const icmpv6_ra_t* ra = (const icmpv6_ra_t*)icmp; + icmpv6_ra_t ra; + if (!netpkt_copyout(pkt, 0, &ra, sizeof(ra))) return; - uint16_t router_lifetime = bswap16(ra->router_lifetime); - uint32_t reachable_time = bswap32(ra->reachable_time); - uint32_t retrans_timer = bswap32(ra->retrans_timer); + uint16_t router_lifetime = bswap16(ra.router_lifetime); + uint32_t reachable_time = bswap32(ra.reachable_time); + uint32_t retrans_timer = bswap32(ra.retrans_timer); - uint32_t router_lifetime_ms = (uint32_t)router_lifetime * 1000u; + uint32_t opt_off = (uint32_t)sizeof(icmpv6_ra_t); + uint32_t opt_len = icmp_len - (uint32_t)sizeof(icmpv6_ra_t); + uint8_t slla[6]; + bool has_slla = false; + if (!ndp_read_lladdr_option(pkt, opt_off, opt_len, NDP_OPT_SOURCE_LLADDR, slla, &has_slla)) return; + if (has_slla && !mac_is_unicast(slla)) has_slla = false; - if (router_lifetime == 0) ndp_table_put_for_l2((uint8_t)ifindex, src_ip, src_mac, 180000, false); - else ndp_table_put_for_l2((uint8_t)ifindex, src_ip, src_mac, router_lifetime_ms, true); + l2_interface_t* l2 = l2_interface_find_by_index(ifx); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t) return; - if (reachable_time) g_ndp_reachable_time_ms = reachable_time; - if (retrans_timer) g_ndp_retrans_timer_ms = retrans_timer; + if (ra.cur_hop_limit) l2->ipv6_default_hop_limit = ra.cur_hop_limit; + + int neighbor = ndp_find_slot(t, src_ip); + if (neighbor < 0 && has_slla) neighbor = ndp_find_free(t); + if (neighbor < 0 && has_slla) neighbor = ndp_find_replacement(t); + if (neighbor >= 0) { + ndp_entry_t* e = &t->entries[neighbor]; + bool new_entry = e->state == NDP_STATE_UNUSED || ipv6_cmp(e->ip, src_ip) != 0; + if (!new_entry && e->static_entry) e->is_router = 1; + else { + if (new_entry) { + if (e->state != NDP_STATE_UNUSED) ndp_entry_clear(e); + ipv6_cpy(e->ip, src_ip); + } + if (has_slla) { + bool changed = new_entry || e->state == NDP_STATE_INCOMPLETE || !mac_equal(e->mac, slla); + mac_copy(e->mac, slla); + e->ttl_ms = t->reachable_time_ms * 4; + if (changed) { + e->state = NDP_STATE_STALE; + e->timer_ms = 0; + } + ndp_flush_pending(ifx, e); + } + e->is_router = 1; + e->probes_sent = 0; + } + } - const uint8_t* opt = (const uint8_t*)(ra + 1); - uint32_t opt_len = icmp_len - (uint32_t)sizeof(icmpv6_ra_t); + int router_slot = -1; + int free_router = -1; + int replacement = -1; + int8_t lowest_pref = 2; + uint32_t lowest_lifetime = UINT32_MAX; - uint8_t idx = (uint8_t)(ifindex - 1); + for (int i = 0; i < NDP_DEFAULT_ROUTER_MAX; i++) { + ndp_default_router_t* r = &t->routers[i]; + if (!r->used) { + if (free_router < 0) free_router = i; + continue; + } + if (ipv6_cmp(r->ip, src_ip) == 0) { + router_slot = i; + break; + } + if (r->preference < lowest_pref || + (r->preference == lowest_pref && r->lifetime_ms < lowest_lifetime)) { + lowest_pref = r->preference; + lowest_lifetime = r->lifetime_ms; + replacement = i; + } + } - if (idx < MAX_L2_INTERFACES) { - g_rs_tries[idx] = 3; - g_rs_timer_ms[idx] = 0; + if (!router_lifetime) { + if (router_slot >= 0) { + ipv6_redirect_invalidate(ifx, src_ip); + memset(&t->routers[router_slot], 0, sizeof(t->routers[router_slot])); + } + } else { + if (router_slot < 0) router_slot = free_router >= 0 ? free_router : replacement; + if (router_slot >= 0) { + ndp_default_router_t* r = &t->routers[router_slot]; + if (r->used && ipv6_cmp(r->ip, src_ip) != 0) ipv6_redirect_invalidate(ifx, r->ip); + memset(r, 0, sizeof(*r)); + r->used = 1; + ipv6_cpy(r->ip, src_ip); + r->lifetime_ms = (uint32_t)router_lifetime * 1000; + + switch ((ra.flags >> 3) & 0x03) { + case 1: + r->preference = 1; + break; + case 3: + r->preference = -1; + break; + default: + break; + } + } } + ndp_sync_default_routes(ifx, t); + + if (reachable_time && reachable_time <= NDP_MAX_RA_REACHABLE_TIME_MS) { + if (t->base_reachable_time_ms != reachable_time) { + t->base_reachable_time_ms = reachable_time; + uint32_t low = reachable_time / 2; + uint32_t high = reachable_time + reachable_time / 2; + t->reachable_time_ms = rng_between32(&g_rng, low, high + 1); + if (!t->reachable_time_ms) t->reachable_time_ms = 1; + t->reachable_recalc_ms = NDP_REACHABLE_RECALC_MS; + } + } + if (retrans_timer) t->retrans_timer_ms = retrans_timer; + + t->rs_tries = 3; + t->rs_timer_ms = 0; while (opt_len >= 2) { - uint8_t opt_type = opt[0]; - uint8_t opt_units = opt[1]; - if (opt_units == 0) break; - - uint32_t opt_size = (uint32_t)opt_units * 8u; - if (opt_size > opt_len) break; - - if (opt_type == 3&&opt_size >= (uint32_t)sizeof(ndp_opt_prefix_info_t)) { - const ndp_opt_prefix_info_t* pio = (const ndp_opt_prefix_info_t*)opt; - - uint8_t pfx_len = pio->prefix_length; - uint8_t autonomous = (pio->flags & 0x40u) ? 1u : 0u; - uint32_t valid_lft = bswap32(pio->valid_lifetime); - uint32_t pref_lft = bswap32(pio->preferred_lifetime); - - uint8_t pfx[16]; - memcpy(pfx, pio->prefix, 16); - - if (pfx_len != 0) ndp_on_ra((uint8_t)ifindex, src_ip, router_lifetime, pfx, pfx_len, valid_lft, pref_lft, autonomous, ra->flags); - } else if (opt_type == 5 && opt_size >= (uint32_t)sizeof(ndp_opt_mtu_t)) { - uint32_t mtu32 = 0; - memcpy(&mtu32, opt + 4, 4); - mtu32= bswap32(mtu32); - - if (mtu32 >= 1280u && mtu32 <= 65535u) { - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); - if (l2) { - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (v6->mtu < 1280) continue; - v6->mtu = mtu32; - } + ndp_opt_t opt; + if (!ndp_read_opt(pkt, &opt_off, &opt_len, &opt)) break; + + if (opt.type == NDP_OPT_PREFIX_INFO && opt.size == sizeof(ndp_opt_prefix_info_t)) { + ndp_opt_prefix_info_t pio; + if (!netpkt_copyout(pkt, opt.off, &pio, sizeof(pio))) break; + + uint8_t pfx_len = pio.prefix_length; + bool onlink = (pio.flags & 0x80) != 0; + bool autonomous = (pio.flags & 0x40) != 0; + uint32_t valid_lft = bswap32(pio.valid_lifetime); + uint32_t pref_lft = bswap32(pio.preferred_lifetime); + + if (pfx_len <= 128) { + uint8_t pfx[16]; + ipv6_prefix_network(pio.prefix, pfx_len, pfx); + bool linklocal_prefix = pfx_len >= 10 && pfx[0] == 0xFE && (pfx[1] & 0xC0) == 0x80; + bool multicast_prefix = pfx_len >= 8 && pfx[0] == 0xFF; + if (!linklocal_prefix && !multicast_prefix) { + if (onlink) ipv6_rt_onlink_update(ifx, pfx, pfx_len, valid_lft); + if (pref_lft <= valid_lft && pfx_len) ndp_on_ra(ifx, pfx, pfx_len, valid_lft, pref_lft, autonomous, ra.flags); } } - } else if (opt_type == 25 && opt_size >= 24u) { - l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ifindex); - if (l2) { - uint32_t addr_bytes = opt_size - 8u; - uint32_t addr_count = addr_bytes / 16u; - - uint8_t zero16[16] = {0}; - - const uint8_t* a0 = (addr_count >= 1) ? (opt + 8) : zero16; - const uint8_t* a1 = (addr_count >= 2) ? (opt + 24) : zero16; - - l3_ipv6_interface_t* slot = NULL; - - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (!(v6->kind & IPV6_ADDRK_GLOBAL)) continue; - if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; - - if (memcmp(v6->prefix, zero16, 16) != 0) { - if (ipv6_common_prefix_len(v6->prefix, src_ip) >= 64) { - slot = v6; - break; - } - } else { - if (ipv6_is_placeholder_gua(v6->ip)) { - slot = v6; - break; - } - if (!ipv6_is_unspecified(v6->ip) && !ipv6_is_multicast(v6->ip) && !ipv6_is_linklocal(v6->ip)) { - if (ipv6_common_prefix_len(v6->ip, src_ip) >= 64) { - slot = v6; - break; - } - } - } - } - - if (!slot) { - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (!v6) continue; - if (v6->cfg == IPV6_CFG_DISABLE) continue; - if (!(v6->kind & IPV6_ADDRK_GLOBAL)) continue; - if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; - slot = v6; - break; - } + } else if (opt.type == NDP_OPT_MTU && opt.size == sizeof(ndp_opt_mtu_t)) { + uint32_t advertised_mtu = 0; + if (!netpkt_copyout(pkt, opt.off + 4, &advertised_mtu, sizeof(advertised_mtu))) break; + advertised_mtu = bswap32(advertised_mtu); + + if (advertised_mtu <= UINT16_MAX) l2_ipv6_set_link_mtu(ifx, (uint16_t)advertised_mtu); + } else if (opt.type == NDP_OPT_RDNSS && opt.size >= 24 && ((opt.size - 8) % 16) == 0) { + uint32_t addr_count = (opt.size - 8) / 16; + uint8_t dns0[16]; + uint8_t dns1[16] = {0}; + if (!netpkt_copyout(pkt, opt.off + 8, dns0, sizeof(dns0))) break; + if (addr_count > 1 && !netpkt_copyout(pkt, opt.off + 24, dns1, sizeof(dns1))) break; + + l3_ipv6_interface_t* dns_l3 = NULL; + l3_ipv6_interface_t* fallback = NULL; + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[i]; + if (!v6 || v6->cfg == IPV6_CFG_DISABLE) continue; + if (!(v6->kind & IPV6_ADDRK_GLOBAL)) continue; + if (!(v6->cfg & (IPV6_CFG_SLAAC | IPV6_CFG_DHCPV6))) continue; + if (!fallback) fallback = v6; + + if (!ipv6_is_unspecified(v6->prefix)) { + if (ipv6_common_prefix_len(v6->prefix, src_ip) < 64) continue; + } else if (!ipv6_is_placeholder_gua(v6->ip)) { + if (ipv6_is_unspecified(v6->ip) || ipv6_is_multicast(v6->ip) || ipv6_is_linklocal(v6->ip)) continue; + if (ipv6_common_prefix_len(v6->ip, src_ip) < 64) continue; } - if (slot) { - if (addr_count >= 1) memcpy(slot->runtime_opts_v6.dns[0], a0, 16); - else memset(slot->runtime_opts_v6.dns[0], 0, 16); + dns_l3 = v6; + break; + } + if (!dns_l3) dns_l3 = fallback; - if (addr_count >= 2) memcpy(slot->runtime_opts_v6.dns[1], a1, 16); - else memset(slot->runtime_opts_v6.dns[1], 0, 16); - } + if (dns_l3) { + ipv6_cpy(dns_l3->runtime_opts_v6.dns[0], dns0); + if (addr_count > 1) ipv6_cpy(dns_l3->runtime_opts_v6.dns[1], dns1); + else memset(dns_l3->runtime_opts_v6.dns[1], 0, 16); } } - - opt += opt_size; - opt_len -= opt_size; } + ndp_daemon_kick(); return; } } -int ndp_daemon_entry(int argc, char* argv[]) { +static bool ndp_has_timed_work(void) { + uint8_t n = l2_interface_count(); + for (uint8_t i = 0; i < n; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2_interface_is_operational(l2) || !l2->nd_table) continue; + + ndp_table_impl_t* t = (ndp_table_impl_t*)l2->nd_table; + for (int r = 0; r < NDP_DEFAULT_ROUTER_MAX; r++) if (t->routers[r].used && t->routers[r].lifetime_ms) return true; + for (int e = 0; e < NDP_TABLE_MAX; e++) { + ndp_entry_t* entry = &t->entries[e]; + if (entry->state != NDP_STATE_UNUSED && !entry->static_entry && entry->ttl_ms) return true; + } + + bool has_lla_ok = false; + for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[s]; + if (!v6 || v6->cfg == IPV6_CFG_DISABLE) continue; + + if (v6->dad_requested || v6->dad_state == IPV6_DAD_IN_PROGRESS || v6->dad_state == IPV6_DAD_FAILED) return true; + if (v6->dad_state == IPV6_DAD_OK && ipv6_is_linklocal(v6->ip)) has_lla_ok = true; + + if (v6->ra_last_update_ms) { + if (v6->valid_lifetime != 0xFFFFFFFFu) return true; + if (v6->preferred_lifetime && v6->preferred_lifetime != 0xFFFFFFFFu) return true; + } + } + + if (t && has_lla_ok && t->rs_tries < 3) return true; + } + + return false; +} + +static int ndp_daemon_entry(int argc, char* argv[]) { (void)argc; (void)argv; - g_ndp_pid = (uint16_t)get_current_proc_pid(); - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&g_rng, virt_timer); + irq_flags_t irq = irq_save_disable(); + g_ndp_daemon_pending = 0; + g_ndp_daemon_running = 1; + irq_restore(irq); - const uint32_t tick_ms = 1000; + rng_init_random(&g_rng); + uint8_t init_n = l2_interface_count(); + for (uint8_t i = 0; i < init_n; i++) { + l2_interface_t* l2 = l2_interface_at(i); + ndp_table_impl_t* t = l2_interface_is_operational(l2) ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (t && t->base_reachable_time_ms) { + uint32_t low = t->base_reachable_time_ms / 2u; + uint32_t high = t->base_reachable_time_ms + t->base_reachable_time_ms / 2u; + t->reachable_time_ms = rng_between32(&g_rng, low, high + 1u); + if (!t->reachable_time_ms) t->reachable_time_ms = 1; + t->reachable_recalc_ms = NDP_REACHABLE_RECALC_MS; + } + } - while (1) { - ndp_tick_all(tick_ms); + const uint32_t tick_ms = 1000; + uint32_t last_ms = (uint32_t)get_time(); - uint32_t now_ms = get_time(); + while (ndp_has_timed_work()) { + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - last_ms; + last_ms = now_ms; uint8_t n = l2_interface_count(); for (uint8_t i = 0; i < n; i++) { l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; + if (!l2_interface_is_operational(l2) || !l2->nd_table) continue; - if (!l2->is_up) { - if (l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES) { - g_rs_tries[l2->ifindex - 1] = 0; - g_rs_timer_ms[l2->ifindex - 1] = 0; - } - continue; - } + if (elapsed_ms) ndp_table_tick_for_l2(l2->ifindex, elapsed_ms); - int is_v6_local = 0; - - for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { - l3_ipv6_interface_t* v6 = l2->l3_v6[i]; - if (v6 && v6->is_localhost) { - is_v6_local = 1; - break; - } - } - - if (!is_v6_local) apply_ra_policy(now_ms, l2); - int has_lla_ok = 0; + bool has_lla_ok = false; + bool lla_became_ready = false; + ndp_table_impl_t* ndp = (ndp_table_impl_t*)l2->nd_table; + uint32_t dad_retrans_ms = ndp ? ndp->retrans_timer_ms : NDP_DEFAULT_RETRANS_TIMER_MS; for (int s = 0; s < MAX_IPV6_PER_INTERFACE; s++) { l3_ipv6_interface_t* v6 = l2->l3_v6[s]; @@ -1148,81 +1651,119 @@ int ndp_daemon_entry(int argc, char* argv[]) { continue; } + bool dad_started_now = 0; + if (v6->dad_requested && v6->dad_state == IPV6_DAD_NONE) { - if (ipv6_is_unspecified(v6->ip) || ipv6_is_multicast(v6->ip) || ipv6_is_placeholder_gua(v6->ip)) { + if (ipv6_is_placeholder_gua(v6->ip)) { v6->dad_requested = 0; continue; } v6->dad_requested = 0; v6->dad_state = IPV6_DAD_IN_PROGRESS; - v6->dad_probes_sent = 0; + v6->dad_probes_sent = 1; v6->dad_timer_ms = 0; - - uint8_t sn[16]; - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, v6->ip, sn); - (void)l2_ipv6_mcast_join(l2->ifindex, sn); + ndp_send_ns_on(l2->ifindex, v6->ip, (const uint8_t[16]){0}, NULL); + dad_started_now = 1; } - if (v6->dad_state == IPV6_DAD_IN_PROGRESS) { - v6->dad_timer_ms += tick_ms; + if (v6->dad_state == IPV6_DAD_IN_PROGRESS && elapsed_ms && !dad_started_now) { + if (UINT32_MAX - v6->dad_timer_ms < elapsed_ms) v6->dad_timer_ms = UINT32_MAX; + else v6->dad_timer_ms += elapsed_ms; - if (v6->dad_probes_sent < g_ndp_max_probes) { - if (v6->dad_timer_ms >= 1000) { + if (v6->dad_probes_sent < NDP_MAX_PROBES) { + if (v6->dad_timer_ms >= dad_retrans_ms) { v6->dad_timer_ms = 0; - uint8_t sn[16]; - uint8_t zero16[16] = {0}; - - ipv6_make_multicast(2, IPV6_MCAST_SOLICITED_NODE, v6->ip, sn); - (void)l2_ipv6_mcast_join(l2->ifindex, sn); - - ndp_send_ns_on(l2->ifindex, v6->ip, zero16); + ndp_send_ns_on(l2->ifindex, v6->ip, (const uint8_t[16]){0}, NULL); v6->dad_probes_sent++; } - } else { - if (v6->dad_timer_ms >= 1000) { - v6->dad_timer_ms = 0; - v6->dad_state = IPV6_DAD_OK; - - uint8_t all_nodes[16]; - uint8_t zero16[16] = {0}; - ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, zero16, all_nodes); - - const uint8_t* my_mac = network_get_mac(l2->ifindex); - if (my_mac) (void)ndp_send_na_on(l2->ifindex, all_nodes, v6->ip, v6->ip, 0, my_mac, 0); - } + } else if (v6->dad_timer_ms >= dad_retrans_ms) { + v6->dad_timer_ms = 0; + v6->dad_state = IPV6_DAD_OK; + lla_became_ready |= ipv6_is_linklocal(v6->ip); + dns_daemon_kick(); + + uint8_t all_nodes[16]; + ipv6_make_multicast(2, IPV6_MCAST_ALL_NODES, NULL, all_nodes); + + const uint8_t* my_mac = network_get_mac(l2->ifindex); + if (my_mac) (void)ndp_send_na_on(l2->ifindex, all_nodes, v6->ip, v6->ip, 0, my_mac, 0); + if (ipv6_is_linklocal(v6->ip)) mld_resend_memberships(l2->ifindex); } } - if (v6->dad_state == IPV6_DAD_OK && ipv6_is_linklocal(v6->ip)) has_lla_ok = 1; + if (v6->dad_state == IPV6_DAD_OK && ipv6_is_linklocal(v6->ip)) has_lla_ok = true; handle_lifetimes(now_ms, v6); } - if (!has_lla_ok && l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES) { - g_rs_tries[l2->ifindex - 1] = 0; - g_rs_timer_ms[l2->ifindex - 1] = 0; - } - - if (has_lla_ok && l2->ifindex && l2->ifindex <= MAX_L2_INTERFACES) { - uint8_t idx = (uint8_t)(l2->ifindex - 1); - - if (g_rs_tries[idx] == 0) { + if (lla_became_ready) apply_ra_policy(now_ms, l2); + if (ndp) { + if (!has_lla_ok) { + ndp->rs_tries = 0; + ndp->rs_timer_ms = 0; + } else if (ndp->rs_tries == 0) { ndp_send_rs_on(l2->ifindex); - g_rs_tries[idx] = 1; - g_rs_timer_ms[idx] = 0; - } else if (g_rs_tries[idx] < 3) { - g_rs_timer_ms[idx] += tick_ms; - - if (g_rs_timer_ms[idx] >= 4000) { - g_rs_timer_ms[idx] = 0; + ndp->rs_tries = 1; + ndp->rs_timer_ms = 0; + } else if (ndp->rs_tries < 3 && elapsed_ms) { + if (UINT32_MAX - ndp->rs_timer_ms < elapsed_ms) ndp->rs_timer_ms = UINT32_MAX; + else ndp->rs_timer_ms += elapsed_ms; + + if (ndp->rs_timer_ms >= 4000) { + ndp->rs_timer_ms = 0; ndp_send_rs_on(l2->ifindex); - g_rs_tries[idx]++; + ndp->rs_tries++; } } } } + if (!ndp_has_timed_work()) break; msleep(tick_ms); } -} \ No newline at end of file + + irq = irq_save_disable(); + g_ndp_daemon_running = 0; + irq_restore(irq); + ndp_daemon_kick(); + return 0; +} + +void ndp_daemon_kick(void) { + irq_flags_t irq = irq_save_disable(); + if (g_ndp_daemon_running || g_ndp_daemon_pending) { + irq_restore(irq); + return; + } + irq_restore(irq); + + if (!ndp_has_timed_work()) return; + + irq = irq_save_disable(); + if (g_ndp_daemon_running || g_ndp_daemon_pending) { + irq_restore(irq); + return; + } + g_ndp_daemon_pending = 1; + irq_restore(irq); + + if (!create_kernel_process("ndp_daemon", ndp_daemon_entry, 0, 0)) { + irq = irq_save_disable(); + g_ndp_daemon_pending = 0; + irq_restore(irq); + } +} + +void ndp_link_state_changed(uint8_t ifindex, bool up) { + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + ndp_table_impl_t* t = l2 ? (ndp_table_impl_t*)l2->nd_table : NULL; + if (!t) return; + + t->rs_tries = 0; + t->rs_timer_ms = 0; + if (!up) { + ipv6_redirect_invalidate(ifindex, NULL); + ipv6_rt_onlink_clear(ifindex); + } else ndp_daemon_kick(); +} diff --git a/kernel/networking/link_layer/ndp.h b/kernel/networking/link_layer/ndp.h index 85b3e5ee..b1c33780 100644 --- a/kernel/networking/link_layer/ndp.h +++ b/kernel/networking/link_layer/ndp.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { @@ -10,6 +11,8 @@ typedef struct ndp_table ndp_table_t; #define RA_FLAG_M 0x80 #define RA_FLAG_O 0x40 +#define NDP_PENDING_MAX 8 +#define NDP_PENDING_MAX_BYTES 32768 typedef enum { NDP_STATE_UNUSED = 0, @@ -25,10 +28,13 @@ typedef struct { uint8_t mac[6]; uint32_t ttl_ms; uint32_t timer_ms; + uint32_t pending_bytes; + netpkt_t** pending; + uint8_t pending_len; uint8_t state; uint8_t probes_sent; uint8_t is_router; - uint32_t router_lifetime_ms; + uint8_t static_entry; } ndp_entry_t; #define NDP_TABLE_MAX 64 @@ -36,15 +42,21 @@ typedef struct { ndp_table_t* ndp_table_create(void); void ndp_table_destroy(ndp_table_t* t); -void ndp_input(uint16_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t src_mac[6], const uint8_t* icmp, uint32_t icmp_len); +void ndp_input(uint8_t ifindex, const uint8_t src_ip[16], const uint8_t dst_ip[16], const uint8_t src_mac[6], netpkt_t* pkt); -void ndp_table_put_for_l2(uint8_t ifindex, const uint8_t ip[16], const uint8_t mac[6], uint32_t ttl_ms, bool router); +void ndp_table_put_for_l2(uint8_t ifindex, const uint8_t ip[16], const uint8_t mac[6], uint32_t ttl_ms, bool router, bool is_static); +bool ndp_table_get_for_l2(uint8_t ifindex, const uint8_t ip[16], uint8_t mac_out[6]); +bool ndp_table_delete_for_l2(uint8_t ifindex, const uint8_t ip[16]); +uint32_t ndp_table_dump_for_l2(uint8_t ifindex, ndp_entry_t* out, uint32_t out_cap); +uint32_t ndp_default_router_lifetime_for_l2(uint8_t ifindex, const uint8_t ip[16]); -bool ndp_resolve_on(uint16_t ifindex, const uint8_t next_hop[16], uint8_t out_mac[6], uint32_t timeout_ms); +bool ndp_send_or_queue_on(uint8_t ifindex, const uint8_t next_hop[16], netpkt_t* pkt); bool ndp_request_dad_on(uint8_t ifindex, const uint8_t ip[16]); +void ndp_note_default_router(uint8_t ifindex, const uint8_t router[16]); -int ndp_daemon_entry(int argc, char* argv[]); +void ndp_daemon_kick(void); +void ndp_link_state_changed(uint8_t ifindex, bool up); #ifdef __cplusplus } diff --git a/kernel/networking/link_layer/nic_types.h b/kernel/networking/link_layer/nic_types.h index 90e37d65..c75d2f6b 100644 --- a/kernel/networking/link_layer/nic_types.h +++ b/kernel/networking/link_layer/nic_types.h @@ -6,13 +6,19 @@ extern "C" { #endif -typedef enum NetIfKind : uint8_t { - NET_IFK_ETH = 0x00, - NET_IFK_WIFI = 0x01, - NET_IFK_OTHER = 0x02, - NET_IFK_LOCALHOST = 0xFE, - NET_IFK_UNKNOWN = 0xFF -} NetIfKind; +typedef enum NetDevKind : uint8_t { + NET_DEV_ETH = 0x00, + NET_DEV_WIFI = 0x01, + NET_DEV_OTHER = 0x02, + NET_DEV_LOOPBACK = 0xFE, + NET_DEV_UNKNOWN = 0xFF +} NetDevKind; + +typedef enum NetLinkKind : uint8_t { + NET_LINK_DIRECT = 0, + NET_LINK_VLAN = 1, + NET_LINK_LOOPBACK = 2 +} NetLinkKind; typedef enum LinkDuplex : uint8_t { LINK_DUPLEX_HALF = 0, diff --git a/kernel/networking/net_fragbuf.c b/kernel/networking/net_fragbuf.c new file mode 100644 index 00000000..ee67b67b --- /dev/null +++ b/kernel/networking/net_fragbuf.c @@ -0,0 +1,101 @@ +#include "networking/net_fragbuf.h" +#include "alloc/allocate.h" +#include "std/memory.h" + +void net_fragbuf_init(net_fragbuf_t *fb) { + if (!fb) return; + *fb = (net_fragbuf_t){0}; + fb->max_len = NET_FRAGBUF_DEFAULT_MAX_LEN; + fb->step = NET_FRAGBUF_DEFAULT_STEP; +} + +void net_fragbuf_free(net_fragbuf_t *fb) { + if (!fb) return; + if (fb->data) release(fb->data); + if (fb->blocks) release(fb->blocks); + *fb = (net_fragbuf_t){0}; +} + +bool net_fragbuf_add(net_fragbuf_t *fb, netpkt_t *pkt, uint32_t pkt_off, uint32_t frag_off, uint32_t frag_len, uint8_t more) { + if (!fb) return false; + if (frag_len) { + if (!pkt) return false; + uint32_t pkt_len = netpkt_len(pkt); + if (pkt_off > pkt_len || frag_len > pkt_len - pkt_off) return false; + } + if (frag_off & 7u) return false; + if (!fb->max_len) fb->max_len = NET_FRAGBUF_DEFAULT_MAX_LEN; + if (!fb->step) fb->step = NET_FRAGBUF_DEFAULT_STEP; + if (frag_off > fb->max_len || frag_len > fb->max_len - frag_off) return false; + if (more && (frag_len & 7u)) return false; + uint32_t frag_end = frag_off + frag_len; + if (fb->have_last && frag_end > fb->total_len) return false; + if (!more && fb->have_last && frag_end != fb->total_len) return false; + if (!more && fb->max_end > frag_end) return false; + + uint32_t need_blocks = (frag_end + 7u) / 8u; + if (!fb->blocks) { + fb->block_count = (fb->max_len + 7u) / 8u; + uint32_t bitmap_bytes = (fb->block_count + 7u) / 8u; + fb->blocks = (uint8_t*)zalloc(bitmap_bytes ? bitmap_bytes : 1u); + if (!fb->blocks) return false; + } + if (need_blocks > fb->block_count) return false; + + uint32_t start = frag_off / 8u; + uint32_t first_byte = start / 8u; + uint32_t end_byte = (need_blocks + 7u) / 8u; + uint8_t first_mask = (uint8_t)(0xFF << (start & 7u)); + uint8_t last_mask = (need_blocks & 7u) ? (uint8_t)((1u << (need_blocks & 7u)) - 1) : 0xFF; + if (first_byte < end_byte) { + uint8_t mask = first_byte + 1 == end_byte ? first_mask & last_mask : first_mask; + if (fb->blocks[first_byte] & mask) return false; + for (uint32_t i = first_byte + 1; i + 1 < end_byte; i++) if (fb->blocks[i]) return false; + if (first_byte + 1 < end_byte && (fb->blocks[end_byte-1] & last_mask)) return false; + } + + if (frag_len && fb->cap < frag_off + frag_len) { + uint32_t new_cap = ((frag_off + frag_len + fb->step - 1u) / fb->step) * fb->step; + if (new_cap < fb->step) new_cap = fb->step; + if (new_cap > fb->max_len) new_cap = fb->max_len; + + uint8_t *new_data = fb->data ? (uint8_t*)reallocate(fb->data, new_cap) : (uint8_t*)zalloc(new_cap); + if (!new_data) return false; + fb->data = new_data; + fb->cap = new_cap; + } + + if (frag_len && !netpkt_copyout(pkt, pkt_off, fb->data + frag_off, frag_len)) return false; + if (first_byte < end_byte) { + if (first_byte+1 == end_byte) fb->blocks[first_byte] |= (first_mask & last_mask); + else { + fb->blocks[first_byte] |= first_mask; + if (end_byte > first_byte + 2) memset(fb->blocks + first_byte + 1, 0xFF, end_byte - first_byte - 2); + fb->blocks[end_byte - 1] |= last_mask; + } + } + fb->filled_blocks += need_blocks - start; + if (frag_end > fb->max_end) fb->max_end = frag_end; + + if (!more) { + fb->have_last = 1; + fb->total_len = frag_end; + } + return true; +} + +bool net_fragbuf_complete(const net_fragbuf_t *fb) { + if (!fb || !fb->have_last || !fb->blocks) return false; + uint32_t need = (fb->total_len + 7u) / 8u; + if (need > fb->block_count) return false; + return fb->filled_blocks == need; +} + +netpkt_t* net_fragbuf_take_packet(net_fragbuf_t *fb) { + if (!net_fragbuf_complete(fb)) return NULL; + netpkt_t *pkt = netpkt_wrap((uintptr_t)fb->data, fb->total_len, 0, fb->total_len, NULL, NULL); + if (!pkt) return NULL; + fb->data = NULL; + fb->cap = 0; + return pkt; +} diff --git a/kernel/networking/net_fragbuf.h b/kernel/networking/net_fragbuf.h new file mode 100644 index 00000000..27b3d244 --- /dev/null +++ b/kernel/networking/net_fragbuf.h @@ -0,0 +1,34 @@ +#pragma once + +#include "types.h" +#include "networking/netpkt.h" + +#define NET_FRAGBUF_DEFAULT_MAX_LEN 65535u +#define NET_FRAGBUF_DEFAULT_STEP 4096u + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint8_t *data; + uint8_t *blocks; + uint32_t cap; + uint32_t total_len; + uint32_t max_end; + uint32_t max_len; + uint32_t step; + uint32_t block_count; + uint32_t filled_blocks; + uint8_t have_last; +} net_fragbuf_t; + +void net_fragbuf_init(net_fragbuf_t *fb); +void net_fragbuf_free(net_fragbuf_t *fb); +bool net_fragbuf_add(net_fragbuf_t *fb, netpkt_t *pkt, uint32_t pkt_off, uint32_t frag_off, uint32_t frag_len, uint8_t more); +bool net_fragbuf_complete(const net_fragbuf_t *fb); +netpkt_t* net_fragbuf_take_packet(net_fragbuf_t *fb); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/net_logger/net_logger.c b/kernel/networking/net_logger/net_logger.c index 2ab6b449..6bbb3743 100644 --- a/kernel/networking/net_logger/net_logger.c +++ b/kernel/networking/net_logger/net_logger.c @@ -3,16 +3,6 @@ #include "networking/transport_layer/trans_utils.h" #include "networking/application_layer/http.h" -static const char* http_method_str(uint32_t m) { - switch ((HTTPMethod)m) { - case HTTP_METHOD_GET: return "GET"; - case HTTP_METHOD_POST: return "POST"; - case HTTP_METHOD_PUT: return "PUT"; - case HTTP_METHOD_DELETE: return "DELETE"; - default: return ""; - } -} - static const char* comp_str(netlog_component_t c) { switch (c) { case NETLOG_COMP_UDP: return "UDP"; @@ -49,19 +39,13 @@ static const char* bind_kind_str(SockBindKind k) { case BIND_L2: return "L2"; case BIND_IP: return "IP"; case BIND_ANY: return "ANY"; + case BIND_ANY4: return "ANY4"; + case BIND_ANY6: return "ANY6"; default: return ""; } } -static const char* dst_kind_str(SockDstKind k) { - switch (k) { - case DST_ENDPOINT: return "EP"; - case DST_DOMAIN: return "DNS"; - default: return ""; - } -} - -void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_event_t* e) { +void netlog_socket_event(const SocketOptions* extra, const netlog_socket_event_t* e) { if (!extra) return; if (!e) return; @@ -97,7 +81,7 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev } if (e->action == NETLOG_ACT_HTTP_RECV_REQUEST) { - kprintf("[NET][%s] %s method=%s path_len=%u body=%u", c, a, http_method_str(e->u0), (uint32_t)e->u1, (uint32_t)e->i0); + kprintf("[NET][%s] %s method=%s path_len=%u body=%u", c, a, http_method_name(e->u0), (uint32_t)e->u1, (uint32_t)e->i0); return; } @@ -123,15 +107,10 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev uint16_t dport = 0; net_ep_split(&e->dst_ep, dip, (int)sizeof(dip), &dv6, &dport); - if (e->dst_kind == DST_DOMAIN && e->s0) { - if (dv6) kprintf("[NET][%s] %s host=%s port=%u dst=[%s]:%u r=%lld", c, a, e->s0, (uint32_t)e->u0, dip, (uint32_t)dport, (long long)e->i0); - else kprintf("[NET][%s] %s host=%s port=%u dst=%s:%u r=%lld", c, a, e->s0, (uint32_t)e->u0, dip, (uint32_t)dport, (long long)e->i0); - } else { - if (dv6) kprintf("[NET][%s] %s dst=[%s]:%u r=%lld", c, a, dip, (uint32_t)dport, (long long)e->i0); - else kprintf("[NET][%s] %s dst=%s:%u r=%lld", c, a, dip, (uint32_t)dport, (long long)e->i0); - } + if (dv6) kprintf("[NET][%s] %s dst=[%s]:%u r=%lld", c, a, dip, (uint32_t)dport, (long long)e->i0); + else kprintf("[NET][%s] %s dst=%s:%u r=%lld", c, a, dip, (uint32_t)dport, (long long)e->i0); } else { - kprintf("[NET][%s] %s kind=%s port=%u", c, a, dst_kind_str(e->dst_kind), (uint32_t)e->u0); + kprintf("[NET][%s] %s port=%u", c, a, (uint32_t)e->u0); } return; } @@ -142,8 +121,8 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev uint16_t rport = 0; net_ep_split(&e->remote_ep, rip, (int)sizeof(rip), &rv6, &rport); - if (rv6) kprintf("[NET][%s] %s method=%s path_len=%u body=%u remote=[%s]:%u", c, a, http_method_str(e->u0), (uint32_t)e->u1, (uint32_t)e->i0, rip, (uint32_t)rport); - else kprintf("[NET][%s] %s method=%s path_len=%u body=%u remote=%s:%u", c, a, http_method_str(e->u0), (uint32_t)e->u1, (uint32_t)e->i0, rip, (uint32_t)rport); + if (rv6) kprintf("[NET][%s] %s method=%s path_len=%u body=%u remote=[%s]:%u", c, a, http_method_name(e->u0), (uint32_t)e->u1, (uint32_t)e->i0, rip, (uint32_t)rport); + else kprintf("[NET][%s] %s method=%s path_len=%u body=%u remote=%s:%u", c, a, http_method_name(e->u0), (uint32_t)e->u1, (uint32_t)e->i0, rip, (uint32_t)rport); return; } @@ -185,7 +164,7 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev } if (e->action == NETLOG_ACT_SENDTO) { - kprintf("[NET][%s] %s kind=%s port=%u n=%u", c, a, dst_kind_str(e->dst_kind), (uint32_t)e->u0, (uint32_t)e->u1); + kprintf("[NET][%s] %s port=%u n=%u", c, a, (uint32_t)e->u0, (uint32_t)e->u1); return; } @@ -235,10 +214,7 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev } if (e->action == NETLOG_ACT_CONNECT) { - if (e->dst_kind == DST_DOMAIN && e->s0) { - if (dst_v6) kprintf("[NET][%s] %s host=%s port=%u dst=[%s]:%u r=%lld", c, a, e->s0, (uint32_t)e->u0, dst_ip, (uint32_t)dst_port, (long long)e->i0); - else kprintf("[NET][%s] %s host=%s port=%u dst=%s:%u r=%lld", c, a, e->s0, (uint32_t)e->u0, dst_ip, (uint32_t)dst_port, (long long)e->i0); - } else if (dst_v6) { + if (dst_v6) { kprintf("[NET][%s] %s dst=[%s]:%u r=%lld", c, a, dst_ip, (uint32_t)dst_port, (long long)e->i0); } else { kprintf("[NET][%s] %s dst=%s:%u r=%lld", c, a, dst_ip, (uint32_t)dst_port, (long long)e->i0); @@ -247,12 +223,8 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev } if (e->action == NETLOG_ACT_SENDTO) { - if (e->dst_kind == DST_DOMAIN && e->s0) - kprintf("[NET][%s] %s host=%s port=%u n=%u", c, a, e->s0, (uint32_t)e->u0, (uint32_t)e->u1); - else if (dst_v6) - kprintf("[NET][%s] %s dst=[%s]:%u n=%u", c, a, dst_ip, (uint32_t)dst_port, (uint32_t)e->u1); - else - kprintf("[NET][%s] %s dst=%s:%u n=%u", c, a, dst_ip, (uint32_t)dst_port, (uint32_t)e->u1); + if (dst_v6) kprintf("[NET][%s] %s dst=[%s]:%u n=%u", c, a, dst_ip, (uint32_t)dst_port, (uint32_t)e->u1); + else kprintf("[NET][%s] %s dst=%s:%u n=%u", c, a, dst_ip, (uint32_t)dst_port, (uint32_t)e->u1); return; } @@ -327,10 +299,10 @@ void netlog_socket_event(const SocketExtraOptions* extra, const netlog_socket_ev if (e->action == NETLOG_ACT_HTTP_RECV_REQUEST) { if (rem_v6) { - if (e->s0) kprintf("[NET][%s] %s method=%s path=%s body=%u remote=[%s]:%u", c, a, http_method_str(e->u0), e->s0, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); + if (e->s0) kprintf("[NET][%s] %s method=%s path=%s body=%u remote=[%s]:%u", c, a, http_method_name(e->u0), e->s0, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); else kprintf("[NET][%s] %s method=%u path_len=%u body=%u remote=[%s]:%u", c, a, (uint32_t)e->u0, (uint32_t)e->u1, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); } else { - if (e->s0) kprintf("[NET][%s] %s method=%s path=%s body=%u remote=%s:%u", c, a, http_method_str(e->u0), e->s0, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); + if (e->s0) kprintf("[NET][%s] %s method=%s path=%s body=%u remote=%s:%u", c, a, http_method_name(e->u0), e->s0, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); else kprintf("[NET][%s] %s method=%u path_len=%u body=%u remote=%s:%u", c, a, (uint32_t)e->u0, (uint32_t)e->u1, (uint32_t)e->i0, rem_ip, (uint32_t)rem_port); } return; diff --git a/kernel/networking/net_logger/net_logger.h b/kernel/networking/net_logger/net_logger.h index 17dedc27..b9517df3 100644 --- a/kernel/networking/net_logger/net_logger.h +++ b/kernel/networking/net_logger/net_logger.h @@ -45,7 +45,6 @@ typedef struct netlog_socket_event_t { SockBindSpec bind_spec; - SockDstKind dst_kind; net_l4_endpoint dst_ep; net_l4_endpoint remote_ep; @@ -53,7 +52,7 @@ typedef struct netlog_socket_event_t { const char* s1; } netlog_socket_event_t; -void netlog_socket_event(const SocketExtraOptions*extra, const netlog_socket_event_t* e); +void netlog_socket_event(const SocketOptions*extra, const netlog_socket_event_t* e); #ifdef __cplusplus } diff --git a/kernel/networking/netpkt.c b/kernel/networking/netpkt.c index d69695ea..b960cbbf 100644 --- a/kernel/networking/netpkt.c +++ b/kernel/networking/netpkt.c @@ -1,36 +1,37 @@ #include "netpkt.h" -#include "std/std.h" +#include "memory/memory.h" +#include "alloc/allocate.h" #include "memory/page_allocator.h" +#include "exceptions/irq.h" #define NETPKT_F_VIEW 1u #define NETPKT_BUF_F_EXTERNAL 1u +#define NETPKT_BUF_F_SMALL 2u #define NETPKT_META_KEEP_EMPTY_PAGES 2u +#define NETPKT_SMALL_CLASS_BYTES 2048u +#define NETPKT_PAYLOAD_CLASS_COUNT 6 +#define NETPKT_PAYLOAD_CACHE_KEEP 8 +#define NETPKT_PAYLOAD_CACHE_MAX_BYTES (2 *1024 * 1024ull) +#define NETPKT_MAX_STORAGE_BYTES 131072u +#define NETPKT_HEADROOM_SLACK 32u typedef struct netpkt_buf netpkt_buf_t; typedef struct meta_slab_page meta_slab_page_t; -typedef struct meta_obj_hdr { - meta_slab_page_t*page; - uintptr_t pad; -} meta_obj_hdr_t; - struct meta_slab_page { meta_slab_page_t* next; + meta_slab_page_t* prev; void* free_list; uint16_t in_use; uint16_t capacity; - uint32_t stride; }; typedef struct meta_slab { meta_slab_page_t* partial; - meta_slab_page_t* full; meta_slab_page_t* empty; - uint32_t obj_size; uint32_t stride; uint32_t empty_pages; - uint32_t keep_empty; } meta_slab_t; struct netpkt_buf { @@ -53,100 +54,209 @@ struct netpkt { }; static uint64_t g_netpkt_page_bytes; -static uint64_t g_netpkt_payload_page_bytes; -static uint64_t g_netpkt_meta_page_bytes; static meta_slab_t g_meta_slab_pkt; static meta_slab_t g_meta_slab_buf; +static uint64_t g_payload_cache_bytes; + +static void* g_netpkt_small_free; +static void* g_payload_cache[NETPKT_PAYLOAD_CLASS_COUNT]; +static uint32_t g_payload_cache_count[NETPKT_PAYLOAD_CLASS_COUNT]; +static const uint32_t g_payload_cache_size[NETPKT_PAYLOAD_CLASS_COUNT] = {PAGE_SIZE, PAGE_SIZE*2, PAGE_SIZE*4, PAGE_SIZE*8, PAGE_SIZE*16, NETPKT_MAX_STORAGE_BYTES}; + +static void* netpkt_payload_alloc(uint32_t cap, uint32_t* flags) { + if (!flags) return 0; + *flags = 0; + if (!cap) return 0; + + if (cap == NETPKT_SMALL_CLASS_BYTES) { + irq_flags_t irq = irq_save_disable(); + if (g_netpkt_small_free) { + void* mem = g_netpkt_small_free; + g_netpkt_small_free = *(void**)mem; + irq_restore(irq); + *flags = NETPKT_BUF_F_SMALL; + return mem; + } + if ((uint64_t)PAGE_SIZE > (uint64_t)NETPKT_MAX_PAGE_BYTES || g_netpkt_page_bytes > (uint64_t)NETPKT_MAX_PAGE_BYTES - (uint64_t)PAGE_SIZE) { + irq_restore(irq); + return 0; + } + g_netpkt_page_bytes += (uint64_t)PAGE_SIZE; + irq_restore(irq); + + void* page = palloc(PAGE_SIZE, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + if (!page) { + irq = irq_save_disable(); + if (g_netpkt_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; + else g_netpkt_page_bytes = 0; + irq_restore(irq); + return 0; + } + + void* second = (void*)((uintptr_t)page+NETPKT_SMALL_CLASS_BYTES); + irq = irq_save_disable(); + *(void**)second = g_netpkt_small_free; + g_netpkt_small_free = second; + irq_restore(irq); + + *flags = NETPKT_BUF_F_SMALL; + return page; + } + + uint32_t cls = NETPKT_PAYLOAD_CLASS_COUNT; + for (uint32_t i = 0; i < NETPKT_PAYLOAD_CLASS_COUNT; i++)if (g_payload_cache_size[i] == cap) { + cls = i; + break; + } + + irq_flags_t irq = irq_save_disable(); + if (cls < NETPKT_PAYLOAD_CLASS_COUNT && g_payload_cache[cls]) { + void* mem = g_payload_cache[cls]; + g_payload_cache[cls] = *(void**)mem; + if (g_payload_cache_count[cls]) g_payload_cache_count[cls]--; + if (g_payload_cache_bytes >= (uint64_t)cap) g_payload_cache_bytes -= (uint64_t)cap; + else g_payload_cache_bytes = 0; + irq_restore(irq); + return mem; + } + if ((uint64_t)cap > (uint64_t)NETPKT_MAX_PAGE_BYTES || g_netpkt_page_bytes > (uint64_t)NETPKT_MAX_PAGE_BYTES - (uint64_t)cap) { + irq_restore(irq); + return 0; + } + g_netpkt_page_bytes += (uint64_t)cap; + irq_restore(irq); + + void* mem = palloc((uint64_t)cap, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + if (!mem) { + irq = irq_save_disable(); + if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; + else g_netpkt_page_bytes = 0; + irq_restore(irq); + return 0; + } + + return mem; +} + +static void netpkt_payload_free(uintptr_t base, uint32_t cap, uint32_t flags) { + if (!base) return; + if (flags & NETPKT_BUF_F_SMALL) { + irq_flags_t irq = irq_save_disable(); + *(void**)base = g_netpkt_small_free; + g_netpkt_small_free = (void*)base; + irq_restore(irq); + return; + } + + uint32_t cls = NETPKT_PAYLOAD_CLASS_COUNT; + for (uint32_t i = 0; i < NETPKT_PAYLOAD_CLASS_COUNT; i++)if (g_payload_cache_size[i] == cap) { + cls = i; + break; + } + if (cls < NETPKT_PAYLOAD_CLASS_COUNT) { + irq_flags_t irq = irq_save_disable(); + if (g_payload_cache_count[cls] < NETPKT_PAYLOAD_CACHE_KEEP && g_payload_cache_bytes <= NETPKT_PAYLOAD_CACHE_MAX_BYTES - (uint64_t)cap) { + *(void**)base = g_payload_cache[cls]; + g_payload_cache[cls] = (void*)base; + g_payload_cache_count[cls]++; + g_payload_cache_bytes += (uint64_t)cap; + irq_restore(irq); + return; + } + if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; + else g_netpkt_page_bytes = 0; + irq_restore(irq); + pfree((void*)base, (uint64_t)cap); + return; + } + + irq_flags_t irq = irq_save_disable(); + if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; + else g_netpkt_page_bytes = 0; + irq_restore(irq); + pfree((void*)base, (uint64_t)cap); +} + +static inline __attribute__((always_inline)) void meta_slab_add(meta_slab_page_t** head, meta_slab_page_t* p) { + p->prev = 0; + p->next = *head; + if (*head) (*head)->prev = p; + *head = p; +} -static uintptr_t g_spare_page; +static inline __attribute__((always_inline)) void meta_slab_remove(meta_slab_page_t** head, meta_slab_page_t* p) { + if (p->prev) p->prev->next = p->next; + else *head = p->next; + if (p->next) p->next->prev = p->prev; + p->next = 0; + p->prev = 0; +} -static void* meta_slab_alloc(meta_slab_t* s, uint32_t obj_size_aligned) { +static void* meta_slab_alloc(meta_slab_t* s, uint32_t obj_size_aligned, irq_flags_t* irq) { if (!s) return 0; if (!obj_size_aligned) return 0; if ((obj_size_aligned & 15u) != 0u) return 0; - if (!s->obj_size) { - s->obj_size = obj_size_aligned; - s->stride = (uint32_t)(((uint64_t)sizeof(meta_obj_hdr_t) + (uint64_t)obj_size_aligned + 15ull) & ~15ull); - if (!s->stride) return 0; - s->keep_empty = NETPKT_META_KEEP_EMPTY_PAGES; - } else { - if (s->obj_size != obj_size_aligned) return 0; - if (!s->stride) return 0; - } + if (!s->stride) s->stride = obj_size_aligned; + else if (s->stride != obj_size_aligned) return 0; if (!s->partial) { if (s->empty) { meta_slab_page_t* p =s->empty; - s->empty = p->next; + meta_slab_remove(&s->empty, p); if (s->empty_pages) s->empty_pages--; - p->next = s->partial; - s->partial = p; + meta_slab_add(&s->partial, p); } else { if ((uint64_t)PAGE_SIZE > (uint64_t)NETPKT_MAX_PAGE_BYTES) return 0; if (g_netpkt_page_bytes > (uint64_t)NETPKT_MAX_PAGE_BYTES - (uint64_t)PAGE_SIZE) return 0; g_netpkt_page_bytes += (uint64_t)PAGE_SIZE; - g_netpkt_meta_page_bytes += (uint64_t)PAGE_SIZE; - + irq_restore(*irq);//TODO lock meta_slab_page_t* p = (meta_slab_page_t*)palloc(PAGE_SIZE, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); if (!p) { - if (g_netpkt_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_page_bytes-= (uint64_t)PAGE_SIZE; - else g_netpkt_page_bytes = 0; - - if (g_netpkt_meta_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_meta_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_meta_page_bytes = 0; + *irq = irq_save_disable(); + g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; return 0; } - p->next = 0;// + p->next = 0; + p->prev = 0; p->free_list = 0; p->in_use = 0; p->capacity = 0; - p->stride = s->stride; uint8_t* start = (uint8_t*)p + (uint64_t)sizeof(*p); start = (uint8_t*)(((uintptr_t)start + 15u) &~(uintptr_t)15u); uint64_t avail = (uint64_t)PAGE_SIZE - (uint64_t)(start - (uint8_t*)p); - if (avail < (uint64_t)p->stride) { + if (avail < (uint64_t)s->stride) { pfree(p, (uint64_t)PAGE_SIZE); - if (g_netpkt_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_page_bytes = 0; - - if (g_netpkt_meta_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_meta_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_meta_page_bytes = 0; + *irq = irq_save_disable(); + g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; return 0; } - uint32_t cap = (uint32_t)(avail / (uint64_t)p->stride); + uint32_t cap = (uint32_t)(avail / (uint64_t)s->stride); if (!cap) { pfree(p, (uint64_t)PAGE_SIZE); - if (g_netpkt_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_page_bytes = 0; - if (g_netpkt_meta_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_meta_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_meta_page_bytes = 0; + *irq = irq_save_disable(); + g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; return 0; } p->capacity = (uint16_t)cap; - uint32_t i = 0; - while (i< cap) { - uint8_t* slot = start + (uint64_t)i * (uint64_t)p->stride; - meta_obj_hdr_t* h = (meta_obj_hdr_t*)(uintptr_t)slot; - h->page = p; - h->pad = 0; - void* obj = (void*)(uintptr_t)(slot + (uint64_t)sizeof(*h)); + for (uint32_t i = 0; i < cap; i++) { + uint8_t* slot = start + (uint64_t)i * (uint64_t)s->stride; + void* obj = slot; *(void**)obj = p->free_list; p->free_list = obj; - i++; } - p->next = s->partial; - s->partial = p; + *irq = irq_save_disable(); + meta_slab_add(&s->partial, p); } } @@ -159,81 +269,41 @@ static void* meta_slab_alloc(meta_slab_t* s, uint32_t obj_size_aligned) { page->in_use++; if (page->in_use == page->capacity) { - s->partial = page->next; - page->next = s->full; - s->full = page; + meta_slab_remove(&s->partial, page); } return obj; } -static void meta_slab_free(meta_slab_t* s, void* obj) { +static void meta_slab_free(meta_slab_t* s, void* obj, irq_flags_t* irq) { if (!s) return; if (!obj) return; - meta_obj_hdr_t* h = (meta_obj_hdr_t*)(uintptr_t)obj -1; - meta_slab_page_t* page = h->page; + meta_slab_page_t* page = (meta_slab_page_t*)((uintptr_t)obj & ~((uintptr_t)PAGE_SIZE - 1)); if (!page) return; *(void**)obj = page->free_list; page->free_list = obj; - bool was_full = false; - if (page->in_use == page->capacity) was_full = true; + bool was_full = page->in_use == page->capacity; if (page->in_use) page->in_use--; - if (was_full) { - meta_slab_page_t* prev = 0; - meta_slab_page_t* cur = s->full; - while (cur && cur != page) { - prev = cur; - cur = cur->next; - } - if (cur) { - if (prev) prev->next = cur->next; - else s->full = cur->next; - cur->next = s->partial; - s->partial = cur; - } - } + if (was_full) meta_slab_add(&s->partial, page); if (!page->in_use) { - meta_slab_page_t* prev = 0; - meta_slab_page_t* cur = s->partial; - while (cur && cur != page) { - prev = cur; - cur = cur->next; - } - if (cur) { - if (prev) prev->next = cur->next; - else s->partial = cur->next; - } else { - prev = 0; - cur = s->full; - while (cur && cur != page) { - prev = cur; - cur = cur->next; - } - if (cur) { - if (prev) prev->next = cur->next; - else s->full = cur->next; - } - } - - page->next = s->empty; - s->empty = page; + meta_slab_remove(&s->partial, page); + meta_slab_add(&s->empty, page); s->empty_pages++; - while (s->empty_pages > s->keep_empty){ + while (s->empty_pages > NETPKT_META_KEEP_EMPTY_PAGES){ meta_slab_page_t* e = s->empty; if (!e) break; - s->empty = e->next; + meta_slab_remove(&s->empty, e); s->empty_pages--; - pfree(e,(uint64_t)PAGE_SIZE); - if (g_netpkt_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_page_bytes = 0; - if (g_netpkt_meta_page_bytes >= (uint64_t)PAGE_SIZE) g_netpkt_meta_page_bytes -= (uint64_t)PAGE_SIZE; - else g_netpkt_meta_page_bytes = 0; + g_netpkt_page_bytes -= (uint64_t)PAGE_SIZE; + irq_restore(*irq); //TODO lock + pfree(e, (uint64_t)PAGE_SIZE); + *irq = irq_save_disable(); } } } @@ -243,66 +313,52 @@ static bool netpkt_realloc_to(netpkt_t* p, uint32_t new_head, uint32_t new_alloc if (!p->buf) return false; if (p->flags & NETPKT_F_VIEW) return false; - uint64_t min = (uint64_t)new_head + (uint64_t)p->len; - if ((uint64_t)new_alloc < min) return false; - - uint64_t bytes = (uint64_t)new_alloc; - if (!bytes) bytes = 1; - uint64_t cap64 = count_pages(bytes, PAGE_SIZE)*(uint64_t)PAGE_SIZE; - if (!cap64) cap64 = PAGE_SIZE; - if (cap64 > (uint64_t)NETPKT_MAX_ALLOC) return false; + if (p->len > NETPKT_MAX_ALLOC) return false; + if ((uint64_t)new_head + (uint64_t)p->len > (uint64_t)new_alloc) return false; + if (new_alloc > NETPKT_MAX_STORAGE_BYTES) return false; - uint32_t cap = (uint32_t)cap64; - if ((uint64_t)new_head+(uint64_t)p->len > (uint64_t)cap) return false; + uint64_t cap64 = new_alloc ? new_alloc : 1; - void* mem = 0; - bool from_spare = false; - if (cap == PAGE_SIZE && g_spare_page) { - mem = (void*)g_spare_page; - g_spare_page = 0; - from_spare = true; - memset(mem, 0, PAGE_SIZE); - } else { - if ((uint64_t)cap > (uint64_t)NETPKT_MAX_PAGE_BYTES) return false; - if (g_netpkt_page_bytes > (uint64_t)NETPKT_MAX_PAGE_BYTES - (uint64_t)cap)return false; - g_netpkt_page_bytes += (uint64_t)cap; - g_netpkt_payload_page_bytes += (uint64_t)cap; - - mem = palloc((uint64_t)cap, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); - if (!mem) { - if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; - else g_netpkt_page_bytes = 0; + uint32_t cap = NETPKT_SMALL_CLASS_BYTES; + if (cap64 > NETPKT_SMALL_CLASS_BYTES) { + cap64 = count_pages(cap64, PAGE_SIZE)*(uint64_t)PAGE_SIZE; + if (!cap64) cap64 = PAGE_SIZE; + if (cap64 > NETPKT_MAX_STORAGE_BYTES)return false; - if (g_netpkt_payload_page_bytes >= (uint64_t)cap) g_netpkt_payload_page_bytes -= (uint64_t)cap; - else g_netpkt_payload_page_bytes = 0; - return false; - } + cap = (uint32_t)cap64; } + if ((uint64_t)new_head+(uint64_t)p->len > (uint64_t)cap) return false; + + uint32_t flags = 0; + void* mem = netpkt_payload_alloc(cap, &flags); + if (!mem) return false; - uint32_t sz = (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15) & ~15ull); - netpkt_buf_t* nb = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, sz); + uint32_t bsz = (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15ull) & ~15ull); + irq_flags_t irq = irq_save_disable(); + netpkt_buf_t* nb = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, bsz, &irq); + irq_restore(irq); if (!nb) { - if (from_spare) { - g_spare_page = (uintptr_t)mem; - } else { - pfree(mem, (uint64_t)cap); - if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= (uint64_t)cap) g_netpkt_payload_page_bytes -= (uint64_t)cap; - else g_netpkt_payload_page_bytes = 0; - } + netpkt_payload_free((uintptr_t)mem, cap, flags); return false; } + uintptr_t old_data = netpkt_data(p); + if (p->len && old_data) memcpy((void*)((uintptr_t)mem + (uintptr_t)new_head), (const void*)old_data, p->len); + nb->base = (uintptr_t)mem; nb->alloc = cap; nb->refs = 1; - nb->flags = 0; + nb->flags = flags; nb->free_fn = 0; nb->free_ctx = 0; - if (p->len) memcpy((void*)(nb->base + (uintptr_t)new_head), (const void*)netpkt_data(p), p->len); + uintptr_t free_base = 0; + uint32_t free_alloc = 0; + uint32_t free_flags = 0; + netpkt_free_fn free_fn = 0; + void* free_ctx = 0; + irq = irq_save_disable(); netpkt_buf_t* ob = p->buf; p->buf = nb; p->off = 0; @@ -310,133 +366,86 @@ static bool netpkt_realloc_to(netpkt_t* p, uint32_t new_head, uint32_t new_alloc p->head = new_head; if (ob) { - if (ob->refs > 1) { - ob->refs--; - } else { - if (ob->flags & NETPKT_BUF_F_EXTERNAL) { - if (ob->free_fn) ob->free_fn(ob->free_ctx, ob->base, ob->alloc); - } else { - bool aligned = ((ob->base & (PAGE_SIZE - 1)) == 0) && ((ob->alloc & (PAGE_SIZE - 1)) == 0); - if (!g_spare_page && aligned) { - g_spare_page = ob->base; - - if (ob->alloc > PAGE_SIZE) { - pfree((void*)(ob->base + PAGE_SIZE), (uint64_t)ob->alloc - (uint64_t)PAGE_SIZE); - uint64_t dec = (uint64_t)ob->alloc-(uint64_t)PAGE_SIZE; - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - } - } else { - if (ob->base) pfree((void*)ob->base,(uint64_t)ob->alloc); - uint64_t dec = (uint64_t)ob->alloc; - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - } - } - - meta_slab_free(&g_meta_slab_buf, ob); + if (ob->refs > 1) ob->refs--; + else { + free_base = ob->base; + free_alloc = ob->alloc; + free_flags = ob->flags; + free_fn = ob->free_fn; + free_ctx = ob->free_ctx; + meta_slab_free(&g_meta_slab_buf, ob, &irq); } } + irq_restore(irq); + if (free_base) { + if (free_flags & NETPKT_BUF_F_EXTERNAL) { + if (free_fn) free_fn(free_ctx, free_base); + else release((void*)free_base); + } else netpkt_payload_free(free_base, free_alloc, free_flags); + } return true; } netpkt_t* netpkt_alloc(uint32_t data_capacity, uint32_t headroom, uint32_t tailroom) { + if (data_capacity > NETPKT_MAX_ALLOC) return 0; uint64_t alloc = (uint64_t)headroom + (uint64_t)data_capacity + (uint64_t)tailroom; - if (alloc > (uint64_t)NETPKT_MAX_ALLOC) return 0; - - if (!alloc) alloc = 1; - uint64_t cap64 = count_pages(alloc, PAGE_SIZE) * (uint64_t)PAGE_SIZE; - if (!cap64) cap64 = PAGE_SIZE; - if (cap64 > (uint64_t)NETPKT_MAX_ALLOC) return 0; - - uint32_t cap = (uint32_t)cap64; - - void* mem = 0; - bool from_spare = false; - if (cap == PAGE_SIZE && g_spare_page) { - mem = (void*)g_spare_page; - g_spare_page = 0; - from_spare = true; - memset(mem, 0, PAGE_SIZE); - } else { - if ((uint64_t)cap > (uint64_t)NETPKT_MAX_PAGE_BYTES) return 0; - if (g_netpkt_page_bytes > (uint64_t)NETPKT_MAX_PAGE_BYTES - (uint64_t)cap) return 0; - g_netpkt_page_bytes += (uint64_t)cap; - g_netpkt_payload_page_bytes += (uint64_t)cap; - - mem = palloc((uint64_t)cap, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); - if (!mem) { - if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= (uint64_t)cap) g_netpkt_payload_page_bytes -= (uint64_t)cap; - else g_netpkt_payload_page_bytes = 0; - return 0; - } + if (alloc > (uint64_t)NETPKT_MAX_STORAGE_BYTES) return 0; + uint64_t cap_bytes = alloc ? alloc : 1; + uint32_t cap = NETPKT_SMALL_CLASS_BYTES; + if (cap_bytes > NETPKT_SMALL_CLASS_BYTES) { + cap_bytes = count_pages(cap_bytes, PAGE_SIZE) * (uint64_t)PAGE_SIZE; + if (!cap_bytes) cap_bytes = PAGE_SIZE; + if (cap_bytes > NETPKT_MAX_STORAGE_BYTES) return 0; + cap = (uint32_t)cap_bytes; } - uint32_t bsz = (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15ull) &~15ull); - netpkt_buf_t* b = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, bsz); - if (!b) { - if (from_spare) { - g_spare_page = (uintptr_t)mem; - } else { - pfree(mem, (uint64_t)cap); - if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= (uint64_t)cap) g_netpkt_payload_page_bytes -= (uint64_t)cap; - else g_netpkt_payload_page_bytes = 0; - } + uint32_t flags = 0; + void* mem = netpkt_payload_alloc(cap, &flags); + if (!mem) return 0; + + irq_flags_t irq = irq_save_disable(); + netpkt_buf_t* b = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15ull) & ~15ull), &irq); + netpkt_t* p = b ? (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) & ~15ull), &irq) : 0; + if (!b || !p) { + if (b) meta_slab_free(&g_meta_slab_buf, b, &irq); + irq_restore(irq); + netpkt_payload_free((uintptr_t)mem, cap, flags); return 0; } b->base = (uintptr_t)mem; b->alloc = cap; b->refs = 1; - b->flags = 0; + b->flags = flags; b->free_fn = 0; b->free_ctx = 0; - uint32_t psz = (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) &~15ull); - netpkt_t* p = (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, psz); - if (!p) { - meta_slab_free(&g_meta_slab_buf, b); - if (from_spare) { - g_spare_page = (uintptr_t)mem; - } else { - pfree(mem, (uint64_t)cap); - if (g_netpkt_page_bytes >= (uint64_t)cap) g_netpkt_page_bytes -= (uint64_t)cap; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= (uint64_t)cap) g_netpkt_payload_page_bytes -= (uint64_t)cap; - else g_netpkt_payload_page_bytes = 0; - } - return 0; - } + uint32_t used = headroom + data_capacity + tailroom; + uint32_t slack = cap > used ? cap - used : 0; + if (slack > NETPKT_HEADROOM_SLACK) slack = NETPKT_HEADROOM_SLACK; p->buf = b; p->off = 0; p->cap = cap; - p->head = headroom; + p->head = headroom + slack; p->len = 0; p->refs = 1; p->flags = 0; + irq_restore(irq); return p; } netpkt_t* netpkt_wrap(uintptr_t base, uint32_t alloc_size, uint32_t data_off, uint32_t data_len, netpkt_free_fn free_fn, void* ctx) { - if (!base) return 0; - if (!alloc_size) return 0; - if (alloc_size > NETPKT_MAX_ALLOC) return 0; - if (data_off > alloc_size) return 0; - if (data_len > alloc_size - data_off) return 0; - - uint32_t bsz = (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15ull) &~15ull); - netpkt_buf_t* b = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, bsz); - if (!b) return 0; + if (!base || !alloc_size || alloc_size > NETPKT_MAX_STORAGE_BYTES || data_len > NETPKT_MAX_ALLOC || data_off > alloc_size || data_len > alloc_size - data_off) return 0; + irq_flags_t irq = irq_save_disable(); + netpkt_buf_t* b = (netpkt_buf_t*)meta_slab_alloc(&g_meta_slab_buf, (uint32_t)(((uint64_t)sizeof(netpkt_buf_t) + 15ull) & ~15ull), &irq); + netpkt_t* p = b ? (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) & ~15ull), &irq) : 0; + if (!b || !p) { + if (b) meta_slab_free(&g_meta_slab_buf, b, &irq); + irq_restore(irq); + return 0; + } b->base = base; b->alloc = alloc_size; @@ -445,13 +454,6 @@ netpkt_t* netpkt_wrap(uintptr_t base, uint32_t alloc_size, uint32_t data_off, ui b->free_fn = free_fn; b->free_ctx = ctx; - uint32_t psz = (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) &~15ull); - netpkt_t* p = (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, psz); - if (!p) { - meta_slab_free(&g_meta_slab_buf, b); - return 0; - } - p->buf = b; p->off = 0; p->cap = alloc_size; @@ -459,22 +461,29 @@ netpkt_t* netpkt_wrap(uintptr_t base, uint32_t alloc_size, uint32_t data_off, ui p->len = data_len; p->refs = 1; p->flags = 0; + irq_restore(irq); return p; } netpkt_t* netpkt_view(netpkt_t* parent, uint32_t off, uint32_t len) { if (!parent) return 0; - if (!parent->buf) return 0; - - uint64_t end = (uint64_t)off+(uint64_t)len; - if (end > (uint64_t)parent->len) return 0; + irq_flags_t irq = irq_save_disable(); + if (!parent->buf || off > parent->len || len > parent->len - off) { + irq_restore(irq); + return 0; + } uint64_t abs = (uint64_t)parent->off + (uint64_t)parent->head + (uint64_t)off; - if (abs + (uint64_t)len > (uint64_t)parent->buf->alloc) return 0; + if (abs + (uint64_t)len > (uint64_t)parent->buf->alloc) { + irq_restore(irq); + return 0; + } - uint32_t psz = (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) &~15ull); - netpkt_t* v = (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, psz); - if (!v) return 0; + netpkt_t* v = (netpkt_t*)meta_slab_alloc(&g_meta_slab_pkt, (uint32_t)(((uint64_t)sizeof(netpkt_t) + 15ull) & ~15ull), &irq); + if (!v) { + irq_restore(irq); + return 0; + } parent->buf->refs++; @@ -485,55 +494,55 @@ netpkt_t* netpkt_view(netpkt_t* parent, uint32_t off, uint32_t len) { v->len = len; v->refs = 1; v->flags = NETPKT_F_VIEW; + irq_restore(irq); return v; } void netpkt_ref(netpkt_t* p){ if (!p) return; + irq_flags_t irq = irq_save_disable(); p->refs++; + irq_restore(irq); } void netpkt_unref(netpkt_t* p) { if (!p) return; + + uintptr_t free_base = 0; + uint32_t free_alloc = 0; + uint32_t free_flags = 0; + netpkt_free_fn free_fn = 0; + void* free_ctx = 0; + + irq_flags_t irq = irq_save_disable(); if (p->refs > 1) { p->refs--; + irq_restore(irq); return; } netpkt_buf_t* b = p->buf; if (b) { - if (b->refs > 1) { - b->refs--; - } else { - if (b->flags & NETPKT_BUF_F_EXTERNAL) { - if (b->free_fn) b->free_fn(b->free_ctx, b->base, b->alloc); - } else { - bool aligned = ((b->base & (PAGE_SIZE - 1)) == 0) && ((b->alloc & (PAGE_SIZE - 1)) == 0); - if (!g_spare_page && aligned) { - g_spare_page = b->base; - if (b->alloc > PAGE_SIZE) { - pfree((void*)(b->base + PAGE_SIZE), (uint64_t)b->alloc-(uint64_t)PAGE_SIZE); - uint64_t dec = (uint64_t)b->alloc-(uint64_t)PAGE_SIZE; - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - } - } else { - if (b->base) pfree((void*)b->base, (uint64_t)b->alloc); - uint64_t dec = (uint64_t)b->alloc; - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - } - } - - meta_slab_free(&g_meta_slab_buf, b); + if (b->refs > 1) b->refs--; + else { + free_base = b->base; + free_alloc = b->alloc; + free_flags = b->flags; + free_fn = b->free_fn; + free_ctx = b->free_ctx; + meta_slab_free(&g_meta_slab_buf, b, &irq); } } - meta_slab_free(&g_meta_slab_pkt, p); + meta_slab_free(&g_meta_slab_pkt, p, &irq); + irq_restore(irq); + + if (free_base) { + if (free_flags & NETPKT_BUF_F_EXTERNAL) { + if (free_fn) free_fn(free_ctx, free_base); + else release((void*)free_base); + } else netpkt_payload_free(free_base, free_alloc, free_flags); + } } uintptr_t netpkt_data(const netpkt_t* p) { @@ -546,56 +555,102 @@ uint32_t netpkt_len(const netpkt_t* p) { return p ? p->len : 0; } +bool netpkt_copyout(const netpkt_t* p, uint32_t off, void* dst, uint32_t len) { + if (!p || !dst) return false; + if (off > p->len) return false; + if (len > p->len - off) return false; + if (!len) return true; + + uintptr_t src = netpkt_data(p); + if (!src) return false; + src += (uintptr_t)off; + + uint8_t* d = (uint8_t*)dst; + const uint8_t* s = (const uint8_t*)src; + switch (len) { + case 1: + d[0] = s[0]; + break; + case 2: + d[0] = s[0]; + d[1] = s[1]; + break; + case 4: + d[0] = s[0]; + d[1] = s[1]; + d[2] = s[2]; + d[3] = s[3]; + break; + default: + memcpy(dst, (const void*)src, len); + break; + } + return true; +} + uint32_t netpkt_headroom(const netpkt_t* p) { return p ? p->head : 0; } uint32_t netpkt_tailroom(const netpkt_t* p) { if (!p) return 0; - uint32_t used = p->head + p->len; - return used >= p->cap ? 0: (p->cap - used); + if (p->head > p->cap || p->len > p->cap - p->head) return 0; + return p->cap - p->head - p->len; } bool netpkt_ensure_headroom(netpkt_t* p, uint32_t need) { - if (!p) return false; - if (!p->buf) return false; - if (p->flags & NETPKT_F_VIEW) return false; - if (need > NETPKT_MAX_ALLOC) return false; + if (!p || need > NETPKT_MAX_STORAGE_BYTES) return false; + irq_flags_t irq = irq_save_disable(); + if (!p->buf || (p->flags & NETPKT_F_VIEW)) { + irq_restore(irq); + return false; + } - if (p->head >= need) { - if (p->buf->refs == 1) return true; - return netpkt_realloc_to(p, p->head, p->cap); + if (p->head >= need && p->buf->refs == 1) { + irq_restore(irq); + return true; } - uint32_t tail = netpkt_tailroom(p); - uint32_t new_head = need; + uint32_t add = need > p->head ? need - p->head : 0; + uint32_t tail = p->head <= p->cap && p->len <= p->cap - p->head ? p->cap - p->head - p->len : 0; + if (add && p->buf->refs == 1 && !(p->buf->flags & NETPKT_BUF_F_EXTERNAL) && tail >= add) { + uintptr_t old_data = p->buf->base + (uintptr_t)p->off + (uintptr_t)p->head; + if (p->len) memmove((void*)(old_data + (uintptr_t)add), (const void*)old_data, p->len); + p->head += add; + irq_restore(irq); + return true; + } - uint64_t alloc = (uint64_t)new_head + (uint64_t)p->len + (uint64_t)tail; - uint64_t min = (uint64_t)p->cap + (uint64_t)(need - p->head); - if (alloc < min) alloc = min; - if (alloc > (uint64_t)NETPKT_MAX_ALLOC) return false; + uint64_t alloc = (uint64_t)need + (uint64_t)p->len + (uint64_t)tail; + uint64_t min = (uint64_t)p->cap + (uint64_t)add; + irq_restore(irq); - return netpkt_realloc_to(p, new_head, (uint32_t)alloc); + if (alloc < min) alloc = min; + if (alloc > (uint64_t)NETPKT_MAX_STORAGE_BYTES) return false; + return netpkt_realloc_to(p, need, (uint32_t)alloc); } bool netpkt_ensure_tailroom(netpkt_t* p, uint32_t need) { - if (!p) return false; - if (!p->buf) return false; - if (p->flags & NETPKT_F_VIEW) return false; - if (need > NETPKT_MAX_ALLOC) return false; + if (!p || need > NETPKT_MAX_STORAGE_BYTES) return false; + irq_flags_t irq = irq_save_disable(); + if (!p->buf || (p->flags & NETPKT_F_VIEW)) { + irq_restore(irq); + return false; + } - uint32_t tail = netpkt_tailroom(p); - if (tail >= need) { - if (p->buf->refs == 1) return true; - return netpkt_realloc_to(p, p->head, p->cap); + uint32_t tail = p->head <= p->cap && p->len <= p->cap - p->head ? p->cap - p->head - p->len : 0; + if (tail >= need && p->buf->refs == 1) { + irq_restore(irq); + return true; } + uint32_t new_head = p->head; uint64_t alloc = (uint64_t)p->head + (uint64_t)p->len + (uint64_t)need; - uint64_t min = (uint64_t)p->cap + (uint64_t)(need - tail); + uint64_t min = (uint64_t)p->cap + (uint64_t)(need > tail ? need - tail : 0); + irq_restore(irq); if (alloc < min) alloc = min; - if (alloc > (uint64_t)NETPKT_MAX_ALLOC) return false; - - return netpkt_realloc_to(p, p->head, (uint32_t)alloc); + if (alloc > (uint64_t)NETPKT_MAX_STORAGE_BYTES) return false; + return netpkt_realloc_to(p, new_head, (uint32_t)alloc); } void* netpkt_push(netpkt_t* p, uint32_t bytes) { @@ -603,85 +658,84 @@ void* netpkt_push(netpkt_t* p, uint32_t bytes) { if ((p->flags & NETPKT_F_VIEW) && bytes) return 0; if (!bytes) return (void*)netpkt_data(p); + if (p->len > NETPKT_MAX_ALLOC - bytes) return 0; + + irq_flags_t irq = irq_save_disable(); + if (p->buf && p->buf->refs == 1 && bytes <= p->head) { + p->head -= bytes; + p->len += bytes; + void* out = (void*)(p->buf->base + (uintptr_t)p->off + (uintptr_t)p->head); + irq_restore(irq); + return out; + } + irq_restore(irq); + if (!netpkt_ensure_headroom(p, bytes)) return 0; + irq = irq_save_disable(); + if (!p->buf || bytes > p->head || p->len > NETPKT_MAX_ALLOC - bytes) { + irq_restore(irq); + return 0; + } p->head -= bytes; p->len += bytes; - return (void*)(p->buf->base + (uintptr_t)p->off + (uintptr_t)p->head); + void* out = (void*)(p->buf->base + (uintptr_t)p->off + (uintptr_t)p->head); + irq_restore(irq); + return out; } void* netpkt_put(netpkt_t* p, uint32_t bytes) { if (!p) return 0; if ((p->flags & NETPKT_F_VIEW) && bytes) return 0; if (!bytes) return (void*)(netpkt_data(p) + (uintptr_t)p->len); + if (p->len > NETPKT_MAX_ALLOC - bytes) return 0; + + irq_flags_t irq = irq_save_disable(); + if (p->buf && p->buf->refs == 1 && p->head <= p->cap && p->len <= p->cap - p->head) { + uint32_t tail = p->cap - p->head - p->len; + if (bytes <= tail) { + uintptr_t out = p->buf->base + (uintptr_t)p->off + (uintptr_t)p->head + (uintptr_t)p->len; + p->len += bytes; + irq_restore(irq); + return (void*)out; + } + } + irq_restore(irq); + if (!netpkt_ensure_tailroom(p, bytes)) return 0; + irq = irq_save_disable(); + if (!p->buf || bytes > netpkt_tailroom(p) || p->len > NETPKT_MAX_ALLOC - bytes) { + irq_restore(irq); + return 0; + } uintptr_t out = p->buf->base+(uintptr_t)p->off + (uintptr_t)p->head + (uintptr_t)p->len; p->len += bytes; + irq_restore(irq); return (void*)out; } bool netpkt_pull(netpkt_t* p, uint32_t bytes) { if (!p) return false; - if (bytes > p->len) return false; + irq_flags_t irq = irq_save_disable(); + if (bytes > p->len) { + irq_restore(irq); + return false; + } p->head += bytes; p->len -= bytes; - - if (p->flags & NETPKT_F_VIEW) return true; - if (!p->buf) return true; - if (p->buf->flags & NETPKT_BUF_F_EXTERNAL) return true; - if (p->buf->refs != 1) return true; - if (p->cap <= PAGE_SIZE) return true; - - uint64_t need = (uint64_t)p->head+(uint64_t)p->len; - if (!need) need = 1; - uint64_t newcap64 = count_pages(need, PAGE_SIZE) * (uint64_t)PAGE_SIZE; - if (newcap64 < PAGE_SIZE) newcap64 = PAGE_SIZE; - if (newcap64 >= (uint64_t)p->cap) return true; - - uint32_t newcap = (uint32_t)newcap64; - uint64_t dec = (uint64_t)p->buf->alloc - (uint64_t)newcap; - if (dec && ((p->buf->base & (PAGE_SIZE - 1)) == 0)) { - pfree((void*)(p->buf->base + (uintptr_t)newcap), dec); - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - p->buf->alloc = newcap; - p->cap = newcap; - } - + irq_restore(irq); return true; } bool netpkt_trim(netpkt_t* p, uint32_t new_len) { if (!p) return false; - if (new_len > p->len) return false; - p->len = new_len; - - if (p->flags & NETPKT_F_VIEW) return true; - if (!p->buf) return true; - if (p->buf->flags & NETPKT_BUF_F_EXTERNAL) return true; - if (p->buf->refs != 1) return true; - if (p->cap <= PAGE_SIZE) return true; - - uint64_t need = (uint64_t)p->head + (uint64_t)p->len; - if (!need) need = 1; - uint64_t newcap64 = count_pages(need, PAGE_SIZE) * (uint64_t)PAGE_SIZE; - if (newcap64 < PAGE_SIZE) newcap64 = PAGE_SIZE; - if (newcap64 >= (uint64_t)p->cap) return true; - - uint32_t newcap = (uint32_t)newcap64; - uint64_t dec = (uint64_t)p->buf->alloc - (uint64_t)newcap; - if (dec && ((p->buf->base & (PAGE_SIZE - 1)) == 0)) { - pfree((void*)(p->buf->base + (uintptr_t)newcap), dec); - if (g_netpkt_page_bytes >= dec) g_netpkt_page_bytes -= dec; - else g_netpkt_page_bytes = 0; - if (g_netpkt_payload_page_bytes >= dec) g_netpkt_payload_page_bytes -= dec; - else g_netpkt_payload_page_bytes = 0; - p->buf->alloc = newcap; - p->cap = newcap; + irq_flags_t irq = irq_save_disable(); + if (new_len > p->len) { + irq_restore(irq); + return false; } - + p->len = new_len; + irq_restore(irq); return true; } diff --git a/kernel/networking/netpkt.h b/kernel/networking/netpkt.h index 1f03c27f..394fa6a8 100644 --- a/kernel/networking/netpkt.h +++ b/kernel/networking/netpkt.h @@ -8,9 +8,9 @@ extern "C" { typedef struct netpkt netpkt_t; -typedef void (*netpkt_free_fn)(void* ctx, uintptr_t base, uint32_t alloc_size); +typedef void (*netpkt_free_fn)(void* ctx, uintptr_t base); -#define NETPKT_MAX_ALLOC 65536u +#define NETPKT_MAX_ALLOC 65535u #define NETPKT_MAX_PAGE_BYTES (32ull * 1024ull * 1024ull) netpkt_t* netpkt_alloc(uint32_t data_capacity, uint32_t headroom, uint32_t tailroom); @@ -22,6 +22,8 @@ void netpkt_unref(netpkt_t* p); uintptr_t netpkt_data(const netpkt_t* p); uint32_t netpkt_len(const netpkt_t* p); +//NOTE use this only for small reads to avoid unnecessary payload copies +bool netpkt_copyout(const netpkt_t* p, uint32_t off, void* dst, uint32_t len); uint32_t netpkt_headroom(const netpkt_t* p); uint32_t netpkt_tailroom(const netpkt_t* p); diff --git a/kernel/networking/network.cpp b/kernel/networking/network.cpp index c1b7e87e..b31a4df2 100644 --- a/kernel/networking/network.cpp +++ b/kernel/networking/network.cpp @@ -1,6 +1,8 @@ #include "network.h" +#include "networking/transport_layer/socket_core.h" +#include "networking/firewall.h" #include "network_dispatch.hpp" -#include "process/scheduler.h" +#include "networking/interface_manager.h" static NetworkDispatch *dispatch = 0; @@ -11,11 +13,9 @@ bool network_init(system_module *mod) { } void network_handle_download_interrupt_nic(uint16_t nic_id) { - if (dispatch) dispatch->handle_rx_irq((size_t)nic_id); } void network_handle_upload_interrupt_nic(uint16_t nic_id) { - if (dispatch) dispatch->handle_tx_irq((size_t)nic_id); } int network_net_task_entry(int argc, char* argv[]) { @@ -23,69 +23,52 @@ int network_net_task_entry(int argc, char* argv[]) { return 0; } -int net_tx_frame_on(uint16_t ifindex, uintptr_t frame_ptr, uint32_t frame_len) { - if (!dispatch || !frame_ptr || !frame_len) return -1; - return dispatch->enqueue_frame(ifindex, {frame_ptr, frame_len}) ? 0 : -1; +int net_tx_packet_on(uint8_t ifindex, netpkt_t* pkt) { + if (!dispatch || !pkt || !netpkt_len(pkt)) return -1; + l2_interface_t *l2 = l2_interface_find_by_index(ifindex); + if (!l2 || !l2->is_up) return -1; + if (l2->parent_ifindex) { + l2_interface_t *parent = l2_interface_find_by_index(l2->parent_ifindex); + if (!parent || !parent->is_up) return -1; + } + return dispatch->enqueue_packet(ifindex, pkt) ? 0 : -1; } -int net_rx_frame(sizedptr* out_frame) { - if (!out_frame) return -1; - out_frame->ptr = 0; - out_frame->size = 0; - return 0; -} - -const uint8_t* network_get_mac(uint16_t ifindex) { - static uint8_t dummy[6] = {0,0,0,0,0,0}; - if (!dispatch) return dummy; - const uint8_t* m = dispatch->mac(ifindex); - return m ? m : dummy; +const uint8_t* network_get_mac(uint8_t ifindex) { + return dispatch ? dispatch->mac(ifindex) : nullptr; } -uint16_t network_get_mtu(uint16_t ifindex) { +uint16_t network_get_device_mtu(uint8_t ifindex) { if (!dispatch) return 0; - return dispatch->mtu(ifindex); + return dispatch->device_mtu(ifindex); } -uint16_t network_get_header_size(uint16_t ifindex) { +uint16_t network_get_header_size(uint8_t ifindex) { if (!dispatch) return 0; return dispatch->header_size(ifindex); } -const char* network_get_ifname(uint16_t ifindex) { +const char* network_get_ifname(uint8_t ifindex) { if (!dispatch) return 0; return dispatch->ifname(ifindex); } -const char* network_get_hw_ifname(uint16_t ifindex) { - if (!dispatch) return 0; - return dispatch->hw_ifname(ifindex); -} - -size_t network_nic_count() { - if (!dispatch) return 0; - return dispatch->nic_count(); -} - -void network_net_set_pid(uint16_t pid) { - if (dispatch) dispatch->set_net_pid(pid); -} - -uint16_t network_net_get_pid() { - return dispatch ? dispatch->get_net_pid() : UINT16_MAX; -} - void network_dump_interfaces() { if (dispatch) dispatch->dump_interfaces(); } -bool network_sync_multicast(uint16_t ifindex, const uint8_t* macs, uint32_t count) { +bool network_sync_multicast(uint8_t ifindex, const uint8_t* macs, uint32_t count) { if (!dispatch) return false; - NetDriver* drv = dispatch->driver_at((uint8_t)ifindex); + NetDriver* drv = dispatch->driver_at(ifindex); if (!drv) return false; return drv->sync_multicast(macs, count); } +void network_cleanup_process(uint16_t pid) { + firewall_cleanup_process(pid); + socket_core_close_process(pid); +} + system_module net_module = (system_module){ .name = "net", .mount = "net", diff --git a/kernel/networking/network.h b/kernel/networking/network.h index d1278479..2509bab7 100644 --- a/kernel/networking/network.h +++ b/kernel/networking/network.h @@ -6,36 +6,30 @@ extern "C" { #include "types.h" #include "net/network_types.h" +#include "networking/netpkt.h" #include "files/system_module.h" #define NET_IRQ_BASE 40 //TODO: consider using the system MTU here #define MAX_PACKET_SIZE 0x1000 -void network_net_set_pid(uint16_t pid); -uint16_t network_net_get_pid(); bool network_init(); void network_handle_download_interrupt_nic(uint16_t nic_id); void network_handle_upload_interrupt_nic(uint16_t nic_id); int network_net_task_entry(int argc, char* argv[]); -int net_tx_frame(uintptr_t frame_ptr, uint32_t frame_len); -int net_tx_frame_on(uint16_t ifindex, uintptr_t frame_ptr, uint32_t frame_len); -int net_rx_frame(sizedptr *out_frame); +int net_tx_packet_on(uint8_t ifindex, netpkt_t* pkt); -const uint8_t* network_get_local_mac(void); -const uint8_t* network_get_mac(uint16_t ifindex); -uint16_t network_get_mtu(uint16_t ifindex); -uint16_t network_get_header_size(uint16_t ifindex); -const char* network_get_ifname(uint16_t ifindex); -const char* network_get_hw_ifname(uint16_t ifindex); -size_t network_nic_count(void); +const uint8_t* network_get_mac(uint8_t ifindex); +uint16_t network_get_device_mtu(uint8_t ifindex); +uint16_t network_get_header_size(uint8_t ifindex); +const char* network_get_ifname(uint8_t ifindex); -void network_update_local_ip(uint32_t ip); +void network_cleanup_process(uint16_t pid); void network_dump_interfaces(void); -bool network_sync_multicast(uint16_t ifindex, const uint8_t* macs, uint32_t count); +bool network_sync_multicast(uint8_t ifindex, const uint8_t* macs, uint32_t count); extern system_module net_module; diff --git a/kernel/networking/network_dispatch.cpp b/kernel/networking/network_dispatch.cpp index 5118c31d..7af41f9c 100644 --- a/kernel/networking/network_dispatch.cpp +++ b/kernel/networking/network_dispatch.cpp @@ -1,47 +1,26 @@ #include "network_dispatch.hpp" -#include "drivers/virtio_net_pci/virtio_net_pci.hpp" #include "drivers/net_bus.hpp" -#include "memory/page_allocator.h" #include "networking/link_layer/eth.h" -#include "net/network_types.h" -#include "port_manager.h" #include "std/memory.h" -#include "std/std.h" #include "console/kio.h" #include "networking/interface_manager.h" -#include "process/scheduler.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" #include "networking/netpkt.h" #include "networking/link_layer/link_utils.h" +#include "networking/transport_layer/csocket_packet.h" #include "networking/drivers/loopback/loopback_driver.hpp" +#include "exceptions/irq.h" -#define RX_INTR_BATCH_LIMIT 64 +#define TASK_RX_QUANTUM 32 +#define TASK_TX_QUANTUM 32 #define TASK_RX_BATCH_LIMIT 256 #define TASK_TX_BATCH_LIMIT 256 NetworkDispatch::NetworkDispatch() { nic_num = 0; - g_net_pid = 0xFFFF; - for (int i = 0; i <= (int)MAX_L2_INTERFACES; ++i) ifindex_to_nicid[i] = 0xFF; - for (size_t i = 0; i < MAX_NIC; ++i) { - nics[i].drv = nullptr; - nics[i].ifindex = 0; - nics[i].ifname_str[0] = 0; - nics[i].hwname_str[0] = 0; - nics[i].mtu_val = 0; - nics[i].hdr_sz = 0; - nics[i].speed_mbps = 0xFFFFFFFFu; - nics[i].duplex_mode = 0xFFu; - nics[i].kind_val = 0xFFu; - nics[i].rx_produced = 0; - nics[i].rx_consumed = 0; - nics[i].tx_produced = 0; - nics[i].tx_consumed = 0; - nics[i].rx_dropped = 0; - nics[i].tx_dropped = 0; - } + for (size_t i = 0; i < MAX_NIC; i++) nics[i] = nullptr; } bool NetworkDispatch::init() @@ -58,192 +37,141 @@ bool NetworkDispatch::init() for (int ix = 1; ix <= (int)MAX_L2_INTERFACES; ++ix){ int nid = nic_for_ifindex((uint8_t)ix); if (nid >= 0) { - const char* nm = nics[nid].ifname_str; - kprintf("[net] ifindex=%i -> nic_id=%i (%s)", ix, nid, nm ); + l2_interface_t* l2 = l2_interface_find_by_index((uint8_t)ix); + kprintf("[net] ifindex=%i -> nic_id=%i (%s)", ix, nid, l2 ? l2->name : "(null)"); } } return nic_num > 0; } -void NetworkDispatch::handle_rx_irq(size_t nic_id) -{ - if (nic_id >= nic_num) return; - if (!nics[nic_id].drv) return; -} - -void NetworkDispatch::handle_tx_irq(size_t nic_id) -{ - if (nic_id >= nic_num) return; - NetDriver* driver = nics[nic_id].drv; - if (!driver) return; - driver->handle_sent_packet(); -} - -bool NetworkDispatch::enqueue_frame(uint8_t ifindex, const sizedptr& frame) +bool NetworkDispatch::enqueue_packet(uint8_t ifindex, netpkt_t* pkt) { int nic_id = nic_for_ifindex(ifindex); if (nic_id < 0) return false; - NetDriver* driver = nics[nic_id].drv; - if (!driver) return false; - if (frame.size == 0) return false; + if (!pkt || !netpkt_len(pkt)) return false; + if (!nics[nic_id]->drv) return false; - sizedptr pkt = driver->allocate_packet(frame.size); - if (!pkt.ptr) return false; + irq_flags_t flags = irq_save_disable(); + int pushed = nics[nic_id]->tx.push(pkt); + irq_restore(flags); - uint16_t hs = nics[nic_id].hdr_sz; - void* dst = (void*)(pkt.ptr + hs); - memcpy(dst, (const void*)frame.ptr, frame.size); - - if (!nics[nic_id].tx.push(pkt)) { - free_frame(pkt); - nics[nic_id].tx_dropped++; - return false; - } - nics[nic_id].tx_produced++; - return true; + return pushed != 0; } int NetworkDispatch::net_task() { - set_net_pid(get_current_proc_pid()); + size_t first = 0; for (;;) { bool did_work = false; - - for (size_t n = 0; n < nic_num; ++n) { - NetDriver* driver = nics[n].drv; - if (driver) { - int lim = nics[n].kind_val == NET_IFK_LOCALHOST ? TASK_RX_BATCH_LIMIT : RX_INTR_BATCH_LIMIT; - for (int i = 0; i < lim; ++i) { - sizedptr raw = driver->handle_receive_packet(); - if (!raw.ptr || raw.size == 0) break; - if (raw.size < sizeof(eth_hdr_t)) { - free_frame(raw); - continue; + uint16_t rx_done[MAX_NIC] = {}; + uint16_t tx_done[MAX_NIC] = {}; + bool progress; + + do { + progress = false; + for (size_t step = 0; step < nic_num; step++) { + size_t n = (first + step) % nic_num; + NetDriver* driver = nics[n]->drv; + if (!driver) continue; + + uint16_t rx_round = 0; + uint16_t tx_round = 0; + while (rx_done[n] < TASK_RX_BATCH_LIMIT && rx_round < TASK_RX_QUANTUM) { + netpkt_t* pkt = driver->handle_receive_packet(); + if (!pkt) break; + if (!netpkt_len(pkt)) { + netpkt_unref(pkt); + break; } - if (!nics[n].rx.push(raw)) { - free_frame(raw); - nics[n].rx_dropped++; - continue; - } - nics[n].rx_produced++; - } - } - int processed = 0; - for (int i = 0; i < TASK_RX_BATCH_LIMIT; ++i) { - if (nics[n].rx.is_empty()) break; - sizedptr pkt{0,0}; - if (!nics[n].rx.pop(pkt)) break; - netpkt_t* np = netpkt_wrap(pkt.ptr, pkt.size, 0 , pkt.size, NULL, 0); - if (np) { - eth_input(nics[n].ifindex, np); - netpkt_unref(np); + + socket_packet_input(nics[n]->l2_ifindex, pkt); + if (netpkt_len(pkt) >= sizeof(eth_hdr_t)) eth_input(nics[n]->l2_ifindex, pkt); + netpkt_unref(pkt); + rx_done[n]++; + rx_round++; } - free_frame(pkt); - nics[n].rx_consumed++; - processed++; - } - if (processed) did_work = true; - } + driver->complete_rx_batch(); + driver->handle_sent_packet(); + while (tx_done[n] < TASK_TX_BATCH_LIMIT && tx_round < TASK_TX_QUANTUM) { + irq_flags_t flags = irq_save_disable(); + if (nics[n]->tx.is_empty()) { + irq_restore(flags); + break; + } + netpkt_t* pkt = nics[n]->tx.peek(); + irq_restore(flags); + + netdev_tx_result_t txr = driver->send_packet(pkt); + if (txr == NETDEV_TX_BUSY) break; + + flags = irq_save_disable(); + netpkt_t* popped = nullptr; + nics[n]->tx.pop(popped); + irq_restore(flags); - for (size_t n = 0; n < nic_num; ++n) { - NetDriver* driver = nics[n].drv; - if (!driver) continue; - int processed = 0; - for (int i = 0; i < TASK_TX_BATCH_LIMIT; ++i) { - if (nics[n].tx.is_empty()) break; - sizedptr pkt{0,0}; - if (!nics[n].tx.pop(pkt)) break; - if (!driver->send_packet(pkt)) { - free_frame(pkt); - nics[n].tx_dropped++; + if (txr == NETDEV_TX_DROP && popped) netpkt_unref(popped); + tx_done[n]++; + tx_round++; + } + driver->complete_tx_batch(); + if (rx_round || tx_round) { + progress = true; + did_work = true; } - nics[n].tx_consumed++; - processed++; } - if (processed) did_work = true; - } + } while (progress); + if (nic_num) first = (first + 1) % nic_num; if (!did_work) msleep(1);//TODO: manage it with an event } } -void NetworkDispatch::set_net_pid(uint16_t pid) -{ - g_net_pid = pid; -} - -uint16_t NetworkDispatch::get_net_pid() const -{ - return g_net_pid; -} - -size_t NetworkDispatch::nic_count() const -{ - return nic_num; -} - const char* NetworkDispatch::ifname(uint8_t ifindex) const { - int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? nullptr : nics[nic_id].ifname_str; + l2_interface_t* l2 = l2_interface_find_by_index(ifindex); + return l2 ? l2->name : nullptr; } const char* NetworkDispatch::hw_ifname(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? nullptr : nics[nic_id].hwname_str; + return nic_id < 0 ? nullptr : nics[nic_id]->hwname_str; } const uint8_t* NetworkDispatch::mac(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? nullptr : nics[nic_id].mac_addr; + return nic_id < 0 ? nullptr : nics[nic_id]->mac_addr; } -uint16_t NetworkDispatch::mtu(uint8_t ifindex) const +uint16_t NetworkDispatch::device_mtu(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? 0 : nics[nic_id].mtu_val; + return nic_id < 0 || !nics[nic_id]->drv ? 0 : nics[nic_id]->drv->get_mtu(); } uint16_t NetworkDispatch::header_size(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? 0 : nics[nic_id].hdr_sz; -} - -l2_interface_t* NetworkDispatch::l2_at(uint8_t ifindex) const -{ - return l2_interface_find_by_index(ifindex); + return nic_id < 0 ? 0 : nics[nic_id]->hdr_sz; } NetDriver* NetworkDispatch::driver_at(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? nullptr : nics[nic_id].drv; + return nic_id < 0 ? nullptr : nics[nic_id]->drv; } uint32_t NetworkDispatch::speed(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? 0xFFFFFFFFu : nics[nic_id].speed_mbps; + return nic_id < 0 ? 0xFFFFFFFFu : nics[nic_id]->speed_mbps; } uint8_t NetworkDispatch::duplex(uint8_t ifindex) const { int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? 0xFFu : nics[nic_id].duplex_mode; -} - -uint8_t NetworkDispatch::kind(uint8_t ifindex) const -{ - int nic_id = nic_for_ifindex(ifindex); - return nic_id < 0 ? 0xFFu : nics[nic_id].kind_val; -} - -void NetworkDispatch::free_frame(const sizedptr &f) -{ - if (f.ptr) free_sized((void*)f.ptr, f.size); + return nic_id < 0 ? 0xFFu : nics[nic_id]->duplex_mode; } bool NetworkDispatch::register_all_from_bus() { @@ -258,13 +186,14 @@ bool NetworkDispatch::register_all_from_bus() { uint16_t hs = net_bus_get_header_size(i); uint32_t sp = net_bus_get_speed_mbps(i); uint8_t dp = net_bus_get_duplex(i); - uint8_t kd = net_bus_get_kind(i); + NetDevKind kd = net_bus_get_kind(i); uint8_t macbuf[6]; net_bus_get_mac(i, macbuf); if (!name) continue; + bool owns_driver = false; if (!drv) { - if (kd != NET_IFK_LOCALHOST) continue; + if (kd != NET_DEV_LOOPBACK) continue; LoopbackDriver* lo_drv = new LoopbackDriver(); if (!lo_drv) continue; if (!lo_drv->init_at(0, 0)) { @@ -272,64 +201,70 @@ bool NetworkDispatch::register_all_from_bus() { continue; } drv = lo_drv; + owns_driver = true; if (!hs) hs = 0; if (!m) m = 65535; } - uint16_t type_cost = - (kd == NET_IFK_LOCALHOST) ? 0 : - (kd == NET_IFK_ETH) ? 20 : - (kd == NET_IFK_WIFI) ? 40 : 30; - - uint16_t speed_cost = - (sp == 0xFFFFFFFFu) ? 10 : - (sp >= 40000u) ? 0 : - (sp >= 10000u) ? 2 : - (sp >= 1000u) ? 5 : - (sp >= 100u) ? 15 : 40; - - uint16_t duplex_cost = - (dp == 1) ? 0 : - (dp == 0) ? 20 : 5; - - uint16_t mtu_cost = - (m >= 9000u) ? 0 : - (m >= 2000u) ? 3 : - (m >= 1500u) ? 5 : - (m >= 576u) ? 15 : 100; - - uint16_t base_metric = (kd == NET_IFK_LOCALHOST) ? 0 : (uint16_t)(type_cost + speed_cost + duplex_cost + mtu_cost); - - NICCtx* c = &nics[nic_num]; + uint16_t type_cost = 30; + if (kd == NET_DEV_LOOPBACK) type_cost = 0; + else if (kd == NET_DEV_ETH) type_cost = 20; + else if (kd == NET_DEV_WIFI) type_cost = 40; + + uint16_t speed_cost = 40; + if (sp == 0xFFFFFFFFu) speed_cost = 10; + else if (sp >= 40000u) speed_cost = 0; + else if (sp >= 10000u) speed_cost = 2; + else if (sp >= 1000u) speed_cost = 5; + else if (sp >= 100u) speed_cost = 15; + + uint16_t duplex_cost = 5; + if (dp == 1) duplex_cost = 0; + else if (dp == 0) duplex_cost = 20; + + uint16_t mtu_cost = 100; + if (m >= 9000u) mtu_cost = 0; + else if (m >= 2000u) mtu_cost = 3; + else if (m >= 1500u) mtu_cost = 5; + else if (m >= 576u) mtu_cost = 15; + + uint16_t base_metric = 0; + if (kd != NET_DEV_LOOPBACK) base_metric = (uint16_t)(type_cost + speed_cost + duplex_cost + mtu_cost); + + NICCtx* c = new NICCtx(); + if (!c) { + if (owns_driver) delete drv; + continue; + } c->drv = drv; - strncpy(c->ifname_str, name, (int)sizeof(c->ifname_str)); strncpy(c->hwname_str, hw, (int)sizeof(c->hwname_str)); - memcpy(c->mac_addr, macbuf, 6); - c->mtu_val = m; + mac_copy(c->mac_addr, macbuf); c->hdr_sz = hs; c->speed_mbps = sp; c->duplex_mode = dp; - c->kind_val = kd; - - uint8_t ix = l2_interface_create(c->ifname_str, (void*)drv, base_metric, kd); - l2_interface_set_up(ix, true); - c->ifindex = ix; - if (ix <= MAX_L2_INTERFACES) ifindex_to_nicid[ix] = (uint8_t)nic_num; + uint8_t ix = l2_interface_create(name, (uint8_t)nic_num, base_metric, kd); + if (!ix) { + delete c; + if (owns_driver) delete drv; + continue; + } + c->l2_ifindex = ix; + nics[nic_num] = c; nic_num += 1; + (void)l2_interface_set_up(ix, true); } return nic_num > 0; } int NetworkDispatch::nic_for_ifindex(uint8_t ifindex) const { - if (!ifindex) return -1; - if (ifindex > MAX_L2_INTERFACES) return -1; - uint8_t nic_id = ifindex_to_nicid[ifindex]; - if (nic_id == 0xFF) return -1; - if (nic_id >= nic_num) return -1; - return (int)nic_id; + l2_interface_t *l2 = l2_interface_find_by_index(ifindex); + if (!l2 || l2->nic_id >= nic_num) return -1; + uint8_t physical = l2_physical_ifindex(l2); + if (!nics[l2->nic_id] || nics[l2->nic_id]->l2_ifindex != physical) return -1; + return (int)l2->nic_id; } void NetworkDispatch::dump_interfaces() @@ -341,25 +276,27 @@ void NetworkDispatch::dump_interfaces() if (!l2) continue; int nid = nic_for_ifindex(ifx); - kprintf("int l2 %u: name=%s up=%u ipv4_count=%u ipv6_count=%u arp=%x nd=%x mcast4=%u mcast6=%u", - (unsigned)ifx, l2->name, l2->is_up?1:0, + kprintf("int l2 %u: name=%s up=%u gen=%u dev=%u link=%u parent=%u vlan=%u mtu=%u ipv4_count=%u ipv6_count=%u arp=%x nd=%x mcast4=%u mcast6=%u", + (unsigned)ifx, l2->name, l2->is_up?1:0, (unsigned)l2->generation, + (unsigned)l2->dev_kind, (unsigned)l2->link_kind, (unsigned)l2->parent_ifindex, + (unsigned)l2->vlan_id, (unsigned)device_mtu(ifx), (unsigned)l2->ipv4_count, (unsigned)l2->ipv6_count, (uint64_t)(uintptr_t)l2->arp_table, (uint64_t)(uintptr_t)l2->nd_table, (unsigned)l2->ipv4_mcast_count, (unsigned)l2->ipv6_mcast_count); if (nid >= 0){ char macs[18]; - mac_to_string(nics[nid].mac_addr, macs); + mac_to_string(nics[nid]->mac_addr, macs); - const char* dpx = (nics[nid].duplex_mode == 0) ? "half" : (nics[nid].duplex_mode == 1) ? "full" : "unknown"; + const char* dpx = "unknown"; + if (nics[nid]->duplex_mode == 0) dpx = "half"; + else if (nics[nid]->duplex_mode == 1) dpx = "full"; - kprintf(" driver: nic_id=%u ifname=%s hw=%s mtu=%u hdr=%u mac=%s drv=%x spd=%u dup=%s kind=%u", - (unsigned)nid, - nics[nid].ifname_str[0] ? nics[nid].ifname_str : "(null)", - nics[nid].hwname_str[0] ? nics[nid].hwname_str : "(null)", - (unsigned)nics[nid].mtu_val, (unsigned)nics[nid].hdr_sz, macs, - (uint64_t)(uintptr_t)nics[nid].drv, - (unsigned)nics[nid].speed_mbps, dpx, (unsigned)nics[nid].kind_val); + kprintf(" driver: nic_id=%u ifname=%s hw=%s hdr=%u mac=%s drv=%x spd=%u dup=%s", + (unsigned)nid, l2->name, + nics[nid]->hwname_str[0] ? nics[nid]->hwname_str : "(null)", + (unsigned)nics[nid]->hdr_sz, macs, (uint64_t)(uintptr_t)nics[nid]->drv, + (unsigned)nics[nid]->speed_mbps, dpx); } else { kprintf(" driver: none"); } diff --git a/kernel/networking/network_dispatch.hpp b/kernel/networking/network_dispatch.hpp index bb3bda67..f62f7855 100644 --- a/kernel/networking/network_dispatch.hpp +++ b/kernel/networking/network_dispatch.hpp @@ -1,9 +1,7 @@ #pragma once #include "types.h" -#include "std/std.h" #include "drivers/net_driver.hpp" #include "net/network_types.h" -#include "networking/internet_layer/ipv4.h" #include "interface_manager.h" #include "data/struct/ring_buffer.hpp" @@ -12,64 +10,39 @@ class NetworkDispatch { NetworkDispatch(); bool init(); - void handle_rx_irq(size_t nic_id); - void handle_tx_irq(size_t nic_id); - - bool enqueue_frame(uint8_t ifindex, const sizedptr&); + bool enqueue_packet(uint8_t ifindex, netpkt_t* pkt); int net_task(); - void set_net_pid(uint16_t pid); - uint16_t get_net_pid() const; - - size_t nic_count() const; - const char* ifname(uint8_t ifindex) const; const char* hw_ifname(uint8_t ifindex) const; const uint8_t* mac(uint8_t ifindex) const; - uint16_t mtu(uint8_t ifindex) const; + uint16_t device_mtu(uint8_t ifindex) const; uint16_t header_size(uint8_t ifindex) const; - l2_interface_t* l2_at(uint8_t ifindex) const; NetDriver* driver_at(uint8_t ifindex) const; uint32_t speed(uint8_t ifindex) const; uint8_t duplex(uint8_t ifindex) const; - uint8_t kind(uint8_t ifindex) const; void dump_interfaces(); private: struct NICCtx { NetDriver* drv; - uint8_t ifindex; - char ifname_str[16]; + uint8_t l2_ifindex; char hwname_str[32]; uint8_t mac_addr[6]; - uint16_t mtu_val; uint16_t hdr_sz; uint32_t speed_mbps; uint8_t duplex_mode; - uint8_t kind_val; - RingBuffer tx; - RingBuffer rx; - uint64_t rx_produced; - uint64_t rx_consumed; - uint64_t tx_produced; - uint64_t tx_consumed; - uint64_t rx_dropped; - uint64_t tx_dropped; + RingBuffer tx; }; - static const size_t MAX_NIC = 16; + static const size_t MAX_NIC = MAX_L2_INTERFACES; - NICCtx nics[MAX_NIC]; + NICCtx* nics[MAX_NIC]; size_t nic_num; - uint16_t g_net_pid; - uint8_t ifindex_to_nicid[MAX_L2_INTERFACES + 1]; - void free_frame(const sizedptr&); bool register_all_from_bus(); - void copy_str(char* dst, int cap, const char* src); - int nic_for_ifindex(uint8_t ifindex) const; }; diff --git a/kernel/networking/port_manager.c b/kernel/networking/port_manager.c deleted file mode 100644 index 0af1a7ba..00000000 --- a/kernel/networking/port_manager.c +++ /dev/null @@ -1,107 +0,0 @@ -#include "networking/port_manager.h" -#include "types.h" -#include "random/random.h" -#include "net/network_types.h" - -static inline bool proto_valid(protocol_t proto) { - return (uint32_t)proto < PROTO_COUNT; -} - -void port_manager_init(port_manager_t* pm) { - if (!pm) return; - for (uint32_t pr = 0; pr < PROTO_COUNT; ++pr) { - for (uint32_t p = 0; p < MAX_PORTS; ++p) { - pm->tab[pr][p].used = false; - pm->tab[pr][p].pid = PORT_FREE_OWNER; - pm->tab[pr][p].handler = NULL; - } - } -} - -int port_alloc_ephemeral(port_manager_t* pm, - protocol_t proto, - uint16_t pid, - port_recv_handler_t handler) -{ - if (!pm || !proto_valid(proto)) return -1; - - rng_t rng; - rng_init_random(&rng); - uint32_t seed = rng_next32(&rng); - - const uint32_t minp = (uint32_t)PORT_MIN_EPHEMERAL; - const uint32_t maxp = (uint32_t)PORT_MAX_EPHEMERAL; - const uint32_t range = maxp - minp + 1u; - const uint32_t first = minp + (seed % range); - - for (uint32_t i = 0; i < range; ++i) { - uint32_t p = minp + ((first - minp + i) % range); - port_entry_t *e = &pm->tab[proto][p]; - if (!e->used) { - e->used = true; - e->pid = pid; - e->handler = handler; - return (int)p; - } - } - return -1; -} - -bool port_bind_manual(port_manager_t* pm, - protocol_t proto, - uint16_t port, - uint16_t pid, - port_recv_handler_t handler) -{ - if (!pm || !proto_valid(proto)) return false; - port_entry_t *e = &pm->tab[proto][port]; - if (e->used) return false; - e->used = true; - e->pid = pid; - e->handler = handler; - return true; -} - -bool port_unbind(port_manager_t* pm, - protocol_t proto, - uint16_t port, - uint16_t pid) -{ - if (!pm || !proto_valid(proto)) return false; - port_entry_t *e = &pm->tab[proto][port]; - if (!e->used || e->pid != pid) return false; - e->used = false; - e->pid = PORT_FREE_OWNER; - e->handler = NULL; - return true; -} - -void port_unbind_all(port_manager_t* pm, uint16_t pid) { - if (!pm) return; - for (uint32_t pr = 0; pr < PROTO_COUNT; ++pr) { - for (uint32_t p = 1; p < MAX_PORTS; ++p) { - port_entry_t *e = &pm->tab[pr][p]; - if (e->used && e->pid == pid) { - e->used = false; - e->pid = PORT_FREE_OWNER; - e->handler = NULL; - } - } - } -} - -bool port_is_bound(const port_manager_t* pm, protocol_t proto, uint16_t port) { - if (!pm || !proto_valid(proto)) return false; - return pm->tab[proto][port].used; -} - -uint16_t port_owner_of(const port_manager_t* pm, protocol_t proto, uint16_t port) { - if (!pm || !proto_valid(proto)) return PORT_FREE_OWNER; - return pm->tab[proto][port].pid; -} - -port_recv_handler_t port_get_handler(const port_manager_t* pm, protocol_t proto, uint16_t port) { - if (!pm || !proto_valid(proto)) return NULL; - const port_entry_t* e = &pm->tab[proto][port]; - return e->used ? e->handler : NULL; -} diff --git a/kernel/networking/port_manager.h b/kernel/networking/port_manager.h deleted file mode 100644 index e2cde304..00000000 --- a/kernel/networking/port_manager.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include "types.h" -#include "net/network_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define MAX_PORTS 65536 -#define PORT_MIN_EPHEMERAL 49152 -#define PORT_MAX_EPHEMERAL 65535 -#define PORT_FREE_OWNER 0xFFFF - -#define PROTO_COUNT 2 - -typedef uint32_t (*port_recv_handler_t)( - uint8_t ifindex, - ip_version_t ipver, - const void* src_ip_addr, - const void* dst_ip_addr, - uintptr_t frame_ptr, - uint32_t frame_len, - uint16_t src_port, - uint16_t dst_port -); - -typedef struct { - uint16_t pid; - port_recv_handler_t handler; - bool used; -} port_entry_t; - -typedef struct { - port_entry_t tab[PROTO_COUNT][MAX_PORTS]; -} port_manager_t; - -void port_manager_init(port_manager_t* pm); - -int port_alloc_ephemeral(port_manager_t* pm, - protocol_t proto, - uint16_t pid, - port_recv_handler_t handler); - -bool port_bind_manual(port_manager_t* pm, - protocol_t proto, - uint16_t port, - uint16_t pid, - port_recv_handler_t handler); - -bool port_unbind(port_manager_t* pm, - protocol_t proto, - uint16_t port, - uint16_t pid); - -void port_unbind_all(port_manager_t* pm, uint16_t pid); - -bool port_is_bound(const port_manager_t* pm, protocol_t proto, uint16_t port); -uint16_t port_owner_of(const port_manager_t* pm, protocol_t proto, uint16_t port); -port_recv_handler_t port_get_handler(const port_manager_t* pm, protocol_t proto, uint16_t port); - -#ifdef __cplusplus -} -#endif diff --git a/kernel/networking/processes/net_proc.c b/kernel/networking/processes/net_proc.c index c9554089..78156b3a 100644 --- a/kernel/networking/processes/net_proc.c +++ b/kernel/networking/processes/net_proc.c @@ -1,7 +1,6 @@ #include "net_proc.h" #include "kernel_processes/kprocess_loader.h" #include "process/scheduler.h" -#include "console/kio.h" #include "std/memory.h" #include "std/string.h" @@ -12,338 +11,247 @@ #include "networking/link_layer/arp.h" #include "networking/link_layer/ndp.h" -#include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv4_utils.h" - -#include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv6_utils.h" -#include "net/checksums.h" -#include "networking/transport_layer/csocket_udp.h" +#include "networking/transport_layer/csocket.h" #include "networking/transport_layer/trans_utils.h" #include "networking/application_layer/csocket_http_client.h" -#include "networking/application_layer/csocket_http_server.h" +#include "networking/application_layer/http_webserver.h" #include "networking/application_layer/dhcp_daemon.h" -#include "networking/application_layer/dns/dns_daemon.h" #include "networking/application_layer/dns/mdns_responder.h" -#include "networking/application_layer/dns/dns.h" -#include "networking/application_layer/sntp_daemon.h" #include "networking/application_layer/ntp.h" #include "networking/application_layer/ntp_daemon.h" #include "networking/application_layer/dhcpv6_daemon.h" -#include "networking/application_layer/ssdp_daemon.h" #include "exceptions/timer.h" -#include "syscalls/syscalls.h" -#include "memory/page_allocator.h" -#include "memory/mmu.h" +#define HTTP_PORT 80 +#define PROBE_PORT 8080 +#define PROBE_TIMEOUT_MS 2000 +#define PROBE_INTERVAL_MS 50 static int udp_probe_server(uint32_t probe_ip, uint16_t probe_port, net_l4_endpoint *out_l4) { - socket_handle_t sock = udp_socket_create(SOCK_ROLE_CLIENT, (uint16_t)get_current_proc_pid(), NULL); - if (!sock) + socket_handle_t sock = create_socket(PROTO_UDP, &(SocketOptions){.flags = SOCK_OPT_NONBLOCK}); + if (!sock) return 0; + if (set_socket_option(sock, SOCK_OPT_BROADCAST_ALLOWED, NULL, 0) < 0) { + close_socket(sock); return 0; + } net_l4_endpoint dst; - make_ep(probe_ip, probe_port, IP_VER4, &dst); + make_ep(&probe_ip, probe_port, IP_VER4, &dst); static const char greeting[] = "hello"; - if (socket_sendto_udp_ex(sock, DST_ENDPOINT, &dst, 0, greeting, sizeof(greeting)) < 0) { - socket_close_udp(sock); - socket_destroy_udp(sock); + if (send_to_socket(sock, &dst, greeting, sizeof(greeting)) < 0) { + close_socket(sock); return 0; } char recv_buf[64]; + net_l4_endpoint src = (net_l4_endpoint){0}; uint32_t waited = 0; - const uint32_t TIMEOUT_MS = 2000; - const uint32_t INTERVAL_MS = 50; int64_t recvd = 0; - net_l4_endpoint src = (net_l4_endpoint){0}; - while (waited < TIMEOUT_MS) { - recvd = socket_recvfrom_udp_ex(sock, recv_buf, sizeof(recv_buf), &src); - if (recvd > 0) - break; - msleep(INTERVAL_MS); - waited += INTERVAL_MS; + while (waited < PROBE_TIMEOUT_MS) { + recvd = receive_from_socket(sock, recv_buf, sizeof(recv_buf), &src); + if (recvd > 0) break; + msleep(PROBE_INTERVAL_MS); + waited += PROBE_INTERVAL_MS; } - socket_close_udp(sock); - socket_destroy_udp(sock); + close_socket(sock); if (recvd <= 0) return 0; if (out_l4) *out_l4 = src; return 1; } -static void free_request(HTTPRequestMsg *req) { - if (!req) return; - - if (req->path.mem_length) string_free(req->path); - - http_headers_common_free(&req->headers_common); - http_headers_extra_free(req->extra_headers, req->extra_header_count); - req->extra_headers = NULL; - req->extra_header_count = 0; - - if (req->body.ptr && req->body.size) free_sized((void*)req->body.ptr, req->body.size); - - req->path = (string){0}; - req->body = (sizedptr){0}; -} - - static void run_http_server() { - //mmu_enable_verbose(); - //page_alloc_enable_verbose(); - uint16_t pid = get_current_proc_pid(); - SocketExtraOptions opt = {0}; - opt.debug_level = SOCK_DBG_ALL; - opt.flags = SOCK_OPT_DEBUG; - http_server_handle_t srv = http_server_create(pid, &opt); - if (!srv) { - stop_current_process(1); - return; - } - struct SockBindSpec spec = {0}; - spec.kind = BIND_ANY; - if (http_server_bind(srv, &spec, 80) < 0) { - http_server_destroy(srv); - stop_current_process(2); - return; - } - - if (http_server_listen(srv, 4) < 0) { - http_server_close(srv); - http_server_destroy(srv); - stop_current_process(3); - return; - } - - mdns_register_service("RedactedOS", "http", "tcp", 80, "path=/"); - - static const char HTML_ROOT[] = + static const char HTTP_ROOT_BODY[] = + "\n" + "RedactedOS\n" + "\n" + "\n" + "\n" "

Hello, world!

\n" - "

[Redacted]

"; + "

[Redacted]

" + "

\n" + "

download test

\n" + "\n"; - static const char HTML_404[] = + static char HTML_404[] = "

404 Regrettably, no such page exists in this realm

\n" "

Im rather inclined to deduce that your page simply does not exist. Given the state of affairs, I dare say it's not altogether surprising, innit?

"; - const string STR_OK = string_from_const("OK"); - const string STR_HTML = string_from_const("text/html"); - const string STR_CLOSE = string_from_const("close"); - const string STR_NOTFOUND= string_from_const("Not Found"); - - while (1) { - http_connection_handle_t conn = http_server_accept(srv); - if (!conn){ - msleep(50); - continue; - } - HTTPRequestMsg req = http_server_recv_request(srv, conn); - if (req.path.length) { - char tmp[128] = {0}; - uint32_t n = req.path.length < sizeof(tmp) - 1 ? req.path.length : sizeof(tmp) - 1; - memcpy(tmp, req.path.data, n); - } - - HTTPResponseMsg res = (HTTPResponseMsg){0}; - - if (req.path.length == 1 && req.path.data[0] == '/') { - res.status_code = HTTP_OK; - res.reason = STR_OK; - res.headers_common.length = sizeof(HTML_ROOT) - 1; - res.headers_common.type = STR_HTML; - res.headers_common.connection = STR_CLOSE; - res.body.ptr = (uintptr_t)HTML_ROOT; - res.body.size = sizeof(HTML_ROOT) - 1; - } else { - res.status_code = HTTP_NOT_FOUND; - res.reason = STR_NOTFOUND; - res.headers_common.length = sizeof(HTML_404) - 1; - res.headers_common.type = STR_HTML; - res.headers_common.connection = STR_CLOSE; - res.body.ptr = (uintptr_t)HTML_404; - res.body.size = sizeof(HTML_404) - 1; - } - - http_server_send_response(srv, conn, &res); - http_connection_close(conn); - free_request(&req); - } + static const HTTPRoute routes[] = { + { + .path = "/", + .methods = HTTP_METHOD_MASK_GET, + .flags = HTTP_ROUTE_HEAD_AS_GET, + .kind = HTTP_ROUTE_STATIC, + .as.response = { + .status = HTTP_OK, + .content_type = "text/html", + .body = HTTP_ROOT_BODY, + .body_len = sizeof(HTTP_ROOT_BODY) - 1 + } + }, + { + .path = "/assets/test.png", + .methods = HTTP_METHOD_MASK_GET, + .flags = HTTP_ROUTE_HEAD_AS_GET, + .kind = HTTP_ROUTE_FILE, + .as.file = { + .fs_path = "/boot/redos/system/demo.red/resources/test.png", + .content_type = "image/png", + .cache_max_age_sec = 60 + } + }, + { + .path = "/favicon.ico", + .methods = HTTP_METHOD_MASK_GET, + .flags = HTTP_ROUTE_HEAD_AS_GET, + .kind = HTTP_ROUTE_FILE, + .as.file = { + .fs_path = "/boot/redos/system/demo.red/resources/test.png", + .content_type = "image/png", + .cache_max_age_sec = 60 + } + }, + }; + + HTTPWebServerConfig config = {0}; + config.port = HTTP_PORT; + config.backlog = 4; + config.routes = routes; + config.route_count = N_ARR(routes); + config.not_found = HTTP_WEB_HTML_RESPONSE(HTTP_NOT_FOUND, HTML_404); + config.mdns_instance = "RedactedOS"; + config.mdns_type = "http"; + config.mdns_proto = "tcp"; + config.mdns_txt = "path=/"; + config.firewall_allow = true; + + http_webserver_run(&config); + stop_current_process(0); } static void test_http(const net_l4_endpoint* ep) { + if (!ep) return; if (ep->ver == IP_VER4) { uint32_t ip_u32; memcpy(&ip_u32, ep->ip, 4); char ip_str[16]; ipv4_to_string(ip_u32, ip_str); - kprintf("[HTTP] GET %s:80", ip_str); + print("[HTTP] GET %s:%i", ip_str, HTTP_PORT); } - uint16_t pid = get_current_proc_pid(); - http_client_handle_t cli = http_client_create(pid, NULL); - if (!cli) { - kprintf("[HTTP] http_client_create FAIL"); - return; - } + http_client_handle_t cli = http_client_create(NULL, NULL); + if (!cli) return; net_l4_endpoint e = {0}; e.ver = ep->ver; + e.port = HTTP_PORT; if (e.ver == IP_VER4) memcpy(e.ip, ep->ip, 4); - else if (e.ver == IP_VER6) memcpy(e.ip, ep->ip, 16); + else if (e.ver == IP_VER6) ipv6_cpy(e.ip, ep->ip); else { http_client_destroy(cli); return; } - e.port = 80; - int rc = http_client_connect_ex(cli, DST_ENDPOINT, &e, 0); - if (rc < 0) { + if (http_client_connect_endpoint(cli, &e) < 0) { http_client_destroy(cli); return; } HTTPRequestMsg req = (HTTPRequestMsg){0}; req.method = HTTP_METHOD_GET; + req.version = HTTP_VERSION_11; req.path = string_from_const("/"); - req.headers_common.connection = string_from_const("close"); + req.headers_common.fields.connection = string_from_const("close"); HTTPResponseMsg resp = http_client_send_request(cli, &req); - if ((int)resp.status_code < 0) { - kprintf("[HTTP] request FAIL status=%i", (int)resp.status_code); - http_client_close(cli); - http_client_destroy(cli); - return; - } - - if (resp.body.ptr && resp.body.size > 0) { - char *body_str = (char*)malloc(resp.body.size + 1); + if ((int)resp.status_code < 0) print("[HTTP] request FAIL status=%i", (int)resp.status_code); + else if (resp.body.data && resp.body.length) { + char *body_str = (char*)zalloc(resp.body.length + 1); if (body_str) { - memcpy(body_str, (void*)resp.body.ptr, resp.body.size); - body_str[resp.body.size] = '\0'; - kprintf("[HTTP] %i %i bytes of body", resp.status_code, resp.body.size); - kprintf("%s", body_str); - free_sized(body_str, resp.body.size + 1); + memcpy(body_str, (void*)resp.body.data, resp.body.length); + body_str[resp.body.length] = '\0'; + print("[HTTP] %i %i bytes of body", resp.status_code, resp.body.length); + print("%s", body_str); + release(body_str); } } http_client_close(cli); http_client_destroy(cli); - if (resp.body.ptr && resp.body.size) free_sized((void*)resp.body.ptr, resp.body.size); - - http_headers_common_free(&resp.headers_common); - - if (resp.reason.data && resp.reason.mem_length) string_free(resp.reason); - http_headers_extra_free(resp.extra_headers, resp.extra_header_count); -} - -static int ifv4_is_ready_nonlocal(const l3_ipv4_interface_t* ifv4) { - if (!ifv4) return 0; - if (ifv4->mode == IPV4_CFG_DISABLED) return 0; - if (!ifv4->ip) return 0; - if (ifv4->is_localhost) return 0; - if ((ifv4->ip & 0xFF000000u) == 0x7F000000u) return 0; - return 1; -} - -static int ifv6_is_ready_nonlocal(const l3_ipv6_interface_t* ifv6) { - if (!ifv6) return 0; - if (ifv6->cfg == IPV6_CFG_DISABLE) return 0; - if (ifv6->is_localhost) return 0; - if (ipv6_is_unspecified(ifv6->ip)) return 0; - if (ifv6->dad_state != IPV6_DAD_OK) return 0; - if (ipv6_is_loopback(ifv6->ip)) return 0; - return 1; -} - -static int any_ipv4_ready(void) { - uint8_t n_if = l2_interface_count(); - for (uint8_t i = 0; i < n_if; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2 || !l2->is_up) continue; - for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { - l3_ipv4_interface_t* ifv4 = l2->l3_v4[j]; - if (ifv4_is_ready_nonlocal(ifv4)) return 1; - } - } - return 0; -} - -static int any_ipv6_ready(void) { - uint8_t n_if = l2_interface_count(); - for (uint8_t i = 0; i < n_if; i++) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2 || !l2->is_up) continue; - for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE; j++) { - l3_ipv6_interface_t* ifv6 = l2->l3_v6[j]; - if (ifv6_is_ready_nonlocal(ifv6)) return 1; - } - } - return 0; + http_response_free(&resp); + http_request_free(&req); } static int ntp(int argc, char* argv[]) { + (void)argc; (void)argv; if (!ntp_is_running()) { - kprintf("[TIME] starting NTP..."); + print("[TIME] starting NTP..."); create_kernel_process("ntpd", ntp_daemon_entry, 0, 0); uint32_t waited = 0; const uint32_t step = 200; const uint32_t timeout = 10000; while (!timer_is_synchronised() && waited < timeout) { - if ((waited % 1000) == 0) kprintf("[TIME] waiting NTP sync..."); + if ((waited % 1000) == 0) print("[TIME] waiting NTP sync..."); msleep(step); waited += step; - } - if (!timer_is_synchronised()) kprintf("[TIME] NTP sync timeout, continuing"); } timer_set_timezone_minutes(120); - kprintf("[TIME]timezone offset %i minutes", (int32_t)timer_get_timezone_minutes()); + print("[TIME]timezone offset %i minutes", (int32_t)timer_get_timezone_minutes()); DateTime now_dt_utc, now_dt_loc; if (timer_now_datetime(&now_dt_utc, 0)) { char s[20]; - timer_datetime_to_string(&now_dt_utc, s, sizeof s); - kprintf("[TIME] UTC: %s", s); + timer_datetime_to_string(&now_dt_utc, s, sizeof(s)); + print("[TIME] UTC: %s", s); } if (timer_now_datetime(&now_dt_loc, 1)) { char s[20]; - timer_datetime_to_string(&now_dt_loc, s, sizeof s); - kprintf("[TIME] LOCAL: %s (TZ %i min)", s, (int32_t)timer_get_timezone_minutes()); + timer_datetime_to_string(&now_dt_loc, s, sizeof(s)); + print("[TIME] LOCAL: %s (TZ %i min)", s, (int32_t)timer_get_timezone_minutes()); } return 0; } -static void test_net_for_interface(l3_ipv4_interface_t* ifv4) { - if (!ifv4_is_ready_nonlocal(ifv4)) return; - char ip_str[16]; - char mask_str[16]; - char gw_str[16]; - ipv4_to_string(ifv4->ip, ip_str); - ipv4_to_string(ifv4->mask, mask_str); - ipv4_to_string(ifv4->gw, gw_str); - uint32_t probe_ip = (ifv4->ip && ifv4->mask) ? ipv4_broadcast_calc(ifv4->ip, ifv4->mask) : 0; - if (!probe_ip) return; - char probe_str[16]; - ipv4_to_string(probe_ip, probe_str); - kprintf("[NET] probing %s (l3_id=%u)", probe_str, (unsigned)ifv4->l3_id); - net_l4_endpoint srv = (net_l4_endpoint){0}; - if (udp_probe_server(probe_ip, 8080, &srv)) { - test_http(&srv); - } else { - kprintf("[NET] no UDP responder at %s:8080 (l3_id=%u)", probe_str, (unsigned)ifv4->l3_id); +static int net_test_entry(int argc, char *argv[]) { + (void)argc; (void)argv; + + for (;;) { + bool ready = false; + uint8_t n_if = l2_interface_count(); + for (uint8_t i = 0; i < n_if && !ready; i++) { + l2_interface_t *l2 = l2_interface_at(i); + if (!l2 || !l2->is_up) continue; + + for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { + l3_ipv4_interface_t *v4 = l2->l3_v4[j]; + if (ipv4_l3_is_ready(v4) && !v4->is_localhost && !ipv4_is_loopback(v4->ip)) { + ready = true; + break; + } + } + for (uint8_t j = 0; j < MAX_IPV6_PER_INTERFACE && !ready; j++) { + l3_ipv6_interface_t *v6 = l2->l3_v6[j]; + if (ipv6_l3_is_ready(v6) && !v6->is_localhost && !ipv6_is_loopback(v6->ip)) ready = true; + } + } + if (ready) break; + msleep(200); } -} + print("[NET] ip ready, starting net_test"); -static void test_net() { create_kernel_process("ntp", ntp, 0, NULL); uint8_t n_if = l2_interface_count(); int tested_any = 0; @@ -352,55 +260,32 @@ static void test_net() { if (!l2 || !l2->is_up) continue; for (uint8_t j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { l3_ipv4_interface_t* ifv4 = l2->l3_v4[j]; - if (!ifv4_is_ready_nonlocal(ifv4)) continue; - test_net_for_interface(ifv4); - tested_any = 1; + if (!ipv4_l3_is_ready(ifv4) || ifv4->is_localhost) continue; + if (ipv4_is_loopback(ifv4->ip) || !ifv4->mask) continue; + + uint32_t probe_ip = ipv4_broadcast_calc(ifv4->ip, ifv4->mask); + if (!probe_ip) continue; + + net_l4_endpoint srv = (net_l4_endpoint){0}; + if (udp_probe_server(probe_ip, PROBE_PORT, &srv)) { + test_http(&srv); + tested_any = 1; + } } } - run_http_server(); if (!tested_any) { net_l4_endpoint srv = (net_l4_endpoint){0}; uint32_t fallback = (192<<24)|(168<<16)|(1<<8)|255; - if (udp_probe_server(fallback, 8080, &srv)) - test_http(&srv); - else - kprintf("[NET] could not find update server"); + if (udp_probe_server(fallback, PROBE_PORT, &srv)) test_http(&srv); } -} -static int net_test_entry(int argc, char* argv[]) { - (void)argc; (void)argv; - test_net(); - return 0; -} - -static int ip_waiter_entry(int argc, char* argv[]) { - (void)argc; (void)argv; - uint32_t waited = 0; - while (!any_ipv4_ready() && !any_ipv6_ready()) { - if ((waited % 1000) == 0) kprintf("[NET] ip_waiter: waiting for ip..."); - msleep(200); - waited += 200; - } - create_kernel_process("net_test", net_test_entry, 0, 0); + run_http_server(); return 0; } process_t* launch_net_process() { create_kernel_process("net_net", network_net_task_entry, 0, 0); - create_kernel_process("arp_daemon", arp_daemon_entry, 0, 0); - create_kernel_process("ndp_daemon", ndp_daemon_entry, 0, 0); //create_kernel_process("ssdp_daemon", ssdp_daemon_entry, 0, 0); - create_kernel_process("dhcp_daemon", dhcp_daemon_entry, 0, 0); - create_kernel_process("dhcpv6_daemon", dhcpv6_daemon_entry, 0, 0); - create_kernel_process("dns_daemon", dns_deamon_entry, 0, 0); - - if (any_ipv4_ready() || any_ipv6_ready()) { - kprintf("[NET] ip ready, starting net_test"); - create_kernel_process("net_test", net_test_entry, 0, 0); - return NULL; - } - - create_kernel_process("ip_waiter", ip_waiter_entry, 0, 0); + create_kernel_process("net_test", net_test_entry, 0, 0); return NULL; } \ No newline at end of file diff --git a/kernel/networking/processes/net_proc.h b/kernel/networking/processes/net_proc.h index 81c2921c..cb6f8f73 100644 --- a/kernel/networking/processes/net_proc.h +++ b/kernel/networking/processes/net_proc.h @@ -1,13 +1,11 @@ #pragma once #include "process/process.h" -process_t* launch_net_process(); - #ifdef __cplusplus extern "C" { #endif -void test_network(); +process_t* launch_net_process(); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/csocket.c b/kernel/networking/transport_layer/csocket.c index d9d19078..3931fd71 100644 --- a/kernel/networking/transport_layer/csocket.c +++ b/kernel/networking/transport_layer/csocket.c @@ -1,205 +1,217 @@ #include "csocket.h" +#include "csocket_ctrl.h" +#include "csocket_packet.h" +#include "csocket_raw.h" #include "csocket_tcp.h" #include "csocket_udp.h" +#include "socket_core.h" +#include "process/scheduler.h" +#include "std/memory.h" #include "memory/page_allocator.h" #include "console/kio.h" #include "data/struct/hashmap.h" #include "alloc/allocate.h" -uint16_t socket_ids; +socket_handle_t create_socket(protocol_t protocol, const SocketOptions* extra){ + SocketOptions default_extra = {}; + if (!extra) extra = &default_extra; + + SocketSpecialKind special_kind = extra->special_kind; + bool special_requested = (extra->flags & SOCK_OPT_SPECIAL) != 0; + bool normal_socket = !special_requested && special_kind == SOCKET_SPECIAL_NONE && (protocol == PROTO_TCP || protocol == PROTO_UDP); + //TODO once privileges are available, require them for raw, packet, control sockets. idem for Broadcast/sock options + bool raw_socket = special_requested && special_kind == SOCKET_SPECIAL_RAW && (protocol == PROTO_ICMP || protocol == PROTO_ICMPV6 || protocol == PROTO_IGMP); + bool ctrl_socket = special_requested && special_kind == SOCKET_SPECIAL_CTRL && protocol == PROTO_NONE; + bool packet_socket = special_requested && special_kind == SOCKET_SPECIAL_PACKET && protocol == PROTO_NONE; + if (!normal_socket && !raw_socket && !ctrl_socket && !packet_socket) return 0; + + uint16_t pid = get_current_proc_pid(); + ksocket_t* socket = NULL; + if (!socket_core_alloc(protocol, special_kind, pid, &socket)) return 0; + + socket_impl_t impl = NULL; + socket_impl_destroy_fn destroy = NULL; + socket_impl_close_fn close = NULL; + socket_impl_setopt_fn setopt = NULL; + socket_impl_getopt_fn getopt = NULL; + + if (protocol == PROTO_UDP) { + impl = udp_socket_create(socket, extra); + destroy = socket_destroy_udp; + close = socket_close_udp; + setopt = socket_setopt_udp; + getopt = socket_getopt_udp; + } else if (protocol == PROTO_TCP) { + impl = socket_tcp_create(socket, extra); + destroy = socket_destroy_tcp; + close = socket_close_tcp; + setopt = socket_setopt_tcp; + getopt = socket_getopt_tcp; + } else if (special_kind == SOCKET_SPECIAL_RAW) { + impl = socket_raw_create(socket, extra); + destroy = socket_destroy_raw; + close = socket_close_raw; + setopt = socket_setopt_raw; + getopt = socket_getopt_raw; + } else if (special_kind == SOCKET_SPECIAL_CTRL) { + impl = socket_ctrl_create(socket, extra); + destroy = socket_destroy_ctrl; + close = socket_close_ctrl; + setopt = socket_setopt_ctrl; + getopt = socket_getopt_ctrl; + } else if (special_kind == SOCKET_SPECIAL_PACKET) { + impl = socket_packet_create(socket, extra); + destroy = socket_destroy_packet; + close = socket_close_packet; + setopt = socket_setopt_packet; + getopt = socket_getopt_packet; + } + if (!impl) { + socket_core_close_socket(socket); + return 0; + } + + if (!socket_core_attach_impl(socket, impl, destroy, close, setopt, getopt)) { + destroy(impl); + socket_core_close_socket(socket); + return 0; + } + return socket_core_export_handle(socket); +} + +int32_t bind_socket(socket_handle_t handle, const SockBindSpec *spec_in, uint16_t port){ + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + if (spec_in) spec = *spec_in; + else spec.kind = BIND_ANY; -void *sock_mem_page; + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; -hash_map_t *map; + int32_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) == SOCKET_SPECIAL_RAW) res = socket_bind_raw(socket_core_impl(socket), &spec); + else if (socket_core_special_kind(socket) == SOCKET_SPECIAL_PACKET) res = socket_bind_packet(socket_core_impl(socket), &spec); + else if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_TCP) res = socket_bind_tcp(socket_core_impl(socket), &spec, port); + else if (socket_core_protocol(socket) == PROTO_UDP) res = socket_bind_udp(socket_core_impl(socket), &spec, port); -typedef struct ksock_handle_t { - uint16_t id; - net_l4_endpoint connection; - protocol_t protocol; - void* sh; - uint16_t pid; -} ksock_handle_t; + socket_core_put(socket); -void* csock_alloc(size_t size){ - return allocate(sock_mem_page, size, page_alloc); + return res; } -static inline void check_mem(){ - if (!sock_mem_page) - sock_mem_page = page_alloc(MEM_PRIV_KERNEL); - if (!map) - map = hash_map_create_alloc(128, csock_alloc, release); +int32_t connect_socket(socket_handle_t handle, const net_l4_endpoint* dst){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + + int32_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) == SOCKET_SPECIAL_RAW) res = socket_connect_raw(socket_core_impl(socket), dst); + else if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_TCP) { + res = socket_connect_tcp(socket_core_impl(socket), dst); + } else if (socket_core_protocol(socket) == PROTO_UDP) res = socket_connect_udp(socket_core_impl(socket), dst); + + socket_core_put(socket); + return res; } -bool create_socket(Socket_Role role, protocol_t protocol, const SocketExtraOptions* extra, uint16_t pid, SocketHandle *out_handle){ - check_mem(); - if (!out_handle) return false; - socket_handle_t *in_handle = {}; - switch (protocol) { - case PROTO_UDP: - in_handle = udp_socket_create(role, pid, extra); - break; - case PROTO_TCP: - in_handle = socket_tcp_create(role, pid, extra); - break; - } - if (!in_handle){ - kprintf("[SOCKET] failed to create socket for %i",pid); - return false; - } - out_handle->id = socket_ids++; - out_handle->connection = (net_l4_endpoint){}; - out_handle->protocol = protocol; - - kprintf("Allocating"); - ksock_handle_t *sh = (ksock_handle_t*)csock_alloc(sizeof(ksock_handle_t)); - sh->sh = in_handle; - sh->id = out_handle->id; - sh->protocol = protocol; - sh->pid = pid; - - if (hash_map_put(map, &out_handle->id, sizeof(uint16_t), sh) < 0){ - kprint("Failed to save socket"); - return false; - } - return true; +int64_t send_on_socket(socket_handle_t handle, const void* buf, uint64_t len){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + + int64_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) == SOCKET_SPECIAL_RAW) res = socket_send_raw(socket_core_impl(socket), buf, len); + else if (socket_core_special_kind(socket) == SOCKET_SPECIAL_CTRL) res = socket_send_ctrl(socket_core_impl(socket), buf, len); + else if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_TCP) res = socket_send_tcp(socket_core_impl(socket), buf, len); + else if (socket_core_protocol(socket) == PROTO_UDP) res = socket_sendto_udp(socket_core_impl(socket), NULL, buf, len); + + socket_core_put(socket); + return res; } -int32_t bind_socket(SocketHandle *handle, uint16_t port, ip_version_t ip_version, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket binding"); - return 0; - } - protocol_t protocol = sh->protocol; - SockBindSpec *spec = csock_alloc(sizeof(SockBindSpec)); - spec->kind = BIND_IP; - spec->ver = ip_version; - memset(spec->ip, 0, sizeof(spec->ip)); - if (ip_version == IP_VER4) memcpy(spec->ip, handle->connection.ip, 4); - else if (ip_version == IP_VER6) memcpy(spec->ip, handle->connection.ip, 16); - int32_t res = -1; - switch (protocol) { - case PROTO_TCP: - res = socket_bind_tcp_ex(sh->sh, spec, port); - break; - case PROTO_UDP: - res = socket_bind_udp_ex(sh->sh, spec, port); - break; - } +int64_t send_to_socket(socket_handle_t handle, const net_l4_endpoint* dst, const void* buf, uint64_t len){ + if (!dst) return SOCK_ERR_INVAL; + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + + int64_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) == SOCKET_SPECIAL_RAW) res = socket_sendto_raw(socket_core_impl(socket), dst, buf, len); + else if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_UDP) res = socket_sendto_udp(socket_core_impl(socket), dst, buf, len); + + socket_core_put(socket); return res; } -int32_t connect_socket(SocketHandle *handle, uint8_t dst_kind, const void* dst, uint16_t port, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket connection"); - return 0; - } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - return socket_connect_tcp_ex(sh->sh, dst_kind, dst, port); - break; - case PROTO_UDP: - kprintf("[SOCKET] connect is a TCP-only function and isn't needed in UDP sockets"); - return -1; - } - return -1; +int64_t receive_from_socket(socket_handle_t handle, void* buf, uint64_t len, net_l4_endpoint* out_src){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + + int64_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) == SOCKET_SPECIAL_RAW) res = socket_recv_raw(socket_core_impl(socket), buf, len, out_src); + else if (socket_core_special_kind(socket) == SOCKET_SPECIAL_CTRL) res = socket_recv_ctrl(socket_core_impl(socket), buf, len); + else if (socket_core_special_kind(socket) == SOCKET_SPECIAL_PACKET) res = socket_recv_packet(socket_core_impl(socket), buf, len); + else if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_TCP) res = socket_recv_tcp(socket_core_impl(socket), buf, len); + else if (socket_core_protocol(socket) == PROTO_UDP) res = socket_recvfrom_udp(socket_core_impl(socket), buf, len, out_src); + + socket_core_put(socket); + return res; } -int64_t send_on_socket(SocketHandle *handle, uint8_t dst_kind, const void* dst, uint16_t port, void* buf, uint64_t len, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket send"); - return 0; - } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - return socket_send_tcp(sh->sh, buf, len); - break; - case PROTO_UDP: - return socket_sendto_udp_ex(sh->sh, dst_kind, dst, port, buf, len); - break; - } - return 0; +int32_t set_socket_option(socket_handle_t handle, int32_t opt, const void* value, uint32_t len){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + int32_t res = socket_core_set_option(socket, opt, value, len); + socket_core_put(socket); + return res; } -int64_t receive_from_socket(SocketHandle *handle, void* buf, uint64_t len, net_l4_endpoint* out_src, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket receive %i, %i",sh->id,pid); - return 0; - } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - return socket_recv_tcp(sh->sh, buf, len); - break; - case PROTO_UDP: - return socket_recvfrom_udp_ex(sh->sh, buf, len, out_src); - break; - } - return 0; +int32_t get_socket_option(socket_handle_t handle, int32_t opt, void* value, uint32_t* len){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + int32_t res = socket_core_get_option(socket, opt, value, len); + socket_core_put(socket); + return res; } -int32_t close_socket(SocketHandle *handle, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket close"); - return 0; - } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - return socket_close_tcp(sh->sh); - break; - case PROTO_UDP: - return socket_close_udp(sh->sh); - break; - } - return 0; +int32_t close_socket(socket_handle_t handle){ + return socket_core_close_handle(handle, get_current_proc_pid()); +} + +int32_t listen_on(socket_handle_t handle, int32_t backlog){ + ksocket_t* socket = socket_core_get(handle, get_current_proc_pid()); + if (!socket) return SOCK_ERR_INVAL; + + int32_t res = SOCK_ERR_PROTO; + if (socket_core_special_kind(socket) != SOCKET_SPECIAL_NONE) res = SOCK_ERR_UNSUP; + else if (socket_core_protocol(socket) == PROTO_TCP) res = socket_listen_tcp(socket_core_impl(socket), backlog); + + socket_core_put(socket); + return res; } -int32_t listen_on(SocketHandle *handle, int32_t backlog, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket listen"); +socket_handle_t accept_on_socket(socket_handle_t handle) { + ksocket_t* listener = socket_core_get(handle, get_current_proc_pid()); + if (!listener) return 0; + if (socket_core_special_kind(listener) != SOCKET_SPECIAL_NONE) { + socket_core_put(listener); return 0; } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - return socket_listen_tcp(sh->sh, backlog); - break; - case PROTO_UDP: - kprintf("[SOCKET] listen is a TCP-only function and isn't needed in UDP sockets"); - break; + if (socket_core_protocol(listener) != PROTO_TCP) { + socket_core_put(listener); + return 0; } - return 0; -} -void accept_on_socket(SocketHandle *handle, uint16_t pid){ - check_mem(); - ksock_handle_t *sh = (ksock_handle_t*)hash_map_get(map, &handle->id, sizeof(uint16_t)); - if (sh->pid != pid){ - kprintf("[SOCKET, error] illegal socket accept"); - return; - } - protocol_t protocol = sh->protocol; - switch (protocol) { - case PROTO_TCP: - socket_accept_tcp(sh->sh); - break; - case PROTO_UDP: - kprintf("[SOCKET] accept is a TCP-only function and isn't needed in UDP sockets"); - break; + ksocket_t* child = socket_accept_tcp(socket_core_impl(listener)); + if (!child) { + socket_core_put(listener); + return 0; } + + socket_handle_t child_handle = socket_core_export_handle(child); + socket_core_put(child); + socket_core_put(listener); + return child_handle; } \ No newline at end of file diff --git a/kernel/networking/transport_layer/csocket.h b/kernel/networking/transport_layer/csocket.h index 0434c30b..c8027e48 100644 --- a/kernel/networking/transport_layer/csocket.h +++ b/kernel/networking/transport_layer/csocket.h @@ -3,14 +3,27 @@ #include "types.h" #include "net/network_types.h" #include "net/socket_types.h" +#include "networking/transport_layer/socket_core.h" -bool create_socket(Socket_Role role, protocol_t protocol, const SocketExtraOptions* extra, uint16_t pid, SocketHandle *out_handle); -int32_t bind_socket(SocketHandle *handle, uint16_t port, ip_version_t ip_vers, uint16_t pid); -int32_t connect_socket(SocketHandle *handle, uint8_t dst_kind, const void* dst, uint16_t port, uint16_t pid); +#ifdef __cplusplus +extern "C" { +#endif -int64_t send_on_socket(SocketHandle *sh, uint8_t dst_kind, const void* dst, uint16_t port, void* buf, uint64_t len, uint16_t pid); -int64_t receive_from_socket(SocketHandle *sh, void* buf, uint64_t len, net_l4_endpoint* out_src, uint16_t pid); -int32_t close_socket(SocketHandle *sh, uint16_t pid); +socket_handle_t create_socket(protocol_t protocol, const SocketOptions* extra); +int32_t bind_socket(socket_handle_t handle, const SockBindSpec* spec, uint16_t port); +int32_t connect_socket(socket_handle_t handle, const net_l4_endpoint* dst); -int32_t listen_on(SocketHandle *sh, int32_t backlog, uint16_t pid); -void accept_on_socket(SocketHandle *sh, uint16_t pid); \ No newline at end of file +int64_t send_on_socket(socket_handle_t handle, const void* buf, uint64_t len); +int64_t send_to_socket(socket_handle_t handle, const net_l4_endpoint* dst, const void* buf, uint64_t len); +int64_t receive_from_socket(socket_handle_t handle, void* buf, uint64_t len, net_l4_endpoint* out_src); +int32_t set_socket_option(socket_handle_t handle, int32_t opt, const void* value, uint32_t len); +int32_t get_socket_option(socket_handle_t handle, int32_t opt, void* value, uint32_t* len); + +int32_t close_socket(socket_handle_t handle); + +int32_t listen_on(socket_handle_t handle, int32_t backlog); +socket_handle_t accept_on_socket(socket_handle_t handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/kernel/networking/transport_layer/csocket_ctrl.c b/kernel/networking/transport_layer/csocket_ctrl.c new file mode 100644 index 00000000..aa7d2c51 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_ctrl.c @@ -0,0 +1,126 @@ +#include "csocket_ctrl.h" +#include "net_ctrl.h" +#include "alloc/allocate.h" +#include "std/memory.h" + +typedef struct ctrl_socket { + uint8_t* rx_buf; + uint32_t rx_len; + uint32_t rx_off; +} ctrl_socket_t; + +socket_impl_t socket_ctrl_create(ksocket_t* owner, const SocketOptions* extra) { + if (!owner) return NULL; + if (socket_core_special_kind(owner) != SOCKET_SPECIAL_CTRL) return NULL; + if (extra && (extra->flags & ~SOCK_OPT_SPECIAL)) return NULL; + + ctrl_socket_t* s = (ctrl_socket_t*)zalloc(sizeof(ctrl_socket_t)); + if (!s) return NULL; + + return s; +} + +void socket_destroy_ctrl(socket_impl_t sh) { + ctrl_socket_t* s = (ctrl_socket_t*)sh; + if (!s) return; + if (s->rx_buf) release(s->rx_buf); + release(s); +} + +int32_t socket_close_ctrl(socket_impl_t sh) { + return sh ? SOCK_OK : SOCK_ERR_INVAL; +} + +int32_t socket_setopt_ctrl(socket_impl_t sh, int32_t opt, const void* value, uint32_t len) { + return sh ? SOCK_ERR_UNSUP : SOCK_ERR_INVAL; +} + +int32_t socket_getopt_ctrl(socket_impl_t sh, int32_t opt, void* value, uint32_t* len) { + ctrl_socket_t* s = (ctrl_socket_t*)sh; + if (!s || !len) return SOCK_ERR_INVAL; + + if ((uint32_t)opt == SOCK_GET_BIND_SPEC) { + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + spec.kind = BIND_ANY; + return socket_common_get_value(&spec, sizeof(spec), value, len); + } + + if ((uint32_t)opt == SOCK_GET_LAST_RX_SPEC) return SOCK_ERR_UNSUP; + + uint32_t v = 0; + switch ((uint32_t)opt) { + case SOCK_GET_BOUND: + break; + case SOCK_GET_RECV_QUEUED: + if (s->rx_buf && s->rx_off < s->rx_len) v = s->rx_len - s->rx_off; + break; + case SOCK_GET_CONNECTED: + case SOCK_GET_LISTENING: + case SOCK_GET_LOCAL_PORT: + case SOCK_GET_SEND_QUEUED: + case SOCK_GET_OPT_RECV_TIMEOUT: + case SOCK_GET_OPT_SEND_TIMEOUT: + case SOCK_GET_OPT_BUF_SIZE: + case SOCK_GET_OPT_DEBUG: + case SOCK_GET_OPT_DONTFRAG: + case SOCK_GET_OPT_TTL: + case SOCK_GET_OPT_KEEPALIVE: + case SOCK_GET_OPT_KEEPALIVE_INTERVAL: + case SOCK_GET_OPT_TCP_NO_DELAY: + case SOCK_GET_OPT_SEND_BUF_SIZE: + case SOCK_GET_OPT_BROADCAST_ALLOWED: + case SOCK_GET_OPT_FILTER: + case SOCK_GET_MCAST_GROUPS: + case SOCK_GET_TCP_STATE: + case SOCK_GET_TCP_MSS: + case SOCK_GET_TCP_RTT_MS: + case SOCK_GET_TCP_RETRANSMITS: + case SOCK_GET_TCP_URGENT_REMAINING: + return SOCK_ERR_UNSUP; + default: + return SOCK_ERR_INVAL; + } + + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int64_t socket_send_ctrl(socket_impl_t sh, const void* buf, uint64_t len) { + ctrl_socket_t* s = (ctrl_socket_t*)sh; + if (!s || (!buf && len) || len > UINT32_MAX) return SOCK_ERR_INVAL; + + if (s->rx_buf) { + release(s->rx_buf); + s->rx_buf = NULL; + s->rx_len = 0; + s->rx_off = 0; + } + + uint8_t* out = NULL; + uint32_t out_len = 0; + int32_t rc = net_ctrl_dispatch(buf, (uint32_t)len, &out, &out_len); + if (rc != SOCK_OK) return rc; + + s->rx_buf = out; + s->rx_len = out_len; + s->rx_off = 0; + return (int64_t)len; +} + +int64_t socket_recv_ctrl(socket_impl_t sh, void* buf, uint64_t len) { + ctrl_socket_t* s = (ctrl_socket_t*)sh; + if (!s || (!buf && len) || len > UINT32_MAX) return SOCK_ERR_INVAL; + if (!s->rx_buf || s->rx_off >= s->rx_len) return SOCK_ERR_WOULDBLOCK; + + uint32_t avail = s->rx_len - s->rx_off; + uint32_t n = avail < (uint32_t)len ? avail : (uint32_t)len; + if (n) memcpy(buf, s->rx_buf + s->rx_off, n); + s->rx_off += n; + if (s->rx_off >= s->rx_len) { + release(s->rx_buf); + s->rx_buf = NULL; + s->rx_len = 0; + s->rx_off = 0; + } + return n; +} diff --git a/kernel/networking/transport_layer/csocket_ctrl.h b/kernel/networking/transport_layer/csocket_ctrl.h new file mode 100644 index 00000000..737216c4 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_ctrl.h @@ -0,0 +1,19 @@ +#pragma once + +#include "socket_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + +socket_impl_t socket_ctrl_create(ksocket_t* owner, const SocketOptions* extra); +void socket_destroy_ctrl(socket_impl_t sh); +int32_t socket_close_ctrl(socket_impl_t sh); +int32_t socket_setopt_ctrl(socket_impl_t sh, int32_t opt, const void* value, uint32_t len); +int32_t socket_getopt_ctrl(socket_impl_t sh, int32_t opt, void* value, uint32_t* len); +int64_t socket_send_ctrl(socket_impl_t sh, const void* buf, uint64_t len); +int64_t socket_recv_ctrl(socket_impl_t sh, void* buf, uint64_t len); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/csocket_packet.c b/kernel/networking/transport_layer/csocket_packet.c new file mode 100644 index 00000000..afe2d7b6 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_packet.c @@ -0,0 +1,541 @@ +#include "csocket_packet.h" +#include "networking/network.h" +#include "networking/link_layer/eth.h" +#include "networking/interface_manager.h" +#include "alloc/allocate.h" +#include "exceptions/irq.h" +#include "std/memory.h" +#include "syscalls/syscalls.h" + +#define PACKET_SOCKET_MAX 64 +#define PACKET_RX_DEFAULT_RING_CAP 64 +#define PACKET_RX_MAX_RING_CAP 1024 +#define PACKET_RX_DEFAULT_BUF_SIZE (MAX_PACKET_SIZE * PACKET_RX_DEFAULT_RING_CAP) + +typedef struct packet_rx_entry { + netpkt_t* pkt; + uint32_t generation; + uint8_t ifindex; + bool strip_vlan; +} packet_rx_entry_t; + +typedef struct packet_socket { + ksocket_t* ownerSocket; + SocketOptions options; + bool registered; + SockBindSpec bind_spec; + uint32_t bind_generation; + SockBindSpec last_rx_spec; + packet_rx_entry_t* ring; + uint32_t ring_cap; + uint32_t head; + uint32_t tail; + uint32_t rx_bytes; +} packet_socket_t; + +typedef struct packet_frame { + l2_interface_t* physical; + l2_interface_t* logical; + uint32_t raw_len; + uint32_t logical_len; + uint16_t ethertype; + uint16_t logical_type; + uint16_t vid; + bool tagged; + bool logical_valid; +} packet_frame_t; + +typedef struct packet_delivery { + packet_socket_t* socket; + uint32_t generation; + uint8_t ifindex; + bool strip_vlan; +} packet_delivery_t; + +static packet_socket_t* g_packet_sockets[PACKET_SOCKET_MAX]; +static uint32_t g_packet_socket_count; //TODO lock + +static void packet_socket_unregister(packet_socket_t* s) { + if (!s || !s->registered) return; + + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < PACKET_SOCKET_MAX; i++) { + if (g_packet_sockets[i] == s) { + g_packet_sockets[i] = NULL; + g_packet_socket_count--; + break; + } + } + s->registered = false; + irq_restore(irq); +} + +static void packet_socket_clear_rx(packet_socket_t* s) { + if (!s || !s->ring) return; + + for (uint32_t i = 0; i < s->ring_cap; i++) { + if (s->ring[i].pkt) netpkt_unref(s->ring[i].pkt); + memset(&s->ring[i], 0, sizeof(s->ring[i])); + } + + release(s->ring); + s->ring = NULL; + s->ring_cap = 0; + s->head = 0; + s->tail = 0; + s->rx_bytes = 0; +} + +static int32_t packet_set_filter(packet_socket_t* s, const void* value, uint32_t len) { + if (!s) return SOCK_ERR_INVAL; + if (!value && !len) { + memset(&s->options.packet_filter, 0, sizeof(s->options.packet_filter)); + s->options.flags &= ~SOCK_OPT_FILTER; + return SOCK_OK; + } + if (!value || len != sizeof(SocketPacketFilter)) return SOCK_ERR_INVAL; + + SocketPacketFilter filter; + memcpy(&filter, value, sizeof(filter)); + + uint32_t valid_flags = SOCKET_PACKET_FILTER_HAS_ETHERTYPE | SOCKET_PACKET_FILTER_HAS_MIN_LEN | SOCKET_PACKET_FILTER_HAS_MAX_LEN; + if (filter.reserved || (filter.flags & ~valid_flags)) return SOCK_ERR_INVAL; + if ((filter.flags & SOCKET_PACKET_FILTER_HAS_ETHERTYPE) && !filter.ethertype) return SOCK_ERR_INVAL; + if (!(filter.flags & SOCKET_PACKET_FILTER_HAS_ETHERTYPE) && filter.ethertype) return SOCK_ERR_INVAL; + if ((filter.flags & SOCKET_PACKET_FILTER_HAS_MIN_LEN) && !filter.min_len) return SOCK_ERR_INVAL; + if (!(filter.flags & SOCKET_PACKET_FILTER_HAS_MIN_LEN) && filter.min_len) return SOCK_ERR_INVAL; + if ((filter.flags & SOCKET_PACKET_FILTER_HAS_MAX_LEN) && !filter.max_len) return SOCK_ERR_INVAL; + if (!(filter.flags & SOCKET_PACKET_FILTER_HAS_MAX_LEN) && filter.max_len) return SOCK_ERR_INVAL; + if ((filter.flags & SOCKET_PACKET_FILTER_HAS_MIN_LEN) && (filter.flags & SOCKET_PACKET_FILTER_HAS_MAX_LEN) && filter.min_len > filter.max_len) return SOCK_ERR_INVAL; + + s->options.packet_filter = filter; + if (filter.flags) s->options.flags |= SOCK_OPT_FILTER; + else s->options.flags &= ~SOCK_OPT_FILTER; + return SOCK_OK; +} + +socket_impl_t socket_packet_create(ksocket_t* owner, const SocketOptions* extra) { + if (!owner) return NULL; + if (socket_core_special_kind(owner) != SOCKET_SPECIAL_PACKET) return NULL; + + uint32_t supported = SOCK_OPT_RECV_TIMEOUT | SOCK_OPT_BUF_SIZE | SOCK_OPT_DEBUG | SOCK_OPT_FILTER | SOCK_OPT_SPECIAL | SOCK_OPT_NONBLOCK | SOCK_OPT_PACKET_TRUNK; + if (extra && (extra->flags & ~supported)) return NULL; + + packet_socket_t* s = (packet_socket_t*)zalloc(sizeof(packet_socket_t)); + if (!s) return NULL; + + s->ownerSocket = owner; + s->options.flags = SOCK_OPT_SPECIAL; + s->options.special_kind = SOCKET_SPECIAL_PACKET; + s->options.buf_size = PACKET_RX_DEFAULT_BUF_SIZE; + s->bind_spec.kind = BIND_ANY; + s->last_rx_spec.kind = BIND_ANY; + + if (extra) { + if ((extra->flags & SOCK_OPT_DEBUG) && extra->debug_level > SOCK_DBG_ALL) { + release(s); + return 0; + } + if (extra->flags & SOCK_OPT_DEBUG) { + s->options.flags |= SOCK_OPT_DEBUG; + s->options.debug_level = extra->debug_level; + } + if (extra->flags & SOCK_OPT_RECV_TIMEOUT) { + s->options.flags |= SOCK_OPT_RECV_TIMEOUT; + s->options.recv_timeout_ms = extra->recv_timeout_ms; + } + if (extra->flags & SOCK_OPT_NONBLOCK) s->options.flags |= SOCK_OPT_NONBLOCK; + if (extra->flags & SOCK_OPT_PACKET_TRUNK) s->options.flags |= SOCK_OPT_PACKET_TRUNK; + if (extra->flags & SOCK_OPT_BUF_SIZE) { + if (!extra->buf_size) { + release(s); + return 0; + } + s->options.flags |= SOCK_OPT_BUF_SIZE; + s->options.buf_size = extra->buf_size; + } + if (extra->flags & SOCK_OPT_FILTER) { + if (packet_set_filter(s, &extra->packet_filter, sizeof(extra->packet_filter)) != SOCK_OK) { + release(s); + return NULL; + } + } + } + + uint32_t usable = s->options.buf_size/MAX_PACKET_SIZE; + if (usable < 4) usable = 4; + if (usable > PACKET_RX_MAX_RING_CAP) usable = PACKET_RX_MAX_RING_CAP; + + s->ring_cap = usable+1; + s->ring = (packet_rx_entry_t*)zalloc(sizeof(packet_rx_entry_t) * s->ring_cap); + if (!s->ring) { + release(s); + return NULL; + } + + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < PACKET_SOCKET_MAX; i++) { + if (!g_packet_sockets[i]) { + g_packet_sockets[i] = s; + g_packet_socket_count++; + s->registered = true; + irq_restore(irq); + return s; + } + } + irq_restore(irq); + + packet_socket_clear_rx(s); + release(s); + return NULL; +} + +void socket_destroy_packet(socket_impl_t sh) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s) return; + + packet_socket_unregister(s); + packet_socket_clear_rx(s); + release(s); +} + +int32_t socket_close_packet(socket_impl_t sh) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + packet_socket_unregister(s); + return SOCK_OK; +} + +int32_t socket_setopt_packet(socket_impl_t sh, int32_t opt, const void* value, uint32_t len) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_OPT_RECV_TIMEOUT: + case SOCK_OPT_DEBUG: + case SOCK_OPT_NONBLOCK: + case SOCK_OPT_PACKET_TRUNK: + return socket_common_options_set(&s->options, opt, value, len); + case SOCK_OPT_FILTER: + return packet_set_filter(s, value, len); + case SOCK_OPT_BUF_SIZE: + case SOCK_OPT_SEND_TIMEOUT: + case SOCK_OPT_SEND_BUF_SIZE: + case SOCK_OPT_KEEPALIVE: + case SOCK_OPT_KEEPALIVE_INTERVAL: + case SOCK_OPT_TCP_NO_DELAY: + case SOCK_OPT_BROADCAST_ALLOWED: + case SOCK_OPT_SPECIAL: + case SOCK_OPT_MCAST_JOIN: + case SOCK_OPT_MCAST_LEAVE: + case SOCK_OPT_DONTFRAG: + case SOCK_OPT_TTL: + return SOCK_ERR_UNSUP; + default: + return SOCK_ERR_INVAL; + } +} + +int32_t socket_getopt_packet(socket_impl_t sh, int32_t opt, void* value, uint32_t* len) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s || !len) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_GET_BIND_SPEC: + return socket_common_get_value(&s->bind_spec, sizeof(s->bind_spec), value, len); + case SOCK_GET_LAST_RX_SPEC: + return socket_common_get_value(&s->last_rx_spec, sizeof(s->last_rx_spec), value, len); + case SOCK_GET_OPT_FILTER: + return socket_common_get_value(&s->options.packet_filter, sizeof(s->options.packet_filter), value, len); + default: + break; + } + + uint32_t v = 0; + switch ((uint32_t)opt) { + case SOCK_GET_BOUND: + v = s->bind_spec.kind != BIND_ANY && (s->bind_spec.kind != BIND_L2 || s->bind_generation != 0); + break; + case SOCK_GET_RECV_QUEUED: + v = s->rx_bytes; + break; + case SOCK_GET_OPT_RECV_TIMEOUT: + case SOCK_GET_OPT_DEBUG: + case SOCK_GET_OPT_BUF_SIZE: + case SOCK_GET_OPT_NONBLOCK: + case SOCK_GET_OPT_PACKET_TRUNK: + return socket_common_options_get(&s->options, opt, value, len); + case SOCK_GET_CONNECTED: + case SOCK_GET_LISTENING: + case SOCK_GET_LOCAL_PORT: + case SOCK_GET_SEND_QUEUED: + case SOCK_GET_OPT_SEND_TIMEOUT: + case SOCK_GET_OPT_SEND_BUF_SIZE: + case SOCK_GET_OPT_KEEPALIVE: + case SOCK_GET_OPT_KEEPALIVE_INTERVAL: + case SOCK_GET_OPT_TCP_NO_DELAY: + case SOCK_GET_OPT_BROADCAST_ALLOWED: + case SOCK_GET_OPT_DONTFRAG: + case SOCK_GET_OPT_TTL: + case SOCK_GET_MCAST_GROUPS: + case SOCK_GET_TCP_STATE: + case SOCK_GET_TCP_MSS: + case SOCK_GET_TCP_RTT_MS: + case SOCK_GET_TCP_RETRANSMITS: + case SOCK_GET_TCP_URGENT_REMAINING: + return SOCK_ERR_UNSUP; + default: + return SOCK_ERR_INVAL; + } + + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int32_t socket_bind_packet(socket_impl_t sh, const SockBindSpec* spec) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s || !spec) return SOCK_ERR_INVAL; + + SockBindSpec next; + memset(&next, 0, sizeof(next)); + + if (spec->kind == BIND_ANY) { + next.kind = BIND_ANY; + s->bind_spec = next; + s->bind_generation = 0; + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + return SOCK_OK; + } + + if (spec->kind != BIND_L2 || !spec->ifindex) return SOCK_ERR_INVAL; + + next.kind = BIND_L2; + next.ifindex = spec->ifindex; + l2_interface_t* l2 = l2_interface_find_by_index(next.ifindex); + if (!l2) return SOCK_ERR_INVAL; + s->bind_spec = next; + s->bind_generation = l2->generation; + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + return SOCK_OK; +} + +int64_t socket_recv_packet(socket_impl_t sh, void* buf, uint64_t len) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s || (!buf && len) || len > UINT32_MAX) return SOCK_ERR_INVAL; + + uint32_t start_ms = (uint32_t)get_time(); + packet_rx_entry_t entry; + uint32_t pkt_len = 0; + for (;;) { + irq_flags_t irq = irq_save_disable(); + if (s->head != s->tail) { + uint32_t pos = s->head; + entry = s->ring[pos]; + memset(&s->ring[pos], 0, sizeof(s->ring[pos])); + s->head = (s->head + 1) % s->ring_cap; + pkt_len = netpkt_len(entry.pkt); + if (entry.strip_vlan && pkt_len >= ETH_VLAN_TAG_LEN) pkt_len -= ETH_VLAN_TAG_LEN; + if (s->rx_bytes >= pkt_len) s->rx_bytes -= pkt_len; + else s->rx_bytes = 0; + irq_restore(irq); + break; + } + irq_restore(irq); + + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + if ((s->options.flags & SOCK_OPT_RECV_TIMEOUT) && s->options.recv_timeout_ms) { + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - start_ms; + if (elapsed_ms >= s->options.recv_timeout_ms) return SOCK_ERR_WOULDBLOCK; + uint32_t wait_ms = s->options.recv_timeout_ms - elapsed_ms; + if (wait_ms > 5) wait_ms = 5; + msleep(wait_ms); + }else msleep(5); + } + + l2_interface_t* source = l2_interface_find_by_index(entry.ifindex); + if (!source || source->generation != entry.generation) { + netpkt_unref(entry.pkt); + return SOCK_ERR_NOT_FOUND; + } + + irq_flags_t irq = irq_save_disable(); + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_L2; + s->last_rx_spec.ifindex = entry.ifindex; + irq_restore(irq); + + uint32_t n = pkt_len; + if (n > len) n = (uint32_t)len; + bool copied = true; + if (n && entry.strip_vlan) { + uint32_t addr_len = MAC_ADDR_LEN * 2; + uint32_t first = n < addr_len ? n : addr_len; + copied = netpkt_copyout(entry.pkt, 0, buf, first); + if (copied && n > addr_len) copied = netpkt_copyout(entry.pkt, addr_len + ETH_VLAN_TAG_LEN, (uint8_t*)buf + addr_len, n - addr_len); + } else if (n) copied = netpkt_copyout(entry.pkt, 0, buf, n); + netpkt_unref(entry.pkt); + if (copied) return n; + return 0; +} + +void socket_packet_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation) { + packet_socket_t* s = (packet_socket_t*)sh; + if (!s || !ifindex) return; + + for (;;) { + netpkt_t* stale = NULL; + irq_flags_t irq = irq_save_disable(); + if (s->bind_spec.kind == BIND_L2 && s->bind_spec.ifindex == ifindex && s->bind_generation == generation) s->bind_generation = 0; + if (s->last_rx_spec.kind == BIND_L2 && s->last_rx_spec.ifindex == ifindex) { + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + } + + if (s->ring && s->ring_cap) { + for (uint32_t pos = s->head; pos != s->tail; pos = (pos + 1) % s->ring_cap) { + packet_rx_entry_t* entry = &s->ring[pos]; + if (entry->ifindex != ifindex || entry->generation != generation) continue; + + stale = entry->pkt; + uint32_t stale_len = netpkt_len(entry->pkt); + if (entry->strip_vlan && stale_len >= ETH_VLAN_TAG_LEN) stale_len -= ETH_VLAN_TAG_LEN; + uint32_t cur = pos; + uint32_t next = (cur + 1) % s->ring_cap; + while (next != s->tail) { + s->ring[cur] = s->ring[next]; + cur = next; + next = (next + 1) % s->ring_cap; + } + memset(&s->ring[cur], 0, sizeof(s->ring[cur])); + s->tail = cur; + if (s->rx_bytes >= stale_len) s->rx_bytes -= stale_len; + else s->rx_bytes = 0; + break; + } + } + irq_restore(irq); + if (!stale) return; + netpkt_unref(stale); + } +} + +bool socket_packet_input(uint8_t ifindex, netpkt_t* pkt) { + if (!ifindex || !pkt) return false; + irq_flags_t irq = irq_save_disable(); + bool have_listeners = g_packet_socket_count != 0; + irq_restore(irq); + if (!have_listeners) return false; + + packet_frame_t frame = {0}; + frame.raw_len = netpkt_len(pkt); + if (frame.raw_len < sizeof(eth_hdr_t)) return false; + + frame.physical = l2_interface_find_by_index(ifindex); + if (!frame.physical) return false; + + frame.ethertype = eth_parse_type(pkt); + frame.logical_type = frame.ethertype; + frame.tagged = frame.ethertype == ETHERTYPE_VLAN1Q && eth_parse_vlan(pkt, &frame.vid, &frame.logical_type); + frame.logical = frame.physical; + frame.logical_valid = frame.ethertype != ETHERTYPE_VLAN1Q || frame.tagged; + if (frame.tagged) { + if (frame.vid == 4095) frame.logical_valid = false; + else if (frame.vid) { + frame.logical = l2_vlan_find(ifindex, frame.vid); + frame.logical_valid = frame.logical && frame.logical->is_up; + } + } + frame.logical_len = frame.tagged ? frame.raw_len - ETH_VLAN_TAG_LEN : frame.raw_len; + + packet_delivery_t targets[PACKET_SOCKET_MAX]; + int n = 0; + irq = irq_save_disable(); + for (int i = 0; i < PACKET_SOCKET_MAX; i++) { + packet_socket_t* s = g_packet_sockets[i]; + if (!s || socket_core_is_closing(s->ownerSocket)) continue; + + bool trunk = (s->options.flags & SOCK_OPT_PACKET_TRUNK) != 0; + l2_interface_t *source = NULL; + uint32_t pkt_len = frame.raw_len; + uint16_t ethertype = frame.ethertype; + bool strip_vlan = false; + + if (trunk) { + if (s->bind_spec.kind == BIND_L2 && s->bind_spec.ifindex != ifindex) { + if (!frame.tagged || !frame.vid || !frame.logical_valid || s->bind_spec.ifindex != frame.logical->ifindex) continue; + source = frame.logical; + } else source = frame.physical; + } else { + if (!frame.logical_valid) continue; + source = frame.logical; + if (s->bind_spec.kind == BIND_L2 && s->bind_spec.ifindex != source->ifindex) continue; + if (frame.tagged) { + pkt_len = frame.logical_len; + ethertype = frame.logical_type; + strip_vlan = true; + } + } + + if (!source) continue; + if (s->bind_spec.kind == BIND_L2 && s->bind_generation != source->generation) continue; + if ((s->options.packet_filter.flags & SOCKET_PACKET_FILTER_HAS_ETHERTYPE) && s->options.packet_filter.ethertype != ethertype) continue; + if ((s->options.packet_filter.flags & SOCKET_PACKET_FILTER_HAS_MIN_LEN) && pkt_len < s->options.packet_filter.min_len) continue; + if ((s->options.packet_filter.flags & SOCKET_PACKET_FILTER_HAS_MAX_LEN) && pkt_len > s->options.packet_filter.max_len) continue; + socket_core_ref(s->ownerSocket); + targets[n].socket = s; + targets[n].ifindex = source->ifindex; + targets[n].generation = source->generation; + targets[n].strip_vlan = strip_vlan; + n++; + } + irq_restore(irq); + + if (!n) return false; + + netpkt_t* view = netpkt_view(pkt, 0, frame.raw_len); + if (!view) { + for (int i = 0; i < n; i++) socket_core_put(targets[i].socket->ownerSocket); + return false; + } + + bool delivered = false; + for (int i = 0; i < n; i++) { + packet_delivery_t* target = &targets[i]; + packet_socket_t* s = target->socket; + uint32_t limit = s->options.buf_size ? s->options.buf_size : PACKET_RX_DEFAULT_BUF_SIZE; + uint32_t pkt_len = target->strip_vlan ? frame.logical_len : frame.raw_len; + if (pkt_len > limit) { + socket_core_put(s->ownerSocket); + continue; + } + + irq = irq_save_disable(); + l2_interface_t* source = l2_interface_find_by_index(target->ifindex); + bool same_bind = s->bind_spec.kind != BIND_L2 || s->bind_generation == target->generation; + bool can_queue = source && source->generation == target->generation && same_bind && s->ring && s->ring_cap && s->rx_bytes <= limit - pkt_len; + uint32_t nexti = 0; + if (can_queue) { + nexti = (s->tail + 1) % s->ring_cap; + if (nexti == s->head) can_queue = false; + } + if (can_queue) { + packet_rx_entry_t* entry = &s->ring[s->tail]; + netpkt_ref(view); + entry->pkt = view; + entry->ifindex = target->ifindex; + entry->generation = target->generation; + entry->strip_vlan = target->strip_vlan; + s->tail = nexti; + s->rx_bytes += pkt_len; + delivered = true; + } + irq_restore(irq); + socket_core_put(s->ownerSocket); + } + + netpkt_unref(view); + return delivered; +} diff --git a/kernel/networking/transport_layer/csocket_packet.h b/kernel/networking/transport_layer/csocket_packet.h new file mode 100644 index 00000000..407d7f28 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_packet.h @@ -0,0 +1,22 @@ +#pragma once + +#include "socket_core.h" +#include "networking/netpkt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +socket_impl_t socket_packet_create(ksocket_t* owner, const SocketOptions* extra); +void socket_destroy_packet(socket_impl_t sh); +int32_t socket_close_packet(socket_impl_t sh); +int32_t socket_setopt_packet(socket_impl_t sh, int32_t opt, const void* value, uint32_t len); +int32_t socket_getopt_packet(socket_impl_t sh, int32_t opt, void* value, uint32_t* len); +int32_t socket_bind_packet(socket_impl_t sh, const SockBindSpec* spec); +int64_t socket_recv_packet(socket_impl_t sh, void* buf, uint64_t len); +bool socket_packet_input(uint8_t ifindex, netpkt_t* pkt); +void socket_packet_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/csocket_raw.c b/kernel/networking/transport_layer/csocket_raw.c new file mode 100644 index 00000000..10148c16 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_raw.c @@ -0,0 +1,744 @@ +#include "csocket_raw.h" +#include "alloc/allocate.h" +#include "exceptions/irq.h" +#include "net/checksums.h" +#include "std/memory.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6.h" +#include "networking/internet_layer/ipv6_route.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/link_layer/eth.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/transport_layer/socket_bind.h" +#include "syscalls/syscalls.h" + +#define RAW_SOCKET_MAX 64 +#define RAW_RX_RING_CAP 16 +#define RAW_RX_MAX_BYTES 32768 + +typedef struct raw_rx_entry { + netpkt_t* pkt; + uint32_t len; + net_l4_endpoint src; + SockBindSpec rx_spec; + uint32_t generation; +} raw_rx_entry_t; + +typedef struct raw_socket { + ksocket_t* ownerSocket; + SocketOptions options; + bool registered; + bool bound; + bool connected; + SockBindSpec bind_spec; + uint32_t bind_generation; + SockBindSpec last_rx_spec; + net_l4_endpoint remote_ep; + raw_rx_entry_t rx[RAW_RX_RING_CAP]; + uint8_t rx_head; + uint8_t rx_tail; + uint8_t rx_count; + uint32_t rx_bytes; +} raw_socket_t; + +static raw_socket_t* g_raw_sockets[RAW_SOCKET_MAX]; +static uint32_t g_raw_count; + +static int32_t raw_set_filter(raw_socket_t* s, const void* value, uint32_t len) { + if (!s) return SOCK_ERR_INVAL; + if (!value && !len) { + memset(&s->options.raw_filter, 0, sizeof(s->options.raw_filter)); + s->options.flags &= ~SOCK_OPT_FILTER; + return SOCK_OK; + } + if (!value || len != sizeof(SocketRawFilter)) return SOCK_ERR_INVAL; + + SocketRawFilter filter; + memcpy(&filter, value, sizeof(filter)); + if (filter.count > SOCKET_RAW_FILTER_MAX_RULES) return SOCK_ERR_INVAL; + for (uint32_t i = 0; i < filter.count; i++) { + SocketRawFilterRule* rule = &filter.rules[i]; + if (rule->reserved || (rule->flags & ~(SOCKET_RAW_FILTER_HAS_CODE | SOCKET_RAW_FILTER_HAS_ID | SOCKET_RAW_FILTER_HAS_SEQ))) return SOCK_ERR_INVAL; + if (!(rule->flags & SOCKET_RAW_FILTER_HAS_CODE) && rule->code) return SOCK_ERR_INVAL; + if (!(rule->flags & SOCKET_RAW_FILTER_HAS_ID) && rule->id) return SOCK_ERR_INVAL; + if (!(rule->flags & SOCKET_RAW_FILTER_HAS_SEQ) && rule->seq) return SOCK_ERR_INVAL; + } + + s->options.raw_filter = filter; + if (filter.count) s->options.flags |= SOCK_OPT_FILTER; + else s->options.flags &= ~SOCK_OPT_FILTER; + return SOCK_OK; +} + +static bool raw_enqueue(raw_socket_t* s, const net_l4_endpoint* src, const SockBindSpec* rx_spec, uint32_t generation, netpkt_t* pkt, uint32_t len) { + if (!s || !src || !pkt || !len || len > RAW_RX_MAX_BYTES) return false; + + if (s->options.raw_filter.count) { + uint8_t hdr[8]; + uint32_t hdr_len = len >= sizeof(hdr) ? (uint32_t)sizeof(hdr) : len; + if (!netpkt_copyout(pkt, 0, hdr, hdr_len)) return false; + bool ok = false; + for (uint32_t i = 0; i < s->options.raw_filter.count; i++) { + const SocketRawFilterRule* rule = &s->options.raw_filter.rules[i]; + if (rule->type != hdr[0]) continue; + if ((rule->flags & SOCKET_RAW_FILTER_HAS_CODE) && (hdr_len < 2 || rule->code != hdr[1])) continue; + if ((rule->flags & SOCKET_RAW_FILTER_HAS_ID) && (hdr_len < 6 || rule->id != rd_be16(hdr+4))) continue; + if ((rule->flags & SOCKET_RAW_FILTER_HAS_SEQ) && (hdr_len < 8 || rule->seq != rd_be16(hdr+6))) continue; + ok = true; + break; + } + if (!ok) return false; + } + + netpkt_ref(pkt); + + irq_flags_t irq = irq_save_disable(); + bool source_valid = false; + if (rx_spec && rx_spec->kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(rx_spec->ifindex); + source_valid = l2 && l2->generation == generation; + } else if (rx_spec && (rx_spec->kind == BIND_L3 || rx_spec->kind == BIND_IP)) { + if (rx_spec->ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(rx_spec->l3_id); + source_valid = v6 && v6->generation == generation; + } else { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(rx_spec->l3_id); + source_valid = v4 && v4->generation == generation; + } + } + if (!source_valid || (s->bound && !s->bind_generation) || s->rx_count >= RAW_RX_RING_CAP || s->rx_bytes > RAW_RX_MAX_BYTES - len) { + irq_restore(irq); + netpkt_unref(pkt); + return false; + } + + uint8_t pos = s->rx_tail; + s->rx[pos].pkt = pkt; + s->rx[pos].len = len; + s->rx[pos].src = *src; + if (rx_spec) s->rx[pos].rx_spec = *rx_spec; + else { + memset(&s->rx[pos].rx_spec, 0, sizeof(s->rx[pos].rx_spec)); + s->rx[pos].rx_spec.kind = BIND_ANY; + } + s->rx[pos].generation = generation; + s->rx_tail = (uint8_t)((s->rx_tail + 1) % RAW_RX_RING_CAP); + s->rx_count++; + s->rx_bytes += len; + irq_restore(irq); + return true; +} + +socket_impl_t socket_raw_create(ksocket_t* owner, const SocketOptions* extra) { + if (!owner || socket_core_special_kind(owner) != SOCKET_SPECIAL_RAW) return NULL; + protocol_t proto = socket_core_protocol(owner); + if (proto != PROTO_ICMP && proto != PROTO_IGMP && proto != PROTO_ICMPV6) return NULL; + //TODO add ESP/AH sock if needed + + uint32_t supported = SOCK_OPT_RECV_TIMEOUT | SOCK_OPT_DEBUG | SOCK_OPT_DONTFRAG | SOCK_OPT_TTL | SOCK_OPT_FILTER | SOCK_OPT_SPECIAL | SOCK_OPT_NONBLOCK | SOCK_OPT_DONTROUTE; + if (extra && (extra->flags & ~supported)) return NULL; + + raw_socket_t* s = (raw_socket_t*)zalloc(sizeof(raw_socket_t)); + if (!s) return NULL; + + s->ownerSocket = owner; + s->options.flags = SOCK_OPT_SPECIAL; + s->options.special_kind = SOCKET_SPECIAL_RAW; + s->last_rx_spec.kind = BIND_ANY; + if (extra) { + if ((extra->flags & SOCK_OPT_RECV_TIMEOUT) && extra->recv_timeout_ms) { + s->options.flags |= SOCK_OPT_RECV_TIMEOUT; + s->options.recv_timeout_ms = extra->recv_timeout_ms; + } + + if (extra->flags & SOCK_OPT_DEBUG) { + if (extra->debug_level > SOCK_DBG_ALL) { + release(s); + return NULL; + } + s->options.flags |= SOCK_OPT_DEBUG; + s->options.debug_level = extra->debug_level; + } + + if (extra->flags & SOCK_OPT_DONTFRAG) s->options.flags |= SOCK_OPT_DONTFRAG; + if (extra->flags & SOCK_OPT_NONBLOCK) s->options.flags |= SOCK_OPT_NONBLOCK; + if (extra->flags & SOCK_OPT_DONTROUTE) s->options.flags |= SOCK_OPT_DONTROUTE; + if ((extra->flags & SOCK_OPT_TTL) && extra->ttl) { + s->options.flags |= SOCK_OPT_TTL; + s->options.ttl = extra->ttl; + } + if (extra->flags & SOCK_OPT_FILTER) { + if (raw_set_filter(s, &extra->raw_filter, sizeof(extra->raw_filter)) != SOCK_OK) { + release(s); + return NULL; + } + } + } + + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < RAW_SOCKET_MAX; i++) { + if (!g_raw_sockets[i]) { + g_raw_sockets[i] = s; + g_raw_count++; + s->registered = true; + irq_restore(irq); + return s; + } + } + irq_restore(irq); + release(s); + return NULL; +} + +void socket_destroy_raw(socket_impl_t sh) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s) return; + + if (s->registered) { + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < RAW_SOCKET_MAX; i++) if (g_raw_sockets[i] == s) { + g_raw_sockets[i] = NULL; + if (g_raw_count) g_raw_count--; + break; + } + s->registered = false; + irq_restore(irq); + } + + for (int i = 0; i < RAW_RX_RING_CAP; i++) if (s->rx[i].pkt) netpkt_unref(s->rx[i].pkt); + release(s); +} + +int32_t socket_close_raw(socket_impl_t sh) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + if (s->registered) { + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < RAW_SOCKET_MAX; i++) { + if (g_raw_sockets[i] == s) { + g_raw_sockets[i] = NULL; + if (g_raw_count) g_raw_count--; + break; + } + } + s->registered = false; + irq_restore(irq); + } + + return SOCK_OK; +} + +int32_t socket_setopt_raw(socket_impl_t sh, int32_t opt, const void* value, uint32_t len) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_OPT_RECV_TIMEOUT: + case SOCK_OPT_DEBUG: + case SOCK_OPT_DONTFRAG: + case SOCK_OPT_TTL: + case SOCK_OPT_NONBLOCK: + case SOCK_OPT_DONTROUTE: + return socket_common_options_set(&s->options, opt, value, len); + case SOCK_OPT_FILTER: + return raw_set_filter(s, value, len); + case SOCK_OPT_BUF_SIZE: + case SOCK_OPT_SPECIAL: + return SOCK_ERR_UNSUP; + default: + return SOCK_ERR_INVAL; + } +} + +int32_t socket_getopt_raw(socket_impl_t sh, int32_t opt, void* value, uint32_t* len) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || !len) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_GET_REMOTE_ENDPOINT: + return socket_common_get_value(&s->remote_ep, sizeof(s->remote_ep), value, len); + case SOCK_GET_BIND_SPEC: + return socket_common_get_value(&s->bind_spec, sizeof(s->bind_spec), value, len); + case SOCK_GET_LAST_RX_SPEC: + return socket_common_get_value(&s->last_rx_spec, sizeof(s->last_rx_spec), value, len); + case SOCK_GET_OPT_FILTER: + return socket_common_get_value(&s->options.raw_filter, sizeof(s->options.raw_filter), value, len); + default: + break; + } + + uint32_t v = 0; + switch ((uint32_t)opt) { + case SOCK_GET_BOUND: + v = s->bound && s->bind_generation != 0; + break; + case SOCK_GET_CONNECTED: + v = s->connected; + break; + case SOCK_GET_RECV_QUEUED: + v = s->rx_bytes; + break; + case SOCK_GET_LISTENING: + case SOCK_GET_LOCAL_PORT: + case SOCK_GET_SEND_QUEUED: + case SOCK_GET_OPT_KEEPALIVE: + case SOCK_GET_OPT_KEEPALIVE_INTERVAL: + case SOCK_GET_OPT_TCP_NO_DELAY: + case SOCK_GET_OPT_SEND_BUF_SIZE: + case SOCK_GET_TCP_STATE: + case SOCK_GET_TCP_MSS: + case SOCK_GET_TCP_RTT_MS: + case SOCK_GET_TCP_RETRANSMITS: + case SOCK_GET_TCP_URGENT_REMAINING: + case SOCK_GET_MCAST_GROUPS: + return SOCK_ERR_UNSUP; + case SOCK_GET_OPT_RECV_TIMEOUT: + case SOCK_GET_OPT_DEBUG: + case SOCK_GET_OPT_DONTFRAG: + case SOCK_GET_OPT_TTL: + case SOCK_GET_OPT_NONBLOCK: + case SOCK_GET_OPT_DONTROUTE: + return socket_common_options_get(&s->options, opt, value, len); + case SOCK_GET_OPT_SEND_TIMEOUT: + case SOCK_GET_OPT_BUF_SIZE: + case SOCK_GET_OPT_BROADCAST_ALLOWED: + return SOCK_ERR_UNSUP; + default: + return SOCK_ERR_INVAL; + } + + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int32_t socket_bind_raw(socket_impl_t sh, const SockBindSpec* spec) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || !spec) return SOCK_ERR_INVAL; + + protocol_t proto = socket_core_protocol(s->ownerSocket); + SockBindSpec next; + memset(&next, 0, sizeof(next)); + + if (spec->kind == BIND_ANY || ((proto == PROTO_ICMP || proto == PROTO_IGMP) && spec->kind == BIND_ANY4) || (proto == PROTO_ICMPV6 && spec->kind == BIND_ANY6)) { + s->bind_spec = next; + s->bind_generation = 0; + s->bound = false; + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + return SOCK_OK; + } + uint32_t generation = 0; + if (spec->kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(spec->ifindex); + if (!l2) return SOCK_ERR_INVAL; + next.kind = BIND_L2; + next.ifindex = spec->ifindex; + generation = l2->generation; + } else if (spec->kind == BIND_L3) { + if (proto == PROTO_ICMP || proto == PROTO_IGMP) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(spec->l3_id); + if (!v4) return SOCK_ERR_INVAL; + next.ver = IP_VER4; + generation = v4->generation; + } else if (proto == PROTO_ICMPV6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(spec->l3_id); + if (!v6) return SOCK_ERR_INVAL; + next.ver = IP_VER6; + generation = v6->generation; + } else return SOCK_ERR_PROTO; + next.kind = BIND_L3; + next.l3_id = spec->l3_id; + } else if (spec->kind == BIND_IP) { + if ((proto == PROTO_ICMP || proto == PROTO_IGMP) && spec->ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, spec->ip, sizeof(ip)); + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(ip); + if (!v4) return SOCK_ERR_INVAL; + next.kind = BIND_IP; + next.ver = IP_VER4; + next.l3_id = v4->l3_id; + memcpy(next.ip, spec->ip, 4); + generation = v4->generation; + } else if (proto == PROTO_ICMPV6 && spec->ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_ip(spec->ip); + if (!v6) return SOCK_ERR_INVAL; + next.kind = BIND_IP; + next.ver = IP_VER6; + next.l3_id = v6->l3_id; + ipv6_cpy(next.ip, spec->ip); + generation = v6->generation; + } else return SOCK_ERR_INVAL; + } else return SOCK_ERR_INVAL; + + s->bind_spec = next; + s->bind_generation = generation; + s->bound = true; + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + return SOCK_OK; +} + +int32_t socket_connect_raw(socket_impl_t sh, const net_l4_endpoint* dst) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || !dst) return SOCK_ERR_INVAL; + + protocol_t proto = socket_core_protocol(s->ownerSocket); + if (((proto == PROTO_ICMP || proto == PROTO_IGMP) && dst->ver != IP_VER4) || (proto == PROTO_ICMPV6 && dst->ver != IP_VER6)) return SOCK_ERR_INVAL; + + s->remote_ep = *dst; + s->remote_ep.port = 0; + s->connected = true; + return SOCK_OK; +} + +int64_t socket_send_raw(socket_impl_t sh, const void* buf, uint64_t len) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || (!buf && len) || len > UINT32_MAX) return SOCK_ERR_INVAL; + if (!s->connected) return SOCK_ERR_NOT_BOUND; + return socket_sendto_raw(sh, &s->remote_ep, buf, len); +} + +int64_t socket_sendto_raw(socket_impl_t sh, const net_l4_endpoint* dst, const void* buf, uint64_t len) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || !dst || (!buf && len) || !len || len > NETPKT_MAX_ALLOC) return SOCK_ERR_INVAL; + + protocol_t proto = socket_core_protocol(s->ownerSocket); + if (((proto == PROTO_ICMP || proto == PROTO_IGMP) && dst->ver != IP_VER4) || (proto == PROTO_ICMPV6 && dst->ver != IP_VER6)) return SOCK_ERR_INVAL; + + if (s->bound) { + uint32_t generation = 0; + if (s->bind_spec.kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(s->bind_spec.ifindex); + generation = l2 ? l2->generation : 0; + } else if (proto == PROTO_ICMPV6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(s->bind_spec.l3_id); + generation = v6 ? v6->generation : 0; + } else { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(s->bind_spec.l3_id); + generation = v4 ? v4->generation : 0; + } + if (!generation || generation != s->bind_generation) return SOCK_ERR_NOT_FOUND; + } + + ip_tx_opts_t tx; + ip_tx_opts_t* txp = NULL; + if (s->bound && s->bind_spec.kind == BIND_L2) { + tx.scope = IP_TX_BOUND_L2; + tx.target.ifindex = s->bind_spec.ifindex; + txp = &tx; + } else if (s->bound && (s->bind_spec.kind == BIND_L3 || s->bind_spec.kind == BIND_IP)) { + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = s->bind_spec.l3_id; + txp = &tx; + } + + uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + ((proto == PROTO_ICMP || proto == PROTO_IGMP) ? (uint32_t)sizeof(ipv4_hdr_t) : (uint32_t)sizeof(ipv6_hdr_t)); + netpkt_t* pkt = netpkt_alloc((uint32_t)len, headroom, 0); + if (!pkt) return SOCK_ERR_SYS; + + void* p = netpkt_put(pkt, (uint32_t)len); + if (!p) { + netpkt_unref(pkt); + return SOCK_ERR_SYS; + } + memcpy(p, buf, (uint32_t)len); + + uint8_t ttl = (s->options.flags & SOCK_OPT_TTL) ? s->options.ttl : 0; + uint8_t dontfrag = (s->options.flags & SOCK_OPT_DONTFRAG) ? 1 : 0; + + if (proto == PROTO_ICMP || proto == PROTO_IGMP) { + uint32_t dst_ip = 0; + memcpy(&dst_ip, dst->ip, sizeof(dst_ip)); + if (s->options.flags & SOCK_OPT_DONTROUTE) { + ipv4_tx_plan_t plan; + if (!socket_bind_build_ipv4_tx_plan(&s->bind_spec, s->bound, dst_ip, &plan) || !ipv4_tx_plan_onlink(&plan, dst_ip)) { + netpkt_unref(pkt); + return SOCK_ERR_NO_ROUTE; + } + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = plan.l3_id; + txp = &tx; + } + return ipv4_send_packet(dst_ip, (uint8_t)proto, pkt, txp, ttl, dontfrag, (s->options.flags & SOCK_OPT_DONTROUTE) ? 1 : 0) ? (int64_t)len : SOCK_ERR_SYS; + } + + ipv6_tx_plan_t plan; + if (!ipv6_build_tx_plan(dst->ip, txp, &plan)) { + netpkt_unref(pkt); + return (s->options.flags & SOCK_OPT_DONTROUTE) ? SOCK_ERR_NO_ROUTE : SOCK_ERR_SYS; + } + + if (s->options.flags & SOCK_OPT_DONTROUTE){ + if (!ipv6_tx_plan_onlink(&plan, dst->ip)) { + netpkt_unref(pkt); + return SOCK_ERR_NO_ROUTE; + } + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = plan.l3_id; + txp = &tx; + } + + if (len >= 4) { + memset((uint8_t*)p + 2, 0, sizeof(uint16_t)); + uint16_t sum = bswap16(checksum16_pipv6(plan.src_ip, dst->ip, PROTO_ICMPV6, p, (uint32_t)len)); + memcpy((uint8_t*)p + 2, &sum, sizeof(sum)); + } + + return ipv6_send_packet(dst->ip, PROTO_ICMPV6, pkt, txp, ttl ? ttl : 64, dontfrag, (s->options.flags & SOCK_OPT_DONTROUTE) ? 1 : 0) ? (int64_t)len : SOCK_ERR_SYS; +} + +int64_t socket_recv_raw(socket_impl_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src) { + raw_socket_t* s = (raw_socket_t*)sh; + if (!s || (!buf && len) || len > UINT32_MAX) return SOCK_ERR_INVAL; + + uint32_t start_ms = (uint32_t)get_time(); + raw_rx_entry_t entry; + for (;;) { + irq_flags_t irq = irq_save_disable(); + if (s->rx_count) { + uint8_t pos = s->rx_head; + entry = s->rx[pos]; + memset(&s->rx[pos], 0, sizeof(s->rx[pos])); + if (s->rx_bytes >= entry.len) s->rx_bytes -= entry.len; + else s->rx_bytes = 0; + s->rx_head = (uint8_t)((s->rx_head + 1) % RAW_RX_RING_CAP); + s->rx_count--; + irq_restore(irq); + break; + } + irq_restore(irq); + + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + if ((s->options.flags & SOCK_OPT_RECV_TIMEOUT) && s->options.recv_timeout_ms) { + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - start_ms; + if (elapsed_ms >= s->options.recv_timeout_ms) return SOCK_ERR_WOULDBLOCK; + uint32_t wait_ms = s->options.recv_timeout_ms - elapsed_ms; + if (wait_ms > 5) wait_ms = 5; + msleep(wait_ms); + } else msleep(5); + } + + bool source_valid = false; + if (entry.rx_spec.kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(entry.rx_spec.ifindex); + source_valid = l2 && l2->generation == entry.generation; + } else if (entry.rx_spec.kind == BIND_L3 || entry.rx_spec.kind == BIND_IP) { + if (entry.rx_spec.ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(entry.rx_spec.l3_id); + source_valid = v6 && v6->generation == entry.generation; + } else { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(entry.rx_spec.l3_id); + source_valid = v4 && v4->generation == entry.generation; + } + } + if (!source_valid) { + netpkt_unref(entry.pkt); + return SOCK_ERR_NOT_FOUND; + } + + irq_flags_t irq = irq_save_disable(); + s->last_rx_spec = entry.rx_spec; + irq_restore(irq); + + uint32_t n = entry.len; + if (n > len) n = (uint32_t)len; + if (n && !netpkt_copyout(entry.pkt, 0, buf, n)) { + netpkt_unref(entry.pkt); + return SOCK_ERR_SYS; + } + if (out_src) *out_src = entry.src; + netpkt_unref(entry.pkt); + return n; +} + +static void raw_drop_rx(raw_socket_t* s, SockBindKind kind, uint32_t id) { + if (!s) return; + + netpkt_t* dropped[RAW_RX_RING_CAP]; + uint8_t dropped_count = 0; + raw_rx_entry_t kept[RAW_RX_RING_CAP]; + uint8_t kept_count = 0; + uint32_t kept_bytes = 0; + + irq_flags_t irq = irq_save_disable(); //TODO lock + for (uint8_t i = 0; i < s->rx_count; i++) { + uint8_t pos = (uint8_t)((s->rx_head + i) % RAW_RX_RING_CAP); + raw_rx_entry_t entry = s->rx[pos]; + bool drop = false; + if (kind == BIND_L2) drop = entry.rx_spec.kind == BIND_L2 && entry.rx_spec.ifindex == (uint8_t)id; + else if (entry.rx_spec.kind == BIND_L3 || entry.rx_spec.kind == BIND_IP) drop = entry.rx_spec.l3_id == (l3_id_t)id; + + if (drop) dropped[dropped_count++] = entry.pkt; + else { + kept[kept_count++] = entry; + kept_bytes += entry.len; + } + } + memset(s->rx, 0, sizeof(s->rx)); + for (uint8_t i = 0; i < kept_count; i++) s->rx[i] = kept[i]; + s->rx_head = 0; + s->rx_tail = (uint8_t)(kept_count % RAW_RX_RING_CAP); + s->rx_count = kept_count; + s->rx_bytes = kept_bytes; + irq_restore(irq); + + for (uint8_t i = 0; i < dropped_count; i++) if (dropped[i]) netpkt_unref(dropped[i]); +} + +void socket_raw_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation) { + raw_socket_t *s = (raw_socket_t*)sh; + if (!s || !ifindex) return; + + irq_flags_t irq = irq_save_disable(); + if (s->last_rx_spec.kind == BIND_L2 && s->last_rx_spec.ifindex == ifindex) { + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + } + if (s->bound && s->bind_spec.kind == BIND_L2 && s->bind_spec.ifindex == ifindex && s->bind_generation == generation) s->bind_generation = 0; + irq_restore(irq); + raw_drop_rx(s, BIND_L2, ifindex); +} + +void socket_raw_l3_deleted(socket_impl_t sh, l3_id_t l3_id) { + raw_socket_t *s = (raw_socket_t*)sh; + if (!s || !l3_id) return; + + irq_flags_t irq = irq_save_disable(); + if ((s->last_rx_spec.kind == BIND_L3 || s->last_rx_spec.kind == BIND_IP) && s->last_rx_spec.l3_id == l3_id) { + memset(&s->last_rx_spec, 0, sizeof(s->last_rx_spec)); + s->last_rx_spec.kind = BIND_ANY; + } + if (s->bound && (s->bind_spec.kind == BIND_L3 || s->bind_spec.kind == BIND_IP) && s->bind_spec.l3_id == l3_id) s->bind_generation = 0; + irq_restore(irq); + raw_drop_rx(s, BIND_L3, l3_id); +} + +bool socket_raw_input_v4(protocol_t protocol, uint8_t ifindex, uint32_t src, uint32_t dst, netpkt_t* pkt) { + if ((protocol != PROTO_ICMP && protocol != PROTO_IGMP) || !ifindex || !pkt) return false; + irq_flags_t check_irq = irq_save_disable(); //TODO lock + bool have_sockets = g_raw_count != 0; + irq_restore(check_irq); + if (!have_sockets) return false; + + net_l4_endpoint src_ep; + make_ep(&src, 0, IP_VER4, &src_ep); + + SockBindSpec rx_spec; + memset(&rx_spec, 0, sizeof(rx_spec)); + uint32_t rx_generation = 0; + l3_ipv4_interface_t* rx_l3 = l3_ipv4_find_by_ip(dst); + if (rx_l3) { + rx_spec.kind = BIND_L3; + rx_spec.ver = IP_VER4; + rx_spec.l3_id = rx_l3->l3_id; + rx_generation = rx_l3->generation; + } else { + l2_interface_t* rx_l2 = l2_interface_find_by_index(ifindex); + if (!rx_l2) return false; + rx_spec.kind = BIND_L2; + rx_spec.ifindex = ifindex; + rx_generation = rx_l2->generation; + } + + raw_socket_t* targets[RAW_SOCKET_MAX]; + int n = 0; + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < RAW_SOCKET_MAX; i++) { + raw_socket_t* s = g_raw_sockets[i]; + if (!s || socket_core_protocol(s->ownerSocket) != protocol || socket_core_is_closing(s->ownerSocket)) continue; + if (s->connected && !net_ep_equal(&s->remote_ep, &src_ep)) continue; + if (s->bound && s->bind_spec.kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(s->bind_spec.ifindex); + if (!l2 || l2->generation != s->bind_generation || s->bind_spec.ifindex != ifindex) continue; + } + if (s->bound && s->bind_spec.kind == BIND_L3) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(s->bind_spec.l3_id); + if (!v4 || v4->generation != s->bind_generation || !v4->l2 || v4->l2->ifindex != ifindex) continue; + if (!ipv4_is_multicast(dst) && dst != IPV4_LIMITED_BROADCAST && (!v4->mask || ipv4_broadcast_calc(v4->ip, v4->mask) != dst) && v4->ip != dst) continue; + } + if (s->bound && s->bind_spec.kind == BIND_IP) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(s->bind_spec.l3_id); + if (!v4 || v4->generation != s->bind_generation) continue; + uint32_t local = 0; + memcpy(&local, s->bind_spec.ip, sizeof(local)); + if (local != dst) continue; + } + socket_core_ref(s->ownerSocket); + targets[n++] = s; + } + irq_restore(irq); + + bool delivered = false; + uint32_t len = netpkt_len(pkt); + for (int i = 0; i < n; i++) { + raw_socket_t* s = targets[i]; + if (raw_enqueue(s, &src_ep, &rx_spec, rx_generation, pkt, len)) delivered = true; + socket_core_put(s->ownerSocket); + } + return delivered; +} + +bool socket_raw_input_v6(uint8_t ifindex, const uint8_t src[16], const uint8_t dst[16], netpkt_t* pkt) { + if (!ifindex || !src || !dst || !pkt) return false; + irq_flags_t check_irq = irq_save_disable(); //TODO lock + bool have_sockets = g_raw_count != 0; + irq_restore(check_irq); + if (!have_sockets) return false; + + net_l4_endpoint src_ep; + make_ep(src, 0, IP_VER6, &src_ep); + + SockBindSpec rx_spec; + memset(&rx_spec, 0, sizeof(rx_spec)); + uint32_t rx_generation = 0; + l3_ipv6_interface_t* rx_l3 = l3_ipv6_find_by_ip(dst); + if (rx_l3) { + rx_spec.kind = BIND_L3; + rx_spec.ver = IP_VER6; + rx_spec.l3_id = rx_l3->l3_id; + rx_generation = rx_l3->generation; + } else { + l2_interface_t* rx_l2 = l2_interface_find_by_index(ifindex); + if (!rx_l2) return false; + rx_spec.kind = BIND_L2; + rx_spec.ifindex = ifindex; + rx_generation = rx_l2->generation; + } + + raw_socket_t* targets[RAW_SOCKET_MAX]; + int n = 0; + irq_flags_t irq = irq_save_disable(); + for (int i = 0; i < RAW_SOCKET_MAX; i++) { + raw_socket_t* s = g_raw_sockets[i]; + if (!s || socket_core_protocol(s->ownerSocket) != PROTO_ICMPV6 || socket_core_is_closing(s->ownerSocket)) continue; + if (s->connected && !net_ep_equal(&s->remote_ep, &src_ep)) continue; + if (s->bound && s->bind_spec.kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(s->bind_spec.ifindex); + if (!l2 || l2->generation != s->bind_generation || s->bind_spec.ifindex != ifindex) continue; + } + if (s->bound && s->bind_spec.kind == BIND_L3) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(s->bind_spec.l3_id); + if (!v6 || v6->generation != s->bind_generation || !v6->l2 || v6->l2->ifindex != ifindex) continue; + if (!ipv6_is_multicast(dst) && ipv6_cmp(v6->ip, dst) != 0) continue; + } + if (s->bound && s->bind_spec.kind == BIND_IP) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(s->bind_spec.l3_id); + if (!v6 || v6->generation != s->bind_generation || ipv6_cmp(s->bind_spec.ip, dst) != 0) continue; + } + socket_core_ref(s->ownerSocket); + targets[n++] = s; + } + irq_restore(irq); + + bool delivered = false; + uint32_t len = netpkt_len(pkt); + for (int i = 0; i < n; i++) { + raw_socket_t* s = targets[i]; + if (raw_enqueue(s, &src_ep, &rx_spec, rx_generation, pkt, len)) delivered = true; + socket_core_put(s->ownerSocket); + } + return delivered; +} diff --git a/kernel/networking/transport_layer/csocket_raw.h b/kernel/networking/transport_layer/csocket_raw.h new file mode 100644 index 00000000..612adae6 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_raw.h @@ -0,0 +1,27 @@ +#pragma once + +#include "socket_core.h" +#include "networking/netpkt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +socket_impl_t socket_raw_create(ksocket_t* owner, const SocketOptions* extra); +void socket_destroy_raw(socket_impl_t sh); +int32_t socket_close_raw(socket_impl_t sh); +int32_t socket_setopt_raw(socket_impl_t sh, int32_t opt, const void* value, uint32_t len); +int32_t socket_getopt_raw(socket_impl_t sh, int32_t opt, void* value, uint32_t* len); +int32_t socket_bind_raw(socket_impl_t sh, const SockBindSpec* spec); +int32_t socket_connect_raw(socket_impl_t sh, const net_l4_endpoint* dst); +int64_t socket_send_raw(socket_impl_t sh, const void* buf, uint64_t len); +int64_t socket_sendto_raw(socket_impl_t sh, const net_l4_endpoint* dst, const void* buf, uint64_t len); +int64_t socket_recv_raw(socket_impl_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src); +void socket_raw_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation); +void socket_raw_l3_deleted(socket_impl_t sh, l3_id_t l3_id); +bool socket_raw_input_v4(protocol_t protocol, uint8_t ifindex, uint32_t src, uint32_t dst, netpkt_t* pkt); +bool socket_raw_input_v6(uint8_t ifindex, const uint8_t src[16], const uint8_t dst[16], netpkt_t* pkt); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/csocket_tcp.c b/kernel/networking/transport_layer/csocket_tcp.c new file mode 100644 index 00000000..7718f7c6 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_tcp.c @@ -0,0 +1,922 @@ +#include "csocket_tcp.h" +#include "networking/transport_layer/socket_bind.h" +#include "networking/transport_layer/tcp.h" +#include "networking/transport_layer/tcp/tcp_internal.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_route.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_route.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/net_logger/net_logger.h" +#include "networking/firewall.h" +#include "syscalls/syscalls.h" +#include "std/memory.h" +#include "alloc/allocate.h" +#include "exceptions/irq.h" + +#define TCP_MAX_BACKLOG 128 + +typedef struct tcp_socket { + uint16_t localPort; + net_l4_endpoint remoteEP; + bool connected; + ksocket_t* ownerSocket; + SocketOptions options; + SockBindSpec bindSpec; + socket_bind_token_t bindToken; + uint32_t bind_generation; + tcp_data flow; + ksocket_t** pending; + int32_t backlogCap; + int32_t backlogLen; + bool listening; + bool closed; +} tcp_socket_t; + +static int32_t tcp_socket_backlog_len(tcp_socket_t* s) { + if (!s) return 0; + + irq_flags_t irq = irq_save_disable(); + int32_t len = s->backlogLen; + irq_restore(irq); + return len; +} + +static ksocket_t* tcp_socket_pop_pending_at(tcp_socket_t* s, int32_t idx, ksocket_t* expected) { + if (!s) return NULL; + + irq_flags_t irq = irq_save_disable(); + if (idx < 0 || idx >= s->backlogLen || (expected && s->pending[idx] != expected)) { + irq_restore(irq); + return NULL; + } + + ksocket_t* client = s->pending[idx]; + for (int32_t i = idx + 1; i < s->backlogLen; ++i) s->pending[i-1] = s->pending[i]; + + s->pending[--s->backlogLen] = NULL; + irq_restore(irq); + return client; +} + +static void tcp_socket_reset_connection(tcp_socket_t* s, bool abort_flow) { + if (!s) return; + if (abort_flow && s->flow.flow_generation) tcp_flow_abort(&s->flow); + + s->connected = false; + memset(&s->flow, 0, sizeof(s->flow)); + s->remoteEP.port = 0; + s->remoteEP.ver = IP_VER4; + memset(s->remoteEP.ip, 0, sizeof(s->remoteEP.ip)); +} + +static void tcp_socket_abort_pending(ksocket_t* owner) { + if (!owner) return; + + tcp_socket_t* child = (tcp_socket_t*)socket_core_impl(owner); + if (child && child->flow.flow_generation) tcp_socket_reset_connection(child, true); + + socket_core_close_socket(owner); + socket_core_put(owner); +} + +static int32_t tcp_socket_connection_state(tcp_socket_t* s) { + if (!s) return -1; + if (!s->flow.flow_generation) { + tcp_socket_reset_connection(s, false); + return -1; + } + + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (!flow) { + tcp_socket_reset_connection(s, false); + return -1; + } + + tcp_state_t state = flow->base.state; + tcp_flow_put(flow); + + if (state == TCP_ESTABLISHED || state == TCP_CLOSE_WAIT) { + s->connected = true; + return 1; + } + + if (state == TCP_STATE_CLOSED || state == TCP_TIME_WAIT) { + if (tcp_flow_release_closed(&s->flow) == TCP_BUSY) return 0; + tcp_socket_reset_connection(s, false); + return -1; + } + + return 0; +} + +socket_impl_t socket_tcp_create(ksocket_t* owner, const SocketOptions* extra) { + if (!owner) return NULL; + + uint32_t supported = SOCK_OPT_KEEPALIVE | SOCK_OPT_KEEPALIVE_INTERVAL | SOCK_OPT_SEND_TIMEOUT | SOCK_OPT_RECV_TIMEOUT | SOCK_OPT_BUF_SIZE | SOCK_OPT_DEBUG | SOCK_OPT_DONTFRAG | SOCK_OPT_TTL | SOCK_OPT_SEND_BUF_SIZE | SOCK_OPT_TCP_NO_DELAY | SOCK_OPT_NONBLOCK | SOCK_OPT_DONTROUTE | SOCK_OPT_REUSEADDR | SOCK_OPT_TCP_MAXSEG | SOCK_OPT_LINGER | SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK; + if (extra) { + if (extra->flags & ~supported) return NULL; + if ((extra->flags & SOCK_OPT_DEBUG) && extra->debug_level > SOCK_DBG_ALL) return NULL; + if ((extra->flags & SOCK_OPT_BUF_SIZE) && !extra->buf_size) return NULL; + if ((extra->flags & SOCK_OPT_SEND_BUF_SIZE) && !extra->send_buf_size) return NULL; + if ((extra->flags & SOCK_OPT_KEEPALIVE_INTERVAL) && !extra->keepalive_ms) return NULL; + } + + tcp_socket_t* s = (tcp_socket_t*)zalloc(sizeof(*s)); + if (!s) return NULL; + + s->ownerSocket = owner; + s->remoteEP.ver = IP_VER4; + s->bindSpec.kind = BIND_ANY; + if (extra) s->options = *extra; + s->options.flags |= SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK; + if (!(s->options.flags & SOCK_OPT_BUF_SIZE)) { + s->options.flags |= SOCK_OPT_BUF_SIZE; + s->options.buf_size = TCP_DEFAULT_RCV_BUF; + } + + s->options.buf_size = tcp_clamp_rcvbuf(s->options.buf_size); + if (!(s->options.flags & SOCK_OPT_TCP_MAXSEG) || !s->options.tcp_maxseg) { + s->options.flags &= ~SOCK_OPT_TCP_MAXSEG; + s->options.tcp_maxseg = 0; + } else if (s->options.tcp_maxseg < 256u || s->options.tcp_maxseg > TCP_MAX_MSS) { + release(s); + return NULL; + } + if (!(s->options.flags & SOCK_OPT_LINGER) || !s->options.linger.enabled) { + s->options.flags &= ~SOCK_OPT_LINGER; + memset(&s->options.linger, 0, sizeof(s->options.linger)); + } else s->options.linger.enabled = 1; + if (s->options.flags & SOCK_OPT_TCP_DSACK) s->options.flags |= SOCK_OPT_TCP_SACK; + return s; +} + +int32_t socket_setopt_tcp(socket_impl_t sh, int32_t opt, const void* value, uint32_t len) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_OPT_KEEPALIVE: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (v) { + s->options.flags |= SOCK_OPT_KEEPALIVE; + if (!s->options.keepalive_ms) s->options.keepalive_ms = SOCKET_DEFAULT_KEEPALIVE_MS; + } else s->options.flags &= ~SOCK_OPT_KEEPALIVE; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + case SOCK_OPT_KEEPALIVE_INTERVAL: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (!v) return SOCK_ERR_INVAL; + s->options.keepalive_ms = v; + s->options.flags |= SOCK_OPT_KEEPALIVE | SOCK_OPT_KEEPALIVE_INTERVAL; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + case SOCK_OPT_TCP_NO_DELAY: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (v) s->options.flags |= SOCK_OPT_TCP_NO_DELAY; + else s->options.flags &= ~SOCK_OPT_TCP_NO_DELAY; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + case SOCK_OPT_SEND_BUF_SIZE: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (!v) return SOCK_ERR_INVAL; + s->options.send_buf_size = v; + s->options.flags |= SOCK_OPT_SEND_BUF_SIZE; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + case SOCK_OPT_REUSEADDR: { + if (s->localPort || s->connected || s->listening) return SOCK_ERR_STATE; + return socket_common_options_set(&s->options, opt, value, len); + } + case SOCK_OPT_DONTROUTE: { + if (s->flow.flow_generation) return SOCK_ERR_STATE; + return socket_common_options_set(&s->options, opt, value, len); + } + case SOCK_OPT_NONBLOCK: + return socket_common_options_set(&s->options, opt, value, len); + case SOCK_OPT_TCP_SACK: + case SOCK_OPT_TCP_DSACK: + if (s->flow.flow_generation || s->listening) return SOCK_ERR_STATE; + return socket_common_options_set(&s->options, opt, value, len); + case SOCK_OPT_TCP_MAXSEG: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (v && (v < 256u || v > TCP_MAX_MSS)) return SOCK_ERR_INVAL; + s->options.tcp_maxseg = v; + if (v) s->options.flags |= SOCK_OPT_TCP_MAXSEG; + else s->options.flags &= ~SOCK_OPT_TCP_MAXSEG; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + case SOCK_OPT_LINGER: { + if (!value || len != sizeof(SocketLinger)) return SOCK_ERR_INVAL; + SocketLinger linger; + memcpy(&linger, value, sizeof(linger)); + if (linger.enabled) { + linger.enabled = 1; + s->options.linger = linger; + s->options.flags |= SOCK_OPT_LINGER; + } else { + memset(&s->options.linger, 0, sizeof(s->options.linger)); + s->options.flags &= ~SOCK_OPT_LINGER; + } + return SOCK_OK; + } + case SOCK_OPT_REUSEPORT: + case SOCK_OPT_MCAST_JOIN: + case SOCK_OPT_MCAST_LEAVE: + case SOCK_OPT_BROADCAST_ALLOWED: + case SOCK_OPT_FILTER: + case SOCK_OPT_SPECIAL: + case SOCK_OPT_PACKET_TRUNK: + return SOCK_ERR_UNSUP; + case SOCK_OPT_BUF_SIZE: { + if (!value || len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + if (s->flow.flow_generation || s->listening) return SOCK_ERR_STATE; + uint32_t v = 0; + memcpy(&v, value, sizeof(v)); + if (!v || v > TCP_RCV_BUF_MAX) return SOCK_ERR_INVAL; + v = tcp_clamp_rcvbuf(v); + return socket_common_options_set(&s->options, opt, &v, sizeof(v)); + } + case SOCK_OPT_RECV_TIMEOUT: + case SOCK_OPT_SEND_TIMEOUT: + case SOCK_OPT_DEBUG: + return socket_common_options_set(&s->options, opt, value, len); + case SOCK_OPT_DONTFRAG: + case SOCK_OPT_TTL: { + int32_t rc = socket_common_options_set(&s->options, opt, value, len); + if (rc != SOCK_OK) return rc; + if (s->flow.flow_generation) tcp_flow_apply_socket_options(&s->flow, &s->options, (uint32_t)opt); + return SOCK_OK; + } + default: + return SOCK_ERR_INVAL; + } +} + +int32_t socket_getopt_tcp(socket_impl_t sh, int32_t opt, void* value, uint32_t* len) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s || !len) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_GET_REMOTE_ENDPOINT: + return socket_common_get_value(&s->remoteEP, sizeof(s->remoteEP), value, len); + case SOCK_GET_BIND_SPEC: + return socket_common_get_value(&s->bindSpec, sizeof(s->bindSpec), value, len); + case SOCK_GET_LAST_RX_SPEC: { + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + spec.kind = BIND_ANY; + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + if (flow->base.l3_id) { + spec.kind = BIND_L3; + spec.ver = flow->base.local.ver; + spec.l3_id = flow->base.l3_id; + } + tcp_flow_put(flow); + } + return socket_common_get_value(&spec, sizeof(spec), value, len); + } + case SOCK_GET_OPT_LINGER: + return socket_common_get_value( &s->options.linger, sizeof(s->options.linger), value, len); + default: + break; + } + + uint32_t v = 0; + switch ((uint32_t)opt) { + case SOCK_GET_BOUND: + v = s->localPort != 0; + break; + case SOCK_GET_CONNECTED: + v = tcp_socket_connection_state(s) > 0; + break; + case SOCK_GET_LISTENING: + v = s->listening; + break; + case SOCK_GET_LOCAL_PORT: + v = s->localPort; + break; + case SOCK_GET_RECV_QUEUED: + v = s->flow.flow_generation ? tcp_flow_readable(&s->flow) : 0; + break; + case SOCK_GET_SEND_QUEUED: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + v = flow->tx.queued_bytes + flow->tx.nagle_len; + tcp_flow_put(flow); + } + break; + } + case SOCK_GET_TCP_STATE: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + v = flow->base.state; + tcp_flow_put(flow); + } else v = s->listening ? TCP_LISTEN : TCP_STATE_CLOSED; + break; + } + case SOCK_GET_TCP_MSS: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + v = flow->tx.mss; + tcp_flow_put(flow); + } + break; + } + case SOCK_GET_TCP_RTT_MS: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + v = flow->tx.rtt_valid ? flow->tx.srtt : 0; + tcp_flow_put(flow); + } + break; + } + case SOCK_GET_TCP_RETRANSMITS: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; ++i) if (flow->tx.txq[i].used) v += flow->tx.txq[i].retransmit_cnt; + tcp_flow_put(flow); + } + break; + } + case SOCK_GET_TCP_URGENT_REMAINING: { + tcp_flow_t* flow = tcp_flow_from_ctx(&s->flow); + if (flow) { + if (flow->rx.urg_valid && TCP_SEQ_GT(flow->rx.urg_seq, flow->rx.rcv_base)) v = flow->rx.urg_seq - flow->rx.rcv_base; + tcp_flow_put(flow); + } + break; + } + case SOCK_GET_OPT_KEEPALIVE: + v = (s->options.flags & SOCK_OPT_KEEPALIVE) != 0; + break; + case SOCK_GET_OPT_KEEPALIVE_INTERVAL: + v = s->options.keepalive_ms; + break; + case SOCK_GET_OPT_TCP_NO_DELAY: + v = (s->options.flags & SOCK_OPT_TCP_NO_DELAY) != 0; + break; + case SOCK_GET_OPT_SEND_BUF_SIZE: + v = s->options.send_buf_size; + break; + case SOCK_GET_OPT_TCP_MAXSEG: + v = s->options.tcp_maxseg; + break; + case SOCK_GET_OPT_REUSEPORT: + case SOCK_GET_MCAST_GROUPS: + case SOCK_GET_OPT_BROADCAST_ALLOWED: + case SOCK_GET_OPT_FILTER: + case SOCK_GET_OPT_PACKET_TRUNK: + return SOCK_ERR_UNSUP; + case SOCK_GET_OPT_RECV_TIMEOUT: + case SOCK_GET_OPT_SEND_TIMEOUT: + case SOCK_GET_OPT_BUF_SIZE: + case SOCK_GET_OPT_DEBUG: + case SOCK_GET_OPT_DONTFRAG: + case SOCK_GET_OPT_TTL: + case SOCK_GET_OPT_NONBLOCK: + case SOCK_GET_OPT_DONTROUTE: + case SOCK_GET_OPT_REUSEADDR: + case SOCK_GET_OPT_TCP_SACK: + case SOCK_GET_OPT_TCP_DSACK: + return socket_common_options_get(&s->options, opt, value, len); + default: + return SOCK_ERR_INVAL; + } + + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int32_t socket_bind_tcp(socket_impl_t sh, const SockBindSpec* spec_in, uint16_t port) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s || !spec_in) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_TCP; + ev.action = NETLOG_ACT_BIND; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u0 = port; + ev.bind_spec = *spec_in; + netlog_socket_event(&s->options, &ev); + + if (s->localPort) return SOCK_ERR_BOUND; + if (!s->ownerSocket) return SOCK_ERR_SYS; + + SockBindSpec spec = *spec_in; + if (!socket_bind_prepare_spec(&spec, PROTO_TCP)) return SOCK_ERR_INVAL; + + int32_t bind_port = port; + socket_bind_token_t token = 0; + if (bind_port == 0) { + bind_port = socket_bind_alloc_ephemeral(s->ownerSocket, PROTO_TCP, &spec, s->options.flags & SOCK_OPT_REUSEADDR, &token); + if (bind_port < 0) return SOCK_ERR_NO_PORT; + } else if (!socket_bind_insert(s->ownerSocket, PROTO_TCP, &spec, port, s->options.flags & SOCK_OPT_REUSEADDR, true, &token)) return SOCK_ERR_BOUND; + + s->bindSpec = spec; + s->bindToken = token; + l2_interface_t *link = spec.kind == BIND_L2 ? l2_interface_find_by_index(spec.ifindex) : NULL; + s->bind_generation = link ? link->generation : 0; + s->localPort = (uint16_t)bind_port; + return SOCK_OK; +} + +int32_t socket_listen_tcp(socket_impl_t sh, int32_t backlog) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + if (!s->bindToken || s->connected) return SOCK_ERR_STATE; + + int32_t cap = backlog > TCP_MAX_BACKLOG ? TCP_MAX_BACKLOG : backlog; + if (cap < 1) cap = 1; + if (s->listening && s->pending && s->backlogCap == cap) return SOCK_OK; + + ksocket_t** pending = (ksocket_t**)zalloc(sizeof(ksocket_t*)*cap); + if (!pending) return SOCK_ERR_SYS; + + irq_flags_t irq = irq_save_disable(); + ksocket_t** old_pending = s->pending; + int32_t old_cap = s->backlogCap; + int32_t old_len = s->backlogLen; + bool old_listening = s->listening; + int32_t valid_old_len = old_pending ? old_len : 0; + int32_t keep = valid_old_len < cap ? valid_old_len : cap; + for (int32_t i = 0; i < keep; ++i) pending[i] = old_pending[i]; + s->pending = pending; + s->backlogCap = cap; + s->backlogLen = keep; + s->listening = true; + irq_restore(irq); + + if (!socket_bind_tcp_listen(s->bindToken)) { + irq = irq_save_disable(); + s->pending = old_pending; + s->backlogCap = old_cap; + s->backlogLen = old_len; + s->listening = old_listening; + irq_restore(irq); + release(pending); + return SOCK_ERR_BOUND; + } + + for (int32_t i = keep; i < valid_old_len; ++i) { + if (!old_pending[i]) continue; + tcp_socket_abort_pending(old_pending[i]); + } + if (old_pending) release(old_pending); + return SOCK_OK; +} + +//TODO replace polling with a socket wait queue when kernel events are present +ksocket_t* socket_accept_tcp(socket_impl_t sh) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s || !s->listening || !s->pending) return NULL; + + uint32_t start_ms = get_time(); + while (1) { + irq_flags_t irq = irq_save_disable(); //TODO lock + ksocket_t* owner = s->listening && s->pending && s->backlogLen > 0 ? s->pending[0] : 0; + if (owner) socket_core_ref(owner); + irq_restore(irq); + + if (owner) { + tcp_socket_t* client = (tcp_socket_t*)socket_core_impl(owner); + int32_t state = client ? tcp_socket_connection_state(client) : -1; + if (state != 0) { + ksocket_t* ready = tcp_socket_pop_pending_at(s, 0, owner); + socket_core_put(owner); + if (ready) { + if (state > 0) return ready; + tcp_socket_abort_pending(ready); + } + continue; + } + socket_core_put(owner); + } + + if (!s->listening || !s->pending || s->closed) return NULL; + if (s->options.flags & SOCK_OPT_NONBLOCK) return NULL; + if ((s->options.flags & SOCK_OPT_RECV_TIMEOUT) && s->options.recv_timeout_ms) { + uint32_t elapsed = get_time() - start_ms; + if (elapsed >= s->options.recv_timeout_ms) return NULL; + } + msleep(5); + } +} + +int32_t socket_connect_tcp(socket_impl_t sh, const net_l4_endpoint* dst) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_TCP; + ev.action = NETLOG_ACT_CONNECT; + ev.pid = socket_core_pid(s->ownerSocket); + if (dst) ev.dst_ep = *dst; + netlog_socket_event(&s->options, &ev); + + if (s->listening) return SOCK_ERR_STATE; + if (!dst || !dst->port) return SOCK_ERR_INVAL; + if (dst->ver != IP_VER4 && dst->ver != IP_VER6) return SOCK_ERR_INVAL; + if (!firewall_allows(PROTO_TCP, NET_CTRL_FIREWALL_OUT, dst, s->localPort, false)) return SOCK_ERR_PERM; + if (s->flow.flow_generation) { + if (s->remoteEP.port && !net_ep_equal(&s->remoteEP, dst)) return SOCK_ERR_STATE; + } else { + if (s->connected) return SOCK_ERR_STATE; + + net_l4_endpoint d = *dst; + l3_id_t chosen_l3 = 0; + + if (d.ver == IP_VER6) { + if (ipv6_is_unspecified(d.ip) || ipv6_is_multicast(d.ip)) return SOCK_ERR_INVAL; + ipv6_tx_plan_t p6; + if (!socket_bind_build_ipv6_tx_plan(&s->bindSpec, s->localPort != 0, d.ip, &p6)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv6_tx_plan_onlink(&p6, d.ip)) return SOCK_ERR_NO_ROUTE; + chosen_l3 = p6.l3_id; + } else if (d.ver == IP_VER4) { + uint32_t dip = 0; + memcpy(&dip, d.ip, 4); + if (ipv4_is_unspecified(dip) || ipv4_is_multicast(dip) || ipv4_is_limited_broadcast(dip)) return SOCK_ERR_INVAL; + ipv4_tx_plan_t p4; + if (!socket_bind_build_ipv4_tx_plan(&s->bindSpec, s->localPort != 0, dip, &p4)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv4_tx_plan_onlink(&p4, dip)) return SOCK_ERR_NO_ROUTE; + chosen_l3 = p4.l3_id; + } else return SOCK_ERR_INVAL; + + if (!chosen_l3) return SOCK_ERR_NO_ROUTE; + + if (d.ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(chosen_l3); + if (!ipv4_l3_is_ready(v4)) return SOCK_ERR_SYS; + uint32_t dip = 0; + memcpy(&dip, d.ip, 4); + if (ipv4_is_directed_broadcast(v4->ip, v4->mask, dip)) return SOCK_ERR_INVAL; + } else if (d.ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen_l3); + if (!ipv6_l3_is_tcp_usable(v6)) return SOCK_ERR_SYS; + } else return SOCK_ERR_INVAL; + + bool ephemeral_allocated = false; + if (s->localPort == 0) { + if (!s->ownerSocket) return SOCK_ERR_SYS; + socket_bind_token_t token = 0; + int32_t p = socket_bind_alloc_ephemeral_l3(s->ownerSocket, PROTO_TCP, chosen_l3, s->options.flags & SOCK_OPT_REUSEADDR, &s->bindSpec, &token); + if (p < 0) return SOCK_ERR_NO_PORT; + s->localPort = (uint16_t)p; + s->bindToken = token; + ephemeral_allocated = true; + } + + memset(&s->flow, 0, sizeof(s->flow)); + if (!tcp_handshake_l3(chosen_l3, s->localPort, &d, &s->flow, &s->options)) { + tcp_socket_reset_connection(s, true); + + if (ephemeral_allocated) { + if (s->bindToken) { + socket_bind_remove(s->bindToken); + s->bindToken = 0; + } + s->localPort = 0; + memset(&s->bindSpec, 0, sizeof(s->bindSpec)); + s->bindSpec.kind = BIND_ANY; + } + return SOCK_ERR_SYS; + } + + s->remoteEP = d; + } + + uint32_t start_ms = get_time(); + uint32_t timeout_ms = (s->options.flags & SOCK_OPT_SEND_TIMEOUT) && s->options.send_timeout_ms ? s->options.send_timeout_ms : TCP_CONNECT_TIMEOUT_MS; + + while (1) { + int32_t state = tcp_socket_connection_state(s); + if (state > 0) break; + if (state < 0) return SOCK_ERR_STATE; + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + + uint32_t now_ms = get_time(); + if (now_ms - start_ms >= timeout_ms) { + tcp_socket_reset_connection(s, true); + return SOCK_ERR_WOULDBLOCK; + } + msleep(5); + } + + netlog_socket_event_t ev1 = {0}; + ev1.comp = NETLOG_COMP_TCP; + ev1.action = NETLOG_ACT_CONNECTED; + ev1.pid = socket_core_pid(s->ownerSocket); + ev1.u0 = s->localPort; + ev1.u1 = s->remoteEP.port; + ev1.local_port = s->localPort; + ev1.remote_ep = s->remoteEP; + netlog_socket_event(&s->options, &ev1); + return SOCK_OK; +} + +int64_t socket_send_tcp(socket_impl_t sh, const void* buf, uint64_t len) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_TCP; + ev.action = NETLOG_ACT_SEND; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u0 = (uint32_t)len; + ev.local_port = s->localPort; + ev.remote_ep = s->remoteEP; + netlog_socket_event(&s->options, &ev); + + int32_t connection_state = tcp_socket_connection_state(s); + if (connection_state <= 0 || !s->flow.flow_generation) return connection_state == 0 ? SOCK_ERR_WOULDBLOCK : SOCK_ERR_STATE; + if (!buf && len) return SOCK_ERR_INVAL; + if (!len) return 0; + + uint32_t chunk = len > UINT32_MAX ? UINT32_MAX : (uint32_t)len; + uint32_t start_ms = (uint32_t)get_time(); + while (1) { + s->flow.payload.ptr = (uintptr_t)buf; + s->flow.payload.size = chunk; + s->flow.flags = (1u << PSH_F) | (1u << ACK_F); + + tcp_result_t res = tcp_flow_send(&s->flow); + if (res != TCP_OK && res != TCP_WOULDBLOCK) return res == TCP_INVALID ? SOCK_ERR_STATE : SOCK_ERR_SYS; + if (s->flow.payload.size) return (int64_t)s->flow.payload.size; + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + + if ((s->options.flags & SOCK_OPT_SEND_TIMEOUT) && s->options.send_timeout_ms) { + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - start_ms; + if (elapsed_ms >= s->options.send_timeout_ms) return SOCK_ERR_WOULDBLOCK; + uint32_t wait_ms = s->options.send_timeout_ms - elapsed_ms; + if (wait_ms > 5) wait_ms = 5; + msleep(wait_ms); + }else msleep(5); + } +} + +int64_t socket_recv_tcp(socket_impl_t sh, void* buf, uint64_t len) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_TCP; + ev.action = NETLOG_ACT_RECV; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u0 = (uint32_t)len; + ev.local_port = s->localPort; + ev.remote_ep = s->remoteEP; + netlog_socket_event(&s->options, &ev); + + if (!buf || !len) return 0; + int32_t connection_state = tcp_socket_connection_state(s); + if (connection_state < 0) return 0; + if (connection_state == 0 || !s->flow.flow_generation) return SOCK_ERR_WOULDBLOCK; + + //TODO add receive low water mark support when socket events exist + uint32_t start_ms = (uint32_t)get_time(); + while (1) { + uint32_t readable = tcp_flow_readable(&s->flow); + bool closed = tcp_flow_recv_closed(&s->flow); + if (readable || closed) { + int64_t n = tcp_flow_read(&s->flow, buf, len); + if (n > 0) return n; + if (n == TCP_DISCONNECT) return 0; + if (n < 0) return SOCK_ERR_STATE; + } + if (closed || !s->connected) return 0; + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + if ((s->options.flags & SOCK_OPT_RECV_TIMEOUT) && s->options.recv_timeout_ms) { + + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - start_ms; + if (elapsed_ms >= s->options.recv_timeout_ms) return SOCK_ERR_WOULDBLOCK; + + uint32_t wait_ms = s->options.recv_timeout_ms - elapsed_ms; + if (wait_ms > 5) wait_ms = 5; + msleep(wait_ms); + }else msleep(5); + } +} + +int32_t socket_close_tcp(socket_impl_t sh) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + if (s->closed) return SOCK_OK; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_TCP; + ev.action = NETLOG_ACT_CLOSE; + ev.pid = socket_core_pid(s->ownerSocket); + ev.local_port = s->localPort; + ev.remote_ep = s->remoteEP; + netlog_socket_event(&s->options, &ev); + + int32_t connection_state = tcp_socket_connection_state(s); + bool linger = (s->options.flags & SOCK_OPT_LINGER) && s->options.linger.enabled; + if (s->flow.flow_generation) { + if (connection_state == 0 && !s->connected) tcp_flow_abort(&s->flow); + else if (connection_state >= 0) { + if (linger && s->options.linger.timeout_ms == 0) tcp_flow_abort(&s->flow); + else { + bool closed = tcp_flow_is_closed(&s->flow); + if (connection_state > 0 && !closed) { + tcp_result_t flush_rc = tcp_flow_flush(&s->flow); + if (flush_rc == TCP_OK || flush_rc == TCP_WOULDBLOCK) { + tcp_result_t close_rc = tcp_flow_close(&s->flow); + if (close_rc != TCP_OK && close_rc != TCP_INVALID) tcp_flow_abort(&s->flow); + } else if (flush_rc != TCP_INVALID) tcp_flow_abort(&s->flow); + closed = tcp_flow_is_closed(&s->flow); + } + + if (linger && !closed) { + if (s->options.flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + uint32_t start_ms = get_time(); + while (!tcp_flow_is_closed(&s->flow)) { + if (get_time() - start_ms >= s->options.linger.timeout_ms) break; + msleep(5); + } + closed = tcp_flow_is_closed(&s->flow); + } + + if (closed) { + tcp_result_t release_rc = tcp_flow_release_closed(&s->flow); + if (release_rc != TCP_OK && release_rc != TCP_INVALID && release_rc != TCP_BUSY) tcp_flow_abort(&s->flow); + } + } + } + } + + if (s->pending) { + for (int32_t i = 0; i < s->backlogLen; ++i) { + if (!s->pending[i]) continue; + tcp_socket_abort_pending(s->pending[i]); + s->pending[i] = NULL; + } + release(s->pending); + s->pending = NULL; + } + s->backlogCap = 0; + s->backlogLen = 0; + s->listening = false; + + if (s->bindToken) { + socket_bind_remove(s->bindToken); + s->bindToken = 0; + } + s->localPort = 0; + memset(&s->bindSpec, 0, sizeof(s->bindSpec)); + s->bindSpec.kind = BIND_ANY; + s->bind_generation = 0; + tcp_socket_reset_connection(s, false); + s->closed = true; + return SOCK_OK; +} + +void socket_tcp_l3_deleted(socket_impl_t sh, l3_id_t l3_id) { + tcp_socket_t *s = (tcp_socket_t*)sh; + if (!s || (s->bindSpec.kind != BIND_L3 && s->bindSpec.kind != BIND_IP) || s->bindSpec.l3_id != l3_id) return; + tcp_socket_reset_connection(s, true); + s->options.flags &= ~SOCK_OPT_LINGER; + socket_close_tcp(sh); +} + +void socket_tcp_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation) { + tcp_socket_t *s = (tcp_socket_t*)sh; + if (!s || s->bindSpec.kind != BIND_L2 || s->bindSpec.ifindex != ifindex || s->bind_generation != generation) return; + tcp_socket_reset_connection(s, true); + s->options.flags &= ~SOCK_OPT_LINGER; + socket_close_tcp(sh); +} + +void socket_destroy_tcp(socket_impl_t sh) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return; + int32_t rc = socket_close_tcp(s); + if (rc == SOCK_ERR_WOULDBLOCK) { + s->options.flags &= ~(SOCK_OPT_NONBLOCK | SOCK_OPT_LINGER); + memset(&s->options.linger, 0, sizeof(s->options.linger)); + rc = socket_close_tcp(s); + } + if (rc != SOCK_OK) tcp_socket_reset_connection(s, true); + release(s); +} + +const SocketOptions* socket_tcp_options(socket_impl_t sh) { + tcp_socket_t* s = (tcp_socket_t*)sh; + if (!s) return NULL; + return &s->options; +} + +int32_t tcp_accept_enqueue(ksocket_t* listener, ip_version_t ipver, l3_id_t l3_id, const void* src_ip_addr, const void* dst_ip_addr, uint16_t src_port, uint16_t dst_port) { + if (!listener) return SOCK_ERR_STATE; + tcp_socket_t* s = (tcp_socket_t*)socket_core_impl(listener); + if (!s) return SOCK_ERR_STATE; + if (!s->listening || !s->localPort || s->localPort != dst_port || !s->pending) return SOCK_ERR_STATE; + + for (int32_t i = 0;;) { + irq_flags_t irq = irq_save_disable(); + ksocket_t* owner = (i >= 0 && i < s->backlogLen) ? s->pending[i] : NULL; + if (owner) socket_core_ref(owner); + irq_restore(irq); + + if (!owner) break; + + tcp_socket_t* client = (tcp_socket_t*)socket_core_impl(owner); + if (client && client->flow.flow_generation) { + bool readable = tcp_flow_readable(&client->flow) != 0; + bool closed = tcp_flow_recv_closed(&client->flow); + if (!closed || readable) { + socket_core_put(owner); + ++i; + continue; + } + } + + ksocket_t* removed = tcp_socket_pop_pending_at(s, i, owner); + socket_core_put(owner); + if (!removed) continue; + tcp_socket_abort_pending(removed); + } + if (tcp_socket_backlog_len(s) >= s->backlogCap) { + tcp_stats.acceptq_drop_full++; + return SOCK_ERR_WOULDBLOCK; + } + + ksocket_t* child_owner = NULL; + uint16_t owner_pid = socket_core_pid(s->ownerSocket); + if (!socket_core_alloc(PROTO_TCP, SOCKET_SPECIAL_NONE, owner_pid, &child_owner)) return SOCK_ERR_WOULDBLOCK; + + tcp_socket_t* child = (tcp_socket_t*)socket_tcp_create(child_owner, NULL); + if (!child) { + socket_core_close_socket(child_owner); + return SOCK_ERR_WOULDBLOCK; + } + child->options = s->options; + child->options.flags &= ~SOCK_OPT_NONBLOCK; + + child->localPort = dst_port; + child->remoteEP.ver = ipver; + memset(child->remoteEP.ip, 0, sizeof(child->remoteEP.ip)); + if (ipver == IP_VER4) memcpy(child->remoteEP.ip, src_ip_addr, 4); + else ipv6_cpy(child->remoteEP.ip, src_ip_addr); + child->remoteEP.port = src_port; + child->bindSpec.kind = BIND_IP; + child->bindSpec.ver = ipver; + child->bindSpec.l3_id = l3_id; + if (ipver == IP_VER4) memcpy(child->bindSpec.ip, dst_ip_addr, 4); + else if (ipver == IP_VER6) ipv6_cpy(child->bindSpec.ip, dst_ip_addr); + + if (!tcp_get_ctx(dst_port, ipver, dst_ip_addr, child->remoteEP.ip, src_port, &child->flow)) { + socket_destroy_tcp(child); + socket_core_close_socket(child_owner); + return SOCK_ERR_STATE; + } + + if (!socket_core_attach_impl(child_owner, child, socket_destroy_tcp, socket_close_tcp, socket_setopt_tcp, socket_getopt_tcp)) { + tcp_socket_reset_connection(child, false); + socket_destroy_tcp(child); + socket_core_close_socket(child_owner); + return SOCK_ERR_STATE; + } + + child->connected = true; + socket_core_ref(child_owner); + + irq_flags_t irq = irq_save_disable(); + if (!s->listening || !s->pending || s->backlogCap <= 0) { + irq_restore(irq); + tcp_socket_reset_connection(child, false); + socket_core_close_socket(child_owner); + socket_core_put(child_owner); + return SOCK_ERR_STATE; + } + if (s->backlogLen >= s->backlogCap) { + irq_restore(irq); + tcp_socket_reset_connection(child, false); + socket_core_close_socket(child_owner); + socket_core_put(child_owner); + tcp_stats.acceptq_drop_full++; + return SOCK_ERR_WOULDBLOCK; + } + s->pending[s->backlogLen++] = child_owner; + irq_restore(irq); + return SOCK_OK; +} diff --git a/kernel/networking/transport_layer/csocket_tcp.cpp b/kernel/networking/transport_layer/csocket_tcp.cpp deleted file mode 100644 index 63659d87..00000000 --- a/kernel/networking/transport_layer/csocket_tcp.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "networking/transport_layer/socket_tcp.hpp" -#include "networking/transport_layer/socket.hpp" -#include "csocket_tcp.h" - -extern "C" { - -socket_handle_t socket_tcp_create(uint8_t role, uint32_t pid, const SocketExtraOptions* extra) { - return reinterpret_cast(new TCPSocket(role, pid, extra)); -} - -int32_t socket_bind_tcp_ex(socket_handle_t sh, const SockBindSpec* spec, uint16_t port) { - if (!sh || !spec) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->bind(*spec, port); -} - -int32_t socket_listen_tcp(socket_handle_t sh, int32_t backlog) { - if (!sh) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->listen(backlog); -} - -socket_handle_t socket_accept_tcp(socket_handle_t sh) { - if (!sh) return nullptr; - TCPSocket* srv = reinterpret_cast(sh); - TCPSocket* client = srv->accept(); - return reinterpret_cast(client); -} - -int32_t socket_connect_tcp_ex(socket_handle_t sh, uint8_t dst_kind, const void* dst, uint16_t port) { - if (!sh || !dst) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->connect(static_cast(dst_kind), dst, port); -} - -int64_t socket_send_tcp(socket_handle_t sh, const void* buf, uint64_t len) { - if (!sh) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->send(buf, len); -} - -int64_t socket_recv_tcp(socket_handle_t sh, void* buf, uint64_t len) { - if (!sh) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->recv(buf, len); -} - -int32_t socket_close_tcp(socket_handle_t sh) { - if (!sh) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->close(); -} - -void socket_destroy_tcp(socket_handle_t sh) { - if (!sh) return; - delete reinterpret_cast(sh); -} - -uint16_t socket_get_local_port_tcp(socket_handle_t sh) { - if (!sh) return 0; - return reinterpret_cast(sh)->get_local_port(); -} - -uint16_t socket_get_remote_port_tcp(socket_handle_t sh) { - if (!sh) return 0; - return reinterpret_cast(sh)->get_remote_port(); -} - -void socket_get_remote_ep_tcp(socket_handle_t sh, net_l4_endpoint* out) { - if (!sh || !out) return; - *out = reinterpret_cast(sh)->get_remote_ep(); -} - -uint8_t socket_get_protocol_tcp(socket_handle_t sh) { - if (!sh) return 0xFF; - return reinterpret_cast(sh)->get_protocol(); -} - -uint8_t socket_get_role_tcp(socket_handle_t sh) { - if (!sh) return 0xFF; - return reinterpret_cast(sh)->get_role(); -} - -bool socket_is_bound_tcp(socket_handle_t sh) { - if (!sh) return false; - return reinterpret_cast(sh)->is_bound(); -} - -bool socket_is_connected_tcp(socket_handle_t sh) { - if (!sh) return false; - return reinterpret_cast(sh)->is_connected(); -} - -} diff --git a/kernel/networking/transport_layer/csocket_tcp.h b/kernel/networking/transport_layer/csocket_tcp.h index bbc7deb2..a0f721ee 100644 --- a/kernel/networking/transport_layer/csocket_tcp.h +++ b/kernel/networking/transport_layer/csocket_tcp.h @@ -1,32 +1,28 @@ #pragma once #include "types.h" #include "net/network_types.h" -#include "socket.hpp" +#include "socket_core.h" #include "net/socket_types.h" #ifdef __cplusplus extern "C" { #endif -typedef void* socket_handle_t; - -socket_handle_t socket_tcp_create(uint8_t role, uint32_t pid, const SocketExtraOptions* extra); -int32_t socket_bind_tcp_ex(socket_handle_t sh, const SockBindSpec* spec, uint16_t port); -int32_t socket_listen_tcp(socket_handle_t sh, int32_t backlog); -socket_handle_t socket_accept_tcp(socket_handle_t sh); -int32_t socket_connect_tcp_ex(socket_handle_t sh, uint8_t dst_kind, const void* dst, uint16_t port); -int64_t socket_send_tcp(socket_handle_t sh, const void* buf, uint64_t len); -int64_t socket_recv_tcp(socket_handle_t sh, void* buf, uint64_t len); -int32_t socket_close_tcp(socket_handle_t sh); -void socket_destroy_tcp(socket_handle_t sh); - -uint16_t socket_get_local_port_tcp(socket_handle_t sh); -uint16_t socket_get_remote_port_tcp(socket_handle_t sh); -void socket_get_remote_ep_tcp(socket_handle_t sh, net_l4_endpoint* out); -uint8_t socket_get_protocol_tcp(socket_handle_t sh); -uint8_t socket_get_role_tcp(socket_handle_t sh); -bool socket_is_bound_tcp(socket_handle_t sh); -bool socket_is_connected_tcp(socket_handle_t sh); +socket_impl_t socket_tcp_create(ksocket_t* owner, const SocketOptions* extra); +int32_t socket_bind_tcp(socket_impl_t sh, const SockBindSpec* spec, uint16_t port); +int32_t socket_listen_tcp(socket_impl_t sh, int32_t backlog); +ksocket_t* socket_accept_tcp(socket_impl_t sh); +int32_t socket_connect_tcp(socket_impl_t sh, const net_l4_endpoint* dst); +int64_t socket_send_tcp(socket_impl_t sh, const void* buf, uint64_t len); +int64_t socket_recv_tcp(socket_impl_t sh, void* buf, uint64_t len); +int32_t socket_close_tcp(socket_impl_t sh); +int32_t socket_setopt_tcp(socket_impl_t sh, int32_t opt, const void* value, uint32_t len); +int32_t socket_getopt_tcp(socket_impl_t sh, int32_t opt, void* value, uint32_t* len); +void socket_destroy_tcp(socket_impl_t sh); +void socket_tcp_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation); +void socket_tcp_l3_deleted(socket_impl_t sh, l3_id_t l3_id); +const SocketOptions* socket_tcp_options(socket_impl_t sh); +int32_t tcp_accept_enqueue(ksocket_t* listener, ip_version_t ipver, l3_id_t l3_id, const void* src_ip_addr, const void* dst_ip_addr, uint16_t src_port, uint16_t dst_port); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/csocket_udp.c b/kernel/networking/transport_layer/csocket_udp.c new file mode 100644 index 00000000..e07e58d4 --- /dev/null +++ b/kernel/networking/transport_layer/csocket_udp.c @@ -0,0 +1,1105 @@ +#include "csocket_udp.h" +#include "exceptions/irq.h" +#include "networking/transport_layer/socket_bind.h" +#include "networking/transport_layer/udp.h" +#include "networking/internet_layer/ipv4_route.h" +#include "networking/internet_layer/ipv6_route.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/internet_layer/ipv6.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/network.h" +#include "networking/firewall.h" +#include "networking/interface_manager.h" +#include "networking/net_logger/net_logger.h" +#include "syscalls/syscalls.h" +#include "std/memory.h" +#include "alloc/allocate.h" +#define UDP_DEFAULT_RING_CAP 64 +#define UDP_REPLY_WINDOW_MS 60000 +#define UDP_RECENT_TX_COUNT 8 +#define UDP_MAX_RING_CAP 1024 + +typedef struct udp_rx_entry { + netpkt_t* pkt; + net_l4_endpoint src; + SockBindSpec rx_spec; +} udp_rx_entry_t; + +typedef struct udp_recent_tx { + net_l4_endpoint remote; + uint32_t sent_at_ms; + bool match_any_source; +} udp_recent_tx_t; + +typedef struct udp_socket { + uint16_t localPort; + net_l4_endpoint remoteEP; + bool connected; + udp_recent_tx_t recent_tx[UDP_RECENT_TX_COUNT]; + uint8_t recent_tx_next; + ksocket_t* ownerSocket; + SocketOptions options; + SockBindSpec bindSpec; + SockBindSpec lastRxSpec; + socket_bind_token_t bindToken; + uint32_t bind_generation; + udp_rx_entry_t* rx_ring; + uint32_t* mcast_ifmasks; + uint32_t ring_cap; + uint32_t r_head; + uint32_t r_tail; + uint32_t rx_bytes; + bool closed; +} udp_socket_t; + +static void udp_record_recent_tx(udp_socket_t* s, const net_l4_endpoint* remote, bool match_any_source) { + if (!s || !remote) return; + uint32_t now_ms = (uint32_t)get_time(); + irq_flags_t irq = irq_save_disable(); + uint8_t index = s->recent_tx_next; + if (++s->recent_tx_next == UDP_RECENT_TX_COUNT) s->recent_tx_next = 0; + s->recent_tx[index] = (udp_recent_tx_t) {.remote = *remote, .sent_at_ms = now_ms, .match_any_source = match_any_source}; + irq_restore(irq); +} + +static bool udp_socket_mcast_endpoint_valid(const net_l4_endpoint* ep) { + if (!ep) return false; + if (ep->ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, ep->ip, 4); + return ipv4_is_multicast(ip); + } + if (ep->ver == IP_VER6) return ipv6_is_multicast(ep->ip); + return false; +} + +static bool udp_socket_mcast_match(udp_socket_t* s, ip_version_t ver, const void* dst_ip_addr, uint8_t ifindex) { + if (!s || !dst_ip_addr || !s->options.mcast_groups || !s->options.mcast_count) return false; + + for (uint32_t i = 0; i < s->options.mcast_count; i++) { + if (!ifindex || !s->mcast_ifmasks || !(s->mcast_ifmasks[i] & (1u << (ifindex - 1)))) continue; + const net_l4_endpoint* group = &s->options.mcast_groups[i]; + if (group->ver != ver) continue; + + if (ver == IP_VER4) { + uint32_t want = 0; + uint32_t got = 0; + memcpy(&want, group->ip, 4); + memcpy(&got, dst_ip_addr, 4); + if (want == got) return true; + } else if (ver == IP_VER6 && ipv6_cmp(group->ip, dst_ip_addr) == 0) return true; + } + + return false; +} + +static int32_t udp_socket_apply_mcast_group(const SockBindSpec* spec, const net_l4_endpoint* group, bool joining, uint32_t* ifmask) { + if (!spec || !group || !ifmask) return SOCK_ERR_INVAL; + if (joining && *ifmask) return SOCK_ERR_STATE; + + uint32_t v4_group = 0; + if (group->ver == IP_VER4) { + memcpy(&v4_group, group->ip, 4); + if (!ipv4_is_multicast(v4_group)) return SOCK_ERR_INVAL; + } else if (group->ver == IP_VER6) { + if (!ipv6_is_multicast(group->ip)) return SOCK_ERR_INVAL; + } else return SOCK_ERR_INVAL; + + if (!joining) { + for (uint8_t ifindex = 1; ifindex <= MAX_L2_INTERFACES; ifindex++) { + uint32_t bit = 1u << (ifindex - 1); + if (!(*ifmask & bit)) continue; + if (group->ver == IP_VER4) l2_ipv4_mcast_leave(ifindex, v4_group); + else l2_ipv6_mcast_leave(ifindex, group->ip); + } + *ifmask = 0; + return SOCK_OK; + } + + SockBindSpec normal = *spec; + if (!socket_bind_prepare_spec(&normal, PROTO_UDP)) return SOCK_ERR_INVAL; + + bool linkscope = group->ver == IP_VER6 && ipv6_is_linkscope_mcast(group->ip); + uint32_t targets = 0; + *ifmask = 0; + + uint8_t l2_count = l2_interface_count(); + for (uint8_t i = 0; i < l2_count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2 || !l2->ifindex || l2->ifindex > MAX_L2_INTERFACES) continue; + + bool matches = false; + if (group->ver == IP_VER4) { + for (uint8_t slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[slot]; + if (!ipv4_l3_is_active(v4)) continue; + if (!socket_bind_match_score(&normal, IP_VER4, v4->l3_id, l2->ifindex, &v4->ip)) continue; + matches = true; + break; + } + } else { + for (uint8_t slot = 0; slot < MAX_IPV6_PER_INTERFACE; slot++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[slot]; + if (!ipv6_l3_is_ready(v6)) continue; + if (linkscope && !ipv6_is_linklocal(v6->ip)) continue; + if (!socket_bind_match_score(&normal, IP_VER6, v6->l3_id, l2->ifindex, v6->ip)) continue; + matches = true; + break; + } + } + + if (matches) targets |= 1u << (l2->ifindex - 1); + } + + if (!targets) return SOCK_ERR_SYS; + for (uint8_t ifindex = 1; ifindex <= MAX_L2_INTERFACES; ifindex++) { + uint32_t bit = 1u << (ifindex - 1); + if (!(targets & bit)) continue; + + bool ok = group->ver == IP_VER4 ? l2_ipv4_mcast_join(ifindex, v4_group) : l2_ipv6_mcast_join(ifindex, group->ip); + if (ok) { + *ifmask |= bit; + continue; + } + + for (uint8_t joined_ifindex = 1; joined_ifindex <= MAX_L2_INTERFACES; joined_ifindex++) { + uint32_t joined_bit = 1u << (joined_ifindex - 1); + if (!(*ifmask & joined_bit)) continue; + if (group->ver == IP_VER4) l2_ipv4_mcast_leave(joined_ifindex, v4_group); + else l2_ipv6_mcast_leave(joined_ifindex, group->ip); + } + *ifmask = 0; + return SOCK_ERR_SYS; + } + + return SOCK_OK; +} + +static int32_t udp_socket_join_mcast_groups(udp_socket_t* s, const SockBindSpec* spec) { + if (!s || !spec || !s->options.mcast_groups || !s->options.mcast_count) return SOCK_OK; + if (!s->mcast_ifmasks) return SOCK_ERR_SYS; + + for (uint32_t i = 0; i < s->options.mcast_count; i++) { + int32_t rc = udp_socket_apply_mcast_group(spec, &s->options.mcast_groups[i], true, &s->mcast_ifmasks[i]); + if (rc == SOCK_OK) continue; + + while (i > 0) { + --i; + (void)udp_socket_apply_mcast_group(spec, &s->options.mcast_groups[i], false, &s->mcast_ifmasks[i]); + } + return rc; + } + + return SOCK_OK; +} + +static int32_t udp_socket_bind_l3(udp_socket_t* s, l3_id_t l3_id) { + if (!s || !s->ownerSocket || !l3_id) return SOCK_ERR_INVAL; + if (s->closed) return SOCK_ERR_STATE; + if (s->localPort) return SOCK_OK; + + SockBindSpec spec = {0}; + socket_bind_token_t token = 0; + int32_t port = socket_bind_alloc_ephemeral_l3(s->ownerSocket, PROTO_UDP, l3_id, s->options.flags & (SOCK_OPT_REUSEADDR | SOCK_OPT_REUSEPORT), &spec, &token); + if (port < 0) return SOCK_ERR_NO_PORT; + + int32_t rc = udp_socket_join_mcast_groups(s, &spec); + if (rc != SOCK_OK) { + socket_bind_remove(token); + return rc; + } + + if (s->connected) socket_bind_udp_set_remote(token, &s->remoteEP); + + irq_flags_t irq = irq_save_disable(); + if (s->closed) { + irq_restore(irq); + socket_bind_remove(token); + return SOCK_ERR_STATE; + } + s->localPort = (uint16_t)port; + s->bindToken = token; + s->bindSpec = spec; + l2_interface_t *bound = spec.kind == BIND_L2 ? l2_interface_find_by_index(spec.ifindex) : NULL; + s->bind_generation = bound ? bound->generation : 0; + irq_restore(irq); + return SOCK_OK; +} + +uint32_t socket_udp_input(ksocket_t* socket, ip_version_t ipver, l3_id_t l3_id, const void* src_ip_addr, const void* dst_ip_addr, netpkt_t* pkt, uint16_t src_port, uint16_t dst_port) { + if (!socket || !pkt || !src_ip_addr || !dst_ip_addr) return 0; + if (ipver != IP_VER4 && ipver != IP_VER6) return 0; + + udp_socket_t* s = (udp_socket_t*)socket_core_impl(socket); + if (!s) return 0; + + uint32_t pkt_len = netpkt_len(pkt); + uint32_t limit = UINT32_MAX; + if ((s->options.flags & SOCK_OPT_BUF_SIZE) && s->options.buf_size) limit = s->options.buf_size; + if (pkt_len > limit) return 0; + + bool multicast = false; + if (ipver == IP_VER4) { + uint32_t dip = 0; + memcpy(&dip, dst_ip_addr, 4); + multicast = ipv4_is_multicast(dip); + } else multicast = ipv6_is_multicast(dst_ip_addr); + + uint8_t rx_ifindex = 0; + if (multicast) { + if (ipver == IP_VER4) { + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(l3_id); + if (v4 && v4->l2) rx_ifindex = v4->l2->ifindex; + } else { + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(l3_id); + if (v6 && v6->l2) rx_ifindex = v6->l2->ifindex; + } + } + + udp_rx_entry_t entry = {0}; + entry.pkt = pkt; + make_ep(src_ip_addr, src_port, ipver, &entry.src); + entry.rx_spec.kind = BIND_L3; + entry.rx_spec.ver = ipver; + entry.rx_spec.l3_id = l3_id; + + irq_flags_t irq = irq_save_disable(); + if (s->closed || s->localPort != dst_port) { + irq_restore(irq); + return 0; + } + bool joined_multicast = multicast && udp_socket_mcast_match(s, ipver, dst_ip_addr, rx_ifindex); + if (multicast && !joined_multicast) { + irq_restore(irq); + return 0; + } + if (s->connected && !net_ep_equal(&s->remoteEP, &entry.src)) { + irq_restore(irq); + return 0; + } + bool recent_tx_match = false; + uint32_t recent_tx_time_ms = 0; + uint8_t index = s->recent_tx_next; + for (uint32_t i = 0; i < UDP_RECENT_TX_COUNT; i++) { + if (!index) index = UDP_RECENT_TX_COUNT; + udp_recent_tx_t* tx = &s->recent_tx[--index]; + if (!tx->remote.port || tx->remote.ver != ipver || tx->remote.port != src_port) continue; + if (tx->match_any_source || net_ep_equal(&tx->remote, &entry.src)) { + recent_tx_match = true; + recent_tx_time_ms = tx->sent_at_ms; + break; + } + } + irq_restore(irq); + + bool related = recent_tx_match && (uint32_t)get_time() - recent_tx_time_ms <= UDP_REPLY_WINDOW_MS; + if (!firewall_allows(PROTO_UDP, NET_CTRL_FIREWALL_IN, &entry.src, dst_port, related)) return 0; + + if (!s->rx_ring || !s->ring_cap) { + uint32_t usable = UDP_DEFAULT_RING_CAP; + if ((s->options.flags & SOCK_OPT_BUF_SIZE) && s->options.buf_size) { + usable = s->options.buf_size / MAX_PACKET_SIZE; + if (usable < 4) usable = 4; + if (usable > UDP_MAX_RING_CAP) usable = UDP_MAX_RING_CAP; + } + + udp_rx_entry_t* ring = (udp_rx_entry_t*)zalloc(sizeof(udp_rx_entry_t) * (usable+1)); + if (!ring) return 0; + irq_flags_t irq = irq_save_disable(); + if (!s->closed && !s->rx_ring) { + s->rx_ring = ring; + s->ring_cap = usable + 1; + ring = NULL; + } + irq_restore(irq); + if (ring) release(ring); + } + + irq = irq_save_disable(); + if (s->closed || s->localPort != dst_port || !s->rx_ring || !s->ring_cap) { + irq_restore(irq); + return 0; + } + + if (multicast && !udp_socket_mcast_match(s, ipver, dst_ip_addr, rx_ifindex)) { + irq_restore(irq); + return 0; + } + + if (s->connected && !net_ep_equal(&s->remoteEP, &entry.src)) { + irq_restore(irq); + return 0; + } + + if (s->rx_bytes > limit - pkt_len) { + irq_restore(irq); + return 0; + } + + uint32_t nexti = (s->r_tail + 1) % s->ring_cap; + if (nexti == s->r_head) { + irq_restore(irq); + return 0; + } + + netpkt_ref(pkt); + s->rx_ring[s->r_tail] = entry; + s->rx_bytes += pkt_len; + s->r_tail = nexti; + irq_restore(irq); + return pkt_len; +} + +socket_impl_t udp_socket_create(ksocket_t* owner, const SocketOptions* extra) { + if (!owner) return NULL; + + uint32_t supported = SOCK_OPT_RECV_TIMEOUT | SOCK_OPT_BUF_SIZE | SOCK_OPT_DEBUG | SOCK_OPT_DONTFRAG | SOCK_OPT_BROADCAST_ALLOWED | SOCK_OPT_TTL | SOCK_OPT_MCAST_JOIN | SOCK_OPT_NONBLOCK | SOCK_OPT_DONTROUTE | SOCK_OPT_REUSEADDR | SOCK_OPT_REUSEPORT; + if (extra) { + if (extra->flags & ~supported) return NULL; + if ((extra->flags & SOCK_OPT_DEBUG) && extra->debug_level > SOCK_DBG_ALL) return NULL; + if ((extra->flags & SOCK_OPT_BUF_SIZE) && !extra->buf_size) return NULL; + } + + udp_socket_t* s = (udp_socket_t*)zalloc(sizeof(*s)); + if (!s) return NULL; + s->ownerSocket = owner; + s->remoteEP.ver = IP_VER4; + s->bindSpec.kind = BIND_ANY; + s->lastRxSpec.kind = BIND_ANY; + if (extra) s->options = *extra; + s->options.flags &= ~(SOCK_OPT_MCAST_JOIN | SOCK_OPT_MCAST_LEAVE); + s->options.mcast_count = 0; + s->options.mcast_groups = NULL; + + if (extra && extra->mcast_count) { + if (!extra->mcast_groups || socket_setopt_udp(s, SOCK_OPT_MCAST_JOIN, extra->mcast_groups, sizeof(net_l4_endpoint) * extra->mcast_count) != SOCK_OK) { + socket_destroy_udp(s); + return NULL; + } + } + + return s; +} + +int32_t socket_bind_udp(socket_impl_t sh, const SockBindSpec* spec_in, uint16_t port) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s || !spec_in) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_UDP; + ev.action = NETLOG_ACT_BIND; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u0 = port; + ev.bind_spec = *spec_in; + netlog_socket_event(&s->options, &ev); + + if (s->closed) return SOCK_ERR_STATE; + if (s->localPort) return SOCK_ERR_BOUND; + if (!s->ownerSocket) return SOCK_ERR_SYS; + + SockBindSpec spec = *spec_in; + if (!socket_bind_prepare_spec(&spec, PROTO_UDP)) return SOCK_ERR_INVAL; + + int32_t bind_port = port; + socket_bind_token_t token = 0; + if (bind_port == 0) { + bind_port = socket_bind_alloc_ephemeral(s->ownerSocket, PROTO_UDP, &spec, s->options.flags & (SOCK_OPT_REUSEADDR | SOCK_OPT_REUSEPORT), &token); + if (bind_port < 0) return SOCK_ERR_NO_PORT; + } else if (!socket_bind_insert(s->ownerSocket, PROTO_UDP, &spec, port, s->options.flags & (SOCK_OPT_REUSEADDR | SOCK_OPT_REUSEPORT), true, &token)) return SOCK_ERR_BOUND; + + int32_t rc = udp_socket_join_mcast_groups(s, &spec); + if (rc != SOCK_OK) { + socket_bind_remove(token); + return rc; + } + + if (s->connected) socket_bind_udp_set_remote(token, &s->remoteEP); + + irq_flags_t irq = irq_save_disable(); + if (s->closed) { + irq_restore(irq); + socket_bind_remove(token); + return SOCK_ERR_STATE; + } + s->bindSpec = spec; + s->bindToken = token; + l2_interface_t *bound = spec.kind == BIND_L2 ? l2_interface_find_by_index(spec.ifindex) : 0; + s->bind_generation = bound ? bound->generation : 0; + s->localPort = (uint16_t)bind_port; + irq_restore(irq); + return SOCK_OK; +} + +int32_t socket_connect_udp(socket_impl_t sh, const net_l4_endpoint* dst) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_UDP; + ev.action = NETLOG_ACT_CONNECT; + ev.pid = socket_core_pid(s->ownerSocket); + if (dst) ev.dst_ep = *dst; + netlog_socket_event(&s->options, &ev); + + if (s->closed) return SOCK_ERR_STATE; + if (!dst || !dst->port) return SOCK_ERR_INVAL; + if (dst->ver != IP_VER4 && dst->ver != IP_VER6) return SOCK_ERR_INVAL; + + if (dst->ver == IP_VER4) { + uint32_t dip = 0; + memcpy(&dip, dst->ip, 4); + ipv4_tx_plan_t plan; + if (!socket_bind_build_ipv4_tx_plan(&s->bindSpec, s->localPort != 0, dip, &plan)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv4_tx_plan_onlink(&plan, dip)) return SOCK_ERR_NO_ROUTE; + } else { + ipv6_tx_plan_t plan; + if (!socket_bind_build_ipv6_tx_plan(&s->bindSpec, s->localPort != 0, dst->ip, &plan)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv6_tx_plan_onlink(&plan, dst->ip)) return SOCK_ERR_NO_ROUTE; + } + + if (s->bindToken) socket_bind_udp_set_remote(s->bindToken, dst); + irq_flags_t irq = irq_save_disable(); + if (s->closed) { + irq_restore(irq); + return SOCK_ERR_STATE; + } + s->remoteEP = *dst; + s->connected = true; + irq_restore(irq); + return SOCK_OK; +} + +int64_t socket_sendto_udp(socket_impl_t sh, const net_l4_endpoint* dst, const void* buf, uint64_t len) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s || (!buf && len)) return SOCK_ERR_INVAL; + if (s->closed) return SOCK_ERR_STATE; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_UDP; + ev.action = NETLOG_ACT_SENDTO; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u1 = (uint32_t)len; + if (dst) { + ev.dst_ep = *dst; + ev.u0 = dst->port; + } + netlog_socket_event(&s->options, &ev); + + bool explicit_dst = dst != NULL; + if (!dst) { + if (!s->connected) return SOCK_ERR_STATE; + dst = &s->remoteEP; + } + if (!dst->port) return SOCK_ERR_INVAL; + if (!s->ownerSocket) return SOCK_ERR_SYS; + + net_l4_endpoint d = *dst; + if (d.ver != IP_VER4 && d.ver != IP_VER6) return SOCK_ERR_INVAL; + uint64_t max_payload = d.ver == IP_VER4 ? UINT16_MAX - sizeof(ipv4_hdr_t) - sizeof(udp_hdr_t) : UINT16_MAX - sizeof(udp_hdr_t); + if (len > max_payload) return SOCK_ERR_INVAL; + if (s->connected && explicit_dst && !net_ep_equal(&d, &s->remoteEP)) return SOCK_ERR_STATE; + if (!firewall_allows(PROTO_UDP, NET_CTRL_FIREWALL_OUT, &d, s->localPort, false)) return SOCK_ERR_PERM; + + sizedptr pay; + pay.ptr = (uintptr_t)buf; + pay.size = (uint32_t)len; + uint8_t ttl = (s->options.flags & SOCK_OPT_TTL) ? s->options.ttl : 0; + uint8_t dontfrag = (s->options.flags & SOCK_OPT_DONTFRAG) ? 1 : 0; + + if (d.ver == IP_VER4) { + uint32_t dip = 0; + memcpy(&dip, d.ip, 4); + + SockBindSpec send_spec = s->bindSpec; + if (!socket_bind_prepare_spec(&send_spec, PROTO_UDP)) return SOCK_ERR_INVAL; + + bool limited_bcast = ipv4_is_limited_broadcast(dip); + l3_id_t chosen_l3 = 0; + l3_ipv4_interface_t* bcast_v4 = NULL; + + if (limited_bcast) { + if (!(s->options.flags & SOCK_OPT_BROADCAST_ALLOWED)) return SOCK_ERR_PERM; + + uint8_t l2_count = l2_interface_count(); + for (uint8_t i = 0; i < l2_count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2) continue; + + for (uint8_t slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[slot]; + if (!ipv4_l3_is_active(v4) || v4->is_localhost) continue; + if (!socket_bind_match_score(&send_spec, IP_VER4, v4->l3_id, l2->ifindex, &v4->ip)) continue; + if (!v4->ip && v4->mode != IPV4_CFG_DHCP) continue; + if (bcast_v4) return SOCK_ERR_INVAL; + chosen_l3 = v4->l3_id; + bcast_v4 = v4; + } + } + if (!bcast_v4) return SOCK_ERR_INVAL; + } else { + uint8_t l2_count = l2_interface_count(); + for (uint8_t i = 0; i < l2_count && !bcast_v4; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!l2) continue; + + for (uint8_t slot = 0; slot < MAX_IPV4_PER_INTERFACE; slot++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[slot]; + if (!ipv4_l3_is_ready(v4) || !v4->mask || v4->is_localhost) continue; + if (!socket_bind_match_score(&send_spec, IP_VER4, v4->l3_id, l2->ifindex, &v4->ip)) continue; + if (ipv4_broadcast_calc(v4->ip, v4->mask) != dip) continue; + chosen_l3 = v4->l3_id; + bcast_v4 = v4; + break; + } + } + if (bcast_v4 && !(s->options.flags & SOCK_OPT_BROADCAST_ALLOWED)) return SOCK_ERR_PERM; + } + + if (bcast_v4) { + int32_t bind_rc = udp_socket_bind_l3(s, chosen_l3); + if (bind_rc != SOCK_OK) return bind_rc; + + net_l4_endpoint src; + make_ep(&bcast_v4->ip, s->localPort, IP_VER4, &src); + + ip_tx_opts_t tx; + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = chosen_l3; + + if (!udp_send_segment(&src, &d, pay, &tx, ttl, dontfrag, 0)) return SOCK_ERR_SYS; + udp_record_recent_tx(s, &d, true); + return (int64_t)len; + } + + ipv4_tx_plan_t plan; + if (!socket_bind_build_ipv4_tx_plan(&s->bindSpec, s->localPort != 0, dip, &plan)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv4_tx_plan_onlink(&plan, dip)) return SOCK_ERR_NO_ROUTE; + + l3_id_t tx_l3 = plan.l3_id; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(tx_l3); + if (!ipv4_l3_is_ready(v4)) return SOCK_ERR_NO_ROUTE; + + int32_t bind_rc = udp_socket_bind_l3(s, tx_l3); + if (bind_rc != SOCK_OK) return bind_rc; + + net_l4_endpoint src; + make_ep(&v4->ip, s->localPort, IP_VER4, &src); + + ip_tx_opts_t tx; + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = plan.l3_id; + + if (!udp_send_segment(&src, &d, pay, &tx, ttl, dontfrag, (s->options.flags & SOCK_OPT_DONTROUTE) ? 1 : 0)) return SOCK_ERR_SYS; + udp_record_recent_tx(s, &d, ipv4_is_multicast(dip)); + return (int64_t)len; + } + + if (d.ver == IP_VER6) { + ipv6_tx_plan_t plan; + if (!socket_bind_build_ipv6_tx_plan(&s->bindSpec, s->localPort != 0, d.ip, &plan)) return SOCK_ERR_NO_ROUTE; + if ((s->options.flags & SOCK_OPT_DONTROUTE) && !ipv6_tx_plan_onlink(&plan, d.ip)) return SOCK_ERR_NO_ROUTE; + + l3_id_t chosen_l3 = plan.l3_id; + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen_l3); + if (!ipv6_l3_is_ready(v6)) return SOCK_ERR_NO_ROUTE; + + int32_t bind_rc = udp_socket_bind_l3(s, chosen_l3); + if (bind_rc != SOCK_OK) return bind_rc; + + net_l4_endpoint src; + make_ep(v6->ip, s->localPort, IP_VER6, &src); + + ip_tx_opts_t tx; + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = plan.l3_id; + + if (!udp_send_segment(&src, &d, pay, &tx, ttl, dontfrag, (s->options.flags & SOCK_OPT_DONTROUTE) ? 1 : 0)) return SOCK_ERR_SYS; + udp_record_recent_tx(s, &d, ipv6_is_multicast(d.ip)); + return (int64_t)len; + } + + return SOCK_ERR_INVAL; +} + +int64_t socket_recvfrom_udp(socket_impl_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s || (!buf && len)) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_UDP; + ev.action = NETLOG_ACT_RECVFROM; + ev.pid = socket_core_pid(s->ownerSocket); + ev.u0 = (uint32_t)len; + ev.local_port = s->localPort; + ev.remote_ep = s->remoteEP; + netlog_socket_event(&s->options, &ev); + + uint32_t start_ms = 0; + netpkt_t* p = NULL; + net_l4_endpoint se = {0}; + + while (1){ + irq_flags_t irq = irq_save_disable(); + if (s->rx_ring && s->r_head != s->r_tail) { + p = s->rx_ring[s->r_head].pkt; + se = s->rx_ring[s->r_head].src; + s->lastRxSpec = s->rx_ring[s->r_head].rx_spec; + memset(&s->rx_ring[s->r_head], 0, sizeof(s->rx_ring[s->r_head])); + s->r_head = (s->r_head + 1) % s->ring_cap; + + uint32_t pkt_len = p ? netpkt_len(p) : 0; + if (s->rx_bytes >= pkt_len) s->rx_bytes -= pkt_len; + else s->rx_bytes = 0; + irq_restore(irq); + break; + } + + bool closed = s->closed; + uint32_t flags = s->options.flags; + uint32_t timeout_ms = s->options.recv_timeout_ms; + irq_restore(irq); + + if (closed) return 0; + if (flags & SOCK_OPT_NONBLOCK) return SOCK_ERR_WOULDBLOCK; + if ((flags & SOCK_OPT_RECV_TIMEOUT) && timeout_ms) { + uint32_t now_ms = (uint32_t)get_time(); + if (!start_ms) start_ms = now_ms; + uint32_t elapsed_ms = now_ms - start_ms; + if (elapsed_ms >= timeout_ms) return SOCK_ERR_WOULDBLOCK; + + uint32_t wait_ms = timeout_ms - elapsed_ms; + if (wait_ms > 5) wait_ms = 5; + msleep(wait_ms); + }else msleep(5); + } + + uint32_t pkt_len = p ? netpkt_len(p) : 0; + uint32_t tocpy = pkt_len; + if (tocpy > len) tocpy = (uint32_t)len; + + if (tocpy && !netpkt_copyout(p, 0, buf, tocpy)) tocpy = 0; + if (out_src) *out_src = se; + + if (p) netpkt_unref(p); + return tocpy; +} + +int32_t socket_setopt_udp(socket_impl_t sh, int32_t opt, const void* value, uint32_t len) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + if (s->closed) return SOCK_ERR_STATE; + + switch ((uint32_t)opt) { + case SOCK_OPT_KEEPALIVE: + case SOCK_OPT_KEEPALIVE_INTERVAL: + case SOCK_OPT_TCP_NO_DELAY: + case SOCK_OPT_SEND_BUF_SIZE: + case SOCK_OPT_TCP_MAXSEG: + case SOCK_OPT_TCP_SACK: + case SOCK_OPT_TCP_DSACK: + case SOCK_OPT_LINGER: + case SOCK_OPT_FILTER: + case SOCK_OPT_SPECIAL: + return SOCK_ERR_UNSUP; + case SOCK_OPT_REUSEADDR: + case SOCK_OPT_REUSEPORT: + if (s->localPort) return SOCK_ERR_STATE; + break; + case SOCK_OPT_NONBLOCK: + case SOCK_OPT_DONTROUTE: + break; + case SOCK_OPT_MCAST_JOIN: { + if (!value || !len || (len % sizeof(net_l4_endpoint)) != 0) return SOCK_ERR_INVAL; + uint32_t count = len / sizeof(net_l4_endpoint); + if (!count || count > 255) return SOCK_ERR_INVAL; + + const net_l4_endpoint* groups = value; + for (uint32_t i = 0; i < count; i++) if (!udp_socket_mcast_endpoint_valid(&groups[i])) return SOCK_ERR_INVAL; + + uint8_t old_count = s->options.mcast_count; + if (old_count && (!s->options.mcast_groups || !s->mcast_ifmasks)) return SOCK_ERR_SYS; + uint32_t add_count = 0; + for (uint32_t i = 0; i < count; i++) { + bool exists = false; + for (uint8_t j = 0; j < old_count; j++) { + if (s->options.mcast_groups[j].ver != groups[i].ver) continue; + uint32_t ip_len = groups[i].ver == IP_VER4 ? 4 : 16; + if (memcmp(s->options.mcast_groups[j].ip, groups[i].ip, ip_len) == 0) { + exists = true; + break; + } + } + if (exists) continue; + for (uint32_t j = 0; j < i; j++) { + if (groups[j].ver != groups[i].ver) continue; + uint32_t ip_len = groups[i].ver == IP_VER4 ? 4 : 16; + if (memcmp(groups[j].ip, groups[i].ip, ip_len) == 0) { + exists = true; + break; + } + } + if (!exists) add_count++; + } + if (!add_count) return SOCK_OK; + if ((uint32_t)old_count + add_count > 255) return SOCK_ERR_INVAL; + + uint32_t capacity = (uint32_t)old_count + add_count; + net_l4_endpoint* next_groups = (net_l4_endpoint*)zalloc(sizeof(net_l4_endpoint) * capacity); + uint32_t* next_ifmasks = (uint32_t*)zalloc(sizeof(uint32_t) * capacity); + if (!next_groups || !next_ifmasks) { + if (next_groups) release(next_groups); + if (next_ifmasks) release(next_ifmasks); + return SOCK_ERR_SYS; + } + + if (old_count) { + memcpy(next_groups, s->options.mcast_groups, sizeof(net_l4_endpoint) * old_count); + memcpy(next_ifmasks, s->mcast_ifmasks, sizeof(uint32_t) * old_count); + } + + uint8_t next_count = old_count; + for (uint32_t i = 0; i < count; i++) { + bool exists = false; + for (uint8_t j = 0; j < next_count; j++) { + if (next_groups[j].ver != groups[i].ver) continue; + uint32_t ip_len = groups[i].ver == IP_VER4 ? 4 : 16; + if (memcmp(next_groups[j].ip, groups[i].ip, ip_len) == 0) { + exists = true; + break; + } + } + if (exists) continue; + + next_groups[next_count] = groups[i]; + next_groups[next_count].port = 0; + if (s->localPort) { + int32_t rc = udp_socket_apply_mcast_group(&s->bindSpec, &next_groups[next_count], true, &next_ifmasks[next_count]); + if (rc != SOCK_OK) { + while (next_count > old_count) { + next_count--; + (void)udp_socket_apply_mcast_group(&s->bindSpec, &next_groups[next_count], false, &next_ifmasks[next_count]); + } + release(next_groups); + release(next_ifmasks); + return rc; + } + } + next_count++; + } + + if (next_count == old_count) { + release(next_groups); + release(next_ifmasks); + return SOCK_OK; + } + + irq_flags_t irq = irq_save_disable(); + net_l4_endpoint* old_groups = (net_l4_endpoint*)s->options.mcast_groups; + uint32_t* old_ifmasks = s->mcast_ifmasks; + s->options.mcast_groups = next_groups; + s->mcast_ifmasks = next_ifmasks; + s->options.mcast_count = next_count; + s->options.flags |= SOCK_OPT_MCAST_JOIN; + irq_restore(irq); + + if (old_groups) release(old_groups); + if (old_ifmasks) release(old_ifmasks); + return SOCK_OK; + } + case SOCK_OPT_MCAST_LEAVE: { + if (!value || !len || (len % sizeof(net_l4_endpoint)) != 0) return SOCK_ERR_INVAL; + uint32_t count = len / sizeof(net_l4_endpoint); + if (!count || count > 255) return SOCK_ERR_INVAL; + if (!s->options.mcast_groups || !s->options.mcast_count) return SOCK_OK; + + const net_l4_endpoint* groups = value; + for (uint32_t i = 0; i < count; i++) if (!udp_socket_mcast_endpoint_valid(&groups[i])) return SOCK_ERR_INVAL; + + uint8_t old_count = s->options.mcast_count; + if (!s->mcast_ifmasks) return SOCK_ERR_SYS; + uint8_t remove_count = 0; + for (uint8_t i = 0; i < old_count; i++) { + for (uint32_t j = 0; j < count; j++) { + if (s->options.mcast_groups[i].ver != groups[j].ver) continue; + uint32_t ip_len = groups[j].ver == IP_VER4 ? 4 : 16; + if (memcmp(s->options.mcast_groups[i].ip, groups[j].ip, ip_len) == 0) { + remove_count++; + break; + } + } + } + if (!remove_count) return SOCK_OK; + + uint8_t capacity = (uint8_t)(old_count - remove_count); + net_l4_endpoint* next_groups = capacity ? (net_l4_endpoint*)zalloc(sizeof(net_l4_endpoint) * capacity) : NULL; + uint32_t* next_ifmasks = capacity ? (uint32_t*)zalloc(sizeof(uint32_t) * capacity) : NULL; + if (capacity && (!next_groups || !next_ifmasks)) { + if (next_groups) release(next_groups); + if (next_ifmasks) release(next_ifmasks); + return SOCK_ERR_SYS; + } + + uint8_t next_count = 0; + for (uint8_t i = 0; i < old_count; i++) { + bool removed = false; + for (uint32_t j = 0; j < count; j++) { + if (s->options.mcast_groups[i].ver != groups[j].ver) continue; + uint32_t ip_len = groups[j].ver == IP_VER4 ? 4 : 16; + if (memcmp(s->options.mcast_groups[i].ip, groups[j].ip, ip_len) == 0) { + removed = true; + break; + } + } + if (removed) continue; + next_groups[next_count] = s->options.mcast_groups[i]; + next_ifmasks[next_count] = s->mcast_ifmasks[i]; + next_count++; + } + + irq_flags_t irq = irq_save_disable(); + net_l4_endpoint* old_groups = (net_l4_endpoint*)s->options.mcast_groups; + uint32_t* old_ifmasks = s->mcast_ifmasks; + s->options.mcast_groups = next_groups; + s->mcast_ifmasks = next_ifmasks; + s->options.mcast_count = next_count; + if (next_count) s->options.flags |= SOCK_OPT_MCAST_JOIN; + else s->options.flags &= ~SOCK_OPT_MCAST_JOIN; + irq_restore(irq); + + if (s->localPort) { + for (uint8_t i = 0; i < old_count; i++) { + bool kept = false; + for (uint8_t j = 0; j < next_count; j++) { + if (old_groups[i].ver != next_groups[j].ver) continue; + uint32_t ip_len = old_groups[i].ver == IP_VER4 ? 4 : 16; + if (memcmp(old_groups[i].ip, next_groups[j].ip, ip_len) == 0) { + kept = true; + break; + } + } + if (!kept) (void)udp_socket_apply_mcast_group(&s->bindSpec, &old_groups[i], false, &old_ifmasks[i]); + } + } + + release(old_groups); + release(old_ifmasks); + return SOCK_OK; + } + case SOCK_OPT_SEND_TIMEOUT: + case SOCK_OPT_PACKET_TRUNK: + return SOCK_ERR_UNSUP; + case SOCK_OPT_RECV_TIMEOUT: + case SOCK_OPT_DEBUG: + case SOCK_OPT_DONTFRAG: + case SOCK_OPT_BROADCAST_ALLOWED: + case SOCK_OPT_TTL: + break; + case SOCK_OPT_BUF_SIZE: + if (s->localPort) return SOCK_ERR_STATE; + break; + default: + return SOCK_ERR_INVAL; + } + + return socket_common_options_set(&s->options, opt, value, len); +} + +int32_t socket_getopt_udp(socket_impl_t sh, int32_t opt, void* value, uint32_t* len) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s || !len) return SOCK_ERR_INVAL; + + switch ((uint32_t)opt) { + case SOCK_GET_REMOTE_ENDPOINT: { + irq_flags_t irq = irq_save_disable(); + net_l4_endpoint remote = s->remoteEP; + irq_restore(irq); + return socket_common_get_value(&remote, sizeof(remote), value, len); + } + case SOCK_GET_BIND_SPEC: { + irq_flags_t irq = irq_save_disable(); + SockBindSpec spec = s->bindSpec; + irq_restore(irq); + return socket_common_get_value(&spec, sizeof(spec), value, len); + } + case SOCK_GET_LAST_RX_SPEC: { + irq_flags_t irq = irq_save_disable(); + SockBindSpec spec = s->lastRxSpec; + irq_restore(irq); + return socket_common_get_value(&spec, sizeof(spec), value, len); + } + case SOCK_GET_MCAST_GROUPS: { + irq_flags_t irq = irq_save_disable(); + uint32_t need = s->options.mcast_count * sizeof(net_l4_endpoint); + if (!value) { + *len = need; + irq_restore(irq); + return SOCK_OK; + } + if (*len < need) { + irq_restore(irq); + return SOCK_ERR_INVAL; + } + if (need) memcpy(value, s->options.mcast_groups, need); + *len = need; + irq_restore(irq); + return SOCK_OK; + } + case SOCK_GET_OPT_RECV_TIMEOUT: + case SOCK_GET_OPT_BUF_SIZE: + case SOCK_GET_OPT_DEBUG: + case SOCK_GET_OPT_DONTFRAG: + case SOCK_GET_OPT_BROADCAST_ALLOWED: + case SOCK_GET_OPT_TTL: + case SOCK_GET_OPT_NONBLOCK: + case SOCK_GET_OPT_DONTROUTE: + case SOCK_GET_OPT_REUSEADDR: + case SOCK_GET_OPT_REUSEPORT: + return socket_common_options_get(&s->options, opt, value, len); + default: + break; + } + + uint32_t v = 0; + irq_flags_t irq = irq_save_disable(); + switch ((uint32_t)opt) { + case SOCK_GET_BOUND: + v = s->localPort != 0; + break; + case SOCK_GET_CONNECTED: + v = s->connected; + break; + case SOCK_GET_LISTENING: + irq_restore(irq); + return SOCK_ERR_UNSUP; + case SOCK_GET_LOCAL_PORT: + v = s->localPort; + break; + case SOCK_GET_RECV_QUEUED: + v = s->rx_bytes; + break; + case SOCK_GET_SEND_QUEUED: + v = 0; + break; + case SOCK_GET_OPT_KEEPALIVE: + case SOCK_GET_OPT_KEEPALIVE_INTERVAL: + case SOCK_GET_OPT_TCP_NO_DELAY: + case SOCK_GET_OPT_SEND_BUF_SIZE: + case SOCK_GET_OPT_TCP_MAXSEG: + case SOCK_GET_OPT_TCP_SACK: + case SOCK_GET_OPT_TCP_DSACK: + case SOCK_GET_OPT_LINGER: + case SOCK_GET_OPT_FILTER: + case SOCK_GET_OPT_SEND_TIMEOUT: + case SOCK_GET_OPT_PACKET_TRUNK: + case SOCK_GET_TCP_STATE: + case SOCK_GET_TCP_MSS: + case SOCK_GET_TCP_RTT_MS: + case SOCK_GET_TCP_RETRANSMITS: + case SOCK_GET_TCP_URGENT_REMAINING: + irq_restore(irq); + return SOCK_ERR_UNSUP; + default: + irq_restore(irq); + return SOCK_ERR_INVAL; + } + + irq_restore(irq); + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int32_t socket_close_udp(socket_impl_t sh) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s) return SOCK_ERR_INVAL; + + netlog_socket_event_t ev = {0}; + ev.comp = NETLOG_COMP_UDP; + ev.action = NETLOG_ACT_CLOSE; + ev.pid = socket_core_pid(s->ownerSocket); + + irq_flags_t irq = irq_save_disable(); + if (s->closed) { + irq_restore(irq); + return SOCK_OK; + } + + udp_rx_entry_t* rx_ring = s->rx_ring; + uint32_t ring_cap = s->ring_cap; + uint32_t r_head = s->r_head; + uint32_t r_tail = s->r_tail; + net_l4_endpoint* mcast_groups = (net_l4_endpoint*)s->options.mcast_groups; + uint32_t* mcast_ifmasks = s->mcast_ifmasks; + uint8_t mcast_count = s->options.mcast_count; + socket_bind_token_t bind_token = s->bindToken; + SockBindSpec bind_spec = s->bindSpec; + SocketOptions options = s->options; + ev.local_port = s->localPort; + ev.remote_ep = s->remoteEP; + + s->closed = true; + s->rx_ring = NULL; + s->ring_cap = 0; + s->r_head = 0; + s->r_tail = 0; + s->rx_bytes = 0; + s->options.mcast_groups = NULL; + s->mcast_ifmasks = NULL; + s->options.mcast_count = 0; + s->options.flags &= ~(SOCK_OPT_MCAST_JOIN | SOCK_OPT_MCAST_LEAVE); + memset(&s->lastRxSpec, 0, sizeof(s->lastRxSpec)); + s->lastRxSpec.kind = BIND_ANY; + s->bindToken = 0; + s->bind_generation = 0; + s->localPort = 0; + memset(&s->bindSpec, 0, sizeof(s->bindSpec)); + s->bindSpec.kind = BIND_ANY; + s->connected = false; + memset(s->recent_tx, 0, sizeof(s->recent_tx)); + s->recent_tx_next = 0; + memset(&s->remoteEP, 0, sizeof(s->remoteEP)); + s->remoteEP.ver = IP_VER4; + irq_restore(irq); + + if (bind_token) socket_bind_remove(bind_token); + netlog_socket_event(&options, &ev); + + if (ev.local_port && mcast_groups && mcast_ifmasks) { + for (uint32_t i = 0; i < mcast_count; i++) (void)udp_socket_apply_mcast_group(&bind_spec, &mcast_groups[i], false, &mcast_ifmasks[i]); + } + + if (rx_ring && ring_cap) { + while (r_head != r_tail) { + if (rx_ring[r_head].pkt) netpkt_unref(rx_ring[r_head].pkt); + r_head = (r_head + 1) % ring_cap; + } + release(rx_ring); + } + if (mcast_groups) release(mcast_groups); + if (mcast_ifmasks) release(mcast_ifmasks); + return SOCK_OK; +} + +void socket_destroy_udp(socket_impl_t sh) { + udp_socket_t* s = (udp_socket_t*)sh; + if (!s) return; + socket_close_udp(s); + release(s); +} + +void socket_udp_l3_deleted(socket_impl_t sh, l3_id_t l3_id) { + udp_socket_t *s = (udp_socket_t*)sh; + if (s && (s->bindSpec.kind == BIND_L3 || s->bindSpec.kind == BIND_IP) && s->bindSpec.l3_id == l3_id) (void)socket_close_udp(sh); +} + +void socket_udp_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation) { + udp_socket_t *s = (udp_socket_t*)sh; + if (!s || !ifindex || ifindex > MAX_L2_INTERFACES) return; + irq_flags_t irq = irq_save_disable(); + bool bound = s->bindSpec.kind == BIND_L2 && s->bindSpec.ifindex == ifindex && s->bind_generation == generation; + if (!bound && s->mcast_ifmasks) { + uint32_t bit = 1u << (ifindex - 1); + for (uint32_t i = 0; i < s->options.mcast_count; i++) s->mcast_ifmasks[i] &= ~bit; + } + irq_restore(irq); + if (bound) socket_close_udp(sh); +} diff --git a/kernel/networking/transport_layer/csocket_udp.cpp b/kernel/networking/transport_layer/csocket_udp.cpp deleted file mode 100644 index 3dc04248..00000000 --- a/kernel/networking/transport_layer/csocket_udp.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "networking/transport_layer/socket_udp.hpp" -#include "networking/transport_layer/socket.hpp" -#include "csocket_udp.h" - -extern "C" socket_handle_t udp_socket_create(uint8_t role, uint32_t pid, const SocketExtraOptions* extra) { - return reinterpret_cast(new UDPSocket(role, pid, extra)); -} - -extern "C" int32_t socket_bind_udp_ex(socket_handle_t sh, const SockBindSpec* spec, uint16_t port) { - if (!sh || !spec) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->bind(*spec, port); -} - -extern "C" int64_t socket_sendto_udp_ex(socket_handle_t sh, uint8_t dst_kind, const void* dst, uint16_t port, const void* buf, uint64_t len) { - if (!sh || !dst || !buf || !len) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->sendto(static_cast(dst_kind), dst, port, buf, len); -} - -extern "C" int64_t socket_recvfrom_udp_ex(socket_handle_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src) { - if (!sh || !buf || !len) return 0; - return reinterpret_cast(sh)->recvfrom(buf, len, out_src); -} - -extern "C" int32_t socket_close_udp(socket_handle_t sh) { - if (!sh) return SOCK_ERR_INVAL; - return reinterpret_cast(sh)->close(); -} - -extern "C" void socket_destroy_udp(socket_handle_t sh) { - if (!sh) return; - delete reinterpret_cast(sh); -} - -extern "C" uint16_t socket_get_local_port_udp(socket_handle_t sh) { - if (!sh) return 0; - return reinterpret_cast(sh)->get_local_port(); -} - -extern "C" uint16_t socket_get_remote_port_udp(socket_handle_t sh) { - if (!sh) return 0; - return reinterpret_cast(sh)->get_remote_port(); -} - -extern "C" void socket_get_remote_ep_udp(socket_handle_t sh, net_l4_endpoint* out) { - if (!sh || !out) return; - *out = reinterpret_cast(sh)->get_remote_ep(); -} - -extern "C" uint8_t socket_get_protocol_udp(socket_handle_t sh) { - if (!sh) return 0xFF; - return reinterpret_cast(sh)->get_protocol(); -} - -extern "C" uint8_t socket_get_role_udp(socket_handle_t sh) { - if (!sh) return 0xFF; - return reinterpret_cast(sh)->get_role(); -} - -extern "C" bool socket_is_bound_udp(socket_handle_t sh) { - if (!sh) return false; - return reinterpret_cast(sh)->is_bound(); -} - -extern "C" bool socket_is_connected_udp(socket_handle_t sh) { - if (!sh) return false; - return reinterpret_cast(sh)->is_connected(); -} diff --git a/kernel/networking/transport_layer/csocket_udp.h b/kernel/networking/transport_layer/csocket_udp.h index 479fefcf..8f9a14b6 100644 --- a/kernel/networking/transport_layer/csocket_udp.h +++ b/kernel/networking/transport_layer/csocket_udp.h @@ -1,30 +1,26 @@ #pragma once #include "types.h" #include "net/network_types.h" -#include "networking/transport_layer/socket.hpp" +#include "socket_core.h" #include "net/socket_types.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { #endif -typedef void* socket_handle_t; - -socket_handle_t udp_socket_create(uint8_t role, uint32_t pid, const SocketExtraOptions* extra); -int32_t socket_bind_udp_ex(socket_handle_t sh, const SockBindSpec* spec, uint16_t port); -int64_t socket_sendto_udp_ex(socket_handle_t sh, uint8_t dst_kind, const void* dst, uint16_t port, const void* buf, uint64_t len); -int64_t socket_recvfrom_udp_ex(socket_handle_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src); -int32_t socket_close_udp(socket_handle_t sh); -void socket_destroy_udp(socket_handle_t sh); - -uint16_t socket_get_local_port_udp(socket_handle_t sh); -uint16_t socket_get_remote_port_udp(socket_handle_t sh); -void socket_get_remote_ep_udp(socket_handle_t sh, net_l4_endpoint* out); - -uint8_t socket_get_protocol_udp(socket_handle_t sh); -uint8_t socket_get_role_udp(socket_handle_t sh); -bool socket_is_bound_udp(socket_handle_t sh); -bool socket_is_connected_udp(socket_handle_t sh); +socket_impl_t udp_socket_create(ksocket_t* owner, const SocketOptions* extra); +int32_t socket_bind_udp(socket_impl_t sh, const SockBindSpec* spec, uint16_t port); +int32_t socket_connect_udp(socket_impl_t sh, const net_l4_endpoint* dst); +int64_t socket_sendto_udp(socket_impl_t sh, const net_l4_endpoint* dst, const void* buf, uint64_t len); +int64_t socket_recvfrom_udp(socket_impl_t sh, void* buf, uint64_t len, net_l4_endpoint* out_src); +int32_t socket_close_udp(socket_impl_t sh); +void socket_destroy_udp(socket_impl_t sh); +void socket_udp_l2_deleted(socket_impl_t sh, uint8_t ifindex, uint32_t generation); +void socket_udp_l3_deleted(socket_impl_t sh, l3_id_t l3_id); +int32_t socket_setopt_udp(socket_impl_t sh, int32_t opt, const void* value, uint32_t len); +int32_t socket_getopt_udp(socket_impl_t sh, int32_t opt, void* value, uint32_t* len); +uint32_t socket_udp_input(ksocket_t* socket, ip_version_t ipver, l3_id_t l3_id, const void* src_ip_addr, const void* dst_ip_addr, netpkt_t* pkt, uint16_t src_port, uint16_t dst_port); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/net_ctrl.c b/kernel/networking/transport_layer/net_ctrl.c new file mode 100644 index 00000000..4662e14a --- /dev/null +++ b/kernel/networking/transport_layer/net_ctrl.c @@ -0,0 +1,894 @@ +#include "net_ctrl.h" +#include "files/buffer.h" +#include "net/socket_types.h" +#include "std/memory.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4_route.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_route.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/link_layer/arp.h" +#include "networking/link_layer/link_utils.h" +#include "networking/link_layer/ndp.h" +#include "networking/network.h" +#include "networking/firewall.h" +#include "networking/link_layer/nic_types.h" +#include "alloc/allocate.h" + +typedef struct { + uint8_t ifindex; + l3_id_t l3_id; + uint8_t prefix_len; + uint8_t ifname_len; + char ifname[16]; + char parent_name[16]; + uint8_t parent_name_len; + uint16_t vlan_id; + uint8_t mac[MAC_ADDR_LEN]; + uint16_t metric; + uint16_t mtu; + uint16_t flags; + int16_t config; + uint8_t state; + uint8_t kind; + uint8_t dad_state; + uint32_t ttl_ms; + uint32_t rule_id; + uint16_t port_from; + uint16_t port_to; + uint8_t action; + uint8_t direction; + uint8_t protocol; + uint32_t present; + net_l4_endpoint address; + net_l4_endpoint gateway; +} net_ctrl_attrs_t; + +static bool net_ctrl_read_attrs(const uint8_t* p, uint32_t len, net_ctrl_attrs_t* out) { + if (!out) return false; + memset(out, 0, sizeof(*out)); + uint32_t off = 0; + while (off < len) { + if (len - off < sizeof(NetCtrlAttr)) return false; + const NetCtrlAttr* attr = (const NetCtrlAttr*)(p + off); + NetCtrlAttr a; + memcpy(&a, attr, sizeof(a)); + off += sizeof(a); + if (a.length > len - off) return false; + const uint8_t* v = NET_CTRL_ATTR_CONST_DATA(attr); + switch ((uint32_t)a.ext) { + case NET_CTRL_EXT_IFINDEX: + if (a.length != sizeof(uint8_t)) return false; + out->ifindex = v[0]; + out->present |= 1u << NET_CTRL_EXT_IFINDEX; + break; + case NET_CTRL_EXT_IFNAME: + if (!a.length || a.length > sizeof(out->ifname)) return false; + memcpy(out->ifname, v, a.length); + out->ifname_len = (uint8_t)a.length; + out->present |= 1u << NET_CTRL_EXT_IFNAME; + break; + case NET_CTRL_EXT_L3_ID: + if (a.length != sizeof(l3_id_t)) return false; + memcpy(&out->l3_id, v, sizeof(out->l3_id)); + out->present |= 1u << NET_CTRL_EXT_L3_ID; + break; + case NET_CTRL_EXT_PREFIX_LEN: + if (a.length != sizeof(uint8_t)) return false; + out->prefix_len = v[0]; + out->present |= 1u << NET_CTRL_EXT_PREFIX_LEN; + break; + case NET_CTRL_EXT_METRIC: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->metric, v, sizeof(out->metric)); + out->present |= 1u << NET_CTRL_EXT_METRIC; + break; + case NET_CTRL_EXT_MTU: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->mtu, v, sizeof(out->mtu)); + out->present |= 1u << NET_CTRL_EXT_MTU; + break; + case NET_CTRL_EXT_FLAGS: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->flags, v, sizeof(out->flags)); + out->present |= 1u << NET_CTRL_EXT_FLAGS; + break; + case NET_CTRL_EXT_STATE: + if (a.length != sizeof(uint8_t)) return false; + out->state = v[0]; + out->present |= 1u << NET_CTRL_EXT_STATE; + break; + case NET_CTRL_EXT_CONFIG: + if (a.length != sizeof(int16_t)) return false; + memcpy(&out->config, v, sizeof(out->config)); + out->present |= 1u << NET_CTRL_EXT_CONFIG; + break; + case NET_CTRL_EXT_KIND: + if (a.length != sizeof(uint8_t)) return false; + out->kind = v[0]; + out->present |= 1u << NET_CTRL_EXT_KIND; + break; + case NET_CTRL_EXT_DAD_STATE: + if (a.length != sizeof(uint8_t)) return false; + out->dad_state = v[0]; + out->present |= 1u << NET_CTRL_EXT_DAD_STATE; + break; + case NET_CTRL_EXT_TTL_MS: + if (a.length != sizeof(uint32_t)) return false; + memcpy(&out->ttl_ms, v, sizeof(out->ttl_ms)); + out->present |= 1u << NET_CTRL_EXT_TTL_MS; + break; + case NET_CTRL_EXT_PROTOCOL: + if (a.length != sizeof(uint8_t)) return false; + out->protocol = v[0]; + out->present |= 1u << NET_CTRL_EXT_PROTOCOL; + break; + case NET_CTRL_EXT_RULE_ID: + if (a.length != sizeof(uint32_t)) return false; + memcpy(&out->rule_id, v, sizeof(out->rule_id)); + out->present |= 1u << NET_CTRL_EXT_RULE_ID; + break; + case NET_CTRL_EXT_ACTION: + if (a.length != sizeof(uint8_t)) return false; + out->action = v[0]; + out->present |= 1u << NET_CTRL_EXT_ACTION; + break; + case NET_CTRL_EXT_DIRECTION: + if (a.length != sizeof(uint8_t)) return false; + out->direction = v[0]; + out->present |= 1u << NET_CTRL_EXT_DIRECTION; + break; + case NET_CTRL_EXT_PORT_FROM: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->port_from, v, sizeof(out->port_from)); + out->present |= 1u << NET_CTRL_EXT_PORT_FROM; + break; + case NET_CTRL_EXT_PORT_TO: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->port_to, v, sizeof(out->port_to)); + out->present |= 1u << NET_CTRL_EXT_PORT_TO; + break; + case NET_CTRL_EXT_VLAN_ID: + if (a.length != sizeof(uint16_t)) return false; + memcpy(&out->vlan_id, v, sizeof(out->vlan_id)); + out->present |= 1u << NET_CTRL_EXT_VLAN_ID; + break; + case NET_CTRL_EXT_PARENT_IFNAME: + if (!a.length || a.length >= sizeof(out->parent_name)) return false; + memcpy(out->parent_name, v, a.length); + out->parent_name_len = (uint8_t)a.length; + out->present |= 1u << NET_CTRL_EXT_PARENT_IFNAME; + break; + case NET_CTRL_EXT_ADDRESS: + if (a.length != sizeof(net_l4_endpoint)) return false; + memcpy(&out->address, v, sizeof(out->address)); + out->present |= 1u << NET_CTRL_EXT_ADDRESS; + break; + case NET_CTRL_EXT_GATEWAY: + if (a.length != sizeof(net_l4_endpoint)) return false; + memcpy(&out->gateway, v, sizeof(out->gateway)); + out->present |= 1u << NET_CTRL_EXT_GATEWAY; + break; + case NET_CTRL_EXT_MAC: + if (a.length != sizeof(out->mac)) return false; + memcpy(out->mac, v, sizeof(out->mac)); + out->present |= 1u << NET_CTRL_EXT_MAC; + break; + //case NET_CTRL_EXT_NONE: + default: + break; + } + off += a.length; + } + return true; +} + +static bool net_ctrl_ifname_matches(const l2_interface_t* l2, const net_ctrl_attrs_t* a) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_IFNAME)) return true; + if (memcmp(l2->name, a->ifname, a->ifname_len) != 0) return false; + return a->ifname_len == sizeof(a->ifname) || l2->name[a->ifname_len] == '\0'; +} + +static inline bool net_ctrl_l2_matches(const l2_interface_t* l2, const net_ctrl_attrs_t* a) { + if (!l2) return false; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_IFINDEX) && l2->ifindex != a->ifindex) return false; + return net_ctrl_ifname_matches(l2, a); +} + +static l2_interface_t* net_ctrl_l2_from_attrs(const net_ctrl_attrs_t* a) { + if (NET_CTRL_HAS(a, NET_CTRL_EXT_IFINDEX)) { + l2_interface_t* l2 = l2_interface_find_by_index(a->ifindex); + if (!l2 || !net_ctrl_ifname_matches(l2, a)) return NULL; + return l2; + } + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_IFNAME)) return NULL; + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (l2 && net_ctrl_ifname_matches(l2, a)) return l2; + } + return NULL; +} + +static bool net_ctrl_link_dump(const net_ctrl_attrs_t* a, buffer* b) { + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!net_ctrl_l2_matches(l2, a)) continue; + NetCtrlLinkInfo info = {0}; + info.ifindex = l2->ifindex; + info.up = l2->is_up ? 1 : 0; + info.metric = l2->base_metric; + info.mtu = network_get_device_mtu(l2->ifindex); + info.kind = l2->dev_kind; + info.link_kind = l2->link_kind; + info.ipv4_count = l2->ipv4_count; + info.ipv6_count = l2->ipv6_count; + memcpy(info.name, l2->name, sizeof(info.name)); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + return true; +} + +static bool net_ctrl_vlan_dump(const net_ctrl_attrs_t* a, buffer* b) { + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!net_ctrl_l2_matches(l2, a) || l2->link_kind != NET_LINK_VLAN) continue; + l2_interface_t *parent = l2_interface_find_by_index(l2->parent_ifindex); + if (!parent) continue; + NetCtrlVlanInfo info = {0}; + info.ifindex = l2->ifindex; + info.parent_ifindex = parent->ifindex; + info.vlan_id = l2->vlan_id; + memcpy(info.name, l2->name, sizeof(info.name)); + memcpy(info.parent, parent->name, sizeof(info.parent)); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + return true; +} + +static int32_t net_ctrl_link_upd(const net_ctrl_attrs_t* a) { + l2_interface_t* l2 = net_ctrl_l2_from_attrs(a); + if (!l2) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_MTU)) return SOCK_ERR_UNSUP; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_STATE) && !l2_interface_set_up(l2->ifindex, a->state != 0)) return SOCK_ERR_SYS; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_METRIC) && !l2_interface_set_metric(l2->ifindex, a->metric)) return SOCK_ERR_INVAL; + return SOCK_OK; +} + +static int32_t net_ctrl_link_add(const net_ctrl_attrs_t* a) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_IFNAME) || !NET_CTRL_HAS(a, NET_CTRL_EXT_PARENT_IFNAME) || + !NET_CTRL_HAS(a, NET_CTRL_EXT_VLAN_ID) || !a->vlan_id || a->vlan_id > 4094 || a->ifname_len >= 16) return SOCK_ERR_INVAL; + if (net_ctrl_l2_from_attrs(a)) return SOCK_ERR_EXIST; + l2_interface_t* parent = NULL; + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t *link = l2_interface_at(i); + if (link && strcmp(link->name, a->parent_name) == 0) { + parent = link; + break; + } + } + if (!parent || parent->dev_kind != NET_DEV_ETH || parent->link_kind != NET_LINK_DIRECT) return SOCK_ERR_INVAL; + char name[16] = {0}; + memcpy(name, a->ifname, a->ifname_len); + uint8_t ifindex = l2_vlan_create(parent->ifindex, a->vlan_id, name); + if (!ifindex) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_METRIC) && !l2_interface_set_metric(ifindex, a->metric)) { + l2_vlan_destroy(ifindex); + return SOCK_ERR_SYS; + } + if (!l2_interface_set_up(ifindex, true)) { + l2_vlan_destroy(ifindex); + return SOCK_ERR_SYS; + } + ifmgr_autoconfig_l2(ifindex); + return SOCK_OK; +} + +static int32_t net_ctrl_link_del(const net_ctrl_attrs_t* a) { + l2_interface_t* vlan = net_ctrl_l2_from_attrs(a); + if (!vlan || vlan->link_kind != NET_LINK_VLAN) return SOCK_ERR_INVAL; + uint8_t ifindex = vlan->ifindex; + if (!l2_interface_set_up(ifindex, false)) return SOCK_ERR_SYS; + for (int i = 0; i < MAX_IPV4_PER_INTERFACE; i++) { + l3_ipv4_interface_t *v4 = vlan->l3_v4[i]; + if (v4 && !l3_ipv4_remove_from_interface(v4->l3_id)) return SOCK_ERR_SYS; + } + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t *v6 = vlan->l3_v6[i]; + if (v6 && !(v6->kind & IPV6_ADDRK_LINK_LOCAL) && !l3_ipv6_remove_from_interface(v6->l3_id)) return SOCK_ERR_SYS; + } + for (int i = 0; i < MAX_IPV6_PER_INTERFACE; i++) { + l3_ipv6_interface_t *v6 = vlan->l3_v6[i]; + if (v6 && !l3_ipv6_remove_from_interface(v6->l3_id)) return SOCK_ERR_SYS; + } + return l2_vlan_destroy(ifindex) ? SOCK_OK : SOCK_ERR_SYS; +} + +static bool net_ctrl_addr_dump(const net_ctrl_attrs_t* a, buffer* b) { + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!net_ctrl_l2_matches(l2, a)) continue; + for (int j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[j]; + if (!v4) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) && v4->l3_id != a->l3_id) continue; + NetCtrlAddrInfo info; + memset(&info, 0, sizeof(info)); + info.prefix.ifindex = l2->ifindex; + info.prefix.l3_id = v4->l3_id; + int prefix_len = ipv4_prefix_len(v4->mask); + info.prefix.prefix_len = prefix_len < 0 ? 0 : (uint8_t)prefix_len; + info.config = v4->mode; + info.epoch = v4->epoch; + info.mtu = l3_ipv4_effective_mtu(v4); + info.prefix.address.ver = IP_VER4; + info.prefix.gateway.ver = IP_VER4; + memcpy(info.prefix.address.ip, &v4->ip, sizeof(v4->ip)); + memcpy(info.prefix.gateway.ip, &v4->gw, sizeof(v4->gw)); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + for (int j = 0; j < MAX_IPV6_PER_INTERFACE; j++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[j]; + if (!v6) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) && v6->l3_id != a->l3_id) continue; + NetCtrlAddrInfo info; + memset(&info, 0, sizeof(info)); + info.prefix.ifindex = l2->ifindex; + info.prefix.l3_id = v6->l3_id; + info.prefix.prefix_len = v6->prefix_len; + info.kind = v6->kind; + info.config = v6->cfg; + info.epoch = v6->epoch; + info.mtu = l3_ipv6_effective_mtu(v6); + info.dad_state = v6->dad_state; + info.prefix.address.ver = IP_VER6; + info.prefix.gateway.ver = IP_VER6; + ipv6_cpy(info.prefix.address.ip, v6->ip); + ipv6_cpy(info.prefix.gateway.ip, v6->gateway); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + } + return true; +} + +static bool net_ctrl_v4_overlap(const l2_interface_t* l2, const l3_ipv4_interface_t* except, uint32_t ip, uint32_t mask) { + if (!l2 || !mask) return false; + for (uint32_t i = 0; i < N_ARR(l2->l3_v4); i++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[i]; + if (!v4 || v4 == except || v4->mode == IPV4_CFG_DISABLED || !v4->ip || !v4->mask) continue; + uint32_t common = v4->mask < mask ? v4->mask : mask; + if (ipv4_net(ip, common) != ipv4_net(v4->ip, common)) continue; + if (v4->mask != mask) return true; + } + return false; +} + +static int32_t net_ctrl_addr_apply(const net_ctrl_attrs_t* a, bool update) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS)) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_MTU)) return SOCK_ERR_UNSUP; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_DAD_STATE)) return SOCK_ERR_UNSUP; + if (update && !NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID)) return SOCK_ERR_INVAL; + l2_interface_t* l2 = update ? NULL : net_ctrl_l2_from_attrs(a); + if (!update && (!l2 || NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID))) return SOCK_ERR_INVAL; + if (a->address.ver == IP_VER4) { + uint32_t ip = 0; + uint32_t gw = 0; + int16_t config = NET_CTRL_HAS(a, NET_CTRL_EXT_CONFIG) ? a->config : IPV4_CFG_STATIC; + if ((config != IPV4_CFG_DISABLED && config != IPV4_CFG_DHCP && config != IPV4_CFG_STATIC) || NET_CTRL_HAS(a, NET_CTRL_EXT_KIND)) return SOCK_ERR_INVAL; + memcpy(&ip, a->address.ip, sizeof(ip)); + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY)) { + if (a->gateway.ver != IP_VER4) return SOCK_ERR_INVAL; + memcpy(&gw, a->gateway.ip, sizeof(gw)); + } + if (config == IPV4_CFG_STATIC && (!NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN) || a->prefix_len > 32)) return SOCK_ERR_INVAL; + if (config != IPV4_CFG_STATIC && NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN) && a->prefix_len > 32) return SOCK_ERR_INVAL; + uint32_t mask = 0; + if (config == IPV4_CFG_STATIC) { + if (a->prefix_len >= 32) mask = 0xFFFFFFFF; + else if (a->prefix_len) mask = 0xFFFFFFFF << (32 - a->prefix_len); + } + if (config != IPV4_CFG_STATIC) { + ip = 0; + gw = 0; + } + if (update) { + l3_ipv4_interface_t* current = l3_ipv4_find_by_id(a->l3_id); + if (!current || !current->l2) return SOCK_ERR_NOT_FOUND; + if (config == IPV4_CFG_STATIC && net_ctrl_v4_overlap(current->l2, current, ip, mask)) return SOCK_ERR_INVAL; + return l3_ipv4_update(a->l3_id, ip, mask, gw, (ipv4_cfg_t)config, NULL) ? SOCK_OK : SOCK_ERR_INVAL; + } + if (config == IPV4_CFG_STATIC && net_ctrl_v4_overlap(l2, NULL, ip, mask)) return SOCK_ERR_INVAL; + return l3_ipv4_add_to_interface(l2->ifindex, ip, mask, gw, (ipv4_cfg_t)config, NULL) ? SOCK_OK : SOCK_ERR_INVAL; + } + if (a->address.ver == IP_VER6) { + int16_t config = NET_CTRL_HAS(a, NET_CTRL_EXT_CONFIG) ? a->config : IPV6_CFG_STATIC; + uint8_t kind = NET_CTRL_HAS(a, NET_CTRL_EXT_KIND) ? a->kind : (ipv6_is_linklocal(a->address.ip) ? IPV6_ADDRK_LINK_LOCAL : IPV6_ADDRK_GLOBAL); + if (config != IPV6_CFG_DISABLE && config != IPV6_CFG_STATIC && config != IPV6_CFG_SLAAC && config != IPV6_CFG_DHCPV6) return SOCK_ERR_INVAL; + if (kind != IPV6_ADDRK_GLOBAL && kind != IPV6_ADDRK_LINK_LOCAL) return SOCK_ERR_INVAL; + if (config == IPV6_CFG_STATIC && (!NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN) || a->prefix_len > 128)) return SOCK_ERR_INVAL; + if (config != IPV6_CFG_STATIC && NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN) && a->prefix_len > 128) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) && a->gateway.ver != IP_VER6) return SOCK_ERR_INVAL; + const uint8_t* gw = NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) ? a->gateway.ip : (const uint8_t[16]){0}; + uint8_t prefix_len = NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN) ? a->prefix_len : 0; + if (update) return l3_ipv6_update(a->l3_id, a->address.ip, prefix_len, gw, (ipv6_cfg_t)config, kind) ? SOCK_OK : SOCK_ERR_INVAL; + return l3_ipv6_add_to_interface(l2->ifindex, a->address.ip, prefix_len, gw, (ipv6_cfg_t)config, kind) ? SOCK_OK : SOCK_ERR_INVAL; + } + return SOCK_ERR_INVAL; +} + +static int32_t net_ctrl_addr_del(const net_ctrl_attrs_t* a) { + if (NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID)) { + if (l3_ipv4_find_by_id(a->l3_id)) return l3_ipv4_remove_from_interface(a->l3_id) ? SOCK_OK : SOCK_ERR_INVAL; + if (l3_ipv6_find_by_id(a->l3_id)) return l3_ipv6_remove_from_interface(a->l3_id) ? SOCK_OK : SOCK_ERR_INVAL; + return SOCK_ERR_INVAL; + } + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS)) return SOCK_ERR_INVAL; + if (a->address.ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, a->address.ip, sizeof(ip)); + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(ip); + return v4 && l3_ipv4_remove_from_interface(v4->l3_id) ? SOCK_OK : SOCK_ERR_INVAL; + } + if (a->address.ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_ip(a->address.ip); + return v6 && l3_ipv6_remove_from_interface(v6->l3_id) ? SOCK_OK : SOCK_ERR_INVAL; + } + return SOCK_ERR_INVAL; +} + +static bool net_ctrl_route_dump(const net_ctrl_attrs_t* a, buffer* b) { + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!net_ctrl_l2_matches(l2, a)) continue; + for (int j = 0; j < MAX_IPV4_PER_INTERFACE; j++) { + l3_ipv4_interface_t* v4 = l2->l3_v4[j]; + if (!v4 || !v4->routing_table) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) && v4->l3_id != a->l3_id) continue; + int n = ipv4_rt_count((const ipv4_rt_table_t*)v4->routing_table); + for (int r = 0; r < n; r++) { + ipv4_rt_entry_t e; + if (!ipv4_rt_get((const ipv4_rt_table_t*)v4->routing_table, r, &e)) continue; + NetCtrlRouteInfo info; + memset(&info, 0, sizeof(info)); + info.prefix.ifindex = l2->ifindex; + info.prefix.l3_id = v4->l3_id; + int prefix_len = ipv4_prefix_len(e.mask); + info.prefix.prefix_len = prefix_len < 0 ? 0 : (uint8_t)prefix_len; + info.metric = e.metric; + info.route_epoch = ipv4_rt_epoch((const ipv4_rt_table_t*)v4->routing_table); + info.prefix.address.ver = IP_VER4; + info.prefix.gateway.ver = IP_VER4; + memcpy(info.prefix.address.ip, &e.network, sizeof(e.network)); + memcpy(info.prefix.gateway.ip, &e.gateway, sizeof(e.gateway)); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + } + for (int j = 0; j < MAX_IPV6_PER_INTERFACE; j++) { + l3_ipv6_interface_t* v6 = l2->l3_v6[j]; + if (!v6 || !v6->routing_table) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) && v6->l3_id != a->l3_id) continue; + int n = ipv6_rt_count((const ipv6_rt_table_t*)v6->routing_table); + for (int r = 0; r < n; r++) { + ipv6_rt_entry_t e; + if (!ipv6_rt_get((const ipv6_rt_table_t*)v6->routing_table, r, &e)) continue; + NetCtrlRouteInfo info; + memset(&info, 0, sizeof(info)); + info.prefix.ifindex = l2->ifindex; + info.prefix.l3_id = v6->l3_id; + info.prefix.prefix_len = e.prefix_len; + info.metric = e.metric; + info.route_epoch = ipv6_rt_epoch((const ipv6_rt_table_t*)v6->routing_table); + info.prefix.address.ver = IP_VER6; + info.prefix.gateway.ver = IP_VER6; + ipv6_cpy(info.prefix.address.ip, e.network); + ipv6_cpy(info.prefix.gateway.ip, e.gateway); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + } + } + return true; +} + +static int32_t net_ctrl_route_apply(const net_ctrl_attrs_t* a, bool add) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) || !NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS) || !NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN)) return SOCK_ERR_INVAL; + uint16_t metric = NET_CTRL_HAS(a, NET_CTRL_EXT_METRIC) ? a->metric : 0; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(a->l3_id); + if (v4) { + uint32_t network = 0; + uint32_t gw = 0; + if (!v4 || v4->is_localhost || a->address.ver != IP_VER4) return SOCK_ERR_INVAL; + memcpy(&network, a->address.ip, sizeof(network)); + if (a->prefix_len > 32) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY)) { + if (a->gateway.ver != IP_VER4) return SOCK_ERR_INVAL; + memcpy(&gw, a->gateway.ip, sizeof(gw)); + } + if (!v4->routing_table && !add) return SOCK_ERR_NOT_FOUND; + if (!v4->routing_table) v4->routing_table = ipv4_rt_create(v4->l3_id); + if (!v4->routing_table) return SOCK_ERR_SYS; + uint32_t mask = 0; + if (a->prefix_len >= 32) mask = 0xFFFFFFFF; + else if (a->prefix_len) mask = 0xFFFFFFFF << (32 -a->prefix_len); + bool exists = false; + int n = ipv4_rt_count((const ipv4_rt_table_t*)v4->routing_table); + for (int i = 0; i < n; i++) { + ipv4_rt_entry_t e; + if (ipv4_rt_get((const ipv4_rt_table_t*)v4->routing_table, i, &e) && e.network == (network & mask) && e.mask == mask) { + exists = true; + break; + } + } + if (add && exists) return SOCK_ERR_EXIST; + if (!add && !exists) return SOCK_ERR_NOT_FOUND; + return ipv4_rt_add_in((ipv4_rt_table_t*)v4->routing_table, network & mask, mask, gw, metric) ? SOCK_OK : SOCK_ERR_INVAL; + } + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(a->l3_id); + if (!v6 || v6->is_localhost || a->address.ver != IP_VER6 || a->prefix_len > 128) return SOCK_ERR_INVAL; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) && a->gateway.ver != IP_VER6) return SOCK_ERR_INVAL; + const uint8_t* gw = NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) ? a->gateway.ip : (const uint8_t[16]){0}; + if (!v6->routing_table && !add) return SOCK_ERR_NOT_FOUND; + if (!v6->routing_table) v6->routing_table = ipv6_rt_create(v6->l3_id); + if (!v6->routing_table) return SOCK_ERR_SYS; + uint8_t net[16]; + ipv6_prefix_network(a->address.ip, a->prefix_len, net); + bool exists = false; + int n = ipv6_rt_count((const ipv6_rt_table_t*)v6->routing_table); + for (int i = 0; i < n; i++) { + ipv6_rt_entry_t e; + if (ipv6_rt_get((const ipv6_rt_table_t*)v6->routing_table, i, &e) && e.prefix_len == a->prefix_len && ipv6_cmp(e.network, net) == 0) { + exists = true; + break; + } + } + if (add && exists) return SOCK_ERR_EXIST; + if (!add && !exists) return SOCK_ERR_NOT_FOUND; + return ipv6_rt_add_in((ipv6_rt_table_t*)v6->routing_table, net, a->prefix_len, gw, metric) ? SOCK_OK : SOCK_ERR_INVAL; +} + +static int32_t net_ctrl_route_del(const net_ctrl_attrs_t* a) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_L3_ID) || !NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS) || !NET_CTRL_HAS(a, NET_CTRL_EXT_PREFIX_LEN)) return SOCK_ERR_INVAL; + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(a->l3_id); + if (v4) { + uint32_t network = 0; + uint32_t gateway = 0; + if (!v4 || a->address.ver != IP_VER4 || a->prefix_len > 32) return SOCK_ERR_INVAL; + if (!v4->routing_table) return SOCK_ERR_NOT_FOUND; + memcpy(&network, a->address.ip, sizeof(network)); + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY)) { + if (a->gateway.ver != IP_VER4) return SOCK_ERR_INVAL; + memcpy(&gateway, a->gateway.ip, sizeof(gateway)); + } + uint32_t mask = 0; + if (a->prefix_len >= 32) mask = 0xFFFFFFFF; + else if (a->prefix_len) mask = 0xFFFFFFFF << (32 -(a->prefix_len)); + uint32_t exact_network = network & mask; + bool found = false; + int n = ipv4_rt_count((const ipv4_rt_table_t*)v4->routing_table); + for (int i = 0; i < n; i++) { + ipv4_rt_entry_t e; + if (!ipv4_rt_get((const ipv4_rt_table_t*)v4->routing_table, i, &e) || e.network != exact_network || e.mask != mask) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) && e.gateway != gateway) continue; + found = true; + break; + } + if (!found) return SOCK_ERR_NOT_FOUND; + return ipv4_rt_del_in((ipv4_rt_table_t*)v4->routing_table, exact_network, mask) ? SOCK_OK : SOCK_ERR_NOT_FOUND; + } + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(a->l3_id); + if (!v6 || a->address.ver != IP_VER6 || a->prefix_len > 128) return SOCK_ERR_INVAL; + if (!v6->routing_table) return SOCK_ERR_NOT_FOUND; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) && a->gateway.ver != IP_VER6) return SOCK_ERR_INVAL; + uint8_t net[16]; + ipv6_prefix_network(a->address.ip, a->prefix_len, net); + bool found = false; + int n = ipv6_rt_count((const ipv6_rt_table_t*)v6->routing_table); + for (int i = 0; i < n; i++) { + ipv6_rt_entry_t e; + if (!ipv6_rt_get((const ipv6_rt_table_t*)v6->routing_table, i, &e) || e.prefix_len != a->prefix_len || ipv6_cmp(e.network, net) != 0) continue; + if (NET_CTRL_HAS(a, NET_CTRL_EXT_GATEWAY) && ipv6_cmp(e.gateway, a->gateway.ip) != 0) continue; + found = true; + break; + } + if (!found) return SOCK_ERR_NOT_FOUND; + return ipv6_rt_del_in((ipv6_rt_table_t*)v6->routing_table, net, a->prefix_len) ? SOCK_OK : SOCK_ERR_NOT_FOUND; +} + +static bool net_ctrl_neigh_dump(const net_ctrl_attrs_t* a, buffer* b) { + uint8_t count = l2_interface_count(); + for (uint8_t i = 0; i < count; i++) { + l2_interface_t* l2 = l2_interface_at(i); + if (!net_ctrl_l2_matches(l2, a)) continue; + arp_entry_t ae[ARP_TABLE_MAX]; + uint32_t an = arp_table_dump_for_l2(l2->ifindex, ae, ARP_TABLE_MAX); + for (uint32_t j = 0; j < an; j++) { + if (NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS)) { + uint32_t want = 0; + if (a->address.ver != IP_VER4) continue; + memcpy(&want, a->address.ip, sizeof(want)); + if (ae[j].ip != want) continue; + } + NetCtrlNeighInfo info; + memset(&info, 0, sizeof(info)); + info.ifindex = l2->ifindex; + info.state = ae[j].state; + info.ttl_ms = ae[j].ttl_ms; + info.router_lifetime_ms = 0; + if (ae[j].static_entry) info.flags |= NET_CTRL_NEIGH_F_STATIC; + info.address.ver = IP_VER4; + memcpy(info.address.ip, &ae[j].ip, sizeof(ae[j].ip)); + mac_copy(info.mac, ae[j].mac); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + ndp_entry_t ne[NDP_TABLE_MAX]; + uint32_t nn = ndp_table_dump_for_l2(l2->ifindex, ne, NDP_TABLE_MAX); + for (uint32_t j = 0; j < nn; j++) { + if (NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS)) { + if (a->address.ver != IP_VER6 || ipv6_cmp(a->address.ip, ne[j].ip) != 0) continue; + } + NetCtrlNeighInfo info; + memset(&info, 0, sizeof(info)); + info.ifindex = l2->ifindex; + info.state = ne[j].state; + info.ttl_ms = ne[j].ttl_ms; + info.router_lifetime_ms = ndp_default_router_lifetime_for_l2(l2->ifindex, ne[j].ip); + if (ne[j].static_entry) info.flags |= NET_CTRL_NEIGH_F_STATIC; + if (ne[j].is_router) info.flags |= NET_CTRL_NEIGH_F_ROUTER; + info.address.ver = IP_VER6; + ipv6_cpy(info.address.ip, ne[j].ip); + mac_copy(info.mac, ne[j].mac); + if (buffer_write_lim(b, (const char*)&info, sizeof(info)) != sizeof(info)) return false; + } + } + return true; +} + +static int32_t net_ctrl_neigh_set(const net_ctrl_attrs_t* a, bool add) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS) || !NET_CTRL_HAS(a, NET_CTRL_EXT_MAC)) return SOCK_ERR_INVAL; + l2_interface_t* l2 = net_ctrl_l2_from_attrs(a); + if (!l2 || l2->link_kind == NET_LINK_LOOPBACK) return SOCK_ERR_INVAL; + bool exists = false; + if (a->address.ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, a->address.ip, sizeof(ip)); + arp_entry_t ae[ARP_TABLE_MAX]; + uint32_t n = arp_table_dump_for_l2(l2->ifindex, ae, ARP_TABLE_MAX); + for (uint32_t i = 0; i < n; i++) { + if (ae[i].ip == ip) { + exists = true; + break; + } + } + if (add && exists) return SOCK_ERR_EXIST; + if (!add && !exists) return SOCK_ERR_NOT_FOUND; + if (!l2_prepare_arp(l2->ifindex)) return SOCK_ERR_SYS; + uint32_t ttl = NET_CTRL_HAS(a, NET_CTRL_EXT_TTL_MS) ? a->ttl_ms : 0; + arp_table_put_for_l2(l2->ifindex, ip, a->mac, ttl, NET_CTRL_HAS(a, NET_CTRL_EXT_FLAGS) && (a->flags & NET_CTRL_NEIGH_F_STATIC)); + return SOCK_OK; + } + if (a->address.ver == IP_VER6) { + ndp_entry_t ne[NDP_TABLE_MAX]; + uint32_t n = ndp_table_dump_for_l2(l2->ifindex, ne, NDP_TABLE_MAX); + for (uint32_t i = 0; i < n; i++) { + if (ipv6_cmp(ne[i].ip, a->address.ip) == 0) { + exists = true; + break; + } + } + if (add && exists) return SOCK_ERR_EXIST; + if (!add && !exists) return SOCK_ERR_NOT_FOUND; + if (!l2_prepare_ndp(l2->ifindex)) return SOCK_ERR_SYS; + uint32_t ttl = NET_CTRL_HAS(a, NET_CTRL_EXT_TTL_MS) ? a->ttl_ms : 0; + bool router = NET_CTRL_HAS(a, NET_CTRL_EXT_FLAGS) && (a->flags & NET_CTRL_NEIGH_F_ROUTER); + bool is_static = NET_CTRL_HAS(a, NET_CTRL_EXT_FLAGS) && (a->flags & NET_CTRL_NEIGH_F_STATIC); + ndp_table_put_for_l2(l2->ifindex, a->address.ip, a->mac, ttl, router, is_static); + return SOCK_OK; + } + return SOCK_ERR_INVAL; +} + +static int32_t net_ctrl_neigh_del(const net_ctrl_attrs_t* a) { + if (!NET_CTRL_HAS(a, NET_CTRL_EXT_ADDRESS)) return SOCK_ERR_INVAL; + l2_interface_t* l2 = net_ctrl_l2_from_attrs(a); + if (!l2) return SOCK_ERR_INVAL; + if (a->address.ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, a->address.ip, sizeof(ip)); + if (!l2->arp_table) return SOCK_ERR_INVAL; + return arp_table_delete_for_l2(l2->ifindex, ip) ? SOCK_OK : SOCK_ERR_INVAL; + } + if (a->address.ver == IP_VER6) { + if (!l2->nd_table) return SOCK_ERR_INVAL; + return ndp_table_delete_for_l2(l2->ifindex, a->address.ip) ? SOCK_OK : SOCK_ERR_INVAL; + } + return SOCK_ERR_INVAL; +} + +int32_t net_ctrl_dispatch(const void* req, uint32_t req_len, uint8_t** out, uint32_t* out_len) { + if (!req || req_len < sizeof(NetCtrlMsg) || !out || !out_len) return SOCK_ERR_INVAL; + *out = NULL; + *out_len = 0; + + NetCtrlMsg in; + memcpy(&in, req, sizeof(in)); + if (in.length != req_len || in.length < sizeof(NetCtrlMsg)) return SOCK_ERR_INVAL; + if (!(in.flags & NET_CTRL_F_REQUEST)) return SOCK_ERR_INVAL; + + net_ctrl_attrs_t attrs; + if (!net_ctrl_read_attrs(NET_CTRL_MSG_CONST_DATA((const NetCtrlMsg*)req), NET_CTRL_MSG_PAYLOAD_LEN(&in), &attrs)) return SOCK_ERR_INVAL; + + buffer b = buffer_create(256, buffer_can_grow); + if (!b.buffer) return SOCK_ERR_SYS; + NetCtrlMsg response = { in.object, in.op, NET_CTRL_F_RESPONSE, 0, sizeof(NetCtrlMsg), SOCK_OK }; + if (buffer_write_lim(&b, (const char*)&response, sizeof(response)) != sizeof(response)) { + buffer_destroy(&b); + return SOCK_ERR_SYS; + } + + int32_t status; + switch ((uint32_t)in.object) { + case NET_CTRL_OBJ_LINK: + switch ((uint32_t)in.op) { + case NET_CTRL_OP_GET: + status = net_ctrl_link_dump(&attrs, &b) ? SOCK_OK : SOCK_ERR_SYS; + break; + case NET_CTRL_OP_UPD: + status = net_ctrl_link_upd(&attrs); + break; + case NET_CTRL_OP_ADD: + status = net_ctrl_link_add(&attrs); + break; + case NET_CTRL_OP_DEL: + status = net_ctrl_link_del(&attrs); + break; + default: + status = SOCK_ERR_INVAL; + break; + } + break; + case NET_CTRL_OBJ_ADDR: + switch ((uint32_t)in.op) { + case NET_CTRL_OP_GET: + status = net_ctrl_addr_dump(&attrs, &b) ? SOCK_OK : SOCK_ERR_SYS; + break; + case NET_CTRL_OP_ADD: + status = net_ctrl_addr_apply(&attrs, false); + break; + case NET_CTRL_OP_UPD: + status = net_ctrl_addr_apply(&attrs, true); + break; + case NET_CTRL_OP_DEL: + status = net_ctrl_addr_del(&attrs); + break; + default: + status = SOCK_ERR_INVAL; + break; + } + break; + case NET_CTRL_OBJ_ROUTE: + switch ((uint32_t)in.op) { + case NET_CTRL_OP_GET: + status = net_ctrl_route_dump(&attrs, &b) ? SOCK_OK : SOCK_ERR_SYS; + break; + case NET_CTRL_OP_ADD: + status = net_ctrl_route_apply(&attrs, true); + break; + case NET_CTRL_OP_UPD: + status = net_ctrl_route_apply(&attrs, false); + break; + case NET_CTRL_OP_DEL: + status = net_ctrl_route_del(&attrs); + break; + default: + status = SOCK_ERR_INVAL; + break; + } + break; + case NET_CTRL_OBJ_VLAN: + if (in.op != NET_CTRL_OP_GET) status = SOCK_ERR_INVAL; + else status = net_ctrl_vlan_dump(&attrs, &b) ? SOCK_OK : SOCK_ERR_SYS; + break; + case NET_CTRL_OBJ_NEIGH: + switch ((uint32_t)in.op) { + case NET_CTRL_OP_GET: + status = net_ctrl_neigh_dump(&attrs, &b) ? SOCK_OK : SOCK_ERR_SYS; + break; + case NET_CTRL_OP_ADD: + status = net_ctrl_neigh_set(&attrs, true); + break; + case NET_CTRL_OP_UPD: + status = net_ctrl_neigh_set(&attrs, false); + break; + case NET_CTRL_OP_DEL: + status = net_ctrl_neigh_del(&attrs); + break; + default: + status = SOCK_ERR_INVAL; + break; + } + break; + case NET_CTRL_OBJ_FIREWALL: + switch ((uint32_t)in.op) { + case NET_CTRL_OP_GET: { + uint32_t capacity = firewall_get_snapshot(NULL, NULL, 0); + NetCtrlFirewallRule* rules = capacity ? (NetCtrlFirewallRule*)zalloc((uint64_t)capacity * sizeof(NetCtrlFirewallRule)) : NULL; + if (capacity && !rules) { + status = SOCK_ERR_SYS; + break; + } + NetCtrlFirewallState state; + uint32_t count = capacity ? firewall_get_snapshot(&state, rules, capacity) : firewall_get_snapshot(&state, NULL, 0); + if (!capacity) { + count = 0; + state.rule_count = 0; + } + status = buffer_write_lim(&b, (const char*)&state, sizeof(state)) == sizeof(state) && (!count || buffer_write_lim(&b, (const char*)rules, sizeof(NetCtrlFirewallRule) * count) == sizeof(NetCtrlFirewallRule) * count) ? SOCK_OK : SOCK_ERR_SYS; + if (rules) release(rules); + break; + } + case NET_CTRL_OP_ADD: { + if (!NET_CTRL_HAS(&attrs, NET_CTRL_EXT_ACTION) || !NET_CTRL_HAS(&attrs, NET_CTRL_EXT_DIRECTION) || + (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_ADDRESS) && attrs.address.ver != IP_VER4 && attrs.address.ver != IP_VER6) || + (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PREFIX_LEN) && !NET_CTRL_HAS(&attrs, NET_CTRL_EXT_ADDRESS)) || + (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PORT_FROM) != NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PORT_TO))) { + status = SOCK_ERR_INVAL; + break; + } + NetCtrlFirewallRule rule = { + .action = attrs.action, + .direction = attrs.direction, + .protocol = NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PROTOCOL) ? attrs.protocol : PROTO_NONE + }; + if (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_ADDRESS)){ + rule.ip_version = attrs.address.ver; + rule.prefix_len = NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PREFIX_LEN) ? attrs.prefix_len : (attrs.address.ver == IP_VER4 ? 32 : 128); + memcpy(rule.address, attrs.address.ip, sizeof(rule.address)); + } + if (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_PORT_FROM)) { + rule.port_from = attrs.port_from; + rule.port_to = attrs.port_to; + } + status = firewall_add_rule(&rule, 0); + break; + } + case NET_CTRL_OP_UPD: { + bool has_state = NET_CTRL_HAS(&attrs, NET_CTRL_EXT_STATE); + bool has_action = NET_CTRL_HAS(&attrs, NET_CTRL_EXT_ACTION); + bool has_direction = NET_CTRL_HAS(&attrs, NET_CTRL_EXT_DIRECTION); + if (has_action != has_direction || (!has_state && !has_action)) { + status = SOCK_ERR_INVAL; + break; + } + status = has_action ? firewall_set_default((NetCtrlFirewallDirection)attrs.direction, (NetCtrlFirewallAction)attrs.action) : SOCK_OK; + if (status == SOCK_OK && has_state) firewall_set_enabled(attrs.state != 0); + break; + } + case NET_CTRL_OP_DEL: + if (NET_CTRL_HAS(&attrs, NET_CTRL_EXT_RULE_ID)) status = firewall_remove_rule(attrs.rule_id); + else { + firewall_clear_rules(); + status = SOCK_OK; + } + break; + default: + status = SOCK_ERR_INVAL; + break; + } + break; + default: + status = SOCK_ERR_INVAL; + break; + } + + if (status != SOCK_OK) b.buffer_size = sizeof(NetCtrlMsg); + NetCtrlMsg* hdr = (NetCtrlMsg*)b.buffer; + hdr->status = status; + hdr->length = b.buffer_size; + *out = b.buffer; + *out_len = b.buffer_size; + return SOCK_OK; +} diff --git a/kernel/networking/transport_layer/net_ctrl.h b/kernel/networking/transport_layer/net_ctrl.h new file mode 100644 index 00000000..b7b557c3 --- /dev/null +++ b/kernel/networking/transport_layer/net_ctrl.h @@ -0,0 +1,14 @@ +#pragma once + +#include "types.h" +#include "net/net_ctrl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t net_ctrl_dispatch(const void* req, uint32_t req_len, uint8_t** out, uint32_t* out_len); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/kernel/networking/transport_layer/socket.hpp b/kernel/networking/transport_layer/socket.hpp deleted file mode 100644 index 2c7a3030..00000000 --- a/kernel/networking/transport_layer/socket.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once -#include "types.h" -#include "net/network_types.h" -#include "networking/port_manager.h" -#include "tcp.h" -#include "udp.h" -#include "net/socket_types.h" -#include "console/kio.h" -#include "networking/net_logger/net_logger.h" - -#ifdef __cplusplus -extern "C" { -#endif - -//TODO: replace with enum -#define SOCK_ROLE_CLIENT 0 -#define SOCK_ROLE_SERVER 1 - -#define SOCK_OK 0 -#define SOCK_ERR_INVAL -1 -#define SOCK_ERR_BOUND -2 -#define SOCK_ERR_NOT_BOUND -3 -#define SOCK_ERR_PERM -4 -#define SOCK_ERR_NO_PORT -5 -#define SOCK_ERR_SYS -6 -#define SOCK_ERR_PROTO -7 -#define SOCK_ERR_STATE -8 -#define SOCK_ERR_DNS -9 - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus - -class Socket { -protected: - static constexpr int SOCK_MAX_L3 = 32; - - uint16_t localPort = 0; - net_l4_endpoint remoteEP = { IP_VER4, {0}, 0 }; - - uint8_t proto = 0; - uint8_t role = 0; - bool bound = false; - bool connected = false; - uint16_t pid = 0; - - SocketExtraOptions extraOpts = {}; - - uint8_t bound_l3[SOCK_MAX_L3] = {0}; - int bound_l3_count = 0; - - Socket(uint8_t protocol, uint8_t r, const SocketExtraOptions* extra) : proto(protocol), role(r) { - if (extra) extraOpts = *extra; - } - - virtual void do_unbind_one(uint8_t l3_id, uint16_t port, uint16_t pid) = 0; - - void clear_bound_l3() { bound_l3_count = 0; } - bool add_bound_l3(uint8_t l3_id) { if (bound_l3_count >= SOCK_MAX_L3) return false; bound_l3[bound_l3_count++] = l3_id; return true; } - void set_remote_endpoint(const net_l4_endpoint& ep) { remoteEP = ep; } - -public: - virtual ~Socket() { close(); } - - virtual int32_t bind(const SockBindSpec& spec, uint16_t port) = 0; - - virtual int32_t close() { - if (bound) { - for (int i = 0; i < bound_l3_count; ++i) { - do_unbind_one(bound_l3[i], localPort, pid); - } - bound = false; - localPort = 0; - clear_bound_l3(); - } - connected = false; - remoteEP.port = 0; - remoteEP.ver = IP_VER4; - memset(remoteEP.ip, 0, 16); - return SOCK_OK; - } - - uint16_t get_local_port() const { return localPort; } - uint16_t get_remote_port() const { return remoteEP.port; } - uint8_t get_protocol() const { return proto; } - uint8_t get_role() const { return role; } - uint16_t get_pid() const { return pid; } - bool is_bound() const { return bound; } - bool is_connected() const { return connected; } - - ip_version_t get_remote_ip_version() const { return remoteEP.ver; } - const uint8_t* get_remote_ip_bytes() const { return remoteEP.ip; } - const net_l4_endpoint& get_remote_endpoint() const { return remoteEP; } - - int get_bound_l3_count() const { return bound_l3_count; } - uint8_t get_bound_l3_id(int idx) const { return (idx >= 0 && idx < bound_l3_count) ? bound_l3[idx] : 0; } -}; - -#endif diff --git a/kernel/networking/transport_layer/socket_bind.c b/kernel/networking/transport_layer/socket_bind.c new file mode 100644 index 00000000..bc499f36 --- /dev/null +++ b/kernel/networking/transport_layer/socket_bind.c @@ -0,0 +1,671 @@ +#include "socket_bind.h" +#include "networking/transport_layer/tcp.h" +#include "exceptions/irq.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/internet_layer/ipv4_route.h" +#include "networking/internet_layer/ipv6_route.h" +#include "random/random.h" +#include "std/memory.h" +#include "data/struct/fixed_hash.h" +#include "data/hash.h" + +#define SOCKET_PORT_MIN_EPHEMERAL 49152u +#define SOCKET_PORT_MAX_EPHEMERAL 65535u +#define SOCKET_BIND_LIST_END 0xFFFF +#define BIND_FANOUT_HASH_MAX 12 +#define SOCKET_BIND_MAP_CAPACITY SOCKET_BIND_MAX * 4 +#define SOCKET_BIND_FLAG_REUSEADDR (1u << 0) +#define SOCKET_BIND_FLAG_REUSEPORT (1u << 1) +#define SOCKET_BIND_FLAG_LISTENING (1u << 2) + +typedef struct socket_bind_entry { + uint16_t port; + uint16_t next; + uint16_t generation; + uint8_t flags; + SockBindSpec spec; + net_l4_endpoint remote; + ksocket_t* socket; +} socket_bind_entry_t; + +static fixed_hash_entry_t bind_groups[SOCKET_BIND_MAX]; +static uint16_t bind_buckets[SOCKET_BIND_MAP_CAPACITY]; +static fixed_hash_t bind_map; +static bool bind_ready = 0; +static uint16_t bind_next_alloc = 0; +static uint16_t bind_used = 0; +static socket_bind_entry_t bind_entries[SOCKET_BIND_MAX]; + +static uint32_t socket_bind_key(protocol_t protocol, uint16_t port) { + return ((uint32_t)protocol << 16) | port; +} + +static bool socket_bind_normalize_spec(SockBindSpec* spec) { + if (!spec) return false; + + if (spec->kind == BIND_L3 && !spec->l3_id && !spec->ifindex && !spec->ver && ipv6_is_unspecified(spec->ip)) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY; + return true; + } + + if (spec->kind == BIND_L3) { + if (!spec->l3_id) return false; + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(spec->l3_id); + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(spec->l3_id); + if (!v4 && !v6) return false; + if (!spec->ver) spec->ver = v6 ? IP_VER6 : IP_VER4; + if ((spec->ver == IP_VER4 && !v4) || (spec->ver == IP_VER6 && !v6)) return false; + if (spec->ver != IP_VER4 && spec->ver != IP_VER6) return false; + spec->ifindex = 0; + memset(spec->ip, 0, sizeof(spec->ip)); + return true; + } + + if (spec->kind == BIND_L2) { + if (!spec->ifindex || (spec->ver && spec->ver != IP_VER4 && spec->ver != IP_VER6)) return false; + spec->l3_id = 0; + memset(spec->ip, 0, sizeof(spec->ip)); + return true; + } + if (spec->kind == BIND_ANY) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY; + return true; + } + if (spec->kind == BIND_ANY4) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY4; + spec->ver = IP_VER4; + return true; + } + if (spec->kind == BIND_ANY6) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY6; + spec->ver = IP_VER6; + return true; + } + if (spec->kind != BIND_IP) return false; + + spec->l3_id = 0; + spec->ifindex = 0; + if (spec->ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, spec->ip, 4); + if (ipv4_is_unspecified(ip)) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY4; + spec->ver = IP_VER4; + return true; + } + if (ipv4_is_multicast(ip) || ipv4_is_limited_broadcast(ip)) return false; + memset(spec->ip + 4, 0, 12); + return true; + } + + if (spec->ver == IP_VER6) { + if (ipv6_is_unspecified(spec->ip)) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY6; + spec->ver = IP_VER6; + return true; + } + if (ipv6_is_multicast(spec->ip)) return false; + return true; + } + + if (!spec->ver && ipv6_is_unspecified(spec->ip)) { + memset(spec, 0, sizeof(*spec)); + spec->kind = BIND_ANY; + return true; + } + + return false; +} + +bool socket_bind_prepare_spec(SockBindSpec* spec, protocol_t protocol) { + if (!spec || (protocol != PROTO_TCP && protocol != PROTO_UDP)) return false; + if (!socket_bind_normalize_spec(spec)) return false; + + if (spec->kind == BIND_ANY || spec->kind == BIND_ANY4 || spec->kind == BIND_ANY6) return true; + if (spec->kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(spec->ifindex); + return l2_interface_is_operational(l2); + } + + if (spec->kind == BIND_L3) { + if (spec->ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(spec->l3_id); + if (!v4 || !v4->l2 || (protocol == PROTO_TCP ? !ipv4_l3_is_ready(v4) : !ipv4_l3_is_active(v4))) return false; + spec->ifindex = v4->l2->ifindex; + return true; + } + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(spec->l3_id); + if (!v6 || !v6->l2 || (protocol == PROTO_TCP ? !ipv6_l3_is_tcp_usable(v6) : !ipv6_l3_is_ready(v6))) return false; + spec->ifindex = v6->l2->ifindex; + return true; + } + + if (spec->ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, spec->ip, 4); + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(ip); + if (!v4 || !v4->l2 || (protocol == PROTO_TCP ? !ipv4_l3_is_ready(v4) : !ipv4_l3_is_active(v4))) return false; + spec->l3_id = v4->l3_id; + spec->ifindex = v4->l2->ifindex; + return true; + } + + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_ip(spec->ip); + if (!v6 || !v6->l2 || (protocol == PROTO_TCP ? !ipv6_l3_is_tcp_usable(v6) : !ipv6_l3_is_ready(v6))) return false; + spec->l3_id = v6->l3_id; + spec->ifindex = v6->l2->ifindex; + return true; +} + +uint8_t socket_bind_match_score(const SockBindSpec* spec, ip_version_t ver, l3_id_t l3_id, uint8_t ifindex, const void* ip_addr) { + if (!spec || !ip_addr || (ver != IP_VER4 && ver != IP_VER6)) return 0; + if ((spec->kind == BIND_L3 || spec->kind == BIND_L2) && spec->ver && spec->ver != ver) return 0; + + switch (spec->kind) { + case BIND_IP: + if (spec->ver != ver) return 0; + return memcmp(spec->ip, ip_addr, ver == IP_VER6 ? 16 : 4) == 0 ? 5 : 0; + case BIND_L3: + return spec->l3_id == l3_id ? 4 : 0; + case BIND_L2: + return spec->ifindex == ifindex ? 3 : 0; + case BIND_ANY4: + return ver == IP_VER4 ? 2 : 0; + case BIND_ANY6: + return ver == IP_VER6 ? 2 : 0; + case BIND_ANY: + return 1; + default: + return 0; + } +} + +static bool socket_bind_specs_overlap(const SockBindSpec* a, const SockBindSpec* b) { + if (!a || !b) return false; + + ip_version_t af = a->kind == BIND_ANY4 ? IP_VER4 : a->kind == BIND_ANY6 ? IP_VER6 : a->ver; + ip_version_t bf = b->kind == BIND_ANY4 ? IP_VER4 : b->kind == BIND_ANY6 ? IP_VER6 : b->ver; + if (af && bf && af != bf) return false; + if (a->kind == BIND_ANY || b->kind == BIND_ANY) return true; + if (a->kind == BIND_ANY4 || a->kind == BIND_ANY6 || b->kind == BIND_ANY4 || b->kind == BIND_ANY6) return true; + + if (a->kind == b->kind) { + if (a->kind == BIND_L3) return a->l3_id == b->l3_id; + if (a->kind == BIND_L2) return a->ifindex == b->ifindex; + if (a->kind == BIND_IP) return af == IP_VER4 ? memcmp(a->ip, b->ip, 4) == 0 : memcmp(a->ip, b->ip, 16) == 0; + return true; + } + + if (a->kind != BIND_L2 && b->kind != BIND_L2 && a->l3_id && b->l3_id) return a->l3_id == b->l3_id; + return a->ifindex && b->ifindex && a->ifindex == b->ifindex; +} + +static bool socket_bind_insert_prepared(ksocket_t* socket, protocol_t protocol, const SockBindSpec* spec, uint16_t port, uint32_t options, bool allow_reuse, socket_bind_token_t* out_token) { + if (!bind_ready) { + irq_flags_t init_irq = irq_save_disable(); + if (!bind_ready) { + if (!fixed_hash_init(&bind_map, bind_groups, bind_buckets, SOCKET_BIND_MAP_CAPACITY, SOCKET_BIND_MAX)) { + irq_restore(init_irq); + return false; + } + bind_next_alloc = 0; + bind_used = 0; + for (uint32_t i = 0; i < SOCKET_BIND_MAX; i++) bind_entries[i].next = SOCKET_BIND_LIST_END; + bind_ready = true; + } + irq_restore(init_irq); + } + + uint8_t flags = 0; + if (options & SOCK_OPT_REUSEADDR) flags |= SOCKET_BIND_FLAG_REUSEADDR; + if (protocol == PROTO_UDP && (options & SOCK_OPT_REUSEPORT)) flags |= SOCKET_BIND_FLAG_REUSEPORT; + irq_flags_t irq = irq_save_disable(); + if (bind_used == SOCKET_BIND_MAX) { + irq_restore(irq); + return false; + } + + if (protocol == PROTO_TCP && tcp_bind_conflicts(spec, port, allow_reuse && (flags & SOCKET_BIND_FLAG_REUSEADDR))) { + irq_restore(irq); + return false; + } + + uint32_t group_key = socket_bind_key(protocol, port); + uint16_t* head_slot = fixed_hash_find_value_ptr(&bind_map, group_key); + uint16_t head = head_slot ? *head_slot : SOCKET_BIND_LIST_END; + for (uint16_t idx = head; idx != SOCKET_BIND_LIST_END; idx = bind_entries[idx].next) { + if (idx >= SOCKET_BIND_MAX) { + irq_restore(irq); + //kprintf("inv chain idx %u ins", (unsigned)idx); + return false; + } + socket_bind_entry_t* other = &bind_entries[idx]; + if (!other->socket || other->port != port) continue; + if (!socket_bind_specs_overlap(spec, &other->spec)) continue; + + bool allowed = allow_reuse && (flags & SOCKET_BIND_FLAG_REUSEADDR) && (other->flags & SOCKET_BIND_FLAG_REUSEADDR); + if (protocol == PROTO_UDP && !allowed) { + allowed = allow_reuse && socket_core_pid(socket) == socket_core_pid(other->socket) && (flags & SOCKET_BIND_FLAG_REUSEPORT) && (other->flags & SOCKET_BIND_FLAG_REUSEPORT); + } + if (protocol == PROTO_TCP && (other->flags & SOCKET_BIND_FLAG_LISTENING)) allowed = false; + if (!allowed) { + irq_restore(irq); + return false; + } + } + + uint16_t idx = SOCKET_BIND_LIST_END; + for (uint32_t i = 0; i < SOCKET_BIND_MAX; ++i) { + uint16_t cand = (uint16_t)((bind_next_alloc + i) % SOCKET_BIND_MAX); + if (bind_entries[cand].socket) continue; + idx = cand; + bind_next_alloc = (uint16_t)((cand + 1) % SOCKET_BIND_MAX); + break; + } + if (idx == SOCKET_BIND_LIST_END) { + irq_restore(irq); + return false; + } + + socket_bind_entry_t* entry = &bind_entries[idx]; + uint16_t generation = (uint16_t)(entry->generation + 1); + if (!generation) generation = 1; + memset(entry, 0, sizeof(*entry)); + entry->port = port; + entry->generation = generation; + entry->flags = flags; + entry->spec = *spec; + entry->socket = socket; + entry->next = head; + if (head_slot) *head_slot = idx; + else if (!fixed_hash_put(&bind_map, group_key, idx)) { + memset(entry, 0, sizeof(*entry)); + entry->generation = generation; + entry->next = SOCKET_BIND_LIST_END; + bind_next_alloc = idx; + //size_t groups = bind_map.size; + irq_restore(irq); + //kprintf("group ins fail proto=%u port=%u groups=%u", (unsigned)protocol, (unsigned)port, (unsigned)groups); + return false; + } + + bind_used++; + socket_core_ref(socket); + if (out_token) *out_token = ((uint32_t)generation << 16) | (uint32_t)(idx + 1); + + irq_restore(irq); + return true; +} + +bool socket_bind_insert(ksocket_t* socket, protocol_t protocol, SockBindSpec* spec, uint16_t port, uint32_t options, bool allow_reuse, socket_bind_token_t* out_token) { + if (out_token) *out_token = 0; + if (!socket || !spec || !port) return false; + if (!socket_bind_prepare_spec(spec, protocol)) return false; + return socket_bind_insert_prepared(socket, protocol, spec, port, options, allow_reuse, out_token); +} + +static bool socket_bind_decode_token(socket_bind_token_t token, uint16_t* idx, uint16_t* generation) { + uint16_t index = (uint16_t)token - 1; + uint16_t gen = (uint16_t)(token >> 16); + if (index >= SOCKET_BIND_MAX || !gen) return false; + + *idx = index; + *generation = gen; + return true; +} + +bool socket_bind_tcp_listen(socket_bind_token_t token) { + uint16_t idx; + uint16_t generation; + if (!socket_bind_decode_token(token, &idx, &generation)) return false; + if (!bind_ready) return false; + irq_flags_t irq = irq_save_disable(); + socket_bind_entry_t* entry = &bind_entries[idx]; + if (!entry->socket || entry->generation != generation || socket_core_protocol(entry->socket) != PROTO_TCP) { + irq_restore(irq); + return false; + } + if (entry->flags & SOCKET_BIND_FLAG_LISTENING) { + irq_restore(irq); + return true; + } + + uint16_t head = SOCKET_BIND_LIST_END; + fixed_hash_find(&bind_map, socket_bind_key(PROTO_TCP, entry->port), &head); + for (uint16_t other_idx = head; other_idx != SOCKET_BIND_LIST_END; other_idx = bind_entries[other_idx].next) { + if (other_idx >= SOCKET_BIND_MAX) { + irq_restore(irq); + //kprintf("inv chain idx %u listen", (unsigned)other_idx); + return false; + } + socket_bind_entry_t* other = &bind_entries[other_idx]; + if (other_idx == idx || !other->socket) continue; + if (!(other->flags & SOCKET_BIND_FLAG_LISTENING) || other->port != entry->port) continue; + if (!socket_bind_specs_overlap(&entry->spec, &other->spec)) continue; + irq_restore(irq); + return false; + } + + entry->flags |= SOCKET_BIND_FLAG_LISTENING; + irq_restore(irq); + return true; +} + +void socket_bind_remove(socket_bind_token_t token) { + uint16_t idx; + uint16_t generation; + if (!socket_bind_decode_token(token, &idx, &generation)) return; + ksocket_t* drop = NULL; + + if (!bind_ready) return; + + irq_flags_t irq = irq_save_disable(); //TODO lock + socket_bind_entry_t* e = &bind_entries[idx]; + if (e->socket && e->generation == generation) { + protocol_t protocol = socket_core_protocol(e->socket); + uint32_t group_key = socket_bind_key(protocol, e->port); + uint16_t* head_slot = fixed_hash_find_value_ptr(&bind_map, group_key); + uint16_t head = head_slot ? *head_slot : SOCKET_BIND_LIST_END; + uint16_t cur = head; + uint16_t prev = SOCKET_BIND_LIST_END; + bool removed = false; + while (cur != SOCKET_BIND_LIST_END) { + if (cur >= SOCKET_BIND_MAX) { + irq_restore(irq); + //kprintf("inv chain idx %u remove", (unsigned)cur); + return; + } + if (cur == idx) { + if (prev == SOCKET_BIND_LIST_END) { + head = bind_entries[cur].next; + if (head == SOCKET_BIND_LIST_END) removed = fixed_hash_remove(&bind_map, group_key); + else if (head_slot) { + *head_slot = head; + removed = true; + } + } else { + bind_entries[prev].next = bind_entries[cur].next; + removed = true; + } + break; + } + prev = cur; + cur = bind_entries[cur].next; + } + + if (removed) { + bind_used--; + drop = e->socket; + memset(e, 0, sizeof(*e)); + e->generation = generation; + e->next = SOCKET_BIND_LIST_END; + bind_next_alloc = idx; + } + } + irq_restore(irq); + + if (drop) socket_core_put(drop); +} + +void socket_bind_udp_set_remote(socket_bind_token_t token, const net_l4_endpoint* remote) { + uint16_t idx; + uint16_t generation; + if (!socket_bind_decode_token(token, &idx, &generation)) return; + + irq_flags_t irq = irq_save_disable(); + socket_bind_entry_t* e = &bind_entries[idx]; + if (e->socket && e->generation == generation && socket_core_protocol(e->socket) == PROTO_UDP) { + memset(&e->remote, 0, sizeof(e->remote)); + if (remote && remote->port && (remote->ver == IP_VER4 || remote->ver == IP_VER6)) e->remote = *remote; + } + irq_restore(irq); +} + +int32_t socket_bind_alloc_ephemeral(ksocket_t* socket, protocol_t protocol, SockBindSpec* spec, uint32_t options, socket_bind_token_t* out_token) { + if (out_token) *out_token = 0; + if (!socket || !spec || !socket_bind_prepare_spec(spec, protocol)) return -1; + + irq_flags_t irq = irq_save_disable(); + bool full = bind_ready && bind_used == SOCKET_BIND_MAX; + irq_restore(irq); + if (full) return -1; + + rng_t rng; + rng_init_random(&rng); + uint32_t seed = rng_next32(&rng); + + uint32_t minp = SOCKET_PORT_MIN_EPHEMERAL; + uint32_t maxp = SOCKET_PORT_MAX_EPHEMERAL; + uint32_t range = maxp - minp + 1; + uint32_t first = minp + (seed % range); + + for (uint32_t i = 0; i < range; ++i) { + uint16_t port = (uint16_t)(minp + ((first - minp + i) % range)); + if (socket_bind_insert_prepared(socket, protocol, spec, port, options, false, out_token)) return port; + } + + return -1; +} + +int32_t socket_bind_alloc_ephemeral_l3(ksocket_t* socket, protocol_t protocol, l3_id_t l3_id, uint32_t options, SockBindSpec* out_spec, socket_bind_token_t* out_token) { + if (!socket || !l3_id || !out_spec) return -1; + + SockBindSpec spec; + memset(&spec, 0, sizeof(spec)); + spec.kind = BIND_L3; + if (l3_ipv4_find_by_id(l3_id)) spec.ver = IP_VER4; + else if (l3_ipv6_find_by_id(l3_id)) spec.ver = IP_VER6; + else return -1; + spec.l3_id = l3_id; + + int32_t port = socket_bind_alloc_ephemeral(socket, protocol, &spec, options, out_token); + if (port >= 0) *out_spec = spec; + return port; +} + +ksocket_t* socket_bind_lookup(protocol_t protocol, ip_version_t ipver, l3_id_t l3_id, uint8_t ifindex, const void* src_ip_addr, uint16_t src_port, const void* dst_ip_addr, uint16_t dst_port) { + if (!dst_ip_addr || (protocol != PROTO_TCP && protocol != PROTO_UDP)) return NULL; + if (ipver != IP_VER4 && ipver != IP_VER6) return NULL; + if (!bind_ready) return NULL; + + uint32_t ip_len = ipver == IP_VER6 ? 16u : 4u; + uint8_t reuseport_key[1 + sizeof(src_port) + sizeof(dst_port) + 32]; + uint32_t reuseport_key_len = 0; + uint64_t reuseport_base = 0; + + irq_flags_t irq = irq_save_disable(); //TODO lock + uint16_t head = SOCKET_BIND_LIST_END; + fixed_hash_find(&bind_map, socket_bind_key(protocol, dst_port), &head); + uint8_t best_score = 0; + bool reuseport_group = false; + uint16_t reuseport_owner = 0; + uint64_t reuseport_hash = 0; + socket_bind_entry_t* first = NULL; + socket_bind_entry_t* reuseport_selected = NULL; + + for (uint16_t idx = head; idx != SOCKET_BIND_LIST_END; idx = bind_entries[idx].next) { + if (idx >= SOCKET_BIND_MAX) { + irq_restore(irq); + //kprintf("inv chain idx %u lookup", (unsigned)idx); + return NULL; + } + socket_bind_entry_t* e = &bind_entries[idx]; + if (!e->socket || e->port != dst_port || socket_core_is_closing(e->socket)) continue; + if (protocol == PROTO_TCP && !(e->flags & SOCKET_BIND_FLAG_LISTENING)) continue; + + uint8_t score = socket_bind_match_score(&e->spec, ipver, l3_id, ifindex, dst_ip_addr); + if (!score) continue; + + if (protocol == PROTO_UDP && e->remote.port) { + if (!src_ip_addr || e->remote.ver != ipver || e->remote.port != src_port) continue; + if (memcmp(e->remote.ip, src_ip_addr, ip_len) != 0) continue; + score += 8; + } + if (score < best_score) continue; + + bool reuseport = protocol == PROTO_UDP && (e->flags & SOCKET_BIND_FLAG_REUSEPORT); + if (score > best_score) { + best_score = score; + reuseport_group = reuseport; + reuseport_owner = socket_core_pid(e->socket); + reuseport_hash = 0; + first = e; + reuseport_selected = NULL; + } else if (!reuseport || socket_core_pid(e->socket) != reuseport_owner) reuseport_group = false; + if (reuseport_group && reuseport) { + if (!reuseport_key_len) { + reuseport_key[reuseport_key_len++] = (uint8_t)ipver; + memcpy(reuseport_key + reuseport_key_len, &src_port, sizeof(src_port)); + reuseport_key_len += sizeof(src_port); + memcpy(reuseport_key + reuseport_key_len, &dst_port, sizeof(dst_port)); + reuseport_key_len += sizeof(dst_port); + if (src_ip_addr) memcpy(reuseport_key + reuseport_key_len, src_ip_addr, ip_len); + else memset(reuseport_key + reuseport_key_len, 0, ip_len); + reuseport_key_len += ip_len; + memcpy(reuseport_key + reuseport_key_len, dst_ip_addr, ip_len); + reuseport_key_len += ip_len; + reuseport_base = hash_map_fnv1a64(reuseport_key, reuseport_key_len); + } + + socket_handle_t handle = socket_core_export_handle(e->socket); + uint64_t hash = reuseport_base ^ ((uint64_t)handle * 0x9E3779B97F4A7C15ULL); + hash = (hash ^ (hash >> 30)) * 0xBF51476C1CE40DB9ULL; + hash = (hash ^ (hash >> 27)) * 0x94D049BB133111EBULL; + hash ^= hash >> 31; + if (!reuseport_selected || hash > reuseport_hash) { + reuseport_selected = e; + reuseport_hash = hash; + } + } + } + + socket_bind_entry_t* selected = reuseport_group && reuseport_selected ? reuseport_selected : first; + ksocket_t* socket = selected ? selected->socket : NULL; + if (socket) socket_core_ref(socket); + irq_restore(irq); + return socket; +} + +ksocket_t* socket_bind_udp_next_fanout(ip_version_t ipver, l3_id_t l3_id, uint8_t ifindex, const void* dst_ip_addr, uint16_t dst_port, bool include_shared, socket_bind_iter_t* iter) { + if (!iter || !dst_ip_addr || (ipver != IP_VER4 && ipver != IP_VER6)) return NULL; + + uint32_t start = iter->next; + if (start >= SOCKET_BIND_MAX) return NULL; + + irq_flags_t irq = irq_save_disable(); + uint16_t head = SOCKET_BIND_LIST_END; + if (!bind_ready || (!iter->scan && !fixed_hash_find(&bind_map, socket_bind_key(PROTO_UDP, dst_port), &head))) { + iter->next = SOCKET_BIND_MAX; + irq_restore(irq); + return NULL; + } + + uint16_t best = SOCKET_BIND_LIST_END ; + if (iter->scan) { + for (uint32_t i = start; i < SOCKET_BIND_MAX; i++) { + socket_bind_entry_t* e = &bind_entries[i]; + if (!e->socket || e->port != dst_port || socket_core_protocol(e->socket) != PROTO_UDP || socket_core_is_closing(e->socket)) continue; + if (!include_shared && e->spec.kind != BIND_L3) continue; + if (!socket_bind_match_score(&e->spec, ipver, l3_id, ifindex, dst_ip_addr)) continue; + best = (uint16_t)i; + break; + } + } else { + for (uint16_t i = head; i != SOCKET_BIND_LIST_END; i = bind_entries[i].next) { + if (i >= SOCKET_BIND_MAX) break; + socket_bind_entry_t* e = &bind_entries[i]; + if (i < start || i >= best || !e->socket || e->port != dst_port || socket_core_is_closing(e->socket)) continue; + if (!include_shared && e->spec.kind != BIND_L3) continue; + if (!socket_bind_match_score(&e->spec, ipver, l3_id, ifindex, dst_ip_addr)) continue; + best = i; + } + } + + ksocket_t* socket = best != SOCKET_BIND_LIST_END ? bind_entries[best].socket : NULL; + if (socket) { + iter->next = (uint16_t)(best + 1); + if (iter->hits < BIND_FANOUT_HASH_MAX) iter->hits++; + if (iter->hits == BIND_FANOUT_HASH_MAX) iter->scan = true; + socket_core_ref(socket); + } else iter->next = SOCKET_BIND_MAX; + irq_restore(irq); + return socket; +} + +static bool socket_bind_build_tx_opts(const SockBindSpec* spec, ip_version_t ver, ip_tx_opts_t* tx, const ip_tx_opts_t** hint) { + if (!spec || !tx || !hint || (ver != IP_VER4 && ver != IP_VER6)) return false; + + SockBindSpec normal = *spec; + if (!socket_bind_normalize_spec(&normal)) return false; + if ((normal.kind == BIND_ANY4 && ver != IP_VER4) || (normal.kind == BIND_ANY6 && ver != IP_VER6)) return false; + if (normal.ver && normal.ver != ver && normal.kind != BIND_ANY && normal.kind != BIND_ANY4 && normal.kind != BIND_ANY6) return false; + + memset(tx, 0, sizeof(*tx)); + *hint = NULL; + if (normal.kind == BIND_ANY || normal.kind == BIND_ANY4 || normal.kind == BIND_ANY6) return true; + if (normal.kind == BIND_L2) { + l2_interface_t* l2 = l2_interface_find_by_index(normal.ifindex); + if (!l2_interface_is_operational(l2)) return false; + tx->scope = IP_TX_BOUND_L2; + tx->target.ifindex = normal.ifindex; + *hint = tx; + return true; + } + + l3_id_t l3_id = normal.l3_id; + if (normal.kind == BIND_IP) { + if (ver == IP_VER4) { + uint32_t ip = 0; + memcpy(&ip, normal.ip, 4); + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(ip); + if (!v4 || !v4->l2) return false; + l3_id = v4->l3_id; + } else { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_ip(normal.ip); + if (!v6 || !v6->l2) return false; + l3_id = v6->l3_id; + } + } else if (normal.kind != BIND_L3) return false; + + if (ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); + if (!v4 || !v4->l2) return false; + } else { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (!v6 || !v6->l2) return false; + } + tx->scope = IP_TX_BOUND_L3; + tx->target.l3_id = l3_id; + *hint = tx; + return true; +} + +bool socket_bind_build_ipv4_tx_plan(const SockBindSpec* spec, bool use_spec, uint32_t dst, ipv4_tx_plan_t* out) { + if (!out) return false; + + ip_tx_opts_t tx; + const ip_tx_opts_t* hint = NULL; + if (use_spec && !socket_bind_build_tx_opts(spec, IP_VER4, &tx, &hint)) return false; + + if (!ipv4_build_tx_plan(dst, hint, out)) return false; + return ipv4_tx_plan_valid(out); +} + +bool socket_bind_build_ipv6_tx_plan(const SockBindSpec* spec, bool use_spec, const uint8_t dst[16], ipv6_tx_plan_t* out) { + if (!out || !dst) return false; + + ip_tx_opts_t tx; + const ip_tx_opts_t* hint = NULL; + if (use_spec && !socket_bind_build_tx_opts(spec, IP_VER6, &tx, &hint)) return false; + + if (!ipv6_build_tx_plan(dst, hint, out)) return false; + return ipv6_tx_plan_valid(out); +} \ No newline at end of file diff --git a/kernel/networking/transport_layer/socket_bind.h b/kernel/networking/transport_layer/socket_bind.h new file mode 100644 index 00000000..74f52f2d --- /dev/null +++ b/kernel/networking/transport_layer/socket_bind.h @@ -0,0 +1,40 @@ +#pragma once + +#include "types.h" +#include "net/network_types.h" +#include "net/socket_types.h" +#include "networking/transport_layer/socket_core.h" +#include "networking/internet_layer/ipv4_route.h" +#include "networking/internet_layer/ipv6_route.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SOCKET_BIND_MAX 1024 +typedef uint32_t socket_bind_token_t; + +bool socket_bind_prepare_spec(SockBindSpec* spec, protocol_t protocol); +uint8_t socket_bind_match_score(const SockBindSpec* spec, ip_version_t ver, l3_id_t l3_id, uint8_t ifindex, const void* ip_addr); +bool socket_bind_insert(ksocket_t* socket, protocol_t protocol, SockBindSpec* spec, uint16_t port, uint32_t options, bool allow_reuse, socket_bind_token_t* out_token); +bool socket_bind_tcp_listen(socket_bind_token_t token); +void socket_bind_remove(socket_bind_token_t token); +void socket_bind_udp_set_remote(socket_bind_token_t token, const net_l4_endpoint* remote); +int32_t socket_bind_alloc_ephemeral(ksocket_t* socket, protocol_t protocol, SockBindSpec* spec, uint32_t options, socket_bind_token_t* out_token); +int32_t socket_bind_alloc_ephemeral_l3(ksocket_t* socket, protocol_t protocol, l3_id_t l3_id, uint32_t options, SockBindSpec* out_spec, socket_bind_token_t* out_token); + +ksocket_t* socket_bind_lookup(protocol_t protocol, ip_version_t ipver, l3_id_t l3_id, uint8_t ifindex, const void* src_ip_addr, uint16_t src_port, const void* dst_ip_addr, uint16_t dst_port); +typedef struct { + uint16_t next; + uint8_t hits; + bool scan; +} socket_bind_iter_t; + +ksocket_t* socket_bind_udp_next_fanout(ip_version_t ipver, l3_id_t l3_id, uint8_t ifindex, const void* dst_ip_addr, uint16_t dst_port, bool include_shared, socket_bind_iter_t* iter); + +bool socket_bind_build_ipv4_tx_plan(const SockBindSpec* spec, bool use_spec, uint32_t dst, ipv4_tx_plan_t* out); +bool socket_bind_build_ipv6_tx_plan(const SockBindSpec* spec, bool use_spec, const uint8_t dst[16], ipv6_tx_plan_t* out); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/socket_core.c b/kernel/networking/transport_layer/socket_core.c new file mode 100644 index 00000000..64c545de --- /dev/null +++ b/kernel/networking/transport_layer/socket_core.c @@ -0,0 +1,445 @@ +#include "socket_core.h" +#include "socket_bind.h" +#include "csocket_udp.h" +#include "csocket_tcp.h" +#include "csocket_packet.h" +#include "csocket_raw.h" +#include "exceptions/irq.h" +#include "std/memory.h" +#include "alloc/allocate.h" + +struct ksocket { + uint32_t id; + uint64_t generation; + uint16_t pid; + protocol_t protocol; + SocketSpecialKind special_kind; + socket_impl_t impl; + socket_impl_destroy_fn destroy; + socket_impl_close_fn close; + socket_impl_setopt_fn setopt; + socket_impl_getopt_fn getopt; + int32_t refs; + bool closing; + bool visible; +}; + +static ksocket_t* sockets[SOCKET_MAX_OPEN]; +static uint64_t generations[SOCKET_MAX_OPEN]; + +bool socket_core_alloc(protocol_t protocol, SocketSpecialKind special_kind, uint16_t pid, ksocket_t** out_socket) { + if (!out_socket) return false; + + ksocket_t* socket = (ksocket_t*)zalloc(sizeof(ksocket_t)); + if (!socket) return false; + + irq_flags_t irq = irq_save_disable(); //TODO lock + + uint32_t id = SOCKET_MAX_OPEN; + for (uint32_t i = 1; i < SOCKET_MAX_OPEN; ++i) { + if (!sockets[i]) { + id = i; + break; + } + } + + if (id == SOCKET_MAX_OPEN) { + irq_restore(irq); + release(socket); + return false; + } + + uint64_t gen = generations[id] + 1; + if (!gen) gen = 1; + generations[id] = gen; + + socket->id = id; + socket->generation = gen; + socket->pid = pid; + socket->protocol = protocol; + socket->special_kind = special_kind; + socket->refs = 1; + sockets[id] = socket; + + irq_restore(irq); + + *out_socket = socket; + return true; +} + +bool socket_core_attach_impl(ksocket_t* socket, socket_impl_t impl, socket_impl_destroy_fn destroy, socket_impl_close_fn close, socket_impl_setopt_fn setopt, socket_impl_getopt_fn getopt) { + if (!socket || !impl || !destroy || !close) return false; + irq_flags_t irq = irq_save_disable(); //TODO lock + if (socket->closing || socket->visible || socket->impl) { + irq_restore(irq); + return false; + } + + socket->impl = impl; + socket->destroy = destroy; + socket->close = close; + socket->setopt = setopt; + socket->getopt = getopt; + socket->visible = true; + irq_restore(irq); + + return true; +} + +ksocket_t* socket_core_get(socket_handle_t handle, uint16_t pid) { + if (!handle) return NULL; + + uint64_t index_mask = ((uint64_t)1 << SOCKET_HANDLE_INDEX_BITS) - 1; + uint32_t id = (uint32_t)(handle & index_mask); + uint64_t generation = handle >> SOCKET_HANDLE_INDEX_BITS; + if (!id || id >= SOCKET_MAX_OPEN || !generation) return NULL; + + irq_flags_t irq = irq_save_disable(); //TODO lock + ksocket_t* socket = sockets[id]; + if (!socket || !socket->visible || socket->closing || socket->generation != generation || (pid && socket->pid != pid)) { + irq_restore(irq); + return NULL; + } + socket->refs++; + irq_restore(irq); + return socket; +} + +void socket_core_ref(ksocket_t* socket) { + if (!socket) return; + irq_flags_t irq = irq_save_disable(); //TODO lock + socket->refs++; + irq_restore(irq); +} + +void socket_core_put(ksocket_t* socket) { + if (!socket) return; + + bool do_destroy = false; + irq_flags_t irq = irq_save_disable(); + if (socket->refs > 0) socket->refs--; + if (socket->refs == 0 && socket->closing) do_destroy = true; + irq_restore(irq); + + if (!do_destroy) return; + + socket_impl_t impl = socket->impl; + socket_impl_destroy_fn destroy = socket->destroy; + socket->impl = NULL; + socket->destroy = NULL; + socket->close = NULL; + socket->setopt = NULL; + socket->getopt = NULL; + + if (destroy && impl) destroy(impl); + release(socket); +} + +int32_t socket_core_close_socket(ksocket_t* socket) { + if (!socket) return SOCK_ERR_INVAL; + + irq_flags_t irq = irq_save_disable(); + if (socket->closing) { + irq_restore(irq); + return SOCK_OK; + } + socket->closing = true; + irq_restore(irq); + + int32_t ret = SOCK_OK; + if (socket->close && socket->impl) ret = socket->close(socket->impl); + + if (ret == SOCK_ERR_WOULDBLOCK) { + irq = irq_save_disable(); + socket->closing = false; + irq_restore(irq); + return ret; + } + + irq = irq_save_disable(); + socket->visible = false; + if (socket->id < SOCKET_MAX_OPEN && sockets[socket->id] == socket) sockets[socket->id] = NULL; + irq_restore(irq); + + socket_core_put(socket); + return ret; +} + +void socket_core_close_process(uint16_t pid) { + if (!pid) return; + + while (true) { + ksocket_t* socket = NULL; + irq_flags_t irq = irq_save_disable(); + for (uint32_t i = 1; i < SOCKET_MAX_OPEN; i++) { + if (!sockets[i] || sockets[i]->pid != pid) continue; + socket = sockets[i]; + socket->refs++; + break; + } + irq_restore(irq); + + if (!socket) break; + + uint32_t nonblock = 1; + socket_core_set_option(socket, SOCK_OPT_NONBLOCK, &nonblock, sizeof(nonblock)); + + int32_t rc = socket_core_close_socket(socket); + if (rc == SOCK_ERR_WOULDBLOCK) { + SocketLinger linger = {0}; + if (socket_core_set_option(socket, SOCK_OPT_LINGER, &linger, sizeof(linger)) == SOCK_OK) rc = socket_core_close_socket(socket); + } + socket_core_put(socket); + if (rc == SOCK_ERR_WOULDBLOCK) break; + } +} + +int32_t socket_core_close_handle(socket_handle_t handle, uint16_t pid) { + ksocket_t* socket = socket_core_get(handle, pid); + if (!socket) return SOCK_ERR_INVAL; + int32_t ret = socket_core_close_socket(socket); + socket_core_put(socket); + return ret; +} + +int32_t socket_core_set_option(ksocket_t* socket, int32_t opt, const void* value, uint32_t len) { + if (!socket) return SOCK_ERR_INVAL; + if (!value && len) return SOCK_ERR_INVAL; + if (!socket->setopt || !socket->impl) return SOCK_ERR_PROTO; + return socket->setopt(socket->impl, opt, value, len); +} + +int32_t socket_core_get_option(ksocket_t* socket, int32_t opt, void* value, uint32_t* len) { + if (!socket || !len) return SOCK_ERR_INVAL; + if (!socket->getopt || !socket->impl) return SOCK_ERR_PROTO; + + uint32_t v = 0; + if (opt == SOCK_GET_PROTOCOL) v = socket->protocol; + else if (opt == SOCK_GET_OWNER_PID) v = socket->pid; + else if (opt == SOCK_GET_SPECIAL_KIND) v = socket->special_kind; + else return socket->getopt(socket->impl, opt, value, len); + + return socket_common_get_value(&v, sizeof(v), value, len); +} + +int32_t socket_common_get_value(const void* data, uint32_t data_len, void* value, uint32_t* len) { + if ((!data && data_len) || !len) return SOCK_ERR_INVAL; + if (!value) { + *len = data_len; + return SOCK_OK; + } + if (*len < data_len) return SOCK_ERR_INVAL; + if (data_len) memcpy(value, data, data_len); + *len = data_len; + return SOCK_OK; +} + +int32_t socket_common_options_set(SocketOptions* opts, int32_t opt, const void* value, uint32_t len) { + if (!opts) return SOCK_ERR_INVAL; + + uint32_t v = 1; + if (value) { + if (len != sizeof(uint32_t)) return SOCK_ERR_INVAL; + memcpy(&v, value, sizeof(v)); + } else if (len || (opt != SOCK_OPT_DONTFRAG && opt != SOCK_OPT_BROADCAST_ALLOWED && opt != SOCK_OPT_PACKET_TRUNK)) return SOCK_ERR_INVAL; + + int32_t rc = SOCK_OK; + irq_flags_t irq = irq_save_disable(); + switch ((uint32_t)opt) { + case SOCK_OPT_RECV_TIMEOUT: + opts->recv_timeout_ms = v; + if (v) opts->flags |= SOCK_OPT_RECV_TIMEOUT; + else opts->flags &= ~SOCK_OPT_RECV_TIMEOUT; + break; + case SOCK_OPT_SEND_TIMEOUT: + opts->send_timeout_ms = v; + if (v) opts->flags |= SOCK_OPT_SEND_TIMEOUT; + else opts->flags &= ~SOCK_OPT_SEND_TIMEOUT; + break; + case SOCK_OPT_BUF_SIZE: + if (!v) rc = SOCK_ERR_INVAL; + else { + opts->buf_size = v; + opts->flags |= SOCK_OPT_BUF_SIZE; + } + break; + case SOCK_OPT_DEBUG: + if (v > SOCK_DBG_ALL) rc = SOCK_ERR_INVAL; + else { + opts->debug_level = (SockDebugLevel)v; + if (v) opts->flags |= SOCK_OPT_DEBUG; + else opts->flags &= ~SOCK_OPT_DEBUG; + } + break; + case SOCK_OPT_DONTFRAG: + if (v) opts->flags |= SOCK_OPT_DONTFRAG; + else opts->flags &= ~SOCK_OPT_DONTFRAG; + break; + case SOCK_OPT_BROADCAST_ALLOWED: + if (v) opts->flags |= SOCK_OPT_BROADCAST_ALLOWED; + else opts->flags &= ~SOCK_OPT_BROADCAST_ALLOWED; + break; + case SOCK_OPT_NONBLOCK: + if (v) opts->flags |= SOCK_OPT_NONBLOCK; + else opts->flags &= ~SOCK_OPT_NONBLOCK; + break; + case SOCK_OPT_PACKET_TRUNK: + if (v) opts->flags |= SOCK_OPT_PACKET_TRUNK; + else opts->flags &= ~SOCK_OPT_PACKET_TRUNK; + break; + + case SOCK_OPT_DONTROUTE: + if (v) opts->flags |= SOCK_OPT_DONTROUTE; + else opts->flags &= ~SOCK_OPT_DONTROUTE; + break; + case SOCK_OPT_REUSEADDR: + if (v) opts->flags |= SOCK_OPT_REUSEADDR; + else opts->flags &= ~SOCK_OPT_REUSEADDR; + break; + case SOCK_OPT_REUSEPORT: + if (v) opts->flags |= SOCK_OPT_REUSEPORT; + else opts->flags &= ~SOCK_OPT_REUSEPORT; + break; + case SOCK_OPT_TCP_SACK: + if (v) opts->flags |= SOCK_OPT_TCP_SACK; + else opts->flags &= ~(SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK); + break; + case SOCK_OPT_TCP_DSACK: + if (v) opts->flags |= SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK; + else opts->flags &= ~SOCK_OPT_TCP_DSACK; + break; + case SOCK_OPT_TTL: + if (v > 255) rc = SOCK_ERR_INVAL; + else { + opts->ttl = (uint8_t)v; + if (v) opts->flags |= SOCK_OPT_TTL; + else opts->flags &= ~SOCK_OPT_TTL; + } + break; + default: + rc = SOCK_ERR_INVAL; + break; + } + irq_restore(irq); + return rc; +} + +int32_t socket_common_options_get(const SocketOptions* opts, int32_t opt, void* value, uint32_t* len) { + if (!opts || !len) return SOCK_ERR_INVAL; + + uint32_t v = 0; + int32_t rc = SOCK_OK; + irq_flags_t irq = irq_save_disable(); + switch ((uint32_t)opt) { + case SOCK_GET_OPT_RECV_TIMEOUT: + v = opts->recv_timeout_ms; + break; + case SOCK_GET_OPT_SEND_TIMEOUT: + v = opts->send_timeout_ms; + break; + case SOCK_GET_OPT_BUF_SIZE: + v = opts->buf_size; + break; + case SOCK_GET_OPT_DEBUG: + v = opts->debug_level; + break; + case SOCK_GET_OPT_DONTFRAG: + v = (opts->flags & SOCK_OPT_DONTFRAG) != 0; + break; + case SOCK_GET_OPT_BROADCAST_ALLOWED: + v = (opts->flags & SOCK_OPT_BROADCAST_ALLOWED) != 0; + break; + case SOCK_GET_OPT_NONBLOCK: + v = (opts->flags & SOCK_OPT_NONBLOCK) != 0; + break; + case SOCK_GET_OPT_PACKET_TRUNK: + v = (opts->flags & SOCK_OPT_PACKET_TRUNK) != 0; + break; + case SOCK_GET_OPT_DONTROUTE: + v = (opts->flags & SOCK_OPT_DONTROUTE) != 0; + break; + case SOCK_GET_OPT_REUSEADDR: + v = (opts->flags & SOCK_OPT_REUSEADDR) != 0; + break; + case SOCK_GET_OPT_REUSEPORT: + v = (opts->flags & SOCK_OPT_REUSEPORT) != 0; + break; + case SOCK_GET_OPT_TCP_SACK: + v = (opts->flags & SOCK_OPT_TCP_SACK) != 0; + break; + case SOCK_GET_OPT_TCP_DSACK: + v = (opts->flags & SOCK_OPT_TCP_DSACK) != 0; + break; + case SOCK_GET_OPT_TTL: + v = opts->ttl; + break; + default: + rc = SOCK_ERR_INVAL; + break; + } + irq_restore(irq); + + if (rc != SOCK_OK) return rc; + return socket_common_get_value(&v, sizeof(v), value, len); +} + +socket_impl_t socket_core_impl(ksocket_t* socket) { + return socket ? socket->impl : NULL; +} + +protocol_t socket_core_protocol(const ksocket_t* socket) { + return socket ? socket->protocol : PROTO_NONE; +} + +SocketSpecialKind socket_core_special_kind(const ksocket_t* socket) { + return socket ? socket->special_kind : SOCKET_SPECIAL_NONE; +} + +uint16_t socket_core_pid(const ksocket_t* socket) { + return socket ? socket->pid : 0; +} + + +bool socket_core_is_closing(const ksocket_t* socket) { + return !socket || socket->closing; +} + +socket_handle_t socket_core_export_handle(const ksocket_t* socket) { + if (!socket || !socket->id || !socket->generation) return 0; + return (socket->generation << SOCKET_HANDLE_INDEX_BITS) | socket->id; +} + +void socket_core_l2_deleted(uint8_t ifindex, uint32_t generation) { + for (uint32_t i = 1; i < SOCKET_MAX_OPEN; i++) { + irq_flags_t irq = irq_save_disable(); + ksocket_t *s = sockets[i]; + bool handles_l2 = s && (s->protocol == PROTO_UDP || s->protocol == PROTO_TCP || s->special_kind == SOCKET_SPECIAL_PACKET || s->special_kind == SOCKET_SPECIAL_RAW); + if (handles_l2 && s->impl && !s->closing) s->refs++; + else s = 0; + irq_restore(irq); + if (!s) continue; + + if (s->special_kind == SOCKET_SPECIAL_PACKET) socket_packet_l2_deleted(s->impl, ifindex, generation); + else if (s->special_kind == SOCKET_SPECIAL_RAW) socket_raw_l2_deleted(s->impl, ifindex, generation); + else if (s->protocol == PROTO_UDP) socket_udp_l2_deleted(s->impl, ifindex, generation); + else socket_tcp_l2_deleted(s->impl, ifindex, generation); + socket_core_put(s); + } +} + +void socket_core_l3_deleted(l3_id_t l3_id) { + for (uint32_t i = 1; i < SOCKET_MAX_OPEN; i++) { + irq_flags_t irq = irq_save_disable(); + ksocket_t *s = sockets[i]; + bool handles_l3 = s && (s->protocol == PROTO_UDP || s->protocol == PROTO_TCP || s->special_kind == SOCKET_SPECIAL_RAW); + if (handles_l3 && s->impl && !s->closing) s->refs++; + else s = 0; + irq_restore(irq); + if (!s) continue; + + if (s->special_kind == SOCKET_SPECIAL_RAW) socket_raw_l3_deleted(s->impl, l3_id); + else if (s->protocol == PROTO_UDP) socket_udp_l3_deleted(s->impl, l3_id); + else socket_tcp_l3_deleted(s->impl, l3_id); + socket_core_put(s); + } +} diff --git a/kernel/networking/transport_layer/socket_core.h b/kernel/networking/transport_layer/socket_core.h new file mode 100644 index 00000000..a854f5a0 --- /dev/null +++ b/kernel/networking/transport_layer/socket_core.h @@ -0,0 +1,47 @@ +#pragma once + +#include "types.h" +#include "net/network_types.h" +#include "net/socket_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* socket_impl_t; +typedef struct ksocket ksocket_t; + +typedef void (*socket_impl_destroy_fn)(socket_impl_t impl); +typedef int32_t (*socket_impl_close_fn)(socket_impl_t impl); +typedef int32_t (*socket_impl_setopt_fn)(socket_impl_t impl, int32_t opt, const void* value, uint32_t len); +typedef int32_t (*socket_impl_getopt_fn)(socket_impl_t impl, int32_t opt, void* value, uint32_t* len); + +#define SOCKET_MAX_OPEN 2048 +#define SOCKET_HANDLE_INDEX_BITS 20 + +bool socket_core_alloc(protocol_t protocol, SocketSpecialKind special_kind, uint16_t pid, ksocket_t** out_socket); +bool socket_core_attach_impl(ksocket_t* socket, socket_impl_t impl, socket_impl_destroy_fn destroy, socket_impl_close_fn close, socket_impl_setopt_fn setopt, socket_impl_getopt_fn getopt); +ksocket_t* socket_core_get(socket_handle_t handle, uint16_t pid); +void socket_core_ref(ksocket_t* socket); +void socket_core_put(ksocket_t* socket); +int32_t socket_core_close_handle(socket_handle_t handle, uint16_t pid); +int32_t socket_core_close_socket(ksocket_t* socket); +void socket_core_close_process(uint16_t pid); +void socket_core_l2_deleted(uint8_t ifindex, uint32_t generation); +void socket_core_l3_deleted(l3_id_t l3_id); +int32_t socket_core_set_option(ksocket_t* socket, int32_t opt, const void* value, uint32_t len); +int32_t socket_core_get_option(ksocket_t* socket, int32_t opt, void* value, uint32_t* len); +int32_t socket_common_options_set(SocketOptions* opts, int32_t opt, const void* value, uint32_t len); +int32_t socket_common_options_get(const SocketOptions* opts, int32_t opt, void* value, uint32_t* len); +int32_t socket_common_get_value(const void* data, uint32_t data_len, void* value, uint32_t* len); + +socket_impl_t socket_core_impl(ksocket_t* socket); +protocol_t socket_core_protocol(const ksocket_t* socket); +SocketSpecialKind socket_core_special_kind(const ksocket_t* socket); +uint16_t socket_core_pid(const ksocket_t* socket); +bool socket_core_is_closing(const ksocket_t* socket); +socket_handle_t socket_core_export_handle(const ksocket_t* socket); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/socket_endpoint.c b/kernel/networking/transport_layer/socket_endpoint.c new file mode 100644 index 00000000..d468bd62 --- /dev/null +++ b/kernel/networking/transport_layer/socket_endpoint.c @@ -0,0 +1,42 @@ +#include "socket_endpoint.h" +#include "networking/transport_layer/trans_utils.h" +#include "std/memory.h" + +uint32_t socket_endpoint_resolve(const char* host, uint16_t port, dns_server_sel_t sel, uint32_t timeout_ms, net_l4_endpoint* out) { + if (!host || !port || !out) return 0; + + memset(out, 0, sizeof(net_l4_endpoint) * 2); + + uint32_t count = 0; + uint8_t v6addr[16]; + uint32_t v4addr = 0; + + memset(v6addr, 0, sizeof(v6addr)); + if (dns_resolve_aaaa(host, v6addr, sel, timeout_ms) == DNS_OK) { + out[count].ver = IP_VER6; + ipv6_cpy(out[count].ip, v6addr); + out[count].port = port; + count++; + } + + if (dns_resolve_a(host, &v4addr, sel, timeout_ms) == DNS_OK) { + make_ep(&v4addr, port, IP_VER4, &out[count]); + count++; + } + + return count; +} + +net_l4_endpoint socket_endpoint_select(const char* host, uint16_t port, ip_version_t preferred, dns_server_sel_t sel, uint32_t timeout_ms) { + net_l4_endpoint endpoints[2]; + memset(endpoints, 0, sizeof(endpoints)); + + uint32_t count = socket_endpoint_resolve(host, port, sel, timeout_ms, endpoints); + if (!count) return (net_l4_endpoint){}; + + if (preferred == IP_VER4 || preferred == IP_VER6) { + for (uint32_t i = 0; i < count; ++i) if (endpoints[i].ver == preferred) return endpoints[i]; + } + + return endpoints[0]; +} diff --git a/kernel/networking/transport_layer/socket_endpoint.h b/kernel/networking/transport_layer/socket_endpoint.h new file mode 100644 index 00000000..b7df79ef --- /dev/null +++ b/kernel/networking/transport_layer/socket_endpoint.h @@ -0,0 +1,16 @@ +#pragma once + +#include "types.h" +#include "net/network_types.h" +#include "networking/application_layer/dns/dns.h" + +#ifdef __cplusplus +extern "C" { +#endif + +uint32_t socket_endpoint_resolve(const char* host, uint16_t port, dns_server_sel_t sel, uint32_t timeout_ms, net_l4_endpoint* out); +net_l4_endpoint socket_endpoint_select(const char* host, uint16_t port, ip_version_t preferred, dns_server_sel_t sel, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/socket_tcp.hpp b/kernel/networking/transport_layer/socket_tcp.hpp deleted file mode 100755 index 2c749317..00000000 --- a/kernel/networking/transport_layer/socket_tcp.hpp +++ /dev/null @@ -1,724 +0,0 @@ -#pragma once - -#include "std/memory.h" -#include "std/string.h" -#include "socket.hpp" -#include "networking/transport_layer/tcp.h" -#include "networking/internet_layer/ipv4.h" -#include "networking/application_layer/dns/dns.h" -#include "types.h" -#include "data/struct/ring_buffer.hpp" -#include "net/socket_types.h" -#include "networking/internet_layer/ipv4_route.h" -#include "networking/internet_layer/ipv6_route.h" -#include "networking/internet_layer/ipv6_utils.h" -#include "networking/transport_layer/trans_utils.h" -#include "syscalls/syscalls.h" - -static constexpr int TCP_MAX_BACKLOG = 8; -static constexpr dns_server_sel_t TCP_DNS_SEL = DNS_USE_BOTH; -static constexpr uint32_t TCP_DNS_TIMEOUT_MS = 3000; - -class TCPSocket : public Socket { - inline static TCPSocket* s_list_head = nullptr; - - static constexpr uint32_t TCP_RING_CAP = 256 * 1024; - RingBuffer ring; - tcp_data* flow = nullptr; - - TCPSocket* pending[TCP_MAX_BACKLOG] = { nullptr }; - int backlogCap = 0; - int backlogLen = 0; - TCPSocket* next = nullptr; - - static bool is_valid_v4_l3_for_bind(l3_ipv4_interface_t* v4) { - if (!v4 || !v4->l2) return false; - if (!v4->l2->is_up) return false; - if (v4->is_localhost) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - if (v4->ip == 0) return false; - if (!v4->port_manager) return false; - return true; - } - - static bool is_valid_v6_l3_for_bind(l3_ipv6_interface_t* v6) { - if (!v6 || !v6->l2) return false; - if (!v6->l2->is_up) return false; - if (v6->is_localhost) return false; - if (v6->cfg == IPV6_CFG_DISABLE) return false; - if (ipv6_is_unspecified(v6->ip)) return false; - if (v6->dad_state == IPV6_DAD_FAILED) return false; - if (!(v6->kind & IPV6_ADDRK_LINK_LOCAL) && v6->dad_state != IPV6_DAD_OK) return false; - if (!v6->port_manager) return false; - return true; - } - - static uint32_t dispatch(uint8_t ifindex, ip_version_t ipver, const void* src_ip_addr, const void* dst_ip_addr, uintptr_t frame_ptr, uint32_t frame_len, uint16_t src_port, uint16_t dst_port) { - if (frame_len == 0){ - for (TCPSocket* srv = s_list_head; srv; srv = srv->next){ - if (srv->role != SOCK_ROLE_SERVER) continue; - if (!srv->bound) continue; - if (srv->localPort != dst_port) continue; - - bool matches_dst = false; - for (int i = 0; i < srv->bound_l3_count; ++i) { - uint8_t id = srv->bound_l3[i]; - - if (ipver == IP_VER4) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (v4->l2->ifindex != ifindex) continue; - if (v4->ip == *(const uint32_t*)dst_ip_addr) { - matches_dst = true; - break; - } - } else if (ipver == IP_VER6) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (v6->l2->ifindex != ifindex) continue; - if (memcmp(v6->ip, dst_ip_addr, 16) == 0){ - matches_dst = true; - break; - } - } - } - - if (!matches_dst) continue; - if (srv->backlogLen >= srv->backlogCap) break; - - TCPSocket* child = new TCPSocket(SOCK_ROLE_CLIENT, srv->pid, &srv->extraOpts); - - child->localPort = dst_port; - child->connected = true; - - child->remoteEP.ver = ipver; - memset(child->remoteEP.ip, 0, 16); - if (ipver == IP_VER4) memcpy(child->remoteEP.ip, src_ip_addr, 4); - else memcpy(child->remoteEP.ip, src_ip_addr, 16); - child->remoteEP.port = src_port; - - uint8_t l3id = 0; - - for (int i = 0; i < srv->bound_l3_count; ++i) { - uint8_t id = srv->bound_l3[i]; - - if (ipver == IP_VER4) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (v4->l2->ifindex != ifindex) continue; - if (v4->ip == *(const uint32_t*)dst_ip_addr) { - l3id = id; - break; - } - } else if (ipver == IP_VER6) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (v6->l2->ifindex != ifindex) continue; - if (memcmp(v6->ip, dst_ip_addr, 16) == 0) { - l3id = id; - break; - } - } - } - - if (!l3id) { - if (ipver == IP_VER4) { - uint32_t v4dst = 0; - memcpy(&v4dst, dst_ip_addr, 4); - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(v4dst); - if (is_valid_v4_l3_for_bind(v4) && v4->l2 && v4->l2->ifindex == ifindex) l3id = v4->l3_id; - } else if (ipver == IP_VER6) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_ip((const uint8_t*)dst_ip_addr); - if (is_valid_v6_l3_for_bind(v6) && v6->l2 && v6->l2->ifindex == ifindex) l3id = v6->l3_id; - } - } - - child->clear_bound_l3(); - - if (l3id) { - child->add_bound_l3(l3id); - } else if (ipver == IP_VER4){ - uint32_t v4dst = 0; - memcpy(&v4dst, dst_ip_addr, 4); - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_ip(v4dst); - if (is_valid_v4_l3_for_bind(v4) && v4->l2 && v4->l2->ifindex == ifindex) { - child->add_bound_l3(v4->l3_id); - } else { - for (int i = 0; i < srv->bound_l3_count; ++i) { - l3_ipv4_interface_t* sv4 = l3_ipv4_find_by_id(srv->bound_l3[i]); - if (!sv4 || !sv4->l2) continue; - if (!sv4->l2->is_up) continue; - if (sv4->l2->ifindex != ifindex) continue; - child->add_bound_l3(sv4->l3_id); - } - } - } else if (ipver == IP_VER6) { - for (int i = 0; i < srv->bound_l3_count; ++i) { - l3_ipv6_interface_t* sv6 = l3_ipv6_find_by_id(srv->bound_l3[i]); - if (!is_valid_v6_l3_for_bind(sv6)) continue; - if (!sv6->l2 || !sv6->l2->is_up) continue; - if (sv6->l2->ifindex != ifindex) continue; - child->add_bound_l3(sv6->l3_id); - } - } - - child->flow = tcp_get_ctx(dst_port, ipver, dst_ip_addr, child->remoteEP.ip, src_port); - if (!child->flow){ - child->close(); - delete child; - break; - } - - child->insert_in_list(); - - srv->pending[srv->backlogLen++] = child; - break; - } - return 0; - } - - for (TCPSocket* s = s_list_head; s; s = s->next) { - if (!s->connected) continue; - if (s->localPort != dst_port) continue; - if (s->remoteEP.port != src_port) continue; - if (s->remoteEP.ver != ipver) continue; - - bool matches_dst = (s->bound_l3_count == 0); - for (int i = 0; !matches_dst && i < s->bound_l3_count; ++i) { - uint8_t id = s->bound_l3[i]; - - if (ipver == IP_VER4) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (v4->l2->ifindex != ifindex) continue; - if (v4->ip == *(const uint32_t*)dst_ip_addr) { - matches_dst = true; - break; - } - } else { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (v6->l2->ifindex != ifindex) continue; - if (memcmp(v6->ip, dst_ip_addr, 16) == 0) { - matches_dst = true; - break; - } - } - } - - if (!matches_dst) continue; - - if (ipver == IP_VER4) { - if (*(const uint32_t*)s->remoteEP.ip != *(const uint32_t*)src_ip_addr) continue; - } else { - if (memcmp(s->remoteEP.ip, src_ip_addr, 16) != 0) continue; - } - - return s->on_receive(frame_ptr, frame_len); - } - - return 0; - } - - uint32_t on_receive(uintptr_t ptr, uint32_t len) { - if(!ptr || !len) return 0; - - uint64_t limit = ring.capacity(); - if ((extraOpts.flags & SOCK_OPT_BUF_SIZE) && extraOpts.buf_size) { - uint64_t m = extraOpts.buf_size; - if (m < limit) limit = m; - } - if (!limit) return 0; - - const uint8_t* src = (const uint8_t*)ptr; - uint32_t pushed = 0; - - uint64_t sz = ring.size(); - if (sz < limit) { - uint64_t free = limit - sz; - - uint32_t accept = len; - if((uint64_t)accept > free) accept = (uint32_t)free; - - pushed = (uint32_t)ring.push_buf(src, accept); - } - - return pushed; - } - - void insert_in_list() { - for (TCPSocket* it = s_list_head; it; it = it->next){ - if (it == this) { - return; - } - } - next = s_list_head; - s_list_head = this; - } - - void remove_from_list() { - TCPSocket** cur = &s_list_head; - while (*cur) { - if (*cur == this) { - *cur = (*cur)->next; - break; - } - cur = &((*cur)->next); - } - next = nullptr; - } - - void do_unbind_one(uint8_t l3_id, uint16_t port, uint16_t pid_) override { - (void)pid_; - if (role != SOCK_ROLE_SERVER) return; - (void)tcp_unbind_l3(l3_id, port, pid); - } - - bool add_all_l3_on_l2(uint8_t ifindex, uint8_t* tmp_ids, ip_version_t* tmp_ver, int& n) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2 || !l2->is_up) return false; - - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (n < SOCK_MAX_L3) { - tmp_ids[n] = v4->l3_id; - tmp_ver[n] = IP_VER4; - ++n; - } - } - - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (n < SOCK_MAX_L3) { - tmp_ids[n] = v6->l3_id; - tmp_ver[n] = IP_VER6; - ++n; - } - } - - return n > 0; - } - - bool add_all_l3_any(uint8_t* tmp_ids, ip_version_t* tmp_ver, int& n) { - uint8_t cnt = l2_interface_count(); - - for (uint8_t i = 0; i < cnt; ++i) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2 || !l2->is_up) continue; - - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (n < SOCK_MAX_L3) { - tmp_ids[n] = v4->l3_id; - tmp_ver[n] = IP_VER4; - ++n; - } - } - - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (n < SOCK_MAX_L3) { - tmp_ids[n] = v6->l3_id; - tmp_ver[n] = IP_VER6; - ++n; - } - } - } - - return n > 0; - } - -public: - explicit TCPSocket(uint8_t r = SOCK_ROLE_CLIENT, uint32_t pid_ = 0, const SocketExtraOptions* extra = nullptr) : Socket(PROTO_TCP, r, extra) { - pid = pid_; - if (!(extraOpts.flags & SOCK_OPT_BUF_SIZE)) { - extraOpts.flags |= SOCK_OPT_BUF_SIZE; - extraOpts.buf_size = TCP_RING_CAP; - } - - if (!extraOpts.buf_size) extraOpts.buf_size = TCP_RING_CAP; - if (extraOpts.buf_size > TCP_RING_CAP) extraOpts.buf_size = TCP_RING_CAP; - insert_in_list(); - } - - ~TCPSocket() override { - remove_from_list(); - close(); - } - - int32_t bind(const SockBindSpec& spec_in, uint16_t port) override { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_TCP; - ev.action = NETLOG_ACT_BIND; - ev.pid = pid; - ev.u0 = port; - ev.bind_spec = spec_in; - netlog_socket_event(&extraOpts, &ev); - if (role != SOCK_ROLE_SERVER) return SOCK_ERR_PERM; - if (bound) return SOCK_ERR_BOUND; - - SockBindSpec spec = spec_in; - bool empty = spec.kind == BIND_L3 && spec.l3_id == 0 && spec.ifindex == 0 && spec.ver == 0 && ipv6_is_unspecified(spec.ip); - if (empty) spec.kind = BIND_ANY; - - uint8_t ids[SOCK_MAX_L3]; - ip_version_t vers[SOCK_MAX_L3]; - int n = 0; - - if (spec.kind == BIND_L3){ - if (!spec.l3_id) return SOCK_ERR_INVAL; - - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(spec.l3_id); - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(spec.l3_id); - - bool ok4 = is_valid_v4_l3_for_bind(v4); - bool ok6 = is_valid_v6_l3_for_bind(v6); - - if (!ok4 && !ok6) return SOCK_ERR_INVAL; - - if (ok4 && n < SOCK_MAX_L3) { - ids[n] = spec.l3_id; - vers[n] = IP_VER4; - ++n; - } - if (ok6 && n < SOCK_MAX_L3) { - ids[n] = spec.l3_id; - vers[n] = IP_VER6; - ++n; - } - } else if (spec.kind == BIND_L2){ - if (!add_all_l3_on_l2(spec.ifindex, ids, vers, n)) return SOCK_ERR_INVAL; - } else if (spec.kind == BIND_IP){ - if (spec.ver == IP_VER4){ - uint32_t v4ip = 0; - memcpy(&v4ip, spec.ip, 4); - l3_ipv4_interface_t* ipif = l3_ipv4_find_by_ip(v4ip); - if (!is_valid_v4_l3_for_bind(ipif)) return SOCK_ERR_INVAL; - ids[n] = ipif->l3_id; - vers[n] = IP_VER4; - ++n; - } else if (spec.ver == IP_VER6) { - l3_ipv6_interface_t* ipif = l3_ipv6_find_by_ip(spec.ip); - if (!is_valid_v6_l3_for_bind(ipif)) return SOCK_ERR_INVAL; - ids[n] = ipif->l3_id; - vers[n] = IP_VER6; - ++n; - } else return SOCK_ERR_INVAL; - } else if (spec.kind == BIND_ANY){ - if (!add_all_l3_any(ids, vers, n)) return SOCK_ERR_INVAL; - } else return SOCK_ERR_INVAL; - - if (n==0) return SOCK_ERR_INVAL; - - uint8_t dedup_ids[SOCK_MAX_L3]; - ip_version_t dedup_ver[SOCK_MAX_L3]; - int m = 0; - - for (int i = 0; i < n; ++i) { - bool seen = false; - for (int j = 0; j < m; ++j) { - if (dedup_ids[j] == ids[i] && dedup_ver[j] == vers[i]) { - seen = true; - break; - } - } - if (!seen && m < SOCK_MAX_L3) { - dedup_ids[m] = ids[i]; - dedup_ver[m] = vers[i]; - ++m; - } - } - - if (m==0) return SOCK_ERR_INVAL; - - uint8_t bound_ids[SOCK_MAX_L3]; - int bdone=0; - - for (int i = 0; i < m; ++i){ - uint8_t id = dedup_ids[i]; - bool ok = tcp_bind_l3(id, port, pid, dispatch, &extraOpts); - if (!ok){ - for (int j=0;j TCP_MAX_BACKLOG ? TCP_MAX_BACKLOG : max_backlog; - backlogLen = 0; - return SOCK_OK; - } - - TCPSocket* accept(){ - const int max_iters = 100; - int iter = 0; - - while (backlogLen == 0){ - if (++iter > max_iters) return nullptr; - msleep(10); - } - - TCPSocket* client = pending[0]; - - for (int i = 1; i < backlogLen; ++i) pending[i - 1] = pending[i]; - pending[--backlogLen] = nullptr; - - return client; - } - - int32_t connect(SockDstKind kind, const void* dst, uint16_t port) { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_TCP; - ev.action = NETLOG_ACT_CONNECT; - ev.pid = pid; - ev.dst_kind = kind; - ev.u0 = port; - if (kind == DST_ENDPOINT && dst) ev.dst_ep = *(const net_l4_endpoint*)dst; - if (kind == DST_DOMAIN) ev.s0 = (const char*)dst; - netlog_socket_event(&extraOpts, &ev); - if (role != SOCK_ROLE_CLIENT) return SOCK_ERR_PERM; - if (connected) return SOCK_ERR_STATE; - if (!dst) return SOCK_ERR_INVAL; - - net_l4_endpoint d{}; - uint8_t chosen_l3 = 0; - - uint8_t allow_v4[SOCK_MAX_L3]; - uint8_t allow_v6[SOCK_MAX_L3]; - int n4 = 0; - int n6 = 0; - - for (int i = 0; i < bound_l3_count; ++i) { - uint8_t id = bound_l3[i]; - - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (is_valid_v4_l3_for_bind(v4) && n4 < SOCK_MAX_L3) allow_v4[n4++] = id; - - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); - if (is_valid_v6_l3_for_bind(v6) && n6 < SOCK_MAX_L3) allow_v6[n6++] = id; - } - - if (kind == DST_ENDPOINT){ - const net_l4_endpoint* ed = (const net_l4_endpoint*)dst; - d = *ed; - if (!d.port && port) d.port = port; - if (!d.port) return SOCK_ERR_INVAL; - - if (d.ver == IP_VER6) { - ipv6_tx_plan_t p6; - if (!ipv6_build_tx_plan(d.ip, nullptr, n6 ? allow_v6 : nullptr, n6, &p6)) return SOCK_ERR_SYS; - chosen_l3 = p6.l3_id; - } else if (d.ver == IP_VER4) { - uint32_t dip = 0; - memcpy(&dip, d.ip, 4); - ipv4_tx_plan_t p4; - if (!ipv4_build_tx_plan(dip, nullptr, n4 ? allow_v4 : nullptr, n4, &p4)) return SOCK_ERR_SYS; - chosen_l3 = p4.l3_id; - } else return SOCK_ERR_INVAL; - } else if (kind == DST_DOMAIN){ - const char* host = (const char*)dst; - if (!port) return SOCK_ERR_INVAL; - - uint8_t v6addr[16]; - memset(v6addr, 0, 16); - uint32_t v4addr = 0; - - dns_result_t dr6 = dns_resolve_aaaa(host, v6addr, TCP_DNS_SEL, TCP_DNS_TIMEOUT_MS); - dns_result_t dr4 = dns_resolve_a(host, &v4addr, TCP_DNS_SEL, TCP_DNS_TIMEOUT_MS); - - if (dr6 != DNS_OK && dr4 != DNS_OK) return SOCK_ERR_DNS; - - if (dr6 == DNS_OK) { - net_l4_endpoint d6{}; - d6.ver = IP_VER6; - memcpy(d6.ip, v6addr, 16); - d6.port = port; - - ipv6_tx_plan_t p6; - if (ipv6_build_tx_plan(d6.ip, nullptr, n6 ? allow_v6 : nullptr, n6, &p6)) { - d = d6; - chosen_l3 = p6.l3_id; - } - } - - if (!chosen_l3 && dr4 == DNS_OK) { - net_l4_endpoint d4{}; - make_ep(v4addr, port, IP_VER4, &d4); - - uint32_t dip = 0; - memcpy(&dip, d4.ip, 4); - ipv4_tx_plan_t p4; - if (ipv4_build_tx_plan(dip, nullptr, n4 ? allow_v4 : nullptr, n4, &p4)) { - d = d4; - chosen_l3 = p4.l3_id; - } - } - - if (!chosen_l3) return SOCK_ERR_SYS; - } else return SOCK_ERR_INVAL; - - if (!chosen_l3) return SOCK_ERR_SYS; - - if (d.ver == IP_VER4) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(chosen_l3); - if (!is_valid_v4_l3_for_bind(v4)) return SOCK_ERR_SYS; - } else if (d.ver == IP_VER6) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen_l3); - if (!is_valid_v6_l3_for_bind(v6)) return SOCK_ERR_SYS; - } else return SOCK_ERR_INVAL; - - if (localPort == 0) { - int p = tcp_alloc_ephemeral_l3(chosen_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - } - - clear_bound_l3(); - add_bound_l3(chosen_l3); - bound = true; - - tcp_data ctx_copy{}; - if (!tcp_handshake_l3(chosen_l3, localPort, &d, &ctx_copy, pid, &extraOpts)) { - Socket::close(); - return SOCK_ERR_SYS; - } - - uint8_t local_ip[16]; - memset(local_ip, 0, sizeof(local_ip)); - if (d.ver == IP_VER4) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(chosen_l3); - if (!is_valid_v4_l3_for_bind(v4)) { - Socket::close(); - return SOCK_ERR_SYS; - } - memcpy(local_ip, &v4->ip, 4); - } else { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen_l3); - if (!is_valid_v6_l3_for_bind(v6)) { - Socket::close(); - return SOCK_ERR_SYS; - } - memcpy(local_ip, v6->ip, 16); - } - - flow = tcp_get_ctx(localPort, d.ver, local_ip, (const void*)d.ip, d.port); - if (!flow) { - Socket::close(); - return SOCK_ERR_SYS; - } - - remoteEP = d; - connected = true; - netlog_socket_event_t ev1{}; - ev1.comp = NETLOG_COMP_TCP; - ev1.action = NETLOG_ACT_CONNECTED; - ev1.pid = pid; - ev1.u0 = localPort; - ev1.u1 = remoteEP.port; - ev1.local_port = localPort; - ev1.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev1); - return SOCK_OK; - } - - int64_t send(const void* buf, uint64_t len) { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_TCP; - ev.action = NETLOG_ACT_SEND; - ev.pid = pid; - ev.u0 = (uint32_t)len; - ev.local_port = localPort; - ev.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev); - if (!connected || !flow) return SOCK_ERR_STATE; - - const uint8_t* p = (const uint8_t*)buf; - uint64_t sent_total = 0; - - while (sent_total < len) { - uint64_t remain = len - sent_total; - uint32_t chunk = remain > UINT32_MAX ? UINT32_MAX : (uint32_t)remain; - flow->payload.ptr = (uintptr_t)(p + sent_total); - flow->payload.size = chunk; - flow->flags = (1u<payload.size; - if (!pushed) break; - sent_total += pushed; - } - - if (sent_total) return (int64_t)sent_total; - return TCP_WOULDBLOCK; - } - - int64_t recv(void* buf, uint64_t len){ - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_TCP; - ev.action = NETLOG_ACT_RECV; - ev.pid = pid; - ev.u0 = (uint32_t)len; - ev.local_port = localPort; - ev.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev); - if (!buf || !len) return 0; - - uint8_t* out = (uint8_t*)buf; - uint64_t n = 0; - - n = ring.pop_buf(out, len); - - if (n) { - if (flow) tcp_flow_on_app_read(flow, (uint32_t)n); - return (int64_t)n; - } - if (connected) return TCP_WOULDBLOCK; - return 0; - } - - int32_t close() override { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_TCP; - ev.action = NETLOG_ACT_CLOSE; - ev.pid = pid; - ev.local_port = localPort; - ev.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev); - if (connected && flow){ - tcp_flow_close(flow); - connected = false; - flow = nullptr; - } - - ring.clear(); - for (int i = 0; i < backlogLen; ++i) delete pending[i]; - backlogLen = 0; - - return Socket::close(); - } - - net_l4_endpoint get_remote_ep() const { return remoteEP; } -}; \ No newline at end of file diff --git a/kernel/networking/transport_layer/socket_udp.hpp b/kernel/networking/transport_layer/socket_udp.hpp deleted file mode 100644 index da1aede9..00000000 --- a/kernel/networking/transport_layer/socket_udp.hpp +++ /dev/null @@ -1,778 +0,0 @@ -#pragma once -#include "socket.hpp" -#include "networking/transport_layer/udp.h" -#include "types.h" -#include "std/memory.h" -#include "networking/application_layer/dns/dns.h" -#include "net/socket_types.h" -#include "networking/internet_layer/ipv4_route.h" -#include "networking/internet_layer/ipv6_route.h" -#include "syscalls/syscalls.h" -#include "networking/internet_layer/ipv4_utils.h" -#include "networking/internet_layer/ipv6_utils.h" -#include "networking/transport_layer/trans_utils.h" -#include "networking/internet_layer/ipv6.h" -#include "networking/internet_layer/igmp.h" -#include "exceptions/irq.h" -#include "sysregs.h" - -static constexpr int32_t UDP_RING_CAP = 1024; -static constexpr dns_server_sel_t UDP_DNS_SEL = DNS_USE_BOTH; -static constexpr uint32_t UDP_DNS_TIMEOUT_MS = 3000; - -class UDPSocket : public Socket { - inline static UDPSocket* s_list_head = nullptr; - - sizedptr ring[UDP_RING_CAP]; - net_l4_endpoint src_eps[UDP_RING_CAP]; - int32_t r_head = 0; - int32_t r_tail = 0; - uint32_t rx_bytes = 0; - - UDPSocket* next = nullptr; - - static bool is_valid_v4_l3_for_bind(l3_ipv4_interface_t* v4) { - if (!v4) return false; - if (!v4->l2) return false; - if (!v4->l2->is_up) return false; - if (v4->mode == IPV4_CFG_DISABLED) return false; - if (!v4->port_manager) return false; - return true; - } - - static bool is_valid_v6_l3_for_bind(l3_ipv6_interface_t* v6) { - if (!v6) return false; - if (!v6->l2) return false; - if (!v6->l2->is_up) return false; - if (v6->cfg == IPV6_CFG_DISABLE) return false; - if (v6->dad_state != IPV6_DAD_OK) return false; - if (!v6->port_manager) return false; - return true; - } - - static bool is_dbcast(uint32_t ip, uint8_t* out_l3) { - uint8_t cnt = l2_interface_count(); - for (uint8_t i = 0; i < cnt; ++i) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!v4) continue; - if (v4->mode == IPV4_CFG_DISABLED) continue; - if (!v4->ip) continue; - if (!v4->mask) continue; - uint32_t b = ipv4_broadcast_calc(v4->ip, v4->mask); - if (b != ip) continue; - if (out_l3) *out_l3 = v4->l3_id; - return true; - } - } - return false; - } - - static bool socket_matches_dst(UDPSocket* s, uint8_t ifx, ip_version_t ver, const void* dst_ip_addr, uint16_t dst_port) { - if (!s) return false; - if (!s->bound) return false; - if (s->localPort != dst_port) return false; - if (!dst_ip_addr) return false; - - if (ver == IP_VER4) { - uint32_t dip = *(const uint32_t*)dst_ip_addr; - bool lb = dip == 0xFFFFFFFFu; - bool mc = ipv4_is_multicast(dip); - - for (int i = 0; i < s->bound_l3_count; ++i) { - uint8_t id = s->bound_l3[i]; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(id); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (v4->l2->ifindex != ifx) continue; - if (lb) return true; - if (mc) return true; - - if (v4->mask) { - uint32_t b = ipv4_broadcast_calc(v4->ip, v4->mask); - if (b == dip)return true; - } - - if (v4->ip == dip) return true; - } - return false; - } - - if (ver == IP_VER6) { - bool mcast = ipv6_is_multicast((const uint8_t*)dst_ip_addr); - - for (int i = 0; i < s->bound_l3_count; ++i) { - uint8_t id = s->bound_l3[i]; - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(id); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (v6->l2->ifindex != ifx) continue; - - if (mcast) return true; - if (memcmp(v6->ip, dst_ip_addr, 16) == 0) return true; - } - return false; - } - - return false; - } - - static uint32_t dispatch(uint8_t ifindex, ip_version_t ipver, const void* src_ip_addr, const void* dst_ip_addr, uintptr_t frame_ptr, uint32_t frame_len, uint16_t src_port, uint16_t dst_port) { - UDPSocket* first = nullptr; - uint32_t ret = frame_len; - irq_flags_t irq = irq_save_disable(); - - for (UDPSocket* s = s_list_head; s; s = s->next) { - if (!socket_matches_dst(s, ifindex, ipver, dst_ip_addr, dst_port)) continue; - if (!first) { - first = s; - continue; - } - - uintptr_t copy = (uintptr_t)malloc(frame_len); - if (!copy) continue; - - memcpy((void*)copy, (const void*)frame_ptr, frame_len); - s->on_receive(ipver, src_ip_addr, src_port, copy, frame_len); - } - - if (first) first->on_receive(ipver, src_ip_addr, src_port, frame_ptr, frame_len); - else if (frame_ptr && frame_len) free_sized((void*)frame_ptr, frame_len); - irq_restore(irq); - return ret; - } - - void on_receive(ip_version_t ver, const void* src_ip_addr, uint16_t src_port, uintptr_t ptr, uint32_t len) { - uint32_t limit = 0xFFFFFFFFu; - if ((extraOpts.flags & SOCK_OPT_BUF_SIZE) && extraOpts.buf_size) limit = extraOpts.buf_size; - if (len > limit) { - if (ptr && len) free_sized((void*)ptr, len); - return; - } - - while (rx_bytes + len > limit && r_head != r_tail) { - rx_bytes -= ring[r_head].size; - free_sized((void*)ring[r_head].ptr, ring[r_head].size); - r_head = (r_head + 1) % UDP_RING_CAP; - } - - int nexti = (r_tail + 1) % UDP_RING_CAP; - if (nexti == r_head) { - rx_bytes -= ring[r_head].size; - free_sized((void*)ring[r_head].ptr, ring[r_head].size); - r_head = (r_head + 1) % UDP_RING_CAP; - } - - ring[r_tail].ptr = ptr; - ring[r_tail].size = len; - rx_bytes += len; - - src_eps[r_tail].ver = ver; - memset(src_eps[r_tail].ip, 0, 16); - - if (ver == IP_VER4) { - uint32_t v4 = *(const uint32_t*)src_ip_addr; - memcpy(src_eps[r_tail].ip, &v4, 4); - } else if (ver == IP_VER6) { - memcpy(src_eps[r_tail].ip, src_ip_addr, 16); - } - - src_eps[r_tail].port = src_port; - - r_tail = nexti; - remoteEP = src_eps[(r_tail + UDP_RING_CAP - 1) % UDP_RING_CAP]; - } - - void insert_in_list() { - for (UDPSocket* it = s_list_head; it; it = it->next) if (it == this) return; - next = s_list_head; - s_list_head = this; - } - - void remove_from_list() { - UDPSocket** cur = &s_list_head; - int hops = 0; - while (*cur && hops++ < UDP_RING_CAP * 4) { - UDPSocket* p = *cur; - if (((uintptr_t)p & HIGH_VA) != HIGH_VA) break; //TODO this check should be useless but for now it prevents crashes if someone modifies the list, remove it once the issue is fixed - if (p == this) { - *cur = p->next; - break; - } - if (p->next && (((uintptr_t)p->next & HIGH_VA) != HIGH_VA)) break; - cur = &(p->next); - } - next = nullptr; - } - - bool add_all_l3_on_l2(uint8_t ifindex, uint8_t* tmp_ids, int& n) { - l2_interface_t* l2 = l2_interface_find_by_index(ifindex); - if (!l2) return false; - if (!l2->is_up) return false; - - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (n < SOCK_MAX_L3) tmp_ids[n++] = v4->l3_id; - } - - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (n < SOCK_MAX_L3) tmp_ids[n++] = v6->l3_id; - } - - return n > 0; - } - - bool add_all_l3_any(uint8_t* tmp_ids, int& n) { - uint8_t cnt = l2_interface_count(); - for (uint8_t i = 0; i < cnt; ++i) { - l2_interface_t* l2 = l2_interface_at(i); - if (!l2) continue; - if (!l2->is_up) continue; - - for (int s = 0; s < MAX_IPV4_PER_INTERFACE; ++s) { - l3_ipv4_interface_t* v4 = l2->l3_v4[s]; - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (n < SOCK_MAX_L3) tmp_ids[n++] = v4->l3_id; - } - - for (int s = 0; s < MAX_IPV6_PER_INTERFACE; ++s) { - l3_ipv6_interface_t* v6 = l2->l3_v6[s]; - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (n < SOCK_MAX_L3) tmp_ids[n++] = v6->l3_id; - } - } - return n > 0; - } - - void do_unbind_one(uint8_t l3_id, uint16_t port, uint16_t pid_) override { - (void)pid_; - udp_unbind_l3(l3_id, port, pid); - } - - static bool pick_v4_l3_for_unicast(uint32_t dip, const uint8_t* candidates, int n, uint8_t* out_l3) { - if (!out_l3) return false; - ipv4_tx_plan_t plan; - if (!ipv4_build_tx_plan(dip, nullptr, candidates, n, &plan)) return false; - *out_l3 = plan.l3_id; - return true; - } - - static bool pick_v6_l3_for_unicast(const uint8_t dst_ip[16], const uint8_t* candidates, int n, uint8_t* out_l3) { - if (!out_l3) return false; - ipv6_tx_plan_t plan; - if (!ipv6_build_tx_plan(dst_ip, nullptr, candidates, n, &plan)) return false; - *out_l3 = plan.l3_id; - return true; - } - -public: - UDPSocket(uint8_t r, uint32_t pid_, const SocketExtraOptions* extra = nullptr) : Socket(PROTO_UDP, r, extra) { - pid = pid_; - irq_flags_t irq = irq_save_disable(); - insert_in_list(); - irq_restore(irq); - } - - ~UDPSocket() override { - irq_flags_t irq = irq_save_disable(); //TODO locking is needed asap - remove_from_list(); - irq_restore(irq); - if ((extraOpts.flags & SOCK_OPT_MCAST_JOIN) && extraOpts.mcast_ver) { - if (extraOpts.mcast_ver == IP_VER4) { - uint32_t g = 0; - memcpy(&g, extraOpts.mcast_group, 4); - if (ipv4_is_multicast(g)) { - for (int i = 0; i < bound_l3_count; ++i) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(bound_l3[i]); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (!v4->l2) continue; - (void)l2_ipv4_mcast_leave(v4->l2->ifindex, g); - } - } - } else if (extraOpts.mcast_ver == IP_VER6) { - if (ipv6_is_multicast(extraOpts.mcast_group)) { - for (int i = 0; i < bound_l3_count; ++i) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(bound_l3[i]); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (!v6->l2) continue; - (void)l2_ipv6_mcast_leave(v6->l2->ifindex, extraOpts.mcast_group); - } - } - } - } - close(); - } - - int32_t bind(const SockBindSpec& spec_in, uint16_t port) override { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_UDP; - ev.action = NETLOG_ACT_BIND; - ev.pid = pid; - ev.u0 = port; - ev.bind_spec = spec_in; - netlog_socket_event(&extraOpts, &ev); - if (role != SOCK_ROLE_SERVER) return SOCK_ERR_PERM; - if (bound) return SOCK_ERR_BOUND; - - SockBindSpec spec = spec_in; - bool empty = spec.kind == BIND_L3 && spec.l3_id == 0 && spec.ifindex == 0 && spec.ver == 0 && ipv6_is_unspecified(spec.ip); - if (empty) spec.kind = BIND_ANY; - - uint8_t ids[SOCK_MAX_L3]; - int n = 0; - - if (spec.kind == BIND_L3) { - if (!spec.l3_id) return SOCK_ERR_INVAL; - - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(spec.l3_id); - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(spec.l3_id); - - bool ok4 = is_valid_v4_l3_for_bind(v4); - bool ok6 = is_valid_v6_l3_for_bind(v6); - - if (!ok4 && !ok6) return SOCK_ERR_INVAL; - - if (ok4 && n < SOCK_MAX_L3) ids[n++] = spec.l3_id; - if (ok6 && n < SOCK_MAX_L3) ids[n++] = spec.l3_id; - } else if (spec.kind == BIND_L2) { - if (!add_all_l3_on_l2(spec.ifindex, ids, n)) return SOCK_ERR_INVAL; - } else if (spec.kind == BIND_IP) { - if (spec.ver == IP_VER4) { - uint32_t v4ip = 0; - memcpy(&v4ip, spec.ip, 4); - l3_ipv4_interface_t* ipif = l3_ipv4_find_by_ip(v4ip); - if (!is_valid_v4_l3_for_bind(ipif)) return SOCK_ERR_INVAL; - if (n < SOCK_MAX_L3) ids[n++] = ipif->l3_id; - } else if (spec.ver == IP_VER6) { - l3_ipv6_interface_t* ipif6 = l3_ipv6_find_by_ip(spec.ip); - if (!is_valid_v6_l3_for_bind(ipif6)) return SOCK_ERR_INVAL; - if (n < SOCK_MAX_L3) ids[n++] = ipif6->l3_id; - } else { - return SOCK_ERR_INVAL; - } - } else if (spec.kind == BIND_ANY) { - if (!add_all_l3_any(ids, n)) return SOCK_ERR_INVAL; - } else { - return SOCK_ERR_INVAL; - } - - if (n == 0) return SOCK_ERR_INVAL; - - uint8_t dedup[SOCK_MAX_L3]; - int m = 0; - for (int i = 0; i < n; ++i) { - bool seen = false; - for (int j = 0; j < m; ++j) { - if (dedup[j] == ids[i]) { - seen = true; - break; - } - } - if (!seen && m < SOCK_MAX_L3) dedup[m++] = ids[i]; - } - if (m == 0) return SOCK_ERR_INVAL; - - int bdone = 0; - for (int i = 0; i < m; ++i) { - uint8_t id = dedup[i]; - if (udp_bind_l3(id, port, pid, dispatch)) { - bdone++; - continue; - } - for (int j = 0; j < bdone; ++j) udp_unbind_l3(dedup[j], port, pid); - return SOCK_ERR_SYS; - } - - clear_bound_l3(); - for (int i = 0; i < m; ++i) add_bound_l3(dedup[i]); - - if ((extraOpts.flags & SOCK_OPT_MCAST_JOIN) && extraOpts.mcast_ver) { - if (extraOpts.mcast_ver == IP_VER4) { - uint32_t g = 0; - memcpy(&g, extraOpts.mcast_group, 4); - if (ipv4_is_multicast(g)) { - for (int i = 0; i < bound_l3_count; ++i) { - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(bound_l3[i]); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (!v4->l2) continue; - (void)l2_ipv4_mcast_join(v4->l2->ifindex, g); - } - } - } else if (extraOpts.mcast_ver == IP_VER6) { - if (ipv6_is_multicast(extraOpts.mcast_group)) { - for (int i = 0; i < bound_l3_count; ++i) { - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(bound_l3[i]); - if (!is_valid_v6_l3_for_bind(v6)) continue; - if (!v6->l2) continue; - (void)l2_ipv6_mcast_join(v6->l2->ifindex, extraOpts.mcast_group); - } - } - } - } - - localPort = port; - bound = true; - return SOCK_OK; - } - - int64_t sendto(SockDstKind kind, const void* dst, uint16_t port, const void* buf, uint64_t len) { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_UDP; - ev.action = NETLOG_ACT_SENDTO; - ev.pid = pid; - ev.dst_kind = kind; - ev.u0 = port; - ev.u1 = (uint32_t)len; - if (kind == DST_ENDPOINT && dst) ev.dst_ep = *(const net_l4_endpoint*)dst; - if (kind == DST_DOMAIN) ev.s0 = (const char*)dst; - netlog_socket_event(&extraOpts, &ev); - if (!dst) return SOCK_ERR_INVAL; - if (!buf) return SOCK_ERR_INVAL; - if (len == 0) return SOCK_ERR_INVAL; - - net_l4_endpoint d; - if (kind == DST_ENDPOINT) { - const net_l4_endpoint* ed = (const net_l4_endpoint*)dst; - d = *ed; - if (!d.port && port) d.port = port; - if (!d.port) return SOCK_ERR_INVAL; - } else if (kind == DST_DOMAIN) { - const char* host = (const char*)dst; - if (!port) return SOCK_ERR_INVAL; - - uint8_t a6[16]; - memset(a6, 0, 16); - uint32_t a4 = 0; - - dns_result_t dr6 = dns_resolve_aaaa(host, a6, UDP_DNS_SEL, UDP_DNS_TIMEOUT_MS); - dns_result_t dr4 = dns_resolve_a(host, &a4, UDP_DNS_SEL, UDP_DNS_TIMEOUT_MS); - if (dr6 != DNS_OK && dr4 != DNS_OK) return SOCK_ERR_DNS; - - uint8_t allow_v4[SOCK_MAX_L3]; - uint8_t allow_v6[SOCK_MAX_L3]; - int n4 = 0; - int n6 = 0; - for (int i = 0; i < bound_l3_count; ++i) { - uint8_t id = bound_l3[i]; - if (n4 < SOCK_MAX_L3 && l3_ipv4_find_by_id(id)) allow_v4[n4++] = id; - if (n6 < SOCK_MAX_L3 && l3_ipv6_find_by_id(id)) allow_v6[n6++] = id; - } - - if (dr6 == DNS_OK) { - ipv6_tx_plan_t p6; - if (ipv6_build_tx_plan(a6, nullptr, n6 ? allow_v6 : nullptr, n6, &p6)) { - d.ver = IP_VER6; - memcpy(d.ip, a6, 16); - d.port = port; - } - } - - if (d.ver == 0 && dr4 == DNS_OK) { - ipv4_tx_plan_t p4; - if (ipv4_build_tx_plan(a4, nullptr, n4 ? allow_v4 : nullptr, n4, &p4)) { - make_ep(a4, port, IP_VER4, &d); - } - } - - if (d.ver == 0) return SOCK_ERR_SYS; - } else { - return SOCK_ERR_INVAL; - } - - sizedptr pay; - pay.ptr = (uintptr_t)buf; - pay.size = (uint32_t)len; - - if (d.ver == IP_VER4) { - uint32_t dip = 0; - memcpy(&dip, d.ip, 4); - - bool is_bcast = false; - if (dip == 0xFFFFFFFFu) is_bcast = true; - else { - uint8_t dummy = 0; - if (is_dbcast(dip, &dummy)) is_bcast = true; - } - - if (is_bcast) { - if (dip == 0xFFFFFFFFu) { - if (bound_l3_count == 0) return SOCK_ERR_SYS; - - for (int i = 0; i < bound_l3_count; ++i) { - uint8_t bl3 = bound_l3[i]; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(bl3); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (!v4->l2) continue; - - if (!bound) { - int p = udp_alloc_ephemeral_l3(bl3, pid, dispatch); - if (p < 0) continue; - localPort = (uint16_t)p; - add_bound_l3(bl3); - bound = true; - } else if (localPort == 0) { - int p = udp_alloc_ephemeral_l3(bl3, pid, dispatch); - if (p < 0) continue; - localPort = (uint16_t)p; - } - - net_l4_endpoint src; - src.ver = IP_VER4; - memset(src.ip, 0, 16); - memcpy(src.ip, &v4->ip, 4); - src.port = localPort; - - ipv4_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = bl3; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - } - - remoteEP = d; - return (int64_t)len; - } - - uint8_t db_l3 = 0; - if (!is_dbcast(dip, &db_l3)) return SOCK_ERR_SYS; - - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(db_l3); - if (!is_valid_v4_l3_for_bind(v4)) return SOCK_ERR_SYS; - if (!v4->l2) return SOCK_ERR_SYS; - - if (!bound) { - int p = udp_alloc_ephemeral_l3(db_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - add_bound_l3(db_l3); - bound = true; - } else if (localPort == 0) { - int p = udp_alloc_ephemeral_l3(db_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - } - - net_l4_endpoint src; - src.ver = IP_VER4; - memset(src.ip, 0, 16); - memcpy(src.ip, &v4->ip, 4); - src.port = localPort; - - ipv4_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = db_l3; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - remoteEP = d; - return (int64_t)len; - } - - if (ipv4_is_multicast(dip)) { - if (bound_l3_count == 0) return SOCK_ERR_SYS; - - for (int i = 0; i < bound_l3_count; ++i) { - uint8_t bl3 = bound_l3[i]; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(bl3); - if (!is_valid_v4_l3_for_bind(v4)) continue; - if (!v4->l2) continue; - - if (!bound) { - int p = udp_alloc_ephemeral_l3(bl3, pid, dispatch); - if (p < 0) continue; - localPort = (uint16_t)p; - add_bound_l3(bl3); - bound = true; - } else if (localPort == 0) { - int p = udp_alloc_ephemeral_l3(bl3, pid, dispatch); - if (p < 0) continue; - localPort = (uint16_t)p; - } - - (void)l2_ipv4_mcast_join(v4->l2->ifindex, dip); - (void)igmp_send_join(v4->l2->ifindex, dip); - - net_l4_endpoint src; - src.ver = IP_VER4; - memset(src.ip, 0, 16); - memcpy(src.ip, &v4->ip, 4); - src.port = localPort; - - ipv4_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = bl3; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - } - - remoteEP = d; - return (int64_t)len; - } - - uint8_t allowed_v4[SOCK_MAX_L3]; - int n_allowed = 0; - for (int i = 0; i < bound_l3_count && n_allowed < SOCK_MAX_L3; ++i) { - uint8_t id = bound_l3[i]; - if (l3_ipv4_find_by_id(id)) allowed_v4[n_allowed++] = id; - } - if (bound_l3_count > 0 && n_allowed == 0) return SOCK_ERR_SYS; - - ipv4_tx_plan_t plan; - if (!ipv4_build_tx_plan(dip, nullptr, n_allowed ? allowed_v4 : nullptr, n_allowed, &plan)) return SOCK_ERR_SYS; - - uint8_t chosen_l3 = plan.l3_id; - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(chosen_l3); - if (!is_valid_v4_l3_for_bind(v4)) return SOCK_ERR_SYS; - - if (!bound) { - int p = udp_alloc_ephemeral_l3(chosen_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - add_bound_l3(chosen_l3); - bound = true; - } else if (localPort == 0) { - int p = udp_alloc_ephemeral_l3(chosen_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - } - - net_l4_endpoint src; - src.ver = IP_VER4; - memset(src.ip, 0, 16); - memcpy(src.ip, &v4->ip, 4); - src.port = localPort; - - ipv4_tx_opts_t tx; - tx.scope = (ip_tx_scope_t)plan.fixed_opts.scope; - tx.index = plan.fixed_opts.index; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - remoteEP = d; - return (int64_t)len; - } - - if (d.ver == IP_VER6) { - bool is_mcast = ipv6_is_multicast(d.ip); - - if (is_mcast) { - if (!bound) return SOCK_ERR_BOUND; - if (!localPort) return SOCK_ERR_BOUND; - if (bound_l3_count == 0) return SOCK_ERR_SYS; - - for (int i = 0; i < bound_l3_count; ++i) { - uint8_t bl3 = bound_l3[i]; - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(bl3); - if (!is_valid_v6_l3_for_bind(v6)) continue; - - net_l4_endpoint src; - src.ver = IP_VER6; - memset(src.ip, 0, 16); - memcpy(src.ip, v6->ip, 16); - src.port = localPort; - - ipv6_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = bl3; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - } - - remoteEP = d; - return (int64_t)len; - } - - uint8_t allowed_v6[SOCK_MAX_L3]; - int n_allowed = 0; - for (int i = 0; i < bound_l3_count && n_allowed < SOCK_MAX_L3; ++i) { - uint8_t id = bound_l3[i]; - if (l3_ipv6_find_by_id(id)) allowed_v6[n_allowed++] = id; - } - if (bound_l3_count > 0 && n_allowed == 0) return SOCK_ERR_SYS; - - ipv6_tx_plan_t plan; - if (!ipv6_build_tx_plan(d.ip, nullptr, n_allowed ? allowed_v6 : nullptr, n_allowed, &plan)) return SOCK_ERR_SYS; - - uint8_t chosen_l3 = plan.l3_id; - - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(chosen_l3); - if (!is_valid_v6_l3_for_bind(v6)) return SOCK_ERR_SYS; - - if (!bound) { - int p = udp_alloc_ephemeral_l3(chosen_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - add_bound_l3(chosen_l3); - bound = true; - } else if (localPort == 0) { - int p = udp_alloc_ephemeral_l3(chosen_l3, pid, dispatch); - if (p < 0) return SOCK_ERR_NO_PORT; - localPort = (uint16_t)p; - } - - net_l4_endpoint src; - src.ver = IP_VER6; - memset(src.ip, 0, 16); - memcpy(src.ip, v6->ip, 16); - src.port = localPort; - - ipv6_tx_opts_t tx; - tx.scope = (ip_tx_scope_t)plan.fixed_opts.scope; - tx.index = plan.fixed_opts.index; - - udp_send_segment(&src, &d, pay, &tx, (extraOpts.flags & SOCK_OPT_TTL) ? extraOpts.ttl : 0, (extraOpts.flags & SOCK_OPT_DONTFRAG) ? 1 : 0); - remoteEP = d; - return (int64_t)len; - } - - return SOCK_ERR_INVAL; - } - - int64_t recvfrom(void* buf, uint64_t len, net_l4_endpoint* src) { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_UDP; - ev.action = NETLOG_ACT_RECVFROM; - ev.pid = pid; - ev.u0 = (uint32_t)len; - ev.local_port = localPort; - ev.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev); - if (r_head == r_tail) return 0; - - sizedptr p = ring[r_head]; - net_l4_endpoint se = src_eps[r_head]; - r_head = (r_head + 1) % UDP_RING_CAP; - rx_bytes -= p.size; - - uint32_t tocpy = p.size; - if (tocpy > len) tocpy = (uint32_t)len; - - memcpy(buf, (void*)p.ptr, tocpy); - if (src) *src = se; - - free_sized((void*)p.ptr, p.size); - remoteEP = se; - return tocpy; - } - - int32_t close() override { - netlog_socket_event_t ev{}; - ev.comp = NETLOG_COMP_UDP; - ev.action = NETLOG_ACT_CLOSE; - ev.pid = pid; - ev.local_port = localPort; - ev.remote_ep = remoteEP; - netlog_socket_event(&extraOpts, &ev); - while (r_head != r_tail) { - rx_bytes -= ring[r_head].size; - free_sized((void*)ring[r_head].ptr, ring[r_head].size); - r_head = (r_head + 1) % UDP_RING_CAP; - } - return Socket::close(); - } - - net_l4_endpoint get_remote_ep() const { - return remoteEP; - } -}; \ No newline at end of file diff --git a/kernel/networking/transport_layer/tcp.h b/kernel/networking/transport_layer/tcp.h index 3498771f..69d4cfe9 100644 --- a/kernel/networking/transport_layer/tcp.h +++ b/kernel/networking/transport_layer/tcp.h @@ -1,8 +1,8 @@ #pragma once -#include "networking/port_manager.h" #include "networking/internet_layer/ipv4.h" #include "networking/link_layer/eth.h" +#include "networking/netpkt.h" #include "std/memory.h" #include "net/network_types.h" #include "net/socket_types.h" @@ -19,17 +19,12 @@ extern "C" { #define URG_F 5 #define ECE_F 6 #define CWR_F 7 - +//todo ECE_F CWR_F rfc 3168 8311 9293 typedef enum { TCP_OK = 0, - TCP_RETRY = 1, - TCP_RESET = 2, - TCP_TIMEOUT = -2, - TCP_CSUM_ERR = -3, TCP_INVALID = -4, TCP_WOULDBLOCK = -5, TCP_DISCONNECT = -6, - TCP_UNIMPLEMENT = -10, TCP_BUSY = -11, } tcp_result_t; @@ -54,6 +49,9 @@ typedef struct { sizedptr payload; uint32_t expected_ack; uint32_t ack_received; + uint16_t flow_index; + uint16_t flow_reserved; + uint32_t flow_generation; } tcp_data; typedef enum { @@ -71,37 +69,37 @@ typedef enum { } tcp_state_t; #define MAX_TCP_FLOWS 512 -#define TCP_SYN_RETRIES 5 -#define TCP_DATA_RETRIES 5 -#define TCP_RETRY_TIMEOUT_MS 200 #define TCP_RECV_WINDOW 65535 -#define TCP_MAX_TX_SEGS 16 -#define TCP_INIT_RTO 200 +#define TCP_MAX_TX_SEGS 128 +#define TCP_INIT_RTO 1000 #define TCP_MIN_RTO 200 #define TCP_MAX_RTO 60000 +#define TCP_SYN_DATA_RTO 3000 #define TCP_MSL_MS 30000 +#define SOCKET_DEFAULT_KEEPALIVE_MS (2u * 60u * 60u * 1000u) #define TCP_2MSL_MS (2 * TCP_MSL_MS) #define TCP_MAX_RETRANS 8 -#define TCP_MAX_PERSIST_PROBES 8 -int find_flow(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port); -tcp_data* tcp_get_ctx(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port); +bool tcp_get_ctx(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port, tcp_data *out_ctx); +bool tcp_bind_conflicts(const SockBindSpec* spec, uint16_t port, bool reuseaddr); -bool tcp_bind_l3(uint8_t l3_id, uint16_t port, uint16_t pid, port_recv_handler_t handler, const SocketExtraOptions* extra); -int tcp_alloc_ephemeral_l3(uint8_t l3_id, uint16_t pid, port_recv_handler_t handler); -bool tcp_unbind_l3(uint8_t l3_id, uint16_t port, uint16_t pid); -bool tcp_handshake_l3(uint8_t l3_id, uint16_t local_port, net_l4_endpoint *dst, tcp_data *flow_ctx, uint16_t pid, const SocketExtraOptions* extra); +bool tcp_handshake_l3(l3_id_t l3_id, uint16_t local_port, net_l4_endpoint *dst, tcp_data *flow_ctx, const SocketOptions* extra); +void tcp_flow_apply_socket_options(tcp_data *flow_ctx, const SocketOptions* extra, uint32_t apply_mask); tcp_result_t tcp_flow_send(tcp_data *flow_ctx); +tcp_result_t tcp_flow_flush(tcp_data *flow_ctx); tcp_result_t tcp_flow_close(tcp_data *flow_ctx); +void tcp_flow_abort(tcp_data *flow_ctx); +bool tcp_flow_is_closed(tcp_data *flow_ctx); +tcp_result_t tcp_flow_release_closed(tcp_data *flow_ctx); +int64_t tcp_flow_read(tcp_data *flow_ctx, void *buf, uint64_t len); +uint32_t tcp_flow_readable(tcp_data *flow_ctx); +bool tcp_flow_recv_closed(tcp_data *flow_ctx); -void tcp_flow_window_update(tcp_data *flow_ctx); -void tcp_flow_on_app_read(tcp_data *flow_ctx, uint32_t bytes_read); - -void tcp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, uint8_t l3_id, uintptr_t ptr, uint32_t len); - -void tcp_tick_all(uint32_t elapsed_ms); -int tcp_daemon_entry(int argc, char *argv[]); +void tcp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, l3_id_t l3_id, netpkt_t* pkt); +void tcp_pmtu_update(l3_id_t l3_id, ip_version_t ver, const void* local_ip, const void* remote_ip, uint16_t local_port, uint16_t remote_port, uint16_t mtu); +void tcp_l3_retire(l3_id_t l3_id, uint32_t l3_generation); +void tcp_l3_mtu_reduce(l3_id_t l3_id, uint32_t l3_generation, uint16_t mtu); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/tcp/tcp_core.c b/kernel/networking/transport_layer/tcp/tcp_core.c index 3d6f9a3b..2f6d53a5 100644 --- a/kernel/networking/transport_layer/tcp/tcp_core.c +++ b/kernel/networking/transport_layer/tcp/tcp_core.c @@ -1,5 +1,4 @@ #include "tcp_internal.h" -#include "networking/port_manager.h" #include "networking/interface_manager.h" #include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv6.h" @@ -7,155 +6,507 @@ #include "networking/internet_layer/ipv6_utils.h" #include "std/memory.h" #include "math/rng.h" +#include "random/random.h" #include "syscalls/syscalls.h" #include "networking/transport_layer/trans_utils.h" +#include "networking/transport_layer/socket_core.h" +#include "networking/transport_layer/socket_bind.h" +#include "exceptions/irq.h" tcp_flow_t *tcp_flows[MAX_TCP_FLOWS]; +uint16_t tcp_active_flows[MAX_TCP_FLOWS]; +uint16_t tcp_active_count; +static uint32_t tcp_generation_next = 1; +static uint16_t tcp_alloc_cursor; + +static uint16_t tcp_active_port_lower_bound(uint16_t local_port) { + uint16_t lo = 0; + uint16_t hi = tcp_active_count; + + while (lo < hi) { + uint16_t mid = lo + ((hi - lo) >> 1); + tcp_flow_t *flow = tcp_flows[tcp_active_flows[mid]]; + if (flow->base.local.port < local_port) lo = (uint16_t)(mid + 1); + else hi = mid; + } + return lo; +} -int find_flow(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port){ - for (int i = 0; i < MAX_TCP_FLOWS; i++){ - tcp_flow_t *f = tcp_flows[i]; - if (!f) continue; +static bool tcp_flow_tuple_matches(const tcp_flow_t *flow, uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port) { + if (!flow || !local_ip || !remote_ip || (ver != IP_VER4 && ver != IP_VER6)) return false; + if (flow->base.local.port != local_port || flow->base.remote.port != remote_port) return false; + if (flow->base.local.ver != ver || flow->base.remote.ver != ver) return false; - if (f->state == TCP_STATE_CLOSED) continue; - if (f->local_port != local_port) continue; - - if (f->state == TCP_LISTEN){ - if (remote_ip || remote_port) continue; - if (f->local.ver && f->local.ver != ver) continue; - if (!local_ip) return i; - - size_t l = (size_t)(ver == IP_VER6 ? 16 : 4); - int unspec = 1; - for (size_t k = 0; k < l; ++k){ - if (f->local.ip[k]){ - unspec = 0; - break; - } - } - if (unspec) return i; - if (memcmp(f->local.ip, local_ip, l) == 0) return i; - continue; + size_t ip_len = ver == IP_VER6 ? 16 : 4; + if (memcmp(flow->base.local.ip, local_ip, ip_len) != 0) return false; + if (memcmp(flow->base.remote.ip, remote_ip, ip_len) != 0) return false; + return true; +} + +bool tcp_active_insert_flow(tcp_flow_t *flow) { + if (!flow) return false; + + irq_flags_t irq = irq_save_disable(); + uint16_t slot = flow->base.slot; + if (slot >= MAX_TCP_FLOWS || tcp_flows[slot] != flow || flow->base.retired || flow->base.active_pos != UINT16_MAX || tcp_active_count >= MAX_TCP_FLOWS) { + irq_restore(irq); + return false; + } + + uint16_t local_port = flow->base.local.port; + uint16_t pos = tcp_active_port_lower_bound(local_port); + for (uint16_t n = pos; n < tcp_active_count; ++n) { + uint16_t cur_slot = tcp_active_flows[n]; + tcp_flow_t *cur = cur_slot < MAX_TCP_FLOWS ? tcp_flows[cur_slot] : NULL; + if (!cur) continue; + if (cur->base.local.port > local_port) break; + if (cur->base.retired || cur->base.state == TCP_STATE_CLOSED) continue; + if (tcp_flow_tuple_matches(cur, local_port, flow->base.local.ver, flow->base.local.ip, flow->base.remote.ip, flow->base.remote.port)) { + irq_restore(irq); + return false; } + } + + while (pos < tcp_active_count) { + uint16_t cur_slot = tcp_active_flows[pos]; + tcp_flow_t *cur = cur_slot < MAX_TCP_FLOWS ? tcp_flows[cur_slot] : NULL; + uint16_t cur_port = cur ? cur->base.local.port : UINT16_MAX; + if (cur_port != local_port || cur_slot > slot) break; + pos++; + } + + for (uint16_t i = tcp_active_count; i > pos; i--) { + uint16_t moved_slot = tcp_active_flows[i-1]; + tcp_active_flows[i] = moved_slot; + if (moved_slot < MAX_TCP_FLOWS && tcp_flows[moved_slot]) tcp_flows[moved_slot]->base.active_pos = i; + } + + tcp_active_flows[pos] = slot; + flow->base.active_pos = pos; + tcp_active_count++; + irq_restore(irq); + return true; +} + +tcp_flow_t *tcp_flow_acquire_match(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port){ + irq_flags_t irq = irq_save_disable(); + uint16_t start = tcp_active_port_lower_bound(local_port); + for (uint16_t n = start; n < tcp_active_count; n++){ + uint16_t slot = tcp_active_flows[n]; + tcp_flow_t *f = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + if (!f) continue; - if (!remote_ip) continue; - if (!local_ip) continue; - if (f->remote.ver != ver) continue; - if (f->remote.port != remote_port) continue; + if (f->base.local.port > local_port) break; + if (f->base.retired || f->base.state == TCP_STATE_CLOSED) continue; + if (f->base.local.port != local_port) continue; - size_t l = (size_t)(ver == IP_VER6 ? 16 : 4); - if (memcmp(f->local.ip, local_ip, l) != 0) continue; - if (memcmp(f->remote.ip, remote_ip, l) != 0) continue; + if (!tcp_flow_tuple_matches(f, local_port, ver, local_ip, remote_ip, remote_port)) continue; + if (!f->base.refs || f->base.refs == UINT16_MAX) break; - return i; + f->base.refs++; + irq_restore(irq); + return f; } - return -1; + irq_restore(irq); + return NULL; } -tcp_data *tcp_get_ctx(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port){ - int idx = find_flow(local_port, ver, local_ip, remote_ip, remote_port); +bool tcp_bind_conflicts(const SockBindSpec* spec, uint16_t port, bool reuseaddr) { + if (!spec || !port) return true; + + bool conflict = false; + irq_flags_t irq = irq_save_disable(); + uint16_t start = tcp_active_port_lower_bound(port); + for (uint16_t n = start; n < tcp_active_count; n++) { + uint16_t slot = tcp_active_flows[n]; + tcp_flow_t* flow = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + if (!flow) continue; + if (flow->base.local.port > port) break; + if (flow->base.retired || flow->base.state == TCP_STATE_CLOSED) continue; + uint8_t ifindex = 0; + if (flow->base.local.ver == IP_VER4) { + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(flow->base.l3_id); + if (v4 && v4->l2) ifindex = v4->l2->ifindex; + } else if (flow->base.local.ver == IP_VER6) { + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(flow->base.l3_id); + if (v6 && v6->l2) ifindex = v6->l2->ifindex; + } + if (flow->base.local.port != port || !socket_bind_match_score(spec, flow->base.local.ver, flow->base.l3_id, ifindex, flow->base.local.ip)) continue; - if (idx < 0) return NULL; - return &tcp_flows[idx]->ctx; + bool allowed = reuseaddr && flow->ip.reuseaddr; + if (!allowed) { + conflict = true; + break; + } + } + irq_restore(irq); + return conflict; } -static void clear_txq(tcp_flow_t *f){ - for (int i = 0; i < TCP_MAX_TX_SEGS; i++){ - tcp_tx_seg_t *s = &f->txq[i]; - - if (s->used && s->buf && s->len) free_sized((void *)s->buf, s->len); - - s->used = 0; - s->syn = 0; - s->fin = 0; - s->rtt_sample = 0; - s->retransmit_cnt = 0; - s->seq = 0; - s->len = 0; - s->buf = 0; - s->timer_ms = 0; - s->timeout_ms = 0; +bool tcp_get_ctx(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port, tcp_data *out_ctx){ + if (!out_ctx) return false; + tcp_flow_t *flow = tcp_flow_acquire_match(local_port, ver, local_ip, remote_ip, remote_port); + if (!flow) return false; + *out_ctx = flow->base.ctx; + tcp_flow_put(flow); + return true; } + +tcp_flow_t *tcp_flow_from_ctx(tcp_data *flow_ctx) { + if (!flow_ctx) return NULL; + if (flow_ctx->flow_index >= MAX_TCP_FLOWS) return NULL; + + irq_flags_t irq = irq_save_disable(); + tcp_flow_t *flow = tcp_flows[flow_ctx->flow_index]; + if (!flow || flow->base.retired || flow->base.generation != flow_ctx->flow_generation || !flow->base.refs || flow->base.refs == UINT16_MAX) { + irq_restore(irq); + return NULL; + } + + flow->base.refs++; + irq_restore(irq); + return flow; } -static void clear_reass(tcp_flow_t *f){ - for (int i = 0; i < TCP_REASS_MAX_SEGS; i++){ - if (f->reass[i].buf && f->reass[i].end > f->reass[i].seq){ - uint32_t l = f->reass[i].end - f->reass[i].seq; - free_sized((void *)f->reass[i].buf, l); +void tcp_flow_apply_options(tcp_flow_t *flow, const SocketOptions* extra, uint32_t apply_mask) { + if (!flow) return; + SocketOptions defaults = {0}; + defaults.flags = SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK; + if (!extra) extra = &defaults; + uint32_t flags = extra->flags; + + if (apply_mask & (SOCK_OPT_KEEPALIVE | SOCK_OPT_KEEPALIVE_INTERVAL)) { + flow->timer.keepalive_on = (flags & SOCK_OPT_KEEPALIVE) ? 1 : 0; + flow->timer.keepalive_ms = flow->timer.keepalive_on ? (extra->keepalive_ms ? extra->keepalive_ms : SOCKET_DEFAULT_KEEPALIVE_MS) : 0; + flow->timer.keepalive_idle_ms = 0; + if (flow->base.active_pos != UINT16_MAX) tcp_daemon_kick(); + } + + if (apply_mask & SOCK_OPT_TCP_NO_DELAY) { + flow->tx.nodelay = (flags & SOCK_OPT_TCP_NO_DELAY) ? 1 : 0; + if (flow->tx.nodelay && flow->tx.nagle_len && flow->tx.snd_wnd > 0 && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + } + + if (apply_mask & SOCK_OPT_SEND_BUF_SIZE) { + uint32_t queued_limit = (flags & SOCK_OPT_SEND_BUF_SIZE) && extra->send_buf_size ? extra->send_buf_size : TCP_TX_MAX_BYTES_PER_FLOW; + if (queued_limit > TCP_TX_MAX_BYTES_PER_FLOW) queued_limit = TCP_TX_MAX_BYTES_PER_FLOW; + flow->tx.queued_limit = queued_limit; + } + + if (apply_mask & SOCK_OPT_TCP_MAXSEG) { + flow->tx.configured_mss = (flags & SOCK_OPT_TCP_MAXSEG) && extra->tcp_maxseg ? extra->tcp_maxseg : 0; + tcp_update_mss(flow); + } + + if (apply_mask & (SOCK_OPT_TCP_SACK | SOCK_OPT_TCP_DSACK)) { + flow->tx.sack_enabled = (flags & SOCK_OPT_TCP_SACK) ? 1 : 0; + flow->tx.dsack_enabled = flow->tx.sack_enabled && (flags & SOCK_OPT_TCP_DSACK) ? 1 : 0; + if (!flow->tx.sack_enabled) { + flow->tx.sack_ok = 0; + flow->tx.sack_range_count = 0; + flow->tx.sack_retransmitted_count = 0; + flow->tx.sack_rescue_rxt = 0; + flow->tx.sack_rescue_valid = 0; + flow->rx.dsack_pending = 0; + flow->rx.dsack_left = 0; + flow->rx.dsack_right = 0; } + } + + if (apply_mask & SOCK_OPT_TTL) flow->ip.ttl = (flags & SOCK_OPT_TTL) ? extra->ttl : 0; + if (apply_mask & SOCK_OPT_DONTFRAG) flow->ip.dontfrag = (flags & SOCK_OPT_DONTFRAG) ? 1 : 0; + if (apply_mask & SOCK_OPT_DONTROUTE) flow->ip.dontroute = (flags & SOCK_OPT_DONTROUTE) ? 1 : 0; + if (apply_mask & SOCK_OPT_REUSEADDR) flow->ip.reuseaddr = (flags & SOCK_OPT_REUSEADDR) ? 1 : 0; +} + +void tcp_flow_apply_socket_options(tcp_data *flow_ctx, const SocketOptions* extra, uint32_t apply_mask) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return; + tcp_flow_apply_options(flow, extra, apply_mask); + tcp_flow_put(flow); +} + +static void tcp_flow_clear_storage(tcp_flow_t *flow) { + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) tcp_tx_seg_clear(flow, &flow->tx.txq[i]); + flow->tx.queued_bytes = 0; + + if (flow->tx.nagle_buf) { + uintptr_t buf = flow->tx.nagle_buf; + flow->tx.nagle_buf = 0; + release((void*)buf); + } + flow->tx.nagle_len = 0; + flow->tx.nagle_cap = 0; + flow->tx.nagle_timer_ms = 0; + flow->tx.nagle_flushing = 0; + flow->tx.nagle_appending = 0; + flow->tx.nagle_psh = 0; + + if (flow->rx.reass_count || flow->rx.rcv_ooo_used) tcp_account_ooo_remove(flow->rx.rcv_ooo_used, flow->rx.reass_count); + if (flow->rx.rcv_buf) { + uintptr_t buf = flow->rx.rcv_buf; + flow->rx.rcv_buf = 0; + release((void*)buf); + } + flow->rx.rcv_base = flow->rx.rcv_nxt; + flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + flow->rx.rcv_ooo_used = 0; + flow->rx.fin_pending = 0; + flow->rx.reass_count = 0; + memset(flow->rx.reass, 0, sizeof(flow->rx.reass)); + if (flow->base.listener) { + socket_core_put(flow->base.listener); + flow->base.listener = NULL; + } +} + +void tcp_flow_put(tcp_flow_t *f) { + if (!f) return; + + irq_flags_t irq = irq_save_disable(); + if (!f->base.refs) { + irq_restore(irq); + return; + } + + f->base.refs--; + bool free_now = f->base.refs == 0; + irq_restore(irq); + + if (!free_now) return; + + tcp_flow_clear_storage(f); + memset(f, 0, sizeof(*f)); + release(f); +} - f->reass[i].seq = 0; - f->reass[i].end = 0; - f->reass[i].buf = 0; +void tcp_enter_time_wait(tcp_flow_t *flow) { + if (!flow) return; + + uint32_t timewait = 0; + tcp_flow_t *oldest = NULL; + uint32_t oldest_ms = 0; + + irq_flags_t irq = irq_save_disable(); + for (uint16_t n = 0; n < tcp_active_count; n++) { + uint16_t slot = tcp_active_flows[n]; + tcp_flow_t *f = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + if (!f || f->base.retired || f->base.state != TCP_TIME_WAIT) continue; + timewait++; + if ((!oldest || f->timer.time_wait_ms > oldest_ms) && f != flow) { + oldest = f; + oldest_ms = f->timer.time_wait_ms; + } + } + if (timewait >= TCP_TIMEWAIT_MAX_GLOBAL && oldest && oldest->base.refs && oldest->base.refs != UINT16_MAX) oldest->base.refs++; + else oldest = NULL; + irq_restore(irq); + + if (timewait >= TCP_TIMEWAIT_MAX_GLOBAL && oldest) { + tcp_stats.timewait_reap_oldest++; + tcp_free_flow(oldest); + tcp_flow_put(oldest); } - f->reass_count = 0; - f->rcv_buf_used = 0; + memset(&flow->timer, 0, sizeof(flow->timer)); + flow->base.state = TCP_TIME_WAIT; + tcp_flow_clear_storage(flow); + tcp_daemon_kick(); } tcp_flow_t *tcp_alloc_flow(void){ - for (int i = 0; i < MAX_TCP_FLOWS; i++){ - if (tcp_flows[i]) continue; + tcp_flow_t *f = (tcp_flow_t *)zalloc(sizeof(tcp_flow_t)); + if (!f) return NULL; + + irq_flags_t irq = irq_save_disable(); + int32_t slot = -1; + for (uint32_t n = 0; n < MAX_TCP_FLOWS; n++) { + uint16_t i = (uint16_t)((tcp_alloc_cursor + n) % MAX_TCP_FLOWS); + if (!tcp_flows[i]) { + slot = i; + tcp_alloc_cursor = (uint16_t)((i + 1) % MAX_TCP_FLOWS); + break; + } + } - tcp_flow_t *f = (tcp_flow_t *)malloc(sizeof(tcp_flow_t)); - if (!f) return NULL; - memset(f, 0, sizeof(tcp_flow_t)); - tcp_flows[i] = f; + if (slot < 0 || tcp_active_count >= MAX_TCP_FLOWS) { + irq_restore(irq); + release(f); + return NULL; + } - f->rto = TCP_INIT_RTO; - f->rcv_wnd_max = TCP_DEFAULT_RCV_BUF; - f->rcv_wnd = f->rcv_wnd_max; + tcp_flows[slot] = f; + f->base.slot = (uint16_t)slot; + f->base.active_pos = UINT16_MAX; + f->base.generation = tcp_generation_next++; + if (!tcp_generation_next) tcp_generation_next = 1; + f->base.ctx.flow_index = f->base.slot; + f->base.ctx.flow_generation = f->base.generation; + f->base.refs = 2; + irq_restore(irq); + + f->tx.rto = TCP_INIT_RTO; + f->rx.rcv_wnd_max = tcp_clamp_rcvbuf(TCP_DEFAULT_RCV_BUF); + f->rx.rcv_wnd = f->rx.rcv_wnd_max; + + f->tx.path_mss = TCP_DEFAULT_MSS; + f->tx.peer_mss = 0; + f->tx.advertised_mss = TCP_DEFAULT_MSS; + f->tx.mss = TCP_DEFAULT_MSS; + f->tx.sack_enabled = 1; + f->tx.dsack_enabled = 1; + f->tx.cwnd = tcp_initial_cwnd(f->tx.mss); + f->tx.ssthresh = TCP_RECV_WINDOW; + return f; +} - f->mss = TCP_DEFAULT_MSS; - f->cwnd = f->mss; - f->ssthresh = TCP_RECV_WINDOW; +void tcp_free_flow(tcp_flow_t *flow) { + if (!flow) return; + irq_flags_t irq = irq_save_disable(); + uint16_t slot = flow->base.slot; + if (slot >= MAX_TCP_FLOWS || tcp_flows[slot] != flow || flow->base.retired) { + irq_restore(irq); + return; + } - clear_reass(f); - clear_txq(f); + flow->base.retired = 1; + flow->base.state = TCP_STATE_CLOSED; - return f; + if (tcp_active_count != 0 && flow->base.active_pos != UINT16_MAX) { + uint16_t pos = flow->base.active_pos; + + if (pos >= tcp_active_count || tcp_active_flows[pos] != slot) { + pos = 0; + while (pos < tcp_active_count && tcp_active_flows[pos] != slot) pos++; + } + + if (pos < tcp_active_count) { + for (uint16_t i = pos; i + 1 < tcp_active_count; i++) { + uint16_t moved = tcp_active_flows[i+1]; + tcp_active_flows[i] = moved; + if (moved < MAX_TCP_FLOWS && tcp_flows[moved]) tcp_flows[moved]->base.active_pos = i; + } + tcp_active_count--; + tcp_active_flows[tcp_active_count] = 0; + } + + flow->base.active_pos = UINT16_MAX; } - return NULL; + tcp_flows[slot] = NULL; + if (slot < tcp_alloc_cursor) tcp_alloc_cursor = slot; + irq_restore(irq); + + tcp_flow_put(flow); } -void tcp_free_flow(int idx) { - if (idx < 0 || idx >= MAX_TCP_FLOWS) return; +void tcp_flow_abort(tcp_data *flow_ctx) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return; + + tcp_state_t state = flow->base.state; + if (state != TCP_STATE_CLOSED && state != TCP_TIME_WAIT && state != TCP_SYN_SENT) { + tcp_hdr_t rst_hdr; + rst_hdr.src_port = bswap16(flow->base.local.port); + rst_hdr.dst_port = bswap16(flow->base.remote.port); + rst_hdr.sequence = bswap32(flow->tx.snd_nxt); + rst_hdr.ack = 0; + rst_hdr.flags = (uint8_t)(1u << RST_F); + rst_hdr.window = 0; + rst_hdr.urgent_ptr = 0; + tcp_send_flow_segment(flow, &rst_hdr, NULL, 0, NULL, 0); + } - tcp_flow_t *f = tcp_flows[idx]; - if (!f) return; + tcp_free_flow(flow); + tcp_flow_put(flow); +} - clear_txq(f); - clear_reass(f); +void tcp_l3_retire(l3_id_t l3_id, uint32_t l3_generation) { + if (!l3_id || !l3_generation) return; - memset(f, 0, sizeof(*f)); + for (uint16_t slot = 0; slot < MAX_TCP_FLOWS; slot++) { + irq_flags_t irq = irq_save_disable(); + tcp_flow_t *flow = tcp_flows[slot]; + if (!flow || flow->base.retired || flow->base.l3_id != l3_id || flow->base.l3_generation != l3_generation || !flow->base.refs || flow->base.refs == UINT16_MAX) { + irq_restore(irq); + continue; + } + flow->base.refs++; + irq_restore(irq); - f->state = TCP_STATE_CLOSED; + tcp_free_flow(flow); + tcp_flow_put(flow); + } +} - f->rto = TCP_INIT_RTO; +void tcp_l3_mtu_reduce(l3_id_t l3_id, uint32_t l3_generation, uint16_t mtu) { + if (!l3_id || !l3_generation || !mtu) return; - f->rcv_wnd_max = TCP_DEFAULT_RCV_BUF; - f->rcv_wnd = f->rcv_wnd_max; + uint32_t headers = 0; + if (l3_ipv4_find_by_id(l3_id)) headers = 40; + else if (l3_ipv6_find_by_id(l3_id)) headers = 60; + else return; - f->mss = TCP_DEFAULT_MSS; - f->cwnd = f->mss; - f->ssthresh = TCP_RECV_WINDOW; + uint32_t path_mss = mtu > headers ? (uint32_t)mtu - headers : 1; - free_sized(f, sizeof(*f)); - tcp_flows[idx] = NULL; + for (uint16_t slot = 0; slot < MAX_TCP_FLOWS; slot++) { + irq_flags_t irq = irq_save_disable(); + tcp_flow_t *flow = tcp_flows[slot]; + if (!flow || flow->base.retired || flow->base.l3_id != l3_id || flow->base.l3_generation != l3_generation || !flow->base.refs || flow->base.refs == UINT16_MAX) { + irq_restore(irq); + continue; + } + flow->base.refs++; + irq_restore(irq); + + if (!flow->tx.path_mss || path_mss < flow->tx.path_mss) { + flow->tx.path_mss = path_mss; + tcp_update_mss(flow); + } + tcp_flow_put(flow); + } +} + +bool tcp_flow_is_closed(tcp_data *flow_ctx) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return true; + bool closed = flow->base.state == TCP_STATE_CLOSED || flow->base.state == TCP_TIME_WAIT; + tcp_flow_put(flow); + return closed; } -bool tcp_send_segment(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len, const ip_tx_opts_t *txp, uint8_t ttl, uint8_t dontfrag){ - if (!hdr) return false; +tcp_result_t tcp_flow_release_closed(tcp_data *flow_ctx) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return TCP_INVALID; + if (flow->base.state == TCP_TIME_WAIT) { + tcp_flow_put(flow); + return TCP_OK; + } + if (flow->base.state != TCP_STATE_CLOSED) { + tcp_flow_put(flow); + return TCP_BUSY; + } + tcp_free_flow(flow); + tcp_flow_put(flow); + return TCP_OK; +} + +bool tcp_send_segment(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len, const ip_tx_opts_t *txp, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute) { + if (!hdr || !src_ip_addr || !dst_ip_addr) return false; + if (ver != IP_VER4 && ver != IP_VER6) return false; if (opts_len & 3u) return false; if (opts_len > 40u) return false; - - uint16_t tcp_len = (uint16_t)(sizeof(tcp_hdr_t) + opts_len + payload_len); - uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)(ver == IP_VER4 ? sizeof(ipv4_hdr_t) : sizeof(ipv6_hdr_t)); + if ((opts_len && !opts) || (payload_len && !payload)) return false; + + uint32_t ip_header_len = ver == IP_VER4 ? (uint32_t)sizeof(ipv4_hdr_t) : (uint32_t)sizeof(ipv6_hdr_t); + uint32_t max_tcp_len = ver == IP_VER4 ? UINT16_MAX - ip_header_len : UINT16_MAX; + uint32_t tcp_len32 = (uint32_t)sizeof(tcp_hdr_t) + (uint32_t)opts_len + (uint32_t)payload_len; + if (tcp_len32 > max_tcp_len || tcp_len32 > NETPKT_MAX_ALLOC) return false; + uint16_t tcp_len = (uint16_t)tcp_len32; + uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + ip_header_len; netpkt_t *pkt = netpkt_alloc(tcp_len, headroom, 0); if (!pkt) return false; uint8_t *segment = (uint8_t*)netpkt_put(pkt, tcp_len); @@ -176,23 +527,23 @@ bool tcp_send_segment(ip_version_t ver, const void *src_ip_addr, const void *dst if (payload_len && payload) memcpy(segment + sizeof(tcp_hdr_t) + opts_len, payload, payload_len); if (ver == IP_VER4){ - uint32_t s = *(const uint32_t *)src_ip_addr; - uint32_t d = *(const uint32_t *)dst_ip_addr; - - ((tcp_hdr_t *)segment)->checksum = tcp_checksum_ipv4(segment, tcp_len, s, d); - ipv4_send_packet(d, 6, pkt, (const ipv4_tx_opts_t *)txp, ttl, dontfrag); - return true; - } else if (ver == IP_VER6){ - ((tcp_hdr_t *)segment)->checksum = tcp_checksum_ipv6(segment, tcp_len, (const uint8_t *)src_ip_addr, (const uint8_t *)dst_ip_addr); - ipv6_send_packet((const uint8_t *)dst_ip_addr, 6, pkt, (const ipv6_tx_opts_t *)txp, ttl, dontfrag); - return true; + uint32_t s = 0; + uint32_t d = 0; + memcpy(&s, src_ip_addr, sizeof(s)); + memcpy(&d, dst_ip_addr, sizeof(d)); + + h.checksum = tcp_checksum_ipv4(segment, tcp_len, s, d); + memcpy(segment, &h, sizeof(h)); + return ipv4_send_packet(d, PROTO_TCP, pkt, txp, ttl, dontfrag, dontroute); } - netpkt_unref(pkt); - return false; + h.checksum = tcp_checksum_ipv6(segment, tcp_len, (const uint8_t *)src_ip_addr, (const uint8_t *)dst_ip_addr); + memcpy(segment, &h, sizeof(h)); + return ipv6_send_packet((const uint8_t *)dst_ip_addr, PROTO_TCP, pkt, txp, ttl, dontfrag, dontroute); } -void tcp_send_reset(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, bool ack_valid){ +void tcp_send_reset(l3_id_t l3_id, ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, bool ack_valid) { + if (!l3_id) return; tcp_hdr_t rst_hdr; rst_hdr.src_port = bswap16(src_port); @@ -211,336 +562,180 @@ void tcp_send_reset(ip_version_t ver, const void *src_ip_addr, const void *dst_i rst_hdr.window = 0; rst_hdr.urgent_ptr = 0; - if (ver == IP_VER4){ - ipv4_tx_opts_t tx; - - tcp_build_tx_opts_from_local_v4(src_ip_addr, &tx); - tcp_send_segment(IP_VER4, src_ip_addr, dst_ip_addr, &rst_hdr, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, 0, 0); - } else if (ver == IP_VER6){ - ipv6_tx_opts_t tx; - - tcp_build_tx_opts_from_local_v6(src_ip_addr, &tx); - tcp_send_segment(IP_VER6, src_ip_addr, dst_ip_addr, &rst_hdr, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, 0, 0); - } + ip_tx_opts_t tx; + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = l3_id; + (void)tcp_send_segment(ver, src_ip_addr, dst_ip_addr, &rst_hdr, NULL, 0, NULL, 0, &tx, 0, 0, 0); } void tcp_rtt_update(tcp_flow_t *flow, uint32_t sample_ms){ if (sample_ms == 0) sample_ms = 1; - if (!flow->rtt_valid){ - flow->srtt = sample_ms; - flow->rttvar = sample_ms / 2; + if (!flow->tx.rtt_valid){ + flow->tx.srtt = sample_ms; + flow->tx.rttvar = sample_ms / 2; - uint32_t rto = flow->srtt + (flow->rttvar << 2); + uint32_t rto = flow->tx.srtt + (flow->tx.rttvar << 2); if (rto < TCP_MIN_RTO) rto = TCP_MIN_RTO; if (rto > TCP_MAX_RTO) rto = TCP_MAX_RTO; - flow->rto = rto; - flow->rtt_valid = 1; + flow->tx.rto = rto; + flow->tx.rtt_valid = 1; return; } - uint32_t srtt = flow->srtt; - uint32_t rttvar = flow->rttvar; + uint32_t srtt = flow->tx.srtt; + uint32_t rttvar = flow->tx.rttvar; uint32_t diff = srtt > sample_ms ? srtt - sample_ms : sample_ms - srtt; uint32_t new_rttvar = (uint32_t)((3 * (uint64_t)rttvar + (uint64_t)diff) >> 2); uint32_t new_srtt = (uint32_t)(((uint64_t)7 * srtt + sample_ms) >> 3); - flow->srtt = new_srtt; - flow->rttvar = new_rttvar; + flow->tx.srtt = new_srtt; + flow->tx.rttvar = new_rttvar; uint32_t rto = new_srtt + (new_rttvar << 2); if (rto < TCP_MIN_RTO) rto = TCP_MIN_RTO; if (rto > TCP_MAX_RTO) rto = TCP_MAX_RTO; - flow->rto = rto; -} - -bool tcp_bind_l3(uint8_t l3_id, uint16_t port, uint16_t pid, port_recv_handler_t handler, const SocketExtraOptions* extra){ - ip_version_t ver = l3_is_v6_from_id(l3_id) ? IP_VER6 : IP_VER4; - - port_manager_t *pm = (ver == IP_VER6) ? ifmgr_pm_v6(l3_id) : ifmgr_pm_v4(l3_id); - - if (!pm) return false; - if (!port_bind_manual(pm, PROTO_TCP, port, pid, handler)) return false; - - int listen_idx = find_flow(port, ver, NULL, NULL, 0); - if (listen_idx >= 0) return true; - - tcp_flow_t *f = tcp_alloc_flow(); - if (!f) { - (void)port_unbind(pm, PROTO_TCP, port, pid); - return false; - } - if (f){ - f->local_port = port; - f->l3_id = l3_id; - - f->local.ver = l3_is_v6_from_id(l3_id) ? IP_VER6 : IP_VER4; - memset(f->local.ip, 0, sizeof(f->local.ip)); - f->local.port = port; - - f->remote.ver = 0; - memset(f->remote.ip, 0, sizeof(f->remote.ip)); - f->remote.port = 0; - - f->state = TCP_LISTEN; - - f->ctx.sequence = 0; - f->ctx.ack = 0; - f->ctx.flags = 0; - - f->rcv_wnd_max = TCP_DEFAULT_RCV_BUF; - if (extra && (extra->flags & SOCK_OPT_BUF_SIZE) && extra->buf_size) f->rcv_wnd_max = extra->buf_size; - f->rcv_buf_used = 0; - f->rcv_adv_edge = 0; - - f->ip_ttl = extra && (extra->flags & SOCK_OPT_TTL) ? extra->ttl : 0; - f->ip_dontfrag = extra && (extra->flags & SOCK_OPT_DONTFRAG) ? 1 : 0; - f->keepalive_on = extra && (extra->flags & SOCK_OPT_KEEPALIVE) ? 1 : 0; - f->keepalive_ms = extra && (extra->flags & SOCK_OPT_KEEPALIVE) ? extra->keepalive_ms : 0; - f->keepalive_idle_ms = 0; - - f->mss = TCP_DEFAULT_MSS; - if (f->rcv_wnd_max > 65535u) { - f->ws_send = 8; - f->ws_recv = 0; - f->ws_ok = 1; - } else { - f->ws_send = 0; - f->ws_recv = 0; - f->ws_ok = 0; - } - f->sack_ok = 1; - - (void)tcp_calc_adv_wnd_field(f, 1); - - f->ctx.options.ptr = 0; - f->ctx.options.size = 0; - f->ctx.payload.ptr = 0; - f->ctx.payload.size = 0; - - f->ctx.expected_ack = 0; - f->ctx.ack_received = 0; - - f->time_wait_ms = 0; - f->fin_wait2_ms = 0; - } - - return true; -} - -int tcp_alloc_ephemeral_l3(uint8_t l3_id, uint16_t pid, port_recv_handler_t handler){ - - port_manager_t *pm = l3_is_v6_from_id(l3_id) ? ifmgr_pm_v6(l3_id) : ifmgr_pm_v4(l3_id); - if (!pm) return -1; - - if (!pm) return -1; - return port_alloc_ephemeral(pm, PROTO_TCP, pid, handler); + flow->tx.rto = rto; } -bool tcp_unbind_l3(uint8_t l3_id, uint16_t port, uint16_t pid){ - ip_version_t ver = l3_is_v6_from_id(l3_id) ? IP_VER6 : IP_VER4; - - port_manager_t *pm = (ver == IP_VER6) ? ifmgr_pm_v6(l3_id) : ifmgr_pm_v4(l3_id); - if (!pm) return false; - - bool res = port_unbind(pm, PROTO_TCP, port, pid); - - if (res){ - for (int i = 0; i < MAX_TCP_FLOWS; i++){ - tcp_flow_t *f = tcp_flows[i]; - if (!f) continue; - if (f->state==TCP_LISTEN && f->local_port==port && f->local.ver==ver) tcp_free_flow(i); - } - } - - return res; -} - -bool tcp_handshake_l3(uint8_t l3_id, uint16_t local_port, net_l4_endpoint *dst, tcp_data *flow_ctx, uint16_t pid, const SocketExtraOptions* extra){ - (void)pid; +bool tcp_handshake_l3(l3_id_t l3_id, uint16_t local_port, net_l4_endpoint *dst, tcp_data *flow_ctx, const SocketOptions* extra) { + if (!dst || !flow_ctx || !local_port || !dst->port || (dst->ver != IP_VER4 && dst->ver != IP_VER6)) return false; tcp_flow_t *flow = tcp_alloc_flow(); if (!flow) return false; + flow->base.l3_id = l3_id; - int idx = -1; - for (int i = 0; i < MAX_TCP_FLOWS; i++) { - if (tcp_flows[i] == flow) { - idx = i; - break; - } - } - if (idx < 0) return false; - - flow->local_port = local_port; - flow->l3_id = l3_id; - - flow->remote.ver = dst->ver; - memcpy(flow->remote.ip, dst->ip, (size_t)(dst->ver == IP_VER6 ? 16 : 4)); - flow->remote.port = dst->port; + flow->base.remote.ver = dst->ver; + memcpy(flow->base.remote.ip, dst->ip, (size_t)(dst->ver == IP_VER6 ? 16 : 4)); + flow->base.remote.port = dst->port; if (dst->ver == IP_VER4){ l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(l3_id); if (!v4 || !v4->ip){ - tcp_free_flow(idx); + tcp_free_flow(flow); + tcp_flow_put(flow); return false; } - make_ep(v4->ip, local_port, IP_VER4, &flow->local); + flow->base.l3_generation = v4->generation; + make_ep(&v4->ip, local_port, IP_VER4, &flow->base.local); } else{ l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(l3_id); if (!v6 || ipv6_is_unspecified(v6->ip)){ - tcp_free_flow(idx); + tcp_free_flow(flow); + tcp_flow_put(flow); return false; } - flow->local.ver = IP_VER6; - memset(flow->local.ip, 0, sizeof(flow->local.ip)); - memcpy(flow->local.ip, v6->ip, sizeof(flow->local.ip)); - flow->local.port = local_port; + flow->base.l3_generation = v6->generation; + flow->base.local.ver = IP_VER6; + memset(flow->base.local.ip, 0, sizeof(flow->base.local.ip)); + memcpy(flow->base.local.ip, v6->ip, sizeof(flow->base.local.ip)); + flow->base.local.port = local_port; } - flow->state = TCP_SYN_SENT; - flow->retries = TCP_SYN_RETRIES; + flow->base.state = TCP_SYN_SENT; + flow->base.active_open = 1; rng_t rng; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&rng, virt_timer); + rng_init_random(&rng); uint32_t iss = rng_next32(&rng); - flow->ctx.sequence = iss; - flow->ctx.ack = 0; - - flow->rcv_nxt = 0; - flow->rcv_buf_used = 0; - flow->rcv_wnd_max = TCP_DEFAULT_RCV_BUF; - if (extra && (extra->flags & SOCK_OPT_BUF_SIZE) && extra->buf_size) flow->rcv_wnd_max = extra->buf_size; - flow->rcv_adv_edge = 0; - - flow->mss = tcp_calc_mss_for_l3(l3_id, dst->ver, dst->ip); - - if (flow->rcv_wnd_max > 65535u) { - flow->ws_send = 8; - flow->ws_recv = 0; - flow->ws_ok = 1; + flow->base.ctx.sequence = iss; + flow->base.ctx.ack = 0; + + flow->rx.rcv_nxt = 0; + flow->rx.rcv_base = 0; + flow->rx.rcv_data_nxt = 0; + flow->rx.rcv_ooo_used = 0; + flow->rx.sack_recent_left = 0; + flow->rx.sack_recent_right = 0; + uint32_t rcvbuf = TCP_DEFAULT_RCV_BUF; + if (extra && (extra->flags & SOCK_OPT_BUF_SIZE) && extra->buf_size) rcvbuf = extra->buf_size; + flow->rx.rcv_wnd_max = tcp_clamp_rcvbuf(rcvbuf); + flow->rx.rcv_adv_edge = 0; + flow->tx.path_mss = tcp_calc_mss_for_l3(l3_id, dst->ver, dst->ip); + flow->tx.peer_mss = 0; + tcp_flow_apply_options(flow, extra, UINT32_MAX); + + if (flow->rx.rcv_wnd_max > 65535u) { + flow->tx.ws_send = 8; + flow->tx.ws_recv = 0; + flow->tx.ws_ok = 1; } else { - flow->ws_send = 0; - flow->ws_recv = 0; - flow->ws_ok = 0; + flow->tx.ws_send = 0; + flow->tx.ws_recv = 0; + flow->tx.ws_ok = 0; } - flow->sack_ok = 1; - - (void)tcp_calc_adv_wnd_field(flow, 1); + flow->tx.sack_ok = flow->tx.sack_enabled; + flow->rx.rcv_buf = 0; + tcp_update_adv_wnd(flow, 1); - flow->ip_ttl = extra && (extra->flags & SOCK_OPT_TTL) ? extra->ttl : 0; - flow->ip_dontfrag = extra && (extra->flags & SOCK_OPT_DONTFRAG) ? 1 : 0; - flow->keepalive_on = extra && (extra->flags & SOCK_OPT_KEEPALIVE) ? 1 : 0; - flow->keepalive_ms = extra && (extra->flags & SOCK_OPT_KEEPALIVE) ? extra->keepalive_ms : 0; - flow->keepalive_idle_ms = 0; + flow->base.ctx.options.ptr = 0; + flow->base.ctx.options.size = 0; + flow->base.ctx.payload.ptr = 0; + flow->base.ctx.payload.size = 0; - flow->ctx.options.ptr = 0; - flow->ctx.options.size = 0; - flow->ctx.payload.ptr = 0; - flow->ctx.payload.size = 0; + flow->base.ctx.flags = (uint8_t)(1u << SYN_F); + flow->base.ctx.expected_ack = iss + 1; + flow->base.ctx.ack_received = 0; - flow->ctx.flags = (uint8_t)(1u << SYN_F); - flow->ctx.expected_ack = iss + 1; - flow->ctx.ack_received = 0; + flow->tx.snd_una = iss; + flow->tx.snd_nxt = iss; + flow->tx.snd_wnd = 0; - flow->snd_una = iss; - flow->snd_nxt = iss; - flow->snd_wnd = 0; + flow->tx.cwnd = tcp_initial_cwnd(flow->tx.mss); + flow->tx.ssthresh = TCP_RECV_WINDOW; + flow->tx.dup_acks = 0; + flow->tx.in_fast_recovery = 0; + flow->tx.recover = 0; + flow->tx.recover_valid = 0; + flow->tx.cwnd_acc = 0; - flow->cwnd = flow->mss; - flow->ssthresh = TCP_RECV_WINDOW; - flow->dup_acks = 0; - flow->in_fast_recovery = 0; - flow->recover = 0; - flow->cwnd_acc = 0; + flow->timer.time_wait_ms = 0; + flow->timer.fin_wait2_ms = 0; - flow->time_wait_ms = 0; - flow->fin_wait2_ms = 0; - - clear_reass(flow); - clear_txq(flow); - - tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow); + tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow, 0); if (!seg){ - tcp_free_flow(idx); + tcp_free_flow(flow); + tcp_flow_put(flow); return false; } seg->syn = 1; seg->fin = 0; + seg->psh = 0; + seg->persist = 0; seg->rtt_sample = 1; + flow->tx.rtt_sample_pending = 1; seg->retransmit_cnt = 0; - seg->seq = flow->snd_nxt; + seg->seq = flow->tx.snd_nxt; seg->len = 0; - seg->buf = 0; + seg->pkt = NULL; + seg->payload_off = 0; seg->timer_ms = 0; - seg->timeout_ms = flow->rto ? flow->rto : TCP_INIT_RTO; - - tcp_hdr_t syn_hdr; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; - syn_hdr.src_port = bswap16(local_port); - syn_hdr.dst_port = bswap16(dst->port); - syn_hdr.sequence = bswap32(flow->snd_nxt); - syn_hdr.ack = bswap32(0); - syn_hdr.flags = (uint8_t)(1u << SYN_F); - syn_hdr.window = flow->ctx.window; - syn_hdr.urgent_ptr = 0; + seg->opts_len = tcp_build_syn_options(seg->opts, (uint16_t)flow->tx.advertised_mss, flow->rx.rcv_wnd_max > 65535u ? flow->tx.ws_send : 0xffu, flow->tx.sack_ok); - uint8_t syn_opts[40]; - uint8_t syn_opts_len = tcp_build_syn_options(syn_opts, (uint16_t)flow->mss, flow->rcv_wnd_max > 65535u ? flow->ws_send : 0xffu, flow->sack_ok); + flow->tx.snd_nxt += 1; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->base.ctx.expected_ack = flow->tx.snd_nxt; - if (dst->ver == IP_VER4){ - ipv4_tx_opts_t tx; - - tcp_build_tx_opts_from_l3(l3_id, &tx); - tcp_send_segment(IP_VER4, flow->local.ip, flow->remote.ip, &syn_hdr, syn_opts, syn_opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } else{ - ipv6_tx_opts_t tx; - - tx.scope = IP_TX_BOUND_L3; - tx.index = l3_id; - tcp_send_segment(IP_VER6, flow->local.ip, flow->remote.ip, &syn_hdr, syn_opts, syn_opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } - - flow->snd_nxt += 1; - flow->ctx.sequence = flow->snd_nxt; - flow->ctx.expected_ack = flow->snd_nxt; - - tcp_daemon_kick(); - - uint64_t waited = 0; - const uint64_t interval = 50; - const uint64_t max_wait = (uint64_t)TCP_MAX_RTO * (uint64_t)(TCP_SYN_RETRIES + 1); - - while (waited < max_wait){ - if (flow->state == TCP_ESTABLISHED){ - tcp_data *ctx = tcp_get_ctx(local_port, dst->ver, flow->local.ip, dst->ip, dst->port); - if (!ctx) return false; - - *flow_ctx = *ctx; - return true; - } - - if (flow->state == TCP_STATE_CLOSED){ - tcp_free_flow(idx); - return false; - } - - msleep(interval); - waited += interval; + if (!tcp_active_insert_flow(flow) || !tcp_send_from_seg(flow, seg)) { + tcp_free_flow(flow); + tcp_flow_put(flow); + return false; } - tcp_free_flow(idx); - return false; + *flow_ctx = flow->base.ctx; + tcp_flow_put(flow); + return true; } \ No newline at end of file diff --git a/kernel/networking/transport_layer/tcp/tcp_internal.h b/kernel/networking/transport_layer/tcp/tcp_internal.h index b11bb212..76e24b38 100644 --- a/kernel/networking/transport_layer/tcp/tcp_internal.h +++ b/kernel/networking/transport_layer/tcp/tcp_internal.h @@ -2,7 +2,6 @@ #include "../tcp.h" #include "types.h" -#include "networking/port_manager.h" #include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv4_utils.h" @@ -12,29 +11,50 @@ #include "math/rng.h" #include "syscalls/syscalls.h" #include "tcp_utils.h" +#include "tcp_limits.h" #ifdef __cplusplus extern "C" { #endif -#define TCP_REASS_MAX_SEGS 32 #define TCP_DEFAULT_MSS 1460 -#define TCP_DEFAULT_RCV_BUF (256u * 1024u) -#define TCP_PERSIST_PROBE_BUFSZ 1 +#define TCP_DEFAULT_PEER_MSS_IPV4 536 +#define TCP_DEFAULT_PEER_MSS_IPV6 1220 +#define TCP_MAX_MSS (NETPKT_MAX_ALLOC - sizeof(ipv4_hdr_t) - sizeof(tcp_hdr_t)) -#define TCP_DELAYED_ACK_MS 200 +#define TCP_SEQ_LT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) < 0) +#define TCP_SEQ_LEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) <= 0) +#define TCP_SEQ_GT(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) > 0) +#define TCP_SEQ_GEQ(a,b) ((int32_t)((uint32_t)(a) - (uint32_t)(b)) >= 0) + +#define TCP_DELAYED_ACK_MS 10 #define TCP_PERSIST_MIN_MS 500 #define TCP_PERSIST_MAX_MS 60000 +#define TCP_NAGLE_FLUSH_THRESHOLD TCP_DEFAULT_MSS +#define TCP_NAGLE_TIMEOUT_MS 10 +#define TCP_CONNECT_TIMEOUT_MS 10000 +#define TCP_SACK_SCOREBOARD_MAX 32 +#define TCP_CHALLENGE_ACK_INTERVAL_MS 100 + +typedef struct { + uint32_t left; + uint32_t right; +} tcp_sack_range_t; typedef struct { uint8_t used; uint8_t syn; uint8_t fin; + uint8_t psh; + uint8_t persist; uint8_t rtt_sample; uint8_t retransmit_cnt; + uint8_t opts_len; + uint8_t opts[40]; uint32_t seq; - uint64_t len; - uintptr_t buf; + uint32_t len; + netpkt_t *pkt; + uint32_t payload_off; uint32_t timer_ms; uint32_t timeout_ms; } tcp_tx_seg_t; @@ -42,36 +62,43 @@ typedef struct { typedef struct { uint32_t seq; uint32_t end; - uintptr_t buf; } tcp_reass_seg_t; typedef struct { - uint16_t local_port; + uint16_t slot; + uint16_t active_pos; + uint32_t generation; net_l4_endpoint local; net_l4_endpoint remote; - uint8_t l3_id; + l3_id_t l3_id; + uint32_t l3_generation; tcp_state_t state; tcp_data ctx; - uint8_t retries; + uint16_t refs; + uint8_t retired; + uint8_t active_open; + struct ksocket *listener; +} tcp_flow_base_t; + +typedef struct { uint32_t snd_wnd; + uint32_t snd_wl1; + uint32_t snd_wl2; uint32_t snd_una; uint32_t snd_nxt; uint32_t srtt; uint32_t rttvar; uint32_t rto; uint8_t rtt_valid; - uint32_t time_wait_ms; - uint32_t fin_wait2_ms; - - uint32_t rcv_nxt; - uint32_t rcv_buf_used; - uint32_t rcv_wnd; - uint32_t rcv_wnd_max; - uint32_t rcv_adv_edge; + uint8_t rtt_sample_pending; + uint32_t rtt_start_ms; uint32_t cwnd; uint32_t ssthresh; uint32_t mss; + uint32_t advertised_mss; + uint32_t path_mss; + uint32_t peer_mss; uint8_t ws_send; uint8_t ws_recv; @@ -79,59 +106,159 @@ typedef struct { uint8_t sack_ok; uint8_t dup_acks; uint8_t in_fast_recovery; + uint8_t recover_valid; uint32_t recover; uint32_t cwnd_acc; + + uint32_t configured_mss; + uint8_t sack_enabled; + uint8_t dsack_enabled; + tcp_sack_range_t sack_ranges[TCP_SACK_SCOREBOARD_MAX]; + tcp_sack_range_t sack_retransmitted_ranges[TCP_SACK_SCOREBOARD_MAX]; + uint8_t sack_range_count; + uint8_t sack_retransmitted_count; + uint32_t sack_rescue_rxt; + uint8_t sack_rescue_valid; - uint8_t persist_active; - uint8_t persist_probe_cnt; - uint32_t persist_timer_ms; - uint32_t persist_timeout_ms; + uint8_t nagle_flushing; + uint8_t nagle_appending; + uint8_t nodelay; + uint8_t nagle_psh; + uint8_t data_tx_valid; - uint8_t delayed_ack_pending; - uint32_t delayed_ack_timer_ms; + uintptr_t nagle_buf; + uint32_t nagle_len; + uint32_t nagle_cap; + uint32_t nagle_timer_ms; + uint32_t last_data_tx_ms; + + tcp_tx_seg_t txq[TCP_MAX_TX_SEGS]; + uint32_t queued_bytes; + uint32_t queued_limit; + uint8_t fin_tx_pending; +} tcp_flow_tx_t; + +typedef struct { + uint32_t rcv_nxt; + uint32_t rcv_base; + uint32_t rcv_data_nxt; + uintptr_t rcv_buf; + uint32_t rcv_ooo_used; + uint32_t sack_recent_left; + uint32_t sack_recent_right; + uint8_t dsack_pending; + uint8_t urg_valid; + uint32_t dsack_left; + uint32_t dsack_right; + uint32_t rcv_wnd; + uint32_t rcv_wnd_max; + uint32_t rcv_adv_edge; tcp_reass_seg_t reass[TCP_REASS_MAX_SEGS]; uint8_t reass_count; - tcp_tx_seg_t txq[TCP_MAX_TX_SEGS]; uint8_t fin_pending; uint32_t fin_seq; + uint32_t urg_seq; +} tcp_flow_rx_t; + +typedef struct { + uint32_t time_wait_ms; + uint32_t fin_wait2_ms; + + uint8_t persist_active; + uint32_t persist_timer_ms; + uint32_t persist_timeout_ms; + + uint8_t delayed_ack_pending; + uint32_t delayed_ack_timer_ms; - uint8_t ip_ttl; - uint8_t ip_dontfrag; uint8_t keepalive_on; uint32_t keepalive_ms; uint32_t keepalive_idle_ms; + + uint8_t challenge_ack_valid; + uint32_t challenge_ack_last_ms; +} tcp_flow_timer_t; + +typedef struct { + uint8_t ttl; + uint8_t dontfrag; + uint8_t reuseaddr; + uint8_t dontroute; +} tcp_flow_ip_t; + +typedef struct tcp_flow { + tcp_flow_base_t base; + tcp_flow_tx_t tx; + tcp_flow_rx_t rx; + tcp_flow_timer_t timer; + tcp_flow_ip_t ip; } tcp_flow_t; +static inline void tcp_persist_start(tcp_flow_t *flow) { + if (!flow) return; + if (!flow->timer.persist_active) { + flow->timer.persist_active = 1; + flow->timer.persist_timer_ms = 0; + } + if (!flow->timer.persist_timeout_ms) { + uint32_t timeout = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + if (timeout < TCP_PERSIST_MIN_MS) timeout = TCP_PERSIST_MIN_MS; + if (timeout > TCP_PERSIST_MAX_MS) timeout = TCP_PERSIST_MAX_MS; + flow->timer.persist_timeout_ms = timeout; + } +} + +static inline void tcp_persist_reset(tcp_flow_t *flow) { + if (!flow) return; + flow->timer.persist_active = 0; + flow->timer.persist_timer_ms = 0; + flow->timer.persist_timeout_ms = 0; +} + +//TODO rfc 6675 is not complete extern tcp_flow_t *tcp_flows[MAX_TCP_FLOWS]; +extern uint16_t tcp_active_flows[MAX_TCP_FLOWS]; +extern uint16_t tcp_active_count; + +void tcp_enter_time_wait(tcp_flow_t *flow); tcp_flow_t *tcp_alloc_flow(void); -void tcp_free_flow(int idx); +void tcp_free_flow(tcp_flow_t *flow); +bool tcp_active_insert_flow(tcp_flow_t *flow); +tcp_flow_t *tcp_flow_from_ctx(tcp_data *flow_ctx); +tcp_flow_t *tcp_flow_acquire_match(uint16_t local_port, ip_version_t ver, const void *local_ip, const void *remote_ip, uint16_t remote_port); +void tcp_flow_put(tcp_flow_t *flow); +void tcp_flow_apply_options(tcp_flow_t *flow, const SocketOptions* extra, uint32_t apply_mask); void tcp_rtt_update(tcp_flow_t *flow, uint32_t sample_ms); -tcp_tx_seg_t *tcp_alloc_tx_seg(tcp_flow_t *flow); -void tcp_send_from_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg); +tcp_tx_seg_t *tcp_alloc_tx_seg(tcp_flow_t *flow, uint32_t reserve_slots); +const uint8_t *tcp_tx_seg_payload_ptr(const tcp_tx_seg_t *seg); +void tcp_tx_seg_clear(tcp_flow_t *flow, tcp_tx_seg_t *seg); +bool tcp_send_from_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg); +bool tcp_retransmit_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg); +bool tcp_send_flow_segment(tcp_flow_t *flow, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len); void tcp_send_ack_now(tcp_flow_t *flow); +void tcp_try_send_pending_fin(tcp_flow_t *flow); +uint64_t tcp_flush_nagle(tcp_flow_t *flow, uint8_t force); static inline uint16_t tcp_checksum_ipv4(const void *segment, uint16_t seg_len, uint32_t src_ip, uint32_t dst_ip) { - uint16_t csum = checksum16_pipv4(src_ip, dst_ip, 6, (const uint8_t *)segment, seg_len); + uint16_t csum = checksum16_pipv4(src_ip, dst_ip, PROTO_TCP, (const uint8_t *)segment, seg_len); return bswap16(csum); } static inline uint16_t tcp_checksum_ipv6(const void *segment, uint16_t seg_len, const uint8_t src_ip[16], const uint8_t dst_ip[16]) { - uint16_t csum = checksum16_pipv6(src_ip, dst_ip, 6, (const uint8_t *)segment, seg_len); + uint16_t csum = checksum16_pipv6(src_ip, dst_ip, PROTO_TCP, (const uint8_t *)segment, seg_len); return bswap16(csum); } -bool tcp_send_segment(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len, const ip_tx_opts_t *txp, uint8_t ttl, uint8_t dontfrag); -void tcp_send_reset(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, bool ack_valid); +bool tcp_send_segment(ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len, const ip_tx_opts_t *txp, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute); +void tcp_send_reset(l3_id_t l3_id, ip_version_t ver, const void *src_ip_addr, const void *dst_ip_addr, uint16_t src_port, uint16_t dst_port, uint32_t seq, uint32_t ack, bool ack_valid); tcp_tx_seg_t *tcp_find_first_unacked(tcp_flow_t *flow); void tcp_cc_on_timeout(tcp_flow_t *f); -int tcp_has_pending_timers(void); - void tcp_daemon_kick(void); -uint16_t tcp_calc_adv_wnd_field(tcp_flow_t *flow, uint8_t apply_scale); +void tcp_update_adv_wnd(tcp_flow_t *flow, uint8_t apply_scale); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/tcp/tcp_limits.c b/kernel/networking/transport_layer/tcp/tcp_limits.c new file mode 100644 index 00000000..24faea40 --- /dev/null +++ b/kernel/networking/transport_layer/tcp/tcp_limits.c @@ -0,0 +1,110 @@ +#include "tcp_internal.h" +#include "exceptions/irq.h" + +uint32_t tcp_ooo_global_bytes; +uint32_t tcp_ooo_global_segs; +uint32_t tcp_tx_global_bytes; +tcp_stats_t tcp_stats; + +uint32_t tcp_clamp_rcvbuf(uint32_t size) { + if (!size) size = TCP_DEFAULT_RCV_BUF; + if (size < TCP_RCV_BUF_MIN) size = TCP_RCV_BUF_MIN; + if (size > TCP_RCV_BUF_MAX) size = TCP_RCV_BUF_MAX; + return size; +} + +void tcp_account_ooo_add(uint32_t bytes, uint32_t segs) { + tcp_ooo_global_bytes += bytes; + tcp_ooo_global_segs += segs; +} + +void tcp_account_ooo_remove(uint32_t bytes, uint32_t segs) { + if (tcp_ooo_global_bytes >= bytes) tcp_ooo_global_bytes -= bytes; + else tcp_ooo_global_bytes = 0; + if (tcp_ooo_global_segs >= segs) tcp_ooo_global_segs -= segs; + else tcp_ooo_global_segs = 0; +} + +void tcp_account_tx_add(tcp_flow_t *flow, uint32_t bytes) { + if (!flow || !bytes) return; + flow->tx.queued_bytes += bytes; + tcp_tx_global_bytes += bytes; +} + +void tcp_account_tx_remove(tcp_flow_t *flow, uint32_t bytes) { + if (!flow || !bytes) return; + if (flow->tx.queued_bytes >= bytes) flow->tx.queued_bytes -= bytes; + else flow->tx.queued_bytes = 0; + if (tcp_tx_global_bytes >= bytes) tcp_tx_global_bytes -= bytes; + else tcp_tx_global_bytes = 0; +} + +tcp_admit_result_t tcp_admit_ooo(tcp_flow_t *flow, uint32_t increase, uint32_t remaining_nodes) { + if (!flow) return TCP_ADMIT_OOO_FLOW_BYTES; + + uint32_t mss = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; + uint32_t limit = flow->rx.rcv_wnd_max >> 1; + uint32_t floor = mss * 4u; + + if (floor > TCP_REASS_MAX_BYTES / 2u) floor = TCP_REASS_MAX_BYTES / 2u; + if (limit > TCP_REASS_MAX_BYTES) limit = TCP_REASS_MAX_BYTES; + if (limit < floor) limit = floor; + if (limit > flow->rx.rcv_wnd_max) limit = flow->rx.rcv_wnd_max; + + uint32_t seg_increase = remaining_nodes >= flow->rx.reass_count ? 1u : 0; + + if (remaining_nodes >= TCP_REASS_MAX_SEGS) return TCP_ADMIT_OOO_FLOW_SEGS; + if (increase && (flow->rx.rcv_ooo_used > limit || increase > limit - flow->rx.rcv_ooo_used)) return TCP_ADMIT_OOO_FLOW_BYTES; + if (increase && (tcp_ooo_global_bytes > TCP_REASS_GLOBAL_MAX_BYTES || increase > TCP_REASS_GLOBAL_MAX_BYTES - tcp_ooo_global_bytes)) return TCP_ADMIT_OOO_GLOBAL_BYTES; + if (seg_increase && tcp_ooo_global_segs + seg_increase > TCP_REASS_GLOBAL_MAX_SEGS) return TCP_ADMIT_OOO_GLOBAL_SEGS; + + return TCP_ADMIT_OK; +} + +tcp_admit_result_t tcp_admit_syn(struct ksocket* listener, ip_version_t ver, const void *src_ip) { + if (!listener) return TCP_ADMIT_SYN_LISTENER; + uint32_t syn_total = 0; + uint32_t syn_listener = 0; + uint32_t syn_source = 0; + size_t ip_len = (size_t)(ver == IP_VER6 ? 16 : 4); + + irq_flags_t irq = irq_save_disable(); + uint32_t active_count = tcp_active_count; + for (uint16_t n = 0; n < active_count; n++) { + uint16_t slot = tcp_active_flows[n]; + tcp_flow_t* flow = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + if (!flow || flow->base.retired || flow->base.state != TCP_SYN_RECEIVED) continue; + syn_total++; + if (flow->base.listener != listener) continue; + syn_listener++; + if (src_ip && flow->base.remote.ver == ver && memcmp(flow->base.remote.ip, src_ip, ip_len) == 0) syn_source++; + } + irq_restore(irq); + + if (active_count >= MAX_TCP_FLOWS) return TCP_ADMIT_FLOW_TABLE_FULL; + + uint32_t available = MAX_TCP_FLOWS - active_count; + if (available <= TCP_FLOW_CONTROL_RESERVE) return TCP_ADMIT_FLOW_RESERVE; + + uint32_t usable = available - TCP_FLOW_CONTROL_RESERVE; + uint32_t global_limit = usable / 2; + if (global_limit < TCP_SYN_RECV_MIN_GLOBAL) global_limit = TCP_SYN_RECV_MIN_GLOBAL; + if (global_limit > TCP_SYN_RECV_MAX_GLOBAL) global_limit = TCP_SYN_RECV_MAX_GLOBAL; + if (global_limit > usable) global_limit = usable; + + uint32_t listener_limit = usable / 8; + if (listener_limit < TCP_SYN_RECV_MIN_LISTENER) listener_limit = TCP_SYN_RECV_MIN_LISTENER; + if (listener_limit > TCP_SYN_RECV_MAX_LISTENER) listener_limit = TCP_SYN_RECV_MAX_LISTENER; + if (listener_limit > global_limit) listener_limit = global_limit; + + uint32_t source_limit = listener_limit / 4; + if (source_limit < TCP_SYN_RECV_MIN_SOURCE) source_limit = TCP_SYN_RECV_MIN_SOURCE; + if (source_limit > TCP_SYN_RECV_MAX_SOURCE) source_limit = TCP_SYN_RECV_MAX_SOURCE; + if (source_limit > listener_limit) source_limit = listener_limit; + + if (syn_total >= global_limit) return TCP_ADMIT_SYN_GLOBAL; + if (syn_listener >= listener_limit) return TCP_ADMIT_SYN_LISTENER; + if (syn_source >= source_limit) return TCP_ADMIT_SYN_SOURCE; + + return TCP_ADMIT_OK; +} diff --git a/kernel/networking/transport_layer/tcp/tcp_limits.h b/kernel/networking/transport_layer/tcp/tcp_limits.h new file mode 100644 index 00000000..ab8075cf --- /dev/null +++ b/kernel/networking/transport_layer/tcp/tcp_limits.h @@ -0,0 +1,77 @@ +#pragma once + +#include "../tcp.h" +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TCP_REASS_MAX_SEGS 32 +#define TCP_REASS_MAX_BYTES (64u * 1024u) +#define TCP_REASS_GLOBAL_MAX_SEGS 1024u +#define TCP_REASS_GLOBAL_MAX_BYTES (2u * 1024u * 1024u) +#define TCP_RCV_BUF_MIN (8u * 1024u) +#define TCP_DEFAULT_RCV_BUF (256u * 1024u) +#define TCP_RCV_BUF_MAX (2u * 1024u * 1024u) +#define TCP_TX_MAX_BYTES_PER_FLOW (512u * 1024u) +#define TCP_TX_MAX_BYTES_GLOBAL (4u * 1024u * 1024u) +#define TCP_TX_CONTROL_RESERVE_SEGS 2u +#define TCP_SYN_RECV_MAX_GLOBAL (MAX_TCP_FLOWS / 4u) +#define TCP_SYN_RECV_MAX_LISTENER 32u +#define TCP_SYN_RECV_MAX_SOURCE 8u +#define TCP_TIMEWAIT_MAX_GLOBAL 128u +#define TCP_FLOW_CONTROL_RESERVE 32u +#define TCP_SYN_RECV_MIN_GLOBAL 16u +#define TCP_SYN_RECV_MIN_LISTENER 4u +#define TCP_SYN_RECV_MIN_SOURCE 2u + +struct tcp_flow; +struct ksocket; + +typedef enum { + TCP_ADMIT_OK = 0, + TCP_ADMIT_OOO_FLOW_BYTES, + TCP_ADMIT_OOO_FLOW_SEGS, + TCP_ADMIT_OOO_GLOBAL_BYTES, + TCP_ADMIT_OOO_GLOBAL_SEGS, + TCP_ADMIT_SYN_GLOBAL, + TCP_ADMIT_SYN_LISTENER, + TCP_ADMIT_SYN_SOURCE, + TCP_ADMIT_FLOW_RESERVE, + TCP_ADMIT_FLOW_TABLE_FULL +} tcp_admit_result_t; + +typedef struct { + uint64_t ooo_drop_flow_bytes; + uint64_t ooo_drop_flow_segs; + uint64_t ooo_drop_global_bytes; + uint64_t ooo_drop_global_segs; + uint64_t syn_drop_global; + uint64_t syn_drop_listener; + uint64_t syn_drop_source; + uint64_t acceptq_drop_full; + uint64_t timewait_reap_oldest; + uint64_t syn_drop_flow_reserve; + uint64_t tx_block_flow_bytes; + uint64_t tx_block_flow_segs; + uint64_t tx_block_global_bytes; + uint64_t flow_table_full; +} tcp_stats_t; + +extern uint32_t tcp_ooo_global_bytes; +extern uint32_t tcp_ooo_global_segs; +extern uint32_t tcp_tx_global_bytes; +extern tcp_stats_t tcp_stats; + +uint32_t tcp_clamp_rcvbuf(uint32_t size); +tcp_admit_result_t tcp_admit_syn(struct ksocket *listener, ip_version_t ver, const void *src_ip); +tcp_admit_result_t tcp_admit_ooo(struct tcp_flow *flow, uint32_t increase, uint32_t remaining_nodes); +void tcp_account_ooo_add(uint32_t bytes, uint32_t segs); +void tcp_account_ooo_remove(uint32_t bytes, uint32_t segs); +void tcp_account_tx_add(struct tcp_flow *flow, uint32_t bytes); +void tcp_account_tx_remove(struct tcp_flow *flow, uint32_t bytes); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/networking/transport_layer/tcp/tcp_rx.c b/kernel/networking/transport_layer/tcp/tcp_rx.c index 1d5deb66..7c368aa8 100644 --- a/kernel/networking/transport_layer/tcp/tcp_rx.c +++ b/kernel/networking/transport_layer/tcp/tcp_rx.c @@ -1,746 +1,1469 @@ #include "tcp_internal.h" -#include "networking/port_manager.h" +#include "networking/transport_layer/socket_bind.h" +#include "networking/transport_layer/csocket_tcp.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/interface_manager.h" #include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" +#include "networking/firewall.h" #include "std/memory.h" #include "math/rng.h" +#include "random/random.h" #include "syscalls/syscalls.h" #include "../tcp.h" -static void tcp_reass_evict_tail(tcp_flow_t *flow, uint32_t need) { - while (flow->reass_count && flow->rcv_buf_used + need > flow->rcv_wnd_max) { - int idx = 0; - uint32_t best = flow->reass[0].seq; - for (int i = 1; i < flow->reass_count; i++) { - if (flow->reass[i].seq > best) { - best = flow->reass[i].seq; - idx = i; - } - } +static void tcp_note_dsack(tcp_flow_t *flow, uint32_t left, uint32_t right) { + if (!flow || !flow->tx.sack_ok || !flow->tx.dsack_enabled || !TCP_SEQ_GT(right, left)) return; + flow->rx.dsack_pending = 1; + flow->rx.dsack_left = left; + flow->rx.dsack_right = right; +} - tcp_reass_seg_t *r = &flow->reass[idx]; - uint32_t olen = r->end - r->seq; - if (r->buf && olen) free_sized((void *)r->buf, olen); - if (flow->rcv_buf_used >= olen) flow->rcv_buf_used -= olen; - else flow->rcv_buf_used = 0; +static void tcp_consume_fin(tcp_flow_t *flow) { + if (!flow) return; - flow->reass[idx] = flow->reass[flow->reass_count - 1]; - flow->reass[flow->reass_count - 1].seq = 0; - flow->reass[flow->reass_count - 1].end = 0; - flow->reass[flow->reass_count - 1].buf = 0; - flow->reass_count--; - } + flow->rx.fin_pending = 0; + flow->rx.rcv_nxt++; + flow->base.ctx.ack = flow->rx.rcv_nxt; + + tcp_state_t old = flow->base.state; + if (old == TCP_ESTABLISHED) flow->base.state = TCP_CLOSE_WAIT; + else if (old == TCP_FIN_WAIT_1) flow->base.state = TCP_CLOSING; + else if (old == TCP_FIN_WAIT_2) tcp_enter_time_wait(flow); } -static void tcp_reass_insert(tcp_flow_t *flow, uint32_t seq, const uint8_t *data, uint32_t len) { - if (!len) return; - if (flow->reass_count >= TCP_REASS_MAX_SEGS) return; - if (seq < flow->rcv_nxt) { - uint32_t d = flow->rcv_nxt - seq; - if (d >= len) return; - seq += d; - data += d; - len -= d; +static bool tcp_reass_count_ok(tcp_flow_t *flow) { + if (!flow) return false; + + if (flow->rx.reass_count <= TCP_REASS_MAX_SEGS) return true; + + if (flow->rx.reass_count || flow->rx.rcv_ooo_used) tcp_account_ooo_remove(flow->rx.rcv_ooo_used, flow->rx.reass_count); + flow->rx.reass_count = 0; + flow->rx.rcv_ooo_used = 0; + flow->rx.sack_recent_left = 0; + flow->rx.sack_recent_right = 0; + + for (uint32_t i = 0; i < TCP_REASS_MAX_SEGS; i++) { + flow->rx.reass[i].seq = 0; + flow->rx.reass[i].end = 0; } - uint32_t wnd_end = flow->rcv_nxt + flow->rcv_wnd; - if (seq >= wnd_end) return; - if (seq + len > wnd_end) len = wnd_end - seq; - if (!len) return; + return false; +} + +static void tcp_reass_remove(tcp_flow_t *flow, int32_t idx) { + if (!tcp_reass_count_ok(flow)) return; + if (idx < 0 || idx >= flow->rx.reass_count) return; + + uint32_t len = 0; + if (TCP_SEQ_GT(flow->rx.reass[idx].end, flow->rx.reass[idx].seq)) len = flow->rx.reass[idx].end - flow->rx.reass[idx].seq; - if (flow->rcv_buf_used + len > flow->rcv_wnd_max) tcp_reass_evict_tail(flow, len); - if (flow->rcv_buf_used + len > flow->rcv_wnd_max) return; + if (flow->rx.rcv_ooo_used >= len) flow->rx.rcv_ooo_used -= len; + else flow->rx.rcv_ooo_used = 0; + tcp_account_ooo_remove(len, 1); - uint32_t orig_seq = seq; - uint32_t start = seq; - uint32_t end = seq + len; + for (int32_t i = idx; i + 1 < flow->rx.reass_count; i++) flow->rx.reass[i] = flow->rx.reass[i+1]; + + if (flow->rx.reass_count) flow->rx.reass_count--; + flow->rx.reass[flow->rx.reass_count].seq = 0; + flow->rx.reass[flow->rx.reass_count].end = 0; +} + +static bool tcp_reass_drain_inseq(tcp_flow_t *flow) { + bool advanced = false; + if (!tcp_reass_count_ok(flow)) return false; for(;;){ - int changed = 0; + int32_t idx = -1; - for (int i = 0; i < flow->reass_count; i++){ - tcp_reass_seg_t *r = &flow->reass[i]; - uint32_t rs = r->seq; - uint32_t re = r->end; + for (int32_t i = 0; i < flow->rx.reass_count; i++){ + if (flow->rx.reass[i].seq != flow->rx.rcv_nxt) continue; + idx = i; + break; + } - if (end <= rs || start >= re) continue; - if (start >= rs && end <= re) return; + if (idx < 0) break; - if (start <= rs && end >= re) { - uint32_t olen = re - rs; + uint32_t end = flow->rx.reass[idx].end; + if (TCP_SEQ_LEQ(end, flow->rx.rcv_nxt)) { + tcp_reass_remove(flow, idx); + continue; + } - if (r->buf && olen) free_sized((void *)r->buf, olen); + flow->rx.rcv_nxt = end; + flow->rx.rcv_data_nxt = end; + flow->base.ctx.ack = flow->rx.rcv_nxt; + tcp_reass_remove(flow, idx); + advanced = true; + } - flow->reass[i] = flow->reass[flow->reass_count - 1]; - flow->reass[flow->reass_count - 1].seq = 0; - flow->reass[flow->reass_count - 1].end = 0; - flow->reass[flow->reass_count - 1].buf = 0; - flow->reass_count--; + if (advanced) tcp_update_adv_wnd(flow, 1); + return advanced; +} - flow->rcv_buf_used -= olen; - changed = 1; - break; +int64_t tcp_flow_read(tcp_data *flow_ctx, void *buf, uint64_t len) { + if (!buf || !len) return 0; + + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return TCP_DISCONNECT; + + int64_t rc = 0; + if (!flow->rx.rcv_buf || !flow->rx.rcv_wnd_max) rc = flow->base.state == TCP_STATE_CLOSED ? TCP_DISCONNECT : 0; + else if (TCP_SEQ_LT(flow->rx.rcv_data_nxt, flow->rx.rcv_base)) rc = flow->base.state == TCP_STATE_CLOSED ? TCP_DISCONNECT : 0; + else { + uint32_t n = flow->rx.rcv_data_nxt - flow->rx.rcv_base; + if (!n) rc = flow->base.state == TCP_STATE_CLOSED ? TCP_DISCONNECT : 0; + else { + if (len < n) n = (uint32_t)len; + + uint8_t *rx = (uint8_t*)flow->rx.rcv_buf; + uint8_t *dst = (uint8_t*)buf; + uint32_t pos = flow->rx.rcv_base % flow->rx.rcv_wnd_max; + uint32_t first = flow->rx.rcv_wnd_max - pos; + + if (first > n) first = n; + if (first) memcpy(dst, rx + pos, first); + if (n > first) memcpy(dst + first, rx, n - first); + + flow->rx.rcv_base += n; + if (flow->rx.urg_valid && TCP_SEQ_GEQ(flow->rx.rcv_base, flow->rx.urg_seq)) { + flow->rx.urg_valid = 0; + flow->rx.urg_seq = 0; } + rc = n; + } + } - if (start < rs && end > rs && end <= re) { - end = rs; - len = end - start; - changed = 1; - break; - } + if (rc > 0) { + uint32_t old_edge = flow->rx.rcv_adv_edge; + uint16_t old_adv_field = flow->base.ctx.window; + uint32_t old_nxt = flow->rx.rcv_nxt; - if (start >= rs && start < re && end > re){ - start = re; - len = end - start; - changed = 1; - break; - } + bool advanced = tcp_reass_drain_inseq(flow); + if (!advanced) tcp_update_adv_wnd(flow, 1); + + if (flow->base.state != TCP_STATE_CLOSED && flow->base.state != TCP_TIME_WAIT) { + uint32_t threshold = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; + uint32_t half = flow->rx.rcv_wnd_max >> 1; + if (half && half < threshold) threshold = half; + if (!threshold) threshold = 1; + + uint32_t delta = TCP_SEQ_GT(flow->rx.rcv_adv_edge, old_edge) ? flow->rx.rcv_adv_edge - old_edge : 0; + if (advanced || flow->rx.rcv_nxt != old_nxt || (!old_adv_field && flow->base.ctx.window) || delta >= threshold) tcp_send_ack_now(flow); } + } + + tcp_flow_put(flow); + return rc; +} - if (!changed) break; - if (!len) return; +uint32_t tcp_flow_readable(tcp_data *flow_ctx) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return 0; + + uint32_t n = 0; + if (flow->rx.rcv_buf && flow->rx.rcv_wnd_max && !TCP_SEQ_LT(flow->rx.rcv_data_nxt, flow->rx.rcv_base)) n = flow->rx.rcv_data_nxt - flow->rx.rcv_base; + + tcp_flow_put(flow); + return n; +} + +bool tcp_flow_recv_closed(tcp_data *flow_ctx) { + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return true; + + bool closed = false; + if (flow->base.state == TCP_STATE_CLOSED || flow->base.state == TCP_TIME_WAIT) closed = true; + else if (flow->base.state == TCP_CLOSE_WAIT) { + if (!flow->rx.rcv_buf || !flow->rx.rcv_wnd_max) closed = true; + else if (TCP_SEQ_LT(flow->rx.rcv_data_nxt, flow->rx.rcv_base)) closed = true; + else closed = flow->rx.rcv_data_nxt == flow->rx.rcv_base; } - if (!len) return; - if (flow->reass_count >= TCP_REASS_MAX_SEGS) return; - if (flow->rcv_buf_used + len > flow->rcv_wnd_max) tcp_reass_evict_tail(flow, len); - if (flow->rcv_buf_used + len > flow->rcv_wnd_max) return; + tcp_flow_put(flow); + return closed; +} + +tcp_tx_seg_t *tcp_find_first_unacked(tcp_flow_t *flow) { + if (!flow) return NULL; + tcp_tx_seg_t *best = NULL; + uint32_t best_seq = 0; + + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++){ + tcp_tx_seg_t *s = &flow->tx.txq[i]; - uintptr_t buf = (uintptr_t)malloc(len); - if (!buf) return; + if (!s->used) continue; - uint32_t offset = start - orig_seq; - memcpy((void *)buf, data + offset, len); + uint32_t end = s->seq + s->len + (s->syn ? 1u : 0u) + (s->fin ? 1u : 0u); + if (TCP_SEQ_LEQ(end, flow->tx.snd_una)) continue; - int pos = flow->reass_count; - while (pos > 0 && flow->reass[pos - 1].seq > start){ - flow->reass[pos] = flow->reass[pos - 1]; - pos--; + if (!best || TCP_SEQ_LT(s->seq, best_seq)){ + best = s; + best_seq = s->seq; + } } - flow->reass[pos].seq = start; - flow->reass[pos].end = start + len; - flow->reass[pos].buf = buf; - flow->reass_count++; + return best; +} + +static bool tcp_segment_acceptable(const tcp_flow_t *flow, uint32_t seq, uint32_t seg_len) { + if (!flow) return false; + + uint32_t rcv_nxt = flow->rx.rcv_nxt; + uint32_t rcv_wnd = flow->rx.rcv_wnd; + if (!rcv_wnd) return !seg_len && seq == rcv_nxt; - flow->rcv_buf_used += len; + uint32_t wnd_end = rcv_nxt + rcv_wnd; + if (!seg_len) return TCP_SEQ_GEQ(seq, rcv_nxt) && TCP_SEQ_LT(seq, wnd_end); - (void)tcp_calc_adv_wnd_field(flow, 1); + uint32_t last = seq + seg_len - 1; + return (TCP_SEQ_GEQ(seq, rcv_nxt) && TCP_SEQ_LT(seq, wnd_end)) || (TCP_SEQ_GEQ(last, rcv_nxt) && TCP_SEQ_LT(last, wnd_end)); } -static void tcp_reass_drain_inseq(tcp_flow_t *flow, port_manager_t *pm, uint8_t ifx, ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, uint16_t src_port, uint16_t dst_port) { - uint32_t rcv_nxt = flow->rcv_nxt; +static void tcp_send_challenge_ack(tcp_flow_t *flow) { + if (!flow) return; - for(;;){ - int idx = -1; + uint32_t now = (uint32_t)get_time(); + if (flow->timer.challenge_ack_valid && now - flow->timer.challenge_ack_last_ms < TCP_CHALLENGE_ACK_INTERVAL_MS) return; + tcp_send_ack_now(flow); + if (flow->timer.delayed_ack_pending) return; - for (int i = 0; i < flow->reass_count; i++){ - if (flow->reass[i].seq != rcv_nxt) continue; - idx = i; - break; + flow->timer.challenge_ack_valid = 1; + flow->timer.challenge_ack_last_ms = now; +} + +static bool tcp_prepare_rcv_buffer(tcp_flow_t *flow) { + if (!flow || !flow->rx.rcv_wnd_max) return false; + if (flow->rx.rcv_buf) return true; + + flow->rx.rcv_buf = (uintptr_t)zalloc(flow->rx.rcv_wnd_max); + if (!flow->rx.rcv_buf) return false; + + flow->rx.rcv_base = flow->rx.rcv_nxt; + flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + tcp_update_adv_wnd(flow, 1); + return true; +} + +static bool tcp_acknowledge_segments(tcp_flow_t *flow, uint32_t ack) { + if (!flow) return false; + + bool syn_retransmitted = false; + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++){ + tcp_tx_seg_t *seg = &flow->tx.txq[i]; + if (!seg->used || TCP_SEQ_GEQ(seg->seq, ack)) continue; + + uint32_t end_seq = seg->seq + seg->len + (seg->syn ? 1u : 0u) + (seg->fin ? 1u : 0u); + if (seg->rtt_sample && TCP_SEQ_GEQ(ack, end_seq)) { + if (seg->retransmit_cnt == 0) tcp_rtt_update(flow, (uint32_t)get_time() - flow->tx.rtt_start_ms); + flow->tx.rtt_sample_pending = 0; + seg->rtt_sample = 0; } - if (idx < 0) break; + uint32_t seq = seg->seq; + if (seg->syn && TCP_SEQ_GT(ack, seq)) { + if (seg->retransmit_cnt) syn_retransmitted = true; + seg->syn = 0; + seq++; + } - tcp_reass_seg_t *seg = &flow->reass[idx]; - uint32_t seg_len = seg->end - seg->seq; + if (seg->len && TCP_SEQ_GT(ack, seq)) { + uint32_t n = ack - seq; + if (n > seg->len) n = (uint32_t)seg->len; + seg->payload_off += n; + seg->len -= n; + seq += n; + tcp_account_tx_remove(flow, n); + } + if (seg->fin && TCP_SEQ_GT(ack, seq)) { + seg->fin = 0; + seq++; + } + + seg->seq = seq; + if (!seg->syn && !seg->fin && !seg->len) tcp_tx_seg_clear(flow, seg); + } + + return syn_retransmitted; +} - if (!seg_len) { - flow->reass[idx] = flow->reass[flow->reass_count - 1]; - flow->reass[flow->reass_count - 1].seq = 0; - flow->reass[flow->reass_count - 1].end = 0; - flow->reass[flow->reass_count - 1].buf = 0; - flow->reass_count--; +static void tcp_restart_retransmit_timer(tcp_flow_t *flow) { + tcp_tx_seg_t *seg = tcp_find_first_unacked(flow); + if (!seg) return; + + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; +} + +static bool tcp_sack_insert(tcp_sack_range_t *ranges, uint8_t *count, uint32_t left, uint32_t right) { + if (!ranges || !count || TCP_SEQ_LEQ(right, left)) return false; + + tcp_sack_range_t merged[TCP_SACK_SCOREBOARD_MAX + 1]; + uint8_t old_count = *count; + uint8_t out = 0; + bool inserted = false; + + for (uint8_t i = 0; i < old_count; i++) { + tcp_sack_range_t current = ranges[i]; + if (TCP_SEQ_LT(current.right, left)) { + merged[out++] = current; continue; } - if (pm) { - port_recv_handler_t h = port_get_handler(pm, PROTO_TCP, dst_port); - uint32_t accepted = seg_len; + if (TCP_SEQ_LT(right, current.left)) { + if (!inserted) { + merged[out].left = left; + merged[out].right = right; + out++; + inserted = true; + } + merged[out++] = current; + continue; + } + + if (TCP_SEQ_LT(current.left, left)) left = current.left; + if (TCP_SEQ_GT(current.right, right)) right = current.right; + } - if (h) accepted = h(ifx, ipver, src_ip_addr, dst_ip_addr, seg->buf, seg_len, src_port, dst_port); - if (accepted > seg_len) accepted = seg_len; + if (!inserted) { + merged[out].left = left; + merged[out].right = right; + out++; + } - if (accepted == 0) { - if (flow->state == TCP_FIN_WAIT_1 || flow->state == TCP_FIN_WAIT_2 || flow->state == TCP_CLOSING || flow->state == TCP_LAST_ACK || flow->state == TCP_TIME_WAIT) { - if (seg->buf) free_sized((void *)seg->buf, seg_len); + uint8_t first = out > TCP_SACK_SCOREBOARD_MAX ? 1 : 0; + uint8_t next_count = out - first; + if (old_count == next_count && memcmp(ranges, &merged[first], sizeof(tcp_sack_range_t)*next_count) == 0) return false; - if (flow->rcv_buf_used >= seg_len) flow->rcv_buf_used -= seg_len; - else flow->rcv_buf_used = 0; + memcpy(ranges, &merged[first], sizeof(tcp_sack_range_t) * next_count); + *count = next_count; + return true; +} - rcv_nxt += seg_len; +static void tcp_sack_trim_ranges(tcp_sack_range_t *ranges, uint8_t *count, uint32_t ack) { + if (!ranges || !count) return; - flow->reass[idx] = flow->reass[flow->reass_count - 1]; - flow->reass[flow->reass_count - 1].seq = 0; - flow->reass[flow->reass_count - 1].end = 0; - flow->reass[flow->reass_count - 1].buf = 0; - flow->reass_count--; - continue; - } + uint8_t out = 0; + for (uint8_t i = 0; i < *count; i++) { + uint32_t left = ranges[i].left; + uint32_t right = ranges[i].right; - break; - } + if (TCP_SEQ_LEQ(right, ack)) continue; + if (TCP_SEQ_LT(left, ack)) left = ack; + ranges[out].left = left; + ranges[out].right = right; + out++; + } - if (accepted < seg_len) { - uint32_t rem = seg_len - accepted; - uintptr_t newbuf = (uintptr_t)malloc(rem); - if (!newbuf) break; + *count = out; +} - memcpy((void *)newbuf, ((const uint8_t *)seg->buf) + accepted, rem); - if (seg->buf) free_sized((void *)seg->buf, seg_len); +static bool tcp_sack_is_lost(tcp_flow_t *flow, uint32_t right) { + if (!flow) return false; - seg->buf = newbuf; - seg->seq += accepted; + uint32_t high_sacked = flow->tx.sack_range_count ? flow->tx.sack_ranges[flow->tx.sack_range_count - 1].right : flow->tx.snd_una; + if (TCP_SEQ_GEQ(right, high_sacked)) return false; - if (flow->rcv_buf_used >= accepted) flow->rcv_buf_used -= accepted; - else flow->rcv_buf_used = 0; + uint32_t bytes = 0; + uint32_t blocks = 0; + uint32_t mss = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; - rcv_nxt += accepted; + for (uint8_t i = 0; i < flow->tx.sack_range_count; i++) { + tcp_sack_range_t *range = &flow->tx.sack_ranges[i]; + if (TCP_SEQ_LEQ(range->right, right)) continue; - flow->rcv_nxt = rcv_nxt; - flow->ctx.ack = rcv_nxt; + uint32_t left = TCP_SEQ_LT(range->left, right) ? right : range->left; + if (TCP_SEQ_GT(range->right, left)) bytes += range->right - left; + blocks++; + } - (void)tcp_calc_adv_wnd_field(flow, 1); - continue; - } - } + return blocks >= 3 || bytes > 2 * mss; +} - rcv_nxt += seg_len; +static bool tcp_sack_select_retransmit(tcp_flow_t *flow, bool force, bool rescue, tcp_tx_seg_t **out_seg, uint32_t *out_left, uint32_t *out_right) { + if (!flow || !out_seg || !out_left || !out_right) return false; + + tcp_tx_seg_t *best_seg = NULL; + uint32_t best_left = 0; + uint32_t best_right = 0; + uint32_t limit; + if (rescue) { + if (flow->tx.recover) limit = flow->tx.recover; + else limit = flow->tx.snd_nxt; + } else if (flow->tx.sack_range_count) { + limit = flow->tx.sack_ranges[flow->tx.sack_range_count-1].right; + } else if (force) limit = flow->tx.snd_nxt; + else limit = flow->tx.snd_una; + uint32_t mss = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; + + for (int i = 0; i < TCP_MAX_TX_SEGS; i++) { + tcp_tx_seg_t *seg = &flow->tx.txq[i]; + if (!seg->used || seg->syn || seg->fin || !seg->len) continue; + + uint32_t start = seg->seq; + uint32_t end = seg->seq + (uint32_t)seg->len; + if (TCP_SEQ_LEQ(end, flow->tx.snd_una) || TCP_SEQ_GEQ(start, limit)) continue; + if (TCP_SEQ_LT(start, flow->tx.snd_una)) start = flow->tx.snd_una; + if (TCP_SEQ_GT(end, limit)) end = limit; + + uint32_t cursor = start; + for (uint8_t r = 0; r <= flow->tx.sack_range_count && TCP_SEQ_LT(cursor, end); r++) { + uint32_t hole_right = end; + if (r < flow->tx.sack_range_count){ + tcp_sack_range_t *range = &flow->tx.sack_ranges[r]; + if (TCP_SEQ_LEQ(range->right, cursor)) continue; + if (TCP_SEQ_LEQ(range->left, cursor)) { + cursor = range->right; + continue; + } + if (TCP_SEQ_LT(range->left, hole_right)) hole_right = range->left; + } + + if (TCP_SEQ_GT(hole_right, cursor) && (rescue || force || tcp_sack_is_lost(flow, hole_right))) { + uint32_t left = cursor; + uint32_t right = hole_right; + + if (rescue) { + if (right - left > mss) left = right - mss; + if (!best_seg || TCP_SEQ_GT(right, best_right)) { + best_seg = seg; + best_left = left; + best_right = right; + } + } else { + for (uint8_t n = 0; n < flow->tx.sack_retransmitted_count; n++) { + tcp_sack_range_t *range = &flow->tx.sack_retransmitted_ranges[n]; + if (TCP_SEQ_LEQ(range->right, left)) continue; + if (TCP_SEQ_GT(range->left, left)) { + if (TCP_SEQ_LT(range->left, right)) right = range->left; + break; + } + left = range->right; + if (TCP_SEQ_GEQ(left, hole_right)) break; + } - if (seg->buf) free_sized((void *)seg->buf, seg_len); - if (flow->rcv_buf_used >= seg_len) flow->rcv_buf_used -= seg_len; - else flow->rcv_buf_used = 0; + if (TCP_SEQ_GT(right, left)) { + if (right - left > mss) right = left + mss; + if (!best_seg || TCP_SEQ_LT(left, best_left)) { + best_seg = seg; + best_left = left; + best_right = right; + } + } + } + } - flow->reass[idx] = flow->reass[flow->reass_count - 1]; - flow->reass[flow->reass_count - 1].seq = 0; - flow->reass[flow->reass_count - 1].end = 0; - flow->reass[flow->reass_count - 1].buf = 0; - flow->reass_count--; + if (r < flow->tx.sack_range_count && TCP_SEQ_GT(flow->tx.sack_ranges[r].right, cursor)) cursor = flow->tx.sack_ranges[r].right; + else break; + } } - flow->rcv_nxt = rcv_nxt; - flow->ctx.ack = rcv_nxt; + if (!best_seg) return false; + *out_seg = best_seg; + *out_left = best_left; + *out_right = best_right; + return true; +} - (void)tcp_calc_adv_wnd_field(flow, 1); +static bool tcp_sack_retransmit_range(tcp_flow_t *flow, tcp_tx_seg_t *seg, uint32_t left, uint32_t right, bool rescue) { + if (!flow || !seg || TCP_SEQ_LEQ(right, left)) return false; + + tcp_tx_seg_t retransmit = { + .seq = left, + .len = right - left, + .pkt = seg->pkt, + .payload_off = seg->payload_off + (left - seg->seq), + .psh = seg->psh && right == seg->seq + seg->len + }; + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) flow->tx.txq[i].rtt_sample = 0; + flow->tx.rtt_sample_pending = 0; + if (!tcp_send_from_seg(flow, &retransmit)) return false; + tcp_sack_insert(flow->tx.sack_retransmitted_ranges, &flow->tx.sack_retransmitted_count, left, right); + if (rescue) { + flow->tx.sack_rescue_rxt = flow->tx.recover; + flow->tx.sack_rescue_valid = 1; + } + if (seg->retransmit_cnt < UINT8_MAX) seg->retransmit_cnt++; + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + return true; } -tcp_tx_seg_t *tcp_find_first_unacked(tcp_flow_t *flow) { - tcp_tx_seg_t *best = NULL; - uint32_t best_seq = 0; +static void tcp_sack_recovery_send(tcp_flow_t *flow, bool force_first) { + if (!flow || !flow->tx.sack_ok) return; - for (int i = 0; i < TCP_MAX_TX_SEGS; i++){ - tcp_tx_seg_t *s = &flow->txq[i]; + if (force_first) { + tcp_tx_seg_t *seg = NULL; + uint32_t left = 0; + uint32_t right = 0; + if (!tcp_sack_select_retransmit(flow, true, false, &seg, &left, &right) || !tcp_sack_retransmit_range(flow, seg, left, right, false)) return; + flow->tx.sack_rescue_rxt = right; + flow->tx.sack_rescue_valid = 1; + } - if (!s->used) continue; + uint32_t pipe = 0; + for (int i = 0; i < TCP_MAX_TX_SEGS; i++) { + tcp_tx_seg_t *seg = &flow->tx.txq[i]; + + if (!seg->used) continue; + + uint32_t left = seg->seq; + uint32_t right = seg->seq + (uint32_t)seg->len + seg->syn + seg->fin; + if (TCP_SEQ_LEQ(right, flow->tx.snd_una)) continue; + if (TCP_SEQ_LT(left, flow->tx.snd_una)) left = flow->tx.snd_una; + + uint32_t cursor = left; + for (uint8_t r = 0; r <= flow->tx.sack_range_count && TCP_SEQ_LT(cursor, right); r++) { + uint32_t hole_right = right; + if (r < flow->tx.sack_range_count) { + tcp_sack_range_t *range = &flow->tx.sack_ranges[r]; + if (TCP_SEQ_LEQ(range->right, cursor)) continue; + if (TCP_SEQ_LEQ(range->left, cursor)) { + cursor = range->right; + continue; + } + if (TCP_SEQ_LT(range->left, hole_right)) hole_right = range->left; + } - uint32_t end = s->seq + s->len + (s->syn ? 1u : 0u) + (s->fin ? 1u : 0u); - if (end <= flow->snd_una) continue; + if (TCP_SEQ_GT(hole_right, cursor)) { + uint32_t retransmitted = 0; + for (uint8_t n = 0; n < flow->tx.sack_retransmitted_count; n++) { + tcp_sack_range_t *range = &flow->tx.sack_retransmitted_ranges[n]; + if (TCP_SEQ_GEQ(cursor, range->right) || TCP_SEQ_GEQ(range->left, hole_right)) continue; - if (!best || s->seq < best_seq){ - best = s; - best_seq = s->seq; + uint32_t range_left = TCP_SEQ_GT(cursor, range->left) ? cursor : range->left; + uint32_t range_right = TCP_SEQ_LT(hole_right, range->right) ? hole_right : range->right; + if (TCP_SEQ_GT(range_right, range_left)) retransmitted += range_right - range_left; + } + + uint32_t hole_len = hole_right - cursor; + if (!tcp_sack_is_lost(flow, hole_right)) pipe += hole_len; + pipe += retransmitted > hole_len ? hole_len : retransmitted; + } + + if (r < flow->tx.sack_range_count && TCP_SEQ_GT(flow->tx.sack_ranges[r].right, cursor)) cursor = flow->tx.sack_ranges[r].right; + else break; } } - return best; + uint32_t cwnd = flow->tx.cwnd ? flow->tx.cwnd : (flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS); + + for (uint32_t sent = 0; sent < TCP_SACK_SCOREBOARD_MAX && pipe < cwnd; sent++) { + tcp_tx_seg_t *seg = NULL; + uint32_t left = 0; + uint32_t right = 0; + bool rescue = false; + if (!tcp_sack_select_retransmit(flow, false, false, &seg, &left, &right)) { + if (!tcp_sack_select_retransmit(flow, true, false, &seg, &left, &right)) { + if ((flow->tx.sack_rescue_valid && !TCP_SEQ_GT(flow->tx.snd_una, flow->tx.sack_rescue_rxt)) || !tcp_sack_select_retransmit(flow, false, true, &seg, &left, &right)) break; + rescue = true; + } + } + uint32_t len = right - left; + if (!len || len > cwnd - pipe) break; + if (!tcp_sack_retransmit_range(flow, seg, left, right, rescue)) break; + pipe += len; + } +} + +static bool tcp_apply_sack_blocks(tcp_flow_t *flow, const tcp_parsed_opts_t *opts) { + if (!flow || !opts || !flow->tx.sack_ok || !opts->sack_count) return false; + + bool changed = false; + for (uint32_t b = 0; b < opts->sack_count; b++) { + uint32_t left = opts->sacks[b].left; + uint32_t right = opts->sacks[b].right; + if (TCP_SEQ_LEQ(right, left)) continue; + if (TCP_SEQ_LT(left, flow->tx.snd_una)) continue; + if (TCP_SEQ_GT(right, flow->tx.snd_nxt)) continue; + if (TCP_SEQ_GEQ(left, flow->tx.snd_nxt)) continue; + if (TCP_SEQ_LEQ(right, flow->tx.snd_una)) continue; + changed |= tcp_sack_insert(flow->tx.sack_ranges, &flow->tx.sack_range_count, left, right); + } + + return changed; } void tcp_cc_on_timeout(tcp_flow_t *f){ - uint32_t mss = f->mss ? f->mss : TCP_DEFAULT_MSS; - uint32_t flight = f->snd_nxt > f->snd_una ? f->snd_nxt - f->snd_una : 0; + uint32_t mss = f->tx.mss ? f->tx.mss : TCP_DEFAULT_MSS; + uint32_t flight = TCP_SEQ_GT(f->tx.snd_nxt, f->tx.snd_una) ? f->tx.snd_nxt - f->tx.snd_una : 0; uint32_t half = flight / 2; uint32_t minth = 2u * mss; if (half < minth) half = minth; - f->ssthresh = half; - f->cwnd = mss; - f->cwnd_acc = 0; - f->dup_acks = 0; - f->in_fast_recovery = 0; - f->recover = 0; + f->tx.ssthresh = half; + f->tx.cwnd = mss; + f->tx.cwnd_acc = 0; + f->tx.dup_acks = 0; + f->tx.in_fast_recovery = 0; + f->tx.recover = f->tx.snd_nxt; + f->tx.recover_valid = 1; + + f->tx.sack_range_count = 0; + f->tx.sack_retransmitted_count = 0; + f->tx.sack_rescue_rxt = 0; + f->tx.sack_rescue_valid = 0; } -static void tcp_cc_on_new_ack(tcp_flow_t *f, uint32_t ack) { - uint32_t mss = f->mss ? f->mss : TCP_DEFAULT_MSS; - - if (f->in_fast_recovery){ - if (ack >= f->recover){ - f->cwnd = f->ssthresh; - if (f->cwnd < mss) f->cwnd = mss; +static void tcp_cc_on_new_ack(tcp_flow_t *f, uint32_t ack, uint32_t prev_una) { + uint32_t mss = f->tx.mss ? f->tx.mss : TCP_DEFAULT_MSS; + uint32_t acked = ack - prev_una; + + if (f->tx.in_fast_recovery){ + if (TCP_SEQ_GEQ(ack, f->tx.recover)){ + f->tx.cwnd = f->tx.ssthresh; + if (f->tx.cwnd < mss) f->tx.cwnd = mss; + + f->tx.in_fast_recovery = 0; + f->tx.recover_valid = 0; + f->tx.dup_acks = 0; + f->tx.cwnd_acc = 0; + f->tx.sack_retransmitted_count = 0; + f->tx.sack_rescue_rxt = 0; + f->tx.sack_rescue_valid = 0; + return; + } - f->in_fast_recovery = 0; - f->dup_acks = 0; - f->cwnd_acc = 0; + if (f->tx.sack_ok) { + f->tx.cwnd = f->tx.ssthresh; + if (f->tx.cwnd < mss) f->tx.cwnd = mss; return; } - f->cwnd = f->ssthresh; - if (f->cwnd < mss) f->cwnd = mss; - return; - } + uint32_t cwnd = acked < f->tx.cwnd ? f->tx.cwnd - acked : 0; + if (acked >= mss) { + uint32_t room = UINT32_MAX - cwnd; + cwnd += room < mss ? room : mss; + } + if (cwnd < mss) cwnd = mss; + f->tx.cwnd = cwnd; - if (f->cwnd < f->ssthresh){ - f->cwnd += mss; - if (f->cwnd < mss) f->cwnd = mss; + tcp_tx_seg_t *seg = tcp_find_first_unacked(f); + if (seg && !tcp_retransmit_seg(f, seg)) tcp_restart_retransmit_timer(f); return; } - uint32_t denom = f->cwnd ? f->cwnd : 1u; - uint32_t inc = (mss * mss) / denom; + if (f->tx.recover_valid && TCP_SEQ_GEQ(ack, f->tx.recover)) f->tx.recover_valid = 0; - if (inc == 0) inc = 1; + if (f->tx.cwnd < f->tx.ssthresh){ + uint32_t inc = acked < mss ? acked : mss; + f->tx.cwnd += inc; + if (f->tx.cwnd < mss) f->tx.cwnd = mss; + return; + } - f->cwnd += inc; + uint64_t acc = (uint64_t) f->tx.cwnd_acc + acked; + if (acc >= f->tx.cwnd) { + acc -= f->tx.cwnd; + f->tx.cwnd += mss; + } + f->tx.cwnd_acc = acc > UINT32_MAX ? UINT32_MAX : (uint32_t)acc; } static void tcp_cc_on_dupack(tcp_flow_t *f) { - uint32_t mss = f->mss ? f->mss : TCP_DEFAULT_MSS; + uint32_t mss = f->tx.mss ? f->tx.mss : TCP_DEFAULT_MSS; - if (f->in_fast_recovery){ - f->cwnd += mss; + if (f->tx.in_fast_recovery){ + if (f->tx.sack_ok) { + tcp_sack_recovery_send(f, false); + return; + } + uint32_t room = UINT32_MAX - f->tx.cwnd; + f->tx.cwnd += room < mss ? room : mss; return; } - if (f->dup_acks != 3) return; + if (f->tx.dup_acks < 3 && (!f->tx.sack_ok || !tcp_sack_is_lost(f, f->tx.snd_una))) return; + if (f->tx.recover_valid && TCP_SEQ_LEQ(f->tx.snd_una, f->tx.recover)) return; - uint32_t flight = f->snd_nxt - f->snd_una; + uint32_t flight = f->tx.snd_nxt - f->tx.snd_una; uint32_t half = flight / 2; uint32_t minth = 2u * mss; if (half < minth) half = minth; - f->ssthresh = half; - f->recover = f->snd_nxt; - f->cwnd = f->ssthresh + 3u * mss; - f->in_fast_recovery = 1; - - tcp_tx_seg_t *s = tcp_find_first_unacked(f); - if (s) { - tcp_send_from_seg(f, s); - s->retransmit_cnt++; - s->timer_ms = 0; + f->tx.ssthresh = half; + f->tx.recover = f->tx.snd_nxt; + f->tx.recover_valid = 1; + if (f->tx.sack_ok) f->tx.cwnd = f->tx.ssthresh; + else { + uint64_t recovery_cwnd = (uint64_t)f->tx.ssthresh + 3u * mss; + f->tx.cwnd = recovery_cwnd > UINT32_MAX ? UINT32_MAX : (uint32_t)recovery_cwnd; + } + f->tx.in_fast_recovery = 1; + f->tx.sack_retransmitted_count = 0; + f->tx.sack_rescue_rxt = 0; + f->tx.sack_rescue_valid = 0; + + if (f->tx.sack_ok) tcp_sack_recovery_send(f, true); + else { + tcp_tx_seg_t *seg = tcp_find_first_unacked(f); + if (seg && !tcp_retransmit_seg(f, seg)) tcp_restart_retransmit_timer(f); } } -void tcp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, uint8_t l3_id, uintptr_t ptr, uint32_t len) { - if (len < sizeof(tcp_hdr_t)) return; - - tcp_hdr_t *hdr = (tcp_hdr_t *)ptr; - - uint16_t recv_checksum = hdr->checksum; - hdr->checksum = 0; - - uint16_t calc; - - if (ipver == IP_VER4) calc = tcp_checksum_ipv4(hdr, (uint16_t)len, *(const uint32_t *)src_ip_addr, *(const uint32_t *)dst_ip_addr); - else calc = tcp_checksum_ipv6(hdr, (uint16_t)len, (const uint8_t *)src_ip_addr, (const uint8_t *)dst_ip_addr); +void tcp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, l3_id_t l3_id, netpkt_t* pkt) { + if (!pkt) return; + if (!src_ip_addr || !dst_ip_addr || (ipver != IP_VER4 && ipver != IP_VER6)) { + netpkt_unref(pkt); + return; + } + const uint8_t* segment = (const uint8_t*)netpkt_data(pkt); + uint32_t len = netpkt_len(pkt); + if (len < sizeof(tcp_hdr_t)) { + netpkt_unref(pkt); + return; + } - hdr->checksum = recv_checksum; - if (recv_checksum != calc) return; + tcp_hdr_t hdr; + if (!netpkt_copyout(pkt, 0, &hdr, sizeof(hdr))) { + netpkt_unref(pkt); + return; + } - uint16_t src_port = bswap16(hdr->src_port); - uint16_t dst_port = bswap16(hdr->dst_port); - uint32_t seq = bswap32(hdr->sequence); - uint32_t ack = bswap32(hdr->ack); - uint8_t flags = hdr->flags; - uint16_t window = bswap16(hdr->window); + if (ipver == IP_VER4) { + uint32_t src_ip = 0; + uint32_t dst_ip = 0; + memcpy(&src_ip, src_ip_addr, sizeof(src_ip)); + memcpy(&dst_ip, dst_ip_addr, sizeof(dst_ip)); + if (tcp_checksum_ipv4((const void*)segment, (uint16_t)len, src_ip, dst_ip) != 0) { + netpkt_unref(pkt); + return; + } + } else { + if (tcp_checksum_ipv6((const void*)segment, (uint16_t)len, (const uint8_t *)src_ip_addr, (const uint8_t *)dst_ip_addr) != 0) { + netpkt_unref(pkt); + return; + } + } - uint8_t hdr_len = (uint8_t)((hdr->data_offset_reserved >> 4) * 4); - if (len < hdr_len) return; + uint16_t src_port = bswap16(hdr.src_port); + uint16_t dst_port = bswap16(hdr.dst_port); + uint32_t seq = bswap32(hdr.sequence); + uint32_t ack = bswap32(hdr.ack); + uint8_t flags = hdr.flags; + uint16_t window = bswap16(hdr.window); + uint16_t urgent_ptr = bswap16(hdr.urgent_ptr); + + uint8_t hdr_len = (uint8_t)((hdr.data_offset_reserved >> 4) * 4); + if (hdr_len < sizeof(tcp_hdr_t) || len < hdr_len) { + netpkt_unref(pkt); + return; + } uint32_t data_len = len - hdr_len; - int idx = find_flow(dst_port, ipver, dst_ip_addr, src_ip_addr, src_port); - tcp_flow_t *flow = idx >= 0 ? tcp_flows[idx] : NULL; - if (flow) flow->keepalive_idle_ms = 0; - if (flow) flow->l3_id = l3_id; + tcp_parsed_opts_t parsed_opts; + uint8_t parsed_opts_buf[40]; + uint32_t parsed_opts_len = (uint32_t)(hdr_len > sizeof(tcp_hdr_t) ? hdr_len - sizeof(tcp_hdr_t) : 0); + if (parsed_opts_len && !netpkt_copyout(pkt, sizeof(tcp_hdr_t), parsed_opts_buf, parsed_opts_len)) { + netpkt_unref(pkt); + return; + } + tcp_parse_options(parsed_opts_len ? parsed_opts_buf : NULL, parsed_opts_len, &parsed_opts); - port_manager_t *pm = NULL; - uint8_t ifx = 0; + tcp_flow_t *flow = tcp_flow_acquire_match(dst_port, ipver, dst_ip_addr, src_ip_addr, src_port); + uint8_t ifx = 0; + uint32_t l3_generation = 0; if (ipver == IP_VER4) { l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(l3_id); - if (!v4 || !v4->l2) return; - pm = ifmgr_pm_v4(l3_id); + if (!v4 || !v4->l2) { + if (flow) tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + uint32_t src_ip = 0; + uint32_t dst_ip = 0; + memcpy(&src_ip, src_ip_addr, sizeof(src_ip)); + memcpy(&dst_ip, dst_ip_addr, sizeof(dst_ip)); + bool dst_invalid = ipv4_is_unspecified(dst_ip) || ipv4_is_multicast(dst_ip) || ipv4_is_limited_broadcast(dst_ip) || ipv4_is_directed_broadcast(v4->ip, v4->mask, dst_ip); + bool src_invalid = ipv4_is_unspecified(src_ip) || ipv4_is_multicast(src_ip) || ipv4_is_limited_broadcast(src_ip) || ipv4_is_directed_broadcast(v4->ip, v4->mask, src_ip); + if (src_invalid || dst_invalid) { + if (flow) tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } ifx = v4->l2->ifindex; + l3_generation = v4->generation; } else { l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(l3_id); - if (!v6 || !v6->l2) return; - pm = ifmgr_pm_v6(l3_id); + if (!v6 || !v6->l2) { + if (flow) tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + if (ipv6_is_unspecified((const uint8_t*)dst_ip_addr) || ipv6_is_multicast((const uint8_t*)dst_ip_addr) || ipv6_is_unspecified((const uint8_t*)src_ip_addr) || ipv6_is_multicast((const uint8_t*)src_ip_addr)) { + if (flow) tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } ifx = v6->l2->ifindex; + l3_generation = v6->generation; } - - if (!pm) return; - if (!flow){ - int listen_idx = find_flow(dst_port, ipver, dst_ip_addr, NULL, 0); - if (listen_idx < 0) - listen_idx = find_flow(dst_port, ipver, NULL, NULL, 0); + if (flow && (flow->base.l3_id != l3_id || !l3_generation || flow->base.l3_generation != l3_generation)) { + tcp_free_flow(flow); + tcp_flow_put(flow); + flow = NULL; + } - if ((flags & (1u << SYN_F)) && !(flags & (1u << ACK_F)) && listen_idx >= 0){ - rng_t rng; - uint64_t virt_timer; - asm volatile ("mrs %0, cntvct_el0" : "=r"(virt_timer)); - rng_seed(&rng, virt_timer); - - int syn_total = 0; - int syn_port = 0; - for (int k = 0; k < MAX_TCP_FLOWS; k++){ - tcp_flow_t *f = tcp_flows[k]; - if (!f) continue; - if (f->state != TCP_SYN_RECEIVED) continue; - syn_total++; - if (f->local_port == dst_port && f->l3_id == l3_id) syn_port++; - } - if (syn_total >= (MAX_TCP_FLOWS / 4) || syn_port >= 32) return; - - tcp_flow_t *lf = tcp_flows[listen_idx]; - tcp_flow_t *nf = tcp_alloc_flow(); - if (!nf) return; + if (flow) { + switch (flow->base.state) { + case TCP_TIME_WAIT: + if ((flags & (1 << SYN_F)) && !(flags& ((1 << ACK_F) | (1 << RST_F) | (1 << FIN_F))) && data_len == 0) { + ksocket_t* listener = socket_bind_lookup(PROTO_TCP, ipver, l3_id, ifx, src_ip_addr, src_port, dst_ip_addr, dst_port); + if (listener) { + socket_core_put(listener); + tcp_free_flow(flow); + tcp_flow_put(flow); + flow = NULL; + } + } - flow = nf; - for (int k = 0; k < MAX_TCP_FLOWS; k++) { - if (tcp_flows[k] == nf) { - idx = k; - break; + break; + + case TCP_SYN_RECEIVED: + if ((flags & (1 << SYN_F)) && !(flags& ((1 << RST_F) | (1 << FIN_F))) && data_len == 0 && seq+1 == flow->rx.rcv_nxt && (!(flags & (1 << ACK_F)) || (flow->base.active_open && TCP_SEQ_GT(ack, flow->tx.snd_una) && TCP_SEQ_LEQ(ack, flow->tx.snd_nxt)))) { + if ((flags & (1 << ACK_F)) && flow->base.active_open && TCP_SEQ_GT(ack, flow->tx.snd_una) && TCP_SEQ_LEQ(ack, flow->tx.snd_nxt)) { + if (!tcp_prepare_rcv_buffer(flow)) { + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + bool syn_retransmitted = tcp_acknowledge_segments(flow, ack); + if (syn_retransmitted && !flow->tx.rtt_valid && flow->tx.rto < TCP_SYN_DATA_RTO) flow->tx.rto = TCP_SYN_DATA_RTO; + flow->tx.snd_una = ack; + flow->tx.snd_wnd = window; + flow->tx.snd_wl1 = seq; + flow->tx.snd_wl2 = ack; + flow->base.ctx.ack_received = ack; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->base.ctx.flags = 0; + flow->base.state = TCP_ESTABLISHED; + flow->base.active_open = 0; + flow->timer.keepalive_idle_ms = 0; + flow->timer.delayed_ack_pending = 0; + flow->timer.delayed_ack_timer_ms = 0; + tcp_restart_retransmit_timer(flow); + tcp_send_ack_now(flow); + } else if (!(flags & (1u << ACK_F))) { + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) { + tcp_tx_seg_t *seg = &flow->tx.txq[i]; + if (!seg->used || !seg->syn) continue; + if (!tcp_retransmit_seg(flow, seg)) tcp_restart_retransmit_timer(flow); + break; + } + } + tcp_flow_put(flow); + netpkt_unref(pkt); + return; } + break; + + default: + break; + } + } + + if (!flow){ + net_l4_endpoint remote = {0}; + make_ep(src_ip_addr, src_port, ipver, &remote); + if (!firewall_allows(PROTO_TCP, NET_CTRL_FIREWALL_IN, &remote, dst_port, false)) { + netpkt_unref(pkt); + return; + } + + bool initial_syn = (flags & (1u << SYN_F)) && !(flags & ((1u << ACK_F) | (1u << RST_F) | (1u << FIN_F))) && data_len == 0; + ksocket_t* listener = socket_bind_lookup(PROTO_TCP, ipver, l3_id, ifx, src_ip_addr, src_port, dst_ip_addr, dst_port); + + if (initial_syn && listener){ + rng_t rng; + rng_init_random(&rng); + + tcp_admit_result_t syn_admit = tcp_admit_syn(listener, ipver, src_ip_addr); + if (syn_admit != TCP_ADMIT_OK) { + if (syn_admit == TCP_ADMIT_SYN_GLOBAL) tcp_stats.syn_drop_global++; + else if (syn_admit == TCP_ADMIT_SYN_LISTENER) tcp_stats.syn_drop_listener++; + else if (syn_admit == TCP_ADMIT_SYN_SOURCE) tcp_stats.syn_drop_source++; + else if (syn_admit == TCP_ADMIT_FLOW_RESERVE) tcp_stats.syn_drop_flow_reserve++; + else if (syn_admit == TCP_ADMIT_FLOW_TABLE_FULL) tcp_stats.flow_table_full++; + socket_core_put(listener); + netpkt_unref(pkt); + return; } - flow->local_port = dst_port; - flow->l3_id = l3_id; + const SocketOptions* listener_extra = socket_tcp_options(socket_core_impl(listener)); + tcp_flow_t *nf = tcp_alloc_flow(); + if (!nf) { + socket_core_put(listener); + netpkt_unref(pkt); + return; + } + flow = nf; + flow->base.l3_id = l3_id; + flow->base.l3_generation = l3_generation; - flow->remote.ver = ipver; - memset(flow->remote.ip, 0, 16); - memcpy(flow->remote.ip, src_ip_addr, (uint64_t)(ipver == IP_VER6 ? 16 : 4)); - flow->remote.port = src_port; + flow->base.remote.ver = ipver; + memset(flow->base.remote.ip, 0, 16); + memcpy(flow->base.remote.ip, src_ip_addr, (uint64_t)(ipver == IP_VER6 ? 16 : 4)); + flow->base.remote.port = src_port; - flow->local.ver = ipver; - memset(flow->local.ip, 0, 16); - memcpy(flow->local.ip, dst_ip_addr, (uint64_t)(ipver == IP_VER6 ? 16 : 4)); - flow->local.port = dst_port; + flow->base.local.ver = ipver; + memset(flow->base.local.ip, 0, 16); + memcpy(flow->base.local.ip, dst_ip_addr, (uint64_t)(ipver == IP_VER6 ? 16 : 4)); + flow->base.local.port = dst_port; - flow->state = TCP_SYN_RECEIVED; - flow->retries = TCP_SYN_RETRIES; + flow->base.state = TCP_SYN_RECEIVED; - tcp_parsed_opts_t pop; - tcp_parse_options((const uint8_t *)(ptr + sizeof(tcp_hdr_t)), (uint32_t)(hdr_len > sizeof(tcp_hdr_t) ? hdr_len - sizeof(tcp_hdr_t) : 0), &pop); + tcp_parsed_opts_t pop = parsed_opts; - flow->ws_send = lf->ws_send; - flow->ws_recv = 0; - flow->ws_ok = (lf->ws_ok && pop.has_wscale) ? 1 : 0; - if (flow->ws_ok) { - flow->ws_recv = pop.wscale; - if (flow->ws_recv > 14) flow->ws_recv = 14; + flow->tx.ws_send = 0; + flow->tx.ws_recv = 0; + flow->tx.ws_ok = pop.has_wscale ? 1 : 0; + if (flow->tx.ws_ok) { + flow->tx.ws_recv = pop.wscale; + if (flow->tx.ws_recv > 14) flow->tx.ws_recv = 14; } else { - flow->ws_send = 0; - flow->ws_recv = 0; + flow->tx.ws_send = 0; + flow->tx.ws_recv = 0; } - flow->sack_ok = (lf->sack_ok && pop.sack_permitted) ? 1 : 0; - - if (pop.has_mss && pop.mss){ - uint32_t m = pop.mss; - uint32_t minm = ipver == IP_VER6 ? 1220u : 536u; - uint32_t maxm = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); - if (m < minm) m = minm; - if (m > maxm) m = maxm; - flow->mss = m; - } else flow->mss = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); - flow->ctx.flags = 0; - flow->ctx.options = lf->ctx.options; - flow->ctx.payload.ptr = 0; - flow->ctx.payload.size = 0; + flow->tx.path_mss = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); + flow->tx.peer_mss = pop.has_mss && pop.mss ? pop.mss : (ipver == IP_VER6 ? TCP_DEFAULT_PEER_MSS_IPV6 : TCP_DEFAULT_PEER_MSS_IPV4); + flow->base.ctx.flags = 0; + flow->base.ctx.options.ptr = 0; + flow->base.ctx.options.size = 0; + flow->base.ctx.payload.ptr = 0; + flow->base.ctx.payload.size = 0; uint32_t iss = rng_next32(&rng); - flow->ctx.sequence = iss; - flow->snd_una = iss; - flow->snd_nxt = iss; + flow->base.ctx.sequence = iss; + flow->tx.snd_una = iss; + flow->tx.snd_nxt = iss; - flow->ctx.ack = seq + 1; - flow->rcv_nxt = seq + 1; + flow->base.ctx.ack = seq + 1; + flow->rx.rcv_nxt = seq + 1; - flow->ctx.expected_ack = iss + 1; - flow->ctx.ack_received = 0; - uint32_t new_wnd = window; - if (flow->ws_ok && flow->ws_recv) new_wnd <<= flow->ws_recv; - flow->snd_wnd = new_wnd; - - flow->persist_active = 0; - flow->persist_timer_ms = 0; - flow->persist_timeout_ms = 0; - - flow->delayed_ack_pending = 0; - flow->delayed_ack_timer_ms = 0; - - flow->rcv_wnd_max = lf->rcv_wnd_max; - flow->rcv_buf_used = 0; - uint16_t synack_wnd = tcp_calc_adv_wnd_field(flow, flow->ws_ok ? 1 : 0); - - flow->ip_ttl = lf->ip_ttl; - flow->ip_dontfrag = lf->ip_dontfrag; - flow->keepalive_on = lf->keepalive_on; - flow->keepalive_ms = lf->keepalive_ms; - flow->keepalive_idle_ms = 0; - - flow->cwnd = flow->mss; - flow->ssthresh = TCP_RECV_WINDOW; - flow->dup_acks = 0; - flow->in_fast_recovery = 0; - flow->recover = 0; - flow->cwnd_acc = 0; - - flow->time_wait_ms = 0; - flow->fin_wait2_ms = 0; - - tcp_hdr_t synack_hdr; - synack_hdr.src_port = bswap16(dst_port); - synack_hdr.dst_port = bswap16(src_port); - synack_hdr.sequence = bswap32(iss); - synack_hdr.ack = bswap32(seq + 1); - synack_hdr.flags = (uint8_t)((1u << SYN_F) | (1u << ACK_F)); - synack_hdr.window = synack_wnd; - synack_hdr.urgent_ptr = 0; - - uint8_t syn_opts[40]; - uint8_t syn_opts_len = tcp_build_syn_options(syn_opts, (uint16_t)flow->mss, flow->ws_ok ? flow->ws_send : 0xffu, flow->sack_ok); - - if (ipver == IP_VER4) { - ipv4_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = l3_id; - tcp_send_segment(IP_VER4, flow->local.ip, src_ip_addr, &synack_hdr, syn_opts, syn_opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } else { - ipv6_tx_opts_t tx; - tx.scope = IP_TX_BOUND_L3; - tx.index = l3_id; - tcp_send_segment(IP_VER6, flow->local.ip, src_ip_addr, &synack_hdr, syn_opts, syn_opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); + flow->base.ctx.expected_ack = iss + 1; + flow->base.ctx.ack_received = 0; + flow->tx.snd_wnd = window; + flow->tx.snd_wl1 = seq; + flow->tx.snd_wl2 = 0; + + tcp_persist_reset(flow); + + flow->timer.delayed_ack_pending = 0; + flow->timer.delayed_ack_timer_ms = 0; + + uint32_t rcvbuf = TCP_DEFAULT_RCV_BUF; + if (listener_extra && (listener_extra->flags & SOCK_OPT_BUF_SIZE) && listener_extra->buf_size) rcvbuf = listener_extra->buf_size; + flow->rx.rcv_wnd_max = tcp_clamp_rcvbuf(rcvbuf); + tcp_flow_apply_options(flow, listener_extra, UINT32_MAX); + flow->tx.sack_ok = flow->tx.sack_enabled && pop.sack_permitted; + if (flow->rx.rcv_wnd_max > 65535u && pop.has_wscale) { + flow->tx.ws_send = 8; + flow->tx.ws_ok = 1; + } + flow->rx.rcv_base = flow->rx.rcv_nxt; + flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + flow->rx.rcv_ooo_used = 0; + flow->rx.sack_recent_left = 0; + flow->rx.sack_recent_right = 0; + flow->rx.rcv_buf = 0; + flow->rx.rcv_adv_edge = flow->rx.rcv_nxt + flow->rx.rcv_wnd_max; + tcp_update_adv_wnd(flow, flow->tx.ws_ok ? 1 : 0); + + flow->tx.cwnd = tcp_initial_cwnd(flow->tx.mss); + flow->tx.ssthresh = TCP_RECV_WINDOW; + flow->tx.dup_acks = 0; + flow->tx.in_fast_recovery = 0; + flow->tx.recover = 0; + flow->tx.recover_valid = 0; + flow->tx.cwnd_acc = 0; + + flow->timer.time_wait_ms = 0; + flow->timer.fin_wait2_ms = 0; + flow->base.listener = listener; + listener = NULL; + + tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow, 0); + if (!seg) { + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; } - tcp_daemon_kick(); + seg->syn = 1; + seg->fin = 0; + seg->psh = 0; + seg->persist = 0; + seg->rtt_sample = 1; + flow->tx.rtt_sample_pending = 1; + seg->retransmit_cnt = 0; + seg->seq = iss; + seg->len = 0; + seg->pkt = NULL; + seg->payload_off = 0; + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + seg->opts_len = tcp_build_syn_options(seg->opts, (uint16_t)flow->tx.advertised_mss, flow->tx.ws_ok ? flow->tx.ws_send : 0xff, flow->tx.sack_ok); + flow->tx.snd_nxt = iss + 1; + flow->base.ctx.sequence = flow->tx.snd_nxt; + if (!tcp_active_insert_flow(flow) || !tcp_send_from_seg(flow, seg)) { + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + tcp_flow_put(flow); + netpkt_unref(pkt); return; } + if (listener) socket_core_put(listener); if (!(flags & (1u << RST_F))){ if (flags & (1u << ACK_F)){ - tcp_send_reset(ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); } else { uint32_t seg_len = data_len; if (flags & (1u << SYN_F)) seg_len++; if (flags & (1u << FIN_F)) seg_len++; - tcp_send_reset(ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, seq, seq + seg_len, true); + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, seq, seq + seg_len, true); } } + netpkt_unref(pkt); return; } - if (flow->state == TCP_TIME_WAIT){ - if (flags & (1u << RST_F)) return; + uint8_t fin = (flags & (1 << FIN_F)) ? 1 : 0; + uint32_t seg_seq = seq; + bool handshake_ack_processed = false; + + switch (flow->base.state) { + case TCP_TIME_WAIT: + if (flags & (1u << RST_F)) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + if ((flags & (1u << FIN_F)) && seq + data_len + 1 == flow->rx.rcv_nxt) flow->timer.time_wait_ms = 0; + tcp_send_ack_now(flow); + + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + + case TCP_SYN_SENT: { + bool ack_set = (flags & (1u << ACK_F)) != 0; + bool ack_acceptable = false; + + if (ack_set) { + if (TCP_SEQ_LEQ(ack, flow->tx.snd_una) || TCP_SEQ_GT(ack, flow->tx.snd_nxt)) { + if (!(flags & (1u << RST_F))) tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + ack_acceptable = true; + } + + if (flags & (1u << RST_F)) { + if (ack_acceptable) tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - uint32_t seg_len = data_len; + if (!(flags & (1u << SYN_F))) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - if (flags & (1u << SYN_F)) seg_len++; - if (flags & (1u << FIN_F)) seg_len++; + flow->timer.keepalive_idle_ms = 0; + flow->base.ctx.ack = seq + 1; + flow->rx.rcv_nxt = seq + 1; + flow->rx.rcv_base = flow->rx.rcv_nxt; + flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + flow->rx.rcv_ooo_used = 0; + flow->rx.sack_recent_left = 0; + flow->rx.sack_recent_right = 0; + + tcp_parsed_opts_t pop = parsed_opts; + + flow->tx.ws_recv = pop.has_wscale ? pop.wscale : 0; + if (flow->tx.ws_recv > 14) flow->tx.ws_recv = 14; + flow->tx.ws_ok = (flow->tx.ws_send != 0) && pop.has_wscale ? 1 : 0; + if (!flow->tx.ws_ok) { + flow->tx.ws_send = 0; + flow->tx.ws_recv = 0; + } - uint32_t seg_end = seq + seg_len; + flow->tx.sack_ok = flow->tx.sack_enabled && pop.sack_permitted; + + flow->tx.path_mss = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); + flow->tx.peer_mss = pop.has_mss && pop.mss ? pop.mss : (ipver == IP_VER6 ? TCP_DEFAULT_PEER_MSS_IPV6 : TCP_DEFAULT_PEER_MSS_IPV4); + tcp_update_mss(flow); + flow->tx.cwnd = tcp_initial_cwnd(flow->tx.mss); + flow->tx.snd_wnd = window; + flow->tx.snd_wl1 = seq; + flow->tx.snd_wl2 = ack_set ? ack : 0; + tcp_update_adv_wnd(flow, 1); + + if (!ack_acceptable) { + flow->base.state = TCP_SYN_RECEIVED; + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) { + tcp_tx_seg_t *seg = &flow->tx.txq[i]; + if (!seg->used || !seg->syn) continue; + if (!tcp_retransmit_seg(flow, seg)) tcp_restart_retransmit_timer(flow); + break; + } + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - if (seq <= flow->rcv_nxt && seg_end >= flow->rcv_nxt){ - flow->time_wait_ms = 0; + if (!tcp_prepare_rcv_buffer(flow)) { + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + bool syn_retransmitted = tcp_acknowledge_segments(flow, ack); + if (syn_retransmitted && !flow->tx.rtt_valid && flow->tx.rto < TCP_SYN_DATA_RTO) flow->tx.rto = TCP_SYN_DATA_RTO; + flow->tx.snd_una = ack; + flow->base.ctx.ack_received = ack; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->base.ctx.flags = 0; + flow->base.state = TCP_ESTABLISHED; + flow->base.active_open = 0; + flow->timer.delayed_ack_pending = 0; + flow->timer.delayed_ack_timer_ms = 0; + tcp_restart_retransmit_timer(flow); tcp_send_ack_now(flow); + + if (!data_len && !fin) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + seg_seq = seq + 1; + if (!tcp_segment_acceptable(flow, seg_seq, data_len + fin)) { + tcp_send_ack_now(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + handshake_ack_processed = true; + break; } - return; - } - uint32_t new_wnd = window; - if (flow->ws_ok && flow->ws_recv) new_wnd <<= flow->ws_recv; - flow->snd_wnd = new_wnd; + case TCP_SYN_RECEIVED: { + uint32_t syn_seg_len = data_len + ((flags & (1u << SYN_F)) ? 1 : 0) + fin; + bool seq_acceptable = tcp_segment_acceptable(flow, seq, syn_seg_len); - if (flow->snd_wnd > 0){ - flow->persist_active = 0; - flow->persist_timer_ms = 0; - flow->persist_timeout_ms = 0; - flow->persist_probe_cnt = 0; - } else { - tcp_daemon_kick(); - } + if (flags & (1u << RST_F)) { + if (seq == flow->rx.rcv_nxt) tcp_free_flow(flow); + else if (tcp_segment_acceptable(flow, seq, 0)) tcp_send_challenge_ack(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - uint8_t fin = (flags & (1u << FIN_F)) ? 1u : 0u; + if (flags & (1u << SYN_F)) { + tcp_send_challenge_ack(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - if (flags & (1u << ACK_F)){ - if (ack > flow->snd_una && ack <= flow->snd_nxt){ - uint32_t prev_una = flow->snd_una; + if (!seq_acceptable) { + tcp_send_ack_now(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - flow->snd_una = ack; - flow->ctx.ack_received = ack; - flow->dup_acks = 0; + if (!(flags & (1u << ACK_F))) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - for (int i = 0; i < TCP_MAX_TX_SEGS; i++){ - tcp_tx_seg_t *s = &flow->txq[i]; - if (!s->used) continue; + if (TCP_SEQ_LEQ(ack, flow->tx.snd_una) || TCP_SEQ_GT(ack, flow->tx.snd_nxt)) { + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, ack, 0, false); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - uint32_t s_end = s->seq + s->len + (s->syn ? 1u : 0u) + (s->fin ? 1u : 0u); + flow->timer.keepalive_idle_ms = 0; - if (s_end <= ack){ - if (s->rtt_sample && s->retransmit_cnt == 0) tcp_rtt_update(flow, s->timer_ms); + if (!tcp_prepare_rcv_buffer(flow)) { + if (!flow->base.active_open) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - if (s->buf && s->len) free_sized((void *)s->buf, s->len); + tcp_send_reset(l3_id, ipver, dst_ip_addr, src_ip_addr, dst_port, src_port, 0, flow->base.ctx.ack, true); + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - s->used = 0; - s->buf = 0; - s->len = 0; + if (!flow->base.active_open) { + int32_t rc = SOCK_ERR_STATE; + if (flow->base.listener) rc = tcp_accept_enqueue(flow->base.listener, ipver, flow->base.l3_id, src_ip_addr, dst_ip_addr, src_port, dst_port); + if (rc != SOCK_OK) { + if (rc == SOCK_ERR_WOULDBLOCK) { + if (flow->rx.rcv_buf) { + uintptr_t buf = flow->rx.rcv_buf; + flow->rx.rcv_buf = 0; + release((void*)buf); + flow->rx.rcv_base = flow->rx.rcv_nxt; + flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + tcp_update_adv_wnd(flow, 1); + } + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + tcp_hdr_t rst_hdr; + rst_hdr.src_port = bswap16(flow->base.local.port); + rst_hdr.dst_port = bswap16(flow->base.remote.port); + rst_hdr.sequence = bswap32(flow->tx.snd_nxt); + rst_hdr.ack = bswap32(flow->base.ctx.ack); + rst_hdr.flags = (uint8_t)((1 << RST_F) | (1 << ACK_F)); + rst_hdr.window = 0; + rst_hdr.urgent_ptr = 0; + + (void)tcp_send_flow_segment(flow, &rst_hdr, NULL, 0, NULL, 0); + + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + if (flow->base.listener) { + socket_core_put(flow->base.listener); + flow->base.listener = NULL; } } - if (ack > prev_una) tcp_cc_on_new_ack(flow, ack); + bool syn_retransmitted_recv = tcp_acknowledge_segments(flow, ack); + if (syn_retransmitted_recv && !flow->tx.rtt_valid && flow->tx.rto < TCP_SYN_DATA_RTO) flow->tx.rto = TCP_SYN_DATA_RTO; + flow->tx.snd_una = ack; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->base.ctx.ack_received = ack; + flow->base.ctx.flags = 0; + flow->base.state = TCP_ESTABLISHED; + flow->base.active_open = 0; + flow->timer.delayed_ack_pending = 0; + flow->timer.delayed_ack_timer_ms = 0; - if (flow->state == TCP_FIN_WAIT_1 && ack >= flow->ctx.expected_ack){ - flow->state = TCP_FIN_WAIT_2; - flow->fin_wait2_ms = 0; - tcp_daemon_kick(); - } else if ((flow->state == TCP_LAST_ACK || flow->state == TCP_CLOSING) && ack >= flow->ctx.expected_ack){ - tcp_free_flow(idx); + uint32_t new_wnd = window; + if (flow->tx.ws_ok && flow->tx.ws_recv) new_wnd = new_wnd << flow->tx.ws_recv; + flow->tx.snd_wnd = new_wnd; + flow->tx.snd_wl1 = seq; + flow->tx.snd_wl2 = ack; + tcp_restart_retransmit_timer(flow); + + if (!data_len && !fin) { + tcp_flow_put(flow); + netpkt_unref(pkt); return; } - } else if (ack == flow->snd_una && data_len == 0 && !fin){ - if (flow->dup_acks < UINT8_MAX) flow->dup_acks++; - tcp_cc_on_dupack(flow); - } else { - flow->dup_acks = 0; + + handshake_ack_processed = true; + break; } + + default: + break; } - uint32_t seg_seq = seq; + if (!handshake_ack_processed) { + uint32_t seg_len = data_len + ((flags & (1u << SYN_F)) ? 1u : 0u) + fin; - switch (flow->state){ - case TCP_SYN_SENT: - if ((flags & (1u << SYN_F)) && (flags & (1u << ACK_F)) && ack == flow->ctx.expected_ack){ - flow->ctx.ack = seq + 1; - flow->rcv_nxt = seq + 1; - flow->ctx.ack_received = ack; - flow->snd_una = ack; - flow->snd_nxt = flow->ctx.sequence; - flow->ctx.sequence = flow->snd_nxt; - flow->ctx.flags = 0; - - tcp_parsed_opts_t pop; - tcp_parse_options((const uint8_t *)(ptr + sizeof(tcp_hdr_t)), (uint32_t)(hdr_len > sizeof(tcp_hdr_t) ? hdr_len - sizeof(tcp_hdr_t) : 0), &pop); - - flow->ws_recv = pop.has_wscale ? pop.wscale : 0; - if (flow->ws_recv > 14) flow->ws_recv = 14; - flow->ws_ok = (flow->ws_send != 0) && pop.has_wscale ? 1 : 0; - if (!flow->ws_ok) { - flow->ws_send = 0; - flow->ws_recv = 0; - } - - flow->sack_ok = pop.sack_permitted ? 1 : 0; - - if (pop.has_mss && pop.mss){ - uint32_t m = pop.mss; - uint32_t minm = ipver == IP_VER6 ? 1220u : 536u; - uint32_t maxm = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); - if (m < minm) m = minm; - if (m > maxm) m = maxm; - flow->mss = m; - } else { - flow->mss = tcp_calc_mss_for_l3(l3_id, ipver, src_ip_addr); - } + if (flags & (1u << RST_F)){ + if (seq == flow->rx.rcv_nxt) tcp_free_flow(flow); + else if (tcp_segment_acceptable(flow, seq, 0)) tcp_send_challenge_ack(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - uint32_t new_wnd = window; - if (flow->ws_ok && flow->ws_recv) new_wnd <<= flow->ws_recv; - flow->snd_wnd = new_wnd; - - (void)tcp_calc_adv_wnd_field(flow, 1); - - tcp_hdr_t final_ack; - final_ack.src_port = bswap16(flow->local_port); - final_ack.dst_port = bswap16(flow->remote.port); - final_ack.sequence = bswap32(flow->ctx.sequence); - final_ack.ack = bswap32(flow->ctx.ack); - final_ack.flags = (uint8_t)(1u << ACK_F); - final_ack.window = flow->ctx.window; - final_ack.urgent_ptr = 0; - - if (flow->local.ver == IP_VER4) { - ipv4_tx_opts_t tx; - tcp_build_tx_opts_from_local_v4(flow->local.ip, &tx); - tcp_send_segment(IP_VER4, flow->local.ip, flow->remote.ip, &final_ack, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } else { - ipv6_tx_opts_t tx; - tcp_build_tx_opts_from_local_v6(flow->local.ip, &tx); - tcp_send_segment(IP_VER6, flow->local.ip, flow->remote.ip, &final_ack, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); + if (flags & (1u << SYN_F)) { + tcp_send_challenge_ack(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + + if (!tcp_segment_acceptable(flow, seq, seg_len)) { + if (data_len && TCP_SEQ_LT(seq, flow->rx.rcv_nxt)) { + uint32_t duplicate_right = seq + data_len; + if (TCP_SEQ_GT(duplicate_right, flow->rx.rcv_nxt)) duplicate_right = flow->rx.rcv_nxt; + tcp_note_dsack(flow, seq, duplicate_right); } + tcp_send_ack_now(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - flow->state = TCP_ESTABLISHED; - flow->delayed_ack_pending = 0; - flow->delayed_ack_timer_ms = 0; - tcp_daemon_kick(); - } else if (flags & (1u << RST_F)){ - flow->state = TCP_STATE_CLOSED; + if (!(flags & (1u << ACK_F))) { + tcp_flow_put(flow); + netpkt_unref(pkt); + return; } - return; + if (TCP_SEQ_GT(ack, flow->tx.snd_nxt)) { + tcp_send_ack_now(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } - case TCP_SYN_RECEIVED: - if ((flags & (1u << ACK_F)) && !(flags & (1u << SYN_F)) && !(flags & (1u << RST_F)) && ack == flow->ctx.expected_ack){ - flow->ctx.sequence += 1; - flow->snd_una = ack; - flow->snd_nxt = flow->ctx.sequence; - flow->state = TCP_ESTABLISHED; - flow->delayed_ack_pending = 0; - flow->delayed_ack_timer_ms = 0; - flow->ctx.ack_received = ack; + flow->timer.keepalive_idle_ms = 0; + uint32_t old_wnd = flow->tx.snd_wnd; + uint32_t new_wnd = window; + if (flow->tx.ws_ok && flow->tx.ws_recv) new_wnd <<= flow->tx.ws_recv; + if (TCP_SEQ_GEQ(ack, flow->tx.snd_una) && (TCP_SEQ_LT(flow->tx.snd_wl1, seq) || (flow->tx.snd_wl1 == seq && TCP_SEQ_LEQ(flow->tx.snd_wl2, ack)))) { + flow->tx.snd_wnd = new_wnd; + flow->tx.snd_wl1 = seq; + flow->tx.snd_wl2 = ack; + if (new_wnd) tcp_persist_reset(flow); + } - port_recv_handler_t h = port_get_handler(pm, PROTO_TCP, dst_port); - if (h) (void)h(ifx, ipver, src_ip_addr, dst_ip_addr, 0, 0, src_port, dst_port); + if (TCP_SEQ_GT(ack, flow->tx.snd_una)) { + uint32_t prev_una = flow->tx.snd_una; + + flow->tx.snd_una = ack; + flow->base.ctx.ack_received = ack; + flow->tx.dup_acks = 0; + bool syn_retransmitted = tcp_acknowledge_segments(flow, ack); + if (syn_retransmitted && !flow->tx.rtt_valid && flow->tx.rto < TCP_SYN_DATA_RTO) flow->tx.rto = TCP_SYN_DATA_RTO; + + tcp_sack_trim_ranges(flow->tx.sack_ranges, &flow->tx.sack_range_count, ack); + tcp_sack_trim_ranges(flow->tx.sack_retransmitted_ranges, &flow->tx.sack_retransmitted_count, ack); + bool sack_changed = flow->tx.sack_ok && parsed_opts.sack_count && tcp_apply_sack_blocks(flow, &parsed_opts); + bool partial_recovery = flow->tx.in_fast_recovery && flow->tx.sack_ok && TCP_SEQ_LT(ack, flow->tx.recover); + tcp_cc_on_new_ack(flow, ack, prev_una); + if (sack_changed && !flow->tx.in_fast_recovery) { + if (flow->tx.dup_acks < UINT8_MAX) flow->tx.dup_acks++; + tcp_cc_on_dupack(flow); + } + tcp_restart_retransmit_timer(flow); + if (partial_recovery && flow->tx.in_fast_recovery) tcp_sack_recovery_send(flow, false); - tcp_daemon_kick(); - } else if (flags & (1u << RST_F)){ - tcp_free_flow(idx); + if (flow->base.state == TCP_FIN_WAIT_1 && TCP_SEQ_GEQ(ack, flow->base.ctx.expected_ack)) { + flow->base.state = TCP_FIN_WAIT_2; + flow->timer.fin_wait2_ms = 0; + tcp_daemon_kick(); + } else if (flow->base.state == TCP_CLOSING && TCP_SEQ_GEQ(ack, flow->base.ctx.expected_ack)) { + tcp_enter_time_wait(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } else if (flow->base.state == TCP_LAST_ACK && TCP_SEQ_GEQ(ack, flow->base.ctx.expected_ack)) { + tcp_free_flow(flow); + tcp_flow_put(flow); + netpkt_unref(pkt); + return; + } + } else { + bool sack_changed = flow->tx.sack_ok && parsed_opts.sack_count && tcp_apply_sack_blocks(flow, &parsed_opts); + if (sack_changed) { + if (flow->tx.in_fast_recovery) tcp_sack_recovery_send(flow, false); + else { + if (flow->tx.dup_acks < UINT8_MAX) flow->tx.dup_acks++; + tcp_cc_on_dupack(flow); + } + } else { + if (ack == flow->tx.snd_una && data_len == 0 && !fin && old_wnd && flow->tx.snd_wnd && TCP_SEQ_LT(flow->tx.snd_una, flow->tx.snd_nxt) && flow->tx.snd_wnd == old_wnd) { + if (flow->tx.dup_acks < UINT8_MAX) flow->tx.dup_acks++; + tcp_cc_on_dupack(flow); + } else flow->tx.dup_acks = 0; + } } + } - return; + if (flags & (1 << URG_F)) { + //do we really need this in 2026? + uint32_t urg_seq = seq + urgent_ptr; + if (TCP_SEQ_GT(urg_seq, flow->rx.rcv_base) && (!flow->rx.urg_valid || TCP_SEQ_GT(urg_seq, flow->rx.urg_seq))) { + flow->rx.urg_valid = 1; + flow->rx.urg_seq = urg_seq; + } + } - default: - break; + if (flow->tx.snd_wnd > 0) { + tcp_tx_seg_t *best = tcp_find_first_unacked(flow); + if (best && best->persist) { + best->persist = 0; + if (!tcp_retransmit_seg(flow, best)) tcp_restart_retransmit_timer(flow); + } } - if (flags & (1u << RST_F)) { - tcp_free_flow(idx); - return; + if (flow->tx.nagle_len && flow->tx.snd_wnd > 0 && !tcp_flush_nagle(flow, flow->tx.fin_tx_pending ? 1 : 0)) tcp_daemon_kick(); + if (flow->tx.fin_tx_pending) { + tcp_try_send_pending_fin(flow); + if (flow->tx.fin_tx_pending) tcp_daemon_kick(); } - int need_ack = 0; - int ack_immediate = 0; - int ack_defer = 0; + bool need_ack = false; + bool ack_immediate = false; if (data_len || fin) { - uint32_t rcv_nxt = flow->rcv_nxt; - uint32_t wnd_end = rcv_nxt + flow->rcv_wnd; + uint32_t rcv_nxt = flow->rx.rcv_nxt; + uint32_t wnd_end = rcv_nxt + flow->rx.rcv_wnd; uint32_t orig_data_len = data_len; uint8_t fin_in = fin; + uint8_t had_reass = flow->rx.reass_count ? 1 : 0; uint32_t fin_seq = seg_seq + orig_data_len; uint32_t orig_end = seg_seq + orig_data_len + (fin ? 1u : 0u); + bool discard_payload = flow->base.state == TCP_FIN_WAIT_1 || flow->base.state == TCP_FIN_WAIT_2 || flow->base.state == TCP_CLOSING || flow->base.state == TCP_LAST_ACK || flow->base.state == TCP_TIME_WAIT; - if (orig_end <= rcv_nxt || seg_seq >= wnd_end) { - need_ack = 1; - ack_immediate = 1; + if (orig_data_len && TCP_SEQ_LT(seg_seq, rcv_nxt)) { + uint32_t duplicate_right = seg_seq + orig_data_len; + if (TCP_SEQ_GT(duplicate_right, rcv_nxt)) duplicate_right = rcv_nxt; + tcp_note_dsack(flow, seg_seq, duplicate_right); + } + + if (TCP_SEQ_LEQ(orig_end, rcv_nxt) || TCP_SEQ_GEQ(seg_seq, wnd_end)) { + need_ack = true; + ack_immediate = true; } else { if (fin_in) { - if (fin_seq < rcv_nxt || fin_seq >= wnd_end) fin_in = 0; + if (TCP_SEQ_LT(fin_seq, rcv_nxt) || TCP_SEQ_GEQ(fin_seq, wnd_end)) fin_in = 0; } - const uint8_t *payload = (const uint8_t *)(ptr + hdr_len); + uint32_t payload = hdr_len; - if (seg_seq < rcv_nxt) { + if (TCP_SEQ_LT(seg_seq, rcv_nxt)) { uint32_t d = rcv_nxt - seg_seq; if (d >= data_len) { payload += data_len; @@ -754,169 +1477,207 @@ void tcp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_a } if (data_len) { - if (seg_seq >= wnd_end) data_len = 0; - else if (seg_seq + data_len > wnd_end) data_len = wnd_end - seg_seq; + if (TCP_SEQ_GEQ(seg_seq, wnd_end)) data_len = 0; + else if (TCP_SEQ_GT(seg_seq + data_len, wnd_end)) data_len = wnd_end - seg_seq; } if (!data_len && !fin_in){ - need_ack = 1; - ack_immediate = 1; - } else if (seg_seq == flow->rcv_nxt) { + need_ack = true; + ack_immediate = true; + } else if (seg_seq == flow->rx.rcv_nxt) { if (data_len){ - uint32_t free_space = (flow->rcv_buf_used < flow->rcv_wnd_max) ? (flow->rcv_wnd_max - flow->rcv_buf_used) : 0; - - port_recv_handler_t h = port_get_handler(pm, PROTO_TCP, dst_port); - uint32_t offer = data_len; - if (offer > free_space) offer = free_space; + uint32_t right_edge = flow->rx.rcv_adv_edge; + uint32_t hard_edge = flow->rx.rcv_buf && flow->rx.rcv_wnd_max ? flow->rx.rcv_base + flow->rx.rcv_wnd_max : flow->rx.rcv_nxt; + if (TCP_SEQ_GT(right_edge, hard_edge)) right_edge = hard_edge; + if (TCP_SEQ_GEQ(seg_seq, right_edge)) offer = 0; + else if (TCP_SEQ_GT(seg_seq + offer, right_edge)) offer = right_edge - seg_seq; uint32_t accepted = 0; - if (offer && h) accepted = h(ifx, ipver, src_ip_addr, dst_ip_addr, (uintptr_t)payload, offer, src_port, dst_port); - if (accepted > offer) accepted = offer; - - if (h && accepted == 0 && data_len) { - (void)tcp_calc_adv_wnd_field(flow, 1); - need_ack = 1; - ack_immediate = 1; + uint32_t stored = 0; + if (offer && discard_payload) accepted = offer; + else if (offer && flow->rx.rcv_buf && flow->rx.rcv_wnd_max) { + uint8_t *rx = (uint8_t *)flow->rx.rcv_buf; + uint32_t cap = flow->rx.rcv_wnd_max; + uint32_t pos = seg_seq % cap; + uint32_t first = cap - pos; + + if (first > offer) first = offer; + if (first && !netpkt_copyout(pkt, payload, rx + pos, first)) stored = 0; + else if (offer > first && !netpkt_copyout(pkt, payload + first, rx, offer - first)) stored = 0; + else stored = offer; + + accepted = stored; } - if (!accepted && offer && (flow->state == TCP_FIN_WAIT_1 || flow->state == TCP_FIN_WAIT_2 || flow->state == TCP_CLOSING || flow->state == TCP_LAST_ACK || flow->state == TCP_TIME_WAIT)) { - flow->rcv_nxt += offer; - flow->ctx.ack = flow->rcv_nxt; - accepted = offer; - } else if (accepted) { - flow->rcv_nxt += accepted; - flow->ctx.ack = flow->rcv_nxt; - flow->rcv_buf_used += accepted; - } - if (accepted < data_len) { - ack_immediate = 1; + if (accepted) { + flow->rx.rcv_nxt += accepted; + if (stored) flow->rx.rcv_data_nxt = flow->rx.rcv_nxt; + flow->base.ctx.ack = flow->rx.rcv_nxt; + } else if (data_len) { + tcp_update_adv_wnd(flow, 1); + ack_immediate = true; } + + if (accepted < data_len) ack_immediate = true; } if (fin_in) { - if (flow->rcv_nxt == fin_seq) { - flow->rcv_nxt += 1; - flow->ctx.ack = flow->rcv_nxt; - - tcp_state_t old = flow->state; - - if (old == TCP_ESTABLISHED) flow->state = TCP_CLOSE_WAIT; - else if (old == TCP_FIN_WAIT_1) flow->state = TCP_CLOSING; - else if (old == TCP_FIN_WAIT_2 || old == TCP_CLOSING || old == TCP_LAST_ACK) { - flow->state = TCP_TIME_WAIT; - flow->time_wait_ms = 0; - tcp_daemon_kick(); - } - - ack_immediate = 1; + if (flow->rx.rcv_nxt == fin_seq) { + tcp_consume_fin(flow); + ack_immediate = true; } else { - flow->fin_pending = 1; - flow->fin_seq = fin_seq; + flow->rx.fin_pending = 1; + flow->rx.fin_seq = fin_seq; } } - tcp_reass_drain_inseq(flow, pm, ifx, ipver, src_ip_addr, dst_ip_addr, src_port, dst_port); + if (tcp_reass_drain_inseq(flow)) ack_immediate = true; + if (had_reass) ack_immediate = true; - if (flow->fin_pending && flow->fin_seq == flow->rcv_nxt){ - flow->fin_pending = 0; - flow->rcv_nxt += 1; - flow->ctx.ack = flow->rcv_nxt; + if (flow->rx.fin_pending && flow->rx.fin_seq == flow->rx.rcv_nxt){ + tcp_consume_fin(flow); + ack_immediate = true; + } - tcp_state_t old = flow->state; + tcp_update_adv_wnd(flow, 1); - if (old == TCP_ESTABLISHED) flow->state = TCP_CLOSE_WAIT; - else if (old == TCP_FIN_WAIT_1) flow->state = TCP_CLOSING; - else if (old == TCP_FIN_WAIT_2 || old == TCP_CLOSING || old == TCP_LAST_ACK) { - flow->state = TCP_TIME_WAIT; - flow->time_wait_ms = 0; - tcp_daemon_kick(); + need_ack = true; + } else { + if (!discard_payload && data_len && flow->rx.rcv_buf && flow->rx.rcv_wnd_max) { + uint32_t ooo_seq = seg_seq; + uint32_t ooo_data = payload; + uint32_t ooo_len = data_len; + + if (TCP_SEQ_LT(ooo_seq, flow->rx.rcv_nxt)) { + uint32_t d = flow->rx.rcv_nxt - ooo_seq; + if (d >= ooo_len) ooo_len= 0; + else { + ooo_seq += d; + ooo_data += d; + ooo_len -= d; + } } - ack_immediate = 1; - } + if (ooo_len) { + uint32_t right_edge = flow->rx.rcv_adv_edge; + uint32_t hard_edge = flow->rx.rcv_base + flow->rx.rcv_wnd_max; + if (TCP_SEQ_GT(right_edge, hard_edge)) right_edge = hard_edge; + if (TCP_SEQ_GEQ(ooo_seq, right_edge)) ooo_len = 0; + else if (TCP_SEQ_GT(ooo_seq + ooo_len, right_edge)) ooo_len = right_edge - ooo_seq; + } - (void)tcp_calc_adv_wnd_field(flow, 1); + if (ooo_len) { + uint32_t start = ooo_seq; + uint32_t end = ooo_seq + ooo_len; + uint32_t merged_start = start; + uint32_t merged_end = end; + uint32_t old_bytes = 0; + uint8_t overlapping = 0; + bool covered = false; + + for (uint32_t i = 0; ooo_len && i < flow->rx.reass_count; i++) { + tcp_reass_seg_t *r = &flow->rx.reass[i]; + if (TCP_SEQ_LEQ(r->seq, start) && TCP_SEQ_GEQ(r->end, end)) { + flow->rx.sack_recent_left = r->seq; + flow->rx.sack_recent_right = r->end; + tcp_note_dsack(flow, start, end); + covered = true; + break; + } + + if (TCP_SEQ_LT(r->end, merged_start) || TCP_SEQ_GT(r->seq, merged_end)) continue; + if (flow->tx.sack_ok && flow->tx.dsack_enabled && !flow->rx.dsack_pending) { + uint32_t duplicate_left = TCP_SEQ_GT(start, r->seq) ? start : r->seq; + uint32_t duplicate_right = TCP_SEQ_LT(end, r->end) ? end : r->end; + tcp_note_dsack(flow, duplicate_left, duplicate_right); + } + + if (TCP_SEQ_LT(r->seq, merged_start)) merged_start = r->seq; + if (TCP_SEQ_GT(r->end, merged_end)) merged_end = r->end; + if (TCP_SEQ_GT(r->end, r->seq)) old_bytes += r->end - r->seq; + overlapping++; + } - if (!ack_immediate && data_len) ack_defer = 1; - need_ack = 1; - } else { - if (!(flow->state == TCP_FIN_WAIT_1 || flow->state == TCP_FIN_WAIT_2 || flow->state == TCP_CLOSING || flow->state == TCP_LAST_ACK || flow->state == TCP_TIME_WAIT) && data_len) tcp_reass_insert(flow, seg_seq, payload, data_len); + if (!covered && ooo_len) { + uint32_t merged_len = merged_end - merged_start; + uint32_t increase = merged_len > old_bytes ? merged_len - old_bytes : 0; + uint32_t remaining_nodes = overlapping < flow->rx.reass_count ? (uint32_t)(flow->rx.reass_count - overlapping) : 0; + + tcp_admit_result_t ooo_admit = tcp_admit_ooo(flow, increase, remaining_nodes); + if (ooo_admit != TCP_ADMIT_OK) { + if (ooo_admit == TCP_ADMIT_OOO_FLOW_BYTES) tcp_stats.ooo_drop_flow_bytes++; + else if (ooo_admit == TCP_ADMIT_OOO_FLOW_SEGS) tcp_stats.ooo_drop_flow_segs++; + else if (ooo_admit == TCP_ADMIT_OOO_GLOBAL_BYTES) tcp_stats.ooo_drop_global_bytes++; + else if (ooo_admit == TCP_ADMIT_OOO_GLOBAL_SEGS) tcp_stats.ooo_drop_global_segs++; + ooo_len = 0; + } + } + + if (!covered && ooo_len) { + uint8_t *rx = (uint8_t *)flow->rx.rcv_buf; + uint32_t cap = flow->rx.rcv_wnd_max; + uint32_t pos = start % cap; + uint32_t first = cap - pos; + bool copied = true; + + if (first > ooo_len) first = ooo_len; + if (first && !netpkt_copyout(pkt, ooo_data, rx + pos, first)) copied = false; + if (copied && ooo_len > first && !netpkt_copyout(pkt, ooo_data + first, rx, ooo_len - first)) copied = false; + + if (copied) { + for (uint32_t i = 0; i < flow->rx.reass_count;) { + tcp_reass_seg_t *r = &flow->rx.reass[i]; + if (TCP_SEQ_LT(r->end, merged_start) || TCP_SEQ_GT(r->seq, merged_end)) { + i++; + continue; + } + + tcp_reass_remove(flow,i); + } + + if (flow->rx.reass_count < TCP_REASS_MAX_SEGS) { + uint32_t pos_idx = flow->rx.reass_count; + while (pos_idx > 0 && TCP_SEQ_GT(flow->rx.reass[pos_idx - 1].seq, merged_start)) { + flow->rx.reass[pos_idx] = flow->rx.reass[pos_idx - 1]; + pos_idx--; + } + + flow->rx.reass[pos_idx].seq = merged_start; + flow->rx.reass[pos_idx].end = merged_end; + flow->rx.reass_count++; + flow->rx.rcv_ooo_used += merged_end - merged_start; + tcp_account_ooo_add(merged_end - merged_start, 1); + flow->rx.sack_recent_left = merged_start; + flow->rx.sack_recent_right = merged_end; + tcp_update_adv_wnd(flow, 1); + } + } + } + } + } if (fin_in){ - flow->fin_pending = 1; - flow->fin_seq = fin_seq; + flow->rx.fin_pending = 1; + flow->rx.fin_seq = fin_seq; } - need_ack = 1; - ack_immediate = 1; + need_ack = true; + ack_immediate = true; } } } if (need_ack){ - if (ack_immediate){ - tcp_send_ack_now(flow); - } else if (ack_defer){ - if (!flow->delayed_ack_pending){ - flow->delayed_ack_pending = 1; - flow->delayed_ack_timer_ms = 0; + if (ack_immediate) tcp_send_ack_now(flow); + else { + if (!flow->timer.delayed_ack_pending){ + flow->timer.delayed_ack_pending = 1; + flow->timer.delayed_ack_timer_ms = 0; tcp_daemon_kick(); - } else { - tcp_send_ack_now(flow); - } - } else { - if (!flow->delayed_ack_pending){ - flow->delayed_ack_pending = 1; - flow->delayed_ack_timer_ms = 0; - tcp_daemon_kick(); - } else { - tcp_send_ack_now(flow); - } - } - } -} - -void tcp_flow_on_app_read(tcp_data *flow_ctx, uint32_t bytes_read){ - if (!flow_ctx || bytes_read == 0) return; - - tcp_flow_t *flow = NULL; - for (int i = 0; i < MAX_TCP_FLOWS; ++i) { - tcp_flow_t *f = tcp_flows[i]; - if (!f) continue; - if (&f->ctx == flow_ctx) { - flow = f; - break; - } - } - if (!flow) return; - - if (bytes_read > flow->rcv_buf_used) bytes_read = flow->rcv_buf_used; - flow->rcv_buf_used -= bytes_read; - - port_manager_t *pm = NULL; - uint8_t ifx = 0; - - if (flow->local.ver == IP_VER4) { - l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(flow->l3_id); - if (v4 && v4->l2) { - pm = ifmgr_pm_v4(flow->l3_id); - ifx = v4->l2->ifindex; + } else tcp_send_ack_now(flow); } - } else if (flow->local.ver == IP_VER6) { - l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(flow->l3_id); - if (v6 && v6->l2) { - pm = ifmgr_pm_v6(flow->l3_id); - ifx = v6->l2->ifindex; - } - } - - if (pm) { - tcp_reass_drain_inseq(flow, pm, ifx, flow->local.ver, flow->remote.ip, flow->local.ip, flow->remote.port, flow->local_port); - } - - if (flow->state != TCP_STATE_CLOSED && flow->state != TCP_TIME_WAIT) { - (void)tcp_calc_adv_wnd_field(flow, 1); - tcp_send_ack_now(flow); } + tcp_flow_put(flow); + netpkt_unref(pkt); } diff --git a/kernel/networking/transport_layer/tcp/tcp_timer.c b/kernel/networking/transport_layer/tcp/tcp_timer.c index b3a92597..edaaeded 100644 --- a/kernel/networking/transport_layer/tcp/tcp_timer.c +++ b/kernel/networking/transport_layer/tcp/tcp_timer.c @@ -2,235 +2,200 @@ #include "kernel_processes/kprocess_loader.h" #include "exceptions/irq.h" -static volatile int tcp_daemon_running = 0; -//TODO make tcp_daemon_running atomic or use a lock, this may end in a double deamon process -void tcp_daemon_kick(void) { - if(!tcp_has_pending_timers()) return; +static volatile int tcp_daemon_running; +static int tcp_daemon_entry(int argc, char *argv[]); + +static bool tcp_timer_send_ack_segment(tcp_flow_t *flow, uint32_t seq, const uint8_t *payload, uint16_t payload_len) { + tcp_hdr_t hdr; + hdr.src_port = bswap16(flow->base.local.port); + hdr.dst_port = bswap16(flow->base.remote.port); + hdr.sequence = bswap32(seq); + hdr.ack = bswap32(flow->base.ctx.ack); + hdr.flags = (uint8_t)(1u << ACK_F); + tcp_update_adv_wnd(flow, 1); + hdr.window = flow->base.ctx.window; + hdr.urgent_ptr = 0; + return tcp_send_flow_segment(flow, &hdr, NULL, 0, payload, payload_len); +} - disable_interrupt(); +//TODO events +void tcp_daemon_kick(void) { + irq_flags_t irq_flags = irq_save_disable(); if(tcp_daemon_running){ - enable_interrupt(); + irq_restore(irq_flags); return; } tcp_daemon_running = 1; - enable_interrupt(); + irq_restore(irq_flags); - process_t *p = create_kernel_process("tcp_timer", tcp_daemon_entry, 0, 0); - if(!p){ - disable_interrupt(); + if (!create_kernel_process("tcp_timer", tcp_daemon_entry, 0, 0)) { + irq_flags = irq_save_disable(); tcp_daemon_running = 0; - enable_interrupt(); + irq_restore(irq_flags); } } -int tcp_has_pending_timers(void) { //TODO mhh this should be event driven to avoid MAX_TCP_FLOWS*TCP_MAX_TX_SEGS scans. - - - for (int i = 0; i < MAX_TCP_FLOWS; i++) { - tcp_flow_t *f = tcp_flows[i]; - if (!f) continue; - if (f->state == TCP_STATE_CLOSED) continue; - - if (f->state == TCP_TIME_WAIT) return 1; - if (f->state == TCP_FIN_WAIT_2) return 1; - if (f->delayed_ack_pending) return 1; - if (f->persist_active) return 1; - if (f->keepalive_on && f->state == TCP_ESTABLISHED && f->keepalive_ms) return 1; - - for (int j = 0; j < TCP_MAX_TX_SEGS; j++) { - tcp_tx_seg_t *s = &f->txq[j]; - if (!s->used) continue; - uint32_t end = s->seq + s->len + (s->syn ? 1u : 0u) + (s->fin ? 1u : 0u); - if (end > f->snd_una) return 1; +static int tcp_daemon_entry(int argc, char *argv[]) { + (void)argc; + (void)argv; + uint32_t last_tick_ms = (uint32_t)get_time(); + + while (true) { + uint32_t now_ms = (uint32_t)get_time(); + uint32_t elapsed_ms = now_ms - last_tick_ms; + last_tick_ms = now_ms; + + uint16_t active_slots[MAX_TCP_FLOWS]; + uint32_t active_generations[MAX_TCP_FLOWS]; + uint16_t active_count; + + irq_flags_t irq = irq_save_disable(); + active_count = tcp_active_count; + if (!active_count) { + tcp_daemon_running = 0; + irq_restore(irq); + return 0; } - } - - return 0; -} + for (uint16_t i = 0; i < active_count; ++i) { + uint16_t slot = tcp_active_flows[i]; + tcp_flow_t* flow = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + active_slots[i] = slot; + active_generations[i] = flow ? flow->base.generation : 0; + } + irq_restore(irq); + + for (uint16_t pos = 0; pos < active_count; ++pos) { + irq = irq_save_disable(); + uint16_t slot = active_slots[pos]; + tcp_flow_t *flow = slot < MAX_TCP_FLOWS ? tcp_flows[slot] : NULL; + if (!flow || flow->base.generation != active_generations[pos] || flow->base.retired || flow->base.state == TCP_STATE_CLOSED || !flow->base.refs || flow->base.refs == UINT16_MAX) { + irq_restore(irq); + continue; + } + flow->base.refs++; + irq_restore(irq); + bool retire_flow = false; -void tcp_tick_all(uint32_t elapsed_ms) { - for (int i = 0; i < MAX_TCP_FLOWS; i++) { - tcp_flow_t *f = tcp_flows[i]; - if (!f) continue; - if (f->state == TCP_STATE_CLOSED) continue; + if (flow->base.state == TCP_TIME_WAIT) { + flow->timer.time_wait_ms += elapsed_ms; + if (flow->timer.time_wait_ms >= TCP_2MSL_MS) retire_flow = true; + } - if (f->state == TCP_TIME_WAIT) { - f->time_wait_ms += elapsed_ms; - if (f->time_wait_ms >= TCP_2MSL_MS) { - tcp_free_flow(i); - continue; + if (!retire_flow && flow->base.state == TCP_FIN_WAIT_2) { + flow->timer.fin_wait2_ms += elapsed_ms; + if (flow->timer.fin_wait2_ms >= TCP_2MSL_MS) retire_flow = true; } - } - if (f->state == TCP_FIN_WAIT_2) { - f->fin_wait2_ms += elapsed_ms; - if (f->fin_wait2_ms >= TCP_2MSL_MS) { - tcp_free_flow(i); + if (retire_flow) { + tcp_free_flow(flow); + tcp_flow_put(flow); continue; } - } - if (f->delayed_ack_pending) { - f->delayed_ack_timer_ms += elapsed_ms; - if (f->delayed_ack_timer_ms >= TCP_DELAYED_ACK_MS) tcp_send_ack_now(f); - } - - if (f->keepalive_on && f->state == TCP_ESTABLISHED && f->keepalive_ms) { - f->keepalive_idle_ms += elapsed_ms; - if (f->keepalive_idle_ms >= f->keepalive_ms) { - tcp_hdr_t hdr; - hdr.src_port = bswap16(f->local_port); - hdr.dst_port = bswap16(f->remote.port); - uint32_t seq = f->snd_nxt; - if (seq) seq -= 1; - hdr.sequence = bswap32(seq); - hdr.ack = bswap32(f->ctx.ack); - hdr.flags = (uint8_t)(1u << ACK_F); - hdr.window = tcp_calc_adv_wnd_field(f, 1); - hdr.urgent_ptr = 0; - - if (f->local.ver == IP_VER4) { - ipv4_tx_opts_t tx; - tcp_build_tx_opts_from_local_v4(f->local.ip, &tx); - (void)tcp_send_segment(IP_VER4, f->local.ip, f->remote.ip, &hdr, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, f->ip_ttl, f->ip_dontfrag); - } else if (f->local.ver == IP_VER6) { - ipv6_tx_opts_t tx; - tcp_build_tx_opts_from_local_v6(f->local.ip, &tx); - (void)tcp_send_segment(IP_VER6, f->local.ip, f->remote.ip, &hdr, NULL, 0, NULL, 0, (const ip_tx_opts_t *)&tx, f->ip_ttl, f->ip_dontfrag); - } - f->keepalive_idle_ms = 0; + if (flow->timer.delayed_ack_pending) { + flow->timer.delayed_ack_timer_ms += elapsed_ms; + if (flow->timer.delayed_ack_timer_ms >= TCP_DELAYED_ACK_MS) tcp_send_ack_now(flow); } - } - if (f->snd_wnd == 0 && f->snd_nxt > f->snd_una) { - if (!f->persist_active) { - f->persist_active = 1; - f->persist_timer_ms = 0; - f->persist_probe_cnt = 0; - f->persist_timeout_ms = TCP_PERSIST_MIN_MS; - } else { - f->persist_timer_ms += elapsed_ms; - if (f->persist_timer_ms >= f->persist_timeout_ms) { - if (f->persist_probe_cnt >= TCP_MAX_PERSIST_PROBES) { - if (f->state == TCP_ESTABLISHED) { - f->ctx.flags = (uint8_t)((1u << FIN_F) | (1u << ACK_F)); - f->ctx.payload.ptr = 0; - f->ctx.payload.size = 0; - - tcp_flow_send(&f->ctx); - f->state = TCP_FIN_WAIT_1; - f->ctx.expected_ack = f->snd_nxt; - tcp_daemon_kick(); - } else { - tcp_free_flow(i); - } - continue; + if (flow->tx.snd_wnd > 0) { + tcp_tx_seg_t *best = tcp_find_first_unacked(flow); + if (best && best->persist) { + best->persist = 0; + if (!tcp_retransmit_seg(flow, best)) { + best->timer_ms = 0; + best->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; } - tcp_tx_seg_t *best = tcp_find_first_unacked(f); + } + } - tcp_hdr_t hdr; - hdr.src_port = bswap16(f->local_port); - hdr.dst_port = bswap16(f->remote.port); + if (flow->tx.nagle_len && flow->tx.snd_wnd > 0) { + if (flow->tx.snd_nxt == flow->tx.snd_una) { + if (!tcp_flush_nagle(flow, 1)) flow->tx.nagle_timer_ms = 0; + } else { + flow->tx.nagle_timer_ms += elapsed_ms; + if (flow->tx.nagle_timer_ms >= TCP_NAGLE_TIMEOUT_MS && !tcp_flush_nagle(flow, 1)) flow->tx.nagle_timer_ms = 0; + } + } - uint8_t payload[1]; - const uint8_t *pp = NULL; - uint16_t pl = 0; + if (flow->tx.fin_tx_pending) tcp_try_send_pending_fin(flow); - uint32_t probe_seq = f->snd_una; + if (!flow->tx.fin_tx_pending && !flow->tx.nagle_len && flow->timer.keepalive_on && flow->base.state == TCP_ESTABLISHED && flow->timer.keepalive_ms && flow->tx.snd_nxt == flow->tx.snd_una) { + flow->timer.keepalive_idle_ms += elapsed_ms; + if (flow->timer.keepalive_idle_ms >= flow->timer.keepalive_ms) { + uint32_t seq = flow->tx.snd_nxt; + if (seq) seq--; + bool sent = tcp_timer_send_ack_segment(flow, seq, NULL, 0); + flow->timer.keepalive_idle_ms = sent || flow->timer.keepalive_ms <= TCP_MIN_RTO ? 0 : flow->timer.keepalive_ms - TCP_MIN_RTO; + } + } - if (best && best->buf && best->len && probe_seq >= best->seq && probe_seq < best->seq + best->len) { - payload[0] = *((uint8_t *)best->buf + (probe_seq - best->seq)); - pp = payload; - pl = 1; - } + bool persist_state = flow->base.state == TCP_ESTABLISHED || flow->base.state == TCP_CLOSE_WAIT || flow->base.state == TCP_FIN_WAIT_1 || flow->base.state == TCP_FIN_WAIT_2 || flow->base.state == TCP_CLOSING || flow->base.state == TCP_LAST_ACK; + if (persist_state && flow->tx.snd_wnd == 0 && (TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una) || flow->tx.nagle_len || flow->tx.fin_tx_pending)) { + if (!flow->timer.persist_active) tcp_persist_start(flow); + else { + flow->timer.persist_timer_ms += elapsed_ms; + if (flow->timer.persist_timer_ms >= flow->timer.persist_timeout_ms) { + tcp_tx_seg_t *best = tcp_find_first_unacked(flow); + + uint8_t payload = 0; + const uint8_t *probe_payload = NULL; + uint16_t probe_len = 0; + + uint32_t probe_seq = flow->tx.snd_una; + if (!best && flow->tx.fin_tx_pending && flow->tx.snd_nxt == flow->tx.snd_una && flow->tx.snd_nxt) probe_seq = flow->tx.snd_nxt - 1; + + const uint8_t *best_payload = tcp_tx_seg_payload_ptr(best); + if (best && best_payload && best->len && TCP_SEQ_GEQ(probe_seq, best->seq) && TCP_SEQ_LT(probe_seq, best->seq + best->len)) { + payload = best_payload[probe_seq - best->seq]; + probe_payload = &payload; + probe_len = 1; + } - hdr.sequence = bswap32(probe_seq); - hdr.ack = bswap32(f->ctx.ack); - hdr.flags = (uint8_t)(1u << ACK_F); - hdr.window = tcp_calc_adv_wnd_field(f, 1); - hdr.urgent_ptr = 0; - - if (f->local.ver == IP_VER4) { - ipv4_tx_opts_t tx; - tcp_build_tx_opts_from_local_v4(f->local.ip, &tx); - (void)tcp_send_segment(IP_VER4, f->local.ip, f->remote.ip, &hdr, NULL, 0, pp, pl, (const ip_tx_opts_t *)&tx, f->ip_ttl, f->ip_dontfrag); - } else if (f->local.ver == IP_VER6) { - ipv6_tx_opts_t tx; - tcp_build_tx_opts_from_local_v6(f->local.ip, &tx); - (void)tcp_send_segment(IP_VER6, f->local.ip, f->remote.ip, &hdr, NULL, 0, pp, pl, (const ip_tx_opts_t *)&tx, f->ip_ttl, f->ip_dontfrag); + bool sent = false; + if (!best && flow->tx.nagle_len) sent = tcp_flush_nagle(flow, 1) != 0; + else sent = tcp_timer_send_ack_segment(flow, probe_seq, probe_payload, probe_len); + flow->timer.persist_timer_ms = 0; + + if (sent) { + if (flow->timer.persist_timeout_ms < TCP_PERSIST_MAX_MS) { + uint32_t next = flow->timer.persist_timeout_ms << 1; + if (next > TCP_PERSIST_MAX_MS) next = TCP_PERSIST_MAX_MS; + flow->timer.persist_timeout_ms = next; + } + } } - - if (f->persist_probe_cnt < UINT8_MAX) f->persist_probe_cnt++; - f->persist_timer_ms = 0; - - if (f->persist_timeout_ms < TCP_PERSIST_MAX_MS) { - uint32_t next = f->persist_timeout_ms << 1; - if (next > TCP_PERSIST_MAX_MS) next = TCP_PERSIST_MAX_MS; - f->persist_timeout_ms = next; + } + } else tcp_persist_reset(flow); + + if (TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una)) { + tcp_tx_seg_t *seg = tcp_find_first_unacked(flow); + if (seg && !(flow->tx.snd_wnd == 0 && !seg->syn)) { + seg->timer_ms += elapsed_ms; + if (seg->timer_ms >= seg->timeout_ms) { + if (seg->retransmit_cnt >= TCP_MAX_RETRANS) retire_flow = true; + else if (!tcp_retransmit_seg(flow, seg)) seg->timer_ms = 0; + else { + tcp_cc_on_timeout(flow); + uint32_t rto = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + if (rto < TCP_MIN_RTO) rto = TCP_MIN_RTO; + if (rto < TCP_MAX_RTO) { + uint32_t next = rto << 1; + if (next > TCP_MAX_RTO) next = TCP_MAX_RTO; + rto = next; + } + flow->tx.rto = rto; + seg->timeout_ms = rto; + } } } } - } else { - f->persist_active = 0; - f->persist_timer_ms = 0; - f->persist_timeout_ms = 0; - f->persist_probe_cnt = 0; - } - - for (int j = 0; j < TCP_MAX_TX_SEGS; j++) { - tcp_tx_seg_t *s = &f->txq[j]; - if (!s->used) continue; - - s->timer_ms += elapsed_ms; - if (s->timer_ms < s->timeout_ms) continue; - if (s->retransmit_cnt >= TCP_MAX_RETRANS) { - tcp_free_flow(i); - break; - } - - tcp_cc_on_timeout(f); - - tcp_send_from_seg(f, s); - - s->retransmit_cnt++; - s->timer_ms = 0; - - if (s->timeout_ms == 0) { - uint32_t rto = f->rto ? f->rto : TCP_INIT_RTO; - if (rto < TCP_MIN_RTO) rto = TCP_MIN_RTO; - s->timeout_ms = rto; - } else if (s->timeout_ms < TCP_MAX_RTO) { - uint32_t next = s->timeout_ms << 1; - if (next > TCP_MAX_RTO) next = TCP_MAX_RTO; - s->timeout_ms = next; - } + if (retire_flow) tcp_free_flow(flow); + tcp_flow_put(flow); } - } -} - - -int tcp_daemon_entry(int argc, char *argv[]) { - (void)argc; - (void)argv; - const uint32_t tick_ms = 25; - const uint32_t grace_ms = 10000; - uint32_t idle_ms = 0; - - while (1) { - if (tcp_has_pending_timers()) { - tcp_tick_all(tick_ms); - idle_ms = 0; - } else { - idle_ms += tick_ms; - if(idle_ms >= grace_ms) break; - } - msleep(tick_ms); + msleep(10); } - disable_interrupt(); - tcp_daemon_running = 0; - enable_interrupt(); return 0; } diff --git a/kernel/networking/transport_layer/tcp/tcp_tx.c b/kernel/networking/transport_layer/tcp/tcp_tx.c index 7f453581..a7616150 100644 --- a/kernel/networking/transport_layer/tcp/tcp_tx.c +++ b/kernel/networking/transport_layer/tcp/tcp_tx.c @@ -1,157 +1,445 @@ #include "tcp_internal.h" +#include "exceptions/irq.h" -uint16_t tcp_calc_adv_wnd_field(tcp_flow_t *flow, uint8_t apply_scale) { - if (!flow) return 0; +const uint8_t *tcp_tx_seg_payload_ptr(const tcp_tx_seg_t *seg) { + if (!seg || !seg->pkt || !seg->len) return NULL; + + uint32_t pkt_len = netpkt_len(seg->pkt); + if (seg->payload_off > pkt_len) return NULL; + if (seg->len > pkt_len - seg->payload_off) return NULL; - uint32_t quantum = 1; - if (apply_scale && flow->ws_ok && flow->ws_send) quantum = 1u << flow->ws_send; + return (const uint8_t*)(netpkt_data(seg->pkt) + (uintptr_t)seg->payload_off); +} + +void tcp_tx_seg_clear(tcp_flow_t *flow, tcp_tx_seg_t *seg) { + if (!seg) return; - uint32_t maxw = flow->rcv_wnd_max; - uint32_t used = flow->rcv_buf_used; - uint32_t freew = maxw > used ? maxw - used: 0; + uint32_t len = seg->len; + if (seg->rtt_sample && flow) flow->tx.rtt_sample_pending = 0; + if (seg->pkt) netpkt_unref(seg->pkt); + if (len && flow) tcp_account_tx_remove(flow, len); - uint32_t free_q = quantum == 1 ? freew : (freew & ~(quantum - 1)); + memset(seg, 0, sizeof(*seg)); +} - if (flow->rcv_adv_edge < flow->rcv_nxt) flow->rcv_adv_edge = flow->rcv_nxt; - uint32_t candidate_edge = flow->rcv_nxt + free_q; - if (candidate_edge > flow->rcv_adv_edge) flow->rcv_adv_edge = candidate_edge; +void tcp_update_adv_wnd(tcp_flow_t *flow, uint8_t apply_scale) { + if (!flow) return; - uint32_t adv = flow->rcv_adv_edge - flow->rcv_nxt; + uint32_t shift = 0; + if (apply_scale && flow->tx.ws_ok && flow->tx.ws_send) shift = flow->tx.ws_send; + if (TCP_SEQ_LT(flow->rx.rcv_adv_edge, flow->rx.rcv_nxt)) flow->rx.rcv_adv_edge = flow->rx.rcv_nxt; + uint32_t accept_edge = flow->rx.rcv_adv_edge; + + uint32_t hard_edge = flow->rx.rcv_nxt; + if (flow->rx.rcv_buf && flow->rx.rcv_wnd_max) { + hard_edge = flow->rx.rcv_base + flow->rx.rcv_wnd_max; + if (TCP_SEQ_LT(hard_edge, flow->rx.rcv_nxt)) hard_edge = flow->rx.rcv_nxt; + } else if ((flow->base.state == TCP_SYN_SENT || flow->base.state == TCP_SYN_RECEIVED) && flow->rx.rcv_wnd_max) hard_edge = flow->rx.rcv_nxt + flow->rx.rcv_wnd_max; + + if (TCP_SEQ_GT(hard_edge, accept_edge)) { + uint32_t adv = hard_edge - flow->rx.rcv_nxt; + + if (apply_scale && adv) { + uint32_t threshold = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; + uint32_t half = flow->rx.rcv_wnd_max >> 1; + uint32_t already = TCP_SEQ_GT(accept_edge, flow->rx.rcv_nxt) ? accept_edge - flow->rx.rcv_nxt : 0; + if (half && half < threshold) threshold = half; + if (!threshold) threshold = 1; + if (!already && adv < threshold) adv = 0; + } - uint32_t field = adv; - if (!apply_scale || !flow->ws_ok || flow->ws_send == 0) { - if (field > 65535u) field = 65535u; - adv = field; - } else { - field = adv >> flow->ws_send; - if (field > 65535u) field = 65535u; - adv = field << flow->ws_send; + if (adv) { + uint32_t field = adv; + uint32_t actual = adv; + + if (shift) { + field = adv >> shift; + if (field > 65535u) field = 65535u; + actual = field << shift; + } else if (field > 65535u) { + field = 65535u; + actual = field; + } + + uint32_t new_edge = flow->rx.rcv_nxt + actual; + if (actual && TCP_SEQ_GT(new_edge, accept_edge)) accept_edge = new_edge; + } } - flow->rcv_wnd = adv; - flow->rcv_adv_edge = flow->rcv_nxt + adv; - flow->ctx.window = (uint16_t)field; - return (uint16_t)field; + if (TCP_SEQ_LT(accept_edge, flow->rx.rcv_nxt)) accept_edge = flow->rx.rcv_nxt; + uint32_t accept_adv = accept_edge - flow->rx.rcv_nxt; + uint32_t field = accept_adv; + + if (shift) field >>= shift; + if (field > 65535) field = 65535; + + flow->rx.rcv_wnd = accept_adv; + flow->rx.rcv_adv_edge = accept_edge; + flow->base.ctx.window = (uint16_t)field; } -static void tcp_persist_arm(tcp_flow_t *flow) { - if (!flow) return; - flow->persist_active = 1; - flow->persist_timer_ms = 0; - if (flow->persist_timeout_ms == 0) flow->persist_timeout_ms = TCP_PERSIST_MIN_MS; - if (flow->persist_timeout_ms < TCP_PERSIST_MIN_MS) flow->persist_timeout_ms = TCP_PERSIST_MIN_MS; - if (flow->persist_timeout_ms > TCP_PERSIST_MAX_MS) flow->persist_timeout_ms = TCP_PERSIST_MAX_MS; - tcp_daemon_kick(); +static uint32_t tcp_nagle_threshold(tcp_flow_t *flow) { + uint32_t threshold = flow && flow->tx.mss? flow->tx.mss : TCP_NAGLE_FLUSH_THRESHOLD; + if (!threshold) threshold = 1; + return threshold; } -tcp_tx_seg_t *tcp_alloc_tx_seg(tcp_flow_t *flow){ - for (int i = 0; i < TCP_MAX_TX_SEGS; i++) { - if (!flow->txq[i].used) { - tcp_tx_seg_t *s = &flow->txq[i]; - s->used = 1; - s->syn = 0; - s->fin = 0; - s->rtt_sample = 0; - s->retransmit_cnt = 0; - s->seq = 0; - s->len = 0; - s->buf = 0; - s->timer_ms = 0; - s->timeout_ms = flow->rto ? flow->rto : TCP_INIT_RTO; +static uint64_t tcp_emit_data(tcp_flow_t *flow, const uint8_t *payload, uint64_t payload_len, uint8_t push_partial) { + if (!flow || (!payload && payload_len)) return 0; + if (flow->base.state != TCP_ESTABLISHED && flow->base.state != TCP_CLOSE_WAIT) return 0; + + uint64_t in_flight = TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una) ? flow->tx.snd_nxt - flow->tx.snd_una : 0; + if (flow->tx.data_tx_valid && !in_flight) { + uint32_t idle_ms = (uint32_t)get_time() - flow->tx.last_data_tx_ms; + uint32_t rto = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + if (idle_ms >= rto) { + uint32_t iw = tcp_initial_cwnd(flow->tx.mss); + if (flow->tx.cwnd > iw) flow->tx.cwnd = iw; + flow->tx.cwnd_acc = 0; + } + } + + uint32_t wnd = flow->tx.snd_wnd; + uint32_t cwnd = flow->tx.cwnd ? flow->tx.cwnd : (flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS); + uint32_t eff_wnd = wnd < cwnd ? wnd : cwnd; + + uint64_t can_send = 0; + if (in_flight < eff_wnd) can_send = eff_wnd - in_flight; + bool persist_only = !can_send && !wnd && !in_flight && payload_len; + if (persist_only) can_send = 1; + + uint64_t remaining = payload_len; + uint64_t sent_bytes = 0; + + while (remaining > 0 && can_send > 0) { + uint64_t sendable = remaining > can_send ? can_send : remaining; + if (flow->tx.mss && sendable > flow->tx.mss) sendable = flow->tx.mss; + if (sendable > UINT32_MAX) sendable = UINT32_MAX; + uint32_t seg_len = (uint32_t)sendable; + + uint32_t tx_limit = flow->tx.queued_limit ? flow->tx.queued_limit : TCP_TX_MAX_BYTES_PER_FLOW; + uint32_t flow_room = tx_limit > flow->tx.queued_bytes ? tx_limit - flow->tx.queued_bytes : 0; + uint32_t global_room = tcp_tx_global_bytes < TCP_TX_MAX_BYTES_GLOBAL ? TCP_TX_MAX_BYTES_GLOBAL - tcp_tx_global_bytes : 0; + if (!flow_room) { + tcp_stats.tx_block_flow_bytes++; + break; + } + if (!global_room) { + tcp_stats.tx_block_global_bytes++; + break; + } + if (seg_len > flow_room) seg_len = flow_room; + if (seg_len > global_room) seg_len = global_room; + if (!seg_len) break; + + tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow, TCP_TX_CONTROL_RESERVE_SEGS); + if (!seg) { + tcp_stats.tx_block_flow_segs++; + break; + } + + netpkt_t *payload_pkt = netpkt_alloc(seg_len, 0, 0); + uint8_t *payload_dst = payload_pkt ? (uint8_t*)netpkt_put(payload_pkt, seg_len) : NULL; + if (!payload_dst) { + if (payload_pkt) netpkt_unref(payload_pkt); + tcp_tx_seg_clear(flow, seg); + break; + } + + memcpy(payload_dst, payload + sent_bytes, seg_len); + + seg->seq = flow->tx.snd_nxt; + seg->len = seg_len; + seg->pkt = payload_pkt; + seg->payload_off = 0; + seg->syn = 0; + seg->fin = 0; + seg->psh = push_partial && seg_len == remaining; + seg->persist = persist_only; + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + seg->retransmit_cnt = 0; + seg->rtt_sample = 0; + + if (!persist_only && !flow->tx.rtt_sample_pending) { + seg->rtt_sample = 1; + flow->tx.rtt_sample_pending = 1; + } + tcp_account_tx_add(flow, seg_len); + flow->tx.snd_nxt += seg_len; + flow->base.ctx.sequence = flow->tx.snd_nxt; + + if (!tcp_send_from_seg(flow, seg)) { + flow->tx.snd_nxt -= seg_len; + flow->base.ctx.sequence = flow->tx.snd_nxt; + tcp_tx_seg_clear(flow, seg); + break; + } + + sent_bytes += seg_len; + remaining -= seg_len; + can_send -= seg_len; + if (persist_only) { + tcp_persist_start(flow); + flow->timer.persist_timer_ms = 0; tcp_daemon_kick(); - return s; + break; } } - return NULL; + + flow->base.ctx.sequence = flow->tx.snd_nxt; + return sent_bytes; } -void tcp_send_from_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg){ - if (flow) flow->keepalive_idle_ms = 0; - tcp_hdr_t hdr; - - hdr.src_port = bswap16(flow->local_port); - hdr.dst_port = bswap16(flow->remote.port); - hdr.sequence = bswap32(seg->seq); - hdr.ack = bswap32(flow->ctx.ack); - - uint8_t flags = 0; - if (!(flow->state == TCP_SYN_SENT && seg->syn && flow->ctx.ack == 0)) flags |= (uint8_t)(1u << ACK_F); - if (seg->syn) flags |= (uint8_t)(1u << SYN_F); - if (seg->fin) flags |= (uint8_t)(1u << FIN_F); - hdr.flags = flags; - - hdr.window = tcp_calc_adv_wnd_field(flow, seg->syn ? 0 : 1); - hdr.urgent_ptr = 0; - - if (flow->local.ver == IP_VER4) { - ipv4_tx_opts_t tx; - tcp_build_tx_opts_from_local_v4(flow->local.ip, &tx); - (void)tcp_send_segment(IP_VER4, flow->local.ip, flow->remote.ip, &hdr, NULL, 0, seg->buf ? (const uint8_t *)seg->buf : NULL, seg->len, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } else if (flow->local.ver == IP_VER6) { - ipv6_tx_opts_t tx; - tcp_build_tx_opts_from_local_v6(flow->local.ip, &tx); - (void)tcp_send_segment(IP_VER6, flow->local.ip, flow->remote.ip, &hdr, NULL, 0, seg->buf ? (const uint8_t *)seg->buf : NULL, seg->len, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); +static uint64_t tcp_nagle_append(tcp_flow_t *flow, const uint8_t *payload, uint64_t payload_len) { + if (!flow || (!payload && payload_len) || !payload_len) return 0; + if (flow->tx.nagle_flushing || flow->tx.nagle_appending) return 0; + + flow->tx.nagle_appending = 1; + + uint32_t cap = tcp_nagle_threshold(flow); + if (flow->tx.nagle_len >= cap) { + flow->tx.nagle_appending = 0; + return 0; } + if (!flow->tx.nagle_buf || flow->tx.nagle_cap < cap) { + uintptr_t nb = flow->tx.nagle_buf ? (uintptr_t)reallocate((void*)flow->tx.nagle_buf, cap) : (uintptr_t)zalloc(cap); + if (!nb) { + flow->tx.nagle_appending = 0; + return 0; + } + + flow->tx.nagle_buf = nb; + flow->tx.nagle_cap = cap; + } + + uint64_t n = payload_len; + uint32_t room = flow->tx.nagle_cap - flow->tx.nagle_len; + if (n > room)n = room; + + memcpy((void*)(flow->tx.nagle_buf + flow->tx.nagle_len), payload, n); + flow->tx.nagle_len += (uint32_t)n; + flow->tx.nagle_appending = 0; tcp_daemon_kick(); + return n; +} + +uint64_t tcp_flush_nagle(tcp_flow_t *flow, uint8_t force) { + if (!flow || !flow->tx.nagle_len || !flow->tx.nagle_buf) return 0; + if (flow->tx.nagle_flushing || flow->tx.nagle_appending) return 0; + + uint64_t in_flight = TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una) ? flow->tx.snd_nxt - flow->tx.snd_una : 0; + if (!force && in_flight && flow->tx.nagle_lentx.nagle_flushing = 1; + + uint32_t old_len = flow->tx.nagle_len; + uint64_t sent = tcp_emit_data(flow, (const uint8_t*)flow->tx.nagle_buf, old_len, flow->tx.nagle_psh); + if (!sent) { + flow->tx.nagle_flushing = 0; + return 0; + } + + if (sent >= old_len) { + flow->tx.nagle_len = 0; + flow->tx.nagle_timer_ms = 0; + flow->tx.nagle_psh = 0; + flow->tx.nagle_flushing = 0; + return sent; + } + + memmove((void*)flow->tx.nagle_buf, (const void*)(flow->tx.nagle_buf + sent), old_len - sent); + flow->tx.nagle_len = old_len - (uint32_t)sent; + flow->tx.nagle_timer_ms = 0; + flow->tx.nagle_flushing = 0; + return sent; +} + +tcp_tx_seg_t *tcp_alloc_tx_seg(tcp_flow_t *flow, uint32_t reserve_slots){ + if (!flow || reserve_slots >= TCP_MAX_TX_SEGS) return NULL; + + tcp_tx_seg_t *seg = NULL; + uint32_t available = 0; + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) { + if (flow->tx.txq[i].used) continue; + if (!seg) seg = &flow->tx.txq[i]; + if (++available > reserve_slots) break; + } + + if (!seg || available <= reserve_slots) return NULL; + memset(seg, 0, sizeof(*seg)); + seg->used = 1; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + return seg; +} + +bool tcp_send_flow_segment(tcp_flow_t *flow, tcp_hdr_t *hdr, const uint8_t *opts, uint8_t opts_len, const uint8_t *payload, uint16_t payload_len) { + if (!flow || !hdr || !flow->base.l3_id) return false; + if (flow->base.local.ver != IP_VER4 && flow->base.local.ver != IP_VER6) return false; + if (flow->base.remote.ver != flow->base.local.ver) return false; + + ip_tx_opts_t tx; + tx.scope = IP_TX_BOUND_L3; + tx.target.l3_id = flow->base.l3_id; + return tcp_send_segment(flow->base.local.ver, flow->base.local.ip, flow->base.remote.ip, hdr, opts, opts_len, payload, payload_len, &tx, flow->ip.ttl, flow->ip.dontfrag, flow->ip.dontroute); +} + +bool tcp_send_from_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg){ + if (!flow || !seg) return false; + if (flow->base.retired || flow->base.state == TCP_STATE_CLOSED) return false; + + irq_flags_t irq = irq_save_disable(); + tcp_tx_seg_t tx = *seg; + if (tx.pkt) netpkt_ref(tx.pkt); + irq_restore(irq); + + uint32_t max_payload = flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS; + if (!max_payload) max_payload = 1; + if (tx.syn && tx.len > max_payload) max_payload = tx.len; + + bool arm_timer = !tx.timer_ms && !tx.retransmit_cnt; + bool sent_any = false; + bool sample = tx.rtt_sample && !tx.retransmit_cnt && !tx.persist; + uint32_t old_rtt_start = flow->tx.rtt_start_ms; + uint32_t off = 0; + + do{ + uint32_t chunk = tx.len - off; + if (chunk > max_payload) chunk = max_payload; + bool final_chunk = off + chunk == tx.len; + + tcp_hdr_t hdr; + + hdr.src_port = bswap16(flow->base.local.port); + hdr.dst_port = bswap16(flow->base.remote.port); + hdr.sequence = bswap32(tx.seq + off); + hdr.ack = bswap32(flow->base.ctx.ack); + + uint8_t flags = 0; + if (!(flow->base.state == TCP_SYN_SENT && tx.syn && flow->base.ctx.ack == 0)) flags |= (uint8_t)(1u << ACK_F); + if (tx.syn && off == 0) flags |= (uint8_t)(1u << SYN_F); + if (tx.fin && final_chunk) flags |= (uint8_t)(1u << FIN_F); + if (tx.psh && chunk && final_chunk) flags |= (uint8_t)(1u << PSH_F); + hdr.flags = flags; + + tcp_update_adv_wnd(flow, tx.syn && off == 0 ? 0 : 1); + hdr.window = flow->base.ctx.window; + hdr.urgent_ptr = 0; + + const uint8_t* payload = NULL; + if (chunk) { + const uint8_t* base = tcp_tx_seg_payload_ptr(&tx); + if (!base) break; + payload = base + off; + } + + const uint8_t* opts = tx.opts_len ? tx.opts : NULL; + if (sample && !sent_any) flow->tx.rtt_start_ms = (uint32_t)get_time(); + if (!tcp_send_flow_segment(flow, &hdr, opts, tx.opts_len, payload, (uint16_t)chunk)) break; + + sent_any = true; + off += chunk; + } while (off < tx.len); + + if (tx.pkt) netpkt_unref(tx.pkt); + if (!sent_any) { + if (sample) flow->tx.rtt_start_ms = old_rtt_start; + return false; + } + + flow->timer.keepalive_idle_ms = 0; + if (tx.len) { + flow->tx.data_tx_valid = 1; + flow->tx.last_data_tx_ms = (uint32_t)get_time(); + } + if (arm_timer) tcp_daemon_kick(); + return true; +} + +bool tcp_retransmit_seg(tcp_flow_t *flow, tcp_tx_seg_t *seg) { + if (!flow || !seg || !seg->used) return false; + for (uint32_t i = 0; i < TCP_MAX_TX_SEGS; i++) flow->tx.txq[i].rtt_sample = 0; + flow->tx.rtt_sample_pending = 0; + if (!tcp_send_from_seg(flow, seg)) return false; + + if (seg->retransmit_cnt < UINT8_MAX) seg->retransmit_cnt++; + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + return true; } void tcp_send_ack_now(tcp_flow_t *flow){ if (!flow) return; tcp_hdr_t ackhdr; - ackhdr.src_port = bswap16(flow->local_port); - ackhdr.dst_port = bswap16(flow->remote.port); - ackhdr.sequence = bswap32(flow->ctx.sequence); - ackhdr.ack = bswap32(flow->ctx.ack); + ackhdr.src_port = bswap16(flow->base.local.port); + ackhdr.dst_port = bswap16(flow->base.remote.port); + ackhdr.sequence = bswap32(flow->base.ctx.sequence); + ackhdr.ack = bswap32(flow->base.ctx.ack); ackhdr.flags = (uint8_t)(1u << ACK_F); - ackhdr.window = tcp_calc_adv_wnd_field(flow, 1); + tcp_update_adv_wnd(flow, 1); + ackhdr.window = flow->base.ctx.window; ackhdr.urgent_ptr = 0; uint8_t opts[64]; uint8_t opts_len = 0; - opts_len = 0; + if (flow->tx.sack_ok && (flow->rx.reass_count > 0 || (flow->tx.dsack_enabled && flow->rx.dsack_pending))) { + tcp_sack_block_t blocks[TCP_SACK_MAX_BLOCKS]; + uint32_t n = 0; + + if (flow->tx.dsack_enabled && flow->rx.dsack_pending && TCP_SEQ_GT(flow->rx.dsack_right, flow->rx.dsack_left)) { + blocks[n].left = flow->rx.dsack_left; + blocks[n].right = flow->rx.dsack_right; + n++; + } + + if (n < TCP_SACK_MAX_BLOCKS && TCP_SEQ_GT(flow->rx.sack_recent_right, flow->rx.sack_recent_left)) { + for (uint32_t i = 0; i < flow->rx.reass_count; i++) { + if (TCP_SEQ_GT(flow->rx.reass[i].seq, flow->rx.sack_recent_left)) continue; + if (TCP_SEQ_LT(flow->rx.reass[i].end, flow->rx.sack_recent_right)) continue; - if (flow->sack_ok && flow->reass_count > 0) { - uint32_t n = flow->reass_count; - if (n > 4) n = 4; + blocks[n].left = flow->rx.reass[i].seq; + blocks[n].right = flow->rx.reass[i].end; + n++; + break; + } + } + + for (uint32_t i = 0; i < flow->rx.reass_count && n < TCP_SACK_MAX_BLOCKS; i++) { + uint32_t left = flow->rx.reass[i].seq; + uint32_t right = flow->rx.reass[i].end; + bool duplicate = false; + + for (uint32_t j = 0; j < n; j++) { + if (blocks[j].left == left && blocks[j].right == right) { + duplicate = true; + break; + } + } + + if (duplicate) continue; + blocks[n].left = left; + blocks[n].right = right; + n++; + } uint32_t need = 2 + 8 * n; uint32_t pad = (4 - (need & 3)) & 3; - if (need + pad <= sizeof(opts)) { + if (n && need + pad <= sizeof(opts)) { opts[0] = 5; opts[1] = (uint8_t)need; uint32_t o = 2; - uint32_t idx[4]; - for (uint32_t i = 0; i < n; i++) idx[i] = i; - - for (uint32_t i = 0; i + 1 < n; i++) { - for (uint32_t j = i + 1; j < n; j++) { - if ((int32_t)(flow->reass[idx[j]].seq > flow->reass[idx[i]].seq)) { - uint32_t t = idx[i]; - idx[i] = idx[j]; - idx[j] = t; - } - } - } - for (uint32_t i = 0; i < n; i++) { - const tcp_reass_seg_t *s = &flow->reass[idx[i]]; - uint32_t left = s->seq; - uint32_t right = s->end; - - opts[o + 0] = (uint8_t)(left >> 24); - opts[o + 1] = (uint8_t)(left >> 16); - opts[o + 2] = (uint8_t)(left >> 8); - opts[o + 3] = (uint8_t)(left); - opts[o + 4] = (uint8_t)(right >> 24); - opts[o + 5] = (uint8_t)(right >> 16); - opts[o + 6] = (uint8_t)(right >> 8); - opts[o + 7] = (uint8_t)(right); + uint32_t left = blocks[i].left; + uint32_t right = blocks[i].right; + + wr_be32(&opts[o], left); + wr_be32(&opts[o + 4], right); o += 8; } @@ -161,155 +449,227 @@ void tcp_send_ack_now(tcp_flow_t *flow){ } } - if (flow->local.ver == IP_VER4) { - ipv4_tx_opts_t tx; - tcp_build_tx_opts_from_local_v4(flow->local.ip, &tx); - (void)tcp_send_segment(IP_VER4, flow->local.ip, flow->remote.ip, &ackhdr, opts_len ? opts : NULL, opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); - } else if (flow->local.ver == IP_VER6) { - ipv6_tx_opts_t tx; - tcp_build_tx_opts_from_local_v6(flow->local.ip, &tx); - (void)tcp_send_segment(IP_VER6, flow->local.ip, flow->remote.ip, &ackhdr, opts_len ? opts : NULL, opts_len, NULL, 0, (const ip_tx_opts_t *)&tx, flow->ip_ttl, flow->ip_dontfrag); + if (!tcp_send_flow_segment(flow, &ackhdr, opts_len ? opts : NULL, opts_len, NULL, 0)) { + flow->timer.delayed_ack_pending = 1; + flow->timer.delayed_ack_timer_ms = 0; + tcp_daemon_kick(); + return; } - flow->delayed_ack_pending = 0; - flow->delayed_ack_timer_ms = 0; - tcp_daemon_kick(); + flow->rx.dsack_pending = 0; + flow->rx.dsack_left = 0; + flow->rx.dsack_right = 0; + flow->timer.delayed_ack_pending = 0; + flow->timer.delayed_ack_timer_ms = 0; } -tcp_result_t tcp_flow_send(tcp_data *flow_ctx){ - if (!flow_ctx) return TCP_INVALID; +void tcp_try_send_pending_fin(tcp_flow_t *flow) { + if (!flow || !flow->tx.fin_tx_pending) return; - tcp_flow_t *flow = NULL; - for (int i = 0; i < MAX_TCP_FLOWS; i++) { - if (!tcp_flows[i]) continue; - if (&tcp_flows[i]->ctx == flow_ctx) { - flow = tcp_flows[i]; + tcp_state_t next_state; + switch (flow->base.state) { + case TCP_ESTABLISHED: + next_state = TCP_FIN_WAIT_1; break; - } - } - if (!flow) return TCP_INVALID; - - uint8_t flags = flow_ctx->flags; - uint8_t *payload_ptr = (uint8_t *)flow_ctx->payload.ptr; - uint64_t payload_len = flow_ctx->payload.size; - flow_ctx->payload.size = 0; - - if (flow->state != TCP_ESTABLISHED && !(flags & (1u << FIN_F))) { - if (!(flow->state == TCP_CLOSE_WAIT && (flags & (1u << FIN_F)))) return TCP_INVALID; + case TCP_CLOSE_WAIT: + next_state = TCP_LAST_ACK; + break; + default: + return; } - if (flow->snd_wnd == 0 && !(flags & (1u << FIN_F))) { - tcp_persist_arm(flow); - return TCP_WOULDBLOCK; + if (flow->tx.nagle_len && flow->tx.snd_wnd == 0) return; + if (flow->tx.nagle_len) { + uint64_t flushed = tcp_flush_nagle(flow, 1); + if (!flushed || flow->tx.nagle_len) return; } - uint64_t in_flight = flow->snd_nxt - flow->snd_una; - uint32_t wnd = flow->snd_wnd; - uint32_t cwnd = flow->cwnd ? flow->cwnd : (flow->mss ? flow->mss : TCP_DEFAULT_MSS); + uint64_t in_flight = TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una) ? flow->tx.snd_nxt - flow->tx.snd_una : 0; + uint32_t wnd = flow->tx.snd_wnd; + uint32_t cwnd = flow->tx.cwnd ? flow->tx.cwnd : (flow->tx.mss ? flow->tx.mss : TCP_DEFAULT_MSS); uint32_t eff_wnd = wnd < cwnd ? wnd : cwnd; - if (eff_wnd == 0) eff_wnd = 1; - if (in_flight >= eff_wnd && !(flags & (1u << FIN_F))) return TCP_WOULDBLOCK; + if (eff_wnd == 0 || in_flight >= eff_wnd) return; + tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow, 0); + if (!seg) return; + + seg->seq = flow->tx.snd_nxt; + seg->len = 0; + seg->pkt = NULL; + seg->payload_off = 0; + seg->syn = 0; + seg->fin = 1; + seg->psh = 0; + seg->persist = 0; + seg->timer_ms = 0; + seg->timeout_ms = flow->tx.rto ? flow->tx.rto : TCP_INIT_RTO; + seg->retransmit_cnt = 0; + seg->rtt_sample = 0; + if (!flow->tx.rtt_sample_pending) { + seg->rtt_sample = 1; + flow->tx.rtt_sample_pending = 1; + } - uint64_t can_send = eff_wnd - in_flight; - if (can_send == 0 && !(flags & (1u << FIN_F))) return TCP_WOULDBLOCK; + tcp_state_t old_state = flow->base.state; + flow->tx.snd_nxt += 1; + flow->base.ctx.expected_ack = flow->tx.snd_nxt; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->tx.fin_tx_pending = 0; + + flow->base.state = next_state; + + if (!tcp_send_from_seg(flow, seg)) { + flow->tx.snd_nxt -= 1; + flow->base.ctx.expected_ack = flow->tx.snd_nxt; + flow->base.ctx.sequence = flow->tx.snd_nxt; + flow->base.state = old_state; + flow->tx.fin_tx_pending = 1; + tcp_tx_seg_clear(flow, seg); + return; + } +} - uint64_t remaining = payload_len; - uint64_t sent_bytes = 0; - int first_segment = 1; +tcp_result_t tcp_flow_flush(tcp_data *flow_ctx){ + if (!flow_ctx) return TCP_INVALID; - while (remaining > 0 && can_send > 0) { - uint64_t seg_len = (uint64_t)(remaining > can_send ? can_send : remaining); - if (flow->mss && seg_len > flow->mss) seg_len = (uint64_t)flow->mss; + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return TCP_INVALID; - tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow); - if (!seg) break; + tcp_result_t rc = TCP_OK; + if (flow->base.state != TCP_ESTABLISHED && flow->base.state != TCP_CLOSE_WAIT && flow->base.state != TCP_FIN_WAIT_1 && flow->base.state != TCP_FIN_WAIT_2) rc = TCP_INVALID; + + while (rc == TCP_OK && flow->tx.nagle_len && flow->tx.snd_wnd > 0) { + uint32_t before = flow->tx.nagle_len; + uint64_t sent = tcp_flush_nagle(flow, 1); + if (!sent || flow->tx.nagle_len == before) break; + } - uintptr_t buf = 0; - if (seg_len) { - buf = (uintptr_t)malloc(seg_len); - if (!buf) { seg->used = 0; break; } - memcpy((void *)buf, payload_ptr + sent_bytes, seg_len); + if (rc == TCP_OK) { + if (flow->tx.fin_tx_pending) { + tcp_try_send_pending_fin(flow); + if (flow->tx.fin_tx_pending) tcp_daemon_kick(); } + if (flow->tx.nagle_len) { + tcp_daemon_kick(); + rc = TCP_WOULDBLOCK; + } + } - seg->seq = flow->snd_nxt; - seg->len = seg_len; - seg->buf = buf; - seg->syn = 0; - seg->fin = 0; - seg->timer_ms = 0; - seg->timeout_ms = flow->rto ? flow->rto : TCP_INIT_RTO; - seg->retransmit_cnt = 0; - seg->rtt_sample = 0; - if (!flow->rtt_valid && first_segment) seg->rtt_sample = 1; + tcp_flow_put(flow); + return rc; +} - tcp_send_from_seg(flow, seg); +tcp_result_t tcp_flow_send(tcp_data *flow_ctx){ + if (!flow_ctx) return TCP_INVALID; - flow->snd_nxt += seg_len; - sent_bytes += seg_len; - remaining -= seg_len; - can_send -= seg_len; - first_segment = 0; - } + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); + if (!flow) return TCP_INVALID; - if ((flags & (1u << FIN_F)) && remaining == 0) { - tcp_tx_seg_t *seg = tcp_alloc_tx_seg(flow); - if (!seg) return sent_bytes ? TCP_OK : TCP_WOULDBLOCK; + uint8_t flags = flow_ctx->flags; + uint8_t *payload_ptr = (uint8_t *)flow_ctx->payload.ptr; + uint64_t payload_len = flow_ctx->payload.size; + flow_ctx->payload.size = 0; + if (payload_len && !payload_ptr) { + tcp_flow_put(flow); + return TCP_INVALID; + } + bool want_fin = (flags & (1u << FIN_F)) != 0; + bool want_push = (flags & (1u << PSH_F)) != 0; + bool fin_queued = false; + uint64_t accepted = 0; + tcp_result_t rc = TCP_OK; + bool zero_window_hold = false; + + if (flow->base.state != TCP_ESTABLISHED && flow->base.state != TCP_CLOSE_WAIT) rc = TCP_INVALID; + + if (rc == TCP_OK && payload_len && flow->tx.snd_wnd == 0 && flow->tx.snd_nxt == flow->tx.snd_una) { + zero_window_hold = true; + if (!flow->tx.nagle_len) { + uint64_t n = tcp_nagle_append(flow, payload_ptr, 1); + if (n && want_push && n == payload_len) flow->tx.nagle_psh = 1; + accepted += n; + payload_ptr += n; + payload_len -= n; + if (n) { + tcp_persist_start(flow); + tcp_daemon_kick(); + } + } + } - seg->seq = flow->snd_nxt; - seg->len = 0; - seg->buf = 0; - seg->syn = 0; - seg->fin = 1; - seg->timer_ms = 0; - seg->timeout_ms = flow->rto ? flow->rto : TCP_INIT_RTO; - seg->retransmit_cnt = 0; - seg->rtt_sample = 0; + if (rc == TCP_OK && !zero_window_hold && !flow->tx.nodelay && payload_len && flow->tx.nagle_len) { + uint64_t n = tcp_nagle_append(flow, payload_ptr, payload_len); + if (n && want_push && n == payload_len) flow->tx.nagle_psh = 1; + accepted += n; + payload_ptr += n; + payload_len -= n; + if (flow->tx.nagle_len >= tcp_nagle_threshold(flow) && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + } - tcp_send_from_seg(flow, seg); + uint64_t in_flight = TCP_SEQ_GT(flow->tx.snd_nxt, flow->tx.snd_una) ? flow->tx.snd_nxt - flow->tx.snd_una : 0; + if (rc == TCP_OK && !zero_window_hold && !flow->tx.nodelay && payload_len && payload_len < tcp_nagle_threshold(flow) && in_flight != 0) { + uint64_t n = tcp_nagle_append(flow, payload_ptr, payload_len); + if (n && want_push && n == payload_len) flow->tx.nagle_psh = 1; + accepted += n; + payload_ptr += n; + payload_len -= n; + if (flow->tx.nagle_len >= tcp_nagle_threshold(flow) && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + } - flow->snd_nxt += 1; - flow->ctx.expected_ack = flow->snd_nxt; + if (rc == TCP_OK && !zero_window_hold && payload_len) { + if (flow->tx.nagle_len && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + if (!flow->tx.nagle_len) { + uint64_t n = tcp_emit_data(flow, payload_ptr, payload_len, want_push ? 1 : 0); + accepted += n; + payload_len -= n; + } } - flow_ctx->sequence = flow->snd_nxt; - flow->ctx.sequence = flow->snd_nxt; + if (rc == TCP_OK && want_fin && payload_len == 0) { + flow->tx.fin_tx_pending = 1; + if (flow->tx.nagle_len) flow->tx.nagle_psh = 1; + fin_queued = true; + if (flow->tx.nagle_len && flow->tx.snd_wnd > 0 && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + tcp_try_send_pending_fin(flow); + if (flow->tx.fin_tx_pending) tcp_daemon_kick(); + } - tcp_daemon_kick(); + flow_ctx->sequence = flow->tx.snd_nxt; + flow->base.ctx.sequence = flow->tx.snd_nxt; + + flow_ctx->payload.size = accepted; + if (rc == TCP_OK) { + if (!accepted && fin_queued && flow->tx.fin_tx_pending) rc = TCP_WOULDBLOCK; + else if (accepted || fin_queued) rc = TCP_OK; + else { + if (flow->tx.snd_wnd == 0) { + tcp_persist_start(flow); + tcp_daemon_kick(); + } + rc = TCP_WOULDBLOCK; + } + } - flow_ctx->payload.size = sent_bytes; - return sent_bytes || (flags & (1u << FIN_F)) ? TCP_OK : TCP_WOULDBLOCK; + tcp_flow_put(flow); + return rc; } tcp_result_t tcp_flow_close(tcp_data *flow_ctx){ if (!flow_ctx) return TCP_INVALID; - tcp_flow_t *flow = NULL; - for (int i = 0; i < MAX_TCP_FLOWS; i++) { - if (!tcp_flows[i]) continue; - if (&tcp_flows[i]->ctx == flow_ctx) { - flow = tcp_flows[i]; - break; - } - } + tcp_flow_t *flow = tcp_flow_from_ctx(flow_ctx); if (!flow) return TCP_INVALID; - if (flow->state == TCP_ESTABLISHED || flow->state == TCP_CLOSE_WAIT) { - flow_ctx->sequence = flow->snd_nxt; - flow_ctx->ack = flow->ctx.ack; - flow_ctx->window = tcp_calc_adv_wnd_field(flow, 1); - flow_ctx->payload.ptr = 0; - flow_ctx->payload.size = 0; - flow_ctx->flags = (uint8_t)((1u << FIN_F) | (1u << ACK_F)); - - tcp_result_t res = tcp_flow_send(flow_ctx); - if (res == TCP_OK || res == TCP_WOULDBLOCK) { - if (flow->state == TCP_ESTABLISHED) flow->state = TCP_FIN_WAIT_1; - else flow->state = TCP_LAST_ACK; - } - tcp_daemon_kick(); - return res; + tcp_result_t rc = TCP_INVALID; + if (flow->base.state == TCP_ESTABLISHED || flow->base.state == TCP_CLOSE_WAIT) { + flow->tx.fin_tx_pending = 1; + if (flow->tx.nagle_len) flow->tx.nagle_psh = 1; + if (flow->tx.nagle_len && flow->tx.snd_wnd > 0 && !tcp_flush_nagle(flow, 1)) tcp_daemon_kick(); + tcp_try_send_pending_fin(flow); + if (flow->tx.fin_tx_pending) tcp_daemon_kick(); + rc = TCP_OK; } - return TCP_INVALID; + tcp_flow_put(flow); + return rc; } \ No newline at end of file diff --git a/kernel/networking/transport_layer/tcp/tcp_utils.c b/kernel/networking/transport_layer/tcp/tcp_utils.c index 8ce48329..74c3313e 100644 --- a/kernel/networking/transport_layer/tcp/tcp_utils.c +++ b/kernel/networking/transport_layer/tcp/tcp_utils.c @@ -1,58 +1,84 @@ #include "tcp_utils.h" +#include "std/memory.h" +#include "tcp_internal.h" +#include "networking/interface_manager.h" +#include "networking/internet_layer/pmtu.h" + +#define TCP_INIT_CWND_SEGS 10u + +uint32_t tcp_initial_cwnd(uint32_t mss) { + if (!mss) mss = TCP_DEFAULT_MSS; + uint32_t ten_mss = mss * TCP_INIT_CWND_SEGS; + uint32_t lower = 2 * mss; + if (lower < 14600u) lower = 14600u; + return ten_mss < lower ? ten_mss : lower; +} + +void tcp_update_mss(tcp_flow_t *flow) { + if (!flow) return; + uint32_t local_mss = flow->tx.path_mss ? flow->tx.path_mss : TCP_DEFAULT_MSS; + if (local_mss > TCP_MAX_MSS) local_mss = TCP_MAX_MSS; + if (flow->tx.configured_mss && flow->tx.configured_mss < local_mss) local_mss = flow->tx.configured_mss; + flow->tx.advertised_mss = local_mss; -uint32_t tcp_calc_mss_for_l3(uint8_t l3_id, ip_version_t ver, const void *remote_ip){ - uint32_t mtu = 1500; - l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); - if (v6) mtu =v6->mtu ? v6->mtu : 1500; + uint32_t mss = local_mss; + if (flow->tx.peer_mss && flow->tx.peer_mss < mss) mss = flow->tx.peer_mss; + flow->tx.mss = mss; +} - l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); - if (v4) mtu = v4->runtime_opts_v4.mtu ? v4->runtime_opts_v4.mtu : 1500; +uint32_t tcp_calc_mss_for_l3(l3_id_t l3_id, ip_version_t ver, const void *remote_ip) { + uint32_t mtu = 0; + uint32_t epoch = 0; + if (ver == IP_VER4) { + l3_ipv4_interface_t* v4 = l3_ipv4_find_by_id(l3_id); + if (v4) { + mtu = l3_ipv4_effective_mtu(v4); + epoch = v4->epoch; + } + } else if (ver == IP_VER6) { + l3_ipv6_interface_t* v6 = l3_ipv6_find_by_id(l3_id); + if (v6) { + mtu = l3_ipv6_effective_mtu(v6); + epoch = v6->epoch; + } + } else return 1; - if (ver == IP_VER6 && remote_ip){ - uint16_t pmtu =ipv6_pmtu_get((const uint8_t*)remote_ip); - if (pmtu && pmtu < mtu) mtu = pmtu; + if (remote_ip && epoch){ + uint16_t path_mtu = pmtu_get(l3_id, epoch, ver, remote_ip); + if (path_mtu && path_mtu < mtu) mtu = path_mtu; } uint32_t ih = (ver == IP_VER6) ? 40u : 20u; uint32_t th = 20u; - if (mtu <= ih + th) return 256; - uint32_t mss = mtu - ih - th; - if (mss < 256u) mss = 256u; - return mss; + if (mtu <= ih + th) return 1; + return mtu - ih - th; } -bool tcp_build_tx_opts_from_local_v4(const void *src_ip_addr, ipv4_tx_opts_t *out){ - if (!out) return false; - l3_ipv4_interface_t *v4 = l3_ipv4_find_by_ip(*(const uint32_t *)src_ip_addr); - if (v4) { - out->scope = IP_TX_BOUND_L3; - out->index = v4->l3_id; - } else { - out->scope = IP_TX_AUTO; - out->index = 0; +void tcp_pmtu_update(l3_id_t l3_id, ip_version_t ver, const void* local_ip, const void* remote_ip, uint16_t local_port, uint16_t remote_port, uint16_t mtu) { + if (!l3_id || !local_ip || !remote_ip || !local_port || !remote_port || !mtu) return; + + tcp_flow_t* flow = tcp_flow_acquire_match(local_port, ver, local_ip, remote_ip, remote_port); + if (!flow) return; + uint32_t l3_generation = 0; + if (ver == IP_VER4) { + l3_ipv4_interface_t *v4 = l3_ipv4_find_by_id(l3_id); + if (v4) l3_generation = v4->generation; + } else if (ver == IP_VER6) { + l3_ipv6_interface_t *v6 = l3_ipv6_find_by_id(l3_id); + if (v6) l3_generation = v6->generation; + } + if (flow->base.l3_id != l3_id || !l3_generation || flow->base.l3_generation != l3_generation) { + tcp_flow_put(flow); + return; } - return true; -} - -bool tcp_build_tx_opts_from_l3(uint8_t l3_id, ipv4_tx_opts_t *out){ - if (!out) return false; - out->scope = IP_TX_BOUND_L3; - out->index = l3_id; - return true; -} -bool tcp_build_tx_opts_from_local_v6(const void *src_ip_addr, ipv6_tx_opts_t *out){ - if (!out) return false; - const uint8_t *sip = (const uint8_t *)src_ip_addr; - l3_ipv6_interface_t *v6 = l3_ipv6_find_by_ip(sip); - if (v6 && v6->l2) { - out->scope = IP_TX_BOUND_L3; - out->index = v6->l3_id; - } else { - out->scope = IP_TX_AUTO; - out->index = 0; + uint32_t headers = ver == IP_VER6 ? 60 : 40; + uint32_t path_mss = mtu > headers ? (uint32_t)mtu - headers : 1; + if (!flow->tx.path_mss || path_mss < flow->tx.path_mss) { + flow->tx.path_mss = path_mss; + tcp_update_mss(flow); } - return true; + tcp_flow_put(flow); } void tcp_parse_options(const uint8_t *opts, uint32_t len, tcp_parsed_opts_t *out) { @@ -63,6 +89,8 @@ void tcp_parse_options(const uint8_t *opts, uint32_t len, tcp_parsed_opts_t *out out->sack_permitted = 0; out->has_mss = 0; out->has_wscale = 0; + out->sack_count = 0; + memset(out->sacks, 0, sizeof(out->sacks)); if (!opts || len == 0) return; @@ -81,13 +109,32 @@ void tcp_parse_options(const uint8_t *opts, uint32_t len, tcp_parsed_opts_t *out if (i + olen > len) break; if (kind == 2 && olen == 4) { - out->mss = (uint16_t)((opts[i + 2] << 8) | opts[i + 3]); + out->mss = rd_be16(&opts[i + 2]); out->has_mss = 1; } else if (kind == 3 && olen == 3) { - out->wscale =opts[i + 2]; - out->has_wscale = 1; + uint8_t ws =opts[i + 2]; + if (ws <= 14) { + out->wscale = ws; + out->has_wscale = 1; + } } else if (kind == 4 && olen == 2) { out->sack_permitted = 1; + } else if (kind == 5 && olen >= 10 && ((olen - 2) % 8) == 0) { + uint32_t blocks = (uint32_t)((olen - 2) / 8); + if (blocks > TCP_SACK_MAX_BLOCKS) blocks = TCP_SACK_MAX_BLOCKS; + + for (uint32_t b = 0; b < blocks; b++) { + uint32_t o = i + 2 + b*8; + uint32_t left = rd_be32(&opts[o]); + uint32_t right = rd_be32(&opts[o+4]); + if (TCP_SEQ_LEQ(right, left)) continue; + + uint8_t n = out->sack_count; + if (n >= TCP_SACK_MAX_BLOCKS) break; + out->sacks[n].left = left; + out->sacks[n].right = right; + out->sack_count = (uint8_t)(n+1); + } } i += olen; @@ -101,8 +148,8 @@ uint8_t tcp_build_syn_options(uint8_t *out, uint16_t mss, uint8_t wscale, uint8_ out[i++] = 2; out[i++] = 4; - out[i++] = (uint8_t)(mss >> 8); - out[i++] = (uint8_t)(mss & 0xff); + wr_be16(out + i, mss); + i += 2; if (wscale != 0xffu){ out[i++] = 1; diff --git a/kernel/networking/transport_layer/tcp/tcp_utils.h b/kernel/networking/transport_layer/tcp/tcp_utils.h index 30e9c9b9..b4739eac 100644 --- a/kernel/networking/transport_layer/tcp/tcp_utils.h +++ b/kernel/networking/transport_layer/tcp/tcp_utils.h @@ -5,29 +5,35 @@ #include "networking/internet_layer/ipv6.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "networking/port_manager.h" #ifdef __cplusplus extern "C" { #endif +#define TCP_SACK_MAX_BLOCKS 4 +typedef struct { + uint32_t left; + uint32_t right; +} tcp_sack_block_t; + +struct tcp_flow; + typedef struct { uint16_t mss; uint8_t wscale; uint8_t sack_permitted; uint8_t has_mss; uint8_t has_wscale; + uint8_t sack_count; + tcp_sack_block_t sacks[TCP_SACK_MAX_BLOCKS]; } tcp_parsed_opts_t; void tcp_parse_options(const uint8_t *opts, uint32_t len, tcp_parsed_opts_t *out); uint8_t tcp_build_syn_options(uint8_t *out, uint16_t mss, uint8_t wscale, uint8_t sack_permitted); -port_manager_t *tcp_pm_for_l3(uint8_t l3_id); -uint32_t tcp_calc_mss_for_l3(uint8_t l3_id, ip_version_t ver, const void *remote_ip); - -bool tcp_build_tx_opts_from_local_v4(const void *src_ip_addr, ipv4_tx_opts_t *out); -bool tcp_build_tx_opts_from_l3(uint8_t l3_id, ipv4_tx_opts_t *out); -bool tcp_build_tx_opts_from_local_v6(const void *src_ip_addr, ipv6_tx_opts_t *out); +void tcp_update_mss(struct tcp_flow *flow); +uint32_t tcp_calc_mss_for_l3(l3_id_t l3_id, ip_version_t ver, const void *remote_ip); +uint32_t tcp_initial_cwnd(uint32_t mss); #ifdef __cplusplus } diff --git a/kernel/networking/transport_layer/trans_utils.h b/kernel/networking/transport_layer/trans_utils.h index 19418cfb..2e2c4aa4 100644 --- a/kernel/networking/transport_layer/trans_utils.h +++ b/kernel/networking/transport_layer/trans_utils.h @@ -34,10 +34,18 @@ static inline void net_ep_split(const net_l4_endpoint* ep, char* ip, int iplen, } } -static void make_ep(uint32_t ip_host, uint16_t port, ip_version_t ver, net_l4_endpoint* ep) { +static inline bool net_ep_equal(const net_l4_endpoint* a, const net_l4_endpoint* b) { + if (!a || !b || a->ver != b->ver || a->port != b->port) return false; + if (a->ver != IP_VER4 && a->ver != IP_VER6) return false; + return memcmp(a->ip, b->ip, a->ver == IP_VER6 ? 16 : 4) == 0; +} + +static inline void make_ep(const void *ip, uint16_t port, ip_version_t ver, net_l4_endpoint* ep) { + if (!ep) return; memset(ep, 0, sizeof(*ep)); ep->ver = ver; - memcpy(ep->ip, &ip_host, 4); + if (ip && ver == IP_VER6) ipv6_cpy(ep->ip, ip); + else if (ip) memcpy(ep->ip, ip, 4); ep->port = port; } diff --git a/kernel/networking/transport_layer/udp.c b/kernel/networking/transport_layer/udp.c index 36e025e4..22da6983 100644 --- a/kernel/networking/transport_layer/udp.c +++ b/kernel/networking/transport_layer/udp.c @@ -1,158 +1,190 @@ #include "udp.h" #include "net/checksums.h" +#include "networking/interface_manager.h" #include "networking/internet_layer/ipv4.h" #include "networking/internet_layer/ipv6.h" -#include "networking/port_manager.h" +#include "networking/transport_layer/socket_bind.h" +#include "networking/transport_layer/csocket_udp.h" #include "std/memory.h" #include "types.h" #include "syscalls/syscalls.h" #include "networking/internet_layer/ipv4_utils.h" - -static inline uint32_t v4_u32_from_arr(const uint8_t ip16[16]) { - uint32_t v = 0; - memcpy(&v, ip16, 4); - return v; -} +#include "networking/internet_layer/ipv6_utils.h" size_t create_udp_segment(uintptr_t buf, const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload) { - udp_hdr_t *udp = (udp_hdr_t *)buf; - udp->src_port = bswap16(src->port); - udp->dst_port = bswap16(dst->port); - uint16_t full_len = (uint16_t)(sizeof(*udp) + payload.size); - udp->length = bswap16(full_len); - udp->checksum = 0; + if (!buf || !src || !dst || src->ver != dst->ver) return 0; + if ((src->ver != IP_VER4 && src->ver != IP_VER6) || (payload.size && !payload.ptr)) return 0; + if (payload.size > UINT16_MAX - sizeof(udp_hdr_t)) return 0; - memcpy((void *)(buf + sizeof(*udp)), (void *)payload.ptr, payload.size); + udp_hdr_t udp; + udp.src_port = bswap16(src->port); + udp.dst_port = bswap16(dst->port); + uint16_t full_len = (uint16_t)(sizeof(udp) + payload.size); + udp.length = bswap16(full_len); + udp.checksum = 0; - if (src->ver == IP_VER4) { - uint32_t s = v4_u32_from_arr(src->ip); - uint32_t d = v4_u32_from_arr(dst->ip); - uint16_t csum = checksum16_pipv4(s, d, 0x11, (const uint8_t *)udp, full_len); - udp->checksum = bswap16(csum); - } else if (src->ver == IP_VER6) { - uint16_t csum = checksum16_pipv6(src->ip, dst->ip, 17, (const uint8_t *)udp, full_len); - udp->checksum = bswap16(csum); - } + memcpy((void*)buf, &udp, sizeof(udp)); + if (payload.size) memcpy((void *)(buf + sizeof(udp)), (void *)payload.ptr, payload.size); + uint16_t checksum = 0; + if (src->ver == IP_VER4) { + uint32_t src_ip = 0; + uint32_t dst_ip = 0; + memcpy(&src_ip, src->ip, 4); + memcpy(&dst_ip, dst->ip, 4); + checksum = checksum16_pipv4(src_ip, dst_ip, PROTO_UDP, (const uint8_t *)buf, full_len); + } else checksum = checksum16_pipv6(src->ip, dst->ip, PROTO_UDP, (const uint8_t *)buf, full_len); + + udp.checksum = checksum ? bswap16(checksum) : UINT16_MAX; + memcpy((void*)(buf + offsetof(udp_hdr_t, checksum)), &udp.checksum, sizeof(udp.checksum)); return full_len; } -void udp_send_segment(const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload, const ip_tx_opts_t* tx_opts, uint8_t ttl, uint8_t dontfrag) { +bool udp_send_segment(const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload, const ip_tx_opts_t* tx_opts, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute) { + if (!src || !dst || src->ver != dst->ver) return false; + if ((src->ver != IP_VER4 && src->ver != IP_VER6) || (payload.size && !payload.ptr)) return false; + if (payload.size > UINT16_MAX - sizeof(udp_hdr_t)) return false; + if (src->ver == IP_VER4 && payload.size > UINT16_MAX - sizeof(udp_hdr_t) - sizeof(ipv4_hdr_t)) return false; + uint32_t udp_len = (uint32_t)(sizeof(udp_hdr_t) + payload.size); uint32_t headroom = (uint32_t)sizeof(eth_hdr_t) + (uint32_t)(src->ver == IP_VER4 ? sizeof(ipv4_hdr_t) : sizeof(ipv6_hdr_t)); netpkt_t* pkt = netpkt_alloc(udp_len, headroom, 0); - if (!pkt) return; + if (!pkt) return false; void* buf = netpkt_put(pkt, udp_len); if (!buf) { netpkt_unref(pkt); - return; + return false; } - size_t written = create_udp_segment((uintptr_t)buf, src, dst, payload); + if (!create_udp_segment((uintptr_t)buf, src, dst, payload)) { + netpkt_unref(pkt); + return false; + } if (src->ver == IP_VER4) { - uint32_t dst_ip = v4_u32_from_arr(dst->ip); - (void)netpkt_trim(pkt, (uint32_t)written); - ipv4_send_packet(dst_ip, 0x11, pkt, (const ipv4_tx_opts_t*)tx_opts, ttl, dontfrag); - } else if (src->ver == IP_VER6) { - (void)netpkt_trim(pkt, (uint32_t)written); - ipv6_send_packet(dst->ip, 0x11, pkt, (const ipv6_tx_opts_t*)tx_opts, ttl, dontfrag); - } else { - netpkt_unref(pkt); + uint32_t dst_ip = 0; + memcpy(&dst_ip, dst->ip, 4); + return ipv4_send_packet(dst_ip, PROTO_UDP, pkt, tx_opts, ttl, dontfrag, dontroute); } + return ipv6_send_packet(dst->ip, PROTO_UDP, pkt, tx_opts, ttl, dontfrag, dontroute); } -sizedptr udp_strip_header(uintptr_t ptr, uint32_t len) { - if (len < sizeof(udp_hdr_t)) { - return (sizedptr){ 0, 0 }; - } - udp_hdr_t *hdr = (udp_hdr_t *)ptr; +bool udp_strip_header(const netpkt_t* pkt, udp_hdr_t* hdr, uint32_t* payload_off, uint32_t* payload_len) { + if (!pkt || !hdr || !payload_off || !payload_len) return false; + uint32_t len = netpkt_len(pkt); + if (len < sizeof(udp_hdr_t)) return false; + if (!netpkt_copyout(pkt, 0, hdr, sizeof(*hdr))) return false; + uint16_t total = bswap16(hdr->length); - if (total < sizeof(udp_hdr_t) || total > len) { - return (sizedptr){ 0, 0 }; - } - return (sizedptr){ - .ptr = ptr + sizeof(udp_hdr_t), - .size = total - sizeof(udp_hdr_t) - }; + if (total < sizeof(udp_hdr_t) || total > len) return false; + *payload_off = (uint32_t)sizeof(udp_hdr_t); + *payload_len = total - (uint32_t)sizeof(udp_hdr_t); + return true; } -void udp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, uint8_t l3_id, uintptr_t ptr, uint32_t len) { - sizedptr pl = udp_strip_header(ptr, len); - if (!pl.ptr) return; - - udp_hdr_t *hdr = (udp_hdr_t *)ptr; +udp_input_result_t udp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, l3_id_t l3_id, netpkt_t* pkt, bool include_shared, bool* checksum_done) { + if (!pkt) return UDP_INPUT_DROP; + udp_hdr_t hdr; + uint32_t payload_off = 0; + uint32_t payload_len = 0; + if (!udp_strip_header(pkt, &hdr, &payload_off, &payload_len)) { + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } - if (hdr->checksum) { + if (ipver == IP_VER6 && !hdr.checksum) { + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } + if (hdr.checksum && (!checksum_done || !*checksum_done)) { if (ipver == IP_VER4) { - uint16_t recv = hdr->checksum; - hdr->checksum = 0; - uint16_t calc = checksum16_pipv4( - *(const uint32_t *)src_ip_addr, *(const uint32_t *)dst_ip_addr, 0x11, - (const uint8_t *)hdr, (uint16_t)(pl.size + sizeof(*hdr)) - ); - hdr->checksum = recv; - if (calc != bswap16(recv)) return; + uint32_t src_ip = 0; + uint32_t dst_ip = 0; + memcpy(&src_ip, src_ip_addr, sizeof(src_ip)); + memcpy(&dst_ip, dst_ip_addr, sizeof(dst_ip)); + if (checksum16_pipv4(src_ip, dst_ip, PROTO_UDP, (const uint8_t*)netpkt_data(pkt), (uint16_t)(payload_len + sizeof(hdr))) != 0) { + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } } else if (ipver == IP_VER6) { - uint16_t recv = hdr->checksum; - hdr->checksum = 0; - uint16_t calc = checksum16_pipv6( (const uint8_t*)src_ip_addr, (const uint8_t*)dst_ip_addr, 0x11, (const uint8_t*)hdr, (uint32_t)(pl.size + sizeof(*hdr))); - hdr->checksum = recv; - if (calc != bswap16(recv)) return; + if (checksum16_pipv6((const uint8_t*)src_ip_addr, (const uint8_t*)dst_ip_addr, PROTO_UDP, (const uint8_t*)netpkt_data(pkt), (uint32_t)(payload_len + sizeof(hdr))) != 0) { + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } + } else { + netpkt_unref(pkt); + return UDP_INPUT_DROP; } } - uint16_t dst_port = bswap16(hdr->dst_port); - uint16_t src_port = bswap16(hdr->src_port); + if (checksum_done) *checksum_done = true; + + uint16_t dst_port = bswap16(hdr.dst_port); + uint16_t src_port = bswap16(hdr.src_port); l3_ipv4_interface_t *v4 = NULL; l3_ipv6_interface_t *v6 = NULL; - port_manager_t *pm = NULL; - if (ipver == IP_VER4) { - v4 = l3_ipv4_find_by_id(l3_id); - if (v4) pm = ifmgr_pm_v4(l3_id); - } else if (ipver == IP_VER6) { - v6 = l3_ipv6_find_by_id(l3_id); - if (v6) pm = ifmgr_pm_v6(l3_id); - } + if (ipver == IP_VER4) v4 = l3_ipv4_find_by_id(l3_id); + else if (ipver == IP_VER6) v6 = l3_ipv6_find_by_id(l3_id); - if (!pm) return; + if (!v4 && !v6) { + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } - port_recv_handler_t handler = port_get_handler(pm, PROTO_UDP, dst_port); - if (handler) { - uintptr_t copy = (uintptr_t)malloc(pl.size); - if (!copy) return; - memcpy((void*)copy, (const void*)pl.ptr, pl.size); + uint8_t ifx = 0; + if (v4 && v4->l2) ifx = v4->l2->ifindex; + else if (v6 && v6->l2) ifx = v6->l2->ifindex; - uint8_t ifx = 0; - if (v4 && v4->l2) ifx = v4->l2->ifindex; - else if (v6 && v6->l2) ifx = v6->l2->ifindex; + bool fanout = false; + if (ipver == IP_VER4) { + uint32_t dst = 0; + memcpy(&dst, dst_ip_addr, sizeof(dst)); + fanout = ipv4_is_multicast(dst) || ipv4_is_limited_broadcast(dst) || (v4 && dst == v4->broadcast); + } else if (ipver == IP_VER6) fanout = ipv6_is_multicast(dst_ip_addr); + + if (!fanout) { + ksocket_t* socket = socket_bind_lookup(PROTO_UDP, ipver, l3_id, ifx, src_ip_addr, src_port, dst_ip_addr, dst_port); + if (!socket) { + netpkt_unref(pkt); + return UDP_INPUT_NO_LISTENER; + } - handler(ifx, ipver, src_ip_addr, dst_ip_addr, copy, pl.size, src_port, dst_port); + netpkt_t* plpkt = netpkt_view(pkt, payload_off, payload_len); + if (!plpkt) { + socket_core_put(socket); + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } + socket_udp_input(socket, ipver, l3_id, src_ip_addr, dst_ip_addr, plpkt, src_port, dst_port); + socket_core_put(socket); + netpkt_unref(plpkt); + netpkt_unref(pkt); + return UDP_INPUT_DELIVERED; } -} -static inline port_manager_t* pm_for_l3(uint8_t l3_id) { - if (l3_ipv4_find_by_id(l3_id)) return ifmgr_pm_v4(l3_id); - if (l3_ipv6_find_by_id(l3_id)) return ifmgr_pm_v6(l3_id); - return NULL; -} + socket_bind_iter_t iter = {0}; + ksocket_t* socket = socket_bind_udp_next_fanout(ipver, l3_id, ifx, dst_ip_addr, dst_port, include_shared, &iter); + if (!socket) { + netpkt_unref(pkt); + return UDP_INPUT_NO_LISTENER; + } -bool udp_bind_l3(uint8_t l3_id, uint16_t port, uint16_t pid, port_recv_handler_t handler) { - port_manager_t* pm = pm_for_l3(l3_id); - if (!pm) return false; - return port_bind_manual(pm, PROTO_UDP, port, pid, handler); -} + netpkt_t* plpkt = netpkt_view(pkt, payload_off, payload_len); + if (!plpkt) { + socket_core_put(socket); + netpkt_unref(pkt); + return UDP_INPUT_DROP; + } -bool udp_unbind_l3(uint8_t l3_id, uint16_t port, uint16_t pid) { - port_manager_t* pm = pm_for_l3(l3_id); - if (!pm) return false; - return port_unbind(pm, PROTO_UDP, port, pid); -} + do { + socket_udp_input(socket, ipver, l3_id, src_ip_addr, dst_ip_addr, plpkt, src_port, dst_port); + socket_core_put(socket); + socket = socket_bind_udp_next_fanout(ipver, l3_id, ifx, dst_ip_addr, dst_port, include_shared, &iter); + } while (socket); -int udp_alloc_ephemeral_l3(uint8_t l3_id, uint16_t pid, port_recv_handler_t handler) { - port_manager_t* pm = pm_for_l3(l3_id); - if (!pm) return -1; - return port_alloc_ephemeral(pm, PROTO_UDP, pid, handler); + netpkt_unref(plpkt); + netpkt_unref(pkt); + return UDP_INPUT_DELIVERED; } diff --git a/kernel/networking/transport_layer/udp.h b/kernel/networking/transport_layer/udp.h index 074012bf..0ce6c1d4 100644 --- a/kernel/networking/transport_layer/udp.h +++ b/kernel/networking/transport_layer/udp.h @@ -1,8 +1,8 @@ #pragma once #include "types.h" #include "net/network_types.h" -#include "networking/port_manager.h" #include "networking/internet_layer/ipv4.h" +#include "networking/netpkt.h" #ifdef __cplusplus extern "C" { @@ -15,23 +15,26 @@ typedef struct __attribute__((packed)) { uint16_t checksum; } udp_hdr_t; +typedef enum { + UDP_INPUT_DROP = 0, + UDP_INPUT_DELIVERED, + UDP_INPUT_NO_LISTENER +} udp_input_result_t; + size_t create_udp_segment(uintptr_t buf, const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload); -void udp_send_segment(const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload, const ip_tx_opts_t* tx_opts, uint8_t ttl, uint8_t dontfrag); +bool udp_send_segment(const net_l4_endpoint *src, const net_l4_endpoint *dst, sizedptr payload, const ip_tx_opts_t* tx_opts, uint8_t ttl, uint8_t dontfrag, uint8_t dontroute); -void udp_input(ip_version_t ipver, +udp_input_result_t udp_input(ip_version_t ipver, const void *src_ip_addr, const void *dst_ip_addr, - uint8_t l3_id, - uintptr_t ptr, - uint32_t len); + l3_id_t l3_id, + netpkt_t* pkt, + bool include_shared, bool* checksum_done); -bool udp_bind_l3(uint8_t l3_id, uint16_t port, uint16_t pid, port_recv_handler_t handler); -bool udp_unbind_l3(uint8_t l3_id, uint16_t port, uint16_t pid); -int udp_alloc_ephemeral_l3(uint8_t l3_id, uint16_t pid, port_recv_handler_t handler); #ifdef __cplusplus } diff --git a/kernel/pci/pci.c b/kernel/pci/pci.c old mode 100755 new mode 100644 diff --git a/kernel/process/kernel_syscall_impl.c b/kernel/process/kernel_syscall_impl.c index f9c35be9..96557f1a 100644 --- a/kernel/process/kernel_syscall_impl.c +++ b/kernel/process/kernel_syscall_impl.c @@ -89,37 +89,48 @@ uint64_t get_time(){ return timer_now_msec(); } -bool socket_create(Socket_Role role, protocol_t protocol, const SocketExtraOptions* extra, SocketHandle *out_handle){ - return create_socket(role, protocol, extra, get_current_proc_pid(), out_handle); +socket_handle_t socket_create(protocol_t protocol, const SocketOptions* extra){ + return create_socket(protocol, extra); } -int32_t socket_bind(SocketHandle *handle, ip_version_t ip_version, uint16_t port){ - return bind_socket(handle, port, ip_version, get_current_proc_pid()); +int32_t socket_bind(socket_handle_t handle, const SockBindSpec* spec, uint16_t port){ + return bind_socket(handle, spec, port); } -int32_t socket_connect(SocketHandle *handle, SockDstKind dst_kind, void* dst, uint16_t port){ - return connect_socket(handle, dst_kind, dst, port, get_current_proc_pid()); +int32_t socket_connect(socket_handle_t handle, const net_l4_endpoint* dst){ + return connect_socket(handle, dst); } -int32_t socket_listen(SocketHandle *handle){ - return listen_on(handle, 0, get_current_proc_pid()); +int32_t socket_listen(socket_handle_t handle, int32_t backlog){ + return listen_on(handle, backlog); } -bool socket_accept(SocketHandle *spec){ - accept_on_socket(spec, get_current_proc_pid()); - return 1; +socket_handle_t socket_accept(socket_handle_t handle){ + return accept_on_socket(handle); } -size_t socket_send(SocketHandle *handle, SockDstKind dst_kind, const void* dst, uint16_t port, void *packet, size_t size){ - return send_on_socket(handle, dst_kind, dst, port, packet, size, get_current_proc_pid()); +int64_t socket_send(socket_handle_t handle, void *packet, size_t size){ + return send_on_socket(handle, packet, size); } -bool socket_receive(SocketHandle *handle, void *packet, size_t size, net_l4_endpoint* out_src){ - return receive_from_socket(handle, packet, size, out_src, get_current_proc_pid()); +int64_t socket_send_to(socket_handle_t handle, const net_l4_endpoint* dst, void *packet, size_t size){ + return send_to_socket(handle, dst, packet, size); } -int32_t socket_close(SocketHandle *handle){ - return close_socket(handle, get_current_proc_pid()); +int64_t socket_receive(socket_handle_t handle, void *packet, size_t size, net_l4_endpoint* out_src){ + return receive_from_socket(handle, packet, size, out_src); +} + +int32_t socket_close(socket_handle_t handle){ + return close_socket(handle); +} + +int32_t socket_set_option(socket_handle_t handle, int32_t opt, const void* value, uint32_t len){ + return set_socket_option(handle, opt, value, len); +} + +int32_t socket_get_option(socket_handle_t handle, int32_t opt, void* value, uint32_t* len){ + return get_socket_option(handle, opt, value, len); } FS_RESULT openf(const char* path, file* descriptor){ diff --git a/kernel/process/loading/dwarf.c b/kernel/process/loading/dwarf.c index f47e9e43..b866f876 100644 --- a/kernel/process/loading/dwarf.c +++ b/kernel/process/loading/dwarf.c @@ -275,14 +275,16 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l .discriminator = 0 }; if (ptr + sizeof(dwarf_debug_line_header) > end_section) return (debug_line_info){}; - dwarf_debug_line_header *hdr = (dwarf_debug_line_header*)ptr; - uintptr_t unit_end = (uptr)&hdr->unit_length + sizeof(hdr->unit_length) + read_unaligned32(&hdr->unit_length); + dwarf_debug_line_header hdr = {0}; + memcpy(&hdr, (const void*)ptr, sizeof(hdr)); + uintptr_t unit_end = ptr + sizeof(hdr.unit_length) + hdr.unit_length; + if (unit_end <= ptr || unit_end > end_section) return (debug_line_info){}; - if (!hdr->line_range || !hdr->opcode_base) return (debug_line_info){}; + if (!hdr.line_range || !hdr.opcode_base) return (debug_line_info){}; - state.is_stmt = hdr->default_is_stmt; + state.is_stmt = hdr.default_is_stmt; - if (read_unaligned16(&hdr->version) != 5) { + if (hdr.version != 5) { kprintf("Only DWARF version 5 is supported"); return (debug_line_info){}; } @@ -292,7 +294,7 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l // kprintf("Header is %x bytes",sizeof(dwarf_debug_line_header)); memset(files, 0, sizeof(files)); - uintptr_t file_ptr = dwarf_decode_entries(ptr + sizeof(dwarf_debug_line_header) + hdr->opcode_base - 1, debug_line_str_base, str_size, type_codes, form_codes, 0); + uintptr_t file_ptr = dwarf_decode_entries(ptr + sizeof(dwarf_debug_line_header) + hdr.opcode_base - 1, debug_line_str_base, str_size, type_codes, form_codes, 0); if (file_ptr != ptr){ // kprintf("Now files %x",file_ptr); dwarf_decode_entries(file_ptr, debug_line_str_base, str_size, type_codes, form_codes, files); @@ -301,9 +303,8 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l // for (int i = 0; i < 256; i++){ // if (files[i]) kprintf("File [%i] = %s",i,files[i]); // } + ptr = ptr + sizeof(hdr.unit_length) + sizeof(hdr.version) + sizeof(hdr.address_size) + sizeof(hdr.segment_selector) + sizeof(hdr.header_length) + hdr.header_length; - ptr = (uintptr_t)&hdr->header_length + sizeof(hdr->header_length) + read_unaligned32(&hdr->header_length); - uint8_t *end = (uint8_t*)unit_end; uint8_t *p = (uint8_t*)ptr; @@ -332,7 +333,7 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l case DW_LNE_set_address: // kprintf("Address changed by DW_LNE_set_address from %x",state.address); - state.address = decode_address(&p, hdr->address_size); + state.address = decode_address(&p, hdr.address_size); // kprintf(" to %x",state.address); break; @@ -344,7 +345,7 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l kprintf("[DWARF ERROR] UNKNOWN EXTENDED OPCODE %i with length %i at %x",ex_opcode, len, p); return (debug_line_info){}; } - } else if (opcode < hdr->opcode_base) { //Standard + } else if (opcode < hdr.opcode_base) { //Standard switch (opcode) { case DW_LNS_copy: emit_row = true; @@ -353,7 +354,7 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l case DW_LNS_advance_pc: { uint64_t operand = decode_uleb128(&p); // kprintf("Advancing DW_LNS_advance_pc by %x",operand); - state.address += operand * hdr->minimum_instruction_length; + state.address += operand * hdr.minimum_instruction_length; break; } @@ -381,15 +382,15 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l break; case DW_LNS_const_add_pc: { - uint8_t adjusted = 255 - hdr->opcode_base; - uint64_t addr_inc = (adjusted / hdr->line_range) * hdr->minimum_instruction_length; + uint8_t adjusted = 255 - hdr.opcode_base; + uint64_t addr_inc = (adjusted / hdr.line_range) * hdr.minimum_instruction_length; state.address += addr_inc; // kprintf("Advancing DW_LNS_const_add_pc by %x. New %x",addr_inc,state.address); break; } case DW_LNS_fixed_advance_pc: { - uint16_t advance = *(uint16_t *)p; + uint16_t advance = read_unaligned16((const uint16_t*)p); p += 2; state.address += advance; // kprintf("Advancing DW_LNS_fixed_advance_pc by %x. New %x",advance,state.address); @@ -414,12 +415,12 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l } } else { //Special - uint8_t adj = opcode - hdr->opcode_base;//146 - 13 = 133 + uint8_t adj = opcode - hdr.opcode_base;//146 - 13 = 133 // kprintf("Special opcode %i - %i = %i",opcode) - uint8_t op_adv = adj/hdr->line_range;//47/14 = 9.xxx - state.line += hdr->line_base + (adj % hdr->line_range); + uint8_t op_adv = adj/hdr.line_range;//47/14 = 9.xxx + state.line += hdr.line_base + (adj % hdr.line_range); // kprintf("Advancing by special by %x",op_adv); - state.address += op_adv * hdr->minimum_instruction_length; + state.address += op_adv * hdr.minimum_instruction_length; state.basic_block = false; state.prologue_end = false; state.epilogue_begin = false; @@ -454,7 +455,7 @@ debug_line_info dwarf_decode_lines(uintptr_t ptr, size_t size, uintptr_t debug_l .file = 1, .line = 1, .column = 0, - .is_stmt = hdr->default_is_stmt, + .is_stmt = hdr.default_is_stmt, .basic_block = false, .end_sequence = false, .prologue_end = false, diff --git a/kernel/process/scheduler.c b/kernel/process/scheduler.c index 5bab0b64..781ce065 100644 --- a/kernel/process/scheduler.c +++ b/kernel/process/scheduler.c @@ -21,6 +21,7 @@ #include "files/dir_list.h" #include "stack_manager.h" #include "graph/tres.h" +#include "networking/network.h" extern void save_pc_interrupt(uintptr_t ptr); extern void restore_context(uintptr_t ptr); @@ -336,6 +337,7 @@ void reset_process(process_t *proc){ proc->packet_buffer.entries[k] = (sizedptr){0}; } close_files_for_process(pid); + network_cleanup_process(pid); if (proc->postmortem_output) { release((void*)proc->postmortem_output); diff --git a/kernel/process/syscall.c b/kernel/process/syscall.c index 87739089..e35f0241 100644 --- a/kernel/process/syscall.c +++ b/kernel/process/syscall.c @@ -14,7 +14,6 @@ #include "std/string.h" #include "exceptions/timer.h" #include "networking/network.h" -#include "networking/port_manager.h" #include "filesystem/filesystem.h" #include "syscalls/syscall_codes.h" #include "graph/tres.h" @@ -245,99 +244,103 @@ u64 syscall_get_time(process_t *ctx, thread_t *current_thread){ } u64 syscall_socket_create(process_t *ctx, thread_t *current_thread){ - Socket_Role role = (Socket_Role)current_thread->PROC_X0; - protocol_t protocol = (protocol_t)current_thread->PROC_X1; - SYSCALL_ARG(const SocketExtraOptions, extra, PROC_X2, false); - SYSCALL_ARG(SocketHandle, out, PROC_X3, true); + protocol_t protocol = (protocol_t)current_thread->PROC_X0; + const SocketOptions* extra = NULL; + + if (current_thread->PROC_X1) { + SYSCALL_ARG(SocketOptions, user_extra, PROC_X1, false); + if (user_extra->mcast_count) { + if (!user_extra->mcast_groups) return 0; + if (!validate_address(ctx, current_thread, (uptr)user_extra->mcast_groups, sizeof(net_l4_endpoint) * user_extra->mcast_count, false)) return 0; + } + extra = user_extra; + } - return create_socket(role, protocol, extra, ctx->id, out); + return create_socket(protocol, extra); } u64 syscall_socket_bind(process_t *ctx, thread_t *current_thread){ - SYSCALL_ARG(SocketHandle,handle,PROC_X0, true); - ip_version_t ip_version = (ip_version_t)current_thread->PROC_X1; + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + const SockBindSpec* spec = NULL; + + if (current_thread->PROC_X1){ + SYSCALL_ARG(SockBindSpec, user_spec, PROC_X1, false); + spec = user_spec; + } + uint16_t port = (uint16_t)current_thread->PROC_X2; - return bind_socket(handle, port, ip_version, ctx->id); + return bind_socket(handle, spec, port); } u64 syscall_socket_connect(process_t *ctx, thread_t *current_thread){ - uint8_t dst_kind = (uint8_t)current_thread->PROC_X1; - uint16_t port = (uint16_t)current_thread->PROC_X3; - - SYSCALL_ARG(SocketHandle,handle,PROC_X0,true); - - const void *dst = 0; - - if (dst_kind == DST_ENDPOINT) { - SYSCALL_ARG(net_l4_endpoint, ep, PROC_X2, true); - dst = ep; - } else if (dst_kind == DST_DOMAIN) { - SYSCALL_STR(domain, PROC_X2, true); - dst = domain; - } else { - return 0; - } - - return connect_socket(handle, dst_kind, dst, port, ctx->id); + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + SYSCALL_ARG(net_l4_endpoint, ep, PROC_X1, false); + return connect_socket(handle, ep); } u64 syscall_socket_listen(process_t *ctx, thread_t *current_thread){ - SYSCALL_ARG(SocketHandle,handle, PROC_X0, true); + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; int32_t backlog = (int32_t)current_thread->PROC_X1; - return listen_on(handle, backlog, ctx->id); + return listen_on(handle, backlog); } u64 syscall_socket_accept(process_t *ctx, thread_t *current_thread){ - SYSCALL_ARG(SocketHandle,handle, PROC_X0, true); - accept_on_socket(handle, ctx->id); - return 1; + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + return accept_on_socket(handle); } u64 syscall_socket_send(process_t *ctx, thread_t *current_thread){ - uint8_t dst_kind = (uint8_t)current_thread->PROC_X1; - uint16_t port = (uint16_t)current_thread->PROC_X3; - size_t size = (size_t)current_thread->regs[5]; - - SYSCALL_ARG(SocketHandle,handle, PROC_X0, true); - - const void *dst = 0; - - if (dst_kind == DST_ENDPOINT){ - SYSCALL_ARG(net_l4_endpoint, ep, PROC_X2, true); - dst = ep; - } else if (dst_kind == DST_DOMAIN){ - SYSCALL_STR(domain, PROC_X2, true); - dst = domain; - } else { - return 0; - } + size_t size = (size_t)current_thread->PROC_X2; - if (!size) return 0; - - uint64_t alloc_size = (size + 0xFFF) & ~0xFFFULL; + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + SYSCALL_ARG_SIZE(void, buf, size, PROC_X1, false); + return send_on_socket(handle, buf, size); +} - SYSCALL_ARG_SIZE(void, kbuf, alloc_size, PROC_X4, true); - if (!kbuf) return 0; +u64 syscall_socket_send_to(process_t *ctx, thread_t *current_thread) { + size_t size = (size_t)current_thread->PROC_X3; + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + SYSCALL_ARG(net_l4_endpoint, dst, PROC_X1, false); + SYSCALL_ARG_SIZE(void, buf, size, PROC_X2, false); - return send_on_socket(handle, dst_kind, dst, port, kbuf, size, ctx->id); + return send_to_socket(handle, dst, buf, size); } u64 syscall_socket_receive(process_t *ctx, thread_t *current_thread){ size_t size = (size_t)current_thread->PROC_X2; - if (!size) return 0; - uint64_t alloc_size = (size + 0xFFF) & ~0xFFFULL; - SYSCALL_ARG(SocketHandle, handle, PROC_X0, true); - SYSCALL_ARG_SIZE(void, buf, alloc_size, PROC_X1, true); + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + SYSCALL_ARG_SIZE(void, buf, size, PROC_X1, true); + + net_l4_endpoint* src = NULL; + if (current_thread->PROC_X3) { + src = (net_l4_endpoint*)current_thread->PROC_X3; + if (!validate_address(ctx, current_thread, (uptr)src, sizeof(net_l4_endpoint), true)) return 0; + } - SYSCALL_ARG(net_l4_endpoint, src, PROC_X3, true); - return receive_from_socket(handle, buf, size, src, ctx->id); + return receive_from_socket(handle, buf, size, src); } u64 syscall_socket_close(process_t *ctx, thread_t *current_thread){ - SYSCALL_ARG(SocketHandle,handle, PROC_X0, true); - return close_socket(handle, ctx->id); + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + return close_socket(handle); +} + +u64 syscall_socket_setopt(process_t *ctx, thread_t *current_thread){ + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + int32_t opt = (int32_t)current_thread->PROC_X1; + uint32_t len = (uint32_t)current_thread->PROC_X3; + SYSCALL_ARG_SIZE(void, value, len, PROC_X2, false); + return set_socket_option(handle, opt, value, len); +} + +u64 syscall_socket_getopt(process_t *ctx, thread_t *current_thread){ + socket_handle_t handle = (socket_handle_t)current_thread->PROC_X0; + int32_t opt = (int32_t)current_thread->PROC_X1; + SYSCALL_ARG(uint32_t, len, PROC_X3, true); + SYSCALL_ARG_SIZE(void, value, *len, PROC_X2, true); + return get_socket_option(handle, opt, value, len); } #define ISOLATEDFS @@ -537,9 +540,12 @@ syscall_entry syscalls[] = { [SOCKET_CONNECT_CODE] = syscall_socket_connect, [SOCKET_LISTEN_CODE] = syscall_socket_listen, [SOCKET_ACCEPT_CODE] = syscall_socket_accept, + [SOCKET_SENDTO_CODE] = syscall_socket_send_to, [SOCKET_SEND_CODE] = syscall_socket_send, [SOCKET_RECEIVE_CODE] = syscall_socket_receive, [SOCKET_CLOSE_CODE] = syscall_socket_close, + [SOCKET_SETOPT_CODE] = syscall_socket_setopt, + [SOCKET_GETOPT_CODE] = syscall_socket_getopt, [FILE_OPEN_CODE] = syscall_openf, [FILE_READ_CODE] = syscall_readf, [FILE_WRITE_CODE] = syscall_writef, diff --git a/kernel/theme/theme.c b/kernel/theme/theme.c index 4cb7bc7f..aaaa8870 100644 --- a/kernel/theme/theme.c +++ b/kernel/theme/theme.c @@ -39,7 +39,7 @@ system_config_t system_config = { .default_pwd = DEFAULT_PWD, .system_name = SYSTEM_NAME, .app_directory = "boot", - .use_net = false, + .use_net = true, .preferred_screen_size = { 1920,1080 }, .headless = false, .use_login = false, diff --git a/kernel/tools/curl.c b/kernel/tools/curl.c new file mode 100644 index 00000000..5fa1b8ed --- /dev/null +++ b/kernel/tools/curl.c @@ -0,0 +1,165 @@ +#include "curl.h" +#include "console/kio.h" +#include "data/format/url.h" +#include "networking/application_layer/csocket_http_client.h" +#include "networking/application_layer/http.h" +#include "process/scheduler.h" +#include "std/memory.h" +#include "std/string.h" +#include "syscalls/syscalls.h" + +#define CURL_MAX_REDIRECTS 5 + +typedef struct { + char *url; + bool head_only; + bool follow; +} curl_opts_t; + +static void print_help(void) { + print("Usage:\t curl [OPTIONS] URL"); + print("transfer data from URL (no HTTPS)\n"); + print("Args:"); + print("\t URL\t URL to fetch\n"); + print("Options:"); + print("\t -I\t headers only"); + print("\t -L\t follow redirects"); + print("\t --help\t help"); +} + +static int curl_fetch(char *url, bool head_only, bool follow) { + string host = (string){0}; + string path = (string){0}; + uint16_t port = 0; + bool https = false; + + if (!url) { + print("curl: bad url"); + return 2; + } + + ParsedURL parsed = parse_url(url, (uint32_t)strlen(url)); + if (!parsed.ok || !parsed.scheme.ptr || !parsed.host.ptr || !parsed.host.size) { + print("curl: bad url"); + return 2; + } + + if (parsed.scheme.size == 5 && strncmp_case((const char*)parsed.scheme.ptr, "https", true, 5) == 0) { + https = true; + port = parsed.port ? parsed.port : 443; + } else if (parsed.scheme.size == 4 && strncmp_case((const char*)parsed.scheme.ptr, "http", true, 4) == 0) port = parsed.port ? parsed.port : 80; + else { + print("curl: bad url"); + return 2; + } + + host = string_from_literal_length((const char*)parsed.host.ptr, parsed.host.size); + if (!host.data) { + print("curl: bad url"); + return 2; + } + + path = string_repeat('\0', 0); + if (parsed.path.ptr && parsed.path.size) string_append_bytes(&path, (const char*)parsed.path.ptr, parsed.path.size); + else string_append_bytes(&path, "/", 1); + if (parsed.query.ptr && parsed.query.size) { + string_append_bytes(&path, "?", 1); + string_append_bytes(&path, (const char*)parsed.query.ptr, parsed.query.size); + } + + if (!path.data) { + string_free(host); + print("curl: bad url"); + return 2; + } + + if (https) { + string_free(host); + string_free(path); + print("curl: https is not supported yet"); + return 3; + } + + HTTPClientPolicyOptions opts = {0}; + opts.flags = HTTP_CLIENT_OPT_FOLLOW_REDIRECTS | HTTP_CLIENT_OPT_MAX_REDIRECTS; + opts.value.follow_redirects = follow != 0; + opts.value.max_redirects = CURL_MAX_REDIRECTS; + + http_client_handle_t cli = http_client_create(NULL, &opts); + if (!cli) { + string_free(host); + string_free(path); + print("curl: socket create failed"); + return 4; + } + + int32_t rc = http_client_connect_domain(cli, host.data, port); + if (rc < 0) { + print("curl: connect failed for %s:%d", host.data, port); + http_client_destroy(cli); + string_free(host); + string_free(path); + return 5; + } + + HTTPRequestMsg req = (HTTPRequestMsg){0}; + req.method = head_only ? HTTP_METHOD_HEAD : HTTP_METHOD_GET; + req.version = HTTP_VERSION_11; + req.path = path; + req.headers_common.fields.connection = string_from_const("close"); + + HTTPResponseMsg resp = http_client_send_request(cli, &req); + if ((int32_t)resp.status_code < 0) { + print("curl: request failed (%d)", (int)resp.status_code); + http_response_free(&resp); + http_headers_common_free(&req.headers_common); + http_client_destroy(cli); + string_free(host); + string_free(path); + return 6; + } + + if (head_only) { + HTTPResponseMsg head = resp; + head.body = (string){0}; + string raw = http_response_builder(&head); + print("%.*s", (int)raw.length, raw.data); + string_free(raw); + } else if (resp.body.data && resp.body.length) print("%.*s", (int)resp.body.length, (const char*)resp.body.data); + + http_response_free(&resp); + http_headers_common_free(&req.headers_common); + + http_client_destroy(cli); + string_free(host); + string_free(path); + return 0; +} + +static bool parse_args(int argc, char *argv[], curl_opts_t *o) { + memset(o, 0, sizeof(*o)); + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-I") == 0) o->head_only = true; + else if (strcmp(argv[i], "-L") == 0) o->follow = true; + else if (!o->url) o->url = argv[i]; + else return false; + } + + return o->url != NULL; +} + +int run_curl(int argc, char* argv[]) { + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + print_help(); + return 0; + } + + curl_opts_t opts; + if (!parse_args(argc, argv, &opts)) { + print_help(); + return 2; + } + + return curl_fetch(opts.url, opts.head_only, opts.follow); +} diff --git a/kernel/tools/curl.h b/kernel/tools/curl.h new file mode 100644 index 00000000..13c58089 --- /dev/null +++ b/kernel/tools/curl.h @@ -0,0 +1,12 @@ +#pragma once +#include "process/process.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int run_curl(int argc, char* argv[]); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/tools/firewall.c b/kernel/tools/firewall.c new file mode 100644 index 00000000..ed83f9f9 --- /dev/null +++ b/kernel/tools/firewall.c @@ -0,0 +1,263 @@ +#include "firewall.h" +#include "types.h" +#include "console/kio.h" +#include "std/string.h" +#include "std/memory.h" +#include "alloc/allocate.h" +#include "net/net_ctrl.h" +#include "net/socket_types.h" +#include "networking/transport_layer/net_ctrl.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" + +static void print_help(void) { + print("Usage:\t firewall [COMMAND]"); + print("\t firewall default DIRECTION ACTION"); + print("\t firewall delete ID"); + print("\t firewall ACTION in PROTOCOL [from ADDRESS] [port PORT]"); + print("\t firewall ACTION out PROTOCOL [to ADDRESS] [port PORT]"); + print("manage firewall rules\n"); + print("Commands:"); + print("\t status\t show status"); + print("\t list\t list rules"); + print("\t on\t enable"); + print("\t off\t disable"); + print("\t clear\t clear rules"); + print("\t reset\t reset rules"); + print("\t delete\t delete rule\n"); + print("Args:"); + print("\t ACTION\t allow|deny"); + print("\t DIRECTION\t in|out"); + print("\t PROTOCOL\t tcp|udp|any"); + print("\t ADDRESS\t address|prefix|any"); + print("\t PORT\t port|FIRST-LAST"); + print("\t ID\t rule id\n"); + print("Options:"); + print("\t --help\t help"); +} + +static void req_init(uint8_t req[256], uint32_t* req_len, NetCtrlOp op) { + memset(req, 0, 256); + NetCtrlMsg* msg = (NetCtrlMsg*)req; + msg->object = NET_CTRL_OBJ_FIREWALL; + msg->op = op; + msg->flags = NET_CTRL_F_REQUEST; + *req_len = sizeof(NetCtrlMsg); +} + +static bool req_attr(uint8_t req[256], uint32_t* req_len, NetCtrlExtAttr ext, const void* data, uint16_t len) { + if (!data || *req_len + sizeof(NetCtrlAttr) + len > 256) return false; + NetCtrlAttr attr = {.ext = ext, .length = len}; + memcpy(req + *req_len, &attr, sizeof(attr)); + *req_len += sizeof(attr); + memcpy(req + *req_len, data, len); + *req_len += len; + return true; +} + +static int32_t req_send(uint8_t req[256], uint32_t req_len, uint8_t** response) { + if (response) *response = NULL; + ((NetCtrlMsg*)req)->length = req_len; + uint8_t* out = NULL; + uint32_t out_len = 0; + int32_t rc = net_ctrl_dispatch(req, req_len, &out, &out_len); + if (rc != SOCK_OK || !out || out_len < sizeof(NetCtrlMsg)) { + if (out) release(out); + return rc == SOCK_OK ? SOCK_ERR_SYS : rc; + } + rc = ((NetCtrlMsg*)out)->status; + if (rc != SOCK_OK || !response) release(out); + else *response = out; + return rc; +} + +static int show_firewall(bool list) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OP_GET); + uint8_t* response = NULL; + int32_t rc = req_send(req, req_len, &response); + if (rc != SOCK_OK) { + print("firewall: request failed (%i)", rc); + return 1; + } + + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t len = NET_CTRL_MSG_PAYLOAD_LEN(msg); + if (len < sizeof(NetCtrlFirewallState)) { + release(response); + return 1; + } + NetCtrlFirewallState* state = NET_CTRL_MSG_DATA(msg); + print("firewall: %s", state->enabled ? "on" : "off"); + print("default: in %s, out %s", state->default_in == NET_CTRL_FIREWALL_ALLOW ? "allow" : "deny", state->default_out == NET_CTRL_FIREWALL_ALLOW ? "allow" : "deny"); + + if (list) { + uint32_t available = (len - sizeof(*state)) / sizeof(NetCtrlFirewallRule); + uint32_t count = state->rule_count < available ? state->rule_count : available; + NetCtrlFirewallRule* rules = (NetCtrlFirewallRule*)((uint8_t*)state + sizeof(*state)); + if (!count) print("no rules"); + for (uint32_t i = 0; i < count; i++) { + char address[96], port[24]; + if (!rules[i].ip_version) string_format_buf(address, sizeof(address), "any"); + else { + net_l4_endpoint ep; + make_ep(rules[i].address, 0, (ip_version_t)rules[i].ip_version, &ep); + char value[64]; + net_ep_split(&ep, value, sizeof(value), NULL, NULL); + string_format_buf(address, sizeof(address), "%s/%u", value, rules[i].prefix_len); + } + if (!rules[i].port_from) string_format_buf(port, sizeof(port), "any"); + else if (rules[i].port_from == rules[i].port_to) string_format_buf(port, sizeof(port), "%u", rules[i].port_from); + else string_format_buf(port, sizeof(port), "%u-%u", rules[i].port_from, rules[i].port_to); + const char* direction = rules[i].direction == NET_CTRL_FIREWALL_IN ? "in" : "out"; + const char* protocol = rules[i].protocol == PROTO_TCP ? "tcp" : rules[i].protocol == PROTO_UDP ? "udp" : "any"; + print("%u %s %s %s %s port %s", rules[i].id, rules[i].action == NET_CTRL_FIREWALL_ALLOW ? "allow" : "deny", direction, protocol, address, port); + } + } + release(response); + return 0; +} + +static int update(uint8_t state, uint8_t direction, uint8_t action) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OP_UPD); + if (state != UINT8_MAX && !req_attr(req, &req_len, NET_CTRL_EXT_STATE, &state, sizeof(state))) return 1; + if (direction && (!req_attr(req, &req_len, NET_CTRL_EXT_DIRECTION, &direction, sizeof(direction)) || !req_attr(req, &req_len, NET_CTRL_EXT_ACTION, &action, sizeof(action)))) return 1; + int32_t rc = req_send(req, req_len, NULL); + if (rc != SOCK_OK) print("firewall: update failed (%i)", rc); + return rc == SOCK_OK ? 0 : 1; +} + +static int delete_rule(const char* value) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OP_DEL); + if (value) { + uint32_t id = 0; + if (!parse_uint32_dec_exact(value, &id) || !id || !req_attr(req, &req_len, NET_CTRL_EXT_RULE_ID, &id, sizeof(id))) return 2; + } + int32_t rc = req_send(req, req_len, NULL); + if (rc != SOCK_OK) print("firewall: delete failed (%i)", rc); + return rc == SOCK_OK ? 0 : 1; +} + +static int add_rule(int argc, char* argv[]) { + if (argc < 4) return 2; + uint8_t action = 0; + if (strcmp_case(argv[1], "allow", true) == 0) action = NET_CTRL_FIREWALL_ALLOW; + else if (strcmp_case(argv[1], "deny", true) == 0) action = NET_CTRL_FIREWALL_DENY; + else return 2; + + uint8_t direction = 0; + if (strcmp_case(argv[2], "in", true) == 0) direction = NET_CTRL_FIREWALL_IN; + else if (strcmp_case(argv[2], "out", true) == 0) direction = NET_CTRL_FIREWALL_OUT; + else return 2; + + uint8_t protocol = PROTO_NONE; + if (strcmp_case(argv[3], "tcp", true) == 0) protocol = PROTO_TCP; + else if (strcmp_case(argv[3], "udp", true) == 0) protocol = PROTO_UDP; + else if (strcmp_case(argv[3], "any", true) != 0) return 2; + + net_l4_endpoint address = {0}; + uint8_t prefix = 0; + uint16_t port_from = 0, port_to = 0; + bool address_seen = false, has_address = false, has_port = false; + for (int i = 4; i < argc; i++) { + if (strcmp_case(argv[i], direction == NET_CTRL_FIREWALL_IN ? "from" : "to", true) == 0) { + if (++i >= argc || address_seen) return 2; + address_seen = true; + has_address = strcmp_case(argv[i], "any", true) != 0; + if (has_address) { + const char* value = argv[i]; + int32_t slash = str_has_char(value, 0, '/'); + uint32_t bits = UINT32_MAX, v4 = 0; + uint8_t v6[16]; + char ip[80]; + if (slash >= 0) { + if (!slash || strlen_max(value, sizeof(ip)) >= sizeof(ip) || !parse_uint32_dec_exact(value + slash + 1, &bits)) return 2; + strncpy(ip, value, sizeof(ip)); + ip[slash] = 0; + value = ip; + } + if (ipv4_parse(value, &v4)) { + make_ep(&v4, 0, IP_VER4, &address); + if (bits == UINT32_MAX) bits = 32; + if (bits > 32) return 2; + } else if (ipv6_parse(value, v6)) { + make_ep(v6, 0, IP_VER6, &address); + if (bits == UINT32_MAX) bits = 128; + if (bits > 128) return 2; + } else return 2; + prefix = (uint8_t)bits; + } + } else if (strcmp_case(argv[i], "port", true) == 0) { + if (++i >= argc || has_port) return 2; + const char* value = argv[i]; + int32_t dash = str_has_char(value, 0, '-'); + uint32_t first = 0, last = 0; + if (dash >= 0) { + char range[24]; + if (!dash || strlen_max(value, sizeof(range)) >= sizeof(range)) return 2; + strncpy(range, value, sizeof(range)); + range[dash] = 0; + if (!parse_uint32_dec_exact(range, &first) || !parse_uint32_dec_exact(range + dash + 1, &last)) return 2; + } else { + if (!parse_uint32_dec_exact(value, &first)) return 2; + last = first; + } + if (!first || !last || first > UINT16_MAX || last > UINT16_MAX || first > last) return 2; + port_from = (uint16_t)first; + port_to = (uint16_t)last; + has_port = true; + } else return 2; + } + + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OP_ADD); + if (!req_attr(req, &req_len, NET_CTRL_EXT_ACTION, &action, sizeof(action)) || !req_attr(req, &req_len, NET_CTRL_EXT_DIRECTION, &direction, sizeof(direction)) || + !req_attr(req, &req_len, NET_CTRL_EXT_PROTOCOL, &protocol, sizeof(protocol))) return 1; + if (has_address && (!req_attr(req, &req_len, NET_CTRL_EXT_ADDRESS, &address, sizeof(address)) || !req_attr(req, &req_len, NET_CTRL_EXT_PREFIX_LEN, &prefix, sizeof(prefix)))) return 1; + if (has_port && (!req_attr(req, &req_len, NET_CTRL_EXT_PORT_FROM, &port_from, sizeof(port_from)) || !req_attr(req, &req_len, NET_CTRL_EXT_PORT_TO, &port_to, sizeof(port_to)))) return 1; + int32_t rc = req_send(req, req_len, NULL); + if (rc != SOCK_OK) print("firewall: add failed (%i)", rc); + return rc == SOCK_OK ? 0 : 1; +} + +int run_firewall(int argc, char* argv[]) { + if (argc == 2 && strcmp_case(argv[1], "--help", true) == 0) { + print_help(); + return 0; + } + + int rc = 2; + if (argc == 1 || (argc == 2 && strcmp_case(argv[1], "list", true) == 0)) rc = show_firewall(true); + else if (argc == 2 && strcmp_case(argv[1], "status", true) == 0) rc = show_firewall(false); + else if (argc == 2 && strcmp_case(argv[1], "on", true) == 0) rc = update(1, 0, 0); + else if (argc == 2 && strcmp_case(argv[1], "off", true) == 0) rc = update(0, 0, 0); + else if (argc == 2 && strcmp_case(argv[1], "clear", true) == 0) rc = delete_rule(NULL); + else if (argc == 3 && strcmp_case(argv[1], "delete", true) == 0) rc = delete_rule(argv[2]); + else if (argc == 4 && strcmp_case(argv[1], "default", true) == 0) { + uint8_t direction = 0; + if (strcmp_case(argv[2], "in", true) == 0) direction = NET_CTRL_FIREWALL_IN; + else if (strcmp_case(argv[2], "out", true) == 0) direction = NET_CTRL_FIREWALL_OUT; + + uint8_t action = 0; + if (strcmp_case(argv[3], "allow", true) == 0) action = NET_CTRL_FIREWALL_ALLOW; + else if (strcmp_case(argv[3], "deny", true) == 0) action = NET_CTRL_FIREWALL_DENY; + + if (direction && action) rc = update(UINT8_MAX, direction, action); + } else if (argc == 2 && strcmp_case(argv[1], "reset", true) == 0) { + if (delete_rule(NULL) || update(UINT8_MAX, NET_CTRL_FIREWALL_IN, NET_CTRL_FIREWALL_DENY) || update(UINT8_MAX, NET_CTRL_FIREWALL_OUT, NET_CTRL_FIREWALL_ALLOW)) rc = 1; + else rc = update(1, 0, 0); + } else if (argc >= 4 && (strcmp_case(argv[1], "allow", true) == 0 || strcmp_case(argv[1], "deny", true) == 0)) { + rc = add_rule(argc, argv); + } + + if (rc != 2) return rc; + print_help(); + return 2; +} diff --git a/kernel/tools/firewall.h b/kernel/tools/firewall.h new file mode 100644 index 00000000..2b7aaa0c --- /dev/null +++ b/kernel/tools/firewall.h @@ -0,0 +1,3 @@ +#pragma once + +int run_firewall(int argc, char *argv[]); diff --git a/kernel/tools/icmp_probe.c b/kernel/tools/icmp_probe.c new file mode 100644 index 00000000..33575347 --- /dev/null +++ b/kernel/tools/icmp_probe.c @@ -0,0 +1,237 @@ +#include "icmp_probe.h" +#include "net/checksums.h" +#include "std/memory.h" +#include "std/string.h" +#include "networking/internet_layer/icmp.h" +#include "networking/internet_layer/icmpv6.h" +#include "networking/internet_layer/ipv4.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/transport_layer/csocket.h" +#include "syscalls/syscalls.h" + +#define ICMP_PROBE_PAYLOAD_LEN 32 + +typedef struct __attribute__((packed)) { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t id; + uint16_t seq; + uint8_t payload[ICMP_PROBE_PAYLOAD_LEN]; +} icmp_probe4_echo_t; + +typedef struct __attribute__((packed)) { + icmpv6_hdr_t hdr; + uint16_t id; + uint16_t seq; + uint8_t payload[ICMP_PROBE_PAYLOAD_LEN]; +} icmp_probe6_echo_t; + +bool icmp_probe_parse_bind(const char* arg, SockBindSpec* out){ + if (!arg || !out) return false; + + memset(out, 0, sizeof(*out)); + uint32_t id = 0; + if (strncmp(arg, "l2:", 3) == 0) { + if (!parse_uint32_dec_exact(arg + 3, &id) || id == 0 || id > UINT8_MAX) return false; + out->kind = BIND_L2; + out->ifindex = (uint8_t)id; + return true; + } + + if (strncmp(arg, "l3:", 3) == 0) { + if (!parse_uint32_dec_exact(arg + 3, &id) || id == 0 || id > UINT16_MAX) return false; + out->kind = BIND_L3; + out->l3_id = (l3_id_t)id; + return true; + } + + uint32_t v4 = 0; + if (ipv4_parse(arg, &v4)) { + out->kind = BIND_IP; + out->ver = IP_VER4; + memcpy(out->ip, &v4, sizeof(v4)); + return true; + } + + if (ipv6_parse(arg, out->ip)) { + out->kind = BIND_IP; + out->ver = IP_VER6; + return true; + } + + return false; +} + +uint32_t icmp_probe_collect(const net_l4_endpoint* dst, uint16_t id, uint16_t seq, uint32_t timeout_ms, const SockBindSpec* bind, uint8_t ttl, icmp_probe_result_t* out, uint32_t max_results){ + if (!dst || !out || !max_results) return 0; + if (dst->ver != IP_VER4 && dst->ver != IP_VER6) return 0; + + SocketOptions opt; + memset(&opt, 0, sizeof(opt)); + opt.special_kind = SOCKET_SPECIAL_RAW; + opt.flags = SOCK_OPT_SPECIAL | SOCK_OPT_FILTER | SOCK_OPT_NONBLOCK; + opt.raw_filter.count = 5; + if (dst->ver == IP_VER4) { + opt.raw_filter.rules[0].type = ICMP_ECHO_REPLY; + opt.raw_filter.rules[0].code = 0; + opt.raw_filter.rules[0].flags = SOCKET_RAW_FILTER_HAS_CODE; + opt.raw_filter.rules[1].type = ICMP_DEST_UNREACH; + opt.raw_filter.rules[2].type = ICMP_TIME_EXCEEDED; + opt.raw_filter.rules[3].type = ICMP_PARAM_PROBLEM; + opt.raw_filter.rules[4].type = ICMP_REDIRECT; + } else { + opt.raw_filter.rules[0].type = ICMPV6_ECHO_REPLY; + opt.raw_filter.rules[0].code = 0; + opt.raw_filter.rules[0].flags = SOCKET_RAW_FILTER_HAS_CODE; + opt.raw_filter.rules[1].type = ICMPV6_DEST_UNREACH; + opt.raw_filter.rules[2].type = ICMPV6_PACKET_TOO_BIG; + opt.raw_filter.rules[3].type = ICMPV6_TIME_EXCEEDED; + opt.raw_filter.rules[4].type = ICMPV6_PARAM_PROBLEM; + } + if (ttl) { + opt.flags |= SOCK_OPT_TTL; + opt.ttl = ttl; + } + + protocol_t proto = dst->ver == IP_VER4 ? PROTO_ICMP : PROTO_ICMPV6; + socket_handle_t sock = create_socket(proto, &opt); + if (!sock) return 0; + + if (bind) { + SockBindSpec spec = *bind; + if (spec.kind == BIND_L3) spec.ver = dst->ver; + if (bind_socket(sock, &spec, 0) != SOCK_OK) { + close_socket(sock); + return 0; + } + } + + int64_t sent = 0; + uint32_t tx_len = 0; + if (dst->ver == IP_VER4) { + icmp_probe4_echo_t echo; + memset(&echo, 0, sizeof(echo)); + echo.type = ICMP_ECHO_REQUEST; + echo.id = bswap16(id); + echo.seq = bswap16(seq); + echo.checksum = bswap16(checksum16(&echo, sizeof(echo))); + tx_len = (uint32_t)sizeof(echo); + sent = send_to_socket(sock, dst, &echo, sizeof(echo)); + } else { + icmp_probe6_echo_t echo; + memset(&echo, 0, sizeof(echo)); + echo.hdr.type = ICMPV6_ECHO_REQUEST; + echo.id = bswap16(id); + echo.seq = bswap16(seq); + tx_len = (uint32_t)sizeof(echo); + sent = send_to_socket(sock, dst, &echo, sizeof(echo)); + } + + if (sent != (int64_t)tx_len) { + close_socket(sock); + return 0; + } + + uint32_t count = 0; + uint32_t start = (uint32_t)get_time(); + while (count < max_results) { + uint32_t now = (uint32_t)get_time(); + if (now - start >= timeout_ms) break; + + uint8_t rx[1280]; + net_l4_endpoint src; + memset(&src, 0, sizeof(src)); + int64_t n = receive_from_socket(sock, rx, sizeof(rx), &src); + if (n == SOCK_ERR_WOULDBLOCK) { + msleep(5); + continue; + } + if (n < 8) { + if (n < 0) msleep(5); + continue; + } + + uint8_t type = rx[0]; + uint8_t code = rx[1]; + uint8_t status = ICMP_PROBE_UNKNOWN_ERROR; + bool matched = false; + + if (dst->ver == IP_VER4) { + if (type == ICMP_ECHO_REPLY && rd_be16(rx + 4) == id && rd_be16(rx + 6) == seq) matched = true; + else if ((type == ICMP_TIME_EXCEEDED || type == ICMP_DEST_UNREACH || type == ICMP_PARAM_PROBLEM || type == ICMP_REDIRECT) && (uint32_t)n >= 8 + sizeof(ipv4_hdr_t) + 8) { + const uint8_t* inner_ip = rx + 8; + uint8_t ihl = (uint8_t)(inner_ip[0] & 0x0F); + uint32_t iphdr = (uint32_t)ihl * 4; + if ((inner_ip[0] >> 4) == IP_VER4 && ihl >= IP_IHL_NOOPTS && (uint32_t)n >= 8 + iphdr + 8 && inner_ip[9] == PROTO_ICMP) { + const uint8_t* inner_icmp = inner_ip + iphdr; + matched = (inner_icmp[0] == ICMP_ECHO_REQUEST || inner_icmp[0] == ICMP_ECHO_REPLY) && rd_be16(inner_icmp + 4) == id && rd_be16(inner_icmp + 6) == seq; + } + } + if (!matched) continue; + + switch (type) { + case ICMP_ECHO_REPLY: status = ICMP_PROBE_OK; break; + case ICMP_DEST_UNREACH: + switch (code) { + case 0: status = ICMP_PROBE_NET_UNREACH; break; + case 1: status = ICMP_PROBE_HOST_UNREACH; break; + case 2: status = ICMP_PROBE_PROTO_UNREACH; break; + case 3: status = ICMP_PROBE_PORT_UNREACH; break; + case 4: status = ICMP_PROBE_FRAG_NEEDED; break; + case 5: status = ICMP_PROBE_SRC_ROUTE_FAILED; break; + case 13: status = ICMP_PROBE_ADMIN_PROHIBITED; break; + default: status = ICMP_PROBE_UNKNOWN_ERROR; break; + } + break; + case ICMP_TIME_EXCEEDED: status = ICMP_PROBE_TTL_EXPIRED; break; + case ICMP_PARAM_PROBLEM: status = ICMP_PROBE_PARAM_PROBLEM; break; + case ICMP_REDIRECT: status = ICMP_PROBE_REDIRECT; break; + default: break; + } + } else { + if (type == ICMPV6_ECHO_REPLY && rd_be16(rx + 4) == id && rd_be16(rx + 6) == seq) matched = true; + else if ((type == ICMPV6_DEST_UNREACH || type == ICMPV6_PACKET_TOO_BIG || type == ICMPV6_TIME_EXCEEDED || type == ICMPV6_PARAM_PROBLEM) && (uint32_t)n >= 8 + sizeof(ipv6_hdr_t) + 8) { + ipv6_hdr_t inner; + memcpy(&inner, rx + 8, sizeof(inner)); + uint32_t v = bswap32(inner.ver_tc_fl); + const uint8_t* inner_icmp = rx + 8 + sizeof(ipv6_hdr_t); + matched = (v >> 28) == IP_VER6 && inner.next_header == PROTO_ICMPV6 && inner_icmp[0] == ICMPV6_ECHO_REQUEST && rd_be16(inner_icmp + 4) == id && rd_be16(inner_icmp + 6) == seq; + } + if (!matched) continue; + + switch (type) { //b + case ICMPV6_ECHO_REPLY: status = ICMP_PROBE_OK; break; + case ICMPV6_DEST_UNREACH: + switch (code) { + case 0:status = ICMP_PROBE_NET_UNREACH; break; + case 1: + case 2:status = ICMP_PROBE_ADMIN_PROHIBITED; break; + case 3:status = ICMP_PROBE_HOST_UNREACH; break; + case 4:status = ICMP_PROBE_PORT_UNREACH; break; + default: status = ICMP_PROBE_UNKNOWN_ERROR; break; + } + break; + case ICMPV6_PACKET_TOO_BIG: status = ICMP_PROBE_FRAG_NEEDED; break; + case ICMPV6_TIME_EXCEEDED: status = ICMP_PROBE_TTL_EXPIRED; break; + case ICMPV6_PARAM_PROBLEM: status = ICMP_PROBE_PARAM_PROBLEM; break; + default: break; + } + } + + icmp_probe_result_t* r = &out[count++]; + memset(r, 0, sizeof(*r)); + r->responder = src; + r->icmp_type = type; + r->icmp_code = code; + r->status = status; + now = (uint32_t)get_time(); + r->rtt_ms = now >= start ? now - start : 0; + if (max_results == 1) break; + } + + close_socket(sock); + return count; +} diff --git a/kernel/tools/icmp_probe.h b/kernel/tools/icmp_probe.h new file mode 100644 index 00000000..3a3600f7 --- /dev/null +++ b/kernel/tools/icmp_probe.h @@ -0,0 +1,41 @@ +#pragma once + +#include "types.h" +#include "net/network_types.h" +#include "net/socket_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ICMP_PROBE_OK = 0, + ICMP_PROBE_TIMEOUT = 1, + ICMP_PROBE_NET_UNREACH = 2, + ICMP_PROBE_HOST_UNREACH = 3, + ICMP_PROBE_PROTO_UNREACH = 4, + ICMP_PROBE_PORT_UNREACH = 5, + ICMP_PROBE_FRAG_NEEDED = 6, + ICMP_PROBE_SRC_ROUTE_FAILED = 7, + ICMP_PROBE_ADMIN_PROHIBITED = 8, + ICMP_PROBE_TTL_EXPIRED = 9, + ICMP_PROBE_PARAM_PROBLEM = 10, + ICMP_PROBE_REDIRECT = 11, + ICMP_PROBE_UNKNOWN_ERROR = 255 +} icmp_probe_status_t; + +typedef struct { + uint32_t rtt_ms; + net_l4_endpoint responder; + uint8_t status; + uint8_t icmp_type; + uint8_t icmp_code; + uint8_t reserved; +} icmp_probe_result_t; + +bool icmp_probe_parse_bind(const char* arg, SockBindSpec* out); +uint32_t icmp_probe_collect(const net_l4_endpoint* dst, uint16_t id, uint16_t seq, uint32_t timeout_ms, const SockBindSpec* bind, uint8_t ttl, icmp_probe_result_t* out, uint32_t max_results); + +#ifdef __cplusplus +} +#endif diff --git a/kernel/tools/ip.c b/kernel/tools/ip.c new file mode 100644 index 00000000..e1644dbf --- /dev/null +++ b/kernel/tools/ip.c @@ -0,0 +1,370 @@ +#include "ip.h" +#include "types.h" +#include "console/kio.h" +#include "std/string.h" +#include "std/memory.h" +#include "alloc/allocate.h" +#include "net/net_ctrl.h" +#include "net/socket_types.h" +#include "networking/network.h" +#include "networking/transport_layer/net_ctrl.h" +#include "networking/transport_layer/trans_utils.h" +#include "networking/internet_layer/ipv4_utils.h" +#include "networking/internet_layer/ipv6_utils.h" +#include "networking/link_layer/link_utils.h" + +static void print_help(void) { + print("Usage:\t ip COMMAND [ARGS]"); + print("show or configure networking\n"); + print("Commands:"); + print("\t link\t interfaces"); + print("\t addr\t addresses"); + print("\t route\t routes"); + print("\t neigh\t neighbors"); + print("\t link add IFACE type vlan id VLAN dev PARENT"); + print("\t link del IFACE\n"); + print("Args:"); + print("\t IFACE\t interface name"); + print("\t PREFIX\t ADDRESS/LENGTH"); + print("\t ADDRESS\t v4|v6 address"); + print("\t GATEWAY\t next hop"); + print("\t METRIC\t route metric\n"); + print("Options:"); + print("\t --help\t help"); +} + +static void req_init(uint8_t req[256], uint32_t* req_len, NetCtrlObject object, NetCtrlOp op) { + memset(req, 0, 256); + NetCtrlMsg* msg = (NetCtrlMsg*)req; + msg->object = object; + msg->op = op; + msg->flags = NET_CTRL_F_REQUEST; + *req_len = sizeof(NetCtrlMsg); +} + +static bool req_attr(uint8_t req[256], uint32_t* req_len, NetCtrlExtAttr ext, const void* data, uint16_t len) { + if (!data || *req_len + sizeof(NetCtrlAttr) + len > 256) return false; + NetCtrlAttr attr = { .ext = ext, .length = len }; + memcpy(req + *req_len, &attr, sizeof(attr)); + *req_len += sizeof(attr); + memcpy(req + *req_len, data, len); + *req_len += len; + return true; +} + +static int32_t req_send(uint8_t req[256], uint32_t req_len, uint8_t** response) { + if (response) *response = NULL; + ((NetCtrlMsg*)req)->length = req_len; + uint8_t* out = NULL; + uint32_t out_len = 0; + int32_t rc = net_ctrl_dispatch(req, req_len, &out, &out_len); + if (rc != SOCK_OK || !out || out_len < sizeof(NetCtrlMsg)) { + if (out) release(out); + return rc == SOCK_OK ? SOCK_ERR_SYS : rc; + } + rc = ((NetCtrlMsg*)out)->status; + if (rc != SOCK_OK || !response) release(out); + else *response = out; + return rc; +} + +static bool parse_prefix(const char* s, net_l4_endpoint* ep, uint8_t* prefix) { + char buf[96]; + if (!s || !ep || !prefix || strlen_max(s, sizeof(buf)) >= sizeof(buf)) return false; + int32_t slash = str_has_char(s, 0, '/'); + if (slash <= 0) return false; + strncpy(buf, s, sizeof(buf)); + buf[slash] = 0; + + uint32_t bits = 0, v4 = 0; + uint8_t v6[16]; + if (!parse_uint32_dec_exact(buf + slash + 1, &bits)) return false; + if (ipv4_parse(buf, &v4)) make_ep(&v4, 0, IP_VER4, ep); + else if (ipv6_parse(buf, v6)) make_ep(v6, 0, IP_VER6, ep); + else return false; + if ((ep->ver == IP_VER4 && bits > 32) || (ep->ver == IP_VER6 && bits > 128)) return false; + *prefix = (uint8_t)bits; + return true; +} + +static int show_link(void) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_LINK, NET_CTRL_OP_GET); + uint8_t* response = NULL; + int32_t rc = req_send(req, req_len, &response); + if (rc != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg) / sizeof(NetCtrlLinkInfo); + NetCtrlLinkInfo* links = NET_CTRL_MSG_DATA(msg); + req_init(req, &req_len, NET_CTRL_OBJ_VLAN, NET_CTRL_OP_GET); + uint8_t* vlans_response = 0; + if (req_send(req, req_len, &vlans_response) != SOCK_OK) { + release(response); + return 1; + } + NetCtrlMsg* vlan_msg = (NetCtrlMsg*)vlans_response; + uint32_t vlan_count = NET_CTRL_MSG_PAYLOAD_LEN(vlan_msg) / sizeof(NetCtrlVlanInfo); + NetCtrlVlanInfo* vlans = NET_CTRL_MSG_DATA(vlan_msg); + for (uint32_t i = 0; i < count; i++){ + const NetCtrlVlanInfo* vlan = NULL; + for (uint32_t j = 0; j < vlan_count; j++) if (vlans[j].ifindex == links[i].ifindex) { + vlan = &vlans[j]; + break; + } + if (vlan) print("%u: %s: %s mtu %u metric %u vlan %u parent %s", links[i].ifindex, links[i].name, + links[i].up ? "UP" : "DOWN", links[i].mtu, links[i].metric, vlan->vlan_id, vlan->parent); + else print("%u: %s: %s mtu %u metric %u", links[i].ifindex, links[i].name, + links[i].up ? "UP" : "DOWN", links[i].mtu, links[i].metric); + } + release(vlans_response); + release(response); + return 0; +} + +static int set_link(int argc, char* argv[]) { + if (argc != 5) return 2; + uint8_t state; + if (strcmp_case(argv[4], "up", true) == 0) state = 1; + else if (strcmp_case(argv[4], "down", true) == 0) state = 0; + else return 2; + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_LINK, NET_CTRL_OP_UPD); + size_t ifname_len = strlen_max(argv[3], 17); + if (!ifname_len || ifname_len > 16 || !req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, argv[3], (uint16_t)ifname_len) || !req_attr(req, &req_len, NET_CTRL_EXT_STATE, &state, sizeof(state))) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int add_vlan(int argc, char *argv[]) { + if (argc != 10 || strcmp_case(argv[4], "type", true) != 0 || strcmp_case(argv[5], "vlan", true) != 0 || + strcmp_case(argv[6], "id", true) != 0 || strcmp_case(argv[8], "dev", true) != 0) return 2; + uint32_t vlan_id = 0; + if (!parse_uint32_dec_exact(argv[7], &vlan_id) || !vlan_id || vlan_id > 4094) return 2; + size_t name_len = strlen_max(argv[3], 16); + size_t parent_len = strlen_max(argv[9], 16); + if (!name_len || name_len >= 16 || !parent_len || parent_len >= 16) return 2; + uint16_t tag = vlan_id; + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_LINK, NET_CTRL_OP_ADD); + if (!req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, argv[3], name_len) || !req_attr(req, &req_len, NET_CTRL_EXT_PARENT_IFNAME, argv[9], parent_len) || + !req_attr(req, &req_len, NET_CTRL_EXT_VLAN_ID, &tag, sizeof(tag))) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int delete_vlan(int argc, char *argv[]) { + if (argc != 4) return 2; + size_t len = strlen_max(argv[3], 16); + if (!len || len >= 16) return 2; + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_LINK, NET_CTRL_OP_DEL); + if (!req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, argv[3], len)) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int show_addr(void) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_ADDR, NET_CTRL_OP_GET); + uint8_t* response = NULL; + if (req_send(req, req_len, &response) != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg) / sizeof(NetCtrlAddrInfo); + NetCtrlAddrInfo* addrs = NET_CTRL_MSG_DATA(msg); + for (uint32_t i = 0; i < count; i++) { + char addr[64]; + net_ep_split(&addrs[i].prefix.address, addr, sizeof(addr), NULL, NULL); + const char* name = network_get_ifname(addrs[i].prefix.ifindex); + print("%s %s/%u dev %s", addrs[i].prefix.address.ver == IP_VER6 ? "inet6" : "inet", addr, addrs[i].prefix.prefix_len, name ? name : "?"); + } + release(response); + return 0; +} + +static int addr_add(int argc, char* argv[]) { + if (argc != 6 || strcmp_case(argv[4], "dev", true) != 0) return 2; + net_l4_endpoint ep; + uint8_t prefix; + if (!parse_prefix(argv[3], &ep, &prefix)) return 2; + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_ADDR, NET_CTRL_OP_ADD); //b + size_t ifname_len = strlen_max(argv[5], 17); + if (!ifname_len || ifname_len > 16 || !req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, argv[5], (uint16_t)ifname_len) || + !req_attr(req, &req_len, NET_CTRL_EXT_ADDRESS, &ep, sizeof(ep)) || + !req_attr(req, &req_len, NET_CTRL_EXT_PREFIX_LEN, &prefix, sizeof(prefix))) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int addr_del(int argc, char* argv[]) { + if (argc != 6 || strcmp_case(argv[4], "dev", true) != 0) return 2; + net_l4_endpoint ep = {0}; + uint32_t v4 = 0; + uint8_t v6[16]; + if (ipv4_parse(argv[3], &v4)) make_ep(&v4, 0, IP_VER4, &ep); + else if (ipv6_parse(argv[3], v6)) make_ep(v6, 0, IP_VER6, &ep); + else return 2; + + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_ADDR, NET_CTRL_OP_GET); + size_t ifname_len = strlen_max(argv[5], 17); + if (!ifname_len || ifname_len > 16 || !req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, argv[5], (uint16_t)ifname_len) || !req_attr(req, &req_len, NET_CTRL_EXT_ADDRESS, &ep, sizeof(ep))) return 1; + uint8_t* response = NULL; + if (req_send(req, req_len, &response) != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg)/sizeof(NetCtrlAddrInfo); + NetCtrlAddrInfo* addrs = NET_CTRL_MSG_DATA(msg); + l3_id_t l3_id = 0; + for (uint32_t i = 0; i < count; i++) { + if (net_ep_equal(&addrs[i].prefix.address, &ep)) { + l3_id = addrs[i].prefix.l3_id; + break; + } + } + release(response); + if (!l3_id) return 1; + req_init(req, &req_len, NET_CTRL_OBJ_ADDR, NET_CTRL_OP_DEL); + if (!req_attr(req, &req_len, NET_CTRL_EXT_L3_ID, &l3_id, sizeof(l3_id))) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int show_route(void) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_ROUTE, NET_CTRL_OP_GET); + uint8_t* response = NULL; + if (req_send(req, req_len, &response) != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg) / sizeof(NetCtrlRouteInfo); + NetCtrlRouteInfo* routes = NET_CTRL_MSG_DATA(msg); + for (uint32_t i = 0; i < count; i++) { + char dst[64], gw[64]; + net_ep_split(&routes[i].prefix.address, dst, sizeof(dst), NULL, NULL); + net_ep_split(&routes[i].prefix.gateway, gw, sizeof(gw), NULL, NULL); + bool def = routes[i].prefix.prefix_len == 0; + uint32_t gateway_v4 = 0; + memcpy(&gateway_v4, routes[i].prefix.gateway.ip, sizeof(gateway_v4)); + bool has_gw = routes[i].prefix.gateway.ver == IP_VER4 ? !ipv4_is_unspecified(gateway_v4) : !ipv6_is_unspecified(routes[i].prefix.gateway.ip); + const char* name = network_get_ifname(routes[i].prefix.ifindex); + if (!name) name = "?"; + if (def && has_gw) print("default via %s dev %s metric %u", gw, name, routes[i].metric); + else if (def) print("default dev %s metric %u", name, routes[i].metric); + else if (has_gw) print("%s/%u via %s dev %s metric %u", dst, routes[i].prefix.prefix_len, gw, name, routes[i].metric); + else print("%s/%u dev %s metric %u", dst, routes[i].prefix.prefix_len, name, routes[i].metric); + } + release(response); + return 0; +} + +static int route_change(int argc, char* argv[], bool add) { + if (argc < 6) return 2; + net_l4_endpoint network = {0}, gateway = {0}; + uint8_t prefix = 0; + bool has_gateway = false; + const char* dev = NULL; + uint16_t metric = 0; + + int i = 3; + bool def = strcmp_case(argv[i], "default", true) == 0; + if (def) i++; + else if (!parse_prefix(argv[i++], &network, &prefix)) return 2; + + while (i < argc) { + if (strcmp_case(argv[i], "via", true) == 0) { + if (++i >= argc || has_gateway) return 2; + uint32_t v4 = 0; + uint8_t v6[16]; + if (ipv4_parse(argv[i], &v4)) make_ep(&v4, 0, IP_VER4, &gateway); + else if (ipv6_parse(argv[i], v6)) make_ep(v6, 0, IP_VER6, &gateway); + else return 2; + i++; + has_gateway = true; + } else if (strcmp_case(argv[i], "dev", true) == 0) { + if (++i >= argc || dev) return 2; + dev = argv[i++]; + } else if (add && strcmp_case(argv[i], "metric", true) == 0) { + uint32_t value = 0; + if (++i >= argc || !parse_uint32_dec_exact(argv[i++], &value) || value > UINT16_MAX) return 2; + metric = (uint16_t)value; + } else return 2; + } + if (!dev || (def && !has_gateway) || (!add && !def && has_gateway)) return 2; + if (def) network.ver = gateway.ver; + if (has_gateway && gateway.ver != network.ver) return 2; + + l3_id_t l3_id = 0; + bool linklocal = network.ver == IP_VER6 && !def && ipv6_is_linklocal(network.ip); + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_ADDR, NET_CTRL_OP_GET); + size_t ifname_len = strlen_max(dev, 17); + if (!ifname_len || ifname_len > 16 || !req_attr(req, &req_len, NET_CTRL_EXT_IFNAME, dev, (uint16_t)ifname_len)) return 1; + uint8_t* response = NULL; + if (req_send(req, req_len, &response) != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg) / sizeof(NetCtrlAddrInfo); + NetCtrlAddrInfo* addrs = NET_CTRL_MSG_DATA(msg); + for (uint32_t j = 0; j < count; j++) { + if (addrs[j].prefix.address.ver != network.ver) continue; + if (network.ver == IP_VER6 && ipv6_is_linklocal(addrs[j].prefix.address.ip) != linklocal) continue; + l3_id = addrs[j].prefix.l3_id; + break; + } + release(response); + if (!l3_id) return 1; + + req_init(req, &req_len, NET_CTRL_OBJ_ROUTE, add ? NET_CTRL_OP_ADD : NET_CTRL_OP_DEL); + if (!req_attr(req, &req_len, NET_CTRL_EXT_L3_ID, &l3_id, sizeof(l3_id)) || + !req_attr(req, &req_len, NET_CTRL_EXT_ADDRESS, &network, sizeof(network)) || + !req_attr(req, &req_len, NET_CTRL_EXT_PREFIX_LEN, &prefix, sizeof(prefix))) return 1; + if (has_gateway && !req_attr(req, &req_len, NET_CTRL_EXT_GATEWAY, &gateway, sizeof(gateway))) return 1; + if (add && metric && !req_attr(req, &req_len, NET_CTRL_EXT_METRIC, &metric, sizeof(metric))) return 1; + return req_send(req, req_len, NULL) == SOCK_OK ? 0 : 1; +} + +static int show_neigh(void) { + uint8_t req[256]; + uint32_t req_len; + req_init(req, &req_len, NET_CTRL_OBJ_NEIGH, NET_CTRL_OP_GET); + uint8_t* response = NULL; + if (req_send(req, req_len, &response) != SOCK_OK) return 1; + NetCtrlMsg* msg = (NetCtrlMsg*)response; + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(msg) / sizeof(NetCtrlNeighInfo); + NetCtrlNeighInfo* entries = NET_CTRL_MSG_DATA(msg); + for (uint32_t i = 0; i < count; i++) { + char addr[64], mac[18]; + net_ep_split(&entries[i].address, addr, sizeof(addr), NULL, NULL); + mac_to_string(entries[i].mac, mac); + const char* name = network_get_ifname(entries[i].ifindex); + print("%s dev %s lladdr %s%s", addr, name ? name : "?", mac, (entries[i].flags & NET_CTRL_NEIGH_F_STATIC) ? " static" : ""); + } + release(response); + return 0; +} + +int run_ip(int argc, char* argv[]) { + if (argc == 2 && strcmp_case(argv[1], "--help", true) == 0) { + print_help(); + return 0; + } + + int rc = 2; + if (argc == 2 && strcmp_case(argv[1], "link", true) == 0) rc = show_link(); + else if (argc >= 3 && strcmp_case(argv[1], "link", true) == 0 && strcmp_case(argv[2], "set", true) == 0) rc = set_link(argc, argv); + else if (argc >= 3 && strcmp_case(argv[1], "link", true) == 0 && strcmp_case(argv[2], "add", true) == 0) rc = add_vlan(argc, argv); + else if (argc >= 3 && strcmp_case(argv[1], "link", true) == 0 && strcmp_case(argv[2], "del", true) == 0) rc = delete_vlan(argc, argv); + else if (argc == 2 && strcmp_case(argv[1], "addr", true) == 0) rc = show_addr(); + else if (argc >= 3 && strcmp_case(argv[1], "addr", true) == 0 && strcmp_case(argv[2], "add", true) == 0) rc = addr_add(argc, argv); + else if (argc >= 3 && strcmp_case(argv[1], "addr", true) == 0 && strcmp_case(argv[2], "del", true) == 0) rc = addr_del(argc, argv); + else if (argc == 2 && strcmp_case(argv[1], "route", true) == 0) rc = show_route(); + else if (argc >= 3 && strcmp_case(argv[1], "route", true) == 0 && strcmp_case(argv[2], "add", true) == 0) rc = route_change(argc, argv, true); + else if (argc >= 3 && strcmp_case(argv[1], "route", true) == 0 && strcmp_case(argv[2], "del", true) == 0) rc = route_change(argc, argv, false); + else if (argc == 2 && strcmp_case(argv[1], "neigh", true) == 0) rc = show_neigh(); + + if (rc != 2) return rc; + print_help(); + return 2; +} diff --git a/kernel/tools/ip.h b/kernel/tools/ip.h new file mode 100644 index 00000000..c2e49a76 --- /dev/null +++ b/kernel/tools/ip.h @@ -0,0 +1,3 @@ +#pragma once + +int run_ip(int argc, char* argv[]); diff --git a/kernel/tools/ping.c b/kernel/tools/ping.c index 157dd36a..f3c07b98 100644 --- a/kernel/tools/ping.c +++ b/kernel/tools/ping.c @@ -1,21 +1,23 @@ #include "ping.h" -#include "networking/internet_layer/icmp.h" -#include "net/network_types.h" +#include "icmp_probe.h" #include "std/string.h" #include "std/memory.h" #include "types.h" #include "console/kio.h" -#include "filesystem/filesystem.h" #include "process/scheduler.h" #include "syscalls/syscalls.h" -#include "networking/internet_layer/ipv4.h" -#include "networking/internet_layer/ipv4_route.h" #include "networking/application_layer/dns/dns.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "networking/internet_layer/icmpv6.h" -//TODO add a print variant that does not append a newline automatically in serial -//std printf behavior is useful here since output requires explicit \n +#include "networking/transport_layer/trans_utils.h" + +#define PING_MAX_REPLIES 16 + +typedef struct { + const char *host; + SockBindSpec bind; + bool bind_set; +} ping_addressing_t; typedef struct { ip_version_t ver; @@ -23,20 +25,33 @@ typedef struct { uint32_t timeout_ms; uint32_t interval_ms; uint32_t ttl; - uint32_t src_ip; - bool src_set; - const char *host; + ping_addressing_t addr; } ping_opts_t; +static void print_help(void) { + print("Usage:\t ping [OPTION] HOST"); + print("send echo requests\n"); + print("Args:"); + print("\t HOST\t host|ip"); + print("\t SOURCE\t IP|l2:ID|l3:ID\n"); + print("Options:"); + print("\t -4\t ipv4"); + print("\t -6\t ipv6"); + print("\t -n COUNT\t request count (4)"); + print("\t -w TIMEOUT\t timeout ms (1000)"); + print("\t -i INTERVAL\t interval ms (1000)"); + print("\t -t TTL\t ttl|hop limit (64)"); + print("\t -s SOURCE\t source"); + print("\t --help\t help"); +} + static bool parse_args(int argc, char *argv[], ping_opts_t *o) { o->ver = IP_VER4; o->count = 4; o->timeout_ms = 1000; o->interval_ms = 1000; o->ttl = 64; - o->src_ip = 0; - o->src_set = false; - o->host = NULL; + memset(&o->addr, 0, sizeof(o->addr)); for (int i = 1; i < argc; ++i) { const char *a = argv[i]; @@ -56,203 +71,205 @@ static bool parse_args(int argc, char *argv[], ping_opts_t *o) { if (++i >= argc) return false; if (!parse_uint32_dec(argv[i], &o->ttl)) return false; } else if (strcmp_case(a, "-s",true) == 0) { - if (++i >= argc) return false; - uint32_t src = 0; - if (!ipv4_parse(argv[i], &src)) return false; - o->src_ip = src; - o->src_set = true; - } - else return false; + if (++i >= argc || o->addr.bind_set) return false; + if (!icmp_probe_parse_bind(argv[i], &o->addr.bind)) return false; + o->addr.bind_set = true; + } else return false; } else { - if (o->host) return false; - o->host = a; + if (o->addr.host) return false; + o->addr.host = a; } } - if (!o->host) return false; + if (!o->addr.host) return false; return true; } static const char *status_to_msg(uint8_t st) { switch (st) { - case PING_TIMEOUT: return "Request timed out."; - case PING_NET_UNREACH: return "Destination Net Unreachable."; - case PING_HOST_UNREACH: return "Destination Host Unreachable."; - case PING_PROTO_UNREACH: return "Protocol Unreachable."; - case PING_PORT_UNREACH: return "Port Unreachable."; - case PING_FRAG_NEEDED: return "Fragmentation Needed."; - case PING_SRC_ROUTE_FAILED: return "Source Route Failed."; - case PING_ADMIN_PROHIBITED: return "Administratively Prohibited."; - case PING_TTL_EXPIRED: return "Time To Live exceeded."; - case PING_PARAM_PROBLEM: return "Parameter Problem."; - case PING_REDIRECT: return "Redirect received."; - default: return "No reply (unknown error)."; + case ICMP_PROBE_TIMEOUT: return "Request timed out."; + case ICMP_PROBE_NET_UNREACH: return "Destination Net Unreachable."; + case ICMP_PROBE_HOST_UNREACH: return "Destination Host Unreachable."; + case ICMP_PROBE_PROTO_UNREACH: return "Protocol Unreachable."; + case ICMP_PROBE_PORT_UNREACH: return "Port Unreachable."; + case ICMP_PROBE_FRAG_NEEDED: return "Fragmentation Needed."; + case ICMP_PROBE_SRC_ROUTE_FAILED: return "Source Route Failed."; + case ICMP_PROBE_ADMIN_PROHIBITED: return "Administratively Prohibited."; + case ICMP_PROBE_TTL_EXPIRED: return "Time To Live exceeded."; + case ICMP_PROBE_PARAM_PROBLEM: return "Parameter Problem."; + case ICMP_PROBE_REDIRECT: return "Redirect received."; + default: return "No reply (unknown error)."; } } static int ping_v4(const ping_opts_t *o) { - const char *host = o->host; + const char *host = o->addr.host; - uint32_t dst_ip_be = 0; - bool is_lit = ipv4_parse(host, &dst_ip_be); + uint32_t dst_ip = 0; + bool is_lit = ipv4_parse(host, &dst_ip); if (!is_lit) { uint32_t r = 0; dns_result_t dr = dns_resolve_a(host, &r, DNS_USE_BOTH, o->timeout_ms); if (dr != DNS_OK) { - print("ping: dns lookup failed (%d) for '%s'\n", (int)dr, host); + print("ping: dns lookup failed (%d) for '%s'", (int)dr, host); return 2; } - dst_ip_be = r; + dst_ip = r; } - char ipstr[16]; - ipv4_to_string(dst_ip_be, ipstr); + if (ipv4_is_limited_broadcast(dst_ip) && (!o->addr.bind_set || o->addr.bind.kind == BIND_L2)) { + print("ping: limited broadcast requires -s local_ipv4 or l3:id"); + return 2; + } - print("PING %s (%s) with 32 bytes of data:\n", host, ipstr); + char ipstr[16]; + ipv4_to_string(dst_ip, ipstr); + print("PING %s (%s) with 32 bytes of data:", host, ipstr); uint32_t sent = 0, received = 0, min_ms = UINT32_MAX, max_ms = 0; uint64_t sum_ms = 0; uint16_t id = (uint16_t)(get_current_proc_pid() & 0xFFFF); uint16_t seq_base = (uint16_t)(get_time() & 0xFFFF); - - ipv4_tx_opts_t txo = {0}; - const ipv4_tx_opts_t *txop = NULL; - if (o->src_set) { - l3_ipv4_interface_t *l3 = l3_ipv4_find_by_ip(o->src_ip); - if (!l3) { - print("ping: invalid source (no local ip match)\n"); - return 2; - } - txo.index = (uint8_t)l3->l3_id; - txo.scope = IP_TX_BOUND_L3; - txop = &txo; - } + const SockBindSpec* bind = o->addr.bind_set ? &o->addr.bind : NULL; + bool multi = ipv4_is_multicast(dst_ip) || ipv4_is_limited_broadcast(dst_ip); + uint32_t max_results = multi ? PING_MAX_REPLIES : 1; + net_l4_endpoint dst; + make_ep(&dst_ip, 0, IP_VER4, &dst); for (uint32_t i = 0; i < o->count; i++) { - ++sent; + sent++; uint16_t seq = (uint16_t)(seq_base + i); - - ping_result_t res = {0}; - bool ok = icmp_ping(dst_ip_be, id, seq, o->timeout_ms, txop, (uint8_t)o->ttl, &res); - - if (ok) { - ++received; - uint32_t rtt = res.rtt_ms; - if (rtt < min_ms) min_ms = rtt; - if (rtt > max_ms) max_ms = rtt; - sum_ms += rtt; - print("Reply from %s: bytes=32 time=%ums\n", ipstr, rtt); - } else { - print("%s\n", status_to_msg(res.status)); - } + icmp_probe_result_t res[PING_MAX_REPLIES]; + uint32_t n = icmp_probe_collect(&dst, id, seq, o->timeout_ms, bind, (uint8_t)o->ttl, res, max_results); + + if (n) { + for (uint32_t j = 0; j < n; j++) { + if (res[j].status == ICMP_PROBE_OK) { + received++; + uint32_t rtt = res[j].rtt_ms; + if (rtt < min_ms) min_ms = rtt; + if (rtt > max_ms) max_ms = rtt; + sum_ms += rtt; + char rip[64]; + net_ep_split(&res[j].responder, rip, (int)sizeof(rip), NULL, NULL); + print("Reply from %s: bytes=32 time=%ums", rip, rtt); + } else print("%s", status_to_msg(res[j].status)); + } + } else print("%s", status_to_msg(ICMP_PROBE_TIMEOUT)); if (i + 1 < o->count) msleep(o->interval_ms); } - print("\n"); - print("--- %s ping statistics ---\n", host); + print(""); + print("--- %s ping statistics ---", host); - uint32_t loss = (sent == 0) ? 0 : (uint32_t)((((uint64_t)(sent - received)) * 100) / sent); + uint32_t loss = (sent == 0 || received >= sent) ? 0 : (uint32_t)((((uint64_t)(sent - received)) * 100) / sent); uint32_t total_time = (o->count > 0) ? (o->count - 1) * o->interval_ms : 0; - print("%u packets transmitted, %u received, %u%% packet loss, time %ums\n", sent, received, loss, total_time); + print("%u packets transmitted, %u received, %u%% packet loss, time %ums", sent, received, loss, total_time); if (received > 0) { uint32_t avg = (uint32_t)(sum_ms / received); if (min_ms == UINT32_MAX) min_ms = avg; - print("rtt min/avg/max = %u/%u/%u ms\n", min_ms, avg, max_ms); + print("rtt min/avg/max = %u/%u/%u ms", min_ms, avg, max_ms); } - return (received > 0) ? 0 : 1; + return received > 0 ? 0 : 1; } static int ping_v6(const ping_opts_t *o) { - const char *host = o->host; - - uint8_t dst6[16] ={0}; - bool is_lit = ipv6_parse(host, dst6); + const char *host = o->addr.host; + uint8_t dst_ip[16] ={0}; + bool is_lit = ipv6_parse(host, dst_ip); if (!is_lit) { - dns_result_t dr = dns_resolve_aaaa(host, dst6, DNS_USE_BOTH, o->timeout_ms); + dns_result_t dr = dns_resolve_aaaa(host, dst_ip, DNS_USE_BOTH, o->timeout_ms); if (dr != DNS_OK) { - print("ping: dns lookup failed (%d) for '%s'\n",(int)dr, host); + print("ping: dns lookup failed (%d) for '%s'",(int)dr, host); return 2; } } + if (ipv6_is_linkscope_mcast(dst_ip) && !o->addr.bind_set) { + print("ping: IPv6 link-local multicast requires -s local_ipv6, l2:id or l3:id"); + return 2; + } + char ipstr[64]; - ipv6_to_string(dst6, ipstr, (int)sizeof(ipstr)); + ipv6_to_string(dst_ip, ipstr, (int)sizeof(ipstr)); - print("PING %s (%s) with 32 bytes of data:\n", host, ipstr); + print("PING %s (%s) with 32 bytes of data:", host, ipstr); uint32_t sent = 0, received = 0, min_ms = UINT32_MAX, max_ms = 0; uint64_t sum_ms = 0; uint16_t id = (uint16_t)(get_current_proc_pid() & 0xFFFF); uint16_t seq_base = (uint16_t)(get_time() & 0xFFFF); + const SockBindSpec* bind = o->addr.bind_set ? &o->addr.bind : NULL; + uint32_t max_results = ipv6_is_multicast(dst_ip) ? PING_MAX_REPLIES : 1; + net_l4_endpoint dst; + make_ep(dst_ip, 0, IP_VER6, &dst); for (uint32_t i = 0; i < o->count; i++) { - ++sent; + sent++; uint16_t seq = (uint16_t)(seq_base + i); - - ping6_result_t res = {0}; - bool ok = icmpv6_ping(dst6, id, seq, o->timeout_ms, NULL, (uint8_t)o->ttl, &res); - - if (ok) { - ++received; - uint32_t rtt = res.rtt_ms; - if (rtt < min_ms) min_ms = rtt; - if (rtt > max_ms) max_ms = rtt; - sum_ms += rtt; - print("Reply from %s: bytes=32 time=%ums\n", ipstr, rtt); - } else { - print("%s\n", status_to_msg(res.status)); - } + icmp_probe_result_t res[PING_MAX_REPLIES]; + uint32_t n = icmp_probe_collect(&dst, id, seq, o->timeout_ms, bind, (uint8_t)o->ttl, res, max_results); + + if (n) { + for (uint32_t j = 0; j < n; j++) { + if (res[j].status == ICMP_PROBE_OK) { + received++; + uint32_t rtt = res[j].rtt_ms; + if (rtt < min_ms) min_ms = rtt; + if (rtt > max_ms) max_ms = rtt; + sum_ms += rtt; + char rip[64]; + net_ep_split(&res[j].responder, rip, (int)sizeof(rip), NULL, NULL); + print("Reply from %s: bytes=32 time=%ums", rip, rtt); + } else print("%s", status_to_msg(res[j].status)); + } + } else print("%s", status_to_msg(ICMP_PROBE_TIMEOUT)); if (i + 1 < o->count) msleep(o->interval_ms); } - print("\n"); + print(""); - print("--- %s ping statistics ---\n", host); + print("--- %s ping statistics ---", host); - uint32_t loss = (sent == 0) ? 0 : (uint32_t)((((uint64_t)(sent - received)) * 100) / sent); + uint32_t loss = (sent == 0 || received >= sent) ? 0 : (uint32_t)((((uint64_t)(sent - received)) * 100) / sent); uint32_t total_time = (o->count > 0) ? (o->count - 1) * o->interval_ms : 0; - print("%u packets transmitted, %u received, %u%% packet loss, time %ums\n", sent, received, loss, total_time); + print("%u packets transmitted, %u received, %u%% packet loss, time %ums", sent, received, loss, total_time); if (received > 0) { uint32_t avg = (uint32_t)(sum_ms / received); if (min_ms == UINT32_MAX) min_ms = avg; - print("rtt min/avg/max = %u/%u/%u ms\n", min_ms, avg, max_ms); + print("rtt min/avg/max = %u/%u/%u ms", min_ms, avg, max_ms); } return (received > 0) ? 0 : 1; } int run_ping(int argc, char *argv[]) { + if (argc == 2 && strcmp_case(argv[1], "--help", true) == 0) { + print_help(); + return 0; + } + ping_opts_t opts; if (!parse_args(argc, argv, &opts)) { - print("usage: ping [-4/-6] [-n times] [-w timeout] [-i interval] [-t TTL] [-s src_local_ip] host\n"); + print_help(); return 2; } - if (opts.ver == IP_VER6 && opts.src_set) { - print("ping: -s is only supported for IPv4\n"); + if (opts.addr.bind_set && opts.addr.bind.kind == BIND_IP && opts.addr.bind.ver != opts.ver) { + print("ping: source address version doesn't match target version"); return 2; } - if (opts.ver == IP_VER4 && opts.src_set) { - l3_ipv4_interface_t *l3 = l3_ipv4_find_by_ip(opts.src_ip); - if (!l3) { - char ssrc[16]; - ipv4_to_string(opts.src_ip, ssrc); - print("ping: invalid source %s (no local ip match)\n", ssrc); - return 2; - } - } - if (opts.ver == IP_VER4) return ping_v4(&opts); if (opts.ver == IP_VER6) return ping_v6(&opts); - print("usage: ping [-4/-6] [-n times] [-w timeout] [-i interval] [-t TTL] [-s src_local_ip] host\n"); + print_help(); return 2; } diff --git a/kernel/tools/shutdown.c b/kernel/tools/shutdown.c index ad11dc4a..fd5ff0ef 100644 --- a/kernel/tools/shutdown.c +++ b/kernel/tools/shutdown.c @@ -7,12 +7,23 @@ #include "hw/power.h" #include "syscalls/syscalls.h" -int run_shutdown(int argc, char* argv[]){ - const char *u = "usage: shutdown [-r|-p]\n -r reboot\n -p power off\n"; - +static void print_help(void) { + print("Usage:\t shutdown -r|-p"); + print("shut down or reboot\n"); + print("Options:"); + print("\t -r\t reboot"); + print("\t -p\t power off"); + print("\t --help\t help"); +} +int run_shutdown(int argc, char* argv[]){ if (argc <= 0){ - print("%s", u); + print_help(); + return 0; + } + + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + print_help(); return 0; } @@ -25,14 +36,14 @@ int run_shutdown(int argc, char* argv[]){ if (strcmp(a, "-r") == 0) mode = SHUTDOWN_REBOOT; else if (strcmp(a, "-p") == 0) mode = SHUTDOWN_POWEROFF; else{ - print("%s", u); + print_help(); msleep(100); return 2; } } if (mode == -1){ - print("%s", u); + print_help(); msleep(100); return 2; } diff --git a/kernel/tools/tools.c b/kernel/tools/tools.c index 4a00bc52..832d5e09 100644 --- a/kernel/tools/tools.c +++ b/kernel/tools/tools.c @@ -2,6 +2,9 @@ #include "ping.h" #include "shutdown.h" #include "tracert.h" +#include "curl.h" +#include "ip.h" +#include "firewall.h" #include "monitor_processes.h" #include "kernel_processes/kprocess_loader.h" #include "filesystem/filesystem.h" @@ -22,10 +25,26 @@ typedef struct open_tools_ref { int (*func)(int argc, char* argv[]); } open_tools_ref; +//[optional args], alt1|alt2, VALUE, (default value) +//template help +// print("Usage:\t ip link"); +// print("\t ip addr"); +// print("show or configure networking\n"); +// print("Commands:"); +// print("\t link\t interfaces"); +// print("Args:"); +// print("\t IFACE\t interface name"); +// print("\t PREFIX\t ADDRESS/LENGTH\n"); +// print("Options:"); +// print("\t --help\t help"); + open_tools_ref available_cmds[] = { { "ping", run_ping }, { "shutdown", run_shutdown }, { "tracert", run_tracert }, + { "curl", run_curl }, + { "ip", run_ip }, + { "firewall", run_firewall }, { "monitor", monitor_procs }, }; diff --git a/kernel/tools/tracert.c b/kernel/tools/tracert.c index a37ea22d..a142baf0 100644 --- a/kernel/tools/tracert.c +++ b/kernel/tools/tracert.c @@ -1,19 +1,15 @@ #include "tracert.h" -#include "networking/internet_layer/icmp.h" -#include "net/network_types.h" +#include "icmp_probe.h" #include "std/string.h" #include "std/memory.h" #include "types.h" #include "console/kio.h" -#include "filesystem/filesystem.h" #include "process/scheduler.h" #include "syscalls/syscalls.h" -#include "networking/internet_layer/ipv4.h" -#include "networking/internet_layer/ipv4_route.h" #include "networking/application_layer/dns/dns.h" #include "networking/internet_layer/ipv4_utils.h" #include "networking/internet_layer/ipv6_utils.h" -#include "networking/internet_layer/icmpv6.h" +#include "networking/transport_layer/trans_utils.h" typedef struct { ip_version_t ver; @@ -21,31 +17,47 @@ typedef struct { uint32_t count; uint32_t timeout_ms; uint32_t interval_ms; - uint32_t src_ip; uint32_t timeout_streak_limit; - bool src_set; + SockBindSpec bind; + bool bind_set; const char *host; } tr_opts_t; +static void print_help(void) { + print("Usage:\t tracert [OPTION] HOST"); + print("trace route to HOST\n"); + print("Args:"); + print("\t HOST\t host|ip"); + print("\t SOURCE\t IP|l2:ID|l3:ID\n"); + print("Options:"); + print("\t -4\t ipv4"); + print("\t -6\t ipv6"); + print("\t -m MAX_HOPS\t max hops (30)"); + print("\t -n PROBES\t probes per hop (3)"); + print("\t -w TIMEOUT\t timeout ms (1000)"); + print("\t -i INTERVAL\t interval ms (250)"); + print("\t -x STREAK\t timeout hops (5)"); + print("\t -s SOURCE\t source"); + print("\t --help\t help"); +} + static bool parse_args(int argc, char *argv[], tr_opts_t *o) { o->ver = IP_VER4; o->max_ttl = 30; o->count = 3; o->timeout_ms = 1000; o->interval_ms = 250; - o->src_ip = 0; o->timeout_streak_limit = 5; - o->src_set = false; + o->bind_set = false; o->host = NULL; + memset(&o->bind, 0, sizeof(o->bind)); for (int i = 1; i < argc; i++) { const char *a = argv[i]; if (a && a[0] == '-') { - if (strcmp_case(a, "-4",true) == 0) { - o->ver = IP_VER4; - } else if (strcmp_case(a, "-6",true) == 0) { - o->ver = IP_VER6; - } else if (strcmp_case(a, "-m",true) == 0) { + if (strcmp_case(a, "-4",true) == 0) o->ver = IP_VER4; + else if (strcmp_case(a, "-6",true) == 0) o->ver = IP_VER6; + else if (strcmp_case(a, "-m",true) == 0) { if (++i >= argc) return false; if (!parse_uint32_dec(argv[i], &o->max_ttl) || o->max_ttl == 0) return false; } else if (strcmp_case(a, "-n",true) == 0) { @@ -61,14 +73,10 @@ static bool parse_args(int argc, char *argv[], tr_opts_t *o) { if (++i >= argc) return false; if (!parse_uint32_dec(argv[i], &o->timeout_streak_limit) || o->timeout_streak_limit == 0) return false; } else if (strcmp_case(a, "-s",true) == 0) { - if (++i >= argc) return false; - uint32_t src = 0; - if (!ipv4_parse(argv[i], &src)) return false; - o->src_ip = src; - o->src_set = true; - } else { - return false; - } + if (++i >= argc || o->bind_set) return false; + if (!icmp_probe_parse_bind(argv[i], &o->bind)) return false; + o->bind_set = true; + } else return false; } else { if (o->host) return false; o->host = a; @@ -86,65 +94,52 @@ static bool parse_args(int argc, char *argv[], tr_opts_t *o) { } static int tracert_v4(const tr_opts_t *o) { - uint32_t dst = 0; - bool lit = ipv4_parse(o->host, &dst); + uint32_t dst_ip = 0; + bool lit = ipv4_parse(o->host, &dst_ip); if (!lit) { uint32_t r = 0; dns_result_t dr = dns_resolve_a(o->host, &r, DNS_USE_BOTH, o->timeout_ms); if (dr != DNS_OK) { - print("tracert: dns lookup failed (%d) for '%s'\n", (int)dr, o->host); + print("tracert: dns lookup failed (%d) for '%s'", (int)dr, o->host); return 2; } - dst = r; + dst_ip = r; } char dip[16]; char line[256]; - ipv4_to_string(dst, dip); - print("Tracing route to %s [%s]\n", o->host, dip); + ipv4_to_string(dst_ip, dip); + print("Tracing route to %s [%s]", o->host, dip); size_t len = string_format_buf(line, sizeof(line), "hop "); for (uint32_t p = 0; p < o->count && len < sizeof(line); p++) len += string_format_buf(line + len, sizeof(line) - len, "rtt%u ", p + 1); string_format_buf(line + len, sizeof(line) - len, "address"); - print("%s\n", line); - - ipv4_tx_opts_t txo = (ipv4_tx_opts_t){0}; - const ipv4_tx_opts_t *txop = NULL; - if (o->src_set) { - l3_ipv4_interface_t *l3 = l3_ipv4_find_by_ip(o->src_ip); - if (!l3) { - char ssrc[16]; - ipv4_to_string(o->src_ip, ssrc); - print("tracert: invalid source %s (no local ip match)\n", ssrc); - return 2; - } - txo.index = l3->l3_id; - txo.scope = IP_TX_BOUND_L3; - txop = &txo; - } + print("%s", line); uint16_t id = (uint16_t)(get_current_proc_pid() & 0xFFFF); uint16_t seq0 = (uint16_t)(get_time() & 0xFFFF); uint32_t dead_streak = 0; + const SockBindSpec* bind = o->bind_set ? &o->bind : NULL; + net_l4_endpoint dst; + make_ep(&dst_ip, 0, IP_VER4, &dst); for (uint32_t ttl = 1; ttl <= o->max_ttl; ttl++) { len = string_format_buf(line, sizeof(line), "%2u ", ttl); uint32_t hop_ip = 0; bool any = false; + bool reached = false; for (uint32_t p = 0; p < o->count && len < sizeof(line); p++) { uint16_t seq = (uint16_t)(seq0 + (ttl << 6) + p); - ping_result_t r = (ping_result_t){0}; - bool ok = icmp_ping(dst, id, seq, o->timeout_ms, txop, ttl, &r); - if (r.responder_ip && hop_ip == 0) hop_ip = r.responder_ip; + icmp_probe_result_t r; + memset(&r, 0, sizeof(r)); + bool answered = icmp_probe_collect(&dst, id, seq, o->timeout_ms, bind, (uint8_t)ttl, &r, 1) && r.status != ICMP_PROBE_UNKNOWN_ERROR; + if (r.responder.ver == IP_VER4 && hop_ip == 0) memcpy(&hop_ip, r.responder.ip, sizeof(hop_ip)); - if (ok || r.status == PING_TTL_EXPIRED || r.status == PING_REDIRECT || r.status == PING_PARAM_PROBLEM || - r.status == PING_NET_UNREACH || r.status == PING_HOST_UNREACH || r.status == PING_ADMIN_PROHIBITED || - r.status == PING_FRAG_NEEDED || r.status == PING_SRC_ROUTE_FAILED) { + if (answered) { any = true; + reached |= r.status == ICMP_PROBE_OK; len += string_format_buf(line + len, sizeof(line) - len, "%ums ", r.rtt_ms); - } else { - len += string_format_buf(line + len, sizeof(line) - len, "* "); - } + } else len += string_format_buf(line + len, sizeof(line) - len, "* "); if (p + 1 < o->count) msleep(o->interval_ms); } @@ -155,18 +150,16 @@ static int tracert_v4(const tr_opts_t *o) { char hip[16]; ipv4_to_string(hop_ip, hip); string_format_buf(line + len, sizeof(line) - len, "%s", hip); - } else { - string_format_buf(line + len, sizeof(line) - len, "???"); - } + } else string_format_buf(line + len, sizeof(line) - len, "???"); } else { dead_streak++; string_format_buf(line + len, sizeof(line) - len, "Request timed out."); } - print("%s\n", line); + print("%s", line); - if (hop_ip == dst) break; + if (reached || hop_ip == dst_ip) break; if (dead_streak >= o->timeout_streak_limit) { - print("stopping after %u consecutive timeout hops\n", dead_streak); + print("stopping after %u consecutive timeout hops", dead_streak); break; } } @@ -175,49 +168,55 @@ static int tracert_v4(const tr_opts_t *o) { } static int tracert_v6(const tr_opts_t *o) { - uint8_t dst[16] = {0}; - bool lit = ipv6_parse(o->host, dst); + uint8_t dst_ip[16] = {0}; + bool lit = ipv6_parse(o->host, dst_ip); if (!lit) { - dns_result_t dr = dns_resolve_aaaa(o->host, dst, DNS_USE_BOTH, o->timeout_ms); + dns_result_t dr = dns_resolve_aaaa(o->host, dst_ip, DNS_USE_BOTH, o->timeout_ms); if (dr != DNS_OK) { - print("tracert: dns lookup failed (%d) for '%s'\n", (int)dr, o->host); + print("tracert: dns lookup failed (%d) for '%s'", (int)dr, o->host); return 2; } } + if (ipv6_is_linkscope_mcast(dst_ip) && !o->bind_set) { + print("tracert: IPv6 link-local multicast requires -s ipv6, l2:id or l3:id"); + return 2; + } + char dip[64]; char line[256]; - ipv6_to_string(dst, dip, (int)sizeof(dip)); - print("Tracing route to %s [%s]\n", o->host, dip); + ipv6_to_string(dst_ip, dip, (int)sizeof(dip)); + print("Tracing route to %s [%s]", o->host, dip); size_t len = string_format_buf(line, sizeof(line), "hop "); for (uint32_t p = 0; p < o->count && len < sizeof(line); p++) len += string_format_buf(line + len, sizeof(line) - len, "rtt%u ", p + 1); string_format_buf(line + len, sizeof(line) - len, "address"); - print("%s\n", line); + print("%s", line); uint16_t id = (uint16_t)(get_current_proc_pid() & 0xFFFF); uint16_t seq0 = (uint16_t)(get_time() & 0xFFFF); uint32_t dead_streak = 0; + const SockBindSpec* bind = o->bind_set ? &o->bind : NULL; + net_l4_endpoint dst; + make_ep(dst_ip, 0, IP_VER6, &dst); for (uint32_t hl = 1; hl <= o->max_ttl; hl++) { len = string_format_buf(line, sizeof(line), "%2u ", hl); uint8_t hop_ip[16] = {0}; bool any = false; + bool reached = false; for (uint32_t p = 0; p < o->count && len < sizeof(line); p++) { uint16_t seq = (uint16_t)(seq0 + (hl << 6) + p); - ping6_result_t r = (ping6_result_t){0}; - bool ok = icmpv6_ping(dst, id, seq, o->timeout_ms, NULL, (uint8_t)hl, &r); + icmp_probe_result_t r; + memset(&r, 0, sizeof(r)); + bool answered = icmp_probe_collect(&dst, id, seq, o->timeout_ms, bind, (uint8_t)hl, &r, 1) && r.status != ICMP_PROBE_UNKNOWN_ERROR; + if (r.responder.ver == IP_VER6 && ipv6_is_unspecified(hop_ip)) ipv6_cpy(hop_ip, r.responder.ip); - if (!ipv6_is_unspecified(r.responder_ip) && ipv6_is_unspecified(hop_ip)) ipv6_cpy(hop_ip, r.responder_ip); - - if (ok || r.status == PING_TTL_EXPIRED || r.status == PING_REDIRECT || r.status == PING_PARAM_PROBLEM || - r.status == PING_NET_UNREACH || r.status == PING_HOST_UNREACH || r.status == PING_ADMIN_PROHIBITED || - r.status == PING_FRAG_NEEDED || r.status == PING_SRC_ROUTE_FAILED) { + if (answered) { any = true; + reached |= r.status == ICMP_PROBE_OK; len += string_format_buf(line + len, sizeof(line) - len, "%ums ", r.rtt_ms); - } else { - len += string_format_buf(line + len, sizeof(line) - len, "* "); - } + } else len += string_format_buf(line + len, sizeof(line) - len, "* "); if (p + 1 < o->count) msleep(o->interval_ms); } @@ -228,18 +227,16 @@ static int tracert_v6(const tr_opts_t *o) { char hip[64]; ipv6_to_string(hop_ip, hip, (int)sizeof(hip)); string_format_buf(line + len, sizeof(line) - len, "%s", hip); - } else { - string_format_buf(line + len, sizeof(line) - len, "???"); - } + } else string_format_buf(line + len, sizeof(line) - len, "???"); } else { dead_streak++; string_format_buf(line + len, sizeof(line) - len, "Request timed out."); } - print("%s\n", line); + print("%s", line); - if (ipv6_cmp(hop_ip, dst) == 0) break; + if (reached || ipv6_cmp(hop_ip, dst_ip) == 0) break; if (dead_streak >= o->timeout_streak_limit) { - print("stopping after %u consecutive timeout hops\n", dead_streak); + print("stopping after %u consecutive timeout hops", dead_streak); break; } } @@ -248,9 +245,19 @@ static int tracert_v6(const tr_opts_t *o) { } int run_tracert(int argc, char *argv[]) { + if (argc == 2 && strcmp_case(argv[1], "--help", true) == 0) { + print_help(); + return 0; + } + tr_opts_t o; if (!parse_args(argc, argv, &o)) { - print("usage: tracert [-4/-6] [-m max_hops] [-n probes] [-w timeout_ms] [-i interval_ms] [-x stop_after_timeouts] [-s src_local_ip] host\n"); + print_help(); + return 2; + } + + if (o.bind_set && o.bind.kind == BIND_IP && o.bind.ver != o.ver) { + print("tracert: source address version doesn't' match target version"); return 2; } diff --git a/kernel/virtio/virtio_pci.c b/kernel/virtio/virtio_pci.c index 76c1259d..4e39b654 100644 --- a/kernel/virtio/virtio_pci.c +++ b/kernel/virtio/virtio_pci.c @@ -58,21 +58,33 @@ void virtio_get_capabilities(virtio_device *dev, uint64_t pci_addr, uint64_t *mm if (mmio_start) *mmio_start = 0; if (mmio_size) *mmio_size = 0; - - uint64_t offset = read32(pci_addr + 0x34); - while (offset) { + //https://docs.oasis-open.org/virtio/virtio/v1.3/csd01/virtio-v1.3-csd01.html + //https://e2e.ti.com/cfs-file/__key/communityserver-discussions-components-files/639/1016.PCI.Local.Bus.Specification.Revision.3.0.pdf + //https://docs.oasis-open.org/virtio/virtio/v1.4/cs01/virtio-v1.4-cs01-diff-from-v1.2-cs01.pdf + //some changes to net virtio capabilities used to break the init + //seems to be ok now, but i believe there might be other bugs + uint8_t offset = read8(pci_addr + 0x34); + for (uint32_t n = 0; offset && n < 48; n++) { + if (offset < 0x40 || offset > 0xF0 || (offset & 3)) break; uint64_t cap_addr = pci_addr + offset; struct virtio_pci_cap* cap = (struct virtio_pci_cap*)(uintptr_t)cap_addr; - uint64_t bar_reg = pci_get_bar_address(pci_addr, 0x10, cap->bar); - uint32_t raw_lo = read32(bar_reg); - uint64_t bar_base = (uint64_t)(raw_lo & ~0xFULL); - if (raw_lo & 0x4) { - uint32_t raw_hi = read32(bar_reg + 4); - bar_base |= ((uint64_t)raw_hi) << 32; - } - - if (cap->cap_vndr == 0x9) { + if (cap->cap_vndr == 0x9 && cap->cap_len >= sizeof(*cap) && cap->cap_len <= 0x100 - offset && cap->bar < 6) { + if (cap->cfg_type == VIRTIO_PCI_CAP_NOTIFY_CFG && cap->cap_len < sizeof(*cap) + 4) { + offset = cap->cap_next; + continue; + } + uint64_t bar_reg = pci_get_bar_address(pci_addr, 0x10, cap->bar); + uint32_t raw_lo = read32(bar_reg); + if ((raw_lo & 1) || (cap->bar == 5 && (raw_lo & 0x4))) { + offset = cap->cap_next; + continue; + } + uint64_t bar_base = (uint64_t)(raw_lo & ~0xFULL); + if (raw_lo & 0x4) { + uint32_t raw_hi = read32(bar_reg + 4); + bar_base |= ((uint64_t)raw_hi) << 32; + } if (cap->cfg_type < VIRTIO_PCI_CAP_PCI_CFG && bar_base == 0){ kprintfv("[VIRTIO] Setting up bar"); bar_base = pci_setup_bar(pci_addr, cap->bar, mmio_start, mmio_size); @@ -109,15 +121,19 @@ bool virtio_init_device(virtio_device *dev) { dev->num_queues = 0; dev->current_queue = 0; dev->negotiated_features = 0; + cfg->device_status = VIRTIO_STATUS_RESET; + asm volatile ("dsb sy" ::: "memory"); uint32_t timeout = 2000; - while (cfg->device_status != 0) { + while (cfg->device_status != VIRTIO_STATUS_RESET) { if (timeout == 0) return false; timeout--; delay(1); } cfg->device_status = VIRTIO_STATUS_ACKNOWLEDGE; + asm volatile ("dsb sy" ::: "memory"); cfg->device_status |= VIRTIO_STATUS_DRIVER; + asm volatile ("dsb sy" ::: "memory"); cfg->device_feature_select = 0; uint32_t f_lo = cfg->device_feature; @@ -128,6 +144,11 @@ bool virtio_init_device(virtio_device *dev) { kprintfv("Features %llx",(unsigned long long)features); uint64_t negotiated = (features & feature_mask); + if ((feature_mask & (1ULL << VIRTIO_F_VERSION_1)) && !(negotiated & (1ULL << VIRTIO_F_VERSION_1))) { + kprintf("[VIRTIO] VIRTIO_F_VERSION_1 not supported"); + cfg->device_status |= VIRTIO_STATUS_FAILED; + return false; + } kprintfv("Negotiated features %llx",(unsigned long long)negotiated); @@ -139,18 +160,16 @@ bool virtio_init_device(virtio_device *dev) { dev->negotiated_features = negotiated; cfg->device_status |= VIRTIO_STATUS_FEATURES_OK; + asm volatile ("dsb sy" ::: "memory"); if (!(cfg->device_status & VIRTIO_STATUS_FEATURES_OK)){ kprintf("Failed to negotiate features. Supported features %llx",(unsigned long long)features); + cfg->device_status |= VIRTIO_STATUS_FAILED; return false; } - dev->memory_page = palloc(0x10000, MEM_PRIV_KERNEL, MEM_DEV | MEM_RW, false); + dev->memory_page = palloc(0x10000, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, false); if (!dev->memory_page) return false; - dev->status_dma = (uint8_t*)kalloc(dev->memory_page, 64, ALIGN_4KB, MEM_PRIV_KERNEL); - if (!dev->status_dma) return false; - *dev->status_dma = 0; - dev->num_queues = cfg->num_queues; if (dev->num_queues > VIRTIO_MAX_QUEUES) dev->num_queues = VIRTIO_MAX_QUEUES; @@ -171,14 +190,11 @@ bool virtio_init_device(virtio_device *dev) { uint64_t avail_alloc = (avail_sz + (uint64_t)(PAGE_SIZE - 1)) & ~(uint64_t)(PAGE_SIZE - 1); uint64_t used_alloc = (used_sz + (uint64_t)(PAGE_SIZE - 1)) & ~(uint64_t)(PAGE_SIZE - 1); - void* base = palloc(desc_alloc, MEM_PRIV_KERNEL, MEM_DEV | MEM_RW, true); - void* avail = palloc(avail_alloc, MEM_PRIV_KERNEL, MEM_DEV | MEM_RW, true); - void* used = palloc(used_alloc, MEM_PRIV_KERNEL, MEM_DEV | MEM_RW, true); + void* base = palloc(desc_alloc, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + void* avail = palloc(avail_alloc, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); + void* used = palloc(used_alloc, MEM_PRIV_KERNEL, MEM_RW | MEM_NORM, true); if (!base || !avail || !used) return false; - memset(base, 0, desc_alloc); - memset(avail, 0, avail_alloc); - memset(used, 0, used_alloc); uint64_t desc_pa = VIRT_TO_PHYS((uint64_t)base); uint64_t driver_pa = VIRT_TO_PHYS((uint64_t)avail); uint64_t device_pa = VIRT_TO_PHYS((uint64_t)used); @@ -223,26 +239,29 @@ uint32_t select_queue(virtio_device *dev, uint32_t index){ return dev->queues[index].size; } -void virtio_notify(virtio_device *dev) { +void virtio_notify_queue(virtio_device *dev, uint16_t index) { if (!dev || !dev->notify_cfg) return; - uint16_t index = dev->current_queue; if (index >= VIRTIO_MAX_QUEUES) return; if (!dev->queues[index].valid) return; uint32_t mul = dev->notify_off_multiplier; - if (!mul) mul = 1; - uint16_t off = dev->queues[index].notify_off; uint16_t value = (dev->negotiated_features & (1ULL << VIRTIO_F_NOTIFICATION_DATA)) ? dev->queues[index].notify_data : index; *(volatile uint16_t*)((uintptr_t)dev->notify_cfg + (uint64_t)off * (uint64_t)mul) = value; } -bool virtio_send_nd(virtio_device *dev, const virtio_buf *bufs, uint16_t n) { +void virtio_notify(virtio_device *dev) { + if (!dev) return; + virtio_notify_queue(dev, dev->current_queue); +} +//TODO this still blocks until the device completes the request +//t would make more sense to split submit/completion later and complete requests through async/events/promises +bool virtio_send_nd(virtio_device *dev, const virtio_buf *bufs, uint16_t n) { if (!dev || !bufs || !n) return false; + if (dev->current_queue >= VIRTIO_MAX_QUEUES || dev->current_queue >= dev->num_queues) return false; - if (dev->current_queue >= VIRTIO_MAX_QUEUES) return false; virtio_queue *queue = &dev->queues[dev->current_queue]; if (!queue->valid || !queue->size || n > queue->size) return false; @@ -276,6 +295,7 @@ bool virtio_send_nd(virtio_device *dev, const virtio_buf *bufs, uint16_t n) { virtio_notify(dev); while (last_used_idx == u->idx);//TODO: OPT + asm volatile ("dmb ishld" ::: "memory"); return true; } diff --git a/kernel/virtio/virtio_pci.h b/kernel/virtio/virtio_pci.h index 2744b651..654b3f3f 100644 --- a/kernel/virtio/virtio_pci.h +++ b/kernel/virtio/virtio_pci.h @@ -9,6 +9,8 @@ extern "C" { #define VIRTQ_DESC_F_NEXT 1 #define VIRTQ_DESC_F_WRITE 2 +#define VIRTQ_AVAIL_F_NO_INTERRUPT 1 + #define VIRTIO_VENDOR 0x1AF4 #define VIRTIO_F_VERSION_1 32 @@ -91,7 +93,6 @@ typedef struct virtio_device { volatile uint8_t* isr_cfg; uint32_t notify_off_multiplier; void *memory_page; - uint8_t* status_dma; uint64_t negotiated_features; uint16_t num_queues; uint16_t current_queue; @@ -107,6 +108,7 @@ typedef struct { #define VBUF(a,l,f) ((virtio_buf){.addr = (uint64_t)(a), .len = (uint32_t)(l), .flags = (uint16_t)(f)}) void virtio_notify(virtio_device *dev); +void virtio_notify_queue(virtio_device *dev, uint16_t index); void virtio_set_feature_mask(uint64_t mask); void virtio_enable_verbose(); void virtio_get_capabilities(virtio_device *dev, uint64_t pci_addr, uint64_t *mmio_start, uint64_t *mmio_size); diff --git a/libs/LibMakefile b/libs/LibMakefile index 4e6a584c..b2d6f57e 100644 --- a/libs/LibMakefile +++ b/libs/LibMakefile @@ -9,9 +9,11 @@ endif CFLAGS := $(CFLAGS_BASE) $(BASE_FLAGS) -CLEAN_OBJS := $(shell find -name "*.o") +CLEAN_OBJS := $(shell find . -name "*.o") +CLEAN_DEPS := $(shell find . -name "*.d") C_SRC := $(shell cat simplemake | xargs -n1 find . -name) OBJ := $(C_SRC:%.c=$(BUILD_DIR)/%.o) +DEP := $(C_SRC:%.c=$(BUILD_DIR)/%.d) TARGET := $(shell echo "../lib$(notdir $(CURDIR)).a") diff --git a/libs/Makefile b/libs/Makefile index 87ea9572..4332cd30 100644 --- a/libs/Makefile +++ b/libs/Makefile @@ -1,9 +1,9 @@ include ../common.mk -SUBDIRS := $(wildcard */.) -SUBCLEAN = $(addsuffix .clean,$(SUBDIRS)) -SUBDUMP = $(addsuffix .dump,$(SUBDIRS)) -MAKEFILE := $(shell pwd)/LibMakefile +SUBDIRS := $(wildcard */) +SUBCLEAN := $(addsuffix .clean,$(SUBDIRS)) +SUBDUMP := $(addsuffix .dump,$(SUBDIRS)) +MAKEFILE := $(CURDIR)/LibMakefile all: $(SUBDIRS) @@ -15,6 +15,4 @@ clean: $(SUBCLEAN) $(SUBCLEAN): %.clean: $(MAKE) -f $(MAKEFILE) -C $* clean -clean: $(SUBCLEAN) - -.PHONY: all $(SUBDIRS) \ No newline at end of file +.PHONY: all clean $(SUBDIRS) $(SUBCLEAN) \ No newline at end of file diff --git a/modules/Makefile b/modules/Makefile index 30131e55..08abac51 100644 --- a/modules/Makefile +++ b/modules/Makefile @@ -1,8 +1,8 @@ include ../common.mk -SUBDIRS := $(wildcard */.) -SUBCLEAN = $(addsuffix .clean, $(SUBDIRS)) -MAKEFILE := $(shell pwd)/MakefileModule +SUBDIRS := $(wildcard */) +SUBCLEAN := $(addsuffix .clean, $(SUBDIRS)) +MAKEFILE := $(CURDIR)/MakefileModule all: $(SUBDIRS) @@ -14,6 +14,4 @@ clean: $(SUBCLEAN) $(SUBCLEAN): %.clean: $(MAKE) -f $(MAKEFILE) -C $* clean DRIVER_TARGET=$(DRIVER_TARGET) -clean: $(SUBCLEAN) - -.PHONY: all $(SUBDIRS) +.PHONY: all clean $(SUBDIRS) $(SUBCLEAN) diff --git a/modules/MakefileModule b/modules/MakefileModule index 434ec1fd..fe0d6a80 100644 --- a/modules/MakefileModule +++ b/modules/MakefileModule @@ -13,11 +13,12 @@ endif CFLAGS := $(CFLAGS_BASE) $(BASE_FLAGS) CXXFLAGS := $(CXXFLAGS_BASE) $(BASE_FLAGS) -CLEAN_OBJS := $(shell find $(DRIVER_TARGET) -name "*.o") $(shell find ./common -name "*.o" 2>/dev/null) -CLEAN_DEPS := $(shell find $(DRIVER_TARGET) -name "*.d") $(shell find ./common -name "*.d" 2>/dev/null) -C_SRC := $(shell find $(DRIVER_TARGET) -name "*.c") $(shell find ./common -name "*.c" 2>/dev/null) -CPP_SRC := $(shell find $(DRIVER_TARGET) -name "*.cpp") $(shell find ./common -name "*.cpp" 2>/dev/null) -ASM_SRC := $(shell find $(DRIVER_TARGET) -name "*.S") $(shell find ./common -name "*.S" 2>/dev/null) +SRC_DIRS := $(if $(strip $(DRIVER_TARGET)),$(wildcard ./$(DRIVER_TARGET)) $(wildcard ./common),.) +CLEAN_OBJS := $(if $(SRC_DIRS),$(shell find $(SRC_DIRS) -name "*.o"),) +CLEAN_DEPS := $(if $(SRC_DIRS),$(shell find $(SRC_DIRS) -name "*.d"),) +C_SRC := $(if $(SRC_DIRS),$(shell find $(SRC_DIRS) -name "*.c"),) +CPP_SRC := $(if $(SRC_DIRS),$(shell find $(SRC_DIRS) -name "*.cpp"),) +ASM_SRC := $(if $(SRC_DIRS),$(shell find $(SRC_DIRS) -name "*.S"),) OBJ := $(C_SRC:%.c=$(BUILD_DIR)/%.o) $(ASM_SRC:%.S=$(BUILD_DIR)/%.o) $(CPP_SRC:%.cpp=$(BUILD_DIR)/%.o) DEP := $(C_SRC:%.c=$(BUILD_DIR)/%.d) $(ASM_SRC:%.S=$(BUILD_DIR)/%.d) $(CPP_SRC:%.cpp=$(BUILD_DIR)/%.d) diff --git a/modules/graph/virt/ramfb.cpp b/modules/graph/virt/ramfb.cpp index 1bda5887..c7cc6dba 100644 --- a/modules/graph/virt/ramfb.cpp +++ b/modules/graph/virt/ramfb.cpp @@ -9,6 +9,14 @@ #include "sysregs.h" #include "memory/addr.h" +bool verbose = true; +#define kprintfv(fmt, ...) \ + ({ \ + if (verbose){\ + kprintf(fmt, ##__VA_ARGS__); \ + }\ + }) + typedef struct { uint64_t addr; uint32_t fourcc; @@ -39,26 +47,34 @@ bool RamFBGPUDriver::init(gpu_size preferred_screen_size){ // preferred_screen_size.height /= 2; // #endif screen_size = preferred_screen_size; + kprintfv("[RAMFB] init requested %ix%i", preferred_screen_size.width, preferred_screen_size.height); if (!screen_size.width || !screen_size.height) return false; stride = bpp * screen_size.width; framebuffer_size = (size_t)(stride * screen_size.height); - fw_find_file("etc/ramfb", &file); - - if (file.selector == 0x0){ + kprintfv("[RAMFB] probing cfg desc"); + if (!fw_find_file("etc/ramfb", &file) || file.selector == 0x0){ kprintf("[RAMFB error] ramfb not found"); return false; } + + kprintfv("[RAMFB] descriptor found selector=%x size=%x name=%s", file.selector, file.size, file.name); mem_page = palloc(0x1000, MEM_PRIV_KERNEL, MEM_RW | MEM_DEV, false); uint8_t* fb_block = (uint8_t*)palloc(framebuffer_size*2, MEM_PRIV_SHARED, MEM_RW, true); - if (!fb_block) return false; + if (!fb_block) { + kprintfv("[RAMFB] failed to allocate fb"); + return false; + } framebuffer = (uint32_t*)fb_block; back_framebuffer = (uint32_t*)(fb_block + framebuffer_size); + kprintfv("[RAMFB] framebuffer va=%x pa=%x", (uintptr_t)framebuffer, (uintptr_t)pt_va_to_pa(framebuffer)); + kprintfv("[RAMFB] backbuffer va=%x pa=%x", (uintptr_t)back_framebuffer, (uintptr_t)pt_va_to_pa(back_framebuffer)); + ctx = { .dirty_rects = {}, .fb = (uint32_t*)back_framebuffer, @@ -87,7 +103,9 @@ void RamFBGPUDriver::update_gpu_fb(){ .stride = __builtin_bswap32(stride), }; + kprintfv("[RAMFB] writing descriptor selector=%x fb_pa=%x %ix%i stride=%x", file.selector, (uintptr_t)fb_pa, screen_size.width, screen_size.height, stride); fw_cfg_dma_write(&fb, sizeof(fb), file.selector); + kprintfv("[RAMFB] descriptor w completed"); } gpu_size RamFBGPUDriver::get_screen_size(){ diff --git a/modules/graph/virt/virtio_gpu_pci.cpp b/modules/graph/virt/virtio_gpu_pci.cpp index 77ec8429..a30a7799 100644 --- a/modules/graph/virt/virtio_gpu_pci.cpp +++ b/modules/graph/virt/virtio_gpu_pci.cpp @@ -6,6 +6,7 @@ #include "theme/theme.h" #include "memory/page_allocator.h" #include "sysregs.h" +#include "fw/fw_cfg.h" #define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100 #define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101 diff --git a/run_virt b/run_virt index fb8c6b07..789bd76f 100755 --- a/run_virt +++ b/run_virt @@ -30,8 +30,8 @@ AUDIO_BACKEND="sdl" VIRTIO_GPU_VARS=",xres=1920,yres=1080" GL="on" -USE_FB=true -USE_NET=false +USE_FB=false +USE_NET=true if [ "$USE_FB" == true ]; then SELECTED_GPU="ramfb" diff --git a/shared b/shared index 1442cef4..90c17106 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 1442cef44fcf72143332558c5f86be9f7d9f6ca6 +Subproject commit 90c171069742635560f4477dba9bb04f870925ba diff --git a/tools/Makefile b/tools/Makefile index e4e94b1d..1cad467d 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,9 +1,9 @@ include ../common.mk -SUBDIRS := $(wildcard */.) -SUBCLEAN = $(addsuffix .clean,$(SUBDIRS)) -SUBDUMP = $(addsuffix .dump,$(SUBDIRS)) -MAKEFILE := $(shell pwd)/ToolMakefile +SUBDIRS := $(sort $(dir $(wildcard */*.S */*.c */*.cpp))) +SUBCLEAN := $(addsuffix .clean,$(wildcard */)) +SUBDUMP := $(addsuffix .dump,$(SUBDIRS)) +MAKEFILE := $(CURDIR)/ToolMakefile all: $(SUBDIRS) @@ -20,6 +20,4 @@ $(SUBCLEAN): %.clean: $(SUBDUMP): %.dump: $(MAKE) -f $(MAKEFILE) -C $* dump -clean: $(SUBCLEAN) - -.PHONY: all $(SUBDIRS) +.PHONY: all clean dump $(SUBDIRS) $(SUBCLEAN) $(SUBDUMP) diff --git a/user/Makefile b/user/Makefile index 7de89320..a838a741 100644 --- a/user/Makefile +++ b/user/Makefile @@ -1,9 +1,10 @@ include ../common.mk -SUBDIRS := $(wildcard */.) -SUBCLEAN = $(addsuffix .clean,$(SUBDIRS)) -SUBDUMP = $(addsuffix .dump,$(SUBDIRS)) -MAKEFILE := $(shell pwd)/UserMakefile +USER_DIRS := $(sort $(dir $(wildcard */*.S */*.c */*.cpp))) +SUBDIRS := $(filter-out build/ resources/ %.red/,$(USER_DIRS)) +SUBCLEAN := $(addsuffix .clean,$(SUBDIRS)) +SUBDUMP := $(addsuffix .dump,$(SUBDIRS)) +MAKEFILE := $(CURDIR)/UserMakefile all: $(SUBDIRS) @@ -11,7 +12,8 @@ $(SUBDIRS): $(MAKE) -f $(MAKEFILE) -C $@ clean: $(SUBCLEAN) - + $(RM) -r build *.red dump + dump: $(SUBDUMP) $(SUBCLEAN): %.clean: @@ -20,6 +22,4 @@ $(SUBCLEAN): %.clean: $(SUBDUMP): %.dump: $(MAKE) -f $(MAKEFILE) -C $* dump -clean: $(SUBCLEAN) - -.PHONY: all $(SUBDIRS) \ No newline at end of file +.PHONY: all clean dump $(SUBDIRS) $(SUBCLEAN) $(SUBDUMP) diff --git a/user/UserMakefile b/user/UserMakefile index da135fa9..5af046a9 100644 --- a/user/UserMakefile +++ b/user/UserMakefile @@ -20,7 +20,12 @@ LOCATION := ../../fs/redos/system/ .PHONY: prepare all clean +ifeq ($(strip $(OBJ)),) +all: + @echo "Skipping $(NAME): no source files" +else all: prepare $(PACKAGE)/$(TARGET) +endif prepare: mkdir -p resources @@ -29,14 +34,10 @@ prepare: cp -r resources $(PACKAGE) $(PACKAGE)/$(TARGET): ../../shared/libshared.a $(OBJ) - $(VLD) $(LDFLAGS) -o $(PACKAGE)/$(ELF) $(addprefix $(BUILD_DIR)/,$(notdir $(OBJ))) ../../shared/libshared.a + $(VLD) $(LDFLAGS) -o $(PACKAGE)/$(ELF) $(OBJ) ../../shared/libshared.a $(OBJCOPY) -O binary $(PACKAGE)/$(ELF) $@ cp -r $(PACKAGE) $(LOCATION) -$(BUILD_DIR)/%.o: %.S - @mkdir -p $(dir $@) - $(VAS) $(CFLAGS) -c $< -o $@ - $(BUILD_DIR)/%.o: %.c @mkdir -p $(dir $@) $(VCC) $(CFLAGS) -c -MMD -MP $< -o $@ @@ -50,6 +51,6 @@ clean: $(RM) -r $(PACKAGE) $(BUILD_DIR) dump: all - $(ARCH)objdump -S $(NAME).red/$(NAME).elf > dump + $(ARCH)objdump -S $(PACKAGE)/$(ELF) > dump -include $(DEP) \ No newline at end of file diff --git a/user/demo/default_process.c b/user/demo/default_process.c index ca841dc2..88449fea 100644 --- a/user/demo/default_process.c +++ b/user/demo/default_process.c @@ -10,6 +10,7 @@ #include "memory/memory.h" #include "files/helpers.h" #include "utils/clipboard.h" +#include "net/net_ctrl.h" #include "math/math.h" #include "draw/textdraw.h" #include "environment/env_types.h" @@ -54,22 +55,24 @@ int img_example() { } int net_example() { - SocketHandle spec = {}; - socket_create(SOCKET_SERVER, PROTO_UDP, NULL, &spec); - print("Created socket for type %i",spec.protocol); - //Fill in manually with your local IP. A syscall will be added soon to get it for you - spec.connection.ip[0] = 0; - spec.connection.ip[1] = 0; - spec.connection.ip[2] = 0; - spec.connection.ip[3] = 0; - if (socket_bind(&spec, IP_VER4, 9000) < 0) return -1; + socket_handle_t sock = socket_create(PROTO_UDP, NULL); + print("Created socket"); + SockBindSpec spec = {}; + spec.kind = BIND_IP; + spec.ver = IP_VER4; + //Fill in manually with your local IPV4. address (0.0.0.0 is ANYV4) + spec.ip[0] = 0; + spec.ip[1] = 0; + spec.ip[2] = 0; + spec.ip[3] = 0; + if (socket_bind(sock, &spec, 9000) < 0) return -1; // socket_listen(&spec); void *ptr = zalloc(0x1000); - print("Waiting for data %i.%i.%i.%i", spec.connection.ip[0],spec.connection.ip[1],spec.connection.ip[2],spec.connection.ip[3]); + print("Waiting for data %i.%i.%i.%i", spec.ip[0],spec.ip[1],spec.ip[2],spec.ip[3]); net_l4_endpoint rc = {}; - while (!socket_receive(&spec, ptr, 0x1000, &rc)){ + while (socket_receive(sock, ptr, 0x1000, &rc) == SOCK_ERR_WOULDBLOCK){ } print("Received data from %i.%i.%i.%i:%i", rc.ip[0],rc.ip[1],rc.ip[2],rc.ip[3],rc.port); @@ -80,13 +83,171 @@ int net_example() { char *str = "Hello node"; - print("Sent %i",socket_send(&spec, DST_ENDPOINT, &rc.ip, rc.port, str, strlen(str))); + print("Sent %i",socket_send_to(sock, &rc, str, strlen(str))); - socket_close(&spec); + socket_close(sock); return 1; } +int net_ctrl_example() { + //create ctrl socket + SocketOptions opt = {}; + opt.special_kind = SOCKET_SPECIAL_CTRL; + opt.flags = SOCK_OPT_SPECIAL; + socket_handle_t sock = socket_create(PROTO_NONE, &opt); + if (!sock) return -1; + + //create a REQUEST to get ADDR + NetCtrlMsg req = {}; + req.object = NET_CTRL_OBJ_ADDR; + req.op = NET_CTRL_OP_GET; + req.flags = NET_CTRL_F_REQUEST; + req.length = sizeof(req); + + if (socket_send(sock, &req, req.length) < 0) { + socket_close(sock); + return -1; + } + + //read response + uint8_t rx[1024]; + int64_t n = socket_receive(sock, rx, sizeof(rx), NULL); + if (n < (int64_t)sizeof(NetCtrlMsg)) { + socket_close(sock); + return -1; + } + + NetCtrlMsg* res = (NetCtrlMsg*)rx; + if (res->status != SOCK_OK) { + socket_close(sock); + return -1; + } + + //parse respose + NetCtrlAddrInfo* addrs = (NetCtrlAddrInfo*)NET_CTRL_MSG_DATA(res); + uint32_t count = NET_CTRL_MSG_PAYLOAD_LEN(res) / sizeof(NetCtrlAddrInfo); + NetCtrlAddrInfo* main_v4 = NULL; + + //find a valid ip + for (uint32_t i = 0; i < count; i++) { + if (addrs[i].prefix.address.ver != IP_VER4) continue; + if (addrs[i].config != IPV4_CFG_DHCP && addrs[i].config != IPV4_CFG_STATIC) continue; + main_v4 = &addrs[i]; + break; + } + + if (!main_v4) { + socket_close(sock); + return -1; + } + + uint32_t current_ip = 0; + memcpy(¤t_ip, main_v4->prefix.address.ip, sizeof(current_ip)); + + uint32_t mask = 0xFFFFFFFF << (32 - main_v4->prefix.prefix_len); + + print("main IPv4 is %u.%u.%u.%u/%u mask %u.%u.%u.%u", + current_ip >> 24, (current_ip >> 16) & 0xFF, (current_ip >> 8) & 0xFF, current_ip & 0xFF, + main_v4->prefix.prefix_len, mask >> 24, (mask >> 16) & 0xFF, (mask >> 8) & 0xFF, mask & 0xFF); + + uint8_t msg_buf[128]; + memset(msg_buf, 0, sizeof(msg_buf)); + + //create a REQUEST to add OBJ_ADDR + NetCtrlMsg* msg = (NetCtrlMsg*)msg_buf; + msg->object = NET_CTRL_OBJ_ADDR; + msg->op = NET_CTRL_OP_ADD; + msg->flags = NET_CTRL_F_REQUEST; + + uint32_t off = sizeof(NetCtrlMsg); + + //reuse the same L2 interface as the found ipv4 + NetCtrlAttr* attr = (NetCtrlAttr*)(msg_buf + off); + attr->ext = NET_CTRL_EXT_IFINDEX; + attr->length = sizeof(main_v4->prefix.ifindex); + off += sizeof(NetCtrlAttr); + memcpy(msg_buf + off, &main_v4->prefix.ifindex, sizeof(main_v4->prefix.ifindex)); + off += sizeof(main_v4->prefix.ifindex); + + //reuse the same mask (prefix length) + attr = (NetCtrlAttr*)(msg_buf + off); + attr->ext = NET_CTRL_EXT_PREFIX_LEN; + attr->length = sizeof(main_v4->prefix.prefix_len); + off += sizeof(NetCtrlAttr); + memcpy(msg_buf + off, &main_v4->prefix.prefix_len, sizeof(main_v4->prefix.prefix_len)); + off += sizeof(main_v4->prefix.prefix_len); + + uint32_t host_mask = ~mask; + if (host_mask <= 1) { + socket_close(sock); + return -1; + } + + uint32_t gateway_ip = 0; + if (main_v4->prefix.gateway.ver == IP_VER4) memcpy(&gateway_ip, main_v4->prefix.gateway.ip, sizeof(gateway_ip)); + + uint32_t network = current_ip & mask; + uint32_t first_host = 1; + uint32_t last_host = host_mask - 1; + if (host_mask > 32) { + first_host = 10; + last_host = host_mask - 10; + } + uint32_t host_span = last_host - first_host + 1; + uint32_t host = first_host + (((uint32_t)get_time() ^ current_ip) % host_span); + uint32_t static_ip = network | host; + for (uint32_t i = 0; i < host_span; i++) { + if (static_ip != current_ip && static_ip != gateway_ip) break; + host++; + if (host > last_host) host = first_host; + static_ip = network | host; + } + + print("adding static %u.%u.%u.%u on the same /%u network", + static_ip >> 24, (static_ip >> 16) & 0xFF, (static_ip >> 8) & 0xFF, static_ip & 0xFF, main_v4->prefix.prefix_len); + + //add another address in the same network and with the same mask + net_l4_endpoint static_addr = {}; + static_addr.ver = IP_VER4; + memcpy(static_addr.ip, &static_ip, sizeof(static_ip)); + + attr = (NetCtrlAttr*)(msg_buf + off); + attr->ext = NET_CTRL_EXT_ADDRESS; + attr->length = sizeof(static_addr); + off += sizeof(NetCtrlAttr); + memcpy(msg_buf + off, &static_addr, sizeof(static_addr)); + off += sizeof(static_addr); + + //static config + int16_t cfg = IPV4_CFG_STATIC; + attr = (NetCtrlAttr*)(msg_buf + off); + attr->ext = NET_CTRL_EXT_CONFIG; + attr->length = sizeof(cfg); + off += sizeof(NetCtrlAttr); + memcpy(msg_buf + off, &cfg, sizeof(cfg)); + off += sizeof(cfg); + msg->length = off; + + int64_t sent = socket_send(sock, msg, msg->length); + if (sent != (int64_t)msg->length) { + socket_close(sock); + return -1; + } + + n = socket_receive(sock, rx, sizeof(rx), NULL); + if (n < (int64_t)sizeof(NetCtrlMsg)) { + socket_close(sock); + return -1; + } + + res = (NetCtrlMsg*)rx; + if (res->status != SOCK_OK) print("add failed with status %i", res->status); + else print("add ok"); + socket_close(sock); + return res->status == SOCK_OK ? 0 : -1; +} + static int8_t mixin[MIXER_INPUTS] = { NULL }; static audio_samples audio[MIXER_INPUTS]; diff --git a/user/terminal/terminal.cpp b/user/terminal/terminal.cpp index af67631c..e79bc3e1 100644 --- a/user/terminal/terminal.cpp +++ b/user/terminal/terminal.cpp @@ -92,7 +92,9 @@ void Terminal::cursor_set_visible(bool visible){ if (last_drawn_cursor_x >= 0 && last_drawn_cursor_y >= 0) { if ((uint32_t)last_drawn_cursor_x < columns && (uint32_t)last_drawn_cursor_y < rows) { char *prev_line = &row_data[((scroll_row_offset + (u32)last_drawn_cursor_y) % rows) * columns]; - render_glyph(last_drawn_cursor_x*char_width, last_drawn_cursor_y * line_height, prev_line[last_drawn_cursor_x], current_format.current_text_color, current_format.current_bg_color, true); + color *prev_line_fg = &row_fg_data[((scroll_row_offset + (u32)last_drawn_cursor_y) % rows) * columns]; + color *prev_line_bg = &row_bg_data[((scroll_row_offset + (u32)last_drawn_cursor_y) % rows) * columns]; + render_glyph(last_drawn_cursor_x*char_width, last_drawn_cursor_y * line_height, prev_line[last_drawn_cursor_x], prev_line_fg[last_drawn_cursor_x], prev_line_bg[last_drawn_cursor_x], true); } last_drawn_cursor_x = -1; last_drawn_cursor_y = -1; @@ -123,6 +125,8 @@ void Terminal::redraw_input_line(){ fb_fill_rect(dctx, 0, current_format.cursor_y * lh, columns * cw, lh, current_format.current_bg_color); char* line = row_data + (((scroll_row_offset + current_format.cursor_y) % rows) * columns); + color* line_fg = &row_fg_data[((scroll_row_offset + current_format.cursor_y) % rows) * columns]; + color* line_bg = &row_bg_data[((scroll_row_offset + current_format.cursor_y) % rows) * columns]; memset(line, 0, columns); if (columns == 0) return; @@ -138,6 +142,12 @@ void Terminal::redraw_input_line(){ for (uint32_t i = 0; i < draw_len; i++) line[prompt_length + i] = input_buf[i]; line[prompt_length + draw_len] = 0; + uint32_t line_len = (uint32_t)prompt_length + draw_len; + for (uint32_t i = 0; i <= line_len; i++) { + line_fg[i] = current_format.current_text_color; + line_bg[i] = current_format.current_bg_color; + } + uint32_t ypix = (current_format.cursor_y * lh) + (lh / 2); fb_draw_char(dctx, 0, ypix, '>', char_scale, current_format.current_text_color); fb_draw_char(dctx, cw, ypix, ' ', char_scale, current_format.current_text_color); diff --git a/user/theme/main.c b/user/theme/main.c old mode 100755 new mode 100644