From 05559703a3abd966480434b30c8142eda19a3414 Mon Sep 17 00:00:00 2001 From: mzz2017 Date: Fri, 4 Dec 2020 18:49:11 +0800 Subject: [PATCH 1/2] feat: support routingA --- config.go | 16 ++- dns/client.go | 2 +- go.mod | 1 + go.sum | 4 + proxy/proxy.go | 2 +- proxy/ss/server.go | 2 +- proxy/trojan/server.go | 4 +- proxy/vless/server.go | 4 +- rule/config.go | 22 +++- rule/internal/matcher/cidrMatcher.go | 38 +++++++ rule/internal/matcher/domainMatcher.go | 46 +++++++++ rule/internal/matcher/integerMatcher.go | 29 ++++++ rule/internal/matcher/matcher.go | 5 + rule/internal/matcher/networkMatcher.go | 28 ++++++ rule/internal/matcher/stringMatcher.go | 19 ++++ rule/internal/trie/trie.go | 128 ++++++++++++++++++++++++ rule/internal/trie/trie_test.go | 35 +++++++ rule/proxy.go | 88 +++++++++++++--- rule/routingA.go | 92 +++++++++++++++++ 19 files changed, 538 insertions(+), 27 deletions(-) create mode 100644 rule/internal/matcher/cidrMatcher.go create mode 100644 rule/internal/matcher/domainMatcher.go create mode 100644 rule/internal/matcher/integerMatcher.go create mode 100644 rule/internal/matcher/matcher.go create mode 100644 rule/internal/matcher/networkMatcher.go create mode 100644 rule/internal/matcher/stringMatcher.go create mode 100644 rule/internal/trie/trie.go create mode 100644 rule/internal/trie/trie_test.go create mode 100644 rule/routingA.go diff --git a/config.go b/config.go index 7a6df28b..2afe693e 100644 --- a/config.go +++ b/config.go @@ -23,8 +23,9 @@ type Config struct { Forwards []string Strategy rule.Strategy - RuleFiles []string - RulesDir string + RuleFiles []string + RulesDir string + RoutingAFile string DNS string DNSConfig dns.Config @@ -56,6 +57,7 @@ func parseConfig() *Config { flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path") flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder") + flag.StringVar(&conf.RoutingAFile, "routingA", "", "routingA file path") // dns configs 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 } diff --git a/dns/client.go b/dns/client.go index 11ba9224..c8ef84e1 100644 --- a/dns/client.go +++ b/dns/client.go @@ -148,7 +148,7 @@ func (c *Client) exchange(qname string, reqBytes []byte, preferTCP bool) ( // use tcp to connect upstream server default 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 // TODO: dialer.Addr() == "REJECT", tricky diff --git a/go.mod b/go.mod index 19a7e2c7..b4e2dd56 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/nadoo/conflag v0.2.3 github.com/nadoo/ipset v0.3.0 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 golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 golang.org/x/mod v0.4.0 // indirect diff --git a/go.sum b/go.sum index cf38a754..faa78847 100644 --- a/go.sum +++ b/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/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= 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.3.0/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/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/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/go.mod h1:W3kVPyNYwZ06p79dNwFWQOVFrdcBpDBsdyvK8moQrYo= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= diff --git a/proxy/proxy.go b/proxy/proxy.go index 03eda74c..4aa76de1 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -11,7 +11,7 @@ type Proxy interface { DialUDP(network, addr string) (pc net.PacketConn, dialer UDPDialer, writeTo net.Addr, err error) // Get the dialer by dstAddr. - NextDialer(dstAddr string) Dialer + NextDialer(network, dstAddr string) Dialer // Record records result while using the dialer from proxy. Record(dialer Dialer, success bool) diff --git a/proxy/ss/server.go b/proxy/ss/server.go index 81adae98..62a784cc 100644 --- a/proxy/ss/server.go +++ b/proxy/ss/server.go @@ -60,7 +60,7 @@ func (s *SS) Serve(c net.Conn) { } network := "tcp" - dialer := s.proxy.NextDialer(tgt.String()) + dialer := s.proxy.NextDialer(network, tgt.String()) rc, err := dialer.Dial(network, tgt.String()) if err != nil { diff --git a/proxy/trojan/server.go b/proxy/trojan/server.go index 4902a91f..287d2ce5 100644 --- a/proxy/trojan/server.go +++ b/proxy/trojan/server.go @@ -106,7 +106,7 @@ func (s *Trojan) Serve(c net.Conn) { } network := "tcp" - dialer := s.proxy.NextDialer(target.String()) + dialer := s.proxy.NextDialer(network, target.String()) if cmd == socks.CmdUDPAssociate { // 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) { // 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) if err != nil { log.F("[trojan-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err) diff --git a/proxy/vless/server.go b/proxy/vless/server.go index 2af9073c..17d409bf 100644 --- a/proxy/vless/server.go +++ b/proxy/vless/server.go @@ -63,7 +63,7 @@ func (s *VLess) Serve(c net.Conn) { c = NewServerConn(c) network := "tcp" - dialer := s.proxy.NextDialer(target) + dialer := s.proxy.NextDialer(network, target) if cmd == CmdUDP { // 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) { // 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) if err != nil { log.F("[vless-fallback] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), tgt, dialer.Addr(), err) diff --git a/rule/config.go b/rule/config.go index aa844f7d..2b0fca1c 100644 --- a/rule/config.go +++ b/rule/config.go @@ -1,8 +1,10 @@ package rule import ( + "github.com/v2rayA/routingA" "io/ioutil" "os" + "path/filepath" "strings" "github.com/nadoo/conflag" @@ -35,11 +37,12 @@ type Strategy struct { DialTimeout int RelayTimeout int IntFace string + RoutingA *RoutingA } // NewConfFromFile returns a new config from file. func NewConfFromFile(ruleFile string) (*Config, error) { - p := &Config{Name: ruleFile} + p := &Config{Name: getFilename(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]") @@ -65,7 +68,6 @@ func NewConfFromFile(ruleFile string) (*Config, error) { if err != nil { return nil, err } - return p, err } @@ -88,3 +90,19 @@ func ListDir(dirPth string, suffix string) (files []string, err error) { } 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) +} diff --git a/rule/internal/matcher/cidrMatcher.go b/rule/internal/matcher/cidrMatcher.go new file mode 100644 index 00000000..6e862c3f --- /dev/null +++ b/rule/internal/matcher/cidrMatcher.go @@ -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)) != "" +} diff --git a/rule/internal/matcher/domainMatcher.go b/rule/internal/matcher/domainMatcher.go new file mode 100644 index 00000000..bfc97d8e --- /dev/null +++ b/rule/internal/matcher/domainMatcher.go @@ -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 +} diff --git a/rule/internal/matcher/integerMatcher.go b/rule/internal/matcher/integerMatcher.go new file mode 100644 index 00000000..8e3b4aeb --- /dev/null +++ b/rule/internal/matcher/integerMatcher.go @@ -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 +} diff --git a/rule/internal/matcher/matcher.go b/rule/internal/matcher/matcher.go new file mode 100644 index 00000000..7709d15c --- /dev/null +++ b/rule/internal/matcher/matcher.go @@ -0,0 +1,5 @@ +package matcher + +type Matcher interface { + Match(t interface{}) bool +} diff --git a/rule/internal/matcher/networkMatcher.go b/rule/internal/matcher/networkMatcher.go new file mode 100644 index 00000000..38ff056b --- /dev/null +++ b/rule/internal/matcher/networkMatcher.go @@ -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)] +} diff --git a/rule/internal/matcher/stringMatcher.go b/rule/internal/matcher/stringMatcher.go new file mode 100644 index 00000000..46217f56 --- /dev/null +++ b/rule/internal/matcher/stringMatcher.go @@ -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 +} diff --git a/rule/internal/trie/trie.go b/rule/internal/trie/trie.go new file mode 100644 index 00000000..ba75afdc --- /dev/null +++ b/rule/internal/trie/trie.go @@ -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 +} diff --git a/rule/internal/trie/trie_test.go b/rule/internal/trie/trie_test.go new file mode 100644 index 00000000..f732b49d --- /dev/null +++ b/rule/internal/trie/trie_test.go @@ -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) + } + } +} diff --git a/rule/proxy.go b/rule/proxy.go index b131f02c..05e0b530 100644 --- a/rule/proxy.go +++ b/rule/proxy.go @@ -1,6 +1,7 @@ package rule import ( + "github.com/nadoo/glider/rule/internal/matcher" "net" "strings" "sync" @@ -11,20 +12,31 @@ import ( // Proxy implements the proxy.Proxy interface with rule support. type Proxy struct { - main *FwdrGroup - all []*FwdrGroup - domainMap sync.Map - ipMap sync.Map - cidrMap sync.Map + main *FwdrGroup + all []*FwdrGroup + name2Group map[string]*FwdrGroup + domainMap sync.Map + ipMap sync.Map + cidrMap sync.Map + routingA *RoutingA } // NewProxy returns a new rule 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 { group := NewFwdrGroup(r.Name, r.Forward, &r.Strategy) rd.all = append(rd.all, group) + rd.name2Group[r.Name] = group for _, domain := range r.Domain { 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 len(mainForwarders) > 0 { - direct := NewFwdrGroup("", nil, mainStrategy) for _, f := range rd.main.fwdrs { host, _, _ := net.SplitHostPort(f.addr) 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. 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. 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. -func (p *Proxy) findDialer(dstAddr string) *FwdrGroup { - host, _, err := net.SplitHostPort(dstAddr) +func (p *Proxy) findDialer(network string, dstAddr string) *FwdrGroup { + host, port, err := net.SplitHostPort(dstAddr) if err != nil { return p.main } // find ip - if ip := net.ParseIP(host); ip != nil { + var ip net.IP + if ip = net.ParseIP(host); ip != nil { // check ip if proxy, ok := p.ipMap.Load(ip.String()); ok { return proxy.(*FwdrGroup) @@ -93,7 +110,6 @@ func (p *Proxy) findDialer(dstAddr string) *FwdrGroup { if ret != nil { return ret } - } 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 } // NextDialer returns next dialer according to rule. -func (p *Proxy) NextDialer(dstAddr string) proxy.Dialer { - return p.findDialer(dstAddr).NextDialer(dstAddr) +func (p *Proxy) NextDialer(network, dstAddr string) proxy.Dialer { + return p.findDialer(network, dstAddr).NextDialer(dstAddr) } // Record records result while using the dialer from proxy. diff --git a/rule/routingA.go b/rule/routingA.go new file mode 100644 index 00000000..ddd0d5f8 --- /dev/null +++ b/rule/routingA.go @@ -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 +} From c2e7b21ce6821fda99ed3d2978cbf59ad3f4244a Mon Sep 17 00:00:00 2001 From: mzz2017 Date: Fri, 4 Dec 2020 20:18:42 +0800 Subject: [PATCH 2/2] docs: annotations --- rule/proxy.go | 6 ++++-- rule/routingA.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/rule/proxy.go b/rule/proxy.go index 05e0b530..0d971240 100644 --- a/rule/proxy.go +++ b/rule/proxy.go @@ -135,15 +135,17 @@ func (p *Proxy) findDialer(network string, dstAddr string) *FwdrGroup { case "tport": matched = m.Matcher.Match(port) case "network": - if network == "tcp" { + if strings.HasPrefix(network, "tcp") { matched = m.Matcher.Match(matcher.TCP) - } else if network == "udp" { + } else if strings.HasPrefix(network, "udp") { matched = m.Matcher.Match(matcher.UDP) } case "sport", "sip": // TODO: UNSUPPORTED case "app": // TODO: UNSUPPORTED + // NOTE: should get the inbound conn, + // and there would be a break change for current code structure. } if matched { break diff --git a/rule/routingA.go b/rule/routingA.go index ddd0d5f8..1260624f 100644 --- a/rule/routingA.go +++ b/rule/routingA.go @@ -24,7 +24,7 @@ const ( type RoutingA struct { DefaultOut string - DomainStrategy DomainStrategy // FIXME: not valid + DomainStrategy DomainStrategy // TODO: not valid. there would be some changes for current code structure Rules []RoutingRule }