1. NBT: NetBIOS over TCP/IP


    

1.1 A Short Bio of NetBIOS

In those days spirits were
brave, the stakes were high,
men were REAL men,
women were REAL women,
and small furry creatures from
Alpha Centauri were REAL
small furry creatures from
Alpha Centauri.
-- The Hitchhikers Guide
To The Galaxy
,
Douglas Adams

  

It all started back in the frontier days of the PC when Microsoft was a lot smaller, IBM seemed a whole lot bigger, and Apple owned personal computer territory as far as the eye could see. Back then, you didn't need no dang standards. If you wanted to sell LANs, you just went out and branded yourself a protocol. Apple had AppleTalk, Digital had DECnet and, for their longhorn Mainframes, IBM had Systems Network Architecture (SNA). SNA was a mighty big horse for little PCs, so IBM hired on a company called Sytek [Annotation] and together they rustled up a product they called "PC Network". Not an inspiring name, but it was a simpler time.

PC Network was a Local Area Network (LAN) system designed to support about 80 nodes at best, with no provision for routing. NetBIOS (Network Basic Input Output System) was the software interface to the PC Network hardware. It offered a set of commands that could control the hardware, establish and delete sessions, transfer data, etc.

1.1.1 NetBIOS and DOS: The Early Years

Starting with DOS version 3.1, Microsoft used the NetBIOS API to transport SMB file service messages. They created something called a redirector, and its job was to catch disk drive or port references (eg. "C:" or "LPT3:") and look them up in a table. If the device was not in the table, the call was passed along to DOS. If the device was in the table, then the call would be redirected. For example:

  • Using the SUBST command, a user could substitute a drive letter for a local path. This simple aliasing provided convenient shortcuts for long path names.

    subst S: C:\FILES\DEEP\IN\A\DIRECTORY
  • Using the NET command, a drive letter could be mapped to a remote file service. So, if the redirector found a remote service entry in its table, it would convert the request into an SMB packet and send it out via NetBIOS.

    net use N: \\SERVER\SERVICE

    Note the double backslash preceding the server name. This syntax is part of Microsoft's "Universal Naming Convention" (UNC) for network services.

[Annotation] These commands are still available from within the DOS shells of contemporary Windows products. It is worth-while to fiddle with them a bit. From the DOS prompt you can type NET HELP for a summary of the NET command and its options1.


1.2 Speaking NetBIOS

The hardware part of IBM's PC Network is no longer in use and the protocol that actually ran on the wire is all but forgotten, yet the NetBIOS API remains. Vast untold hoards of programs--including DOS itself--were written to use NetBIOS. Like COBOL, it may never die.
 

Genuine Imitation
-- well known oxymoron

  

Many vendors, eager for a piece of the Microsoft desktop pie, figured out how to implement the NetBIOS API on top of other protocols. There is NetBIOS over DECnet, NetBIOS over NetWare, NetBIOS over mashed potatoes and gravy with creamed corn, NetBIOS over SNA, NetBIOS over TCP/IP, and more. Of these, the most popular, tasty, and important is NetBIOS over TCP/IP, and that's what this chapter is really all about.

[Figure 1.1]

NetBIOS over TCP/IP is sometimes called NetBT or NBT. Folks from IBM--for reasons unfathomable--sometimes call it TCPBEUI. NBT is the simplest and most common name, so we'll stick with that.

On the 7-layer OSI reference model, NetBIOS is a session-layer (layer 5) API. Under DOS and its offspring, applications talk to NetBIOS by filling in a record structure known as a Network Control Block (NCB) and signaling an interrupt. The NCBs are used to pass commands and messages between applications and the underlying protocol stack.

Fortunately, the NetBIOS API is specific to DOS and its kin. Unix and other systems do not need to implement the NetBIOS API, as there is no legacy of programs that use it. Instead, these systems participate in NBT networks by directly handling the TCP and UDP packets described in two Internet Engineering Task Force (IETF) Request for Comments documents: RFC 1001 and RFC 1002 (known collectively as Internet Standard #19). These RFCs describe a set of services which work together to create virtual NetBIOS LANs over IP.

1.2.1 Emulating "NetBIOS LANs"

...there is often confusion in
the use of some of the terms...
-- NetBIOS NetBEUI NBF
Networking
,
Timothy D. Evans

  

At this point, we hit an interesting twist in the terminology. NetBIOS is a driver that presents an API; it is neither a protocol nor a topology. The API does, however, make a number of assumptions about the workings of the underlying network, and it presents some quirky restrictions. The terms "NetBIOS Network" and "NetBIOS LAN" are commonly used to identify the network architecture that is, essentially, defined by the NetBIOS API.

RFC 1001 and 1002 list three basic services which must be supported in order to implement NetBIOS LAN emulation. These are:

  • the Name Service,
  • the Datagram Service, and
  • the Session Service.

The Name Service is used to map NetBIOS names (addresses) to IP addresses in the underlying IP network. The Datagram Service provides for the delivery of NetBIOS datagrams via UDP, and the Session Service is used to establish and maintain point-to-point, connection-oriented NetBIOS sessions over TCP.

1.2.1.1 The NetBIOS Name Service

The NetBIOS name service is
the collection of procedures
through which nodes acquire,
defend, and locate the holders
of NetBIOS names.
-- RFC 1001, Section 15
  

The NetBIOS LAN architecture is very simple. No routers, no switches--just a bunch of nodes connected to a (virtual) wire. There is no need for separate hardware addresses, network addresses, or even port numbers as there is in IP. Instead, the communications endpoints are identified by 16-byte strings known as "NetBIOS Names".

NetBIOS addressing is dynamic. Applications may add names as needed, and remove those names when they are finished. Each node on the LAN will also have a default name, known as the Machine Name or the Workstation Service Name, which is typically added when NetBIOS starts. The process of adding a name is called registration.

There are two kinds of names that can be registered: unique and group. Group names may be shared by multiple clients, thus providing a mechanism for multicast. In contrast, unique names may only be used by one client per LAN. Keep in mind, though, that these are virtual LANs which may actually be spread out across different subnets in a routed IP internetwork.

[Figure 1.2]

The Name Service is supposed to keep track of all of the NetBIOS names in use within the virtual LAN, and ensure that messages sent to a given NetBIOS name are directed to the correct underlying IP address. It does this in two ways:
 
On an IP LAN:  Each node keeps a list of the names that it has registered (that is, the names it "owns"). When sending a message, the first step is to send an IP broadcast query, called a NAME QUERY REQUEST. If there is a machine on the IP LAN that owns the queried name, it will reply by sending a NAME QUERY RESPONSE.

So, to send a message to the node which has registered the name EADFRITH the sender calls out "Yo! Eadfrith!". EADFRITH responds with an "I am here!" message, giving its IP address.

[Figure 1.3]

This is known as 'B mode' (broadcast) name resolution, and participants are referred to as 'B nodes'. In B mode, each node keeps track of--and answers queries for--its own names, so the NetBIOS Name Service "database" is a distributed database.
 

Over a Routed Internet:  Broadcasts aren't meant to cross subnet boundaries, so a different mechanism is used when the nodes are separated by routers.

The Network Administrator chooses a machine to be the NetBIOS Name Server (NBNS, aka WINS Server2). Typically this will be a Unix host running Samba, or a Windows NT or W2K server. In order to use the NBNS, all of the nodes that are participating in the virtual NetBIOS LAN must be given the server's IP address. This can be done by entering the address in the client's NetBIOS configuration or, on Windows systems, via DHCP.

NBT client nodes send NetBIOS name registrations and queries directly to the NBNS, which maintains a central database of all registered names in the virtual LAN. This is known as 'P mode' (point-to-point) name resolution, and participants are referred to as 'P nodes'.

[Figure 1.4]

These are the two basic modes of NetBIOS Name Resolution over NBT. There are, of course, others. The RFCs describe 'M mode' (mixed mode), which combines P and B mode characteristics. 'H mode' (hybrid mode) was introduced later. It is similar to M mode except for the order in which B and P mode behavior is applied.

The Name Service runs on UDP port 137. According to the RFCs the use of TCP port 137 can be negotiated for some queries, though few (if any) implementations actually support this.

1.2.1.2 The NetBIOS Datagram Service

Upon receipt, the duct tape
is removed and the paper
copy of the datagram is
optically scanned into
a[n] electronically
transmittable form.
-- RFC 1149
  

In the IP world, TCP provides connection-oriented sessions in which packets are acknowledged, put in order, and retransmitted if lost. This creates the illusion of a continuous, sequential data stream from one end to the other. In contrast, UDP datagrams are simply sent. Thus, UDP requires less overhead, but it is less reliable than TCP. NetBIOS also provides connection-oriented (session) and connectionless (datagram) communications. Naturally, NBT maps NetBIOS sessions to TCP and NetBIOS datagrams to UDP.

The Datagram Distribution Service is the NBT service that handles NetBIOS datagram transport. It runs on UDP port 138, and can handle unicast (also known as "specific"), multicast (group), and broadcast NetBIOS datagrams.

Unicast (Specific):  

The handling of unicast datagrams is fairly straight-forward. The Name Service is used to resolve the destination name to an IP address. The NetBIOS packet is then encapsulated in a UDP packet and sent to the specified IP.
 

Multicast (Group)
and Broadcast:
 

According to the RFCs, a B node can simply encapsulate NetBIOS multicast and broadcast datagrams in UDP and send them to the IP broadcast address. The UDP datagram will then be picked up by all local nodes listening on the Datagram Service port (138/UDP). Thus, NetBIOS broadcast datagrams will reach all nodes in the virtual LAN. In the case of multicast datagrams, nodes which are not members of the group (have not registered the group name) will discard the message.

P, M, and H nodes are a bit more complicated, as you might expect. When the virtual LAN extends beyond the physical LAN, an IP broadcast will not reach all of the nodes in the NetBIOS name space. In order to deliver group and broadcast datagrams, the NBNS database must be consulted. How this is (or isn't) actually done will be explained in strikingly painful detail later on. Section 1.5 is dedicated to the workings of datagram distribution.
 

In theory, theory and practice
are the same. In practice,
they're not.
-- Unknown
  

The Datagram Service is probably the second-least well understood aspect of NBT, most likely because correct implementation isn't critical to filesharing. Many implementations get it wrong, and there is much debate over the value of getting it right.

1.2.1.3 The NetBIOS Session Service

The Session Service is the traditional transport for SMB, and this is our primary reason for caring about NetBIOS at all. The Session Service runs on TCP port 1393. There is no particular mechanism for multicast or broadcast because each session is, by definition, a one-to-one connection. The RFCs do, however, briefly discuss what might happen if a session setup request were sent to a group name (see RFC 1001, Section 16.1.1.2).

We will get to the details of session creation, use, and closure when we discuss Session Service implementation.

Weirdness Alert:

TCP/138 has no defined behavior under NBT and Microsoft never implemented support for NBT Name Resolution over TCP/137, yet some versions of Windows seem to listen on these two TCP ports when NBT is active.
C:\> netstat -a

Active Connections

  Proto  Local Address          Foreign Address        State
  TCP    paris:137              PARIS:0                LISTENING
  TCP    paris:138              PARIS:0                LISTENING
  TCP    paris:nbsession        PARIS:0                LISTENING
  UDP    paris:nbname           *:*
  UDP    paris:nbdatagram       *:*

It turns out that this is due to a known bug in the netstat utility included with older Windows releases:

email


From: Jean-Baptiste Marchand
To: Chris Hertel

Hello,

I've noticed that, in section 1.2, there is a weirdness alert about the Windows netstat program showing TCP ports 137 and 138 open, whereas only UDP ports 137 and 138 are actually opened by the NetBT driver.

In fact, it is a known problem in Windows NT (this is fixed in Windows 2000 and later) that netstat shows TCP ports opened, whereas only UDP ports with the same number are opened. This is documented in an entry of Microsoft's knowledge base (194171).

This article states that this is only a display problem. This is true and can be verified using any TCP port scanner.
 

1.2.2 Scope: The Final Frontier

This is a good point at which to get up, stretch, make a nice hot cup of tea for yourself, take a soothing bath, play with your cat, go for a long walk in the park, take dance lessons, volunteer in your community, sort and organize your old photographs, or join a United Nations Peace Keeping Force. The Datagram Service was previously described as 'the second-least well understood aspect of NBT'. Guess which bit wins first prize.

Scope is an oddity of NBT, not because it was a bad idea (though perhaps it was) but because few have ever bothered to really understand it. In practice this feature is rarely used, in part because it is rarely implemented to its full potential.

In the RFCs, the term scope is used as a name for:
 

Proof by Scope: The proof
of this theorem is beyond the
scope of this book.
-- Jonathan Young, PhD.
  
  • the set of NetBIOS nodes that participate in an NBT virtual LAN,
  • an identifier used to distinguish one virtual LAN from another, and
  • that which is included within the purpose of the RFC document.

...but the last of these is beyond the scope of this discussion, so let's take a closer look at the first two.

Scope is explained in RFC 1001, Section 9, which starts off by saying:

A "NetBIOS Scope" is the population of computers across which a registered NetBIOS name is known. NetBIOS broadcast and multicast datagram operations must reach the entire extent of the NetBIOS scope.

This basically means all nodes connected to the virtual LAN. So, for B nodes the NetBIOS scope consists of all nodes within the local IP broadcast domain that are running NBT. For P nodes, the NetBIOS scope includes all nodes across the routed internetwork that run NBT and share the same NBNS. For an M or H node, the scope is the union of the local broadcast and the NBNS scopes.

This is all quite straight-forward when all NBT nodes are of the same node type, but strange things can happen when you mix modes, particularly in a routed environment.

P & B: 

Two separate scopes are defined. The B nodes will only see other B nodes on the same wire, and the P nodes will only see other P nodes using the same NBNS. If creating separate NetBIOS vLANs is your goal, then mixing P and B nodes on the same wire is perfectly okay.
 

P & M: 

This results in a single scope. The M nodes perform all of the functions of a P node, including registering their names with the NBNS. Thus, all P nodes can see all M nodes, though M nodes on the same wire can bypass the NBNS when resolving names.
 

B & M: 

On a single, non-routed IP LAN there will be only one scope. The M nodes will register and resolve names via the broadcast mechanism, making their use of the NBNS pointless.

Things start going terribly wrong, though, when the NetBIOS vLAN is distributed across multiple subnets in a routed internetwork. When this happens the result is multiple, intersecting scopes. B nodes on one subnet will not be able to see any node on any other subnet. M nodes will see all other M nodes, but only the B nodes on their local wire. Thus, parts of the NetBIOS vLAN are hidden from other parts, yet all are somewhat connected via the common M node scope.

One result of this mess is the potential for name collisions. A B node could register a name that is already in the NBNS database, and an M node might register a name that one or more B nodes on remote subnets have already claimed. Name resolution then essentially fails because the same name does not resolve to the same IP address across the fractured scope.

The RFCs recognize this potential for disaster and warn against it. See RFC 1001, Section 10.
 

P, B, & M: 

From bad to worse. The P nodes can see all of the M nodes which can see some of the B nodes which cannot see any P nodes at all. B nodes and M (or H) nodes don't mix.

We now have a good handle on our first definition of scope: "the set of NetBIOS nodes that participate in a virtual LAN". What about the second: "an identifier used to distinguish one virtual LAN from another"? (This is a good point at which to get up, stretch, make a nice hot cup of tea for yourself...)

Every scope has a name, called the Scope Identifier (Scope ID). The most common Scope ID is the empty string: "". Indeed, this is the default in Windows, Samba, jCIFS, and every other system encountered so far. The only problem with this name is that it becomes too easy to forget that the Scope ID exists.

We have already seen that distinct NetBIOS vLANs can be created by using the behavior of B, P, M, and H nodes to create separate scopes. For example, multiple scopes are defined when multiple independent NBNS's provide service for P nodes. B nodes on separate IP LANs are also in separate scopes, and so on. The Scope ID provides another, more refined mechanism for separating scopes.
 

I can listen,
but I can't hear.
-- Turn a Deaf Ear
Rab Noakes
  

Think of an IP LAN with a bunch of B nodes. Some of the B nodes have Scope ID DOG, and others have Scope ID CAT. Only members of scope DOG will listen to messages sent with that ID; the cats will ignore messages sent to the dogs. So, even though all of the B nodes are on the same wire, we have two separate scopes. The same applies to P and M nodes. The Scope IDs identify, and separate, virtual NetBIOS LANs. Note, though, that an NBNS will handle requests from any node regardless of scope. A single NBNS server can, therefore, support multiple scopes.

[Figure 1.5]

According to RFC 1001/1002, a node may belong to more than one scope. In practice, however, it is much easier to choose a single scope and stick with it. This is particularly true for DOS and Windows systems because NetBIOS itself has no concept of scope. The Scope ID is a feature of NBT, and programs that call the NetBIOS API have no way of telling NBT which scope to use.

The RFCs suggest that extensions might be added to NetBIOS to manage scope, but using those extensions would require changes to applications. Further, other NetBIOS transports would not support the extensions which would result in compatibility problems.

Confusion Alert:

Scope IDs are used by the Name Service and the Datagram Service, but not the Session Service. This seems awkward at first, but it makes sense when you consider that the NetBIOS API itself has no knowledge of Scope.

Once again, Scope IDs serve only to identify virtual NetBIOS LANs. They operate at a lower level than the NetBIOS API.
 

1.2.3 Thus Endeth the Overview

Now that you have a clear and precise understanding of the workings of NetBIOS over TCP, go read RFC 1001. That ought to muddy the waters a bit. Clear or not, the next step is to write some code and see what works--and what doesn't. Actual implementation will provide a lot of opportunity to discuss details, bugs, and common errors.


1.3 The Basics of NBT Implementation

This is where the rubber
meets the road.
-- Unknown
  

Ready?

We have identified the three key parts of NBT: the Name Service, the Datagram Service, and the Session Service. This is enough to get us started. We will begin by coding up a simple Name Service Query, just to see what kind of trouble that gets us into.

Before we start, though, it's probably a good idea to check our tools.

Sniffer
You need one of these. If you have Windows systems available, see if you can get a copy of Microsoft's NetMon (Network Monitor). You will want the latest and most complete version. The advantage of NetMon is that Microsoft have included parsers for many of their protocols.

Another excellent choice is Ethereal, an Open Source protocol analyzer portable to most Unix-ish platforms and to Windows. It can create its own captures or read captures made by several other sniffer packages, including TCPDump and NetMon. Richard Sharpe and Tim Potter of the Samba Team have worked on NetBIOS and SMB packet parsers for Ethereal, which helps a big bunch.

Language
There are a lot of programming languages out there. Samba is written in C, and jCIFS is in Java. The key factors when choosing a language for your implementation are:
  • Good network coding capabilities.
  • That warm fuzzy feeling you get when you code in a language you truly grok.
Meditate on that for a while. Bad karma in the coding environment will distract you from your purpose.

Test Environment
If you do not have a couple of hubs, a router, various Windows boxes, and some Samba servers in your home, you may need to do your testing at the office. Netiquette and job security would suggest that you test after hours. (Um... actually, you probably shouldn't do any testing on a production network, and check office policy before you sniff.)

Medication
An aromatic black tea, such as a good Earl Grey, is best. Try Lapsang Souchong to get through really difficult coding sessions. Those sweet, mass-produced, over-caffeinated soft drinks will disturb your focus.

Ready!

In this section, we will implement a broadcast NAME QUERY REQUEST. That is, B mode name resolution. This will allow us to introduce some of the basic concepts and establish a frame of reference. In other words, we have to start somewhere and this seems to be as good a place as any.

1.3.1 You Got the Name, Look Up the Number

Shirley Shirley Bo Birley
Banana Fanna Fo Firley
-- The Name Game,
Shirley Ellis
  

The structure of an NBT name query is similar to that of a Domain Name System query. As RFC 1001, section 11.1.1, explains:

The NBNS design attempts to align itself with the Domain Name System in a number of ways.

The goal of this attempted alignment was an eventual merger between the NBNS and the DNS system. The NBT authors even predicted dynamic DNS update. With Windows 2000, Microsoft did move CIFS naming services to Dynamic DNS, though the mechanism is not quite what was envisioned by the authors of the NBT RFCs.

1.3.1.1 Encoding NetBIOS Names

RFC 1001 & 1002 reference RFC 883 when discussing domain name syntax rules. RFC 883 was later superseded by RFC 1035, but both give the same preferred4 syntax for domain names:

    <domain>      ::= <subdomain> | " "
    <subdomain>   ::= <label> | <subdomain> "." <label>
    <label>       ::= <letter> [ [ <ldh-str> ] <let-dig> ]
    <ldh-str>     ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
    <let-dig-hyp> ::= <let-dig> | "-"
    <let-dig>     ::= <letter> | <digit>
    <letter>      ::= any one of the 52 alphabetic characters
                      A through Z in upper case and a through
                      z in lower case
    <digit>       ::= any one of the ten digits 0 through 9

This is the syntax that the NBT authors tried to match. Unfortunately, except for the 16-byte length restriction, there are few syntax rules for NetBIOS names. With a few notable exceptions just about any octet value may be used, so the NBT authors came up with a scheme to force NetBIOS names into compliance. Here's how it works:

  • Names shorter than 16 bytes are padded on the right with spaces. Longer names are truncated.
  • Each byte is divided into two nibbles (4 bits each, unsigned)5. The result is a string of 32 integer values, each in the range 0..15.
  • The ASCII value of the letter 'A' (65, or 0x41) is added to each nibble and the result is taken as a character. This creates a string of 32 characters, each in the range 'A'..'P'.

This is called First Level Encoding, and is described in RFC 1001, Section 14.1.

Using First Level Encoding, the name "Neko" would be converted as follows:

char hex split + 'A' hex result
N = 0x4E 0x04 +
0x0E +
0x41
0x41
= 0x45 =
= 0x4F =
E
O
e = 0x65 0x06 +
0x05 +
0x41
0x41
= 0x47 =
= 0x46 =
G
F
k = 0x6B 0x06 +
0x0B +
0x41
0x41
= 0x47 =
= 0x4C =
G
L
o = 0x6F 0x06 +
0x0F +
0x41
0x41
= 0x47 =
= 0x50 =
G
P
' ' = 0x20 0x02 +
0x00 +
0x41
0x41
= 0x43 =
= 0x41 =
C
A
' ' = 0x20 0x02 +
0x00 +
0x41
0x41
= 0x43 =
= 0x41 =
C
A
:
:

This results in the string: EOGFGLGPCACACACACACACACACACACACA
Lovely, isn't it?
 

Coding style is very
personal...
-- Linus Torvalds
  

...and here is our first bit of code:

[Listing 1.1]

This function reads up to 16 characters from the input string name and converts each to the encoded format, stuffing the result into the target string dst. The space character (0x20) always converts to the two-character value "CA" so, if the source string is less than 16 bytes, we simply pad the target string with CACA. Note that the target character array must be at least 33 bytes long--one extra byte to account for the nul terminator6.
 



Bedevere:
  Oooohoohohooo!
Lancelot:
  No, no. 'Aaaauugggh',
at the back of the throat.
Aaauugh.
-- Monty Python And
The Holy Grail
,
Monty Python's Flying
Circus
  

Typo Alert:

RFC 1001 provides an example of First Level Encoding in Section 14.1. The string "The NetBIOS name" is encoded as:

FEGHGFCAEOGFHEECEJEPFDCAHEGBGNGF

Decoding this string, however, we get "Tge NetBIOS tame".
Perhaps it's a secret message.

The correct encoding would be:

FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF

1.3.1.2 Fully Qualified NBT Names

Now that we've managed to convert the NetBIOS name into a DNS-aligned form, it is time to combine it with the NBT Scope ID. The result will be a fully-qualified NBT address, which we will call the "NBT Name". To be pedantic, when the RFCs talk about First Level Encoding, this fully qualified form is what they really mean.

As expected, the syntax of the Scope ID follows the DNS recommendations given in RFC 883 (and repeated in RFC 1035). That is, a Scope ID looks like a DNS name. So, if the Scope ID is cat.org, and the NetBIOS name is Neko, the resultant NBT name would be:

EOGFGLGPCACACACACACACACACACACACA.CAT.ORG

Imagine typing that into your web browser. This is why the RFC 1001/1002 scheme for merging the NBNS with the DNS never took hold.

1.3.1.3 Second Level Encoding

Now that we have an NBT name in a nice familiar format, it is time to convert it into something else.

DNS names (and, therefore, NBT names) are made up of labels separated by dots. Dividing the name above into its component labels gives us:

length label
32  EOGFGLGPCACACACACACACACACACACACA
3 CAT
3 ORG
0 <nul>

The Second Level Encoded NBT name is a concatenation of the lengths and the labels, as in:

'\x20' + "EOGFGLGPCACACACACACACACACACACACA" + '\x03' + "CAT" + '\x03' + "ORG" + '\0'

The empty label at the end is important. It is a label of zero length, and it represents the root of the DNS (and NBT) namespace. That means that the final nul byte is part of the encoded NBT name, and not a mere terminator. In practice, you can manipulate the encoded NBT name as if it were a nul-terminated string, but always keep in mind that it is really a series of length-delimited strings7.
 

Any useful piece of code
deserves to be rewritten
at least once.
-- Unknown
  

Our second bit of code will convert a NetBIOS name and Scope ID into a Second Level Encoded string:

[Listing 1.2]

Not the prettiest piece of code, but it does the job. We will run through the function quickly, just to familiarize you with the workings of this particular programmer's twisted little brain. If the code is fairly obvious to you, feel free to skip ahead to the next section.

  if( NULL == L1_Encode( &dst[1], name ) )
    return( -1 );
  dst[0] = 0x20;
  lenpos = 33;

Call L1_Encode() to convert the NetBIOS name into its First Level Encoded form, then prefix the encoded name with a length byte. This gives us the first label of the encoded NBT name. Note that we check for a NULL return value. This is paranoia on the programmer's part since this version of L1_Encode() does not return NULL. (An improved version of L1_Encode() might return NULL if it detected an error.)

The variable lenpos is set to the offset at which the next length byte will be written. The L1_Encode() function has already placed a nul byte at this location so, if the scope string is empty, the NBT name is already completely encoded.

  if( '\0' != *scope )
    {
    do
      {
      :
      } while( '.' == *(scope++) );
    dst[lenpos] = '\0';
    }

The processing of scope labels is contained within the do..while loop. If the scope is empty, then we can skip this loop entirely. Note that the root label is added to the end of the target string, dst, following the scope labels.

    for( i = 0, j = (lenpos + 1);
         ('.' != scope[i]) && ('\0' != scope[i]);
         i++, j++)
      dst[j] = scope[i];

Run through the current label, copying it to the destination string. The variable i keeps track of the length of the label. A dot or a nul will mark the end of the current label.

      dst[lenpos] = (uchar)i;
      lenpos     += i + 1;
      scope      += i;

Write the length byte for the current label, and then move on to the next by advancing lenpos. The variable scope is advanced by the length of the current label, which should leave it pointing to the dot or nul that terminated the label. It will be advanced one more byte within the while clause at the end of the loop.

Hopefully that was a nice, short waste of time. As we progress, it will become necessary to move more quickly and provide less code and less analysis of the code. There is a lot of ground to cover.

1.3.1.4 Name Service Packet Headers

Once again, our attention is drawn to the ancient lore of RFC 883, which was written about four years ahead of RFC 1001/1002 and was eventually replaced by RFC 1035. The comings and goings of the RFCs are a study unto themselves.

NBT Name Service packets are an intentional rip-off of DNS Messages. New flag field values, operation codes, and return codes were added but the design was in keeping with the goal of eventually merging NBNS services into the DNS.

This, conceptually, is what a Name Service packet header looks like:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
NAME_TRN_ID
R OPCODE NM_FLAGS RCODE
QDCOUNT
ANCOUNT
NSCOUNT
ARCOUNT

...and here is a description of the fields:

NAME_TRN_ID:   A two-byte transaction identifier. Each time the NBT Name Service starts a new transaction it assigns an ID so that it can figure out which responses go with which requests. The obvious way to handle this is to start with zero and increment each time one is used, allowing rollover at 0xFFFF.

For our purposes any number will do. So we will pick something semi-random for ourselves. How 'bout 1964?

R:   This one-bit field indicates whether the packet is:
0 ==  a request, or
1 ==  a response.

Ours is a request. It initiates a transaction, so we will use 0.

OPCODE:   Six operations are defined by the RFCs. These are:
0x0 ==  Query
0x5 ==  Name Registration
0x6 ==  Name Release
0x7 ==  WACK (Wait for Acknowledgement)
0x8 ==  Name Refresh
0x9 ==  Name Refresh (Alternate Opcode)

The 0x9 OpCode value is the result of a typo in RFC 1002. In section 4.2.1.1 a value of 0x8 is listed, but section 4.2.4 shows a value of 0x9. A sensible implementation will handle either, though 0x8 is the preferred value.

One more OpCode was added after the RFCs were published:

0xF ==  Multi-Homed Name Registration

Our immediate interest, of course, is with the Query operation--OpCode value 0x0.

NM_FLAGS:   As the name suggests, this is a set of one-bit flags, as follows:

 0  1  2  3  4  5  6
AA TC RD RA 0 0 B

We will go into the details of this later on. For now, note that the B flag means "Broadcast" and that we are attempting to do a broadcast query, so we will want to turn this bit on. We will also set the RD flag. RD stands for "Recursion Desired"; for now, just take it on faith that this bit should be set. All others will be clear (zero).

RCODE:   Return codes. This field is always 0x00 in request packets (those with an R value of zero). Each response packet type has its own set of possible RCODE values.

QDCOUNT:   The number of names that follow in the query section. We will set this to 1 for our broadcast name query.

ANCOUNT:   The number of answers in a POSITIVE NAME QUERY RESPONSE message. This field will be used in the replies we get in response to our broadcast query.

NSCOUNT:  
ARCOUNT:  
These are "Name Service Authority Count" and "Additional Record Count", respectively. These can be ignored for now.

So, for a broadcast NAME QUERY REQUEST, our header will look like this:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
1964
0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0
1
0
0
0

To make it easier to write the code for the above query, we will hand-convert the header into a string of bytes. We could do this in code (in fact, that will be necessary for a real implementation), but dealing with such details at this point would be an unnecessary tangent. So...

  unsigned char header[] =
    {
    0x07, 0xAC, /* 1964 == 0x07AC.     */
    0x01, 0x10, /* 0 0000 0010001 0000 */
    0x00, 0x01, /* One name query.     */
    0x00, 0x00, /* Zero answers.       */
    0x00, 0x00, /* Zero authorities.   */
    0x00, 0x00  /* Zero additional.    */
    };

1.3.1.5 The Query Entry

The query entries follow the header. A query entry consists of a Level 2 Encoded NBT name followed by two additional fields: the QUESTION_TYPE and QUESTION_CLASS. Once again, this is taken directly from the DNS query packet.

Under NBT, the QUESTION_TYPE field is limited to two possible values, representing the two types of query that are defined in the RFCs. These are:

NB == 0x0020  The NAME QUERY REQUEST, which we will use to perform our broadcast query.
NBSTAT == 0x0021  The NODE STATUS REQUEST, also known as an "Adapter Status" query. The latter is a reference to the original NetBIOS API command name.

Only one QUESTION_CLASS is defined for NBT, and that is the Internet Class: 0x0001.

So, our completed NAME QUERY REQUEST packet will consist of:

  • the NBT header, as given above,
  • the Second Level encoded NBT name,
  • the unsigned short values 0x0020 and 0x0001.

1.3.1.6 Some Trouble Ahead

He felt that if once he went
beyond the crown of the pass
and took one step veritably
down into the land of Mordor,
that step would be irrevocable.
He could never come back.
-- J.R.R. Tolkein
The Lord of the Rings
  

It would seem that it should now be easy to send a broadcast name query. Just put the pieces together and send them to UDP port 137 at the broadcast address. Yes that should be easy...except that we are now crossing the line between theory and practice, and that means trouble. Be brave.

Upper Case/lower case

RFCs 883 and 1035 state that DNS name lookups should be case-insensitive. That is, CAT.ORG is equivalent to cat.org and Cat.Org, etc. Case-insensitive comparison is not difficult, and First Level Encoding always produces a string of upper-case characters in the range 'A'..'P', so we should have no trouble comparing

EOGFGLGPCACACACACACACACACACACACA.CAT.ORG  against
EOGFGLGPCACACACACACACACACACACACA.cat.org

...but what about the original NetBIOS name? The strings "Neko" and "NEKO" translate, respectively, to

EOGFGLGPCACACACACACACACACACACACA and
EOEFELEPCACACACACACACACACACACACA

These strings do not match, and so we seem to have a problem. Are the two original names considered equivalent? If so, how should we handle them?

RFC 1001 and 1002 do not provide answers to these questions, so we need to look to other sources. Of course, the ultimate source for Truth and Wisdom is empirical information. That is: what actually happens on the wire? A little packet sniffing and a few simple tests will provide the answers we need. Here's the plan:

  1. Use lower-case or mixed-case names when configuring your test hosts.
  2. Set up your sniffer to capture packets on UDP port 137.
  3. Start or restart your test hosts.
  4. After a few minutes, stop the capture.

If your sniffer can decode the NetBIOS names (Ethereal and NetMon can) then you will see that the NetBIOS names are all in upper case. This is normal behavior for NBT, even though it is not documented in the RFCs. Scope strings are also converted to upper case before on-the-wire use.

Here is another interesting test that you can perform if you have both Windows and Samba systems (and/or others) on your network:

  1. Modify the NBT Name Query code (in listing 1.3, below) so that it converts the NetBIOS name to lower case rather than upper case. (That is, change toupper to tolower.)
  2. Recompile.
  3. Start your sniffer.
  4. Use the code to send some queries. Try group names in particular.

In tests using Samba 2.0 and Windows, Samba servers respond to these lower-case queries while the Windows systems do not. This suggests that Windows compares the encoded string, while Samba is decoding the string and performing a case-insensitive comparison on the result 8. One might argue that Samba's behavior is more robust, but comparing names without decoding them first is certainly faster.

NetBIOS Name Syntax

We have specified a syntax for NBT names. So far, however, we have said little about the syntax for the original NetBIOS name. RFC 1001 says only that the name may not begin with an asterisk ('*'), because the asterisk is used for 'wildcard' queries.

At the low level there are few real rules for forming NetBIOS names other than the length limit. Applications, however, often place restrictions on the names that users may choose. Windows, for example, will only allow a specific set of printable characters for workstation names, yet Microsoft's nbtstat program is much more accepting.

For the implementor, this is a bit of a problem. You want to be sure that your code can handle any bizarre name that reaches it over the network, yet help the user avoid choosing names that might cause another system to choke. A good rule of thumb is to warn users against choosing any character that is not legal in a "best practices" DNS label.

Padding Permutations

The space character (0x20) is the designated padding character for NetBIOS names, but there are a few exceptions. One of these is associated with wildcard queries. When sending a wildcard query, the name is padded with nul bytes rather than spaces, giving:

'*'  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0

Which translates to: CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Samba will respond to either space or nul padded wildcard queries, but Windows will only respond if the name is nul padded9. Once again, this indicates that Samba decodes NBT names before comparison, but Windows does not.

Microsoft has added a few other non-space-padded names as well. These are special case names, used with particular applications. Still, they demonstrate the need for flexibility in our encoding and decoding functions.

Label Length Limits

We did not bother to mention earlier that the label length bytes placed before each label during Second Level Encoding are not 8-bit values. The uppermost two bits are used as flags, leaving only 6 bits for the label length. Normally these flag bits are both zero (unset), so we can ignore them for now and (as with so many other little details) deal with them later on.

With only 6 bits, the length of each label is limited to 63 characters. The overall length of the Second Level Encoded string is further limited to 255 bytes. [Annotation] Our example code does not have any checks to ensure that the Scope ID has the correct syntax, though such tests would be required in any "real" implementation.

The Fine Print at the End

The RFCs do not say so, but the last byte of the NetBIOS name is reserved.

The practice probably goes back to the early days and IBM. The 16th byte of a NetBIOS name is used to designate the purpose of the name. This byte is known as the "suffix" (or sometimes the "type byte"), and it contains a value which indicates the type of service that registered the name. Some example suffix values include:

0x00 == Workstation Service (aka Machine Name or Client Service)
0x03 == Messenger Service
0x20 == File Server Service
0x1B == Domain Master Browser

The care and feeding of suffix values is yet another topic to be covered in detail later on. A suffix value of 0x00 is fairly common, so we will use that in our broadcast query. Note that this changes the encoding of the NetBIOS name. Once again using the name Neko:

instead of EOEFELEPCACACACACACACACACACACACA,
you get EOEFELEPCACACACACACACACACACACAAA.

Shorthand Alert:

When writing a NetBIOS name, the suffix value is often specified in hex, surrounded by angle brackets. So, the name NEKO with a suffix of 0x1D would be written:

NEKO<1D>

It is also fairly common to use the '#' character to indicate the suffix:

NEKO#1D

We will use the angle bracket notation where appropriate.
 

1.3.1.7 Finally! A Simple Broadcast Name Query

...almost, but not quite,
entirely unlike tea...
-- The Hitchhikers Guide
To The Galaxy
,
Douglas Adams
  

This next bit of code is full of shortcuts. The packet header is hard-coded, as are the QUESTION_TYPE and QUESTION_CLASS. No syntax checking is done on the scope string. Worst of all, the program sends the query but does not bother to listen for a reply. For that, we will use a sniffer.

Tools such as the nmblookup utility that comes with Samba, or Microsoft's nbtstat program, could also be used to send a name query. The goal, however, is to implement these tools on our own, and the next bit of code gives us a start10.

[Listing 1.3]

The updated L1_Encode() function takes two new parameters: pad and sfx. These allow us to specify the padding character and the suffix, respectively. The L2_Encode() function also takes these additional parameters, so that it can pass them along to L1_Encode(), and both functions make use of toupper() to ensure that the NetBIOS name and Scope ID are in upper case.

The function Send_Nbtn_Bcast() does the job of transmitting a block of data via UDP. The destination is port UDP/137 at the universal broadcast address. The program mainline simply strings together the various pieces of the NBT query, taking the NetBIOS name and Scope ID from the command line.

Compile the code and give the executable the name namequery. The program takes one or two arguments. The first is the NetBIOS name, and the second is the Scope ID (the Scope ID is optional). For example, on a Unix system the command line (including the $ prompt) might be:

$ namequery neko cat.org

Start your sniffer with the filter set to capture only packets sent to/from UDP port 137. If you are using TCPDump or Ethereal the filter string is: udp port 137. Depending on your OS, you may need to have Root or Administrator privilege in order to run the sniffer.

Run namequery with the input shown above, and then stop the capture. You should get something like this:

    + Frame 1 (100 on wire, 100 captured)
    + Ethernet II
    + Internet Protocol
    + User Datagram Protocol
    - NetBIOS Name Service
         Transaction ID: 0x07ac
       + Flags: 0x0110 (Name query)
         Questions: 1
         Answer RRs: 0
         Authority RRs: 0
         Additional RRs: 0
       - Queries
         + NEKO           <00>.CAT.ORG: type NB, class inet

This example is copied from Ethereal output.

Compare the parsed output provided by the sniffer against the hard-coded information in the program. They should match up. Next, try a query using a name on your own network and take a look at the response. If you use the name of a Workgroup or NT Domain, you may get responses from several systems.

Another way to get multiple replies is to use a wildcard query. If all NBT nodes on your local LAN use the same Scope ID, and if they are not P nodes, then they will all respond to the wildcard name. To try this, you must first change the call to L2_Encode() within main() so that it passes '\0' as the padding character. That is:

total_len += L2_Encode( &bufr[total_len], name, '\0', '\0', scope );

...then recompile and give the asterisk as the NetBIOS name:

$ namequery "*"

Try using other tools such as nbtstat in Windows or Samba's nmblookup to generate queries, and spend a bit of time looking at the results of these captures. You can also simply let the sniffer run for a while. If your network is active you will see all sorts of NetBIOS packets fly by (particularly if you are on a shared rather than a switched LAN).

1.3.2 Interlude

We now have method, madness, and a vague sense of the direction. We are ready to head out on the open code. Let us first take a moment to meditate on what we have covered so far. Start by considering this mental image...

Imagine a cold, rainy autumn day. Still thinking of summer, you have forgotten to wear a jacket. The chill of the rain runs through your entire body as you hurry along the street. You try to keep your neck dry by pulling up your thin sweater and hunching your shoulders. Down the road you spot a café. It looks warm and bright inside. You quicken your pace, then dash through the door as the drizzly rain becomes more enthusiastic and thunder rumbles in the distance.

The shop is cozy, but not too small. There are potted plants scattered about. Light jazz plays over well-hidden speakers. The clientele are trendy urban business types having quiet, serious discussions in pairs at small tables. Paintings by a local artist hang on the walls.

You step to the counter. A young woman with a dozen earrings and short-cut hair smiles and asks you what you would like. A nice, hot cup of tea. She reaches down behind the counter and grabs a large white mug. Then she opens a box and pulls out a tea bag that is at least three years old, drops it into the mug, and pours in hot water from the sink. "Three dollars" she says, still smiling.

If you are a coffee drinker, you probably don't understand. Replace the words "opens a box and pulls out a tea bag" with "opens a jar and scoops out one spoonful of freeze-dried instant" and you will get the point. The point is that details matter. Certainly, an old tea bag in warm water will make a cup of tea...but not one worth drinking11.

Just so, our examples provide some working code but are far from satisfying. If we are going to write something truly enjoyable we need to dig into the details.

Let's get to it.


1.4 The Name Service in Detail

This is gonna hurt me
more than it does you.
-- common lie
  

Think of the Name Service as a database system. The data may be stored in an NBNS server (P mode), distributed across all of the participating nodes in an IP subnet (B mode), or a combination of the two (M or H mode).

Name Service messages are the transactions that maintain and utilize the NBT name-to-IP address mapping database. These transactions fall into three basic categories:

Name Registration/Refresh: The process by which an application adds and maintains a NetBIOS Name to IP address mapping within an NBT scope.
Name Query:  The process of resolving a NetBIOS name to an IP address.
Name Release: The process by which a NetBIOS name to IP address mapping is removed from within an NBT scope.

These three represent the lifecycle of an NBT name.
 

Hello, I love you
Won't you tell me your name?
-- Hello, I Love You
The Doors
  

The RFCs also specify support for the NetBIOS API Adapter Status Query function. Implementation of the Adapter Status Query is quite similar to that of the Name Query, so it gets lumped in with the Name Service. This is fairly reasonable, since the query packets are almost identical and the most important result of the status query is a list of names owned by the target node.

1.4.1 NBT Names: Once More With Feeling

Let's review what we've learned so far:

  • Though the RFCs do not say so, NetBIOS names should be converted to upper case before they are encoded. The practice probably goes back to early IBM implementations. Converting NetBIOS names to upper case allows for comparison of the encoded string, rather than requiring that NBT names be decoded and compared using a case-insensitive function. Some existing implementations use this shortcut, and will not recognize names with encoded lower-case characters.

  • The RFCs list NetBIOS names as being 16 bytes in length. It is common practice, however, to implement NetBIOS names as two subfields: a 15-byte name and a one-byte suffix. (That's what Microsoft does so everyone else has to do it too.) The suffix byte actually winds up being quite useful. The suffix byte is read as an integer in the range 0..255, so it is not converted to upper-case.

  • If the NetBIOS name is less than 15 bytes, it must be padded. The space character (0x20) is the designated padding character (though there are some rare, special-case exceptions.)

  • Other than length and padding, the only restriction the RFCs place on the syntax of a NetBIOS name is that it may not begin with an asterisk ('*').

1.4.1.1 Valid NetBIOS Name Characters

Any octet value can be encoded using the first-level mechanism. In theory, then, any eight-bit value can be part of a NetBIOS name. Keep this in mind and be prepared. There are some very strange names in use in the wild.

In practice, implementations do place some restrictions on the characters that may be used in NetBIOS names. These restrictions are implemented at the application layer, and should be considered artificial. Under Windows 9x, for example, the "Network Identity" control panel allows only the following characters in a machine name:

Valid Windows 9x
Machine Name Characters
' ' == 0x20 '-' == 0x2D
'!' == 0x21 '.' == 0x2E
'#' == 0x23 '@' == 0x40
'$' == 0x24 '^' == 0x5E
'%' == 0x25 '_' == 0x5F
'&' == 0x26 '{' == 0x7B
'\'' == 0x27 (single quote)       '}' == 0x7D
'(' == 0x28 '~' == 0x7E
')' == 0x29 Alpha-numeric characters.

Yet the same Windows 9x system may also register the special-purpose name "\x01\x02__MSBROWSE__\x02\x01", which contains control characters as shown.

Note that the set of alpha-numeric characters may include extended characters, such as 'Å' and 'Ü'. Unfortunately, these are often represented by different octet values under different operating systems, or even under different configurations of the same operating system.

Some examples:

Character ISO Latin-1 DOS Code Page 437
'Ä' 0xC4 0x8E
'Ç' 0xC7 0x80
'É' 0xC9 0x90
'Î' 0xCE -
'Ö' 0xD6 0x99
'Ñ' 0xD1 0xA5
'Ù' 0xD9 -

As you can see, the mapping between character sets can be a bit of a challenge--particularly since there is no standard character set for use in NBT and no mechanism for negotiating a common character set12.

One more thing to consider when dealing with NetBIOS name characters: Windows NT will generate a warning--and W2K an error--if the Machine Name is not also a valid DNS name. You may need to do some testing to determine which characters Windows considers valid DNS label characters.

1.4.1.2 NetBIOS Names Within Scope

Under NBT, NetBIOS names exist within a scope. The scope is the set of all machines which can "see" the name. For B nodes, the scope is limited to the IP broadcast domain. For P nodes, the scope is limited to the set of nodes that share the same NBNS. For M and H nodes, the scope is the union of the broadcast domain and the shared NBNS.

Scope can be further refined using a Scope ID. The Scope ID effectively sub-divides a virtual NetBIOS LAN into separate, named vLANs. Unfortunately, few (if any) implementations actually support multiple Scope IDs so this feature is of limited practical use.

The syntax of the Scope ID matches the best-practices recommendations for DNS domain names. (Some Windows flavors allow almost any character value in a Scope ID string. Sigh.) Scope IDs should be converted to upper case before use on the wire.
 



The best way to eliminate
the problem is to
remove Scopes completely.
-- John Terpstra,
Samba Team,
in a message to the
Samba-Technical mailing list.
  

Annoyance Alert:

In versions of Windows 95 and '98 that we tested, the Scope ID field in the network setup control panel is greyed out if no WINS server IP address is specified. That is, you cannot enter a Scope ID if your machine is running in B mode.

You can work around this by entering the Scope ID in the right place in the registry, or by entering a (bogus) WINS server IP, entering the Scope ID, saving your changes, rebooting, reopening the network control panel, removing the WINS IP entry, saving your changes, and rebooting again.

The system does not seem to clear the Scope ID once it has been entered. To clear the Scope ID you must either edit the registry, or enter a (bogus) WINS server IP, clear out the Scope ID in the control panel, save your changes, reboot, reopen the network control panel, remove the WINS IP entry, save your changes, and reboot.

Windows NT behaves correctly, and does allow the entry of a Scope ID in B mode.
 

  [Buy the Book!]   

1.4.1.3 Encoding and Decoding NBT Names

First Level Encoding converts a 16-byte NetBIOS name into a 32-byte encoded name, and then combines it with the Scope ID. For example:

"EOGFGLGPCACACACACACACACACACACAAA.CAT.ORG"

We have chosen to call this format the NBT Name. Second Level Encoding is applied to the NBT name to create the on-the-wire format, which we will refer to as the Encoded NBT Name:

"\x20EOGFGLGPCACACACACACACACACACACAAA\x03CAT\x03ORG\0"

As previously described, the maximum length of a label in an NBT name is 63 bytes. This is because the label length field is divided into two sub-fields, the first of which is a two-bit flag field with four possible values:

00 == 0: Label Length
01 == 1: Reserved (unused)
10 == 2: Reserved (unused)
11 == 3: Label String Pointer

With both bits clear (zero) the next 6 bits are the label LENGTH. The LENGTH field is an unsigned integer with a value in the range 0..63.

 0  1  2  3  4  5  6  7
0 0 LENGTH

If both flag bits are set, however, then the next fourteen bits are a "Label String Pointer"; the offset at which the real label can be found.

 0  1  2  3  4  5  6  7  8  9  10  11  12  13  14  15
1 1 LABEL STRING POINTER

Label string pointers are used to reduce the size of Name Service messages that might otherwise contain two copies of the same NBT name. For example, a NAME REGISTRATION REQUEST message includes both a QUESTION_RECORD and an ADDITIONAL_RECORD, each of which would otherwise contain the same NBT name. Instead of duplicating the name, however, the ADDITIONAL_RECORD.RR_NAME field contains a label string pointer to the QUESTION_RECORD.QUESTION_NAME field.

Label string pointers are a prime example of the NBT theory/practice dichotomy, and another throw-back to the DNS system. As it turns out, the only label string pointer value ever used in NBT is 0xC00C. The reason for this is quite simple. The NBT header is a fixed size (12 bytes), and is always followed by a block that starts with an encoded NBT Name. Thus, the offset of the first name in the packet is always 12 (0x0C). Any further name field in the packet will point back to the first.

So, the rule of thumb is that the encoded NBT name will always be found at byte offset 0x000C. As a short-cut, some implementations work directly with the encoded name and only bother to decode the name when interacting with a user. Decoding, however, is fairly straight forward:

[Listing 1.4]

The L2_Decode() function copies the encoded NBT name to the destination buffer, skipping the first label length byte and replacing internal label length bytes with the dot character. That is, given the input string:

"\x20EOGFGLGPCACACACACACACACACACACAAA\x03CAT\x03ORG\0"

it will produce the string:

"EOGFGLGPCACACACACACACACACACACAAA.CAT.ORG"

The L1_Decode() function decodes the First Level Encoded NetBIOS name, and hands back the suffix byte as its return value.

1.4.2 NBT Name Service Packets

RFC 1002 lists 17 different Name Service packet types, constructed from three basic building blocks:

  • A Header
  • Query Records
  • Resource Records

These pieces are described in more detail below.

1.4.2.1 Name Service Headers

The header is an array of six 16-bit values, as follows:

0   NAME_TRN_ID  
1 FLAGS
2 QDCOUNT
3 ANCOUNT
4 NSCOUNT
5 ARCOUNT

Managing Name Service headers is fairly straight-forward. With the exception of the FLAGS field, all of the fields are simple unsigned integers. The entire thing can be represented in memory as an array of unsigned short int, or whatever is appropriate in your programming language of choice.

The FLAGS field is further broken down thus:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
R OPCODE NM_FLAGS RCODE

Handling the bits in the FLAGS field is fairly trivial for any seasoned programmer. One simple solution is to shift the values given in RFC 1002, section 4.2.1.1 into their absolute positions. For example, an OPCODE value of 0x7 (WACK) would be left shifted 11 bits to align it properly in the OPCODE subfield:
 

It's just a jump to the left
-- Time Warp
Richard O'Brien
  

(0x0007 << 11) = 0x3800 = 0011100000000000(bin)

...which puts it where it's supposed to be:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
R OPCODE NM_FLAGS RCODE
0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0

Listing 1.5 presents nbt_nsHeader.h, a header file that will be referenced as we move forward. It provides a set of re-aligned FLAGS subfield values plus a few extra constants. These values will be covered below, when we explain how to use each of the Name Service message types.

[Listing 1.5]

The NAME_TRN_ID field is the transaction ID, which should probably be handled by the bit of code that sends and receives the NBT messages. Many implementations use a simple counter to generate new transaction IDs (Samba uses a random number generator), but these should always be checked to ensure that they are not, by chance, the same as the transaction ID of a conversation initiated by some other node. Better yet, the originating node's IP address should be used as an additional key for segregating transactions.

The four COUNT fields indicate the number of Question and Resource Records which follow. In theory, each of these fields can contain a value in the range 0..65535. In practice, however, the count fields will contain either 0 or 1 as shown in the record layouts in RFC 1002, section 4.2. It appears as though some implementations either ignore these fields or read them as simple booleans.

One final consideration is the byte order of NBT messages. True to its DNS roots, NBT uses network byte order (big-endian). Some microprocessors--including Alpha, MIPS, and Intel i386 family--use or can use little-endian byte order13. If your target system is little-endian, or if you want your code to be portable, you will need to ensure that your integers are properly converted to and from network byte order. Many systems provide the htonl(), htons(), ntohl(), and ntohs() functions for exactly this purpose.
 


My brain hurts!
-- Attributed to
Mr. T. F. Gumby
Gumby Brain Specialist
Monty Python's Flying Circus
  

Bizarre Twist Alert:

The SMB protocol was originally built to run on DOS. DOS was originally built to run on Intel chips, so SMB is little-endian...the opposite of the NBT transport!
 

This next bit of code is nbt_nsHeader.c. It shows how to create and parse NBT Name Service headers. As with all of the code presented in this book, it is designed to be illustrative, not efficient. (We know you can do better.)

[Listing 1.6]

1.4.2.2 Name Service Question Records

The question record is also simple. It consists of an encoded NBT name (in the QUESTION_NAME field) followed by two unsigned 16-bit integer fields: the QUESTION_TYPE and QUESTION_CLASS.

The length of an encoded NBT name is at least 34 bytes, but it will be longer if a Scope ID is used, so the QUESTION_NAME field has no fixed length. There is also no padding done to align the integer fields. The QUESTION_TYPE and QUESTION_CLASS follow immediately after the QUESTION_NAME.

>= 34 bytes 2 bytes 2 bytes
 QUESTION_NAME ...    QUESTION_TYPE   QUESTION_CLASS 

There are only two valid values for the QUESTION_TYPE field. These are:

NB  = 0x0020   Indicates a standard Name Query
NBSTAT  = 0x0021   Indicates a Node Status Query

The QUESTION_CLASS field always has a value of:

    IN  = 0x0001   Indicates the "Internet Class"

Go back and take a look at the broadcast name query example presented earlier. In that example, we hard-coded both the NBT Name Service header and the tail-end of the question record. Now that you have a clearer understanding of the fields involved, you should be able to design much more flexible code. Here's a start:

[Listing 1.7]

1.4.2.3 Name Service Resource Records

For convenience, we will break the Resource Record into three sub-parts:

  • the Name section
  • the TTL field
  • the Resource Data section

The Name section has the same structure as a Query Entry record, except that the RR_NAME field may contain a 16-bit label string pointer instead of a complete NBT name.

 2-bytes or >= 34 bytes  2 bytes 2 bytes
 RR_NAME ...    RR_TYPE   RR_CLASS 

The RR_TYPE field is used to indicate the type of the resource record, which has an effect on the structure of the resource data section. The available values for this field are:

A  == 0x0001 (not used in practice)
NS  == 0x0002 (not used in practice)
NULL  == 0x000A (not used in practice)
NB  == 0x0020
NBSTAT  == 0x0021

The values marked "not used in practice" are described in the RFCs, and indicated as valid values, but are never really used in modern implementations. The value of RR_TYPE will be NB except in a NODE STATUS REPLY, in which case NBSTAT is used.

As with the question record, the RR_CLASS field always has a value of:

    IN  == 0x0001

The TTL field follows the name section. It indicates the "Time To Live" value associated with a resource record. Each NBT name-to-IP address mapping in the NBNS database has a TTL value. This allows records to "fade out" if they are not renewed or properly released. The TTL field is an unsigned long integer, measured in seconds. A value of zero indicates infinite TTL.

0 1 2 3 4 5 6 7 8 9 1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
TTL

The last sub-part of the resource record is the resource data section, which is made up of two fields:

2 bytes  RDLENGTH bytes 
 RDLENGTH   RDATA ... 

The RDLENGTH field is an unsigned 16-bit integer value indicating the length, in bytes, of the RDATA field. The structure of the contents of the RDATA field will vary from message type to message type.

The Resource Record structure, as described in section 4.2.1.3 of RFC 1002, looks just like this:

                     1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
/                            RR_NAME                            /
/                                                               /
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           RR_TYPE             |          RR_CLASS             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              TTL                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           RDLENGTH            |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
/                                                               /
/                             RDATA                             /
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

It is always good to have some code to play with. This next set of functions can be used to manipulate Resource Records.

[Listing 1.8]

1.4.3 Conversations with the Name Service

We will now introduce a simple syntax for describing how to fill network packets. This syntax is neither standard nor rigorous, just something the author whipped up to help explain what goes into a message. If it looks like someone else's syntax (one which perhaps took long hours of study, concentration, and thought to develop) then apologies are probably in order.

Disclaimer Alert:

Any resemblance to an actual syntax, living or dead, real or imaginary, is entirely coincidental.

A broadcast name query, described using our little syntax, would look like this:

    NAME QUERY REQUEST (Broadcast)
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x0
          RD     = TRUE
          B      = TRUE
          }
        QDCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT Name>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      }

Basically, the rules are these:

  • If a record (a header, question record, or resource record) is not specified, it is not included in the packet. In the example above there are no resource records specified. We know from the example code that there are no resource records in a NAME QUERY REQUEST.
  • If a field is not specified, it is zeroed. In the example above the RCODE field of the FLAGS sub-record has a value of 0x0, and the NSCOUNT field (among others) also has a value of 0.
  • Comments in angle brackets are short explanations, describing what should go into the field. More complete explanations, if needed, will be found in the accompanying text.
  • Comments in parentheses provide additional information, such as the value of a specified constant.
  • ...and yes, each squirrelly bracket gets its own line.

It's not a particularly formal syntax, but it will serve the purpose.

1.4.3.1 Name Registration

Nodes send NAME REGISTRATION REQUEST messages when they wish to claim ownership of a name. The messages may be broadcast on the local LAN (B mode), or sent directly to an NBNS (P mode). (M and H mode are combinations of B and P mode with their own special quirks. We will get to those further on.)

A NAME REGISTRATION REQUEST message looks like this:

    NAME REGISTRATION REQUEST
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x5 (Registration)
          RD     = TRUE (1)
          B      = <TRUE for broadcast registration, else FALSE>
          }
        QDCOUNT = 1
        ARCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be registered>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      ADDITIONAL_RECORD
        {
        RR_NAME  = 0xC00C (Label String Pointer to QUESTION_NAME)
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = <Zero for broadcast, about three days for unicast>
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Owner type>
            }
          NB_ADDRESS = <Requesting node's IP address>
          }
        }
      }

