/* $Id: relay.c,v 0.6 2003/10/16 10:38:32 kjc Exp $ */
/*
 *  Copyright (c) 1996-2000
 *	Sony Computer Science Laboratories, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms of parts of or the
 * whole original or derived work are permitted provided that the above
 * copyright notice is retained and the original work is properly
 * attributed to the author. The name of the author may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

/* a standalone tool to relay ttt packets */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

/* for address family independent socket address structure */
union sockunion {
	struct sockinet {
		u_char si_len;
		u_char si_family;
		u_short si_port;
	} su_si;
	struct sockaddr_in  su_sin;
#ifdef INET6
	struct sockaddr_in6 su_sin6;
#endif
};
#define su_len		su_si.si_len
#define su_family	su_si.si_family
#define su_port		su_si.si_port

#define TTT_PORT		7288		/* receiver port */
#define BUFFER_SIZE	4096	/* big enough */
static char buffer[BUFFER_SIZE];

void fatal_error(const char *fmt, ...);

int name2sockaddr(char *name, int port, union sockunion *addrp, int family)
{
    unsigned long inaddr;
    struct hostent *hep;

    memset(addrp, 0, sizeof(union sockunion));
#ifdef INET6
    {
	struct addrinfo hints, *res;
	char portstr[64];
	int error;

	sprintf(portstr, "%d", port);
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_DGRAM;
	if (name == NULL)
	    hints.ai_flags = AI_PASSIVE;

	if ((error = getaddrinfo(name, portstr, &hints, &res)) != 0) {
	    fprintf(stderr, "can't get addrinfo for %s: %s\n",
		    name, gai_strerror(error));
	    return (-1);
	}

	*addrp = *(union sockunion *)res->ai_addr;

	freeaddrinfo(res);
    }
#else /* INET6 */    
    addrp->su_sin.sin_family = AF_INET;
    addrp->su_sin.sin_len = sizeof(struct sockaddr_in);
    if (name != NULL) {
	if ((inaddr = inet_addr(name)) != -1)
	    memcpy(&addrp->su_sin.sin_addr, &inaddr, sizeof(inaddr));
	else if ((hep = gethostbyname(name)) != NULL)
	    memcpy(&addrp->su_sin.sin_addr, hep->h_addr, hep->h_length);
	else
	    return (-1);
    }
    else
	addrp->su_sin.sin_addr.s_addr = htonl(INADDR_ANY);
    addrp->su_sin.sin_port = htons(port);
#endif /* INET6 */    
    return 0;
}

int usage()
{
    printf("usage: tttrelay [options] dest\n");
    printf(" options:\n");
    printf("    [-addr addr]\n");
    printf("    [-mcastifaddr addr]\n");
    printf("    [-out addr]\n");
    printf("    [-port recv_port]\n");
    printf("    [-dport dest_port]\n");
    printf("    [-probe addr]\n");
    printf("    [-mloop {0|1}]\n");
    exit(1);
}

int main(argc, argv)
    int argc;
    char **argv;
{
    union sockunion my_addr, probe_addr, view_addr;
    int in_fd, out_fd;
    int in_port = TTT_PORT;		/* my port number */
    int dest_port = TTT_PORT;		/* receiver's port number */
    char *my_name = NULL;
    char *out_name = NULL;
    char *mcastif_name = NULL;
    char *view_name = NULL;
    char *probe_name = NULL;
    int shared_port = 1;
    u_char ttl = 1;
    u_char mloop = 0;
    int in_family = AF_UNSPEC;
    int out_family = AF_UNSPEC;
    int packets = 0;
    const char *ptr = NULL;
#ifdef INET6
    char str[INET6_ADDRSTRLEN];
#else
    char str[16];
#endif
    
    while (--argc > 0) {
	if (strncmp(*++argv, "-port", 4) == 0 && --argc > 0)
	    in_port = atoi(*++argv);
	else if (strncmp(*argv, "-addr", 4) == 0 && --argc > 0)
	    my_name = *++argv;
	else if (strncmp(*argv, "-out", 4) == 0 && --argc > 0)
	    out_name = *++argv;
	else if (strncmp(*argv, "-mcastifaddr", 4) == 0 && --argc > 0)
	    mcastif_name = *++argv;
	else if (strncmp(*argv, "-probe", 4) == 0 && --argc > 0)
	    probe_name = *++argv;
	else if (strncmp(*argv, "-ttl", 4) == 0 && --argc > 0)
	    ttl = atoi(*++argv);
	else if (strncmp(*argv, "-dport", 4) == 0 && --argc > 0)
	    dest_port = atoi(*++argv);
	else if (strncmp(*argv, "-mloop", 4) == 0 && --argc > 0)
	    mloop = atoi(*++argv);
	else if (view_name == NULL)
	    view_name = *argv;
	else
	    usage();
    }

    if (view_name == NULL) {
	printf("no destination specified!\n");
	usage();
    }

    /* set up input socket */
    if (name2sockaddr(my_name, in_port, &my_addr, in_family) < 0)
	fatal_error("can't get my address!");
    if ((in_fd = socket(my_addr.su_family, SOCK_DGRAM, 0)) < 0)
	fatal_error("can't open socket");
#ifdef IP_ADD_MEMBERSHIP
    if (my_addr.su_family == AF_INET &&
	IN_MULTICAST(ntohl(my_addr.su_sin.sin_addr.s_addr))) {
	struct ip_mreq mreq;
	union sockunion ifaddr;

	mreq.imr_multiaddr.s_addr = my_addr.su_sin.sin_addr.s_addr;
	if (mcastif_name == NULL) {
	    mreq.imr_interface.s_addr = htonl(INADDR_ANY); /* use default if */
	}
	else {
	    if (name2sockaddr(mcastif_name, in_port, &ifaddr, AF_INET) < 0)
		fatal_error("can't get local address!");
	    mreq.imr_interface.s_addr = ifaddr.su_sin.sin_addr.s_addr;
	}
	
	if (setsockopt(in_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		       (char *)&mreq, sizeof(mreq)) < 0)
	    fatal_error("can't join group");
	
	printf("joined multicast group: %s\n", my_name);
    }
#endif /* IP_ADD_MEMBERSHIP */
#ifdef INET6
    else if (my_addr.su_family == AF_INET6 &&
	     IN6_IS_ADDR_MULTICAST(&my_addr.su_sin6.sin6_addr)) {
	struct ipv6_mreq mreq6;
	union sockunion ifaddr;

	mreq6.ipv6mr_multiaddr = my_addr.su_sin6.sin6_addr;
	if (mcastif_name == NULL) {
	    mreq6.ipv6mr_interface = 0; /* use default if */
	}
	else {
	    if (name2sockaddr(mcastif_name, in_port, &ifaddr, AF_INET6) < 0)
		fatal_error("can't get local address!");
	    mreq6.ipv6mr_interface = ifaddr.su_sin6.sin6_ifindex;
	}

	if (setsockopt(in_fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
		       (char *)&mreq6, sizeof(mreq6)) < 0)
	    fatal_error("can't join group");
	
	printf("joined multicast group: %s\n", view_name);
    }
#endif /* INET6 */
    if (shared_port) {
	int one = 1;
	if (setsockopt(in_fd, SOL_SOCKET, SO_REUSEADDR,
		       (char *)&one, sizeof(one)) < 0)
	    fatal_error("can't share the port");
	printf("port %d is shared.\n", in_port);
    }
    if (bind(in_fd, (struct sockaddr *)&my_addr, my_addr.su_len) < 0)
	fatal_error("can't bind");
    
    /* set up output socket */
    if (name2sockaddr(view_name, dest_port, &view_addr, out_family) < 0)
	fatal_error("can't get viewer's address!");

    if (name2sockaddr(out_name, 0, &my_addr, view_addr.su_family) < 0)
	fatal_error("can't get my address!");
    if ((out_fd = socket(my_addr.su_family, SOCK_DGRAM, 0)) < 0)
	fatal_error("can't open socket");

#ifdef IP_MULTICAST_TTL
    if (view_addr.su_family == AF_INET &&
	IN_MULTICAST(ntohl(view_addr.su_sin.sin_addr.s_addr))) {
	if (ttl != 1) {
	    if (setsockopt(out_fd, IPPROTO_IP, IP_MULTICAST_TTL,
			   &ttl, sizeof(ttl)) < 0)
		fatal_error("can't set ttl");
	}
#ifdef IP_MULTICAST_LOOP
	if (setsockopt(out_fd, IPPROTO_IP, IP_MULTICAST_LOOP,
		       &mloop, sizeof(mloop)) < 0)
	    fatal_error("can't disable mcast loop");
#endif
    }
#endif
#ifdef INET6
    else if (view_addr.su_family == AF_INET6 &&
	IN6_IS_ADDR_MULTICAST(&view_addr.su_sin6.sin6_addr)) {
	int hops = ttl;
	u_int loop = mloop;
	
	if (hops != 1) {
	    if (setsockopt(out_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
			   &hops, sizeof(hops)) < 0)
		fatal_error("can't set ttl");
	}
	if (setsockopt(out_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
		       &loop, sizeof(loop)) < 0)
	    fatal_error("can't disable mcast loop");
    }
#endif /* INET6 */

    if (bind(out_fd, (struct sockaddr *)&my_addr, my_addr.su_len) < 0)
	fatal_error("can't bind");
    
    switch (view_addr.su_family) {
    case AF_INET:
	ptr = inet_ntop(AF_INET, &view_addr.su_sin.sin_addr,
			str, sizeof(str));
	break;
#ifdef INET6
    case AF_INET6:
	ptr = inet_ntop(AF_INET6, &view_addr.su_sin6.sin6_addr,
			str, sizeof(str));
	break;
#endif
    }
    printf("relay started. sending to %s:%d ....\n", ptr, dest_port);

    if (probe_name != NULL) {
	if (name2sockaddr(probe_name, 0, &probe_addr, my_addr.su_family) < 0)
	    fatal_error("can't get probe address!");
	printf("reading from [%s] ....\n", probe_name);
    }
    else
	memset(&probe_addr, 0, sizeof(probe_addr));
	
    while (1) {
	int nbytes, fromlen;
	union sockunion from_addr;

	fromlen = sizeof(from_addr);
	if ((nbytes = recvfrom(in_fd, buffer, BUFFER_SIZE, 0,
		      (struct sockaddr *)&from_addr, &fromlen)) == -1) {
	    perror("recvfrom");
	    continue;
	}

	if (probe_addr.su_family == 0) {
	    probe_addr = from_addr;
	    switch (from_addr.su_family) {
	    case AF_INET:
		ptr = inet_ntop(AF_INET, &from_addr.su_sin.sin_addr,
				str, sizeof(str));
		break;
#ifdef INET6
	    case AF_INET6:
		ptr = inet_ntop(AF_INET6, &from_addr.su_sin6.sin6_addr,
				str, sizeof(str));
		break;
#endif
	    }
	    printf("reading from probe [%s] ....\n", ptr);
	}

	/* is this the probe we are reading from? */
	if (from_addr.su_family != probe_addr.su_family ||
	    (from_addr.su_family == AF_INET &&
	     from_addr.su_sin.sin_addr.s_addr !=
	     probe_addr.su_sin.sin_addr.s_addr)
#ifdef INET6
	    ||
	    (from_addr.su_family == AF_INET6 &&
	     (from_addr.su_sin6.sin6_addr.s6_addr32[0] !=
	      probe_addr.su_sin6.sin6_addr.s6_addr32[0] ||
	      from_addr.su_sin6.sin6_addr.s6_addr32[1] !=
	      probe_addr.su_sin6.sin6_addr.s6_addr32[1] || 
	      from_addr.su_sin6.sin6_addr.s6_addr32[2] !=
	      probe_addr.su_sin6.sin6_addr.s6_addr32[2] || 
	      from_addr.su_sin6.sin6_addr.s6_addr32[3] !=
	      probe_addr.su_sin6.sin6_addr.s6_addr32[3]))
#endif
	    ) {
	    static int warned = 0;
	    if (!warned) {
		printf("[warning] there are multiple probes.\n");
		switch (from_addr.su_family) {
		case AF_INET:
		    ptr = inet_ntop(AF_INET, &from_addr.su_sin.sin_addr,
				    str, sizeof(str));
		    break;
#ifdef INET6
		case AF_INET6:
		    ptr = inet_ntop(AF_INET6, &from_addr.su_sin6.sin6_addr,
				    str, sizeof(str));
		    break;
#endif
		}
		printf("\tignoring probe: %s\n", ptr);
		warned = 1;
	    }
	    continue;
	}
	
	if ((nbytes = sendto(out_fd, buffer, nbytes, 0,
		      (struct sockaddr *)&view_addr, view_addr.su_len)) < 0)
	    perror("sendto");
	packets++;
#if 1
	printf("*"); fflush(stdout);
	if ((packets % 60) == 0)
	    printf("\n");
#endif
    }
}

#include <errno.h>
#include <stdarg.h>

void
fatal_error(const char *fmt, ...)
{
    va_list ap;

    if (errno != 0)
	perror("fatal_error");
    else
	fprintf(stderr, "fatal_error: ");
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    exit(1);
}



syntax highlighted by Code2HTML, v. 0.9.1