From 663133ed18e0d84df27c27a2c0cb6515c74996df Mon Sep 17 00:00:00 2001 From: Mikko Reinikainen Date: Mon, 11 May 2026 23:33:57 +0300 Subject: [PATCH 1/4] Add internet forwarding rules to chain DOCKER-USER instead of chain FORWARD - Chain FORWARD can contain Docker's DOCKER-FORWARD rules that accepts anything, so the forwarding rules never takes effect - DOCKER-USER is meant for policies like this that must run before Docker's own forwarding rules - Return error if chain DOCKER-USER doesn't exist --- .../lib/internet-forwarding.just | 20 +++++++++++-- .../test/examnet.test.ts | 30 +++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/ytl-linux-digabi2-examnet/lib/internet-forwarding.just b/packages/ytl-linux-digabi2-examnet/lib/internet-forwarding.just index 5a4ea81..759af74 100644 --- a/packages/ytl-linux-digabi2-examnet/lib/internet-forwarding.just +++ b/packages/ytl-linux-digabi2-examnet/lib/internet-forwarding.just @@ -30,6 +30,8 @@ enable net-device-wan net-device-lan error-code: \ just internet-forwarding enable-kernel-ip-forwarding {{error-code}} + just internet-forwarding assert-chain-exists filter DOCKER-USER {{error-code}} + # Remove possible existing iptables rules and recreate allowlist filter chain to ensure idempotency just internet-forwarding remove-iptables-rules-by-comment "$INTERNET_ALLOWLIST_IPSET_NAME" {{error-code}} just iptables recreate-chain filter $INTERNET_ALLOWLIST_FILTER_CHAIN {{error-code}} @@ -61,14 +63,14 @@ enable net-device-wan net-device-lan error-code: \ --jump DROP # Allow return traffic - just iptables add-rule filter FORWARD {{error-code}} \ + just iptables add-rule filter DOCKER-USER {{error-code}} \ --in-interface "{{net-device-wan}}" --out-interface "{{net-device-lan}}" \ --match comment --comment "$INTERNET_ALLOWLIST_IPSET_NAME" \ --match conntrack --ctstate RELATED,ESTABLISHED \ --jump ACCEPT # Enforce allowlist for LAN->WAN traffic - just iptables add-rule filter FORWARD {{error-code}} \ + just iptables add-rule filter DOCKER-USER {{error-code}} \ --in-interface "{{net-device-lan}}" --out-interface "{{net-device-wan}}" \ --match comment --comment "$INTERNET_ALLOWLIST_IPSET_NAME" \ --jump "$INTERNET_ALLOWLIST_FILTER_CHAIN" @@ -103,6 +105,19 @@ disable error-code: \ just log info "Done disabling internet forwarding" +assert-chain-exists table chain error-code: + #!/usr/bin/env bash + set -euo pipefail + + just log debug "Checking that iptables chain {{chain}} exists in table {{table}}" + + if ! iptables --wait --table {{table}} --list {{chain}} --line-numbers --numeric > /dev/null 2>&1; then + just log error "Required iptables chain {{chain}} does not exist in table {{table}}" + exit {{error-code}} + fi + + just log debug "Iptables chain {{chain}} exists in table {{table}}" + configure-logging error-code: #!/usr/bin/env bash set -euo pipefail @@ -153,6 +168,7 @@ remove-iptables-rules-by-comment comment error-code: set -euo pipefail just iptables remove-rules-by-comment nat POSTROUTING {{comment}} {{error-code}} + just iptables remove-rules-by-comment filter DOCKER-USER {{comment}} {{error-code}} just iptables remove-rules-by-comment filter FORWARD {{comment}} {{error-code}} flush-and-destroy-ipset ipset-name error-code: diff --git a/packages/ytl-linux-digabi2-examnet/test/examnet.test.ts b/packages/ytl-linux-digabi2-examnet/test/examnet.test.ts index 0c5436a..b6c0daa 100644 --- a/packages/ytl-linux-digabi2-examnet/test/examnet.test.ts +++ b/packages/ytl-linux-digabi2-examnet/test/examnet.test.ts @@ -237,6 +237,8 @@ describe('examnet (just port)', () => { callRm(`${mockEtcDir}/hosts.tmp`), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -271,6 +273,8 @@ describe('examnet (just port)', () => { callRm(`${mockEtcDir}/hosts.tmp`), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -303,6 +307,8 @@ describe('examnet (just port)', () => { callRm(`${mockEtcDir}/hosts.tmp`), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -476,8 +482,11 @@ describe('examnet (just port)', () => { callIpLinkShow('eth1'), callSysctl('1'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -510,12 +519,12 @@ describe('examnet (just port)', () => { callIptablesCheckChain('filter', 'YTL_LAN_WAN_IPSET', '--jump DROP'), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth0 --out-interface eth1 --match comment --comment ytl_internet_allowlist --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT' ), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth1 --out-interface eth0 --match comment --comment ytl_internet_allowlist --jump YTL_LAN_WAN_IPSET' ), callIptablesCheckChain( @@ -555,8 +564,11 @@ describe('examnet (just port)', () => { callIpLinkShow('eth1'), callSysctl('1'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -618,8 +630,11 @@ describe('examnet (just port)', () => { callIpLinkShow('eth1'), callSysctl('1'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -651,12 +666,12 @@ describe('examnet (just port)', () => { callIptablesCheckChain('filter', 'YTL_LAN_WAN_IPSET', '--jump DROP'), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth0 --out-interface eth1 --match comment --comment ytl_internet_allowlist --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT' ), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth1 --out-interface eth0 --match comment --comment ytl_internet_allowlist --jump YTL_LAN_WAN_IPSET' ), callIptablesCheckChain( @@ -813,8 +828,11 @@ describe('examnet (just port)', () => { callIpLinkShow('eth1'), callSysctl('1'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('nat', 'POSTROUTING'), callIptablesList('nat', 'POSTROUTING'), + callIptablesList('filter', 'DOCKER-USER'), + callIptablesList('filter', 'DOCKER-USER'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'FORWARD'), callIptablesList('filter', 'YTL_LAN_WAN_IPSET'), @@ -846,12 +864,12 @@ describe('examnet (just port)', () => { callIptablesCheckChain('filter', 'YTL_LAN_WAN_IPSET', '--jump DROP'), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth0 --out-interface eth1 --match comment --comment ytl_internet_allowlist --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT' ), callIptablesCheckChain( 'filter', - 'FORWARD', + 'DOCKER-USER', '--in-interface eth1 --out-interface eth0 --match comment --comment ytl_internet_allowlist --jump YTL_LAN_WAN_IPSET' ), callIptablesCheckChain( From 012c25b116e124c78dca9632dc2b9e4b3c6b9fce Mon Sep 17 00:00:00 2001 From: Mikko Reinikainen Date: Tue, 12 May 2026 07:43:24 +0300 Subject: [PATCH 2/4] Log also forwarded DNS requests, their replies and IP addresses added to ipset in ytl-linux-internet-forwarding.log --- .../rsyslog-internet-forwarding.conf.template | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/ytl-linux-digabi2-examnet/templates/rsyslog-internet-forwarding.conf.template b/packages/ytl-linux-digabi2-examnet/templates/rsyslog-internet-forwarding.conf.template index 1f10ee8..7139f98 100644 --- a/packages/ytl-linux-digabi2-examnet/templates/rsyslog-internet-forwarding.conf.template +++ b/packages/ytl-linux-digabi2-examnet/templates/rsyslog-internet-forwarding.conf.template @@ -5,5 +5,17 @@ if ($msg contains "YTL_ALLOW_NEW-") then { createDirs="off" fileCreateMode="0644" ) - stop +} + +if ($programname == "dnsmasq" and ( + $msg contains " forwarded " or + $msg contains " reply " or + $msg contains " ipset add " +)) then { + action( + type="omfile" + file="$PATH_INTERNET_FORWARDING_LOGS" + createDirs="off" + fileCreateMode="0644" + ) } From 58a1d521d9258be7bf09474ae3eabd06866903dd Mon Sep 17 00:00:00 2001 From: Mikko Reinikainen Date: Tue, 12 May 2026 08:04:03 +0300 Subject: [PATCH 3/4] Reduce dnsmasq DNS cache TTL - Microsoft has very short TTL for A records of the allowlisted servers --- .../ytl-linux-digabi2-examnet/templates/dnsmasq.conf.template | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/ytl-linux-digabi2-examnet/templates/dnsmasq.conf.template b/packages/ytl-linux-digabi2-examnet/templates/dnsmasq.conf.template index 44648de..738a325 100644 --- a/packages/ytl-linux-digabi2-examnet/templates/dnsmasq.conf.template +++ b/packages/ytl-linux-digabi2-examnet/templates/dnsmasq.conf.template @@ -47,3 +47,7 @@ ${ALLOWLISTED_IPSET_CONFIGURATION} # request stalls (since this is not a router) for however long the client timeout is set to; possibly Infinity address=/#/0.0.0.0 address=/#/:: + +# Reduce DNS cache TTLs as Microsoft has very short TTL for the A records of the allowlisted servers +max-cache-ttl=5 +max-ttl=5 From 6cf738a3e53430719b121ef78edbda2907b03074 Mon Sep 17 00:00:00 2001 From: Mikko Reinikainen Date: Tue, 12 May 2026 09:26:26 +0300 Subject: [PATCH 4/4] Change legacy test name - This way it is easier to recognize which test are being run --- packages/ytl-linux-digabi2-examnet/test/examnet-legacy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ytl-linux-digabi2-examnet/test/examnet-legacy.test.ts b/packages/ytl-linux-digabi2-examnet/test/examnet-legacy.test.ts index 90d7968..454427b 100644 --- a/packages/ytl-linux-digabi2-examnet/test/examnet-legacy.test.ts +++ b/packages/ytl-linux-digabi2-examnet/test/examnet-legacy.test.ts @@ -6,7 +6,7 @@ import { mkdtemp, writeFile, chmod, readFile, mkdir, unlink, truncate, access } import { tmpdir } from 'node:os' const ENV_TEST_MODE = { TEST_MODE: 'test' } -describe('examnet', async () => { +describe('examnet (legacy)', async () => { let callsLog let mockBinDir let mockEtcDir