# DNS-name Targets (https://portunus.bybee.dev/en/docs/features/dns-targets)



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 [#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 &#x2A;*`[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 [#push-a-dns-target-rule]

```sh
# 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 [#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 [#failure-visibility]

DNS failures are exposed two ways:

```sh
# 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 [#resolver-tunables]

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

| Knob                      | Value |
| ------------------------- | ----- |
| `cache_floor`             | 5 s   |
| `cache_ceiling`           | 5 min |
| `stale_while_error_grace` | 30 s  |
| `attempt_timeout`         | 3 s   |
| `negative_cache_retry`    | 3 s   |
| `max_concurrent_resolves` | 64    |
| `max_cache_entries`       | 8192  |
