Warda-DNSDocs Warda: from ward — to protect, guardian
v0.8.2

Protection outside the home

From 0.8.0 (subscription, roaming; beta, open to everyone during the beta; internal/roaming, internal/admin/roaming.go, internal/relayaddr). The threats it answers: a phone or a laptop of the family leaves the filter of the house as soon as it leaves the Wi-Fi (mobile data, the Wi-Fi of a friend, of a school, of a hotel), and a phone that shares its connection takes the devices that join it out of the filter too.

What it does. Each device of the household enrolled on the box gets its own address of DNS over HTTPS at the online service: https://<online service>/dns/<10 letters of the box>/<secret> (for example https://cloud.warda-dns.com/dns/k3x9abcdef/abcdefghijklmnopqrstuvwxyz). The secret is 26 characters of base32 in lower case (130 bits) from the random generator of the system, shown once: the box keeps only its SHA-256. A lost address cannot be shown again: New address gives another one, and the old one stops at once. The box keeps a WebSocket open to the online service (GET /v1/relay, signed with the key of the box, tls/cloud.key, like its other calls); the service passes each query of an enrolled device through it, and the box answers as that device, through the same filter as on the network (server.Handler.ServeAs): the rules of the device, the categories and the services of its person or profile, safe search, the hours of Internet and the cuts, the pauses and the names unblocked for the device, the quarantine, the threats, the restricted mode, the statistics and the query log. It works for a device not seen on the network for days (its profile is kept).

How to use it. Household → Outside the home (#/roaming; marked Subscription and Beta; the roles that change the devices):

  1. The box needs its name of the online service: Network → DNS services → step 1 → certificate "A name given by Warda" (<letters>.warda-dns.net, see Encrypted DNS for the devices; only the name is used, not the certificate). Without it the page says "Waiting: the box has no name of the online service of Warda", with a link, and no address can be given (409).
  2. Turn on Protect the enrolled devices outside the home (off by default); optionally give the name of the home Wi-Fi (32 bytes at most), written in the profiles of the Apple devices.
  3. Choose a device (or Protect outside on its row of Household → Devices) and give it an address: the page shows it once, with Copy, a QR code, Download the profile (iPhone, iPad, Mac) and a foldable block per platform, the one of the system Warda guessed for the device open.

The page also shows the connection (connected since, the last problem and when, the box name and the service, the queries answered through the relay since the start), the queries the service answered without the box (below), the enrolled devices (New address, Remove, both confirmed), the events of the last 30 days on mobile data at home, and the privacy line. Without the plan (once subscriptions open) the page shows the lock and the relay does not run.

What to install on each device (the texts of the page):

  • iPhone, iPad, Mac: the profile (com.apple.dnsSettings.managed, DNS over HTTPS, ServerURL the address, identifier com.warda-dns.roaming.<device id> so that a new profile replaces the old, shown as "Warda – <device name>"). With the name of the home Wi-Fi, the profile asks the box directly on that Wi-Fi and the relay elsewhere (OnDemandRules); without it, always the relay (it works at home too, marked "at home"). The profile is not signed (the device says so). It is made only right after the address is given, since the box does not keep the secret.
  • Android: Private DNS takes a host name, not an address: it cannot carry the address. An app that accepts a DNS over HTTPS address does: RethinkDNS (DNS → Other DNS → DNS over HTTPS → custom address) or Intra (Settings → Select DNS-over-HTTPS server → Custom server URL). Both run as a local VPN: turn on Always-on VPN for the app in the settings of Android.
  • Windows 11: Settings → Network & internet → the adapter → Hardware properties → DNS server assignment → Manual: IPv4 preferred DNS an IPv4 address of the service (nslookup), DNS over HTTPS On (manual template), the template the address, fallback to plaintext off. The address of the service (Cloudflare) may change: then put the new one.
  • Firefox (Settings → Privacy & Security → DNS over HTTPS → Max Protection → Custom), Chrome (Security → Use secure DNS → Customised), Edge (Privacy, search and services → Use secure DNS → Choose a service provider): a browser filters only its own queries.

Mobile data at home and connection sharing. The box tells the parents when a minor's device probably goes around the filter of the house. What it reads, and only that: the home flag the service sends with each query (the public address of the device is the one of the box's connection: the same IPv4 address, or the same IPv6 /56; the addresses are never kept nor sent to the box), the last time the device asked the box on the network (the relayed queries never change it), and the last relayed query from home. Both events are inferred, and their texts say so:

  • bypass.mobile: an enrolled device asks through the relay from outside less than 15 minutes after it was last seen at home: "{device} uses mobile data (or another network) less than 15 minutes after it was on your network: still at home, or just gone out. Its own traffic stays filtered; other devices sharing its connection are not, unless enrolled." The box cannot tell "at home on mobile data" from "just gone out", nor mobile data from another Wi-Fi. Once per device per 30 minutes; a device is looked at once a minute at most. An enrolled laptop that leaves the Wi-Fi for the hotspot of a phone gives the same event (and stays filtered).
  • bypass.sharing: within the 2 minutes after it, another device of the same person, or a device of a child or a teenager that is not enrolled, which had asked the box in the 3 minutes before, stops asking: "{other} stopped asking Warda on your network just after {device} switched to mobile data (or another network): probably on the connection it shares, and not filtered." Probable only: a device that falls asleep or is switched off at that moment looks the same. Not seen: a device that was not asking just before, an adult's device, a guest's device.

Adults are not watched: the devices of an adult (alone) give no event; the events are written for a minor's device (person: the minor), a device of nobody, or a device shared with a minor. They are in Alerts (group Bypass, subscription; SIEM severity 4) and on the page. A device of the hotspot that is not enrolled is not filtered: it is only told, probably.

Queries answered without the box. When the box is not connected, or does not answer within 3 seconds (power, the Internet of the house down, an update, a deployment of the service), the service answers by itself through Quad9 (WARDA_CLOUD_RELAY_UPSTREAM, which blocks known malware), without the rules of the household, so that the device never loses Internet. The welcome of each connection tells the box how many it answered since the last one; the box adds them up (meta roaming_fallback, bounded at 10^12) and the page says "N queries answered without filter while the box was unreachable (since …)", with Reset the count.

The relay, in detail. A hello with the SHA-256 of each enrolled secret, then the whole list again at each enrolment, removal or merge of devices (and checked every minute); pings every 30 seconds, the connection closed without a pong for 75 seconds; reconnection after 1 second, doubled up to 60 seconds with jitter (back to 1 second only after a connection that lasted a minute; a Retry-After of the service followed, an hour at most; never two connections in the same second, since the signature covers the date in seconds and the service refuses a signature seen twice); closed with 1000 "relay turned off" when the protection is turned off or the box loses its name, 1001 "box stopping" when Warda stops. Frames of 24 KiB at most; an answer too large for a frame (about 18 KB) is sent truncated (TC set); more than 256 queries in flight get SERVFAIL at once, never left unanswered. Each enrolled device has two addresses of its own in 100::/64, the IPv6 block that discards everything (RFC 6666), one for "outside" and one for "at home through the relay", which every part of the filter maps to the device; every listener of the box (DNS, DoT, DoH, DoQ, the interface) refuses a packet from 100::/64 whatever the allowed ranges (even ::/0), since such a source on the network could only be forged to pose as an enrolled device. A merge of two devices moves the enrolment to the device kept (its own wins) and the relay reads its devices at once.

Stored. The table roaming_devices (the SHA-256 of each secret, the device, when), in the encrypted database; meta roaming (on/off, the home Wi-Fi) and roaming_fallback; the column relayed of the journal. Not in the backups: the enrolments are tied to the name of this box, and a restore forgets them (the devices must get new addresses). From 0.8.2 the setting (on or off, the home Wi-Fi) is in the backups (see Backups); before, a restore kept the one of the box it was restored on.

In a pair of high availability (from 0.8.2): the secondary receives the enrolled devices, the setting and the identity of the relay of the primary, stays in standby while the primary answers, and relays the devices under the primary's identity while it is down, so that they stay filtered; the addresses are given and taken back on the primary only. See The relay outside the home in a pair. Up to 0.8.0 only the primary relayed, and while it was down the service answered without filter. Administration log: roaming.settings (on/off), roaming.enroll, roaming.revoke (the device), roaming.fallback.reset.

