# Networking

---

# Networking

_Peios / Using Peios / Networking_

> How a Peios machine gets onto a network — netd reconciles links, addresses, routes and DHCP to what the registry says; nothing else configures the network.

A Peios machine's network is described in the registry and made real by one service, **netd**. There is no `ifconfig` step, no interface file, no daemon-specific configuration format: what the network should be is written under `Machine\System\Network`, and netd keeps the kernel matching it.

This page explains the model. [Configuring profiles](/peios/using-peios/networking/configuring-profiles.md) covers writing the configuration; [The net command](/peios/using-peios/networking/the-net-command.md) covers looking at the result.

## The registry is the truth

netd is a **reconciler**. It reads the desired state — profiles from the registry, plus whatever leases its DHCP clients currently hold — compares it with what the kernel reports over rtnetlink, and applies the difference. It does this on start, whenever the kernel reports a change (a cable plugged in, an interface appearing), whenever the registry changes, and whenever a DHCP lease arrives or expires.

Two consequences follow, and both are deliberate:

- **A manual change to a managed interface is reverted.** Adding an address by hand lasts until the next reconcile. The way to change the network is to change the registry.
- **Restarting netd changes nothing visible.** It re-derives the same desired state, finds the kernel already matches, and does nothing. Interfaces do not flap.

netd owns what it configured and nothing else. Addresses on a managed interface are all netd's; routes are netd's only when they carry its routing-protocol tag, so a route another program adds for itself is left alone.

## Profiles match interfaces

Configuration is not written per interface, because interface names are not stable across kernels and an image is built before its hardware is known. Instead a **profile** is a *match rule* with the configuration to apply to whatever it matches: by name, MAC address, bus path, driver or type, with one `*` wildcard allowed. netd evaluates every profile against every interface, highest priority first, and the first whose match keys all hold wins.

Nothing is "activated". A profile that matches two interfaces configures both; an interface no profile matches is left alone. A default profile shipped with the image claims every Ethernet interface for DHCP, at a priority low enough that anything you write outranks it.

## The inventory

netd writes what it finds under `Machine\System\Network\Interfaces\<ifid>`: the kernel name, MAC, bus path, driver, type, and which profile won. The **interface id** is derived from the bus path and MAC, so the same card in the same slot keeps the same key across boots however the kernel names it. One value there is yours: `Enabled`, which takes an interface down and keeps it down regardless of its profile.

Nothing transient is stored in the registry — not leases, not link state, not learned DNS servers. `net status` shows those.

## Addresses

A profile gets its addresses three ways, and they combine:

- **DHCPv4**, on by default. netd runs its own client on each interface once it has carrier. The lease's address, gateway, classless routes, DNS servers and MTU are applied; the address is remembered on disk so the next boot asks for the same one first.
- **Static** addresses in CIDR form, with an optional gateway that overrides DHCP's.
- **Link-local** (169.254/16), self-assigned when DHCP discovery goes unanswered, so two machines on a cable with no server can still talk. Discovery continues, and the link-local address is dropped when a lease arrives.

When a lease expires and cannot be renewed the address is dropped by default: the server may have given it to someone else, and an address conflict is worse than no address. A profile can choose to keep it instead.

## Readiness is a level, not a boolean

"The network is up" means different things to different services. netd reports each interface at one of four **levels** — `absent`, `link` (up with carrier), `addressed` (has an address), `routed` (has a default route) — and the machine's level is the highest among its managed interfaces. `net wait routed` blocks until there is a default route; a service definition will be able to declare which level it needs.

## What netd does not do

- **Names.** netd never answers a DNS query. It tells resolvd, live, what each interface contributes — servers, search domains, addresses — and resolvd routes every name to one interface's servers. Nothing is written to `/etc/resolv.conf` or `/etc/hosts`; see [name resolution](/peios/using-peios/networking/name-resolution.md).
- **Firewalling.** Traffic policy is a separate component.
- **Port ownership.** Who may bind a port is a kernel decision under `Machine\System\Network\TcpIp\PortReservations`.
- **WiFi association.** A wireless supplicant brings the link up; netd treats the result like any other interface.
- **IPv6 autoconfiguration.** The kernel's own router-advertisement handling applies for now.

