From 8e2a1e762de1e6b22e560bc30047ec2de6c6d42b Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 26 Sep 2026 22:39:54 +0200 Subject: [PATCH] A found tunnel carries its MTU to the mesh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host parses MTU from the found [Interface] and reports it, so the mesh's interface can come up with the same MTU when it takes the tunnel over. A path tuned to 1380 regresses to the 1420 default otherwise — invisible to ping, fatal to TLS handshakes and transfers over that path (novox/hq: the mesh had no MTU concept). Zero when the config named none, and the mesh writes no MTU line then. --- cmd/mesh-host/main.go | 2 +- internal/link/enrol.go | 1 + internal/tunnel/tunnel.go | 11 +++++++++++ internal/tunnel/tunnel_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/cmd/mesh-host/main.go b/cmd/mesh-host/main.go index 3bacc00..4e8aefe 100644 --- a/cmd/mesh-host/main.go +++ b/cmd/mesh-host/main.go @@ -910,7 +910,7 @@ func rekeyOnto(mine identity.Identity, found tunnel.Found) (identity.Identity, l // carried is a found tunnel as it is presented to the mesh: everything but its private key. func carried(t tunnel.Found) *link.Tunnel { out := &link.Tunnel{Interface: t.Interface, Unit: t.Unit, Config: t.Config, Port: t.Port, - Address: t.Address, Range: t.Range, PublicKey: t.PublicKey} + Address: t.Address, Range: t.Range, MTU: t.MTU, PublicKey: t.PublicKey} for _, p := range t.Peers { out.Peers = append(out.Peers, link.TunnelPeer{PublicKey: p.PublicKey, Address: p.Address}) } diff --git a/internal/link/enrol.go b/internal/link/enrol.go index 8f755c8..8f0aa6f 100644 --- a/internal/link/enrol.go +++ b/internal/link/enrol.go @@ -68,6 +68,7 @@ type Tunnel struct { Port int `json:"port"` Address string `json:"address"` Range string `json:"range"` + MTU int `json:"mtu,omitempty"` PublicKey string `json:"public_key"` Peers []TunnelPeer `json:"peers,omitempty"` } diff --git a/internal/tunnel/tunnel.go b/internal/tunnel/tunnel.go index 53c6bce..9e1793d 100644 --- a/internal/tunnel/tunnel.go +++ b/internal/tunnel/tunnel.go @@ -44,6 +44,11 @@ type Found struct { Port int `json:"port"` Address string `json:"address"` Range string `json:"range"` + // MTU is the interface's, when the found config set one. Kept because a tuned tunnel (a path + // that needs 1380, say) breaks silently if the mesh's interface comes up at the 1420 default: + // no ping fails, but TLS handshakes stall and transfers hang (novox/hq: a taken tunnel carries + // its MTU). Zero when the config named none, and the mesh sets no MTU line then. + MTU int `json:"mtu,omitempty"` // PublicKey is what every peer knows this tunnel by — derived here from the private key, so // it is the key the file actually holds and not a comment beside it. PublicKey string `json:"public_key"` @@ -209,6 +214,12 @@ func Parse(raw []byte) (Found, error) { return Found{}, fmt.Errorf("ListenPort %q is not a port", value) } f.Port = port + case "mtu": + mtu, err := strconv.Atoi(value) + if err != nil || mtu < 576 || mtu > 65535 { + return Found{}, fmt.Errorf("MTU %q is not a plausible MTU", value) + } + f.MTU = mtu case "address": // The first address is the interface's; a second family would be a second // tunnel's worth of addressing, which this does not carry. diff --git a/internal/tunnel/tunnel_test.go b/internal/tunnel/tunnel_test.go index 7272a6b..19d73b3 100644 --- a/internal/tunnel/tunnel_test.go +++ b/internal/tunnel/tunnel_test.go @@ -170,3 +170,27 @@ func TestNoneUpIsAnOrdinaryAnswerAndSeveralIsAQuestion(t *testing.T) { t.Errorf("naming a tunnel that is not up was not refused: %v", err) } } + +func TestAFoundTunnelReadsItsMTU(t *testing.T) { + // A tuned path sets MTU in [Interface]; the mesh must carry it or the tunnel regresses to the + // default silently (novox/hq: a taken tunnel carries its MTU). + private, public := aKey(t) + withMTU := "# tuned\n[Interface]\nPrivateKey = " + private + + "\nListenPort = 51820\nAddress = 10.10.0.3/24\nMTU = 1380\n" + + "[Peer]\nPublicKey = " + public + "\nAllowedIPs = 10.10.0.1/32\n" + f, err := Parse([]byte(withMTU)) + if err != nil { + t.Fatal(err) + } + if f.MTU != 1380 { + t.Fatalf("MTU 1380 was not read; got %d", f.MTU) + } + // And a config with none leaves MTU zero, so the mesh writes no MTU line. + f2, err := Parse([]byte(aConfig(private, public))) + if err != nil { + t.Fatal(err) + } + if f2.MTU != 0 { + t.Fatalf("a config with no MTU must leave it zero; got %d", f2.MTU) + } +}