Network extensions provide VPN, content filtering, DNS proxy, and app proxy capabilities to your app.
Network extension types
Expo Apple Targets supports all four network extension types:
Packet Tunnel VPN and custom network protocols
App Proxy Per-app VPN and network filtering
DNS Proxy Custom DNS resolution
Filter Data Content filtering and parental controls
Network extensions require the Network Extensions entitlement from Apple. You must request this capability from Apple Developer Support before using these extension types.
Packet Tunnel Provider
Creates VPN tunnels and custom network protocols.
Extension point
Property Value Type network-packet-tunnelExtension Point com.apple.networkextension.packet-tunnelFrameworks NetworkExtensionEmbedded Swift Yes
Creating a packet tunnel extension
npx create-target network-packet-tunnel
Implementation
import NetworkExtension
import os
class PacketTunnelProvider : NEPacketTunnelProvider {
override func startTunnel ( options : [ String : NSObject] ? , completionHandler : @escaping ( Error ? ) -> Void ) {
// Configure tunnel settings
let tunnelNetworkSettings = NEPacketTunnelNetworkSettings ( tunnelRemoteAddress : "10.0.0.1" )
// Set IPv4 settings
let ipv4Settings = NEIPv4Settings (
addresses : [ "10.0.0.2" ],
subnetMasks : [ "255.255.255.0" ]
)
ipv4Settings. includedRoutes = [NEIPv4Route. default ()]
tunnelNetworkSettings. ipv4Settings = ipv4Settings
// Set DNS settings
let dnsSettings = NEDNSSettings ( servers : [ "8.8.8.8" , "8.8.4.4" ])
tunnelNetworkSettings. dnsSettings = dnsSettings
// Apply settings
setTunnelNetworkSettings (tunnelNetworkSettings) { error in
if let error = error {
completionHandler (error)
return
}
// Start reading packets
self . startReadingPackets ()
completionHandler ( nil )
}
}
override func stopTunnel ( with reason : NEProviderStopReason, completionHandler : @escaping () -> Void ) {
// Clean up tunnel
completionHandler ()
}
private func startReadingPackets () {
packetFlow. readPackets { packets, protocols in
// Process incoming packets
for (index, packet) in packets. enumerated () {
let protocolNumber = protocols[index]
// Handle packet based on protocol (IPv4, IPv6, etc.)
self . handlePacket (packet, protocol : protocolNumber)
}
// Continue reading
self . startReadingPackets ()
}
}
private func handlePacket ( _ packet : Data, protocol protocolNumber : NSNumber) {
// Process and forward packet
// Send outgoing packets with:
// packetFlow.writePackets([packet], withProtocols: [protocolNumber])
}
}
Starting the VPN
From your React Native app (requires native module):
import NetworkExtension
func startVPN () {
let manager = NETunnelProviderManager ()
manager. loadFromPreferences { error in
if error != nil {
// Create new configuration
let proto = NETunnelProviderProtocol ()
proto. providerBundleIdentifier = "com.myapp.network-packet-tunnel"
proto. serverAddress = "VPN Server"
manager. protocolConfiguration = proto
manager. localizedDescription = "My VPN"
manager. isEnabled = true
manager. saveToPreferences { error in
if error == nil {
try ? manager. connection . startVPNTunnel ()
}
}
} else {
// Start existing VPN
try ? manager. connection . startVPNTunnel ()
}
}
}
App Proxy Provider
App-level VPN that only affects specific apps.
Extension point
Property Value Type network-app-proxyExtension Point com.apple.networkextension.app-proxyFrameworks NetworkExtensionEmbedded Swift Yes
npx create-target network-app-proxy
Implementation is similar to packet tunnel but uses NEAppProxyProvider base class.
DNS Proxy Provider
Custom DNS resolution for all network traffic.
Extension point
Property Value Type network-dns-proxyExtension Point com.apple.networkextension.dns-proxyFrameworks NetworkExtensionEmbedded Swift Yes
npx create-target network-dns-proxy
Implementation
import NetworkExtension
class DNSProxyProvider : NEDNSProxyProvider {
override func startProxy ( options : [ String : Any ] ? , completionHandler : @escaping ( Error ? ) -> Void ) {
// Configure DNS proxy
let settings = NEDNSProxyProviderSettings ()
settings. dnsServers = [ "8.8.8.8" , "1.1.1.1" ]
setProxyConfiguration (settings) { error in
completionHandler (error)
}
}
override func stopProxy ( with reason : NEProviderStopReason, completionHandler : @escaping () -> Void ) {
completionHandler ()
}
override func handleNewFlow ( _ flow : NEAppProxyFlow) -> Bool {
if let udpFlow = flow as? NEAppProxyUDPFlow {
// Handle DNS query
handleDNSQuery (udpFlow)
return true
}
return false
}
private func handleDNSQuery ( _ flow : NEAppProxyUDPFlow) {
flow. readDatagrams { datagrams, endpoints, error in
guard let query = datagrams ? . first else { return }
// Parse DNS query
// Resolve query (custom logic or forward to upstream DNS)
// Write response
let response = self . resolveDNS (query)
flow. writeDatagrams ([response], sentBy : endpoints ?? []) { error in
// Continue reading
self . handleDNSQuery (flow)
}
}
}
private func resolveDNS ( _ query : Data) -> Data {
// Implement DNS resolution logic
return query // Placeholder
}
}
Filter Data Provider
Content filtering and parental controls.
Extension point
Property Value Type network-filter-dataExtension Point com.apple.networkextension.filter-dataFrameworks NetworkExtensionEmbedded Swift Yes
npx create-target network-filter-data
Implementation
import NetworkExtension
class FilterDataProvider : NEFilterDataProvider {
override func startFilter ( completionHandler : @escaping ( Error ? ) -> Void ) {
// Initialize filter
completionHandler ( nil )
}
override func stopFilter ( with reason : NEProviderStopReason, completionHandler : @escaping () -> Void ) {
completionHandler ()
}
override func handleNewFlow ( _ flow : NEFilterFlow) -> NEFilterNewFlowVerdict {
guard let socketFlow = flow as? NEFilterSocketFlow else {
return . allow ()
}
// Check if URL should be blocked
if let url = socketFlow. url ? .absoluteString {
if shouldBlock (url) {
return . drop ()
}
}
// Allow and monitor data
return . filterDataVerdict (
withFilterInbound : true ,
peekInboundBytes : 1024 ,
filterOutbound : true ,
peekOutboundBytes : 1024
)
}
override func handleInboundData ( from flow : NEFilterFlow, readBytesStartOffset offset : Int , readBytes : Data) -> NEFilterDataVerdict {
// Inspect inbound data
if containsBlockedContent (readBytes) {
return . drop ()
}
return . allow ()
}
override func handleOutboundData ( from flow : NEFilterFlow, readBytesStartOffset offset : Int , readBytes : Data) -> NEFilterDataVerdict {
// Inspect outbound data
return . allow ()
}
private func shouldBlock ( _ url : String ) -> Bool {
// Implement blocking logic
let blocklist = [ "example.com" , "blocked.com" ]
return blocklist. contains ( where : { url. contains ( $0 ) })
}
private func containsBlockedContent ( _ data : Data) -> Bool {
// Check for blocked keywords or patterns
return false
}
}
Required entitlements
All network extensions require special entitlements:
< key > com.apple.developer.networking.networkextension </ key >
< array >
< string > packet-tunnel-provider </ string >
<!-- or app-proxy, dns-proxy, content-filter-provider -->
</ array >
You must request the Network Extensions capability from Apple Developer Support. This is not automatically granted.
Testing network extensions
Build the extension
Build your network extension target in Xcode.
Install on device
Network extensions must be tested on a real device - they don’t work in the simulator.
Trust the VPN configuration
When starting the VPN for the first time, you’ll need to allow the VPN configuration in Settings.
Monitor system logs
Use Console.app to view network extension logs.
Best practices
Always call completion handlers
Log errors for debugging
Implement proper error recovery
Test network failure scenarios
Validate all external data
Use secure connections for VPN servers
Implement proper authentication
Don’t log sensitive information
Learn more
Entitlements Configure network entitlements
Target config Configure network extensions
Complete target list All extension types
Apple documentation Official NetworkExtension documentation