The NAME REGISTRATION REQUEST includes both a QUESTION_RECORD and an ADDITIONAL_RECORD. In a sense, it is two messages in one. It says "Does anyone own this name?" and "I want to own this name!", both in the same packet.

The NAME REGISTRATION REQUEST gives us our first look at a Label String Pointer in its native habitat. In the packet above the QUESTION_NAME and the RR_NAME are the same name, so the latter field contains a pointer back to the former. The size of the header is constant; if there is a QUESTION_NAME in a packet it will always be found at offset 0x000C (12). The field value is 0xC00C because (as is always the case with label string pointers) the first two bits are set in order to indicate that the remainder is a pointer rather than a 6-bit label length. So, Label String Pointers in NBT messages always have the value 0xC00C.

The TTL field in the ADDITIONAL_RECORD provides a Time-To-Live value, in seconds, for the name. In B mode, the TTL value is not significant and is generally set to zero. In P mode, the TTL is used by the NBNS to determine when to purge old entries from the database, and is typically set to something on the order of three days in the NAME REGISTRATION REQUEST. The NBNS may override the client's request and reply with a different TTL value, which the client must accept.

The ADDITIONAL_RECORD.RDATA field is 6 bytes long (as shown in ADDITIONAL_RECORD.RDLENGTH) and contains two subfields. The first is the NB_FLAGS field, which provides information about the name and its owner. It looks something like this:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
G ONT UNUSED

