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