netset: add nftables set support
Some checks failed
Build / Build (push) Has been cancelled

This commit is contained in:
nadoo 2026-07-15 23:41:21 +08:00
parent 6a7fc56a5e
commit 43552b60c9
17 changed files with 504 additions and 126 deletions

View File

@ -5,7 +5,7 @@
[![Actions Status](https://img.shields.io/github/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions) [![Actions Status](https://img.shields.io/github/actions/workflow/status/nadoo/glider/build.yml?branch=dev&style=flat-square)](https://github.com/nadoo/glider/actions)
[![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](https://hub.docker.com/r/nadoo/glider) [![DockerHub](https://img.shields.io/docker/image-size/nadoo/glider?color=blue&label=docker&style=flat-square)](https://hub.docker.com/r/nadoo/glider)
glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset management features. glider is a forward proxy with multiple protocols support, and also a dns/dhcp server with ipset and nftables set management features.
we can set up local listeners as proxy servers, and forward requests to internet via forwarders. we can set up local listeners as proxy servers, and forward requests to internet via forwarders.
@ -28,10 +28,10 @@ we can set up local listeners as proxy servers, and forward requests to internet
- dns over proxy - dns over proxy
- force upstream querying by tcp - force upstream querying by tcp
- association rules between dns and forwarder choosing - association rules between dns and forwarder choosing
- association rules between dns and ipset - association rules between dns and ipset/nftables sets
- dns cache support - dns cache support
- custom dns record - custom dns record
- IPSet management (linux kernel version >= 2.6.32): - IPSet and nftables set management on Linux:
- add ip/cidrs from rule files on startup - add ip/cidrs from rule files on startup
- add resolved ips for domains from rule files by dns forwarding server - add resolved ips for domains from rule files by dns forwarding server
- Serve http and socks5 on the same port - Serve http and socks5 on the same port
@ -541,7 +541,7 @@ glider -config CONFIG_PATH
## Links ## Links
- [ipset](https://github.com/nadoo/ipset): netlink ipset package for Go. - [netset](https://github.com/nadoo/netset): netlink ipset and nftables set package for Go.
- [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support. - [conflag](https://github.com/nadoo/conflag): a drop-in replacement for Go's standard flag package with config file support.
- [ArchLinux](https://archlinux.org/packages/extra/x86_64/glider): a great linux distribution with glider pre-built package. - [ArchLinux](https://archlinux.org/packages/extra/x86_64/glider): a great linux distribution with glider pre-built package.
- [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40` - [urlencode](https://www.w3schools.com/tags/ref_urlencode.asp): you should encode special characters in scheme url. e.g., `@`->`%40`

View File

@ -73,14 +73,22 @@ checkinterval=30
# DNS SERVER for domains in this rule file # DNS SERVER for domains in this rule file
dnsserver=208.67.222.222:53 dnsserver=208.67.222.222:53
# IPSET MANAGEMENT # NETFILTER SET MANAGEMENT
# ---------------- # ------------------------
# Create and mange ipset on linux based on destinations in rule files # Create and manage ipset or nftables sets on linux based on destinations in rule files
# - add ip/cidrs in rule files on startup # - add ip/cidrs in rule files on startup
# - add resolved ips for domains in rule files by dns forwarding server # - add resolved ips for domains in rule files by dns forwarding server
# Usually used in transparent proxy mode on linux # Usually used in transparent proxy mode on linux
# This creates glider for ipv4 and glider6 for ipv6
ipset=glider ipset=glider
# nftset uses [[FAMILY/]TABLE/]NAME. FAMILY defaults to inet and TABLE to glider.
# The nftables table must already exist. This also creates NAME and NAME6.
# nftset=glider
# nftset=custom_table/glider
# nftset=inet/custom_table/glider
# ipset and nftset can both be enabled in the same rule file during migration.
# YOU CAN SPECIFY DESTINATIONS TO USE THE ABOVE FORWARDERS # YOU CAN SPECIFY DESTINATIONS TO USE THE ABOVE FORWARDERS
# matches abc.com and *.abc.com # matches abc.com and *.abc.com
domain=abc.com domain=abc.com

View File

@ -6,7 +6,7 @@ PC Client -> Gateway with glider running(linux box) -> Upstream Forwarders -> In
#### In this mode, glider will act as the following roles: #### In this mode, glider will act as the following roles:
1. A transparent proxy server 1. A transparent proxy server
2. A dns forwarding server 2. A dns forwarding server
3. A ipset manager 3. An ipset or nftables set manager
so you don't need any dns server in your network. so you don't need any dns server in your network.
@ -76,6 +76,27 @@ iptables -t nat -I PREROUTING -p tcp -m set --match-set glider dst -j REDIRECT -
iptables -t nat -I OUTPUT -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081 iptables -t nat -I OUTPUT -p tcp -m set --match-set glider dst -j REDIRECT --to-ports 1081
``` ```
#### Nftables alternative
Use `nftset=glider` instead of `ipset=glider` in the rule file. It means
`inet/glider/glider` for IPv4 and `inet/glider/glider6` for IPv6. The table
must already exist:
```bash
nft add table inet glider
```
Glider creates both interval sets and flushes them on startup. If nftables
rules are loaded before glider starts, create compatible sets first:
```bash
nft 'add set inet glider glider { type ipv4_addr; flags interval; }'
nft 'add set inet glider glider6 { type ipv6_addr; flags interval; }'
nft 'add chain inet glider prerouting { type nat hook prerouting priority dstnat; policy accept; }'
nft 'add rule inet glider prerouting ip daddr @glider tcp redirect to :1081'
nft 'add rule inet glider prerouting ip6 daddr @glider6 tcp redirect to :1081'
```
#### Server DNS settings #### Server DNS settings
Set server's nameserver to glider: Set server's nameserver to glider:
```bash ```bash

View File

@ -3,7 +3,7 @@
# / /`_ | | | | | | \ | |_ | |_) # / /`_ | | | | | | \ | |_ | |_)
# \_\_/ |_|__ |_| |_|_/ |_|__ |_| \ # \_\_/ |_|__ |_| |_|_/ |_|__ |_| \
# #
# Glider is a forward proxy with multiple protocols support, and also a dns forwarding server with ipset management features(like dnsmasq). # Glider is a forward proxy with multiple protocols support, and also a dns forwarding server with ipset and nftables set management features(like dnsmasq).
# #
# We can set up local listeners as proxy, and forward requests to internet via forwarders. # We can set up local listeners as proxy, and forward requests to internet via forwarders.
# #

View File

@ -20,15 +20,19 @@ checkinterval=30
# DNS SERVER for domains in this rule file # DNS SERVER for domains in this rule file
dnsserver=208.67.222.222:53 dnsserver=208.67.222.222:53
# IPSET MANAGEMENT # NETFILTER SET MANAGEMENT
# ---------------- # ------------------------
# Create and mange ipset on linux based on destinations in rule files # Create and manage ipset or nftables sets on linux based on destinations in rule files
# - add ip/cidrs in rule files on startup # - add ip/cidrs in rule files on startup
# - add resolved ips for domains in rule files by dns forwarding server # - add resolved ips for domains in rule files by dns forwarding server
# Usually used in transparent proxy mode on linux # Usually used in transparent proxy mode on linux
# Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6 # Note: this will create 2 ipsets, glider for ipv4 and glider6 for ipv6
ipset=glider ipset=glider
# nftset uses [[FAMILY/]TABLE/]NAME. FAMILY defaults to inet and TABLE to glider.
# The nftables table must already exist. This creates glider and glider6.
# nftset=glider
# DESTINATIONS # DESTINATIONS
# ------------ # ------------
# ALL destinations matches the following rules will be forward using forwarders specified above # ALL destinations matches the following rules will be forward using forwarders specified above

View File

@ -143,7 +143,9 @@ func (c *Client) extractAnswer(resp *Message) ([]string, int) {
if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA { if answer.TYPE == QTypeA || answer.TYPE == QTypeAAAA {
if answer.IP.IsValid() && !answer.IP.IsUnspecified() { if answer.IP.IsValid() && !answer.IP.IsUnspecified() {
for _, h := range c.handlers { for _, h := range c.handlers {
h(resp.Question.QNAME, answer.IP) if err := h(resp.Question.QNAME, answer.IP); err != nil {
log.F("[dns] answer handler error for %s/%s: %s", resp.Question.QNAME, answer.IP, err)
}
} }
ips = append(ips, answer.IP.String()) ips = append(ips, answer.IP.String())
} }

2
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e github.com/insomniacslk/dhcp v0.0.0-20260603135910-a415979eb11e
github.com/nadoo/conflag v0.3.1 github.com/nadoo/conflag v0.3.1
github.com/nadoo/ipset v0.5.0 github.com/nadoo/netset v0.1.0
github.com/xtaci/kcp-go/v5 v5.6.72 github.com/xtaci/kcp-go/v5 v5.6.72
golang.org/x/crypto v0.54.0 golang.org/x/crypto v0.54.0
golang.org/x/sys v0.47.0 golang.org/x/sys v0.47.0

4
go.sum
View File

@ -39,8 +39,8 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw= github.com/nadoo/conflag v0.3.1 h1:4pHkLIz8PUsfg6ajNYRRSY3bt6m2LPsu6KOzn5uIXQw=
github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co= github.com/nadoo/conflag v0.3.1/go.mod h1:dzFfDUpXdr2uS2oV+udpy5N2vfNOu/bFzjhX1WI52co=
github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/netset v0.1.0 h1:FNdGCVtoSjBX0Ctzve3BWwTOSXRxK/oSPBzS3/SemY4=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= github.com/nadoo/netset v0.1.0/go.mod h1:w2GPCnz+TxqH+HJIrhOEC4oZAeQ5Crefw4lToBIxEww=
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@ -1,78 +0,0 @@
package ipset
import (
"net/netip"
"strings"
"sync"
"github.com/nadoo/ipset"
"github.com/nadoo/glider/rule"
)
// Manager struct.
type Manager struct {
domainSet sync.Map
}
// NewManager returns a Manager
func NewManager(rules []*rule.Config) (*Manager, error) {
if err := ipset.Init(); err != nil {
return nil, err
}
m := &Manager{}
sets := make(map[string]struct{})
for _, r := range rules {
if r.IPSet == "" {
continue
}
if _, ok := sets[r.IPSet]; !ok {
sets[r.IPSet] = struct{}{}
ipset.Create(r.IPSet)
ipset.Flush(r.IPSet)
ipset.Create(r.IPSet+"6", ipset.OptIPv6())
ipset.Flush(r.IPSet + "6")
}
for _, domain := range r.Domain {
m.domainSet.Store(domain, r.IPSet)
}
for _, ip := range r.IP {
addToSet(r.IPSet, ip)
}
for _, cidr := range r.CIDR {
addToSet(r.IPSet, cidr)
}
}
return m, nil
}
// AddDomainIP implements the dns AnswerHandler function, used to update ipset according to domainSet rule.
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
domain = strings.ToLower(domain)
for i := len(domain); i != -1; {
i = strings.LastIndexByte(domain[:i], '.')
if setName, ok := m.domainSet.Load(domain[i+1:]); ok {
addAddrToSet(setName.(string), ip)
}
}
return nil
}
func addToSet(s, item string) error {
if strings.IndexByte(item, '.') == -1 {
return ipset.Add(s+"6", item)
}
return ipset.Add(s, item)
}
func addAddrToSet(s string, ip netip.Addr) error {
if ip.Is4() {
return ipset.AddAddr(s, ip)
}
return ipset.AddAddr(s+"6", ip)
}

View File

@ -1,23 +0,0 @@
//go:build !linux
package ipset
import (
"errors"
"net/netip"
"github.com/nadoo/glider/rule"
)
// Manager struct
type Manager struct{}
// NewManager returns a Manager
func NewManager(rules []*rule.Config) (*Manager, error) {
return nil, errors.New("ipset not supported on this os")
}
// AddDomainIP implements the DNSAnswerHandler function
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
return errors.New("ipset not supported on this os")
}

17
main.go
View File

@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/nadoo/glider/dns" "github.com/nadoo/glider/dns"
"github.com/nadoo/glider/ipset" "github.com/nadoo/glider/netset"
"github.com/nadoo/glider/pkg/log" "github.com/nadoo/glider/pkg/log"
"github.com/nadoo/glider/proxy" "github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/rule" "github.com/nadoo/glider/rule"
@ -25,8 +25,15 @@ func main() {
// global rule proxy // global rule proxy
pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules) pxy := rule.NewProxy(config.Forwards, &config.Strategy, config.rules)
// ipset manager // ipset and nftables set manager
ipsetM, _ := ipset.NewManager(config.rules) netsetM, _ := netset.NewManager(config.rules)
if netsetM != nil {
defer func() {
if err := netsetM.Close(); err != nil {
log.F("[netset] close error: %s", err)
}
}()
}
// check and setup dns server // check and setup dns server
if config.DNS != "" { if config.DNS != "" {
@ -46,8 +53,8 @@ func main() {
// add a handler to update proxy rules when a domain resolved // add a handler to update proxy rules when a domain resolved
d.AddHandler(pxy.AddDomainIP) d.AddHandler(pxy.AddDomainIP)
if ipsetM != nil { if netsetM != nil {
d.AddHandler(ipsetM.AddDomainIP) d.AddHandler(netsetM.AddDomainIP)
} }
d.Start() d.Start()

92
netset/config.go Normal file
View File

@ -0,0 +1,92 @@
package netset
import (
"fmt"
"strings"
libnetset "github.com/nadoo/netset"
)
const (
defaultNFTFamily = "inet"
defaultNFTTable = "glider"
maxNFTNameLength = 256
)
type nftSetConfig struct {
family libnetset.Family
familyName string
table string
name string
}
func parseNFTSetConfig(value string) (nftSetConfig, error) {
parts := strings.Split(strings.TrimSpace(value), "/")
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
config := nftSetConfig{
family: libnetset.FamilyINet,
familyName: defaultNFTFamily,
table: defaultNFTTable,
}
switch len(parts) {
case 1:
config.name = parts[0]
case 2:
config.table = parts[0]
config.name = parts[1]
case 3:
config.familyName = parts[0]
config.table = parts[1]
config.name = parts[2]
default:
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: expected [[FAMILY/]TABLE/]NAME", value)
}
if config.familyName == "" || config.table == "" || config.name == "" {
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: family, table, and set name must not be empty", value)
}
family, ok := nftFamilies[strings.ToLower(config.familyName)]
if !ok {
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: unsupported family %q", value, config.familyName)
}
config.family = family
config.familyName = strings.ToLower(config.familyName)
if err := validateNFTName("table", config.table); err != nil {
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: %w", value, err)
}
if err := validateNFTName("set", config.name); err != nil {
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: %w", value, err)
}
if err := validateNFTName("IPv6 set", config.name+"6"); err != nil {
return nftSetConfig{}, fmt.Errorf("invalid nftset %q: %w", value, err)
}
return config, nil
}
func validateNFTName(kind, name string) error {
if strings.IndexByte(name, 0) >= 0 {
return fmt.Errorf("%s name must not contain NUL", kind)
}
if len(name) >= maxNFTNameLength {
return fmt.Errorf("%s name is too long", kind)
}
return nil
}
func (c nftSetConfig) key() string {
return c.familyName + "/" + c.table + "/" + c.name
}
var nftFamilies = map[string]libnetset.Family{
"inet": libnetset.FamilyINet,
"ip": libnetset.FamilyIPv4,
"ip6": libnetset.FamilyIPv6,
"arp": libnetset.FamilyARP,
"netdev": libnetset.FamilyNetdev,
"bridge": libnetset.FamilyBridge,
}

200
netset/manager.go Normal file
View File

@ -0,0 +1,200 @@
package netset
import (
"errors"
"fmt"
"net/netip"
"strings"
"github.com/nadoo/glider/rule"
)
type addressSet interface {
AddAddr(netip.Addr) error
AddPrefix(netip.Prefix) error
}
type setPair struct {
label string
ipv4 addressSet
ipv6 addressSet
}
func (p *setPair) addAddr(ip netip.Addr) error {
if ip.Is4() {
return p.ipv4.AddAddr(ip)
}
return p.ipv6.AddAddr(ip)
}
func (p *setPair) addPrefix(prefix netip.Prefix) error {
if prefix.Addr().Is4() {
return p.ipv4.AddPrefix(prefix)
}
return p.ipv6.AddPrefix(prefix)
}
type backend interface {
IPSetPair(name string) (*setPair, error)
NFTSetPair(config nftSetConfig) (*setPair, error)
Close() error
}
// Manager manages the ipset and nftables sets associated with rules.
type Manager struct {
backend backend
domainSets map[string][]*setPair
pairs map[string]*setPair
}
// NewManager creates and populates all sets configured by rules.
func NewManager(rules []*rule.Config) (*Manager, error) {
if !hasSetConfig(rules) {
return nil, nil
}
b, err := newBackend()
if err != nil {
return nil, err
}
return newManager(rules, b)
}
func newManager(rules []*rule.Config, b backend) (*Manager, error) {
m := &Manager{
backend: b,
domainSets: make(map[string][]*setPair),
pairs: make(map[string]*setPair),
}
for _, config := range rules {
if err := m.addRule(config); err != nil {
return nil, errors.Join(err, b.Close())
}
}
return m, nil
}
func hasSetConfig(rules []*rule.Config) bool {
for _, config := range rules {
if config.IPSet != "" || config.NFTSet != "" {
return true
}
}
return false
}
func (m *Manager) addRule(config *rule.Config) error {
pairs := make([]*setPair, 0, 2)
if config.IPSet != "" {
pair, err := m.pair("ipset:"+config.IPSet, func() (*setPair, error) {
return m.backend.IPSetPair(config.IPSet)
})
if err != nil {
return fmt.Errorf("rule %q: initialize ipset %q: %w", config.RulePath, config.IPSet, err)
}
pairs = append(pairs, pair)
}
if config.NFTSet != "" {
nftConfig, err := parseNFTSetConfig(config.NFTSet)
if err != nil {
return fmt.Errorf("rule %q: %w", config.RulePath, err)
}
pair, err := m.pair("nftset:"+nftConfig.key(), func() (*setPair, error) {
return m.backend.NFTSetPair(nftConfig)
})
if err != nil {
return fmt.Errorf("rule %q: initialize nftset %q: %w", config.RulePath, nftConfig.key(), err)
}
pairs = append(pairs, pair)
}
for _, item := range config.IP {
ip, err := netip.ParseAddr(item)
if err != nil {
return fmt.Errorf("rule %q: parse set IP %q: %w", config.RulePath, item, err)
}
for _, pair := range pairs {
if err := pair.addAddr(ip); err != nil {
return fmt.Errorf("rule %q: add IP %q to %s: %w", config.RulePath, item, pair.label, err)
}
}
}
for _, item := range config.CIDR {
prefix, err := netip.ParsePrefix(item)
if err != nil {
return fmt.Errorf("rule %q: parse set CIDR %q: %w", config.RulePath, item, err)
}
for _, pair := range pairs {
if err := pair.addPrefix(prefix); err != nil {
return fmt.Errorf("rule %q: add CIDR %q to %s: %w", config.RulePath, item, pair.label, err)
}
}
}
for _, domain := range config.Domain {
domain = strings.ToLower(domain)
for _, pair := range pairs {
m.domainSets[domain] = appendPair(m.domainSets[domain], pair)
}
}
return nil
}
func (m *Manager) pair(key string, create func() (*setPair, error)) (*setPair, error) {
if pair, ok := m.pairs[key]; ok {
return pair, nil
}
pair, err := create()
if err != nil {
return nil, err
}
m.pairs[key] = pair
return pair, nil
}
func appendPair(pairs []*setPair, pair *setPair) []*setPair {
for _, item := range pairs {
if item == pair {
return pairs
}
}
return append(pairs, pair)
}
// AddDomainIP updates all sets associated with domain or one of its parents.
func (m *Manager) AddDomainIP(domain string, ip netip.Addr) error {
if !ip.IsValid() {
return errors.New("netset: invalid DNS answer address")
}
domain = strings.ToLower(domain)
seen := make(map[*setPair]struct{})
var errs []error
for i := len(domain); i != -1; {
i = strings.LastIndexByte(domain[:i], '.')
for _, pair := range m.domainSets[domain[i+1:]] {
if _, ok := seen[pair]; ok {
continue
}
seen[pair] = struct{}{}
if err := pair.addAddr(ip); err != nil {
errs = append(errs, fmt.Errorf("add DNS answer %s for %q to %s: %w", ip, domain, pair.label, err))
}
}
}
return errors.Join(errs...)
}
// Close closes all netlink sockets owned by the manager.
func (m *Manager) Close() error {
if m == nil || m.backend == nil {
return nil
}
return m.backend.Close()
}

130
netset/manager_linux.go Normal file
View File

@ -0,0 +1,130 @@
//go:build linux
package netset
import (
"errors"
"fmt"
"net/netip"
"syscall"
libnetset "github.com/nadoo/netset"
)
type linuxBackend struct {
ipset *libnetset.IPSet
nftsets []*libnetset.NFTSet
}
func newBackend() (backend, error) {
return &linuxBackend{}, nil
}
func (b *linuxBackend) IPSetPair(name string) (*setPair, error) {
if b.ipset == nil {
client, err := libnetset.NewIPSet()
if err != nil {
return nil, err
}
b.ipset = client
}
if err := ensureIPSet(b.ipset, name); err != nil {
return nil, err
}
if err := ensureIPSet(b.ipset, name+"6", libnetset.OptIPv6()); err != nil {
return nil, err
}
return &setPair{
label: "ipset " + name,
ipv4: ipSetTarget{client: b.ipset, name: name},
ipv6: ipSetTarget{client: b.ipset, name: name + "6"},
}, nil
}
func ensureIPSet(client *libnetset.IPSet, name string, opts ...libnetset.Option) error {
if err := client.Create(name, opts...); err != nil && !errors.Is(err, syscall.EEXIST) {
return fmt.Errorf("create %q: %w", name, err)
}
if err := client.Flush(name); err != nil {
return fmt.Errorf("flush %q: %w", name, err)
}
return nil
}
func (b *linuxBackend) NFTSetPair(config nftSetConfig) (*setPair, error) {
ipv4, err := b.newNFTSet(config, config.name, false)
if err != nil {
return nil, err
}
ipv6, err := b.newNFTSet(config, config.name+"6", true)
if err != nil {
return nil, err
}
return &setPair{
label: "nftset " + config.key(),
ipv4: nftSetTarget{set: ipv4},
ipv6: nftSetTarget{set: ipv6},
}, nil
}
func (b *linuxBackend) newNFTSet(config nftSetConfig, name string, ipv6 bool) (*libnetset.NFTSet, error) {
opts := []libnetset.Option{libnetset.OptFamily(config.family), libnetset.OptInterval()}
if ipv6 {
opts = append(opts, libnetset.OptIPv6())
}
set, err := libnetset.NewNFTSet(config.table, name, opts...)
if err != nil {
return nil, fmt.Errorf("open %s/%s/%s: %w", config.familyName, config.table, name, err)
}
b.nftsets = append(b.nftsets, set)
if err := set.Create(); err != nil && !errors.Is(err, syscall.EEXIST) {
return nil, fmt.Errorf("create %s/%s/%s: %w", config.familyName, config.table, name, err)
}
if err := set.Flush(); err != nil {
return nil, fmt.Errorf("flush %s/%s/%s: %w", config.familyName, config.table, name, err)
}
return set, nil
}
func (b *linuxBackend) Close() error {
var errs []error
if b.ipset != nil {
errs = append(errs, b.ipset.Close())
b.ipset = nil
}
for _, set := range b.nftsets {
errs = append(errs, set.Close())
}
b.nftsets = nil
return errors.Join(errs...)
}
type ipSetTarget struct {
client *libnetset.IPSet
name string
}
func (s ipSetTarget) AddAddr(ip netip.Addr) error {
return s.client.AddAddr(s.name, ip)
}
func (s ipSetTarget) AddPrefix(prefix netip.Prefix) error {
return s.client.AddPrefix(s.name, prefix)
}
type nftSetTarget struct {
set *libnetset.NFTSet
}
func (s nftSetTarget) AddAddr(ip netip.Addr) error {
return s.set.AddAddr(ip)
}
func (s nftSetTarget) AddPrefix(prefix netip.Prefix) error {
return s.set.AddPrefix(prefix)
}

13
netset/manager_other.go Normal file
View File

@ -0,0 +1,13 @@
//go:build !linux
package netset
import (
"fmt"
libnetset "github.com/nadoo/netset"
)
func newBackend() (backend, error) {
return nil, fmt.Errorf("netset manager is supported only on linux: %w", libnetset.ErrUnsupported)
}

View File

@ -16,6 +16,7 @@ type Config struct {
DNSServers []string DNSServers []string
IPSet string IPSet string
NFTSet string
Domain []string Domain []string
IP []string IP []string
@ -57,6 +58,7 @@ func NewConfFromFile(ruleFile string) (*Config, error) {
f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server") f.StringSliceUniqVar(&p.DNSServers, "dnsserver", nil, "remote dns server")
f.StringVar(&p.IPSet, "ipset", "", "ipset NAME, will create 2 sets: NAME for ipv4 and NAME6 for ipv6") f.StringVar(&p.IPSet, "ipset", "", "ipset NAME, will create 2 sets: NAME for ipv4 and NAME6 for ipv6")
f.StringVar(&p.NFTSet, "nftset", "", "nftables set [[FAMILY/]TABLE/]NAME, defaults to FAMILY=inet and TABLE=glider; will create NAME for ipv4 and NAME6 for ipv6")
f.StringSliceVar(&p.Domain, "domain", nil, "domain") f.StringSliceVar(&p.Domain, "domain", nil, "domain")
f.StringSliceVar(&p.IP, "ip", nil, "ip") f.StringSliceVar(&p.IP, "ip", nil, "ip")

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Glider Service (%i) Description=Glider Service (%i)
After=network.target iptables.service ip6tables.service After=network.target iptables.service ip6tables.service nftables.service
[Service] [Service]
Type=simple Type=simple
@ -13,7 +13,7 @@ ExecStart=/usr/bin/glider -config /etc/glider/%i.conf
# NOTE: # NOTE:
# work with systemd v229 or later, so glider can listen on port below 1024 with none-root user # work with systemd v229 or later, so glider can listen on port below 1024 with none-root user
# CAP_NET_ADMIN: ipset, setsockopt: IP_TRANSPARENT # CAP_NET_ADMIN: ipset, nftables set, setsockopt: IP_TRANSPARENT
# CAP_NET_BIND_SERVICE: bind ports under 1024 # CAP_NET_BIND_SERVICE: bind ports under 1024
# CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd) # CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd)
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW