/*
 *        Unofficial release 1.3
 *                B I N G
 *
 */

/* $Id: bing_probes.c,v 1.12 1999/10/24 23:28:14 fgouget Exp $ */

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <stdlib.h>

/* types.h provides u_short on HPUX10 and Solaris */
#include <sys/types.h>

#ifdef WIN32
#include <winsock.h>
#include "win32/types.h"
#else
#include <netinet/in_systm.h>
#include <netinet/in.h>
#endif

#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

#include "bing_misc.h"
#include "bing_probes.h"
#include "mod_icmp.h"

#ifdef NO_RANDOM
#define random          rand
#define srandom         srand
#endif


/* (!!) this will have to move somewhere else */
#ifndef ICMP_ROUTERADVERT
#define ICMP_ROUTERADVERT       9               /* router advertisement */
#endif
#ifndef ICMP_ROUTERSOLICIT
#define ICMP_ROUTERSOLICIT      10              /* router solicitation */
#endif
#ifndef ICMP_TIMXCEED
#define ICMP_TIMXCEED           11              /* time exceeded, code: */
#endif
#ifndef ICMP_PARAMPROB
#define ICMP_PARAMPROB          12              /* ip header bad */
#endif
#ifndef ICMP_TSTAMP
#define ICMP_TSTAMP             13              /* timestamp request */
#endif
#ifndef ICMP_TSTAMPREPLY
#define ICMP_TSTAMPREPLY        14              /* timestamp reply */
#endif
#ifndef ICMP_IREQ
#define ICMP_IREQ               15              /* information request */
#endif
#ifndef ICMP_IREQREPLY
#define ICMP_IREQREPLY          16              /* information reply */
#endif
#ifndef ICMP_MASKREQ
#define ICMP_MASKREQ            17              /* address mask request */
#endif
#ifndef ICMP_MASKREPLY
#define ICMP_MASKREPLY          18              /* address mask reply */
#endif

/*
 * bing_probes default settings
 */

#define DEF_UDP_PORT 28933

/*
 * This structure holds the information kept in a probe handle.
 */
typedef struct {
    /*
     * Options modified via probe_set_option
     */
    unsigned char bp_om:2,      /* specifies the probe method */
         bp_fill:2,             /* specifies the type of packet filling */
         bp_unused:4;           /* not used */
    short udp_port;             /* the invalid UDP port to probe in BP_OM_UNREACH_PORT mode */
    int pattern_size;           /* the pattern size if one exists */
    char* pattern;              /* the pattern data */
    
    /*
     * Internal data house-keeping
     */
    icmp_handle icmp_handle;    /* the icmp module handle used to send icmp packets */
    struct icmp* packet;        /* the icmp packet contents, contains the header and data */
                                /* (!!) does it contain the ip header or just the icmp header? */
    int packet_options_size;    /* the options size packet+packet_options_size point to the data */
    int packet_data_size;       /* the packet data size */
    int packet_data_update;     /* 1 if the packet data needs updating */
} bp_state_t;

#define handle2state(h)        ((bp_state_t*)h)

/*
 * Some internal functions.
 */

void dump_packet(char* packet, int size)
{
    struct ip* ip;
    struct sockaddr src_addr,dst_addr;
    char* ipopt;
    int hlen;
    int j;

    /* Dump the ip header */
    ip=(struct ip*)packet;
    hlen=ip->ip_hl<<2;
    SOCKADDR_IN(&src_addr)->sin_family=AF_INET;
    SOCKADDR_IN(&src_addr)->sin_port=0;
    SOCKADDR_IN(&src_addr)->sin_addr.s_addr=ip->ip_src.s_addr;
    SOCKADDR_IN(&dst_addr)->sin_family=AF_INET;
    SOCKADDR_IN(&dst_addr)->sin_port=0;
    SOCKADDR_IN(&dst_addr)->sin_addr.s_addr=ip->ip_dst.s_addr;

    printf("\n");
    for (j=0;j<hlen;j++) {
        printf("%02x ",(unsigned char)*(packet+j));
    }
    printf("\nVr HL  TOS Len   ID   Flg off  TTL Pro cks  Src             Dst\n");
    printf(" %1x %2d  %02x  %5d %04x",
       ip->ip_v, ip->ip_hl, ip->ip_tos, (unsigned short)ip->ip_len, ip->ip_id);
    printf(" %1x   %04x", ((ip->ip_off) & 0xe000) >> 13,
       (ip->ip_off) & 0x1fff);
    printf(" %02x  %02x  %04x", ip->ip_ttl, ip->ip_p, ip->ip_sum);
    printf(" %-15s", inet_ntoa(*(struct in_addr *)&ip->ip_src.s_addr));
    printf(" %s\n", inet_ntoa(*(struct in_addr *)&ip->ip_dst.s_addr));

    /* dump any option bytes */
    if (hlen>sizeof(struct ip)) {
        printf("IP Options: ");
        ipopt=(char*)ip+sizeof(struct ip);         /* point to options */
        while (hlen-->sizeof(struct ip)) {
            printf("%02x", *ipopt++);
        }
        putchar('\n');
    }

    /* Dump the icmp header if any*/
    if (ip->ip_p==1) {
        struct icmp* icp;

        icp=(struct icmp*)(packet+(ip->ip_hl<<2));
        switch (icp->icmp_type) {
        case ICMP_ECHOREPLY:
            printf("Echo Reply:   code=%d  id=%d seq=%d\n",icp->icmp_code,icp->icmp_id,icp->icmp_seq);
            break;
        case ICMP_SOURCEQUENCH:
            printf("ICMP_SOURCEQUENCH");
            break;
        case ICMP_REDIRECT:
            printf("ICMP_REDIRECT");
            break;
        case ICMP_ECHO:
            printf("Echo Request: code=%d  id=%d seq=%d\n",icp->icmp_code,icp->icmp_id,icp->icmp_seq);
            break;
        case ICMP_ROUTERADVERT:
            printf("ICMP_ROUTERADVERT");
            break;
        case ICMP_ROUTERSOLICIT:
            printf("ICMP_ROUTERSOLICIT");
            break;
        case ICMP_TIMXCEED:
            printf("ICMP_TIMXCEED");
            break;
        case ICMP_PARAMPROB:
            printf("ICMP_PARAMPROB");
            break;
        case ICMP_TSTAMP:
            printf("ICMP_TSTAMP");
            break;
        case ICMP_TSTAMPREPLY:
            printf("ICMP_TSTAMPREPLY");
            break;
        case ICMP_IREQ:
            printf("ICMP_IREQ");
            break;
        case ICMP_IREQREPLY:
            printf("ICMP_IREQREPLY");
            break;
        case ICMP_MASKREQ:
            printf("ICMP_MASKREQ");
            break;
        case ICMP_MASKREPLY:
            printf("ICMP_MASKREPLY");
            break;
        default:
            printf("unknown type %d\n",icp->icmp_type);
        }
    }
    
    /* Give short info about the payload */

}

/*
  --------------------------------------------------------------------------
        Builds the data field to be sent with the packet. The data put 
    in the packet is either dynamic, e.g. random data, or static (i.e.
    does not change depending on the packet), e.g. a user specified 
    pattern. In the first case the data is built on the fly while in the
    second case it is precomputed and recomputed each time the packet 
    size increases.
  ------------+----+-----+--------------------------------------------------
  Parameter   | IN | OUT | Role
  ------------+----+-----+--------------------------------------------------
  state       | X  |     | Internal state information
  size        | X  |     | The required packet size
  ------------+----+-----+--------------------------------------------------
  RETURN      |    |  X  | No return value
  ------------+----+-----+--------------------------------------------------
*/
static int generate_data(bp_state_t* state, int size)
{
    int i;

    /* maybe reallocate the data buffer */
    if (size>state->packet_data_size) {
    /* Allocate buffers which size is a multiple of 1024 */
    state->packet_data_size=(size+state->packet_options_size+1024) & ~1023;
    state->packet=realloc(state->packet,state->packet_data_size);
        state->packet_data_size-=state->packet_options_size;
    if (state->packet==NULL)
        return -1;
    state->packet_data_update=1;
    }

    /* maybe update the data in the buffer */
    if (state->packet_data_update==1) {
        unsigned char* packet_data;

        packet_data=((unsigned char*)state->packet)+state->packet_options_size;
        switch (state->bp_fill) {
        case BP_FILL_SEQ:
            /* Default case: just put some predictible data in 
             * the packet.
             */
            for (i=0;i<state->packet_data_size;i++) {
                packet_data[i]=(unsigned char)(i & 0xff);
            }
            state->packet_data_update=0;
            break;
        case BP_FILL_PATTERN:
            /* Fill the buffer with the pattern. Since this 
             * pattern does not change we can compute it ahead 
             * of time
             */
            i=0;
            while (i<state->packet_data_size) {
                memcpy(packet_data+i,state->pattern,
                   MIN(state->pattern_size,state->packet_data_size-i));
                i+=state->pattern_size;
            }
            state->packet_data_update=0;
            break;
        case BP_FILL_RANDOM:
            /* Initialise the packet payload with random data.
             * Note that on some platforms (e.g. Win32) RAND_MAX is less
             * than INT_MAX. Thus we only use the lower byte of the returned
             * int which in turn relies on the assumption that RAND_MAX+1
             * is a multiple of 256. Fortunately this seems to always be the 
             * case.
             */
	    /*printf("Filling the packet with %d random bytes:",state->packet_data_size);*/
            for (i=0;i<state->packet_data_size;i++) {
                packet_data[i]=(unsigned char)(random() & 0xff);
		/*if (i%16==0)
		    printf("\n%3d: ",i);
		printf("%2x,",packet_data[i]);*/
            }
	    /*printf("\n");*/
	    
            /* Unlike the other types of "filling" this one must be 
             * updated with each outgoing packet, thus we do not reset
             * packet_data_update.
             */
            break;
        default:
            return -1;
        }
    }
    return 0;
}

