File: SocketOptionProviderTest.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (294 lines) | stat: -rw-r--r-- 12,858 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import NIO
import XCTest

final class SocketOptionProviderTest: XCTestCase {
    var group: MultiThreadedEventLoopGroup!
    var serverChannel: Channel!
    var clientChannel: Channel!
    var ipv4DatagramChannel: Channel!
    var ipv6DatagramChannel: Channel?

    struct CastError: Error { }

    private func convertedChannel(file: StaticString = #file, line: UInt = #line) throws -> SocketOptionProvider {
        guard let provider = self.clientChannel as? SocketOptionProvider else {
            XCTFail("Unable to cast \(String(describing: self.clientChannel)) to SocketOptionProvider", file: (file), line: line)
            throw CastError()
        }
        return provider
    }

    private func ipv4MulticastProvider(file: StaticString = #file, line: UInt = #line) throws -> SocketOptionProvider {
        guard let provider = self.ipv4DatagramChannel as? SocketOptionProvider else {
            XCTFail("Unable to cast \(String(describing: self.ipv4DatagramChannel)) to SocketOptionProvider", file: (file), line: line)
            throw CastError()
        }
        return provider
    }

    private func ipv6MulticastProvider(file: StaticString = #file, line: UInt = #line) throws -> SocketOptionProvider? {
        guard let ipv6Channel = self.ipv6DatagramChannel else {
            return nil
        }

        guard let provider = ipv6Channel as? SocketOptionProvider else {
            XCTFail("Unable to cast \(ipv6Channel)) to SocketOptionChannel", file: (file), line: line)
            throw CastError()
        }

        return provider
    }

    override func setUp() {
        self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        self.serverChannel = try? assertNoThrowWithValue(ServerBootstrap(group: group).bind(host: "127.0.0.1", port: 0).wait())
        self.clientChannel = try? assertNoThrowWithValue(ClientBootstrap(group: group).connect(to: serverChannel.localAddress!).wait())

        // We need to join these multicast groups on the loopback interface to work around issues with rapidly joining and leaving
        // many multicast groups. On some OSes, if we do that on a public interface, we can build up a kernel backlog of IGMP
        // joins/leaves that may eventually lead to an ENOMEM and a spurious test failure. As joining/leaving groups on loopback
        // interfaces does not require IGMP joins/leaves, forcing these joins onto the loopback interface saves us from this
        // risk.
        let v4LoopbackAddress = try! assertNoThrowWithValue(SocketAddress(ipAddress: "127.0.0.1", port: 0))
        let v6LoopbackAddress = try! assertNoThrowWithValue(SocketAddress(ipAddress: "::1", port: 0))
        let v4LoopbackInterface = try! assertNoThrowWithValue(System.enumerateDevices().filter {
            $0.address == v4LoopbackAddress
        }.first)!

        // Only run the setup if the loopback interface supports multicast
        if v4LoopbackAddress.isMulticast {
            self.ipv4DatagramChannel = try? assertNoThrowWithValue(
                DatagramBootstrap(group: group).bind(host: "127.0.0.1", port: 0).flatMap { channel in
                    return (channel as! MulticastChannel).joinGroup(try! SocketAddress(ipAddress: "224.0.2.66", port: 0), device: v4LoopbackInterface).map { channel }
                }.wait()
            )
        }

        // Only run the setup if the loopback interface supports multicast
        if v6LoopbackAddress.isMulticast {
            // The IPv6 setup is allowed to fail, some hosts don't have IPv6.
            let v6LoopbackInterface = try? assertNoThrowWithValue(System.enumerateDevices().filter { $0.address == v6LoopbackAddress }.first)
            self.ipv6DatagramChannel = try? DatagramBootstrap(group: group).bind(host: "::1", port: 0).flatMap { channel in
                return (channel as! MulticastChannel).joinGroup(try! SocketAddress(ipAddress: "ff12::beeb", port: 0), device: v6LoopbackInterface).map { channel }
            }.wait()
        }
    }

    override func tearDown() {
        XCTAssertNoThrow(try ipv6DatagramChannel?.close().wait())
        XCTAssertNoThrow(try ipv4DatagramChannel?.close().wait())
        XCTAssertNoThrow(try clientChannel.close().wait())
        XCTAssertNoThrow(try serverChannel.close().wait())
        XCTAssertNoThrow(try group.syncShutdownGracefully())
    }

