mirror of https://github.com/cbeuw/Cloak
Refactor client config
This commit is contained in:
parent
402cfc9e25
commit
a163f066a6
|
|
@ -4,220 +4,16 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cbeuw/Cloak/internal/client"
|
"github.com/cbeuw/Cloak/internal/client"
|
||||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string
|
var version string
|
||||||
|
|
||||||
func makeSession(sta *client.State, isAdmin bool) *mux.Session {
|
|
||||||
log.Info("Attempting to start a new session")
|
|
||||||
if !isAdmin {
|
|
||||||
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
|
|
||||||
// sessionID is limited to its UID.
|
|
||||||
quad := make([]byte, 4)
|
|
||||||
util.CryptoRandRead(quad)
|
|
||||||
atomic.StoreUint32(&sta.SessionID, binary.BigEndian.Uint32(quad))
|
|
||||||
}
|
|
||||||
|
|
||||||
d := net.Dialer{Control: protector, KeepAlive: sta.KeepAlive}
|
|
||||||
connsCh := make(chan net.Conn, sta.NumConn)
|
|
||||||
var _sessionKey atomic.Value
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for i := 0; i < sta.NumConn; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
makeconn:
|
|
||||||
remoteConn, err := d.Dial("tcp", net.JoinHostPort(sta.RemoteHost, sta.RemotePort))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to establish new connections to remote: %v", err)
|
|
||||||
// TODO increase the interval if failed multiple times
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
goto makeconn
|
|
||||||
}
|
|
||||||
var sk []byte
|
|
||||||
remoteConn, sk, err = sta.Transport.PrepareConnection(sta, remoteConn)
|
|
||||||
if err != nil {
|
|
||||||
remoteConn.Close()
|
|
||||||
log.Errorf("Failed to prepare connection to remote: %v", err)
|
|
||||||
time.Sleep(time.Second * 3)
|
|
||||||
goto makeconn
|
|
||||||
}
|
|
||||||
_sessionKey.Store(sk)
|
|
||||||
connsCh <- remoteConn
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
log.Debug("All underlying connections established")
|
|
||||||
|
|
||||||
sessionKey := _sessionKey.Load().([]byte)
|
|
||||||
obfuscator, err := mux.GenerateObfs(sta.EncryptionMethod, sessionKey, sta.Transport.HasRecordLayer())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
seshConfig := &mux.SessionConfig{
|
|
||||||
Obfuscator: obfuscator,
|
|
||||||
Valve: nil,
|
|
||||||
UnitRead: sta.Transport.UnitReadFunc(),
|
|
||||||
Unordered: sta.Unordered,
|
|
||||||
}
|
|
||||||
sesh := mux.MakeSession(sta.SessionID, seshConfig)
|
|
||||||
|
|
||||||
for i := 0; i < sta.NumConn; i++ {
|
|
||||||
conn := <-connsCh
|
|
||||||
sesh.AddConnection(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Session %v established", sta.SessionID)
|
|
||||||
return sesh
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeUDP(sta *client.State, adminUID []byte) {
|
|
||||||
var sesh *mux.Session
|
|
||||||
localUDPAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(sta.LocalHost, sta.LocalPort))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
start:
|
|
||||||
localConn, err := net.ListenUDP("udp", localUDPAddr)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var otherEnd atomic.Value
|
|
||||||
data := make([]byte, 10240)
|
|
||||||
i, oe, err := localConn.ReadFromUDP(data)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to read first packet from proxy client: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
otherEnd.Store(oe)
|
|
||||||
|
|
||||||
if sesh == nil || sesh.IsClosed() {
|
|
||||||
sesh = makeSession(sta, adminUID != nil)
|
|
||||||
}
|
|
||||||
log.Debugf("proxy local address %v", otherEnd.Load().(*net.UDPAddr).String())
|
|
||||||
stream, err := sesh.OpenStream()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to open stream: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
//localConnWrite.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = stream.Write(data[:i])
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to write to stream: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
//localConnWrite.Close()
|
|
||||||
stream.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// stream to proxy
|
|
||||||
go func() {
|
|
||||||
buf := make([]byte, 16380)
|
|
||||||
for {
|
|
||||||
i, err := io.ReadAtLeast(stream, buf, 1)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
localConn.Close()
|
|
||||||
stream.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
_, err = localConn.WriteToUDP(buf[:i], otherEnd.Load().(*net.UDPAddr))
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
localConn.Close()
|
|
||||||
stream.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// proxy to stream
|
|
||||||
buf := make([]byte, 16380)
|
|
||||||
if sta.Timeout != 0 {
|
|
||||||
localConn.SetReadDeadline(time.Now().Add(sta.Timeout))
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if sta.Timeout != 0 {
|
|
||||||
localConn.SetReadDeadline(time.Now().Add(sta.Timeout))
|
|
||||||
}
|
|
||||||
i, oe, err := localConn.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
localConn.Close()
|
|
||||||
stream.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
otherEnd.Store(oe)
|
|
||||||
_, err = stream.Write(buf[:i])
|
|
||||||
if err != nil {
|
|
||||||
localConn.Close()
|
|
||||||
stream.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto start
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func routeTCP(sta *client.State, adminUID []byte) {
|
|
||||||
tcpListener, err := net.Listen("tcp", net.JoinHostPort(sta.LocalHost, sta.LocalPort))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var sesh *mux.Session
|
|
||||||
for {
|
|
||||||
localConn, err := tcpListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sesh == nil || sesh.IsClosed() {
|
|
||||||
sesh = makeSession(sta, adminUID != nil)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
data := make([]byte, 10240)
|
|
||||||
i, err := io.ReadAtLeast(localConn, data, 1)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to read first packet from proxy client: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stream, err := sesh.OpenStream()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to open stream: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = stream.Write(data[:i])
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to write to stream: %v", err)
|
|
||||||
localConn.Close()
|
|
||||||
stream.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go util.Pipe(localConn, stream, 0)
|
|
||||||
util.Pipe(stream, localConn, sta.Timeout)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Should be 127.0.0.1 to listen to a proxy client on this machine
|
// Should be 127.0.0.1 to listen to a proxy client on this machine
|
||||||
var localHost string
|
var localHost string
|
||||||
|
|
@ -234,16 +30,12 @@ func main() {
|
||||||
|
|
||||||
log_init()
|
log_init()
|
||||||
|
|
||||||
|
ssPluginMode := os.Getenv("SS_LOCAL_HOST") != ""
|
||||||
|
|
||||||
verbosity := flag.String("verbosity", "info", "verbosity level")
|
verbosity := flag.String("verbosity", "info", "verbosity level")
|
||||||
if os.Getenv("SS_LOCAL_HOST") != "" {
|
if ssPluginMode {
|
||||||
localHost = os.Getenv("SS_LOCAL_HOST")
|
|
||||||
localPort = os.Getenv("SS_LOCAL_PORT")
|
|
||||||
remoteHost = os.Getenv("SS_REMOTE_HOST")
|
|
||||||
remotePort = os.Getenv("SS_REMOTE_PORT")
|
|
||||||
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
config = os.Getenv("SS_PLUGIN_OPTIONS")
|
||||||
|
|
||||||
flag.Parse() // for verbosity only
|
flag.Parse() // for verbosity only
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
flag.StringVar(&localHost, "i", "127.0.0.1", "localHost: Cloak listens to proxy clients on this ip")
|
flag.StringVar(&localHost, "i", "127.0.0.1", "localHost: Cloak listens to proxy clients on this ip")
|
||||||
flag.StringVar(&localPort, "l", "1984", "localPort: Cloak listens to proxy clients on this port")
|
flag.StringVar(&localPort, "l", "1984", "localPort: Cloak listens to proxy clients on this port")
|
||||||
|
|
@ -255,6 +47,9 @@ func main() {
|
||||||
flag.StringVar(&b64AdminUID, "a", "", "adminUID: enter the adminUID to serve the admin api")
|
flag.StringVar(&b64AdminUID, "a", "", "adminUID: enter the adminUID to serve the admin api")
|
||||||
askVersion := flag.Bool("v", false, "Print the version number")
|
askVersion := flag.Bool("v", false, "Print the version number")
|
||||||
printUsage := flag.Bool("h", false, "Print this message")
|
printUsage := flag.Bool("h", false, "Print this message")
|
||||||
|
|
||||||
|
// commandline arguments overrides json
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *askVersion {
|
if *askVersion {
|
||||||
|
|
@ -276,36 +71,62 @@ func main() {
|
||||||
}
|
}
|
||||||
log.SetLevel(lvl)
|
log.SetLevel(lvl)
|
||||||
|
|
||||||
sta := &client.State{
|
rawConfig, err := client.ParseConfig(config)
|
||||||
LocalHost: localHost,
|
|
||||||
LocalPort: localPort,
|
|
||||||
RemotePort: remotePort,
|
|
||||||
Now: time.Now,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sta.ParseConfig(config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if proxyMethod != "" {
|
if ssPluginMode {
|
||||||
sta.ProxyMethod = proxyMethod
|
rawConfig.ProxyMethod = "shadowsocks"
|
||||||
|
// json takes precedence over environment variables
|
||||||
|
// i.e. if json field isn't empty, use that
|
||||||
|
if rawConfig.RemoteHost == "" {
|
||||||
|
rawConfig.RemoteHost = os.Getenv("SS_REMOTE_HOST")
|
||||||
|
}
|
||||||
|
if rawConfig.RemotePort == "" {
|
||||||
|
rawConfig.RemoteHost = os.Getenv("SS_REMOTE_PORT")
|
||||||
|
}
|
||||||
|
if rawConfig.LocalHost == "" {
|
||||||
|
rawConfig.LocalHost = os.Getenv("SS_LOCAL_HOST")
|
||||||
|
}
|
||||||
|
if rawConfig.LocalPort == "" {
|
||||||
|
rawConfig.LocalPort = os.Getenv("SS_LOCAL_PORT")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// commandline argument takes precedence over json
|
||||||
|
// if commandline argument is set, use commandline
|
||||||
|
flag.Visit(func(f *flag.Flag) {
|
||||||
|
// manually set ones
|
||||||
|
switch f.Name {
|
||||||
|
case "i":
|
||||||
|
rawConfig.LocalHost = localHost
|
||||||
|
case "l":
|
||||||
|
rawConfig.LocalPort = localPort
|
||||||
|
case "s":
|
||||||
|
rawConfig.RemoteHost = remoteHost
|
||||||
|
case "p":
|
||||||
|
rawConfig.RemotePort = remotePort
|
||||||
|
case "proxy":
|
||||||
|
rawConfig.ProxyMethod = proxyMethod
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// ones with default values
|
||||||
|
if rawConfig.LocalHost == "" {
|
||||||
|
rawConfig.LocalHost = localHost
|
||||||
|
}
|
||||||
|
if rawConfig.LocalPort == "" {
|
||||||
|
rawConfig.LocalPort = localPort
|
||||||
|
}
|
||||||
|
if rawConfig.RemotePort == "" {
|
||||||
|
rawConfig.RemotePort = remotePort
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if remoteHost != "" {
|
localConfig, remoteConfig, authInfo, err := rawConfig.SplitConfigs()
|
||||||
sta.RemoteHost = remoteHost
|
if err != nil {
|
||||||
}
|
log.Fatal(err)
|
||||||
|
|
||||||
if os.Getenv("SS_LOCAL_HOST") != "" {
|
|
||||||
sta.ProxyMethod = "shadowsocks"
|
|
||||||
}
|
|
||||||
|
|
||||||
if sta.LocalPort == "" {
|
|
||||||
log.Fatal("Must specify localPort")
|
|
||||||
}
|
|
||||||
if sta.RemoteHost == "" {
|
|
||||||
log.Fatal("Must specify remoteHost")
|
|
||||||
}
|
}
|
||||||
|
remoteConfig.Protector = protector
|
||||||
|
|
||||||
var adminUID []byte
|
var adminUID []byte
|
||||||
if b64AdminUID != "" {
|
if b64AdminUID != "" {
|
||||||
|
|
@ -315,26 +136,34 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var seshMaker func() *mux.Session
|
||||||
|
|
||||||
if adminUID != nil {
|
if adminUID != nil {
|
||||||
log.Infof("API base is %v", net.JoinHostPort(sta.LocalHost, sta.LocalPort))
|
log.Infof("API base is %v", localConfig.LocalAddr)
|
||||||
sta.SessionID = 0
|
authInfo.UID = adminUID
|
||||||
sta.UID = adminUID
|
remoteConfig.NumConn = 1
|
||||||
sta.NumConn = 1
|
|
||||||
|
seshMaker = func() *mux.Session {
|
||||||
|
return client.MakeSession(remoteConfig, authInfo, true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var network string
|
var network string
|
||||||
if udp {
|
if udp {
|
||||||
network = "UDP"
|
network = "UDP"
|
||||||
sta.Unordered = true
|
authInfo.Unordered = true
|
||||||
} else {
|
} else {
|
||||||
network = "TCP"
|
network = "TCP"
|
||||||
sta.Unordered = false
|
authInfo.Unordered = false
|
||||||
|
}
|
||||||
|
log.Infof("Listening on %v %v for %v client", network, localConfig.LocalAddr, authInfo.ProxyMethod)
|
||||||
|
seshMaker = func() *mux.Session {
|
||||||
|
return client.MakeSession(remoteConfig, authInfo, false)
|
||||||
}
|
}
|
||||||
log.Infof("Listening on %v %v for %v client", network, net.JoinHostPort(sta.LocalHost, sta.LocalPort), sta.ProxyMethod)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if udp {
|
if udp {
|
||||||
routeUDP(sta, adminUID)
|
client.RouteUDP(localConfig, seshMaker)
|
||||||
} else {
|
} else {
|
||||||
routeTCP(sta, adminUID)
|
client.RouteTCP(localConfig, seshMaker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
@ -45,7 +46,7 @@ func addExtRec(typ []byte, data []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshalAuthenticationInfo(ai authenticationPayload, serverName string) (ret clientHelloFields) {
|
func genStegClientHello(ai authenticationPayload, serverName string) (ret clientHelloFields) {
|
||||||
// random is marshalled ephemeral pub key 32 bytes
|
// random is marshalled ephemeral pub key 32 bytes
|
||||||
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
|
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
|
||||||
ret.random = ai.randPubKey[:]
|
ret.random = ai.randPubKey[:]
|
||||||
|
|
@ -56,7 +57,7 @@ func unmarshalAuthenticationInfo(ai authenticationPayload, serverName string) (r
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectTLS struct {
|
type DirectTLS struct {
|
||||||
Transport
|
browser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (DirectTLS) HasRecordLayer() bool { return true }
|
func (DirectTLS) HasRecordLayer() bool { return true }
|
||||||
|
|
@ -64,10 +65,10 @@ func (DirectTLS) UnitReadFunc() func(net.Conn, []byte) (int, error) { return uti
|
||||||
|
|
||||||
// PrepareConnection handles the TLS handshake for a given conn and returns the sessionKey
|
// PrepareConnection handles the TLS handshake for a given conn and returns the sessionKey
|
||||||
// if the server proceed with Cloak authentication
|
// if the server proceed with Cloak authentication
|
||||||
func (DirectTLS) PrepareConnection(sta *State, conn net.Conn) (preparedConn net.Conn, sessionKey []byte, err error) {
|
func (tls DirectTLS) PrepareConnection(authInfo *authInfo, conn net.Conn) (preparedConn net.Conn, sessionKey []byte, err error) {
|
||||||
preparedConn = conn
|
preparedConn = conn
|
||||||
payload, sharedSecret := makeAuthenticationPayload(sta, rand.Reader)
|
payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now())
|
||||||
chOnly := sta.browser.composeClientHello(unmarshalAuthenticationInfo(payload, sta.ServerName))
|
chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain))
|
||||||
chWithRecordLayer := util.AddRecordLayer(chOnly, []byte{0x16}, []byte{0x03, 0x01})
|
chWithRecordLayer := util.AddRecordLayer(chOnly, []byte{0x16}, []byte{0x03, 0x01})
|
||||||
_, err = preparedConn.Write(chWithRecordLayer)
|
_, err = preparedConn.Write(chWithRecordLayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"github.com/cbeuw/Cloak/internal/ecdh"
|
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
"io"
|
"io"
|
||||||
"sync/atomic"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -17,9 +18,19 @@ type authenticationPayload struct {
|
||||||
ciphertextWithTag [64]byte
|
ciphertextWithTag [64]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type authInfo struct {
|
||||||
|
UID []byte
|
||||||
|
SessionId uint32
|
||||||
|
ProxyMethod string
|
||||||
|
EncryptionMethod byte
|
||||||
|
Unordered bool
|
||||||
|
ServerPubKey crypto.PublicKey
|
||||||
|
MockDomain string
|
||||||
|
}
|
||||||
|
|
||||||
// makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and
|
// makeAuthenticationPayload generates the ephemeral key pair, calculates the shared secret, and then compose and
|
||||||
// encrypt the authenticationPayload
|
// encrypt the authenticationPayload
|
||||||
func makeAuthenticationPayload(sta *State, randReader io.Reader) (ret authenticationPayload, sharedSecret []byte) {
|
func makeAuthenticationPayload(authInfo *authInfo, randReader io.Reader, time time.Time) (ret authenticationPayload, sharedSecret []byte) {
|
||||||
/*
|
/*
|
||||||
Authentication data:
|
Authentication data:
|
||||||
+----------+----------------+---------------------+-------------+--------------+--------+------------+
|
+----------+----------------+---------------------+-------------+--------------+--------+------------+
|
||||||
|
|
@ -32,17 +43,17 @@ func makeAuthenticationPayload(sta *State, randReader io.Reader) (ret authentica
|
||||||
copy(ret.randPubKey[:], ecdh.Marshal(ephPub))
|
copy(ret.randPubKey[:], ecdh.Marshal(ephPub))
|
||||||
|
|
||||||
plaintext := make([]byte, 48)
|
plaintext := make([]byte, 48)
|
||||||
copy(plaintext, sta.UID)
|
copy(plaintext, authInfo.UID)
|
||||||
copy(plaintext[16:28], sta.ProxyMethod)
|
copy(plaintext[16:28], authInfo.ProxyMethod)
|
||||||
plaintext[28] = sta.EncryptionMethod
|
plaintext[28] = authInfo.EncryptionMethod
|
||||||
binary.BigEndian.PutUint64(plaintext[29:37], uint64(sta.Now().Unix()))
|
binary.BigEndian.PutUint64(plaintext[29:37], uint64(time.Unix()))
|
||||||
binary.BigEndian.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID))
|
binary.BigEndian.PutUint32(plaintext[37:41], authInfo.SessionId)
|
||||||
|
|
||||||
if sta.Unordered {
|
if authInfo.Unordered {
|
||||||
plaintext[41] |= UNORDERED_FLAG
|
plaintext[41] |= UNORDERED_FLAG
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
sharedSecret = ecdh.GenerateSharedSecret(ephPv, authInfo.ServerPubKey)
|
||||||
ciphertextWithTag, _ := util.AESGCMEncrypt(ret.randPubKey[:12], sharedSecret, plaintext)
|
ciphertextWithTag, _ := util.AESGCMEncrypt(ret.randPubKey[:12], sharedSecret, plaintext)
|
||||||
copy(ret.ciphertextWithTag[:], ciphertextWithTag[:])
|
copy(ret.ciphertextWithTag[:], ciphertextWithTag[:])
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||||
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type remoteConnConfig struct {
|
||||||
|
NumConn int
|
||||||
|
KeepAlive time.Duration
|
||||||
|
Protector func(string, string, syscall.RawConn) error
|
||||||
|
RemoteAddr string
|
||||||
|
Transport Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeSession(connConfig *remoteConnConfig, authInfo *authInfo, isAdmin bool) *mux.Session {
|
||||||
|
log.Info("Attempting to start a new session")
|
||||||
|
if !isAdmin {
|
||||||
|
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
|
||||||
|
// sessionID is limited to its UID.
|
||||||
|
quad := make([]byte, 4)
|
||||||
|
util.CryptoRandRead(quad)
|
||||||
|
authInfo.SessionId = binary.BigEndian.Uint32(quad)
|
||||||
|
} else {
|
||||||
|
authInfo.SessionId = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
d := net.Dialer{Control: connConfig.Protector, KeepAlive: connConfig.KeepAlive}
|
||||||
|
connsCh := make(chan net.Conn, connConfig.NumConn)
|
||||||
|
var _sessionKey atomic.Value
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < connConfig.NumConn; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
makeconn:
|
||||||
|
remoteConn, err := d.Dial("tcp", connConfig.RemoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to establish new connections to remote: %v", err)
|
||||||
|
// TODO increase the interval if failed multiple times
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
goto makeconn
|
||||||
|
}
|
||||||
|
var sk []byte
|
||||||
|
remoteConn, sk, err = connConfig.Transport.PrepareConnection(authInfo, remoteConn)
|
||||||
|
if err != nil {
|
||||||
|
remoteConn.Close()
|
||||||
|
log.Errorf("Failed to prepare connection to remote: %v", err)
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
goto makeconn
|
||||||
|
}
|
||||||
|
_sessionKey.Store(sk)
|
||||||
|
connsCh <- remoteConn
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
log.Debug("All underlying connections established")
|
||||||
|
|
||||||
|
sessionKey := _sessionKey.Load().([]byte)
|
||||||
|
obfuscator, err := mux.GenerateObfs(authInfo.EncryptionMethod, sessionKey, connConfig.Transport.HasRecordLayer())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
seshConfig := &mux.SessionConfig{
|
||||||
|
Obfuscator: obfuscator,
|
||||||
|
Valve: nil,
|
||||||
|
UnitRead: connConfig.Transport.UnitReadFunc(),
|
||||||
|
Unordered: authInfo.Unordered,
|
||||||
|
}
|
||||||
|
sesh := mux.MakeSession(authInfo.SessionId, seshConfig)
|
||||||
|
|
||||||
|
for i := 0; i < connConfig.NumConn; i++ {
|
||||||
|
conn := <-connsCh
|
||||||
|
sesh.AddConnection(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Session %v established", authInfo.SessionId)
|
||||||
|
return sesh
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||||
|
"github.com/cbeuw/Cloak/internal/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RouteUDP(localConfig *localConnConfig, newSeshFunc func() *mux.Session) {
|
||||||
|
var sesh *mux.Session
|
||||||
|
localUDPAddr, err := net.ResolveUDPAddr("udp", localConfig.LocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
start:
|
||||||
|
localConn, err := net.ListenUDP("udp", localUDPAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
var otherEnd atomic.Value
|
||||||
|
data := make([]byte, 10240)
|
||||||
|
i, oe, err := localConn.ReadFromUDP(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to read first packet from proxy client: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
otherEnd.Store(oe)
|
||||||
|
|
||||||
|
if sesh == nil || sesh.IsClosed() {
|
||||||
|
sesh = newSeshFunc()
|
||||||
|
}
|
||||||
|
log.Debugf("proxy local address %v", otherEnd.Load().(*net.UDPAddr).String())
|
||||||
|
stream, err := sesh.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to open stream: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
//localConnWrite.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = stream.Write(data[:i])
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to write to stream: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
//localConnWrite.Close()
|
||||||
|
stream.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream to proxy
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 16380)
|
||||||
|
for {
|
||||||
|
i, err := io.ReadAtLeast(stream, buf, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
localConn.Close()
|
||||||
|
stream.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = localConn.WriteToUDP(buf[:i], otherEnd.Load().(*net.UDPAddr))
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
localConn.Close()
|
||||||
|
stream.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// proxy to stream
|
||||||
|
buf := make([]byte, 16380)
|
||||||
|
if localConfig.Timeout != 0 {
|
||||||
|
localConn.SetReadDeadline(time.Now().Add(localConfig.Timeout))
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if localConfig.Timeout != 0 {
|
||||||
|
localConn.SetReadDeadline(time.Now().Add(localConfig.Timeout))
|
||||||
|
}
|
||||||
|
i, oe, err := localConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
localConn.Close()
|
||||||
|
stream.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
otherEnd.Store(oe)
|
||||||
|
_, err = stream.Write(buf[:i])
|
||||||
|
if err != nil {
|
||||||
|
localConn.Close()
|
||||||
|
stream.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto start
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func RouteTCP(localConfig *localConnConfig, newSeshFunc func() *mux.Session) {
|
||||||
|
tcpListener, err := net.Listen("tcp", localConfig.LocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
var sesh *mux.Session
|
||||||
|
for {
|
||||||
|
localConn, err := tcpListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if sesh == nil || sesh.IsClosed() {
|
||||||
|
sesh = newSeshFunc()
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
data := make([]byte, 10240)
|
||||||
|
i, err := io.ReadAtLeast(localConn, data, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to read first packet from proxy client: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream, err := sesh.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to open stream: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = stream.Write(data[:i])
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to write to stream: %v", err)
|
||||||
|
localConn.Close()
|
||||||
|
stream.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go util.Pipe(localConn, stream, 0)
|
||||||
|
util.Pipe(stream, localConn, localConfig.Timeout)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,54 +13,51 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawConfig represents the fields in the config json file
|
// rawConfig represents the fields in the config json file
|
||||||
|
// nullable means if it's empty, a default value will be chosen in SplitConfigs
|
||||||
|
// jsonOptional means if the json's empty, its value will be set from environment variables or commandline args
|
||||||
|
// but it mustn't be empty when SplitConfigs is called
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
ServerName string
|
ServerName string
|
||||||
ProxyMethod string
|
ProxyMethod string
|
||||||
EncryptionMethod string
|
EncryptionMethod string
|
||||||
UID []byte
|
UID []byte
|
||||||
PublicKey []byte
|
PublicKey []byte
|
||||||
BrowserSig string
|
|
||||||
Transport string
|
|
||||||
NumConn int
|
NumConn int
|
||||||
StreamTimeout int
|
LocalHost string // jsonOptional
|
||||||
KeepAlive int
|
LocalPort string // jsonOptional
|
||||||
RemoteHost string
|
RemoteHost string // jsonOptional
|
||||||
RemotePort int
|
RemotePort string // jsonOptional
|
||||||
|
//TODO: udp
|
||||||
|
|
||||||
|
// defaults set in SplitConfigs
|
||||||
|
BrowserSig string // nullable
|
||||||
|
Transport string // nullable
|
||||||
|
StreamTimeout int // nullable
|
||||||
|
KeepAlive int // nullable
|
||||||
}
|
}
|
||||||
|
|
||||||
// State stores the parsed configuration fields
|
type localConnConfig struct {
|
||||||
type State struct {
|
LocalAddr string
|
||||||
LocalHost string
|
Timeout time.Duration
|
||||||
LocalPort string
|
|
||||||
RemoteHost string
|
|
||||||
RemotePort string
|
|
||||||
Unordered bool
|
|
||||||
|
|
||||||
Transport Transport
|
|
||||||
|
|
||||||
SessionID uint32
|
|
||||||
UID []byte
|
|
||||||
|
|
||||||
staticPub crypto.PublicKey
|
|
||||||
Now func() time.Time // for easier testing
|
|
||||||
browser browser
|
|
||||||
|
|
||||||
ProxyMethod string
|
|
||||||
EncryptionMethod byte
|
|
||||||
ServerName string
|
|
||||||
NumConn int
|
|
||||||
Timeout time.Duration
|
|
||||||
KeepAlive time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// semi-colon separated value. This is for Android plugin options
|
// semi-colon separated value. This is for Android plugin options
|
||||||
func ssvToJson(ssv string) (ret []byte) {
|
func ssvToJson(ssv string) (ret []byte) {
|
||||||
|
elem := func(val string, lst []string) bool {
|
||||||
|
for _, v := range lst {
|
||||||
|
if val == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
unescape := func(s string) string {
|
unescape := func(s string) string {
|
||||||
r := strings.Replace(s, `\\`, `\`, -1)
|
r := strings.Replace(s, `\\`, `\`, -1)
|
||||||
r = strings.Replace(r, `\=`, `=`, -1)
|
r = strings.Replace(r, `\=`, `=`, -1)
|
||||||
r = strings.Replace(r, `\;`, `;`, -1)
|
r = strings.Replace(r, `\;`, `;`, -1)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive"}
|
||||||
lines := strings.Split(unescape(ssv), ";")
|
lines := strings.Split(unescape(ssv), ";")
|
||||||
ret = []byte("{")
|
ret = []byte("{")
|
||||||
for _, ln := range lines {
|
for _, ln := range lines {
|
||||||
|
|
@ -73,7 +69,7 @@ func ssvToJson(ssv string) (ret []byte) {
|
||||||
value := sp[1]
|
value := sp[1]
|
||||||
// JSON doesn't like quotation marks around int and bool
|
// JSON doesn't like quotation marks around int and bool
|
||||||
// This is extremely ugly but it's still better than writing a tokeniser
|
// This is extremely ugly but it's still better than writing a tokeniser
|
||||||
if key == "NumConn" || key == "Unordered" || key == "StreamTimeout" {
|
if elem(key, unquoted) {
|
||||||
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
||||||
} else {
|
} else {
|
||||||
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
||||||
|
|
@ -84,8 +80,7 @@ func ssvToJson(ssv string) (ret []byte) {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseConfig parses the config (either a path to json or Android config) into a State variable
|
func ParseConfig(conf string) (raw *rawConfig, err error) {
|
||||||
func (sta *State) ParseConfig(conf string) (err error) {
|
|
||||||
var content []byte
|
var content []byte
|
||||||
// Checking if it's a path to json or a ssv string
|
// Checking if it's a path to json or a ssv string
|
||||||
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
||||||
|
|
@ -93,75 +88,116 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
} else {
|
} else {
|
||||||
content, err = ioutil.ReadFile(conf)
|
content, err = ioutil.ReadFile(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var preParse rawConfig
|
|
||||||
err = json.Unmarshal(content, &preParse)
|
raw = new(rawConfig)
|
||||||
|
err = json.Unmarshal(content, &raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
return
|
||||||
switch strings.ToLower(preParse.EncryptionMethod) {
|
}
|
||||||
case "plain":
|
|
||||||
sta.EncryptionMethod = mux.E_METHOD_PLAIN
|
func (raw *rawConfig) SplitConfigs() (local *localConnConfig, remote *remoteConnConfig, auth *authInfo, err error) {
|
||||||
case "aes-gcm":
|
nullErr := func(field string) (local *localConnConfig, remote *remoteConnConfig, auth *authInfo, err error) {
|
||||||
sta.EncryptionMethod = mux.E_METHOD_AES_GCM
|
err = fmt.Errorf("%v cannot be empty", field)
|
||||||
case "chacha20-poly1305":
|
return
|
||||||
sta.EncryptionMethod = mux.E_METHOD_CHACHA20_POLY1305
|
}
|
||||||
default:
|
|
||||||
return errors.New("Unknown encryption method")
|
auth = new(authInfo)
|
||||||
}
|
if raw.ServerName == "" {
|
||||||
|
return nullErr("ServerName")
|
||||||
switch strings.ToLower(preParse.BrowserSig) {
|
}
|
||||||
case "chrome":
|
auth.MockDomain = raw.ServerName
|
||||||
sta.browser = &Chrome{}
|
if raw.ProxyMethod == "" {
|
||||||
case "firefox":
|
return nullErr("ServerName")
|
||||||
sta.browser = &Firefox{}
|
}
|
||||||
default:
|
auth.ProxyMethod = raw.ProxyMethod
|
||||||
return errors.New("unsupported browser signature")
|
if len(raw.UID) == 0 {
|
||||||
}
|
return nullErr("UID")
|
||||||
|
}
|
||||||
switch strings.ToLower(preParse.Transport) {
|
auth.UID = raw.UID
|
||||||
case "direct":
|
|
||||||
sta.Transport = DirectTLS{}
|
// static public key
|
||||||
case "cdn":
|
if len(raw.PublicKey) == 0 {
|
||||||
sta.Transport = WSOverTLS{}
|
return nullErr("PublicKey")
|
||||||
default:
|
}
|
||||||
sta.Transport = DirectTLS{}
|
pub, ok := ecdh.Unmarshal(raw.PublicKey)
|
||||||
}
|
if !ok {
|
||||||
|
err = fmt.Errorf("failed to unmarshal Public key")
|
||||||
sta.RemoteHost = preParse.RemoteHost
|
return
|
||||||
sta.ProxyMethod = preParse.ProxyMethod
|
}
|
||||||
sta.ServerName = preParse.ServerName
|
auth.ServerPubKey = pub
|
||||||
sta.NumConn = preParse.NumConn
|
|
||||||
if preParse.StreamTimeout == 0 {
|
// Encryption method
|
||||||
sta.Timeout = 300 * time.Second
|
switch strings.ToLower(raw.EncryptionMethod) {
|
||||||
} else {
|
case "plain":
|
||||||
sta.Timeout = time.Duration(preParse.StreamTimeout) * time.Second
|
auth.EncryptionMethod = mux.E_METHOD_PLAIN
|
||||||
}
|
case "aes-gcm":
|
||||||
if preParse.KeepAlive <= 0 {
|
auth.EncryptionMethod = mux.E_METHOD_AES_GCM
|
||||||
sta.KeepAlive = -1
|
case "chacha20-poly1305":
|
||||||
} else {
|
auth.EncryptionMethod = mux.E_METHOD_CHACHA20_POLY1305
|
||||||
sta.KeepAlive = time.Duration(preParse.KeepAlive) * time.Second
|
default:
|
||||||
}
|
err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod)
|
||||||
sta.UID = preParse.UID
|
return
|
||||||
|
}
|
||||||
pub, ok := ecdh.Unmarshal(preParse.PublicKey)
|
|
||||||
if !ok {
|
remote = new(remoteConnConfig)
|
||||||
return errors.New("Failed to unmarshal Public key")
|
if raw.RemoteHost == "" {
|
||||||
}
|
return nullErr("RemoteHost")
|
||||||
sta.staticPub = pub
|
}
|
||||||
|
if raw.RemotePort == "" {
|
||||||
// OPTIONAL: set RemotePort via JSON
|
return nullErr("RemotePort")
|
||||||
// if RemotePort is specified in the JSON we overwrite sta.RemotePort
|
}
|
||||||
// if not, don't do anything, since sta.RemotePort is already initialised in ck-client.go
|
remote.RemoteAddr = net.JoinHostPort(raw.RemoteHost, raw.RemotePort)
|
||||||
if preParse.RemotePort != 0 {
|
if raw.NumConn == 0 {
|
||||||
// basic validity check
|
return nullErr("NumConn")
|
||||||
if preParse.RemotePort >= 1 && preParse.RemotePort <= 65535 {
|
}
|
||||||
sta.RemotePort = strconv.Itoa(preParse.RemotePort)
|
remote.NumConn = raw.NumConn
|
||||||
}
|
|
||||||
}
|
// Transport and (if TLS mode), browser
|
||||||
|
switch strings.ToLower(raw.Transport) {
|
||||||
return nil
|
case "cdn":
|
||||||
|
remote.Transport = WSOverTLS{remote.RemoteAddr}
|
||||||
|
case "direct":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
var browser browser
|
||||||
|
switch strings.ToLower(raw.BrowserSig) {
|
||||||
|
case "firefox":
|
||||||
|
browser = &Firefox{}
|
||||||
|
case "chrome":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
browser = &Chrome{}
|
||||||
|
}
|
||||||
|
remote.Transport = DirectTLS{browser}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeepAlive
|
||||||
|
if raw.KeepAlive <= 0 {
|
||||||
|
remote.KeepAlive = -1
|
||||||
|
} else {
|
||||||
|
remote.KeepAlive = remote.KeepAlive * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
local = new(localConnConfig)
|
||||||
|
|
||||||
|
if raw.LocalHost == "" {
|
||||||
|
return nullErr("LocalHost")
|
||||||
|
}
|
||||||
|
if raw.LocalPort == "" {
|
||||||
|
return nullErr("LocalPort")
|
||||||
|
}
|
||||||
|
local.LocalAddr = net.JoinHostPort(raw.LocalHost, raw.LocalPort)
|
||||||
|
// stream no write timeout
|
||||||
|
if raw.StreamTimeout == 0 {
|
||||||
|
local.Timeout = 300 * time.Second
|
||||||
|
} else {
|
||||||
|
local.Timeout = time.Duration(raw.StreamTimeout) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package client
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
type Transport interface {
|
type Transport interface {
|
||||||
PrepareConnection(*State, net.Conn) (net.Conn, []byte, error)
|
PrepareConnection(*authInfo, net.Conn) (net.Conn, []byte, error)
|
||||||
HasRecordLayer() bool
|
HasRecordLayer() bool
|
||||||
UnitReadFunc() func(net.Conn, []byte) (int, error)
|
UnitReadFunc() func(net.Conn, []byte) (int, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,35 +10,36 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
utls "github.com/refraction-networking/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WSOverTLS struct {
|
type WSOverTLS struct {
|
||||||
Transport
|
cdnDomainPort string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (WSOverTLS) HasRecordLayer() bool { return false }
|
func (WSOverTLS) HasRecordLayer() bool { return false }
|
||||||
func (WSOverTLS) UnitReadFunc() func(net.Conn, []byte) (int, error) { return util.ReadWebSocket }
|
func (WSOverTLS) UnitReadFunc() func(net.Conn, []byte) (int, error) { return util.ReadWebSocket }
|
||||||
|
|
||||||
func (WSOverTLS) PrepareConnection(sta *State, conn net.Conn) (preparedConn net.Conn, sessionKey []byte, err error) {
|
func (ws WSOverTLS) PrepareConnection(authInfo *authInfo, cdnConn net.Conn) (preparedConn net.Conn, sessionKey []byte, err error) {
|
||||||
utlsConfig := &utls.Config{
|
utlsConfig := &utls.Config{
|
||||||
ServerName: sta.ServerName,
|
ServerName: authInfo.MockDomain,
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
uconn := utls.UClient(conn, utlsConfig, utls.HelloChrome_Auto)
|
uconn := utls.UClient(cdnConn, utlsConfig, utls.HelloChrome_Auto)
|
||||||
err = uconn.Handshake()
|
err = uconn.Handshake()
|
||||||
preparedConn = uconn
|
preparedConn = uconn
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse("ws://" + sta.RemoteHost + ":" + sta.RemotePort) //TODO IPv6
|
u, err := url.Parse("ws://" + ws.cdnDomainPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return preparedConn, nil, fmt.Errorf("failed to parse ws url: %v", err)
|
return preparedConn, nil, fmt.Errorf("failed to parse ws url: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, sharedSecret := makeAuthenticationPayload(sta, rand.Reader)
|
payload, sharedSecret := makeAuthenticationPayload(authInfo, rand.Reader, time.Now())
|
||||||
header := http.Header{}
|
header := http.Header{}
|
||||||
header.Add("hidden", base64.StdEncoding.EncodeToString(append(payload.randPubKey[:], payload.ciphertextWithTag[:]...)))
|
header.Add("hidden", base64.StdEncoding.EncodeToString(append(payload.randPubKey[:], payload.ciphertextWithTag[:]...)))
|
||||||
c, _, err := websocket.NewClient(preparedConn, u, header, 16480, 16480)
|
c, _, err := websocket.NewClient(preparedConn, u, header, 16480, 16480)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue