net: implement OpenBSD and NetBSD support & add stubs for other targets & cleanup

This commit is contained in:
Laytan Laats
2026-01-11 20:07:03 +01:00
parent a6ec199a52
commit c10771305d
26 changed files with 519 additions and 330 deletions

View File

@@ -11,8 +11,7 @@ NO_CORE_NAMED_TYPES :: #config(ODIN_CORE_FLAGS_NO_CORE_NAMED_TYPES, false)
IMPORTING_TIME :: #config(ODIN_CORE_FLAGS_USE_TIME, time.IS_SUPPORTED)
// Override support for parsing `net` types.
// TODO: Update this when the BSDs are supported.
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD)
IMPORTING_NET :: #config(ODIN_CORE_FLAGS_USE_NET, ODIN_OS == .Windows || ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .OpenBSD)
TAG_ARGS :: "args"
SUBTAG_NAME :: "name"

View File

@@ -1,5 +1,7 @@
package flags
import "base:runtime"
import "core:net"
import "core:os"
Parse_Error_Reason :: enum {
@@ -24,6 +26,12 @@ Parse_Error :: struct {
message: string,
}
Unified_Parse_Error_Reason :: union #shared_nil {
Parse_Error_Reason,
runtime.Allocator_Error,
net.Parse_Endpoint_Error,
}
// Raised during parsing.
// Provides more granular information than what just a string could hold.
Open_File_Error :: struct {

View File

@@ -1,9 +0,0 @@
#+build netbsd, openbsd
package flags
import "base:runtime"
Unified_Parse_Error_Reason :: union #shared_nil {
Parse_Error_Reason,
runtime.Allocator_Error,
}

View File

@@ -1,12 +0,0 @@
#+build !netbsd
#+build !openbsd
package flags
import "base:runtime"
import "core:net"
Unified_Parse_Error_Reason :: union #shared_nil {
Parse_Error_Reason,
runtime.Allocator_Error,
net.Parse_Endpoint_Error,
}

View File

@@ -5,6 +5,7 @@ import "base:intrinsics"
import "base:runtime"
import "core:fmt"
import "core:mem"
import "core:net"
import "core:os"
import "core:reflect"
import "core:strconv"
@@ -310,7 +311,18 @@ parse_and_set_pointer_by_named_type :: proc(ptr: rawptr, str: string, data_type:
}
when IMPORTING_NET {
if try_net_parse_workaround(data_type, str, ptr, out_error) {
if data_type == net.Host_Or_Endpoint {
addr, net_error := net.parse_hostname_or_endpoint(str)
if net_error != nil {
// We pass along `net.Error` here.
out_error^ = Parse_Error {
net_error,
"Invalid Host/Endpoint.",
}
return
}
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
return
}
}

View File

@@ -1,32 +0,0 @@
#+private
#+build !netbsd
#+build !openbsd
package flags
import "core:net"
// This proc exists purely as a workaround for import restrictions.
// Returns true if caller should return early.
try_net_parse_workaround :: #force_inline proc (
data_type: typeid,
str: string,
ptr: rawptr,
out_error: ^Error,
) -> bool {
if data_type == net.Host_Or_Endpoint {
addr, net_error := net.parse_hostname_or_endpoint(str)
if net_error != nil {
// We pass along `net.Error` here.
out_error^ = Parse_Error {
net_error,
"Invalid Host/Endpoint.",
}
return true
}
(cast(^net.Host_Or_Endpoint)ptr)^ = addr
return true
}
return false
}

View File

