3

I'm writing a simple TLV style service using TCP in go, that should be able to handle multiple connections, and allow for multiple messages to be sent on the same connection.

I need to be able to ensure that a message with incorrect length doesn't block the connection indefinitely, but I need to ensure that subsequent messages can be sent on the same connection without timing out.

I am using io.ReadFull to read in a fixed number of bytes that contain the type and length, and then when I receive the length, I am calling io.ReadFull again and reading in length number of bytes.

If I am reading in 4 bytes for the type and length, but the client only sends 3 for whatever reason, io.ReadFull will hang. Similarly, if the client sends 300 for the length, but the length should only be 200, io.ReadFull will hang, blocking all communication on that channel.

I've tried using conn.SetReadDeadline() and setting it to 5 seconds for this example. This causes the connection to timeout if the improper length is sent, which is great. The issue is that the connection will timeout if the next request isn't sent until after >5 seconds.

// ...
    for {
        conn, err := lis.Accept() 
        if err != nil {
            fmt.Println(err)
            continue
        }
        fmt.Println("Connected")
        go handleC(conn)
    }
func handleC(conn net.Conn) {
    for {
        err := conn.SetReadDeadline(time.Now().Add(5 * time.Second))
        if err != nil {
            fmt.Println(err)
            break
        }
        l, err := readTL(conn)
        if err != nil {
            if err, ok := err.(net.Error); ok && err.Timeout() {
                fmt.Println("Timeout", err)
                break
            }
            fmt.Println("Other error"), err
            break
        }

        v, err := readV(conn, l)
        if err != nil {
            if err, ok := err.(net.Error); ok && err.Timeout() {
                fmt.Println("Timeout", err)
                break
            }
            fmt.Println("Other error"), err
            break
        }
        // go doStuffWithv()

    }
}

func readTL(conn net.Conn) (l int, err error) {
    buf := make([]byte, 4)
    n, err := io.ReadFull(conn, buf)
    if err != nil {
        return l, err
    }
    fmt.Printf("Read %d bytes\n", n)
    // getLengthFromTL()
    return l, err
}

func readV(conn net.Conn, l int) (v []byte, err error) {
    buf := make([]byte, l)
    n, err := io.ReadFull(conn, buf)
    if err != nil {
        return v, err
    }
    fmt.Printf("Read %d bytes\n", n)
    return v, err
}

If a client sends one request with the proper TL, things work as intended. However, if the same client doesn't send a second message for 10 seconds, the connection will timeout before then, with the error tls: use of closed connection Is there a way to ensure that this doesn't occur?

One thing I've tried doing is in the event of a timeout, it simply continues, rather than breaking. I added in another error check to see if it is EOF, and break if it is.

My first impression is that this works, but I'm not sure if there are instances where a connection timeout can mean that the connection is dead and shouldn't be used anymore or not, or if that would always return an EOF error.

jimpeter
  • 61
  • 1
  • 4
  • 1
    The code is written to expect a message every 5 seconds, but it sounds like that's not the case. How often to you expect the peer to send a message? If it's unknown, then the deadline should only be set after reading the first byte of the header (or the entire header) and cleared after reading the message. – Charlie Tumahai Oct 31 '19 at 14:17
  • 1
    The time between messages is unknown. What you described sounds like it should do the trick for me. The client is "trusted", so I don't think this will ever be an issue, but with my current implementation using `io.ReadFull`, if the client sent 2 bytes instead of 4, and the deadline was set after reading the header, it would block trying to read the rest of the header indefinitely. I like your idea of setting it after reading the first byte - I will make some changes and give that a shot. Thank you :) – jimpeter Oct 31 '19 at 14:33
  • If the peer is trusted, there's not a lot of value in protecting against a peer sending a partial header and then going silent. There is value in detecting dead connections, but that requires that the peer send heartbeat messages. – Charlie Tumahai Oct 31 '19 at 15:12
  • Thanks @CeriseLimón - I think maybe I was being too paranoid with protecting against a partial header. – jimpeter Oct 31 '19 at 15:54

0 Answers0