dns
stableDNS lookup, IP address parsing, hostname validation, and DNS record formatting utilities for network-aware Zolo programs.
use plugin dns::{lookup, lookup_first, resolve, …} Functions (11)
- lookup Resolves a hostname to all its IP addresses
- lookup_first Resolves a hostname to its first IP address
- resolve Resolves a host:port string to socket addresses
- is_ipv4 Returns true if a string is a valid IPv4 address
- is_ipv6 Returns true if a string is a valid IPv6 address
- is_loopback Returns true if an IP is a loopback address
- is_private Returns true if an IP is a private network address
- parse_ip Parses an IP string into a detail table
- reverse_lookup_str Converts an IP to its reverse DNS ARPA form
- format_record Formats a DNS record in zone file syntax
- is_valid_hostname Validates a hostname against RFC rules
Overview
The dns plugin gives Zolo programs network-awareness without pulling in an external resolver library. It wraps the operating system's name resolver to turn hostnames into IP addresses, and adds a toolkit of pure string utilities for classifying IPs (IPv4 vs IPv6, loopback, private), validating hostnames, building reverse-DNS ARPA names, and formatting zone-file records. Every function is stateless — there are no handles or connections to manage; you call a function with strings and integers and get a value back. Reach for it whenever you need to resolve a host, sanity-check user-supplied addresses, or emit DNS records.
Common patterns
Resolve a hostname and inspect the address it returned:
use plugin dns::{lookup_first, is_private, is_ipv6}
let ip = lookup_first("example.com")
if ip != nil {
print("resolved to {ip}")
print("private? {is_private(ip)}")
print("ipv6? {is_ipv6(ip)}")
}
Validate a hostname before trying to resolve it, so a typo doesn't reach the resolver:
use plugin dns::{is_valid_hostname, lookup}
let host = "my-service.internal"
if is_valid_hostname(host) {
let ips = lookup(host)
print("first address: {ips[1]}")
} else {
print("'{host}' is not a valid hostname")
}
Build a zone-file fragment and the matching reverse-DNS name for an address:
use plugin dns::{format_record, reverse_lookup_str}
let ip = "93.184.216.34"
print(format_record("A", "example.com.", ip))
print(reverse_lookup_str(ip))
// "34.216.184.93.in-addr.arpa"
Resolves a hostname to all its IP addresses
Resolves a hostname to all its IP addresses using the system resolver. Returns a table (array) of IP address strings. Duplicates are removed.
use plugin dns::{lookup}
let ips = lookup("example.com")
print(ips[1]) // e.g. "93.184.216.34"
Iterate over every address a host resolves to:
use plugin dns::{lookup}
let ips = lookup("localhost")
for ip in ips {
print("address: {ip}")
}
Resolves a hostname to its first IP address
Resolves a hostname and returns only the first IP address, or nil if resolution fails. Useful when you only need one address.
use plugin dns::{lookup_first}
let ip = lookup_first("example.com")
if ip != nil {
print("Resolved: {ip}")
}
Resolves a host:port string to socket addresses
Resolves a "host:port" string to a table of socket address strings (e.g. "93.184.216.34:80"). Use when you need the port preserved in the result.
use plugin dns::{resolve}
let addrs = resolve("example.com:80")
print(addrs[1]) // "93.184.216.34:80"
Returns true if a string is a valid IPv4 address
Returns true if the string is a valid IPv4 address, false otherwise.
use plugin dns::{is_ipv4}
print(is_ipv4("192.168.1.1")) // true
print(is_ipv4("::1")) // false
print(is_ipv4("not-an-ip")) // false
Returns true if a string is a valid IPv6 address
Returns true if the string is a valid IPv6 address, false otherwise.
use plugin dns::{is_ipv6}
print(is_ipv6("::1")) // true
print(is_ipv6("192.168.1.1")) // false
Returns true if an IP is a loopback address
Returns true if the IP address is a loopback address (127.x.x.x for IPv4 or ::1 for IPv6).
use plugin dns::{is_loopback}
print(is_loopback("127.0.0.1")) // true
print(is_loopback("::1")) // true
print(is_loopback("8.8.8.8")) // false
Returns true if an IP is a private network address
Returns true if the IP address is in a private network range: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 for IPv4, or fc00::/7 for IPv6.
use plugin dns::{is_private}
print(is_private("192.168.0.1")) // true
print(is_private("10.0.0.5")) // true
print(is_private("8.8.8.8")) // false
Parses an IP string into a detail table
Parses an IP address string into a table with fields: address (normalized), kind ("v4" or "v6"), is_loopback, and is_unspecified. Errors if the string is not a valid IP.
use plugin dns::{parse_ip}
let info = parse_ip("192.168.1.100")
print(info["kind"]) // "v4"
print(info["is_loopback"]) // false
Use parse_ip to branch on address family and flag special addresses:
use plugin dns::{parse_ip}
let info = parse_ip("::1")
print("normalized: {info["address"]}") // "::1"
print("kind: {info["kind"]}") // "v6"
if info["is_loopback"] {
print("this is a loopback address")
}
Converts an IP to its reverse DNS ARPA form
Returns the reverse DNS ARPA form of an IP address. For IPv4, this is d.c.b.a.in-addr.arpa; for IPv6, it is the full nibble-reversed .ip6.arpa form.
use plugin dns::{reverse_lookup_str}
print(reverse_lookup_str("93.184.216.34"))
// "34.216.184.93.in-addr.arpa"
IPv6 addresses produce the full nibble-reversed .ip6.arpa name:
use plugin dns::{reverse_lookup_str}
print(reverse_lookup_str("::1"))
// "1.0.0. ... .0.0.ip6.arpa"
Formats a DNS record in zone file syntax
Formats a DNS record in standard zone file syntax. Supports A, AAAA, CNAME, MX, TXT, NS, SOA, and SRV record types. ttl defaults to 3600.
use plugin dns::{format_record}
print(format_record("A", "example.com.", "93.184.216.34"))
// "example.com. 3600 IN A 93.184.216.34"
print(format_record("MX", "example.com.", "mail.example.com.", 3600, 10))
// "example.com. 3600 IN MX 10 mail.example.com."
print(format_record("TXT", "example.com.", "v=spf1 include:_spf.google.com ~all"))
// "example.com. 3600 IN TXT \"v=spf1 include:_spf.google.com ~all\""
SRV records take both a priority and a weight, and any record type accepts a custom ttl:
use plugin dns::{format_record}
print(format_record("SRV", "_sip._tcp.example.com.", "sip.example.com.", 7200, 10, 60))
// "_sip._tcp.example.com. 7200 IN SRV 10 60 sip.example.com."
print(format_record("CNAME", "www.example.com.", "example.com.", 300))
// "www.example.com. 300 IN CNAME example.com."
Validates a hostname against RFC rules
Validates a hostname against RFC rules: max 253 characters total, each label max 63 characters, labels contain only alphanumeric characters and hyphens, and labels do not start or end with a hyphen.
use plugin dns::{is_valid_hostname}
print(is_valid_hostname("example.com")) // true
print(is_valid_hostname("-bad.example.com")) // false
print(is_valid_hostname("ok-label.test")) // true