Skip to content

Commit c50df4e

Browse files
fix: [NPM-WIN] add an empty set to lists when theyre created (#1431)
1 parent 67a6f9a commit c50df4e

File tree

6 files changed

+227
-24
lines changed

6 files changed

+227
-24
lines changed

npm/pkg/dataplane/dataplane.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type DataPlane struct {
4242

4343
func NewDataPlane(nodeName string, ioShim *common.IOShim, cfg *Config, stopChannel <-chan struct{}) (*DataPlane, error) {
4444
metrics.InitializeAll()
45+
if util.IsWindowsDP() {
46+
klog.Infof("[DataPlane] enabling AddEmptySetToLists for Windows")
47+
cfg.IPSetManagerCfg.AddEmptySetToLists = true
48+
}
4549
dp := &DataPlane{
4650
Config: cfg,
4751
policyMgr: policies.NewPolicyManager(ioShim, cfg.PolicyManagerCfg),

npm/pkg/dataplane/ipsets/ipset.go

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/Azure/azure-container-networking/log"
88
"github.com/Azure/azure-container-networking/npm/util"
9+
"k8s.io/klog"
910
)
1011

1112
type IPSetMetadata struct {
@@ -60,9 +61,13 @@ func (setMetadata *IPSetMetadata) GetPrefixName() string {
6061
return fmt.Sprintf("%s%s", util.NamespaceLabelPrefix, setMetadata.Name)
6162
case NestedLabelOfPod:
6263
return fmt.Sprintf("%s%s", util.NestedLabelPrefix, setMetadata.Name)
64+
case EmptyHashSet:
65+
return fmt.Sprintf("%s%s", util.EmptySetPrefix, setMetadata.Name)
6366
case UnknownType: // adding this to appease golint
67+
klog.Errorf("experienced unknown type in set metadata: %+v", setMetadata)
6468
return Unknown
6569
default:
70+
klog.Errorf("experienced unexpected type %d in set metadata: %+v", setMetadata.Type, setMetadata)
6671
return Unknown
6772
}
6873
}
@@ -83,6 +88,8 @@ func (setType SetType) getSetKind() SetKind {
8388
return HashSet
8489
case KeyValueLabelOfPod:
8590
return HashSet
91+
case EmptyHashSet:
92+
return HashSet
8693
case KeyLabelOfNamespace:
8794
return ListSet
8895
case KeyValueLabelOfNamespace:
@@ -132,6 +139,7 @@ type SetProperties struct {
132139

133140
type SetType int8
134141

142+
// Possble values for SetType
135143
const (
136144
// Unknown SetType
137145
UnknownType SetType = 0
@@ -154,6 +162,9 @@ const (
154162
NestedLabelOfPod SetType = 7
155163
// CIDRBlocks holds CIDR blocks
156164
CIDRBlocks SetType = 8
165+
// EmptyHashSet is a set meant to have no members
166+
EmptyHashSet SetType = 9
167+
157168
// Unknown const for unknown string
158169
Unknown string = "unknown"
159170
)
@@ -162,13 +173,14 @@ var (
162173
setTypeName = map[SetType]string{
163174
UnknownType: Unknown,
164175
Namespace: "Namespace",
165-
KeyLabelOfNamespace: "KeyLabelOfNameSpace",
166-
KeyValueLabelOfNamespace: "KeyValueLabelOfNameSpace",
176+
KeyLabelOfNamespace: "KeyLabelOfNamespace",
177+
KeyValueLabelOfNamespace: "KeyValueLabelOfNamespace",
167178
KeyLabelOfPod: "KeyLabelOfPod",
168179
KeyValueLabelOfPod: "KeyValueLabelOfPod",
169180
NamedPorts: "NamedPorts",
170181
NestedLabelOfPod: "NestedLabelOfPod",
171182
CIDRBlocks: "CIDRBlocks",
183+
EmptyHashSet: "EmptySet",
172184
}
173185
// ErrIPSetInvalidKind is returned when IPSet kind is invalid
174186
ErrIPSetInvalidKind = errors.New("invalid IPSet Kind")
@@ -342,11 +354,18 @@ func (set *IPSet) canBeForceDeleted() bool {
342354
!set.referencedInList()
343355
}
344356

345-
func (set *IPSet) canBeDeleted() bool {
357+
func (set *IPSet) canBeDeleted(ignorableMember *IPSet) bool {
358+
var firstMember *IPSet
359+
for _, member := range set.MemberIPSets {
360+
firstMember = member
361+
break
362+
}
363+
listMembersOk := len(set.MemberIPSets) == 0 || (len(set.MemberIPSets) == 1 && firstMember == ignorableMember)
364+
346365
return !set.usedByNetPol() &&
347366
!set.referencedInList() &&
348-
len(set.MemberIPSets) == 0 &&
349-
len(set.IPPodKey) == 0
367+
len(set.IPPodKey) == 0 &&
368+
listMembersOk
350369
}
351370

352371
// usedByNetPol check if an IPSet is referred in network policies.

npm/pkg/dataplane/ipsets/ipset_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import (
77
)
88

99
func TestShouldBeInKernelAndCanDelete(t *testing.T) {
10+
ignorableSetMetadata := &IPSetMetadata{"ignorableSet", EmptyHashSet}
11+
ignorableSet := NewIPSet(ignorableSetMetadata)
12+
1013
s := &IPSetMetadata{"test-set", Namespace}
1114
l := &IPSetMetadata{"test-list", KeyLabelOfNamespace}
15+
1216
tests := []struct {
1317
name string
1418
set *IPSet
@@ -87,6 +91,21 @@ func TestShouldBeInKernelAndCanDelete(t *testing.T) {
8791
wantInKernel: false,
8892
wantDeletable: false,
8993
},
94+
{
95+
name: "only has ignorable member",
96+
set: &IPSet{
97+
Name: l.GetPrefixName(),
98+
SetProperties: SetProperties{
99+
Type: l.Type,
100+
Kind: l.GetSetKind(),
101+
},
102+
MemberIPSets: map[string]*IPSet{
103+
ignorableSet.Name: ignorableSet,
104+
},
105+
},
106+
wantInKernel: false,
107+
wantDeletable: true,
108+
},
90109
{
91110
name: "only has ip members",
92111
set: &IPSet{
@@ -125,9 +144,9 @@ func TestShouldBeInKernelAndCanDelete(t *testing.T) {
125144
}
126145

127146
if tt.wantDeletable {
128-
require.True(t, tt.set.canBeDeleted())
147+
require.True(t, tt.set.canBeDeleted(ignorableSet))
129148
} else {
130-
require.False(t, tt.set.canBeDeleted())
149+
require.False(t, tt.set.canBeDeleted(ignorableSet))
131150
}
132151
})
133152
}

npm/pkg/dataplane/ipsets/ipsetmanager.go

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,21 @@ const (
3333
ApplyOnNeed IPSetMode = "on-need"
3434
)
3535

36+
var (
37+
emptySetMetadata = &IPSetMetadata{
38+
Name: "emptyhashset",
39+
Type: EmptyHashSet,
40+
}
41+
emptySetPrefixName = emptySetMetadata.GetPrefixName()
42+
)
43+
3644
type IPSetManager struct {
37-
iMgrCfg *IPSetManagerCfg
45+
iMgrCfg *IPSetManagerCfg
46+
// emptySet is a direct reference to the empty ipset that should always be in the kernel.
47+
// This set is used based on the AddEmptySetToLists flag.
48+
// If emptySet is non-nil, it should be in the kernel or ready to be created in the dirtyCache.
49+
// Its reference counts are currently unaccounted for and may be incorrect.
50+
emptySet *IPSet
3851
setMap map[string]*IPSet
3952
dirtyCache dirtyCacheInterface
4053
ioShim *common.IOShim
@@ -45,11 +58,16 @@ type IPSetManagerCfg struct {
4558
IPSetMode IPSetMode
4659
// NetworkName can be left empty or set to 'azure' (the only supported network)
4760
NetworkName string
61+
// AddEmptySetToLists determines whether all lists should have an empty set as a member.
62+
// This is necessary for HNS (Windows); otherwise, an allow ACL with a list condition
63+
// allows all IPs if the list has no members.
64+
AddEmptySetToLists bool
4865
}
4966

5067
func NewIPSetManager(iMgrCfg *IPSetManagerCfg, ioShim *common.IOShim) *IPSetManager {
5168
return &IPSetManager{
5269
iMgrCfg: iMgrCfg,
70+
emptySet: nil, // will be set if needed in calls to AddToLists
5371
setMap: make(map[string]*IPSet),
5472
dirtyCache: newDirtyCache(),
5573
ioShim: ioShim,
@@ -60,11 +78,6 @@ func NewIPSetManager(iMgrCfg *IPSetManagerCfg, ioShim *common.IOShim) *IPSetMana
6078
Reconcile removes empty/unreferenced sets from the cache.
6179
For ApplyAllIPSets mode, those sets are added to the toDeleteCache.
6280
We can't delete from kernel immediately unless we lock iMgr during policy CRUD.
63-
If this call adds a set to the toDeleteCache, and then that set is created before
64-
ApplyIPSets is called, then the set may be unnecessarily added to the toAddOrUpdateCache,
65-
meaning:
66-
- for Linux, an unnecessary "-N set-name --exists" call would be made in the restore file
67-
- for Windows, ...
6881
*/
6982
func (iMgr *IPSetManager) Reconcile() {
7083
iMgr.Lock()
@@ -86,6 +99,7 @@ func (iMgr *IPSetManager) ResetIPSets() error {
8699
metrics.ResetIPSetEntries()
87100
err := iMgr.resetIPSets()
88101
iMgr.setMap = make(map[string]*IPSet)
102+
iMgr.emptySet = nil
89103
iMgr.clearDirtyCache()
90104
if err != nil {
91105
metrics.SendErrorLogAndMetric(util.IpsmID, "error: failed to reset ipsetmanager: %s", err.Error())
@@ -109,12 +123,28 @@ func (iMgr *IPSetManager) createAndGetIPSet(setMetadata *IPSetMetadata) *IPSet {
109123
if exists {
110124
return set
111125
}
126+
112127
set = NewIPSet(setMetadata)
113128
iMgr.setMap[prefixedName] = set
114129
metrics.IncNumIPSets()
115130
if iMgr.iMgrCfg.IPSetMode == ApplyAllIPSets {
116131
iMgr.modifyCacheForKernelCreation(set)
117132
}
133+
134+
// if configured, add the empty set to lists of type KeyLabelOfNamespace and KeyValueLabelOfNamespace.
135+
// The NestedLabelOfPod list ipset type is assumed to always have a member (it is created specifically for network policy pod selectors).
136+
if iMgr.iMgrCfg.AddEmptySetToLists && (set.Type == KeyLabelOfNamespace || set.Type == KeyValueLabelOfNamespace) {
137+
if iMgr.emptySet == nil {
138+
// duplicate of code chunk above
139+
iMgr.emptySet = NewIPSet(emptySetMetadata)
140+
iMgr.setMap[emptySetPrefixName] = iMgr.emptySet
141+
metrics.IncNumIPSets()
142+
iMgr.modifyCacheForKernelCreation(iMgr.emptySet)
143+
}
144+
145+
iMgr.addMemberToList(set, iMgr.emptySet)
146+
}
147+
118148
return set
119149
}
120150

@@ -214,7 +244,6 @@ func (iMgr *IPSetManager) AddToSets(addToSets []*IPSetMetadata, ip, podKey strin
214244
return npmerrors.Errorf(npmerrors.AppendIPSet, true, msg)
215245
}
216246

217-
// TODO check if the IP is IPV4 family in controller
218247
iMgr.Lock()
219248
defer iMgr.Unlock()
220249

@@ -334,19 +363,24 @@ func (iMgr *IPSetManager) AddToLists(listMetadatas, setMetadatas []*IPSetMetadat
334363
}
335364
member := iMgr.setMap[memberName]
336365

337-
iMgr.modifyCacheForKernelMemberAdd(list, member.HashedName)
338-
list.MemberIPSets[memberName] = member
339-
member.incIPSetReferCount()
340-
metrics.AddEntryToIPSet(list.Name)
366+
iMgr.addMemberToList(list, member)
341367
listIsInKernel := iMgr.shouldBeInKernel(list)
342368
if listIsInKernel {
343369
iMgr.incKernelReferCountAndModifyCache(member)
344370
}
345371
}
346372
}
373+
347374
return nil
348375
}
349376

377+
func (iMgr *IPSetManager) addMemberToList(list, member *IPSet) {
378+
iMgr.modifyCacheForKernelMemberAdd(list, member.HashedName)
379+
list.MemberIPSets[member.Name] = member
380+
member.incIPSetReferCount()
381+
metrics.AddEntryToIPSet(list.Name)
382+
}
383+
350384
func (iMgr *IPSetManager) RemoveFromList(listMetadata *IPSetMetadata, setMetadatas []*IPSetMetadata) error {
351385
if len(setMetadatas) == 0 {
352386
return nil
@@ -370,9 +404,15 @@ func (iMgr *IPSetManager) RemoveFromList(listMetadata *IPSetMetadata, setMetadat
370404
for _, setMetadata := range setMetadatas {
371405
memberName := setMetadata.GetPrefixName()
372406
if memberName == "" {
373-
metrics.SendErrorLogAndMetric(util.IpsmID, "[RemoveFromList] warning: removing empty member name from list %s", list.Name)
407+
metrics.SendErrorLogAndMetric(util.IpsmID, "[RemoveFromList] warning: tried to remove empty member name from list %s", list.Name)
408+
continue
409+
}
410+
411+
if iMgr.iMgrCfg.AddEmptySetToLists && memberName == emptySetPrefixName {
412+
metrics.SendErrorLogAndMetric(util.IpsmID, "[RemoveFromList] warning: tried to remove empty set from list %s", list.Name)
374413
continue
375414
}
415+
376416
member, exists := iMgr.setMap[memberName]
377417
if !exists {
378418
continue
@@ -449,21 +489,23 @@ func (iMgr *IPSetManager) exists(name string) bool {
449489

450490
// the metric for number of ipsets in the kernel will be lower than in reality until the next applyIPSet call
451491
func (iMgr *IPSetManager) modifyCacheForCacheDeletion(set *IPSet, deleteOption util.DeleteOption) {
492+
if set == iMgr.emptySet {
493+
return
494+
}
495+
452496
if deleteOption == util.ForceDelete {
453497
// If force delete, then check if Set is used by other set or network policy
454498
// else delete the set even if it has members
455499
if !set.canBeForceDeleted() {
456500
return
457501
}
458-
} else if !set.canBeDeleted() {
502+
} else if !set.canBeDeleted(iMgr.emptySet) {
459503
return
460504
}
461505

462506
delete(iMgr.setMap, set.Name)
463507
metrics.DeleteIPSet(set.Name)
464508
if iMgr.iMgrCfg.IPSetMode == ApplyAllIPSets {
465-
// NOTE: in ApplyAllIPSets mode, if this ipset has never been created in the kernel,
466-
// it would be added to the deleteCache, and then the OS would fail to delete it
467509
iMgr.modifyCacheForKernelRemoval(set)
468510
}
469511
// if mode is ApplyOnNeed, the set will not be in the kernel (or will be in the delete cache already) since there are no references
@@ -489,7 +531,7 @@ func (iMgr *IPSetManager) incKernelReferCountAndModifyCache(member *IPSet) {
489531
}
490532

491533
func (iMgr *IPSetManager) shouldBeInKernel(set *IPSet) bool {
492-
return set.shouldBeInKernel() || iMgr.iMgrCfg.IPSetMode == ApplyAllIPSets
534+
return set.shouldBeInKernel() || iMgr.iMgrCfg.IPSetMode == ApplyAllIPSets || set == iMgr.emptySet
493535
}
494536

495537
func (iMgr *IPSetManager) modifyCacheForKernelRemoval(set *IPSet) {

0 commit comments

Comments
 (0)