Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Send to a broadcastable socket on iOS 17 device #106588

Open
amp64 opened this issue Aug 18, 2024 · 22 comments
Open

Cannot Send to a broadcastable socket on iOS 17 device #106588

amp64 opened this issue Aug 18, 2024 · 22 comments
Assignees
Labels
area-System.Net.Sockets needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration os-ios Apple iOS
Milestone

Comments

@amp64
Copy link

amp64 commented Aug 18, 2024

Description

If you try and Send a Broadcast to a socket it will fail on iOS 17 devices with the error "no route to host". I believe this is due to the flag IP_BOUND_IF missing, described here: https://developer.apple.com/forums/thread/658518?answerId=631476022#631476022

Reproduction Steps

        private static async Task TestBroadcastAsync()
        {
            try
            {
                IPAddress localIP;
                using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0))
                {
                    socket.Connect("8.8.8.8", 65530);
                    IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
                    localIP = endPoint.Address;
                }

                bool multicast = false;

                using (var socket = new Socket(localIP.AddressFamily, SocketType.Dgram, ProtocolType.Udp))
                {
                    if (!multicast)
                    {
                        socket.EnableBroadcast = true;
                    }
                    socket.ExclusiveAddressUse = false;

                    var dest = new IPEndPoint(IPAddress.Broadcast, 1900);
                    socket.Bind(new IPEndPoint(localIP, 0));
                    Debug.WriteLine($"Test-Bound ok to {localIP}");
                    
                    var bytes = new ArraySegment<byte>(new byte[1]);
                    await socket.SendToAsync(bytes, SocketFlags.None, dest);
                }
            } 
            catch (Exception ex)
            {
               Debug.WriteLine("Exception: " + ex.Message);
            }
        }

Expected behavior

The Send to work

Actual behavior

"No route to host" exception.

Regression?

Unknown. This does work on iOS 15 devices, and iOS 17 simulators.

Known Workarounds

No response

Configuration

iPhone SE 3, iOS 17.5.1, .NET 8 (and 7)

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 18, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Aug 18, 2024
@jkotas jkotas added area-System.Net.Sockets os-ios Apple iOS and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 18, 2024
@amp64
Copy link
Author

amp64 commented Aug 20, 2024

Update, I was mistaken about one of my phones:
This affects iOS 16 as well, on all devices I have tried.
It is fine on iOS 15.3 devices.

@matouskozak
Copy link
Member

matouskozak commented Aug 30, 2024

Update, I was mistaken about one of my phones: This affects iOS 16 as well, on all devices I have tried. It is fine on iOS 15.3 devices.

@amp64 could you please share what exact version of .NET 8 are using (dotnet --info)? Also are you using a MAUI app or plain dotnet new ios app?

I tried you repro locally with latest .NET 8 servicing release (8.0.8), iPhone 11 with iOS 17.5.1 and the code worked correctly (i.e., it reported "Bound ok")

@matouskozak matouskozak added needs-author-action An issue or pull request that requires more info or actions from the author. and removed untriaged New issue has not been triaged by the area owner labels Aug 30, 2024
@amp64
Copy link
Author

amp64 commented Aug 30, 2024

Dumb question but how do I determine the exact version of dotnet on my iPhone? dotnet --version on my PC says 8.0.6, my PC has the 8.0.302 NET SDK from VS installed. I am using Avalonia 11.1.2 to x-target iOS.

In the logs the closest I see to a version is Microsoft.iOS.Sdk/17.2.8053

In Solution Explorer if I click under Frameworks I see 8.0.624.26715, so it seems like I need to update this.

@dotnet-policy-service dotnet-policy-service bot added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed needs-author-action An issue or pull request that requires more info or actions from the author. labels Aug 30, 2024
@amp64
Copy link
Author

amp64 commented Aug 30, 2024

Sorry I have done a poor job with the repro here. I was thinking the error was from the Bind, but its actually in the subsequent SendTo. Add this:

var remoteIP = IPAddress.Parse("239.255.255.250");
var dest = new IPEndPoint(remoteIP, 1900);
await socket.SendToAsync(buff, SocketFlags.None, dest);

@matouskozak
Copy link
Member

Dumb question but how do I determine the exact version of dotnet on my iPhone? dotnet --version on my PC says 8.0.6, my PC has the 8.0.302 NET SDK from VS installed. I am using Avalonia 11.1.2 to x-target iOS.

In the logs the closest I see to a version is Microsoft.iOS.Sdk/17.2.8053

