4.1 NBT Names: Once More with Feeling

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 (' * ').

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

alphanumeric 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 alphanumeric characters may include extended characters, such as ' ' or ' '. 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 set. [1]

[1] 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 (). 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.

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.

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.

Annoyance Alert

graphics/alert.gif

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.


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 
 "\x20EOGFGLGPCACACACACACACACACACACAAA\x03CAT\x03ORG 
 "\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.

graphics/45fig01.gif

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.

graphics/45fig02.gif

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 throwback 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 shortcut, some implementations work directly with the encoded name and only bother to decode the name when interacting with a user . Decoding, however, is fairly straightforward:

Listing 4.1 Level 2 and Level 1 decoding
 int L2_Decode(uchar *dst,     /* Decoded name target buffer.   */                uchar *src,     /* Encoded name source buffer.   */                int    srcpos,  /* Start position of name.       */                int    srcmax) /* Size of source buffer.        */   {   int len;   int pos;   int next;   /* Be safe. */   dst[0] = ' 
 int L2_Decode(uchar *dst, /* Decoded name target buffer. */ uchar *src, /* Encoded name source buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int len; int pos; int next; /* Be safe. */ dst[0] = '\0'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void) memcpy (dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '\0' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '\0'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '\0'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '\0'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */ 
'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void)memcpy(dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '
 int L2_Decode(uchar *dst, /* Decoded name target buffer. */ uchar *src, /* Encoded name source buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int len; int pos; int next; /* Be safe. */ dst[0] = '\0'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void) memcpy (dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '\0' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '\0'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '\0'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '\0'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */ 
' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '
 int L2_Decode(uchar *dst, /* Decoded name target buffer. */ uchar *src, /* Encoded name source buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int len; int pos; int next; /* Be safe. */ dst[0] = '\0'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void) memcpy (dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '\0' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '\0'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '\0'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '\0'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */ 
'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '
 int L2_Decode(uchar *dst, /* Decoded name target buffer. */ uchar *src, /* Encoded name source buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int len; int pos; int next; /* Be safe. */ dst[0] = '\0'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void) memcpy (dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '\0' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '\0'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '\0'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '\0'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */ 
'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '
 int L2_Decode(uchar *dst, /* Decoded name target buffer. */ uchar *src, /* Encoded name source buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int len; int pos; int next; /* Be safe. */ dst[0] = '\0'; /* Get encoded string length (doesn't include root label). */ len = strlen((char *)&(src[srcpos])); /* If length is zero, return the empty string. */ if(0 == len) return(0); /* Make sure name does not exceed source buffer length. */ if(len >= (srcmax - srcpos)) return(-1); /* Copy source to destination skipping the first label length byte * (but including the terminating nul label length). */ (void) memcpy (dst, &(src[srcpos+1]), len); /* Now find remaining label length bytes * and convert them to dots. */ for(pos = src[srcpos]; /* Read the first label length. */ '\0' != (next = dst[pos]); /* While label length is > 0... */ pos += next + 1) /* Move one byte beyond label. */ { dst[pos] = '.'; } return(--len); /* Return string length. */ } /* L2_Decode */ int L1_Decode(uchar *name, /* Target. Minimum 16 bytes. */ uchar *src, /* Message buffer. */ int srcpos, /* Start position of name. */ int srcmax) /* Size of source buffer. */ { int i; int suffix; uchar *p = &src[srcpos]; /* Make sure we have 32 bytes worth of message to read. */ if((srcmax - srcpos) < 32) { name[0] = '\0'; return(-1); } /* Convert each source pair to their original octet value. */ for(i = 0; i < 32; i++) name[i/2] = ((((int)(p[i]) - 'A') << 4) + ((int)(p[++i]) - 'A')); /* Copy out suffix byte and replace with nul terminator. */ suffix = name[15]; name[15] = '\0'; /* Trim off trailing spaces, if any. */ for(i = 14; (i >= 0) && (' ' == name[i]); i--) name[i] = '\0'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */ 
'; return(suffix); /* Return the suffix value as an int. */ } /* L1_Decode */

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 
 "\x20EOGFGLGPCACACACACACACACACACACAAA\x03CAT\x03ORG 
 "\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.



Implementing CIFS. The Common Internet File System
Implementing CIFS: The Common Internet File System
ISBN: 013047116X
EAN: 2147483647
Year: 2002
Pages: 210

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net