mirror of
https://github.com/wweir/sower.git
synced 2024-04-21 12:42:15 +00:00
Add Socks5 support
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user