mirror of
https://github.com/tailscale/tailscale.git
synced 2026-06-03 21:01:54 +08:00
Some checks failed
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
natlab-basic / EasyEasy (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / fuzz (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-bench, benchmarks) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-1-2, 1/2) (push) Has been cancelled
CI / Windows (${{ matrix.name || matrix.shard}}) (win-shard-2-2, 2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=darwin, arm64, darwin, macOS) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=linux, amd64, linux, Linux) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--with-tags-all=windows, amd64, windows, Windows) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=1/4, amd64, linux, Portable (1/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=2/4, amd64, linux, Portable (2/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=3/4, amd64, linux, Portable (3/4)) (push) Has been cancelled
CI / staticcheck (${{ matrix.name }}) (--without-tags-any=windows,darwin,linux --shard=4/4, amd64, linux, Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Single-pod ingress/egress proxies already called ClampMSSToPMTU when setting up forwarding rules, but the proxy group (HA) code paths in egressservices.go and ingressservices.go did not. This caused TCP connections through proxy group pods to suffer from MSS/MTU mismatch issues in environments where path MTU discovery is not working. Add ClampMSSToPMTU calls in the egress sync loop (alongside the existing EnsureSNATForDst call) and in addDNATRuleForSvc (alongside the existing EnsureDNATRuleForSvc call), mirroring what the single-pod forwarding rules already do. Also add MSS clamping assertions to TestSyncIngressConfigs and track ClampMSSToPMTU calls in FakeNetfilterRunner. Fixes issue #19812 https://github.com/tailscale/tailscale/issues/19812. Tracking internal ticket TSS-86326. Signed-off-by: Jay Tung <ltung@crusoeenergy.com> Co-authored-by: Jay Tung <ltung@crusoeenergy.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
260 lines
8.1 KiB
Go
260 lines
8.1 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"net/netip"
|
|
"slices"
|
|
"testing"
|
|
|
|
"tailscale.com/kube/ingressservices"
|
|
"tailscale.com/util/linuxfw"
|
|
)
|
|
|
|
func TestSyncIngressConfigs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
currentConfigs *ingressservices.Configs
|
|
currentStatus *ingressservices.Status
|
|
wantServices map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}
|
|
wantClampedAddrs []netip.Addr // cluster IPs that should have MSS clamping applied
|
|
}{
|
|
{
|
|
name: "add_new_rules_when_no_existing_config",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.1", "", ""),
|
|
},
|
|
currentStatus: nil,
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:foo": makeWantService("100.64.0.1", "10.0.0.1"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
|
|
},
|
|
{
|
|
name: "add_multiple_services",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.1", "", ""),
|
|
"svc:bar": makeServiceConfig("100.64.0.2", "10.0.0.2", "", ""),
|
|
"svc:baz": makeServiceConfig("100.64.0.3", "10.0.0.3", "", ""),
|
|
},
|
|
currentStatus: nil,
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:foo": makeWantService("100.64.0.1", "10.0.0.1"),
|
|
"svc:bar": makeWantService("100.64.0.2", "10.0.0.2"),
|
|
"svc:baz": makeWantService("100.64.0.3", "10.0.0.3"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{
|
|
netip.MustParseAddr("10.0.0.1"),
|
|
netip.MustParseAddr("10.0.0.2"),
|
|
netip.MustParseAddr("10.0.0.3"),
|
|
},
|
|
},
|
|
{
|
|
name: "add_both_ipv4_and_ipv6_rules",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.1", "2001:db8::1", "2001:db8::2"),
|
|
},
|
|
currentStatus: nil,
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:foo": makeWantService("2001:db8::1", "2001:db8::2"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{
|
|
netip.MustParseAddr("10.0.0.1"),
|
|
netip.MustParseAddr("2001:db8::2"),
|
|
},
|
|
},
|
|
{
|
|
name: "add_ipv6_only_rules",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:ipv6": makeServiceConfig("", "", "2001:db8::10", "2001:db8::20"),
|
|
},
|
|
currentStatus: nil,
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:ipv6": makeWantService("2001:db8::10", "2001:db8::20"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{netip.MustParseAddr("2001:db8::20")},
|
|
},
|
|
{
|
|
name: "delete_all_rules_when_config_removed",
|
|
currentConfigs: nil,
|
|
currentStatus: &ingressservices.Status{
|
|
Configs: ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.1", "", ""),
|
|
"svc:bar": makeServiceConfig("100.64.0.2", "10.0.0.2", "", ""),
|
|
},
|
|
PodIPv4: "10.0.0.2", // Current pod IPv4
|
|
PodIPv6: "2001:db8::2", // Current pod IPv6
|
|
},
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{},
|
|
wantClampedAddrs: nil, // no rules added, no clamping
|
|
},
|
|
{
|
|
name: "add_remove_modify",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.2", "", ""), // Changed cluster IP
|
|
"svc:new": makeServiceConfig("100.64.0.4", "10.0.0.4", "", ""),
|
|
},
|
|
currentStatus: &ingressservices.Status{
|
|
Configs: ingressservices.Configs{
|
|
"svc:foo": makeServiceConfig("100.64.0.1", "10.0.0.1", "", ""),
|
|
"svc:bar": makeServiceConfig("100.64.0.2", "10.0.0.2", "", ""),
|
|
"svc:baz": makeServiceConfig("100.64.0.3", "10.0.0.3", "", ""),
|
|
},
|
|
PodIPv4: "10.0.0.2", // Current pod IPv4
|
|
PodIPv6: "2001:db8::2", // Current pod IPv6
|
|
},
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:foo": makeWantService("100.64.0.1", "10.0.0.2"),
|
|
"svc:new": makeWantService("100.64.0.4", "10.0.0.4"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{
|
|
netip.MustParseAddr("10.0.0.2"),
|
|
netip.MustParseAddr("10.0.0.4"),
|
|
},
|
|
},
|
|
{
|
|
name: "update_with_outdated_status",
|
|
currentConfigs: &ingressservices.Configs{
|
|
"svc:web": makeServiceConfig("100.64.0.10", "10.0.0.10", "", ""),
|
|
"svc:web-ipv6": {
|
|
IPv6Mapping: &ingressservices.Mapping{
|
|
TailscaleServiceIP: netip.MustParseAddr("2001:db8::10"),
|
|
ClusterIP: netip.MustParseAddr("2001:db8::20"),
|
|
},
|
|
},
|
|
"svc:api": makeServiceConfig("100.64.0.20", "10.0.0.20", "", ""),
|
|
},
|
|
currentStatus: &ingressservices.Status{
|
|
Configs: ingressservices.Configs{
|
|
"svc:web": makeServiceConfig("100.64.0.10", "10.0.0.10", "", ""),
|
|
"svc:web-ipv6": {
|
|
IPv6Mapping: &ingressservices.Mapping{
|
|
TailscaleServiceIP: netip.MustParseAddr("2001:db8::10"),
|
|
ClusterIP: netip.MustParseAddr("2001:db8::20"),
|
|
},
|
|
},
|
|
"svc:old": makeServiceConfig("100.64.0.30", "10.0.0.30", "", ""),
|
|
},
|
|
PodIPv4: "10.0.0.1", // Outdated pod IP
|
|
PodIPv6: "2001:db8::1", // Outdated pod IP
|
|
},
|
|
wantServices: map[string]struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
"svc:web": makeWantService("100.64.0.10", "10.0.0.10"),
|
|
"svc:web-ipv6": makeWantService("2001:db8::10", "2001:db8::20"),
|
|
"svc:api": makeWantService("100.64.0.20", "10.0.0.20"),
|
|
},
|
|
wantClampedAddrs: []netip.Addr{
|
|
netip.MustParseAddr("10.0.0.10"),
|
|
netip.MustParseAddr("10.0.0.20"),
|
|
netip.MustParseAddr("2001:db8::20"),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
nfr := linuxfw.NewFakeNetfilterRunner()
|
|
|
|
ep := &ingressProxy{
|
|
nfr: nfr,
|
|
podIPv4: "10.0.0.2", // Current pod IPv4
|
|
podIPv6: "2001:db8::2", // Current pod IPv6
|
|
}
|
|
|
|
err := ep.syncIngressConfigs(tt.currentConfigs, tt.currentStatus)
|
|
if err != nil {
|
|
t.Fatalf("syncIngressConfigs failed: %v", err)
|
|
}
|
|
|
|
gotServices := nfr.GetServiceState()
|
|
if len(gotServices) != len(tt.wantServices) {
|
|
t.Errorf("got %d services, want %d", len(gotServices), len(tt.wantServices))
|
|
}
|
|
for svc, want := range tt.wantServices {
|
|
got, ok := gotServices[svc]
|
|
if !ok {
|
|
t.Errorf("service %s not found", svc)
|
|
continue
|
|
}
|
|
if got.TailscaleServiceIP != want.TailscaleServiceIP {
|
|
t.Errorf("service %s: got TailscaleServiceIP %v, want %v", svc, got.TailscaleServiceIP, want.TailscaleServiceIP)
|
|
}
|
|
if got.ClusterIP != want.ClusterIP {
|
|
t.Errorf("service %s: got ClusterIP %v, want %v", svc, got.ClusterIP, want.ClusterIP)
|
|
}
|
|
}
|
|
|
|
gotClamped := nfr.GetClampedAddrs()
|
|
slices.SortFunc(gotClamped, func(a, b netip.Addr) int { return a.Compare(b) })
|
|
slices.SortFunc(tt.wantClampedAddrs, func(a, b netip.Addr) int { return a.Compare(b) })
|
|
if len(gotClamped) != len(tt.wantClampedAddrs) {
|
|
t.Errorf("ClampMSSToPMTU: got %v, want %v", gotClamped, tt.wantClampedAddrs)
|
|
} else {
|
|
for i := range gotClamped {
|
|
if gotClamped[i] != tt.wantClampedAddrs[i] {
|
|
t.Errorf("ClampMSSToPMTU: got %v, want %v", gotClamped, tt.wantClampedAddrs)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func makeServiceConfig(tsIP, clusterIP string, tsIP6, clusterIP6 string) ingressservices.Config {
|
|
cfg := ingressservices.Config{}
|
|
if tsIP != "" && clusterIP != "" {
|
|
cfg.IPv4Mapping = &ingressservices.Mapping{
|
|
TailscaleServiceIP: netip.MustParseAddr(tsIP),
|
|
ClusterIP: netip.MustParseAddr(clusterIP),
|
|
}
|
|
}
|
|
if tsIP6 != "" && clusterIP6 != "" {
|
|
cfg.IPv6Mapping = &ingressservices.Mapping{
|
|
TailscaleServiceIP: netip.MustParseAddr(tsIP6),
|
|
ClusterIP: netip.MustParseAddr(clusterIP6),
|
|
}
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func makeWantService(tsIP, clusterIP string) struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
} {
|
|
return struct {
|
|
TailscaleServiceIP netip.Addr
|
|
ClusterIP netip.Addr
|
|
}{
|
|
TailscaleServiceIP: netip.MustParseAddr(tsIP),
|
|
ClusterIP: netip.MustParseAddr(clusterIP),
|
|
}
|
|
}
|