What leaves the box, and to whom. The queries of a device outside the home go device → Cloudflare → warda-cloud → the box (WebSocket) → back, encrypted on each leg (HTTPS, WSS); TLS ends at Cloudflare, which carries the service and can see them in passing, as for the whole service. warda-cloud keeps none of them (no name, no answer, no address): only the SHA-256 of each secret, when the relay was last connected, the count of the queries it answered itself, and counts by day (see cloud). The queries it answers itself go to Quad9, in plain DNS from the server unless the service is set with tls://. The box sends the hashes of the secrets and the answers; the secret is shown once and sent back by the page only to make a profile (in the body of a POST, never in an address). The relayed queries are in the query log and the statistics of the box like any query of the device, with the mark outside.

Limits, said plainly.

  • Only what the device itself asks through its system DNS: an app with its own DNS or DNS over HTTPS, a VPN, iCloud Private Relay go around it; Android needs a third-party app; a browser set alone filters only itself.
  • While the box is unreachable, the answers are not filtered (counted, as above).
  • A removed device gets no DNS outside until its setting is removed from it (the service answers 404).
  • The address depends on the name of the box at the online service and on the host of the service: choosing another mode of certificate gives the name back (the addresses stop working); a failover of the service to another domain changes the host, and the enrolled devices keep the old address (give them new ones).
  • The names of the home network are answered at home only (from 0.8.2, internal/server/handler.go). A query the service relays with home false (the device outside the home) gets NXDOMAIN, as a public resolver would answer, for: the names of Warda (warda, warda.lan, warda.home.arpa, warda.<local domain>, wpad.<local domain>); the local DNS records, whatever their name (a record of a public-looking name such as nas.example.com too: the box cannot tell a record made for the network from one made to override a public name); the names of the devices (DHCP, scan) and their reverse lookups; the local zones (conditional forwarding, such as a domain of Active Directory), which are not asked to their resolver; and, as for every client, the names under the local domain, the single labels, .lan, .home, .local, .internal, home.arpa and the reverse lookups of private ranges. None of them is sent to an upstream server. The names of the encrypted DNS of the box (<letters>.warda-dns.net) are public: outside they are resolved the usual way (filtered), never with the address of the network. At home (home true, or on the network) nothing changes. The journal gives these queries the status local, counted with the local answers. Up to 0.8.0 they were answered outside too.
  • "At home" is decided by the service from the public addresses. A box that reaches the service over IPv4 only (Docker without IPv6, for example) while the phone at home uses IPv6 is seen outside: its queries are marked "Outside the home", and from 0.8.2 it loses warda and the local names on that path. The bypass.mobile event needs the device seen on the network in the 15 minutes before, so it is rarely affected. A device whose profile has the home Wi-Fi asks the box directly at home, and has neither problem.
  • An Apple profile (not supervised) can be removed by the user of the device (Settings → General → VPN & Device Management), an Android app stopped: the box does not see it (no query, no signal).
  • 256 devices per box.
  • Cloudflare in front of the service: its bot protections could challenge some DNS over HTTPS clients (a point the owner of Warda tests).

API (all devices area: read for GET, written otherwise):

Route Body and answer
GET /api/v1/roaming enabled, home_wifi, waiting (off, plan, no_name, missing, from 0.8.2 standby: the secondary of a pair while the primary relays), name (of the box), service (host), relay (connected, since, last_error, last_error_at, devices, answered), fallback (count, since, last, last_since, last_at), devices (device_id, label, kind, system, person_name, enrolled), max (256), events (30 days); 402 without the plan
PUT /api/v1/roaming {"enabled": true, "home_wifi": "…"} → the view; 400 for a Wi-Fi name over 32 bytes
POST /api/v1/roaming/devices/{id} gives or renews the address: {"device_id", "label", "url", "secret"}, the only time the secret is returned; 409 when off, without a name, 256 reached, or on the secondary of a pair; 400 for the box itself
DELETE /api/v1/roaming/devices/{id} removes the address; 404 when not enrolled; 409 on the secondary of a pair
POST /api/v1/roaming/devices/{id}/profile {"secret": "…"} (compared in constant time with the hash kept) → the .mobileconfig as an attachment; 400 for another secret
POST /api/v1/roaming/fallback/reset the count back to 0