The NB_FLAGS.G bit indicates whether the name is a group name or a unique name, and NB_FLAGS.ONT identifies the owner node type. ONT is a two-bit field with the following possible values:

00 == B node
01 == P node
10 == M node
11 == H node (added by Microsoft)

The ADDITIONAL_RECORD.RDATA.NB_ADDRESS holds the 4-byte IPV4 address that will be mapped to the name. This should, of course, match the address of the node registering the name.

Take a good look at the structure of the RDATA subrecord in the NAME REGISTRATION REQUEST. This is the most common RDATA format, which gives us an excuse for writing a little more code...

[Listing 1.9]

1.4.3.1.1 Broadcast Name Registration

You've seen the basic form of NAME REGISTRATION REQUEST packet. When sending a broadcast registration, the following rules apply.

  • The B bit is set.
  • The TTL is zero.
  • The RDATA.NB_FLAGS.ONT should never be ONT_P, since P nodes never register their names via broadcast.

A node sending a broadcast NAME REGISTRATION REQUEST (the requester) may receive a unicast NEGATIVE NAME REGISTRATION RESPONSE from another node that already claims ownership of the name (the owner). That is the only valid message in response to a broadcast registration.

    NAME REGISTRATION RESPONSE (Negative)
      {
      HEADER
        {
        NAME_TRN_ID = <Must match REQUEST transaction ID>
        FLAGS
          {
          R      = TRUE (1; This is a response packet)
          OPCODE = 0x5 (Registration)
          AA     = TRUE (1)
          RD     = TRUE (1)
          RA     = TRUE (1)
          RCODE  = ACT_ERR (0x6)
          B      = FALSE (0; Message is unicast back to requester)
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The Encoded NBT Name>
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = 0 (TTL has no meaning in this context)
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Owner type>
            }
          NB_ADDRESS = <Owner's IP address>
          }
        }
      }

When a requester receives a NEGATIVE NAME REGISTRATION RESPONSE, it is obliged to give up. Registration has failed because another node has prior--and conflicting--claim to the name. That is, the name already has an owner.

The RCODE field of the response will be ACT_ERR (0x6), indicating that the name is in use. The RDATA field should contain the real owner's name information:

  • NB_FLAGS.G indicates whether the name in use is a group or unique name,
  • NB_FLAGS.ONT is the owner's node type,
  • NB_ADDRESS is the owner's IP address.

Recall that the NAME REGISTRATION REQUEST contains a name query, so the ANSWER_RECORD in the reply should be constructed as it would be in a POSITIVE NAME QUERY RESPONSE. It is wrong to simply parrot back the information in the request14.

NEGATIVE NAME REGISTRATION RESPONSE messages are only sent if a unique name is involved. Owners of a group name will not complain if a requester tries to join the group. If, however, a requester tries to register a unique name that matches an already registered group name, the members of the group will send negative responses. In a broadcast environment, a single unique name registration request can generate a large number of negative replies.

[Figure 1.6]
 

I want it, I want it, I want it...
You can't have it!
-- Magic Bus
The Who
  

If there are no conflicts the requesting node will hear no complaints, in which case it must retry the request two more times...just to be sure. The RFCs specify a minimum timeout of 250 milliseconds between broadcast retries (Windows uses 750ms). After the third query has timed out, the requesting node should broadcast a NAME OVERWRITE DEMAND declaring itself the victor and owner of the name. The NAME OVERWRITE DEMAND message is identical to the NAME REGISTRATION REQUEST, except that the RD bit is clear (Recursion Desired is 0).

This next program will allow you to play around with broadcast name registration. It uses functions and constants from previous listings to format a NAME REGISTRATION REQUEST and broadcast it on the local IP subnet, then it listens for and reports any replies it receives.

[Listing 1.10]

The transaction ID in the NAME_TRN_ID field should be the same for all three registration attempts, for the final NAME OVERWRITE DEMAND, and for any negative response packets a remote node may care to send. All of these are part of the same transaction.
 



...the correct expression
is "up and died."
-- from the errata for
Applied Cryptography,
2nd. Ed.
,
By Bruce Schneier
  

Blue Screen of Death Alert:

Some OEM versions of Windows 95 had a bug that would cause the system to go into "Blue Screen of Death" mode (that is, system crash) if the NetBIOS Machine Name was in conflict. The problem was made worse by PC vendors who would ship systems with NBT turned on, all preconfigured with the same name. Customers who purchased several computers for local networks would turn them on for the first time and all but one would crash.
 

1.4.3.1.2 Unicast (NBNS) Name Registration

Unicast name registrations are subtly different from the broadcast variety.

  • The B bit is cleared (zero) and the destination IP is the unicast address of the NBNS.

    The message is sent "point-to-point" directly to the NBNS, rather than being broadcast on the local LAN. This is the fundamental difference between B and P mode.

  • The TTL field has real meaning when you are talking to an NBNS.

    The RFCs do not specify a default TTL value. Windows systems use 300,000 seconds, which is three days, eleven hours and twenty minutes. Samba uses 259,200 seconds, which is three days even. Both of these values are ugly in hex15.

  • The timeout between retries is longer.

    The longer timeout between retries is based on the assumption that routed links may have higher latency than the local LAN. RFC 1002 specifies a timeout value of five seconds, which is excessive on today's Internet. A client will try to register a name three times, so the total (worst case) timeout would be fifteen seconds. Samba uses a two second per-packet timeout instead, for a total of six seconds. The timeout under Windows is only 1.5 seconds per packet.

The NBNS should respond with a NAME REGISTRATION RESPONSE, which will include one of the following RCODE values:

0x0:  Success
POSITIVE NAME REGISTRATION RESPONSE
You win! The NBNS has accepted the registration. Do not forget to send a refresh before the TTL expires (see the section on Name Refresh below).
 
FMT_ERR (0x1):  Format Error
The NBNS did not like your message. Something was wrong with the packet format (perhaps it was mangled on the wire).
 
SRV_ERR (0x2):  Server failure
The NBNS is sick and cannot handle requests just now.
 
IMP_ERR (0x4):  Unsupported request error
This one is a bit of a mystery. It basically means that the NBNS does not know how to handle a request. The only clue we have to its intended usage is a poorly worded note in RFC 1002, which says:

Allowable only for challenging NBNS when gets an Update type registration request.

Huh?

