mirror of
https://github.com/odin-lang/Odin.git
synced 2026-02-15 07:43:13 +00:00
manually start merging core_net
This commit is contained in:
818
core/net/addr.odin
Normal file
818
core/net/addr.odin
Normal 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
71
core/net/addr_darwin.odin
Normal 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
93
core/net/addr_linux.odin
Normal 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 }
|
||||
69
core/net/addr_openbsd.odin
Normal file
69
core/net/addr_openbsd.odin
Normal 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
|
||||
}
|
||||
69
core/net/addr_windows.odin
Normal file
69
core/net/addr_windows.odin
Normal 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
437
core/net/common.odin
Normal 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
873
core/net/dns.odin
Normal 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
83
core/net/dns_unix.odin
Normal 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
166
core/net/dns_windows.odin
Normal 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
47
core/net/doc.odin
Normal 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
68
core/net/interface.odin
Normal 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)
|
||||
}
|
||||
23
core/net/interface_darwin.odin
Normal file
23
core/net/interface_darwin.odin
Normal 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.
|
||||
*/
|
||||
147
core/net/interface_linux.odin
Normal file
147
core/net/interface_linux.odin
Normal 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[:], {}
|
||||
}
|
||||
182
core/net/interface_windows.odin
Normal file
182
core/net/interface_windows.odin
Normal 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
87
core/net/socket.odin
Normal 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
513
core/net/socket_darwin.odin
Normal 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
532
core/net/socket_linux.odin
Normal 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
|
||||
}
|
||||
515
core/net/socket_openbsd.odin
Normal file
515
core/net/socket_openbsd.odin
Normal 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
|
||||
}
|
||||
577
core/net/socket_windows.odin
Normal file
577
core/net/socket_windows.odin
Normal 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
235
core/net/url.odin
Normal 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);
|
||||
}
|
||||
*/
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
10
core/sys/windows/dnsapi.odin
Normal file
10
core/sys/windows/dnsapi.odin
Normal 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) ---
|
||||
}
|
||||
234
core/sys/windows/ip_helper.odin
Normal file
234
core/sys/windows/ip_helper.odin
Normal 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 ---
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
508
tests/core/net/test_core_net.odin
Normal file
508
tests/core/net/test_core_net.odin
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user