manually start merging core_net

This commit is contained in:
Colin Davidson
2023-03-01 07:58:30 -08:00
parent 3567c006e6
commit 28f7f57247
31 changed files with 7142 additions and 147 deletions

818
core/net/addr.odin Normal file
View File

@@ -0,0 +1,818 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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..<len(pieces) - 1 {
if pieces[i] == "" {
/*
Return if skip has already been set.
*/
if zero_skip != -1 {
return
}
zero_skip = i
}
}
/*
Now check if we have the necessary number pieces, accounting for any `::`,
and how many were skipped by it if applicable.
*/
before_skip := 0
after_skip := 0
num_skipped := 0
if zero_skip != -1 {
before_skip = zero_skip
after_skip = len(pieces) - zero_skip - 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 `::`.
*/
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] == "" {
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..<before_skip {
/*
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
If we have an IPv4 address, stop on the penultimate index.
*/
if have_ipv4 && val_idx == 6 {
break
}
piece := pieces[idx]
/*
An IPv6 piece can at most contain 4 hex digits.
*/
if len(piece) > 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..<after_skip {
/*
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
If we have an IPv4 address, stop on the penultimate index.
*/
if have_ipv4 && val_idx == 6 {
break
}
piece := pieces[idx]
/*
An IPv6 piece can at most contain 4 hex digits.
*/
if len(piece) > 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
}

71
core/net/addr_darwin.odin Normal file
View File

@@ -0,0 +1,71 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

93
core/net/addr_linux.odin Normal file
View File

@@ -0,0 +1,93 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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 }

View File

@@ -0,0 +1,69 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

View File

@@ -0,0 +1,69 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

437
core/net/common.odin Normal file
View File

@@ -0,0 +1,437 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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,
}

873
core/net/dns.odin Normal file
View File

@@ -0,0 +1,873 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

83
core/net/dns_unix.odin Normal file
View File

@@ -0,0 +1,83 @@
//+build linux, darwin, freebsd, openbsd, !windows
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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[:])
}

166
core/net/dns_windows.odin Normal file
View File

@@ -0,0 +1,166 @@
//+build windows
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

47
core/net/doc.odin Normal file
View File

@@ -0,0 +1,47 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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

68
core/net/interface.odin Normal file
View File

@@ -0,0 +1,68 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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)
}

View File

@@ -0,0 +1,23 @@
//+build darwin
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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.
*/

View File

@@ -0,0 +1,147 @@
//+build linux, darwin, openbsd, !windows
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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[:], {}
}

View File

@@ -0,0 +1,182 @@
//+build windows
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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()
}

87
core/net/socket.odin Normal file
View File

@@ -0,0 +1,87 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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,
}

513
core/net/socket_darwin.odin Normal file
View File

@@ -0,0 +1,513 @@
// +build darwin
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

532
core/net/socket_linux.odin Normal file
View File

@@ -0,0 +1,532 @@
// +build linux
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

View File

@@ -0,0 +1,515 @@
// +build openbsd
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

View File

@@ -0,0 +1,577 @@
// +build windows
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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
}

235
core/net/url.odin Normal file
View File

@@ -0,0 +1,235 @@
/*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
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);
}
*/

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)))
}

View File

@@ -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) ---
}

View File

@@ -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 ---
}

View File

@@ -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,
}

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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 ---

View File

@@ -0,0 +1,508 @@
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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)
}