@@ -1,4 +1,3 @@
#+build windows, linux, darwin, freebsd
package net
/*
@@ -22,7 +21,6 @@ package net
import "core:strconv"
import "core:strings"
import "core:fmt"
/*
Expects an IPv4 address with no leading or trailing whitespace:
@@ -473,13 +471,20 @@ join_port :: proc(address_or_host: string, port: int, allocator := context.alloc
addr := parse_address(addr_or_host)
if addr == nil {
// hostname
fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
strings.write_string(&b, addr_or_host)
strings.write_string(&b, ":")
strings.write_int(&b, port)
} else {
switch _ in addr {
case IP4_Address:
fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
strings.write_string(&b, address_to_string(addr))
strings.write_string(&b, ":")
strings.write_int(&b, port)
case IP6_Address:
fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
strings.write_string(&b, "[")
strings.write_string(&b, address_to_string(addr))
strings.write_string(&b, "]:")
strings.write_int(&b, port)
}
}
return strings.to_string(b)
@@ -509,7 +514,13 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) ->
b := strings.builder_make(allocator)
switch v in addr {
case IP4_Address:
fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
strings.write_uint(&b, uint(v[0]))
strings.write_byte(&b, '.')
strings.write_uint(&b, uint(v[1]))
strings.write_byte(&b, '.')
strings.write_uint(&b, uint(v[2]))
strings.write_byte(&b, '.')
strings.write_uint(&b, uint(v[3]))
case IP6_Address:
// First find the longest run of zeroes.
Zero_Run :: struct {
@@ -563,25 +574,33 @@ address_to_string :: proc(addr: Address, allocator := context.temp_allocator) ->
for val, i in v {
if best.start == i || best.end == i {
// For the left and right side of the best zero run, print a `:`.
fmt.sbprint(&b, ":")
strings.write_string(&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)
buf: [32]byte
str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {})
strings.write_string(&b, str)
if i < best.start - 1 {
fmt.sbprintf(&b, ":")
strings.write_string(&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)
buf: [32]byte
str := strconv.write_bits(buf[:], u64(val), 16, false, size_of(val), strconv.digits, {})
strings.write_string(&b, str)
if i != 7 {
fmt.sbprintf(&b, ":")
strings.write_string(&b, ":")
}
}
}
@@ -598,8 +617,14 @@ endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) ->
s := address_to_string(ep.address, context.temp_allocator)
b := strings.builder_make(allocator)
switch a in ep.address {
case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port)
case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
case IP4_Address:
strings.write_string(&b, s)
strings.write_int(&b, ep.port)
case IP6_Address:
strings.write_string(&b, "[")
strings.write_string(&b, s)
strings.write_string(&b, "]:")
strings.write_int(&b, ep.port)
}
return strings.to_string(b)
}

View File

@@ -1,4 +1,3 @@
#+build windows, linux, darwin, freebsd
package net
/*
@@ -91,6 +90,7 @@ Parse_Endpoint_Error :: enum u32 {
Resolve_Error :: enum u32 {
None = 0,
Unable_To_Resolve = 1,
Allocation_Failure,
}
DNS_Error :: enum u32 {
@@ -144,11 +144,11 @@ 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{}
IP4_Any :: IP4_Address{}
IP6_Any :: IP6_Address{}
IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}
IP4_mDNS_Broadcast :: Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353}
IP6_mDNS_Broadcast :: Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}
Endpoint :: struct {
address: Address,

View File

@@ -1,4 +1,3 @@
#+build windows, linux, darwin, freebsd
package net
/*
@@ -22,13 +21,18 @@ package net
Haesbaert: Security fixes
*/
@(require) import "base:runtime"
@(require)
import "base:runtime"
import "core:bufio"
import "core:io"
import "core:math/rand"
import "core:mem"
import "core:strings"
import "core:time"
import "core:os"
import "core:math/rand"
@(require) import "core:sync"
@(require)
import "core:sync"
dns_config_initialized: sync.Once
when ODIN_OS == .Windows {
@@ -42,20 +46,12 @@ when ODIN_OS == .Windows {
hosts_file = "/etc/hosts",
}
} else {
#panic("Please add a configuration for this OS.")
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{}
}
/*
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
Is automatically called, once, by `get_dns_records_*`.
*/
@(private)
init_dns_configuration :: proc() {
when ODIN_OS == .Windows {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
_init_dns_configuration()
}
}
@@ -178,9 +174,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
See `destroy_records`.
*/
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
init_dns_configuration()
return _get_dns_records_os(hostname, type, allocator)
}
@@ -196,51 +190,14 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat
See `destroy_records`.
*/
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
when ODIN_OS == .Windows {
sync.once_do(&dns_config_initialized, init_dns_configuration)
}
init_dns_configuration()
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
}
}
id := u16be(rand.uint32())
dns_packet_buf: [DNS_PACKET_MIN_LEN]byte = ---
dns_packet := make_dns_packet(dns_packet_buf[:], id, hostname, type) or_return
hdr := DNS_Header{
id = u16be(rand.uint32()),
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_buf: [4096]u8 = ---
dns_response: []u8
for name_server in name_servers {
conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
@@ -283,6 +240,42 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type
return
}
DNS_PACKET_MIN_LEN :: (size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)
make_dns_packet :: proc(buf: []byte, id: u16be, hostname: string, type: DNS_Record_Type) -> (packet: []byte, err: DNS_Error) {
assert(len(buf) >= DNS_PACKET_MIN_LEN)
hdr := DNS_Header{
id = id,
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,
}
_, 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 }
b := strings.builder_from_slice(buf[:])
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[:]))
return buf[:strings.builder_len(b)], nil
}
// `records` slice is also destroyed.
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
context.allocator = allocator
@@ -364,13 +357,8 @@ unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
return hdr
}
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
context.allocator = allocator
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
defer delete(res)
resolv_str := string(res)
parse_resolv_conf :: proc(resolv_str: string, allocator := context.allocator) -> (name_servers: []Endpoint) {
resolv_str := resolv_str
id_str := "nameserver"
id_len := len(id_str)
@@ -401,41 +389,51 @@ load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocato
append(&_name_servers, endpoint)
}
return _name_servers[:], true
return _name_servers[:]
}
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
context.allocator = allocator
parse_hosts :: proc(stream: io.Stream, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
s := bufio.scanner_init(&{}, stream, allocator)
defer bufio.scanner_destroy(s)
res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
defer delete(res)
resize(&s.buf, 256)
_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
_hosts: [dynamic]DNS_Host_Entry
_hosts.allocator = allocator
defer if !ok {
for host in _hosts {
delete(host.name, allocator)
}
delete(_hosts)
}
splits := strings.fields(line)
defer delete(splits)
for bufio.scanner_scan(s) {
line := bufio.scanner_text(s)
(len(splits) >= 2) or_continue
line, _, _ = strings.partition(line, "#")
(len(line) > 0) or_continue
ip_str := strings.fields_iterator(&line) or_continue
ip_str := splits[0]
addr := parse_address(ip_str)
if addr == nil {
continue
}
(addr != nil) or_continue
for hostname in splits[1:] {
if len(hostname) != 0 {
append(&_hosts, DNS_Host_Entry{hostname, addr})
}
for hostname in strings.fields_iterator(&line) {
(len(hostname) > 0) or_continue
clone, alloc_err := strings.clone(hostname, allocator)
if alloc_err != nil { return }
_, alloc_err = append(&_hosts, DNS_Host_Entry{clone, addr})
if alloc_err != nil { return }
}
}
return _hosts[:], true
if bufio.scanner_error(s) != nil { return }
hosts = _hosts[:]
ok = true
return
}
// www.google.com -> 3www6google3com0
@@ -594,7 +592,7 @@ decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.alloc
// Uses RFC 952 & RFC 1123
validate_hostname :: proc(hostname: string) -> (ok: bool) {
if len(hostname) > 255 || len(hostname) == 0 {
if len(hostname) > NAME_MAX || len(hostname) == 0 {
return
}
@@ -604,7 +602,7 @@ validate_hostname :: proc(hostname: string) -> (ok: bool) {
_hostname := hostname
for label in strings.split_iterator(&_hostname, ".") {
if len(label) > 63 || len(label) == 0 {
if len(label) > LABEL_MAX || len(label) == 0 {
return
}
@@ -868,4 +866,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
xid = hdr.id
return _records[:], xid, true
}
}

24
core/net/dns_os.odin Normal file
View File

@@ -0,0 +1,24 @@
#+build darwin, freebsd, openbsd, netbsd, linux, windows, wasi
#+private
package net
import "core:os"
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
context.allocator = allocator
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
defer delete(res)
resolv_str := string(res)
return parse_resolv_conf(resolv_str), true
}
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
hosts_file, err := os.open(hosts_file_path)
if err != nil { return }
defer os.close(hosts_file)
return parse_hosts(os.stream_from_handle(hosts_file), allocator)
}

12
core/net/dns_others.odin Normal file
View File

@@ -0,0 +1,12 @@
#+build !windows
#+build !linux
#+build !darwin
#+build !freebsd
#+build !netbsd
#+build !openbsd
package net
@(private)
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
return
}

View File

@@ -1,4 +1,4 @@
#+build linux, darwin, freebsd
#+build linux, darwin, freebsd, openbsd, netbsd
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
@@ -42,14 +42,19 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
}
hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
defer delete(hosts)
if !hosts_ok {
return nil, .Invalid_Hosts_Config_Error
}
defer {
for h in hosts {
delete(h.name)
}
delete(hosts)
}
host_overrides := make([dynamic]DNS_Record)
for host in hosts {
if strings.compare(host.name, hostname) != 0 {
if host.name != hostname {
continue
}
@@ -79,4 +84,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
}
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
}
}

