aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-26 01:18:43 -0400
committerFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-26 10:05:24 -0400
commite61d893a749d6219c498152bb53d750a384d13f4 (patch)
tree37ab259dc9e08eb43cf193f0b8b2034ad1b9dadf
parent10ce76fcc2ce45a28858ca1562e2b802a781ee58 (diff)
Port `core:net` to FreeBSD
-rw-r--r--core/net/addr.odin4
-rw-r--r--core/net/common.odin4
-rw-r--r--core/net/dns.odin4
-rw-r--r--core/net/dns_unix.odin2
-rw-r--r--core/net/errors_freebsd.odin191
-rw-r--r--core/net/interface.odin2
-rw-r--r--core/net/interface_freebsd.odin158
-rw-r--r--core/net/socket.odin2
-rw-r--r--core/net/socket_freebsd.odin365
9 files changed, 723 insertions, 9 deletions
diff --git a/core/net/addr.odin b/core/net/addr.odin
index c01724d99..b6f8ef3f5 100644
--- a/core/net/addr.odin
+++ b/core/net/addr.odin
@@ -1,4 +1,4 @@
-// +build windows, linux, darwin
+// +build windows, linux, darwin, freebsd
package net
/*
@@ -743,4 +743,4 @@ parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := D
get_network_interfaces :: proc() -> []Address {
// TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces.
return nil
-} \ No newline at end of file
+}
diff --git a/core/net/common.odin b/core/net/common.odin
index db969eab8..69fce7d33 100644
--- a/core/net/common.odin
+++ b/core/net/common.odin
@@ -1,4 +1,4 @@
-// +build windows, linux, darwin
+// +build windows, linux, darwin, freebsd
package net
/*
@@ -413,4 +413,4 @@ DNS_Record_Header :: struct #packed {
DNS_Host_Entry :: struct {
name: string,
addr: Address,
-} \ No newline at end of file
+}
diff --git a/core/net/dns.odin b/core/net/dns.odin
index 408fd201b..99af83cfb 100644
--- a/core/net/dns.odin
+++ b/core/net/dns.odin
@@ -1,4 +1,4 @@
-// +build windows, linux, darwin
+// +build windows, linux, darwin, freebsd
package net
/*
@@ -30,7 +30,7 @@ when ODIN_OS == .Windows {
resolv_conf = "",
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
}
-} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
+} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
resolv_conf = "/etc/resolv.conf",
hosts_file = "/etc/hosts",
diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin
index e9b7bd066..b917a9d26 100644
--- a/core/net/dns_unix.odin
+++ b/core/net/dns_unix.odin
@@ -1,4 +1,4 @@
-//+build linux, darwin
+//+build linux, darwin, freebsd
package net
/*
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin
new file mode 100644
index 000000000..5f11c2dee
--- /dev/null
+++ b/core/net/errors_freebsd.odin
@@ -0,0 +1,191 @@
+//+build freebsd
+package net
+
+import "core:c"
+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,
+ Insufficient_Buffer_Space = 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,
+}
+
+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.
+ Refused = cast(c.int)freebsd.Errno.ECONNREFUSED,
+ 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.
+}
+
+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.
+}
+
+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,
+}
+
+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,
+
+ // NOTE: This is the same as EWOULDBLOCK.
+ No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN,
+
+ New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED,
+}
+
+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,
+
+ // 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,
+}
+
+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,
+
+ // 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,
+}
+
+TCP_Send_Error :: enum c.int {
+ None = 0,
+ 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,
+
+ 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.
+ /* The system was unable to allocate an internal buffer.
+ The operation may succeed when buffers become available. */
+
+ /* 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,
+}
+
+// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware.
+UDP_Send_Error :: distinct TCP_Send_Error
+
+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,
+}
+
+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,
+}
+
+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,
+ Cannot_Install_Accept_Filter_On_Non_Listening_Socket = 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,
+}
+
+Set_Blocking_Error :: enum c.int {
+ None = 0,
+ Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
+ Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
+}
diff --git a/core/net/interface.odin b/core/net/interface.odin
index df7d0223e..06d1de129 100644
--- a/core/net/interface.odin
+++ b/core/net/interface.odin
@@ -1,4 +1,4 @@
-// +build windows, linux, darwin
+// +build windows, linux, darwin, freebsd
package net
/*
diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin
new file mode 100644
index 000000000..f3c455b21
--- /dev/null
+++ b/core/net/interface_freebsd.odin
@@ -0,0 +1,158 @@
+//+build freebsd
+package net
+
+import "core:c"
+import "core:strings"
+import "core:sys/freebsd"
+
+@(private)
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+ // This is a simplified implementation of `getifaddrs` from the FreeBSD
+ // libc using only Odin and syscalls.
+ context.allocator = allocator
+
+ mib := [6]freebsd.MIB_Identifier {
+ .CTL_NET,
+ cast(freebsd.MIB_Identifier)freebsd.Protocol_Family.ROUTE,
+ freebsd.MIB_Identifier(0),
+ freebsd.MIB_Identifier(0),
+ .NET_RT_IFLISTL,
+ freebsd.MIB_Identifier(0),
+ }
+
+ // Figure out how much space we need.
+ needed: c.size_t = ---
+
+ errno := freebsd.sysctl(mib[:], nil, &needed, nil, 0)
+ if errno != nil {
+ return nil, .Unable_To_Enumerate_Network_Interfaces
+ }
+
+ // Allocate and get the entries.
+ buf, alloc_err := make([]byte, needed)
+ if alloc_err != nil {
+ return nil, .Unable_To_Enumerate_Network_Interfaces
+ }
+ defer delete(buf)
+
+ errno = freebsd.sysctl(mib[:], &buf[0], &needed, nil, 0)
+ if errno != nil {
+ return nil, .Unable_To_Enumerate_Network_Interfaces
+ }
+
+ // Build the interfaces with each message.
+ if_builder: [dynamic]Network_Interface
+ for message_pointer: uintptr = 0; message_pointer < cast(uintptr)needed; /**/ {
+ rtm := cast(^freebsd.Route_Message_Header)&buf[message_pointer]
+ if rtm.version != freebsd.RTM_VERSION {
+ continue
+ }
+
+ #partial switch rtm.type {
+ case .IFINFO:
+ ifm := cast(^freebsd.Interface_Message_Header_Len)&buf[message_pointer]
+ if .IFP not_in ifm.addrs {
+ // No name available.
+ break
+ }
+
+ dl := cast(^freebsd.Socket_Address_Data_Link)&buf[message_pointer + cast(uintptr)ifm.len]
+
+ if_data := cast(^freebsd.Interface_Data)&buf[message_pointer + cast(uintptr)ifm.data_off]
+
+ // This is done this way so the different message types can
+ // dynamically build a `Network_Interface`.
+ resize(&if_builder, max(len(if_builder), 1 + cast(int)ifm.index))
+ interface := if_builder[ifm.index]
+
+ interface.adapter_name = strings.clone_from_bytes(dl.data[0:dl.nlen])
+ interface.mtu = if_data.mtu
+
+ switch if_data.link_state {
+ case .UNKNOWN: /* Do nothing; the default value is valid. */
+ case .UP: interface.link.state |= { .Up }
+ case .DOWN: interface.link.state |= { .Down }
+ }
+
+ // TODO: Uncertain if these are equivalent:
+ // interface.link.transmit_speed = if_data.baudrate
+ // interface.link.receive_speed = if_data.baudrate
+
+ if dl.type == .LOOP {
+ interface.link.state |= { .Loopback }
+ } else {
+ interface.physical_address = physical_address_to_string(dl.data[dl.nlen:][:6])
+ }
+
+ if_builder[ifm.index] = interface
+
+ case .NEWADDR:
+ RTA_MASKS :: freebsd.Route_Address_Flags { .IFA, .NETMASK }
+ ifam := cast(^freebsd.Interface_Address_Message_Header_Len)&buf[message_pointer]
+ if ifam.addrs & RTA_MASKS == {} {
+ break
+ }
+
+ resize(&if_builder, max(len(if_builder), 1 + cast(int)ifam.index))
+ interface := if_builder[ifam.index]
+
+ address_pointer := message_pointer + cast(uintptr)ifam.len
+
+ lease: Lease
+ address_set: bool
+ for address_type in ifam.addrs {
+ ptr := cast(^freebsd.Socket_Address_Basic)&buf[address_pointer]
+
+ #partial switch address_type {
+ case .IFA:
+ #partial switch ptr.family {
+ case .INET:
+ real := cast(^freebsd.Socket_Address_Internet)ptr
+ lease.address = cast(IP4_Address)real.addr.addr8
+ address_set = true
+ case .INET6:
+ real := cast(^freebsd.Socket_Address_Internet6)ptr
+ lease.address = cast(IP6_Address)real.addr.addr16
+ address_set = true
+ }
+ case .NETMASK:
+ #partial switch ptr.family {
+ case .INET:
+ real := cast(^freebsd.Socket_Address_Internet)ptr
+ lease.netmask = cast(Netmask)cast(IP4_Address)real.addr.addr8
+ case .INET6:
+ real := cast(^freebsd.Socket_Address_Internet6)ptr
+ lease.netmask = cast(Netmask)cast(IP6_Address)real.addr.addr16
+ }
+ }
+
+ SALIGN : u8 : size_of(c.long) - 1
+ address_advance: uintptr = ---
+ if ptr.len > 0 {
+ address_advance = cast(uintptr)((ptr.len + SALIGN) & ~SALIGN)
+ } else {
+ address_advance = cast(uintptr)(SALIGN + 1)
+ }
+
+ address_pointer += address_advance
+ }
+
+ if address_set {
+ append(&interface.unicast, lease)
+ }
+
+ if_builder[ifam.index] = interface
+ }
+
+ message_pointer += cast(uintptr)rtm.msglen
+ }
+
+ // Remove any interfaces that were allocated but had no name.
+ #no_bounds_check for i := len(if_builder) - 1; i >= 0; i -= 1 {
+ if len(if_builder[i].adapter_name) == 0 {
+ ordered_remove(&if_builder, i)
+ }
+ }
+
+ return if_builder[:], nil
+}
diff --git a/core/net/socket.odin b/core/net/socket.odin
index 5f137401e..1472eb365 100644
--- a/core/net/socket.odin
+++ b/core/net/socket.odin
@@ -1,4 +1,4 @@
-// +build windows, linux, darwin
+// +build windows, linux, darwin, freebsd
package net
/*
diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin
new file mode 100644
index 000000000..301c68536
--- /dev/null
+++ b/core/net/socket_freebsd.odin
@@ -0,0 +1,365 @@
+//+build freebsd
+package net
+
+import "core:c"
+import "core:sys/freebsd"
+import "core:time"
+
+Fd :: freebsd.Fd
+
+Socket_Option :: enum c.int {
+ // TODO: Test and implement more socket options.
+ // DEBUG
+ // ACCEPTCONN
+ Reuse_Address = cast(c.int)freebsd.Socket_Option.REUSEADDR,
+ Keep_Alive = cast(c.int)freebsd.Socket_Option.KEEPALIVE,
+ // DONTROUTE
+ Broadcast = cast(c.int)freebsd.Socket_Option.BROADCAST,
+ Use_Loopback = cast(c.int)freebsd.Socket_Option.USELOOPBACK,
+ Linger = cast(c.int)freebsd.Socket_Option.LINGER,
+ Out_Of_Bounds_Data_Inline = cast(c.int)freebsd.Socket_Option.OOBINLINE,
+ // REUSEPORT
+ // TIMESTAMP
+ // NOSIGPIPE
+ // ACCEPTFILTER
+ // BINTIME
+ // NO_OFFLOAD
+ // NO_DDP
+ // REUSEPORT_LB
+ // RERROR
+
+ Send_Buffer_Size = cast(c.int)freebsd.Socket_Option.SNDBUF,
+ Receive_Buffer_Size = cast(c.int)freebsd.Socket_Option.RCVBUF,
+ // SNDLOWAT
+ // RCVLOWAT
+ Send_Timeout = cast(c.int)freebsd.Socket_Option.SNDTIMEO,
+ Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
+}
+
+@(private)
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+ sys_family: freebsd.Protocol_Family = ---
+ sys_protocol: freebsd.Protocol = ---
+ sys_socket_type: freebsd.Socket_Type = ---
+
+ switch family {
+ case .IP4: sys_family = .INET
+ case .IP6: sys_family = .INET6
+ }
+
+ switch protocol {
+ case .TCP: sys_protocol = .TCP; sys_socket_type = .STREAM
+ case .UDP: sys_protocol = .UDP; sys_socket_type = .DGRAM
+ }
+
+ new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
+ if errno != nil {
+ err = cast(Create_Socket_Error)errno
+ return
+ }
+
+ switch protocol {
+ case .TCP: return cast(TCP_Socket)new_socket, nil
+ case .UDP: return cast(UDP_Socket)new_socket, nil
+ }
+
+ return
+}
+
+@(private)
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+ if endpoint.port == 0 {
+ return 0, .Port_Required
+ }
+
+ family := family_from_endpoint(endpoint)
+ new_socket := create_socket(family, .TCP) or_return
+ socket = new_socket.(TCP_Socket)
+
+ sockaddr := _endpoint_to_sockaddr(endpoint)
+ errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
+ if errno != nil {
+ err = cast(Dial_Error)errno
+ return
+ }
+
+ return
+}
+
+@(private)
+_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_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
+ }
+ return
+}
+
+@(private)
+_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
+ family := family_from_endpoint(interface_endpoint)
+ new_socket := create_socket(family, .TCP) or_return
+ socket = new_socket.(TCP_Socket)
+
+ bind(socket, interface_endpoint) or_return
+
+ errno := freebsd.listen(cast(Fd)socket, backlog)
+ if errno != nil {
+ err = cast(Listen_Error)errno
+ return
+ }
+
+ return
+}
+
+@(private)
+_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+ sockaddr: freebsd.Socket_Address_Storage
+
+ result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
+ if errno != nil {
+ err = cast(Accept_Error)errno
+ return
+ }
+
+ client = cast(TCP_Socket)result
+ source = _sockaddr_to_endpoint(&sockaddr)
+ return
+}
+
+@(private)
+_close :: proc(socket: Any_Socket) {
+ real_socket := cast(Fd)any_socket_to_socket(socket)
+ // TODO: This returns an error number, but the `core:net` interface does not handle it.
+ _ = freebsd.close(real_socket)
+}
+
+@(private)
+_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+ if len(buf) == 0 {
+ return
+ }
+ result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
+ if errno != nil {
+ err = cast(TCP_Recv_Error)errno
+ return
+ }
+ return cast(int)result, nil
+}
+
+@(private)
+_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+ if len(buf) == 0 {
+ return
+ }
+ from: freebsd.Socket_Address_Storage
+
+ result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
+ if errno != nil {
+ err = cast(UDP_Recv_Error)errno
+ return
+ }
+ return cast(int)result, _sockaddr_to_endpoint(&from), nil
+}
+
+@(private)
+_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+ for bytes_written < len(buf) {
+ limit := min(int(max(i32)), len(buf) - bytes_written)
+ remaining := buf[bytes_written:][:limit]
+
+ result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE)
+ if errno != nil {
+ err = cast(TCP_Send_Error)errno
+ return
+ }
+ bytes_written += cast(int)result
+ }
+ return
+}
+
+@(private)
+_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_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)
+ if errno != nil {
+ err = cast(UDP_Send_Error)errno
+ return
+ }
+ bytes_written += cast(int)result
+ }
+ return
+}
+
+@(private)
+_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_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
+}
+
+@(private)
+_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_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
+ // TODO: Assuming no larger than i32, but the system may accept i64.
+ int_value: i32
+ timeval_value: freebsd.timeval
+
+ ptr: rawptr
+ len: freebsd.socklen_t
+
+ // TODO: Verify that these options perform adequately.
+ switch option {
+ case
+ .Reuse_Address,
+ .Keep_Alive,
+ .Broadcast,
+ .Use_Loopback,
+ .Out_Of_Bounds_Data_Inline:
+ switch real in value {
+ case bool: bool_value = cast(b32)real
+ case b8: bool_value = cast(b32)real
+ case b16: bool_value = cast(b32)real
+ case b32: bool_value = real
+ case b64: bool_value = cast(b32)real
+ case:
+ panic("set_option() value must be a boolean here", loc)
+ }
+ ptr = &bool_value
+ len = size_of(bool_value)
+ case
+ .Linger,
+ .Send_Timeout,
+ .Receive_Timeout:
+ t, ok := value.(time.Duration)
+ if !ok {
+ panic("set_option() value must be a time.Duration here", loc)
+ }
+
+ micros := cast(freebsd.time_t)time.duration_microseconds(t)
+ timeval_value.usec = cast(freebsd.suseconds_t)micros % 1e6
+ timeval_value.sec = (micros - cast(freebsd.time_t)timeval_value.usec) / 1e6
+
+ ptr = &timeval_value
+ len = size_of(timeval_value)
+ case
+ .Receive_Buffer_Size,
+ .Send_Buffer_Size:
+ switch real in value {
+ case i8: int_value = cast(i32)real
+ case u8: int_value = cast(i32)real
+ case i16: int_value = cast(i32)real
+ case u16: int_value = cast(i32)real
+ case i32: int_value = real
+ case u32:
+ if real > u32(max(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case i64:
+ if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case u64:
+ if real > u64(max(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case i128:
+ if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case u128:
+ if real > u128(max(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case int:
+ if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case uint:
+ if real > uint(max(i32)) { return .Value_Out_Of_Range }
+ int_value = cast(i32)real
+ case:
+ panic("set_option() value must be an integer here", loc)
+ }
+ case:
+ unimplemented("set_option() option not yet implemented", 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 nil
+}
+
+@(private)
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_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
+ }
+
+ if should_block {
+ flags &= ~{ .NONBLOCK }
+ } else {
+ flags |= { .NONBLOCK }
+ }
+
+ errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
+ if errno != nil {
+ return cast(Set_Blocking_Error)errno
+ }
+
+ return
+}
+
+@(private)
+_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: freebsd.Socket_Address_Storage) {
+ switch addr in ep.address {
+ case IP4_Address:
+ (cast(^freebsd.Socket_Address_Internet)(&sockaddr))^ = {
+ len = size_of(freebsd.Socket_Address_Internet),
+ family = .INET,
+ port = cast(freebsd.in_port_t)ep.port,
+ addr = transmute(freebsd.IP4_Address)addr,
+ }
+ case IP6_Address:
+ (cast(^freebsd.Socket_Address_Internet6)(&sockaddr))^ = {
+ len = size_of(freebsd.Socket_Address_Internet),
+ family = .INET6,
+ port = cast(freebsd.in_port_t)ep.port,
+ addr = transmute(freebsd.IP6_Address)addr,
+ }
+ }
+ return
+}
+
+@(private)
+_sockaddr_to_endpoint :: proc(native_addr: ^freebsd.Socket_Address_Storage) -> (ep: Endpoint) {
+ #partial switch native_addr.family {
+ case .INET:
+ addr := cast(^freebsd.Socket_Address_Internet)native_addr
+ ep = {
+ address = transmute(IP4_Address)addr.addr.addr8,
+ port = cast(int)addr.port,
+ }
+ case .INET6:
+ addr := cast(^freebsd.Socket_Address_Internet6)native_addr
+ ep = {
+ address = transmute(IP6_Address)addr.addr.addr16,
+ port = cast(int)addr.port,
+ }
+ case:
+ panic("native_addr is neither an IP4 or IP6 address")
+ }
+ return
+}