4.3 Conversations with the Name ServiceWe 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
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:
It's not a particularly formal syntax, but it will serve the purpose. 4.3.1 Name RegistrationNodes 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 modes 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 0x C 00C 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:
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: 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 4.6a RDATA Address Records: NS_RDaddr.h/* RDATA NB_FLAGS. */ #define GROUP_BIT 0x8000 /* Group indicator */ #define ONT_B 0x0000 /* Broadcast node */ #define ONT_P 0x2000 /* Point-to-point node */ #define ONT_M 0x4000 /* Mixed mode node */ #define ONT_H 0x6000 /* MS Hybrid mode node */ #define ONT_MASK 0x6000 /* Mask */ /* RDATA NAME_FLAGS. */ #define DRG 0x0100 /* Deregister. */ #define CNF 0x0800 /* Conflict. */ #define ACT 0x0400 /* Active. */ #define PRM 0x0200 /* Permanent. */ Listing 4.6b RDATA Address Records: NS_RDaddr.c#include <string.h> /* For memcpy() */ #include <netinet/in.h> /* htons(), ntohs(), etc. */ #include "NS_RDaddr.h" int Put_RDLength( uchar *rrec, int offset, ushort rdlen ) /* ---------------------------------------------------- ** * Set the value of the RDLENGTH field. * ---------------------------------------------------- ** */ { rdlen = htons( rdlen ); (void)memcpy( &(rrec[offset]), &rdlen, 2 ); return( 2 ); } /* Put_RDLength */ int Put_RD_Addr( uchar *rrec, int offset, ushort nb_flags, struct in_addr nb_addr ) /* ---------------------------------------------------- ** * Write IP NB_FLAGS and NB_ADDRESS fields to the * packet buffer. * * See inet(3) on any Linux/Unix/BSD system for more * information on 'struct in_addr'. * ---------------------------------------------------- ** */ { nb_flags = htons( nb_flags ); (void)memcpy( &(rrec[offset]), &nb_flags, 2 ); (void)memcpy( &(rrec[offset+2]), &nb_addr.s_addr, 4 ); return( 6 ); } /* Put_RD_Addr */ ushort Get_RDLength( const uchar *rrec, int offset ) /* ---------------------------------------------------- ** * Read the RDLENGTH field to find out how big the * RDATA field is. * ---------------------------------------------------- ** */ { ushort tmp; (void)memcpy( &tmp, &(rrec[offset]), 2 ); return( ntohs( tmp ) ); } /* Get_RDLength */ ushort Get_RD_NB_Flags( const uchar *rrec, int offset ) /* ---------------------------------------------------- ** * Read the NB_FLAGS field from an RDATA record. * ---------------------------------------------------- ** */ { ushort tmp; (void)memcpy( &tmp, &(rrec[offset]), 2 ); return( ntohs( tmp ) ); } /* Get_RD_NB_Flags */ struct in_addr Get_RD_NB_Addr( const uchar *rrec, int offset ) /* ---------------------------------------------------- ** * Read the NB_ADDRESS field from an RDATA record. * ---------------------------------------------------- ** */ { ulong tmp; struct in_addr tmp_addr; (void)memcpy( &tmp, &(rrec[offset]), 4 ); tmp_addr.s_addr = ntohl( tmp ); return( tmp_addr ); } /* Get_RD_NB_Addr */ 4.3.1.1 Broadcast Name RegistrationYou've seen the basic form of NAME REGISTRATION REQUEST packet. When sending a broadcast registration, the following rules apply.
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. Figure 4.1a. Broadcast unique/unique name conflictA unique name may not be registered if another node already owns that unique name.
Figure 4.1b. Broadcast unique/group name conflictA unique name may not be registered if the same name is registered as a group name.
Figure 4.1c. Broadcast group/unique name conflictA group name may not be registered if another node already owns the name as a unique name.
Figure 4.1d. No conflict when joining a groupAny node may join a group. Existing group members will not respond to the registration request.
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:
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 request. [3]
NEGATIVE NAME REGISTRATION RESPONSE messages are only sent if a unique name is involved. [4] 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.
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 750 ms). 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 4.7 A broadcast name registration#include <stdio.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "NS_Header.h" #include "NS_Qrec.h" #include "NS_Rrec.h" #include "NS_RDaddr.h" #define NBT_BCAST_ADDR "255.255.255.255" #define uchar unsigned char #define ushort unsigned short int BuildRegMsg( uchar *msg, const uchar *name, struct in_addr addr ) /* ---------------------------------------------------- ** * Create a Bcast Name Registration Message. * * This function hard-codes several values. * Obviously, a "real" implementation would need * to be much more flexible. * ---------------------------------------------------- ** */ { ushort *hdr = (ushort *)msg; uchar *rrec; ushort flags; int len; int rr_len; flags = OPCODE_REGISTER NM_RD_BIT NM_B_BIT; Put_NS_TID( hdr, 1964 ); Put_NS_Hdr_Flags( hdr, flags ); Put_NS_Hdr_Rec_Counts( hdr, (QUERYREC ADDREC) ); len = 12; /* Fixed size of header. */ len += Put_Qrec( &msg[len], /* Query Rec Pointer */ name, /* NetBIOS name */ ' ', /* Padding char */ '#include <stdio.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "NS_Header.h" #include "NS_Qrec.h" #include "NS_Rrec.h" #include "NS_RDaddr.h" #define NBT_BCAST_ADDR "255.255.255.255" #define uchar unsigned char #define ushort unsigned short int BuildRegMsg( uchar *msg, const uchar *name, struct in_addr addr ) /* ---------------------------------------------------- ** * Create a Bcast Name Registration Message. * * This function hard-codes several values. * Obviously, a "real" implementation would need * to be much more flexible. * ---------------------------------------------------- ** */ { ushort *hdr = (ushort *)msg; uchar *rrec; ushort flags; int len; int rr_len; flags = OPCODE_REGISTER NM_RD_BIT NM_B_BIT; Put_NS_TID( hdr, 1964 ); Put_NS_Hdr_Flags( hdr, flags ); Put_NS_Hdr_Rec_Counts( hdr, (QUERYREC ADDREC) ); len = 12; /* Fixed size of header. */ len += Put_Qrec( &msg[len], /* Query Rec Pointer */ name, /* NetBIOS name */ ' ', /* Padding char */ '\0', /* Suffix */ "", /* Scope ID */ QTYPE_NB ); /* Qtype: Name */ rrec = &msg[len]; rr_len = Put_RRec_LSP( rrec, RRTYPE_NB ); rr_len += Put_RRec_TTL( rrec, rr_len, 0 ); rr_len += Put_RDLength( rrec, rr_len, 6 ); rr_len += Put_RD_Addr( rrec, rr_len, ONT_B, addr ); return( len + rr_len ); } /* BuildRegMsg */ void ReadRegReply( int sock ) /* ---------------------------------------------------- ** * Read a reply packet, and verify that it contains the * expected RCODE value. * ---------------------------------------------------- ** */ { uchar bufr[512]; int msglen; ushort flags; msglen = recv( sock, bufr, 512, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } if( msglen < 12 ) { printf( "Truncated reply received.\n" ); exit( EXIT_FAILURE ); } flags = Get_NS_Hdr_Flags( (ushort *)bufr ); switch( RCODE_MASK & flags ) { case RCODE_ACT_ERR: /* This is the only valid Rcode in response to * a broadcast name registration request. */ printf( "RCODE_ACT_ERR: Name is in use.\n" ); break; default: printf( "Unexpected return code: 0x%.2x.\n", (RCODE_MASK & flags) ); break; } } /* ReadRegReply */ int OpenSocket() /* ---------------------------------------------------- ** * Open the UDP socket, enable broadcast, and bind the * socket to a high-numbered UDP port so that we can * listen for replies. * ---------------------------------------------------- ** */ { int s; int test = 1; struct sockaddr_in sox; s = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); if( s < 0 ) { perror( "socket()" ); exit( EXIT_FAILURE ); } if( setsockopt( s, SOL_SOCKET, SO_BROADCAST, &test, sizeof(int) ) < 0 ) { perror( "setsockopt()" ); exit( EXIT_FAILURE ); } sox.sin_addr.s_addr = INADDR_ANY; sox.sin_family = AF_INET; sox.sin_port = 0; /* 0 == any port */ test = bind( s, (struct sockaddr *)&sox, sizeof(struct sockaddr_in) ); if( test < 0 ) { perror( "bind()" ); exit( EXIT_FAILURE ); } return( s ); } /* OpenSocket */ void SendBcastMsg( int sock, uchar *msg, int msglen ) /* ---------------------------------------------------- ** * Nice front-end to the sendto(2) function. * ---------------------------------------------------- ** */ { int result; struct sockaddr_in to; if( 0 == inet_aton( NBT_BCAST_ADDR, &(to.sin_addr) ) ) { printf( "Invalid destination IP address.\n" ); exit( EXIT_FAILURE ); } to.sin_family = AF_INET; to.sin_port = htons( 137 ); result = sendto( sock, (void *)msg, msglen, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in) ); if( result < 0 ) { perror( " sendto ()" ); exit( EXIT_FAILURE ); } } /* SendBcastMsg */ int AwaitResponse( int sock, int milliseconds ) /* ---------------------------------------------------- ** * Wait for an incoming message. * One ms == 1/1000 second. * ---------------------------------------------------- ** */ { int result; struct pollfd pfd[1]; pfd->fd = sock; pfd->events = POLLIN; result = poll( pfd, 1, milliseconds ); if( result < 0 ) { perror( "poll()" ); exit( EXIT_FAILURE ); } return( result ); } /* AwaitResponse */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * This program demonstrates a Broadcast NBT Name * Registration. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; uchar *name; struct in_addr address; if( argc != 3 ) { printf( "Usage: %s <name> <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } name = (uchar *)argv[1]; if( 0 == inet_aton( argv[2], &address ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <name> <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } ns_sock = OpenSocket(); msg_len = BuildRegMsg( bufr, name, address ); for( i = 0; i < 3; i++ ) { printf( "Trying...\n" ); SendBcastMsg( ns_sock, bufr, msg_len ); result = AwaitResponse( ns_sock, 750 ); if( result ) { ReadRegReply( ns_sock ); exit( EXIT_FAILURE ); } } printf( "Success: No negative replies received.\n" ); /* Turn off RD bit for NAME OVERWRITE DEMAND. */ Put_NS_Hdr_Flags( (ushort *)bufr, OPCODE_REGISTER NM_B_BIT ); SendBcastMsg( ns_sock, bufr, msg_len ); close( ns_sock ); return( EXIT_SUCCESS ); } /* main */', /* Suffix */ "", /* Scope ID */ QTYPE_NB ); /* Qtype: Name */ rrec = &msg[len]; rr_len = Put_RRec_LSP( rrec, RRTYPE_NB ); rr_len += Put_RRec_TTL( rrec, rr_len, 0 ); rr_len += Put_RDLength( rrec, rr_len, 6 ); rr_len += Put_RD_Addr( rrec, rr_len, ONT_B, addr ); return( len + rr_len ); } /* BuildRegMsg */ void ReadRegReply( int sock ) /* ---------------------------------------------------- ** * Read a reply packet, and verify that it contains the * expected RCODE value. * ---------------------------------------------------- ** */ { uchar bufr[512]; int msglen; ushort flags; msglen = recv( sock, bufr, 512, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } if( msglen < 12 ) { printf( "Truncated reply received.\n" ); exit( EXIT_FAILURE ); } flags = Get_NS_Hdr_Flags( (ushort *)bufr ); switch( RCODE_MASK & flags ) { case RCODE_ACT_ERR: /* This is the only valid Rcode in response to * a broadcast name registration request. */ printf( "RCODE_ACT_ERR: Name is in use.\n" ); break; default: printf( "Unexpected return code: 0x%.2x.\n", (RCODE_MASK & flags) ); break; } } /* ReadRegReply */ int OpenSocket() /* ---------------------------------------------------- ** * Open the UDP socket, enable broadcast, and bind the * socket to a high-numbered UDP port so that we can * listen for replies. * ---------------------------------------------------- ** */ { int s; int test = 1; struct sockaddr_in sox; s = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ); if( s < 0 ) { perror( "socket()" ); exit( EXIT_FAILURE ); } if( setsockopt( s, SOL_SOCKET, SO_BROADCAST, &test, sizeof(int) ) < 0 ) { perror( "setsockopt()" ); exit( EXIT_FAILURE ); } sox.sin_addr.s_addr = INADDR_ANY; sox.sin_family = AF_INET; sox.sin_port = 0; /* 0 == any port */ test = bind( s, (struct sockaddr *)&sox, sizeof(struct sockaddr_in) ); if( test < 0 ) { perror( "bind()" ); exit( EXIT_FAILURE ); } return( s ); } /* OpenSocket */ void SendBcastMsg( int sock, uchar *msg, int msglen ) /* ---------------------------------------------------- ** * Nice front-end to the sendto(2) function. * ---------------------------------------------------- ** */ { int result; struct sockaddr_in to; if( 0 == inet_aton( NBT_BCAST_ADDR, &(to.sin_addr) ) ) { printf( "Invalid destination IP address.\n" ); exit( EXIT_FAILURE ); } to.sin_family = AF_INET; to.sin_port = htons( 137 ); result = sendto( sock, (void *)msg, msglen, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in) ); if( result < 0 ) { perror( "sendto()" ); exit( EXIT_FAILURE ); } } /* SendBcastMsg */ int AwaitResponse( int sock, int milliseconds ) /* ---------------------------------------------------- ** * Wait for an incoming message. * One ms == 1/1000 second. * ---------------------------------------------------- ** */ { int result; struct pollfd pfd[1]; pfd->fd = sock; pfd->events = POLLIN; result = poll( pfd, 1, milliseconds ); if( result < 0 ) { perror( "poll()" ); exit( EXIT_FAILURE ); } return( result ); } /* AwaitResponse */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * This program demonstrates a Broadcast NBT Name * Registration. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; uchar *name; struct in_addr address; if( argc != 3 ) { printf( "Usage: %s <name> <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } name = (uchar *)argv[1]; if( 0 == inet_aton( argv[2], &address ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <name> <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } ns_sock = OpenSocket(); msg_len = BuildRegMsg( bufr, name, address ); for( i = 0; i < 3; i++ ) { printf( "Trying...\n" ); SendBcastMsg( ns_sock, bufr, msg_len ); result = AwaitResponse( ns_sock, 750 ); if( result ) { ReadRegReply( ns_sock ); exit( EXIT_FAILURE ); } } printf( "Success: No negative replies received.\n" ); /* Turn off RD bit for NAME OVERWRITE DEMAND. */ Put_NS_Hdr_Flags( (ushort *)bufr, OPCODE_REGISTER NM_B_BIT ); SendBcastMsg( ns_sock, bufr, msg_len ); close( ns_sock ); return( EXIT_SUCCESS ); } /* main */ 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. Blue Screen of Death Alert
4.3.1.2 Unicast (NBNS) Name RegistrationUnicast name registrations are subtly different from the broadcast variety.
The NBNS should respond with a NAME REGISTRATION RESPONSE , which will include one of the following RCODE values: 0x0: Success
FMT_ERR (0x1): Format Error
SRV_ERR (0x2): Server failure
IMP_ERR (0x4): Unsupported request error
RFS_ERR (0x5): Refused error
ACT_ERR (0x6) : Active error
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. 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
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. 4.3.1.3 M and H Node Name RegistrationMixed 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, 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 mode 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. 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. 4.3.1.4 Registering Multi- Homed HostsA multi-homed host is a machine that has multiple network interfaces (physical or virtual), each with its own IP address assigned. RFCs 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
B nodes with interfaces on the same subnet
Multi-homed hosts and the NBNS
Figure 4.5. Locating a multi-homed P nodeNode LANE gets two IPs when it asks for PATTY 's address.
As you might expect, the handling of M and H mode multi-homed hosts is a fairly straightforward combination of B and P mode behavior. M and H mode name registration for single-homed hosts has already been covered. 4.3.2 Name QueryEach NBT node has 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.
Figure 4.6a. Verification query ( RD == FALSE )RUBY sends a unicast query to node TERU asking about ANDOR . The RD bit is clear , so TERU does not check the NBNS database. It checks only the local name table and, finding no reference to the name ANDOR<20> , sends a NEGATIVE NAME QUERY RESPONSE .
Figure 4.6b. Verification query ( RD == TRUE )RUBY sends a unicast query to node TERU asking about ANDOR . The RD bit is set , so TERU checks the NBNS database, where it finds an entry for ANDOR<20> . TERU sends a POSITIVE NAME QUERY RESPONSE .
Note that:
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 its 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 practice. [7] 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.
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
Unicast Resolution queries
Verification queries
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. 4.3.2.1 Negative Query ResponseA 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 } } 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 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
SRV_ERR (0x2): Server failure
NAM_ERR (0x3): Name Error
4.3.2.2 Positive Query ResponseThe POSITIVE NAME QUERY RESPONSE is similar to the negative response, with the following differences:
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. 4.3.2.3 The Redirect Name Query ResponseThe 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. 4.3.2.4 A Simple Name Query RevisitedRemember Listing 3.3? In that example we provided code for generating a simple broadcast name query. Listing 4.8 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 4.8 Broadcast name query revisited#include <stdio.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "NS_Header.h" #include "NS_Qrec.h" #define uchar unsigned char #define ushort unsigned short int BuildQuery( uchar *msg, const int bcast, const int rdbit, const uchar *name, const uchar pad, const uchar suffix, const uchar *scope, const ushort qtype ) /* ---------------------------------------------------- ** * Create a name query. * * This is much more flexible than the registration * example in Listing 4.7. There are also a lot more * parameters. :-) * ---------------------------------------------------- ** */ { ushort *hdr = (ushort *)msg; ushort flags; int len; /* RD always set if B is set. */ if( bcast ) flags = NM_RD_BIT NM_B_BIT; else flags = rdbit ? NM_RD_BIT : 0; Put_NS_TID( hdr, 1964 ); Put_NS_Hdr_Flags( hdr, flags ); Put_NS_Hdr_Rec_Counts( hdr, QUERYREC ); len = 12; /* Fixed size of header. */ len += Put_Qrec( &msg[len], /* Query Rec Pointer */ name, /* NetBIOS name */ pad, /* Padding char */ suffix, /* Suffix */ scope, /* Scope ID */ qtype ); /* Query type */ return( len ); } /* BuildQuery */ void ReadQueryReply( int sock ) /* ---------------------------------------------------- ** * Read the query reply message(s). * ---------------------------------------------------- ** */ { uchar bufr[512]; int msglen; ushort flags; msglen = recv( sock, bufr, 512, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } if( msglen < 12 ) { printf( "Truncated reply received.\n" ); exit( EXIT_FAILURE ); } flags = Get_NS_Hdr_Flags( (ushort *)bufr ); switch( RCODE_MASK & flags ) { case RCODE_POS_RSP: printf( "Positive Name Query Response.\n" ); break; case RCODE_FMT_ERR: printf( "RCODE_FMT_ERR: Format Error.\n" ); break; case RCODE_SRV_ERR: printf( "RCODE_SRV_ERR: Server Error.\n" ); break; case RCODE_NAM_ERR: printf( "RCODE_NAM_ERR: Name Not Found.\n" ); break; default: printf( "Unexpected return code: 0x%.2x.\n", (RCODE_MASK & flags) ); break; } } /* ReadQueryReply */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * This program demonstrates a Broadcast NBT Name Query. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; uchar *name; if( argc != 2 ) { printf( "Usage: %s <name>\n", argv[0] ); exit( EXIT_FAILURE ); } name = (uchar *)argv[1]; ns_sock = OpenSocket(); msg_len = BuildQuery( bufr, /* Target buffer. */ 1, /* Broadcast true. */ 1, /* RD bit true. */ name, /* NetBIOS name. */ ' ', /* Padding (space). */ '#include <stdio.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "NS_Header.h" #include "NS_Qrec.h" #define uchar unsigned char #define ushort unsigned short int BuildQuery( uchar *msg, const int bcast, const int rdbit, const uchar *name, const uchar pad, const uchar suffix, const uchar *scope, const ushort qtype ) /* ---------------------------------------------------- ** * Create a name query. * * This is much more flexible than the registration * example in Listing 4.7. There are also a lot more * parameters. :-) * ---------------------------------------------------- ** */ { ushort *hdr = (ushort *)msg; ushort flags; int len; /* RD always set if B is set. */ if( bcast ) flags = NM_RD_BIT NM_B_BIT; else flags = rdbit ? NM_RD_BIT : 0; Put_NS_TID( hdr, 1964 ); Put_NS_Hdr_Flags( hdr, flags ); Put_NS_Hdr_Rec_Counts( hdr, QUERYREC ); len = 12; /* Fixed size of header. */ len += Put_Qrec( &msg[len], /* Query Rec Pointer */ name, /* NetBIOS name */ pad, /* Padding char */ suffix, /* Suffix */ scope, /* Scope ID */ qtype ); /* Query type */ return( len ); } /* BuildQuery */ void ReadQueryReply( int sock ) /* ---------------------------------------------------- ** * Read the query reply message(s). * ---------------------------------------------------- ** */ { uchar bufr[512]; int msglen; ushort flags; msglen = recv( sock, bufr, 512, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } if( msglen < 12 ) { printf( "Truncated reply received.\n" ); exit( EXIT_FAILURE ); } flags = Get_NS_Hdr_Flags( (ushort *)bufr ); switch( RCODE_MASK & flags ) { case RCODE_POS_RSP: printf( "Positive Name Query Response.\n" ); break; case RCODE_FMT_ERR: printf( "RCODE_FMT_ERR: Format Error.\n" ); break; case RCODE_SRV_ERR: printf( "RCODE_SRV_ERR: Server Error.\n" ); break; case RCODE_NAM_ERR: printf( "RCODE_NAM_ERR: Name Not Found.\n" ); break; default: printf( "Unexpected return code: 0x%.2x.\n", (RCODE_MASK & flags) ); break; } } /* ReadQueryReply */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * This program demonstrates a Broadcast NBT Name Query. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; uchar *name; if( argc != 2 ) { printf( "Usage: %s <name>\n", argv[0] ); exit( EXIT_FAILURE ); } name = (uchar *)argv[1]; ns_sock = OpenSocket(); msg_len = BuildQuery( bufr, /* Target buffer. */ 1, /* Broadcast true. */ 1, /* RD bit true. */ name, /* NetBIOS name. */ ' ', /* Padding (space). */ '\0', /* Suffix (0x00). */ "", /* Scope (""). */ QTYPE_NB ); /* Query type. */ for( i = 0; i < 3; i++ ) { printf( "Trying...\n" ); SendBcastMsg( ns_sock, bufr, msg_len ); result = AwaitResponse( ns_sock, 750 ); if( result ) { do { /* We may get multiple replies. */ ReadQueryReply( ns_sock ); } while( AwaitResponse( ns_sock, 750 ) ); exit( EXIT_SUCCESS ); } } printf( "No replies received.\n" ); close( ns_sock ); return( EXIT_FAILURE ); } /* main */', /* Suffix (0x00). */ "", /* Scope (""). */ QTYPE_NB ); /* Query type. */ for( i = 0; i < 3; i++ ) { printf( "Trying...\n" ); SendBcastMsg( ns_sock, bufr, msg_len ); result = AwaitResponse( ns_sock, 750 ); if( result ) { do { /* We may get multiple replies. */ ReadQueryReply( ns_sock ); } while( AwaitResponse( ns_sock, 750 ) ); exit( EXIT_SUCCESS ); } } printf( "No replies received.\n" ); close( ns_sock ); return( EXIT_FAILURE ); } /* main */ 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 4.9 Listing ADDR_ENTRY records#include "NS_RDaddr.h" int Find_RDLength( uchar *msg ) /* ---------------------------------------------------- ** * Calculate the offset of the RDLENGTH field within a * POSITIVE NAME QUERY RESPONSE. * ---------------------------------------------------- ** */ { int len; len = 12 /* Length of the header */ + strlen( &msg[12] ) + 1 /* NBT Name length */ + 2 + 2 + 4; /* Type, Class, & TTL */ return( len ); } /* Find_RDLength */ void List_Addr_Entry( uchar *msg ) /* ---------------------------------------------------- ** * This function nicely prints the contents of an * RDATA.ADDR_ENTRY[] array. * ---------------------------------------------------- ** */ { ushort numIPs; ushort flags; int offset; int i; offset = Find_RDLength( msg ); numIPs = Get_RDLength( msg, offset ) / 6; offset += 2; /* Move past the RDLENGTH field. */ for( i = 0; i < numIPs ; i++, offset += 6 ) { /* Read the NB_FLAGS field. */ flags = Get_RD_NB_Flags( msg, offset ); /* If there are more than one, number the entries. */ if( numIPs > 1 ) printf( "ADDR_ENTRY[%d]: ", i ); /* Print the IP address. */ printf( "%d.%d.%d.%d\t", msg[offset+2], msg[offset+3], msg[offset+4], msg[offset+5] ); /* Group or Unique. */ if( GROUP_BIT & flags ) printf( "<Group>\t" ); else printf( "<Unique>\t" ); /* Finally, the owner node type. */ switch( ONT_MASK & flags ) { case ONT_B: printf( "<B-node>\n" ); break; case ONT_P: printf( "<P-node>\n" ); break; case ONT_M: printf( "<M-node>\n" ); break; case ONT_H: printf( "<H-node>\n" ); break; } } } /* List_Addr_Entry */ 4.3.3 Name RefreshName 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
RD
TTL
RDATA
From watching packets on the wire, [8] 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 NBNS. [9]
The NBNS handles a NAME REFRESH REQUEST in exactly the same manner as 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. 4.3.4 Name ReleaseBoth 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 one [10] ). 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> } } } 4.3.4.1 Name Release ResponseThe 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
FMT_ERR (0x1): Format error
SRV_ERR (0x2): Server failure
NAM_ERR (0x3): Name error
RFS_ERR (0x5): Refused error
ACT_ERR (0x6): Active error
4.3.5 Node StatusThe 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. 4.3.5.1 Node Status ResponseThe 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 listed 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:
The above is the same as an NB_FLAGS field with four extra bits defined. DRG: Deregister
CNF: Conflict
ACT: Active
PRM: Permanent
These flag values are displayed by Samba's nmblookup program. For example:
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
Time for another chunk of code. Listing 4.10 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 4.10 Node Status Requestvoid Hex_Print( uchar *src, int len ) /* ---------------------------------------------------- ** * Print len bytes of src. Escape any non-printing * characters. * ---------------------------------------------------- ** */ { int i; for( i = 0; i < len; i++ ) { if( isprint( src[i] ) ) putchar( src[i] ); else printf( "\x%.2x", src[i] ); } } /* Hex_Print */ void SendMsg( int sock, uchar *msg, int msglen, struct in_addr address ) /* ---------------------------------------------------- ** * Send a message to port UDP/137 at the * specified IP address. * ---------------------------------------------------- ** */ { int result; struct sockaddr_in to; to.sin_addr = address; to.sin_family = AF_INET; to.sin_port = htons( 137 ); result = sendto( sock, (void *)msg, msglen, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in) ); if( result < 0 ) { perror( "sendto()" ); exit( EXIT_FAILURE ); } } /* SendMsg */ void ReadStatusReply( int sock ) /* ---------------------------------------------------- ** * Read the Node Status Response message, parse the * NODE_NAME[] entries, and print everything in a * readable format. * ---------------------------------------------------- ** */ { uchar bufr[1024]; ushort flags; int msglen; int offset; int num_names; int i; /* Read the message. */ msglen = recv( sock, bufr, 1024, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } /* Find start of RDATA (two bytes beyond RDLENGTH). */ offset = 2 + Find_RDLength( bufr ); /* The NUM_NAMES field is one byte long. */ num_names = bufr[offset++]; /* Now go through and print each name entry. */ for( i = 0; i < num_names; i++, offset += 18 ) { flags = (bufr[offset+16] << 8) bufr[offset+17]; printf( "NODE_NAME[%d]: ", i ); Hex_Print( &bufr[offset], 15 ); printf( "<%.2x>\t", bufr[offset+15] ); /* Group or Unique. */ printf( "[%c", ( GROUP_BIT & flags ) ? 'G' : 'U' ); /* The owner node type. */ switch( ONT_MASK & flags ) { case ONT_B: printf( ",B" ); break; case ONT_P: printf( ",P" ); break; case ONT_M: printf( ",M" ); break; case ONT_H: printf( ",H" ); break; } /* Additional flags */ if( DRG & flags ) printf( ",DRG" ); if( CNF & flags ) printf( ",CNF" ); if( ACT & flags ) printf( ",ACT" ); if( PRM & flags ) printf( ",PRM" ); printf( "]\n" ); } /* Windows systems will also send the MAC address. */ printf( "MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", bufr[offset], bufr[offset+1], bufr[offset+2], bufr[offset+3], bufr[offset+4], bufr[offset+5] ); } /* ReadStatusReply */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * NBT Node Status Request. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; struct in_addr address; if( argc != 2 ) { printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } if( 0 == inet_aton( argv[1], &address ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } ns_sock = OpenSocket(); msg_len = BuildQuery( bufr, /* Target buffer. */ 0, /* Broadcast false. */ 0, /* RD bit false. */ "*", /* NetBIOS name. */ 'void Hex_Print( uchar *src, int len ) /* ---------------------------------------------------- ** * Print len bytes of src. Escape any non-printing * characters . * ---------------------------------------------------- ** */ { int i; for( i = 0; i < len; i++ ) { if( isprint ( src[i] ) ) putchar ( src[i] ); else printf( "\\x%.2x", src[i] ); } } /* Hex_Print */ void SendMsg( int sock, uchar *msg, int msglen, struct in_addr address ) /* ---------------------------------------------------- ** * Send a message to port UDP/137 at the * specified IP address. * ---------------------------------------------------- ** */ { int result; struct sockaddr_in to; to.sin_addr = address; to.sin_family = AF_INET; to.sin_port = htons( 137 ); result = sendto( sock, (void *)msg, msglen, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in) ); if( result < 0 ) { perror( "sendto()" ); exit( EXIT_FAILURE ); } } /* SendMsg */ void ReadStatusReply( int sock ) /* ---------------------------------------------------- ** * Read the Node Status Response message, parse the * NODE_NAME[] entries, and print everything in a * readable format. * ---------------------------------------------------- ** */ { uchar bufr[1024]; ushort flags; int msglen; int offset; int num_names; int i; /* Read the message. */ msglen = recv( sock, bufr, 1024, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } /* Find start of RDATA (two bytes beyond RDLENGTH). */ offset = 2 + Find_RDLength( bufr ); /* The NUM_NAMES field is one byte long. */ num_names = bufr[offset++]; /* Now go through and print each name entry. */ for( i = 0; i < num_names; i++, offset += 18 ) { flags = (bufr[offset+16] << 8) bufr[offset+17]; printf( "NODE_NAME[%d]: ", i ); Hex_Print( &bufr[offset], 15 ); printf( "<%.2x>\t", bufr[offset+15] ); /* Group or Unique. */ printf( "[%c", ( GROUP_BIT & flags ) ? 'G' : 'U' ); /* The owner node type. */ switch( ONT_MASK & flags ) { case ONT_B: printf( ",B" ); break; case ONT_P: printf( ",P" ); break; case ONT_M: printf( ",M" ); break; case ONT_H: printf( ",H" ); break; } /* Additional flags */ if( DRG & flags ) printf( ",DRG" ); if( CNF & flags ) printf( ",CNF" ); if( ACT & flags ) printf( ",ACT" ); if( PRM & flags ) printf( ",PRM" ); printf( "]\n" ); } /* Windows systems will also send the MAC address. */ printf( "MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", bufr[offset], bufr[offset+1], bufr[offset+2], bufr[offset+3], bufr[offset+4], bufr[offset+5] ); } /* ReadStatusReply */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * NBT Node Status Request. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; struct in_addr address; if( argc != 2 ) { printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } if( 0 == inet_aton( argv[1], &address ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } ns_sock = OpenSocket(); msg_len = BuildQuery( bufr, /* Target buffer. */ 0, /* Broadcast false. */ 0, /* RD bit false. */ "*", /* NetBIOS name. */ '\0', /* Padding (space). */ '\0', /* Suffix (0x00). */ "", /* Scope (""). */ QTYPE_NBSTAT ); for( i = 0; i < 3; i++ ) { printf( "Sending NODE STATUS query to %s...\n", argv[1] ); SendMsg( ns_sock, bufr, msg_len, address ); result = AwaitResponse( ns_sock, 750 ); if( result ) { ReadStatusReply( ns_sock ); exit( EXIT_SUCCESS ); } } printf( "No replies received.\n" ); close( ns_sock ); return( EXIT_FAILURE ); } /* main */', /* Padding (space). */ 'void Hex_Print( uchar *src, int len ) /* ---------------------------------------------------- ** * Print len bytes of src. Escape any non-printing * characters . * ---------------------------------------------------- ** */ { int i; for( i = 0; i < len; i++ ) { if( isprint ( src[i] ) ) putchar ( src[i] ); else printf( "\\x%.2x", src[i] ); } } /* Hex_Print */ void SendMsg( int sock, uchar *msg, int msglen, struct in_addr address ) /* ---------------------------------------------------- ** * Send a message to port UDP/137 at the * specified IP address. * ---------------------------------------------------- ** */ { int result; struct sockaddr_in to; to.sin_addr = address; to.sin_family = AF_INET; to.sin_port = htons( 137 ); result = sendto( sock, (void *)msg, msglen, 0, (struct sockaddr *)&to, sizeof(struct sockaddr_in) ); if( result < 0 ) { perror( "sendto()" ); exit( EXIT_FAILURE ); } } /* SendMsg */ void ReadStatusReply( int sock ) /* ---------------------------------------------------- ** * Read the Node Status Response message, parse the * NODE_NAME[] entries, and print everything in a * readable format. * ---------------------------------------------------- ** */ { uchar bufr[1024]; ushort flags; int msglen; int offset; int num_names; int i; /* Read the message. */ msglen = recv( sock, bufr, 1024, 0 ); if( msglen < 0 ) { perror( "recv()" ); exit( EXIT_FAILURE ); } /* Find start of RDATA (two bytes beyond RDLENGTH). */ offset = 2 + Find_RDLength( bufr ); /* The NUM_NAMES field is one byte long. */ num_names = bufr[offset++]; /* Now go through and print each name entry. */ for( i = 0; i < num_names; i++, offset += 18 ) { flags = (bufr[offset+16] << 8) bufr[offset+17]; printf( "NODE_NAME[%d]: ", i ); Hex_Print( &bufr[offset], 15 ); printf( "<%.2x>\t", bufr[offset+15] ); /* Group or Unique. */ printf( "[%c", ( GROUP_BIT & flags ) ? 'G' : 'U' ); /* The owner node type. */ switch( ONT_MASK & flags ) { case ONT_B: printf( ",B" ); break; case ONT_P: printf( ",P" ); break; case ONT_M: printf( ",M" ); break; case ONT_H: printf( ",H" ); break; } /* Additional flags */ if( DRG & flags ) printf( ",DRG" ); if( CNF & flags ) printf( ",CNF" ); if( ACT & flags ) printf( ",ACT" ); if( PRM & flags ) printf( ",PRM" ); printf( "]\n" ); } /* Windows systems will also send the MAC address. */ printf( "MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", bufr[offset], bufr[offset+1], bufr[offset+2], bufr[offset+3], bufr[offset+4], bufr[offset+5] ); } /* ReadStatusReply */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * NBT Node Status Request. * ---------------------------------------------------- ** */ { int i; int result; int ns_sock; int msg_len; uchar bufr[512]; struct in_addr address; if( argc != 2 ) { printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } if( 0 == inet_aton( argv[1], &address ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <IP>\n", argv[0] ); exit( EXIT_FAILURE ); } ns_sock = OpenSocket(); msg_len = BuildQuery( bufr, /* Target buffer. */ 0, /* Broadcast false. */ 0, /* RD bit false. */ "*", /* NetBIOS name. */ '\0', /* Padding (space). */ '\0', /* Suffix (0x00). */ "", /* Scope (""). */ QTYPE_NBSTAT ); for( i = 0; i < 3; i++ ) { printf( "Sending NODE STATUS query to %s...\n", argv[1] ); SendMsg( ns_sock, bufr, msg_len, address ); result = AwaitResponse( ns_sock, 750 ); if( result ) { ReadStatusReply( ns_sock ); exit( EXIT_SUCCESS ); } } printf( "No replies received.\n" ); close( ns_sock ); return( EXIT_FAILURE ); } /* main */', /* Suffix (0x00). */ "", /* Scope (""). */ QTYPE_NBSTAT ); for( i = 0; i < 3; i++ ) { printf( "Sending NODE STATUS query to %s...\n", argv[1] ); SendMsg( ns_sock, bufr, msg_len, address ); result = AwaitResponse( ns_sock, 750 ); if( result ) { ReadStatusReply( ns_sock ); exit( EXIT_SUCCESS ); } } printf( "No replies received.\n" ); close( ns_sock ); return( EXIT_FAILURE ); } /* main */ 4.3.6 Name Conflict DemandThe 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 NBNS. [11] 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. 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. 4.3.6.1 Name Release Demand RevisitedThere 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. |