NWConnection how to force ipv4 ?

I have the following code to force the connectivity over cellular and it works perfectly

Code Block
let tcpOptions = NWProtocolTCP.Options()
    tcpOptions.connectionTimeout = 5
    var tlsOptions: NWProtocolTLS.Options?
    var port = 80
    if (url.scheme!.starts(with:"https")) {
      port = 443
      tlsOptions = .init()
    }
    /*force network connection to cellular only*/
    let params = NWParameters(tls: tlsOptions , tcp: tcpOptions)
    params.requiredInterfaceType = .cellular
    params.prohibitExpensivePaths = false
    params.prohibitedInterfaceTypes = [.wifi]
    /* create network connection */
    connection = NWConnection(host: NWEndpoint.Host(url.host!), port: NWEndpoint.Port(rawValue: UInt16(port))!, using: params)


Now I'm looking to force the connection to use ipv4 (even if the phone has an ipv6 available) because the targeted server has a broken ipv6 support and I don't have control over it (it belongs to a mobile operator)

Any idea?
E



Replies

Now I'm looking to force the connection to use ipv4

That’d be the version property.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"

How exactly would I set the desired IP protocol version on a connection? I followed the link to find NWProtocolIP.Options.Version which is an enum having v4 as one of its cases. But for the life of me I can't figure out how to pass this as an option. I tried lots of things with NWParameters and so on. The documentation does not really help. Could you please provide a working code fragment and explain it? I think it would also be "educational" to understand how the Apple frameworks expect the various parameters and options specified - I am talking to you, eskimo1 😉

Thanks in advance, Giulio

This is one of those places where the general-purpose nature of Network framework makes it harder to see what’s going on. Within an NWParameters value, there’s a defaultProtocolStack property that has a bunch of different properties like internetProtocol. That’s declared to be of type NWProtocolOptions but at runtime it’s actually a subclass value, with the type NWProtocolIP.Options. You have to force cast it to get at the version property.

For example:

import Foundation
import Network

func main() {
    let params = NWParameters.tcp
    let ip = params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
    ip.version = .v4
    let conn = NWConnection(to: .hostPort(host: "localhost", port: 12345), using: params)
    withExtendedLifetime(conn) {
        conn.stateUpdateHandler = { newState in
            print(newState)
        }
        let message = Data("Hello Cruel World!\r\n\(Date())\r\n".utf8)
        conn.send(content: message, completion: .contentProcessed({ error in
            print(error)
        }))
        conn.start(queue: .main)
        dispatchMain()
    }
}

main()

Put this in a new tool project. Then open a Terminal window and run this command:

% nc -l 12345

Then run your tool from Xcode, and you’ll see something like this in Terminal:

% nc -l 12345
Hello Cruel World!
2021-11-22 11:57:29 +0000

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi Eskimo, Thanks for your example. This works great on Simulator but on Real Device i always get only IPV6 address for the same code base and the same server. Do you know what can be the reason ?

Btw: Instead of direct ip/port for connection i'm using a NWEndpoint that is created by a NWBrowser .bonjour instance and listening for browseResultsChangedHandler from where i get NWEndpoint .

Any idea why ? Thanks!

Instead of direct ip/port for connection i'm using a NWEndpoint that is created by a NWBrowser

So, you get your NWEndpoint from Bonjour and you create your NWConnection from that? With code like this:

let ep: NWEndpoint = … from NWBrowser …
let params = NWParameters.tcp
let ip = params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
ip.version = .v4
let conn = NWConnection(to: ep, using: params)

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hoping to revive this thread. I'm getting NWEndpoint from NWBrowser and trying to force IPv4.

let parameters = NWParameters.udp parameters.includePeerToPeer = true (parameters.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options).version = .v4

But checking to see if parameters.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options).version == .v4 returns false. It doesn't seem to be setting the version as expected. I checked and it remains as any.

It seems like NWProtocolUDP.Options() and NWProtocolTLS.Options() initializers exist but NWProtocolIP.Options does not have an init exposed so I can't try setting

params.defaultProtocolStack.internetProtocol = NWProtocolIP.Options() // and set version on options before assigning here

But checking to see if parameters.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options).version == .v4 returns false

OK, to be clear, this code:

let parameters = NWParameters.udp
let ip = parameters.defaultProtocolStack.internetProtocol as! NWProtocolIP.Options
ip.version = .v4
print(ip.version)
let ipBis = parameters.defaultProtocolStack.internetProtocol as! NWProtocolIP.Options
print(ipBis.version)

prints:

v4
any

This is an artefact of the way that the Network framework Swift API interacts with the underlying C API. When you get ipBis, the Swift API constructs a new instance of NWProtocolIP.Options and it has a local copy of the version property that it initialises to .any. It does this because it can’t read the value back from the underlying C object. That object has a setter for this value, nw_ip_options_set_version, but no getter.

IMO this is a bug in the Swift API and I encourage you to file it as such. Please post your bug number, just for the record.

However, this bug is just with the getter. The .v4 value you set does make it down to the underlying C object and will apply to your connection.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo i tried the above method, but it seems like the end server always get the ipv6 no matter what, also not sure if this Swipt API bug for the getter has been fixed,i take it not as i see the same output as yours, any pointers on forcing ipv4?, thanks

This continues to work for me. Consider the code pasted in at the end of this post. It’s pretty much the same as before except:

  • I connect to the DNS name of a remote machine, not localhost.

  • I print the path details at each state change.

On the remote machine I run nc like this:

% nc -4 -6 -l 12345

which means it listens for both IPv4 and IPv6 connections. On my Mac, I ran my test project as written:

preparing
nil Optional(virtual-vente.local.:12345)
ready
Optional(192.168.1.71:54839) Optional(192.168.1.122%en0:12345)
nil

It connected over IPv4. I then change .v4 to .v6 and run it again:

preparing
nil Optional(virtual-vente.local.:12345)
ready
Optional(fe80::c8e:50ea:7db0:a02b%en0.54841) Optional(fe80::441:2950:b4d5:35ff%en0.12345)
nil

Now it connected over IPv6.

This is all on macOS 13.5.2, but my experience is that this works fine on all systems that support Network framework.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


import Foundation
import Network

func main() {
    let params = NWParameters.tcp
    let ip = params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
    ip.version = .v4
    let conn = NWConnection(to: .hostPort(host: "virtual-vente.local.", port: 12345), using: params)
    withExtendedLifetime(conn) {
        conn.stateUpdateHandler = { newState in
            print(newState)
            print(conn.currentPath?.localEndpoint, conn.currentPath?.remoteEndpoint)
        }
        let message = Data("Hello Cruel World!\r\n\(Date())\r\n".utf8)
        conn.send(content: message, completion: .contentProcessed({ error in
            print(error)
        }))
        conn.start(queue: .main)
        dispatchMain()
    }
}

main()

Hi everyone,

I'm currently working on establishing connections using either IPv4 or IPv6 in my Swift application. However, I've hit a snag with NWProtocolIP.Options. It appears that I'm unable to create its instance to configure additional options.

I would appreciate any tips or advice on how to achieve IPv4 or IPv6 connections exclusively. Here's a breakdown of what I'm aiming to accomplish in my app:

Initialize the protocol options and offer choices for IP versions and various other transport layer options. Initialize the endpoint by passing the IP address struct and port number. Create the connection object using the endpoint and parameters. If IPv4 was selected as the option and the IP address struct contained an IPv6 address, I expect the NWConnection to fail, providing the corresponding error code. Any insights or guidance on resolving this issue would be highly appreciated.

Thank you in advance for your help!