Network connections send and receive data using transport and security protocols.

Posts under Network tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

SwiftNIO: Issue to enhancr WebSocket server logic to support secured connection
I am able to build prototype of WebSocket server in iOS device using below sample example. it worked https://github.com/apple/swift-nio/blob/main/Sources/NIOWebSocketServer/main.swift But I tried enhancing the logic to support SSL connection using approach given in following link: https://cocoapods.org/pods/SwiftNIOSSL. it didn't work when tried connection through client (postman) to url: wss://127.0.01:60259, it gives below error Error: write EPROTO 5812094680:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:../../../../src/third_party/boringssl/src/ssl/tls_record.cc:242: When I use url ws://127.0.01:60259 (without SSL), it connects. Not sure what is missing in code for SSL connection Note: generated certificate and private key is valid as I have tested same with WebSocket server created using ws library part of node based application Please help here. below is the code (Removed irrelevant/personal code) import Foundation import NIOCore import NIOPosix import NIOHTTP1 import NIOWebSocket import NIOSSL private final class HTTPHandler: ChannelInboundHandler, RemovableChannelHandler { private var responseBody: ByteBuffer! func handlerAdded(context: ChannelHandlerContext) { //some code } func handlerRemoved(context: ChannelHandlerContext) { self.responseBody = nil } func channelActive(context: ChannelHandlerContext) { //save of client channel } func channelInactive(context: ChannelHandlerContext) { //removing of client channel } func channelRead(context: ChannelHandlerContext, data: NIOAny) { Handle read } private func respond405(context: ChannelHandlerContext) { //handle respond } } private final class WebSocketServerHandler: ChannelInboundHandler { func channelInactive(context: ChannelHandlerContext) { // Remove the disconnected client from the list of connected clients } public func handlerAdded(context: ChannelHandlerContext) { self.sendMessage(context: context) } public func channelRead(context: ChannelHandlerContext, data: NIOAny) { //read messages } public func channelReadComplete(context: ChannelHandlerContext) { context.flush() } private func receivedClose(context: ChannelHandlerContext, frame: WebSocketFrame) { // Handle a received close frame. In websockets, we're just going to send the close } } //This start function eventually called when a button is clicked and its part of a class func start() { do{ let wshandler = WebSocketServerHandler() NioWSServerSSL.wshandler = wshandler let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) let upgrader = NIOWebSocketServerUpgrader(shouldUpgrade: { (channel: Channel, head: HTTPRequestHead) in channel.eventLoop.makeSucceededFuture(HTTPHeaders()) }, upgradePipelineHandler: { (channel: Channel, _: HTTPRequestHead) in channel.pipeline.addHandler(wshandler) }) //Note: generated key.pem and cert.pem using openssl and this is just for development purpose let path = ">>>path where the key.pem and cert.pem is stores" let configuration = TLSConfiguration.makeServerConfiguration( certificateChain: try NIOSSLCertificate.fromPEMFile("\(path)/cert.pem").map { .certificate($0) }, privateKey: .file("\(path)/key.pem") ) let sslContext = try NIOSSLContext(configuration: configuration) let bootstrap = ServerBootstrap(group: group) // Specify backlog and enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.backlog, value: 256) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) // Set the handlers that are applied to the accepted Channels .childChannelInitializer { channel in let httpHandler = HTTPHandler() let sslHandler = NIOSSLServerHandler(context: sslContext) let config: NIOHTTPServerUpgradeConfiguration = ( upgraders: [ upgrader ], completionHandler: { _ in channel.pipeline.removeHandler(httpHandler, promise: nil) } ) return channel.pipeline.configureHTTPServerPipeline(withServerUpgrade: config).flatMap { channel.pipeline.addHandler(httpHandler).flatMap { channel.pipeline.addHandler(sslHandler) } } } // Enable SO_REUSEADDR for the accepted Channels .childChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) let channel = try bootstrap.bind(host: "127.0.0.1", port: 60259).wait() //let channel = try bootstrap.bind(host: "127.0.0.1", port: 12346).wait() guard let localAddress = channel.localAddress else { fatalError("Address was unable to bind. Please check that the socket was not closed or that the address family was understood.") } print("Server started and listening on \(localAddress)") } catch { print("Error with Web socket connection \(error)") } }
2
0
463
Jul ’23
Evaluating Trust from self signed keystore fails
Hi all, I am trying to get a mutual authentication (client authentication) connection working (sockets, not http and all local network currently) which is working for other platforms (clients) but for iOS (client) I do not seem to be able to get it working. In comparison with other platforms, the connection is closed at the moment that the client handshake part of the SSL handshake process (according to the SSL debug output on the server) should be happening. The code on iOS shows an error when trying to evaluate the trust of the keystore. On other platforms it shows the client handshake and the connection works. So something is probably wrong with the client keystore or setting it up in the app. I am using self signed certificates, in the p12 format. They have a long life currently, 2027 end year. I also create a certificate from a .cer file which is the public key of the server. The connection is of type TLSv1.2. Additional information is that the app support iOS 9.3 and up. I use Swift 5. So the first step to get it all working is to get the trust evaluating go successful. The error that I currently get is "certificate is not standards compliant". The keystore is loaded, both the trust and the identity is retrieved, the certificate array holds the appropriate amount of certificates. Here is the code that sets up the connection: sslContext = SSLCreateContext(nil, .clientSide, .streamType) if(sslContext != nil) { let tlsProtocolVersion: SSLProtocol = .tlsProtocol12 SSLSetProtocolVersionMin(sslContext!, tlsProtocolVersion) SSLSetProtocolVersionMax(sslContext!, tlsProtocolVersion) } else { print("sslContext is null") return } var certs : [SecCertificate] = [] do { certs = try loadCertificates() } catch { print("Crap, cannot load the certificates") print(error) return } let cfArray = certs as CFArray var trustObj: SecTrust? do { trustObj = try loadTrustFromKeyStore() } catch { print("Crap, cannot load the Trust object") print(error) return } // Set the trust evaluation options let trustPolicy = SecPolicyCreateSSL(true, nil) SecTrustSetPolicies(trustObj!, trustPolicy) SecTrustSetAnchorCertificates(trustObj!, cfArray) var trustResult: SecTrustResultType = .invalid var trustError: UnsafeMutablePointer<CFError?>? var isTrusted = true if #available(iOS 12.0, *) { var error:CFError? isTrusted = SecTrustEvaluateWithError(trustObj!, &error) if error != nil { print("Error: " + error!.localizedDescription) } } And reading the keystore to get the trust and the identify object (both separate functions) func loadTrustFromKeyStore() throws -> SecTrust? { var trust : SecTrust? let store = "client-ios-keystore" print("Reading store for Trust object: " + store) guard let keystorePath = Bundle.main.path(forResource: store, ofType: "p12") else { print("Failed to find " + store) throw "Cannot find or open keystore" } let keystoreURL = URL(fileURLWithPath: keystorePath) let keystoreData = try Data(contentsOf: keystoreURL) let keystoreOptions: [String: Any] = [ kSecImportExportPassphrase as String: password ] var keystoreItems: CFArray? let keystoreStatus = SecPKCS12Import(keystoreData as CFData, keystoreOptions as CFDictionary, &keystoreItems) guard keystoreStatus == errSecSuccess else { print("Failed SecPCKS12Import") throw NSError(domain: NSOSStatusErrorDomain, code: Int(keystoreStatus), userInfo: nil) } if(keystoreItems != nil) { let normalArray = keystoreItems as! Array<CFDictionary> normalArray.forEach { item in print("Checking item in keystore") if let dict = item as? [String: AnyObject] { print("Keystore content: " + dict.debugDescription) for (key, value) in dict { if(key == "identity") { // Skip in this case } else if(key == "trust") { let trustRef: SecTrust = value as! SecTrust trust = trustRef } else if(key == "chain") { // Skip in this case } else { print("Found unexpected content in p12 file") } } } } } return trust } And the loadCertificates function is the same as the loadTrustFromKeyStore function except that it checks the identify instead of the trust and exports an array of certificates like: if(key == "identity") { let keystoreItem = value as! SecIdentity let certArray: CFArray = [keystoreItem] as CFArray SSLSetCertificate(sslContext!, certArray) var secCert: SecCertificate? let status = SecIdentityCopyCertificate(keystoreItem, &secCert) // Checking if the certificate retrieval was successful if status == errSecSuccess { print("success") let certCopy = SecCertificateCopyData(secCert!) let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, certCopy) certificates.append(certificate!) } else { print("failed to import certificate") } } I hope I have mentioned all important information and left out the non-essential information but if you need more information, please let me know. I am probably missing something obvious or important but currently I am just stuck and in need of some fresh eyes, in any case, thank you for taking the time to check out this issue.
9
0
976
Jul ’23
Should we add Precise Location to our SDK's privacy manifest if we use the current SSID?
Our (company internal) SDK for connecting iPhones to accessories uses the SSID of the local network to determine whether local communication is possible, or remote communication (via our company's back-end) is required. Apps that use this functionality of our SDK need to have the "Access Wi-Fi Information" Entitlement and at run-time the user has to give permission to use the precise location (otherwise the SSID cannot be read). Does this mean we should add the data type "Precise Location" to the privacy manifest of our SDK? PS: We only use the SSID; not the precise location (coordinates), but an SSID can identify a precise location.
3
0
903
Jul ’23
Check if iPhone has an active internet connection in SwiftUI
Hello. I'm developing an app that completely relies on an internet connection and as such I want to know if there's a way to check if the iPhone has an active internet connection available because if it doesn't, I want to display a different view to the user stating that the app doesn't have an internet connection and so the app cannot be used. This is a feature that exists in first party Apple apps like the App Store and also third party apps such as YouTube. Does anyone know of an effective way to achieve this please?
3
0
1.5k
Jul ’23
Scanning for devices connected in current WiFi not working for iOS versions 16 and above
I am using an SDK for managing the scanning for devices connected in current WiFi. The feature is working fine iOS version below 16. But on devices having os versions 16 and above, the feature is not working properly. Have anyone encountered this issue before? The feature is working properly in iOS 16 version Simulators. The SDK that I am using is: [https://github.com/umeye/umeye-specs)] We have a hardware device which we connect to a WiFi and use the iOS app to scan for the device in that WiFi.
2
0
362
Jul ’23
socket & http about ipv4 or ipv6
Recently, i had some problems about ipv4 and ipv6. I simplely introducing my problems, i use my iphone(iphone 11/15.3.1) to establish with device A by socket(swift/ Network.framework), but the premise is that iphone needs to connect device A's wifi. When I connect device A's wifi , i found there are one ipv4 and two ipv6s. My iphone establishs with device A successful by socket, the remote ip is ipv6, and iphone uses one of ipv6s automatically. Device A sends me device B's ipaddress, and now i need use my iphone to establish with B synchronously, but i find iphone using another ipv6 address. B's firewall blocked because ip is different from client ipaddress when connecting with device A . I don't know why iphone using another ipv6 to establish with device B, this is question A. Now i resolved the question through "connection.currentPath?.localEndpoint" and requiredLocalEndpoint, I can get client ipaddress when connecing with device A through "connection.currentPath?.localEndpoint", and then i set "requiredLocalEndpoint" the same as the ip i just got, but needs to modify port when connecting with device B. Device B sends my iphone mutible data that contain urls, when my iphone request s urls through alamofire or pure nsurlsession, i found that my iphone's client ipaddress was ipv4, so device B's firewall blocked. I don't know how to set https clientaddress and why iphone uses ipv4 but socket was ipv6.
0
0
331
Jul ’23
How to get the current connected Network Interface in MacOS
I want to ping to IPV6 via local-link address, which needs to get the current active Network Interface like Wi-Fi or Ethernet via adapter connection. Like: ping6 fe80::122b:41ff:feb3:6a20%en0 With en0 is the Wi-Fi Interface. I have tried : private func getAllNetInterfaceName() -> [String] { let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] ?? [] return interfaces.compactMap { SCNetworkInterfaceGetBSDName($0) as? String }.filter { !$0.isEmpty } } It returns the array of the current Interfaces, but I still can not get which one is currently connected. Did you have any clue?
9
0
1.6k
Aug ’23
Question about Apple SSO domains and relevant IP addresses
Hi team, one of my customer is building their SaaS application on our cloud instance and testing Apple SSO environments. Public IP address of relevant instances are located in South Korea and they've experienced intermittent connection failure when they try to login Apple SSO. As per we investigated this issue, we've found out that IP address of relevant URL (https://appleid.apple.com/auth/keys) is changed. Below are few IP addresses that make connection issue and we want to know that if these IP address are actually valid IP address of belonging URLs. [IP address that we cannot make SSL connection] 17.188.23.52 17.188.23.24 [IP address that normally makes successful connection] 17.111.105.242 Moreover, one of the notable difference between those IP addresses are we cannot lookup domain on above IP addresses using our laptop whereas below IP addresses normally received domain from our laptop. Please kindly look into this issue and let me know if those IP addresses are validate on belonging URLs. Thank you. Regards,
1
0
215
Jul ’23
TCP back source speed in the VPN becomes slower.
Our application is a VPN app based on PackTunnelProvider. In order to handle traffic based on domain name or accelerate it through a tunnel, we intercept almost all traffic. The problem arises with the traffic that is being sourced back. We found that when the VPN is enabled and uploading files, the TCP data being sourced back through our VPN code is noticeably slower. After capturing packets and analyzing them with Wireshark, we found that our TCP packets are being split, resulting in one larger packet and one smaller packet (as shown in the image below). However, when we checked our code's logs for the data being written to the TCP, we did not find any of these smaller packets. Android also uses the same code, but Android does not have similar situations as shown in the figure below. The code logic is that the data received from the VPN is parsed by lwip and then sent out through TCP. I would like to ask why this situation occurs on iOS and if there is any way to avoid or optimize it.
1
0
401
Jul ’23
Alternatives to using en0 for WiFi IP Address lookup
Currently, we have a Flutter app that uses the network_info_plus plugin to connect to the Wifi network of the current iOS device our app is running on. Unfortunately, we are unable to use Bonjour on the target device that we are attempting to connect to, which acts as an AP, due to legacy device issues. Hence the reason why we use the device's Wifi IP address to interact with this device when it's connected to this device's Access Point. This plugin's iOS implementation is using a non-guaranteed way of fetching the device's Wifi IP address according to a recent DevForum post, presuming that it's en0 as seen here: - (void)enumerateWifiAddresses:(NSInteger)family usingBlock:(void (^)(struct ifaddrs *))block { struct ifaddrs *interfaces = NULL; struct ifaddrs *temp_addr = NULL; int success = 0; // retrieve the current interfaces - returns 0 on success success = getifaddrs(&interfaces); if (success == 0) { // Loop through linked list of interfaces temp_addr = interfaces; while (temp_addr != NULL) { if (temp_addr->ifa_addr->sa_family == family) { // en0 is the wifi connection on iOS if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { block(temp_addr); } } temp_addr = temp_addr->ifa_next; } } // Free memory freeifaddrs(interfaces); } I was just wondering what an alternative Objective-C implementation would look like for fetching the actual IP address using a subnet broadcast, since the aforementioned DevForum post suggested that we get all Ethernet-like interfaces (I'm assuming those devices that are prefixed with en) and create a Socket for them bound to IP_BOUND_IF. Thank you in advance.
12
0
916
Jul ’23
File upload task called from launchAgent process sometimes take too long.
Hi, I'm working on macOS launchAgent based project, and using 3rd party code to upload big files to remote server. from time to time, I see that the upload rate is very slow and when i try it to use command line tool, the paste is much faster. Therefore, I believe that launchAgent based processes, may get low priority in using network bandwidth compared to foreground tools. I wonder if there's anything I can do on the process' info.plist file to get better prioritization on network resources. Perhaps I need to call the file uploader/downloader from dedicated XPC helper tool, but I prefer doing it from the same process. Thanks !
3
0
542
Jul ’23
Calling BSD Sockets from Swift
I find myself using BSD Sockets a lot. Ironically this isn’t because I’m doing networking. If I’m writing networking code on Apple platforms, I use one of Apple’s preferred APIs, like URLSession or Network framework. For more on this see TN3151 Choosing the right networking API No, I’m using BSD Sockets tangentially, not for my core networking code but for other, weirder stuff. For example, I might want to get a list of all the IP addresses on the system, and that requires calling getifaddrs, which returns its results as struct sockaddr values, which is the BSD Sockets address primitive. Calling BSD Sockets correctly from Swift is a challenge [1]. Over the years I’ve come up with numerous wrappers to make this easier. I’ve found that tricky to do. If I were writing production code I would create a comprehensive wrapper that covers all the details. However, I don’t write production code, I write test projects, snippets for posting to DevForums, and so on. Given that, I want my wrapper to be thin, so that the reader can understand the BSD Sockets concepts without getting lost of the wrapper. Oh, and I don’t really care about efficiency (-: Recently I’ve used my understanding of these goals to create a BSD Sockets wrapper that I’m comfortable sharing. It’s convenient, reasonably ‘thin’, and works well for test projects, snippets, and so on. It can also be wildly inefficient but, as mentioned above, I don’t care about that (-: IMPORTANT TN3151 discusses the circumstances under which it makes sense to use BSD Sockets for networking. Most of these involve calling BSD Sockets from a C-based language rather than Swift, and my wrapper is irrelevant in that case. So, with that out of the way, meet QSocket2023! [2] Well… actually… I’m going to put this in four separate posts: QSocket: Addresses QSocket: DNS QSocket: Interfaces QSocket: System Additions QSocket: I/O QSocket: Socket Options Finally, if you’re using BSD Sockets for networking, you might be interested in Monitoring Socket Viability. If you have any questions or comments about this stuff, please start a new thread here on DevForums. Tag it with Network so that I see it. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] Rumour has it that, even in C, calling BSD Sockets without bumping into undefined behaviour is a challenge, but I’ll let the C language lawyers litigate that (-: [2] That’s right, I’ve written so many BSD Sockets wrappers that I’ve started using the year to distinguish them! Revision History 2023-10-27 Added a link to Monitoring Socket Viability. 2023-07-20 First posted.
0
0
788
Oct ’23
QSocket: Addresses
IMPORTANT If you haven’t yet read Calling BSD Sockets from Swift, do that first. The biggest problem with calling BSD Sockets from Swift is how to represent an address. The C API works in terms of struct sockaddr *, which is a ‘base class’ with various ‘subclasses’, like struct sockaddr_in *. C lets you [1] freely cast between these, but that’s harder in Swift. My solution for this — and remember that one of my key goals is to create a wrapper that’s good for test projects — is to represent addresses as strings. I start by defining a namespace for this stuff: /// A namespace for helpers that work with BSD Sockets addresses. /// /// These convert between IP address strings and the `sockaddr` pointers used by /// the BSD Sockets API. For example, here’s how you can call `connect` with an /// IP address and string. /// /// ```swift /// let success = try QSockAddr.withSockAddr(address: "1.2.3.4", port: 12345) { sa, saLen in /// connect(fd, sa, saLen) >= 0 /// } /// ``` /// /// This example calls ``withSockAddr(address:port:_:)``, which is what you use /// when passing an address into BSD Sockets. There’s also /// ``fromSockAddr(sa:saLen:)``, to use when getting an address back from BSD /// Sockets. /// /// > important: Representing addresses as strings is potentially very /// inefficient. For example, if you were to wrap the BSD Sockets `sendto` call /// in this way, you would end up doing a string-to-address conversion every /// time you sent a datagram! However, it _is_ very convenient, making it perfect /// for small test projects, wrapping weird low-level APIs, and so on. /// /// Keep in mind that I rarely use BSD Sockets for _networking_ these days. /// Apple platforms have better networking APIs; see TN3151 [Choosing the right /// networking API][tn3151] for the details. /// /// [tn3151]: <[TN3151: Choosing the right networking API | Apple Developer Documentation](https://developer.apple.com/documentation/technotes/tn3151-choosing-the-right-networking-api)> public enum QSockAddr { } I then extend it with various helpers. The first is for calling a routine that takes a sockaddr input: extension QSockAddr { /// Calls a closure with a socket address and length. /// /// Use this to pass an address in to a BSD Sockets call. For example: /// /// ```swift /// let success = try QSockAddr.withSockAddr(address: "1.2.3.4", port: 12345) { sa, saLen in /// connect(fd, sa, saLen) >= 0 /// } /// ``` /// /// - Parameters: /// - address: The address as a string. This can be either an IPv4 or IPv6 /// address, in any format accepted by `getaddrinfo` when the /// `AI_NUMERICHOST` flag is set. /// - port: The port number. /// - body: A closure to call with the corresponding `sockaddr` pointer /// and length. /// - Returns: The value returned by that closure. public static func withSockAddr<ReturnType>( address: String, port: UInt16, _ body: (_ sa: UnsafePointer<sockaddr>, _ saLen: socklen_t) throws -> ReturnType ) throws -> ReturnType { var addrList: UnsafeMutablePointer<addrinfo>? = nil var hints = addrinfo() hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV let err = getaddrinfo(address, "\(port)", &hints, &addrList) guard err == 0 else { throw QSockAddr.NetDBError(code: err) } guard let addr = addrList else { throw QSockAddr.NetDBError(code: EAI_NODATA) } defer { freeaddrinfo(addrList) } return try body(addr.pointee.ai_addr, addr.pointee.ai_addrlen) } } The second is for calling a routine that returns a sockaddr: extension QSockAddr { /// Creates an address by calling a closure to fill in a socket address and /// length. /// /// Use this to get an address back from a BSD Sockets call. For example: /// /// ```swift /// let peer = try QSockAddr.fromSockAddr() { sa, saLen in /// getpeername(fd, sa, &saLen) >= 0 /// } /// guard peer.result else { … something went wrong … } /// … use peer.address and peer.port … /// ``` /// /// - Parameter body: The closure to call. It passes this a mutable pointer /// to a `sockaddr` and an `inout` length. The closure is expected to /// populate that memory with an IPv4 or IPv6 address, or throw an error. /// - Returns: A tuple containing the closure result, the address string, /// and the port. public static func fromSockAddr<ReturnType>(_ body: (_ sa: UnsafeMutablePointer<sockaddr>, _ saLen: inout socklen_t) throws -> ReturnType) throws -> (result: ReturnType, address: String, port: UInt16) { var ss = sockaddr_storage() var saLen = socklen_t(MemoryLayout<sockaddr_storage>.size) return try withUnsafeMutablePointer(to: &ss) { try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in let result = try body(sa, &saLen) let (address, port) = try fromSockAddr(sa: sa, saLen: saLen) return (result, address, port) } } } } And the third is for when you start with a sockaddr pointer: extension QSockAddr { /// Creates an address from an address pointer and length. /// /// Use this when you have an existing `sockaddr` pointer, for example, when /// working with `getifaddrs`. /// /// - Parameters: /// - sa: The address pointer /// - saLen: The address length. /// - Returns: A tuple containing the address string, and the port. public static func fromSockAddr(sa: UnsafeMutablePointer<sockaddr>, saLen: socklen_t) throws -> (address: String, port: UInt16) { var host = [CChar](repeating: 0, count: Int(NI_MAXHOST)) var serv = [CChar](repeating: 0, count: Int(NI_MAXSERV)) let err = getnameinfo(sa, saLen, &host, socklen_t(host.count), &serv, socklen_t(serv.count), NI_NUMERICHOST | NI_NUMERICSERV) guard err == 0 else { throw QSockAddr.NetDBError(code: err) } guard let port = UInt16(String(cString: serv)) else { throw QSockAddr.NetDBError(code: EAI_SERVICE) } return (String(cString: host), port) } } Finally, these routines rely on this error type: extension QSockAddr { /// Wraps an error coming from the DNS subsytem. /// /// The code values correspond to `EAI_***` in `<netdb.h>`. struct NetDBError: Error { var code: CInt } } Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] Or does it? There’s some debate as to whether the BSD Sockets API is legal C (-:
0
0
501
Jul ’23
QSocket: DNS
IMPORTANT If you haven’t yet read Calling BSD Sockets from Swift, do that first. DNS is pretty central to networking. When you connect to a service across the network, you usually start with a DNS name. In BSD Sockets you first resolve that name and then connect. This is one of the key problems with the API. Apple APIs support connect-by-name semantics, which allows the networking stack to do a lot of work on your behalf. If you’re using BSD Sockets in production code, you have to do all this work yourself. For more on this, see Connect by name, BSD Sockets best practices and DNS best practices in TN3151 Choosing the right networking API. However, my test projects sometimes do need to resolve DNS names and so I’ve created a simple wrapper for this: extension QSockAddr { /// Resolves the specified DNS name and service to a list of IPv4 and IPv6 /// addresses. /// /// Equivalent to the `getaddrinfo` BSD Sockets call. /// /// The list may contain redundant values. For example, when you resolve /// `localhost` you get back 4 addresses, two for IPv4 and two for IPv6. /// /// The list is in the same order as that returned by `getaddrinfo`. /// /// - Parameters: /// - host: The DNS name (or IP address) to resolve. /// - service: A service name, like `"echo"`, or a port number, like `"4"`. public static func resolving(host: String, service: String) throws -> [(address: String, port: UInt16)] { var addrList: UnsafeMutablePointer<addrinfo>? = nil let err = getaddrinfo(host, service, nil, &addrList) guard err == 0 else { throw NetDBError(code: err) } defer { freeaddrinfo(addrList) } guard let first = addrList else { return [] } return try sequence(first: first, next: { $0.pointee.ai_next }) .compactMap { addr in guard [AF_INET, AF_INET6].contains(addr.pointee.ai_family), let sa = addr.pointee.ai_addr, case let saLen = addr.pointee.ai_addrlen, saLen != 0 else { return nil } return try QSockAddr.fromSockAddr(sa: sa, saLen: saLen) } } // There’s no wrapper for `getnameinfo` because /much/ more obscure than // `getaddrinfo`. If you want to see an example of that, read the code for // `QSockAddr.fromSockAddr(sa:saLen:)`. } In my test projects I usually just pull the first item off the list and connect to that. IMPORTANT If you use BSD Sockets in production code, connecting to the first address is not sufficient. For compatibility with a wide range of network environments, implement the Happy Eyeballs algorithm. See TN3151 Choosing the right networking API for the details. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
412
Jul ’23
QSocket: Interfaces
IMPORTANT If you haven’t yet read Calling BSD Sockets from Swift, do that first. Sometimes you need to get information about the local network interfaces on the device. All Apple platforms support the getifaddrs routine, which returns the list of network interfaces and their addresses. However, it works in terms of struct sockaddr values, which are hard to use from Swift. Here’s an example of how you might use the QSockAddr primitives to return strings instead: extension QSockAddr { /// Returns a list of interfaces that have an associated IPv4 or IPv6 /// address. /// /// Equivalent to the `getifaddrs` BSD Sockets call. /// /// The list is in the same order as that returned by `getifaddrs`. /// ``` public static func interfaceNamesAndAddresses() -> [(name: String, address: String)] { var addrList: UnsafeMutablePointer<ifaddrs>? = nil let err = getifaddrs(&addrList) // In theory we could check `errno` here but, honestly, what are gonna // do with that info? guard err >= 0, let first = addrList else { return [] } defer { freeifaddrs(addrList) } return sequence(first: first, next: { $0.pointee.ifa_next }) .compactMap { addr in guard let name = addr.pointee.ifa_name, let sa = addr.pointee.ifa_addr, [AF_INET, AF_INET6].contains(CInt(sa.pointee.sa_family)), let (address, _) = try? QSockAddr.fromSockAddr(sa: sa, saLen: socklen_t(sa.pointee.sa_len)) else { return nil } return (String(cString: name), address) } } } And once you have this primitive, you can add wrappers to get just the names, just the addresses, or the addresses grouped by the interface: extension QSockAddr { public static func interfaceNames() -> [String] { interfaceNamesAndAddresses() .map { $0.name } } public static func interfaceAddresses() -> [String] { interfaceNamesAndAddresses() .map { $0.address } } public static func addressesByInterface() -> [String: [String]] { interfaceNamesAndAddresses() .reduce(into: [:]) { soFar, i in soFar[i.name, default: []].append(i.address) } } } Note If you’re targeting macOS you have a lot more options in this space. Most notably, System Configuration framework has a dynamic store API that returns detailed information about the Mac’s network state. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
347
Jul ’23
QSocket: System Additions
IMPORTANT If you haven’t yet read Calling BSD Sockets from Swift, do that first. With addressing sorted out, there’s the question of how you call the BSD Sockets API itself. The Swift System framework has wrappers for some BSD calls, like open and close, but not for BSD Sockets. However, using QSockAddr it’s relatively easy to create these wrappers yourself. The full set of wrappers is rather large, so I’m just going to post the most critical stuff. Let’s start with opening a socket: extension FileDescriptor { /// Creates a socket. /// /// Equivalent to the `socket` BSD Sockets call. public static func socket(_ domain: CInt, _ type: CInt, _ proto: CInt, retryOnInterrupt: Bool = true) throws -> FileDescriptor { let socket = try errnoQ(retryOnInterrupt: retryOnInterrupt) { Foundation.socket(domain, type, proto) } return FileDescriptor(rawValue: socket) } } This extends the Swift System FileDescriptor type, which is a common feature of all these wrappers. To close the socket, call the file descriptor’s close() method. This wrapper, and all the wrappers below, rely on the following helper: /// Calls a closure that might fail with `EINTR`. /// /// This calls the supplied closure and, if it returns a negative value, /// extracts the error from `errno`. If `retryOnInterrupt` on interrupt is set /// and the error is `EINTR`, it repeats the call. Otherwise it throws that /// error. /// /// This is marked with `@discardableResult` because in many cases, like /// `setsockopt`, the result isn’t relevant. /// /// - Parameters: /// - retryOnInterrupt: If true, check for `EINTR` and call the closure again. /// - body: The closure to call. /// - Returns: The closure result. This will not be negative. @discardableResult public func errnoQ<Result: SignedInteger>(retryOnInterrupt: Bool, _ body: () -> Result) throws -> Result { repeat { let result = body() let e = Foundation.errno if result >= 0 { return result } if retryOnInterrupt && e == Foundation.EINTR { continue } throw Errno(rawValue: e) } while true } The next step typically involves connecting or binding the socket. Here’s how you’d call connect using QSockAddr.withSockAddr(…) to convert the address: extension FileDescriptor { /// Connects a socket to an address. /// /// Equivalent to the `connect` BSD Sockets call. /// /// The `ignoreInProgressError` parameter defaults to false. If you set it, /// the call treats an `EINPROGRESS` error as success. Do this for a /// non-blocking connect, where you monitor the connection status using /// `select` or one of its friends. public func connect(_ address: String, _ port: UInt16, ignoreInProgressError: Bool = false, retryOnInterrupt: Bool = true) throws { _ = try QSockAddr.withSockAddr(address: address, port: port) { sa, saLen in try errnoQ(retryOnInterrupt: retryOnInterrupt) { var err = Foundation.connect(self.rawValue, sa, saLen) if err < 0 && errno == EINPROGRESS { err = 0 } return err } } } } The wrapper for bind is very similar (sans the special case for EINPROGRESS). The listen and accept operations are super easy: extension FileDescriptor { /// Configures a socket for listening. /// /// Equivalent to the `listen` BSD Sockets call. public func listen(_ backlog: CInt, retryOnInterrupt: Bool = true) throws { try errnoQ(retryOnInterrupt: retryOnInterrupt) { Foundation.listen(self.rawValue, backlog) } } /// Accepts an incoming connection /// /// Equivalent to the `accept` BSD Sockets call when you pass `NULL` to the /// `address` and `address_len` parameters. If you need the connection’s /// remote address, call ``getPeerName(retryOnInterrupt:)``. public func accept(retryOnInterrupt: Bool = true) throws -> FileDescriptor { let newSocket = try errnoQ(retryOnInterrupt: retryOnInterrupt) { Foundation.accept(self.rawValue, nil, nil) } return FileDescriptor(rawValue: newSocket) } } As is the get-local-address operation: extension FileDescriptor { /// Gets the socket’s local address. /// /// Equivalent to the `getsockname` BSD Sockets call. public func getSockName(retryOnInterrupt: Bool = true) throws -> (address: String, port: UInt16) { let result = try QSockAddr.fromSockAddr() { sa, saLen in try errnoQ(retryOnInterrupt: retryOnInterrupt) { Foundation.getsockname(self.rawValue, sa, &saLen) } } return (result.address, result.port) } } To get the remote address, wrap getpeername in the same way. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com"
0
0
278
Jul ’23
QSocket: I/O
IMPORTANT If you haven’t yet read Calling BSD Sockets from Swift, do that first. To read and write a connected socket, use the FileDescriptor read and write methods. To read and write an unconnected socket, you need helpers to convert the address: extension FileDescriptor { /// Sends a datagram to an address. /// /// Equivalent to the `sendto` BSD Sockets call. /// /// If you’re working with a TCP socket, use /// ``write(data:retryOnInterrupt:)`` method. /// /// - important: This builds the destination address from the supplied /// string every time you send a datagram. That’s horribly inefficient. /// That’s not a problem given the design constraints of this package but, /// oh gosh, don’t use this in a real project. /// /// If the socket is non-blocking, be prepare for this to throw `EAGAIN`. /// /// The result is discardable because this method is most commonly used with /// a UDP socket and that’s all or nothing. @discardableResult func send(data: Data, flags: CInt = 0, to destination: (address: String, port: UInt16), retryOnInterrupt: Bool = true) throws -> Int { try data.withUnsafeBytes { buf in try QSockAddr.withSockAddr(address: destination.address, port: destination.port) { sa, saLen in try errnoQ(retryOnInterrupt: retryOnInterrupt) { // If `count` is 0 then `baseAddress` might be zero. We’re // assuming that the `sendto` call will be OK with that. Foundation.sendto(self.rawValue, buf.baseAddress, buf.count, flags, sa, saLen) } } } } /// Receive a datagram and its source address. /// /// Equivalent to the `recvfrom` BSD Sockets call. /// /// If you’re working with a TCP socket, use the /// ``read(maxCount:retryOnInterrupt:)`` method. /// /// - important: This builds the destination address string from the /// returned address every time you receive a datagram. That’s horribly /// inefficient. That’s not a problem given the design constraints of this /// package but, oh gosh, don’t use this in a real project. /// /// If the socket is non-blocking, be prepare for this to throw `EAGAIN`. /// /// The result is non-optional because UDP allows us to send and receive /// zero length datagrams. func receiveFrom(maxCount: Int = 65536, flags: CInt = 0, retryOnInterrupt: Bool = true) throws -> (data: Data, from: (address: String, port: UInt16)) { var result = Data(count: maxCount) let (bytesRead, address, port) = try result.withUnsafeMutableBytes { buf in try QSockAddr.fromSockAddr { sa, saLen in try errnoQ(retryOnInterrupt: retryOnInterrupt) { recvfrom(self.rawValue, buf.baseAddress, buf.count, flags, sa, &saLen) } } } result = result.prefix(bytesRead) return (result, (address, port)) } } Wrappers for the other BSD Sockets I/O primitives are left as an exercise for the reader [1]. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] Good luck with sendmsg and recvmsg! (-:
0
0
305
Jul ’23