< Reliable Multicasting | Multicasting Main | Winsock & Quality of Service >
What do we have in this chapter 9 part 6?
|
ATM Multipoint
Native ATM through Winsock also supports multicasting, which offers significantly different capabilities than IP multicasting. Remember that ATM supports rooted control and data planes, so when a multicast server, or c_root, is established, it has control over who is allowed to join the group as well as how data is transmitted within the group. One important distinction is that on an ATM network, IP over ATM can be enabled. This configuration allows the ATM network to emulate an IP network by mapping IP addresses to ATM native addresses. With IP over ATM enabled, you have a choice of using IP multicasting, which will be translated appropriately to the ATM layer, or of using the native ATM multicasting capabilities, which we present in this section. The behavior of IP multicasting on ATM when configured for IP over ATM should be the same because it appears that you are on an IP network. The only exception is that IGMP is not present because all multicast calls are translated into ATM native commands. In addition, it is possible to configure an ATM network with one or more LAN emulation (LANE) networks. The purpose of a LANE is to make the ATM appear as a “regular” network capable of multiple protocols such as IPX/SPX, NetBEUI, TCP/IP, IGMP, and ICMP. In this situation, IP multicasting does look every bit like IP multicasting on an Ethernet network, which means IGMP is present as well. We mentioned that ATM supports rooted control planes and rooted data planes. Therefore, when you create a multicast group, you establish a root node that “invites” leaf nodes to join the multicast group. Currently, only root-initiated joins are supported, meaning that a leaf cannot request to be added to a group. In addition, the root node (as a rooted data plane) sends data in one direction only, from the root to the leaves. One startling contrast between ATM and IP multicasting is that ATM needs no special addresses. All that is required is that the root has knowledge of the addresses of each leaf that it will invite. Also, only one root node can be in a multicast group. If another ATM endpoint starts inviting the same leaves, this association becomes a separate group.
ATM Multipoint with WSAJoinLeaf()
The WSAJoinLeaf() API is the only method of using ATM multicasting. In addition, the semantics of setting up an ATM multipoint session are client-server oriented but conceptually reversed. The server creates a socket and then initiates a multipoint join with WSAJoinLeaf() to each client it wishes to invite into the multicast group. Each client creates a socket, listens, and waits on an accept call. If the server invites the client, the accept call completes. The next two sections describe the exact steps required for setting up an ATM leaf (client) node followed by the root (server) node however we begin with the an ATM multicast sample first. |
Add the following source code.
// Description:
// This sample illustrates multicasting on an ATM network.
// ATM multicasting features rooted control and data planes.
// This means our multicast server explicitly invites each
// leaf not via a WSAJoinLeaf call. The leaf nodes simply
// wait for on an accept for the invitation.
//
// For the root node we specify the -s options along with
// one or more -l:Address options. The address is the ATM
// address of the leaf to invite. All ATM addresses are only
// the first 38 chars. The port (selector) which is the other
// 2 characters of the address are given in the -p:xx option.
//
// For the client simply call the app with no parameters (the
// -i:Addr can be used for both client and server).
//
// Command Line Options/Parameters:
// atmmulticastexample -s -l ATM-Addr -i ATM-Addr -p XX -n int
// -s Act as root\n");
// -l str Leaf address to invite (38 chars)
// May be specified multiple times.
// -i str Local interface to bind to (38 chars)
// -p xx Port number (2 hex chars)
// -n int Number of packets to send
//
// Link to ws2_32.lib
// Already included in support.h
// #include <winsock2.h>
#include "support.h"
#include <stdio.h>
#include <stdlib.h>
#define BUFSIZE 1024
#define MAX_ATM_LEAF 4
#define ATM_PORT_OFFSET ((ATM_ADDR_SIZE*2)-2)
#define MAX_ATM_STR_LEN (ATM_ADDR_SIZE*2)
DWORD dwAddrCount=0, dwDataCount=20;
BOOL bServer=FALSE, bLocalAddress=FALSE;
char szLeafAddresses[MAX_ATM_LEAF][MAX_ATM_STR_LEN+1], szLocalAddress[MAX_ATM_STR_LEN+1], szPort[3];
SOCKET sLeafSock[MAX_ATM_LEAF];
// Module: usage
// Description: Print usage information.
int usage(char *progname)
{
printf("Usage: %s [-s]\n", progname);
printf(" -s Act as root\n");
printf(" -l str Leaf address to invite (38 chars)\n");
printf(" May be specified multiple times\n");
printf(" -i str Local interface to bind to (38 chars)\n");
printf(" -p xx Port number (2 hex chars)\n");
printf(" -n int Number of packets to send\n");
return 0;
}
// Module: ValidateArgs
// Description: Parse command line arguments.
void ValidateArgs(int argc, char **argv)
{
int i;
memset(szLeafAddresses, 0, MAX_ATM_LEAF * (MAX_ATM_STR_LEN+1));
memset(szPort, 0, sizeof(szPort));
for(i=1; i < argc ;i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 's': // server
bServer = TRUE;
break;
case 'l': // leaf address
if (i+1 >=argc)
usage(argv[0]);
strncpy_s(szLeafAddresses[dwAddrCount++], sizeof(szLeafAddresses), argv[++i], MAX_ATM_STR_LEN-2);
break;
case 'i': // local interface
if (i+1 >= argc)
usage(argv[0]);
strncpy_s(szLocalAddress, sizeof(szLocalAddress), argv[++i], MAX_ATM_STR_LEN-2);
bLocalAddress = TRUE;
break;
case 'p': // port address to use
if (i+1 >= argc)
usage(argv[0]);
strncpy_s(szPort, sizeof(szPort), argv[++i], 2);
break;
case 'n': // number of packets to send
if (i+1 >= argc)
usage(argv[0]);
dwDataCount = atoi(argv[++i]);
break;
default:
usage(argv[0]);
break;
}
}
}
return;
}
// Function: Server
// Description:
// Bind to the local interface and then invite each leaf
// address which was specified on the command line.
// Once each connection is made, send some data.
void Server(SOCKET s, WSAPROTOCOL_INFO *lpSocketProtocol)
{
// Server routine
SOCKADDR_ATM atmleaf, atmroot;
WSABUF wsasend;
char sendbuf[BUFSIZE], szAddr[BUFSIZE];
DWORD dwBytesSent, dwAddrLen=BUFSIZE, dwNumInterfaces, i;
int ret;
// If no specified local interface is given pick the first one
memset(&atmroot, 0, sizeof(SOCKADDR_ATM));
if (!bLocalAddress)
{
dwNumInterfaces = GetNumATMInterfaces(s);
GetATMAddress(s, 0, &atmroot.satm_number);
}
else
AtoH((char *)&atmroot.satm_number.Addr[0], szLocalAddress, ATM_ADDR_SIZE-1);
// Set the port number in the address structure
AtoH((char *)&atmroot.satm_number.Addr[ATM_ADDR_SIZE-1], szPort, 1);
// Fill in the rest of the SOCKADDR_ATM structure
atmroot.satm_family = AF_ATM;
atmroot.satm_number.AddressType = ATM_NSAP;
atmroot.satm_number.NumofDigits = ATM_ADDR_SIZE;
atmroot.satm_blli.Layer2Protocol = SAP_FIELD_ANY;
atmroot.satm_blli.Layer3Protocol = SAP_FIELD_ABSENT;
atmroot.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;
// Print out what we're binding to and bind()
if (WSAAddressToString((LPSOCKADDR)&atmroot, sizeof(atmroot), lpSocketProtocol, (LPWSTR)szAddr, &dwAddrLen))
{
printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
}
else
printf("WSAAddressToString() should be fine!\n");
printf("Binding to: <%s>\n", szAddr);
if (bind(s, (SOCKADDR *)&atmroot, sizeof(SOCKADDR_ATM)) == SOCKET_ERROR)
{
printf("bind() failed with error code %d\n", WSAGetLastError());
return;
}
else
printf("bind() is oK!\n");
// Invite each leaf
for(i=0; i < dwAddrCount ;i++)
{
// Fill in the SOCKADR_ATM structure for each leaf
memset(&atmleaf, 0, sizeof(SOCKADDR_ATM));
AtoH((char *)&atmleaf.satm_number.Addr[0], szLeafAddresses[i], ATM_ADDR_SIZE-1);
AtoH((char *)&atmleaf.satm_number.Addr[ATM_ADDR_SIZE-1], szPort, 1);
atmleaf.satm_family = AF_ATM;
atmleaf.satm_number.AddressType = ATM_NSAP;
atmleaf.satm_number.NumofDigits = ATM_ADDR_SIZE;
atmleaf.satm_blli.Layer2Protocol = SAP_FIELD_ANY;
atmleaf.satm_blli.Layer3Protocol = SAP_FIELD_ABSENT;
atmleaf.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;
// Print out clients address and the invite it
if (WSAAddressToString((LPSOCKADDR)&atmleaf,sizeof(atmleaf), lpSocketProtocol, (LPWSTR)szAddr,
&dwAddrLen))
{
printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
}
else
printf("WSAAddressToString() is OK!\n");
printf("[%02d] Inviting: <%s>\n", i, szAddr);
if ((sLeafSock[i] = WSAJoinLeaf(s, (SOCKADDR *)&atmleaf, sizeof(SOCKADDR_ATM), NULL,
NULL, NULL, NULL, JL_SENDER_ONLY)) == INVALID_SOCKET)
{
printf("WSAJoinLeaf() failed with error code %d\n", WSAGetLastError());
WSACleanup();
return;
}
else
printf("WSAJoinLeaf() is working!\n");
}
// Note that the ATM protocol is a bit different that TCP.
// When the WSAJoinLeaf (or connect call) completes the
// peer has not necessarily accepted the connection yet
// so immediately sending data will result in an error which is why we wait a short bit.
printf("Press a key to start sending.");
getchar();
printf("\n");
// Now send some data to the group address which will
// be replicated to all clients
wsasend.buf = sendbuf;
wsasend.len = 128;
for(i=0; i < dwDataCount ;i++)
{
memset(sendbuf, '$' + (i%26), 128);
ret = WSASend(s, &wsasend, 1, &dwBytesSent, 0, NULL, NULL);
if (ret == SOCKET_ERROR)
{
printf("WSASend() failed: %d\n", WSAGetLastError());
break;
}
printf("[%02d] Wrote: %d bytes\n", i, dwBytesSent);
Sleep(500);
}
for(i=0; i < dwAddrCount ;i++)
closesocket(sLeafSock[i]);
return;
}
// Function: Client
// Description:
// The client binds to the local interface (either one specified
// on the command line or the first local ATM address). It
// then waits on an accept call for the root invitation. It then waits to receive data.
void Client(SOCKET s, WSAPROTOCOL_INFO *lpSocketProtocol)
{
SOCKET sl;
SOCKADDR_ATM atm_leaf, atm_root;
DWORD dwNumInterfaces, dwBytesRead, dwAddrLen=BUFSIZE, dwFlags, i;
WSABUF wsarecv;
char recvbuf[BUFSIZE], szAddr[BUFSIZE];
int iLen = sizeof(SOCKADDR_ATM),ret;
// Setup the local interface
memset(&atm_leaf, 0, sizeof(SOCKADDR_ATM));
if (!bLocalAddress)
{
dwNumInterfaces = GetNumATMInterfaces(s);
GetATMAddress(s, 0, &atm_leaf.satm_number);
}
else
AtoH((char *)&atm_leaf.satm_number.Addr[0], szLocalAddress,ATM_ADDR_SIZE-1);
AtoH((char *)&atm_leaf.satm_number.Addr[ATM_ADDR_SIZE-1],szPort, 1);
// Fill in the SOCKADDR_ATM structure
atm_leaf.satm_family = AF_ATM;
atm_leaf.satm_number.AddressType = ATM_NSAP;
atm_leaf.satm_number.NumofDigits = ATM_ADDR_SIZE;
atm_leaf.satm_blli.Layer2Protocol = SAP_FIELD_ANY;
atm_leaf.satm_blli.Layer3Protocol = SAP_FIELD_ABSENT;
atm_leaf.satm_bhli.HighLayerInfoType = SAP_FIELD_ABSENT;
// Print the address we're binding to and bind
if (WSAAddressToString((LPSOCKADDR)&atm_leaf, sizeof(atm_leaf), lpSocketProtocol, (LPWSTR)szAddr, &dwAddrLen))
{
printf("WSAAddressToString() failed with error code %d\n", WSAGetLastError());
}
else
printf("WSAAddressToString() is OK!\n");
printf("Binding to: <%s>\n", szAddr);
if (bind(s, (SOCKADDR *)&atm_leaf, sizeof(SOCKADDR_ATM)) == SOCKET_ERROR)
{
printf("bind() failed with error code %d\n", WSAGetLastError());
return;
}
else
printf("bind() is OK!\n");
if(listen(s, 1) == 0)
printf("listen() is OK!\n");
else
printf("listen() failed with error code %d\n", WSAGetLastError());
// Wait for the invitation
memset(&atm_root, 0, sizeof(SOCKADDR_ATM));
if ((sl = WSAAccept(s, (SOCKADDR *)&atm_root, &iLen, NULL, 0)) == INVALID_SOCKET)
{
printf("WSAAccept() failed with error code %d\n", WSAGetLastError());
return;
}
else
printf("WSAAccept() is fine!\n");
printf("Received a connection!\n");
// Receive some data
wsarecv.buf = recvbuf;
for(i=0; i < dwDataCount ;i++)
{
dwFlags = 0;
wsarecv.len = BUFSIZE;
ret = WSARecv(sl, &wsarecv, 1, &dwBytesRead, &dwFlags,
NULL, NULL);
if (ret == SOCKET_ERROR)
{
printf("WSARecv() failed with error code %d\n", WSAGetLastError());
break;
}
if (dwBytesRead == 0)
break;
recvbuf[dwBytesRead] = 0;
printf("[%02d] Read %d bytes: '%s'\n", i, dwBytesRead, recvbuf);
}
if(closesocket(sl) == 0)
printf("closesocket(sl) is OK!\n");
else
printf("closesocket(sl) failed with error code %d\n",WSAGetLastError());
return;
}
// Function: main
// Description:
// This function loads Winsock library, parses command line
// arguments, creates the appropriate socket (with the right
// root or leaf flags), and start the client or server
// functions depending on the specified flags.
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET s;
WSAPROTOCOL_INFO lpSocketProtocol;
DWORD dwFlags;
if(argc < 2)
{
usage(argv[0]);
exit(1);
}
ValidateArgs(argc, argv);
// Load the Winsock library
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf("WSAStartup() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("WSAStartup() is OK!\n");
// Find an ATM capable protocol
if (FindProtocol(&lpSocketProtocol) == FALSE)
{
printf("Unable to find ATM protocol entry!\n");
return -1;
}
else
printf("ATM protocol entry found!\n");
// Create the socket using the appropriate root or leaf flags
if (bServer)
dwFlags = WSA_FLAG_OVERLAPPED | WSA_FLAG_MULTIPOINT_C_ROOT | WSA_FLAG_MULTIPOINT_D_ROOT;
else
dwFlags = WSA_FLAG_OVERLAPPED | WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF;
if ((s = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
FROM_PROTOCOL_INFO, &lpSocketProtocol, 0, dwFlags)) == INVALID_SOCKET)
{
printf("WSASocket() failed with error code %d\n", WSAGetLastError());
WSACleanup();
return -1;
}
else
printf("WSASocket() is OK!\n");
// Start the correct driver depending on what flags were supplied on the command line.
if (bServer)
{
Server(s, &lpSocketProtocol);
}
else
{
Client(s, &lpSocketProtocol);
}
if(closesocket(s) == 0)
printf("closesocket(s) is OK!\n");
else
printf("closesocket(s) failed with error code %d\n", WSAGetLastError());
if(WSACleanup() == 0)
printf("WSACleanup() is OK!\n");
else
printf("WSACleanup() failed with error code %d\n", WSAGetLastError());
return 0;
}
Add the support.h header file.
--------------------------------------------------------------
Add the source code.
// Module: support.h
//
// Description:
// This file contains function prototypes for the support routines found in support.cpp.
#include <winsock2.h>
#include <ws2atm.h>
int GetNumATMInterfaces(SOCKET s);
BOOL GetATMAddress(SOCKET s, int device, ATM_ADDRESS *atmaddr);
BOOL FindProtocol(WSAPROTOCOL_INFO *lpProto);
void AtoH( CHAR *szDest, CHAR *szSource, INT iCount );
Add the definition file, support.cpp for support.h header file.
Add the source code.
// Module: support.cpp
//
// Description:
// This file contains support routines necessary for the
// mcastatm.c sample. These function return the number of
// ATM interfaces as well as their corresponding ATM
// addresses.
//
// The BtoH and AtoH functions are provided which manually
// perform the string conversion to an ATM address. These
// are provided since some older platforms do not support
// the ATM address family in WSAStringToAddress and WSAAddressToString.
#include "support.h"
#include <stdio.h>
UCHAR BtoH( CHAR ch );
// Function: GetNumATMInterfaces
// Description: This function returns the number of ATM interfaces on the local machine.
int GetNumATMInterfaces(SOCKET s)
{
DWORD dwNum, nbytes=sizeof(DWORD);
if (WSAIoctl(s, SIO_GET_NUMBER_OF_ATM_DEVICES, NULL, 0,
(LPVOID)&dwNum, sizeof(DWORD), &nbytes, NULL, NULL) == SOCKET_ERROR)
{
return -1;
}
return dwNum;
}
// Function: GetATMAddress
// Description:
// This function returns the ATM address for a given interface.
// Use the previous function to return how many interfaces there
// are and then call this function with the device parameter
// from 0 to the number of interfaces - 1.
BOOL GetATMAddress(SOCKET s, int device, ATM_ADDRESS *atmaddr)
{
DWORD bytes;
if (WSAIoctl(s, SIO_GET_ATM_ADDRESS, (LPVOID)&device, sizeof(DWORD),
(LPVOID)atmaddr, sizeof(ATM_ADDRESS), &bytes, NULL, NULL) == SOCKET_ERROR)
{
return FALSE;
}
return TRUE;
}
// Function: FindProtocol
// Description: This function enumerates all installed protocol providers and returns the first ATM provider.
BOOL FindProtocol(WSAPROTOCOL_INFO *lpProto)
{
WSAPROTOCOL_INFO *lpProtocolBuf=NULL;
DWORD dwErr, dwRet, dwBufLen=0, i;
if (WSAEnumProtocols(NULL, lpProtocolBuf, &dwBufLen) != SOCKET_ERROR)
{
// This should never happen as there is a NULL buffer
printf("WSAEnumProtocols() failed with error code %d!\n", WSAGetLastError());
return FALSE;
}
else if ((dwErr = WSAGetLastError()) != WSAENOBUFS)
{
// We failed for some reason not relating to buffer size, also odd
printf("WSAEnumProtocols() failed with error code %d\n", dwErr);
return FALSE;
}
// Allocate the correct buffer size for WSAEnumProtocols as well as the buffer to return
lpProtocolBuf = (WSAPROTOCOL_INFO *)GlobalAlloc(GMEM_FIXED, dwBufLen);
if (lpProtocolBuf == NULL)
{
printf("GlobalAlloc() for protocol buffer failed with error code %d\n", GetLastError());
return FALSE;
}
dwRet = WSAEnumProtocols(NULL, lpProtocolBuf, &dwBufLen);
if (dwRet == SOCKET_ERROR)
{
printf("WSAEnumProtocols() failed with error code %d\n", WSAGetLastError());
GlobalFree(lpProtocolBuf);
return FALSE;
}
// Loop through the returned protocol information looking for those
// that are in the AF_NETBIOS address family.
for (i=0; i < dwRet ;i++)
{
if ( (lpProtocolBuf[i].iAddressFamily == AF_ATM) &&
(lpProtocolBuf[i].iSocketType == SOCK_RAW) &&
(lpProtocolBuf[i].iProtocol == ATMPROTO_AAL5) )
{
memcpy(lpProto, &lpProtocolBuf[i], sizeof(WSAPROTOCOL_INFO));
GlobalFree(lpProtocolBuf);
return TRUE;
}
}
GlobalFree(lpProtocolBuf);
return FALSE;
}
// Function: AtoH
// Description:
// This function coverts the ATM address specified in the
// string(ascii) format to the binary(hexadecimal) format.
void AtoH( CHAR *szDest, CHAR *szSource, INT iCount )
{
while (iCount--)
{
*szDest++ = ( BtoH ( *szSource++ ) << 4 ) + BtoH ( *szSource++ );
}
return;
}
// Function: BtoH
// Description:
// This function returns the equivalent binary value for
// an individual character specified in the ascii format.
UCHAR BtoH( CHAR ch )
{
if ( ch >= '0' && ch <= '9' )
{
return ( ch - '0' );
}
if ( ch >= 'A' && ch <= 'F' )
{
return ( ch - 'A' + 0xA );
}
if (ch >= 'a' && ch <= 'f' )
{
return ( ch - 'a' + 0xA );
}
// Illegal values in the address will not be excepted
return -1;
}
Build and run the project.
Unfortunately we don’t have ATM adapter to test this program.
Creating a leaf node in a multicast group is straightforward. On an ATM network, the leaf must listen for an invitation from a root to join a group. Here are the four steps needed to do this:
Using the WSASocket() function, create a socket of address family AF_ATM including the flags WSA_FLAG_MULTIPOINT_C_LEAF and WSA_FLAG_MULTIPOINT_D_LEAF.
Bind the socket to the local ATM address and port with the bind() function.
Call listen().
Wait for an invitation by using accept(), WSAAccept(), or AcceptEx(). Depending on the I/O model you use, this will differ.
Once the connection is established, the leaf node can receive data sent from the root. Remember that with ATM multicasting, the data flow is one way: from the root to the leaves. Windows 98, Windows Me, Windows 2000, and Windows XP currently support only a single ATM leaf node on the system at any given time. Only a single process on the entire system can be a leaf member of any ATM point-to-multipoint session.
Creating a root node is even easier than creating an ATM leaf. The process includes two basic steps:
Using the WSASocket() function, create a socket of address family AF_ATM including the flags WSA_FLAG_MULTIPOINT_C_ROOT and WSA_FLAG_MULTIPOINT_D_ROOT.
Call WSAJoinLeaf() with the ATM address for each endpoint you want to invite.
A root node can invite as many endpoints as it wants, but it must issue a separate WSAJoinLeaf() call for each.
Useful References:
1. IPv4 Multicasting Tools and Settings.
2. Windows Server 2003 Resource Kit Tools.
3. TCP/IP and NBT configuration parameters for Windows XP.
< Reliable Multicasting | Multicasting Main | Winsock & Quality of Service >