Merge pull request #122692 from aroradaman/reject-packets-to-invalid-port

proxy/nftables: reject packets destined for invalid ports of service ips
This commit is contained in:
Kubernetes Prow Robot 2024-01-24 23:17:34 +01:00 committed by GitHub
commit 55f9657e07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 457 additions and 150 deletions

View File

@ -921,7 +921,7 @@ func (s *ProxyServer) Run() error {
options.LabelSelector = labelSelector.String() options.LabelSelector = labelSelector.String()
})) }))
// Create configs (i.e. Watches for Services and EndpointSlices) // Create configs (i.e. Watches for Services, EndpointSlices and ServiceCIDRs)
// Note: RegisterHandler() calls need to happen before creation of Sources because sources // Note: RegisterHandler() calls need to happen before creation of Sources because sources
// only notify on changes, and the initial update (on process start) may be lost if no handlers // only notify on changes, and the initial update (on process start) may be lost if no handlers
// are registered yet. // are registered yet.
@ -933,6 +933,11 @@ func (s *ProxyServer) Run() error {
endpointSliceConfig.RegisterEventHandler(s.Proxier) endpointSliceConfig.RegisterEventHandler(s.Proxier)
go endpointSliceConfig.Run(wait.NeverStop) go endpointSliceConfig.Run(wait.NeverStop)
if utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
serviceCIDRConfig := config.NewServiceCIDRConfig(informerFactory.Networking().V1alpha1().ServiceCIDRs(), s.Config.ConfigSyncPeriod.Duration)
serviceCIDRConfig.RegisterEventHandler(s.Proxier)
go serviceCIDRConfig.Run(wait.NeverStop)
}
// This has to start after the calls to NewServiceConfig because that // This has to start after the calls to NewServiceConfig because that
// function must configure its shared informer event handlers first. // function must configure its shared informer event handlers first.
informerFactory.Start(wait.NeverStop) informerFactory.Start(wait.NeverStop)

View File

@ -18,13 +18,17 @@ package config
import ( import (
"fmt" "fmt"
"sync"
"time" "time"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1" discoveryv1 "k8s.io/api/discovery/v1"
networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime"
coreinformers "k8s.io/client-go/informers/core/v1" "k8s.io/apimachinery/pkg/util/sets"
discoveryinformers "k8s.io/client-go/informers/discovery/v1" v1informers "k8s.io/client-go/informers/core/v1"
discoveryv1informers "k8s.io/client-go/informers/discovery/v1"
networkingv1alpha1informers "k8s.io/client-go/informers/networking/v1alpha1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
@ -51,13 +55,13 @@ type ServiceHandler interface {
type EndpointSliceHandler interface { type EndpointSliceHandler interface {
// OnEndpointSliceAdd is called whenever creation of new endpoint slice // OnEndpointSliceAdd is called whenever creation of new endpoint slice
// object is observed. // object is observed.
OnEndpointSliceAdd(endpointSlice *discovery.EndpointSlice) OnEndpointSliceAdd(endpointSlice *discoveryv1.EndpointSlice)
// OnEndpointSliceUpdate is called whenever modification of an existing // OnEndpointSliceUpdate is called whenever modification of an existing
// endpoint slice object is observed. // endpoint slice object is observed.
OnEndpointSliceUpdate(oldEndpointSlice, newEndpointSlice *discovery.EndpointSlice) OnEndpointSliceUpdate(oldEndpointSlice, newEndpointSlice *discoveryv1.EndpointSlice)
// OnEndpointSliceDelete is called whenever deletion of an existing // OnEndpointSliceDelete is called whenever deletion of an existing
// endpoint slice object is observed. // endpoint slice object is observed.
OnEndpointSliceDelete(endpointSlice *discovery.EndpointSlice) OnEndpointSliceDelete(endpointSlice *discoveryv1.EndpointSlice)
// OnEndpointSlicesSynced is called once all the initial event handlers were // OnEndpointSlicesSynced is called once all the initial event handlers were
// called and the state is fully propagated to local cache. // called and the state is fully propagated to local cache.
OnEndpointSlicesSynced() OnEndpointSlicesSynced()
@ -70,12 +74,12 @@ type EndpointSliceConfig struct {
} }
// NewEndpointSliceConfig creates a new EndpointSliceConfig. // NewEndpointSliceConfig creates a new EndpointSliceConfig.
func NewEndpointSliceConfig(endpointSliceInformer discoveryinformers.EndpointSliceInformer, resyncPeriod time.Duration) *EndpointSliceConfig { func NewEndpointSliceConfig(endpointSliceInformer discoveryv1informers.EndpointSliceInformer, resyncPeriod time.Duration) *EndpointSliceConfig {
result := &EndpointSliceConfig{ result := &EndpointSliceConfig{
listerSynced: endpointSliceInformer.Informer().HasSynced, listerSynced: endpointSliceInformer.Informer().HasSynced,
} }
endpointSliceInformer.Informer().AddEventHandlerWithResyncPeriod( _, _ = endpointSliceInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
AddFunc: result.handleAddEndpointSlice, AddFunc: result.handleAddEndpointSlice,
UpdateFunc: result.handleUpdateEndpointSlice, UpdateFunc: result.handleUpdateEndpointSlice,
@ -107,7 +111,7 @@ func (c *EndpointSliceConfig) Run(stopCh <-chan struct{}) {
} }
func (c *EndpointSliceConfig) handleAddEndpointSlice(obj interface{}) { func (c *EndpointSliceConfig) handleAddEndpointSlice(obj interface{}) {
endpointSlice, ok := obj.(*discovery.EndpointSlice) endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok { if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj)) utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj))
return return
@ -119,12 +123,12 @@ func (c *EndpointSliceConfig) handleAddEndpointSlice(obj interface{}) {
} }
func (c *EndpointSliceConfig) handleUpdateEndpointSlice(oldObj, newObj interface{}) { func (c *EndpointSliceConfig) handleUpdateEndpointSlice(oldObj, newObj interface{}) {
oldEndpointSlice, ok := oldObj.(*discovery.EndpointSlice) oldEndpointSlice, ok := oldObj.(*discoveryv1.EndpointSlice)
if !ok { if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", newObj)) utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", newObj))
return return
} }
newEndpointSlice, ok := newObj.(*discovery.EndpointSlice) newEndpointSlice, ok := newObj.(*discoveryv1.EndpointSlice)
if !ok { if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", newObj)) utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", newObj))
return return
@ -136,14 +140,14 @@ func (c *EndpointSliceConfig) handleUpdateEndpointSlice(oldObj, newObj interface
} }
func (c *EndpointSliceConfig) handleDeleteEndpointSlice(obj interface{}) { func (c *EndpointSliceConfig) handleDeleteEndpointSlice(obj interface{}) {
endpointSlice, ok := obj.(*discovery.EndpointSlice) endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
if !ok { if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown) tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok { if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj)) utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj))
return return
} }
if endpointSlice, ok = tombstone.Obj.(*discovery.EndpointSlice); !ok { if endpointSlice, ok = tombstone.Obj.(*discoveryv1.EndpointSlice); !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj)) utilruntime.HandleError(fmt.Errorf("unexpected object type: %T", obj))
return return
} }
@ -161,12 +165,12 @@ type ServiceConfig struct {
} }
// NewServiceConfig creates a new ServiceConfig. // NewServiceConfig creates a new ServiceConfig.
func NewServiceConfig(serviceInformer coreinformers.ServiceInformer, resyncPeriod time.Duration) *ServiceConfig { func NewServiceConfig(serviceInformer v1informers.ServiceInformer, resyncPeriod time.Duration) *ServiceConfig {
result := &ServiceConfig{ result := &ServiceConfig{
listerSynced: serviceInformer.Informer().HasSynced, listerSynced: serviceInformer.Informer().HasSynced,
} }
serviceInformer.Informer().AddEventHandlerWithResyncPeriod( _, _ = serviceInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
AddFunc: result.handleAddService, AddFunc: result.handleAddService,
UpdateFunc: result.handleUpdateService, UpdateFunc: result.handleUpdateService,
@ -288,12 +292,12 @@ type NodeConfig struct {
} }
// NewNodeConfig creates a new NodeConfig. // NewNodeConfig creates a new NodeConfig.
func NewNodeConfig(nodeInformer coreinformers.NodeInformer, resyncPeriod time.Duration) *NodeConfig { func NewNodeConfig(nodeInformer v1informers.NodeInformer, resyncPeriod time.Duration) *NodeConfig {
result := &NodeConfig{ result := &NodeConfig{
listerSynced: nodeInformer.Informer().HasSynced, listerSynced: nodeInformer.Informer().HasSynced,
} }
nodeInformer.Informer().AddEventHandlerWithResyncPeriod( _, _ = nodeInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
AddFunc: result.handleAddNode, AddFunc: result.handleAddNode,
UpdateFunc: result.handleUpdateNode, UpdateFunc: result.handleUpdateNode,
@ -371,3 +375,97 @@ func (c *NodeConfig) handleDeleteNode(obj interface{}) {
c.eventHandlers[i].OnNodeDelete(node) c.eventHandlers[i].OnNodeDelete(node)
} }
} }
// ServiceCIDRHandler is an abstract interface of objects which receive
// notifications about ServiceCIDR object changes.
type ServiceCIDRHandler interface {
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
OnServiceCIDRsChanged(cidrs []string)
}
// ServiceCIDRConfig tracks a set of service configurations.
type ServiceCIDRConfig struct {
listerSynced cache.InformerSynced
eventHandlers []ServiceCIDRHandler
mu sync.Mutex
cidrs sets.Set[string]
}
// NewServiceCIDRConfig creates a new ServiceCIDRConfig.
func NewServiceCIDRConfig(serviceCIDRInformer networkingv1alpha1informers.ServiceCIDRInformer, resyncPeriod time.Duration) *ServiceCIDRConfig {
result := &ServiceCIDRConfig{
listerSynced: serviceCIDRInformer.Informer().HasSynced,
cidrs: sets.New[string](),
}
_, _ = serviceCIDRInformer.Informer().AddEventHandlerWithResyncPeriod(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
result.handleServiceCIDREvent(nil, obj)
},
UpdateFunc: func(oldObj, newObj interface{}) {
result.handleServiceCIDREvent(oldObj, newObj)
},
DeleteFunc: func(obj interface{}) {
result.handleServiceCIDREvent(obj, nil)
},
},
resyncPeriod,
)
return result
}
// RegisterEventHandler registers a handler which is called on every ServiceCIDR change.
func (c *ServiceCIDRConfig) RegisterEventHandler(handler ServiceCIDRHandler) {
c.eventHandlers = append(c.eventHandlers, handler)
}
// Run waits for cache synced and invokes handlers after syncing.
func (c *ServiceCIDRConfig) Run(stopCh <-chan struct{}) {
klog.InfoS("Starting serviceCIDR config controller")
if !cache.WaitForNamedCacheSync("serviceCIDR config", stopCh, c.listerSynced) {
return
}
c.handleServiceCIDREvent(nil, nil)
}
// handleServiceCIDREvent is a helper function to handle Add, Update and Delete
// events on ServiceCIDR objects and call downstream event handlers.
func (c *ServiceCIDRConfig) handleServiceCIDREvent(oldObj, newObj interface{}) {
var oldServiceCIDR, newServiceCIDR *networkingv1alpha1.ServiceCIDR
var ok bool
if oldObj != nil {
oldServiceCIDR, ok = oldObj.(*networkingv1alpha1.ServiceCIDR)
if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %v", oldObj))
return
}
}
if newObj != nil {
newServiceCIDR, ok = newObj.(*networkingv1alpha1.ServiceCIDR)
if !ok {
utilruntime.HandleError(fmt.Errorf("unexpected object type: %v", newObj))
return
}
}
c.mu.Lock()
defer c.mu.Unlock()
if oldServiceCIDR != nil {
c.cidrs.Delete(oldServiceCIDR.Spec.CIDRs...)
}
if newServiceCIDR != nil {
c.cidrs.Insert(newServiceCIDR.Spec.CIDRs...)
}
for i := range c.eventHandlers {
klog.V(4).InfoS("Calling handler.OnServiceCIDRsChanged")
c.eventHandlers[i].OnServiceCIDRsChanged(c.cidrs.UnsortedList())
}
}

