Cha.OS
Cha.OS

Reputation: 98

Binding UDP Socket to Cellular IP

I am trying to create an iOS client that sends data to a server on a UDP socket over the device's cellular communication.

Following Does IOS support simultaneous wifi and 3g/4g connections? link to iOS Multipath BSD Sockets Test, I've tried implementing the solution in Swift 3, that is enumerate network interfaces in the device, identifying the Cellular interface (as suggested in Swift - Get device's IP Address), create a UDP socket and bind it to the sockaddr retrieved from the interface.

Implementation of socket programming in Swift was done by following examples from Socket Programming in Swift: Part 1 - getaddrinfo and following posts.

Unfortunately I received Operation not permitted when trying to send data on the socket, so instead I've tried creating the socket and binding it to the data from getaddrinfo called on a designated port (5555).

That too didn't do the trick. The interesting thing is that while trying to understand what's wrong, I created a test application for both methods, and when tested for 1000 consecutive create->bind->send->close, about 3-5 of the attempts actually did send the data without the error on either method.

Needless to say this was tested on an actual iPhone.

Quite at a loss, I'd appreciate any advice regarding this.

Code implemented in a static "SocketManager" class (edit: fixed sockaddr allocation size)

// Return IP address String, port String & sockaddr of WWAN interface (pdp_ip0), or `nil`
public static func getInterface() -> (String?, String?, UnsafeMutablePointer<sockaddr>?) {
    var host : String?
    var service : String?

    // Get list of all interfaces on the local machine:
    var ifaddr : UnsafeMutablePointer<ifaddrs>?
    var clt : UnsafeMutablePointer<sockaddr>?

    guard getifaddrs(&ifaddr) == 0 else {
        return (nil, nil, clt)
    }
    guard let firstAddr = ifaddr else {
        return (nil, nil, clt)
    }

    // For each interface ...
    for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
        let interface = ifptr.pointee
        let flags = Int32(ifptr.pointee.ifa_flags)

        /// Check for running IPv4 interfaces. Skip the loopback interface.
        if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
            let addrFamily = interface.ifa_addr.pointee.sa_family
            if addrFamily == UInt8(AF_INET) { //Interested in IPv4 for in particular case

                // Check interface name:
                let name = String(cString: interface.ifa_name)
                print("interface name: \(name)")
                if  name.hasPrefix("pdp_ip") { //cellular interface

                    // Convert interface address to a human readable string:
                    let ifa_addr_Value = interface.ifa_addr.pointee
                    clt = UnsafeMutablePointer<sockaddr>.allocate(capacity: 1)
                    clt?.initialize(to: ifa_addr_Value, count: 1)

                    var hostnameBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV))
                    getnameinfo(interface.ifa_addr, socklen_t(ifa_addr_Value.sa_len),
                            &hostnameBuffer, socklen_t(hostnameBuffer.count),
                            &serviceBuffer,
                            socklen_t(serviceBuffer.count),
                            NI_NUMERICHOST | NI_NUMERICSERV)
                    host = String(cString: hostnameBuffer)
                    if let host = host {
                      print("found host \(String(describing: host))")
                    }

                    service = String(cString: serviceBuffer)
                    if let service = service {
                        print("found service \(String(describing: service))")
                    }
                    break;
                }
            }
        }
    }
    freeifaddrs(ifaddr)

    return (host, service, clt)
}

public static func bindSocket(ip: String, port : String, clt : UnsafeMutablePointer<sockaddr>, useCltAddr : Bool = false) -> Int32 {

    print("binding socket for IP: \(ip):\(port) withCltAddr=\(useCltAddr)")
    var hints = addrinfo(ai_flags: 0,
                            ai_family: AF_INET,
                            ai_socktype: SOCK_DGRAM,
                            ai_protocol: IPPROTO_UDP,
                            ai_addrlen: 0,
                            ai_canonname: nil,
                            ai_addr: nil,
                            ai_next: nil)

    var connectionInfo : UnsafeMutablePointer<addrinfo>? = nil

    let status = getaddrinfo(
            ip,
            port,
            &hints,
            &connectionInfo)
    if status != 0 {
        var strError: String
        if status == EAI_SYSTEM {
            strError = String(validatingUTF8: strerror(errno)) ?? "Unknown error code"
        } else {
            strError = String(validatingUTF8: gai_strerror(status)) ?? "Unknown error code"
        }
        print(strError)
        return -1
    }

    let socketDescriptor = useCltAddr ? socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) : socket(connectionInfo!.pointee.ai_family, connectionInfo!.pointee.ai_socktype, connectionInfo!.pointee.ai_protocol)

    if socketDescriptor == -1 {
        let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
        let message = "Socket creation error \(errno) (\(strError))"
        freeaddrinfo(connectionInfo)
        print(message)
        return -1
    }
    let res = useCltAddr ? bind(socketDescriptor, clt, socklen_t(clt.pointee.sa_len)) : bind(socketDescriptor, connectionInfo?.pointee.ai_addr, socklen_t((connectionInfo?.pointee.ai_addrlen)!))

    if res != 0 {
        let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
        let message = "Socket bind error \(errno) (\(strError))"
        freeaddrinfo(connectionInfo)
        close(socketDescriptor)
        print(message)
        return -1
    }
    freeaddrinfo(connectionInfo)
    print("returned socket descriptor \(socketDescriptor)")
    return socketDescriptor   
}

