diff --git a/obfssh_client/config.go b/obfssh_client/config.go index 2b003d0..60aae64 100644 --- a/obfssh_client/config.go +++ b/obfssh_client/config.go @@ -59,6 +59,15 @@ type config struct { LocalForwards stringSlice `yaml:"local_forward"` RemoteForwards stringSlice `yaml:"remote_forward"` DynamicForwards stringSlice `yaml:"dynamic_forward"` + Proxy proxy +} + +type proxy struct { + Scheme string + Host string + Port int + SNI string + Insecure bool } // loadConfig load config from config file diff --git a/obfssh_client/config_example.yaml b/obfssh_client/config_example.yaml index 23ba2e6..9e1dfe2 100644 --- a/obfssh_client/config_example.yaml +++ b/obfssh_client/config_example.yaml @@ -14,6 +14,32 @@ # port: 2223 +# proxy +# the proxy server to connect to +# supported scheme: http, https, socks5 +# when scheme is http will create a tcp connection to proxy server +# and send http CONNECT request to target server +# when scheme is https will create a TLS connection to proxy server +# and send http CONNECT request to target server +# when scheme is socks5 will handshake with proxy server with socks5 protocol +# and send a connect request to target server +# +# https proxy has an sni and insecure options +# sni is the server TLS SNI name +# insecure indicates verify server certificate or not + +# proxy: +# scheme: socks5 +# host: 127.0.0.1 +# port: 9050 + +# proxy: +# scheme: https +# host: www.example.com +# port: 443 +# sni: example.com +# insecure: false + # obfs_method # diff --git a/obfssh_client/config_test.go b/obfssh_client/config_test.go index 9c11537..4828bef 100644 --- a/obfssh_client/config_test.go +++ b/obfssh_client/config_test.go @@ -6,7 +6,8 @@ import ( ) func TestConfig(t *testing.T) { - c, err := loadConfig("config_example.yaml") + var c config + err := loadConfig(&c, "config_example.yaml") if err != nil { t.Error(err) } diff --git a/obfssh_client/proxy.go b/obfssh_client/proxy.go new file mode 100644 index 0000000..582ad8a --- /dev/null +++ b/obfssh_client/proxy.go @@ -0,0 +1,138 @@ +package main + +import ( + "bufio" + "crypto/tls" + "fmt" + "github.com/fangdingjun/obfssh" + socks "github.com/fangdingjun/socks-go" + "net" + "net/url" + "os" + "strconv" + "strings" +) + +func updateProxyFromEnv(cfg *config) { + if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 { + obfssh.Log(obfssh.DEBUG, "proxy already specified by config, not parse environment proxy") + return + } + proxyStr := os.Getenv("http_proxy") + if proxyStr == "" { + proxyStr = os.Getenv("https_proxy") + } + if proxyStr == "" { + return + } + u, err := url.Parse(proxyStr) + if err != nil { + obfssh.Log(obfssh.DEBUG, "parse proxy from environment failed: %s", err) + return + } + cfg.Proxy.Scheme = u.Scheme + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + cfg.Proxy.Host = host + cfg.Proxy.Port = 8080 + } else { + cfg.Proxy.Host = host + p, err := strconv.ParseInt(port, 10, 32) + if err == nil { + cfg.Proxy.Port = int(p) + } else { + cfg.Proxy.Port = 8080 + } + } +} + +func httpProxyHandshake(c net.Conn, host string, port int) 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) + + // read status line + statusLine, err := r.ReadString('\n') + if err != nil { + return err + } + + if statusLine[0:4] != "HTTP" { + return err + } + + status := strings.Fields(statusLine)[1] + + statusCode, err := strconv.ParseInt(status, 10, 32) + if err != nil { + return err + } + + if statusCode != 200 { + return fmt.Errorf("http status error %d", statusCode) + } + + // read header + for { + h, err := r.ReadString('\n') + if err != nil { + return err + } + h1 := strings.Trim(h, " \r\n") + if h1 == "" { + break + } + } + + return nil +} + +func dialHTTPProxy(host string, port int, p proxy) (net.Conn, error) { + c, err := net.Dial("tcp", fmt.Sprintf("%s:%d", p.Host, p.Port)) + if err = httpProxyHandshake(c, host, port); err != nil { + c.Close() + return nil, err + } + return c, 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.Dial("tcp", fmt.Sprintf("%s:%d", p.Host, p.Port), tlsconfig) + if err := c.Handshake(); err != nil { + c.Close() + return nil, err + } + + if err = httpProxyHandshake(c, host, port); err != nil { + c.Close() + return nil, err + } + return c, nil +} + +func dialSocks5Proxy(host string, port int, p proxy) (net.Conn, error) { + c, err := net.Dial("tcp", fmt.Sprintf("%s:%d", p.Host, p.Port)) + if err != nil { + c.Close() + return nil, err + } + + c1 := &socks.Client{Conn: c} + if err = c1.Connect(host, uint16(port)); err != nil { + c1.Close() + return nil, err + } + return c1, err +} diff --git a/obfssh_client/ssh.go b/obfssh_client/ssh.go index 0f81469..36b1a48 100644 --- a/obfssh_client/ssh.go +++ b/obfssh_client/ssh.go @@ -156,9 +156,27 @@ func main() { Timeout: 10 * time.Second, } + // parse environment proxy + updateProxyFromEnv(&cfg) + rhost := net.JoinHostPort(host, fmt.Sprintf("%d", cfg.Port)) - c, err := net.Dial("tcp", rhost) + var c net.Conn + if cfg.Proxy.Scheme != "" && cfg.Proxy.Host != "" && cfg.Proxy.Port != 0 { + switch cfg.Proxy.Scheme { + case "http": + c, err = dialHTTPProxy(host, cfg.Port, cfg.Proxy) + case "https": + c, err = dialHTTPSProxy(host, cfg.Port, cfg.Proxy) + case "socks5": + c, err = dialSocks5Proxy(host, cfg.Port, cfg.Proxy) + default: + err = fmt.Errorf("unsupported scheme: %s", cfg.Proxy.Scheme) + } + } else { + c, err = net.Dial("tcp", rhost) + } + if err != nil { log.Fatal(err) }