diff --git a/component/ipset/ipset_linux.go b/component/ipset/ipset_linux.go new file mode 100644 index 0000000..a00cef6 --- /dev/null +++ b/component/ipset/ipset_linux.go @@ -0,0 +1,22 @@ +//go:build linux + +package ipset + +import ( + "net" + + "github.com/vishvananda/netlink" +) + +// Test whether the ip is in the set or not +func Test(setName string, ip net.IP) (bool, error) { + return netlink.IpsetTest(setName, &netlink.IPSetEntry{ + IP: ip, + }) +} + +// Verify dumps a specific ipset to check if we can use the set normally +func Verify(setName string) error { + _, err := netlink.IpsetList(setName) + return err +} diff --git a/component/ipset/ipset_others.go b/component/ipset/ipset_others.go new file mode 100644 index 0000000..546e5c3 --- /dev/null +++ b/component/ipset/ipset_others.go @@ -0,0 +1,17 @@ +//go:build !linux + +package ipset + +import ( + "net" +) + +// Always return false in non-linux +func Test(setName string, ip net.IP) (bool, error) { + return false, nil +} + +// Always pass in non-linux +func Verify(setName string) error { + return nil +} diff --git a/constant/rule.go b/constant/rule.go index 9e1908f..5cbf8d9 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -12,6 +12,7 @@ const ( DstPort Process ProcessPath + IPSet MATCH ) @@ -39,6 +40,8 @@ func (rt RuleType) String() string { return "Process" case ProcessPath: return "ProcessPath" + case IPSet: + return "IPSet" case MATCH: return "Match" default: diff --git a/go.mod b/go.mod index fe736cc..94b9323 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/samber/lo v1.38.1 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.2 + github.com/vishvananda/netlink v1.2.1-beta.2.0.20230420174744-55c8b9515a01 go.etcd.io/bbolt v1.3.7 go.uber.org/atomic v1.10.0 go.uber.org/automaxprocs v1.5.2 @@ -37,6 +38,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect + github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect diff --git a/go.sum b/go.sum index d0e6fea..c97bca2 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,10 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= +github.com/vishvananda/netlink v1.2.1-beta.2.0.20230420174744-55c8b9515a01 h1:F9xjJm4IH8VjcqG4ujciOF+GIM4mjPkHhWLLzOghPtM= +github.com/vishvananda/netlink v1.2.1-beta.2.0.20230420174744-55c8b9515a01/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= @@ -71,8 +75,10 @@ golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= diff --git a/rule/ipset.go b/rule/ipset.go new file mode 100644 index 0000000..07999ec --- /dev/null +++ b/rule/ipset.go @@ -0,0 +1,54 @@ +package rules + +import ( + "github.com/Dreamacro/clash/component/ipset" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" +) + +type IPSet struct { + name string + adapter string + noResolveIP bool +} + +func (f *IPSet) RuleType() C.RuleType { + return C.IPSet +} + +func (f *IPSet) Match(metadata *C.Metadata) bool { + exist, err := ipset.Test(f.name, metadata.DstIP) + if err != nil { + log.Warnln("check ipset '%s' failed: %s", f.name, err.Error()) + return false + } + return exist +} + +func (f *IPSet) Adapter() string { + return f.adapter +} + +func (f *IPSet) Payload() string { + return f.name +} + +func (f *IPSet) ShouldResolveIP() bool { + return !f.noResolveIP +} + +func (f *IPSet) ShouldFindProcess() bool { + return false +} + +func NewIPSet(name string, adapter string, noResolveIP bool) (*IPSet, error) { + if err := ipset.Verify(name); err != nil { + return nil, err + } + + return &IPSet{ + name: name, + adapter: adapter, + noResolveIP: noResolveIP, + }, nil +} diff --git a/rule/parser.go b/rule/parser.go index 212029b..a26ef0a 100644 --- a/rule/parser.go +++ b/rule/parser.go @@ -35,6 +35,9 @@ func ParseRule(tp, payload, target string, params []string) (C.Rule, error) { parsed, parseErr = NewProcess(payload, target, true) case "PROCESS-PATH": parsed, parseErr = NewProcess(payload, target, false) + case "IPSET": + noResolve := HasNoResolve(params) + parsed, parseErr = NewIPSet(payload, target, noResolve) case "MATCH": parsed = NewMatch(target) default: