mirror of
https://github.com/nadoo/glider.git
synced 2025-04-20 03:02:09 +08:00
feat: support routingA
This commit is contained in:
parent
10b7f2d5e6
commit
05559703a3
16
config.go
16
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
1
go.mod
1
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
|
||||
|
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/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=
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
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
|
||||
|
||||
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.
|
||||
|
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