## Where to start

- [Configuring profiles](/peios/using-peios/networking/configuring-profiles.md) — write a profile, static or DHCP, and see it take effect.
- [The net command](/peios/using-peios/networking/the-net-command.md) — status, levels, renewals.
- `regman Machine\System\Network` on a Peios machine documents every key netd reads.

---

# Configuring profiles

_Peios / Using Peios / Networking_

> Write a network profile in the registry — match an interface, give it DHCP or a static address, set DNS — and watch netd apply it live.

A profile is a subkey of `Machine\System\Network\Profiles`. Its `Match` subkey says which interfaces it claims; `Address` and `DNS` say what to do with them. netd watches the subtree, so a change takes effect within a moment of the write — no restart, no reload command.

Every key and value is documented on the machine: `regman Machine\System\Network\Profiles`.

## See what you have

```
net status
net profile list
reg ls Machine/System/Network/Profiles
```

`net status` shows each interface with the profile that claimed it. An image's default profile, `ethernet`, claims every Ethernet interface for DHCP at priority 10.

## A static address

Claim the interface by its bus path (from `net status`, stable across boots) and give it an address and gateway:

```
reg new Machine/System/Network/Profiles/office
reg set Machine/System/Network/Profiles/office Priority dword:100
reg new Machine/System/Network/Profiles/office/Match
reg set Machine/System/Network/Profiles/office/Match Path pci-0000:00:03.0
reg new Machine/System/Network/Profiles/office/Address
reg set Machine/System/Network/Profiles/office/Address DHCP4 dword:0
reg set Machine/System/Network/Profiles/office/Address Static multi:10.0.0.5/24
reg set Machine/System/Network/Profiles/office/Address Gateway 10.0.0.1
reg new Machine/System/Network/Profiles/office/DNS
reg set Machine/System/Network/Profiles/office/DNS Servers multi:10.0.0.1
```

Priority 100 outranks the default profile's 10, so `office` wins for that interface and DHCP stops. `net status` shows the new address and `level routed` once the default route is in place.

## Match keys

Every value present under `Match` must hold; a profile with no match keys matches nothing. Comparison ignores case and allows one `*`.

| Value | Matches | Example |
|---|---|---|
| `Name` | the kernel's interface name | `enp*` |
| `MAC` | the hardware address | `52:54:00:*` |
| `Path` | the bus path from the inventory | `pci-0000:00:03.0` |
| `Driver` | the kernel driver | `virtio_net` |
| `Type` | `ether`, `wlan`, `loopback`, `other` | `ether` |

Prefer `Path` or `MAC` for a specific machine and `Type` or `Driver` for a class. `Name` is the least stable: the kernel may call the same card something else after a hardware change.

## DHCP with adjustments

The default profile is DHCP with everything from the lease. To keep DHCP but pin DNS, or to hold the address across an outage:

```
reg set Machine/System/Network/Profiles/ethernet/DNS UseFromDHCP dword:0
reg set Machine/System/Network/Profiles/ethernet/DNS Servers multi:1.1.1.1,9.9.9.9
```

Two more `DNS` values decide *which* names an interface's servers are
asked for, which matters the moment a second interface appears —
typically a VPN:

```
reg set Machine/System/Network/Profiles/vpn/DNS SearchDomains multi:corp.example
reg set Machine/System/Network/Profiles/vpn/DNS DNSDefaultRoute dword:1
reg set Machine/System/Network/Profiles/vpn/DNS DNSExclusive dword:1
```

