redesign client
This commit is contained in:
parent
962fd9df51
commit
289acfd058
@ -9,6 +9,12 @@ type Chain struct {
|
|||||||
Nodes []Node
|
Nodes []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewChain(nodes ...Node) *Chain {
|
||||||
|
return &Chain{
|
||||||
|
Nodes: nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Chain) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
func (c *Chain) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
if len(c.Nodes) == 0 {
|
if len(c.Nodes) == 0 {
|
||||||
return net.Dial("tcp", addr)
|
return net.Dial("tcp", addr)
|
||||||
@ -20,23 +26,35 @@ func (c *Chain) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn, err = nodes[0].Client.Handshake(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for i, node := range nodes {
|
for i, node := range nodes {
|
||||||
if i == len(nodes)-1 {
|
if i == len(nodes)-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, err := node.Client.Connect(ctx, conn, nodes[i+1].Addr)
|
next := nodes[i+1]
|
||||||
|
cc, err := node.Client.Connect(ctx, conn, next.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = cn
|
cc, err = next.Client.Handshake(ctx, cc)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn = cc
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, err := nodes[len(nodes)-1].Client.Connect(ctx, conn, addr)
|
cc, err := nodes[len(nodes)-1].Client.Connect(ctx, conn, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cn, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
66
gost/cli/cli.go
Normal file
66
gost/cli/cli.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/ginuerzh/gost/gost"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
gost.Debug = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
chain := gost.NewChain(
|
||||||
|
gost.Node{
|
||||||
|
Addr: "127.0.0.1:1080",
|
||||||
|
Client: gost.NewClient(
|
||||||
|
gost.HTTPConnector(url.UserPassword("admin", "123456")),
|
||||||
|
gost.TCPTransporter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
gost.Node{
|
||||||
|
Addr: "172.24.222.54:8338",
|
||||||
|
Client: gost.NewClient(
|
||||||
|
gost.ShadowConnector(url.UserPassword("chacha20", "123456")),
|
||||||
|
gost.TCPTransporter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
gost.Node{
|
||||||
|
Addr: "172.24.222.54:8080",
|
||||||
|
Client: gost.NewClient(
|
||||||
|
gost.SOCKS5Connector(url.UserPassword("cmdsh", "cmdsh123456")),
|
||||||
|
gost.TCPTransporter(),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
conn, err := chain.Dial(context.Background(), "baidu.com:443")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
|
||||||
|
req, err := http.NewRequest(http.MethodGet, "https://www.baidu.com", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
rb, _ := httputil.DumpRequest(req, true)
|
||||||
|
log.Println(string(rb))
|
||||||
|
rb, _ = httputil.DumpResponse(resp, true)
|
||||||
|
log.Println(string(rb))
|
||||||
|
}
|
181
gost/client.go
181
gost/client.go
@ -1,167 +1,58 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks4"
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
|
||||||
"github.com/go-log/log"
|
|
||||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Protocol string
|
Connector Connector
|
||||||
Transport *Transport
|
Transporter Transporter
|
||||||
User *url.Userinfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultClient is a standard HTTP proxy
|
||||||
|
var DefaultClient = NewClient(HTTPConnector(nil), TCPTransporter())
|
||||||
|
|
||||||
|
func NewClient(c Connector, tr Transporter) *Client {
|
||||||
|
return &Client{
|
||||||
|
Connector: c,
|
||||||
|
Transporter: tr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the target address
|
||||||
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
return c.Transport.Dial(ctx, addr)
|
return net.Dial(c.Transporter.Network(), addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||||
|
return c.Transporter.Handshake(ctx, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
func (c *Client) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
protocol := c.Protocol
|
return c.Connector.Connect(ctx, conn, addr)
|
||||||
|
}
|
||||||
|
|
||||||
switch protocol {
|
type Connector interface {
|
||||||
case "ss": // shadowsocks
|
Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error)
|
||||||
rawaddr, err := ss.RawAddr(addr)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var method, password string
|
type Transporter interface {
|
||||||
if c.User != nil {
|
Network() string
|
||||||
method = c.User.Username()
|
Handshake(ctx context.Context, conn net.Conn) (net.Conn, error)
|
||||||
password, _ = c.User.Password()
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cipher, err := ss.NewCipher(method, password)
|
type tcpTransporter struct {
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher)
|
func TCPTransporter() Transporter {
|
||||||
if err != nil {
|
return &tcpTransporter{}
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
conn = ShadowConn(sc)
|
|
||||||
|
|
||||||
case "socks", "socks5":
|
func (tr *tcpTransporter) Network() string {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
return "tcp"
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
|
|
||||||
Type: gosocks5.AddrDomain,
|
|
||||||
Host: host,
|
|
||||||
Port: uint16(p),
|
|
||||||
})
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Log("[socks5]", req)
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Log("[socks5]", reply)
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
return nil, errors.New("Service unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "socks4", "socks4a":
|
|
||||||
atype := gosocks4.AddrDomain
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
|
|
||||||
if protocol == "socks4" {
|
|
||||||
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
host = taddr.IP.String()
|
|
||||||
p = taddr.Port
|
|
||||||
atype = gosocks4.AddrIPv4
|
|
||||||
}
|
|
||||||
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
|
||||||
&gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Logf("[%s] %s", protocol, req)
|
|
||||||
|
|
||||||
reply, err := gosocks4.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Logf("[%s] %s", protocol, reply)
|
|
||||||
|
|
||||||
if reply.Code != gosocks4.Granted {
|
|
||||||
return nil, fmt.Errorf("%s: code=%d", protocol, reply.Code)
|
|
||||||
}
|
|
||||||
case "http":
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
URL: &url.URL{Host: addr},
|
|
||||||
Host: addr,
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Header: make(http.Header),
|
|
||||||
}
|
|
||||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
|
||||||
if c.User != nil {
|
|
||||||
s := c.User.String()
|
|
||||||
if _, set := c.User.Password(); !set {
|
|
||||||
s += ":"
|
|
||||||
}
|
|
||||||
req.Header.Set("Proxy-Authorization",
|
|
||||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
|
||||||
}
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
dump, _ := httputil.DumpRequest(req, false)
|
|
||||||
log.Log(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Debug {
|
|
||||||
dump, _ := httputil.DumpResponse(resp, false)
|
|
||||||
log.Log(string(dump))
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (tr *tcpTransporter) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transport struct {
|
|
||||||
Dial func(ctx context.Context, addr string) (net.Conn, error)
|
|
||||||
TLSClientConfig *tls.Config
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-log/log"
|
||||||
|
)
|
||||||
|
|
||||||
var Debug bool
|
var Debug bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.DefaultLogger = &logger{}
|
||||||
|
}
|
||||||
|
68
gost/http.go
Normal file
68
gost/http.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/go-log/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpConnector struct {
|
||||||
|
User *url.Userinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPConnector(user *url.Userinfo) Connector {
|
||||||
|
return &httpConnector{User: user}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodConnect,
|
||||||
|
URL: &url.URL{Host: addr},
|
||||||
|
Host: addr,
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
req.Header.Set("Proxy-Connection", "keep-alive")
|
||||||
|
|
||||||
|
if c.User != nil {
|
||||||
|
s := c.User.String()
|
||||||
|
if _, set := c.User.Password(); !set {
|
||||||
|
s += ":"
|
||||||
|
}
|
||||||
|
req.Header.Set("Proxy-Authorization",
|
||||||
|
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpRequest(req, false)
|
||||||
|
log.Log(string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Log(string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s", resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
18
gost/log.go
Normal file
18
gost/log.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Log(v ...interface{}) {
|
||||||
|
log.Println(v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logger) Logf(format string, v ...interface{}) {
|
||||||
|
log.Printf(format, v...)
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Addr string
|
Addr string
|
||||||
Client *Client
|
Protocol string
|
||||||
|
Transport string
|
||||||
|
Client *Client
|
||||||
}
|
}
|
||||||
|
225
gost/socks.go
Normal file
225
gost/socks.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ginuerzh/gosocks4"
|
||||||
|
"github.com/ginuerzh/gosocks5"
|
||||||
|
"github.com/go-log/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MethodTLS uint8 = 0x80 // extended method for tls
|
||||||
|
MethodTLSAuth uint8 = 0x82 // extended method for tls+auth
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CmdUdpTun uint8 = 0xF3 // extended method for udp over tcp
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientSelector struct {
|
||||||
|
methods []uint8
|
||||||
|
User *url.Userinfo
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *ClientSelector) Methods() []uint8 {
|
||||||
|
return selector.methods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *ClientSelector) AddMethod(methods ...uint8) {
|
||||||
|
selector.methods = append(selector.methods, methods...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *ClientSelector) Select(methods ...uint8) (method uint8) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *ClientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) {
|
||||||
|
switch method {
|
||||||
|
case MethodTLS:
|
||||||
|
conn = tls.Client(conn, selector.TLSConfig)
|
||||||
|
|
||||||
|
case gosocks5.MethodUserPass, MethodTLSAuth:
|
||||||
|
if method == MethodTLSAuth {
|
||||||
|
conn = tls.Client(conn, selector.TLSConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
var username, password string
|
||||||
|
if selector.User != nil {
|
||||||
|
username = selector.User.Username()
|
||||||
|
password, _ = selector.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password)
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
log.Log("[socks5]", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Log("[socks5]", req)
|
||||||
|
}
|
||||||
|
resp, err := gosocks5.ReadUserPassResponse(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Log("[socks5]", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Log("[socks5]", resp)
|
||||||
|
}
|
||||||
|
if resp.Status != gosocks5.Succeeded {
|
||||||
|
return nil, gosocks5.ErrAuthFailure
|
||||||
|
}
|
||||||
|
case gosocks5.MethodNoAcceptable:
|
||||||
|
return nil, gosocks5.ErrBadMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type socks5Connector struct {
|
||||||
|
User *url.Userinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func SOCKS5Connector(user *url.Userinfo) Connector {
|
||||||
|
return &socks5Connector{User: user}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
selector := &ClientSelector{
|
||||||
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
User: c.User,
|
||||||
|
}
|
||||||
|
selector.AddMethod(
|
||||||
|
gosocks5.MethodNoAuth,
|
||||||
|
gosocks5.MethodUserPass,
|
||||||
|
MethodTLS,
|
||||||
|
)
|
||||||
|
|
||||||
|
cc := gosocks5.ClientConn(conn, selector)
|
||||||
|
if err := cc.Handleshake(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn = cc
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
|
||||||
|
Type: gosocks5.AddrDomain,
|
||||||
|
Host: host,
|
||||||
|
Port: uint16(p),
|
||||||
|
})
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Log("[socks5]", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := gosocks5.ReadReply(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Log("[socks5]", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply.Rep != gosocks5.Succeeded {
|
||||||
|
return nil, errors.New("Service unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type socks4Connector struct{}
|
||||||
|
|
||||||
|
func SOCKS4Connector() Connector {
|
||||||
|
return &socks4Connector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
||||||
|
&gosocks4.Addr{
|
||||||
|
Type: gosocks4.AddrIPv4,
|
||||||
|
Host: taddr.IP.String(),
|
||||||
|
Port: uint16(taddr.Port),
|
||||||
|
}, nil,
|
||||||
|
)
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[socks4] %s", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := gosocks4.ReadReply(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[socks4] %s", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply.Code != gosocks4.Granted {
|
||||||
|
return nil, fmt.Errorf("[socks4] %d", reply.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type socks4aConnector struct{}
|
||||||
|
|
||||||
|
func SOCKS4AConnector() Connector {
|
||||||
|
return &socks4aConnector{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *socks4aConnector) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p, _ := strconv.Atoi(port)
|
||||||
|
|
||||||
|
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
||||||
|
&gosocks4.Addr{Type: gosocks4.AddrDomain, Host: host, Port: uint16(p)}, nil)
|
||||||
|
if err := req.Write(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[socks4] %s", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := gosocks4.ReadReply(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[socks4] %s", reply)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply.Code != gosocks4.Granted {
|
||||||
|
return nil, fmt.Errorf("[socks4] %d", reply.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
36
gost/ss.go
36
gost/ss.go
@ -1,8 +1,12 @@
|
|||||||
package gost
|
package gost
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
||||||
@ -48,3 +52,35 @@ func (c *shadowConn) SetReadDeadline(t time.Time) error {
|
|||||||
func (c *shadowConn) SetWriteDeadline(t time.Time) error {
|
func (c *shadowConn) SetWriteDeadline(t time.Time) error {
|
||||||
return c.conn.SetWriteDeadline(t)
|
return c.conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type shadowConnector struct {
|
||||||
|
Cipher *url.Userinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShadowConnector(cipher *url.Userinfo) Connector {
|
||||||
|
return &shadowConnector{Cipher: cipher}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowConnector) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
|
||||||
|
rawaddr, err := ss.RawAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var method, password string
|
||||||
|
if c.Cipher != nil {
|
||||||
|
method = c.Cipher.Username()
|
||||||
|
password, _ = c.Cipher.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, err := ss.NewCipher(method, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &shadowConn{conn: sc}, nil
|
||||||
|
}
|
||||||
|
23
gost/tls.go
Normal file
23
gost/tls.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tlsTransporter struct {
|
||||||
|
TLSClientConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func TLSTransporter(cfg *tls.Config) Transporter {
|
||||||
|
return &tlsTransporter{TLSClientConfig: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *tlsTransporter) Network() string {
|
||||||
|
return "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *tlsTransporter) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||||
|
return tls.Client(conn, tr.TLSClientConfig), nil
|
||||||
|
}
|
202
tcp/client.go
202
tcp/client.go
@ -1,202 +0,0 @@
|
|||||||
package tcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/ginuerzh/gosocks4"
|
|
||||||
"github.com/ginuerzh/gosocks5"
|
|
||||||
"github.com/ginuerzh/gost"
|
|
||||||
"github.com/ginuerzh/gost/client"
|
|
||||||
"github.com/go-log/log"
|
|
||||||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nodeClient struct {
|
|
||||||
options *client.Options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) Init(opts ...client.Option) {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(c.options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) Options() *Options {
|
|
||||||
return c.options
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) Connect() (net.Conn, error) {
|
|
||||||
return net.Dial("tcp", c.options.Addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) Handshake(conn net.Conn) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) Dial(conn net.Conn, addr string) (net.Conn, error) {
|
|
||||||
if c.options.Protocol == "socks5" {
|
|
||||||
selector := &gost.ClientSelector{
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: !c.options.SecureVerify,
|
|
||||||
ServerName: c.options.ServerName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
selector.AddMethod(
|
|
||||||
gosocks5.MethodNoAuth,
|
|
||||||
gosocks5.MethodUserPass,
|
|
||||||
gost.MethodTLS,
|
|
||||||
)
|
|
||||||
users := c.options.Users
|
|
||||||
if len(users) > 0 {
|
|
||||||
selector.User = &users[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := gosocks5.ClientConn(conn, selector)
|
|
||||||
if err := cc.Handleshake(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = cc
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.dial(conn, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *nodeClient) dial(conn net.Conn, addr string) (net.Conn, error) {
|
|
||||||
protocol := c.options.Protocol
|
|
||||||
switch protocol {
|
|
||||||
case "ss": // shadowsocks
|
|
||||||
rawaddr, err := ss.RawAddr(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var method, password string
|
|
||||||
users := c.options.Users
|
|
||||||
if len(users) > 0 {
|
|
||||||
method = users[0].Username()
|
|
||||||
password, _ = users[0].Password()
|
|
||||||
}
|
|
||||||
|
|
||||||
cipher, err := ss.NewCipher(method, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sc, err := ss.DialWithRawAddrConn(rawaddr, conn, cipher)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn = gost.ShadowConn(sc)
|
|
||||||
|
|
||||||
case "socks", "socks5":
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &gosocks5.Addr{
|
|
||||||
Type: gosocks5.AddrDomain,
|
|
||||||
Host: host,
|
|
||||||
Port: uint16(p),
|
|
||||||
})
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Log("[socks5]", req)
|
|
||||||
|
|
||||||
reply, err := gosocks5.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Log("[socks5]", reply)
|
|
||||||
if reply.Rep != gosocks5.Succeeded {
|
|
||||||
return nil, errors.New("Service unavailable")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "socks4", "socks4a":
|
|
||||||
atype := gosocks4.AddrDomain
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, _ := strconv.Atoi(port)
|
|
||||||
|
|
||||||
if protocol == "socks4" {
|
|
||||||
taddr, err := net.ResolveTCPAddr("tcp4", addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
host = taddr.IP.String()
|
|
||||||
p = taddr.Port
|
|
||||||
atype = gosocks4.AddrIPv4
|
|
||||||
}
|
|
||||||
req := gosocks4.NewRequest(gosocks4.CmdConnect,
|
|
||||||
&gosocks4.Addr{Type: atype, Host: host, Port: uint16(p)}, nil)
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Logf("[%s] %s", protocol, req)
|
|
||||||
|
|
||||||
reply, err := gosocks4.ReadReply(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Logf("[%s] %s", protocol, reply)
|
|
||||||
|
|
||||||
if reply.Code != gosocks4.Granted {
|
|
||||||
return nil, fmt.Errorf("%s: code=%d", protocol, reply.Code)
|
|
||||||
}
|
|
||||||
case "http":
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
req := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
URL: &url.URL{Host: addr},
|
|
||||||
Host: addr,
|
|
||||||
ProtoMajor: 1,
|
|
||||||
ProtoMinor: 1,
|
|
||||||
Header: make(http.Header),
|
|
||||||
}
|
|
||||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
|
||||||
users := c.options.Users
|
|
||||||
if len(users) > 0 {
|
|
||||||
user := users[0]
|
|
||||||
s := user.String()
|
|
||||||
if _, set := user.Password(); !set {
|
|
||||||
s += ":"
|
|
||||||
}
|
|
||||||
req.Header.Set("Proxy-Authorization",
|
|
||||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(s)))
|
|
||||||
}
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//if glog.V(LDEBUG) {
|
|
||||||
dump, _ := httputil.DumpRequest(req, false)
|
|
||||||
log.Log(string(dump))
|
|
||||||
//}
|
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//if glog.V(LDEBUG) {
|
|
||||||
dump, _ = httputil.DumpResponse(resp, false)
|
|
||||||
log.Log(string(dump))
|
|
||||||
//}
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user