mirror of https://github.com/ginuerzh/gost
23 changed files with 516 additions and 677 deletions
@ -1,10 +0,0 @@ |
|||
package gost |
|||
|
|||
import "net" |
|||
|
|||
// Client represents a node client
|
|||
type Client interface { |
|||
Connect() (net.Conn, error) |
|||
Handshake(conn net.Conn) (net.Conn, error) |
|||
Dial(conn net.Conn, addr string) (net.Conn, error) |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"context" |
|||
"net" |
|||
) |
|||
|
|||
type Chain struct { |
|||
Nodes []Node |
|||
} |
|||
|
|||
func (c *Chain) Dial(ctx context.Context, addr string) (net.Conn, error) { |
|||
if len(c.Nodes) == 0 { |
|||
return net.Dial("tcp", addr) |
|||
} |
|||
|
|||
nodes := c.Nodes |
|||
conn, err := nodes[0].Client.Dial(ctx, nodes[0].Addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
for i, node := range nodes { |
|||
if i == len(nodes)-1 { |
|||
break |
|||
} |
|||
|
|||
cn, err := node.Client.Connect(ctx, conn, nodes[i+1].Addr) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
conn = cn |
|||
} |
|||
|
|||
cn, err := nodes[len(nodes)-1].Client.Connect(ctx, conn, addr) |
|||
if err != nil { |
|||
conn.Close() |
|||
return nil, err |
|||
} |
|||
return cn, nil |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"bufio" |
|||
"context" |
|||
"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/go-log/log" |
|||
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks" |
|||
) |
|||
|
|||
type Client struct { |
|||
Protocol string |
|||
Transport *Transport |
|||
User *url.Userinfo |
|||
} |
|||
|
|||
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) { |
|||
return c.Transport.Dial(ctx, addr) |
|||
} |
|||
|
|||
func (c *Client) Connect(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) { |
|||
protocol := c.Protocol |
|||
|
|||
switch protocol { |
|||
case "ss": // shadowsocks
|
|||
rawaddr, err := ss.RawAddr(addr) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
var method, password string |
|||
if c.User != nil { |
|||
method = c.User.Username() |
|||
password, _ = c.User.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 = 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") |
|||
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) |
|||
} |
|||
} |
|||
|
|||
return conn, nil |
|||
} |
|||
|
|||
type Transport struct { |
|||
Dial func(ctx context.Context, addr string) (net.Conn, error) |
|||
TLSClientConfig *tls.Config |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
package gost |
|||
|
|||
var Debug bool |
|||
@ -0,0 +1,6 @@ |
|||
package gost |
|||
|
|||
type Node struct { |
|||
Addr string |
|||
Client *Client |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"net/url" |
|||
) |
|||
|
|||
type Server struct { |
|||
Addr string `opt:"addr"` // [host]:port
|
|||
Protocol string `opt:"protocol"` // protocol: http/socks5/ss
|
|||
TLSConfig *tls.Config |
|||
Chain *Chain |
|||
Users []url.Userinfo `opt:"user"` // authentication for proxy
|
|||
} |
|||
|
|||
func (s *Server) Run() error { |
|||
return nil |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"net" |
|||
"time" |
|||
) |
|||
|
|||
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
|||
// we wrap around it to make io.Copy happy
|
|||
type shadowConn struct { |
|||
conn net.Conn |
|||
} |
|||
|
|||
func ShadowConn(conn net.Conn) net.Conn { |
|||
return &shadowConn{conn: conn} |
|||
} |
|||
|
|||
func (c *shadowConn) Read(b []byte) (n int, err error) { |
|||
return c.conn.Read(b) |
|||
} |
|||
|
|||
func (c *shadowConn) Write(b []byte) (n int, err error) { |
|||
n = len(b) // force byte length consistent
|
|||
_, err = c.conn.Write(b) |
|||
return |
|||
} |
|||
|
|||
func (c *shadowConn) Close() error { |
|||
return c.conn.Close() |
|||
} |
|||
|
|||
func (c *shadowConn) LocalAddr() net.Addr { |
|||
return c.conn.LocalAddr() |
|||
} |
|||
|
|||
func (c *shadowConn) RemoteAddr() net.Addr { |
|||
return c.conn.RemoteAddr() |
|||
} |
|||
|
|||
func (c *shadowConn) SetDeadline(t time.Time) error { |
|||
return c.conn.SetDeadline(t) |
|||
} |
|||
|
|||
func (c *shadowConn) SetReadDeadline(t time.Time) error { |
|||
return c.conn.SetReadDeadline(t) |
|||
} |
|||
|
|||
func (c *shadowConn) SetWriteDeadline(t time.Time) error { |
|||
return c.conn.SetWriteDeadline(t) |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
package gost |
|||
|
|||
import ( |
|||
"log" |
|||
"net/url" |
|||
"reflect" |
|||
) |
|||
|
|||
// Options holds options of node
|
|||
type Options interface { |
|||
BaseOptions() *BaseOptions |
|||
} |
|||
|
|||
type Option func(Options) |
|||
|
|||
type BaseOptions struct { |
|||
Addr string `opt:"addr"` // [host]:port
|
|||
Protocol string `opt:"protocol"` // protocol: http/socks5/ss
|
|||
Transport string `opt:"transport"` // transport: ws/wss/tls/http2/tcp/udp/rtcp/rudp
|
|||
Users []url.Userinfo `opt:"users"` // authentication for proxy
|
|||
} |
|||
|
|||
func AddrOption(a string) Option { |
|||
return func(opts Options) { |
|||
opts.BaseOptions().Addr = a |
|||
} |
|||
} |
|||
|
|||
func ProtocolOption(p string) Option { |
|||
return func(opts Options) { |
|||
opts.BaseOptions().Protocol = p |
|||
} |
|||
} |
|||
|
|||
func TransportOption(t string) Option { |
|||
return func(opts Options) { |
|||
opts.BaseOptions().Transport = t |
|||
} |
|||
} |
|||
|
|||
func UsersOption(users ...url.Userinfo) Option { |
|||
return func(opts Options) { |
|||
opts.BaseOptions().Users = users |
|||
} |
|||
} |
|||
|
|||
func GetOption(i interface{}, opt string) interface{} { |
|||
ps := reflect.ValueOf(i) |
|||
if ps.Kind() != reflect.Ptr && ps.Kind() != reflect.Interface { |
|||
return nil |
|||
} |
|||
s := ps.Elem() |
|||
for n := 0; n < s.NumField(); n++ { |
|||
log.Println("tag:", s.Type().Field(n).Tag.Get("opt")) |
|||
if opt == s.Type().Field(n).Tag.Get("opt") && s.Field(n).CanInterface() { |
|||
// return s.Field(n).Interface()
|
|||
} |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func SetOption(i interface{}, opt string, v interface{}) { |
|||
ps := reflect.ValueOf(i) |
|||
if ps.Kind() != reflect.Ptr || ps.Kind() != reflect.Interface { |
|||
return |
|||
} |
|||
s := ps.Elem() |
|||
|
|||
for n := 0; n < s.NumField(); n++ { |
|||
if opt == s.Type().Field(n).Tag.Get("opt") && |
|||
s.Field(n).IsValid() && s.Field(n).CanSet() { |
|||
s.Field(n).Set(reflect.ValueOf(v)) |
|||
return |
|||
} |
|||
} |
|||
} |
|||
@ -1,167 +0,0 @@ |
|||
package socks |
|||
|
|||
import ( |
|||
"crypto/tls" |
|||
"net" |
|||
"net/url" |
|||
|
|||
"github.com/ginuerzh/gosocks5" |
|||
"github.com/golang/glog" |
|||
) |
|||
|
|||
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 { |
|||
glog.Infoln("socks5 auth:", err) |
|||
return nil, err |
|||
} |
|||
glog.Infoln(req) |
|||
|
|||
resp, err := gosocks5.ReadUserPassResponse(conn) |
|||
if err != nil { |
|||
glog.Infoln("socks5 auth:", err) |
|||
return nil, err |
|||
} |
|||
glog.Infoln(resp) |
|||
|
|||
if resp.Status != gosocks5.Succeeded { |
|||
return nil, gosocks5.ErrAuthFailure |
|||
} |
|||
case gosocks5.MethodNoAcceptable: |
|||
return nil, gosocks5.ErrBadMethod |
|||
} |
|||
|
|||
return conn, nil |
|||
} |
|||
|
|||
type ServerSelector struct { |
|||
methods []uint8 |
|||
users []*url.Userinfo |
|||
tlsConfig *tls.Config |
|||
} |
|||
|
|||
func (selector *ServerSelector) Methods() []uint8 { |
|||
return selector.methods |
|||
} |
|||
|
|||
func (selector *ServerSelector) Select(methods ...uint8) (method uint8) { |
|||
glog.Infof("%d %d %v", gosocks5.Ver5, len(methods), methods) |
|||
|
|||
method = gosocks5.MethodNoAuth |
|||
for _, m := range methods { |
|||
if m == MethodTLS { |
|||
method = m |
|||
break |
|||
} |
|||
} |
|||
|
|||
// when user/pass is set, auth is mandatory
|
|||
if selector.users != nil { |
|||
if method == gosocks5.MethodNoAuth { |
|||
method = gosocks5.MethodUserPass |
|||
} |
|||
if method == MethodTLS { |
|||
method = MethodTLSAuth |
|||
} |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func (selector *ServerSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { |
|||
glog.Infof("%d %d", gosocks5.Ver5, method) |
|||
|
|||
switch method { |
|||
case MethodTLS: |
|||
conn = tls.Server(conn, selector.tlsConfig) |
|||
|
|||
case gosocks5.MethodUserPass, MethodTLSAuth: |
|||
if method == MethodTLSAuth { |
|||
conn = tls.Server(conn, selector.tlsConfig) |
|||
} |
|||
|
|||
req, err := gosocks5.ReadUserPassRequest(conn) |
|||
if err != nil { |
|||
glog.Infoln("[socks5-auth]", err) |
|||
return nil, err |
|||
} |
|||
glog.Infoln("[socks5]", req.String()) |
|||
|
|||
valid := false |
|||
for _, user := range selector.users { |
|||
username := user.Username() |
|||
password, _ := user.Password() |
|||
if (req.Username == username && req.Password == password) || |
|||
(req.Username == username && password == "") || |
|||
(username == "" && req.Password == password) { |
|||
valid = true |
|||
break |
|||
} |
|||
} |
|||
if len(selector.users) > 0 && !valid { |
|||
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) |
|||
if err := resp.Write(conn); err != nil { |
|||
glog.Infoln("[socks5-auth]", err) |
|||
return nil, err |
|||
} |
|||
glog.Infoln("[socks5]", resp) |
|||
glog.Infoln("[socks5-auth] proxy authentication required") |
|||
|
|||
return nil, gosocks5.ErrAuthFailure |
|||
} |
|||
|
|||
resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded) |
|||
if err := resp.Write(conn); err != nil { |
|||
glog.Infoln("[socks5-auth]", err) |
|||
return nil, err |
|||
} |
|||
glog.Infoln(resp) |
|||
|
|||
case gosocks5.MethodNoAcceptable: |
|||
return nil, gosocks5.ErrBadMethod |
|||
} |
|||
|
|||
return conn, nil |
|||
} |
|||
@ -1,46 +0,0 @@ |
|||
package tcp |
|||
|
|||
import ( |
|||
"github.com/ginuerzh/gost" |
|||
) |
|||
|
|||
type tcpNode struct { |
|||
options *nodeOptions |
|||
client *nodeClient |
|||
server *nodeServer |
|||
} |
|||
|
|||
// NewNode creates a tcpNode with options
|
|||
func NewNode(opts ...gost.Option) gost.Node { |
|||
options := new(nodeOptions) |
|||
for _, opt := range opts { |
|||
opt(options) |
|||
} |
|||
node := &tcpNode{ |
|||
options: options, |
|||
client: &nodeClient{options: options}, |
|||
server: &nodeServer{options: options}, |
|||
} |
|||
|
|||
return node |
|||
} |
|||
|
|||
func (node *tcpNode) Init(opts ...gost.Option) error { |
|||
for _, opt := range opts { |
|||
opt(node.options) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (node *tcpNode) Client() gost.Client { |
|||
return node.client |
|||
} |
|||
|
|||
func (node *tcpNode) Server() gost.Server { |
|||
return node.server |
|||
} |
|||
|
|||
func (node *tcpNode) Options() gost.Options { |
|||
return node.options |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
package tcp |
|||
|
|||
import ( |
|||
"github.com/ginuerzh/gost" |
|||
) |
|||
|
|||
type nodeOptions struct { |
|||
base *gost.BaseOptions |
|||
certFile string `opt:"cert"` |
|||
keyFile string `opt:"key"` |
|||
serverName string `opt:"server_name"` |
|||
secureVerify bool `opt:"secure"` |
|||
} |
|||
|
|||
func (o *nodeOptions) BaseOptions() *gost.BaseOptions { |
|||
return o.base |
|||
} |
|||
|
|||
func (o *nodeOptions) ServerNameOption(n string) gost.Option { |
|||
return func(opts gost.Options) { |
|||
if o, ok := opts.(*nodeOptions); ok { |
|||
o.serverName = n |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (o *nodeOptions) SecureVerifyOption(b bool) gost.Option { |
|||
return func(opts gost.Options) { |
|||
if o, ok := opts.(*nodeOptions); ok { |
|||
o.secureVerify = b |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (o *nodeOptions) CertFileOption(f string) gost.Option { |
|||
return func(opts gost.Options) { |
|||
if o, ok := opts.(*nodeOptions); ok { |
|||
o.certFile = f |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (o *nodeOptions) KeyFileOption(f string) gost.Option { |
|||
return func(opts gost.Options) { |
|||
if o, ok := opts.(*nodeOptions); ok { |
|||
o.keyFile = f |
|||
} |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
package tcp |
|||
|
|||
import "testing" |
|||
import "net/url" |
|||
import "reflect" |
|||
|
|||
var tests = []struct { |
|||
Opt string |
|||
Value interface{} |
|||
}{ |
|||
{"addr", "localhost:8080"}, |
|||
{"protocol", "http"}, |
|||
{"transport", "tcp"}, |
|||
{"users", []url.Userinfo{*url.UserPassword("admin", "123456")}}, |
|||
} |
|||
|
|||
func TestOptions(t *testing.T) { |
|||
opts := new(tcpNodeOptions) |
|||
for _, test := range tests { |
|||
opts.Set(test.Opt, test.Value) |
|||
v := opts.Get(test.Opt) |
|||
if !reflect.DeepEqual(v, test.Value) { |
|||
t.Log("not equal:", test.Opt, v) |
|||
t.Fail() |
|||
} |
|||
} |
|||
t.Log("addr:", opts.Addr) |
|||
t.Log("protocol:", opts.Protocol) |
|||
t.Log("transport:", opts.Transport) |
|||
t.Log("users:", opts.Users) |
|||
} |
|||
Loading…
Reference in new issue