How to set NEDNSSettings port, or how to bind on port 53 for MacOS Network Extension?

In my Packet Tunnel Provider, I'm setting the NEDNSSettings to localhost as I have a local DNS server listening on port 53 (this is a dns forwarder which conditionally forwards to different upstreams based on rules).

On iOS it works just fine, I'm able to listen on localhost:53 in the Network Extension, then set NEDNSSettings servers to "127.0.0.1".

However on macOS due to the port being under 1024, I get a Permission denied OS code 13 error. I'm assuming this is due to the Network Extension not running as root. Can this be changed?

This could be rectified if you could customize the port in NEDNSSettings, as the listener could be on port 5353, but it doesn't look like it is possible?

Just wondering if there is some other way to accomplish what I'm trying to do in the macOS Network Extension?

Answered by DTS Engineer in 806177022
I'm assuming this is due to the Network Extension not running as root.

Yes and no.

macOS programs can bind to low-numbered ports without root privileges. Consider this:

let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0)
defer { try! fd.close() }
try fd.bind("0.0.0.0", 54)

IMPORTANT This is using my QSocket helpers; follow the link from Extra-ordinary Networking.

It runs just fine. I’m testing on macOS 14.6.1, but IIRC this restriction was lifted in macOS 10.14.

There are still restrictions though. For example:

let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0)
defer { try! fd.close() }
try fd.bind("127.0.0.1", 54)

This fails with EACCES. I suspect that’s what you’re seeing.

Note I’m testing with port 54, rather than 53, because there are parts of the system that treat 53 specially. That doesn’t seem to be the case here. The above code works the same with port 53. However, I didn’t try putting this code into an NE provider (-:


Also, NE providers can run as root on macOS; you just have to package them as a system extension. See TN3134 Network Extension provider deployment for more about packaging and deployment.

In general, sysex packaging makes more sense on macOS because the networking stack is global to the whole computer. However, the appex packaging continues to exist for various reasons.

Share and Enjoy

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

I'm assuming this is due to the Network Extension not running as root.

Yes and no.

macOS programs can bind to low-numbered ports without root privileges. Consider this:

let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0)
defer { try! fd.close() }
try fd.bind("0.0.0.0", 54)

IMPORTANT This is using my QSocket helpers; follow the link from Extra-ordinary Networking.

It runs just fine. I’m testing on macOS 14.6.1, but IIRC this restriction was lifted in macOS 10.14.

There are still restrictions though. For example:

let fd = try FileDescriptor.socket(AF_INET, SOCK_DGRAM, 0)
defer { try! fd.close() }
try fd.bind("127.0.0.1", 54)

This fails with EACCES. I suspect that’s what you’re seeing.

Note I’m testing with port 54, rather than 53, because there are parts of the system that treat 53 specially. That doesn’t seem to be the case here. The above code works the same with port 53. However, I didn’t try putting this code into an NE provider (-:


Also, NE providers can run as root on macOS; you just have to package them as a system extension. See TN3134 Network Extension provider deployment for more about packaging and deployment.

In general, sysex packaging makes more sense on macOS because the networking stack is global to the whole computer. However, the appex packaging continues to exist for various reasons.

Share and Enjoy

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

Thanks for the explanation, I have recreated the Packet Tunnel Provider as a system extension.

However I'm having trouble getting the system extension started. After hitting tunnel.connection.startVPNTunnel() on the main app, nothing really happens and no error is caught.

Trying to attach the debugger as root also does not initiate, as it seems the extension is not started at all.

I have followed your advice detailed at: https://developer.apple.com/forums/thread/725805

  1. The app is now copied to /Applications upon build
  2. In the VPNManager, the providerBundleIdentifier is set to the bundleIdentifier of the system extension network extension.
  3. I've added os_log to the main.swift initiation point, but no log shows up in the console app filtered by the subsystem

Any ideas where else to check?

Any ideas where else to check?

Can you see any log points at all from your system extension process? I’m not talking about your first light log point, but any log points where the process name matches your sysex?

I suspect you won’t find any. If you don’t, then it’s likely that the sysex failed to launch. Sometimes that generates a crash report for the sysex itself. Do you see any of those?

Share and Enjoy

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

I noticed whenever I hit connect the toggle UI element in the VPN profile of system preferences moves a little indicating the profile at least matches.

In the console app I do not see any crash reports, but filtering on my PacketTunnelDesktop target, I see:

"Found 0 registrations for com.example.desktop.PacketTunnel (com.apple.networkextension.packet-tunnel)"

I'm guessing some identifier is not matching? Unfortunately googling "Found 0 registrations for" yields 0 results on Google :(

The app bundle has the system extension packaged at Conents/Library/SystemExtensions/com.example.desktop.PacketTunnel .systemextension

How to set NEDNSSettings port, or how to bind on port 53 for MacOS Network Extension?
 
 
Q