mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
738 lines
25 KiB
Swift
738 lines
25 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import Darwin
|
|
import Foundation
|
|
import _SwiftNetworkOverlayShims
|
|
|
|
internal extension sockaddr_in {
|
|
init(_ address:in_addr, _ port: in_port_t) {
|
|
self.init(sin_len: UInt8(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: port,
|
|
sin_addr: address, sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
|
}
|
|
|
|
func withSockAddr<ReturnType>(_ body: (_ sa: UnsafePointer<sockaddr>) throws -> ReturnType) rethrows -> ReturnType {
|
|
// We need to create a mutable copy of `self` so that we can pass it to `withUnsafePointer(to:_:)`.
|
|
var sin = self
|
|
return try withUnsafePointer(to: &sin) {
|
|
try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
try body($0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal extension sockaddr_in6 {
|
|
init(_ address:in6_addr, _ port: in_port_t, flow: UInt32 = 0, scope: UInt32 = 0) {
|
|
self.init(sin6_len: UInt8(MemoryLayout<sockaddr_in6>.size), sin6_family: sa_family_t(AF_INET6), sin6_port: port,
|
|
sin6_flowinfo: flow, sin6_addr: address, sin6_scope_id: scope)
|
|
}
|
|
|
|
func withSockAddr<ReturnType>(_ body: (_ sa: UnsafePointer<sockaddr>) throws -> ReturnType) rethrows -> ReturnType {
|
|
// We need to create a mutable copy of `self` so that we can pass it to `withUnsafePointer(to:_:)`.
|
|
var sin6 = self
|
|
return try withUnsafePointer(to: &sin6) {
|
|
try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
try body($0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal extension in_addr {
|
|
init(address: UInt32) {
|
|
self.init()
|
|
self.s_addr = address.bigEndian
|
|
}
|
|
}
|
|
|
|
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
|
private func getaddrinfo_numeric(_ string: String, family: Int32 = 0) -> NWEndpoint.Host? {
|
|
// Determine if this string has an interface scope specified "127.0.0.1%lo0" or "fe80::1%lo0"
|
|
var string = string
|
|
var interface : NWInterface? = nil
|
|
if let range = string.range(of: "%", options: String.CompareOptions.backwards) {
|
|
interface = NWInterface(String(string[range.upperBound...]))
|
|
if interface != nil {
|
|
string.removeSubrange(range.lowerBound...)
|
|
}
|
|
}
|
|
|
|
// call getaddrinfo
|
|
var hints = addrinfo(ai_flags: AI_NUMERICHOST, ai_family: family, ai_socktype: SOCK_STREAM, ai_protocol: 0,
|
|
ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil)
|
|
var resolved : UnsafeMutablePointer<addrinfo>? = nil
|
|
// After this point we must ensure we free addrinfo before we return
|
|
guard getaddrinfo(string, nil, &hints, &resolved) == 0, let addrinfo = resolved else {
|
|
return nil
|
|
}
|
|
|
|
var result: NWEndpoint.Host? = nil
|
|
|
|
if let sa = addrinfo.pointee.ai_addr {
|
|
if sa.pointee.sa_family == AF_INET {
|
|
sa.withMemoryRebound(to: sockaddr_in.self, capacity: 1, { (sin) -> Void in
|
|
result = NWEndpoint.Host.ipv4(IPv4Address(sin.pointee.sin_addr, interface))
|
|
})
|
|
} else if sa.pointee.sa_family == AF_INET6 {
|
|
sa.withMemoryRebound(to: sockaddr_in6.self, capacity: 1, { (sin6) -> Void in
|
|
if sin6.pointee.sin6_scope_id != 0 {
|
|
interface = NWInterface(Int(sin6.pointee.sin6_scope_id))
|
|
}
|
|
let ipv6 = IPv6Address(sin6.pointee.sin6_addr, interface);
|
|
if ipv6.isIPv4Mapped && family == AF_UNSPEC, let ipv4 = ipv6.asIPv4 {
|
|
// Treat IPv4 mapped as IPv4
|
|
result = NWEndpoint.Host.ipv4(ipv4)
|
|
} else {
|
|
result = NWEndpoint.Host.ipv6(ipv6)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
freeaddrinfo(addrinfo)
|
|
return result
|
|
}
|
|
|
|
private func getnameinfo_numeric(address: UnsafeRawPointer) -> String {
|
|
let sa = address.assumingMemoryBound(to: sockaddr.self)
|
|
var result : String? = nil
|
|
let maxLen = socklen_t(100)
|
|
let storage = UnsafeMutablePointer<Int8>.allocate(capacity: Int(maxLen))
|
|
if getnameinfo(sa, socklen_t(sa.pointee.sa_len), storage, maxLen, nil, 0, NI_NUMERICHOST) == 0 {
|
|
result = String(cString: storage)
|
|
}
|
|
storage.deallocate()
|
|
return result ?? "?"
|
|
}
|
|
|
|
/// An IP address
|
|
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
|
public protocol IPAddress {
|
|
|
|
/// Fetch the raw address as data
|
|
var rawValue: Data { get }
|
|
|
|
/// Create an IP address from data. The length of the data must
|
|
/// match the expected length of addresses in the address family
|
|
/// (four bytes for IPv4, and sixteen bytes for IPv6)
|
|
init?(_ rawValue: Data, _ interface: NWInterface?)
|
|
|
|
/// Create an IP address from an address literal string.
|
|
/// If the string contains '%' to indicate an interface, the interface will be
|
|
/// associated with the address, such as "::1%lo0" being associated with the loopback
|
|
/// interface.
|
|
/// This function does not perform host name to address resolution. This is the same as calling getaddrinfo
|
|
/// and using AI_NUMERICHOST.
|
|
init?(_ string: String)
|
|
|
|
/// The interface the address is scoped to, if any.
|
|
var interface: NWInterface? { get }
|
|
|
|
/// Indicates if this address is loopback
|
|
var isLoopback : Bool { get }
|
|
|
|
/// Indicates if this address is link-local
|
|
var isLinkLocal : Bool { get }
|
|
|
|
/// Indicates if this address is multicast
|
|
var isMulticast : Bool { get }
|
|
}
|
|
|
|
/// IPv4Address
|
|
/// Base type to hold an IPv4 address and convert between strings and raw bytes.
|
|
/// Note that an IPv4 address may be scoped to an interface.
|
|
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
|
public struct IPv4Address: IPAddress, Hashable, CustomDebugStringConvertible {
|
|
|
|
/// The IPv4 any address used for listening
|
|
public static let any = IPv4Address(in_addr(address: INADDR_ANY), nil)
|
|
|
|
/// The IPv4 broadcast address used to broadcast to all hosts
|
|
public static let broadcast = IPv4Address(in_addr(address: INADDR_BROADCAST), nil)
|
|
|
|
/// The IPv4 loopback address
|
|
public static let loopback = IPv4Address(in_addr(address: INADDR_LOOPBACK), nil)
|
|
|
|
/// The IPv4 all hosts multicast group
|
|
public static let allHostsGroup = IPv4Address(in_addr(address: INADDR_ALLHOSTS_GROUP), nil)
|
|
|
|
/// The IPv4 all routers multicast group
|
|
public static let allRoutersGroup = IPv4Address(in_addr(address: INADDR_ALLRTRS_GROUP), nil)
|
|
|
|
/// The IPv4 all reports multicast group for ICMPv3 membership reports
|
|
public static let allReportsGroup = IPv4Address(in_addr(address: INADDR_ALLRPTS_GROUP), nil)
|
|
|
|
/// The IPv4 multicast DNS group. (Note: Use the dns_sd APIs instead of creating your own responder/resolver)
|
|
public static let mdnsGroup = IPv4Address(in_addr(address: INADDR_ALLMDNS_GROUP), nil)
|
|
|
|
/// Indicates if this IPv4 address is loopback (127.0.0.1)
|
|
public var isLoopback : Bool {
|
|
return self == IPv4Address.loopback
|
|
}
|
|
|
|
/// Indicates if this IPv4 address is link-local
|
|
public var isLinkLocal : Bool {
|
|
let linkLocalMask: UInt32 = IN_CLASSB_NET
|
|
let linkLocalCompare: UInt32 = IN_LINKLOCALNETNUM
|
|
return (self.address.s_addr & linkLocalMask.bigEndian) == linkLocalCompare.bigEndian
|
|
}
|
|
|
|
/// Indicates if this IPv4 address is multicast
|
|
public var isMulticast : Bool {
|
|
let multicastMask: UInt32 = IN_CLASSD_NET
|
|
let multicastCompare: UInt32 = INADDR_UNSPEC_GROUP
|
|
return (self.address.s_addr & multicastMask.bigEndian) == multicastCompare.bigEndian
|
|
}
|
|
|
|
/// Fetch the raw address (four bytes)
|
|
public var rawValue: Data {
|
|
var temporary = self.address
|
|
return withUnsafeBytes(of: &temporary) { (bytes) -> Data in
|
|
Data(bytes)
|
|
}
|
|
}
|
|
|
|
internal init(_ address: in_addr, _ interface: NWInterface?) {
|
|
self.address = address
|
|
self.interface = interface
|
|
}
|
|
|
|
/// Create an IPv4 address from a 4-byte data. Optionally specify an interface.
|
|
///
|
|
/// - Parameter rawValue: The raw bytes of the IPv4 address, must be exactly 4 bytes or init will fail.
|
|
/// - Parameter interface: An optional network interface to scope the address to. Defaults to nil.
|
|
/// - Returns: An IPv4Address or nil if the Data parameter did not contain an IPv4 address.
|
|
public init?(_ rawValue: Data, _ interface: NWInterface? = nil) {
|
|
if rawValue.count != MemoryLayout<in_addr>.size {
|
|
return nil
|
|
}
|
|
let v4 = rawValue.withUnsafeBytes { $0.load(as: in_addr.self) }
|
|
self.init(v4, interface)
|
|
}
|
|
|
|
/// Create an IPv4 address from an address literal string.
|
|
///
|
|
/// This function does not perform host name to address resolution. This is the same as calling getaddrinfo
|
|
/// and using AI_NUMERICHOST.
|
|
///
|
|
/// - Parameter string: An IPv4 address literal string such as "127.0.0.1", "169.254.8.8%en0".
|
|
/// - Returns: An IPv4Address or nil if the string parameter did not
|
|
/// contain an IPv4 address literal.
|
|
public init?(_ string: String) {
|
|
guard let result = getaddrinfo_numeric(string, family: AF_INET) else {
|
|
return nil
|
|
}
|
|
guard case .ipv4(let address) = result else {
|
|
return nil
|
|
}
|
|
self = address
|
|
}
|
|
|
|
fileprivate let address : in_addr
|
|
|
|
/// The interface the address is scoped to, if any.
|
|
public let interface: NWInterface?
|
|
|
|
// Hashable
|
|
public static func == (lhs: IPv4Address, rhs: IPv4Address) -> Bool {
|
|
return lhs.address.s_addr == rhs.address.s_addr && lhs.interface == rhs.interface
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(self.address.s_addr)
|
|
hasher.combine(self.interface)
|
|
}
|
|
|
|
// CustomDebugStringConvertible
|
|
public var debugDescription: String {
|
|
var sin = sockaddr_in(self.address, 0)
|
|
let addressString = getnameinfo_numeric(address: &sin)
|
|
if let interface = self.interface {
|
|
return String("\(addressString)%\(interface)")
|
|
} else {
|
|
return addressString
|
|
}
|
|
}
|
|
}
|
|
|
|
/// IPv6Address
|
|
/// Base type to hold an IPv6 address and convert between strings and raw bytes.
|
|
/// Note that an IPv6 address may be scoped to an interface.
|
|
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
|
public struct IPv6Address: IPAddress, Hashable, CustomDebugStringConvertible {
|
|
|
|
/// IPv6 any address
|
|
public static let any = IPv6Address(in6addr_any, nil)
|
|
|
|
/// IPv6 broadcast address
|
|
public static let broadcast = IPv6Address(in6addr_any, nil)
|
|
|
|
/// IPv6 loopback address
|
|
public static let loopback = IPv6Address(in6addr_loopback, nil)
|
|
|
|
/// IPv6 all node local nodes multicast
|
|
public static let nodeLocalNodes = IPv6Address(in6addr_nodelocal_allnodes, nil)
|
|
|
|
/// IPv6 all link local nodes multicast
|
|
public static let linkLocalNodes = IPv6Address(in6addr_linklocal_allnodes, nil)
|
|
|
|
/// IPv6 all link local routers multicast
|
|
public static let linkLocalRouters = IPv6Address(in6addr_linklocal_allrouters, nil)
|
|
|
|
public enum Scope: UInt8 {
|
|
case nodeLocal = 1
|
|
case linkLocal = 2
|
|
case siteLocal = 5
|
|
case organizationLocal = 8
|
|
case global = 0x0e
|
|
}
|
|
|
|
/// Is the Any address "::0"
|
|
public var isAny : Bool {
|
|
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.1 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.2 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.3 == 0
|
|
}
|
|
|
|
/// Is the looback address "::1"
|
|
public var isLoopback : Bool {
|
|
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.1 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.2 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.3 != 0 &&
|
|
self.address.__u6_addr.__u6_addr32.3 == UInt32(1).bigEndian
|
|
}
|
|
|
|
/// Is an IPv4 compatible address
|
|
public var isIPv4Compatabile : Bool {
|
|
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.1 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.2 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.3 != 0 &&
|
|
self.address.__u6_addr.__u6_addr32.3 != UInt32(1).bigEndian
|
|
}
|
|
|
|
/// Is an IPv4 mapped address such as "::ffff:1.2.3.4"
|
|
public var isIPv4Mapped : Bool {
|
|
return self.address.__u6_addr.__u6_addr32.0 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.1 == 0 &&
|
|
self.address.__u6_addr.__u6_addr32.2 == UInt32(0x0000ffff).bigEndian
|
|
}
|
|
|
|
/// For IPv6 addresses that are IPv4 mapped, returns the IPv4 address
|
|
///
|
|
/// - Returns: nil unless the IPv6 address was mapped or compatible, in which case the IPv4 address is
|
|
/// returned.
|
|
public var asIPv4 : IPv4Address? {
|
|
guard self.isIPv4Mapped || self.isIPv4Compatabile else {
|
|
return nil
|
|
}
|
|
return IPv4Address(in_addr(address: self.address.__u6_addr.__u6_addr32.3.bigEndian),
|
|
self.interface)
|
|
}
|
|
|
|
/// Is a 6to4 IPv6 address
|
|
public var is6to4 : Bool {
|
|
return self.address.__u6_addr.__u6_addr16.0 == UInt16(0x2002).bigEndian
|
|
}
|
|
|
|
/// Is a link-local address
|
|
public var isLinkLocal : Bool {
|
|
return self.address.__u6_addr.__u6_addr8.0 == UInt8(0xfe) &&
|
|
(self.address.__u6_addr.__u6_addr8.1 & 0xc0) == 0x80
|
|
}
|
|
|
|
/// Is multicast
|
|
public var isMulticast : Bool {
|
|
return self.address.__u6_addr.__u6_addr8.0 == 0xff
|
|
}
|
|
|
|
/// Returns the multicast scope
|
|
public var multicastScope : IPv6Address.Scope? {
|
|
if (self.isMulticast) {
|
|
return IPv6Address.Scope(rawValue: self.address.__u6_addr.__u6_addr8.1 & 0x0f)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
internal init(_ ip6: in6_addr, _ interface: NWInterface?) {
|
|
self.address = ip6
|
|
self.interface = interface
|
|
}
|
|
|
|
/// Create an IPv6 from a raw 16 byte value and optional interface
|
|
///
|
|
/// - Parameter rawValue: A 16 byte IPv6 address
|
|
/// - Parameter interface: An optional interface the address is scoped to. Defaults to nil.
|
|
/// - Returns: nil unless the raw data contained an IPv6 address
|
|
public init?(_ rawValue: Data, _ interface: NWInterface? = nil) {
|
|
if rawValue.count != MemoryLayout<in6_addr>.size {
|
|
return nil
|
|
}
|
|
let v6 = rawValue.withUnsafeBytes { $0.load(as: in6_addr.self) }
|
|
self.init(v6, interface)
|
|
}
|
|
|
|
/// Create an IPv6 address from a string literal such as "fe80::1%lo0" or "2001:DB8::5"
|
|
///
|
|
/// This function does not perform hostname resolution. This is similar to calling getaddrinfo with
|
|
/// AI_NUMERICHOST.
|
|
///
|
|
/// - Parameter string: An IPv6 address literal string.
|
|
/// - Returns: nil unless the string contained an IPv6 literal
|
|
public init?(_ string: String) {
|
|
guard let result = getaddrinfo_numeric(string, family: AF_INET6) else {
|
|
return nil
|
|
}
|
|
guard case .ipv6(let address) = result else {
|
|
return nil
|
|
}
|
|
self = address
|
|
}
|
|
|
|
fileprivate let address: in6_addr
|
|
|
|
/// The interface the address is scoped to, if any.
|
|
public let interface: NWInterface?
|
|
|
|
/// Fetch the raw address (sixteen bytes)
|
|
public var rawValue: Data {
|
|
var temporary = self.address
|
|
return withUnsafeBytes(of: &temporary) { (bytes) -> Data in
|
|
Data(bytes)
|
|
}
|
|
}
|
|
|
|
// Hashable
|
|
public static func ==(lhs: IPv6Address, rhs: IPv6Address) -> Bool {
|
|
return lhs.address.__u6_addr.__u6_addr32.0 == rhs.address.__u6_addr.__u6_addr32.0 &&
|
|
lhs.address.__u6_addr.__u6_addr32.1 == rhs.address.__u6_addr.__u6_addr32.1 &&
|
|
lhs.address.__u6_addr.__u6_addr32.2 == rhs.address.__u6_addr.__u6_addr32.2 &&
|
|
lhs.address.__u6_addr.__u6_addr32.3 == rhs.address.__u6_addr.__u6_addr32.3 &&
|
|
lhs.interface == rhs.interface
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(self.address.__u6_addr.__u6_addr32.0)
|
|
hasher.combine(self.address.__u6_addr.__u6_addr32.1)
|
|
hasher.combine(self.address.__u6_addr.__u6_addr32.2)
|
|
hasher.combine(self.address.__u6_addr.__u6_addr32.3)
|
|
hasher.combine(self.interface)
|
|
}
|
|
|
|
// CustomDebugStringConvertible
|
|
public var debugDescription: String {
|
|
var sin6 = sockaddr_in6(self.address, 0)
|
|
let addressString = getnameinfo_numeric(address: &sin6)
|
|
if let interface = self.interface {
|
|
return String("\(addressString)%\(interface)")
|
|
} else {
|
|
return addressString
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
|
|
/// NWEndpoint represents something that can be connected to.
|
|
public enum NWEndpoint: Hashable, CustomDebugStringConvertible {
|
|
// Depends on non-exhaustive enum support for forward compatibility - in the event we need to add
|
|
// a new case in the future
|
|
// https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md
|
|
|
|
/// A host port endpoint represents an endpoint defined by the host and port.
|
|
case hostPort(host: NWEndpoint.Host, port: NWEndpoint.Port)
|
|
|
|
/// A service endpoint represents a Bonjour service
|
|
case service(name: String, type: String, domain: String, interface: NWInterface?)
|
|
|
|
/// A unix endpoint represents a path that supports connections using AF_UNIX domain sockets.
|
|
case unix(path: String)
|
|
|
|
/// A Host is a name or address
|
|
public enum Host: Hashable, CustomDebugStringConvertible, ExpressibleByStringLiteral {
|
|
public typealias StringLiteralType = String
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
case .name(let hostName, let hostInterface):
|
|
hasher.combine(hostInterface)
|
|
hasher.combine(hostName)
|
|
case .ipv4(let v4Address):
|
|
hasher.combine(v4Address)
|
|
case .ipv6(let v6Address):
|
|
hasher.combine(v6Address)
|
|
}
|
|
}
|
|
|
|
/// A host specified as a name and optional interface scope
|
|
case name(String, NWInterface?)
|
|
|
|
/// A host specified as an IPv4 address
|
|
case ipv4(IPv4Address)
|
|
|
|
/// A host specified an an IPv6 address
|
|
case ipv6(IPv6Address)
|
|
|
|
public init(stringLiteral: StringLiteralType) {
|
|
self.init(stringLiteral)
|
|
}
|
|
|
|
/// Create a host from a string.
|
|
///
|
|
/// This is the preferred way to create a host. If the string is an IPv4 address literal ("198.51.100.2"), an
|
|
/// IPv4 host will be created. If the string is an IPv6 address literal ("2001:DB8::2", "fe80::1%lo", etc), an IPv6
|
|
/// host will be created. If the string is an IPv4 mapped IPv6 address literal ("::ffff:198.51.100.2") an IPv4
|
|
/// host will be created. Otherwise, a named host will be created.
|
|
///
|
|
/// - Parameter string: An IPv4 address literal, an IPv6 address literal, or a hostname.
|
|
/// - Returns: A Host object
|
|
public init(_ string: String) {
|
|
if let result = getaddrinfo_numeric(string) {
|
|
self = result
|
|
} else {
|
|
if let range = string.range(of: "%", options: String.CompareOptions.backwards),
|
|
let interface = NWInterface(String(string[range.upperBound...])){
|
|
self = .name(String(string[..<range.lowerBound]), interface)
|
|
} else {
|
|
self = .name(string, nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the interface the host is scoped to if any
|
|
public var interface : NWInterface? {
|
|
switch self {
|
|
case .ipv4(let ip4):
|
|
return ip4.interface
|
|
case .ipv6(let ip6):
|
|
return ip6.interface
|
|
case .name(_, let interface):
|
|
return interface
|
|
}
|
|
}
|
|
|
|
public var debugDescription: String {
|
|
switch self {
|
|
case .ipv4(let ip4):
|
|
return ip4.debugDescription
|
|
case .ipv6(let ip6):
|
|
return ip6.debugDescription
|
|
case .name(let name, let interface):
|
|
if let interface = interface {
|
|
return String("\(name)%\(interface)")
|
|
} else {
|
|
return name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A network port (TCP or UDP)
|
|
public struct Port : Hashable, CustomDebugStringConvertible, ExpressibleByIntegerLiteral, RawRepresentable {
|
|
public typealias IntegerLiteralType = UInt16
|
|
|
|
fileprivate let port: IntegerLiteralType
|
|
|
|
public static let any : NWEndpoint.Port = 0
|
|
public static let ssh : NWEndpoint.Port = 22
|
|
public static let smtp : NWEndpoint.Port = 25
|
|
public static let http : NWEndpoint.Port = 80
|
|
public static let pop : NWEndpoint.Port = 110
|
|
public static let imap : NWEndpoint.Port = 143
|
|
public static let https : NWEndpoint.Port = 443
|
|
public static let imaps : NWEndpoint.Port = 993
|
|
public static let socks : NWEndpoint.Port = 1080
|
|
|
|
public var rawValue: UInt16 {
|
|
return self.port
|
|
}
|
|
|
|
/// Create a port from a string.
|
|
///
|
|
/// Supports common service names such as "http" as well as number strings such as "80".
|
|
///
|
|
/// - Parameter service: A service string such as "http" or a number string such as "80"
|
|
/// - Returns: A port if the string can be converted to a port, nil otherwise.
|
|
public init?(_ service: String) {
|
|
var hints = addrinfo(ai_flags: AI_DEFAULT, ai_family: AF_INET6, ai_socktype: SOCK_STREAM, ai_protocol: 0,
|
|
ai_addrlen: 0, ai_canonname: nil, ai_addr: nil, ai_next: nil)
|
|
var resolved : UnsafeMutablePointer<addrinfo>? = nil
|
|
if getaddrinfo(nil, service, &hints, &resolved) != 0 {
|
|
return nil
|
|
}
|
|
|
|
// Check that it didn't return NULL.
|
|
guard let addrinfo = resolved else {
|
|
return nil
|
|
}
|
|
|
|
// After this point we must ensure we free addrinfo before we return
|
|
guard let sa = addrinfo.pointee.ai_addr, sa.pointee.sa_family == AF_INET6 else {
|
|
freeaddrinfo(addrinfo)
|
|
return nil
|
|
}
|
|
|
|
self.port = sa.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) {sin6 in
|
|
return sin6.pointee.sin6_port.bigEndian
|
|
}
|
|
|
|
freeaddrinfo(addrinfo)
|
|
}
|
|
|
|
public init(integerLiteral value: IntegerLiteralType) {
|
|
self.port = value
|
|
}
|
|
|
|
public init?(rawValue: UInt16) {
|
|
self.port = rawValue
|
|
}
|
|
|
|
internal init(_ value: UInt16) {
|
|
self.init(integerLiteral: value)
|
|
}
|
|
|
|
public var debugDescription: String {
|
|
return String(port)
|
|
}
|
|
}
|
|
|
|
/// Returns the interface the endpoint is scoped to if any
|
|
public var interface : NWInterface? {
|
|
switch self {
|
|
case .hostPort(host: let host, port: _):
|
|
return host.interface
|
|
case .service(name: _, type: _, domain: _, interface: let interface):
|
|
return interface
|
|
case .unix(_):
|
|
return nil
|
|
}
|
|
}
|
|
|
|
internal init?(_ nw: nw_endpoint_t?) {
|
|
guard let nw = nw else {
|
|
return nil
|
|
}
|
|
var interface: NWInterface? = nil
|
|
if let nwinterface = nw_endpoint_copy_interface(nw) {
|
|
interface = NWInterface(nwinterface)
|
|
}
|
|
if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_host {
|
|
let host = NWEndpoint.Host.name(String(cString: nw_endpoint_get_hostname(nw)), interface)
|
|
self = .hostPort(host: host, port: NWEndpoint.Port(nw_endpoint_get_port(nw)))
|
|
} else if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_address {
|
|
let port = NWEndpoint.Port(nw_endpoint_get_port(nw))
|
|
let address = nw_endpoint_get_address(nw)
|
|
if address.pointee.sa_family == AF_INET && address.pointee.sa_len == MemoryLayout<sockaddr_in>.size {
|
|
let host = address.withMemoryRebound(to: sockaddr_in.self, capacity: 1) {
|
|
(sin: UnsafePointer<sockaddr_in>) -> NWEndpoint.Host in
|
|
return NWEndpoint.Host.ipv4(IPv4Address(sin.pointee.sin_addr, interface))
|
|
}
|
|
self = .hostPort(host: host, port: port)
|
|
} else if address.pointee.sa_family == AF_INET6 &&
|
|
address.pointee.sa_len == MemoryLayout<sockaddr_in6>.size {
|
|
let host = address.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) {
|
|
(sin6) -> NWEndpoint.Host in
|
|
if interface == nil && sin6.pointee.sin6_scope_id != 0 {
|
|
interface = NWInterface(Int(sin6.pointee.sin6_scope_id))
|
|
}
|
|
return NWEndpoint.Host.ipv6(IPv6Address(sin6.pointee.sin6_addr,
|
|
interface))
|
|
}
|
|
self = .hostPort(host: host, port: port)
|
|
} else if address.pointee.sa_family == AF_UNIX {
|
|
// sockaddr_un is very difficult to deal with in swift. Fortunately, nw_endpoint_copy_address_string
|
|
// already does exactly what we need.
|
|
let path = nw_endpoint_copy_address_string(nw)
|
|
self = .unix(path: String(cString: path))
|
|
path.deallocate()
|
|
} else {
|
|
return nil
|
|
}
|
|
} else if nw_endpoint_get_type(nw) == Network.nw_endpoint_type_bonjour_service {
|
|
self = .service(name: String(cString: nw_endpoint_get_bonjour_service_name(nw)),
|
|
type: String(cString: nw_endpoint_get_bonjour_service_type(nw)),
|
|
domain: String(cString: nw_endpoint_get_bonjour_service_domain(nw)),
|
|
interface: interface)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
case .hostPort(host: let host, port: let port):
|
|
hasher.combine(host)
|
|
hasher.combine(port)
|
|
case .service(name: let name, type: let type, domain: let domain, interface: let interface):
|
|
hasher.combine(name)
|
|
hasher.combine(type)
|
|
hasher.combine(domain)
|
|
hasher.combine(interface)
|
|
case .unix(let path):
|
|
hasher.combine(path)
|
|
}
|
|
}
|
|
|
|
public var debugDescription: String {
|
|
switch self {
|
|
case .hostPort(host: let host, port: let port):
|
|
var separator = ":"
|
|
if case .ipv6 = host {
|
|
separator = "."
|
|
}
|
|
return String("\(host)\(separator)\(port)")
|
|
case .service(name: let name, type: let type, domain: let domain, interface: let interface):
|
|
if let interface = interface {
|
|
return String("\(name).\(type)\(domain)%\(interface)")
|
|
}
|
|
return String("\(name).\(type)\(domain)")
|
|
case .unix(let path):
|
|
return path
|
|
}
|
|
}
|
|
|
|
internal var nw: nw_endpoint_t? {
|
|
var endpoint: nw_endpoint_t? = nil
|
|
var interface: NWInterface? = nil
|
|
switch self {
|
|
case .hostPort(host: let host, port: let port):
|
|
switch host {
|
|
case .ipv4(let ipv4):
|
|
let sin = sockaddr_in(ipv4.address, port.port.bigEndian)
|
|
endpoint = sin.withSockAddr { (sa) -> nw_endpoint_t in
|
|
nw_endpoint_create_address(sa)
|
|
}
|
|
interface = ipv4.interface
|
|
case .ipv6(let ipv6):
|
|
let sin6 = sockaddr_in6(ipv6.address, port.port.bigEndian)
|
|
endpoint = sin6.withSockAddr { (sa) -> nw_endpoint_t? in
|
|
nw_endpoint_create_address(sa)
|
|
}
|
|
interface = ipv6.interface
|
|
case .name(let host, let hostInterface):
|
|
endpoint = nw_endpoint_create_host(host, port.debugDescription)
|
|
interface = hostInterface
|
|
}
|
|
case .service(name: let name, type: let type, domain: let domain, interface: let bonjourInterface):
|
|
endpoint = nw_endpoint_create_bonjour_service(name, type, domain)
|
|
interface = bonjourInterface
|
|
case .unix(let path):
|
|
endpoint = nw_endpoint_create_unix(path)
|
|
}
|
|
if interface != nil && endpoint != nil {
|
|
nw_endpoint_set_interface(endpoint!, interface!.nw)
|
|
}
|
|
return endpoint
|
|
}
|
|
}
|