< IPv4 & Multicast Sourcing | Multicasting Main | Reliable Multicasting >
What do we have in this chapter 9 part 4?
|
IPv6
Multicasting with IPv6 is similar to IPv4 multicasting except that the socket options are named differently and take slightly different input parameters. The options for IPv6 are IPV6_ADD_MEMBERSHIP and IPV6_DROP_MEMBERSHIP. The option level is IPPROTO_IPV6. The structure that specifies the multicast group and interface is a struct ipv6_mreq that is defined as:
typedef struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; /* IPv6 multicast address */ unsigned int ipv6mr_interface; /* Interface index */ } IPV6_MREQ;
The structure fields are equivalent to the IPv4 structure struct ip_mreq except that the local interface is the interface index instead of the full IPv6 address. The easiest way to find the interface index of a local IPv6 address is via the IP Helper API GetAdaptersAddresses(). Also, if the link-local address is used as the local interface, the scope-ID of that link is the interface index. The following code sample illustrates joining an IPv6 multicast group when the link-local address is given for the local interface:
SOCKET s; struct ipv6_mreq mreq6; struct addrinfo *reslocal, *resmulti, hints;
s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
// Get the local wildcard address to bind to (i.e. "::") memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; getaddrinfo(NULL, "5150", &hints, &reslocal); bind(s, reslocal->ai_addr, reslocal->ai_addrlen); freeaddrinfo(reslocal);
// Resolve the link-local interface getaddrinfo("fe80::250:4ff:fe7c:7036%6", NULL, NULL, &reslocal); // Resolve the multicast address getaddrinfo("ff12::1", NULL, NULL, &resmulti); |
// Join the multicast group
mreq6.ipv6mr_multiaddr = ((SOCKADDR_IN6 *)resmulti->ai_addr)->sin6_addr;
mreq6.ipv6mr_interface = ((SOCKADDR_IN6 *)reslocal->ai_addr)->sin6_scope_id;
setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (char *)&mreq6, sizeof(mreq6));
freeaddrinfo(resmulti);
freeaddrinfo(reslocal);
As with IPv4, to leave the multicast group the same struct ipv6_mreq structure is passed into setsockopt() but with the IPV6_DROP_MEMBERSHIP option.
The previous sections have shown how to join and leave multicast groups to receive data sent to that group; however, an application need not join the group to send data to it. There is one issue to be aware of: multihomed computers. If an application creates a UDP socket and calls sendto() with a destination address of “234.5.6.7”, which interface is the data sent on? Basically, the first interface listed in the routing table is the interface the data is sent on. To override this behavior, applications may use the IP_MULTICAST_IF socket option to specify the interface for outgoing data. The option value is simply the 32-bit IPv4 address of the local interface. The following code sample sets the outgoing interface on a socket.
SOCKET s;
SOCKADDR_IN dest;
ULONG localif;
char buf[1024];
int buflen=1024;
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
localif = inet_addr("157.124.22.104");
setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localif, sizeof(localif));
dest.sin_family = AF_INET;
dest.sin_port = htons(5150);
dest.sin_addr.s_addr = inet_addr("234.5.6.7");
sendto(s, buf, buflen, 0, (SOCKADDR *)&dest, sizeof(dest));
The same sending issue that applies to IPv4 is also present for IPv6 multicasting. The outgoing interface for data sent to an IPv6 multicast group is specified with the IPV6_MULTICAST_IF option. The input parameter is an integer that specifies the adapter interface index that outgoing data is sent on. Also, if sendto() or WSASendTo() is used for sending data to the multicast group, the sin6_scope_id of the SOCKADDR_IN6 structure given for the to address parameter should be zero.
For IPv4 source multicasting, there is an ioctl that can specify one or more source addresses to include or exclude with a single call. This is SIO_SET_MULTICAST_FILTER. The input parameter is a struct ip_msfilter structure that is defined as:
struct ip_msfilter {
struct in_addr imsf_multiaddr; /* IP multicast address of group */
struct in_addr imsf_interface; /* local IP address of interface */
u_long imsf_fmode; /* filter mode - INCLUDE or EXCLUDE */
u_long imsf_numsrc; /* number of sources in src_list */
struct in_addr imsf_slist[1];
};
The first two fields are self-explanatory. The third field, imsf_fmode, indicates whether the source addresses listed in the imsf_slist array are sources that should be included or excluded from the multicast group. To include the sources, the constant MCAST_INCLUDE is supplied; otherwise, to exclude these sources MCAST_EXCLUDE is used. imsf_numsrc indicates the number of sources supplied, and imsf_slist is the array of source addresses. The following code sample illustrates using this ioctl:
SOCKET s;
char buf[1024];
struct ip_msfilter *msfilter;
DWORD bytes;
int filterlen;
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// bind the socket to INADDR_ANY
msfilter = (struct ip_msfilter *)buf;
msfilter->imsf_multiaddr.s_addr = inet_addr("234.5.6.7");
msfilter->imsf_interface.s_addr = inet_addr("157.124.22.104");
msfilter->imsf_fmode = MCAST_INCLUDE;
msfilter->imsf_numsrc = 3;
msfilter->imsf_slist[0].s_addr = inet_addr("172.138.104.10");
msfilter->imsf_slist[1].s_addr = inet_addr("172.138.104.11");
msfilter->imsf_slist[2].s_addr = inet_addr("172.138.104.12");
filterlen = sizeof(struct ip_msfilter) + ((msfilter->imsf_numsrc - 1) * sizeof(struct in_addr));
WSAIoctl(s, SIO_SET_MULTICAST_FILTER, (void *)msfilter, filterlen, NULL, 0, &bytes, NULL, NULL);
This sample joins a multicast group and specifies three valid sources to accept data from. To retrieve the multicast filter set, use the ioctl SIO_GET_MULTICAST_ FILTER. The input parameter must be a struct ip_msfilter structure that contains the local interface and multicast group on which to obtain the multicast state for. Upon success, a struct ip_msfilter structure is returned via the output parameter, which contains the full set of sources for the group and interface. Note that you must specify a large enough buffer to hold the filter plus all the sources.
Winsock 2 introduces the WSAJoinLeaf() API, which is designed to be protocol independent. The API is defined as:
SOCKET WSAJoinLeaf(
IN SOCKET s,
IN const struct sockaddr FAR * name,
IN int namelen,
IN LPWSABUF lpCallerData,
OUT LPWSABUF lpCalleeData,
IN LPQOS lpSQOS,
IN LPQOS lpGQOS,
IN DWORD dwFlags
);
In most respects, WSAJoinLeaf() takes the same parameters as WSAConnect(). The exceptions are that name indicates the multicast address to join and dwFlags indicates whether the socket will be sending or receiving data on the multicast socket. The valid flag values are JL_SENDER_ONLY, JL_RECEIVER_ONLY, and JL_BOTH.
For IP multicasting, a UDP socket must be created by using the WSASocket() API, and the flags WSA_FLAG_MULTIPOINT_C_LEAF and WSA_FLAG_MULTIPOINT_D_LEAF must be specified. The socket should then be bound to the specific interface on which the group is to be joined. Then WSAJoinLeaf() is invoked with the multicast address of the group to join. There are a couple of differences here. First, instead of binding to the wildcard address, you should bind to the specific local interface. Second, the outgoing (sending) interface does not have to be set. When the multicast group is joined, the outgoing interface is automatically set to the interface the socket is bound to. Finally, only a single multicast group can be joined on a socket. That is, unlike using setsockopt(), WSAJoinLeaf() can be invoked only once on a socket and the multicast group is joined until the socket is closed, there is no other way to leave the group.
The following program example demonstrates the IPv4/v6 multicasting using WSAJoinLeaf().
// Sample: IPv4/v6 Multicasting using WSAJoinLeaf()
//
// Files:
// ip_wsajoinleaf.cpp - this file
// resolve.cpp - common routines for name resolution
// resolve.h - header file for common routines
//
// Description:
// This sample illustrates how to use the WSJoinLeaf API for
// multicasting. This sample may be invoked as either a sender
// or a receiver. This sample works for both IPv4 and IPv6.
// The one drawback to using the WSAJoinLeaf API is that only
// a single multicast group may be joined on a socket.
//
// Command Line:
// ip_wsajoinleaf [-s] [-m str] [-p int] [-i str] [-b str] [-l] [-n int]
// -i addr Local address to bind to
// -j Don't join group
// -l 0/1 Disable/enable loopback
// -m addr Multicast address to join
// -n int Send/recv count
// -p int Port number
// -s Act as sender; default is receiver
// -t int Set multicast TTL
// -z int Buffer size (in bytes)
//
// Link to ws2_32.lib
#include <winsock2.h>
#include <ws2tcpip.h>
#include "resolve.h"
#include <stdio.h>
#define MCASTADDRV4 "224.0.0.255"
#define MCASTADDRV6 "ff12::1"
#define MCASTPORT "25000"
#define BUFSIZE 1024
#define DEFAULT_COUNT 500
#define DEFAULT_TTL 8
BOOL bSender=FALSE, // Act as as sender, default is receiver
bLoopBack=FALSE, // Enable or disable loopback
bDontJoin=FALSE; // Join the group (sender only)
int gSocketType=SOCK_DGRAM, // datagram
gProtocol=IPPROTO_UDP, // UDP
gCount=DEFAULT_COUNT, // Number of messages to send/recv
gBufferSize=BUFSIZE, // Buffer size for send/recv
gLoopBack=0, // Enable or disable loopback
gTtl=DEFAULT_TTL; // Set multicast TTL
char *gInterface=NULL, // Local address to bind to
*gMulticast=MCASTADDRV4, // Multicast group to join
*gPort=MCASTPORT; // Port number to use
// Function: usage
// Description: Display usage information.
int usage(char *progname)
{
fprintf(stderr, "Usage: %s [-s] [-i local-addr] [-m remote-addr] [-p port-num] [-n count]\n", progname);
fprintf(stderr,
" -i addr Local address to bind to\n"
" -l 0/1 Disable/enable loopback\n"
" -m addr Multicast address to join\n"
" -n int Send/recv count\n"
" -p int Port number\n"
" -s Act as sender; default is receiver\n"
" -t int Set multicast TTL\n"
" -z int Buffer size (in bytes)\n"
);
return 0;
}
// Function: ValidateArgs
// Description: Parse the command line and setup the global variables which indicate how this app should run.
void ValidateArgs(int argc, char **argv)
{
int i;
for(i=1; i < argc ;i++)
{
if (((argv[i][0] != '/') && (argv[i][0] != '-')) || (strlen(argv[i]) < 2))
usage(argv[0]);
switch(tolower(argv[i][1]))
{
case 'i': // local address to bind to
if (i+1 >= argc)
usage(argv[0]);
gInterface = argv[++i];
break;
case 'l': // enable/disable loopback
if (i+1 >= argc)
usage(argv[0]);
bLoopBack = TRUE;
gLoopBack = atoi(argv[++i]);
break;
case 'm': // remote (multicast) address to connect/join to
if (i+1 >= argc)
usage(argv[0]);
gMulticast = argv[++i];
break;
case 'p': // port number
if (i+1 >= argc)
usage(argv[0]);
gPort = argv[++i];
break;
case 'n': // number of sends/recvs
if (i+1 >= argc)
usage(argv[0]);
gCount = atoi(argv[++i]);
break;
case 's': // sender or receiver
bSender = TRUE;
break;
case 't': // multicast ttl
if (i+1 >= argc)
usage(argv[0]);
gTtl = atoi(argv[++i]);
break;
case 'z': // buffer size
if (i+1 >= argc)
usage(argv[0]);
gBufferSize = atoi(argv[++i]);
break;
default:
usage(argv[0]);
break;
}
}
}
// Function: SetMulticastTtl
// Description: This routine sets the multicast TTL value for the socket.
int SetMulticastTtl(SOCKET s, int af, int ttl)
{
char *optval=NULL;
int optlevel, option, optlen, rc;
rc = NO_ERROR;
if (af == AF_INET)
{
// Set the options for V4
optlevel = IPPROTO_IP;
option = IP_MULTICAST_TTL;
optval = (char *) &ttl;
optlen = sizeof(ttl);
}
else if (af == AF_INET6)
{
// Set the options for V6
optlevel = IPPROTO_IPV6;
option = IPV6_MULTICAST_HOPS;
optval = (char *) &ttl;
optlen = sizeof(ttl);
}
else
{
fprintf(stderr, "Attempting to set TTL for invalid address family!\n");
rc = SOCKET_ERROR;
}
if (rc != SOCKET_ERROR)
{
// Set the TTL value
rc = setsockopt( s, optlevel, option, optval, optlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "SetMulticastTtl: setsockopt failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Set multicast ttl to: %d\n", ttl);
}
}
return rc;
}
// Function: SetMulticastLoopBack
// Description:
// This function enabled or disables multicast loopback. If loopback is enabled
// (and the socket is a member of the destination multicast group) then the
// data will be placed in the receive queue for the socket such that if a
// receive is posted on the socket its own data will be read. For this sample
// it doesn't really matter as if invoked as the sender, no data is read.
int SetMulticastLoopBack(SOCKET s, int af, int loopval)
{
char *optval=NULL;
int optlevel, option, optlen, rc;
rc = NO_ERROR;
if (af == AF_INET)
{
// Set the v4 options
optlevel = IPPROTO_IP;
option = IP_MULTICAST_LOOP;
optval = (char *) &loopval;
optlen = sizeof(loopval);
}
else if (af == AF_INET6)
{
// Set the v6 options
optlevel = IPPROTO_IPV6;
option = IPV6_MULTICAST_LOOP;
optval = (char *) &loopval;
optlen = sizeof(loopval);
}
else
{
fprintf(stderr, "Attempting to set multicast loopback for invalid address family!\n");
rc = SOCKET_ERROR;
}
if (rc != SOCKET_ERROR)
{
// Set the multipoint loopback
rc = setsockopt(s, optlevel, option, optval, optlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "SetMulticastLoopBack: setsockopt failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Setting multicast loopback to: %d\n", loopval);
}
}
return rc;
}
// Function: SetSendInterface
// Description:
// This routine sets the send (outgoing) interface of the socket.
// Again, for v4 the IP address is used to specify the interface while for v6 its the scope-ID.
int SetSendInterface(SOCKET s, struct addrinfo *iface)
{
char *optval=NULL;
int optlevel, option, optlen, rc;
rc = NO_ERROR;
if (iface->ai_family == AF_INET)
{
// Setup the v4 option values
optlevel = IPPROTO_IP;
option = IP_MULTICAST_IF;
optval = (char *) &((SOCKADDR_IN *)iface->ai_addr)->sin_addr.s_addr;
optlen = sizeof(((SOCKADDR_IN *)iface->ai_addr)->sin_addr.s_addr);
}
else if (iface->ai_family == AF_INET6)
{
// Setup the v6 option values
optlevel = IPPROTO_IPV6;
option = IPV6_MULTICAST_IF;
optval = (char *) &((SOCKADDR_IN6 *)iface->ai_addr)->sin6_scope_id;
optlen = sizeof(((SOCKADDR_IN6 *)iface->ai_addr)->sin6_scope_id);
}
else
{
fprintf(stderr, "Attempting to set sent interface for invalid address family!\n");
rc = SOCKET_ERROR;
}
// Set send IF
if (rc != SOCKET_ERROR)
{
// Set the send interface
rc = setsockopt(s, optlevel, option, optval, optlen);
if (rc == SOCKET_ERROR)
{
printf("setsockopt() failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Set sending interface to: ");
PrintAddress(iface->ai_addr, iface->ai_addrlen);
printf("\n");
}
}
return rc;
}
int main(int argc, char **argv)
{
WSADATA wsd;
struct addrinfo *reslocal = NULL, *resmulti = NULL;
SOCKET s, rs;
char *buf=NULL;
int rc, i;
if(argc < 2)
{
usage(argv[0]);
exit(1);
}
ValidateArgs(argc, argv);
// Load Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf("WSAStartup() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("WSAStartup() is OK!\n");
// resolve the local interface first
reslocal = ResolveAddress(gInterface, (bSender ? "0": gPort), AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP);
if (reslocal == NULL)
{
fprintf(stderr, "Unable to resolve local interface: %s\n", gInterface);
return -1;
}
else
printf("ResolveAddress() should be fine!\n");
// Create the socket - remember to specify the multicast flags
s = WSASocket(
reslocal->ai_family,
reslocal->ai_socktype,
reslocal->ai_protocol,
NULL,
0,
WSA_FLAG_OVERLAPPED | WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF
);
if (s == INVALID_SOCKET)
{
fprintf(stderr, "socket(af = %d) failed with error code %d\n", reslocal->ai_family, WSAGetLastError());
return -1;
}
else
printf("socket(af = %d) is fine!\n", reslocal->ai_family);
rc = bind(s, reslocal->ai_addr, reslocal->ai_addrlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "bind() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("bind() is OK!\n");
printf("bound to: ");
PrintAddress(reslocal->ai_addr, reslocal->ai_addrlen);
printf("\n");
// Resolve the multicast address
resmulti = ResolveAddress(gMulticast, gPort, reslocal->ai_family, reslocal->ai_socktype, reslocal->ai_protocol);
if (resmulti == NULL)
{
fprintf(stderr, "Unable to resolve multicast address: %s\n", gMulticast);
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
else
printf("ResolveAddress() is OK!\n");
// Set the multicast TTL
rc = SetMulticastTtl(s, resmulti->ai_family, gTtl);
if (rc == SOCKET_ERROR)
{
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
// Set the loopback value if indicated
if (bLoopBack)
{
rc = SetMulticastLoopBack(s, resmulti->ai_family, gLoopBack);
if (rc == SOCKET_ERROR)
{
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
}
// If the don't join option is set then just connect the socket
if (bDontJoin == FALSE)
{
rs = WSAJoinLeaf(
s,
resmulti->ai_addr,
resmulti->ai_addrlen,
NULL,
NULL,
NULL,
NULL,
(bSender ? JL_SENDER_ONLY : JL_RECEIVER_ONLY)
);
if (rs == INVALID_SOCKET)
{
fprintf(stderr, "WSAJoinLeaf() failed with error code %d\n", WSAGetLastError());
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
else
printf("WSAJoinLeaf() is OK!\n");
}
else
{
rc = SetSendInterface(s, reslocal);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "Unable to set outgoing multicast interface\n");
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
rc = connect(s, resmulti->ai_addr, resmulti->ai_addrlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "connect() failed with error code %d\n", WSAGetLastError());
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
return -1;
}
else
printf("connect() is OK!\n");
}
printf("joining group: ");
PrintAddress(resmulti->ai_addr, resmulti->ai_addrlen);
printf("\n");
freeaddrinfo(reslocal);
freeaddrinfo(resmulti);
buf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, gBufferSize);
if (buf == NULL)
{
fprintf(stderr, "HeapAlloc() for buffer failed with error code %d\n", GetLastError());
return -1;
}
else
printf("HeapAlloc() for buffer is OK!\n");
memset(buf, '$', gBufferSize);
if (bSender)
{
for(i=0; i < gCount ;i++)
{
rc = send(s, buf, gBufferSize, 0);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "send() failed with error code %d\n", WSAGetLastError());
HeapFree(GetProcessHeap(), 0, buf);
return -1;
}
else
{
printf("wrote %d bytes\n", rc);
}
}
}
else
{
for(i=0; i < gCount ;i++)
{
rc = recv(s, buf, gBufferSize, 0);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "recv() failed with error code %d\n", rc);
HeapFree(GetProcessHeap(), 0, buf);
return -1;
}
else
{
printf("read %d bytes\n", rc);
}
}
}
HeapFree(GetProcessHeap(), 0, buf);
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 (import) the existing resolve.h and its definition, resolve.cpp into the project. Firstly, copy both files from the previous project.
-----------------------------------------------------------
Then paste those file into the current project file.
Then, add those files into the project using Add > Existing Item menu.
Multiple select those files and click the Add button.
Those files should be visible in the solution explorer as shown in the following Figure.
Build and run the project.
Run the project as receiver/client (in this case we run the receiver on 192.168.1.2).
Run the project as sender/server (in this case we run the sender on 192.168.1.1).
The previous receiver/client screenshot is shown below.
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.
< IPv4 & Multicast Sourcing | Multicasting Main | Reliable Multicasting >