swift-nio/Sources/NIO/NetworkDevices.swift

368 lines
13 KiB
Swift

//===----------------------------------------------------------------------===//
//
// 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<sockaddr>? {
#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<sockaddr>? {
#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<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
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<IP_ADAPTER_ADDRESSES>,
_ pAddress: UnsafeMutablePointer<IP_ADAPTER_UNICAST_ADDRESS>) {
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<CChar>(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<CChar>(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)
}
}