Home > Enterprise >  Proper handling of reading and writing with a net.Conn connection
Proper handling of reading and writing with a net.Conn connection

Time:11-11

I am trying to read and write using a network connection. What seems to be happening is some data is being discarded or lost if data comes in too quick to the server. The client connects, negotiates a connection, then I have it send 3 commands in quick succession to update a status page. Each communication is a JSON string that is translated to a struct and decoded with a stored key.

If I click the request on the client several times (each generating 3 \n-terminated JSON payloads), the server at some point will throw an ERROR: invalid character X after top-level value. I dumped the information being sent by the client and it looks like 3 properly formed JSON entries on the client side; on the server side, it looks like one of the JSON payloads is missing completely and one of them is missing the first 515 characters. Because the first 515 characters are missing, the JSON is malformed, therefore the marshaling fails.

My question is, what can I do to prevent the loss of data reads from the connection? Am I hitting some kind of race condition or mishandling how to read and send on the connection?

Below is basically what I'm using for the client connection handler. The client and server negotiate an encrypted connection so there are several references to mode and RSA status, and mode 4 is used when the keys are properly set so the server and client can exchange commands and results. At a high level, the handler spins off a goroutine that reads from the connection and sends it to a channel. That string is read and converted to a struct. The first part of the session is dedicated to a "handshake" to negotiate an encryption key and save the session information; once stage 4 is reached the struct carries encrypted commands from the client and sends the results back until the connection errors or is closed.

func HandleClientConnection(conClient net.Conn) {
    defer conClient.Close()

    chnLogging <- "Connection from "   conClient.RemoteAddr().String()

    tmTimeout := time.NewTimer(time.Minute * SERVER_INACTIVITY_TIMEOUT_MINUTES)
    chnCloseConn := make(chan bool)

    chnDataFromClient := make(chan string, 1000)
    go func(chnData chan string) {

        for {
            netData, err := bufio.NewReader(conClient).ReadString('\n')
            if err != nil {
                if !strings.Contains(err.Error(), "EOF") {
                    chnLogging <- "Error from client "   conClient.RemoteAddr().String()   ": "   err.Error()
                } else {
                    chnLogging <- "Client "   conClient.RemoteAddr().String()   " disconnected"
                }
                chnCloseConn <- true
                return
            }

            tmTimeout.Stop()
            tmTimeout.Reset(time.Minute * SERVER_INACTIVITY_TIMEOUT_MINUTES)

            chnData <- netData
        }
    }(chnDataFromClient)

    for {
        select {
        case <-chnCloseConn:
            chnLogging <- "Connection listener exiting for "   conClient.RemoteAddr().String()
            return
        case <-tmTimeout.C:
            chnLogging <- "Connection Timeout for "   conClient.RemoteAddr().String()
            return
        case strNetData := <-chnDataFromClient:

            var strctNetEncrypted stctNetEncrypted
            err := json.Unmarshal([]byte(strNetData), &strctNetEncrypted)
            CheckErr(err)

            switch strctNetEncrypted.IntMode {
            case 1:

                keyPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
                CheckErr(err)

                btServerPrivateKey, err := json.Marshal(keyPrivateKey)
                CheckErr(err)

                strctClientPubKeys.SetClientPubkey(strctNetEncrypted.BtPubKey, btServerPrivateKey)

                defer strctClientPubKeys.DelClientPubkey(strctNetEncrypted.BtPubKey)

                strctConnections.SetConnection(strctNetEncrypted.BtPubKey, conClient)

                defer strctConnections.DelConnection(strctNetEncrypted.BtPubKey)

                strctNetResponse := CreateStctNetEncryptedToClient("", strctNetEncrypted.BtPubKey, 2)
                if strctNetResponse.BtPubKey == nil ||
                    strctNetResponse.BtRemotePubKey == nil {
                    chnLogging <- "Error generating stage two response struct"
                    chnCloseConn <- true
                    return
                }

                btJSON, err := json.Marshal(strctNetResponse)
                CheckErr(err)
                chnLogging <- "Sending stage 2 negotation response"
                conClient.Write(btJSON)
                conClient.Write([]byte("\n"))

            case 2:

                chnLogging <- "WARNING: Received mode 2 network communication even though I shouldn't have"

            case 3:

                chnLogging <- "Received stage 3 negotiation response"

                strMessage, err := strctNetEncrypted.RSADecrypt()
                CheckErr(err)

                if len(strMessage) != 32 {
                    chnLogging <- "Unexpected shared key length; Aborting"
                    chnCloseConn <- true
                    return
                }

                strctClientPubKeys.SetClientSharedKey(strMessage, strctNetEncrypted.BtPubKey, conClient.RemoteAddr().String())

            case 4:

                strMessageDecrypted := DecryptPayloadFromClient(strctNetEncrypted)

                if strMessageDecrypted != "" {

                    if strings.ToLower(strMessageDecrypted) == "close" {
                        chnLogging <- "Client requests disconnection"
                        chnCloseConn <- true
                        return
                    }

                    // Keepalive message; disregard
                    if strMessageDecrypted == "PING" {
                        continue
                    }

                    btResult := InterpretClientCommand(strMessageDecrypted)

                    strctResponse := CreateStctNetEncryptedToClient(string(btResult), strctNetEncrypted.BtPubKey, 4)

                    btJSON, err := json.Marshal(strctResponse)
                    CheckErr(err)
                    conClient.Write(btJSON)
                    conClient.Write([]byte("\n"))

                } else {
                    chnLogging <- "Invalid command \""   strMessageDecrypted   "\""
                }

            default:

                chnLogging <- "ERROR: Message received without mode set"
            }
        }
    }
}

CodePudding user response:

The application slurps up data to a buffered reader and then discards the reader and any data it may have buffered past the first line.

Retain the buffered reader for the lifetime of the connection:

    rdr := bufio.NewReader(conClient)
    for {
        netData, err := rdr.ReadString('\n')
        ...

You can simplify the code (and fix other issues unrelated to the buffer issue) by eliminating the goroutine. Use the read deadline to handle the unresponsive server.

func HandleClientConnection(conClient net.Conn) {
    defer conClient.Close()
    chnLogging <- "Connection from "   conClient.RemoteAddr().String()
    conClient.SetReadDeadline(time.Minute * SERVER_INACTIVITY_TIMEOUT_MINUTES)
    scanner := bufio.NewScanner(conClient)
    for scanner.Scan() {
        var strctNetEncrypted stctNetEncrypted
        err := json.Unmarshal(scanner.Bytes(), &strctNetEncrypted)
        CheckErr(err)
        switch strctNetEncrypted.IntMode {
             // Insert contents of switch statement from
             // question here with references to 
             // chnCloseConn removed.
        }
        conClient.SetReadDeadline(time.Minute * SERVER_INACTIVITY_TIMEOUT_MINUTES)
    }
    if scanner.Err() != nil {
        chnLogging <- "Error from client "   conClient.RemoteAddr().String()   ": "   err.Error()
    } else {
        chnLogging <- "Client "   conClient.RemoteAddr().String()   " disconnected"
    }
}
  • Related