Network architecture
The DNF network operates at two scales: an isolated local zone, then multiple zones connected by a mesh VPN. The vocabulary is defined in Concepts.
Simple network (one zone)
Section titled “Simple network (one zone)”A zone is a gateway and its machines.
- The gateway shares Internet (NAT) and protects the zone (firewall).
dnsmasqdistributes addresses (DHCP) and resolves local names (<host>.<zone>).- On the Internet side (WAN), everything is closed. On the local side (LAN), only SSH, DNS, DHCP and the home page are open.
Advertisement Filtering (AdGuardHome)
Section titled “Advertisement Filtering (AdGuardHome)”Optional service, enabled on the gateway. It sits in front of dnsmasq.
- AdGuardHome becomes the DNS resolver for workstations (port 53).
- It blocks ads and trackers, and encrypts outgoing queries.
dnsmasqmoves back to an internal port and keeps local names and DHCP.- No rate limiting: zone resolver, closed to the WAN. A burst of queries (fleet update) loses no response.
Wide area network (VPN)
Section titled “Wide area network (VPN)”Several zones are linked by a mesh VPN, the tailnet, coordinated by the coordination server (HCS).
- The HCS (Headscale) is public and coordinates the meshing; traffic goes directly from one node to another.
- Each gateway is a subnet router: it publishes the subnet of its zone into the VPN.
- Zones can see each other; remote machines (smartphone, PC) join the network via Tailscale.
- Nothing is allowed by default: an access policy, generated from
etc/config.yaml, specifies who can reach what. - A pivot resolver on the HCS routes each zone’s names to its gateway.
Routing, DNS, MagicDNS, access policy, and activation keys are detailed in Tailnet operation.
What goes through a gateway
Section titled “What goes through a gateway”A gateway routes, and a routed packet never goes through the port opening rules : those only concern what is addressed to it. The two filters are independent.
Only the following traffic goes through a gateway :
| Traffic | Usage |
|---|---|
| Zone → WAN | Internet sharing (NAT) |
| Zone → backup link | Internet sharing when the box goes down (backup links) |
| Zone ↔ VPN | The other zones, remote machines |
| Port replies and redirections | Return of outbound traffic, forwardPorts |
Everything else is dropped. In particular, an Internet machine that set up a static route to a zone subnet reaches nothing : without this filtering, a gateway with a public address would let everything through.
Any extra routing requirement goes into the host file :
networking.firewall.extraForwardRules = '' iifname "lan0" oifname "wg0" accept'';SSH access by role
Section titled “SSH access by role”Port 22 is never open “everywhere”. A rule without an interface name applies to all interfaces, WAN included: on a gateway, this would expose SSH to the Internet.
| Host | SSH reachable from |
|---|---|
| Zone gateway | Its local network and the VPN |
| Global zone host (HCS) | The Internet — an intentional decision, since it hosts the VPN |
| Zone machine (workstation, server) | Its own interface only, i.e. the local network |
Practical consequence: a gateway does not respond to SSH from the Internet. Remote access goes through the VPN:
just enter gwScope of internal services
Section titled “Scope of internal services”An internal service opens on the interface that matches its actual scope, never on all of them. Three scopes coexist:
| Scope | Opening | Examples |
|---|---|---|
| Tailnet | tailscale0 | Loki (logs), relay for exposed services |
| Local network | lan0 on a gateway, the single interface elsewhere | DNS, DHCP, homepage, Nix cache |
| Address list | ip saddr rule, or client-side service list | NFS shares, LAN games |
The third case exists because a zone prefix is not an identity: any machine that obtains a local address is part of it, Wi-Fi guests included. Where the service allows it, the declared host list replaces the prefix.
Names and DNS resolution
Section titled “Names and DNS resolution”Three families of names coexist on the network. All are generated from
etc/config.yaml.
| Name | Form | Scope | Example |
|---|---|---|---|
| Simple name | <host> | Entire internal network | server |
| Zone FQDN | <name>.<zone>.<domain> | Entire internal network | cloud.home.domain.tld |
| Global service | <name>.<domain> | Including the Internet | git.domain.tld |
- Simple names: these are the machines (declared hosts). Resolved everywhere on the network, to the machine’s real address, whatever its zone.
- Zone FQDN: these are the services of a zone. They point to that zone’s gateway, which serves them through its reverse proxy.
- Global services: services marked
globalinetc/config.yaml. Exposed on the HCS with public DNS: reachable from anywhere.
Who resolves what:
dnsmasq, on every gateway, knows all internal names (machines and services of all zones): it resolves simple names and FQDNs.- The current zone is resolved locally; the other zones and the global domain are routed to the right gateway, or to the HCS pivot resolver.
- Public (Internet) names are sent upstream to encrypted DNS, filtered by AdGuardHome if it is enabled.
- Only declared names exist : an unknown name under a zone answers
NXDOMAIN, without redirecting to the gateway. - Tailnet names (
<node>.tailnet.internal) are relayed by each gateway to MagicDNS : also resolved from the LANs.
The pivot resolver (HCS)
Section titled “The pivot resolver (HCS)”On the coordination server (HCS), a pivot resolver (unbound) centralizes the internal VPN resolution. It knows the zone breakdown and simply routes :
- each zone FQDN goes to its zone’s gateway, at its tailnet address ;
- global services hosted on the HCS are resolved locally ;
- everything else goes to an encrypted external DNS.
Remote devices query it via MagicDNS (split DNS), and gateways query it for names in the domain they don’t know. Between two zones, a gateway queries the other zone’s gateway directly. Details in DNS.
Resolution from outside
Section titled “Resolution from outside”A machine outside any zone (laptop on the go, smartphone) joins the network via the VPN. Its DNS resolution is split (split-DNS) based on the nature of the requested name:
- internal names: routed through the VPN to the HCS pivot resolver, which then directs them to the correct gateway;
- tailnet node names (
<node>.tailnet.internal, or the short name): resolved on the device itself by MagicDNS; - public names: resolved by the local resolver of the visited network (home router, public access point), without going through the VPN.
Machines outside their zone
Section titled “Machines outside their zone”A machine is declared in one zone, but it is not always there. A laptop goes on the road. A new gateway is installed from another zone, before being delivered on site.
Without special provision, this machine receives an arbitrary address from the DHCP range of the visited zone, and its name keeps pointing to its original address, unreachable from there. Nothing allows reaching it reliably.
A reserved address in each zone
Section titled “A reserved address in each zone”Each zone reserves the <prefix>.6.x block for machines from the other
zones. The generator places a DHCP reservation there for every MAC address known
on the network that does not belong to the zone.
The number is assigned at the scale of the whole network : a machine keeps the same last octet in every zone it visits.
| Machine | Declared zone | At home | Visiting atelier |
|---|---|---|---|
portable | maison | 10.0.2.4 | 10.1.6.3 |
serveur | atelier | 10.1.1.2 | (it is at home there) |
The address is therefore known before the machine boots, without observing anything on the network.
A name that designates the place
Section titled “A name that designates the place”The reservation comes with a DNS name in the visited zone. The simple name, for its part, keeps its meaning.
| Name | Resolves to |
|---|---|
portable | its original address, everywhere on the network |
portable.atelier.domain.tld | its address in the atelier zone |
A lease that no longer moves
Section titled “A lease that no longer moves”dnsmasq indexes leases solely on the MAC address, not on the DHCP client
identifier.
The reason : a given device does not advertise the same identifier depending on the system running on it. An installation image and the installed system look like two different machines, and receive two addresses. Relying on the MAC, the address no longer changes between installation and the first real boot.
What the tooling does
Section titled “What the tooling does”The installation recipes compute the address all by themselves. They first identify the zone where the administration machine is located, based on its real default gateway and not on its declared zone (an administration laptop is precisely what moves around), then deduce the address of the target.
This address is then checked before being used : a reservation says where the machine would be if it were plugged in here, which is correct during the installation and wrong as soon as it is back home. If nothing responds at the reserved address, the tooling falls back to the host name when the latter responds — port 22, or 2222 for an encrypted machine waiting in its initrd. If nothing responds anywhere, the reserved address is kept : it is the one on which the installer will appear.
Finding a machine again after a reboot
Section titled “Finding a machine again after a reboot”The calculation above gives one address to boot from. After the reboot that closes an installation, it is no longer sufficient : the machine may reappear elsewhere, or not really be up yet.
just full-install therefore probes all known addresses of the host, in
this order, on ports 22 and 2222, and continues with the first one that
answers :
- the address used for the installation;
- the fixed address of its origin zone, the one DNS publishes everywhere;
- the transit address reserved by the local zone;
- its name, resolved by the local gateway.
An answer on port 2222 means the host is waiting for its LUKS passphrase in
its initrd : just unlock answers it without intervention. See Disk
encryption.
Joining a roaming host
Section titled “Joining a roaming host”Colmena 🡕 (just apply), fleet-update and
just enter target a host by its name, which denotes its original address. On
an administration machine, every SSH connection to a network host goes through
dnf-locate, which tries :
- the name alone first : the common case costs nothing extra;
- otherwise, in parallel, its reservation in each zone
(
portable.workshop.domain.tld) and its tailnet name (portable.tailnet.internal).
The first path that answers wins. dnf-locate --print portable displays the one
it finds.
The tailnet path only applies to an administration machine (tag:admin) :
the access policy opens no other tailnet client to the zones.
To enforce an address, just configure sets a temporary SSH redirection for a
roaming host, removed at the end of the installation or by hand :
just roaming # show current redirectionsjust roaming-clear # remove all of them