{
    "product": "Cenvero Stratum",
    "generated_at": "2026-08-03T07:18:48+00:00",
    "format": "cenvero-docs-v1",
    "document_count": 1,
    "documents": [
        {
            "slug": "networking/dhcp-dns",
            "title": "DHCP & DNS",
            "category": "Networking",
            "url": "https://www.stratum.cenvero.com/docs/networking/dhcp-dns",
            "headings": [
                {
                    "level": 1,
                    "text": "DHCP & DNS"
                },
                {
                    "level": 2,
                    "text": "How a workload gets its address"
                },
                {
                    "level": 2,
                    "text": "How a name is resolved"
                },
                {
                    "level": 2,
                    "text": "DHCP pools"
                },
                {
                    "level": 3,
                    "text": "Reservations (static leases)"
                },
                {
                    "level": 3,
                    "text": "Inspecting leases"
                },
                {
                    "level": 2,
                    "text": "Relayed clients"
                },
                {
                    "level": 2,
                    "text": "Authoritative DNS"
                },
                {
                    "level": 3,
                    "text": "Adding manual DNS records"
                },
                {
                    "level": 3,
                    "text": "Upstream forwarders"
                },
                {
                    "level": 2,
                    "text": "See also"
                }
            ],
            "word_count": 1591,
            "markdown": "# DHCP & DNS\n\nStratum runs its own DHCP server and authoritative DNS inside the agent process — there are no external daemons to configure or keep in sync. Both serve whatever scopes and zones you configure; creating a network does not configure them for you. Addresses are handed to a network's endpoints (an endpoint is an IP ↔ MAC profile). This page covers pool configuration, static leases, DHCP relay, and DNS zone management.\n\n## How a workload gets its address\n\nFour things are consulted, in this order, and the first that applies wins:\n\n1. **An address it already holds.** A workload that comes back before its lease\n   expires gets the same address, so a reboot does not renumber anything.\n2. **A reservation for its hardware address**, if you have made one.\n3. **An offer it was already given**, so a client that asks repeatedly is not\n   handed a different address each time.\n4. **The next free address in the network's range.**\n\n> **Adding a reservation does not move a workload that already has a lease.**\n> Step 1 is checked first, so the workload keeps its current address until that\n> lease expires or you release it. If you need the reservation to take effect\n> immediately, release the existing lease after making it.\n\nOffering and committing are separate: the address is held tentatively when\noffered and only becomes a lease once the client accepts it. An offer a client\nnever takes up is returned to the pool automatically rather than leaking.\n\nReservations require a DHCP scope covering that subnet. Creating a network does\n**not** create one — see [DHCP pools](#dhcp-pools) below. If you see *\"no address\npool configured for this network\"* when reserving, either no scope covers that\naddress or none is configured at all.\n\n## How a name is resolved\n\nA query is answered by the first of these that applies:\n\n1. **Blocked names** are refused outright, whatever else would have answered.\n2. **Your own zones.** If the name is in a zone this node is authoritative for,\n   the answer comes from your records — and if it is *not* there, the answer is\n   an authoritative \"no such name\". The query is never forwarded, because this\n   node is the authority for that zone and the correct answer is that it does not\n   exist.\n3. **Upstream resolvers**, for everything else — but only if the asking client is\n   permitted to use recursion. A client outside the permitted set is refused\n   rather than served, so the node cannot be used as an open resolver by\n   strangers.\n4. With no upstreams configured and no local answer, the reply is \"no such name\".\n\n> The practical consequence of step 2: once you create a zone, this node owns\n> **every** name under it. A name you have not added returns \"does not exist\"\n> rather than falling through to the public internet. That is what makes an\n> internal zone trustworthy — but it means creating `example.com` internally\n> stops you resolving the real one.\n\nRecords can also be scoped to a source subnet, so the same name answers\ndifferently depending on who is asking — an internal client gets the internal\naddress while everyone else gets the public one, from one zone.\n\n## DHCP pools\n\n`network create` **does** create the address pool for the subnet. What it does not\ncreate is the **DHCP scope** that binds serving to that pool — and until a scope\nexists the DHCP server has no address to offer for that subnet, pool or no pool.\n\nThat failure is quiet by design. RFC 2131 says a server with nothing to offer\nmust not reply, so the client simply retries: its interface shows DHCP going out\nand nothing coming back, which looks identical to a broken L2 path. The agent\nlogs a warning naming the client — *\"no address pool for this client's\nnetwork\"* — the first time it happens, then holds it down so a fresh node is not\nburied in noise. If a workload never gets an address, search the agent log for\nthat line before you go looking at the bridge.\n\nOnce a pool exists and a scope is bound to it, leases are allocated from the IPAM\npool and released back to it on expiry.\n\n```bash\nsudo cenvero-str-ctl network create \\\n  --name db-net \\\n  --cidr 10.30.0.0/24 \\\n  --gateway 10.30.0.1\n```\n\n`network create` accepts `--name`, `--cidr`, and optionally `--gateway`, `--vlan`, and `--tenant`. (Only IPv4 CIDRs are supported.)\n\nThe DHCP server tells clients:\n\n| Option | Value |\n|--------|-------|\n| Subnet mask | derived from `--cidr` |\n| Default gateway | `--gateway` |\n| DNS server | the agent's gateway IP (same as `--gateway`) |\n| Lease time | the server's lease TTL |\n\nNo domain-name or domain-search option is offered, so configure a search domain on the workload itself if you need short names to resolve.\n\n### Reservations (static leases)\n\nPin a specific IP to a MAC address so an endpoint always gets the same address. The address is matched to its pool from the IP itself, so the reservation does not name a network:\n\n```bash\nsudo cenvero-str-ctl dhcp reserve \\\n  --mac 52:54:00:de:ad:01 \\\n  --ip 10.30.0.10 \\\n  --hostname db-primary\n```\n\nThe MAC and IP may also be given positionally, which is handy for one-liners:\n\n```bash\nsudo cenvero-str-ctl dhcp reserve 52:54:00:de:ad:01 10.30.0.10 --hostname db-primary\n```\n\nA reservation pins the address only — it does not create a DNS record. To make the hostname resolve, add a DNS record with `dns record add` (or the API); see **Adding manual DNS records** below.\n\nList the reservations you have made:\n\n```bash\ncenvero-str-ctl dhcp reservations\n```\n\n### Inspecting leases\n\n`dhcp leases` dumps every active lease the server has handed out:\n\n```bash\ncenvero-str-ctl dhcp leases\n```\n\nTo release a reservation early (e.g. before re-provisioning a workload), identify it by MAC:\n\n```bash\nsudo cenvero-str-ctl dhcp release --mac 52:54:00:ab:01:02\n```\n\nThe MAC may be positional here too — `dhcp release 52:54:00:ab:01:02` is equivalent.\n\n## Relayed clients\n\nClients on a segment that has no directly-attached agent — for example a physical VLAN reached through an external DHCP relay — are supported. Point your existing relay agent at the node, and the relayed request is matched to the right scope by the relay's `giaddr` (the gateway/relay address the relay stamps into the request). Add a scope whose `subnet` covers that segment (see [Network Services Control → Scopes](/docs/networking/services)) and the built-in server answers the relayed request from that scope's pool.\n\n## Authoritative DNS\n\nThe agent runs a DNS server bound to the bridge addresses (the management and user bridge IPv4 addresses) rather than every interface, so the resolver is never exposed on an untrusted NIC.\n\nZones are explicit: creating a network does not create a zone for it, so you choose the domain and create it yourself. Pick any name you like — a `.internal` suffix is a good convention for names that should never resolve publicly.\n\n```bash\nsudo cenvero-str-ctl dns zone add app-net.internal\n```\n\n```text\n{\n  \"data\": {\n    \"id\": 1,\n    \"name\": \"app-net.internal.\",\n    \"serial\": 1,\n    \"status\": \"created\"\n  },\n  \"status\": \"ok\"\n}\n```\n\nThe `id` in that response is the **zone id**, and every record command refers to the zone by that numeric id rather than by name. List your zones at any time to look one up:\n\n```bash\ncenvero-str-ctl dns zones\n```\n\nA DHCP lease does not by itself create a DNS record. To make a hostname resolve to a leased (or reserved) address, add an A record for it — it then resolves inside the zone:\n\n```text\nweb-01.app-net.internal    →  10.20.0.50\ndb-primary.db-net.internal →  10.30.0.10\n```\n\n### Adding manual DNS records\n\n`dns record add` takes positional arguments: the zone id, the record name, its type, its value, and optionally a TTL and a source subnet.\n\n```bash\n# dns record add <zone_id> <name> <type> <value> [ttl] [source_subnet]\nsudo cenvero-str-ctl dns record add 1 api A 10.20.0.55 300\n\nsudo cenvero-str-ctl dns record add 1 services CNAME api.app-net.internal\n```\n\nThe name is relative to the zone, so `api` in zone `app-net.internal` becomes `api.app-net.internal`. When you omit the TTL a default is applied. `dns add` is a shorthand alias for `dns record add` and takes exactly the same arguments.\n\nList records for a zone by its id:\n\n```bash\ncenvero-str-ctl dns list 1\n```\n\n```text\n{\n  \"data\": {\n    \"records\": [\n      {\n        \"id\": 3,\n        \"zone_id\": 1,\n        \"name\": \"api.app-net.internal.\",\n        \"type\": \"A\",\n        \"value\": \"10.20.0.55\",\n        \"ttl\": 300\n      }\n    ]\n  },\n  \"status\": \"ok\"\n}\n```\n\nOmit the zone id (`dns list`), or use `dns records`, to list records across every zone.\n\nRemove a record by its **record id** — the `id` field above, not its name:\n\n```bash\nsudo cenvero-str-ctl dns record delete 3\n```\n\nDelete a whole zone by its zone id with `dns zone delete 1`.\n\n### Upstream forwarders\n\nQueries for names outside the local zones are forwarded to configurable upstream resolvers. Manage them at runtime with the `dns forwarder` commands:\n\n```bash\n# Show the active forwarders\ncenvero-str-ctl dns forwarder list\n\n# Replace the whole list\nsudo cenvero-str-ctl dns forwarder set 1.1.1.1 8.8.8.8\n\n# Add or remove one\nsudo cenvero-str-ctl dns forwarder add 9.9.9.9\nsudo cenvero-str-ctl dns forwarder remove 8.8.8.8\n```\n\nEach forwarder is an IP or `IP:port` (a bare IP defaults to `:53`). The agent tries forwarders in order and falls back to the next on timeout. `dns forwarder` changes take effect on the running agent; to persist them across restarts, set the `dns_upstreams` key:\n\n```bash\nsudo cenvero-str-ctl config set dns_upstreams \"1.1.1.1,8.8.8.8\"\n```\n\nThe agent falls back to public defaults when the list is empty.\n\n> **Not an open resolver.** Recursion (forwarding a query upstream) is gated by a client ACL. When the ACL is unset it defaults to the private and loopback ranges (`127.0.0.0/8`, `::1/128`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), so only clients inside those ranges have their queries forwarded; a query from outside the allowed set is refused rather than silently forwarded. Authoritative answers for local zones are always returned regardless of the ACL.\n\n## See also\n\n- [Networking Overview](/docs/networking/overview) — how DHCP and DNS fit into the data plane.\n- [Zero-Trust Firewall](/docs/networking/firewall) — DNS traffic on port 53 must be explicitly allowed for cross-network queries.\n- [Configuration](/docs/configuration) — how settings reach a node and what lives in the node config.\n"
        }
    ]
}