Compare commits

...

8 Commits

Author SHA1 Message Date
Qian Wang bea21b7166 Remove UID from backup file name to fix #38 2019-07-14 17:32:25 +10:00
Qian Wang 8317f447d1 Detect TCP connection issues immediately rather than waiting for tokens 2019-06-18 22:21:38 +10:00
Qian Wang ba7f29d9e6 Fix a mutex deadlock 2019-06-18 22:21:31 +10:00
Qian Wang 10c17c4aca Start session only after a proxy connection is made 2019-06-17 21:01:06 +10:00
Qian Wang 930e647226 Timeout unused new sessions 2019-06-16 23:42:13 +10:00
Qian Wang 7be1586973
Update README.md 2019-06-04 21:34:06 +10:00
Qian Wang a3c3a9b03f
Update README.md 2019-05-30 15:07:01 +10:00
Qian Wang 2dd48ef71e
Update README.md 2019-05-30 15:05:13 +10:00
5 changed files with 58 additions and 43 deletions

View File

@ -1,11 +1,11 @@
# Cloak # Cloak
A Shadowsocks plugin that obfuscates the traffic as normal HTTPS traffic and disguises the proxy server as a normal webserver. A Shadowsocks plugin that obfuscates the traffic as normal HTTPS traffic to non-blocked websites through domain fronting and disguises the proxy server as a normal webserver.
Cloak multiplexes all traffic through a fixed amount of underlying TCP connections which eliminates the TCP handshake overhead when using vanilla Shadowsocks. Cloak also provides user management, allowing multiple users to connect to the proxy server using **one single port**. It also provides QoS controls for individual users such as upload and download credit limit, as well as bandwidth control. Cloak multiplexes all traffic through a fixed amount of underlying TCP connections which eliminates the TCP handshake overhead when using vanilla Shadowsocks. Cloak also provides user management, allowing multiple users to connect to the proxy server using **one single port**. It also provides QoS controls for individual users such as upload and download credit limit, as well as bandwidth control.
To external observers (such as the GFW), Cloak is completely transparent and behaves like an ordinary HTTPS server. This is done through several [cryptographic mechanisms](https://github.com/cbeuw/Cloak/wiki/Cryptographic-Mechanisms). This eliminates the risk of being detected by traffic analysis and/or active probing To external observers (such as the GFW), Cloak is completely transparent and behaves like an ordinary HTTPS server. This is done through several [cryptographic mechanisms](https://github.com/cbeuw/Cloak/wiki/Cryptographic-Mechanisms). This eliminates the risk of being detected by traffic analysis and/or active probing.
This project is based on my previous project [GoQuiet](https://github.com/cbeuw/GoQuiet). The most significant improvement form GoQuiet is that there will not be new TLS handshake being done each time a client application establishes a new connection to the Shadowsocks client. This gives a siginifcant boost to webpage loading time (reduction in time ranges from 10% to 50+%, depending on the amount of content on the webpage, see [benchmarks](https://github.com/cbeuw/Cloak/wiki/Web-page-loading-benchmarks)). This project is based on a previous project [GoQuiet](https://github.com/cbeuw/GoQuiet). Through multiplexing, Cloak provides a siginifcant reduction in webpage loading time compared to GoQuiet (from 10% to 50+%, depending on the amount of content on the webpage, see [benchmarks](https://github.com/cbeuw/Cloak/wiki/Web-page-loading-benchmarks)).
## Build ## Build
Simply `make client` and `make server`. Output binary will be in the build folder. Simply `make client` and `make server`. Output binary will be in the build folder.
@ -20,7 +20,7 @@ Do `make server_pprof` if you want to access the live profiling data.
`AdminUID` is the UID of the admin user in base64. `AdminUID` is the UID of the admin user in base64.
`DatabasePath` is the path to userinfo.db. If userinfo.db doesn't exist in this directory, Cloak will create one automatically. **If Cloak is started as a Shadowsocks plugin and Shadowsocks is started with its working directory as / (e.g. starting ss-server with systemctl), you need to set this field as an absolute path to a desired folder. If you leave it as default then Cloak will attempt to create userinfo.db under /, which it doesn't have the permission to do so and will raise an error. See Issue #13. `DatabasePath` is the path to userinfo.db. If userinfo.db doesn't exist in this directory, Cloak will create one automatically. **If Cloak is started as a Shadowsocks plugin and Shadowsocks is started with its working directory as / (e.g. starting ss-server with systemctl), you need to set this field as an absolute path to a desired folder. If you leave it as default then Cloak will attempt to create userinfo.db under /, which it doesn't have the permission to do so and will raise an error. See Issue #13.**
`BackupDirPath` is the path to save the backups of userinfo.db whenever you delete a user. If left blank, Cloak will attempt to create a folder called db-backup under its working directory. This may not be desired. See notes above. `BackupDirPath` is the path to save the backups of userinfo.db whenever you delete a user. If left blank, Cloak will attempt to create a folder called db-backup under its working directory. This may not be desired. See notes above.
@ -39,7 +39,7 @@ Do `make server_pprof` if you want to access the live profiling data.
## Setup ## Setup
### For the administrator of the server ### For the administrator of the server
**Run this script: https://gist.github.com/cbeuw/37a9d434c237840d7e6d5e497539c1ca** or do it manually: **Run this script: https://github.com/HirbodBehnam/Shadowsocks-Cloak-Installer/blob/master/Shadowsocks-Cloak-Installer.sh (thanks to [@HirbodBehnam](https://github.com/HirbodBehnam))** or do it manually:
0. [Install and configure shadowsocks-libev on your server](https://github.com/shadowsocks/shadowsocks-libev#installation) 0. [Install and configure shadowsocks-libev on your server](https://github.com/shadowsocks/shadowsocks-libev#installation)
1. Download [the latest release](https://github.com/cbeuw/Cloak/releases) or clone and build this repo. If you wish to build it, make sure you fetch the dependencies using `go get github.com/boltdb/bolt`, `go get github.com/juju/ratelimit` and `go get golang.org/x/crypto/curve25519` 1. Download [the latest release](https://github.com/cbeuw/Cloak/releases) or clone and build this repo. If you wish to build it, make sure you fetch the dependencies using `go get github.com/boltdb/bolt`, `go get github.com/juju/ratelimit` and `go get golang.org/x/crypto/curve25519`
@ -66,3 +66,12 @@ Note: the user database is persistent as it's in-disk. You don't need to add the
2. Obtain the public key and your UID (or the AdminUID, if you are the server admin) from the administrator of your server 2. Obtain the public key and your UID (or the AdminUID, if you are the server admin) from the administrator of your server
3. Put the public key and the UID you obtained into config/ckclient.json 3. Put the public key and the UID you obtained into config/ckclient.json
4. Configure your shadowsocks client with your server information. The field `plugin` should be the path to ck-server binary and `plugin_opts` should be the path to ckclient.json 4. Configure your shadowsocks client with your server information. The field `plugin` should be the path to ck-server binary and `plugin_opts` should be the path to ckclient.json
## Support me
If you find this project useful, donations are greatly appreciated!
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SAUYKGSREP8GL&source=url)
BTC: `bc1q59yvpnh0356qq9vf0j2y7hx36t9ysap30spx9h`
ETH: `0x8effF29a8F9bD38A367580527AC303972c92b60c`

View File

@ -85,6 +85,41 @@ func makeRemoteConn(sta *client.State) (net.Conn, error) {
} }
func makeSession(sta *client.State) *mux.Session {
log.Println("Attemtping to start a new session")
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
// sessionID is limited to its UID.
rand.Seed(time.Now().UnixNano())
sessionID := rand.Uint32()
sta.SetSessionID(sessionID)
var UNLIMITED_DOWN int64 = 1e15
var UNLIMITED_UP int64 = 1e15
valve := mux.MakeValve(1e12, 1e12, &UNLIMITED_DOWN, &UNLIMITED_UP)
obfs := mux.MakeObfs(sta.UID)
deobfs := mux.MakeDeobfs(sta.UID)
sesh := mux.MakeSession(sessionID, valve, obfs, deobfs, util.ReadTLS)
var wg sync.WaitGroup
for i := 0; i < sta.NumConn; i++ {
wg.Add(1)
go func() {
makeconn:
conn, err := makeRemoteConn(sta)
if err != nil {
log.Printf("Failed to establish new connections to remote: %v\n", err)
time.Sleep(time.Second * 3)
goto makeconn
}
sesh.AddConnection(conn)
wg.Done()
}()
}
wg.Wait()
log.Printf("Session %v established", sessionID)
return sesh
}
func main() { 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 ss-local on this machine
var localHost string var localHost string
@ -170,48 +205,17 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
start: var sesh *mux.Session
log.Println("Attemtping to start a new session")
// sessionID is usergenerated. There shouldn't be a security concern because the scope of
// sessionID is limited to its UID.
rand.Seed(time.Now().UnixNano())
sessionID := rand.Uint32()
sta.SetSessionID(sessionID)
var UNLIMITED_DOWN int64 = 1e15
var UNLIMITED_UP int64 = 1e15
valve := mux.MakeValve(1e12, 1e12, &UNLIMITED_DOWN, &UNLIMITED_UP)
obfs := mux.MakeObfs(sta.UID)
deobfs := mux.MakeDeobfs(sta.UID)
sesh := mux.MakeSession(sessionID, valve, obfs, deobfs, util.ReadTLS)
var wg sync.WaitGroup
for i := 0; i < sta.NumConn; i++ {
wg.Add(1)
go func() {
makeconn:
conn, err := makeRemoteConn(sta)
if err != nil {
log.Printf("Failed to establish new connections to remote: %v\n", err)
time.Sleep(time.Second * 3)
goto makeconn
}
sesh.AddConnection(conn)
wg.Done()
}()
}
wg.Wait()
log.Printf("Session %v established", sessionID)
for { for {
if sesh.IsBroken() {
goto start
}
ssConn, err := listener.Accept() ssConn, err := listener.Accept()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
continue continue
} }
if sesh == nil || sesh.IsBroken() {
sesh = makeSession(sta)
}
go func() { go func() {
data := make([]byte, 10240) data := make([]byte, 10240)
i, err := io.ReadAtLeast(ssConn, data, 1) i, err := io.ReadAtLeast(ssConn, data, 1)

View File

@ -55,6 +55,7 @@ func MakeSession(id uint32, valve *Valve, obfs Obfser, deobfs Deobfser, obfsedRe
die: make(chan struct{}), die: make(chan struct{}),
} }
sesh.sb = makeSwitchboard(sesh, valve) sesh.sb = makeSwitchboard(sesh, valve)
go sesh.timeoutAfter(30 * time.Second)
return sesh return sesh
} }

View File

@ -112,7 +112,9 @@ func (sb *switchboard) removeConn(closing *connEnclave) {
} }
} }
if len(sb.ces) == 0 { if len(sb.ces) == 0 {
sb.cesM.Unlock()
sb.session.Close() sb.session.Close()
return
} }
sb.cesM.Unlock() sb.cesM.Unlock()
} }
@ -132,13 +134,13 @@ func (sb *switchboard) deplex(ce *connEnclave) {
buf := make([]byte, 20480) buf := make([]byte, 20480)
for { for {
n, err := sb.session.obfsedRead(ce.remoteConn, buf) n, err := sb.session.obfsedRead(ce.remoteConn, buf)
sb.rxWait(n)
if err != nil { if err != nil {
//log.Println(err) //log.Println(err)
go ce.remoteConn.Close() go ce.remoteConn.Close()
sb.removeConn(ce) sb.removeConn(ce)
return return
} }
sb.rxWait(n)
if sb.AddRxCredit(-int64(n)) < 0 { if sb.AddRxCredit(-int64(n)) < 0 {
log.Println(ErrNoRxCredit) log.Println(ErrNoRxCredit)
sb.session.Close() sb.session.Close()

View File

@ -1,7 +1,6 @@
package usermanager package usermanager
import ( import (
"encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
"os" "os"
@ -308,7 +307,7 @@ func (up *Userpanel) addNewUser(uinfo UserInfo) error {
} }
func (up *Userpanel) delUser(UID []byte) error { func (up *Userpanel) delUser(UID []byte) error {
err := up.backupDB(strconv.FormatInt(time.Now().Unix(), 10) + "_pre_del_" + base64.StdEncoding.EncodeToString(UID) + ".bak") err := up.backupDB(strconv.FormatInt(time.Now().Unix(), 10) + ".bak")
if err != nil { if err != nil {
return err return err
} }