Separate Client out into its own library package

ptspec
Andy Wang 2 years ago
parent fe78c7b713
commit 2aa49ce543
No known key found for this signature in database
GPG Key ID: 181B49F9F38F3374

@ -8,12 +8,12 @@ import (
"encoding/binary"
"flag"
"fmt"
"github.com/cbeuw/Cloak/internal/cli_client"
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client"
"net"
"os"
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/internal/client"
log "github.com/sirupsen/logrus"
)
@ -83,7 +83,7 @@ func main() {
}
log.SetLevel(lvl)
rawConfig, err := client.ParseConfig(config)
rawConfig, err := cli_client.ParseConfig(config)
if err != nil {
log.Fatal(err)
}
@ -138,7 +138,7 @@ func main() {
}
}
localConfig, remoteConfig, authInfo, err := rawConfig.ProcessRawConfig(common.RealWorldState)
localConfig, remoteConfig, authInfo, err := rawConfig.ProcessCLIConfig(common.RealWorldState)
if err != nil {
log.Fatal(err)
}
@ -194,12 +194,12 @@ func main() {
return net.ListenUDP("udp", udpAddr)
}
client.RouteUDP(acceptor, localConfig.Timeout, remoteConfig.Singleplex, seshMaker)
cli_client.RouteUDP(acceptor, localConfig.Timeout, remoteConfig.Singleplex, seshMaker)
} else {
listener, err := net.Listen("tcp", localConfig.LocalAddr)
if err != nil {
log.Fatal(err)
}
client.RouteTCP(listener, localConfig.Timeout, remoteConfig.Singleplex, seshMaker)
cli_client.RouteTCP(listener, localConfig.Timeout, remoteConfig.Singleplex, seshMaker)
}
}

