Cloak 2: generalising cloak as a universal pluggable transport for arbitary proxies

This commit is contained in:
Qian Wang 2019-06-09 16:10:22 +10:00
parent ebd7e6b1bd
commit 3e9855191b
12 changed files with 134 additions and 174 deletions

View File

@ -48,12 +48,12 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
d := net.Dialer{Control: protector}
clientHello := TLS.ComposeInitHandshake(sta)
connectingIP := sta.SS_REMOTE_HOST
connectingIP := sta.RemoteHost
if net.ParseIP(connectingIP).To4() == nil {
// IPv6 needs square brackets
connectingIP = "[" + connectingIP + "]"
}
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.SS_REMOTE_PORT)
remoteConn, err := d.Dial("tcp", connectingIP+":"+sta.RemotePort)
if err != nil {
log.Printf("Connecting to remote: %v\n", err)
return nil, err
@ -86,15 +86,15 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
}
func main() {
// Should be 127.0.0.1 to listen to ss-local on this machine
// Should be 127.0.0.1 to listen to a proxy client on this machine
var localHost string
// server_port in ss config, ss sends data on loopback using this port
// port used by proxy clients to communicate with cloak client
var localPort string
// The ip of the proxy server
var remoteHost string
// The proxy port,should be 443
var remotePort string
var pluginOpts string
var config string
isAdmin := new(bool)
log.SetFlags(log.LstdFlags | log.Lshortfile)
@ -106,13 +106,13 @@ func main() {
localPort = os.Getenv("SS_LOCAL_PORT")
remoteHost = os.Getenv("SS_REMOTE_HOST")
remotePort = os.Getenv("SS_REMOTE_PORT")
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
config = os.Getenv("SS_PLUGIN_OPTIONS")
} else {
localHost = "127.0.0.1"
flag.StringVar(&localPort, "l", "", "localPort: same as server_port in ss config, the plugin listens to SS using this")
flag.StringVar(&localPort, "l", "", "localPort: Cloak listens to proxy clients on this port")
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
flag.StringVar(&pluginOpts, "c", "ckclient.json", "pluginOpts: path to ckclient.json or options seperated with semicolons")
flag.StringVar(&config, "c", "ckclient.json", "config: path to the configuration file or options seperated with semicolons")
askVersion := flag.Bool("v", false, "Print the version number")
isAdmin = flag.Bool("a", false, "Admin mode")
printUsage := flag.Bool("h", false, "Print this message")
@ -133,7 +133,7 @@ func main() {
if *isAdmin {
sta := client.InitState("", "", "", "", time.Now)
err := sta.ParseConfig(pluginOpts)
err := sta.ParseConfig(config)
if err != nil {
log.Fatal(err)
}
@ -145,27 +145,27 @@ func main() {
}
sta := client.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
err := sta.ParseConfig(pluginOpts)
err := sta.ParseConfig(config)
if err != nil {
log.Fatal(err)
}
if sta.SS_LOCAL_PORT == "" {
if sta.LocalPort == "" {
log.Fatal("Must specify localPort")
}
if sta.SS_REMOTE_HOST == "" {
if sta.RemoteHost == "" {
log.Fatal("Must specify remoteHost")
}
if sta.TicketTimeHint == 0 {
log.Fatal("TicketTimeHint cannot be empty or 0")
}
listeningIP := sta.SS_LOCAL_HOST
listeningIP := sta.LocalHost
if net.ParseIP(listeningIP).To4() == nil {
// IPv6 needs square brackets
listeningIP = "[" + listeningIP + "]"
}
listener, err := net.Listen("tcp", listeningIP+":"+sta.SS_LOCAL_PORT)
log.Println("Listening for ss on " + listeningIP + ":" + sta.SS_LOCAL_PORT)
listener, err := net.Listen("tcp", listeningIP+":"+sta.LocalPort)
log.Println("Listening for proxy clients on " + listeningIP + ":" + sta.LocalPort)
if err != nil {
log.Fatal(err)
}
@ -207,34 +207,34 @@ start:
if sesh.IsBroken() {
goto start
}
ssConn, err := listener.Accept()
localConn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
go func() {
data := make([]byte, 10240)
i, err := io.ReadAtLeast(ssConn, data, 1)
i, err := io.ReadAtLeast(localConn, data, 1)
if err != nil {
log.Println(err)
ssConn.Close()
localConn.Close()
return
}
stream, err := sesh.OpenStream()
if err != nil {
log.Println(err)
ssConn.Close()
localConn.Close()
return
}
_, err = stream.Write(data[:i])
if err != nil {
log.Println(err)
ssConn.Close()
localConn.Close()
stream.Close()
return
}
go pipe(ssConn, stream)
pipe(stream, ssConn)
go pipe(localConn, stream)
pipe(stream, localConn)
}()
}

View File

@ -42,7 +42,7 @@ func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
func dispatchConnection(conn net.Conn, sta *server.State) {
goWeb := func(data []byte) {
webConn, err := net.Dial("tcp", sta.WebServerAddr)
webConn, err := net.Dial("tcp", sta.RedirAddr)
if err != nil {
log.Printf("Making connection to redirection server: %v\n", err)
return
@ -64,14 +64,19 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
data := buf[:i]
ch, err := server.ParseClientHello(data)
if err != nil {
log.Printf("+1 non SS non (or malformed) TLS traffic from %v\n", conn.RemoteAddr())
log.Printf("+1 non Cloak non (or malformed) TLS traffic from %v\n", conn.RemoteAddr())
goWeb(data)
return
}
isSS, UID, sessionID := server.TouchStone(ch, sta)
if !isSS {
log.Printf("+1 non SS TLS traffic from %v\n", conn.RemoteAddr())
isCloak, UID, sessionID, proxyMethod := server.TouchStone(ch, sta)
if !isCloak {
log.Printf("+1 non Cloak TLS traffic from %v\n", conn.RemoteAddr())
goWeb(data)
return
}
if _, ok := sta.ProxyBook[proxyMethod]; !ok {
log.Printf("+1 Cloak TLS traffic with invalid proxy method `%v` from %v\n", proxyMethod, conn.RemoteAddr())
goWeb(data)
return
}
@ -167,47 +172,35 @@ func dispatchConnection(conn net.Conn, sta *server.State) {
continue
}
}
ssIP := sta.SS_LOCAL_HOST
if net.ParseIP(ssIP).To4() == nil {
// IPv6 needs square brackets
ssIP = "[" + ssIP + "]"
}
ssConn, err := net.Dial("tcp", ssIP+":"+sta.SS_LOCAL_PORT)
localConn, err := net.Dial("tcp", sta.ProxyBook[proxyMethod])
if err != nil {
log.Printf("Failed to connect to ssserver: %v\n", err)
log.Printf("Failed to connect to %v: %v\n", proxyMethod, err)
continue
}
go pipe(ssConn, newStream)
go pipe(newStream, ssConn)
go pipe(localConn, newStream)
go pipe(newStream, localConn)
}
}
}
func main() {
// Should be 127.0.0.1 to listen to ss-server on this machine
var localHost string
// server_port in ss config, same as remotePort in plugin mode
var localPort string
// server in ss config, the outbound listening ip
var remoteHost string
var bindHost string
// Outbound listening ip, should be 443
var remotePort string
var pluginOpts string
var bindPort string
var config string
log.SetFlags(log.LstdFlags | log.Lshortfile)
if os.Getenv("SS_LOCAL_HOST") != "" {
localHost = os.Getenv("SS_LOCAL_HOST")
localPort = os.Getenv("SS_LOCAL_PORT")
remoteHost = os.Getenv("SS_REMOTE_HOST")
remotePort = os.Getenv("SS_REMOTE_PORT")
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
bindHost = os.Getenv("SS_REMOTE_HOST")
bindPort = os.Getenv("SS_REMOTE_PORT")
config = os.Getenv("SS_PLUGIN_OPTIONS")
} else {
localAddr := flag.String("r", "", "localAddr: the ip:port ss-server is listening on, set in Shadowsocks' configuration. If ss-server is running locally, it should be 127.0.0.1:some port")
flag.StringVar(&remoteHost, "s", "0.0.0.0", "remoteHost: outbound listing ip, set to 0.0.0.0 to listen to everything")
flag.StringVar(&remotePort, "p", "443", "remotePort: outbound listing port, should be 443")
flag.StringVar(&pluginOpts, "c", "server.json", "pluginOpts: path to server.json or options seperated by semicolons")
flag.StringVar(&bindHost, "s", "0.0.0.0", "bindHost: ip to bind to, set to 0.0.0.0 to listen to everything")
flag.StringVar(&bindPort, "p", "443", "bindPort: port to bind to, should be 443")
flag.StringVar(&config, "c", "server.json", "config: path to the configuration file or its content")
askVersion := flag.Bool("v", false, "Print the version number")
printUsage := flag.Bool("h", false, "Print this message")
@ -240,20 +233,25 @@ func main() {
startPprof(*pprofAddr)
}
if *localAddr == "" {
log.Fatal("Must specify localAddr")
}
localHost = strings.Split(*localAddr, ":")[0]
localPort = strings.Split(*localAddr, ":")[1]
log.Printf("Starting standalone mode, listening on %v:%v to ss at %v:%v\n", remoteHost, remotePort, localHost, localPort)
log.Printf("Starting standalone mode, listening on %v:%v", bindHost, bindPort)
}
sta, _ := server.InitState(localHost, localPort, remoteHost, remotePort, time.Now)
sta, _ := server.InitState(bindHost, bindPort, time.Now)
err := sta.ParseConfig(pluginOpts)
err := sta.ParseConfig(config)
if err != nil {
log.Fatalf("Configuration file error: %v", err)
}
// when cloak is started as a shadowsocks plugin
if os.Getenv("SS_LOCAL_HOST") != "" && os.Getenv("SS_LOCAL_PORT") != "" {
ssLocalHost := os.Getenv("SS_LOCAL_HOST")
ssLocalPort := os.Getenv("SS_LOCAL_PORT")
if net.ParseIP(ssLocalHost).To4() == nil {
ssLocalHost = "[" + ssLocalHost + "]"
}
sta.ProxyBook["shadowsocks"] = ssLocalHost + ":" + ssLocalPort
}
if sta.AdminUID == nil {
log.Fatalln("AdminUID cannot be empty!")
}
@ -277,7 +275,7 @@ func main() {
}
// When listening on an IPv6 and IPv4, SS gives REMOTE_HOST as e.g. ::|0.0.0.0
listeningIP := strings.Split(sta.SS_REMOTE_HOST, "|")
listeningIP := strings.Split(sta.BindHost, "|")
for i, ip := range listeningIP {
if net.ParseIP(ip).To4() == nil {
// IPv6 needs square brackets
@ -286,9 +284,9 @@ func main() {
// The last listener must block main() because the program exits on main return.
if i == len(listeningIP)-1 {
listen(ip, sta.SS_REMOTE_PORT)
listen(ip, sta.BindPort)
} else {
go listen(ip, sta.SS_REMOTE_PORT)
go listen(ip, sta.BindPort)
}
}

View File

@ -1,8 +1,9 @@
{
"ProxyMethod":"shadowsocks",
"UID":"iGAO85zysIyR4c09CyZSLdNhtP/ckcYu7nIPI082AHA=",
"PublicKey":"IYoUzkle/T/kriE+Ufdm7AHQtIeGnBWbhhlTbmDpUUI=",
"ServerName":"www.bing.com",
"TicketTimeHint":3600,
"NumConn":4,
"MaskBrowser":"chrome"
"BrowserSig":"chrome"
}

View File

@ -1,5 +1,10 @@
{
"WebServerAddr":"204.79.197.200:443",
"ProxyBook":{
"shadowsocks":"127.0.0.1:8388",
"openvpn":"127.0.0.1:8389",
"tor":"127.0.0.1:9001"
},
"RedirAddr":"204.79.197.200:443",
"PrivateKey":"EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=",
"AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=",
"DatabasePath":"userinfo.db",

View File

@ -49,13 +49,13 @@ func addExtRec(typ []byte, data []byte) []byte {
// ComposeInitHandshake composes ClientHello with record layer
func ComposeInitHandshake(sta *client.State) []byte {
var ch []byte
switch sta.MaskBrowser {
switch sta.BrowserSig {
case "chrome":
ch = (&chrome{}).composeClientHello(sta)
case "firefox":
ch = (&firefox{}).composeClientHello(sta)
default:
panic("Unsupported browser:" + sta.MaskBrowser)
panic("Unsupported browser:" + sta.BrowserSig)
}
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01})
}

View File

@ -34,7 +34,7 @@ func MakeRandomField(sta *State) []byte {
}
func MakeSessionTicket(sta *State) []byte {
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes][padding 124 bytes]
// sessionTicket: [marshalled ephemeral pub key 32 bytes][encrypted UID+sessionID 36 bytes and proxy method 16 bytes][padding 108 bytes]
// The first 16 bytes of the marshalled ephemeral public key is used as the IV
// for encrypting the UID
tthInterval := sta.Now().Unix() / int64(sta.TicketTimeHint)
@ -52,11 +52,12 @@ func MakeSessionTicket(sta *State) []byte {
ticket := make([]byte, 192)
copy(ticket[0:32], ecdh.Marshal(ephKP.PublicKey))
key := ecdh.GenerateSharedSecret(ephKP.PrivateKey, sta.staticPub)
plainUIDsID := make([]byte, 36)
copy(plainUIDsID, sta.UID)
binary.BigEndian.PutUint32(plainUIDsID[32:36], sta.sessionID)
cipherUIDsID := util.AESEncrypt(ticket[0:16], key, plainUIDsID)
copy(ticket[32:68], cipherUIDsID)
plain := make([]byte, 52)
copy(plain, sta.UID)
binary.BigEndian.PutUint32(plain[32:36], sta.sessionID)
copy(plain[36:52], []byte(sta.ProxyMethod))
cipher := util.AESEncrypt(ticket[0:16], key, plain)
copy(ticket[32:84], cipher)
// The purpose of adding sessionID is that, the generated padding of sessionTicket needs to be unpredictable.
// As shown in auth.go, the padding is generated by a psudo random generator. The seed
// needs to be the same for each TicketTimeHint interval. However the value of epoch/TicketTimeHint
@ -67,6 +68,6 @@ func MakeSessionTicket(sta *State) []byte {
// With the sessionID value generated at startup of ckclient and used as a part of the seed, the
// sessionTicket is still identical for each TicketTimeHint interval, but others won't be able to know
// how it was generated. It will also be different for each client.
copy(ticket[68:192], util.PsudoRandBytes(124, tthInterval+int64(sta.sessionID)))
copy(ticket[84:192], util.PsudoRandBytes(108, tthInterval+int64(sta.sessionID)))
return ticket
}

View File

@ -15,19 +15,20 @@ import (
type rawConfig struct {
ServerName string
ProxyMethod string
UID string
PublicKey string
TicketTimeHint int
MaskBrowser string
BrowserSig string
NumConn int
}
// State stores global variables
type State struct {
SS_LOCAL_HOST string
SS_LOCAL_PORT string
SS_REMOTE_HOST string
SS_REMOTE_PORT string
LocalHost string
LocalPort string
RemoteHost string
RemotePort string
Now func() time.Time
sessionID uint32
@ -36,19 +37,20 @@ type State struct {
keyPairsM sync.RWMutex
keyPairs map[int64]*keyPair
ProxyMethod string
TicketTimeHint int
ServerName string
MaskBrowser string
BrowserSig string
NumConn int
}
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
ret := &State{
SS_LOCAL_HOST: localHost,
SS_LOCAL_PORT: localPort,
SS_REMOTE_HOST: remoteHost,
SS_REMOTE_PORT: remotePort,
Now: nowFunc,
LocalHost: localHost,
LocalPort: localPort,
RemoteHost: remoteHost,
RemotePort: remotePort,
Now: nowFunc,
}
ret.keyPairs = make(map[int64]*keyPair)
return ret
@ -102,9 +104,10 @@ func (sta *State) ParseConfig(conf string) (err error) {
if err != nil {
return err
}
sta.ProxyMethod = preParse.ProxyMethod
sta.ServerName = preParse.ServerName
sta.TicketTimeHint = preParse.TicketTimeHint
sta.MaskBrowser = preParse.MaskBrowser
sta.BrowserSig = preParse.BrowserSig
sta.NumConn = preParse.NumConn
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
if err != nil {

View File

@ -9,13 +9,11 @@ import (
// order of arrival is not guaranteed. A stream's first packet may be sent through
// connection0 and its second packet may be sent through connection1. Although both
// packets are transmitted reliably (as TCP is reliable), packet1 may arrive to the
// remote side before packet0.
//
// However, shadowsocks' protocol does not provide sequence control. We must therefore
// make sure packets arrive in order.
// remote side before packet0. Cloak have to therefore sequence the packets so that they
// arrive in order as they were sent by the proxy software
//
// Cloak packets will have a 32-bit sequence number on them, so we know in which order
// they should be sent to shadowsocks. The code in this file provides buffering and sorting.
// they should be sent to the proxy software. The code in this file provides buffering and sorting.
//
// Similar to TCP, the next seq number after 2^32-1 is 0. This is called wrap around.
//

View File

@ -12,12 +12,12 @@ import (
)
// input ticket, return UID
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32) {
func decryptSessionTicket(staticPv crypto.PrivateKey, ticket []byte) ([]byte, uint32, string) {
ephPub, _ := ecdh.Unmarshal(ticket[0:32])
key := ecdh.GenerateSharedSecret(staticPv, ephPub)
UIDsID := util.AESDecrypt(ticket[0:16], key, ticket[32:68])
sessionID := binary.BigEndian.Uint32(UIDsID[32:36])
return UIDsID[0:32], sessionID
plain := util.AESDecrypt(ticket[0:16], key, ticket[32:84])
sessionID := binary.BigEndian.Uint32(plain[32:36])
return plain[0:32], sessionID, string(bytes.Trim(plain[36:52], "\x00"))
}
func validateRandom(random []byte, UID []byte, time int64) bool {
@ -32,7 +32,7 @@ func validateRandom(random []byte, UID []byte, time int64) bool {
h.Write(preHash)
return bytes.Equal(h.Sum(nil)[0:16], random[16:32])
}
func TouchStone(ch *ClientHello, sta *State) (isSS bool, UID []byte, sessionID uint32) {
func TouchStone(ch *ClientHello, sta *State) (isCK bool, UID []byte, sessionID uint32, proxyMethod string) {
var random [32]byte
copy(random[:], ch.random)
@ -43,17 +43,17 @@ func TouchStone(ch *ClientHello, sta *State) (isSS bool, UID []byte, sessionID u
if used != 0 {
log.Println("Replay! Duplicate random")
return false, nil, 0
return false, nil, 0, ""
}
ticket := ch.extensions[[2]byte{0x00, 0x23}]
if len(ticket) < 68 {
return false, nil, 0
return false, nil, 0, ""
}
UID, sessionID = decryptSessionTicket(sta.staticPv, ticket)
isSS = validateRandom(ch.random, UID, sta.Now().Unix())
if !isSS {
return false, nil, 0
UID, sessionID, proxyMethod = decryptSessionTicket(sta.staticPv, ticket)
isCK = validateRandom(ch.random, UID, sta.Now().Unix())
if !isCK {
return false, nil, 0, ""
}
return

View File

@ -16,7 +16,7 @@ func TestDecryptSessionTicket(t *testing.T) {
staticPv, _ := ecdh.Unmarshal(pvb)
sessionTicket, _ := hex.DecodeString("f586223b50cada583d61dc9bf3d01cc3a45aab4b062ed6a31ead0badb87f7761aab4f9f737a1d8ff2a2aa4d50ceb808844588ee3c8fdf36c33a35ef5003e287337659c8164a7949e9e63623090763fc24d0386c8904e47bdd740e09dd9b395c72de669629c2a865ed581452d23306adf26de0c8a46ee05e3dac876f2bcd9a2de946d319498f579383d06b3e66b3aca05f533fdc5f017eeba45b42080aabd4f71151fa0dfc1b0e23be4ed3abdb47adc0d5740ca7b7689ad34426309fb6984a086")
decryUID, decrySessionID := decryptSessionTicket(staticPv, sessionTicket)
decryUID, decrySessionID, _ := decryptSessionTicket(staticPv, sessionTicket)
if !bytes.Equal(decryUID, UID) {
t.Error(
"For", "UID",

View File

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"io/ioutil"
"strings"
"sync"
"time"
@ -14,7 +13,8 @@ import (
)
type rawConfig struct {
WebServerAddr string
ProxyBook map[string]string
RedirAddr string
PrivateKey string
AdminUID string
DatabasePath string
@ -23,10 +23,10 @@ type rawConfig struct {
// State type stores the global state of the program
type State struct {
SS_LOCAL_HOST string
SS_LOCAL_PORT string
SS_REMOTE_HOST string
SS_REMOTE_PORT string
ProxyBook map[string]string
BindHost string
BindPort string
Now func() time.Time
AdminUID []byte
@ -35,62 +35,35 @@ type State struct {
usedRandomM sync.RWMutex
usedRandom map[[32]byte]int
WebServerAddr string
RedirAddr string
}
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) (*State, error) {
func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, error) {
ret := &State{
SS_LOCAL_HOST: localHost,
SS_LOCAL_PORT: localPort,
SS_REMOTE_HOST: remoteHost,
SS_REMOTE_PORT: remotePort,
Now: nowFunc,
BindHost: bindHost,
BindPort: bindPort,
Now: nowFunc,
}
ret.usedRandom = make(map[[32]byte]int)
return ret, nil
}
// semi-colon separated value.
func ssvToJson(ssv string) (ret []byte) {
unescape := func(s string) string {
r := strings.Replace(s, `\\`, `\`, -1)
r = strings.Replace(r, `\=`, `=`, -1)
r = strings.Replace(r, `\;`, `;`, -1)
return r
}
lines := strings.Split(unescape(ssv), ";")
ret = []byte("{")
for _, ln := range lines {
if ln == "" {
break
}
sp := strings.SplitN(ln, "=", 2)
key := sp[0]
value := sp[1]
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
func (sta *State) ParseConfig(conf string) (err error) {
var content []byte
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
return err
}
}
var preParse rawConfig
err = json.Unmarshal(content, &preParse)
if err != nil {
return errors.New("Failed to unmarshal: " + err.Error())
content, errPath := ioutil.ReadFile(conf)
if errPath != nil {
errJson := json.Unmarshal(content, &preParse)
if errJson != nil {
return errors.New("Failed to read/unmarshal configuration, path is invalid or " + errJson.Error())
}
} else {
errJson := json.Unmarshal(content, &preParse)
if errJson != nil {
return errors.New("Failed to read configuration file: " + errJson.Error())
}
}
up, err := usermanager.MakeUserpanel(preParse.DatabasePath, preParse.BackupDirPath)
@ -99,7 +72,8 @@ func (sta *State) ParseConfig(conf string) (err error) {
}
sta.Userpanel = up
sta.WebServerAddr = preParse.WebServerAddr
sta.RedirAddr = preParse.RedirAddr
sta.ProxyBook = preParse.ProxyBook
pvBytes, err := base64.StdEncoding.DecodeString(preParse.PrivateKey)
if err != nil {

View File

@ -1,20 +0,0 @@
package server
import (
"bytes"
"testing"
)
func TestSSVtoJson(t *testing.T) {
ssv := "WebServerAddr=204.79.197.200:443;PrivateKey=EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=;AdminUID=ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=;DatabasePath=userinfo.db;BackupDirPath=;"
json := ssvToJson(ssv)
expected := []byte(`{"WebServerAddr":"204.79.197.200:443","PrivateKey":"EN5aPEpNBO+vw+BtFQY2OnK9bQU7rvEj5qmnmgwEtUc=","AdminUID":"ugDmcEmxWf0pKxfkZ/8EoP35Ht+wQnqf3L0xYgyQFlQ=","DatabasePath":"userinfo.db","BackupDirPath":""}`)
if !bytes.Equal(expected, json) {
t.Error(
"For", "ssvToJson",
"expecting", string(expected),
"got", string(json),
)
}
}