mirror of https://github.com/cbeuw/Cloak
More comments
This commit is contained in:
parent
c44a061cbe
commit
87a7684e10
|
|
@ -37,6 +37,8 @@ func addExtRec(typ []byte, data []byte) []byte {
|
|||
return ret
|
||||
}
|
||||
|
||||
// PrepareConnection handles the TLS handshake for a given conn and returns the sessionKey
|
||||
// if the server proceed with Cloak authentication
|
||||
func PrepareConnection(sta *State, conn net.Conn) (sessionKey []byte, err error) {
|
||||
hd, sharedSecret := makeHiddenData(sta)
|
||||
chOnly := sta.browser.composeClientHello(hd)
|
||||
|
|
@ -56,8 +58,8 @@ func PrepareConnection(sta *State, conn net.Conn) (sessionKey []byte, err error)
|
|||
|
||||
encrypted := append(buf[11:43], buf[89:121]...)
|
||||
nonce := encrypted[0:12]
|
||||
ciphertext := encrypted[12:60]
|
||||
sessionKey, err = util.AESGCMDecrypt(nonce, sharedSecret, ciphertext)
|
||||
ciphertextWithTag := encrypted[12:60]
|
||||
sessionKey, err = util.AESGCMDecrypt(nonce, sharedSecret, ciphertextWithTag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,19 @@ type chHiddenData struct {
|
|||
chExtSNI []byte
|
||||
}
|
||||
|
||||
// makeHiddenData generates the ephemeral key pair, calculates the shared secret, and then compose and
|
||||
// encrypt the Authentication data. It also composes SNI extension.
|
||||
func makeHiddenData(sta *State) (ret chHiddenData, 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] [1 byte flag] [6 bytes reserved] [16 bytes authentication tag]
|
||||
/*
|
||||
Authentication data:
|
||||
+----------+----------------+---------------------+-------------+--------------+--------+------------+
|
||||
| _UID_ | _Proxy Method_ | _Encryption Method_ | _Timestamp_ | _Session Id_ | _Flag_ | _reserved_ |
|
||||
+----------+----------------+---------------------+-------------+--------------+--------+------------+
|
||||
| 16 bytes | 12 bytes | 1 byte | 8 bytes | 4 bytes | 1 byte | 6 bytes |
|
||||
+----------+----------------+---------------------+-------------+--------------+--------+------------+
|
||||
*/
|
||||
// The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare
|
||||
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
||||
ret.chRandom = ecdh.Marshal(ephPub)
|
||||
|
||||
|
|
@ -38,9 +48,9 @@ func makeHiddenData(sta *State) (ret chHiddenData, sharedSecret []byte) {
|
|||
|
||||
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
||||
nonce := ret.chRandom[0:12]
|
||||
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
||||
ret.chSessionId = ciphertext[0:32]
|
||||
ret.chX25519KeyShare = ciphertext[32:64]
|
||||
ciphertextWithTag, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
||||
ret.chSessionId = ciphertextWithTag[0:32]
|
||||
ret.chX25519KeyShare = ciphertextWithTag[32:64]
|
||||
ret.chExtSNI = makeServerName(sta.ServerName)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Chrome 76
|
||||
// Fingerprint of Chrome 76
|
||||
|
||||
package client
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Firefox 68
|
||||
// Fingerprint of Firefox 68
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/ecdh"
|
||||
mux "github.com/cbeuw/Cloak/internal/multiplex"
|
||||
)
|
||||
|
||||
// rawConfig represents the fields in the config json file
|
||||
type rawConfig struct {
|
||||
ServerName string
|
||||
ProxyMethod string
|
||||
|
|
@ -23,7 +25,7 @@ type rawConfig struct {
|
|||
StreamTimeout int
|
||||
}
|
||||
|
||||
// State stores global variables
|
||||
// State stores the parsed configuration fields
|
||||
type State struct {
|
||||
LocalHost string
|
||||
LocalPort string
|
||||
|
|
@ -35,7 +37,7 @@ type State struct {
|
|||
UID []byte
|
||||
|
||||
staticPub crypto.PublicKey
|
||||
now func() time.Time
|
||||
now func() time.Time // for easier testing
|
||||
browser browser
|
||||
|
||||
ProxyMethod string
|
||||
|
|
@ -45,6 +47,7 @@ type State struct {
|
|||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// TODO: remove this and let the caller declare it directly
|
||||
func InitState(localHost, localPort, remoteHost, remotePort string, nowFunc func() time.Time) *State {
|
||||
ret := &State{
|
||||
LocalHost: localHost,
|
||||
|
|
@ -73,8 +76,8 @@ func ssvToJson(ssv string) (ret []byte) {
|
|||
sp := strings.SplitN(ln, "=", 2)
|
||||
key := sp[0]
|
||||
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
|
||||
// JSON doesn't like quotation marks around int and bool
|
||||
// This is extremely ugly but it's still better than writing a tokeniser
|
||||
if key == "NumConn" || key == "Unordered" || key == "StreamTimeout" {
|
||||
ret = append(ret, []byte(`"`+key+`":`+value+`,`)...)
|
||||
} else {
|
||||
|
|
@ -89,6 +92,7 @@ func ssvToJson(ssv string) (ret []byte) {
|
|||
// ParseConfig parses the config (either a path to json or Android config) into a State variable
|
||||
func (sta *State) ParseConfig(conf string) (err error) {
|
||||
var content []byte
|
||||
// Checking if it's a path to json or a ssv string
|
||||
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
|
||||
content = ssvToJson(conf)
|
||||
} else {
|
||||
|
|
@ -105,11 +109,11 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
|
||||
switch strings.ToLower(preParse.EncryptionMethod) {
|
||||
case "plain":
|
||||
sta.EncryptionMethod = 0x00
|
||||
sta.EncryptionMethod = mux.E_METHOD_PLAIN
|
||||
case "aes-gcm":
|
||||
sta.EncryptionMethod = 0x01
|
||||
sta.EncryptionMethod = mux.E_METHOD_AES_GCM
|
||||
case "chacha20-poly1305":
|
||||
sta.EncryptionMethod = 0x02
|
||||
sta.EncryptionMethod = mux.E_METHOD_CHACHA20_POLY1305
|
||||
default:
|
||||
return errors.New("Unknown encryption method")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
const BUF_SIZE_LIMIT = 1 << 20 * 500
|
||||
|
||||
// The point of a bufferedPipe is that Read() will block until data is available
|
||||
type bufferedPipe struct {
|
||||
buf *bytes.Buffer
|
||||
closed bool
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import (
|
|||
|
||||
const DATAGRAM_NUMBER_LIMIT = 1024
|
||||
|
||||
// datagramBuffer is the same as bufferedPipe with the exception that it's message-oriented,
|
||||
// instead of byte-oriented. The integrity of datagrams written into this buffer is preserved.
|
||||
// it won't get chopped up into individual bytes
|
||||
type datagramBuffer struct {
|
||||
buf [][]byte
|
||||
closed bool
|
||||
|
|
@ -36,6 +39,7 @@ func (d *datagramBuffer) Read(target []byte) (int, error) {
|
|||
}
|
||||
d.rwCond.Wait()
|
||||
}
|
||||
// TODO: return error if len(target) is smaller than the datagram
|
||||
var data []byte
|
||||
data, d.buf = d.buf[0], d.buf[1:]
|
||||
copy(target, data)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ const (
|
|||
|
||||
func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
|
||||
obfs := func(f *Frame, buf []byte) (int, error) {
|
||||
// we need the encrypted data to be at least 8 bytes to be used as nonce for salsa20 stream header encryption
|
||||
// this will be the case if the encryption method is an AEAD cipher, however for plain, it's well possible
|
||||
// that the frame payload is smaller than 8 bytes, so we need to add on the difference
|
||||
var extraLen uint8
|
||||
if payloadCipher == nil {
|
||||
if len(f.Payload) < 8 {
|
||||
|
|
@ -37,14 +40,29 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
|
|||
extraLen = uint8(payloadCipher.Overhead())
|
||||
}
|
||||
|
||||
// usefulLen is the amount of bytes that will be eventually sent off
|
||||
usefulLen := 5 + HEADER_LEN + len(f.Payload) + int(extraLen)
|
||||
if len(buf) < usefulLen {
|
||||
return 0, errors.New("buffer is too small")
|
||||
|
||||
}
|
||||
// we do as much in-place as possible to save allocation
|
||||
useful := buf[:usefulLen] // tls header + payload + potential overhead
|
||||
recordLayer := useful[0:5]
|
||||
header := useful[5 : 5+HEADER_LEN]
|
||||
encryptedPayload := useful[5+HEADER_LEN:]
|
||||
encryptedPayloadWithExtra := useful[5+HEADER_LEN:]
|
||||
|
||||
// TODO: Once Seq wraps around, the chance of a nonce reuse will be 1/65536 which is unacceptably low
|
||||
// prohibit Seq wrap around? simple solution : 2^32 messages per stream may be too little
|
||||
//
|
||||
// use uint64 Seq? Vastly reduces the complexity of frameSorter : concern with 64 bit number performance on
|
||||
// embedded systems (frameSorter already has a non-trivial performance impact on RPi2B, can only be worse on
|
||||
// mipsle). HOWEVER since frameSorter already deals with uint64, prehaps changing it totally wouldn't matter much?
|
||||
//
|
||||
// regular rekey? Improves security in general : when to rekey? Not easy to synchronise, also will add a decent
|
||||
// amount of complexity
|
||||
//
|
||||
// LEANING TOWARDS uint64 Seq. Adds extra 2 bytes of overhead but shouldn't really matter that much
|
||||
|
||||
// header: [StreamID 4 bytes][Seq 4 bytes][Closing 1 byte][extraLen 1 bytes][random 2 bytes]
|
||||
putU32(header[0:4], f.StreamID)
|
||||
|
|
@ -54,16 +72,16 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
|
|||
prand.Read(header[10:12])
|
||||
|
||||
if payloadCipher == nil {
|
||||
copy(encryptedPayload, f.Payload)
|
||||
copy(encryptedPayloadWithExtra, f.Payload)
|
||||
if extraLen != 0 {
|
||||
rand.Read(encryptedPayload[len(encryptedPayload)-int(extraLen):])
|
||||
rand.Read(encryptedPayloadWithExtra[len(encryptedPayloadWithExtra)-int(extraLen):])
|
||||
}
|
||||
} else {
|
||||
ciphertext := payloadCipher.Seal(nil, header, f.Payload, nil)
|
||||
copy(encryptedPayload, ciphertext)
|
||||
copy(encryptedPayloadWithExtra, ciphertext)
|
||||
}
|
||||
|
||||
nonce := encryptedPayload[len(encryptedPayload)-8:]
|
||||
nonce := encryptedPayloadWithExtra[len(encryptedPayloadWithExtra)-8:]
|
||||
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
|
||||
|
||||
// Composing final obfsed message
|
||||
|
|
@ -71,7 +89,7 @@ func MakeObfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Obfser {
|
|||
recordLayer[0] = 0x17
|
||||
recordLayer[1] = 0x03
|
||||
recordLayer[2] = 0x03
|
||||
binary.BigEndian.PutUint16(recordLayer[3:5], uint16(HEADER_LEN+len(encryptedPayload)))
|
||||
binary.BigEndian.PutUint16(recordLayer[3:5], uint16(HEADER_LEN+len(encryptedPayloadWithExtra)))
|
||||
return usefulLen, nil
|
||||
}
|
||||
return obfs
|
||||
|
|
@ -87,7 +105,7 @@ func MakeDeobfs(salsaKey [32]byte, payloadCipher cipher.AEAD) Deobfser {
|
|||
copy(peeled, in[5:])
|
||||
|
||||
header := peeled[:12]
|
||||
pldWithOverHead := peeled[12:] // plaintext + potential overhead
|
||||
pldWithOverHead := peeled[12:] // payload + potential overhead
|
||||
|
||||
nonce := peeled[len(peeled)-8:]
|
||||
salsa20.XORKeyStream(header, header, nonce, &salsaKey)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const (
|
|||
var ErrBrokenSession = errors.New("broken session")
|
||||
var errRepeatSessionClosing = errors.New("trying to close a closed session")
|
||||
|
||||
// Obfuscator is responsible for the obfuscation and deobfuscation of frames
|
||||
type Obfuscator struct {
|
||||
// Used in Stream.Write. Add multiplexing headers, encrypt and add TLS header
|
||||
Obfs Obfser
|
||||
|
|
@ -33,7 +34,7 @@ type SessionConfig struct {
|
|||
|
||||
Valve
|
||||
|
||||
// This is supposed to read one TLS message, the same as GoQuiet's ReadTillDrain
|
||||
// This is supposed to read one TLS message.
|
||||
UnitRead func(net.Conn, []byte) (int, error)
|
||||
|
||||
Unordered bool
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import (
|
|||
|
||||
var ErrBrokenStream = errors.New("broken stream")
|
||||
|
||||
// ReadWriteCloseLener is io.ReadWriteCloser with Len()
|
||||
// used for bufferedPipe and datagramBuffer
|
||||
type ReadWriteCloseLener interface {
|
||||
io.ReadWriteCloser
|
||||
Len() int
|
||||
|
|
@ -34,6 +36,7 @@ type Stream struct {
|
|||
|
||||
writingM sync.RWMutex
|
||||
|
||||
// atomic
|
||||
closed uint32
|
||||
|
||||
obfsBuf []byte
|
||||
|
|
@ -41,6 +44,7 @@ type Stream struct {
|
|||
// we assign each stream a fixed underlying TCP connection to utilise order guarantee provided by TCP itself
|
||||
// so that frameSorter should have few to none ooo frames to deal with
|
||||
// overall the streams in a session should be uniformly distributed across all connections
|
||||
// This is not used in unordered connection mode
|
||||
assignedConnId uint32
|
||||
}
|
||||
|
||||
|
|
@ -64,8 +68,6 @@ func makeStream(sesh *Session, id uint32, assignedConnId uint32) *Stream {
|
|||
return stream
|
||||
}
|
||||
|
||||
//func (s *Stream) reassignConnId(connId uint32) { atomic.StoreUint32(&s.assignedConnId,connId)}
|
||||
|
||||
func (s *Stream) isClosed() bool { return atomic.LoadUint32(&s.closed) == 1 }
|
||||
|
||||
func (s *Stream) writeFrame(frame *Frame) {
|
||||
|
|
@ -77,6 +79,7 @@ func (s *Stream) writeFrame(frame *Frame) {
|
|||
}
|
||||
}
|
||||
|
||||
// Read implements io.Read
|
||||
func (s *Stream) Read(buf []byte) (n int, err error) {
|
||||
//log.Tracef("attempting to read from stream %v", s.id)
|
||||
if len(buf) == 0 {
|
||||
|
|
@ -103,6 +106,7 @@ func (s *Stream) Read(buf []byte) (n int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Write implements io.Write
|
||||
func (s *Stream) Write(in []byte) (n int, err error) {
|
||||
// RWMutex used here isn't really for RW.
|
||||
// we use it to exploit the fact that RLock doesn't create contention.
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ func (sb *switchboard) send(data []byte, connId *uint32) (n int, err error) {
|
|||
|
||||
}
|
||||
|
||||
// returns a random connId
|
||||
func (sb *switchboard) assignRandomConn() (uint32, error) {
|
||||
sb.connsM.RLock()
|
||||
defer sb.connsM.RUnlock()
|
||||
|
|
@ -124,6 +125,7 @@ func (sb *switchboard) assignRandomConn() (uint32, error) {
|
|||
}
|
||||
|
||||
// actively triggered by session.Close()
|
||||
// TODO: closeALl needs to clear the conns map
|
||||
func (sb *switchboard) closeAll() {
|
||||
if atomic.SwapUint32(&sb.broken, 1) == 1 {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -199,9 +199,8 @@ func composeServerHello(sessionId []byte, sharedSecret []byte, sessionKey []byte
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// composeReply composes the ServerHello, ChangeCipherSpec and Finished messages
|
||||
// together with their respective record layers into one byte slice. The content
|
||||
// of these messages are random and useless for this plugin
|
||||
// composeReply composes the ServerHello, ChangeCipherSpec and an ApplicationData messages
|
||||
// together with their respective record layers into one byte slice.
|
||||
func composeReply(ch *ClientHello, sharedSecret []byte, sessionKey []byte) ([]byte, error) {
|
||||
TLS12 := []byte{0x03, 0x03}
|
||||
sh, err := composeServerHello(ch.sessionId, sharedSecret, sessionKey)
|
||||
|
|
@ -223,6 +222,10 @@ var ErrNotCloak = errors.New("TLS but non-Cloak ClientHello")
|
|||
var ErrReplay = errors.New("duplicate random")
|
||||
var ErrBadProxyMethod = errors.New("invalid proxy method")
|
||||
|
||||
// PrepareConnection checks if the first packet of data is ClientHello, and checks if it was from a Cloak client
|
||||
// if it is from a Cloak client, it returns the ClientInfo with the decrypted fields. It doesn't check if the user
|
||||
// is authorised. It also returns a finisher callback function to be called when the caller wishes to proceed with
|
||||
// the handshake
|
||||
func PrepareConnection(firstPacket []byte, sta *State, conn net.Conn) (info ClientInfo, finisher func([]byte) error, err error) {
|
||||
ch, err := parseClientHello(firstPacket)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type ActiveUser struct {
|
|||
sessions map[uint32]*mux.Session
|
||||
}
|
||||
|
||||
// DeleteSession closes a session and removes its reference from the user
|
||||
func (u *ActiveUser) DeleteSession(sessionID uint32, reason string) {
|
||||
u.sessionsM.Lock()
|
||||
sesh, existing := u.sessions[sessionID]
|
||||
|
|
@ -34,6 +35,9 @@ func (u *ActiveUser) DeleteSession(sessionID uint32, reason string) {
|
|||
u.sessionsM.Unlock()
|
||||
}
|
||||
|
||||
// GetSession returns the reference to an existing session, or if one such session doesn't exist, it queries
|
||||
// the UserManager for the authorisation for a new session. If a new session is allowed, it creates this new session
|
||||
// and returns its reference
|
||||
func (u *ActiveUser) GetSession(sessionID uint32, config *mux.SessionConfig) (sesh *mux.Session, existing bool, err error) {
|
||||
u.sessionsM.Lock()
|
||||
defer u.sessionsM.Unlock()
|
||||
|
|
@ -54,6 +58,7 @@ func (u *ActiveUser) GetSession(sessionID uint32, config *mux.SessionConfig) (se
|
|||
}
|
||||
}
|
||||
|
||||
// Terminate closes all sessions of this active user
|
||||
func (u *ActiveUser) Terminate(reason string) {
|
||||
u.sessionsM.Lock()
|
||||
for _, sesh := range u.sessions {
|
||||
|
|
@ -66,6 +71,7 @@ func (u *ActiveUser) Terminate(reason string) {
|
|||
u.panel.DeleteActiveUser(u)
|
||||
}
|
||||
|
||||
// NumSession returns the number of active sessions
|
||||
func (u *ActiveUser) NumSession() int {
|
||||
u.sessionsM.RLock()
|
||||
defer u.sessionsM.RUnlock()
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ var ErrInvalidPubKey = errors.New("public key has invalid format")
|
|||
var ErrCiphertextLength = errors.New("ciphertext has the wrong length")
|
||||
var ErrTimestampOutOfWindow = errors.New("timestamp is outside of the accepting window")
|
||||
|
||||
// touchStone checks if a ClientHello came from a Cloak client by checking and decrypting the fields Cloak hides data in
|
||||
// It returns the ClientInfo, but it doesn't check if the UID is authorised
|
||||
func touchStone(ch *ClientHello, staticPv crypto.PrivateKey, now func() time.Time) (info ClientInfo, sharedSecret []byte, err error) {
|
||||
ephPub, ok := ecdh.Unmarshal(ch.random)
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func InitState(bindHost, bindPort string, nowFunc func() time.Time) (*State, err
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// ParseConfig parses the config (either a path to json or in-line ssv config) into a State variable
|
||||
// ParseConfig parses the config (either a path to json or the json itself as argument) into a State variable
|
||||
func (sta *State) ParseConfig(conf string) (err error) {
|
||||
var content []byte
|
||||
var preParse rawConfig
|
||||
|
|
@ -83,7 +83,6 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
|
||||
if preParse.CncMode {
|
||||
//TODO: implement command & control mode
|
||||
|
||||
} else {
|
||||
manager, err := usermanager.MakeLocalManager(preParse.DatabasePath)
|
||||
if err != nil {
|
||||
|
|
@ -143,6 +142,7 @@ func (sta *State) ParseConfig(conf string) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// IsBypass checks if a UID is a bypass user
|
||||
func (sta *State) IsBypass(UID []byte) bool {
|
||||
var arrUID [16]byte
|
||||
copy(arrUID[:], UID)
|
||||
|
|
@ -154,7 +154,7 @@ const TIMESTAMP_TOLERANCE = 180 * time.Second
|
|||
|
||||
const CACHE_CLEAN_INTERVAL = 12 * time.Hour
|
||||
|
||||
// UsedRandomCleaner clears the cache of used random fields every 12 hours
|
||||
// UsedRandomCleaner clears the cache of used random fields every CACHE_CLEAN_INTERVAL
|
||||
func (sta *State) UsedRandomCleaner() {
|
||||
for {
|
||||
time.Sleep(CACHE_CLEAN_INTERVAL)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ func i32ToB(value int32) []byte {
|
|||
return nib
|
||||
}
|
||||
|
||||
// localManager is responsible for routing API calls to appropriate handlers and manage the local user database accordingly
|
||||
type localManager struct {
|
||||
db *bolt.DB
|
||||
Router *gmux.Router
|
||||
|
|
@ -63,6 +64,8 @@ func (manager *localManager) registerMux() *gmux.Router {
|
|||
return r
|
||||
}
|
||||
|
||||
// Authenticate user returns err==nil along with the users' up and down bandwidths if the UID is allowed to connect
|
||||
// More specifically it checks that the user exists, that it has positive credit and that it hasn't expired
|
||||
func (manager *localManager) AuthenticateUser(UID []byte) (int64, int64, error) {
|
||||
var upRate, downRate, upCredit, downCredit, expiryTime int64
|
||||
err := manager.db.View(func(tx *bolt.Tx) error {
|
||||
|
|
@ -93,6 +96,8 @@ func (manager *localManager) AuthenticateUser(UID []byte) (int64, int64, error)
|
|||
return upRate, downRate, nil
|
||||
}
|
||||
|
||||
// AuthoriseNewSession returns err==nil when the user is allowed to make a new session
|
||||
// More specifically it checks that the user exists, has credit, hasn't expired and hasn't reached sessionsCap
|
||||
func (manager *localManager) AuthoriseNewSession(UID []byte, ainfo AuthorisationInfo) error {
|
||||
var arrUID [16]byte
|
||||
copy(arrUID[:], UID)
|
||||
|
|
@ -128,6 +133,9 @@ func (manager *localManager) AuthoriseNewSession(UID []byte, ainfo Authorisation
|
|||
return nil
|
||||
}
|
||||
|
||||
// UploadStatus gets StatusUpdates representing the recent status of each user, and update them in the database
|
||||
// it returns a slice of StatusResponse, which represents actions need to be taken for specific users.
|
||||
// If no action is needed, there won't be a StatusResponse entry for that user
|
||||
func (manager *localManager) UploadStatus(uploads []StatusUpdate) ([]StatusResponse, error) {
|
||||
var responses []StatusResponse
|
||||
err := manager.db.Update(func(tx *bolt.Tx) error {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ func MakeUserPanel(manager usermanager.UserManager) *userPanel {
|
|||
return ret
|
||||
}
|
||||
|
||||
// GetBypassUser does the same as GetUser except it unconditionally creates an ActiveUser when the UID isn't already active
|
||||
func (panel *userPanel) GetBypassUser(UID []byte) (*ActiveUser, error) {
|
||||
panel.activeUsersM.Lock()
|
||||
var arrUID [16]byte
|
||||
|
|
@ -49,6 +50,8 @@ func (panel *userPanel) GetBypassUser(UID []byte) (*ActiveUser, error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
// GetUser retrieves the reference to an ActiveUser if it's already active, or creates a new ActiveUser of specified
|
||||
// UID with UserInfo queried from the UserManger, should the particular UID is allowed to connect
|
||||
func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
|
||||
panel.activeUsersM.Lock()
|
||||
var arrUID [16]byte
|
||||
|
|
@ -76,7 +79,9 @@ func (panel *userPanel) GetUser(UID []byte) (*ActiveUser, error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
// DeleteActiveUser deletes the references to the active user
|
||||
func (panel *userPanel) DeleteActiveUser(user *ActiveUser) {
|
||||
// TODO: terminate the user here?
|
||||
panel.updateUsageQueueForOne(user)
|
||||
panel.activeUsersM.Lock()
|
||||
delete(panel.activeUsers, user.arrUID)
|
||||
|
|
@ -97,6 +102,7 @@ type usagePair struct {
|
|||
down *int64
|
||||
}
|
||||
|
||||
// updateUsageQueue zeroes the accumulated usage all ActiveUsers valve and put the usage data im usageUpdateQueue
|
||||
func (panel *userPanel) updateUsageQueue() {
|
||||
panel.activeUsersM.Lock()
|
||||
panel.usageUpdateQueueM.Lock()
|
||||
|
|
@ -119,6 +125,8 @@ func (panel *userPanel) updateUsageQueue() {
|
|||
panel.usageUpdateQueueM.Unlock()
|
||||
}
|
||||
|
||||
// updateUsageQueueForOne is the same as updateUsageQueue except it only updates one user's usage
|
||||
// this is useful when the user is being terminated
|
||||
func (panel *userPanel) updateUsageQueueForOne(user *ActiveUser) {
|
||||
// used when one particular user deactivates
|
||||
if user.bypass {
|
||||
|
|
@ -137,6 +145,8 @@ func (panel *userPanel) updateUsageQueueForOne(user *ActiveUser) {
|
|||
|
||||
}
|
||||
|
||||
// commitUpdate put all usageUpdates into a slice of StatusUpdate, calls Manager.UploadStatus, gets the responses
|
||||
// and act to each user according to the responses
|
||||
func (panel *userPanel) commitUpdate() error {
|
||||
panel.usageUpdateQueueM.Lock()
|
||||
statuses := make([]usermanager.StatusUpdate, 0, len(panel.usageUpdateQueue))
|
||||
|
|
|
|||
|
|
@ -58,11 +58,14 @@ func ReadTLS(conn net.Conn, buffer []byte) (n int, err error) {
|
|||
left := dataLength
|
||||
readPtr := 5
|
||||
|
||||
// TODO: Deadline here?
|
||||
for left != 0 {
|
||||
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
|
||||
// if left = buffer size, the entire buffer is all there left to read
|
||||
// if left < buffer size (i.e. multiple messages came together),
|
||||
// only the message we want is read
|
||||
|
||||
// TODO: Why ReadFull here? Shouldn't it be just normal read since we adjust left and readPtr according to read amount?
|
||||
i, err = io.ReadFull(conn, buffer[readPtr:readPtr+left])
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
|||
Loading…
Reference in New Issue