From 361c586a7bd16d1697b6038d4188716f801b9fd5 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 18:21:01 +0300 Subject: docs-infra: force latexmk through per-glyph/per-image LaTeX errors in PDF build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pdflatex hard-errors on the Devanagari etymology text in docs/introducing/history.md ("! LaTeX Error: Unicode character व (U+0935) not set up for use with LaTeX") and separately cannot embed several pre-existing .webp images (no BoundingBox) — both previously undiscovered because the Devanagari error always halted the build first. latexmk -f (force mode) + pdflatex -interaction=nonstopmode makes the build tolerate both classes of per-glyph/per-image failure and finish, matching how ReadTheDocs has always built this project's PDF (verified against the live docs.vyos.io PDF: same Devanagari glyphs blanked, same ~4 images embedded out of 2000+ pages). latexmk exits non-zero in force mode even on a fully-produced PDF, so the workflow step now verifies the artifact itself (exists, >2MB) instead of relying on the command's exit code. 🤖 Generated by [robots](https://vyos.io) --- .github/workflows/docs-build.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index fc9a2239..cff205e2 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -56,15 +56,36 @@ jobs: cache-to: type=gha,mode=max - name: Build HTML + PDF in in-workflow-built container + env: + # pdflatex hard-errors ("! LaTeX Error: Unicode character ... not + # set up for use with LaTeX") on the Devanagari etymology text in + # docs/introducing/history.md, and separately cannot embed some + # pre-existing .webp images (no BoundingBox). latexmk's force mode + # (-f) plus non-interactive pdflatex (-interaction=nonstopmode) + # makes it skip both classes of per-glyph/per-image failure and + # finish the document instead of halting — this is how ReadTheDocs + # has always built this project's PDF (verified against the live + # docs.vyos.io PDF: same Devanagari glyphs blanked, same ~4 images + # embedded out of 2000+ pages, rather than a failed build). + # latexmk still exits non-zero in force mode even when it produces + # a complete PDF, so success below is verified by checking the + # artifact itself, not the command's exit code. + LATEXMKOPTS: -f + LATEXOPTS: -interaction=nonstopmode run: | set -eu docker run --rm -v "$PWD:/src" -w /src \ -e DOCS_VERSION_SLUG="${{ steps.matrix.outputs.slug }}" \ -e DOCS_VERSION_BRANCH="${{ github.ref_name }}" \ + -e LATEXMKOPTS \ + -e LATEXOPTS \ docs-build:local bash -c ' + set -e cd docs && make html if [ "${{ inputs.skip_pdf }}" != "true" ]; then - make latexpdf || make latexpdf # retry once (§14 LaTeX flakiness) + make latexpdf || true + pdf=$(find _build/latex -maxdepth 1 -name "*.pdf" -size +2M) + [ -n "$pdf" ] || { echo "no PDF >2MB produced in _build/latex — build genuinely failed"; exit 1; } fi' - name: Assemble artifact (nest under en//, §7.1) -- cgit v1.2.3 From 50acf8f188475b0554b99a739cb8ada12d51e77b Mon Sep 17 00:00:00 2001 From: zdc Date: Fri, 10 Jul 2026 18:33:10 +0300 Subject: docs(DMVPN): T4667: Add firewall rule to prevent unencrypted GRE leaks (#2136) * docs(DMVPN): T4667: Add firewall rule to prevent unencrypted GRE leaks In DMVPN the mGRE tunnel and the IPSec protecting it are handled independently, so GRE can be forwarded while no IPSec SA is active for a peer (e.g. while an SA is still being negotiated or after one expires), allowing unencrypted GRE to leave the router. This is inherent to combining GRE with IPSec and is common to DMVPN implementations in general. Add a "Protecting against unencrypted traffic leaks" section to the DMVPN reference page explaining the behaviour and recommending an output filter rule that drops GRE not matched by an outbound IPSec policy (ipsec match-none-out). Note that this disables unencrypted GRE on the node entirely, so coexisting plain GRE tunnels would stop working. Apply the same rule in the Dual HUB Dual Cloud example on the VyOS nodes. Co-authored-by: Daniil Baturin * docs(DMVPN): reflow DMVPN documentation for line length compliance Reformat the DMVPN guide and dual-hub dual-cloud example to wrap long lines and improve readability without changing the documented behavior or configuration guidance. --------- Co-authored-by: Daniil Baturin --- docs/configexamples/dmvpn-dualhub-dualcloud.md | 59 ++++++++++++---- docs/configuration/vpn/dmvpn.md | 95 ++++++++++++++++++++------ 2 files changed, 119 insertions(+), 35 deletions(-) diff --git a/docs/configexamples/dmvpn-dualhub-dualcloud.md b/docs/configexamples/dmvpn-dualhub-dualcloud.md index 20c1a064..d98f45d9 100644 --- a/docs/configexamples/dmvpn-dualhub-dualcloud.md +++ b/docs/configexamples/dmvpn-dualhub-dualcloud.md @@ -6,11 +6,12 @@ lastproofread: '2024-02-21' # DMVPN Dual HUB Dual Cloud -This document is to describe a basic setup to build DMVPN network with two Hubs and two clouds using DMVPN Phase3. +This document is to describe a basic setup to build DMVPN network with two Hubs +and two clouds using DMVPN Phase3. OSPF is used as routing protocol inside DMVPN. -In this example we use VyOS 1.5 as HUBs and Spokes (HUB-1, HUB-2, SPOKE-2, SPOKE-3) and Cisco IOSv 15.5(3)M (SPOKE-1) -as a Spoke. +In this example we use VyOS 1.5 as HUBs and Spokes (HUB-1, HUB-2, SPOKE-2, +SPOKE-3) and Cisco IOSv 15.5(3)M (SPOKE-1) as a Spoke. ## Network Topology @@ -79,10 +80,12 @@ set protocols static route 0.0.0.0/0 next-hop 10.0.13.1 ### NHRP configuration -The next step is to configure the NHRP protocol. In a Dual cloud network, every HUB has to be configured with one GRE -multipoint tunnel interface and every spoke has to be configured with two tunnel interfaces, one tunnel to each hub. -In this example tunnel networks are 10.100.100.0/24 for the first cloud and 10.100.101.0/24 for the second cloud. -But VyOS uses FRR for NHRP, that is why the tunnel address mask must be /32. +The next step is to configure the NHRP protocol. In a Dual cloud network, every +HUB has to be configured with one GRE multipoint tunnel interface and every +spoke has to be configured with two tunnel interfaces, one tunnel to each hub. +In this example tunnel networks are 10.100.100.0/24 for the first cloud +and 10.100.101.0/24 for the second cloud. But VyOS uses FRR for NHRP, that is +why the tunnel address mask must be /32. HUB-1 @@ -209,8 +212,10 @@ set protocols nhrp tunnel tun101 shortcut ### Overlay configuration -The last step is to configure the routing protocol. In this scenario, OSPF was chosen as the dynamic routing protocol. -But you can use iBGP or eBGP. To form fast convergence it is possible to use BFD protocol. +The last step is to configure the routing protocol. In this scenario, OSPF was +chosen as the dynamic routing protocol. +But you can use iBGP or eBGP. To form fast convergence it is possible to use +BFD protocol. HUB-1 @@ -380,10 +385,31 @@ SPOKE-1 tunnel protection ipsec profile gre_protection shared ``` +Because GRE forwarding in DMVPN is independent of IPSec, there can be conditions +when traffic is routed over the tunnel but there is no active IPsec SA for +a peer that would get that packets encrypted at that moment. +That may result in unencrypted GRE leaving the router. + +To prevent this, drop any GRE that is not protected by an outbound IPSec policy. +Add the following rule on the VyOS nodes (HUB-1, HUB-2, SPOKE-2 and SPOKE-3), +and make sure it comes before any rule that permits GRE. + +Note that this disables unencrypted GRE on the node entirely, +so any plain GRE tunnels without IPSec will stop working. +If your setup requires unencrypted GRE tunnels together with DMVPN, +you have to find a way to exempt their traffic from that filter. +See {ref}`vpn-dmvpn` for the full explanation. + +```none +set firewall ipv4 output filter rule 10 action 'drop' +set firewall ipv4 output filter rule 10 protocol 'gre' +set firewall ipv4 output filter rule 10 ipsec match-none-out +``` ## Monitoring -All spokes created IPSec tunnels to Hubs, are registered on Hubs using NHRP protocol and formed adjacency in OSPF. +All spokes created IPSec tunnels to Hubs, are registered on Hubs using NHRP +protocol and formed adjacency in OSPF. ```none vyos@HUB-1:~$ show vpn ipsec sa @@ -472,7 +498,8 @@ trace to 192.168.11.2, 8 hops max, press Ctrl+C to stop ``` First trace goes via HUB but the second goes directly from SPOKE-1 to SPOKE-2. -Now routing tables are changed. LAN networks 192.168.12.0/24 and 192.168.11.0/24 available directly via SPOKES. +Now routing tables are changed. LAN networks 192.168.12.0/24 +and 192.168.11.0/24 available directly via SPOKES. ```none vyos@SPOKE-2:~$ show ip route @@ -545,8 +572,10 @@ dmvpn-NHRPVPN-tun101-child up 5m58s 5K/4K 62/51 ## Summary -If one of the Hubs loses connectivity to the Internet, the other Hub will be available and take the main role. -This is a simple example where only one internet connection is used. But in the real world, there can be two -connections to the Internet. In this case, there is a recommendation to build each tunnel via each Internet connection, -choose the main cloud, and manipulate traffic via a routing protocol. It allows the creation failover on link-level +If one of the Hubs loses connectivity to the Internet, the other Hub will be +available and take the main role. This is a simple example where only one +internet connection is used. But in the real world, there can be two +connections to the Internet. In this case, there is a recommendation to build +each tunnel via each Internet connection, choose the main cloud, and manipulate +traffic via a routing protocol. It allows the creation failover on link-level connections too. diff --git a/docs/configuration/vpn/dmvpn.md b/docs/configuration/vpn/dmvpn.md index 4dc2c85f..dc0cd4f4 100644 --- a/docs/configuration/vpn/dmvpn.md +++ b/docs/configuration/vpn/dmvpn.md @@ -63,8 +63,8 @@ set interfaces tunnel tun100 source-interface 'eth0' :::{note} The IP-address is assigned as host prefix to tunnel interface. - NHRP will automatically create additional host routes pointing to tunnel interface - when a connection with these hosts is established. + NHRP will automatically create additional host routes pointing to tunnel + interface when a connection with these hosts is established. ::: The tunnel interface subnet prefix should be announced by routing protocol @@ -114,12 +114,13 @@ then destination NBMA address (or addresses) are learnt dynamically. * **network-id** - NHRP network id <1-4294967295> -Enable NHRP on this interface and set the interface’s network ID. The network ID -is used to allow creating multiple nhrp domains on a router when multiple interfaces -are configured on the router. Interfaces configured with the same ID are part of the -same logical NBMA network. The ID is a local only parameter and is not sent to other -NHRP nodes and so IDs on different nodes do not need to match. When NHRP packets are -received on an interface they are assigned to the local NHRP domain for that interface. +Enable NHRP on this interface and set the interface’s network ID. +The network ID is used to allow creating multiple nhrp domains on a router when +multiple interfaces are configured on the router. Interfaces configured with +the same ID are part of the same logical NBMA network. The ID is a local only +parameter and is not sent to other NHRP nodes and so IDs on different nodes +do not need to match. When NHRP packets are received on an interface they +are assigned to the local NHRP domain for that interface. ``` ```{cfgcmd} set protocols nhrp tunnel \ nhs tunnel-ip \ nbma \ @@ -127,39 +128,40 @@ received on an interface they are assigned to the local NHRP domain for that int * **tunnel-ip** - Tunnel ip address in format **x.x.x.x** or **dynamic** * **nbma-ip** - NBMA ip address in format **x.x.x.x** -Configure the Next Hop Server address and its NBMA address. If dynamic is specified -then Next Hop Server can have dynamic address which maps to its NBMA address. +Configure the Next Hop Server address and its NBMA address. If dynamic is +specified then Next Hop Server can have dynamic address which maps to +its NBMA address. ``` ```{cfgcmd} set protocols nhrp tunnel \ redirect This enable redirect replies on the NHS similar to ICMP redirects except this is -managed by the nhrp protocol. This setting allows spokes to communicate with each -others directly. +managed by the nhrp protocol. This setting allows spokes to communicate with +each others directly. ``` ```{cfgcmd} set protocols nhrp tunnel \ registration-no-unique -Allow the client to not set the unique flag in the NHRP packets. This is useful when -a station has a dynamic IP address that could change over time. +Allow the client to not set the unique flag in the NHRP packets. This is useful +when a station has a dynamic IP address that could change over time. ``` ```{cfgcmd} set protocols nhrp tunnel \ shortcut -Enable shortcut (spoke-to-spoke) tunnels to allow NHC to talk to each others directly -after establishing a connection without going through the hub. +Enable shortcut (spoke-to-spoke) tunnels to allow NHC to talk to each others +directly after establishing a connection without going through the hub. ``` ### IPSEC configuration -- Please refer to the {ref}`ipsec_general` documentation for the individual IPSec - related options. +- Please refer to the {ref}`ipsec_general` documentation for the individual + IPSec related options. :::{note} NHRP daemon based on FRR nhrpd. It controls IPSEC. That's why 'close-action' -parameter in IKE configuration always is set to 'close' and 'dead-peer-detection action' -always is set to 'clear'. +parameter in IKE configuration always is set to 'close' +and 'dead-peer-detection action' always is set to 'clear'. ::: ```{cfgcmd} set vpn ipsec profile \ authentication mode pre-shared-secret @@ -188,6 +190,59 @@ Map IKE group to IPSEC profile ``` +### Protecting against unencrypted traffic leaks + +In DMVPN, the mGRE tunnel and the IPSec SA that protects it are handled +independently: GRE forwarding follows the DMVPN/NHRP routing decisions on its +own and does not depend on IPSec SAs being established. + +Because peers are discovered and IPSec SAs are negotiated on demand, +there are conditions when traffic is routed over the tunnel while there is +no active IPSec security association for a given peer—for example, while +the SA for a newly discovered spoke is still being negotiated, or after an +existing SA has expired. + +Such conditions can be short-lived, but they can also persist for a long time +depending on the state of IPSec. +Whenever they occur, the affected packets may leave the router as +unencrypted GRE. This is an inherent property of running GRE and IPSec +independently and is common to DMVPN implementations in general. + +To close this gap you can add a firewall rule that drops any GRE traffic that is +not protected by an outbound IPSec policy. The `match-none-out` matcher matches +packets leaving the router that did not match any outbound IPSec policy, so +combined with `protocol gre` and `action drop` it discards GRE that would +otherwise leave the router in cleartext: + +```none +set firewall ipv4 output filter rule 10 action 'drop' +set firewall ipv4 output filter rule 10 protocol 'gre' +set firewall ipv4 output filter rule 10 ipsec match-none-out +``` + +:::{note} +This rule must be evaluated before any rule that permits GRE. Give it a low rule +number (here `rule 10`) so that it is placed ahead of any GRE-permitting rules +in the `output` filter. Only GRE that is already protected by IPSec +(i.e., matches an outbound IPSec policy) will then be allowed out. +::: + +:::{note} +Because this rule drops all GRE that is not protected by IPSec, it disables +In that case, refine the rule so that it only matches the DMVPN traffic you want +to protect (for example, by also matching on the tunnel source). +Alternatively, you can explicitly allow traffic of known unencrypted tunnels +by their source or destination addresses, or other criteria. +to protect (for example, by also matching on the tunnel source). +Alternatively, you can explicitly allow traffic of known unencrypted tunnels +by their source or destination addresses, or other criteria. +::: + +- Please refer to the {ref}`firewall-ipv4-configuration` documentation for +details on the `set firewall ipv4 output filter rule ipsec match-none-out` +matcher and other firewall options. + + ## Monitoring ```{opcmd} show ip nhrp cache -- cgit v1.2.3 From bd19892033a56782065b40a11adf7162a9dacb4a Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 19:45:50 +0300 Subject: docs-infra: PDF artifact check — exact file, page-count floor, restored retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- .github/workflows/docs-build.yml | 24 ++++++++++++++++++++---- docker/Dockerfile | 3 ++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index cff205e2..3cc4298c 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -69,7 +69,13 @@ jobs: # embedded out of 2000+ pages, rather than a failed build). # latexmk still exits non-zero in force mode even when it produces # a complete PDF, so success below is verified by checking the - # artifact itself, not the command's exit code. + # produced artifact directly (exact filename + page count), not the + # command's exit code. A bare existence/size check would let a + # truncated-but-large PDF (LaTeX aborting partway through) pass, so + # the completeness signal is pdfinfo's page count instead of file + # size. The retry-once below (§14 LaTeX flakiness) still applies + # first; the trailing `|| true` only swallows latexmk -f's expected + # non-zero exit on a genuinely complete document. LATEXMKOPTS: -f LATEXOPTS: -interaction=nonstopmode run: | @@ -83,9 +89,19 @@ jobs: set -e cd docs && make html if [ "${{ inputs.skip_pdf }}" != "true" ]; then - make latexpdf || true - pdf=$(find _build/latex -maxdepth 1 -name "*.pdf" -size +2M) - [ -n "$pdf" ] || { echo "no PDF >2MB produced in _build/latex — build genuinely failed"; exit 1; } + make latexpdf || make latexpdf || true + pdf=_build/latex/VyOS.pdf + [ -f "$pdf" ] || { echo "$pdf not produced — build genuinely failed"; exit 1; } + # Page-count floor as a completeness signal (a truncated forced-mode + # run can still leave behind a file that exists and is large). rolling + # is verified ~2000+ pages; 1.4/1.5 page counts have not been + # individually verified, so 1000 is a conservative floor intended to + # clear all three release branches without masking a real truncation. + pages=$(pdfinfo "$pdf" 2>/dev/null | awk "/^Pages:/{print \$2}") + case "$pages" in + ""|*[!0-9]*) pages=0 ;; + esac + [ "$pages" -ge 1000 ] || { echo "$pdf has $pages pages (<1000) — build genuinely failed"; exit 1; } fi' - name: Assemble artifact (nest under en//, §7.1) diff --git a/docker/Dockerfile b/docker/Dockerfile index fee5c91c..05171a07 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,7 +25,8 @@ RUN apt-get update && apt-get install -y \ gosu \ graphviz \ curl \ - dos2unix + dos2unix \ + poppler-utils RUN pip3 install --break-system-packages \ Sphinx \ -- cgit v1.2.3 From ac31ab70216716bcaebb4168074b9a790f4fda8a Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Fri, 10 Jul 2026 19:57:21 +0300 Subject: docs-infra: retry LaTeX only when PDF absent; constrained poppler install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by [robots](https://vyos.io) --- .github/workflows/docs-build.yml | 13 +++++++++---- docker/Dockerfile | 6 +++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 3cc4298c..bdb9e41d 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -73,9 +73,11 @@ jobs: # command's exit code. A bare existence/size check would let a # truncated-but-large PDF (LaTeX aborting partway through) pass, so # the completeness signal is pdfinfo's page count instead of file - # size. The retry-once below (§14 LaTeX flakiness) still applies - # first; the trailing `|| true` only swallows latexmk -f's expected - # non-zero exit on a genuinely complete document. + # size. The §14 retry-once is gated on artifact ABSENCE: latexmk -f + # exits non-zero even on a fully-produced PDF, so an unconditional + # `|| make latexpdf` would double the ~10-min LaTeX step on every + # forced success. Each `|| true` only swallows that expected + # non-zero exit; the validation below is the real success signal. LATEXMKOPTS: -f LATEXOPTS: -interaction=nonstopmode run: | @@ -89,8 +91,11 @@ jobs: set -e cd docs && make html if [ "${{ inputs.skip_pdf }}" != "true" ]; then - make latexpdf || make latexpdf || true pdf=_build/latex/VyOS.pdf + make latexpdf || true + # retry ONLY when no artifact was produced (§14 LaTeX flakiness) — + # latexmk -f exits non-zero even on success, so exit code cannot gate this + [ -f "$pdf" ] || make latexpdf || true [ -f "$pdf" ] || { echo "$pdf not produced — build genuinely failed"; exit 1; } # Page-count floor as a completeness signal (a truncated forced-mode # run can still leave behind a file that exists and is large). rolling diff --git a/docker/Dockerfile b/docker/Dockerfile index 05171a07..ba79bbb1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,7 +25,11 @@ RUN apt-get update && apt-get install -y \ gosu \ graphviz \ curl \ - dos2unix \ + dos2unix + +# pdfinfo (PDF page-count validation in the docs-build workflow) ships in the +# main poppler-utils package — no recommends needed. +RUN apt-get update && apt-get install -y --no-install-recommends \ poppler-utils RUN pip3 install --break-system-packages \ -- cgit v1.2.3