1/*
2 * Copyright 2003-2004 by Marco d'Itri <md@linux.it>
3 * This software is distributed under the terms of the GNU GPL. If we meet some
4 * day, and you think this stuff is worth it, you can buy me a beer in return.
5 *
6 */
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <unistd.h>
11#include <stdarg.h>
12#include <string.h>		/* strerror */
13#include <signal.h>
14#include <errno.h>
15#include <fcntl.h>
16#include <getopt.h>		/* getopt_long */
17#include <sys/types.h>
18#include <sys/select.h>
19#include <sys/socket.h>
20#include <sys/stat.h>
21#include <netdb.h>
22#include <arpa/inet.h>
23#include <netinet/in.h>
24
25#define RECV_BUF_SIZE	64 * 1024
26
27/* SSM and protocol-independent API from RFC3678 */
28#ifndef IP_ADD_SOURCE_MEMBERSHIP
29# define IP_ADD_SOURCE_MEMBERSHIP 39
30struct ip_mreq_source {
31    struct in_addr imr_multiaddr;	/* multicast group */
32    struct in_addr imr_sourceaddr;	/* source */
33    struct in_addr imr_interface;	/* local IP address of the interface */
34};
35#endif /* IP_ADD_SOURCE_MEMBERSHIP */
36
37#ifndef MCAST_JOIN_GROUP
38# define MCAST_JOIN_GROUP 42
39# define MCAST_JOIN_SOURCE_GROUP 46
40struct group_req {
41    uint32_t                gr_interface;	/* interface index */
42    struct sockaddr_storage gr_group;		/* group address */
43};
44struct group_source_req {
45    uint32_t                gsr_interface;	/* interface index */
46    struct sockaddr_storage gsr_group;		/* group address */
47    struct sockaddr_storage gsr_source;		/* source address */
48};
49#endif
50
51
52#define v_printf(level, x...) \
53    if (verbose >= level) \
54	fprintf(stderr, x)
55
56/* prototypes*/
57//void chat(const int sock, const struct sockaddr_in6 saddr);
58void chat(const int sock);
59void dump(const int sock, int out_fd, const int dump_packets);
60static int open_multicast_socket(const char *group, const char *port,
61	const char *source);
62static int open_packet_file(void *buf, int n, const char *addr);
63static void alarm_exit(int signaln);
64static void usage(void);
65static void err_quit(const char *, ...);
66static void err_sys(const char *, ...);
67
68/* global variables */
69static struct option longopts[] = {
70    { "help",		no_argument,		NULL, 'h' },
71    { "verbose",	no_argument,		NULL, 'v' },
72    { "timeout",	required_argument,	NULL, 't' },
73    { "output",		required_argument,	NULL, 'o' },
74    { "source",		required_argument,	NULL, 's' },
75    { NULL,		0,			NULL, 0   }
76};
77
78static int verbose = 0;
79
80int main(int argc, char *argv[])
81{
82    int timeout = 0;
83    int out_fd = STDOUT_FILENO;
84    int dump_packets = 0;
85    int ch, sock;
86    char *source = NULL;
87
88    while ((ch = getopt_long(argc, argv, "hvo:pt:s:", longopts, 0)) > 0) {
89	switch (ch) {
90	case 'o':
91	    out_fd = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 0666);
92	    if (out_fd < 0)
93		err_sys("open");
94	    break;
95	case 'p':
96	    dump_packets = 1;
97	    break;
98	case 't':
99	    timeout = atoi(optarg);
100	    break;
101	case 's':
102	    source = optarg;
103	    break;
104	case 'v':
105	    verbose++;
106	    break;
107	default:
108	    usage();
109	}
110    }
111    argc -= optind;
112    argv += optind;
113
114    if (argc != 2)
115	usage();
116
117    sock = open_multicast_socket(argv[0], argv[1], source);
118
119    if (timeout) {
120	signal(SIGALRM, alarm_exit);
121	alarm(timeout);
122    }
123
124    chat(sock);
125    dump(sock, out_fd, dump_packets);
126    exit(0);
127}
128
129//void chat(const int sock, const struct sockaddr_in6 saddr)
130void chat(const int sock)
131{
132    fd_set in;
133
134    while (1) {
135	int res;
136
137	FD_ZERO(&in);
138
139	FD_SET(0, &in);
140	FD_SET(sock, &in);
141
142	res = select(sock + 1, &in, 0, 0, 0);
143	if (res < 0)
144	    break;
145
146	if (FD_ISSET(0, &in)) {
147	    char buffer[8192];
148	    int red = read(0, buffer, sizeof(buffer) - 1);
149	    buffer[red] = 0;
150#if 0
151	    if (sendto(sock, buffer, red + 1, 0, (struct sockaddr *) &saddr,
152			sizeof(saddr)) < 0)
153#else
154	    if (send(sock, buffer, red + 1, 0) < 0)
155#endif
156		err_sys("send");
157	}
158	if (FD_ISSET(sock, &in)) {
159	    struct sockaddr_in6 from;
160	    socklen_t fromlen = sizeof(from);
161	    char taddr[64];
162	    char buffer[8192];
163
164	    int red = recvfrom(sock, buffer, sizeof(buffer), 0,
165		    (struct sockaddr *) &from, &fromlen);
166	    buffer[red] = 0;
167
168	    inet_ntop(AF_INET6, &from.sin6_addr, taddr, sizeof(taddr));
169
170	    printf("<%s> %s\n", taddr, buffer);
171	}
172    }
173}
174
175void dump(const int sock, int out_fd, const int dump_packets) {
176    int n;
177    struct sockaddr_storage sraddr;
178    struct sockaddr *raddr = (struct sockaddr *)&sraddr;
179    socklen_t rlen = sizeof(struct sockaddr_storage);
180    char buf[RECV_BUF_SIZE];
181
182    while ((n = recvfrom(sock, buf, sizeof(buf), 0, raddr, &rlen)) > 0) {
183	char address[NI_MAXHOST], port[NI_MAXSERV];
184
185	if (verbose >= 2) {
186	    if (getnameinfo(raddr, rlen, address, sizeof(address), port,
187			sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV) < 0)
188		err_sys("getnameinfo");
189	    printf("Received %d bytes from [%s]:%s.\n", n, address, port);
190	}
191	if (dump_packets)
192	    out_fd = open_packet_file(buf, n, address);
193	if (write(out_fd, buf, n) != n)
194	    err_sys("write");
195	if (dump_packets)
196	    if (close(out_fd) < 0)
197		err_sys("close");
198    }
199    if (n < 0)
200	err_sys("recvfrom");
201
202    if (!dump_packets && out_fd != STDOUT_FILENO)
203	if (close(out_fd) < 0)
204	    err_sys("close");
205
206    v_printf(1, "End of stream.\n");
207}
208
209static int open_multicast_socket(const char *group, const char *port,
210	const char *source)
211{
212    int fd;
213    int yes = 1;
214#ifdef AF_INET6
215    int err;
216    struct addrinfo hints, *res, *ai;
217    int level;
218    struct group_req gr;
219    struct group_source_req gsr;
220
221    memset(&hints, 0, sizeof(hints));
222    hints.ai_family = AF_UNSPEC;
223    hints.ai_socktype = SOCK_DGRAM;
224
225    if ((err = getaddrinfo(group, port, &hints, &res)) != 0)
226	err_quit("getaddrinfo(%s, %s): %s", source, port, gai_strerror(err));
227    for (ai = res; ai; ai = ai->ai_next) {
228	if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
229	    continue;		/* ignore */
230	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
231	    err_sys("setsockopt(SO_REUSEADDR)");
232	if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0)
233	    break;		/* success */
234	close(fd);
235    }
236
237    if (connect(fd, ai->ai_addr, ai->ai_addrlen) < 0)
238	err_sys("connect");
239
240    if (!ai)
241	err_sys("bind");
242
243    switch (ai->ai_family) {
244    case AF_INET:
245	level = IPPROTO_IP;
246	break;
247    case AF_INET6:
248	level = IPPROTO_IPV6;
249	break;
250    default:
251	err_quit("FATAL: family %d is not known", ai->ai_family);
252    }
253
254    if (source) {
255	struct addrinfo shints, *sai;
256
257	memset(&shints, 0, sizeof(shints));
258	shints.ai_family = ai->ai_family;
259	shints.ai_protocol = ai->ai_protocol;
260
261	if ((err = getaddrinfo(source, port, &shints, &sai)) != 0)
262	    err_quit("getaddrinfo(%s, %s): %s", source, port,
263		    gai_strerror(err));
264
265	memcpy(&gsr.gsr_group, ai->ai_addr, ai->ai_addrlen);
266	memcpy(&gsr.gsr_source, sai->ai_addr, sai->ai_addrlen);
267	gsr.gsr_interface = 0;
268	if (setsockopt(fd, level, MCAST_JOIN_SOURCE_GROUP, &gsr,
269		    sizeof(gsr)) < 0)
270	    err_sys("setsockopt(MCAST_JOIN_SOURCE_GROUP)");
271	freeaddrinfo(sai);
272    } else {
273	memcpy(&gr.gr_group, ai->ai_addr, ai->ai_addrlen);
274	gr.gr_interface = 0;
275	if (setsockopt(fd, level, MCAST_JOIN_GROUP, &gr, sizeof(gr)) < 0)
276	    err_sys("setsockopt(MCAST_JOIN_GROUP)");
277    }
278
279    freeaddrinfo(res);
280#else
281    struct hostent *hostinfo;
282    struct sockaddr_in saddr;
283    struct ip_mreq mr;
284    struct ip_mreq_source mrs;
285
286    if ((hostinfo = gethostbyname(group)) == NULL)
287	err_quit("Host %s not found.", group);
288
289    if (!IN_MULTICAST(ntohl(*hostinfo->h_addr)))
290	err_quit("%s is not a multicast address", group);
291
292    if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0)
293	err_sys("socket");
294
295    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
296	err_sys("setsockopt(SO_REUSEADDR)");
297
298    memset(&saddr, 0, sizeof(saddr));
299    saddr.sin_family = AF_INET;
300    saddr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
301    saddr.sin_port = htons(atoi(port));
302    if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
303	err_sys("bind");
304
305    if (source) {
306	mrs.imr_multiaddr = saddr.sin_addr;
307	mrs.imr_sourceaddr.s_addr = inet_addr(source);
308	mrs.imr_interface.s_addr = htonl(INADDR_ANY);
309	if (setsockopt(fd, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mrs,
310		    sizeof(mrs)) < 0)
311	    err_sys("setsockopt(IP_ADD_SOURCE_MEMBERSHIP)");
312    } else {
313	mr.imr_multiaddr = saddr.sin_addr;
314	mr.imr_interface.s_addr = htonl(INADDR_ANY);
315	if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr, sizeof(mr))<0)
316	    err_sys("setsockopt(IP_ADD_MEMBERSHIP)");
317    }
318#endif
319
320    return fd;
321}
322
323static int open_packet_file(void *buf, int n, const char *address)
324{
325    static unsigned long int counter = 0;
326    char filename[1024];
327    int fd;
328
329    snprintf(filename, sizeof(filename), "pkt-%lu-%s", counter++, address);
330    fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666);
331    if (fd < 0)
332	err_sys("open");
333    return fd;
334}
335
336static void alarm_exit(int signaln)
337{
338    v_printf(1, "Timeout.\n");
339    exit(0);
340}
341
342static void usage(void)
343{
344    fprintf(stderr,
345"Usage: multicat [OPTIONS...] GROUP PORT\n"
346"\n"
347"  -o, --output=FILE  write the stream to FILE\n"
348"  -p                 write every packet to a new file\n"
349"  -t, --timeout=NUM  the program will exit after NUM seconds\n"
350"  -s, --source=ADDR  join the SSM source group ADDR\n"
351"  -v, --verbose      tell me more. Use multiple times for more details\n"
352"  -h, --help         display this help and exit\n"
353"\n"
354"If --output is not specified, standard output is used.\n"
355);
356    exit(0);
357}
358
359/* Error routines */
360static void err_sys(const char *fmt, ...)
361{
362    va_list ap;
363
364    va_start(ap, fmt);
365    vfprintf(stderr, fmt, ap);
366    fprintf(stderr, ": %s\n", strerror(errno));
367    va_end(ap);
368    exit(1);
369}
370
371static void err_quit(const char *fmt, ...)
372{
373    va_list ap;
374
375    va_start(ap, fmt);
376    vfprintf(stderr, fmt, ap);
377    fputs("\n", stderr);
378    va_end(ap);
379    exit(1);
380}
381
382