diff --git a/Sources/NIO/Linux.swift b/Sources/NIO/Linux.swift index 01e303ae..4d998a0f 100644 --- a/Sources/NIO/Linux.swift +++ b/Sources/NIO/Linux.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -118,9 +118,6 @@ internal enum Epoll { } internal enum Linux { - static let cfsQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" - static let cfsPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" - static let cpuSetPath = "/sys/fs/cgroup/cpuset/cpuset.cpus" #if os(Android) static let SOCK_CLOEXEC = Glibc.SOCK_CLOEXEC static let SOCK_NONBLOCK = Glibc.SOCK_NONBLOCK @@ -140,54 +137,5 @@ internal enum Linux { } return fd } - - private static func firstLineOfFile(path: String) throws -> Substring { - let fh = try NIOFileHandle(path: path) - defer { try! fh.close() } - // linux doesn't properly report /sys/fs/cgroup/* files lengths so we use a reasonable limit - var buf = ByteBufferAllocator().buffer(capacity: 1024) - try buf.writeWithUnsafeMutableBytes(minimumWritableBytes: buf.capacity) { ptr in - let res = try fh.withUnsafeFileDescriptor { fd -> IOResult in - return try Posix.read(descriptor: fd, pointer: ptr.baseAddress!, size: ptr.count) - } - switch res { - case .processed(let n): - return n - case .wouldBlock: - preconditionFailure("read returned EWOULDBLOCK despite a blocking fd") - } - } - return String(buffer: buf).prefix(while: { $0 != "\n" }) - } - - private static func countCoreIds(cores: Substring) -> Int { - let ids = cores.split(separator: "-", maxSplits: 1) - guard - let first = ids.first.flatMap({ Int($0, radix: 10) }), - let last = ids.last.flatMap({ Int($0, radix: 10) }), - last >= first - else { preconditionFailure("cpuset format is incorrect") } - return 1 + last - first - } - - static func coreCount(cpuset cpusetPath: String) -> Int? { - guard - let cpuset = try? firstLineOfFile(path: cpusetPath).split(separator: ","), - !cpuset.isEmpty - else { return nil } - return cpuset.map(countCoreIds).reduce(0, +) - } - - static func coreCount(quota quotaPath: String, period periodPath: String) -> Int? { - guard - let quota = try? Int(firstLineOfFile(path: quotaPath)), - quota > 0 - else { return nil } - guard - let period = try? Int(firstLineOfFile(path: periodPath)), - period > 0 - else { return nil } - return (quota - 1 + period) / period // always round up if fractional CPU quota requested - } } #endif diff --git a/Sources/NIO/NetworkDevices.swift b/Sources/NIO/NetworkDevices.swift deleted file mode 100644 index 077110e5..00000000 --- a/Sources/NIO/NetworkDevices.swift +++ /dev/null @@ -1,367 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2021 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 -// -//===----------------------------------------------------------------------===// - -#if os(Linux) || os(FreeBSD) || os(Android) -import CNIOLinux -#endif - -#if os(Windows) -import let WinSDK.AF_INET -import let WinSDK.AF_INET6 - -import let WinSDK.INET_ADDRSTRLEN -import let WinSDK.INET6_ADDRSTRLEN - -import struct WinSDK.ADDRESS_FAMILY -import struct WinSDK.IP_ADAPTER_ADDRESSES -import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS - -import typealias WinSDK.UINT8 -#endif - -#if !os(Windows) -private extension ifaddrs { - var dstaddr: UnsafeMutablePointer? { - #if os(Linux) || os(Android) - return self.ifa_ifu.ifu_dstaddr - #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - return self.ifa_dstaddr - #endif - } - - var broadaddr: UnsafeMutablePointer? { - #if os(Linux) || os(Android) - return self.ifa_ifu.ifu_broadaddr - #elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - return self.ifa_dstaddr - #endif - } -} -#endif - -/// A representation of a single network device on a system. -public struct NIONetworkDevice { - private var backing: Backing - - /// The name of the network device. - public var name: String { - get { - return self.backing.name - } - set { - self.uniquifyIfNeeded() - self.backing.name = newValue - } - } - - /// The address associated with the given network device. - public var address: SocketAddress? { - get { - return self.backing.address - } - set { - self.uniquifyIfNeeded() - self.backing.address = newValue - } - } - - /// The netmask associated with this address, if any. - public var netmask: SocketAddress? { - get { - return self.backing.netmask - } - set { - self.uniquifyIfNeeded() - self.backing.netmask = newValue - } - } - - /// The broadcast address associated with this socket interface, if it has one. Some - /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. - public var broadcastAddress: SocketAddress? { - get { - return self.backing.broadcastAddress - } - set { - self.uniquifyIfNeeded() - self.backing.broadcastAddress = newValue - } - } - - /// The address of the peer on a point-to-point interface, if this is one. Some - /// interfaces do not have such an address: most of those have a `broadcastAddress` - /// instead. - public var pointToPointDestinationAddress: SocketAddress? { - get { - return self.backing.pointToPointDestinationAddress - } - set { - self.uniquifyIfNeeded() - self.backing.pointToPointDestinationAddress = newValue - } - } - - /// If the Interface supports Multicast - public var multicastSupported: Bool { - get { - return self.backing.multicastSupported - } - set { - self.uniquifyIfNeeded() - self.backing.multicastSupported = newValue - } - } - - /// The index of the interface, as provided by `if_nametoindex`. - public var interfaceIndex: Int { - get { - return self.backing.interfaceIndex - } - set { - self.uniquifyIfNeeded() - self.backing.interfaceIndex = newValue - } - } - - /// Create a brand new network interface. - /// - /// This constructor will fail if NIO does not understand the format of the underlying - /// socket address family. This is quite common: for example, Linux will return AF_PACKET - /// addressed interfaces on most platforms, which NIO does not currently understand. -#if os(Windows) - internal init?(_ pAdapter: UnsafeMutablePointer, - _ pAddress: UnsafeMutablePointer) { - guard let backing = Backing(pAdapter, pAddress) else { - return nil - } - self.backing = backing - } -#else - internal init?(_ caddr: ifaddrs) { - guard let backing = Backing(caddr) else { - return nil - } - - self.backing = backing - } -#endif - -#if !os(Windows) - /// Convert a `NIONetworkInterface` to a `NIONetworkDevice`. As `NIONetworkDevice`s are a superset of `NIONetworkInterface`s, - /// it is always possible to perform this conversion. - @available(*, deprecated, message: "This is a compatibility helper, and will be removed in a future release") - public init(_ interface: NIONetworkInterface) { - self.backing = Backing( - name: interface.name, - address: interface.address, - netmask: interface.netmask, - broadcastAddress: interface.broadcastAddress, - pointToPointDestinationAddress: interface.pointToPointDestinationAddress, - multicastSupported: interface.multicastSupported, - interfaceIndex: interface.interfaceIndex - ) - } -#endif - - public init(name: String, - address: SocketAddress?, - netmask: SocketAddress?, - broadcastAddress: SocketAddress?, - pointToPointDestinationAddress: SocketAddress, - multicastSupported: Bool, - interfaceIndex: Int) { - self.backing = Backing( - name: name, - address: address, - netmask: netmask, - broadcastAddress: broadcastAddress, - pointToPointDestinationAddress: pointToPointDestinationAddress, - multicastSupported: multicastSupported, - interfaceIndex: interfaceIndex - ) - } - - private mutating func uniquifyIfNeeded() { - if !isKnownUniquelyReferenced(&self.backing) { - self.backing = Backing(copying: self.backing) - } - } -} - -extension NIONetworkDevice { - fileprivate final class Backing { - /// The name of the network interface. - var name: String - - /// The address associated with the given network interface. - var address: SocketAddress? - - /// The netmask associated with this address, if any. - var netmask: SocketAddress? - - /// The broadcast address associated with this socket interface, if it has one. Some - /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. - var broadcastAddress: SocketAddress? - - /// The address of the peer on a point-to-point interface, if this is one. Some - /// interfaces do not have such an address: most of those have a `broadcastAddress` - /// instead. - var pointToPointDestinationAddress: SocketAddress? - - /// If the Interface supports Multicast - var multicastSupported: Bool - - /// The index of the interface, as provided by `if_nametoindex`. - var interfaceIndex: Int - - /// Create a brand new network interface. - /// - /// This constructor will fail if NIO does not understand the format of the underlying - /// socket address family. This is quite common: for example, Linux will return AF_PACKET - /// addressed interfaces on most platforms, which NIO does not currently understand. -#if os(Windows) - internal init?(_ pAdapter: UnsafeMutablePointer, - _ pAddress: UnsafeMutablePointer) { - self.name = String(decodingCString: pAdapter.pointee.FriendlyName, - as: UTF16.self) - self.address = pAddress.pointee.Address.lpSockaddr.convert() - - // TODO: convert the prefix length to the mask itself - let v4mask: (UINT8) -> SocketAddress? = { _ in - var buffer: [CChar] = - Array(repeating: 0, count: Int(INET_ADDRSTRLEN)) - var mask: sockaddr_in = sockaddr_in() - mask.sin_family = ADDRESS_FAMILY(AF_INET) - _ = buffer.withUnsafeMutableBufferPointer { - try! NIOBSDSocket.inet_ntop(af: .inet, src: &mask, - dst: $0.baseAddress!, - size: INET_ADDRSTRLEN) - } - return SocketAddress(mask) - } - let v6mask: (UINT8) -> SocketAddress? = { _ in - var buffer: [CChar] = - Array(repeating: 0, count: Int(INET6_ADDRSTRLEN)) - var mask: sockaddr_in6 = sockaddr_in6() - mask.sin6_family = ADDRESS_FAMILY(AF_INET6) - _ = buffer.withUnsafeMutableBufferPointer { - try! NIOBSDSocket.inet_ntop(af: .inet6, src: &mask, - dst: $0.baseAddress!, - size: INET6_ADDRSTRLEN) - } - return SocketAddress(mask) - } - - switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family { - case ADDRESS_FAMILY(AF_INET): - self.netmask = v4mask(pAddress.pointee.OnLinkPrefixLength) - self.interfaceIndex = Int(pAdapter.pointee.IfIndex) - break - case ADDRESS_FAMILY(AF_INET6): - self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength) - self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex) - break - default: - return nil - } - - // TODO(compnerd) handle broadcast/ppp/multicast information - self.broadcastAddress = nil - self.pointToPointDestinationAddress = nil - self.multicastSupported = false - } -#else - internal init?(_ caddr: ifaddrs) { - self.name = String(cString: caddr.ifa_name) - self.address = caddr.ifa_addr.flatMap { $0.convert() } - self.netmask = caddr.ifa_netmask.flatMap { $0.convert() } - - if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { - self.broadcastAddress = addr.convert() - self.pointToPointDestinationAddress = nil - } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { - self.broadcastAddress = nil - self.pointToPointDestinationAddress = addr.convert() - } else { - self.broadcastAddress = nil - self.pointToPointDestinationAddress = nil - } - - self.multicastSupported = (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 - do { - self.interfaceIndex = Int(try Posix.if_nametoindex(caddr.ifa_name)) - } catch { - return nil - } - } -#endif - - init(copying original: Backing) { - self.name = original.name - self.address = original.address - self.netmask = original.netmask - self.broadcastAddress = original.broadcastAddress - self.pointToPointDestinationAddress = original.pointToPointDestinationAddress - self.multicastSupported = original.multicastSupported - self.interfaceIndex = original.interfaceIndex - } - - init(name: String, - address: SocketAddress?, - netmask: SocketAddress?, - broadcastAddress: SocketAddress?, - pointToPointDestinationAddress: SocketAddress?, - multicastSupported: Bool, - interfaceIndex: Int) { - self.name = name - self.address = address - self.netmask = netmask - self.broadcastAddress = broadcastAddress - self.pointToPointDestinationAddress = pointToPointDestinationAddress - self.multicastSupported = multicastSupported - self.interfaceIndex = interfaceIndex - } - } -} - -extension NIONetworkDevice: CustomDebugStringConvertible { - public var debugDescription: String { - let baseString = "Device \(self.name): address \(String(describing: self.address))" - let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" - return baseString + maskString - } -} - -// Sadly, as this is class-backed we cannot synthesise the implementation. -extension NIONetworkDevice: Equatable { - public static func ==(lhs: NIONetworkDevice, rhs: NIONetworkDevice) -> Bool { - return lhs.name == rhs.name && - lhs.address == rhs.address && - lhs.netmask == rhs.netmask && - lhs.broadcastAddress == rhs.broadcastAddress && - lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress && - lhs.interfaceIndex == rhs.interfaceIndex - } -} - -extension NIONetworkDevice: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(self.name) - hasher.combine(self.address) - hasher.combine(self.netmask) - hasher.combine(self.broadcastAddress) - hasher.combine(self.pointToPointDestinationAddress) - hasher.combine(self.interfaceIndex) - } -} diff --git a/Sources/NIO/SelectableChannel.swift b/Sources/NIO/SelectableChannel.swift index 0e8050ba..57f5eb75 100644 --- a/Sources/NIO/SelectableChannel.swift +++ b/Sources/NIO/SelectableChannel.swift @@ -49,17 +49,3 @@ internal protocol SelectableChannel: Channel { func reregister(selector: Selector, interested: SelectorEventSet) throws } - -/// Multicast is not supported on this interface. -public struct NIOMulticastNotSupportedError: Error { - public var device: NIONetworkDevice - - public init(device: NIONetworkDevice) { - self.device = device - } -} - -/// Multicast has not been properly implemented on this channel. -public struct NIOMulticastNotImplementedError: Error { - public init() {} -} diff --git a/Sources/NIO/System.swift b/Sources/NIO/System.swift index 35589805..92b3cc0b 100644 --- a/Sources/NIO/System.swift +++ b/Sources/NIO/System.swift @@ -99,7 +99,6 @@ private let sysDup: @convention(c) (CInt) -> CInt = dup #if !os(Windows) private let sysGetpeername: @convention(c) (CInt, UnsafeMutablePointer?, UnsafeMutablePointer?) -> CInt = getpeername private let sysGetsockname: @convention(c) (CInt, UnsafeMutablePointer?, UnsafeMutablePointer?) -> CInt = getsockname -private let sysGetifaddrs: @convention(c) (UnsafeMutablePointer?>?) -> CInt = getifaddrs #endif private let sysFreeifaddrs: @convention(c) (UnsafeMutablePointer?) -> Void = freeifaddrs private let sysIfNameToIndex: @convention(c) (UnsafePointer?) -> CUnsignedInt = if_nametoindex @@ -488,13 +487,6 @@ internal enum Posix { return sysGetsockname(socket, address, addressLength) } } - - @inline(never) - internal static func getifaddrs(_ addrs: UnsafeMutablePointer?>) throws { - _ = try syscall(blocking: false) { - sysGetifaddrs(addrs) - } - } #endif @inline(never) diff --git a/Sources/NIO/Utilities.swift b/Sources/NIO/Utilities.swift index d5d360f1..43329b98 100644 --- a/Sources/NIO/Utilities.swift +++ b/Sources/NIO/Utilities.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,28 +12,6 @@ // //===----------------------------------------------------------------------===// -#if os(Linux) || os(FreeBSD) || os(Android) -import CNIOLinux -#endif - -#if os(Windows) -import let WinSDK.RelationProcessorCore - -import let WinSDK.AF_UNSPEC -import let WinSDK.ERROR_SUCCESS - -import func WinSDK.GetAdaptersAddresses -import func WinSDK.GetLastError -import func WinSDK.GetLogicalProcessorInformation - -import struct WinSDK.IP_ADAPTER_ADDRESSES -import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS -import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION -import struct WinSDK.ULONG - -import typealias WinSDK.DWORD -#endif - /// A utility function that runs the body code only in debug builds, without /// emitting compiler warnings. /// @@ -49,151 +27,3 @@ final class Box { let value: T init(_ value: T) { self.value = value } } - -public enum System { - /// A utility function that returns an estimate of the number of *logical* cores - /// on the system. - /// - /// This value can be used to help provide an estimate of how many threads to use with - /// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number - /// of threads to use is a matter for the programmer, and can be determined based on the - /// specific execution behaviour of the program. - /// - /// - returns: The logical core count on the system. - public static var coreCount: Int { -#if os(Windows) - var dwLength: DWORD = 0 - _ = GetLogicalProcessorInformation(nil, &dwLength) - - let alignment: Int = - MemoryLayout.alignment - let pBuffer: UnsafeMutableRawPointer = - UnsafeMutableRawPointer.allocate(byteCount: Int(dwLength), - alignment: alignment) - defer { - pBuffer.deallocate() - } - - let dwSLPICount: Int = - Int(dwLength) / MemoryLayout.stride - let pSLPI: UnsafeMutablePointer = - pBuffer.bindMemory(to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self, - capacity: dwSLPICount) - - let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength) - precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())") - - return UnsafeBufferPointer(start: pSLPI, - count: dwSLPICount) - .filter { $0.Relationship == RelationProcessorCore } - .map { $0.ProcessorMask.nonzeroBitCount } - .reduce(0, +) -#elseif os(Linux) || os(Android) - if let quota = Linux.coreCount(quota: Linux.cfsQuotaPath, period: Linux.cfsPeriodPath) { - return quota - } else if let cpusetCount = Linux.coreCount(cpuset: Linux.cpuSetPath) { - return cpusetCount - } else { - return sysconf(CInt(_SC_NPROCESSORS_ONLN)) - } -#else - return sysconf(CInt(_SC_NPROCESSORS_ONLN)) -#endif - } - -#if !os(Windows) - /// A utility function that enumerates the available network interfaces on this machine. - /// - /// This function returns values that are true for a brief snapshot in time. These results can - /// change, and the returned values will not change to reflect them. This function must be called - /// again to get new results. - /// - /// - returns: An array of network interfaces available on this machine. - /// - throws: If an error is encountered while enumerating interfaces. - @available(*, deprecated, renamed: "enumerateDevices") - public static func enumerateInterfaces() throws -> [NIONetworkInterface] { - var interfaces: [NIONetworkInterface] = [] - interfaces.reserveCapacity(12) // Arbitrary choice. - - var interface: UnsafeMutablePointer? = nil - try Posix.getifaddrs(&interface) - let originalInterface = interface - defer { - freeifaddrs(originalInterface) - } - - while let concreteInterface = interface { - if let nioInterface = NIONetworkInterface._construct(from: concreteInterface.pointee) { - interfaces.append(nioInterface) - } - interface = concreteInterface.pointee.ifa_next - } - - return interfaces - } -#endif - - /// A utility function that enumerates the available network devices on this machine. - /// - /// This function returns values that are true for a brief snapshot in time. These results can - /// change, and the returned values will not change to reflect them. This function must be called - /// again to get new results. - /// - /// - returns: An array of network devices available on this machine. - /// - throws: If an error is encountered while enumerating interfaces. - public static func enumerateDevices() throws -> [NIONetworkDevice] { - var devices: [NIONetworkDevice] = [] - devices.reserveCapacity(12) // Arbitrary choice. - -#if os(Windows) - var ulSize: ULONG = 0 - _ = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, nil, &ulSize) - - let stride: Int = MemoryLayout.stride - let pBuffer: UnsafeMutableBufferPointer = - UnsafeMutableBufferPointer.allocate(capacity: Int(ulSize) / stride) - defer { - pBuffer.deallocate() - } - - let ulResult: ULONG = - GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pBuffer.baseAddress, - &ulSize) - guard ulResult == ERROR_SUCCESS else { - throw IOError(windows: ulResult, reason: "GetAdaptersAddresses") - } - - var pAdapter: UnsafeMutablePointer? = - UnsafeMutablePointer(pBuffer.baseAddress) - while pAdapter != nil { - let pUnicastAddresses: UnsafeMutablePointer? = - pAdapter!.pointee.FirstUnicastAddress - var pUnicastAddress: UnsafeMutablePointer? = - pUnicastAddresses - while pUnicastAddress != nil { - if let device = NIONetworkDevice(pAdapter!, pUnicastAddress!) { - devices.append(device) - } - pUnicastAddress = pUnicastAddress!.pointee.Next - } - pAdapter = pAdapter!.pointee.Next - } -#else - var interface: UnsafeMutablePointer? = nil - try Posix.getifaddrs(&interface) - let originalInterface = interface - defer { - freeifaddrs(originalInterface) - } - - while let concreteInterface = interface { - if let nioInterface = NIONetworkDevice(concreteInterface.pointee) { - devices.append(nioInterface) - } - interface = concreteInterface.pointee.ifa_next - } - -#endif - return devices - } -} diff --git a/Sources/NIOCore/Interfaces.swift b/Sources/NIOCore/Interfaces.swift index 60be6aee..fedf9873 100644 --- a/Sources/NIOCore/Interfaces.swift +++ b/Sources/NIOCore/Interfaces.swift @@ -81,7 +81,7 @@ public final class NIONetworkInterface { /// The index of the interface, as provided by `if_nametoindex`. public let interfaceIndex: Int - fileprivate init?(_ caddr: ifaddrs) { + internal init?(_ caddr: ifaddrs) { self.name = String(cString: caddr.ifa_name) guard caddr.ifa_addr != nil else { @@ -122,12 +122,6 @@ public final class NIONetworkInterface { return nil } } - - // This is public just so we can avoid needing to pull over any of the System helpers: they can - // construct this type directly. Ideally, we'd have avoided even needing this in NIOCore. - public static func _construct(from caddr: ifaddrs) -> NIONetworkInterface? { - return NIONetworkInterface(caddr) - } } @available(*, deprecated, renamed: "NIONetworkDevice") @@ -173,3 +167,320 @@ extension UnsafeMutablePointer where Pointee == sockaddr { } } } + +/// A representation of a single network device on a system. +public struct NIONetworkDevice { + private var backing: Backing + + /// The name of the network device. + public var name: String { + get { + return self.backing.name + } + set { + self.uniquifyIfNeeded() + self.backing.name = newValue + } + } + + /// The address associated with the given network device. + public var address: SocketAddress? { + get { + return self.backing.address + } + set { + self.uniquifyIfNeeded() + self.backing.address = newValue + } + } + + /// The netmask associated with this address, if any. + public var netmask: SocketAddress? { + get { + return self.backing.netmask + } + set { + self.uniquifyIfNeeded() + self.backing.netmask = newValue + } + } + + /// The broadcast address associated with this socket interface, if it has one. Some + /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. + public var broadcastAddress: SocketAddress? { + get { + return self.backing.broadcastAddress + } + set { + self.uniquifyIfNeeded() + self.backing.broadcastAddress = newValue + } + } + + /// The address of the peer on a point-to-point interface, if this is one. Some + /// interfaces do not have such an address: most of those have a `broadcastAddress` + /// instead. + public var pointToPointDestinationAddress: SocketAddress? { + get { + return self.backing.pointToPointDestinationAddress + } + set { + self.uniquifyIfNeeded() + self.backing.pointToPointDestinationAddress = newValue + } + } + + /// If the Interface supports Multicast + public var multicastSupported: Bool { + get { + return self.backing.multicastSupported + } + set { + self.uniquifyIfNeeded() + self.backing.multicastSupported = newValue + } + } + + /// The index of the interface, as provided by `if_nametoindex`. + public var interfaceIndex: Int { + get { + return self.backing.interfaceIndex + } + set { + self.uniquifyIfNeeded() + self.backing.interfaceIndex = newValue + } + } + + /// Create a brand new network interface. + /// + /// This constructor will fail if NIO does not understand the format of the underlying + /// socket address family. This is quite common: for example, Linux will return AF_PACKET + /// addressed interfaces on most platforms, which NIO does not currently understand. +#if os(Windows) + internal init?(_ pAdapter: UnsafeMutablePointer, + _ pAddress: UnsafeMutablePointer) { + guard let backing = Backing(pAdapter, pAddress) else { + return nil + } + self.backing = backing + } +#else + internal init?(_ caddr: ifaddrs) { + guard let backing = Backing(caddr) else { + return nil + } + + self.backing = backing + } +#endif + +#if !os(Windows) + /// Convert a `NIONetworkInterface` to a `NIONetworkDevice`. As `NIONetworkDevice`s are a superset of `NIONetworkInterface`s, + /// it is always possible to perform this conversion. + @available(*, deprecated, message: "This is a compatibility helper, and will be removed in a future release") + public init(_ interface: NIONetworkInterface) { + self.backing = Backing( + name: interface.name, + address: interface.address, + netmask: interface.netmask, + broadcastAddress: interface.broadcastAddress, + pointToPointDestinationAddress: interface.pointToPointDestinationAddress, + multicastSupported: interface.multicastSupported, + interfaceIndex: interface.interfaceIndex + ) + } +#endif + + public init(name: String, + address: SocketAddress?, + netmask: SocketAddress?, + broadcastAddress: SocketAddress?, + pointToPointDestinationAddress: SocketAddress, + multicastSupported: Bool, + interfaceIndex: Int) { + self.backing = Backing( + name: name, + address: address, + netmask: netmask, + broadcastAddress: broadcastAddress, + pointToPointDestinationAddress: pointToPointDestinationAddress, + multicastSupported: multicastSupported, + interfaceIndex: interfaceIndex + ) + } + + private mutating func uniquifyIfNeeded() { + if !isKnownUniquelyReferenced(&self.backing) { + self.backing = Backing(copying: self.backing) + } + } +} + +extension NIONetworkDevice { + fileprivate final class Backing { + /// The name of the network interface. + var name: String + + /// The address associated with the given network interface. + var address: SocketAddress? + + /// The netmask associated with this address, if any. + var netmask: SocketAddress? + + /// The broadcast address associated with this socket interface, if it has one. Some + /// interfaces do not, especially those that have a `pointToPointDestinationAddress`. + var broadcastAddress: SocketAddress? + + /// The address of the peer on a point-to-point interface, if this is one. Some + /// interfaces do not have such an address: most of those have a `broadcastAddress` + /// instead. + var pointToPointDestinationAddress: SocketAddress? + + /// If the Interface supports Multicast + var multicastSupported: Bool + + /// The index of the interface, as provided by `if_nametoindex`. + var interfaceIndex: Int + + /// Create a brand new network interface. + /// + /// This constructor will fail if NIO does not understand the format of the underlying + /// socket address family. This is quite common: for example, Linux will return AF_PACKET + /// addressed interfaces on most platforms, which NIO does not currently understand. +#if os(Windows) + internal init?(_ pAdapter: UnsafeMutablePointer, + _ pAddress: UnsafeMutablePointer) { + self.name = String(decodingCString: pAdapter.pointee.FriendlyName, + as: UTF16.self) + self.address = pAddress.pointee.Address.lpSockaddr.convert() + + // TODO: convert the prefix length to the mask itself + let v4mask: (UINT8) -> SocketAddress? = { _ in + var buffer: [CChar] = + Array(repeating: 0, count: Int(INET_ADDRSTRLEN)) + var mask: sockaddr_in = sockaddr_in() + mask.sin_family = ADDRESS_FAMILY(AF_INET) + _ = buffer.withUnsafeMutableBufferPointer { + try! NIOBSDSocket.inet_ntop(af: .inet, src: &mask, + dst: $0.baseAddress!, + size: INET_ADDRSTRLEN) + } + return SocketAddress(mask) + } + let v6mask: (UINT8) -> SocketAddress? = { _ in + var buffer: [CChar] = + Array(repeating: 0, count: Int(INET6_ADDRSTRLEN)) + var mask: sockaddr_in6 = sockaddr_in6() + mask.sin6_family = ADDRESS_FAMILY(AF_INET6) + _ = buffer.withUnsafeMutableBufferPointer { + try! NIOBSDSocket.inet_ntop(af: .inet6, src: &mask, + dst: $0.baseAddress!, + size: INET6_ADDRSTRLEN) + } + return SocketAddress(mask) + } + + switch pAddress.pointee.Address.lpSockaddr.pointee.sa_family { + case ADDRESS_FAMILY(AF_INET): + self.netmask = v4mask(pAddress.pointee.OnLinkPrefixLength) + self.interfaceIndex = Int(pAdapter.pointee.IfIndex) + break + case ADDRESS_FAMILY(AF_INET6): + self.netmask = v6mask(pAddress.pointee.OnLinkPrefixLength) + self.interfaceIndex = Int(pAdapter.pointee.Ipv6IfIndex) + break + default: + return nil + } + + // TODO(compnerd) handle broadcast/ppp/multicast information + self.broadcastAddress = nil + self.pointToPointDestinationAddress = nil + self.multicastSupported = false + } +#else + internal init?(_ caddr: ifaddrs) { + self.name = String(cString: caddr.ifa_name) + self.address = caddr.ifa_addr.flatMap { $0.convert() } + self.netmask = caddr.ifa_netmask.flatMap { $0.convert() } + + if (caddr.ifa_flags & UInt32(IFF_BROADCAST)) != 0, let addr = caddr.broadaddr { + self.broadcastAddress = addr.convert() + self.pointToPointDestinationAddress = nil + } else if (caddr.ifa_flags & UInt32(IFF_POINTOPOINT)) != 0, let addr = caddr.dstaddr { + self.broadcastAddress = nil + self.pointToPointDestinationAddress = addr.convert() + } else { + self.broadcastAddress = nil + self.pointToPointDestinationAddress = nil + } + + self.multicastSupported = (caddr.ifa_flags & UInt32(IFF_MULTICAST)) != 0 + do { + self.interfaceIndex = Int(try SystemCalls.if_nametoindex(caddr.ifa_name)) + } catch { + return nil + } + } +#endif + + init(copying original: Backing) { + self.name = original.name + self.address = original.address + self.netmask = original.netmask + self.broadcastAddress = original.broadcastAddress + self.pointToPointDestinationAddress = original.pointToPointDestinationAddress + self.multicastSupported = original.multicastSupported + self.interfaceIndex = original.interfaceIndex + } + + init(name: String, + address: SocketAddress?, + netmask: SocketAddress?, + broadcastAddress: SocketAddress?, + pointToPointDestinationAddress: SocketAddress?, + multicastSupported: Bool, + interfaceIndex: Int) { + self.name = name + self.address = address + self.netmask = netmask + self.broadcastAddress = broadcastAddress + self.pointToPointDestinationAddress = pointToPointDestinationAddress + self.multicastSupported = multicastSupported + self.interfaceIndex = interfaceIndex + } + } +} + +extension NIONetworkDevice: CustomDebugStringConvertible { + public var debugDescription: String { + let baseString = "Device \(self.name): address \(String(describing: self.address))" + let maskString = self.netmask != nil ? " netmask \(self.netmask!)" : "" + return baseString + maskString + } +} + +// Sadly, as this is class-backed we cannot synthesise the implementation. +extension NIONetworkDevice: Equatable { + public static func ==(lhs: NIONetworkDevice, rhs: NIONetworkDevice) -> Bool { + return lhs.name == rhs.name && + lhs.address == rhs.address && + lhs.netmask == rhs.netmask && + lhs.broadcastAddress == rhs.broadcastAddress && + lhs.pointToPointDestinationAddress == rhs.pointToPointDestinationAddress && + lhs.interfaceIndex == rhs.interfaceIndex + } +} + +extension NIONetworkDevice: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(self.name) + hasher.combine(self.address) + hasher.combine(self.netmask) + hasher.combine(self.broadcastAddress) + hasher.combine(self.pointToPointDestinationAddress) + hasher.combine(self.interfaceIndex) + } +} + diff --git a/Sources/NIOCore/Linux.swift b/Sources/NIOCore/Linux.swift new file mode 100644 index 00000000..f463deec --- /dev/null +++ b/Sources/NIOCore/Linux.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// This is a companion to System.swift that provides only Linux specials: either things that exist +// only on Linux, or things that have Linux-specific extensions. + +#if os(Linux) || os(Android) +import CNIOLinux +enum Linux { + static let cfsQuotaPath = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" + static let cfsPeriodPath = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" + static let cpuSetPath = "/sys/fs/cgroup/cpuset/cpuset.cpus" + + private static func firstLineOfFile(path: String) throws -> Substring { + let fh = try NIOFileHandle(path: path) + defer { try! fh.close() } + // linux doesn't properly report /sys/fs/cgroup/* files lengths so we use a reasonable limit + var buf = ByteBufferAllocator().buffer(capacity: 1024) + try buf.writeWithUnsafeMutableBytes(minimumWritableBytes: buf.capacity) { ptr in + let res = try fh.withUnsafeFileDescriptor { fd -> CoreIOResult in + return try SystemCalls.read(descriptor: fd, pointer: ptr.baseAddress!, size: ptr.count) + } + switch res { + case .processed(let n): + return n + case .wouldBlock: + preconditionFailure("read returned EWOULDBLOCK despite a blocking fd") + } + } + return String(buffer: buf).prefix(while: { $0 != "\n" }) + } + + private static func countCoreIds(cores: Substring) -> Int { + let ids = cores.split(separator: "-", maxSplits: 1) + guard + let first = ids.first.flatMap({ Int($0, radix: 10) }), + let last = ids.last.flatMap({ Int($0, radix: 10) }), + last >= first + else { preconditionFailure("cpuset format is incorrect") } + return 1 + last - first + } + + static func coreCount(cpuset cpusetPath: String) -> Int? { + guard + let cpuset = try? firstLineOfFile(path: cpusetPath).split(separator: ","), + !cpuset.isEmpty + else { return nil } + return cpuset.map(countCoreIds).reduce(0, +) + } + + static func coreCount(quota quotaPath: String, period periodPath: String) -> Int? { + guard + let quota = try? Int(firstLineOfFile(path: quotaPath)), + quota > 0 + else { return nil } + guard + let period = try? Int(firstLineOfFile(path: periodPath)), + period > 0 + else { return nil } + return (quota - 1 + period) / period // always round up if fractional CPU quota requested + } +} +#endif diff --git a/Sources/NIO/MulticastChannel.swift b/Sources/NIOCore/MulticastChannel.swift similarity index 95% rename from Sources/NIO/MulticastChannel.swift rename to Sources/NIOCore/MulticastChannel.swift index 66476747..9669617d 100644 --- a/Sources/NIO/MulticastChannel.swift +++ b/Sources/NIOCore/MulticastChannel.swift @@ -162,3 +162,18 @@ extension MulticastChannel { promise?.fail(NIOMulticastNotImplementedError()) } } + +/// Multicast is not supported on this interface. +public struct NIOMulticastNotSupportedError: Error { + public var device: NIONetworkDevice + + public init(device: NIONetworkDevice) { + self.device = device + } +} + +/// Multicast has not been properly implemented on this channel. +public struct NIOMulticastNotImplementedError: Error { + public init() {} +} + diff --git a/Sources/NIOCore/SystemCallHelpers.swift b/Sources/NIOCore/SystemCallHelpers.swift index 35a6425c..9451e30c 100644 --- a/Sources/NIOCore/SystemCallHelpers.swift +++ b/Sources/NIOCore/SystemCallHelpers.swift @@ -33,8 +33,13 @@ private let sysDup: @convention(c) (CInt) -> CInt = dup private let sysClose: @convention(c) (CInt) -> CInt = close private let sysOpenWithMode: @convention(c) (UnsafePointer, CInt, mode_t) -> CInt = open private let sysLseek: @convention(c) (CInt, off_t, CInt) -> off_t = lseek +private let sysRead: @convention(c) (CInt, UnsafeMutableRawPointer?, size_t) -> size_t = read private let sysIfNameToIndex: @convention(c) (UnsafePointer?) -> CUnsignedInt = if_nametoindex +#if !os(Windows) +private let sysGetifaddrs: @convention(c) (UnsafeMutablePointer?>?) -> CInt = getifaddrs +#endif + private func isUnacceptableErrno(_ code: Int32) -> Bool { switch code { case EFAULT, EBADF: @@ -121,10 +126,26 @@ enum SystemCalls { }.result } + @inline(never) + internal static func read(descriptor: CInt, pointer: UnsafeMutableRawPointer, size: size_t) throws -> CoreIOResult { + return try syscall(blocking: true) { + sysRead(descriptor, pointer, size) + } + } + @inline(never) internal static func if_nametoindex(_ name: UnsafePointer?) throws -> CUnsignedInt { return try syscall(blocking: false) { sysIfNameToIndex(name) }.result } + + #if !os(Windows) + @inline(never) + internal static func getifaddrs(_ addrs: UnsafeMutablePointer?>) throws { + _ = try syscall(blocking: false) { + sysGetifaddrs(addrs) + } + } + #endif } diff --git a/Sources/NIOCore/Utilities.swift b/Sources/NIOCore/Utilities.swift index c3ca1d42..ccd1c0ba 100644 --- a/Sources/NIOCore/Utilities.swift +++ b/Sources/NIOCore/Utilities.swift @@ -11,6 +11,28 @@ // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// +#if os(Linux) || os(FreeBSD) || os(Android) +import CNIOLinux +import Glibc +#elseif os(Windows) +import let WinSDK.RelationProcessorCore + +import let WinSDK.AF_UNSPEC +import let WinSDK.ERROR_SUCCESS + +import func WinSDK.GetAdaptersAddresses +import func WinSDK.GetLastError +import func WinSDK.GetLogicalProcessorInformation + +import struct WinSDK.IP_ADAPTER_ADDRESSES +import struct WinSDK.IP_ADAPTER_UNICAST_ADDRESS +import struct WinSDK.SYSTEM_LOGICAL_PROCESSOR_INFORMATION +import struct WinSDK.ULONG + +import typealias WinSDK.DWORD +#elseif os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +import Darwin +#endif /// A utility function that runs the body code only in debug builds, without /// emitting compiler warnings. @@ -30,3 +52,151 @@ final class Box { init(_ value: T) { self.value = value } } +public enum System { + /// A utility function that returns an estimate of the number of *logical* cores + /// on the system. + /// + /// This value can be used to help provide an estimate of how many threads to use with + /// the `MultiThreadedEventLoopGroup`. The exact ratio between this number and the number + /// of threads to use is a matter for the programmer, and can be determined based on the + /// specific execution behaviour of the program. + /// + /// - returns: The logical core count on the system. + public static var coreCount: Int { +#if os(Windows) + var dwLength: DWORD = 0 + _ = GetLogicalProcessorInformation(nil, &dwLength) + + let alignment: Int = + MemoryLayout.alignment + let pBuffer: UnsafeMutableRawPointer = + UnsafeMutableRawPointer.allocate(byteCount: Int(dwLength), + alignment: alignment) + defer { + pBuffer.deallocate() + } + + let dwSLPICount: Int = + Int(dwLength) / MemoryLayout.stride + let pSLPI: UnsafeMutablePointer = + pBuffer.bindMemory(to: SYSTEM_LOGICAL_PROCESSOR_INFORMATION.self, + capacity: dwSLPICount) + + let bResult: Bool = GetLogicalProcessorInformation(pSLPI, &dwLength) + precondition(bResult, "GetLogicalProcessorInformation: \(GetLastError())") + + return UnsafeBufferPointer(start: pSLPI, + count: dwSLPICount) + .filter { $0.Relationship == RelationProcessorCore } + .map { $0.ProcessorMask.nonzeroBitCount } + .reduce(0, +) +#elseif os(Linux) || os(Android) + if let quota = Linux.coreCount(quota: Linux.cfsQuotaPath, period: Linux.cfsPeriodPath) { + return quota + } else if let cpusetCount = Linux.coreCount(cpuset: Linux.cpuSetPath) { + return cpusetCount + } else { + return sysconf(CInt(_SC_NPROCESSORS_ONLN)) + } +#else + return sysconf(CInt(_SC_NPROCESSORS_ONLN)) +#endif + } + +#if !os(Windows) + /// A utility function that enumerates the available network interfaces on this machine. + /// + /// This function returns values that are true for a brief snapshot in time. These results can + /// change, and the returned values will not change to reflect them. This function must be called + /// again to get new results. + /// + /// - returns: An array of network interfaces available on this machine. + /// - throws: If an error is encountered while enumerating interfaces. + @available(*, deprecated, renamed: "enumerateDevices") + public static func enumerateInterfaces() throws -> [NIONetworkInterface] { + var interfaces: [NIONetworkInterface] = [] + interfaces.reserveCapacity(12) // Arbitrary choice. + + var interface: UnsafeMutablePointer? = nil + try SystemCalls.getifaddrs(&interface) + let originalInterface = interface + defer { + freeifaddrs(originalInterface) + } + + while let concreteInterface = interface { + if let nioInterface = NIONetworkInterface(concreteInterface.pointee) { + interfaces.append(nioInterface) + } + interface = concreteInterface.pointee.ifa_next + } + + return interfaces + } +#endif + + /// A utility function that enumerates the available network devices on this machine. + /// + /// This function returns values that are true for a brief snapshot in time. These results can + /// change, and the returned values will not change to reflect them. This function must be called + /// again to get new results. + /// + /// - returns: An array of network devices available on this machine. + /// - throws: If an error is encountered while enumerating interfaces. + public static func enumerateDevices() throws -> [NIONetworkDevice] { + var devices: [NIONetworkDevice] = [] + devices.reserveCapacity(12) // Arbitrary choice. + +#if os(Windows) + var ulSize: ULONG = 0 + _ = GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, nil, &ulSize) + + let stride: Int = MemoryLayout.stride + let pBuffer: UnsafeMutableBufferPointer = + UnsafeMutableBufferPointer.allocate(capacity: Int(ulSize) / stride) + defer { + pBuffer.deallocate() + } + + let ulResult: ULONG = + GetAdaptersAddresses(ULONG(AF_UNSPEC), 0, nil, pBuffer.baseAddress, + &ulSize) + guard ulResult == ERROR_SUCCESS else { + throw IOError(windows: ulResult, reason: "GetAdaptersAddresses") + } + + var pAdapter: UnsafeMutablePointer? = + UnsafeMutablePointer(pBuffer.baseAddress) + while pAdapter != nil { + let pUnicastAddresses: UnsafeMutablePointer? = + pAdapter!.pointee.FirstUnicastAddress + var pUnicastAddress: UnsafeMutablePointer? = + pUnicastAddresses + while pUnicastAddress != nil { + if let device = NIONetworkDevice(pAdapter!, pUnicastAddress!) { + devices.append(device) + } + pUnicastAddress = pUnicastAddress!.pointee.Next + } + pAdapter = pAdapter!.pointee.Next + } +#else + var interface: UnsafeMutablePointer? = nil + try SystemCalls.getifaddrs(&interface) + let originalInterface = interface + defer { + freeifaddrs(originalInterface) + } + + while let concreteInterface = interface { + if let nioInterface = NIONetworkDevice(concreteInterface.pointee) { + devices.append(nioInterface) + } + interface = concreteInterface.pointee.ifa_next + } + +#endif + return devices + } +} + diff --git a/Tests/NIOTests/LinuxTest.swift b/Tests/NIOTests/LinuxTest.swift index 3538014e..292b8286 100644 --- a/Tests/NIOTests/LinuxTest.swift +++ b/Tests/NIOTests/LinuxTest.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftNIO open source project // -// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors +// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import XCTest -@testable import NIO +@testable import NIOCore class LinuxTest: XCTestCase { func testCoreCountQuota() {