AppleTalk
AppleTalk support in Winsock has been around for a while, although few people are aware of it. You probably will not choose AppleTalk unless you are communicating with Macintosh computers. AppleTalk is somewhat similar to NetBIOS in that it is name-based on a per-process basis. That is, a server dynamically registers a particular name that it will be known as. Clients use this name to establish a connection. However, AppleTalk names are substantially more complicated than NetBIOS names. The next section will discuss how computers using the AppleTalk protocol are addressed on the network.
Addressing
An AppleTalk name is actually based on three separate names: name, type, and zone. Each name can be up to 32 characters long. The name identifies the process and its associated socket on a machine. The type is a subgrouping mechanism for zones. Traditionally, a zone is a network of AppleTalk-enabled computers physically located on the same loop. Microsoft's implementation of AppleTalk allows a Windows machine to specify the default zone it is located within. Multiple networks can be bridged together. These human-friendly names map to a socket number, a node number, and a network number. An AppleTalk name must be unique within the given type and zone. This requirement is enforced by the Name Binding Protocol (NBP), which broadcasts a query to see if the name is already in use. Under the hood, AppleTalk uses the Routing Table Maintenance Protocol (RTMP) to dynamically discover routes to the different AppleTalk networks linked together.
The following structure provides the basis for addressing AppleTalk hosts from Winsock:
typedef struct sockaddr_at { USHORT sat_family; USHORT sat_net; UCHAR sat_node; UCHAR sat_socket; } SOCKADDR_AT, *PSOCKADDR_AT;
Notice that the address structure contains only characters or short integers and not friendly names. The SOCKADDR_AT structure is passed into Winsock calls such as bind, connect, and WSAConnect, but to translate the human-readable names you must query the network to either resolve or register that name first. This is done by using a call to getsockopt or setsockopt, respectively.
Registering an AppleTalk Name
A server that wants to register a particular name so that clients can easily connect to it calls setsockopt with the SO_REGISTER_NAME option. For all socket options involving AppleTalk names, use the WSH_NBP_NAME structure, which is defined as
typedef struct { CHAR ObjectNameLen; CHAR ObjectName[MAX_ENTITY]; CHAR TypeNameLen; CHAR TypeName[MAX_ENTITY]; CHAR ZoneNameLen; CHAR ZoneName[MAX_ENTITY]; } WSH_NBP_NAME, *PWSH_NBP_NAME;
A number of types—which include WSH_REGISTER_NAME, WSH_DEREGISTER_NAME, and WSH_REMOVE_NAME—are defined based on the WSH_NBP_NAME structure. Using the appropriate type depends on whether you look up a name, register a name, or remove a name.
The following code sample illustrates how to register an AppleTalk name:
#define MY_ZONE "*" #define MY_TYPE "Winsock-Test-App" #define MY_OBJECT "AppleTalk-Server" WSH_REGISTER_NAME atname; SOCKADDR_AT ataddr; SOCKET s; // // Fill in the name to register // strcpy(atname.ObjectName, MY_OBJECT); atname.ObjectNameLen = strlen(MY_OBJECT); strcpy(atname.TypeName, MY_TYPE); atname.TypeNameLen = strlen(MY_TYPE); strcpy(atname.ZoneName, MY_ZONE); atname.ZoneNameLen = strlen(MY_ZONE); s = socket(AF_APPLETALK, SOCK_STREAM, ATPROTO_ADSP); if (s == INVALID_SOCKET) { // Error } ataddr.sat_socket = 0; ataddr.sat_family = AF_APPLETALK; if (bind(s, (SOCKADDR *)&ataddr, sizeof(ataddr)) == SOCKET_ERROR) { // Unable to open an endpoint on the AppleTalk network } if (setsockopt(s, SOL_APPLETALK, SO_REGISTER_NAME, (char *)&atname, sizeof(WSH_NBP_NAME)) == SOCKET_ERROR) { // Name registration failed! }
The first thing you'll notice is the MY_ZONE, MY_TYPE, and MY_OBJECT strings. Remember that an AppleTalk name is three-tiered. Notice that the zone is an asterisk (*). This is a special character used in the zone field to specify the “current” zone the computer is located in. Next, we create a socket of type SOCK_STREAM of the AppleTalk Data Stream Protocol (ADSP). Following socket creation, you'll notice a call to the bind function with an address structure that has a zeroed-out sat_socket field and only the protocol family field set. This is important because it creates an endpoint on the AppleTalk network for your application to make requests from. Note that although this call to bind allows you to perform simple actions on the network, by itself it doesn't allow your application to accept incoming connection requests from clients. To accept client connections, you must register your name on the network, which is the next step.
Registering an AppleTalk name is simple. Make the call to setsockopt by passing SOL_APPLETALK as the level parameter and SO_REGISTER_NAME as the optname parameter. The last two parameters are a pointer to our WSH_REGISTER_NAME structure and its size. If the call succeeds, our server name was successfully registered. If the call fails, the name is probably already in use. The Winsock error returned is WSAEADDRINUSE (10048). Note that for both datagram-oriented and stream-oriented AppleTalk protocols, a process that wants to receive data must register a name that clients can either send datagrams to or connect to.
Resolving an AppleTalk Name
On the client side of the equation, an application usually knows a server by its friendly name and must resolve that into the network, node, and socket numbers Winsock calls use. This is accomplished by calling getsockopt with the SO_LOOKUP_NAME option. Performing a name lookup relies on the WSH_LOOKUP_NAME structure. This structure and its dependent structure are defined as
typedef struct { WSH_ATALK_ADDRESS Address; USHORT Enumerator; WSH_NBP_NAME NbpName; } WSH_NBP_TUPLE, *PWSH_NBP_TUPLE; typedef struct _WSH_LOOKUP_NAME { // Array of NoTuple WSH_NBP_TUPLEs WSH_NBP_TUPLE LookupTuple; ULONG NoTuples; } WSH_LOOKUP_NAME, *PWSH_LOOKUP_NAME;
When we call getsockopt with the SO_LOOKUP_NAME option, we pass a buffer cast as a WSH_LOOKUP_NAME structure and fill in the WSH_NBP_NAME field within the first LookupTuple member. Upon a successful call, getsockopt returns an array of WSH_NBP_TUPLE elements containing physical address information for that name. The following sample contains the file ATALKNM.C, which illustrates how to look up a name. In addition, it shows how to list all “discovered” AppleTalk zones and how to find your default zone. Zone information can be obtained by using the getsockopt options SO_LOOKUP_ZONES and SO_LOOKUP_MYZONE.
#include <winsock.h> #include <atalkwsh.h> #include <stdio.h> #include <stdlib.h> #define DEFAULT_ZONE "*" #define DEFAULT_TYPE "Windows Sockets" #define DEFAULT_OBJECT "AppleTalk-Server" char szZone[MAX_ENTITY], szType[MAX_ENTITY], szObject[MAX_ENTITY]; BOOL bFindName = FALSE, bListZones = FALSE, bListMyZone = FALSE; void usage() { printf("usage: atlookup [options]\n"); printf(" Name Lookup:\n"); printf(" -z:ZONE-NAME\n"); printf(" -t:TYPE-NAME\n"); printf(" -o:OBJECT-NAME\n"); printf(" List All Zones:\n"); printf(" -lz\n"); printf(" List My Zone:\n"); printf(" -lm\n"); ExitProcess(1); } void ValidateArgs(int argc, char **argv) { int i; strcpy(szZone, DEFAULT_ZONE); strcpy(szType, DEFAULT_TYPE); strcpy(szObject, DEFAULT_OBJECT); for(i = 1; i < argc; i++) { if (strlen(argv[i]) < 2) continue; if ((argv[i][0] == '-') (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'z': // Specify a zone name if (strlen(argv[i]) > 3) strncpy(szZone, &argv[i][3], MAX_ENTITY); bFindName = TRUE; break; case 't': // Specify a type name if (strlen(argv[i]) > 3) strncpy(szType, &argv[i][3], MAX_ENTITY); bFindName = TRUE; break; case 'o': // Specify an object name if (strlen(argv[i]) > 3) strncpy(szObject, &argv[i][3], MAX_ENTITY); bFindName = TRUE; break; case 'l': // List zones information if (strlen(argv[i]) == 3) // List all zones if (tolower(argv[i][2]) == 'z') bListZones = TRUE; // List my zone else if (tolower(argv[i][2]) == 'm') bListMyZone = TRUE; break; default: usage(); } } } } int main(int argc, char **argv) { WSADATA wsd; char cLookupBuffer[16000], *pTupleBuffer = NULL; PWSH_NBP_TUPLE pTuples = NULL; PWSH_LOOKUP_NAME atlookup; PWSH_LOOKUP_ZONES zonelookup; SOCKET s; DWORD dwSize = sizeof(cLookupBuffer); SOCKADDR_AT ataddr; int i; // Load the Winsock library // if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0) { printf("Unable to load Winsock library!\n"); return 1; } ValidateArgs(argc, argv); atlookup = (PWSH_LOOKUP_NAME)cLookupBuffer; zonelookup = (PWSH_LOOKUP_ZONES)cLookupBuffer; if (bFindName) { // Fill in the name to look up // strcpy(atlookup->LookupTuple.NbpName.ObjectName, szObject); atlookup->LookupTuple.NbpName.ObjectNameLen = strlen(szObject); strcpy(atlookup->LookupTuple.NbpName.TypeName, szType); atlookup->LookupTuple.NbpName.TypeNameLen = strlen(szType); strcpy(atlookup->LookupTuple.NbpName.ZoneName, szZone); atlookup->LookupTuple.NbpName.ZoneNameLen = strlen(szZone); } // Create the AppleTalk socket // s = socket(AF_APPLETALK, SOCK_STREAM, ATPROTO_ADSP); if (s == INVALID_SOCKET) { printf("socket() failed: %d\n", WSAGetLastError()); return 1; } // We need to bind in order to create an endpoint on the // AppleTalk network to make our query from // ZeroMemory(&ataddr, sizeof(ataddr)); ataddr.sat_family = AF_APPLETALK; ataddr.sat_socket = 0; if (bind(s, (SOCKADDR *)&ataddr, sizeof(ataddr)) == INVALID_SOCKET) { printf("bind() failed: %d\n", WSAGetLastError()); return 1; } if (bFindName) { printf("Looking up: %s:%s@%s\n", szObject, szType, szZone); if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_NAME, (char *)atlookup, &dwSize) == INVALID_SOCKET) { printf("getsockopt(SO_LOOKUP_NAME) failed: %d\n", WSAGetLastError()); return 1; } printf("Lookup returned: %d entries\n", atlookup->NoTuples); // // Our character buffer now contains an array of // WSH_NBP_TUPLE structures after our WSH_LOOKUP_NAME // structure // pTupleBuffer = (char *)cLookupBuffer + sizeof(WSH_LOOKUP_NAME); pTuples = (PWSH_NBP_TUPLE) pTupleBuffer; for(i = 0; i < atlookup->NoTuples; i++) { ataddr.sat_family = AF_APPLETALK; ataddr.sat_net = pTuples[i].Address.Network; ataddr.sat_node = pTuples[i].Address.Node; ataddr.sat_socket = pTuples[i].Address.Socket; printf("server address = %lx.%lx.%lx.\n", ataddr.sat_net, ataddr.sat_node, ataddr.sat_socket); } } else if (bListZones) { // It is very important to pass a sufficiently big buffer // for this option. Windows NT 4 SP3 blue screens if it // is too small. // if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_ZONES, (char *)atlookup, &dwSize) == INVALID_SOCKET) { printf("getsockopt(SO_LOOKUP_NAME) failed: %d\n", WSAGetLastError()); return 1; } printf("Lookup returned: %d zones\n", zonelookup->NoZones); // // The character buffer contains a list of null-separated // strings after the WSH_LOOKUP_ZONES structure // pTupleBuffer = (char *)cLookupBuffer + sizeof(WSH_LOOKUP_ZONES); for(i = 0; i < zonelookup->NoZones; i++) { printf("%3d: '%s'\n", i+1, pTupleBuffer); while (*pTupleBuffer++); } } else if (bListMyZone) { // This option returns a simple string // if (getsockopt(s, SOL_APPLETALK, SO_LOOKUP_MYZONE, (char *)cLookupBuffer, &dwSize) == INVALID_SOCKET) { printf("getsockopt(SO_LOOKUP_NAME) failed: %d\n", WSAGetLastError()); return 1; } printf("My Zone: '%s'\n", cLookupBuffer); } else usage(); WSACleanup(); return 0; }
When you are using most of the AppleTalk socket options—such as SO_LOOKUP_MYZONE, SO_LOOKUP_ZONES, and SO_LOOKUP_NAME—you need to provide a large character buffer to the getsockopt call. If you call an option that requires you to provide a structure, that structure needs to be at the start of the supplied character buffer. If the call to getsockopt is successful, the function places the returned data in the character buffer after the end of the supplied structure. Take a look at the SO_LOOKUP_NAME section in the above code sample. The variable, cLookupBuffer, is a simple character array used in the call to getsockopt. First, cast it as a PWSH_LOOKUP_NAME and fill in the name information you want to find. Pass the buffer into getsockopt, and upon return, increment the character pointer pTupleBuffer so that it points to the character after the end of the WSH_LOOKUP_NAME structure. Next, cast that pointer to a variable of PWSH_NBP_TUPLE because the data returned from a lookup name call is an array of WSH_NBP_TUPLE structures. Once you have the proper starting location and type of the tuples, you can walk through the array. Chapter 7 contains more in-depth information about the various socket options specific to the AppleTalk address family.
Creating a Socket
AppleTalk is available in Winsock 1.1 and later, so you can use either socket-creation routine. Again, you have two options of specifying the underlying AppleTalk protocols. First, you can supply the corresponding define from atalkwsh.h for the protocol you want, or you can enumerate the protocols using WSAEnumProtocols and passing the WSAPROTOCOL_INFO structure. Table 4-1 lists the required parameters for each AppleTalk protocol type when you create a socket directly using socket or WSASocket.
Protocol | Address Family | Socket Type | Protocol Type |
MSAFD AppleTalk [ADSP] | SOCK_RDM | ATPROTO_ADSP | |
MSAFD AppleTalk [ADSP] [Pseudo-Stream] | SOCK_STREAM | ATPROTO_ADSP | |
MSAFD AppleTalk [PAP] | AF_APPLETALK | SOCK_RDM | ATPROTO_PAP |
MSAFD AppleTalk [RTMP] | SOCK_DGRAM | DDPPROTO_RTMP | |
MSAFD AppleTalk [ZIP] | SOCK_DGRAM | DDPPROTO_ZIP |
On the companion CD, you will find an AppleTalk application named ATALK.CPP which can operate as a sender or a receiver application over the PAP and ADSP protocols. | |