Home | History | Annotate | Line # | Download | only in netstat
route.c revision 1.3
      1 /*
      2  * Copyright (c) 1983, 1988 Regents of the University of California.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  * 3. All advertising materials mentioning features or use of this software
     14  *    must display the following acknowledgement:
     15  *	This product includes software developed by the University of
     16  *	California, Berkeley and its contributors.
     17  * 4. Neither the name of the University nor the names of its contributors
     18  *    may be used to endorse or promote products derived from this software
     19  *    without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     31  * SUCH DAMAGE.
     32  */
     33 
     34 #ifndef lint
     35 static char sccsid[] = "@(#)route.c	5.20 (Berkeley) 11/29/90";
     36 #endif /* not lint */
     37 
     38 #include <sys/param.h>
     39 #include <sys/socket.h>
     40 #include <sys/mbuf.h>
     41 
     42 #include <net/if.h>
     43 #define  KERNEL
     44 #include <net/route.h>
     45 #undef KERNEL
     46 #include <netinet/in.h>
     47 
     48 #ifdef NS
     49 #include <netns/ns.h>
     50 #endif
     51 
     52 #include <netdb.h>
     53 #include <sys/kinfo.h>
     54 
     55 #include <stdio.h>
     56 #include <string.h>
     57 
     58 extern	int nflag, aflag, Aflag, af;
     59 int do_rtent;
     60 extern	char *routename(), *netname(), *plural();
     61 #ifdef NS
     62 extern	char *ns_print();
     63 #endif
     64 extern	char *malloc();
     65 #define kget(p, d) \
     66 	(kvm_read((off_t)(p), (char *)&(d), sizeof (d)))
     67 
     68 /*
     69  * Definitions for showing gateway flags.
     70  */
     71 struct bits {
     72 	short	b_mask;
     73 	char	b_val;
     74 } bits[] = {
     75 	{ RTF_UP,	'U' },
     76 	{ RTF_GATEWAY,	'G' },
     77 	{ RTF_HOST,	'H' },
     78 	{ RTF_DYNAMIC,	'D' },
     79 	{ RTF_MODIFIED,	'M' },
     80 	{ RTF_CLONING,	'C' },
     81 	{ RTF_XRESOLVE,	'X' },
     82 	{ RTF_LLINFO,	'L' },
     83 	{ RTF_REJECT,	'R' },
     84 	{ 0 }
     85 };
     86 
     87 /*
     88  * Print address family.
     89  */
     90 void
     91 p_proto(proto)
     92 	int	proto;
     93 {
     94 	switch (proto)
     95 	{
     96 	case AF_INET:
     97 		printf("inet");
     98 		break;
     99 	case AF_NS:
    100 		printf("ns");
    101 		break;
    102 	case AF_ISO:
    103 		printf("iso");
    104 		break;
    105 	case AF_CCITT:
    106 		printf("ccitt");
    107 		break;
    108 	default:
    109 		printf("%d", proto);
    110 		break;
    111 	}
    112 }
    113 
    114 
    115 /*
    116  * Print routing tables.
    117  */
    118 routepr(hostaddr, netaddr, hashsizeaddr, treeaddr)
    119 	off_t hostaddr, netaddr, hashsizeaddr, treeaddr;
    120 {
    121 	struct mbuf mb;
    122 	register struct ortentry *rt;
    123 	register struct mbuf *m;
    124 	char name[16], *flags;
    125 	struct mbuf **routehash;
    126 	int hashsize;
    127 	int i, doinghost = 1;
    128 
    129 	printf("Routing tables\n");
    130 	if (Aflag)
    131 		printf("%-8.8s ","Address");
    132 	printf("%-16.16s %-18.18s %-6.6s  %6.6s%8.8s  %s\n",
    133 		"Destination", "Gateway",
    134 		"Flags", "Refs", "Use", "Interface");
    135 	if (treeaddr)
    136 		return treestuff(treeaddr);
    137 	if (hostaddr == 0) {
    138 		printf("rthost: symbol not in namelist\n");
    139 		return;
    140 	}
    141 	if (netaddr == 0) {
    142 		printf("rtnet: symbol not in namelist\n");
    143 		return;
    144 	}
    145 	if (hashsizeaddr == 0) {
    146 		printf("rthashsize: symbol not in namelist\n");
    147 		return;
    148 	}
    149 	kget(hashsizeaddr, hashsize);
    150 	routehash = (struct mbuf **)malloc( hashsize*sizeof (struct mbuf *) );
    151 	kvm_read(hostaddr, (char *)routehash, hashsize*sizeof (struct mbuf *));
    152 again:
    153 	for (i = 0; i < hashsize; i++) {
    154 		if (routehash[i] == 0)
    155 			continue;
    156 		m = routehash[i];
    157 		while (m) {
    158 			kget(m, mb);
    159 			if (Aflag)
    160 				printf("%8.8x ", m);
    161 			p_ortentry((struct ortentry *)(mb.m_dat));
    162 			m = mb.m_next;
    163 		}
    164 	}
    165 	if (doinghost) {
    166 		kvm_read(netaddr, (char *)routehash,
    167 			hashsize*sizeof (struct mbuf *));
    168 		doinghost = 0;
    169 		goto again;
    170 	}
    171 	free((char *)routehash);
    172 	return;
    173 }
    174 
    175 static union {
    176 	struct	sockaddr u_sa;
    177 	u_short	u_data[128];
    178 } pt_u;
    179 int do_rtent = 0;
    180 struct rtentry rtentry;
    181 struct radix_node rnode;
    182 struct radix_mask rmask;
    183 
    184 int NewTree = 0;
    185 treestuff(rtree)
    186 off_t rtree;
    187 {
    188 	struct radix_node_head *rnh, head;
    189 
    190 	if (Aflag == 0 && NewTree)
    191 		return(ntreestuff());
    192 	for (kget(rtree, rnh); rnh; rnh = head.rnh_next) {
    193 		kget(rnh, head);
    194 		if (head.rnh_af == 0) {
    195 			if (Aflag || af == AF_UNSPEC) {
    196 				printf("Netmasks:\n");
    197 				p_tree(head.rnh_treetop);
    198 			}
    199 		} else if (af == AF_UNSPEC || af == head.rnh_af) {
    200 			printf("\nRoute Tree for Protocol Family ");
    201 			p_proto(head.rnh_af);
    202 			printf(":\n");
    203 			do_rtent = 1;
    204 			p_tree(head.rnh_treetop);
    205 		}
    206 	}
    207 }
    208 
    209 struct sockaddr *
    210 kgetsa(dst)
    211 register struct sockaddr *dst;
    212 {
    213 	kget(dst, pt_u.u_sa);
    214 	if (pt_u.u_sa.sa_len > sizeof (pt_u.u_sa)) {
    215 		kvm_read((off_t)dst, pt_u.u_data, pt_u.u_sa.sa_len);
    216 	}
    217 	return (&pt_u.u_sa);
    218 }
    219 
    220 p_tree(rn)
    221 struct radix_node *rn;
    222 {
    223 
    224 again:
    225 	kget(rn, rnode);
    226 	if (rnode.rn_b < 0) {
    227 		if (Aflag)
    228 			printf("%-8.8x ", rn);
    229 		if (rnode.rn_flags & RNF_ROOT)
    230 			printf("(root node)%s",
    231 				    rnode.rn_dupedkey ? " =>\n" : "\n");
    232 		else if (do_rtent) {
    233 			kget(rn, rtentry);
    234 			p_rtentry(&rtentry);
    235 			if (Aflag)
    236 				p_rtnode();
    237 		} else {
    238 			p_sockaddr(kgetsa((struct sockaddr *)rnode.rn_key),
    239 				    0, 44);
    240 			putchar('\n');
    241 		}
    242 		if (rn = rnode.rn_dupedkey)
    243 			goto again;
    244 	} else {
    245 		if (Aflag && do_rtent) {
    246 			printf("%-8.8x ", rn);
    247 			p_rtnode();
    248 		}
    249 		rn = rnode.rn_r;
    250 		p_tree(rnode.rn_l);
    251 		p_tree(rn);
    252 	}
    253 }
    254 char nbuf[20];
    255 
    256 p_rtnode()
    257 {
    258 
    259 	struct radix_mask *rm = rnode.rn_mklist;
    260 	if (rnode.rn_b < 0) {
    261 		if (rnode.rn_mask) {
    262 			printf("\t  mask ");
    263 			p_sockaddr(kgetsa((struct sockaddr *)rnode.rn_mask),
    264 				    0, -1);
    265 		} else if (rm == 0)
    266 			return;
    267 	} else {
    268 		sprintf(nbuf, "(%d)", rnode.rn_b);
    269 		printf("%6.6s %8.8x : %8.8x", nbuf, rnode.rn_l, rnode.rn_r);
    270 	}
    271 	while (rm) {
    272 		kget(rm, rmask);
    273 		sprintf(nbuf, " %d refs, ", rmask.rm_refs);
    274 		printf(" mk = %8.8x {(%d),%s",
    275 			rm, -1 - rmask.rm_b, rmask.rm_refs ? nbuf : " ");
    276 		p_sockaddr(kgetsa((struct sockaddr *)rmask.rm_mask), 0, -1);
    277 		putchar('}');
    278 		if (rm = rmask.rm_mklist)
    279 			printf(" ->");
    280 	}
    281 	putchar('\n');
    282 }
    283 
    284 ntreestuff()
    285 {
    286 	int needed;
    287 	char *buf, *next, *lim;
    288 	register struct rt_msghdr *rtm;
    289 
    290 	if ((needed = getkerninfo(KINFO_RT_DUMP, 0, 0, 0)) < 0)
    291 		{ perror("route-getkerninfo-estimate"); exit(1);}
    292 	if ((buf = malloc(needed)) == 0)
    293 		{ printf("out of space\n"); exit(1);}
    294 	if (getkerninfo(KINFO_RT_DUMP, buf, &needed, 0) < 0)
    295 		{ perror("actual retrieval of routing table"); exit(1);}
    296 	lim  = buf + needed;
    297 	for (next = buf; next < lim; next += rtm->rtm_msglen) {
    298 		rtm = (struct rt_msghdr *)next;
    299 		np_rtentry(rtm);
    300 	}
    301 }
    302 
    303 np_rtentry(rtm)
    304 register struct rt_msghdr *rtm;
    305 {
    306 	register struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
    307 	static int masks_done, old_af, banner_printed;
    308 	int af = 0, interesting = RTF_UP | RTF_GATEWAY | RTF_HOST;
    309 
    310 #ifdef notdef
    311 	/* for the moment, netmasks are skipped over */
    312 	if (!banner_printed) {
    313 		printf("Netmasks:\n");
    314 		banner_printed = 1;
    315 	}
    316 	if (masks_done == 0) {
    317 		if (rtm->rtm_addrs != RTA_DST ) {
    318 			masks_done = 1;
    319 			af = sa->sa_family;
    320 		}
    321 	} else
    322 #endif
    323 		af = sa->sa_family;
    324 	if (af != old_af) {
    325 		printf("\nRoute Tree for Protocol Family %d:\n", af);
    326 		old_af = af;
    327 	}
    328 	if (rtm->rtm_addrs == RTA_DST)
    329 		p_sockaddr(sa, 0, 36);
    330 	else {
    331 		p_sockaddr(sa, rtm->rtm_flags, 16);
    332 		if (sa->sa_len == 0)
    333 			sa->sa_len = sizeof(long);
    334 		sa = (struct sockaddr *)(sa->sa_len + (char *)sa);
    335 		p_sockaddr(sa, 0, 18);
    336 	}
    337 	p_flags(rtm->rtm_flags & interesting, "%-6.6s ");
    338 	putchar('\n');
    339 }
    340 
    341 p_sockaddr(sa, flags, width)
    342 struct sockaddr *sa;
    343 int flags, width;
    344 {
    345 	char format[20], workbuf[128], *cp, *cplim;
    346 	register char *cpout;
    347 
    348 	switch(sa->sa_family) {
    349 	case AF_INET:
    350 	    {
    351 		register struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    352 
    353 		cp = (sin->sin_addr.s_addr == 0) ? "default" :
    354 		      ((flags & RTF_HOST) ?
    355 			routename(sin->sin_addr) : netname(sin->sin_addr, 0L));
    356 	    }
    357 		break;
    358 
    359 #ifdef NS
    360 	case AF_NS:
    361 		cp = ns_print((struct sockaddr_ns *)sa);
    362 		break;
    363 #endif
    364 
    365 	default:
    366 	    {
    367 		register u_short *s = ((u_short *)sa->sa_data), *slim;
    368 
    369 		slim = (u_short *) sa + ((sa->sa_len + sizeof(u_short) - 1) /
    370 		    sizeof(u_short));
    371 		cp = workbuf;
    372 		cplim = cp + sizeof(workbuf) - 6;
    373 		cp += sprintf(cp, "(%d)", sa->sa_family);
    374 		while (s < slim && cp < cplim)
    375 			cp += sprintf(cp, " %x", *s++);
    376 		cp = workbuf;
    377 	    }
    378 	}
    379 	if (width < 0 )
    380 		printf("%s ", cp);
    381 	else {
    382 		if (nflag)
    383 			printf("%-*s ", width, cp);
    384 		else
    385 			printf("%-*.*s ", width, width, cp);
    386 	}
    387 }
    388 
    389 p_flags(f, format)
    390 register int f;
    391 char *format;
    392 {
    393 	char name[33], *flags;
    394 	register struct bits *p = bits;
    395 	for (flags = name; p->b_mask; p++)
    396 		if (p->b_mask & f)
    397 			*flags++ = p->b_val;
    398 	*flags = '\0';
    399 	printf(format, name);
    400 }
    401 
    402 p_rtentry(rt)
    403 register struct rtentry *rt;
    404 {
    405 	char name[16];
    406 	register struct sockaddr *sa;
    407 	struct ifnet ifnet;
    408 
    409 	p_sockaddr(kgetsa(rt_key(rt)), rt->rt_flags, 16);
    410 	p_sockaddr(kgetsa(rt->rt_gateway), RTF_HOST, 18);
    411 	p_flags(rt->rt_flags, "%-6.6s ");
    412 	printf("%6d %8d ", rt->rt_refcnt, rt->rt_use);
    413 	if (rt->rt_ifp == 0) {
    414 		putchar('\n');
    415 		return;
    416 	}
    417 	kget(rt->rt_ifp, ifnet);
    418 	kvm_read((off_t)ifnet.if_name, name, 16);
    419 	printf(" %.15s%d%s", name, ifnet.if_unit,
    420 		rt->rt_nodes[0].rn_dupedkey ? " =>\n" : "\n");
    421 }
    422 
    423 p_ortentry(rt)
    424 register struct ortentry *rt;
    425 {
    426 	char name[16], *flags;
    427 	register struct bits *p;
    428 	register struct sockaddr_in *sin;
    429 	struct ifnet ifnet;
    430 
    431 	p_sockaddr(&rt->rt_dst, rt->rt_flags, 16);
    432 	p_sockaddr(&rt->rt_gateway, 0, 18);
    433 	p_flags(rt->rt_flags, "%-6.6s ");
    434 	printf("%6d %8d ", rt->rt_refcnt, rt->rt_use);
    435 	if (rt->rt_ifp == 0) {
    436 		putchar('\n');
    437 		return;
    438 	}
    439 	kget(rt->rt_ifp, ifnet);
    440 	kvm_read((off_t)ifnet.if_name, name, 16);
    441 	printf(" %.15s%d\n", name, ifnet.if_unit);
    442 }
    443 
    444 char *
    445 routename(in)
    446 	struct in_addr in;
    447 {
    448 	register char *cp;
    449 	static char line[MAXHOSTNAMELEN + 1];
    450 	struct hostent *hp;
    451 	static char domain[MAXHOSTNAMELEN + 1];
    452 	static int first = 1;
    453 	char *index();
    454 
    455 	if (first) {
    456 		first = 0;
    457 		if (gethostname(domain, MAXHOSTNAMELEN) == 0 &&
    458 		    (cp = index(domain, '.')))
    459 			(void) strcpy(domain, cp + 1);
    460 		else
    461 			domain[0] = 0;
    462 	}
    463 	cp = 0;
    464 	if (!nflag) {
    465 		hp = gethostbyaddr((char *)&in, sizeof (struct in_addr),
    466 			AF_INET);
    467 		if (hp) {
    468 			if ((cp = index(hp->h_name, '.')) &&
    469 			    !strcmp(cp + 1, domain))
    470 				*cp = 0;
    471 			cp = hp->h_name;
    472 		}
    473 	}
    474 	if (cp)
    475 		strncpy(line, cp, sizeof(line) - 1);
    476 	else {
    477 #define C(x)	((x) & 0xff)
    478 		in.s_addr = ntohl(in.s_addr);
    479 		sprintf(line, "%u.%u.%u.%u", C(in.s_addr >> 24),
    480 			C(in.s_addr >> 16), C(in.s_addr >> 8), C(in.s_addr));
    481 	}
    482 	return (line);
    483 }
    484 
    485 /*
    486  * Return the name of the network whose address is given.
    487  * The address is assumed to be that of a net or subnet, not a host.
    488  */
    489 char *
    490 netname(in, mask)
    491 	struct in_addr in;
    492 	u_long mask;
    493 {
    494 	char *cp = 0;
    495 	static char line[MAXHOSTNAMELEN + 1];
    496 	struct netent *np = 0;
    497 	u_long net;
    498 	register i;
    499 	int subnetshift;
    500 
    501 	i = ntohl(in.s_addr);
    502 	if (!nflag && i) {
    503 		if (mask == 0) {
    504 			if (IN_CLASSA(i)) {
    505 				mask = IN_CLASSA_NET;
    506 				subnetshift = 8;
    507 			} else if (IN_CLASSB(i)) {
    508 				mask = IN_CLASSB_NET;
    509 				subnetshift = 8;
    510 			} else {
    511 				mask = IN_CLASSC_NET;
    512 				subnetshift = 4;
    513 			}
    514 			/*
    515 			 * If there are more bits than the standard mask
    516 			 * would suggest, subnets must be in use.
    517 			 * Guess at the subnet mask, assuming reasonable
    518 			 * width subnet fields.
    519 			 */
    520 			while (i &~ mask)
    521 				mask = (long)mask >> subnetshift;
    522 		}
    523 		net = i & mask;
    524 		while ((mask & 1) == 0)
    525 			mask >>= 1, net >>= 1;
    526 		np = getnetbyaddr(net, AF_INET);
    527 		if (np)
    528 			cp = np->n_name;
    529 	}
    530 	if (cp)
    531 		strncpy(line, cp, sizeof(line) - 1);
    532 	else if ((i & 0xffffff) == 0)
    533 		sprintf(line, "%u", C(i >> 24));
    534 	else if ((i & 0xffff) == 0)
    535 		sprintf(line, "%u.%u", C(i >> 24) , C(i >> 16));
    536 	else if ((i & 0xff) == 0)
    537 		sprintf(line, "%u.%u.%u", C(i >> 24), C(i >> 16), C(i >> 8));
    538 	else
    539 		sprintf(line, "%u.%u.%u.%u", C(i >> 24),
    540 			C(i >> 16), C(i >> 8), C(i));
    541 	return (line);
    542 }
    543 
    544 /*
    545  * Print routing statistics
    546  */
    547 rt_stats(off)
    548 	off_t off;
    549 {
    550 	struct rtstat rtstat;
    551 
    552 	if (off == 0) {
    553 		printf("rtstat: symbol not in namelist\n");
    554 		return;
    555 	}
    556 	kvm_read(off, (char *)&rtstat, sizeof (rtstat));
    557 	printf("routing:\n");
    558 	printf("\t%u bad routing redirect%s\n",
    559 		rtstat.rts_badredirect, plural(rtstat.rts_badredirect));
    560 	printf("\t%u dynamically created route%s\n",
    561 		rtstat.rts_dynamic, plural(rtstat.rts_dynamic));
    562 	printf("\t%u new gateway%s due to redirects\n",
    563 		rtstat.rts_newgateway, plural(rtstat.rts_newgateway));
    564 	printf("\t%u destination%s found unreachable\n",
    565 		rtstat.rts_unreach, plural(rtstat.rts_unreach));
    566 	printf("\t%u use%s of a wildcard route\n",
    567 		rtstat.rts_wildcard, plural(rtstat.rts_wildcard));
    568 }
    569 #ifdef NS
    570 short ns_nullh[] = {0,0,0};
    571 short ns_bh[] = {-1,-1,-1};
    572 
    573 char *
    574 ns_print(sns)
    575 struct sockaddr_ns *sns;
    576 {
    577 	struct ns_addr work;
    578 	union { union ns_net net_e; u_long long_e; } net;
    579 	u_short port;
    580 	static char mybuf[50], cport[10], chost[25];
    581 	char *host = "";
    582 	register char *p; register u_char *q;
    583 
    584 	work = sns->sns_addr;
    585 	port = ntohs(work.x_port);
    586 	work.x_port = 0;
    587 	net.net_e  = work.x_net;
    588 	if (ns_nullhost(work) && net.long_e == 0) {
    589 		if (port ) {
    590 			sprintf(mybuf, "*.%xH", port);
    591 			upHex(mybuf);
    592 		} else
    593 			sprintf(mybuf, "*.*");
    594 		return (mybuf);
    595 	}
    596 
    597 	if (bcmp(ns_bh, work.x_host.c_host, 6) == 0) {
    598 		host = "any";
    599 	} else if (bcmp(ns_nullh, work.x_host.c_host, 6) == 0) {
    600 		host = "*";
    601 	} else {
    602 		q = work.x_host.c_host;
    603 		sprintf(chost, "%02x%02x%02x%02x%02x%02xH",
    604 			q[0], q[1], q[2], q[3], q[4], q[5]);
    605 		for (p = chost; *p == '0' && p < chost + 12; p++);
    606 		host = p;
    607 	}
    608 	if (port)
    609 		sprintf(cport, ".%xH", htons(port));
    610 	else
    611 		*cport = 0;
    612 
    613 	sprintf(mybuf,"%xH.%s%s", ntohl(net.long_e), host, cport);
    614 	upHex(mybuf);
    615 	return(mybuf);
    616 }
    617 
    618 char *
    619 ns_phost(sns)
    620 struct sockaddr_ns *sns;
    621 {
    622 	struct sockaddr_ns work;
    623 	static union ns_net ns_zeronet;
    624 	char *p;
    625 
    626 	work = *sns;
    627 	work.sns_addr.x_port = 0;
    628 	work.sns_addr.x_net = ns_zeronet;
    629 
    630 	p = ns_print(&work);
    631 	if (strncmp("0H.", p, 3) == 0) p += 3;
    632 	return(p);
    633 }
    634 upHex(p0)
    635 char *p0;
    636 {
    637 	register char *p = p0;
    638 	for (; *p; p++) switch (*p) {
    639 
    640 	case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
    641 		*p += ('A' - 'a');
    642 	}
    643 }
    644 #endif /* NS */
    645