Skip to content

Commit bbceb53

Browse files
mtparetclaude
andcommitted
cmd/containerboot: add route to table 52 for egress Service destinations
Tailscale Services are not WireGuard peers, so tailscaled doesn't add routes for them to routing table 52. This caused egress traffic to Service IPs to be routed via the default gateway (eth0) instead of tailscale0, resulting in connection timeouts. This fix adds a route for the egress destination IP to table 52 via tailscale0 in installEgressForwardingRule(). The route addition is non-fatal (logs a warning) since for regular Tailscale peers, tailscaled already manages routes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2bfef27 commit bbceb53

1 file changed

Lines changed: 37 additions & 0 deletions

File tree

cmd/containerboot/forwarding.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@ import (
1515
"path/filepath"
1616
"strings"
1717

18+
"github.com/tailscale/netlink"
19+
"go4.org/netipx"
1820
"tailscale.com/util/linuxfw"
1921
)
2022

23+
const (
24+
// tailscaleRouteTable is the routing table number used by Tailscale.
25+
tailscaleRouteTable = 52
26+
)
27+
2128
// ensureIPForwarding enables IPv4/IPv6 forwarding for the container.
2229
func ensureIPForwarding(root, clusterProxyTargetIP, tailnetTargetIP, tailnetTargetFQDN string, routes *string) error {
2330
var (
@@ -123,6 +130,36 @@ func installEgressForwardingRule(_ context.Context, dstStr string, tsIPs []netip
123130
if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
124131
return fmt.Errorf("installing egress proxy rules: %w", err)
125132
}
133+
// Add route to table 52 for the destination via tailscale0.
134+
// This is needed for Tailscale Services which are not WireGuard peers
135+
// and thus don't have routes added by tailscaled.
136+
// We log a warning but don't fail if this fails, since for regular
137+
// Tailscale peers, tailscaled already manages routes.
138+
if err := addRouteForEgressDestination(dst); err != nil {
139+
log.Printf("[warning] failed to add route for egress destination %v: %v", dst, err)
140+
}
141+
return nil
142+
}
143+
144+
// addRouteForEgressDestination adds a route for the given destination IP
145+
// to routing table 52 via the tailscale0 interface. This ensures that
146+
// traffic to Tailscale Services (which are not WireGuard peers) is routed
147+
// through the Tailscale interface rather than the default gateway.
148+
func addRouteForEgressDestination(dst netip.Addr) error {
149+
link, err := netlink.LinkByName("tailscale0")
150+
if err != nil {
151+
return fmt.Errorf("getting tailscale0 link: %w", err)
152+
}
153+
dstPrefix := netip.PrefixFrom(dst, dst.BitLen())
154+
route := &netlink.Route{
155+
LinkIndex: link.Attrs().Index,
156+
Dst: netipx.PrefixIPNet(dstPrefix),
157+
Table: tailscaleRouteTable,
158+
}
159+
if err := netlink.RouteReplace(route); err != nil {
160+
return fmt.Errorf("adding route %v to table %d: %w", dstPrefix, tailscaleRouteTable, err)
161+
}
162+
log.Printf("Added route for %v via tailscale0 to table %d", dst, tailscaleRouteTable)
126163
return nil
127164
}
128165

0 commit comments

Comments
 (0)