4cdedd435f42129c04fedf9b53f2aa8adca71cfa
howto/vyos1.5.x.md
| ... | ... | @@ -0,0 +1,624 @@ |
| 1 | +# VyOS 1.5.x circinus |
|
| 2 | + |
|
| 3 | +VyOS is an open source software router. It is feature rich and supports |
|
| 4 | +multiple deployment options such as physical hardware (old PCs) or a VPC/VM. |
|
| 5 | + |
|
| 6 | +VyOS offers three release channels: |
|
| 7 | + |
|
| 8 | +- **Rolling release** – bleeding-edge nightly builds with all the latest |
|
| 9 | + features (including WireGuard), but no stability guarantees: anything may |
|
| 10 | + change and experimental features can be added or removed at any time. Best |
|
| 11 | + for development and testing only. |
|
| 12 | +- **VyOS Stream** – a quarterly technology-preview / quality-gate on the way |
|
| 13 | + to the 1.5 (circinus) LTS. It carries most of the upcoming LTS features but |
|
| 14 | + is considerably more stable than the rolling release, since features are |
|
| 15 | + only backported once their design is settled. Stream images are named after |
|
| 16 | + their release year and month, e.g. `vyos-2026.03-generic-amd64.iso`. |
|
| 17 | +- **LTS** – the stable long-term-support release, available to subscribers |
|
| 18 | + (or via the no-cost subscription for those who qualify, or by building from |
|
| 19 | + source). |
|
| 20 | + |
|
| 21 | +This guide was written and tested on **VyOS Stream (circinus), version |
|
| 22 | +2026.03**. |
|
| 23 | + |
|
| 24 | +Rolling images: <https://vyos.net/get/nightly-builds/><br> |
|
| 25 | +VyOS Stream images: <https://vyos.net/get/stream/> |
|
| 26 | + |
|
| 27 | +This guide uses the following example values throughout. Replace the |
|
| 28 | +**operator-assigned** ones with your own DN42 registry allocation; the |
|
| 29 | +**DN42-wide** ranges are fixed and must be left as-is. |
|
| 30 | + |
|
| 31 | +| Example value | Meaning | Change it? | |
|
| 32 | +| ------------------------ | ---------------------------------------- | -------------------------- | |
|
| 33 | +| `4242421234` | **Your** ASN | Yes | |
|
| 34 | +| `4242424242` | **Your peer's** ASN | Yes | |
|
| 35 | +| `172.20.20.0/27` | **Your assigned IPv4** (`inetnum`) | Yes | |
|
| 36 | +| `fd88:9deb:a69e::/48` | **Your assigned IPv6** (`inet6num`) | Yes | |
|
| 37 | +| `10.0.0.0/8`, `172.20.0.0/14`, `172.31.0.0/16` | Whole DN42 IPv4 space | No, leave as-is | |
|
| 38 | +| `fd00::/8` | Whole DN42 IPv6 space | No, leave as-is | |
|
| 39 | +| `172.20.0.53`, `172.23.0.53` | DN42 recursive resolvers | No, leave as-is | |
|
| 40 | + |
|
| 41 | +## Firewall |
|
| 42 | + |
|
| 43 | +### Firewall Baseline (stateful global) |
|
| 44 | + |
|
| 45 | +By default, VyOS performs **stateless** filtering: with no ruleset nothing is filtered, and once a drop-by-default ruleset is applied, return traffic of established sessions is **not** allowed back automatically. We enable stateful inspection globally so we don't have to repeat established/related rules in every ruleset. |
|
| 46 | + |
|
| 47 | +```sh |
|
| 48 | +set firewall global-options state-policy established action 'accept' |
|
| 49 | +set firewall global-options state-policy related action 'accept' |
|
| 50 | +``` |
|
| 51 | + |
|
| 52 | +### Edge: accept invalid |
|
| 53 | + |
|
| 54 | +We also accept `invalid` packets on our network's edge. This should **not** become common practice elsewhere. It is specific to a DN42 BGP edge, where conntrack-untracked traffic (including BGP) can otherwise be wrongly classified as `invalid` and dropped. |
|
| 55 | + |
|
| 56 | +```sh |
|
| 57 | +set firewall global-options state-policy invalid action 'accept' |
|
| 58 | +``` |
|
| 59 | + |
|
| 60 | +### Per-WireGuard-interface rulesets |
|
| 61 | + |
|
| 62 | +These create **in** (transit) and **local** (to-the-router) baseline templates to be applied to every WireGuard interface facing a peer. |
|
| 63 | + |
|
| 64 | +Only the two `My-Assigned-Space-*` lines need editing to match your own registry prefixes; the `Allowed-Transit-*` ranges are the whole of DN42 and stay as-is. |
|
| 65 | + |
|
| 66 | +```sh |
|
| 67 | +# --- Groups v4 --- |
|
| 68 | +set firewall group network-group Allowed-Transit-v4 network '10.0.0.0/8' |
|
| 69 | +set firewall group network-group Allowed-Transit-v4 network '172.20.0.0/14' |
|
| 70 | +set firewall group network-group Allowed-Transit-v4 network '172.31.0.0/16' |
|
| 71 | +set firewall group network-group My-Assigned-Space-v4 network '172.20.20.0/27' |
|
| 72 | + |
|
| 73 | +# --- Groups v6 --- |
|
| 74 | +set firewall group ipv6-network-group Allowed-Transit-v6 network 'fd00::/8' |
|
| 75 | +set firewall group ipv6-network-group My-Assigned-Space-v6 network 'fd88:9deb:a69e::/48' |
|
| 76 | + |
|
| 77 | +# --- Inbound / transit v4 --- |
|
| 78 | +set firewall ipv4 name Tunnels_In_v4 default-action 'drop' |
|
| 79 | +set firewall ipv4 name Tunnels_In_v4 default-log |
|
| 80 | +set firewall ipv4 name Tunnels_In_v4 rule 68 action 'drop' |
|
| 81 | +set firewall ipv4 name Tunnels_In_v4 rule 68 description 'Block Traffic to Operator Assigned IP Space' |
|
| 82 | +set firewall ipv4 name Tunnels_In_v4 rule 68 destination group network-group 'My-Assigned-Space-v4' |
|
| 83 | +set firewall ipv4 name Tunnels_In_v4 rule 68 log |
|
| 84 | +set firewall ipv4 name Tunnels_In_v4 rule 70 action 'accept' |
|
| 85 | +set firewall ipv4 name Tunnels_In_v4 rule 70 description 'Allow Peer Transit' |
|
| 86 | +set firewall ipv4 name Tunnels_In_v4 rule 70 destination group network-group 'Allowed-Transit-v4' |
|
| 87 | +set firewall ipv4 name Tunnels_In_v4 rule 70 source group network-group 'Allowed-Transit-v4' |
|
| 88 | +set firewall ipv4 name Tunnels_In_v4 rule 99 action 'drop' |
|
| 89 | +set firewall ipv4 name Tunnels_In_v4 rule 99 description 'Black Hole' |
|
| 90 | +set firewall ipv4 name Tunnels_In_v4 rule 99 log |
|
| 91 | + |
|
| 92 | +# --- Inbound / transit v6 --- |
|
| 93 | +set firewall ipv6 name Tunnels_In_v6 default-action 'drop' |
|
| 94 | +set firewall ipv6 name Tunnels_In_v6 default-log |
|
| 95 | +set firewall ipv6 name Tunnels_In_v6 rule 68 action 'drop' |
|
| 96 | +set firewall ipv6 name Tunnels_In_v6 rule 68 description 'Block Traffic to Operator Assigned IP Space' |
|
| 97 | +set firewall ipv6 name Tunnels_In_v6 rule 68 destination group network-group 'My-Assigned-Space-v6' |
|
| 98 | +set firewall ipv6 name Tunnels_In_v6 rule 68 log |
|
| 99 | +set firewall ipv6 name Tunnels_In_v6 rule 70 action 'accept' |
|
| 100 | +set firewall ipv6 name Tunnels_In_v6 rule 70 description 'Allow Peer Transit' |
|
| 101 | +set firewall ipv6 name Tunnels_In_v6 rule 70 destination group network-group 'Allowed-Transit-v6' |
|
| 102 | +set firewall ipv6 name Tunnels_In_v6 rule 70 source group network-group 'Allowed-Transit-v6' |
|
| 103 | +set firewall ipv6 name Tunnels_In_v6 rule 99 action 'drop' |
|
| 104 | +set firewall ipv6 name Tunnels_In_v6 rule 99 description 'Black Hole' |
|
| 105 | +set firewall ipv6 name Tunnels_In_v6 rule 99 log |
|
| 106 | + |
|
| 107 | +# --- Local / to the router v4 --- |
|
| 108 | +set firewall ipv4 name Tunnels_Local_v4 default-action 'drop' |
|
| 109 | +set firewall ipv4 name Tunnels_Local_v4 rule 50 action 'accept' |
|
| 110 | +set firewall ipv4 name Tunnels_Local_v4 rule 50 protocol 'icmp' |
|
| 111 | +set firewall ipv4 name Tunnels_Local_v4 rule 61 action 'accept' |
|
| 112 | +set firewall ipv4 name Tunnels_Local_v4 rule 61 description 'Allow BGP' |
|
| 113 | +set firewall ipv4 name Tunnels_Local_v4 rule 61 destination port '179' |
|
| 114 | +set firewall ipv4 name Tunnels_Local_v4 rule 61 protocol 'tcp' |
|
| 115 | +set firewall ipv4 name Tunnels_Local_v4 rule 99 action 'drop' |
|
| 116 | +set firewall ipv4 name Tunnels_Local_v4 rule 99 description 'Black Hole' |
|
| 117 | +set firewall ipv4 name Tunnels_Local_v4 rule 99 log |
|
| 118 | + |
|
| 119 | +# --- Local / to the router v6 --- |
|
| 120 | +set firewall ipv6 name Tunnels_Local_v6 default-action 'drop' |
|
| 121 | +set firewall ipv6 name Tunnels_Local_v6 rule 50 action 'accept' |
|
| 122 | +set firewall ipv6 name Tunnels_Local_v6 rule 50 protocol 'ipv6-icmp' |
|
| 123 | +set firewall ipv6 name Tunnels_Local_v6 rule 61 action 'accept' |
|
| 124 | +set firewall ipv6 name Tunnels_Local_v6 rule 61 description 'Allow BGP' |
|
| 125 | +set firewall ipv6 name Tunnels_Local_v6 rule 61 destination port '179' |
|
| 126 | +set firewall ipv6 name Tunnels_Local_v6 rule 61 protocol 'tcp' |
|
| 127 | +set firewall ipv6 name Tunnels_Local_v6 rule 99 action 'drop' |
|
| 128 | +set firewall ipv6 name Tunnels_Local_v6 rule 99 description 'Black Hole' |
|
| 129 | +set firewall ipv6 name Tunnels_Local_v6 rule 99 log |
|
| 130 | +``` |
|
| 131 | + |
|
| 132 | +## WireGuard |
|
| 133 | + |
|
| 134 | +### Setup Keys |
|
| 135 | + |
|
| 136 | +You can generate one keypair and reuse it for every WireGuard peering, or generate a different one per peering. |
|
| 137 | + |
|
| 138 | +```sh |
|
| 139 | +generate pki wireguard key-pair |
|
| 140 | + |
|
| 141 | +# Output give public keys like: UcqcZsJvq1MlYgo3gObjaJ8FH+N7wkfV+EH3YDAMyRE= |
|
| 142 | +``` |
|
| 143 | + |
|
| 144 | +To generate **and install** a unique keypair on an interface in one shot (from `configure` mode, top level): |
|
| 145 | + |
|
| 146 | +A WireGuard interface name on VyOS must be `wg` followed by digits only (letters are rejected, so `wgpeer` won't work). The name has no protocol meaning, but since interface names must be unique, the DN42 convention is to name each tunnel after the peer's ASN (for example `wg4242424242`, or a short form). That way the name tells you who's on the other end. Don't name a tunnel after your own ASN: it would collide the moment you add a second peer and wouldn't identify anything. |
|
| 147 | + |
|
| 148 | +```sh |
|
| 149 | +run generate pki wireguard key-pair install interface wg4242 |
|
| 150 | +# 1 value(s) installed. Use "compare" to see pending changes, and "commit" to apply. |
|
| 151 | +# Corresponding public-key to use on peer system is: 'UcqcZsJvq1MlYgo3gObjaJ8FH+N7wkfV+EH3YDAMyRE=' |
|
| 152 | +``` |
|
| 153 | + |
|
| 154 | +To retrieve the public key later (op-mode): |
|
| 155 | + |
|
| 156 | +```sh |
|
| 157 | +run show interfaces wireguard wg4242 public-key |
|
| 158 | + |
|
| 159 | +# Output example: |
|
| 160 | +# UcqcZsJvq1MlYgo3gObjaJ8FH+N7wkfV+EH3YDAMyRE= |
|
| 161 | +``` |
|
| 162 | + |
|
| 163 | +### Configure the peer's tunnel |
|
| 164 | + |
|
| 165 | +This example assumes your ASN is `4242421234` and your peer's ASN is `4242424242`. |
|
| 166 | +Interface naming follows the DN42 convention `wg<peer-ASN-last-4-digits>` -> `wg4242`. |
|
| 167 | + |
|
| 168 | +```sh |
|
| 169 | +set interfaces wireguard wg4242 description 'AS4242424242 - My Peer' |
|
| 170 | + |
|
| 171 | +# DN42 port convention: |
|
| 172 | +# - YOUR listen port = 2 + last 4 digits of your PEER's ASN -> 2 + 4242 = 24242 |
|
| 173 | +# - the port you point to = 2 + last 4 digits of YOUR ASN -> 2 + 1234 = 21234 |
|
| 174 | +set interfaces wireguard wg4242 port '24242' |
|
| 175 | + |
|
| 176 | +# NOTE: the private key is already installed by |
|
| 177 | +# 'run generate pki wireguard key-pair install interface wg4242' |
|
| 178 | +# Do NOT re-set it here, or you will overwrite your real key. |
|
| 179 | + |
|
| 180 | +# Your link-local IPv6 (the one you give to the auto-peering portal). |
|
| 181 | +# Arbitrary, but must be inside fe80::/64, e.g. derived from your ASN. |
|
| 182 | +set interfaces wireguard wg4242 address 'fe80::1234/64' |
|
| 183 | + |
|
| 184 | +# (No IPv4 on the tunnel: with extended-next-hop enabled, your DN42 IPv4 |
|
| 185 | +# lives on the LAN interface instead, anchored by a blackhole route, |
|
| 186 | +# see the BGP prerequisites section below.) |
|
| 187 | + |
|
| 188 | +# Your peer's clearnet endpoint, must be an IP literal, not a DNS name. |
|
| 189 | +# Resolve the peer's FQDN first and use the resulting IP. |
|
| 190 | +set interfaces wireguard wg4242 peer mypeer address '<peer endpoint IP>' |
|
| 191 | +set interfaces wireguard wg4242 peer mypeer port '21234' |
|
| 192 | + |
|
| 193 | +# Allow everything and rely on the firewall + BGP for control |
|
| 194 | +set interfaces wireguard wg4242 peer mypeer allowed-ips '0.0.0.0/0' |
|
| 195 | +set interfaces wireguard wg4242 peer mypeer allowed-ips '::/0' |
|
| 196 | + |
|
| 197 | +# Your peer's WireGuard public key (from the auto-peering result) |
|
| 198 | +set interfaces wireguard wg4242 peer mypeer public-key '<peer wireguard public key>' |
|
| 199 | + |
|
| 200 | +# Helps the BGP session come up reliably |
|
| 201 | +set interfaces wireguard wg4242 peer mypeer persistent-keepalive '60' |
|
| 202 | + |
|
| 203 | +# Remove the auto-generated link-local so the BGP session sources from YOUR |
|
| 204 | +# chosen link-local only. With two link-locals on the interface, BGP may source |
|
| 205 | +# from the wrong one and the peer won't recognize the session. |
|
| 206 | +set interfaces wireguard wg4242 ipv6 address no-default-link-local |
|
| 207 | +``` |
|
| 208 | + |
|
| 209 | +**Placeholders to replace with your own values:** |
|
| 210 | + |
|
| 211 | +| Placeholder | Meaning | |
|
| 212 | +| ----------------------------- | ----------------------------------------------------- | |
|
| 213 | +| `wg4242` | interface name = `wg` + your peer's last 4 ASN digits | |
|
| 214 | +| `24242` | your listen port = `2` + peer's last 4 ASN digits | |
|
| 215 | +| `fe80::1234/64` | your link-local (the one you gave the portal) | |
|
| 216 | +| `<peer endpoint IP>` | peer's clearnet endpoint, as an IP literal | |
|
| 217 | +| `21234` | peer's port = `2` + your last 4 ASN digits | |
|
| 218 | +| `<peer wireguard public key>` | from the auto-peering result | |
|
| 219 | + |
|
| 220 | +### Apply the firewall rulesets to the tunnel |
|
| 221 | + |
|
| 222 | +```sh |
|
| 223 | +# Group every DN42 peer tunnel together (add future wgXXXX interfaces here) |
|
| 224 | +set firewall group interface-group DN42-Peers interface 'wg4242' |
|
| 225 | + |
|
| 226 | +# Transit traffic (forward hook) -> Tunnels_In_* |
|
| 227 | +set firewall ipv4 forward filter rule 10 description 'DN42 peers transit' |
|
| 228 | +set firewall ipv4 forward filter rule 10 action 'jump' |
|
| 229 | +set firewall ipv4 forward filter rule 10 inbound-interface group 'DN42-Peers' |
|
| 230 | +set firewall ipv4 forward filter rule 10 jump-target 'Tunnels_In_v4' |
|
| 231 | +set firewall ipv6 forward filter rule 10 description 'DN42 peers transit' |
|
| 232 | +set firewall ipv6 forward filter rule 10 action 'jump' |
|
| 233 | +set firewall ipv6 forward filter rule 10 inbound-interface group 'DN42-Peers' |
|
| 234 | +set firewall ipv6 forward filter rule 10 jump-target 'Tunnels_In_v6' |
|
| 235 | + |
|
| 236 | +# Traffic to the router itself, e.g. BGP (input hook) -> Tunnels_Local_* |
|
| 237 | +set firewall ipv4 input filter rule 10 description 'DN42 peers to router' |
|
| 238 | +set firewall ipv4 input filter rule 10 action 'jump' |
|
| 239 | +set firewall ipv4 input filter rule 10 inbound-interface group 'DN42-Peers' |
|
| 240 | +set firewall ipv4 input filter rule 10 jump-target 'Tunnels_Local_v4' |
|
| 241 | +set firewall ipv6 input filter rule 10 description 'DN42 peers to router' |
|
| 242 | +set firewall ipv6 input filter rule 10 action 'jump' |
|
| 243 | +set firewall ipv6 input filter rule 10 inbound-interface group 'DN42-Peers' |
|
| 244 | +set firewall ipv6 input filter rule 10 jump-target 'Tunnels_Local_v6' |
|
| 245 | +``` |
|
| 246 | + |
|
| 247 | +You will also want a plain accept for your own LAN, both for traffic transiting the router and for traffic to the router itself: |
|
| 248 | + |
|
| 249 | +```sh |
|
| 250 | +set firewall ipv4 forward filter rule 20 description 'LAN forward' |
|
| 251 | +set firewall ipv4 forward filter rule 20 action 'accept' |
|
| 252 | +set firewall ipv4 forward filter rule 20 inbound-interface name 'eth2' |
|
| 253 | +set firewall ipv4 input filter rule 20 description 'LAN to router' |
|
| 254 | +set firewall ipv4 input filter rule 20 action 'accept' |
|
| 255 | +set firewall ipv4 input filter rule 20 inbound-interface name 'eth2' |
|
| 256 | +set firewall ipv6 forward filter rule 20 description 'LAN forward v6' |
|
| 257 | +set firewall ipv6 forward filter rule 20 action 'accept' |
|
| 258 | +set firewall ipv6 forward filter rule 20 inbound-interface name 'eth2' |
|
| 259 | +set firewall ipv6 input filter rule 20 description 'LAN to router v6' |
|
| 260 | +set firewall ipv6 input filter rule 20 action 'accept' |
|
| 261 | +set firewall ipv6 input filter rule 20 inbound-interface name 'eth2' |
|
| 262 | +``` |
|
| 263 | + |
|
| 264 | +## BGP |
|
| 265 | + |
|
| 266 | +### Prerequisites: anchor your prefixes (blackhole + LAN address) |
|
| 267 | + |
|
| 268 | +BGP only advertises a prefix from a `network` statement if a route of the **exact |
|
| 269 | +same length** already exists in the routing table. Instead of a dummy interface, |
|
| 270 | +we anchor each registry prefix with a static **blackhole** route. A blackhole |
|
| 271 | +route is always present regardless of interface or tunnel state, so BGP keeps |
|
| 272 | +advertising your aggregate even when a link flaps. |
|
| 273 | + |
|
| 274 | +Your actual DN42 addresses live on your **LAN-facing interface**, where your |
|
| 275 | +clients and services sit. Traffic to real hosts follows the connected route; |
|
| 276 | +traffic to unused addresses in the range falls through to the blackhole and is |
|
| 277 | +dropped, so you never leak or loop packets for hosts that don't exist. |
|
| 278 | + |
|
| 279 | +```sh |
|
| 280 | +# DN42 addresses on the LAN interface (adjust eth2 to your LAN NIC). |
|
| 281 | +# The /27 gives you the IPv4 LAN; the /64 is one segment carved from your /48. |
|
| 282 | +set interfaces ethernet eth2 description 'LAN services DN42' |
|
| 283 | +set interfaces ethernet eth2 address '172.20.20.1/27' |
|
| 284 | +set interfaces ethernet eth2 address 'fd88:9deb:a69e:1::1/64' |
|
| 285 | + |
|
| 286 | +# Always-up aggregate routes so BGP can advertise the exact registry prefixes, |
|
| 287 | +# independent of any interface or tunnel state. |
|
| 288 | +set protocols static route 172.20.20.0/27 blackhole |
|
| 289 | +set protocols static route6 fd88:9deb:a69e::/48 blackhole |
|
| 290 | +``` |
|
| 291 | + |
|
| 292 | +Here the IPv4 LAN is a `/27` matching your `inetnum`, and the IPv6 `/48` |
|
| 293 | +blackhole matches your `inet6num` exactly, which is what the BGP `network` |
|
| 294 | +statements need. The `/64` on the LAN is just one usable segment of that `/48` |
|
| 295 | +for client autoconfiguration. |
|
| 296 | + |
|
| 297 | +Verify: |
|
| 298 | + |
|
| 299 | +```sh |
|
| 300 | +run show interfaces ethernet eth2 |
|
| 301 | +run show ip route 172.20.20.0/27 |
|
| 302 | +run show ipv6 route fd88:9deb:a69e::/48 |
|
| 303 | +``` |
|
| 304 | + |
|
| 305 | +### Initial Router Setup |
|
| 306 | + |
|
| 307 | +```sh |
|
| 308 | +# Your ASN |
|
| 309 | +set protocols bgp system-as '4242421234' |
|
| 310 | + |
|
| 311 | +# Advertise your EXACT registry prefixes (a more-specific subnet may be filtered by peers) |
|
| 312 | +set protocols bgp address-family ipv4-unicast network '172.20.20.0/27' |
|
| 313 | +set protocols bgp address-family ipv6-unicast network 'fd88:9deb:a69e::/48' |
|
| 314 | + |
|
| 315 | +# Router-id = your lowest DN42 IPv4 |
|
| 316 | +set protocols bgp parameters router-id '172.20.20.1' |
|
| 317 | +``` |
|
| 318 | + |
|
| 319 | +### Neighbor: MP-BGP over IPv6 link-local + extended next-hop |
|
| 320 | + |
|
| 321 | +One peer-group carries the address families, the extended-next-hop capability, and `remote-as external` (every DN42 peer is a different external AS). |
|
| 322 | + |
|
| 323 | +```sh |
|
| 324 | +# Shared peer-group for all DN42 link-local MP-BGP peers |
|
| 325 | +set protocols bgp peer-group dn42 remote-as 'external' |
|
| 326 | +set protocols bgp peer-group dn42 address-family ipv4-unicast |
|
| 327 | +set protocols bgp peer-group dn42 address-family ipv6-unicast |
|
| 328 | +set protocols bgp peer-group dn42 capability extended-nexthop |
|
| 329 | + |
|
| 330 | +# The peer, addressed on the peer's link-local, scoped to the tunnel interface. |
|
| 331 | +# Replace fe80::5678 with the link-local your peer's auto-peering portal gave you |
|
| 332 | +# (this is the PEER's link-local, not yours). |
|
| 333 | +set protocols bgp neighbor fe80::5678 interface source-interface 'wg4242' |
|
| 334 | +set protocols bgp neighbor fe80::5678 peer-group 'dn42' |
|
| 335 | +``` |
|
| 336 | + |
|
| 337 | +Address the peer's link-local **explicitly** as shown above. Avoid unnumbered |
|
| 338 | +peering (`neighbor wg4242 interface v6only`): unnumbered relies on the peer |
|
| 339 | +sending IPv6 Router Advertisements to auto-discover its link-local, and some |
|
| 340 | +peers (for example those running BIRD) do not send them, so the session stays |
|
| 341 | +`Idle` and never sends a single packet. |
|
| 342 | + |
|
| 343 | +Also, do not add `remote-as` on the neighbor when the peer-group already sets |
|
| 344 | +`remote-as external`; VyOS rejects it with `cannot override remote-as of |
|
| 345 | +peer-group`. |
|
| 346 | + |
|
| 347 | +### Verify |
|
| 348 | + |
|
| 349 | +```sh |
|
| 350 | +run show bgp summary # both address families |
|
| 351 | +run show bgp ipv4 summary |
|
| 352 | +run show bgp ipv6 summary |
|
| 353 | +``` |
|
| 354 | + |
|
| 355 | +A healthy session shows a number (received prefixes) under `State/PfxRcd`, not `Connect`/`Active`: |
|
| 356 | + |
|
| 357 | +```txt |
|
| 358 | +Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc |
|
| 359 | +fe80::5678 4 4242424242 1921 1850 1103 0 0 00:00:08 1097 1098 N/A |
|
| 360 | +``` |
|
| 361 | + |
|
| 362 | +Once the session is `Established`, confirm end-to-end routing to DN42. Because the |
|
| 363 | +router has no DN42 IPv4 on the tunnel, source pings from your DN42 LAN address: |
|
| 364 | + |
|
| 365 | +```sh |
|
| 366 | +run ping 172.20.0.53 source-address 172.20.20.1 count 3 |
|
| 367 | +run ping fd42:d42:d42:53::1 source-address fd88:9deb:a69e:1::1 count 3 |
|
| 368 | +``` |
|
| 369 | + |
|
| 370 | +A reply (with `ttl` < 64, showing it crossed DN42 hops) confirms the extended-next-hop |
|
| 371 | +is working, IPv4 destinations resolved via an IPv6 next-hop over the tunnel. |
|
| 372 | + |
|
| 373 | +## RPKI / ROA Filtering |
|
| 374 | + |
|
| 375 | +On DN42, RPKI lets your router reject route announcements whose origin AS doesn't match the registry (likely hijacks). You run a small **RTR cache** that downloads the DN42 ROA table, and point VyOS at it. |
|
| 376 | + |
|
| 377 | +Cloudflare's GoRTR is archived; DN42 now uses **StayRTR** (`rpki/stayrtr`), a |
|
| 378 | +drop-in fork with the same flags, and the ROA file is still published by Burble. |
|
| 379 | +StayRTR has no `-verify` flag (GoRTR had one): a stray `-verify=false` makes the |
|
| 380 | +container exit immediately (`Exited (2)`) and just print its help. Valid StayRTR |
|
| 381 | +args: `-cache <url> -checktime=false [-bind :8082]`. |
|
| 382 | + |
|
| 383 | +### Option A: Run StayRTR as a container on VyOS |
|
| 384 | + |
|
| 385 | +```sh |
|
| 386 | +# op-mode: pull the image |
|
| 387 | +run add container image rpki/stayrtr |
|
| 388 | +``` |
|
| 389 | + |
|
| 390 | +```sh |
|
| 391 | +# config-mode: an internal-only network for the cache |
|
| 392 | +set container network rpki prefix '172.16.2.0/24' |
|
| 393 | + |
|
| 394 | +# the StayRTR container, note: flags go in `arguments`, not `command` |
|
| 395 | +set container name stayrtr image 'rpki/stayrtr' |
|
| 396 | +set container name stayrtr arguments '-cache https://dn42.burble.com/roa/dn42_roa_46.json -checktime=false -bind :8082' |
|
| 397 | +set container name stayrtr network rpki address '172.16.2.10' |
|
| 398 | +set container name stayrtr restart 'on-failure' |
|
| 399 | +``` |
|
| 400 | + |
|
| 401 | +### Option B: Run StayRTR on a separate host (Docker) |
|
| 402 | + |
|
| 403 | +```sh |
|
| 404 | +docker run -d --name dn42-rpki --restart=always -p 8082:8082 \ |
|
| 405 | + rpki/stayrtr -cache https://dn42.burble.com/roa/dn42_roa_46.json \ |
|
| 406 | + -checktime=false -bind :8082 |
|
| 407 | +``` |
|
| 408 | + |
|
| 409 | +### Point VyOS at the cache |
|
| 410 | + |
|
| 411 | +```sh |
|
| 412 | +set protocols rpki cache 172.16.2.10 port '8082' |
|
| 413 | +set protocols rpki cache 172.16.2.10 preference '1' |
|
| 414 | +``` |
|
| 415 | + |
|
| 416 | +*(Use the container IP `172.16.2.10` for Option A, or the separate host's IP for Option B.)* |
|
| 417 | + |
|
| 418 | +Verify the cache is connected and the table is populated: |
|
| 419 | + |
|
| 420 | +```sh |
|
| 421 | +run show rpki cache-connection |
|
| 422 | +run show rpki prefix-table |
|
| 423 | +``` |
|
| 424 | + |
|
| 425 | +After pointing VyOS at the cache, you may need to force FRR to (re)connect. Otherwise it may stay |
|
| 426 | +on "No connection" because it gave up while the container was starting: |
|
| 427 | + |
|
| 428 | +```sh |
|
| 429 | +run reset rpki |
|
| 430 | +``` |
|
| 431 | + |
|
| 432 | +Then re-verify: |
|
| 433 | + |
|
| 434 | +```sh |
|
| 435 | +run show rpki cache-connection # expect: (connected) |
|
| 436 | +run show rpki prefix-table # expect: thousands of entries |
|
| 437 | +``` |
|
| 438 | + |
|
| 439 | +### Filtering route-map (RPKI + DN42 range sanity, combined) |
|
| 440 | + |
|
| 441 | +A single route-map does both jobs: drop RPKI-`invalid` prefixes **and** only accept prefixes that fall inside DN42's address space (a bogon guard RPKI alone doesn't give you). |
|
| 442 | + |
|
| 443 | +The `le '32'` on IPv4 is deliberate: DN42's recursive resolvers are announced as |
|
| 444 | +host routes (`172.20.0.53/32`, `172.23.0.53/32`). A stricter bound would silently |
|
| 445 | +drop every `/32`, so those resolvers never enter your table and DNS later fails |
|
| 446 | +for no obvious reason. Keep `le '32'` (and `le '64'` on IPv6) so host routes are |
|
| 447 | +accepted. |
|
| 448 | + |
|
| 449 | +```sh |
|
| 450 | +# --- Prefix-lists: DN42's allocated ranges --- |
|
| 451 | +set policy prefix-list DN42-v4 rule 10 action 'permit' |
|
| 452 | +set policy prefix-list DN42-v4 rule 10 prefix '172.20.0.0/14' |
|
| 453 | +set policy prefix-list DN42-v4 rule 10 le '32' |
|
| 454 | +set policy prefix-list DN42-v4 rule 20 action 'permit' |
|
| 455 | +set policy prefix-list DN42-v4 rule 20 prefix '10.0.0.0/8' |
|
| 456 | +set policy prefix-list DN42-v4 rule 20 le '32' |
|
| 457 | + |
|
| 458 | +set policy prefix-list6 DN42-v6 rule 10 action 'permit' |
|
| 459 | +set policy prefix-list6 DN42-v6 rule 10 prefix 'fd00::/8' |
|
| 460 | +set policy prefix-list6 DN42-v6 rule 10 le '64' |
|
| 461 | + |
|
| 462 | +# --- One combined filter, used both import and export --- |
|
| 463 | +set policy route-map DN42-PEERING rule 5 action 'deny' |
|
| 464 | +set policy route-map DN42-PEERING rule 5 description 'Reject RPKI invalid (possible hijack)' |
|
| 465 | +set policy route-map DN42-PEERING rule 5 match rpki 'invalid' |
|
| 466 | +set policy route-map DN42-PEERING rule 20 action 'permit' |
|
| 467 | +set policy route-map DN42-PEERING rule 20 description 'Accept DN42 IPv4 ranges' |
|
| 468 | +set policy route-map DN42-PEERING rule 20 match ip address prefix-list 'DN42-v4' |
|
| 469 | +set policy route-map DN42-PEERING rule 21 action 'permit' |
|
| 470 | +set policy route-map DN42-PEERING rule 21 description 'Accept DN42 IPv6 ranges' |
|
| 471 | +set policy route-map DN42-PEERING rule 21 match ipv6 address prefix-list 'DN42-v6' |
|
| 472 | +set policy route-map DN42-PEERING rule 99 action 'deny' |
|
| 473 | +set policy route-map DN42-PEERING rule 99 description 'Drop everything else' |
|
| 474 | +``` |
|
| 475 | + |
|
| 476 | +The same map works for both address families: a `match ip address` rule simply |
|
| 477 | +doesn't match IPv6 routes (and vice-versa), so each AF falls through to its own |
|
| 478 | +rule. The `le '32'` / `le '64'` also caps absurdly long prefixes; tighten or |
|
| 479 | +loosen to taste. |
|
| 480 | + |
|
| 481 | +### Apply the filter to the peer-group (not per neighbor) |
|
| 482 | + |
|
| 483 | +Because every DN42 peer shares the `dn42` peer-group, you apply the filter **once** and all peers inherit it: |
|
| 484 | + |
|
| 485 | +```sh |
|
| 486 | +set protocols bgp peer-group dn42 address-family ipv4-unicast route-map import 'DN42-PEERING' |
|
| 487 | +set protocols bgp peer-group dn42 address-family ipv4-unicast route-map export 'DN42-PEERING' |
|
| 488 | +set protocols bgp peer-group dn42 address-family ipv6-unicast route-map import 'DN42-PEERING' |
|
| 489 | +set protocols bgp peer-group dn42 address-family ipv6-unicast route-map export 'DN42-PEERING' |
|
| 490 | +commit |
|
| 491 | +``` |
|
| 492 | + |
|
| 493 | +### Apply & verify |
|
| 494 | + |
|
| 495 | +Route-maps only affect new updates, so refresh the session, then check. |
|
| 496 | + |
|
| 497 | +```sh |
|
| 498 | +vtysh -c "clear bgp * soft" |
|
| 499 | +show bgp ipv4 summary |
|
| 500 | +show bgp ipv6 summary |
|
| 501 | +vtysh -c "show bgp ipv4 unicast 172.20.0.0/14 longer-prefixes" # spot-check learned routes |
|
| 502 | +``` |
|
| 503 | + |
|
| 504 | +Your `State/PfxRcd` count may drop slightly after filtering, that's the invalid/out-of-range prefixes being rejected, which is exactly the point. |
|
| 505 | + |
|
| 506 | +To inspect what a specific neighbor sent you before filtering (handy when a route |
|
| 507 | +you expect is missing), enable soft-reconfiguration inbound and look at the |
|
| 508 | +filtered routes: |
|
| 509 | + |
|
| 510 | +```sh |
|
| 511 | +set protocols bgp neighbor fe80::5678 address-family ipv4-unicast soft-reconfiguration inbound |
|
| 512 | +commit |
|
| 513 | +``` |
|
| 514 | + |
|
| 515 | +```sh |
|
| 516 | +vtysh -c "show bgp ipv4 unicast neighbors fe80::5678 filtered-routes" |
|
| 517 | +``` |
|
| 518 | + |
|
| 519 | +## DNS Resolution (resolving `.dn42`) |
|
| 520 | + |
|
| 521 | +Once routing works, you'll want to resolve DN42 names like `git.dn42`. DN42 runs |
|
| 522 | +recursive resolvers (for example `172.20.0.53` and `172.23.0.53`) that also |
|
| 523 | +resolve the clearnet, so they can serve as your only upstream if you want |
|
| 524 | +everything to go through DN42. |
|
| 525 | + |
|
| 526 | +We run a small DNS forwarder on the router itself. LAN clients query the router, |
|
| 527 | +and the router forwards to the DN42 resolvers. |
|
| 528 | + |
|
| 529 | +Two things to get right, both reflected in the config below: |
|
| 530 | + |
|
| 531 | +Queries the router generates itself leave through the WireGuard tunnel, but the |
|
| 532 | +kernel has no DN42 IPv4 on that interface, so by default it picks your public WAN |
|
| 533 | +IP as the source. The DN42 resolver then has nowhere to send its reply and the |
|
| 534 | +query times out. `source-address` forces the forwarder to source from your DN42 |
|
| 535 | +LAN address. |
|
| 536 | + |
|
| 537 | +Use the **global** `name-server` form, not conditional `domain` forwarding. |
|
| 538 | +VyOS turns `domain` entries into pdns `forward-zones=` (RD=0), which asks the |
|
| 539 | +upstream only for what it is authoritative for. The DN42 resolvers are recursive, |
|
| 540 | +not authoritative for `.dn42`, so they answer `REFUSED`, surfaced to the client |
|
| 541 | +as `SERVFAIL`. Global `name-server` entries become `forward-zones-recurse` |
|
| 542 | +(RD=1, the `+` prefix in the generated zone file), which is what recursive |
|
| 543 | +upstreams expect. Since we want all DNS to go through DN42 anyway, the global |
|
| 544 | +form is both simpler and correct. |
|
| 545 | + |
|
| 546 | +### Config |
|
| 547 | + |
|
| 548 | +```sh |
|
| 549 | +# Listen on the router's DN42 LAN address; only the LAN may query us |
|
| 550 | +set service dns forwarding listen-address '172.20.20.1' |
|
| 551 | +set service dns forwarding allow-from '172.20.20.0/27' |
|
| 552 | + |
|
| 553 | +# Source outgoing queries from our DN42 address |
|
| 554 | +set service dns forwarding source-address '172.20.20.1' |
|
| 555 | + |
|
| 556 | +# Global upstreams = DN42 recursive resolvers (no 'domain' entries). |
|
| 557 | +# These resolve both .dn42 and the clearnet. |
|
| 558 | +set service dns forwarding name-server '172.20.0.53' |
|
| 559 | +set service dns forwarding name-server '172.23.0.53' |
|
| 560 | + |
|
| 561 | +# DN42 resolvers are not DNSSEC-signed to the ICANN root; validation would |
|
| 562 | +# SERVFAIL .dn42, so turn it off on the forwarder. |
|
| 563 | +set service dns forwarding dnssec 'off' |
|
| 564 | +commit |
|
| 565 | +``` |
|
| 566 | + |
|
| 567 | +### Verify |
|
| 568 | + |
|
| 569 | +From the router, query your own forwarder explicitly (not the system resolver): |
|
| 570 | + |
|
| 571 | +```sh |
|
| 572 | +dig @172.20.20.1 git.dn42 A # expect NOERROR + an answer |
|
| 573 | +dig @172.20.20.1 example.com A # clearnet must work too |
|
| 574 | +``` |
|
| 575 | + |
|
| 576 | +If you get `SERVFAIL`, check the generated zone file: the `.dn42` upstream must be |
|
| 577 | +the catch-all `+.` line (recursive). A separate `dn42=` line without the `+` |
|
| 578 | +prefix means a stray `domain` entry slipped in and should be removed. |
|
| 579 | + |
|
| 580 | +```sh |
|
| 581 | +sudo cat /run/pdns-recursor/recursor.forward-zones.conf |
|
| 582 | +# +.=172.20.0.53:53, 172.23.0.53:53 <- good, recursive |
|
| 583 | +# dn42=172.20.0.53:53, ... <- bad, non-recursive, remove the 'domain' entry |
|
| 584 | +``` |
|
| 585 | + |
|
| 586 | +To confirm the source address is correct, capture an outgoing query; the source |
|
| 587 | +must be your DN42 address, not your public WAN IP: |
|
| 588 | + |
|
| 589 | +```sh |
|
| 590 | +sudo tcpdump -ni any port 53 and host 172.20.0.53 |
|
| 591 | +# 172.20.20.1.xxxxx > 172.20.0.53.53 <- good |
|
| 592 | +# <public IP>.xxxxx > 172.20.0.53.53 <- bad, source-address not applied |
|
| 593 | +``` |
|
| 594 | + |
|
| 595 | +### Hand DNS to LAN clients |
|
| 596 | + |
|
| 597 | +Point your DHCP clients at the forwarder and drop the clearnet resolvers (which |
|
| 598 | +don't know `.dn42`): |
|
| 599 | + |
|
| 600 | +```sh |
|
| 601 | +set service dhcp-server shared-network-name LAN subnet 172.20.20.0/27 option name-server '172.20.20.1' |
|
| 602 | +commit |
|
| 603 | +``` |
|
| 604 | + |
|
| 605 | +With a single peer, all DNS (clearnet included) now depends on that BGP session |
|
| 606 | +and the DN42 resolvers. If the peer drops, the LAN loses DNS entirely. Add a |
|
| 607 | +second peer (and ideally resolvers reachable over different paths) before relying |
|
| 608 | +on this, or keep one clearnet resolver lower in the list as a fallback, at the |
|
| 609 | +cost of it not resolving `.dn42`. |
|
| 610 | + |
|
| 611 | +## Credits |
|
| 612 | + |
|
| 613 | +This How-To has to be considered a work-in-progress by **mathys-lopinto** |
|
| 614 | +The doc was updated with the help of Claude AI. |
|
| 615 | + |
|
| 616 | +It's based on the original VyOS How-To made by **Matwolf** and **bri**: [How-To/vyos1.4.x](/howto/vyos1.4.x). |
|
| 617 | + |
|
| 618 | +The commands in this page have been adapted to be compatible with the new version of VyOS 1.5.x (circinus) and to include configurations for IPv6 (MP-BGP over link-local and extended next-hop). |
|
| 619 | + |
|
| 620 | +If you have any questions or suggestions please reach out. |
|
| 621 | + |
|
| 622 | +## See also |
|
| 623 | + |
|
| 624 | +[WireGuard](https://docs.vyos.io/en/latest/configuration/interfaces/wireguard.html) and [BGP](https://docs.vyos.io/en/latest/configuration/protocols/bgp.html) in the official VyOS documentation. |
|
| ... | ... | \ No newline at end of file |