@ -0,0 +1,147 @@
package cli_client
import (
"encoding/json"
"fmt"
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net"
"strings"
"time"
)
type CLIConfig struct {
client.RawConfig
// LocalHost is the hostname or IP address to listen for incoming proxy client connections
LocalHost string // jsonOptional
// LocalPort is the port to listen for incomig proxy client connections
LocalPort string // jsonOptional
// AlternativeNames is a list of ServerName Cloak may randomly pick from for different sessions
AlternativeNames []string
// StreamTimeout is the duration, in seconds, for an incoming connection to be automatically closed after the last
// piece of incoming data .
// Defaults to 300
StreamTimeout int
}
// semi-colon separated value. This is for Android plugin options
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 {
r := strings.Replace(s, `\\`, `\`, -1)
r = strings.Replace(r, `\=`, `=`, -1)
r = strings.Replace(r, `\;`, `;`, -1)
return r
}
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
lines := strings.Split(unescape(ssv), ";")
ret = []byte("{")
for _, ln := range lines {
if ln == "" {
break
}
sp := strings.SplitN(ln, "=", 2)
if len(sp) < 2 {
log.Errorf("Malformed config option: %v", ln)
continue
}
key := sp[0]
value := sp[1]
if strings.HasPrefix(key, "AlternativeNames") {
switch strings.Contains(value, ",") {
case true:
domains := strings.Split(value, ",")
for index, domain := range domains {
domains[index] = `"` + domain + `"`
}
value = strings.Join(domains, ",")
ret = append(ret, []byte(`"`+key+`":[`+value+`],`)...)
case false:
ret = append(ret, []byte(`"`+key+`":["`+value+`"],`)...)
}
continue
}
// JSON doesn't like quotation marks around int and bool
// This is extremely ugly but it's still better than writing a tokeniser
if elem(key, unquoted) {
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
} else {
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
}
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
func ParseConfig(conf string) (raw *CLIConfig, err error) {
var content []byte
// Checking if it's a path to json or a ssv string
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
return
}
}
raw = new(CLIConfig)
err = json.Unmarshal(content, &raw)
if err != nil {
return
}
return
}
type LocalConnConfig struct {
LocalAddr string
Timeout time.Duration
MockDomainList []string
}
func (raw *CLIConfig) ProcessCLIConfig(worldState common.WorldState) (local LocalConnConfig, remote client.RemoteConnConfig, auth client.AuthInfo, err error) {
remote, auth, err = raw.RawConfig.ProcessRawConfig(worldState)
if err != nil {
return
}
var filteredAlternativeNames []string
for _, alternativeName := range raw.AlternativeNames {
if len(alternativeName) > 0 {
filteredAlternativeNames = append(filteredAlternativeNames, alternativeName)
}
}
raw.AlternativeNames = filteredAlternativeNames
local.MockDomainList = raw.AlternativeNames
local.MockDomainList = append(local.MockDomainList, auth.MockDomain)
if raw.LocalHost == "" {
err = fmt.Errorf("LocalHost cannot be empty")
return
}
if raw.LocalPort == "" {
err = fmt.Errorf("LocalPort cannot be empty")
return
}
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
}

@ -1,4 +1,4 @@
package client
package cli_client
import (
"io/ioutil"

@ -1,18 +1,18 @@
package client
package cli_client
import (
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client"
"io"
"net"
"sync"
"time"
"github.com/cbeuw/Cloak/internal/common"
log "github.com/sirupsen/logrus"
)
func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration, singleplex bool, newSeshFunc func() *CloakClient) {
var cloakClient *CloakClient
func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration, singleplex bool, newSeshFunc func() *client.CloakClient) {
var cloakClient *client.CloakClient
localConn, err := bindFunc()
if err != nil {
log.Fatal(err)
@ -94,8 +94,8 @@ func RouteUDP(bindFunc func() (*net.UDPConn, error), streamTimeout time.Duration
}
}
func RouteTCP(listener net.Listener, streamTimeout time.Duration, singleplex bool, newSeshFunc func() *CloakClient) {
var cloakClient *CloakClient
func RouteTCP(listener net.Listener, streamTimeout time.Duration, singleplex bool, newSeshFunc func() *client.CloakClient) {
var cloakClient *client.CloakClient
for {
localConn, err := listener.Accept()
if err != nil {
@ -105,7 +105,7 @@ func RouteTCP(listener net.Listener, streamTimeout time.Duration, singleplex boo
if !singleplex && (cloakClient == nil || cloakClient.IsClosed()) {
cloakClient = newSeshFunc()
}
go func(sesh *CloakClient, localConn net.Conn, timeout time.Duration) {
go func(sesh *client.CloakClient, localConn net.Conn, timeout time.Duration) {
if singleplex {
sesh = newSeshFunc()
}

@ -5,6 +5,13 @@ import (
"encoding/base64"
"encoding/binary"
"fmt"
"github.com/cbeuw/Cloak/internal/cli_client"
"github.com/cbeuw/Cloak/internal/common"
mux "github.com/cbeuw/Cloak/internal/multiplex"
"github.com/cbeuw/Cloak/internal/server"
"github.com/cbeuw/Cloak/libcloak/client"
"github.com/cbeuw/connutil"
"github.com/stretchr/testify/assert"
"io"
"math/rand"
"net"
@ -12,13 +19,6 @@ import (
"testing"
"time"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/common"
mux "github.com/cbeuw/Cloak/internal/multiplex"
"github.com/cbeuw/Cloak/internal/server"
"github.com/cbeuw/connutil"
"github.com/stretchr/testify/assert"
log "github.com/sirupsen/logrus"
)
@ -76,6 +76,8 @@ func serveUDPEcho(listener *connutil.PipeListener) {
var bypassUID = [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
var publicKey, _ = base64.StdEncoding.DecodeString("7f7TuKrs264VNSgMno8PkDlyhGhVuOSR8JHLE6H4Ljc=")
var privateKey, _ = base64.StdEncoding.DecodeString("SMWeC6VuZF8S/id65VuFQFlfa7hTEJBpL6wWhqPP100=")
var four = 4
var zero = 0
var basicUDPConfig = client.RawConfig{
ServerName: "www.example.com",
@ -83,13 +85,11 @@ var basicUDPConfig = client.RawConfig{
EncryptionMethod: "plain",
UID: bypassUID[:],
PublicKey: publicKey,
NumConn: 4,
NumConn: &four,
UDP: true,
Transport: "direct",
RemoteHost: "fake.com",
RemotePort: "9999",
LocalHost: "127.0.0.1",
LocalPort: "9999",
}
var basicTCPConfig = client.RawConfig{
@ -98,13 +98,11 @@ var basicTCPConfig = client.RawConfig{
EncryptionMethod: "plain",
UID: bypassUID[:],
PublicKey: publicKey,
NumConn: 4,
NumConn: &four,
UDP: false,
Transport: "direct",
RemoteHost: "fake.com",
RemotePort: "9999",
LocalHost: "127.0.0.1",
LocalPort: "9999",
BrowserSig: "firefox",
}
@ -114,22 +112,20 @@ var singleplexTCPConfig = client.RawConfig{
EncryptionMethod: "plain",
UID: bypassUID[:],
PublicKey: publicKey,
NumConn: 0,
NumConn: &zero,
UDP: false,
Transport: "direct",
RemoteHost: "fake.com",
RemotePort: "9999",
LocalHost: "127.0.0.1",
LocalPort: "9999",
BrowserSig: "safari",
BrowserSig: "chrome",
}
func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) (client.LocalConnConfig, client.RemoteConnConfig, client.AuthInfo) {
lcl, rmt, auth, err := rawConfig.ProcessRawConfig(state)
func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) (client.RemoteConnConfig, client.AuthInfo) {
rmt, auth, err := rawConfig.ProcessRawConfig(state)
if err != nil {
log.Fatal(err)
}
return lcl, rmt, auth
return rmt, auth
}
func basicServerState(ws common.WorldState) *server.State {
@ -161,7 +157,7 @@ func (m *mockUDPDialer) Dial(network, address string) (net.Conn, error) {
return net.DialUDP("udp", nil, m.raddr)
}
func establishSession(lcc client.LocalConnConfig, rcc client.RemoteConnConfig, ai client.AuthInfo, serverState *server.State) (common.Dialer, *connutil.PipeListener, common.Dialer, net.Listener, error) {
func establishSession(rcc client.RemoteConnConfig, ai client.AuthInfo, serverState *server.State) (common.Dialer, *connutil.PipeListener, common.Dialer, net.Listener, error) {
// redirecting web server
// ^
// |
@ -202,12 +198,12 @@ func establishSession(lcc client.LocalConnConfig, rcc client.RemoteConnConfig, a
addrCh <- conn.LocalAddr().(*net.UDPAddr)
return conn, err
}
go client.RouteUDP(acceptor, lcc.Timeout, rcc.Singleplex, clientSeshMaker)
go cli_client.RouteUDP(acceptor, 300*time.Second, rcc.Singleplex, clientSeshMaker)
proxyToCkClientD = mDialer
} else {
var proxyToCkClientL *connutil.PipeListener
proxyToCkClientD, proxyToCkClientL = connutil.DialerListener(10 * 1024)
go client.RouteTCP(proxyToCkClientL, lcc.Timeout, rcc.Singleplex, clientSeshMaker)
go cli_client.RouteTCP(proxyToCkClientL, 300*time.Second, rcc.Singleplex, clientSeshMaker)
}
// set up server
@ -259,11 +255,11 @@ func TestUDP(t *testing.T) {
log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicUDPConfig, worldState)
rcc, ai := generateClientConfigs(basicUDPConfig, worldState)
sta := basicServerState(worldState)
t.Run("simple send", func(t *testing.T) {
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -300,7 +296,7 @@ func TestUDP(t *testing.T) {
const echoMsgLen = 1024
t.Run("user echo", func(t *testing.T) {
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -320,9 +316,9 @@ func TestUDP(t *testing.T) {
func TestTCPSingleplex(t *testing.T) {
log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(singleplexTCPConfig, worldState)
rcc, ai := generateClientConfigs(singleplexTCPConfig, worldState)
sta := basicServerState(worldState)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -381,7 +377,7 @@ func TestTCPMultiplex(t *testing.T) {
log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
sta := basicServerState(worldState)
t.Run("user echo single", func(t *testing.T) {
@ -390,7 +386,7 @@ func TestTCPMultiplex(t *testing.T) {
writeData := make([]byte, dataLen)
rand.Read(writeData)
t.Run(fmt.Sprintf("data length %v", dataLen), func(t *testing.T) {
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -423,7 +419,7 @@ func TestTCPMultiplex(t *testing.T) {
const echoMsgLen = 16384
t.Run("user echo", func(t *testing.T) {
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -441,7 +437,7 @@ func TestTCPMultiplex(t *testing.T) {
})
t.Run("redir echo", func(t *testing.T) {
_, _, netToCkServerD, redirFromCkServerL, err := establishSession(lcc, rcc, ai, sta)
_, _, netToCkServerD, redirFromCkServerL, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -466,9 +462,9 @@ func TestClosingStreamsFromProxy(t *testing.T) {
clientConfig := clientConfig
clientConfigName := clientConfigName
t.Run(clientConfigName, func(t *testing.T) {
lcc, rcc, ai := generateClientConfigs(clientConfig, worldState)
rcc, ai := generateClientConfigs(clientConfig, worldState)
sta := basicServerState(worldState)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
t.Fatal(err)
}
@ -528,7 +524,7 @@ func TestClosingStreamsFromProxy(t *testing.T) {
func BenchmarkIntegration(b *testing.B) {
log.SetLevel(log.ErrorLevel)
worldState := common.WorldOfTime(time.Unix(10, 0))
lcc, rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
rcc, ai := generateClientConfigs(basicTCPConfig, worldState)
sta := basicServerState(worldState)
const bufSize = 16 * 1024
@ -542,7 +538,7 @@ func BenchmarkIntegration(b *testing.B) {
for name, method := range encryptionMethods {
b.Run(name, func(b *testing.B) {
ai.EncryptionMethod = method
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(lcc, rcc, ai, sta)
proxyToCkClientD, proxyFromCkServerL, _, _, err := establishSession(rcc, ai, sta)
if err != nil {
b.Fatal(err)
}

@ -4,6 +4,9 @@ import (
"crypto"
"encoding/json"
"fmt"
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client/browsers"
"github.com/cbeuw/Cloak/libcloak/client/transports"
"io/ioutil"
"net"
"strings"
@ -65,22 +68,12 @@ type RawConfig struct {
// is set to `cdn`
// Defaults to RemoteHost
CDNOriginHost string
// StreamTimeout is the duration, in seconds, for a stream to be automatically closed after the last write.
// Defaults to 300
StreamTimeout int
// KeepAlive is the interval between TCP KeepAlive packets to be sent over the underlying TLS connections
// Defaults to -1, which means no TCP KeepAlive is ever sent
KeepAlive int
// RemotePort is the port Cloak server is listening to
// Defaults to 443
RemotePort string
// LocalHost is the hostname or IP address to listen for incoming proxy client connections
LocalHost string // jsonOptional
// LocalPort is the port to listen for incomig proxy client connections
LocalPort string // jsonOptional
// AlternativeNames is a list of ServerName Cloak may randomly pick from for different sessions
AlternativeNames []string
}
type RemoteConnConfig struct {
@ -91,93 +84,10 @@ type RemoteConnConfig struct {
TransportMaker func() transports.Transport
}
type LocalConnConfig struct {
LocalAddr string
Timeout time.Duration
MockDomainList []string
}
type AuthInfo = transports.AuthInfo
// semi-colon separated value. This is for Android plugin options
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 {
r := strings.Replace(s, `\\`, `\`, -1)
r = strings.Replace(r, `\=`, `=`, -1)
r = strings.Replace(r, `\;`, `;`, -1)
return r
}
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
lines := strings.Split(unescape(ssv), ";")
ret = []byte("{")
for _, ln := range lines {
if ln == "" {
break
}
sp := strings.SplitN(ln, "=", 2)
if len(sp) < 2 {
log.Errorf("Malformed config option: %v", ln)
continue
}
key := sp[0]
value := sp[1]
if strings.HasPrefix(key, "AlternativeNames") {
switch strings.Contains(value, ",") {
case true:
domains := strings.Split(value, ",")
for index, domain := range domains {
domains[index] = `"` + domain + `"`
}
value = strings.Join(domains, ",")
ret = append(ret, []byte(`"`+key+`":[`+value+`],`)...)
case false:
ret = append(ret, []byte(`"`+key+`":["`+value+`"],`)...)
}
continue
}
// JSON doesn't like quotation marks around int and bool
// This is extremely ugly but it's still better than writing a tokeniser
if elem(key, unquoted) {
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
} else {
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
}
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
func ParseConfig(conf string) (raw *RawConfig, err error) {
var content []byte
// Checking if it's a path to json or a ssv string
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
return
}
}
raw = new(RawConfig)
err = json.Unmarshal(content, &raw)
if err != nil {
return
}
return
}
func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
nullErr := func(field string) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (remote RemoteConnConfig, auth AuthInfo, err error) {
nullErr := func(field string) (remote RemoteConnConfig, auth AuthInfo, err error) {
err = fmt.Errorf("%v cannot be empty", field)
return
}
@ -189,18 +99,8 @@ func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (local Loca
}
auth.MockDomain = raw.ServerName
var filteredAlternativeNames []string
for _, alternativeName := range raw.AlternativeNames {
if len(alternativeName) > 0 {
filteredAlternativeNames = append(filteredAlternativeNames, alternativeName)
}
}
raw.AlternativeNames = filteredAlternativeNames
local.MockDomainList = raw.AlternativeNames
local.MockDomainList = append(local.MockDomainList, auth.MockDomain)
if raw.ProxyMethod == "" {
return nullErr("ServerName")
return nullErr("ProxyMethod")
}
auth.ProxyMethod = raw.ProxyMethod
if len(raw.UID) == 0 {
@ -302,19 +202,5 @@ func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (local Loca
remote.KeepAlive = remote.KeepAlive * time.Second
}
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
}

@ -1,6 +1,8 @@
package transports
import (
"github.com/cbeuw/Cloak/internal/common"
"github.com/cbeuw/Cloak/libcloak/client/browsers"
utls "github.com/refraction-networking/utls"
log "github.com/sirupsen/logrus"
"net"
Loading…
Cancel
Save