Skip to content

dns

stable

DNS lookup, IP address parsing, hostname validation, and DNS record formatting utilities for network-aware Zolo programs.

use plugin dns::{lookup, lookup_first, resolve, …}
11 functions Networking
/ filter jk navigate Esc clear
Functions (11)
  1. lookup Resolves a hostname to all its IP addresses
  2. lookup_first Resolves a hostname to its first IP address
  3. resolve Resolves a host:port string to socket addresses
  4. is_ipv4 Returns true if a string is a valid IPv4 address
  5. is_ipv6 Returns true if a string is a valid IPv6 address
  6. is_loopback Returns true if an IP is a loopback address
  7. is_private Returns true if an IP is a private network address
  8. parse_ip Parses an IP string into a detail table
  9. reverse_lookup_str Converts an IP to its reverse DNS ARPA form
  10. format_record Formats a DNS record in zone file syntax
  11. 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
enespt-br