123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859 |
- #+build windows, linux, darwin, freebsd
- 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 <[email protected]>
- Copyright 2022 Colin Davidson <[email protected]>
- Copyright 2022 Jeroen van Rijn <[email protected]>.
- Copyright 2024 Feoramund <[email protected]>.
- 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
- Feoramund: FreeBSD platform code
- */
- import "core:mem"
- import "core:strings"
- import "core:time"
- import "core:os"
- /*
- Default configuration for DNS resolution.
- */
- when ODIN_OS == .Windows {
- DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
- resolv_conf = "",
- hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
- }
- } 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",
- }
- } else {
- #panic("Please add a configuration for this OS.")
- }
- @(init)
- init_dns_configuration :: proc() {
- /*
- Resolve %ENVIRONMENT% placeholders in their paths.
- */
- dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
- dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
- }
- destroy_dns_configuration :: proc() {
- delete(dns_configuration.resolv_conf)
- delete(dns_configuration.hosts_file)
- }
- dns_configuration := DEFAULT_DNS_CONFIGURATION
- // Always allocates for consistency.
- replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
- // Nothing to replace. Return a clone of the original.
- if strings.count(path, "%") != 2 {
- return strings.clone(path, allocator), true
- }
- left := strings.index(path, "%") + 1
- assert(left > 0 && left <= len(path)) // should be covered by there being two %
- right := strings.index(path[left:], "%") + 1
- assert(right > 0 && right <= len(path)) // should be covered by there being two %
- env_key := path[left: right]
- env_val := os.get_env(env_key, allocator)
- defer delete(env_val)
- res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
- return res, true
- }
- /*
- Resolves a hostname to exactly one IP4 and IP6 endpoint.
- It's then up to you which one you use.
- Note that which address you use to open a socket, determines the type of the socket you get.
- Returns `ok=false` if the host name could not be resolved to any endpoints.
- Returned endpoints have the same port as provided in the string, or 0 if absent.
- If you want to use a specific port, just modify the field after the call to this procedure.
- If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped.
- This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get
- back an endpoint in both cases.
- */
- resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {
- target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
- switch t in target {
- case Endpoint:
- // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
- switch _ in t.address {
- case IP4_Address: ep4 = t
- case IP6_Address: ep6 = t
- case: unreachable()
- }
- return
- case Host:
- err4, err6: Network_Error = ---, ---
- ep4, err4 = resolve_ip4(t.hostname)
- ep6, err6 = resolve_ip6(t.hostname)
- ep4.port = t.port if err4 == nil else 0
- ep6.port = t.port if err6 == nil else 0
- if err4 != nil && err6 != nil {
- err = err4
- }
- return
- }
- 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 {
- case Endpoint:
- // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
- switch _ in t.address {
- case IP4_Address:
- return t, nil
- case IP6_Address:
- err = .Unable_To_Resolve
- return
- }
- case Host:
- recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
- if len(recs) == 0 {
- err = .Unable_To_Resolve
- return
- }
- ep4 = {
- address = recs[0].(DNS_Record_IP4).address,
- port = t.port,
- }
- return
- }
- 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 {
- case Endpoint:
- // NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
- switch _ in t.address {
- case IP4_Address:
- err = .Unable_To_Resolve
- return
- case IP6_Address:
- return t, nil
- }
- case Host:
- recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
- if len(recs) == 0 {
- err = .Unable_To_Resolve
- return
- }
- ep6 = {
- address = recs[0].(DNS_Record_IP6).address,
- port = t.port,
- }
- return
- }
- unreachable()
- }
- /*
- 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)
- }
- /*
- A generic DNS client usable on any platform.
- 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.
- 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
- if type != .SRV {
- // NOTE(tetra): 'hostname' can contain underscores when querying SRV records
- ok := validate_hostname(hostname)
- if !ok {
- 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
- for name_server in name_servers {
- 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) or_continue
- if set_option(conn, .Receive_Timeout, time.Second * 1) != nil {
- 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
- }
- dns_response = dns_response_buf[:recv_sz]
- rsp, _ok := parse_response(dns_response, type)
- if !_ok {
- return nil, .Server_Error
- }
- if len(rsp) == 0 {
- continue
- }
- return rsp[:], nil
- }
- return
- }
- // `records` slice is also destroyed.
- destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
- context.allocator = allocator
- for rec in records {
- switch r in rec {
- case DNS_Record_IP4:
- delete(r.base.record_name)
- case DNS_Record_IP6:
- delete(r.base.record_name)
- case DNS_Record_CNAME:
- delete(r.base.record_name)
- delete(r.host_name)
- case DNS_Record_TXT:
- delete(r.base.record_name)
- delete(r.value)
- case DNS_Record_NS:
- delete(r.base.record_name)
- delete(r.host_name)
- case DNS_Record_MX:
- delete(r.base.record_name)
- delete(r.host_name)
- case DNS_Record_SRV:
- delete(r.record_name)
- delete(r.target)
- }
- }
- delete(records, allocator)
- }
- /*
- TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP
- or is that what recursion desired does? Do we need to handle recursion unavailable?
- How do we deal with is_authoritative / is_truncated?
- */
- NAME_MAX :: 255
- LABEL_MAX :: 63
- pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) {
- id = hdr.id
- bits = hdr.opcode << 1 | u16be(hdr.response_code)
- if hdr.is_response {
- bits |= 1 << 15
- }
- if hdr.is_authoritative {
- bits |= 1 << 10
- }
- if hdr.is_truncated {
- bits |= 1 << 9
- }
- if hdr.is_recursion_desired {
- bits |= 1 << 8
- }
- if hdr.is_recursion_available {
- bits |= 1 << 7
- }
- return id, bits
- }
- unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
- hdr.id = id
- hdr.is_response = (bits & (1 << 15)) != 0
- hdr.opcode = (bits >> 11) & 0xF
- hdr.is_authoritative = (bits & (1 << 10)) != 0
- hdr.is_truncated = (bits & (1 << 9)) != 0
- hdr.is_recursion_desired = (bits & (1 << 8)) != 0
- hdr.is_recursion_available = (bits & (1 << 7)) != 0
- hdr.response_code = DNS_Response_Code(bits & 0xF)
- return hdr
- }
- load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
- context.allocator = allocator
- res := os.read_entire_file_from_filename(resolv_conf_path) or_return
- defer delete(res)
- resolv_str := string(res)
- id_str := "nameserver"
- id_len := len(id_str)
- _name_servers := make([dynamic]Endpoint, 0, allocator)
- for line in strings.split_lines_iterator(&resolv_str) {
- if len(line) == 0 || line[0] == '#' {
- continue
- }
- if len(line) < id_len || strings.compare(line[:id_len], id_str) != 0 {
- continue
- }
- server_ip_str := strings.trim_left_space(line[id_len:])
- if len(server_ip_str) == 0 {
- continue
- }
- addr := parse_address(server_ip_str)
- if addr == nil {
- continue
- }
- endpoint := Endpoint{
- addr,
- 53,
- }
- append(&_name_servers, endpoint)
- }
- return _name_servers[:], true
- }
- load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
- context.allocator = allocator
- res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
- defer delete(res)
- _hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
- hosts_str := string(res)
- for line in strings.split_lines_iterator(&hosts_str) {
- if len(line) == 0 || line[0] == '#' {
- continue
- }
- splits := strings.fields(line)
- defer delete(splits)
- ip_str := splits[0]
- addr := parse_address(ip_str)
- if addr == nil {
- continue
- }
- for hostname in splits[1:] {
- if len(hostname) != 0 {
- append(&_hosts, DNS_Host_Entry{hostname, addr})
- }
- }
- }
- return _hosts[:], true
- }
- // www.google.com -> 3www6google3com0
- encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) {
- _hostname := hostname
- for section in strings.split_iterator(&_hostname, ".") {
- if len(section) > LABEL_MAX {
- return
- }
- strings.write_byte(b, u8(len(section)))
- strings.write_string(b, section)
- }
- strings.write_byte(b, 0)
- return true
- }
- skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) {
- out_size := 0
- cur_idx := start_idx
- iteration_max := 0
- top: for cur_idx < len(packet) {
- if packet[cur_idx] == 0 {
- out_size += 1
- break
- }
- if iteration_max > 255 {
- return
- }
- if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
- return
- }
- switch packet[cur_idx] {
- case 0xC0:
- out_size += 2
- break top
- case:
- label_size := int(packet[cur_idx]) + 1
- idx2 := cur_idx + label_size
- if idx2 < cur_idx + 1 || idx2 > len(packet) {
- return
- }
- out_size += label_size
- cur_idx = idx2
- }
- iteration_max += 1
- }
- if start_idx + out_size > len(packet) {
- return
- }
- return out_size, true
- }
- decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {
- output := [NAME_MAX]u8{}
- b := strings.builder_from_slice(output[:])
- // If you're on level 0, update out_bytes, everything through a pointer
- // doesn't count towards this hostname's packet length
- // Evaluate tokens to generate the hostname
- out_size := 0
- level := 0
- print_size := 0
- cur_idx := start_idx
- iteration_max := 0
- labels_added := 0
- for cur_idx < len(packet) {
- if packet[cur_idx] == 0 {
- if (level == 0) {
- out_size += 1
- }
- break
- }
- if iteration_max > 255 {
- return
- }
- if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
- return
- }
- switch packet[cur_idx] {
- // This is a offset to more data in the packet, jump to it
- case 0xC0:
- pkt := packet[cur_idx:cur_idx+2]
- val := (^u16be)(raw_data(pkt))^
- offset := int(val & 0x3FFF)
- if offset > len(packet) {
- return
- }
- cur_idx = offset
- if (level == 0) {
- out_size += 2
- level += 1
- }
- // This is a label, insert it into the hostname
- case:
- label_size := int(packet[cur_idx])
- idx2 := cur_idx + label_size + 1
- if idx2 < cur_idx + 1 || idx2 > len(packet) {
- return
- }
- if print_size + label_size + 1 > NAME_MAX {
- return
- }
- if labels_added > 0 {
- strings.write_byte(&b, '.')
- }
- strings.write_bytes(&b, packet[cur_idx+1:idx2])
- print_size += label_size + 1
- labels_added += 1
- cur_idx = idx2
- if (level == 0) {
- out_size += label_size + 1
- }
- }
- iteration_max += 1
- }
- if start_idx + out_size > len(packet) {
- return
- }
- return strings.clone(strings.to_string(b), allocator), out_size, true
- }
- // Uses RFC 952 & RFC 1123
- validate_hostname :: proc(hostname: string) -> (ok: bool) {
- if len(hostname) > 255 || len(hostname) == 0 {
- return
- }
- if hostname[0] == '-' {
- return
- }
- _hostname := hostname
- for label in strings.split_iterator(&_hostname, ".") {
- if len(label) > 63 || len(label) == 0 {
- return
- }
- for ch in label {
- switch ch {
- case:
- return
- case 'a'..='z', 'A'..='Z', '0'..='9', '-':
- continue
- }
- }
- }
- return true
- }
- parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) {
- record_buf := packet[cur_off^:]
- srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return
- // TODO(tetra): Not sure what we should call this.
- // Is it really only used in SRVs?
- // Maybe some refactoring is required?
- ahdr_sz := size_of(DNS_Record_Header)
- if len(record_buf) - hn_sz < ahdr_sz {
- return
- }
- record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz]
- record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes)
- data_sz := record_hdr.length
- data_off := cur_off^ + int(hn_sz) + int(ahdr_sz)
- data := packet[data_off:data_off+int(data_sz)]
- cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz)
- if u16be(filter) != record_hdr.type {
- return nil, true
- }
- _record: DNS_Record
- #partial switch DNS_Record_Type(record_hdr.type) {
- case .IP4:
- if len(data) != 4 {
- return
- }
- addr := (^IP4_Address)(raw_data(data))^
- _record = DNS_Record_IP4{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- address = addr,
- }
- case .IP6:
- if len(data) != 16 {
- return
- }
- addr := (^IP6_Address)(raw_data(data))^
- _record = DNS_Record_IP6{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- address = addr,
- }
- case .CNAME:
- hostname, _ := decode_hostname(packet, data_off) or_return
- _record = DNS_Record_CNAME{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- host_name = hostname,
- }
- case .TXT:
- _record = DNS_Record_TXT{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- value = strings.clone(string(data)),
- }
- case .NS:
- name, _ := decode_hostname(packet, data_off) or_return
- _record = DNS_Record_NS{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- host_name = name,
- }
- case .SRV:
- if len(data) <= 6 {
- return
- }
- _data := mem.slice_data_cast([]u16be, data)
- priority, weight, port := _data[0], _data[1], _data[2]
- target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return
- // NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
- // The record name is the name of the record.
- // Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
- // by making this request in the first place.
- // NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
- // It's already cloned, after all. I wouldn't put them on the temp allocator like this.
- parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator)
- if len(parts) != 3 {
- return
- }
- service_name, protocol_name := parts[0], parts[1]
- _record = DNS_Record_SRV{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- target = target,
- service_name = service_name,
- protocol_name = protocol_name,
- priority = int(priority),
- weight = int(weight),
- port = int(port),
- }
- case .MX:
- if len(data) <= 2 {
- return
- }
- preference: u16be = mem.slice_data_cast([]u16be, data)[0]
- hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return
- _record = DNS_Record_MX{
- base = DNS_Record_Base{
- record_name = strings.clone(srv_record_name),
- ttl_seconds = u32(record_hdr.ttl),
- },
- host_name = hostname,
- preference = int(preference),
- }
- case:
- return
- }
- return _record, true
- }
- /*
- DNS Query Response Format:
- - DNS_Header (packed)
- - Query Count
- - Answer Count
- - Authority Count
- - Additional Count
- - Query[]
- - Hostname -- encoded
- - Type
- - Class
- - Answer[]
- - DNS Record Data
- - Authority[]
- - DNS Record Data
- - Additional[]
- - DNS Record Data
- DNS Record Data:
- - DNS_Record_Header
- - Data[]
- */
- parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
- 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])
- hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
- if !hdr.is_response {
- return
- }
- question_count := int(dns_hdr_chunks[2])
- if question_count != 1 {
- return
- }
- answer_count := int(dns_hdr_chunks[3])
- authority_count := int(dns_hdr_chunks[4])
- additional_count := int(dns_hdr_chunks[5])
- cur_idx := HEADER_SIZE_BYTES
- for _ in 0..<question_count {
- if cur_idx == len(response) {
- continue
- }
- dq_sz :: 4
- hn_sz := skip_hostname(response, cur_idx) or_return
- cur_idx += hn_sz + dq_sz
- }
- for _ in 0..<answer_count {
- if cur_idx == len(response) {
- continue
- }
- rec := parse_record(response, &cur_idx, filter) or_return
- if rec != nil {
- append(&_records, rec)
- }
- }
- for _ in 0..<authority_count {
- if cur_idx == len(response) {
- continue
- }
- rec := parse_record(response, &cur_idx, filter) or_return
- if rec != nil {
- append(&_records, rec)
- }
- }
- for _ in 0..<additional_count {
- if cur_idx == len(response) {
- continue
- }
- rec := parse_record(response, &cur_idx, filter) or_return
- if rec != nil {
- append(&_records, rec)
- }
- }
- return _records[:], true
- }
|