Issue with Multicast Response via NWConnectionGroup Behind a Firewall

Hello Everyone,

I’m working on a project that involves multicast communication between processes running on different devices within the same network. For all my Apple devices (macOS, iOS, etc.), I am using NWConnectionGroup, which listens on a multicast address "XX.XX.XX.XX" and a specific multicast port.

The issue occurs when a requestor (such as a non-Apple process) sends a multicast request, and the server, which is a process running on an Apple device using NWConnectionGroup (the responder), attempts to reply. The problem is that the response is sent from a different ephemeral port rather than the port on which the multicast request was received.

If the client is behind a firewall that blocks unsolicited traffic, the firewall only allows incoming packets on the same multicast port used for the initial request. Since the multicast response is sent from a different ephemeral port, the firewall blocks this response, preventing the requestor from receiving it.

Questions:

  1. Is there a recommended approach within the NWConnectionGroup or Network.framework to ensure that responses to multicast requests are sent from the same port used for the request?

  2. Are there any best practices for handling multicast responses in scenarios where the requestor is behind a restrictive firewall?

Any insights or suggestions on how to account for this behavior and ensure reliable multicast communication in such environments would be greatly appreciated.

Thanks,

Harshal

Answered by DTS Engineer in 837614022

Sorry about the delay. WWDC is playing havoc with my scheduling.

I had another look at this today and I think I might have an answer for you. The trick is to force the local endpoint of the connection group itself:

let desc = try NWMulticastGroup(for: [.hostPort(host: "239.0.0.25", port: 5000)])
let parameters = NWParameters.udp
parameters.requiredLocalEndpoint = .hostPort(host: "::", port: 5000)
let group = NWConnectionGroup(with: desc, using: parameters)

Once you do this, a send from the group like this:

group.send(content: content) { error in
    if let error {
        print("did not send, error: \(error)")
    } else {
        print("did send")
    }
}

results in a datagram like this:

% sudo tcpdump -n port 5000
…
16:01:19.958966 IP 192.168.1.109.5000 > 239.0.0.25.5000: UDP, length 46

where 192.168.1.109 is my Mac’s local IP address.

That’s what you want, right?

Share and Enjoy

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

Sorry I missed this originally; I was out of the office.

If the client is behind a firewall that blocks unsolicited traffic

Just to confirm, you’re talking about a non-Apple firewall, right? So, not the firewall built in to macOS?

And the sequence is:

  1. Your accessory sends a multicast datagram.

  2. It passes out of the firewall, which records its address tuple, that is, local IP / local port / remote IP / remote port.

  3. The datagram is delivered to your app on an Apple device

  4. Your app sends a reply.

  5. The firewall blocks the incoming datagram because its tuple isn’t aligned with the tuple from step 2.

Is that right?

If so, can you give me an example tuple for the datagrams in steps 2 and 5. Well, for step 5 I’d like two example tuples, a working one and a failing one.

Share and Enjoy

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

Hi @DTS Engineer ,

Yes, when I mentioned the firewall, I was referring to a non-Apple firewall — one that's external to macOS and potentially blocking unsolicited traffic.

As for the address tuples you requested:

Step 2 (Recorded Tuple) :

Local IP: 10.20.16.45

Local Port: 5000

Remote IP: 10.20.16.144

Remote Port: 5000

Step 5 (Working Tuple):

Local IP: 10.20.16.45

Local Port: 5000

Remote IP: 10.20.16.144

Remote Port: 5000

Step 5 (Failing Tuple):

Local IP: 10.20.16.45

Local Port: 5000

Remote IP: 10.20.16.144

Remote Port: 53000 (Response is sent from ephemeral port in case of NF)

Let me know if you need any more details!

Thanks.

So step 2 suggests that your accessory is 10.20.16.45 and the Apple device is 10.20.16.144. Is that right?

Also, you started this thread with:

I am using NWConnectionGroup … on a multicast address "XX.XX.XX.XX"

but none of the addresses in these tuples are IPv4 multicast addresses.

Share and Enjoy

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

HI @DTS Engineer ,

So step 2 suggests that your accessory is 10.20.16.45 and the Apple device is 10.20.16.144. Is that right?

Yes, that's correct. The accessory is 10.20.16.45, and the Apple device is 10.20.16.144.

Apologies for the incorrect example in my previous reply.

Here are the step 2 and step 5 tuples with context of multicast:

Step 2:

Local IP: 10.20.16.45

Local Port: 5000

Remote IP: 239.0.0.25

Remote Port: 5000

Step 5: (working tuple)

Local IP: 10.20.16.28 ( a linux device jonied on same multicast ip and group)

Local port: 5000

Remote IP: 239.0.0.25

Remote Port: 5000

Step 5 : (failing tuple)

Local IP: 10.20.16.144 (Apple device running Network Framework using NWConnectionGroup)

Local Port: 53000 (because responses are sent from ephemeral ports)

Remote IP: 239.0.0.25

Remote Port: 5000

Thanks for all those details. That’s exactly what I needed.

With regards step 4, what API are you using to send your reply?

ps I think this might be another case where you have to use BSD Sockets rather than Network framework, something I mention in TN3151 and then touch on again in Extra-ordinary Networking > Broadcasts and Multicasts, Hints and Tips. However, I don’t want to say this for sure until I full understand your setup.

Share and Enjoy

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

Sorry for the delay in response. I am currently using “.reply” method using NWConnectionGroup to reply back to the multicast messages received. Now since I have sent the message from the port “X” and the responder is replying from the port “Y” the firewall blocks the incoming packets from the responder. Is there a recommended approach within the NWConnectionGroup or Network.framework to ensure that responses to multicast requests are sent from the same port used for the request? Are there any best practices for handling multicast responses in scenarios where the requestor is behind a restrictive firewall?

Hi @DTS Engineer,

I am using .reply method on NWConnectionGroup object to reply back to the multicast messages received.

**

Step 2:

Local IP: 10.20.16.45

Local Port: 5000

Remote IP: 239.0.0.25

Remote Port: 5000

Step 5: (working tuple)

Local IP: 10.20.16.28 ( a linux device jonied on same multicast ip and group)

Local port: 5000

Remote IP: 239.0.0.25

Remote Port: 5000

Step 5 : (failing tuple)

Local IP: 10.20.16.144 (Apple device running Network Framework using NWConnectionGroup)

Local Port: 53000 (because responses are sent from ephemeral ports)

Remote IP: 239.0.0.25

Remote Port: 5000

**

For the given use case, I have sent the message from the port 5000 but receiving it back on port 53000 which causes the firewall to block the incoming packets.

Is there a recommended approach within the NWConnectionGroup or Network.framework to ensure that responses to multicast requests are sent from the same port used for the request?

Are there any best practices for handling multicast responses in scenarios where the requestor is behind a restrictive firewall?

Hi @DTS Engineer, did you miss it?

did you miss it?

Nope. It’s on my to-do list but it’s been a bit bananas here in DTS recently. Sorry about the delay. I’ll try to get back to it sometime this week.

Share and Enjoy

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

Hi @DTS Engineer, any updates?

Sorry about the delay. WWDC is playing havoc with my scheduling.

I had another look at this today and I think I might have an answer for you. The trick is to force the local endpoint of the connection group itself:

let desc = try NWMulticastGroup(for: [.hostPort(host: "239.0.0.25", port: 5000)])
let parameters = NWParameters.udp
parameters.requiredLocalEndpoint = .hostPort(host: "::", port: 5000)
let group = NWConnectionGroup(with: desc, using: parameters)

Once you do this, a send from the group like this:

group.send(content: content) { error in
    if let error {
        print("did not send, error: \(error)")
    } else {
        print("did send")
    }
}

results in a datagram like this:

% sudo tcpdump -n port 5000
…
16:01:19.958966 IP 192.168.1.109.5000 > 239.0.0.25.5000: UDP, length 46

where 192.168.1.109 is my Mac’s local IP address.

That’s what you want, right?

Share and Enjoy

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

Hi @DTS Engineer,

I have two flows- one is requestor and another one is responder.

As a requestor, when we perform .send to the responder which invokes receiveHandler of the responder. Now using .reply, we want to invoke the newConnectionHandler on the requestor side.

Once you do this, a send from the group like this:

This will only cause the receiveHandler to be invoked from the requestor side when responder sends the packet using .send instead of .reply.

Due to which I have following questions:

  1. Is there a recommended approach within the NWConnectionGroup or Network.framework to ensure that responses to multicast requests are sent from the same port used for the request?
  2. Are there any best practices for handling multicast responses in scenarios where the requestor is behind a restrictive firewall?

Is there a specific reason you need to use reply(…)?

My way of thinking here is that you’re primarily concerned with the shape of the on-the-wire traffic, and thus it doesn’t matter how you actually send the reply as long as it has the right local IP / local port / remote IP / remote port tuple.

Share and Enjoy

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

Hi @DTS Engineer,

As a requestor, I send a multicast packet to a multicast group through which a responder responds back using .reply. Using .reply, I will able to invoke the newConnectionHandler on the requestor side for my use cases. I necessarily want to invoke this newConnectionHandler so that I can further create a unicast connection with the responder.

If I use .send on the responder side too, it will again invoke the receiveHandler on the requestor side which I don't want for my use cases.

I can’t imagine how that’d work. You want the remote peer to send a datagram with a particular local IP / local port / remote IP / remote port tuple, so that it gets through the firewall. That datagram triggers the receive handler on the local peer. There’s no way that the local peer can tell what API the remote peer called in order to send the datagram. It only has access to the datagram itself. Thus there’s no way that the local peer can know that it needs to invoke your new connection handler.

Share and Enjoy

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

Issue with Multicast Response via NWConnectionGroup Behind a Firewall
 
 
Q