4.2 NBT Name Service PacketsRFC 1002 lists 17 different Name Service packet types, constructed from three basic building blocks:
These pieces are described in more detail below. 4.2.1 Name Service HeadersThe header is an array of six 16-bit values, as follows :
Managing Name Service headers is fairly straightforward. 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:
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: (0x0007 << 11) = 0x3800 = 0 0111 00000000000(bin) ...which puts it where it's supposed to be:
Listing 4.2 presents NS_Header.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 4.2 Name Service packet header FLAGS subfield values: NS_Header.h#define NBTNS_R_BIT 0x8000 /* The 'R'esponse bit */ /* OPCODE values */ #define OPCODE_QUERY 0x0000 /* Query (0<<11) */ #define OPCODE_REGISTER 0x2800 /* Registration (5<<11) */ #define OPCODE_RELEASE 0x3000 /* Release (6<<11) */ #define OPCODE_WACK 0x3800 /* WACK (7<<11) */ #define OPCODE_REFRESH 0x4000 /* Refresh (8<<11) */ #define OPCODE_ALTREFRESH 0x4800 /* Alt Refresh (9<<11) */ #define OPCODE_MULTIHOMED 0x7800 /* Multi-homed (f<<11) */ #define OPCODE_MASK 0x7800 /* Mask */ /* NM_FLAGS subfield bits */ #define NM_AA_BIT 0x0400 /* Authoritative Answer */ #define NM_TR_BIT 0x0200 /* TRuncation flag */ #define NM_RD_BIT 0x0100 /* Recursion Desired */ #define NM_RA_BIT 0x0080 /* Recursion Available */ #define NM_B_BIT 0x0010 /* Broadcast flag */ /* Return Codes */ #define RCODE_POS_RSP 0x0000 /* Positive Response */ #define RCODE_FMT_ERR 0x0001 /* Format Error */ #define RCODE_SRV_ERR 0x0002 /* Server failure */ #define RCODE_NAM_ERR 0x0003 /* Name Not Found */ #define RCODE_IMP_ERR 0x0004 /* Unsupported request */ #define RCODE_RFS_ERR 0x0005 /* Refused */ #define RCODE_ACT_ERR 0x0006 /* Active error */ #define RCODE_CFT_ERR 0x0007 /* Name in conflict */ #define RCODE_MASK 0x0007 /* Mask */ /* Used to set the record count fields. */ #define QUERYREC 0x1000 /* Query Record */ #define ANSREC 0x0100 /* Answer Record */ #define NSREC 0x0010 /* NS Rec (never used) */ #define ADDREC 0x0001 /* Additional Record */ 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 order. [2] 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.
Bizarre Twist Alert
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 4.3 Read and write Name Service headers: NS_Header.c#include <netinet/in.h> /* htons(), ntohs(), etc. */ #include "NS_Header.h" /* From Listing 4.2. */ void Put_NS_TID( ushort hdr[], ushort TrnID ) /* ---------------------------------------------------- ** * Store the transaction ID in the Name Service header. * ---------------------------------------------------- ** */ { hdr[0] = htons( TrnID ); } /* Put_NS_TID */ void Put_NS_Hdr_Flags( ushort hdr[], ushort flags ) /* ---------------------------------------------------- ** * Store the flags in the NBT Name Service header. * ---------------------------------------------------- ** */ { hdr[1] = htons( flags ); } /* Put_NS_Hdr_Flags */ void Put_NS_Hdr_Rec_Counts( ushort hdr[], int reccount ) /* ---------------------------------------------------- ** * Place (ushort)1 into each record count field for * which the matching flag bit is set in reccount. * ---------------------------------------------------- ** */ { ushort one; one = htons( 1 ); hdr[2] = ( QUERYREC & reccount ) ? one : 0; hdr[3] = ( ANSREC & reccount ) ? one : 0; hdr[4] = ( NSREC & reccount ) ? one : 0; hdr[5] = ( ADDREC & reccount ) ? one : 0; } /* Put_NS_Hdr_Rec_Counts */ ushort Get_NS_Hdr_TID( ushort hdr[] ) /* ---------------------------------------------------- ** * Read and return the transaction ID. * ---------------------------------------------------- ** */ { return( ntohs( hdr[0] ) ); } /* Get_NS_Hdr_TID */ ushort Get_NS_Hdr_Flags( ushort hdr[] ) /* ---------------------------------------------------- ** * Read and return the flags field. * ---------------------------------------------------- ** */ { return( ntohs( hdr[1] ) ); } /* Get_NS_Hdr_Flags */ int Get_NS_Hdr_Rec_Counts( ushort hdr[] ) /* ---------------------------------------------------- ** * Convert the four record count fields into a single * flagset. * ---------------------------------------------------- ** */ { int tmp = 0; if( hdr[2] ) tmp = QUERYREC; if( hdr[3] ) tmp = ANSREC; if( hdr[4] ) tmp = NSREC; if( hdr[5] ) tmp = ADDREC; return( tmp ); } /* Get_NS_Hdr_Rec_Counts */ 4.2.2 Name Service Question RecordsThe 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 .
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 4.4a Reading/writing question records: NS_Qrec.h/* Query Type */ #define QTYPE_NB 0x0020 /* Name Query */ #define QTYPE_NBSTAT 0x0021 /* Adapter Status */ /* Query Class */ #define QCLASS_IN 0x0001 /* Internet Class */ Listing 4.4b Reading/writing question records: NS_Qrec.c#include <string.h> /* For memcpy() */ #include <netinet/in.h> /* htons(), ntohs(), etc. */ #include "NS_Qrec.h" int Put_Qrec( uchar *dst, const uchar *name, const uchar pad, const uchar sfx, const uchar *scope, const ushort qtype ) /* ---------------------------------------------------- ** * Store the fully encoded NBT name in the destination * buffer. Also write the QUERY_TYPE and QUERY_CLASS * values. * ---------------------------------------------------- ** */ { int len; ushort tmp; ushort qclass_in; qclass_in = htons( QCLASS_IN ); /* Validate the qtype. */ if( (QTYPE_NB != qtype) && (QTYPE_NBSTAT != qtype ) ) return( -1 ); len = L2_Encode( dst, name, pad, sfx, scope ); if( len < 0 ) return( len ); tmp = htons( qtype ); (void)memcpy( &(dst[len]), &tmp, 2 ); len += 2; (void)memcpy( &(dst[len]), &qclass_in, 2 ); return( len + 2 ); } /* Put_Qrec */ ushort Get_Qtype( const uchar *qrec, int offset ) /* ---------------------------------------------------- ** * Read the QUERY_TYPE field from a query record. * Note that the offset parameter can be derived using * L2_Decode() function in Listing 4.1. Either that, * or use 1+strlen( qrec ). * ---------------------------------------------------- ** */ { ushort tmp; /* Read the two bytes from the packet. */ tmp = (ushort)(qrec[offset]) * 256; tmp = qrec[1+offset]; /* Convert to host byte order and return. */ return( ntohs( tmp ) ); } /* Get_Qtype */ 4.2.3 Name Service Resource RecordsFor convenience, we will break the Resource Record into three sub- parts :
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.
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:
The values marked as "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 .
The last sub-part of the resource record is the resource data section, which is made up of two fields:
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 one message type to another. The Resource Record structure, as described in Section 4.2.1.3 of RFC 1002, looks just like this:
It is always good to have some code to play with. This next set of functions can be used to manipulate Resource Records. Listing 4.5a Name Service Resource Records: NS_Rrec.h/* Label String Pointer. */ #define LSP 0xC00C /* Pointer to offset 12 */ /* Resource Record Type. */ #define RRTYPE_A 0x0001 /* IP Addr RR (unused) */ #define RRTYPE_NS 0x0002 /* Name Server (unused) */ #define RRTYPE_NULL 0x000A /* NULL RR (unused) */ #define RRTYPE_NB 0x0020 /* NetBIOS */ #define RRTYPE_NBSTAT 0x0021 /* NB Status Response */ /* Resource Record Class. */ #define RRCLASS_IN 0x0001 /* Internet Class */ Listing 4.5b Name Service Resource Records: NS_Rrec.c#include <string.h> /* For memcpy() */ #include <netinet/in.h> /* htons(), ntohs(), etc. */ #include "NS_Rrec.h" int Put_RRec_Name( uchar *rrec, const uchar *name, const uchar pad, const uchar sfx, const uchar *scope, const ushort rrtype ) /* ---------------------------------------------------- ** * Create and store the fully qualified NBT name in the * destination buffer. Also store the RR_TYPE and * RR_CLASS values. * Return the number of bytes written. * ---------------------------------------------------- ** */ { int len; ushort tmp; ushort rrclass_in; /* Validate the rrtype. * Note that we exclude the A, NS, and NULL RRTYPEs * as these are never used. */ if( (RRTYPE_NB != rrtype) && (RRTYPE_NBSTAT != rrtype ) ) return( -1 ); len = L2_Encode( rrec, name, pad, sfx, scope ); if( len < 0 ) return( len ); tmp = htons( rrtype ); (void)memcpy( &(rrec[len]), &tmp, 2 ); len += 2; rrclass_in = htons( RRCLASS_IN ); (void)memcpy( &(rrec[len]), &rrclass_in, 2 ); return( len + 2 ); } /* Put_RRec_Name */ int Put_RRec_LSP( uchar *rrec, const ushort rrtype ) /* ---------------------------------------------------- ** * Write a Label String Pointer (LSP) instead of an NBT * name. RR_TYPE and RR_CLASS are also written. * Return the number of bytes written (always 6). * ---------------------------------------------------- ** */ { ushort tmp; ushort lsp; ushort rrclass_in; lsp = htons( 0xC00C ); (void)memcpy( rrec, &lsp, 2 ); tmp = htons( rrtype ); (void)memcpy( &(rrec[2]), &tmp, 2 ); rrclass_in = htons( RRCLASS_IN ); (void)memcpy( &(rrec[4]), &rrclass_in, 2 ); return( 6 ); } /* Put_RRec_LSP */ int Put_RRec_TTL( uchar *rrec, int offset, ulong ttl ) /* ---------------------------------------------------- ** * Write the TTL value at rrec[offset]. * By this point it should be obvious that functions or * macros for transferring long and short integers to * and from packet buffers would be a good idea. * ---------------------------------------------------- ** */ { ttl = htonl( ttl ); (void)memcpy( &(rrec[offset]), &ttl, 4 ); return( 4 ); } /* Put_RRec_TTL */ int Is_RRec_LSP( const uchar *rrec ) /* ---------------------------------------------------- ** * Check the Resource Record to see if the name field * is actually a Label String Pointer. * * If the name is not an LSP, the function returns 0. * If the name is a valid LSP, the function returns 12 * (which is the offset into the received packet at * which the QUERY_NAME can be found). * If the name contains an invalid label length, or * an invalid LSP, the function will return -1. * ---------------------------------------------------- ** */ { if( 0 == (0xC0 & rrec[0]) ) return( 0 ); /* Not an LSP */ if( (0xC0 == rrec[0]) && (0x0C == rrec[1]) ) return( 12 ); /* Valid LSP */ return( -1 ); /* Bogon */ } /* Is_RRec_LSP */ ushort Get_RR_type( const uchar *rrec, int offset ) /* ---------------------------------------------------- ** * Read the RR_TYPE value. The offset can be * determined by decoding the NBT name using the * L2_Decode() function from Listing 4.1. * ---------------------------------------------------- ** */ { ushort tmp; /* Read the two bytes from the packet. */ (void)memcpy( &tmp, &(rrec[offset]), 2 ); /* Convert to host byte order and return. */ return( ntohs( tmp ) ); } /* Get_RR_type */ ulong Get_RRec_TTL( const uchar *rrec, int offset ) /* ---------------------------------------------------- ** * Read the TTL value. * ---------------------------------------------------- ** */ { ulong tmp; (void)memcpy( &tmp, &(rrec[offset]), 4 ); return( ntohl( tmp ) ); } /* Get_RRec_TTL */ |