fix: prevent NPM tunnel collisions and false app health restarts

This commit is contained in:
archipelago
2026-09-30 16:30:40 -04:00
parent 2992443d5d
commit c82c1eee98
9 changed files with 485 additions and 9 deletions
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Real nftables routing check. Run: sudo unshare --net python3 <this file>.
Never runs in the host network namespace; creates no persistent namespaces.
"""
import importlib.util
import os
from pathlib import Path
import socket
import subprocess
import threading
assert os.geteuid() == 0
assert os.readlink('/proc/self/ns/net') != os.readlink('/proc/1/ns/net'), 'requires isolated network namespace'
repo=Path(__file__).resolve().parents[2]
spec=importlib.util.spec_from_file_location('fixture',repo/'scripts/tests/test_repair_npm_tunnel.py')
f=importlib.util.module_from_spec(spec);spec.loader.exec_module(f)
def run(*args,input=None):
return subprocess.run(args,input=input,text=True,capture_output=True,check=True,timeout=10).stdout
run('ip','link','set','lo','up')
peer=subprocess.Popen(['unshare','--net','sleep','60'])
try:
import time
for _ in range(100):
if os.readlink(f'/proc/{peer.pid}/ns/net')!=os.readlink('/proc/self/ns/net'): break
time.sleep(.02)
else: raise AssertionError('peer namespace did not start')
run('ip','link','add','wg-web','type','veth','peer','name','wgpeer')
run('ip','link','set','wgpeer','netns',str(peer.pid))
run('ip','addr','add','10.77.0.2/30','dev','wg-web')
run('ip','link','set','wg-web','up')
prefix=('nsenter','-t',str(peer.pid),'-n')
run(*prefix,'ip','addr','add','10.77.0.1/30','dev','wgpeer')
run(*prefix,'ip','link','set','wgpeer','up')
run(*prefix,'ip','link','set','lo','up')
listeners=[]
for address,reply in [(('10.77.0.2',18081),b'NPM'),(('0.0.0.0',18080),b'LND')]:
listener=socket.socket();listener.bind(address);listener.listen();listeners.append(listener)
def serve(sock=listener,data=reply):
connection,_=sock.accept()
with connection: connection.sendall(data)
threading.Thread(target=serve,daemon=True).start()
run('nft','-f','-',input=f.RULES)
_,rules,_=f.m.plan(f.DROP,f.RULES)
transaction='delete table inet web_tunnel\n'+rules
run('nft','--check','-f','-',input=transaction)
run('nft','-f','-',input=transaction)
result=run(*prefix,'python3','-c',"import socket; s=socket.create_connection(('10.77.0.2',18080),3); print(s.recv(10).decode())")
assert result.strip()=='NPM',result
with socket.create_connection(('127.0.0.1',18080),3) as connection:
assert connection.recv(10)==b'LND'
print('PASS: original peer HTTP port reaches NPM; local LND REST port remains separate')
finally:
peer.terminate();peer.wait(timeout=5)
+1
View File
@@ -73,6 +73,7 @@ stage "git-diff-check" git diff --check
stage "cargo-fmt" timeout 240 cargo fmt --manifest-path core/Cargo.toml --all --check
stage "app-build-contexts" python3 tests/regression/app-build-contexts.py
stage "manifest-shell" python3 scripts/check-manifest-shell.py
stage "npm-tunnel-migration" python3 -m unittest discover -s scripts/tests -p test_repair_npm_tunnel.py
stage "doctor-ports" bash tests/regression/container-doctor-ports.sh
stage "bitcoin-pruning" python3 tests/regression/bitcoin-prune-entrypoint.py
stage "lnd-ui-readiness" node --test tests/regression/lnd-ui-readiness.cjs