Kitura / BlueSocket

Socket framework for Swift using the Swift Package Manager. Works on iOS, macOS, and Linux.
Apache License 2.0
1.41k stars 197 forks source link

Cannot connect to listening UNIX socket #207

Closed tombell closed 2 years ago

tombell commented 2 years ago

I'm trying to creaste a listening UNIX socket, and also connect to it using BlueSocket, however I'm getting the error Error code: -9993(0x-2709), Invalid argument when calling socket.acceptClientConnection() when connecting using BlueSocket, however testing with the utility socat it connects fine.

Server code:

import Foundation
import Socket

public class Daemon {
    private static let maxReadBufferSize = 1024

    private let lockQueue = DispatchQueue(label: "io.tomb.swm")

    private var isRunning = false

    private var running: Bool {
        get {
            lockQueue.sync { self.isRunning }
        }
        set(newValue) {
            lockQueue.sync { self.isRunning = newValue }
        }
    }

    private var listen: Socket?

    public init() {}

    public func run() throws {
        do {
            try listen = Socket.create(family: .unix, type: .stream, proto: .unixw)
        } catch {
            throw DaemonError.unableToCreateSocket
        }

        guard let socket = listen else {
            throw DaemonError.unableToUnwrapSocket
        }

        do {
            try socket.listen(on: try Daemon.socketFilePath())
        } catch {
            throw DaemonError.unableToListenOnSocket
        }

        running = true

        let queue = DispatchQueue.global(qos: .userInteractive)

        // swiftlint:disable:next unowned_variable_capture
        queue.async { [unowned self] in
            repeat {
                do {
                    let client = try socket.acceptClientConnection()
                    self.handle(socket: client)
                } catch {
                    fputs("error: accepting incoming client connection - \(error)\n", stderr)
                }
            } while self.running
        }
    }

    public func shutdown() {
        running = false

        do {
            let path = try Daemon.socketFilePath()
            try FileManager.default.removeItem(atPath: path)
        } catch {}
    }

    private func handle(socket: Socket) {
        let queue = DispatchQueue.global(qos: .userInitiated)

        queue.async {
            print("socket connected: \(socket.remotePath ?? "unkown")")
        }
    }

    public static func socketFilePath() throws -> String {
        guard let user = ProcessInfo.processInfo.environment["USER"] else {
            throw DaemonError.userEnvVarMissing
        }

        return FileManager
            .default
            .temporaryDirectory
            .appendingPathComponent("swm_\(user).sock", isDirectory: false)
            .path
    }
}

Client

import Foundation
import Socket

public enum Client {
    public static func send(message _: MessageDomain, args _: [String]) throws -> Int32 {
        let socket = try Socket.create(family: .unix, type: .stream, proto: .unix)
        try socket.connect(to: try Daemon.socketFilePath())

        return EXIT_SUCCESS
    }
}

I am assuming I am missing something from the connecting side of things.

My project: https://github.com/tombell/swm

dannys42 commented 2 years ago

I'm not sure why you're getting that error. Perhaps there's a threading issue due to the use of the global queue? I tried a paired down version and it seems to connect ok:

server.swift:

#!/usr/bin/swift sh
import Socket // kitura/BlueSocket

let socketPath = "socket"

let socket = try Socket.create(family: .unix, type: .stream, proto: .unix)
try socket.listen(on: socketPath)

while true {
    print("Waiting for connection...")
    let client = try socket.acceptClientConnection()

    print("Got client: \(client)")
}

print("Done!")

client.swift:

#!/usr/bin/swift sh
import Socket // kitura/BlueSocket

let socketPath = "socket"
let socket = try Socket.create(family: .unix, type: .stream, proto: .unix)
try socket.connect(to: socketPath)

print("Done!")

This makes use of swift-sh

tombell commented 2 years ago

@dannys42 Yeah, it's a complete mystery to me, I made a separate project client and that could connect fine, using socat could connect fine, but any sort of client inside the same project as the server, it just doesn't seem to work.

Update:

Screenshot 2021-11-09 at 12 42 51

Looks like sometimes it connects, and the majority of the time it fails to accept the connection

Update 2:

After some more debugging with trial and error, a bare separate client executable in the project appeared to connect fine each time.

I then commented everything out in the main executable, except for calling the client to connect and send a hard coded message, and that could connect.

I slowly uncommented code until the error presented itself... and it appears to do with let arguments = Arguments.parseOrExit(), now I have no idea what that is doing to cause the issue. :joy:

Update 3:

I added swift-argument-parser to the test project and called parseOrExit for some test args, and the socket managed to connect, so I'm kind of decuding that the problem must be something with my exectuable target and "library" target.

tombell commented 2 years ago

I've managed to create a simple minimal project that reproduces the issue for me https://github.com/tombell/minimal-socket-bug

tombell commented 2 years ago

Since this doesn't appear to be a bug directly with BlueSocket, and I've managed to figure out a work around, I'll close this off.

mschrage commented 2 years ago

Hey @tombell, would you mind sharing your workaround? I'm running into an issue that sounds awfully similar to this.

tombell commented 2 years ago

@mschrage my work around was kind of just splitting things out into functions, and I think wrapping the Socket function calls in do { ... } catch { ... } seemed to help to