Merge pull request #5007 from laytan/net-errors-overhaul

net: rework errors to be cross-platform
This commit is contained in:
Jeroen van Rijn
2025-04-05 17:53:10 +02:00
committed by GitHub
19 changed files with 1458 additions and 914 deletions

View File

@@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
Maybe :: runtime.Maybe
Network_Error :: union #shared_nil {
General_Error,
Platform_Error,
Create_Socket_Error,
Dial_Error,
Listen_Error,
@@ -65,6 +63,7 @@ Network_Error :: union #shared_nil {
TCP_Recv_Error,
UDP_Recv_Error,
Shutdown_Error,
Interfaces_Error,
Socket_Option_Error,
Set_Blocking_Error,
Parse_Endpoint_Error,
@@ -74,14 +73,13 @@ Network_Error :: union #shared_nil {
#assert(size_of(Network_Error) == 8)
General_Error :: enum u32 {
None = 0,
Unable_To_Enumerate_Network_Interfaces = 1,
Interfaces_Error :: enum u32 {
None,
Unable_To_Enumerate_Network_Interfaces,
Allocation_Failure,
Unknown,
}
// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
Platform_Error :: enum u32 {}
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,

275
core/net/errors.odin Normal file
View File

@@ -0,0 +1,275 @@
package net
/*
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`)
*/
@(require_results)
last_platform_error :: proc() -> i32 {
return _last_platform_error()
}
/*
Retrieve a stringified version of the last platform error.
*/
@(require_results)
last_platform_error_string :: proc() -> string {
return _last_platform_error_string()
}
set_last_platform_error :: proc(err: i32) {
_set_last_platform_error(err)
}
Create_Socket_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid/unsupported family or protocol.
Invalid_Argument,
// The user has no permission to create a socket of this type and/or protocol.
Insufficient_Permissions,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Dial_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid endpoint and/or options.
Invalid_Argument,
// An attempt was made to connect to a broadcast socket on a socket that doesn't support it.
Broadcast_Not_Supported,
// The socket is already connected.
Already_Connected,
// The socket is already in the progress of making a connection.
Already_Connecting,
// The address is already in use.
Address_In_Use,
// Could not reach the remote host.
Host_Unreachable,
// The remote host refused the connection or isn't listening.
Refused,
// The connection was reset by the remote host.
Reset,
// Timed out before making a connection.
Timeout,
// Non-blocking socket that would need to block waiting to connect.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Endpoint given without a port, which is required.
Port_Required,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Bind_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or endpoint, or invalid combination of the two.
Invalid_Argument,
// The socket is already bound to an address.
Already_Bound,
// The address is protected and the current user has insufficient permissions to access it.
Insufficient_Permissions_For_Address,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Listen_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// The socket or backlog is invalid.
Invalid_Argument,
// The socket is valid, but does not support listening.
Unsupported_Socket,
// The socket is already connected.
Already_Connected,
// The address is already in use.
Address_In_Use,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Accept_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket, or options.
Invalid_Argument,
// The given socket does not support accepting connections.
Unsupported_Socket,
// accept called on a socket which is not listening.
Not_Listening,
// A connection arrived but was closed while in the listen queue.
Aborted,
// Timed out before being able to accept a connection.
Timeout,
// Non-blocking socket that would need to block waiting for a connection.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// The socket is not connected.
Not_Connected,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Recv_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// "Connection" was refused by remote, or closed/broken/shutdown while receiving data.
Connection_Refused,
// Timed out before being able to receive any data.
Timeout,
// Non-blocking socket that would need to block waiting on data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded.
Excess_Truncated,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
TCP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Connection was closed/broken/shutdown while receiving data.
Connection_Closed,
// The socket is not connected.
Not_Connected,
// Could not reach the remote host.
Host_Unreachable,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
UDP_Send_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Invalid socket or buffer given.
Invalid_Argument,
// Could not reach the remote host.
Host_Unreachable,
// "Connection" was refused by remote, or closed/broken/shutdown while sending data.
Connection_Refused,
// Timed out before being able to send any data.
Timeout,
// Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
Would_Block,
// Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
Interrupted,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Shutdown_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid or not connected, or the manner given is invalid.
Invalid_Argument,
// Connection was closed/aborted/shutdown.
Connection_Closed,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Socket_Option_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
Insufficient_Resources,
// Socket is invalid, not connected, or the connection has been closed/reset/shutdown.
Invalid_Socket,
// Unknown or unsupported option for the socket.
Invalid_Option,
// Invalid level or value.
Invalid_Value,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}
Set_Blocking_Error :: enum i32 {
None,
// No network connection, or the network stack is not initialized.
Network_Unreachable,
// Socket is invalid.
Invalid_Argument,
// An error unable to be categorized in above categories, `last_platform_error` may have more info.
Unknown,
}

View File

@@ -20,192 +20,232 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import "core:sys/posix"
@(private)
ESHUTDOWN :: 58
Create_Socket_Error :: enum c.int {
None = 0,
Family_Not_Supported_For_This_Socket = c.int(posix.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(posix.EMFILE),
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
No_Memory_Available = c.int(posix.ENOMEM),
Protocol_Unsupported_By_System = c.int(posix.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(posix.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(posix.EPROTONOSUPPORT),
_last_platform_error :: proc() -> i32 {
return i32(posix.errno())
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1, // Attempted to dial an endpointing without a port being set.
Address_In_Use = c.int(posix.EADDRINUSE),
In_Progress = c.int(posix.EINPROGRESS),
Cannot_Use_Any_Address = c.int(posix.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(posix.EAFNOSUPPORT),
Refused = c.int(posix.ECONNREFUSED),
Is_Listening_Socket = c.int(posix.EACCES),
Already_Connected = c.int(posix.EISCONN),
Network_Unreachable = c.int(posix.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(posix.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
Not_Socket = c.int(posix.ENOTSOCK),
Timeout = c.int(posix.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(posix.EWOULDBLOCK),
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(posix.errno())
return description
}
Bind_Error :: enum c.int {
None = 0,
Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access.
Address_In_Use = c.int(posix.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(posix.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(posix.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(posix.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(posix.ENOBUFS), // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
posix.errno(posix.Errno(err))
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(posix.EADDRINUSE),
Already_Connected = c.int(posix.EISCONN),
No_Socket_Descriptors_Available = c.int(posix.EMFILE),
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
Nonlocal_Address = c.int(posix.EADDRNOTAVAIL),
Not_Socket = c.int(posix.ENOTSOCK),
Listening_Not_Supported_For_This_Socket = c.int(posix.EOPNOTSUPP),
_create_socket_error :: proc() -> Create_Socket_Error {
#partial switch posix.errno() {
case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
Reset = c.int(posix.ECONNRESET),
Not_Listening = c.int(posix.EINVAL),
No_Socket_Descriptors_Available_For_Client_Socket = c.int(posix.EMFILE),
No_Buffer_Space_Available = c.int(posix.ENOBUFS),
Not_Socket = c.int(posix.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(posix.EOPNOTSUPP),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(posix.EWOULDBLOCK),
_dial_error :: proc() -> Dial_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETDOWN:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Shutdown = ESHUTDOWN,
Not_Connected = c.int(posix.ENOTCONN),
// TODO(tetra): Is this error actually possible here?
Connection_Broken = c.int(posix.ENETRESET),
Not_Socket = c.int(posix.ENOTSOCK),
Aborted = c.int(posix.ECONNABORTED),
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = c.int(posix.ECONNRESET),
Offline = c.int(posix.ENETDOWN),
Host_Unreachable = c.int(posix.EHOSTUNREACH),
Interrupted = c.int(posix.EINTR),
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(posix.EWOULDBLOCK),
_bind_error :: proc() -> Bind_Error {
#partial switch posix.errno() {
case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .EINVAL:
return .Already_Bound
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Buffer_Too_Small = c.int(posix.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(posix.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(posix.EWOULDBLOCK),
Socket_Not_Bound = c.int(posix.EINVAL), // The socket must be bound for this operation, but isn't.
_listen_error :: proc() -> Listen_Error {
#partial switch posix.errno() {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = c.int(posix.ECONNABORTED),
Connection_Closed = c.int(posix.ECONNRESET),
Not_Connected = c.int(posix.ENOTCONN),
Shutdown = 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(posix.ENOBUFS),
Offline = c.int(posix.ENETDOWN),
Host_Unreachable = c.int(posix.EHOSTUNREACH),
Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
Timeout = c.int(posix.EWOULDBLOCK),
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
_accept_error :: proc() -> Accept_Error {
#partial switch posix.errno() {
case .EMFILE, .ENFILE, .ENOMEM:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EOPNOTSUPP:
return .Unsupported_Socket
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO
UDP_Send_Error :: enum c.int {
None = 0,
Message_Too_Long = c.int(posix.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
// TODO: not sure what the exact circumstances for this is yet
Network_Unreachable = c.int(posix.ENETUNREACH),
No_Outbound_Ports_Available = c.int(posix.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(posix.EWOULDBLOCK),
Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(posix.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(posix.ENOBUFS),
No_Memory_Available = c.int(posix.ENOMEM), // No memory was available to properly manage the send queue.
_tcp_recv_error :: proc() -> TCP_Recv_Error {
#partial switch posix.errno() {
case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Manner :: enum c.int {
Receive = c.int(posix.SHUT_RD),
Send = c.int(posix.SHUT_WR),
Both = c.int(posix.SHUT_RDWR),
_udp_recv_error :: proc() -> UDP_Recv_Error {
#partial switch posix.errno() {
case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = c.int(posix.ECONNABORTED),
Reset = c.int(posix.ECONNRESET),
Offline = c.int(posix.ENETDOWN),
Not_Connected = c.int(posix.ENOTCONN),
Not_Socket = c.int(posix.ENOTSOCK),
Invalid_Manner = c.int(posix.EINVAL),
_tcp_send_error :: proc() -> TCP_Send_Error {
#partial switch posix.errno() {
case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Offline = c.int(posix.ENETDOWN),
Timeout_When_Keepalive_Set = c.int(posix.ENETRESET),
Invalid_Option_For_Socket = c.int(posix.ENOPROTOOPT),
Reset_When_Keepalive_Set = c.int(posix.ENOTCONN),
Not_Socket = c.int(posix.ENOTSOCK),
_udp_send_error :: proc() -> UDP_Send_Error {
#partial switch posix.errno() {
case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .ENETDOWN, .ENETUNREACH:
return .Network_Unreachable
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
// TODO: Add errors for `set_blocking`
_shutdown_error :: proc() -> Shutdown_Error {
#partial switch posix.errno() {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc() -> Socket_Option_Error {
#partial switch posix.errno() {
case .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EISCONN:
return .Invalid_Socket
case .EINVAL, .ENOPROTOOPT:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch posix.errno() {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -20,198 +20,267 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import "core:sys/freebsd"
Create_Socket_Error :: enum c.int {
None = 0,
Access_Denied = cast(c.int)freebsd.Errno.EACCES,
Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM,
Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT,
Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE,
@(private="file", thread_local)
_last_error: freebsd.Errno
_last_platform_error :: proc() -> i32 {
return i32(_last_error)
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT,
Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED,
// `Refused` alias for `core:net` tests.
// The above default name `Refused_By_Remote_Host` is more explicit.
Refused = Refused_By_Remote_Host,
Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET,
Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH,
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY,
Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES,
Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
// NOTE: There are additional connect() error possibilities, but they are
// strictly for addresses in the UNIX domain.
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(_last_error)
return description
}
Bind_Error :: enum c.int {
None = 0,
Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
// NOTE: bind() can also return EINVAL if the underlying `addrlen` is an
// invalid length for the address family. This shouldn't happen for the net
// package, but it's worth noting.
Already_Bound = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
Protected_Address = cast(c.int)freebsd.Errno.EACCES,
Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
// NOTE: There are additional bind() error possibilities, but they are
// strictly for addresses in the UNIX domain.
_set_last_platform_error :: proc(err: i32) {
_last_error = freebsd.Errno(err)
}
Listen_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ,
Already_Connected = cast(c.int)freebsd.Errno.EINVAL,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP,
_create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Interrupted = cast(c.int)freebsd.Errno.EINTR,
Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL,
Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT,
_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
assert(errno != nil)
_last_error = errno
// NOTE: This is the same as EWOULDBLOCK.
No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN,
// `Would_Block` alias for `core:net` tests.
Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED,
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
assert(errno != nil)
_last_error = errno
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
// but I'm including them for completeness's sake.
Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE,
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
Timeout = cast(c.int)freebsd.Errno.EAGAIN,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
#partial switch errno {
case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Insufficient_Resources
case .EBADF:
return .Invalid_Argument
case .EINVAL:
return .Already_Bound
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
assert(errno != nil)
_last_error = errno
// NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
// but I'm including them for completeness's sake.
Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE,
Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
Timeout = cast(c.int)freebsd.Errno.EAGAIN,
Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
#partial switch errno {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
assert(errno != nil)
_last_error = errno
Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE,
/* The socket is marked non-blocking, or MSG_DONTWAIT is
specified, and the requested operation would block. */
Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
/* NOTE: This error arises for two distinct reasons:
1. The system was unable to allocate an internal buffer.
The operation may succeed when buffers become available.
2. The output queue for a network interface was full.
This generally indicates that the interface has stopped
sending, but may be caused by transient congestion.
*/
No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED,
Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN,
Network_Down = cast(c.int)freebsd.Errno.ENETDOWN,
Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE,
#partial switch errno {
case .EMFILE, .ENFILE:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EINVAL:
return .Not_Listening
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware.
UDP_Send_Error :: distinct TCP_Send_Error
_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
assert(errno != nil)
_last_error = errno
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,
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Not_Connected
case .ECONNRESET:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL,
Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
_udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNRESET, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Value_Out_Of_Range = -1,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT,
Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
// This error can arise for many different reasons.
Invalid_Value = cast(c.int)freebsd.Errno.EINVAL,
System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM,
Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS,
_tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
_udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
return .Invalid_Argument
case .ENOBUFS:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT:
return .Invalid_Option
case .EINVAL, .EFAULT:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTTY:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import "core:sys/linux"
Create_Socket_Error :: enum c.int {
None = 0,
Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
No_Memory_Available_Available = c.int(linux.Errno.ENOMEM),
Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT),
Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT),
Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT),
@(private="file", thread_local)
_last_error: linux.Errno
_last_platform_error :: proc() -> i32 {
return i32(_last_error)
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Address_In_Use = c.int(linux.Errno.EADDRINUSE),
In_Progress = c.int(linux.Errno.EINPROGRESS),
Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL),
Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT),
Refused = c.int(linux.Errno.ECONNREFUSED),
Is_Listening_Socket = c.int(linux.Errno.EACCES),
Already_Connected = c.int(linux.Errno.EISCONN),
Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Timeout = c.int(linux.Errno.ETIMEDOUT),
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(linux.Errno.EWOULDBLOCK),
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(_last_error)
return description
}
Bind_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine.
Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket.
Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address.
No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
_last_error = linux.Errno(err)
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = c.int(linux.Errno.EADDRINUSE),
Already_Connected = c.int(linux.Errno.EISCONN),
No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP),
_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
return .Insufficient_Resources
case .EAFNOSUPPORT, .EPROTOTYPE:
return .Invalid_Argument
case .EACCES, .EPERM:
return .Insufficient_Permissions
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Listening = c.int(linux.Errno.EINVAL),
No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE),
No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP),
_dial_error :: proc(errno: linux.Errno) -> Dial_Error {
assert(errno != nil)
_last_error = errno
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = c.int(linux.Errno.EWOULDBLOCK),
#partial switch errno {
case .EAGAIN:
return .Insufficient_Resources
case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Invalid_Argument
case .EISCONN:
return .Already_Connected
case .EALREADY:
return .Already_Connecting
case .EADDRINUSE:
return .Address_In_Use
case .ENETUNREACH:
return .Network_Unreachable
case .EHOSTUNREACH:
return .Host_Unreachable
case .ECONNREFUSED:
return .Refused
case .ECONNRESET:
return .Reset
case .ETIMEDOUT:
return .Timeout
case .EINPROGRESS:
return .Would_Block
case .EINTR:
return .Interrupted
case .EACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Shutdown = c.int(linux.Errno.ESHUTDOWN),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Connection_Broken = c.int(linux.Errno.ENETRESET),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Aborted = c.int(linux.Errno.ECONNABORTED),
_bind_error :: proc(errno: linux.Errno) -> Bind_Error {
assert(errno != nil)
_last_error = errno
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN),
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
Interrupted = c.int(linux.Errno.EINTR),
Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
#partial switch errno {
case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
return .Insufficient_Resources
case .EINVAL:
return .Already_Bound
case .EBADF:
return .Invalid_Argument
case .EACCES:
return .Insufficient_Permissions_For_Address
case .EADDRINUSE:
return .Address_In_Use
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
_listen_error :: proc(errno: linux.Errno) -> Listen_Error {
assert(errno != nil)
_last_error = errno
Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
Timeout = c.int(linux.Errno.EWOULDBLOCK),
Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't.
#partial switch errno {
case .EBADF, .ENOTSOCK:
return .Invalid_Argument
case .EDESTADDRREQ, .EOPNOTSUPP:
return .Unsupported_Socket
case .EINVAL:
return .Already_Connected
case:
return .Unknown
}
}
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = c.int(linux.Errno.ECONNABORTED),
Connection_Closed = c.int(linux.Errno.ECONNRESET),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Shutdown = c.int(linux.Errno.ESHUTDOWN),
_accept_error :: proc(errno: linux.Errno) -> Accept_Error {
assert(errno != nil)
_last_error = errno
// 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(linux.Errno.ENOBUFS),
Offline = c.int(linux.Errno.ENETDOWN),
Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
#partial switch errno {
case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .EINVAL:
return .Not_Listening
case .ECONNABORTED:
return .Aborted
case .EWOULDBLOCK:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO
UDP_Send_Error :: enum c.int {
None = 0,
Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
assert(errno != nil)
_last_error = errno
// TODO: not sure what the exact circumstances for this is yet
Network_Unreachable = c.int(linux.Errno.ENETUNREACH),
No_Outbound_Ports_Available = c.int(linux.Errno.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(linux.Errno.EWOULDBLOCK),
Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
Interrupted = c.int(linux.Errno.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(linux.Errno.ENOBUFS),
No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue.
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ENOTCONN:
return .Not_Connected
case .ECONNREFUSED:
return .Connection_Closed
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
// TODO(flysand): slight regression
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),
_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .ENOTSOCK, .EFAULT:
return .Invalid_Argument
case .ECONNREFUSED, .ENOTCONN:
return .Connection_Refused
case .ETIMEDOUT:
return .Timeout
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = c.int(linux.Errno.ECONNABORTED),
Reset = c.int(linux.Errno.ECONNRESET),
Offline = c.int(linux.Errno.ENETDOWN),
Not_Connected = c.int(linux.Errno.ENOTCONN),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
Invalid_Manner = c.int(linux.Errno.EINVAL),
_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Closed
case .ENOTCONN:
return .Not_Connected
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Offline = c.int(linux.Errno.ENETDOWN),
Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT),
Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN),
Not_Socket = c.int(linux.Errno.ENOTSOCK),
_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
return .Invalid_Argument
case .ENOBUFS, .ENOMEM:
return .Insufficient_Resources
case .ECONNRESET, .EPIPE:
return .Connection_Refused
case .EHOSTUNREACH:
return .Host_Unreachable
case .EHOSTDOWN:
return .Host_Unreachable
case .ENETDOWN:
return .Network_Unreachable
case .EAGAIN:
return .Would_Block
case .EINTR:
return .Interrupted
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
assert(errno != nil)
_last_error = errno
// TODO: add errors occuring on followig calls:
// flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0)
// linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK))
}
#partial switch errno {
case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
return .Invalid_Argument
case:
return .Unknown
}
}
_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .ENOMEM, .ENOBUFS:
return .Insufficient_Resources
case .EBADF, .ENOTSOCK:
return .Invalid_Socket
case .ENOPROTOOPT, .EINVAL:
return .Invalid_Option
case .EFAULT, .EDOM:
return .Invalid_Value
case:
return .Unknown
}
}
_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
assert(errno != nil)
_last_error = errno
#partial switch errno {
case .EBADF:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -0,0 +1,20 @@
#+build !darwin
#+build !linux
#+build !freebsd
#+build !windows
package net
@(private="file", thread_local)
_last_error: i32
_last_platform_error :: proc() -> i32 {
return _last_error
}
_last_platform_error_string :: proc() -> string {
return ""
}
_set_last_platform_error :: proc(err: i32) {
_last_error = err
}

View File

@@ -20,250 +20,242 @@ package net
Feoramund: FreeBSD platform code
*/
import "core:c"
import "core:reflect"
import win "core:sys/windows"
Create_Socket_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
No_Socket_Descriptors_Available = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
_last_platform_error :: proc() -> i32 {
return i32(win.WSAGetLastError())
}
Dial_Error :: enum c.int {
None = 0,
Port_Required = -1,
Address_In_Use = win.WSAEADDRINUSE,
In_Progress = win.WSAEALREADY,
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
Refused = win.WSAECONNREFUSED,
Is_Listening_Socket = win.WSAEINVAL,
Already_Connected = win.WSAEISCONN,
Network_Unreachable = win.WSAENETUNREACH, // Device is offline
Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK,
Timeout = win.WSAETIMEDOUT,
Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
_last_platform_error_string :: proc() -> string {
description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError()))
return description
}
Bind_Error :: enum c.int {
None = 0,
Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
_set_last_platform_error :: proc(err: i32) {
win.WSASetLastError(err)
}
Listen_Error :: enum c.int {
None = 0,
Address_In_Use = win.WSAEADDRINUSE,
Already_Connected = win.WSAEISCONN,
No_Socket_Descriptors_Available = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Nonlocal_Address = win.WSAEADDRNOTAVAIL,
Not_Socket = win.WSAENOTSOCK,
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
_create_socket_error :: proc() -> Create_Socket_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT:
return .Network_Unreachable
case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT:
return .Invalid_Argument
case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT:
return .Insufficient_Resources
case:
return .Unknown
}
}
Accept_Error :: enum c.int {
None = 0,
Not_Listening = win.WSAEINVAL,
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK,
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
Would_Block = win.WSAEWOULDBLOCK,
_dial_error :: proc() -> Dial_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEINTR:
return .Interrupted
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAEALREADY:
return .Already_Connecting
case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
return .Invalid_Argument
case .WSAECONNREFUSED:
return .Refused
case .WSAEISCONN:
return .Already_Connected
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAETIMEDOUT:
return .Timeout
case .WSAEACCES:
return .Broadcast_Not_Supported
case:
return .Unknown
}
}
TCP_Recv_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Not_Connected = win.WSAENOTCONN,
Bad_Buffer = win.WSAEFAULT,
Keepalive_Failure = win.WSAENETRESET,
Not_Socket = win.WSAENOTSOCK,
Shutdown = win.WSAESHUTDOWN,
Would_Block = win.WSAEWOULDBLOCK,
Aborted = win.WSAECONNABORTED,
Timeout = win.WSAETIMEDOUT,
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
Connection_Closed = win.WSAECONNRESET,
// TODO: verify can actually happen
Host_Unreachable = win.WSAEHOSTUNREACH,
_bind_error :: proc() -> Bind_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK:
return .Invalid_Argument
case:
return .Unknown
}
}
UDP_Recv_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Aborted = win.WSAECONNABORTED,
Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
Shutdown = win.WSAESHUTDOWN,
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
Bad_Buffer = win.WSAEFAULT,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
Would_Block = win.WSAEWOULDBLOCK,
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
Timeout = win.WSAETIMEDOUT,
// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
Incorrectly_Configured = win.WSAEINVAL,
TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
_listen_error :: proc() -> Listen_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEADDRINUSE:
return .Address_In_Use
case .WSAEINPROGRESS, .WSAENOTSOCK:
return .Invalid_Argument
case .WSAEISCONN:
return .Already_Connected
case .WSAEOPNOTSUPP, .WSAEINVAL:
return .Unsupported_Socket
case:
return .Unknown
}
}
// TODO: consider merging some errors to make handling them easier
// TODO: verify once more what errors to actually expose
TCP_Send_Error :: enum c.int {
None = 0,
Aborted = win.WSAECONNABORTED,
Not_Connected = win.WSAENOTCONN,
Shutdown = win.WSAESHUTDOWN,
Connection_Closed = win.WSAECONNRESET,
No_Buffer_Space_Available = win.WSAENOBUFS,
Network_Subsystem_Failure = win.WSAENETDOWN,
Host_Unreachable = win.WSAEHOSTUNREACH,
Would_Block = win.WSAEWOULDBLOCK,
// TODO: verify possible, as not mentioned in docs
Offline = win.WSAENETUNREACH,
Timeout = win.WSAETIMEDOUT,
// A broadcast address was specified, but the .Broadcast socket option isn't set.
Broadcast_Disabled = win.WSAEACCES,
Bad_Buffer = win.WSAEFAULT,
// Connection is broken due to keepalive activity detecting a failure during the operation.
Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
_accept_error :: proc() -> Accept_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEMFILE, .WSAENOBUFS:
return .Insufficient_Resources
case .WSAECONNRESET:
return .Aborted
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAEINVAL:
return .Not_Listening
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAEOPNOTSUPP:
return .Unsupported_Socket
case:
return .Unknown
}
}
UDP_Send_Error :: enum c.int {
None = 0,
Network_Subsystem_Failure = win.WSAENETDOWN,
Aborted = win.WSAECONNABORTED,
Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size.
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
Broadcast_Disabled = win.WSAEACCES,
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
// TODO: not functionally different from Reset; merge?
Keepalive_Failure = win.WSAENETRESET,
No_Buffer_Space_Available = win.WSAENOBUFS,
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
// This socket is unidirectional and cannot be used to send any data.
// TODO: verify possible; decide whether to keep if not
Receive_Only = win.WSAEOPNOTSUPP,
Would_Block = win.WSAEWOULDBLOCK,
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
Timeout = win.WSAETIMEDOUT,
_tcp_recv_error :: proc() -> TCP_Recv_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP:
return .Invalid_Argument
case .WSAENOTCONN:
return .Not_Connected
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
_udp_recv_error :: proc() -> UDP_Recv_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
return .Connection_Refused
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Shutdown_Error :: enum c.int {
None = 0,
Aborted = win.WSAECONNABORTED,
Reset = win.WSAECONNRESET,
Offline = win.WSAENETDOWN,
Not_Connected = win.WSAENOTCONN,
Not_Socket = win.WSAENOTSOCK,
Invalid_Manner = win.WSAEINVAL,
_tcp_send_error :: proc() -> TCP_Send_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN:
return .Network_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAENOTCONN:
return .Not_Connected
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case .WSAEHOSTUNREACH:
return .Host_Unreachable
case:
return .Unknown
}
}
Socket_Option :: enum c.int {
// bool: Whether the address that this socket is bound to can be reused by other sockets.
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
Reuse_Address = win.SO_REUSEADDR,
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
Keep_Alive = win.SO_KEEPALIVE,
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
Dont_Linger = win.SO_DONTLINGER,
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
// bool: When true, disables send-coalescing, therefore reducing latency.
TCP_Nodelay = win.TCP_NODELAY,
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
// is some remaining data waiting to be sent, and net.close() is called.
Linger = win.SO_LINGER,
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
Receive_Buffer_Size = win.SO_RCVBUF,
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
Send_Buffer_Size = win.SO_SNDBUF,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Receive_Timeout = win.SO_RCVTIMEO,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Send_Timeout = win.SO_SNDTIMEO,
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
Broadcast = win.SO_BROADCAST,
_udp_send_error :: proc() -> UDP_Send_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH:
return .Network_Unreachable
case .WSAENOBUFS:
return .Insufficient_Resources
case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ:
return .Invalid_Argument
case .WSAEINTR:
return .Interrupted
case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
return .Connection_Refused
case .WSAEWOULDBLOCK:
return .Would_Block
case .WSAETIMEDOUT:
return .Timeout
case:
return .Unknown
}
}
Socket_Option_Error :: enum c.int {
None = 0,
Linger_Only_Supports_Whole_Seconds = 1,
// The given value is too big or small to be given to the OS.
Value_Out_Of_Range,
Network_Subsystem_Failure = win.WSAENETDOWN,
Timeout_When_Keepalive_Set = win.WSAENETRESET,
Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
Reset_When_Keepalive_Set = win.WSAENOTCONN,
Not_Socket = win.WSAENOTSOCK,
_shutdown_error :: proc() -> Shutdown_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAECONNABORTED, .WSAECONNRESET:
return .Connection_Closed
case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Argument
case:
return .Unknown
}
}
Set_Blocking_Error :: enum c.int {
None = 0,
_socket_option_error :: proc() -> Socket_Option_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEFAULT, .WSAEINVAL:
return .Invalid_Value
case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK:
return .Invalid_Socket
case .WSAENOPROTOOPT:
return .Invalid_Option
case:
return .Unknown
}
}
Network_Subsystem_Failure = win.WSAENETDOWN,
Blocking_Call_In_Progress = win.WSAEINPROGRESS,
Not_Socket = win.WSAENOTSOCK,
// TODO: are those errors possible?
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
Invalid_Argument_Pointer = win.WSAEFAULT,
}
_set_blocking_error :: proc() -> Set_Blocking_Error {
#partial switch win.System_Error(win.WSAGetLastError()) {
case .WSAENETDOWN, .WSANOTINITIALISED:
return .Network_Unreachable
case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT:
return .Invalid_Argument
case:
return .Unknown
}
}

View File

@@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3
/*
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
*/
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
return _enumerate_interfaces(allocator)
}

View File

@@ -26,7 +26,7 @@ import "core:sys/posix"
foreign import lib "system:System.framework"
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
head: ^ifaddrs
@@ -47,7 +47,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
iface.adapter_name = key_ptr^
}
if mem_err != nil {
return {}, .Unable_To_Enumerate_Network_Interfaces
return {}, .Allocation_Failure
}
address: Address

View File

@@ -25,7 +25,7 @@ import "core:strings"
import "core:sys/freebsd"
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
// This is a simplified implementation of `getifaddrs` from the FreeBSD
// libc using only Odin and syscalls.
context.allocator = allocator
@@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// Allocate and get the entries.
buf, alloc_err := make([]byte, needed)
if alloc_err != nil {
return nil, .Unable_To_Enumerate_Network_Interfaces
return nil, .Allocation_Failure
}
defer delete(buf)

View File

@@ -30,7 +30,7 @@ package net
// NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html
// apparently musl libc uses this to enumerate network interfaces
@(private)
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
// head: ^os.ifaddrs
@@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// }
// return _interfaces[:], {}
return nil, {}
}
}

View File

@@ -23,7 +23,7 @@ package net
import sys "core:sys/windows"
import strings "core:strings"
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
buf: []u8
@@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
case 0:
break gaa
case:
return {}, Platform_Error(res)
set_last_platform_error(i32(res))
return {}, .Unknown
}
}
@@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
_interfaces := make([dynamic]Network_Interface, 0, allocator)
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
if err1 != nil { return {}, Platform_Error(err1) }
if err1 != nil { return {}, .Allocation_Failure }
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
if err2 != nil { return {}, Platform_Error(err2) }
if err2 != nil { return {}, .Allocation_Failure }
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
if err3 != nil { return {}, Platform_Error(err3) }
if err3 != nil { return {}, .Allocation_Failure }
interface := Network_Interface{
adapter_name = strings.clone(string(adapter.AdapterName)),
@@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
case: return // Empty or invalid address type
}
unreachable()
}
}

View File

@@ -35,6 +35,8 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
`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_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname_and_port) or_return
@@ -47,6 +49,8 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option
`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`
*/
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname) or_return
@@ -62,6 +66,8 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o
/*
Expects the `host` as Host.
Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
if host.port == 0 {
@@ -76,6 +82,8 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke
/*
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_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
switch t in target {
@@ -87,11 +95,20 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa
unreachable()
}
// Dial from an Address
/*
Dial from an Address.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
return dial_tcp_from_endpoint({address, port}, options)
}
/*
Dial from an Endpoint.
Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
*/
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
return _dial_tcp_from_endpoint(endpoint, options)
}
@@ -105,11 +122,11 @@ dial_tcp :: proc{
dial_tcp_from_host_or_endpoint,
}
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
return _create_socket(family, protocol)
}
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
return _bind(socket, ep)
}
@@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
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) -> (socket: UDP_Socket, err: Network_Error) {
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) {
sock := create_socket(family, .UDP) or_return
socket = sock.(UDP_Socket)
return
@@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket,
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_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
if bound_address == nil {
@@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP
return
}
/*
Creates a TCP socket and starts listening on the given endpoint.
Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error`
*/
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && backlog < int(max(i32)))
@@ -150,11 +174,11 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/*
Returns the endpoint that the given socket is listening / bound on.
*/
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) {
bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) {
return _bound_endpoint(socket)
}
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options)
}
@@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) {
_close(socket)
}
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
return _recv_tcp(socket, buf)
}
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
return _recv_udp(socket, buf)
}
@@ -175,6 +199,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en
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`
*/
recv_any :: proc(socket: Any_Socket, buf: []byte) -> (
bytes_read: int,
@@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any}
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(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
return _send_tcp(socket, buf)
}
@@ -207,10 +233,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
UDP packets are not guarenteed to be received in order.
*/
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
return _send_udp(socket, buf, to)
}
/*
Sends data over the socket.
Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error`
*/
send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> (
bytes_written: int,
err: Network_Error,
@@ -226,14 +257,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) ->
send :: proc{send_tcp, send_udp, send_any}
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
return _shutdown(socket, manner)
}
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
return _set_option(socket, option, value, loc)
}
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
return _set_blocking(socket, should_block)
}