View File

@ -673,6 +673,10 @@ func (proxier *Proxier) OnNodeDelete(node *v1.Node) {
func (proxier *Proxier) OnNodeSynced() { func (proxier *Proxier) OnNodeSynced() {
} }
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
func (proxier *Proxier) OnServiceCIDRsChanged(_ []string) {}
// portProtoHash takes the ServicePortName and protocol for a service // portProtoHash takes the ServicePortName and protocol for a service
// returns the associated 16 character hash. This is computed by hashing (sha256) // returns the associated 16 character hash. This is computed by hashing (sha256)
// then encoding to base32 and truncating to 16 chars. We do this because IPTables // then encoding to base32 and truncating to 16 chars. We do this because IPTables

View File

@ -892,6 +892,10 @@ func (proxier *Proxier) OnNodeDelete(node *v1.Node) {
func (proxier *Proxier) OnNodeSynced() { func (proxier *Proxier) OnNodeSynced() {
} }
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
func (proxier *Proxier) OnServiceCIDRsChanged(_ []string) {}
// This is where all of the ipvs calls happen. // This is where all of the ipvs calls happen.
func (proxier *Proxier) syncProxyRules() { func (proxier *Proxier) syncProxyRules() {
proxier.mu.Lock() proxier.mu.Lock()

View File

@ -53,6 +53,7 @@ func (*FakeProxier) OnEndpointSliceAdd(slice *discoveryv1.EndpointSlice)
func (*FakeProxier) OnEndpointSliceUpdate(oldSlice, slice *discoveryv1.EndpointSlice) {} func (*FakeProxier) OnEndpointSliceUpdate(oldSlice, slice *discoveryv1.EndpointSlice) {}
func (*FakeProxier) OnEndpointSliceDelete(slice *discoveryv1.EndpointSlice) {} func (*FakeProxier) OnEndpointSliceDelete(slice *discoveryv1.EndpointSlice) {}
func (*FakeProxier) OnEndpointSlicesSynced() {} func (*FakeProxier) OnEndpointSlicesSynced() {}
func (*FakeProxier) OnServiceCIDRsChanged(_ []string) {}
func NewHollowProxy( func NewHollowProxy(
nodeName string, nodeName string,

View File

@ -158,3 +158,10 @@ func (proxier *metaProxier) OnNodeSynced() {
proxier.ipv4Proxier.OnNodeSynced() proxier.ipv4Proxier.OnNodeSynced()
proxier.ipv6Proxier.OnNodeSynced() proxier.ipv6Proxier.OnNodeSynced()
} }
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
func (proxier *metaProxier) OnServiceCIDRsChanged(cidrs []string) {
proxier.ipv4Proxier.OnServiceCIDRsChanged(cidrs)
proxier.ipv6Proxier.OnServiceCIDRsChanged(cidrs)
}

View File

@ -51,7 +51,7 @@ the forward path.
## kube-proxy's use of nftables hooks ## kube-proxy's use of nftables hooks
Kube-proxy uses nftables for four things: Kube-proxy uses nftables for seven things:
- Using DNAT to rewrite traffic from service IPs (cluster IPs, external IPs, load balancer - Using DNAT to rewrite traffic from service IPs (cluster IPs, external IPs, load balancer
IP, and NodePorts on node IPs) to the corresponding endpoint IPs. IP, and NodePorts on node IPs) to the corresponding endpoint IPs.
@ -65,6 +65,10 @@ Kube-proxy uses nftables for four things:
- Rejecting packets for services with no local or remote endpoints. - Rejecting packets for services with no local or remote endpoints.
- Dropping packets to ClusterIPs which are not yet allocated.
- Rejecting packets to undefined ports of ClusterIPs.
This is implemented as follows: This is implemented as follows:
- We do the DNAT for inbound traffic in `prerouting`: this covers traffic coming from - We do the DNAT for inbound traffic in `prerouting`: this covers traffic coming from
@ -87,13 +91,9 @@ This is implemented as follows:
explicitly before or after any other rules (since they match packets that wouldn't be explicitly before or after any other rules (since they match packets that wouldn't be
matched by any other rules). But with kernels before 5.9, `reject` is not allowed in matched by any other rules). But with kernels before 5.9, `reject` is not allowed in
`prerouting`, so we can't just do them in the same place as the source ranges `prerouting`, so we can't just do them in the same place as the source ranges
firewall. So we do these checks from `input`, `forward`, and `output`, to cover all firewall. So we do these checks from `input`, `forward`, and `output` for
three paths. (In fact, we only need to check `@no-endpoint-nodeports` on the `input` `@no-endpoint-services` and from `input` for `@no-endpoint-nodeports` to cover all
hook, but it's easier to just check them both in one place, and this code is likely to the possible paths.
be rewritten later anyway. Note that the converse statement "we only need to check
`@no-endpoint-services` on the `forward` and `output` hooks" is *not* true, because
`@no-endpoint-services` may include externalIPs/LB IPs that are assigned to local
interfaces.)
- Masquerading has to happen in the `postrouting` hook, because "masquerade" means "SNAT - Masquerading has to happen in the `postrouting` hook, because "masquerade" means "SNAT
to the IP of the interface the packet is going out on", so it has to happen after the to the IP of the interface the packet is going out on", so it has to happen after the
@ -101,3 +101,9 @@ This is implemented as follows:
network IP, because masquerading is about ensuring that the packet eventually gets network IP, because masquerading is about ensuring that the packet eventually gets
routed back to the host network namespace on this node, so if it's never getting routed back to the host network namespace on this node, so if it's never getting
routed away from there, there's nothing to do.) routed away from there, there's nothing to do.)
- We install a `reject` rule for ClusterIPs matching `@cluster-ips` set and a `drop`
rule for ClusterIPs belonging to any of the ServiceCIDRs in `forward` and `output` hook, with a
higher (i.e. less urgent) priority than the DNAT chains making sure all valid
traffic directed for ClusterIPs is already DNATed. Drop rule will only
be installed if `MultiCIDRServiceAllocator` feature is enabled.

View File

@ -22,6 +22,7 @@ package nftables
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"regexp" "regexp"
"runtime" "runtime"
"sort" "sort"
@ -174,7 +175,7 @@ func newNFTablesTracer(t *testing.T, nft *knftables.Fake, nodeIPs []string) *nft
} }
} }
func (tracer *nftablesTracer) addressMatches(ipStr, not, ruleAddress string) bool { func (tracer *nftablesTracer) addressMatches(ipStr string, wantMatch bool, ruleAddress string) bool {
ip := netutils.ParseIPSloppy(ipStr) ip := netutils.ParseIPSloppy(ipStr)
if ip == nil { if ip == nil {
tracer.t.Fatalf("Bad IP in test case: %s", ipStr) tracer.t.Fatalf("Bad IP in test case: %s", ipStr)
@ -195,18 +196,16 @@ func (tracer *nftablesTracer) addressMatches(ipStr, not, ruleAddress string) boo
match = ip.Equal(ip2) match = ip.Equal(ip2)
} }
if not == "!= " { return match == wantMatch
return !match
} else {
return match
}
} }
func (tracer *nftablesTracer) noneAddressesMatch(ipStr, ruleAddress string) bool { func (tracer *nftablesTracer) addressMatchesSet(ipStr string, wantMatch bool, ruleAddress string) bool {
ruleAddress = strings.ReplaceAll(ruleAddress, " ", "") ruleAddress = strings.ReplaceAll(ruleAddress, " ", "")
addresses := strings.Split(ruleAddress, ",") addresses := strings.Split(ruleAddress, ",")
var match bool
for _, address := range addresses { for _, address := range addresses {
if tracer.addressMatches(ipStr, "", address) { match = tracer.addressMatches(ipStr, true, address)
if match != wantMatch {
return false return false
} }
} }
@ -240,7 +239,7 @@ func (tracer *nftablesTracer) matchDest(elements []*knftables.Element, destIP, p
// found. // found.
func (tracer *nftablesTracer) matchDestAndSource(elements []*knftables.Element, destIP, protocol, destPort, sourceIP string) *knftables.Element { func (tracer *nftablesTracer) matchDestAndSource(elements []*knftables.Element, destIP, protocol, destPort, sourceIP string) *knftables.Element {
for _, element := range elements { for _, element := range elements {
if element.Key[0] == destIP && element.Key[1] == protocol && element.Key[2] == destPort && tracer.addressMatches(sourceIP, "", element.Key[3]) { if element.Key[0] == destIP && element.Key[1] == protocol && element.Key[2] == destPort && tracer.addressMatches(sourceIP, true, element.Key[3]) {
return element return element
} }
} }
@ -270,7 +269,7 @@ func (tracer *nftablesTracer) matchDestPort(elements []*knftables.Element, proto
// match verdictRegexp. // match verdictRegexp.
var destAddrRegexp = regexp.MustCompile(`^ip6* daddr (!= )?(\S+)`) var destAddrRegexp = regexp.MustCompile(`^ip6* daddr (!= )?(\S+)`)
var destAddrLookupRegexp = regexp.MustCompile(`^ip6* daddr != \{([^}]*)\}`) var destAddrLookupRegexp = regexp.MustCompile(`^ip6* daddr (!= )?\{([^}]*)\}`)
var destAddrLocalRegexp = regexp.MustCompile(`^fib daddr type local`) var destAddrLocalRegexp = regexp.MustCompile(`^fib daddr type local`)
var destPortRegexp = regexp.MustCompile(`^(tcp|udp|sctp) dport (\d+)`) var destPortRegexp = regexp.MustCompile(`^(tcp|udp|sctp) dport (\d+)`)
var destIPOnlyLookupRegexp = regexp.MustCompile(`^ip6* daddr @(\S+)`) var destIPOnlyLookupRegexp = regexp.MustCompile(`^ip6* daddr @(\S+)`)
@ -282,13 +281,13 @@ var destDispatchRegexp = regexp.MustCompile(`^ip6* daddr \. meta l4proto \. th d
var destPortDispatchRegexp = regexp.MustCompile(`^meta l4proto \. th dport vmap @(\S+)$`) var destPortDispatchRegexp = regexp.MustCompile(`^meta l4proto \. th dport vmap @(\S+)$`)
var sourceAddrRegexp = regexp.MustCompile(`^ip6* saddr (!= )?(\S+)`) var sourceAddrRegexp = regexp.MustCompile(`^ip6* saddr (!= )?(\S+)`)
var sourceAddrLookupRegexp = regexp.MustCompile(`^ip6* saddr != \{([^}]*)\}`) var sourceAddrLookupRegexp = regexp.MustCompile(`^ip6* saddr (!= )?\{([^}]*)\}`)
var sourceAddrLocalRegexp = regexp.MustCompile(`^fib saddr type local`) var sourceAddrLocalRegexp = regexp.MustCompile(`^fib saddr type local`)
var endpointVMAPRegexp = regexp.MustCompile(`^numgen random mod \d+ vmap \{(.*)\}$`) var endpointVMAPRegexp = regexp.MustCompile(`^numgen random mod \d+ vmap \{(.*)\}$`)
var endpointVMapEntryRegexp = regexp.MustCompile(`\d+ : goto (\S+)`) var endpointVMapEntryRegexp = regexp.MustCompile(`\d+ : goto (\S+)`)
var masqueradeRegexp = regexp.MustCompile(`^jump ` + kubeMarkMasqChain + `$`) var masqueradeRegexp = regexp.MustCompile(`^jump ` + markMasqChain + `$`)
var jumpRegexp = regexp.MustCompile(`^(jump|goto) (\S+)$`) var jumpRegexp = regexp.MustCompile(`^(jump|goto) (\S+)$`)
var returnRegexp = regexp.MustCompile(`^return$`) var returnRegexp = regexp.MustCompile(`^return$`)
var verdictRegexp = regexp.MustCompile(`^(drop|reject)$`) var verdictRegexp = regexp.MustCompile(`^(drop|reject)$`)
@ -402,11 +401,12 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP
} }
case destAddrLookupRegexp.MatchString(rule): case destAddrLookupRegexp.MatchString(rule):
// `^ip6* daddr != \{([^}]*)\}` // `^ip6* daddr (!= )?\{([^}]*)\}`
// Tests whether destIP doesn't match an anonymous set. // Tests whether destIP doesn't match an anonymous set.
match := destAddrLookupRegexp.FindStringSubmatch(rule) match := destAddrLookupRegexp.FindStringSubmatch(rule)
rule = strings.TrimPrefix(rule, match[0]) rule = strings.TrimPrefix(rule, match[0])
if !tracer.noneAddressesMatch(destIP, match[1]) { wantMatch, set := match[1] != "!= ", match[2]
if !tracer.addressMatchesSet(destIP, wantMatch, set) {
rule = "" rule = ""
break break
} }
@ -416,8 +416,8 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP
// Tests whether destIP does/doesn't match a literal. // Tests whether destIP does/doesn't match a literal.
match := destAddrRegexp.FindStringSubmatch(rule) match := destAddrRegexp.FindStringSubmatch(rule)
rule = strings.TrimPrefix(rule, match[0]) rule = strings.TrimPrefix(rule, match[0])
not, ip := match[1], match[2] wantMatch, ip := match[1] != "!= ", match[2]
if !tracer.addressMatches(destIP, not, ip) { if !tracer.addressMatches(destIP, wantMatch, ip) {
rule = "" rule = ""
break break
} }
@ -444,11 +444,12 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP
} }
case sourceAddrLookupRegexp.MatchString(rule): case sourceAddrLookupRegexp.MatchString(rule):
// `^ip6* saddr != \{([^}]*)\}` // `^ip6* saddr (!= )?\{([^}]*)\}`
// Tests whether sourceIP doesn't match an anonymous set. // Tests whether sourceIP doesn't match an anonymous set.
match := sourceAddrLookupRegexp.FindStringSubmatch(rule) match := sourceAddrLookupRegexp.FindStringSubmatch(rule)
rule = strings.TrimPrefix(rule, match[0]) rule = strings.TrimPrefix(rule, match[0])
if !tracer.noneAddressesMatch(sourceIP, match[1]) { wantMatch, set := match[1] != "!= ", match[2]
if !tracer.addressMatchesSet(sourceIP, wantMatch, set) {
rule = "" rule = ""
break break
} }
@ -458,8 +459,8 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP
// Tests whether sourceIP does/doesn't match a literal. // Tests whether sourceIP does/doesn't match a literal.
match := sourceAddrRegexp.FindStringSubmatch(rule) match := sourceAddrRegexp.FindStringSubmatch(rule)
rule = strings.TrimPrefix(rule, match[0]) rule = strings.TrimPrefix(rule, match[0])
not, ip := match[1], match[2] wantMatch, ip := match[1] != "!= ", match[2]
if !tracer.addressMatches(sourceIP, not, ip) { if !tracer.addressMatches(sourceIP, wantMatch, ip) {
rule = "" rule = ""
break break
} }
@ -569,6 +570,7 @@ func (tracer *nftablesTracer) runChain(chname, sourceIP, protocol, destIP, destP
// destinations (a comma-separated list of IPs, or one of the special targets "ACCEPT", // destinations (a comma-separated list of IPs, or one of the special targets "ACCEPT",
// "DROP", or "REJECT"), and whether the packet would be masqueraded. // "DROP", or "REJECT"), and whether the packet would be masqueraded.
func tracePacket(t *testing.T, nft *knftables.Fake, sourceIP, protocol, destIP, destPort string, nodeIPs []string) ([]string, string, bool) { func tracePacket(t *testing.T, nft *knftables.Fake, sourceIP, protocol, destIP, destPort string, nodeIPs []string) ([]string, string, bool) {
var err error
tracer := newNFTablesTracer(t, nft, nodeIPs) tracer := newNFTablesTracer(t, nft, nodeIPs)
// filter-prerouting goes first, then nat-prerouting if not terminated. // filter-prerouting goes first, then nat-prerouting if not terminated.
@ -579,11 +581,19 @@ func tracePacket(t *testing.T, nft *knftables.Fake, sourceIP, protocol, destIP,
// After the prerouting rules run, pending DNATs are processed (which would affect // After the prerouting rules run, pending DNATs are processed (which would affect
// the destination IP that later rules match against). // the destination IP that later rules match against).
if len(tracer.outputs) != 0 { if len(tracer.outputs) != 0 {
destIP = strings.Split(tracer.outputs[0], ":")[0] destIP, _, err = net.SplitHostPort(tracer.outputs[0])
if err != nil {
t.Errorf("failed to parse host port '%s': %s", tracer.outputs[0], err.Error())
}
} }
// Run filter-forward, skip filter-input as it ought to be fully redundant with the filter-forward chain. // Run filter-forward, return if packet is terminated.
tracer.runChain("filter-forward", sourceIP, protocol, destIP, destPort) if tracer.runChain("filter-forward", sourceIP, protocol, destIP, destPort) {
return tracer.matches, strings.Join(tracer.outputs, ", "), tracer.markMasq
}
// Run filter-input
tracer.runChain("filter-input", sourceIP, protocol, destIP, destPort)
// Skip filter-output and nat-output as they ought to be fully redundant with the prerouting chains. // Skip filter-output and nat-output as they ought to be fully redundant with the prerouting chains.
// Skip nat-postrouting because it only does masquerading and we handle that separately. // Skip nat-postrouting because it only does masquerading and we handle that separately.

View File

@ -55,6 +55,7 @@ import (
proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables" proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables"
"k8s.io/kubernetes/pkg/util/async" "k8s.io/kubernetes/pkg/util/async"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
netutils "k8s.io/utils/net"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
) )
@ -63,27 +64,44 @@ const (
// so they don't need any "kube-" or "kube-proxy-" prefix of their own. // so they don't need any "kube-" or "kube-proxy-" prefix of their own.
kubeProxyTable = "kube-proxy" kubeProxyTable = "kube-proxy"
// base chains
filterPreroutingChain = "filter-prerouting"
filterInputChain = "filter-input"
filterForwardChain = "filter-forward"
filterOutputChain = "filter-output"
filterOutputPostDNATChain = "filter-output-post-dnat"
natPreroutingChain = "nat-prerouting"
natOutputChain = "nat-output"
natPostroutingChain = "nat-postrouting"
// service dispatch // service dispatch
kubeServicesChain = "services" servicesChain = "services"
kubeServiceIPsMap = "service-ips" serviceIPsMap = "service-ips"
kubeServiceNodePortsMap = "service-nodeports" serviceNodePortsMap = "service-nodeports"
// set of IPs that accept NodePort traffic // set of IPs that accept NodePort traffic
kubeNodePortIPsSet = "nodeport-ips" nodePortIPsSet = "nodeport-ips"
// set of active ClusterIPs.
clusterIPsSet = "cluster-ips"
// handling for services with no endpoints // handling for services with no endpoints
kubeEndpointsCheckChain = "endpoints-check" serviceEndpointsCheckChain = "service-endpoints-check"
kubeNoEndpointServicesMap = "no-endpoint-services" nodePortEndpointsCheckChain = "nodeport-endpoints-check"
kubeNoEndpointNodePortsMap = "no-endpoint-nodeports" noEndpointServicesMap = "no-endpoint-services"
kubeRejectChain = "reject-chain" noEndpointNodePortsMap = "no-endpoint-nodeports"
rejectChain = "reject-chain"
// handling traffic to unallocated ClusterIPs and undefined ports of ClusterIPs
clusterIPsCheckChain = "cluster-ips-check"
// LoadBalancerSourceRanges handling // LoadBalancerSourceRanges handling
kubeFirewallIPsMap = "firewall-ips" firewallIPsMap = "firewall-ips"
kubeFirewallCheckChain = "firewall-check" firewallCheckChain = "firewall-check"
// masquerading // masquerading
kubeMarkMasqChain = "mark-for-masquerade" markMasqChain = "mark-for-masquerade"
kubeMasqueradingChain = "masquerading" masqueradingChain = "masquerading"
) )
// internal struct for string service information // internal struct for string service information
@ -179,6 +197,10 @@ type Proxier struct {
// staleChains contains information about chains to be deleted later // staleChains contains information about chains to be deleted later
staleChains map[string]time.Time staleChains map[string]time.Time
// serviceCIDRs is a comma separated list of ServiceCIDRs belonging to the IPFamily
// which proxier is operating on, can be directly consumed by knftables.
serviceCIDRs string
} }
// Proxier implements proxy.Provider // Proxier implements proxy.Provider
@ -309,13 +331,14 @@ type nftablesBaseChain struct {
var nftablesBaseChains = []nftablesBaseChain{ var nftablesBaseChains = []nftablesBaseChain{
// We want our filtering rules to operate on pre-DNAT dest IPs, so our filter // We want our filtering rules to operate on pre-DNAT dest IPs, so our filter
// chains have to run before DNAT. // chains have to run before DNAT.
{"filter-prerouting", knftables.FilterType, knftables.PreroutingHook, knftables.DNATPriority + "-10"}, {filterPreroutingChain, knftables.FilterType, knftables.PreroutingHook, knftables.DNATPriority + "-10"},
{"filter-input", knftables.FilterType, knftables.InputHook, knftables.DNATPriority + "-10"}, {filterInputChain, knftables.FilterType, knftables.InputHook, knftables.DNATPriority + "-10"},
{"filter-forward", knftables.FilterType, knftables.ForwardHook, knftables.DNATPriority + "-10"}, {filterForwardChain, knftables.FilterType, knftables.ForwardHook, knftables.DNATPriority + "-10"},
{"filter-output", knftables.FilterType, knftables.OutputHook, knftables.DNATPriority + "-10"}, {filterOutputChain, knftables.FilterType, knftables.OutputHook, knftables.DNATPriority + "-10"},
{"nat-prerouting", knftables.NATType, knftables.PreroutingHook, knftables.DNATPriority}, {filterOutputPostDNATChain, knftables.FilterType, knftables.OutputHook, knftables.DNATPriority + "+10"},
{"nat-output", knftables.NATType, knftables.OutputHook, knftables.DNATPriority}, {natPreroutingChain, knftables.NATType, knftables.PreroutingHook, knftables.DNATPriority},
{"nat-postrouting", knftables.NATType, knftables.PostroutingHook, knftables.SNATPriority}, {natOutputChain, knftables.NATType, knftables.OutputHook, knftables.DNATPriority},
{natPostroutingChain, knftables.NATType, knftables.PostroutingHook, knftables.SNATPriority},
} }
// nftablesJumpChains lists our top-level "regular chains" that are jumped to directly // nftablesJumpChains lists our top-level "regular chains" that are jumped to directly
@ -328,19 +351,23 @@ type nftablesJumpChain struct {
} }
var nftablesJumpChains = []nftablesJumpChain{ var nftablesJumpChains = []nftablesJumpChain{
// We can't jump to kubeEndpointsCheckChain from filter-prerouting like // We can't jump to endpointsCheckChain from filter-prerouting like
// kubeFirewallCheckChain because reject action is only valid in chains using the // firewallCheckChain because reject action is only valid in chains using the
// input, forward or output hooks. // input, forward or output hooks.
{kubeEndpointsCheckChain, "filter-input", "ct state new"}, {nodePortEndpointsCheckChain, filterInputChain, "ct state new"},
{kubeEndpointsCheckChain, "filter-forward", "ct state new"}, {serviceEndpointsCheckChain, filterInputChain, "ct state new"},
{kubeEndpointsCheckChain, "filter-output", "ct state new"}, {serviceEndpointsCheckChain, filterForwardChain, "ct state new"},
{serviceEndpointsCheckChain, filterOutputChain, "ct state new"},
{kubeFirewallCheckChain, "filter-prerouting", "ct state new"}, {firewallCheckChain, filterPreroutingChain, "ct state new"},
{kubeFirewallCheckChain, "filter-output", "ct state new"}, {firewallCheckChain, filterOutputChain, "ct state new"},
{kubeServicesChain, "nat-output", ""}, {servicesChain, natOutputChain, ""},
{kubeServicesChain, "nat-prerouting", ""}, {servicesChain, natPreroutingChain, ""},
{kubeMasqueradingChain, "nat-postrouting", ""}, {masqueradingChain, natPostroutingChain, ""},
{clusterIPsCheckChain, filterForwardChain, "ct state new"},
{clusterIPsCheckChain, filterOutputPostDNATChain, "ct state new"},
} }
// ensureChain adds commands to tx to ensure that chain exists and doesn't contain // ensureChain adds commands to tx to ensure that chain exists and doesn't contain
@ -399,51 +426,79 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) {
} }
// Ensure all of our other "top-level" chains exist // Ensure all of our other "top-level" chains exist
for _, chain := range []string{kubeServicesChain, kubeMasqueradingChain, kubeMarkMasqChain} { for _, chain := range []string{servicesChain, clusterIPsCheckChain, masqueradingChain, markMasqChain} {
ensureChain(chain, tx, createdChains) ensureChain(chain, tx, createdChains)
} }
// Add the rules in the mark-for-masquerade and masquerading chains // Add the rules in the mark-for-masquerade and masquerading chains
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeMarkMasqChain, Chain: markMasqChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"mark", "set", "mark", "or", proxier.masqueradeMark, "mark", "set", "mark", "or", proxier.masqueradeMark,
), ),
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeMasqueradingChain, Chain: masqueradingChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"mark", "and", proxier.masqueradeMark, "==", "0", "mark", "and", proxier.masqueradeMark, "==", "0",
"return", "return",
), ),
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeMasqueradingChain, Chain: masqueradingChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"mark", "set", "mark", "xor", proxier.masqueradeMark, "mark", "set", "mark", "xor", proxier.masqueradeMark,
), ),
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeMasqueradingChain, Chain: masqueradingChain,
Rule: "masquerade fully-random", Rule: "masquerade fully-random",
}) })
// add cluster-ips set.
tx.Add(&knftables.Set{
Name: clusterIPsSet,
Type: ipvX_addr,
Comment: ptr.To("Active ClusterIPs"),
})
// reject traffic to invalid ports of ClusterIPs.
tx.Add(&knftables.Rule{
Chain: clusterIPsCheckChain,
Rule: knftables.Concat(
ipX, "daddr", "@", clusterIPsSet, "reject",
),
Comment: ptr.To("Reject traffic to invalid ports of ClusterIPs"),
})
// drop traffic to unallocated ClusterIPs.
if len(proxier.serviceCIDRs) > 0 {
tx.Add(&knftables.Rule{
Chain: clusterIPsCheckChain,
Rule: knftables.Concat(
ipX, "daddr", "{", proxier.serviceCIDRs, "}",
"drop",
),
Comment: ptr.To("Drop traffic to unallocated ClusterIPs"),
})
}
// Fill in nodeport-ips set if needed (or delete it if not). (We do "add+delete" // Fill in nodeport-ips set if needed (or delete it if not). (We do "add+delete"
// rather than just "delete" when we want to ensure the set doesn't exist, because // rather than just "delete" when we want to ensure the set doesn't exist, because
// doing just "delete" would return an error if the set didn't exist.) // doing just "delete" would return an error if the set didn't exist.)
tx.Add(&knftables.Set{ tx.Add(&knftables.Set{
Name: kubeNodePortIPsSet, Name: nodePortIPsSet,
Type: ipvX_addr, Type: ipvX_addr,
Comment: ptr.To("IPs that accept NodePort traffic"), Comment: ptr.To("IPs that accept NodePort traffic"),
}) })
if proxier.nodePortAddresses.MatchAll() { if proxier.nodePortAddresses.MatchAll() {
tx.Delete(&knftables.Set{ tx.Delete(&knftables.Set{
Name: kubeNodePortIPsSet, Name: nodePortIPsSet,
}) })
} else { } else {
tx.Flush(&knftables.Set{ tx.Flush(&knftables.Set{
Name: kubeNodePortIPsSet, Name: nodePortIPsSet,
}) })
nodeIPs, err := proxier.nodePortAddresses.GetNodeIPs(proxier.networkInterfacer) nodeIPs, err := proxier.nodePortAddresses.GetNodeIPs(proxier.networkInterfacer)
if err != nil { if err != nil {
@ -455,7 +510,7 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) {
continue continue
} }
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Set: kubeNodePortIPsSet, Set: nodePortIPsSet,
Key: []string{ Key: []string{
ip.String(), ip.String(),
}, },
@ -465,108 +520,107 @@ func (proxier *Proxier) setupNFTables(tx *knftables.Transaction) {
// Set up "no endpoints" drop/reject handling // Set up "no endpoints" drop/reject handling
tx.Add(&knftables.Map{ tx.Add(&knftables.Map{
Name: kubeNoEndpointServicesMap, Name: noEndpointServicesMap,
Type: ipvX_addr + " . inet_proto . inet_service : verdict", Type: ipvX_addr + " . inet_proto . inet_service : verdict",
Comment: ptr.To("vmap to drop or reject packets to services with no endpoints"), Comment: ptr.To("vmap to drop or reject packets to services with no endpoints"),
}) })
tx.Add(&knftables.Map{ tx.Add(&knftables.Map{
Name: kubeNoEndpointNodePortsMap, Name: noEndpointNodePortsMap,
Type: "inet_proto . inet_service : verdict", Type: "inet_proto . inet_service : verdict",
Comment: ptr.To("vmap to drop or reject packets to service nodeports with no endpoints"), Comment: ptr.To("vmap to drop or reject packets to service nodeports with no endpoints"),
}) })
tx.Add(&knftables.Chain{ tx.Add(&knftables.Chain{
Name: kubeRejectChain, Name: rejectChain,
Comment: ptr.To("helper for @no-endpoint-services / @no-endpoint-nodeports"), Comment: ptr.To("helper for @no-endpoint-services / @no-endpoint-nodeports"),
}) })
tx.Flush(&knftables.Chain{ tx.Flush(&knftables.Chain{
Name: kubeRejectChain, Name: rejectChain,
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeRejectChain, Chain: rejectChain,
Rule: "reject", Rule: "reject",
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeEndpointsCheckChain, Chain: serviceEndpointsCheckChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr", ".", "meta l4proto", ".", "th dport", ipX, "daddr", ".", "meta l4proto", ".", "th dport",
"vmap", "@", kubeNoEndpointServicesMap, "vmap", "@", noEndpointServicesMap,
), ),
}) })
if proxier.nodePortAddresses.MatchAll() { if proxier.nodePortAddresses.MatchAll() {
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeEndpointsCheckChain, Chain: nodePortEndpointsCheckChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"fib daddr type local",
noLocalhost, noLocalhost,
"meta l4proto . th dport", "meta l4proto . th dport",
"vmap", "@", kubeNoEndpointNodePortsMap, "vmap", "@", noEndpointNodePortsMap,
), ),
}) })
} else { } else {
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeEndpointsCheckChain, Chain: nodePortEndpointsCheckChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr", "@", kubeNodePortIPsSet, ipX, "daddr", "@", nodePortIPsSet,
"meta l4proto . th dport", "meta l4proto . th dport",
"vmap", "@", kubeNoEndpointNodePortsMap, "vmap", "@", noEndpointNodePortsMap,
), ),
}) })
} }
// Set up LoadBalancerSourceRanges firewalling // Set up LoadBalancerSourceRanges firewalling
tx.Add(&knftables.Map{ tx.Add(&knftables.Map{
Name: kubeFirewallIPsMap, Name: firewallIPsMap,
Type: ipvX_addr + " . inet_proto . inet_service : verdict", Type: ipvX_addr + " . inet_proto . inet_service : verdict",
Comment: ptr.To("destinations that are subject to LoadBalancerSourceRanges"), Comment: ptr.To("destinations that are subject to LoadBalancerSourceRanges"),
}) })
ensureChain(kubeFirewallCheckChain, tx, createdChains) ensureChain(firewallCheckChain, tx, createdChains)
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeFirewallCheckChain, Chain: firewallCheckChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr", ".", "meta l4proto", ".", "th dport", ipX, "daddr", ".", "meta l4proto", ".", "th dport",
"vmap", "@", kubeFirewallIPsMap, "vmap", "@", firewallIPsMap,
), ),
}) })
// Set up service dispatch // Set up service dispatch
tx.Add(&knftables.Map{ tx.Add(&knftables.Map{
Name: kubeServiceIPsMap, Name: serviceIPsMap,
Type: ipvX_addr + " . inet_proto . inet_service : verdict", Type: ipvX_addr + " . inet_proto . inet_service : verdict",
Comment: ptr.To("ClusterIP, ExternalIP and LoadBalancer IP traffic"), Comment: ptr.To("ClusterIP, ExternalIP and LoadBalancer IP traffic"),
}) })
tx.Add(&knftables.Map{ tx.Add(&knftables.Map{
Name: kubeServiceNodePortsMap, Name: serviceNodePortsMap,
Type: "inet_proto . inet_service : verdict", Type: "inet_proto . inet_service : verdict",
Comment: ptr.To("NodePort traffic"), Comment: ptr.To("NodePort traffic"),
}) })
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeServicesChain, Chain: servicesChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr", ".", "meta l4proto", ".", "th dport", ipX, "daddr", ".", "meta l4proto", ".", "th dport",
"vmap", "@", kubeServiceIPsMap, "vmap", "@", serviceIPsMap,
), ),
}) })
if proxier.nodePortAddresses.MatchAll() { if proxier.nodePortAddresses.MatchAll() {
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeServicesChain, Chain: servicesChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"fib daddr type local", "fib daddr type local",
noLocalhost, noLocalhost,
"meta l4proto . th dport", "meta l4proto . th dport",
"vmap", "@", kubeServiceNodePortsMap, "vmap", "@", serviceNodePortsMap,
), ),
}) })
} else { } else {
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: kubeServicesChain, Chain: servicesChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr @nodeport-ips", ipX, "daddr @nodeport-ips",
"meta l4proto . th dport", "meta l4proto . th dport",
"vmap", "@", kubeServiceNodePortsMap, "vmap", "@", serviceNodePortsMap,
), ),
}) })
} }
@ -764,6 +818,26 @@ func (proxier *Proxier) OnNodeDelete(node *v1.Node) {
func (proxier *Proxier) OnNodeSynced() { func (proxier *Proxier) OnNodeSynced() {
} }
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
func (proxier *Proxier) OnServiceCIDRsChanged(cidrs []string) {
proxier.mu.Lock()
defer proxier.mu.Unlock()
cidrsForProxier := make([]string, 0)
for _, cidr := range cidrs {
isIPv4CIDR := netutils.IsIPv4CIDRString(cidr)
if proxier.ipFamily == v1.IPv4Protocol && isIPv4CIDR {
cidrsForProxier = append(cidrsForProxier, cidr)
}
if proxier.ipFamily == v1.IPv6Protocol && !isIPv4CIDR {
cidrsForProxier = append(cidrsForProxier, cidr)
}
}
proxier.serviceCIDRs = strings.Join(cidrsForProxier, ",")
}
const ( const (
// Maximum length for one of our chain name prefixes, including the trailing // Maximum length for one of our chain name prefixes, including the trailing
// hyphen. // hyphen.
@ -955,20 +1029,23 @@ func (proxier *Proxier) syncProxyRules() {
} }
// We currently fully-rebuild our sets and maps on each resync // We currently fully-rebuild our sets and maps on each resync
tx.Flush(&knftables.Map{ tx.Flush(&knftables.Set{
Name: kubeFirewallIPsMap, Name: clusterIPsSet,
}) })
tx.Flush(&knftables.Map{ tx.Flush(&knftables.Map{
Name: kubeNoEndpointServicesMap, Name: firewallIPsMap,
}) })
tx.Flush(&knftables.Map{ tx.Flush(&knftables.Map{
Name: kubeNoEndpointNodePortsMap, Name: noEndpointServicesMap,
}) })
tx.Flush(&knftables.Map{ tx.Flush(&knftables.Map{
Name: kubeServiceIPsMap, Name: noEndpointNodePortsMap,
}) })
tx.Flush(&knftables.Map{ tx.Flush(&knftables.Map{
Name: kubeServiceNodePortsMap, Name: serviceIPsMap,
})
tx.Flush(&knftables.Map{
Name: serviceNodePortsMap,
}) })
// Accumulate service/endpoint chains and affinity sets to keep. // Accumulate service/endpoint chains and affinity sets to keep.
@ -1074,8 +1151,8 @@ func (proxier *Proxier) syncProxyRules() {
// generate any chains in the "nat" table for the service; only // generate any chains in the "nat" table for the service; only
// rules in the "filter" table rejecting incoming packets for // rules in the "filter" table rejecting incoming packets for
// the service's IPs. // the service's IPs.
internalTrafficFilterVerdict = fmt.Sprintf("goto %s", kubeRejectChain) internalTrafficFilterVerdict = fmt.Sprintf("goto %s", rejectChain)
externalTrafficFilterVerdict = fmt.Sprintf("goto %s", kubeRejectChain) externalTrafficFilterVerdict = fmt.Sprintf("goto %s", rejectChain)
} else { } else {
if !hasInternalEndpoints { if !hasInternalEndpoints {
// The internalTrafficPolicy is "Local" but there are no local // The internalTrafficPolicy is "Local" but there are no local
@ -1095,9 +1172,13 @@ func (proxier *Proxier) syncProxyRules() {
} }
// Capture the clusterIP. // Capture the clusterIP.
tx.Add(&knftables.Element{
Set: clusterIPsSet,
Key: []string{svcInfo.ClusterIP().String()},
})
if hasInternalEndpoints { if hasInternalEndpoints {
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeServiceIPsMap, Map: serviceIPsMap,
Key: []string{ Key: []string{
svcInfo.ClusterIP().String(), svcInfo.ClusterIP().String(),
protocol, protocol,
@ -1110,7 +1191,7 @@ func (proxier *Proxier) syncProxyRules() {
} else { } else {
// No endpoints. // No endpoints.
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeNoEndpointServicesMap, Map: noEndpointServicesMap,
Key: []string{ Key: []string{
svcInfo.ClusterIP().String(), svcInfo.ClusterIP().String(),
protocol, protocol,
@ -1129,7 +1210,7 @@ func (proxier *Proxier) syncProxyRules() {
// Send traffic bound for external IPs to the "external // Send traffic bound for external IPs to the "external
// destinations" chain. // destinations" chain.
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeServiceIPsMap, Map: serviceIPsMap,
Key: []string{ Key: []string{
externalIP.String(), externalIP.String(),
protocol, protocol,
@ -1145,7 +1226,7 @@ func (proxier *Proxier) syncProxyRules() {
// external traffic (DROP anything that didn't get // external traffic (DROP anything that didn't get
// short-circuited by the EXT chain.) // short-circuited by the EXT chain.)
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeNoEndpointServicesMap, Map: noEndpointServicesMap,
Key: []string{ Key: []string{
externalIP.String(), externalIP.String(),
protocol, protocol,
@ -1197,7 +1278,7 @@ func (proxier *Proxier) syncProxyRules() {
for _, lbip := range svcInfo.LoadBalancerVIPs() { for _, lbip := range svcInfo.LoadBalancerVIPs() {
if hasEndpoints { if hasEndpoints {
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeServiceIPsMap, Map: serviceIPsMap,
Key: []string{ Key: []string{
lbip.String(), lbip.String(),
protocol, protocol,
@ -1211,7 +1292,7 @@ func (proxier *Proxier) syncProxyRules() {
if usesFWChain { if usesFWChain {
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeFirewallIPsMap, Map: firewallIPsMap,
Key: []string{ Key: []string{
lbip.String(), lbip.String(),
protocol, protocol,
@ -1230,7 +1311,7 @@ func (proxier *Proxier) syncProxyRules() {
// by the EXT chain.) // by the EXT chain.)
for _, lbip := range svcInfo.LoadBalancerVIPs() { for _, lbip := range svcInfo.LoadBalancerVIPs() {
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeNoEndpointServicesMap, Map: noEndpointServicesMap,
Key: []string{ Key: []string{
lbip.String(), lbip.String(),
protocol, protocol,
@ -1251,7 +1332,7 @@ func (proxier *Proxier) syncProxyRules() {
// worse, nodeports are not subect to loadBalancerSourceRanges, // worse, nodeports are not subect to loadBalancerSourceRanges,
// and we can't change that. // and we can't change that.
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeServiceNodePortsMap, Map: serviceNodePortsMap,
Key: []string{ Key: []string{
protocol, protocol,
strconv.Itoa(svcInfo.NodePort()), strconv.Itoa(svcInfo.NodePort()),
@ -1266,7 +1347,7 @@ func (proxier *Proxier) syncProxyRules() {
// external traffic (DROP anything that didn't get // external traffic (DROP anything that didn't get
// short-circuited by the EXT chain.) // short-circuited by the EXT chain.)
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeNoEndpointNodePortsMap, Map: noEndpointNodePortsMap,
Key: []string{ Key: []string{
protocol, protocol,
strconv.Itoa(svcInfo.NodePort()), strconv.Itoa(svcInfo.NodePort()),
@ -1287,7 +1368,7 @@ func (proxier *Proxier) syncProxyRules() {
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "daddr", svcInfo.ClusterIP(), ipX, "daddr", svcInfo.ClusterIP(),
protocol, "dport", svcInfo.Port(), protocol, "dport", svcInfo.Port(),
"jump", kubeMarkMasqChain, "jump", markMasqChain,
), ),
}) })
} else if proxier.localDetector.IsImplemented() { } else if proxier.localDetector.IsImplemented() {
@ -1302,7 +1383,7 @@ func (proxier *Proxier) syncProxyRules() {
ipX, "daddr", svcInfo.ClusterIP(), ipX, "daddr", svcInfo.ClusterIP(),
protocol, "dport", svcInfo.Port(), protocol, "dport", svcInfo.Port(),
proxier.localDetector.IfNotLocalNFT(), proxier.localDetector.IfNotLocalNFT(),
"jump", kubeMarkMasqChain, "jump", markMasqChain,
), ),
}) })
} }
@ -1319,7 +1400,7 @@ func (proxier *Proxier) syncProxyRules() {
tx.Add(&knftables.Rule{ tx.Add(&knftables.Rule{
Chain: externalTrafficChain, Chain: externalTrafficChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"jump", kubeMarkMasqChain, "jump", markMasqChain,
), ),
}) })
} else { } else {
@ -1348,7 +1429,7 @@ func (proxier *Proxier) syncProxyRules() {
Chain: externalTrafficChain, Chain: externalTrafficChain,
Rule: knftables.Concat( Rule: knftables.Concat(
"fib", "saddr", "type", "local", "fib", "saddr", "type", "local",
"jump", kubeMarkMasqChain, "jump", markMasqChain,
), ),
Comment: ptr.To("masquerade local traffic"), Comment: ptr.To("masquerade local traffic"),
}) })
@ -1441,7 +1522,7 @@ func (proxier *Proxier) syncProxyRules() {
Chain: endpointChain, Chain: endpointChain,
Rule: knftables.Concat( Rule: knftables.Concat(
ipX, "saddr", epInfo.IP(), ipX, "saddr", epInfo.IP(),
"jump", kubeMarkMasqChain, "jump", markMasqChain,
), ),
}) })

View File

@ -28,6 +28,8 @@ import (
"github.com/danwinship/knftables" "github.com/danwinship/knftables"
"github.com/lithammer/dedent" "github.com/lithammer/dedent"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
discovery "k8s.io/api/discovery/v1" discovery "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -40,9 +42,8 @@ import (
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/proxy" "k8s.io/kubernetes/pkg/proxy"
"k8s.io/kubernetes/pkg/proxy/conntrack" "k8s.io/kubernetes/pkg/proxy/conntrack"
"k8s.io/kubernetes/pkg/proxy/metrics"
"k8s.io/kubernetes/pkg/proxy/healthcheck" "k8s.io/kubernetes/pkg/proxy/healthcheck"
"k8s.io/kubernetes/pkg/proxy/metrics"
proxyutil "k8s.io/kubernetes/pkg/proxy/util" proxyutil "k8s.io/kubernetes/pkg/proxy/util"
proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables" proxyutiliptables "k8s.io/kubernetes/pkg/proxy/util/iptables"
proxyutiltest "k8s.io/kubernetes/pkg/proxy/util/testing" proxyutiltest "k8s.io/kubernetes/pkg/proxy/util/testing"
@ -78,9 +79,11 @@ func NewFakeProxier(ipFamily v1.IPFamily) (*knftables.Fake, *Proxier) {
// invocation into a Run() method. // invocation into a Run() method.
nftablesFamily := knftables.IPv4Family nftablesFamily := knftables.IPv4Family
podCIDR := "10.0.0.0/8" podCIDR := "10.0.0.0/8"
serviceCIDRs := "172.30.0.0/16"
if ipFamily == v1.IPv6Protocol { if ipFamily == v1.IPv6Protocol {
nftablesFamily = knftables.IPv6Family nftablesFamily = knftables.IPv6Family
podCIDR = "fd00:10::/64" podCIDR = "fd00:10::/64"
serviceCIDRs = "fd00:10:96::/112"
} }
detectLocal, _ := proxyutiliptables.NewDetectLocalByCIDR(podCIDR) detectLocal, _ := proxyutiliptables.NewDetectLocalByCIDR(podCIDR)
@ -119,6 +122,7 @@ func NewFakeProxier(ipFamily v1.IPFamily) (*knftables.Fake, *Proxier) {
nodePortAddresses: proxyutil.NewNodePortAddresses(ipFamily, nil), nodePortAddresses: proxyutil.NewNodePortAddresses(ipFamily, nil),
networkInterfacer: networkInterfacer, networkInterfacer: networkInterfacer,
staleChains: make(map[string]time.Time), staleChains: make(map[string]time.Time),
serviceCIDRs: serviceCIDRs,
} }
p.setInitialized(true) p.setInitialized(true)
p.syncRunner = async.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, time.Minute, 1) p.syncRunner = async.NewBoundedFrequencyRunner("test-sync-runner", p.syncProxyRules, 0, time.Minute, 1)
@ -303,12 +307,16 @@ func TestOverallNFTablesRules(t *testing.T) {
add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; } add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; }
add rule ip kube-proxy filter-prerouting ct state new jump firewall-check add rule ip kube-proxy filter-prerouting ct state new jump firewall-check
add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; } add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; }
add rule ip kube-proxy filter-forward ct state new jump endpoints-check add rule ip kube-proxy filter-forward ct state new jump service-endpoints-check
add rule ip kube-proxy filter-forward ct state new jump cluster-ips-check
add chain ip kube-proxy filter-input { type filter hook input priority -110 ; } add chain ip kube-proxy filter-input { type filter hook input priority -110 ; }
add rule ip kube-proxy filter-input ct state new jump endpoints-check add rule ip kube-proxy filter-input ct state new jump nodeport-endpoints-check
add rule ip kube-proxy filter-input ct state new jump service-endpoints-check
add chain ip kube-proxy filter-output { type filter hook output priority -110 ; } add chain ip kube-proxy filter-output { type filter hook output priority -110 ; }
add rule ip kube-proxy filter-output ct state new jump endpoints-check add rule ip kube-proxy filter-output ct state new jump service-endpoints-check
add rule ip kube-proxy filter-output ct state new jump firewall-check add rule ip kube-proxy filter-output ct state new jump firewall-check
add chain ip kube-proxy filter-output-post-dnat { type filter hook output priority -90 ; }
add rule ip kube-proxy filter-output-post-dnat ct state new jump cluster-ips-check
add chain ip kube-proxy nat-output { type nat hook output priority -100 ; } add chain ip kube-proxy nat-output { type nat hook output priority -100 ; }
add rule ip kube-proxy nat-output jump services add rule ip kube-proxy nat-output jump services
add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; } add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; }
@ -316,6 +324,11 @@ func TestOverallNFTablesRules(t *testing.T) {
add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; } add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; }
add rule ip kube-proxy nat-prerouting jump services add rule ip kube-proxy nat-prerouting jump services
add set ip kube-proxy cluster-ips { type ipv4_addr ; comment "Active ClusterIPs" ; }
add chain ip kube-proxy cluster-ips-check
add rule ip kube-proxy cluster-ips-check ip daddr @cluster-ips reject comment "Reject traffic to invalid ports of ClusterIPs"
add rule ip kube-proxy cluster-ips-check ip daddr { 172.30.0.0/16 } drop comment "Drop traffic to unallocated ClusterIPs"
add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; }
add chain ip kube-proxy firewall-check add chain ip kube-proxy firewall-check
add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips
@ -326,9 +339,10 @@ func TestOverallNFTablesRules(t *testing.T) {
add map ip kube-proxy no-endpoint-services { type ipv4_addr . inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to services with no endpoints" ; } add map ip kube-proxy no-endpoint-services { type ipv4_addr . inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to services with no endpoints" ; }
add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; } add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; }
add chain ip kube-proxy endpoints-check add chain ip kube-proxy nodeport-endpoints-check
add rule ip kube-proxy endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services add rule ip kube-proxy nodeport-endpoints-check ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports
add rule ip kube-proxy endpoints-check fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports add chain ip kube-proxy service-endpoints-check
add rule ip kube-proxy service-endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services
add map ip kube-proxy service-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "ClusterIP, ExternalIP and LoadBalancer IP traffic" ; } add map ip kube-proxy service-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "ClusterIP, ExternalIP and LoadBalancer IP traffic" ; }
add map ip kube-proxy service-nodeports { type inet_proto . inet_service : verdict ; comment "NodePort traffic" ; } add map ip kube-proxy service-nodeports { type inet_proto . inet_service : verdict ; comment "NodePort traffic" ; }
@ -344,6 +358,7 @@ func TestOverallNFTablesRules(t *testing.T) {
add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 ip saddr 10.180.0.1 jump mark-for-masquerade
add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80 add rule ip kube-proxy endpoint-5OJB2KTY-ns1/svc1/tcp/p80__10.180.0.1/80 meta l4proto tcp dnat to 10.180.0.1:80
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
# svc2 # svc2
@ -358,6 +373,7 @@ func TestOverallNFTablesRules(t *testing.T) {
add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 ip saddr 10.180.0.2 jump mark-for-masquerade
add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80 add rule ip kube-proxy endpoint-SGOXE6O3-ns2/svc2/tcp/p80__10.180.0.2/80 meta l4proto tcp dnat to 10.180.0.2:80
add element ip kube-proxy cluster-ips { 172.30.0.42 }
add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 80 : goto service-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy service-ips { 192.168.99.22 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 } add element ip kube-proxy service-ips { 192.168.99.22 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 }
add element ip kube-proxy service-ips { 1.2.3.4 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 } add element ip kube-proxy service-ips { 1.2.3.4 . tcp . 80 : goto external-42NFTM6N-ns2/svc2/tcp/p80 }
@ -378,6 +394,7 @@ func TestOverallNFTablesRules(t *testing.T) {
add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 ip saddr 10.180.0.3 jump mark-for-masquerade
add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80 add rule ip kube-proxy endpoint-UEIP74TE-ns3/svc3/tcp/p80__10.180.0.3/80 meta l4proto tcp dnat to 10.180.0.3:80
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3003 : goto external-4AT6LBPK-ns3/svc3/tcp/p80 }
@ -395,6 +412,7 @@ func TestOverallNFTablesRules(t *testing.T) {
add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 ip saddr 10.180.0.4 jump mark-for-masquerade
add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80 add rule ip kube-proxy endpoint-UNZV3OEC-ns4/svc4/tcp/p80__10.180.0.4/80 meta l4proto tcp dnat to 10.180.0.4:80
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
add element ip kube-proxy service-ips { 192.168.99.33 . tcp . 80 : goto external-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 192.168.99.33 . tcp . 80 : goto external-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -416,12 +434,14 @@ func TestOverallNFTablesRules(t *testing.T) {
add chain ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 add chain ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80
add rule ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr != { 203.0.113.0/25 } drop add rule ip kube-proxy firewall-HVFWP5L3-ns5/svc5/tcp/p80 ip saddr != { 203.0.113.0/25 } drop
add element ip kube-proxy cluster-ips { 172.30.0.45 }
add element ip kube-proxy service-ips { 172.30.0.45 . tcp . 80 : goto service-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.45 . tcp . 80 : goto service-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy service-ips { 5.6.7.8 . tcp . 80 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy service-ips { 5.6.7.8 . tcp . 80 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy service-nodeports { tcp . 3002 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy service-nodeports { tcp . 3002 : goto external-HVFWP5L3-ns5/svc5/tcp/p80 }
add element ip kube-proxy firewall-ips { 5.6.7.8 . tcp . 80 comment "ns5/svc5:p80" : goto firewall-HVFWP5L3-ns5/svc5/tcp/p80 } add element ip kube-proxy firewall-ips { 5.6.7.8 . tcp . 80 comment "ns5/svc5:p80" : goto firewall-HVFWP5L3-ns5/svc5/tcp/p80 }
# svc6 # svc6
add element ip kube-proxy cluster-ips { 172.30.0.46 }
add element ip kube-proxy no-endpoint-services { 172.30.0.46 . tcp . 80 comment "ns6/svc6:p80" : goto reject-chain } add element ip kube-proxy no-endpoint-services { 172.30.0.46 . tcp . 80 comment "ns6/svc6:p80" : goto reject-chain }
`) `)
@ -678,14 +698,21 @@ func TestClusterIPGeneral(t *testing.T) {
protocol: v1.ProtocolUDP, protocol: v1.ProtocolUDP,
destIP: "172.30.0.42", destIP: "172.30.0.42",
destPort: 80, destPort: 80,
output: "", output: "REJECT",
}, },
{ {
name: "svc1 does not accept svc2's ports", name: "svc1 does not accept svc2's ports",
sourceIP: "10.180.0.2", sourceIP: "10.180.0.2",
destIP: "172.30.0.41", destIP: "172.30.0.41",
destPort: 443, destPort: 443,
output: "", output: "REJECT",
},
{
name: "packet to unallocated cluster ip",
sourceIP: "10.180.0.2",
destIP: "172.30.0.50",
destPort: 80,
output: "DROP",
}, },
}) })
} }
@ -3919,27 +3946,33 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
baseRules := dedent.Dedent(` baseRules := dedent.Dedent(`
add table ip kube-proxy { comment "rules for kube-proxy" ; } add table ip kube-proxy { comment "rules for kube-proxy" ; }
add chain ip kube-proxy endpoints-check add chain ip kube-proxy cluster-ips-check
add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; } add chain ip kube-proxy filter-prerouting { type filter hook prerouting priority -110 ; }
add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; } add chain ip kube-proxy filter-forward { type filter hook forward priority -110 ; }
add chain ip kube-proxy filter-input { type filter hook input priority -110 ; } add chain ip kube-proxy filter-input { type filter hook input priority -110 ; }
add chain ip kube-proxy filter-output { type filter hook output priority -110 ; } add chain ip kube-proxy filter-output { type filter hook output priority -110 ; }
add chain ip kube-proxy filter-output-post-dnat { type filter hook output priority -90 ; }
add chain ip kube-proxy firewall-check add chain ip kube-proxy firewall-check
add chain ip kube-proxy mark-for-masquerade add chain ip kube-proxy mark-for-masquerade
add chain ip kube-proxy masquerading add chain ip kube-proxy masquerading
add chain ip kube-proxy nat-output { type nat hook output priority -100 ; } add chain ip kube-proxy nat-output { type nat hook output priority -100 ; }
add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; } add chain ip kube-proxy nat-postrouting { type nat hook postrouting priority 100 ; }
add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; } add chain ip kube-proxy nat-prerouting { type nat hook prerouting priority -100 ; }
add chain ip kube-proxy nodeport-endpoints-check
add chain ip kube-proxy reject-chain { comment "helper for @no-endpoint-services / @no-endpoint-nodeports" ; } add chain ip kube-proxy reject-chain { comment "helper for @no-endpoint-services / @no-endpoint-nodeports" ; }
add chain ip kube-proxy services add chain ip kube-proxy services
add chain ip kube-proxy service-endpoints-check
add rule ip kube-proxy endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services add rule ip kube-proxy cluster-ips-check ip daddr @cluster-ips reject comment "Reject traffic to invalid ports of ClusterIPs"
add rule ip kube-proxy endpoints-check fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports add rule ip kube-proxy cluster-ips-check ip daddr { 172.30.0.0/16 } drop comment "Drop traffic to unallocated ClusterIPs"
add rule ip kube-proxy filter-prerouting ct state new jump firewall-check add rule ip kube-proxy filter-prerouting ct state new jump firewall-check
add rule ip kube-proxy filter-forward ct state new jump endpoints-check add rule ip kube-proxy filter-forward ct state new jump service-endpoints-check
add rule ip kube-proxy filter-input ct state new jump endpoints-check add rule ip kube-proxy filter-forward ct state new jump cluster-ips-check
add rule ip kube-proxy filter-output ct state new jump endpoints-check add rule ip kube-proxy filter-input ct state new jump nodeport-endpoints-check
add rule ip kube-proxy filter-input ct state new jump service-endpoints-check
add rule ip kube-proxy filter-output ct state new jump service-endpoints-check
add rule ip kube-proxy filter-output ct state new jump firewall-check add rule ip kube-proxy filter-output ct state new jump firewall-check
add rule ip kube-proxy filter-output-post-dnat ct state new jump cluster-ips-check
add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips add rule ip kube-proxy firewall-check ip daddr . meta l4proto . th dport vmap @firewall-ips
add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000 add rule ip kube-proxy mark-for-masquerade mark set mark or 0x4000
add rule ip kube-proxy masquerading mark and 0x4000 == 0 return add rule ip kube-proxy masquerading mark and 0x4000 == 0 return
@ -3948,9 +3981,13 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
add rule ip kube-proxy nat-output jump services add rule ip kube-proxy nat-output jump services
add rule ip kube-proxy nat-postrouting jump masquerading add rule ip kube-proxy nat-postrouting jump masquerading
add rule ip kube-proxy nat-prerouting jump services add rule ip kube-proxy nat-prerouting jump services
add rule ip kube-proxy nodeport-endpoints-check ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @no-endpoint-nodeports
add rule ip kube-proxy reject-chain reject add rule ip kube-proxy reject-chain reject
add rule ip kube-proxy services ip daddr . meta l4proto . th dport vmap @service-ips add rule ip kube-proxy services ip daddr . meta l4proto . th dport vmap @service-ips
add rule ip kube-proxy services fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @service-nodeports add rule ip kube-proxy services fib daddr type local ip daddr != 127.0.0.0/8 meta l4proto . th dport vmap @service-nodeports
add rule ip kube-proxy service-endpoints-check ip daddr . meta l4proto . th dport vmap @no-endpoint-services
add set ip kube-proxy cluster-ips { type ipv4_addr ; comment "Active ClusterIPs" ; }
add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; } add map ip kube-proxy firewall-ips { type ipv4_addr . inet_proto . inet_service : verdict ; comment "destinations that are subject to LoadBalancerSourceRanges" ; }
add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; } add map ip kube-proxy no-endpoint-nodeports { type inet_proto . inet_service : verdict ; comment "vmap to drop or reject packets to service nodeports with no endpoints" ; }
@ -4020,6 +4057,8 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.syncProxyRules() fp.syncProxyRules()
expected := baseRules + dedent.Dedent(` expected := baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.42 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 }
@ -4069,6 +4108,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.42 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 } add element ip kube-proxy service-ips { 172.30.0.42 . tcp . 8080 : goto service-MHHHYRWA-ns2/svc2/tcp/p8080 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
@ -4100,6 +4142,8 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.OnServiceDelete(svc2) fp.OnServiceDelete(svc2)
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
@ -4126,6 +4170,8 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
ageStaleChains() ageStaleChains()
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
@ -4159,6 +4205,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
) )
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
@ -4195,6 +4244,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
) )
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -4230,6 +4282,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
// The old endpoint chain (for 10.0.3.1) will not be deleted yet. // The old endpoint chain (for 10.0.3.1) will not be deleted yet.
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -4268,6 +4323,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -4304,6 +4362,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.OnEndpointSliceUpdate(eps3update2, eps3update3) fp.OnEndpointSliceUpdate(eps3update2, eps3update3)
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy no-endpoint-services { 172.30.0.43 . tcp . 80 comment "ns3/svc3:p80" : goto reject-chain } add element ip kube-proxy no-endpoint-services { 172.30.0.43 . tcp . 80 comment "ns3/svc3:p80" : goto reject-chain }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -4336,6 +4397,9 @@ func TestSyncProxyRulesRepeated(t *testing.T) {
fp.OnEndpointSliceUpdate(eps3update3, eps3update2) fp.OnEndpointSliceUpdate(eps3update3, eps3update2)
fp.syncProxyRules() fp.syncProxyRules()
expected = baseRules + dedent.Dedent(` expected = baseRules + dedent.Dedent(`
add element ip kube-proxy cluster-ips { 172.30.0.41 }
add element ip kube-proxy cluster-ips { 172.30.0.43 }
add element ip kube-proxy cluster-ips { 172.30.0.44 }
add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.41 . tcp . 80 : goto service-ULMVA6XW-ns1/svc1/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.43 . tcp . 80 : goto service-4AT6LBPK-ns3/svc3/tcp/p80 }
add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 } add element ip kube-proxy service-ips { 172.30.0.44 . tcp . 80 : goto service-LAUZTJTB-ns4/svc4/tcp/p80 }
@ -4808,3 +4872,21 @@ func Test_servicePortEndpointChainNameBase(t *testing.T) {
}) })
} }
} }
func TestProxier_OnServiceCIDRsChanged(t *testing.T) {
var proxier *Proxier
proxier = &Proxier{ipFamily: v1.IPv4Protocol}
proxier.OnServiceCIDRsChanged([]string{"172.30.0.0/16", "fd00:10:96::/112"})
assert.Equal(t, proxier.serviceCIDRs, "172.30.0.0/16")
proxier.OnServiceCIDRsChanged([]string{"172.30.0.0/16", "172.50.0.0/16", "fd00:10:96::/112", "fd00:172:30::/112"})
assert.Equal(t, proxier.serviceCIDRs, "172.30.0.0/16,172.50.0.0/16")
proxier = &Proxier{ipFamily: v1.IPv6Protocol}
proxier.OnServiceCIDRsChanged([]string{"172.30.0.0/16", "fd00:10:96::/112"})
assert.Equal(t, proxier.serviceCIDRs, "fd00:10:96::/112")
proxier.OnServiceCIDRsChanged([]string{"172.30.0.0/16", "172.50.0.0/16", "fd00:10:96::/112", "fd00:172:30::/112"})
assert.Equal(t, proxier.serviceCIDRs, "fd00:10:96::/112,fd00:172:30::/112")
}

