Skip to content

SCTP will lose data if a stream is closed 'too soon' after the last blocking write call. #361

@coridonhenshaw

Description

@coridonhenshaw

Summary

SCTP will lose data if a stream is closed 'too soon' after the last blocking write call.

The stream.Close() API does not flush transmission buffers before returning, nor is there any obvious way to manually flush a stream transmission buffer before calling Close(). As a result, any data stored for transmission will be lost, and "use of closed network connection" errors will result from lower layers of the communications stack, such as client.Shutdown(), and (udp)conn.Close().

Motivation

Data loss is a bad thing. SCTP should honor the implied intent of sctp.ReliabilityTypeReliable when terminating streams, by sending all pending data to the peer before closing the stream.

Describe alternatives you've considered

There does not seem to be any robust solution to conduct an orderly shutdown of a stream without cooperation from the peer to communicate that it has received all expected data.

Blocking until an OnBufferedAmountLow() handler reports 0 bytes in the send buffer only moves the issue down to the underlying transport. Closing a stream may succeed without error, but data will still be lost by lower layers of the stack and a "use of closed network connection" error will still result.

Example

Based on the pinger/ponger examples. Error checking removed for brevity.

Send:

func main() {
conn, err := net.Dial("udp", "127.0.0.1:9899")

config := sctp.Config{
	NetConn:       conn,
	LoggerFactory: logging.NewDefaultLoggerFactory(),
	BlockWrite:    true,
}
a, err := sctp.Client(config)

stream, err := a.OpenStream(0, sctp.PayloadTypeWebRTCString)

stream.SetReliabilityParams(false, sctp.ReliabilityTypeReliable, 10)

const Size = 256
const Iter = (Size * 1024 * 1024) / 65536
  // Sends 4096 blocks of 64KiB each.
for i := 0; i < Iter; i++ {
	pingMsg := make([]byte, 65536)
	rand.Read(pingMsg)
	_, err = stream.Write([]byte(pingMsg))
}

  // One of these (usually conn.Close()) will return a "use of closed network connection" error)
err = stream.Close()
if err != nil { log.Panic(err) }
err = a.Close()
if err != nil { log.Panic(err) }
err = conn.Close()
if err != nil { log.Panic(err) }
}

Receive:

func main() {
addr := net.UDPAddr{
	IP:   net.IPv4(0, 0, 0, 0),
	Port: 9899,
}
conn, _ := net.ListenUDP("udp", &addr)
config := sctp.Config{
	NetConn:       &disconnectedPacketConn{pConn: conn},
	LoggerFactory: logging.NewDefaultLoggerFactory(),
}
a, _ := sctp.Server(config)
stream, _ := a.AcceptStream()
stream.SetReliabilityParams(false, sctp.ReliabilityTypeReliable, 0)
var pongSeqNum int
for {
	buff := make([]byte, 65536)
	stream.Read(buff)

	fmt.Printf("received: %v, count: %v\n", len(buff), pongSeqNum)
	pongSeqNum++
    }
}

This code will typically stop, as a result of the send process ending, at block 4093 (zero-based count) out of 4094. Block 4094 will be lost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions