Cross-platform Berkeley Sockets, `DNS` resolution and associated procedures. 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.

Collection Info

View Source
Collection
core
Path
net
Entries
162

Source Files

(hidden platform specific files)

Constants

17

DEFAULT_DIGIT_BASES #

Source
DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex}

DEFAULT_TCP_OPTIONS #

Source
DEFAULT_TCP_OPTIONS :: TCP_Options{no_delay = ODIN_NET_TCP_NODELAY_DEFAULT}

DNS_PACKET_MIN_LEN #

Source
DNS_PACKET_MIN_LEN :: (size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)

IP4_mDNS_Broadcast #

Source
IP4_mDNS_Broadcast :: Endpoint{address = IP4_Address{224, 0, 0, 251}, port = 5353}

IP6_Loopback #

Source
IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}

IP6_mDNS_Broadcast #

Source
IP6_mDNS_Broadcast :: Endpoint{address = IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353}

IPv6_MIN_STRING_LENGTH #

Source
IPv6_MIN_STRING_LENGTH :: 2

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)

MAX_INTERFACE_ENUMERATION_TRIES #

Source
MAX_INTERFACE_ENUMERATION_TRIES :: 3

ODIN_NET_TCP_NODELAY_DEFAULT #

Source
ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)

Config Values

1

ODIN_NET_TCP_NODELAY_DEFAULT #

Source
ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)

Types

63

DNS_Record_Base #

Source
DNS_Record_Base :: DNS_Record_Base

Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.

DNS_Record_CNAME #

Source
DNS_Record_CNAME :: DNS_Record_CNAME

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_MX #

Source
DNS_Record_MX :: DNS_Record_MX

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_NS #

Source
DNS_Record_NS :: DNS_Record_NS

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_SRV #

Source
DNS_Record_SRV :: DNS_Record_SRV

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_TXT #

Source
DNS_Record_TXT :: DNS_Record_TXT

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.

Socket #

Source
Socket :: distinct Socket

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.

Procedures

72

address_to_string_allocator #

Source
address_to_string_allocator :: proc(addr: Address, allocator := context.temp_allocator) -> string {…}

Returns a temporarily-allocated string representation of the address. See RFC 5952 section 4 for IPv6 representation recommendations.

address_to_string_builder #

Source
address_to_string_builder :: proc(addr: Address, b: ^Builder) -> string {…}

Returns a string representation of the address using a `strings.Builder`. See RFC 5952 section 4 for IPv6 representation recommendations.

aton #

Source
aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only: bool = false) -> (addr: Address, ok: bool) {…}

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.

decode_hostname #

Source
decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {…}

destroy_dns_records #

Source
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {…}

`records` slice is also destroyed.

destroy_interfaces #

Source
destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) {…}

`destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`.

dial_tcp_from_address_and_port #

Source
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options: TCP_Options = DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {…}

Dial from an Address. Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`

dial_tcp_from_host #

Source
dial_tcp_from_host :: proc(host: Host, options: TCP_Options = DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {…}

Expects the `host` as Host. Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`

dial_tcp_from_host_or_endpoint #

Source
dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options: TCP_Options = DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {…}

Expects the `target` as a Host_OrEndpoint. Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`. Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`

dial_tcp_from_hostname_and_port_string #

Source
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options: TCP_Options = DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {…}

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 `dial_tcp_from_host_or_endpoint`. Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`

dial_tcp_from_hostname_with_port_override #

Source
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options: TCP_Options = DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {…}

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. Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`

endpoint_to_string_allocator #

Source
endpoint_to_string_allocator :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string {…}

Returns a temporarily-allocated string representation of the endpoint. If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively.

endpoint_to_string_builder #

Source
endpoint_to_string_builder :: proc(ep: Endpoint, b: ^Builder) -> string {…}

Returns a string representation of the endpoint using a `strings.Builder`. If there's a port, uses the `ip4address:port` or `[ip6address]:port` format, respectively.

enumerate_interfaces #

Source
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {…}

`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.

get_dns_records_from_nameservers #

Source
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) {…}

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. 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 #

Source
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {…}

Performs a recursive DNS query for records of a particular type for the hostname using the OS. NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf, meaning that DNS queries for a hostname will resolve through CNAME records until an IP address is reached. IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough! See `destroy_records`.

get_network_interfaces #

Source
get_network_interfaces :: proc() -> []Address {…}

Returns an address for each interface that can be bound to.

init_dns_configuration #

Source
init_dns_configuration :: proc() {…}

join_port_allocator #

Source
join_port_allocator :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string {…}

Joins an address or hostname with a port, allocated using an Allocator.

join_port_builder #

Source
join_port_builder :: proc(address_or_host: string, port: int, b: ^Builder) -> string {…}

Joins an address or hostname with a port, allocated using a `strings.Builder`.

last_platform_error #

Source
@(require_results)
last_platform_error :: proc() -> i32 {…}

Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough. Platforms specific returns: - Darwin: `posix.Errno` (`core:sys/posix`) - Linux: `linux.Errno` (`core:sys/linux`) - FreeBSD: `freebsd.Errno` (`core:sys/freebsd`) - Windows: `windows.System_Error` (`core:sys/windows`)

last_platform_error_string #

Source
@(require_results)
last_platform_error_string :: proc() -> string {…}

Retrieve a stringified version of the last platform error.

listen_tcp #

Source
listen_tcp :: proc(interface_endpoint: Endpoint, backlog: int = 1000) -> (socket: TCP_Socket, err: Network_Error) {…}

Creates a TCP socket and starts listening on the given endpoint. Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error`

make_bound_udp_socket #

Source
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {…}

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. Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error`

make_unbound_udp_socket #

Source
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) {…}

This type of socket becomes bound when you try to send data. It is likely what you want if you want to send data unsolicited. This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.

parse_address #

Source
parse_address :: proc(address_and_maybe_port: string, non_decimal_address: bool = false) -> Address {…}

Try parsing as an IPv6 address. If it's determined not to be, try as an IPv4 address, optionally in non-decimal format.

parse_hostname_or_endpoint #

Source
parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) {…}

Takes a string consisting of a hostname or IP address, and an optional port, and return the component parts in a useful form.

parse_ip_component #

Source
parse_ip_component :: proc(input: string, max_value: u64 = u64(max(u32)), bases: bit_set[Digit_Parse_Base] = DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) {…}

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_ip4_address #

Source
parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal: bool = false) -> (addr: IP4_Address, ok: bool) {…}

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 `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255.

parse_resolv_conf #

Source
parse_resolv_conf :: proc(resolv_str: string, allocator := context.allocator) -> (name_servers: []Endpoint) {…}

physical_address_to_string #

Source
physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) {…}

Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string.

recv_any #

Source
recv_any :: proc(socket: Any_Socket, buf: []u8) -> (bytes_read: int, remote_endpoint: Maybe($T=Endpoint), err: Network_Error) {…}

Receive data into a buffer from any socket. Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it will always return `nil`. Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`. If no error occurs, `recv_any` returns the number of bytes received and `buf` will contain this data received. If the connection has been gracefully closed, the return value is `0, nil, nil` (0 bytes read and no error).

recv_tcp #

Source
recv_tcp :: proc(socket: TCP_Socket, buf: []u8) -> (bytes_read: int, err: TCP_Recv_Error) {…}

Receive data into a buffer from a TCP socket. If no error occurs, `recv_tcp` returns the number of bytes received and `buf` will contain this data received. If the connection has been gracefully closed, the return value is `0, nil` (0 bytes read and no error).

recv_udp #

Source
recv_udp :: proc(socket: UDP_Socket, buf: []u8) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {…}

Receive data into a buffer from a UDP socket. If no error occurs, `recv_udp` returns the number of bytes received and `buf` will contain this data received. If the "connection" has been gracefully closed, the return value is `0, nil` (0 bytes read and no error).

resolve #

Source
resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {…}

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.

send_any #

Source
send_any :: proc(socket: Any_Socket, buf: []u8, to: Maybe($T=Endpoint) = nil) -> (bytes_written: int, err: Network_Error) {…}

Sends data over the socket. Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error`

send_tcp #

Source
send_tcp :: proc(socket: TCP_Socket, buf: []u8) -> (bytes_written: int, err: TCP_Send_Error) {…}

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_udp #

Source
send_udp :: proc(socket: UDP_Socket, buf: []u8, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {…}

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.

split_port #

Source
split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) {…}

Takes an endpoint string and returns its parts. Returns ok=false if port is not a number.

Procedure Groups

7

Variables

2

dns_configuration #

Source
dns_configuration: DNS_Configuration = DNS_Configuration{resolv_conf = "", hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts"}