diff --git a/cmd/kube-proxy/app/server.go b/cmd/kube-proxy/app/server.go index 761e6d5be02..0aff9d2ca31 100644 --- a/cmd/kube-proxy/app/server.go +++ b/cmd/kube-proxy/app/server.go @@ -199,6 +199,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&o.config.IPVS.StrictARP, "ipvs-strict-arp", o.config.IPVS.StrictARP, "Enable strict ARP by setting arp_ignore to 1 and arp_announce to 2") fs.BoolVar(&o.config.IPTables.MasqueradeAll, "masquerade-all", o.config.IPTables.MasqueradeAll, "If using the pure iptables proxy, SNAT all traffic sent via Service cluster IPs (this not commonly needed)") + fs.BoolVar(o.config.IPTables.LocalhostNodePorts, "iptables-localhost-nodeports", pointer.BoolDeref(o.config.IPTables.LocalhostNodePorts, true), "If false Kube-proxy will disable the legacy behavior of allowing NodePort services to be accessed via localhost, This only applies to iptables mode and ipv4.") fs.BoolVar(&o.config.EnableProfiling, "profiling", o.config.EnableProfiling, "If true enables profiling via web interface on /debug/pprof handler. This parameter is ignored if a config file is specified by --config.") fs.Float32Var(&o.config.ClientConnection.QPS, "kube-api-qps", o.config.ClientConnection.QPS, "QPS to use while talking with kubernetes apiserver") diff --git a/cmd/kube-proxy/app/server_others.go b/cmd/kube-proxy/app/server_others.go index 460dd2327eb..9836584bfa4 100644 --- a/cmd/kube-proxy/app/server_others.go +++ b/cmd/kube-proxy/app/server_others.go @@ -197,6 +197,7 @@ func newProxyServer( config.IPTables.SyncPeriod.Duration, config.IPTables.MinSyncPeriod.Duration, config.IPTables.MasqueradeAll, + *config.IPTables.LocalhostNodePorts, int(*config.IPTables.MasqueradeBit), localDetectors, hostname, @@ -221,6 +222,7 @@ func newProxyServer( config.IPTables.SyncPeriod.Duration, config.IPTables.MinSyncPeriod.Duration, config.IPTables.MasqueradeAll, + *config.IPTables.LocalhostNodePorts, int(*config.IPTables.MasqueradeBit), localDetector, hostname, diff --git a/cmd/kube-proxy/app/server_test.go b/cmd/kube-proxy/app/server_test.go index 8e4b6a85182..b3cb9422cf1 100644 --- a/cmd/kube-proxy/app/server_test.go +++ b/cmd/kube-proxy/app/server_test.go @@ -107,6 +107,7 @@ iptables: masqueradeBit: 17 minSyncPeriod: 10s syncPeriod: 60s + localhostNodePorts: true ipvs: minSyncPeriod: 10s syncPeriod: 60s @@ -246,10 +247,11 @@ nodePortAddresses: HealthzBindAddress: tc.healthzBindAddress, HostnameOverride: "foo", IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{ - MasqueradeAll: true, - MasqueradeBit: pointer.Int32(17), - MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, - SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, + MasqueradeAll: true, + MasqueradeBit: pointer.Int32(17), + LocalhostNodePorts: pointer.Bool(true), + MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, + SyncPeriod: metav1.Duration{Duration: 60 * time.Second}, }, IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{ MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second}, diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index fcba2fb375c..993eef17ce5 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -52001,6 +52001,13 @@ func schema_k8sio_kube_proxy_config_v1alpha1_KubeProxyIPTablesConfiguration(ref Format: "", }, }, + "localhostNodePorts": { + SchemaProps: spec.SchemaProps{ + Description: "LocalhostNodePorts tells kube-proxy to allow service NodePorts to be accessed via localhost (iptables mode only)", + Type: []string{"boolean"}, + Format: "", + }, + }, "syncPeriod": { SchemaProps: spec.SchemaProps{ Description: "syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', '2h22m'). Must be greater than 0.", @@ -52016,7 +52023,7 @@ func schema_k8sio_kube_proxy_config_v1alpha1_KubeProxyIPTablesConfiguration(ref }, }, }, - Required: []string{"masqueradeBit", "masqueradeAll", "syncPeriod", "minSyncPeriod"}, + Required: []string{"masqueradeBit", "masqueradeAll", "localhostNodePorts", "syncPeriod", "minSyncPeriod"}, }, }, Dependencies: []string{ diff --git a/pkg/kubemark/hollow_proxy.go b/pkg/kubemark/hollow_proxy.go index 6ca4d25d6b3..bbd4bd04d5d 100644 --- a/pkg/kubemark/hollow_proxy.go +++ b/pkg/kubemark/hollow_proxy.go @@ -94,6 +94,7 @@ func NewHollowProxyOrDie( proxierSyncPeriod, proxierMinSyncPeriod, false, + false, 0, proxyutiliptables.NewNoOpLocalDetector(), nodeName, diff --git a/pkg/proxy/apis/config/fuzzer/fuzzer.go b/pkg/proxy/apis/config/fuzzer/fuzzer.go index 720ca3f81b1..ac624c6d254 100644 --- a/pkg/proxy/apis/config/fuzzer/fuzzer.go +++ b/pkg/proxy/apis/config/fuzzer/fuzzer.go @@ -42,6 +42,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { obj.FeatureGates = map[string]bool{c.RandString(): true} obj.HealthzBindAddress = fmt.Sprintf("%d.%d.%d.%d:%d", c.Intn(256), c.Intn(256), c.Intn(256), c.Intn(256), c.Intn(65536)) obj.IPTables.MasqueradeBit = pointer.Int32(c.Int31()) + obj.IPTables.LocalhostNodePorts = pointer.Bool(c.RandBool()) obj.MetricsBindAddress = fmt.Sprintf("%d.%d.%d.%d:%d", c.Intn(256), c.Intn(256), c.Intn(256), c.Intn(256), c.Intn(65536)) obj.OOMScoreAdj = pointer.Int32(c.Int31()) obj.ClientConnection.ContentType = "bar" diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml index 569d47ee6c5..5e8c2de74a1 100644 --- a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/after/v1alpha1.yaml @@ -22,6 +22,7 @@ enableProfiling: false healthzBindAddress: 0.0.0.0:10256 hostnameOverride: "" iptables: + localhostNodePorts: true masqueradeAll: false masqueradeBit: 14 minSyncPeriod: 1s diff --git a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/roundtrip/default/v1alpha1.yaml b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/roundtrip/default/v1alpha1.yaml index 569d47ee6c5..5e8c2de74a1 100644 --- a/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/roundtrip/default/v1alpha1.yaml +++ b/pkg/proxy/apis/config/scheme/testdata/KubeProxyConfiguration/roundtrip/default/v1alpha1.yaml @@ -22,6 +22,7 @@ enableProfiling: false healthzBindAddress: 0.0.0.0:10256 hostnameOverride: "" iptables: + localhostNodePorts: true masqueradeAll: false masqueradeBit: 14 minSyncPeriod: 1s diff --git a/pkg/proxy/apis/config/types.go b/pkg/proxy/apis/config/types.go index c58fa61c87c..8674e54e1da 100644 --- a/pkg/proxy/apis/config/types.go +++ b/pkg/proxy/apis/config/types.go @@ -33,6 +33,9 @@ type KubeProxyIPTablesConfiguration struct { MasqueradeBit *int32 // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. MasqueradeAll bool + // LocalhostNodePorts tells kube-proxy to allow service NodePorts to be accessed via + // localhost (iptables mode only) + LocalhostNodePorts *bool // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', // '2h22m'). Must be greater than 0. SyncPeriod metav1.Duration diff --git a/pkg/proxy/apis/config/v1alpha1/defaults.go b/pkg/proxy/apis/config/v1alpha1/defaults.go index 667486624c9..127913249ad 100644 --- a/pkg/proxy/apis/config/v1alpha1/defaults.go +++ b/pkg/proxy/apis/config/v1alpha1/defaults.go @@ -64,6 +64,9 @@ func SetDefaults_KubeProxyConfiguration(obj *kubeproxyconfigv1alpha1.KubeProxyCo if obj.IPTables.MinSyncPeriod.Duration == 0 { obj.IPTables.MinSyncPeriod = metav1.Duration{Duration: 1 * time.Second} } + if obj.IPTables.LocalhostNodePorts == nil { + obj.IPTables.LocalhostNodePorts = pointer.Bool(true) + } if obj.IPVS.SyncPeriod.Duration == 0 { obj.IPVS.SyncPeriod = metav1.Duration{Duration: 30 * time.Second} } diff --git a/pkg/proxy/apis/config/v1alpha1/defaults_test.go b/pkg/proxy/apis/config/v1alpha1/defaults_test.go index 8485063513a..dfcd3acb9c9 100644 --- a/pkg/proxy/apis/config/v1alpha1/defaults_test.go +++ b/pkg/proxy/apis/config/v1alpha1/defaults_test.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "k8s.io/utils/pointer" "testing" "time" @@ -27,7 +28,6 @@ import ( ) func TestDefaultsKubeProxyConfiguration(t *testing.T) { - masqBit := int32(14) oomScore := int32(-999) ctMaxPerCore := int32(32768) ctMin := int32(131072) @@ -50,10 +50,11 @@ func TestDefaultsKubeProxyConfiguration(t *testing.T) { Burst: 10, }, IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ - MasqueradeBit: &masqBit, - MasqueradeAll: false, - SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + MasqueradeBit: pointer.Int32(14), + MasqueradeAll: false, + LocalhostNodePorts: pointer.Bool(true), + SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, }, IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, @@ -85,10 +86,11 @@ func TestDefaultsKubeProxyConfiguration(t *testing.T) { Burst: 10, }, IPTables: kubeproxyconfigv1alpha1.KubeProxyIPTablesConfiguration{ - MasqueradeBit: &masqBit, - MasqueradeAll: false, - SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, - MinSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, + MasqueradeBit: pointer.Int32(14), + MasqueradeAll: false, + LocalhostNodePorts: pointer.Bool(true), + SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, + MinSyncPeriod: metav1.Duration{Duration: 1 * time.Second}, }, IPVS: kubeproxyconfigv1alpha1.KubeProxyIPVSConfiguration{ SyncPeriod: metav1.Duration{Duration: 30 * time.Second}, diff --git a/pkg/proxy/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/proxy/apis/config/v1alpha1/zz_generated.conversion.go index 8c46561fce9..ab8c42ba754 100644 --- a/pkg/proxy/apis/config/v1alpha1/zz_generated.conversion.go +++ b/pkg/proxy/apis/config/v1alpha1/zz_generated.conversion.go @@ -237,6 +237,7 @@ func Convert_config_KubeProxyConntrackConfiguration_To_v1alpha1_KubeProxyConntra func autoConvert_v1alpha1_KubeProxyIPTablesConfiguration_To_config_KubeProxyIPTablesConfiguration(in *v1alpha1.KubeProxyIPTablesConfiguration, out *config.KubeProxyIPTablesConfiguration, s conversion.Scope) error { out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) out.MasqueradeAll = in.MasqueradeAll + out.LocalhostNodePorts = (*bool)(unsafe.Pointer(in.LocalhostNodePorts)) out.SyncPeriod = in.SyncPeriod out.MinSyncPeriod = in.MinSyncPeriod return nil @@ -250,6 +251,7 @@ func Convert_v1alpha1_KubeProxyIPTablesConfiguration_To_config_KubeProxyIPTables func autoConvert_config_KubeProxyIPTablesConfiguration_To_v1alpha1_KubeProxyIPTablesConfiguration(in *config.KubeProxyIPTablesConfiguration, out *v1alpha1.KubeProxyIPTablesConfiguration, s conversion.Scope) error { out.MasqueradeBit = (*int32)(unsafe.Pointer(in.MasqueradeBit)) out.MasqueradeAll = in.MasqueradeAll + out.LocalhostNodePorts = (*bool)(unsafe.Pointer(in.LocalhostNodePorts)) out.SyncPeriod = in.SyncPeriod out.MinSyncPeriod = in.MinSyncPeriod return nil diff --git a/pkg/proxy/apis/config/zz_generated.deepcopy.go b/pkg/proxy/apis/config/zz_generated.deepcopy.go index 8ceac506314..fd83125fc2a 100644 --- a/pkg/proxy/apis/config/zz_generated.deepcopy.go +++ b/pkg/proxy/apis/config/zz_generated.deepcopy.go @@ -157,6 +157,11 @@ func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesCon *out = new(int32) **out = **in } + if in.LocalhostNodePorts != nil { + in, out := &in.LocalhostNodePorts, &out.LocalhostNodePorts + *out = new(bool) + **out = **in + } out.SyncPeriod = in.SyncPeriod out.MinSyncPeriod = in.MinSyncPeriod return diff --git a/pkg/proxy/iptables/proxier.go b/pkg/proxy/iptables/proxier.go index 998672681cb..82f9f8ee5f8 100644 --- a/pkg/proxy/iptables/proxier.go +++ b/pkg/proxy/iptables/proxier.go @@ -203,7 +203,10 @@ type Proxier struct { // optimize for performance over debuggability. largeClusterMode bool - // Values are as a parameter to select the interfaces where nodeport works. + // localhostNodePorts indicates whether to generate iptables rules that + // disable NodePort services to be accessed via localhost. + localhostNodePorts bool + // Values are as a parameter to select the interfaces where nodePort works. nodePortAddresses []string // networkInterfacer defines an interface for several net library functions. // Inject for test purpose. @@ -224,6 +227,7 @@ func NewProxier(ipt utiliptables.Interface, syncPeriod time.Duration, minSyncPeriod time.Duration, masqueradeAll bool, + localhostNodePorts bool, masqueradeBit int, localDetector proxyutiliptables.LocalTrafficDetector, hostname string, @@ -232,9 +236,10 @@ func NewProxier(ipt utiliptables.Interface, healthzServer healthcheck.ProxierHealthUpdater, nodePortAddresses []string, ) (*Proxier, error) { - if utilproxy.ContainsIPv4Loopback(nodePortAddresses) { + if localhostNodePorts && utilproxy.ContainsIPv4Loopback(nodePortAddresses) { // Set the route_localnet sysctl we need for exposing NodePorts on loopback addresses - klog.InfoS("Setting route_localnet=1, use nodePortAddresses to filter loopback addresses for NodePorts to skip it https://issues.k8s.io/90259") + // Refer to https://issues.k8s.io/90259 + klog.InfoS("Setting route_localnet=1 to allows nodePort services can be accessed via localhost. You can set flag '--iptables-localhost-nodeports' to false or use nodePortAddresses (--nodeport-addresses) to filter loopback addresses to change this") if err := utilproxy.EnsureSysctl(sysctl, sysctlRouteLocalnet, 1); err != nil { return nil, err } @@ -289,6 +294,7 @@ func NewProxier(ipt utiliptables.Interface, filterRules: utilproxy.LineBuffer{}, natChains: utilproxy.LineBuffer{}, natRules: utilproxy.LineBuffer{}, + localhostNodePorts: localhostNodePorts, nodePortAddresses: nodePortAddresses, networkInterfacer: utilproxy.RealNetwork{}, } @@ -320,6 +326,7 @@ func NewDualStackProxier( syncPeriod time.Duration, minSyncPeriod time.Duration, masqueradeAll bool, + localhostNodePorts bool, masqueradeBit int, localDetectors [2]proxyutiliptables.LocalTrafficDetector, hostname string, @@ -331,14 +338,14 @@ func NewDualStackProxier( // Create an ipv4 instance of the single-stack proxier ipFamilyMap := utilproxy.MapCIDRsByIPFamily(nodePortAddresses) ipv4Proxier, err := NewProxier(ipt[0], sysctl, - exec, syncPeriod, minSyncPeriod, masqueradeAll, masqueradeBit, localDetectors[0], hostname, + exec, syncPeriod, minSyncPeriod, masqueradeAll, localhostNodePorts, masqueradeBit, localDetectors[0], hostname, nodeIP[0], recorder, healthzServer, ipFamilyMap[v1.IPv4Protocol]) if err != nil { return nil, fmt.Errorf("unable to create ipv4 proxier: %v", err) } ipv6Proxier, err := NewProxier(ipt[1], sysctl, - exec, syncPeriod, minSyncPeriod, masqueradeAll, masqueradeBit, localDetectors[1], hostname, + exec, syncPeriod, minSyncPeriod, masqueradeAll, false, masqueradeBit, localDetectors[1], hostname, nodeIP[1], recorder, healthzServer, ipFamilyMap[v1.IPv6Protocol]) if err != nil { return nil, fmt.Errorf("unable to create ipv6 proxier: %v", err) @@ -1421,23 +1428,52 @@ func (proxier *Proxier) syncProxyRules() { } } - // Finally, tail-call to the nodeports chain. This needs to be after all + // Finally, tail-call to the nodePorts chain. This needs to be after all // other service portal rules. for address := range nodeAddresses { if utilproxy.IsZeroCIDR(address) { + destinations := []string{"-m", "addrtype", "--dst-type", "LOCAL"} + if isIPv6 { + // For IPv6, Regardless of the value of localhostNodePorts is true + // or false, we should disable access to the nodePort via localhost. Since it never works and always + // cause kernel warnings. + destinations = append(destinations, "!", "-d", "::1/128") + } + + if !proxier.localhostNodePorts && !isIPv6 { + // If set localhostNodePorts to "false"(route_localnet=0), We should generate iptables rules that + // disable NodePort services to be accessed via localhost. Since it doesn't work and causes + // the kernel to log warnings if anyone tries. + destinations = append(destinations, "!", "-d", "127.0.0.0/8") + } + proxier.natRules.Write( "-A", string(kubeServicesChain), "-m", "comment", "--comment", `"kubernetes service nodeports; NOTE: this must be the last rule in this chain"`, - "-m", "addrtype", "--dst-type", "LOCAL", + destinations, "-j", string(kubeNodePortsChain)) - // Nothing else matters after the zero CIDR. break } + // Ignore IP addresses with incorrect version if isIPv6 && !netutils.IsIPv6String(address) || !isIPv6 && netutils.IsIPv6String(address) { klog.ErrorS(nil, "IP has incorrect IP version", "IP", address) continue } + + // For ipv6, Regardless of the value of localhostNodePorts is true or false, we should disallow access + // to the nodePort via lookBack address. + if isIPv6 && utilproxy.IsLoopBack(address) { + klog.ErrorS(nil, "disallow nodePort services to be accessed via ipv6 localhost address", "IP", address) + continue + } + + // For ipv4, When localhostNodePorts is set to false, Ignore ipv4 lookBack address + if !isIPv6 && utilproxy.IsLoopBack(address) && !proxier.localhostNodePorts { + klog.ErrorS(nil, "disallow nodePort services to be accessed via ipv4 localhost address", "IP", address) + continue + } + // create nodeport rules for each IP one by one proxier.natRules.Write( "-A", string(kubeServicesChain), diff --git a/pkg/proxy/iptables/proxier_test.go b/pkg/proxy/iptables/proxier_test.go index 351c01c18a8..963fc5a14de 100644 --- a/pkg/proxy/iptables/proxier_test.go +++ b/pkg/proxy/iptables/proxier_test.go @@ -421,6 +421,7 @@ func NewFakeProxier(ipt utiliptables.Interface) *Proxier { natChains: utilproxy.LineBuffer{}, natRules: utilproxy.LineBuffer{}, nodeIP: netutils.ParseIPSloppy(testNodeIP), + localhostNodePorts: true, nodePortAddresses: make([]string, 0), networkInterfacer: networkInterfacer, } @@ -3253,6 +3254,477 @@ func TestOnlyLocalLoadBalancing(t *testing.T) { }) } +func TestEnableLocalhostNodePortsIPv4(t *testing.T) { + ipt := iptablestest.NewFake() + fp := NewFakeProxier(ipt) + fp.localDetector = proxyutiliptables.NewNoOpLocalDetector() + fp.localhostNodePorts = true + + expected := dedent.Dedent(` + *filter + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + COMMIT + *nat + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXT-XPGD46QRK7WJZT7O - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-SEP-6KG6DFHVBKBK53RU - [0:0] + :KUBE-SEP-KDGX2M2ONE25PSWH - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + :KUBE-SVL-XPGD46QRK7WJZT7O - [0:0] + -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m tcp -p tcp --dport 30001 -j KUBE-EXT-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.69.0.10 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "masquerade LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "route LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -j KUBE-SVL-XPGD46QRK7WJZT7O + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -s 10.244.0.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.0.1:80 + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -s 10.244.2.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.2.1:80 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.0.1:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-6KG6DFHVBKBK53RU + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + -A KUBE-SVL-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + COMMIT +`) + svcIP := "10.69.0.10" + svcPort := 80 + svcNodePort := 30001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + }), + ) + + epIP1 := "10.244.0.1" + epIP2 := "10.244.2.1" + tcpProtocol := v1.ProtocolTCP + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP1}, + NodeName: nil, + }, { + Addresses: []string{epIP2}, + NodeName: pointer.String(testHostname), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: pointer.String(svcPortName.Port), + Port: pointer.Int32(int32(svcPort)), + Protocol: &tcpProtocol, + }} + }), + ) + + fp.syncProxyRules() + assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) +} + +func TestDisableLocalhostNodePortsIPv4(t *testing.T) { + ipt := iptablestest.NewFake() + fp := NewFakeProxier(ipt) + fp.localDetector = proxyutiliptables.NewNoOpLocalDetector() + fp.localhostNodePorts = false + + expected := dedent.Dedent(` + *filter + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + COMMIT + *nat + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXT-XPGD46QRK7WJZT7O - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-SEP-6KG6DFHVBKBK53RU - [0:0] + :KUBE-SEP-KDGX2M2ONE25PSWH - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + :KUBE-SVL-XPGD46QRK7WJZT7O - [0:0] + -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m tcp -p tcp --dport 30001 -j KUBE-EXT-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.69.0.10 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL ! -d 127.0.0.0/8 -j KUBE-NODEPORTS + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "masquerade LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "route LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -j KUBE-SVL-XPGD46QRK7WJZT7O + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -s 10.244.0.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.0.1:80 + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -s 10.244.2.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.2.1:80 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.0.1:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-6KG6DFHVBKBK53RU + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + -A KUBE-SVL-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + COMMIT +`) + svcIP := "10.69.0.10" + svcPort := 80 + svcNodePort := 30001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + }), + ) + + epIP1 := "10.244.0.1" + epIP2 := "10.244.2.1" + tcpProtocol := v1.ProtocolTCP + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP1}, + NodeName: nil, + }, { + Addresses: []string{epIP2}, + NodeName: pointer.String(testHostname), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: pointer.String(svcPortName.Port), + Port: pointer.Int32(int32(svcPort)), + Protocol: &tcpProtocol, + }} + }), + ) + + fp.syncProxyRules() + assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) +} + +func TestDisableLocalhostNodePortsIPv4WithNodeAddress(t *testing.T) { + ipt := iptablestest.NewFake() + fp := NewFakeProxier(ipt) + fp.localDetector = proxyutiliptables.NewNoOpLocalDetector() + fp.localhostNodePorts = false + fp.networkInterfacer.InterfaceAddrs() + fp.nodePortAddresses = []string{"127.0.0.0/8"} + + expected := dedent.Dedent(` + *filter + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + COMMIT + *nat + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXT-XPGD46QRK7WJZT7O - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-SEP-6KG6DFHVBKBK53RU - [0:0] + :KUBE-SEP-KDGX2M2ONE25PSWH - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + :KUBE-SVL-XPGD46QRK7WJZT7O - [0:0] + -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m tcp -p tcp --dport 30001 -j KUBE-EXT-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d 10.69.0.10 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "masquerade LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "route LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -j KUBE-SVL-XPGD46QRK7WJZT7O + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -s 10.244.0.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-6KG6DFHVBKBK53RU -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.0.1:80 + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -s 10.244.2.1 -j KUBE-MARK-MASQ + -A KUBE-SEP-KDGX2M2ONE25PSWH -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination 10.244.2.1:80 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.0.1:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-6KG6DFHVBKBK53RU + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + -A KUBE-SVL-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> 10.244.2.1:80" -j KUBE-SEP-KDGX2M2ONE25PSWH + COMMIT +`) + svcIP := "10.69.0.10" + svcPort := 80 + svcNodePort := 30001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + }), + ) + + epIP1 := "10.244.0.1" + epIP2 := "10.244.2.1" + tcpProtocol := v1.ProtocolTCP + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv4 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP1}, + NodeName: nil, + }, { + Addresses: []string{epIP2}, + NodeName: pointer.String(testHostname), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: pointer.String(svcPortName.Port), + Port: pointer.Int32(int32(svcPort)), + Protocol: &tcpProtocol, + }} + }), + ) + + fp.syncProxyRules() + assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) +} + +func TestEnableLocalhostNodePortsIPv6(t *testing.T) { + ipt := iptablestest.NewIPv6Fake() + fp := NewFakeProxier(ipt) + fp.localDetector = proxyutiliptables.NewNoOpLocalDetector() + fp.localhostNodePorts = true + + expected := dedent.Dedent(` + *filter + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + COMMIT + *nat + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXT-XPGD46QRK7WJZT7O - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-SEP-LIGRYQQLSZN4UWQ5 - [0:0] + :KUBE-SEP-XJJ5QXWGJG344QDZ - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + :KUBE-SVL-XPGD46QRK7WJZT7O - [0:0] + -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m tcp -p tcp --dport 30001 -j KUBE-EXT-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d fd00:ab34::20 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL ! -d ::1/128 -j KUBE-NODEPORTS + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "masquerade LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "route LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -j KUBE-SVL-XPGD46QRK7WJZT7O + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-SEP-LIGRYQQLSZN4UWQ5 -m comment --comment ns1/svc1:p80 -s ff06::c1 -j KUBE-MARK-MASQ + -A KUBE-SEP-LIGRYQQLSZN4UWQ5 -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination [ff06::c1]:80 + -A KUBE-SEP-XJJ5QXWGJG344QDZ -m comment --comment ns1/svc1:p80 -s ff06::c2 -j KUBE-MARK-MASQ + -A KUBE-SEP-XJJ5QXWGJG344QDZ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination [ff06::c2]:80 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c1]:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-LIGRYQQLSZN4UWQ5 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c2]:80" -j KUBE-SEP-XJJ5QXWGJG344QDZ + -A KUBE-SVL-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c2]:80" -j KUBE-SEP-XJJ5QXWGJG344QDZ + COMMIT +`) + svcIP := "fd00:ab34::20" + svcPort := 80 + svcNodePort := 30001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + }), + ) + + epIP1 := "ff06::c1" + epIP2 := "ff06::c2" + tcpProtocol := v1.ProtocolTCP + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv6 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP1}, + NodeName: nil, + }, { + Addresses: []string{epIP2}, + NodeName: pointer.String(testHostname), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: pointer.String(svcPortName.Port), + Port: pointer.Int32(int32(svcPort)), + Protocol: &tcpProtocol, + }} + }), + ) + + fp.syncProxyRules() + assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) +} + +func TestDisableLocalhostNodePortsIPv6(t *testing.T) { + ipt := iptablestest.NewIPv6Fake() + fp := NewFakeProxier(ipt) + fp.localDetector = proxyutiliptables.NewNoOpLocalDetector() + fp.localhostNodePorts = false + + expected := dedent.Dedent(` + *filter + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXTERNAL-SERVICES - [0:0] + :KUBE-FORWARD - [0:0] + :KUBE-PROXY-FIREWALL - [0:0] + -A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT + -A KUBE-FORWARD -m comment --comment "kubernetes forwarding conntrack rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + COMMIT + *nat + :KUBE-NODEPORTS - [0:0] + :KUBE-SERVICES - [0:0] + :KUBE-EXT-XPGD46QRK7WJZT7O - [0:0] + :KUBE-MARK-MASQ - [0:0] + :KUBE-POSTROUTING - [0:0] + :KUBE-SEP-LIGRYQQLSZN4UWQ5 - [0:0] + :KUBE-SEP-XJJ5QXWGJG344QDZ - [0:0] + :KUBE-SVC-XPGD46QRK7WJZT7O - [0:0] + :KUBE-SVL-XPGD46QRK7WJZT7O - [0:0] + -A KUBE-NODEPORTS -m comment --comment ns1/svc1:p80 -m tcp -p tcp --dport 30001 -j KUBE-EXT-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "ns1/svc1:p80 cluster IP" -m tcp -p tcp -d fd00:ab34::20 --dport 80 -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL ! -d ::1/128 -j KUBE-NODEPORTS + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "masquerade LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-MARK-MASQ + -A KUBE-EXT-XPGD46QRK7WJZT7O -m comment --comment "route LOCAL traffic for ns1/svc1:p80 external destinations" -m addrtype --src-type LOCAL -j KUBE-SVC-XPGD46QRK7WJZT7O + -A KUBE-EXT-XPGD46QRK7WJZT7O -j KUBE-SVL-XPGD46QRK7WJZT7O + -A KUBE-MARK-MASQ -j MARK --or-mark 0x4000 + -A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN + -A KUBE-POSTROUTING -j MARK --xor-mark 0x4000 + -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE + -A KUBE-SEP-LIGRYQQLSZN4UWQ5 -m comment --comment ns1/svc1:p80 -s ff06::c1 -j KUBE-MARK-MASQ + -A KUBE-SEP-LIGRYQQLSZN4UWQ5 -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination [ff06::c1]:80 + -A KUBE-SEP-XJJ5QXWGJG344QDZ -m comment --comment ns1/svc1:p80 -s ff06::c2 -j KUBE-MARK-MASQ + -A KUBE-SEP-XJJ5QXWGJG344QDZ -m comment --comment ns1/svc1:p80 -m tcp -p tcp -j DNAT --to-destination [ff06::c2]:80 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c1]:80" -m statistic --mode random --probability 0.5000000000 -j KUBE-SEP-LIGRYQQLSZN4UWQ5 + -A KUBE-SVC-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c2]:80" -j KUBE-SEP-XJJ5QXWGJG344QDZ + -A KUBE-SVL-XPGD46QRK7WJZT7O -m comment --comment "ns1/svc1:p80 -> [ff06::c2]:80" -j KUBE-SEP-XJJ5QXWGJG344QDZ + COMMIT +`) + svcIP := "fd00:ab34::20" + svcPort := 80 + svcNodePort := 30001 + svcPortName := proxy.ServicePortName{ + NamespacedName: makeNSN("ns1", "svc1"), + Port: "p80", + Protocol: v1.ProtocolTCP, + } + + makeServiceMap(fp, + makeTestService(svcPortName.Namespace, svcPortName.Name, func(svc *v1.Service) { + svc.Spec.Type = "NodePort" + svc.Spec.ClusterIP = svcIP + svc.Spec.Ports = []v1.ServicePort{{ + Name: svcPortName.Port, + Port: int32(svcPort), + Protocol: v1.ProtocolTCP, + NodePort: int32(svcNodePort), + }} + svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal + }), + ) + + epIP1 := "ff06::c1" + epIP2 := "ff06::c2" + tcpProtocol := v1.ProtocolTCP + populateEndpointSlices(fp, + makeTestEndpointSlice(svcPortName.Namespace, svcPortName.Name, 1, func(eps *discovery.EndpointSlice) { + eps.AddressType = discovery.AddressTypeIPv6 + eps.Endpoints = []discovery.Endpoint{{ + Addresses: []string{epIP1}, + NodeName: nil, + }, { + Addresses: []string{epIP2}, + NodeName: pointer.String(testHostname), + }} + eps.Ports = []discovery.EndpointPort{{ + Name: pointer.String(svcPortName.Port), + Port: pointer.Int32(int32(svcPort)), + Protocol: &tcpProtocol, + }} + }), + ) + + fp.syncProxyRules() + assertIPTablesRulesEqual(t, getLine(), true, expected, fp.iptablesData.String()) +} + func TestOnlyLocalNodePortsNoClusterCIDR(t *testing.T) { ipt := iptablestest.NewFake() fp := NewFakeProxier(ipt) diff --git a/pkg/proxy/util/utils.go b/pkg/proxy/util/utils.go index 1cd78a4a8ca..92ef4658082 100644 --- a/pkg/proxy/util/utils.go +++ b/pkg/proxy/util/utils.go @@ -118,6 +118,15 @@ func IsZeroCIDR(cidr string) bool { return false } +// IsLoopBack checks if a given IP address is a loopback address. +func IsLoopBack(ip string) bool { + netIP := netutils.ParseIPSloppy(ip) + if netIP != nil { + return netIP.IsLoopback() + } + return false +} + // IsProxyableIP checks if a given IP address is permitted to be proxied func IsProxyableIP(ip string) error { netIP := netutils.ParseIPSloppy(ip) diff --git a/staging/src/k8s.io/kube-proxy/config/v1alpha1/types.go b/staging/src/k8s.io/kube-proxy/config/v1alpha1/types.go index 0d60dc4a50d..3c6f3ecf6cc 100644 --- a/staging/src/k8s.io/kube-proxy/config/v1alpha1/types.go +++ b/staging/src/k8s.io/kube-proxy/config/v1alpha1/types.go @@ -29,6 +29,9 @@ type KubeProxyIPTablesConfiguration struct { MasqueradeBit *int32 `json:"masqueradeBit"` // masqueradeAll tells kube-proxy to SNAT everything if using the pure iptables proxy mode. MasqueradeAll bool `json:"masqueradeAll"` + // LocalhostNodePorts tells kube-proxy to allow service NodePorts to be accessed via + // localhost (iptables mode only) + LocalhostNodePorts *bool `json:"localhostNodePorts"` // syncPeriod is the period that iptables rules are refreshed (e.g. '5s', '1m', // '2h22m'). Must be greater than 0. SyncPeriod metav1.Duration `json:"syncPeriod"` diff --git a/staging/src/k8s.io/kube-proxy/config/v1alpha1/zz_generated.deepcopy.go b/staging/src/k8s.io/kube-proxy/config/v1alpha1/zz_generated.deepcopy.go index f5893982b1b..5ecdfb06c68 100644 --- a/staging/src/k8s.io/kube-proxy/config/v1alpha1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/kube-proxy/config/v1alpha1/zz_generated.deepcopy.go @@ -135,6 +135,11 @@ func (in *KubeProxyIPTablesConfiguration) DeepCopyInto(out *KubeProxyIPTablesCon *out = new(int32) **out = **in } + if in.LocalhostNodePorts != nil { + in, out := &in.LocalhostNodePorts, &out.LocalhostNodePorts + *out = new(bool) + **out = **in + } out.SyncPeriod = in.SyncPeriod out.MinSyncPeriod = in.MinSyncPeriod return