add Dialer to create connection

ws
fangdingjun 2 years ago
parent 64e7a7d74b
commit 17b70bca64

@ -0,0 +1,123 @@
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
}

@ -1,61 +1,14 @@
package main package main
import ( import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net" "net"
"net/textproto"
"net/url" "net/url"
"os" "os"
"strconv" "strconv"
"strings"
"time"
"github.com/fangdingjun/go-log/v5" "github.com/fangdingjun/go-log/v5"
socks "github.com/fangdingjun/socks-go"
) )
type httpProxyConn struct {
c net.Conn
r io.Reader
}
func (hc *httpProxyConn) Read(b []byte) (int, error) {
return hc.r.Read(b)
}
func (hc *httpProxyConn) Write(b []byte) (int, error) {
return hc.c.Write(b)
}
func (hc *httpProxyConn) Close() error {
return hc.c.Close()
}
func (hc *httpProxyConn) LocalAddr() net.Addr {
return hc.c.LocalAddr()
}
func (hc *httpProxyConn) RemoteAddr() net.Addr {
return hc.c.RemoteAddr()
}
func (hc *httpProxyConn) SetDeadline(t time.Time) error {
return hc.c.SetDeadline(t)
}
func (hc *httpProxyConn) SetReadDeadline(t time.Time) error {
return hc.c.SetReadDeadline(t)
}
func (hc *httpProxyConn) SetWriteDeadline(t time.Time) error {
return hc.c.SetWriteDeadline(t)
}
// validate the interface implements
var _ net.Conn = &httpProxyConn{}
func updateProxyFromEnv(cfg *config) { func updateProxyFromEnv(cfg *config) {
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 { if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
log.Debugf("proxy already specified by config, not parse environment proxy") log.Debugf("proxy already specified by config, not parse environment proxy")
@ -108,99 +61,3 @@ func updateProxyFromEnv(cfg *config) {
} }
} }
} }
func httpProxyHandshake(c net.Conn, host string, port int) (net.Conn, error) {
fmt.Fprintf(c, "CONNECT %s:%d HTTP/1.1\r\n", host, port)
fmt.Fprintf(c, "Host: %s:%d\r\n", host, port)
fmt.Fprintf(c, "User-Agent: go/1.7\r\n")
fmt.Fprintf(c, "\r\n")
r := bufio.NewReader(c)
tp := textproto.NewReader(r)
// read status line
statusLine, err := tp.ReadLine()
if err != nil {
return nil, err
}
if statusLine[0:4] != "HTTP" {
return nil, fmt.Errorf("not http reply")
}
status := strings.Fields(statusLine)[1]
statusCode, err := strconv.Atoi(status)
if err != nil {
return nil, err
}
if statusCode != 200 {
return nil, fmt.Errorf("http status error %d", statusCode)
}
// read header
if _, err = tp.ReadMIMEHeader(); err != nil {
return nil, err
}
return &httpProxyConn{c: c, r: r}, nil
}
func dialHTTPProxy(host string, port int, p proxy) (net.Conn, error) {
c, err := dialer.Dial("tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)))
if err != nil {
return nil, err
}
c1, err := httpProxyHandshake(c, host, port)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialHTTPSProxy(host string, port int, p proxy) (net.Conn, error) {
hostname := p.Host
if p.SNI != "" {
hostname = p.SNI
}
tlsconfig := &tls.Config{
ServerName: hostname,
InsecureSkipVerify: p.Insecure,
}
c, err := tls.DialWithDialer(dialer, "tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)), tlsconfig)
if err != nil {
return nil, err
}
if err := c.Handshake(); err != nil {
c.Close()
return nil, err
}
c1, err := httpProxyHandshake(c, host, port)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialSocks5Proxy(host string, port int, p proxy) (net.Conn, error) {
c, err := dialer.Dial("tcp", net.JoinHostPort(p.Host, fmt.Sprintf("%d", p.Port)))
if err != nil {
return nil, err
}
c1 := &socks.Client{Conn: c}
c2, err := c1.Dial("tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
c1.Close()
return nil, err
}
return c2, err
}

@ -34,7 +34,6 @@ func main() {
flag.StringVar(&cfg.Password, "pw", "", "ssh password") flag.StringVar(&cfg.Password, "pw", "", "ssh password")
flag.IntVar(&cfg.Port, "p", 22, "remote port") flag.IntVar(&cfg.Port, "p", 22, "remote port")
flag.StringVar(&cfg.PrivateKey, "i", "", "private key file") flag.StringVar(&cfg.PrivateKey, "i", "", "private key file")
flag.BoolVar(&cfg.TLS, "tls", false, "use tls or not")
flag.BoolVar(&cfg.TLSInsecure, "tls-insecure", false, "insecure tls connnection") flag.BoolVar(&cfg.TLSInsecure, "tls-insecure", false, "insecure tls connnection")
flag.Var(&cfg.LocalForwards, "L", "forward local port to remote, format [local_host:]local_port:remote_host:remote_port") flag.Var(&cfg.LocalForwards, "L", "forward local port to remote, format [local_host:]local_port:remote_host:remote_port")
flag.Var(&cfg.RemoteForwards, "R", "forward remote port to local, format [remote_host:]remote_port:local_host:local_port") flag.Var(&cfg.RemoteForwards, "R", "forward remote port to local, format [remote_host:]remote_port:local_host:local_port")
@ -148,10 +147,13 @@ func main() {
cmd = strings.Join(args, " ") cmd = strings.Join(args, " ")
} }
var serverName string
if strings.Contains(host, "@") { if strings.Contains(host, "@") {
ss := strings.SplitN(host, "@", 2) u, _ := url.Parse(host)
cfg.Username = ss[0] cfg.Username = u.User.Username()
host = ss[1] u.User = nil
host = u.String()
serverName, _, _ = net.SplitHostPort(u.Host)
} }
// process user specified private key // process user specified private key
@ -193,74 +195,33 @@ func main() {
// parse environment proxy // parse environment proxy
updateProxyFromEnv(&cfg) updateProxyFromEnv(&cfg)
var c net.Conn
var rhost string
if strings.HasPrefix(host, "ws://") || strings.HasPrefix(host, "wss://") {
c, err = obfssh.NewWSConn(host)
u, _ := url.Parse(host)
rhost = u.Host
} else {
rhost = net.JoinHostPort(host, fmt.Sprintf("%d", cfg.Port))
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
switch cfg.Proxy.Scheme {
case "http":
log.Debugf("use http proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialHTTPProxy(host, cfg.Port, cfg.Proxy)
case "https":
log.Debugf("use https proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialHTTPSProxy(host, cfg.Port, cfg.Proxy)
case "socks5":
log.Debugf("use socks proxy %s:%d to connect to server",
cfg.Proxy.Host, cfg.Proxy.Port)
c, err = dialSocks5Proxy(host, cfg.Port, cfg.Proxy)
default:
err = fmt.Errorf("unsupported scheme: %s", cfg.Proxy.Scheme)
}
} else {
log.Debugf("dail to %s", rhost)
c, err = dialer.Dial("tcp", rhost)
}
}
if err != nil {
log.Fatal(err)
}
log.Debugf("dail success")
timeout := time.Duration(cfg.KeepaliveInterval*2) * time.Second timeout := time.Duration(cfg.KeepaliveInterval*2) * time.Second
var _conn net.Conn = &obfssh.TimedOutConn{Conn: c, Timeout: timeout}
if cfg.TLS {
log.Debugf("begin tls handshake")
_conn = tls.Client(_conn, &tls.Config{
ServerName: host,
InsecureSkipVerify: cfg.TLSInsecure,
})
if err := _conn.(*tls.Conn).Handshake(); err != nil {
log.Fatal(err)
}
log.Debugf("tls handshake done")
}
conf := &obfssh.Conf{ conf := &obfssh.Conf{
Timeout: timeout, Timeout: timeout,
KeepAliveInterval: time.Duration(cfg.KeepaliveInterval) * time.Second, KeepAliveInterval: time.Duration(cfg.KeepaliveInterval) * time.Second,
KeepAliveMax: cfg.KeepaliveMax, KeepAliveMax: cfg.KeepaliveMax,
} }
log.Debugf("ssh negotation") dialer := &obfssh.Dialer{
client, err := obfssh.NewClient(_conn, config, rhost, conf) Proxy: func() (*url.URL, error) {
if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 {
return &url.URL{
Scheme: cfg.Proxy.Scheme,
Host: fmt.Sprintf("%s:%d", cfg.Proxy.Host, cfg.Proxy.Port),
}, nil
}
return nil, nil
},
TLSClientConfig: &tls.Config{ServerName: serverName, InsecureSkipVerify: cfg.TLSInsecure},
NetConf: conf,
}
client, err := dialer.Dial(host, config)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Debugf("ssh negotation success")
if agentClient != nil { if agentClient != nil {
client.SetAuthAgent(agentClient) client.SetAuthAgent(agentClient)
} }
@ -394,13 +355,19 @@ func passwordAuth() (string, error) {
func usage() { func usage() {
usageStr := `Usage: usageStr := `Usage:
obfssh -N -d -D [bind_address:]port -f configfile obfssh -N -d -D [bind_address:]port -f configfile
-tls -tls-insecure -log_file /path/to/file -tls-insecure -log_file /path/to/file
-log_count 10 -log_size 10 -log_count 10 -log_size 10
-log_level INFO -log_level INFO
-i identity_file -L [bind_address:]port:host:hostport -i identity_file -L [bind_address:]port:host:hostport
-l login_name -pw password -p port -l login_name -pw password -p port
-http [bind_addr:]port -http [bind_addr:]port
-R [bind_address:]port:host:hostport [user@]hostname [command] -R [bind_address:]port:host:hostport host [command]
host can be multiple forms, example:
user@example.com
tls://hostname
ws://hostname/path
wss://hostname/path
Options: Options:
-d verbose mode -d verbose mode
@ -461,11 +428,8 @@ Options:
encrypt ssh connection encrypt ssh connection
similar like -D, but input is http, not socks5 similar like -D, but input is http, not socks5
-tls
connect to server via TLS
-tls-insecure -tls-insecure
do not verify server's tls ceritificate do not verify server's tls ceritificate when use tls:// or wss://
-log_file -log_file
log file, default stdout log file, default stdout

@ -0,0 +1,151 @@
package obfssh
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/textproto"
"net/url"
"strconv"
"strings"
"time"
log "github.com/fangdingjun/go-log/v5"
socks "github.com/fangdingjun/socks-go"
)
type httpProxyConn struct {
c net.Conn
r io.Reader
}
func (hc *httpProxyConn) Read(b []byte) (int, error) {
return hc.r.Read(b)
}
func (hc *httpProxyConn) Write(b []byte) (int, error) {
return hc.c.Write(b)
}
func (hc *httpProxyConn) Close() error {
return hc.c.Close()
}
func (hc *httpProxyConn) LocalAddr() net.Addr {
return hc.c.LocalAddr()
}
func (hc *httpProxyConn) RemoteAddr() net.Addr {
return hc.c.RemoteAddr()
}
func (hc *httpProxyConn) SetDeadline(t time.Time) error {
return hc.c.SetDeadline(t)
}
func (hc *httpProxyConn) SetReadDeadline(t time.Time) error {
return hc.c.SetReadDeadline(t)
}
func (hc *httpProxyConn) SetWriteDeadline(t time.Time) error {
return hc.c.SetWriteDeadline(t)
}
// validate the interface implements
var _ net.Conn = &httpProxyConn{}
func httpProxyHandshake(c net.Conn, addr string) (net.Conn, error) {
log.Debugf("http handshake with %s", addr)
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\n", addr)
fmt.Fprintf(c, "Host: %s\r\n", addr)
fmt.Fprintf(c, "User-Agent: go/1.7\r\n")
fmt.Fprintf(c, "\r\n")
r := bufio.NewReader(c)
tp := textproto.NewReader(r)
// read status line
statusLine, err := tp.ReadLine()
if err != nil {
return nil, err
}
if statusLine[0:4] != "HTTP" {
return nil, fmt.Errorf("not http reply")
}
status := strings.Fields(statusLine)[1]
statusCode, err := strconv.Atoi(status)
if err != nil {
return nil, err
}
if statusCode != 200 {
return nil, fmt.Errorf("http status error %d", statusCode)
}
// read header
if _, err = tp.ReadMIMEHeader(); err != nil {
return nil, err
}
return &httpProxyConn{c: c, r: r}, nil
}
func dialHTTPProxy(addr string, p *url.URL) (net.Conn, error) {
log.Debugf("dial to %s", p.Host)
c, err := dialer.Dial("tcp", p.Host)
if err != nil {
return nil, err
}
c1, err := httpProxyHandshake(c, addr)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialHTTPSProxy(addr string, p *url.URL) (net.Conn, error) {
hostname := p.Host
tlsconfig := &tls.Config{
ServerName: hostname,
InsecureSkipVerify: true,
}
c, err := tls.DialWithDialer(dialer, "tcp", p.Host, tlsconfig)
if err != nil {
return nil, err
}
if err := c.Handshake(); err != nil {
c.Close()
return nil, err
}
c1, err := httpProxyHandshake(c, addr)
if err != nil {
c.Close()
return nil, err
}
return c1, nil
}
func dialSocks5Proxy(addr string, p *url.URL) (net.Conn, error) {
c, err := dialer.Dial("tcp", p.Host)
if err != nil {
return nil, err
}
c1 := &socks.Client{Conn: c}
c2, err := c1.Dial("tcp", addr)
if err != nil {
c1.Close()
return nil, err
}
return c2, err
}

27
ws.go

@ -3,14 +3,12 @@ package obfssh
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"net" "net"
"net/http"
"sync" "sync"
"time" "time"
"github.com/fangdingjun/go-log/v5" log "github.com/fangdingjun/go-log/v5"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@ -23,29 +21,6 @@ type wsConn struct {
var _ net.Conn = &wsConn{} var _ net.Conn = &wsConn{}
// NewWSConn dial to websocket server and return net.Conn
func NewWSConn(p string) (net.Conn, error) {
conn, resp, err := websocket.DefaultDialer.Dial(p, nil)
if err != nil {
return nil, err
}
resp.Body.Close()
if resp.StatusCode != http.StatusSwitchingProtocols {
return nil, fmt.Errorf("http status %d", resp.StatusCode)
}
c := &wsConn{Conn: conn,
buf: bytes.NewBuffer(nil),
mu: new(sync.Mutex),
ch: make(chan struct{}),
}
go c.readLoop()
return c, nil
}
func (wc *wsConn) readLoop() { func (wc *wsConn) readLoop() {
for { for {
_, data, err := wc.ReadMessage() _, data, err := wc.ReadMessage()

Loading…
Cancel
Save