From 28f7f572473c4e97ccd6133bb4f5fa6f45505530 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 07:58:30 -0800 Subject: [PATCH 01/23] manually start merging core_net --- core/net/addr.odin | 818 ++++++++++++++++++++++++++++ core/net/addr_darwin.odin | 71 +++ core/net/addr_linux.odin | 93 ++++ core/net/addr_openbsd.odin | 69 +++ core/net/addr_windows.odin | 69 +++ core/net/common.odin | 437 +++++++++++++++ core/net/dns.odin | 873 ++++++++++++++++++++++++++++++ core/net/dns_unix.odin | 83 +++ core/net/dns_windows.odin | 166 ++++++ core/net/doc.odin | 47 ++ core/net/interface.odin | 68 +++ core/net/interface_darwin.odin | 23 + core/net/interface_linux.odin | 147 +++++ core/net/interface_windows.odin | 182 +++++++ core/net/socket.odin | 87 +++ core/net/socket_darwin.odin | 513 ++++++++++++++++++ core/net/socket_linux.odin | 532 ++++++++++++++++++ core/net/socket_openbsd.odin | 515 ++++++++++++++++++ core/net/socket_windows.odin | 577 ++++++++++++++++++++ core/net/url.odin | 235 ++++++++ core/os/os_darwin.odin | 239 ++++++++ core/os/os_linux.odin | 243 +++++++++ core/sys/unix/syscalls_linux.odin | 36 ++ core/sys/windows/dnsapi.odin | 10 + core/sys/windows/ip_helper.odin | 234 ++++++++ core/sys/windows/types.odin | 369 ++++++++----- core/sys/windows/util.odin | 21 + core/sys/windows/ws2_32.odin | 14 +- tests/core/Makefile | 5 +- tests/core/build.bat | 5 + tests/core/net/test_core_net.odin | 508 +++++++++++++++++ 31 files changed, 7142 insertions(+), 147 deletions(-) create mode 100644 core/net/addr.odin create mode 100644 core/net/addr_darwin.odin create mode 100644 core/net/addr_linux.odin create mode 100644 core/net/addr_openbsd.odin create mode 100644 core/net/addr_windows.odin create mode 100644 core/net/common.odin create mode 100644 core/net/dns.odin create mode 100644 core/net/dns_unix.odin create mode 100644 core/net/dns_windows.odin create mode 100644 core/net/doc.odin create mode 100644 core/net/interface.odin create mode 100644 core/net/interface_darwin.odin create mode 100644 core/net/interface_linux.odin create mode 100644 core/net/interface_windows.odin create mode 100644 core/net/socket.odin create mode 100644 core/net/socket_darwin.odin create mode 100644 core/net/socket_linux.odin create mode 100644 core/net/socket_openbsd.odin create mode 100644 core/net/socket_windows.odin create mode 100644 core/net/url.odin create mode 100644 core/sys/windows/dnsapi.odin create mode 100644 core/sys/windows/ip_helper.odin create mode 100644 tests/core/net/test_core_net.odin diff --git a/core/net/addr.odin b/core/net/addr.odin new file mode 100644 index 000000000..80ba91110 --- /dev/null +++ b/core/net/addr.odin @@ -0,0 +1,818 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, IPv4 + IPv6 parsers, documentation. +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:strconv" +import "core:strings" +import "core:fmt" + +/* + Expects an IPv4 address with no leading or trailing whitespace: + - a.b.c.d + - a.b.c.d:port + - [a.b.c.d]:port + + If the IP address is bracketed, the port must be present and valid (though it will be ignored): + - [a.b.c.d] will be treated as a parsing failure. + + The port, if present, is required to be a base 10 number in the range 0-65535, inclusive. + + If `non_decimal_address` is true, `aton` is told each component must be decimal and max 255. +*/ +parse_ip4_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> (addr: IP4_Address, ok: bool) { + res, res_ok := aton(address_and_maybe_port, .IP4, !non_decimal_address) + if ip4, ip4_ok := res.(IP4_Address); ip4_ok { + return ip4, res_ok + } + return {}, false +} + +/* + Parses an IP address in "non-decimal" `inet_aton` form. + + e.g."00377.0x0ff.65534" = 255.255.255.254 + 00377 = 255 in octal + 0x0ff = 255 in hexadecimal + This leaves 16 bits worth of address + .65534 then accounts for the last two digits + + For the address part the allowed forms are: + a.b.c.d - where each part represents a byte + a.b.c - where `a` & `b` represent a byte and `c` a u16 + a.b - where `a` represents a byte and `b` supplies the trailing 24 bits + a - where `a` gives the entire 32-bit value + + The port, if present, is required to be a base 10 number in the range 0-65535, inclusive. +*/ +aton :: proc(address_and_maybe_port: string, family: Address_Family, is_decimal_only := false) -> (addr: Address, ok: bool) { + switch family { + case .IP4: + /* + There is no valid address shorter than `0.0.0.0`. + */ + if len(address_and_maybe_port) < 7 { + return {}, false + } + + address, _ := split_port(address_and_maybe_port) or_return // This call doesn't allocate + + buf: [4]u64 = {} + i := 0 + + max_value := u64(max(u32)) + bases := DEFAULT_DIGIT_BASES + + if is_decimal_only { + max_value = 255 + bases = {.Dec} + } + + for len(address) > 0 { + if i == 4 { + return {}, false + } + + /* + Decimal-only addresses may not have a leading zero. + */ + if is_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' { + return + } + + number, consumed, number_ok := parse_ip_component(address, max_value, bases) + if !number_ok || consumed == 0 { + return {}, false + } + + buf[i] = number + + address = address[consumed:] + + if len(address) > 0 && address[0] == '.' { + address = address[1:] + } + i += 1 + } + + /* + Distribute parts. + */ + switch i { + case 1: + buf[1] = buf[0] & 0xffffff + buf[0] >>= 24 + fallthrough + case 2: + buf[2] = buf[1] & 0xffff + buf[1] >>= 16 + fallthrough + case 3: + buf[3] = buf[2] & 0xff + buf[2] >>= 8 + } + + a: [4]u8 = --- + for v, i in buf { + if v > 255 { return {}, false } + a[i] = u8(v) + } + return IP4_Address(a), true + + case .IP6: + return parse_ip6_address(address_and_maybe_port) + + case: + return nil, false + } +} + +/* + The minimum length of a valid IPv6 address string is 2, e.g. `::` + + The maximum length of a valid IPv6 address string is 45, when it embeds an IPv4, + e.g. `0000:0000:0000:0000:0000:ffff:255.255.255.255` + + An IPv6 address must contain at least 3 pieces, e.g. `::`, + and at most 9 (using `::` for a trailing or leading 0) +*/ +IPv6_MIN_STRING_LENGTH :: 2 +IPv6_MAX_STRING_LENGTH :: 45 +IPv6_MIN_COLONS :: 2 +IPv6_PIECE_COUNT :: 8 + +parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) { + /* + If we have an IPv6 address of the form [IP]:Port, first get us just the IP. + */ + address, _ := split_port(address_and_maybe_port) or_return + + /* + Early bailouts based on length and number of pieces. + */ + if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return } + + /* + Do a pre-pass on the string that checks how many `:` and `.` we have, + if they're in the right order, and if the things between them are digits as expected. + + It's not strictly necessary considering we could use `strings.split`, + but this way we can avoid using an allocator and return earlier on bogus input. Win-win. + */ + colon_count := 0 + dot_count := 0 + + pieces_temp: [IPv6_PIECE_COUNT + 1]string + + piece_start := 0 + piece_end := 0 + + for ch, i in address { + switch ch { + case '0'..'9', 'a'..'f', 'A'..'F': + piece_end += 1 + + case ':': + /* + If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, + instead of it being the tail: invalid. + */ + if dot_count > 0 { return } + + pieces_temp[colon_count] = address[piece_start:piece_end] + + colon_count += 1 + if colon_count > IPv6_PIECE_COUNT { return } + + /* + If there's anything left, put it in the next piece. + */ + piece_start = i + 1 + piece_end = piece_start + + case '.': + /* + IPv4 address is treated as one piece. No need to update `piece_*`. + */ + dot_count += 1 + + + case: // Invalid character, return early + return + } + } + + if colon_count < IPv6_MIN_COLONS { return } + + /* + Assign the last piece string. + */ + pieces_temp[colon_count] = address[piece_start:] + + /* + `pieces` now holds the same output as it would if had used `strings.split`. + */ + pieces := pieces_temp[:colon_count + 1] + + /* + Check if we have what looks like an embedded IPv4 address. + */ + ipv4: IP4_Address + have_ipv4: bool + + if dot_count > 0 { + /* + If we have an IPv4 address accounting for the last 32 bits, + this means we can have at most 6 IPv6 pieces, like so: `x:x:X:x:x:x:d.d.d.d` + + Or, put differently: 6 pieces IPv6 (5 colons), a colon, 1 piece IPv4 (3 dots), + for a total of 6 colons and 3 dots. + */ + if dot_count != 3 || colon_count > 6 { return } + + /* + Try to parse IPv4 address. + If successful, we have our least significant 32 bits. + If not, it invalidates the whole address and we can bail. + */ + ipv4, have_ipv4 = parse_ip4_address(pieces_temp[colon_count]) + if !have_ipv4 { return } + } + + /* + Check for `::` being used more than once, and save the skip. + */ + zero_skip := -1 + for i in 1.. 0 { return } + } + + /* + Adjust for trailing `::`. + */ + if pieces[len(pieces) - 1] == "" { + after_skip -= 1 + // Trailing `:` can only be part of `::`. + if after_skip > 0 { return } + } + + /* + Calculate how many zero pieces we skipped. + It should be at least one, considering we encountered a `::`. + */ + num_skipped = IPv6_PIECE_COUNT - before_skip - after_skip + if num_skipped < 1 { return } + + } else { + /* + No zero skip means everything is part of "before the skip". + An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1. + */ + piece_count := len(pieces) + if have_ipv4 { + piece_count += 1 + } + + /* + Do we have the complete set? + */ + if piece_count != IPv6_PIECE_COUNT { return } + + /* + Validate leading and trailing empty parts, as they can only be part of a `::`. + */ + if pieces[0] == "" || pieces[len(pieces) - 1] == "" { return } + + + before_skip = piece_count + after_skip = 0 + num_skipped = 0 + } + + /* + Now try to parse the pieces into a 8 16-bit pieces. + */ + piece_values: [IPv6_PIECE_COUNT]u16be + + idx := 0 + val_idx := 0 + + for _ in 0.. 4 { return } + + if piece != "" { + val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return + piece_values[val_idx] = u16be(val) + } + + idx += 1 + val_idx += 1 + } + + if before_skip == 0 { + idx += 1 + } + + if num_skipped > 0 { + idx += 1 + val_idx += num_skipped + } + + if after_skip > 0 { + for _ in 0.. 4 { return } + + if piece != "" { + val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return + piece_values[val_idx] = u16be(val) + } + + idx += 1 + val_idx += 1 + } + } + + /* + Distribute IPv4 address into last two pieces, if applicable. + */ + if have_ipv4 { + val := u16(ipv4[0]) << 8 + val |= u16(ipv4[1]) + piece_values[6] = u16be(val) + + + val = u16(ipv4[2]) << 8 + val |= u16(ipv4[3]) + piece_values[7] = u16be(val) + } + + return transmute(IP6_Address)piece_values, true +} + +/* + Try parsing as an IPv6 address. + If it's determined not to be, try as an IPv4 address, optionally in non-decimal format. +*/ +parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address { + addr6, ok6 := parse_ip6_address(address_and_maybe_port) + if ok6 do return addr6 + addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address) + if ok4 do return addr4 + return nil +} + +parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) { + addr_str, port, split_ok := split_port(endpoint_str) + if !split_ok do return + + addr := parse_address(addr_str) + if addr == nil do return + + ep = Endpoint { address = addr, port = port } + ok = true + return +} + +Host :: struct { + hostname: string, + port: int, +} +Host_Or_Endpoint :: union { + Host, + Endpoint, +} +Parse_Endpoint_Error :: enum { + Bad_Port = 1, + Bad_Address, + Bad_Hostname, +} + +// Takes a string consisting of a hostname or IP address, and an optional port, +// and return the component parts in a useful form. +parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Network_Error) { + host, port, port_ok := split_port(endpoint_str) + if !port_ok { + return nil, .Bad_Port + } + if addr := parse_address(host); addr != nil { + return Endpoint{addr, port}, nil + } + if !validate_hostname(host) { + return nil, .Bad_Hostname + } + return Host{host, port}, nil +} + + +// Takes an endpoint string and returns its parts. +// Returns ok=false if port is not a number. +split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) { + // IP6 [addr_or_host]:port + if i := strings.last_index(endpoint_str, "]:"); i != -1 { + addr_or_host = endpoint_str[1:i] + port, ok = strconv.parse_int(endpoint_str[i+2:], 10) + + if port > 65535 { + ok = false + } + return + } + + if n := strings.count(endpoint_str, ":"); n == 1 { + // IP4 addr_or_host:port + i := strings.last_index(endpoint_str, ":") + assert(i != -1) + + addr_or_host = endpoint_str[:i] + port, ok = strconv.parse_int(endpoint_str[i+1:], 10) + + if port > 65535 { + ok = false + } + return + } else if n > 1 { + // IP6 address without port + } + + // No port + addr_or_host = endpoint_str + port = 0 + ok = true + return +} + +// Joins an address or hostname with a port. +join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string { + addr_or_host, _, ok := split_port(address_or_host) + if !ok do return addr_or_host + + b := strings.make_builder(allocator) + + addr := parse_address(addr_or_host) + if addr == nil { + // hostname + fmt.sbprintf(&b, "%v:%v", addr_or_host, port) + } else { + switch in addr { + case IP4_Address: + fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port) + case IP6_Address: + fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port) + } + } + return strings.to_string(b) +} + + + +// TODO(tetra): Do we need this? +map_to_ip6 :: proc(addr: Address) -> Address { + if addr6, ok := addr.(IP6_Address); ok { + return addr6 + } + addr4 := addr.(IP4_Address) + addr4_u16 := transmute([2]u16be) addr4 + addr6: IP6_Address + addr6[4] = 0xffff + copy(addr6[5:], addr4_u16[:]) + return addr6 +} + +/* + Returns a temporarily-allocated string representation of the address. + + See RFC 5952 section 4 for IPv6 representation recommendations. +*/ +address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string { + b := strings.make_builder(allocator) + switch v in addr { + case IP4_Address: + fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3]) + case IP6_Address: + /* + First find the longest run of zeroes. + */ + Zero_Run :: struct { + start: int, + end: int, + } + + /* + We're dealing with 0-based indices, appropriately enough for runs of zeroes. + Still, it means we need to initialize runs with some value outside of the possible range. + */ + run := Zero_Run{-1, -1} + best := Zero_Run{-1, -1} + + addr := transmute([8]u16be)v + + last := u16be(1) + for val, i in addr { + /* + If we encounter adjacent zeroes, then start a new run if not already in one. + Also remember the rightmost index regardless, because it'll be the new + frontier of both new and existing runs. + */ + if last == 0 && val == 0 { + run.end = i + if run.start == -1 { + run.start = i - 1 + } + } + + /* + If we're in a run check if its length is better than the best recorded so far. + If so, update the best run's start and end. + */ + if run.start != -1 { + length_to_beat := best.end - best.start + length := run.end - run.start + + if length > length_to_beat { + best = run + } + } + + /* + If we were in a run, this is where we reset it. + */ + if val != 0 { + run = {-1, -1} + } + + last = val + } + + for val, i in addr { + if best.start == i || best.end == i { + /* + For the left and right side of the best zero run, print a `:`. + */ + fmt.sbprint(&b, ":") + } else if i < best.start { + /* + If we haven't made it to the best run yet, print the digit. + Make sure we only print a `:` after the digit if it's not + immediately followed by the run's own leftmost `:`. + */ + fmt.sbprintf(&b, "%x", val) + if i < best.start - 1 { + fmt.sbprintf(&b, ":") + } + } else if i > best.end { + /* + If there are any digits after the zero run, print them. + But don't print the `:` at the end of the IP number. + */ + fmt.sbprintf(&b, "%x", val) + if i != 7 { + fmt.sbprintf(&b, ":") + } + } + } + } + return strings.to_string(b) +} + +// Returns a temporarily-allocated string representation of the endpoint. +// If there's a port, uses the `[address]:port` format. +endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string { + if ep.port == 0 { + return address_to_string(ep.address, allocator) + } else { + s := address_to_string(ep.address, context.temp_allocator) + b := strings.make_builder(allocator) + switch a in ep.address { + case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port) + case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port) + } + return strings.to_string(b) + } +} + +to_string :: proc{address_to_string, endpoint_to_string} + + +family_from_address :: proc(addr: Address) -> Address_Family { + switch in addr { + case IP4_Address: return .IP4 + case IP6_Address: return .IP6 + case: + unreachable() + } +} +family_from_endpoint :: proc(ep: Endpoint) -> Address_Family { + return family_from_address(ep.address) +} + + +Digit_Parse_Base :: enum u8 { + Dec = 0, // No prefix + Oct = 1, // Leading zero + Hex = 2, // 0x prefix + IPv6 = 3, // Unprefixed IPv6 piece hex. Can't be used with other bases. +} +Digit_Parse_Bases :: bit_set[Digit_Parse_Base; u8] +DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex} + +/* + Parses a single unsigned number in requested `bases` from `input`. + `max_value` represents the maximum allowed value for this number. + + Returns the `value`, the `bytes_consumed` so far, and `ok` to signal success or failure. + + An out-of-range or invalid number will return the accumulated value so far (which can be out of range), + the number of bytes consumed leading up the error, and `ok = false`. + + When `.` or `:` are encountered, they'll be considered valid separators and will stop parsing, + returning the valid number leading up to it. + + Other non-digit characters are treated as an error. + + Octal numbers are expected to have a leading zero, with no 'o' format specifier. + Hexadecimal numbers are expected to be preceded by '0x' or '0X'. + Numbers will otherwise be considered to be in base 10. +*/ +parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) { + /* + Default to base 10 + */ + base := u64(10) + input := input + + /* + We keep track of the number of prefix bytes and digit bytes separately. + This way if a prefix is consumed and we encounter a separator or the end of the string, + the number is only considered valid if at least 1 digit byte has been consumed and the value is within range. + */ + prefix_bytes := 0 + digit_bytes := 0 + + /* + IPv6 hex bytes are unprefixed and can't be disambiguated from octal or hex unless the digit is out of range. + If we got the `.IPv6` option, skip prefix scanning and other flags aren't also used. + */ + if .IPv6 in bases { + if bases != {.IPv6} { return } // Must be used on its own. + base = 16 + } else { + /* + Scan for and consume prefix, if applicable. + */ + if len(input) >= 2 && input[0] == '0' { + if .Hex in bases && (input[1] == 'x' || input[1] == 'X') { + base = 16 + input = input[2:] + prefix_bytes = 2 + } + if prefix_bytes == 0 && .Oct in bases { + base = 8 + input = input[1:] + prefix_bytes = 1 + } + } + } + + parse_loop: for ch in input { + switch ch { + case '0'..'7': + digit_bytes += 1 + value = value * base + u64(ch - '0') + + case '8'..'9': + digit_bytes += 1 + + if base == 8 { + /* + Out of range for octal numbers. + */ + return value, digit_bytes + prefix_bytes, false + } + value = value * base + u64(ch - '0') + + case 'a'..'f': + digit_bytes += 1 + + if base == 8 || base == 10 { + /* + Out of range for octal and decimal numbers. + */ + return value, digit_bytes + prefix_bytes, false + } + value = value * base + (u64(ch - 'a') + 10) + + case 'A'..'F': + digit_bytes += 1 + + if base == 8 || base == 10 { + /* + Out of range for octal and decimal numbers. + */ + return value, digit_bytes + prefix_bytes, false + } + value = value * base + (u64(ch - 'A') + 10) + + case '.', ':': + /* + Number separator. Return early. + We don't need to check if the number is in range. + We do that each time through the loop. + */ + break parse_loop + + case: + /* + Invalid character encountered. + */ + return value, digit_bytes + prefix_bytes, false + } + + if value > max_value { + /* + Out-of-range number. + */ + return value, digit_bytes + prefix_bytes, false + } + } + + /* + If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. + */ + return value, digit_bytes + prefix_bytes, digit_bytes >= 1 +} \ No newline at end of file diff --git a/core/net/addr_darwin.odin b/core/net/addr_darwin.odin new file mode 100644 index 000000000..801b89a96 --- /dev/null +++ b/core/net/addr_darwin.odin @@ -0,0 +1,71 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:os" + +// Returns an address for each interface that can be bound to. +get_network_interfaces :: proc() -> []Address { + // TODO + return nil +} + +@private +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(os.in_addr) a, + sin_family = u8(os.AF_INET), + sin_len = size_of(os.sockaddr_in), + } + return + case IP6_Address: + (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(os.in6_addr) a, + sin6_family = u8(os.AF_INET6), + sin6_len = size_of(os.sockaddr_in6), + } + return + } + unreachable() +} + +@private +sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.family { + case u8(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u8(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} diff --git a/core/net/addr_linux.odin b/core/net/addr_linux.odin new file mode 100644 index 000000000..90ce0c9ef --- /dev/null +++ b/core/net/addr_linux.odin @@ -0,0 +1,93 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:os" + +// Returns an address for each interface that can be bound to. +get_network_interfaces :: proc() -> []Address { + // TODO + return nil +} + +@private +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(os.in_addr) a, + sin_family = u16(os.AF_INET), + } + return + case IP6_Address: + (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(os.in6_addr) a, + sin6_family = u16(os.AF_INET6), + } + return + } + unreachable() +} + +sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u16(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} + +sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) { + switch native_addr.sa_family { + case u16(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + //panic("native_addr is neither IP4 or IP6 address") + return {} + } + return +} + +sockaddr_to_endpoint :: proc { sockaddr_basic_to_endpoint, sockaddr_storage_to_endpoint } \ No newline at end of file diff --git a/core/net/addr_openbsd.odin b/core/net/addr_openbsd.odin new file mode 100644 index 000000000..34a49b526 --- /dev/null +++ b/core/net/addr_openbsd.odin @@ -0,0 +1,69 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:os" + +// Returns an address for each interface that can be bound to. +get_network_interfaces :: proc() -> []Address { + // TODO + return nil +} + +@private +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(os.in_addr) a, + sin_family = u8(os.AF_INET), + } + return + case IP6_Address: + (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(os.in6_addr) a, + sin6_family = u8(os.AF_INET6), + } + return + } + unreachable() +} + +@private +sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u8(os.AF_INET): + addr := cast(^os.sockaddr_in)native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u8(os.AF_INET6): + addr := cast(^os.sockaddr_in6)native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} \ No newline at end of file diff --git a/core/net/addr_windows.odin b/core/net/addr_windows.odin new file mode 100644 index 000000000..4225216ca --- /dev/null +++ b/core/net/addr_windows.odin @@ -0,0 +1,69 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import win "core:sys/windows" + +// Returns an address for each interface that can be bound to. +get_network_interfaces :: proc() -> []Address { + // TODO + return nil +} + +@private +endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in { + sin_port = u16be(win.USHORT(ep.port)), + sin_addr = transmute(win.in_addr) a, + sin_family = u16(win.AF_INET), + } + return + case IP6_Address: + (^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 { + sin6_port = u16be(win.USHORT(ep.port)), + sin6_addr = transmute(win.in6_addr) a, + sin6_family = u16(win.AF_INET6), + } + return + } + unreachable() +} + +@private +sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u16(win.AF_INET): + addr := cast(^win.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(win.AF_INET6): + addr := cast(^win.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} \ No newline at end of file diff --git a/core/net/common.odin b/core/net/common.odin new file mode 100644 index 000000000..9e980e88e --- /dev/null +++ b/core/net/common.odin @@ -0,0 +1,437 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. + + This file collects structs, enums and settings applicable to the entire package in one handy place. + Platform-specific ones can be found in their respective `*_windows.odin` and similar files. +*/ +package net + +import "core:runtime" + +/* + TUNEABLES +*/ + +/* + Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new + socket, and the client socket, respectively. + This can also be set on a per-socket basis using the 'options' optional parameter to those procedures. + + When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being + coalesced into fewer network packets. + + This makes the networking layer more eagerly send data when you ask it to, + which can reduce latency by up to 200ms. + + This does mean that a lot of small writes will negatively effect throughput however, + since the Nagle algorithm will be disabled, and each write becomes one + IP packet. This will increase traffic by a factor of 40, with IP and TCP + headers for each payload. + + However, you can avoid this by buffering things up yourself if you wish to send a lot of + short data chunks, when TCP_NODELAY is enabled on that socket. +*/ +ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true) + +/* + See also top of `dns.odin` for DNS configuration. +*/ + +/* + COMMON DEFINITIONS +*/ + +Maybe :: runtime.Maybe + +General_Error :: enum { + Unable_To_Enumerate_Network_Interfaces = 1, +} + +/* + `Platform_Error` is used to wrap errors returned by the different platforms that defy translation into a common error. +*/ +Platform_Error :: enum u32 {} + +/* + NOTE(tetra): Enums in Network_Error should not have a named zero value. + If you have a proc that returns an enum with an Ok=0 value, using or_return from the callsite, when the caller returns a union, works as expected. + However, if that proc returns the union directly, returning the Ok value will NOT work with the caller's or_return, as it will treat Error{.Ok} as != nil, and early-return with it. + + The approach currently taken to avoid this is: + - Remove the named zero values for the enums + - Use the union everywhere +*/ +Network_Error :: union { + General_Error, + Platform_Error, + Create_Socket_Error, + Dial_Error, + Listen_Error, + Accept_Error, + Bind_Error, + TCP_Send_Error, + UDP_Send_Error, + TCP_Recv_Error, + UDP_Recv_Error, + Shutdown_Error, + Socket_Option_Error, + Parse_Endpoint_Error, + Resolve_Error, + DNS_Error, +} + + +Resolve_Error :: enum { + Unable_To_Resolve = 1, +} + +DNS_Error :: enum { + Invalid_Hostname_Error = 1, + Invalid_Hosts_Config_Error, + Invalid_Resolv_Config_Error, + Connection_Error, + Server_Error, + System_Error, +} + +/* + SOCKET OPTIONS & DEFINITIONS +*/ + +TCP_Options :: struct { + no_delay: bool, +} + +default_tcp_options := TCP_Options { + no_delay = ODIN_NET_TCP_NODELAY_DEFAULT, +} + +/* + To allow freely using `Socket` in your own data structures in a cross-platform manner, + we treat it as a handle large enough to accomodate OS-specific notions of socket handles. + + The platform code will perform the cast so you don't have to. +*/ +Socket :: distinct i64 + +TCP_Socket :: distinct Socket +UDP_Socket :: distinct Socket + +Socket_Protocol :: enum { + TCP, + UDP, +} + +Any_Socket :: union { + TCP_Socket, + UDP_Socket, +} + +/* + ADDRESS DEFINITIONS +*/ + +IP4_Address :: distinct [4]u8 +IP6_Address :: distinct [8]u16be +Address :: union {IP4_Address, IP6_Address} + +IP4_Loopback := IP4_Address{127, 0, 0, 1} +IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} + +IP4_Any := IP4_Address{} +IP6_Any := IP6_Address{} + +Endpoint :: struct { + address: Address, + port: int, +} + +Address_Family :: enum { + IP4, + IP6, +} + +Netmask :: distinct Address + +/* + INTERFACE / LINK STATE +*/ +Network_Interface :: struct { + adapter_name: string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage. + friendly_name: string, + description: string, + dns_suffix: string, + + physical_address: string, // MAC address, etc. + mtu: u32, + + unicast: [dynamic]Lease, + multicast: [dynamic]Address, + anycast: [dynamic]Address, + + gateways: [dynamic]Address, + dhcp_v4: Address, + dhcp_v6: Address, + + tunnel_type: Tunnel_Type, + + link: struct { + state: Link_State, + transmit_speed: u64, + receive_speed: u64, + }, +} + +/* + Empty bit set is unknown state. +*/ +Link_States :: enum u32 { + Up = 1, + Down = 2, + Testing = 3, + Dormant = 4, + Not_Present = 5, + Lower_Layer_Down = 6, + Loopback = 7, +} +Link_State :: bit_set[Link_States; u32] + +Lease :: struct { + address: Address, + netmask: Netmask, + lifetime: struct { + valid: u32, + preferred: u32, + lease: u32, + }, + origin: struct { + prefix: Prefix_Origin, + suffix: Suffix_Origin, + }, + address_duplication: Address_Duplication, +} + +Tunnel_Type :: enum i32 { + None = 0, + Other = 1, + Direct = 2, + IPv4_To_IPv6 = 11, + ISA_TAP = 13, + Teredo = 14, + IP_HTTPS = 15, +} + +Prefix_Origin :: enum i32 { + Other = 0, + Manual = 1, + Well_Known = 2, + DHCP = 3, + Router_Advertisement = 4, + Unchanged = 16, +} + +Suffix_Origin :: enum i32 { + Other = 0, + Manual = 1, + Well_Known = 2, + DHCP = 3, + Link_Layer_Address = 4, + Random = 5, + Unchanged = 16, +} + +Address_Duplication :: enum i32 { + Invalid = 0, + Tentative = 1, + Duplicate = 2, + Deprecated = 3, + Preferred = 4, +} + +/* + DNS DEFINITIONS +*/ + +DNS_Configuration :: struct { + /* + Configuration files. + */ + resolv_conf: string, + hosts_file: string, + + // TODO: Allow loading these up with `reload_configuration()` call or the like so we don't have to do it each call. + name_servers: []Endpoint, + hosts_file_entries: []DNS_Record, +} + +DNS_Record_Type :: enum u16 { + DNS_TYPE_A = 0x1, // IP4 address. + DNS_TYPE_NS = 0x2, // IP6 address. + DNS_TYPE_CNAME = 0x5, // Another host name. + DNS_TYPE_MX = 0xf, // Arbitrary binary data or text. + DNS_TYPE_AAAA = 0x1c, // Address of a name (DNS) server. + DNS_TYPE_TEXT = 0x10, // Address and preference priority of a mail exchange server. + DNS_TYPE_SRV = 0x21, // Address, port, priority, and weight of a host that provides a particular service. + + IP4 = DNS_TYPE_A, + IP6 = DNS_TYPE_AAAA, + CNAME = DNS_TYPE_CNAME, + TXT = DNS_TYPE_TEXT, + NS = DNS_TYPE_NS, + MX = DNS_TYPE_MX, + SRV = DNS_TYPE_SRV, +} + +/* + Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field. +*/ +DNS_Record_Base :: struct { + record_name: string, + ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated. +} + +/* + An IP4 address that the domain name maps to. There can be any number of these. +*/ +DNS_Record_IP4 :: struct { + using base: DNS_Record_Base, + address: IP4_Address, +} + +/* + An IPv6 address that the domain name maps to. There can be any number of these. +*/ +DNS_Record_IP6 :: struct { + using base: DNS_Record_Base, + address: IP6_Address, +} + +/* + Another domain name that the domain name maps to. + Domains can be pointed to another domain instead of directly to an IP address. + `get_dns_records` will recursively follow these if you request this type of record. +*/ +DNS_Record_CNAME :: struct { + using base: DNS_Record_Base, + host_name: string, +} + +/* + Arbitrary string data that is associated with the domain name. + Commonly of the form `key=value` to be parsed, though there is no specific format for them. + These can be used for any purpose. +*/ +DNS_Record_TXT :: struct { + using base: DNS_Record_Base, + value: string, +} + +/* + Domain names of other DNS servers that are associated with the domain name. + TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these. +*/ +DNS_Record_NS :: struct { + using base: DNS_Record_Base, + host_name: string, +} + +// Domain names for email servers that are associated with the domain name. +// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred. +DNS_Record_MX :: struct { + using base: DNS_Record_Base, + host_name: string, + preference: int, +} + +// An endpoint for a service that is available through the domain name. +// This is the way to discover the services that a domain name provides. +// +// Clients MUST attempt to contact the host with the lowest priority that they can reach. +// If two hosts have the same priority, they should be contacted in the order according to their weight. +// Hosts with larger weights should have a proportionally higher chance of being contacted by clients. +// A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise). +// +// The host may be "." to indicate that it is "decidedly not available" on this domain. +DNS_Record_SRV :: struct { + // base contains the full name of this record. + // e.g: _sip._tls.example.com + using base: DNS_Record_Base, + + // The hostname or address where this service can be found. + target: string, + // The port on which this service can be found. + port: int, + + service_name: string, // NOTE(tetra): These are substrings of 'record_name' + protocol_name: string, // NOTE(tetra): These are substrings of 'record_name' + + // Lower is higher priority + priority: int, + // Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight. + // The number of seconds that it will take to update the record. + weight: int, +} + +DNS_Record :: union { + DNS_Record_IP4, + DNS_Record_IP6, + DNS_Record_CNAME, + DNS_Record_TXT, + DNS_Record_NS, + DNS_Record_MX, + DNS_Record_SRV, +} + +DNS_Response_Code :: enum u16be { + No_Error, + Format_Error, + Server_Failure, + Name_Error, + Not_Implemented, + Refused, +} + +DNS_Query :: enum u16be { + Host_Address = 1, + Authoritative_Name_Server = 2, + Mail_Destination = 3, + Mail_Forwarder = 4, + CNAME = 5, + All = 255, +} + +DNS_Header :: struct { + id: u16be, + is_response: bool, + opcode: u16be, + is_authoritative: bool, + is_truncated: bool, + is_recursion_desired: bool, + is_recursion_available: bool, + response_code: DNS_Response_Code, +} + +DNS_Record_Header :: struct #packed { + type: u16be, + class: u16be, + ttl: u32be, + length: u16be, +} + +DNS_Host_Entry :: struct { + name: string, + addr: Address, +} \ No newline at end of file diff --git a/core/net/dns.odin b/core/net/dns.odin new file mode 100644 index 000000000..32bc1be6b --- /dev/null +++ b/core/net/dns.odin @@ -0,0 +1,873 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:mem" +import "core:strings" +import "core:time" +import "core:os" + +/* + Default configuration for DNS resolution. +*/ +when ODIN_OS == .Windows { + getenv :: proc(key: string) -> (val: string) { + return os.get_env(key) + } + + DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ + resolv_conf = "", + hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", + } +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + getenv :: proc(key: string) -> (val: string) { + val, _ = os.getenv(key) + return + } + + DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ + resolv_conf = "/etc/resolv.conf", + hosts_file = "/etc/hosts", + } +} else { + #panic("Please add a configuration for this OS.") +} + +@(init) +init_dns_configuration :: proc() { + /* + Resolve %ENVIRONMENT% placeholders in their paths. + */ + dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf) + dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file) +} + +destroy_dns_configuration :: proc() { + delete(dns_configuration.resolv_conf) + delete(dns_configuration.hosts_file) +} + +dns_configuration := DEFAULT_DNS_CONFIGURATION + +/* + Always allocates for consistency. +*/ +replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) { + /* + Nothing to replace. Return a clone of the original. + */ + if strings.count(path, "%") != 2 { + return strings.clone(path), true + } + + left := strings.index(path, "%") + 1 + assert(left > 0 && left <= len(path)) // should be covered by there being two % + + right := strings.index(path[left:], "%") + 1 + assert(right > 0 && right <= len(path)) // should be covered by there being two % + + env_key := path[left: right] + env_val := getenv(env_key) + defer delete(env_val) + + res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1) + + return res, true +} + + +/* + Resolves a hostname to exactly one IP4 and IP6 endpoint. + It's then up to you which one you use. + Note that which address you use to open a socket, determines the type of the socket you get. + + Returns `ok=false` if the host name could not be resolved to any endpoints. + + Returned endpoints have the same port as provided in the string, or 0 if absent. + If you want to use a specific port, just modify the field after the call to this procedure. + + If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped. + This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get + back an endpoint in both cases. +*/ +resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) { + target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return + switch t in target { + case Endpoint: + // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it. + switch in t.address { + case IP4_Address: ep4 = t + case IP6_Address: ep6 = t + case: unreachable() + } + return + + case Host: + err4, err6: Network_Error = ---, --- + ep4, err4 = resolve_ip4(t.hostname) + ep6, err6 = resolve_ip6(t.hostname) + if err4 != nil && err6 != nil { + err = err4 + } + return + } + unreachable() +} +resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) { + target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return + switch t in target { + case Endpoint: + // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it. + switch in t.address { + case IP4_Address: + return t, nil + case IP6_Address: + err = .Unable_To_Resolve + return + } + case Host: + recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + if len(recs) == 0 { + err = .Unable_To_Resolve + return + } + ep4 = { + address = recs[0].(DNS_Record_IP4).address, + port = t.port, + } + return + } + unreachable() +} +resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) { + target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return + switch t in target { + case Endpoint: + // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it. + switch in t.address { + case IP4_Address: + err = .Unable_To_Resolve + return + case IP6_Address: + return t, nil + } + case Host: + recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + if len(recs) == 0 { + err = .Unable_To_Resolve + return + } + ep6 = { + address = recs[0].(DNS_Record_IP6).address, + port = t.port, + } + return + } + unreachable() +} + +/* + `get_dns_records` uses OS-specific methods to query DNS records. +*/ +when ODIN_OS == .Windows { + get_dns_records_from_os :: get_dns_records_windows +} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { + get_dns_records_from_os :: get_dns_records_unix +} else { + #panic("get_dns_records_from_os not implemented on this OS") +} + +/* + A generic DNS client usable on any platform. + Performs a recursive DNS query for records of a particular type for the hostname. + + NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, + meaning that DNS queries for a hostname will resolve through CNAME records until an + IP address is reached. +*/ +get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + context.allocator = allocator + + if type != .SRV { + // NOTE(tetra): 'hostname' can contain underscores when querying SRV records + ok := validate_hostname(hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + } + + hdr := DNS_Header{ + id = 0, + is_response = false, + opcode = 0, + is_authoritative = false, + is_truncated = false, + is_recursion_desired = true, + is_recursion_available = false, + response_code = DNS_Response_Code.No_Error, + } + + id, bits := pack_dns_header(hdr) + dns_hdr := [6]u16be{} + dns_hdr[0] = id + dns_hdr[1] = bits + dns_hdr[2] = 1 + + dns_query := [2]u16be{ u16be(type), 1 } + + output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} + b := strings.builder_from_slice(output[:]) + + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) + ok := encode_hostname(&b, hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) + + dns_packet := output[:strings.builder_len(b)] + + dns_response_buf := [4096]u8{} + dns_response: []u8 + for name_server in name_servers { + conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) + if sock_err != nil { + return nil, .Connection_Error + } + defer close(conn) + + _, send_err := send(conn, dns_packet[:], name_server) + if send_err != nil { + continue + } + + set_err := set_option(conn, .Receive_Timeout, time.Second * 1) + if set_err != nil { + return nil, .Connection_Error + } + + recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:]) + if recv_err == UDP_Recv_Error.Timeout { + continue + } else if recv_err != nil { + continue + } + + if recv_sz == 0 { + continue + } + + dns_response = dns_response_buf[:recv_sz] + + rsp, _ok := parse_response(dns_response, type) + if !_ok { + return nil, .Server_Error + } + + if len(rsp) == 0 { + continue + } + + return rsp[:], nil + } + + return +} + +// `records` slice is also destroyed. +destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { + context.allocator = allocator + + for rec in records { + switch r in rec { + case DNS_Record_IP4: + delete(r.base.record_name) + + case DNS_Record_IP6: + delete(r.base.record_name) + + case DNS_Record_CNAME: + delete(r.base.record_name) + delete(r.host_name) + + case DNS_Record_TXT: + delete(r.base.record_name) + delete(r.value) + + case DNS_Record_NS: + delete(r.base.record_name) + delete(r.host_name) + + case DNS_Record_MX: + delete(r.base.record_name) + delete(r.host_name) + + case DNS_Record_SRV: + delete(r.record_name) + delete(r.target) + } + } + + delete(records, allocator) +} + +/* + TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP + or is that what recursion desired does? Do we need to handle recursion unavailable? + How do we deal with is_authoritative / is_truncated? +*/ + +NAME_MAX :: 255 +LABEL_MAX :: 63 + +pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) { + id = hdr.id + bits = hdr.opcode << 1 | u16be(hdr.response_code) + if hdr.is_response { + bits |= 1 << 15 + } + if hdr.is_authoritative { + bits |= 1 << 10 + } + if hdr.is_truncated { + bits |= 1 << 9 + } + if hdr.is_recursion_desired { + bits |= 1 << 8 + } + if hdr.is_recursion_available { + bits |= 1 << 7 + } + + return id, bits +} + +unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) { + hdr.id = id + hdr.is_response = (bits & (1 << 15)) != 0 + hdr.opcode = (bits >> 11) & 0xF + hdr.is_authoritative = (bits & (1 << 10)) != 0 + hdr.is_truncated = (bits & (1 << 9)) != 0 + hdr.is_recursion_desired = (bits & (1 << 8)) != 0 + hdr.is_recursion_available = (bits & (1 << 7)) != 0 + hdr.response_code = DNS_Response_Code(bits & 0xF) + + return hdr +} + +load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { + context.allocator = allocator + + res, success := os.read_entire_file_from_filename(resolv_conf_path) + if !success { + return + } + defer delete(res) + resolv_str := string(res) + + _name_servers := make([dynamic]Endpoint, 0, allocator) + for line in strings.split_lines_iterator(&resolv_str) { + if len(line) == 0 || line[0] == '#' { + continue + } + + id_str := "nameserver" + if strings.compare(line[:len(id_str)], id_str) != 0 { + continue + } + + server_ip_str := strings.trim_left_space(line[len(id_str):]) + if len(server_ip_str) == 0 { + continue + } + + addr := parse_address(server_ip_str) + endpoint := Endpoint{ + addr, + 53, + } + append(&_name_servers, endpoint) + } + + return _name_servers[:], true +} + +load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { + context.allocator = allocator + + res, success := os.read_entire_file_from_filename(hosts_file_path, allocator) + if !success { + return + } + defer delete(res) + + _hosts := make([dynamic]DNS_Host_Entry, 0, allocator) + hosts_str := string(res) + for line in strings.split_lines_iterator(&hosts_str) { + if len(line) == 0 || line[0] == '#' { + continue + } + + splits := strings.fields(line) + defer delete(splits) + + ip_str := splits[0] + addr := parse_address(ip_str) + if addr == nil { + continue + } + + for hostname in splits[1:] { + if len(hostname) == 0 { + continue + } + + append(&_hosts, DNS_Host_Entry{hostname, addr}) + } + } + + return _hosts[:], true +} + +/* + www.google.com -> 3www6google3com0 +*/ +encode_hostname :: proc(b: ^strings.Builder, hostname: string, allocator := context.allocator) -> (ok: bool) { + _hostname := hostname + for section in strings.split_iterator(&_hostname, ".") { + if len(section) > LABEL_MAX { + return + } + + strings.write_byte(b, u8(len(section))) + strings.write_string(b, section) + } + strings.write_byte(b, 0) + + return true +} + +skip_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (encode_size: int, ok: bool) { + out_size := 0 + + cur_idx := start_idx + iteration_max := 0 + top: for cur_idx < len(packet) { + if packet[cur_idx] == 0 { + out_size += 1 + break + } + + if iteration_max > 255 { + return + } + + if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 { + return + } + + switch packet[cur_idx] { + case 0xC0: + out_size += 2 + break top + case: + label_size := int(packet[cur_idx]) + 1 + idx2 := cur_idx + label_size + + if idx2 < cur_idx + 1 || idx2 > len(packet) { + return + } + + out_size += label_size + cur_idx = idx2 + } + + iteration_max += 1 + } + + if start_idx + out_size > len(packet) { + return + } + + return out_size, true +} + +decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) { + output := [NAME_MAX]u8{} + b := strings.builder_from_slice(output[:]) + + // If you're on level 0, update out_bytes, everything through a pointer + // doesn't count towards this hostname's packet length + + // Evaluate tokens to generate the hostname + out_size := 0 + level := 0 + print_size := 0 + cur_idx := start_idx + iteration_max := 0 + labels_added := 0 + for cur_idx < len(packet) { + if packet[cur_idx] == 0 { + + if (level == 0) { + out_size += 1 + } + + break + } + + if iteration_max > 255 { + return + } + + if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 { + return + } + + switch packet[cur_idx] { + + // This is a offset to more data in the packet, jump to it + case 0xC0: + pkt := packet[cur_idx:cur_idx+2] + val := (^u16be)(raw_data(pkt))^ + offset := int(val & 0x3FFF) + if offset > len(packet) { + return + } + + cur_idx = offset + + if (level == 0) { + out_size += 2 + level += 1 + } + + // This is a label, insert it into the hostname + case: + label_size := int(packet[cur_idx]) + idx2 := cur_idx + label_size + 1 + if idx2 < cur_idx + 1 || idx2 > len(packet) { + return + } + + if print_size + label_size + 1 > NAME_MAX { + return + } + + if labels_added > 0 { + strings.write_byte(&b, '.') + } + strings.write_bytes(&b, packet[cur_idx+1:idx2]) + print_size += label_size + 1 + labels_added += 1 + + cur_idx = idx2 + + if (level == 0) { + out_size += label_size + 1 + } + } + + iteration_max += 1 + } + + if start_idx + out_size > len(packet) { + return + } + + return strings.clone(strings.to_string(b), allocator), out_size, true +} + +// Uses RFC 952 & RFC 1123 +validate_hostname :: proc(hostname: string) -> (ok: bool) { + if len(hostname) > 255 || len(hostname) == 0 { + return + } + + if hostname[0] == '-' { + return + } + + _hostname := hostname + for label in strings.split_iterator(&_hostname, ".") { + if len(label) > 63 || len(label) == 0 { + return + } + + for ch in label { + switch ch { + case: + return + case 'a'..'z', 'A'..'Z', '0'..'9', '-': + continue + } + } + } + + return true +} + +parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) { + record_buf := packet[cur_off^:] + + srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return + // TODO(tetra): Not sure what we should call this. + // Is it really only used in SRVs? + // Maybe some refactoring is required? + + ahdr_sz := size_of(DNS_Record_Header) + if len(record_buf) - hn_sz < ahdr_sz { + return + } + + record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz] + record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes) + + data_sz := record_hdr.length + data_off := cur_off^ + int(hn_sz) + int(ahdr_sz) + data := packet[data_off:data_off+int(data_sz)] + cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz) + + if u16be(filter) != record_hdr.type { + return nil, true + } + + _record: DNS_Record + #partial switch DNS_Record_Type(record_hdr.type) { + case .IP4: + if len(data) != 4 { + return + } + + addr := (^IP4_Address)(raw_data(data))^ + + _record = DNS_Record_IP4{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + address = addr, + } + + case .IP6: + if len(data) != 16 { + return + } + + addr := (^IP6_Address)(raw_data(data))^ + + _record = DNS_Record_IP6{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + address = addr, + } + + case .CNAME: + hostname, _ := decode_hostname(packet, data_off) or_return + + _record = DNS_Record_CNAME{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + host_name = hostname, + } + + case .TXT: + _record = DNS_Record_TXT{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + value = strings.clone(string(data)), + } + + case .NS: + name, _ := decode_hostname(packet, data_off) or_return + + _record = DNS_Record_NS{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + host_name = name, + } + + case .SRV: + if len(data) <= 6 { + return + } + + priority: u16be = mem.slice_data_cast([]u16be, data)[0] + weight: u16be = mem.slice_data_cast([]u16be, data)[1] + port: u16be = mem.slice_data_cast([]u16be, data)[2] + target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return + + // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' + // The record name is the name of the record. + // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up + // by making this request in the first place. + + // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name. + // It's already cloned, after all. I wouldn't put them on the temp allocator like this. + + parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator) + if len(parts) != 3 { + return + } + service_name, protocol_name := parts[0], parts[1] + + _record = DNS_Record_SRV{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + target = target, + service_name = service_name, + protocol_name = protocol_name, + priority = int(priority), + weight = int(weight), + port = int(port), + } + + case .MX: + if len(data) <= 2 { + return + } + + preference: u16be = mem.slice_data_cast([]u16be, data)[0] + hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return + + _record = DNS_Record_MX{ + base = DNS_Record_Base{ + record_name = strings.clone(srv_record_name), + ttl_seconds = u32(record_hdr.ttl), + }, + host_name = hostname, + preference = int(preference), + } + + case: + return + + } + + return _record, true +} + +/* + DNS Query Response Format: + - DNS_Header (packed) + - Query Count + - Answer Count + - Authority Count + - Additional Count + - Query[] + - Hostname -- encoded + - Type + - Class + - Answer[] + - DNS Record Data + - Authority[] + - DNS Record Data + - Additional[] + - DNS Record Data + + DNS Record Data: + - DNS_Record_Header + - Data[] +*/ + +parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) { + header_size_bytes :: 12 + if len(response) < header_size_bytes { + return + } + + _records := make([dynamic]DNS_Record, 0) + + dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:header_size_bytes]) + hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1]) + if !hdr.is_response { + return + } + + question_count := int(dns_hdr_chunks[2]) + if question_count != 1 { + return + } + answer_count := int(dns_hdr_chunks[3]) + authority_count := int(dns_hdr_chunks[4]) + additional_count := int(dns_hdr_chunks[5]) + + cur_idx := header_size_bytes + + for i := 0; i < question_count; i += 1 { + if cur_idx == len(response) { + continue + } + + dq_sz :: 4 + hn_sz := skip_hostname(response, cur_idx, context.temp_allocator) or_return + dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz]) + + cur_idx += hn_sz + dq_sz + } + + for i := 0; i < answer_count; i += 1 { + if cur_idx == len(response) { + continue + } + + rec := parse_record(response, &cur_idx, filter) or_return + if rec == nil { + continue + } + + append(&_records, rec) + } + + for i := 0; i < authority_count; i += 1 { + if cur_idx == len(response) { + continue + } + + rec := parse_record(response, &cur_idx, filter) or_return + if rec == nil { + continue + } + + append(&_records, rec) + } + + for i := 0; i < additional_count; i += 1 { + if cur_idx == len(response) { + continue + } + + rec := parse_record(response, &cur_idx, filter) or_return + if rec == nil { + continue + } + + append(&_records, rec) + } + + return _records[:], true +} diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin new file mode 100644 index 000000000..7f57927fd --- /dev/null +++ b/core/net/dns_unix.odin @@ -0,0 +1,83 @@ +//+build linux, darwin, freebsd, openbsd, !windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:strings" + +get_dns_records_unix :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + context.allocator = allocator + + if type != .SRV { + // NOTE(tetra): 'hostname' can contain underscores when querying SRV records + ok := validate_hostname(hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + } + + name_servers, resolve_ok := load_resolv_conf(dns_configuration.resolv_conf) + defer delete(name_servers) + if !resolve_ok { + return nil, .Invalid_Resolv_Config_Error + } + if len(name_servers) == 0 { + return + } + + hosts, hosts_ok := load_hosts(dns_configuration.hosts_file) + defer delete(hosts) + if !hosts_ok { + return nil, .Invalid_Hosts_Config_Error + } + if len(hosts) == 0 { + return + } + + host_overrides := make([dynamic]DNS_Record) + for host in hosts { + if strings.compare(host.name, hostname) != 0 { + continue + } + + if type == .IP4 && family_from_address(host.addr) == .IP4 { + record := DNS_Record_IP4{ + base = { + record_name = strings.clone(hostname), + ttl_seconds = 0, + }, + address = host.addr.(IP4_Address), + } + append(&host_overrides, record) + } else if type == .IP6 && family_from_address(host.addr) == .IP6 { + record := DNS_Record_IP6{ + base = { + record_name = strings.clone(hostname), + ttl_seconds = 0, + }, + address = host.addr.(IP6_Address), + } + append(&host_overrides, record) + } + } + + if len(host_overrides) > 0 { + return host_overrides[:], nil + } + + return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) +} diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin new file mode 100644 index 000000000..ae38ca05f --- /dev/null +++ b/core/net/dns_windows.odin @@ -0,0 +1,166 @@ +//+build windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:strings" +import "core:mem" + +import win "core:sys/windows" + +// Performs a recursive DNS query for records of a particular type for the hostname. +// +// NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, +// meaning that DNS queries for a hostname will resolve through CNAME records until an +// IP address is reached. +// +// WARNING: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! +// See `destroy_records`. +get_dns_records_windows :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + context.allocator = allocator + + host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) + rec: ^win.DNS_RECORD + res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil) + + switch u32(res) { + case 0: + // okay + case win.ERROR_INVALID_NAME: + return nil, .Invalid_Hostname_Error + case win.DNS_INFO_NO_RECORDS: + return + case: + return nil, .System_Error + } + defer win.DnsRecordListFree(rec, 1) // 1 means that we're freeing a list... because the proc name isn't enough. + + count := 0 + for r := rec; r != nil; r = r.pNext { + if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but... + count += 1 + } + + + recs := make([dynamic]DNS_Record, 0, count) + if recs == nil do return nil, .System_Error // return no results if OOM. + + for r := rec; r != nil; r = r.pNext { + if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but... + + base_record := DNS_Record_Base{ + record_name = strings.clone(string(r.pName)), + ttl_seconds = r.dwTtl, + } + + switch DNS_Record_Type(r.wType) { + case .IP4: + addr := IP4_Address(transmute([4]u8)r.Data.A) + record := DNS_Record_IP4{ + base = base_record, + address = addr, + } + append(&recs, record) + + case .IP6: + addr := IP6_Address(transmute([8]u16be) r.Data.AAAA) + record := DNS_Record_IP6{ + base = base_record, + address = addr, + } + append(&recs, record) + + case .CNAME: + + hostname := strings.clone(string(r.Data.CNAME)) + record := DNS_Record_CNAME{ + base = base_record, + host_name = hostname, + } + append(&recs, record) + + case .TXT: + n := r.Data.TXT.dwStringCount + ptr := &r.Data.TXT.pStringArray + c_strs := mem.slice_ptr(ptr, int(n)) + + for cstr in c_strs { + record := DNS_Record_TXT{ + base = base_record, + value = strings.clone(string(cstr)), + } + append(&recs, record) + } + + case .NS: + hostname := strings.clone(string(r.Data.NS)) + record := DNS_Record_NS{ + base = base_record, + host_name = hostname, + } + append(&recs, record) + + case .MX: + /* + TODO(tetra): Order by preference priority? (Prefer hosts with lower preference values.) + Or maybe not because you're supposed to just use the first one that works + and which order they're in changes every few calls. + */ + + record := DNS_Record_MX{ + base = base_record, + host_name = strings.clone(string(r.Data.MX.pNameExchange)), + preference = int(r.Data.MX.wPreference), + } + append(&recs, record) + + case .SRV: + target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on + priority := int(r.Data.SRV.wPriority) + weight := int(r.Data.SRV.wWeight) + port := int(r.Data.SRV.wPort) + + // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' + // The record name is the name of the record. + // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up + // by making this request in the first place. + + // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name. + // It's already cloned, after all. I wouldn't put them on the temp allocator like this. + + parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator) + if len(parts) != 3 { + continue + } + service_name, protocol_name := parts[0], parts[1] + + append(&recs, DNS_Record_SRV { + base = base_record, + target = target, + port = port, + service_name = service_name, + protocol_name = protocol_name, + priority = priority, + weight = weight, + + }) + } + } + + records = recs[:] + return +} diff --git a/core/net/doc.odin b/core/net/doc.odin new file mode 100644 index 000000000..0c6c08daa --- /dev/null +++ b/core/net/doc.odin @@ -0,0 +1,47 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. + + Features: + - Supports Windows, Linux and OSX. + - Opening and closing of TCP and UDP sockets. + - Sending to and receiving from these sockets. + - DNS name lookup, using either the OS or our own resolver. + + Planned: + - Nonblocking IO + - `Connection` struct + A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle. + - IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures. + - Use `context.temp_allocator` instead of stack-based arenas? + And check it's the default temp allocator or can give us 4 MiB worth of memory + without punting to the main allocator by comparing their addresses in an @(init) procedure. + Panic if this assumption is not met. + + - Document assumptions about libc usage (or avoidance thereof) for each platform. + + Assumptions: + - For performance reasons this package relies on the `context.temp_allocator` in some places. + + You can replace the default `context.temp_allocator` with your own as long as it meets + this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed. + + If this expectation is not met, the package's @(init) procedure will attempt to detect + this and panic to avoid temp allocations prematurely overwriting data and garbling results, + or worse. This means that should you replace the temp allocator with an insufficient one, + we'll do our best to loudly complain the first time you try it. + +*/ +package net \ No newline at end of file diff --git a/core/net/interface.odin b/core/net/interface.odin new file mode 100644 index 000000000..354cba53f --- /dev/null +++ b/core/net/interface.odin @@ -0,0 +1,68 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:strings" + +/* + `destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`. +*/ +destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) { + context.allocator = allocator + + for i in interfaces { + delete(i.adapter_name) + delete(i.friendly_name) + delete(i.description) + delete(i.dns_suffix) + + delete(i.physical_address) + + delete(i.unicast) + delete(i.multicast) + delete(i.anycast) + delete(i.gateways) + } + delete(interfaces, allocator) +} + +/* + Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string. +*/ +physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) { + context.allocator = allocator + + MAC_HEX := "0123456789ABCDEF" + + if len(phy_addr) == 0 { + return "" + } + + buf: strings.Builder + + for b, i in phy_addr { + if i > 0 { + strings.write_rune_builder(&buf, ':') + } + + hi := rune(MAC_HEX[b >> 4]) + lo := rune(MAC_HEX[b & 15]) + strings.write_rune_builder(&buf, hi) + strings.write_rune_builder(&buf, lo) + } + return strings.to_string(buf) +} \ No newline at end of file diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin new file mode 100644 index 000000000..9a9bda5d3 --- /dev/null +++ b/core/net/interface_darwin.odin @@ -0,0 +1,23 @@ +//+build darwin +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +/* + TODO: Implement. Can probably use the (current) Linux implementation, + which will itself be switched over to talking to the kernel via NETLINK protocol once we have raw sockets. +*/ diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin new file mode 100644 index 000000000..c5973fa2d --- /dev/null +++ b/core/net/interface_linux.odin @@ -0,0 +1,147 @@ +//+build linux, darwin, openbsd, !windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +/* + This file uses `getifaddrs` libc call to enumerate interfaces. + + TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc. +*/ + +import "core:os" +import "core:strings" + +/* + `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. +*/ +enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + context.allocator = allocator + + head: ^os.ifaddrs + + if res := os._getifaddrs(&head); res < 0 { + return {}, .Unable_To_Enumerate_Network_Interfaces + } + + /* + Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct. + We're going to have to iterate over a list and coalesce information as we go. + */ + + ifaces: map[string]^Network_Interface + defer delete(ifaces) + + for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next { + adapter_name := string(ifaddr.name) + + /* + Check if we have seen this interface name before so we can reuse the `Network_Interface`. + Else, create a new one. + */ + if adapter_name not_in ifaces { + ifaces[adapter_name] = new(Network_Interface) + ifaces[adapter_name].adapter_name = strings.clone(adapter_name) + } + iface := ifaces[adapter_name] + + address: Address + netmask: Netmask + + if ifaddr.address != nil { + switch int(ifaddr.address.sa_family) { + case os.AF_INET, os.AF_INET6: + address = sockaddr_to_endpoint(ifaddr.address).address + + case os.AF_PACKET: + /* + For some obscure reason the 64-bit `getifaddrs` calls returns a pointer to a + 32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count + is truncated beyond usefulness. + + We're not going to retrieve stats now. Instead this serves as a reminder to use + the NETLINK protocol for this purpose. + + But in case you were curious: + stats := transmute(^os.rtnl_link_stats)ifaddr.data + fmt.println(stats) + */ + case: + } + } + + if ifaddr.netmask != nil { + switch int(ifaddr.netmask.sa_family) { + case os.AF_INET, os.AF_INET6: + netmask = Netmask(sockaddr_to_endpoint(ifaddr.netmask).address) + case: + } + } + + if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags { + switch int(ifaddr.broadcast_or_dest.sa_family) { + case os.AF_INET, os.AF_INET6: + broadcast := sockaddr_to_endpoint(ifaddr.broadcast_or_dest).address + append(&iface.multicast, broadcast) + case: + } + } + + if address != nil { + lease := Lease{ + address = address, + netmask = netmask, + } + append(&iface.unicast, lease) + } + + /* + TODO: Refine this based on the type of adapter. + */ + state := Link_State{} + + if .UP in ifaddr.flags { + state |= {.Up} + } + + if .DORMANT in ifaddr.flags { + state |= {.Dormant} + } + + if .LOOPBACK in ifaddr.flags { + state |= {.Loopback} + } + + iface.link.state = state + } + + /* + Free the OS structures. + */ + os._freeifaddrs(head) + + /* + Turn the map into a slice to return. + */ + _interfaces := make([dynamic]Network_Interface, 0, allocator) + for _, iface in ifaces { + append(&_interfaces, iface^) + free(iface) + } + + return _interfaces[:], {} +} \ No newline at end of file diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin new file mode 100644 index 000000000..57cc0e8ef --- /dev/null +++ b/core/net/interface_windows.odin @@ -0,0 +1,182 @@ +//+build windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import sys "core:sys/windows" +import win32 "core:sys/win32" +import strings "core:strings" + +MAX_INTERFACE_ENUMERATION_TRIES :: 3 + +/* + `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. +*/ +enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + context.allocator = allocator + + buf: []u8 + defer delete(buf) + buf_size: u32 + + res: u32 + + gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES { + res = sys.get_adapters_addresses( + .Unspecified, // Return both IPv4 and IPv6 adapters. + sys.GAA_Flags{ + .Include_Prefix, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses. + .Include_Gateways, // (Vista+) Return the addresses of default gateways. + .Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order. + }, + nil, // Reserved + (^sys.IP_Adapter_Addresses)(raw_data(buf)), + &buf_size, + ) + + switch res { + case 111: // ERROR_BUFFER_OVERFLOW: + delete(buf) + buf = make([]u8, buf_size) + case 0: + break gaa + case: + return {}, Platform_Error(res) + } + } + + if res != 0 { + return {}, .Unable_To_Enumerate_Network_Interfaces + } + + _interfaces := make([dynamic]Network_Interface, 0, allocator) + + for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { + interface := Network_Interface{ + adapter_name = strings.clone(string(adapter.AdapterName)), + friendly_name = wstring_to_string(adapter.FriendlyName), + description = wstring_to_string(adapter.Description), + dns_suffix = wstring_to_string(adapter.DnsSuffix), + + mtu = adapter.MTU, + + link = { + transmit_speed = adapter.TransmitLinkSpeed, + receive_speed = adapter.ReceiveLinkSpeed, + }, + } + + if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) { + interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength]) + } + + for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next { + win_addr := parse_socket_address(u_addr.Address) + + lease := Lease{ + address = win_addr.address, + origin = { + prefix = Prefix_Origin(u_addr.PrefixOrigin), + suffix = Suffix_Origin(u_addr.SuffixOrigin), + }, + lifetime = { + valid = u_addr.ValidLifetime, + preferred = u_addr.PreferredLifetime, + lease = u_addr.LeaseLifetime, + }, + address_duplication = Address_Duplication(u_addr.DadState), + } + append(&interface.unicast, lease) + } + + for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next { + addr := parse_socket_address(a_addr.Address) + append(&interface.anycast, addr.address) + } + + for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next { + addr := parse_socket_address(m_addr.Address) + append(&interface.multicast, addr.address) + } + + for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next { + addr := parse_socket_address(g_addr.Address) + append(&interface.gateways, addr.address) + } + + interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address + interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address + + switch adapter.OperStatus { + case .Up: interface.link.state = {.Up} + case .Down: interface.link.state = {.Down} + case .Testing: interface.link.state = {.Testing} + case .Dormant: interface.link.state = {.Dormant} + case .NotPresent: interface.link.state = {.Not_Present} + case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down} + case .Unknown: fallthrough + case: interface.link.state = {} + } + + interface.tunnel_type = Tunnel_Type(adapter.TunnelType) + + append(&_interfaces, interface) + } + + return _interfaces[:], {} +} + +/* + Takes a UTF-16 Wstring and clones it. +*/ +wstring_to_string :: proc(s: ^u16, max_size := 256, allocator := context.allocator) -> (res: string) { + temp := win32.wstring_to_utf8((win32.Wstring)(s), max_size, context.temp_allocator) + return strings.clone(temp[:len(temp)], allocator) +} + +/* + Interpret SOCKET_ADDRESS as an Address +*/ +parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) { + if addr_in.lpSockaddr == nil { + return // Empty or invalid address type + } + + sock := addr_in.lpSockaddr^ + + switch sock.sa_family { + case u16(sys.AF_INET): + win_addr := cast(^sys.sockaddr_in)addr_in.lpSockaddr + port := int(win_addr.sin_port) + return Endpoint { + address = IP4_Address(transmute([4]byte)win_addr.sin_addr), + port = port, + } + + case u16(sys.AF_INET6): + win_addr := cast(^sys.sockaddr_in6)addr_in.lpSockaddr + port := int(win_addr.sin6_port) + return Endpoint { + address = IP6_Address(transmute([8]u16be)win_addr.sin6_addr), + port = port, + } + + + case: return // Empty or invalid address type + } + unreachable() +} \ No newline at end of file diff --git a/core/net/socket.odin b/core/net/socket.odin new file mode 100644 index 000000000..1fa57aac0 --- /dev/null +++ b/core/net/socket.odin @@ -0,0 +1,87 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +// +// TODO(tetra): Bluetooth, Raw +// + +any_socket_to_socket :: proc(any_socket: Any_Socket) -> Socket { + switch s in any_socket { + case TCP_Socket: return Socket(s) + case UDP_Socket: return Socket(s) + case: + return Socket({}) + } +} + +/* + Expects both hostname and port to be present in the `hostname_and_port` parameter, either as: + `a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent. + + Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`. +*/ +dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + target := parse_hostname_or_endpoint(hostname_and_port) or_return + switch t in target { + case Endpoint: + return dial_tcp_from_endpoint(t, options) + case Host: + if t.port == 0 { + return 0, .Port_Required + } + ep4, ep6 := resolve(t.hostname) or_return + ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4. + ep.port = t.port + return dial_tcp_from_endpoint(ep, options) + } + unreachable() +} + +/* + Expects the `hostname` as a string and `port` as a `int`. + `parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP. + + If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param. +*/ +dial_tcp_from_hostname_string_and_explicit_port :: proc(hostname: string, port: int, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + target := parse_hostname_or_endpoint(hostname) or_return + switch t in target { + case Endpoint: + return dial_tcp_from_endpoint({t.address, port}, options) + case Host: + if port == 0 { + return 0, .Port_Required + } + ep4, ep6 := resolve(t.hostname) or_return + ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4. + ep.port = port + return dial_tcp_from_endpoint(ep, options) + } + unreachable() +} + +dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + return dial_tcp_from_endpoint({address, port}, options) +} + +dial_tcp :: proc{ + dial_tcp_from_endpoint, + dial_tcp_from_address_and_port, + dial_tcp_from_hostname_and_port_string, + dial_tcp_from_hostname_string_and_explicit_port, +} \ No newline at end of file diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin new file mode 100644 index 000000000..89ad7b31c --- /dev/null +++ b/core/net/socket_darwin.odin @@ -0,0 +1,513 @@ +// +build darwin +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import "core:os" +import "core:time" + +Platform_Socket :: os.Socket + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + c_type, c_protocol, c_family: int + + switch family { + case .IP4: c_family = os.AF_INET + case .IP6: c_family = os.AF_INET6 + case: + unreachable() + } + + switch protocol { + case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP + case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP + case: + unreachable() + } + + sock, ok := os.socket(c_family, c_type, c_protocol) + if ok != os.ERROR_NONE { + err = Create_Socket_Error(ok) + return + } + + switch protocol { + case .TCP: return TCP_Socket(sock), nil + case .UDP: return UDP_Socket(sock), nil + case: + unreachable() + } +} + + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + return 0, .Port_Required + } + + family := family_from_endpoint(endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + _ = set_option(skt, .Reuse_Address, true) + + sockaddr := endpoint_to_sockaddr(endpoint) + res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) + if res != os.ERROR_NONE { + err = Dial_Error(res) + return + } + + return +} + + +Bind_Error :: enum c.int { + // Another application is currently bound to this endpoint. + Address_In_Use = c.int(os.EADDRINUSE), + // The address is not a local address on this machine. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Broadcast_Disabled = c.int(os.EACCES), + // The address family of the address does not match that of the socket. + Address_Family_Mismatch = c.int(os.EFAULT), + // The socket is already bound to an address. + Already_Bound = c.int(os.EINVAL), + // There are not enough ephemeral ports available. + No_Ports_Available = c.int(os.ENOBUFS), +} + +bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := endpoint_to_sockaddr(ep) + s := any_socket_to_socket(skt) + res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) + if res != os.ERROR_NONE { + err = Bind_Error(res) + } + return +} + + +// This type of socket becomes bound when you try to send data. +// This is likely what you want if you want to send data unsolicited. +// +// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. +make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { + sock := create_socket(family, .UDP) or_return + skt = sock.(UDP_Socket) + return +} + +// This type of socket is bound immediately, which enables it to receive data on the port. +// Since it's UDP, it's also able to send data without receiving any first. +// +// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. +// +// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. +make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { + skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return + bind(skt, {bound_address, port}) or_return + return +} + + + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { + assert(backlog > 0 && i32(backlog) < max(i32)) + + family := family_from_endpoint(interface_endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + // + // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! + set_option(sock, .Reuse_Address, true) or_return + + bind(sock, interface_endpoint) or_return + + res := os.listen(Platform_Socket(skt), backlog) + if res != os.ERROR_NONE { + err = Listen_Error(res) + return + } + + return +} + + + +Accept_Error :: enum c.int { + Reset = c.int(os.ECONNRESET), // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + sockaddr: os.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + + client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + if ok != os.ERROR_NONE { + err = Accept_Error(ok) + return + } + client = TCP_Socket(client_sock) + source = sockaddr_to_endpoint(&sockaddr) + return +} + + + +close :: proc(skt: Any_Socket) { + s := any_socket_to_socket(skt) + os.close(os.Handle(Platform_Socket(s))) +} + + + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + Connection_Broken = c.int(os.ENETRESET), // TODO(tetra): Is this error actually possible here? + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) <= 0 { + return + } + res, ok := os.recv(Platform_Socket(skt), buf, 0) + if ok != os.ERROR_NONE { + err = TCP_Recv_Error(ok) + return + } + return int(res), nil +} + +UDP_Recv_Error :: enum c.int { + // The buffer is too small to fit the entire message, and the message was truncated. + Truncated = c.int(os.EMSGSIZE), + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The socket must be bound for this operation, but isn't. + Socket_Not_Bound = c.int(os.EINVAL), +} + +recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) <= 0 { + return + } + + from: os.SOCKADDR_STORAGE_LH + fromsize := c.int(size_of(from)) + res, ok := os.recvfrom(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) + if ok != os.ERROR_NONE { + err = UDP_Recv_Error(ok) + return + } + + bytes_read = int(res) + remote_endpoint = sockaddr_to_endpoint(&from) + return +} + +recv :: proc{recv_tcp, recv_udp} + + + +// TODO +TCP_Send_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), // TODO: merge with other errors? + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), +} + +// Repeatedly sends data until the entire buffer is sent. +// If a send fails before all data is sent, returns the amount +// sent up to that point. +send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + res, ok := os.send(Platform_Socket(skt), remaining, 0) + if ok != os.ERROR_NONE { + err = TCP_Send_Error(ok) + return + } + bytes_written += int(res) + } + return +} + +// TODO +UDP_Send_Error :: enum c.int { + // The message is too big. No data was sent. + Truncated = c.int(os.EMSGSIZE), + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + No_Outbound_Ports_Available = c.int(os.EAGAIN), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + // No memory was available to properly manage the send queue. + No_Memory_Available = c.int(os.ENOMEM), +} + +send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + toaddr := endpoint_to_sockaddr(to) + for bytes_written < len(buf) { + limit := min(1<<31, len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + res, ok := os.sendto(Platform_Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) + if ok != os.ERROR_NONE { + err = UDP_Send_Error(ok) + return + } + bytes_written += int(res) + } + return +} + +send :: proc{send_tcp, send_udp} + + + + +Shutdown_Manner :: enum c.int { + Receive = c.int(os.SHUT_RD), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + s := any_socket_to_socket(skt) + res := os.shutdown(Platform_Socket(s), int(manner)) + if res != os.ERROR_NONE { + return Shutdown_Error(res) + } + return +} + + + + +Socket_Option :: enum c.int { + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), + TCP_Nodelay = c.int(os.TCP_NODELAY), + + Linger = c.int(os.SO_LINGER), + + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO), + Send_Timeout = c.int(os.SO_SNDTIMEO), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} + +set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP + + // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; + // it _has_ to be a b32. + // I haven't tested if you can give more than that. + bool_value: b32 + int_value: i32 + timeval_value: os.Timeval + + ptr: rawptr + len: os.socklen_t + + switch option { + case + .Reuse_Address, + .Keep_Alive, + .Out_Of_Bounds_Data_Inline, + .TCP_Nodelay: + // TODO: verify whether these are options or not on Linux + // .Broadcast, + // .Conditional_Accept, + // .Dont_Linger: + switch x in value { + case bool, b8: + x2 := x + bool_value = b32((^bool)(&x2)^) + case b16: + bool_value = b32(x) + case b32: + bool_value = b32(x) + case b64: + bool_value = b32(x) + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case + .Linger, + .Send_Timeout, + .Receive_Timeout: + t, ok := value.(time.Duration) + if !ok do panic("set_option() value must be a time.Duration here", loc) + + nanos := time.duration_nanoseconds(t) + timeval_value.nanoseconds = int(nanos % 1e9) + timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9 + + ptr = &timeval_value + len = size_of(timeval_value) + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + // TODO: check for out of range values and return .Value_Out_Of_Range? + switch i in value { + case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) + case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) + case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + } + + skt := any_socket_to_socket(s) + res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) + if res != os.ERROR_NONE { + return Socket_Option_Error(res) + } + + return nil +} diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin new file mode 100644 index 000000000..dcc48d3fa --- /dev/null +++ b/core/net/socket_linux.odin @@ -0,0 +1,532 @@ +// +build linux +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import "core:os" +import "core:time" + +Platform_Socket :: os.Socket + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + c_type, c_protocol, c_family: int + + switch family { + case .IP4: c_family = os.AF_INET + case .IP6: c_family = os.AF_INET6 + case: + unreachable() + } + + switch protocol { + case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP + case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP + case: + unreachable() + } + + sock, ok := os.socket(c_family, c_type, c_protocol) + if ok != os.ERROR_NONE { + err = Create_Socket_Error(ok) + return + } + + switch protocol { + case .TCP: return TCP_Socket(sock), nil + case .UDP: return UDP_Socket(sock), nil + case: + unreachable() + } +} + + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + return 0, .Port_Required + } + + family := family_from_endpoint(endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + _ = set_option(skt, .Reuse_Address, true) + + sockaddr := endpoint_to_sockaddr(endpoint) + res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + if res != os.ERROR_NONE { + err = Dial_Error(res) + return + } + + if options.no_delay { + _ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + } + + return +} + + +Bind_Error :: enum c.int { + // Another application is currently bound to this endpoint. + Address_In_Use = c.int(os.EADDRINUSE), + // The address is not a local address on this machine. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Broadcast_Disabled = c.int(os.EACCES), + // The address family of the address does not match that of the socket. + Address_Family_Mismatch = c.int(os.EFAULT), + // The socket is already bound to an address. + Already_Bound = c.int(os.EINVAL), + // There are not enough ephemeral ports available. + No_Ports_Available = c.int(os.ENOBUFS), +} + +bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := endpoint_to_sockaddr(ep) + s := any_socket_to_socket(skt) + res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + if res != os.ERROR_NONE { + err = Bind_Error(res) + } + return +} + + +// This type of socket becomes bound when you try to send data. +// This is likely what you want if you want to send data unsolicited. +// +// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. +make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { + sock := create_socket(family, .UDP) or_return + skt = sock.(UDP_Socket) + return +} + +// This type of socket is bound immediately, which enables it to receive data on the port. +// Since it's UDP, it's also able to send data without receiving any first. +// +// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. +// +// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. +make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { + skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return + bind(skt, {bound_address, port}) or_return + return +} + + + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { + assert(backlog > 0 && i32(backlog) < max(i32)) + + family := family_from_endpoint(interface_endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + // + // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! + set_option(sock, .Reuse_Address, true) or_return + + bind(sock, interface_endpoint) or_return + + res := os.listen(Platform_Socket(skt), backlog) + if res != os.ERROR_NONE { + err = Listen_Error(res) + return + } + + return +} + + + +Accept_Error :: enum c.int { + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + sockaddr: os.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + + client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + if ok != os.ERROR_NONE { + err = Accept_Error(ok) + return + } + client = TCP_Socket(client_sock) + source = sockaddr_to_endpoint(&sockaddr) + if options.no_delay { + _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + } + return +} + + + +close :: proc(skt: Any_Socket) { + s := any_socket_to_socket(skt) + os.close(os.Handle(Platform_Socket(s))) +} + + + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + Connection_Broken = c.int(os.ENETRESET), + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) <= 0 { + return + } + res, ok := os.recv(Platform_Socket(skt), buf, 0) + if ok != os.ERROR_NONE { + err = TCP_Recv_Error(ok) + return + } + return int(res), nil +} + +UDP_Recv_Error :: enum c.int { + // The buffer is too small to fit the entire message, and the message was truncated. + // When this happens, the rest of message is lost. + Buffer_Too_Small = c.int(os.EMSGSIZE), + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was received. + // See Socket_Option.Receive_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The socket must be bound for this operation, but isn't. + Socket_Not_Bound = c.int(os.EINVAL), +} + +recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) <= 0 { + return + } + + from: os.SOCKADDR_STORAGE_LH = --- + fromsize := c.int(size_of(from)) + + // NOTE(tetra): On Linux, if the buffer is too small to fit the entire datagram payload, the rest is silently discarded, + // and no error is returned. + // However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read. + // We can use this fact to detect this condition and return .Buffer_Too_Small. + res, ok := os.recvfrom(Platform_Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize) + if ok != os.ERROR_NONE { + err = UDP_Recv_Error(ok) + return + } + + bytes_read = int(res) + remote_endpoint = sockaddr_to_endpoint(&from) + + if bytes_read > len(buf) { + // NOTE(tetra): The buffer has been filled, with a partial message. + bytes_read = len(buf) + err = .Buffer_Too_Small + } + + return +} + +recv :: proc{recv_tcp, recv_udp} + + + +// TODO +TCP_Send_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), // TODO(tetra): merge with other errors? + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), +} + +// Repeatedly sends data until the entire buffer is sent. +// If a send fails before all data is sent, returns the amount +// sent up to that point. +send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + res, ok := os.send(Platform_Socket(skt), remaining, 0) + if ok != os.ERROR_NONE { + err = TCP_Send_Error(ok) + return + } + bytes_written += int(res) + } + return +} + +// TODO +UDP_Send_Error :: enum c.int { + // The message is too big. No data was sent. + Message_Too_Long = c.int(os.EMSGSIZE), + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + No_Outbound_Ports_Available = c.int(os.EAGAIN), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + // No memory was available to properly manage the send queue. + No_Memory_Available = c.int(os.ENOMEM), +} + +// Sends a single UDP datagram packet. +// +// Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. +// UDP packets are not guarenteed to be received in order. +send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + toaddr := endpoint_to_sockaddr(to) + res, os_err := os.sendto(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr)) + if os_err != os.ERROR_NONE { + err = UDP_Send_Error(os_err) + return + } + bytes_written = int(res) + return +} + +send :: proc{send_tcp, send_udp} + + + + +Shutdown_Manner :: enum c.int { + Receive = c.int(os.SHUT_RD), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + s := any_socket_to_socket(skt) + res := os.shutdown(Platform_Socket(s), int(manner)) + if res != os.ERROR_NONE { + return Shutdown_Error(res) + } + return +} + + + + +Socket_Option :: enum c.int { + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), + TCP_Nodelay = c.int(os.TCP_NODELAY), + + Linger = c.int(os.SO_LINGER), + + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW), + Send_Timeout = c.int(os.SO_SNDTIMEO_NEW), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} + +set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP + + // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; + // it _has_ to be a b32. + // I haven't tested if you can give more than that. + bool_value: b32 + int_value: i32 + timeval_value: os.Timeval + + ptr: rawptr + len: os.socklen_t + + switch option { + case + .Reuse_Address, + .Keep_Alive, + .Out_Of_Bounds_Data_Inline, + .TCP_Nodelay: + // TODO: verify whether these are options or not on Linux + // .Broadcast, + // .Conditional_Accept, + // .Dont_Linger: + switch x in value { + case bool, b8: + x2 := x + bool_value = b32((^bool)(&x2)^) + case b16: + bool_value = b32(x) + case b32: + bool_value = b32(x) + case b64: + bool_value = b32(x) + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case + .Linger, + .Send_Timeout, + .Receive_Timeout: + t, ok := value.(time.Duration) + if !ok do panic("set_option() value must be a time.Duration here", loc) + + nanos := time.duration_nanoseconds(t) + timeval_value.nanoseconds = int(nanos % 1e9) + timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9 + + ptr = &timeval_value + len = size_of(timeval_value) + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + // TODO: check for out of range values and return .Value_Out_Of_Range? + switch i in value { + case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) + case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) + case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + } + + skt := any_socket_to_socket(s) + res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) + if res != os.ERROR_NONE { + return Socket_Option_Error(res) + } + + return nil +} \ No newline at end of file diff --git a/core/net/socket_openbsd.odin b/core/net/socket_openbsd.odin new file mode 100644 index 000000000..746b886ef --- /dev/null +++ b/core/net/socket_openbsd.odin @@ -0,0 +1,515 @@ +// +build openbsd +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. + + + IMPORTANT/TODO: This is a carbon copy of `socket_darwin.odin`. Adjust if necessary. + +*/ +package net + +import "core:c" +import "core:os" +import "core:time" + +Platform_Socket :: os.Socket + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + c_type, c_protocol, c_family: int + + switch family { + case .IP4: c_family = os.AF_INET + case .IP6: c_family = os.AF_INET6 + case: + unreachable() + } + + switch protocol { + case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP + case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP + case: + unreachable() + } + + sock, ok := os.socket(c_family, c_type, c_protocol) + if ok != os.ERROR_NONE { + err = Create_Socket_Error(ok) + return + } + + switch protocol { + case .TCP: return TCP_Socket(sock), nil + case .UDP: return UDP_Socket(sock), nil + case: + unreachable() + } +} + + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + return 0, .Port_Required + } + + family := family_from_endpoint(endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + _ = set_option(skt, .Reuse_Address, true) + + sockaddr := endpoint_to_sockaddr(endpoint) + res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + if res != os.ERROR_NONE { + err = Dial_Error(res) + return + } + + return +} + + +Bind_Error :: enum c.int { + // Another application is currently bound to this endpoint. + Address_In_Use = c.int(os.EADDRINUSE), + // The address is not a local address on this machine. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Broadcast_Disabled = c.int(os.EACCES), + // The address family of the address does not match that of the socket. + Address_Family_Mismatch = c.int(os.EFAULT), + // The socket is already bound to an address. + Already_Bound = c.int(os.EINVAL), + // There are not enough ephemeral ports available. + No_Ports_Available = c.int(os.ENOBUFS), +} + +bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := endpoint_to_sockaddr(ep) + s := any_socket_to_socket(skt) + res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + if res != os.ERROR_NONE { + err = Bind_Error(res) + } + return +} + + +// This type of socket becomes bound when you try to send data. +// This is likely what you want if you want to send data unsolicited. +// +// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. +make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { + sock := create_socket(family, .UDP) or_return + skt = sock.(UDP_Socket) + return +} + +// This type of socket is bound immediately, which enables it to receive data on the port. +// Since it's UDP, it's also able to send data without receiving any first. +// +// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. +// +// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. +make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { + skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return + bind(skt, {bound_address, port}) or_return + return +} + + + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { + assert(backlog > 0 && i32(backlog) < max(i32)) + + family := family_from_endpoint(interface_endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + // + // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! + set_option(sock, .Reuse_Address, true) or_return + + bind(sock, interface_endpoint) or_return + + res := os.listen(Platform_Socket(skt), backlog) + if res != os.ERROR_NONE { + err = Listen_Error(res) + return + } + + return +} + + + +Accept_Error :: enum c.int { + Reset = c.int(os.ECONNRESET), // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + sockaddr: os.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + + client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + if ok != os.ERROR_NONE { + err = Accept_Error(ok) + return + } + client = TCP_Socket(client_sock) + source = sockaddr_to_endpoint(&sockaddr) + return +} + + + +close :: proc(skt: Any_Socket) { + s := any_socket_to_socket(skt) + os.close(os.Handle(Platform_Socket(s))) +} + + + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + Connection_Broken = c.int(os.ENETRESET), // TODO(tetra): Is this error actually possible here? + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) <= 0 { + return + } + res, ok := os.recv(Platform_Socket(skt), buf, 0) + if ok != os.ERROR_NONE { + err = TCP_Recv_Error(ok) + return + } + return int(res), nil +} + +UDP_Recv_Error :: enum c.int { + // The buffer is too small to fit the entire message, and the message was truncated. + Truncated = c.int(os.EMSGSIZE), + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The socket must be bound for this operation, but isn't. + Socket_Not_Bound = c.int(os.EINVAL), +} + +recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) <= 0 { + return + } + + from: os.SOCKADDR_STORAGE_LH + fromsize := c.int(size_of(from)) + res, ok := os.recvfrom(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) + if ok != os.ERROR_NONE { + err = UDP_Recv_Error(ok) + return + } + + bytes_read = int(res) + remote_endpoint = sockaddr_to_endpoint(&from) + return +} + +recv :: proc{recv_tcp, recv_udp} + + + +// TODO +TCP_Send_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), // TODO: merge with other errors? + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +// Repeatedly sends data until the entire buffer is sent. +// If a send fails before all data is sent, returns the amount +// sent up to that point. +send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + res, ok := os.send(Platform_Socket(skt), remaining, 0) + if ok != os.ERROR_NONE { + err = TCP_Send_Error(ok) + return + } + bytes_written += int(res) + } + return +} + +// TODO +UDP_Send_Error :: enum c.int { + // The message is too big. No data was sent. + Truncated = c.int(os.EMSGSIZE), + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + No_Outbound_Ports_Available = c.int(os.EAGAIN), + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. + // See signal(7). + Interrupted = c.int(os.EINTR), + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + // No memory was available to properly manage the send queue. + No_Memory_Available = c.int(os.ENOMEM), +} + +send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + toaddr := endpoint_to_sockaddr(to) + for bytes_written < len(buf) { + limit := min(1<<31, len(buf) - bytes_written) + remaining := buf[bytes_written:][:limit] + res, ok := os.sendto(Platform_Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, size_of(toaddr)) + if ok != os.ERROR_NONE { + err = UDP_Send_Error(ok) + return + } + bytes_written += int(res) + } + return +} + +send :: proc{send_tcp, send_udp} + + + + +Shutdown_Manner :: enum c.int { + Receive = c.int(os.SHUT_RD), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + s := any_socket_to_socket(skt) + res := os.shutdown(Platform_Socket(s), int(manner)) + if res != os.ERROR_NONE { + return Shutdown_Error(res) + } + return +} + + + + +Socket_Option :: enum c.int { + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), + TCP_Nodelay = c.int(os.TCP_NODELAY), + + Linger = c.int(os.SO_LINGER), + + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO), + Send_Timeout = c.int(os.SO_SNDTIMEO), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} + +set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP + + // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; + // it _has_ to be a b32. + // I haven't tested if you can give more than that. + bool_value: b32 + int_value: i32 + timeval_value: os.Timeval + + ptr: rawptr + len: os.socklen_t + + switch option { + case + .Reuse_Address, + .Keep_Alive, + .Out_Of_Bounds_Data_Inline, + .TCP_Nodelay: + // TODO: verify whether these are options or not on Linux + // .Broadcast, + // .Conditional_Accept, + // .Dont_Linger: + switch x in value { + case bool, b8: + x2 := x + bool_value = b32((^bool)(&x2)^) + case b16: + bool_value = b32(x) + case b32: + bool_value = b32(x) + case b64: + bool_value = b32(x) + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case + .Linger, + .Send_Timeout, + .Receive_Timeout: + t, ok := value.(time.Duration) + if !ok do panic("set_option() value must be a time.Duration here", loc) + + nanos := time.duration_nanoseconds(t) + timeval_value.nanoseconds = int(nanos % 1e9) + timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9 + + ptr = &timeval_value + len = size_of(timeval_value) + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + // TODO: check for out of range values and return .Value_Out_Of_Range? + switch i in value { + case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) + case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) + case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + } + + skt := any_socket_to_socket(s) + res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) + if res != os.ERROR_NONE { + return Socket_Option_Error(res) + } + + return nil +} \ No newline at end of file diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin new file mode 100644 index 000000000..a08248d91 --- /dev/null +++ b/core/net/socket_windows.odin @@ -0,0 +1,577 @@ +// +build windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import win "core:sys/windows" +import "core:time" + +Platform_Socket :: win.SOCKET + +Create_Socket_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, + No_Socket_Descriptors_Available = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT, + Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE, + Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT, +} + +@(init, private) +ensure_winsock_initialized :: proc() { + win.ensure_winsock_initialized() +} + +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + c_type, c_protocol, c_family: c.int + + switch family { + case .IP4: c_family = win.AF_INET + case .IP6: c_family = win.AF_INET6 + case: + unreachable() + } + + switch protocol { + case .TCP: c_type = win.SOCK_STREAM; c_protocol = win.IPPROTO_TCP + case .UDP: c_type = win.SOCK_DGRAM; c_protocol = win.IPPROTO_UDP + case: + unreachable() + } + + sock := win.socket(c_family, c_type, c_protocol) + if sock == win.INVALID_SOCKET { + err = Create_Socket_Error(win.WSAGetLastError()) + return + } + + switch protocol { + case .TCP: return TCP_Socket(sock), nil + case .UDP: return UDP_Socket(sock), nil + case: + unreachable() + } +} + + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = win.WSAEADDRINUSE, + In_Progress = win.WSAEALREADY, + Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, + Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT, + Refused = win.WSAECONNREFUSED, + Is_Listening_Socket = win.WSAEINVAL, + Already_Connected = win.WSAEISCONN, + Network_Unreachable = win.WSAENETUNREACH, // Device is offline + Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, + Timeout = win.WSAETIMEDOUT, + Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { + if endpoint.port == 0 { + err = .Port_Required + return + } + + family := family_from_endpoint(endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): This is so that if we crash while the socket is open, we can + // bypass the cooldown period, and allow the next run of the program to + // use the same address immediately. + _ = set_option(skt, .Reuse_Address, true) + + sockaddr := endpoint_to_sockaddr(endpoint) + res := win.connect(Platform_Socket(skt), &sockaddr, size_of(sockaddr)) + if res < 0 { + err = Dial_Error(win.WSAGetLastError()) + return + } + + if options.no_delay { + _ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + } + + return +} + +Bind_Error :: enum c.int { + // Another application is currently bound to this endpoint. + Address_In_Use = win.WSAEADDRINUSE, + // The address is not a local address on this machine. + Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, + // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Broadcast_Disabled = win.WSAEACCES, + // The address family of the address does not match that of the socket. + Address_Family_Mismatch = win.WSAEFAULT, + // The socket is already bound to an address. + Already_Bound = win.WSAEINVAL, + // There are not enough ephemeral ports available. + No_Ports_Available = win.WSAENOBUFS, +} + +bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + sockaddr := endpoint_to_sockaddr(ep) + s := any_socket_to_socket(skt) + res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) + if res < 0 { + err = Bind_Error(win.WSAGetLastError()) + } + return +} + + +// This type of socket becomes bound when you try to send data. +// This is likely what you want if you want to send data unsolicited. +// +// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. +make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { + sock := create_socket(family, .UDP) or_return + skt = sock.(UDP_Socket) + return +} + +// This type of socket is bound immediately, which enables it to receive data on the port. +// Since it's UDP, it's also able to send data without receiving any first. +// +// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. +// +// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. +make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { + skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return + bind(skt, {bound_address, port}) or_return + return +} + + + +Listen_Error :: enum c.int { + Address_In_Use = win.WSAEADDRINUSE, + Already_Connected = win.WSAEISCONN, + No_Socket_Descriptors_Available = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Nonlocal_Address = win.WSAEADDRNOTAVAIL, + Not_Socket = win.WSAENOTSOCK, + Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, +} + +listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { + assert(backlog > 0 && i32(backlog) < max(i32)) + + family := family_from_endpoint(interface_endpoint) + sock := create_socket(family, .TCP) or_return + skt = sock.(TCP_Socket) + + // NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will + // prevent hijacking of the server's endpoint by other applications. + set_option(skt, .Exclusive_Addr_Use, true) or_return + + bind(sock, interface_endpoint) or_return + + res := win.listen(Platform_Socket(skt), i32(backlog)) + if res == win.SOCKET_ERROR { + err = Listen_Error(win.WSAGetLastError()) + return + } + + return +} + + + +Accept_Error :: enum c.int { + Not_Listening = win.WSAEINVAL, + No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, + Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, + Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + for { + sockaddr: win.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + client_sock := win.accept(Platform_Socket(sock), &sockaddr, &sockaddrlen) + if int(client_sock) == win.SOCKET_ERROR { + e := win.WSAGetLastError() + if e == win.WSAECONNRESET { + // NOTE(tetra): Reset just means that a client that connection immediately lost the connection. + // There's no need to concern the user with this, so we handle it for them. + // On Linux, this error isn't possible in the first place according the man pages, so we also + // can do this to match the behaviour. + continue + } + err = Accept_Error(e) + return + } + client = TCP_Socket(client_sock) + source = sockaddr_to_endpoint(&sockaddr) + if options.no_delay { + _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + } + return + } +} + + + +close :: proc(skt: Any_Socket) { + if s := any_socket_to_socket(skt); s != {} { + win.closesocket(Platform_Socket(s)) + } +} + + + +TCP_Recv_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Not_Connected = win.WSAENOTCONN, + Bad_Buffer = win.WSAEFAULT, + Keepalive_Failure = win.WSAENETRESET, + Not_Socket = win.WSAENOTSOCK, + Shutdown = win.WSAESHUTDOWN, + Would_Block = win.WSAEWOULDBLOCK, + Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? + Timeout = win.WSAETIMEDOUT, + Connection_Closed = win.WSAECONNRESET, // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Host_Unreachable = win.WSAEHOSTUNREACH, // TODO: verify can actually happen +} + +recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + if len(buf) <= 0 { + return + } + res := win.recv(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0) + if res < 0 { + err = TCP_Recv_Error(win.WSAGetLastError()) + return + } + return int(res), nil +} + +UDP_Recv_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? + // UDP packets are limited in size, and the length of the incoming message exceeded it. + Truncated = win.WSAEMSGSIZE, + // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. + Remote_Not_Listening = win.WSAECONNRESET, + Shutdown = win.WSAESHUTDOWN, + // A broadcast address was specified, but the .Broadcast socket option isn't set. + Broadcast_Disabled = win.WSAEACCES, + Bad_Buffer = win.WSAEFAULT, + No_Buffer_Space_Available = win.WSAENOBUFS, + // The socket is not valid socket handle. + Not_Socket = win.WSAENOTSOCK, + Would_Block = win.WSAEWOULDBLOCK, + // The remote host cannot be reached from this host at this time. + Host_Unreachable = win.WSAEHOSTUNREACH, + // The network cannot be reached from this host at this time. + Offline = win.WSAENETUNREACH, + Timeout = win.WSAETIMEDOUT, + // The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled. + Incorrectly_Configured = win.WSAEINVAL, // TODO: can this actually happen? + // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint. + TTL_Expired = win.WSAENETRESET, +} + +recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + if len(buf) <= 0 { + return + } + + from: win.SOCKADDR_STORAGE_LH + fromsize := c.int(size_of(from)) + res := win.recvfrom(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) + if res < 0 { + err = UDP_Recv_Error(win.WSAGetLastError()) + return + } + + bytes_read = int(res) + remote_endpoint = sockaddr_to_endpoint(&from) + return +} + +recv :: proc{recv_tcp, recv_udp} + + +// +// TODO: consider merging some errors to make handling them easier +// TODO: verify once more what errors to actually expose +// + +TCP_Send_Error :: enum c.int { + Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? + Not_Connected = win.WSAENOTCONN, + Shutdown = win.WSAESHUTDOWN, + Connection_Closed = win.WSAECONNRESET, + No_Buffer_Space_Available = win.WSAENOBUFS, + Network_Subsystem_Failure = win.WSAENETDOWN, + Host_Unreachable = win.WSAEHOSTUNREACH, + Offline = win.WSAENETUNREACH, // TODO: verify possible, as not mentioned in docs + Timeout = win.WSAETIMEDOUT, + // A broadcast address was specified, but the .Broadcast socket option isn't set. + Broadcast_Disabled = win.WSAEACCES, + Bad_Buffer = win.WSAEFAULT, + // Connection is broken due to keepalive activity detecting a failure during the operation. + Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? + // The so-called socket is not an open socket. + Not_Socket = win.WSAENOTSOCK, +} + +// Repeatedly sends data until the entire buffer is sent. +// If a send fails before all data is sent, returns the amount +// sent up to that point. +send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:] + res := win.send(Platform_Socket(skt), raw_data(remaining), c.int(limit), 0) + if res < 0 { + err = TCP_Send_Error(win.WSAGetLastError()) + return + } + bytes_written += int(res) + } + return +} + +UDP_Send_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? + // UDP packets are limited in size, and len(buf) exceeded it. + Message_Too_Long = win.WSAEMSGSIZE, + // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. + Remote_Not_Listening = win.WSAECONNRESET, + Shutdown = win.WSAESHUTDOWN, + // A broadcast address was specified, but the .Broadcast socket option isn't set. + Broadcast_Disabled = win.WSAEACCES, + Bad_Buffer = win.WSAEFAULT, + // Connection is broken due to keepalive activity detecting a failure during the operation. + Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? + No_Buffer_Space_Available = win.WSAENOBUFS, + // The socket is not valid socket handle. + Not_Socket = win.WSAENOTSOCK, + // This socket is unidirectional and cannot be used to send any data. + // TODO: verify possible; decide whether to keep if not + Receive_Only = win.WSAEOPNOTSUPP, + Would_Block = win.WSAEWOULDBLOCK, + // The remote host cannot be reached from this host at this time. + Host_Unreachable = win.WSAEHOSTUNREACH, + // Attempt to send to the Any address. + Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, + // The address is of an incorrect address family for this socket. + Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, + // The network cannot be reached from this host at this time. + Offline = win.WSAENETUNREACH, + Timeout = win.WSAETIMEDOUT, +} + +// Sends a single UDP datagram packet. +// +// Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. +// UDP packets are not guarenteed to be received in order. +send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + if len(buf) > int(max(c.int)) { + // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading. + err = .Message_Too_Long + return + } + toaddr := endpoint_to_sockaddr(to) + res := win.sendto(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) + if res < 0 { + err = UDP_Send_Error(win.WSAGetLastError()) + return + } + bytes_written = int(res) + return +} + +send :: proc{send_tcp, send_udp} + + + + +Shutdown_Manner :: enum c.int { + Receive = win.SD_RECEIVE, + Send = win.SD_SEND, + Both = win.SD_BOTH, +} + +Shutdown_Error :: enum c.int { + Aborted = win.WSAECONNABORTED, + Reset = win.WSAECONNRESET, + Offline = win.WSAENETDOWN, + Not_Connected = win.WSAENOTCONN, + Not_Socket = win.WSAENOTSOCK, + Invalid_Manner = win.WSAEINVAL, +} + +shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + s := any_socket_to_socket(skt) + res := win.shutdown(Platform_Socket(s), c.int(manner)) + if res < 0 { + return Shutdown_Error(win.WSAGetLastError()) + } + return +} + + + + +Socket_Option :: enum c.int { + // bool: Whether the address that this socket is bound to can be reused by other sockets. + // This allows you to bypass the cooldown period if a program dies while the socket is bound. + Reuse_Address = win.SO_REUSEADDR, + // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. + Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, + // bool: When true, keepalive packets will be automatically be sent for this connection. + // TODO: verify this understanding + Keep_Alive = win.SO_KEEPALIVE, + // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than + // being accepted. + Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, + // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. + Dont_Linger = win.SO_DONTLINGER, + // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, + // the same as normal 'in-band' data. + Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, + // bool: When true, disables send-coalescing, therefore reducing latency. + TCP_Nodelay = win.TCP_NODELAY, + // win.LINGER: Customizes how long (if at all) the socket will remain open when there is some remaining data + // waiting to be sent, and net.close() is called. + Linger = win.SO_LINGER, + // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. + Receive_Buffer_Size = win.SO_RCVBUF, + // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. + Send_Buffer_Size = win.SO_SNDBUF, + // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Receive_Timeout = win.SO_RCVTIMEO, + // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Send_Timeout = win.SO_SNDTIMEO, + // bool: Allow sending to, receiving from, and binding to, a broadcast address. + Broadcast = win.SO_BROADCAST, +} + +Socket_Option_Error :: enum c.int { + Linger_Only_Supports_Whole_Seconds = 1, + // The given value is too big or small to be given to the OS. + Value_Out_Of_Range, + + Network_Subsystem_Failure = win.WSAENETDOWN, + Timeout_When_Keepalive_Set = win.WSAENETRESET, + Invalid_Option_For_Socket = win.WSAENOPROTOOPT, + Reset_When_Keepalive_Set = win.WSAENOTCONN, + Not_Socket = win.WSAENOTSOCK, +} + +set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP + + bool_value: b32 + int_value: i32 + linger_value: win.LINGER + + ptr: rawptr + len: c.int + + switch option { + case + .Reuse_Address, + .Exclusive_Addr_Use, + .Keep_Alive, + .Out_Of_Bounds_Data_Inline, + .TCP_Nodelay, + .Broadcast, + .Conditional_Accept, + .Dont_Linger: + switch x in value { + case bool, b8: + x2 := x + bool_value = b32((^bool)(&x2)^) + case b16: + bool_value = b32(x) + case b32: + bool_value = b32(x) + case b64: + bool_value = b32(x) + case: + panic("set_option() value must be a boolean here", loc) + } + ptr = &bool_value + len = size_of(bool_value) + case .Linger: + t, ok := value.(time.Duration) + if !ok do panic("set_option() value must be a time.Duration here", loc) + + num_secs := i64(time.duration_seconds(t)) + if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds + if num_secs > i64(max(u16)) do return .Value_Out_Of_Range + linger_value.l_onoff = 1 + linger_value.l_linger = c.ushort(num_secs) + + ptr = &linger_value + len = size_of(linger_value) + case + .Receive_Timeout, + .Send_Timeout: + t, ok := value.(time.Duration) + if !ok do panic("set_option() value must be a time.Duration here", loc) + + int_value = i32(time.duration_milliseconds(t)) + ptr = &int_value + len = size_of(int_value) + + case + .Receive_Buffer_Size, + .Send_Buffer_Size: + switch i in value { + case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^) + case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^) + case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^) + case: + panic("set_option() value must be an integer here", loc) + } + ptr = &int_value + len = size_of(int_value) + } + + skt := any_socket_to_socket(s) + res := win.setsockopt(Platform_Socket(skt), c.int(level), c.int(option), ptr, len) + if res < 0 { + return Socket_Option_Error(win.WSAGetLastError()) + } + + return nil +} diff --git a/core/net/url.odin b/core/net/url.odin new file mode 100644 index 000000000..ffd1bbf83 --- /dev/null +++ b/core/net/url.odin @@ -0,0 +1,235 @@ +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:strings" +import "core:strconv" +import "core:unicode/utf8" +import "core:mem" + +split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) { + s := url + + i := strings.last_index(s, "://") + if i != -1 { + scheme = s[:i] + s = s[i+3:] + } + + i = strings.index(s, "?") + if i != -1 { + query_str := s[i+1:] + s = s[:i] + if query_str != "" { + queries_parts := strings.split(query_str, "&") + queries = make(map[string]string, len(queries_parts), allocator) + for q in queries_parts { + parts := strings.split(q, "=") + switch len(parts) { + case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present. + case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something. + case: break + } + } + } + } + + i = strings.index(s, "/") + if i == -1 { + host = s + path = "/" + } else { + host = s[:i] + path = s[i:] + } + + return +} + +join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string { + using strings + + b := make_builder(allocator) + grow_builder(&b, len(scheme) + 3 + len(host) + 1 + len(path)) + + write_string(&b, scheme) + write_string(&b, "://") + write_string(&b, trim_space(host)) + + if path != "" { + if path[0] != '/' do write_string(&b, "/") + write_string(&b, trim_space(path)) + } + + + if len(queries) > 0 do write_string(&b, "?") + for query_name, query_value in queries { + write_string(&b, query_name) + if query_value != "" { + write_string(&b, "=") + write_string(&b, query_value) + } + } + + return to_string(b) +} + +percent_encode :: proc(s: string, allocator := context.allocator) -> string { + using strings + + b := make_builder(allocator) + grow_builder(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape. + + for ch in s { + switch ch { + case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~': + write_rune_builder(&b, ch) + case: + bytes, n := utf8.encode_rune(ch) + for byte in bytes[:n] { + buf: [2]u8 = --- + t := strconv.append_int(buf[:], i64(byte), 16) + write_rune_builder(&b, '%') + write_string(&b, t) + } + } + } + + return to_string(b) +} + +percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) { + using strings + + b := make_builder(allocator) + grow_builder(&b, len(encoded_string)) + defer if !ok do destroy_builder(&b) + + stack_buf: [4]u8 + pending := mem.buffer_from_slice(stack_buf[:]) + s := encoded_string + + for len(s) > 0 { + i := index_rune(s, '%') + if i == -1 { + write_string(&b, s) // no '%'s; the string is already decoded + break + } + + write_string(&b, s[:i]) + s = s[i:] + + if len(s) == 0 do return // percent without anything after it + s = s[1:] + + if s[0] == '%' { + write_rune_builder(&b, '%') + s = s[1:] + continue + } + + if len(s) < 2 do return // percent without encoded value + + n: int + n, _ = strconv.parse_int(s[:2], 16) + switch n { + case 0x20: write_rune_builder(&b, ' ') + case 0x21: write_rune_builder(&b, '!') + case 0x23: write_rune_builder(&b, '#') + case 0x24: write_rune_builder(&b, '$') + case 0x25: write_rune_builder(&b, '%') + case 0x26: write_rune_builder(&b, '&') + case 0x27: write_rune_builder(&b, '\'') + case 0x28: write_rune_builder(&b, '(') + case 0x29: write_rune_builder(&b, ')') + case 0x2A: write_rune_builder(&b, '*') + case 0x2B: write_rune_builder(&b, '+') + case 0x2C: write_rune_builder(&b, ',') + case 0x2F: write_rune_builder(&b, '/') + case 0x3A: write_rune_builder(&b, ':') + case 0x3B: write_rune_builder(&b, ';') + case 0x3D: write_rune_builder(&b, '=') + case 0x3F: write_rune_builder(&b, '?') + case 0x40: write_rune_builder(&b, '@') + case 0x5B: write_rune_builder(&b, '[') + case 0x5D: write_rune_builder(&b, ']') + case: + // utf-8 bytes + // TODO(tetra): Audit this - 4 bytes??? + append(&pending, s[0]) + append(&pending, s[1]) + if len(pending) == 4 { + r, _ := utf8.decode_rune(pending[:]) + write_rune_builder(&b, r) + clear(&pending) + } + } + s = s[2:] + } + + ok = true + decoded_string = to_string(b) + return +} + +// +// TODO: encoding/base64 is broken... +// + +// // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to +// // make a table for this ... sigh - so this'll do for now. +/* +base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string { + out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator); + for b, i in out { + switch b { + case '+': out[i] = '-'; + case '/': out[i] = '_'; + } + } + i := len(out)-1; + for ; i >= 0; i -= 1 { + if out[i] != '=' do break; + } + return string(out[:i+1]); +} + +base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte { + size := len(s); + padding := 0; + for size % 4 != 0 { + size += 1; // TODO: SPEED + padding += 1; + } + + temp := make([]byte, size, context.temp_allocator); + copy(temp, transmute([]byte) s); + + for b, i in temp { + switch b { + case '-': temp[i] = '+'; + case '_': temp[i] = '/'; + } + } + + for in 0..padding-1 { + temp[len(temp)-1] = '='; + } + + return base64.decode(string(temp), base64.DEC_TABLE, allocator); +} +*/ \ No newline at end of file diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index c16cf737e..e1327c3b6 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -179,6 +179,93 @@ RTLD_NODELETE :: 0x80 RTLD_NOLOAD :: 0x10 RTLD_FIRST :: 0x100 +SOL_SOCKET :: 0xFFFF + +SOCK_STREAM :: 1 +SOCK_DGRAM :: 2 +SOCK_RAW :: 3 +SOCK_RDM :: 4 +SOCK_SEQPACKET :: 5 + +SO_DEBUG :: 0x0001 +SO_ACCEPTCONN :: 0x0002 +SO_REUSEADDR :: 0x0004 +SO_KEEPALIVE :: 0x0008 +SO_DONTROUTE :: 0x0010 +SO_BROADCAST :: 0x0020 +SO_USELOOPBACK :: 0x0040 +SO_LINGER :: 0x0080 +SO_OOBINLINE :: 0x0100 +SO_REUSEPORT :: 0x0200 +SO_TIMESTAMP :: 0x0400 + +SO_DONTTRUNC :: 0x2000 +SO_WANTMORE :: 0x4000 +SO_WANTOOBFLAG :: 0x8000 +SO_SNDBUF :: 0x1001 +SO_RCVBUF :: 0x1002 +SO_SNDLOWAT :: 0x1003 +SO_RCVLOWAT :: 0x1004 +SO_SNDTIMEO :: 0x1005 +SO_RCVTIMEO :: 0x1006 +SO_ERROR :: 0x1007 +SO_TYPE :: 0x1008 +SO_PRIVSTATE :: 0x1009 +SO_NREAD :: 0x1020 +SO_NKE :: 0x1021 + +AF_UNSPEC :: 0 +AF_LOCAL :: 1 +AF_UNIX :: AF_LOCAL +AF_INET :: 2 +AF_IMPLINK :: 3 +AF_PUP :: 4 +AF_CHAOS :: 5 +AF_NS :: 6 +AF_ISO :: 7 +AF_OSI :: AF_ISO +AF_ECMA :: 8 +AF_DATAKIT :: 9 +AF_CCITT :: 10 +AF_SNA :: 11 +AF_DECnet :: 12 +AF_DLI :: 13 +AF_LAT :: 14 +AF_HYLINK :: 15 +AF_APPLETALK :: 16 +AF_ROUTE :: 17 +AF_LINK :: 18 +pseudo_AF_XTP :: 19 +AF_COIP :: 20 +AF_CNT :: 21 +pseudo_AF_RTIP :: 22 +AF_IPX :: 23 +AF_SIP :: 24 +pseudo_AF_PIP :: 25 +pseudo_AF_BLUE :: 26 +AF_NDRV :: 27 +AF_ISDN :: 28 +AF_E164 :: AF_ISDN +pseudo_AF_KEY :: 29 +AF_INET6 :: 30 +AF_NATM :: 31 +AF_SYSTEM :: 32 +AF_NETBIOS :: 33 +AF_PPP :: 34 + +TCP_NODELAY :: 0x01 +TCP_MAXSEG :: 0x02 +TCP_NOPUSH :: 0x04 +TCP_NOOPT :: 0x08 + +IPPROTO_ICMP :: 1 +IPPROTO_TCP :: 6 +IPPROTO_UDP :: 17 + +SHUT_RD :: 0 +SHUT_WR :: 1 +SHUT_RDWR :: 2 + // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() @@ -224,6 +311,58 @@ Dirent :: struct { Dir :: distinct rawptr // DIR* +SOCKADDR :: struct #packed { + len: c.char, + family: c.char, + sa_data: [14]c.char, +} + +SOCKADDR_STORAGE_LH :: struct #packed { + len: c.char, + family: c.char, + __ss_pad1: [6]c.char, + __ss_align: i64, + __ss_pad2: [112]c.char, +} + +sockaddr_in :: struct #packed { + sin_len: c.char, + sin_family: c.char, + sin_port: u16be, + sin_addr: in_addr, + sin_zero: [8]c.char, +} + +sockaddr_in6 :: struct #packed { + sin6_len: c.char, + sin6_family: c.char, + sin6_port: u16be, + sin6_flowinfo: c.uint, + sin6_addr: in6_addr, + sin6_scope_id: c.uint, +} + +in_addr :: struct #packed { + s_addr: u32, +} + +in6_addr :: struct #packed { + s6_addr: [16]u8, +} + +Timeval :: struct { + seconds: i64, + nanoseconds: int, +} + +Linger :: struct { + onoff: int, + linger: int, +} + +Socket :: distinct int +socklen_t :: c.int + // File type S_IFMT :: 0o170000 // Type of file mask S_IFIFO :: 0o010000 // Named pipe (fifo) @@ -318,6 +457,18 @@ foreign libc { @(link_name="strerror") _darwin_string_error :: proc(num : c.int) -> cstring --- @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + @(link_name="socket") _unix_socket :: proc(domain: int, type: int, protocol: int) -> int --- + @(link_name="listen") _unix_listen :: proc(socket: int, backlog: int) -> int --- + @(link_name="accept") _unix_accept :: proc(socket: int, addr: rawptr, addr_len: rawptr) -> int --- + @(link_name="connect") _unix_connect :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- + @(link_name="bind") _unix_bind :: proc(socket: int, addr: rawptr, addr_len: socklen_t) -> int --- + @(link_name="setsockopt") _unix_setsockopt :: proc(socket: int, level: int, opt_name: int, opt_val: rawptr, opt_len: socklen_t) -> int --- + @(link_name="recvfrom") _unix_recvfrom :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: ^socklen_t) -> c.ssize_t --- + @(link_name="recv") _unix_recv :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- + @(link_name="sendto") _unix_sendto :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int, addr: rawptr, addr_len: socklen_t) -> c.ssize_t --- + @(link_name="send") _unix_send :: proc(socket: int, buffer: rawptr, buffer_len: c.size_t, flags: int) -> c.ssize_t --- + @(link_name="shutdown") _unix_shutdown :: proc(socket: int, how: int) -> int --- + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- } @@ -815,3 +966,91 @@ _alloc_command_line_arguments :: proc() -> []string { } return res } + +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { + result := _unix_socket(domain, type, protocol) + if result < 0 { + return 0, Errno(get_last_error()) + } + return Socket(result), ERROR_NONE +} + +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { + result := _unix_connect(int(sd), addr, len) + if result < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { + result := _unix_bind(int(sd), addr, len) + if result < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { + result := _unix_accept(int(sd), rawptr(addr), len) + if result < 0 { + return 0, Errno(get_last_error()) + } + return Socket(result), ERROR_NONE +} + +listen :: proc(sd: Socket, backlog: int) -> (Errno) { + result := _unix_listen(int(sd), backlog) + if result < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { + result := _unix_setsockopt(int(sd), level, optname, optval, optlen) + if result < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { + result := _unix_recvfrom(int(sd), raw_data(data), len(data), flags, addr, addr_size) + if result < 0 { + return 0, Errno(get_last_error()) + } + return u32(result), ERROR_NONE +} + +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { + result := _unix_recv(int(sd), raw_data(data), len(data), flags) + if result < 0 { + return 0, Errno(get_last_error()) + } + return u32(result), ERROR_NONE +} + +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { + result := _unix_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) + if result < 0 { + return 0, Errno(get_last_error()) + } + return u32(result), ERROR_NONE +} + +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { + result := _unix_send(int(sd), raw_data(data), len(data), 0) + if result < 0 { + return 0, Errno(get_last_error()) + } + return u32(result), ERROR_NONE +} + +shutdown :: proc(sd: Socket, how: int) -> (Errno) { + result := _unix_shutdown(int(sd), how) + if result < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 09b7f1a0f..22858d6ac 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -171,6 +171,59 @@ SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE + +AF_UNSPEC: int : 0 +AF_UNIX: int : 1 +AF_LOCAL: int : AF_UNIX +AF_INET: int : 2 +AF_INET6: int : 10 +AF_PACKET: int : 17 +AF_BLUETOOTH: int : 31 + +SOCK_STREAM: int : 1 +SOCK_DGRAM: int : 2 +SOCK_RAW: int : 3 +SOCK_RDM: int : 4 +SOCK_SEQPACKET: int : 5 +SOCK_PACKET: int : 10 + +INADDR_ANY: c.ulong : 0 +INADDR_BROADCAST: c.ulong : 0xffffffff +INADDR_NONE: c.ulong : 0xffffffff +INADDR_DUMMY: c.ulong : 0xc0000008 + +IPPROTO_IP: int : 0 +IPPROTO_ICMP: int : 1 +IPPROTO_TCP: int : 6 +IPPROTO_UDP: int : 17 +IPPROTO_IPV6: int : 41 +IPPROTO_ETHERNET: int : 143 +IPPROTO_RAW: int : 255 + +SHUT_RD: int : 0 +SHUT_WR: int : 1 +SHUT_RDWR: int : 2 + + +SOL_SOCKET: int : 1 +SO_DEBUG: int : 1 +SO_REUSEADDR: int : 2 +SO_DONTROUTE: int : 5 +SO_BROADCAST: int : 6 +SO_SNDBUF: int : 7 +SO_RCVBUF: int : 8 +SO_KEEPALIVE: int : 9 +SO_OOBINLINE: int : 10 +SO_LINGER: int : 13 +SO_REUSEPORT: int : 15 +SO_RCVTIMEO_NEW: int : 66 +SO_SNDTIMEO_NEW: int : 67 + +TCP_NODELAY: int : 1 +TCP_CORK: int : 3 + +MSG_TRUNC : int : 0x20 + // NOTE(zangent): These are OS specific! // Do not mix these up! RTLD_LAZY :: 0x001 @@ -217,6 +270,102 @@ Dirent :: struct { name: [256]byte, } +ADDRESS_FAMILY :: u16 +SOCKADDR :: struct #packed { + sa_family: ADDRESS_FAMILY, + sa_data: [14]c.char, +} + +SOCKADDR_STORAGE_LH :: struct #packed { + ss_family: ADDRESS_FAMILY, + __ss_pad1: [6]c.char, + __ss_align: i64, + __ss_pad2: [112]c.char, +} + +sockaddr_in :: struct #packed { + sin_family: ADDRESS_FAMILY, + sin_port: u16be, + sin_addr: in_addr, + sin_zero: [8]c.char, +} + +sockaddr_in6 :: struct #packed { + sin6_family: ADDRESS_FAMILY, + sin6_port: u16be, + sin6_flowinfo: c.ulong, + sin6_addr: in6_addr, + sin6_scope_id: c.ulong, +} + +in_addr :: struct #packed { + s_addr: u32, +} + +in6_addr :: struct #packed { + s6_addr: [16]u8, +} + +rtnl_link_stats :: struct #packed { + rx_packets: u32, + tx_packets: u32, + rx_bytes: u32, + tx_bytes: u32, + rx_errors: u32, + tx_errors: u32, + rx_dropped: u32, + tx_dropped: u32, + multicast: u32, + collisions: u32, + rx_length_errors: u32, + rx_over_errors: u32, + rx_crc_errors: u32, + rx_frame_errors: u32, + rx_fifo_errors: u32, + rx_missed_errors: u32, + tx_aborted_errors: u32, + tx_carrier_errors: u32, + tx_fifo_errors: u32, + tx_heartbeat_errors: u32, + tx_window_errors: u32, + rx_compressed: u32, + tx_compressed: u32, + rx_nohandler: u32, +} + +SIOCGIFFLAG :: enum c.int { + UP = 0, /* Interface is up. */ + BROADCAST = 1, /* Broadcast address valid. */ + DEBUG = 2, /* Turn on debugging. */ + LOOPBACK = 3, /* Is a loopback net. */ + POINT_TO_POINT = 4, /* Interface is point-to-point link. */ + NO_TRAILERS = 5, /* Avoid use of trailers. */ + RUNNING = 6, /* Resources allocated. */ + NOARP = 7, /* No address resolution protocol. */ + PROMISC = 8, /* Receive all packets. */ + ALL_MULTI = 9, /* Receive all multicast packets. Unimplemented. */ + MASTER = 10, /* Master of a load balancer. */ + SLAVE = 11, /* Slave of a load balancer. */ + MULTICAST = 12, /* Supports multicast. */ + PORTSEL = 13, /* Can set media type. */ + AUTOMEDIA = 14, /* Auto media select active. */ + DYNAMIC = 15, /* Dialup device with changing addresses. */ + LOWER_UP = 16, + DORMANT = 17, + ECHO = 18, +} +SIOCGIFFLAGS :: bit_set[SIOCGIFFLAG; c.int] + +ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: SIOCGIFFLAGS, + address: ^SOCKADDR, + netmask: ^SOCKADDR, + broadcast_or_dest: ^SOCKADDR, // Broadcast or Point-to-Point address + data: rawptr, // Address-specific data. +} + Dir :: distinct rawptr // DIR* // File type @@ -297,6 +446,9 @@ foreign dl { @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- + + @(link_name="getifaddrs") _getifaddrs :: proc(ifap: ^^ifaddrs) -> (c.int) --- + @(link_name="freeifaddrs") _freeifaddrs :: proc(ifa: ^ifaddrs) --- } is_path_separator :: proc(r: rune) -> bool { @@ -823,3 +975,94 @@ _alloc_command_line_arguments :: proc() -> []string { } return res } + +socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Errno) { + result := unix.sys_socket(domain, type, protocol) + if result < 0 { + return 0, _get_errno(result) + } + return Socket(result), ERROR_NONE +} + +bind :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { + result := unix.sys_bind(int(sd), addr, len) + if result < 0 { + return _get_errno(result) + } + return ERROR_NONE +} + + +connect :: proc(sd: Socket, addr: ^SOCKADDR, len: socklen_t) -> (Errno) { + result := unix.sys_connect(int(sd), addr, len) + if result < 0 { + return _get_errno(result) + } + return ERROR_NONE +} + +accept :: proc(sd: Socket, addr: ^SOCKADDR, len: rawptr) -> (Socket, Errno) { + result := unix.sys_accept(int(sd), rawptr(addr), len) + if result < 0 { + return 0, _get_errno(result) + } + return Socket(result), ERROR_NONE +} + +listen :: proc(sd: Socket, backlog: int) -> (Errno) { + result := unix.sys_listen(int(sd), backlog) + if result < 0 { + return _get_errno(result) + } + return ERROR_NONE +} + +setsockopt :: proc(sd: Socket, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> (Errno) { + result := unix.sys_setsockopt(int(sd), level, optname, optval, optlen) + if result < 0 { + return _get_errno(result) + } + return ERROR_NONE +} + + +recvfrom :: proc(sd: Socket, data: []byte, flags: int, addr: ^SOCKADDR, addr_size: ^socklen_t) -> (u32, Errno) { + result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, addr, uintptr(addr_size)) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), ERROR_NONE +} + +recv :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { + result := unix.sys_recvfrom(int(sd), raw_data(data), len(data), flags, nil, 0) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), ERROR_NONE +} + + +sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: socklen_t) -> (u32, Errno) { + result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, addr, addrlen) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), ERROR_NONE +} + +send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Errno) { + result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) + if result < 0 { + return 0, _get_errno(int(result)) + } + return u32(result), ERROR_NONE +} + +shutdown :: proc(sd: Socket, how: int) -> (Errno) { + result := unix.sys_shutdown(int(sd), how) + if result < 0 { + return _get_errno(result) + } + return ERROR_NONE +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 91c0accfa..3659d8437 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2008,6 +2008,42 @@ sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, fla return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags))) } +sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int { + return int(intrinsics.syscall(unix.SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol))) +} + +sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: socklen_t) -> int { + return int(intrinsics.syscall(unix.SYS_connect, uintptr(sd), uintptr(addr), uintptr(len))) +} + +sys_accept :: proc "contextless" (sd: int, addr: rawptr, len: rawptr) -> int { + return int(intrinsics.syscall(unix.SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0))) +} + +sys_listen :: proc "contextless" (sd: int, backlog: int) -> int { + return int(intrinsics.syscall(unix.SYS_listen, uintptr(sd), uintptr(backlog))) +} + +sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: socklen_t) -> int { + return int(intrinsics.syscall(unix.SYS_bind, uintptr(sd), uintptr(addr), uintptr(len))) +} + +sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> int { + return int(intrinsics.syscall(unix.SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen))) +} + +sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: uintptr) -> i64 { + return i64(intrinsics.syscall(unix.SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) +} + +sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: socklen_t) -> i64 { + return i64(intrinsics.syscall(unix.SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) +} + +sys_shutdown :: proc "contextless" (sd: int, how: int) -> int { + return int(intrinsics.syscall(unix.SYS_shutdown, uintptr(sd), uintptr(how))) +} + sys_perf_event_open :: proc "contextless" (event_attr: rawptr, pid: i32, cpu: i32, group_fd: i32, flags: u32) -> int { return int(intrinsics.syscall(SYS_perf_event_open, uintptr(event_attr), uintptr(pid), uintptr(cpu), uintptr(group_fd), uintptr(flags))) } diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin new file mode 100644 index 000000000..623fa2889 --- /dev/null +++ b/core/sys/windows/dnsapi.odin @@ -0,0 +1,10 @@ +// +build windows +package sys_windows + +foreign import "system:Dnsapi.lib" + +@(default_calling_convention="std") +foreign Dnsapi { + DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- + DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- +} diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin new file mode 100644 index 000000000..d8f93f533 --- /dev/null +++ b/core/sys/windows/ip_helper.odin @@ -0,0 +1,234 @@ +// +build windows +package sys_windows + +foreign import "system:iphlpapi.lib" + +Address_Family :: enum u32 { + Unspecified = 0, // Return both IPv4 and IPv6 addresses associated with adapters with them enabled. + IPv4 = 2, // Return only IPv4 addresses associated with adapters with it enabled. + IPv6 = 23, // Return only IPv6 addresses associated with adapters with it enabled. +} + +GAA_Flag :: enum u32 { + Skip_Unicast = 0, // Do not return unicast addresses. + Skip_Anycast = 1, // Do not return IPv6 anycast addresses. + Skip_Multicast = 2, // Do not return multicast addresses. + Skip_DNS_Server = 3, // Do not return addresses of DNS servers. + Include_Prefix = 4, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses. + Skip_Friendly_Name = 5, // Do not return the adapter friendly name. + Include_WINS_info = 6, // (Vista+) Return addresses of Windows Internet Name Service (WINS) servers. + Include_Gateways = 7, // (Vista+) Return the addresses of default gateways. + Include_All_Interfaces = 8, // (Vista+) Return addresses for all NDIS interfaces. + Include_All_Compartments = 9, // (Reserved, Unsupported) Return addresses in all routing compartments. + Include_Tunnel_Binding_Order = 10, // (Vista+) Return the adapter addresses sorted in tunnel binding order. +} +GAA_Flags :: bit_set[GAA_Flag; u32] + +IP_Adapter_Addresses :: struct { + Raw: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + IfIndex: u32, + }, + }, + Next: ^IP_Adapter_Addresses, + AdapterName: cstring, + FirstUnicastAddress: ^IP_ADAPTER_UNICAST_ADDRESS_LH, + FirstAnycastAddress: ^IP_ADAPTER_ANYCAST_ADDRESS_XP, + FirstMulticastAddress: ^IP_ADAPTER_MULTICAST_ADDRESS_XP, + FirstDnsServerAddress: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP, + DnsSuffix: ^u16, + Description: ^u16, + FriendlyName: ^u16, + PhysicalAddress: [8]u8, + PhysicalAddressLength: u32, + Anonymous2: struct #raw_union { + Flags: u32, + Anonymous: struct { + _bitfield: u32, + }, + }, + MTU: u32, + IfType: u32, + OperStatus: IF_OPER_STATUS, + Ipv6IfIndex: u32, + ZoneIndices: [16]u32, + FirstPrefix: rawptr, // ^IP_ADAPTER_PREFIX_XP, + TransmitLinkSpeed: u64, + ReceiveLinkSpeed: u64, + FirstWinsServerAddress: rawptr, // ^IP_ADAPTER_WINS_SERVER_ADDRESS_LH, + FirstGatewayAddress: ^IP_ADAPTER_GATEWAY_ADDRESS_LH, + Ipv4Metric: u32, + Ipv6Metric: u32, + Luid: NET_LUID_LH, + Dhcpv4Server: SOCKET_ADDRESS, + CompartmentId: u32, + NetworkGuid: GUID, + ConnectionType: NET_IF_CONNECTION_TYPE, + TunnelType: TUNNEL_TYPE, + Dhcpv6Server: SOCKET_ADDRESS, + Dhcpv6ClientDuid: [130]u8, + Dhcpv6ClientDuidLength: u32, + Dhcpv6Iaid: u32, + FirstDnsSuffix: rawptr, // ^IP_ADAPTER_DNS_SUFFIX, +} + +IP_ADAPTER_UNICAST_ADDRESS_LH :: struct { + Anonymous: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + Flags: u32, + }, + }, + Next: ^IP_ADAPTER_UNICAST_ADDRESS_LH, + Address: SOCKET_ADDRESS, + PrefixOrigin: NL_PREFIX_ORIGIN, + SuffixOrigin: NL_SUFFIX_ORIGIN, + DadState: NL_DAD_STATE, + ValidLifetime: u32, + PreferredLifetime: u32, + LeaseLifetime: u32, + OnLinkPrefixLength: u8, +} + +IP_ADAPTER_ANYCAST_ADDRESS_XP :: struct { + Anonymous: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + Flags: u32, + }, + }, + Next: ^IP_ADAPTER_ANYCAST_ADDRESS_XP, + Address: SOCKET_ADDRESS, +} + +IP_ADAPTER_MULTICAST_ADDRESS_XP :: struct { + Anonymous: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + Flags: u32, + }, + }, + Next: ^IP_ADAPTER_MULTICAST_ADDRESS_XP, + Address: SOCKET_ADDRESS, +} + +IP_ADAPTER_GATEWAY_ADDRESS_LH :: struct { + Anonymous: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + Reserved: u32, + }, + }, + Next: ^IP_ADAPTER_GATEWAY_ADDRESS_LH, + Address: SOCKET_ADDRESS, +} + +IP_ADAPTER_DNS_SERVER_ADDRESS_XP :: struct { + Anonymous: struct #raw_union { + Alignment: u64, + Anonymous: struct { + Length: u32, + Reserved: u32, + }, + }, + Next: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP, + Address: SOCKET_ADDRESS, +} + +IF_OPER_STATUS :: enum i32 { + Up = 1, + Down = 2, + Testing = 3, + Unknown = 4, + Dormant = 5, + NotPresent = 6, + LowerLayerDown = 7, +} + +NET_LUID_LH :: struct #raw_union { + Value: u64, + Info: struct { + _bitfield: u64, + }, +} + +SOCKET_ADDRESS :: struct { + lpSockaddr: ^SOCKADDR, + iSockaddrLength: i32, +} + +NET_IF_CONNECTION_TYPE :: enum i32 { + NET_IF_CONNECTION_DEDICATED = 1, + NET_IF_CONNECTION_PASSIVE = 2, + NET_IF_CONNECTION_DEMAND = 3, + NET_IF_CONNECTION_MAXIMUM = 4, +} + +TUNNEL_TYPE :: enum i32 { + TUNNEL_TYPE_NONE = 0, + TUNNEL_TYPE_OTHER = 1, + TUNNEL_TYPE_DIRECT = 2, + TUNNEL_TYPE_6TO4 = 11, + TUNNEL_TYPE_ISATAP = 13, + TUNNEL_TYPE_TEREDO = 14, + TUNNEL_TYPE_IPHTTPS = 15, +} +NL_PREFIX_ORIGIN :: enum i32 { + IpPrefixOriginOther = 0, + IpPrefixOriginManual = 1, + IpPrefixOriginWellKnown = 2, + IpPrefixOriginDhcp = 3, + IpPrefixOriginRouterAdvertisement = 4, + IpPrefixOriginUnchanged = 16, +} + +NL_SUFFIX_ORIGIN :: enum i32 { + NlsoOther = 0, + NlsoManual = 1, + NlsoWellKnown = 2, + NlsoDhcp = 3, + NlsoLinkLayerAddress = 4, + NlsoRandom = 5, + IpSuffixOriginOther = 0, + IpSuffixOriginManual = 1, + IpSuffixOriginWellKnown = 2, + IpSuffixOriginDhcp = 3, + IpSuffixOriginLinkLayerAddress = 4, + IpSuffixOriginRandom = 5, + IpSuffixOriginUnchanged = 16, +} + +NL_DAD_STATE :: enum i32 { + NldsInvalid = 0, + NldsTentative = 1, + NldsDuplicate = 2, + NldsDeprecated = 3, + NldsPreferred = 4, + IpDadStateInvalid = 0, + IpDadStateTentative = 1, + IpDadStateDuplicate = 2, + IpDadStateDeprecated = 3, + IpDadStatePreferred = 4, +} + +@(default_calling_convention = "std") +foreign iphlpapi { + /* + The GetAdaptersAddresses function retrieves the addresses associated with the adapters on the local computer. + See: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses + */ + @(link_name="GetAdaptersAddresses") get_adapters_addresses :: proc( + family: Address_Family, + flags: GAA_Flags, + _reserved: rawptr, + adapter_addresses: [^]IP_Adapter_Addresses, + size: ^u32, + ) -> ULONG --- + +} diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 1d5bdaf04..cb2c55cd3 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -154,10 +154,6 @@ TIMER_QUERY_STATE :: 0x0001 TIMER_MODIFY_STATE :: 0x0002 TIMER_ALL_ACCESS :: STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE | TIMER_MODIFY_STATE -SOCKET :: distinct uintptr // TODO -socklen_t :: c_int -ADDRESS_FAMILY :: USHORT - TRUE :: BOOL(true) FALSE :: BOOL(false) @@ -1868,30 +1864,6 @@ BI_BITFIELDS :: 3 BI_JPEG :: 4 BI_PNG :: 5 -WSA_FLAG_OVERLAPPED: DWORD : 0x01 -WSA_FLAG_NO_HANDLE_INHERIT: DWORD : 0x80 - -WSADESCRIPTION_LEN :: 256 -WSASYS_STATUS_LEN :: 128 -WSAPROTOCOL_LEN: DWORD : 255 -INVALID_SOCKET :: ~SOCKET(0) - -WSAEACCES: c_int : 10013 -WSAEINVAL: c_int : 10022 -WSAEWOULDBLOCK: c_int : 10035 -WSAEPROTOTYPE: c_int : 10041 -WSAEADDRINUSE: c_int : 10048 -WSAEADDRNOTAVAIL: c_int : 10049 -WSAECONNABORTED: c_int : 10053 -WSAECONNRESET: c_int : 10054 -WSAENOTCONN: c_int : 10057 -WSAESHUTDOWN: c_int : 10058 -WSAETIMEDOUT: c_int : 10060 -WSAECONNREFUSED: c_int : 10061 -WSATRY_AGAIN: c_int : 11002 - -MAX_PROTOCOL_CHAIN: DWORD : 7 - MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024 FSCTL_GET_REPARSE_POINT: DWORD : 0x900a8 IO_REPARSE_TAG_SYMLINK: DWORD : 0xa000000c @@ -1949,44 +1921,6 @@ CREATE_NEW_PROCESS_GROUP: DWORD : 0x00000200 CREATE_UNICODE_ENVIRONMENT: DWORD : 0x00000400 STARTF_USESTDHANDLES: DWORD : 0x00000100 -AF_INET: c_int : 2 -AF_INET6: c_int : 23 -SD_BOTH: c_int : 2 -SD_RECEIVE: c_int : 0 -SD_SEND: c_int : 1 -SOCK_DGRAM: c_int : 2 -SOCK_STREAM: c_int : 1 -SOL_SOCKET: c_int : 0xffff -SO_RCVTIMEO: c_int : 0x1006 -SO_SNDTIMEO: c_int : 0x1005 -SO_REUSEADDR: c_int : 0x0004 -IPPROTO_IP: c_int : 0 -IPPROTO_TCP: c_int : 6 -IPPROTO_IPV6: c_int : 41 -TCP_NODELAY: c_int : 0x0001 -IP_TTL: c_int : 4 -IPV6_V6ONLY: c_int : 27 -SO_ERROR: c_int : 0x1007 -SO_BROADCAST: c_int : 0x0020 -IP_MULTICAST_LOOP: c_int : 11 -IPV6_MULTICAST_LOOP: c_int : 11 -IP_MULTICAST_TTL: c_int : 10 -IP_ADD_MEMBERSHIP: c_int : 12 -IP_DROP_MEMBERSHIP: c_int : 13 -IPV6_ADD_MEMBERSHIP: c_int : 12 -IPV6_DROP_MEMBERSHIP: c_int : 13 -MSG_PEEK: c_int : 0x2 - -ip_mreq :: struct { - imr_multiaddr: in_addr, - imr_interface: in_addr, -} - -ipv6_mreq :: struct { - ipv6mr_multiaddr: in6_addr, - ipv6mr_interface: c_uint, -} - VOLUME_NAME_DOS: DWORD : 0x0 MOVEFILE_REPLACE_EXISTING: DWORD : 1 @@ -2369,11 +2303,6 @@ STARTUPINFO :: struct { hStdError: HANDLE, } -SOCKADDR :: struct { - sa_family: ADDRESS_FAMILY, - sa_data: [14]CHAR, -} - FILETIME :: struct { dwLowDateTime: DWORD, dwHighDateTime: DWORD, @@ -2406,74 +2335,6 @@ ADDRESS_MODE :: enum c_int { AddrModeFlat, } -SOCKADDR_STORAGE_LH :: struct { - ss_family: ADDRESS_FAMILY, - __ss_pad1: [6]CHAR, - __ss_align: i64, - __ss_pad2: [112]CHAR, -} - -ADDRINFOA :: struct { - ai_flags: c_int, - ai_family: c_int, - ai_socktype: c_int, - ai_protocol: c_int, - ai_addrlen: size_t, - ai_canonname: ^c_char, - ai_addr: ^SOCKADDR, - ai_next: ^ADDRINFOA, -} - -PADDRINFOEXW :: ^ADDRINFOEXW -LPADDRINFOEXW :: ^ADDRINFOEXW -ADDRINFOEXW :: struct { - ai_flags: c_int, - ai_family: c_int, - ai_socktype: c_int, - ai_protocol: c_int, - ai_addrlen: size_t, - ai_canonname: wstring, - ai_addr: ^sockaddr, - ai_blob: rawptr, - ai_bloblen: size_t, - ai_provider: LPGUID, - ai_next: ^ADDRINFOEXW, -} - -LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" ( - dwErrorCode: DWORD, - dwNumberOfBytesTransfered: DWORD, - lpOverlapped: LPOVERLAPPED, -) - -sockaddr :: struct { - sa_family: USHORT, - sa_data: [14]byte, -} - -sockaddr_in :: struct { - sin_family: ADDRESS_FAMILY, - sin_port: USHORT, - sin_addr: in_addr, - sin_zero: [8]CHAR, -} - -sockaddr_in6 :: struct { - sin6_family: ADDRESS_FAMILY, - sin6_port: USHORT, - sin6_flowinfo: c_ulong, - sin6_addr: in6_addr, - sin6_scope_id: c_ulong, -} - -in_addr :: struct { - s_addr: u32, -} - -in6_addr :: struct { - s6_addr: [16]u8, -} - EXCEPTION_DISPOSITION :: enum c_int { ExceptionContinueExecution, ExceptionContinueSearch, @@ -3884,3 +3745,233 @@ COORD :: struct { X: SHORT, Y: SHORT, } + +// +// Networking +// +WSA_FLAG_OVERLAPPED :: 1 +WSA_FLAG_MULTIPOINT_C_ROOT :: 2 +WSA_FLAG_MULTIPOINT_C_LEAF :: 4 +WSA_FLAG_MULTIPOINT_D_ROOT :: 8 +WSA_FLAG_MULTIPOINT_D_LEAF :: 16 +WSA_FLAG_ACCESS_SYSTEM_SECURITY :: 32 +WSA_FLAG_NO_HANDLE_INHERIT :: 128 +WSADESCRIPTION_LEN :: 256 +WSASYS_STATUS_LEN :: 128 +WSAPROTOCOL_LEN :: 255 +INVALID_SOCKET :: ~SOCKET(0) +SOMAXCONN :: 128 // The number of messages that can be queued in memory after being received; use 2-4 for Bluetooth. + // This is for the 'backlog' parameter to listen(). +SOCKET_ERROR :: -1 + +// Networking errors +WSAEINTR :: 10004 // Call interrupted. CancelBlockingCall was called. (This is different on Linux.) +WSAEACCES :: 10013 // If you try to bind a Udp socket to the broadcast address without the socket option set. +WSAEFAULT :: 10014 // A pointer that was passed to a WSA function is invalid, such as a buffer size is smaller than you said it was +WSAEINVAL :: 10022 // Invalid argument supplied +WSAEMFILE :: 10024 // SOCKET handles exhausted +WSAEWOULDBLOCK :: 10035 // No data is ready yet +WSAENOTSOCK :: 10038 // Not a socket. +WSAEINPROGRESS :: 10036 // WS1.1 call is in progress or callback function is still being processed +WSAEALREADY :: 10037 // Already connecting in parallel. +WSAEMSGSIZE :: 10040 // Message was truncated because it exceeded max datagram size. +WSAEPROTOTYPE :: 10041 // Wrong protocol for the provided socket +WSAENOPROTOOPT :: 10042 // TODO +WSAEPROTONOSUPPORT :: 10043 // Protocol not supported +WSAESOCKTNOSUPPORT :: 10044 // Socket type not supported in the given address family +WSAEAFNOSUPPORT :: 10047 // Address family not supported +WSAEOPNOTSUPP :: 10045 // Attempt to accept on non-stream socket, etc. +WSAEADDRINUSE :: 10048 // Endpoint being bound is in use by another socket. +WSAEADDRNOTAVAIL :: 10049 // Not a valid local IP address on this computer. +WSAENETDOWN :: 10050 // Network subsystem failure on the local machine. +WSAENETUNREACH :: 10051 // The local machine is not connected to the network. +WSAENETRESET :: 10052 // Keepalive failure detected, or TTL exceeded when receiving UDP packets. +WSAECONNABORTED :: 10053 // Connection has been aborted by software in the host machine. +WSAECONNRESET :: 10054 // The connection was reset while trying to accept, read or write. +WSAENOBUFS :: 10055 // No buffer space is available. The outgoing queue may be full in which case you should probably try again after a pause. +WSAEISCONN :: 10056 // The socket is already connected. +WSAENOTCONN :: 10057 // The socket is not connected yet, or no address was supplied to sendto. +WSAESHUTDOWN :: 10058 // The socket has been shutdown in the direction required. +WSAETIMEDOUT :: 10060 // The timeout duration was reached before any data was received / before all data was sent. +WSAECONNREFUSED :: 10061 // The remote machine is not listening on that endpoint. +WSAEHOSTDOWN :: 10064 // Destination host was down. +WSAEHOSTUNREACH :: 10065 // The remote machine is not connected to the network. +WSAENOTINITIALISED :: 10093 // Needs WSAStartup call +WSAEINVALIDPROCTABLE :: 10104 // Invalid or incomplete procedure table was returned +WSAEINVALIDPROVIDER :: 10105 // Service provider version is not 2.2 +WSAEPROVIDERFAILEDINIT :: 10106 // Service provider failed to initialize + +// Address families +AF_UNSPEC : c_int : 0 // Unspecified +AF_INET : c_int : 2 // IPv4 +AF_INET6 : c_int : 23 // IPv6 +AF_IRDA : c_int : 26 // Infrared +AF_BTH : c_int : 32 // Bluetooth + +// Socket types +SOCK_STREAM : c_int : 1 // TCP +SOCK_DGRAM : c_int : 2 // UDP +SOCK_RAW : c_int : 3 // Requires options IP_HDRINCL for v4, IPV6_HDRINCL for v6, on the socket +SOCK_RDM : c_int : 4 // Requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols +SOCK_SEQPACKET : c_int : 5 // Provides psuedo-stream packet based on DGRAMs. + +// Protocols +IPPROTO_IP : c_int : 0 +IPPROTO_ICMP : c_int : 1 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified +IPPROTO_IGMP : c_int : 2 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW | not specified +BTHPROTO_RFCOMM : c_int : 3 // Bluetooth: AF_BTH + SOCK_STREAM +IPPROTO_TCP : c_int : 6 // (AF_INET, AF_INET6) + SOCK_STREAM +IPPROTO_UDP : c_int : 17 // (AF_INET, AF_INET6) + SOCK_DGRAM +IPPROTO_ICMPV6 : c_int : 58 // (AF_UNSPEC, AF_INET, AF_INET6) + SOCK_RAW +IPPROTO_RM : c_int : 113 // AF_INET + SOCK_RDM [requires "Reliable Multicast Protocol" to be installed - see WSAEnumProtocols] + +// Shutdown manners +SD_RECEIVE : c_int : 0 +SD_SEND : c_int : 1 +SD_BOTH : c_int : 2 + +// Socket 'levels' +SOL_SOCKET : c_int : 0xffff // Socket options for any socket. +IPPROTO_IPV6 : c_int : 41 // Socket options for IPV6. + +// Options for any sockets +SO_ACCEPTCONN : c_int : 0x0002 +SO_REUSEADDR : c_int : 0x0004 +SO_KEEPALIVE : c_int : 0x0008 +SO_SNDTIMEO : c_int : 0x1005 +SO_RCVTIMEO : c_int : 0x1006 +SO_EXCLUSIVEADDRUSE : c_int : ~SO_REUSEADDR +SO_CONDITIONAL_ACCEPT : c_int : 0x3002 +SO_DONTLINGER : c_int : ~SO_LINGER +SO_OOBINLINE : c_int : 0x0100 +SO_LINGER : c_int : 0x0080 +SO_RCVBUF : c_int : 0x1002 +SO_SNDBUF : c_int : 0x1001 +SO_ERROR : c_int : 0x1007 +SO_BROADCAST : c_int : 0x0020 + +TCP_NODELAY: c_int : 0x0001 +IP_TTL: c_int : 4 +IPV6_V6ONLY: c_int : 27 +IP_MULTICAST_LOOP: c_int : 11 +IPV6_MULTICAST_LOOP: c_int : 11 +IP_MULTICAST_TTL: c_int : 10 +IP_ADD_MEMBERSHIP: c_int : 12 + +IPV6_ADD_MEMBERSHIP: c_int : 12 +IPV6_DROP_MEMBERSHIP: c_int : 13 + +MAX_PROTOCOL_CHAIN: DWORD : 7 + +// Used with the SO_LINGER socket option to setsockopt(). +LINGER :: struct { + l_onoff: c.ushort, + l_linger: c.ushort, +} +// Send/Receive flags. +MSG_OOB : c_int : 1 // `send`/`recv` should process out-of-band data. +MSG_PEEK : c_int : 2 // `recv` should not remove the data from the buffer. Only valid for non-overlapped operations. + + +SOCKET :: distinct uintptr // TODO +socklen_t :: c_int +ADDRESS_FAMILY :: USHORT + +ip_mreq :: struct { + imr_multiaddr: in_addr, + imr_interface: in_addr, +} + +ipv6_mreq :: struct { + ipv6mr_multiaddr: in6_addr, + ipv6mr_interface: c_uint, +} + +SOCKADDR_STORAGE_LH :: struct { + ss_family: ADDRESS_FAMILY, + __ss_pad1: [6]CHAR, + __ss_align: i64, + __ss_pad2: [112]CHAR, +} + +ADDRINFOA :: struct { + ai_flags: c_int, + ai_family: c_int, + ai_socktype: c_int, + ai_protocol: c_int, + ai_addrlen: size_t, + ai_canonname: ^c_char, + ai_addr: ^SOCKADDR, + ai_next: ^ADDRINFOA, +} + +sockaddr_in :: struct { + sin_family: ADDRESS_FAMILY, + sin_port: u16be, + sin_addr: in_addr, + sin_zero: [8]CHAR, +} +sockaddr_in6 :: struct { + sin6_family: ADDRESS_FAMILY, + sin6_port: u16be, + sin6_flowinfo: c_ulong, + sin6_addr: in6_addr, + sin6_scope_id: c_ulong, +} + +in_addr :: struct { + s_addr: u32, +} + +in6_addr :: struct { + s6_addr: [16]u8, +} + + +DNS_STATUS :: distinct DWORD // zero is success +DNS_INFO_NO_RECORDS :: 9501 +DNS_QUERY_NO_RECURSION :: 0x00000004 + +DNS_RECORD :: struct { + pNext: ^DNS_RECORD, + pName: cstring, + wType: WORD, + wDataLength: USHORT, + Flags: DWORD, + dwTtl: DWORD, + _: DWORD, + Data: struct #raw_union { + CNAME: DNS_PTR_DATAA, + A: u32be, // Ipv4 Address + AAAA: u128be, // Ipv6 Address + TXT: DNS_TXT_DATAA, + NS: DNS_PTR_DATAA, + MX: DNS_MX_DATAA, + SRV: DNS_SRV_DATAA, + }, +} + +DNS_TXT_DATAA :: struct { + dwStringCount: DWORD, + pStringArray: cstring, +} + +DNS_PTR_DATAA :: cstring + +DNS_MX_DATAA :: struct { + pNameExchange: cstring, // the hostname + wPreference: WORD, // lower values preferred + _: WORD, // padding. +} +DNS_SRV_DATAA :: struct { + pNameTarget: cstring, + wPriority: u16, + wWeight: u16, + wPort: u16, + _: WORD, // padding +} + +SOCKADDR :: struct { + sa_family: ADDRESS_FAMILY, + sa_data: [14]CHAR, +} diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 298588cb6..7f8e51d38 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -485,3 +485,24 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P return false } } + +ensure_winsock_initialized :: proc() { + @static gate := false + @static initted := false + + if initted { + return + } + + for intrinsics.atomic_compare_exchange_strong(&gate, false, true) { + intrinsics.cpu_relax() + } + defer intrinsics.atomic_store(&gate, false) + + unused_info: WSADATA + version_requested := WORD(2) << 8 | 2 + res := WSAStartup(version_requested, &unused_info) + assert(res == 0, "unable to initialized Winsock2") + + initted = true +} diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 09af86bce..30515d430 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -54,7 +54,7 @@ foreign ws2_32 { buf: rawptr, len: c_int, flags: c_int, - addr: ^SOCKADDR, + addr: ^SOCKADDR_STORAGE_LH, addrlen: ^c_int, ) -> c_int --- sendto :: proc( @@ -62,11 +62,11 @@ foreign ws2_32 { buf: rawptr, len: c_int, flags: c_int, - addr: ^SOCKADDR, + addr: ^SOCKADDR_STORAGE_LH, addrlen: c_int, ) -> c_int --- shutdown :: proc(socket: SOCKET, how: c_int) -> c_int --- - accept :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> SOCKET --- + accept :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> SOCKET --- setsockopt :: proc( s: SOCKET, @@ -75,11 +75,11 @@ foreign ws2_32 { optval: rawptr, optlen: c_int, ) -> c_int --- - getsockname :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int --- - getpeername :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: ^c_int) -> c_int --- - bind :: proc(socket: SOCKET, address: ^SOCKADDR, address_len: socklen_t) -> c_int --- + getsockname :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int --- + getpeername :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: ^c_int) -> c_int --- + bind :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, address_len: socklen_t) -> c_int --- listen :: proc(socket: SOCKET, backlog: c_int) -> c_int --- - connect :: proc(socket: SOCKET, address: ^SOCKADDR, len: c_int) -> c_int --- + connect :: proc(socket: SOCKET, address: ^SOCKADDR_STORAGE_LH, len: c_int) -> c_int --- getaddrinfo :: proc( node: cstring, service: cstring, diff --git a/tests/core/Makefile b/tests/core/Makefile index 478d6ae2c..77d4b85a0 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -2,7 +2,7 @@ ODIN=../../odin PYTHON=$(shell which python3) all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ - math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test + math_test linalg_glsl_math_test filepath_test reflect_test os_exit_test i18n_test match_test c_libc_test net_test download_test_assets: $(PYTHON) download_assets.py @@ -54,3 +54,6 @@ match_test: c_libc_test: $(ODIN) run c/libc -out:test_core_libc + +net_test: + $(ODIN) run net -out:test_core_net diff --git a/tests/core/build.bat b/tests/core/build.bat index e4e146588..5a86e5b41 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -71,6 +71,11 @@ echo Running core:text/i18n tests echo --- %PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe +echo --- +echo Running core:net +echo --- +%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe + echo --- echo Running core:text/lua tests echo --- diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin new file mode 100644 index 000000000..ad764c24d --- /dev/null +++ b/tests/core/net/test_core_net.odin @@ -0,0 +1,508 @@ +/* + Copyright 2021 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Jeroen van Rijn: Initial implementation. + graphitemaster: pton/ntop IANA test vectors + + A test suite for `core:net` +*/ +package test_core_net + +import "core:testing" +import "core:mem" +import "core:fmt" +import "core:net" +import "core:strconv" +import "core:time" +import "core:thread" + +_, _ :: time, thread + +TEST_count := 0 +TEST_fail := 0 + +t := &testing.T{} + +when ODIN_TEST { + expect :: testing.expect + log :: testing.log +} else { + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.printf("[%v] %v\n", loc, message) + return + } + } + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +_tracking_allocator := mem.Tracking_Allocator{} + +print_tracking_allocator_report :: proc() { + for _, leak in _tracking_allocator.allocation_map { + fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) + } + + for bf in _tracking_allocator.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory) + } +} + +main :: proc() { + mem.tracking_allocator_init(&_tracking_allocator, context.allocator) + context.allocator = mem.tracking_allocator(&_tracking_allocator) + + address_parsing_test(t) + + when ODIN_OS != .Windows { + fmt.printf("IMPORTANT: `core:thread` seems to still be a bit wonky on Linux and MacOS, so we can't run tests relying on them.\n", ODIN_OS) + } else { + tcp_tests(t) + } + + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + + print_tracking_allocator_report() +} + +@test +address_parsing_test :: proc(t: ^testing.T) { + for vector in IP_Address_Parsing_Test_Vectors { + kind := "" + switch vector.family { + case .IP4: kind = "[IPv4]" + case .IP4_Alt: kind = "[IPv4 Non-Decimal]" + case .IP6: kind = "[IPv6]" + case: panic("Add support to the test for this type.") + } + + valid := len(vector.binstr) > 0 + + fmt.printf("%v %v\n", kind, vector.input) + + msg := "-set a proper message-" + switch vector.family { + case .IP4, .IP4_Alt: + /* + Does `net.parse_ip4_address` think we parsed the address properly? + */ + non_decimal := vector.family == .IP4_Alt + + any_addr := net.parse_address(vector.input, non_decimal) + parsed_ok := any_addr != nil + parsed: net.IP4_Address + + /* + Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. + */ + switch addr in any_addr { + case net.IP4_Address: + parsed = addr + case net.IP6_Address: + parsed_ok = false + msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr) + expect(t, false, msg) + } + + if !parsed_ok && valid { + msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr)) + + } else if parsed_ok && !valid { + msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) + } + expect(t, parsed_ok == valid, msg) + + if valid && parsed_ok { + actual_binary := address_to_binstr(parsed) + msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) + expect(t, actual_binary == vector.binstr, msg) + + /* + Do we turn an address back into the same string properly? + No point in testing the roundtrip if the first part failed. + */ + if len(vector.output) > 0 && actual_binary == vector.binstr { + stringified := net.address_to_string(parsed) + msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) + expect(t, stringified == vector.output, msg) + } + } + + case .IP6: + /* + Do we parse the address properly? + */ + parsed, parsed_ok := net.parse_ip6_address(vector.input) + + if !parsed_ok && valid { + msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr)) + + } else if parsed_ok && !valid { + msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) + } + expect(t, parsed_ok == valid, msg) + + if valid && parsed_ok { + actual_binary := address_to_binstr(parsed) + msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) + expect(t, actual_binary == vector.binstr, msg) + + /* + Do we turn an address back into the same string properly? + No point in testing the roundtrip if the first part failed. + */ + if len(vector.output) > 0 && actual_binary == vector.binstr { + stringified := net.address_to_string(parsed) + msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) + expect(t, stringified == vector.output, msg) + } + } + } + } +} + +address_to_binstr :: proc(address: net.Address) -> (binstr: string) { + switch t in address { + case net.IP4_Address: + b := transmute(u32be)t + return fmt.tprintf("%08x", b) + case net.IP6_Address: + b := transmute(u128be)t + return fmt.tprintf("%32x", b) + case: + return "" + } + unreachable() +} + +binstr_to_address :: proc(binstr: string) -> (address: net.Address) { + switch len(binstr) { + case 8: // IPv4 + a, ok := strconv.parse_u64_of_base(binstr, 16) + expect(t, ok, "failed to parse test case bin string") + + ipv4 := u32be(a) + return net.IP4_Address(transmute([4]u8)ipv4) + + + case 32: // IPv6 + a, ok := strconv.parse_u128_of_base(binstr, 16) + expect(t, ok, "failed to parse test case bin string") + + ipv4 := u128be(a) + return net.IP6_Address(transmute([8]u16be)ipv4) + + case 0: + return nil + } + panic("Invalid test case") +} + +Kind :: enum { + IP4, // Decimal IPv4 + IP4_Alt, // Non-decimal address + IP6, // Hex IPv6 or mixed IPv4/IPv6. +} + +IP_Address_Parsing_Test_Vector :: struct { + // Give it to the IPv4 or IPv6 parser? + family: Kind, + + // Input address to try and parse. + input: string, + + /* + Hexadecimal representation of the expected numeric value of the address. + Zero length means input is invalid and the parser should report failure. + */ + binstr: string, + + // Expected `address_to_string` output, if a valid input and this string is non-empty. + output: string, +} + +IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{ + // dotted-decimal notation + { .IP4, "0.0.0.0", "00000000", "0.0.0.0" }, + { .IP4, "127.0.0.1", "7f000001", "127.0.0.1" }, + { .IP4, "10.0.128.31", "0a00801f", "10.0.128.31" }, + { .IP4, "255.255.255.255", "ffffffff", "255.255.255.255"}, + + // Odin custom: Address + port, valid + { .IP4, "0.0.0.0:80", "00000000", "0.0.0.0" }, + { .IP4, "127.0.0.1:80", "7f000001", "127.0.0.1" }, + { .IP4, "10.0.128.31:80", "0a00801f", "10.0.128.31" }, + { .IP4, "255.255.255.255:80", "ffffffff", "255.255.255.255"}, + + { .IP4, "[0.0.0.0]:80", "00000000", "0.0.0.0" }, + { .IP4, "[127.0.0.1]:80", "7f000001", "127.0.0.1" }, + { .IP4, "[10.0.128.31]:80", "0a00801f", "10.0.128.31" }, + { .IP4, "[255.255.255.255]:80", "ffffffff", "255.255.255.255"}, + + // Odin custom: Address + port, invalid + { .IP4, "[]:80", "", ""}, + { .IP4, "[0.0.0.0]", "", ""}, + { .IP4, "[127.0.0.1]:", "", ""}, + { .IP4, "[10.0.128.31] :80", "", ""}, + { .IP4, "[255.255.255.255]:65536", "", ""}, + + + // numbers-and-dots notation, but not dotted-decimal + { .IP4_Alt, "1.2.03.4", "01020304", ""}, + { .IP4_Alt, "1.2.0x33.4", "01023304", ""}, + { .IP4_Alt, "1.2.0XAB.4", "0102ab04", ""}, + { .IP4_Alt, "1.2.0xabcd", "0102abcd", ""}, + { .IP4_Alt, "1.0xabcdef", "01abcdef", ""}, + { .IP4_Alt, "0x01abcdef", "01abcdef", ""}, + { .IP4_Alt, "00377.0x0ff.65534", "fffffffe", ""}, + + // invalid as decimal address + { .IP4, "", "", ""}, + { .IP4, ".1.2.3", "", ""}, + { .IP4, "1..2.3", "", ""}, + { .IP4, "1.2.3.", "", ""}, + { .IP4, "1.2.3.4.5", "", ""}, + { .IP4, "1.2.3.a", "", ""}, + { .IP4, "1.256.2.3", "", ""}, + { .IP4, "1.2.4294967296.3", "", ""}, + { .IP4, "1.2.-4294967295.3", "", ""}, + { .IP4, "1.2. 3.4", "", ""}, + + // invalid as non-decimal address + { .IP4_Alt, "", "", ""}, + { .IP4_Alt, ".1.2.3", "", ""}, + { .IP4_Alt, "1..2.3", "", ""}, + { .IP4_Alt, "1.2.3.", "", ""}, + { .IP4_Alt, "1.2.3.4.5", "", ""}, + { .IP4_Alt, "1.2.3.a", "", ""}, + { .IP4_Alt, "1.256.2.3", "", ""}, + { .IP4_Alt, "1.2.4294967296.3", "", ""}, + { .IP4_Alt, "1.2.-4294967295.3", "", ""}, + { .IP4_Alt, "1.2. 3.4", "", ""}, + + // Valid IPv6 addresses + { .IP6, "::", "00000000000000000000000000000000", "::"}, + { .IP6, "::1", "00000000000000000000000000000001", "::1"}, + { .IP6, "::192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"}, + { .IP6, "0000:0000:0000:0000:0000:ffff:255.255.255.255", "00000000000000000000ffffffffffff", "::ffff:ffff:ffff"}, + + { .IP6, "0:0:0:0:0:0:192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"}, + { .IP6, "0:0::0:0:0:192.168.1.1", "000000000000000000000000c0a80101", "::c0a8:101"}, + { .IP6, "::ffff:192.168.1.1", "00000000000000000000ffffc0a80101", "::ffff:c0a8:101"}, + { .IP6, "a:0b:00c:000d:E:F::", "000a000b000c000d000e000f00000000", "a:b:c:d:e:f::"}, + { .IP6, "1:2:3:4:5:6::", "00010002000300040005000600000000", "1:2:3:4:5:6::"}, + { .IP6, "1:2:3:4:5:6:7::", "00010002000300040005000600070000", "1:2:3:4:5:6:7:0"}, + { .IP6, "::1:2:3:4:5:6", "00000000000100020003000400050006", "::1:2:3:4:5:6"}, + { .IP6, "::1:2:3:4:5:6:7", "00000001000200030004000500060007", "0:1:2:3:4:5:6:7"}, + { .IP6, "a:b::c:d:e:f", "000a000b00000000000c000d000e000f", "a:b::c:d:e:f"}, + { .IP6, "0:0:0:0:0:ffff:c0a8:5e4", "00000000000000000000ffffc0a805e4", "::ffff:c0a8:5e4"}, + { .IP6, "0::ffff:c0a8:5e4", "00000000000000000000ffffc0a805e4", "::ffff:c0a8:5e4"}, + + + // If multiple zero runs are present, shorten the longest one. + { .IP6, "1:0:0:2:0:0:0:3", "00010000000000020000000000000003", "1:0:0:2::3"}, + + // Invalid IPv6 addresses + { .IP6, "", "", ""}, + { .IP6, ":", "", ""}, + { .IP6, ":::", "", ""}, + { .IP6, "192.168.1.1", "", ""}, + { .IP6, ":192.168.1.1", "", ""}, + { .IP6, "::012.34.56.78", "", ""}, + { .IP6, ":ffff:192.168.1.1", "", ""}, + { .IP6, ".192.168.1.1", "", ""}, + { .IP6, ":.192.168.1.1", "", ""}, + { .IP6, "a:0b:00c:000d:0000e:f::", "", ""}, + { .IP6, "1:2:3:4:5:6:7:8::", "", ""}, + { .IP6, "1:2:3:4:5:6:7::9", "", ""}, + { .IP6, "::1:2:3:4:5:6:7:8", "", ""}, + { .IP6, "ffff:c0a8:5e4", "", ""}, + { .IP6, ":ffff:c0a8:5e4", "", ""}, + { .IP6, "0:0:0:0:ffff:c0a8:5e4", "", ""}, + { .IP6, "::0::ffff:c0a8:5e4", "", ""}, + { .IP6, "c0a8", "", ""}, +} + + +ENDPOINT := net.Endpoint{ + net.IP4_Address{127, 0, 0, 1}, + 9999, +} + +CONTENT := "Hellope!" + +SEND_TIMEOUT :: time.Duration(2 * time.Second) +RECV_TIMEOUT :: time.Duration(2 * time.Second) + +Thread_Data :: struct { + skt: net.Any_Socket, + err: net.Network_Error, + tid: ^thread.Thread, + + no_accept: bool, // Tell the server proc not to accept. + + data: [1024]u8, // Received data and its length + length: int, +} + +thread_data := [3]Thread_Data{} + +/* + This runs a bunch of socket tests using threads: + - two servers trying to bind the same endpoint + - client trying to connect to closed port + - client trying to connect to an open port with a non-accepting server + - client sending server data and server sending client data + - etc. +*/ +tcp_tests :: proc(t: ^testing.T) { + fmt.println("Testing two servers trying to bind to the same endpoint...") + two_servers_binding_same_endpoint(t) + fmt.println("Testing client connecting to a closed port...") + client_connects_to_closed_port(t) + fmt.println("Testing client connecting to port that doesn't accept...") + client_connects_to_open_but_non_accepting_port(t) + fmt.println("Testing client sending server data...") + client_sends_server_data(t) +} + +tcp_client :: proc(retval: rawptr) { + r := transmute(^Thread_Data)retval + + if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { + return + } + defer net.close(r.skt) + + net.set_option(r.skt, .Send_Timeout, SEND_TIMEOUT) + net.set_option(r.skt, .Receive_Timeout, RECV_TIMEOUT) + + _, r.err = net.send(r.skt.(net.TCP_Socket), transmute([]u8)CONTENT) + return +} + +tcp_server :: proc(retval: rawptr) { + r := transmute(^Thread_Data)retval + + if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil { + return + } + defer net.close(r.skt) + + if r.no_accept { + // Don't accept any connections, just listen. + return + } + + client: net.TCP_Socket + if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil { + return + } + defer net.close(client) + + r.length, r.err = net.recv_tcp(client, r.data[:]) + return +} + +cleanup_thread :: proc(data: Thread_Data) { + net.close(data.skt) + + thread.terminate(data.tid, 1) + thread.destroy(data.tid) +} + +two_servers_binding_same_endpoint :: proc(t: ^testing.T) { + thread_data = {} + + thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) + thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_server, context) + + defer { + cleanup_thread(thread_data[0]) + cleanup_thread(thread_data[1]) + } + + // Give the two servers enough time to try and bind the same endpoint + time.sleep(1 * time.Second) + + first_won := thread_data[0].err == nil && thread_data[1].err == net.Bind_Error.Address_In_Use + second_won := thread_data[1].err == nil && thread_data[0].err == net.Bind_Error.Address_In_Use + + okay := first_won || second_won + msg := fmt.tprintf("Expected servers to return `nil` and `Address_In_Use`, got %v and %v", thread_data[0].err, thread_data[1].err) + expect(t, okay, msg) +} + +client_connects_to_closed_port :: proc(t: ^testing.T) { + thread_data = {} + + thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_client, context) + + defer { + cleanup_thread(thread_data[0]) + } + + // Give the socket enough time to return `Refused` + time.sleep(4 * time.Second) + + okay := thread_data[0].err == net.Dial_Error.Refused + msg := fmt.tprintf("Expected client to return `Refused` connecting to closed port, got %v", thread_data[0].err) + expect(t, okay, msg) +} + +client_connects_to_open_but_non_accepting_port :: proc(t: ^testing.T) { + thread_data = {} + + // Tell server proc not to accept + thread_data[0].no_accept = true + + thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) + thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context) + + defer { + cleanup_thread(thread_data[0]) + cleanup_thread(thread_data[1]) + } + + // Give the two servers enough time to try and bind the same endpoint + time.sleep(4 * time.Second) + + okay := thread_data[0].err == nil && thread_data[1].err == net.Dial_Error.Refused + msg := fmt.tprintf("Expected server and client to return `nil` and `Refused`, got %v and %v", thread_data[0].err, thread_data[1].err) + expect(t, okay, msg) +} + +client_sends_server_data :: proc(t: ^testing.T) { + thread_data = {} + + // Tell server proc not to accept + // thread_data[0].no_accept = true + + thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) + thread_data[1].tid = thread.create_and_start_with_data(&thread_data[1], tcp_client, context) + + defer { + cleanup_thread(thread_data[0]) + cleanup_thread(thread_data[1]) + } + + // Give the two servers enough time to try and bind the same endpoint + time.sleep(1 * time.Second) + + okay := thread_data[0].err == nil && thread_data[1].err == nil + msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) + expect(t, okay, msg) + + received := string(thread_data[0].data[:thread_data[0].length]) + + okay = received == CONTENT + msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received) + expect(t, okay, msg) +} \ No newline at end of file From d569daae3353c633c3c59ed3477ddabf50ea16f2 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 08:17:41 -0800 Subject: [PATCH 02/23] more manual type carryover --- core/net/addr.odin | 18 ++++----- core/net/dns.odin | 2 +- core/net/url.odin | 62 +++++++++++++++---------------- core/os/os_darwin.odin | 1 + core/os/os_linux.odin | 8 ++++ core/sys/unix/syscalls_linux.odin | 9 +++-- core/sys/windows/types.odin | 21 +++++++++++ 7 files changed, 76 insertions(+), 45 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 80ba91110..b296048d2 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -182,7 +182,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, for ch, i in address { switch ch { - case '0'..'9', 'a'..'f', 'A'..'F': + case '0'..='9', 'a'..='f', 'A'..='F': piece_end += 1 case ':': @@ -522,7 +522,7 @@ join_port :: proc(address_or_host: string, port: int, allocator := context.alloc addr_or_host, _, ok := split_port(address_or_host) if !ok do return addr_or_host - b := strings.make_builder(allocator) + b := strings.builder_make(allocator) addr := parse_address(addr_or_host) if addr == nil { @@ -560,7 +560,7 @@ map_to_ip6 :: proc(addr: Address) -> Address { See RFC 5952 section 4 for IPv6 representation recommendations. */ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string { - b := strings.make_builder(allocator) + b := strings.builder_make(allocator) switch v in addr { case IP4_Address: fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3]) @@ -657,7 +657,7 @@ endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> return address_to_string(ep.address, allocator) } else { s := address_to_string(ep.address, context.temp_allocator) - b := strings.make_builder(allocator) + b := strings.builder_make(allocator) switch a in ep.address { case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port) case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port) @@ -751,11 +751,11 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D parse_loop: for ch in input { switch ch { - case '0'..'7': + case '0'..='7': digit_bytes += 1 value = value * base + u64(ch - '0') - case '8'..'9': + case '8'..='9': digit_bytes += 1 if base == 8 { @@ -766,7 +766,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D } value = value * base + u64(ch - '0') - case 'a'..'f': + case 'a'..='f': digit_bytes += 1 if base == 8 || base == 10 { @@ -777,7 +777,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D } value = value * base + (u64(ch - 'a') + 10) - case 'A'..'F': + case 'A'..='F': digit_bytes += 1 if base == 8 || base == 10 { @@ -815,4 +815,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. */ return value, digit_bytes + prefix_bytes, digit_bytes >= 1 -} \ No newline at end of file +} diff --git a/core/net/dns.odin b/core/net/dns.odin index 32bc1be6b..26f6fbb4d 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -612,7 +612,7 @@ validate_hostname :: proc(hostname: string) -> (ok: bool) { switch ch { case: return - case 'a'..'z', 'A'..'Z', '0'..'9', '-': + case 'a'..='z', 'A'..='Z', '0'..='9', '-': continue } } diff --git a/core/net/url.odin b/core/net/url.odin index ffd1bbf83..c7bf77671 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -63,8 +63,8 @@ split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string { using strings - b := make_builder(allocator) - grow_builder(&b, len(scheme) + 3 + len(host) + 1 + len(path)) + b := builder_make(allocator) + builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path)) write_string(&b, scheme) write_string(&b, "://") @@ -91,19 +91,19 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, allocat percent_encode :: proc(s: string, allocator := context.allocator) -> string { using strings - b := make_builder(allocator) - grow_builder(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape. + b := builder_make(allocator) + builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape. for ch in s { switch ch { case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~': - write_rune_builder(&b, ch) + write_rune(&b, ch) case: bytes, n := utf8.encode_rune(ch) for byte in bytes[:n] { buf: [2]u8 = --- t := strconv.append_int(buf[:], i64(byte), 16) - write_rune_builder(&b, '%') + write_rune(&b, '%') write_string(&b, t) } } @@ -115,8 +115,8 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string { percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) { using strings - b := make_builder(allocator) - grow_builder(&b, len(encoded_string)) + b := builder_make(allocator) + builder_grow(&b, len(encoded_string)) defer if !ok do destroy_builder(&b) stack_buf: [4]u8 @@ -137,7 +137,7 @@ percent_decode :: proc(encoded_string: string, allocator := context.allocator) - s = s[1:] if s[0] == '%' { - write_rune_builder(&b, '%') + write_rune(&b, '%') s = s[1:] continue } @@ -147,26 +147,26 @@ percent_decode :: proc(encoded_string: string, allocator := context.allocator) - n: int n, _ = strconv.parse_int(s[:2], 16) switch n { - case 0x20: write_rune_builder(&b, ' ') - case 0x21: write_rune_builder(&b, '!') - case 0x23: write_rune_builder(&b, '#') - case 0x24: write_rune_builder(&b, '$') - case 0x25: write_rune_builder(&b, '%') - case 0x26: write_rune_builder(&b, '&') - case 0x27: write_rune_builder(&b, '\'') - case 0x28: write_rune_builder(&b, '(') - case 0x29: write_rune_builder(&b, ')') - case 0x2A: write_rune_builder(&b, '*') - case 0x2B: write_rune_builder(&b, '+') - case 0x2C: write_rune_builder(&b, ',') - case 0x2F: write_rune_builder(&b, '/') - case 0x3A: write_rune_builder(&b, ':') - case 0x3B: write_rune_builder(&b, ';') - case 0x3D: write_rune_builder(&b, '=') - case 0x3F: write_rune_builder(&b, '?') - case 0x40: write_rune_builder(&b, '@') - case 0x5B: write_rune_builder(&b, '[') - case 0x5D: write_rune_builder(&b, ']') + case 0x20: write_rune(&b, ' ') + case 0x21: write_rune(&b, '!') + case 0x23: write_rune(&b, '#') + case 0x24: write_rune(&b, '$') + case 0x25: write_rune(&b, '%') + case 0x26: write_rune(&b, '&') + case 0x27: write_rune(&b, '\'') + case 0x28: write_rune(&b, '(') + case 0x29: write_rune(&b, ')') + case 0x2A: write_rune(&b, '*') + case 0x2B: write_rune(&b, '+') + case 0x2C: write_rune(&b, ',') + case 0x2F: write_rune(&b, '/') + case 0x3A: write_rune(&b, ':') + case 0x3B: write_rune(&b, ';') + case 0x3D: write_rune(&b, '=') + case 0x3F: write_rune(&b, '?') + case 0x40: write_rune(&b, '@') + case 0x5B: write_rune(&b, '[') + case 0x5D: write_rune(&b, ']') case: // utf-8 bytes // TODO(tetra): Audit this - 4 bytes??? @@ -174,7 +174,7 @@ percent_decode :: proc(encoded_string: string, allocator := context.allocator) - append(&pending, s[1]) if len(pending) == 4 { r, _ := utf8.decode_rune(pending[:]) - write_rune_builder(&b, r) + write_rune(&b, r) clear(&pending) } } @@ -232,4 +232,4 @@ base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte { return base64.decode(string(temp), base64.DEC_TABLE, allocator); } -*/ \ No newline at end of file +*/ diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index e1327c3b6..8066a4a90 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -67,6 +67,7 @@ ENOPROTOOPT: Errno : 42 /* Protocol not available */ EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ ENOTSUP: Errno : 45 /* Operation not supported */ +EOPNOTSUPP:: ENOTSUP EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ EADDRINUSE: Errno : 48 /* Address already in use */ diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 22858d6ac..85abb0d21 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -14,6 +14,7 @@ Handle :: distinct i32 Pid :: distinct i32 File_Time :: distinct u64 Errno :: distinct i32 +Socket :: distinct int INVALID_HANDLE :: ~Handle(0) @@ -231,6 +232,13 @@ RTLD_NOW :: 0x002 RTLD_BINDING_MASK :: 0x3 RTLD_GLOBAL :: 0x100 +socklen_t :: c.int + +Timeval :: struct { + seconds: i64, + nanoseconds: int, +} + // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 3659d8437..5435366c4 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1518,6 +1518,7 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } + // syscall related constants AT_FDCWD :: ~uintptr(99) AT_REMOVEDIR :: uintptr(0x200) @@ -2012,7 +2013,7 @@ sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int return int(intrinsics.syscall(unix.SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol))) } -sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: socklen_t) -> int { +sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int { return int(intrinsics.syscall(unix.SYS_connect, uintptr(sd), uintptr(addr), uintptr(len))) } @@ -2024,11 +2025,11 @@ sys_listen :: proc "contextless" (sd: int, backlog: int) -> int { return int(intrinsics.syscall(unix.SYS_listen, uintptr(sd), uintptr(backlog))) } -sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: socklen_t) -> int { +sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int { return int(intrinsics.syscall(unix.SYS_bind, uintptr(sd), uintptr(addr), uintptr(len))) } -sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: socklen_t) -> int { +sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: i32) -> int { return int(intrinsics.syscall(unix.SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen))) } @@ -2036,7 +2037,7 @@ sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, return i64(intrinsics.syscall(unix.SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) } -sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: socklen_t) -> i64 { +sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: i32) -> i64 { return i64(intrinsics.syscall(unix.SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) } diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index cb2c55cd3..96c3c0dbd 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2425,6 +2425,27 @@ FILE_ATTRIBUTE_TAG_INFO :: struct { ReparseTag: DWORD, } +PADDRINFOEXW :: ^ADDRINFOEXW +LPADDRINFOEXW :: ^ADDRINFOEXW +ADDRINFOEXW :: struct { + ai_flags: c_int, + ai_family: c_int, + ai_socktype: c_int, + ai_protocol: c_int, + ai_addrlen: size_t, + ai_canonname: wstring, + ai_addr: ^sockaddr, + ai_blob: rawptr, + ai_bloblen: size_t, + ai_provider: LPGUID, + ai_next: ^ADDRINFOEXW, +} + +LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" ( + dwErrorCode: DWORD, + dwNumberOfBytesTransfered: DWORD, + lpOverlapped: LPOVERLAPPED, +) // https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info From caf9716bf143e24f594e081b7e8ec19a3319dee7 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 08:21:53 -0800 Subject: [PATCH 03/23] more cleanup ripple --- core/sys/unix/syscalls_linux.odin | 18 +++++++++--------- core/sys/windows/types.odin | 5 +++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 5435366c4..779bc3be3 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -2010,39 +2010,39 @@ sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, fla } sys_socket :: proc "contextless" (domain: int, type: int, protocol: int) -> int { - return int(intrinsics.syscall(unix.SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol))) + return int(intrinsics.syscall(SYS_socket, uintptr(domain), uintptr(type), uintptr(protocol))) } sys_connect :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int { - return int(intrinsics.syscall(unix.SYS_connect, uintptr(sd), uintptr(addr), uintptr(len))) + return int(intrinsics.syscall(SYS_connect, uintptr(sd), uintptr(addr), uintptr(len))) } sys_accept :: proc "contextless" (sd: int, addr: rawptr, len: rawptr) -> int { - return int(intrinsics.syscall(unix.SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0))) + return int(intrinsics.syscall(SYS_accept4, uintptr(sd), uintptr(addr), uintptr(len), uintptr(0))) } sys_listen :: proc "contextless" (sd: int, backlog: int) -> int { - return int(intrinsics.syscall(unix.SYS_listen, uintptr(sd), uintptr(backlog))) + return int(intrinsics.syscall(SYS_listen, uintptr(sd), uintptr(backlog))) } sys_bind :: proc "contextless" (sd: int, addr: rawptr, len: i32) -> int { - return int(intrinsics.syscall(unix.SYS_bind, uintptr(sd), uintptr(addr), uintptr(len))) + return int(intrinsics.syscall(SYS_bind, uintptr(sd), uintptr(addr), uintptr(len))) } sys_setsockopt :: proc "contextless" (sd: int, level: int, optname: int, optval: rawptr, optlen: i32) -> int { - return int(intrinsics.syscall(unix.SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen))) + return int(intrinsics.syscall(SYS_setsockopt, uintptr(sd), uintptr(level), uintptr(optname), uintptr(optval), uintptr(optlen))) } sys_recvfrom :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: uintptr) -> i64 { - return i64(intrinsics.syscall(unix.SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) + return i64(intrinsics.syscall(SYS_recvfrom, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) } sys_sendto :: proc "contextless" (sd: int, buf: rawptr, len: uint, flags: int, addr: rawptr, alen: i32) -> i64 { - return i64(intrinsics.syscall(unix.SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) + return i64(intrinsics.syscall(SYS_sendto, uintptr(sd), uintptr(buf), uintptr(len), uintptr(flags), uintptr(addr), uintptr(alen))) } sys_shutdown :: proc "contextless" (sd: int, how: int) -> int { - return int(intrinsics.syscall(unix.SYS_shutdown, uintptr(sd), uintptr(how))) + return int(intrinsics.syscall(SYS_shutdown, uintptr(sd), uintptr(how))) } sys_perf_event_open :: proc "contextless" (event_attr: rawptr, pid: i32, cpu: i32, group_fd: i32, flags: u32) -> int { diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 96c3c0dbd..1b45f61ba 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -3926,6 +3926,11 @@ ADDRINFOA :: struct { ai_next: ^ADDRINFOA, } +sockaddr :: struct { + sa_family: USHORT, + sa_data: [14]byte, +} + sockaddr_in :: struct { sin_family: ADDRESS_FAMILY, sin_port: u16be, From 2ca30e3acd413bdcc9dbe055b2b70084f08a4ba7 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 08:27:07 -0800 Subject: [PATCH 04/23] more test cleanup --- core/net/dns.odin | 11 +---------- core/net/interface.odin | 8 ++++---- core/net/url.odin | 2 +- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/core/net/dns.odin b/core/net/dns.odin index 26f6fbb4d..087d2e146 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -25,20 +25,11 @@ import "core:os" Default configuration for DNS resolution. */ when ODIN_OS == .Windows { - getenv :: proc(key: string) -> (val: string) { - return os.get_env(key) - } - DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", } } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { - getenv :: proc(key: string) -> (val: string) { - val, _ = os.getenv(key) - return - } - DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ resolv_conf = "/etc/resolv.conf", hosts_file = "/etc/hosts", @@ -81,7 +72,7 @@ replace_environment_path :: proc(path: string, allocator := context.allocator) - assert(right > 0 && right <= len(path)) // should be covered by there being two % env_key := path[left: right] - env_val := getenv(env_key) + env_val := os.get_env(env_key) defer delete(env_val) res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1) diff --git a/core/net/interface.odin b/core/net/interface.odin index 354cba53f..5e6d09830 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -56,13 +56,13 @@ physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocato for b, i in phy_addr { if i > 0 { - strings.write_rune_builder(&buf, ':') + strings.write_rune(&buf, ':') } hi := rune(MAC_HEX[b >> 4]) lo := rune(MAC_HEX[b & 15]) - strings.write_rune_builder(&buf, hi) - strings.write_rune_builder(&buf, lo) + strings.write_rune(&buf, hi) + strings.write_rune(&buf, lo) } return strings.to_string(buf) -} \ No newline at end of file +} diff --git a/core/net/url.odin b/core/net/url.odin index c7bf77671..980fb5e90 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -117,7 +117,7 @@ percent_decode :: proc(encoded_string: string, allocator := context.allocator) - b := builder_make(allocator) builder_grow(&b, len(encoded_string)) - defer if !ok do destroy_builder(&b) + defer if !ok do builder_destroy(&b) stack_buf: [4]u8 pending := mem.buffer_from_slice(stack_buf[:]) From 14eed79a218aa6778276bed6f4d411ebbd86d538 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 08:33:48 -0800 Subject: [PATCH 05/23] make baby pandas (and Jeroen) happy --- core/os/os_darwin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 8066a4a90..ef85909c0 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -67,7 +67,7 @@ ENOPROTOOPT: Errno : 42 /* Protocol not available */ EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ ENOTSUP: Errno : 45 /* Operation not supported */ -EOPNOTSUPP:: ENOTSUP +EOPNOTSUPP:: ENOTSUP EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ EADDRINUSE: Errno : 48 /* Address already in use */ From 707c2b3d7a621529f9232eced0fbe85f77b2731e Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 18:24:37 -0800 Subject: [PATCH 06/23] remove win32 ref --- core/net/interface_windows.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index 57cc0e8ef..a9634afee 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -18,7 +18,6 @@ package net import sys "core:sys/windows" -import win32 "core:sys/win32" import strings "core:strings" MAX_INTERFACE_ENUMERATION_TRIES :: 3 @@ -144,7 +143,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N Takes a UTF-16 Wstring and clones it. */ wstring_to_string :: proc(s: ^u16, max_size := 256, allocator := context.allocator) -> (res: string) { - temp := win32.wstring_to_utf8((win32.Wstring)(s), max_size, context.temp_allocator) + temp := sys.wstring_to_utf8((sys.Wstring)(s), max_size, context.temp_allocator) return strings.clone(temp[:len(temp)], allocator) } @@ -179,4 +178,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) { case: return // Empty or invalid address type } unreachable() -} \ No newline at end of file +} From 13c6352b8e7302f741ac76f86863b416dd04a14a Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Wed, 1 Mar 2023 18:55:02 -0800 Subject: [PATCH 07/23] catch alloc error on wstring_to_utf8 convert --- core/net/interface_windows.odin | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index a9634afee..c4c0ad1bc 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -63,13 +63,21 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N } _interfaces := make([dynamic]Network_Interface, 0, allocator) - for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { + friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator) + if err1 != nil { return {}, Platform_Error(err1) } + + description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator) + if err2 != nil { return {}, Platform_Error(err2) } + + dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator) + if err3 != nil { return {}, Platform_Error(err3) } + interface := Network_Interface{ adapter_name = strings.clone(string(adapter.AdapterName)), - friendly_name = wstring_to_string(adapter.FriendlyName), - description = wstring_to_string(adapter.Description), - dns_suffix = wstring_to_string(adapter.DnsSuffix), + friendly_name = friendly_name, + description = description, + dns_suffix = dns_suffix, mtu = adapter.MTU, @@ -139,14 +147,6 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N return _interfaces[:], {} } -/* - Takes a UTF-16 Wstring and clones it. -*/ -wstring_to_string :: proc(s: ^u16, max_size := 256, allocator := context.allocator) -> (res: string) { - temp := sys.wstring_to_utf8((sys.Wstring)(s), max_size, context.temp_allocator) - return strings.clone(temp[:len(temp)], allocator) -} - /* Interpret SOCKET_ADDRESS as an Address */ From c02ff3af27afc013336de24570bbbaf1d66643d0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 2 Mar 2023 13:45:12 +0100 Subject: [PATCH 08/23] Update comments --- core/net/addr.odin | 188 ++++++++++++-------------------------- core/net/addr_linux.odin | 3 +- core/net/common.odin | 74 +++++---------- core/net/dns.odin | 19 +--- core/net/dns_unix.odin | 2 +- core/net/dns_windows.odin | 21 +++-- core/net/doc.odin | 1 - core/net/socket.odin | 3 - 8 files changed, 100 insertions(+), 211 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index b296048d2..77debcf26 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -31,10 +31,10 @@ import "core:fmt" The port, if present, is required to be a base 10 number in the range 0-65535, inclusive. - If `non_decimal_address` is true, `aton` is told each component must be decimal and max 255. + If `allow_non_decimal` is true, `aton` is told each component must be decimal and max 255. */ -parse_ip4_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> (addr: IP4_Address, ok: bool) { - res, res_ok := aton(address_and_maybe_port, .IP4, !non_decimal_address) +parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) { + res, res_ok := aton(address_and_maybe_port, .IP4, !allow_non_decimal) if ip4, ip4_ok := res.(IP4_Address); ip4_ok { return ip4, res_ok } @@ -58,12 +58,10 @@ parse_ip4_address :: proc(address_and_maybe_port: string, non_decimal_address := The port, if present, is required to be a base 10 number in the range 0-65535, inclusive. */ -aton :: proc(address_and_maybe_port: string, family: Address_Family, is_decimal_only := false) -> (addr: Address, ok: bool) { +aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only := false) -> (addr: Address, ok: bool) { switch family { case .IP4: - /* - There is no valid address shorter than `0.0.0.0`. - */ + // There is no valid address shorter than `0.0.0.0`. if len(address_and_maybe_port) < 7 { return {}, false } @@ -76,7 +74,7 @@ aton :: proc(address_and_maybe_port: string, family: Address_Family, is_decimal_ max_value := u64(max(u32)) bases := DEFAULT_DIGIT_BASES - if is_decimal_only { + if allow_decimal_only { max_value = 255 bases = {.Dec} } @@ -86,10 +84,8 @@ aton :: proc(address_and_maybe_port: string, family: Address_Family, is_decimal_ return {}, false } - /* - Decimal-only addresses may not have a leading zero. - */ - if is_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' { + // Decimal-only addresses may not have a leading zero. + if allow_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' { return } @@ -108,9 +104,7 @@ aton :: proc(address_and_maybe_port: string, family: Address_Family, is_decimal_ i += 1 } - /* - Distribute parts. - */ + // Distribute parts. switch i { case 1: buf[1] = buf[0] & 0xffffff @@ -155,14 +149,10 @@ IPv6_MIN_COLONS :: 2 IPv6_PIECE_COUNT :: 8 parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) { - /* - If we have an IPv6 address of the form [IP]:Port, first get us just the IP. - */ + // If we have an IPv6 address of the form [IP]:Port, first get us just the IP. address, _ := split_port(address_and_maybe_port) or_return - /* - Early bailouts based on length and number of pieces. - */ + // Early bailouts based on length and number of pieces. if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return } /* @@ -186,10 +176,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, piece_end += 1 case ':': - /* - If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, - instead of it being the tail: invalid. - */ + // If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, instead of it being the tail: invalid. if dot_count > 0 { return } pieces_temp[colon_count] = address[piece_start:piece_end] @@ -197,19 +184,14 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, colon_count += 1 if colon_count > IPv6_PIECE_COUNT { return } - /* - If there's anything left, put it in the next piece. - */ + // If there's anything left, put it in the next piece. piece_start = i + 1 piece_end = piece_start case '.': - /* - IPv4 address is treated as one piece. No need to update `piece_*`. - */ + // IPv4 address is treated as one piece. No need to update `piece_*`. dot_count += 1 - case: // Invalid character, return early return } @@ -217,19 +199,13 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, if colon_count < IPv6_MIN_COLONS { return } - /* - Assign the last piece string. - */ + // Assign the last piece string. pieces_temp[colon_count] = address[piece_start:] - /* - `pieces` now holds the same output as it would if had used `strings.split`. - */ + // `pieces` now holds the same output as it would if had used `strings.split`. pieces := pieces_temp[:colon_count + 1] - /* - Check if we have what looks like an embedded IPv4 address. - */ + // Check if we have what looks like an embedded IPv4 address. ipv4: IP4_Address have_ipv4: bool @@ -252,19 +228,12 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, if !have_ipv4 { return } } - /* - Check for `::` being used more than once, and save the skip. - */ + // Check for `::` being used more than once, and save the skip. zero_skip := -1 - for i in 1.. (addr: IP6_Address, if zero_skip != -1 { before_skip = zero_skip - after_skip = len(pieces) - zero_skip - 1 + after_skip = colon_count - zero_skip - /* - An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1. - */ + // An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1. if have_ipv4 { after_skip += 1 } - /* - Adjust for leading `::`. - */ + // Adjust for leading `::`. if pieces[0] == "" { before_skip -= 1 // Leading `:` can only be part of `::`. if before_skip > 0 { return } } - /* - Adjust for trailing `::`. - */ - if pieces[len(pieces) - 1] == "" { + // Adjust for trailing `::`. + if pieces[colon_count] == "" { after_skip -= 1 // Trailing `:` can only be part of `::`. if after_skip > 0 { return } @@ -318,20 +281,16 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, No zero skip means everything is part of "before the skip". An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1. */ - piece_count := len(pieces) + piece_count := colon_count + 1 if have_ipv4 { piece_count += 1 } - /* - Do we have the complete set? - */ + // Do we have the complete set? if piece_count != IPv6_PIECE_COUNT { return } - /* - Validate leading and trailing empty parts, as they can only be part of a `::`. - */ - if pieces[0] == "" || pieces[len(pieces) - 1] == "" { return } + // Validate leading and trailing empty parts, as they can only be part of a `::`. + if pieces[0] == "" || pieces[colon_count] == "" { return } before_skip = piece_count @@ -339,9 +298,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, num_skipped = 0 } - /* - Now try to parse the pieces into a 8 16-bit pieces. - */ + // Now try to parse the pieces into a 8 16-bit pieces. piece_values: [IPv6_PIECE_COUNT]u16be idx := 0 @@ -358,9 +315,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, piece := pieces[idx] - /* - An IPv6 piece can at most contain 4 hex digits. - */ + // An IPv6 piece can at most contain 4 hex digits. if len(piece) > 4 { return } if piece != "" { @@ -393,9 +348,7 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, piece := pieces[idx] - /* - An IPv6 piece can at most contain 4 hex digits. - */ + // An IPv6 piece can contain at most 4 hex digits. if len(piece) > 4 { return } if piece != "" { @@ -408,20 +361,16 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, } } - /* - Distribute IPv4 address into last two pieces, if applicable. - */ + // Distribute IPv4 address into last two pieces, if applicable. if have_ipv4 { val := u16(ipv4[0]) << 8 val |= u16(ipv4[1]) piece_values[6] = u16be(val) - val = u16(ipv4[2]) << 8 val |= u16(ipv4[3]) piece_values[7] = u16be(val) } - return transmute(IP6_Address)piece_values, true } @@ -430,22 +379,21 @@ parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, If it's determined not to be, try as an IPv4 address, optionally in non-decimal format. */ parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address { - addr6, ok6 := parse_ip6_address(address_and_maybe_port) - if ok6 do return addr6 - addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address) - if ok4 do return addr4 + if addr6, ok6 := parse_ip6_address(address_and_maybe_port); ok6 { + return addr6 + } + if addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address); ok4 { + return addr4 + } return nil } parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) { - addr_str, port, split_ok := split_port(endpoint_str) - if !split_ok do return - - addr := parse_address(addr_str) - if addr == nil do return - - ep = Endpoint { address = addr, port = port } - ok = true + if addr_str, port, split_ok := split_port(endpoint_str); split_ok { + if addr := parse_address(addr_str); addr != nil { + return Endpoint { address = addr, port = port }, true + } + } return } @@ -565,9 +513,7 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> case IP4_Address: fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3]) case IP6_Address: - /* - First find the longest run of zeroes. - */ + // First find the longest run of zeroes. Zero_Run :: struct { start: int, end: int, @@ -609,9 +555,7 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> } } - /* - If we were in a run, this is where we reset it. - */ + // If we were in a run, this is where we reset it. if val != 0 { run = {-1, -1} } @@ -621,9 +565,7 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> for val, i in addr { if best.start == i || best.end == i { - /* - For the left and right side of the best zero run, print a `:`. - */ + // For the left and right side of the best zero run, print a `:`. fmt.sbprint(&b, ":") } else if i < best.start { /* @@ -710,9 +652,7 @@ DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex} Numbers will otherwise be considered to be in base 10. */ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) { - /* - Default to base 10 - */ + // Default to base 10 base := u64(10) input := input @@ -732,9 +672,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D if bases != {.IPv6} { return } // Must be used on its own. base = 16 } else { - /* - Scan for and consume prefix, if applicable. - */ + // Scan for and consume prefix, if applicable. if len(input) >= 2 && input[0] == '0' { if .Hex in bases && (input[1] == 'x' || input[1] == 'X') { base = 16 @@ -759,9 +697,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D digit_bytes += 1 if base == 8 { - /* - Out of range for octal numbers. - */ + // Out of range for octal numbers. return value, digit_bytes + prefix_bytes, false } value = value * base + u64(ch - '0') @@ -770,9 +706,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D digit_bytes += 1 if base == 8 || base == 10 { - /* - Out of range for octal and decimal numbers. - */ + // Out of range for octal and decimal numbers. return value, digit_bytes + prefix_bytes, false } value = value * base + (u64(ch - 'a') + 10) @@ -781,9 +715,7 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D digit_bytes += 1 if base == 8 || base == 10 { - /* - Out of range for octal and decimal numbers. - */ + // Out of range for octal and decimal numbers. return value, digit_bytes + prefix_bytes, false } value = value * base + (u64(ch - 'A') + 10) @@ -797,22 +729,16 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D break parse_loop case: - /* - Invalid character encountered. - */ + // Invalid character encountered. return value, digit_bytes + prefix_bytes, false } if value > max_value { - /* - Out-of-range number. - */ + // Out-of-range number. return value, digit_bytes + prefix_bytes, false } } - /* - If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. - */ + // If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. return value, digit_bytes + prefix_bytes, digit_bytes >= 1 -} +} \ No newline at end of file diff --git a/core/net/addr_linux.odin b/core/net/addr_linux.odin index 90ce0c9ef..f93b508c4 100644 --- a/core/net/addr_linux.odin +++ b/core/net/addr_linux.odin @@ -84,8 +84,7 @@ sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) port = port, } case: - //panic("native_addr is neither IP4 or IP6 address") - return {} + panic("native_addr is neither IP4 or IP6 address") } return } diff --git a/core/net/common.odin b/core/net/common.odin index 9e980e88e..6be377aad 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -22,10 +22,8 @@ package net import "core:runtime" /* - TUNEABLES -*/ + TUNEABLES - See also top of `dns.odin` for DNS configuration. -/* Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new socket, and the client socket, respectively. This can also be set on a per-socket basis using the 'options' optional parameter to those procedures. @@ -44,25 +42,17 @@ import "core:runtime" However, you can avoid this by buffering things up yourself if you wish to send a lot of short data chunks, when TCP_NODELAY is enabled on that socket. */ + ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true) -/* - See also top of `dns.odin` for DNS configuration. -*/ - -/* - COMMON DEFINITIONS -*/ - +// COMMON DEFINITIONS Maybe :: runtime.Maybe General_Error :: enum { Unable_To_Enumerate_Network_Interfaces = 1, } -/* - `Platform_Error` is used to wrap errors returned by the different platforms that defy translation into a common error. -*/ +// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. Platform_Error :: enum u32 {} /* @@ -93,7 +83,6 @@ Network_Error :: union { DNS_Error, } - Resolve_Error :: enum { Unable_To_Resolve = 1, } @@ -107,10 +96,7 @@ DNS_Error :: enum { System_Error, } -/* - SOCKET OPTIONS & DEFINITIONS -*/ - +// SOCKET OPTIONS & DEFINITIONS TCP_Options :: struct { no_delay: bool, } @@ -195,9 +181,7 @@ Network_Interface :: struct { }, } -/* - Empty bit set is unknown state. -*/ +// Empty bit set is unknown state. Link_States :: enum u32 { Up = 1, Down = 2, @@ -261,18 +245,14 @@ Address_Duplication :: enum i32 { Preferred = 4, } -/* - DNS DEFINITIONS -*/ - +// DNS DEFINITIONS DNS_Configuration :: struct { - /* - Configuration files. - */ + // Configuration files. resolv_conf: string, hosts_file: string, - // TODO: Allow loading these up with `reload_configuration()` call or the like so we don't have to do it each call. + // TODO: Allow loading these up with `reload_configuration()` call or the like, + // so we don't have to do it each call. name_servers: []Endpoint, hosts_file_entries: []DNS_Record, } @@ -295,25 +275,19 @@ DNS_Record_Type :: enum u16 { SRV = DNS_TYPE_SRV, } -/* - Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field. -*/ +// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field. DNS_Record_Base :: struct { record_name: string, - ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated. + ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated. } -/* - An IP4 address that the domain name maps to. There can be any number of these. -*/ +// An IP4 address that the domain name maps to. There can be any number of these. DNS_Record_IP4 :: struct { using base: DNS_Record_Base, address: IP4_Address, } -/* - An IPv6 address that the domain name maps to. There can be any number of these. -*/ +// An IPv6 address that the domain name maps to. There can be any number of these. DNS_Record_IP6 :: struct { using base: DNS_Record_Base, address: IP6_Address, @@ -356,15 +330,17 @@ DNS_Record_MX :: struct { preference: int, } -// An endpoint for a service that is available through the domain name. -// This is the way to discover the services that a domain name provides. -// -// Clients MUST attempt to contact the host with the lowest priority that they can reach. -// If two hosts have the same priority, they should be contacted in the order according to their weight. -// Hosts with larger weights should have a proportionally higher chance of being contacted by clients. -// A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise). -// -// The host may be "." to indicate that it is "decidedly not available" on this domain. +/* + An endpoint for a service that is available through the domain name. + This is the way to discover the services that a domain name provides. + + Clients MUST attempt to contact the host with the lowest priority that they can reach. + If two hosts have the same priority, they should be contacted in the order according to their weight. + Hosts with larger weights should have a proportionally higher chance of being contacted by clients. + A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise). + + The host may be "." to indicate that it is "decidedly not available" on this domain. +*/ DNS_Record_SRV :: struct { // base contains the full name of this record. // e.g: _sip._tls.example.com diff --git a/core/net/dns.odin b/core/net/dns.odin index 087d2e146..2875029de 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -54,13 +54,9 @@ destroy_dns_configuration :: proc() { dns_configuration := DEFAULT_DNS_CONFIGURATION -/* - Always allocates for consistency. -*/ +// Always allocates for consistency. replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) { - /* - Nothing to replace. Return a clone of the original. - */ + // Nothing to replace. Return a clone of the original. if strings.count(path, "%") != 2 { return strings.clone(path), true } @@ -76,7 +72,6 @@ replace_environment_path :: proc(path: string, allocator := context.allocator) - defer delete(env_val) res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1) - return res, true } @@ -171,9 +166,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net unreachable() } -/* - `get_dns_records` uses OS-specific methods to query DNS records. -*/ +// `get_dns_records` uses OS-specific methods to query DNS records. when ODIN_OS == .Windows { get_dns_records_from_os :: get_dns_records_windows } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { @@ -434,9 +427,7 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> ( return _hosts[:], true } -/* - www.google.com -> 3www6google3com0 -*/ +// www.google.com -> 3www6google3com0 encode_hostname :: proc(b: ^strings.Builder, hostname: string, allocator := context.allocator) -> (ok: bool) { _hostname := hostname for section in strings.split_iterator(&_hostname, ".") { @@ -861,4 +852,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator } return _records[:], true -} +} \ No newline at end of file diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index 7f57927fd..12c3eaf29 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -80,4 +80,4 @@ get_dns_records_unix :: proc(hostname: string, type: DNS_Record_Type, allocator } return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) -} +} \ No newline at end of file diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index ae38ca05f..d445c8816 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -22,14 +22,16 @@ import "core:mem" import win "core:sys/windows" -// Performs a recursive DNS query for records of a particular type for the hostname. -// -// NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, -// meaning that DNS queries for a hostname will resolve through CNAME records until an -// IP address is reached. -// -// WARNING: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! -// See `destroy_records`. +/* + Performs a recursive DNS query for records of a particular type for the hostname. + + NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, + meaning that DNS queries for a hostname will resolve through CNAME records until an + IP address is reached. + + WARNING: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! + See `destroy_records`. +*/ get_dns_records_windows :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator @@ -55,7 +57,6 @@ get_dns_records_windows :: proc(hostname: string, type: DNS_Record_Type, allocat count += 1 } - recs := make([dynamic]DNS_Record, 0, count) if recs == nil do return nil, .System_Error // return no results if OOM. @@ -163,4 +164,4 @@ get_dns_records_windows :: proc(hostname: string, type: DNS_Record_Type, allocat records = recs[:] return -} +} \ No newline at end of file diff --git a/core/net/doc.odin b/core/net/doc.odin index 0c6c08daa..0f1b33172 100644 --- a/core/net/doc.odin +++ b/core/net/doc.odin @@ -42,6 +42,5 @@ this and panic to avoid temp allocations prematurely overwriting data and garbling results, or worse. This means that should you replace the temp allocator with an insufficient one, we'll do our best to loudly complain the first time you try it. - */ package net \ No newline at end of file diff --git a/core/net/socket.odin b/core/net/socket.odin index 1fa57aac0..96985d173 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -16,10 +16,7 @@ */ package net -// // TODO(tetra): Bluetooth, Raw -// - any_socket_to_socket :: proc(any_socket: Any_Socket) -> Socket { switch s in any_socket { case TCP_Socket: return Socket(s) From 64f200dc7419a3b1b8b4ae387d5add57b680ca3e Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Thu, 2 Mar 2023 06:43:20 -0800 Subject: [PATCH 09/23] big error cleanup --- core/net/errors_darwin.odin | 182 +++++++++++++++++++++++++ core/net/errors_linux.odin | 175 ++++++++++++++++++++++++ core/net/errors_openbsd.odin | 191 +++++++++++++++++++++++++++ core/net/errors_windows.odin | 249 +++++++++++++++++++++++++++++++++++ core/net/socket_darwin.odin | 207 ++--------------------------- core/net/socket_linux.odin | 203 ++-------------------------- core/net/socket_openbsd.odin | 206 ++--------------------------- core/net/socket_windows.odin | 237 --------------------------------- 8 files changed, 829 insertions(+), 821 deletions(-) create mode 100644 core/net/errors_darwin.odin create mode 100644 core/net/errors_linux.odin create mode 100644 core/net/errors_openbsd.odin create mode 100644 core/net/errors_windows.odin diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin new file mode 100644 index 000000000..3ee28bc9b --- /dev/null +++ b/core/net/errors_darwin.odin @@ -0,0 +1,182 @@ +// +build darwin +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import "core:os" + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +Bind_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. + Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket. + Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address. + No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available. +} + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +Accept_Error :: enum c.int { + + // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. + Reset = c.int(os.ECONNRESET), + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + + // TODO(tetra): Is this error actually possible here? + Connection_Broken = c.int(os.ENETRESET), + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + + // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Connection_Closed = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), +} + +UDP_Recv_Error :: enum c.int { + Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. +} + +// TODO +TCP_Send_Error :: enum c.int { + // TODO: merge with other errors? + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. +} + +// TODO +UDP_Send_Error :: enum c.int { + Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent. + + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + + // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin new file mode 100644 index 000000000..16a5411ea --- /dev/null +++ b/core/net/errors_linux.odin @@ -0,0 +1,175 @@ +// +build linux +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import "core:os" + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +Dial_Error :: enum c.int { + Port_Required = -1, + + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +Bind_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. + Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket. + Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address. + No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available. +} + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +Accept_Error :: enum c.int { + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + Connection_Broken = c.int(os.ENETRESET), + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + + // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Connection_Closed = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +UDP_Recv_Error :: enum c.int { + // The buffer is too small to fit the entire message, and the message was truncated. + // When this happens, the rest of message is lost. + Buffer_Too_Small = c.int(os.EMSGSIZE), + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. +} + +// TODO +TCP_Send_Error :: enum c.int { + + // TODO(tetra): merge with other errors? + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), // A signal occurred before any data was transmitted. See signal(7). + Interrupted = c.int(os.EINTR), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. +} + +// TODO +UDP_Send_Error :: enum c.int { + Message_Too_Long = c.int(os.EMSGSIZE), // The message is too big. No data was sent. + + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + + // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} diff --git a/core/net/errors_openbsd.odin b/core/net/errors_openbsd.odin new file mode 100644 index 000000000..51d9cc7e8 --- /dev/null +++ b/core/net/errors_openbsd.odin @@ -0,0 +1,191 @@ +// +build openbsd +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. + + + IMPORTANT/TODO: This is a carbon copy of `socket_darwin.odin`. Adjust if necessary. + +*/ +package net + +import "core:c" +import "core:os" + +Create_Socket_Error :: enum c.int { + Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available_Available = c.int(os.ENOMEM), + Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), + Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), + Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), +} + +Dial_Error :: enum c.int { + Port_Required = -1, + Address_In_Use = c.int(os.EADDRINUSE), + In_Progress = c.int(os.EINPROGRESS), + Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), + Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), + Refused = c.int(os.ECONNREFUSED), + Is_Listening_Socket = c.int(os.EACCES), + Already_Connected = c.int(os.EISCONN), + Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline + Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Timeout = c.int(os.ETIMEDOUT), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +Bind_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. + Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. + Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket. + Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address. + No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available. +} + +Listen_Error :: enum c.int { + Address_In_Use = c.int(os.EADDRINUSE), + Already_Connected = c.int(os.EISCONN), + No_Socket_Descriptors_Available = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Nonlocal_Address = c.int(os.EADDRNOTAVAIL), + Not_Socket = c.int(os.ENOTSOCK), + Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), +} + +Accept_Error :: enum c.int { + + // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. + Reset = c.int(os.ECONNRESET), + Not_Listening = c.int(os.EINVAL), + No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Not_Socket = c.int(os.ENOTSOCK), + Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = c.int(os.EWOULDBLOCK), +} + +TCP_Recv_Error :: enum c.int { + Shutdown = c.int(os.ESHUTDOWN), + Not_Connected = c.int(os.ENOTCONN), + + // TODO(tetra): Is this error actually possible here? + Connection_Broken = c.int(os.ENETRESET), + Not_Socket = c.int(os.ENOTSOCK), + Aborted = c.int(os.ECONNABORTED), + + // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Connection_Closed = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), + Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... +} + +UDP_Recv_Error :: enum c.int { + // The buffer is too small to fit the entire message, and the message was truncated. + Truncated = c.int(os.EMSGSIZE), + // The so-called socket is not an open socket. + Not_Socket = c.int(os.ENOTSOCK), + // The so-called socket is, in fact, not even a valid descriptor. + Not_Descriptor = c.int(os.EBADF), + // The buffer did not point to a valid location in memory. + Bad_Buffer = c.int(os.EFAULT), + // A signal occurred before any data was transmitted. See signal(7). + Interrupted = c.int(os.EINTR), + + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. +} + +// TODO +TCP_Send_Error :: enum c.int { + + // TODO: merge with other errors? + Aborted = c.int(os.ECONNABORTED), + Connection_Closed = c.int(os.ECONNRESET), + Not_Connected = c.int(os.ENOTCONN), + Shutdown = c.int(os.ESHUTDOWN), + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + Offline = c.int(os.ENETDOWN), + Host_Unreachable = c.int(os.EHOSTUNREACH), + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), +} + +// TODO +UDP_Send_Error :: enum c.int { + Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent. + + // TODO: not sure what the exact circumstances for this is yet + Network_Unreachable = c.int(os.ENETUNREACH), + No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. + + // The send timeout duration passed before all data was sent. + // See Socket_Option.Send_Timeout. + // NOTE: No, really. Presumably this means something different for nonblocking sockets... + Timeout = c.int(os.EWOULDBLOCK), + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). + + // The send queue was full. + // This is usually a transient issue. + // + // This also shouldn't normally happen on Linux, as data is dropped if it + // doesn't fit in the send queue. + No_Buffer_Space_Available = c.int(os.ENOBUFS), + No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. +} + +Shutdown_Error :: enum c.int { + Aborted = c.int(os.ECONNABORTED), + Reset = c.int(os.ECONNRESET), + Offline = c.int(os.ENETDOWN), + Not_Connected = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), + Invalid_Manner = c.int(os.EINVAL), +} + +Socket_Option_Error :: enum c.int { + Offline = c.int(os.ENETDOWN), + Timeout_When_Keepalive_Set = c.int(os.ENETRESET), + Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), + Reset_When_Keepalive_Set = c.int(os.ENOTCONN), + Not_Socket = c.int(os.ENOTSOCK), +} diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin new file mode 100644 index 000000000..9f9589f29 --- /dev/null +++ b/core/net/errors_windows.odin @@ -0,0 +1,249 @@ +// +build windows +/* + Copyright 2022 Tetralux + Copyright 2022 Colin Davidson + Copyright 2022 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Tetralux: Initial implementation + Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver + Jeroen van Rijn: Cross platform unification, code style, documentation +*/ + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ +package net + +import "core:c" +import win "core:sys/windows" + +Create_Socket_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, + No_Socket_Descriptors_Available = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT, + Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE, + Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT, +} + +Dial_Error :: enum c.int { + Port_Required = -1, + Address_In_Use = win.WSAEADDRINUSE, + In_Progress = win.WSAEALREADY, + Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, + Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT, + Refused = win.WSAECONNREFUSED, + Is_Listening_Socket = win.WSAEINVAL, + Already_Connected = win.WSAEISCONN, + Network_Unreachable = win.WSAENETUNREACH, // Device is offline + Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, + Timeout = win.WSAETIMEDOUT, + Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +} + +Bind_Error :: enum c.int { + Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint. + Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine. + Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. + Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket. + Already_Bound = win.WSAEINVAL, // The socket is already bound to an address. + No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available. +} + +Listen_Error :: enum c.int { + Address_In_Use = win.WSAEADDRINUSE, + Already_Connected = win.WSAEISCONN, + No_Socket_Descriptors_Available = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Nonlocal_Address = win.WSAEADDRNOTAVAIL, + Not_Socket = win.WSAENOTSOCK, + Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, +} + +Accept_Error :: enum c.int { + Not_Listening = win.WSAEINVAL, + No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, + Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, + + // TODO: we may need special handling for this; maybe make a socket a struct with metadata? + Would_Block = win.WSAEWOULDBLOCK, +} + +TCP_Recv_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + Not_Connected = win.WSAENOTCONN, + Bad_Buffer = win.WSAEFAULT, + Keepalive_Failure = win.WSAENETRESET, + Not_Socket = win.WSAENOTSOCK, + Shutdown = win.WSAESHUTDOWN, + Would_Block = win.WSAEWOULDBLOCK, + + // TODO: not functionally different from Reset; merge? + Aborted = win.WSAECONNABORTED, + Timeout = win.WSAETIMEDOUT, + + // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? + Connection_Closed = win.WSAECONNRESET, + + // TODO: verify can actually happen + Host_Unreachable = win.WSAEHOSTUNREACH, +} + +UDP_Recv_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + + // TODO: not functionally different from Reset; merge? + // UDP packets are limited in size, and the length of the incoming message exceeded it. + Aborted = win.WSAECONNABORTED, + Truncated = win.WSAEMSGSIZE, + Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. + Shutdown = win.WSAESHUTDOWN, + Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set. + Bad_Buffer = win.WSAEFAULT, + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. + Would_Block = win.WSAEWOULDBLOCK, + Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. + Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. + Timeout = win.WSAETIMEDOUT, + + // TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled. + Incorrectly_Configured = win.WSAEINVAL, + TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint. +} + +// TODO: consider merging some errors to make handling them easier +// TODO: verify once more what errors to actually expose +TCP_Send_Error :: enum c.int { + + // TODO: not functionally different from Reset; merge? + Aborted = win.WSAECONNABORTED, + Not_Connected = win.WSAENOTCONN, + Shutdown = win.WSAESHUTDOWN, + Connection_Closed = win.WSAECONNRESET, + No_Buffer_Space_Available = win.WSAENOBUFS, + Network_Subsystem_Failure = win.WSAENETDOWN, + Host_Unreachable = win.WSAEHOSTUNREACH, + + // TODO: verify possible, as not mentioned in docs + Offline = win.WSAENETUNREACH, + Timeout = win.WSAETIMEDOUT, + + // A broadcast address was specified, but the .Broadcast socket option isn't set. + Broadcast_Disabled = win.WSAEACCES, + Bad_Buffer = win.WSAEFAULT, + + // Connection is broken due to keepalive activity detecting a failure during the operation. + Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? + Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket. +} + +UDP_Send_Error :: enum c.int { + Network_Subsystem_Failure = win.WSAENETDOWN, + + // TODO: not functionally different from Reset; merge? + Aborted = win.WSAECONNABORTED, // UDP packets are limited in size, and len(buf) exceeded it. + Message_Too_Long = win.WSAEMSGSIZE, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. + Remote_Not_Listening = win.WSAECONNRESET, + Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set. + Broadcast_Disabled = win.WSAEACCES, + Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation. + + // TODO: not functionally different from Reset; merge? + Keepalive_Failure = win.WSAENETRESET, + No_Buffer_Space_Available = win.WSAENOBUFS, + Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. + + // This socket is unidirectional and cannot be used to send any data. + // TODO: verify possible; decide whether to keep if not + Receive_Only = win.WSAEOPNOTSUPP, + Would_Block = win.WSAEWOULDBLOCK, + Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. + Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address. + Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket. + Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. + Timeout = win.WSAETIMEDOUT, +} + +Shutdown_Manner :: enum c.int { + Receive = win.SD_RECEIVE, + Send = win.SD_SEND, + Both = win.SD_BOTH, +} + +Shutdown_Error :: enum c.int { + Aborted = win.WSAECONNABORTED, + Reset = win.WSAECONNRESET, + Offline = win.WSAENETDOWN, + Not_Connected = win.WSAENOTCONN, + Not_Socket = win.WSAENOTSOCK, + Invalid_Manner = win.WSAEINVAL, +} + +Socket_Option :: enum c.int { + // bool: Whether the address that this socket is bound to can be reused by other sockets. + // This allows you to bypass the cooldown period if a program dies while the socket is bound. + Reuse_Address = win.SO_REUSEADDR, + + // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. + Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, + + // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding + Keep_Alive = win.SO_KEEPALIVE, + + // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. + Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, + + // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. + Dont_Linger = win.SO_DONTLINGER, + + // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. + Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, + + // bool: When true, disables send-coalescing, therefore reducing latency. + TCP_Nodelay = win.TCP_NODELAY, + + // win.LINGER: Customizes how long (if at all) the socket will remain open when there + // is some remaining data waiting to be sent, and net.close() is called. + Linger = win.SO_LINGER, + + // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. + Receive_Buffer_Size = win.SO_RCVBUF, + + // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. + Send_Buffer_Size = win.SO_SNDBUF, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Receive_Timeout = win.SO_RCVTIMEO, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Send_Timeout = win.SO_SNDTIMEO, + + // bool: Allow sending to, receiving from, and binding to, a broadcast address. + Broadcast = win.SO_BROADCAST, +} + +Socket_Option_Error :: enum c.int { + Linger_Only_Supports_Whole_Seconds = 1, + + // The given value is too big or small to be given to the OS. + Value_Out_Of_Range, + + Network_Subsystem_Failure = win.WSAENETDOWN, + Timeout_When_Keepalive_Set = win.WSAENETRESET, + Invalid_Option_For_Socket = win.WSAENOPROTOOPT, + Reset_When_Keepalive_Set = win.WSAENOTCONN, + Not_Socket = win.WSAENOTSOCK, +} diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 89ad7b31c..75c0eaf56 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -23,16 +23,6 @@ import "core:time" Platform_Socket :: os.Socket -Create_Socket_Error :: enum c.int { - Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available_Available = c.int(os.ENOMEM), - Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), -} - create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int @@ -64,25 +54,6 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } - -Dial_Error :: enum c.int { - Port_Required = -1, - - Address_In_Use = c.int(os.EADDRINUSE), - In_Progress = c.int(os.EINPROGRESS), - Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), - Refused = c.int(os.ECONNREFUSED), - Is_Listening_Socket = c.int(os.EACCES), - Already_Connected = c.int(os.EISCONN), - Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Timeout = c.int(os.ETIMEDOUT), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required @@ -107,22 +78,6 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option return } - -Bind_Error :: enum c.int { - // Another application is currently bound to this endpoint. - Address_In_Use = c.int(os.EADDRINUSE), - // The address is not a local address on this machine. - Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Broadcast_Disabled = c.int(os.EACCES), - // The address family of the address does not match that of the socket. - Address_Family_Mismatch = c.int(os.EFAULT), - // The socket is already bound to an address. - Already_Bound = c.int(os.EINVAL), - // There are not enough ephemeral ports available. - No_Ports_Available = c.int(os.ENOBUFS), -} - bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) @@ -133,7 +88,6 @@ bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { return } - // This type of socket becomes bound when you try to send data. // This is likely what you want if you want to send data unsolicited. // @@ -156,18 +110,6 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_So return } - - -Listen_Error :: enum c.int { - Address_In_Use = c.int(os.EADDRINUSE), - Already_Connected = c.int(os.EISCONN), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - Not_Socket = c.int(os.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), -} - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) @@ -193,18 +135,6 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } - - -Accept_Error :: enum c.int { - Reset = c.int(os.ECONNRESET), // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. - Not_Listening = c.int(os.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -219,28 +149,11 @@ accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, e return } - - close :: proc(skt: Any_Socket) { s := any_socket_to_socket(skt) os.close(os.Handle(Platform_Socket(s))) } - - -TCP_Recv_Error :: enum c.int { - Shutdown = c.int(os.ESHUTDOWN), - Not_Connected = c.int(os.ENOTCONN), - Connection_Broken = c.int(os.ENETRESET), // TODO(tetra): Is this error actually possible here? - Not_Socket = c.int(os.ENOTSOCK), - Aborted = c.int(os.ECONNABORTED), - Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return @@ -253,25 +166,6 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -UDP_Recv_Error :: enum c.int { - // The buffer is too small to fit the entire message, and the message was truncated. - Truncated = c.int(os.EMSGSIZE), - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The socket must be bound for this operation, but isn't. - Socket_Not_Bound = c.int(os.EINVAL), -} - recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return @@ -292,32 +186,6 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo recv :: proc{recv_tcp, recv_udp} - - -// TODO -TCP_Send_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), // TODO: merge with other errors? - Connection_Closed = c.int(os.ECONNRESET), - Not_Connected = c.int(os.ENOTCONN), - Shutdown = c.int(os.ESHUTDOWN), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), -} - // Repeatedly sends data until the entire buffer is sent. // If a send fails before all data is sent, returns the amount // sent up to that point. @@ -335,36 +203,6 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -// TODO -UDP_Send_Error :: enum c.int { - // The message is too big. No data was sent. - Truncated = c.int(os.EMSGSIZE), - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(os.ENETUNREACH), - // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - No_Outbound_Ports_Available = c.int(os.EAGAIN), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - // No memory was available to properly manage the send queue. - No_Memory_Available = c.int(os.ENOMEM), -} - send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { toaddr := endpoint_to_sockaddr(to) for bytes_written < len(buf) { @@ -382,22 +220,10 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: send :: proc{send_tcp, send_udp} - - - Shutdown_Manner :: enum c.int { Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -Shutdown_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), - Reset = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Not_Connected = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), - Invalid_Manner = c.int(os.EINVAL), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), } shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { @@ -409,29 +235,16 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } - - - Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - - Linger = c.int(os.SO_LINGER), - - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO), - Send_Timeout = c.int(os.SO_SNDTIMEO), -} - -Socket_Option_Error :: enum c.int { - Offline = c.int(os.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(os.ENETRESET), - Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), + TCP_Nodelay = c.int(os.TCP_NODELAY), + Linger = c.int(os.SO_LINGER), + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO), + Send_Timeout = c.int(os.SO_SNDTIMEO), } set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index dcc48d3fa..4b80ef202 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -23,16 +23,6 @@ import "core:time" Platform_Socket :: os.Socket -Create_Socket_Error :: enum c.int { - Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available_Available = c.int(os.ENOMEM), - Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), -} - create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int @@ -64,25 +54,6 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } - -Dial_Error :: enum c.int { - Port_Required = -1, - - Address_In_Use = c.int(os.EADDRINUSE), - In_Progress = c.int(os.EINPROGRESS), - Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), - Refused = c.int(os.ECONNREFUSED), - Is_Listening_Socket = c.int(os.EACCES), - Already_Connected = c.int(os.EISCONN), - Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Timeout = c.int(os.ETIMEDOUT), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required @@ -112,21 +83,6 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option } -Bind_Error :: enum c.int { - // Another application is currently bound to this endpoint. - Address_In_Use = c.int(os.EADDRINUSE), - // The address is not a local address on this machine. - Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Broadcast_Disabled = c.int(os.EACCES), - // The address family of the address does not match that of the socket. - Address_Family_Mismatch = c.int(os.EFAULT), - // The socket is already bound to an address. - Already_Bound = c.int(os.EINVAL), - // There are not enough ephemeral ports available. - No_Ports_Available = c.int(os.ENOBUFS), -} - bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) @@ -161,17 +117,6 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_So } - -Listen_Error :: enum c.int { - Address_In_Use = c.int(os.EADDRINUSE), - Already_Connected = c.int(os.EISCONN), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - Not_Socket = c.int(os.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), -} - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) @@ -197,17 +142,6 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } - - -Accept_Error :: enum c.int { - Not_Listening = c.int(os.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -232,21 +166,6 @@ close :: proc(skt: Any_Socket) { os.close(os.Handle(Platform_Socket(s))) } - - -TCP_Recv_Error :: enum c.int { - Shutdown = c.int(os.ESHUTDOWN), - Not_Connected = c.int(os.ENOTCONN), - Connection_Broken = c.int(os.ENETRESET), - Not_Socket = c.int(os.ENOTSOCK), - Aborted = c.int(os.ECONNABORTED), - Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return @@ -259,26 +178,6 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -UDP_Recv_Error :: enum c.int { - // The buffer is too small to fit the entire message, and the message was truncated. - // When this happens, the rest of message is lost. - Buffer_Too_Small = c.int(os.EMSGSIZE), - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was received. - // See Socket_Option.Receive_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The socket must be bound for this operation, but isn't. - Socket_Not_Bound = c.int(os.EINVAL), -} - recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return @@ -312,31 +211,6 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo recv :: proc{recv_tcp, recv_udp} - -// TODO -TCP_Send_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), // TODO(tetra): merge with other errors? - Connection_Closed = c.int(os.ECONNRESET), - Not_Connected = c.int(os.ENOTCONN), - Shutdown = c.int(os.ESHUTDOWN), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), -} - // Repeatedly sends data until the entire buffer is sent. // If a send fails before all data is sent, returns the amount // sent up to that point. @@ -354,36 +228,6 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -// TODO -UDP_Send_Error :: enum c.int { - // The message is too big. No data was sent. - Message_Too_Long = c.int(os.EMSGSIZE), - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(os.ENETUNREACH), - // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - No_Outbound_Ports_Available = c.int(os.EAGAIN), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - // No memory was available to properly manage the send queue. - No_Memory_Available = c.int(os.ENOMEM), -} - // Sends a single UDP datagram packet. // // Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. @@ -401,22 +245,10 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: send :: proc{send_tcp, send_udp} - - - Shutdown_Manner :: enum c.int { Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -Shutdown_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), - Reset = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Not_Connected = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), - Invalid_Manner = c.int(os.EINVAL), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), } shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { @@ -428,29 +260,16 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } - - - Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - - Linger = c.int(os.SO_LINGER), - - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW), - Send_Timeout = c.int(os.SO_SNDTIMEO_NEW), -} - -Socket_Option_Error :: enum c.int { - Offline = c.int(os.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(os.ENETRESET), - Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), + TCP_Nodelay = c.int(os.TCP_NODELAY), + Linger = c.int(os.SO_LINGER), + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW), + Send_Timeout = c.int(os.SO_SNDTIMEO_NEW), } set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { @@ -529,4 +348,4 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal } return nil -} \ No newline at end of file +} diff --git a/core/net/socket_openbsd.odin b/core/net/socket_openbsd.odin index 746b886ef..f69a62a21 100644 --- a/core/net/socket_openbsd.odin +++ b/core/net/socket_openbsd.odin @@ -27,16 +27,6 @@ import "core:time" Platform_Socket :: os.Socket -Create_Socket_Error :: enum c.int { - Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available_Available = c.int(os.ENOMEM), - Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), -} - create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int @@ -68,25 +58,6 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } - -Dial_Error :: enum c.int { - Port_Required = -1, - - Address_In_Use = c.int(os.EADDRINUSE), - In_Progress = c.int(os.EINPROGRESS), - Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), - Refused = c.int(os.ECONNREFUSED), - Is_Listening_Socket = c.int(os.EACCES), - Already_Connected = c.int(os.EISCONN), - Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Timeout = c.int(os.ETIMEDOUT), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required @@ -111,22 +82,6 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option return } - -Bind_Error :: enum c.int { - // Another application is currently bound to this endpoint. - Address_In_Use = c.int(os.EADDRINUSE), - // The address is not a local address on this machine. - Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Broadcast_Disabled = c.int(os.EACCES), - // The address family of the address does not match that of the socket. - Address_Family_Mismatch = c.int(os.EFAULT), - // The socket is already bound to an address. - Already_Bound = c.int(os.EINVAL), - // There are not enough ephemeral ports available. - No_Ports_Available = c.int(os.ENOBUFS), -} - bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) @@ -160,18 +115,6 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_So return } - - -Listen_Error :: enum c.int { - Address_In_Use = c.int(os.EADDRINUSE), - Already_Connected = c.int(os.EISCONN), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - Not_Socket = c.int(os.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), -} - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) @@ -197,18 +140,6 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } - - -Accept_Error :: enum c.int { - Reset = c.int(os.ECONNRESET), // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. - Not_Listening = c.int(os.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), - Would_Block = c.int(os.EWOULDBLOCK), // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -223,28 +154,11 @@ accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, e return } - - close :: proc(skt: Any_Socket) { s := any_socket_to_socket(skt) os.close(os.Handle(Platform_Socket(s))) } - - -TCP_Recv_Error :: enum c.int { - Shutdown = c.int(os.ESHUTDOWN), - Not_Connected = c.int(os.ENOTCONN), - Connection_Broken = c.int(os.ENETRESET), // TODO(tetra): Is this error actually possible here? - Not_Socket = c.int(os.ENOTSOCK), - Aborted = c.int(os.ECONNABORTED), - Connection_Closed = c.int(os.ECONNRESET), // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return @@ -257,25 +171,6 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -UDP_Recv_Error :: enum c.int { - // The buffer is too small to fit the entire message, and the message was truncated. - Truncated = c.int(os.EMSGSIZE), - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The socket must be bound for this operation, but isn't. - Socket_Not_Bound = c.int(os.EINVAL), -} - recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return @@ -296,30 +191,6 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo recv :: proc{recv_tcp, recv_udp} - - -// TODO -TCP_Send_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), // TODO: merge with other errors? - Connection_Closed = c.int(os.ECONNRESET), - Not_Connected = c.int(os.ENOTCONN), - Shutdown = c.int(os.ESHUTDOWN), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - // Repeatedly sends data until the entire buffer is sent. // If a send fails before all data is sent, returns the amount // sent up to that point. @@ -337,36 +208,6 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -// TODO -UDP_Send_Error :: enum c.int { - // The message is too big. No data was sent. - Truncated = c.int(os.EMSGSIZE), - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(os.ENETUNREACH), - // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - No_Outbound_Ports_Available = c.int(os.EAGAIN), - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. - // See signal(7). - Interrupted = c.int(os.EINTR), - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - // No memory was available to properly manage the send queue. - No_Memory_Available = c.int(os.ENOMEM), -} - send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { toaddr := endpoint_to_sockaddr(to) for bytes_written < len(buf) { @@ -384,22 +225,10 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: send :: proc{send_tcp, send_udp} - - - Shutdown_Manner :: enum c.int { Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -Shutdown_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), - Reset = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Not_Connected = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), - Invalid_Manner = c.int(os.EINVAL), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), } shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { @@ -411,29 +240,16 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } - - - Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - - Linger = c.int(os.SO_LINGER), - - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO), - Send_Timeout = c.int(os.SO_SNDTIMEO), -} - -Socket_Option_Error :: enum c.int { - Offline = c.int(os.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(os.ENETRESET), - Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), + TCP_Nodelay = c.int(os.TCP_NODELAY), + Linger = c.int(os.SO_LINGER), + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO), + Send_Timeout = c.int(os.SO_SNDTIMEO), } set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { @@ -512,4 +328,4 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal } return nil -} \ No newline at end of file +} diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index a08248d91..ed6342316 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -23,16 +23,6 @@ import "core:time" Platform_Socket :: win.SOCKET -Create_Socket_Error :: enum c.int { - Network_Subsystem_Failure = win.WSAENETDOWN, - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT, - Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE, - Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT, -} - @(init, private) ensure_winsock_initialized :: proc() { win.ensure_winsock_initialized() @@ -69,25 +59,6 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } - -Dial_Error :: enum c.int { - Port_Required = -1, - - Address_In_Use = win.WSAEADDRINUSE, - In_Progress = win.WSAEALREADY, - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, - Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT, - Refused = win.WSAECONNREFUSED, - Is_Listening_Socket = win.WSAEINVAL, - Already_Connected = win.WSAEISCONN, - Network_Unreachable = win.WSAENETUNREACH, // Device is offline - Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Timeout = win.WSAETIMEDOUT, - Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { err = .Port_Required @@ -117,21 +88,6 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option return } -Bind_Error :: enum c.int { - // Another application is currently bound to this endpoint. - Address_In_Use = win.WSAEADDRINUSE, - // The address is not a local address on this machine. - Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, - // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Broadcast_Disabled = win.WSAEACCES, - // The address family of the address does not match that of the socket. - Address_Family_Mismatch = win.WSAEFAULT, - // The socket is already bound to an address. - Already_Bound = win.WSAEINVAL, - // There are not enough ephemeral ports available. - No_Ports_Available = win.WSAENOBUFS, -} - bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) @@ -165,18 +121,6 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_So return } - - -Listen_Error :: enum c.int { - Address_In_Use = win.WSAEADDRINUSE, - Already_Connected = win.WSAEISCONN, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Nonlocal_Address = win.WSAEADDRNOTAVAIL, - Not_Socket = win.WSAENOTSOCK, - Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, -} - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) @@ -199,17 +143,6 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } - - -Accept_Error :: enum c.int { - Not_Listening = win.WSAEINVAL, - No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, - Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? -} - accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { for { sockaddr: win.SOCKADDR_STORAGE_LH @@ -236,30 +169,12 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: } } - - close :: proc(skt: Any_Socket) { if s := any_socket_to_socket(skt); s != {} { win.closesocket(Platform_Socket(s)) } } - - -TCP_Recv_Error :: enum c.int { - Network_Subsystem_Failure = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Bad_Buffer = win.WSAEFAULT, - Keepalive_Failure = win.WSAENETRESET, - Not_Socket = win.WSAENOTSOCK, - Shutdown = win.WSAESHUTDOWN, - Would_Block = win.WSAEWOULDBLOCK, - Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? - Timeout = win.WSAETIMEDOUT, - Connection_Closed = win.WSAECONNRESET, // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Host_Unreachable = win.WSAEHOSTUNREACH, // TODO: verify can actually happen -} - recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return @@ -272,32 +187,6 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -UDP_Recv_Error :: enum c.int { - Network_Subsystem_Failure = win.WSAENETDOWN, - Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? - // UDP packets are limited in size, and the length of the incoming message exceeded it. - Truncated = win.WSAEMSGSIZE, - // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Remote_Not_Listening = win.WSAECONNRESET, - Shutdown = win.WSAESHUTDOWN, - // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, - No_Buffer_Space_Available = win.WSAENOBUFS, - // The socket is not valid socket handle. - Not_Socket = win.WSAENOTSOCK, - Would_Block = win.WSAEWOULDBLOCK, - // The remote host cannot be reached from this host at this time. - Host_Unreachable = win.WSAEHOSTUNREACH, - // The network cannot be reached from this host at this time. - Offline = win.WSAENETUNREACH, - Timeout = win.WSAETIMEDOUT, - // The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled. - Incorrectly_Configured = win.WSAEINVAL, // TODO: can this actually happen? - // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint. - TTL_Expired = win.WSAENETRESET, -} - recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return @@ -318,31 +207,6 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo recv :: proc{recv_tcp, recv_udp} - -// -// TODO: consider merging some errors to make handling them easier -// TODO: verify once more what errors to actually expose -// - -TCP_Send_Error :: enum c.int { - Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? - Not_Connected = win.WSAENOTCONN, - Shutdown = win.WSAESHUTDOWN, - Connection_Closed = win.WSAECONNRESET, - No_Buffer_Space_Available = win.WSAENOBUFS, - Network_Subsystem_Failure = win.WSAENETDOWN, - Host_Unreachable = win.WSAEHOSTUNREACH, - Offline = win.WSAENETUNREACH, // TODO: verify possible, as not mentioned in docs - Timeout = win.WSAETIMEDOUT, - // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, - // Connection is broken due to keepalive activity detecting a failure during the operation. - Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? - // The so-called socket is not an open socket. - Not_Socket = win.WSAENOTSOCK, -} - // Repeatedly sends data until the entire buffer is sent. // If a send fails before all data is sent, returns the amount // sent up to that point. @@ -360,36 +224,6 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -UDP_Send_Error :: enum c.int { - Network_Subsystem_Failure = win.WSAENETDOWN, - Aborted = win.WSAECONNABORTED, // TODO: not functionally different from Reset; merge? - // UDP packets are limited in size, and len(buf) exceeded it. - Message_Too_Long = win.WSAEMSGSIZE, - // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Remote_Not_Listening = win.WSAECONNRESET, - Shutdown = win.WSAESHUTDOWN, - // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, - // Connection is broken due to keepalive activity detecting a failure during the operation. - Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? - No_Buffer_Space_Available = win.WSAENOBUFS, - // The socket is not valid socket handle. - Not_Socket = win.WSAENOTSOCK, - // This socket is unidirectional and cannot be used to send any data. - // TODO: verify possible; decide whether to keep if not - Receive_Only = win.WSAEOPNOTSUPP, - Would_Block = win.WSAEWOULDBLOCK, - // The remote host cannot be reached from this host at this time. - Host_Unreachable = win.WSAEHOSTUNREACH, - // Attempt to send to the Any address. - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, - // The address is of an incorrect address family for this socket. - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, - // The network cannot be reached from this host at this time. - Offline = win.WSAENETUNREACH, - Timeout = win.WSAETIMEDOUT, -} // Sends a single UDP datagram packet. // @@ -413,24 +247,6 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: send :: proc{send_tcp, send_udp} - - - -Shutdown_Manner :: enum c.int { - Receive = win.SD_RECEIVE, - Send = win.SD_SEND, - Both = win.SD_BOTH, -} - -Shutdown_Error :: enum c.int { - Aborted = win.WSAECONNABORTED, - Reset = win.WSAECONNRESET, - Offline = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, - Invalid_Manner = win.WSAEINVAL, -} - shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) res := win.shutdown(Platform_Socket(s), c.int(manner)) @@ -440,59 +256,6 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } - - - -Socket_Option :: enum c.int { - // bool: Whether the address that this socket is bound to can be reused by other sockets. - // This allows you to bypass the cooldown period if a program dies while the socket is bound. - Reuse_Address = win.SO_REUSEADDR, - // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. - Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, - // bool: When true, keepalive packets will be automatically be sent for this connection. - // TODO: verify this understanding - Keep_Alive = win.SO_KEEPALIVE, - // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than - // being accepted. - Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, - // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. - Dont_Linger = win.SO_DONTLINGER, - // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, - // the same as normal 'in-band' data. - Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, - // bool: When true, disables send-coalescing, therefore reducing latency. - TCP_Nodelay = win.TCP_NODELAY, - // win.LINGER: Customizes how long (if at all) the socket will remain open when there is some remaining data - // waiting to be sent, and net.close() is called. - Linger = win.SO_LINGER, - // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. - Receive_Buffer_Size = win.SO_RCVBUF, - // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. - Send_Buffer_Size = win.SO_SNDBUF, - // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Receive_Timeout = win.SO_RCVTIMEO, - // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Send_Timeout = win.SO_SNDTIMEO, - // bool: Allow sending to, receiving from, and binding to, a broadcast address. - Broadcast = win.SO_BROADCAST, -} - -Socket_Option_Error :: enum c.int { - Linger_Only_Supports_Whole_Seconds = 1, - // The given value is too big or small to be given to the OS. - Value_Out_Of_Range, - - Network_Subsystem_Failure = win.WSAENETDOWN, - Timeout_When_Keepalive_Set = win.WSAENETRESET, - Invalid_Option_For_Socket = win.WSAENOPROTOOPT, - Reset_When_Keepalive_Set = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, -} - set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP From 5b55fbff23732254dc85f9d1656730c2ff8a34fb Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Thu, 2 Mar 2023 06:47:05 -0800 Subject: [PATCH 10/23] cleanup openbsd errors more --- core/net/errors_openbsd.odin | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/core/net/errors_openbsd.odin b/core/net/errors_openbsd.odin index 51d9cc7e8..781d7837a 100644 --- a/core/net/errors_openbsd.odin +++ b/core/net/errors_openbsd.odin @@ -104,22 +104,17 @@ TCP_Recv_Error :: enum c.int { } UDP_Recv_Error :: enum c.int { - // The buffer is too small to fit the entire message, and the message was truncated. - Truncated = c.int(os.EMSGSIZE), - // The so-called socket is not an open socket. - Not_Socket = c.int(os.ENOTSOCK), - // The so-called socket is, in fact, not even a valid descriptor. - Not_Descriptor = c.int(os.EBADF), - // The buffer did not point to a valid location in memory. - Bad_Buffer = c.int(os.EFAULT), - // A signal occurred before any data was transmitted. See signal(7). - Interrupted = c.int(os.EINTR), + Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. + Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. + Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. + Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. + Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). // The send timeout duration passed before all data was sent. // See Socket_Option.Send_Timeout. // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), - Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. + Timeout = c.int(os.EWOULDBLOCK), + Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. } // TODO From 38d58e818c171761e91ce81a480cca2955fe12bf Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Thu, 2 Mar 2023 06:56:54 -0800 Subject: [PATCH 11/23] ripple bill-suggestions --- core/net/addr.odin | 11 ++++------- core/net/dns.odin | 26 ++++++++++---------------- core/net/url.odin | 2 +- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 77debcf26..52222f904 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -34,11 +34,8 @@ import "core:fmt" If `allow_non_decimal` is true, `aton` is told each component must be decimal and max 255. */ parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) { - res, res_ok := aton(address_and_maybe_port, .IP4, !allow_non_decimal) - if ip4, ip4_ok := res.(IP4_Address); ip4_ok { - return ip4, res_ok - } - return {}, false + res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return + return res.? } /* @@ -432,7 +429,7 @@ parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_End // Returns ok=false if port is not a number. split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) { // IP6 [addr_or_host]:port - if i := strings.last_index(endpoint_str, "]:"); i != -1 { + if i := strings.last_index(endpoint_str, "]:"); i >= 0 { addr_or_host = endpoint_str[1:i] port, ok = strconv.parse_int(endpoint_str[i+2:], 10) @@ -741,4 +738,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D // If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. return value, digit_bytes + prefix_bytes, digit_bytes >= 1 -} \ No newline at end of file +} diff --git a/core/net/dns.odin b/core/net/dns.odin index 2875029de..e2c74d359 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -58,7 +58,7 @@ dns_configuration := DEFAULT_DNS_CONFIGURATION replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) { // Nothing to replace. Return a clone of the original. if strings.count(path, "%") != 2 { - return strings.clone(path), true + return strings.clone(path, allocator), true } left := strings.index(path, "%") + 1 @@ -68,10 +68,10 @@ replace_environment_path :: proc(path: string, allocator := context.allocator) - assert(right > 0 && right <= len(path)) // should be covered by there being two % env_key := path[left: right] - env_val := os.get_env(env_key) + env_val := os.get_env(env_key, allocator) defer delete(env_val) - res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1) + res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator) return res, true } @@ -356,10 +356,7 @@ unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) { load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) { context.allocator = allocator - res, success := os.read_entire_file_from_filename(resolv_conf_path) - if !success { - return - } + res := os.read_entire_file_from_filename(resolv_conf_path) or_return defer delete(res) resolv_str := string(res) @@ -393,10 +390,7 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) { context.allocator = allocator - res, success := os.read_entire_file_from_filename(hosts_file_path, allocator) - if !success { - return - } + res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return defer delete(res) _hosts := make([dynamic]DNS_Host_Entry, 0, allocator) @@ -800,7 +794,7 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator cur_idx := header_size_bytes - for i := 0; i < question_count; i += 1 { + for _ in 0.. (scheme, host, s := url i := strings.last_index(s, "://") - if i != -1 { + if i >= 0 { scheme = s[:i] s = s[i+3:] } From 96ac40595281f5112aea41618901e0b70a324100 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 12:04:36 +0100 Subject: [PATCH 12/23] Alignment + unnecessary allocator param. --- core/net/addr.odin | 2 +- core/net/addr_darwin.odin | 2 +- core/net/dns.odin | 14 +++++++------- core/net/socket_linux.odin | 10 +++++----- core/net/socket_openbsd.odin | 10 +++++----- core/net/socket_windows.odin | 10 +++++----- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 52222f904..caa9b8a13 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -738,4 +738,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D // If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. return value, digit_bytes + prefix_bytes, digit_bytes >= 1 -} +} \ No newline at end of file diff --git a/core/net/addr_darwin.odin b/core/net/addr_darwin.odin index 801b89a96..996540692 100644 --- a/core/net/addr_darwin.odin +++ b/core/net/addr_darwin.odin @@ -68,4 +68,4 @@ sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpo panic("native_addr is neither IP4 or IP6 address") } return -} +} \ No newline at end of file diff --git a/core/net/dns.odin b/core/net/dns.odin index e2c74d359..8eee68136 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -348,7 +348,7 @@ unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) { hdr.is_truncated = (bits & (1 << 9)) != 0 hdr.is_recursion_desired = (bits & (1 << 8)) != 0 hdr.is_recursion_available = (bits & (1 << 7)) != 0 - hdr.response_code = DNS_Response_Code(bits & 0xF) + hdr.response_code = DNS_Response_Code(bits & 0xF) return hdr } @@ -422,7 +422,7 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> ( } // www.google.com -> 3www6google3com0 -encode_hostname :: proc(b: ^strings.Builder, hostname: string, allocator := context.allocator) -> (ok: bool) { +encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) { _hostname := hostname for section in strings.split_iterator(&_hostname, ".") { if len(section) > LABEL_MAX { @@ -437,7 +437,7 @@ encode_hostname :: proc(b: ^strings.Builder, hostname: string, allocator := cont return true } -skip_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (encode_size: int, ok: bool) { +skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) { out_size := 0 cur_idx := start_idx @@ -690,9 +690,9 @@ parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) return } - priority: u16be = mem.slice_data_cast([]u16be, data)[0] - weight: u16be = mem.slice_data_cast([]u16be, data)[1] - port: u16be = mem.slice_data_cast([]u16be, data)[2] + _data := mem.slice_data_cast([]u16be, data) + + priority, weight, port := _data[0], _data[1], _data[2] target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname' @@ -800,7 +800,7 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator } dq_sz :: 4 - hn_sz := skip_hostname(response, cur_idx, context.temp_allocator) or_return + hn_sz := skip_hostname(response, cur_idx) or_return dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz]) cur_idx += hn_sz + dq_sz diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 4b80ef202..167fb8e7e 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -328,12 +328,12 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal .Send_Buffer_Size: // TODO: check for out of range values and return .Value_Out_Of_Range? switch i in value { - case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) - case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) - case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) - case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) + case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) - case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) case: panic("set_option() value must be an integer here", loc) } diff --git a/core/net/socket_openbsd.odin b/core/net/socket_openbsd.odin index f69a62a21..4fac28e4c 100644 --- a/core/net/socket_openbsd.odin +++ b/core/net/socket_openbsd.odin @@ -308,12 +308,12 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal .Send_Buffer_Size: // TODO: check for out of range values and return .Value_Out_Of_Range? switch i in value { - case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) - case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) - case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) - case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) + case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) - case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) case: panic("set_option() value must be an integer here", loc) } diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index ed6342316..3b296977c 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -317,12 +317,12 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal .Receive_Buffer_Size, .Send_Buffer_Size: switch i in value { - case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^) - case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^) - case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^) - case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^) + case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^) case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^) - case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^) + case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^) case: panic("set_option() value must be an integer here", loc) } From d5ea492ef55c802f9c1f1930cd6413e1c366481f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 13:00:43 +0100 Subject: [PATCH 13/23] Make more private. --- core/net/addr.odin | 20 +- core/net/addr_darwin.odin | 71 ------- core/net/addr_linux.odin | 92 --------- core/net/addr_openbsd.odin | 69 ------- core/net/addr_windows.odin | 69 ------- core/net/dns.odin | 36 ++-- core/net/dns_unix.odin | 18 +- core/net/dns_windows.odin | 26 +-- core/net/errors_openbsd.odin | 186 ------------------ core/net/interface_darwin.odin | 14 +- core/net/interface_linux.odin | 24 ++- core/net/socket_darwin.odin | 69 +++++-- core/net/socket_linux.odin | 90 +++++++-- core/net/socket_openbsd.odin | 331 --------------------------------- core/net/socket_windows.odin | 55 +++++- core/net/url.odin | 12 +- 16 files changed, 266 insertions(+), 916 deletions(-) delete mode 100644 core/net/addr_darwin.odin delete mode 100644 core/net/addr_linux.odin delete mode 100644 core/net/addr_openbsd.odin delete mode 100644 core/net/addr_windows.odin delete mode 100644 core/net/errors_openbsd.odin delete mode 100644 core/net/socket_openbsd.odin diff --git a/core/net/addr.odin b/core/net/addr.odin index caa9b8a13..83d4a0ead 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,3 +1,9 @@ +package net +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -7,15 +13,9 @@ List of contributors: Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, IPv4 + IPv6 parsers, documentation. + Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:strconv" import "core:strings" import "core:fmt" @@ -738,4 +738,10 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D // If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. return value, digit_bytes + prefix_bytes, digit_bytes >= 1 +} + +// Returns an address for each interface that can be bound to. +get_network_interfaces :: proc() -> []Address { + // TODO + return nil } \ No newline at end of file diff --git a/core/net/addr_darwin.odin b/core/net/addr_darwin.odin deleted file mode 100644 index 996540692..000000000 --- a/core/net/addr_darwin.odin +++ /dev/null @@ -1,71 +0,0 @@ -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - -import "core:os" - -// Returns an address for each interface that can be bound to. -get_network_interfaces :: proc() -> []Address { - // TODO - return nil -} - -@private -endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { - switch a in ep.address { - case IP4_Address: - (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { - sin_port = u16be(ep.port), - sin_addr = transmute(os.in_addr) a, - sin_family = u8(os.AF_INET), - sin_len = size_of(os.sockaddr_in), - } - return - case IP6_Address: - (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { - sin6_port = u16be(ep.port), - sin6_addr = transmute(os.in6_addr) a, - sin6_family = u8(os.AF_INET6), - sin6_len = size_of(os.sockaddr_in6), - } - return - } - unreachable() -} - -@private -sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { - switch native_addr.family { - case u8(os.AF_INET): - addr := cast(^os.sockaddr_in) native_addr - port := int(addr.sin_port) - ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, - } - case u8(os.AF_INET6): - addr := cast(^os.sockaddr_in6) native_addr - port := int(addr.sin6_port) - ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, - } - case: - panic("native_addr is neither IP4 or IP6 address") - } - return -} \ No newline at end of file diff --git a/core/net/addr_linux.odin b/core/net/addr_linux.odin deleted file mode 100644 index f93b508c4..000000000 --- a/core/net/addr_linux.odin +++ /dev/null @@ -1,92 +0,0 @@ -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - -import "core:os" - -// Returns an address for each interface that can be bound to. -get_network_interfaces :: proc() -> []Address { - // TODO - return nil -} - -@private -endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { - switch a in ep.address { - case IP4_Address: - (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { - sin_port = u16be(ep.port), - sin_addr = transmute(os.in_addr) a, - sin_family = u16(os.AF_INET), - } - return - case IP6_Address: - (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { - sin6_port = u16be(ep.port), - sin6_addr = transmute(os.in6_addr) a, - sin6_family = u16(os.AF_INET6), - } - return - } - unreachable() -} - -sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { - switch native_addr.ss_family { - case u16(os.AF_INET): - addr := cast(^os.sockaddr_in) native_addr - port := int(addr.sin_port) - ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, - } - case u16(os.AF_INET6): - addr := cast(^os.sockaddr_in6) native_addr - port := int(addr.sin6_port) - ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, - } - case: - panic("native_addr is neither IP4 or IP6 address") - } - return -} - -sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) { - switch native_addr.sa_family { - case u16(os.AF_INET): - addr := cast(^os.sockaddr_in) native_addr - port := int(addr.sin_port) - ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, - } - case u16(os.AF_INET6): - addr := cast(^os.sockaddr_in6) native_addr - port := int(addr.sin6_port) - ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, - } - case: - panic("native_addr is neither IP4 or IP6 address") - } - return -} - -sockaddr_to_endpoint :: proc { sockaddr_basic_to_endpoint, sockaddr_storage_to_endpoint } \ No newline at end of file diff --git a/core/net/addr_openbsd.odin b/core/net/addr_openbsd.odin deleted file mode 100644 index 34a49b526..000000000 --- a/core/net/addr_openbsd.odin +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - -import "core:os" - -// Returns an address for each interface that can be bound to. -get_network_interfaces :: proc() -> []Address { - // TODO - return nil -} - -@private -endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { - switch a in ep.address { - case IP4_Address: - (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { - sin_port = u16be(ep.port), - sin_addr = transmute(os.in_addr) a, - sin_family = u8(os.AF_INET), - } - return - case IP6_Address: - (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { - sin6_port = u16be(ep.port), - sin6_addr = transmute(os.in6_addr) a, - sin6_family = u8(os.AF_INET6), - } - return - } - unreachable() -} - -@private -sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { - switch native_addr.ss_family { - case u8(os.AF_INET): - addr := cast(^os.sockaddr_in)native_addr - port := int(addr.sin_port) - ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, - } - case u8(os.AF_INET6): - addr := cast(^os.sockaddr_in6)native_addr - port := int(addr.sin6_port) - ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, - } - case: - panic("native_addr is neither IP4 or IP6 address") - } - return -} \ No newline at end of file diff --git a/core/net/addr_windows.odin b/core/net/addr_windows.odin deleted file mode 100644 index 4225216ca..000000000 --- a/core/net/addr_windows.odin +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - -import win "core:sys/windows" - -// Returns an address for each interface that can be bound to. -get_network_interfaces :: proc() -> []Address { - // TODO - return nil -} - -@private -endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) { - switch a in ep.address { - case IP4_Address: - (^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in { - sin_port = u16be(win.USHORT(ep.port)), - sin_addr = transmute(win.in_addr) a, - sin_family = u16(win.AF_INET), - } - return - case IP6_Address: - (^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 { - sin6_port = u16be(win.USHORT(ep.port)), - sin6_addr = transmute(win.in6_addr) a, - sin6_family = u16(win.AF_INET6), - } - return - } - unreachable() -} - -@private -sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { - switch native_addr.ss_family { - case u16(win.AF_INET): - addr := cast(^win.sockaddr_in) native_addr - port := int(addr.sin_port) - ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, - } - case u16(win.AF_INET6): - addr := cast(^win.sockaddr_in6) native_addr - port := int(addr.sin6_port) - ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, - } - case: - panic("native_addr is neither IP4 or IP6 address") - } - return -} \ No newline at end of file diff --git a/core/net/dns.odin b/core/net/dns.odin index 8eee68136..15e980594 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -113,6 +113,7 @@ resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Ne } unreachable() } + resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) { target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return switch t in target { @@ -139,6 +140,7 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net } unreachable() } + resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) { target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return switch t in target { @@ -166,13 +168,18 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net unreachable() } -// `get_dns_records` uses OS-specific methods to query DNS records. -when ODIN_OS == .Windows { - get_dns_records_from_os :: get_dns_records_windows -} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { - get_dns_records_from_os :: get_dns_records_unix -} else { - #panic("get_dns_records_from_os not implemented on this OS") +/* + Performs a recursive DNS query for records of a particular type for the hostname using the OS. + + NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, + meaning that DNS queries for a hostname will resolve through CNAME records until an + IP address is reached. + + IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! + See `destroy_records`. +*/ +get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + return _get_dns_records_os(hostname, type, allocator) } /* @@ -182,6 +189,9 @@ when ODIN_OS == .Windows { NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, meaning that DNS queries for a hostname will resolve through CNAME records until an IP address is reached. + + IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! + See `destroy_records`. */ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator @@ -771,14 +781,16 @@ parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) */ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) { - header_size_bytes :: 12 - if len(response) < header_size_bytes { + context.allocator = allocator + + HEADER_SIZE_BYTES :: 12 + if len(response) < HEADER_SIZE_BYTES { return } _records := make([dynamic]DNS_Record, 0) - dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:header_size_bytes]) + dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES]) hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1]) if !hdr.is_response { return @@ -792,7 +804,7 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator authority_count := int(dns_hdr_chunks[4]) additional_count := int(dns_hdr_chunks[5]) - cur_idx := header_size_bytes + cur_idx := HEADER_SIZE_BYTES for _ in 0.. Copyright 2022 Colin Davidson @@ -10,16 +16,10 @@ Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation */ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:strings" -get_dns_records_unix :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { +@(private) +_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator if type != .SRV { diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index d445c8816..72d67c54a 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -1,4 +1,11 @@ //+build windows +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,28 +18,13 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:strings" import "core:mem" import win "core:sys/windows" -/* - Performs a recursive DNS query for records of a particular type for the hostname. - - NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, - meaning that DNS queries for a hostname will resolve through CNAME records until an - IP address is reached. - - WARNING: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! - See `destroy_records`. -*/ -get_dns_records_windows :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { +@(private) +_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) diff --git a/core/net/errors_openbsd.odin b/core/net/errors_openbsd.odin deleted file mode 100644 index 781d7837a..000000000 --- a/core/net/errors_openbsd.odin +++ /dev/null @@ -1,186 +0,0 @@ -// +build openbsd -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. - - - IMPORTANT/TODO: This is a carbon copy of `socket_darwin.odin`. Adjust if necessary. - -*/ -package net - -import "core:c" -import "core:os" - -Create_Socket_Error :: enum c.int { - Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available_Available = c.int(os.ENOMEM), - Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), -} - -Dial_Error :: enum c.int { - Port_Required = -1, - Address_In_Use = c.int(os.EADDRINUSE), - In_Progress = c.int(os.EINPROGRESS), - Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), - Refused = c.int(os.ECONNREFUSED), - Is_Listening_Socket = c.int(os.EACCES), - Already_Connected = c.int(os.EISCONN), - Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Timeout = c.int(os.ETIMEDOUT), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(os.EWOULDBLOCK), -} - -Bind_Error :: enum c.int { - Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. - Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket. - Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address. - No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available. -} - -Listen_Error :: enum c.int { - Address_In_Use = c.int(os.EADDRINUSE), - Already_Connected = c.int(os.EISCONN), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - Not_Socket = c.int(os.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), -} - -Accept_Error :: enum c.int { - - // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. - Reset = c.int(os.ECONNRESET), - Not_Listening = c.int(os.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(os.EWOULDBLOCK), -} - -TCP_Recv_Error :: enum c.int { - Shutdown = c.int(os.ESHUTDOWN), - Not_Connected = c.int(os.ENOTCONN), - - // TODO(tetra): Is this error actually possible here? - Connection_Broken = c.int(os.ENETRESET), - Not_Socket = c.int(os.ENOTSOCK), - Aborted = c.int(os.ECONNABORTED), - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), - Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - -UDP_Recv_Error :: enum c.int { - Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. - Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), - Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. -} - -// TODO -TCP_Send_Error :: enum c.int { - - // TODO: merge with other errors? - Aborted = c.int(os.ECONNABORTED), - Connection_Closed = c.int(os.ECONNRESET), - Not_Connected = c.int(os.ENOTCONN), - Shutdown = c.int(os.ESHUTDOWN), - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), -} - -// TODO -UDP_Send_Error :: enum c.int { - Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent. - - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(os.ENETUNREACH), - No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - - // The send timeout duration passed before all data was sent. - // See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), - Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. -} - -Shutdown_Error :: enum c.int { - Aborted = c.int(os.ECONNABORTED), - Reset = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Not_Connected = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), - Invalid_Manner = c.int(os.EINVAL), -} - -Socket_Option_Error :: enum c.int { - Offline = c.int(os.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(os.ENETRESET), - Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), -} diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 9a9bda5d3..a61d63377 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -1,4 +1,11 @@ +package net //+build darwin + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -9,15 +16,8 @@ Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation -*/ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net -/* TODO: Implement. Can probably use the (current) Linux implementation, which will itself be switched over to talking to the kernel via NETLINK protocol once we have raw sockets. */ diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index c5973fa2d..255a96f0b 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -1,4 +1,11 @@ -//+build linux, darwin, openbsd, !windows +package net +//+build linux + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -9,17 +16,8 @@ Tetralux: Initial implementation Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation -*/ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - -/* This file uses `getifaddrs` libc call to enumerate interfaces. - TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc. */ @@ -65,7 +63,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N if ifaddr.address != nil { switch int(ifaddr.address.sa_family) { case os.AF_INET, os.AF_INET6: - address = sockaddr_to_endpoint(ifaddr.address).address + address = _sockaddr_basic_to_endpoint(ifaddr.address).address case os.AF_PACKET: /* @@ -87,7 +85,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N if ifaddr.netmask != nil { switch int(ifaddr.netmask.sa_family) { case os.AF_INET, os.AF_INET6: - netmask = Netmask(sockaddr_to_endpoint(ifaddr.netmask).address) + netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address) case: } } @@ -95,7 +93,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags { switch int(ifaddr.broadcast_or_dest.sa_family) { case os.AF_INET, os.AF_INET6: - broadcast := sockaddr_to_endpoint(ifaddr.broadcast_or_dest).address + broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address append(&iface.multicast, broadcast) case: } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 75c0eaf56..bd9f45ffa 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -1,4 +1,11 @@ +package net // +build darwin + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import "core:os" import "core:time" @@ -68,7 +69,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option // use the same address immediately. _ = set_option(skt, .Reuse_Address, true) - sockaddr := endpoint_to_sockaddr(endpoint) + sockaddr := _endpoint_to_sockaddr(endpoint) res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) if res != os.ERROR_NONE { err = Dial_Error(res) @@ -79,7 +80,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option } bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { - sockaddr := endpoint_to_sockaddr(ep) + sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) if res != os.ERROR_NONE { @@ -145,7 +146,7 @@ accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, e return } client = TCP_Socket(client_sock) - source = sockaddr_to_endpoint(&sockaddr) + source = _sockaddr_to_endpoint(&sockaddr) return } @@ -180,7 +181,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo } bytes_read = int(res) - remote_endpoint = sockaddr_to_endpoint(&from) + remote_endpoint = _sockaddr_to_endpoint(&from) return } @@ -204,7 +205,7 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw } send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { - toaddr := endpoint_to_sockaddr(to) + toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] @@ -324,3 +325,49 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal return nil } + +@private +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(os.in_addr) a, + sin_family = u8(os.AF_INET), + sin_len = size_of(os.sockaddr_in), + } + return + case IP6_Address: + (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(os.in6_addr) a, + sin6_family = u8(os.AF_INET6), + sin6_len = size_of(os.sockaddr_in6), + } + return + } + unreachable() +} + +@private +_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.family { + case u8(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u8(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} \ No newline at end of file diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index 167fb8e7e..cc8113d15 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -1,4 +1,11 @@ +package net // +build linux + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import "core:os" import "core:time" @@ -68,7 +69,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option // use the same address immediately. _ = set_option(skt, .Reuse_Address, true) - sockaddr := endpoint_to_sockaddr(endpoint) + sockaddr := _endpoint_to_sockaddr(endpoint) res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) if res != os.ERROR_NONE { err = Dial_Error(res) @@ -84,7 +85,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { - sockaddr := endpoint_to_sockaddr(ep) + sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) if res != os.ERROR_NONE { @@ -152,7 +153,7 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: return } client = TCP_Socket(client_sock) - source = sockaddr_to_endpoint(&sockaddr) + source = _sockaddr_storage_to_endpoint(&sockaddr) if options.no_delay { _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored } @@ -197,7 +198,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo } bytes_read = int(res) - remote_endpoint = sockaddr_to_endpoint(&from) + remote_endpoint = _sockaddr_storage_to_endpoint(&from) if bytes_read > len(buf) { // NOTE(tetra): The buffer has been filled, with a partial message. @@ -233,7 +234,7 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw // Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. // UDP packets are not guarenteed to be received in order. send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { - toaddr := endpoint_to_sockaddr(to) + toaddr := _endpoint_to_sockaddr(to) res, os_err := os.sendto(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr)) if os_err != os.ERROR_NONE { err = UDP_Send_Error(os_err) @@ -349,3 +350,70 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal return nil } + +@(private) +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + sin_port = u16be(ep.port), + sin_addr = transmute(os.in_addr) a, + sin_family = u16(os.AF_INET), + } + return + case IP6_Address: + (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + sin6_port = u16be(ep.port), + sin6_addr = transmute(os.in6_addr) a, + sin6_family = u16(os.AF_INET6), + } + return + } + unreachable() +} + +@(private) +_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u16(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} + +@(private) +_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) { + switch native_addr.sa_family { + case u16(os.AF_INET): + addr := cast(^os.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(os.AF_INET6): + addr := cast(^os.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} \ No newline at end of file diff --git a/core/net/socket_openbsd.odin b/core/net/socket_openbsd.odin deleted file mode 100644 index 4fac28e4c..000000000 --- a/core/net/socket_openbsd.odin +++ /dev/null @@ -1,331 +0,0 @@ -// +build openbsd -/* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . - Made available under Odin's BSD-3 license. - - List of contributors: - Tetralux: Initial implementation - Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver - Jeroen van Rijn: Cross platform unification, code style, documentation -*/ - -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. - - - IMPORTANT/TODO: This is a carbon copy of `socket_darwin.odin`. Adjust if necessary. - -*/ -package net - -import "core:c" -import "core:os" -import "core:time" - -Platform_Socket :: os.Socket - -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { - c_type, c_protocol, c_family: int - - switch family { - case .IP4: c_family = os.AF_INET - case .IP6: c_family = os.AF_INET6 - case: - unreachable() - } - - switch protocol { - case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP - case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP - case: - unreachable() - } - - sock, ok := os.socket(c_family, c_type, c_protocol) - if ok != os.ERROR_NONE { - err = Create_Socket_Error(ok) - return - } - - switch protocol { - case .TCP: return TCP_Socket(sock), nil - case .UDP: return UDP_Socket(sock), nil - case: - unreachable() - } -} - -dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { - if endpoint.port == 0 { - return 0, .Port_Required - } - - family := family_from_endpoint(endpoint) - sock := create_socket(family, .TCP) or_return - skt = sock.(TCP_Socket) - - // NOTE(tetra): This is so that if we crash while the socket is open, we can - // bypass the cooldown period, and allow the next run of the program to - // use the same address immediately. - _ = set_option(skt, .Reuse_Address, true) - - sockaddr := endpoint_to_sockaddr(endpoint) - res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) - if res != os.ERROR_NONE { - err = Dial_Error(res) - return - } - - return -} - -bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { - sockaddr := endpoint_to_sockaddr(ep) - s := any_socket_to_socket(skt) - res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) - if res != os.ERROR_NONE { - err = Bind_Error(res) - } - return -} - - -// This type of socket becomes bound when you try to send data. -// This is likely what you want if you want to send data unsolicited. -// -// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. -make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { - sock := create_socket(family, .UDP) or_return - skt = sock.(UDP_Socket) - return -} - -// This type of socket is bound immediately, which enables it to receive data on the port. -// Since it's UDP, it's also able to send data without receiving any first. -// -// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. -// -// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. -make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { - skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return - bind(skt, {bound_address, port}) or_return - return -} - -listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { - assert(backlog > 0 && i32(backlog) < max(i32)) - - family := family_from_endpoint(interface_endpoint) - sock := create_socket(family, .TCP) or_return - skt = sock.(TCP_Socket) - - // NOTE(tetra): This is so that if we crash while the socket is open, we can - // bypass the cooldown period, and allow the next run of the program to - // use the same address immediately. - // - // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! - set_option(sock, .Reuse_Address, true) or_return - - bind(sock, interface_endpoint) or_return - - res := os.listen(Platform_Socket(skt), backlog) - if res != os.ERROR_NONE { - err = Listen_Error(res) - return - } - - return -} - -accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { - sockaddr: os.SOCKADDR_STORAGE_LH - sockaddrlen := c.int(size_of(sockaddr)) - - client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) - if ok != os.ERROR_NONE { - err = Accept_Error(ok) - return - } - client = TCP_Socket(client_sock) - source = sockaddr_to_endpoint(&sockaddr) - return -} - -close :: proc(skt: Any_Socket) { - s := any_socket_to_socket(skt) - os.close(os.Handle(Platform_Socket(s))) -} - -recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { - if len(buf) <= 0 { - return - } - res, ok := os.recv(Platform_Socket(skt), buf, 0) - if ok != os.ERROR_NONE { - err = TCP_Recv_Error(ok) - return - } - return int(res), nil -} - -recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { - if len(buf) <= 0 { - return - } - - from: os.SOCKADDR_STORAGE_LH - fromsize := c.int(size_of(from)) - res, ok := os.recvfrom(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) - if ok != os.ERROR_NONE { - err = UDP_Recv_Error(ok) - return - } - - bytes_read = int(res) - remote_endpoint = sockaddr_to_endpoint(&from) - return -} - -recv :: proc{recv_tcp, recv_udp} - -// Repeatedly sends data until the entire buffer is sent. -// If a send fails before all data is sent, returns the amount -// sent up to that point. -send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { - for bytes_written < len(buf) { - limit := min(int(max(i32)), len(buf) - bytes_written) - remaining := buf[bytes_written:][:limit] - res, ok := os.send(Platform_Socket(skt), remaining, 0) - if ok != os.ERROR_NONE { - err = TCP_Send_Error(ok) - return - } - bytes_written += int(res) - } - return -} - -send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { - toaddr := endpoint_to_sockaddr(to) - for bytes_written < len(buf) { - limit := min(1<<31, len(buf) - bytes_written) - remaining := buf[bytes_written:][:limit] - res, ok := os.sendto(Platform_Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, size_of(toaddr)) - if ok != os.ERROR_NONE { - err = UDP_Send_Error(ok) - return - } - bytes_written += int(res) - } - return -} - -send :: proc{send_tcp, send_udp} - -Shutdown_Manner :: enum c.int { - Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { - s := any_socket_to_socket(skt) - res := os.shutdown(Platform_Socket(s), int(manner)) - if res != os.ERROR_NONE { - return Shutdown_Error(res) - } - return -} - -Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - Linger = c.int(os.SO_LINGER), - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO), - Send_Timeout = c.int(os.SO_SNDTIMEO), -} - -set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { - level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP - - // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; - // it _has_ to be a b32. - // I haven't tested if you can give more than that. - bool_value: b32 - int_value: i32 - timeval_value: os.Timeval - - ptr: rawptr - len: os.socklen_t - - switch option { - case - .Reuse_Address, - .Keep_Alive, - .Out_Of_Bounds_Data_Inline, - .TCP_Nodelay: - // TODO: verify whether these are options or not on Linux - // .Broadcast, - // .Conditional_Accept, - // .Dont_Linger: - switch x in value { - case bool, b8: - x2 := x - bool_value = b32((^bool)(&x2)^) - case b16: - bool_value = b32(x) - case b32: - bool_value = b32(x) - case b64: - bool_value = b32(x) - case: - panic("set_option() value must be a boolean here", loc) - } - ptr = &bool_value - len = size_of(bool_value) - case - .Linger, - .Send_Timeout, - .Receive_Timeout: - t, ok := value.(time.Duration) - if !ok do panic("set_option() value must be a time.Duration here", loc) - - nanos := time.duration_nanoseconds(t) - timeval_value.nanoseconds = int(nanos % 1e9) - timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9 - - ptr = &timeval_value - len = size_of(timeval_value) - case - .Receive_Buffer_Size, - .Send_Buffer_Size: - // TODO: check for out of range values and return .Value_Out_Of_Range? - switch i in value { - case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) - case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) - case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) - case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) - case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) - case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) - case: - panic("set_option() value must be an integer here", loc) - } - ptr = &int_value - len = size_of(int_value) - } - - skt := any_socket_to_socket(s) - res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) - if res != os.ERROR_NONE { - return Socket_Option_Error(res) - } - - return nil -} diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 3b296977c..74d80a97d 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -74,7 +74,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option // use the same address immediately. _ = set_option(skt, .Reuse_Address, true) - sockaddr := endpoint_to_sockaddr(endpoint) + sockaddr := _endpoint_to_sockaddr(endpoint) res := win.connect(Platform_Socket(skt), &sockaddr, size_of(sockaddr)) if res < 0 { err = Dial_Error(win.WSAGetLastError()) @@ -89,7 +89,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option } bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { - sockaddr := endpoint_to_sockaddr(ep) + sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) if res < 0 { @@ -161,7 +161,7 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: return } client = TCP_Socket(client_sock) - source = sockaddr_to_endpoint(&sockaddr) + source = _sockaddr_to_endpoint(&sockaddr) if options.no_delay { _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored } @@ -201,7 +201,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo } bytes_read = int(res) - remote_endpoint = sockaddr_to_endpoint(&from) + remote_endpoint = _sockaddr_to_endpoint(&from) return } @@ -235,7 +235,7 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: err = .Message_Too_Long return } - toaddr := endpoint_to_sockaddr(to) + toaddr := _endpoint_to_sockaddr(to) res := win.sendto(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) if res < 0 { err = UDP_Send_Error(win.WSAGetLastError()) @@ -338,3 +338,48 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal return nil } + + +@(private) +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) { + switch a in ep.address { + case IP4_Address: + (^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in { + sin_port = u16be(win.USHORT(ep.port)), + sin_addr = transmute(win.in_addr) a, + sin_family = u16(win.AF_INET), + } + return + case IP6_Address: + (^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 { + sin6_port = u16be(win.USHORT(ep.port)), + sin6_addr = transmute(win.in6_addr) a, + sin6_family = u16(win.AF_INET6), + } + return + } + unreachable() +} + +@(private) +_sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { + switch native_addr.ss_family { + case u16(win.AF_INET): + addr := cast(^win.sockaddr_in) native_addr + port := int(addr.sin_port) + ep = Endpoint { + address = IP4_Address(transmute([4]byte) addr.sin_addr), + port = port, + } + case u16(win.AF_INET6): + addr := cast(^win.sockaddr_in6) native_addr + port := int(addr.sin6_port) + ep = Endpoint { + address = IP6_Address(transmute([8]u16be) addr.sin6_addr), + port = port, + } + case: + panic("native_addr is neither IP4 or IP6 address") + } + return +} \ No newline at end of file diff --git a/core/net/url.odin b/core/net/url.odin index c1d46033f..a5e529928 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -1,3 +1,9 @@ +package net +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -10,12 +16,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:strings" import "core:strconv" import "core:unicode/utf8" From 5267a864db5717d61465d6bb73ee8005f3568c9f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 14:15:15 +0100 Subject: [PATCH 14/23] Coalesce more. --- core/net/common.odin | 18 +++++++++--------- core/net/errors_darwin.odin | 15 ++++++++------- core/net/errors_linux.odin | 13 +++++++------ core/net/errors_windows.odin | 13 +++++++------ core/net/interface.odin | 9 +++++++++ core/net/interface_darwin.odin | 15 ++++++++++++--- core/net/interface_linux.odin | 11 +++-------- core/net/interface_windows.odin | 26 +++++++++++--------------- 8 files changed, 66 insertions(+), 54 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 6be377aad..ce8a6a721 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,3 +1,12 @@ +package net +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. + + This file collects structs, enums and settings applicable to the entire package in one handy place. + Platform-specific ones can be found in their respective `*_windows.odin` and similar files. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -10,15 +19,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. - - This file collects structs, enums and settings applicable to the entire package in one handy place. - Platform-specific ones can be found in their respective `*_windows.odin` and similar files. -*/ -package net - import "core:runtime" /* diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index 3ee28bc9b..8e256a9de 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -1,4 +1,11 @@ +package net // +build darwin + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import "core:os" @@ -179,4 +180,4 @@ Socket_Option_Error :: enum c.int { Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), Reset_When_Keepalive_Set = c.int(os.ENOTCONN), Not_Socket = c.int(os.ENOTSOCK), -} +} \ No newline at end of file diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 16a5411ea..fd2ccab03 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -1,4 +1,11 @@ +package net // +build linux + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import "core:os" diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index 9f9589f29..c45ebbabf 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -1,4 +1,11 @@ +package net // +build windows + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import win "core:sys/windows" diff --git a/core/net/interface.odin b/core/net/interface.odin index 5e6d09830..68eb73aa6 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -18,6 +18,15 @@ package net import "core:strings" +MAX_INTERFACE_ENUMERATION_TRIES :: 3 + +/* + `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. +*/ +enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + return _enumerate_interfaces(allocator) +} + /* `destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`. */ diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index a61d63377..90d996a4a 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -17,7 +17,16 @@ package net Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Jeroen van Rijn: Cross platform unification, code style, documentation - - TODO: Implement. Can probably use the (current) Linux implementation, - which will itself be switched over to talking to the kernel via NETLINK protocol once we have raw sockets. */ + +@(private) +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { + context.allocator = allocator + + + // TODO: Implement. Can probably use the (current) Linux implementation, + // which will itself be switched over to talking to the kernel via NETLINK protocol + // once we have raw sockets. + + unimplemented() +} \ No newline at end of file diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index 255a96f0b..e889cfa97 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -24,10 +24,8 @@ package net import "core:os" import "core:strings" -/* - `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. -*/ -enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +@(private) +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { context.allocator = allocator head: ^os.ifaddrs @@ -40,7 +38,6 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct. We're going to have to iterate over a list and coalesce information as we go. */ - ifaces: map[string]^Network_Interface defer delete(ifaces) @@ -67,7 +64,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N case os.AF_PACKET: /* - For some obscure reason the 64-bit `getifaddrs` calls returns a pointer to a + For some obscure reason the 64-bit `getifaddrs` call returns a pointer to a 32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count is truncated beyond usefulness. @@ -123,7 +120,6 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N if .LOOPBACK in ifaddr.flags { state |= {.Loopback} } - iface.link.state = state } @@ -140,6 +136,5 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N append(&_interfaces, iface^) free(iface) } - return _interfaces[:], {} } \ No newline at end of file diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index c4c0ad1bc..f8bac253a 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -1,4 +1,11 @@ +package net //+build windows + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,28 +18,17 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import sys "core:sys/windows" import strings "core:strings" -MAX_INTERFACE_ENUMERATION_TRIES :: 3 - -/* - `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. -*/ -enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { context.allocator = allocator buf: []u8 defer delete(buf) - buf_size: u32 - res: u32 + buf_size: u32 + res: u32 gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES { res = sys.get_adapters_addresses( @@ -178,4 +174,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) { case: return // Empty or invalid address type } unreachable() -} +} \ No newline at end of file From 798932523e5bc63e473a6a768a47cff6e920cdff Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 15:21:40 +0100 Subject: [PATCH 15/23] Coalesce socket_windows --- core/net/socket.odin | 110 +++++++++++++++++++++++++++++++---- core/net/socket_darwin.odin | 22 ------- core/net/socket_linux.odin | 24 -------- core/net/socket_windows.odin | 92 ++++++++++------------------- 4 files changed, 132 insertions(+), 116 deletions(-) diff --git a/core/net/socket.odin b/core/net/socket.odin index 96985d173..1b73fcf53 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,7 +1,7 @@ /* - Copyright 2022 Tetralux - Copyright 2022 Colin Davidson - Copyright 2022 Jeroen van Rijn . + Copyright 2022-2023 Tetralux + Copyright 2022-2023 Colin Davidson + Copyright 2022-2023 Jeroen van Rijn . Made available under Odin's BSD-3 license. List of contributors: @@ -16,12 +16,12 @@ */ package net -// TODO(tetra): Bluetooth, Raw -any_socket_to_socket :: proc(any_socket: Any_Socket) -> Socket { - switch s in any_socket { +any_socket_to_socket :: proc(socket: Any_Socket) -> Socket { + switch s in socket { case TCP_Socket: return Socket(s) case UDP_Socket: return Socket(s) case: + // TODO(tetra): Bluetooth, Raw return Socket({}) } } @@ -32,7 +32,7 @@ any_socket_to_socket :: proc(any_socket: Any_Socket) -> Socket { Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`. */ -dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname_and_port) or_return switch t in target { case Endpoint: @@ -55,7 +55,7 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param. */ -dial_tcp_from_hostname_string_and_explicit_port :: proc(hostname: string, port: int, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname) or_return switch t in target { case Endpoint: @@ -72,13 +72,103 @@ dial_tcp_from_hostname_string_and_explicit_port :: proc(hostname: string, port: unreachable() } -dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +// Dial from an Address +dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { return dial_tcp_from_endpoint({address, port}, options) } +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { + return _dial_tcp_from_endpoint(endpoint, options) +} + dial_tcp :: proc{ dial_tcp_from_endpoint, dial_tcp_from_address_and_port, dial_tcp_from_hostname_and_port_string, - dial_tcp_from_hostname_string_and_explicit_port, + dial_tcp_from_hostname_with_port_override, +} + +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { + return _create_socket(family, protocol) +} + +bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { + return _bind(socket, ep) +} + +/* + This type of socket becomes bound when you try to send data. + It is likely what you want if you want to send data unsolicited. + + This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. +*/ +make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) { + sock := create_socket(family, .UDP) or_return + socket = sock.(UDP_Socket) + return +} + +/* + This type of socket is bound immediately, which enables it to receive data on the port. + Since it's UDP, it's also able to send data without receiving any first. + + This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. + The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use. +*/ +make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) { + socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return + bind(socket, {bound_address, port}) or_return + return +} + +listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { + assert(backlog > 0 && backlog < int(max(i32))) + + return _listen_tcp(interface_endpoint, backlog) +} + +accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { + return _accept_tcp(socket, options) +} + +close :: proc(socket: Any_Socket) { + _close(socket) +} + +recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { + return _recv_tcp(socket, buf) +} + +recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { + return _recv_udp(socket, buf) +} + +recv :: proc{recv_tcp, recv_udp} + +/* + Repeatedly sends data until the entire buffer is sent. + If a send fails before all data is sent, returns the amount sent up to that point. +*/ +send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { + return _send_tcp(socket, buf) +} + +/* + Sends a single UDP datagram packet. + + Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. + UDP packets are not guarenteed to be received in order. +*/ +send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { + return _send_udp(socket, buf, to) +} + +send :: proc{send_tcp, send_udp} + +shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + return _shutdown(socket, manner) +} + +set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { + return _set_option(socket, option, value, loc) } \ No newline at end of file diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index bd9f45ffa..cca701e07 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -89,28 +89,6 @@ bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { return } -// This type of socket becomes bound when you try to send data. -// This is likely what you want if you want to send data unsolicited. -// -// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. -make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { - sock := create_socket(family, .UDP) or_return - skt = sock.(UDP_Socket) - return -} - -// This type of socket is bound immediately, which enables it to receive data on the port. -// Since it's UDP, it's also able to send data without receiving any first. -// -// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. -// -// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. -make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { - skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return - bind(skt, {bound_address, port}) or_return - return -} - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index cc8113d15..dbe8b1a19 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -94,30 +94,6 @@ bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { return } - -// This type of socket becomes bound when you try to send data. -// This is likely what you want if you want to send data unsolicited. -// -// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. -make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { - sock := create_socket(family, .UDP) or_return - skt = sock.(UDP_Socket) - return -} - -// This type of socket is bound immediately, which enables it to receive data on the port. -// Since it's UDP, it's also able to send data without receiving any first. -// -// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. -// -// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. -make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { - skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return - bind(skt, {bound_address, port}) or_return - return -} - - listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 74d80a97d..488d835c5 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -1,4 +1,11 @@ +package net // +build windows + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -11,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:c" import win "core:sys/windows" import "core:time" @@ -28,7 +29,8 @@ ensure_winsock_initialized :: proc() { win.ensure_winsock_initialized() } -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +@(private) +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: c.int switch family { @@ -59,7 +61,8 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } -dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +@(private) +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { err = .Port_Required return @@ -88,7 +91,8 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option return } -bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +@(private) +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) @@ -98,32 +102,8 @@ bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { return } - -// This type of socket becomes bound when you try to send data. -// This is likely what you want if you want to send data unsolicited. -// -// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. -make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) { - sock := create_socket(family, .UDP) or_return - skt = sock.(UDP_Socket) - return -} - -// This type of socket is bound immediately, which enables it to receive data on the port. -// Since it's UDP, it's also able to send data without receiving any first. -// -// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. -// -// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use. -make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) { - skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return - bind(skt, {bound_address, port}) or_return - return -} - -listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { - assert(backlog > 0 && i32(backlog) < max(i32)) - +@(private) +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return skt = sock.(TCP_Socket) @@ -134,16 +114,14 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S bind(sock, interface_endpoint) or_return - res := win.listen(Platform_Socket(skt), i32(backlog)) - if res == win.SOCKET_ERROR { + if res := win.listen(Platform_Socket(skt), i32(backlog)); res == win.SOCKET_ERROR { err = Listen_Error(win.WSAGetLastError()) - return } - return } -accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { for { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -169,13 +147,15 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: } } -close :: proc(skt: Any_Socket) { +@(private) +_close :: proc(skt: Any_Socket) { if s := any_socket_to_socket(skt); s != {} { win.closesocket(Platform_Socket(s)) } } -recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +@(private) +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return } @@ -187,7 +167,8 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +@(private) +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return } @@ -205,12 +186,8 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo return } -recv :: proc{recv_tcp, recv_udp} - -// Repeatedly sends data until the entire buffer is sent. -// If a send fails before all data is sent, returns the amount -// sent up to that point. -send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:] @@ -224,12 +201,8 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } - -// Sends a single UDP datagram packet. -// -// Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. -// UDP packets are not guarenteed to be received in order. -send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { if len(buf) > int(max(c.int)) { // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading. err = .Message_Too_Long @@ -245,9 +218,8 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: return } -send :: proc{send_tcp, send_udp} - -shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +@(private) +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) res := win.shutdown(Platform_Socket(s), c.int(manner)) if res < 0 { @@ -256,7 +228,8 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } -set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +@(private) +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP bool_value: b32 @@ -339,7 +312,6 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal return nil } - @(private) _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) { switch a in ep.address { From 5da5ebff130647e1d64130e0577a344dea45b2e5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 17:12:21 +0100 Subject: [PATCH 16/23] More coalescing. --- core/net/errors_darwin.odin | 6 ++ core/net/errors_linux.odin | 8 ++- core/net/socket_darwin.odin | 34 ++++------- core/net/socket_linux.odin | 107 ++++++++++++++++------------------- core/net/socket_windows.odin | 48 ++++++++-------- 5 files changed, 97 insertions(+), 106 deletions(-) diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index 8e256a9de..2196933d1 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -165,6 +165,12 @@ UDP_Send_Error :: enum c.int { No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. } +Shutdown_Manner :: enum c.int { + Receive = c.int(os.SHUT_RD), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), +} + Shutdown_Error :: enum c.int { Aborted = c.int(os.ECONNABORTED), Reset = c.int(os.ECONNRESET), diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index fd2ccab03..1552339d8 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -158,6 +158,12 @@ UDP_Send_Error :: enum c.int { No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. } +Shutdown_Manner :: enum c.int { + Receive = c.int(os.SHUT_RD), + Send = c.int(os.SHUT_WR), + Both = c.int(os.SHUT_RDWR), +} + Shutdown_Error :: enum c.int { Aborted = c.int(os.ECONNABORTED), Reset = c.int(os.ECONNRESET), @@ -173,4 +179,4 @@ Socket_Option_Error :: enum c.int { Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), Reset_When_Keepalive_Set = c.int(os.ENOTCONN), Not_Socket = c.int(os.ENOTSOCK), -} +} \ No newline at end of file diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index cca701e07..624fe2393 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -24,6 +24,18 @@ import "core:time" Platform_Socket :: os.Socket +Socket_Option :: enum c.int { + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), + TCP_Nodelay = c.int(os.TCP_NODELAY), + Linger = c.int(os.SO_LINGER), + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO), + Send_Timeout = c.int(os.SO_SNDTIMEO), +} + create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int @@ -163,8 +175,6 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo return } -recv :: proc{recv_tcp, recv_udp} - // Repeatedly sends data until the entire buffer is sent. // If a send fails before all data is sent, returns the amount // sent up to that point. @@ -197,14 +207,6 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: return } -send :: proc{send_tcp, send_udp} - -Shutdown_Manner :: enum c.int { - Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) res := os.shutdown(Platform_Socket(s), int(manner)) @@ -214,18 +216,6 @@ shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Erro return } -Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - Linger = c.int(os.SO_LINGER), - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO), - Send_Timeout = c.int(os.SO_SNDTIMEO), -} - set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index dbe8b1a19..81419f6c9 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -22,9 +22,20 @@ import "core:c" import "core:os" import "core:time" -Platform_Socket :: os.Socket +Socket_Option :: enum c.int { + Reuse_Address = c.int(os.SO_REUSEADDR), + Keep_Alive = c.int(os.SO_KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), + TCP_Nodelay = c.int(os.TCP_NODELAY), + Linger = c.int(os.SO_LINGER), + Receive_Buffer_Size = c.int(os.SO_RCVBUF), + Send_Buffer_Size = c.int(os.SO_SNDBUF), + Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW), + Send_Timeout = c.int(os.SO_SNDTIMEO_NEW), +} -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +@(private) +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int switch family { @@ -55,7 +66,8 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } -dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +@(private) +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required } @@ -70,31 +82,32 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option _ = set_option(skt, .Reuse_Address, true) sockaddr := _endpoint_to_sockaddr(endpoint) - res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) if res != os.ERROR_NONE { err = Dial_Error(res) return } if options.no_delay { - _ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + _ = _set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored } return } - -bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +@(private) +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) - res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) + res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) if res != os.ERROR_NONE { err = Bind_Error(res) } return } -listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { +@(private) +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) family := family_from_endpoint(interface_endpoint) @@ -110,7 +123,7 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S bind(sock, interface_endpoint) or_return - res := os.listen(Platform_Socket(skt), backlog) + res := os.listen(os.Socket(skt), backlog) if res != os.ERROR_NONE { err = Listen_Error(res) return @@ -119,11 +132,12 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } -accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) - client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) if ok != os.ERROR_NONE { err = Accept_Error(ok) return @@ -131,23 +145,23 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: client = TCP_Socket(client_sock) source = _sockaddr_storage_to_endpoint(&sockaddr) if options.no_delay { - _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored + _ = _set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored } return } - - -close :: proc(skt: Any_Socket) { +@(private) +_close :: proc(skt: Any_Socket) { s := any_socket_to_socket(skt) - os.close(os.Handle(Platform_Socket(s))) + os.close(os.Handle(os.Socket(s))) } -recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +@(private) +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return } - res, ok := os.recv(Platform_Socket(skt), buf, 0) + res, ok := os.recv(os.Socket(skt), buf, 0) if ok != os.ERROR_NONE { err = TCP_Recv_Error(ok) return @@ -155,7 +169,8 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +@(private) +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return } @@ -167,7 +182,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo // and no error is returned. // However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read. // We can use this fact to detect this condition and return .Buffer_Too_Small. - res, ok := os.recvfrom(Platform_Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize) + res, ok := os.recvfrom(os.Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize) if ok != os.ERROR_NONE { err = UDP_Recv_Error(ok) return @@ -185,17 +200,12 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo return } -recv :: proc{recv_tcp, recv_udp} - - -// Repeatedly sends data until the entire buffer is sent. -// If a send fails before all data is sent, returns the amount -// sent up to that point. -send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.send(Platform_Socket(skt), remaining, 0) + res, ok := os.send(os.Socket(skt), remaining, 0) if ok != os.ERROR_NONE { err = TCP_Send_Error(ok) return @@ -205,13 +215,10 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -// Sends a single UDP datagram packet. -// -// Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. -// UDP packets are not guarenteed to be received in order. -send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { toaddr := _endpoint_to_sockaddr(to) - res, os_err := os.sendto(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr)) + res, os_err := os.sendto(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr)) if os_err != os.ERROR_NONE { err = UDP_Send_Error(os_err) return @@ -220,36 +227,18 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: return } -send :: proc{send_tcp, send_udp} - -Shutdown_Manner :: enum c.int { - Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +@(private) +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) - res := os.shutdown(Platform_Socket(s), int(manner)) + res := os.shutdown(os.Socket(s), int(manner)) if res != os.ERROR_NONE { return Shutdown_Error(res) } return } -Socket_Option :: enum c.int { - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - Linger = c.int(os.SO_LINGER), - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW), - Send_Timeout = c.int(os.SO_SNDTIMEO_NEW), -} - -set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +@(private) +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; @@ -319,7 +308,7 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal } skt := any_socket_to_socket(s) - res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) + res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len) if res != os.ERROR_NONE { return Socket_Option_Error(res) } diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index 488d835c5..a6ab0b3fa 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -62,7 +62,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { err = .Port_Required return @@ -70,15 +70,15 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio family := family_from_endpoint(endpoint) sock := create_socket(family, .TCP) or_return - skt = sock.(TCP_Socket) + socket = sock.(TCP_Socket) // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to // use the same address immediately. - _ = set_option(skt, .Reuse_Address, true) + _ = set_option(socket, .Reuse_Address, true) sockaddr := _endpoint_to_sockaddr(endpoint) - res := win.connect(Platform_Socket(skt), &sockaddr, size_of(sockaddr)) + res := win.connect(Platform_Socket(socket), &sockaddr, size_of(sockaddr)) if res < 0 { err = Dial_Error(win.WSAGetLastError()) return @@ -92,9 +92,9 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } @(private) -_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) - s := any_socket_to_socket(skt) + s := any_socket_to_socket(socket) res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) if res < 0 { err = Bind_Error(win.WSAGetLastError()) @@ -103,18 +103,18 @@ _bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { } @(private) -_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { family := family_from_endpoint(interface_endpoint) sock := create_socket(family, .TCP) or_return - skt = sock.(TCP_Socket) + socket = sock.(TCP_Socket) // NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will // prevent hijacking of the server's endpoint by other applications. - set_option(skt, .Exclusive_Addr_Use, true) or_return + set_option(socket, .Exclusive_Addr_Use, true) or_return bind(sock, interface_endpoint) or_return - if res := win.listen(Platform_Socket(skt), i32(backlog)); res == win.SOCKET_ERROR { + if res := win.listen(Platform_Socket(socket), i32(backlog)); res == win.SOCKET_ERROR { err = Listen_Error(win.WSAGetLastError()) } return @@ -148,18 +148,18 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client } @(private) -_close :: proc(skt: Any_Socket) { - if s := any_socket_to_socket(skt); s != {} { +_close :: proc(socket: Any_Socket) { + if s := any_socket_to_socket(socket); s != {} { win.closesocket(Platform_Socket(s)) } } @(private) -_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return } - res := win.recv(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0) + res := win.recv(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0) if res < 0 { err = TCP_Recv_Error(win.WSAGetLastError()) return @@ -168,14 +168,14 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ } @(private) -_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return } from: win.SOCKADDR_STORAGE_LH fromsize := c.int(size_of(from)) - res := win.recvfrom(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) + res := win.recvfrom(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) if res < 0 { err = UDP_Recv_Error(win.WSAGetLastError()) return @@ -187,11 +187,11 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp } @(private) -_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:] - res := win.send(Platform_Socket(skt), raw_data(remaining), c.int(limit), 0) + res := win.send(Platform_Socket(socket), raw_data(remaining), c.int(limit), 0) if res < 0 { err = TCP_Send_Error(win.WSAGetLastError()) return @@ -202,14 +202,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net } @(private) -_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { if len(buf) > int(max(c.int)) { // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading. err = .Message_Too_Long return } toaddr := _endpoint_to_sockaddr(to) - res := win.sendto(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) + res := win.sendto(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) if res < 0 { err = UDP_Send_Error(win.WSAGetLastError()) return @@ -219,8 +219,8 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: } @(private) -_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { - s := any_socket_to_socket(skt) +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { + s := any_socket_to_socket(socket) res := win.shutdown(Platform_Socket(s), c.int(manner)) if res < 0 { return Shutdown_Error(win.WSAGetLastError()) @@ -303,8 +303,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca len = size_of(int_value) } - skt := any_socket_to_socket(s) - res := win.setsockopt(Platform_Socket(skt), c.int(level), c.int(option), ptr, len) + socket := any_socket_to_socket(s) + res := win.setsockopt(Platform_Socket(socket), c.int(level), c.int(option), ptr, len) if res < 0 { return Socket_Option_Error(win.WSAGetLastError()) } From 5c05038af0e005600451f195351f2178d352debc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 17:26:44 +0100 Subject: [PATCH 17/23] Finish cleaning up core_net. --- core/net/socket_darwin.odin | 63 +++++++++++++++++-------------- core/net/socket_windows.odin | 26 ++++++------- tests/core/net/test_core_net.odin | 4 +- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 624fe2393..a0a5af6ca 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -22,8 +22,6 @@ import "core:c" import "core:os" import "core:time" -Platform_Socket :: os.Socket - Socket_Option :: enum c.int { Reuse_Address = c.int(os.SO_REUSEADDR), Keep_Alive = c.int(os.SO_KEEPALIVE), @@ -36,7 +34,8 @@ Socket_Option :: enum c.int { Send_Timeout = c.int(os.SO_SNDTIMEO), } -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +@(private) +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { c_type, c_protocol, c_family: int switch family { @@ -67,7 +66,8 @@ create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (soc } } -dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +@(private) +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required } @@ -82,7 +82,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option _ = set_option(skt, .Reuse_Address, true) sockaddr := _endpoint_to_sockaddr(endpoint) - res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) + res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) if res != os.ERROR_NONE { err = Dial_Error(res) return @@ -91,17 +91,19 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option return } -bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +@(private) +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) - res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) + res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) if res != os.ERROR_NONE { err = Bind_Error(res) } return } -listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { +@(private) +_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) { assert(backlog > 0 && i32(backlog) < max(i32)) family := family_from_endpoint(interface_endpoint) @@ -117,7 +119,7 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S bind(sock, interface_endpoint) or_return - res := os.listen(Platform_Socket(skt), backlog) + res := os.listen(os.Socket(skt), backlog) if res != os.ERROR_NONE { err = Listen_Error(res) return @@ -126,11 +128,12 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_S return } -accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { sockaddr: os.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) - client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) + client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) if ok != os.ERROR_NONE { err = Accept_Error(ok) return @@ -140,16 +143,18 @@ accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, e return } -close :: proc(skt: Any_Socket) { +@(private) +_close :: proc(skt: Any_Socket) { s := any_socket_to_socket(skt) - os.close(os.Handle(Platform_Socket(s))) + os.close(os.Handle(os.Socket(s))) } -recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +@(private) +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { if len(buf) <= 0 { return } - res, ok := os.recv(Platform_Socket(skt), buf, 0) + res, ok := os.recv(os.Socket(skt), buf, 0) if ok != os.ERROR_NONE { err = TCP_Recv_Error(ok) return @@ -157,14 +162,15 @@ recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network return int(res), nil } -recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +@(private) +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { if len(buf) <= 0 { return } from: os.SOCKADDR_STORAGE_LH fromsize := c.int(size_of(from)) - res, ok := os.recvfrom(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) + res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) if ok != os.ERROR_NONE { err = UDP_Recv_Error(ok) return @@ -175,14 +181,12 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo return } -// Repeatedly sends data until the entire buffer is sent. -// If a send fails before all data is sent, returns the amount -// sent up to that point. -send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.send(Platform_Socket(skt), remaining, 0) + res, ok := os.send(os.Socket(skt), remaining, 0) if ok != os.ERROR_NONE { err = TCP_Send_Error(ok) return @@ -192,12 +196,13 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw return } -send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +@(private) +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, ok := os.sendto(Platform_Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) + res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) if ok != os.ERROR_NONE { err = UDP_Send_Error(ok) return @@ -207,16 +212,18 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: return } -shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +@(private) +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(skt) - res := os.shutdown(Platform_Socket(s), int(manner)) + res := os.shutdown(os.Socket(s), int(manner)) if res != os.ERROR_NONE { return Shutdown_Error(res) } return } -set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +@(private) +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; @@ -286,7 +293,7 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal } skt := any_socket_to_socket(s) - res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len) + res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len) if res != os.ERROR_NONE { return Socket_Option_Error(res) } diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index a6ab0b3fa..d7264d617 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -22,8 +22,6 @@ import "core:c" import win "core:sys/windows" import "core:time" -Platform_Socket :: win.SOCKET - @(init, private) ensure_winsock_initialized :: proc() { win.ensure_winsock_initialized() @@ -78,7 +76,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio _ = set_option(socket, .Reuse_Address, true) sockaddr := _endpoint_to_sockaddr(endpoint) - res := win.connect(Platform_Socket(socket), &sockaddr, size_of(sockaddr)) + res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) if res < 0 { err = Dial_Error(win.WSAGetLastError()) return @@ -94,8 +92,8 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio @(private) _bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { sockaddr := _endpoint_to_sockaddr(ep) - s := any_socket_to_socket(socket) - res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) + sock := any_socket_to_socket(socket) + res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr)) if res < 0 { err = Bind_Error(win.WSAGetLastError()) } @@ -114,7 +112,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T bind(sock, interface_endpoint) or_return - if res := win.listen(Platform_Socket(socket), i32(backlog)); res == win.SOCKET_ERROR { + if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { err = Listen_Error(win.WSAGetLastError()) } return @@ -125,7 +123,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client for { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) - client_sock := win.accept(Platform_Socket(sock), &sockaddr, &sockaddrlen) + client_sock := win.accept(win.SOCKET(sock), &sockaddr, &sockaddrlen) if int(client_sock) == win.SOCKET_ERROR { e := win.WSAGetLastError() if e == win.WSAECONNRESET { @@ -150,7 +148,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client @(private) _close :: proc(socket: Any_Socket) { if s := any_socket_to_socket(socket); s != {} { - win.closesocket(Platform_Socket(s)) + win.closesocket(win.SOCKET(s)) } } @@ -159,7 +157,7 @@ _recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Net if len(buf) <= 0 { return } - res := win.recv(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0) + res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0) if res < 0 { err = TCP_Recv_Error(win.WSAGetLastError()) return @@ -175,7 +173,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e from: win.SOCKADDR_STORAGE_LH fromsize := c.int(size_of(from)) - res := win.recvfrom(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) + res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) if res < 0 { err = UDP_Recv_Error(win.WSAGetLastError()) return @@ -191,7 +189,7 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:] - res := win.send(Platform_Socket(socket), raw_data(remaining), c.int(limit), 0) + res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0) if res < 0 { err = TCP_Send_Error(win.WSAGetLastError()) return @@ -209,7 +207,7 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt return } toaddr := _endpoint_to_sockaddr(to) - res := win.sendto(Platform_Socket(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) + res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) if res < 0 { err = UDP_Send_Error(win.WSAGetLastError()) return @@ -221,7 +219,7 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt @(private) _shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { s := any_socket_to_socket(socket) - res := win.shutdown(Platform_Socket(s), c.int(manner)) + res := win.shutdown(win.SOCKET(s), c.int(manner)) if res < 0 { return Shutdown_Error(win.WSAGetLastError()) } @@ -304,7 +302,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca } socket := any_socket_to_socket(s) - res := win.setsockopt(Platform_Socket(socket), c.int(level), c.int(option), ptr, len) + res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len) if res < 0 { return Socket_Option_Error(win.WSAGetLastError()) } diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index ad764c24d..528cd89c4 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -338,8 +338,8 @@ ENDPOINT := net.Endpoint{ CONTENT := "Hellope!" -SEND_TIMEOUT :: time.Duration(2 * time.Second) -RECV_TIMEOUT :: time.Duration(2 * time.Second) +SEND_TIMEOUT :: time.Duration(1 * time.Second) +RECV_TIMEOUT :: time.Duration(1 * time.Second) Thread_Data :: struct { skt: net.Any_Socket, From f6134422e647290eaae473ec60683a2ebb7198e0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 17:50:49 +0100 Subject: [PATCH 18/23] Fix one last review comment. --- core/net/addr.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 83d4a0ead..f12799d4d 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -403,7 +403,8 @@ Host_Or_Endpoint :: union { Endpoint, } Parse_Endpoint_Error :: enum { - Bad_Port = 1, + None = 0, + Bad_Port = 1, Bad_Address, Bad_Hostname, } @@ -416,12 +417,12 @@ parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_End return nil, .Bad_Port } if addr := parse_address(host); addr != nil { - return Endpoint{addr, port}, nil + return Endpoint{addr, port}, .None } if !validate_hostname(host) { return nil, .Bad_Hostname } - return Host{host, port}, nil + return Host{host, port}, .None } @@ -742,6 +743,6 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D // Returns an address for each interface that can be bound to. get_network_interfaces :: proc() -> []Address { - // TODO + // TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces. return nil } \ No newline at end of file From 6e9475d61d303effccc3f0a48196738427f5687f Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Fri, 3 Mar 2023 09:09:50 -0800 Subject: [PATCH 19/23] add core_net to examples --- examples/all/all_main.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 9f1865d46..9515d2a00 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -49,6 +49,7 @@ import whirlpool "core:crypto/whirlpool" import x25519 "core:crypto/x25519" import dynlib "core:dynlib" +import net "core:net" import base32 "core:encoding/base32" import base64 "core:encoding/base64" @@ -161,6 +162,7 @@ _ :: crypto_util _ :: whirlpool _ :: x25519 _ :: dynlib +_ :: net _ :: base32 _ :: base64 _ :: csv @@ -214,4 +216,4 @@ _ :: sysinfo _ :: unicode _ :: utf8 _ :: utf8string -_ :: utf16 \ No newline at end of file +_ :: utf16 From d939d6079a980f83f02cee10da62fea348a46b6f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 3 Mar 2023 18:24:26 +0100 Subject: [PATCH 20/23] Don't try to check core:net on the BSDs. --- core/net/addr.odin | 2 ++ core/net/common.odin | 2 ++ core/net/dns.odin | 14 ++++++++------ core/net/interface.odin | 14 ++++++++------ core/net/socket.odin | 14 ++++++++------ 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index f12799d4d..636616932 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -1,4 +1,6 @@ +// +build windows, linux, darwin package net + /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. For other protocols and their features, see subdirectories of this package. diff --git a/core/net/common.odin b/core/net/common.odin index ce8a6a721..62854415e 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -1,4 +1,6 @@ +// +build windows, linux, darwin package net + /* Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. For other protocols and their features, see subdirectories of this package. diff --git a/core/net/dns.odin b/core/net/dns.odin index 15e980594..5714ab9b0 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -1,3 +1,11 @@ +// +build windows, linux, darwin +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -10,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:mem" import "core:strings" import "core:time" diff --git a/core/net/interface.odin b/core/net/interface.odin index 68eb73aa6..df7d0223e 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -1,3 +1,11 @@ +// +build windows, linux, darwin +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022 Tetralux Copyright 2022 Colin Davidson @@ -10,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - import "core:strings" MAX_INTERFACE_ENUMERATION_TRIES :: 3 diff --git a/core/net/socket.odin b/core/net/socket.odin index 1b73fcf53..95eb50496 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -1,3 +1,11 @@ +// +build windows, linux, darwin +package net + +/* + Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. + For other protocols and their features, see subdirectories of this package. +*/ + /* Copyright 2022-2023 Tetralux Copyright 2022-2023 Colin Davidson @@ -10,12 +18,6 @@ Jeroen van Rijn: Cross platform unification, code style, documentation */ -/* - Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures. - For other protocols and their features, see subdirectories of this package. -*/ -package net - any_socket_to_socket :: proc(socket: Any_Socket) -> Socket { switch s in socket { case TCP_Socket: return Socket(s) From 38ea140b3f585d410dcc859a62ceacbfad8fb6cb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 4 Mar 2023 10:04:55 +0100 Subject: [PATCH 21/23] Update addr.odin Fix comment --- core/net/addr.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 636616932..7f7b5b683 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -33,7 +33,7 @@ import "core:fmt" The port, if present, is required to be a base 10 number in the range 0-65535, inclusive. - If `allow_non_decimal` is true, `aton` is told each component must be decimal and max 255. + If `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255. */ parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) { res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return From e254581a1bbcab4aff1c83956ee94db85c09024a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 4 Mar 2023 10:39:20 +0100 Subject: [PATCH 22/23] Apply #shared_nil to Network_Error --- core/net/addr.odin | 8 +------ core/net/common.odin | 38 +++++++++++++++---------------- core/net/errors_windows.odin | 11 +++++++++ tests/core/net/test_core_net.odin | 17 ++++++++------ 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/core/net/addr.odin b/core/net/addr.odin index 7f7b5b683..e6b503aaf 100644 --- a/core/net/addr.odin +++ b/core/net/addr.odin @@ -404,16 +404,10 @@ Host_Or_Endpoint :: union { Host, Endpoint, } -Parse_Endpoint_Error :: enum { - None = 0, - Bad_Port = 1, - Bad_Address, - Bad_Hostname, -} // Takes a string consisting of a hostname or IP address, and an optional port, // and return the component parts in a useful form. -parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Network_Error) { +parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) { host, port, port_ok := split_port(endpoint_str) if !port_ok { return nil, .Bad_Port diff --git a/core/net/common.odin b/core/net/common.odin index 62854415e..a9f2ba9bb 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -50,23 +50,7 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true) // COMMON DEFINITIONS Maybe :: runtime.Maybe -General_Error :: enum { - Unable_To_Enumerate_Network_Interfaces = 1, -} - -// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. -Platform_Error :: enum u32 {} - -/* - NOTE(tetra): Enums in Network_Error should not have a named zero value. - If you have a proc that returns an enum with an Ok=0 value, using or_return from the callsite, when the caller returns a union, works as expected. - However, if that proc returns the union directly, returning the Ok value will NOT work with the caller's or_return, as it will treat Error{.Ok} as != nil, and early-return with it. - - The approach currently taken to avoid this is: - - Remove the named zero values for the enums - - Use the union everywhere -*/ -Network_Error :: union { +Network_Error :: union #shared_nil { General_Error, Platform_Error, Create_Socket_Error, @@ -85,11 +69,27 @@ Network_Error :: union { DNS_Error, } -Resolve_Error :: enum { +General_Error :: enum u32 { + None = 0, + Unable_To_Enumerate_Network_Interfaces = 1, +} + +// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. +Platform_Error :: enum u32 {} + +Parse_Endpoint_Error :: enum { + None = 0, + Bad_Port = 1, + Bad_Address, + Bad_Hostname, +} + +Resolve_Error :: enum u32 { + None = 0, Unable_To_Resolve = 1, } -DNS_Error :: enum { +DNS_Error :: enum u32 { Invalid_Hostname_Error = 1, Invalid_Hosts_Config_Error, Invalid_Resolv_Config_Error, diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index c45ebbabf..154f6c2d8 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -22,6 +22,7 @@ import "core:c" import win "core:sys/windows" Create_Socket_Error :: enum c.int { + None = 0, Network_Subsystem_Failure = win.WSAENETDOWN, Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, No_Socket_Descriptors_Available = win.WSAEMFILE, @@ -32,6 +33,7 @@ Create_Socket_Error :: enum c.int { } Dial_Error :: enum c.int { + None = 0, Port_Required = -1, Address_In_Use = win.WSAEADDRINUSE, In_Progress = win.WSAEALREADY, @@ -49,6 +51,7 @@ Dial_Error :: enum c.int { } Bind_Error :: enum c.int { + None = 0, Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint. Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine. Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. @@ -58,6 +61,7 @@ Bind_Error :: enum c.int { } Listen_Error :: enum c.int { + None = 0, Address_In_Use = win.WSAEADDRINUSE, Already_Connected = win.WSAEISCONN, No_Socket_Descriptors_Available = win.WSAEMFILE, @@ -68,6 +72,7 @@ Listen_Error :: enum c.int { } Accept_Error :: enum c.int { + None = 0, Not_Listening = win.WSAEINVAL, No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, No_Buffer_Space_Available = win.WSAENOBUFS, @@ -79,6 +84,7 @@ Accept_Error :: enum c.int { } TCP_Recv_Error :: enum c.int { + None = 0, Network_Subsystem_Failure = win.WSAENETDOWN, Not_Connected = win.WSAENOTCONN, Bad_Buffer = win.WSAEFAULT, @@ -99,6 +105,7 @@ TCP_Recv_Error :: enum c.int { } UDP_Recv_Error :: enum c.int { + None = 0, Network_Subsystem_Failure = win.WSAENETDOWN, // TODO: not functionally different from Reset; merge? @@ -124,6 +131,7 @@ UDP_Recv_Error :: enum c.int { // TODO: consider merging some errors to make handling them easier // TODO: verify once more what errors to actually expose TCP_Send_Error :: enum c.int { + None = 0, // TODO: not functionally different from Reset; merge? Aborted = win.WSAECONNABORTED, @@ -148,6 +156,7 @@ TCP_Send_Error :: enum c.int { } UDP_Send_Error :: enum c.int { + None = 0, Network_Subsystem_Failure = win.WSAENETDOWN, // TODO: not functionally different from Reset; merge? @@ -181,6 +190,7 @@ Shutdown_Manner :: enum c.int { } Shutdown_Error :: enum c.int { + None = 0, Aborted = win.WSAECONNABORTED, Reset = win.WSAECONNRESET, Offline = win.WSAENETDOWN, @@ -237,6 +247,7 @@ Socket_Option :: enum c.int { } Socket_Option_Error :: enum c.int { + None = 0, Linger_Only_Supports_Whole_Seconds = 1, // The given value is too big or small to be given to the OS. diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 528cd89c4..00c29db95 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -374,17 +374,19 @@ tcp_tests :: proc(t: ^testing.T) { } tcp_client :: proc(retval: rawptr) { - r := transmute(^Thread_Data)retval + send :: proc(content: []u8) -> (err: net.Network_Error) { + skt := net.dial_tcp(ENDPOINT) or_return + defer net.close(skt) - if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { + net.set_option(skt, .Send_Timeout, SEND_TIMEOUT) + net.set_option(skt, .Receive_Timeout, RECV_TIMEOUT) + + _, err = net.send(skt, content) return } - defer net.close(r.skt) - net.set_option(r.skt, .Send_Timeout, SEND_TIMEOUT) - net.set_option(r.skt, .Receive_Timeout, RECV_TIMEOUT) - - _, r.err = net.send(r.skt.(net.TCP_Socket), transmute([]u8)CONTENT) + r := transmute(^Thread_Data)retval + r.err = send(transmute([]u8)CONTENT) return } @@ -407,6 +409,7 @@ tcp_server :: proc(retval: rawptr) { } defer net.close(client) + r.length, r.err = net.recv_tcp(client, r.data[:]) return } From ee597fc9b8c53ad93bb1c795e502a40be4a58b02 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 4 Mar 2023 11:12:11 +0100 Subject: [PATCH 23/23] Add .None to Linux & Darwin, too. --- core/net/errors_darwin.odin | 13 ++++++++++++- core/net/errors_linux.odin | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index 2196933d1..645bbe555 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -22,6 +22,7 @@ import "core:c" import "core:os" Create_Socket_Error :: enum c.int { + None = 0, Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), No_Socket_Descriptors_Available = c.int(os.EMFILE), No_Buffer_Space_Available = c.int(os.ENOBUFS), @@ -32,6 +33,7 @@ Create_Socket_Error :: enum c.int { } Dial_Error :: enum c.int { + None = 0, Port_Required = -1, Address_In_Use = c.int(os.EADDRINUSE), @@ -52,6 +54,7 @@ Dial_Error :: enum c.int { } Bind_Error :: enum c.int { + None = 0, Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. @@ -61,6 +64,7 @@ Bind_Error :: enum c.int { } Listen_Error :: enum c.int { + None = 0, Address_In_Use = c.int(os.EADDRINUSE), Already_Connected = c.int(os.EISCONN), No_Socket_Descriptors_Available = c.int(os.EMFILE), @@ -71,7 +75,7 @@ Listen_Error :: enum c.int { } Accept_Error :: enum c.int { - + None = 0, // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. Reset = c.int(os.ECONNRESET), Not_Listening = c.int(os.EINVAL), @@ -85,6 +89,7 @@ Accept_Error :: enum c.int { } TCP_Recv_Error :: enum c.int { + None = 0, Shutdown = c.int(os.ESHUTDOWN), Not_Connected = c.int(os.ENOTCONN), @@ -104,6 +109,7 @@ TCP_Recv_Error :: enum c.int { } UDP_Recv_Error :: enum c.int { + None = 0, Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. @@ -118,6 +124,8 @@ UDP_Recv_Error :: enum c.int { // TODO TCP_Send_Error :: enum c.int { + None = 0, + // TODO: merge with other errors? Aborted = c.int(os.ECONNABORTED), Connection_Closed = c.int(os.ECONNRESET), @@ -142,6 +150,7 @@ TCP_Send_Error :: enum c.int { // TODO UDP_Send_Error :: enum c.int { + None = 0, Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent. // TODO: not sure what the exact circumstances for this is yet @@ -172,6 +181,7 @@ Shutdown_Manner :: enum c.int { } Shutdown_Error :: enum c.int { + None = 0, Aborted = c.int(os.ECONNABORTED), Reset = c.int(os.ECONNRESET), Offline = c.int(os.ENETDOWN), @@ -181,6 +191,7 @@ Shutdown_Error :: enum c.int { } Socket_Option_Error :: enum c.int { + None = 0, Offline = c.int(os.ENETDOWN), Timeout_When_Keepalive_Set = c.int(os.ENETRESET), Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 1552339d8..9575d7c18 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -22,6 +22,7 @@ import "core:c" import "core:os" Create_Socket_Error :: enum c.int { + None = 0, Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), No_Socket_Descriptors_Available = c.int(os.EMFILE), No_Buffer_Space_Available = c.int(os.ENOBUFS), @@ -32,7 +33,8 @@ Create_Socket_Error :: enum c.int { } Dial_Error :: enum c.int { - Port_Required = -1, + None = 0, + Port_Required = -1, Address_In_Use = c.int(os.EADDRINUSE), In_Progress = c.int(os.EINPROGRESS), @@ -52,6 +54,7 @@ Dial_Error :: enum c.int { } Bind_Error :: enum c.int { + None = 0, Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. @@ -61,6 +64,7 @@ Bind_Error :: enum c.int { } Listen_Error :: enum c.int { + None = 0, Address_In_Use = c.int(os.EADDRINUSE), Already_Connected = c.int(os.EISCONN), No_Socket_Descriptors_Available = c.int(os.EMFILE), @@ -71,6 +75,7 @@ Listen_Error :: enum c.int { } Accept_Error :: enum c.int { + None = 0, Not_Listening = c.int(os.EINVAL), No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), No_Buffer_Space_Available = c.int(os.ENOBUFS), @@ -82,6 +87,7 @@ Accept_Error :: enum c.int { } TCP_Recv_Error :: enum c.int { + None = 0, Shutdown = c.int(os.ESHUTDOWN), Not_Connected = c.int(os.ENOTCONN), Connection_Broken = c.int(os.ENETRESET), @@ -97,6 +103,8 @@ TCP_Recv_Error :: enum c.int { } UDP_Recv_Error :: enum c.int { + None = 0, + // The buffer is too small to fit the entire message, and the message was truncated. // When this happens, the rest of message is lost. Buffer_Too_Small = c.int(os.EMSGSIZE), @@ -113,7 +121,7 @@ UDP_Recv_Error :: enum c.int { // TODO TCP_Send_Error :: enum c.int { - + None = 0, // TODO(tetra): merge with other errors? Aborted = c.int(os.ECONNABORTED), Connection_Closed = c.int(os.ECONNRESET), @@ -135,6 +143,7 @@ TCP_Send_Error :: enum c.int { // TODO UDP_Send_Error :: enum c.int { + None = 0, Message_Too_Long = c.int(os.EMSGSIZE), // The message is too big. No data was sent. // TODO: not sure what the exact circumstances for this is yet @@ -165,6 +174,7 @@ Shutdown_Manner :: enum c.int { } Shutdown_Error :: enum c.int { + None = 0, Aborted = c.int(os.ECONNABORTED), Reset = c.int(os.ECONNRESET), Offline = c.int(os.ENETDOWN), @@ -174,6 +184,7 @@ Shutdown_Error :: enum c.int { } Socket_Option_Error :: enum c.int { + None = 0, Offline = c.int(os.ENETDOWN), Timeout_When_Keepalive_Set = c.int(os.ENETRESET), Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),