apple / swift-nio-transport-services

Extensions for SwiftNIO to support Apple platforms as first-class citizens.
https://swiftpackageindex.com/apple/swift-nio-transport-services/main/documentation/niotransportservices
Apache License 2.0
282 stars 71 forks source link

Connection failures to closed ports hang and do not propagate the exception #204

Closed Aloisius closed 4 weeks ago

Aloisius commented 4 weeks ago

Expected behavior

When a connection to an unfirewalled closed port is made, an exception should be thrown

Actual behavior

Connection refuses do not propagate.

Steps to reproduce

  1. Ensure a local port is closed
  2. Setup bootstrap with a long connect timeout
  3. Attempt to .connect() to the closed port

If possible, minimal yet complete reproducer code (or URL to code)

Ensure local port 6666 doesn't have anything listening on it. The channel future whenFailure won't be called either (not shown). This code won't exit until the connection timeout happens even though TS gets an event for connection refused:

        import NIOTransportServices

        let group = NIOTSEventLoopGroup()

        let channel = try NIOTSConnectionBootstrap(group: group)
                    .connectTimeout(.seconds(9999))
                    .connect(host: "localhost", port: 6666)
                    .wait()

Wheres the equivalent for POSIX sockets immediately throws an NIOConnectionError when the connection fails:

        import NIO

        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

        let channel = try ClientBootstrap(group: group)
            .connectTimeout(.seconds(9999))
            .connect(host: "localhost", port: 6666)
            .wait()

The issue appears to be that when StateManagedNWConnectionChannel.stateUpdateHandler state transitions to .waiting while in .activating state, a user inbound event is fired which is ignored by all the default user event handlers, but close0 is never called.

SwiftNIO version/commit hash

swift-nio 2.70.0 swift-nio-transport-services 1.21.0

Swift & OS version (output of swift --version && uname -a)

swift-driver version: 1.113 Apple Swift version 6.0 (swiftlang-6.0.0.7.6 clang-1600.0.24.1) Target: arm64-apple-macosx15.0 Darwin redacted.local 24.0.0 Darwin Kernel Version 24.0.0: Wed Jul 31 21:48:27 PDT 2024; root:xnu-11215.0.199.501.2~1/RELEASE_ARM64_T8103 arm64

Aloisius commented 4 weeks ago

Ahh. I didn't realize this was the expected behavior.

This fails properly.

       let channel = try NIOTSConnectionBootstrap(group: group)
                    .connectTimeout(.seconds(9999))
                    .channelOption(NIOTSChannelOptions.waitForActivity, value: false)
                    .connect(host: "localhost", port: 6666)
                    .wait()
Lukasa commented 4 weeks ago

Yeah, this behaviour is in line with what Network.framework prefers to do. You've correctly discovered the way to toggle the behaviour.