Skip to content

Commit c11d8fa

Browse files
committed
WIP: Socket get/set options
1 parent 9b66b31 commit c11d8fa

File tree

4 files changed

+214
-125
lines changed

4 files changed

+214
-125
lines changed

Sources/System/Internals/Exports.swift

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import ucrt
2525
#endif
2626

2727
internal typealias _COffT = off_t
28+
internal typealias _CSockLenT = socklen_t
2829

2930
// MARK: syscalls and variables
3031

Sources/System/Internals/Syscalls.swift

+27
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,30 @@ internal func system_recv(
154154
#endif
155155
return recv(socket, buffer, len, flags)
156156
}
157+
158+
internal func system_getsockopt(
159+
_ socket: CInt,
160+
_ level: CInt,
161+
_ option: CInt,
162+
_ value: UnsafeMutableRawPointer!,
163+
_ length: UnsafeMutablePointer<socklen_t>!
164+
) -> CInt {
165+
#if ENABLE_MOCKING
166+
if mockingEnabled { return _mock(socket, level, option, value, length) }
167+
#endif
168+
return getsockopt(socket, level, option, value, length)
169+
}
170+
171+
internal func system_setsockopt(
172+
_ socket: CInt,
173+
_ level: CInt,
174+
_ option: CInt,
175+
_ value: UnsafeRawPointer!,
176+
_ length: socklen_t
177+
) -> CInt {
178+
#if ENABLE_MOCKING
179+
if mockingEnabled { return _mock(socket, level, option, value, length) }
180+
#endif
181+
return setsockopt(socket, level, option, value, length)
182+
}
183+

Sources/System/Sockets/SocketOptions.swift

