Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add module for resolving DNS names to IP addresses #735

Open
yorickpeterse opened this issue Jul 30, 2024 · 8 comments
Open

Add module for resolving DNS names to IP addresses #735

yorickpeterse opened this issue Jul 30, 2024 · 8 comments
Assignees
Labels
feature New things to add to Inko, such as a new standard library module std Changes related to the standard library
Milestone

Comments

@yorickpeterse
Copy link
Collaborator

yorickpeterse commented Jul 30, 2024

Description

Inko's standard library should provide a std.net.dns module, which in turn provides types/methods for looking up DNS names and resolving them to IP addresses. Ideally we'd do so without using libc, by instead parsing /etc/resolv.conf ourselves and querying the DNS servers through the standard library. For macOS some special care is likely needed as it doesn't use resolv.conf (or at least considers it obsolete). What exactly is needed here I'm not yet sure of.

Related issues

Related work

@yorickpeterse yorickpeterse added feature New things to add to Inko, such as a new standard library module std Changes related to the standard library labels Jul 30, 2024
@yorickpeterse
Copy link
Collaborator Author

For the initial setup I think we should just go with using getaddrinfo(). This works across all Unix platforms (including macOS), and is easier to implement compared to a pure-Inko DNS stack. At some point in the future we could add a pure Inko resolver, and only use it on e.g. Linux and FreeBSD.

@yorickpeterse yorickpeterse added this to the 0.16.0 milestone Jul 31, 2024
@yorickpeterse yorickpeterse self-assigned this Jul 31, 2024
@yorickpeterse
Copy link
Collaborator Author

Note to self: reverse DNS is done using getnameinfo().

@yorickpeterse
Copy link
Collaborator Author

A sketch of using getaddrinfo, without handling it blocking the OS thread:

dns.inko
import std.fmt (fmt)
import std.net.ip (IpAddress)
import std.stdio (STDOUT)

let AF_INET = 2
let AF_INET6 = 10
let SOCK_STREAM = 1
let SOCK_DGRAM = 2
let SOCK_RAW = 3

class extern InAddr {
  let @s_addr: UInt32
}

class extern In6Addr {
  let @0: UInt8
  let @1: UInt8
  let @2: UInt8
  let @3: UInt8
  let @4: UInt8
  let @5: UInt8
  let @6: UInt8
  let @7: UInt8
}

class extern SockAddr {
  let @sa_family_t: UInt16
  let @sa_data: Pointer[UInt8]
}

class extern SockAddrIn {
  let @sin_family: UInt16
  let @sin_port: UInt16
  let @sin_addr: InAddr
}

class extern SockAddrIn6 {
  let @sin6_family: UInt16
  let @sin6_port: UInt16
  let @sin6_flowinfo: UInt32
  let @sin6_addr: In6Addr
  let @sin6_scope_id: UInt32
}

class extern AddrInfo {
  let @ai_flags: Int32
  let @ai_family: Int32
  let @ai_socktype: Int32
  let @ai_protocol: Int32
  let @ai_addrlen: Int32
  let @ai_addr: Pointer[SockAddr]
  let @ai_canonname: Pointer[UInt8]
  let @ai_next: Pointer[AddrInfo]
}

fn extern getaddrinfo(
  node: Pointer[UInt8],
  service: Pointer[UInt8],
  hints: Pointer[AddrInfo],
  res: Pointer[AddrInfo],
) -> Int32

fn extern freeaddrinfo(res: Pointer[AddrInfo])