View File

@@ -37,8 +37,14 @@ Socket_Option :: enum c.int {
Send_Timeout = c.int(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),
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type: posix.Sock
c_protocol: posix.Protocol
c_family: posix.AF
@@ -59,7 +65,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := posix.socket(c_family, c_type, c_protocol)
if sock < 0 {
err = Create_Socket_Error(posix.errno())
err = _create_socket_error()
return
}
@@ -88,28 +94,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint)
if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno()
err = _dial_error()
close(skt)
return {}, Dial_Error(errno)
}
return
}
// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it.
MAX_PRIVILEGED_PORT :: 1023
@(private)
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt)
if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
errno := posix.errno()
if errno == .EACCES && ep.port <= MAX_PRIVILEGED_PORT {
err = .Privileged_Port_Without_Root
} else {
err = Bind_Error(errno)
}
err = _bind_error()
}
return
@@ -128,25 +125,23 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
// 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
_ = set_option(sock, .Reuse_Address, true)
bind(sock, interface_endpoint) or_return
if posix.listen(posix.FD(skt), i32(backlog)) != .OK {
err = Listen_Error(posix.errno())
return
err = _listen_error()
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
err = Listen_Error(posix.errno())
err = _listen_error()
return
}
@@ -155,12 +150,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len)
if client_sock < 0 {
err = Accept_Error(posix.errno())
err = _accept_error()
return
}
@@ -176,14 +171,14 @@ _close :: proc(skt: Any_Socket) {
}
@(private)
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {})
if res < 0 {
err = TCP_Recv_Error(posix.errno())
err = _tcp_recv_error()
return
}
@@ -191,7 +186,7 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
}
@(private)
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
@@ -200,7 +195,7 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
fromsize := posix.socklen_t(size_of(from))
res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize)
if res < 0 {
err = UDP_Recv_Error(posix.errno())
err = _udp_recv_error()
return
}
@@ -210,20 +205,13 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
}
@(private)
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL})
if res < 0 {
errno := posix.errno()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = TCP_Send_Error.Connection_Closed
return
}
err = TCP_Send_Error(errno)
err = _tcp_send_error()
return
}
@@ -233,21 +221,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
}
@(private)
_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: UDP_Send_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 := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len))
if res < 0 {
errno := posix.errno()
if errno == .EPIPE {
// EPIPE arises if the socket has been closed remotely.
err = UDP_Send_Error.Not_Socket
return
}
err = UDP_Send_Error(errno)
err = _udp_send_error()
return
}
@@ -257,16 +238,16 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
}
@(private)
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(skt)
if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK {
err = Shutdown_Error(posix.errno())
err = _shutdown_error()
}
return
}
@(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
@@ -337,19 +318,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
skt := any_socket_to_socket(s)
if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK {
return Socket_Option_Error(posix.errno())
return _socket_option_error()
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0)
if flags_ < 0 {
return Set_Blocking_Error(posix.errno())
return _set_blocking_error()
}
flags := transmute(posix.O_Flags)flags_
@@ -360,7 +341,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
}
if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 {
return Set_Blocking_Error(posix.errno())
return _set_blocking_error()
}
return nil

View File

@@ -54,8 +54,14 @@ Socket_Option :: enum c.int {
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
}
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,
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
sys_family: freebsd.Protocol_Family = ---
sys_protocol: freebsd.Protocol = ---
sys_socket_type: freebsd.Socket_Type = ---
@@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
if errno != nil {
err = cast(Create_Socket_Error)errno
return
}
// NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE
// error is raised during the writing of a socket that may be closed.
// This behavior is unlikely to be expected by general users.
//
// There are two workarounds. One is to apply the .NOSIGNAL flag when using
// the `sendto` syscall. However, that would prevent users of this library
// from re-enabling the SIGPIPE-raising functionality, if they really
// wanted it.
//
// So I have disabled it here with this socket option for all sockets.
truth: b32 = true
errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth))
if errno != nil {
err = cast(Socket_Option_Error)errno
err = _create_socket_error(errno)
return
}
@@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
close(socket)
return {}, cast(Dial_Error)errno
return {}, _dial_error(errno)
}
return
}
@(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
real_socket := any_socket_to_socket(socket)
errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
err = cast(Bind_Error)errno
err = _bind_error(errno)
}
return
}
@@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
errno := freebsd.listen(cast(Fd)socket, backlog)
if errno != nil {
err = cast(Listen_Error)errno
err = _listen_error(errno)
return
}
@@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil {
err = cast(Listen_Error)errno
err = _listen_error(errno)
return
}
@@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
sockaddr: freebsd.Socket_Address_Storage
result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
if errno != nil {
err = cast(Accept_Error)errno
err = _accept_error(errno)
return
}
@@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) == 0 {
return
}
result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
if errno != nil {
err = cast(TCP_Recv_Error)errno
err = _tcp_recv_error(errno)
return
}
return result, nil
}
@(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) == 0 {
return
}
@@ -208,21 +197,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
if errno != nil {
err = cast(UDP_Recv_Error)errno
err = _udp_recv_error(errno)
return
}
return result, _sockaddr_to_endpoint(&from), nil
}
@(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE)
result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL)
if errno != nil {
err = cast(TCP_Send_Error)errno
err = _tcp_send_error(errno)
return
}
bytes_written += result
@@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr)
result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr)
if errno != nil {
err = cast(UDP_Send_Error)errno
err = _udp_send_error(errno)
return
}
bytes_written += result
@@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt
}
@(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
real_socket := cast(Fd)any_socket_to_socket(socket)
errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner)
if errno != nil {
return cast(Shutdown_Error)errno
return _shutdown_error(errno)
}
return
}
@(private)
_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
// NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32
// bits for a boolean socket option value. Nothing less will work.
bool_value: b32
@@ -315,25 +304,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
case u16: int_value = cast(i32)real
case i32: int_value = real
case u32:
if real > u32(max(i32)) { return .Value_Out_Of_Range }
if real > u32(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i64:
if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range }
if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u64:
if real > u64(max(i32)) { return .Value_Out_Of_Range }
if real > u64(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i128:
if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range }
if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u128:
if real > u128(max(i32)) { return .Value_Out_Of_Range }
if real > u128(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case int:
if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range }
if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case uint:
if real > uint(max(i32)) { return .Value_Out_Of_Range }
if real > uint(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case:
panic("set_option() value must be an integer here", loc)
@@ -347,19 +336,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
real_socket := any_socket_to_socket(socket)
errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len)
if errno != nil {
return cast(Socket_Option_Error)errno
return _socket_option_error(errno)
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
real_socket := any_socket_to_socket(socket)
flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket)
if errno != nil {
return cast(Set_Blocking_Error)errno
return _set_blocking_error(errno)
}
if should_block {
@@ -370,7 +359,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
if errno != nil {
return cast(Set_Blocking_Error)errno
return _set_blocking_error(errno)
}
return

View File

@@ -38,15 +38,21 @@ Socket_Option :: enum c.int {
Broadcast = c.int(linux.Socket_Option.BROADCAST),
}
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),
}
// Wrappers and unwrappers for system-native types
@(private="file")
_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd {
_unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd {
return linux.Fd(any_socket_to_socket(sock))
}
@(private="file")
_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket {
_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket {
switch protocol {
case .TCP: return TCP_Socket(Socket(sock))
case .UDP: return UDP_Socket(Socket(sock))
@@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol
}
@(private="file")
_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family {
_unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family {
switch family {
case .IP4: return .INET
case .IP6: return .INET6
@@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_
}
@(private="file")
_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) {
_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) {
switch protocol {
case .TCP: return .TCP, .STREAM
case .UDP: return .UDP, .DGRAM
@@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li
}
@(private="file")
_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) {
_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any {
switch address in endpoint.address {
case IP4_Address:
return {
@@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any
}
@(private="file")
_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint {
#partial switch addr.family {
case .INET:
return {
@@ -117,12 +123,12 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
}
}
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) {
family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE {
return {}, Create_Socket_Error(errno)
return {}, _create_socket_error(errno)
}
return _wrap_os_socket(sock, protocol), nil
}
@@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
return {}, Create_Socket_Error(errno)
return {}, _create_socket_error(errno)
}
// 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
@@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno = linux.connect(linux.Fd(os_sock), &addr)
if errno != .NONE {
close(cast(TCP_Socket) os_sock)
return {}, Dial_Error(errno)
return {}, _dial_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
no_delay: b32 = cast(b32) options.no_delay
@@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) {
_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) {
addr := _unwrap_os_addr(endpoint)
errno := linux.bind(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
return Bind_Error(errno)
return _bind_error(errno)
}
return nil
}
@@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
err = Create_Socket_Error(errno)
err = _create_socket_error(errno)
return
}
socket = cast(TCP_Socket)os_sock
@@ -193,31 +199,30 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
do_reuse_addr: b32 = true
if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE {
err = Listen_Error(errno)
err = _listen_error(errno)
return
}
// Bind the socket to endpoint address
if errno = linux.bind(os_sock, &ep_address); errno != .NONE {
err = Bind_Error(errno)
err = _bind_error(errno)
return
}
// Listen on bound socket
if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE {
err = Listen_Error(errno)
return
err = _listen_error(errno)
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
err = Listen_Error(errno)
err = _listen_error(errno)
return
}
@@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) {
addr: linux.Sock_Addr_Any
client_sock, errno := linux.accept(linux.Fd(sock), &addr)
if errno != .NONE {
return {}, {}, Accept_Error(errno)
return {}, {}, _accept_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
val: b32 = cast(b32) options.no_delay
@@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) {
}
@(private)
_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) {
if len(buf) <= 0 {
return 0, nil
}
bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {})
if errno != .NONE {
return 0, TCP_Recv_Error(errno)
return 0, _tcp_recv_error(errno)
}
return int(bytes_read), nil
}
@(private)
_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) {
_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) {
if len(buf) <= 0 {
// NOTE(flysand): It was returning no error, I didn't change anything
return 0, {}, {}
@@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_
from_addr: linux.Sock_Addr_Any
bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr)
if errno != .NONE {
return 0, {}, UDP_Recv_Error(errno)
return 0, {}, _udp_recv_error(errno)
}
if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message.
return len(buf), {}, .Buffer_Too_Small
return len(buf), {}, .Excess_Truncated
}
return bytes_read, _wrap_os_addr(from_addr), nil
}
@(private)
_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) {
total_written := 0
for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
if errno == .EPIPE {
// If the peer is disconnected when we are trying to send we will get an `EPIPE` error,
// so we turn that into a clearer error
return total_written, TCP_Send_Error.Connection_Closed
} else if errno != .NONE {
return total_written, TCP_Send_Error(errno)
if errno != .NONE {
return total_written, _tcp_send_error(errno)
}
total_written += int(res)
}
@@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
}
@(private)
_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) {
_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) {
to_addr := _unwrap_os_addr(to)
bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr)
if errno != .NONE {
return bytes_written, UDP_Send_Error(errno)
return bytes_written, _udp_send_error(errno)
}
return int(bytes_written), nil
}
@(private)
_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
os_sock := _unwrap_os_socket(sock)
errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner)
if errno != .NONE {
return Shutdown_Error(errno)
return _shutdown_error(errno)
}
return nil
}
// TODO(flysand): Figure out what we want to do with this on core:sys/ level.
@(private)
_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level: int
if option == .TCP_Nodelay {
level = int(linux.SOL_TCP)
@@ -388,19 +389,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
errno = linux.setsockopt(os_sock, level, int(option), &int_value)
}
if errno != .NONE {
return Socket_Option_Error(errno)
return _socket_option_error(errno)
}
return nil
}
@(private)
_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
errno: linux.Errno
flags: linux.Open_Flags
os_sock := _unwrap_os_socket(sock)
flags, errno = linux.fcntl(os_sock, linux.F_GETFL)
if errno != .NONE {
return Set_Blocking_Error(errno)
return _set_blocking_error(errno)
}
if should_block {
flags -= {.NONBLOCK}
@@ -409,7 +410,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err
}
errno = linux.fcntl(os_sock, linux.F_SETFL, flags)
if errno != .NONE {
return Set_Blocking_Error(errno)
return _set_blocking_error(errno)
}
return nil
}