This error occurs only under odd circumstances, which will be explained in more detail later on in this section. Basically, though, an IMP_ERR should only be returned by an NBNS if it receives an unsolicited NAME UPDATE REQUEST from a client. (Be patient, we'll get there.)
 

RFS_ERR (0x5):  Refused error
This indicates that the NBNS has made a policy decision not to register the name.
 
ACT_ERR (0x6):  Active error
The NBNS has verified that the name is in use by another node. You can't have it.

Note that the difference between a positive and negative NAME REGISTRATION RESPONSE is simply the RCODE value.

If you get no response then it is correct to assume that the NBNS is "down". If the name cannot be registered then your node does not own it, and your application should recover as gracefully as possible. In P mode, handle a non-responsive NBNS as you would a NEGATIVE NAME REGISTRATION RESPONSE. (If the client is running in H or M mode, then it may--with caution--revert to B mode operation until the NBNS is available again.)

There are two other packet types that you may receive when registering a name with an NBNS. These are WACK and END-NODE CHALLENGE NAME REGISTRATION RESPONSE. The WACK message tells the client to wait while the NBNS figures things out. This is typically done so that the NBNS has time to send queries to another node that has claimed ownership of the requested name. A WACK looks like this:

    WAIT FOR ACKNOWLEDGEMENT (WACK) RESPONSE
      {
      HEADER
        {
        NAME_TRN_ID = <Must match REQUEST transaction ID>
        FLAGS
          {
          R      = TRUE (1; This is a response packet)
          OPCODE = 0x7 (WACK)
          AA     = TRUE (1)
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The Encoded NBT Name from the request>
        RR_TYPE  = NB (0x0020; note the typo in RFC 1002, 4.2.16)
        RR_CLASS = IN (0x0001)
        TTL      = <Number of seconds to wait; 0 == Infinite>
        RDLENGTH = 2
        RDATA    = <Copy of the two-byte HEADER.FLAGS field
                   of the original request>
        }
      }

The key field in the WACK is the TTL field, which tells the client how long to wait for a response. This is used to extend the timeout period on the client, and give the NBNS a chance to do a reality check.

Samba uses a TTL value of 60 seconds, which provides ample time to generate a proper reply. Unless it is shut down after sending the WACK message, Samba's NBNS service will always send a NAME REGISTRATION RESPONSE (positive or negative) well before the 60 seconds has elapsed. Microsoft's WINS takes a different approach, using a value of only 2 seconds. If the 2 seconds expire, however, the requesting client will simply send another NAME REGISTRATION REQUEST, and then another for a total of three tries. WINS should be able to respond within that total timeframe.

WACK messages are sent by honest, hard-working servers that take good care of their clients. In contrast, a lazy and careless NBNS server will send an END-NODE CHALLENGE NAME REGISTRATION RESPONSE. This latter response tells the client that the requested name has a registered owner, but the NBNS is not going to bother to do the work to check that the owner is still up and running and using the name.

Once again, the format of this message is so familiar that there is no need to list all of the fields. The END-NODE CHALLENGE NAME REGISTRATION RESPONSE packet is just a NAME REGISTRATION RESPONSE with:

  • RCODE = 0x0
  • RA = 0 (Recursion Available clear)
  • ANSWER_RECORD.RDATA = <Information retrieved from the NBNS database.>

The annoying thing about this packet is that the RCODE value indicates success, making it look almost exactly like a POSITIVE NAME REGISTRATION RESPONSE. The RA bit must be checked to distinguish between the two message types.
 

You might as well ask the cat.
-- Basil Fawlty
(John Cleese),
Fawlty Towers
  

When a client receives an END-NODE CHALLENGE, its duty is to query the owner (the owner's IP address will be in the ANSWER_RECORD.RDATA.NB_ADDRESS field) to see if the owner still wants the name. If the owner does not respond, or if it replies with a NEGATIVE NAME QUERY RESPONSE, then the name is available and the requester may send a NAME UPDATE REQUEST to the NBNS. The NBNS will blindly trust the requester, change the entry, and reply with a POSITIVE NAME REGISTRATION RESPONSE. The NAME UPDATE REQUEST is the same as the unicast NAME REGISTRATION REQUEST except that the RD bit is clear (Recursion Desired is 0).

There is nothing to stop a client from skipping the name query and sending the update message to the NBNS, effectively stealing the name. This is why the RFCs use the term non-secured when describing this mechanism.

Terminology Turmoil Alert:

In the RFCs, the terms "NAME UPDATE REQUEST" and "NAME OVERWRITE REQUEST & DEMAND" are both used to refer to the same packet structure. These terms are interchanged somewhat randomly in the text without any explanation regarding their relationship to one another (all probably due to an editing oversight). This is confusing.

In this book we make a semantic distinction between the two message types, and shorten "NAME OVERWRITE REQUEST & DEMAND" to simply "NAME OVERWRITE DEMAND".

Here's why:

The RFCs specify that a REQUEST is a message to which a RESPONSE is expected. So, for example, once a NAME REGISTRATION REQUEST has been sent the requester must wait a reasonable period of time for a reply, and retry the request twice before giving up. A DEMAND, however, never generates a RESPONSE. It is simply sent and forgotten so there is no need to wait. Thus, the term "NAME OVERWRITE REQUEST & DEMAND" is contradictory. The message is either a REQUEST or a DEMAND, but not both.

To clear things up, we use NAME UPDATE REQUEST to indicate the packet sent to a non-secured NBNS following a name challenge. The requester expects to receive a POSITIVE NAME REGISTRATION RESPONSE in reply to the NAME UPDATE REQUEST. In contrast, the NAME OVERWRITE DEMAND is sent as the last step in a successful broadcast registration, and no reply is expected.

Again, these packets all share the same structure as the NAME REGISTRATION REQUEST. Only the RD and B flag bits distinguish them syntactically.
 

Oh...one more thing. Remember the IMP_ERR return code? It is used to indicate that an NBNS which did not send an END-NODE CHALLENGE is annoyed at having received a NAME UPDATE REQUEST from a client. An NBNS server should never receive unsolicited NAME UPDATE REQUESTs from clients.

1.4.3.1.3 M and H Node Name Registration

Mixed mode (M mode) and Hybrid mode (H mode) are both speed hacks, which combine aspects of Broadcast (B) and Point-to-Point (P) modes to short-cut Name Service operations.

M mode was designed in the days when local LAN traffic was likely to be faster than internetwork links, which were typically carried over leased lines or even dial-up connections, tin cans with string, or pigeon (see RFC 1149). Since local broadcasts were both faster and more reliable than traffic to a remote NBNS, M nodes attempt B mode behavior first and try P mode behavior second.

When an M node registers a name, for example, it starts by sending a broadcast NAME REGISTRATION REQUEST. If it receives a negative response it tries no further (thus saving some time). If, however, it receives no complaints after three retries, it will attempt to register with the NBNS as a P node would. If and only if the P mode registration succeeds, the M node will broadcast a NAME OVERWRITE DEMAND. If the unicast registration fails, the NAME OVERWRITE will not be sent and the node will not assume ownership of the name.

[Annotation] Hybrid mode (H mode) was introduced (probably by Microsoft) after the RFCs were published. H mode assumes that internetwork links are fast and reliable, in which case it makes sense to try P mode behavior first and revert to B mode behavior only if the NBNS does not respond. Compared with M mode, H mode generates less broadcast traffic on local LANs.

H mode is a little trickier than M mode. A node running in H mode will attempt a unicast name registration and, if the NBNS accepts the registration, the H node will assume ownership without generating any broadcast (B mode) traffic at all. If the NetBIOS vLAN is configured properly all of the nodes within the scope will also be registering with the NBNS, thus preventing accidental name conflicts.

If the NBNS is down or unreachable, however, an H node will revert to B mode behavior and hope that no conflicts will arise when the NBNS comes back.

1.4.3.1.4 Registering Multi-Homed Hosts

A multi-homed host is a machine that has multiple network interfaces (physical or virtual), each with its own IP address assigned. RFC 1001 and 1002 do not discuss handling of multi-homed hosts.

The annoying thing about multi-homed hosts in an NBT environment is that they try to register their NetBIOS names on each interface, which means multiple IP addresses per name. This is not a problem for group names because group names map to several IP addresses anyway--that's what NBT group names are all about. Unique names are a problem because, from the network's point of view, there is no difference between a multi-homed host and multiple machines. To an NBNS, or to B nodes on a local LAN, multiple registrations for the same name will look like a name conflict.

There are three scenarios to consider when working with multi-homed hosts:

B Nodes with Interfaces on Separate Subnets

[Figure 1.7]

If each IP address is on a separate IP subnet and the node is running in B mode then springtime returns to the cities, birds sing, and little children dance for joy. Each name<->IP address mapping is unique within its NBT scope, which is the broadcast space within the subnet, so there are no name conflicts.

The only multi-homed-specific problem that can occur in this scenario starts with a regular old-fashioned run-of-the-mill name conflict. If there is a name conflict with another node on one or more, but not all, subnets then we have a quandary because the name is valid on some subnets, but not others. Two solutions are possible here: the multi-homed host may decide to disable the name on all interfaces (probably the best option), or just on the interfaces on which the conflict exists.

Another thing to keep in mind is that replies to name queries must return the correct IP address for the subnet, so it is important to know on which interface the query was received. This can be done by checking both the source and destination IP addresses of the original query packet. If the query is a broadcast query, then it is best to send only the IP address of the interface. Unicast queries, however, should contain a full list of the IPs registered to the name. This quirk will be examined further when we tackle P mode multi-homed registration.

B Nodes with Interfaces on the Same Subnet

[Figure 1.8]

Problems occur if two or more interfaces have IP addresses on the same subnet. This is equivalent to having two or more separate nodes on the same subnet, all trying to claim the same unique name. There is no standard fix for this situation. Fortunately this configuration is rare, though it does occur in the wild--typically when someone tries to build a fault-tolerant or load-balanced server system. The only known work-around is to write additional code to control which of the multi-homed interfaces "owns" a name at any given time.

Multi-Homed Hosts and the NBNS

[Figure 1.9]

P mode multi-homed name registration is a circus. In P mode, a multi-homed host will send multiple registrations--one per interface--to the NBNS. Normally the NBNS would reject all but the first such registration, viewing the others as name conflicts. To get around this problem, we use a new OpCode:

0xF == Multi-Homed Name Registration.

Instead of sending normal registration requests, the host concurrently sends individual MULTI-HOMED NAME REGISTRATION REQUEST packets from each interface it wishes to register. Other than the OpCode, these are identical to normal NAME REGISTRATION REQUEST packets, though each request has its own NAME_TRN_ID (transaction ID).

The NBNS will respond to the first of these messages by sending a POSITIVE NAME REGISTRATION RESPONSE. It then sends 2-second WACK messages in reply to all of the other MULTI-HOMED NAME REGISTRATION REQUEST packets it receives (all that are trying to register the same unique name). The WACK gives the NBNS extra time to process the registration.

Next, the NBNS will send a unicast NAME QUERY REQUEST to the source address of the first message it received (the one that got the POSITIVE NAME REGISTRATION RESPONSE). This is a unicast query (the B bit is clear), so the query response should contain the complete list of IP addresses that are allowed to share the name.

The NBNS will then send POSITIVE NAME REGISTRATION RESPONSE messages to all of the WACKed IPs in the list, and a NEGATIVE NAME REGISTRATION RESPONSE, with an RCODE value of ACT_ERR (0x6), to any others. The NBNS finishes with a double back-flip in pike position through flaming hoops into a piece of unbuttered toast and the crowd cheers wildly.

One problem still remains, however. Consider node LANE (operating in P mode), which is trying to talk to node PATTY. The first thing LANE will do is send a NAME QUERY REQUEST to the NBNS. The NBNS has no way of knowing which IP address represents the best route between LANE and PATTY, so it must send the complete list of PATTY's IPs. LANE has to guess which IP is the best. Typically, the client will choose a destination IP by sending some sort of message (eg., a unicast name query) to all of the listed IPs to see which one answers first. Note that in order to make this work the NBNS must keep track of all IPs associated with the NBT name registered by the multi-homed host16.

[Figure 1.10]

Proof by Assignment:
The proof of this theorem
is part of your homework.
-- Jonathan Young, PhD.
  

As you might expect, the handling of M and H mode multi-homed hosts is a fairly straight-forward combination of B and P mode behavior. M and H mode name registration for single-homed hosts has already been covered.

1.4.3.2 Name Query

Each NBT node keeps track of its own local name table, which holds the list of the NetBIOS names that the node thinks it owns. NBT nodes may also register their names with a NetBIOS nameserver. Both the local name table and the NBNS database can be used to answer queries.

Name queries look like this:

    NAME QUERY REQUEST
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x0 (Query)
          RD     = <Typically TRUE (1); see discussion below>
          B      = <TRUE for broadcast queries, else FALSE (0)>
          }
        QDCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be queried>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      }

As you can see from the packet description, name queries are really very simple (just as the eye of a hurricane is calm). The only fiddly bits are the B and RD flags.

  • The B bit is used to distinguish between broadcast and unicast queries.

    Broadcast queries are used for name resolution within the broadcast scope, as shown by the example code presented earlier. Since P nodes are excluded from B mode scope, P nodes and the NBNS will both ignore broadcast name queries. Only local B, M, and H nodes (with the same Scope ID as the sender) will respond.

    The only valid reply to a broadcast name query is a POSITIVE NAME QUERY RESPONSE from a node that actually owns the name in question. Queries for group names may generate multiple responses, one per group member on the local LAN.

    In P mode, names are resolved to IP addresses by sending a unicast query to the NBNS, which checks the list of registered names (the NBNS database). If the name is found, the NBNS will reply with a POSITIVE NAME QUERY RESPONSE, otherwise it will send a NEGATIVE NAME QUERY RESPONSE. If the requester gets no response at all, then the NBNS is assumed to be down or unreachable.

    Unicast queries can also be used to determine whether an NBT end node owns a given NetBIOS name. All NBT node types (B, P, M, and H) will respond to unicast queries. As with queries sent to the NBNS, NBT end nodes may reply with either a positive or negative NAME QUERY RESPONSE.

    A unicast query for the wildcard name is the NBT equivalent of a ping.

  • The RD bit is used to distinguish between two different types of unicast query.

    In discussing the use of the B bit, above, we made a subtle distinction between resolution and verification queries. A name resolution query is the most familiar. It is used to convert a name to an IP address. Unicast queries sent to the NBNS are always resolution queries. A verification query is a unicast query sent to an NBT end node to ask whether the node is using the name in question. In order to send a verification query, the sender must already have the IP of the target NBT end node so name resolution is pointless.

    Note that:

    • Broadcast name resolution queries are always answered from data in the receiving node's local name table.

    • Unicast name resolution queries are supposed to be answered from data in the NBNS database.

    • Unicast name verification queries must be answered from the node's local name table--not the NBNS database.

    So... what happens if you send a unicast query to a node that is both an NBT participant and the NBNS? Which kind of query is it, and which name list should be consulted?

    That's where the RD bit comes in. If RD is FALSE then only the local name table is consulted, forcing a verification query. If RD is TRUE and the NBNS service is running on the receiving node, then the NBNS database may also be used to answer the query--that makes it a resolution query.

    This particular problem, and the solution, are not covered in the RFCs. The diagram in RFC 1002, section 4.2.12 shows the RD bit as always set, and this is common practice17. The state of the RD bit in a query message is typically ignored, and is only significant in the one case we have described: a unicast query sent to a node that is both an NBT participant and the NBNS.

 
One of the great things
about books is sometimes
there are some fantastic
pictures.
-- George W. Bush
  

[Figure 1.11]

In summary:

    /* Pseudocode */
    if( the B bit is TRUE )
      { /* It's a broadcast query. */
      if( the receiver is a B, M, or H node )
        {
        entry = lookup name in local name table;
        if( entry was found )
          send( POSITIVE NAME QUERY RESPONSE );
        }
      }
    else
      { /* It's a unicast query. */
      entry = lookup name in local name table;
      if( entry was not found & RD is TRUE & receiver is the NBNS )
        {
        entry = lookup name in NBNS database;
        }
      if( entry was found )
        send( POSITIVE NAME QUERY RESPONSE );
      else
        send( NEGATIVE NAME QUERY RESPONSE );
      }

Got it? Good. Let's move on...

As with other NBT Name Service requests, if there is no response to a Name Query within a reasonable timeout period the query is sent again. This happens twice for a maximum of two retries (that is, three query messages). Timeouts vary from system to system and depend upon the type of query being sent. Query timeouts should be matched to those used for name registration where possible.

Broadcast queries: between 250ms and 750ms is typical. RFC 1002 specifies 250ms.
 
Unicast Resolution queries: a range of 1.5 to 2 seconds is common. These queries go to the NBNS, and the expectation is that the NBNS will be able to answer quickly. RFC 1002 specifies 5 seconds.
 
Verification queries: intervals of 1.5 to 5 seconds have been spotted. Once again, RFC 1002 specifies 5 seconds.

Timeout values are a balance between reliability and user annoyance. Too short, and replies will be missed. Too long, and the user goes off to make another pot of tea.

1.4.3.2.1 Negative Query Response

A negative response looks like this:

    NEGATIVE NAME QUERY RESPONSE
      {
      HEADER
        {
        NAME_TRN_ID = <Same as QUERY REQUEST>
        FLAGS
          {
          R      = TRUE (1; This is a response packet)
          OPCODE = 0x0 (Query)
          AA     = TRUE (1)
          RD     = <Copy RD bit from QUERY REQUEST>
          RA     = <TRUE if the reply is from the NBNS>
          B      = FALSE (0)
          RCODE  = <Error code>
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The Encoded NBT Name from the request>
        RR_TYPE  = <NB (0x0020), or possibly NULL (0x000A)>
        RR_CLASS = IN (0x0001)
        TTL      = 0
        RDLENGTH = 0
        }
      }

You are in a maze of
twisty little passages,
all alike.
-- Dungeon
R. M. Supnik, InfoCom
  

RFC 1002 is inconsistent in its descriptions of the RD and RA bits as used in NAME QUERY RESPONSE messages. There is also a small issue regarding the RR_TYPE field. Let's clear things up:

  • The diagram in RFC 1002 section 4.2.14 shows RD always set in the reply. Most implementations do, in fact, set the RD bit in all NAME QUERY RESPONSE messages. To be painfully correct, however, the right thing to do is to copy the RD value from the NAME QUERY REQUEST as described in RFC 1002, section 4.2.1.1. It really doesn't matter, though, because the RD bit is probably ignored by the node receiving the query response.

  • Regarding the RA bit: There is a weird little note in RFC 1002, section 4.2.15 which states:
        An end node responding to a NAME QUERY REQUEST always responds
        with the AA and RA bits set for both the NEGATIVE and POSITIVE
        NAME QUERY RESPONSE packets.

    That's poop. The RA bit should not be set by an end-node. Only the NBNS should set the RA bit, as explained in 4.2.1.1:

        RA    3   Recursion Available Flag.
    
                  Only valid in responses from a NetBIOS Name
                  Server -- must be zero in all other
                  responses.

    In modern usage, the RA bit should mean that the responding node is running the NBNS service.

  • [Annotation] The diagram in RFC 1002 section 4.2.14 specifies that RR_TYPE should have a value of 0x000A (NULL). In practice, the value 0x0020 (NB) is used instead (no exceptions were found in testing).

The NEGATIVE NAME QUERY RESPONSE will include an RCODE value, indicating the reason for the negative reply. RFC 1002 lists several possible RCODE values, but at least two of them--IMP_ERR and RFS_ERR--are incorrect as they are never generated in response to a query. The valid values or a NEGATIVE NAME QUERY RESPONSE are:

FMT_ERR (0x1):  Format Error
The NBNS did not like your message. Something was wrong with the packet format (perhaps it was mangled on the wire).
 
SRV_ERR (0x2):  Server failure
The NBNS is sick and cannot handle requests just now.
 
NAM_ERR (0x3):  Name Error
The requested name does not exist in the selected name table(s).
 

1.4.3.2.2 Positive Query Response

The POSITIVE NAME QUERY RESPONSE is similar to the negative response, with the following exceptions:

  • The RCODE is 0x0 (success),
  • the RR_TYPE field always has a value of 0x0020 (NB),
  • the TTL field is non-zero, and
  • the RDATA field contains an array of IP address information, like so:
    POSITIVE NAME QUERY RESPONSE
      {
      HEADER
        {
        NAME_TRN_ID = <Same as QUERY REQUEST>
        FLAGS
          {
          R      = TRUE (1; This is a response packet)
          OPCODE = 0x0 (Query)
          AA     = TRUE (1)
          RD     = <Copy RD bit from QUERY REQUEST>
          RA     = <TRUE if the reply is from the NBNS>
          B      = FALSE (0)
          RCODE  = 0x0
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The Encoded NBT Name from the request>
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = <Time To Live>
        RDLENGTH = <6 × number of entries>
        RDATA
          {
          ADDR_ENTRY[]
            {
            NB_FLAGS
              {
              G   = <TRUE for a group name, FALSE for a unique name>
              ONT = <Owner type>
              }
            NB_ADDRESS = <Owner's IP address>
            }
          }
        }
      }

If the packet is sent by the NBNS, the TTL field will contain the number of seconds until the entry's Time-To-Live expires (the remaining TTL). End nodes responding to verification queries will typically use the default TTL value which, as we described earlier, is something around 3 days.

1.4.3.2.3 The Redirect Name Query Response

The RFCs provide a mechanism whereby one NBNS can redirect a client to another NBNS. That is, the NBNS can return a message saying "I don't know, ask someone else".

No living examples of this mechanism have been seen in the wild. It is probably extinct. Fossil remains may be found in RFC 1001, section 15.1.5.3, and RFC 1002, section 4.2.15.

1.4.3.2.4 A Simple Name Query Revisited

[Annotation] Remember Listing 1.3? In that example we provided code for generating a simple broadcast name query. Listing 1.11, below, provides an updated version which is a bit more flexible. In particular, the BuildQuery() function takes several parameters, allowing you to customize the query you want to send. The program mainline, as given, sends only broadcast queries. It can, however, be easily hacked to create a more versitile command-line tool. This new version also listens for replies.

[Listing 1.11]

The sweet and chewey center of a POSITIVE NAME QUERY RESPONSE is the RDATA section, which contains an array of address entries. In most cases there will be only one entry, but a group name or a multi-homed host name may have several associated IP addresses. The contents of the ADDR_ENTRY records should be fairly familiar by now, so we won't dwell on them. Here are some quick functions which can be used to display the IP addresses and NB_FLAGS of an ADDR_ENTRY array:

[Listing 1.12]

1.4.3.3 Name Refresh

The refresh has two purposes. The first is to remind the NBNS that the client exists, thus ensuring that the name entry in the NBNS database does not expire. The second is to rebuild the NBNS database in the event of an NBNS crash. NAME REFRESH REQUEST messages are not needed in B mode since each node keeps track of its own names.

    NAME REFRESH REQUEST
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = <0x8 or 0x9> (Refresh)
          RD     = FALSE (0)
          B      = FALSE (0)
          }
        QDCOUNT = 1
        ARCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be refreshed>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      ADDITIONAL_RECORD
        {
        RR_NAME  = 0xC00C (Label String Pointer to QUESTION_NAME)
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = <Client's default TTL value (3 days)>
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Owner type>
            }
          NB_ADDRESS = <Requesting node's IP address>
          }
        }
      }

