mirror of
https://github.com/nadoo/glider.git
synced 2026-07-17 17:30:11 +08:00
This commit is contained in:
parent
6a7fc56a5e
commit
43552b60c9
@ -5,7 +5,7 @@
|
|||||||
[](https://github.com/nadoo/glider/actions)
|
[](https://github.com/nadoo/glider/actions)
|
||||||
[](https://hub.docker.com/r/nadoo/glider)
|
[](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`
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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.
|
||||||
#
|
#
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
@ -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
17
main.go
@ -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
92
netset/config.go
Normal 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
200
netset/manager.go
Normal 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
130
netset/manager_linux.go
Normal 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
13
netset/manager_other.go
Normal 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)
|
||||||
|
}
|
||||||
@ -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")
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user