//returns 0 for failure, 1 for success
public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{

    print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)")
    var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size)

    target.pointee.sin_family = sa_family_t(AF_INET)
    target.pointee.sin_addr.s_addr = inet_addr(toIP)
    target.pointee.sin_port = in_port_t(onPort)!

    var res = 0
    data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
        let rawPtr = UnsafeRawPointer(u8Ptr)
        withUnsafeMutablePointer(to: &target) {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target)))
                if bytesSent > 0 {
                    print("😄😄😄 Sent \(bytesSent) bytes 😄😄😄")
                    res = 1
                }
                if bytesSent == -1 {
                    let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
                    let message = "Socket sendto error \(errno) (\(strError))"
                    print(message)
                }
            }
        }
    }
    return res
}

public static func closeSocket(socketDescriptor : Int32, clt : UnsafeMutablePointer<sockaddr>) {
    print("closing socket descriptor \(socketDescriptor)")
    close(socketDescriptor)
    clt.deinitialize()
    clt.deallocate(capacity: 1) 
}

On ViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    var i = 0
    for _ in 0..<1000 {
        i += connectSendClose(withDescriptor: false) // change withDescriptor to switch socket create/bind method
    }
    print("Sent \(i) packets")   
}

private func connectSendClose(withDescriptor : Bool) -> Int {
    let interface = SocketManager.getInterface()
    guard let ip = interface.0 else {
        print("no relevant interface")
        return 0
    }
    guard let clt = interface.2 else {
        print("no addr")
        return 0
    }
    let socketDescriptor = SocketManager.bindSocket(ip: ip, port: "5555", clt: clt, useCltAddr: withDescriptor)
    if socketDescriptor == -1 {
        print("faild to configure socket")
        return 0
    }
    let serverIP = "59.122.442.9" //dummy IP, test was preformed on actual server 
    let serverPort = "10025" //dummy port, test was preformed on actual server
    let input = 42.13
    var value = input
    let data = withUnsafePointer(to: &value) {
        Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: input))
    }

    let res = SocketManager.sendData(toIP: serverIP, onPort: serverPort, withSocketDescriptor: socketDescriptor, data: data)

    SocketManager.closeSocket(socketDescriptor: socketDescriptor, clt: clt)
    return res

}

Upvotes: 3

Views: 2730

Answers (1)

Cha.OS
Cha.OS

Reputation: 98

Edit: Fixed Network byte order bug in creation of target sockadd_in.

Alright, found the problem: First, as Martin noted, I miss used UnsafeMutablePointer allocation as I took capacity/count parameters as bytes.

This was done also when I allocated sockaddr_in for server details in sendData function (var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size as opposed to var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1).

After fixing this to, I started to get better results (about 16 out of 1000 sends passed), but obviously it was not enough. I Found Send a message using UDP in Swift 3, and decided to change the use of sockaddr_in to var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0)), everything works.

I'm still puzzled as to why using Unsafe Memory with this struct didn't work though.

Another thing: I moved this code back to my actual App, trying to bind the socket to my own addrinfo via getaddrinfo constantly fails with Can't assign requested address, using the one I get from enumerated interfaces works, but I receive lots of No buffer space available errors (something for another research :).

In the test code, both binding methods (enumerated & getaddrinfo) work fine.

Fixed sendData function:

public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{

    print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)")        
    var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(bigEndian: onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0))

    var res = 0
    data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
        let rawPtr = UnsafeRawPointer(u8Ptr)
        withUnsafeMutablePointer(to: &target) {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target)))
                if bytesSent > 0 {
                    print("😄😄😄 Sent \(bytesSent) bytes 😄😄😄")
                    res = 1
                }
                if bytesSent == -1 {
                    let strError = String(utf8String: strerror(errno)) ?? "Unknown error code"
                    let message = "Socket sendto error \(errno) (\(strError))"
                    print(message)
                }
            }
        }
    }
    return res        
}

Upvotes: 1

Related Questions