/* ICMP-related user commands
 * Copyright 1991 Phil Karn, KA9Q
 */
/* Mods by PA0GRI */
#include "global.h"
#include "icmp.h"
#include "ip.h"
#include "mbuf.h"
#include "netuser.h"
#include "internet.h"
#include "timer.h"
#include "socket.h"
#include "proc.h"
#include "session.h"
#include "cmdparse.h"
#include "commands.h"
  
static int doicmpec __ARGS((int argc, char *argv[],void *p));
static int doicmpstat __ARGS((int argc, char *argv[],void *p));
static int doicmptr __ARGS((int argc, char *argv[],void *p));
static int doicmpquench __ARGS((int argc, char *argv[],void *p));
static int doicmptimeexceed __ARGS((int argc, char *argv[],void *p));
static void pingtx __ARGS((int s,void *ping1,void *p));
static void pinghdr __ARGS((struct session *sp,struct ping *ping));
  
static struct cmds DFAR Icmpcmds[] = {
    "echo",         doicmpec,       0, 0, NULLCHAR,
    "quench",       doicmpquench, 0, 0, NULLCHAR,
    "status",       doicmpstat,     0, 0, NULLCHAR,
    "timeexceed",   doicmptimeexceed,0,0, NULLCHAR,
    "trace",        doicmptr,       0, 0, NULLCHAR,
    NULLCHAR
};
  
int Icmp_trace;
int Icmp_SQ = 1;
static int Icmp_echo = 1;
  
int
doicmp(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return subcmd(Icmpcmds,argc,argv,p);
}
  
static int
doicmpstat(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    register int i;
    int lim;
  
    /* Note that the ICMP variables are shown in column order, because
     * that lines up the In and Out variables on the same line
     */
    lim = NUMICMPMIB/2;
    for(i=1;i<=lim;i++){
        tprintf("(%2u)%-20s%10lu",i,Icmp_mib[i].name,
        Icmp_mib[i].value.integer);
        tprintf("     (%2u)%-20s%10lu\n",i+lim,Icmp_mib[i+lim].name,
        Icmp_mib[i+lim].value.integer);
    }
    return 0;
}
  
static int
doicmptr(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    if(argc < 2) {
        tprintf("ICMP Tracing is %d\n",Icmp_trace) ;
        return 0 ;
    }
  
    switch(argv[1][0]) {
        case '0':
        case '1':
        case '2': Icmp_trace=atoi(argv[1]);
            break;
        default:
            tputs("Trace modes are: 0|1|2\n") ;
            return -1 ;
    }
  
    return 0 ;
}
  
static int
doicmpec(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Icmp_echo,"ICMP echo response accept",argc,argv);
}
  
static int
doicmpquench(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Icmp_SQ,"Mem Low Source Quench",argc,argv);
}
  
int Icmp_timeexceed = 1;
static int
doicmptimeexceed(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    return setbool(&Icmp_timeexceed,"Ttl time exceed reply",argc,argv);
}
  
