Portunus
Features

DNS-name Targets

Use a hostname instead of an IP literal as the rule target. Cached, single-flighted, RFC-8767 stale-while-error.

Available since v0.3.0. The target host in any push-rule may be a DNS name (e.g. api.example.com:443) instead of an IP literal.

How it works

  • Resolution happens lazily on first connect through hickory-resolver reading /etc/resolv.conf.
  • Results cache per the resolver-reported TTL clamped to [5 s, 5 min]. The cache is bounded (default 8192 entries) so high name-cardinality workloads can't grow it without limit; expired entries are evicted first, then the live entries closest to expiry if still over the cap.
  • On refresh failure the rule stays Active and the last-known answer continues serving for 30 s of grace (RFC 8767 stale-while-error). After grace expiry, a fresh attempt is allowed every 3 s.
  • Per-rule single-flight collapses concurrent first-connects to ONE upstream resolver call.
  • Multi-A/AAAA fallback tries each returned address in family-preference order — a single dead IP doesn't fail the connection.
  • IPv4-first by default; pass --prefer-ipv6 to flip per rule.

Push a DNS-target rule

# IPv4-first (default)
portunus-server push-rule edge-01 8443 api.example.com:443

# AAAA-first; falls back to A if no AAAA
portunus-server push-rule edge-01 8444 api.example.com:443 --prefer-ipv6

Hot path

IP-literal targets skip the resolver entirely: IpAddr::from_str short-circuits straight to the dial. The dial itself is time-boxed by the per-attempt timeout (3 s), so a SYN-blackhole target (a firewall that drops rather than refuses) fails fast instead of wedging a task in kernel SYN-retransmit for ~75–127 s.

DNS-target hot path performance:

  • Cache-hit lookup: ~75 ns median (one async-mutex acquire + HashMap get + Vec clone).
  • 100 concurrent first-connects to the same hostname: resolver invoked exactly once.

Failure visibility

DNS failures are exposed two ways:

# Per-rule field
portunus-server rule-stats <id>
# rule_id=… dns_failures=3 …

# Prometheus
curl -s 127.0.0.1:7081/metrics | grep dns_failures
# portunus_rule_dns_failures_total{client="edge-01",owner="alice",rule="2"} 3

The collector is one row per rule (not one per failure), preserving the cardinality budget. The row is removed alongside rule_active_connections on remove-rule.

Resolver tunables

Currently spec-fixed (no operator overrides yet). Listed here for visibility; a future release may surface them under [resolver] in server.toml:

KnobValue
cache_floor5 s
cache_ceiling5 min
stale_while_error_grace30 s
attempt_timeout3 s
negative_cache_retry3 s
max_concurrent_resolves64
max_cache_entries8192

On this page