From 87a7684e1007516940d9c89a7ad94e9667bb24d1 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Tue, 20 Aug 2019 22:43:04 +0100 Subject: [PATCH] More comments --- internal/client/TLS.go | 6 ++-- internal/client/auth.go | 18 +++++++++--- internal/client/chrome.go | 2 +- internal/client/firefox.go | 3 +- internal/client/state.go | 18 +++++++----- internal/multiplex/bufferedPipe.go | 1 + internal/multiplex/datagramBuffer.go | 4 +++ internal/multiplex/obfs.go | 32 ++++++++++++++++----- internal/multiplex/session.go | 3 +- internal/multiplex/stream.go | 8 ++++-- internal/multiplex/switchboard.go | 2 ++ internal/server/TLS.go | 9 ++++-- internal/server/activeuser.go | 6 ++++ internal/server/auth.go | 2 ++ internal/server/state.go | 6 ++-- internal/server/usermanager/localmanager.go | 8 ++++++ internal/server/userpanel.go | 10 +++++++ internal/util/util.go | 3 ++ 18 files changed, 110 insertions(+), 31 deletions(-) diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 189d1a0..56f2dfb 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -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 } diff --git a/internal/client/auth.go b/internal/client/auth.go index 928fcfd..3fdebf3 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -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 } diff --git a/internal/client/chrome.go b/internal/client/chrome.go index 2eccb0e..02544e9 100644 --- a/internal/client/chrome.go +++ b/internal/client/chrome.go @@ -1,4 +1,4 @@ -// Chrome 76 +// Fingerprint of Chrome 76 package client diff --git a/internal/client/firefox.go b/internal/client/firefox.go index 99a9399..b7547ac 100644 --- a/internal/client/firefox.go +++ b/internal/client/firefox.go @@ -1,4 +1,5 @@ -// Firefox 68 +// Fingerprint of Firefox 68 + package client import ( diff --git a/internal/client/state.go b/internal/client/state.go index 28d87cc..b438df5 100644 --- a/internal/client/state.go +++ b/internal/client/state.go @@ -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") } diff --git a/internal/multiplex/bufferedPipe.go b/internal/multiplex/bufferedPipe.go index 4b940a4..12da757 100644 --- a/internal/multiplex/bufferedPipe.go +++ b/internal/multiplex/bufferedPipe.go @@ -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 diff --git a/internal/multiplex/datagramBuffer.go b/internal/multiplex/datagramBuffer.go index 6d27af9..38d8ce3 100644 --- a/internal/multiplex/datagramBuffer.go +++ b/internal/multiplex/datagramBuffer.go @@ -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) diff --git a/internal/multiplex/obfs.go b/internal/multiplex/obfs.go index b71e8a0..fba8c62 100644 --- a/internal/multiplex/obfs.go +++ b/internal/multiplex/obfs.go @@ -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) diff --git a/internal/multiplex/session.go b/internal/multiplex/session.go index f17189e..4c51845 100644 --- a/internal/multiplex/session.go +++ b/internal/multiplex/session.go @@ -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 diff --git a/internal/multiplex/stream.go b/internal/multiplex/stream.go index 83f3efd..bd9d305 100644 --- a/internal/multiplex/stream.go +++ b/internal/multiplex/stream.go @@ -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. diff --git a/internal/multiplex/switchboard.go b/internal/multiplex/switchboard.go index b4622b9..5449f7f 100644 --- a/internal/multiplex/switchboard.go +++ b/internal/multiplex/switchboard.go @@ -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 diff --git a/internal/server/TLS.go b/internal/server/TLS.go index 7b35b5e..dcb7b63 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -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 { diff --git a/internal/server/activeuser.go b/internal/server/activeuser.go index 48d6ebe..280b32b 100644 --- a/internal/server/activeuser.go +++ b/internal/server/activeuser.go @@ -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() diff --git a/internal/server/auth.go b/internal/server/auth.go index 82be4dc..1afa14f 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -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 { diff --git a/internal/server/state.go b/internal/server/state.go index 2343f24..172c70e 100644 --- a/internal/server/state.go +++ b/internal/server/state.go @@ -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) diff --git a/internal/server/usermanager/localmanager.go b/internal/server/usermanager/localmanager.go index 1814dd0..58958ff 100644 --- a/internal/server/usermanager/localmanager.go +++ b/internal/server/usermanager/localmanager.go @@ -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 { diff --git a/internal/server/userpanel.go b/internal/server/userpanel.go index 73987cf..2ba2d75 100644 --- a/internal/server/userpanel.go +++ b/internal/server/userpanel.go @@ -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)) diff --git a/internal/util/util.go b/internal/util/util.go index 44ca514..8fbd236 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -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