diff options
| author | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-26 01:18:43 -0400 |
|---|---|---|
| committer | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-26 10:05:24 -0400 |
| commit | e61d893a749d6219c498152bb53d750a384d13f4 (patch) | |
| tree | 37ab259dc9e08eb43cf193f0b8b2034ad1b9dadf /core/net/socket_freebsd.odin | |
| parent | 10ce76fcc2ce45a28858ca1562e2b802a781ee58 (diff) | |
Port `core:net` to FreeBSD
Diffstat (limited to 'core/net/socket_freebsd.odin')
| -rw-r--r-- | core/net/socket_freebsd.odin | 365 |
1 files changed, 365 insertions, 0 deletions
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 +} |