2015-06-05 01:59:23 +03:00
# define the KCC object
2015-06-05 01:49:30 +03:00
#
# Copyright (C) Dave Craft 2011
# Copyright (C) Andrew Bartlett 2015
#
# Andrew Bartlett's alleged work performed by his underlings Douglas
# Bagnall and Garming Sam.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import random
import uuid
import itertools
2015-06-10 06:39:40 +03:00
from samba import unix2nttime , nttime2unix
from samba import ldb , dsdb , drs_utils
2015-06-05 01:49:30 +03:00
from samba . auth import system_session
from samba . samdb import SamDB
2015-06-10 06:39:40 +03:00
from samba . dcerpc import drsuapi , misc
2015-06-10 07:42:37 +03:00
from samba . kcc . kcc_utils import Site , Partition , Transport , SiteLink
from samba . kcc . kcc_utils import NCReplica , NCType , nctype_lut , GraphNode
from samba . kcc . kcc_utils import RepsFromTo , KCCError , KCCFailedObject
from samba . kcc . kcc_utils import convert_schedule_to_repltimes
2015-06-10 06:39:40 +03:00
from samba . ndr import ndr_pack
2015-06-04 02:16:15 +03:00
from samba . kcc . graph_utils import verify_and_dot
2015-06-05 01:49:30 +03:00
2015-06-05 06:09:05 +03:00
from samba . kcc import ldif_import_export
2015-06-10 07:42:54 +03:00
from samba . kcc . graph import setup_graph , get_spanning_tree_edges
2015-06-10 07:42:37 +03:00
from samba . kcc . graph import Vertex
2015-06-04 02:16:15 +03:00
from samba . kcc . debug import DEBUG , DEBUG_FN , logger
from samba . kcc import debug
2015-06-10 06:35:05 +03:00
def sort_replica_by_dsa_guid ( rep1 , rep2 ) :
""" Helper to sort NCReplicas by their DSA guids
The guids need to be sorted in their NDR form .
: param rep1 : An NC replica
: param rep2 : Another replica
: return : - 1 , 0 , or 1 , indicating sort order .
"""
return cmp ( ndr_pack ( rep1 . rep_dsa_guid ) , ndr_pack ( rep2 . rep_dsa_guid ) )
def sort_dsa_by_gc_and_guid ( dsa1 , dsa2 ) :
""" Helper to sort DSAs by guid global catalog status
GC DSAs come before non - GC DSAs , other than that , the guids are
sorted in NDR form .
: param dsa1 : A DSA object
: param dsa2 : Another DSA
: return : - 1 , 0 , or 1 , indicating sort order .
"""
if dsa1 . is_gc ( ) and not dsa2 . is_gc ( ) :
return - 1
if not dsa1 . is_gc ( ) and dsa2 . is_gc ( ) :
return + 1
return cmp ( ndr_pack ( dsa1 . dsa_guid ) , ndr_pack ( dsa2 . dsa_guid ) )
def is_smtp_replication_available ( ) :
""" Can the KCC use SMTP replication?
Currently always returns false because Samba doesn ' t implement
SMTP transfer for NC changes between DCs .
: return : Boolean ( always False )
"""
return False
2015-06-05 01:49:30 +03:00
class KCC ( object ) :
""" The Knowledge Consistency Checker class.
A container for objects and methods allowing a run of the KCC . Produces a
set of connections in the samdb for which the Distributed Replication
Service can then utilize to replicate naming contexts
: param unix_now : The putative current time in seconds since 1970.
: param read_only : Don ' t write to the database.
: param verify : Check topological invariants for the generated graphs
: param debug : Write verbosely to stderr .
" param dot_files: write Graphviz files in /tmp showing topology
"""
2015-06-05 01:59:23 +03:00
def __init__ ( self , unix_now , readonly = False , attempt_live_connections = True ,
verify = False , debug = False , dot_files = False ) :
2015-06-05 01:49:30 +03:00
""" Initializes the partitions class which can hold
our local DCs partitions or all the partitions in
the forest
"""
self . part_table = { } # partition objects
self . site_table = { }
self . transport_table = { }
self . ip_transport = None
self . sitelink_table = { }
self . dsa_by_dnstr = { }
self . dsa_by_guid = { }
self . get_dsa_by_guidstr = self . dsa_by_guid . get
self . get_dsa = self . dsa_by_dnstr . get
# TODO: These should be backed by a 'permanent' store so that when
# calling DRSGetReplInfo with DS_REPL_INFO_KCC_DSA_CONNECT_FAILURES,
# the failure information can be returned
self . kcc_failed_links = { }
self . kcc_failed_connections = set ( )
# Used in inter-site topology computation. A list
# of connections (by NTDSConnection object) that are
# to be kept when pruning un-needed NTDS Connections
self . kept_connections = set ( )
self . my_dsa_dnstr = None # My dsa DN
self . my_dsa = None # My dsa object
self . my_site_dnstr = None
self . my_site = None
self . samdb = None
2015-06-05 01:59:23 +03:00
self . unix_now = unix_now
self . nt_now = unix2nttime ( unix_now )
self . readonly = readonly
self . attempt_live_connections = attempt_live_connections
self . verify = verify
self . debug = debug
self . dot_files = dot_files
2015-06-05 01:49:30 +03:00
def load_all_transports ( self ) :
""" Loads the inter-site transport objects for Sites
: return : None
: raise KCCError : if no IP transport is found
"""
try :
res = self . samdb . search ( " CN=Inter-Site Transports,CN=Sites, %s " %
self . samdb . get_config_basedn ( ) ,
scope = ldb . SCOPE_SUBTREE ,
expression = " (objectClass=interSiteTransport) " )
except ldb . LdbError , ( enum , estr ) :
raise KCCError ( " Unable to find inter-site transports - ( %s ) " %
estr )
for msg in res :
dnstr = str ( msg . dn )
transport = Transport ( dnstr )
transport . load_transport ( self . samdb )
self . transport_table . setdefault ( str ( transport . guid ) ,
transport )
if transport . name == ' IP ' :
self . ip_transport = transport
if self . ip_transport is None :
raise KCCError ( " there doesn ' t seem to be an IP transport " )
def load_all_sitelinks ( self ) :
""" Loads the inter-site siteLink objects
: return : None
: raise KCCError : if site - links aren ' t found
"""
try :
res = self . samdb . search ( " CN=Inter-Site Transports,CN=Sites, %s " %
self . samdb . get_config_basedn ( ) ,
scope = ldb . SCOPE_SUBTREE ,
expression = " (objectClass=siteLink) " )
except ldb . LdbError , ( enum , estr ) :
raise KCCError ( " Unable to find inter-site siteLinks - ( %s ) " % estr )
for msg in res :
dnstr = str ( msg . dn )
# already loaded
if dnstr in self . sitelink_table :
continue
sitelink = SiteLink ( dnstr )
sitelink . load_sitelink ( self . samdb )
# Assign this siteLink to table
# and index by dn
self . sitelink_table [ dnstr ] = sitelink
def load_site ( self , dn_str ) :
""" Helper for load_my_site and load_all_sites.
Put all the site ' s DSAs into the KCC indices.
: param dn_str : a site dn_str
: return : the Site object pertaining to the dn_str
"""
2015-06-05 01:59:23 +03:00
site = Site ( dn_str , self . unix_now )
2015-06-05 01:49:30 +03:00
site . load_site ( self . samdb )
# We avoid replacing the site with an identical copy in case
# somewhere else has a reference to the old one, which would
# lead to all manner of confusion and chaos.
guid = str ( site . site_guid )
if guid not in self . site_table :
self . site_table [ guid ] = site
self . dsa_by_dnstr . update ( site . dsa_table )
self . dsa_by_guid . update ( ( str ( x . dsa_guid ) , x )
for x in site . dsa_table . values ( ) )
return self . site_table [ guid ]
def load_my_site ( self ) :
""" Load the Site object for the local DSA.
: return : None
"""
self . my_site_dnstr = ( " CN= %s ,CN=Sites, %s " % (
self . samdb . server_site_name ( ) ,
self . samdb . get_config_basedn ( ) ) )
self . my_site = self . load_site ( self . my_site_dnstr )
def load_all_sites ( self ) :
""" Discover all sites and create Site objects.
: return : None
: raise : KCCError if sites can ' t be found
"""
try :
res = self . samdb . search ( " CN=Sites, %s " %
self . samdb . get_config_basedn ( ) ,
scope = ldb . SCOPE_SUBTREE ,
expression = " (objectClass=site) " )
except ldb . LdbError , ( enum , estr ) :
raise KCCError ( " Unable to find sites - ( %s ) " % estr )
for msg in res :
sitestr = str ( msg . dn )
self . load_site ( sitestr )
def load_my_dsa ( self ) :
""" Discover my nTDSDSA dn thru the rootDSE entry
: return : None
: raise : KCCError if DSA can ' t be found
"""
dn = ldb . Dn ( self . samdb , " <GUID= %s > " % self . samdb . get_ntds_GUID ( ) )
try :
res = self . samdb . search ( base = dn , scope = ldb . SCOPE_BASE ,
attrs = [ " objectGUID " ] )
except ldb . LdbError , ( enum , estr ) :
logger . warning ( " Search for %s failed: %s . This typically happens "
" in --importldif mode due to lack of module "
" support. " , dn , estr )
try :
# We work around the failure above by looking at the
# dsServiceName that was put in the fake rootdse by
# the --exportldif, rather than the
# samdb.get_ntds_GUID(). The disadvantage is that this
# mode requires we modify the @ROOTDSE dnq to support
# --forced-local-dsa
service_name_res = self . samdb . search ( base = " " ,
scope = ldb . SCOPE_BASE ,
attrs = [ " dsServiceName " ] )
dn = ldb . Dn ( self . samdb ,
service_name_res [ 0 ] [ " dsServiceName " ] [ 0 ] )
res = self . samdb . search ( base = dn , scope = ldb . SCOPE_BASE ,
attrs = [ " objectGUID " ] )
except ldb . LdbError , ( enum , estr ) :
raise KCCError ( " Unable to find my nTDSDSA - ( %s ) " % estr )
if len ( res ) != 1 :
raise KCCError ( " Unable to find my nTDSDSA at %s " %
dn . extended_str ( ) )
ntds_guid = misc . GUID ( self . samdb . get_ntds_GUID ( ) )
if misc . GUID ( res [ 0 ] [ " objectGUID " ] [ 0 ] ) != ntds_guid :
raise KCCError ( " Did not find the GUID we expected, "
" perhaps due to --importldif " )
self . my_dsa_dnstr = str ( res [ 0 ] . dn )
self . my_dsa = self . my_site . get_dsa ( self . my_dsa_dnstr )
if self . my_dsa_dnstr not in self . dsa_by_dnstr :
2015-06-05 01:59:23 +03:00
debug . DEBUG_DARK_YELLOW ( " my_dsa %s isn ' t in self.dsas_by_dnstr: "
" it must be RODC. \n "
" Let ' s add it, because my_dsa is special! "
" \n (likewise for self.dsa_by_guid) " %
self . my_dsas_dnstr )
2015-06-05 01:49:30 +03:00
self . dsa_by_dnstr [ self . my_dsa_dnstr ] = self . my_dsa
self . dsa_by_guid [ str ( self . my_dsa . dsa_guid ) ] = self . my_dsa
def load_all_partitions ( self ) :
""" Discover and load all partitions.
Each NC is inserted into the part_table by partition
dn string ( not the nCName dn string )
: return : None
: raise : KCCError if partitions can ' t be found
"""
try :
res = self . samdb . search ( " CN=Partitions, %s " %
self . samdb . get_config_basedn ( ) ,
scope = ldb . SCOPE_SUBTREE ,
expression = " (objectClass=crossRef) " )
except ldb . LdbError , ( enum , estr ) :
raise KCCError ( " Unable to find partitions - ( %s ) " % estr )
for msg in res :
partstr = str ( msg . dn )
# already loaded
if partstr in self . part_table :
continue
part = Partition ( partstr )
part . load_partition ( self . samdb )
self . part_table [ partstr ] = part
def should_be_present_test ( self ) :
""" Enumerate all loaded partitions and DSAs in local
site and test if NC should be present as replica
"""
for partdn , part in self . part_table . items ( ) :
for dsadn , dsa in self . my_site . dsa_table . items ( ) :
needed , ro , partial = part . should_be_present ( dsa )
logger . info ( " dsadn: %s \n ncdn: %s \n needed= %s :ro= %s :partial= %s \n " %
( dsadn , part . nc_dnstr , needed , ro , partial ) )
def refresh_failed_links_connections ( self ) :
2015-05-06 09:02:34 +03:00
""" Ensure the failed links list is up to date
2015-06-05 01:49:30 +03:00
2015-05-06 09:02:34 +03:00
Based on MS - ADTS 6.2 .2 .1
"""
2015-06-05 01:49:30 +03:00
# LINKS: Refresh failed links
self . kcc_failed_links = { }
current , needed = self . my_dsa . get_rep_tables ( )
for replica in current . values ( ) :
# For every possible connection to replicate
for reps_from in replica . rep_repsFrom :
failure_count = reps_from . consecutive_sync_failures
if failure_count < = 0 :
continue
dsa_guid = str ( reps_from . source_dsa_obj_guid )
time_first_failure = reps_from . last_success
last_result = reps_from . last_attempt
dns_name = reps_from . dns_name1
f = self . kcc_failed_links . get ( dsa_guid )
if not f :
f = KCCFailedObject ( dsa_guid , failure_count ,
time_first_failure , last_result ,
dns_name )
self . kcc_failed_links [ dsa_guid ] = f
#elif f.failure_count == 0:
# f.failure_count = failure_count
# f.time_first_failure = time_first_failure
# f.last_result = last_result
else :
f . failure_count = max ( f . failure_count , failure_count )
f . time_first_failure = min ( f . time_first_failure ,
time_first_failure )
f . last_result = last_result
# CONNECTIONS: Refresh failed connections
restore_connections = set ( )
2015-06-05 01:59:23 +03:00
if self . attempt_live_connections :
2015-06-05 01:49:30 +03:00
DEBUG ( " refresh_failed_links: checking if links are still down " )
for connection in self . kcc_failed_connections :
try :
drs_utils . drsuapi_connect ( connection . dns_name , lp , creds )
# Failed connection is no longer failing
restore_connections . add ( connection )
except drs_utils . drsException :
# Failed connection still failing
connection . failure_count + = 1
else :
DEBUG ( " refresh_failed_links: not checking live links because we \n "
" weren ' t asked to --attempt-live-connections " )
# Remove the restored connections from the failed connections
self . kcc_failed_connections . difference_update ( restore_connections )
def is_stale_link_connection ( self , target_dsa ) :
""" Check whether a link to a remote DSA is stale
Used in MS - ADTS 6.2 .2 .2 Intrasite Connection Creation
Returns True if the remote seems to have been down for at
least two hours , otherwise False .
: param target_dsa : the remote DSA object
: return : True if link is stale , otherwise False
"""
failed_link = self . kcc_failed_links . get ( str ( target_dsa . dsa_guid ) )
if failed_link :
# failure_count should be > 0, but check anyways
if failed_link . failure_count > 0 :
unix_first_failure = \
nttime2unix ( failed_link . time_first_failure )
# TODO guard against future
2015-06-05 01:59:23 +03:00
if unix_first_failure > self . unix_now :
2015-06-05 01:49:30 +03:00
logger . error ( " The last success time attribute for \
repsFrom is in the future ! " )
# Perform calculation in seconds
2015-06-05 01:59:23 +03:00
if ( self . unix_now - unix_first_failure ) > 60 * 60 * 2 :
2015-06-05 01:49:30 +03:00
return True
# TODO connections
return False
# TODO: This should be backed by some form of local database
def remove_unneeded_failed_links_connections ( self ) :
# Remove all tuples in kcc_failed_links where failure count = 0
# In this implementation, this should never happen.
# Remove all connections which were not used this run or connections
# that became active during this run.
pass
def remove_unneeded_ntdsconn ( self , all_connected ) :
""" Remove unneeded NTDS Connections once topology is calculated
Based on MS - ADTS 6.2 .2 .4 Removing Unnecessary Connections
: param all_connected : indicates whether all sites are connected
: return : None
"""
mydsa = self . my_dsa
# New connections won't have GUIDs which are needed for
# sorting. Add them.
for cn_conn in mydsa . connect_table . values ( ) :
if cn_conn . guid is None :
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
cn_conn . guid = misc . GUID ( str ( uuid . uuid4 ( ) ) )
2015-06-05 01:59:23 +03:00
cn_conn . whenCreated = self . nt_now
2015-06-05 01:49:30 +03:00
else :
cn_conn . load_connection ( self . samdb )
for cn_conn in mydsa . connect_table . values ( ) :
s_dnstr = cn_conn . get_from_dnstr ( )
if s_dnstr is None :
cn_conn . to_be_deleted = True
continue
# Get the source DSA no matter what site
# XXX s_dsa is NEVER USED. It will be removed.
s_dsa = self . get_dsa ( s_dnstr )
#XXX should an RODC be regarded as same site
same_site = s_dnstr in self . my_site . dsa_table
# Given an nTDSConnection object cn, if the DC with the
# nTDSDSA object dc that is the parent object of cn and
# the DC with the nTDSDA object referenced by cn!fromServer
# are in the same site, the KCC on dc deletes cn if all of
# the following are true:
#
# Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
#
# No site settings object s exists for the local DC's site, or
# bit NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED is clear in
# s!options.
#
# Another nTDSConnection object cn2 exists such that cn and
# cn2 have the same parent object, cn!fromServer = cn2!fromServer,
# and either
#
# cn!whenCreated < cn2!whenCreated
#
# cn!whenCreated = cn2!whenCreated and
# cn!objectGUID < cn2!objectGUID
#
# Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
if same_site :
if not cn_conn . is_generated ( ) :
continue
if self . my_site . is_cleanup_ntdsconn_disabled ( ) :
continue
# Loop thru connections looking for a duplicate that
# fulfills the previous criteria
lesser = False
packed_guid = ndr_pack ( cn_conn . guid )
for cn2_conn in mydsa . connect_table . values ( ) :
if cn2_conn is cn_conn :
continue
s2_dnstr = cn2_conn . get_from_dnstr ( )
# If the NTDS Connections has a different
# fromServer field then no match
if s2_dnstr != s_dnstr :
continue
#XXX GUID comparison
lesser = ( cn_conn . whenCreated < cn2_conn . whenCreated or
( cn_conn . whenCreated == cn2_conn . whenCreated and
packed_guid < ndr_pack ( cn2_conn . guid ) ) )
if lesser :
break
if lesser and not cn_conn . is_rodc_topology ( ) :
cn_conn . to_be_deleted = True
# Given an nTDSConnection object cn, if the DC with the nTDSDSA
# object dc that is the parent object of cn and the DC with
# the nTDSDSA object referenced by cn!fromServer are in
# different sites, a KCC acting as an ISTG in dc's site
# deletes cn if all of the following are true:
#
# Bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options.
#
# cn!fromServer references an nTDSDSA object for a DC
# in a site other than the local DC's site.
#
# The keepConnections sequence returned by
# CreateIntersiteConnections() does not contain
# cn!objectGUID, or cn is "superseded by" (see below)
# another nTDSConnection cn2 and keepConnections
# contains cn2!objectGUID.
#
# The return value of CreateIntersiteConnections()
# was true.
#
# Bit NTDSCONN_OPT_RODC_TOPOLOGY is clear in
# cn!options
#
else : # different site
if not mydsa . is_istg ( ) :
continue
if not cn_conn . is_generated ( ) :
continue
# TODO
# We are directly using this connection in intersite or
# we are using a connection which can supersede this one.
#
# MS-ADTS 6.2.2.4 - Removing Unnecessary Connections does not
# appear to be correct.
#
# 1. cn!fromServer and cn!parent appear inconsistent with
# no cn2
# 2. The repsFrom do not imply each other
#
if cn_conn in self . kept_connections : # and not_superceded:
continue
# This is the result of create_intersite_connections
if not all_connected :
continue
if not cn_conn . is_rodc_topology ( ) :
cn_conn . to_be_deleted = True
2015-06-05 01:59:23 +03:00
if mydsa . is_ro ( ) or self . readonly :
2015-06-05 01:49:30 +03:00
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_deleted :
DEBUG_FN ( " TO BE DELETED: \n %s " % connect )
if connect . to_be_added :
DEBUG_FN ( " TO BE ADDED: \n %s " % connect )
# Peform deletion from our tables but perform
# no database modification
mydsa . commit_connections ( self . samdb , ro = True )
else :
# Commit any modified connections
mydsa . commit_connections ( self . samdb )
def modify_repsFrom ( self , n_rep , t_repsFrom , s_rep , s_dsa , cn_conn ) :
""" Update an repsFrom object if required.
Part of MS - ADTS 6.2 .2 .5 .
Update t_repsFrom if necessary to satisfy requirements . Such
updates are typically required when the IDL_DRSGetNCChanges
server has moved from one site to another - - for example , to
enable compression when the server is moved from the
client ' s site to another site.
The repsFrom . update_flags bit field may be modified
auto - magically if any changes are made here . See
kcc_utils . RepsFromTo for gory details .
: param n_rep : NC replica we need
: param t_repsFrom : repsFrom tuple to modify
: param s_rep : NC replica at source DSA
: param s_dsa : source DSA
: param cn_conn : Local DSA NTDSConnection child
: return : None
"""
s_dnstr = s_dsa . dsa_dnstr
update = 0x0
same_site = s_dnstr in self . my_site . dsa_table
# if schedule doesn't match then update and modify
times = convert_schedule_to_repltimes ( cn_conn . schedule )
if times != t_repsFrom . schedule :
t_repsFrom . schedule = times
update | = drsuapi . DRSUAPI_DRS_UPDATE_SCHEDULE
# Bit DRS_PER_SYNC is set in replicaFlags if and only
# if nTDSConnection schedule has a value v that specifies
# scheduled replication is to be performed at least once
# per week.
if cn_conn . is_schedule_minimum_once_per_week ( ) :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_PER_SYNC ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_PER_SYNC
# Bit DRS_INIT_SYNC is set in t.replicaFlags if and only
# if the source DSA and the local DC's nTDSDSA object are
# in the same site or source dsa is the FSMO role owner
# of one or more FSMO roles in the NC replica.
if same_site or n_rep . is_fsmo_role_owner ( s_dnstr ) :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_INIT_SYNC ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_INIT_SYNC
# If bit NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT is set in
# cn!options, bit DRS_NEVER_NOTIFY is set in t.replicaFlags
# if and only if bit NTDSCONN_OPT_USE_NOTIFY is clear in
# cn!options. Otherwise, bit DRS_NEVER_NOTIFY is set in
# t.replicaFlags if and only if s and the local DC's
# nTDSDSA object are in different sites.
if ( ( cn_conn . options &
dsdb . NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT ) != 0x0 ) :
if ( cn_conn . options & dsdb . NTDSCONN_OPT_USE_NOTIFY ) == 0x0 :
# XXX WARNING
#
# it LOOKS as if this next test is a bit silly: it
# checks the flag then sets it if it not set; the same
# effect could be achieved by unconditionally setting
# it. But in fact the repsFrom object has special
# magic attached to it, and altering replica_flags has
# side-effects. That is bad in my opinion, but there
# you go.
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_NEVER_NOTIFY ) == 0x0 ) :
t_repsFrom . replica_flags | = \
drsuapi . DRSUAPI_DRS_NEVER_NOTIFY
elif not same_site :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_NEVER_NOTIFY ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_NEVER_NOTIFY
# Bit DRS_USE_COMPRESSION is set in t.replicaFlags if
# and only if s and the local DC's nTDSDSA object are
# not in the same site and the
# NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION bit is
# clear in cn!options
if ( not same_site and
( cn_conn . options &
dsdb . NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION ) == 0x0 ) :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_USE_COMPRESSION ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_USE_COMPRESSION
# Bit DRS_TWOWAY_SYNC is set in t.replicaFlags if and only
# if bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options.
if ( cn_conn . options & dsdb . NTDSCONN_OPT_TWOWAY_SYNC ) != 0x0 :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_TWOWAY_SYNC ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_TWOWAY_SYNC
# Bits DRS_DISABLE_AUTO_SYNC and DRS_DISABLE_PERIODIC_SYNC are
# set in t.replicaFlags if and only if cn!enabledConnection = false.
if not cn_conn . is_enabled ( ) :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_DISABLE_AUTO_SYNC ) == 0x0 ) :
t_repsFrom . replica_flags | = \
drsuapi . DRSUAPI_DRS_DISABLE_AUTO_SYNC
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_DISABLE_PERIODIC_SYNC ) == 0x0 ) :
t_repsFrom . replica_flags | = \
drsuapi . DRSUAPI_DRS_DISABLE_PERIODIC_SYNC
# If s and the local DC's nTDSDSA object are in the same site,
# cn!transportType has no value, or the RDN of cn!transportType
# is CN=IP:
#
# Bit DRS_MAIL_REP in t.replicaFlags is clear.
#
# t.uuidTransport = NULL GUID.
#
# t.uuidDsa = The GUID-based DNS name of s.
#
# Otherwise:
#
# Bit DRS_MAIL_REP in t.replicaFlags is set.
#
# If x is the object with dsname cn!transportType,
# t.uuidTransport = x!objectGUID.
#
# Let a be the attribute identified by
# x!transportAddressAttribute. If a is
# the dNSHostName attribute, t.uuidDsa = the GUID-based
# DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
#
# It appears that the first statement i.e.
#
# "If s and the local DC's nTDSDSA object are in the same
# site, cn!transportType has no value, or the RDN of
# cn!transportType is CN=IP:"
#
# could be a slightly tighter statement if it had an "or"
# between each condition. I believe this should
# be interpreted as:
#
# IF (same-site) OR (no-value) OR (type-ip)
#
# because IP should be the primary transport mechanism
# (even in inter-site) and the absense of the transportType
# attribute should always imply IP no matter if its multi-site
#
# NOTE MS-TECH INCORRECT:
#
# All indications point to these statements above being
# incorrectly stated:
#
# t.uuidDsa = The GUID-based DNS name of s.
#
# Let a be the attribute identified by
# x!transportAddressAttribute. If a is
# the dNSHostName attribute, t.uuidDsa = the GUID-based
# DNS name of s. Otherwise, t.uuidDsa = (s!parent)!a.
#
# because the uuidDSA is a GUID and not a GUID-base DNS
# name. Nor can uuidDsa hold (s!parent)!a if not
# dNSHostName. What should have been said is:
#
# t.naDsa = The GUID-based DNS name of s
#
# That would also be correct if transportAddressAttribute
# were "mailAddress" because (naDsa) can also correctly
# hold the SMTP ISM service address.
#
nastr = " %s ._msdcs. %s " % ( s_dsa . dsa_guid , self . samdb . forest_dns_name ( ) )
# We're not currently supporting SMTP replication
# so is_smtp_replication_available() is currently
# always returning False
if ( ( same_site or
cn_conn . transport_dnstr is None or
cn_conn . transport_dnstr . find ( " CN=IP " ) == 0 or
not is_smtp_replication_available ( ) ) ) :
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_MAIL_REP ) != 0x0 ) :
t_repsFrom . replica_flags & = ~ drsuapi . DRSUAPI_DRS_MAIL_REP
t_repsFrom . transport_guid = misc . GUID ( )
# See (NOTE MS-TECH INCORRECT) above
if t_repsFrom . version == 0x1 :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name1 != nastr :
t_repsFrom . dns_name1 = nastr
else :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name2 is None or \
t_repsFrom . dns_name1 != nastr or \
t_repsFrom . dns_name2 != nastr :
t_repsFrom . dns_name1 = nastr
t_repsFrom . dns_name2 = nastr
else :
# XXX This entire branch is NEVER used! Because we don't do SMTP!
# (see the if condition above). Just close your eyes here.
if ( ( t_repsFrom . replica_flags &
drsuapi . DRSUAPI_DRS_MAIL_REP ) == 0x0 ) :
t_repsFrom . replica_flags | = drsuapi . DRSUAPI_DRS_MAIL_REP
# We have a transport type but its not an
# object in the database
if cn_conn . transport_guid not in self . transport_table :
raise KCCError ( " Missing inter-site transport - ( %s ) " %
cn_conn . transport_dnstr )
x_transport = self . transport_table [ str ( cn_conn . transport_guid ) ]
if t_repsFrom . transport_guid != x_transport . guid :
t_repsFrom . transport_guid = x_transport . guid
# See (NOTE MS-TECH INCORRECT) above
if x_transport . address_attr == " dNSHostName " :
if t_repsFrom . version == 0x1 :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name1 != nastr :
t_repsFrom . dns_name1 = nastr
else :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name2 is None or \
t_repsFrom . dns_name1 != nastr or \
t_repsFrom . dns_name2 != nastr :
t_repsFrom . dns_name1 = nastr
t_repsFrom . dns_name2 = nastr
else :
# MS tech specification says we retrieve the named
# attribute in "transportAddressAttribute" from the parent of
# the DSA object
try :
pdnstr = s_dsa . get_parent_dnstr ( )
attrs = [ x_transport . address_attr ]
res = self . samdb . search ( base = pdnstr , scope = ldb . SCOPE_BASE ,
attrs = attrs )
except ldb . LdbError , ( enum , estr ) :
raise KCCError (
" Unable to find attr ( %s ) for ( %s ) - ( %s ) " %
( x_transport . address_attr , pdnstr , estr ) )
msg = res [ 0 ]
nastr = str ( msg [ x_transport . address_attr ] [ 0 ] )
# See (NOTE MS-TECH INCORRECT) above
if t_repsFrom . version == 0x1 :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name1 != nastr :
t_repsFrom . dns_name1 = nastr
else :
if t_repsFrom . dns_name1 is None or \
t_repsFrom . dns_name2 is None or \
t_repsFrom . dns_name1 != nastr or \
t_repsFrom . dns_name2 != nastr :
t_repsFrom . dns_name1 = nastr
t_repsFrom . dns_name2 = nastr
if t_repsFrom . is_modified ( ) :
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " modify_repsFrom(): %s " % t_repsFrom )
2015-06-05 01:49:30 +03:00
def is_repsFrom_implied ( self , n_rep , cn_conn ) :
""" Given a NC replica and NTDS Connection, determine if the connection
implies a repsFrom tuple should be present from the source DSA listed
in the connection to the naming context
: param n_rep : NC replica
: param conn : NTDS Connection
: : returns ( True | | False ) , source DSA :
"""
#XXX different conditions for "implies" than MS-ADTS 6.2.2
# NTDS Connection must satisfy all the following criteria
# to imply a repsFrom tuple is needed:
#
# cn!enabledConnection = true.
# cn!options does not contain NTDSCONN_OPT_RODC_TOPOLOGY.
# cn!fromServer references an nTDSDSA object.
s_dsa = None
if cn_conn . is_enabled ( ) and not cn_conn . is_rodc_topology ( ) :
s_dnstr = cn_conn . get_from_dnstr ( )
if s_dnstr is not None :
s_dsa = self . get_dsa ( s_dnstr )
# No DSA matching this source DN string?
if s_dsa is None :
return False , None
# To imply a repsFrom tuple is needed, each of these
# must be True:
#
# An NC replica of the NC "is present" on the DC to
# which the nTDSDSA object referenced by cn!fromServer
# corresponds.
#
# An NC replica of the NC "should be present" on
# the local DC
s_rep = s_dsa . get_current_replica ( n_rep . nc_dnstr )
if s_rep is None or not s_rep . is_present ( ) :
return False , None
# To imply a repsFrom tuple is needed, each of these
# must be True:
#
# The NC replica on the DC referenced by cn!fromServer is
# a writable replica or the NC replica that "should be
# present" on the local DC is a partial replica.
#
# The NC is not a domain NC, the NC replica that
# "should be present" on the local DC is a partial
# replica, cn!transportType has no value, or
# cn!transportType has an RDN of CN=IP.
#
implied = ( not s_rep . is_ro ( ) or n_rep . is_partial ( ) ) and \
( not n_rep . is_domain ( ) or
n_rep . is_partial ( ) or
cn_conn . transport_dnstr is None or
cn_conn . transport_dnstr . find ( " CN=IP " ) == 0 )
if implied :
return True , s_dsa
else :
return False , None
def translate_ntdsconn ( self , current_dsa = None ) :
""" Adjust repsFrom to match NTDSConnections
This function adjusts values of repsFrom abstract attributes of NC
replicas on the local DC to match those implied by
nTDSConnection objects .
Based on [ MS - ADTS ] 6.2 .2 .5
: param current_dsa : optional DSA on whose behalf we are acting .
: return : None
"""
count = 0
if current_dsa is None :
current_dsa = self . my_dsa
if current_dsa . is_translate_ntdsconn_disabled ( ) :
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " skipping translate_ntdsconn() "
" because disabling flag is set " )
2015-06-05 01:49:30 +03:00
return
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " translate_ntdsconn(): enter " )
2015-06-05 01:49:30 +03:00
current_rep_table , needed_rep_table = current_dsa . get_rep_tables ( )
# Filled in with replicas we currently have that need deleting
delete_reps = set ( )
# We're using the MS notation names here to allow
# correlation back to the published algorithm.
#
# n_rep - NC replica (n)
# t_repsFrom - tuple (t) in n!repsFrom
# s_dsa - Source DSA of the replica. Defined as nTDSDSA
# object (s) such that (s!objectGUID = t.uuidDsa)
# In our IDL representation of repsFrom the (uuidDsa)
# attribute is called (source_dsa_obj_guid)
# cn_conn - (cn) is nTDSConnection object and child of the local
# DC's nTDSDSA object and (cn!fromServer = s)
# s_rep - source DSA replica of n
#
# If we have the replica and its not needed
# then we add it to the "to be deleted" list.
for dnstr in current_rep_table :
if dnstr not in needed_rep_table :
delete_reps . add ( dnstr )
DEBUG_FN ( ' current %d needed %d delete %d ' % ( len ( current_rep_table ) ,
len ( needed_rep_table ) , len ( delete_reps ) ) )
if delete_reps :
DEBUG ( ' deleting these reps: %s ' % delete_reps )
for dnstr in delete_reps :
del current_rep_table [ dnstr ]
# Now perform the scan of replicas we'll need
# and compare any current repsFrom against the
# connections
for n_rep in needed_rep_table . values ( ) :
# load any repsFrom and fsmo roles as we'll
# need them during connection translation
n_rep . load_repsFrom ( self . samdb )
n_rep . load_fsmo_roles ( self . samdb )
# Loop thru the existing repsFrom tupples (if any)
# XXX This is a list and could contain duplicates
# (multiple load_repsFrom calls)
for t_repsFrom in n_rep . rep_repsFrom :
# for each tuple t in n!repsFrom, let s be the nTDSDSA
# object such that s!objectGUID = t.uuidDsa
guidstr = str ( t_repsFrom . source_dsa_obj_guid )
s_dsa = self . get_dsa_by_guidstr ( guidstr )
# Source dsa is gone from config (strange)
# so cleanup stale repsFrom for unlisted DSA
if s_dsa is None :
logger . warning ( " repsFrom source DSA guid ( %s ) not found " %
guidstr )
t_repsFrom . to_be_deleted = True
continue
s_dnstr = s_dsa . dsa_dnstr
# Retrieve my DSAs connection object (if it exists)
# that specifies the fromServer equivalent to
# the DSA that is specified in the repsFrom source
connections = current_dsa . get_connection_by_from_dnstr ( s_dnstr )
count = 0
cn_conn = None
for con in connections :
if con . is_rodc_topology ( ) :
continue
cn_conn = con
# Let (cn) be the nTDSConnection object such that (cn)
# is a child of the local DC's nTDSDSA object and
# (cn!fromServer = s) and (cn!options) does not contain
# NTDSCONN_OPT_RODC_TOPOLOGY or NULL if no such (cn) exists.
# KCC removes this repsFrom tuple if any of the following
# is true:
# cn = NULL.
# [...]
#XXX varying possible interpretations of rodc_topology
if cn_conn is None :
t_repsFrom . to_be_deleted = True
continue
# [...] KCC removes this repsFrom tuple if:
#
# No NC replica of the NC "is present" on DSA that
# would be source of replica
#
# A writable replica of the NC "should be present" on
# the local DC, but a partial replica "is present" on
# the source DSA
s_rep = s_dsa . get_current_replica ( n_rep . nc_dnstr )
if s_rep is None or not s_rep . is_present ( ) or \
( not n_rep . is_ro ( ) and s_rep . is_partial ( ) ) :
t_repsFrom . to_be_deleted = True
continue
# If the KCC did not remove t from n!repsFrom, it updates t
self . modify_repsFrom ( n_rep , t_repsFrom , s_rep , s_dsa , cn_conn )
# Loop thru connections and add implied repsFrom tuples
# for each NTDSConnection under our local DSA if the
# repsFrom is not already present
for cn_conn in current_dsa . connect_table . values ( ) :
implied , s_dsa = self . is_repsFrom_implied ( n_rep , cn_conn )
if not implied :
continue
# Loop thru the existing repsFrom tupples (if any) and
# if we already have a tuple for this connection then
# no need to proceed to add. It will have been changed
# to have the correct attributes above
for t_repsFrom in n_rep . rep_repsFrom :
guidstr = str ( t_repsFrom . source_dsa_obj_guid )
#XXX what?
if s_dsa is self . get_dsa_by_guidstr ( guidstr ) :
s_dsa = None
break
if s_dsa is None :
continue
# Create a new RepsFromTo and proceed to modify
# it according to specification
t_repsFrom = RepsFromTo ( n_rep . nc_dnstr )
t_repsFrom . source_dsa_obj_guid = s_dsa . dsa_guid
s_rep = s_dsa . get_current_replica ( n_rep . nc_dnstr )
self . modify_repsFrom ( n_rep , t_repsFrom , s_rep , s_dsa , cn_conn )
# Add to our NC repsFrom as this is newly computed
if t_repsFrom . is_modified ( ) :
n_rep . rep_repsFrom . append ( t_repsFrom )
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
# Display any to be deleted or modified repsFrom
text = n_rep . dumpstr_to_be_deleted ( )
if text :
logger . info ( " TO BE DELETED: \n %s " % text )
text = n_rep . dumpstr_to_be_modified ( )
if text :
logger . info ( " TO BE MODIFIED: \n %s " % text )
# Peform deletion from our tables but perform
# no database modification
n_rep . commit_repsFrom ( self . samdb , ro = True )
else :
# Commit any modified repsFrom to the NC replica
n_rep . commit_repsFrom ( self . samdb )
def merge_failed_links ( self ) :
""" Merge of kCCFailedLinks and kCCFailedLinks from bridgeheads.
The KCC on a writable DC attempts to merge the link and connection
failure information from bridgehead DCs in its own site to help it
identify failed bridgehead DCs .
Based on MS - ADTS 6.2 .2 .3 .2 " Merge of kCCFailedLinks and kCCFailedLinks
from Bridgeheads "
: param ping : An oracle of current bridgehead availability
: return : None
"""
# 1. Queries every bridgehead server in your site (other than yourself)
# 2. For every ntDSConnection that references a server in a different
# site merge all the failure info
#
# XXX - not implemented yet
2015-06-05 01:59:23 +03:00
if self . attempt_live_connections :
debug . DEBUG_RED ( " merge_failed_links() is NOT IMPLEMENTED " )
2015-06-05 01:49:30 +03:00
else :
DEBUG_FN ( " skipping merge_failed_links() because it requires "
" real network connections \n "
" and we weren ' t asked to --attempt-live-connections " )
def setup_graph ( self , part ) :
""" Set up an intersite graph
An intersite graph has a Vertex for each site object , a
MultiEdge for each SiteLink object , and a MutliEdgeSet for
each siteLinkBridge object ( or implied siteLinkBridge ) . It
reflects the intersite topology in a slightly more abstract
graph form .
Roughly corresponds to MS - ADTS 6.2 .2 .3 .4 .3
: param part : a Partition object
: returns : an InterSiteGraph object
"""
2015-06-10 07:42:54 +03:00
# If 'Bridge all site links' is enabled and Win2k3 bridges required
# is not set
# NTDSTRANSPORT_OPT_BRIDGES_REQUIRED 0x00000002
# No documentation for this however, ntdsapi.h appears to have:
# NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000
bridges_required = self . my_site . site_options & 0x00001002 == 0
2015-06-05 01:49:30 +03:00
2015-06-10 07:42:54 +03:00
g = setup_graph ( part , self . site_table , self . transport_table ,
self . sitelink_table , bridges_required )
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
for edge in g . edges :
for a , b in itertools . combinations ( edge . vertices , 2 ) :
dot_edges . append ( ( a . site . site_dnstr , b . site . site_dnstr ) )
verify_properties = ( )
verify_and_dot ( ' site_edges ' , dot_edges , directed = False ,
label = self . my_dsa_dnstr ,
properties = verify_properties , debug = DEBUG ,
2015-06-05 01:59:23 +03:00
verify = self . verify ,
dot_files = self . dot_files )
2015-06-05 01:49:30 +03:00
return g
def get_bridgehead ( self , site , part , transport , partial_ok , detect_failed ) :
""" Get a bridghead DC for a site.
Part of MS - ADTS 6.2 .2 .3 .4 .4
: param site : site object representing for which a bridgehead
DC is desired .
: param part : crossRef for NC to replicate .
: param transport : interSiteTransport object for replication
traffic .
: param partial_ok : True if a DC containing a partial
replica or a full replica will suffice , False if only
a full replica will suffice .
: param detect_failed : True to detect failed DCs and route
replication traffic around them , False to assume no DC
has failed .
: return : dsa object for the bridgehead DC or None
"""
bhs = self . get_all_bridgeheads ( site , part , transport ,
partial_ok , detect_failed )
if len ( bhs ) == 0 :
2015-06-05 01:59:23 +03:00
debug . DEBUG_MAGENTA ( " get_bridgehead: \n \t sitedn= %s \n \t bhdn=None " %
site . site_dnstr )
2015-06-05 01:49:30 +03:00
return None
else :
2015-06-05 01:59:23 +03:00
debug . DEBUG_GREEN ( " get_bridgehead: \n \t sitedn= %s \n \t bhdn= %s " %
( site . site_dnstr , bhs [ 0 ] . dsa_dnstr ) )
2015-06-05 01:49:30 +03:00
return bhs [ 0 ]
def get_all_bridgeheads ( self , site , part , transport ,
partial_ok , detect_failed ) :
""" Get all bridghead DCs on a site satisfying the given criteria
Part of MS - ADTS 6.2 .2 .3 .4 .4
: param site : site object representing the site for which
bridgehead DCs are desired .
: param part : partition for NC to replicate .
: param transport : interSiteTransport object for
replication traffic .
: param partial_ok : True if a DC containing a partial
replica or a full replica will suffice , False if
only a full replica will suffice .
: param detect_failed : True to detect failed DCs and route
replication traffic around them , FALSE to assume
no DC has failed .
: return : list of dsa object for available bridgehead DCs
"""
bhs = [ ]
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " get_all_bridgeheads: %s " % transport . name )
2015-06-05 01:49:30 +03:00
if ' Site-5 ' in site . site_dnstr :
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " get_all_bridgeheads with %s , part %s , "
" partial_ok %s detect_failed %s " %
( site . site_dnstr , part . partstr , partial_ok ,
detect_failed ) )
DEBUG_FN ( site . rw_dsa_table )
2015-06-05 01:49:30 +03:00
for dsa in site . rw_dsa_table . values ( ) :
pdnstr = dsa . get_parent_dnstr ( )
# IF t!bridgeheadServerListBL has one or more values and
# t!bridgeheadServerListBL does not contain a reference
# to the parent object of dc then skip dc
if ( ( len ( transport . bridgehead_list ) != 0 and
pdnstr not in transport . bridgehead_list ) ) :
continue
# IF dc is in the same site as the local DC
# IF a replica of cr!nCName is not in the set of NC replicas
# that "should be present" on dc or a partial replica of the
# NC "should be present" but partialReplicasOkay = FALSE
# Skip dc
if self . my_site . same_site ( dsa ) :
needed , ro , partial = part . should_be_present ( dsa )
if not needed or ( partial and not partial_ok ) :
continue
rep = dsa . get_current_replica ( part . nc_dnstr )
# ELSE
# IF an NC replica of cr!nCName is not in the set of NC
# replicas that "are present" on dc or a partial replica of
# the NC "is present" but partialReplicasOkay = FALSE
# Skip dc
else :
rep = dsa . get_current_replica ( part . nc_dnstr )
if rep is None or ( rep . is_partial ( ) and not partial_ok ) :
continue
# IF AmIRODC() and cr!nCName corresponds to default NC then
# Let dsaobj be the nTDSDSA object of the dc
# IF dsaobj.msDS-Behavior-Version < DS_DOMAIN_FUNCTION_2008
# Skip dc
if self . my_dsa . is_ro ( ) and rep is not None and rep . is_default ( ) :
if not dsa . is_minimum_behavior ( dsdb . DS_DOMAIN_FUNCTION_2008 ) :
continue
# IF t!name != "IP" and the parent object of dc has no value for
# the attribute specified by t!transportAddressAttribute
# Skip dc
if transport . name != " IP " :
# MS tech specification says we retrieve the named
# attribute in "transportAddressAttribute" from the parent
# of the DSA object
try :
attrs = [ transport . address_attr ]
res = self . samdb . search ( base = pdnstr , scope = ldb . SCOPE_BASE ,
attrs = attrs )
except ldb . LdbError , ( enum , estr ) :
continue
msg = res [ 0 ]
if transport . address_attr not in msg :
continue
#XXX nastr is NEVER USED. It will be removed.
nastr = str ( msg [ transport . address_attr ] [ 0 ] )
# IF BridgeheadDCFailed(dc!objectGUID, detectFailedDCs) = TRUE
# Skip dc
if self . is_bridgehead_failed ( dsa , detect_failed ) :
DEBUG ( " bridgehead is failed " )
continue
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " get_all_bridgeheads: dsadn= %s " % dsa . dsa_dnstr )
2015-06-05 01:49:30 +03:00
bhs . append ( dsa )
# IF bit NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED is set in
# s!options
# SORT bhs such that all GC servers precede DCs that are not GC
# servers, and otherwise by ascending objectGUID
# ELSE
# SORT bhs in a random order
if site . is_random_bridgehead_disabled ( ) :
bhs . sort ( sort_dsa_by_gc_and_guid )
else :
random . shuffle ( bhs )
2015-06-05 01:59:23 +03:00
debug . DEBUG_YELLOW ( bhs )
2015-06-05 01:49:30 +03:00
return bhs
def is_bridgehead_failed ( self , dsa , detect_failed ) :
""" Determine whether a given DC is known to be in a failed state
: param dsa : the bridgehead to test
: param detect_failed : True to really check , False to assume no failure
: return : True if and only if the DC should be considered failed
Here we DEPART from the pseudo code spec which appears to be
wrong . It says , in full :
/ * * * * * BridgeheadDCFailed * * * * * /
/ * Determine whether a given DC is known to be in a failed state .
* IN : objectGUID - objectGUID of the DC ' s nTDSDSA object.
* IN : detectFailedDCs - TRUE if and only failed DC detection is
* enabled .
* RETURNS : TRUE if and only if the DC should be considered to be in a
* failed state .
* /
BridgeheadDCFailed ( IN GUID objectGUID , IN bool detectFailedDCs ) : bool
{
IF bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set in
the options attribute of the site settings object for the local
DC ' s site
RETURN FALSE
ELSEIF a tuple z exists in the kCCFailedLinks or
kCCFailedConnections variables such that z . UUIDDsa =
objectGUID , z . FailureCount > 1 , and the current time -
z . TimeFirstFailure > 2 hours
RETURN TRUE
ELSE
RETURN detectFailedDCs
ENDIF
}
where you will see detectFailedDCs is not behaving as
advertised - - it is acting as a default return code in the
event that a failure is not detected , not a switch turning
detection on or off . Elsewhere the documentation seems to
concur with the comment rather than the code .
"""
if not detect_failed :
return False
# NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008
# When DETECT_STALE_DISABLED, we can never know of if
# it's in a failed state
if self . my_site . site_options & 0x00000008 :
return False
return self . is_stale_link_connection ( dsa )
def create_connection ( self , part , rbh , rsite , transport ,
lbh , lsite , link_opt , link_sched ,
partial_ok , detect_failed ) :
""" Create an nTDSConnection object as specified if it doesn ' t exist.
Part of MS - ADTS 6.2 .2 .3 .4 .5
: param part : crossRef object for the NC to replicate .
: param rbh : nTDSDSA object for DC to act as the
IDL_DRSGetNCChanges server ( which is in a site other
than the local DC ' s site).
: param rsite : site of the rbh
: param transport : interSiteTransport object for the transport
to use for replication traffic .
: param lbh : nTDSDSA object for DC to act as the
IDL_DRSGetNCChanges client ( which is in the local DC ' s site).
: param lsite : site of the lbh
: param link_opt : Replication parameters ( aggregated siteLink options ,
etc . )
: param link_sched : Schedule specifying the times at which
to begin replicating .
: partial_ok : True if bridgehead DCs containing partial
replicas of the NC are acceptable .
: param detect_failed : True to detect failed DCs and route
replication traffic around them , FALSE to assume no DC
has failed .
"""
rbhs_all = self . get_all_bridgeheads ( rsite , part , transport ,
partial_ok , False )
rbh_table = { x . dsa_dnstr : x for x in rbhs_all }
2015-06-05 01:59:23 +03:00
debug . DEBUG_GREY ( " rbhs_all: %s %s " % ( len ( rbhs_all ) ,
[ x . dsa_dnstr for x in rbhs_all ] ) )
2015-06-05 01:49:30 +03:00
# MS-TECH says to compute rbhs_avail but then doesn't use it
# rbhs_avail = self.get_all_bridgeheads(rsite, part, transport,
# partial_ok, detect_failed)
lbhs_all = self . get_all_bridgeheads ( lsite , part , transport ,
partial_ok , False )
if lbh . is_ro ( ) :
lbhs_all . append ( lbh )
2015-06-05 01:59:23 +03:00
debug . DEBUG_GREY ( " lbhs_all: %s %s " % ( len ( lbhs_all ) ,
[ x . dsa_dnstr for x in lbhs_all ] ) )
2015-06-05 01:49:30 +03:00
# MS-TECH says to compute lbhs_avail but then doesn't use it
# lbhs_avail = self.get_all_bridgeheads(lsite, part, transport,
# partial_ok, detect_failed)
# FOR each nTDSConnection object cn such that the parent of cn is
# a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
for ldsa in lbhs_all :
for cn in ldsa . connect_table . values ( ) :
rdsa = rbh_table . get ( cn . from_dnstr )
if rdsa is None :
continue
2015-06-05 01:59:23 +03:00
debug . DEBUG_DARK_YELLOW ( " rdsa is %s " % rdsa . dsa_dnstr )
2015-06-05 01:49:30 +03:00
# IF bit NTDSCONN_OPT_IS_GENERATED is set in cn!options and
# NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options and
# cn!transportType references t
if ( ( cn . is_generated ( ) and
not cn . is_rodc_topology ( ) and
cn . transport_guid == transport . guid ) ) :
# IF bit NTDSCONN_OPT_USER_OWNED_SCHEDULE is clear in
# cn!options and cn!schedule != sch
# Perform an originating update to set cn!schedule to
# sched
if ( ( not cn . is_user_owned_schedule ( ) and
not cn . is_equivalent_schedule ( link_sched ) ) ) :
cn . schedule = link_sched
cn . set_modified ( True )
# IF bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
# NTDSCONN_OPT_USE_NOTIFY are set in cn
if cn . is_override_notify_default ( ) and \
cn . is_use_notify ( ) :
# IF bit NTDSSITELINK_OPT_USE_NOTIFY is clear in
# ri.Options
# Perform an originating update to clear bits
# NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
# NTDSCONN_OPT_USE_NOTIFY in cn!options
if ( link_opt & dsdb . NTDSSITELINK_OPT_USE_NOTIFY ) == 0 :
cn . options & = \
~ ( dsdb . NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
dsdb . NTDSCONN_OPT_USE_NOTIFY )
cn . set_modified ( True )
# ELSE
else :
# IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in
# ri.Options
# Perform an originating update to set bits
# NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
# NTDSCONN_OPT_USE_NOTIFY in cn!options
if ( link_opt & dsdb . NTDSSITELINK_OPT_USE_NOTIFY ) != 0 :
cn . options | = \
( dsdb . NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
dsdb . NTDSCONN_OPT_USE_NOTIFY )
cn . set_modified ( True )
# IF bit NTDSCONN_OPT_TWOWAY_SYNC is set in cn!options
if cn . is_twoway_sync ( ) :
# IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is clear in
# ri.Options
# Perform an originating update to clear bit
# NTDSCONN_OPT_TWOWAY_SYNC in cn!options
if ( link_opt & dsdb . NTDSSITELINK_OPT_TWOWAY_SYNC ) == 0 :
cn . options & = ~ dsdb . NTDSCONN_OPT_TWOWAY_SYNC
cn . set_modified ( True )
# ELSE
else :
# IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in
# ri.Options
# Perform an originating update to set bit
# NTDSCONN_OPT_TWOWAY_SYNC in cn!options
if ( link_opt & dsdb . NTDSSITELINK_OPT_TWOWAY_SYNC ) != 0 :
cn . options | = dsdb . NTDSCONN_OPT_TWOWAY_SYNC
cn . set_modified ( True )
# IF bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION is set
# in cn!options
if cn . is_intersite_compression_disabled ( ) :
# IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is clear
# in ri.Options
# Perform an originating update to clear bit
# NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
# cn!options
if ( ( link_opt &
dsdb . NTDSSITELINK_OPT_DISABLE_COMPRESSION ) == 0 ) :
cn . options & = \
~ dsdb . NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
cn . set_modified ( True )
# ELSE
else :
# IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
# ri.Options
# Perform an originating update to set bit
# NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in
# cn!options
if ( ( link_opt &
dsdb . NTDSSITELINK_OPT_DISABLE_COMPRESSION ) != 0 ) :
cn . options | = \
dsdb . NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
cn . set_modified ( True )
# Display any modified connection
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
if cn . to_be_modified :
logger . info ( " TO BE MODIFIED: \n %s " % cn )
ldsa . commit_connections ( self . samdb , ro = True )
else :
ldsa . commit_connections ( self . samdb )
# ENDFOR
valid_connections = 0
# FOR each nTDSConnection object cn such that cn!parent is
# a DC in lbhsAll and cn!fromServer references a DC in rbhsAll
for ldsa in lbhs_all :
for cn in ldsa . connect_table . values ( ) :
rdsa = rbh_table . get ( cn . from_dnstr )
if rdsa is None :
continue
2015-06-05 01:59:23 +03:00
debug . DEBUG_DARK_YELLOW ( " round 2: rdsa is %s " % rdsa . dsa_dnstr )
2015-06-05 01:49:30 +03:00
# IF (bit NTDSCONN_OPT_IS_GENERATED is clear in cn!options or
# cn!transportType references t) and
# NTDSCONN_OPT_RODC_TOPOLOGY is clear in cn!options
if ( ( ( not cn . is_generated ( ) or
cn . transport_guid == transport . guid ) and
not cn . is_rodc_topology ( ) ) ) :
# LET rguid be the objectGUID of the nTDSDSA object
# referenced by cn!fromServer
# LET lguid be (cn!parent)!objectGUID
# IF BridgeheadDCFailed(rguid, detectFailedDCs) = FALSE and
# BridgeheadDCFailed(lguid, detectFailedDCs) = FALSE
# Increment cValidConnections by 1
if ( ( not self . is_bridgehead_failed ( rdsa , detect_failed ) and
not self . is_bridgehead_failed ( ldsa , detect_failed ) ) ) :
valid_connections + = 1
# IF keepConnections does not contain cn!objectGUID
# APPEND cn!objectGUID to keepConnections
self . kept_connections . add ( cn )
# ENDFOR
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " valid connections %d " % valid_connections )
2015-06-05 01:49:30 +03:00
DEBUG ( " kept_connections: \n %s " % ( self . kept_connections , ) )
# IF cValidConnections = 0
if valid_connections == 0 :
# LET opt be NTDSCONN_OPT_IS_GENERATED
opt = dsdb . NTDSCONN_OPT_IS_GENERATED
# IF bit NTDSSITELINK_OPT_USE_NOTIFY is set in ri.Options
# SET bits NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT and
# NTDSCONN_OPT_USE_NOTIFY in opt
if ( link_opt & dsdb . NTDSSITELINK_OPT_USE_NOTIFY ) != 0 :
opt | = ( dsdb . NTDSCONN_OPT_OVERRIDE_NOTIFY_DEFAULT |
dsdb . NTDSCONN_OPT_USE_NOTIFY )
# IF bit NTDSSITELINK_OPT_TWOWAY_SYNC is set in ri.Options
# SET bit NTDSCONN_OPT_TWOWAY_SYNC opt
if ( link_opt & dsdb . NTDSSITELINK_OPT_TWOWAY_SYNC ) != 0 :
opt | = dsdb . NTDSCONN_OPT_TWOWAY_SYNC
# IF bit NTDSSITELINK_OPT_DISABLE_COMPRESSION is set in
# ri.Options
# SET bit NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION in opt
if ( ( link_opt &
dsdb . NTDSSITELINK_OPT_DISABLE_COMPRESSION ) != 0 ) :
opt | = dsdb . NTDSCONN_OPT_DISABLE_INTERSITE_COMPRESSION
# Perform an originating update to create a new nTDSConnection
# object cn that is a child of lbh, cn!enabledConnection = TRUE,
# cn!options = opt, cn!transportType is a reference to t,
# cn!fromServer is a reference to rbh, and cn!schedule = sch
DEBUG_FN ( " new connection, KCC dsa: %s " % self . my_dsa . dsa_dnstr )
cn = lbh . new_connection ( opt , 0 , transport ,
rbh . dsa_dnstr , link_sched )
# Display any added connection
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
if cn . to_be_added :
logger . info ( " TO BE ADDED: \n %s " % cn )
lbh . commit_connections ( self . samdb , ro = True )
else :
lbh . commit_connections ( self . samdb )
# APPEND cn!objectGUID to keepConnections
self . kept_connections . add ( cn )
def add_transports ( self , vertex , local_vertex , graph , detect_failed ) :
""" Build a Vertex ' s transport lists
Each vertex has accept_red_red and accept_black lists that
list what transports they accept under various conditions . The
only transport that is ever accepted is IP , and a dummy extra
transport called " EDGE_TYPE_ALL " .
Part of MS - ADTS 6.2 .2 .3 .4 .3 - - ColorVertices
: param vertex : the remote vertex we are thinking about
: param local_vertex : the vertex relating to the local site .
: param graph : the intersite graph
: param detect_failed : whether to detect failed links
: return : True if some bridgeheads were not found
"""
# The docs ([MS-ADTS] 6.2.2.3.4.3) say to use local_vertex
# here, but using vertex seems to make more sense. That is,
# the docs want this:
#
#bh = self.get_bridgehead(vertex.site, vertex.part, transport,
# local_vertex.is_black(), detect_failed)
#
# TODO WHY?????
vertex . accept_red_red = [ ]
vertex . accept_black = [ ]
found_failed = False
for t_guid , transport in self . transport_table . items ( ) :
if transport . name != ' IP ' :
#XXX well this is cheating a bit
2015-06-05 01:59:23 +03:00
logger . warning ( " WARNING: we are ignoring a transport named %r "
% transport . name )
2015-06-05 01:49:30 +03:00
continue
# FLAG_CR_NTDS_DOMAIN 0x00000002
if ( ( vertex . is_red ( ) and transport . name != " IP " and
vertex . part . system_flags & 0x00000002 ) ) :
continue
if vertex not in graph . connected_vertices :
continue
partial_replica_okay = vertex . is_black ( )
bh = self . get_bridgehead ( vertex . site , vertex . part , transport ,
partial_replica_okay , detect_failed )
if bh is None :
found_failed = True
continue
vertex . accept_red_red . append ( t_guid )
vertex . accept_black . append ( t_guid )
# Add additional transport to allow another run of Dijkstra
vertex . accept_red_red . append ( " EDGE_TYPE_ALL " )
vertex . accept_black . append ( " EDGE_TYPE_ALL " )
return found_failed
def create_connections ( self , graph , part , detect_failed ) :
""" Construct an NC replica graph for the NC identified by
the given crossRef , then create any additional nTDSConnection
objects required .
: param graph : site graph .
: param part : crossRef object for NC .
: param detect_failed : True to detect failed DCs and route
replication traffic around them , False to assume no DC
has failed .
Modifies self . kept_connections by adding any connections
deemed to be " in use " .
: : returns : ( all_connected , found_failed_dc )
( all_connected ) True if the resulting NC replica graph
connects all sites that need to be connected .
( found_failed_dc ) True if one or more failed DCs were
detected .
"""
all_connected = True
found_failed = False
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " create_connections(): enter \n "
" \t partdn= %s \n \t detect_failed= %s " %
( part . nc_dnstr , detect_failed ) )
2015-06-05 01:49:30 +03:00
# XXX - This is a highly abbreviated function from the MS-TECH
# ref. It creates connections between bridgeheads to all
# sites that have appropriate replicas. Thus we are not
# creating a minimum cost spanning tree but instead
# producing a fully connected tree. This should produce
# a full (albeit not optimal cost) replication topology.
my_vertex = Vertex ( self . my_site , part )
my_vertex . color_vertex ( )
for v in graph . vertices :
v . color_vertex ( )
if self . add_transports ( v , my_vertex , graph , False ) :
found_failed = True
# No NC replicas for this NC in the site of the local DC,
# so no nTDSConnection objects need be created
if my_vertex . is_white ( ) :
return all_connected , found_failed
edge_list , n_components = get_spanning_tree_edges ( graph ,
self . my_site ,
label = part . partstr )
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " %s Number of components: %d " %
( part . nc_dnstr , n_components ) )
2015-06-05 01:49:30 +03:00
if n_components > 1 :
all_connected = False
# LET partialReplicaOkay be TRUE if and only if
# localSiteVertex.Color = COLOR.BLACK
partial_ok = my_vertex . is_black ( )
# Utilize the IP transport only for now
transport = self . ip_transport
DEBUG ( " edge_list %s " % edge_list )
for e in edge_list :
# XXX more accurate comparison?
if e . directed and e . vertices [ 0 ] . site is self . my_site :
continue
if e . vertices [ 0 ] . site is self . my_site :
rsite = e . vertices [ 1 ] . site
else :
rsite = e . vertices [ 0 ] . site
# We don't make connections to our own site as that
# is intrasite topology generator's job
if rsite is self . my_site :
DEBUG ( " rsite is my_site " )
continue
# Determine bridgehead server in remote site
rbh = self . get_bridgehead ( rsite , part , transport ,
partial_ok , detect_failed )
if rbh is None :
continue
# RODC acts as an BH for itself
# IF AmIRODC() then
# LET lbh be the nTDSDSA object of the local DC
# ELSE
# LET lbh be the result of GetBridgeheadDC(localSiteVertex.ID,
# cr, t, partialReplicaOkay, detectFailedDCs)
if self . my_dsa . is_ro ( ) :
lsite = self . my_site
lbh = self . my_dsa
else :
lsite = self . my_site
lbh = self . get_bridgehead ( lsite , part , transport ,
partial_ok , detect_failed )
# TODO
if lbh is None :
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " DISASTER! lbh is None " )
2015-06-05 01:49:30 +03:00
return False , True
2015-06-05 01:59:23 +03:00
debug . DEBUG_CYAN ( " SITES " )
2015-06-05 01:49:30 +03:00
print lsite , rsite
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " vertices " )
2015-06-05 01:49:30 +03:00
print e . vertices
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " bridgeheads " )
2015-06-05 01:49:30 +03:00
print lbh , rbh
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " - " * 70 )
2015-06-05 01:49:30 +03:00
sitelink = e . site_link
if sitelink is None :
link_opt = 0x0
link_sched = None
else :
link_opt = sitelink . options
link_sched = sitelink . schedule
self . create_connection ( part , rbh , rsite , transport ,
lbh , lsite , link_opt , link_sched ,
partial_ok , detect_failed )
return all_connected , found_failed
def create_intersite_connections ( self ) :
2015-05-06 09:03:05 +03:00
""" Create NTDSConnections as necessary for all partitions.
Computes an NC replica graph for each NC replica that " should be
2015-06-05 01:49:30 +03:00
present " on the local DC or " is present " on any DC in the same site
as the local DC . For each edge directed to an NC replica on such a
DC from an NC replica on a DC in another site , the KCC creates an
nTDSConnection object to imply that edge if one does not already
exist .
Modifies self . kept_connections - A set of nTDSConnection
objects for edges that are directed
to the local DC ' s site in one or more NC replica graphs.
2015-05-06 09:03:05 +03:00
: return : True if spanning trees were created for all NC replica
graphs , otherwise False .
2015-06-05 01:49:30 +03:00
"""
all_connected = True
self . kept_connections = set ( )
# LET crossRefList be the set containing each object o of class
# crossRef such that o is a child of the CN=Partitions child of the
# config NC
# FOR each crossRef object cr in crossRefList
# IF cr!enabled has a value and is false, or if FLAG_CR_NTDS_NC
# is clear in cr!systemFlags, skip cr.
# LET g be the GRAPH return of SetupGraph()
for part in self . part_table . values ( ) :
if not part . is_enabled ( ) :
continue
if part . is_foreign ( ) :
continue
graph = self . setup_graph ( part )
# Create nTDSConnection objects, routing replication traffic
# around "failed" DCs.
found_failed = False
connected , found_failed = self . create_connections ( graph ,
part , True )
DEBUG ( " with detect_failed: connected %s Found failed %s " %
( connected , found_failed ) )
if not connected :
all_connected = False
if found_failed :
# One or more failed DCs preclude use of the ideal NC
# replica graph. Add connections for the ideal graph.
self . create_connections ( graph , part , False )
return all_connected
def intersite ( self ) :
""" The head method for generating the inter-site KCC replica
connection graph and attendant nTDSConnection objects
in the samdb .
Produces self . kept_connections set of NTDS Connections
that should be kept during subsequent pruning process .
: : return ( True or False ) : ( True ) if the produced NC replica
graph connects all sites that need to be connected
"""
# Retrieve my DSA
mydsa = self . my_dsa
mysite = self . my_site
all_connected = True
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " intersite(): enter " )
2015-06-05 01:49:30 +03:00
# Determine who is the ISTG
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
mysite . select_istg ( self . samdb , mydsa , ro = True )
else :
mysite . select_istg ( self . samdb , mydsa , ro = False )
# Test whether local site has topology disabled
if mysite . is_intersite_topology_disabled ( ) :
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " intersite(): exit disabled all_connected= %d " %
all_connected )
2015-06-05 01:49:30 +03:00
return all_connected
if not mydsa . is_istg ( ) :
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " intersite(): exit not istg all_connected= %d " %
all_connected )
2015-06-05 01:49:30 +03:00
return all_connected
self . merge_failed_links ( )
# For each NC with an NC replica that "should be present" on the
# local DC or "is present" on any DC in the same site as the
# local DC, the KCC constructs a site graph--a precursor to an NC
# replica graph. The site connectivity for a site graph is defined
# by objects of class interSiteTransport, siteLink, and
# siteLinkBridge in the config NC.
all_connected = self . create_intersite_connections ( )
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " intersite(): exit all_connected= %d " % all_connected )
2015-06-05 01:49:30 +03:00
return all_connected
def update_rodc_connection ( self ) :
2015-05-06 09:05:21 +03:00
""" Updates the RODC NTFRS connection object.
If the local DSA is not an RODC , this does nothing .
2015-06-05 01:49:30 +03:00
"""
2015-05-06 09:05:21 +03:00
if not self . my_dsa . is_ro ( ) :
return
2015-06-05 01:49:30 +03:00
# Given an nTDSConnection object cn1, such that cn1.options contains
# NTDSCONN_OPT_RODC_TOPOLOGY, and another nTDSConnection object cn2,
# does not contain NTDSCONN_OPT_RODC_TOPOLOGY, modify cn1 to ensure
# that the following is true:
#
# cn1.fromServer = cn2.fromServer
# cn1.schedule = cn2.schedule
#
# If no such cn2 can be found, cn1 is not modified.
# If no such cn1 can be found, nothing is modified by this task.
all_connections = self . my_dsa . connect_table . values ( )
ro_connections = [ x for x in all_connections if x . is_rodc_topology ( ) ]
rw_connections = [ x for x in all_connections
if x not in ro_connections ]
# XXX here we are dealing with multiple RODC_TOPO connections,
# if they exist. It is not clear whether the spec means that
# or if it ever arises.
if rw_connections and ro_connections :
for con in ro_connections :
cn2 = rw_connections [ 0 ]
con . from_dnstr = cn2 . from_dnstr
con . schedule = cn2 . schedule
con . to_be_modified = True
2015-06-05 01:59:23 +03:00
self . my_dsa . commit_connections ( self . samdb , ro = self . readonly )
2015-06-05 01:49:30 +03:00
def intrasite_max_node_edges ( self , node_count ) :
""" Returns the maximum number of edges directed to a node in
the intrasite replica graph .
The KCC does not create more
than 50 edges directed to a single DC . To optimize replication ,
we compute that each node should have n + 2 total edges directed
to it such that ( n ) is the smallest non - negative integer
satisfying ( node_count < = 2 * ( n * n ) + 6 * n + 7 )
( If the number of edges is m ( i . e . n + 2 ) , that is the same as
2 * m * m - 2 * m + 3 ) .
edges n nodecount
2 0 7
3 1 15
4 2 27
5 3 43
. . .
50 48 4903
: param node_count : total number of nodes in the replica graph
The intention is that there should be no more than 3 hops
between any two DSAs at a site . With up to 7 nodes the 2 edges
of the ring are enough ; any configuration of extra edges with
8 nodes will be enough . It is less clear that the 3 hop
guarantee holds at e . g . 15 nodes in degenerate cases , but
those are quite unlikely given the extra edges are randomly
arranged .
"""
n = 0
while True :
if node_count < = ( 2 * ( n * n ) + ( 6 * n ) + 7 ) :
break
n = n + 1
n = n + 2
if n < 50 :
return n
return 50
def construct_intrasite_graph ( self , site_local , dc_local ,
nc_x , gc_only , detect_stale ) :
# [MS-ADTS] 6.2.2.2
# We're using the MS notation names here to allow
# correlation back to the published algorithm.
#
# nc_x - naming context (x) that we are testing if it
# "should be present" on the local DC
# f_of_x - replica (f) found on a DC (s) for NC (x)
# dc_s - DC where f_of_x replica was found
# dc_local - local DC that potentially needs a replica
# (f_of_x)
# r_list - replica list R
# p_of_x - replica (p) is partial and found on a DC (s)
# for NC (x)
# l_of_x - replica (l) is the local replica for NC (x)
# that should appear on the local DC
# r_len = is length of replica list |R|
#
# If the DSA doesn't need a replica for this
# partition (NC x) then continue
needed , ro , partial = nc_x . should_be_present ( dc_local )
2015-06-05 01:59:23 +03:00
debug . DEBUG_YELLOW ( " construct_intrasite_graph(): enter " +
" \n \t gc_only= %d " % gc_only +
" \n \t detect_stale= %d " % detect_stale +
" \n \t needed= %s " % needed +
" \n \t ro= %s " % ro +
" \n \t partial= %s " % partial +
" \n %s " % nc_x )
2015-06-05 01:49:30 +03:00
if not needed :
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " %s lacks ' should be present ' status, "
" aborting construct_intersite_graph! " %
nc_x . nc_dnstr )
2015-06-05 01:49:30 +03:00
return
# Create a NCReplica that matches what the local replica
# should say. We'll use this below in our r_list
l_of_x = NCReplica ( dc_local . dsa_dnstr , dc_local . dsa_guid ,
nc_x . nc_dnstr )
l_of_x . identify_by_basedn ( self . samdb )
l_of_x . rep_partial = partial
l_of_x . rep_ro = ro
# Add this replica that "should be present" to the
# needed replica table for this DSA
dc_local . add_needed_replica ( l_of_x )
# Replica list
#
# Let R be a sequence containing each writable replica f of x
# such that f "is present" on a DC s satisfying the following
# criteria:
#
# * s is a writable DC other than the local DC.
#
# * s is in the same site as the local DC.
#
# * If x is a read-only full replica and x is a domain NC,
# then the DC's functional level is at least
# DS_BEHAVIOR_WIN2008.
#
# * Bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED is set
# in the options attribute of the site settings object for
# the local DC's site, or no tuple z exists in the
# kCCFailedLinks or kCCFailedConnections variables such
# that z.UUIDDsa is the objectGUID of the nTDSDSA object
# for s, z.FailureCount > 0, and the current time -
# z.TimeFirstFailure > 2 hours.
r_list = [ ]
# We'll loop thru all the DSAs looking for
# writeable NC replicas that match the naming
# context dn for (nc_x)
#
for dc_s in self . my_site . dsa_table . values ( ) :
# If this partition (nc_x) doesn't appear as a
# replica (f_of_x) on (dc_s) then continue
if not nc_x . nc_dnstr in dc_s . current_rep_table :
continue
# Pull out the NCReplica (f) of (x) with the dn
# that matches NC (x) we are examining.
f_of_x = dc_s . current_rep_table [ nc_x . nc_dnstr ]
# Replica (f) of NC (x) must be writable
if f_of_x . is_ro ( ) :
continue
# Replica (f) of NC (x) must satisfy the
# "is present" criteria for DC (s) that
# it was found on
if not f_of_x . is_present ( ) :
continue
# DC (s) must be a writable DSA other than
# my local DC. In other words we'd only replicate
# from other writable DC
if dc_s . is_ro ( ) or dc_s is dc_local :
continue
# Certain replica graphs are produced only
# for global catalogs, so test against
# method input parameter
if gc_only and not dc_s . is_gc ( ) :
continue
# DC (s) must be in the same site as the local DC
# as this is the intra-site algorithm. This is
# handled by virtue of placing DSAs in per
# site objects (see enclosing for() loop)
# If NC (x) is intended to be read-only full replica
# for a domain NC on the target DC then the source
# DC should have functional level at minimum WIN2008
#
# Effectively we're saying that in order to replicate
# to a targeted RODC (which was introduced in Windows 2008)
# then we have to replicate from a DC that is also minimally
# at that level.
#
# You can also see this requirement in the MS special
# considerations for RODC which state that to deploy
# an RODC, at least one writable domain controller in
# the domain must be running Windows Server 2008
if ro and not partial and nc_x . nc_type == NCType . domain :
if not dc_s . is_minimum_behavior ( dsdb . DS_DOMAIN_FUNCTION_2008 ) :
continue
# If we haven't been told to turn off stale connection
# detection and this dsa has a stale connection then
# continue
if detect_stale and self . is_stale_link_connection ( dc_s ) :
continue
# Replica meets criteria. Add it to table indexed
# by the GUID of the DC that it appears on
r_list . append ( f_of_x )
# If a partial (not full) replica of NC (x) "should be present"
# on the local DC, append to R each partial replica (p of x)
# such that p "is present" on a DC satisfying the same
# criteria defined above for full replica DCs.
#
# XXX This loop and the previous one differ only in whether
# the replica is partial or not. here we only accept partial
# (because we're partial); before we only accepted full. Order
# doen't matter (the list is sorted a few lines down) so these
# loops could easily be merged. Or this could be a helper
# function.
if partial :
# Now we loop thru all the DSAs looking for
# partial NC replicas that match the naming
# context dn for (NC x)
for dc_s in self . my_site . dsa_table . values ( ) :
# If this partition NC (x) doesn't appear as a
# replica (p) of NC (x) on the dsa DC (s) then
# continue
if not nc_x . nc_dnstr in dc_s . current_rep_table :
continue
# Pull out the NCReplica with the dn that
# matches NC (x) we are examining.
p_of_x = dc_s . current_rep_table [ nc_x . nc_dnstr ]
# Replica (p) of NC (x) must be partial
if not p_of_x . is_partial ( ) :
continue
# Replica (p) of NC (x) must satisfy the
# "is present" criteria for DC (s) that
# it was found on
if not p_of_x . is_present ( ) :
continue
# DC (s) must be a writable DSA other than
# my DSA. In other words we'd only replicate
# from other writable DSA
if dc_s . is_ro ( ) or dc_s is dc_local :
continue
# Certain replica graphs are produced only
# for global catalogs, so test against
# method input parameter
if gc_only and not dc_s . is_gc ( ) :
continue
# If we haven't been told to turn off stale connection
# detection and this dsa has a stale connection then
# continue
if detect_stale and self . is_stale_link_connection ( dc_s ) :
continue
# Replica meets criteria. Add it to table indexed
# by the GUID of the DSA that it appears on
r_list . append ( p_of_x )
# Append to R the NC replica that "should be present"
# on the local DC
r_list . append ( l_of_x )
r_list . sort ( sort_replica_by_dsa_guid )
r_len = len ( r_list )
max_node_edges = self . intrasite_max_node_edges ( r_len )
# Add a node for each r_list element to the replica graph
graph_list = [ ]
for rep in r_list :
node = GraphNode ( rep . rep_dsa_dnstr , max_node_edges )
graph_list . append ( node )
# For each r(i) from (0 <= i < |R|-1)
i = 0
while i < ( r_len - 1 ) :
# Add an edge from r(i) to r(i+1) if r(i) is a full
# replica or r(i+1) is a partial replica
if not r_list [ i ] . is_partial ( ) or r_list [ i + 1 ] . is_partial ( ) :
graph_list [ i + 1 ] . add_edge_from ( r_list [ i ] . rep_dsa_dnstr )
# Add an edge from r(i+1) to r(i) if r(i+1) is a full
# replica or ri is a partial replica.
if not r_list [ i + 1 ] . is_partial ( ) or r_list [ i ] . is_partial ( ) :
graph_list [ i ] . add_edge_from ( r_list [ i + 1 ] . rep_dsa_dnstr )
i = i + 1
# Add an edge from r|R|-1 to r0 if r|R|-1 is a full replica
# or r0 is a partial replica.
if not r_list [ r_len - 1 ] . is_partial ( ) or r_list [ 0 ] . is_partial ( ) :
graph_list [ 0 ] . add_edge_from ( r_list [ r_len - 1 ] . rep_dsa_dnstr )
# Add an edge from r0 to r|R|-1 if r0 is a full replica or
# r|R|-1 is a partial replica.
if not r_list [ 0 ] . is_partial ( ) or r_list [ r_len - 1 ] . is_partial ( ) :
graph_list [ r_len - 1 ] . add_edge_from ( r_list [ 0 ] . rep_dsa_dnstr )
DEBUG ( " r_list is length %s " % len ( r_list ) )
DEBUG ( ' \n ' . join ( str ( ( x . rep_dsa_guid , x . rep_dsa_dnstr ) )
for x in r_list ) )
2015-06-05 01:59:23 +03:00
do_dot_files = self . dot_files and self . debug
if self . verify or do_dot_files :
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
dot_vertices = set ( )
for v1 in graph_list :
dot_vertices . add ( v1 . dsa_dnstr )
for v2 in v1 . edge_from :
dot_edges . append ( ( v2 , v1 . dsa_dnstr ) )
dot_vertices . add ( v2 )
verify_properties = ( ' connected ' , ' directed_double_ring_or_small ' )
verify_and_dot ( ' intrasite_pre_ntdscon ' , dot_edges , dot_vertices ,
label = ' %s __ %s __ %s ' % ( site_local . site_dnstr ,
nctype_lut [ nc_x . nc_type ] ,
nc_x . nc_dnstr ) ,
properties = verify_properties , debug = DEBUG ,
2015-06-05 01:59:23 +03:00
verify = self . verify ,
2015-06-05 01:49:30 +03:00
dot_files = do_dot_files , directed = True )
# For each existing nTDSConnection object implying an edge
# from rj of R to ri such that j != i, an edge from rj to ri
# is not already in the graph, and the total edges directed
# to ri is less than n+2, the KCC adds that edge to the graph.
for vertex in graph_list :
dsa = self . my_site . dsa_table [ vertex . dsa_dnstr ]
for connect in dsa . connect_table . values ( ) :
remote = connect . from_dnstr
if remote in self . my_site . dsa_table :
vertex . add_edge_from ( remote )
DEBUG ( ' reps are: %s ' % ' ' . join ( x . rep_dsa_dnstr for x in r_list ) )
DEBUG ( ' dsas are: %s ' % ' ' . join ( x . dsa_dnstr for x in graph_list ) )
for tnode in graph_list :
# To optimize replication latency in sites with many NC
# replicas, the KCC adds new edges directed to ri to bring
# the total edges to n+2, where the NC replica rk of R
# from which the edge is directed is chosen at random such
# that k != i and an edge from rk to ri is not already in
# the graph.
#
# Note that the KCC tech ref does not give a number for
# the definition of "sites with many NC replicas". At a
# bare minimum to satisfy n+2 edges directed at a node we
# have to have at least three replicas in |R| (i.e. if n
# is zero then at least replicas from two other graph
# nodes may direct edges to us).
if r_len > = 3 and not tnode . has_sufficient_edges ( ) :
candidates = [ x for x in graph_list if
( x is not tnode and
x . dsa_dnstr not in tnode . edge_from ) ]
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " looking for random link for %s . r_len %d , "
" graph len %d candidates %d "
% ( tnode . dsa_dnstr , r_len , len ( graph_list ) ,
len ( candidates ) ) )
2015-06-05 01:49:30 +03:00
DEBUG ( " candidates %s " % [ x . dsa_dnstr for x in candidates ] )
while candidates and not tnode . has_sufficient_edges ( ) :
other = random . choice ( candidates )
DEBUG ( " trying to add candidate %s " % other . dsa_dstr )
if not tnode . add_edge_from ( other ) :
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " could not add %s " % other . dsa_dstr )
2015-06-05 01:49:30 +03:00
candidates . remove ( other )
else :
DEBUG_FN ( " not adding links to %s : nodes %s , links is %s / %s " %
( tnode . dsa_dnstr , r_len , len ( tnode . edge_from ) ,
tnode . max_edges ) )
# Print the graph node in debug mode
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " %s " % tnode )
2015-06-05 01:49:30 +03:00
# For each edge directed to the local DC, ensure a nTDSConnection
# points to us that satisfies the KCC criteria
if tnode . dsa_dnstr == dc_local . dsa_dnstr :
tnode . add_connections_from_edges ( dc_local )
2015-06-05 01:59:23 +03:00
if self . verify or do_dot_files :
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
dot_vertices = set ( )
for v1 in graph_list :
dot_vertices . add ( v1 . dsa_dnstr )
for v2 in v1 . edge_from :
dot_edges . append ( ( v2 , v1 . dsa_dnstr ) )
dot_vertices . add ( v2 )
verify_properties = ( ' connected ' , ' directed_double_ring_or_small ' )
verify_and_dot ( ' intrasite_post_ntdscon ' , dot_edges , dot_vertices ,
label = ' %s __ %s __ %s ' % ( site_local . site_dnstr ,
nctype_lut [ nc_x . nc_type ] ,
nc_x . nc_dnstr ) ,
properties = verify_properties , debug = DEBUG ,
2015-06-05 01:59:23 +03:00
verify = self . verify ,
2015-06-05 01:49:30 +03:00
dot_files = do_dot_files , directed = True )
def intrasite ( self ) :
""" The head method for generating the intra-site KCC replica
connection graph and attendant nTDSConnection objects
in the samdb
"""
# Retrieve my DSA
mydsa = self . my_dsa
2015-06-05 01:59:23 +03:00
DEBUG_FN ( " intrasite(): enter " )
2015-06-05 01:49:30 +03:00
# Test whether local site has topology disabled
mysite = self . my_site
if mysite . is_intrasite_topology_disabled ( ) :
return
detect_stale = ( not mysite . is_detect_stale_disabled ( ) )
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_CYAN ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
# Loop thru all the partitions, with gc_only False
for partdn , part in self . part_table . items ( ) :
self . construct_intrasite_graph ( mysite , mydsa , part , False ,
detect_stale )
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
# If the DC is a GC server, the KCC constructs an additional NC
# replica graph (and creates nTDSConnection objects) for the
# config NC as above, except that only NC replicas that "are present"
# on GC servers are added to R.
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_YELLOW ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
# Do it again, with gc_only True
for partdn , part in self . part_table . items ( ) :
if part . is_config ( ) :
self . construct_intrasite_graph ( mysite , mydsa , part , True ,
detect_stale )
# The DC repeats the NC replica graph computation and nTDSConnection
# creation for each of the NC replica graphs, this time assuming
# that no DC has failed. It does so by re-executing the steps as
# if the bit NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED were
# set in the options attribute of the site settings object for
# the local DC's site. (ie. we set "detec_stale" flag to False)
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_BLUE ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
# Loop thru all the partitions.
for partdn , part in self . part_table . items ( ) :
self . construct_intrasite_graph ( mysite , mydsa , part , False ,
False ) # don't detect stale
# If the DC is a GC server, the KCC constructs an additional NC
# replica graph (and creates nTDSConnection objects) for the
# config NC as above, except that only NC replicas that "are present"
# on GC servers are added to R.
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_RED ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
for partdn , part in self . part_table . items ( ) :
if part . is_config ( ) :
self . construct_intrasite_graph ( mysite , mydsa , part , True ,
False ) # don't detect stale
2015-06-05 01:59:23 +03:00
if self . readonly :
2015-06-05 01:49:30 +03:00
# Display any to be added or modified repsFrom
for connect in mydsa . connect_table . values ( ) :
if connect . to_be_deleted :
logger . info ( " TO BE DELETED: \n %s " % connect )
if connect . to_be_modified :
logger . info ( " TO BE MODIFIED: \n %s " % connect )
if connect . to_be_added :
2015-06-05 01:59:23 +03:00
debug . DEBUG_GREEN ( " TO BE ADDED: \n %s " % connect )
2015-06-05 01:49:30 +03:00
mydsa . commit_connections ( self . samdb , ro = True )
else :
# Commit any newly created connections to the samdb
mydsa . commit_connections ( self . samdb )
def list_dsas ( self ) :
""" Compile a comprehensive list of DSA DNs
These are all the DSAs on all the sites that KCC would be
dealing with .
This method is not idempotent and may not work correctly in
sequence with KCC . run ( ) .
: return : a list of DSA DN strings .
"""
self . load_my_site ( )
self . load_my_dsa ( )
self . load_all_sites ( )
self . load_all_partitions ( )
self . load_all_transports ( )
self . load_all_sitelinks ( )
dsas = [ ]
for site in self . site_table . values ( ) :
dsas . extend ( [ dsa . dsa_dnstr . replace ( ' CN=NTDS Settings, ' , ' ' , 1 )
for dsa in site . dsa_table . values ( ) ] )
return dsas
def load_samdb ( self , dburl , lp , creds ) :
""" Load the database using an url, loadparm, and credentials
: param dburl : a database url .
: param lp : a loadparm object .
: param cred : a Credentials object .
"""
self . samdb = SamDB ( url = dburl ,
session_info = system_session ( ) ,
credentials = creds , lp = lp )
def plot_all_connections ( self , basename , verify_properties = ( ) ) :
2015-06-05 01:59:23 +03:00
verify = verify_properties and self . verify
plot = self . dot_files
2015-06-05 01:49:30 +03:00
if not ( verify or plot ) :
return
dot_edges = [ ]
dot_vertices = [ ]
edge_colours = [ ]
vertex_colours = [ ]
for dsa in self . dsa_by_dnstr . values ( ) :
dot_vertices . append ( dsa . dsa_dnstr )
if dsa . is_ro ( ) :
vertex_colours . append ( ' #cc0000 ' )
else :
vertex_colours . append ( ' #0000cc ' )
for con in dsa . connect_table . values ( ) :
if con . is_rodc_topology ( ) :
edge_colours . append ( ' red ' )
else :
edge_colours . append ( ' blue ' )
dot_edges . append ( ( con . from_dnstr , dsa . dsa_dnstr ) )
verify_and_dot ( basename , dot_edges , vertices = dot_vertices ,
label = self . my_dsa_dnstr , properties = verify_properties ,
debug = DEBUG , verify = verify , dot_files = plot ,
directed = True , edge_colors = edge_colours ,
vertex_colors = vertex_colours )
def run ( self , dburl , lp , creds , forced_local_dsa = None ,
forget_local_links = False , forget_intersite_links = False ) :
""" Method to perform a complete run of the KCC and
produce an updated topology for subsequent NC replica
syncronization between domain controllers
"""
# We may already have a samdb setup if we are
# currently importing an ldif for a test run
if self . samdb is None :
try :
self . load_samdb ( dburl , lp , creds )
except ldb . LdbError , ( num , msg ) :
logger . error ( " Unable to open sam database %s : %s " %
( dburl , msg ) )
return 1
if forced_local_dsa :
self . samdb . set_ntds_settings_dn ( " CN=NTDS Settings, %s " %
forced_local_dsa )
try :
# Setup
self . load_my_site ( )
self . load_my_dsa ( )
self . load_all_sites ( )
self . load_all_partitions ( )
self . load_all_transports ( )
self . load_all_sitelinks ( )
2015-06-05 01:59:23 +03:00
if self . verify or self . dot_files :
2015-06-05 01:49:30 +03:00
guid_to_dnstr = { }
for site in self . site_table . values ( ) :
guid_to_dnstr . update ( ( str ( dsa . dsa_guid ) , dnstr )
for dnstr , dsa
in site . dsa_table . items ( ) )
self . plot_all_connections ( ' dsa_initial ' )
dot_edges = [ ]
current_reps , needed_reps = self . my_dsa . get_rep_tables ( )
for dnstr , c_rep in current_reps . items ( ) :
DEBUG ( " c_rep %s " % c_rep )
dot_edges . append ( ( self . my_dsa . dsa_dnstr , dnstr ) )
verify_and_dot ( ' dsa_repsFrom_initial ' , dot_edges ,
directed = True , label = self . my_dsa_dnstr ,
2015-06-05 01:59:23 +03:00
properties = ( ) , debug = DEBUG , verify = self . verify ,
dot_files = self . dot_files )
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
for site in self . site_table . values ( ) :
for dsa in site . dsa_table . values ( ) :
current_reps , needed_reps = dsa . get_rep_tables ( )
for dn_str , rep in current_reps . items ( ) :
for reps_from in rep . rep_repsFrom :
DEBUG ( " rep %s " % rep )
dsa_guid = str ( reps_from . source_dsa_obj_guid )
dsa_dn = guid_to_dnstr [ dsa_guid ]
dot_edges . append ( ( dsa . dsa_dnstr , dsa_dn ) )
verify_and_dot ( ' dsa_repsFrom_initial_all ' , dot_edges ,
directed = True , label = self . my_dsa_dnstr ,
2015-06-05 01:59:23 +03:00
properties = ( ) , debug = DEBUG , verify = self . verify ,
dot_files = self . dot_files )
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
for link in self . sitelink_table . values ( ) :
for a , b in itertools . combinations ( link . site_list , 2 ) :
dot_edges . append ( ( str ( a ) , str ( b ) ) )
properties = ( ' connected ' , )
verify_and_dot ( ' dsa_sitelink_initial ' , dot_edges ,
directed = False ,
label = self . my_dsa_dnstr , properties = properties ,
2015-06-05 01:59:23 +03:00
debug = DEBUG , verify = self . verify ,
dot_files = self . dot_files )
2015-06-05 01:49:30 +03:00
if forget_local_links :
for dsa in self . my_site . dsa_table . values ( ) :
dsa . connect_table = { k : v for k , v in
dsa . connect_table . items ( )
if v . is_rodc_topology ( ) }
self . plot_all_connections ( ' dsa_forgotten_local ' )
if forget_intersite_links :
for site in self . site_table . values ( ) :
for dsa in site . dsa_table . values ( ) :
dsa . connect_table = { k : v for k , v in
dsa . connect_table . items ( )
if site is self . my_site and
v . is_rodc_topology ( ) }
self . plot_all_connections ( ' dsa_forgotten_all ' )
# These are the published steps (in order) for the
# MS-TECH description of the KCC algorithm ([MS-ADTS] 6.2.2)
# Step 1
self . refresh_failed_links_connections ( )
# Step 2
self . intrasite ( )
# Step 3
all_connected = self . intersite ( )
# Step 4
self . remove_unneeded_ntdsconn ( all_connected )
# Step 5
self . translate_ntdsconn ( )
# Step 6
self . remove_unneeded_failed_links_connections ( )
# Step 7
self . update_rodc_connection ( )
2015-06-05 01:59:23 +03:00
if self . verify or self . dot_files :
2015-06-05 01:49:30 +03:00
self . plot_all_connections ( ' dsa_final ' ,
( ' connected ' , ' forest_of_rings ' ) )
2015-06-05 01:59:23 +03:00
debug . DEBUG_MAGENTA ( " there are %d dsa guids " %
len ( guid_to_dnstr ) )
2015-06-05 01:49:30 +03:00
dot_edges = [ ]
edge_colors = [ ]
my_dnstr = self . my_dsa . dsa_dnstr
current_reps , needed_reps = self . my_dsa . get_rep_tables ( )
for dnstr , n_rep in needed_reps . items ( ) :
for reps_from in n_rep . rep_repsFrom :
guid_str = str ( reps_from . source_dsa_obj_guid )
dot_edges . append ( ( my_dnstr , guid_to_dnstr [ guid_str ] ) )
edge_colors . append ( ' # ' + str ( n_rep . nc_guid ) [ : 6 ] )
verify_and_dot ( ' dsa_repsFrom_final ' , dot_edges , directed = True ,
label = self . my_dsa_dnstr ,
2015-06-05 01:59:23 +03:00
properties = ( ) , debug = DEBUG , verify = self . verify ,
dot_files = self . dot_files ,
2015-06-05 01:49:30 +03:00
edge_colors = edge_colors )
dot_edges = [ ]
for site in self . site_table . values ( ) :
for dsa in site . dsa_table . values ( ) :
current_reps , needed_reps = dsa . get_rep_tables ( )
for n_rep in needed_reps . values ( ) :
for reps_from in n_rep . rep_repsFrom :
dsa_guid = str ( reps_from . source_dsa_obj_guid )
dsa_dn = guid_to_dnstr [ dsa_guid ]
dot_edges . append ( ( dsa . dsa_dnstr , dsa_dn ) )
verify_and_dot ( ' dsa_repsFrom_final_all ' , dot_edges ,
directed = True , label = self . my_dsa_dnstr ,
2015-06-05 01:59:23 +03:00
properties = ( ) , debug = DEBUG , verify = self . verify ,
dot_files = self . dot_files )
2015-06-05 01:49:30 +03:00
except :
raise
return 0
def import_ldif ( self , dburl , lp , creds , ldif_file ) :
""" Import all objects and attributes that are relevent
to the KCC algorithms from a previously exported LDIF file .
The point of this function is to allow a programmer / debugger to
import an LDIF file with non - security relevent information that
was previously extracted from a DC database . The LDIF file is used
to create a temporary abbreviated database . The KCC algorithm can
then run against this abbreviated database for debug or test
verification that the topology generated is computationally the
same between different OSes and algorithms .
: param dburl : path to the temporary abbreviated db to create
: param ldif_file : path to the ldif file to import
"""
try :
2015-06-05 06:09:05 +03:00
self . samdb = ldif_import_export . ldif_to_samdb ( dburl , lp , ldif_file ,
self . forced_local_dsa )
except ldif_import_export . LdifError , e :
2015-06-05 01:49:30 +03:00
print e
return 1
return 0
def export_ldif ( self , dburl , lp , creds , ldif_file ) :
""" Routine to extract all objects and attributes that are relevent
to the KCC algorithms from a DC database .
The point of this function is to allow a programmer / debugger to
extract an LDIF file with non - security relevent information from
a DC database . The LDIF file can then be used to " import " via
the import_ldif ( ) function this file into a temporary abbreviated
database . The KCC algorithm can then run against this abbreviated
database for debug or test verification that the topology generated
is computationally the same between different OSes and algorithms .
: param dburl : LDAP database URL to extract info from
: param ldif_file : output LDIF file name to create
"""
try :
2015-06-05 06:09:05 +03:00
ldif_import_export . samdb_to_ldif_file ( self . samdb , dburl , lp , creds ,
ldif_file )
except ldif_import_export . LdifError , e :
2015-06-05 01:49:30 +03:00
print e
return 1
return 0