Cloak/internal/client/state.go

235 lines
5.5 KiB
Go
Raw Normal View History

2018-10-07 17:09:45 +00:00
package client
import (
2020-04-08 21:07:11 +00:00
"crypto"
2018-10-07 17:09:45 +00:00
"encoding/json"
2020-04-06 12:07:16 +00:00
"fmt"
2020-04-09 21:11:12 +00:00
"github.com/cbeuw/Cloak/internal/common"
log "github.com/sirupsen/logrus"
2018-10-07 17:09:45 +00:00
"io/ioutil"
2020-04-06 12:07:16 +00:00
"net"
2018-10-07 17:09:45 +00:00
"strings"
"time"
2018-10-14 19:32:54 +00:00
"github.com/cbeuw/Cloak/internal/ecdh"
2019-08-20 21:43:04 +00:00
mux "github.com/cbeuw/Cloak/internal/multiplex"
2018-10-07 17:09:45 +00:00
)
2020-04-09 21:11:12 +00:00
// RawConfig represents the fields in the config json file
2020-04-06 12:07:16 +00:00
// 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
2020-04-09 21:11:12 +00:00
type RawConfig struct {
2019-06-09 11:05:41 +00:00
ServerName string
ProxyMethod string
EncryptionMethod string
2019-12-29 16:55:21 +00:00
UID []byte
PublicKey []byte
2019-08-12 21:43:16 +00:00
NumConn int
2020-04-06 12:07:16 +00:00
LocalHost string // jsonOptional
LocalPort string // jsonOptional
RemoteHost string // jsonOptional
RemotePort string // jsonOptional
// defaults set in SplitConfigs
UDP bool // nullable
2020-04-06 12:07:16 +00:00
BrowserSig string // nullable
Transport string // nullable
StreamTimeout int // nullable
KeepAlive int // nullable
2018-10-07 17:09:45 +00:00
}
2020-04-10 13:09:48 +00:00
type RemoteConnConfig struct {
2020-04-08 21:07:11 +00:00
NumConn int
KeepAlive time.Duration
RemoteAddr string
TransportMaker func() Transport
}
2020-04-10 13:09:48 +00:00
type LocalConnConfig struct {
2020-04-06 12:07:16 +00:00
LocalAddr string
Timeout time.Duration
2018-10-07 17:09:45 +00:00
}
2020-04-10 13:09:48 +00:00
type AuthInfo struct {
2020-04-08 21:07:11 +00:00
UID []byte
SessionId uint32
ProxyMethod string
EncryptionMethod byte
Unordered bool
ServerPubKey crypto.PublicKey
MockDomain string
2020-04-09 21:11:12 +00:00
WorldState common.WorldState
2020-04-08 21:07:11 +00:00
}
2018-10-07 17:09:45 +00:00
// semi-colon separated value. This is for Android plugin options
func ssvToJson(ssv string) (ret []byte) {
2020-04-06 12:07:16 +00:00
elem := func(val string, lst []string) bool {
for _, v := range lst {
if val == v {
return true
}
}
return false
}
2018-10-07 17:09:45 +00:00
unescape := func(s string) string {
2018-12-17 22:12:38 +00:00
r := strings.Replace(s, `\\`, `\`, -1)
r = strings.Replace(r, `\=`, `=`, -1)
r = strings.Replace(r, `\;`, `;`, -1)
2018-10-07 17:09:45 +00:00
return r
}
unquoted := []string{"NumConn", "StreamTimeout", "KeepAlive", "UDP"}
2018-10-07 17:09:45 +00:00
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
}
2018-10-07 17:09:45 +00:00
key := sp[0]
value := sp[1]
2019-08-20 21:43:04 +00:00
// JSON doesn't like quotation marks around int and bool
// This is extremely ugly but it's still better than writing a tokeniser
2020-04-06 12:07:16 +00:00
if elem(key, unquoted) {
2018-12-17 22:12:38 +00:00
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
2018-10-07 17:09:45 +00:00
} else {
2018-12-17 22:12:38 +00:00
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
2018-10-07 17:09:45 +00:00
}
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
2020-04-09 21:11:12 +00:00
func ParseConfig(conf string) (raw *RawConfig, err error) {
2018-10-07 17:09:45 +00:00
var content []byte
2019-08-20 21:43:04 +00:00
// Checking if it's a path to json or a ssv string
2018-10-07 17:09:45 +00:00
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
2020-04-06 12:07:16 +00:00
return
2018-10-07 17:09:45 +00:00
}
}
2020-04-06 12:07:16 +00:00
2020-04-09 21:11:12 +00:00
raw = new(RawConfig)
2020-04-06 12:07:16 +00:00
err = json.Unmarshal(content, &raw)
2018-10-07 17:09:45 +00:00
if err != nil {
2020-04-06 12:07:16 +00:00
return
2018-10-07 17:09:45 +00:00
}
2020-04-06 12:07:16 +00:00
return
}
2019-06-09 11:05:41 +00:00
2020-04-10 13:09:48 +00:00
func (raw *RawConfig) SplitConfigs(worldState common.WorldState) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
nullErr := func(field string) (local LocalConnConfig, remote RemoteConnConfig, auth AuthInfo, err error) {
2020-04-06 12:07:16 +00:00
err = fmt.Errorf("%v cannot be empty", field)
return
}
auth.UID = raw.UID
auth.Unordered = raw.UDP
2020-04-06 12:07:16 +00:00
if raw.ServerName == "" {
return nullErr("ServerName")
}
auth.MockDomain = raw.ServerName
if raw.ProxyMethod == "" {
return nullErr("ServerName")
}
auth.ProxyMethod = raw.ProxyMethod
if len(raw.UID) == 0 {
return nullErr("UID")
}
// static public key
if len(raw.PublicKey) == 0 {
return nullErr("PublicKey")
}
pub, ok := ecdh.Unmarshal(raw.PublicKey)
if !ok {
err = fmt.Errorf("failed to unmarshal Public key")
return
}
auth.ServerPubKey = pub
2020-04-09 21:11:12 +00:00
auth.WorldState = worldState
2020-04-06 12:07:16 +00:00
// Encryption method
switch strings.ToLower(raw.EncryptionMethod) {
2019-06-09 11:05:41 +00:00
case "plain":
2020-04-06 12:07:16 +00:00
auth.EncryptionMethod = mux.E_METHOD_PLAIN
case "aes-gcm":
2020-04-06 12:07:16 +00:00
auth.EncryptionMethod = mux.E_METHOD_AES_GCM
2019-06-14 10:26:26 +00:00
case "chacha20-poly1305":
2020-04-06 12:07:16 +00:00
auth.EncryptionMethod = mux.E_METHOD_CHACHA20_POLY1305
2019-06-09 11:05:41 +00:00
default:
2020-04-06 12:07:16 +00:00
err = fmt.Errorf("unknown encryption method %v", raw.EncryptionMethod)
return
2019-06-09 11:05:41 +00:00
}
2020-04-06 12:07:16 +00:00
if raw.RemoteHost == "" {
return nullErr("RemoteHost")
}
if raw.RemotePort == "" {
return nullErr("RemotePort")
2019-08-02 15:02:25 +00:00
}
2020-04-06 12:07:16 +00:00
remote.RemoteAddr = net.JoinHostPort(raw.RemoteHost, raw.RemotePort)
if raw.NumConn <= 0 {
raw.NumConn = 0
2020-04-06 12:07:16 +00:00
}
remote.NumConn = raw.NumConn
2019-08-02 15:02:25 +00:00
2020-04-06 12:07:16 +00:00
// Transport and (if TLS mode), browser
switch strings.ToLower(raw.Transport) {
2019-09-02 13:03:10 +00:00
case "cdn":
remote.TransportMaker = func() Transport {
return &WSOverTLS{
cdnDomainPort: remote.RemoteAddr,
}
}
2020-04-06 12:07:16 +00:00
case "direct":
fallthrough
2019-08-31 17:01:39 +00:00
default:
2020-04-06 12:07:16 +00:00
var browser browser
switch strings.ToLower(raw.BrowserSig) {
case "firefox":
browser = &Firefox{}
case "chrome":
fallthrough
default:
browser = &Chrome{}
}
remote.TransportMaker = func() Transport {
return &DirectTLS{
browser: browser,
}
}
2019-08-31 17:01:39 +00:00
}
2020-04-06 12:07:16 +00:00
// KeepAlive
if raw.KeepAlive <= 0 {
remote.KeepAlive = -1
} else {
2020-04-06 12:07:16 +00:00
remote.KeepAlive = remote.KeepAlive * time.Second
}
2019-06-09 11:05:41 +00:00
2020-04-06 12:07:16 +00:00
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
}
2020-04-06 12:07:16 +00:00
return
2018-10-14 19:32:54 +00:00
}