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