General:
DevForums tag: Network Extension
Network Extension framework documentation
Network Extension and VPN Glossary DevForums post
Debugging a Network Extension Provider DevForums post
Exporting a Developer ID Network Extension DevForums post
Network Extension vs ad hoc techniques on macOS DevForums post
Extra-ordinary Networking DevForums post
Wi-Fi management:
Wi-Fi Fundamentals DevForums post
TN3111 iOS Wi-Fi API overview technote
How to modernize your captive network developer news post
iOS Network Signal Strength DevForums post
See also Networking Resources.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Network Extension
RSS for tagCustomize and extend the core networking features of iOS, iPad OS, and macOS using Network Extension.
Posts under Network Extension tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello,
I have a company laptop thats connected to the internet without a VPN. I need to be able to resolve my company's sub domains using a specific dns server, and have all other domains resolved by the system wide name server.
In windows, this is trivial to do. In an admin powershell I run
"Add-DnsClientNrptRule -Namespace ".foo.mycompany.com" -Nameserver "127.0.0.1"
and resolution requests for *.foo.mycompany.com is sent to a name server running on the localhost. All other dns resolution requests are handled by the system configured resolver.
MacOS does have the /etc/resolver/ solution for this, but my understanding from these forums is that this is not the recommended approach. Note - I have tried it and it works.
AFAIU, the recommended approach is to create a system Network extension using NEDNSProxyProvider, override handleNewFlow() and do what's necessary.
The issue with this solution is that it requires
handling all the dns flow
parsing of DNS datagrams to extract the host
forwarding the datagrams to the appropriate dns server
Handle responses.
Deal with flow control
Handle edge cases.
I was hoping for something much simpler than us needing to implement datagram parsing.
Could you please shed light on our options and how we could proceed ?
I'm use iPad OS 17.5.1, when I try to use socket to connect to an ipv6 address created by PacketTunnelProvider in my iOS device, an error occurs. Here is the code to create socket server and client:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int dx_create_ipv6_server(const char *ipv6_address, int port) {
int server_fd;
struct sockaddr_in6 server_addr;
server_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket() failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(port);
if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
perror("inet_pton() failed");
close(server_fd);
return -1;
}
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind() failed");
close(server_fd);
return -1;
}
if (listen(server_fd, 5) == -1) {
perror("listen() failed");
close(server_fd);
return -1;
}
printf("Server is listening on [%s]:%d\n", ipv6_address, port);
return server_fd;
}
int dx_accept_client_connection(int server_fd) {
int client_fd;
struct sockaddr_in6 client_addr;
socklen_t client_addr_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd == -1) {
perror("accept() failed");
return -1;
}
char client_ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &client_addr.sin6_addr, client_ip, sizeof(client_ip));
printf("Client connected: [%s]\n", client_ip);
return client_fd;
}
int dx_connect_to_ipv6_server(const char *ipv6_address, int port) {
int client_fd;
struct sockaddr_in6 server_addr;
client_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket() failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(port);
if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
perror("inet_pton() failed");
close(client_fd);
return -1;
}
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect() failed");
close(client_fd);
return -1;
}
printf("Connected to server [%s]:%d\n", ipv6_address, port);
close(client_fd);
return 0;
}
@implementation SocketTest
+ (void)startSever:(NSString *)addr port:(int)port {
[[NSOperationQueue new] addOperationWithBlock:^{
int server_fd = dx_create_ipv6_server(addr.UTF8String, port);
if (server_fd == -1) {
return;
}
int client_fd = dx_accept_client_connection(server_fd);
if (client_fd == -1) {
close(server_fd);
return;
}
close(client_fd);
close(server_fd);
}];
}
+ (void)clientConnect:(NSString *)addr port:(int)port{
[[NSOperationQueue new] addOperationWithBlock:^{
dx_connect_to_ipv6_server(addr.UTF8String, port);
}];
}
@end
PacketTunnelProvider code:
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "fd84:306d:fc4e::1")
let ipv6 = NEIPv6Settings(addresses: ["fd84:306d:fc4e::1"], networkPrefixLengths: 64)
settings.ipv6Settings = ipv6
setTunnelNetworkSettings(settings) { error in
if error == nil {
self.readPackets()
}
completionHandler(error)
}
}
private func readPackets() {
// do nothing
packetFlow.readPackets { [self] packets, protocols in
self.packetFlow.writePackets(packets, withProtocols: protocols)
self.readPackets()
}
}
At main target, in viewcontroller's viewDidAppear, after starting the VPN, executed following code:
[SocketTest startSever:@"fd84:306d:fc4e::1" port:12345];
sleep(3);
[SocketTest clientConnect:@"fd84:306d:fc4e::1" port:12345];
The startSever is executed correctly, but when executing:
connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))
in clientConnect, the code is blocked until it times out and returns -1.
**Even if I use GCDAsyncSocket or BlueSocket, I get the same error. The strange thing is that if I use the ipv4 address in PacketTunnelProvider, and change the above code to the ipv4 version and connect to ipv4 address, or use GCDAsyncSocket to perform the corresponding operation, it can be executed correctly.
**
I tried to search Google for problems with ios-related ipv6 addresses, but I still couldn't find a solution. Is this a bug in the ios system or is there something wrong with my code? I hope to get your help!
Stackoverflow url: iOS Socket cannot connect ipv6 address when use PacketTunnelProvider
When I try to use socket to connect to an ipv6 address created by PacketTunnelProvider in my iOS device, an error occurs. Here is the code to create socket server and client:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int dx_create_ipv6_server(const char *ipv6_address, int port) {
int server_fd;
struct sockaddr_in6 server_addr;
server_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket() failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(port);
if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
perror("inet_pton() failed");
close(server_fd);
return -1;
}
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind() failed");
close(server_fd);
return -1;
}
if (listen(server_fd, 5) == -1) {
perror("listen() failed");
close(server_fd);
return -1;
}
printf("Server is listening on [%s]:%d\n", ipv6_address, port);
return server_fd;
}
int dx_accept_client_connection(int server_fd) {
int client_fd;
struct sockaddr_in6 client_addr;
socklen_t client_addr_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd == -1) {
perror("accept() failed");
return -1;
}
char client_ip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &client_addr.sin6_addr, client_ip, sizeof(client_ip));
printf("Client connected: [%s]\n", client_ip);
return client_fd;
}
int dx_connect_to_ipv6_server(const char *ipv6_address, int port) {
int client_fd;
struct sockaddr_in6 server_addr;
client_fd = socket(AF_INET6, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket() failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(port);
if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
perror("inet_pton() failed");
close(client_fd);
return -1;
}
if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect() failed");
close(client_fd);
return -1;
}
printf("Connected to server [%s]:%d\n", ipv6_address, port);
close(client_fd);
return 0;
}
@implementation SocketTest
+ (void)startSever:(NSString *)addr port:(int)port {
[[NSOperationQueue new] addOperationWithBlock:^{
int server_fd = dx_create_ipv6_server(addr.UTF8String, port);
if (server_fd == -1) {
return;
}
int client_fd = dx_accept_client_connection(server_fd);
if (client_fd == -1) {
close(server_fd);
return;
}
close(client_fd);
close(server_fd);
}];
}
+ (void)clientConnect:(NSString *)addr port:(int)port{
[[NSOperationQueue new] addOperationWithBlock:^{
dx_connect_to_ipv6_server(addr.UTF8String, port);
}];
}
@end
PacketTunnelProvider code:
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "fd84:306d:fc4e::1")
let ipv6 = NEIPv6Settings(addresses: ["fd84:306d:fc4e::1"], networkPrefixLengths: 64)
settings.ipv6Settings = ipv6
setTunnelNetworkSettings(settings) { error in
if error == nil {
self.readPackets()
}
completionHandler(error)
}
}
private func readPackets() {
// do nothing
packetFlow.readPackets { [self] packets, protocols in
self.packetFlow.writePackets(packets, withProtocols: protocols)
self.readPackets()
}
}
At main target, in viewcontroller's viewDidAppear, after starting the VPN, executed following code:
[SocketTest startSever:@"fd84:306d:fc4e::1" port:12345];
sleep(3);
[SocketTest clientConnect:@"fd84:306d:fc4e::1" port:12345];
The startSever is executed correctly, but when executing:
connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))
in clientConnect, the code is blocked until it times out and returns -1.
Even if I use GCDAsyncSocket or BlueSocket, I get the same error.
The strange thing is that if I use the ipv4 address in PacketTunnelProvider, and change the above code to the ipv4 version and connect to ipv4 address, or use GCDAsyncSocket to perform the corresponding operation, it can be executed correctly.
I tried to search Google for problems with ios-related ipv6 addresses, but I still couldn't find a solution. Is this a bug in the ios system or is there something wrong with my code? I hope to get your help!
I am working on developing a Mac app (WireGuard Apple VPN) that will be distributed outside the App Store.
I have added the network extension which is included in the system extension with packet tunneling capability.
I have created a build following these steps here: https://developer.apple.com/forums/thread/737894
as per your suggestions in my accepted post: https://developer.apple.com/forums/thread/761251
It works fine in this case when the machine has SIP disabled and systemextensionsctl developer enabled.
As soon as I have made changes on the machine to disable systemextensionsctl developer and enable SIP, it loads the system extension and also asks for network extension permission. But it does not connect to the VPN.
I have copied the app to the "/Applications" directory before opening it.
This issue is specific to macOS 15.1. It works fine for macOS 14.* and 13.*. Speaking of macOS 15.0, it didn't work in both cases with SIP enabled or disabled. So, it seems that it must be a bug in macOS 15.0 and it seems that this bug was partially fixed in macOS 15.1. Is that right?
I am currently planning to distribute the app to testers for final testing before rolling it out to a wider audience.
Am I missing something? Thanks in advance.
I have a need to do a coverage test for the networkextension function code implemented by the system extension, but I don't know how to implement this method.
For example, how do you use gtest or how do you use xctest to achieve these capabilities?
If you know, please let me know. Thanks
I'm working on a network extension that provides a VPN tunnel. The logic behind the tunnel provider requires me to connect the backend to pull recent configuration and then configure the routing. It works in general but fails with some circumstances. I have 100% reproducible fails if I run OpenVPN tunnel in parallel. When it happens it looks like the network extension cannot connect the backend (any internet resource actually). Requests fail by timeout.
To troubleshoot this situations I've added NWPathMonitor at my NEPacketTunnelProvider subclass:
pathMonitor?.pathUpdateHandler = { path in
logger.info("Path update: \(path)")
logger.info(" : \(path.availableInterfaces)")
}
On successful scenarios I observed logs:
14:53:19:829 Starting VPN tunnel...
14:53:19:895 Path update: satisfied (Path is satisfied), interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi
14:53:19:899 : [en0]
14:53:22:237 Path update: satisfied (Path is satisfied), interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi
14:53:22:253 : [en0, utun12]
14:53:22:325 VPN tunnel is started.
But if I start another tunnel first using OpenVPN (it's our corporate VPN) I observe failures with such log messages:
14:54:26:113 Starting VPN tunnel...
14:54:26:140 Path update: satisfied (Path is satisfied), interface: en0[802.11], scoped, ipv4, ipv6, dns, uses wifi
14:54:26:141 : [en0]
14:55:28:259 Failed to start VPN tunnel.
utun12 that was used by the extension in case of success is now occupied by the OpenVPN tunnel. The system creates utun13 for me but it feels like its misconfigured:
> ifconfig
(omitted most of the output)
utun12: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
inet 172.28.11.236 --> 172.28.10.1 netmask 0xfffffe00
nd6 options=201<PERFORMNUD,DAD>
utun13: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500
options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
Is it a system bug and what else can I do to diagnose the root cause of these failures?
Starting on macOS Sequoia, flows originated in Firefox have an empty sourceAppAuditToken. Other apps contain a valid token.
Background: in order to fetch process info for a certain NEFilterFlow, my content filter extension uses sourceAppAuditToken, audit_token_to_pid() and proc_* (as recommended in #126820). When that fails, we use SecCodeCopyGuestWithAttributes, recommended in some other thread as a better alternative. Both approaches break when the sourceAppAuditToken is empty since they need the pid.
Debugging:
My logging shows audit token is empty for Firefox
Typical logs from com.apple.networkextension also indicate it fails to fetch the same info I'm looking for:
com.apple.networkextension debug 11:22:07.024588-0300 Fetching appInfo from cache for pid: 948 uuid: 5C40B765-C6C9-3641-A822-2BC44D264361 bundle id: (null)
com.apple.networkextension debug 11:22:07.024657-0300 Calling delegate lookup handler with pid: 948, uuid: 5C40B765-C6C9-3641-A822-2BC44D264361, bundleID: (null)
com.apple.networkextension debug 11:22:07.025856-0300 Could not look up appInfo for pid: 948 bundle id: (null) uuid: 5C40B765-C6C9-3641-A822-2BC44D264361
com.apple.networkextension error 11:22:07.025897-0300 Could not find app info, return the original flow without filling in app info
Handling new flow:
identifier = D89B5B5D-793C-4940-D992-4E90F2AD1900
procPID = 953
eprocPID = 948
direction = outbound
inBytes = 0
outBytes = 0
signature = {length = 32, bytes = 0x4afeafde b484aa0c c5cb8698 0567343d ... 7cdee33e 135666dd }
socketID = 19adf2904e92d9
localEndpoint = 0.0.0.0:0
remoteEndpoint = 17.33.202.170:443
protocol = 6
family = 2
type = 1
procUUID = 0C68E603-967E-3643-B225-378BD2A655F7
eprocUUID = 5C40B765-C6C9-3641-A822-2BC44D264361
Perhaps there's a bug when generating the audit token or could it be something with the Firefox signature?
I double-checked Firefox and it seems fine:
$ codesign --verify --verbose /Applications/Firefox.app
/Applications/Firefox.app: valid on disk
/Applications/Firefox.app: satisfies its Designated Requirement
Not sure if relevant, but codesign with -dv showed different flags in CodeDirectory when compared to chrome:
codesign -dv /Applications/Firefox.app
...
CodeDirectory v=20500 size=863 flags=0x10000(runtime) hashes=18+5
...
Versus chrome
CodeDirectory v=20500 size=1821 flags=0x12a00(kill,restrict,library-validation,runtime) hashes=46+7 location=embedded
Hi,
I developed a system extension that uses the content filter providers of the network extension. When I am using a VPN, I turn on the network extension and the VPN is disconnected. Can this problem be avoided? How to prevent VPN disconnection
There could be a case where-in multiple transparent proxies might exist in the system (for ex., Cisco AnyConnect, GlobalProtect, etc).
We want to know if there is a way to order transparent proxies so that the desired transparent proxy gets the request first. During our research, we found a resource which talks about ordering transparent proxies through MDM.
https://developer.apple.com/documentation/devicemanagement/vpn/transparentproxy
Using this reference, we tried to create a profile and push it through JAMF. Below is the profile that we created and pushed with JAMF.
Property List -
<?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>TransparentProxy</key>
<array>
<dict>
<key>ProviderBundleIdentifier</key>
<string>com.paloaltonetworks.GlobalProtect.client.extension</string>
<key>Order</key>
<string>1</string>
</dict>
<dict>
<key>ProviderBundleIdentifier</key>
<string>com.cisco.anyconnect.macos.acsockext</string>
<key>Order</key>
<string>2</string>
</dict>
<dict>
<key>ProviderBundleIdentifier</key>
<string>com.mydomain.transparentproxy</string>
<key>Order</key>
<string>3</string>
</dict>
</array>
We are not sure if this is the right way to create the profile, though JAMF is not throwing any error while pushing this profile.
We see this profile on the local machine as "/Library/Managed Preferences/com.apple.networking.vpn-transparent-list.plist".
Is there a way to know if the profile took effect and the order of transparent proxies has changed.
Thanks in advance.
Hello Apple Developer Community,
I am currently working on a macOS project where my primary goal is to intercept IP packets, modify them (specifically the TCP payload), and then forward them. My intended use case involves selectively intercepting outgoing packets based on their destination IP, altering their content, and sending them on their way to the original destination.
What I’ve Tried:
NEAppProxyProvider:
• I explored using App Proxy Provider to handle new TCP and UDP flows.
• While it allowed me to read the data, handling direct packet modification and forwarding without creating a new connection or proxy setup proved challenging, especially for maintaining TCP state and handling TLS traffic.
System Extension with NEFilterPacketProvider:
• I considered NEFilterPacketProvider for intercepting and modifying network packets.
• However, the documentation implies that packet filtering only supports allow/block actions, not modification and reinjection of packets back into the system.
I am planning to try NEPacketTunnelProvider: But the documentation states that this is not the right use case. Packets are expected to go into the tunnel. Since I don't have any requirement to create and maintain a tunnel, this doesn't look like an option for me.
Transparent proxy setups like NETransparentProxyProvider do not appear to offer direct packet modification capabilities without involving a user-space proxy approach.
Implementing packet-level interception outside of the Network Extension framework (e.g., Network Kernel Extension) seems unsupported in newer macOS versions (Sequoia and later).
My Questions:
Is there a recommended approach or combination of Network Extension capabilities that would allow intercepting and modifying IP packets directly?
Can NEFilterPacketProvider or any other extension be utilized in a way to modify and reinject packets back into the system?
Are there any examples or sample projects that achieve similar functionality, possibly using a blend of Network Extension and lower-level networking frameworks?
I appreciate any insights or pointers to documentation or examples that could help achieve this.
Thanks and Regards.
Prasanna.
Hello there!
We have an app that connects to an external device via Wi-Fi to send and query content from it. This external device generates a hidden AP that the phone connects against. However, sometimes the app fails to connect to the external device with the system alert "Unable to join the network...".
We have been debugging for a couple but couldn't find any clear reason of why this thing is happening. What could be the reason behind this alert appearing?
For the connection, we are using the NEHotspotConfigurationManager to connect to the AP of this external device.
The configuration for the connection is the following:
NEHotspotConfiguration(
ssid: ssid,
passphrase: password,
isWEP: false
)
configuration.hidden = true
There are some logs that we extracted that show two connections.
One happened at 20:37, which was a successful connection.
wifi_logs_success 2.log
Another connection was made at 20:38, which failed.
wifi_logs_failure.log
Inspecting the logs, one difference that I see between them is the __WiFiDeviceManagerDispatchUserForcedAssociationCallback: result %lld, which in the successful case is 0 and in the failed case is 1.
Can anyone help with this? We're very lost on why this configuration could be an issue at all.
Trying to flesh out an idea for an application which would rely on Endpoint Security Framework and Network Extension Framework, where intend the application to:
Forward certain ESF events to a backend (on a separate server)
Forward certain Unified logs to a backend (on a separate server)
Forwarding various DNS queries and responses (on a separate server)
Retrieve configuration from the backend to set Network Extension Filters
Are there any limitations and/or reasons not to bundle all this functionality into a single system extension?
I know of other applications where system extension is very thin and main application (daemon) communicates over xpc with the system extension, would this be considered best practice?
PLATFORM AND VERSION
macOS
Development environment: Xcode 15.0, macOS 15.0.1
Run-time configuration: macOS 15.0.1
DESCRIPTION OF PROBLEM
We are currently developing a macOS app using the NEFilterDataProvider in the Network Extension framework, and we've encountered an issue regarding hostname resolution that we would like your guidance on.
In our implementation, we need to drop network flows based on the hostname. The app successfully receives the remoteHostname or remoteEndpoint.hostname for browsers such as Safari and Mozilla Firefox. However, for other browsers like Chrome, Opera Mini, Arc, Brave, and Edge, we only receive the IP address instead of the hostname.
We are particularly looking for a way to retrieve the hostname for all browsers to apply our filtering logic consistently. Could you please advise whether there is any additional configuration or API we can use to ensure that we receive hostnames for these browsers as well? Alternatively, is this a limitation of the browsers themselves, and should we expect to only receive IP addresses for certain cases?
STEPS TO REPRODUCE
For Chrome, Brave, Edge, and Arc browsers you won't receive the hostname in NEFilterFlow.
Using the same sample project provided in WWDC 2019 https://developer.apple.com/documentation/networkextension/filtering_network_traffic
import NetworkExtension
import os.log
import Network
/**
The FilterDataProvider class handles connections that match the installed rules by prompting
the user to allow or deny the connections.
*/
class FilterDataProvider: NEFilterDataProvider {
// MARK: NEFilterDataProvider
override func startFilter(completionHandler: @escaping (Error?) -> Void) {
completionHandler(nil)
}
override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
completionHandler()
}
override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
guard let socketFlow = flow as? NEFilterSocketFlow,
let remoteEndpoint = socketFlow.remoteEndpoint as? NWHostEndpoint,
let localEndpoint = socketFlow.localEndpoint as? NWHostEndpoint else {
return .allow()
}
var hostName: String? = nil
// Attempt to use the URL host for native apps (e.g., Safari)
if let url = socketFlow.url {
hostName = url.host
os_log("URL-based Host: %@", hostName ?? "No host found")
}
// Fallback: Use remote hostname for third-party browsers like Chrome
if hostName == nil {
if #available(macOS 11.0, *), let remoteHostname = socketFlow.remoteHostname {
hostName = remoteHostname
os_log("Remote Hostname: %@", hostName ?? "No hostname found")
} else {
hostName = remoteEndpoint.hostname
os_log("IP-based Hostname: %@", hostName ?? "No hostname found")
}
}
let flowInfo = [
FlowInfoKey.localPort.rawValue: localEndpoint.port,
FlowInfoKey.remoteAddress.rawValue: remoteEndpoint.hostname,
FlowInfoKey.hostName.rawValue: hostName ?? "No host found"
]
// Ask the app to prompt the user
let prompted = IPCConnection.shared.promptUser(aboutFlow: flowInfo, rawFlow: flow) { allow in
let userVerdict: NEFilterNewFlowVerdict = allow ? .allow() : .drop()
self.resumeFlow(flow, with: userVerdict)
}
guard prompted else {
return .allow()
}
return .pause()
}
// Helper function to check if a string is an IP address
func isIPAddress(_ hostName: String) -> Bool {
var sin = sockaddr_in()
var sin6 = sockaddr_in6()
if hostName.withCString({ inet_pton(AF_INET, $0, &sin.sin_addr) }) == 1 {
return true
} else if hostName.withCString({ inet_pton(AF_INET6, $0, &sin6.sin6_addr) }) == 1 {
return true
}
return false
}
}
PLATFORM AND VERSION
iOS
Development environment: Xcode 16.0, macOS 15.0.1
Run-time configuration: iOS 17.5.1
DESCRIPTION OF PROBLEM
We are working on an iOS application that utilizes the NEFilterDataProvider class from the Network Extension framework to control network flows. However, we are encountering an issue where network flows are not being detected as expected.
Here are the details of our setup:
We are using the NEFilterDataProvider class to filter network traffic in our app.
The filtering setup works well for certain flows/apps, but we cannot detect Facebook network flows as intended.
The app is correctly configured with the necessary entitlements, and we have set up the required App Groups and Network Extension capabilities.
We would like to request guidance on how to troubleshoot or resolve this issue. Could you provide insights on:
Whether there are any known limitations or conditions under which network flows may not be detected by NEFilterDataProvider.
Recommendations for additional debugging techniques to better understand why some flows might not be captured.
Recommendations for additional code to be added to detect some flows that might not be captured.
Any specific scenarios or configurations that might be causing this issue in iOS.
STEPS TO CHECK
Replace below code in FilterDataProvider.
Try running the app and set debugger in FilterDataProvider.
Launch Facebook app.
You will observe that no NEFilterFlow is detected in handleNewFlow for actions such as posts, reels, etc.
import NetworkExtension
class FilterDataProvider: NEFilterDataProvider {
let blockedDomains = [
"facebook.com"
]
override func startFilter(completionHandler: @escaping (Error?) -> Void) {
// Perform any necessary setup here.
DNSLogger.shared.log(message: "Filter started")
completionHandler(nil)
}
override func stopFilter(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Perform any necessary cleanup here.
DNSLogger.shared.log(message: "Filter stopped with reason: \(reason)")
completionHandler()
}
override func handleNewFlow(_ flow: NEFilterFlow) -> NEFilterNewFlowVerdict {
var url: URL?
if let urlFlow = flow as? NEFilterBrowserFlow {
url = urlFlow.url
}
else {
let urlFlow = flow as? NEFilterSocketFlow
url = urlFlow?.url
}
guard let hostName = url?.host else { return .allow() }
DNSLogger.shared.log(message: "Domain reveived: \(hostName)")
return .allow()
}
// Handle inbound data (data received from the network)
override func handleInboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict {
DNSLogger.shared.log(message: "Inbound data: \(readBytes)")
return .needRules()
}
// Handle outbound data (data sent to the network)
override func handleOutboundData(from flow: NEFilterFlow, readBytesStartOffset offset: Int, readBytes: Data) -> NEFilterDataVerdict {
// Inspect or modify outbound data if needed
// For example, you could log the data or modify it before sending
DNSLogger.shared.log(message: "Outbound data: \(readBytes)")
return .needRules()
}
override func handleRemediation(for flow: NEFilterFlow) -> NEFilterRemediationVerdict {
return .needRules()
}
override func handleRulesChanged() {
// Handle any changes to the rules
}
}
Hi,
I'm troubleshooting an iOS network connectivity issue when my app is running 'in' a per-app VPN and would like some clarification about the ordering of some of the logging generated after installing various debugging profiles on the device (VPN (Network Extension), Network Diagnostics, mDNSResponder).
Context
The connectivity issue is between two vendors my app is involved with. One supplies an app proxy provider extension to provide per-app VPN capability for my app. The other vendor provides an SDK framework that's attempting to make network connections which normally work when the VPN is not involved. We have confirmed with the VPN vendor that it is not a configuration (whitelisting, etc) type issue.
I am trying to understand from the logs what component caused/initiated the network connection termination. Was it the kernel, was it the App Proxy Provider Network Extension code or was it the app (SDK framework) code ?
Log entries
I've attached a short log file and number the lines for reference, and have redacted a few commercially sensitive parts.
NetworkLogExcerpt.txt
Questions
Can this log help determine who caused the network connection failure, and if not, is there any more instrumentation I could enable that might help?
Do the log entries (and their timestamps) reflect the actual order/timing of events reported on, or is there some jumbling occurring due to my app, the kernel and iOSAppProxyProvider running in different processes/threads?
After the app initiates the network connection (line 1), it appears that the kernel flow diversion code in netinet/flow_divert.c establishes the flow and closes it (lines 2 - 6) before iOSAppProxyProvider even starts to establish the flow (lines 7 - 10).
Then the app somehow seems to detects a network error (line 8), before the iOSAppProxyProvider has even matched the VPN extension (line 12) to it and then finally the iOSAppProxyProvider closes the flow (lines 13-17).
I'd have expected an interleaving of kernel and iOSAppProxyProvider log entries, with the app's own logging just occurring at the start and end, bracketing the whole interaction...
I am new to this area of iOS, so apologies if I am missing some important foundational concepts about how these components all work together.
Thanks in advance,
Rob
We have network system extension which is fundamental part of our application and needs to be installed before the application can run.
In many cases we need the installation to be automated, i.e. without logged-in user (with the help of MDM solution like JAMF).
Is there a way to activate the extension fully automated without logged-in users?
I tried to call 'open -W -a /Application/' from the package's post install script. But seems launch fails if no user is logged in.
I try to mix content filter and endpoint security in one system extension, but get error below when the program invoke es_new_client(returned ES_NEW_CLIENTRESULT_ERR_INTERNAL).
Failed to open services: 0xe00002e2: Caller was denied connecting to the ES subsystem, possibly due to a sandbox violation.
how to solve this error while keeping two functionalities in one system extension?
or I have to seperate them?
I applied to Apple for authorization for the following page about 3 weeks ago, but have not received the results yet
https://developer.apple.com/documentation/networkextension/local_push_connectivity
Should I try to submit the application again with the same information or can I wait a little longer?
I would appreciate it if you could give me a little information about the same application or even another authority, such as the time it took to reply to that application.
Here is the actual page I applied for
https://developer.apple.com/contact/request/local-push-connectivity
Hello,
I am writing a NetworkExtension VPN using custom protocol and our client would like to able to use 5G network slice on the VPN, is this possible at all?
From Apple's documentation, I found the following statement:
If both network slicing and VPN are configured for an app or device, the VPN connection takes precedence over the network slice, rendering the network slice unused.
Is it possible to assign a network slice on a NetworkExtension-based VPN and let the VPN traffic uses the assign network slice?
Many thanks
System extensions on iOS have very low limits on allowed memory. For instance the DNS proxy extensions seem to be limited to 15MB. When I try to monitor the extension with instruments it quickly runs out of memory, most likely due to the way instruments tracks memory usage.
I did find that there are two entitlements related to memory usage but it is unclear if these would work for extensions or only for applications.
What are the best techniques for debugging extensions that run out of memory?
Is there a way to temporarily increase the limit while debugging?