View File

@@ -24,13 +24,67 @@ 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,
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
Keep_Alive = win.SO_KEEPALIVE,
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
Dont_Linger = win.SO_DONTLINGER,
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
// bool: When true, disables send-coalescing, therefore reducing latency.
TCP_Nodelay = win.TCP_NODELAY,
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
// is some remaining data waiting to be sent, and net.close() is called.
Linger = win.SO_LINGER,
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
Receive_Buffer_Size = win.SO_RCVBUF,
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
Send_Buffer_Size = win.SO_SNDBUF,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Receive_Timeout = win.SO_RCVTIMEO,
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
// For non-blocking sockets, ignored.
// Use a value of zero to potentially wait forever.
Send_Timeout = win.SO_SNDTIMEO,
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
Broadcast = win.SO_BROADCAST,
}
Shutdown_Manner :: enum c.int {
Receive = win.SD_RECEIVE,
Send = win.SD_SEND,
Both = win.SD_BOTH,
}
@(init, private)
ensure_winsock_initialized :: proc() {
win.ensure_winsock_initialized()
}
@(private)
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type, c_protocol, c_family: c.int
switch family {
@@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := win.socket(c_family, c_type, c_protocol)
if sock == win.INVALID_SOCKET {
err = Create_Socket_Error(win.WSAGetLastError())
err = _create_socket_error()
return
}
@@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint)
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
if res < 0 {
err = Dial_Error(win.WSAGetLastError())
err = _dial_error()
close(socket)
return {}, err
}
@@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
sock := any_socket_to_socket(socket)
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
if res < 0 {
err = Bind_Error(win.WSAGetLastError())
err = _bind_error()
}
return
}
@@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
bind(sock, interface_endpoint) or_return
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError())
err = _listen_error()
}
return
}
@(private)
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
err = Listen_Error(win.WSAGetLastError())
err = _listen_error()
return
}
@@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
for {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
@@ -150,7 +204,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
// can do this to match the behaviour.
continue
}
err = Accept_Error(e)
err = _accept_error()
return
}
client = TCP_Socket(client_sock)
@@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
if res < 0 {
err = TCP_Recv_Error(win.WSAGetLastError())
err = _tcp_recv_error()
return
}
return int(res), nil
}
@(private)
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
@@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
fromsize := c.int(size_of(from))
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
if res < 0 {
err = UDP_Recv_Error(win.WSAGetLastError())
err = _udp_recv_error()
return
}
@@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
}
@(private)
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:]
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
if res < 0 {
err = TCP_Send_Error(win.WSAGetLastError())
err = _tcp_send_error()
return
}
bytes_written += int(res)
@@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
if len(buf) > int(max(c.int)) {
// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
err = .Message_Too_Long
return
}
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
if res < 0 {
err = UDP_Send_Error(win.WSAGetLastError())
return
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:]
res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr))
if res < 0 {
err = _udp_send_error()
return
}
bytes_written += int(res)
}
bytes_written = int(res)
return
}
@(private)
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(socket)
res := win.shutdown(win.SOCKET(s), c.int(manner))
if res < 0 {
return Shutdown_Error(win.WSAGetLastError())
return _shutdown_error()
}
return
}
@(private)
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
bool_value: b32
@@ -283,11 +337,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
num_secs := i64(time.duration_seconds(t))
if time.Duration(num_secs * 1e9) != t {
return .Linger_Only_Supports_Whole_Seconds
}
if num_secs > i64(max(u16)) {
return .Value_Out_Of_Range
return .Invalid_Value
}
linger_value.l_onoff = 1
linger_value.l_linger = c.ushort(num_secs)
@@ -323,19 +374,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
socket := any_socket_to_socket(s)
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
if res < 0 {
return Socket_Option_Error(win.WSAGetLastError())
return _socket_option_error()
}
return nil
}
@(private)
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
arg: win.DWORD = 0 if should_block else 1
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
if res == win.SOCKET_ERROR {
return Set_Blocking_Error(win.WSAGetLastError())
return _set_blocking_error()
}
return nil

