/*
 * Copyright 2000, International Business Machines Corporation and others.
 * All Rights Reserved.
 * 
 * This software has been released under the terms of the IBM Public
 * License.  For details, see the LICENSE file in the top-level source
 * directory or online at http://www.openafs.org/dl/license10.html
 */

#include <afsconfig.h>
#include <afs/param.h>

RCSID
    ("$Header: /cvs/openafs/src/ntp/ntp_sock.c,v 1.5 2003/07/15 23:15:51 shadow Exp $");

#include <sys/types.h>
#include <sys/param.h>
#ifdef	AFS_SUN5_ENV
#define BSD_COMP
#endif
#include <sys/ioctl.h>
#ifdef	AFS_SUN5_ENV
#include <fcntl.h>
#endif
#include <sys/file.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <errno.h>
#include <syslog.h>
#include <stdio.h>
#include "ntp.h"

#define	MAX_INTF	10
struct intf addrs[MAX_INTF];
int nintf;

#ifdef	TEST
extern int errno;

#include "AFS_component_version_number.c"

main()
{
    int i, cc, val;
    char foo[10];

    syslog(LOG_ERR, "ifconfig test");
    create_sockets(htons(43242));
    for (i = 0; i < nintf; i++) {
	printf("%d: %s fd %d  addr %s  mask %x ", i, addrs[i].name,
	       addrs[i].fd, inet_ntoa(addrs[i].sin.sin_addr.s_addr),
	       ntohl(addrs[i].mask.sin_addr.s_addr));
	cc = sizeof(val);
	if (getsockopt
	    (addrs[0].fd, SOL_SOCKET, SO_BROADCAST, (char *)&val, &cc)) {
	    perror("getsockopt");
	    exit(1);
	}
	printf("BCAST opt %d", val);
	cc = sizeof(val);
	if (getsockopt(addrs[0].fd, SOL_SOCKET, SO_RCVBUF, (char *)&val, &cc)) {
	    perror("getsockopt");
	    exit(1);
	}
	printf("sockbuf size = %d ", val);
	putchar('\n');
    }

    for (i = 0; i < nintf; i++) {
	fprintf(stderr, "Read fd %d.. ", addrs[i].fd);
	cc = read(addrs[i].fd, foo, 10);
	fprintf(stderr, " returns %d ", cc);
	perror("read errno");
    }
}
#endif

#ifndef	SIOCGIFCONF
/*
 *  If we can't determine the interface configuration, just listen with one
 *  socket at the INADDR_ANY address.
 */