This message is almost identical to the unicast NAME REGISTRATION REQUEST, with a few small exceptions. Note, in particular, the following:

OPCODE: The NAME REFRESH REQUEST packet uses the Refresh OpCode. Due to a typo in RFC 1002, the OPCODE values 0x8 and 0x9 are considered equivalent and both mean NAME REFRESH REQUEST. 0x8 is more commonly used.
 
RD: The RD field is set to FALSE, which is a little strange since the NAME REFRESH REQUEST deals directly with the NBNS.
 
TTL: The TTL field typically contains the client's default TTL value; the same value used in the NAME REGISTRATION REQUEST. Once again, the NBNS has the right to override the client's TTL value in the TTL field of the response.
 
RDATA: The RDATA should match the data stored by the NBNS. If not, the NBNS will treat the request as if it were a registration request. If the refresh RDATA conflicts with the existing data, the NBNS may need to send a query to validate the older information in its database.

From watching packets on the wire18, it seems that Windows systems use the following formula to determine how frequently a refresh message should be sent:

    Refresh_Time = minimum( 40 minutes, (TTL/2) )

Based on the above formula, and considering that the default TTL value used by most clients is about three days, Windows NBNS clients typically send NAME REFRESH REQUEST messages every 40 minutes. This is a fairly high frequency, and it suggests a general lack of faith in the stability of the NBNS19.

The NBNS handles a NAME REFRESH REQUEST in exactly the same manner that it handles a NAME REGISTRATION REQUEST. There is little reason to distinguish between the two message types. Indeed, there is no multi-homed variant of the refresh message so multi-homed hosts perform the refresh operation by sending MULTI-HOMED NAME REGISTRATION REQUEST messages.

1.4.3.4 Name Release

Both B and P nodes (and their hybrid offspring, the M and H nodes) send NAME RELEASE messages to announce that they are giving up ownership of a name.

A NAME RELEASE sent in B mode is a NAME RELEASE DEMAND, as no response is expected. Any node receiving the release message will flush the released name from its local cache (if it has one20). In P mode, the release message sent by a node is a NAME RELEASE REQUEST, and it is always unicast to the NBNS. The message structure is the same in both cases:

    NAME RELEASE REQUEST or NAME RELEASE DEMAND
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x6 (Release)
          B      = <FALSE (0) for REQUEST, TRUE (1) for DEMAND>
          }
        QDCOUNT = 1
        ARCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be released>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      ADDITIONAL_RECORD
        {
        RR_NAME  = 0xC00C (Label String Pointer to QUESTION_NAME)
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = 0 (zero)
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Owner type>
            }
          NB_ADDRESS = <Releasing node's IP address>
          }
        }
      }
1.4.3.4.1 Name Release Response

The NBNS will always respond to a NAME RELEASE REQUEST. The response packet looks like this:

    NAME RELEASE RESPONSE
      {
      HEADER
        {
        NAME_TRN_ID = <Must match REQUEST transaction ID>
        FLAGS
          {
          R      = TRUE (1; This is a response packet)
          OPCODE = 0x6 (Release)
          AA     = TRUE (1)
          RCODE  = <See discussion>
          B      = FALSE (0)
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The Released Name, encoded as usual>
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = 0 (TTL has no meaning in this context)
        RDLENGTH = 6
        RDATA    = <Same as request packet>
        }
      }

Possible values for RCODE are:

0x0:  Success
POSITIVE NAME RELEASE RESPONSE
The name entry has been removed from the NBNS database.
 
FMT_ERR (0x1):  Format Error
Something got messed up, and the NBNS couldn't understand the request.
 
SRV_ERR (0x2):  Server failure
The NBNS is sick and cannot handle requests just now.
 
NAM_ERR (0x3):  Name Error
The name does not exist in the NBNS database or, if the name exists, the NB_FLAGS did not match (so it's not really the same name).
 
RFS_ERR (0x5):  Refused error
The NBNS has made a policy decision not to release the name. For some reason, the end node that sent the request does not have authority to remove it.
 
ACT_ERR (0x6):  Active error
The name was found in the database, but the NB_ADDRESS field did not match. Another node owns the name, so your node may not release it.

1.4.3.5 Node Status

The Node Status Request operation goes by many names: "Node Status Query", "Adapter Status Query", "NBSTAT", etc. This NBT message is used to implement the old NetBIOS Adapter Status command, which was used to retrieve information from LAN Adapter cards (LANAs, in PC Network terms).

    NODE STATUS REQUEST
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x0 (Query)
          B      = FALSE (0)
          }
        QDCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be queried>
        QUESTION_TYPE  = NBSTAT (0x0021)
        QUESTION_CLASS = IN (0x0001)
        }
      }

Note that these queries are sent from one end node to another. The NBNS is never involved. This is because the NBNS itself is not connected to an NBT virtual LAN Adapter. The NBNS is part of the infrastructure that creates the NetBIOS virtual LAN. Only the end nodes are actually members of the LAN.

1.4.3.5.1 Node Status Response

The response is not as simple as the query. The format of the reply depends upon the type of card and/or virtual adapter used to build the network. In the old days, different implementations of NetBIOS were built on top of different LANAs, or emulated on top of a variety of underlying transport protocols. Each implementation kept track of its own set of status information, so the reply to the Adapter Status command was vendor-specific.

The RFC authors developed their own reply structure, probably based in part on existing samples. The NODE STATUS RESPONSE looks like this:

    NODE STATUS RESPONSE
      {
      HEADER
        {
        NAME_TRN_ID = <Same as request ID.>
        FLAGS
          {
          R      = TRUE (1)
          OPCODE = 0x0 (Query)
          AA     = TRUE (1)
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <The queried name, copied from the request>
        RR_TYPE  = NBSTAT (0x0021)
        RR_CLASS = IN (0x0001)
        TTL      = 0 (TTL has no meaning in this context)
        RDLENGTH = <Total length of following fields>
        RDATA
          {
          NUM_NAMES = <Number of NODE_NAME[] entries>
          NODE_NAME[]
            {
            NETBIOS_NAME = <16-octet NetBIOS name, unencoded>
            NAME_FLAGS   = <See discussion below>
            }
          STATISTICS = <See discussion below>
          }
        }
      }

This packet will need some tearing apart.

The RDATA.NUM_NAMES field is one octet in length. The RDATA.NODE_NAME array represents the responding node's local name table: the list of names the end node believes it owns. Each entry in the array contains a NETBIOS_NAME field and a NAME_FLAGS field.

The NETBIOS_NAME field is 16 bytes in length. The 16-byte name includes the suffix byte and any required padding, and is not encoded. The wildcard name (an asterisk followed by 15 nul bytes) is never included in the name list, which contains only registered names.

The list of NetBIOS names all exist within the same NBT scope. The Scope ID will have been sent as part of the original query, and will be stored as part of the RR_NAME field in the reply. Recall that the empty string, "", is a valid Scope ID.

Along with each NETBIOS_NAME there is a NAME_FLAGS field, which provides name status information. It looks like this:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
 G   ONT  DRG CNF ACT PRM UNUSED

The above is the same as an NB_FLAGS field with four extra bits defined.

DRG: Deregister

When an end node starts the process of releasing a name, it sets this flag. The name will continue to exist in the node's local name table until the name is released.
 

CNF: Conflict

We have not fully described the Name Conflict condition yet. To put it simply, if two nodes believe they both own the same name (and at least one node claims that the name is unique) then the two nodes are in conflict. One of them has to lose. The loser sets the CNF bit in its local name table and gives up using the disputed name.
 

ACT: Active

This should always be set in the NODE STATUS RESPONSE packets. If, for some strange reason, the end node stores inactive names in its local name table, these are not reported.
 

PRM: Permanent

According to the RFCs, every NBT end node should register a permanent name. This flag identifies that name. In practice, however, most implementations do not bother with a permanent name and this flag is not used.

These flag values are displayed by Samba's nmblookup program. For example:

 shell
 $ nmblookup -S zathras#20
 querying zathras on 192.168.101.255
 192.168.101.15 zathras<20>
 Looking up status of 192.168.101.15
     ZATHRAS         <00> -         B <CONFLICT> <ACTIVE> 
     UBIQX           <00> - <GROUP> B <ACTIVE>
     ZATHRAS         <03> -         B <ACTIVE>
     ZATHRAS         <20> -         B <ACTIVE>
     UBIQX           <1e> - <GROUP> B <ACTIVE>
    

The above shows that all of the names are ACTIVE, as they should be. The name ZATHRAS<00>, however, has been disabled due to a name conflict. From the column of B's, it is apparent that Zathras is operating in B mode.

Now let's take a look at the RDATA.STATISTICS field.

This is where things really fall apart. Microsoft's STATISTICS blob is quite different from what is specified in the RFCs, and most likely for good reason. At the time the RFCs were published, Microsoft already had at least one NetBIOS implementation. Over time they built a few others, and they had software written to use those implementations. It probably made more sense to stick with familiar layouts than adopt the new one specified in the RFCs.

Fortunately, the data in the STATISTICS record is not particularly interesting, and current systems often fill most of it with zeros anyway. Only the first six bytes are commonly used now. Windows systems will attempt to place an Ethernet MAC address into this space. Samba leaves it zero filled.

Buglet Alert:

The NBT Name Service listens on port 137, but queries may originate from any UDP port number. Such is the nature of UDP. Programs like Samba's nmblookup utility will open a high-numbered UDP port (something above 1023) in order to send a query. The reply should be sent back to that same port.

[Annotation] In early versions of Windows 95, however, the source port in NODE STATUS REQUEST messages was ignored. The NODE STATUS RESPONSE message was sent to UDP port 137--the wrong port. As a result, the node that sent the query might never hear the reply.
 

Time for another chunk of code. This next listing sends a NODE STATUS REQUEST message and then parses and displays the reply. As usual, it uses and builds upon functions presented in previous listings.

[Listing 1.13]

1.4.3.6 Name Conflict Demand

The name conflict demand is a simple message. It looks exactly like the NEGATIVE NAME REGISTRATION RESPONSE that we covered earlier, except that the RCODE field contains CFT_ERR (0x7).

To review:

    NAME CONFLICT DEMAND
      {
      HEADER
        {
        NAME_TRN_ID = <Whatever you like>
        FLAGS
          {
          R      = TRUE (1)
          OPCODE = 0x5 (Registration)
          AA     = TRUE (1)
          RD     = TRUE (1)
          RA     = TRUE (1)
          RCODE  = CFT_ERR (0x7)
          B      = FALSE (0)
          }
        ANCOUNT = 1
        }
      ANSWER_RECORD
        {
        RR_NAME  = <An NBT name owned by the target node>
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = 0
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Owner type>
            }
          NB_ADDRESS = <Owner's IP address>
          }
        }
      }

Once you've got NAME REGISTRATION RESPONSE packets coded up this one will be easy. The question is, what does it do?

The NAME CONFLICT DEMAND is sent whenever the NBNS or an end node discovers a name conflict somewhere on the NBT network. The goal is to make the offending node aware of the fact that it has stolen another node's name. An NBNS might send one of these if it finds an inconsistency in its database, possibly as a result of synchronizing with another NBNS21. An end node will send a NAME CONFLICT DEMAND if it gets conflicting replies to a NAME QUERY REQUEST, working under the assumption that the first response is the correct one.

When a node receives a NAME CONFLICT DEMAND it is supposed to disable the offending name. Any existing connections that were made using that name are unaffected, but the node will no longer respond to name queries for the disabled name, nor will it allow the disabled name to be used for new connections. It's as if the name no longer exists.
 

...the moral equivalent of
Internet cow-tipping.
-- David Farmer III
  

There is an obvious security problem with this behavior. An evildoer can easily disable a name on, say, a file server or other important node. That alone could cause a Denial of Service condition but the evildoer can go further by registering the same name itself, thus assuming the identity of the disabled node. For this reason, Samba and most (but not all) Windows systems ignore NAME CONFLICT DEMAND messages.

1.4.3.6.1 Name Release Demand Revisited

There are actually two messages that can be used to force a node to give up a name. In addition to the NAME CONFLICT DEMAND, there is the NAME RELEASE DEMAND. You may recall that a node operating in B (or M or H) mode will broadcast a release announcement when it wants to release one of its own names. The same message can be unicast to another node to force the node to give up a name it holds.

    NAME RELEASE DEMAND (unicast)
      {
      HEADER
        {
        NAME_TRN_ID = <Set when packet is transmitted>
        FLAGS
          {
          OPCODE = 0x6 (Release)
          B      = FALSE (0)
          }
        QDCOUNT = 1
        ARCOUNT = 1
        }
      QUESTION_RECORD
        {
        QUESTION_NAME  = <Encoded NBT name to be released>
        QUESTION_TYPE  = NB (0x0020)
        QUESTION_CLASS = IN (0x0001)
        }
      ADDITIONAL_RECORD
        {
        RR_NAME  = 0xC00C (Label String Pointer to QUESTION_NAME)
        RR_TYPE  = NB (0x0020)
        RR_CLASS = IN (0x0001)
        TTL      = 0 (zero)
        RDLENGTH = 6
        RDATA
          {
          NB_FLAGS
            {
            G   = <TRUE for a group name, FALSE for a unique name>
            ONT = <Target node's owner type>
            }
          NB_ADDRESS = <Target node's IP address>
          }
        }
      }

As with the NAME CONFLICT DEMAND most (but not all) systems ignore this message. Play around... see what you find.

1.4.4 Enough Already

We could dig deeper. We could provide finer detail. We could, for instance, discuss the design, implementation, care, and feeding of a full-scale NBNS. ...but not now. It's getting late and we still have a lot of NBT ground to cover. Go ahead and take a quick break. Hug the spouse, make a fresh pot of tea, visit the facilities, scratch the dog, and then we'll move on to the Datagram Service.

When you get back, we will start by overstating one of the key points of this section: that the purpose of the Name Service is to create a virtual NetBIOS LAN on top of a TCP/IP (inter)network.


1.5 The Datagram Service in Detail

Let's drill home this key concept one more time:
 

Oh no no, not again
-- Oh No, Not Again
Alan Hull, Lindisfarne
  

NBT provides a set of services which combine to create a virtual NetBIOS LAN over TCP/UPD/IP transport.

This would be a senseless thing to do except for the fact that lots of software uses (or used to use) the NetBIOS API. The whole point is to maintain the form and function of the API while completely replacing the guts and machinery which lie beneath. This point gets lost, however, when we deal with systems that are not derived from MS-DOS and have no use for NetBIOS itself. On these systems we work directly with the guts of NBT and, therefore, are easily confused by the odd behavior of the machinery.

So, to provide a little context, here are the four NetBIOS API functions which the Datagram Service was designed to support:

  • Send Specific Datagram
  • Receive Specific Datagram
  • Send Broadcast Datagram
  • Receive Broadcast Datagram

Let's start by looking at the two Send Datagram functions. These two API calls provide us with three transmission options: unicast, multicast, and broadcast. Here's how they work:

Send Specific Datagram:
This function requires a NetBIOS name as a parameter.
  • If the name is unique, the datagram is unicast.
  • If the name is a group name, then the datagram is multicast.

Send Broadcast Datagram:
This function does not accept a NetBIOS name.
Broadcast datagrams are sent the length and breadth of the NetBIOS LAN, and picked up by any node that is listening.

That was easy. Now let's look at what happens when we map those functions onto UDP/IP at the NBT layer...

Send Specific Datagram:
A NAME QUERY REQUEST is issued to discover whether the destination name is a unique or group name.
  • If it is a unique name, then the message can be encapsulated in a UDP packet and sent to the IP address given in the NAME QUERY RESPONSE. In NBT terminology, this is a DIRECT UNIQUE DATAGRAM.
  • If it is a group name...
    • If the sender is operating in B mode, it will broadcast the packet on the local IP subnet so that all group members can receive it.
    • If the sender is not operating in B mode, then the datagram is forwarded to the NetBIOS Datagram Distribution Server (NBDD).
    In NBT terminology, a multicast datagram is known as a DIRECT GROUP DATAGRAM.

