Skip to content

Commit

Permalink
PIA-1940: Make DNS Proxy use DnsFlowHandlerComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
kp-michele-emiliani committed Jun 18, 2024
1 parent 3f41c30 commit a9f4243
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 32 deletions.
75 changes: 75 additions & 0 deletions ProxyExtension/IO/DNS/DnsFlowHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Foundation
import NetworkExtension
import NIO

// Responsible for handling DNS flows, both new flows and pre-existing
final class DnsFlowHandler: FlowHandlerProtocol {
let eventLoopGroup: MultiThreadedEventLoopGroup
var idGenerator: IDGenerator

// explicitly set these for tests
var proxySessionFactory: ProxySessionFactory
var networkInterfaceFactory: NetworkInterfaceFactory

init() {
self.idGenerator = IDGenerator()
self.proxySessionFactory = DefaultProxySessionFactory()
self.networkInterfaceFactory = DefaultNetworkInterfaceFactory()
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
}

deinit {
try! eventLoopGroup.syncShutdownGracefully()
}

public func handleNewFlow(_ flow: Flow, vpnState: VpnState) -> Bool {
// We need to handle two modes here:
// - Follow App Rules
// - VPN DNS Only
// assume for now that Name Servers is set to Follow App Rules
// if(name server is follow app rules)
// if(app is bypass app)
// proxy to physical
// else
// if(vpn is disconnected)
// block
// else
// proxy to vpn //using current DNS settings
return startProxySession(flow: flow, vpnState: vpnState)
}

private func startProxySession(flow: Flow, vpnState: VpnState) -> Bool {
let interface = networkInterfaceFactory.create(interfaceName: vpnState.bindInterface)

// Verify we have a valid bindIp - if not, trace it and ignore the flow
guard let bindIp = interface.ip4() else {
log(.error, "Cannot find ipv4 ip for interface: \(interface.interfaceName)" +
" - ignoring matched flow: \(flow.sourceAppSigningIdentifier)")
// TODO: Should block the flow instead - especially for vpnOnly flows?
return false
}

let sessionConfig = SessionConfig(bindIp: bindIp, eventLoopGroup: eventLoopGroup)

flow.openFlow { error in
guard error == nil else {
log(.error, "\(flow.sourceAppSigningIdentifier) \"\(error!.localizedDescription)\" in \(String(describing: flow.self)) open()")
return
}
self.handleFlowIO(flow, sessionConfig: sessionConfig)
}
return true
}

// Fire off a proxy session for each new flow
func handleFlowIO(_ flow: Flow, sessionConfig: SessionConfig) {
let nextId = idGenerator.generate()
if let tcpFlow = flow as? FlowTCP {
let tcpSession = proxySessionFactory.createTCP(flow: tcpFlow, config: sessionConfig, id: nextId)
tcpSession.start()
} else if let udpFlow = flow as? FlowUDP {
let udpSession = proxySessionFactory.createUDP(flow: udpFlow, config: sessionConfig, id: nextId)
udpSession.start()
}
}
}
4 changes: 1 addition & 3 deletions ProxyExtension/IO/FlowHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ struct SessionConfig {

protocol FlowHandlerProtocol {
func handleNewFlow(_ flow: Flow, vpnState: VpnState) -> Bool
func startProxySession(flow: Flow, vpnState: VpnState) -> Bool
}

// Responsible for handling flows, both new flows and pre-existing
Expand Down Expand Up @@ -51,8 +50,7 @@ final class FlowHandler: FlowHandlerProtocol {
}
}

// temporarly public
public func startProxySession(flow: Flow, vpnState: VpnState) -> Bool {
private func startProxySession(flow: Flow, vpnState: VpnState) -> Bool {
let interface = networkInterfaceFactory.create(interfaceName: vpnState.bindInterface)

// Verify we have a valid bindIp - if not, trace it and ignore the flow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,69 @@ import Foundation
import NetworkExtension

final class SplitTunnelDNSProxyProvider : NEDNSProxyProvider {
public var flowHandler: FlowHandlerProtocol!
public var vpnState: VpnState!

// The engine
public var engine: ProxyEngineProtocol!

// The logger
public var logger: LoggerProtocol!
override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) {

override func startProxy(options: [String: Any]? , completionHandler: @escaping (Error?) -> Void) {
let logLevel: String = options?["logLevel"] as? String ?? ""
let logFile: String = options?["logFile"] as? String ?? ""

self.logger = self.logger ?? Logger.instance

// Ensure the logger is initialized first
// Ensure the logger is initialized first.
// May be redundant
logger.updateLogger(logLevel: logLevel, logFile: logFile)

// init just once, set up swiftNIO event loop
self.flowHandler = FlowHandler()

var options = [
"bypassApps" : ["/usr/bin/curl", "org.mozilla.firefox"],

// assume the option array is passed when the DNS proxy is started
// or it's shared with the transparent proxy
let _options = [
"bypassApps" : ["com.apple.nslookup", "com.apple.curl", "com.apple.ping"],
"vpnOnlyApps" : [],
"bindInterface" : "en0",
"serverAddress" : "127.0.0.1",
// do we want to use the same log file or a different one?
"logFile" : "/tmp/STProxy.log",
"logLevel" : "debug",
"routeVpn" : true,
"isConnected" : true,
"dnsFollowAppRules": true,
"whitelistGroupName" : "piavpn"
] as [String : Any]
guard let vpnState2 = VpnStateFactory.create(options: options) else {
guard let vpnState = VpnStateFactory.create(options: _options) else {
log(.error, "provided incorrect list of options. They might be missing or an incorrect type")
return
}
vpnState = vpnState2

completionHandler(nil)
}

override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
completionHandler()
// Right now we do not share the engine with the transparent proxy.
// This means we will have 2 SwiftNIO event loops and that both proxies are indipendent of eachother.
// This can be refactored later if the DNS proxy never runs when the transparent proxy is off
self.engine = self.engine ?? ProxyEngine(vpnState: vpnState, flowHandler: DnsFlowHandler())

// Whitelist this process in the firewall - error logging happens in function.
// May be redundant
guard FirewallWhitelister(groupName: vpnState.whitelistGroupName).whitelist() else {
return
}

completionHandler(nil)
log(.info, "DNS Proxy started!")
}

// Be aware that by returning false in NEDNSProxyProvider handleNewFlow(),
// the flow is discarded and the connection is closed.
// This is similar to how NEAppProxyProvider works, compared to what we use
// for traffic Split Tunnel which is NETransparentProxyProvider.
// This means that we need to handle ALL DNS requests when DNS Split Tunnel
// is enabled, even for non-managed apps.
// This means that we need to handle the DNS requests of ALL apps
// when DNS Split Tunnel is enabled
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
var appName = flow.sourceAppSigningIdentifier
if appName == "com.apple.nslookup" || appName == "com.apple.curl" {
flowHandler.startProxySession(flow: flow, vpnState: vpnState)
return true
} else {
return false
}
return engine.handleNewFlow(flow)
}

override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
log(.info, "DNS Proxy stopped!")
}
}
12 changes: 12 additions & 0 deletions SplitTunnelProxy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
D19DF2782C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19DF2772C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift */; };
D1D447042ABCE52400A69316 /* ExtensionRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D447032ABCE52400A69316 /* ExtensionRequestDelegate.swift */; };
D1D5B6B62B18F7DF0063363E /* ProcessUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D5B6B52B18F7DF0063363E /* ProcessUtilities.swift */; };
D1D77BB62C21D8300065420C /* DnsFlowHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */; };
D1E2637B2BBF4B06007EC69E /* LoggerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E2637A2BBF4B06007EC69E /* LoggerTest.swift */; };
D1E9AEFE2AE31D4F007603BF /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E9AEFD2AE31D4F007603BF /* NetworkInterface.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -260,6 +261,7 @@
D19DF2772C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTunnelDNSProxyProvider.swift; sourceTree = "<group>"; };
D1D447032ABCE52400A69316 /* ExtensionRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionRequestDelegate.swift; sourceTree = "<group>"; };
D1D5B6B52B18F7DF0063363E /* ProcessUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessUtilities.swift; sourceTree = "<group>"; };
D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DnsFlowHandler.swift; sourceTree = "<group>"; };
D1E2637A2BBF4B06007EC69E /* LoggerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerTest.swift; sourceTree = "<group>"; };
D1E9AEFD2AE31D4F007603BF /* NetworkInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterface.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -509,6 +511,7 @@
D1D5B6B42B18F7200063363E /* IO */ = {
isa = PBXGroup;
children = (
D1D77BB42C21D8060065420C /* DNS */,
5278E5AE2B5AFB7600210466 /* ProxySession */,
52D4C8CD2B52FBEB0059F7DD /* InboundHandler.swift */,
527F56A72B3180D6003E7DF7 /* AppPolicy.swift */,
Expand All @@ -528,6 +531,14 @@
path = IO;
sourceTree = "<group>";
};
D1D77BB42C21D8060065420C /* DNS */ = {
isa = PBXGroup;
children = (
D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */,
);
path = DNS;
sourceTree = "<group>";
};
D1FE4FB7295A1EA3002BBA20 /* ProxyTests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -878,6 +889,7 @@
5274A5E52B4AAC4500FD0A10 /* ProxySessionUDP.swift in Sources */,
976B8CD72B0B55F60097FE7F /* Logger.swift in Sources */,
5235FB272B511DD700F3B772 /* NEAppProxyFlow+Extensions.swift in Sources */,
D1D77BB62C21D8300065420C /* DnsFlowHandler.swift in Sources */,
D1D5B6B62B18F7DF0063363E /* ProcessUtilities.swift in Sources */,
D19C05F22B50A67500E804DE /* VpnState.swift in Sources */,
5278E5B62B5B859600210466 /* InboundHandlerUDP.swift in Sources */,
Expand Down

0 comments on commit a9f4243

Please sign in to comment.