/* ip.c 	system IP or UDP level stuff
 *
 * $Id: ip.c,v 1.4 1995/03/19 17:21:06 bdale Exp $
 *
 * Copyright 1991, Michael Westerhof, Sun Microsystems, Inc.
 * This software may be freely used, distributed, or modified, providing
 * this header is not removed.
 *
 * Added support for Linux - Ron Atkinson N8FOW 
 */

#include "ipip.h"

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netdb.h>
#include <fcntl.h>
#include <memory.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>

#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif

extern int errno;

#define IF_NAME "ip"		/* for use with the error checking macros */

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * open and initialize the IO interface.  Return -1 for error.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
int ip_open(ifp)
struct interface *ifp;
{
	struct sockaddr_in ip_udpbind;

	CK_IFNULL(ifp);
	CK_IFTYPE2(ifp,IF_TYPE_IPIP,IF_TYPE_IPUDP);

	if((ifp->status & IF_STAT_OPEN)) return 1;

	if(ifp->type == IF_TYPE_IPUDP){
		ifp->fd = socket(AF_INET, SOCK_DGRAM, 0);
	} else {
		ifp->fd = socket(AF_INET, SOCK_RAW, ifp->unit);
	}
	if (ifp->fd<0) {
		PERR("opening socket");
		return -1;
	}

	if (fcntl(ifp->fd, F_SETFL, FNDELAY) < 0) {
		PERR("setting non-blocking I/O on raw socket");
		return -1;
	}

	if(ifp->type == IF_TYPE_IPUDP){
		(void)memset( (char *)&ip_udpbind, 0, sizeof(struct sockaddr) );
		ip_udpbind.sin_addr.s_addr = INADDR_ANY;
		ip_udpbind.sin_family = AF_INET;
		ip_udpbind.sin_port = htons((unsigned short)ifp->unit);
		if(bind(ifp->fd,(struct sockaddr *)&ip_udpbind,sizeof ip_udpbind)<0){
			PERR("binding udp socket");
			return -1;
		}
	}

	ifp->status = IF_STAT_OPEN;
	return 1;
}


/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Read data from the specified interface. Return a complete IP datagram.
 * If the datagram is not complete, then don't return anything.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
int ip_read(ifp, m)
struct interface *ifp;
struct message *m;
{
	unsigned char buf[MAX_SIZE], *p;
	int n, hdr_len;
	unsigned int fromlen;
#ifdef LINUX
	struct iphdr *ipptr;
#else
	struct ip *ipptr;
#endif
	struct sockaddr_in ip_from;

	CK_IFNULL(ifp);
	CK_IFTYPE2(ifp,IF_TYPE_IPIP,IF_TYPE_IPUDP);
	CK_IFOPEN(ifp);
	CK_MNULL(m);

	(void)memset((char *)&ip_from, 0, sizeof(struct sockaddr));
	ip_from.sin_family = AF_INET;
	fromlen = sizeof ip_from;

	n = recvfrom(ifp->fd, (char *)buf, MAX_SIZE, 0,(struct sockaddr *)&ip_from, &fromlen);
	if(n<0){
		m->length = 0;
		if(errno==EINTR)return 0;
		if(errno==EWOULDBLOCK)return 0;
		PERR("recvfrom: on socket");
		return -1;
	}

	if(n==0)return 0;

	if(ifp->type == IF_TYPE_IPUDP){
		p = buf;
	} else {
#ifdef LINUX
		ipptr = (struct iphdr *)buf;
		hdr_len = 4 * ipptr->ihl;
#else
                ipptr = (struct ip *)buf;
                hdr_len = 4 * ipptr->ip_hl;
#endif
		p = buf + hdr_len;
		n = n - hdr_len;
	}

	(void)memcpy((char *)m->msg,(char *)p, n);
	m->length = n;
	(void)memcpy( (char *)&(m->fip), (char *)&ip_from.sin_addr, 4);
	m->fport = ip_from.sin_port;
	return n;
}

/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * write data from to the specified interface. Return as soon as possible.
 * The buffer provided will be a complete IP datagram.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
int ip_send(ifp, m)
struct interface *ifp;
struct message *m;
{
	int n;
	struct sockaddr_in ip_to;

	CK_IFNULL(ifp);
	CK_IFTYPE2(ifp,IF_TYPE_IPIP,IF_TYPE_IPUDP);
	CK_IFOPEN(ifp);
	CK_MNULL(m);

	if(m->length<=0)return 0;

	if((long)m->tip==0){
		syslog(LOG_WARNING,"attempt to send to IP address 0.0.0.0");
		return 0;
	}

	if((ifp->type==IF_TYPE_IPUDP)&&(m->tport==0)){
		syslog(LOG_WARNING,"attempt to send to UDP port 0");
		return 0;
	}

	(void)memset( (char *)&ip_to, 0, sizeof(struct sockaddr) );
	ip_to.sin_family = AF_INET;
	ip_to.sin_port = m->tport;
	(void)memcpy((char *)&ip_to.sin_addr, (char *)&(m->tip), 4);

	n = sendto(ifp->fd, (char *)m->msg, m->length, 0,
			(struct sockaddr *)&ip_to, sizeof ip_to);
	if(n<0){
		/* Log errors but don't die, since most errors are transient */
		char bugger[80];
		unsigned char *p = (unsigned char *)&m->tip;
		sprintf(bugger,
			"ip_send(dest:%d.%d.%d.%d) sendto(): %s",
			p[0], p[1], p[2], p[3], strerror (errno) ) ;
		PERR(bugger);
		return 0;		/* Who cares?  Continue. */
	}

	return n;
}