/* Send ICMP Echo Request packets */
int
doping(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct proc *pinger = NULLPROC; /* Transmit process */
    struct sockaddr_in from;
    struct icmp icmp;
    struct mbuf *bp;
    int32 timestamp,rtt,abserr;
    int s,fromlen;
    struct ping ping;
    struct session *sp;
  
    memset((char *)&ping,0,sizeof(ping));
    if(argc > 3) {
        /* Make sure this is a 'one-shot- ping
         * if not coming from the console - WG7J
         */
        if(Curproc->input != Command->input)
            return 0;
        ping.interval = atol(argv[3]);
    }
  
    if(argc > 2)
        ping.len = atoi(argv[2]);
  
    /* Optionally ping a range of IP addresses */
    if(argc > 4)
        ping.incflag = 1;
  
    if(ping.interval)
        /* Multi shot ping. Allocate a session descriptor */
        if((sp = ping.sp = newsession(argv[1],PING,0)) == NULLSESSION){
            tputs(TooManySessions);
            return 1;
        }
  
    tprintf("Resolving %s... ",argv[1]);
    usflush(Current->output);
  
    if((ping.target = resolve(argv[1])) == 0){
        tprintf(Badhost,argv[1]);
        if(ping.interval) {
            keywait(NULLCHAR,1);
            freesession(sp);
        }
        return 1;
    }
    if((s = socket(AF_INET,SOCK_RAW,ICMP_PTCL)) == -1){
        tputs(Nosock);
        if(ping.interval) {
            keywait(NULLCHAR,1);
            freesession(sp);
        }
        return 1;
    }
  
    tprintf(" pinging [%s]\n",inet_ntoa(ping.target));
    usflush(Current->output);
  
    if(ping.interval == 0) {
        /* One shot ping; let echo_proc hook handle response.
         * An ID of MAXINT16 will not be confused with a legal socket
         * number, which is used to identify repeated pings
         */
        pingem(s,ping.target,0,MAXINT16,ping.len);
        close_s(s);
        return 0;
    }
  
    sp->s = s;
  
    /* start the continuous pinger process */
    pinger = newproc("pingtx",300,pingtx,s,&ping,NULL,0);
  
    /* Now collect the replies */
    pinghdr(sp,&ping);
    for(;;){
        fromlen = sizeof(from);
        if(recv_mbuf(s,&bp,0,(char *)&from,&fromlen) == -1)
            break;
        ntohicmp(&icmp,&bp);
        if(icmp.type != ICMP_ECHO_REPLY || icmp.args.echo.id != s){
            /* Ignore other people's responses */
            free_p(bp);
            continue;
        }
        /* Get stamp */
        if(pullup(&bp,(char *)&timestamp,sizeof(timestamp))
        != sizeof(timestamp)){
            /* The timestamp is missing! */
            free_p(bp);     /* Probably not necessary */
            continue;
        }
        free_p(bp);
  
        ping.responses++;
  
        /* Compute round trip time, update smoothed estimates */
        rtt = msclock() - timestamp;
        abserr = (rtt > ping.srtt) ? (rtt-ping.srtt) : (ping.srtt-rtt);
  
        if(ping.responses == 1){
            /* First response, base entire SRTT on it */
            ping.srtt = rtt;
            ping.mdev = 0;
        } else {
            ping.srtt = (7*ping.srtt + rtt + 4) >> 3;
            ping.mdev = (3*ping.mdev + abserr + 2) >> 2;
        }
        if((ping.responses % 20) == 0)
            pinghdr(sp,&ping);
        tprintf("%10lu%10lu%5lu%10lu%10lu%10lu\n",
        ping.sent,ping.responses,
        (ping.responses*100 + ping.sent/2)/ping.sent,
        rtt,ping.srtt,ping.mdev);
    }
    if(pinger != NULLPROC)
        killproc(pinger);
    freesession(sp);
    return 0;
}
  
static void
pinghdr(sp,ping)
struct session *sp;
struct ping *ping;
{
    tprintf("Pinging %s (%s); data %d interval %lu ms:\n",
    sp->name,inet_ntoa(ping->target),ping->len,ping->interval);
    tputs("      sent      rcvd    %       rtt   avg rtt      mdev\n");
}
  
void
echo_proc(source,dest,icmp,bp)
int32 source;
int32 dest;
struct icmp *icmp;
struct mbuf *bp;
{
    int32 timestamp,rtt;
  
