diff --git a/README.md b/README.md index e8486af..6cc78cd 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ glider -h click to see details ```bash -./glider 0.11.1 usage: +./glider 0.11.2 usage: -checkdisabledonly check disabled fowarders only -checkinterval int @@ -105,6 +105,8 @@ glider -h local dns server listen address -dnsalwaystcp always use tcp to query upstream dns servers no matter there is a forwarder or not + -dnscachesize int + size of CACHE (default 1024) -dnsmaxttl int maximum TTL value for entries in the CACHE(seconds) (default 1800) -dnsminttl int @@ -170,13 +172,13 @@ VLESS scheme: vless://uuid@host:port[?fallback=127.0.0.1:80] Trojan scheme: - trojan://pass@host:port[?skipVerify=true] + trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true] Available securities for vmess: none, aes-128-gcm, chacha20-poly1305 TLS client scheme: - tls://host:port[?skipVerify=true][&serverName=SERVERNAME] + tls://host:port[?serverName=SERVERNAME][&skipVerify=true] Proxy over tls client: tls://host:port[?skipVerify=true][&serverName=SERVERNAME],scheme:// diff --git a/config.go b/config.go index 7352081..021420a 100644 --- a/config.go +++ b/config.go @@ -56,14 +56,17 @@ func parseConfig() *Config { flag.StringSliceUniqVar(&conf.RuleFiles, "rulefile", nil, "rule file path") flag.StringVar(&conf.RulesDir, "rules-dir", "", "rule file folder") + // dns configs flag.StringVar(&conf.DNS, "dns", "", "local dns server listen address") flag.StringSliceUniqVar(&conf.DNSConfig.Servers, "dnsserver", []string{"8.8.8.8:53"}, "remote dns server address") flag.BoolVar(&conf.DNSConfig.AlwaysTCP, "dnsalwaystcp", false, "always use tcp to query upstream dns servers no matter there is a forwarder or not") flag.IntVar(&conf.DNSConfig.Timeout, "dnstimeout", 3, "timeout value used in multiple dnsservers switch(seconds)") flag.IntVar(&conf.DNSConfig.MaxTTL, "dnsmaxttl", 1800, "maximum TTL value for entries in the CACHE(seconds)") flag.IntVar(&conf.DNSConfig.MinTTL, "dnsminttl", 0, "minimum TTL value for entries in the CACHE(seconds)") + flag.IntVar(&conf.DNSConfig.CacheSize, "dnscachesize", 1024, "size of CACHE") flag.StringSliceUniqVar(&conf.DNSConfig.Records, "dnsrecord", nil, "custom dns record, format: domain/ip") + // service configs flag.StringSliceUniqVar(&conf.Services, "service", nil, "run specified services, format: SERVICE_NAME[,SERVICE_CONFIG]") flag.Usage = usage @@ -166,7 +169,7 @@ func usage() { fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Trojan scheme:\n") - fmt.Fprintf(w, " trojan://pass@host:port[?skipVerify=true]\n") + fmt.Fprintf(w, " trojan://pass@host:port[?serverName=SERVERNAME][&skipVerify=true]\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Available securities for vmess:\n") @@ -174,7 +177,7 @@ func usage() { fmt.Fprintf(w, "\n") fmt.Fprintf(w, "TLS client scheme:\n") - fmt.Fprintf(w, " tls://host:port[?skipVerify=true][&serverName=SERVERNAME]\n") + fmt.Fprintf(w, " tls://host:port[?serverName=SERVERNAME][&skipVerify=true]\n") fmt.Fprintf(w, "\n") fmt.Fprintf(w, "Proxy over tls client:\n") diff --git a/config/glider.conf.example b/config/glider.conf.example index 2bcfd0e..839b25c 100644 --- a/config/glider.conf.example +++ b/config/glider.conf.example @@ -108,7 +108,7 @@ listen=socks5://:1080 # forward=http://1.1.1.1:8080 # trojan as forwarder -# forward=trojan://PASSWORD@1.1.1.1:8080[?skipVerify=true] +# forward=trojan://PASSWORD@1.1.1.1:8080[?serverName=SERVERNAME][&skipVerify=true] # vless forwarder # forward=vless://5a146038-0b56-4e95-b1dc-5c6f5a32cd98@1.1.1.1:443 diff --git a/dns/cache.go b/dns/cache.go index 329ff05..4771b43 100644 --- a/dns/cache.go +++ b/dns/cache.go @@ -3,87 +3,124 @@ package dns import ( "sync" "time" - - "github.com/nadoo/glider/pool" ) -// LongTTL is 50 years duration in seconds, used for none-expired items. -const LongTTL = 50 * 365 * 24 * 3600 - -type item struct { - value []byte - expire time.Time +// LruCache is the struct of LruCache. +type LruCache struct { + mu sync.Mutex + size int + head *Item + tail *Item + cache map[string]*Item + store map[string][]byte } -// Cache is the struct of cache. -type Cache struct { - store map[string]*item - mutex sync.RWMutex - storeCopy bool +// Item is the struct of cache item. +type Item struct { + key string + val []byte + exp int64 + prev *Item + next *Item } -// NewCache returns a new cache. -func NewCache(storeCopy bool) (c *Cache) { - c = &Cache{store: make(map[string]*item), storeCopy: storeCopy} - go func() { - for now := range time.Tick(time.Second) { - c.mutex.Lock() - for k, v := range c.store { - if now.After(v.expire) { - delete(c.store, k) - if storeCopy { - pool.PutBuffer(v.value) - } - } - } - c.mutex.Unlock() +// NewCache returns a new LruCache. +func NewLruCache(size int) *LruCache { + // init 2 items here, it doesn't matter cuz they will be deleted when the cache if full + head, tail := &Item{key: "head"}, &Item{key: "tail"} + head.next, tail.prev = tail, head + c := &LruCache{ + size: size, + head: head, + tail: tail, + cache: make(map[string]*Item, size), + store: make(map[string][]byte), + } + c.cache[head.key], c.cache[tail.key] = head, tail + return c +} + +// Get gets an item from cache. +func (c *LruCache) Get(k string) (v []byte, expired bool) { + c.mu.Lock() + defer c.mu.Unlock() + + if v, ok := c.store[k]; ok { + return v, false + } + + if it, ok := c.cache[k]; ok { + v = it.val + if it.exp < time.Now().Unix() { + expired = true } - }() + c.moveToHead(it) + } return } -// Len returns the length of cache. -func (c *Cache) Len() int { - return len(c.store) +// Set sets an item with key, value, and ttl(seconds). +// if the ttl is zero, this item will be set and never be deleted. +// if the key exists, update it with value and exp and move it to head. +// if the key does not exist, put an item to the cache's head. +// finally, remove the tail if the cache is full. +func (c *LruCache) Set(k string, v []byte, ttl int) { + c.mu.Lock() + defer c.mu.Unlock() + + if ttl == 0 { + c.store[k] = v + return + } + + exp := time.Now().Add(time.Second * time.Duration(ttl)).Unix() + if it, ok := c.cache[k]; ok { + it.val = v + it.exp = exp + c.moveToHead(it) + return + } + + c.putToHead(k, v, exp) + if len(c.cache) > c.size { + c.removeTail() + } } -// Put an item into cache, invalid after ttl seconds. -func (c *Cache) Put(k string, v []byte, ttl int) { - if len(v) != 0 { - c.mutex.Lock() - it, ok := c.store[k] - if !ok { - if c.storeCopy { - it = &item{value: valCopy(v)} - } else { - it = &item{value: v} - } - c.store[k] = it +// putToHead puts a new item to cache's head. +func (c *LruCache) putToHead(k string, v []byte, exp int64) { + it := &Item{key: k, val: v, exp: exp, prev: nil, next: c.head} + c.cache[k] = it + + it.prev = nil + it.next = c.head + c.head.prev = it + c.head = it +} + +// moveToHead moves an existing item to cache's head. +func (c *LruCache) moveToHead(it *Item) { + if it != c.head { + if c.tail == it { + c.tail = it.prev + c.tail.next = nil + } else { + it.prev.next = it.next + it.next.prev = it.prev } - it.expire = time.Now().Add(time.Duration(ttl) * time.Second) - c.mutex.Unlock() + it.prev = nil + it.next = c.head + c.head.prev = it + c.head = it } } -// Get gets an item from cache(do not modify it). -func (c *Cache) Get(k string) (v []byte) { - c.mutex.RLock() - if it, ok := c.store[k]; ok { - v = it.value - } - c.mutex.RUnlock() - return -} +// removeTail removes the tail from cache. +func (c *LruCache) removeTail() { + delete(c.cache, c.tail.key) -// GetCopy gets an item from cache and returns it's copy(so you can modify it). -func (c *Cache) GetCopy(k string) []byte { - return valCopy(c.Get(k)) -} - -func valCopy(v []byte) (b []byte) { - if v != nil { - b = pool.GetBuffer(len(v)) - copy(b, v) + if c.tail.prev != nil { + c.tail.prev.next = nil } - return + c.tail = c.tail.prev } diff --git a/dns/client.go b/dns/client.go index 7e5e5f7..757fc5f 100644 --- a/dns/client.go +++ b/dns/client.go @@ -24,12 +24,13 @@ type Config struct { MinTTL int Records []string AlwaysTCP bool + CacheSize int } // Client is a dns client struct. type Client struct { proxy proxy.Proxy - cache *Cache + cache *LruCache config *Config upStream *UPStream upStreamMap map[string]*UPStream @@ -40,7 +41,7 @@ type Client struct { func NewClient(proxy proxy.Proxy, config *Config) (*Client, error) { c := &Client{ proxy: proxy, - cache: NewCache(true), + cache: NewLruCache(config.CacheSize), config: config, upStream: NewUPStream(config.Servers), upStreamMap: make(map[string]*UPStream), @@ -63,8 +64,9 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([ } if req.Question.QTYPE == QTypeA || req.Question.QTYPE == QTypeAAAA { - v := c.cache.GetCopy(qKey(req.Question)) + v, _ := c.cache.Get(qKey(req.Question)) if len(v) > 4 { + v = valCopy(v) binary.BigEndian.PutUint16(v[2:4], req.ID) log.F("[dns] %s <-> cache, type: %d, %s", clientAddr, req.Question.QTYPE, req.Question.QNAME) @@ -93,7 +95,7 @@ func (c *Client) Exchange(reqBytes []byte, clientAddr string, preferTCP bool) ([ // add to cache only when there's a valid ip address if len(ips) != 0 && ttl > 0 { - c.cache.Put(qKey(resp.Question), respBytes, ttl) + c.cache.Set(qKey(resp.Question), valCopy(respBytes), ttl) } log.F("[dns] %s <-> %s(%s) via %s, type: %d, %s: %s", @@ -274,7 +276,7 @@ func (c *Client) AddRecord(record string) error { } binary.BigEndian.PutUint16(wb.Bytes()[:2], uint16(n)) - c.cache.Put(qKey(m.Question), wb.Bytes(), LongTTL) + c.cache.Set(qKey(m.Question), wb.Bytes(), 0) return nil } @@ -315,3 +317,11 @@ func qKey(q *Question) string { } return q.QNAME } + +func valCopy(v []byte) (b []byte) { + if v != nil { + b = pool.GetBuffer(len(v)) + copy(b, v) + } + return +} diff --git a/go.mod b/go.mod index 9a39d6b..04139d4 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/xtaci/kcp-go/v5 v5.5.17 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 // indirect - golang.org/x/sys v0.0.0-20201007082116-8445cc04cbdf // indirect - golang.org/x/tools v0.0.0-20201007032633-0806396f153e // indirect + golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71 // indirect + golang.org/x/tools v0.0.0-20201008025239-9df69603baec // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect ) diff --git a/go.sum b/go.sum index a5ddc78..c687699 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201007082116-8445cc04cbdf h1:AvBTl0xbF/KtHyvm61X4gSPF7/dKJ/xQqJwKr1Qu9no= -golang.org/x/sys v0.0.0-20201007082116-8445cc04cbdf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71 h1:ZPX6UakxrJCxWiyGWpXtFY+fp86Esy7xJT/jJCG8bgU= +golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -177,8 +177,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200425043458-8463f397d07c h1:iHhCR0b26amDCiiO+kBguKZom9aMF+NrFxh9zeKR/XU= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200808161706-5bf02b21f123/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201007032633-0806396f153e h1:FJA2W4BQfMGZ+CD/tiAc39HXecuRsJl3EuczaSUu/Yk= -golang.org/x/tools v0.0.0-20201007032633-0806396f153e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201008025239-9df69603baec h1:RY2OghEV/7X1MLaecgm1mwFd3sGvUddm5pGVSxQvX0c= +golang.org/x/tools v0.0.0-20201008025239-9df69603baec/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= diff --git a/proxy/socks5/packet.go b/proxy/socks5/packet.go index 67b0e88..defb594 100644 --- a/proxy/socks5/packet.go +++ b/proxy/socks5/packet.go @@ -28,7 +28,8 @@ func NewPktConn(c net.PacketConn, writeAddr net.Addr, tgtAddr socks.Addr, tgtHea writeAddr: writeAddr, tgtAddr: tgtAddr, tgtHeader: tgtHeader, - ctrlConn: ctrlConn} + ctrlConn: ctrlConn, + } if ctrlConn != nil { go func() { diff --git a/proxy/tls/tls.go b/proxy/tls/tls.go index c33e14e..de5588e 100644 --- a/proxy/tls/tls.go +++ b/proxy/tls/tls.go @@ -54,11 +54,12 @@ func NewTLS(s string, d proxy.Dialer, p proxy.Proxy) (*TLS, error) { } if t.serverName == "" { - host, port, _ := net.SplitHostPort(t.addr) - if port == "" { + idx := strings.LastIndex(t.addr, ":") + if idx == -1 { + idx = len(t.addr) t.addr = net.JoinHostPort(t.addr, "443") } - t.serverName = host + t.serverName = t.addr[:idx] } return t, nil diff --git a/proxy/trojan/trojan.go b/proxy/trojan/trojan.go index b55f419..876c4f8 100644 --- a/proxy/trojan/trojan.go +++ b/proxy/trojan/trojan.go @@ -9,6 +9,7 @@ import ( "encoding/hex" "net" "net/url" + "strings" "github.com/nadoo/glider/log" "github.com/nadoo/glider/pool" @@ -50,11 +51,12 @@ func NewTrojan(s string, d proxy.Dialer, p proxy.Proxy) (*Trojan, error) { } if t.serverName == "" { - host, port, _ := net.SplitHostPort(t.addr) - if port == "" { + idx := strings.LastIndex(t.addr, ":") + if idx == -1 { + idx = len(t.addr) t.addr = net.JoinHostPort(t.addr, "443") } - t.serverName = host + t.serverName = t.addr[:idx] } // pass diff --git a/rule/group.go b/rule/group.go index c8bbad4..3793a75 100644 --- a/rule/group.go +++ b/rule/group.go @@ -236,7 +236,7 @@ func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf [] rc.SetDeadline(time.Now().Add(timeout)) } - _, err = io.WriteString(rc, "GET / HTTP/1.0\r\n\r\n") + _, err = io.WriteString(rc, "GET / HTTP/1.1\r\n\r\n") if err != nil { log.F("[check] %s(%d) -> %s, FAILED. error in write: %s", fwdr.Addr(), fwdr.Priority(), website, err) fwdr.Disable()