    func testSettingAndGettingComplexSocketOption() throws {
        let provider = try assertNoThrowWithValue(self.convertedChannel())

        let newTimeout = timeval(tv_sec: 5, tv_usec: 0)
        let retrievedTimeout = try assertNoThrowWithValue(provider.unsafeSetSocketOption(level: .socket, name: .so_rcvtimeo, value: newTimeout).flatMap {
            provider.unsafeGetSocketOption(level: .socket, name: .so_rcvtimeo) as EventLoopFuture<timeval>
        }.wait())

        XCTAssertEqual(retrievedTimeout.tv_sec, newTimeout.tv_sec)
        XCTAssertEqual(retrievedTimeout.tv_usec, newTimeout.tv_usec)
    }

    func testObtainingDefaultValueOfComplexSocketOption() throws {
        let provider = try assertNoThrowWithValue(self.convertedChannel())

        let retrievedTimeout: timeval = try assertNoThrowWithValue(provider.unsafeGetSocketOption(level: .socket, name: .so_rcvtimeo).wait())
        XCTAssertEqual(retrievedTimeout.tv_sec, 0)
        XCTAssertEqual(retrievedTimeout.tv_usec, 0)
    }

    func testSettingAndGettingSimpleSocketOption() throws {
        let provider = try assertNoThrowWithValue(self.convertedChannel())

        let newReuseAddr = 1 as CInt
        let retrievedReuseAddr = try assertNoThrowWithValue(provider.unsafeSetSocketOption(level: .socket, name: .so_reuseaddr, value: newReuseAddr).flatMap {
            provider.unsafeGetSocketOption(level: .socket, name: .so_reuseaddr) as EventLoopFuture<CInt>
        }.wait())

        XCTAssertNotEqual(retrievedReuseAddr, 0)
    }

    func testObtainingDefaultValueOfSimpleSocketOption() throws {
        let provider = try assertNoThrowWithValue(self.convertedChannel())

        let reuseAddr: CInt = try assertNoThrowWithValue(provider.unsafeGetSocketOption(level: .socket, name: .so_reuseaddr).wait())
        XCTAssertEqual(reuseAddr, 0)
    }

    func testPassingInvalidSizeToSetComplexSocketOptionFails() throws {
        // You'll notice that there are no other size mismatch tests in this file. The reason for that is that
        // setsockopt is pretty dumb, and getsockopt is dumber. Specifically, setsockopt checks only that the length
        // of the option value is *at least as large* as the expected struct (which is why this test will actually
        // work), and getsockopt will happily return without error even in the buffer is too small. Either way,
        // we just abandon the other tests: this is sufficient to prove that the error path works.
        let provider = try assertNoThrowWithValue(self.convertedChannel())

        XCTAssertThrowsError(try provider.unsafeSetSocketOption(level: .socket, name: .so_rcvtimeo, value: 1).wait()) { error in
            XCTAssertEqual(EINVAL, (error as? IOError)?.errnoCode)
        }
    }


    // MARK: Tests for the safe helper functions.
    func testLinger() throws {
        let newLingerValue = linger(l_onoff: 1, l_linger: 64)

        let provider = try self.convertedChannel()
        XCTAssertNoThrow(try provider.setSoLinger(newLingerValue).flatMap {
            provider.getSoLinger()
        }.map {
            XCTAssertEqual($0.l_linger, newLingerValue.l_linger)
            XCTAssertEqual($0.l_onoff, newLingerValue.l_onoff)
        }.wait())
    }

    func testSoIpMulticastIf() throws {
        guard let channel = self.ipv4DatagramChannel else {
            // no multicast support
            return
        }
        let provider = try assertNoThrowWithValue(self.ipv4MulticastProvider())

        let address: in_addr
        switch channel.localAddress {
        case .some(.v4(let addr)):
            address = addr.address.sin_addr
        default:
            XCTFail("Local address must be IPv4, but is \(channel.localAddress.debugDescription)")
            return
        }

        XCTAssertNoThrow(try provider.setIPMulticastIF(address).flatMap {
            provider.getIPMulticastIF()
        }.map {
            XCTAssertEqual($0.s_addr, address.s_addr)
        }.wait())
    }

