//
// $Id: Network.cc,v 1.1.1.1 2000/03/10 16:32:19 engin Exp $
//
// Author(s): Ramesh Govindan

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <cerrno>

extern "C" {
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
};

#include "util/Types.hh"
#include "util/Trail.hh"
#include "util/Handler.hh"
#include "util/Buffer.hh"
#include "sys/File.hh"
#include "sys/Pipe.hh"
#include "sched/Job.hh"
#include "sched/Dispatcher.hh"

#include "network/Ping.hh"
#include "network/Prefix.hh"
#include "network/Mtrace.hh"
#include "network/Network.hh"
#include "network/Headers.hh"

// Constants
static const int	MaxBufferSize = 1024;
static const int	MaxInterfaces = 32;

// File local
static TraceCode	traceNetwork("network");

// Globals
Network*	network = NULL;

static void
netReadHandler(void* ptr,
                void* arg)
{
    ((Network*) ptr)->receive((RawSocket*) arg);
}

Network::Interface::Interface(char* ifname,
                              U32 ifaddr)
	: ListNode()
{
    name = strdup(ifname);
    address.set(ifaddr);
    TRACE(traceNetwork, "adding interface %s:%s\n", name, address.name());
}

Network::Interface::~Interface()
{
    TRACE(traceNetwork, "deleting interface %s:%s\n", name, address.name());
    free(name);
}

Network::Network()
{
    Handler		rh(netReadHandler, this);
    Handler		nh(NULL, NULL);
    struct sockaddr_in	sin;
    int			socklen = sizeof(sin);
    int 		sock;
    struct ifconf	ifc;
    struct ifreq	ifrs[MaxInterfaces];
    struct ifreq	*ifr;
    Network::Interface*	interface;
    U32			addr;
    Address		group;

    icmpSocket = new RawSocket(rh, nh, RawSocketICMP);
    igmpSocket = new RawSocket(rh, nh, RawSocketIGMP);

    group.set(MtraceMulticast);
    igmpSocket->join(group);
    recvBuffer = new Buffer(MaxBufferSize);

    // First find all interfaces
    if ((sock = ::socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        FATAL("couldn't open socket for getting interfaces: %s\n",
              strerror(errno));
        // NotReached
    }

    ifc.ifc_len = sizeof(struct ifreq) * MaxInterfaces;
    ifc.ifc_buf = (char*) &(ifrs[0]);

    if (ioctl(sock, SIOCGIFCONF, (void*) &ifc) < 0) {
        FATAL("failed ioctl for getting interface list: %s\n",
              strerror(errno));
        // NotReached
    }

    for (int i = 0; i < ifc.ifc_len; i++) {
        ifr = &(ifrs[i]);
        addr = ntohl(((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr.s_addr);
        if (addr == 0) {
            break;
        }
        if (addr != INADDR_LOOPBACK) {
            interface = new Network::Interface(ifr->ifr_name, addr);
            interfaces.append(interface);
        }
    }
    close(sock);

    if (interfaces.isEmpty()) {
        FATAL("couldn't find any usable interfaces\n");
        // NotReached
    }

    tprobesSent = 0;
    tprobesTimedOut = 0;
    icmprobesSent = 0;
    icmprobesTimedOut = 0;
    
    return;
}

Network::~Network()
{
    delete icmpSocket;
    delete igmpSocket;
    delete recvBuffer;
    interfaces.clear();
    pendingPings.clear();
    pendingTProbes.clear();
    pendingICMProbes.clear();
}

void
Network::dump(FILE* fp)
{
    fprintf(fp, "tprobes sent %Lu timed-out %Lu\n", 
            tprobesSent, tprobesTimedOut);
    fprintf(fp, "icmprobes sent %Lu timed-out %Lu\n", 
            icmprobesSent, icmprobesTimedOut);
    return;
}

void
Network::receive(RawSocket* socket)
{
    int		length;
    IP*		ip;
    ICMP*	icmp;
    IGMP*	igmp;
    U16		sum;

    length = socket->read(recvBuffer->contents, recvBuffer->capacity);
    if (length < 0) {
        return;
    }
    ip = (IP*) recvBuffer->contents;
    ip->ntoh();

    if (ip->totalLength != length) {
        return;
    }

    if (length < (ip->headerLength << 2)) {
        return;
    }
    length -= ip->headerLength << 2;

    switch (ip->protocol) {
        case IPPROTO_ICMP: 
        {
            // Sanity check ICMP packet
            if (length < sizeof(ICMP)) {
                return;
            }
    
            icmp = (ICMP*) ((char*) ip + (ip->headerLength << 2));
            sum = icmp->checksum;
            icmp->checksum = 0;
            if (sum && (sum != network->cksum((U16*) icmp, length))) {
                return;
            }
            icmp->ntoh();

            // Farm out packet to whoever's waiting
            for (Ping* ping = pendingPings.head(); ping != NULL;
                 ping = pendingPings.next(ping)) {
                if (ping->receive(ip)) {
                    break;
                }
            }
            for (TProbe* tp = pendingTProbes.head(); tp != NULL;
                 tp = pendingTProbes.next(tp)) {
                if (tp->receive(ip)) {
                    break;
                }
            }
            for (ICMProbe* tp = pendingICMProbes.head(); tp != NULL;
                 tp = pendingICMProbes.next(tp)) {
                if (tp->receive(ip)) {
                    break;
                }
            }
            break;
        }
            
        case IPPROTO_IGMP:
        {
            // Sanity check IGMP packet
            if (length < sizeof(IGMP)) {
                return;
            }

            igmp = (IGMP*) ((char*) ip + (ip->headerLength << 2));
            sum = igmp->checksum;
            igmp->checksum = 0;
            if (sum && (sum != network->cksum((U16*) igmp, length))) {
                return;
            }
            igmp->ntoh();
	    length-= sizeof(IGMP);
	    if (igmp->type != IGMPMtraceResp)
		    return;
	    if (length < sizeof(IGMPTrace)) {
		    TRACE(traceNetwork, "receive: IGMPMtraceResp from is too short\n");
		    return;
	    }
	    IGMPTrace *trace = (IGMPTrace*) (igmp + 1);
	    trace->ntoh();
	    length -= sizeof(IGMPTrace);

            Mtrace* mt = pendingMtraces.head();
            while (mt != NULL) {
                Mtrace* nextMt = pendingMtraces.next(mt);
		if (mt->accept_trace(trace))
		    mt->receive(trace, length);
                mt = nextMt;
            }
            break;
        }
            
        default:
            return;
    }
    return;
}

U16
Network::cksum(const U16* addr,
               int len)
{
    int nleft = len;
    const u_short *w = addr;
    u_short answer;
    u_short odd_byte = 0;
    register int sum = 0;

    /*
     *  Our algorithm is simple, using a 32 bit accumulator (sum),
     *  we add sequential 16 bit words to it, and at the end, fold
     *  back all the carry bits from the top 16 bits into the lower
     *  16 bits.
     */
    while( nleft > 1 )  {
        sum += *w++;
        nleft -= 2;
    }

    /* mop up an odd byte, if necessary */
    if( nleft == 1 ) {
        *(u_char *)(&odd_byte) = *(u_char *)w;
        sum += odd_byte;
    }

    /*
     * add back carry outs from top 16 bits to low 16 bits
     */
    sum = (sum >> 16) + (sum & 0xffff);     /* add hi 16 to low 16 */
    sum += (sum >> 16);                     /* add carry */
    answer = ~sum;                          /* truncate to 16 bits */
    return (answer);
}


//  Copyright (c) 1994 by the University of Southern California.
//  All rights reserved.
//
//  Permission to use, copy, modify, and distribute this software and
//  its documentation in source and binary forms for lawful
//  non-commercial purposes and without fee is hereby granted, provided
//  that the above copyright notice appear in all copies and that both
//  the copyright notice and this permission notice appear in supporting
//  documentation, and that any documentation, advertising materials,
//  and other materials related to such distribution and use acknowledge
//  that the software was developed by the University of Southern
//  California and/or Information Sciences Institute.
//  The name of the University of Southern California may not
//  be used to endorse or promote products derived from this software
//  without specific prior written permission.
//
//  THE UNIVERSITY OF SOUTHERN CALIFORNIA DOES NOT MAKE ANY REPRESENTATIONS
//  ABOUT THE SUITABILITY OF THIS SOFTWARE FOR ANY PURPOSE.  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, TITLE, AND 
//  NON-INFRINGEMENT.
//
//  IN NO EVENT SHALL USC, OR ANY OTHER CONTRIBUTOR BE LIABLE FOR ANY
//  SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, WHETHER IN CONTRACT,
//  TORT, OR OTHER FORM OF ACTION, ARISING OUT OF OR IN CONNECTION WITH,
//  THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
//  Questions concerning this software should be directed to 
//  scan@isi.edu.
//