In Solution Explorer if I click under Frameworks I see 8.0.624.26715, so it seems like I need to update this.

Not a dumb question at all. Running dotnet --info on your PC should give you all necessary information: runtime, SDK and iOS workload versions. Basically, all the things that you mentioned in your comment.

@matouskozak
Copy link
Member

Sorry I have done a poor job with the repro here. I was thinking the error was from the Bind, but its actually in the subsequent SendTo. Add this:

var remoteIP = IPAddress.Parse("239.255.255.250");
var dest = new IPEndPoint(remoteIP, 1900);
await socket.SendToAsync(buff, SocketFlags.None, dest);

No problem, could you please update the repro example and I'll check it out.

@amp64 amp64 changed the title Cannot Bind a broadcastable socket on iOS 17 device Cannot Send to a broadcastable socket on iOS 17 device Aug 30, 2024
@amp64
Copy link
Author

amp64 commented Aug 30, 2024

I updated the description and the repro to improve usefulness and accuracy.

@vitek-karas vitek-karas added this to the 10.0.0 milestone Sep 2, 2024
@vitek-karas
Copy link
Member

I'm marking these as 10.0 - as the priority is not that high. Once we figure out the real issue we can consider backporting to 9 as necessary.

@matouskozak
Copy link
Member

I updated the description and the repro to improve usefulness and accuracy.

Can confirm that the new repro reproduces inside dotnet new ios sample app on iOS 17.5.1 device.

@Spoxeman
Copy link

Any fix in sight or this? Is it fixed in .net 9? Sucks to be dealing with this.

@matouskozak
Copy link
Member

Any fix in sight or this? Is it fixed in .net 9? Sucks to be dealing with this.

@Spoxeman there haven't been any progress on this issue yet.

I just tried .NET 9 iOS sample app and it still fails on iOS 17.5.1 device but seems like it is working fine on iOS 18.2 simulator (don't have device to confirm).

@Spoxeman
Copy link

@matouskozak it looks like it’s definitely still broken on physical device too. I did a lot of testing the other day. What I know for sure is if you build the app using an older version of Xcode like 15.x the issue disappears. Problem with this is by April 24 Apple will reject any apps built with older versions of Xcode so where do we go from there ?

@matouskozak
Copy link
Member

if you build the app using an older version of Xcode like 15.x the issue disappears

@rolfbjarne are you aware of any Xcode related changes that would be causing this regression on our side?

@rolfbjarne
Copy link
Member

Yes, this is a change in iOS 16+.

Please read dotnet/macios#21814, where the customer talks to Apple engineers about this exact problem in https://developer.apple.com/forums//thread/771139.

One thing that complicates diagnosing, is that broadcasting/multicasting requires a specific entitlement (https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.networking.multicast?language=objc) - which requires permission from Apple before you get it.

I've kind of recently looked into this, and from a managed perspective there are a couple of annoyances:

  • IP_BOUND_IF isn't bound.
  • There's no way to get the name of a particular interface (this is done by calling if_nametoindex - which the BCL actually calls, and stores the name somewhere, but as far as I could figure out it's not available using public APIs).

I wasn't able to figure out what the problem was though.

@rolfbjarne
Copy link
Member

rolfbjarne commented Mar 4, 2025

I got this to work, and this is required:

  1. Ask Apple for access to the com.apple.developer.networking.multicast entitlement: https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.networking.multicast?language=objc
  2. Once you have the entitlement, add it to your Entitlements.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>com.apple.developer.networking.multicast</key>
        <true />
    </dict>
</plist>
  1. Add an entry for NSLocalNetworkUsageDescription in your Info.plist (without this it seems the privacy dialog to ask for permission to access the local network never shows up... and you never get permission):
<key>NSLocalNetworkUsageDescription</key>
<string>Hello local network!</string>
  1. Do something on the local network:
var sendbuf = new byte [16];
using var udpClient = new UdpClient (new IPEndPoint(IPAddress.Any, 12345));
udpClient.Send (sendbuf, "255.255.255.255", 12345);
Console.WriteLine ($"UDP message sent");

@rolfbjarne
Copy link
Member

Here's the test project I used:

ios-plain-08ec931.zip

and I run it like this:

dotnet build /t:Run /p:RuntimeIdentifier=ios-arm64 /p:_DeviceName="Rolf's iPhone 13" ios-plain.csproj /tl:off

hitting the button that shows up, yields this in the console:

Interface information for localhost.
	Number of interfaces .................... : 24
lo0
===
	Interface type .......................... : Loopback
	Physical Address ........................ :
	Operational status ...................... : Up
	IP version .............................. : IPv4 IPv6
