From d85c2c1ca7d73c5f0513c731a13c80116124b9e4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 22:16:24 +0100 Subject: [PATCH 1/3] Add mDNS/Bonjour/Avahi (.local) support for Windows --- core/net/common.odin | 3 +++ core/net/dns_windows.odin | 7 ++++++- core/net/socket_linux.odin | 4 +++- core/sys/windows/dnsapi.odin | 2 +- core/sys/windows/types.odin | 25 +++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 263fc770fb3..64155313d2e 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -95,6 +95,7 @@ Resolve_Error :: enum u32 { } DNS_Error :: enum u32 { + None = 0, Invalid_Hostname_Error = 1, Invalid_Hosts_Config_Error, Invalid_Resolv_Config_Error, @@ -147,6 +148,8 @@ IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} +IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} + Endpoint :: struct { address: Address, port: int, diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index 2f3831767d2..7736851b843 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -29,9 +29,14 @@ import win "core:sys/windows" _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator + options := win.DNS_QUERY_OPTIONS{} + if strings.has_suffix(hostname, ".local") { + options = {.MULTICAST_ONLY, .MULTICAST_WAIT} // 0x00020500 + } + host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) rec: ^win.DNS_RECORD - res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil) + res := win.DnsQuery_UTF8(host_cstr, u16(type), options, nil, &rec, nil) switch u32(res) { case 0: diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index b7816b0b6e1..cafec747dbb 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -35,6 +35,7 @@ Socket_Option :: enum c.int { Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), + Broadcast = c.int(linux.Socket_Option.BROADCAST), } // Wrappers and unwrappers for system-native types @@ -337,7 +338,8 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := .Reuse_Address, .Keep_Alive, .Out_Of_Bounds_Data_Inline, - .TCP_Nodelay: + .TCP_Nodelay, + .Broadcast: // TODO: verify whether these are options or not on Linux // .Broadcast, <-- yes // .Conditional_Accept, diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin index 4fd9f7a1963..728813696b7 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -5,6 +5,6 @@ foreign import "system:Dnsapi.lib" @(default_calling_convention="system") foreign Dnsapi { - DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- + DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DNS_QUERY_OPTIONS, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- } diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index ab79c682a49..8069659c9fb 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -4576,6 +4576,31 @@ DNS_SRV_DATAA :: struct { _: WORD, // padding } +// See https://learn.microsoft.com/en-us/windows/win32/dns/dns-constants +DNS_QUERY_OPTION :: enum DWORD { + ACCEPT_TRUNCATED_RESPONSE = 0, + DNS_QUERY_USE_TCP_ONLY = 1, + NO_RECURSION = 2, + BYPASS_CACHE = 3, + NO_WIRE_QUERY = 4, + NO_LOCAL_NAME = 5, + NO_HOSTS_FILE = 6, + NO_NETBT = 7, + WIRE_ONLY = 8, + RETURN_MESSAGE = 9, + MULTICAST_ONLY = 10, + NO_MULTICAST = 11, + TREAT_AS_FQDN = 12, + ADDRCONFIG = 13, + DUAL_ADDR = 14, + MULTICAST_WAIT = 17, + MULTICAST_VERIFY = 18, + DONT_RESET_TTL_VALUES = 20, + DISABLE_IDN_ENCODING = 21, + APPEND_MULTILABEL = 23, +} +DNS_QUERY_OPTIONS :: bit_set[DNS_QUERY_OPTION; DWORD] + SOCKADDR :: struct { sa_family: ADDRESS_FAMILY, sa_data: [14]CHAR, From 8998d74a926f52ef02a8f77936922dae3da6085f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 22:55:48 +0100 Subject: [PATCH 2/3] Add mDNS for *nix. --- core/net/common.odin | 1 + core/net/dns.odin | 89 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 64155313d2e..12add82258f 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -149,6 +149,7 @@ IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} +IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353} Endpoint :: struct { address: Address, diff --git a/core/net/dns.odin b/core/net/dns.odin index ffb97fc5b42..b3649c686c9 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -132,7 +132,14 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net return } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_mdns(t.hostname, .IP4, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -159,7 +166,14 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net return t, nil } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_mdns(t.hostname, .IP6, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -283,6 +297,77 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return } +get_dns_records_from_mdns :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + assert(type == .IP4 || type == .IP6) + + context.allocator = allocator + + if !validate_hostname(hostname) { + return nil, .Invalid_Hostname_Error + } + + hdr := DNS_Header{ + id = 0, + is_response = false, + opcode = 0, + is_authoritative = false, + is_truncated = false, + is_recursion_desired = true, + is_recursion_available = false, + response_code = DNS_Response_Code.No_Error, + } + + id, bits := pack_dns_header(hdr) + dns_hdr := [6]u16be{} + dns_hdr[0] = id + dns_hdr[1] = bits + dns_hdr[2] = 1 + + dns_query := [2]u16be{ u16be(type), 1 } + + output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} + b := strings.builder_from_slice(output[:]) + + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) + ok := encode_hostname(&b, hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) + + dns_packet := output[:strings.builder_len(b)] + + dns_response_buf := [4096]u8{} + dns_response: []u8 + + name_server := IP4_mDNS_Broadcast if type == .IP4 else IP6_mDNS_Broadcast + + conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) + if sock_err != nil { + return nil, .Connection_Error + } + defer close(conn) + + send(conn, dns_packet[:], name_server) + + if set_option(conn, .Receive_Timeout, time.Second * 1) != nil { + return nil, .Connection_Error + } + + recv_sz, _, _ := recv_udp(conn, dns_response_buf[:]) + if recv_sz == 0 { + return nil, .Server_Error + } + + dns_response = dns_response_buf[:recv_sz] + + rsp, _ok := parse_response(dns_response, type) + if !_ok { + return nil, .Server_Error + } + return rsp[:], nil +} + // `records` slice is also destroyed. destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { context.allocator = allocator From cc29bdaefc8cc34f3a18e7304224252f446a421d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 23:04:15 +0100 Subject: [PATCH 3/3] Simplify *nix mDNS --- core/net/dns.odin | 81 ++--------------------------------------------- 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/core/net/dns.odin b/core/net/dns.odin index b3649c686c9..6d5dfea23b3 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -135,7 +135,7 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net recs: []DNS_Record if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { - recs, _ = get_dns_records_from_mdns(t.hostname, .IP4, context.temp_allocator) + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP4, {IP4_mDNS_Broadcast}, nil, context.temp_allocator) } else { recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) } @@ -169,7 +169,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net recs: []DNS_Record if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { - recs, _ = get_dns_records_from_mdns(t.hostname, .IP6, context.temp_allocator) + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP6, {IP6_mDNS_Broadcast}, nil, context.temp_allocator) } else { recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) } @@ -269,12 +269,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return nil, .Connection_Error } - // recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:]) - // if recv_err == UDP_Recv_Error.Timeout { - // continue - // } else if recv_err != nil { - // continue - // } recv_sz, _ := recv_udp(conn, dns_response_buf[:]) or_continue if recv_sz == 0 { continue @@ -297,77 +291,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return } -get_dns_records_from_mdns :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { - assert(type == .IP4 || type == .IP6) - - context.allocator = allocator - - if !validate_hostname(hostname) { - return nil, .Invalid_Hostname_Error - } - - hdr := DNS_Header{ - id = 0, - is_response = false, - opcode = 0, - is_authoritative = false, - is_truncated = false, - is_recursion_desired = true, - is_recursion_available = false, - response_code = DNS_Response_Code.No_Error, - } - - id, bits := pack_dns_header(hdr) - dns_hdr := [6]u16be{} - dns_hdr[0] = id - dns_hdr[1] = bits - dns_hdr[2] = 1 - - dns_query := [2]u16be{ u16be(type), 1 } - - output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} - b := strings.builder_from_slice(output[:]) - - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) - ok := encode_hostname(&b, hostname) - if !ok { - return nil, .Invalid_Hostname_Error - } - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) - - dns_packet := output[:strings.builder_len(b)] - - dns_response_buf := [4096]u8{} - dns_response: []u8 - - name_server := IP4_mDNS_Broadcast if type == .IP4 else IP6_mDNS_Broadcast - - conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) - if sock_err != nil { - return nil, .Connection_Error - } - defer close(conn) - - send(conn, dns_packet[:], name_server) - - if set_option(conn, .Receive_Timeout, time.Second * 1) != nil { - return nil, .Connection_Error - } - - recv_sz, _, _ := recv_udp(conn, dns_response_buf[:]) - if recv_sz == 0 { - return nil, .Server_Error - } - - dns_response = dns_response_buf[:recv_sz] - - rsp, _ok := parse_response(dns_response, type) - if !_ok { - return nil, .Server_Error - } - return rsp[:], nil -} - // `records` slice is also destroyed. destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { context.allocator = allocator