gost_software/gost/chain.go
2017-07-23 21:20:09 +08:00

111 lines
2.1 KiB
Go

package gost
import (
"errors"
"net"
)
var (
// ErrEmptyChain is an error that implies the chain is empty.
ErrEmptyChain = errors.New("empty chain")
)
// Chain is a proxy chain that holds a list of proxy nodes.
type Chain struct {
nodes []Node
}
// NewChain creates a proxy chain with proxy nodes nodes.
func NewChain(nodes ...Node) *Chain {
return &Chain{
nodes: nodes,
}
}
// Nodes returns the proxy nodes that the chain holds.
func (c *Chain) Nodes() []Node {
return c.nodes
}
// LastNode returns the last node of the node list.
// If the chain is empty, an empty node is returns.
func (c *Chain) LastNode() Node {
if c.IsEmpty() {
return Node{}
}
return c.nodes[len(c.nodes)-1]
}
// AddNode appends the node(s) to the chain.
func (c *Chain) AddNode(nodes ...Node) {
if c == nil {
return
}
c.nodes = append(c.nodes, nodes...)
}
// IsEmpty checks if the chain is empty.
// An empty chain means that there is no proxy node in the chain.
func (c *Chain) IsEmpty() bool {
return c == nil || len(c.nodes) == 0
}
// Dial connects to the target address addr through the chain.
// If the chain is empty, it will use the net.Dial directly.
func (c *Chain) Dial(addr string) (net.Conn, error) {
if c.IsEmpty() {
return net.Dial("tcp", addr)
}
conn, err := c.Conn()
if err != nil {
return nil, err
}
cc, err := c.LastNode().Client.Connect(conn, addr)
if err != nil {
conn.Close()
return nil, err
}
return cc, nil
}
// Conn obtains a handshaked connection to the last node of the chain.
// If the chain is empty, it returns an ErrEmptyChain error.
func (c *Chain) Conn() (net.Conn, error) {
if c.IsEmpty() {
return nil, ErrEmptyChain
}
nodes := c.nodes
conn, err := nodes[0].Client.Dial(nodes[0].Addr)
if err != nil {
return nil, err
}
conn, err = nodes[0].Client.Handshake(conn)
if err != nil {
return nil, err
}
for i, node := range nodes {
if i == len(nodes)-1 {
break
}
next := nodes[i+1]
cc, err := node.Client.Connect(conn, next.Addr)
if err != nil {
conn.Close()
return nil, err
}
cc, err = next.Client.Handshake(cc)
if err != nil {
conn.Close()
return nil, err
}
conn = cc
}
return conn, nil
}