[...]
en0
===
	Interface type .......................... : Wireless80211
	Physical Address ........................ : 020000000000
	Operational status ...................... : Up
	IP version .............................. : IPv4 IPv6
	Index ................................... : 13
	MTU...................................... : 1500
	Receive Only ............................ : False
	Multicast ............................... : True
[...]
Sending to 1 interfaces: en0
Sending to en0...
    Mapped interface en0 to index 13
    UDP message sent to en0 (rv = 16)

You'll have to update a few things first in the sample though:

  • Change the bundle identifier in the Info.plist.
  • Create a provisioning profile for that bundle identifier that enables the com.apple.developer.networking.multicast entitlement.
  • Use the name of your own device instead of mine in the command above.

@Spoxeman
Copy link

Spoxeman commented Mar 4, 2025

You have got to be kidding me!
All I had to do was add the below to my plist ( I already had the MC entitlement) and it all came back to life.

NSLocalNetworkUsageDescription
Hello local network!

@rolfbjarne just curious, was there particular documentation you reviewed to find this out?

@rolfbjarne
Copy link
Member

@rolfbjarne just curious, was there particular documentation you reviewed to find this out?

Heh yeah, I read a lot of documentation from Apple.

I started here:

https://developer.apple.com/forums//thread/771139

and then followed pretty much every link that looked remotely relevant:

and a bunch more.

The last one has this tiny bit of relevant info:

If your app accesses the local network, add the NSLocalNetworkUsageDescription property to its Info.plist to explain its behavior to the user.

but since it didn't sound all that important, I ignored it at first since I was just doing a simple debug app, especially because I got the privacy prompt once... but never again, no matter what I did.

Eventually (after quite a few hours of scratching my head, and trying different things, with multiple phones, almost wiped a phone...) I tried adding the usage description. The privacy prompt promptly showed up, and everything worked like a charm from then on!

@rolfbjarne
Copy link
Member

I guess we can close this then, since it's working for you now?

@Spoxeman
Copy link

Spoxeman commented Mar 4, 2025

I have no objections to closing it. Thanks for your help.

I guess we can close this then, since it's working for you now?

@amp64
Copy link
Author

amp64 commented Mar 4, 2025

My app has the changes quoted by @rolfbjarne and the MC entitlement but, when I opened this bug, they did not help. However my Mac is dead right now so I cannot verify this as I cannot build any iOS code. I am nervous about closing this code without someone else verifying this flag is not in fact required on a broadcast socket.

@rolfbjarne if you change multicast to true in my repro, does that work for you also?

@rolfbjarne
Copy link
Member

@amp64

I added this code to my test app:

static void TestBroadcast(bool multicast)
{
	try
	{
		IPAddress localIP;
		using (Socket socket = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, 0)) {
			socket.Connect ("8.8.8.8", 65530);
			IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
			localIP = endPoint.Address;
		}

		using (var socket = new Socket (localIP.AddressFamily, SocketType.Dgram, ProtocolType.Udp)) {
			if (!multicast)
				socket.EnableBroadcast = true;
			socket.ExclusiveAddressUse = false;

			var dest = new IPEndPoint (IPAddress.Broadcast, 1900);
			socket.Bind (new IPEndPoint (localIP, 0));
			Console.WriteLine ($"Test-Bound ok to {localIP}");

			var bytes = new ArraySegment<byte> (new byte[1]);
			socket.SendTo (bytes, SocketFlags.None, dest);
			Console.WriteLine ($"TestBroadcast({multicast}): Success!");
		}
	} catch (Exception ex) {
	   Console.WriteLine ("TestBroadcast Exception: " + ex);
	}
}

and called it like this:

TestBroadcast (false);
TestBroadcast (true);

and it printed this:

Test-Bound ok to 192.168.1.111
TestBroadcast(False): Success!
Test-Bound ok to 192.168.1.111
TestBroadcast Exception: System.Net.Sockets.SocketException (13): Permission denied
	   at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName)
	   at System.Net.Sockets.Socket.SendTo(ReadOnlySpan`1 buffer, SocketFlags socketFlags, EndPoint remoteEP)
	   at ios_plain.AppDelegate.TestBroadcast(Boolean multicast) in /Users/rolf/test/dotnet/ios-plain/AppDelegate.cs:line 69

This doesn't look like the other error though, so it might be something wrong with the sample code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.Net.Sockets needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration os-ios Apple iOS
Projects
None yet
Development

No branches or pull requests

6 participants