Yep, I see logs from the main.swift
That is what I have added
func main() -> Never {
let logger = OSLog(subsystem: "proxy.main", category: "provider")
os_log(.debug, log: logger, "will start system extension mode")
autoreleasepool {
NEProvider.startSystemExtensionMode()
}
os_log(.debug, log: logger, "will dispatch main")
dispatchMain()
}
main()
Post
Replies
Boosts
Views
Activity
Ok, thank you!
Thank you a lot for a clear explanation! I have managed to build and test my project following the steps you have provided!
However, let's consider a situation when the Xcode can not generate a profile automatically, and a developer has to contact an Apple account admin to get a proper provisioning profile. Is there any way to test a system extension without getting the profile from the admin? For instance, would it work "normally" if the SIP is disabled?
So, App Store Connect keys helped us resolve an issue
Thank you!
Thank you for your response, @eskimo.
However, do you have any recommendations on where to store App Store Connect API private key?
We have changed a stored profile name, checked whether it is presented in the Keychain, and pushed CD pipelines several times with successful result. However, after some time it failed and continued delivering the same results with mentioned error. So, I make an assumption that notarytool has not enough rights to read something from the Keychain. Should we run it as a root process, or set an owner to root? The pipeline is running in the same user session as store-credentials executed.
Also, can anyone suggest the way how to check via Terminal whether profile has been added to keychain successfully?
Hi @eskimo!
Thank you, for your response and suggestions. I ran an additional step in CD pipeline with two commands security find-generic-password -l '<profile-name>' and
security find-generic-password -a ' com.apple.gke.notary.tool.com.saved-creds.<profile-name>'. The first one successfully found the entry in the keychain, however, the second one failed. On which result should I rely on?
One more question. Currently I have tried to log number of packets received by the tunnel provider using readPackets(). I have wrote the next code to achieve that
swift
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) - Void)
{
os_log(.default, "Tunnel is starting...")
self.pendingCompletion = completionHandler
self.startPacketHandling()
}
Where startPacketHandling() is a private method which configure settings with IP and mask of my local machine:
swift
private func startPacketHandling()
{
os_log(.default, "Start packet handling")
let settings = NEPacketTunnelNetworkSettings()
settings.ipv4Settings = NEIPv4Settings(addresses: ["ip of machine"], subnetMasks: ["255.255.255.0"] )
settings.ipv4Settings?.includedRoutes = [defaultIPv4Route]
setTunnelNetworkSettings(settings)
{ error in
self.pendingCompletion?(nil)
self.pendingCompletion = nil
self.readPackets();
}
}
In readPackets() I try to perform logging:
swift
private func readPackets()
{
os_log(.default, "Package reading started")
self.packetFlow.readPackets
{ packets, protocols in
os_log("Packets received from tun interface: %@", packets.count)
self.readPackets() // making a recursive call to be able to continue reading the packets
}
}
And now when I start the app with the extension I could see only logs till "Package reading started". But no messages about number of received packets. Hence self.packetFlow.readPackets() don't work. And I don't understand what am I doing wrong. Could I use NEIPv4Settings() configurations of the machine in my local network?
Thank you for reply, @meaton!
I have created container app for extension and currently it provide two prompts for user: first one to allow the system extension installation and second to allow access to VPN configuration.
But let's return to my first question about creating a split tunnel using NEPacketTunnelProvider. Besides WWDC session about Network Extension - https://developer.apple.com/videos/play/wwdc2019/714 I have watched also Session 717: What’s New in NetworkExtension and VPN - https://developer.apple.com/videos/play/wwdc2015/717/. In documentation I found that includeRoutes method from NEIPvSettings class may help me. As I understood, when outgoing ipv4 packets' destination adress matches with list that has been used with includeRoutes then they pass to TUN interface and to NEPacketTunnelProvider which redirects them to tunnel server. So the flow of outgoing packets could be described as:
When the packet destination matches with list item - redirect to TUN interface - redirect to NEPacketTunnelProvider - redirect to tunnel server. Otherwise packets simply going to original destination without passing to TUN interface.
Do I understand the packets flow in case of NEPacketTunnelProvider right?
@meaton
Thank you for quick response and detailed explanation!
Currently I am also struggled with launching extension that use Packet Tunnel Provider. I have been trying to make an app without GUI that just launch the extension. To check that tunnel started I want to log some message from startTunnel() and see it in Console:
swift
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) - Void)
{
os_log(.default, "Tunnel started successfuly")
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "some address")
setTunnelNetworkSettings(settings) { error in
completionHandler(error)
}
}
Currently when I launch the app, dialog appears with message which says that application trying to install system extension. After opening "Security and Privacy" option in System Preferences and allowing installation I can found in systemextensionctl list that the extension has status "activated enabled". But there is no messages in Console about tunnel start.
To achieve 'no GUI' goal I removed ViewController from project and modified AppDelegate::applicationDidFinishLaunching():
swift
func applicationDidFinishLaunching(_ aNotification: Notification)
{
guard let extensionIdentifier = self.extensionBundle.bundleIdentifier else
{
os_log(.default, "Problems with extension bundle ID")
return
}
os_log(.default, "SE activation request")
let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: extensionIdentifier, queue: .main)
activationRequest.delegate = self
OSSystemExtensionManager.shared.submitRequest(activationRequest)
}
To handle activation request result I have also added next thing:
swift
extension AppDelegate: OSSystemExtensionRequestDelegate
{
func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result)
{
guard result == .completed else
{
os_log(.default, "Unexpected result for SystemExtension activation request: %@", result.rawValue)
return
}
enableTunnelConfiguration()
}
}
enableTunnelConfiguration:
swift
func enableTunnelConfiguration()
{
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let loadError = error
{
os_log("Error while loading from preferences: %@", loadError.localizedDescription)
}
let managersList = managers ?? []
if managersList.count 0
{
self.tunnelManager = managersList.first!
}
else
{
self.tunnelManager = self.makeManager()
}
self.tunnelManager.saveToPreferences { error in
if let saveError = error
{
os_log("Error while saving to prefs: %@", saveError.localizedDescription)
return
}
os_log("Successfuly saved")
}
}
do
{
try self.tunnelManager.connection.startVPNTunnel()
}
catch
{
os_log("Failed to start tunnel")
}
}
and makeManager():
swift
private func makeManager() - NETunnelProviderManager {
let manager = NETunnelProviderManager()
manager.localizedDescription = "CustomVPN"
let proto = NETunnelProviderProtocol()
proto.providerBundleIdentifier = "extension bundle id"
proto.serverAddress = "same as address in startTunnel()"
proto.providerConfiguration = [:]
manager.protocolConfiguration = proto
manager.isEnabled = true
return manager
}
Also I'm getting log "Failed to start tunnel" after startVPNTunnel() call. Should I call this method to start tunnel and see desired message. Or extension is always launch automatically when app starts. Could it be issue with entitlements or code signing? Is it mandatory to use NETunnelManagerProvider to successfully launch extension? I save only one configuration in app, so managers list will contain only one element always? Would be glad if you provide explanation one more time. Sorry if my questions sounds dumb
Thank you for response, Matt! With your example I have achieved the goal.
Hello
I have been trying to do similar thing - filter outbound HTTP requests from Safari by URL. I am also using SimpleFirewall app provided as a sample for WWDC19 session about Network Extension. But when I try to get url of the flow originated from Safari always getting nil of string with address. I have modified only handleNewFlow
And here it is:
swift
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()
}
let blacklisted = "www.example.com"
os_log("Flow %@", flow)
if socketFlow.direction != .outbound
{
os_log("Allow non-outbound connections")
return .allow()
}
if socketFlow.url != nil
{
os_log("Host %@", socketFlow.url?.host as! CVarArg)
if blacklisted == socketFlow.url?.host
{
return .drop()
}
}
else
{
os_log("URL is nil")
}
os_log("Endpoint hostname %@", remoteEndpoint.hostname)
os_log("New flow with local endpoint %@, remote endpoint %@", localEndpoint, remoteEndpoint)
let flowInfo = [
FlowInfoKey.localPort.rawValue: localEndpoint.port,
FlowInfoKey.remoteAddress.rawValue: remoteEndpoint.hostname
]
let prompted = IPCConnection.shared.promptUser(aboutFlow: flowInfo) { allow in
let userVerdict: NEFilterNewFlowVerdict = allow ? .allow() : .drop()
self.resumeFlow(flow, with: userVerdict)
}
guard prompted else {
return .allow()
}
return .pause()
}
When I run a firewall and check log in console I always see "URL is nil" message when I try to open websites from Safari
Could you help to understand what am I doing wrong?