Issue Sending Multicast Packets Across Multiple Interfaces Using NWConnectionGroup

Hi everyone,

I'm currently working on a project where I need to send multicast packets across all available network interfaces using Apple Network Framework's NWConnectionGroup. Specifically, the MacBook (device I am using for sending multicast requests, MacOS: 15.1) is connected to two networks: Wi-Fi (Network 1) and Ethernet (Network 2), and I need to send multicast requests over both interfaces.

I tried using the .requiredInterface property as suggested by Eskimo in this post, but I’m running into issues.

It seems like I can't create an NWInterface object because it doesn't have any initializers.

Here is the code which I wrote:

var multicast_group_descriptor : NWMulticastGroup
var multicast_endpoint : NWEndpoint
multicast_endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("234.0.0.1"), port: NWEndpoint.Port(rawValue: 49154)!)
var connection_group : NWConnectionGroup
var multicast_params : NWParameters
multicast_params = NWParameters.udp
 var interface = NWInterface(NWInterface.InterfaceType.wiredEthernet)

I get following error:

'NWInterface' cannot be constructed because it has no accessible initializers

I also experimented with the .requiredInterfaceType property. Even when I set it to .wiredEthernet and then change it to .wifi, I am still unable to send requests over the Wi-Fi network.

Here is the code I wrote:

var multicast_params : NWParameters
multicast_params = NWParameters.udp
multicast_params.allowLocalEndpointReuse = true
multicast_params.requiredInterfaceType = .wiredEthernet
    
var ip = multicast_params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
ip.disableMulticastLoopback = true

connection_group = NWConnectionGroup(with: multicast_group_descriptor, using: multicast_params)

connection_group.stateUpdateHandler = { state in
    print(state)
    
    if state == .ready {
        connection_group.send(content: "Hello from machine on 15".data(using: .utf8)) { error in
            print("Send to mg1 completed on wired Ethernet with error \(error?.errorCode)")
            
            var params = connection_group.parameters
            params.requiredInterfaceType = .wifi
            
            connection_group.send(content: "Hello from machine on 15 P2 on Wi-Fi".data(using: .utf8)) { error in
                print("Send to mg1 completed on Wi-Fi with error \(error?.errorCode)")
            }
        }
    }
}

Is this expected behavior when using NWConnectionGroup? Or is there a different approach I should take to ensure multicast requests are sent over both interfaces simultaneously?

Any insights or suggestions would be greatly appreciated!

Thanks in advance,

Harshal

Answered by DTS Engineer in 831639022

I discussed your situation with the Network framework team, just confirm my understanding of what should work. The conclusion is that my expectations were reasonable. That is:

  • In the absence of an interface constraint, a group should receive multicasts on all interfaces.

  • And also send on all interfaces (assuming you don’t target a specific endpoint)

  • Applying an interface constraint (via requiredInterface and friends) should limit this to just the specific interfaces.

I don’t have time today to actually test any of this but, if you’re seeing different behaviour, it’s reasonable for you to file a bug about that.

Please post your bug number, just for the record.

Share and Enjoy

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

Contrary to what Eskimo stated in the reply here

QUOTE

"Upon initializing NWConnectionGroup, it appears that packets are received on all interfaces without the ability to control this at the interface level. Is this correct?"

Eskimo: Correct.

UNQUOTE

I wrote below code:


import Network
import Foundation
  
var multicast_group_descriptor : NWMulticastGroup
var multicast_endpoint : NWEndpoint
multicast_endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host("234.0.0.1"), port: NWEndpoint.Port(rawValue: 49154)!)
  
do {
    multicast_group_descriptor = try NWMulticastGroup(for: [multicast_endpoint], disableUnicast: false)
  
  
var connection_group : NWConnectionGroup
var multicast_params : NWParameters
multicast_params = NWParameters.udp
multicast_params.allowLocalEndpointReuse = true
    
    var ip = multicast_params.defaultProtocolStack.internetProtocol! as! NWProtocolIP.Options
    
    ip.disableMulticastLoopback = true
    
    
connection_group = NWConnectionGroup (with: multicast_group_descriptor, using: multicast_params)
  
    connection_group.stateUpdateHandler = { state in
             
            print (state)
             
        if (state == .ready) {
             
                connection_group.send(content: "Hello from machine on 15".data(using: .utf8)) { error in
                    print ("send to mg1 completed with error \(error?.errorCode)")
 
            }
            
            
        }
         
    }
     
    connection_group.newConnectionHandler = { connection in
             
        print ("received new connection")
        print (connection.endpoint)
        connection.stateUpdateHandler = { state in
             
            print ("Received connection state: \(state)")
             
            if (state == .ready) {
                 
                connection.receive(minimumIncompleteLength: 1, maximumLength: 65000) { content, contentContext, isComplete, error in
                    print ("received message with error \(error?.errorCode) is: ")
                    print (String (data: content!, encoding: .utf8))
                }
            }
        }
        connection.start(queue: .global())
        }
     
    connection_group.setReceiveHandler { message, content, isComplete in
        print ("Recevied on Receive Handler: \(String (data: content!, encoding: .utf8))")
        print (message.remoteEndpoint)
         
           
            message.reply(content: "hello back from 15 P2".data(using: .utf8))
        
    }
     
    connection_group.start(queue: .global())
    RunLoop.main.run()
     
} catch (let err) {
     
    print (err)
}

When I send packets from a device connected via Ethernet (Network 2), they are delivered to the Receive Handler. However, packets sent from a device on Wi-Fi (Network 1) are not received by the handler, despite both packets being captured on my machine (confirmed via Wireshark).

Wireshark Trace:

10 130.653672 X.Y.Z.Q 234.0.0.1 UDP 60 64737 → 49154 Len=18
11 134.479501 A.B.C.D 234.0.0.1 UDP 60 56643 → 49154 Len=18

Output from Code:

Received on Receive Handler: Optional("\u{01}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0X")
Optional(X.Y.Z.Q:60504)

Note:

  • X.Y.Z.Q is the IP for the machine connected to Ethernet (Network 2).

  • A.B.C.D is the IP for the machine connected to Wi-Fi (Network 1).

When I run the networksetup -listallnetworkservices command, I get the following output:

n asterisk (*) denotes that a network service is disabled.
AX88179A
Belkin USB-C LAN // my ethernet adaptor
Wi-Fi   // my wifi adaptor
Thunderbolt Bridge
GlobalProtect

This confirms that both the Wi-Fi and Ethernet services are active.

How can I ensure that I receive all the multicast packets from all available interfaces (Ethernet and Wi-Fi) on the Receive Handler?

Regards,

Harshal

Bah, I’m still dealing with a big backlog, so I don’t have time to respond fully, but I can at least answer this.

It seems like I can't create an NWInterface object because it doesn't have any initializers.

Indeed. You can use an Ethernet channel path monitor for this. For the details, see this thread.

Share and Enjoy

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

Sorry for the delay in response. As per the suggestion, I tried to used NWPathMonitor() to get all the interfaces I have. And then I create a NWMulticastGroup object passing all the interfaces whose file descriptor is further used to create a NWConnectionGroup object which is used for send/receive operations. However, the issues still occur for the interfaces where the system has taken one of them as the primary interface (let us say we have two interfaces, ethernet and wifi and ethernet is taken as primary interface) and I am unable to send/receive packets through other interfaces (wifi). I have confirmed this behavior using wireshark where I am unable to send/receive multicast packets from the receiver using the other interfaces (wifi), but I am only able to get those messages from the primary interface (ethernet).

Hi @DTS Engineer,

For the above observation, I am using the following piece of code:

pathMonitor.pathUpdateHandler = { path in
    if path.status == .satisfied {
        multicastEndpoints.removeAll()
        
        for interface in path.availableInterfaces {
            if interface.type == .wifi || interface.type == .wiredEthernet {
                let multicastEndpoint = NWEndpoint.hostPort(host: multicastHost, port: multicastPort)
                multicastEndpoints.append(multicastEndpoint)
            }
        }

        if !multicastEndpoints.isEmpty {
            do {
                let multicastGroupDescriptor = try NWMulticastGroup(for: multicastEndpoints, disableUnicast: false)
                var multicastParams = NWParameters.udp
                multicastParams.allowLocalEndpointReuse = true
                let ip_options = multicastParams.defaultProtocolStack.internetProtocol as! NWProtocolIP.Options
                ip_options.disableMulticastLoopback = true
            
                connectionGroup = NWConnectionGroup(with: multicastGroupDescriptor, using: multicastParams)
                
                connectionGroup?.stateUpdateHandler = { state in
                    print("Connection group state: \(state)")
                    
                    if state == .ready {
                        connectionGroup?.send(content: "Hello from machine on 15".data(using: .utf8)) { error in
                            print("Send completed with error \(error?.errorCode ?? 0)")
                        }
                    }
                }
                connectionGroup?.newConnectionHandler = { connection in
                    print("Received new connection on endpoint")                
}
                connectionGroup?.setReceiveHandler { message, content, isComplete in
                    if let content = content, let messageString = String(data: content, encoding: .utf8) {
                        print("Received message: \(messageString)")
                        message.reply(content: "Hello back from mac 15".data(using: .utf8))
                    }
                }
                
                connectionGroup?.start(queue: .global())
            } catch let error {
                print("Error creating multicast group: \(error)")
            }
        } else {
            print("No valid network interfaces found.")
        }
    } else {
        print("No network interfaces available.")
    }
}

Here, I am creating an NWMulticastGroup with the available multicastEndpoints tracked using pathMonitor. The multicastGroupDescriptor returned by NWMulticastGroup is further used to create NWConnectionGroup which is then used for send/receive operations over the available interfaces (in this case .wifi and .ethernet).

Now, the above issue occurs since the system has taken one of them as primary interfaces (in my case it is .ethernet). Due to this, I am unable to send/receive using other interfaces (.wifi in my case).

I have confirmed this behavior using wireshark as stated earlier.

Hmmmm, I starting to suspect this is one of the many limitations of Network framework’s multicast and broadcast support )-: I’m off to check something and I’ll reply back here when I’m done, hopefully later this week.

Share and Enjoy

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

I discussed your situation with the Network framework team, just confirm my understanding of what should work. The conclusion is that my expectations were reasonable. That is:

  • In the absence of an interface constraint, a group should receive multicasts on all interfaces.

  • And also send on all interfaces (assuming you don’t target a specific endpoint)

  • Applying an interface constraint (via requiredInterface and friends) should limit this to just the specific interfaces.

I don’t have time today to actually test any of this but, if you’re seeing different behaviour, it’s reasonable for you to file a bug about that.

Please post your bug number, just for the record.

Share and Enjoy

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

Issue Sending Multicast Packets Across Multiple Interfaces Using NWConnectionGroup
 
 
Q