From d9cce6332c283632469f1e72a72d711bc0f21b2c Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Mon, 12 Nov 2018 16:28:22 +0800 Subject: [PATCH] add live reloading support for peer config --- cmd/gost/cfg.go | 49 -------------- cmd/gost/main.go | 53 ++------------- cmd/gost/peer.go | 163 +++++++++++++++++++++++++++++++++++++++++++++ cmd/gost/peer.json | 18 ----- cmd/gost/peer.txt | 14 ++++ node.go | 7 +- 6 files changed, 189 insertions(+), 115 deletions(-) create mode 100644 cmd/gost/peer.go delete mode 100644 cmd/gost/peer.json create mode 100644 cmd/gost/peer.txt diff --git a/cmd/gost/cfg.go b/cmd/gost/cfg.go index 30eb321..4eb9dfc 100644 --- a/cmd/gost/cfg.go +++ b/cmd/gost/cfg.go @@ -173,55 +173,6 @@ func parseIP(s string, port string) (ips []string) { return } -type peerConfig struct { - Strategy string `json:"strategy"` - Filters []string `json:"filters"` - MaxFails int `json:"max_fails"` - FailTimeout int `json:"fail_timeout"` - Nodes []string `json:"nodes"` - Bypass *bypass `json:"bypass"` // global bypass -} - -type bypass struct { - Reverse bool `json:"reverse"` - Patterns []string `json:"patterns"` -} - -func loadPeerConfig(peer string) (config peerConfig, err error) { - if peer == "" { - return - } - content, err := ioutil.ReadFile(peer) - if err != nil { - return - } - err = json.Unmarshal(content, &config) - return -} - -func (cfg *peerConfig) Validate() { - if cfg.MaxFails <= 0 { - cfg.MaxFails = 1 - } - if cfg.FailTimeout <= 0 { - cfg.FailTimeout = 30 // seconds - } -} - -func parseStrategy(s string) gost.Strategy { - switch s { - case "random": - return &gost.RandomStrategy{} - case "fifo": - return &gost.FIFOStrategy{} - case "round": - fallthrough - default: - return &gost.RoundStrategy{} - - } -} - func parseBypass(s string) *gost.Bypass { if s == "" { return nil diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 7aa26e1..8a8b12c 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -6,6 +6,7 @@ import ( "flag" "fmt" "net" + // _ "net/http/pprof" "os" "runtime" @@ -102,65 +103,23 @@ func (r *route) initChain() (*gost.Chain, error) { ngroup.ID = gid gid++ - // parse the base node + // parse the base nodes nodes, err := parseChainNode(ns) if err != nil { return nil, err } nid := 1 // node ID - for i := range nodes { nodes[i].ID = nid nid++ } ngroup.AddNode(nodes...) - // parse peer nodes if exists - peerCfg, err := loadPeerConfig(nodes[0].Get("peer")) - if err != nil { - log.Log(err) - } - peerCfg.Validate() - - strategy := peerCfg.Strategy - // overwrite the strategry in the peer config if `strategy` param exists. - if s := nodes[0].Get("strategy"); s != "" { - strategy = s - } - ngroup.Options = append(ngroup.Options, - gost.WithFilter(&gost.FailFilter{ - MaxFails: peerCfg.MaxFails, - FailTimeout: time.Duration(peerCfg.FailTimeout) * time.Second, - }), - gost.WithStrategy(parseStrategy(strategy)), - ) - - for _, s := range peerCfg.Nodes { - nodes, err = parseChainNode(s) - if err != nil { - return nil, err - } - - for i := range nodes { - nodes[i].ID = nid - nid++ - } - - ngroup.AddNode(nodes...) - } - - var bypass *gost.Bypass - // global bypass - if peerCfg.Bypass != nil { - bypass = gost.NewBypassPatterns(peerCfg.Bypass.Reverse, peerCfg.Bypass.Patterns...) - } - nodes = ngroup.Nodes() - for i := range nodes { - if nodes[i].Bypass == nil { - nodes[i].Bypass = bypass // use global bypass if local bypass does not exist. - } - } + go gost.PeriodReload(&peerConfig{ + group: ngroup, + baseNodes: nodes, + }, nodes[0].Get("peer")) chain.AddNodeGroup(ngroup) } diff --git a/cmd/gost/peer.go b/cmd/gost/peer.go new file mode 100644 index 0000000..7952ba2 --- /dev/null +++ b/cmd/gost/peer.go @@ -0,0 +1,163 @@ +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "io" + "io/ioutil" + "strconv" + "strings" + "time" + + "github.com/ginuerzh/gost" +) + +type peerConfig struct { + Strategy string `json:"strategy"` + MaxFails int `json:"max_fails"` + FailTimeout time.Duration `json:"fail_timeout"` + period time.Duration // the period for live reloading + Nodes []string `json:"nodes"` + group *gost.NodeGroup + baseNodes []gost.Node +} + +type bypass struct { + Reverse bool `json:"reverse"` + Patterns []string `json:"patterns"` +} + +func parsePeerConfig(cfg string, group *gost.NodeGroup, baseNodes []gost.Node) *peerConfig { + pc := &peerConfig{ + group: group, + baseNodes: baseNodes, + } + go gost.PeriodReload(pc, cfg) + return pc +} + +func (cfg *peerConfig) Validate() { + if cfg.MaxFails <= 0 { + cfg.MaxFails = 1 + } + if cfg.FailTimeout <= 0 { + cfg.FailTimeout = 30 // seconds + } +} + +func (cfg *peerConfig) Reload(r io.Reader) error { + if err := cfg.parse(r); err != nil { + return err + } + cfg.Validate() + + group := cfg.group + strategy := cfg.Strategy + if len(cfg.baseNodes) > 0 { + // overwrite the strategry in the peer config if `strategy` param exists. + if s := cfg.baseNodes[0].Get("strategy"); s != "" { + strategy = s + } + } + group.Options = append([]gost.SelectOption{}, + gost.WithFilter(&gost.FailFilter{ + MaxFails: cfg.MaxFails, + FailTimeout: time.Duration(cfg.FailTimeout) * time.Second, + }), + gost.WithStrategy(parseStrategy(strategy)), + ) + + gNodes := cfg.baseNodes + nid := len(gNodes) + 1 + for _, s := range cfg.Nodes { + nodes, err := parseChainNode(s) + if err != nil { + return err + } + + for i := range nodes { + nodes[i].ID = nid + nid++ + } + + gNodes = append(gNodes, nodes...) + } + + group.SetNodes(gNodes...) + + return nil +} + +func (cfg *peerConfig) parse(r io.Reader) error { + data, err := ioutil.ReadAll(r) + if err != nil { + return err + } + + // compatible with JSON format + if err := json.NewDecoder(bytes.NewReader(data)).Decode(cfg); err == nil { + return nil + } + + split := func(line string) []string { + if line == "" { + return nil + } + if n := strings.IndexByte(line, '#'); n >= 0 { + line = line[:n] + } + line = strings.Replace(line, "\t", " ", -1) + line = strings.TrimSpace(line) + + var ss []string + for _, s := range strings.Split(line, " ") { + if s = strings.TrimSpace(s); s != "" { + ss = append(ss, s) + } + } + return ss + } + + cfg.Nodes = nil + scanner := bufio.NewScanner(bytes.NewReader(data)) + for scanner.Scan() { + line := scanner.Text() + ss := split(line) + if len(ss) < 2 { + continue + } + + switch ss[0] { + case "strategy": + cfg.Strategy = ss[1] + case "max_fails": + cfg.MaxFails, _ = strconv.Atoi(ss[1]) + case "fail_timeout": + cfg.FailTimeout, _ = time.ParseDuration(ss[1]) + case "reload": + cfg.period, _ = time.ParseDuration(ss[1]) + case "peer": + cfg.Nodes = append(cfg.Nodes, ss[1]) + } + } + + return scanner.Err() +} + +func (cfg *peerConfig) Period() time.Duration { + return cfg.period +} + +func parseStrategy(s string) gost.Strategy { + switch s { + case "random": + return &gost.RandomStrategy{} + case "fifo": + return &gost.FIFOStrategy{} + case "round": + fallthrough + default: + return &gost.RoundStrategy{} + } +} diff --git a/cmd/gost/peer.json b/cmd/gost/peer.json deleted file mode 100644 index 4cbcd4d..0000000 --- a/cmd/gost/peer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "strategy": "round", - "max_fails": 3, - "fail_timeout": 30, - "nodes":[ - "socks5://:1081", - "socks://:1082", - "socks4a://:1083" - ], - "bypass":{ - "reverse": false, - "patterns": [ - "10.0.0.1", - "192.168.0.0/24", - "*.example.com" - ] - } -} \ No newline at end of file diff --git a/cmd/gost/peer.txt b/cmd/gost/peer.txt new file mode 100644 index 0000000..eb87043 --- /dev/null +++ b/cmd/gost/peer.txt @@ -0,0 +1,14 @@ +# strategy for node selecting +strategy random + +max_fails 1 + +fail_timeout 30s + +# period for live reloading +reload 10s + +# peers +peer http://:18080 +peer socks://:11080 +peer ss://chacha20:123456@:18338 \ No newline at end of file diff --git a/node.go b/node.go index c70c134..247dcff 100644 --- a/node.go +++ b/node.go @@ -180,7 +180,7 @@ func NewNodeGroup(nodes ...Node) *NodeGroup { } } -// AddNode adds node or node list into group +// AddNode appends node or node list into group node. func (group *NodeGroup) AddNode(node ...Node) { if group == nil { return @@ -188,6 +188,11 @@ func (group *NodeGroup) AddNode(node ...Node) { group.nodes = append(group.nodes, node...) } +// SetNodes replaces the group nodes to the specified nodes. +func (group *NodeGroup) SetNodes(nodes ...Node) { + group.nodes = nodes +} + // SetSelector sets node selector with options for the group. func (group *NodeGroup) SetSelector(selector NodeSelector, opts ...SelectOption) { if group == nil {