Add Socks5 support

This commit is contained in:
wweir
2020-02-17 06:19:36 +08:00
parent 4681dd9845
commit 7d77f4a85e
14 changed files with 187 additions and 139 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ func init() {
flag.StringVar(&Server.Upstream, "s", "", "upstream http service, eg: 127.0.0.1:8080")
flag.StringVar(&Server.CertFile, "s_cert", "", "tls cert file, gen cert from letsencrypt if empty")
flag.StringVar(&Server.KeyFile, "s_key", "", "tls key file, gen cert from letsencrypt if empty")
flag.StringVar(&Client.Address, "c", "", "remote server domain, eg: aa.bb.cc") // TODO: socks5://127.0.0.1:1080
flag.StringVar(&Client.Address, "c", "", "remote server domain, eg: aa.bb.cc, socks5h://127.0.0.1:1080")
flag.StringVar(&Client.HTTPProxy.Address, "http_proxy", ":8080", "http proxy, empty to disable")
flag.StringVar(&Client.DNS.ServeIP, "dns_ip", "", "upstream dns, eg: 127.0.0.1, disable dns proxy if empty")
flag.StringVar(&Client.DNS.Upstream, "dns_upstream", "", "dns relay server ip, dynamic detect if empty")
+2 -2
View File
@@ -15,13 +15,13 @@ import (
"github.com/wweir/utils/log"
)
const svcPath = "/Library/LaunchDaemons/cc.wweir.sower.plist"
const svcPath = "/Library/LaunchDaemons/sower.plist"
const svcFile = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>cc.wweir.sower</string>
<string>sower</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
+14 -7
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/wweir/sower/internal/http"
"github.com/wweir/sower/internal/socks5"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
"github.com/wweir/utils/mem"
@@ -78,15 +79,21 @@ func (d *dynamic) Get(key interface{}) (err error) {
defer wg.Done()
var conn net.Conn
if conn, err = tls.Dial("tcp", net.JoinHostPort(Client.Address, "443"), &tls.Config{}); err != nil {
log.Errorw("tls dial", "addr", net.JoinHostPort(Client.Address, "443"), "err", err)
return
}
if addr, ok := socks5.IsSocks5Schema(Client.Address); ok {
conn, err = net.Dial("tcp", addr)
conn = socks5.ToSocks5(conn, domain, uint16(ping.port))
if ping.port == http.HTTP {
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTP, "", 80)
} else {
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTPS, "", 443)
conn, err = tls.Dial("tcp", net.JoinHostPort(Client.Address, "443"), &tls.Config{})
if ping.port == http.HTTP {
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTP, "", 80)
} else {
conn = http.NewTgtConn(conn, passwordData, http.TGT_HTTPS, "", 443)
}
}
if err != nil {
log.Errorw("sower dial", "addr", Client.Address, "err", err)
return
}
if err := ping.port.PingWithConn(domain, conn, timeout); err != nil {
+1 -1
View File
@@ -1,5 +1,5 @@
[client]
address = "aa.bb.cc"
address = "" # aa.bb.cc, socks5h://127.0.0.1:1080
[client.dns]
flush_cmd="" # macOS: pkill mDNSResponder || true, Windows: ipconfig /flushdnss
+1 -1
View File
@@ -10,5 +10,5 @@ require (
github.com/pkg/errors v0.9.1
github.com/wweir/utils v0.0.0-20200214114658-f6f356a08736
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4
)
+2 -1
View File
@@ -73,8 +73,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+29 -32
View File
@@ -72,16 +72,10 @@ func (c *conn) Write(b []byte) (n int, err error) {
}
// ParseAddr parse target addr from net.Conn
func ParseAddr(conn net.Conn, password []byte) (c net.Conn, domain string, port uint16, err error) {
func ParseAddr(conn net.Conn, password []byte) (_ net.Conn, domain string, port uint16, err error) {
teeConn := &util.TeeConn{Conn: conn}
teeConn.StartOrReset()
defer func() {
if err != nil {
teeConn.Close()
} else {
teeConn.Stop()
}
}()
defer teeConn.Stop()
head := new(header)
if err = binary.Read(conn, binary.BigEndian, head); err != nil {
@@ -102,37 +96,40 @@ func ParseAddr(conn net.Conn, password []byte) (c net.Conn, domain string, port
case TGT_HTTP:
teeConn.DropAndRestart()
var resp *http.Request
resp, err = http.ReadRequest(bufio.NewReader(teeConn))
if err != nil {
return nil, "", 0, err
}
idx := strings.LastIndex(resp.Host, ":")
if idx == -1 {
return teeConn, resp.Host, 80, nil
}
var port uint64
if port, err = strconv.ParseUint(resp.Host[idx+1:], 10, 16); err != nil {
return nil, "", 0, err
}
return teeConn, resp.Host[:idx], uint16(port), nil
return ParseHTTP(teeConn)
case TGT_HTTPS:
teeConn.DropAndRestart()
domain, _, err := extractSNI(teeConn)
if err != nil {
return nil, "", 0, err
}
return teeConn, domain, head.Port, nil
conn, domain, err = ParseHTTPS(teeConn)
return conn, domain, head.Port, err
default:
return nil, "", 0, errors.New("invalid request")
return teeConn, "", 0, errors.New("invalid request")
}
}
func ParseHTTP(teeConn net.Conn) (_ net.Conn, domain string, port uint16, err error) {
resp, err := http.ReadRequest(bufio.NewReader(teeConn))
if err != nil {
return teeConn, "", 0, err
}
idx := strings.LastIndex(resp.Host, ":")
if idx == -1 {
return teeConn, resp.Host, 80, nil
}
p, err := strconv.ParseUint(resp.Host[idx+1:], 10, 16)
if err != nil {
return teeConn, "", 0, err
}
return teeConn, resp.Host[:idx], uint16(p), nil
}
func ParseHTTPS(teeConn net.Conn) (_ net.Conn, domain string, err error) {
if domain, _, err = extractSNI(teeConn); err != nil {
return nil, "", err
}
return teeConn, domain, nil
}
var errChecksum = errors.New("invalid checksum")
+44
View File
@@ -0,0 +1,44 @@
package socks5
// https://tools.ietf.org/html/rfc1928
type authReq struct {
VER byte
NMETHODS byte
METHODS [1]byte // 1 to 255, fix to no authentication
}
type authResp struct {
VER byte
METHOD byte
}
type request struct {
req
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type req struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *request) Bytes() []byte {
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
out = append(out, r.DST_ADDR...)
return append(out, r.DST_PORT...)
}
type response struct {
resp
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type resp struct {
VER byte
REP byte
RSV byte
ATYP byte
}
+20 -46
View File
@@ -5,13 +5,28 @@ import (
"fmt"
"io"
"net"
"strconv"
"strings"
)
func ToSocks5(c net.Conn, domain, port string) net.Conn {
num, _ := strconv.Atoi(port)
bytes := []byte{byte(num >> 8), byte(num)}
return &conn{init: make(chan struct{}), Conn: c, domain: domain, port: bytes}
func IsSocks5Schema(addr string) (string, bool) {
if strings.HasPrefix(addr, "socks5://") {
return strings.TrimPrefix(addr, "socks5://"), true
}
if strings.HasPrefix(addr, "socks5h://") {
return strings.TrimPrefix(addr, "socks5h://"), true
}
return addr, false
}
func ToSocks5(c net.Conn, domain string, port uint16) net.Conn {
return &conn{
init: make(chan struct{}),
Conn: c,
domain: domain,
port: []byte{byte(port >> 8), byte(port)},
}
}
type conn struct {
@@ -106,44 +121,3 @@ func (c *conn) Write(b []byte) (n int, err error) {
close(c.init)
return c.Conn.Write(b)
}
type authReq struct {
VER byte
NMETHODS byte
METHODS [1]byte // 1 to 255, fix to no authentication
}
type authResp struct {
VER byte
METHOD byte
}
type request struct {
req
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type req struct {
VER byte
CMD byte
RSV byte
ATYP byte
}
func (r *request) Bytes() []byte {
out := []byte{r.VER, r.CMD, r.RSV, r.ATYP}
out = append(out, r.DST_ADDR...)
return append(out, r.DST_PORT...)
}
type response struct {
resp
DST_ADDR []byte // first byte is length
DST_PORT []byte // two bytes
}
type resp struct {
VER byte
REP byte
RSV byte
ATYP byte
}
+1 -1
View File
@@ -47,7 +47,7 @@ func ServeDNS(redirectIP, relayServer string) {
server, err := pickRelayAddr(relayServer)
if err != nil {
log.Errorw("detect upstream dns", "err", err)
} else {
} else if relayServer != server {
relayServer = server
log.Infow("detect upstream dns", "addr", relayServer)
}
+10 -34
View File
@@ -11,6 +11,7 @@ import (
"github.com/wweir/sower/conf"
_http "github.com/wweir/sower/internal/http"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
)
@@ -33,20 +34,12 @@ func startHTTPProxy(httpProxyAddr, serverAddr string, password []byte) {
}
func httpProxy(w http.ResponseWriter, r *http.Request, serverAddr string, password []byte) {
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
host = r.Host
}
host, port := util.ParseHostPort(r.Host, 80)
roundTripper := &http.Transport{}
if conf.ShouldProxy(host) {
roundTripper.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := tls.Dial("tcp", net.JoinHostPort(serverAddr, "443"), &tls.Config{})
if err != nil {
return nil, err
}
return _http.NewTgtConn(conn, password, _http.TGT_HTTP, "", 80), nil
return dial(serverAddr, password, _http.TGT_HTTP, host, port)
}
}
@@ -67,17 +60,7 @@ func httpProxy(w http.ResponseWriter, r *http.Request, serverAddr string, passwo
}
func httpsProxy(w http.ResponseWriter, r *http.Request, serverAddr string, password []byte) {
var host string
var port = uint16(443)
if h, p, err := net.SplitHostPort(r.Host); err != nil {
host = r.Host
} else if pNum, err := strconv.ParseUint(p, 10, 16); err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
} else {
host = h
port = uint16(pNum)
}
host, port := util.ParseHostPort(r.Host, 443)
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
@@ -94,21 +77,14 @@ func httpsProxy(w http.ResponseWriter, r *http.Request, serverAddr string, passw
var rc net.Conn
if conf.ShouldProxy(host) {
rc, err = tls.Dial("tcp", net.JoinHostPort(serverAddr, "443"), &tls.Config{})
if err != nil {
conn.Write([]byte("sower: tls dial: " + err.Error()))
conn.Close()
return
}
rc = _http.NewTgtConn(rc, password, _http.TGT_HTTPS, "", port)
rc, err = dial(serverAddr, password, _http.TGT_HTTPS, host, port)
} else {
rc, err = net.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(int(port))))
if err != nil {
conn.Write([]byte("sower: tcp dial: " + err.Error()))
conn.Close()
return
}
}
if err != nil {
conn.Write([]byte("sower dial " + serverAddr + " fail: " + err.Error()))
conn.Close()
return
}
defer rc.Close()
+23 -13
View File
@@ -7,6 +7,8 @@ import (
"strconv"
_http "github.com/wweir/sower/internal/http"
"github.com/wweir/sower/internal/socks5"
"github.com/wweir/sower/util"
"github.com/wweir/utils/log"
"golang.org/x/crypto/acme/autocert"
)
@@ -20,6 +22,8 @@ type head struct {
func StartClient(password, serverAddr, httpProxy, dnsServeIP string, forwards map[string]string) {
passwordData := []byte(password)
_, isSocks5 := socks5.IsSocks5Schema(serverAddr)
if httpProxy != "" {
go startHTTPProxy(httpProxy, serverAddr, passwordData)
}
@@ -39,15 +43,30 @@ func StartClient(password, serverAddr, httpProxy, dnsServeIP string, forwards ma
go func(conn net.Conn) {
defer conn.Close()
if isSocks5 {
teeConn := &util.TeeConn{Conn: conn}
teeConn.StartOrReset()
switch tgtType {
case _http.TGT_HTTP:
conn, host, port, err = _http.ParseHTTP(teeConn)
case _http.TGT_HTTPS:
conn, host, err = _http.ParseHTTPS(teeConn)
}
if err != nil {
log.Errorw("parse socks5 target", "err", err)
return
}
teeConn.Stop()
}
rc, err := tls.Dial("tcp", net.JoinHostPort(serverAddr, "443"), &tls.Config{})
rc, err := dial(serverAddr, passwordData, tgtType, host, port)
if err != nil {
log.Errorw("tls dial", "addr", net.JoinHostPort(serverAddr, "443"), "err", err)
log.Errorw("dial", "addr", serverAddr, "err", err)
return
}
defer rc.Close()
relay(conn, _http.NewTgtConn(rc, passwordData, tgtType, host, port))
relay(conn, rc)
}(conn)
}
}
@@ -59,16 +78,7 @@ func StartClient(password, serverAddr, httpProxy, dnsServeIP string, forwards ma
for from, to := range forwards {
go func(from, to string) {
host, portStr, err := net.SplitHostPort(to)
if err != nil {
log.Fatalw("parse port forward", "target", to, "err", err)
}
portNum, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
log.Fatalw("parse port forward", "target", to, "err", err)
}
port := uint16(portNum)
host, port := util.ParseHostPort(to, 0)
relayToRemote(_http.TGT_OTHER, from, host, port)
}(from, to)
}
+20
View File
@@ -1,13 +1,33 @@
package proxy
import (
"crypto/tls"
"io"
"net"
"sync"
"sync/atomic"
"time"
"github.com/wweir/sower/internal/http"
"github.com/wweir/sower/internal/socks5"
)
func dial(serverAddr string, password []byte, tgtType byte, domain string, port uint16) (net.Conn, error) {
if addr, ok := socks5.IsSocks5Schema(serverAddr); ok {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, err
}
return socks5.ToSocks5(conn, domain, port), nil
}
conn, err := tls.Dial("tcp", net.JoinHostPort(serverAddr, "443"), &tls.Config{})
if err != nil {
return nil, err
}
return http.NewTgtConn(conn, password, tgtType, domain, port), nil
}
func relay(conn1, conn2 net.Conn) {
wg := &sync.WaitGroup{}
exitFlag := new(int32)
+19
View File
@@ -0,0 +1,19 @@
package util
import (
"net"
"strconv"
)
func ParseHostPort(addr string, defaultPort uint16) (string, uint16) {
h, p, err := net.SplitHostPort(addr)
if err != nil {
if defaultPort == 0 {
panic("parse port fail with no default, addr: " + addr)
}
return addr, defaultPort
}
pNum, _ := strconv.ParseUint(p, 10, 16)
return h, uint16(pNum)
}