|
|
|
@ -1,13 +1,17 @@ |
|
|
|
package gost |
|
|
|
|
|
|
|
import ( |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"net/url" |
|
|
|
"strconv" |
|
|
|
"strings" |
|
|
|
"sync" |
|
|
|
"sync/atomic" |
|
|
|
"time" |
|
|
|
) |
|
|
|
|
|
|
|
var ( |
|
|
|
// ErrInvalidNode is an error that implies the node is invalid.
|
|
|
|
ErrInvalidNode = errors.New("invalid node") |
|
|
|
) |
|
|
|
|
|
|
|
// Node is a proxy node, mainly used to construct a proxy chain.
|
|
|
|
@ -23,9 +27,7 @@ type Node struct { |
|
|
|
DialOptions []DialOption |
|
|
|
HandshakeOptions []HandshakeOption |
|
|
|
Client *Client |
|
|
|
group *NodeGroup |
|
|
|
failCount uint32 |
|
|
|
failTime int64 |
|
|
|
marker *failMarker |
|
|
|
Bypass *Bypass |
|
|
|
} |
|
|
|
|
|
|
|
@ -33,8 +35,9 @@ type Node struct { |
|
|
|
// The proxy node string pattern is [scheme://][user:pass@host]:port.
|
|
|
|
// Scheme can be divided into two parts by character '+', such as: http+tls.
|
|
|
|
func ParseNode(s string) (node Node, err error) { |
|
|
|
s = strings.TrimSpace(s) |
|
|
|
if s == "" { |
|
|
|
return Node{}, nil |
|
|
|
return Node{}, ErrInvalidNode |
|
|
|
} |
|
|
|
|
|
|
|
if !strings.Contains(s, "://") { |
|
|
|
@ -51,6 +54,7 @@ func ParseNode(s string) (node Node, err error) { |
|
|
|
Remote: strings.Trim(u.EscapedPath(), "/"), |
|
|
|
Values: u.Query(), |
|
|
|
User: u.User, |
|
|
|
marker: &failMarker{}, |
|
|
|
} |
|
|
|
|
|
|
|
schemes := strings.Split(u.Scheme, "+") |
|
|
|
@ -89,25 +93,29 @@ func ParseNode(s string) (node Node, err error) { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// MarkDead marks the node fail status.
|
|
|
|
func (node *Node) MarkDead() { |
|
|
|
if node.marker == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
node.marker.Mark() |
|
|
|
} |
|
|
|
|
|
|
|
// ResetDead resets the node fail status.
|
|
|
|
func (node *Node) ResetDead() { |
|
|
|
if node.marker == nil { |
|
|
|
return |
|
|
|
} |
|
|
|
node.marker.Reset() |
|
|
|
} |
|
|
|
|
|
|
|
// Clone clones the node, it will prevent data race.
|
|
|
|
func (node *Node) Clone() Node { |
|
|
|
return Node{ |
|
|
|
ID: node.ID, |
|
|
|
Addr: node.Addr, |
|
|
|
Host: node.Host, |
|
|
|
Protocol: node.Protocol, |
|
|
|
Transport: node.Transport, |
|
|
|
Remote: node.Remote, |
|
|
|
User: node.User, |
|
|
|
Values: node.Values, |
|
|
|
DialOptions: node.DialOptions, |
|
|
|
HandshakeOptions: node.HandshakeOptions, |
|
|
|
Client: node.Client, |
|
|
|
group: node.group, |
|
|
|
failCount: atomic.LoadUint32(&node.failCount), |
|
|
|
failTime: atomic.LoadInt64(&node.failTime), |
|
|
|
Bypass: node.Bypass, |
|
|
|
nd := *node |
|
|
|
if node.marker != nil { |
|
|
|
nd.marker = node.marker.Clone() |
|
|
|
} |
|
|
|
return nd |
|
|
|
} |
|
|
|
|
|
|
|
// Get returns node parameter specified by key.
|
|
|
|
@ -127,8 +135,9 @@ func (node *Node) GetInt(key string) int { |
|
|
|
return n |
|
|
|
} |
|
|
|
|
|
|
|
func (node *Node) String() string { |
|
|
|
return fmt.Sprintf("%d@%s", node.ID, node.Addr) |
|
|
|
func (node Node) String() string { |
|
|
|
return fmt.Sprintf("%d@%s+%s://%s", |
|
|
|
node.ID, node.Protocol, node.Transport, node.Addr) |
|
|
|
} |
|
|
|
|
|
|
|
// NodeGroup is a group of nodes.
|
|
|
|
@ -194,18 +203,7 @@ func (group *NodeGroup) Nodes() []Node { |
|
|
|
return group.nodes |
|
|
|
} |
|
|
|
|
|
|
|
func (group *NodeGroup) copyNodes() []Node { |
|
|
|
group.mux.RLock() |
|
|
|
defer group.mux.RUnlock() |
|
|
|
|
|
|
|
var nodes []Node |
|
|
|
for i := range group.nodes { |
|
|
|
nodes = append(nodes, group.nodes[i]) |
|
|
|
} |
|
|
|
return nodes |
|
|
|
} |
|
|
|
|
|
|
|
// GetNode returns a copy of the node specified by index in the group.
|
|
|
|
// GetNode returns the node specified by index in the group.
|
|
|
|
func (group *NodeGroup) GetNode(i int) Node { |
|
|
|
group.mux.RLock() |
|
|
|
defer group.mux.RUnlock() |
|
|
|
@ -213,43 +211,7 @@ func (group *NodeGroup) GetNode(i int) Node { |
|
|
|
if i < 0 || group == nil || len(group.nodes) <= i { |
|
|
|
return Node{} |
|
|
|
} |
|
|
|
return group.nodes[i].Clone() |
|
|
|
} |
|
|
|
|
|
|
|
// MarkDeadNode marks the node with ID nid status to dead.
|
|
|
|
func (group *NodeGroup) MarkDeadNode(nid int) { |
|
|
|
group.mux.RLock() |
|
|
|
defer group.mux.RUnlock() |
|
|
|
|
|
|
|
if group == nil || nid <= 0 { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
for i := range group.nodes { |
|
|
|
if group.nodes[i].ID == nid { |
|
|
|
atomic.AddUint32(&group.nodes[i].failCount, 1) |
|
|
|
atomic.StoreInt64(&group.nodes[i].failTime, time.Now().Unix()) |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ResetDeadNode resets the node with ID nid status.
|
|
|
|
func (group *NodeGroup) ResetDeadNode(nid int) { |
|
|
|
group.mux.RLock() |
|
|
|
defer group.mux.RUnlock() |
|
|
|
|
|
|
|
if group == nil || nid <= 0 { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
for i := range group.nodes { |
|
|
|
if group.nodes[i].ID == nid { |
|
|
|
atomic.StoreUint32(&group.nodes[i].failCount, 0) |
|
|
|
atomic.StoreInt64(&group.nodes[i].failTime, 0) |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
return group.nodes[i] |
|
|
|
} |
|
|
|
|
|
|
|
// Next selects a node from group.
|
|
|
|
@ -272,7 +234,6 @@ func (group *NodeGroup) Next() (node Node, err error) { |
|
|
|
if err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
node.group = group |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
|