I want to add network extension for my Mac OS application. When I add it it create entitlement file with default DNSProxyProvider class as I have selected category as DNS proxy. But I don't know how to use it. my requirement is I want to route all the traffic except some local domains that client will provide. local domains should resolve by default DNS which device has, and all the remaining public domains should be resolved by client server. This seems completely new to me in Mac OS development.
Post
Replies
Boosts
Views
Activity
Hi @eskimo, @meaton,
Im new to Mac Development, we are having our own custom DNS app with the use of NEDNSSetting, it just resolving all the traffic on device when we activate our own custom DNS option in System Setting->Network Preferences. Now we have to add DNSProxy into the app. But as per My research till now and after reading similar replies on developer forum, NEDNSSetting and NEDNSProxy service can not be run together or it's complex to run them together. So I have remove earlier DNSSetting implementation and added NEDNSProxy, Below is what I have done so far:
Custom Class I have created:
import Foundation
import SystemExtensions
import NetworkExtension
import os
@available(macOS 10.15, *)
public final class ConfigureProxyManager: NSObject {
public static let shared = ConfigureProxyManager()
public func installSystemExtension() {
let request = OSSystemExtensionRequest.activationRequest(
forExtensionWithIdentifier: "com.***.MacOS-DNSProxyNetworkExtension",
queue: .main
)
request.delegate = self
OSSystemExtensionManager.shared.submitRequest(request)
print("System Request has been submitted successfully")
}
public func startNetworkExtension() {
print("Enter in startNetworkExtension")
NEDNSProxyManager.shared().loadFromPreferences { error in
precondition(Thread.isMainThread)
if let nsError = error as NSError? {
/* Handle error */
return
}
let proto = NEDNSProxyProviderProtocol()
proto.serverAddress = "localhost" /* Here we have to pass custom DNS*/
proto.providerBundleIdentifier = "com.***.MacOS-DNSProxyNetworkExtension"
NEDNSProxyManager.shared().providerProtocol = proto
NEDNSProxyManager.shared().isEnabled = true
NEDNSProxyManager.shared().localizedDescription = "Custom DNS Proxy"
NEDNSProxyManager.shared().saveToPreferences { saveError in
if let nsError = saveError as NSError? {
/* Handle error */
return
}
/* Handle Success Case */
}
}
}
}
@available(macOS 10.15, *)
extension ConfigureProxyManager: OSSystemExtensionRequestDelegate {
public func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction {
return .replace
}
public func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
print("Enter in requestNeedsUserApproval")
}
public func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) {
print("Enter in didFailWithError")
}
/* Other delegate methods here */
public func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) {
switch result {
case .completed:
startNetworkExtension()
//manager = NEDNSProxyManager.shared()
case .willCompleteAfterReboot: break
/* Proceed with result here */
@unknown default: break
/* Proceed with result here */
}
}
}
Extension DNSProxyProvider
import NetworkExtension
import os
class DNSProxyProvider: NEDNSProxyProvider {
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DNSProxyProvider")
override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) { os_log("Entered in startProxy Method")
// Add code here to start the DNS proxy.
completionHandler(nil)
}
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
// Add code here to stop the DNS proxy.
completionHandler()
}
override func sleep(completionHandler: @escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
// Add code here to handle the incoming flow.
logger.log("Flow Enter in handlenewflow method")
return false
}
Extension Main File
import Foundation
import NetworkExtension
autoreleasepool {
NEProvider.startSystemExtensionMode()
}
dispatchMain()
Container app entitlement
<?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>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.hyas.protect.agent-spks</string>
<string>com.hyas.protect.agent-spki</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix)com.*** </string>
</array>
</dict>
</plist>
Extension entitlement
<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(TeamIdentifierPrefix)com.***.DNSProxyProvider</string>
</array>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-proxy</string>
</array>
</dict>
</plist>
**This is setup till now I have. **
I trying to debug my system extension its showing waiting for attach, due to that Im not getting call in handleNewFlow method of DNSProxyProvider. What’s wrong Im doing here?
Im working on project where I am want to use DNSProxy network extension to our app in iOS and Mac OS. We are able to add DNSProxy extension successfully and getting the call in startProxy and handleNewFlow Method of DNSProxyProvider in iOS. But I have big confusion on the steps and followed used to process the flow and pass it to datagrams and entire implementation.
Here is my confusion:
I have my own DNS resolver API which takes name of remoteHostName as query parameter and return the response in JSON.
First confusion is as per the DNSProxy Documentation every flow needs to processed through these steps, openLocaleEndpoints, ReadDataGrams, outboundCopier (Read in some developer threads), InboundCopiers and finally to WriteDataGrams. Am I correct here?
If above steps are correct, to process each flow then how can I use my own DNS API resolver in between these predefined steps. and How and where to use that response data sent my custom DNS API?
Currently when Im making DNSProxy active then immediately device internet connection getting interrupted, this might be because we are not hanging the flow properly in handleNewFlow Method. Am I correct here?
Perhaps this could be repetitive or basic question but I have implemented following code of NEDNSProxyProvider. My basic requirement is I want to process the flow but instead of using data from datagrams I want to use data received from our custom DNS server. After tons of articles documentation Im able to write following code. But it's failing continuously in writeDataGrams with "Invalid arguments data" and "The operation could not be completed because Flow not connected". I know somethings is wrong in processing the data but what is wrong Im not able to figure out. Also I want to know is this even possible to achieve this by using API call inside datagrams for loop and then send data to writedatagrams?
After getting JSONResponse Im using third party library to convert query form JSONData binary before sending it to writeDataGrams.
https://github.com/Bouke/DNS
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
NSLog("DNSProxyProvider: handleFlow")
var handled: Bool = false
if #available(iOSApplicationExtension 14.2, *) {
hostName = flow.remoteHostname!
}
if let udpFlow = flow as? NEAppProxyUDPFlow {
udpFlow.open(withLocalEndpoint: udpFlow.localEndpoint as? NWHostEndpoint) { error in
if error == nil {
self.flowOut(flow as! NEAppProxyUDPFlow)
} else {
NSLog("Error in opening Flow")
}
}
handled = true
} else {
handled = false
NSLog("Unsupported Flow")
}
return handled
}
/*
Read From flow, then write to remote endpoint.
*/
private func flowOut(_ flow: NEAppProxyUDPFlow) {
flow.readDatagrams(completionHandler: { (datagrams, endpoints, error) in
self.proxyUDPFlow = flow
if error != nil {
NSLog("ERROR: 'readDatagramsWithCompletionHandler' failed with: \(String(describing: error?.localizedDescription))")
return
}
if datagrams?.count == 0 {
flow.closeReadWithError(error)
flow.closeWriteWithError(error)
return
}
guard let dataArray = datagrams else { return }
if #available(iOSApplicationExtension 14.2, *) {
for (index, data) in dataArray.enumerated() {
var hostEndPoint: NWHostEndpoint = endpoints?[index] as! NWHostEndpoint
hostEndPoint = NWHostEndpoint(hostname: hostEndPoint.hostname, port: hostEndPoint.port)
guard let hostname = flow.remoteHostname else { return }
let dNSRequest = self.configureDNSRequest(hostname)
let urlsession = URLSession.shared.dataTask(with: dNSRequest) { data, response, error in
if let data = data {
do {
let reply = try JSONDecoder().decode(JSONReply.self, from: data)
let requestQuery = Message(
type: .response,
questions: [
Question(name: reply.questions[0].name, type: .pointer)
])
let requestData = try requestQuery.serialize()
self.flowIn(responsdata: requestData, flow, endpoint: hostEndPoint)
} catch let error {
print("error \(error)")
}
}
}
urlsession.resume()
}
}
})
}
private func flowIn(responsdata: Data, _ flow: NEAppProxyUDPFlow, endpoint: NWHostEndpoint) {
let resultData = Data(responsdata)
flow.writeDatagrams([resultData], sentBy: [endpoint], completionHandler: { error in
// Flow not connected
if error != nil {
os_log("Error in resolving query \(error)")
self.logger.log("error => \(error)")
} else {
self.proxyUDPFlow?.closeReadWithError(error)
self.proxyUDPFlow?.closeWriteWithError(error)
}
})
}
private func configureDNSRequest(_ hostName: String) -> URLRequest {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = “customserver.com"
urlComponents.path = “/resolverquery"
urlComponents.queryItems = [
URLQueryItem(name: "name", value: hostname),
URLQueryItem(name: "type", value: "A")
]
guard let url = urlComponents.url else { assert(false) }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue(“xyzabcd”, forHTTPHeaderField: "Client-ID")
request.setValue("*/*", forHTTPHeaderField: "Accept")
request.setValue("keep-alive", forHTTPHeaderField: "Connection")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
return request
}
}
extension Data {
func object<T>(at index: Index = 0) -> T {
subdata(in: index..<self.index(index, offsetBy: MemoryLayout<T>.size))
.withUnsafeBytes { $0.load(as: T.self) }
}
}
Hi,
We have an app of Mac Security Agent having DNSSettingManager Functionality implemented. Once this extension got added we have to enable it in order run it. But what I observed when machine has only our extension installed then its get enabled easily no issue. But when Falcom or other content filter, transparent proxy, DNS Proxy extensions are already present there as per this screen then Im not able to enable my extension in fact no click has been accepting complete blocked. So I have following questions:
Is this default behaviour of extensions?
As we can not control third party extension or other extension how can prioritise or apply our extension to system while other extension are also running?
Can we stop other extensions and prioritise our one?