Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions .github/workflows/check-license-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ name: Check License Dependencies
on:
push:
branches: [ main ]
paths:
- 'go.mod'
- 'go.sum'
- '.github/workflows/check-license-dependencies.yml'
pull_request:
paths:
- 'go.mod'
- 'go.sum'
- '.github/workflows/check-license-dependencies.yml'
Comment thread
lixmal marked this conversation as resolved.

jobs:
check-dependencies:
check-internal-dependencies:
name: Check Internal AGPL Dependencies
runs-on: ubuntu-latest

steps:
Expand All @@ -33,9 +42,52 @@ jobs:
if [ $FOUND_ISSUES -eq 1 ]; then
echo ""
echo "❌ Found dependencies on management/, signal/, or relay/ packages"
echo "These packages will change license and should not be imported by client or shared code"
echo "These packages are licensed under AGPLv3 and must not be imported by BSD-licensed code"
exit 1
else
echo ""
echo "✅ All license dependencies are clean"
echo "✅ All internal license dependencies are clean"
fi

check-external-licenses:
name: Check External GPL/AGPL Licenses
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: true

- name: Install go-licenses
run: go install github.com/google/go-licenses@latest

- name: Check for GPL/AGPL licensed dependencies
run: |
echo "Checking for GPL/AGPL/LGPL licensed dependencies..."
echo ""

# Check all Go packages for copyleft licenses
COPYLEFT_DEPS=$(go-licenses report ./... 2>/dev/null | grep -E 'GPL|AGPL|LGPL' || true)
Comment thread
lixmal marked this conversation as resolved.
Outdated

if [ -n "$COPYLEFT_DEPS" ]; then
echo "❌ Found copyleft licensed dependencies:"
echo "$COPYLEFT_DEPS"
echo ""
echo "Note: MPL-2.0 is weak copyleft and compatible with BSD."
echo "GPL/AGPL/LGPL licenses are NOT compatible with BSD-3-Clause."

# Filter out MPL-2.0 (which is compatible) and check for incompatible licenses
INCOMPATIBLE=$(echo "$COPYLEFT_DEPS" | grep -E 'GPL-[0-9]|AGPL-[0-9]|LGPL-[0-9]' || true)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if [ -n "$INCOMPATIBLE" ]; then
echo ""
echo "❌ INCOMPATIBLE licenses found:"
echo "$INCOMPATIBLE"
exit 1
fi
fi

echo "✅ All external license dependencies are compatible with BSD-3-Clause"
136 changes: 107 additions & 29 deletions client/firewall/iptables/acl_linux.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package iptables