/*
 * Implementation of the bing_probe interface.
 */

bp_handle probe_open()
{
    bp_state_t* h;

    /* Allocate the data structure */
    h=malloc(sizeof(*h));
    if (h==NULL)
        return NULL;

    /* Open the ICMP handle. This requires root privilege on Unix */
    h->icmp_handle=icmp_open();
    if (h->icmp_handle==NULL) {
        free(h);
        return NULL;
    }

    /* And initialise the other fields */
    /* (!!) what should be the default ??? */
    h->bp_om=BP_OM_ECHO_REPLY;
    h->bp_fill=BP_FILL_SEQ;
    h->udp_port=DEF_UDP_PORT;
    h->pattern_size=0;
    h->pattern=NULL;
    h->packet=NULL;
    /* (!!) and what if the options are bigger, like if we do loose source routing or some such (which means the packet would also contain the ip options, not only the icmp options */
    h->packet_options_size=(char*)h->packet->icmp_data-(char*)h->packet;
    h->packet_data_size=0;
    h->packet_data_update=1;

    return h;
}

int probe_set_option(bp_handle handle, int level, int option, void* optval, int optlen)
{
    bp_state_t* state;
    int intval;

    if (handle==NULL) {
        errno=EINVAL;
        return -1;
    }

    state=handle2state(handle);
    if (level!=SOL_BP)
        return icmp_set_option(state->icmp_handle,level,option,optval,optlen);

    switch (option) {
    case BPO_FILLING:
        intval=*((int*)optval);
        if (((intval & ~BP_FILL_MASK)!=0) || (intval==BP_FILL_MASK)) {
            errno=EINVAL;
            return -1;
        }
        if ((intval==BP_FILL_PATTERN) && (state->pattern==NULL)) {
            errno=EFAULT;
            return -1;
        }
        state->bp_fill=intval;
        break;
    case BPO_PATTERN:
        /* check parameter */
        if ((optlen==0) && (state->bp_fill==BP_FILL_PATTERN)) {
            errno=EINVAL;
            return -1;
        }

        /* and reallocate the pattern buffer */
        state->pattern_size=optlen;
        state->pattern=realloc(state->pattern,state->pattern_size);
        if (state->pattern==NULL)
                return (optlen==0?0:-1);
        state->packet_data_update=1; /* the old pattern is lost */
        memcpy(state->pattern,(char*)optval,state->pattern_size);
        break;
    case BPO_OM:
        intval=*((int*)optval);
        if (((intval & ~BP_OM_MASK)!=0) || (intval==BP_OM_MASK)){
            errno=EINVAL;
            return -1;
        }
        state->bp_om=intval;
        break;
    case BPO_UDP_PORT:
        handle2state(handle)->udp_port=(int)optval;
        break;
    default:
        errno=EINVAL;
        return -1;
    }
    return 0;
}

