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

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


#ifndef lint
static char rcsid_tctl_net_c[] = "$Header:tctl_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"

/* tctl_net.c */

/* This file includes the routines to send and receive packets on
 * the control connection.
 */

#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/ioctl.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	<rvd_types.h>
#include	<logging.h>
#include	<extern.h>

static	int	ctl_sock = 0;		/* control socket descriptor */
static 	struct	sockaddr_in	lhost = { AF_INET }; /* local host addr/port */
static	struct	sockaddr_in	fhost;	/* foreign host addr/port */

struct	ctl_stats {			/* control statistics */
	int	cs_recvs;		/* received packets */
	int	cs_sends;		/* sent packets */
	int	cs_serr;		/* send errors */
} ctl_stats;


/* Initialize the control connection.  Open a socket on the RVD-CONTROL/UDP
 * service.  If the socket can't be opened, kill the process immediately.
 */

ctl_init(dest)

char	*dest;				/* foreign host name */
{
	register struct	sockaddr_in	*fh; /* temp for for. host addr */
	register struct	servent	*ctl_serv; /* ptr to service structure */
	register struct	protoent *ctl_proto; /* ptr to protocol struct */
	extern	struct	sockaddr_in	*resolve_host();
	int	onoff = 1;		/* arg for FIONBIO ioctl */

	if(ctl_sock)
		ctl_close();

	if ((fh = resolve_host(dest)) == NULL) {
		fprintf(stderr, "ctl_init: host %s unknown\n", dest);
		exit(1);
	}
	fhost = *fh;

	if ((ctl_serv = getservbyname("rvd-control", "udp")) == NULL) {
		fprintf(stderr, "ctl_init: rvd-control service unknown\n");
		exit(1);
	}

	if ((ctl_proto = getprotobyname(ctl_serv->s_proto)) == NULL) {
		fprintf(stderr, "ctl_init: udp protocol unknown\n");
		exit(1);
	}

	fhost.sin_port = ctl_serv->s_port;

	if ((ctl_sock = socket(AF_INET, SOCK_DGRAM, ctl_proto->p_proto)) < 0 ||
	    bind(ctl_sock, (struct sockaddr_in *)&lhost,
	    sizeof(struct sockaddr_in)) < 0 ||
	    ioctl(ctl_sock, (int)FIONBIO, (char *)&onoff) < 0) {
		perror("ctl_init");
		exit(1);
	}
}

/* Receive the next available packet from the control socket.  The buf argument
 * is the address of a buffer of size buflen into which the packet is to be
 * received.  Fhost is a pointer to a place to put the source address of the
 * packet.  Returns the number of bytes received, or 0 if no packets
 * are available.
 */

ctl_recv(buf, buflen)

char	*buf;					/* buffer for packet */
int	buflen;					/* size of buffer in bytes */
{
	struct	sockaddr_in	from;		/* sender */
	int	fhlen;				/* for length of fhost */
	int	len;				/* received packet length */

	fhlen = sizeof(struct sockaddr_in);

	while ((len = recvfrom(ctl_sock, buf, buflen, 0,
	    (struct sockaddr *) &from, &fhlen)) < 0) {
		if (errno == EWOULDBLOCK)	/* none available */
			return(0);
		if (errno != EINTR)		/* fatal error */
			bughalt(errno < sys_nerr ? sys_errlist[errno] :
			    "ctl_recv: unknown error");
	}

	ctl_stats.cs_recvs++;

	if (loglevel(LOG_TRACE))
		ctl_log(&fhost, buf, len, 1);

	return(len);
}

/* Send the specified buffer of the specified length to the specified
 * destination host.
 */

ctl_send(buf, buflen)

char	*buf;				/* buffer containing packet */
int	buflen;				/* size of buffer in bytes */
{
	if (loglevel(LOG_TRACE))
		ctl_log(&fhost, buf, buflen, 0);

	if (sendto(ctl_sock, buf, buflen, 0, (struct sockaddr *)&fhost,
	    sizeof(struct sockaddr_in)) != buflen) {
		ctl_stats.cs_serr++;
		perror("ctl_send");
		return;
	}
	ctl_stats.cs_sends++;
}

/* Perform a packet exchange on the control connection.  Send the packet in
 * frombuf and wait for a response to be received into tobuf.  If no response
 * is received for tmo seconds, retransmit; up to nrxmit retransmits.  Returns
 * the length of the received packet in bytes, or 0 on long timeout or error.
 */

ctl_exch(frombuf, fromlen, tobuf, tolen, tmo, nrxmit)

char	*frombuf;			/* packet to be sent */
int	fromlen;			/* length of sent packet in bytes */
char	*tobuf;				/* buffer for received packet */
int	tolen;				/* length of receive buffer in bytes */
int	tmo;				/* retransmit timeout in seconds */
int	nrxmit;				/* maximum allowable retransmits */
{
	struct	timeval	timeout;	/* for select call */
	int	readfds;		/* select mask */
	int	nfd;			/* result from select */

	timeout.tv_sec = tmo;
	timeout.tv_usec = 0;

	while (nrxmit-- >= 0) {
		readfds = (1 << ctl_sock);	/* select is value/result */
		ctl_send(frombuf, fromlen);
		if ((nfd = select(32, &readfds, (int *)0, (int *)0, &timeout))
		     > 0)			/* success, receive it */
			return(ctl_recv(tobuf, tolen));
		else if (nfd < 0) {		/* error */
			perror("ctl_exch");
			return(0);
		}				/* else timeout; retry */
	}
	return(0);				/* long timeout */
}

/* Log the specified control packet to the standard output.
 */

ctl_log(fhost, buf, buflen, dir)

register struct	sockaddr_in	*fhost;	/* foreign host */
char	*buf;				/* packet */
int	buflen;				/* size of packet in bytes */
int	dir;				/* 0 ==> input, 1 ==> output */
{
	printf("%s control packet %s %X len %D\n", (dir ? "Input" : "Output"),
	    (dir ? "from" : "to"), fhost->sin_addr.s_addr, buflen);
	(void)fwrite(buf, 1, buflen, stdout);
	printf("\n\n");
}

/* Close control socket
 */

ctl_close()

{
	close(ctl_sock);
}