import (
"errors"
"fmt"
"net"
"slices"
"syscall"

"github.com/coreos/go-iptables/iptables"
"github.com/google/uuid"
"github.com/nadoo/ipset"
ipset "github.com/lrh3321/ipset-go"
log "github.com/sirupsen/logrus"

firewall "github.com/netbirdio/netbird/client/firewall/manager"
Expand Down Expand Up @@ -40,19 +42,13 @@ type aclManager struct {
}

func newAclManager(iptablesClient *iptables.IPTables, wgIface iFaceMapper) (*aclManager, error) {
m := &aclManager{
return &aclManager{
iptablesClient: iptablesClient,
wgIface: wgIface,
entries: make(map[string][][]string),
optionalEntries: make(map[string][]entry),
ipsetStore: newIpsetStore(),
}

if err := ipset.Init(); err != nil {
return nil, fmt.Errorf("init ipset: %w", err)
}

return m, nil
}, nil
}

func (m *aclManager) init(stateManager *statemanager.Manager) error {
Expand Down Expand Up @@ -98,8 +94,8 @@ func (m *aclManager) AddPeerFiltering(
specs = append(specs, "-j", actionToStr(action))
if ipsetName != "" {
if ipList, ipsetExists := m.ipsetStore.ipset(ipsetName); ipsetExists {
if err := ipset.Add(ipsetName, ip.String()); err != nil {
return nil, fmt.Errorf("failed to add IP to ipset: %w", err)
if err := m.addToIPSet(ipsetName, ip); err != nil {
return nil, fmt.Errorf("add IP to ipset: %w", err)
}
// if ruleset already exists it means we already have the firewall rule
// so we need to update IPs in the ruleset and return new fw.Rule object for ACL manager.
Expand All @@ -113,14 +109,18 @@ func (m *aclManager) AddPeerFiltering(
}}, nil
}

if err := ipset.Flush(ipsetName); err != nil {
log.Errorf("flush ipset %s before use it: %s", ipsetName, err)
if err := m.flushIPSet(ipsetName); err != nil {
if errors.Is(err, syscall.ENOENT) {
log.Debugf("flush ipset %s before use: %v", ipsetName, err)
} else {
log.Errorf("flush ipset %s before use: %v", ipsetName, err)
}
Comment thread
lixmal marked this conversation as resolved.
}
if err := ipset.Create(ipsetName); err != nil {
return nil, fmt.Errorf("failed to create ipset: %w", err)
if err := m.createIPSet(ipsetName); err != nil {
return nil, fmt.Errorf("create ipset: %w", err)
}
if err := ipset.Add(ipsetName, ip.String()); err != nil {
return nil, fmt.Errorf("failed to add IP to ipset: %w", err)
if err := m.addToIPSet(ipsetName, ip); err != nil {
return nil, fmt.Errorf("add IP to ipset: %w", err)
}

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

shouldDestroyIpset := false
if ipsetList, ok := m.ipsetStore.ipset(r.ipsetName); ok {
// delete IP from ruleset IPs list and ipset
if _, ok := ipsetList.ips[r.ip]; ok {
if err := ipset.Del(r.ipsetName, r.ip); err != nil {
return fmt.Errorf("failed to delete ip from ipset: %w", err)
ip := net.ParseIP(r.ip)
if ip == nil {
return fmt.Errorf("parse IP %s", r.ip)
}
if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
return fmt.Errorf("delete ip from ipset: %w", err)
}
delete(ipsetList.ips, r.ip)
}
Expand All @@ -190,10 +195,7 @@ func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
// we delete last IP from the set, that means we need to delete
// set itself and associated firewall rule too
m.ipsetStore.deleteIpset(r.ipsetName)

if err := ipset.Destroy(r.ipsetName); err != nil {
log.Errorf("delete empty ipset: %v", err)
}
shouldDestroyIpset = true
}

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

if shouldDestroyIpset {
if err := m.destroyIPSet(r.ipsetName); err != nil {
if errors.Is(err, syscall.EBUSY) || errors.Is(err, syscall.ENOENT) {
log.Debugf("destroy empty ipset: %v", err)
} else {
log.Errorf("destroy empty ipset: %v", err)
}
}
}

m.updateState()

return nil
Expand Down Expand Up @@ -264,11 +276,19 @@ func (m *aclManager) cleanChains() error {
}

for _, ipsetName := range m.ipsetStore.ipsetNames() {
if err := ipset.Flush(ipsetName); err != nil {
log.Errorf("flush ipset %q during reset: %v", ipsetName, err)
if err := m.flushIPSet(ipsetName); err != nil {
if errors.Is(err, syscall.ENOENT) {
log.Debugf("flush ipset %q during reset: %v", ipsetName, err)
} else {
log.Errorf("flush ipset %q during reset: %v", ipsetName, err)
}
}
if err := ipset.Destroy(ipsetName); err != nil {
log.Errorf("delete ipset %q during reset: %v", ipsetName, err)
if err := m.destroyIPSet(ipsetName); err != nil {
if errors.Is(err, syscall.EBUSY) || errors.Is(err, syscall.ENOENT) {
log.Debugf("destroy ipset %q during reset: %v", ipsetName, err)
} else {
log.Errorf("destroy ipset %q during reset: %v", ipsetName, err)
}
}
m.ipsetStore.deleteIpset(ipsetName)
}
Expand Down Expand Up @@ -368,8 +388,8 @@ func (m *aclManager) updateState() {
// filterRuleSpecs returns the specs of a filtering rule
func filterRuleSpecs(ip net.IP, protocol string, sPort, dPort *firewall.Port, action firewall.Action, ipsetName string) (specs []string) {
matchByIP := true
// don't use IP matching if IP is ip 0.0.0.0
if ip.String() == "0.0.0.0" {
// don't use IP matching if IP is 0.0.0.0
if ip.IsUnspecified() {
matchByIP = false
}

Expand Down Expand Up @@ -416,3 +436,61 @@ func transformIPsetName(ipsetName string, sPort, dPort *firewall.Port, action fi
return ipsetName + actionSuffix
}
}

func (m *aclManager) createIPSet(name string) error {
opts := ipset.CreateOptions{
Replace: true,
}

if err := ipset.Create(name, ipset.TypeHashNet, opts); err != nil {
return fmt.Errorf("create ipset %s: %w", name, err)
}

log.Debugf("created ipset %s with type hash:net", name)
return nil
}
Comment thread
lixmal marked this conversation as resolved.

func (m *aclManager) addToIPSet(name string, ip net.IP) error {
cidr := uint8(32)
if ip.To4() == nil {
cidr = 128
}

entry := &ipset.Entry{
IP: ip,
CIDR: cidr,
Replace: true,
}

if err := ipset.Add(name, entry); err != nil {
return fmt.Errorf("add IP to ipset %s: %w", name, err)
}

return nil
}

func (m *aclManager) delFromIPSet(name string, ip net.IP) error {
cidr := uint8(32)
if ip.To4() == nil {
cidr = 128
}

entry := &ipset.Entry{
IP: ip,
CIDR: cidr,
}

if err := ipset.Del(name, entry); err != nil {
return fmt.Errorf("delete IP from ipset %s: %w", name, err)
}

return nil
}

func (m *aclManager) flushIPSet(name string) error {
return ipset.Flush(name)
}

func (m *aclManager) destroyIPSet(name string) error {
return ipset.Destroy(name)
}
50 changes: 40 additions & 10 deletions client/firewall/iptables/router_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"github.com/coreos/go-iptables/iptables"
"github.com/hashicorp/go-multierror"
"github.com/nadoo/ipset"
ipset "github.com/lrh3321/ipset-go"
log "github.com/sirupsen/logrus"

nberrors "github.com/netbirdio/netbird/client/errors"
Expand Down Expand Up @@ -107,10 +107,6 @@ func newRouter(iptablesClient *iptables.IPTables, wgIface iFaceMapper, mtu uint1
},
)

if err := ipset.Init(); err != nil {
return nil, fmt.Errorf("init ipset: %w", err)
}

return r, nil
}

Expand Down Expand Up @@ -232,12 +228,12 @@ func (r *router) findSets(rule []string) []string {
}

func (r *router) createIpSet(setName string, sources []netip.Prefix) error {
if err := ipset.Create(setName, ipset.OptTimeout(0)); err != nil {
if err := r.createIPSet(setName); err != nil {
return fmt.Errorf("create set %s: %w", setName, err)
}

for _, prefix := range sources {
if err := ipset.AddPrefix(setName, prefix); err != nil {
if err := r.addPrefixToIPSet(setName, prefix); err != nil {
return fmt.Errorf("add element to set %s: %w", setName, err)
}
}
Expand All @@ -246,7 +242,7 @@ func (r *router) createIpSet(setName string, sources []netip.Prefix) error {
}

func (r *router) deleteIpSet(setName string) error {
if err := ipset.Destroy(setName); err != nil {
if err := r.destroyIPSet(setName); err != nil {
return fmt.Errorf("destroy set %s: %w", setName, err)
}

Expand Down Expand Up @@ -915,8 +911,8 @@ func (r *router) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
log.Tracef("skipping IPv6 prefix %s: IPv6 support not yet implemented", prefix)
continue
}
if err := ipset.AddPrefix(set.HashedName(), prefix); err != nil {
merr = multierror.Append(merr, fmt.Errorf("increment ipset counter: %w", err))
if err := r.addPrefixToIPSet(set.HashedName(), prefix); err != nil {
merr = multierror.Append(merr, fmt.Errorf("add prefix to ipset: %w", err))
}
}
if merr == nil {
Expand Down Expand Up @@ -993,3 +989,37 @@ func applyPort(flag string, port *firewall.Port) []string {

return []string{flag, strconv.Itoa(int(port.Values[0]))}
}

func (r *router) createIPSet(name string) error {
opts := ipset.CreateOptions{
Replace: true,
}

if err := ipset.Create(name, ipset.TypeHashNet, opts); err != nil {
return fmt.Errorf("create ipset %s: %w", name, err)
}

log.Debugf("created ipset %s with type hash:net", name)
return nil
}

func (r *router) addPrefixToIPSet(name string, prefix netip.Prefix) error {
addr := prefix.Addr()
ip := addr.AsSlice()

entry := &ipset.Entry{
IP: ip,
CIDR: uint8(prefix.Bits()),
Replace: true,
}

if err := ipset.Add(name, entry); err != nil {
return fmt.Errorf("add prefix to ipset %s: %w", name, err)
}

return nil
}

func (r *router) destroyIPSet(name string) error {
return ipset.Destroy(name)
}
Loading
Loading