Cellular UDP Socket Binding

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

After Does IOS Support Wi-Fi and 3G / 4G Connected Simultaneously? link to iOS Multipath BSD Sockets Test , I tried to implement solution in Swift 3, this is enumeration of network interfaces into device identifying cellular interface (as suggested in Swift - get device IP address ), create UDP socket and bind it to sockaddr obtained from interface.

The implementation of socket programming in Swift was done using the following examples from Socket Programming in Swift: Part 1 - getaddrinfo and following posts.

Unfortunately I got Operation not allowed when trying to send data on a socket, so instead I tried to create a socket and bind it to data from getaddrinfo on the designated port (5555).

It didn't help either. The interesting thing is that while trying to figure out what's wrong, I created a test application for both methods, and when testing for 1000 sequential create-> bind-> send-> close, about 3-5 attempts actually send data without error in both methods.

Needless to say, this has been tested on a real iPhone.

It is unlikely that I will lose any losses, I would appreciate any advice on this matter.

Code implemented in static SocketManager class ( change: fixed size of sockaddr allocation)

// 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) 
}

      

In 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

}

      

+3


source to share


1 answer


Edit: Fixed network byte byte error when creating target sockadd_in

.

Ok, found the problem: First, as Martin pointed out, I missed the allocation UnsafeMutablePointer

since I accepted the parameters capacity/count

as bytes.

This was also done when I highlighted sockaddr_in

for server details in a function sendData

( 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, I started getting better results (about 16 out of 1000 messages sent), but obviously it wasn't enough. I found Send post using UDP in Swift 3 and decided to change the use 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 am still puzzled as to why using Unsafe Memory with this structure does not work.



Another thing: I moved this code back to my actual application, trying to bind a socket to my own addrinfo

through getaddrinfo

constantly failing to assign the requested address using one of which I get from enumerable interfaces, but I get a lot of errors Without buffer space (something for another study :).

In the test code, both binding methods (listed and getaddrinfo

) work fine.

Fixed function sendData

:

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        
}

      

+1


source







All Articles