A multicast test. The mctest program acts as both a source and a sink

for multicast packets.  Parameters for the interface, packet size,
number of packets, and interpacket gap may be given on the command line.
The sink records how many packets were missed, and at what time each
packet arrived.
This commit is contained in:
George V. Neville-Neil 2008-03-19 12:44:23 +00:00
parent 0d8563d31e
commit 48ff5f8424
2 changed files with 373 additions and 0 deletions

View File

@ -0,0 +1,341 @@
//
// All rights reserved.
//
// Author: George V. Neville-Neil
//
// This is a relatively simple multicast test which can act as a
// source and sink. The purpose of this test is to determine the
// latency between two hosts, the source and the sink. The programs
// expect to be run somewhat unsynchronized hosts. The source and
// the sink both record the time on their own machine and then the
// sink will correlate the data at the end of the run.
//
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
// C++ STL and other related includes
#include <iostream>
#include <string>
// Operating System and other C based includes
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
// Private include files
#include "mctest.h"
using namespace std;
//
// usage - just the program's usage line
//
//
void usage()
{
cout << "mctest -i interface -g multicast group -s packet size -n number -t inter-packet gap\n";
exit(-1);
}
//
// usage - print out the usage with a possible message and exit
//
// \param message optional string
//
//
void usage(string message)
{
cerr << message << endl;
usage();
}
//
// absorb and record packets
//
// @param interface ///< text name of the interface (em0 etc.)
// @param group ///< multicast group
// @param pkt_size ///< packet Size
// @param number ///< number of packets we're expecting
//
// @return 0 for 0K, -1 for error, sets errno
//
int sink(char *interface, struct in_addr *group, int pkt_size, int number) {
int sock;
socklen_t recvd_len;
struct sockaddr_in local, recvd;
struct ip_mreq mreq;
struct ifreq ifreq;
struct in_addr lgroup;
struct timeval timeout;
if (group == NULL) {
group = &lgroup;
if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
return (-1);
}
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("failed to open datagram socket");
return (-1);
}
strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
perror("no such interface");
return (-1);
}
memcpy(&mreq.imr_interface,
&((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
sizeof(struct in_addr));
mreq.imr_multiaddr.s_addr = group->s_addr;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
sizeof(mreq)) < 0) {
perror("failed to add membership");
return (-1);
}
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
&((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
sizeof(struct in_addr)) < 0) {
perror("failed to bind interface");
return (-1);
}
local.sin_family = AF_INET;
local.sin_addr.s_addr = group->s_addr;
local.sin_port = htons(DEFAULT_PORT);
local.sin_len = sizeof(local);
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0) {
perror("could not bind socket");
return (-1);
}
timeval packets[number];
timeval result;
char *packet;
packet = new char[pkt_size];
int n = 0;
timerclear(&timeout);
timeout.tv_sec = TIMEOUT;
if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout)) < 0)
perror("setsockopt failed");
while (n < number) {
if (recvfrom(sock, packet, pkt_size, 0, (struct sockaddr *)&recvd,
&recvd_len) < 0) {
if (errno == EWOULDBLOCK)
break;
perror("recvfrom failed");
return -1;
}
gettimeofday(&packets[ntohl(*(int *)packet)], 0);
n++;
}
cout << "Packet run complete\n";
if (n < number)
cout << "Missed " << number - n << " packets." << endl;
long maxgap = 0, mingap= INT_MAX;
for (int i = 0; i < number; i++) {
cout << "sec: " << packets[i].tv_sec << " usec: " <<
packets[i].tv_usec << endl;
if (i < number - 1) {
timersub(&packets[i+1], &packets[i], &result);
long gap = (result.tv_sec * 1000000) + result.tv_usec;
if (gap > maxgap)
maxgap = gap;
if (gap < mingap)
mingap = gap;
}
}
cout << "maximum gap (usecs): " << maxgap << endl;
cout << "minimum gap (usecs): " << mingap << endl;
return 0;
}
//
// transmit packets for the multicast test
//
// @param interface ///< text name of the interface (em0 etc.)
// @param group ///< multicast group
// @param pkt_size ///< packet size
// @param number ///< number of packets
// @param gap ///< inter packet gap in nano-seconds
//
// @return 0 for OK, -1 for error, sets errno
//
int source(char *interface, struct in_addr *group, int pkt_size,
int number, int gap) {
int sock;
struct sockaddr_in addr;
struct ip_mreq mreq;
struct ifreq ifreq;
struct in_addr lgroup;
if (group == NULL) {
group = &lgroup;
if (inet_pton(AF_INET, DEFAULT_GROUP, group) < 1)
return (-1);
}
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("could not open dgram socket");
return (-1);
}
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(DEFAULT_PORT);
addr.sin_addr.s_addr = group->s_addr;
addr.sin_len = sizeof(addr);
strncpy(ifreq.ifr_name, interface, IFNAMSIZ);
if (ioctl(sock, SIOCGIFADDR, &ifreq) < 0) {
perror("no such interface");
return (-1);
}
memcpy(&mreq.imr_interface,
&((struct sockaddr_in*) &ifreq.ifr_addr)->sin_addr,
sizeof(struct in_addr));
mreq.imr_multiaddr.s_addr = group->s_addr;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
sizeof(mreq)) < 0) {
perror("failed to add membership");
return (-1);
}
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
&((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
sizeof(struct in_addr)) < 0) {
perror("failed to bind interface");
return (-1);
}
u_char ttl = 64;
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
&ttl, sizeof(ttl)) < 0) {
perror("failed to set TTL");
return (-1);
}
char *packets[number];
for (int i = 0;i < number; i++) {
packets[i] = new char[pkt_size];
*(int *)packets[i] = htonl(i);
}
struct timespec sleeptime;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = gap;
for (int i = 0;i < number; i++) {
if (sendto(sock, (void *)packets[i], pkt_size, 0,
(struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("sendto failed");
return -1;
}
if (gap > 0)
if (nanosleep(&sleeptime, NULL) < 0) {
perror("nanosleep failed");
return -1;
}
}
return 0;
}
//
// main - the main program
//
// \param -g multicast group address to which to send/recv packets on
// \param -n the number of packets to send
// \param -s packet size in bytes
// \param -t inter-packet gap, in nanoseconds
//
//
int main(int argc, char**argv)
{
const int MAXNSECS = 999999999; ///< Must be < 1.0 x 10**9 nanoseconds
char ch; ///< character from getopt()
extern char* optarg; ///< option argument
char* interface; ///< Name of the interface
struct in_addr *group = NULL; ///< the multicast group address
int pkt_size; ///< packet size
int gap; ///< inter packet gap (in nanoseconds)
int number; ///< number of packets to transmit
bool server = false;
if (argc < 2 || argc > 11)
usage();
while ((ch = getopt(argc, argv, "g:i:n:s:t:rh")) != -1) {
switch (ch) {
case 'g':
group = new (struct in_addr );
if (inet_pton(AF_INET, optarg, group) < 1)
usage(argv[0] + string(" Error: invalid multicast group") +
optarg);
break;
case 'i':
interface = optarg;
break;
case 'n':
number = atoi(optarg);
if (number < 0 || number > 10000)
usage(argv[0] + string(" Error: ") + optarg +
string(" number of packets out of range"));
break;
case 's':
pkt_size = atoi(optarg);
if (pkt_size < 0 || pkt_size > 65535)
usage(argv[0] + string(" Error: ") + optarg +
string(" packet size out of range"));
break;
case 't':
gap = atoi(optarg);
if (gap < 0 || gap > MAXNSECS)
usage(argv[0] + string(" Error: ") + optarg +
string(" gap out of range"));
break;
case 'r':
server = true;
break;
case 'h':
usage(string("Help\n"));
break;
}
}
if (server) {
sink(interface, group, pkt_size, number);
} else {
source(interface, group, pkt_size, number, gap);
}
}

View File

@ -0,0 +1,32 @@
//
// Copyright (c) 2008, George V. Neville-Neil
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
//
// $FreeBSD$
//
const char* DEFAULT_GROUP = "239.255.255.1";
const int DEFAULT_PORT = 6666;
const int TIMEOUT = 10;