Skip to content

Bug: Connection fails to Azure SQL Database with "Connection reset by peer" #1

@mklahn

Description

@mklahn

Description

CosmoSQLClient-Swift fails to establish connections to Azure SQL Database, throwing an IOError: Connection reset by peer (errno: 54) during the TLS handshake phase.

Environment

  • Package Version: v1.6.4
  • Platform: macOS (also likely affects Linux)
  • Target Database: Azure SQL Database
  • Swift Version: 5.9+

Reproduction

import CosmoMSSQL

let config = MSSQLConnection.Configuration(
    host: "your-server.database.windows.net",
    port: 1433,
    database: "your_database",
    username: "username",
    password: "password",
    tls: .require,
    trustServerCertificate: true
)

let pool = MSSQLConnectionPool(configuration: config)
let rows = try await pool.withConnection { conn in
    try await conn.query("SELECT 1")
}
// ❌ Fails with: IOError: Connection reset by peer

Expected Behavior

Successfully connects to Azure SQL Database and executes queries.

Actual Behavior

Connection fails during TLS handshake with error:

IOError: read(descriptor:pointer:size:): Connection reset by peer (errno: 54)

Root Cause

The TDSTLSFramer class in Sources/CosmoMSSQL/TDS/TDSTLSFramer.swift does not properly accumulate fragmented TCP packets. When Azure SQL Database sends a TLS ServerHello response that exceeds the TCP segment size (typically ~4KB TDS packet in ~1.5KB TCP segments), SwiftNIO delivers the data across multiple channelRead callbacks. The current implementation does not maintain state between these calls, causing incomplete packet parsing.

Debug trace showing the issue:

[TDSTLSFramer] Inbound: 2048 bytes  // First fragment
[TDSTLSFramer] Incomplete packet - lengthBE=4096, available=2048
[TDSTLSFramer] Inbound: 2700 bytes  // Second fragment (not accumulated!)
[TDSTLSFramer] Incomplete packet - lengthBE=8390891566170136948  // Corrupt length
❌ Connection reset

Proposed Fix

Add buffer accumulation to TDSTLSFramer:

final class TDSTLSFramer: ChannelDuplexHandler, @unchecked Sendable {
    // ... existing code ...

    // ADD: Buffer to accumulate fragmented packets
    private var accumulatedBuffer: ByteBuffer?

    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        guard active else {
            context.fireChannelRead(data)
            return
        }
        var incoming = unwrapInboundIn(data)

        // Accumulate with previous data
        if var accumulated = accumulatedBuffer {
            accumulated.writeBuffer(&incoming)
            accumulatedBuffer = accumulated
        } else {
            accumulatedBuffer = incoming
        }

        guard var buf = accumulatedBuffer else { return }

        // Process complete TDS packets
        while buf.readableBytes >= 8 {
            guard
                let lengthBE: UInt16 = buf.getInteger(at: buf.readerIndex + 2, endianness: .big),
                Int(lengthBE) <= buf.readableBytes,
                Int(lengthBE) >= 8
            else {
                accumulatedBuffer = buf  // Save for next read
                return
            }
            let packetLen = Int(lengthBE)
            var packet = buf.readSlice(length: packetLen)!
            packet.moveReaderIndex(forwardBy: 8)
            context.fireChannelRead(wrapInboundOut(packet))
        }

        // Clear if fully processed
        accumulatedBuffer = buf.readableBytes == 0 ? nil : buf
    }
}

Additional Issue (Minor)

The Pre-Login packet advertises TDS 8.0, but TDS 7.4 is more widely compatible:

File: Sources/CosmoMSSQL/TDS/TDSPreLogin.swift line 24

// Change from:
var clientVersion: (UInt8, UInt8, UInt8, UInt8) = (8, 0, 0, 0)
// To:
var clientVersion: (UInt8, UInt8, UInt8, UInt8) = (7, 4, 0, 0)

Verification

After applying the fix, the connection succeeds:

✅ Connected successfully!
✅ Query executed successfully!

Tested against Azure SQL Database with successful SELECT queries on production databases.

Impact

High: Affects all users connecting to Azure SQL Database and potentially on-premises SQL Server instances with large TLS certificates. This is a critical bug for production use with Azure.

Files Affected

  • Sources/CosmoMSSQL/TDS/TDSTLSFramer.swift (critical)
  • Sources/CosmoMSSQL/TDS/TDSPreLogin.swift (compatibility improvement)

I'm happy to submit a PR with these fixes if helpful. Full technical analysis available upon request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions