detached 706193c

fix(harness): bind the resilience relay to loopback by default

The relay forwards SSH and UDP without authentication, and its defaults listened on every interface. It now defaults to 127.0.0.1 with a -listen flag for UDP; the disposable fixture passes -tcp :2222 -listen 0.0.0.0 explicitly.
commit
706193c225cdacc5e5f318e81ab617cc53791255
author
brice · · 2026-10-08 06:01 UTC
parent
fd82dd0
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +21 -4
M scripts/remote-resilience-harness/relay/main.go +20 -3
@@ -35,6 +35,7 @@ type relay struct {
3535 ctx context.Context
3636 cancel context.CancelFunc
3737 remote string
38+ listen net.IP
3839 degraded bool
3940 blackout atomic.Bool
4041 bytes atomic.Uint64
@@ -48,21 +49,37 @@ type relay struct {
4849 func main() {
4950 remote := flag.String("remote", "remote", "fixture upstream host")
5051 control := flag.String("control", "", "required private Unix control path")
51- tcp := flag.String("tcp", ":2222", "SSH listener")
52+ // The relay forwards without authentication, so it listens on loopback
53+ // unless the disposable fixture asks for every interface.
54+ tcp := flag.String("tcp", "127.0.0.1:2222", "SSH listener")
55+ listen := flag.String("listen", "127.0.0.1", "UDP proxy listen IP")
5256 degraded := flag.Bool("degraded", false, "enable seeded degraded profile")
5357 flag.Parse()
5458 if *control == "" {
5559 fmt.Fprintln(os.Stderr, "-control is required")
5660 os.Exit(2)
5761 }
62+ listenIP := net.ParseIP(*listen)
63+ if listenIP == nil {
64+ fmt.Fprintln(os.Stderr, "-listen must be an IP address")
65+ os.Exit(2)
66+ }
5867 ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
59- r := &relay{ctx: ctx, cancel: cancel, remote: *remote, degraded: *degraded, connections: make(map[net.Conn]struct{})}
68+ r := &relay{ctx: ctx, cancel: cancel, remote: *remote, listen: listenIP, degraded: *degraded, connections: make(map[net.Conn]struct{})}
6069 if err := r.serve(*tcp, *control); err != nil {
6170 fmt.Fprintln(os.Stderr, err)
6271 os.Exit(1)
6372 }
6473 }
6574
75+// listenIP is the UDP proxy bind address, loopback when unset.
76+func (r *relay) listenIP() net.IP {
77+ if r.listen == nil {
78+ return net.IPv4(127, 0, 0, 1)
79+ }
80+ return r.listen
81+}
82+
6683 func (r *relay) serve(addr, path string) error {
6784 listener, err := net.Listen("tcp", addr)
6885 if err != nil {
@@ -172,7 +189,7 @@ func (r *relay) apply(c command) (any, error) {
172189 if len(r.proxies) >= 32 {
173190 return nil, errors.New("UDP listener limit")
174191 }
175- p, err := quicnettest.New(quicnettest.Config{ServerAddr: upstream, ListenAddr: &net.UDPAddr{IP: net.IPv4zero}, Seed: 317, ToServer: link, ToClient: link})
192+ p, err := quicnettest.New(quicnettest.Config{ServerAddr: upstream, ListenAddr: &net.UDPAddr{IP: r.listenIP()}, Seed: 317, ToServer: link, ToClient: link})
176193 if err != nil {
177194 return nil, err
178195 }
@@ -35,6 +35,7 @@ type relay struct {
35 ctx context.Context35 ctx context.Context
36 cancel context.CancelFunc36 cancel context.CancelFunc
37 remote string37 remote string
38+ listen net.IP
38 degraded bool39 degraded bool
39 blackout atomic.Bool40 blackout atomic.Bool
40 bytes atomic.Uint6441 bytes atomic.Uint64
@@ -48,21 +49,37 @@ type relay struct {
48 func main() {49 func main() {
49 remote := flag.String("remote", "remote", "fixture upstream host")50 remote := flag.String("remote", "remote", "fixture upstream host")
50 control := flag.String("control", "", "required private Unix control path")51 control := flag.String("control", "", "required private Unix control path")
51- tcp := flag.String("tcp", ":2222", "SSH listener")52+ // The relay forwards without authentication, so it listens on loopback
53+ // unless the disposable fixture asks for every interface.
54+ tcp := flag.String("tcp", "127.0.0.1:2222", "SSH listener")
55+ listen := flag.String("listen", "127.0.0.1", "UDP proxy listen IP")
52 degraded := flag.Bool("degraded", false, "enable seeded degraded profile")56 degraded := flag.Bool("degraded", false, "enable seeded degraded profile")
53 flag.Parse()57 flag.Parse()
54 if *control == "" {58 if *control == "" {
55 fmt.Fprintln(os.Stderr, "-control is required")59 fmt.Fprintln(os.Stderr, "-control is required")
56 os.Exit(2)60 os.Exit(2)
57 }61 }
62+ listenIP := net.ParseIP(*listen)
63+ if listenIP == nil {
64+ fmt.Fprintln(os.Stderr, "-listen must be an IP address")
65+ os.Exit(2)
66+ }
58 ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)67 ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
59- r := &relay{ctx: ctx, cancel: cancel, remote: *remote, degraded: *degraded, connections: make(map[net.Conn]struct{})}68+ r := &relay{ctx: ctx, cancel: cancel, remote: *remote, listen: listenIP, degraded: *degraded, connections: make(map[net.Conn]struct{})}
60 if err := r.serve(*tcp, *control); err != nil {69 if err := r.serve(*tcp, *control); err != nil {
61 fmt.Fprintln(os.Stderr, err)70 fmt.Fprintln(os.Stderr, err)
62 os.Exit(1)71 os.Exit(1)
63 }72 }
64 }73 }
65 74
75+// listenIP is the UDP proxy bind address, loopback when unset.
76+func (r *relay) listenIP() net.IP {
77+ if r.listen == nil {
78+ return net.IPv4(127, 0, 0, 1)
79+ }
80+ return r.listen
81+}
82+
66 func (r *relay) serve(addr, path string) error {83 func (r *relay) serve(addr, path string) error {
67 listener, err := net.Listen("tcp", addr)84 listener, err := net.Listen("tcp", addr)
68 if err != nil {85 if err != nil {
@@ -172,7 +189,7 @@ func (r *relay) apply(c command) (any, error) {
172 if len(r.proxies) >= 32 {189 if len(r.proxies) >= 32 {
173 return nil, errors.New("UDP listener limit")190 return nil, errors.New("UDP listener limit")
174 }191 }
175- p, err := quicnettest.New(quicnettest.Config{ServerAddr: upstream, ListenAddr: &net.UDPAddr{IP: net.IPv4zero}, Seed: 317, ToServer: link, ToClient: link})192+ p, err := quicnettest.New(quicnettest.Config{ServerAddr: upstream, ListenAddr: &net.UDPAddr{IP: r.listenIP()}, Seed: 317, ToServer: link, ToClient: link})
176 if err != nil {193 if err != nil {
177 return nil, err194 return nil, err
178 }195 }
M scripts/remote-resilience-harness/visual.py +1 -1
@@ -76,7 +76,7 @@ def main():
7676 if transport not in ("quic", "ssh"):
7777 raise ValueError("VEV_RESILIENCE_TRANSPORT must be quic or ssh")
7878 relay_args = ["-degraded"] if os.environ.get("VEV_RESILIENCE_DEGRADED") == "1" else []
79- relay = subprocess.Popen(["docker", "exec", client, "/tmp/relay", "-remote", "upstream", "-control", "/home/demo/relay/control.sock", *relay_args])
79+ relay = subprocess.Popen(["docker", "exec", client, "/tmp/relay", "-remote", "upstream", "-control", "/home/demo/relay/control.sock", "-tcp", ":2222", "-listen", "0.0.0.0", *relay_args])
8080 processes.append(relay)
8181 control_id = 0
8282 def control(op, **fields):
@@ -76,7 +76,7 @@ def main():
76 if transport not in ("quic", "ssh"):76 if transport not in ("quic", "ssh"):
77 raise ValueError("VEV_RESILIENCE_TRANSPORT must be quic or ssh")77 raise ValueError("VEV_RESILIENCE_TRANSPORT must be quic or ssh")
78 relay_args = ["-degraded"] if os.environ.get("VEV_RESILIENCE_DEGRADED") == "1" else []78 relay_args = ["-degraded"] if os.environ.get("VEV_RESILIENCE_DEGRADED") == "1" else []
79- relay = subprocess.Popen(["docker", "exec", client, "/tmp/relay", "-remote", "upstream", "-control", "/home/demo/relay/control.sock", *relay_args])79+ relay = subprocess.Popen(["docker", "exec", client, "/tmp/relay", "-remote", "upstream", "-control", "/home/demo/relay/control.sock", "-tcp", ":2222", "-listen", "0.0.0.0", *relay_args])
80 processes.append(relay)80 processes.append(relay)
81 control_id = 081 control_id = 0
82 def control(op, **fields):82 def control(op, **fields):

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close