Skip to content

Commit a9f4243

Browse files
PIA-1940: Make DNS Proxy use DnsFlowHandlerComponent
1 parent 3f41c30 commit a9f4243

File tree

4 files changed

+124
-32
lines changed

4 files changed

+124
-32
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Foundation
2+
import NetworkExtension
3+
import NIO
4+
5+
// Responsible for handling DNS flows, both new flows and pre-existing
6+
final class DnsFlowHandler: FlowHandlerProtocol {
7+
let eventLoopGroup: MultiThreadedEventLoopGroup
8+
var idGenerator: IDGenerator
9+
10+
// explicitly set these for tests
11+
var proxySessionFactory: ProxySessionFactory
12+
var networkInterfaceFactory: NetworkInterfaceFactory
13+
14+
init() {
15+
self.idGenerator = IDGenerator()
16+
self.proxySessionFactory = DefaultProxySessionFactory()
17+
self.networkInterfaceFactory = DefaultNetworkInterfaceFactory()
18+
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
19+
}
20+
21+
deinit {
22+
try! eventLoopGroup.syncShutdownGracefully()
23+
}
24+
25+
public func handleNewFlow(_ flow: Flow, vpnState: VpnState) -> Bool {
26+
// We need to handle two modes here:
27+
// - Follow App Rules
28+
// - VPN DNS Only
29+
// assume for now that Name Servers is set to Follow App Rules
30+
// if(name server is follow app rules)
31+
// if(app is bypass app)
32+
// proxy to physical
33+
// else
34+
// if(vpn is disconnected)
35+
// block
36+
// else
37+
// proxy to vpn //using current DNS settings
38+
return startProxySession(flow: flow, vpnState: vpnState)
39+
}
40+
41+
private func startProxySession(flow: Flow, vpnState: VpnState) -> Bool {
42+
let interface = networkInterfaceFactory.create(interfaceName: vpnState.bindInterface)
43+
44+
// Verify we have a valid bindIp - if not, trace it and ignore the flow
45+
guard let bindIp = interface.ip4() else {
46+
log(.error, "Cannot find ipv4 ip for interface: \(interface.interfaceName)" +
47+
" - ignoring matched flow: \(flow.sourceAppSigningIdentifier)")
48+
// TODO: Should block the flow instead - especially for vpnOnly flows?
49+
return false
50+
}
51+
52+
let sessionConfig = SessionConfig(bindIp: bindIp, eventLoopGroup: eventLoopGroup)
53+
54+
flow.openFlow { error in
55+
guard error == nil else {
56+
log(.error, "\(flow.sourceAppSigningIdentifier) \"\(error!.localizedDescription)\" in \(String(describing: flow.self)) open()")
57+
return
58+
}
59+
self.handleFlowIO(flow, sessionConfig: sessionConfig)
60+
}
61+
return true
62+
}
63+
64+
// Fire off a proxy session for each new flow
65+
func handleFlowIO(_ flow: Flow, sessionConfig: SessionConfig) {
66+
let nextId = idGenerator.generate()
67+
if let tcpFlow = flow as? FlowTCP {
68+
let tcpSession = proxySessionFactory.createTCP(flow: tcpFlow, config: sessionConfig, id: nextId)
69+
tcpSession.start()
70+
} else if let udpFlow = flow as? FlowUDP {
71+
let udpSession = proxySessionFactory.createUDP(flow: udpFlow, config: sessionConfig, id: nextId)
72+
udpSession.start()
73+
}
74+
}
75+
}

ProxyExtension/IO/FlowHandler.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ struct SessionConfig {
1414

1515
protocol FlowHandlerProtocol {
1616
func handleNewFlow(_ flow: Flow, vpnState: VpnState) -> Bool
17-
func startProxySession(flow: Flow, vpnState: VpnState) -> Bool
1817
}
1918

2019
// Responsible for handling flows, both new flows and pre-existing
@@ -51,8 +50,7 @@ final class FlowHandler: FlowHandlerProtocol {
5150
}
5251
}
5352

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

5856
// Verify we have a valid bindIp - if not, trace it and ignore the flow

ProxyExtension/SplitTunnelDNSProxyProvider/SplitTunnelDNSProxyProvider.swift

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,69 @@ import Foundation
22
import NetworkExtension
33

44
final class SplitTunnelDNSProxyProvider : NEDNSProxyProvider {
5-
6-
public var flowHandler: FlowHandlerProtocol!
7-
public var vpnState: VpnState!
8-
5+
6+
// The engine
7+
public var engine: ProxyEngineProtocol!
8+
99
// The logger
1010
public var logger: LoggerProtocol!
11-
12-
override func startProxy(options:[String: Any]? = nil, completionHandler: @escaping (Error?) -> Void) {
11+
12+
override func startProxy(options: [String: Any]? , completionHandler: @escaping (Error?) -> Void) {
1313
let logLevel: String = options?["logLevel"] as? String ?? ""
1414
let logFile: String = options?["logFile"] as? String ?? ""
1515

1616
self.logger = self.logger ?? Logger.instance
1717

18-
// Ensure the logger is initialized first
18+
// Ensure the logger is initialized first.
19+
// May be redundant
1920
logger.updateLogger(logLevel: logLevel, logFile: logFile)
20-
21-
// init just once, set up swiftNIO event loop
22-
self.flowHandler = FlowHandler()
23-
24-
var options = [
25-
"bypassApps" : ["/usr/bin/curl", "org.mozilla.firefox"],
21+
22+
// assume the option array is passed when the DNS proxy is started
23+
// or it's shared with the transparent proxy
24+
let _options = [
25+
"bypassApps" : ["com.apple.nslookup", "com.apple.curl", "com.apple.ping"],
2626
"vpnOnlyApps" : [],
2727
"bindInterface" : "en0",
2828
"serverAddress" : "127.0.0.1",
29+
// do we want to use the same log file or a different one?
2930
"logFile" : "/tmp/STProxy.log",
3031
"logLevel" : "debug",
3132
"routeVpn" : true,
3233
"isConnected" : true,
34+
"dnsFollowAppRules": true,
3335
"whitelistGroupName" : "piavpn"
3436
] as [String : Any]
35-
guard let vpnState2 = VpnStateFactory.create(options: options) else {
37+
guard let vpnState = VpnStateFactory.create(options: _options) else {
3638
log(.error, "provided incorrect list of options. They might be missing or an incorrect type")
3739
return
3840
}
39-
vpnState = vpnState2
40-
41-
completionHandler(nil)
42-
}
4341

44-
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
45-
completionHandler()
42+
// Right now we do not share the engine with the transparent proxy.
43+
// This means we will have 2 SwiftNIO event loops and that both proxies are indipendent of eachother.
44+
// This can be refactored later if the DNS proxy never runs when the transparent proxy is off
45+
self.engine = self.engine ?? ProxyEngine(vpnState: vpnState, flowHandler: DnsFlowHandler())
46+
47+
// Whitelist this process in the firewall - error logging happens in function.
48+
// May be redundant
49+
guard FirewallWhitelister(groupName: vpnState.whitelistGroupName).whitelist() else {
50+
return
51+
}
52+
53+
completionHandler(nil)
54+
log(.info, "DNS Proxy started!")
4655
}
4756

4857
// Be aware that by returning false in NEDNSProxyProvider handleNewFlow(),
4958
// the flow is discarded and the connection is closed.
5059
// This is similar to how NEAppProxyProvider works, compared to what we use
5160
// for traffic Split Tunnel which is NETransparentProxyProvider.
52-
// This means that we need to handle ALL DNS requests when DNS Split Tunnel
53-
// is enabled, even for non-managed apps.
61+
// This means that we need to handle the DNS requests of ALL apps
62+
// when DNS Split Tunnel is enabled
5463
override func handleNewFlow(_ flow: NEAppProxyFlow) -> Bool {
55-
var appName = flow.sourceAppSigningIdentifier
56-
if appName == "com.apple.nslookup" || appName == "com.apple.curl" {
57-
flowHandler.startProxySession(flow: flow, vpnState: vpnState)
58-
return true
59-
} else {
60-
return false
61-
}
64+
return engine.handleNewFlow(flow)
65+
}
66+
67+
override func stopProxy(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
68+
log(.info, "DNS Proxy stopped!")
6269
}
6370
}

SplitTunnelProxy.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
D19DF2782C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D19DF2772C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift */; };
115115
D1D447042ABCE52400A69316 /* ExtensionRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D447032ABCE52400A69316 /* ExtensionRequestDelegate.swift */; };
116116
D1D5B6B62B18F7DF0063363E /* ProcessUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D5B6B52B18F7DF0063363E /* ProcessUtilities.swift */; };
117+
D1D77BB62C21D8300065420C /* DnsFlowHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */; };
117118
D1E2637B2BBF4B06007EC69E /* LoggerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E2637A2BBF4B06007EC69E /* LoggerTest.swift */; };
118119
D1E9AEFE2AE31D4F007603BF /* NetworkInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E9AEFD2AE31D4F007603BF /* NetworkInterface.swift */; };
119120
/* End PBXBuildFile section */
@@ -260,6 +261,7 @@
260261
D19DF2772C0D274A00CE0FB3 /* SplitTunnelDNSProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTunnelDNSProxyProvider.swift; sourceTree = "<group>"; };
261262
D1D447032ABCE52400A69316 /* ExtensionRequestDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionRequestDelegate.swift; sourceTree = "<group>"; };
262263
D1D5B6B52B18F7DF0063363E /* ProcessUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessUtilities.swift; sourceTree = "<group>"; };
264+
D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DnsFlowHandler.swift; sourceTree = "<group>"; };
263265
D1E2637A2BBF4B06007EC69E /* LoggerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggerTest.swift; sourceTree = "<group>"; };
264266
D1E9AEFD2AE31D4F007603BF /* NetworkInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkInterface.swift; sourceTree = "<group>"; };
265267
/* End PBXFileReference section */
@@ -509,6 +511,7 @@
509511
D1D5B6B42B18F7200063363E /* IO */ = {
510512
isa = PBXGroup;
511513
children = (
514+
D1D77BB42C21D8060065420C /* DNS */,
512515
5278E5AE2B5AFB7600210466 /* ProxySession */,
513516
52D4C8CD2B52FBEB0059F7DD /* InboundHandler.swift */,
514517
527F56A72B3180D6003E7DF7 /* AppPolicy.swift */,
@@ -528,6 +531,14 @@
528531
path = IO;
529532
sourceTree = "<group>";
530533
};
534+
D1D77BB42C21D8060065420C /* DNS */ = {
535+
isa = PBXGroup;
536+
children = (
537+
D1D77BB52C21D8300065420C /* DnsFlowHandler.swift */,
538+
);
539+
path = DNS;
540+
sourceTree = "<group>";
541+
};
531542
D1FE4FB7295A1EA3002BBA20 /* ProxyTests */ = {
532543
isa = PBXGroup;
533544
children = (
@@ -878,6 +889,7 @@
878889
5274A5E52B4AAC4500FD0A10 /* ProxySessionUDP.swift in Sources */,
879890
976B8CD72B0B55F60097FE7F /* Logger.swift in Sources */,
880891
5235FB272B511DD700F3B772 /* NEAppProxyFlow+Extensions.swift in Sources */,
892+
D1D77BB62C21D8300065420C /* DnsFlowHandler.swift in Sources */,
881893
D1D5B6B62B18F7DF0063363E /* ProcessUtilities.swift in Sources */,
882894
D19C05F22B50A67500E804DE /* VpnState.swift in Sources */,
883895
5278E5B62B5B859600210466 /* InboundHandlerUDP.swift in Sources */,

0 commit comments

Comments
 (0)