mDNSResponder suppressing queries from a spawned process

I'm trying to figure out how to debug failure to successfully resolve DNS queries.

I have an app that installs a network extension as a system extension. Then, the system extension spawns a second process via posix_spawn.

This second process fails to resolve DNS queries, but the initial system extension process can connect to a URL involving the same hostname.

In mDNSResponder I see:

2024-08-23 11:01:30.313470+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56090] DNSServiceCreateConnection START PID[70515](coder)
2024-08-23 11:01:30.313857+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56091] DNSServiceQueryRecord(15000, 0, <mask.hash: 'sUpGaOtvrWLwu6toEcVb1g=='>(e8da8e0d), A) START PID[70515](coder)
2024-08-23 11:01:30.314945+0400 0x2336     Debug       0x0                  1320   0    mDNSResponder: (Network) [com.apple.network:] -[NWConcrete_nw_path_evaluator dealloc] AE46B126-E438-4804-B030-F0E337AED7A0
2024-08-23 11:01:30.315004+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [Q18806] InitDNSConfig: Setting StopTime on the uDNS question 0x13d356ce0 <mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='> (Addr)
2024-08-23 11:01:30.315051+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56091->Q18806] Question for <mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='> (Addr) assigned DNS service 1461
2024-08-23 11:01:30.315075+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [Q18806] DetermineUnicastQuerySuppression: Query suppressed for <mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='> Addr (blocked by policy)
2024-08-23 11:01:30.316901+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56091->Q18806] GenerateNegativeResponse: Generating negative response for question <mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='> (Addr)
2024-08-23 11:01:30.316953+0400 0x2336     Debug       0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56091] QueryRecordOpCallback: Suppressed question <mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='> (Addr)
2024-08-23 11:01:30.316984+0400 0x2336     Default     0x0                  1320   0    mDNSResponder: [com.apple.mDNSResponder:Default] [R56091->Q18806] DNSServiceQueryRecord(<mask.hash: 'H8NJEpnLHE9dtbSyztCK1A=='>(e8da8e0d), A) RESULT ADD interface 0: (mortal, DNSSEC Indeterminate)<mask.hash: 'fy5Hgf26/rhBtId5NoaY9A=='>

So, my query is getting "suppressed" by mDNSResponder, blocked by policy. It doesn't seem to matter what DNS name my 2nd process queries---they are all suppressed.

What policies does mDNSResponder enforce? How can I figure out why my queries are being suppressed?

I have an app that installs a network extension as a system extension. Then, the system extension spawns a second process via posix_spawn.

Are you sure you're extension isn't what's blocking things? What's the network extension type and what policies/behavior does it have? Any tool your extension spawned would have been treated the same as the "broader" system, NOT you network extension.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Are you sure you're extension isn't what's blocking things? What's the network extension type and what policies/behavior does it have?

The system extension is a PacketTunnelProvider, setting up a custom VPN. It doesn't do any filtering / policy.

Any tool your extension spawned would have been treated the same as the "broader" system, NOT you network extension.

So that's the strangest part about this: if I run the same binary from a terminal, it resolves DNS and connects just fine, including when run as root. It's only when spawned from my system extension that DNS queries are "blocked by policy."

At this point I'm still trying to understand DNS policy at the highest level: where do the policies come from? Are they baked into the system like sandbox policies, or are they configurable via some API?

First, let me clarify this question:

Are they baked into the system like sandbox policies, or are they configurable via some API?

Neither and both. Architecturally, there isn't actually very much "baked into" to any of our policies (sandbox or otherwise). The way the sandbox (and other similar system) actually work is that the processes creating each new process is responsible for defining the "rules" of the process it's creating. There are private SPIs that explicitly manage this but the "default" behavior is that a newly created process simply inherits the sandbox of it's parent process*. The ensures that a process can't widen it's capabilities by creating a child process so it's used fairly broadly, even components that aren't part of the standard "app sandbox" architecture.

