Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions Sources/Samples/Connect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,53 @@ struct Connect: ParsableCommand {
@Flag(help: "Send data out-of-band")
var outOfBand: Bool = false

@Option(help: "TCP connection timeout, in seconds")
var connectionTimeout: CInt?

@Option(help: "Socket send timeout, in seconds")
var sendTimeout: CInt?

@Option(help: "Socket receive timeout, in seconds")
var receiveTimeout: CInt?

@Option(help: "TCP connection keepalive interval, in seconds")
var keepalive: CInt?

func connect(
to addresses: [SocketAddress.Info]
) throws -> (SocketDescriptor, SocketAddress)? {
for addressinfo in addresses {
do {
let socket = try SocketDescriptor.open(
addressinfo.domain,
addressinfo.type,
addressinfo.protocol)
do {
try socket.connect(to: addressinfo.address)
return (socket, addressinfo.address)
}
catch {
try? socket.close()
throw error
}
// Only try the first address for now
guard let addressinfo = addresses.first else { return nil }
print(addressinfo)
let socket = try SocketDescriptor.open(
addressinfo.domain,
addressinfo.type,
addressinfo.protocol)
do {
if let connectionTimeout = connectionTimeout {
try socket.setOption(.tcp, .tcpConnectionTimeout, to: connectionTimeout)
}
if let sendTimeout = sendTimeout {
try socket.setOption(.socketOption, .sendTimeout, to: sendTimeout)
}
catch {
continue
if let receiveTimeout = receiveTimeout {
try socket.setOption(.socketOption, .receiveTimeout, to: receiveTimeout)
}
if let keepalive = keepalive {
try socket.setOption(.tcp, .tcpKeepAlive, to: keepalive)
}
try socket.connect(to: addressinfo.address)
return (socket, addressinfo.address)
}
catch {
try? socket.close()
throw error
}
return nil
}

func run() throws {
let addresses = try SocketAddress.resolveName(
hostname: nil,
hostname: hostname,
service: service,
family: ipv6 ? .ipv6 : .ipv4,
type: udp ? .datagram : .stream)
Expand Down
12 changes: 7 additions & 5 deletions Sources/Samples/Listen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ struct Listen: ParsableCommand {
} else {
let conn = try socket.accept(client: &client)
complain("Connection from \(client.niceDescription)")
while true {
let (count, flags) =
try conn.closeAfter {
while true {
let (count, flags) =
try conn.receive(into: buffer, sender: &client, ancillary: &ancillary)
guard count > 0 else { break }
print(prefix(client: client, flags: flags), terminator: "")
try FileDescriptor.standardOutput.writeAll(buffer[..<count])
guard count > 0 else { break }
print(prefix(client: client, flags: flags), terminator: "")
try FileDescriptor.standardOutput.writeAll(buffer[..<count])
}
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions Sources/System/Sockets/SocketAddress+IPv4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extension SocketAddress {
return IPv4(rawValue: value)
}

/// Construct a `SocketAddress` holding an IPv4 address and port
/// Construct a `SocketAddress` holding an IPv4 address and port number.
@_alwaysEmitIntoClient
public init(ipv4 address: IPv4.Address, port: Port) {
self.init(IPv4(address: address, port: port))
Expand Down Expand Up @@ -120,7 +120,7 @@ extension SocketAddress.IPv4 {
}
}

/// The port this socket is listening on.
/// The port number associated with this address.
@_alwaysEmitIntoClient
public var port: SocketAddress.Port {
get { SocketAddress.Port(CInterop.InPort(_networkOrder: rawValue.sin_port)) }
Expand Down Expand Up @@ -194,3 +194,13 @@ extension SocketAddress.IPv4.Address: LosslessStringConvertible {
}
}
}

// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension SocketAddress.IPv4.Address: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
guard let address = Self(value) else {
preconditionFailure("'\(value)' is not a valid IPv4 address string")
}
self = address
}
}
18 changes: 14 additions & 4 deletions Sources/System/Sockets/SocketAddress+IPv6.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extension SocketAddress {
return IPv6(rawValue: value)
}

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

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

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

/// The IPv4 loopback address "::1".
/// The IPv6 loopback address "::1".
///
/// This corresponds to the C constant `IN6ADDR_LOOPBACK_INIT`.
@_alwaysEmitIntoClient
Expand Down Expand Up @@ -238,3 +238,13 @@ extension SocketAddress.IPv6.Address: LosslessStringConvertible {
}
}
}

// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension SocketAddress.IPv6.Address: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
guard let address = Self(value) else {
preconditionFailure("'\(value)' is not a valid IPv6 address string")
}
self = address
}
}
10 changes: 5 additions & 5 deletions Sources/System/Sockets/SocketAddress+Local.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ private var _pathOffset: Int {

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

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

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

// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension SocketAddress.Local {
/// The path to the file used to advertise the socket name to clients.
/// The path representing the socket name in the local filesystem namespace.
///
/// The corresponding C struct member is `sun_path`.
public var path: FilePath { _path }
Expand Down
11 changes: 9 additions & 2 deletions Sources/System/Sockets/SocketAddress+Resolution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ extension SocketAddress {
/// Information about a resolved address.
///
/// The members of this struct can be passed directly to
/// `SocketDescriptor.connect()` or `SocketDescriptor.bind().
/// `SocketDescriptor.open()`, `SocketDescriptor.connect()`
/// or `SocketDescriptor.bind()` to initiate connections.
///
/// This loosely corresponds to the C `struct addrinfo`.
public struct Info {
Expand Down Expand Up @@ -390,7 +391,9 @@ extension SocketAddress {
extension SocketAddress {
/// Get a list of IP addresses and port numbers for a host and service.
///
/// TODO: communicate that on failure, this throws a `ResolverError`.
/// On failure, this throws either a `ResolverError` or an `Errno`,
/// depending on the error code returned by the underlying `getaddrinfo`
/// function.
///
/// The method corresponds to the C function `getaddrinfo`.
public static func resolveName(
Expand Down Expand Up @@ -507,6 +510,10 @@ extension SocketAddress {
extension SocketAddress {
/// Resolve a socket address to hostname and service name.
///
/// On failure, this throws either a `ResolverError` or an `Errno`,
/// depending on the error code returned by the underlying `getnameinfo`
/// function.
///
/// This method corresponds to the C function `getnameinfo`.
public static func resolveAddress(
_ address: SocketAddress,
Expand Down
52 changes: 49 additions & 3 deletions Sources/System/Sockets/SocketAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,57 @@
*/

// @available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
/// An opaque type holding a socket address and port number in some address family.
/// An opaque type representing a socket address in some address family,
/// such as an IP address along with a port number.
///
/// TODO: Show examples of creating an ipv4 and ipv6 address
/// `SocketAddress` values can be passed directly to `SocketDescriptor.connect`
/// or `.bind` to establish a network connection.
///
/// The corresponding C type is `sockaddr_t`
/// We can use the `SocketAddress.resolveName` method resolve a pair of
/// host/service name strings to a list of socket addresses:
///
/// let results =
/// try SocketAddress.resolveName(hostname: "swift.org", service: "https")
/// for result in results {
/// let try socket =
/// SocketDescriptor.open(result.domain, result.type, result.protocol)
/// do {
/// try socket.connect(to: result.address)
/// } catch {
/// try? socket.close()
/// throw error
/// }
/// return socket
/// }
///
/// To create an IPv4, IPv6 or Local domain address, we can use convenience
/// initializers that take the corresponding information:
///
/// let ipv4 = SocketAddress(ipv4: "127.0.0.1", port: 8080)!
/// let ipv6 = SocketAddress(ipv6: "::1", port: 80)!
/// let local = SocketAddress(local: "/var/run/example.sock")
///
/// (Note that you may prefer to use the concrete address types
/// `SocketAddress.IPv4`, `SocketAddress.IPv6` and `SocketAddress.Local`
/// instead -- they provide easy access to the address parameters.)
///
/// `SocketAddress` also provides ways to access its underlying contents
/// as a raw unsafe memory buffer. This is useful for dealing with address
/// families that `System` doesn't model, or for passing the socket address
/// to C functions that expect a pointer to a `sockaddr` value.
///
/// `SocketAddress` stores its contents in a managed storage buffer, and
/// it can serve as a reusable receptacle for addresses that are returned
/// by system calls. You can use the `init(minimumCapacity:)` initializer
/// to create an empty socket address with the specified storage capacity,
/// then you can pass it to functions like `.accept(client:)` to retrieve
/// addresses without repeatedly allocating memory.
///
/// `SocketAddress` is able to hold any IPv4 or IPv6 address without allocating
/// any memory. For other address families, it may need to heap allocate a
/// storage buffer, depending on the size of the stored value.
///
/// The corresponding C type is `sockaddr_t`.
public struct SocketAddress {
internal var _variant: _Variant

Expand Down
2 changes: 1 addition & 1 deletion Sources/System/Sockets/SocketDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ extension SocketDescriptor {

/// Specify the part (or all) of a full-duplex connection to shutdown.
@frozen
public struct ShutdownKind: RawRepresentable, Hashable, Codable, CustomStringConvertible {
public struct ShutdownKind: RawRepresentable, Hashable, CustomStringConvertible {
@_alwaysEmitIntoClient
public var rawValue: CInt

Expand Down
50 changes: 49 additions & 1 deletion Sources/System/Sockets/SocketOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,38 @@ extension SocketDescriptor {
}.map(SocketDescriptor.init(rawValue:))
}

/// Bind a name to a socket.
/// Bind a socket to an address.
///
/// The corresponding C function is `bind`.
@_alwaysEmitIntoClient
public func bind(to address: SocketAddress) throws {
try _bind(to: address).get()
}

/// Bind a socket to an IPv4 address.
///
/// The corresponding C function is `bind`.
@_alwaysEmitIntoClient
public func bind(to address: SocketAddress.IPv4) throws {
try _bind(to: SocketAddress(address)).get()
}

/// Bind a socket to an IPv6 address.
///
/// The corresponding C function is `bind`.
@_alwaysEmitIntoClient
public func bind(to address: SocketAddress.IPv6) throws {
try _bind(to: SocketAddress(address)).get()
}

/// Bind a socket to an address in the local domain.
///
/// The corresponding C function is `bind`.
@_alwaysEmitIntoClient
public func bind(to address: SocketAddress.Local) throws {
try _bind(to: SocketAddress(address)).get()
}

@usableFromInline
internal func _bind(to address: SocketAddress) -> Result<(), Errno> {
let success = address.withUnsafeCInterop { addr, len in
Expand Down Expand Up @@ -139,6 +163,30 @@ extension SocketDescriptor {
try _connect(to: address).get()
}

/// Initiate a connection to an IPv4 address.
///
/// The corresponding C function is `connect`.
@_alwaysEmitIntoClient
public func connect(to address: SocketAddress.IPv4) throws {
try _connect(to: SocketAddress(address)).get()
}

/// Initiate a connection to an IPv6 address.
///
/// The corresponding C function is `connect`.
@_alwaysEmitIntoClient
public func connect(to address: SocketAddress.IPv6) throws {
try _connect(to: SocketAddress(address)).get()
}

/// Initiate a connection to an address in the local domain.
///
/// The corresponding C function is `connect`.
@_alwaysEmitIntoClient
public func connect(to address: SocketAddress.Local) throws {
try _connect(to: SocketAddress(address)).get()
}

@usableFromInline
internal func _connect(to address: SocketAddress) -> Result<(), Errno> {
let success = address.withUnsafeCInterop { addr, len in
Expand Down
Loading