Skip to content

Commit a869b8d

Browse files
committed
Delete the NodeNetworkPolicy chains left behind in the datapath
syncIPTables only rewrites the chains the caches know about, so a chain deleted while a sync was holding an older snapshot is recreated by that sync, and nothing removes it afterwards: the rules of a deleted policy stay in the datapath until the Agent restarts. The periodic sync now lists the chains of the filter table and deletes the ones which are not in the caches any more. The datapath is listed before the caches are read, which is what makes it safe: the rules of a chain are stored in the caches before the chain is written to the datapath, so a chain which appears in the listing was already in the caches when the listing was taken, and is not mistaken for a leftover. Signed-off-by: Hongliang Liu <hongliang.liu@broadcom.com>
1 parent cef9fe0 commit a869b8d

4 files changed

Lines changed: 126 additions & 0 deletions

File tree

pkg/agent/route/route_linux.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ const (
6161
vxlanPort = 4789
6262
genevePort = 6081
6363

64+
// nodeNetworkPolicyChainPrefix is the prefix of the chains created for NodeNetworkPolicy, including
65+
// the two chains holding the rules which jump to the per-policy ones.
66+
nodeNetworkPolicyChainPrefix = "ANTREA-POL"
67+
6468
// syncDebounceDuration is how long a notification waits in the queue, so that the updates occurring
6569
// in quick succession are coalesced into a single sync.
6670
syncDebounceDuration = 100 * time.Millisecond
@@ -511,6 +515,7 @@ func (c *Client) syncNetworkConfig(ctx context.Context) {
511515
return
512516
}
513517
c.triggerIPTablesSync()
518+
c.cleanupOrphanNodeNetworkPolicyChains()
514519
if c.nftables != nil {
515520
if err := c.syncNFTables(ctx); err != nil {
516521
klog.ErrorS(err, "Failed to sync nftables")
@@ -1031,6 +1036,45 @@ func (c *Client) removeUnexpectedAntreaJumpRule(protocol iptables.Protocol, jump
10311036

10321037
// syncIPTables ensure that the iptables infrastructure we use is set up.
10331038
// It's idempotent and can safely be called on every startup.
1039+
// cleanupOrphanNodeNetworkPolicyChains deletes the NodeNetworkPolicy chains which are still in the datapath but not
1040+
// in the cache any more. syncIPTables only rewrites the chains the cache knows about, so a chain deleted while a sync
1041+
// was holding an older snapshot is recreated by that sync and nothing removes it afterwards.
1042+
//
1043+
// The datapath is listed before the cache is read, which is what makes this safe: the rules of a chain are stored in
1044+
// the cache before the chain is written to the datapath, so a chain which appears in the listing was already in the
1045+
// cache when the listing was taken, and is not mistaken for an orphan.
1046+
func (c *Client) cleanupOrphanNodeNetworkPolicyChains() {
1047+
if !c.nodeNetworkPolicyEnabled {
1048+
return
1049+
}
1050+
chainsInDatapath, err := c.iptables.ListChains(c.getIPProtocol(), iptables.FilterTable)
1051+
if err != nil {
1052+
klog.ErrorS(err, "Failed to list the chains of the filter table")
1053+
return
1054+
}
1055+
for ipProtocol, chains := range chainsInDatapath {
1056+
cache := c.iptablesCache.ipv4[featureNodeNetworkPolicy]
1057+
if iptables.IsIPv6Protocol(ipProtocol) {
1058+
cache = c.iptablesCache.ipv6[featureNodeNetworkPolicy]
1059+
}
1060+
for _, chain := range chains {
1061+
if !strings.HasPrefix(chain, nodeNetworkPolicyChainPrefix) {
1062+
continue
1063+
}
1064+
if _, exists := cache.Load(chain); exists {
1065+
continue
1066+
}
1067+
// The chain may still be referenced by a jump rule which the next sync will remove, in
1068+
// which case the deletion fails and is retried on the next period.
1069+
if err := c.iptables.DeleteChain(ipProtocol, iptables.FilterTable, chain); err != nil {
1070+
klog.V(2).InfoS("Failed to delete an orphan NodeNetworkPolicy chain, will retry", "chain", chain, "err", err)
1071+
continue
1072+
}
1073+
klog.InfoS("Deleted an orphan NodeNetworkPolicy chain", "chain", chain)
1074+
}
1075+
}
1076+
}
1077+
10341078
func (c *Client) syncIPTables(cleanupStaleJumpRules bool) error {
10351079
ipProtocol := c.getIPProtocol()
10361080
jumpRules := []jumpRule{

pkg/agent/route/route_linux_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,54 @@ func TestSyncIPSet(t *testing.T) {
381381
}
382382
}
383383

384+
func TestCleanupOrphanNodeNetworkPolicyChains(t *testing.T) {
385+
const orphanChain = "ANTREA-POL-RULE-ORPHAN"
386+
387+
tests := []struct {
388+
name string
389+
nodeNetworkPolicyEnabled bool
390+
chainsInDatapath []string
391+
expectedDeletes []string
392+
}{
393+
{
394+
name: "delete the chains which are not in the cache any more",
395+
nodeNetworkPolicyEnabled: true,
396+
// The first two are seeded in the cache by initNodeNetworkPolicy, the last one is not
397+
// managed by Antrea at all.
398+
chainsInDatapath: []string{config.NodeNetworkPolicyIngressRulesChain, orphanChain, "KUBE-SERVICES"},
399+
expectedDeletes: []string{orphanChain},
400+
},
401+
{
402+
// Antrea does not own these chains when the feature is disabled, so it must not touch them.
403+
name: "do nothing when NodeNetworkPolicy is disabled",
404+
nodeNetworkPolicyEnabled: false,
405+
chainsInDatapath: []string{orphanChain},
406+
},
407+
}
408+
for _, tt := range tests {
409+
t.Run(tt.name, func(t *testing.T) {
410+
ctrl := gomock.NewController(t)
411+
mockIPTables := iptablestest.NewMockInterface(ctrl)
412+
c := &Client{
413+
networkConfig: &config.NetworkConfig{IPv4Enabled: true},
414+
nodeNetworkPolicyEnabled: tt.nodeNetworkPolicyEnabled,
415+
iptablesCache: newIPTablesCache(),
416+
iptables: mockIPTables,
417+
}
418+
if tt.nodeNetworkPolicyEnabled {
419+
c.iptablesCache.ipv4[featureNodeNetworkPolicy].Store(config.NodeNetworkPolicyIngressRulesChain, []string{})
420+
mockIPTables.EXPECT().ListChains(iptables.ProtocolIPv4, iptables.FilterTable).Return(
421+
map[iptables.Protocol][]string{iptables.ProtocolIPv4: tt.chainsInDatapath}, nil)
422+
}
423+
for _, chain := range tt.expectedDeletes {
424+
mockIPTables.EXPECT().DeleteChain(iptables.ProtocolIPv4, iptables.FilterTable, chain).Return(nil)
425+
}
426+
427+
c.cleanupOrphanNodeNetworkPolicyChains()
428+
})
429+
}
430+
}
431+
384432
func TestSyncIPTables(t *testing.T) {
385433
mockIPTablesListRulesOfChains := func(mockIPTables *iptablestest.MockInterfaceMockRecorder,
386434
protocol iptables.Protocol,

pkg/agent/util/iptables/iptables.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ type Interface interface {
115115

116116
ListRules(protocol Protocol, table string, chain string) (map[Protocol][]string, error)
117117

118+
ListChains(protocol Protocol, table string) (map[Protocol][]string, error)
119+
118120
Restore(data string, flush bool, useIPv6 bool) error
119121

120122
Save() ([]byte, error)
@@ -401,6 +403,23 @@ func (c *Client) Restore(data string, flush bool, useIPv6 bool) error {
401403
}
402404

403405
// Save calls iptables-saves to dump chains and tables in iptables.
406+
// ListChains lists the names of the chains of the given table, for every matching protocol.
407+
func (c *Client) ListChains(protocol Protocol, table string) (map[Protocol][]string, error) {
408+
allChains := make(map[Protocol][]string)
409+
for p := range c.ipts {
410+
ipt := c.ipts[p]
411+
if !matchProtocol(ipt, protocol) {
412+
continue
413+
}
414+
chains, err := ipt.ListChains(table)
415+
if err != nil {
416+
return nil, fmt.Errorf("error listing chains of table %s: %w", table, err)
417+
}
418+
allChains[p] = chains
419+
}
420+
return allChains, nil
421+
}
422+
404423
func (c *Client) Save() ([]byte, error) {
405424
var output []byte
406425
for p := range c.ipts {

pkg/agent/util/iptables/testing/mock_iptables_linux.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)