    if(Icmp_echo && icmp->args.echo.id == MAXINT16
        && pullup(&bp,(char *)&timestamp,sizeof(timestamp))
    == sizeof(timestamp)){
        /* Compute round trip time */
        rtt = msclock() - timestamp;
        tprintf("%s: rtt %lu\n",inet_ntoa(source),rtt);
    }
    free_p(bp);
}
/* Ping transmit process. Runs until killed */
static void
pingtx(s,ping1,p)
int s;          /* Socket to use */
void *ping1;
void *p;
{
    struct ping *ping;
  
    ping = (struct ping *)ping1;
    ping->sent = 0;
    if(ping->incflag){
        for(;;){
            pingem(s,ping->target++,0,MAXINT16,ping->len);
            ping->sent++;
            pause(ping->interval);
        }
    } else {
        for(;;){
            pingem(s,ping->target,(int16)ping->sent++,(int16)s,ping->len);
            pause(ping->interval);
        }
    }
}
  
  
/* Send ICMP Echo Request packet */
int
pingem(s,target,seq,id,len)
int s;          /* Raw socket on which to send ping */
int32 target;   /* Site to be pinged */
int16 seq;      /* ICMP Echo Request sequence number */
int16 id;       /* ICMP Echo Request ID */
int16 len;      /* Length of optional data field */
{
    struct mbuf *data;
    struct mbuf *bp;
    struct icmp icmp;
    struct sockaddr_in to;
    int32 clock;
  
    clock = msclock();
    data = ambufw((int16)(len+sizeof(clock)));
    data->cnt = len+sizeof(clock);
    /* Set optional data field, if any, to all 55's */
    if(len != 0)
        memset(data->data+sizeof(clock),0x55,len);
  
    /* Insert timestamp and build ICMP header */
    memcpy(data->data,(char *)&clock,sizeof(clock));
    icmpOutEchos++;
    icmpOutMsgs++;
    icmp.type = ICMP_ECHO;
    icmp.code = 0;
    icmp.args.echo.seq = seq;
    icmp.args.echo.id = id;
    if((bp = htonicmp(&icmp,data)) == NULLBUF){
        free_p(data);
        return 0;
    }
    to.sin_family = AF_INET;
    to.sin_addr.s_addr = target;
    send_mbuf(s,bp,0,(char *)&to,sizeof(to));
    return 0;
}
  
#if defined GATECMDS
/* Send ICMP Echo Request and wait for response
   For mailbox command 'p <hostid> [length]'
   WA3DSP 12/93
   Modified by WG7J to add a timeout value in seconds, 2/12/94
*/
int
dombping(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    struct sockaddr_in from;
    struct icmp icmp;
    struct mbuf *bp;
    struct route *rp;
    int32 timestamp,rtt;
    int s,fromlen,rval;
    int32 target;
    int16 len = 0;
    long timeout;
  
    tprintf("Resolving %s...",argv[1]);
    usflush(Curproc->output);
  
    if((target = resolve(argv[1])) == 0){
        tprintf(Badhost,argv[1]);
        return 0;
    }
    if((rp=rt_lookup(target)) == NULL && !ismyaddr(target)) {
        tprintf("No route to %s\n",argv[1]);
        return 0;
    }
    if((s = socket(AF_INET,SOCK_RAW,ICMP_PTCL)) == -1){
        tputs(Nosock);
        return 1;
    }
  
    tprintf(" pinging [%s]\n",inet_ntoa(target));
    usflush(Curproc->output);
  
    if(argc > 2)
        /* Set a maximum value for the data, so no-one can
         * wedge us by send a large data ping - WG7J
         */
        len = min(atoi(argv[2]),512);
    if(argc > 3)
        timeout = atol(argv[3]) * 1000L;
    else
        timeout = 30000L;
  
    pingem(s,target,0,(int16)s,len);
  
    for(;;){
        fromlen = sizeof(from);
        alarm(timeout);
        rval=recv_mbuf(s,&bp,0,(char *)&from,&fromlen);
        alarm(0L);
        if (rval == -1 || errno == EALARM) {
            tprintf("Timeout - No response\n\n");
            close_s(s);
            return 0;
        }
        ntohicmp(&icmp,&bp);
        if(icmp.type != ICMP_ECHO_REPLY || icmp.args.echo.id != s){
            /* Ignore other people's responses */
            free_p(bp);
            continue;
        }
        /* Get stamp */
        if(pullup(&bp,(char *)&timestamp,sizeof(timestamp))
        != sizeof(timestamp)){
            /* The timestamp is missing! */
            free_p(bp);     /* Probably not necessary */
            tprintf("Response Received - missing timestamp\n\n");
            close_s(s);
            return 0;
        } else {
            break;
        }
    }
    close_s(s);
    free_p(bp);
  
    /* Compute round trip time, update smoothed estimates */
    rtt = msclock() - timestamp;
    tprintf("[%s]  rtt %ld ms\n\n",inet_ntoa(target),rtt);
    return 0;
}
  
#endif  /* GATECMDS */
  