View File

@ -29,6 +29,7 @@ type Provider interface {
config.EndpointSliceHandler config.EndpointSliceHandler
config.ServiceHandler config.ServiceHandler
config.NodeHandler config.NodeHandler
config.ServiceCIDRHandler
// Sync immediately synchronizes the Provider's current state to proxy rules. // Sync immediately synchronizes the Provider's current state to proxy rules.
Sync() Sync()

View File

@ -1009,6 +1009,10 @@ func (proxier *Proxier) OnEndpointSlicesSynced() {
proxier.syncProxyRules() proxier.syncProxyRules()
} }
// OnServiceCIDRsChanged is called whenever a change is observed
// in any of the ServiceCIDRs, and provides complete list of service cidrs.
func (proxier *Proxier) OnServiceCIDRsChanged(_ []string) {}
func (proxier *Proxier) cleanupAllPolicies() { func (proxier *Proxier) cleanupAllPolicies() {
for svcName, svc := range proxier.svcPortMap { for svcName, svc := range proxier.svcPortMap {
svcInfo, ok := svc.(*serviceInfo) svcInfo, ok := svc.(*serviceInfo)

View File

@ -534,6 +534,10 @@ func ClusterRoles() []rbacv1.ClusterRole {
eventsRule(), eventsRule(),
} }
if utilfeature.DefaultFeatureGate.Enabled(features.MultiCIDRServiceAllocator) {
nodeProxierRules = append(nodeProxierRules, rbacv1helpers.NewRule("list", "watch").Groups(networkingGroup).Resources("servicecidrs").RuleOrDie())
}
nodeProxierRules = append(nodeProxierRules, rbacv1helpers.NewRule("list", "watch").Groups(discoveryGroup).Resources("endpointslices").RuleOrDie()) nodeProxierRules = append(nodeProxierRules, rbacv1helpers.NewRule("list", "watch").Groups(discoveryGroup).Resources("endpointslices").RuleOrDie())
roles = append(roles, rbacv1.ClusterRole{ roles = append(roles, rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "system:node-proxier"}, ObjectMeta: metav1.ObjectMeta{Name: "system:node-proxier"},