-
-
Notifications
You must be signed in to change notification settings - Fork 43
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 7e98311
Showing
8 changed files
with
252 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,26 @@ | ||
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 | ||
|
||
# A type that can resolve DNS queries, such as resolving a hostname into a list | ||
# of IP addresses. | ||
trait Resolve { | ||
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 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,124 @@ | ||
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) | ||
|
||
# The varlink method to use for resolving a hostname. | ||
let RESOLVE_HOST = 'io.systemd.Resolve.ResolveHostname' | ||
|
||
# The path to the systemd varlink socket. | ||
let VARLINK_SOCKET = '/run/systemd/resolve/io.systemd.Resolve' | ||
|
||
# The amount of bytes to read from a socket in a single call. | ||
let READ_SIZE = 8 * 1024 | ||
|
||
# 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) -> Array[IpAddress] { | ||
json | ||
.query | ||
.key('parameters') | ||
.key('addresses') | ||
.as_array | ||
.get | ||
.iter | ||
.select_map(fn (val) { | ||
let fam = try val.query.key('family').as_int | ||
let capa = if fam == libc.AF_INET6 { 16 } else { 4 } | ||
let addr = try val.query.key('address').as_array.then(fn (nums) { | ||
nums | ||
.iter | ||
.try_reduce(ByteArray.with_capacity(capa), fn (bytes, num) { | ||
try num.query.as_int.ok_or(nil).map(fn (v) { bytes.push(v) }) | ||
Result.Ok(bytes) | ||
}) | ||
.ok | ||
}) | ||
|
||
match fam { | ||
case libc.AF_INET if addr.size == 4 -> { | ||
Option.Some(net.parse_v4_address(addr.to_pointer as Pointer[UInt8])) | ||
} | ||
case libc.AF_INET6 if addr.size == 16 -> { | ||
Option.Some(net.parse_v6_address(addr.to_pointer as Pointer[UInt16])) | ||
} | ||
case _ -> Option.None | ||
} | ||
}) | ||
.to_array | ||
} | ||
|
||
# A resolver that uses systemd-resolve's through its varlink | ||
# (https://varlink.org/) protocol. | ||
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 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 _ -> {} | ||
} | ||
} | ||
|
||
# At this point it doesn't really matter what error we throw in case of | ||
# invalid JSON, because outside of unexpected systemd changes we're unlikely | ||
# to ever throw an error in the first place. | ||
let res = Json | ||
.parse(Buffer.new(@buffer)) | ||
.ok | ||
.map(fn (v) { parse_resolve_host_response(v) }) | ||
.ok_or(Error.InvalidData) | ||
|
||
@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.file? { | ||
SystemdResolver.new.map(fn (r) { r as Resolve }).or_else(fn { | ||
system_resolver | ||
}) | ||
} else { | ||
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,71 @@ | ||
import std.io (Error) | ||
import std.libc.linux (self as linux) | ||
import std.net.dns (Resolve) | ||
import std.net.ip (IpAddress) | ||
import std.sys.unix.net (self as sys) if unix | ||
|
||
# TODO: push into std.libc | ||
type extern AddrInfo { | ||
let @ai_flags: Int32 | ||
let @ai_family: Int32 | ||
let @ai_socktype: Int32 | ||
let @ai_protocol: Int32 | ||
let @ai_addrlen: UInt64 | ||
let @ai_addr: Pointer[linux.SockAddr] | ||
let @ai_canonname: Pointer[UInt8] | ||
let @ai_next: Pointer[AddrInfo] | ||
} | ||
|
||
# TODO: push into std.libc | ||
fn extern getaddrinfo( | ||
node: Pointer[UInt8], | ||
service: Pointer[UInt8], | ||
hints: Pointer[AddrInfo], | ||
res: Pointer[AddrInfo], | ||
) -> Int32 | ||
|
||
# TODO: push into std.libc | ||
fn extern freeaddrinfo(addr: Pointer[AddrInfo]) | ||
|
||
type Resolver { | ||
fn inline static new -> Resolver { | ||
Resolver() | ||
} | ||
} | ||
|
||
impl Resolve for Resolver { | ||
fn pub mut resolve(host: String) -> Result[Array[IpAddress], Error] { | ||
let hints = AddrInfo( | ||
ai_flags: 0 as Int32, | ||
ai_family: 0 as Int32, | ||
ai_socktype: linux.SOCK_STREAM as Int32, | ||
ai_protocol: 0 as Int32, | ||
ai_addrlen: 0 as UInt64, | ||
ai_addr: 0x0 as Pointer[linux.SockAddr], | ||
ai_canonname: 0x0 as Pointer[UInt8], | ||
ai_next: 0x0 as Pointer[AddrInfo], | ||
) | ||
let list = 0x0 as Pointer[AddrInfo] | ||
let res = getaddrinfo(host.to_pointer, ''.to_pointer, mut hints, mut list) | ||
|
||
# TODO: better error handling | ||
if res as Int != 0 { panic('getaddrinfo(3) failed') } | ||
|
||
let mut cur = list | ||
let ips = [] | ||
|
||
while cur as Int != 0 { | ||
let addr_ptr = cur.ai_addr as Pointer[linux.SockAddrStorage] | ||
|
||
ips.push(sys.parse_ip_socket_address(addr_ptr).0) | ||
cur = cur.ai_next | ||
} | ||
|
||
freeaddrinfo(list) | ||
Result.Ok(ips) | ||
} | ||
} | ||
|
||
fn inline resolver -> Resolve { | ||
Resolver.new as Resolve | ||
} |
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