Skip to content

Commit e4b41d0

Browse files
[client] Replace ipset lib (netbirdio#4777)
* Replace ipset lib * Update .github/workflows/check-license-dependencies.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Ignore internal licenses * Ignore dependencies from AGPL code * Use exported errors * Use fixed version --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 9cc9462 commit e4b41d0

5 files changed

Lines changed: 225 additions & 51 deletions

File tree

.github/workflows/check-license-dependencies.yml

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ name: Check License Dependencies
33
on:
44
push:
55
branches: [ main ]
6+
paths:
7+
- 'go.mod'
8+
- 'go.sum'
9+
- '.github/workflows/check-license-dependencies.yml'
610
pull_request:
11+
paths:
12+
- 'go.mod'
13+
- 'go.sum'
14+
- '.github/workflows/check-license-dependencies.yml'
715

816
jobs:
9-
check-dependencies:
17+
check-internal-dependencies:
18+
name: Check Internal AGPL Dependencies
1019
runs-on: ubuntu-latest
1120

1221
steps:
@@ -33,9 +42,67 @@ jobs:
3342
if [ $FOUND_ISSUES -eq 1 ]; then
3443
echo ""
3544
echo "❌ Found dependencies on management/, signal/, or relay/ packages"
36-
echo "These packages will change license and should not be imported by client or shared code"
45+
echo "These packages are licensed under AGPLv3 and must not be imported by BSD-licensed code"
3746
exit 1
3847
else
3948
echo ""
40-
echo "✅ All license dependencies are clean"
49+
echo "✅ All internal license dependencies are clean"
4150
fi
51+
52+
check-external-licenses:
53+
name: Check External GPL/AGPL Licenses
54+
runs-on: ubuntu-latest
55+
56+
steps:
57+
- uses: actions/checkout@v4
58+
59+
- name: Set up Go
60+
uses: actions/setup-go@v5
61+
with:
62+
go-version-file: 'go.mod'
63+
cache: true
64+
65+
- name: Install go-licenses
66+
run: go install github.com/google/go-licenses@v1.6.0
67+
68+
- name: Check for GPL/AGPL licensed dependencies
69+
run: |
70+
echo "Checking for GPL/AGPL/LGPL licensed dependencies..."
71+
echo ""
72+
73+
# Check all Go packages for copyleft licenses, excluding internal netbird packages
74+
COPYLEFT_DEPS=$(go-licenses report ./... 2>/dev/null | grep -E 'GPL|AGPL|LGPL' | grep -v 'github.com/netbirdio/netbird/' || true)
75+
76+
if [ -n "$COPYLEFT_DEPS" ]; then
77+
echo "Found copyleft licensed dependencies:"
78+
echo "$COPYLEFT_DEPS"
79+
echo ""
80+
81+
# Filter out dependencies that are only pulled in by internal AGPL packages
82+
INCOMPATIBLE=""
83+
while IFS=',' read -r package url license; do
84+
if echo "$license" | grep -qE 'GPL-[0-9]|AGPL-[0-9]|LGPL-[0-9]'; then
85+
# Find ALL packages that import this GPL package using go list
86+
IMPORTERS=$(go list -json -deps ./... 2>/dev/null | jq -r "select(.Imports[]? == \"$package\") | .ImportPath")
87+
88+
# Check if any importer is NOT in management/signal/relay
89+
BSD_IMPORTER=$(echo "$IMPORTERS" | grep -v "github.com/netbirdio/netbird/\(management\|signal\|relay\)" | head -1)
90+
91+
if [ -n "$BSD_IMPORTER" ]; then
92+
echo "❌ $package ($license) is imported by BSD-licensed code: $BSD_IMPORTER"
93+
INCOMPATIBLE="${INCOMPATIBLE}${package},${url},${license}\n"
94+
else
95+
echo "✓ $package ($license) is only used by internal AGPL packages - OK"
96+
fi
97+
fi
98+
done <<< "$COPYLEFT_DEPS"
99+
100+
if [ -n "$INCOMPATIBLE" ]; then
101+
echo ""
102+
echo "❌ INCOMPATIBLE licenses found that are used by BSD-licensed code:"
103+
echo -e "$INCOMPATIBLE"
104+
exit 1
105+
fi
106+
fi
107+
108+
echo "✅ All external license dependencies are compatible with BSD-3-Clause"

client/firewall/iptables/acl_linux.go

Lines changed: 106 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package iptables
22

33
import (
4+
"errors"
45
"fmt"
56
"net"
67
"slices"
78

89
"github.com/coreos/go-iptables/iptables"
910
"github.com/google/uuid"
10-
"github.com/nadoo/ipset"
11+
ipset "github.com/lrh3321/ipset-go"
1112
log "github.com/sirupsen/logrus"
1213

1314
firewall "github.com/netbirdio/netbird/client/firewall/manager"
@@ -40,19 +41,13 @@ type aclManager struct {
4041
}
4142

4243
func newAclManager(iptablesClient *iptables.IPTables, wgIface iFaceMapper) (*aclManager, error) {
43-
m := &aclManager{
44+
return &aclManager{
4445
iptablesClient: iptablesClient,
4546
wgIface: wgIface,
4647
entries: make(map[string][][]string),
4748
optionalEntries: make(map[string][]entry),
4849
ipsetStore: newIpsetStore(),
49-
}
50-
51-
if err := ipset.Init(); err != nil {
52-
return nil, fmt.Errorf("init ipset: %w", err)
53-
}
54-
55-
return m, nil
50+
}, nil
5651
}
5752

5853
func (m *aclManager) init(stateManager *statemanager.Manager) error {
@@ -98,8 +93,8 @@ func (m *aclManager) AddPeerFiltering(
9893
specs = append(specs, "-j", actionToStr(action))
9994
if ipsetName != "" {
10095
if ipList, ipsetExists := m.ipsetStore.ipset(ipsetName); ipsetExists {
101-
if err := ipset.Add(ipsetName, ip.String()); err != nil {
102-
return nil, fmt.Errorf("failed to add IP to ipset: %w", err)
96+
if err := m.addToIPSet(ipsetName, ip); err != nil {
97+
return nil, fmt.Errorf("add IP to ipset: %w", err)
10398
}
10499
// if ruleset already exists it means we already have the firewall rule
105100
// so we need to update IPs in the ruleset and return new fw.Rule object for ACL manager.
@@ -113,14 +108,18 @@ func (m *aclManager) AddPeerFiltering(
113108
}}, nil
114109
}
115110

116-
if err := ipset.Flush(ipsetName); err != nil {
117-
log.Errorf("flush ipset %s before use it: %s", ipsetName, err)
111+
if err := m.flushIPSet(ipsetName); err != nil {
112+
if errors.Is(err, ipset.ErrSetNotExist) {
113+
log.Debugf("flush ipset %s before use: %v", ipsetName, err)
114+
} else {
115+
log.Errorf("flush ipset %s before use: %v", ipsetName, err)
116+
}
118117
}
119-
if err := ipset.Create(ipsetName); err != nil {
120-
return nil, fmt.Errorf("failed to create ipset: %w", err)
118+
if err := m.createIPSet(ipsetName); err != nil {
119+
return nil, fmt.Errorf("create ipset: %w", err)
121120
}
122-
if err := ipset.Add(ipsetName, ip.String()); err != nil {
123-
return nil, fmt.Errorf("failed to add IP to ipset: %w", err)
121+
if err := m.addToIPSet(ipsetName, ip); err != nil {
122+
return nil, fmt.Errorf("add IP to ipset: %w", err)
124123
}
125124

126125
ipList := newIpList(ip.String())
@@ -172,11 +171,16 @@ func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
172171
return fmt.Errorf("invalid rule type")
173172
}
174173

174+
shouldDestroyIpset := false
175175
if ipsetList, ok := m.ipsetStore.ipset(r.ipsetName); ok {
176176
// delete IP from ruleset IPs list and ipset
177177
if _, ok := ipsetList.ips[r.ip]; ok {
178-
if err := ipset.Del(r.ipsetName, r.ip); err != nil {
179-
return fmt.Errorf("failed to delete ip from ipset: %w", err)
178+
ip := net.ParseIP(r.ip)
179+
if ip == nil {
180+
return fmt.Errorf("parse IP %s", r.ip)
181+
}
182+
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
183+
return fmt.Errorf("delete ip from ipset: %w", err)
180184
}
181185
delete(ipsetList.ips, r.ip)
182186
}
@@ -190,10 +194,7 @@ func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
190194
// we delete last IP from the set, that means we need to delete
191195
// set itself and associated firewall rule too
192196
m.ipsetStore.deleteIpset(r.ipsetName)
193-
194-
if err := ipset.Destroy(r.ipsetName); err != nil {
195-
log.Errorf("delete empty ipset: %v", err)
196-
}
197+
shouldDestroyIpset = true
197198
}
198199

199200
if err := m.iptablesClient.Delete(tableName, r.chain, r.specs...); err != nil {
@@ -206,6 +207,16 @@ func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
206207
}
207208
}
208209

210+
if shouldDestroyIpset {
211+
if err := m.destroyIPSet(r.ipsetName); err != nil {
212+
if errors.Is(err, ipset.ErrBusy) || errors.Is(err, ipset.ErrSetNotExist) {
213+
log.Debugf("destroy empty ipset: %v", err)
214+
} else {
215+
log.Errorf("destroy empty ipset: %v", err)
216+
}
217+
}
218+
}
219+
209220
m.updateState()
210221

211222
return nil
@@ -264,11 +275,19 @@ func (m *aclManager) cleanChains() error {
264275
}
265276

266277
for _, ipsetName := range m.ipsetStore.ipsetNames() {
267-
if err := ipset.Flush(ipsetName); err != nil {
268-
log.Errorf("flush ipset %q during reset: %v", ipsetName, err)
278+
if err := m.flushIPSet(ipsetName); err != nil {
279+
if errors.Is(err, ipset.ErrSetNotExist) {
280+
log.Debugf("flush ipset %q during reset: %v", ipsetName, err)
281+
} else {
282+
log.Errorf("flush ipset %q during reset: %v", ipsetName, err)
283+
}
269284
}
270-
if err := ipset.Destroy(ipsetName); err != nil {
271-
log.Errorf("delete ipset %q during reset: %v", ipsetName, err)
285+
if err := m.destroyIPSet(ipsetName); err != nil {
286+
if errors.Is(err, ipset.ErrBusy) || errors.Is(err, ipset.ErrSetNotExist) {
287+
log.Debugf("destroy ipset %q during reset: %v", ipsetName, err)
288+
} else {
289+
log.Errorf("destroy ipset %q during reset: %v", ipsetName, err)
290+
}
272291
}
273292
m.ipsetStore.deleteIpset(ipsetName)
274293
}
@@ -368,8 +387,8 @@ func (m *aclManager) updateState() {
368387
// filterRuleSpecs returns the specs of a filtering rule
369388
func filterRuleSpecs(ip net.IP, protocol string, sPort, dPort *firewall.Port, action firewall.Action, ipsetName string) (specs []string) {
370389
matchByIP := true
371-
// don't use IP matching if IP is ip 0.0.0.0
372-
if ip.String() == "0.0.0.0" {
390+
// don't use IP matching if IP is 0.0.0.0
391+
if ip.IsUnspecified() {
373392
matchByIP = false
374393
}
375394

@@ -416,3 +435,61 @@ func transformIPsetName(ipsetName string, sPort, dPort *firewall.Port, action fi
416435
return ipsetName + actionSuffix
417436
}
418437
}
438+
439+
func (m *aclManager) createIPSet(name string) error {
440+
opts := ipset.CreateOptions{
441+
Replace: true,
442+
}
443+
444+
if err := ipset.Create(name, ipset.TypeHashNet, opts); err != nil {
445+
return fmt.Errorf("create ipset %s: %w", name, err)
446+
}
447+
448+
log.Debugf("created ipset %s with type hash:net", name)
449+
return nil
450+
}
451+
452+
func (m *aclManager) addToIPSet(name string, ip net.IP) error {
453+
cidr := uint8(32)
454+
if ip.To4() == nil {
455+
cidr = 128
456+
}
457+
458+
entry := &ipset.Entry{
459+
IP: ip,
460+
CIDR: cidr,
461+
Replace: true,
462+
}
463+
464+
if err := ipset.Add(name, entry); err != nil {
465+
return fmt.Errorf("add IP to ipset %s: %w", name, err)
466+
}
467+
468+
return nil
469+
}
470+
471+
func (m *aclManager) delFromIPSet(name string, ip net.IP) error {
472+
cidr := uint8(32)
473+
if ip.To4() == nil {
474+
cidr = 128
475+
}
476+
477+
entry := &ipset.Entry{
478+
IP: ip,
479+
CIDR: cidr,
480+
}
481+
482+
if err := ipset.Del(name, entry); err != nil {
483+
return fmt.Errorf("delete IP from ipset %s: %w", name, err)
484+
}
485+
486+
return nil
487+
}
488+
489+
func (m *aclManager) flushIPSet(name string) error {
490+
return ipset.Flush(name)
491+
}
492+
493+
func (m *aclManager) destroyIPSet(name string) error {
494+
return ipset.Destroy(name)
495+
}

0 commit comments

Comments
 (0)