class async Main {
  fn async main {
    let name = 'yorickpeterse.com'
    let port = '80'
    let addr = 0x0 as Pointer[AddrInfo]
    let stdout = STDOUT.new
    let hints = AddrInfo(
      ai_flags: 0 as Int32,
      ai_family: 0 as Int32,
      ai_socktype: SOCK_STREAM as Int32,
      ai_protocol: 0 as Int32,
      ai_addrlen: 0 as Int32,
      ai_addr: 0x0 as Pointer[SockAddr],
      ai_canonname: 0x0 as Pointer[UInt8],
      ai_next: 0x0 as Pointer[AddrInfo],
    )

    match
      getaddrinfo(name.to_pointer, port.to_pointer, mut hints, mut addr) as Int
    {
      case 0 -> {}
      case n -> panic('getaddrinfo() failed: ${n}')
    }

    let mut current = addr

    while current as Int != 0x0 {
      match current.ai_addr.sa_family_t as Int {
        case AF_INET -> {
          let fam = current.ai_family as Int
          let proto = current.ai_protocol as Int
          let typ = current.ai_socktype as Int
          let addr = current.ai_addr as Pointer[SockAddrIn]
          let port = (addr.sin_port as Int).swap_bytes >>> 48
          let raw = addr.sin_addr.s_addr as Int
          let ip = IpAddress.v4(
            raw as UInt8 as Int,
            raw >> 8 as UInt8 as Int,
            raw >> 16 as UInt8 as Int,
            raw >> 24 as UInt8 as Int,
          )

          stdout.print(
            'IPv4: ${ip}, port = ${port}, family = ${fam}, type = ${typ}, proto = ${proto}',
          )
        }
        case AF_INET6 -> {
          let fam = current.ai_family as Int
          let proto = current.ai_protocol as Int
          let typ = current.ai_socktype as Int
          let addr = current.ai_addr as Pointer[SockAddrIn6]
          let port = (addr.sin6_port as Int).swap_bytes >>> 48
          let ip = IpAddress.v6(
            addr.sin6_addr.0 as Int,
            addr.sin6_addr.1 as Int,
            addr.sin6_addr.2 as Int,
            addr.sin6_addr.3 as Int,
            addr.sin6_addr.4 as Int,
            addr.sin6_addr.5 as Int,
            addr.sin6_addr.6 as Int,
            addr.sin6_addr.7 as Int,
          )

          stdout.print(
            'IPv6: ${ip}, port = ${port}, family = ${fam}, type = ${typ}, proto = ${proto}',
          )
        }
        case _ -> panic('unknown socket family')
      }

      current = current.ai_next
    }

    freeaddrinfo(addr)
  }
}

@yorickpeterse
Copy link
Collaborator Author

getaddrinfo has apparently no way to specify a timeout/deadline for the DNS operation, meaning it can (in theory) hand indefinitely. This can lead to outages such as the one described at https://www.uber.com/en-NL/blog/denial-by-dns/.

@yorickpeterse
Copy link
Collaborator Author

Given the hairy nature of getaddrinfo(), I'm leaning more towards a pure-Inko DNS client. This means it won't support everything out of the box (e.g. mDNS might be tricky), but it might be good enough for common cases, and wouldn't suffer from the same issues as getaddrinfo(). We'd still need a way to fetch the name servers though, which at least on macOS isn't trivial.

@yorickpeterse
Copy link
Collaborator Author

For Linux and FreeBSD, parsing resolv.conf is easy enough. For macOS we could probably do something like https://gist.github.com/leiless/5dddcdfbb1d578bd96c44ff727b2cc05, assuming that code is legit.

@yorickpeterse yorickpeterse removed this from the 0.16.0 milestone Jul 31, 2024
@yorickpeterse
Copy link
Collaborator Author

Given the complexity this likely brings, I'll un-assign this from the 0.16.0 milestone for the time being.

@yorickpeterse yorickpeterse removed their assignment Aug 3, 2024
@yorickpeterse yorickpeterse added this to the 0.17.0 milestone Aug 16, 2024
@yorickpeterse yorickpeterse self-assigned this Aug 16, 2024
@yorickpeterse yorickpeterse modified the milestones: 0.17.0, 0.18.0 Sep 5, 2024
@yorickpeterse
Copy link
Collaborator Author

To ensure we have something to offer, we'll start by just using getaddrinfo() instead of implementing our own DNS stack. While on macOS this function may be slow due to macOS' DNS logic being complex, at least on Linux it shouldn't be that big of a deal. If in several years time it turns out we need something more performant, we can look into implementing a pure Inko DNS resolver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New things to add to Inko, such as a new standard library module std Changes related to the standard library
Projects
None yet
Development

No branches or pull requests

1 participant