mirror of https://github.com/cbeuw/Cloak
Add unordered mode
This commit is contained in:
parent
52fac535e3
commit
c36ec04ce5
|
|
@ -8,9 +8,13 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UNORDERED_FLAG = 0x01 // 0000 0001
|
||||||
|
)
|
||||||
|
|
||||||
func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) {
|
func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) {
|
||||||
// random is marshalled ephemeral pub key 32 bytes
|
// random is marshalled ephemeral pub key 32 bytes
|
||||||
// TLSsessionID || keyShare is [encrypted UID 16 bytes, proxy method 12 bytes, encryption method 1 byte, timestamp 8 bytes, sessionID 4 bytes] [unused data] [16 bytes authentication tag]
|
// TLSsessionID || keyShare is [encrypted UID 16 bytes, proxy method 12 bytes, encryption method 1 byte, timestamp 8 bytes, sessionID 4 bytes] [1 byte flag] [6 bytes reserved] [16 bytes authentication tag]
|
||||||
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
||||||
random = ecdh.Marshal(ephPub)
|
random = ecdh.Marshal(ephPub)
|
||||||
|
|
||||||
|
|
@ -21,6 +25,10 @@ func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []
|
||||||
binary.BigEndian.PutUint64(plaintext[29:37], uint64(sta.Now().Unix()))
|
binary.BigEndian.PutUint64(plaintext[29:37], uint64(sta.Now().Unix()))
|
||||||
binary.BigEndian.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID))
|
binary.BigEndian.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID))
|
||||||
|
|
||||||
|
if sta.Unordered {
|
||||||
|
plaintext[41] |= UNORDERED_FLAG
|
||||||
|
}
|
||||||
|
|
||||||
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
||||||
nonce := random[0:12]
|
nonce := random[0:12]
|
||||||
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ type rawConfig struct {
|
||||||
PublicKey string
|
PublicKey string
|
||||||
BrowserSig string
|
BrowserSig string
|
||||||
Unordered bool
|
Unordered bool
|
||||||
|
|
||||||
NumConn int
|
NumConn int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,6 +41,7 @@ type State struct {
|
||||||
EncryptionMethod byte
|
EncryptionMethod byte
|
||||||
ServerName string
|
ServerName string
|
||||||
NumConn int
|
NumConn int
|
||||||
|
Unordered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||||
|
|
@ -74,7 +74,7 @@ func ssvToJson(ssv string) (ret []byte) {
|
||||||
value := sp[1]
|
value := sp[1]
|
||||||
// JSON doesn't like quotation marks around int
|
// JSON doesn't like quotation marks around int
|
||||||
// Yes this is extremely ugly but it's still better than writing a tokeniser
|
// Yes this is extremely ugly but it's still better than writing a tokeniser
|
||||||
if key == "NumConn" {
|
if key == "NumConn" || key == "Unordered" {
|
||||||
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+`",`)...)
|
||||||
|
|
@ -125,6 +125,7 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
||||||
sta.ProxyMethod = preParse.ProxyMethod
|
sta.ProxyMethod = preParse.ProxyMethod
|
||||||
sta.ServerName = preParse.ServerName
|
sta.ServerName = preParse.ServerName
|
||||||
sta.NumConn = preParse.NumConn
|
sta.NumConn = preParse.NumConn
|
||||||
|
sta.Unordered = preParse.Unordered
|
||||||
|
|
||||||
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,7 @@ type Obfuscator struct {
|
||||||
SessionKey []byte
|
SessionKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwitchboardStrategy int
|
type switchboardStrategy int
|
||||||
|
|
||||||
const (
|
|
||||||
FixedConnMapping SwitchboardStrategy = iota
|
|
||||||
Uniform
|
|
||||||
)
|
|
||||||
|
|
||||||
type SessionConfig struct {
|
type SessionConfig struct {
|
||||||
*Obfuscator
|
*Obfuscator
|
||||||
|
|
@ -41,8 +36,6 @@ type SessionConfig struct {
|
||||||
UnitRead func(net.Conn, []byte) (int, error)
|
UnitRead func(net.Conn, []byte) (int, error)
|
||||||
|
|
||||||
Unordered bool
|
Unordered bool
|
||||||
|
|
||||||
SwitchboardStrategy SwitchboardStrategy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
|
@ -85,8 +78,11 @@ func MakeSession(id uint32, config *SessionConfig) *Session {
|
||||||
|
|
||||||
sbConfig := &switchboardConfig{
|
sbConfig := &switchboardConfig{
|
||||||
Valve: config.Valve,
|
Valve: config.Valve,
|
||||||
unordered: config.Unordered,
|
}
|
||||||
strategy: config.SwitchboardStrategy,
|
if config.Unordered {
|
||||||
|
sbConfig.strategy = UNIFORM_SPREAD
|
||||||
|
} else {
|
||||||
|
sbConfig.strategy = FIXED_CONN_MAPPING
|
||||||
}
|
}
|
||||||
sesh.sb = makeSwitchboard(sesh, sbConfig)
|
sesh.sb = makeSwitchboard(sesh, sbConfig)
|
||||||
go sesh.timeoutAfter(30 * time.Second)
|
go sesh.timeoutAfter(30 * time.Second)
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,14 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FIXED_CONN_MAPPING switchboardStrategy = iota
|
||||||
|
UNIFORM_SPREAD
|
||||||
|
)
|
||||||
|
|
||||||
type switchboardConfig struct {
|
type switchboardConfig struct {
|
||||||
Valve
|
Valve
|
||||||
unordered bool
|
strategy switchboardStrategy
|
||||||
strategy SwitchboardStrategy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// switchboard is responsible for keeping the reference of TLS connections between client and server
|
// switchboard is responsible for keeping the reference of TLS connections between client and server
|
||||||
|
|
@ -64,15 +68,21 @@ func (sb *switchboard) removeConn(connId uint32) {
|
||||||
|
|
||||||
// a pointer to connId is passed here so that the switchboard can reassign it
|
// a pointer to connId is passed here so that the switchboard can reassign it
|
||||||
func (sb *switchboard) send(data []byte, connId *uint32) (int, error) {
|
func (sb *switchboard) send(data []byte, connId *uint32) (int, error) {
|
||||||
sb.Valve.rxWait(len(data))
|
|
||||||
sb.connsM.RLock()
|
sb.connsM.RLock()
|
||||||
defer sb.connsM.RUnlock()
|
defer sb.connsM.RUnlock()
|
||||||
|
if sb.strategy == UNIFORM_SPREAD {
|
||||||
|
randConnId := rand.Intn(len(sb.conns))
|
||||||
|
conn, ok := sb.conns[uint32(randConnId)]
|
||||||
|
if !ok {
|
||||||
|
return 0, errBrokenSwitchboard
|
||||||
|
} else {
|
||||||
|
return conn.Write(data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
conn, ok := sb.conns[*connId]
|
conn, ok := sb.conns[*connId]
|
||||||
if ok {
|
if ok {
|
||||||
n, err := conn.Write(data)
|
return conn.Write(data)
|
||||||
sb.Valve.AddTx(int64(n))
|
|
||||||
return n, err
|
|
||||||
} else {
|
} else {
|
||||||
// do not call assignRandomConn() here.
|
// do not call assignRandomConn() here.
|
||||||
// we'll have to do connsM.RLock() after we get a new connId from assignRandomConn, in order to
|
// we'll have to do connsM.RLock() after we get a new connId from assignRandomConn, in order to
|
||||||
|
|
@ -89,9 +99,8 @@ func (sb *switchboard) send(data []byte, connId *uint32) (int, error) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, errBrokenSwitchboard
|
return 0, errBrokenSwitchboard
|
||||||
} else {
|
} else {
|
||||||
n, err := conn.Write(data)
|
return conn.Write(data)
|
||||||
sb.Valve.AddTx(int64(n))
|
}
|
||||||
return n, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package multiplex
|
package multiplex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cbeuw/Cloak/internal/util"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
@ -10,13 +9,17 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
||||||
seshConfig := &SessionConfig{
|
seshConfig := &SessionConfig{
|
||||||
Obfuscator: nil,
|
Obfuscator: nil,
|
||||||
Valve: nil,
|
Valve: nil,
|
||||||
UnitRead: util.ReadTLS,
|
UnitRead: nil,
|
||||||
}
|
}
|
||||||
sesh := MakeSession(0, seshConfig)
|
sesh := MakeSession(0, seshConfig)
|
||||||
|
sbConfig := &switchboardConfig{
|
||||||
|
Valve: UNLIMITED_VALVE,
|
||||||
|
strategy: FIXED_CONN_MAPPING,
|
||||||
|
}
|
||||||
|
sb := makeSwitchboard(sesh, sbConfig)
|
||||||
hole := newBlackHole()
|
hole := newBlackHole()
|
||||||
sesh.sb.addConn(hole)
|
sb.addConn(hole)
|
||||||
connId, err := sesh.sb.assignRandomConn()
|
connId, err := sb.assignRandomConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error("failed to get a random conn", err)
|
b.Error("failed to get a random conn", err)
|
||||||
return
|
return
|
||||||
|
|
@ -25,7 +28,7 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
||||||
rand.Read(data)
|
rand.Read(data)
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
n, err := sesh.sb.send(data, &connId)
|
n, err := sb.send(data, &connId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
|
|
@ -33,34 +36,3 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
||||||
b.SetBytes(int64(n))
|
b.SetBytes(int64(n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSwitchboard_TxCredit(t *testing.T) {
|
|
||||||
seshConfig := &SessionConfig{
|
|
||||||
Obfuscator: nil,
|
|
||||||
Valve: MakeValve(1<<20, 1<<20),
|
|
||||||
UnitRead: util.ReadTLS,
|
|
||||||
}
|
|
||||||
sesh := MakeSession(0, seshConfig)
|
|
||||||
hole := newBlackHole()
|
|
||||||
sesh.sb.addConn(hole)
|
|
||||||
connId, err := sesh.sb.assignRandomConn()
|
|
||||||
if err != nil {
|
|
||||||
t.Error("failed to get a random conn", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data := make([]byte, 1000)
|
|
||||||
rand.Read(data)
|
|
||||||
|
|
||||||
n, err := sesh.sb.send(data[:10], &connId)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if n != 10 {
|
|
||||||
t.Errorf("wanted to send %v, got %v", 10, n)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if *sesh.sb.Valve.(*LimitedValve).tx != 10 {
|
|
||||||
t.Error("tx credit didn't increase by 10")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,13 @@ type ClientInfo struct {
|
||||||
SessionId uint32
|
SessionId uint32
|
||||||
ProxyMethod string
|
ProxyMethod string
|
||||||
EncryptionMethod byte
|
EncryptionMethod byte
|
||||||
|
Unordered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
UNORDERED_FLAG = 0x01 // 0000 0001
|
||||||
|
)
|
||||||
|
|
||||||
var ErrReplay = errors.New("duplicate random")
|
var ErrReplay = errors.New("duplicate random")
|
||||||
var ErrInvalidPubKey = errors.New("public key has invalid format")
|
var ErrInvalidPubKey = errors.New("public key has invalid format")
|
||||||
var ErrCiphertextLength = errors.New("ciphertext has the wrong length")
|
var ErrCiphertextLength = errors.New("ciphertext has the wrong length")
|
||||||
|
|
@ -59,6 +64,7 @@ func TouchStone(ch *ClientHello, sta *State) (info ClientInfo, sharedSecret []by
|
||||||
SessionId: 0,
|
SessionId: 0,
|
||||||
ProxyMethod: string(bytes.Trim(plaintext[16:28], "\x00")),
|
ProxyMethod: string(bytes.Trim(plaintext[16:28], "\x00")),
|
||||||
EncryptionMethod: plaintext[28],
|
EncryptionMethod: plaintext[28],
|
||||||
|
Unordered: plaintext[41]&UNORDERED_FLAG != 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := int64(binary.BigEndian.Uint64(plaintext[29:37]))
|
timestamp := int64(binary.BigEndian.Uint64(plaintext[29:37]))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue