* Copyright 2003-2004 by Marco d'Itri <md@linux.it>
* This software is distributed under the terms of the GNU GPL. If we meet some
* day, and you think this stuff is worth it, you can buy me a beer in return.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define RECV_BUF_SIZE 64 * 1024
#ifndef IP_ADD_SOURCE_MEMBERSHIP
# define IP_ADD_SOURCE_MEMBERSHIP 39
struct ip_mreq_source {
struct in_addr imr_multiaddr;
struct in_addr imr_sourceaddr;
struct in_addr imr_interface;
};
#endif
#ifndef MCAST_JOIN_GROUP
# define MCAST_JOIN_GROUP 42
# define MCAST_JOIN_SOURCE_GROUP 46
struct group_req {
uint32_t gr_interface;
struct sockaddr_storage gr_group;
};
struct group_source_req {
uint32_t gsr_interface;
struct sockaddr_storage gsr_group;
struct sockaddr_storage gsr_source;
};
#endif
#define v_printf(level, x...) \
if (verbose >= level) \
fprintf(stderr, x)
void chat(const int sock);
void dump(const int sock, int out_fd, const int dump_packets);
static int open_multicast_socket(const char *group, const char *port,
const char *source);
static int open_packet_file(void *buf, int n, const char *addr);
static void alarm_exit(int signaln);
static void usage(void);
static void err_quit(const char *, ...);
static void err_sys(const char *, ...);
static struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "verbose", no_argument, NULL, 'v' },
{ "timeout", required_argument, NULL, 't' },
{ "output", required_argument, NULL, 'o' },
{ "source", required_argument, NULL, 's' },
{ NULL, 0, NULL, 0 }
};
static int verbose = 0;
int main(int argc, char *argv[])
{
int timeout = 0;
int out_fd = STDOUT_FILENO;
int dump_packets = 0;
int ch, sock;
char *source = NULL;
while ((ch = getopt_long(argc, argv, "hvo:pt:s:", longopts, 0)) > 0) {
switch (ch) {
case 'o':
out_fd = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 0666);
if (out_fd < 0)
err_sys("open");
break;
case 'p':
dump_packets = 1;
break;
case 't':
timeout = atoi(optarg);
break;
case 's':
source = optarg;
break;
case 'v':
verbose++;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc != 2)
usage();
sock = open_multicast_socket(argv[0], argv[1], source);
if (timeout) {
signal(SIGALRM, alarm_exit);
alarm(timeout);
}
chat(sock);
dump(sock, out_fd, dump_packets);
exit(0);
}
void chat(const int sock)
{
fd_set in;
while (1) {
int res;
FD_ZERO(&in);
FD_SET(0, &in);
FD_SET(sock, &in);
res = select(sock + 1, &in, 0, 0, 0);
if (res < 0)
break;
if (FD_ISSET(0, &in)) {
char buffer[8192];
int red = read(0, buffer, sizeof(buffer) - 1);
buffer[red] = 0;
#if 0
if (sendto(sock, buffer, red + 1, 0, (struct sockaddr *) &saddr,
sizeof(saddr)) < 0)
#else
if (send(sock, buffer, red + 1, 0) < 0)
#endif
err_sys("send");
}
if (FD_ISSET(sock, &in)) {
struct sockaddr_in6 from;
socklen_t fromlen = sizeof(from);
char taddr[64];
char buffer[8192];
int red = recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr *) &from, &fromlen);
buffer[red] = 0;
inet_ntop(AF_INET6, &from.sin6_addr, taddr, sizeof(taddr));
printf("<%s> %s\n", taddr, buffer);
}
}
}
void dump(const int sock, int out_fd, const int dump_packets) {
int n;
struct sockaddr_storage sraddr;
struct sockaddr *raddr = (struct sockaddr *)&sraddr;
socklen_t rlen = sizeof(struct sockaddr_storage);
char buf[RECV_BUF_SIZE];
while ((n = recvfrom(sock, buf, sizeof(buf), 0, raddr, &rlen)) > 0) {
char address[NI_MAXHOST], port[NI_MAXSERV];
if (verbose >= 2) {
if (getnameinfo(raddr, rlen, address, sizeof(address), port,
sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
err_sys("getnameinfo");
printf("Received %d bytes from [%s]:%s.\n", n, address, port);
}
if (dump_packets)
out_fd = open_packet_file(buf, n, address);
if (write(out_fd, buf, n) != n)
err_sys("write");
if (dump_packets)
if (close(out_fd) < 0)
err_sys("close");
}
if (n < 0)
err_sys("recvfrom");
if (!dump_packets && out_fd != STDOUT_FILENO)
if (close(out_fd) < 0)
err_sys("close");
v_printf(1, "End of stream.\n");
}
static int open_multicast_socket(const char *group, const char *port,
const char *source)
{
int fd;
int yes = 1;
#ifdef AF_INET6
int err;
struct addrinfo hints, *res, *ai;
int level;
struct group_req gr;
struct group_source_req gsr;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if ((err = getaddrinfo(group, port, &hints, &res)) != 0)
err_quit("getaddrinfo(%s, %s): %s", source, port, gai_strerror(err));
for (ai = res; ai; ai = ai->ai_next) {
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
continue;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
err_sys("setsockopt(SO_REUSEADDR)");
if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0)
break;
close(fd);
}
if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0)
err_sys("connect");
if (!ai)
err_sys("bind");
switch (ai->ai_family) {
case AF_INET:
level = IPPROTO_IP;
break;
case AF_INET6:
level = IPPROTO_IPV6;
break;
default:
err_quit("FATAL: family %d is not known", ai->ai_family);
}
if (source) {
struct addrinfo shints, *sai;
memset(&shints, 0, sizeof(shints));
shints.ai_family = ai->ai_family;
shints.ai_protocol = ai->ai_protocol;
if ((err = getaddrinfo(source, port, &shints, &sai)) != 0)
err_quit("getaddrinfo(%s, %s): %s", source, port,
gai_strerror(err));
memcpy(&gsr.gsr_group, ai->ai_addr, ai->ai_addrlen);
memcpy(&gsr.gsr_source, sai->ai_addr, sai->ai_addrlen);
gsr.gsr_interface = 0;
if (setsockopt(fd, level, MCAST_JOIN_SOURCE_GROUP, &gsr,
sizeof(gsr)) < 0)
err_sys("setsockopt(MCAST_JOIN_SOURCE_GROUP)");
freeaddrinfo(sai);
} else {
memcpy(&gr.gr_group, ai->ai_addr, ai->ai_addrlen);
gr.gr_interface = 0;
if (setsockopt(fd, level, MCAST_JOIN_GROUP, &gr, sizeof(gr)) < 0)
err_sys("setsockopt(MCAST_JOIN_GROUP)");
}
freeaddrinfo(res);
#else
struct hostent *hostinfo;
struct sockaddr_in saddr;
struct ip_mreq mr;
struct ip_mreq_source mrs;
if ((hostinfo = gethostbyname(group)) == NULL)
err_quit("Host %s not found.", group);
if (!IN_MULTICAST(ntohl(*hostinfo->h_addr)))
err_quit("%s is not a multicast address", group);
if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0)
err_sys("socket");
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
err_sys("setsockopt(SO_REUSEADDR)");
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
saddr.sin_port = htons(atoi(port));
if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
err_sys("bind");
if (source) {
mrs.imr_multiaddr = saddr.sin_addr;
mrs.imr_sourceaddr.s_addr = inet_addr(source);
mrs.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mrs,
sizeof(mrs)) < 0)
err_sys("setsockopt(IP_ADD_SOURCE_MEMBERSHIP)");
} else {
mr.imr_multiaddr = saddr.sin_addr;
mr.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr, sizeof(mr))<0)
err_sys("setsockopt(IP_ADD_MEMBERSHIP)");
}
#endif
return fd;
}
static int open_packet_file(void *buf, int n, const char *address)
{
static unsigned long int counter = 0;
char filename[1024];
int fd;
snprintf(filename, sizeof(filename), "pkt-%lu-%s", counter++, address);
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666);
if (fd < 0)
err_sys("open");
return fd;
}
static void alarm_exit(int signaln)
{
v_printf(1, "Timeout.\n");
exit(0);
}
static void usage(void)
{
fprintf(stderr,
"Usage: multicat [OPTIONS...] GROUP PORT\n"
"\n"
" -o, --output=FILE write the stream to FILE\n"
" -p write every packet to a new file\n"
" -t, --timeout=NUM the program will exit after NUM seconds\n"
" -s, --source=ADDR join the SSM source group ADDR\n"
" -v, --verbose tell me more. Use multiple times for more details\n"
" -h, --help display this help and exit\n"
"\n"
"If --output is not specified, standard output is used.\n"
);
exit(0);
}
static void err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, ": %s\n", strerror(errno));
va_end(ap);
exit(1);
}
static void err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fputs("\n", stderr);
va_end(ap);
exit(1);
}