1717 * along with ProtonVPN. If not, see <https://www.gnu.org/licenses/>.
1818 */
1919
20- using System . Management . Automation ;
21- using System . Management . Automation . Runspaces ;
22- using Microsoft . PowerShell ;
20+ using Microsoft . Win32 ;
2321
2422namespace ProtonVPN . OperatingSystems . NRPT ;
2523
2624public static class StaticNrptInvoker
2725{
28- private const string NRPT_COMMENT = "Force all DNS requests via Proton VPN" ;
29- private const string NRPT_ADD_SCRIPT = "Add-DnsClientNrptRule -Namespace \" .\" -NameServers {0} -Comment \" " + NRPT_COMMENT + "\" " ;
30- private const string NRPT_REMOVE_SCRIPT = "Get-DnsClientNrptRule | Where-Object { $_.Comment -eq \" " + NRPT_COMMENT + "\" } | Remove-DnsClientNrptRule -Force" ;
31-
26+ private const string NRPT_COMMENT_KEY_NAME = "Comment" ;
27+ private const string NRPT_DISPLAY_NAME_KEY_NAME = "DisplayName" ;
28+
29+ private const string NRPT_COMMENT_VALUE = "Force all DNS requests via Proton VPN" ;
30+ private const string NRPT_DISPLAY_NAME_VALUE = "Proton VPN" ;
31+
32+ private const string NRPT_RULES_PATH = @"SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig" ;
33+
34+ private static readonly string [ ] _allDomains = [ "." ] ;
3235 private static readonly object _lock = new ( ) ;
3336
3437 /// <returns>If the NRPT rule was added successfully</returns>
35- public static bool CreateRule ( string nameServers , Action < string , Exception > onException = null ,
36- Action < string , List < ErrorRecord > > onError = null , Action < string > onSuccess = null )
38+ public static bool CreateRule ( string nameServers , Action < string , Exception > onException , Action < string > onError , Action < string > onSuccess )
3739 {
3840 try
3941 {
40- string script = string . Format ( NRPT_ADD_SCRIPT , nameServers ) ;
41- return ExecuteNrptPowershellCommand ( "Add" , ps => ps . AddScript ( script ) , onError , onSuccess ) ;
42+ lock ( _lock )
43+ {
44+ string ruleGuid = Guid . NewGuid ( ) . ToString ( ) . ToUpper ( ) ;
45+ string rulePath = $ "{ NRPT_RULES_PATH } \\ {{{ruleGuid}}}";
46+ using ( RegistryKey ruleKey = RegistryKey . OpenBaseKey ( RegistryHive . LocalMachine , RegistryView . Registry64 ) . CreateSubKey ( rulePath , writable : true ) )
47+ {
48+ if ( ruleKey == null )
49+ {
50+ onError ( $ "Failed to open or create NRPT registry path { NRPT_RULES_PATH } .") ;
51+ return false ;
52+ }
53+
54+ ruleKey . SetValue ( NRPT_COMMENT_KEY_NAME , NRPT_COMMENT_VALUE , RegistryValueKind . String ) ;
55+ ruleKey . SetValue ( "ConfigOptions" , 8 , RegistryValueKind . DWord ) ;
56+ ruleKey . SetValue ( NRPT_DISPLAY_NAME_KEY_NAME , NRPT_DISPLAY_NAME_VALUE , RegistryValueKind . String ) ;
57+ ruleKey . SetValue ( "GenericDNSServers" , nameServers , RegistryValueKind . String ) ;
58+ ruleKey . SetValue ( "IPSECCARestriction" , string . Empty , RegistryValueKind . String ) ;
59+ ruleKey . SetValue ( "Name" , _allDomains , RegistryValueKind . MultiString ) ;
60+ ruleKey . SetValue ( "Version" , 2 , RegistryValueKind . DWord ) ;
61+ return true ;
62+ }
63+ }
4264 }
4365 catch ( Exception ex )
4466 {
@@ -50,54 +72,74 @@ public static bool CreateRule(string nameServers, Action<string, Exception> onEx
5072 }
5173 }
5274
53- /// <returns>If the NRPT script was executed successfully</returns>
54- private static bool ExecuteNrptPowershellCommand ( string actionVerb , Func < PowerShell , PowerShell > value ,
55- Action < string , List < ErrorRecord > > onError = null , Action < string > onSuccess = null )
75+ /// <returns>If the NRPT rule was removed successfully</returns>
76+ public static bool DeleteRule ( Action < string , Exception > onException , Action < string > onSuccess )
5677 {
57- lock ( _lock )
78+ try
5879 {
59- InitialSessionState iss = InitialSessionState . CreateDefault ( ) ;
60- iss . ExecutionPolicy = ExecutionPolicy . Bypass ;
61-
62- using ( Runspace runspace = RunspaceFactory . CreateRunspace ( iss ) )
80+ lock ( _lock )
6381 {
64- runspace . Open ( ) ;
65- using ( PowerShell ps = PowerShell . Create ( runspace ) )
66- {
67- ps . AddCommand ( "Import-Module" ) . AddArgument ( "DnsClient" ) . Invoke ( ) ;
68- ps . Commands . Clear ( ) ;
69-
70- value ( ps ) . Invoke ( ) ;
82+ bool result = false ;
7183
72- if ( ps . HadErrors )
84+ using ( RegistryKey pathKey = RegistryKey . OpenBaseKey ( RegistryHive . LocalMachine , RegistryView . Registry64 ) . OpenSubKey ( NRPT_RULES_PATH , writable : true ) )
85+ {
86+ if ( pathKey == null )
7387 {
74- List < ErrorRecord > errors = ps . Streams . Error . ToList ( ) ;
75- if ( onError is not null )
76- {
77- onError ( $ "Error when performing NRPT rule '{ actionVerb } ' action.", errors ) ;
78- }
79- return false ;
88+ return false ; // NRPT path doesn't exist, nothing to do here
8089 }
81- else
90+
91+ string [ ] nrptRulesKeyNames = pathKey . GetSubKeyNames ( ) ;
92+
93+ foreach ( string nrptRuleKeyName in nrptRulesKeyNames )
8294 {
83- if ( onSuccess is not null )
84- {
85- onSuccess ( $ "Success when performing NRPT rule '{ actionVerb } ' action.") ;
86- }
87- return true ;
95+ result = CheckAndDeleteRule ( nrptRuleKeyName , pathKey , onException , onSuccess : onSuccess ) || result ;
8896 }
8997 }
98+
99+ return result ;
100+ }
101+ }
102+ catch ( Exception ex )
103+ {
104+ if ( onException is not null )
105+ {
106+ onException ( "Exception thrown when removing the NRPT rule" , ex ) ;
90107 }
108+ return false ;
91109 }
92110 }
93111
94- /// <returns>If the NRPT rule was removed successfully</returns>
95- public static bool DeleteRule ( Action < string , Exception > onException = null ,
96- Action < string , List < ErrorRecord > > onError = null , Action < string > onSuccess = null )
112+ /// <returns>Was the NRPT rule removed successfully (Failure doesn't mean anything wrong, might not be our rule) </returns>
113+ private static bool CheckAndDeleteRule ( string nrptRuleKeyName , RegistryKey pathKey ,
114+ Action < string , Exception > onException , Action < string > onSuccess )
97115 {
98116 try
99117 {
100- return ExecuteNrptPowershellCommand ( "Remove" , ps => ps . AddScript ( NRPT_REMOVE_SCRIPT ) , onError , onSuccess ) ;
118+ using ( RegistryKey nrptRuleKey = pathKey . OpenSubKey ( nrptRuleKeyName ) )
119+ {
120+ if ( nrptRuleKey == null )
121+ {
122+ return false ; // NRPT rule key name doesn't exist
123+ }
124+
125+ object displayNameObj = nrptRuleKey . GetValue ( NRPT_DISPLAY_NAME_KEY_NAME ) ;
126+ string displayName = displayNameObj ? . ToString ( ) ;
127+ if ( displayName is not null && displayName . Equals ( NRPT_DISPLAY_NAME_VALUE , StringComparison . InvariantCultureIgnoreCase ) )
128+ {
129+ DeleteKey ( pathKey , nrptRuleKeyName , onSuccess ) ;
130+ return true ;
131+ }
132+
133+ object commentObj = nrptRuleKey . GetValue ( NRPT_COMMENT_KEY_NAME ) ;
134+ string comment = commentObj ? . ToString ( ) ;
135+ if ( comment is not null && comment . Equals ( NRPT_COMMENT_VALUE , StringComparison . InvariantCultureIgnoreCase ) )
136+ {
137+ DeleteKey ( pathKey , nrptRuleKeyName , onSuccess ) ;
138+ return true ;
139+ }
140+ }
141+
142+ return false ; // This NRPT rule exists but is not ours, leave it as is
101143 }
102144 catch ( Exception ex )
103145 {
@@ -108,4 +150,10 @@ public static bool DeleteRule(Action<string, Exception> onException = null,
108150 return false ;
109151 }
110152 }
153+
154+ private static void DeleteKey ( RegistryKey pathKey , string nrptRuleKeyName , Action < string > onSuccess )
155+ {
156+ pathKey . DeleteSubKey ( nrptRuleKeyName ) ;
157+ onSuccess ( $ "Successfully deleted the NRPT rule '{ nrptRuleKeyName } '.") ;
158+ }
111159}
0 commit comments