`SearchDomains` sends names under `corp.example` to this interface's
servers. `DNSDefaultRoute` sends *every other* name there too (unset, an
interface claims that only when it carries the default route).
`DNSExclusive` goes further: while this interface is up, no other
interface's servers are consulted for anything — the "ultimate
protection" a VPN wants, and the setting that stops a hostile LAN from
advertising the VPN's own domain. See [name resolution](/peios/using-peios/networking/name-resolution.md).
reg set Machine/System/Network/Profiles/ethernet/Address OnLeaseExpiry Keep
```

`OnLeaseExpiry Keep` trades the risk of an address conflict for continuity; the default `Drop` is the safe choice.

## Prefer a cable over WiFi

Both interfaces get a default route; the metric decides which is used. Wired defaults to 100 and wireless to 600, so no configuration is needed. To invert it, set `RouteMetric` on the profiles.

## Hand an interface to something else

`Managed` off means netd never touches a matched interface — no addresses, no routes, no link changes, no DHCP:

```
reg new Machine/System/Network/Profiles/leave-alone
reg set Machine/System/Network/Profiles/leave-alone Priority dword:200
reg new Machine/System/Network/Profiles/leave-alone/Match
reg set Machine/System/Network/Profiles/leave-alone/Match Name tap0
reg set Machine/System/Network/Profiles/leave-alone Managed dword:0
```

## Take an interface down

`Enabled` in the inventory is the persistent switch, independent of the profile:

```
reg set Machine/System/Network/Interfaces/<ifid> Enabled dword:0
```

The interface id is in `net status`. Set it back to 1 to bring the interface up again.

## Set the hostname

```
reg set Machine/System/Network Hostname workshop
```

netd applies it on the next pass. A profile with `AcceptHostname` on adopts a DHCP-supplied name when this value is unset.

## Where to go next

- [The net command](~peios/networking/the-net-command) — read back what netd did.
- [Networking](~peios/networking/overview) — the model behind these keys.

---

# The net command

_Peios / Using Peios / Networking_

> net shows what netd made of the network — interfaces, levels, leases — and asks it to renew, reconcile, or wait for a readiness level.

`net` talks to netd over `/run/netd/control.sock`. It shows state and pokes the daemon; it does not change configuration — that is a registry write, and [Configuring profiles](/peios/using-peios/networking/configuring-profiles.md) covers it.

## Commands

| Command | Does |
|---|---|
| `net status` | Hostname, the machine's readiness level, and every interface: profile, link state, level, hardware identity, addresses, gateway, DNS, lease. |
| `net wait <level> [seconds]` | Block until the machine reaches `link`, `addressed` or `routed`, or the timeout (default 60) passes. For scripts that need the network. |
| `net renew <interface>` | Renew the DHCP lease now. Accepts a kernel name or an interface id. |
| `net reconcile` | Re-run the reconciler immediately rather than waiting for an event. |
| `net profile list` | The profiles in the registry with their priorities. |

## Reading `net status`

```
hostname  workshop
level     routed

enp0s3  [3f2a…]
  profile   ethernet
  state     up, carrier
  level     routed
  hardware  52:54:00:12:34:56 pci-0000:00:03.0 virtio_net
  address   10.0.2.15/24
  gateway   10.0.2.2
  dns       10.0.2.3
  lease     bound from 10.0.2.2, 86395s left
