mirror of
https://github.com/nadoo/glider.git
synced 2025-04-21 19:52:07 +08:00
feat: support routingA
This commit is contained in:
parent
10b7f2d5e6
commit
05559703a3
12
config.go
12
config.go
@ -25,6 +25,7 @@ type Config struct {
|
|||||||
|
|
||||||
RuleFiles []string
|
RuleFiles []string
|
||||||
RulesDir string
|
RulesDir string
|
||||||
|
RoutingAFile string
|
||||||
|
|
||||||
DNS string
|
DNS string
|
||||||
DNSConfig dns.Config
|
DNSConfig dns.Config
|
||||||
@ -56,6 +57,7 @@ func parseConfig() *Config {
|
|||||||
|
|
||||||
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path")
|
||||||
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder")
|
||||||
|
flag.StringVar(&conf.RoutingAFile, "routingA", "", "routingA file path")
|
||||||
|
|
||||||
// dns configs
|
// dns configs
|
||||||
flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address")
|
flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address")
|
||||||
@ -118,6 +120,16 @@ func parseConfig() *Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.RoutingAFile != "" {
|
||||||
|
if !path.IsAbs(conf.RoutingAFile) {
|
||||||
|
conf.RoutingAFile = path.Join(flag.ConfDir(), conf.RoutingAFile)
|
||||||
|
}
|
||||||
|
conf.Strategy.RoutingA, err = rule.ParseRoutingA(conf.RoutingAFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) (
|
|||||||
|
|
||||||
// use tcp to connect upstream server default
|
// use tcp to connect upstream server default
|
||||||
network = "tcp"
|
network = "tcp"
|
||||||
dialer := c.proxy.NextDialer(qname + ":53")
|
dialer := c.proxy.NextDialer(network, qname+":53")
|
||||||
|
|
||||||
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
// if we are resolving the dialer's domain, then use Direct to avoid denpency loop
|
||||||
// TODO: dialer.Addr() == "REJECT", tricky
|
// TODO: dialer.Addr() == "REJECT", tricky
|
||||||
|
1
go.mod
1
go.mod
@ -13,6 +13,7 @@ require (
|
|||||||
github.com/nadoo/conflag v0.2.3
|
github.com/nadoo/conflag v0.2.3
|
||||||
github.com/nadoo/ipset v0.3.0
|
github.com/nadoo/ipset v0.3.0
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/v2rayA/routingA v0.0.0-20201204065601-aef348ea7aa1
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1
|
github.com/xtaci/kcp-go/v5 v5.6.1
|
||||||
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
|
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
|
||||||
golang.org/x/mod v0.4.0 // indirect
|
golang.org/x/mod v0.4.0 // indirect
|
||||||
|
4
go.sum
4
go.sum
@ -11,6 +11,8 @@ github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvS
|
|||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow=
|
||||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||||
|
github.com/gocarina/gocsv v0.0.0-20201103164230-b291445e0dd2 h1:DTpqi8htDqlk4dGMxZ3+7BVX2OoMki9akiCHWQpSXfA=
|
||||||
|
github.com/gocarina/gocsv v0.0.0-20201103164230-b291445e0dd2/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
@ -73,6 +75,8 @@ github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
|
|||||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||||
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
|
||||||
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
|
||||||
|
github.com/v2rayA/routingA v0.0.0-20201204065601-aef348ea7aa1 h1:PITT+h0KRoBQwifYJr9vLqkhPH0UyJTonP+sIa5yGa4=
|
||||||
|
github.com/v2rayA/routingA v0.0.0-20201204065601-aef348ea7aa1/go.mod h1:V86dy1jY9VJKMrEinUjUQTclZaQQv+nIXBuFCSf67A4=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
|
github.com/xtaci/kcp-go/v5 v5.6.1 h1:Pwn0aoeNSPF9dTS7IgiPXn0HEtaIlVb6y5UKWPsx8bI=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
|
github.com/xtaci/kcp-go/v5 v5.6.1/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||||
|
@ -11,7 +11,7 @@ type Proxy interface {
|
|||||||
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, writeTo net.Addr, err error)
|
DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, writeTo net.Addr, err error)
|
||||||
|
|
||||||
// Get the dialer by dstAddr.
|
// Get the dialer by dstAddr.
|
||||||
NextDialer(dstAddr string) Dialer
|
NextDialer(network, dstAddr string) Dialer
|
||||||
|
|
||||||
// Record records result while using the dialer from proxy.
|
// Record records result while using the dialer from proxy.
|
||||||
Record(dialer Dialer, success bool)
|
Record(dialer Dialer, success bool)
|
||||||
|
@ -60,7 +60,7 @@ func (s *SS) Serve(c net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
dialer := s.proxy.NextDialer(tgt.String())
|
dialer := s.proxy.NextDialer(network, tgt.String())
|
||||||
|
|
||||||
rc, err := dialer.Dial(network, tgt.String())
|
rc, err := dialer.Dial(network, tgt.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -106,7 +106,7 @@ func (s *Trojan) Serve(c net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
dialer := s.proxy.NextDialer(target.String())
|
dialer := s.proxy.NextDialer(network, target.String())
|
||||||
|
|
||||||
if cmd == socks.CmdUDPAssociate {
|
if cmd == socks.CmdUDPAssociate {
|
||||||
// there is no upstream proxy, just serve it
|
// there is no upstream proxy, just serve it
|
||||||
@ -137,7 +137,7 @@ func (s *Trojan) Serve(c net.Conn) {
|
|||||||
|
|
||||||
func (s *Trojan) serveFallback(c net.Conn, tgt string, headBuf *bytes.Buffer) {
|
func (s *Trojan) serveFallback(c net.Conn, tgt string, headBuf *bytes.Buffer) {
|
||||||
// TODO: should we access fallback directly or via proxy?
|
// TODO: should we access fallback directly or via proxy?
|
||||||
dialer := s.proxy.NextDialer(tgt)
|
dialer := s.proxy.NextDialer("tcp", tgt)
|
||||||
rc, err := dialer.Dial("tcp", tgt)
|
rc, err := dialer.Dial("tcp", tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[trojan-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[trojan-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
|
@ -63,7 +63,7 @@ func (s *VLess) Serve(c net.Conn) {
|
|||||||
c = NewServerConn(c)
|
c = NewServerConn(c)
|
||||||
|
|
||||||
network := "tcp"
|
network := "tcp"
|
||||||
dialer := s.proxy.NextDialer(target)
|
dialer := s.proxy.NextDialer(network, target)
|
||||||
|
|
||||||
if cmd == CmdUDP {
|
if cmd == CmdUDP {
|
||||||
// there is no upstream proxy, just serve it
|
// there is no upstream proxy, just serve it
|
||||||
@ -94,7 +94,7 @@ func (s *VLess) Serve(c net.Conn) {
|
|||||||
|
|
||||||
func (s *VLess) serveFallback(c net.Conn, tgt string, headBuf *bytes.Buffer) {
|
func (s *VLess) serveFallback(c net.Conn, tgt string, headBuf *bytes.Buffer) {
|
||||||
// TODO: should we access fallback directly or via proxy?
|
// TODO: should we access fallback directly or via proxy?
|
||||||
dialer := s.proxy.NextDialer(tgt)
|
dialer := s.proxy.NextDialer("tcp", tgt)
|
||||||
rc, err := dialer.Dial("tcp", tgt)
|
rc, err := dialer.Dial("tcp", tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.F("[vless-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
log.F("[vless-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/v2rayA/routingA"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nadoo/conflag"
|
"github.com/nadoo/conflag"
|
||||||
@ -35,11 +37,12 @@ type Strategy struct {
|
|||||||
DialTimeout int
|
DialTimeout int
|
||||||
RelayTimeout int
|
RelayTimeout int
|
||||||
IntFace string
|
IntFace string
|
||||||
|
RoutingA *RoutingA
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfFromFile returns a new config from file.
|
// NewConfFromFile returns a new config from file.
|
||||||
func NewConfFromFile(ruleFile string) (*Config, error) {
|
func NewConfFromFile(ruleFile string) (*Config, error) {
|
||||||
p := &Config{Name: ruleFile}
|
p := &Config{Name: getFilename(ruleFile)}
|
||||||
|
|
||||||
f := conflag.NewFromFile("rule", ruleFile)
|
f := conflag.NewFromFile("rule", ruleFile)
|
||||||
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
f.StringSliceUniqVar(&p.Forward, "forward", nil, "forward url, format: SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS[,SCHEME://[USER|METHOD:PASSWORD@][HOST]:PORT?PARAMS]")
|
||||||
@ -65,7 +68,6 @@ func NewConfFromFile(ruleFile string) (*Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,3 +90,19 @@ func ListDir(dirPth string, suffix string) (files []string, err error) {
|
|||||||
}
|
}
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFilename(ruleFile string) string {
|
||||||
|
return strings.TrimSuffix(filepath.Base(ruleFile), filepath.Ext(ruleFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseRoutingA(filename string) (*RoutingA, error) {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ra, err := routingA.Parse(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewRoutingA(ra)
|
||||||
|
}
|
||||||
|
38
rule/internal/matcher/cidrMatcher.go
Normal file
38
rule/internal/matcher/cidrMatcher.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nadoo/glider/rule/internal/trie"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CIDRMatcher trie.Trie
|
||||||
|
|
||||||
|
// TODO: ipv6
|
||||||
|
func NewCIDRMatcher(CIDRs []string) *CIDRMatcher {
|
||||||
|
dict := make([]string, 0, len(CIDRs))
|
||||||
|
for _, CIDR := range CIDRs {
|
||||||
|
grp := strings.SplitN(CIDR, "/", 2)
|
||||||
|
if len(grp) == 1 {
|
||||||
|
grp = append(grp, "32")
|
||||||
|
}
|
||||||
|
l, _ := strconv.Atoi(grp[1])
|
||||||
|
arr := strings.Split(grp[0], ".")
|
||||||
|
var builder strings.Builder
|
||||||
|
for _, sec := range arr {
|
||||||
|
itg, _ := strconv.Atoi(sec)
|
||||||
|
tmp := strconv.FormatInt(int64(itg), 2)
|
||||||
|
builder.WriteString(strings.Repeat("0", 8-len(tmp)))
|
||||||
|
builder.WriteString(tmp)
|
||||||
|
if builder.Len() >= l {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict = append(dict, builder.String()[:l])
|
||||||
|
}
|
||||||
|
return (*CIDRMatcher)(trie.New(dict))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m CIDRMatcher) Match(t interface{}) bool {
|
||||||
|
return (*trie.Trie)(&m).Match(t.(string)) != ""
|
||||||
|
}
|
46
rule/internal/matcher/domainMatcher.go
Normal file
46
rule/internal/matcher/domainMatcher.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type SuffixDomainTree map[string]interface{}
|
||||||
|
|
||||||
|
func NewSuffixDomainTree(domains []string) *SuffixDomainTree {
|
||||||
|
var (
|
||||||
|
tree = make(map[string]interface{})
|
||||||
|
m = tree
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
for _, d := range domains {
|
||||||
|
m = tree
|
||||||
|
fields := strings.Split(d, ".")
|
||||||
|
for i := len(fields) - 1; i >= 0; i-- {
|
||||||
|
var t interface{}
|
||||||
|
if t, ok = m[fields[i]]; ok {
|
||||||
|
m = t.(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
m[fields[i]] = make(map[string]interface{})
|
||||||
|
m = m[fields[i]].(map[string]interface{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m[".end"] = struct{}{}
|
||||||
|
}
|
||||||
|
return (*SuffixDomainTree)(&tree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m SuffixDomainTree) Match(t interface{}) bool {
|
||||||
|
fields := strings.Split(t.(string), ".")
|
||||||
|
mm := m
|
||||||
|
for i := len(fields) - 1; i >= 0; i-- {
|
||||||
|
var tt interface{}
|
||||||
|
var ok bool
|
||||||
|
if tt, ok = mm[fields[i]]; ok {
|
||||||
|
mm = tt.(map[string]interface{})
|
||||||
|
if _, ok := mm[".end"]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
29
rule/internal/matcher/integerMatcher.go
Normal file
29
rule/internal/matcher/integerMatcher.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nadoo/glider/log"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntegerMatcher map[int]struct{}
|
||||||
|
|
||||||
|
func NewIntegerMatcher(ports []string) *IntegerMatcher {
|
||||||
|
m := make(map[int]struct{})
|
||||||
|
for _, port := range ports {
|
||||||
|
p, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
log.F("invalid port: ", port)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m[p] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(m) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*IntegerMatcher)(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m IntegerMatcher) Match(t interface{}) bool {
|
||||||
|
_, ok := m[t.(int)]
|
||||||
|
return ok
|
||||||
|
}
|
5
rule/internal/matcher/matcher.go
Normal file
5
rule/internal/matcher/matcher.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
type Matcher interface {
|
||||||
|
Match(t interface{}) bool
|
||||||
|
}
|
28
rule/internal/matcher/networkMatcher.go
Normal file
28
rule/internal/matcher/networkMatcher.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
import "github.com/nadoo/glider/log"
|
||||||
|
|
||||||
|
const (
|
||||||
|
TCP = iota
|
||||||
|
UDP
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkMatcher [2]bool
|
||||||
|
|
||||||
|
func NewNetworkMatcher(networks []string) *NetworkMatcher {
|
||||||
|
var bucket [2]bool
|
||||||
|
for _, n := range networks {
|
||||||
|
if n == "tcp" {
|
||||||
|
bucket[TCP] = true
|
||||||
|
} else if n == "udp" {
|
||||||
|
bucket[UDP] = true
|
||||||
|
} else {
|
||||||
|
log.F("invalid network: ", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*NetworkMatcher)(&bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m NetworkMatcher) Match(t interface{}) bool {
|
||||||
|
return m[t.(int)]
|
||||||
|
}
|
19
rule/internal/matcher/stringMatcher.go
Normal file
19
rule/internal/matcher/stringMatcher.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package matcher
|
||||||
|
|
||||||
|
type StringMatcher map[string]struct{}
|
||||||
|
|
||||||
|
func NewStringMatcher(app []string) *StringMatcher {
|
||||||
|
m := make(map[string]struct{})
|
||||||
|
for _, name := range app {
|
||||||
|
m[name] = struct{}{}
|
||||||
|
}
|
||||||
|
if len(m) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (*StringMatcher)(&m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m StringMatcher) Match(t interface{}) bool {
|
||||||
|
_, ok := m[t.(string)]
|
||||||
|
return ok
|
||||||
|
}
|
128
rule/internal/trie/trie.go
Normal file
128
rule/internal/trie/trie.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Static trie
|
||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cast map[rune]*next
|
||||||
|
|
||||||
|
type next struct {
|
||||||
|
*node
|
||||||
|
str *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
c cast
|
||||||
|
end bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Trie struct {
|
||||||
|
root *node
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNode() *node {
|
||||||
|
return &node{
|
||||||
|
c: cast{},
|
||||||
|
end: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(dict []string) (trie *Trie) {
|
||||||
|
var t Trie
|
||||||
|
var ok bool
|
||||||
|
var p *node
|
||||||
|
t.root = newNode()
|
||||||
|
for _, d := range dict {
|
||||||
|
p = t.root
|
||||||
|
for i, r := range d {
|
||||||
|
_, ok = p.c[r]
|
||||||
|
if !ok {
|
||||||
|
n := next{
|
||||||
|
node: newNode(),
|
||||||
|
str: nil,
|
||||||
|
}
|
||||||
|
p.c[r] = &n
|
||||||
|
}
|
||||||
|
p = p.c[r].node
|
||||||
|
if i == len(d)-1 {
|
||||||
|
p.end = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//make jump
|
||||||
|
makeJump(t.root)
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastJump(from *next, to *next, str *string) {
|
||||||
|
from.str = str
|
||||||
|
from.node = to.node
|
||||||
|
}
|
||||||
|
|
||||||
|
func _makeJump(cur *next, from *next, builder *strings.Builder) {
|
||||||
|
var fork bool
|
||||||
|
if cur.node.end || len(cur.node.c) > 1 {
|
||||||
|
if builder.Len() > 1 {
|
||||||
|
s := builder.String()
|
||||||
|
fastJump(from, cur, &s)
|
||||||
|
}
|
||||||
|
fork = true
|
||||||
|
}
|
||||||
|
for k := range cur.node.c {
|
||||||
|
child := cur.node.c[k]
|
||||||
|
if fork {
|
||||||
|
from = child
|
||||||
|
builder = new(strings.Builder)
|
||||||
|
}
|
||||||
|
builder.WriteRune(k)
|
||||||
|
_makeJump(child, from, builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJump(root *node) {
|
||||||
|
//DFS
|
||||||
|
for k := range root.c {
|
||||||
|
builder := new(strings.Builder)
|
||||||
|
builder.WriteRune(k)
|
||||||
|
_makeJump(root.c[k], root.c[k], builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trie) Match(str string) (prefix string) {
|
||||||
|
var builder strings.Builder
|
||||||
|
var runes = []rune(str)
|
||||||
|
var length = len(runes)
|
||||||
|
p := t.root
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
r := runes[i]
|
||||||
|
tmp, ok := p.c[r]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tmp.str == nil {
|
||||||
|
builder.WriteRune(r)
|
||||||
|
} else {
|
||||||
|
if lenTmp := len(*tmp.str); builder.Len()+lenTmp <= length {
|
||||||
|
if string(runes[i:lenTmp+i]) == *tmp.str {
|
||||||
|
builder.WriteString(*tmp.str)
|
||||||
|
i += len(*tmp.str) - 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tmp.node.end {
|
||||||
|
if builder.Len() <= length {
|
||||||
|
prefix = builder.String()
|
||||||
|
if len(prefix) == length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = tmp.node
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
35
rule/internal/trie/trie_test.go
Normal file
35
rule/internal/trie/trie_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package trie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTrie_Match(t *testing.T) {
|
||||||
|
trie := New([]string{
|
||||||
|
"12",
|
||||||
|
"12345",
|
||||||
|
"1234567",
|
||||||
|
"2222",
|
||||||
|
"1",
|
||||||
|
})
|
||||||
|
test := [][2]string{
|
||||||
|
{"1", "1"},
|
||||||
|
{"123", "12"},
|
||||||
|
{"1233", "12"},
|
||||||
|
{"12345", "12345"},
|
||||||
|
{"123456", "12345"},
|
||||||
|
{"1234567", "1234567"},
|
||||||
|
{"123456789", "1234567"},
|
||||||
|
{"222", ""},
|
||||||
|
{"2222", "2222"},
|
||||||
|
{"22222", "2222"},
|
||||||
|
{"122", "12"},
|
||||||
|
}
|
||||||
|
for _, tt := range test {
|
||||||
|
if p := trie.Match(tt[0]); p == tt[1] {
|
||||||
|
t.Log(tt[0], "match prefix", p)
|
||||||
|
} else {
|
||||||
|
t.Error(tt[0], "expect", tt[1], "wrong prefix", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package rule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/nadoo/glider/rule/internal/matcher"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -13,18 +14,29 @@ import (
|
|||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
main *FwdrGroup
|
main *FwdrGroup
|
||||||
all []*FwdrGroup
|
all []*FwdrGroup
|
||||||
|
name2Group map[string]*FwdrGroup
|
||||||
domainMap sync.Map
|
domainMap sync.Map
|
||||||
ipMap sync.Map
|
ipMap sync.Map
|
||||||
cidrMap sync.Map
|
cidrMap sync.Map
|
||||||
|
routingA *RoutingA
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProxy returns a new rule proxy.
|
// NewProxy returns a new rule proxy.
|
||||||
func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config) *Proxy {
|
func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config) *Proxy {
|
||||||
rd := &Proxy{main: NewFwdrGroup("main", mainForwarders, mainStrategy)}
|
rd := &Proxy{
|
||||||
|
main: NewFwdrGroup("main", mainForwarders, mainStrategy),
|
||||||
|
name2Group: make(map[string]*FwdrGroup),
|
||||||
|
routingA: mainStrategy.RoutingA,
|
||||||
|
}
|
||||||
|
|
||||||
|
rd.name2Group[OutProxy] = rd.main
|
||||||
|
rd.name2Group[OutDirect] = NewFwdrGroup("", nil, mainStrategy)
|
||||||
|
rd.name2Group[OutReject] = NewFwdrGroup("", []string{"reject://"}, mainStrategy)
|
||||||
|
|
||||||
for _, r := range rules {
|
for _, r := range rules {
|
||||||
group := NewFwdrGroup(r.Name, r.Forward, &r.Strategy)
|
group := NewFwdrGroup(r.Name, r.Forward, &r.Strategy)
|
||||||
rd.all = append(rd.all, group)
|
rd.all = append(rd.all, group)
|
||||||
|
rd.name2Group[r.Name] = group
|
||||||
|
|
||||||
for _, domain := range r.Domain {
|
for _, domain := range r.Domain {
|
||||||
rd.domainMap.Store(strings.ToLower(domain), group)
|
rd.domainMap.Store(strings.ToLower(domain), group)
|
||||||
@ -40,14 +52,18 @@ func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rd.routingA != nil {
|
||||||
|
rd.name2Group[OutDefault] = rd.name2Group[rd.routingA.DefaultOut]
|
||||||
|
} else {
|
||||||
|
rd.name2Group[OutDefault] = rd.main
|
||||||
|
}
|
||||||
|
|
||||||
// if there's any forwarder defined in main config, make sure they will be accessed directly.
|
// if there's any forwarder defined in main config, make sure they will be accessed directly.
|
||||||
if len(mainForwarders) > 0 {
|
if len(mainForwarders) > 0 {
|
||||||
direct := NewFwdrGroup("", nil, mainStrategy)
|
|
||||||
for _, f := range rd.main.fwdrs {
|
for _, f := range rd.main.fwdrs {
|
||||||
host, _, _ := net.SplitHostPort(f.addr)
|
host, _, _ := net.SplitHostPort(f.addr)
|
||||||
if ip := net.ParseIP(host); ip == nil {
|
if ip := net.ParseIP(host); ip == nil {
|
||||||
rd.domainMap.Store(strings.ToLower(host), direct)
|
rd.domainMap.Store(strings.ToLower(host), rd.name2Group[OutDirect])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,23 +73,24 @@ func NewProxy(mainForwarders []string, mainStrategy *Strategy, rules []*Config)
|
|||||||
|
|
||||||
// Dial dials to targer addr and return a conn.
|
// Dial dials to targer addr and return a conn.
|
||||||
func (p *Proxy) Dial(network, addr string) (net.Conn, proxy.Dialer, error) {
|
func (p *Proxy) Dial(network, addr string) (net.Conn, proxy.Dialer, error) {
|
||||||
return p.findDialer(addr).Dial(network, addr)
|
return p.findDialer(network, addr).Dial(network, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialUDP connects to the given address via the proxy.
|
// DialUDP connects to the given address via the proxy.
|
||||||
func (p *Proxy) DialUDP(network, addr string) (pc net.PacketConn, dialer proxy.UDPDialer, writeTo net.Addr, err error) {
|
func (p *Proxy) DialUDP(network, addr string) (pc net.PacketConn, dialer proxy.UDPDialer, writeTo net.Addr, err error) {
|
||||||
return p.findDialer(addr).DialUDP(network, addr)
|
return p.findDialer(network, addr).DialUDP(network, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findDialer returns a dialer by dstAddr according to rule.
|
// findDialer returns a dialer by dstAddr according to rule.
|
||||||
func (p *Proxy) findDialer(dstAddr string) *FwdrGroup {
|
func (p *Proxy) findDialer(network string, dstAddr string) *FwdrGroup {
|
||||||
host, _, err := net.SplitHostPort(dstAddr)
|
host, port, err := net.SplitHostPort(dstAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return p.main
|
return p.main
|
||||||
}
|
}
|
||||||
|
|
||||||
// find ip
|
// find ip
|
||||||
if ip := net.ParseIP(host); ip != nil {
|
var ip net.IP
|
||||||
|
if ip = net.ParseIP(host); ip != nil {
|
||||||
// check ip
|
// check ip
|
||||||
if proxy, ok := p.ipMap.Load(ip.String()); ok {
|
if proxy, ok := p.ipMap.Load(ip.String()); ok {
|
||||||
return proxy.(*FwdrGroup)
|
return proxy.(*FwdrGroup)
|
||||||
@ -93,7 +110,6 @@ func (p *Proxy) findDialer(dstAddr string) *FwdrGroup {
|
|||||||
if ret != nil {
|
if ret != nil {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
host = strings.ToLower(host)
|
host = strings.ToLower(host)
|
||||||
@ -104,12 +120,52 @@ func (p *Proxy) findDialer(dstAddr string) *FwdrGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check routingA
|
||||||
|
if p.routingA != nil {
|
||||||
|
for _, r := range p.routingA.Rules {
|
||||||
|
matched := false
|
||||||
|
for _, m := range r.Matchers {
|
||||||
|
switch m.RuleType {
|
||||||
|
case "domain":
|
||||||
|
matched = m.Matcher.Match(host)
|
||||||
|
case "tip":
|
||||||
|
if ip != nil {
|
||||||
|
matched = m.Matcher.Match(ip.String())
|
||||||
|
}
|
||||||
|
case "tport":
|
||||||
|
matched = m.Matcher.Match(port)
|
||||||
|
case "network":
|
||||||
|
if network == "tcp" {
|
||||||
|
matched = m.Matcher.Match(matcher.TCP)
|
||||||
|
} else if network == "udp" {
|
||||||
|
matched = m.Matcher.Match(matcher.UDP)
|
||||||
|
}
|
||||||
|
case "sport", "sip":
|
||||||
|
// TODO: UNSUPPORTED
|
||||||
|
case "app":
|
||||||
|
// TODO: UNSUPPORTED
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
if g, ok := p.name2Group[r.Out]; ok {
|
||||||
|
return g
|
||||||
|
} else {
|
||||||
|
log.F("invalid out rule: ", r.Out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.name2Group[OutDefault]
|
||||||
|
}
|
||||||
|
|
||||||
return p.main
|
return p.main
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextDialer returns next dialer according to rule.
|
// NextDialer returns next dialer according to rule.
|
||||||
func (p *Proxy) NextDialer(dstAddr string) proxy.Dialer {
|
func (p *Proxy) NextDialer(network, dstAddr string) proxy.Dialer {
|
||||||
return p.findDialer(dstAddr).NextDialer(dstAddr)
|
return p.findDialer(network, dstAddr).NextDialer(dstAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record records result while using the dialer from proxy.
|
// Record records result while using the dialer from proxy.
|
||||||
|
92
rule/routingA.go
Normal file
92
rule/routingA.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/nadoo/glider/rule/internal/matcher"
|
||||||
|
"github.com/v2rayA/routingA"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutType int
|
||||||
|
type DomainStrategy int
|
||||||
|
type Network int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OutDirect = "direct"
|
||||||
|
OutProxy = "proxy"
|
||||||
|
OutReject = "reject"
|
||||||
|
OutDefault = "_default" //try not to conflict with user's definition
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
DomainStrategyAsIs DomainStrategy = iota
|
||||||
|
DomainStrategyIPIfNonMatch
|
||||||
|
DomainStrategyIPOnDemand
|
||||||
|
)
|
||||||
|
|
||||||
|
type RoutingA struct {
|
||||||
|
DefaultOut string
|
||||||
|
DomainStrategy DomainStrategy // FIXME: not valid
|
||||||
|
Rules []RoutingRule
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRoutingA(routing routingA.RoutingA) (ra *RoutingA, err error) {
|
||||||
|
ra = &RoutingA{
|
||||||
|
DefaultOut: OutProxy,
|
||||||
|
DomainStrategy: DomainStrategyIPIfNonMatch,
|
||||||
|
}
|
||||||
|
rs, ds := routing.Unfold()
|
||||||
|
for _, d := range ds {
|
||||||
|
switch d.Name {
|
||||||
|
case "default":
|
||||||
|
ra.DefaultOut = d.Value.(string)
|
||||||
|
case "domainStrategy":
|
||||||
|
switch d.Value.(string) {
|
||||||
|
case "AsIs":
|
||||||
|
ra.DomainStrategy = DomainStrategyAsIs
|
||||||
|
case "IPIfNonMatch":
|
||||||
|
ra.DomainStrategy = DomainStrategyIPIfNonMatch
|
||||||
|
case "IPOnDemand":
|
||||||
|
ra.DomainStrategy = DomainStrategyIPOnDemand
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported domainStrategy: %v", d.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, r := range rs {
|
||||||
|
var rr RoutingRule
|
||||||
|
rr.Out = r.Out
|
||||||
|
for _, cond := range r.And {
|
||||||
|
m := Matcher{
|
||||||
|
RuleType: cond.Name,
|
||||||
|
}
|
||||||
|
switch cond.Name {
|
||||||
|
case "domain":
|
||||||
|
m.Matcher = matcher.NewSuffixDomainTree(cond.Params)
|
||||||
|
case "tip", "sip":
|
||||||
|
m.Matcher = matcher.NewCIDRMatcher(cond.Params)
|
||||||
|
case "network":
|
||||||
|
m.Matcher = matcher.NewNetworkMatcher(cond.Params)
|
||||||
|
case "app", "tport", "sport":
|
||||||
|
m.Matcher = matcher.NewStringMatcher(cond.Params)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rr.Matchers = append(rr.Matchers, m)
|
||||||
|
}
|
||||||
|
ra.Rules = append(ra.Rules, rr)
|
||||||
|
}
|
||||||
|
return ra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
//func (ra *RoutingA) MergeAdjacentRules() {
|
||||||
|
//}
|
||||||
|
|
||||||
|
type Matcher struct {
|
||||||
|
matcher.Matcher
|
||||||
|
RuleType string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutingRule struct {
|
||||||
|
Out string
|
||||||
|
Matchers []Matcher
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user