]> git.ipfire.org Git - thirdparty/squid.git/blob - src/acl/Arp.cc
Author: Henrik Nordstrom <henrik@henriknordstrom.net>
[thirdparty/squid.git] / src / acl / Arp.cc
1 /*
2 * DEBUG: section 28 Access Control
3 * AUTHOR: Duane Wessels
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 *
32 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
33 */
34
35 #include "config.h"
36 #ifdef _SQUID_CYGWIN_
37 #include <squid_windows.h>
38 #endif
39 #include "squid.h"
40 #include "ip/IpAddress.h"
41
42 #ifdef _SQUID_WIN32_
43
44 struct arpreq {
45
46 IpAddress arp_pa; /* protocol address */
47
48 struct sockaddr arp_ha; /* hardware address */
49 int arp_flags; /* flags */
50 };
51
52 #include <Iphlpapi.h>
53 #else
54
55 #ifdef _SQUID_SOLARIS_
56 #include <sys/sockio.h>
57 #else
58 #include <sys/sysctl.h>
59 #endif
60 #ifdef _SQUID_LINUX_
61 #include <net/if_arp.h>
62 #include <sys/ioctl.h>
63 #else
64 #include <net/if_dl.h>
65 #endif
66 #include <net/route.h>
67 #include <net/if.h>
68 #if defined(_SQUID_FREEBSD_) || defined(_SQUID_NETBSD_) || defined(_SQUID_OPENBSD_) || defined(_SQUID_DRAGONFLY_)
69 #include <net/if_arp.h>
70 #endif
71 #if HAVE_NETINET_IF_ETHER_H
72 #include <netinet/if_ether.h>
73 #endif
74 #endif
75
76 #include "acl/Arp.h"
77 #include "acl/FilledChecklist.h"
78 #include "wordlist.h"
79
80 #if !USE_ARP_ACL
81 #error USE_ARP_ACL Not defined
82 #endif
83 static void aclParseArpList(SplayNode<acl_arp_data *> **curlist);
84 static int decode_eth(const char *asc, char *eth);
85 static int aclMatchArp(SplayNode<acl_arp_data *> **dataptr, IpAddress &c);
86 static SplayNode<acl_arp_data *>::SPLAYCMP aclArpCompare;
87 static SplayNode<acl_arp_data *>::SPLAYWALKEE aclDumpArpListWalkee;
88
89
90
91 ACL *
92 ACLARP::clone() const
93 {
94 return new ACLARP(*this);
95 }
96
97 ACLARP::ACLARP (char const *theClass) : data (NULL), class_ (theClass)
98 {}
99
100 ACLARP::ACLARP (ACLARP const & old) : data (NULL), class_ (old.class_)
101 {
102 /* we don't have copy constructors for the data yet */
103 assert (!old.data);
104 }
105
106 ACLARP::~ACLARP()
107 {
108 if (data)
109 data->destroy(SplayNode<acl_arp_data*>::DefaultFree);
110 }
111
112 char const *
113 ACLARP::typeString() const
114 {
115 return class_;
116 }
117
118 bool
119 ACLARP::empty () const
120 {
121 return data->empty();
122 }
123
124 /* ==== BEGIN ARP ACL SUPPORT ============================================= */
125
126 /*
127 * From: dale@server.ctam.bitmcnit.bryansk.su (Dale)
128 * To: wessels@nlanr.net
129 * Subject: Another Squid patch... :)
130 * Date: Thu, 04 Dec 1997 19:55:01 +0300
131 * ============================================================================
132 *
133 * Working on setting up a proper firewall for a network containing some
134 * Win'95 computers at our Univ, I've discovered that some smart students
135 * avoid the restrictions easily just changing their IP addresses in Win'95
136 * Contol Panel... It has been getting boring, so I took Squid-1.1.18
137 * sources and added a new acl type for hard-wired access control:
138 *
139 * acl <name> arp <Ethernet address> ...
140 *
141 * For example,
142 *
143 * acl students arp 00:00:21:55:ed:22 00:00:21:ff:55:38
144 *
145 * NOTE: Linux code by David Luyer <luyer@ucs.uwa.edu.au>.
146 * Original (BSD-specific) code no longer works.
147 * Solaris code by R. Gancarz <radekg@solaris.elektrownia-lagisza.com.pl>
148 */
149
150 /**
151 * Decode an ascii representation (asc) of an ethernet address.
152 *
153 * \param asc ASCII representation of an ethernet (MAC) address
154 * \param eth Binary representation of the ethernet address
155 * \retval 0 Conversion to binary failed. Invalid address
156 * \retval 1 Conversion completed successfully
157 */
158 static int
159 decode_eth(const char *asc, char *eth)
160 {
161 int a1 = 0, a2 = 0, a3 = 0, a4 = 0, a5 = 0, a6 = 0;
162
163 if (sscanf(asc, "%x:%x:%x:%x:%x:%x", &a1, &a2, &a3, &a4, &a5, &a6) != 6) {
164 debugs(28, 0, "decode_eth: Invalid ethernet address '" << asc << "'");
165 return 0; /* This is not valid address */
166 }
167
168 eth[0] = (u_char) a1;
169 eth[1] = (u_char) a2;
170 eth[2] = (u_char) a3;
171 eth[3] = (u_char) a4;
172 eth[4] = (u_char) a5;
173 eth[5] = (u_char) a6;
174 return 1;
175 }
176
177 acl_arp_data *
178 aclParseArpData(const char *t)
179 {
180 LOCAL_ARRAY(char, eth, 256);
181 acl_arp_data *q = new acl_arp_data;
182 debugs(28, 5, "aclParseArpData: " << t);
183
184 if (sscanf(t, "%[0-9a-fA-F:]", eth) != 1) {
185 debugs(28, 0, "aclParseArpData: Bad ethernet address: '" << t << "'");
186 safe_free(q);
187 return NULL;
188 }
189
190 if (!decode_eth(eth, q->eth)) {
191 debugs(28, 0, "" << cfg_filename << " line " << config_lineno << ": " << config_input_line);
192 debugs(28, 0, "aclParseArpData: Ignoring invalid ARP acl entry: can't parse '" << eth << "'");
193 safe_free(q);
194 return NULL;
195 }
196
197 return q;
198 }
199
200
201 /*******************/
202 /* aclParseArpList */
203 /*******************/
204 void
205 ACLARP::parse()
206 {
207 aclParseArpList (&data);
208 }
209
210 void
211 aclParseArpList(SplayNode<acl_arp_data *> **curlist)
212 {
213 char *t = NULL;
214 SplayNode<acl_arp_data *> **Top = curlist;
215 acl_arp_data *q = NULL;
216
217 while ((t = strtokFile())) {
218 if ((q = aclParseArpData(t)) == NULL)
219 continue;
220
221 *Top = (*Top)->insert(q, aclArpCompare);
222 }
223 }
224
225 int
226 ACLARP::match(ACLChecklist *cl)
227 {
228 ACLFilledChecklist *checklist = Filled(cl);
229
230 /* IPv6 does not do ARP */
231 if (!checklist->src_addr.IsIPv4()) {
232 debugs(14, 3, "ACLARP::match: IPv4 Required for ARP Lookups. Skipping " << checklist->src_addr );
233 return 0;
234 }
235
236 return aclMatchArp(&data, checklist->src_addr);
237 }
238
239 /***************/
240 /* aclMatchArp */
241 /***************/
242 int
243 aclMatchArp(SplayNode<acl_arp_data *> **dataptr, IpAddress &c)
244 {
245 struct arpreq arpReq;
246 struct sockaddr_in *sa = NULL;
247
248 IpAddress ipAddr = c;
249
250 #if defined(_SQUID_LINUX_)
251
252 unsigned char ifbuffer[sizeof(struct ifreq) * 64];
253 struct ifconf ifc;
254
255 struct ifreq *ifr;
256 int offset;
257
258 SplayNode<acl_arp_data*> **Top = dataptr;
259 /*
260 * The linux kernel 2.2 maintains per interface ARP caches and
261 * thus requires an interface name when doing ARP queries.
262 *
263 * The older 2.0 kernels appear to use a unified ARP cache,
264 * and require an empty interface name
265 *
266 * To support both, we attempt the lookup with a blank interface
267 * name first. If that does not succeed, the try each interface
268 * in turn
269 */
270
271 /*
272 * Set up structures for ARP lookup with blank interface name
273 */
274 memset(&arpReq, '\0', sizeof(arpReq));
275
276 sa = (sockaddr_in*)&arpReq.arp_pa;
277 ipAddr.GetSockAddr(*sa);
278 /* Query ARP table */
279
280 if (ioctl(HttpSockets[0], SIOCGARP, &arpReq) != -1) {
281 /* Skip non-ethernet interfaces */
282
283 if (arpReq.arp_ha.sa_family != ARPHRD_ETHER) {
284 return 0;
285 }
286
287 debugs(28, 4, "Got address "<< std::setfill('0') << std::hex <<
288 std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" <<
289 std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" <<
290 std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" <<
291 std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" <<
292 std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" <<
293 std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff));
294
295 /* Do lookup */
296 acl_arp_data X;
297 memcpy (X.eth, arpReq.arp_ha.sa_data, 6);
298 *Top = (*Top)->splay(&X, aclArpCompare);
299 debugs(28, 3, "aclMatchArp: '" << c << "' " << (splayLastResult ? "NOT found" : "found"));
300 return (0 == splayLastResult);
301 }
302
303 /* lookup list of interface names */
304 ifc.ifc_len = sizeof(ifbuffer);
305
306 ifc.ifc_buf = (char *)ifbuffer;
307
308 if (ioctl(HttpSockets[0], SIOCGIFCONF, &ifc) < 0) {
309 debugs(28, 1, "Attempt to retrieve interface list failed: " << xstrerror());
310 return 0;
311 }
312
313 if (ifc.ifc_len > (int)sizeof(ifbuffer)) {
314 debugs(28, 1, "Interface list too long - " << ifc.ifc_len);
315 return 0;
316 }
317
318 /* Attempt ARP lookup on each interface */
319 offset = 0;
320
321 while (offset < ifc.ifc_len) {
322
323 ifr = (struct ifreq *) (ifbuffer + offset);
324 offset += sizeof(*ifr);
325 /* Skip loopback and aliased interfaces */
326
327 if (0 == strncmp(ifr->ifr_name, "lo", 2))
328 continue;
329
330 if (NULL != strchr(ifr->ifr_name, ':'))
331 continue;
332
333 debugs(28, 4, "Looking up ARP address for " << c << " on " << ifr->ifr_name);
334
335 /* Set up structures for ARP lookup */
336
337 memset(&arpReq, '\0', sizeof(arpReq));
338
339 sa = (sockaddr_in*)&arpReq.arp_pa;
340 ipAddr.GetSockAddr(*sa);
341
342 strncpy(arpReq.arp_dev, ifr->ifr_name, sizeof(arpReq.arp_dev) - 1);
343
344 arpReq.arp_dev[sizeof(arpReq.arp_dev) - 1] = '\0';
345
346 /* Query ARP table */
347 if (-1 == ioctl(HttpSockets[0], SIOCGARP, &arpReq)) {
348 /*
349 * Query failed. Do not log failed lookups or "device
350 * not supported"
351 */
352
353 if (ENXIO == errno)
354 (void) 0;
355 else if (ENODEV == errno)
356 (void) 0;
357 else
358 debugs(28, 1, "ARP query failed: " << ifr->ifr_name << ": " << xstrerror());
359
360 continue;
361 }
362
363 /* Skip non-ethernet interfaces */
364 if (arpReq.arp_ha.sa_family != ARPHRD_ETHER)
365 continue;
366
367 debugs(28, 4, "Got address "<< std::setfill('0') << std::hex <<
368 std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" <<
369 std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" <<
370 std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" <<
371 std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" <<
372 std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" <<
373 std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff) << " on "<<
374 std::setfill(' ') << ifr->ifr_name);
375
376 /* Do lookup */
377 acl_arp_data X;
378
379 memcpy (X.eth, arpReq.arp_ha.sa_data, 6);
380
381 *Top = (*Top)->splay(&X, aclArpCompare);
382
383 /* Return if match, otherwise continue to other interfaces */
384 if (0 == splayLastResult) {
385 debugs(28, 3, "aclMatchArp: " << c << " found on " << ifr->ifr_name);
386 return 1;
387 }
388
389 /*
390 * Should we stop looking here? Can the same IP address
391 * exist on multiple interfaces?
392 */
393 }
394
395 #elif defined(_SQUID_SOLARIS_)
396
397 SplayNode<acl_arp_data *> **Top = dataptr;
398
399 /*
400 * Set up structures for ARP lookup with blank interface name
401 */
402
403 memset(&arpReq, '\0', sizeof(arpReq));
404
405 sa = (sockaddr_in*)&arpReq.arp_pa;
406 ipAddr.GetSockAddr(*sa);
407
408 /* Query ARP table */
409 if (ioctl(HttpSockets[0], SIOCGARP, &arpReq) != -1) {
410 /*
411 * Solaris (at least 2.6/x86) does not use arp_ha.sa_family -
412 * it returns 00:00:00:00:00:00 for non-ethernet media
413 */
414
415 if (arpReq.arp_ha.sa_data[0] == 0 &&
416 arpReq.arp_ha.sa_data[1] == 0 &&
417 arpReq.arp_ha.sa_data[2] == 0 &&
418 arpReq.arp_ha.sa_data[3] == 0 &&
419 arpReq.arp_ha.sa_data[4] == 0 && arpReq.arp_ha.sa_data[5] == 0)
420 return 0;
421
422 debugs(28, 4, "Got address "<< std::setfill('0') << std::hex <<
423 std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" <<
424 std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" <<
425 std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" <<
426 std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" <<
427 std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" <<
428 std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff));
429
430 /* Do lookup */
431 *Top = (*Top)->splay((acl_arp_data *)&arpReq.arp_ha.sa_data, aclArpCompare);
432
433 debugs(28, 3, "aclMatchArp: '" << c << "' " << (splayLastResult ? "NOT found" : "found"));
434
435 return (0 == splayLastResult);
436 }
437
438 #elif defined(_SQUID_FREEBSD_) || defined(_SQUID_NETBSD_) || defined(_SQUID_OPENBSD_) || defined(_SQUID_DRAGONFLY_)
439
440 SplayNode<acl_arp_data *> **Top = dataptr;
441
442 int mib[6];
443
444 size_t needed;
445
446 char *lim, *buf, *next;
447
448 struct rt_msghdr *rtm;
449
450 struct sockaddr_inarp *sin;
451
452 struct sockaddr_dl *sdl;
453
454 /*
455 * Set up structures for ARP lookup with blank interface name
456 */
457
458 memset(&arpReq, '\0', sizeof(arpReq));
459
460 sa = (struct sockaddr_in*) &arpReq.arp_pa;
461 ipAddr.GetSockAddr(*sa);
462
463 /* Query ARP table */
464 mib[0] = CTL_NET;
465
466 mib[1] = PF_ROUTE;
467
468 mib[2] = 0;
469
470 mib[3] = AF_INET;
471
472 mib[4] = NET_RT_FLAGS;
473
474 mib[5] = RTF_LLINFO;
475
476 if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) {
477 debugs(28, 0, "Can't estimate ARP table size!");
478 return 0;
479 }
480
481 if ((buf = (char *)xmalloc(needed)) == NULL) {
482 debugs(28, 0, "Can't allocate temporary ARP table!");
483 return 0;
484 }
485
486 if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) {
487 debugs(28, 0, "Can't retrieve ARP table!");
488 xfree(buf);
489 return 0;
490 }
491
492 lim = buf + needed;
493
494 for (next = buf; next < lim; next += rtm->rtm_msglen) {
495
496 rtm = (struct rt_msghdr *) next;
497
498 sin = (struct sockaddr_inarp *) (rtm + 1);
499 /*sdl = (struct sockaddr_dl *) (sin + 1); */
500
501 #define ROUNDUP(a) \
502 ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
503
504 sdl = (struct sockaddr_dl *)((char *) sin + ROUNDUP(sin->sin_len));
505
506 if (c == sin->sin_addr) {
507 if (sdl->sdl_alen) {
508
509 arpReq.arp_ha.sa_len = sizeof(struct sockaddr);
510 arpReq.arp_ha.sa_family = AF_UNSPEC;
511 memcpy(arpReq.arp_ha.sa_data, LLADDR(sdl), sdl->sdl_alen);
512 }
513 }
514 }
515
516 xfree(buf);
517
518 if (arpReq.arp_ha.sa_data[0] == 0 && arpReq.arp_ha.sa_data[1] == 0 &&
519 arpReq.arp_ha.sa_data[2] == 0 && arpReq.arp_ha.sa_data[3] == 0 &&
520 arpReq.arp_ha.sa_data[4] == 0 && arpReq.arp_ha.sa_data[5] == 0)
521 return 0;
522
523 debugs(28, 4, "Got address "<< std::setfill('0') << std::hex <<
524 std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" <<
525 std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" <<
526 std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" <<
527 std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" <<
528 std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" <<
529 std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff));
530
531 /* Do lookup */
532 *Top = (*Top)->splay((acl_arp_data *)&arpReq.arp_ha.sa_data, aclArpCompare);
533
534 debugs(28, 3, "aclMatchArp: '" << c << "' " << (splayLastResult ? "NOT found" : "found"));
535
536 return (0 == splayLastResult);
537
538 #elif defined(_SQUID_WIN32_)
539
540 DWORD dwNetTable = 0;
541
542 DWORD ipNetTableLen = 0;
543
544 PMIB_IPNETTABLE NetTable = NULL;
545
546 DWORD i;
547
548 SplayNode<acl_arp_data *> **Top = dataptr;
549
550 memset(&arpReq, '\0', sizeof(arpReq));
551
552 /* Get size of Windows ARP table */
553 if (GetIpNetTable(NetTable, &ipNetTableLen, FALSE) != ERROR_INSUFFICIENT_BUFFER) {
554 debugs(28, 0, "Can't estimate ARP table size!");
555 return 0;
556 }
557
558 /* Allocate space for ARP table and assign pointers */
559 if ((NetTable = (PMIB_IPNETTABLE)xmalloc(ipNetTableLen)) == NULL) {
560 debugs(28, 0, "Can't allocate temporary ARP table!");
561 return 0;
562 }
563
564 /* Get actual ARP table */
565 if ((dwNetTable = GetIpNetTable(NetTable, &ipNetTableLen, FALSE)) != NO_ERROR) {
566 debugs(28, 0, "Can't retrieve ARP table!");
567 xfree(NetTable);
568 return 0;
569 }
570
571 /* Find MAC address from net table */
572 for (i = 0 ; i < NetTable->dwNumEntries ; i++) {
573 in_addr a;
574 a.s_addr = NetTable->table[i].dwAddr;
575 if (c == a && (NetTable->table[i].dwType > 2)) {
576 arpReq.arp_ha.sa_family = AF_UNSPEC;
577 memcpy(arpReq.arp_ha.sa_data, NetTable->table[i].bPhysAddr, NetTable->table[i].dwPhysAddrLen);
578 }
579 }
580
581 xfree(NetTable);
582
583 if (arpReq.arp_ha.sa_data[0] == 0 && arpReq.arp_ha.sa_data[1] == 0 &&
584 arpReq.arp_ha.sa_data[2] == 0 && arpReq.arp_ha.sa_data[3] == 0 &&
585 arpReq.arp_ha.sa_data[4] == 0 && arpReq.arp_ha.sa_data[5] == 0)
586 return 0;
587
588 debugs(28, 4, "Got address "<< std::setfill('0') << std::hex <<
589 std::setw(2) << (arpReq.arp_ha.sa_data[0] & 0xff) << ":" <<
590 std::setw(2) << (arpReq.arp_ha.sa_data[1] & 0xff) << ":" <<
591 std::setw(2) << (arpReq.arp_ha.sa_data[2] & 0xff) << ":" <<
592 std::setw(2) << (arpReq.arp_ha.sa_data[3] & 0xff) << ":" <<
593 std::setw(2) << (arpReq.arp_ha.sa_data[4] & 0xff) << ":" <<
594 std::setw(2) << (arpReq.arp_ha.sa_data[5] & 0xff));
595
596 /* Do lookup */
597 *Top = (*Top)->splay((acl_arp_data *)&arpReq.arp_ha.sa_data, aclArpCompare);
598
599 debugs(28, 3, "aclMatchArp: '" << c << "' " << (splayLastResult ? "NOT found" : "found"));
600
601 return (0 == splayLastResult);
602
603 #else
604
605 #error "ARP type ACL not supported on this operating system."
606
607 #endif
608 /*
609 * Address was not found on any interface
610 */
611 debugs(28, 3, "aclMatchArp: " << c << " NOT found");
612
613 return 0;
614 }
615
616 static int
617 aclArpCompare(acl_arp_data * const &a, acl_arp_data * const &b)
618 {
619 return memcmp(a->eth, b->eth, 6);
620 }
621
622 static void
623 aclDumpArpListWalkee(acl_arp_data * const &node, void *state)
624 {
625 acl_arp_data *arp = node;
626 static char buf[24];
627 snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
628 arp->eth[0] & 0xff, arp->eth[1] & 0xff,
629 arp->eth[2] & 0xff, arp->eth[3] & 0xff,
630 arp->eth[4] & 0xff, arp->eth[5] & 0xff);
631 wordlistAdd((wordlist **)state, buf);
632 }
633
634 wordlist *
635 ACLARP::dump() const
636 {
637 wordlist *w = NULL;
638 data->walk(aclDumpArpListWalkee, &w);
639 return w;
640 }
641
642 /* ==== END ARP ACL SUPPORT =============================================== */