create_sockets(port)
     unsigned int port;
{
    addrs[0].sin.sin_family = AF_INET;
    addrs[0].sin.sin_port = 0;
    addrs[0].sin.sin_addr.s_addr = INADDR_ANY;
    addrs[0].sin.sin_mask.s_addr = htonl(~0);
    addrs[0].name = "wildcard";

    if ((addrs[0].fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	syslog(LOG_ERR, "socket() failed: %m");
#ifdef	TEST
	perror("socket() failed");
#endif
	exit(1);
     /*NOTREACHED*/}

    if (fcntl(addrs[i].fd, F_SETFL, FNDELAY) < 0) {
	syslog(LOG_ERR, "fcntl(FNDELAY) fails: %m");
#ifdef	TEST
	perror("fcntl(FNDELAY) fails");
#endif
	exit(1);
     /*NOTREACHED*/}
    addrs[0].sin.sin_family = AF_INET;
    addrs[0].sin.sin_port = port;
    addrs[0].if_flags = 0;
    if (bind
	(addrs[0].fd, (struct sockaddr *)&addrs[0].sin,
	 sizeof(addrs[0].sin)) < 0) {
	syslog(LOG_ERR, "bind() fails: %m");
#ifdef	TEST
	perror("bind fails\n");
#endif
	exit(1);
    }
    nintf = 1;
    return nintf;
}
#else
/*
 *  Grab interface configuration, and create a socket for each interface
 *  address.
 */
create_sockets(port)
     unsigned int port;
{
    char buf[1024];
    struct ifconf ifc;
    struct ifreq ifreq, *ifr;
    int on = 1, off = 0;
    int n, i, vs;
    extern char *malloc();

    /*
     * create pseudo-interface with wildcard address
     */
    addrs[nintf].sin.sin_family = AF_INET;
    addrs[nintf].sin.sin_port = 0;
    addrs[nintf].sin.sin_addr.s_addr = INADDR_ANY;
    addrs[nintf].name = "wild";
    addrs[nintf].mask.sin_addr.s_addr = htonl(~0);

    nintf = 1;

    if ((vs = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	syslog(LOG_ERR, "vs=socket(AF_INET, SOCK_DGRAM) %m");
#ifdef	TEST
	perror("vs=socket(AF_INET, SOCK_DGRAM)");
#endif
	exit(1);
    }
    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = buf;
    if (ioctl(vs, SIOCGIFCONF, (char *)&ifc) < 0) {
	syslog(LOG_ERR, "get interface configuration: %m");
#ifdef	TEST
	perror("ioctl(SIOCGIFCONF) fails");
#endif
	exit(1);
    }
    n = ifc.ifc_len / sizeof(struct ifreq);

    for (ifr = ifc.ifc_req; n > 0; n--, ifr++) {
	if (ifr->ifr_addr.sa_family != AF_INET)
	    continue;
	ifreq = *ifr;
	if (ioctl(vs, SIOCGIFFLAGS, (char *)&ifreq) < 0) {
	    syslog(LOG_ERR, "get interface flags: %m");
#ifdef	TEST
	    perror("SIOCGIFFFLAGS fails");
#endif
	    continue;
	}
	if ((ifreq.ifr_flags & IFF_UP) == 0)
	    continue;
	addrs[nintf].if_flags = ifreq.ifr_flags;

	if (ioctl(vs, SIOCGIFADDR, (char *)&ifreq) < 0) {
	    syslog(LOG_ERR, "get interface addr: %m");
#ifdef	TEST
	    perror("SIOCGIFADDR fails");
#endif
	    continue;
	}
	if ((addrs[nintf].name = malloc(strlen(ifreq.ifr_name) + 1))
	    == NULL) {
	    syslog(LOG_ERR, "malloc failed");
	    exit(1);
	}
	strcpy(addrs[nintf].name, ifreq.ifr_name);
	addrs[nintf].sin = *(struct sockaddr_in *)&ifreq.ifr_addr;

#ifdef SIOCGIFBRDADDR
	if (addrs[nintf].if_flags & IFF_BROADCAST) {
	    if (ioctl(vs, SIOCGIFBRDADDR, (char *)&ifreq) < 0) {
		syslog(LOG_ERR, "SIOCGIFBRDADDR fails");
#ifdef	TEST
		perror("SIOCGIFBRDADDR fails");
#endif
		exit(1);
	    }
#ifdef ifr_broadaddr
	    addrs[nintf].bcast = *(struct sockaddr_in *)&ifreq.ifr_broadaddr;
#else
	    addrs[nintf].bcast = *(struct sockaddr_in *)&ifreq.ifr_addr;
#endif
	}
#endif /* SIOCGIFBRDADDR */
#ifdef SIOCGIFNETMASK
	if (ioctl(vs, SIOCGIFNETMASK, (char *)&ifreq) < 0) {
	    syslog(LOG_ERR, "SIOCGIFNETMASK fails");
#ifdef	TEST
	    perror("SIOCGIFNETMASK fails");
#endif
	    exit(1);
	}
	addrs[nintf].mask = *(struct sockaddr_in *)&ifreq.ifr_addr;
#endif /* SIOCGIFNETMASK */

	/* 
	 * look for an already existing source interface address.  If
	 * the machine has multiple point to point interfaces, then 
	 * the local address may appear more than once.
	 */
	for (i = 0; i < nintf; i++)
	    if (addrs[i].sin.sin_addr.s_addr ==
		addrs[nintf].sin.sin_addr.s_addr) {
#ifdef TEST
		printf("dup interface address %s on %s\n",
		       inet_ntoa(addrs[nintf].sin.sin_addr.s_addr),
		       ifreq.ifr_name);
#endif
		goto next;
	    }
	nintf++;
      next:;
    }
    close(vs);

    for (i = 0; i < nintf; i++) {
	/* create a datagram (UDP) socket */
	if ((addrs[i].fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	    syslog(LOG_ERR, "socket() failed: %m");
#ifdef	TEST
	    perror("socket(AF_INET, SOCK_DGRAM) fails");
#endif
	    exit(1);
	 /*NOTREACHED*/}

	/* set SO_REUSEADDR since we will be binding the same port
	 * number on each interface */
	if (setsockopt
	    (addrs[i].fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on,
	     sizeof(on))) {
#ifdef	TEST
	    perror("setsockopt SO_REUSEADDR on");
#endif
	    syslog(LOG_ERR, "setsockopt SO_REUSEADDR on fails: %m");
	}

	/*
	 * set non-blocking I/O on the descriptor
	 */
	if (fcntl(addrs[i].fd, F_SETFL, FNDELAY) < 0) {
	    syslog(LOG_ERR, "fcntl(FNDELAY) fails: %m");
#ifdef	TEST
	    perror("fcntl(F_SETFL, FNDELAY) fails");
#endif
	    exit(1);
	 /*NOTREACHED*/}

	/*
	 * finally, bind the local address address.
	 */
	addrs[i].sin.sin_family = AF_INET;
	addrs[i].sin.sin_port = port;
	if (bind
	    (addrs[i].fd, (struct sockaddr *)&addrs[i].sin,
	     sizeof(addrs[i].sin)) < 0) {
	    syslog(LOG_ERR, "bind() fails: %m");
#ifdef	TEST
	    perror("bind fails");
#endif
	    exit(1);
	}

	/*
	 *  Turn off the SO_REUSEADDR socket option.  It apparently
	 *  causes heartburn on systems with multicast IP installed.
	 *  On normal systems it only gets looked at when the address
	 *  is being bound anyway..
	 */
	if (setsockopt
	    (addrs[i].fd, SOL_SOCKET, SO_REUSEADDR, (char *)&off,
	     sizeof(off))) {
	    syslog(LOG_ERR, "setsockopt SO_REUSEADDR off fails: %m");
#ifdef	TEST
	    perror("setsockopt SO_REUSEADDR off");
#endif
	}
#ifdef SO_BROADCAST
	/* if this interface can support broadcast, set SO_BROADCAST */
	if (addrs[i].if_flags & IFF_BROADCAST) {
	    if (setsockopt
		(addrs[i].fd, SOL_SOCKET, SO_BROADCAST, (char *)&on,
		 sizeof(on))) {
		syslog(LOG_ERR, "setsockopt(SO_BROADCAST): %m");
#ifdef	TEST
		perror("setsockopt(SO_BROADCAST) on");
#endif
	    }
	}
#endif /* SO_BROADCAST */
    }
    return nintf;
}

#endif
