DHCP & DNS
Stratum 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.
How a workload gets its address
Five things are consulted, in this order, and the first that applies wins:
- A reservation for its hardware address, if you have made one.
- An address it already holds. A workload that comes back before its lease expires gets the same address, so a reboot does not renumber anything.
- The endpoint it is attached to. A workload attached with
network attachis offered that endpoint's address — the address it was attached with — once a scope covers the network. The address stays with the endpoint: a lease on it that expires or is released does not return it to the pool;network detachdoes. - An offer it was already given, so a client that asks repeatedly is not handed a different address each time.
- The next free address in the network's range.
A reservation moves a workload at its next DHCP exchange. A workload that
already holds a lease is given the reserved address the next time it renews or
asks again, and its old address goes back to the pool. To move it right away,
renew the lease from inside the workload (for example dhclient -r && dhclient).
Offering and committing are separate: the address is held tentatively when offered and only becomes a lease once the client accepts it. An offer a client never takes up is returned to the pool automatically rather than leaking.
Reservations require a DHCP scope covering that subnet. Creating a network does not create one — see DHCP pools below. If you see *"no address pool configured for this network"* when reserving, either no scope covers that address or none is configured at all.
How a name is resolved
A query is answered by the first of these that applies:
- Blocked names are refused outright, whatever else would have answered.
- Your own zones. If the name is in a zone this node is authoritative for, the answer comes from your records — and if it is not there, the answer is an authoritative "no such name". The query is never forwarded, because this node is the authority for that zone and the correct answer is that it does not exist.
- Upstream resolvers, for everything else — but only if the asking client is permitted to use recursion. A client outside the permitted set is refused rather than served, so the node cannot be used as an open resolver by strangers.
- With no upstreams configured and no local answer, the reply is "no such name".
The practical consequence of step 2: once you create a zone, this node owns
every name under it. A name you have not added returns "does not exist"
rather than falling through to the public internet. That is what makes an
internal zone trustworthy — but it means creating example.com internally
stops you resolving the real one.
Records can also be scoped to a source subnet, so the same name answers differently depending on who is asking — an internal client gets the internal address while everyone else gets the public one, from one zone.
DHCP pools
network create does create the address pool for the subnet. What it does not
create is the DHCP scope that binds serving to that pool — and until a scope
exists the DHCP server has no address to offer for that subnet, pool or no pool.
That failure is quiet by design. RFC 2131 says a server with nothing to offer must not reply, so the client simply retries: its interface shows DHCP going out and nothing coming back, which looks identical to a broken L2 path. The agent logs a warning naming the client — *"no address pool for this client's network"* — the first time it happens, then holds it down so a fresh node is not buried in noise. If a workload never gets an address, search the agent log for that line before you go looking at the bridge.
Once a pool exists and a scope is bound to it, leases are allocated from that pool and released back to it on expiry.
sudo cenvero-str-ctl network create \
--name db-net \
--cidr 10.30.0.0/24 \
--gateway 10.30.0.1
network create accepts --name, --cidr, and optionally --gateway, --vlan, and --tenant. (Only IPv4 CIDRs are supported.)
Then bind a scope to the network's pool. Scopes are added over the API —
GET /api/v1/ipam/pools lists pools, and a network's pool carries the network's
name — or in the node configuration (dhcp_scopes):
curl -k -X POST "$NODE/api/v1/dhcp/scopes" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"subnet":"10.30.0.0/24","pool_id":7,"server_ip":"10.30.0.1","gateway":"10.30.0.1",
"subnet_mask":"255.255.255.0","dns":["1.1.1.1"],"lease_seconds":3600}'
A scope added over the API is kept by the node and is still there after the agent restarts; one in the node configuration is applied at every start. Adding a scope for a subnet that already has one replaces it. See Network Services Control → Scopes.
The DHCP server tells clients what the matching scope says:
| Option | Value |
|---|---|
| Subnet mask | the scope's subnet_mask; when empty, the mask of the scope's own subnet |
| Default gateway | the scope's gateway |
| DNS servers | the scope's dns list |
| Server identity | the scope's server_ip; when empty, this node's own address on the client's subnet (for a relayed client, the address the relay sent the request to) |
| Lease time | the scope's lease_seconds, or one hour when that is 0 |
A gateway or DNS list the scope leaves empty is simply not sent, so set them on every scope you expect clients to route and resolve through. The gateway is never filled in for you: whether workloads route through this node is your decision.
A reserved address is answered with the options of the scope whose subnet holds that address, so a reservation is configured for the network it is on.
No domain-name or domain-search option is offered, so configure a search domain on the workload itself if you need short names to resolve.
Reservations (static leases)
Pin 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:
sudo cenvero-str-ctl dhcp reserve \
--mac 52:54:00:de:ad:01 \
--ip 10.30.0.10 \
--hostname db-primary
The MAC and IP may also be given positionally, which is handy for one-liners:
sudo cenvero-str-ctl dhcp reserve 52:54:00:de:ad:01 10.30.0.10 --hostname db-primary
A 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.
List the reservations you have made:
cenvero-str-ctl dhcp reservations
Inspecting leases
dhcp leases dumps every active lease the server has handed out:
cenvero-str-ctl dhcp leases
To release a reservation early (e.g. before re-provisioning a workload), identify it by MAC:
sudo cenvero-str-ctl dhcp release --mac 52:54:00:ab:01:02
The MAC may be positional here too — dhcp release 52:54:00:ab:01:02 is equivalent.
How a request finds its scope
A workload attached directly to the node — on the workload bridge, with no
relay in between — is matched by the interface its request arrives on. The node
uses the scope whose subnet contains an address the node itself holds on that
interface. So for a network to be served directly, the node needs an address in
that network's subnet on the bridge the workloads are on — normally the network's
gateway address (see Host addressing in the API Reference). The
same address is a natural server_ip and gateway for the scope.
A request that arrives on an interface where the node holds no address inside any scope's subnet — the uplink, or a bridge you have not addressed — is not answered from any scope. With nothing to offer the server stays silent and logs the "no address pool for this client's network" warning described above.
Clients on a segment the node is not attached to — for example a physical
VLAN reached through an external DHCP relay — are matched by the relay's
giaddr (the address the relay stamps into the request). Point your relay agent
at the node and add a scope whose subnet covers that segment (see
Network Services Control → Scopes); the relayed
request is answered from that scope's pool.
Who is answered
Once any endpoint or MAC binding exists on the node, the server answers only hardware addresses the node has a binding for. A request from any other hardware address is not offered an address at all: the server stays silent (RFC 2131 allows it) rather than hand out an address it would refuse to lease, and the agent logs one rate-limited warning naming the address. A node with no bindings at all answers everyone.
Authoritative DNS
The agent runs a DNS server on UDP port 53 of the management bridge's IPv4 address (the workload bridge's if the management bridge has none, loopback if neither does) — never on every interface at once. Pin it elsewhere with config set dns_listen_addr <address>:53; see Network Services Control → Listen address, which also covers what this means on a single-card server.
Zones 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.
sudo cenvero-str-ctl dns zone add app-net.internal
{
"data": {
"id": 1,
"name": "app-net.internal.",
"serial": 1,
"status": "created"
},
"status": "ok"
}
The 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:
cenvero-str-ctl dns zones
A 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:
web-01.app-net.internal → 10.20.0.50
db-primary.db-net.internal → 10.30.0.10
Adding manual DNS records
dns record add takes positional arguments: the zone id, the record name, its type, its value, and optionally a TTL and a source subnet.
# dns record add <zone_id> <name> <type> <value> [ttl] [source_subnet]
sudo cenvero-str-ctl dns record add 1 api A 10.20.0.55 300
sudo cenvero-str-ctl dns record add 1 services CNAME api.app-net.internal
The 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.
List records for a zone by its id:
cenvero-str-ctl dns list 1
{
"data": {
"records": [
{
"id": 3,
"zone_id": 1,
"name": "api.app-net.internal.",
"type": "A",
"value": "10.20.0.55",
"ttl": 300
}
]
},
"status": "ok"
}
Omit the zone id (dns list), or use dns records, to list records across every zone.
Remove a record by its record id — the id field above, not its name:
sudo cenvero-str-ctl dns record delete 3
Delete a whole zone by its zone id with dns zone delete 1.
Upstream forwarders
Queries for names outside the local zones are forwarded to configurable upstream resolvers. Manage them at runtime with the dns forwarder commands:
# Show the active forwarders
cenvero-str-ctl dns forwarder list
# Replace the whole list
sudo cenvero-str-ctl dns forwarder set 1.1.1.1 8.8.8.8
# Add or remove one
sudo cenvero-str-ctl dns forwarder add 9.9.9.9
sudo cenvero-str-ctl dns forwarder remove 8.8.8.8
Each 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:
sudo cenvero-str-ctl config set dns_upstreams "1.1.1.1,8.8.8.8"
The agent falls back to public defaults when the list is empty.
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.
See also
- Networking Overview — how DHCP and DNS fit into the data plane.
- Firewall — DNS traffic on port 53 must be explicitly allowed for cross-network queries.
- Configuration — how settings reach a node and what lives in the node config.