+180-120
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
extension SocketDescriptor {
11-
@frozen
11+
@frozen
1212
public struct Option: RawRepresentable, Hashable {
1313
@_alwaysEmitIntoClient
1414
public var rawValue: CInt
@@ -317,129 +317,129 @@ extension SocketDescriptor {
317317
/// A value of -1 resets to the default value.
318318
///
319319
/// The corresponding C constant is `IPV6_UNICAST_HOPS`.
320-
@_alwaysEmitIntoClient
321-
public static var ipv6UnicastHops: Option { Option(_IPV6_UNICAST_HOPS) }
322-
323-
/// The interface from which multicast packets will be sent.
324-
///
325-
/// A value of 0 specifies the default interface.
326-
///
327-
/// The corresponding C constant is `IPV6_MULTICAST_IF`.
328-
@_alwaysEmitIntoClient
329-
public static var ipv6MulticastInterface: Option { Option(_IPV6_MULTICAST_IF) }
330-
331-
/// The default hop limit header field for outgoing multicast datagrams.
332-
///
333-
/// The corresponding C constant is `IPV6_MULTICAST_HOPS`.
334-
@_alwaysEmitIntoClient
335-
public static var ipv6MulticastHops: Option { Option(_IPV6_MULTICAST_HOPS) }
336-
337-
/// Whether multicast datagrams will be looped back.
338-
///
339-
/// The corresponding C constant is `IPV6_MULTICAST_LOOP`.
340-
@_alwaysEmitIntoClient
341-
public static var ipv6MulticastLoop: Option { Option(_IPV6_MULTICAST_LOOP) }
342-
343-
/// Join a multicast group.
344-
///
345-
/// The corresponding C constant is `IPV6_JOIN_GROUP`.
346-
@_alwaysEmitIntoClient
347-
public static var ipv6JoinGroup: Option { Option(_IPV6_JOIN_GROUP) }
348-
349-
/// Leave a multicast group.
350-
///
351-
/// The corresponding C constant is `IPV6_LEAVE_GROUP`.
352-
@_alwaysEmitIntoClient
353-
public static var ipv6LeaveGroup: Option { Option(_IPV6_LEAVE_GROUP) }
354-
355-
/// Allocation policy of ephemeral ports for when the kernel automatically
356-
/// binds a local address to this socket.
357-
///
358-
/// TODO: portrange struct somewhere, with _DEFAULT, _HIGH, _LOW
359-
///
360-
/// The corresponding C constant is `IPV6_PORTRANGE`.
361-
@_alwaysEmitIntoClient
362-
public static var ipv6PortRange: Option { Option(_IPV6_PORTRANGE) }
363-
364-
// /// Whether additional information about subsequent packets will be
365-
// /// provided in `recvmsg` calls.
366-
// ///
367-
// /// The corresponding C constant is `IPV6_PKTINFO`.
368-
// @_alwaysEmitIntoClient
369-
// public static var ipv6ReceivePacketInfo: Option { Option(_IPV6_PKTINFO) }
320+
@_alwaysEmitIntoClient
321+
public static var ipv6UnicastHops: Option { Option(_IPV6_UNICAST_HOPS) }
322+
323+
/// The interface from which multicast packets will be sent.
324+
///
325+
/// A value of 0 specifies the default interface.
326+
///
327+
/// The corresponding C constant is `IPV6_MULTICAST_IF`.
328+
@_alwaysEmitIntoClient
329+
public static var ipv6MulticastInterface: Option { Option(_IPV6_MULTICAST_IF) }
330+
331+
/// The default hop limit header field for outgoing multicast datagrams.
332+
///
333+
/// The corresponding C constant is `IPV6_MULTICAST_HOPS`.
334+
@_alwaysEmitIntoClient
335+
public static var ipv6MulticastHops: Option { Option(_IPV6_MULTICAST_HOPS) }
336+
337+
/// Whether multicast datagrams will be looped back.
338+
///
339+
/// The corresponding C constant is `IPV6_MULTICAST_LOOP`.
340+
@_alwaysEmitIntoClient
341+
public static var ipv6MulticastLoop: Option { Option(_IPV6_MULTICAST_LOOP) }
342+
343+
/// Join a multicast group.
344+
///
345+
/// The corresponding C constant is `IPV6_JOIN_GROUP`.
346+
@_alwaysEmitIntoClient
347+
public static var ipv6JoinGroup: Option { Option(_IPV6_JOIN_GROUP) }
348+
349+
/// Leave a multicast group.
350+
///
351+
/// The corresponding C constant is `IPV6_LEAVE_GROUP`.
352+
@_alwaysEmitIntoClient
353+
public static var ipv6LeaveGroup: Option { Option(_IPV6_LEAVE_GROUP) }
354+
355+
/// Allocation policy of ephemeral ports for when the kernel automatically
356+
/// binds a local address to this socket.
357+
///
358+
/// TODO: portrange struct somewhere, with _DEFAULT, _HIGH, _LOW
359+
///
360+
/// The corresponding C constant is `IPV6_PORTRANGE`.
361+
@_alwaysEmitIntoClient
362+
public static var ipv6PortRange: Option { Option(_IPV6_PORTRANGE) }
363+
364+
// /// Whether additional information about subsequent packets will be
365+
// /// provided in `recvmsg` calls.
366+
// ///
367+
// /// The corresponding C constant is `IPV6_PKTINFO`.
368+
// @_alwaysEmitIntoClient
369+
// public static var ipv6ReceivePacketInfo: Option { Option(_IPV6_PKTINFO) }
370370
//
371-
// /// Whether the hop limit header field from subsequent packets will
372-
// /// be provided in `recvmsg` calls.
373-
// ///
374-
// /// The corresponding C constant is `IPV6_HOPLIMIT`.
375-
// @_alwaysEmitIntoClient
376-
// public static var ipv6ReceiveHopLimit: Option { Option(_IPV6_HOPLIMIT) }
371+
// /// Whether the hop limit header field from subsequent packets will
372+
// /// be provided in `recvmsg` calls.
373+
// ///
374+
// /// The corresponding C constant is `IPV6_HOPLIMIT`.
375+
// @_alwaysEmitIntoClient
376+
// public static var ipv6ReceiveHopLimit: Option { Option(_IPV6_HOPLIMIT) }
377377
//
378-
// /// Whether hop-by-hop options from subsequent packets will
379-
// /// be provided in `recvmsg` calls.
380-
// ///
381-
// /// The corresponding C constant is `IPV6_HOPOPTS`.
382-
// @_alwaysEmitIntoClient
383-
// public static var ipv6ReceiveHopOptions: Option { Option(_IPV6_HOPOPTS) }
378+
// /// Whether hop-by-hop options from subsequent packets will
379+
// /// be provided in `recvmsg` calls.
380+
// ///
381+
// /// The corresponding C constant is `IPV6_HOPOPTS`.
382+
// @_alwaysEmitIntoClient
383+
// public static var ipv6ReceiveHopOptions: Option { Option(_IPV6_HOPOPTS) }
384384
//
385-
// /// Whether destination options from subsequent packets will
386-
// /// be provided in `recvmsg` calls.
387-
// ///
388-
// /// The corresponding C constant is `IPV6_DSTOPTS`.
389-
// @_alwaysEmitIntoClient
390-
// public static var ipv6ReceiveDestinationOptions: Option { Option(_IPV6_DSTOPTS) }
391-
392-
/// The value of the traffic class field for outgoing datagrams.
393-
///
394-
/// The corresponding C constant is `IPV6_TCLASS`.
395-
@_alwaysEmitIntoClient
396-
public static var ipv6TrafficClass: Option { Option(_IPV6_TCLASS) }
397-
398-
/// Whether traffic class header field from subsequent packets will
399-
/// be provided in `recvmsg` calls.
400-
///
401-
/// The corresponding C constant is `IPV6_RECVTCLASS`.
402-
@_alwaysEmitIntoClient
403-
public static var ipv6ReceiveTrafficClass: Option { Option(_IPV6_RECVTCLASS) }
404-
405-
// /// Whether the routing header from subsequent packets will
406-
// /// be provided in `recvmsg` calls.
407-
// ///
408-
// /// The corresponding C constant is `IPV6_RTHDR`.
409-
// @_alwaysEmitIntoClient
410-
// public static var ipv6ReceiveRoutingHeader: Option { Option(_IPV6_RTHDR) }
385+
// /// Whether destination options from subsequent packets will
386+
// /// be provided in `recvmsg` calls.
387+
// ///
388+
// /// The corresponding C constant is `IPV6_DSTOPTS`.
389+
// @_alwaysEmitIntoClient
390+
// public static var ipv6ReceiveDestinationOptions: Option { Option(_IPV6_DSTOPTS) }
391+
392+
/// The value of the traffic class field for outgoing datagrams.
393+
///
394+
/// The corresponding C constant is `IPV6_TCLASS`.
395+
@_alwaysEmitIntoClient
396+
public static var ipv6TrafficClass: Option { Option(_IPV6_TCLASS) }
397+
398+
/// Whether traffic class header field from subsequent packets will
399+
/// be provided in `recvmsg` calls.
400+
///
401+
/// The corresponding C constant is `IPV6_RECVTCLASS`.
402+
@_alwaysEmitIntoClient
403+
public static var ipv6ReceiveTrafficClass: Option { Option(_IPV6_RECVTCLASS) }
404+
405+
// /// Whether the routing header from subsequent packets will
406+
// /// be provided in `recvmsg` calls.
407+
// ///
408+
// /// The corresponding C constant is `IPV6_RTHDR`.
409+
// @_alwaysEmitIntoClient
410+
// public static var ipv6ReceiveRoutingHeader: Option { Option(_IPV6_RTHDR) }
411411
//
412-
// /// Get or set all header options and extension headers at one time
413-
// /// on the last packet sent or received.
414-
// ///
415-
// /// The corresponding C constant is `IPV6_PKTOPTIONS`.
416-
// @_alwaysEmitIntoClient
417-
// public static var ipv6PacketOptions: Option { Option(_IPV6_PKTOPTIONS) }
418-
419-
/// The byte offset into a packet where 16-bit checksum is located.
420-
///
421-
/// The corresponding C constant is `IPV6_CHECKSUM`.
422-
@_alwaysEmitIntoClient
423-
public static var ipv6Checksum: Option { Option(_IPV6_CHECKSUM) }
424-
425-
/// Whether only IPv6 connections can be made to this socket.
426-
///
427-
/// The corresponding C constant is `IPV6_V6ONLY`.
428-
@_alwaysEmitIntoClient
429-
public static var ipv6Only: Option { Option(_IPV6_V6ONLY) }
430-
431-
// /// Whether the minimal IPv6 maximum transmission unit (MTU) size
432-
// /// will be used to avoid fragmentation for subsequenet outgoing
433-
// /// datagrams.
434-
// ///
435-
// /// The corresponding C constant is `IPV6_USE_MIN_MTU`.
436-
// @_alwaysEmitIntoClient
437-
// public static var ipv6UseMinimalMTU: Option { Option(_IPV6_USE_MIN_MTU) }
412+
// /// Get or set all header options and extension headers at one time
413+
// /// on the last packet sent or received.
414+
// ///
415+
// /// The corresponding C constant is `IPV6_PKTOPTIONS`.
416+
// @_alwaysEmitIntoClient
417+
// public static var ipv6PacketOptions: Option { Option(_IPV6_PKTOPTIONS) }
418+
419+
/// The byte offset into a packet where 16-bit checksum is located.
420+
///
421+
/// The corresponding C constant is `IPV6_CHECKSUM`.
422+
@_alwaysEmitIntoClient
423+
public static var ipv6Checksum: Option { Option(_IPV6_CHECKSUM) }
424+
425+
/// Whether only IPv6 connections can be made to this socket.
426+
///
427+
/// The corresponding C constant is `IPV6_V6ONLY`.
428+
@_alwaysEmitIntoClient
429+
public static var ipv6Only: Option { Option(_IPV6_V6ONLY) }
430+
431+
// /// Whether the minimal IPv6 maximum transmission unit (MTU) size
432+
// /// will be used to avoid fragmentation for subsequenet outgoing
433+
// /// datagrams.
434+
// ///
435+
// /// The corresponding C constant is `IPV6_USE_MIN_MTU`.
436+
// @_alwaysEmitIntoClient
437+
// public static var ipv6UseMinimalMTU: Option { Option(_IPV6_USE_MIN_MTU) }
438438
}
439439
}
440440

441441
extension SocketDescriptor.Option {
442-
/// The level at which a socket option resides
442+
/// The level at which a socket option resides
443443
@frozen
444444
public struct Level: RawRepresentable, Hashable {
445445
@_alwaysEmitIntoClient
@@ -453,27 +453,87 @@ extension SocketDescriptor.Option {
453453

454454
/// Socket options that only apply to IP sockets.
455455
///
456-
/// The corresponding C constant is `IPPROTO_IP`.
456+
/// The corresponding C constant is `IPPROTO_IP`.
457457
@_alwaysEmitIntoClient
458458
public static var ip: Level { Level(_IPPROTO_IP) }
459459

460460
/// Socket options that only apply to IPv6 sockets
461461
///
462-
/// The corresponding C constant is `IPPROTO_IPV6`.
462+
/// The corresponding C constant is `IPPROTO_IPV6`.
463463
@_alwaysEmitIntoClient
464464
public static var ipv6: Level { Level(_IPPROTO_IPV6) }
465465

466466
/// Socket options that only apply to TCP sockets
467467
///
468-
/// The corresponding C constant is `IPPROTO_TCP`.
468+
/// The corresponding C constant is `IPPROTO_TCP`.
469469
@_alwaysEmitIntoClient
470470
public static var tcp: Level { Level(_IPPROTO_TCP) }
471471

472472
/// Socket options that apply to all sockets.
473473
///
474-
/// The corresponding C constant is `SOL_SOCKET`.
474+
/// The corresponding C constant is `SOL_SOCKET`.
475475
@_alwaysEmitIntoClient
476476
public static var socket: Level { Level(_SOL_SOCKET) }
477477
}
478478
}
479479

480+
extension SocketDescriptor {
481+
// TODO: Convenience/performance overloads for `Bool` and other concrete types
482+
483+
@_alwaysEmitIntoClient
484+
public func getOption<T>(
485+
_ level: Option.Level, _ option: Option
486+
) throws -> T {
487+
try _getOption(level, option).get()
488+
}
489+
490+
@usableFromInline
491+
internal func _getOption<T>(
492+
_ level: Option.Level, _ option: Option
493+
) -> Result<T, Errno> {
494+
// We can't zero-initialize `T` directly, nor can we pass an uninitialized `T`
495+
// to `withUnsafeMutableBytes(of:)`. Instead, we will allocate :-(
496+
let rawBuf = UnsafeMutableRawBufferPointer.allocate(
497+
byteCount: MemoryLayout<T>.stride,
498+
alignment: MemoryLayout<T>.alignment)
499+
rawBuf.initializeMemory(as: UInt8.self, repeating: 0)
500+
let resultPtr = rawBuf.baseAddress!.bindMemory(to: T.self, capacity: 1)
501+
defer {
502+
resultPtr.deinitialize(count: 1)
503+
rawBuf.deallocate()
504+
}
505+
506+
var length: _CSockLenT = 0
507+
508+
let success = system_getsockopt(
509+
self.rawValue,
510+
level.rawValue,
511+
option.rawValue,
512+
resultPtr, &length)
513+
514+
return nothingOrErrno(success).map { resultPtr.pointee }
515+
}
516+
517+
@_alwaysEmitIntoClient
518+
public func setOption<T>(
519+
_ level: Option.Level, _ option: Option, to value: T
520+
) throws {
521+
try _setOption(level, option, to: value).get()
522+
}
523+
524+
@usableFromInline
525+
internal func _setOption<T>(
526+
_ level: Option.Level, _ option: Option, to value: T
527+
) -> Result<(), Errno> {
528+
let len = _CSockLenT(MemoryLayout<T>.stride)
529+
let success = withUnsafeBytes(of: value) {
530+
return system_setsockopt(
531+
self.rawValue,
532+
level.rawValue,
533+
option.rawValue,
534+
$0.baseAddress,
535+
len)
536+
}
537+
return nothingOrErrno(success)
538+
}
539+
}

0 commit comments

Comments
 (0)