Send Broadcast Datagram:
The wildcard name (with the sender's Scope ID appended) is used as the destination name.
  • If the sender is operating in B mode, it will broadcast the packet on the local IP subnet. All NBT nodes within the same scope will be able to receive the message.
  • If the sender is not operating in B mode, then the datagram is forwarded to the NetBIOS Datagram Distribution Server (NBDD).

As you can see from the description, unicast datagrams are easy, B mode is easy, but handling multicast or broadcast in P, or M, or H mode is a bit more complicated. We'll give that topic a section heading all its own, just to show that it is a fairly hefty chunk of tofu.

1.5.1 Datagram Distribution over Routed IP Internetworks

The NetBIOS Datagram Distribution Server (NBDD) compliments the NBNS. It assists in extending the virtual NetBIOS LAN across a routed IP internetwork by relaying multicast and broadcast NetBIOS datagrams to nodes on remote subnets. The NBDD's job is to make sure that the datagrams get to where they're supposed to go. It works something like a lawn sprinkler--one input leads to a spray of outputs. Here's what happens:

  • A P (or M or H) node sends a datagram to the NBDD.
  • The NBDD consults the NBNS database and gathers the IP addresses of all intended recipients.
  • The NBDD then sends a copy of the message, via unicast UDP datagrams, to each of the IP addresses in the list.

[Figure 1.12]

That seems simple enough, but we claimed earlier that the Datagram Service is the second-least well understood aspect of NBT. What are we missing?
 

For best results,
avoid doing stupid things.
-- Acme Klein Bottle, Inc.
  

A closer inspection reveals an obvious problem. If the number of destination nodes is large, a whole bigbunch of traffic will be generated--possibly enough to bring the NBT virtual LAN to its knees (which might really, really annoy people). The NBDD design will work well enough for small, trusted networks but it simply does not scale.

Another problem is that RFC 1001 offers a loophole for implementors: the NBDD is permitted to silently ignore requests to relay datagrams. If, for any reason (including laziness on the implementor's part) the NBDD will not or can not relay a datagram, it simply discards the message without sending any sort of error message back to the originating node.

This loophole might make the NBDD so unreliable as to be useless, except that the Datagram Service also supports a query operation that allows the client to ask the NBDD whether or not it will relay a message. If the NBDD answers the query with a "yes", then the client can send the datagram with the assurance that it will be relayed to all intended recipients. A negative reply means that the NBDD will not relay the message.

Reminder Alert:

Datagrams are not considered reliable. As with the UDP service in an IP network, it is expected that some NetBIOS datagrams may be lost.

By allowing the NBDD to silently discard datagrams, however, the lack of reliability is amplified well beyond what would be expected from simple network packet loss.
 

One more monkey-wrench to throw into the works... Given a multicast (not broadcast) datagram, if the NBDD will not relay the message the client can give it another shot. The client has already performed a Name Service NAME QUERY REQUEST operation, and received a NAME QUERY RESPONSE from the NBNS. It did this to determine that the destination name was, in fact, a group name rather than a unique name. If the NBNS is RFC-compliant, then the NAME QUERY RESPONSE will contain a list of all of the IP addresses of the group members. If the NBDD won't relay the message, then the client can unicast the datagram to each entry in the list.

To summarize:

  • Unicast datagrams are simply sent to the intended recipient.
  • In B mode, multicast/broadcast datagrams are broadcast on the local LAN.
  • For multicast/broadcast datagrams in P, H, and M modes the NBDD should be queried to see if will relay the datagram.
    • If a positive response is received, then send the datagram to the NBDD for distribution.
    • Else:
      • If the datagram is multicast and the Name Server returned a complete IP list, then send the message via unicast datagrams to each IP in the list.
      • Else, broadcast the datagram on the local subnet and hope that some nodes will receive it.

Confused? Don't be surprised if you are. It isn't a pretty system...and it gets worse. Because of the potential network problems and the awkwardness of the design, very few implementations even try to match the RFC specification. Unfortunately, no one has come up with a better solution...which means that what actually exists in the wild is worse than what was just described.

1.5.2 The NBDD and the Damage Done

Further, I believe [RFC]
1001/1002 in general to be
overspecified.
--Paul Leach, Microsoft
  

To be blunt, Microsoft messed up the Datagram Service. Their NBNS implementation (WINS) does not report all of the IP addresses associated with a group name. Instead, group names are mapped to a single IP address--the limited broadcast address: 255.255.255.255. This is contrary to the RFCs, and it causes a few problems.

Without a list of IPs for each group name, the NBDD cannot be implemented at all. With no NBDD and no IP list, there is no way to ensure that multicast and broadcast datagrams will be sent to all group members. This breaks the continuity of the NetBIOS virtual LAN. On a "real" NetBIOS LAN, a message sent to a group name would actually reach all members of that group. That is what should happen under NBT as well, but it doesn't. The best that can be done is to broadcast on the local subnet, in which case some members of the group may get the message.

[Annotation] Microsoft must have realized their mistake, because they later created what they call "Internet Group" names (also called "Special Group" names). For names in this category, WINS comes close to behaving like a proper NBNS; it will store up to 25 IP addresses per name, deleting the oldest entry to make room if necessary. For these names, a POSITIVE NAME QUERY RESPONSE from a WINS server will list up to 25 valid IP addresses.

Internet Group names are identified by their suffix. Originally only group names with the 0x1C suffix were given special treatment, but more recently (with W2K?) group names with a suffix value of 0x20 can be defined as having Internet Group status. Note that unique names may also have these suffixes but, since they are not group names, no special handling is required.
 

Who's more foolish, the fool
or the fool who follows him?
-- Obi Wan Kenobi
(Sir Alec Guinness)
Star Wars
  

Sadly, most non-Microsoft implementations (including Samba) follow Microsoft's example. They map group names to the 255.255.255.255 IP address, store only 25 IPs for Special Group names, and fail to implement the NBDD22. This can cause trouble for some clients (OS/2, for example) which expect RFC behavior.

Sigh.

1.5.3 Implementing a Workable Datagram Service

That last section was a bit of a rant. Sorry. Time to pick up the pieces and move on. Let's talk about how the parts that work actually work.

As with the Name Service, each Datagram Service packet has a header. The datagram header is 10 bytes long, arranged as follows:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
MSG_TYPE FLAGS
DGM_ID

SOURCE_IP
 
SOURCE_PORT

Here is a quick rundown of the fields:

MSG_TYPE: (1 byte)
This field is something like the OPCODE field in the Name Service header. It indicates which type of Datagram Service message is being sent. It has the following possible values:
        0x10 == DIRECT_UNIQUE DATAGRAM
        0x11 == DIRECT_GROUP DATAGRAM
        0x12 == BROADCAST DATAGRAM
        0x13 == DATAGRAM ERROR
        0x14 == DATAGRAM QUERY REQUEST
        0x15 == DATAGRAM POSITIVE QUERY RESPONSE
        0x16 == DATAGRAM NEGATIVE QUERY RESPONSE

The first three of these represent unicast, multicast, and broadcast datagrams, respectively. The DATAGRAM ERROR packet is used to report errors within the Datagram Service. (There are only three errors defined in the RFCs.) The final three types are used in the query mechanism described earlier.
 

FLAGS: (1 byte)
This field breaks down into a set of bitwise subfields:

 0  1  2  3  4  5  6  7
UNUSED SNT F M

SNT Sending Node Type
This subfield has the following set of possible values (in binary):

00 == B node
01 == P node
10 == M node
11 == NBDD

Microsoft did not implement the NBDD. They did, however, introduce H mode. In practice the value 11 is used to indicate that the sending node is an H node.
 

F FIRST flag.
Indicates that this is the first (and possibly only) packet in a fragmented series.
 
M MORE flag.
Indicates that the message is fragmented, and that the next fragment should follow.

The F and M flags are used to manage fragmented messages, which will be described in more detail real soon now.
 

DGM_ID: (2 bytes)
The Datagram ID is similar in purpose to the NAME_TRN_ID field in Name Service headers. There should be a unique DGM_ID for each (conceptual) call to the NetBIOS Send Specific Datagram or Send Broadcast Datagram functions. More about this when we discuss fragmented messages.
 
SOURCE_IP: (4 bytes)
The IP address of the originating node. If the datagram is being relayed via the NBDD, then the IP header (at the IP layer of the stack, rather than the NBT layer) will contain the IP address of the NBDD. The SOURCE_IP field keeps track of the IP address of the node that actually composed the datagram.
 
SOURCE_PORT: (2 bytes)
As with the SOURCE_IP field, this field indicates the UDP port used by the originating node.

The above fields are common to all Datagram Service messages. RFC 1002 includes two more fields in its header layout: the DGM_LENGTH and PACKET_OFFSET fields. It is kind of silly to have those fields in the header, as they are specific to the messages which actually carry a data payload: the DIRECT_UNIQUE, DIRECT_GROUP, and BROADCAST DATAGRAM message types.

Since the DGM_LENGTH and PACKET_OFFSET fields are associated with the datagram transport messages, we will break with tradition and put those fields together with the message structure. Here is a record layout:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
DGM_LENGTH
PACKET_OFFSET

SOURCE_NAME
 

DESTINATION_NAME
 

USER_DATA
 

DGM_LENGTH: (2 bytes)
The formula given for calculating the value of the DGM_LENGTH field is:
        DGM_LENGTH = length( SOURCE_NAME )
                   + length( DESTINATION_NAME )
                   + length( USER_DATA )
That is, the number of bytes following the PACKET_OFFSET field23.
 
PACKET_OFFSET: (2 bytes)
Used in conjunction with the F and M flags in the header to allow reconstruction of fragmented NetBIOS datagrams.
 
SOURCE_NAME: (34..255 bytes)
The encoded NBT name of the sending application. Recall that NBT names are communication endpoints in much the same way that a UDP or TCP port is an endpoint. The correct SOURCE_NAME must be supplied to identify the service or application that sent the datagram.
 
DESTINATION_NAME: (34..255 bytes)
The encoded NBT name of the destination application or service. For broadcast datagrams, the DESTINATION_NAME will be the wildcard name--an asterisk ('*') followed by fifteen nul bytes. The NBT name must include the Scope ID (even if it is the default empty scope: "").
 
USER_DATA: (0..512 bytes)
The actual data to be transmitted.

That's basically all there is to it, with the exception of fragmentation. The example packet below describes an unfragmented message.

    DATAGRAM_HEADER (unfragmented)
      {
      MSG_TYPE = <10 = unicast, 11 = multicast, 12 = broadcast>
      FLAGS
        {
        SNT = <Node type, as shown above>
        F   = TRUE   (This is the first in the series)
        M   = FALSE  (No additional fragments follow)
        }
      DGM_ID      = <Datagram identifier>
      SOURCE_IP   = <IP address of the originating node>
      SOURCE_PORT = <Originating UDP port>
      }
    DATAGRAM_DATA
      {
      DGM_LENGTH       = <Name lengths plus USER_DATA length>
      PACKET_OFFSET    = 0
      SOURCE_NAME      = <Fully encoded NBT name of the sender>
      DESTINATION_NAME = <Fully encoded NBT name of the receiver>
      USER_DATA        = <Datagram payload>
      }

Some quick notes:

  • The DGM_ID should be unique with respect to other active datagrams originating from the same source. With 64K values from which to choose, this should not be difficult.

  • Once again, the SOURCE_IP, SOURCE_PORT, and SOURCE_NAME are all relative to the originator of the datagram. An NBDD does not alter these fields when it relays a message.

  • NBT datagrams are sent within scope. The Scope ID must be present in the SOURCE_NAME and DESTINATION_NAME fields, even if it is the empty scope ("").

1.5.3.1 Fragmenting Datagrams

A little way back, we mentioned the NetBIOS API Send Specific Datagram and Send Broadcast Datagram functions. These functions each accept up to 512 bytes of data on input. Given that number, the maximum on-the-wire size of an NBT Datagram is:

       10 bytes (Header)
    +   2 bytes (DGM_LENGTH)
    +   2 bytes (PACKET_OFFSET)
    + 255 bytes (maximum size of SOURCE_NAME)
    + 255 bytes (maximum size of DESTINATION_NAME)
    + 512 bytes (maximum size of USER_DATA)
    -----------
     1036 bytes

...and that, of course, does not include the UDP and IP headers. The whole thing is fairly chunky--easily more than double the size of the data actually being sent.

The RFC authors were concerned that the UDP transport might not carry datagrams that big, so they provided a mechanism for breaking the USER_DATA into smaller fragments, like so:

First Fragment:
    FLAGS.F       = TRUE (This is the first fragment)
    FLAGS.M       = TRUE (Additional fragments follow)
    PACKET_OFFSET = 0

Middle Fragments:
    FLAGS.F       = FALSE (This is the not the first fragment)
    FLAGS.M       = TRUE  (Additional fragments follow)
    PACKET_OFFSET = <Data offset of fragment>

Final Fragment:
    FLAGS.F       = FALSE (This is not the first fragment)
    FLAGS.M       = FALSE (No more fragments follow)
    PACKET_OFFSET = <Data offset of fragment>

The value of the PACKET_OFFSET field is the sum of the lengths of all previous fragments. This value is included in the message so that the receiver can keep the fragments in sync as it rebuilds the original USER_DATA. This is necessary, because datagrams do not always arrive in the order in which they were sent.

Now that you have learned all of that, you can forget most of it. As is typical for the Datagram Service, the fragmentation feature is rarely--if ever--used. The IP layer has its own mechanism for handling large packets so NBT datagram fragmentation is redundant.

It is possible that someone, somewhere, has implemented fragmentation, so an NBT implementation should be prepared to deal with it. One simple option is to discard fragments. This can be considered valid because the Datagram Service is considered "unreliable".

Something else to keep in mind: The 512-byte maximum size for the USER_DATA field is required at the NetBIOS layer, but not the NBT layer. Since the NetBIOS API is not required for implementing NBT, you mustn't expect that the datagrams you receive will fit within the limit. Code defensively.

1.5.3.2 Receiving Datagrams

If a tree falls in the
forest, and no one is there
to hear it...
  

NBT receives datagram messages on UDP port 138, so clients must listen on that port at the UDP level. When a message datagram is received (MSG_TYPE is one of 0x10, 0x11, or 0x12) the DESTINATION_NAME is checked against the local name table. If the name is not found, the client should reply with a DATAGRAM ERROR MESSAGE. The available error codes are:

      0x82 == DESTINATION NAME NOT PRESENT
      0x83 == INVALID SOURCE NAME FORMAT
      0x84 == INVALID DESTINATION NAME FORMAT

The first value is used whenever the DESTINATION_NAME is not in the local name table at the receiving end. The other two codes are sent whenever the source or destination NBT names, respectively, cannot be parsed.

If the name is found in the local table, then the Datagram may be passed to any application or service that is listening for the given DESTINATION_NAME. The NetBIOS API provides the Receive Specific Datagram and Receive Broadcast Datagram calls for this purpose. If there are no Receive Datagram requests waiting, the datagram is quietly discarded.

NBDD processing (for those bold enough to want to implement an NBDD) is similar. When the NBDD receives a datagram it will search the NBNS database instead of the local name table. Error messages are returned as above for missing or malformed names.

One more note: As a safety precaution, the receiving node should probably verify that the SOURCE_IP field in the datagram header matches either the source address in the IP header, or the NBDD address (if there is one).

1.5.3.3 Querying the NBDD

The NBDD query message is simply an NBT Datagram Service header with the DESTINATION_NAME appended:

    DATAGRAM_HEADER
      {
      MSG_TYPE = 0x14 (DATAGRAM QUERY REQUEST)
      FLAGS
        {
        SNT = <Node type>
        F   = TRUE
        M   = FALSE
        }
      DGM_ID      = <Datagram identifier>
      SOURCE_IP   = <IP address of the originating node>
      SOURCE_PORT = <Originating UDP port>
      }
    DATAGRAM_DATA
      {
      DESTINATION_NAME = <Encoded NBT name of the intended receiver>
      }

If there is an NBDD, and if it can relay the request, it will change the MSG_TYPE field to 0x15 (POSITIVE QUERY RESPONSE) and echo the packet back to the sender. If the NBDD is unwilling or unable to relay the message it will set MSG_TYPE to 0x16 (NEGATIVE QUERY RESPONSE) before sending the reply.

1.5.3.4 The Second-least Well Understood Aspect of NBT

It really should have been much simpler, but given the design flaws and implementation errors it is no wonder people have trouble with the Datagram Service. Our hope is that this section has cleared things up a bit, and explained the problems well enough to make them easier to solve.

Just to finish up, here are a few tips:

  • The NBDD should never relay datagrams to itself. If the NBDD host is also an NBT end node, then it must deliver datagrams to itself and then pass them along to the NBDD. There is no way to know if a received datagram is intended for the end node or the NBDD.

  • Likewise, if a host is acting as both end node and NBDD, the end node processing should not generate DESTINATION NAME NOT PRESENT (0x82) errors. The datagram should be passed along to the NBDD instead.

  • The NBNS should store all IP addresses associated with a group name. If necessary, it can return the local broadcast IP address (255.255.255.255) in response to name queries, thus maintaining compatibly with Microsoft's WINS. Storing all group name IP addresses is necessary for NBDD implementation.

  • Set a limit on the size of the IP list to which an NBDD will relay messages.

  • Don't worry about it. If you get the basics right, your system will work well enough. Very few systems expect a complete and proper NBT Datagram Service implementation.


1.6 The Session Service in Detail

This is the last big chunk of NBT. It is also the easiest, which should bring a great sigh of relief. We have already covered all of the background material we need to cover, so there is no need to waste any time with preliminaries. Let's dive right in...

1.6.1 Session Service Header

The Session Service header, as presented in RFC 1002, is as follows:

 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
TYPE FLAGS
LENGTH

The FLAGS field breaks down further into:

 0  1  2  3  4  5  6  7
reserved E

The reserved bits are always supposed to be zero, and the E bit is an additional high-order bit which is prepended to the LENGTH field. Another way to look at the layout is like this:

0 1 2 3 4 5 6 7 8 9 1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8
2
9
3
0
3
1
TYPE reserved LENGTH (17 bits)

We will stick with the latter, simpler format and ignore the FLAGS field, which is never really used.

The LENGTH field contains the number of bytes of payload, and the TYPE field is used to distinguish between the six different Session Service message types, which are:

    0x00 == Session Message
    0x81 == Session Request
    0x82 == Positive Session Response
    0x83 == Negative Session Response
    0x84 == Retarget Session Response
    0x85 == Session Keepalive

...each of which are explained below.

1.6.2 Creating an NBT Session

The first step in setting up an NBT session is to discover the IP address of the remote node. The IP address is, of course, required in order to create the TCP session that will carry the NBT Session. The NBT Name Service is generally used to find the remote host's IP address, though several implementations support kludges which bypass the Name Service. Once the TCP session is established (something we assume you know how to do) the NBT session is initiated using a SESSION REQUEST message, which looks like this:

    SESSION REQUEST
      {
      HEADER
        {
        TYPE   = 0x81 (Session Request)
        LENGTH = 68   (See discussion below)
        }
      CALLED_NAME  = <Destination Level 2 Encoded NetBIOS name>
      CALLING_NAME = <Source Level 2 Encoded NetBIOS name>
      }

One oddity of the Session Service is that the Scope ID is dropped from the name fields in the SESSION REQUEST message. That results in a fixed length of 34 bytes per name. That's one byte for the leading label (always 0x20), 32 bytes for the level-one encoded NetBIOS name, and one more byte for the trailing label (always 0x00). The payload of a SESSION REQUEST message is, therefore, fixed at 2 × 34 = 68 bytes.
 

You don't have to call me
  darlin', darlin'
You never even call me
  by my name.
-- You Never Even Call Me
  By My Name
,
Steve Goodman
  

Caveat Alert:

The RFCs do not specify whether the Scope ID should or should not be included in the CALLED or CALLING NAME. It would make sense to assume that the Scope ID should be included, since both the Name Service and Datagram Service require the Scope ID, but that's not how things actually work on the wire.

As it is, the behavior of the Session Service is inconsistent with the rest of the NBT system. Fortunately, Scope is enforced by the Name Service, so it is not critical that it be enforced by the Session Service.
 

There are three possible replies to the SESSION REQUEST message:

0x82: POSITIVE SESSION RESPONSE

The remote node has accepted the session request, and the session is established. Kewl!

        POSITIVE SESSION RESPONSE
          {
          HEADER
            {
            TYPE   = 0x82
            LENGTH = 0
            }
          }

0x83: NEGATIVE SESSION RESPONSE

Something went wrong, and the remote node has rejected the session request.

        NEGATIVE SESSION RESPONSE
          {
          HEADER
            {
            TYPE   = 0x83
            LENGTH = 1
            }
          ERROR_CODE = <A Session Service Error Code>
          }

The one-byte ERROR_CODE field is supposed to indicate the cause of the trouble. Possible values are:

0x80: Not Listening On Called Name
The remote node has registered the CALLED NAME, but no application or service is listening for session connection requests on that name.
 
0x81: Not Listening For Calling Name
The remote node has registered the CALLED NAME and is listening for connections, but it doesn't want to talk to you. It is expecting a call from some other CALLING NAME.

There are some interesting implications to this. It means that a server could, potentially, be selective about which nodes may connect. On the other hand, it would be trivial to spoof the CALLING NAME.
 

0x82: Called Name Not Present
The remote node has not even registered the CALLED NAME. Better re-try your name resolution.
 
0x83: Insufficient Resources
The remote node is busy and cannot take your call at this time.
 
0x8F: Unspecified Error
Something is wrong on the far end, but we are not quite sure what the problem is.

It is annoying that the error code values overlap the Session Service message type values.

0x84: RETARGET SESSION RESPONSE

This Session Service message tells the calling node to try a different IP address and/or port number, something like a Redirect directive on a web page. When a client receives a RETARGET SESSION RESPONSE message in response to a SESSION REQUEST, it is supposed to close the existing TCP connection and open a new one using the IP address and port number provided.

        RETARGET SESSION RESPONSE
          {
          HEADER
            {
            TYPE   = 0x84
            LENGTH = 6
            }
          RETARGET_IP_ADDRESS = <New IP address>
          PORT                = <New TCP port number>
          }

This feature opens up some interesting possibilities. Retargeting could be used for load-balancing, fault-tolerance, or to allow unprivileged users to run their own SMB servers on high-numbered ports.

Of course, client support for this feature is inconsistent. Based on some simple tests, it seems that Samba's smbclient handles retargeting just fine, as do Windows 95 and Windows 98. In contrast, Windows2000 deals with the RETARGET SESSION RESPONSE as if it were an error message of some sort. W2K will retry the original IP address and port number, and then give up.

[Listing 1.14]

Who ya gonna call?
-- Ghostbusters
  

One more note regarding the Retarget message: there are NetBIOS name issues to consider. The CALLED NAME must be in the name table of the node that sends the RETARGET SESSION RESPONSE message, but it must also be accepted by the node to which the session is retargeted. That may take some juggling to get right.

[Figure 1.13]

For those interested in playing with retargeting, it is fairly easily done. Samba's smbd daemon can be told to listen on a non-standard port and, as a bonus, it ignores the CALLED NAME in the session request. You can run the retarget daemon listed above in combination with the Samba nmbd Name Service daemon, and retarget connections to smbd running on a high port on the same machine, or running on a remote machine.

1.6.3 Maintaining an NBT Session

There are two more Session Service message types to cover:

0x00: SESSION MESSAGE

Once you have established a session (by sending a SESSION REQUEST and receiving a POSITIVE SESSION RESPONSE) you are ready to send messages. Each message is prefixed with a SESSION MESSAGE header, which looks like this:

        HEADER
          {
          TYPE   = 0x00
          LENGTH = <Length of data to follow>
          }

Since the TYPE byte has a value of 0x00, and the next seven bits are always supposed to be zero as well, the Session Message header may be viewed simply as a long integer length value.

        length  = ntohl( *(ulong *)packet );

It might be wise to mask out the unused FLAGS bits, just in case.

0x85: SESSION KEEPALIVE

The Keepalive is used to detect a lost connection. Basically, if one node hasn't sent anything to the other node for a while (typically five to six minutes) it will send a SESSION KEEPALIVE, just to make sure the remote end is still listening. The receiver simply discards the message.

        HEADER
          {
          TYPE   = 0x85
          LENGTH = 0
          }

TCP is a connection-oriented protocol, so the Keepalive should generate an ACKnowledgement, or possibly a series of retries if the TCP ACK doesn't show up right away. The Keepalive message forces TCP to verify that the connection is still working, and to report back if there is a problem. If a problem is detected, the client or server can gracefully shut down its end of the connection.

RFC 1001 makes it clear that sending the NBT Session Service Keepalive message is optional. TCP itself also has a keepalive mechanism, which should be used instead, if possible.

1.6.4 Closing an NBT Session

Nothing to it. Once all activity across the session has stopped, simply shut down the TCP connection. At the NBT level, there are no special messages to send when closing the session.


1.7 Where it All Went Wrong


Danger, Will Robinson!
-- Robot
Lost In Space
  

Implementation is the journey from Theory to Practice. The two extremes, as we have shown, are divided by a vast chasm wherein dwell all the evil monsters from all the cheesy SCI-FI TV shows ever produced. For NBT implementors the journey can be perilous, and those who have gone before have blazed a somewhat twisted trail. Here are some of the dangers you will encounter along the way...

1.7.1 The 0x1Dirty Little Secret

Master Browser Servers (which are described later in the book) register unique names with the suffix 0x1D. The WINS server will happily acknowledge such registrations--and then drop them into a black hole and forget about them. When queried, the WINS server denies the existence of any 0x1D unique names. Nodes from one subnet can never know about the Master Browsers on other subnets, and there may be multiple nodes using the same unique 0x1D name.

B nodes are immune to this behavior, since they do not make use of NBNS services. If the NBT vLAN is operating in M or H mode, however, the Master Browser names will be unique within the local IP subnet only. The same name may be registered by another Master Browser on a separate subnet. Since the WINS server does not keep any record of 0x1D names, Master Browsers can only be located using a broadcast query, which means that P nodes can never find 0x1D names.

The strange handling of 0x1D names may be related to the lack of NBDD functionality. As you can see, an implementation that strays from the path will quickly find itself lost in a jungle of exceptions, special cases, and other yucky stuff from an old episode of "Outer Limits".

1.7.2 Twenty-five IPs or Less

An NBNS is supposed to keep a complete list of all IPs associated with each NBT name (group or multi-homed). If the list is too large to fit in a single UDP Name Query Reply datagram then, according to the RFCs, the NBNS is supposed to send a partial list with the truncation (TC) bit set. The client may then repeat the query using TCP port 137.

...but that never happens. If anyone ever did provide support for NBT Name Service over TCP, the code is now lost in space. As explained earlier, when WINS sends a NAME QUERY RESPONSE it will contain a maximum of 25 IPs per name.

1.7.3 Special Handling Required for 0x1B Names

A Domain Master Browser (a special kind of Master Browser, which is described later on in the book) will register unique names with a suffix of 0x1B. These names get special treatment in WINS. Whenever a 0x1B name is registed in WINS, the WINS server will look for a matching 0x1C group name and modify the entry.

[Annotation] As you may recall from our diatribe in the Datagram section, 0x1C group names are "Internet Group" or "Special Group" names, and WINS will keep track of 25 IP addresses per 0x1C group name. The weird thing is that WINS also sorts this IP list so that the IP address associated with the 0x1B name is at the top of the list.

Why?
 

Billions of bilious
blistering blue barnacles!
-- Captain Haddock
The Adventures of Tintin
Hergé
  

[Annotation] Well, see, the 0x1C names represent the set of Domain Controllers for a given NT Domain. Only the Primary Domain Controller is allowed to run the Domain Master Browser service and register the 0x1B name. So, by sorting the IP list, WINS ensures that the IP address of the Primary Domain Controller is always the first IP address in the 0x1C list of Domain Controllers.

1.7.4 Alternate Name Resoultion

There are several ways to bypass the NBT Name Service. The simplest is the use of an LMHOSTS file, which provides NetBIOS name to IP address mappings. LMHOSTS is similar in concept to the /etc/hosts file commonly used by Unix-y systems to provide TCP/IP name mappings.

Another Name Service bypass trick involves using DNS names or IP addresses instead of using NetBIOS names to find remote services. This trick is generally used when connecting to an SMB server via the NBT Session Service. The obvious problem here is that the Session Service expects that the CALLED NAME in the SESSION MESSAGE be correct.

There are several work-arounds to the naming problem.

  • One may guess that the service name matches the first label of the DNS name. This often works, but it is not guaranteed.

  • Another option is to send a NODE STATUS REQUEST and look through the reply for a unique name with a suffix of 0x20, which is likely to be the correct service name. (The SMB Server Service always uses the suffix 0x20.)

  • The ugliest (and also the most common) solution is to place the NetBIOS name "*SMBSERVER" into the CALLED NAME field (encoded, of course). This special name was introduced with Windows NT4, and is now supported by Samba and several commercial implementations. It is accepted for Session Service connections to the SMB Server Service, no matter what NetBIOS name is actually registered. Note that "*SMBSERVER" starts with an asterisk, which makes it an illegal NetBIOS name. The "*SMBSERVER" name is never registered, and name queries for this name should always fail.

  [Buy the Book!]   

1.7.5 The Awful Truth

The awful truth is that an NBT implementation must accommodate--and often comply with--the errors, kludges, omissions, and fumbles of the past. The installed base is simply too big to try and get it right. Not to worry. You now have enough information to build a working NBT implementation. Writing an NBNS and NBDD server should also be within reach, and you can pull code from some of the many Open Source projects that are out there (as long as you respect the licenses). We have covered all of the major pitfalls, and NBT is a resilient little system. Making it work is a lot easier than getting it right.


1 ...or, if you type the way I do, you can enter NEWT KELP to generate an error message.

2 Microsoft calls their NBNS implementation "Windows Internet Name Service" (WINS). The term WINS is now commonly used instead of NBNS, but we will be pedantic and stick with the latter.

3 If the NBT authors had used TCP/138 instead of 139 for the Session Service, they could have saved a couple of ports. Instead, TCP/138 and UDP/139 are wasted.

4 Note the use of the term "preferred". A close read of RFCs 883, 1034, 1035, and 2181 shows that the idea of using binary data in DNS records has been around for some time.

5 Some call this a "half-ASCII"-ed encoding scheme.

6 Such considerations are important when programming in C and its ilk.

7 "Pedantic" is the politically correct way to say "anal retentive".

8 Samba's behavior may change if no reason can be found to compare the decoded names. Decoding costs a few cycles, which could be significant in an NBNS implementation.

9 The lab in the basement is rather sparse, and not all versions of MS Windows can be tested. The reported behavior appears on those variants that were available. We leave it as an exercise for the reader to verify that the behavior is consistent. Please let us know of any contradictory results.

10 The program in listing 1.3 has been tested on Debian GNU/Linux and OpenBSD. You may have to fiddle a bit to get it to work on other platforms. Under older versions of NetBSD, OpenBSD, Miami for Amiga, and possibly other BSD-derived TCP stacks, messages sent to the limited broadcast address (255.255.255.255) may not actually be sent as Ethernet broadcasts. On these systems, it will be necessary to change the value of NBT_BCAST_ADDR to the directed broadcast address of the local subnet (the local subnet broadcast address). This bug has been fixed in both NetBSD and in OpenBSD. See the original NetBSD bug report (#7682) for more information.

11 With a few notable exceptions, this is the way that tea is prepared in American cafés. Ick.

12 To further complicate matters Microsoft has registered its own character sets, such as the Windows-1252 character set. Windows-1252 is a superset of ISO Latin-1. It uses octets in the range 0x80..0x9F (normally reserved for control characters) to represent some additional display characters, such as the trademark symbol (TM). This is why non-Microsoft web browsers on non-Microsoft platforms often display question marks all over the screen when they load web pages generated by Microsoft products.

13 Big-endian byte order is also known as "normal", "intuitive", or "obvious" byte order. Little-endian is sometimes referred to as "annoying", "dysfunctional", or "stupid". These designations do not, of course, reflect any bias or preference.

14 It is easy, but wrong, to simply copy back the information from the ADDITIONAL_RECORD of the NAME REGISTRATION REQUEST. The NEGATIVE NAME REGISTRATION RESPONSE should identify the node that currently owns the name. (...and yes, some day I may fix this in Samba.)

15  3 days 00:00:00 == 259,200 seconds == 0x0003F480 (Samba)
3 days 11:20:00 == 300,000 seconds == 0x000493E0 (Windows)
3 days 19:01:20 == 327,680 seconds == 0x00050000

16 Many thanks to Monyo for providing packet captures.

17 For example, when an NBNS is processing a multi-homed registration it should send name queries with the RD bit clear, yet all Windows systems that were tested set the RD bit. It may not matter, however, unless the multi-homed host is also the node running the NBNS, in which case the problem would likely be solved using internal mechanisms (because the NBNS would be sending the query to itself). The right thing to do is to send verification queries with the RD flag turned OFF.

18 Many thanks to Jean François for all of his work on WINS behavior and TTL gymnastics.

19 Microsoft may be assuming that the NBNS service is being provided by their own WINS implementation. Samba's NBNS, which is part of the nmbd daemon, periodically writes the contents of its database to a file called wins.dat. The wins.dat file is re-read at startup, and any non-expired names are placed back into the database. This prevents data loss due to system restart. Samba sends refreshes every TTL/2 seconds, and there have been reports of Samba server names "disappearing" from WINS databases following a Windows system crash. It is likely that newer versions of Samba (V3.0 and beyond) will use Microsoft's formula for calculating name refresh time.

20 Windows systems typically cache resolved names for about seven minutes. Use the nbtstat -c command from the DOS prompt to see the cache contents.

21 Microsoft's WINS servers can be configured to replicate with one another, simultaneously distributing the database for greater reliability and increasing the risk of conflicts and other corruption. WINS replication takes place over TCP port 42, should you care to observe. The replication protocol is fairly straight-forward and has been untangled. There are plans to add WINS replication support to Samba sometime after version 3.0 is released.

22 Network Telesystems, which has since been acquired by Efficient Networks, used to have an NBNS implementation that handled group names correctly and worked quite well with IBM's OS/2. Brian Landy has also written a set of patches for Samba's nmbd daemon which provide more complete NBDD support. See: http://www.landy.cx/

23 This field is probably not even used by most implementations. For a long time, Samba miscalculated the DGM_LENGTH field by including the 14-byte RFC header length. This bug (fixed as of 2.2.4) did not seem to cause any trouble.


<Previous] [Contents] [Next> [W3C Validated] Copyright © 1999-2003 Christopher R. Hertel 
All rights reserved.   $Revision: 1.265 $