It’s easy to forget that when we say “Directory Services” we are really talking about multiple technologies. I remember when the idea that what we support is so much more than simply a user account repository first hit me. It happened when I first read the Windows 2000 Distributed Systems Guide from the Windows 2000 Resource Kit. This was, and in many ways still is, the “go to” reference and starting point for DS knowledge, and it made the idea that we support a collection of disparate but often interdependent technologies really hit home to me.
One of those technologies is Lightweight Directory Access Protocol (LDAP). LDAP is not truly a separate technology but rather a network protocol for data retrieval and modification. Active Directory is accessible using LDAP and how AD replicates is conceptually and practically built on the LDAP actions of Add, Modify, Move and Delete (for more info on that read the Distributed Systems Guide on originating updates, pages 315-317) . This is important to keep in mind since it gives a deeper understanding of how AD works under the hood.
LDAP is used in other DS components as well. If you were to capture the network traffic from your domain-joined computer while it was processing group policy you would see a series of LDAP queries to the domain as the list of group policies which apply to the user (or computer) are assessed for applicability.
The true value in LDAP is its use as a standard for applications to interact with AD. The actions of Add, Modify, Move and Delete I mentioned above are exposed in code that a developer can use write into their applications in order to store and retrieve data in AD. This is one of the many things that make Microsoft Active Directory and Lightweight Directory Service (also known as ADAM) such great solutions for writing business solutions. LDAP is simply one of many exposed entry points for data access in AD-otherwise known as Active Directory Service Interfaces. One stop shopping-code samples and references, a wide array services interfaces, reliability and tough integrated security are just how Microsoft AD rolls.
If you’ve read my blog posts before you know to expect that there is probably a specific scenario that brought all these thoughts together and instigated this blog post.
This scenario started with a customer who was developing an “in house” application which would query the Active Directory for selected attribute values for a list of users who belonged to a specific security group as part of the many things it did to get its intended purpose accomplished. This group was either domain local or universal in scope and as such contained members from each of the 4 domains in their forest.
The problem they saw was that the queries-which were directed to a domain controller in the root domain-would only return root domain users in the results response despite the fact that users from the other three domains were also members of that security group. This AzMan article outlines what an LDAP query intended to gather a list of users in a group should generally look like.
So the first order of business in troubleshooting LDAP is to see the LDAP query for ourselves rather than relying on what we expect or assume it may be. There are several ways to do that easily. First, you can take a network capture of the query using Netmon or Wireshark and simply filter for LDAP (provided the LDAP session is not SSL encrypted). This technique of viewing the network traffic in order to see the bind type can be useful since we know that an unauthenticated LDAP bind will not receive an LDAP referral given the default Active Directory security in later versions. More information on LDAP authentication and signing can be found here.
Or we can configure the registry keys below on the domain controller receiving the query to provide the details in the Directory Service event log. The caveat being that you must know which domain controller will be servicing the query.
15 Field Engineering = 5
(add dword values)
Expensive Search Results Threshold: = 1
Inefficient Search Results Threshold: = 1
Which in our case gives us this result:
Internal event: A client issued a search operation with the following options. Client:192.168.2.4 Starting node: DC=tspring,DC=com Filter: (memberOf=CN=coolpeople,OU=DallasUsers,DC=tspring,DC=com) Search scope: subtree Attribute selection:displayName,objectSid,proxyAddresses,cn,sAMAccountName,distinguishedName,groupType Server controls: Visited entries:5 Returned entries:1
Some folks who are reading this have already discerned a possible problem or two. What port was the query directed to? Or, was the option for “chase referrals” passed in with the query?
Let’s take a moment to talk about that. The way LDAP compliant servers work is by simply providing an answer to a query that is passed to them by a client. All servers, Microsoft or otherwise, must comply to the public RFCs which define how LDAP will behave.
Getting back to our concern. What if the specific domain controller doesn’t have all of the data that the query is asking for? Such as when it resides in another domain? When that happens the DC will issue an LDAP network response called a referral.
For Active Directory the basic rules around when you will receive a referral are when the client indicates in the query they will chase them, or when the DC answering the query notices that that domain name which all or part of the query is target at is not one which it recognizes as part of the forest. It is able to recognize internal domains by reading the list of crossref objects it contains in its copy of the forests Configuration container which essentially show the domains which the forest knows of-both internal to the forest and external to it (trusted or trusting).
Crossref objects are good things to consider when reviewing LDAP referral behaviors.
We have a KB article which describes the process of reviewing the crossrefs and determining whether to issue a referral. Here is the most pertinent portion of that, which assumes that the LDAP query option of “chase referrals” was not passed in the initial query:
In a situation where we are seeing a different or incomplete result being returned for a query like this our initial goal should be to see if the same results happen when we use LDP.EXE (a Support Tool in Server 2003 but included during role install in 2008 and later).
In LDP.EXE, after we bind and connect to our directory, we can issue a query by selecting Search from the Browse menu.
To set “chase referrals” in the query you need to select Options and put the little check there for that.
Where’s the value in emulating the LDAP query using LDP.EXE? The value lies in ruling out any behavior that is inherent in the customized code being used to issue the query. If the above query without the referral chasing option selected gives the exact same results as the failing query, and the with the option selected we get the correct results (every user who is a member of that group) then we have a firm idea of what is happening.
In this case, since the users are all in domains local to the forest the initial query must contain the chase referral’s option in order to receive all users who are members of that group throughout the forest.
However, that is not the best option. Consider the act of asking for some information and then getting only some of the results, then being referred elsewhere for the rest. In itself that’s not a huge performance loss but if you repeat that action many times in sequence or simultaneously then there will be a performance loss both on the network and on the domain controller servicing the requests.
This is where your Global Catalogs come in handy. The global catalog contains information from each domain in the forest. All we need in order to service a request like we have discussed in this blog post is to direct the query to the global catalog rather than to a specific domain and end up chasing referrals all over your network.
How do we direct the query to the global catalog? Rather than relying on the default connection port of 389 simply send the query to TCP port 3268 (or 3269 if SSL encrypted) explicitly in your connection. Here's a MSDN link which goes over this in detail.
There are times when the initial answer isn’t the best one. In this case, the answer to the question of ‘why is the query not giving us all of the group members?’ was ‘because the query didn’t specify to chase referrals’. But the best option was to never query the local directory at all and instead direct the query to the global catalog.