*This is also why things like app launches and XPC services are actually launched by "launchd", not directly by the original parent process. launchd can apply whatever sandbox configuration it chooses (based on the process configuration), which most processes (even system components) cannot.

At this point I'm still trying to understand DNS policy at the highest level: where do the policies come from?

The network system itself. At a code leve, you can actually see the "blocked by policy" error in mDNSReponder's code.. In your particular case, it's almost certainly because of something about how your packet tunnel provider is configured which is then being inherited by your new child process, possibly (this is a guess) combined with differences in how you're actually doing the networking.

A few things here:

  • mDNSResponder has it's own debugging profile, which will both increase the detail of what's logged and un-redact the existing messages. That's the first thing I would try here, as the additional logging may be enough to tell you exactly what the problem is. There is also a VPN profile which could be worth installing as well. Note that you can install as many of these profiles as you want, however, they will increase log volume which can create it's own challenges.

  • Why are you spawning a new process and what are you actually trying to do? I'm concerned that you're straying outside the guidance in TN3120, which rarely goes well.

  • As context, what are you actually trying to resolve (bonjour or standard DNS) and what networking API are you actually using? Have you actually tested the same code in your extension (not just the same resolution with a different API)?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

There are private SPIs that explicitly manage this but the "default" behavior is that a newly created process simply inherits the sandbox of it's parent process*.

Is DNS policy enforced by mDNSResponder part of the Sandbox, or is DNS policy a different thing? If it's the Sandbox, and the child process inherits the same profile, then how can DNS resolve correctly in the parent but not the child?

At a code leve, you can actually see the "blocked by policy" error in mDNSReponder's code.. In your particular case, it's almost certainly because of something about how your packet tunnel provider is configured which is then being inherited by your new child process

Right, so I took a look at the open source mDNSResponder, and this appears to be where it blocks queries.

The problem for me in understanding what's happening is that this function calls into undocumented APIs, creating something called a path evaluator, which then spits out a verdict of "satisfied" or "unsatisfied" for the DNS query. Do you know what this function actually does and how it decides whether the path is satisfied?

In collecting parameters for this evaluation query, it (again via an undocumented API) collects a "UUID" associated with the PID of the querier. Do you know what this UUID is? I've tried searching Mac docs for any mention of a process UUID, but have so far come up empty.

Even when the VPN is enabled, DNS resolution seems to work fine when I run the same binary from a Terminal, but fails when spawned from the system extension. I'm certainly not confident that my tunnel configuration itself has nothing to do with it, but it happens regardless of whether I add any DNS settings to the tunnel configuration or not. I'm flying blind here because I haven't found any information about how mDNSResponder decides what is and is not blocked by policy, and how tunnel DNS configuration might enter in the mix.

Why are you spawning a new process and what are you actually trying to do? I'm concerned that you're straying outside the guidance in TN3120, which rarely goes well.

I'm spawning a new process because the code that actually implements the tunnel is written in Go (it's essentially Wireguard), and I don't want to have Swift threads/memory management alongside Go goroutines & garbage collection in the same process. I'm following the guidance of TN3120---the goal really is a packet tunnel. The spawned process tries to dial the VPN server by DNS name and that's where it's getting hung up.

As context, what are you actually trying to resolve (bonjour or standard DNS) and what networking API are you actually using? Have you actually tested the same code in your extension (not just the same resolution with a different API)?

In the System Extension process, I've tried both the URLSession API to reach the VPN server by name and the lower level POSIX getaddrinfo. These resolve the standard DNS name correctly, and I can see mDNSResponder dropping logs about the query. The logs look the same as the ones dropped when the spawned process makes the query, other than the spawned process gets "blocked by policy."

I'm pretty sure Go uses getaddrinfo, but I can try to double check it's the same API.

I've also tried using the standard Swift Process() call to spawn the child from the System Extension, and I get the same results regarding DNS (the reason I'm using posix_spawn is to pass the TUN file descriptor, Process() only supports stdin/stdout/stderr file descriptors).

mDNSResponder suppressing queries from a spawned process
 
 
Q