View File

@@ -20,11 +20,29 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:strings"
import "base:runtime"
import "core:mem"
import "core:os"
import "core:strings"
import "core:sync"
import win "core:sys/windows"
/*
Replaces environment placeholders in `dns_configuration`. Only necessary on Windows.
Is automatically called, once, by `get_dns_records_*`.
*/
@(private)
_init_dns_configuration :: proc() {
sync.once_do(&dns_config_initialized, proc() {
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator)
copy(dns_configuration.hosts_file_buf[:], val)
dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)])
})
}
@(private)
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
context.allocator = allocator
@@ -171,4 +189,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator :
records = recs[:]
return
}
}

View File

@@ -139,6 +139,11 @@ Accept_Error :: enum i32 {
Unknown,
}
Recv_Error :: union #shared_nil {
TCP_Recv_Error,
UDP_Recv_Error,
}
TCP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
@@ -187,6 +192,11 @@ UDP_Recv_Error :: enum i32 {
Unknown,
}
Send_Error :: union #shared_nil {
TCP_Send_Error,
UDP_Send_Error,
}
TCP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.

View File

@@ -2,6 +2,8 @@
#+build !linux
#+build !freebsd
#+build !windows
#+build !netbsd
#+build !openbsd
package net
@(private="file", thread_local)
@@ -18,10 +20,3 @@ _last_platform_error_string :: proc() -> string {
_set_last_platform_error :: proc(err: i32) {
_last_error = err
}
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
Bad_Address,
Bad_Hostname,
}

