-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: add std.net.dns for DNS lookups
This fixes #735. Changelog: added
- Loading branch information
1 parent
505a78e
commit bd8108f
Showing
9 changed files
with
356 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import std.io (Error) | ||
import std.net.ip (IpAddress) | ||
import std.sys.linux.dns (self as sys) if linux | ||
import std.sys.unix.dns (self as sys) if mac | ||
import std.sys.unix.dns (self as sys) if freebsd | ||
import std.time (ToInstant) | ||
|
||
# A type that can resolve DNS queries, such as resolving a hostname into a list | ||
# of IP addresses. | ||
trait Resolve { | ||
# Sets the point in time after which IO operations must time out. | ||
fn pub mut timeout_after=[T: ToInstant](deadline: ref T) | ||
|
||
# Resets the deadline stored in `self`. | ||
fn pub mut reset_deadline | ||
|
||
# Resolves the given hostname into an array of IP addresses. | ||
# | ||
# If the underlying IO operations fail or time out, an `Error` is returned. | ||
fn pub mut resolve(host: String) -> Result[Array[IpAddress], Error] | ||
} | ||
|
||
# A type for resolving DNS queries. | ||
type pub inline Resolver { | ||
let @inner: Resolve | ||
|
||
fn pub static new -> Resolver { | ||
Resolver(sys.resolver) | ||
} | ||
} | ||
|
||
impl Resolve for Resolver { | ||
fn pub mut timeout_after=[T: ToInstant](deadline: ref T) { | ||
@inner.timeout_after = deadline | ||
} | ||
|
||
fn pub mut reset_deadline { | ||
@inner.reset_deadline | ||
} | ||
|
||
fn pub mut resolve(host: String) -> Result[Array[IpAddress], Error] { | ||
@inner.resolve(host) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import std.io (Buffer, Error) | ||
import std.json (Json) | ||
import std.libc | ||
import std.net.dns (Resolve) | ||
import std.net.ip (IpAddress) | ||
import std.net.socket (UnixClient) | ||
import std.sys.unix.dns (resolver as system_resolver) | ||
import std.sys.unix.net (self as net) | ||
import std.time (ToInstant) | ||
|
||
let RESOLVE_HOST = 'io.systemd.Resolve.ResolveHostname' | ||
let VARLINK_SOCKET = '/run/systemd/resolve/io.systemd.Resolve' | ||
let READ_SIZE = 8 * 1024 | ||
let NX_DOMAIN = 3 | ||
|
||
# Returns a new `ResolveHostname` message. | ||
fn resolve_host(host: String) -> String { | ||
let msg = Map.new | ||
let params = Map.new | ||
|
||
params.set('name', Json.String(host)) | ||
params.set('family', Json.Int(libc.AF_UNSPEC)) | ||
msg.set('method', Json.String(RESOLVE_HOST)) | ||
msg.set('parameters', Json.Object(params)) | ||
Json.Object(msg).to_string | ||
} | ||
|
||
# Parses the response of the `ResolveHostname` call. | ||
fn parse_resolve_host_response(json: Json) -> Result[Array[IpAddress], Error] { | ||
let params = json.query.key('parameters') | ||
|
||
if json.query.key('error').as_string.some? { | ||
match params.key('rcode').as_int { | ||
case Some(NX_DOMAIN) or None -> throw Error.NotFound | ||
case Some(e) -> throw Error.Other(e) | ||
} | ||
} | ||
|
||
params.key('addresses').as_array.get.iter.try_reduce([], fn (vals, val) { | ||
let fam = try val.query.key('family').as_int.ok_or(Error.InvalidData) | ||
let capa = if fam == libc.AF_INET6 { 16 } else { 4 } | ||
let addr = try val | ||
.query | ||
.key('address') | ||
.as_array | ||
.ok_or(Error.InvalidData) | ||
.then(fn (nums) { | ||
nums.iter.try_reduce(ByteArray.with_capacity(capa), fn (bytes, num) { | ||
try num.query.as_int.ok_or(Error.InvalidData).map(fn (v) { | ||
bytes.push(v) | ||
}) | ||
Result.Ok(bytes) | ||
}) | ||
}) | ||
|
||
let ptr = addr.to_pointer | ||
let ip = match fam { | ||
case libc.AF_INET if addr.size == 4 -> { | ||
net.parse_v4_address(ptr as Pointer[UInt8]) | ||
} | ||
case libc.AF_INET6 if addr.size == 16 -> { | ||
net.parse_v6_address(ptr as Pointer[UInt16]) | ||
} | ||
case _ -> throw Error.InvalidData | ||
} | ||
|
||
vals.push(ip) | ||
Result.Ok(vals) | ||
}) | ||
} | ||
|
||
# A resolver that uses systemd-resolve's through its | ||
# [varlink](https://varlink.org/) API. | ||
type SystemdResolver { | ||
let @socket: UnixClient | ||
let @buffer: ByteArray | ||
|
||
fn static new -> Option[SystemdResolver] { | ||
let sock = try UnixClient.new(VARLINK_SOCKET.to_path).ok | ||
let buf = ByteArray.new | ||
|
||
Option.Some(SystemdResolver(socket: sock, buffer: buf)) | ||
} | ||
} | ||
|
||
impl Resolve for SystemdResolver { | ||
fn pub mut timeout_after=[T: ToInstant](deadline: ref T) { | ||
@socket.socket.timeout_after = deadline | ||
} | ||
|
||
fn pub mut reset_deadline { | ||
@socket.socket.reset_deadline | ||
} | ||
|
||
fn pub mut resolve(host: String) -> Result[Array[IpAddress], Error] { | ||
@buffer.append(resolve_host(host)) | ||
@buffer.push(0) | ||
try @socket.write_bytes(@buffer) | ||
@buffer.clear | ||
|
||
# Read until the trailing NULL byte. | ||
loop { | ||
match try @socket.read(into: @buffer, size: READ_SIZE) { | ||
case 0 -> break | ||
case _ if @buffer.last.or(-1) == 0 -> { | ||
@buffer.pop | ||
break | ||
} | ||
case _ -> {} | ||
} | ||
} | ||
|
||
# The JSON itself should always be valid, so we don't need to handle parsing | ||
# errors in a better way. | ||
let res = Json | ||
.parse(Buffer.new(@buffer)) | ||
.map_error(fn (_) { Error.InvalidData }) | ||
.then(fn (v) { parse_resolve_host_response(v) }) | ||
|
||
@buffer.clear | ||
res | ||
} | ||
} | ||
|
||
fn inline resolver -> Resolve { | ||
# If systemd-resolve is present then we try to use its varlink interface. In | ||
# the rare case that the socket is available but for some reason we can't | ||
# connect to it, we fall back to using the system resolver. | ||
if VARLINK_SOCKET.to_path.exists? { | ||
match SystemdResolver.new { | ||
case Some(r) -> return r as Resolve | ||
case _ -> {} | ||
} | ||
} | ||
|
||
system_resolver | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import std.io (Error, start_blocking, stop_blocking) | ||
import std.libc | ||
import std.libc.freebsd (self as sys_libc) if freebsd | ||
import std.libc.linux (self as sys_libc) if linux | ||
import std.libc.mac (self as sys_libc) if mac | ||
import std.net.dns (Resolve) | ||
import std.net.ip (IpAddress) | ||
import std.sys.unix.net (self as sys_net) if unix | ||
import std.time (ToInstant) | ||
|
||
# A resolver that uses `getaddrinfo()`. | ||
type Resolver { | ||
fn inline static new -> Resolver { | ||
Resolver() | ||
} | ||
} | ||
|
||
impl Resolve for Resolver { | ||
fn pub mut timeout_after=[T: ToInstant](deadline: ref T) { | ||
# getaddrinfo() doesn't support timeouts. | ||
} | ||
|
||
fn pub mut reset_deadline { | ||
# getaddrinfo() doesn't support timeouts. | ||
} | ||
|
||
fn pub mut resolve(host: String) -> Result[Array[IpAddress], Error] { | ||
let hints = sys_libc.AddrInfo( | ||
ai_flags: 0 as Int32, | ||
ai_family: 0 as Int32, | ||
ai_socktype: libc.SOCK_STREAM as Int32, | ||
ai_protocol: 0 as Int32, | ||
ai_addrlen: 0 as UInt64, | ||
ai_addr: 0x0 as Pointer[sys_libc.SockAddr], | ||
ai_canonname: 0x0 as Pointer[UInt8], | ||
ai_next: 0x0 as Pointer[sys_libc.AddrInfo], | ||
) | ||
let list = 0x0 as Pointer[sys_libc.AddrInfo] | ||
|
||
start_blocking | ||
|
||
let ptr = host.to_pointer | ||
let res = libc.getaddrinfo(ptr, ''.to_pointer, mut hints, mut list) | ||
let errno = stop_blocking | ||
|
||
match res as Int { | ||
case 0 -> {} | ||
case libc.EAI_NONAME or libc.EAI_SERVICE -> throw Error.NotFound | ||
case libc.EAI_SYSTEM -> throw Error.from_os_error(errno) | ||
case e -> throw Error.Other(e) | ||
} | ||
|
||
let mut cur = list | ||
let ips = [] | ||
|
||
while cur as Int != 0 { | ||
let addr_ptr = cur.ai_addr as Pointer[sys_libc.SockAddrStorage] | ||
|
||
ips.push(sys_net.parse_ip_socket_address(addr_ptr).0) | ||
cur = cur.ai_next | ||
} | ||
|
||
libc.freeaddrinfo(list) | ||
Result.Ok(ips) | ||
} | ||
} | ||
|
||
fn inline resolver -> Resolve { | ||
Resolver.new as Resolve | ||
} |
Oops, something went wrong.