Make more private.

This commit is contained in:
Jeroen van Rijn
2023-03-03 13:00:43 +01:00
parent 96ac405952
commit d5ea492ef5
16 changed files with 266 additions and 916 deletions
+13 -7
View File
@@ -1,3 +1,9 @@
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -7,15 +13,9 @@
List of contributors: List of contributors:
Tetralux: Initial implementation Tetralux: Initial implementation
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
Jeroen van Rijn: Cross platform unification, code style, IPv4 + IPv6 parsers, documentation. Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:strconv" import "core:strconv"
import "core:strings" import "core:strings"
import "core:fmt" import "core:fmt"
@@ -739,3 +739,9 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D
// If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range. // 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 return value, digit_bytes + prefix_bytes, digit_bytes >= 1
} }
// Returns an address for each interface that can be bound to.
get_network_interfaces :: proc() -> []Address {
// TODO
return nil
}
-71
View File
@@ -1,71 +0,0 @@
/*
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
}
-92
View File
@@ -1,92 +0,0 @@
/*
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
}
sockaddr_to_endpoint :: proc { sockaddr_basic_to_endpoint, sockaddr_storage_to_endpoint }
-69
View File
@@ -1,69 +0,0 @@
/*
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
View File
@@ -1,69 +0,0 @@
/*
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
}
+23 -11
View File
@@ -113,6 +113,7 @@ resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Ne
} }
unreachable() unreachable()
} }
resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) { resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
switch t in target { switch t in target {
@@ -139,6 +140,7 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net
} }
unreachable() unreachable()
} }
resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) { resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
switch t in target { switch t in target {
@@ -166,13 +168,18 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net
unreachable() unreachable()
} }
// `get_dns_records` uses OS-specific methods to query DNS records. /*
when ODIN_OS == .Windows { Performs a recursive DNS query for records of a particular type for the hostname using the OS.
get_dns_records_from_os :: get_dns_records_windows
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD { NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
get_dns_records_from_os :: get_dns_records_unix meaning that DNS queries for a hostname will resolve through CNAME records until an
} else { IP address is reached.
#panic("get_dns_records_from_os not implemented on this OS")
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
See `destroy_records`.
*/
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
return _get_dns_records_os(hostname, type, allocator)
} }
/* /*
@@ -182,6 +189,9 @@ when ODIN_OS == .Windows {
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, 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 meaning that DNS queries for a hostname will resolve through CNAME records until an
IP address is reached. IP address is reached.
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
See `destroy_records`.
*/ */
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { 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 context.allocator = allocator
@@ -771,14 +781,16 @@ parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil)
*/ */
parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) { parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
header_size_bytes :: 12 context.allocator = allocator
if len(response) < header_size_bytes {
HEADER_SIZE_BYTES :: 12
if len(response) < HEADER_SIZE_BYTES {
return return
} }
_records := make([dynamic]DNS_Record, 0) _records := make([dynamic]DNS_Record, 0)
dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:header_size_bytes]) dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES])
hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1]) hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
if !hdr.is_response { if !hdr.is_response {
return return
@@ -792,7 +804,7 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator
authority_count := int(dns_hdr_chunks[4]) authority_count := int(dns_hdr_chunks[4])
additional_count := int(dns_hdr_chunks[5]) additional_count := int(dns_hdr_chunks[5])
cur_idx := header_size_bytes cur_idx := HEADER_SIZE_BYTES
for _ in 0..<question_count { for _ in 0..<question_count {
if cur_idx == len(response) { if cur_idx == len(response) {
+9 -9
View File
@@ -1,4 +1,10 @@
//+build linux, darwin, freebsd, openbsd, !windows //+build linux, darwin
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -10,16 +16,10 @@
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:strings" import "core:strings"
get_dns_records_unix :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { @(private)
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
context.allocator = allocator context.allocator = allocator
if type != .SRV { if type != .SRV {
+9 -17
View File
@@ -1,4 +1,11 @@
//+build windows //+build windows
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -11,28 +18,13 @@
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:strings" import "core:strings"
import "core:mem" import "core:mem"
import win "core:sys/windows" import win "core:sys/windows"
/* @(private)
Performs a recursive DNS query for records of a particular type for the hostname. _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
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 context.allocator = allocator
host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
-186
View File
@@ -1,186 +0,0 @@
// +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"
Create_Socket_Error :: enum c.int {
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
No_Memory_Available_Available = c.int(os.ENOMEM),
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
}
Dial_Error :: enum c.int {
Port_Required = -1,
Address_In_Use = c.int(os.EADDRINUSE),
In_Progress = c.int(os.EINPROGRESS),
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
Refused = c.int(os.ECONNREFUSED),
Is_Listening_Socket = c.int(os.EACCES),
Already_Connected = c.int(os.EISCONN),
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Not_Socket = c.int(os.ENOTSOCK),
Timeout = c.int(os.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(os.EWOULDBLOCK),
}
Bind_Error :: enum c.int {
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
}
Listen_Error :: enum c.int {
Address_In_Use = c.int(os.EADDRINUSE),
Already_Connected = c.int(os.EISCONN),
No_Socket_Descriptors_Available = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
Not_Socket = c.int(os.ENOTSOCK),
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
}
Accept_Error :: enum c.int {
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
Reset = c.int(os.ECONNRESET),
Not_Listening = c.int(os.EINVAL),
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Not_Socket = c.int(os.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(os.EWOULDBLOCK),
}
TCP_Recv_Error :: enum c.int {
Shutdown = c.int(os.ESHUTDOWN),
Not_Connected = c.int(os.ENOTCONN),
// TODO(tetra): Is this error actually possible here?
Connection_Broken = c.int(os.ENETRESET),
Not_Socket = c.int(os.ENOTSOCK),
Aborted = c.int(os.ECONNABORTED),
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = c.int(os.ECONNRESET),
Offline = c.int(os.ENETDOWN),
Host_Unreachable = c.int(os.EHOSTUNREACH),
Interrupted = c.int(os.EINTR),
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
}
UDP_Recv_Error :: enum c.int {
Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated.
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send timeout duration passed before all data was sent.
// See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
}
// TODO
TCP_Send_Error :: enum c.int {
// TODO: merge with other errors?
Aborted = c.int(os.ECONNABORTED),
Connection_Closed = c.int(os.ECONNRESET),
Not_Connected = c.int(os.ENOTCONN),
Shutdown = c.int(os.ESHUTDOWN),
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(os.ENOBUFS),
Offline = c.int(os.ENETDOWN),
Host_Unreachable = c.int(os.EHOSTUNREACH),
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send timeout duration passed before all data was sent.
// See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
}
// TODO
UDP_Send_Error :: enum c.int {
Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
// TODO: not sure what the exact circumstances for this is yet
Network_Unreachable = c.int(os.ENETUNREACH),
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
// The send timeout duration passed before all data was sent.
// See Socket_Option.Send_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(os.EWOULDBLOCK),
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send queue was full.
// This is usually a transient issue.
//
// This also shouldn't normally happen on Linux, as data is dropped if it
// doesn't fit in the send queue.
No_Buffer_Space_Available = c.int(os.ENOBUFS),
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
}
Shutdown_Error :: enum c.int {
Aborted = c.int(os.ECONNABORTED),
Reset = c.int(os.ECONNRESET),
Offline = c.int(os.ENETDOWN),
Not_Connected = c.int(os.ENOTCONN),
Not_Socket = c.int(os.ENOTSOCK),
Invalid_Manner = c.int(os.EINVAL),
}
Socket_Option_Error :: enum c.int {
Offline = c.int(os.ENETDOWN),
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
Not_Socket = c.int(os.ENOTSOCK),
}
+7 -7
View File
@@ -1,4 +1,11 @@
package net
//+build darwin //+build darwin
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -9,15 +16,8 @@
Tetralux: Initial implementation Tetralux: Initial implementation
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
/*
TODO: Implement. Can probably use the (current) Linux implementation, 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. which will itself be switched over to talking to the kernel via NETLINK protocol once we have raw sockets.
*/ */
+11 -13
View File
@@ -1,4 +1,11 @@
//+build linux, darwin, openbsd, !windows package net
//+build linux
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -9,17 +16,8 @@
Tetralux: Initial implementation Tetralux: Initial implementation
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
/*
This file uses `getifaddrs` libc call to enumerate interfaces. 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. TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc.
*/ */
@@ -65,7 +63,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N
if ifaddr.address != nil { if ifaddr.address != nil {
switch int(ifaddr.address.sa_family) { switch int(ifaddr.address.sa_family) {
case os.AF_INET, os.AF_INET6: case os.AF_INET, os.AF_INET6:
address = sockaddr_to_endpoint(ifaddr.address).address address = _sockaddr_basic_to_endpoint(ifaddr.address).address
case os.AF_PACKET: case os.AF_PACKET:
/* /*
@@ -87,7 +85,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N
if ifaddr.netmask != nil { if ifaddr.netmask != nil {
switch int(ifaddr.netmask.sa_family) { switch int(ifaddr.netmask.sa_family) {
case os.AF_INET, os.AF_INET6: case os.AF_INET, os.AF_INET6:
netmask = Netmask(sockaddr_to_endpoint(ifaddr.netmask).address) netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
case: case:
} }
} }
@@ -95,7 +93,7 @@ enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []N
if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags { if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
switch int(ifaddr.broadcast_or_dest.sa_family) { switch int(ifaddr.broadcast_or_dest.sa_family) {
case os.AF_INET, os.AF_INET6: case os.AF_INET, os.AF_INET6:
broadcast := sockaddr_to_endpoint(ifaddr.broadcast_or_dest).address broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
append(&iface.multicast, broadcast) append(&iface.multicast, broadcast)
case: case:
} }
+58 -11
View File
@@ -1,4 +1,11 @@
package net
// +build darwin // +build darwin
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -11,12 +18,6 @@
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:c" import "core:c"
import "core:os" import "core:os"
import "core:time" import "core:time"
@@ -68,7 +69,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
// use the same address immediately. // use the same address immediately.
_ = set_option(skt, .Reuse_Address, true) _ = set_option(skt, .Reuse_Address, true)
sockaddr := endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
if res != os.ERROR_NONE { if res != os.ERROR_NONE {
err = Dial_Error(res) err = Dial_Error(res)
@@ -79,7 +80,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
} }
bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
sockaddr := endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
if res != os.ERROR_NONE { if res != os.ERROR_NONE {
@@ -145,7 +146,7 @@ accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, e
return return
} }
client = TCP_Socket(client_sock) client = TCP_Socket(client_sock)
source = sockaddr_to_endpoint(&sockaddr) source = _sockaddr_to_endpoint(&sockaddr)
return return
} }
@@ -180,7 +181,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo
} }
bytes_read = int(res) bytes_read = int(res)
remote_endpoint = sockaddr_to_endpoint(&from) remote_endpoint = _sockaddr_to_endpoint(&from)
return return
} }
@@ -204,7 +205,7 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw
} }
send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
toaddr := endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) { for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written) limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit] remaining := buf[bytes_written:][:limit]
@@ -324,3 +325,49 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal
return nil 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
}
+79 -11
View File
@@ -1,4 +1,11 @@
package net
// +build linux // +build linux
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -11,12 +18,6 @@
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:c" import "core:c"
import "core:os" import "core:os"
import "core:time" import "core:time"
@@ -68,7 +69,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
// use the same address immediately. // use the same address immediately.
_ = set_option(skt, .Reuse_Address, true) _ = set_option(skt, .Reuse_Address, true)
sockaddr := endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
if res != os.ERROR_NONE { if res != os.ERROR_NONE {
err = Dial_Error(res) err = Dial_Error(res)
@@ -84,7 +85,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
sockaddr := endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr)) res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
if res != os.ERROR_NONE { if res != os.ERROR_NONE {
@@ -152,7 +153,7 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client:
return return
} }
client = TCP_Socket(client_sock) client = TCP_Socket(client_sock)
source = sockaddr_to_endpoint(&sockaddr) source = _sockaddr_storage_to_endpoint(&sockaddr)
if options.no_delay { if options.no_delay {
_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
} }
@@ -197,7 +198,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo
} }
bytes_read = int(res) bytes_read = int(res)
remote_endpoint = sockaddr_to_endpoint(&from) remote_endpoint = _sockaddr_storage_to_endpoint(&from)
if bytes_read > len(buf) { if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message. // NOTE(tetra): The buffer has been filled, with a partial message.
@@ -233,7 +234,7 @@ send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Netw
// Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. // 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. // 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) { send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
toaddr := endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
res, os_err := os.sendto(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr)) res, os_err := os.sendto(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr))
if os_err != os.ERROR_NONE { if os_err != os.ERROR_NONE {
err = UDP_Send_Error(os_err) err = UDP_Send_Error(os_err)
@@ -349,3 +350,70 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal
return nil return nil
} }
@(private)
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
switch a in ep.address {
case IP4_Address:
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
sin_port = u16be(ep.port),
sin_addr = transmute(os.in_addr) a,
sin_family = u16(os.AF_INET),
}
return
case IP6_Address:
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
sin6_port = u16be(ep.port),
sin6_addr = transmute(os.in6_addr) a,
sin6_family = u16(os.AF_INET6),
}
return
}
unreachable()
}
@(private)
_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
switch native_addr.ss_family {
case u16(os.AF_INET):
addr := cast(^os.sockaddr_in) native_addr
port := int(addr.sin_port)
ep = Endpoint {
address = IP4_Address(transmute([4]byte) addr.sin_addr),
port = port,
}
case u16(os.AF_INET6):
addr := cast(^os.sockaddr_in6) native_addr
port := int(addr.sin6_port)
ep = Endpoint {
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
port = port,
}
case:
panic("native_addr is neither IP4 or IP6 address")
}
return
}
@(private)
_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
switch native_addr.sa_family {
case u16(os.AF_INET):
addr := cast(^os.sockaddr_in) native_addr
port := int(addr.sin_port)
ep = Endpoint {
address = IP4_Address(transmute([4]byte) addr.sin_addr),
port = port,
}
case u16(os.AF_INET6):
addr := cast(^os.sockaddr_in6) native_addr
port := int(addr.sin6_port)
ep = Endpoint {
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
port = port,
}
case:
panic("native_addr is neither IP4 or IP6 address")
}
return
}
-331
View File
@@ -1,331 +0,0 @@
// +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 :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
c_type, c_protocol, c_family: int
switch family {
case .IP4: c_family = os.AF_INET
case .IP6: c_family = os.AF_INET6
case:
unreachable()
}
switch protocol {
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
case:
unreachable()
}
sock, ok := os.socket(c_family, c_type, c_protocol)
if ok != os.ERROR_NONE {
err = Create_Socket_Error(ok)
return
}
switch protocol {
case .TCP: return TCP_Socket(sock), nil
case .UDP: return UDP_Socket(sock), nil
case:
unreachable()
}
}
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
return 0, .Port_Required
}
family := family_from_endpoint(endpoint)
sock := create_socket(family, .TCP) or_return
skt = sock.(TCP_Socket)
// NOTE(tetra): This is so that if we crash while the socket is open, we can
// bypass the cooldown period, and allow the next run of the program to
// use the same address immediately.
_ = set_option(skt, .Reuse_Address, true)
sockaddr := endpoint_to_sockaddr(endpoint)
res := os.connect(Platform_Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
if res != os.ERROR_NONE {
err = Dial_Error(res)
return
}
return
}
bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
sockaddr := endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt)
res := os.bind(Platform_Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
if res != os.ERROR_NONE {
err = Bind_Error(res)
}
return
}
// This type of socket becomes bound when you try to send data.
// This is likely what you want if you want to send data unsolicited.
//
// This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
make_unbound_udp_socket :: proc(family: Address_Family) -> (skt: UDP_Socket, err: Network_Error) {
sock := create_socket(family, .UDP) or_return
skt = sock.(UDP_Socket)
return
}
// This type of socket is bound immediately, which enables it to receive data on the port.
// Since it's UDP, it's also able to send data without receiving any first.
//
// This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
//
// The bound_address is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (skt: UDP_Socket, err: Network_Error) {
skt = make_unbound_udp_socket(family_from_address(bound_address)) or_return
bind(skt, {bound_address, port}) or_return
return
}
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && i32(backlog) < max(i32))
family := family_from_endpoint(interface_endpoint)
sock := create_socket(family, .TCP) or_return
skt = sock.(TCP_Socket)
// NOTE(tetra): This is so that if we crash while the socket is open, we can
// bypass the cooldown period, and allow the next run of the program to
// use the same address immediately.
//
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
set_option(sock, .Reuse_Address, true) or_return
bind(sock, interface_endpoint) or_return
res := os.listen(Platform_Socket(skt), backlog)
if res != os.ERROR_NONE {
err = Listen_Error(res)
return
}
return
}
accept_tcp :: proc(sock: TCP_Socket) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
sockaddr: os.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
client_sock, ok := os.accept(Platform_Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
if ok != os.ERROR_NONE {
err = Accept_Error(ok)
return
}
client = TCP_Socket(client_sock)
source = sockaddr_to_endpoint(&sockaddr)
return
}
close :: proc(skt: Any_Socket) {
s := any_socket_to_socket(skt)
os.close(os.Handle(Platform_Socket(s)))
}
recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
if len(buf) <= 0 {
return
}
res, ok := os.recv(Platform_Socket(skt), buf, 0)
if ok != os.ERROR_NONE {
err = TCP_Recv_Error(ok)
return
}
return int(res), nil
}
recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
if len(buf) <= 0 {
return
}
from: os.SOCKADDR_STORAGE_LH
fromsize := c.int(size_of(from))
res, ok := os.recvfrom(Platform_Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
if ok != os.ERROR_NONE {
err = UDP_Recv_Error(ok)
return
}
bytes_read = int(res)
remote_endpoint = sockaddr_to_endpoint(&from)
return
}
recv :: proc{recv_tcp, recv_udp}
// Repeatedly sends data until the entire buffer is sent.
// If a send fails before all data is sent, returns the amount
// sent up to that point.
send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res, ok := os.send(Platform_Socket(skt), remaining, 0)
if ok != os.ERROR_NONE {
err = TCP_Send_Error(ok)
return
}
bytes_written += int(res)
}
return
}
send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
toaddr := endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res, ok := os.sendto(Platform_Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, size_of(toaddr))
if ok != os.ERROR_NONE {
err = UDP_Send_Error(ok)
return
}
bytes_written += int(res)
}
return
}
send :: proc{send_tcp, send_udp}
Shutdown_Manner :: enum c.int {
Receive = c.int(os.SHUT_RD),
Send = c.int(os.SHUT_WR),
Both = c.int(os.SHUT_RDWR),
}
shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
s := any_socket_to_socket(skt)
res := os.shutdown(Platform_Socket(s), int(manner))
if res != os.ERROR_NONE {
return Shutdown_Error(res)
}
return
}
Socket_Option :: enum c.int {
Reuse_Address = c.int(os.SO_REUSEADDR),
Keep_Alive = c.int(os.SO_KEEPALIVE),
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
TCP_Nodelay = c.int(os.TCP_NODELAY),
Linger = c.int(os.SO_LINGER),
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
Send_Buffer_Size = c.int(os.SO_SNDBUF),
Receive_Timeout = c.int(os.SO_RCVTIMEO),
Send_Timeout = c.int(os.SO_SNDTIMEO),
}
set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
// it _has_ to be a b32.
// I haven't tested if you can give more than that.
bool_value: b32
int_value: i32
timeval_value: os.Timeval
ptr: rawptr
len: os.socklen_t
switch option {
case
.Reuse_Address,
.Keep_Alive,
.Out_Of_Bounds_Data_Inline,
.TCP_Nodelay:
// TODO: verify whether these are options or not on Linux
// .Broadcast,
// .Conditional_Accept,
// .Dont_Linger:
switch x in value {
case bool, b8:
x2 := x
bool_value = b32((^bool)(&x2)^)
case b16:
bool_value = b32(x)
case b32:
bool_value = b32(x)
case b64:
bool_value = b32(x)
case:
panic("set_option() value must be a boolean here", loc)
}
ptr = &bool_value
len = size_of(bool_value)
case
.Linger,
.Send_Timeout,
.Receive_Timeout:
t, ok := value.(time.Duration)
if !ok do panic("set_option() value must be a time.Duration here", loc)
nanos := time.duration_nanoseconds(t)
timeval_value.nanoseconds = int(nanos % 1e9)
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
ptr = &timeval_value
len = size_of(timeval_value)
case
.Receive_Buffer_Size,
.Send_Buffer_Size:
// TODO: check for out of range values and return .Value_Out_Of_Range?
switch i in value {
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
case:
panic("set_option() value must be an integer here", loc)
}
ptr = &int_value
len = size_of(int_value)
}
skt := any_socket_to_socket(s)
res := os.setsockopt(Platform_Socket(skt), int(level), int(option), ptr, len)
if res != os.ERROR_NONE {
return Socket_Option_Error(res)
}
return nil
}
+50 -5
View File
@@ -74,7 +74,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
// use the same address immediately. // use the same address immediately.
_ = set_option(skt, .Reuse_Address, true) _ = set_option(skt, .Reuse_Address, true)
sockaddr := endpoint_to_sockaddr(endpoint) sockaddr := _endpoint_to_sockaddr(endpoint)
res := win.connect(Platform_Socket(skt), &sockaddr, size_of(sockaddr)) res := win.connect(Platform_Socket(skt), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
err = Dial_Error(win.WSAGetLastError()) err = Dial_Error(win.WSAGetLastError())
@@ -89,7 +89,7 @@ dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_option
} }
bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
sockaddr := endpoint_to_sockaddr(ep) sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt) s := any_socket_to_socket(skt)
res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr)) res := win.bind(Platform_Socket(s), &sockaddr, size_of(sockaddr))
if res < 0 { if res < 0 {
@@ -161,7 +161,7 @@ accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client:
return return
} }
client = TCP_Socket(client_sock) client = TCP_Socket(client_sock)
source = sockaddr_to_endpoint(&sockaddr) source = _sockaddr_to_endpoint(&sockaddr)
if options.no_delay { if options.no_delay {
_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored _ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
} }
@@ -201,7 +201,7 @@ recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpo
} }
bytes_read = int(res) bytes_read = int(res)
remote_endpoint = sockaddr_to_endpoint(&from) remote_endpoint = _sockaddr_to_endpoint(&from)
return return
} }
@@ -235,7 +235,7 @@ send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
err = .Message_Too_Long err = .Message_Too_Long
return return
} }
toaddr := endpoint_to_sockaddr(to) toaddr := _endpoint_to_sockaddr(to)
res := win.sendto(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) res := win.sendto(Platform_Socket(skt), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
if res < 0 { if res < 0 {
err = UDP_Send_Error(win.WSAGetLastError()) err = UDP_Send_Error(win.WSAGetLastError())
@@ -338,3 +338,48 @@ set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #cal
return nil 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
}
+6 -6
View File
@@ -1,3 +1,9 @@
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
/* /*
Copyright 2022 Tetralux <tetraluxonpc@gmail.com> Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
Copyright 2022 Colin Davidson <colrdavidson@gmail.com> Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
@@ -10,12 +16,6 @@
Jeroen van Rijn: Cross platform unification, code style, documentation Jeroen van Rijn: Cross platform unification, code style, documentation
*/ */
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
For other protocols and their features, see subdirectories of this package.
*/
package net
import "core:strings" import "core:strings"
import "core:strconv" import "core:strconv"
import "core:unicode/utf8" import "core:unicode/utf8"