View File

@@ -91,6 +91,7 @@ foreign ws2_32 {
WSACleanup :: proc() -> c_int ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror)
WSAGetLastError :: proc() -> c_int ---
WSASetLastError :: proc(err: c_int) ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll)
WSAPoll :: proc(fdArray: ^WSA_POLLFD, fds: c_ulong, timeout: c_int) -> c_int ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaduplicatesocketw)

View File

@@ -17,9 +17,6 @@ import "core:net"
import "core:time"
import "core:testing"
ENDPOINT_DUPLICATE_BINDING := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11000}
ENDPOINT_EPIPE_TEST := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 11001}
@test
test_duplicate_binding :: proc(t: ^testing.T) {
// FreeBSD has the capacity to permit multiple processes and sockets to
@@ -35,11 +32,16 @@ test_duplicate_binding :: proc(t: ^testing.T) {
if !testing.expect_value(t, err_set1, nil) {
return
}
err_bind1 := net.bind(tcp_socket1, ENDPOINT_DUPLICATE_BINDING)
err_bind1 := net.bind(tcp_socket1, {net.IP4_Loopback, 0})
if !testing.expect_value(t, err_bind1, nil) {
return
}
ep, err_bound := net.bound_endpoint(tcp_socket1)
if !testing.expect_value(t, err_bound, nil) {
return
}
raw_socket2, err_create2 := net.create_socket(.IP4, .TCP)
if !testing.expect_value(t, err_create2, nil) {
return
@@ -50,7 +52,7 @@ test_duplicate_binding :: proc(t: ^testing.T) {
if !testing.expect_value(t, err_set2, nil) {
return
}
err_bind2 := net.bind(tcp_socket2, ENDPOINT_DUPLICATE_BINDING)
err_bind2 := net.bind(tcp_socket2, ep)
if !testing.expect_value(t, err_bind2, nil) {
return
}
@@ -60,13 +62,18 @@ test_duplicate_binding :: proc(t: ^testing.T) {
test_sigpipe_bypass :: proc(t: ^testing.T) {
// If the internals aren't working as expected, this test will fail by raising SIGPIPE.
server_socket, listen_err := net.listen_tcp(ENDPOINT_EPIPE_TEST)
server_socket, listen_err := net.listen_tcp({net.IP4_Loopback, 0})
if !testing.expect_value(t, listen_err, nil) {
return
}
defer net.close(server_socket)
client_socket, dial_err := net.dial_tcp(ENDPOINT_EPIPE_TEST)
ep, bound_err := net.bound_endpoint(server_socket)
if !testing.expect_value(t, bound_err, nil) {
return
}
client_socket, dial_err := net.dial_tcp(ep)
if !testing.expect_value(t, dial_err, nil) {
return
}
@@ -80,7 +87,7 @@ test_sigpipe_bypass :: proc(t: ^testing.T) {
data := "Hellope!"
bytes_written, err_send := net.send(client_socket, transmute([]u8)data)
if !testing.expect_value(t, err_send, net.TCP_Send_Error.Cannot_Send_More_Data) {
if !testing.expect_value(t, err_send, net.TCP_Send_Error.Connection_Closed) {
return
}
if !testing.expect_value(t, bytes_written, 0) {