Skip to content

Commit b4acb97

Browse files
authored
Network receptacles (#3)
* Update getsockopt/setsockopt * Add getsockopt/setsockopt docs * Doc updates
1 parent 5a06de1 commit b4acb97

6 files changed

+196
-70
lines changed

Sources/Samples/Connect.swift

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,53 @@ struct Connect: ParsableCommand {
3838
@Flag(help: "Send data out-of-band")
3939
var outOfBand: Bool = false
4040

41+
@Option(help: "TCP connection timeout, in seconds")
42+
var connectionTimeout: CInt?
43+
44+
@Option(help: "Socket send timeout, in seconds")
45+
var sendTimeout: CInt?
46+
47+
@Option(help: "Socket receive timeout, in seconds")
48+
var receiveTimeout: CInt?
49+
50+
@Option(help: "TCP connection keepalive interval, in seconds")
51+
var keepalive: CInt?
52+
4153
func connect(
4254
to addresses: [SocketAddress.Info]
4355
) throws -> (SocketDescriptor, SocketAddress)? {
44-
for addressinfo in addresses {
45-
do {
46-
let socket = try SocketDescriptor.open(
47-
addressinfo.domain,
48-
addressinfo.type,
49-
addressinfo.protocol)
50-
do {
51-
try socket.connect(to: addressinfo.address)
52-
return (socket, addressinfo.address)
53-
}
54-
catch {
55-
try? socket.close()
56-
throw error
57-
}
56+
// Only try the first address for now
57+
guard let addressinfo = addresses.first else { return nil }
58+
print(addressinfo)
59+
let socket = try SocketDescriptor.open(
60+
addressinfo.domain,
61+
addressinfo.type,
62+
addressinfo.protocol)
63+
do {
64+
if let connectionTimeout = connectionTimeout {
65+
try socket.setOption(.tcp, .tcpConnectionTimeout, to: connectionTimeout)
66+
}
67+
if let sendTimeout = sendTimeout {
68+
try socket.setOption(.socketOption, .sendTimeout, to: sendTimeout)
5869
}
59-
catch {
60-
continue
70+
if let receiveTimeout = receiveTimeout {
71+
try socket.setOption(.socketOption, .receiveTimeout, to: receiveTimeout)
6172
}
73+
if let keepalive = keepalive {
74+
try socket.setOption(.tcp, .tcpKeepAlive, to: keepalive)
75+
}
76+
try socket.connect(to: addressinfo.address)
77+
return (socket, addressinfo.address)
78+
}
79+
catch {
80+
try? socket.close()
81+
throw error
6282
}
63-
return nil
6483
}
6584

6685
func run() throws {
6786
let addresses = try SocketAddress.resolveName(
68-
hostname: nil,
87+
hostname: hostname,
6988
service: service,
7089
family: ipv6 ? .ipv6 : .ipv4,
7190
type: udp ? .datagram : .stream)

Sources/System/Sockets/SocketAddress+IPv4.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ extension SocketAddress.IPv4 {
120120
}
121121
}
122122

123-
/// The port this socket is listening on.
123+
/// The port number associated with this address.
124124
@_alwaysEmitIntoClient
125125
public var port: SocketAddress.Port {
126126
get { SocketAddress.Port(CInterop.InPort(_networkOrder: rawValue.sin_port)) }

Sources/System/Sockets/SocketAddress+IPv6.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ extension SocketAddress {
4747
return IPv6(rawValue: value)
4848
}
4949

50-
/// Construct a `SocketAddress` holding an IPv4 address and port
50+
/// Construct a `SocketAddress` holding an IPv6 address and port
5151
@_alwaysEmitIntoClient
5252
public init(ipv6 address: IPv6.Address, port: Port) {
5353
self.init(IPv6(address: address, port: port))
@@ -107,7 +107,7 @@ extension SocketAddress.IPv6: CustomStringConvertible {
107107

108108
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
109109
extension SocketAddress.IPv6 {
110-
/// The port on which this socket is listening.
110+
/// The port number associated with this address.
111111
@_alwaysEmitIntoClient
112112
public var port: SocketAddress.Port {
113113
get { SocketAddress.Port(CInterop.InPort(_networkOrder: rawValue.sin6_port)) }
@@ -120,7 +120,7 @@ extension SocketAddress.IPv6 {
120120
/// A 128-bit IPv6 address.
121121
@frozen
122122
public struct Address: RawRepresentable {
123-
/// The raw internet address value, in host byte order.
123+
/// The raw IPv6 address value. (16 bytes in network byte order.)
124124
@_alwaysEmitIntoClient
125125
public var rawValue: CInterop.In6Addr
126126

@@ -147,7 +147,7 @@ extension SocketAddress.IPv6.Address {
147147
Self(rawValue: CInterop.In6Addr())
148148
}
149149

150-
/// The IPv4 loopback address "::1".
150+
/// The IPv6 loopback address "::1".
151151
///
152152
/// This corresponds to the C constant `IN6ADDR_LOOPBACK_INIT`.
153153
@_alwaysEmitIntoClient

Sources/System/Sockets/SocketAddress+Local.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ private var _pathOffset: Int {
1414

1515
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
1616
extension SocketAddress {
17-
/// A "local" (i.e. UNIX domain) socket address, for inter-process
17+
/// A socket address in the local (a.k.a. UNIX) domain, for inter-process
1818
/// communication on the same machine.
1919
///
2020
/// The corresponding C type is `sockaddr_un`.
2121
public struct Local: Hashable {
2222
internal let _path: FilePath
2323

24-
/// A "local" (i.e. UNIX domain) socket address, for inter-process
25-
/// communication on the same machine.
24+
/// Creates a socket address in the local (a.k.a. UNIX) domain,
25+
/// for inter-process communication on the same machine.
2626
///
2727
/// The corresponding C type is `sockaddr_un`.
2828
public init(_ path: FilePath) { self._path = path }
@@ -31,7 +31,7 @@ extension SocketAddress {
3131

3232
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
3333
extension SocketAddress {
34-
/// Create a SocketAddress from a local (i.e. UNIX domain) socket address.
34+
/// Create a `SocketAddress` from a local (i.e. UNIX domain) socket address.
3535
public init(_ local: Local) {
3636
let offset = _pathOffset
3737
let length = offset + local._path.length + 1
@@ -75,7 +75,7 @@ extension SocketAddress {
7575

7676
// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
7777
extension SocketAddress.Local {
78-
/// The path to the file used to advertise the socket name to clients.
78+
/// The path representing the socket name in the local filesystem namespace.
7979
///
8080
/// The corresponding C struct member is `sun_path`.
8181
public var path: FilePath { _path }

Sources/System/Sockets/SocketAddress+Resolution.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ extension SocketAddress {
1212
/// Information about a resolved address.
1313
///
1414
/// The members of this struct can be passed directly to
15-
/// `SocketDescriptor.connect()` or `SocketDescriptor.bind().
15+
/// `SocketDescriptor.open()`, `SocketDescriptor.connect()`
16+
/// or `SocketDescriptor.bind()` to initiate connections.
1617
///
1718
/// This loosely corresponds to the C `struct addrinfo`.
1819
public struct Info {
@@ -390,7 +391,9 @@ extension SocketAddress {
390391
extension SocketAddress {
391392
/// Get a list of IP addresses and port numbers for a host and service.
392393
///
393-
/// TODO: communicate that on failure, this throws a `ResolverError`.
394+
/// On failure, this throws either a `ResolverError` or an `Errno`,
395+
/// depending on the error code returned by the underlying `getaddrinfo`
396+
/// function.
394397
///
395398
/// The method corresponds to the C function `getaddrinfo`.
396399
public static func resolveName(
@@ -507,6 +510,10 @@ extension SocketAddress {
507510
extension SocketAddress {
508511
/// Resolve a socket address to hostname and service name.
509512
///
513+
/// On failure, this throws either a `ResolverError` or an `Errno`,
514+
/// depending on the error code returned by the underlying `getnameinfo`
515+
/// function.
516+
///
510517
/// This method corresponds to the C function `getnameinfo`.
511518
public static func resolveAddress(
512519
_ address: SocketAddress,

Sources/System/Sockets/SocketOptions.swift

Lines changed: 140 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -442,64 +442,164 @@ extension SocketDescriptor {
442442
}
443443

444444
extension SocketDescriptor {
445-
// TODO: Convenience/performance overloads for `Bool` and other concrete types
446-
447-
/// Get an option associated with this socket.
445+
// TODO: Wrappers and convenience overloads for other concrete types
446+
// (timeval, linger)
447+
// For now, clients can use the UMRBP-based variants below.
448+
449+
/// Copy an option associated with this socket into the specified buffer.
450+
///
451+
/// The method corresponds to the C function `getsockopt`.
452+
///
453+
/// - Parameters:
454+
/// - level: The option level. To get a socket-level option, specify `.socketLevel`.
455+
/// Otherwise use the protocol value that defines your desired option.
456+
/// - option: The option identifier within the level.
457+
/// - buffer: The buffer into which to copy the option value.
458+
///
459+
/// - Returns: The number of bytes copied into the supplied buffer.
448460
@_alwaysEmitIntoClient
449-
public func getOption<T>(
450-
_ level: ProtocolID, _ option: Option
451-
) throws -> T {
452-
try _getOption(level, option).get()
461+
public func getOption(
462+
_ level: ProtocolID,
463+
_ option: Option,
464+
into buffer: UnsafeMutableRawBufferPointer
465+
) throws -> Int {
466+
try _getOption(level, option, into: buffer).get()
453467
}
454468

455-
@usableFromInline
456-
internal func _getOption<T>(
457-
_ level: ProtocolID, _ option: Option
458-
) -> Result<T, Errno> {
459-
// We can't zero-initialize `T` directly, nor can we pass an uninitialized `T`.
460-
// to `withUnsafeMutableBytes(of:)`. Instead, we will allocate :-(
461-
let rawBuf = UnsafeMutableRawBufferPointer.allocate(
462-
byteCount: MemoryLayout<T>.stride,
463-
alignment: MemoryLayout<T>.alignment)
464-
rawBuf.initializeMemory(as: UInt8.self, repeating: 0)
465-
let resultPtr = rawBuf.baseAddress!.bindMemory(to: T.self, capacity: 1)
466-
defer {
467-
resultPtr.deinitialize(count: 1)
468-
rawBuf.deallocate()
469+
/// Return the value of an option associated with this socket as a `CInt` value.
470+
///
471+
/// The method corresponds to the C function `getsockopt`.
472+
///
473+
/// - Parameters:
474+
/// - level: The option level. To get a socket-level option, specify `.socketLevel`.
475+
/// Otherwise use the protocol value that defines your desired option.
476+
/// - option: The option identifier within the level.
477+
/// - type: The type to return. Must be set to `CInt.self` (the default).
478+
///
479+
/// - Returns: The current value of the option.
480+
@_alwaysEmitIntoClient
481+
public func getOption(
482+
_ level: ProtocolID,
483+
_ option: Option,
484+
as type: CInt.Type = CInt.self
485+
) throws -> CInt {
486+
var value: CInt = 0
487+
try withUnsafeMutableBytes(of: &value) { buffer in
488+
// Note: return value is intentionally ignored.
489+
_ = try _getOption(level, option, into: buffer).get()
469490
}
491+
return value
492+
}
470493

471-
var length: CInterop.SockLen = 0
494+
/// Return the value of an option associated with this socket as a `Bool` value.
495+
///
496+
/// The method corresponds to the C function `getsockopt`.
497+
///
498+
/// - Parameters:
499+
/// - level: The option level. To get a socket-level option, specify `.socketLevel`.
500+
/// Otherwise use the protocol value that defines your desired option.
501+
/// - option: The option identifier within the level.
502+
/// - type: The type to return. Must be set to `Bool.self` (the default).
503+
///
504+
/// - Returns: True if the current value is not zero; otherwise false.
505+
@_alwaysEmitIntoClient
506+
public func getOption(
507+
_ level: ProtocolID,
508+
_ option: Option,
509+
as type: Bool.Type = Bool.self
510+
) throws -> Bool {
511+
try 0 != getOption(level, option, as: CInt.self)
512+
}
472513

514+
@usableFromInline
515+
internal func _getOption(
516+
_ level: ProtocolID,
517+
_ option: Option,
518+
into buffer: UnsafeMutableRawBufferPointer
519+
) -> Result<Int, Errno> {
520+
var length = CInterop.SockLen(buffer.count)
473521
let success = system_getsockopt(
474522
self.rawValue,
475523
level.rawValue,
476524
option.rawValue,
477-
resultPtr, &length)
525+
buffer.baseAddress, &length)
526+
return nothingOrErrno(success).map { _ in Int(length) }
527+
}
528+
}
478529

479-
return nothingOrErrno(success).map { resultPtr.pointee }
530+
extension SocketDescriptor {
531+
/// Set the value of an option associated with this socket to the contents
532+
/// of the specified buffer.
533+
///
534+
/// The method corresponds to the C function `setsockopt`.
535+
///
536+
/// - Parameters:
537+
/// - level: The option level. To set a socket-level option, specify `.socketLevel`.
538+
/// Otherwise use the protocol value that defines your desired option.
539+
/// - option: The option identifier within the level.
540+
/// - buffer: The buffer that contains the desired option value.
541+
@_alwaysEmitIntoClient
542+
public func setOption(
543+
_ level: ProtocolID,
544+
_ option: Option,
545+
from buffer: UnsafeRawBufferPointer
546+
) throws {
547+
try _setOption(level, option, from: buffer).get()
548+
}
549+
550+
/// Set the value of an option associated with this socket to the supplied
551+
/// `CInt` value.
552+
///
553+
/// The method corresponds to the C function `setsockopt`.
554+
///
555+
/// - Parameters:
556+
/// - level: The option level. To set a socket-level option, specify `.socketLevel`.
557+
/// Otherwise use the protocol value that defines your desired option.
558+
/// - option: The option identifier within the level.
559+
/// - value: The desired new value for the option.
560+
@_alwaysEmitIntoClient
561+
public func setOption(
562+
_ level: ProtocolID,
563+
_ option: Option,
564+
to value: CInt
565+
) throws {
566+
return try withUnsafeBytes(of: value) { buffer in
567+
// Note: return value is intentionally ignored.
568+
_ = try _setOption(level, option, from: buffer).get()
569+
}
480570
}
481571

482-
/// Set an option associated with this socket.
572+
/// Set the value of an option associated with this socket to the supplied
573+
/// `Bool` value.
574+
///
575+
/// The method corresponds to the C function `setsockopt`.
576+
///
577+
/// - Parameters:
578+
/// - level: The option level. To set a socket-level option, specify `.socketLevel`.
579+
/// Otherwise use the protocol value that defines your desired option.
580+
/// - option: The option identifier within the level.
581+
/// - value: The desired new value for the option. (`true` gets stored
582+
/// as `(1 as CInt)`. `false` is represented by `(0 as CInt)`).
483583
@_alwaysEmitIntoClient
484-
public func setOption<T>(
485-
_ level: ProtocolID, _ option: Option, to value: T
584+
public func setOption(
585+
_ level: ProtocolID,
586+
_ option: Option,
587+
to value: Bool
486588
) throws {
487-
try _setOption(level, option, to: value).get()
589+
try setOption(level, option, to: (value ? 1 : 0) as CInt)
488590
}
489591

490592
@usableFromInline
491-
internal func _setOption<T>(
492-
_ level: ProtocolID, _ option: Option, to value: T
493-
) -> Result<(), Errno> {
494-
let len = CInterop.SockLen(MemoryLayout<T>.stride)
495-
let success = withUnsafeBytes(of: value) {
496-
return system_setsockopt(
497-
self.rawValue,
498-
level.rawValue,
499-
option.rawValue,
500-
$0.baseAddress,
501-
len)
502-
}
593+
internal func _setOption(
594+
_ level: ProtocolID,
595+
_ option: Option,
596+
from buffer: UnsafeRawBufferPointer
597+
) -> Result<Void, Errno> {
598+
let success = system_setsockopt(
599+
self.rawValue,
600+
level.rawValue,
601+
option.rawValue,
602+
buffer.baseAddress, CInterop.SockLen(buffer.count))
503603
return nothingOrErrno(success)
504604
}
505605
}

0 commit comments

Comments
 (0)