2020-09-23 22:14:18 +08:00
|
|
|
package rule
|
2017-07-13 21:55:41 +08:00
|
|
|
|
2017-08-23 16:35:39 +08:00
|
|
|
import (
|
2018-08-25 23:56:18 +08:00
|
|
|
"bytes"
|
2018-08-26 01:25:22 +08:00
|
|
|
"hash/fnv"
|
2018-08-25 23:56:18 +08:00
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
|
|
|
|
2018-06-26 16:15:48 +08:00
|
|
|
"github.com/nadoo/glider/common/log"
|
|
|
|
"github.com/nadoo/glider/proxy"
|
2017-08-23 16:35:39 +08:00
|
|
|
)
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
// StrategyConfig is strategy config struct.
|
|
|
|
type StrategyConfig struct {
|
2020-04-21 12:17:14 +08:00
|
|
|
Strategy string
|
|
|
|
CheckWebSite string
|
|
|
|
CheckInterval int
|
|
|
|
CheckTimeout int
|
|
|
|
CheckDisabledOnly bool
|
|
|
|
MaxFailures int
|
2020-05-04 16:51:41 +08:00
|
|
|
DialTimeout int
|
|
|
|
RelayTimeout int
|
2020-04-21 12:17:14 +08:00
|
|
|
IntFace string
|
2018-08-10 19:03:30 +08:00
|
|
|
}
|
|
|
|
|
2018-08-25 23:56:18 +08:00
|
|
|
// forwarder slice orderd by priority
|
2019-09-18 12:53:04 +08:00
|
|
|
type priSlice []*Forwarder
|
2018-08-25 23:56:18 +08:00
|
|
|
|
|
|
|
func (p priSlice) Len() int { return len(p) }
|
|
|
|
func (p priSlice) Less(i, j int) bool { return p[i].Priority() > p[j].Priority() }
|
|
|
|
func (p priSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
// FwdrGroup is a forwarder group.
|
|
|
|
type FwdrGroup struct {
|
|
|
|
config *StrategyConfig
|
2020-04-03 20:31:59 +08:00
|
|
|
fwdrs priSlice
|
2020-04-05 11:55:48 +08:00
|
|
|
avail []*Forwarder // available forwarders
|
2020-04-03 20:31:59 +08:00
|
|
|
mu sync.RWMutex
|
|
|
|
index uint32
|
|
|
|
priority uint32
|
|
|
|
next func(addr string) *Forwarder
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
// NewFwdrGroup returns a new forward group.
|
|
|
|
func NewFwdrGroup(name string, s []string, c *StrategyConfig) *FwdrGroup {
|
2019-09-18 12:53:04 +08:00
|
|
|
var fwdrs []*Forwarder
|
2018-08-10 19:03:30 +08:00
|
|
|
for _, chain := range s {
|
2020-05-04 16:51:41 +08:00
|
|
|
fwdr, err := ForwarderFromURL(chain, c.IntFace,
|
|
|
|
time.Duration(c.DialTimeout)*time.Second, time.Duration(c.RelayTimeout)*time.Second)
|
2018-08-10 19:03:30 +08:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2018-08-23 00:01:31 +08:00
|
|
|
fwdr.SetMaxFailures(uint32(c.MaxFailures))
|
2018-08-10 19:03:30 +08:00
|
|
|
fwdrs = append(fwdrs, fwdr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(fwdrs) == 0 {
|
2019-09-18 12:53:04 +08:00
|
|
|
// direct forwarder
|
2020-05-04 16:51:41 +08:00
|
|
|
fwdrs = append(fwdrs, DirectForwarder(c.IntFace,
|
|
|
|
time.Duration(c.DialTimeout)*time.Second, time.Duration(c.RelayTimeout)*time.Second))
|
2019-09-18 12:53:04 +08:00
|
|
|
c.Strategy = "rr"
|
2018-01-13 20:08:49 +08:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
return newFwdrGroup(name, fwdrs, c)
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
// newFwdrGroup returns a new Proxy.
|
|
|
|
func newFwdrGroup(name string, fwdrs []*Forwarder, c *StrategyConfig) *FwdrGroup {
|
|
|
|
p := &FwdrGroup{fwdrs: fwdrs, config: c}
|
2020-04-03 20:31:59 +08:00
|
|
|
sort.Sort(p.fwdrs)
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
p.init()
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
if strings.IndexByte(p.config.CheckWebSite, ':') == -1 {
|
|
|
|
p.config.CheckWebSite += ":80"
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2018-08-10 19:03:30 +08:00
|
|
|
switch c.Strategy {
|
2018-01-13 20:08:49 +08:00
|
|
|
case "rr":
|
2020-04-03 20:31:59 +08:00
|
|
|
p.next = p.scheduleRR
|
2020-08-26 19:21:35 +08:00
|
|
|
log.F("[strategy] %s: forward in round robin mode.", name)
|
2018-01-13 20:08:49 +08:00
|
|
|
case "ha":
|
2020-04-03 20:31:59 +08:00
|
|
|
p.next = p.scheduleHA
|
2020-08-26 19:21:35 +08:00
|
|
|
log.F("[strategy] %s: forward in high availability mode.", name)
|
2018-08-14 19:33:18 +08:00
|
|
|
case "lha":
|
2020-04-03 20:31:59 +08:00
|
|
|
p.next = p.scheduleLHA
|
2020-08-26 19:21:35 +08:00
|
|
|
log.F("[strategy] %s: forward in latency based high availability mode.", name)
|
2018-08-24 18:45:57 +08:00
|
|
|
case "dh":
|
2020-04-03 20:31:59 +08:00
|
|
|
p.next = p.scheduleDH
|
2020-08-26 19:21:35 +08:00
|
|
|
log.F("[strategy] %s: forward in destination hashing mode.", name)
|
2018-01-13 20:08:49 +08:00
|
|
|
default:
|
2020-04-03 20:31:59 +08:00
|
|
|
p.next = p.scheduleRR
|
2020-08-26 19:21:35 +08:00
|
|
|
log.F("[strategy] %s: not supported forward mode '%s', use round robin mode.", name, c.Strategy)
|
2017-07-29 21:31:01 +08:00
|
|
|
}
|
|
|
|
|
2018-08-25 23:56:18 +08:00
|
|
|
for _, f := range fwdrs {
|
2020-04-03 20:31:59 +08:00
|
|
|
f.AddHandler(p.onStatusChanged)
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
return p
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// Dial connects to the address addr on the network net.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) Dial(network, addr string) (net.Conn, proxy.Dialer, error) {
|
2019-09-18 19:40:14 +08:00
|
|
|
nd := p.NextDialer(addr)
|
|
|
|
c, err := nd.Dial(network, addr)
|
2020-04-28 15:18:19 +08:00
|
|
|
return c, nd, err
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// DialUDP connects to the given address.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) DialUDP(network, addr string) (pc net.PacketConn, writeTo net.Addr, err error) {
|
2019-09-18 19:40:14 +08:00
|
|
|
return p.NextDialer(addr).DialUDP(network, addr)
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// NextDialer returns the next dialer.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) NextDialer(dstAddr string) proxy.Dialer {
|
2019-09-18 19:40:14 +08:00
|
|
|
p.mu.RLock()
|
|
|
|
defer p.mu.RUnlock()
|
2018-08-26 01:43:28 +08:00
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
if len(p.avail) == 0 {
|
2020-04-04 00:03:47 +08:00
|
|
|
return p.fwdrs[atomic.AddUint32(&p.index, 1)%uint32(len(p.fwdrs))]
|
2020-04-03 20:15:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return p.next(dstAddr)
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2020-04-28 15:18:19 +08:00
|
|
|
// Record records result while using the dialer from proxy.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) Record(dialer proxy.Dialer, success bool) {
|
2020-05-01 20:54:23 +08:00
|
|
|
OnRecord(dialer, success)
|
|
|
|
}
|
|
|
|
|
2020-05-02 21:49:30 +08:00
|
|
|
// OnRecord records result while using the dialer from proxy.
|
2020-05-01 20:54:23 +08:00
|
|
|
func OnRecord(dialer proxy.Dialer, success bool) {
|
|
|
|
if fwdr, ok := dialer.(*Forwarder); ok {
|
2020-05-02 21:49:30 +08:00
|
|
|
if !success {
|
2020-05-01 20:54:23 +08:00
|
|
|
fwdr.IncFailures()
|
2020-05-02 21:49:30 +08:00
|
|
|
} else {
|
|
|
|
fwdr.Enable()
|
2020-05-01 20:54:23 +08:00
|
|
|
}
|
2020-04-28 15:18:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// Priority returns the active priority of dialer.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) Priority() uint32 { return atomic.LoadUint32(&p.priority) }
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// SetPriority sets the active priority of daler.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) SetPriority(pri uint32) { atomic.StoreUint32(&p.priority, pri) }
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
// init traverse d.fwdrs and init the available forwarder slice.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) init() {
|
2019-09-18 19:40:14 +08:00
|
|
|
for _, f := range p.fwdrs {
|
2018-08-26 01:25:22 +08:00
|
|
|
if f.Enabled() {
|
2019-09-18 19:40:14 +08:00
|
|
|
p.SetPriority(f.Priority())
|
2018-08-26 01:25:22 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
p.avail = nil
|
2019-09-18 19:40:14 +08:00
|
|
|
for _, f := range p.fwdrs {
|
|
|
|
if f.Enabled() && f.Priority() >= p.Priority() {
|
2020-04-03 20:31:59 +08:00
|
|
|
p.avail = append(p.avail, f)
|
2018-08-26 01:25:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
if len(p.avail) == 0 {
|
2018-08-29 23:54:00 +08:00
|
|
|
// no available forwarders, set priority to 0 to check all forwarders in check func
|
2019-09-18 19:40:14 +08:00
|
|
|
p.SetPriority(0)
|
2020-07-13 10:30:40 +08:00
|
|
|
// log.F("[strategy] no available forwarders, please check your config file or network settings")
|
2018-08-26 01:25:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// onStatusChanged will be called when fwdr's status changed.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) onStatusChanged(fwdr *Forwarder) {
|
2019-09-18 19:40:14 +08:00
|
|
|
p.mu.Lock()
|
|
|
|
defer p.mu.Unlock()
|
2018-08-25 23:56:18 +08:00
|
|
|
|
|
|
|
if fwdr.Enabled() {
|
2018-08-26 22:36:14 +08:00
|
|
|
log.F("[strategy] %s changed status from Disabled to Enabled ", fwdr.Addr())
|
2019-09-18 19:40:14 +08:00
|
|
|
if fwdr.Priority() == p.Priority() {
|
2020-04-03 20:31:59 +08:00
|
|
|
p.avail = append(p.avail, fwdr)
|
2019-09-18 19:40:14 +08:00
|
|
|
} else if fwdr.Priority() > p.Priority() {
|
2020-04-03 20:31:59 +08:00
|
|
|
p.init()
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
2018-08-26 22:36:14 +08:00
|
|
|
} else {
|
|
|
|
log.F("[strategy] %s changed status from Enabled to Disabled", fwdr.Addr())
|
2020-04-03 20:31:59 +08:00
|
|
|
for i, f := range p.avail {
|
2018-08-25 23:56:18 +08:00
|
|
|
if f == fwdr {
|
2020-04-03 20:31:59 +08:00
|
|
|
p.avail[i], p.avail = p.avail[len(p.avail)-1], p.avail[:len(p.avail)-1]
|
2018-08-25 23:56:18 +08:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 20:31:59 +08:00
|
|
|
if len(p.avail) == 0 {
|
|
|
|
p.init()
|
|
|
|
}
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2019-03-18 23:37:01 +08:00
|
|
|
// Check implements the Checker interface.
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) Check() {
|
2019-09-18 12:53:04 +08:00
|
|
|
// no need to check when there's only 1 forwarder
|
2019-09-18 19:40:14 +08:00
|
|
|
if len(p.fwdrs) > 1 {
|
|
|
|
for i := 0; i < len(p.fwdrs); i++ {
|
2020-04-21 00:50:12 +08:00
|
|
|
go p.check(p.fwdrs[i])
|
2019-09-18 12:53:04 +08:00
|
|
|
}
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) check(f *Forwarder) {
|
2020-04-21 00:50:12 +08:00
|
|
|
wait := uint8(0)
|
2018-08-25 23:56:18 +08:00
|
|
|
buf := make([]byte, 4)
|
2020-04-21 00:50:12 +08:00
|
|
|
intval := time.Duration(p.config.CheckInterval) * time.Second
|
2018-08-25 23:56:18 +08:00
|
|
|
|
|
|
|
for {
|
2020-04-21 00:50:12 +08:00
|
|
|
time.Sleep(intval * time.Duration(wait))
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2018-08-29 23:54:00 +08:00
|
|
|
// check all forwarders at least one time
|
2020-04-21 00:50:12 +08:00
|
|
|
if wait > 0 && (f.Priority() < p.Priority()) {
|
2018-08-29 23:54:00 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-04-21 12:17:14 +08:00
|
|
|
if f.Enabled() && p.config.CheckDisabledOnly {
|
2020-04-21 00:50:12 +08:00
|
|
|
continue
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
|
|
|
|
2020-04-21 00:50:12 +08:00
|
|
|
if checkWebSite(f, p.config.CheckWebSite, time.Duration(p.config.CheckTimeout)*time.Second, buf) {
|
|
|
|
wait = 1
|
2018-08-25 23:56:18 +08:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-04-21 00:50:12 +08:00
|
|
|
if wait == 0 {
|
|
|
|
wait = 1
|
|
|
|
}
|
2019-03-18 23:37:01 +08:00
|
|
|
|
2020-04-21 00:50:12 +08:00
|
|
|
wait *= 2
|
|
|
|
if wait > 16 {
|
|
|
|
wait = 16
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
2020-04-21 00:50:12 +08:00
|
|
|
}
|
|
|
|
}
|
2018-08-25 23:56:18 +08:00
|
|
|
|
2020-04-21 00:50:12 +08:00
|
|
|
func checkWebSite(fwdr *Forwarder, website string, timeout time.Duration, buf []byte) bool {
|
|
|
|
startTime := time.Now()
|
|
|
|
|
|
|
|
rc, err := fwdr.Dial("tcp", website)
|
|
|
|
if err != nil {
|
|
|
|
fwdr.Disable()
|
|
|
|
log.F("[check] %s(%d) -> %s, DISABLED. error in dial: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
|
2020-05-05 01:30:57 +08:00
|
|
|
if timeout > 0 {
|
|
|
|
rc.SetDeadline(time.Now().Add(timeout))
|
|
|
|
}
|
2020-05-04 15:33:26 +08:00
|
|
|
|
2020-08-10 18:43:41 +08:00
|
|
|
_, err = io.WriteString(rc, "GET / HTTP/1.0\r\n\r\n")
|
2020-04-21 00:50:12 +08:00
|
|
|
if err != nil {
|
|
|
|
fwdr.Disable()
|
|
|
|
log.F("[check] %s(%d) -> %s, DISABLED. error in write: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, err)
|
|
|
|
return false
|
2018-08-25 23:56:18 +08:00
|
|
|
}
|
2020-04-21 00:50:12 +08:00
|
|
|
|
|
|
|
_, err = io.ReadFull(rc, buf)
|
|
|
|
if err != nil {
|
|
|
|
fwdr.Disable()
|
|
|
|
log.F("[check] %s(%d) -> %s, DISABLED. error in read: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Equal([]byte("HTTP"), buf) {
|
|
|
|
fwdr.Disable()
|
|
|
|
log.F("[check] %s(%d) -> %s, DISABLED. server response: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, buf)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
readTime := time.Since(startTime)
|
|
|
|
fwdr.SetLatency(int64(readTime))
|
|
|
|
|
|
|
|
if readTime > timeout {
|
|
|
|
fwdr.Disable()
|
|
|
|
log.F("[check] %s(%d) -> %s, DISABLED. check timeout: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, readTime)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fwdr.Enable()
|
|
|
|
log.F("[check] %s(%d) -> %s, ENABLED. connect time: %s", fwdr.Addr(), fwdr.Priority(),
|
|
|
|
website, readTime)
|
|
|
|
|
|
|
|
return true
|
2017-07-29 21:31:01 +08:00
|
|
|
}
|
2018-08-26 01:25:22 +08:00
|
|
|
|
2018-08-27 19:38:42 +08:00
|
|
|
// Round Robin
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) scheduleRR(dstAddr string) *Forwarder {
|
2020-04-03 20:31:59 +08:00
|
|
|
return p.avail[atomic.AddUint32(&p.index, 1)%uint32(len(p.avail))]
|
2018-08-26 01:25:22 +08:00
|
|
|
}
|
|
|
|
|
2018-08-27 19:38:42 +08:00
|
|
|
// High Availability
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) scheduleHA(dstAddr string) *Forwarder {
|
2020-04-03 20:31:59 +08:00
|
|
|
return p.avail[0]
|
2018-08-26 01:25:22 +08:00
|
|
|
}
|
|
|
|
|
2018-08-27 19:38:42 +08:00
|
|
|
// Latency based High Availability
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) scheduleLHA(dstAddr string) *Forwarder {
|
2020-04-03 20:31:59 +08:00
|
|
|
fwdr := p.avail[0]
|
2018-08-26 01:25:22 +08:00
|
|
|
lowest := fwdr.Latency()
|
2020-04-03 20:31:59 +08:00
|
|
|
for _, f := range p.avail {
|
2018-08-26 01:25:22 +08:00
|
|
|
if f.Latency() < lowest {
|
|
|
|
lowest = f.Latency()
|
|
|
|
fwdr = f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fwdr
|
|
|
|
}
|
|
|
|
|
2018-08-27 19:38:42 +08:00
|
|
|
// Destination Hashing
|
2020-09-23 22:14:18 +08:00
|
|
|
func (p *FwdrGroup) scheduleDH(dstAddr string) *Forwarder {
|
2018-08-26 01:25:22 +08:00
|
|
|
fnv1a := fnv.New32a()
|
|
|
|
fnv1a.Write([]byte(dstAddr))
|
2020-04-03 20:31:59 +08:00
|
|
|
return p.avail[fnv1a.Sum32()%uint32(len(p.avail))]
|
2018-08-26 01:25:22 +08:00
|
|
|
}
|