1 /*
2 * Copyright (c) 1982, 1986, 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 * $Id: if_ether.c,v 1.1 94/10/20 10:53:23 root Exp $
34 */
35
36 /*
37 * Ethernet address resolution protocol.
38 * TODO:
39 * run at splnet (add ARP protocol intr.)
40 * link entries onto hash chains, keep free list
41 * add "inuse/lock" bit (or ref. count) along with valid bit
42 */
43
44 #include "sys/param.h"
45 #include "systm.h"
46 #include "malloc.h"
47 #include "mbuf.h"
48 #include "sys/socket.h"
49 #include "sys/time.h"
50 #include "kernel.h"
51 #include "sys/errno.h"
52 #include "sys/ioctl.h"
53 #include "sys/syslog.h"
54 #include "prototypes.h"
55
56 #include "if.h"
57 #include "route.h" /* XXX for looutput */
58 #include "in.h"
59 #include "in_systm.h"
60 #include "in_var.h"
61 #include "ip.h"
62 #define _ARP_PROTOTYPES
63 #include "if_ether.h"
64 #undef _ARP_PROTOYPES
65
66 #ifdef GATEWAY
67 #define ARPTAB_BSIZ 16 /* bucket size */
68 #define ARPTAB_NB 37 /* number of buckets */
69 #else
70 #define ARPTAB_BSIZ 9 /* bucket size */
71 #define ARPTAB_NB 19 /* number of buckets */
72 #endif
73 #define ARPTAB_SIZE (ARPTAB_BSIZ * ARPTAB_NB)
74 struct arptab arptab[ARPTAB_SIZE];
75 int arptab_size = ARPTAB_SIZE; /* for arp command */
76
77 /*
78 * ARP trailer negotiation. Trailer protocol is not IP specific,
79 * but ARP request/response use IP addresses.
80 */
81 #define ETHERTYPE_IPTRAILERS ETHERTYPE_TRAIL
82
83 #define ARPTAB_HASH(a) \
84 ((u_long)(a) % ARPTAB_NB)
85
86 #define ARPTAB_LOOK(at,addr) { \
87 register n; \
88 at = &arptab[ARPTAB_HASH(addr) * ARPTAB_BSIZ]; \
89 for (n = 0 ; n < ARPTAB_BSIZ ; n++,at++) \
90 if (at->at_iaddr.s_addr == addr) \
91 break; \
92 if (n >= ARPTAB_BSIZ) \
93 at = 0; \
94 }
95
96 /* timer values */
97 #define ARPT_AGE (60*1) /* aging timer, 1 min. */
98 #define ARPT_KILLC 20 /* kill completed entry in 20 mins. */
99 #define ARPT_KILLI 3 /* kill incomplete entry in 3 minutes */
100
101
102 /*
103 * Timeout routine. Age arp_tab entries once a minute.
104 */
arptimer()105 arptimer()
106 {
107 register struct arptab *at;
108 register i;
109
110 timeout(arptimer, (caddr_t)0, ARPT_AGE * hz);
111 at = &arptab[0];
112 for (i = 0; i < ARPTAB_SIZE; i++, at++) {
113 if (at->at_flags == 0 || (at->at_flags & ATF_PERM))
114 continue;
115 if (++at->at_timer < ((at->at_flags&ATF_COM) ?
116 ARPT_KILLC : ARPT_KILLI))
117 continue;
118 /* timer has expired, clear entry */
119 arptfree(at);
120 }
121 }
122
123 /*
124 * Broadcast an ARP packet, asking who has addr on interface ac.
125 */
126 void
arpwhohas(struct arpcom * ac,struct in_addr * addr)127 arpwhohas(struct arpcom *ac, struct in_addr *addr)
128 {
129 struct mbuf *m;
130 struct ether_header *eh;
131 struct ether_arp *ea;
132 struct sockaddr sa;
133
134 if ((m = m_gethdr(M_DONTWAIT, MT_DATA)) == NULL)
135 return;
136 m->m_len = sizeof(*ea);
137 m->m_pkthdr.len = sizeof(*ea);
138 MH_ALIGN(m, sizeof(*ea));
139 ea = mtod(m, struct ether_arp *);
140 eh = (struct ether_header *)sa.sa_data;
141 (void) memset((caddr_t)ea, 0, sizeof (*ea));
142 (void) memcpy((caddr_t)eh->ether_dhost, (caddr_t)etherbroadcastaddr,
143 sizeof(eh->ether_dhost));
144 eh->ether_type = ETHERTYPE_ARP; /* if_output will swap */
145 ea->arp_hrd = htons(ARPHRD_ETHER);
146 ea->arp_pro = htons(ETHERTYPE_IP);
147 ea->arp_hln = sizeof(ea->arp_sha); /* hardware address length */
148 ea->arp_pln = sizeof(ea->arp_spa); /* protocol address length */
149 ea->arp_op = htons(ARPOP_REQUEST);
150 (void) memcpy((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,
151 sizeof(ea->arp_sha));
152 (void) memcpy((caddr_t)ea->arp_spa, (caddr_t)&ac->ac_ipaddr,
153 sizeof(ea->arp_spa));
154 (void) memcpy((caddr_t)ea->arp_tpa, (caddr_t)addr, sizeof(ea->arp_tpa));
155 sa.sa_family = AF_UNSPEC;
156 sa.sa_len = sizeof(sa);
157 (*ac->ac_if.if_output)(&ac->ac_if, m, &sa, (struct rtentry *)0);
158 }
159
160 int useloopback = 1; /* use loopback interface for local traffic */
161
162 /*
163 * Resolve an IP address into an ethernet address. If success,
164 * desten is filled in. If there is no entry in arptab,
165 * set one up and broadcast a request for the IP address.
166 * Hold onto this mbuf and resend it once the address
167 * is finally resolved. A return value of 1 indicates
168 * that desten has been filled in and the packet should be sent
169 * normally; a 0 return indicates that the packet has been
170 * taken over here, either now or for later transmission.
171 *
172 * We do some (conservative) locking here at splimp, since
173 * arptab is also altered from input interrupt service (ecintr/ilintr
174 * calls arpinput when ETHERTYPE_ARP packets come in).
175 */
176 int
arpresolve(struct arpcom * ac,struct mbuf * m,struct in_addr * destip,u_char * desten,int * usetrailers)177 arpresolve(struct arpcom *ac, struct mbuf *m, struct in_addr *destip,
178 u_char *desten, int *usetrailers)
179 {
180 register struct arptab *at;
181 struct sockaddr_in sin;
182 register struct in_ifaddr *ia;
183 u_long lna;
184 int s;
185
186 *usetrailers = 0;
187 if (m->m_flags & M_BCAST) { /* broadcast */
188 (void) memcpy((caddr_t)desten, (caddr_t)etherbroadcastaddr,
189 sizeof(etherbroadcastaddr));
190 return (1);
191 }
192 lna = in_lnaof(*destip);
193 /* if for us, use software loopback driver if up */
194 for (ia = in_ifaddr; ia; ia = ia->ia_next)
195 if ((ia->ia_ifp == &ac->ac_if) &&
196 (destip->s_addr == ia->ia_addr.sin_addr.s_addr)) {
197 /*
198 * This test used to be
199 * if (loif.if_flags & IFF_UP)
200 * It allowed local traffic to be forced
201 * through the hardware by configuring the loopback down.
202 * However, it causes problems during network configuration
203 * for boards that can't receive packets they send.
204 * It is now necessary to clear "useloopback"
205 * to force traffic out to the hardware.
206 */
207 if (useloopback) {
208 const struct ifnet *lif = esym_fetch(loif);
209 sin.sin_family = AF_INET;
210 sin.sin_addr = *destip;
211 (void) looutput((struct ifnet *)lif, m, (struct sockaddr *)&sin, 0);
212 /*
213 * The packet has already been sent and freed.
214 */
215 return (0);
216 } else {
217 (void) memcpy((caddr_t)desten, (caddr_t)ac->ac_enaddr,
218 sizeof(ac->ac_enaddr));
219 return (1);
220 }
221 }
222 s = splimp();
223 ARPTAB_LOOK(at, destip->s_addr);
224 if (at == 0) { /* not found */
225 if (ac->ac_if.if_flags & IFF_NOARP) {
226 (void) memcpy((caddr_t)desten, (caddr_t)ac->ac_enaddr, 3);
227 desten[3] = (lna >> 16) & 0x7f;
228 desten[4] = (lna >> 8) & 0xff;
229 desten[5] = lna & 0xff;
230 splx(s);
231 return (1);
232 } else {
233 at = arptnew(destip);
234 if (at == 0)
235 panic("arpresolve: no free entry");
236 at->at_hold = m;
237 arpwhohas(ac, destip);
238 splx(s);
239 return (0);
240 }
241 }
242 at->at_timer = 0; /* restart the timer */
243 if (at->at_flags & ATF_COM) { /* entry IS complete */
244 (void) memcpy((caddr_t)desten, (caddr_t)at->at_enaddr,
245 sizeof(at->at_enaddr));
246 if (at->at_flags & ATF_USETRAILERS)
247 *usetrailers = 1;
248 splx(s);
249 return (1);
250 }
251 /*
252 * There is an arptab entry, but no ethernet address
253 * response yet. Replace the held mbuf with this
254 * latest one.
255 */
256 if (at->at_hold)
257 m_freem(at->at_hold);
258 at->at_hold = m;
259 arpwhohas(ac, destip); /* ask again */
260 splx(s);
261 return (0);
262 }
263
264 /*
265 * Called from 10 Mb/s Ethernet interrupt handlers
266 * when ether packet type ETHERTYPE_ARP
267 * is received. Common length and type checks are done here,
268 * then the protocol-specific routine is called.
269 */
270 void
arpinput(struct arpcom * ac,struct mbuf * m)271 arpinput(struct arpcom *ac, struct mbuf *m)
272 {
273 register struct arphdr *ar;
274
275 if (ac->ac_if.if_flags & IFF_NOARP)
276 goto out;
277 if (m->m_len < sizeof(struct arphdr))
278 goto out;
279 ar = mtod(m, struct arphdr *);
280 if (ntohs(ar->ar_hrd) != ARPHRD_ETHER)
281 goto out;
282 if (m->m_len < sizeof(struct arphdr) + 2 * ar->ar_hln + 2 * ar->ar_pln)
283 goto out;
284
285 switch (ntohs(ar->ar_pro)) {
286
287 case ETHERTYPE_IP:
288 case ETHERTYPE_IPTRAILERS:
289 in_arpinput(ac, m);
290 return;
291
292 default:
293 break;
294 }
295 out:
296 m_freem(m);
297 }
298
299 /*
300 * ARP for Internet protocols on 10 Mb/s Ethernet.
301 * Algorithm is that given in RFC 826.
302 * In addition, a sanity check is performed on the sender
303 * protocol address, to catch impersonators.
304 * We also handle negotiations for use of trailer protocol:
305 * ARP replies for protocol type ETHERTYPE_TRAIL are sent
306 * along with IP replies if we want trailers sent to us,
307 * and also send them in response to IP replies.
308 * This allows either end to announce the desire to receive
309 * trailer packets.
310 * We reply to requests for ETHERTYPE_TRAIL protocol as well,
311 * but don't normally send requests.
312 */
in_arpinput(ac,m)313 in_arpinput(ac, m)
314 register struct arpcom *ac;
315 struct mbuf *m;
316 {
317 register struct ether_arp *ea;
318 struct ether_header *eh;
319 register struct arptab *at; /* same as "merge" flag */
320 register struct in_ifaddr *ia;
321 struct in_ifaddr *maybe_ia = 0;
322 struct mbuf *mcopy = 0;
323 struct sockaddr_in sin;
324 struct sockaddr sa;
325 struct in_addr isaddr, itaddr, myaddr;
326 int proto, op, s, completed = 0;
327
328 ea = mtod(m, struct ether_arp *);
329 proto = ntohs(ea->arp_pro);
330 op = ntohs(ea->arp_op);
331 (void) memcpy((caddr_t)&isaddr, (caddr_t)ea->arp_spa, sizeof (isaddr));
332 (void) memcpy((caddr_t)&itaddr, (caddr_t)ea->arp_tpa, sizeof (itaddr));
333 for (ia = in_ifaddr; ia; ia = ia->ia_next)
334 if (ia->ia_ifp == &ac->ac_if) {
335 maybe_ia = ia;
336 if ((itaddr.s_addr == ia->ia_addr.sin_addr.s_addr) ||
337 (isaddr.s_addr == ia->ia_addr.sin_addr.s_addr))
338 break;
339 }
340 if (maybe_ia == 0)
341 goto out;
342 myaddr = ia ? ia->ia_addr.sin_addr : maybe_ia->ia_addr.sin_addr;
343 if (!memcmp((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,
344 sizeof (ea->arp_sha)))
345 goto out; /* it's from me, ignore it. */
346 if (!memcmp((caddr_t)ea->arp_sha, (caddr_t)etherbroadcastaddr,
347 sizeof (ea->arp_sha))) {
348 log(LOG_ERR,
349 "arp: ether address is broadcast for IP address %x!\n",
350 ntohl(isaddr.s_addr));
351 goto out;
352 }
353 if (isaddr.s_addr == myaddr.s_addr) {
354 log(LOG_ERR,
355 "duplicate IP address %x!! sent from ethernet address: %s\n",
356 ntohl(isaddr.s_addr), ether_sprintf(ea->arp_sha));
357 itaddr = myaddr;
358 if (op == ARPOP_REQUEST)
359 goto reply;
360 goto out;
361 }
362 s = splimp();
363 ARPTAB_LOOK(at, isaddr.s_addr);
364 if (at) {
365 (void) memcpy((caddr_t)at->at_enaddr, (caddr_t)ea->arp_sha,
366 sizeof(ea->arp_sha));
367 if ((at->at_flags & ATF_COM) == 0)
368 completed = 1;
369 at->at_flags |= ATF_COM;
370 if (at->at_hold) {
371 sin.sin_family = AF_INET;
372 sin.sin_addr = isaddr;
373 (*ac->ac_if.if_output)(&ac->ac_if, at->at_hold,
374 (struct sockaddr *)&sin, (struct rtentry *)0);
375 at->at_hold = 0;
376 }
377 }
378 if (at == 0 && itaddr.s_addr == myaddr.s_addr) {
379 /* ensure we have a table entry */
380 if (at = arptnew(&isaddr)) {
381 (void) memcpy((caddr_t)at->at_enaddr, (caddr_t)ea->arp_sha,
382 sizeof(ea->arp_sha));
383 completed = 1;
384 at->at_flags |= ATF_COM;
385 }
386 }
387 splx(s);
388 reply:
389 switch (proto) {
390
391 case ETHERTYPE_IPTRAILERS:
392 /* partner says trailers are OK */
393 if (at)
394 at->at_flags |= ATF_USETRAILERS;
395 /*
396 * Reply to request iff we want trailers.
397 */
398 if (op != ARPOP_REQUEST || ac->ac_if.if_flags & IFF_NOTRAILERS)
399 goto out;
400 break;
401
402 case ETHERTYPE_IP:
403 /*
404 * Reply if this is an IP request,
405 * or if we want to send a trailer response.
406 * Send the latter only to the IP response
407 * that completes the current ARP entry.
408 */
409 if (op != ARPOP_REQUEST &&
410 (completed == 0 || ac->ac_if.if_flags & IFF_NOTRAILERS))
411 goto out;
412 }
413 if (itaddr.s_addr == myaddr.s_addr) {
414 /* I am the target */
415 (void) memcpy((caddr_t)ea->arp_tha, (caddr_t)ea->arp_sha,
416 sizeof(ea->arp_sha));
417 (void) memcpy((caddr_t)ea->arp_sha, (caddr_t)ac->ac_enaddr,
418 sizeof(ea->arp_sha));
419 } else {
420 ARPTAB_LOOK(at, itaddr.s_addr);
421 if (at == NULL || (at->at_flags & ATF_PUBL) == 0)
422 goto out;
423 (void) memcpy((caddr_t)ea->arp_tha, (caddr_t)ea->arp_sha,
424 sizeof(ea->arp_sha));
425 (void) memcpy((caddr_t)ea->arp_sha, (caddr_t)at->at_enaddr,
426 sizeof(ea->arp_sha));
427 }
428
429 (void) memcpy((caddr_t)ea->arp_tpa, (caddr_t)ea->arp_spa, sizeof(ea->arp_spa));
430 (void) memcpy((caddr_t)ea->arp_spa, (caddr_t)&itaddr, sizeof(ea->arp_spa));
431 ea->arp_op = htons(ARPOP_REPLY);
432 /*
433 * If incoming packet was an IP reply,
434 * we are sending a reply for type IPTRAILERS.
435 * If we are sending a reply for type IP
436 * and we want to receive trailers,
437 * send a trailer reply as well.
438 */
439 if (op == ARPOP_REPLY)
440 ea->arp_pro = htons(ETHERTYPE_IPTRAILERS);
441 else if (proto == ETHERTYPE_IP &&
442 (ac->ac_if.if_flags & IFF_NOTRAILERS) == 0)
443 mcopy = m_copy(m, 0, (int)M_COPYALL);
444 eh = (struct ether_header *)sa.sa_data;
445 (void) memcpy((caddr_t)eh->ether_dhost, (caddr_t)ea->arp_tha,
446 sizeof(eh->ether_dhost));
447 eh->ether_type = ETHERTYPE_ARP;
448 sa.sa_family = AF_UNSPEC;
449 sa.sa_len = sizeof(sa);
450 (*ac->ac_if.if_output)(&ac->ac_if, m, &sa, (struct rtentry *)0);
451 if (mcopy) {
452 ea = mtod(mcopy, struct ether_arp *);
453 ea->arp_pro = htons(ETHERTYPE_IPTRAILERS);
454 (*ac->ac_if.if_output)(&ac->ac_if,
455 mcopy, &sa, (struct rtentry *)0);
456 }
457 return;
458 out:
459 m_freem(m);
460 return;
461 }
462
463 /*
464 * Free an arptab entry.
465 */
arptfree(at)466 arptfree(at)
467 register struct arptab *at;
468 {
469 int s = splimp();
470
471 if (at->at_hold)
472 m_freem(at->at_hold);
473 at->at_hold = 0;
474 at->at_timer = at->at_flags = 0;
475 at->at_iaddr.s_addr = 0;
476 splx(s);
477 }
478
479 /*
480 * Enter a new address in arptab, pushing out the oldest entry
481 * from the bucket if there is no room.
482 * This always succeeds since no bucket can be completely filled
483 * with permanent entries (except from arpioctl when testing whether
484 * another permanent entry will fit).
485 * MUST BE CALLED AT SPLIMP.
486 */
487 struct arptab *
arptnew(addr)488 arptnew(addr)
489 struct in_addr *addr;
490 {
491 register n;
492 int oldest = -1;
493 register struct arptab *at, *ato = NULL;
494 static int first = 1;
495
496 if (first) {
497 first = 0;
498 timeout(arptimer, (caddr_t)0, hz);
499 }
500 at = &arptab[ARPTAB_HASH(addr->s_addr) * ARPTAB_BSIZ];
501 for (n = 0; n < ARPTAB_BSIZ; n++,at++) {
502 if (at->at_flags == 0)
503 goto out; /* found an empty entry */
504 if (at->at_flags & ATF_PERM)
505 continue;
506 if ((int) at->at_timer > oldest) {
507 oldest = at->at_timer;
508 ato = at;
509 }
510 }
511 if (ato == NULL)
512 return (NULL);
513 at = ato;
514 arptfree(at);
515 out:
516 at->at_iaddr = *addr;
517 at->at_flags = ATF_INUSE;
518 return (at);
519 }
520
521 int
arpioctl(int cmd,caddr_t data)522 arpioctl(int cmd, caddr_t data)
523 {
524 struct arpreq *ar = (struct arpreq *)data;
525 struct arptab *at;
526 struct sockaddr_in *sin;
527 int s;
528
529 sin = (struct sockaddr_in *)&ar->arp_ha;
530 #if defined(COMPAT_43) && BYTE_ORDER != BIG_ENDIAN
531 if (sin->sin_family == 0 && sin->sin_len < 16)
532 sin->sin_family = sin->sin_len;
533 #endif
534 sin->sin_len = sizeof(ar->arp_ha);
535 sin = (struct sockaddr_in *)&ar->arp_pa;
536 #if defined(COMPAT_43) && BYTE_ORDER != BIG_ENDIAN
537 if (sin->sin_family == 0 && sin->sin_len < 16)
538 sin->sin_family = sin->sin_len;
539 #endif
540 sin->sin_len = sizeof(ar->arp_pa);
541 if (ar->arp_pa.sa_family != AF_INET ||
542 ar->arp_ha.sa_family != AF_UNSPEC)
543 return (EAFNOSUPPORT);
544 s = splimp();
545 ARPTAB_LOOK(at, sin->sin_addr.s_addr);
546 if (at == NULL) { /* not found */
547 if (cmd != SIOCSARP) {
548 splx(s);
549 return (ENXIO);
550 }
551 if (ifa_ifwithnet(&ar->arp_pa) == NULL) {
552 splx(s);
553 return (ENETUNREACH);
554 }
555 }
556 switch (cmd) {
557
558 case SIOCSARP: /* set entry */
559 if (at == NULL) {
560 at = arptnew(&sin->sin_addr);
561 if (at == NULL) {
562 splx(s);
563 return (EADDRNOTAVAIL);
564 }
565 if (ar->arp_flags & ATF_PERM) {
566 /* never make all entries in a bucket permanent */
567 register struct arptab *tat;
568
569 /* try to re-allocate */
570 tat = arptnew(&sin->sin_addr);
571 if (tat == NULL) {
572 arptfree(at);
573 splx(s);
574 return (EADDRNOTAVAIL);
575 }
576 arptfree(tat);
577 }
578 }
579 (void) memcpy((caddr_t)at->at_enaddr, (caddr_t)ar->arp_ha.sa_data,
580 sizeof(at->at_enaddr));
581 at->at_flags = ATF_COM | ATF_INUSE |
582 (ar->arp_flags & (ATF_PERM|ATF_PUBL|ATF_USETRAILERS));
583 at->at_timer = 0;
584 break;
585
586 case SIOCDARP: /* delete entry */
587 arptfree(at);
588 break;
589
590 case SIOCGARP: /* get entry */
591 case OSIOCGARP:
592 (void) memcpy((caddr_t)ar->arp_ha.sa_data, (caddr_t)at->at_enaddr,
593 sizeof(at->at_enaddr));
594 #ifdef COMPAT_43
595 if (cmd == OSIOCGARP)
596 *(u_short *)&ar->arp_ha = ar->arp_ha.sa_family;
597 #endif
598 ar->arp_flags = at->at_flags;
599 break;
600 }
601 splx(s);
602 return (0);
603 }
604