Pull request 2024: 6233-imp-ipset
Updates #6233.
Squashed commit of the following:
commit 308754d9cfc24005352bae6db420ad8a5ccde3eb
Merge: 8289df04f 5d7e59e37
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Fri Oct 6 13:04:13 2023 +0300
Merge branch 'master' into 6233-imp-ipset
commit 8289df04f1827e28ea481e6880ceb72d4036dd4f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Oct 5 19:53:53 2023 +0300
ipset: imp naming
commit b24ddd547128db58dcba1a0bf153398db8d9b71c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date: Thu Oct 5 19:16:40 2023 +0300
all: imp ipset
This commit is contained in:
parent
5d7e59e37a
commit
ef88f7462f
|
@ -6,8 +6,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
|
"github.com/AdguardTeam/AdGuardHome/internal/ipset"
|
||||||
"github.com/AdguardTeam/golibs/errors"
|
"github.com/AdguardTeam/golibs/errors"
|
||||||
"github.com/AdguardTeam/golibs/log"
|
"github.com/AdguardTeam/golibs/log"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -15,14 +15,14 @@ import (
|
||||||
|
|
||||||
// ipsetCtx is the ipset context. ipsetMgr can be nil.
|
// ipsetCtx is the ipset context. ipsetMgr can be nil.
|
||||||
type ipsetCtx struct {
|
type ipsetCtx struct {
|
||||||
ipsetMgr aghnet.IpsetManager
|
ipsetMgr ipset.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes the ipset context. It is not safe for concurrent use.
|
// init initializes the ipset context. It is not safe for concurrent use.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Rewrite into a simple constructor?
|
// TODO(a.garipov): Rewrite into a simple constructor?
|
||||||
func (c *ipsetCtx) init(ipsetConf []string) (err error) {
|
func (c *ipsetCtx) init(ipsetConf []string) (err error) {
|
||||||
c.ipsetMgr, err = aghnet.NewIpsetManager(ipsetConf)
|
c.ipsetMgr, err = ipset.NewManager(ipsetConf)
|
||||||
if errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrPermission) {
|
if errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrPermission) {
|
||||||
// ipset cannot currently be initialized if the server was installed
|
// ipset cannot currently be initialized if the server was installed
|
||||||
// from Snap or when the user or the binary doesn't have the required
|
// from Snap or when the user or the binary doesn't have the required
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
package aghnet
|
// Package ipset provides ipset functionality.
|
||||||
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IpsetManager is the ipset manager interface.
|
// Manager is the ipset manager interface.
|
||||||
//
|
//
|
||||||
// TODO(a.garipov): Perhaps generalize this into some kind of a NetFilter type,
|
// TODO(a.garipov): Perhaps generalize this into some kind of a NetFilter type,
|
||||||
// since ipset is exclusive to Linux?
|
// since ipset is exclusive to Linux?
|
||||||
type IpsetManager interface {
|
type Manager interface {
|
||||||
Add(host string, ip4s, ip6s []net.IP) (n int, err error)
|
Add(host string, ip4s, ip6s []net.IP) (n int, err error)
|
||||||
Close() (err error)
|
Close() (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIpsetManager returns a new ipset. IPv4 addresses are added to an ipset
|
// NewManager returns a new ipset manager. IPv4 addresses are added to an
|
||||||
// with an ipv4 family; IPv6 addresses, to an ipv6 ipset. ipset must exist.
|
// ipset with an ipv4 family; IPv6 addresses, to an ipv6 ipset. ipset must
|
||||||
|
// exist.
|
||||||
//
|
//
|
||||||
// The syntax of the ipsetConf is:
|
// The syntax of the ipsetConf is:
|
||||||
//
|
//
|
||||||
|
@ -22,10 +24,10 @@ type IpsetManager interface {
|
||||||
//
|
//
|
||||||
// If ipsetConf is empty, msg and err are nil. The error is of type
|
// If ipsetConf is empty, msg and err are nil. The error is of type
|
||||||
// *aghos.UnsupportedError if the OS is not supported.
|
// *aghos.UnsupportedError if the OS is not supported.
|
||||||
func NewIpsetManager(ipsetConf []string) (mgr IpsetManager, err error) {
|
func NewManager(ipsetConf []string) (mgr Manager, err error) {
|
||||||
if len(ipsetConf) == 0 {
|
if len(ipsetConf) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return newIpsetMgr(ipsetConf)
|
return newManager(ipsetConf)
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
//go:build linux
|
//go:build linux
|
||||||
|
|
||||||
package aghnet
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -31,9 +31,9 @@ import (
|
||||||
// 6. Run "sudo ipset list example_set". The Members field should contain the
|
// 6. Run "sudo ipset list example_set". The Members field should contain the
|
||||||
// resolved IP addresses.
|
// resolved IP addresses.
|
||||||
|
|
||||||
// newIpsetMgr returns a new Linux ipset manager.
|
// newManager returns a new Linux ipset manager.
|
||||||
func newIpsetMgr(ipsetConf []string) (set IpsetManager, err error) {
|
func newManager(ipsetConf []string) (set Manager, err error) {
|
||||||
return newIpsetMgrWithDialer(ipsetConf, defaultDial)
|
return newManagerWithDialer(ipsetConf, defaultDial)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultDial is the default netfilter dialing function.
|
// defaultDial is the default netfilter dialing function.
|
||||||
|
@ -53,11 +53,11 @@ type ipsetConn interface {
|
||||||
Header(name string) (p *ipset.HeaderPolicy, err error)
|
Header(name string) (p *ipset.HeaderPolicy, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsetDialer creates an ipsetConn.
|
// dialer creates an ipsetConn.
|
||||||
type ipsetDialer func(pf netfilter.ProtoFamily, conf *netlink.Config) (conn ipsetConn, err error)
|
type dialer func(pf netfilter.ProtoFamily, conf *netlink.Config) (conn ipsetConn, err error)
|
||||||
|
|
||||||
// ipsetProps contains one Linux Netfilter ipset properties.
|
// props contains one Linux Netfilter ipset properties.
|
||||||
type ipsetProps struct {
|
type props struct {
|
||||||
name string
|
name string
|
||||||
family netfilter.ProtoFamily
|
family netfilter.ProtoFamily
|
||||||
}
|
}
|
||||||
|
@ -74,12 +74,12 @@ type ipInIpsetEntry struct {
|
||||||
ipArr [net.IPv6len]byte
|
ipArr [net.IPv6len]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsetMgr is the Linux Netfilter ipset manager.
|
// manager is the Linux Netfilter ipset manager.
|
||||||
type ipsetMgr struct {
|
type manager struct {
|
||||||
nameToIpset map[string]ipsetProps
|
nameToIpset map[string]props
|
||||||
domainToIpsets map[string][]ipsetProps
|
domainToIpsets map[string][]props
|
||||||
|
|
||||||
dial ipsetDialer
|
dial dialer
|
||||||
|
|
||||||
// mu protects all properties below.
|
// mu protects all properties below.
|
||||||
mu *sync.Mutex
|
mu *sync.Mutex
|
||||||
|
@ -96,7 +96,7 @@ type ipsetMgr struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialNetfilter establishes connections to Linux's netfilter module.
|
// dialNetfilter establishes connections to Linux's netfilter module.
|
||||||
func (m *ipsetMgr) dialNetfilter(conf *netlink.Config) (err error) {
|
func (m *manager) dialNetfilter(conf *netlink.Config) (err error) {
|
||||||
// The kernel API does not actually require two sockets but package
|
// The kernel API does not actually require two sockets but package
|
||||||
// github.com/digineo/go-ipset does.
|
// github.com/digineo/go-ipset does.
|
||||||
//
|
//
|
||||||
|
@ -145,7 +145,7 @@ func parseIpsetConfig(confStr string) (hosts, ipsetNames []string, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsetProps returns the properties of an ipset with the given name.
|
// ipsetProps returns the properties of an ipset with the given name.
|
||||||
func (m *ipsetMgr) ipsetProps(name string) (set ipsetProps, err error) {
|
func (m *manager) ipsetProps(name string) (set props, err error) {
|
||||||
// The family doesn't seem to matter when we use a header query, so
|
// The family doesn't seem to matter when we use a header query, so
|
||||||
// query only the IPv4 one.
|
// query only the IPv4 one.
|
||||||
//
|
//
|
||||||
|
@ -165,14 +165,14 @@ func (m *ipsetMgr) ipsetProps(name string) (set ipsetProps, err error) {
|
||||||
return set, fmt.Errorf("unexpected ipset family %d", family)
|
return set, fmt.Errorf("unexpected ipset family %d", family)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipsetProps{
|
return props{
|
||||||
name: name,
|
name: name,
|
||||||
family: family,
|
family: family,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipsets returns currently known ipsets.
|
// ipsets returns currently known ipsets.
|
||||||
func (m *ipsetMgr) ipsets(names []string) (sets []ipsetProps, err error) {
|
func (m *manager) ipsets(names []string) (sets []props, err error) {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
set, ok := m.nameToIpset[name]
|
set, ok := m.nameToIpset[name]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -193,16 +193,16 @@ func (m *ipsetMgr) ipsets(names []string) (sets []ipsetProps, err error) {
|
||||||
return sets, nil
|
return sets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIpsetMgrWithDialer returns a new Linux ipset manager using the provided
|
// newManagerWithDialer returns a new Linux ipset manager using the provided
|
||||||
// dialer.
|
// dialer.
|
||||||
func newIpsetMgrWithDialer(ipsetConf []string, dial ipsetDialer) (mgr IpsetManager, err error) {
|
func newManagerWithDialer(ipsetConf []string, dial dialer) (mgr Manager, err error) {
|
||||||
defer func() { err = errors.Annotate(err, "ipset: %w") }()
|
defer func() { err = errors.Annotate(err, "ipset: %w") }()
|
||||||
|
|
||||||
m := &ipsetMgr{
|
m := &manager{
|
||||||
mu: &sync.Mutex{},
|
mu: &sync.Mutex{},
|
||||||
|
|
||||||
nameToIpset: make(map[string]ipsetProps),
|
nameToIpset: make(map[string]props),
|
||||||
domainToIpsets: make(map[string][]ipsetProps),
|
domainToIpsets: make(map[string][]props),
|
||||||
|
|
||||||
dial: dial,
|
dial: dial,
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ func newIpsetMgrWithDialer(ipsetConf []string, dial ipsetDialer) (mgr IpsetManag
|
||||||
return nil, fmt.Errorf("config line at idx %d: %w", i, err)
|
return nil, fmt.Errorf("config line at idx %d: %w", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipsets []ipsetProps
|
var ipsets []props
|
||||||
ipsets, err = m.ipsets(ipsetNames)
|
ipsets, err = m.ipsets(ipsetNames)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
|
@ -249,7 +249,7 @@ func newIpsetMgrWithDialer(ipsetConf []string, dial ipsetDialer) (mgr IpsetManag
|
||||||
|
|
||||||
// lookupHost find the ipsets for the host, taking subdomain wildcards into
|
// lookupHost find the ipsets for the host, taking subdomain wildcards into
|
||||||
// account.
|
// account.
|
||||||
func (m *ipsetMgr) lookupHost(host string) (sets []ipsetProps) {
|
func (m *manager) lookupHost(host string) (sets []props) {
|
||||||
// Search for matching ipset hosts starting with most specific domain.
|
// Search for matching ipset hosts starting with most specific domain.
|
||||||
// We could use a trie here but the simple, inefficient solution isn't
|
// We could use a trie here but the simple, inefficient solution isn't
|
||||||
// that expensive: ~10 ns for TLD + SLD vs. ~140 ns for 10 subdomains on
|
// that expensive: ~10 ns for TLD + SLD vs. ~140 ns for 10 subdomains on
|
||||||
|
@ -274,7 +274,7 @@ func (m *ipsetMgr) lookupHost(host string) (sets []ipsetProps) {
|
||||||
|
|
||||||
// addIPs adds the IP addresses for the host to the ipset. set must be same
|
// addIPs adds the IP addresses for the host to the ipset. set must be same
|
||||||
// family as set's family.
|
// family as set's family.
|
||||||
func (m *ipsetMgr) addIPs(host string, set ipsetProps, ips []net.IP) (n int, err error) {
|
func (m *manager) addIPs(host string, set props, ips []net.IP) (n int, err error) {
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
@ -325,11 +325,11 @@ func (m *ipsetMgr) addIPs(host string, set ipsetProps, ips []net.IP) (n int, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// addToSets adds the IP addresses to the corresponding ipset.
|
// addToSets adds the IP addresses to the corresponding ipset.
|
||||||
func (m *ipsetMgr) addToSets(
|
func (m *manager) addToSets(
|
||||||
host string,
|
host string,
|
||||||
ip4s []net.IP,
|
ip4s []net.IP,
|
||||||
ip6s []net.IP,
|
ip6s []net.IP,
|
||||||
sets []ipsetProps,
|
sets []props,
|
||||||
) (n int, err error) {
|
) (n int, err error) {
|
||||||
for _, set := range sets {
|
for _, set := range sets {
|
||||||
var nn int
|
var nn int
|
||||||
|
@ -356,8 +356,8 @@ func (m *ipsetMgr) addToSets(
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add implements the IpsetManager interface for *ipsetMgr
|
// Add implements the [Manager] interface for *manager.
|
||||||
func (m *ipsetMgr) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
func (m *manager) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
@ -371,8 +371,8 @@ func (m *ipsetMgr) Add(host string, ip4s, ip6s []net.IP) (n int, err error) {
|
||||||
return m.addToSets(host, ip4s, ip6s, sets)
|
return m.addToSets(host, ip4s, ip6s, sets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the IpsetManager interface for *ipsetMgr.
|
// Close implements the [Manager] interface for *manager.
|
||||||
func (m *ipsetMgr) Close() (err error) {
|
func (m *manager) Close() (err error) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//go:build linux
|
//go:build linux
|
||||||
|
|
||||||
package aghnet
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
@ -15,16 +15,16 @@ import (
|
||||||
"github.com/ti-mo/netfilter"
|
"github.com/ti-mo/netfilter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fakeIpsetConn is a fake ipsetConn for tests.
|
// fakeConn is a fake ipsetConn for tests.
|
||||||
type fakeIpsetConn struct {
|
type fakeConn struct {
|
||||||
ipv4Header *ipset.HeaderPolicy
|
ipv4Header *ipset.HeaderPolicy
|
||||||
ipv4Entries *[]*ipset.Entry
|
ipv4Entries *[]*ipset.Entry
|
||||||
ipv6Header *ipset.HeaderPolicy
|
ipv6Header *ipset.HeaderPolicy
|
||||||
ipv6Entries *[]*ipset.Entry
|
ipv6Entries *[]*ipset.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add implements the ipsetConn interface for *fakeIpsetConn.
|
// Add implements the [ipsetConn] interface for *fakeConn.
|
||||||
func (c *fakeIpsetConn) Add(name string, entries ...*ipset.Entry) (err error) {
|
func (c *fakeConn) Add(name string, entries ...*ipset.Entry) (err error) {
|
||||||
if strings.Contains(name, "ipv4") {
|
if strings.Contains(name, "ipv4") {
|
||||||
*c.ipv4Entries = append(*c.ipv4Entries, entries...)
|
*c.ipv4Entries = append(*c.ipv4Entries, entries...)
|
||||||
|
|
||||||
|
@ -38,13 +38,13 @@ func (c *fakeIpsetConn) Add(name string, entries ...*ipset.Entry) (err error) {
|
||||||
return errors.Error("test: ipset not found")
|
return errors.Error("test: ipset not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the ipsetConn interface for *fakeIpsetConn.
|
// Close implements the [ipsetConn] interface for *fakeConn.
|
||||||
func (c *fakeIpsetConn) Close() (err error) {
|
func (c *fakeConn) Close() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header implements the ipsetConn interface for *fakeIpsetConn.
|
// Header implements the [ipsetConn] interface for *fakeConn.
|
||||||
func (c *fakeIpsetConn) Header(name string) (p *ipset.HeaderPolicy, err error) {
|
func (c *fakeConn) Header(name string) (p *ipset.HeaderPolicy, err error) {
|
||||||
if strings.Contains(name, "ipv4") {
|
if strings.Contains(name, "ipv4") {
|
||||||
return c.ipv4Header, nil
|
return c.ipv4Header, nil
|
||||||
} else if strings.Contains(name, "ipv6") {
|
} else if strings.Contains(name, "ipv6") {
|
||||||
|
@ -54,7 +54,7 @@ func (c *fakeIpsetConn) Header(name string) (p *ipset.HeaderPolicy, err error) {
|
||||||
return nil, errors.Error("test: ipset not found")
|
return nil, errors.Error("test: ipset not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIpsetMgr_Add(t *testing.T) {
|
func TestManager_Add(t *testing.T) {
|
||||||
ipsetConf := []string{
|
ipsetConf := []string{
|
||||||
"example.com,example.net/ipv4set",
|
"example.com,example.net/ipv4set",
|
||||||
"example.org,example.biz/ipv6set",
|
"example.org,example.biz/ipv6set",
|
||||||
|
@ -67,7 +67,7 @@ func TestIpsetMgr_Add(t *testing.T) {
|
||||||
pf netfilter.ProtoFamily,
|
pf netfilter.ProtoFamily,
|
||||||
conf *netlink.Config,
|
conf *netlink.Config,
|
||||||
) (conn ipsetConn, err error) {
|
) (conn ipsetConn, err error) {
|
||||||
return &fakeIpsetConn{
|
return &fakeConn{
|
||||||
ipv4Header: &ipset.HeaderPolicy{
|
ipv4Header: &ipset.HeaderPolicy{
|
||||||
Family: ipset.NewUInt8Box(uint8(netfilter.ProtoIPv4)),
|
Family: ipset.NewUInt8Box(uint8(netfilter.ProtoIPv4)),
|
||||||
},
|
},
|
||||||
|
@ -79,7 +79,7 @@ func TestIpsetMgr_Add(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := newIpsetMgrWithDialer(ipsetConf, fakeDial)
|
m, err := newManagerWithDialer(ipsetConf, fakeDial)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ip4 := net.IP{1, 2, 3, 4}
|
ip4 := net.IP{1, 2, 3, 4}
|
||||||
|
@ -114,21 +114,21 @@ func TestIpsetMgr_Add(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ipsetPropsSink []ipsetProps
|
var ipsetPropsSink []props
|
||||||
|
|
||||||
func BenchmarkIpsetMgr_lookupHost(b *testing.B) {
|
func BenchmarkManager_LookupHost(b *testing.B) {
|
||||||
propsLong := []ipsetProps{{
|
propsLong := []props{{
|
||||||
name: "example.com",
|
name: "example.com",
|
||||||
family: netfilter.ProtoIPv4,
|
family: netfilter.ProtoIPv4,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
propsShort := []ipsetProps{{
|
propsShort := []props{{
|
||||||
name: "example.net",
|
name: "example.net",
|
||||||
family: netfilter.ProtoIPv4,
|
family: netfilter.ProtoIPv4,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
m := &ipsetMgr{
|
m := &manager{
|
||||||
domainToIpsets: map[string][]ipsetProps{
|
domainToIpsets: map[string][]props{
|
||||||
"": propsLong,
|
"": propsLong,
|
||||||
"example.net": propsShort,
|
"example.net": propsShort,
|
||||||
},
|
},
|
|
@ -1,11 +1,11 @@
|
||||||
//go:build !linux
|
//go:build !linux
|
||||||
|
|
||||||
package aghnet
|
package ipset
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newIpsetMgr(_ []string) (mgr IpsetManager, err error) {
|
func newManager(_ []string) (mgr Manager, err error) {
|
||||||
return nil, aghos.Unsupported("ipset")
|
return nil, aghos.Unsupported("ipset")
|
||||||
}
|
}
|
Loading…
Reference in New Issue