xref: /onnv-gate/usr/src/cmd/cmd-inet/usr.sbin/ifconfig/revarp.c (revision 3628:98d9a6c27bd3)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*3628Sss150715  * Common Development and Distribution License (the "License").
6*3628Sss150715  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
22*3628Sss150715  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  */
250Sstevel@tonic-gate /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
260Sstevel@tonic-gate /*	  All Rights Reserved	*/
270Sstevel@tonic-gate 
280Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
290Sstevel@tonic-gate 
300Sstevel@tonic-gate #include "defs.h"
310Sstevel@tonic-gate #include "ifconfig.h"
320Sstevel@tonic-gate #include <sys/types.h>
330Sstevel@tonic-gate #include <libdlpi.h>
340Sstevel@tonic-gate #include <sys/sysmacros.h>
35*3628Sss150715 #include <sys/time.h>
360Sstevel@tonic-gate #include <deflt.h>
370Sstevel@tonic-gate 
380Sstevel@tonic-gate #define	IPADDRL		sizeof (struct in_addr)
390Sstevel@tonic-gate #define	RARPRETRIES	5
40*3628Sss150715 #define	MSEC2NSEC(msec)	((msec) * 1000000)
41*3628Sss150715 #define	NSEC2MSEC(nsec)	((nsec) / 1000000)
420Sstevel@tonic-gate 
430Sstevel@tonic-gate /*
440Sstevel@tonic-gate  * The following value (8) is determined to work reliably in switched 10/100MB
450Sstevel@tonic-gate  * ethernet environments. Use caution if you plan on decreasing it.
460Sstevel@tonic-gate  */
470Sstevel@tonic-gate #define	RARPTIMEOUT	8
480Sstevel@tonic-gate 
490Sstevel@tonic-gate static char	defaultfile[] = "/etc/inet/rarp";
500Sstevel@tonic-gate static char	retries_var[] = "RARP_RETRIES=";
510Sstevel@tonic-gate static int rarp_timeout = RARPTIMEOUT;
520Sstevel@tonic-gate static int rarp_retries = RARPRETRIES;
530Sstevel@tonic-gate 
54*3628Sss150715 static dlpi_handle_t rarp_open(const char *, size_t *, uchar_t *, uchar_t *);
55*3628Sss150715 static int rarp_recv(dlpi_handle_t, struct arphdr *, size_t, size_t, int64_t);
560Sstevel@tonic-gate 
570Sstevel@tonic-gate int
58*3628Sss150715 doifrevarp(const char *linkname, struct sockaddr_in *laddr)
590Sstevel@tonic-gate {
60*3628Sss150715 	int			s, retval;
610Sstevel@tonic-gate 	struct arphdr		*req, *ans;
620Sstevel@tonic-gate 	struct in_addr		from;
630Sstevel@tonic-gate 	struct in_addr		answer;
640Sstevel@tonic-gate 	struct lifreq		lifr;
650Sstevel@tonic-gate 	int			tries_left;
66*3628Sss150715 	size_t			physaddrlen, ifrarplen;
67*3628Sss150715 	uchar_t			my_macaddr[DLPI_PHYSADDR_MAX];
68*3628Sss150715 	uchar_t 		my_broadcast[DLPI_PHYSADDR_MAX];
69*3628Sss150715 	dlpi_handle_t		dh;
700Sstevel@tonic-gate 
71*3628Sss150715 	if (linkname[0] == '\0') {
720Sstevel@tonic-gate 		(void) fprintf(stderr, "ifconfig: doifrevarp: name not set\n");
730Sstevel@tonic-gate 		exit(1);
740Sstevel@tonic-gate 	}
750Sstevel@tonic-gate 
760Sstevel@tonic-gate 	if (debug)
77*3628Sss150715 		(void) printf("doifrevarp interface %s\n", linkname);
780Sstevel@tonic-gate 
79*3628Sss150715 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
800Sstevel@tonic-gate 		Perror0_exit("socket");
81*3628Sss150715 
82*3628Sss150715 	(void) strlcpy(lifr.lifr_name, linkname, sizeof (lifr.lifr_name));
83*3628Sss150715 	if (ioctl(s, SIOCGLIFFLAGS, (char *)&lifr) < 0) {
84*3628Sss150715 		(void) close(s);
85*3628Sss150715 		Perror0_exit("SIOCGLIFFLAGS");
860Sstevel@tonic-gate 	}
870Sstevel@tonic-gate 
880Sstevel@tonic-gate 	/* don't try to revarp if we know it won't work */
890Sstevel@tonic-gate 	if ((lifr.lifr_flags & IFF_LOOPBACK) ||
900Sstevel@tonic-gate 	    (lifr.lifr_flags & IFF_NOARP) ||
91*3628Sss150715 	    (lifr.lifr_flags & IFF_POINTOPOINT)) {
92*3628Sss150715 		(void) close(s);
930Sstevel@tonic-gate 		return (0);
94*3628Sss150715 	}
950Sstevel@tonic-gate 
960Sstevel@tonic-gate 	/* open rarp interface */
97*3628Sss150715 	dh = rarp_open(linkname, &physaddrlen, my_macaddr, my_broadcast);
98*3628Sss150715 	if (dh == NULL) {
99*3628Sss150715 		(void) close(s);
1000Sstevel@tonic-gate 		return (0);
101*3628Sss150715 	}
1020Sstevel@tonic-gate 
1030Sstevel@tonic-gate 	/*
1040Sstevel@tonic-gate 	 * RARP looks at /etc/ethers and NIS, which only works
1050Sstevel@tonic-gate 	 * with 6 byte addresses currently.
1060Sstevel@tonic-gate 	 */
107*3628Sss150715 	if (physaddrlen != ETHERADDRL) {
108*3628Sss150715 		dlpi_close(dh);
109*3628Sss150715 		(void) close(s);
1100Sstevel@tonic-gate 		return (0);
1110Sstevel@tonic-gate 	}
1120Sstevel@tonic-gate 
113*3628Sss150715 	ifrarplen = sizeof (struct arphdr) + (2 * IPADDRL) + (2 * physaddrlen);
1140Sstevel@tonic-gate 
1150Sstevel@tonic-gate 	/* look for adjustments to rarp_retries in the RARP defaults file */
1160Sstevel@tonic-gate 	if (defopen(defaultfile) == 0) {
1170Sstevel@tonic-gate 		char	*cp;
1180Sstevel@tonic-gate 
1190Sstevel@tonic-gate 		if (cp = defread(retries_var)) {
1200Sstevel@tonic-gate 			int	ntries;
1210Sstevel@tonic-gate 
1220Sstevel@tonic-gate 			ntries = atoi(cp);
1230Sstevel@tonic-gate 			if (ntries > 0)
1240Sstevel@tonic-gate 				rarp_retries = ntries;
1250Sstevel@tonic-gate 		}
1260Sstevel@tonic-gate 		(void) defopen(NULL);	/* close default file */
1270Sstevel@tonic-gate 	}
1280Sstevel@tonic-gate 
1290Sstevel@tonic-gate 	/* allocate request and response buffers */
130*3628Sss150715 	if (((req = malloc(ifrarplen)) == NULL) ||
131*3628Sss150715 	    ((ans = malloc(ifrarplen)) == NULL)) {
132*3628Sss150715 		dlpi_close(dh);
133*3628Sss150715 		(void) close(s);
1340Sstevel@tonic-gate 		free(req);
1350Sstevel@tonic-gate 		return (0);
1360Sstevel@tonic-gate 	}
1370Sstevel@tonic-gate 
1380Sstevel@tonic-gate 	/* create rarp request */
1390Sstevel@tonic-gate 	(void) memset(req, 0, ifrarplen);
1400Sstevel@tonic-gate 	req->ar_hrd = htons(ARPHRD_ETHER);
1410Sstevel@tonic-gate 	req->ar_pro = htons(ETHERTYPE_IP);
142*3628Sss150715 	req->ar_hln = physaddrlen;
1430Sstevel@tonic-gate 	req->ar_pln = IPADDRL;
1440Sstevel@tonic-gate 	req->ar_op = htons(REVARP_REQUEST);
1450Sstevel@tonic-gate 
146*3628Sss150715 	(void) memcpy(&req[1], my_macaddr, physaddrlen);
1470Sstevel@tonic-gate 	(void) memcpy((uchar_t *)req + sizeof (struct arphdr) + IPADDRL +
148*3628Sss150715 	    physaddrlen, my_macaddr, physaddrlen);
1490Sstevel@tonic-gate 
150*3628Sss150715 	for (tries_left = rarp_retries; tries_left > 0; --tries_left) {
151*3628Sss150715 		/* send the request */
152*3628Sss150715 		retval = dlpi_send(dh, my_broadcast, physaddrlen, req,
153*3628Sss150715 		    ifrarplen, NULL);
154*3628Sss150715 		if (retval != DLPI_SUCCESS) {
155*3628Sss150715 			Perrdlpi("doifrevarp: cannot send rarp request",
156*3628Sss150715 			    linkname, retval);
157*3628Sss150715 			break;
1580Sstevel@tonic-gate 		}
1590Sstevel@tonic-gate 
160*3628Sss150715 		if (debug)
161*3628Sss150715 			(void) printf("rarp sent\n");
162*3628Sss150715 
163*3628Sss150715 		retval = rarp_recv(dh, ans, ifrarplen, physaddrlen,
164*3628Sss150715 		    rarp_timeout * MILLISEC);
165*3628Sss150715 
166*3628Sss150715 		if (retval != DLPI_ETIMEDOUT)
167*3628Sss150715 			break;
1680Sstevel@tonic-gate 
169*3628Sss150715 		if (debug)
170*3628Sss150715 			(void) printf("rarp retry\n");
171*3628Sss150715 	}
172*3628Sss150715 
173*3628Sss150715 	if (retval == DLPI_SUCCESS) {
174*3628Sss150715 		(void) memcpy(&answer, (uchar_t *)ans +
175*3628Sss150715 		    sizeof (struct arphdr) + (2 * physaddrlen) + IPADDRL,
176*3628Sss150715 		    sizeof (answer));
177*3628Sss150715 		(void) memcpy(&from, (uchar_t *)ans + physaddrlen +
178*3628Sss150715 		    sizeof (struct arphdr), sizeof (from));
1790Sstevel@tonic-gate 
1800Sstevel@tonic-gate 		if (debug) {
181*3628Sss150715 			(void) printf("answer: %s", inet_ntoa(answer));
182*3628Sss150715 			(void) printf(" [from %s]\n", inet_ntoa(from));
1830Sstevel@tonic-gate 		}
184*3628Sss150715 		laddr->sin_addr = answer;
185*3628Sss150715 	} else if (debug) {
186*3628Sss150715 		Perrdlpi("doifrevarp: could not receive rarp reply",
187*3628Sss150715 		    linkname, retval);
188*3628Sss150715 	}
1890Sstevel@tonic-gate 
190*3628Sss150715 	dlpi_close(dh);
191*3628Sss150715 	(void) close(s);
1920Sstevel@tonic-gate 	free(req);
1930Sstevel@tonic-gate 	free(ans);
194*3628Sss150715 	return (retval == DLPI_SUCCESS);
1950Sstevel@tonic-gate }
1960Sstevel@tonic-gate 
1970Sstevel@tonic-gate /*
1980Sstevel@tonic-gate  * Open the datalink provider device and bind to the REVARP type.
199*3628Sss150715  * Return the resulting DLPI handle.
2000Sstevel@tonic-gate  */
201*3628Sss150715 static	dlpi_handle_t
202*3628Sss150715 rarp_open(const char *linkname, size_t *alen, uchar_t *myaddr, uchar_t *mybaddr)
2030Sstevel@tonic-gate {
204*3628Sss150715 	int		retval;
205*3628Sss150715 	char		*physaddr, *bcastaddr;
206*3628Sss150715 	dlpi_info_t	dlinfo;
207*3628Sss150715 	dlpi_handle_t	dh;
2080Sstevel@tonic-gate 
2090Sstevel@tonic-gate 	if (debug)
210*3628Sss150715 		(void) printf("rarp_open %s\n", linkname);
2110Sstevel@tonic-gate 
212*3628Sss150715 	if ((retval = dlpi_open(linkname, &dh, 0)) != DLPI_SUCCESS) {
213*3628Sss150715 		Perrdlpi("rarp_open: dlpi_open failed", linkname, retval);
214*3628Sss150715 		return (NULL);
2150Sstevel@tonic-gate 	}
2160Sstevel@tonic-gate 
217*3628Sss150715 	if ((retval = dlpi_bind(dh, ETHERTYPE_REVARP, NULL)) != DLPI_SUCCESS) {
218*3628Sss150715 		Perrdlpi("rarp_open: dlpi_bind failed", linkname, retval);
2190Sstevel@tonic-gate 		goto failed;
2200Sstevel@tonic-gate 	}
2210Sstevel@tonic-gate 
222*3628Sss150715 	if ((retval = dlpi_info(dh, &dlinfo, 0)) != DLPI_SUCCESS) {
223*3628Sss150715 		Perrdlpi("rarp_open: dlpi_info failed", linkname, retval);
2240Sstevel@tonic-gate 		goto failed;
2250Sstevel@tonic-gate 	}
2260Sstevel@tonic-gate 
227*3628Sss150715 	if (dlinfo.di_bcastaddrlen == 0) {
228*3628Sss150715 		(void) fprintf(stderr, "ifconfig: rarp_open: %s broadcast "
229*3628Sss150715 		    "not supported\n", linkname);
2300Sstevel@tonic-gate 		goto failed;
2310Sstevel@tonic-gate 	}
2320Sstevel@tonic-gate 
233*3628Sss150715 	/* we assume the following are equal and fill in 'alen' */
234*3628Sss150715 	assert(dlinfo.di_bcastaddrlen == dlinfo.di_physaddrlen);
235*3628Sss150715 
236*3628Sss150715 	(void) memcpy(mybaddr, dlinfo.di_bcastaddr, dlinfo.di_bcastaddrlen);
237*3628Sss150715 
238*3628Sss150715 	*alen = dlinfo.di_physaddrlen;
239*3628Sss150715 
240*3628Sss150715 	(void) memcpy(myaddr, dlinfo.di_physaddr, dlinfo.di_physaddrlen);
241*3628Sss150715 
2420Sstevel@tonic-gate 	if (debug) {
243*3628Sss150715 		bcastaddr = _link_ntoa(mybaddr, NULL, dlinfo.di_bcastaddrlen,
244*3628Sss150715 		    IFT_OTHER);
245*3628Sss150715 
246*3628Sss150715 		physaddr = _link_ntoa(myaddr, NULL, dlinfo.di_physaddrlen,
247*3628Sss150715 		    IFT_OTHER);
248*3628Sss150715 
249*3628Sss150715 		if (physaddr != NULL && bcastaddr != NULL) {
250*3628Sss150715 			(void) printf("device %s: broadcast address %s, mac "
251*3628Sss150715 			    "address %s\n", linkname, bcastaddr, physaddr);
252*3628Sss150715 		}
253*3628Sss150715 
254*3628Sss150715 		free(physaddr);
255*3628Sss150715 		free(bcastaddr);
256*3628Sss150715 
257*3628Sss150715 		(void) printf("rarp_open: addr length = %d\n",
258*3628Sss150715 		    dlinfo.di_physaddrlen);
259*3628Sss150715 	}
260*3628Sss150715 
261*3628Sss150715 	return (dh);
262*3628Sss150715 
263*3628Sss150715 failed:
264*3628Sss150715 	dlpi_close(dh);
265*3628Sss150715 	return (NULL);
266*3628Sss150715 }
267*3628Sss150715 
268*3628Sss150715 /*
269*3628Sss150715  * Read reply for RARP request. If a reply is received within waitms,
270*3628Sss150715  * validate the reply. If it is a correct RARP reply return DLPI_SUCCESS,
271*3628Sss150715  * otherwise return DLPI_ETIMEDOUT. If there is an error while reading retrun
272*3628Sss150715  * the error code.
273*3628Sss150715  */
274*3628Sss150715 static int
275*3628Sss150715 rarp_recv(dlpi_handle_t dh, struct arphdr *ans, size_t msglen,
276*3628Sss150715     size_t physaddrlen, int64_t waitms)
277*3628Sss150715 {
278*3628Sss150715 	int		retval;
279*3628Sss150715 	char		*cause;
280*3628Sss150715 	size_t		anslen = msglen;
281*3628Sss150715 	hrtime_t	endtime = gethrtime() + MSEC2NSEC(waitms);
282*3628Sss150715 	hrtime_t	currtime;
283*3628Sss150715 
284*3628Sss150715 	while ((currtime = gethrtime()) < endtime) {
285*3628Sss150715 		waitms = NSEC2MSEC(endtime - currtime);
286*3628Sss150715 		retval = dlpi_recv(dh, NULL, NULL, ans, &anslen, waitms, NULL);
287*3628Sss150715 		if (retval == DLPI_SUCCESS) {
288*3628Sss150715 			cause = NULL;
289*3628Sss150715 
290*3628Sss150715 			if (anslen < msglen)
291*3628Sss150715 				cause = "short packet";
292*3628Sss150715 			else if (ans->ar_hrd != htons(ARPHRD_ETHER))
293*3628Sss150715 				cause = "hardware type not Ethernet";
294*3628Sss150715 			else if (ans->ar_pro != htons(ETHERTYPE_IP))
295*3628Sss150715 				cause = "protocol type not IP";
296*3628Sss150715 			else if (ans->ar_hln != physaddrlen)
297*3628Sss150715 				cause = "unexpected hardware address length";
298*3628Sss150715 			else if (ans->ar_pln != IPADDRL)
299*3628Sss150715 				cause = "unexpected protocol address length";
300*3628Sss150715 			if (cause != NULL) {
301*3628Sss150715 				(void) fprintf(stderr, "RARP packet received "
302*3628Sss150715 				    "but discarded (%s)\n", cause);
303*3628Sss150715 				continue;
304*3628Sss150715 			}
305*3628Sss150715 			switch (ntohs(ans->ar_op)) {
306*3628Sss150715 			case REVARP_REQUEST:
307*3628Sss150715 				if (debug)
308*3628Sss150715 					(void) printf("Got a rarp request.\n");
309*3628Sss150715 				break;
310*3628Sss150715 
311*3628Sss150715 			case REVARP_REPLY:
312*3628Sss150715 				return (DLPI_SUCCESS);
313*3628Sss150715 
314*3628Sss150715 			default:
315*3628Sss150715 				(void) fprintf(stderr, "ifconfig: unknown "
316*3628Sss150715 				    "RARP opcode 0x%x\n", ans->ar_op);
317*3628Sss150715 				break;
318*3628Sss150715 			}
319*3628Sss150715 		} else if (retval != DLPI_ETIMEDOUT) {
320*3628Sss150715 			Perrdlpi("doifrevarp: dlpi_recv failed",
321*3628Sss150715 			    dlpi_linkname(dh), retval);
322*3628Sss150715 			return (retval);
3230Sstevel@tonic-gate 		}
3240Sstevel@tonic-gate 	}
3250Sstevel@tonic-gate 
326*3628Sss150715 	return (DLPI_ETIMEDOUT);
3270Sstevel@tonic-gate }
3280Sstevel@tonic-gate 
3290Sstevel@tonic-gate int
330*3628Sss150715 dlpi_set_address(const char *linkname, uchar_t *physaddr, uint_t physaddrlen)
3310Sstevel@tonic-gate {
332*3628Sss150715 	int		retval;
333*3628Sss150715 	dlpi_handle_t	dh;
3340Sstevel@tonic-gate 
335*3628Sss150715 	if ((retval = dlpi_open(linkname, &dh, 0)) != DLPI_SUCCESS) {
336*3628Sss150715 		Perrdlpi("dlpi_open failed", linkname, retval);
3370Sstevel@tonic-gate 		return (-1);
3380Sstevel@tonic-gate 	}
3390Sstevel@tonic-gate 
340*3628Sss150715 	if ((retval = dlpi_set_physaddr(dh, DL_CURR_PHYS_ADDR, physaddr,
341*3628Sss150715 	    physaddrlen)) != DLPI_SUCCESS) {
342*3628Sss150715 		Perrdlpi("dlpi_set_physaddr failed", linkname, retval);
343*3628Sss150715 		dlpi_close(dh);
3440Sstevel@tonic-gate 		return (-1);
3450Sstevel@tonic-gate 	}
3460Sstevel@tonic-gate 
347*3628Sss150715 	dlpi_close(dh);
3480Sstevel@tonic-gate 	return (0);
3490Sstevel@tonic-gate }
3500Sstevel@tonic-gate 
3510Sstevel@tonic-gate void
352*3628Sss150715 dlpi_print_address(const char *linkname)
3530Sstevel@tonic-gate {
354*3628Sss150715 	uint_t	physaddrlen = DLPI_PHYSADDR_MAX;
355*3628Sss150715 	uchar_t	physaddr[DLPI_PHYSADDR_MAX];
356*3628Sss150715 	char	*str;
357*3628Sss150715 	int	retv;
358*3628Sss150715 	dlpi_handle_t	dh;
359*3628Sss150715 	dlpi_info_t	dlinfo;
3600Sstevel@tonic-gate 
361*3628Sss150715 	if (dlpi_open(linkname, &dh, 0) != DLPI_SUCCESS) {
3620Sstevel@tonic-gate 		/* Do not report an error */
3630Sstevel@tonic-gate 		return;
3640Sstevel@tonic-gate 	}
3650Sstevel@tonic-gate 
366*3628Sss150715 	retv = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, physaddr, &physaddrlen);
367*3628Sss150715 	if (retv != DLPI_SUCCESS) {
368*3628Sss150715 		Perrdlpi("dlpi_get_physaddr failed", linkname, retv);
369*3628Sss150715 		dlpi_close(dh);
37013Syw138387 		return;
3710Sstevel@tonic-gate 	}
3720Sstevel@tonic-gate 
373*3628Sss150715 	retv = dlpi_info(dh, &dlinfo, 0);
374*3628Sss150715 	if (retv != DLPI_SUCCESS) {
375*3628Sss150715 		Perrdlpi("dlpi_info failed", linkname, retv);
376*3628Sss150715 		dlpi_close(dh);
377*3628Sss150715 		return;
3780Sstevel@tonic-gate 	}
379*3628Sss150715 	dlpi_close(dh);
3800Sstevel@tonic-gate 
381*3628Sss150715 	str = _link_ntoa(physaddr, NULL, physaddrlen, IFT_OTHER);
3820Sstevel@tonic-gate 
3830Sstevel@tonic-gate 	if (str != NULL) {
384*3628Sss150715 		switch (dlinfo.di_mactype) {
3850Sstevel@tonic-gate 			case DL_IB:
3860Sstevel@tonic-gate 				(void) printf("\tipib %s \n", str);
3870Sstevel@tonic-gate 				break;
3880Sstevel@tonic-gate 			default:
3890Sstevel@tonic-gate 				(void) printf("\tether %s \n", str);
3900Sstevel@tonic-gate 				break;
3910Sstevel@tonic-gate 		}
3920Sstevel@tonic-gate 		free(str);
3930Sstevel@tonic-gate 	}
3940Sstevel@tonic-gate }
395