add Dialer to create connection
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
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue