Merge branch 'dev' of github.com:ginuerzh/gost into dev
This commit is contained in:
commit
b3068a7d8f
133
chain.go
133
chain.go
@ -1,9 +1,10 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
@ -15,6 +16,8 @@ var (
|
||||
|
||||
// Chain is a proxy chain that holds a list of proxy nodes.
|
||||
type Chain struct {
|
||||
isRoute bool
|
||||
Retries int
|
||||
nodeGroups []*NodeGroup
|
||||
}
|
||||
|
||||
@ -27,6 +30,12 @@ func NewChain(nodes ...Node) *Chain {
|
||||
return chain
|
||||
}
|
||||
|
||||
func newRoute(nodes ...Node) *Chain {
|
||||
chain := NewChain(nodes...)
|
||||
chain.isRoute = true
|
||||
return chain
|
||||
}
|
||||
|
||||
// Nodes returns the proxy nodes that the chain holds.
|
||||
// If a node is a node group, the first node in the group will be returned.
|
||||
func (c *Chain) Nodes() (nodes []Node) {
|
||||
@ -50,8 +59,8 @@ func (c *Chain) LastNode() Node {
|
||||
if c.IsEmpty() {
|
||||
return Node{}
|
||||
}
|
||||
last := c.nodeGroups[len(c.nodeGroups)-1]
|
||||
return last.nodes[0]
|
||||
group := c.nodeGroups[len(c.nodeGroups)-1]
|
||||
return group.nodes[0].Clone()
|
||||
}
|
||||
|
||||
// LastNodeGroup returns the last group of the group list.
|
||||
@ -90,17 +99,27 @@ func (c *Chain) IsEmpty() bool {
|
||||
|
||||
// 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) {
|
||||
func (c *Chain) Dial(addr string) (conn net.Conn, err error) {
|
||||
if c.IsEmpty() {
|
||||
return net.Dial("tcp", addr)
|
||||
return net.DialTimeout("tcp", addr, DialTimeout)
|
||||
}
|
||||
|
||||
for i := 0; i < c.Retries+1; i++ {
|
||||
conn, err = c.dial(addr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Chain) dial(addr string) (net.Conn, error) {
|
||||
route, err := c.selectRoute()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := c.getConn(route)
|
||||
conn, err := route.getConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -116,77 +135,60 @@ func (c *Chain) Dial(addr string) (net.Conn, error) {
|
||||
// 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() (conn net.Conn, err error) {
|
||||
route, err := c.selectRoute()
|
||||
for i := 0; i < c.Retries+1; i++ {
|
||||
var route *Chain
|
||||
route, err = c.selectRoute()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
continue
|
||||
}
|
||||
conn, err = route.getConn()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
conn, err = c.getConn(route)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Chain) selectRoute() (route *Chain, err error) {
|
||||
route = NewChain()
|
||||
for _, group := range c.nodeGroups {
|
||||
selector := group.Selector
|
||||
if selector == nil {
|
||||
selector = &defaultSelector{}
|
||||
}
|
||||
// select node from node group
|
||||
node, err := selector.Select(group.Nodes(), group.Options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node.Client.Transporter.Multiplex() {
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
ChainDialOption(route),
|
||||
)
|
||||
route = NewChain() // cutoff the chain for multiplex
|
||||
}
|
||||
route.AddNode(node)
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Chain) getConn(route *Chain) (conn net.Conn, err error) {
|
||||
if route.IsEmpty() {
|
||||
func (c *Chain) getConn() (conn net.Conn, err error) {
|
||||
if c.IsEmpty() {
|
||||
err = ErrEmptyChain
|
||||
return
|
||||
}
|
||||
nodes := route.Nodes()
|
||||
nodes := c.Nodes()
|
||||
node := nodes[0]
|
||||
|
||||
addr, err := selectIP(&node)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cn, err := node.Client.Dial(addr, node.DialOptions...)
|
||||
cn, err := node.Client.Dial(node.Addr, node.DialOptions...)
|
||||
if err != nil {
|
||||
node.MarkDead()
|
||||
return
|
||||
}
|
||||
|
||||
cn, err = node.Client.Handshake(cn, node.HandshakeOptions...)
|
||||
if err != nil {
|
||||
node.MarkDead()
|
||||
return
|
||||
}
|
||||
node.ResetDead()
|
||||
|
||||
preNode := node
|
||||
for _, node := range nodes[1:] {
|
||||
addr, err = selectIP(&node)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var cc net.Conn
|
||||
cc, err = preNode.Client.Connect(cn, addr)
|
||||
cc, err = preNode.Client.Connect(cn, node.Addr)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
node.MarkDead()
|
||||
return
|
||||
}
|
||||
cc, err = node.Client.Handshake(cc, node.HandshakeOptions...)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
node.MarkDead()
|
||||
return
|
||||
}
|
||||
node.ResetDead()
|
||||
|
||||
cn = cc
|
||||
preNode = node
|
||||
}
|
||||
@ -195,29 +197,34 @@ func (c *Chain) getConn(route *Chain) (conn net.Conn, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func selectIP(node *Node) (string, error) {
|
||||
addr := node.Addr
|
||||
s := node.IPSelector
|
||||
if s == nil {
|
||||
s = &RandomIPSelector{}
|
||||
func (c *Chain) selectRoute() (route *Chain, err error) {
|
||||
if c.isRoute {
|
||||
return c, nil
|
||||
}
|
||||
// select IP from IP list
|
||||
ip, err := s.Select(node.IPs)
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
route = newRoute()
|
||||
route.Retries = c.Retries
|
||||
|
||||
for _, group := range c.nodeGroups {
|
||||
node, err := group.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if ip != "" {
|
||||
if !strings.Contains(ip, ":") {
|
||||
_, sport, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
buf.WriteString(fmt.Sprintf("%s -> ", node.String()))
|
||||
|
||||
if node.Client.Transporter.Multiplex() {
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
ChainDialOption(route),
|
||||
)
|
||||
route = newRoute() // cutoff the chain for multiplex.
|
||||
route.Retries = c.Retries
|
||||
}
|
||||
ip = ip + ":" + sport
|
||||
|
||||
route.AddNode(node)
|
||||
}
|
||||
addr = ip
|
||||
// override the original address
|
||||
node.HandshakeOptions = append(node.HandshakeOptions, AddrHandshakeOption(addr))
|
||||
if Debug {
|
||||
log.Log("select route:", buf.String())
|
||||
}
|
||||
log.Log("select IP:", node.Addr, node.IPs, addr)
|
||||
return addr, nil
|
||||
return
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ func ChainDialOption(chain *Chain) DialOption {
|
||||
// HandshakeOptions describes the options for handshake.
|
||||
type HandshakeOptions struct {
|
||||
Addr string
|
||||
Host string
|
||||
User *url.Userinfo
|
||||
Timeout time.Duration
|
||||
Interval time.Duration
|
||||
@ -136,6 +137,13 @@ func AddrHandshakeOption(addr string) HandshakeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// HostHandshakeOption specifies the hostname
|
||||
func HostHandshakeOption(host string) HandshakeOption {
|
||||
return func(opts *HandshakeOptions) {
|
||||
opts.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
// UserHandshakeOption specifies the user used by Transporter.Handshake
|
||||
func UserHandshakeOption(user *url.Userinfo) HandshakeOption {
|
||||
return func(opts *HandshakeOptions) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
{
|
||||
"Debug": false,
|
||||
"Retries": 1,
|
||||
"ServeNodes": [
|
||||
":8080",
|
||||
"ss://chacha20:12345678@:8338"
|
||||
@ -6,5 +8,23 @@
|
||||
"ChainNodes": [
|
||||
"http://192.168.1.1:8080",
|
||||
"https://10.0.2.1:443"
|
||||
],
|
||||
|
||||
"Routes": [
|
||||
{
|
||||
"Retries": 1,
|
||||
"ServeNodes": [
|
||||
"ws://:1443"
|
||||
],
|
||||
"ChainNodes": [
|
||||
"socks://:192.168.1.1:1080"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Retries": 3,
|
||||
"ServeNodes": [
|
||||
"quic://:443"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
172
cmd/gost/main.go
172
cmd/gost/main.go
@ -22,10 +22,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
options struct {
|
||||
ChainNodes, ServeNodes stringList
|
||||
Debug bool
|
||||
}
|
||||
options route
|
||||
routes []route
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -43,12 +41,17 @@ func init() {
|
||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||
flag.Parse()
|
||||
|
||||
if len(options.ServeNodes) > 0 {
|
||||
routes = append(routes, options)
|
||||
}
|
||||
gost.Debug = options.Debug
|
||||
|
||||
if err := loadConfigureFile(configureFile); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if flag.NFlag() == 0 {
|
||||
if flag.NFlag() == 0 || len(routes) == 0 {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
@ -57,8 +60,6 @@ func init() {
|
||||
fmt.Fprintf(os.Stderr, "gost %s (%s)\n", gost.Version, runtime.Version())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
gost.Debug = options.Debug
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -72,45 +73,73 @@ func main() {
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
|
||||
chain, err := initChain()
|
||||
if err != nil {
|
||||
for _, route := range routes {
|
||||
if err := route.serve(); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := serve(chain); err != nil {
|
||||
log.Log(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func initChain() (*gost.Chain, error) {
|
||||
type route struct {
|
||||
ChainNodes, ServeNodes stringList
|
||||
Retries int
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func (r *route) initChain() (*gost.Chain, error) {
|
||||
chain := gost.NewChain()
|
||||
for _, ns := range options.ChainNodes {
|
||||
chain.Retries = r.Retries
|
||||
|
||||
gid := 1 // group ID
|
||||
|
||||
for _, ns := range r.ChainNodes {
|
||||
ngroup := gost.NewNodeGroup()
|
||||
ngroup.ID = gid
|
||||
gid++
|
||||
|
||||
// parse the base node
|
||||
node, err := parseChainNode(ns)
|
||||
nodes, err := parseChainNode(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ngroup := gost.NewNodeGroup(node)
|
||||
nid := 1 // node ID
|
||||
|
||||
// parse node peers if exists
|
||||
peerCfg, err := loadPeerConfig(node.Values.Get("peer"))
|
||||
for i := range nodes {
|
||||
nodes[i].ID = nid
|
||||
nid++
|
||||
}
|
||||
ngroup.AddNode(nodes...)
|
||||
|
||||
// parse peer nodes if exists
|
||||
peerCfg, err := loadPeerConfig(nodes[0].Values.Get("peer"))
|
||||
if err != nil {
|
||||
log.Log(err)
|
||||
}
|
||||
peerCfg.Validate()
|
||||
ngroup.Options = append(ngroup.Options,
|
||||
// gost.WithFilter(),
|
||||
gost.WithFilter(&gost.FailFilter{
|
||||
MaxFails: peerCfg.MaxFails,
|
||||
FailTimeout: time.Duration(peerCfg.FailTimeout) * time.Second,
|
||||
}),
|
||||
gost.WithStrategy(parseStrategy(peerCfg.Strategy)),
|
||||
)
|
||||
|
||||
for _, s := range peerCfg.Nodes {
|
||||
node, err = parseChainNode(s)
|
||||
nodes, err = parseChainNode(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ngroup.AddNode(node)
|
||||
|
||||
for i := range nodes {
|
||||
nodes[i].ID = nid
|
||||
nid++
|
||||
}
|
||||
|
||||
ngroup.AddNode(nodes...)
|
||||
}
|
||||
|
||||
chain.AddNodeGroup(ngroup)
|
||||
@ -119,15 +148,12 @@ func initChain() (*gost.Chain, error) {
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func parseChainNode(ns string) (node gost.Node, err error) {
|
||||
node, err = gost.ParseNode(ns)
|
||||
func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
node.IPs = parseIP(node.Values.Get("ip"))
|
||||
node.IPSelector = &gost.RoundRobinIPSelector{}
|
||||
|
||||
users, err := parseUsers(node.Values.Get("secrets"))
|
||||
if err != nil {
|
||||
return
|
||||
@ -135,7 +161,7 @@ func parseChainNode(ns string) (node gost.Node, err error) {
|
||||
if node.User == nil && len(users) > 0 {
|
||||
node.User = users[0]
|
||||
}
|
||||
serverName, _, _ := net.SplitHostPort(node.Addr)
|
||||
serverName, sport, _ := net.SplitHostPort(node.Addr)
|
||||
if serverName == "" {
|
||||
serverName = "localhost" // default server name
|
||||
}
|
||||
@ -177,7 +203,7 @@ func parseChainNode(ns string) (node gost.Node, err error) {
|
||||
*/
|
||||
config, err := parseKCPConfig(node.Values.Get("c"))
|
||||
if err != nil {
|
||||
return node, err
|
||||
return nil, err
|
||||
}
|
||||
tr = gost.KCPTransporter(config)
|
||||
case "ssh":
|
||||
@ -206,7 +232,7 @@ func parseChainNode(ns string) (node gost.Node, err error) {
|
||||
|
||||
case "obfs4":
|
||||
if err := gost.Obfs4Init(node, false); err != nil {
|
||||
return node, err
|
||||
return nil, err
|
||||
}
|
||||
tr = gost.Obfs4Transporter()
|
||||
case "ohttp":
|
||||
@ -249,24 +275,41 @@ func parseChainNode(ns string) (node gost.Node, err error) {
|
||||
|
||||
interval, _ := strconv.Atoi(node.Values.Get("ping"))
|
||||
retry, _ := strconv.Atoi(node.Values.Get("retry"))
|
||||
node.HandshakeOptions = append(node.HandshakeOptions,
|
||||
handshakeOptions := []gost.HandshakeOption{
|
||||
gost.AddrHandshakeOption(node.Addr),
|
||||
gost.HostHandshakeOption(node.Host),
|
||||
gost.UserHandshakeOption(node.User),
|
||||
gost.TLSConfigHandshakeOption(tlsCfg),
|
||||
gost.IntervalHandshakeOption(time.Duration(interval)*time.Second),
|
||||
gost.TimeoutHandshakeOption(time.Duration(timeout)*time.Second),
|
||||
gost.IntervalHandshakeOption(time.Duration(interval) * time.Second),
|
||||
gost.TimeoutHandshakeOption(time.Duration(timeout) * time.Second),
|
||||
gost.RetryHandshakeOption(retry),
|
||||
)
|
||||
}
|
||||
node.Client = &gost.Client{
|
||||
Connector: connector,
|
||||
Transporter: tr,
|
||||
}
|
||||
|
||||
ips := parseIP(node.Values.Get("ip"), sport)
|
||||
for _, ip := range ips {
|
||||
node.Addr = ip
|
||||
node.HandshakeOptions = append(handshakeOptions, gost.AddrHandshakeOption(ip))
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
node.HandshakeOptions = handshakeOptions
|
||||
nodes = []gost.Node{node}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func serve(chain *gost.Chain) error {
|
||||
for _, ns := range options.ServeNodes {
|
||||
func (r *route) serve() error {
|
||||
chain, err := r.initChain()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ns := range r.ServeNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -475,9 +518,24 @@ func loadConfigureFile(configureFile string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal(content, &options); err != nil {
|
||||
var cfg struct {
|
||||
route
|
||||
Routes []route
|
||||
}
|
||||
if err := json.Unmarshal(content, &cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cfg.route.ServeNodes) > 0 {
|
||||
routes = append(routes, cfg.route)
|
||||
}
|
||||
for _, route := range cfg.Routes {
|
||||
if len(cfg.route.ServeNodes) > 0 {
|
||||
routes = append(routes, route)
|
||||
}
|
||||
}
|
||||
gost.Debug = cfg.Debug
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -544,16 +602,23 @@ func parseUsers(authFile string) (users []*url.Userinfo, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseIP(s string) (ips []string) {
|
||||
func parseIP(s string, port string) (ips []string) {
|
||||
if s == "" {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
if port == "" {
|
||||
port = "8080" // default port
|
||||
}
|
||||
|
||||
file, err := os.Open(s)
|
||||
if err != nil {
|
||||
ss := strings.Split(s, ",")
|
||||
for _, s := range ss {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
if !strings.Contains(s, ":") {
|
||||
s = s + ":" + port
|
||||
}
|
||||
ips = append(ips, s)
|
||||
}
|
||||
|
||||
@ -567,6 +632,9 @@ func parseIP(s string) (ips []string) {
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, ":") {
|
||||
line = line + ":" + port
|
||||
}
|
||||
ips = append(ips, line)
|
||||
}
|
||||
return
|
||||
@ -575,6 +643,8 @@ func parseIP(s string) (ips []string) {
|
||||
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"`
|
||||
}
|
||||
|
||||
@ -590,13 +660,23 @@ func loadPeerConfig(peer string) (config peerConfig, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseStrategy(s string) gost.Strategy {
|
||||
switch s {
|
||||
case "round":
|
||||
return &gost.RoundStrategy{}
|
||||
case "random":
|
||||
fallthrough
|
||||
default:
|
||||
return &gost.RandomStrategy{}
|
||||
func (cfg *peerConfig) Validate() {
|
||||
if cfg.MaxFails <= 0 {
|
||||
cfg.MaxFails = 3
|
||||
}
|
||||
if cfg.FailTimeout <= 0 {
|
||||
cfg.FailTimeout = 30 // seconds
|
||||
}
|
||||
}
|
||||
|
||||
func parseStrategy(s string) gost.Strategy {
|
||||
switch s {
|
||||
case "random":
|
||||
return &gost.RandomStrategy{}
|
||||
case "round":
|
||||
fallthrough
|
||||
default:
|
||||
return &gost.RoundStrategy{}
|
||||
|
||||
}
|
||||
}
|
||||
|
10
cmd/gost/peer.json
Normal file
10
cmd/gost/peer.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"strategy": "round",
|
||||
"max_fails": 3,
|
||||
"fail_timeout": 30,
|
||||
"nodes":[
|
||||
"socks5://:1081",
|
||||
"socks://:1082",
|
||||
"socks4a://:1083"
|
||||
]
|
||||
}
|
1
gost.go
1
gost.go
@ -56,6 +56,7 @@ func SetLogger(logger log.Logger) {
|
||||
log.DefaultLogger = logger
|
||||
}
|
||||
|
||||
// GenCertificate generates a random TLS certificate
|
||||
func GenCertificate() (cert tls.Certificate, err error) {
|
||||
rawCert, rawKey, err := generateKeyPair()
|
||||
if err != nil {
|
||||
|
@ -95,7 +95,8 @@ func (h *autoHandler) Handle(conn net.Conn) {
|
||||
cc := &bufferdConn{Conn: conn, br: br}
|
||||
switch b[0] {
|
||||
case gosocks4.Ver4:
|
||||
return // SOCKS4(a) does not suppport authentication method, so we ignore it.
|
||||
cc.Close()
|
||||
return // SOCKS4(a) does not suppport authentication method, so we ignore it for security reason.
|
||||
case gosocks5.Ver5:
|
||||
SOCKS5Handler(h.options...).Handle(cc)
|
||||
default: // http
|
||||
|
7
http.go
7
http.go
@ -93,6 +93,13 @@ func (h *httpHandler) Handle(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
h.handleRequest(conn, req)
|
||||
}
|
||||
|
||||
func (h *httpHandler) handleRequest(conn net.Conn, req *http.Request) {
|
||||
if req == nil {
|
||||
return
|
||||
}
|
||||
if Debug {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Logf("[http] %s -> %s\n%s", conn.RemoteAddr(), req.Host, string(dump))
|
||||
|
100
node.go
100
node.go
@ -1,14 +1,18 @@
|
||||
package gost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Node is a proxy node, mainly used to construct a proxy chain.
|
||||
type Node struct {
|
||||
ID int
|
||||
Addr string
|
||||
IPs []string
|
||||
Host string
|
||||
Protocol string
|
||||
Transport string
|
||||
Remote string // remote address, used by tcp/udp port forwarding
|
||||
@ -17,7 +21,9 @@ type Node struct {
|
||||
DialOptions []DialOption
|
||||
HandshakeOptions []HandshakeOption
|
||||
Client *Client
|
||||
IPSelector IPSelector
|
||||
group *NodeGroup
|
||||
failCount uint32
|
||||
failTime int64
|
||||
}
|
||||
|
||||
// ParseNode parses the node info.
|
||||
@ -38,6 +44,7 @@ func ParseNode(s string) (node Node, err error) {
|
||||
|
||||
node = Node{
|
||||
Addr: u.Host,
|
||||
Host: u.Host,
|
||||
Remote: strings.Trim(u.EscapedPath(), "/"),
|
||||
Values: u.Query(),
|
||||
User: u.User,
|
||||
@ -79,8 +86,68 @@ func ParseNode(s string) (node Node, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// MarkDead marks the node fail status.
|
||||
func (node *Node) MarkDead() {
|
||||
atomic.AddUint32(&node.failCount, 1)
|
||||
atomic.StoreInt64(&node.failTime, time.Now().Unix())
|
||||
|
||||
if node.group == nil {
|
||||
return
|
||||
}
|
||||
for i := range node.group.nodes {
|
||||
if node.group.nodes[i].ID == node.ID {
|
||||
atomic.AddUint32(&node.group.nodes[i].failCount, 1)
|
||||
atomic.StoreInt64(&node.group.nodes[i].failTime, time.Now().Unix())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResetDead resets the node fail status.
|
||||
func (node *Node) ResetDead() {
|
||||
atomic.StoreUint32(&node.failCount, 0)
|
||||
atomic.StoreInt64(&node.failTime, 0)
|
||||
|
||||
if node.group == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range node.group.nodes {
|
||||
if node.group.nodes[i].ID == node.ID {
|
||||
atomic.StoreUint32(&node.group.nodes[i].failCount, 0)
|
||||
atomic.StoreInt64(&node.group.nodes[i].failTime, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
func (node *Node) String() string {
|
||||
return fmt.Sprintf("%d@%s", node.ID, node.Addr)
|
||||
}
|
||||
|
||||
// NodeGroup is a group of nodes.
|
||||
type NodeGroup struct {
|
||||
ID int
|
||||
nodes []Node
|
||||
Options []SelectOption
|
||||
Selector NodeSelector
|
||||
@ -94,17 +161,34 @@ func NewNodeGroup(nodes ...Node) *NodeGroup {
|
||||
}
|
||||
|
||||
// AddNode adds node or node list into group
|
||||
func (ng *NodeGroup) AddNode(node ...Node) {
|
||||
if ng == nil {
|
||||
func (group *NodeGroup) AddNode(node ...Node) {
|
||||
if group == nil {
|
||||
return
|
||||
}
|
||||
ng.nodes = append(ng.nodes, node...)
|
||||
group.nodes = append(group.nodes, node...)
|
||||
}
|
||||
|
||||
// Nodes returns node list in the group
|
||||
func (ng *NodeGroup) Nodes() []Node {
|
||||
if ng == nil {
|
||||
func (group *NodeGroup) Nodes() []Node {
|
||||
if group == nil {
|
||||
return nil
|
||||
}
|
||||
return ng.nodes
|
||||
return group.nodes
|
||||
}
|
||||
|
||||
// Next selects the next node from group.
|
||||
// It also selects IP if the IP list exists.
|
||||
func (group *NodeGroup) Next() (node Node, err error) {
|
||||
selector := group.Selector
|
||||
if selector == nil {
|
||||
selector = &defaultSelector{}
|
||||
}
|
||||
// select node from node group
|
||||
node, err = selector.Select(group.Nodes(), group.Options...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
node.group = group
|
||||
|
||||
return
|
||||
}
|
||||
|
5
obfs.go
5
obfs.go
@ -35,7 +35,7 @@ func (tr *obfsHTTPTransporter) Handshake(conn net.Conn, options ...HandshakeOpti
|
||||
for _, option := range options {
|
||||
option(opts)
|
||||
}
|
||||
return &obfsHTTPConn{Conn: conn}, nil
|
||||
return &obfsHTTPConn{Conn: conn, host: opts.Host}, nil
|
||||
}
|
||||
|
||||
type obfsHTTPListener struct {
|
||||
@ -66,6 +66,7 @@ func (l *obfsHTTPListener) Accept() (net.Conn, error) {
|
||||
|
||||
type obfsHTTPConn struct {
|
||||
net.Conn
|
||||
host string
|
||||
request *http.Request
|
||||
response *http.Response
|
||||
rbuf []byte
|
||||
@ -151,7 +152,7 @@ func (c *obfsHTTPConn) clientHandshake() (err error) {
|
||||
Method: http.MethodGet,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
URL: &url.URL{Scheme: "http", Host: "www.baidu.com"},
|
||||
URL: &url.URL{Scheme: "http", Host: c.host},
|
||||
Header: make(http.Header),
|
||||
}
|
||||
r.Header.Set("Connection", "keep-alive")
|
||||
|
147
selector.go
147
selector.go
@ -2,6 +2,8 @@ package gost
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
@ -37,52 +39,6 @@ func (s *defaultSelector) Select(nodes []Node, opts ...SelectOption) (Node, erro
|
||||
return sopts.Strategy.Apply(nodes), nil
|
||||
}
|
||||
|
||||
// Filter is used to filter a node during the selection process
|
||||
type Filter interface {
|
||||
Filter([]Node) []Node
|
||||
}
|
||||
|
||||
// Strategy is a selection strategy e.g random, round robin
|
||||
type Strategy interface {
|
||||
Apply([]Node) Node
|
||||
String() string
|
||||
}
|
||||
|
||||
// RoundStrategy is a strategy for node selector
|
||||
type RoundStrategy struct {
|
||||
count uint64
|
||||
}
|
||||
|
||||
// Apply applies the round robin strategy for the nodes
|
||||
func (s *RoundStrategy) Apply(nodes []Node) Node {
|
||||
if len(nodes) == 0 {
|
||||
return Node{}
|
||||
}
|
||||
old := s.count
|
||||
atomic.AddUint64(&s.count, 1)
|
||||
return nodes[int(old%uint64(len(nodes)))]
|
||||
}
|
||||
|
||||
func (s *RoundStrategy) String() string {
|
||||
return "round"
|
||||
}
|
||||
|
||||
// RandomStrategy is a strategy for node selector
|
||||
type RandomStrategy struct{}
|
||||
|
||||
// Apply applies the random strategy for the nodes
|
||||
func (s *RandomStrategy) Apply(nodes []Node) Node {
|
||||
if len(nodes) == 0 {
|
||||
return Node{}
|
||||
}
|
||||
|
||||
return nodes[time.Now().Nanosecond()%len(nodes)]
|
||||
}
|
||||
|
||||
func (s *RandomStrategy) String() string {
|
||||
return "random"
|
||||
}
|
||||
|
||||
// SelectOption used when making a select call
|
||||
type SelectOption func(*SelectOptions)
|
||||
|
||||
@ -107,43 +63,86 @@ func WithStrategy(s Strategy) SelectOption {
|
||||
}
|
||||
}
|
||||
|
||||
// IPSelector as a mechanism to pick IPs and mark their status.
|
||||
type IPSelector interface {
|
||||
Select(ips []string) (string, error)
|
||||
// Strategy is a selection strategy e.g random, round robin
|
||||
type Strategy interface {
|
||||
Apply([]Node) Node
|
||||
String() string
|
||||
}
|
||||
|
||||
// RandomIPSelector is an IP Selector that selects an IP with random strategy.
|
||||
type RandomIPSelector struct {
|
||||
}
|
||||
|
||||
// Select selects an IP from ips list.
|
||||
func (s *RandomIPSelector) Select(ips []string) (string, error) {
|
||||
if len(ips) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return ips[time.Now().Nanosecond()%len(ips)], nil
|
||||
}
|
||||
|
||||
func (s *RandomIPSelector) String() string {
|
||||
return "random"
|
||||
}
|
||||
|
||||
// RoundRobinIPSelector is an IP Selector that selects an IP with round-robin strategy.
|
||||
type RoundRobinIPSelector struct {
|
||||
// RoundStrategy is a strategy for node selector
|
||||
type RoundStrategy struct {
|
||||
count uint64
|
||||
}
|
||||
|
||||
// Select selects an IP from ips list.
|
||||
func (s *RoundRobinIPSelector) Select(ips []string) (string, error) {
|
||||
if len(ips) == 0 {
|
||||
return "", nil
|
||||
// Apply applies the round robin strategy for the nodes
|
||||
func (s *RoundStrategy) Apply(nodes []Node) Node {
|
||||
if len(nodes) == 0 {
|
||||
return Node{}
|
||||
}
|
||||
old := s.count
|
||||
old := atomic.LoadUint64(&s.count)
|
||||
atomic.AddUint64(&s.count, 1)
|
||||
return ips[int(old%uint64(len(ips)))], nil
|
||||
return nodes[int(old%uint64(len(nodes)))]
|
||||
}
|
||||
|
||||
func (s *RoundRobinIPSelector) String() string {
|
||||
func (s *RoundStrategy) String() string {
|
||||
return "round"
|
||||
}
|
||||
|
||||
// RandomStrategy is a strategy for node selector
|
||||
type RandomStrategy struct {
|
||||
Seed int64
|
||||
rand *rand.Rand
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Apply applies the random strategy for the nodes
|
||||
func (s *RandomStrategy) Apply(nodes []Node) Node {
|
||||
s.once.Do(func() {
|
||||
seed := s.Seed
|
||||
if seed == 0 {
|
||||
seed = time.Now().UnixNano()
|
||||
}
|
||||
s.rand = rand.New(rand.NewSource(seed))
|
||||
})
|
||||
if len(nodes) == 0 {
|
||||
return Node{}
|
||||
}
|
||||
|
||||
return nodes[s.rand.Int()%len(nodes)]
|
||||
}
|
||||
|
||||
func (s *RandomStrategy) String() string {
|
||||
return "random"
|
||||
}
|
||||
|
||||
// Filter is used to filter a node during the selection process
|
||||
type Filter interface {
|
||||
Filter([]Node) []Node
|
||||
String() string
|
||||
}
|
||||
|
||||
// FailFilter filters the dead node.
|
||||
// A node is marked as dead if its failed count is greater than MaxFails.
|
||||
type FailFilter struct {
|
||||
MaxFails int
|
||||
FailTimeout time.Duration
|
||||
}
|
||||
|
||||
// Filter filters nodes.
|
||||
func (f *FailFilter) Filter(nodes []Node) []Node {
|
||||
if f.MaxFails <= 0 {
|
||||
return nodes
|
||||
}
|
||||
nl := []Node{}
|
||||
for i := range nodes {
|
||||
if atomic.LoadUint32(&nodes[i].failCount) < uint32(f.MaxFails) ||
|
||||
time.Since(time.Unix(atomic.LoadInt64(&nodes[i].failTime), 0)) >= f.FailTimeout {
|
||||
nl = append(nl, nodes[i].Clone())
|
||||
}
|
||||
}
|
||||
return nl
|
||||
}
|
||||
|
||||
func (f *FailFilter) String() string {
|
||||
return "fail"
|
||||
}
|
||||
|
16
sni.go
16
sni.go
@ -11,6 +11,7 @@ import (
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -53,14 +54,21 @@ func (h *sniHandler) Handle(conn net.Conn) {
|
||||
return
|
||||
}
|
||||
conn = &bufferdConn{br: br, Conn: conn}
|
||||
defer conn.Close()
|
||||
|
||||
if hdr[0] != dissector.Handshake {
|
||||
// We assume that it is HTTP request
|
||||
HTTPHandler(h.options...).Handle(conn)
|
||||
// We assume it is an HTTP request
|
||||
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if !req.URL.IsAbs() {
|
||||
req.URL.Scheme = "http" // make sure that the URL is absolute
|
||||
}
|
||||
if err != nil {
|
||||
log.Logf("[sni] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
HTTPHandler(h.options...).(*httpHandler).handleRequest(conn, req)
|
||||
return
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
b, host, err := readClientHelloRecord(conn, "", false)
|
||||
if err != nil {
|
||||
|
19
ss.go
19
ss.go
@ -124,11 +124,15 @@ func (h *shadowHandler) Handle(conn net.Conn) {
|
||||
|
||||
log.Logf("[ss] %s - %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
|
||||
addr, err := h.getRequest(conn)
|
||||
if err != nil {
|
||||
log.Logf("[ss] %s - %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||
return
|
||||
}
|
||||
// clear timer
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
log.Logf("[ss] %s -> %s", conn.RemoteAddr(), addr)
|
||||
|
||||
if !Can("tcp", addr, h.options.Whitelist, h.options.Blacklist) {
|
||||
@ -165,19 +169,16 @@ const (
|
||||
)
|
||||
|
||||
// This function is copied from shadowsocks library with some modification.
|
||||
func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
func (h *shadowHandler) getRequest(r io.Reader) (host string, err error) {
|
||||
// buf size should at least have the same size with the largest possible
|
||||
// request size (when addrType is 3, domain name has at most 256 bytes)
|
||||
// 1(addrType) + 1(lenByte) + 256(max length address) + 2(port)
|
||||
buf := make([]byte, smallBufferSize)
|
||||
|
||||
// read till we get possible domain length field
|
||||
conn.SetReadDeadline(time.Now().Add(ReadTimeout))
|
||||
if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil {
|
||||
if _, err = io.ReadFull(r, buf[:idType+1]); err != nil {
|
||||
return
|
||||
}
|
||||
// clear timer
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
var reqStart, reqEnd int
|
||||
addrType := buf[idType]
|
||||
@ -187,16 +188,16 @@ func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
case typeIPv6:
|
||||
reqStart, reqEnd = idIP0, idIP0+lenIPv6
|
||||
case typeDm:
|
||||
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil {
|
||||
if _, err = io.ReadFull(r, buf[idType+1:idDmLen+1]); err != nil {
|
||||
return
|
||||
}
|
||||
reqStart, reqEnd = idDm0, int(idDm0+buf[idDmLen]+lenDmBase)
|
||||
reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase
|
||||
default:
|
||||
err = fmt.Errorf("addr type %d not supported", addrType&ss.AddrMask)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
|
||||
if _, err = io.ReadFull(r, buf[reqStart:reqEnd]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -209,7 +210,7 @@ func (h *shadowHandler) getRequest(conn net.Conn) (host string, err error) {
|
||||
case typeIPv6:
|
||||
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
|
||||
case typeDm:
|
||||
host = string(buf[idDm0 : idDm0+buf[idDmLen]])
|
||||
host = string(buf[idDm0 : idDm0+int(buf[idDmLen])])
|
||||
}
|
||||
// parse port
|
||||
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
|
||||
|
6
ssh.go
6
ssh.go
@ -830,13 +830,13 @@ func (c *sshConn) RemoteAddr() net.Addr {
|
||||
}
|
||||
|
||||
func (c *sshConn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sshConn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sshConn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "ssh", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
return nil
|
||||
}
|
||||
|
8
ws.go
8
ws.go
@ -129,7 +129,7 @@ func (tr *wsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (n
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Addr, Path: "/ws"}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Host, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, nil, wsOptions)
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func (tr *mwsTransporter) initSession(addr string, conn net.Conn, opts *Handshak
|
||||
if opts.WSOptions != nil {
|
||||
wsOptions = opts.WSOptions
|
||||
}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Addr, Path: "/ws"}
|
||||
url := url.URL{Scheme: "ws", Host: opts.Host, Path: "/ws"}
|
||||
conn, err := websocketClientConn(url.String(), conn, nil, wsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -252,7 +252,7 @@ func (tr *wssTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (
|
||||
if opts.TLSConfig == nil {
|
||||
opts.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Addr, Path: "/ws"}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Host, Path: "/ws"}
|
||||
return websocketClientConn(url.String(), conn, opts.TLSConfig, wsOptions)
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ func (tr *mwssTransporter) initSession(addr string, conn net.Conn, opts *Handsha
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Addr, Path: "/ws"}
|
||||
url := url.URL{Scheme: "wss", Host: opts.Host, Path: "/ws"}
|
||||
conn, err := websocketClientConn(url.String(), conn, tlsConfig, wsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
Loading…
Reference in New Issue
Block a user