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:
Stanislav Chzhen 2023-10-06 13:16:39 +03:00
parent 5d7e59e37a
commit ef88f7462f
5 changed files with 63 additions and 61 deletions

View File

@ -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

View File

@ -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)
} }

View File

@ -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()

View File

@ -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,
}, },

View File

@ -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")
} }