Skip to content

Commit a370d9e

Browse files
authored
Support Unix Domain Socket connections (#146)
1 parent 55f6921 commit a370d9e

File tree

11 files changed

+105
-8
lines changed

11 files changed

+105
-8
lines changed

.devcontainer/docker-compose.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
# run this with docker-compose -f docker/docker-compose.yml run test
1+
# run this with: docker-compose -f docker-compose.yml run test
22
version: "3.3"
33

44
services:
55
app:
66
image: swift:5.9
77
volumes:
88
- ..:/workspace
9+
- mosquitto-socket:/workspace/mosquitto/socket
910
depends_on:
1011
- mosquitto
1112
environment:
@@ -18,8 +19,12 @@ services:
1819
volumes:
1920
- ../mosquitto/config:/mosquitto/config
2021
- ../mosquitto/certs:/mosquitto/certs
22+
- mosquitto-socket:/mosquitto/socket
2123
ports:
2224
- "1883:1883"
2325
- "8883:8883"
2426
- "8080:8080"
2527
- "8081:8081"
28+
29+
volumes:
30+
mosquitto-socket:

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767
volumes:
6868
- ${{ github.workspace }}/mosquitto/config:/mosquitto/config
6969
- ${{ github.workspace }}/mosquitto/certs:/mosquitto/certs
70+
- ${{ github.workspace }}/mosquitto/socket:/mosquitto/socket
7071

7172
steps:
7273
- name: Checkout

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ MQTTNIO is a Swift NIO based implementation of a MQTT client. It supports
1414
- WebSocket connections
1515
- Posix sockets
1616
- Apple's Network framework via [NIOTransportServices](https://github.com/apple/swift-nio-transport-services) (required for iOS).
17+
- Unix domain sockets
1718

1819
You can find documentation for MQTTNIO
1920
[here](https://swift-server-community.github.io/mqtt-nio/documentation/mqttnio/). There is also a sample demonstrating the use MQTTNIO in an iOS app found [here](https://github.com/adam-fowler/EmCuTeeTee)

Sources/MQTTNIO/MQTTClient.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,30 @@ public final class MQTTClient {
141141
self.inflight = .init()
142142
}
143143

144+
/// Create MQTT client
145+
/// - Parameters:
146+
/// - unixSocketPath: Path to unix socket of MQTT broker
147+
/// - identifier: Client identifier. This must be unique
148+
/// - eventLoopGroupProvider: EventLoopGroup to run on
149+
/// - logger: Logger client should use
150+
/// - configuration: Configuration of client
151+
public convenience init(
152+
unixSocketPath: String,
153+
identifier: String,
154+
eventLoopGroupProvider: NIOEventLoopGroupProvider,
155+
logger: Logger? = nil,
156+
configuration: Configuration = Configuration()
157+
) {
158+
self.init(
159+
host: unixSocketPath,
160+
port: 0,
161+
identifier: identifier,
162+
eventLoopGroupProvider: eventLoopGroupProvider,
163+
logger: logger,
164+
configuration: configuration
165+
)
166+
}
167+
144168
deinit {
145169
guard isShutdown.load(ordering: .relaxed) else {
146170
preconditionFailure("Client not shut down before the deinit. Please call client.syncShutdownGracefully() when no longer needed.")

Sources/MQTTNIO/MQTTConnection.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ final class MQTTConnection {
4646
do {
4747
// get bootstrap based off what eventloop we are running on
4848
let bootstrap = try getBootstrap(client: client)
49-
bootstrap
5049
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
5150
.channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
5251
.connectTimeout(client.configuration.connectTimeout)
@@ -75,7 +74,16 @@ final class MQTTConnection {
7574
return channel.pipeline.addHandlers(handlers)
7675
}
7776
}
78-
.connect(host: client.host, port: client.port)
77+
78+
let channelFuture: EventLoopFuture<Channel>
79+
80+
if client.port == 0 {
81+
channelFuture = bootstrap.connect(unixDomainSocketPath: client.host)
82+
} else {
83+
channelFuture = bootstrap.connect(host: client.host, port: client.port)
84+
}
85+
86+
channelFuture
7987
.map { channel in
8088
if !client.configuration.useWebSockets {
8189
channelPromise.succeed(channel)

Sources/MQTTNIO/MQTTNIO.docc/mqttnio-connections.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Connections
22

3-
Support for TLS and WebSockets.
3+
Support for TLS, WebSockets, and Unix Domain Sockets.
44

55
## TLS
66

@@ -31,3 +31,23 @@ MQTT also supports Web Socket connections. Provide a `WebSocketConfiguration` wh
3131
## NIO Transport Services
3232

3333
On macOS and iOS you can use the NIO Transport Services library (NIOTS) and Apple's `Network.framework` for communication with the MQTT broker. If you don't provide an `eventLoopGroup` or a `TLSConfigurationType` then this is the default for both platforms. If you do provide either of these then the library will base it's decision on whether to use NIOTS or NIOSSL on what you provide. Provide a `MultiThreadedEventLoopGroup` or `NIOSSL.TLSConfiguration` and the client will use NIOSSL. Provide a `NIOTSEventLoopGroup` or `TSTLSConfiguration` and the client will use NIOTS. If you provide a `MultiThreadedEventLoopGroup` and a `TSTLSConfiguration` then the client will throw an error. If you are running on iOS you should always choose NIOTS.
34+
35+
## Unix Domain Sockets
36+
37+
MQTT NIO can connect to a local MQTT broker via a Unix Domain Socket.
38+
39+
```swift
40+
let client = MQTTClient(
41+
unixSocketPath: "/path/to/broker.socket",
42+
identifier: "UDSClient",
43+
eventLoopGroupProvider: .createNew
44+
)
45+
```
46+
47+
Under the hood, `MQTTClient.port` will be 0 and `MQTTClient.host` will be the specified unix socket path when connecting to a unix socket.
48+
49+
Note that mosquitto supports listening on a unix domain socket. This can be enabled by adding a `listener` option to the mosquitto config.
50+
51+
```
52+
listener 0 /path/to/broker.socket
53+
```

Tests/MQTTNIOTests/MQTTNIOTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,20 @@ final class MQTTNIOTests: XCTestCase {
146146
}
147147
#endif
148148

149+
func testUnixDomainConnect() throws {
150+
let client = MQTTClient(
151+
unixSocketPath: MQTTNIOTests.rootPath + "/mosquitto/socket/mosquitto.sock",
152+
identifier: "testUnixDomainConnect",
153+
eventLoopGroupProvider: .createNew,
154+
logger: self.logger,
155+
configuration: .init()
156+
)
157+
defer { XCTAssertNoThrow(try client.syncShutdownGracefully()) }
158+
_ = try client.connect().wait()
159+
try client.ping().wait()
160+
try client.disconnect().wait()
161+
}
162+
149163
func testMQTTPublishQoS0() throws {
150164
let client = self.createClient(identifier: "testMQTTPublishQoS0")
151165
defer { XCTAssertNoThrow(try client.syncShutdownGracefully()) }

docker-compose.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# run this with docker-compose -f docker/docker-compose.yml run test
1+
# run this with: docker-compose -f docker-compose.yml run test
22
version: "3.3"
33

44
services:
@@ -7,20 +7,25 @@ services:
77
working_dir: /mqtt-nio
88
volumes:
99
- .:/mqtt-nio
10+
- mosquitto-socket:/mqtt-nio/mosquitto/socket
1011
depends_on:
1112
- mosquitto
1213
environment:
1314
- MOSQUITTO_SERVER=mosquitto
1415
- CI=true
15-
command: /bin/bash -xcl "swift test --enable-test-discovery"
16+
command: /bin/bash -xcl "swift test"
1617

1718
mosquitto:
1819
image: eclipse-mosquitto
1920
volumes:
2021
- ./mosquitto/config:/mosquitto/config
2122
- ./mosquitto/certs:/mosquitto/certs
23+
- mosquitto-socket:/mosquitto/socket
2224
ports:
2325
- "1883:1883"
2426
- "8883:8883"
2527
- "8080:8080"
2628
- "8081:8081"
29+
30+
volumes:
31+
mosquitto-socket:

mosquitto/config/mosquitto.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,8 @@ allow_anonymous true
3636
cafile ./mosquitto/certs/ca.pem
3737
certfile ./mosquitto/certs/server.pem
3838
keyfile ./mosquitto/certs/server.key
39+
40+
# Unix Domain Socket
41+
listener 0 ./mosquitto/socket/mosquitto.sock
42+
protocol mqtt
43+
allow_anonymous true

mosquitto/socket/.gitkeep

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
A local mosquitto server using `mosquitto/config/mosquitto.conf` will create a `mosquitto.sock` socket in this directory.
2+
3+
If using the `docker-compose.yml` container environment, a shared container volume will be mounted here.
4+
5+
This allows tests that connect to mosquitto via unix domain socket to assume the socket(s) will be found in this directory and work from multiple environments.
6+
7+
Do not remove this directory.

0 commit comments

Comments
 (0)