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"
|
||||
)
|
||||
|
||||
const (
|
||||
UNORDERED_FLAG = 0x01 // 0000 0001
|
||||
)
|
||||
|
||||
func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) {
|
||||
// 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)
|
||||
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.PutUint32(plaintext[37:41], atomic.LoadUint32(&sta.SessionID))
|
||||
|
||||
if sta.Unordered {
|
||||
plaintext[41] |= UNORDERED_FLAG
|
||||
}
|
||||
|
||||
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
||||
nonce := random[0:12]
|
||||
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ type rawConfig struct {
|
|||
PublicKey string
|
||||
BrowserSig string
|
||||
Unordered bool
|
||||
|
||||
NumConn int
|
||||
NumConn int
|
||||
}
|
||||
|
||||
// State stores global variables
|
||||
|
|
@ -42,6 +41,7 @@ type State struct {
|
|||
EncryptionMethod byte
|
||||
ServerName string
|
||||
NumConn int
|
||||
Unordered bool
|
||||
}
|
||||
|
||||
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]
|
||||
// JSON doesn't like quotation marks around int
|
||||
// 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+`,`)...)
|
||||
} else {
|
||||
ret = append(ret, []byte(`"`+key+`":"`+value+`",`)...)
|
||||
|
|
@ -125,6 +125,7 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
sta.ProxyMethod = preParse.ProxyMethod
|
||||
sta.ServerName = preParse.ServerName
|
||||
sta.NumConn = preParse.NumConn
|
||||
sta.Unordered = preParse.Unordered
|
||||
|
||||
uid, err := base64.StdEncoding.DecodeString(preParse.UID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -25,12 +25,7 @@ type Obfuscator struct {
|
|||
SessionKey []byte
|
||||
}
|
||||
|
||||
type SwitchboardStrategy int
|
||||
|
||||
const (
|
||||
FixedConnMapping SwitchboardStrategy = iota
|
||||
Uniform
|
||||
)
|
||||
type switchboardStrategy int
|
||||
|
||||
type SessionConfig struct {
|
||||
*Obfuscator
|
||||
|
|
@ -41,8 +36,6 @@ type SessionConfig struct {
|
|||
UnitRead func(net.Conn, []byte) (int, error)
|
||||
|
||||
Unordered bool
|
||||
|
||||
SwitchboardStrategy SwitchboardStrategy
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
|
|
@ -84,9 +77,12 @@ func MakeSession(id uint32, config *SessionConfig) *Session {
|
|||
}
|
||||
|
||||
sbConfig := &switchboardConfig{
|
||||
Valve: config.Valve,
|
||||
unordered: config.Unordered,
|
||||
strategy: config.SwitchboardStrategy,
|
||||
Valve: config.Valve,
|
||||
}
|
||||
if config.Unordered {
|
||||
sbConfig.strategy = UNIFORM_SPREAD
|
||||
} else {
|
||||
sbConfig.strategy = FIXED_CONN_MAPPING
|
||||
}
|
||||
sesh.sb = makeSwitchboard(sesh, sbConfig)
|
||||
go sesh.timeoutAfter(30 * time.Second)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,14 @@ import (
|
|||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
FIXED_CONN_MAPPING switchboardStrategy = iota
|
||||
UNIFORM_SPREAD
|
||||
)
|
||||
|
||||
type switchboardConfig struct {
|
||||
Valve
|
||||
unordered bool
|
||||
strategy SwitchboardStrategy
|
||||
strategy switchboardStrategy
|
||||
}
|
||||
|
||||
// switchboard is responsible for keeping the reference of TLS connections between client and server
|
||||
|
|
@ -64,34 +68,39 @@ func (sb *switchboard) removeConn(connId uint32) {
|
|||
|
||||
// a pointer to connId is passed here so that the switchboard can reassign it
|
||||
func (sb *switchboard) send(data []byte, connId *uint32) (int, error) {
|
||||
sb.Valve.rxWait(len(data))
|
||||
sb.connsM.RLock()
|
||||
defer sb.connsM.RUnlock()
|
||||
var conn net.Conn
|
||||
conn, ok := sb.conns[*connId]
|
||||
if ok {
|
||||
n, err := conn.Write(data)
|
||||
sb.Valve.AddTx(int64(n))
|
||||
return n, err
|
||||
} else {
|
||||
// do not call assignRandomConn() here.
|
||||
// we'll have to do connsM.RLock() after we get a new connId from assignRandomConn, in order to
|
||||
// get the new conn through conns[newConnId]
|
||||
// however between connsM.RUnlock() in assignRandomConn and our call to connsM.RLock(), things may happen.
|
||||
// in particular if newConnId is removed between the RUnlock and RLock, conns[newConnId] will return
|
||||
// a nil pointer. To prevent this we must get newConnId and the reference to conn itself in one single mutex
|
||||
// protection
|
||||
if atomic.LoadUint32(&sb.broken) == 1 || len(sb.conns) == 0 {
|
||||
return 0, errBrokenSwitchboard
|
||||
}
|
||||
newConnId := rand.Intn(len(sb.conns))
|
||||
conn, ok = sb.conns[uint32(newConnId)]
|
||||
if sb.strategy == UNIFORM_SPREAD {
|
||||
randConnId := rand.Intn(len(sb.conns))
|
||||
conn, ok := sb.conns[uint32(randConnId)]
|
||||
if !ok {
|
||||
return 0, errBrokenSwitchboard
|
||||
} else {
|
||||
n, err := conn.Write(data)
|
||||
sb.Valve.AddTx(int64(n))
|
||||
return n, err
|
||||
return conn.Write(data)
|
||||
}
|
||||
} else {
|
||||
var conn net.Conn
|
||||
conn, ok := sb.conns[*connId]
|
||||
if ok {
|
||||
return conn.Write(data)
|
||||
} else {
|
||||
// do not call assignRandomConn() here.
|
||||
// we'll have to do connsM.RLock() after we get a new connId from assignRandomConn, in order to
|
||||
// get the new conn through conns[newConnId]
|
||||
// however between connsM.RUnlock() in assignRandomConn and our call to connsM.RLock(), things may happen.
|
||||
// in particular if newConnId is removed between the RUnlock and RLock, conns[newConnId] will return
|
||||
// a nil pointer. To prevent this we must get newConnId and the reference to conn itself in one single mutex
|
||||
// protection
|
||||
if atomic.LoadUint32(&sb.broken) == 1 || len(sb.conns) == 0 {
|
||||
return 0, errBrokenSwitchboard
|
||||
}
|
||||
newConnId := rand.Intn(len(sb.conns))
|
||||
conn, ok = sb.conns[uint32(newConnId)]
|
||||
if !ok {
|
||||
return 0, errBrokenSwitchboard
|
||||
} else {
|
||||
return conn.Write(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package multiplex
|
||||
|
||||
import (
|
||||
"github.com/cbeuw/Cloak/internal/util"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -10,13 +9,17 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
|||
seshConfig := &SessionConfig{
|
||||
Obfuscator: nil,
|
||||
Valve: nil,
|
||||
UnitRead: util.ReadTLS,
|
||||
UnitRead: nil,
|
||||
}
|
||||
sesh := MakeSession(0, seshConfig)
|
||||
|
||||
sbConfig := &switchboardConfig{
|
||||
Valve: UNLIMITED_VALVE,
|
||||
strategy: FIXED_CONN_MAPPING,
|
||||
}
|
||||
sb := makeSwitchboard(sesh, sbConfig)
|
||||
hole := newBlackHole()
|
||||
sesh.sb.addConn(hole)
|
||||
connId, err := sesh.sb.assignRandomConn()
|
||||
sb.addConn(hole)
|
||||
connId, err := sb.assignRandomConn()
|
||||
if err != nil {
|
||||
b.Error("failed to get a random conn", err)
|
||||
return
|
||||
|
|
@ -25,7 +28,7 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
|||
rand.Read(data)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
n, err := sesh.sb.send(data, &connId)
|
||||
n, err := sb.send(data, &connId)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
|
|
@ -33,34 +36,3 @@ func BenchmarkSwitchboard_Send(b *testing.B) {
|
|||
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
|
||||
ProxyMethod string
|
||||
EncryptionMethod byte
|
||||
Unordered bool
|
||||
}
|
||||
|
||||
const (
|
||||
UNORDERED_FLAG = 0x01 // 0000 0001
|
||||
)
|
||||
|
||||
var ErrReplay = errors.New("duplicate random")
|
||||
var ErrInvalidPubKey = errors.New("public key has invalid format")
|
||||
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,
|
||||
ProxyMethod: string(bytes.Trim(plaintext[16:28], "\x00")),
|
||||
EncryptionMethod: plaintext[28],
|
||||
Unordered: plaintext[41]&UNORDERED_FLAG != 0,
|
||||
}
|
||||
|
||||
timestamp := int64(binary.BigEndian.Uint64(plaintext[29:37]))
|
||||
|
|
|
|||
Loading…
Reference in New Issue