@ -86,12 +86,8 @@ func newNRPTRuleDatabase(logf logger.Logf) *nrptRuleDatabase {
}
}
func ( db * nrptRuleDatabase ) loadRuleSubkeyNames ( ) {
func ( db * nrptRuleDatabase ) loadRuleSubkeyNames ( ) {
result := winutil . GetRegStrings ( nrptRuleIDValueName , nil )
if result == nil {
// Use the legacy rule ID if none are specified in our registry key
// Use the legacy rule ID if none are specified in our registry key
result = [ ] string { nrptSingleRuleID }
db . ruleIDs = winutil . GetRegStrings ( nrptRuleIDValueName , [ ] string { nrptSingleRuleID } )
}
db . ruleIDs = result
}
}
// detectWriteAsGP determines which registry path should be used for writing
// detectWriteAsGP determines which registry path should be used for writing
@ -113,41 +109,20 @@ func (db *nrptRuleDatabase) detectWriteAsGP() {
db . writeAsGP = writeAsGP
db . writeAsGP = writeAsGP
db . logf ( "nrptRuleDatabase using group policy: %v, was %v\n" , writeAsGP , prev )
db . logf ( "nrptRuleDatabase using group policy: %v, was %v\n" , writeAsGP , prev )
// When db.watcher == nil, prev != writeAsGP because we're initializing, not
// When db.watcher == nil, prev != writeAsGP because we're initializing, not
// because anything has changed. We do not invoke db.movePolicies in that case.
// because anything has changed. We do not invoke
// db.updateGroupPoliciesLocked in that case.
if db . watcher != nil && prev != writeAsGP {
if db . watcher != nil && prev != writeAsGP {
db . movePolicies ( writeAsGP )
db . updateGroupPoliciesLocked ( writeAsGP )
}
}
} ( )
} ( )
dnsKey , err := registry . OpenKey ( registry . LOCAL_MACHINE , dnsBaseGP , registry . READ )
if err != nil {
db . logf ( "Failed to open key %q with error: %v\n" , dnsBaseGP , err )
return
}
defer dnsKey . Close ( )
ki , err := dnsKey . Stat ( )
if err != nil {
db . logf ( "Failed to stat key %q with error: %v\n" , dnsBaseGP , err )
return
}
// If the dnsKey contains any values, then we need to use the GP key.
if ki . ValueCount > 0 {
writeAsGP = true
return
}
if ki . SubKeyCount == 0 {
// If dnsKey contains no values and no subkeys, then we definitely don't
// need to use the GP key.
return
}
// Get a list of all the NRPT rules under the GP subkey.
// Get a list of all the NRPT rules under the GP subkey.
nrptKey , err := registry . OpenKey ( registry . LOCAL_MACHINE , nrptBaseGP , registry . READ )
nrptKey , err := registry . OpenKey ( registry . LOCAL_MACHINE , nrptBaseGP , registry . READ )
if err != nil {
if err != nil {
if err != registry . ErrNotExist {
db . logf ( "Failed to open key %q with error: %v\n" , nrptBaseGP , err )
db . logf ( "Failed to open key %q with error: %v\n" , nrptBaseGP , err )
}
// If this subkey does not exist then we definitely don't need to use the GP key.
return
return
}
}
defer nrptKey . Close ( )
defer nrptKey . Close ( )
@ -253,14 +228,7 @@ func (db *nrptRuleDatabase) WriteSplitDNSConfig(servers []string, domains []dnsn
// NRPT has an undocumented restriction that each rule may only be associated
// NRPT has an undocumented restriction that each rule may only be associated
// with a maximum of 50 domains. If we are setting rules for more domains
// with a maximum of 50 domains. If we are setting rules for more domains
// than that, we need to split domains into chunks and write out a rule per chunk.
// than that, we need to split domains into chunks and write out a rule per chunk.
dq := len ( domains ) / nrptMaxDomainsPerRule
domainRulesLen := ( len ( domains ) + nrptMaxDomainsPerRule - 1 ) / nrptMaxDomainsPerRule
dr := len ( domains ) % nrptMaxDomainsPerRule
domainRulesLen := dq
if dr > 0 {
domainRulesLen ++
}
db . loadRuleSubkeyNames ( )
db . loadRuleSubkeyNames ( )
for len ( db . ruleIDs ) < domainRulesLen {
for len ( db . ruleIDs ) < domainRulesLen {
@ -348,28 +316,26 @@ func (db *nrptRuleDatabase) refreshLocked() {
}
}
func ( db * nrptRuleDatabase ) writeNRPTRule ( ruleID string , servers , doms [ ] string ) error {
func ( db * nrptRuleDatabase ) writeNRPTRule ( ruleID string , servers , doms [ ] string ) error {
var nrptBase string
subKeys := [ ] string { nrptBaseLocal , nrptBaseGP }
if db . writeAsGP {
if ! db . writeAsGP {
nrptBase = nrptBaseGP
// We don't want to write to the GP key, so chop nrptBaseGP off of subKeys.
} else {
subKeys = subKeys [ : 1 ]
nrptBase = nrptBaseLocal
}
}
keyStr := nrptBase + ` \ ` + ruleID
for _ , subKeyBase := range subKeys {
subKey := strings . Join ( [ ] string { subKeyBase , ruleID } , ` \ ` )
// CreateKey is actually open-or-create, which suits us fine.
key , _ , err := registry . CreateKey ( registry . LOCAL_MACHINE , subKey , registry . SET_VALUE )
key , _ , err := registry . CreateKey ( registry . LOCAL_MACHINE , keyStr , registry . SET_VALUE )
if err != nil {
if err != nil {
return fmt . Errorf ( "opening % s: %w", keyStr , err )
return fmt . Errorf ( "opening % q: %w", subKey , err )
}
}
defer key . Close ( )
defer key . Close ( )
if err := writeNRPTValues ( key , strings . Join ( servers , "; " ) , doms ) ; err != nil {
if err := writeNRPTValues ( key , strings . Join ( servers , "; " ) , doms ) ; err != nil {
return err
return err
}
}
}
db . isGPDirty = db . writeAsGP
db . isGPDirty = db . writeAsGP
return nil
return nil
}
}
@ -400,8 +366,6 @@ func writeNRPTValues(key registry.Key, servers string, doms []string) error {
}
}
func ( db * nrptRuleDatabase ) watchForGPChanges ( ) {
func ( db * nrptRuleDatabase ) watchForGPChanges ( ) {
db . isGPRefreshPending . Store ( false )
watchHandler := func ( ) {
watchHandler := func ( ) {
// Do not invoke detectWriteAsGP when we ourselves were responsible for
// Do not invoke detectWriteAsGP when we ourselves were responsible for
// initiating the group policy refresh.
// initiating the group policy refresh.
@ -420,35 +384,30 @@ func (db *nrptRuleDatabase) watchForGPChanges() {
db . watcher = watcher
db . watcher = watcher
}
}
// movePolicies moves each NRPT rule depending on the value of writeAsGP.
// updateGroupPoliciesLocked updates the NRPT group policy table depending on
// When writeAsGP is true, each NRPT rule is moved from the local NRPT table
// the value of writeAsGP. When writeAsGP is true, each NRPT rule is copied from
// to the group policy NRPT table. When writeAsGP is false, the move is
// the local NRPT table to the group policy NRPT table. When writeAsGP is false,
// executed in the opposite direction. db.mu should already be locked.
// we remove any Tailscale NRPT rules from the group policy table and, if no
func ( db * nrptRuleDatabase ) movePolicies ( writeAsGP bool ) {
// non-Tailscale rules remain, we also delete the entire DnsPolicyConfig subkey.
// Since we're moving either in or out of the group policy NRPT table, we need
// db.mu must already be locked.
// to refresh once this movePolicies is done.
func ( db * nrptRuleDatabase ) updateGroupPoliciesLocked ( writeAsGP bool ) {
// Since we're updating the group policy NRPT table, we need
// to refresh once this updateGroupPoliciesLocked is done.
defer db . refreshLocked ( )
defer db . refreshLocked ( )
var fromBase string
for _ , id := range db . ruleIDs {
var toBase string
if writeAsGP {
if writeAsGP {
fromBase = nrptBaseLocal
if err := copyNRPTRule ( id ) ; err != nil {
toBase = nrptBaseGP
db . logf ( "updateGroupPoliciesLocked: copyNRPTRule(%q) failed with error %v" , id , err )
} else {
return
fromBase = nrptBaseGP
toBase = nrptBaseLocal
}
}
fromBase += ` \ `
} else {
toBase += ` \ `
subKeyFrom := strings . Join ( [ ] string { nrptBaseGP , id } , ` \ ` )
if err := registry . DeleteKey ( registry . LOCAL_MACHINE , subKeyFrom ) ; err != nil && err != registry . ErrNotExist {
for _ , id := range db . ruleIDs {
db . logf ( "updateGroupPoliciesLocked: DeleteKey for rule %q failed with error %v" , id , err )
fromStr := fromBase + id
toStr := toBase + id
if err := executeMove ( fromStr , toStr ) ; err != nil {
db . logf ( "movePolicies: executeMove(\"%s\", \"%s\") failed with error %v" , fromStr , toStr , err )
return
return
}
}
}
db . isGPDirty = true
db . isGPDirty = true
}
}
@ -457,30 +416,31 @@ func (db *nrptRuleDatabase) movePolicies(writeAsGP bool) {
return
return
}
}
// Now that we have moved our rules out of the group policy subkey, it should
// Now that we have removed our rules from group policy subkey, it should
// now be empty. Let's verify that.
// now be empty. Let's verify that.
isEmpty , err := isPolicyConfigSubkeyEmpty ( )
isEmpty , err := isPolicyConfigSubkeyEmpty ( )
if err != nil {
if err != nil {
db . logf ( " movePolicies : isPolicyConfigSubkeyEmpty error %v", err )
db . logf ( " updateGroupPoliciesLocked : isPolicyConfigSubkeyEmpty error %v", err )
return
return
}
}
if ! isEmpty {
if ! isEmpty {
db . logf ( " movePolicies : policy config subkey should be empty, but isn't!")
db . logf ( " updateGroupPoliciesLocked : policy config subkey should be empty, but isn't!")
return
return
}
}
// Delete the subkey itself. Group policy will continue to override local
// Delete the subkey itself. Group policy will continue to override local
// settings unless we do so.
// settings unless we do so.
if err := registry . DeleteKey ( registry . LOCAL_MACHINE , nrptBaseGP ) ; err != nil {
if err := registry . DeleteKey ( registry . LOCAL_MACHINE , nrptBaseGP ) ; err != nil {
db . logf ( " movePolicies DeleteKey error %v", err )
db . logf ( " updateGroupPoliciesLocked DeleteKey error %v", err )
}
}
db . isGPDirty = true
db . isGPDirty = true
}
}
func executeMove ( subKeyFrom , subKeyTo string ) error {
func copyNRPTRule ( ruleID string ) error {
err := func ( ) error {
subKeyFrom := strings . Join ( [ ] string { nrptBaseLocal , ruleID } , ` \ ` )
// Move the NRPT registry values from subKeyFrom to subKeyTo.
subKeyTo := strings . Join ( [ ] string { nrptBaseGP , ruleID } , ` \ ` )
fromKey , err := registry . OpenKey ( registry . LOCAL_MACHINE , subKeyFrom , registry . QUERY_VALUE )
fromKey , err := registry . OpenKey ( registry . LOCAL_MACHINE , subKeyFrom , registry . QUERY_VALUE )
if err != nil {
if err != nil {
return err
return err
@ -499,13 +459,6 @@ func executeMove(subKeyFrom, subKeyTo string) error {
}
}
return writeNRPTValues ( toKey , servers , doms )
return writeNRPTValues ( toKey , servers , doms )
} ( )
if err != nil {
return err
}
// This is a move operation, so we must delete subKeyFrom.
return registry . DeleteKey ( registry . LOCAL_MACHINE , subKeyFrom )
}
}
func ( db * nrptRuleDatabase ) Close ( ) error {
func ( db * nrptRuleDatabase ) Close ( ) error {