|  | /* This file is part of the UCB release of Plan 9. It is subject to the license | 
|  | * terms in the LICENSE file found in the top-level directory of this | 
|  | * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No | 
|  | * part of the UCB release of Plan 9, including this file, may be copied, | 
|  | * modified, propagated, or distributed except according to the terms contained | 
|  | * in the LICENSE file. */ | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <parlib/parlib.h> | 
|  | #include <unistd.h> | 
|  | #include <signal.h> | 
|  | #include <iplib/iplib.h> | 
|  | #include <iplib/icmp.h> | 
|  | #include <ctype.h> | 
|  | #include <pthread.h> | 
|  | #include <parlib/spinlock.h> | 
|  | #include <parlib/timing.h> | 
|  | #include <parlib/tsc-compat.h> | 
|  | #include <parlib/printf-ext.h> | 
|  | #include <parlib/stdio.h> | 
|  | #include <parlib/alarm.h> | 
|  | #include <ndblib/ndb.h> | 
|  |  | 
|  | #define NR_MSG				4 | 
|  | #define SLEEPMS				1000 | 
|  | #define SECONDTSC			(get_tsc_freq()) | 
|  | #define MINUTETSC			(60 * SECONDTSC) | 
|  | #define BUFSIZE				(64 * 1024 + 512) | 
|  |  | 
|  | typedef struct Req Req; | 
|  | struct Req | 
|  | { | 
|  | uint16_t	seq;	/* sequence number */ | 
|  | uint64_t	tsctime;	/* time sent */ | 
|  | int64_t	rtt; | 
|  | int	ttl; | 
|  | int	replied; | 
|  | Req	 *next; | 
|  | }; | 
|  |  | 
|  | struct proto { | 
|  | int	version; | 
|  | char	*net; | 
|  | int	echocmd; | 
|  | int	echoreply; | 
|  | unsigned iphdrsz; | 
|  |  | 
|  | void	(*prreply)(Req *r, void *v); | 
|  | void	(*prlost)(uint16_t seq, void *v); | 
|  | }; | 
|  |  | 
|  |  | 
|  | Req	*first;		/* request list */ | 
|  | Req	*last;		/* ... */ | 
|  | struct spin_pdr_lock listlock = SPINPDR_INITIALIZER; | 
|  |  | 
|  | char *argv0; | 
|  |  | 
|  | int addresses; | 
|  | int debug; | 
|  | int done; | 
|  | int flood; | 
|  | int lostmsgs; | 
|  | int lostonly; | 
|  | int quiet; | 
|  | int rcvdmsgs; | 
|  | int pingrint; | 
|  | uint16_t firstseq; | 
|  | int64_t sum; | 
|  | int waittime = 5000; | 
|  |  | 
|  | static char *network, *target; | 
|  |  | 
|  | void lost(Req*, void*); | 
|  | void reply(Req*, void*); | 
|  |  | 
|  | static void usage(void) | 
|  | { | 
|  | fprintf(stderr, | 
|  | "usage: %s [-6alq] [-s msgsize] [-i millisecs] [-n #pings] dest\n", | 
|  | argv0); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | static void prlost4(uint16_t seq, void *v) | 
|  | { | 
|  | struct ip4hdr *ip4 = v; | 
|  |  | 
|  | printf("lost %u: %i -> %i\n", seq, ip4->src, ip4->dst); | 
|  | } | 
|  |  | 
|  | static void prlost6(uint16_t seq, void *v) | 
|  | { | 
|  | struct ip6hdr *ip6 = v; | 
|  |  | 
|  | printf("lost %u: %i -> %i\n", seq, ip6->src, ip6->dst); | 
|  | } | 
|  |  | 
|  | static void prreply4(Req *r, void *v) | 
|  | { | 
|  | struct ip4hdr *ip4 = v; | 
|  |  | 
|  | printf("%u: %i -> %i rtt %lld µs, avg rtt %lld µs, ttl = %d\n", | 
|  | r->seq - firstseq, ip4->src, ip4->dst, r->rtt, sum/rcvdmsgs, | 
|  | r->ttl); | 
|  | } | 
|  |  | 
|  | static void prreply6(Req *r, void *v) | 
|  | { | 
|  | struct ip6hdr *ip6 = v; | 
|  |  | 
|  | printf("%u: %i -> %i rtt %lld µs, avg rtt %lld µs, ttl = %d\n", | 
|  | r->seq - firstseq, ip6->src, ip6->dst, r->rtt, sum/rcvdmsgs, | 
|  | r->ttl); | 
|  | } | 
|  |  | 
|  | static struct proto v4pr = { | 
|  | 4,		"icmp", | 
|  | EchoRequest,	EchoReply, | 
|  | IPV4HDR_LEN, | 
|  | prreply4,	prlost4, | 
|  | }; | 
|  | static struct proto v6pr = { | 
|  | 6,		"icmpv6", | 
|  | EchoRequestV6,	EchoReplyV6, | 
|  | IPV6HDR_LEN, | 
|  | prreply6,	prlost6, | 
|  | }; | 
|  |  | 
|  | static struct proto *proto = &v4pr; | 
|  |  | 
|  |  | 
|  | struct icmphdr *geticmp(void *v) | 
|  | { | 
|  | char *p = v; | 
|  |  | 
|  | return (struct icmphdr *)(p + proto->iphdrsz); | 
|  | } | 
|  |  | 
|  | void clean(uint16_t seq, int64_t now, void *v) | 
|  | { | 
|  | int ttl; | 
|  | Req **l, *r; | 
|  |  | 
|  | ttl = 0; | 
|  | if (v) { | 
|  | if (proto->version == 4) | 
|  | ttl = ((struct ip4hdr *)v)->ttl; | 
|  | else | 
|  | ttl = ((struct ip6hdr *)v)->ttl; | 
|  | } | 
|  | spin_pdr_lock(&listlock); | 
|  | last = NULL; | 
|  | for(l = &first; *l; ){ | 
|  | r = *l; | 
|  | if(v && r->seq == seq){ | 
|  | r->rtt = ndiff(r->tsctime, now); | 
|  | r->ttl = ttl; | 
|  | reply(r, v); | 
|  | } | 
|  | if (now - r->tsctime > MINUTETSC) { | 
|  | *l = r->next; | 
|  | r->rtt = ndiff(r->tsctime, now); | 
|  | if(v) | 
|  | r->ttl = ttl; | 
|  | if(r->replied == 0) | 
|  | lost(r, v); | 
|  | free(r); | 
|  | }else{ | 
|  | last = r; | 
|  | l = &r->next; | 
|  | } | 
|  | } | 
|  | spin_pdr_unlock(&listlock); | 
|  | } | 
|  |  | 
|  | static uint8_t loopbacknet[IPaddrlen] = { | 
|  | 0, 0, 0, 0, | 
|  | 0, 0, 0, 0, | 
|  | 0, 0, 0xff, 0xff, | 
|  | 127, 0, 0, 0 | 
|  | }; | 
|  | static uint8_t loopbackmask[IPaddrlen] = { | 
|  | 0xff, 0xff, 0xff, 0xff, | 
|  | 0xff, 0xff, 0xff, 0xff, | 
|  | 0xff, 0xff, 0xff, 0xff, | 
|  | 0xff, 0, 0, 0 | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * find first ip addr suitable for proto and | 
|  | * that isn't the friggin loopback address. | 
|  | * deprecate link-local and multicast addresses. | 
|  | */ | 
|  | static int myipvnaddr(uint8_t *ip, struct proto *proto, char *net) | 
|  | { | 
|  | int ipisv4, wantv4; | 
|  | struct ipifc *nifc; | 
|  | struct iplifc *lifc; | 
|  | uint8_t mynet[IPaddrlen], linklocal[IPaddrlen]; | 
|  | static struct ipifc *ifc; | 
|  |  | 
|  | ipmove(linklocal, IPnoaddr); | 
|  | wantv4 = proto->version == 4; | 
|  | ifc = readipifc(net, ifc, -1); | 
|  | for(nifc = ifc; nifc; nifc = nifc->next) | 
|  | for(lifc = nifc->lifc; lifc; lifc = lifc->next){ | 
|  | maskip(lifc->ip, loopbackmask, mynet); | 
|  | if(ipcmp(mynet, loopbacknet) == 0) | 
|  | continue; | 
|  | if(ISIPV6MCAST(lifc->ip) || ISIPV6LINKLOCAL(lifc->ip)) { | 
|  | ipmove(linklocal, lifc->ip); | 
|  | continue; | 
|  | } | 
|  | ipisv4 = isv4(lifc->ip) != 0; | 
|  | if(ipcmp(lifc->ip, IPnoaddr) != 0 && wantv4 == ipisv4){ | 
|  | ipmove(ip, lifc->ip); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | /* no global unicast addrs found, fall back to link-local, if any */ | 
|  | ipmove(ip, linklocal); | 
|  | return ipcmp(ip, IPnoaddr) == 0? -1: 0; | 
|  | } | 
|  |  | 
|  | void sender(int fd, int msglen, int interval, int n) | 
|  | { | 
|  | int i, extra; | 
|  | uint16_t seq; | 
|  | char *buf = malloc(BUFSIZE); | 
|  | uint8_t me[IPaddrlen], mev4[IPv4addrlen]; | 
|  | struct icmphdr *icmp; | 
|  | Req *r; | 
|  |  | 
|  | firstseq = seq = rand(); | 
|  |  | 
|  | icmp = geticmp(buf); | 
|  | memset(buf, 0, proto->iphdrsz + ICMP_HDRSIZE); | 
|  | for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) | 
|  | buf[i] = i; | 
|  | icmp->type = proto->echocmd; | 
|  | icmp->code = 0; | 
|  |  | 
|  | /* arguably the kernel should fill in the right src addr. */ | 
|  | myipvnaddr(me, proto, network); | 
|  | if (proto->version == 4) { | 
|  | v6tov4(mev4, me); | 
|  | memmove(((struct ip4hdr *)buf)->src, mev4, IPv4addrlen); | 
|  | } else | 
|  | ipmove(((struct ip6hdr *)buf)->src, me); | 
|  | if (addresses) | 
|  | printf("\t%i -> %s\n", me, target); | 
|  |  | 
|  | if(pingrint != 0 && interval <= 0) | 
|  | pingrint = 0; | 
|  | extra = 0; | 
|  | for(i = 0; i < n; i++){ | 
|  | if(i != 0){ | 
|  | if(pingrint != 0) | 
|  | extra = rand(); | 
|  | /* uth_sleep takes seconds, interval is in ms */ | 
|  | uthread_usleep((interval + extra) * 1000); | 
|  | } | 
|  | r = calloc(sizeof *r, 1); | 
|  | if (r == NULL){ | 
|  | printf("out of memory? \n"); | 
|  | break; | 
|  | } | 
|  | hnputs(icmp->seq, seq); | 
|  | r->seq = seq; | 
|  | r->next = NULL; | 
|  | r->replied = 0; | 
|  | r->tsctime = read_tsc();	/* avoid early free in reply! */ | 
|  | spin_pdr_lock(&listlock); | 
|  | if(first == NULL) | 
|  | first = r; | 
|  | else | 
|  | last->next = r; | 
|  | last = r; | 
|  | spin_pdr_unlock(&listlock); | 
|  | r->tsctime = read_tsc(); | 
|  | if(write(fd, buf, msglen) < msglen){ | 
|  | fprintf(stderr, "%s: write failed: %r\n", argv0); | 
|  | return; | 
|  | } | 
|  | seq++; | 
|  | } | 
|  | done = 1; | 
|  | } | 
|  |  | 
|  | void rcvr(int fd, int msglen, int interval, int nmsg) | 
|  | { | 
|  | int i, n, munged; | 
|  | uint16_t x; | 
|  | int64_t now; | 
|  | uint8_t *buf = malloc(BUFSIZE); | 
|  | struct icmphdr *icmp; | 
|  | Req *r; | 
|  | struct alarm_waiter waiter; | 
|  |  | 
|  | init_awaiter(&waiter, alarm_abort_sysc); | 
|  | waiter.data = current_uthread; | 
|  |  | 
|  | sum = 0; | 
|  | while(lostmsgs+rcvdmsgs < nmsg){ | 
|  | /* arm to wake ourselves if the read doesn't connect in time */ | 
|  | set_awaiter_rel(&waiter, 1000 * | 
|  | ((nmsg - lostmsgs - rcvdmsgs) * interval | 
|  | + waittime)); | 
|  | set_alarm(&waiter); | 
|  | n = read(fd, buf, BUFSIZE); | 
|  | /* cancel immediately, so future syscalls don't get aborted */ | 
|  | unset_alarm(&waiter); | 
|  |  | 
|  | now = read_tsc(); | 
|  | if(n <= 0){	/* read interrupted - time to go */ | 
|  | /* Faking time being a minute in the future, so clean | 
|  | * marks our message as lost.  Note this will also end | 
|  | * up cancelling any other pending replies that would | 
|  | * have expired by then.  Whatever. */ | 
|  | clean(0, now + MINUTETSC, NULL); | 
|  | continue; | 
|  | } | 
|  | if(n < msglen){ | 
|  | printf("bad len %d/%d\n", n, msglen); | 
|  | continue; | 
|  | } | 
|  | icmp = geticmp(buf); | 
|  | munged = 0; | 
|  | for(i = proto->iphdrsz + ICMP_HDRSIZE; i < msglen; i++) | 
|  | if(buf[i] != (uint8_t)i) | 
|  | munged++; | 
|  | if(munged) | 
|  | printf("corrupted reply\n"); | 
|  | x = nhgets(icmp->seq); | 
|  | if(icmp->type != proto->echoreply || icmp->code != 0) { | 
|  | printf("bad type/code/seq %d/%d/%d (want %d/%d/%d)\n", | 
|  | icmp->type, icmp->code, x, | 
|  | proto->echoreply, 0, x); | 
|  | continue; | 
|  | } | 
|  | clean(x, now, buf); | 
|  | } | 
|  |  | 
|  | spin_pdr_lock(&listlock); | 
|  | for(r = first; r; r = r->next) | 
|  | if(r->replied == 0) | 
|  | lostmsgs++; | 
|  | spin_pdr_unlock(&listlock); | 
|  |  | 
|  | if(!quiet && lostmsgs) | 
|  | printf("%d out of %d messages lost\n", lostmsgs, | 
|  | lostmsgs+rcvdmsgs); | 
|  | } | 
|  |  | 
|  | static int | 
|  | isdottedquad(char *name) | 
|  | { | 
|  | int dot = 0, digit = 0; | 
|  |  | 
|  | for (; *name != '\0'; name++) | 
|  | if (*name == '.') | 
|  | dot++; | 
|  | else if (isdigit(*name)) | 
|  | digit++; | 
|  | else | 
|  | return 0; | 
|  | return dot && digit; | 
|  | } | 
|  |  | 
|  | static int | 
|  | isv6lit(char *name) | 
|  | { | 
|  | int colon = 0, hex = 0; | 
|  |  | 
|  | for (; *name != '\0'; name++) | 
|  | if (*name == ':') | 
|  | colon++; | 
|  | else if (isxdigit(*name)) | 
|  | hex++; | 
|  | else | 
|  | return 0; | 
|  | return colon; | 
|  | } | 
|  |  | 
|  | /* from /sys/src/libc/9sys/dial.c */ | 
|  |  | 
|  | enum | 
|  | { | 
|  | Maxstring	= 128, | 
|  | Maxpath		= 256, | 
|  | }; | 
|  |  | 
|  | typedef struct DS DS; | 
|  | struct DS { | 
|  | /* dist string */ | 
|  | char	buf[Maxstring]; | 
|  | char	*netdir; | 
|  | char	*proto; | 
|  | char	*rem; | 
|  |  | 
|  | /* other args */ | 
|  | char	*local; | 
|  | char	*dir; | 
|  | int	*cfdp; | 
|  | }; | 
|  |  | 
|  | /* | 
|  | *  parse a dial string | 
|  | */ | 
|  | static void | 
|  | _dial_string_parse(char *str, DS *ds) | 
|  | { | 
|  | char *p, *p2; | 
|  |  | 
|  | strncpy(ds->buf, str, Maxstring); | 
|  | ds->buf[Maxstring-1] = 0; | 
|  |  | 
|  | p = strchr(ds->buf, '!'); | 
|  | if(p == 0) { | 
|  | ds->netdir = 0; | 
|  | ds->proto = "net"; | 
|  | ds->rem = ds->buf; | 
|  | } else { | 
|  | if(*ds->buf != '/' && *ds->buf != '#'){ | 
|  | ds->netdir = 0; | 
|  | ds->proto = ds->buf; | 
|  | } else { | 
|  | for(p2 = p; *p2 != '/'; p2--) | 
|  | ; | 
|  | *p2++ = 0; | 
|  | ds->netdir = ds->buf; | 
|  | ds->proto = p2; | 
|  | } | 
|  | *p = 0; | 
|  | ds->rem = p + 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* end excerpt from /sys/src/libc/9sys/dial.c */ | 
|  |  | 
|  | /* side effect: sets network & target */ | 
|  | static int | 
|  | isv4name(char *name) | 
|  | { | 
|  | int r = 1; | 
|  | char *root, *ip, *pr; | 
|  | DS ds; | 
|  |  | 
|  | _dial_string_parse(name, &ds); | 
|  |  | 
|  | /* cope with leading /net.alt/icmp! and the like */ | 
|  | root = NULL; | 
|  | if (ds.netdir != NULL) { | 
|  | pr = strrchr(ds.netdir, '/'); | 
|  | if (pr == NULL) | 
|  | pr = ds.netdir; | 
|  | else { | 
|  | *pr++ = '\0'; | 
|  | root = ds.netdir; | 
|  | network = strdup(root); | 
|  | } | 
|  | if (strcmp(pr, v4pr.net) == 0) | 
|  | return 1; | 
|  | if (strcmp(pr, v6pr.net) == 0) | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* if it's a literal, it's obvious from syntax which proto it is */ | 
|  | free(target); | 
|  | target = strdup(ds.rem); | 
|  | if (isdottedquad(ds.rem)) | 
|  | return 1; | 
|  | else if (isv6lit(ds.rem)) | 
|  | return 0; | 
|  | /*we don't have cs.*/ | 
|  | /* map name to ip and look at its syntax */ | 
|  | ip = csgetvalue(root, "sys", ds.rem, "ip", NULL); | 
|  | if (ip == NULL) | 
|  | ip = csgetvalue(root, "dom", ds.rem, "ip", NULL); | 
|  | if (ip == NULL) | 
|  | ip = csgetvalue(root, "sys", ds.rem, "ipv6", NULL); | 
|  | if (ip == NULL) | 
|  | ip = csgetvalue(root, "dom", ds.rem, "ipv6", NULL); | 
|  | if (ip != NULL) | 
|  | r = isv4name(ip); | 
|  | free(ip); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /* Feel free to put these in a struct or something */ | 
|  | int fd, msglen, interval, nmsg; | 
|  |  | 
|  | void *rcvr_thread(void* arg) | 
|  | { | 
|  | rcvr(fd, msglen, interval, nmsg); | 
|  | printd(lostmsgs ? "lost messages" : ""); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int main(int argc, char **argv) | 
|  | { | 
|  | char *ds, ds_store[256]; | 
|  | int pid; | 
|  | pthread_t rcvr; | 
|  |  | 
|  | register_printf_specifier('i', printf_ipaddr, printf_ipaddr_info); | 
|  |  | 
|  | msglen = interval = 0; | 
|  | nmsg = NR_MSG; | 
|  |  | 
|  | argv0 = argv[0]; | 
|  | if (argc <= 1) | 
|  | usage(); | 
|  | argc--, argv++; | 
|  | while (**argv == '-'){ | 
|  | switch(argv[0][1]){ | 
|  | case '6': | 
|  | proto = &v6pr; | 
|  | break; | 
|  | case 'a': | 
|  | addresses = 1; | 
|  | break; | 
|  | case 'd': | 
|  | debug++; | 
|  | break; | 
|  | case 'f': | 
|  | flood = 1; | 
|  | break; | 
|  | case 'i': | 
|  | argc--,argv++; | 
|  | interval = atoi(*argv); | 
|  | if(interval < 0) | 
|  | usage(); | 
|  | break; | 
|  | case 'l': | 
|  | lostonly++; | 
|  | break; | 
|  | case 'n': | 
|  | argc--,argv++; | 
|  | nmsg = atoi(*argv); | 
|  | if(nmsg < 0) | 
|  | usage(); | 
|  | break; | 
|  | case 'q': | 
|  | quiet = 1; | 
|  | break; | 
|  | case 'r': | 
|  | pingrint = 1; | 
|  | break; | 
|  | case 's': | 
|  | argc--,argv++; | 
|  | msglen = atoi(*argv); | 
|  | break; | 
|  | case 'w': | 
|  | argc--,argv++; | 
|  | waittime = atoi(*argv); | 
|  | if(waittime < 0) | 
|  | usage(); | 
|  | break; | 
|  | default: | 
|  | usage(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | argc--,argv++; | 
|  | } | 
|  |  | 
|  | if(msglen < proto->iphdrsz + ICMP_HDRSIZE) | 
|  | msglen = proto->iphdrsz + ICMP_HDRSIZE; | 
|  | if(msglen < 64) | 
|  | msglen = 64; | 
|  | if(msglen >= 64*1024) | 
|  | msglen = 64*1024-1; | 
|  | if(interval <= 0 && !flood) | 
|  | interval = SLEEPMS; | 
|  |  | 
|  | if(argc < 1) | 
|  | usage(); | 
|  |  | 
|  | /* TODO: consider catching ctrl-c and other signals. */ | 
|  |  | 
|  | if (!isv4name(argv[0])) | 
|  | proto = &v6pr; | 
|  | ds = netmkaddr(argv[0], proto->net, "1", ds_store, sizeof(ds_store)); | 
|  | printf("ping: dial %s\n", ds); | 
|  | fd = dial9(ds, 0, 0, 0, 0); | 
|  | if(fd < 0){ | 
|  | fprintf(stderr, "%s: couldn't dial %s: %r\n", argv0, ds); | 
|  | exit(1); | 
|  | } | 
|  |  | 
|  | if (!quiet) | 
|  | printf("sending %d %d byte messages %d ms apart to %s\n", | 
|  | nmsg, msglen, interval, ds); | 
|  |  | 
|  | /* Spawning the receiver on a separate thread, possibly separate core */ | 
|  | if (pthread_create(&rcvr, NULL, &rcvr_thread, NULL)) { | 
|  | perror("Failed to create recevier"); | 
|  | exit(-1); | 
|  | } | 
|  | sender(fd, msglen, interval, nmsg); | 
|  | /* races with prints from the rcvr.  either lock, or live with it! */ | 
|  | printd("Sent, now joining\n"); | 
|  | pthread_join(rcvr, NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Note: this gets called from clean, which happens when we complete a loop and | 
|  | * got a reply in the receiver.  One problem is that we call printf from the | 
|  | * receive loop, blocking all future receivers, so that their times include the | 
|  | * printf time.  This isn't a huge issue, since the sender sleeps between each | 
|  | * one, and hopefully the print is done when the sender fires again. */ | 
|  | void reply(Req *r, void *v) | 
|  | { | 
|  | r->rtt /= 1000LL; | 
|  | sum += r->rtt; | 
|  | if(!r->replied) | 
|  | rcvdmsgs++; | 
|  | if(!quiet && !lostonly) | 
|  | if(addresses) | 
|  | (*proto->prreply)(r, v); | 
|  | else | 
|  | printf("%3d: rtt %5lld usec, avg rtt %5lld usec, ttl = %d\n", | 
|  | r->seq - firstseq, r->rtt, sum/rcvdmsgs, | 
|  | r->ttl); | 
|  | r->replied = 1; | 
|  | } | 
|  |  | 
|  | void lost(Req *r, void *v) | 
|  | { | 
|  | if(!quiet) | 
|  | if(addresses && v != NULL) | 
|  | (*proto->prlost)(r->seq - firstseq, v); | 
|  | else | 
|  | printf("%3d: lost\n", r->seq - firstseq); | 
|  | lostmsgs++; | 
|  | } |