    func testIpMulticastTtl() throws {
        guard self.ipv4DatagramChannel != nil else {
            // alas, no multicast, let's skip.
            return
        }
        let provider = try assertNoThrowWithValue(self.ipv4MulticastProvider())
        XCTAssertNoThrow(try provider.setIPMulticastTTL(6).flatMap {
            provider.getIPMulticastTTL()
        }.map {
            XCTAssertEqual($0, 6)
        }.wait())
    }

    func testIpMulticastLoop() throws {
        guard self.ipv4DatagramChannel != nil else {
            // alas, no multicast, let's skip.
            return
        }
        let provider = try assertNoThrowWithValue(self.ipv4MulticastProvider())
        XCTAssertNoThrow(try provider.setIPMulticastLoop(1).flatMap {
            provider.getIPMulticastLoop()
        }.map {
            XCTAssertNotEqual($0, 0)
        }.wait())
    }

    func testIpv6MulticastIf() throws {
        guard let provider = try assertNoThrowWithValue(self.ipv6MulticastProvider()) else {
            // Skip on systems without IPv6.
            return
        }
        guard self.ipv6DatagramChannel != nil else {
            // alas, no multicast, let's skip.
            return
        }

        // TODO: test this when we know what the interface indices are.
        let loopbackAddress = try assertNoThrowWithValue(SocketAddress(ipAddress: "::1", port: 0))
        guard let loopbackInterface = try assertNoThrowWithValue(System.enumerateDevices().filter({ $0.address == loopbackAddress }).first) else {
            XCTFail("Could not find index of loopback address")
            return
        }

        XCTAssertNoThrow(try provider.setIPv6MulticastIF(CUnsignedInt(loopbackInterface.interfaceIndex)).flatMap {
            provider.getIPv6MulticastIF()
        }.map {
            XCTAssertEqual($0, CUnsignedInt(loopbackInterface.interfaceIndex))
        }.wait())
    }

    func testIPv6MulticastHops() throws {
        guard let provider = try assertNoThrowWithValue(self.ipv6MulticastProvider()) else {
            // Skip on systems without IPv6.
            return
        }
        guard self.ipv6DatagramChannel != nil else {
            // alas, no multicast, let's skip.
            return
        }

        XCTAssertNoThrow(try provider.setIPv6MulticastHops(6).flatMap {
            provider.getIPv6MulticastHops()
        }.map {
            XCTAssertEqual($0, 6)
        }.wait())
    }

    func testIPv6MulticastLoop() throws {
        guard let provider = try assertNoThrowWithValue(self.ipv6MulticastProvider()) else {
            // Skip on systems without IPv6.
            return
        }
        guard self.ipv6DatagramChannel != nil else {
            // alas, no multicast, let's skip.
            return
        }

        XCTAssertNoThrow(try provider.setIPv6MulticastLoop(1).flatMap {
            provider.getIPv6MulticastLoop()
        }.map {
            XCTAssertNotEqual($0, 0)
        }.wait())
    }

    func testTCPInfo() throws {
        // This test only runs on Linux, FreeBSD, and Android.
        #if os(Linux) || os(FreeBSD) || os(Android)
        let channel = self.clientChannel! as! SocketOptionProvider
        let tcpInfo = try assertNoThrowWithValue(channel.getTCPInfo().wait())

        // We just need to soundness check something here to ensure that the data is vaguely reasonable.
        XCTAssertEqual(tcpInfo.tcpi_state, UInt8(TCP_ESTABLISHED))
        #endif
    }

    func testTCPConnectionInfo() throws {
        // This test only runs on Darwin.
        #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
        let channel = self.clientChannel! as! SocketOptionProvider
        let tcpConnectionInfo = try assertNoThrowWithValue(channel.getTCPConnectionInfo().wait())

        #if os(macOS) // deliberately only on macOS
        // We just need to soundness check something here to ensure that the data is vaguely reasonable.
        XCTAssertEqual(tcpConnectionInfo.tcpi_state, UInt8(TSI_S_ESTABLISHED))
        #endif
        #endif
    }
}