View File

@@ -1,4 +1,4 @@
#+build darwin
#+build darwin, netbsd, openbsd
package net
/*

View File

@@ -63,7 +63,7 @@ _dial_error :: proc() -> Dial_Error {
return .Already_Connecting
case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
return .Invalid_Argument
case .WSAECONNREFUSED:
case .WSAECONNREFUSED, .CONNECTION_REFUSED:
return .Refused
case .WSAEISCONN:
return .Already_Connected
@@ -122,7 +122,7 @@ _accept_error :: proc() -> Accept_Error {
return .Aborted
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
return .Invalid_Argument
case .WSAEINTR:
case .WSAEINTR, .OPERATION_ABORTED:
return .Interrupted
case .WSAEINVAL:
return .Not_Listening

View File

@@ -0,0 +1,11 @@
#+build !darwin
#+build !linux
#+build !freebsd
#+build !windows
#+build !netbsd
#+build !openbsd
package net
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
return
}

View File

@@ -1,4 +1,4 @@
#+build darwin
#+build darwin, openbsd, netbsd
package net
/*
@@ -117,32 +117,47 @@ IF_Flag :: enum u32 {
BROADCAST,
DEBUG,
LOOPBACK,
POINTTOPOINT,
NOTRAILERS,
RUNNING,
NOARP,
PROMISC,
ALLMULTI,
OACTIVE,
SIMPLEX,
LINK0,
LINK1,
LINK2,
MULTICAST,
// NOTE: different order on other BSDs but we don't even need these.
// POINTTOPOINT,
// NOTRAILERS,
// RUNNING,
// NOARP,
// PROMISC,
// ALLMULTI,
// OACTIVE,
// SIMPLEX,
// LINK0,
// LINK1,
// LINK2,
// MULTICAST,
}
@(private)
IF_Flags :: bit_set[IF_Flag; u32]
@(private)
ifaddrs :: struct {
next: ^ifaddrs,
name: cstring,
flags: IF_Flags,
addr: ^posix.sockaddr,
netmask: ^posix.sockaddr,
dstaddr: ^posix.sockaddr,
data: rawptr,
when ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
@(private)
ifaddrs :: struct {
next: ^ifaddrs,
name: cstring,
flags: IF_Flags,
addr: ^posix.sockaddr,
netmask: ^posix.sockaddr,
dstaddr: ^posix.sockaddr,
data: rawptr,
}
} else when ODIN_OS == .NetBSD {
@(private)
ifaddrs :: struct {
next: ^ifaddrs,
name: cstring,
flags: IF_Flags,
addr: ^posix.sockaddr,
netmask: ^posix.sockaddr,
dstaddr: ^posix.sockaddr,
data: rawptr,
addrflags: u32,
}
}
@(private)

View File

@@ -1,4 +1,3 @@
#+build windows, linux, darwin, freebsd
package net
/*
@@ -20,6 +19,35 @@ package net
Feoramund: FreeBSD platform code
*/
Socket_Option :: enum i32 {
Broadcast = i32(_SOCKET_OPTION_BROADCAST),
Reuse_Address = i32(_SOCKET_OPTION_REUSE_ADDRESS),
Keep_Alive = i32(_SOCKET_OPTION_KEEP_ALIVE),
Out_Of_Bounds_Data_Inline = i32(_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE),
Linger = i32(_SOCKET_OPTION_LINGER),
Receive_Buffer_Size = i32(_SOCKET_OPTION_RECEIVE_BUFFER_SIZE),
Send_Buffer_Size = i32(_SOCKET_OPTION_SEND_BUFFER_SIZE),
Receive_Timeout = i32(_SOCKET_OPTION_RECEIVE_TIMEOUT),
Send_Timeout = i32(_SOCKET_OPTION_SEND_TIMEOUT),
TCP_Nodelay = i32(_SOCKET_OPTION_TCP_NODELAY),
Use_Loopback = i32(_SOCKET_OPTION_USE_LOOPBACK),
Reuse_Port = i32(_SOCKET_OPTION_REUSE_PORT),
No_SIGPIPE_From_EPIPE = i32(_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE),
Reuse_Port_Load_Balancing = i32(_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING),
Exclusive_Addr_Use = i32(_SOCKET_OPTION_EXCLUSIVE_ADDR_USE),
Conditional_Accept = i32(_SOCKET_OPTION_CONDITIONAL_ACCEPT),
Dont_Linger = i32(_SOCKET_OPTION_DONT_LINGER),
}
Shutdown_Manner :: enum i32 {
Receive = i32(_SHUTDOWN_MANNER_RECEIVE),
Send = i32(_SHUTDOWN_MANNER_SEND),
Both = i32(_SHUTDOWN_MANNER_BOTH),
}
any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
switch s in socket {
case TCP_Socket: return Socket(s)

View File

@@ -20,45 +20,35 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:sys/freebsd"
import "core:time"
Fd :: freebsd.Fd
Socket_Option :: enum c.int {
// TODO: Test and implement more socket options.
// DEBUG
Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR,
Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE,
// DONTROUTE
Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST,
Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK,
Linger = cast(c.int)freebsd.Socket_Option.LINGER,
Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE,
Reuse_Port = cast(c.int)freebsd.Socket_Option.REUSEPORT,
// TIMESTAMP
No_SIGPIPE_From_EPIPE = cast(c.int)freebsd.Socket_Option.NOSIGPIPE,
// ACCEPTFILTER
// BINTIME
// NO_OFFLOAD
// NO_DDP
Reuse_Port_Load_Balancing = cast(c.int)freebsd.Socket_Option.REUSEPORT_LB,
// RERROR
_SOCKET_OPTION_BROADCAST :: freebsd.Socket_Option.BROADCAST
_SOCKET_OPTION_REUSE_ADDRESS :: freebsd.Socket_Option.REUSEADDR
_SOCKET_OPTION_KEEP_ALIVE :: freebsd.Socket_Option.KEEPALIVE
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: freebsd.Socket_Option.OOBINLINE
_SOCKET_OPTION_LINGER :: freebsd.Socket_Option.LINGER
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: freebsd.Socket_Option.RCVBUF
_SOCKET_OPTION_SEND_BUFFER_SIZE :: freebsd.Socket_Option.SNDBUF
_SOCKET_OPTION_RECEIVE_TIMEOUT :: freebsd.Socket_Option.RCVTIMEO
_SOCKET_OPTION_SEND_TIMEOUT :: freebsd.Socket_Option.SNDTIMEO
Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF,
Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF,
// SNDLOWAT
// RCVLOWAT
Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO,
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
}
_SOCKET_OPTION_TCP_NODELAY :: -1
Shutdown_Manner :: enum c.int {
Receive = cast(c.int)freebsd.Shutdown_Method.RD,
Send = cast(c.int)freebsd.Shutdown_Method.WR,
Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
}
_SOCKET_OPTION_USE_LOOPBACK :: freebsd.Socket_Option.USELOOPBACK
_SOCKET_OPTION_REUSE_PORT :: freebsd.Socket_Option.REUSEPORT
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: freebsd.Socket_Option.NOSIGPIPE
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: freebsd.Socket_Option.REUSEPORT_LB
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
_SOCKET_OPTION_DONT_LINGER :: -1
_SHUTDOWN_MANNER_RECEIVE :: freebsd.Shutdown_Method.RD
_SHUTDOWN_MANNER_SEND :: freebsd.Shutdown_Method.WR
_SHUTDOWN_MANNER_BOTH :: freebsd.Shutdown_Method.RDWR
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
@@ -272,7 +262,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
ptr: rawptr
len: freebsd.socklen_t
switch option {
#partial switch option {
case
.Reuse_Address,
.Keep_Alive,
@@ -344,7 +334,7 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
ptr = &int_value
len = size_of(int_value)
case:
unimplemented("set_option() option not yet implemented", loc)
return .Invalid_Option
}
real_socket := any_socket_to_socket(socket)

View File

@@ -21,28 +21,33 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:time"
import "core:sys/linux"
Socket_Option :: enum c.int {
Reuse_Address = c.int(linux.Socket_Option.REUSEADDR),
Keep_Alive = c.int(linux.Socket_Option.KEEPALIVE),
Out_Of_Bounds_Data_Inline = c.int(linux.Socket_Option.OOBINLINE),
TCP_Nodelay = c.int(linux.Socket_TCP_Option.NODELAY),
Linger = c.int(linux.Socket_Option.LINGER),
Receive_Buffer_Size = c.int(linux.Socket_Option.RCVBUF),
Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF),
Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO),
Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO),
Broadcast = c.int(linux.Socket_Option.BROADCAST),
}
_SOCKET_OPTION_BROADCAST :: linux.Socket_Option.BROADCAST
_SOCKET_OPTION_REUSE_ADDRESS :: linux.Socket_Option.REUSEADDR
_SOCKET_OPTION_KEEP_ALIVE :: linux.Socket_Option.KEEPALIVE
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: linux.Socket_Option.OOBINLINE
_SOCKET_OPTION_LINGER :: linux.Socket_Option.LINGER
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: linux.Socket_Option.RCVBUF
_SOCKET_OPTION_SEND_BUFFER_SIZE :: linux.Socket_Option.SNDBUF
_SOCKET_OPTION_RECEIVE_TIMEOUT :: linux.Socket_Option.RCVTIMEO
_SOCKET_OPTION_SEND_TIMEOUT :: linux.Socket_Option.SNDTIMEO
Shutdown_Manner :: enum c.int {
Receive = c.int(linux.Shutdown_How.RD),
Send = c.int(linux.Shutdown_How.WR),
Both = c.int(linux.Shutdown_How.RDWR),
}
_SOCKET_OPTION_TCP_NODELAY :: linux.Socket_TCP_Option.NODELAY
_SOCKET_OPTION_USE_LOOPBACK :: -1
_SOCKET_OPTION_REUSE_PORT :: -1
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
_SOCKET_OPTION_DONT_LINGER :: -1
_SHUTDOWN_MANNER_RECEIVE :: linux.Shutdown_How.RD
_SHUTDOWN_MANNER_SEND :: linux.Shutdown_How.WR
_SHUTDOWN_MANNER_BOTH :: linux.Shutdown_How.RDWR
// Wrappers and unwrappers for system-native types
@@ -347,7 +352,7 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
int_value: i32
timeval_value: linux.Time_Val
errno: linux.Errno
switch option {
#partial switch option {
case
.Reuse_Address,
.Keep_Alive,
@@ -400,10 +405,14 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
panic("set_option() value must be an integer here", loc)
}
errno = linux.setsockopt(os_sock, level, int(option), &int_value)
case:
return .Invalid_Socket
}
if errno != .NONE {
return _socket_option_error(errno)
}
return nil
}

105
core/net/socket_others.odin Normal file
View File

@@ -0,0 +1,105 @@
#+build !darwin
#+build !linux
#+build !freebsd
#+build !windows
#+build !netbsd
#+build !openbsd
#+private
package net
_SOCKET_OPTION_BROADCAST :: -1
_SOCKET_OPTION_REUSE_ADDRESS :: -1
_SOCKET_OPTION_KEEP_ALIVE :: -1
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: -1
_SOCKET_OPTION_LINGER :: -1
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: -1
_SOCKET_OPTION_SEND_BUFFER_SIZE :: -1
_SOCKET_OPTION_RECEIVE_TIMEOUT :: -1
_SOCKET_OPTION_SEND_TIMEOUT :: -1
_SOCKET_OPTION_TCP_NODELAY :: -1
_SOCKET_OPTION_USE_LOOPBACK :: -1
_SOCKET_OPTION_REUSE_PORT :: -1
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
_SOCKET_OPTION_DONT_LINGER :: -1
_SHUTDOWN_MANNER_RECEIVE :: -1
_SHUTDOWN_MANNER_SEND :: -1
_SHUTDOWN_MANNER_BOTH :: -1
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (sock: TCP_Socket, err: Network_Error) {
err = Create_Socket_Error.Network_Unreachable
return
}
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (sock: Any_Socket, err: Create_Socket_Error) {
err = .Network_Unreachable
return
}
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
err = .Network_Unreachable
return
}
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
err = Create_Socket_Error.Network_Unreachable
return
}
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
err = .Network_Unreachable
return
}
_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) {
err = .Network_Unreachable
return
}
_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
err = .Network_Unreachable
return
}
_close :: proc(skt: Any_Socket) {
}
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
err = .Network_Unreachable
return
}
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
err = .Network_Unreachable
return
}
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
err = .Network_Unreachable
return
}
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
err = .Network_Unreachable
return
}
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
err = .Network_Unreachable
return
}
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
return .Network_Unreachable
}
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
err = .Network_Unreachable
return
}

View File

@@ -1,4 +1,4 @@
#+build darwin
#+build darwin, netbsd, openbsd
package net
/*
@@ -20,28 +20,33 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:sys/posix"
import "core:time"
Socket_Option :: enum c.int {
Broadcast = c.int(posix.Sock_Option.BROADCAST),
Reuse_Address = c.int(posix.Sock_Option.REUSEADDR),
Keep_Alive = c.int(posix.Sock_Option.KEEPALIVE),
Out_Of_Bounds_Data_Inline = c.int(posix.Sock_Option.OOBINLINE),
TCP_Nodelay = c.int(posix.TCP_NODELAY),
Linger = c.int(posix.Sock_Option.LINGER),
Receive_Buffer_Size = c.int(posix.Sock_Option.RCVBUF),
Send_Buffer_Size = c.int(posix.Sock_Option.SNDBUF),
Receive_Timeout = c.int(posix.Sock_Option.RCVTIMEO),
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO),
}
_SOCKET_OPTION_BROADCAST :: posix.Sock_Option.BROADCAST
_SOCKET_OPTION_REUSE_ADDRESS :: posix.Sock_Option.REUSEADDR
_SOCKET_OPTION_KEEP_ALIVE :: posix.Sock_Option.KEEPALIVE
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: posix.Sock_Option.OOBINLINE
_SOCKET_OPTION_LINGER :: posix.Sock_Option.LINGER
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: posix.Sock_Option.RCVBUF
_SOCKET_OPTION_SEND_BUFFER_SIZE :: posix.Sock_Option.SNDBUF
_SOCKET_OPTION_RECEIVE_TIMEOUT :: posix.Sock_Option.RCVTIMEO
_SOCKET_OPTION_SEND_TIMEOUT :: posix.Sock_Option.SNDTIMEO
Shutdown_Manner :: enum c.int {
Receive = c.int(posix.SHUT_RD),
Send = c.int(posix.SHUT_WR),
Both = c.int(posix.SHUT_RDWR),
}
_SOCKET_OPTION_TCP_NODELAY :: posix.TCP_NODELAY
_SOCKET_OPTION_USE_LOOPBACK :: -1
_SOCKET_OPTION_REUSE_PORT :: -1
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: -1
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: -1
_SOCKET_OPTION_DONT_LINGER :: -1
_SHUTDOWN_MANNER_RECEIVE :: posix.SHUT_RD
_SHUTDOWN_MANNER_SEND :: posix.SHUT_WR
_SHUTDOWN_MANNER_BOTH :: posix.SHUT_RDWR
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
@@ -273,7 +278,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
ptr: rawptr
len: posix.socklen_t
switch option {
#partial switch option {
case
.Broadcast,
.Reuse_Address,
@@ -327,6 +332,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
}
ptr = &int_value
len = size_of(int_value)
case:
return .Invalid_Option
}
skt := any_socket_to_socket(s)

View File

@@ -24,59 +24,30 @@ import "core:c"
import win "core:sys/windows"
import "core:time"
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,
_SOCKET_OPTION_BROADCAST :: win.SO_BROADCAST
_SOCKET_OPTION_REUSE_ADDRESS :: win.SO_REUSEADDR
_SOCKET_OPTION_KEEP_ALIVE :: win.SO_KEEPALIVE
_SOCKET_OPTION_OUT_OF_BOUNDS_DATA_INLINE :: win.SO_OOBINLINE
_SOCKET_OPTION_LINGER :: win.SO_LINGER
_SOCKET_OPTION_RECEIVE_BUFFER_SIZE :: win.SO_RCVBUF
_SOCKET_OPTION_SEND_BUFFER_SIZE :: win.SO_SNDBUF
_SOCKET_OPTION_RECEIVE_TIMEOUT :: win.SO_RCVTIMEO
_SOCKET_OPTION_SEND_TIMEOUT :: win.SO_SNDTIMEO
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
_SOCKET_OPTION_TCP_NODELAY :: win.TCP_NODELAY
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
Keep_Alive = win.SO_KEEPALIVE,
_SOCKET_OPTION_USE_LOOPBACK :: -1
_SOCKET_OPTION_REUSE_PORT :: -1
_SOCKET_OPTION_NO_SIGPIPE_FROM_EPIPE :: -1
_SOCKET_OPTION_REUSE_PORT_LOAD_BALANCING :: -1
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
_SOCKET_OPTION_EXCLUSIVE_ADDR_USE :: win.SO_EXCLUSIVEADDRUSE
_SOCKET_OPTION_CONDITIONAL_ACCEPT :: win.SO_CONDITIONAL_ACCEPT
_SOCKET_OPTION_DONT_LINGER :: win.SO_DONTLINGER
// 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,
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
}
_SHUTDOWN_MANNER_RECEIVE :: win.SD_RECEIVE
_SHUTDOWN_MANNER_SEND :: win.SD_SEND
_SHUTDOWN_MANNER_BOTH :: win.SD_BOTH
@(init, private)
ensure_winsock_initialized :: proc "contextless" () {
@@ -322,7 +293,7 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
ptr: rawptr
len: c.int
switch option {
#partial switch option {
case
.Reuse_Address,
.Exclusive_Addr_Use,
@@ -383,6 +354,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
}
ptr = &int_value
len = size_of(int_value)
case:
return .Invalid_Option
}
socket := any_socket_to_socket(s)

View File

@@ -10,8 +10,6 @@
A test suite for `core:net`
*/
#+build !netbsd
#+build !openbsd
#+feature dynamic-literals
package test_core_net