int do_probe(bp_handle handle, struct sockaddr* target, int size, int ttl, bp_probedata_t *probe)
{
    bp_state_t* state;
    static int icp_seq=0;
    int res;
    int src_addr_size;
    int ret;

    probe->contents=NULL;
    if ((handle==NULL) || (size<PAD_PKT_SIZE)){
        errno=EINVAL;
        return -1;
    }
    state=handle2state(handle);
    ret=-1;

    /* (!!) hack ??? */
    if (ttl==0)
        ttl=1;

    /* Prepare the packet payload */
    generate_data(state,size);

    /* Probe the remote host according to the chosen method */
    switch (state->bp_om) {
    case BP_OM_UNREACH_PORT:
        /* BP_OM_UNREACH_PORT: send a UDP packet to a non-existing port.
         * We will either receive a TTL Exceeded or a Port Unreachable
         * ICMP message.
         */
        return -1;
        break;
    case BP_OM_ECHO_REPLY:
    case BP_OM_TTL_EXCEEDED:
        probe->size=state->packet_options_size+size;
        probe->contents=malloc(sizeof(struct ip)+probe->size);
        /* BP_OM_TTL_EXCEEDED and BP_OM_ECHO_REPLY: send an ICMP EchoRequest
         * message.
         * In the first case the upper layer should have arranged to receive 
         * TTL Exceeded ICMP messages while in the second case we should receive 
         * Echo Reply ICMP messages.
         */
        state->packet->icmp_type=ICMP_ECHO;
        state->packet->icmp_code=0;
        /* icmp_id is set by icmp_send */
        state->packet->icmp_seq = icp_seq++;
        /* (TODO) target should be in the sockaddr format.
         * Find something else to compute the packet size.
         * The ttl is not handled.
         */
        if (ttl>0) {
            probe_set_option(handle, IPPROTO_IP, IP_TTL, &ttl, 4);
        }
        res=icmp_send(state->icmp_handle,state->packet,
            probe->size,
            target,sizeof(*target));
        if (res<0) {
            return -1;
        }
        /* Now try to get the answer to our packet. Since we receive all ICMP
         * packets for this host we may receive other packets before our own, 
         * we may even receive the packet that we just sent !
         */
        while (1) {
            src_addr_size=sizeof(probe->src_addr);
            res=icmp_recv(state->icmp_handle,
                probe->contents,probe->size+sizeof(struct ip),
                &probe->src_addr,&src_addr_size,
                &probe->rtt);
            if (res>0) {
                struct ip* ip;
                struct icmp* icp;
                /* On linux the returned size does not take into account the ip header ! 
                 * Furthermore the ip->ip_len field does not take into account endianess
                 * issues. That's why I'm using res+(ip->ip_hl<<2).
                 */
        
                /* ip header */
                ip=(struct ip*)(probe->contents);
                /* icmp header */
                icp=(struct icmp*)(probe->contents+(ip->ip_hl<<2));
                probe->dst_addr.sa_family=PF_INET;
                memcpy(&((struct sockaddr_in*)(&probe->dst_addr))->sin_addr,&ip->ip_dst,4);
                ((struct sockaddr_in*)&(probe->dst_addr))->sin_port=0;

                switch (icp->icmp_type) {
                case ICMP_ECHOREPLY:
                    /*DEBUG_MSG((stderr,"icmp_type/code=%d/%d rtt=%g",icp->icmp_type,icp->icmp_code,probe->rtt));*/
                    if ((icp->icmp_seq+1==icp_seq)  && (icp->icmp_id==icmp_get_id(state->icmp_handle))) {
                        probe->size=res+(ip->ip_hl<<2);
                        return BP_RES_HIT;
                    } else {
                        printf("received a stale Echo Reply id=%d seq=%d\n",icp->icmp_id,icp->icmp_seq);
                    }
                    break;
                case ICMP_UNREACH:
                    DEBUG_MSG((stderr,"icmp_type/code=%d/%d rtt=%g",icp->icmp_type,icp->icmp_code,probe->rtt));
                    if (icp->icmp_code==ICMP_UNREACH_PORT) {
                        probe->size=res+(ip->ip_hl<<2);
                        return BP_RES_HIT;
                    }
                    break;
                case ICMP_TIMXCEED:
                    DEBUG_MSG((stderr,"icmp_type/code=%d/%d rtt=%g",icp->icmp_type,icp->icmp_code,probe->rtt));
                    if (icp->icmp_code==ICMP_TIMXCEED_INTRANS) {
                        probe->size=res+(ip->ip_hl<<2);
                        return BP_RES_TTL_EXCEEDED;
                    }
                    break;
                case ICMP_ECHO:
                    /* this is most likely the echo that we sent */
                    break;
                default:
                    DEBUG_MSG((stderr,"icmp_type/code=%d/%d",icp->icmp_type,icp->icmp_code));
                    dump_packet(probe->contents,probe->size);
                }
            } else if (res==0) {
                 DEBUG_MSG((stderr,"probe timeout"));
                 return BP_RES_TIMEOUT;
            } else {
                 DEBUG_MSG((stderr,"res=%d errno=%d",res,errno));
                 return res;
            }
            /*DEBUG_MSG((stderr,"*** retrying"));*/
        }
        break;
    }

    return ret;
}

int probe_close(bp_handle handle)
{
    bp_state_t* state;

    if (handle==NULL) {
    errno=EINVAL;
    return -1;
    }
    state=handle2state(handle);

    if (state->pattern!=NULL)
    free(state->pattern);
    icmp_close(state->icmp_handle);
    if (state->packet!=NULL)
    free(state->packet);
    free(state);
    return 0;
}
