/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:net.c 12.0$ */
/* $ACIS:net.c 12.0$ */
/* $Source: /ibm/acis/usr/src/ibm/rvd/server/RCS/net.c,v $ */

#ifndef lint
static char *rcsid = "$Header:net.c 12.0$";
#endif


#ifndef lint
static char rcsid_net_c[] = "$Header:net.c 12.0$";
#endif lint

/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in the file "notice.h" */
#include "notice.h"

/* This file contains all the routines for handling the network:
 * opening the network connection, reading and writing packets,
 * waiting for network input to become available, and so forth.
 */

#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/param.h>
#include	<sys/ioctl.h>
#include	<sys/time.h>
#include	<stdio.h>
#include	<netdb.h>
#include	<errno.h>
#include	<netinet/in.h>
#include	<netinet/in_systm.h>
#include	<netinet/ip.h>
#include	<netinet/rvd.h>

#include	"logging.h"
#include	"rvd_types.h"
#include	"rvdadd.h"
#include	"custom.h"
#include	"obj.h"
#include	"queue.h"
#include	"packet.h"
#include	"extern.h"

static	int	rvd_sock;		/* rvd socket descriptor */

struct	net_stats {			/* network statistics */
	int	nt_rcvd;		/* number of received packets */
	int	nt_sent;		/* number of sent packets */
	int	nt_rblock;		/* times read returned EWOULDBLK */
	int	nt_wblock;		/* times writes blocked */
	int	nt_operr;		/* times couldn't get through */
} net_stats;


int
net_init()

/* Initialize the network connection.  Open the socket on the raw RVD
 * protocol, and set it to non-blocking mode.  If the socket can't
 * be opened, kill the process immediately.
 * Returns a bitmask which can be passed to select (possibly or'ed
 * with other bitmasks) to wait for input to become available on
 * the net connection.
 */
{
	struct	protoent *rvd_proto;	/* ptr to protocol struct */
	int	onoff = 1;		/* arg for ioctl FIONBIO */
	int	hiwat;			/* high water mark on kernel queue */

	if ((rvd_proto = getprotobyname("rvd")) == NULL) {
	    syslog(LOG_ALERT, "net_init: rvd protocol unknown");
	    exit(1);
	}

	if ((rvd_sock = socket(AF_INET, SOCK_RAW, rvd_proto->p_proto)) < 0 ||
	    ioctl(rvd_sock, (int)FIONBIO, (char *)&onoff) < 0) {
		syslog(LOG_ALERT, "net_init: %m");
		exit(1);
	}

	hiwat = 32764;			/* largest allowable multiple of 4 */
	if (ioctl(rvd_sock, (int)SIOCSHIWAT, (char *)&hiwat) < 0) {
		syslog(LOG_ALERT, "net_init: can't set high water mark: %m");
	}

	return(1 << rvd_sock);
}


struct rvd_pkt *
net_recv()

/* Receive the next available packet from the rvd socket.  This routine
 * allocates a packet buffer, receives the packet, and returns the
 * buffer.  The rp_len field of the received packet is set to the total
 * number of bytes received; the rp_fhost field is set to the internet
 * address of the packet's sender.
 * Returns NULL if no packets are available.
 */
{
	struct	sockaddr_in	fhost;	/* foreign host's address */
	int	fhlen;			/* for length of fhost address */
	register struct	rvd_pkt	*pkt;	/* received packet ptr. */
	register int		len;	/* received packet length */

	pkt = pkt_alloc();
	fhlen = sizeof(struct sockaddr_in);

	while ((len = recvfrom(rvd_sock, (char *)&pkt->rp_ip, MAXPKTSIZE, 0,
	    (struct sockaddr *)&fhost, &fhlen )) < 0) {
		if (errno == EWOULDBLOCK) { /* none available */
			net_stats.nt_rblock++;
			pkt_free(pkt);	/* free the packet back up */
			return(NULL);
		}
		if (errno != EINTR)	/* fatal error */
			bughalt(errno < sys_nerr ? sys_errlist[errno] :
			    "net_recv: unknown error");
	}

	if((net_stats.nt_rcvd++ % 1000) == 0)
		(void)time((time_t *)&now);	/* Update 'now' every 1000 packets recv'd */

	pkt->rp_len = len;		/* store rcvd length in packet */
	pkt->rp_fhost.s_addr = fhost.sin_addr.s_addr; /* and foreign host */

	if (loglevel(LOG_TRACE))
		trace(pkt, 0);		/* trace input packet */
	return(pkt);
}


net_send(pkt)

/* Send the specified packet out on the rvd socket.  The packet's rp_fhost
 * field is assumed to contain the destination host; it's rp_len field is
 * assumed to contain the length of the RVD header and data portions of
 * the packet.  If necessary, this routine will block until the packet
 * can be sent.  After sending the packet, it is deallocated.
 */

register struct	rvd_pkt	*pkt;		/* packet to be sent */
{
	struct	sockaddr_in	fhost;	/* destination host */

	net_stats.nt_sent++;

	if (loglevel(LOG_TRACE))
		trace(pkt, 1);		/* trace output packet */

	fhost.sin_family = AF_INET;	/* set up dest. addr */
	fhost.sin_port = 0;
	fhost.sin_addr.s_addr = pkt->rp_fhost.s_addr;

	while (sendto(rvd_sock, (char *)&pkt->rp_rvd, pkt->rp_len, 0, 
	    (struct sockaddr *)&fhost, sizeof(struct sockaddr_in)) !=
	    pkt->rp_len) {
		if (errno == EWOULDBLOCK) { /* have to wait... */
			int netmask;

			net_stats.nt_wblock++;
			netmask = 1 << rvd_sock;
			if (select(32, (fd_set *)0, (fd_set *)&netmask, (fd_set *)0,
			    (struct timeval *)0) >= 0 || errno == EINTR)
				continue;
		}
		switch(errno) {
			case ENETDOWN:
			case ENETUNREACH:
			case ENETRESET:
			case EHOSTDOWN:
			case EHOSTUNREACH:
				net_stats.nt_operr++;
				break;

			default:
				syslog(LOG_INFO, "net_send: %m");
				break;
		}
		break;
	}

	pkt_free(pkt);
}


net_show()

/* Show network statistics in log.
 */
{
	syslog(LOG_INFO, "Network connection statistics:");
	syslog(LOG_INFO, " %8d    rvd packets received", net_stats.nt_rcvd);
	syslog(LOG_INFO, " %8d    rvd packets sent", net_stats.nt_sent);
	syslog(LOG_INFO, " %8d    reads with no packet", net_stats.nt_rblock);
	syslog(LOG_INFO, " %8d    times write blocked", net_stats.nt_wblock);
	syslog(LOG_INFO, " %8d    network operation errs", net_stats.nt_operr);
}

/* Only called by bughalt when restarting the server.
 */
net_close()
{
	close(rvd_sock);
}