```

`level` is the readiness level: `absent` (down or no carrier), `link`, `addressed`, `routed`. The machine's level is the highest among managed interfaces. `state` also reports `unmanaged` (the profile has `Managed` off) or `disabled` (`Enabled` is 0 in the inventory).

The bracketed value after the name is the interface id — the inventory key under `Machine\System\Network\Interfaces`.

## Who may run it

The control object is a security descriptor: `Machine\System\Network\ControlSecurity`, or a compiled default when that is unset. `status`, `wait` and `profile list` need `NETWORK_QUERY`, which the default grants to everyone. `renew` and `reconcile` need `NETWORK_CONTROL`, which the default grants to SYSTEM and Administrators. A denied request reports `access denied`.

## Exit status

| Code | Meaning |
|---|---|
| 0 | Done; for `wait`, the level was reached. |
| 1 | netd refused or could not be reached; for `wait`, the timeout passed. |
| 2 | Usage error. |

## See also

- [Networking](/peios/using-peios/networking/overview.md)
- [Configuring profiles](/peios/using-peios/networking/configuring-profiles.md)
- `regman Machine\System\Network` on the machine, for every key netd reads.

---

# Name resolution

_Peios / Using Peios / Networking_

> How a Peios machine turns names into addresses — resolvd answers every program through one policy, per-interface servers come from netd, static names live in the registry, and there is no /etc/hosts.

Every name a program asks for on a Peios machine is answered by one
process: **resolvd**, the stub resolver. It is a DNS *client* — it does
not recurse and it is not a server — and it is the only source of host
resolution, the way `authd` is the only source of identity.

## Three doors

A program reaches resolvd one of three ways, and gets the same answer
by each:

- **`getaddrinfo`** — glibc's `hosts` database is fixed to
  `libnss_peios_net.so.2`, which forwards to resolvd. There is nothing
  to configure in `nsswitch.conf` and nothing behind it: no `files`, no
  `dns`.
- **DNS on `127.0.0.53`** — for programs that resolve without libc (a
  Go binary, a musl program). `/etc/resolv.conf` is a constant file
  pointing here; it is never regenerated and carries no server list.
- **The native socket** — `/run/resolvd/resolv.sock`, for Peios tools
  and `resolv`, which get records with TTLs, where the answer came from,
  and a validation state.

A bare `printer` typed into a Go program is expanded with the same
search domains, sent to the same server and cached in the same place as
one from a C program.

## Where the servers come from

netd tells resolvd what each interface contributes — servers, search
domains, addresses, whether it carries the default route — over its
control socket, live. Nothing about that is in the registry, and
nothing is in a file. `resolv status` shows the picture:

```
hostname   workshop
netd       connected
cache      12 entries

eth0  metric 100  [default-route]
  server   10.0.2.3
  domain   lan
  subnet   10.0.2.15/24
```

A name goes to **one** interface's servers, never to all of them:

1. an *exclusive* interface, if one is up (a VPN with
   `DNSExclusive = 1` takes everything);
2. the interface whose search domain the name ends with — `git.corp`
   goes to the VPN that supplied `corp`;
3. for a reverse lookup, the interface whose subnet holds the address;
4. otherwise the interface with the default route.

That is what stops a VPN's names leaking to the coffee-shop network.
Only when *no* interface supplies servers does resolvd use
`Machine\System\Network\Resolver Servers`.

A single-label name is only ever asked with a search domain appended;
with none to apply it is simply not found, and no query leaves the
machine. A name with a dot in it is never expanded.

## Static names

There is no `/etc/hosts`. Put static names in the registry:

```
reg new Machine/System/Network/Resolver/Hosts
reg set Machine/System/Network/Resolver/Hosts printer 10.0.2.9
```

resolvd answers `printer` at every door — including the DNS door, so
the Go binary sees it too — and `10.0.2.9` reverses to `printer`. An
entry here wins over DNS. `localhost`, the machine's own hostname, and
their reverses are answered without configuration.

`.local` names are never sent to a DNS server (they belong to mDNS,
which resolvd does not yet speak), and LLMNR is never used.

## The `resolv` command

| Command | What it does |
|---|---|
| `resolv status` | Scopes, servers (with any currently demoted), cache size, counters. |
| `resolv query <name> [type]` | One question. Prints the outcome, the source (`synthetic`, `hosts`, `cache`, `dns` via which server on which interface), and the records. Exit 0 found, 2 not found, 3 unavailable. |
| `resolv lookup <name>` | The addresses of a name as `getaddrinfo` sees them, with the canonical name. |
| `resolv reverse <address>` | The names of an address. |
| `resolv flush` | Drop the cache (administrators). |

`resolv query` tells you three things `dig` cannot: whether the answer
was cached, which interface's server gave it, and — once resolvd
validates — whether it was DNSSEC-secure. Today every network answer
reports `unvalidated`.

## Not found versus unavailable

resolvd keeps two things apart that `/etc/hosts`-era stacks blur. A
name that a server says does not exist is **not found**, and is
remembered for a few minutes. A name that could not be resolved because
every server timed out or refused is **unavailable**: it is never
cached, and programs see "try again" rather than "no such host", so an
outage is not remembered as a fact.

## What is not there yet

DNSSEC validation, DNS over TLS and mDNS are not in this version;
`Resolver\Policy` is reserved for them. Per-program resolution policy
is not either — the native socket knows who is asking, so it can be
added without changing the wire.
