You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
3.2 KiB
Go

package obfssh
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
"sync"
"time"
log "github.com/fangdingjun/go-log/v5"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
type Dialer struct {
// NetDial specifies the dial function for creating TCP connections. If
// NetDial is nil, net.Dial is used.
NetDial func(network, addr string) (net.Conn, error)
Proxy func() (*url.URL, error)
// TLSClientConfig specifies the TLS configuration to use with tls.Client.
// If nil, the default configuration is used.
// If either NetDialTLS or NetDialTLSContext are set, Dial assumes the TLS handshake
// is done there and TLSClientConfig is ignored.
TLSClientConfig *tls.Config
NetConf *Conf
}
func (d *Dialer) Dial(addr string, conf *ssh.ClientConfig) (*Client, error) {
if d.NetConf.Timeout == 0 {
d.NetConf.Timeout = 15 * time.Second
}
if d.NetConf.KeepAliveInterval == 0 {
d.NetConf.KeepAliveInterval = 10
}
if d.NetConf.KeepAliveMax == 0 {
d.NetConf.KeepAliveMax = 3
}
var dialFunc func(network, addr string) (net.Conn, error)
if d.NetDial == nil {
dialFunc = dialer.Dial
}
u, err := url.Parse(addr)
if err != nil {
return nil, err
}
if d.Proxy != nil {
dialFunc = func(network, addr string) (net.Conn, error) {
var conn net.Conn
var err error
u1, _ := d.Proxy()
if u1 == nil {
return dialer.Dial(network, addr)
}
log.Debugf("connect to proxy %s", u1.String())
switch u1.Scheme {
case "http":
conn, err = dialHTTPProxy(addr, u1)
case "https":
conn, err = dialHTTPSProxy(addr, u1)
case "socks5":
conn, err = dialSocks5Proxy(addr, u1)
default:
return nil, fmt.Errorf("unknown proxy scheme %s", u1.Scheme)
}
if err != nil {
log.Errorf("connect to proxy error %s", err)
}
return conn, err
}
}
switch u.Scheme {
case "":
conn, err := dialFunc("tcp", u.Host)
if err != nil {
return nil, err
}
return NewClient(&TimedOutConn{Conn: conn, Timeout: d.NetConf.Timeout}, conf, u.Host, d.NetConf)
case "tls":
conn, err := dialFunc("tcp", u.Host)
if err != nil {
return nil, err
}
conn = tls.Client(&TimedOutConn{Conn: conn, Timeout: d.NetConf.Timeout}, d.TLSClientConfig)
return NewClient(conn, conf, u.Host, d.NetConf)
case "ws":
fallthrough
case "wss":
_addr := fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)
_dailer := websocket.Dialer{
NetDial: func(network, addr string) (net.Conn, error) {
c, err := dialFunc(network, addr)
return &TimedOutConn{Conn: c, Timeout: d.NetConf.Timeout}, err
},
TLSClientConfig: d.TLSClientConfig,
}
wsconn, res, err := _dailer.Dial(_addr, nil)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusSwitchingProtocols {
return nil, fmt.Errorf("websocket connect failed, http code %d", res.StatusCode)
}
_conn := &wsConn{Conn: wsconn, buf: new(bytes.Buffer), mu: new(sync.Mutex), ch: make(chan struct{})}
go _conn.readLoop()
return NewClient(_conn, conf, u.Host, d.NetConf)
default:
return nil, fmt.Errorf("unknow scheme %s", u.Scheme)
}
}
func (d *Dialer) DialContext(ctx context.Context, addr string, conf *ssh.ClientConfig) (*Client, error) {
return nil, nil
}