feat: support routingA

This commit is contained in:
mzz2017 2020-12-04 18:49:11 +08:00
parent 10b7f2d5e6
commit 05559703a3
19 changed files with 538 additions and 27 deletions

View File

@ -23,8 +23,9 @@ type Config struct {
Forwards []string Forwards []string
Strategy rule.Strategy Strategy rule.Strategy
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
} }

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View 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)) != ""
}

View 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
}

View 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
}

View File

@ -0,0 +1,5 @@
package matcher
type Matcher interface {
Match(t interface{}) bool
}

View 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)]
}

View 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
View 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
}

View 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)
}
}
}

View File

@ -1,6 +1,7 @@
package rule package rule
import ( import (
"github.com/nadoo/glider/rule/internal/matcher"
"net" "net"
"strings" "strings"
"sync" "sync"
@ -11,20 +12,31 @@ import (
// Proxy implements the proxy.Proxy interface with rule support. // Proxy implements the proxy.Proxy interface with rule support.
type Proxy struct { type Proxy struct {
main *FwdrGroup main *FwdrGroup
all []*FwdrGroup all []*FwdrGroup
domainMap sync.Map name2Group map[string]*FwdrGroup
ipMap sync.Map domainMap sync.Map
cidrMap sync.Map ipMap 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
View 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
}