Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 8 Sep 2008 22:30:21 +0300
From:      Gleb Kurtsou <gleb.kurtsou@gmail.com>
To:        freebsd-net@freebsd.org
Cc:        Max Laier <mlaier@FreeBSD.org>, Brooks Davis <brooks@FreeBSD.org>, Andrew Thompson <thompsa@FreeBSD.org>
Subject:   [patch] gsoc project: improving layer2 filtering
Message-ID:  <20080908193020.GA37900@rybacik>

next in thread | raw e-mail | index | archive | help

--4Ckj6UjgE2iN1+kY
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline

[Max Laier and Brooks Davis CCed as suggested by Andrew Thompson]

This summer I was working on improving layer2 filtering (my mentor is
Andrew Thompson) as a google summer of code project.  The project was
successfully completed. 

I'd like to ask for a public review of the patch attached.
To apply patch (against -CURRENT):
cd /usr/src; patch -p0 < gk_l2filter.patch

Note, that the patch is not so clean: style(9) issues, stale comments,
some inaccurate variable names, etc. But is should be just fine for a
general review.  I'd like to continue working further to improve it, if
community is interested and if there is possibility for it to get
commited.  I would appreciate any comments and suggestions.

Some additional details and examples of new functionality can be found on
my blog: http://blogs.freebsdish.org/gleb/

Project's perforce repository: http://perforce.freebsd.org/changeList.cgi?CMD=changes&FSPC=//depot/projects/soc2008/gk%5fl2filter/...

To sum it up, following project goals were achieved (old todo list):

general:
    * Implement pfil hooks for filtering ethernet packets
    * Add mtag containing source and destination layer2 addresses to
      every mbuf
    * Add per interface flags: l2filter, l2tag 

ipfw:
    * Update ipfw layer2 not to touch ip headers, but to use mentioned
      mtags to do MAC-IP filtering
    * Add src-ether and dst-ether ipfw options
    * Support mac addresses in ipfw lookup tables
    * Stateful filtering by mac addresses
    * Implement ARP filtering options
    * Update documentation 

pf:
    * Add stateful filtering against mac addresses. Make it part of
      present layer3 stateful filtering.
    * Extend pf's tables facility to contain layer2 address apart with
      layer3 address.
    * Support in userspace (pf.conf, pfctl).
    * Update documentation



--4Ckj6UjgE2iN1+kY
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment; filename="gk_l2filter.patch"

diff -urN -x .hg -x .svn ../my/contrib/pf/man/pf.conf.5 ./contrib/pf/man/pf.conf.5
--- ../my/contrib/pf/man/pf.conf.5	2008-09-07 19:05:35.000000000 +0300
+++ ./contrib/pf/man/pf.conf.5	2008-09-07 22:24:44.000000000 +0300
@@ -123,6 +123,7 @@
 rules and in the routing options of filter rules, but only for
 .Ar round-robin
 pools.
+Table entry can contain optional ethernet address (MAC address).
 .Pp
 Tables can be defined with any of the following
 .Xr pfctl 8
@@ -1485,6 +1486,10 @@
 This is especially useful with
 .Ar nat .
 .Pp
+Optional ethernet address (MAC address) can be assigned to addresses
+specified in CIDR notation (matching netblocks), as symbolic host names or
+interface names.
+.Pp
 Ports can be specified either by number or by name.
 For example, port 80 can be specified as
 .Em www .
@@ -2044,6 +2049,10 @@
 must be specified explicitly to apply options to a rule.
 .Pp
 .Bl -tag -width xxxx -compact
+.It Ar ether
+Enable layer 2 stateful filtering for a rule.
+Source and destination ethernet addresses (MAC addresses) are used to
+create a state entry and to check if packet matches any state entry.
 .It Ar max Aq Ar number
 Limits the number of concurrent states the rule may create.
 When this limit is reached, further packets matching the rule that would
@@ -2735,6 +2744,9 @@
 block in on $ext_if proto tcp from any os {"Windows 95", "Windows 98"} \e
       to any port smtp
 
+pass in on $bridge_if proto tcp from 10.1.1.1 ether 00:11:11:11:11:11 \e
+      to ($int_if) ether 00:22:22:22:22:22 keep state (ether)
+
 # IPv6
 # pass in/out all IPv6 traffic: note that we have to enable this in two
 # different ways, on both our physical interface and our tunnel
@@ -2835,7 +2847,7 @@
 tableopts      = "persist" | "const" | "file" string |
                  "{" [ tableaddr-list ] "}"
 tableaddr-list = tableaddr-list [ "," ] tableaddr-spec | tableaddr-spec
-tableaddr-spec = [ "!" ] tableaddr [ "/" mask-bits ]
+tableaddr-spec = [ "!" ] tableaddr [ "/" mask-bits ] [ "ether" ether-addr ]
 tableaddr      = hostname | ipv4-dotted-quad | ipv6-coloned-hex |
                  interface-name | "self"
 
@@ -2890,7 +2902,7 @@
 redirhost      = address [ "/" mask-bits ]
 routehost      = "(" interface-name [ address [ "/" mask-bits ] ] ")"
 address        = ( interface-name | "(" interface-name ")" | hostname |
-                 ipv4-dotted-quad | ipv6-coloned-hex )
+                 ipv4-dotted-quad | ipv6-coloned-hex ) [ "ether" ether-addr ]
 host-list      = host [ [ "," ] host-list ]
 redirhost-list = redirhost [ [ "," ] redirhost-list ]
 routehost-list = routehost [ [ "," ] routehost-list ]
@@ -2923,7 +2935,7 @@
                  [ "0x" ] number )
 
 state-opts     = state-opt [ [ "," ] state-opts ]
-state-opt      = ( "max" number | "no-sync" | timeout |
+state-opt      = ( "ether" | "max" number | "no-sync" | timeout |
                  "source-track" [ ( "rule" | "global" ) ] |
                  "max-src-nodes" number | "max-src-states" number |
                  "max-src-conn" number |
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/parse.y ./contrib/pf/pfctl/parse.y
--- ../my/contrib/pf/pfctl/parse.y	2008-09-07 19:05:35.000000000 +0300
+++ ./contrib/pf/pfctl/parse.y	2008-09-07 22:24:43.000000000 +0300
@@ -128,7 +128,7 @@
 	    PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN,
 	    PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES,
 	    PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK,
-	    PF_STATE_OPT_TIMEOUT };
+	    PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_ETHER };
 
 enum	{ PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE };
 
@@ -409,7 +409,7 @@
 
 %}
 
-%token	PASS BLOCK SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS
+%token	PASS BLOCK SCRUB RETURN IN OS OUT LOG QUICK ON ETHER FROM TO FLAGS
 %token	RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE
 %token	ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF
 %token	MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL
@@ -442,7 +442,7 @@
 %type	<v.icmp>		icmp6_list icmp6_item
 %type	<v.fromto>		fromto
 %type	<v.peer>		ipportspec from to
-%type	<v.host>		ipspec xhost host dynaddr host_list
+%type	<v.host>		ipspec ether xhost host dynaddr host_list
 %type	<v.host>		redir_host_list redirspec
 %type	<v.host>		route_host route_host_list routespec
 %type	<v.os>			os xos os_list
@@ -1906,6 +1906,10 @@
 					}
 					r.timeout[o->data.timeout.number] =
 					    o->data.timeout.seconds;
+					break;
+				case PF_STATE_OPT_ETHER:
+					r.rule_flag |= PFRULE_ETHERSTATE;
+					break;
 				}
 				o = o->next;
 				free(p);
@@ -2471,12 +2475,38 @@
 		}
 		;
 
-xhost		: not host			{
+ether		: /* empty */			{ $$ = NULL; }
+		| ETHER ANY			{ $$ = NULL; }
+		| ETHER STRING			{
+			$$ = host_ether($2);
+			free($2);
+			if ($$ == NULL) {
+				YYERROR;
+			}
+		}
+		;
+
+xhost		: not host ether		{
 			struct node_host	*n;
 
 			for (n = $2; n != NULL; n = n->next)
 				n->not = $1;
 			$$ = $2;
+			if ($3) {
+				for (n = $$; n != NULL; n = n->next) {
+					if (n->addr.type != PF_ADDR_ADDRMASK &&
+					    n->addr.type != PF_ADDR_DYNIFTL) {
+						yyerror("ethernet address can be specified only for host or interface name");
+						free($3);
+						$3 = NULL;
+						YYERROR;
+					} else {
+						n->addr.addr_ether = $3->addr.addr_ether;
+					}
+				}
+				if ($3)
+					free($3);
+			}
 		}
 		| not NOROUTE			{
 			$$ = calloc(1, sizeof(struct node_host));
@@ -3198,6 +3228,14 @@
 			$$->next = NULL;
 			$$->tail = $$;
 		}
+		| ETHER {
+			$$ = calloc(1, sizeof(struct node_state_opt));
+			if ($$ == NULL)
+				err(1, "state_opt_item: calloc");
+			$$->type = PF_STATE_OPT_ETHER;
+			$$->next = NULL;
+			$$->tail = $$;
+		}
 		| sourcetrack {
 			$$ = calloc(1, sizeof(struct node_state_opt));
 			if ($$ == NULL)
@@ -4894,6 +4932,7 @@
 		{ "drop",		DROP},
 		{ "drop-ovl",		FRAGDROP},
 		{ "dup-to",		DUPTO},
+		{ "ether",		ETHER},
 		{ "fastroute",		FASTROUTE},
 		{ "file",		FILENAME},
 		{ "fingerprints",	FINGERPRINTS},
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pf_print_state.c ./contrib/pf/pfctl/pf_print_state.c
--- ../my/contrib/pf/pfctl/pf_print_state.c	2008-09-07 19:05:35.000000000 +0300
+++ ./contrib/pf/pfctl/pf_print_state.c	2008-09-07 22:24:44.000000000 +0300
@@ -119,6 +119,26 @@
 		if (bits != (af == AF_INET ? 32 : 128))
 			printf("/%d", bits);
 	}
+
+	putchar(' ');
+	print_addr_ether(&addr->addr_ether, 0);
+}
+
+void
+print_addr_ether(struct pf_addr_ether *addr, int verbose)
+{
+	if ((addr->flags & PFAE_CHECK) == 0) {
+		if (verbose)
+			printf("ether any");
+		return;
+	}
+	if (addr->flags & PFAE_MULTICAST) {
+		printf("ether multicast");
+	} else {
+                u_int8_t *ea = addr->octet;
+                printf("ether %02x:%02x:%02x:%02x:%02x:%02x",
+                    ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]);
+	}
 }
 
 void
@@ -299,6 +319,28 @@
 		if (s->nat_src_node != NULL)
 			printf(", sticky-address");
 		printf("\n");
+		if (s->local_flags & PFSTATE_ETHER) {
+			int left_printed = 0;
+
+			printf("   ");
+			if (s->lan.addr_ether.flags & PFAE_CHECK) {
+				print_addr_ether(&s->lan.addr_ether, 1);
+				if (s->direction == PF_OUT)
+					printf(" -> ");
+				else
+					printf(" <- ");
+				left_printed = 1;
+			}
+			if (!left_printed || (s->gwy.addr_ether.flags & PFAE_CHECK)) {
+				print_addr_ether(&s->gwy.addr_ether, 1);
+				if (s->direction == PF_OUT)
+					printf(" -> ");
+				else
+					printf(" <- ");
+			}
+			print_addr_ether(&s->ext.addr_ether, 1);
+			printf("\n");
+		}
 	}
 	if (opts & PF_OPT_VERBOSE2) {
 		printf("   id: %016llx creatorid: %08x%s\n",
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl.c ./contrib/pf/pfctl/pfctl.c
--- ../my/contrib/pf/pfctl/pfctl.c	2008-09-07 19:05:35.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl.c	2008-09-07 22:24:44.000000000 +0300
@@ -1902,8 +1902,8 @@
 
 	if (ioctl(dev, DIOCGETALTQS, &pa)) {
 		if (errno == ENODEV) {
-			if (!(opts & PF_OPT_QUIET))
-				fprintf(stderr, "No ALTQ support in kernel\n"
+			if (opts & PF_OPT_VERBOSE)
+				fprintf(stderr, "No ALTQ support in kernel. "
 				    "ALTQ related functions disabled\n");
 			return (0);
 		} else
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl.h ./contrib/pf/pfctl/pfctl.h
--- ../my/contrib/pf/pfctl/pfctl.h	2008-09-07 19:05:35.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl.h	2008-09-07 22:24:44.000000000 +0300
@@ -117,6 +117,7 @@
 char		*rate2str(double);
 
 void	 print_addr(struct pf_addr_wrap *, sa_family_t, int);
+void	 print_addr_ether(struct pf_addr_ether *, int);
 void	 print_host(struct pf_state_host *, sa_family_t, int);
 void	 print_seq(struct pf_state_peer *);
 void	 print_state(struct pf_state *, int);
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl_parser.c ./contrib/pf/pfctl/pfctl_parser.c
--- ../my/contrib/pf/pfctl/pfctl_parser.c	2008-09-07 19:05:36.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl_parser.c	2008-09-07 22:24:44.000000000 +0300
@@ -46,6 +46,7 @@
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
 #include <net/pfvar.h>
+#include <net/ethernet.h>
 #include <arpa/inet.h>
 
 #include <stdio.h>
@@ -876,6 +877,8 @@
 	for (i = 0; !opts && i < PFTM_MAX; ++i)
 		if (r->timeout[i])
 			opts = 1;
+	if (r->rule_flag & PFRULE_ETHERSTATE)
+		opts = 1;
 	if (opts) {
 		printf(" (");
 		if (r->max_states) {
@@ -954,6 +957,12 @@
 				    "inv.timeout" : pf_timeouts[j].name,
 				    r->timeout[i]);
 			}
+		if (r->rule_flag & PFRULE_ETHERSTATE) {
+			if (!opts)
+				printf(", ");
+			printf("ether");
+			opts = 0;
+		}
 		printf(")");
 	}
 	if (r->rule_flag & PFRULE_FRAGMENT)
@@ -1419,6 +1428,35 @@
 }
 
 struct node_host *
+host_ether(const char *s)
+{
+	struct pf_addr_ether	*addr;
+	struct node_host	*h = NULL;
+
+	if (strcmp(s, "any") == 0) {
+		return (NULL);
+	} 
+
+	h = calloc(1, sizeof(*h));
+	if (h == NULL)
+		err(1, "host_ether: malloc");
+	addr = &h->addr.addr_ether;
+
+	if (strcmp(s, "multicast") == 0) {
+		addr->flags = PFAE_CHECK | PFAE_MULTICAST;
+		return (h);
+	} 
+	if (!ether_aton_r(s, (struct ether_addr*)addr->octet)) {
+		fprintf(stderr, "can't parse ethernet address: %s\n", s);
+		free(h);
+		return (NULL);
+	}
+	addr->flags = PFAE_CHECK;
+
+	return (h);
+}
+
+struct node_host *
 host_if(const char *s, int mask)
 {
 	struct node_host	*n, *h = NULL;
@@ -1606,16 +1644,39 @@
 int
 append_addr(struct pfr_buffer *b, char *s, int test)
 {
-	char			 *r;
+	char			 *r, *rs, *p;
 	struct node_host	*h, *n;
+	struct pf_addr_ether	 addr_ether;
 	int			 rv, not = 0;
 
 	for (r = s; *r == '!'; r++)
 		not = !not;
-	if ((n = host(r)) == NULL) {
+	if ((rs = strdup(r)) == NULL)
+		err(1, "append_addr: strdup");
+	bzero(&addr_ether, sizeof (addr_ether));
+	if ((p = strstr(rs, "ether")) != NULL) {
+		char *s_ether = p + strlen("ether");
+		if (p > rs && isspace(*(p - 1)) && isspace(*s_ether++)) {
+			while (isspace(*s_ether)) 
+				s_ether++;
+			h = host_ether(s_ether);
+			if (h) {
+				addr_ether = h->addr.addr_ether;
+				free(h);
+				h = NULL;
+			}
+			for (p--; p >= rs && isspace(*p); p--) 
+				*p = '\0';
+		}
+	}
+	if ((n = host(rs)) == NULL) {
 		errno = 0;
 		return (-1);
 	}
+	for (h = n; h != NULL; h = h->next)
+		h->addr.addr_ether = addr_ether;
+	h = NULL;
+	free(rs);
 	rv = append_addr_host(b, n, test, not);
 	do {
 		h = n;
@@ -1661,6 +1722,7 @@
 			errno = EINVAL;
 			return (-1);
 		}
+		addr.pfra_ether = n->addr.addr_ether;
 		if (pfr_buf_add(b, &addr))
 			return (-1);
 	} while ((n = n->next) != NULL);
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl_parser.h ./contrib/pf/pfctl/pfctl_parser.h
--- ../my/contrib/pf/pfctl/pfctl_parser.h	2008-09-07 19:05:36.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl_parser.h	2008-09-07 22:24:44.000000000 +0300
@@ -296,6 +296,7 @@
 struct node_host	*ifa_exists(const char *);
 struct node_host	*ifa_lookup(const char *, int);
 struct node_host	*host(const char *);
+struct node_host	*host_ether(const char *);
 
 int			 append_addr(struct pfr_buffer *, char *, int);
 int			 append_addr_host(struct pfr_buffer *,
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl_radix.c ./contrib/pf/pfctl/pfctl_radix.c
--- ../my/contrib/pf/pfctl/pfctl_radix.c	2008-09-07 19:05:36.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl_radix.c	2008-09-07 22:24:44.000000000 +0300
@@ -607,12 +607,20 @@
 	do {
 		if (i < BUF_SIZE)
 			buf[i++] = next_ch;
-		next_ch = fgetc(fp);
-	} while (!feof(fp) && !isspace(next_ch));
+		/* leave only 1 space */
+		if (isspace(next_ch)) {
+			while (isspace(next_ch) && next_ch != '\n' && !feof(fp))
+				next_ch = fgetc(fp);
+		} else {
+			next_ch = fgetc(fp);
+		}
+	} while (!feof(fp) && next_ch != '\n');
 	if (i >= BUF_SIZE) {
 		errno = EINVAL;
 		return (-1);
 	}
+	if (i > 0 && isspace(buf[i-1]))
+		i--;
 	buf[i] = '\0';
 	return (1);
 }
diff -urN -x .hg -x .svn ../my/contrib/pf/pfctl/pfctl_table.c ./contrib/pf/pfctl/pfctl_table.c
--- ../my/contrib/pf/pfctl/pfctl_table.c	2008-09-07 19:05:36.000000000 +0300
+++ ./contrib/pf/pfctl/pfctl_table.c	2008-09-07 22:24:44.000000000 +0300
@@ -438,6 +438,8 @@
 	printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf);
 	if (ad->pfra_net < hostnet)
 		printf("/%d", ad->pfra_net);
+	putchar(' ');
+	print_addr_ether(&ad->pfra_ether, 0);
 	if (rad != NULL && fback != PFR_FB_NONE) {
 		if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf))
 			errx(1, "print_addrx: strlcpy");
diff -urN -x .hg -x .svn ../my/sbin/ifconfig/ifconfig.8 ./sbin/ifconfig/ifconfig.8
--- ../my/sbin/ifconfig/ifconfig.8	2008-09-07 19:10:19.000000000 +0300
+++ ./sbin/ifconfig/ifconfig.8	2008-09-07 22:24:43.000000000 +0300
@@ -240,6 +240,27 @@
 If the Address Resolution Protocol is enabled,
 the host will perform normally,
 sending out requests and listening for replies.
+.It Cm l2tag
+Special tag containing source and destination layer 2 addresses will be
+attached to every packet passing through interface.
+Note that only incoming or outgoing packets may be tagged (but not both), it is
+interface dependant.
+.It Fl l2tag
+Disable special packet tagging with layer 2 addresses.
+.It Cm l2filter
+Perform layer 2 filtering of packets passing through interface.
+This option doesn't imply
+.Cm l2tag
+option.
+With
+.Cm l2filter
+specified packets are passed to firewall as they were received from wire.
+But
+.Cm l2tag
+just tags packet and usual layer 3 filtering is performed.
+.It Fl l2filter
+Disable layer 2 filtering.
+Higher level filtering will perform normally.
 .It Cm broadcast
 (Inet only.)
 Specify the address to use to represent broadcasts to the
diff -urN -x .hg -x .svn ../my/sbin/ifconfig/ifconfig.c ./sbin/ifconfig/ifconfig.c
--- ../my/sbin/ifconfig/ifconfig.c	2008-09-07 19:10:19.000000000 +0300
+++ ./sbin/ifconfig/ifconfig.c	2008-09-07 22:24:43.000000000 +0300
@@ -772,7 +772,7 @@
 #define	IFFBITS \
 "\020\1UP\2BROADCAST\3DEBUG\4LOOPBACK\5POINTOPOINT\6SMART\7RUNNING" \
 "\10NOARP\11PROMISC\12ALLMULTI\13OACTIVE\14SIMPLEX\15LINK0\16LINK1\17LINK2" \
-"\20MULTICAST\22PPROMISC\23MONITOR\24STATICARP\25NEEDSGIANT"
+"\20MULTICAST\22PPROMISC\23MONITOR\24STATICARP\25NEEDSGIANT\26L2FILTER\27L2TAG"
 
 #define	IFCAPBITS \
 "\020\1RXCSUM\2TXCSUM\3NETCONS\4VLAN_MTU\5VLAN_HWTAGGING\6JUMBO_MTU\7POLLING" \
@@ -1010,6 +1010,10 @@
 	DEF_CMD("-monitor",	-IFF_MONITOR,	setifflags),
 	DEF_CMD("staticarp",	IFF_STATICARP,	setifflags),
 	DEF_CMD("-staticarp",	-IFF_STATICARP,	setifflags),
+	DEF_CMD("l2filter",	IFF_L2FILTER,	setifflags),
+	DEF_CMD("-l2filter",	-IFF_L2FILTER,	setifflags),
+	DEF_CMD("l2tag",	IFF_L2TAG,	setifflags),
+	DEF_CMD("-l2tag",	-IFF_L2TAG,	setifflags),
 	DEF_CMD("rxcsum",	IFCAP_RXCSUM,	setifcap),
 	DEF_CMD("-rxcsum",	-IFCAP_RXCSUM,	setifcap),
 	DEF_CMD("txcsum",	IFCAP_TXCSUM,	setifcap),
diff -urN -x .hg -x .svn ../my/sbin/ipfw/ipfw.8 ./sbin/ipfw/ipfw.8
--- ../my/sbin/ipfw/ipfw.8	2008-09-07 19:10:21.000000000 +0300
+++ ./sbin/ipfw/ipfw.8	2008-09-07 22:24:43.000000000 +0300
@@ -45,7 +45,7 @@
 .Cm set show
 .Pp
 .Nm
-.Cm table Ar number Cm add Ar addr Ns Oo / Ns Ar masklen Oc Op Ar value
+.Cm table Ar number Cm add Ar addr Ns Oo / Ns Ar masklen Oc Oo Cm ether Ar etheraddr Oc Op Ar value
 .Nm
 .Cm table Ar number Cm delete Ar addr Ns Op / Ns Ar masklen
 .Nm
@@ -332,9 +332,9 @@
 to temporarily disable the firewall to regain access to the network,
 allowing you to fix the problem.
 .Sh PACKET FLOW
-A packet is checked against the active ruleset in multiple places
-in the protocol stack, under control of several sysctl variables.
-These places and variables are shown below, and it is important to
+A packet is checked against the active ruleset in multiple places in the
+protocol stack, under control of several sysctl variables and interface flags.
+These places and variables and flags are shown below, and it is important to
 have this picture in mind in order to design a correct ruleset.
 .Bd -literal -offset indent
        ^    to upper layers    V
@@ -342,11 +342,12 @@
        +----------->-----------+
        ^                       V
  [ip(6)_input]           [ip(6)_output]     net.inet(6).ip(6).fw.enable=1
+       |                       |            (l2tag interface flag)
        |                       |
        ^                       V
- [ether_demux]        [ether_output_frame]  net.link.ether.ipfw=1
+ [ether_demux]        [ether_output_frame]  l2filter interface flag
        |                       |
-       +-->--[bdg_forward]-->--+            net.link.bridge.ipfw=1
+       +-->----[bridge]----->--+            l2filter interface flag
        ^                       V
        |      to devices       |
 .Ed
@@ -370,13 +371,39 @@
 or
 .Cm ip6_input() .
 .Pp
+Note that packets do 
+.Em not
+contain IP header when invoked from 
+.Cm ether_demux() , ether_output_frame()
+or
+.Cm bridge .
+.Pp
+In order to filter by both MAC and IP headers interface flag
+.Cm l2tag
+should be used.
+When enabled a special tag containing MAC header is appended to incoming
+packets. Tag is used when
+.Nm
+invoked from
+.Cm ip_input()
+or
+.Cm ip6_input() .
+Note that as a rule only incoming packets are tagged, but
+.Cm bridge
+appends tag to outgoing packets too.
+Therefore dynamic rules (like rules created by 
+.Cm keep-state
+option) do not check specified MAC header options if there is no
+.Cm l2tag
+tag appended to packet.
+.Pp
 Also note that each packet is always checked against the complete ruleset,
 irrespective of the place where the check occurs, or the source of the packet.
 If a rule contains some match patterns or actions which are not valid
 for the place of invocation (e.g.\& trying to match a MAC header within
 .Cm ip_input
 or
-.Cm ip6_input ),
+.Cm ip6_input ) Ns ,
 the match pattern will not match, but a
 .Cm not
 operator in front of such patterns
@@ -390,7 +417,7 @@
 .Cm skipto
 rules can be useful here, as an example:
 .Bd -literal -offset indent
-# packets from ether_demux or bdg_forward
+# packets from ether_demux or bridge
 ipfw add 10 skipto 1000 all from any to any layer2 in
 # packets from ip_input
 ipfw add 10 skipto 2000 all from any to any not layer2 in
@@ -401,7 +428,7 @@
 .Ed
 .Pp
 (yes, at the moment there is no way to differentiate between
-ether_demux and bdg_forward).
+ether_demux and bridge).
 .Sh SYNTAX
 In general, each keyword or argument must be provided as
 a separate command line argument, with no leading or trailing
@@ -1119,6 +1146,19 @@
 You can have comment-only rules, which are listed as having a
 .Cm count
 action followed by the comment.
+.It Cm arp-op Ar arp-op
+Matches Address Resolution Protocol (ARP) packets whose 
+.Em Operation
+field corresponds to one of those specified as argument.
+.Ar arp-op
+is specified in the same way as port numbers (i.e., one or more
+comma-separated single values or ranges).  You can use symbolic names
+for known values such as
+.Em request , reply , rev_request , rev_reply , inv_request , inv_reply .
+Values can be entered as decimal or hexadecimal (if prefixed by 0x), and
+they are always printed as hexadecimal (unless the
+.Cm -N
+option is used, in which case symbolic resolution will be attempted).
 .It Cm bridged
 Alias for
 .Cm layer2 .
@@ -1130,6 +1170,25 @@
 .It Cm diverted-output
 Matches only packets going from a divert socket back outward to the IP
 stack output for delivery.
+.It Cm dst-arp Ar dst-arp
+Matches Address Resolution Protocol (ARP) packets whose 
+.Em Target protocol address (TPA)
+and optionally
+.Em Target hardware address (THA)
+fields correspond to entry in the lookup table
+.Ar dst-arp .
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm dst-ether Ar dst-ether
+Match packets with a given destination MAC address
+.Ar dst-ether Ns ,
+specified as the
+.Cm any
+keyword (matching any MAC address),
+.Cm muticast
+keyword (matching multicast MAC addresses), or six groups of hex digits
+separated by colons.
 .It Cm dst-ip Ar ip-address
 Matches IPv4 packets whose destination IP is one of the address(es)
 specified as argument.
@@ -1141,6 +1200,19 @@
 specified as argument.
 .It Cm established
 Matches TCP packets that have the RST or ACK bits set.
+.It Cm ether-type Ar ether-type
+Matches packets whose Ethernet Type field
+corresponds to one of those specified as argument.
+.Ar ether-type
+is specified in the same way as
+.Cm port numbers
+(i.e., one or more comma-separated single values or ranges).
+You can use symbolic names for known values such as
+.Em vlan , ipv4, ipv6 .
+Values can be entered as decimal or hexadecimal (if prefixed by 0x),
+and they are always printed as hexadecimal (unless the
+.Cm -N
+option is used, in which case symbolic resolution will be attempted).
 .It Cm ext6hdr Ar header
 Matches IPv6 packets containing the extended header given by
 .Ar header .
@@ -1345,57 +1417,6 @@
 specified.
 Currently,
 only IPv4 flows are supported.
-.It Cm { MAC | mac } Ar dst-mac src-mac
-Match packets with a given
-.Ar dst-mac
-and
-.Ar src-mac
-addresses, specified as the
-.Cm any
-keyword (matching any MAC address), or six groups of hex digits
-separated by colons,
-and optionally followed by a mask indicating the significant bits.
-The mask may be specified using either of the following methods:
-.Bl -enum -width indent
-.It
-A slash
-.Pq /
-followed by the number of significant bits.
-For example, an address with 33 significant bits could be specified as:
-.Pp
-.Dl "MAC 10:20:30:40:50:60/33 any"
-.Pp
-.It
-An ampersand
-.Pq &
-followed by a bitmask specified as six groups of hex digits separated
-by colons.
-For example, an address in which the last 16 bits are significant could
-be specified as:
-.Pp
-.Dl "MAC 10:20:30:40:50:60&00:00:00:00:ff:ff any"
-.Pp
-Note that the ampersand character has a special meaning in many shells
-and should generally be escaped.
-.Pp
-.El
-Note that the order of MAC addresses (destination first,
-source second) is
-the same as on the wire, but the opposite of the one used for
-IP addresses.
-.It Cm mac-type Ar mac-type
-Matches packets whose Ethernet Type field
-corresponds to one of those specified as argument.
-.Ar mac-type
-is specified in the same way as
-.Cm port numbers
-(i.e., one or more comma-separated single values or ranges).
-You can use symbolic names for known values such as
-.Em vlan , ipv4, ipv6 .
-Values can be entered as decimal or hexadecimal (if prefixed by 0x),
-and they are always printed as hexadecimal (unless the
-.Cm -N
-option is used, in which case symbolic resolution will be attempted).
 .It Cm proto Ar protocol
 Matches packets with the corresponding IP protocol.
 .It Cm recv | xmit | via Brq Ar ifX | Ar if Ns Cm * | Ar ipno | Ar any
@@ -1444,6 +1465,40 @@
 Matches TCP packets that have the SYN bit set but no ACK bit.
 This is the short form of
 .Dq Li tcpflags\ syn,!ack .
+.It Cm state-options Ar spec
+Specifies options for dynamic rule creation by
+.Cm keep-state
+or
+.Cm limit .
+.Ar spec
+is comma separated list of options.
+The supported options are:
+.Bl -tag -width xxxxxxxx -compact
+.It Cm ether
+Enable layer 2 stateful filtering for a rule.
+Source and destination ethernet addresses (MAC addresses) are used to
+create a state entry (dynamic rule) and to check if packet matches any
+state entry.
+.El
+.It Cm src-arp Ar src-arp
+Matches Address Resolution Protocol (ARP) packets whose 
+.Em Sender protocol address (SPA)
+and optionally
+.Em Sender hardware address (SHA)
+fields correspond to entry in the lookup table
+.Ar src-arp .
+See the
+.Sx LOOKUP TABLES
+section below for more information on lookup tables.
+.It Cm src-ether Ar src-ether
+Match packets with a given source MAC address
+.Ar src-ether Ns ,
+specified as the
+.Cm any
+keyword (matching any MAC address),
+.Cm muticast
+keyword (matching multicast MAC addresses), or six groups of hex digits
+separated by colons.
 .It Cm src-ip Ar ip-address
 Matches IPv4 packets whose source IP is one of the address(es)
 specified as an argument.
@@ -1600,6 +1655,8 @@
 is not specified, it defaults to 32.
 When looking up an IP address in a table, the most specific
 entry will match.
+Optionally each entry specifies MAC address
+.Pq Cm ether Ar etheraddr Ns .
 Associated with each entry is a 32-bit unsigned
 .Ar value ,
 which can optionally be checked by a rule matching code.
@@ -1733,6 +1790,13 @@
 .Em dst
 are used here only to denote the initial match addresses, but they
 are completely equivalent afterwards).
+If rule specifies ethernet source or destination address it is also used
+by dynamic rule to match packets.
+But note that packets without
+.Cm l2tag
+appended to them match against such dynamic rules, because
+.Cm l2tag
+usually presents only in incoming or outgoing packets, but not in both.
 Dynamic rules will be checked at the first
 .Cm check-state, keep-state
 or
diff -urN -x .hg -x .svn ../my/sbin/ipfw/ipfw2.c ./sbin/ipfw/ipfw2.c
--- ../my/sbin/ipfw/ipfw2.c	2008-09-07 19:10:21.000000000 +0300
+++ ./sbin/ipfw/ipfw2.c	2008-09-07 22:24:43.000000000 +0300
@@ -52,6 +52,7 @@
 #include <net/ethernet.h>
 #include <net/if.h>
 #include <net/if_dl.h>
+#include <net/if_arp.h>
 #include <net/pfvar.h>
 #include <net/route.h> /* def. of struct route */
 #include <netinet/in.h>
@@ -188,6 +189,11 @@
 	{ NULL,	0 }
 };
 
+static struct _s_x f_stateopts[] = {
+	{ "ether",	IP_FW_STATEOPT_ETHER},
+	{ NULL,	0 }
+};
+
 static struct _s_x limit_masks[] = {
 	{"all",		DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT},
 	{"src-addr",	DYN_SRC_ADDR},
@@ -202,6 +208,7 @@
  * This is only used in this code.
  */
 #define IPPROTO_ETHERTYPE	0x1000
+#define IPPROTO_ARPOP		0x1001
 static struct _s_x ether_types[] = {
     /*
      * Note, we cannot use "-:&/" in the names because they are field
@@ -229,6 +236,15 @@
 	{ "ns",		0x0600 },
 	{ NULL,		0 }
 };
+static struct _s_x arp_ops[] = {
+	{ "request",		ARPOP_REQUEST },
+	{ "reply",		ARPOP_REPLY },
+	{ "rev_request",	ARPOP_REVREQUEST  },
+	{ "rev_reply",		ARPOP_REVREPLY },
+	{ "inv_request",	ARPOP_INVREQUEST },
+	{ "inv_reply",		ARPOP_INVREPLY },
+	{ NULL,		0 }
+};
 
 static void show_usage(void);
 
@@ -294,8 +310,10 @@
 	TOK_TCPACK,
 	TOK_TCPWIN,
 	TOK_ICMPTYPES,
-	TOK_MAC,
-	TOK_MACTYPE,
+	TOK_ETHER,
+	TOK_ETHER_SRC,
+	TOK_ETHER_DST,
+	TOK_ETHER_TYPE,
 	TOK_VERREVPATH,
 	TOK_VERSRCREACH,
 	TOK_ANTISPOOF,
@@ -344,6 +362,12 @@
 
 	TOK_FIB,
 	TOK_SETFIB,
+
+	TOK_STATEOPTS,
+
+	TOK_ARP_OP,
+	TOK_ARP_SRC,
+	TOK_ARP_DST,
 };
 
 struct _s_x dummynet_params[] = {
@@ -475,9 +499,13 @@
 	{ "dst-port",		TOK_DSTPORT },
 	{ "src-port",		TOK_SRCPORT },
 	{ "proto",		TOK_PROTO },
-	{ "MAC",		TOK_MAC },
-	{ "mac",		TOK_MAC },
-	{ "mac-type",		TOK_MACTYPE },
+	{ "MAC",		TOK_ETHER },
+	{ "mac",		TOK_ETHER },
+	{ "ether",		TOK_ETHER },
+	{ "src-ether",		TOK_ETHER_SRC },
+	{ "dst-ether",		TOK_ETHER_DST },
+	{ "mac-type",		TOK_ETHER_TYPE },
+	{ "ether-type",		TOK_ETHER_TYPE },
 	{ "verrevpath",		TOK_VERREVPATH },
 	{ "versrcreach",	TOK_VERSRCREACH },
 	{ "antispoof",		TOK_ANTISPOOF },
@@ -494,6 +522,11 @@
 	{ "dst-ip6",		TOK_DSTIP6},
 	{ "src-ipv6",		TOK_SRCIP6},
 	{ "src-ip6",		TOK_SRCIP6},
+	{ "state-options",	TOK_STATEOPTS },
+	{ "state-opts",		TOK_STATEOPTS },
+	{ "arp-op",		TOK_ARP_OP},
+	{ "src-arp",		TOK_ARP_SRC},
+	{ "dst-arp",		TOK_ARP_DST},
 	{ "//",			TOK_COMMENT },
 
 	{ "not",		TOK_NOT },		/* pseudo option */
@@ -639,6 +672,13 @@
 			printf("%s", s);
 		else
 			printf("0x%04x", port);
+	} else if (proto == IPPROTO_ARPOP) { 
+		char const *s;
+
+		if (do_resolv && (s = match_value(arp_ops, port)) )
+			printf("%s", s);
+		else
+			printf("0x%04x", port);
 	} else {
 		struct servent *se = NULL;
 		if (do_resolv) {
@@ -659,7 +699,8 @@
 	{"ipid",	O_IPID},
 	{"iplen",	O_IPLEN},
 	{"ipttl",	O_IPTTL},
-	{"mac-type",	O_MAC_TYPE},
+	{"ether-type",	O_ETHER_TYPE},
+	{"arp-op",	O_ARP_OP},
 	{"tcpdatalen",	O_TCPDATALEN},
 	{"tagged",	O_TAGGED},
 	{NULL,		0}
@@ -700,6 +741,7 @@
  * In particular:
  *	proto == -1 disables the protocol check;
  *	proto == IPPROTO_ETHERTYPE looks up an internal table
+ *	proto == IPPROTO_ARPOP looks up an internal table
  *	proto == <some value in /etc/protocols> matches the values there.
  * Returns *end == s in case the parameter is not found.
  */
@@ -743,6 +785,13 @@
 			*end = s1;
 			return i;
 		}
+	} else if (proto == IPPROTO_ARPOP) {
+		i = match_token(arp_ops, buf);
+		free(buf);
+		if (i != -1) {	/* found */
+			*end = s1;
+			return i;
+		}
 	} else {
 		struct protoent *pe = NULL;
 		struct servent *se;
@@ -1130,24 +1179,19 @@
 }
 
 /*
- * prints a MAC address/mask pair
+ * prints a ethernet (MAC) address/mask pair
  */
 static void
-print_mac(uint8_t *addr, uint8_t *mask)
+print_ether(ipfw_ether_addr *addr)
 {
-	int l = contigmask(mask, 48);
-
-	if (l == 0)
+	if ((addr->flags & IPFW_EA_CHECK) == 0) {
 		printf(" any");
-	else {
+	} else if (addr->flags & IPFW_EA_MULTICAST) {
+		printf(" multicast");
+	} else {
+		u_char *ea = addr->octet;
 		printf(" %02x:%02x:%02x:%02x:%02x:%02x",
-		    addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
-		if (l == -1)
-			printf("&%02x:%02x:%02x:%02x:%02x:%02x",
-			    mask[0], mask[1], mask[2],
-			    mask[3], mask[4], mask[5]);
-		else if (l < 48)
-			printf("/%d", l);
+		    ea[0], ea[1], ea[2], ea[3], ea[4], ea[5]);
 	}
 }
 
@@ -1417,7 +1461,7 @@
  * The first argument is the list of fields we have, the second is
  * the list of fields we want to be printed.
  *
- * Special cases if we have provided a MAC header:
+ * Special cases if we have provided a ethernet header:
  *   + if the rule does not contain IP addresses/ports, do not print them;
  *   + if the rule does not contain an IP proto, print "all" instead of "ip";
  *
@@ -1807,16 +1851,23 @@
 			if (cmd->len & F_NOT && cmd->opcode != O_IN)
 				printf(" not");
 			switch(cmd->opcode) {
-			case O_MACADDR2: {
-				ipfw_insn_mac *m = (ipfw_insn_mac *)cmd;
+			case O_ETHER_SRC: {
+				ipfw_insn_ether *m = (ipfw_insn_ether *)cmd;
 
-				printf(" MAC");
-				print_mac(m->addr, m->mask);
-				print_mac(m->addr + 6, m->mask + 6);
+				printf(" src-ether");
+				print_ether(&m->ether);
 				}
 				break;
 
-			case O_MAC_TYPE:
+			case O_ETHER_DST: {
+				ipfw_insn_ether *m = (ipfw_insn_ether *)cmd;
+
+				printf(" dst-ether");
+				print_ether(&m->ether);
+				}
+				break;
+
+			case O_ETHER_TYPE:
 				print_newports((ipfw_insn_u16 *)cmd,
 						IPPROTO_ETHERTYPE, cmd->opcode);
 				break;
@@ -1830,6 +1881,21 @@
 				printf(" fib %u", cmd->arg1 );
 				break;
 
+			case O_ARP_OP:
+				print_newports((ipfw_insn_u16 *)cmd,
+						IPPROTO_ARPOP, cmd->opcode);
+				break;
+
+			case O_ARP_SRC_LOOKUP:
+			case O_ARP_DST_LOOKUP:
+				printf(" %s-arp table(%u", 
+				    cmd->opcode == O_ARP_DST_LOOKUP ? "dst" : "src",
+				    ((ipfw_insn *)cmd)->arg1);
+				if (F_LEN((ipfw_insn *)cmd) == F_INSN_SIZE(ipfw_insn_u32))
+					printf(",%u", *((ipfw_insn_u32 *)cmd)->d);
+				printf(")");
+				break;
+
 			case O_IN:
 				printf(cmd->len & F_NOT ? " out" : " in");
 				break;
@@ -1997,6 +2063,10 @@
 				comment = (char *)(cmd + 1);
 				break;
 
+			case O_STATEOPTS:
+				print_flags("state-options", cmd, f_stateopts);
+				break;
+
 			case O_KEEP_STATE:
 				printf(" keep-state");
 				break;
@@ -2720,7 +2790,7 @@
 "		redirect_port linkspec|redirect_proto linkspec}\n"
 "set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
 "set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n"
-"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"
+"table N {add ip[/bits] [ether ETHERADDR] [value] | delete ip[/bits] | flush | list}\n"
 "\n"
 "RULE-BODY:	check-state [PARAMS] | ACTION [PARAMS] ADDR [OPTION_LIST]\n"
 "ACTION:	check-state | allow | count | deny | unreach{,6} CODE |\n"
@@ -2734,17 +2804,20 @@
 "IP6ADDR:	[not] { any | me | me6 | ip6/bits | IP6LIST }\n"
 "IP6LIST:	{ ip6 | ip6/bits }[,IP6LIST]\n"
 "IPLIST:	{ ip | ip/bits | ip:mask }[,IPLIST]\n"
+"ETHERADDR:	{ any | multicast | ether }\n"
 "OPTION_LIST:	OPTION [OPTION_LIST]\n"
-"OPTION:	bridged | diverted | diverted-loopback | diverted-output |\n"
+"OPTION:	arp-op LIST | bridged | diverted | diverted-loopback |\n"
+"	{dst-arp|src-arp} table(t[,v]) | diverted-output |\n"
 "	{dst-ip|src-ip} IPADDR | {dst-ip6|src-ip6|dst-ipv6|src-ipv6} IP6ADDR |\n"
 "	{dst-port|src-port} LIST |\n"
 "	estab | frag | {gid|uid} N | icmptypes LIST | in | out | ipid LIST |\n"
 "	iplen LIST | ipoptions SPEC | ipprecedence | ipsec | iptos SPEC |\n"
 "	ipttl LIST | ipversion VER | keep-state | layer2 | limit ... |\n"
 "	icmp6types LIST | ext6hdr LIST | flow-id N[,N] | fib FIB |\n"
-"	mac ... | mac-type LIST | proto LIST | {recv|xmit|via} {IF|IPADDR} |\n"
-"	setup | {tcpack|tcpseq|tcpwin} NN | tcpflags SPEC | tcpoptions SPEC |\n"
-"	tcpdatalen LIST | verrevpath | versrcreach | antispoof\n"
+"	{src-ether|dst-ether} ETHERADDR | ether-type LIST | proto LIST |\n"
+"	{recv|xmit|via} {IF|IPADDR} | setup | {tcpack|tcpseq|tcpwin} NN |\n"
+"	tcpflags SPEC | tcpoptions SPEC | tcpdatalen LIST |\n"
+"	verrevpath | versrcreach | antispoof\n"
 );
 exit(0);
 }
@@ -4446,50 +4519,27 @@
 }
 
 static void
-get_mac_addr_mask(const char *p, uint8_t *addr, uint8_t *mask)
+get_ether_addr(const char *p, ipfw_ether_addr *addr)
 {
 	int i, l;
-	char *ap, *ptr, *optr;
-	struct ether_addr *mac;
-	const char *macset = "0123456789abcdefABCDEF:";
+	struct ether_addr *ether;
+	const char *etherset = "0123456789abcdefABCDEF:";
 
+	bzero(addr, sizeof(*addr));
 	if (strcmp(p, "any") == 0) {
-		for (i = 0; i < ETHER_ADDR_LEN; i++)
-			addr[i] = mask[i] = 0;
 		return;
 	}
-
-	optr = ptr = strdup(p);
-	if ((ap = strsep(&ptr, "&/")) != NULL && *ap != 0) {
-		l = strlen(ap);
-		if (strspn(ap, macset) != l || (mac = ether_aton(ap)) == NULL)
-			errx(EX_DATAERR, "Incorrect MAC address");
-		bcopy(mac, addr, ETHER_ADDR_LEN);
-	} else
-		errx(EX_DATAERR, "Incorrect MAC address");
-
-	if (ptr != NULL) { /* we have mask? */
-		if (p[ptr - optr - 1] == '/') { /* mask len */
-			l = strtol(ptr, &ap, 10);
-			if (*ap != 0 || l > ETHER_ADDR_LEN * 8 || l < 0)
-				errx(EX_DATAERR, "Incorrect mask length");
-			for (i = 0; l > 0 && i < ETHER_ADDR_LEN; l -= 8, i++)
-				mask[i] = (l >= 8) ? 0xff: (~0) << (8 - l);
-		} else { /* mask */
-			l = strlen(ptr);
-			if (strspn(ptr, macset) != l ||
-			    (mac = ether_aton(ptr)) == NULL)
-				errx(EX_DATAERR, "Incorrect mask");
-			bcopy(mac, mask, ETHER_ADDR_LEN);
-		}
-	} else { /* default mask: ff:ff:ff:ff:ff:ff */
-		for (i = 0; i < ETHER_ADDR_LEN; i++)
-			mask[i] = 0xff;
+	if (strcmp(p, "multicast") == 0) {
+		addr->flags = IPFW_EA_CHECK | IPFW_EA_MULTICAST;
+		return;
 	}
-	for (i = 0; i < ETHER_ADDR_LEN; i++)
-		addr[i] &= mask[i];
 
-	free(optr);
+	if (strspn(p, etherset) != strlen(p) ||
+			(ether = ether_aton(p)) == NULL)
+		errx(EX_DATAERR, "Incorrect ethernet (MAC) address");
+
+	memcpy(addr->octet, ether, ETHER_ADDR_LEN);
+	addr->flags = IPFW_EA_CHECK;
 }
 
 /*
@@ -4552,31 +4602,44 @@
  * two microinstructions, and returns the pointer to the last one.
  */
 static ipfw_insn *
-add_mac(ipfw_insn *cmd, int ac, char *av[])
+add_ether(ipfw_insn *cmd, int opcode, char *arg)
 {
-	ipfw_insn_mac *mac;
+	ipfw_insn_ether *ether;
 
-	if (ac < 2)
-		errx(EX_DATAERR, "MAC dst src");
-
-	cmd->opcode = O_MACADDR2;
-	cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac);
+	cmd->opcode = opcode;
+	cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_ether);
 
-	mac = (ipfw_insn_mac *)cmd;
-	get_mac_addr_mask(av[0], mac->addr, mac->mask);	/* dst */
-	get_mac_addr_mask(av[1], &(mac->addr[ETHER_ADDR_LEN]),
-	    &(mac->mask[ETHER_ADDR_LEN])); /* src */
+	ether = (ipfw_insn_ether *)cmd;
+	get_ether_addr(arg, &ether->ether);
 	return cmd;
 }
 
 static ipfw_insn *
-add_mactype(ipfw_insn *cmd, int ac, char *av)
+add_ether_src(ipfw_insn *cmd, int ac, char *av[])
+{
+	if (ac < 1)
+		errx(EX_DATAERR, "src-ether src");
+
+	return add_ether(cmd, O_ETHER_SRC, av[0]);
+}
+
+static ipfw_insn *
+add_ether_dst(ipfw_insn *cmd, int ac, char *av[])
 {
 	if (ac < 1)
-		errx(EX_DATAERR, "missing MAC type");
+		errx(EX_DATAERR, "dst-ether dst");
+
+	return add_ether(cmd, O_ETHER_DST, av[0]);
+}
+
+static ipfw_insn *
+add_ethertype(ipfw_insn *cmd, int ac, char *av)
+{
+	if (ac < 1)
+		errx(EX_DATAERR, "missing ether-type argument");
 	if (strcmp(av, "any") != 0) { /* we have a non-null type */
 		fill_newports((ipfw_insn_u16 *)cmd, av, IPPROTO_ETHERTYPE);
-		cmd->opcode = O_MAC_TYPE;
+		cmd->opcode = O_ETHER_TYPE;
 		return cmd;
 	} else
 		return NULL;
@@ -5585,16 +5648,31 @@
 				    *av);
 			break;
 
-		case TOK_MAC:
-			if (add_mac(cmd, ac, av)) {
-				ac -= 2; av += 2;
+		case TOK_ETHER:
+			if (ac >= 2 && add_ether_dst(cmd, ac, av)) {
+				/*
+				 * XXX will not allocate next command here
+				 */
+				av[0] = strdup("src-ether");
 			}
 			break;
 
-		case TOK_MACTYPE:
-			NEED1("missing mac type");
-			if (!add_mactype(cmd, ac, *av))
-				errx(EX_DATAERR, "invalid mac type %s", *av);
+		case TOK_ETHER_SRC:
+			if (add_ether_src(cmd, ac, av)) {
+				ac--; av++;
+			}
+			break;
+
+		case TOK_ETHER_DST:
+			if (add_ether_dst(cmd, ac, av)) {
+				ac--; av++;
+			}
+			break;
+
+		case TOK_ETHER_TYPE:
+			NEED1("missing ether type");
+			if (!add_ethertype(cmd, ac, *av))
+				errx(EX_DATAERR, "invalid ether type %s", *av);
 			ac--; av++;
 			break;
 
@@ -5663,6 +5741,37 @@
 			ac--; av++;
 			break;
 
+		case TOK_ARP_OP:
+			NEED1("missing arp operation");
+			if (strcmp(*av, "any") != 0) {
+				if (!fill_newports((ipfw_insn_u16 *)cmd, *av, IPPROTO_ARPOP))
+					errx(EX_DATAERR, "invalid arp operation %s", *av);
+				cmd->opcode = O_ARP_OP;
+			}
+			ac--; av++;
+			break;
+
+		case TOK_STATEOPTS:
+			NEED1("missing argument for state-options");
+			fill_flags(cmd, O_STATEOPTS, f_stateopts, *av);
+			if ((cmd->arg1 >> 8) & 0xff) /* clear flags specified */
+				errx(EX_DATAERR, "invalid state-options %s", *av);
+			ac--; av++;
+			break;
+
+		case TOK_ARP_SRC:
+		case TOK_ARP_DST:
+			NEED1("missing lookup table argument");
+			fill_ip((ipfw_insn_ip *)cmd, *av);
+			if (cmd->opcode != O_IP_DST_LOOKUP)	/* table */
+				errx(EX_USAGE, "invalid lookup table %s\n", *av);
+			if (i == TOK_ARP_DST)
+				cmd->opcode = O_ARP_DST_LOOKUP;
+			else
+				cmd->opcode = O_ARP_SRC_LOOKUP;
+			ac--; av++;
+			break;
+
 		default:
 			errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s);
 		}
@@ -5898,6 +6007,11 @@
 		if (lookup_host(*av, (struct in_addr *)&ent.addr) != 0)
 			errx(EX_NOHOST, "hostname ``%s'' unknown", *av);
 		ac--; av++;
+		bzero(&ent.ether_addr, sizeof(ent.ether_addr));
+		if (do_add && ac >= 2 && strcmp(*av, "ether") == 0) {
+			get_ether_addr(av[1], &ent.ether_addr);
+			ac-=2; av+=2;
+		}
 		if (do_add && ac) {
 			unsigned int tval;
 			/* isdigit is a bit of a hack here.. */
@@ -5946,20 +6060,28 @@
 			err(EX_OSERR, "getsockopt(IP_FW_TABLE_LIST)");
 		for (a = 0; a < tbl->cnt; a++) {
 			unsigned int tval;
+			char tval_buf[128];
+			char tether_buf[128];
 			tval = tbl->ent[a].value;
 			if (do_value_as_ip) {
-			    char tbuf[128];
-			    strncpy(tbuf, inet_ntoa(*(struct in_addr *)
-				&tbl->ent[a].addr), 127);
 			    /* inet_ntoa expects network order */
 			    tval = htonl(tval);
-			    printf("%s/%u %s\n", tbuf, tbl->ent[a].masklen,
-			        inet_ntoa(*(struct in_addr *)&tval));
+			    strlcpy(tval_buf, inet_ntoa(*(struct in_addr *)
+				&tval), sizeof(tval_buf));
+			} else {
+			    snprintf(tval_buf, sizeof(tval_buf), "%u", tval);
+			}
+			if (tbl->ent[a].ether_addr.flags & IPFW_EA_CHECK) {
+			    uint8_t *x = (uint8_t *)&tbl->ent[a].ether_addr;
+		            snprintf(tether_buf, sizeof(tether_buf), "ether %02x:%02x:%02x:%02x:%02x:%02x ",
+		                 x[0], x[1], x[2], x[3], x[4], x[5]);
 			} else {
-			    printf("%s/%u %u\n",
-			        inet_ntoa(*(struct in_addr *)&tbl->ent[a].addr),
-			        tbl->ent[a].masklen, tval);
+			    tether_buf[0] = 0;
 			}
+
+			printf("%s/%u %s%s\n",
+			    inet_ntoa(*(struct in_addr *)&tbl->ent[a].addr),
+			    tbl->ent[a].masklen, tether_buf, tval_buf);
 		}
 	} else
 		errx(EX_USAGE, "invalid table command %s", *av);
diff -urN -x .hg -x .svn ../my/share/man/man4/if_bridge.4 ./share/man/man4/if_bridge.4
--- ../my/share/man/man4/if_bridge.4	2008-09-07 19:11:06.000000000 +0300
+++ ./share/man/man4/if_bridge.4	2008-09-07 22:24:44.000000000 +0300
@@ -171,6 +171,14 @@
 to only allow IP packets to pass (subject to firewall rules), set to
 .Li 0
 to unconditionally pass all non-IP Ethernet frames.
+.It Va net.link.bridge.pfil_layer2_arp
+Set to
+.Li 1
+to enable layer2 ARP filtering with
+.Xr pfil 9 ,
+set to
+.Li 0
+to disable it.
 .It Va net.link.bridge.pfil_member
 Set to
 .Li 1
@@ -192,36 +200,6 @@
 Set to
 .Li 0
 to disable this feature.
-.It Va net.link.bridge.ipfw
-Set to
-.Li 1
-to enable layer2 filtering with
-.Xr ipfirewall 4 ,
-set to
-.Li 0
-to disable it.
-This needs to be enabled for
-.Xr dummynet 4
-support.
-When
-.Va ipfw
-is enabled,
-.Va pfil_bridge
-and
-.Va pfil_member
-will be disabled so that IPFW
-is not run twice; these can be re-enabled if desired.
-.It Va net.link.bridge.ipfw_arp
-Set to
-.Li 1
-to enable layer2 ARP filtering with
-.Xr ipfirewall 4 ,
-set to
-.Li 0
-to disable it.
-Requires
-.Va ipfw
-to be enabled.
 .El
 .Pp
 ARP and REVARP packets are forwarded without being filtered and others
diff -urN -x .hg -x .svn ../my/sys/contrib/pf/net/pf.c ./sys/contrib/pf/net/pf.c
--- ../my/sys/contrib/pf/net/pf.c	2008-09-07 19:12:21.000000000 +0300
+++ ./sys/contrib/pf/net/pf.c	2008-09-07 22:24:44.000000000 +0300
@@ -337,6 +337,8 @@
 			    kif, &key, PF_LAN_EXT);			\
 		if (*state == NULL || (*state)->timeout == PFTM_PURGE)	\
 			return (PF_DROP);				\
+		if (!pf_state_match_addr_ether(*state, pd, direction))	\
+			return (PF_DROP);				\
 		if (direction == PF_OUT &&				\
 		    (((*state)->rule.ptr->rt == PF_ROUTETO &&		\
 		    (*state)->rule.ptr->direction == PF_OUT) ||		\
@@ -701,6 +703,35 @@
 	}
 }
 
+static __inline int
+pf_state_match_addr_ether(struct pf_state *state, struct pf_pdesc *pd, int direction)
+{
+	struct pf_addr_ether	*src, *dst;
+
+#ifdef __FreeBSD__
+	if ((state->local_flags & PFSTATE_ETHER) == 0)
+		return (1);
+#else
+	/* XXX only FreeBSD is supported */
+	if ((state->rule.ptr->rule_flag & PFRULE_ETHERSTATE) == 0)
+		return (1);
+#endif
+
+	if (direction == PF_IN) {
+		src = &state->ext.addr_ether;
+		dst = &state->gwy.addr_ether;
+	} else {
+		src = &state->lan.addr_ether;
+		dst = &state->ext.addr_ether;
+	}
+
+	if (pf_match_addr_ether(src, &pd->src_ether, 1) &&
+		pf_match_addr_ether(dst, &pd->dst_ether, 1))
+		return (1);
+
+	return (0);
+}
+
 void
 pf_init_threshold(struct pf_threshold *threshold,
     u_int32_t limit, u_int32_t seconds)
@@ -2108,6 +2139,26 @@
 }
 
 int
+pf_match_addr_ether(struct pf_addr_ether *want, struct pf_addr_ether *a, int match_empty)
+{
+	static struct pf_addr_ether mask = {
+		.octet = { 0xff, 0xff, 0xff, 0xff, 0xff,0xff },
+		.flags = 0
+	};
+	if (want == NULL || (want->flags & PFAE_CHECK) == 0)
+		return (1);
+	if (a == NULL || (a->flags & PFAE_CHECK) == 0)
+		return (match_empty);
+
+	if (want->flags & PFAE_MULTICAST) {
+		return (ETHER_IS_MULTICAST(a->octet));
+	}
+#define EA_CMP(x) (*((u_int64_t*)(x)) & *((u_int64_t*)&mask))
+	return (EA_CMP(want) == EA_CMP(a));
+#undef EA_CMP
+}
+
+int
 pf_match(u_int8_t op, u_int32_t a1, u_int32_t a2, u_int32_t p)
 {
 	switch (op) {
@@ -3315,14 +3366,14 @@
 			r = r->skip[PF_SKIP_AF].ptr;
 		else if (r->proto && r->proto != IPPROTO_TCP)
 			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, saddr, af,
-		    r->src.neg, kif))
+		else if (PF_MISMATCHAW_L2(&r->src.addr, saddr, &pd->src_ether,
+		    af, r->src.neg, kif))
 			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
 		else if (r->src.port_op && !pf_match_port(r->src.port_op,
 		    r->src.port[0], r->src.port[1], th->th_sport))
 			r = r->skip[PF_SKIP_SRC_PORT].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, daddr, af,
-		    r->dst.neg, NULL))
+		else if (PF_MISMATCHAW_L2(&r->dst.addr, daddr, &pd->dst_ether,
+		    af, r->dst.neg, NULL))
 			r = r->skip[PF_SKIP_DST_ADDR].ptr;
 		else if (r->dst.port_op && !pf_match_port(r->dst.port_op,
 		    r->dst.port[0], r->dst.port[1], th->th_dport))
@@ -3506,11 +3557,17 @@
 		s->proto = IPPROTO_TCP;
 		s->direction = direction;
 		s->af = af;
+#ifdef __FreeBSD__
+		if (r->rule_flag & PFRULE_ETHERSTATE)
+			s->local_flags |= PFSTATE_ETHER;
+#endif
 		if (direction == PF_OUT) {
 			PF_ACPY(&s->gwy.addr, saddr, af);
 			s->gwy.port = th->th_sport;		/* sport */
+			s->gwy.addr_ether = pd->src_ether;
 			PF_ACPY(&s->ext.addr, daddr, af);
 			s->ext.port = th->th_dport;
+			s->ext.addr_ether = pd->dst_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->lan.addr, &pd->baddr, af);
 				s->lan.port = bport;
@@ -3521,8 +3578,10 @@
 		} else {
 			PF_ACPY(&s->lan.addr, daddr, af);
 			s->lan.port = th->th_dport;
+			s->lan.addr_ether = pd->dst_ether;
 			PF_ACPY(&s->ext.addr, saddr, af);
 			s->ext.port = th->th_sport;
+			s->ext.addr_ether = pd->src_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->gwy.addr, &pd->baddr, af);
 				s->gwy.port = bport;
@@ -3737,14 +3796,14 @@
 			r = r->skip[PF_SKIP_AF].ptr;
 		else if (r->proto && r->proto != IPPROTO_UDP)
 			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, saddr, af,
-		    r->src.neg, kif))
+		else if (PF_MISMATCHAW_L2(&r->src.addr, saddr, &pd->src_ether,
+		    af, r->src.neg, kif))
 			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
 		else if (r->src.port_op && !pf_match_port(r->src.port_op,
 		    r->src.port[0], r->src.port[1], uh->uh_sport))
 			r = r->skip[PF_SKIP_SRC_PORT].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, daddr, af,
-		    r->dst.neg, NULL))
+		else if (PF_MISMATCHAW_L2(&r->dst.addr, daddr, &pd->dst_ether,
+		    af, r->dst.neg, NULL))
 			r = r->skip[PF_SKIP_DST_ADDR].ptr;
 		else if (r->dst.port_op && !pf_match_port(r->dst.port_op,
 		    r->dst.port[0], r->dst.port[1], uh->uh_dport))
@@ -3903,11 +3962,17 @@
 		s->proto = IPPROTO_UDP;
 		s->direction = direction;
 		s->af = af;
+#ifdef __FreeBSD__
+		if (r->rule_flag & PFRULE_ETHERSTATE)
+			s->local_flags |= PFSTATE_ETHER;
+#endif
 		if (direction == PF_OUT) {
 			PF_ACPY(&s->gwy.addr, saddr, af);
 			s->gwy.port = uh->uh_sport;
+			s->gwy.addr_ether = pd->src_ether;
 			PF_ACPY(&s->ext.addr, daddr, af);
 			s->ext.port = uh->uh_dport;
+			s->ext.addr_ether = pd->dst_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->lan.addr, &pd->baddr, af);
 				s->lan.port = bport;
@@ -3918,8 +3983,10 @@
 		} else {
 			PF_ACPY(&s->lan.addr, daddr, af);
 			s->lan.port = uh->uh_dport;
+			s->lan.addr_ether = pd->dst_ether;
 			PF_ACPY(&s->ext.addr, saddr, af);
 			s->ext.port = uh->uh_sport;
+			s->ext.addr_ether = pd->src_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->gwy.addr, &pd->baddr, af);
 				s->gwy.port = bport;
@@ -4094,11 +4161,11 @@
 			r = r->skip[PF_SKIP_AF].ptr;
 		else if (r->proto && r->proto != pd->proto)
 			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, saddr, af,
-		    r->src.neg, kif))
+		else if (PF_MISMATCHAW_L2(&r->src.addr, saddr, &pd->src_ether,
+		    af, r->src.neg, kif))
 			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, daddr, af,
-		    r->dst.neg, NULL))
+		else if (PF_MISMATCHAW_L2(&r->dst.addr, daddr, &pd->dst_ether,
+		    af, r->dst.neg, NULL))
 			r = r->skip[PF_SKIP_DST_ADDR].ptr;
 		else if (r->type && r->type != icmptype + 1)
 			r = TAILQ_NEXT(r, entries);
@@ -4216,11 +4283,17 @@
 		s->proto = pd->proto;
 		s->direction = direction;
 		s->af = af;
+#ifdef __FreeBSD__
+		if (r->rule_flag & PFRULE_ETHERSTATE)
+			s->local_flags |= PFSTATE_ETHER;
+#endif
 		if (direction == PF_OUT) {
 			PF_ACPY(&s->gwy.addr, saddr, af);
 			s->gwy.port = nport;
+			s->gwy.addr_ether = pd->src_ether;
 			PF_ACPY(&s->ext.addr, daddr, af);
 			s->ext.port = 0;
+			s->ext.addr_ether = pd->dst_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->lan.addr, &pd->baddr, af);
 				s->lan.port = bport;
@@ -4231,8 +4304,10 @@
 		} else {
 			PF_ACPY(&s->lan.addr, daddr, af);
 			s->lan.port = nport;
+			s->lan.addr_ether = pd->dst_ether;
 			PF_ACPY(&s->ext.addr, saddr, af);
 			s->ext.port = 0; 
+			s->ext.addr_ether = pd->src_ether;
 			if (nr != NULL) {
 				PF_ACPY(&s->gwy.addr, &pd->baddr, af);
 				s->gwy.port = bport;
@@ -4357,11 +4432,11 @@
 			r = r->skip[PF_SKIP_AF].ptr;
 		else if (r->proto && r->proto != pd->proto)
 			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, pd->src, af,
-		    r->src.neg, kif))
+		else if (PF_MISMATCHAW_L2(&r->src.addr, pd->src, &pd->src_ether,
+		    af, r->src.neg, kif))
 			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af,
-		    r->dst.neg, NULL))
+		else if (PF_MISMATCHAW_L2(&r->dst.addr, pd->dst, &pd->dst_ether,
+		    af, r->dst.neg, NULL))
 			r = r->skip[PF_SKIP_DST_ADDR].ptr;
 		else if (r->tos && !(r->tos == pd->tos))
 			r = TAILQ_NEXT(r, entries);
@@ -4503,16 +4578,24 @@
 		s->proto = pd->proto;
 		s->direction = direction;
 		s->af = af;
+#ifdef __FreeBSD__
+		if (r->rule_flag & PFRULE_ETHERSTATE)
+			s->local_flags |= PFSTATE_ETHER;
+#endif
 		if (direction == PF_OUT) {
 			PF_ACPY(&s->gwy.addr, saddr, af);
+			s->gwy.addr_ether = pd->src_ether;
 			PF_ACPY(&s->ext.addr, daddr, af);
+			s->ext.addr_ether = pd->dst_ether;
 			if (nr != NULL)
 				PF_ACPY(&s->lan.addr, &pd->baddr, af);
 			else
 				PF_ACPY(&s->lan.addr, &s->gwy.addr, af);
 		} else {
 			PF_ACPY(&s->lan.addr, daddr, af);
+			s->lan.addr_ether = pd->dst_ether;
 			PF_ACPY(&s->ext.addr, saddr, af);
+			s->ext.addr_ether = pd->src_ether;
 			if (nr != NULL)
 				PF_ACPY(&s->gwy.addr, &pd->baddr, af);
 			else
@@ -4574,11 +4657,11 @@
 			r = r->skip[PF_SKIP_AF].ptr;
 		else if (r->proto && r->proto != pd->proto)
 			r = r->skip[PF_SKIP_PROTO].ptr;
-		else if (PF_MISMATCHAW(&r->src.addr, pd->src, af,
-		    r->src.neg, kif))
+		else if (PF_MISMATCHAW_L2(&r->src.addr, pd->src, &pd->src_ether,
+		    af, r->src.neg, kif))
 			r = r->skip[PF_SKIP_SRC_ADDR].ptr;
-		else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af,
-		    r->dst.neg, NULL))
+		else if (PF_MISMATCHAW_L2(&r->dst.addr, pd->dst, &pd->dst_ether,
+		    af, r->dst.neg, NULL))
 			r = r->skip[PF_SKIP_DST_ADDR].ptr;
 		else if (r->tos && !(r->tos == pd->tos))
 			r = TAILQ_NEXT(r, entries);
@@ -6866,6 +6949,12 @@
 	pd.tos = h->ip_tos;
 	pd.tot_len = ntohs(h->ip_len);
 	pd.eh = eh;
+	if (eh) {
+		memcpy(pd.src_ether.octet, eh->ether_shost, ETHER_ADDR_LEN);
+		pd.src_ether.flags = PFAE_CHECK;
+		memcpy(pd.dst_ether.octet, eh->ether_dhost, ETHER_ADDR_LEN);
+		pd.dst_ether.flags = PFAE_CHECK;
+	}
 
 	/* handle fragments that didn't get reassembled by normalization */
 	if (h->ip_off & htons(IP_MF | IP_OFFMASK)) {
@@ -7260,6 +7349,12 @@
 	pd.tos = 0;
 	pd.tot_len = ntohs(h->ip6_plen) + sizeof(struct ip6_hdr);
 	pd.eh = eh;
+	if (eh) {
+		memcpy(pd.src_ether.octet, eh->ether_shost, ETHER_ADDR_LEN);
+		pd.src_ether.flags = PFAE_CHECK;
+		memcpy(pd.dst_ether.octet, eh->ether_dhost, ETHER_ADDR_LEN);
+		pd.dst_ether.flags = PFAE_CHECK;
+	}
 
 	off = ((caddr_t)h - m->m_data) + sizeof(struct ip6_hdr);
 	pd.proto = h->ip6_nxt;
diff -urN -x .hg -x .svn ../my/sys/contrib/pf/net/pf_ioctl.c ./sys/contrib/pf/net/pf_ioctl.c
--- ../my/sys/contrib/pf/net/pf_ioctl.c	2008-09-07 19:12:21.000000000 +0300
+++ ./sys/contrib/pf/net/pf_ioctl.c	2008-09-07 22:24:44.000000000 +0300
@@ -112,6 +112,7 @@
 
 #ifdef __FreeBSD__
 #include <sys/md5.h>
+#include <net/ethernet.h>
 #else
 #include <dev/rndvar.h>
 #include <crypto/md5.h>
@@ -3637,6 +3638,7 @@
 	 * byte order. 
 	 */
 	struct ip *h = NULL;
+	struct m_tag *tag_ether_hdr;
 	int chk;
 
 	if ((*m)->m_pkthdr.len >= (int)sizeof(struct ip)) {
@@ -3645,7 +3647,10 @@
 	        HTONS(h->ip_len);
 	        HTONS(h->ip_off);
 	}
-	chk = pf_test(PF_IN, ifp, m, NULL, inp);
+	tag_ether_hdr = m_tag_locate(*m, MTAG_ETHER, MTAG_ETHER_HEADER, NULL);
+	chk = pf_test(PF_IN, ifp, m, 
+	    tag_ether_hdr ? (struct ether_header *)(tag_ether_hdr + 1) : NULL, 
+	    inp);
 	if (chk && *m) {
 		m_freem(*m);
 		*m = NULL;
@@ -3672,6 +3677,7 @@
 	 * byte order. 
 	 */
 	struct ip *h = NULL;
+	struct m_tag *tag_ether_hdr;
 	int chk;
 
 	/* We need a proper CSUM befor we start (s. OpenBSD ip_output) */
@@ -3685,7 +3691,10 @@
 	        HTONS(h->ip_len);
 	        HTONS(h->ip_off);
 	}
-	chk = pf_test(PF_OUT, ifp, m, NULL, inp);
+	tag_ether_hdr = m_tag_locate(*m, MTAG_ETHER, MTAG_ETHER_HEADER, NULL);
+	chk = pf_test(PF_OUT, ifp, m, 
+	    tag_ether_hdr ? (struct ether_header *)(tag_ether_hdr + 1) : NULL, 
+	    inp);
 	if (chk && *m) {
 		m_freem(*m);
 		*m = NULL;
@@ -3707,6 +3716,7 @@
 	/*
 	 * IPv6 is not affected by ip_len/ip_off byte order changes.
 	 */
+	struct m_tag *tag_ether_hdr;
 	int chk;
 
 	/*
@@ -3714,8 +3724,10 @@
 	 * order to support scoped addresses. In order to support stateful
 	 * filtering we have change this to lo0 as it is the case in IPv4.
 	 */
+	tag_ether_hdr = m_tag_locate(*m, MTAG_ETHER, MTAG_ETHER_HEADER, NULL);
 	chk = pf_test6(PF_IN, (*m)->m_flags & M_LOOP ? &V_loif[0] : ifp, m,
-	    NULL, inp);
+	    tag_ether_hdr ? (struct ether_header *)(tag_ether_hdr + 1) : NULL, 
+	    inp);
 	if (chk && *m) {
 		m_freem(*m);
 		*m = NULL;
@@ -3730,6 +3742,7 @@
 	/*
 	 * IPv6 does not affected ip_len/ip_off byte order changes.
 	 */
+	struct m_tag *tag_ether_hdr;
 	int chk;
 
 	/* We need a proper CSUM befor we start (s. OpenBSD ip_output) */
@@ -3737,7 +3750,10 @@
 		in_delayed_cksum(*m);
 		(*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
 	}
-	chk = pf_test6(PF_OUT, ifp, m, NULL, inp);
+	tag_ether_hdr = m_tag_locate(*m, MTAG_ETHER, MTAG_ETHER_HEADER, NULL);
+	chk = pf_test6(PF_OUT, ifp, m,
+	    tag_ether_hdr ? (struct ether_header *)(tag_ether_hdr + 1) : NULL, 
+	    inp);
 	if (chk && *m) {
 		m_freem(*m);
 		*m = NULL;
diff -urN -x .hg -x .svn ../my/sys/contrib/pf/net/pf_table.c ./sys/contrib/pf/net/pf_table.c
--- ../my/sys/contrib/pf/net/pf_table.c	2008-09-07 19:12:21.000000000 +0300
+++ ./sys/contrib/pf/net/pf_table.c	2008-09-07 22:24:44.000000000 +0300
@@ -917,6 +917,7 @@
 	ke->pfrke_net = ad->pfra_net;
 	ke->pfrke_not = ad->pfra_not;
 	ke->pfrke_intrpool = intr;
+	ke->pfrke_ether = ad->pfra_ether;
 	return (ke);
 }
 
@@ -1145,6 +1146,7 @@
 		ad->pfra_ip4addr = ke->pfrke_sa.sin.sin_addr;
 	else if (ad->pfra_af == AF_INET6)
 		ad->pfra_ip6addr = ke->pfrke_sa.sin6.sin6_addr;
+	ad->pfra_ether = ke->pfrke_ether;
 }
 
 int
@@ -2089,6 +2091,12 @@
 int
 pfr_match_addr(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af)
 {
+	return pfr_match_addr_ether(kt, a, af, NULL);
+}
+
+int
+pfr_match_addr_ether(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af, struct pf_addr_ether *ae)
+{
 	struct pfr_kentry	*ke = NULL;
 	int			 match;
 
@@ -2115,7 +2123,10 @@
 		break;
 #endif /* INET6 */
 	}
-	match = (ke && !ke->pfrke_not);
+	match = (ke != NULL);
+	if (match && ae)
+		match = pf_match_addr_ether(&ke->pfrke_ether, ae, 0);
+	match = (match && !ke->pfrke_not);
 	if (match)
 		kt->pfrkt_match++;
 	else
diff -urN -x .hg -x .svn ../my/sys/contrib/pf/net/pfvar.h ./sys/contrib/pf/net/pfvar.h
--- ../my/sys/contrib/pf/net/pfvar.h	2008-09-07 19:12:21.000000000 +0300
+++ ./sys/contrib/pf/net/pfvar.h	2008-09-07 22:24:45.000000000 +0300
@@ -165,6 +165,14 @@
 #define PFI_AFLAG_MODEMASK	0x07
 #define PFI_AFLAG_NOALIAS	0x08
 
+#define PFAE_CHECK		0x01
+#define PFAE_MULTICAST		0x02
+
+struct pf_addr_ether {
+	u_int8_t		octet[6];
+	u_int16_t		flags;
+};
+
 struct pf_addr_wrap {
 	union {
 		struct {
@@ -185,6 +193,7 @@
 		int			 dyncnt;
 		int			 tblcnt;
 	}			 p;
+	struct pf_addr_ether	 addr_ether;
 	u_int8_t		 type;		/* PF_ADDR_* */
 	u_int8_t		 iflags;	/* PFI_AFLAG_* */
 };
@@ -401,7 +410,7 @@
 #endif /* PF_INET6_ONLY */
 #endif /* PF_INET_INET6 */
 
-#define	PF_MISMATCHAW(aw, x, af, neg, ifp)				\
+#define	PF_MISMATCHAW_L2(aw, x, xl2, af, neg, ifp)			\
 	(								\
 		(((aw)->type == PF_ADDR_NOROUTE &&			\
 		    pf_routable((x), (af), NULL)) ||			\
@@ -410,16 +419,24 @@
 		((aw)->type == PF_ADDR_RTLABEL &&			\
 		    !pf_rtlabel_match((x), (af), (aw))) ||		\
 		((aw)->type == PF_ADDR_TABLE &&				\
-		    !pfr_match_addr((aw)->p.tbl, (x), (af))) ||		\
+		    !pfr_match_addr_ether((aw)->p.tbl, (x),		\
+		    (af), (xl2))) ||					\
 		((aw)->type == PF_ADDR_DYNIFTL &&			\
-		    !pfi_match_addr((aw)->p.dyn, (x), (af))) || 	\
+		    !(pfi_match_addr((aw)->p.dyn, (x), (af)) &&		\
+		    pf_match_addr_ether(&(aw)->addr_ether,		\
+		    (xl2), 0))) || 					\
 		((aw)->type == PF_ADDR_ADDRMASK &&			\
 		    !PF_AZERO(&(aw)->v.a.mask, (af)) &&			\
-		    !PF_MATCHA(0, &(aw)->v.a.addr,			\
-		    &(aw)->v.a.mask, (x), (af))))) !=			\
+		    !(PF_MATCHA(0, &(aw)->v.a.addr,			\
+		    &(aw)->v.a.mask, (x), (af)) &&			\
+		    pf_match_addr_ether(&(aw)->addr_ether,		\
+		    (xl2), 0))))) !=					\
 		(neg)							\
 	)
 
+#define	PF_MISMATCHAW(aw, x, af, neg, ifp)				\
+	PF_MISMATCHAW_L2(aw, x, NULL, af, neg, ifp)
+
 
 struct pf_rule_uid {
 	uid_t		 uid[2];
@@ -690,6 +707,7 @@
 #define	PFRULE_NOSYNC		0x0010
 #define PFRULE_SRCTRACK		0x0020  /* track source states */
 #define PFRULE_RULESRCTRACK	0x0040  /* per rule */
+#define PFRULE_ETHERSTATE	0x0080  /* per rule */
 
 /* scrub flags */
 #define	PFRULE_NODF		0x0100
@@ -752,6 +770,8 @@
 
 struct pf_state_host {
 	struct pf_addr	addr;
+	struct pf_addr_ether
+			addr_ether;
 	u_int16_t	port;
 	u_int16_t	pad;
 };
@@ -796,6 +816,7 @@
 #ifdef __FreeBSD__
 	u_int8_t	 local_flags;
 #define	PFSTATE_EXPIRING 0x01
+#define	PFSTATE_ETHER	 0x02
 #else
 	u_int8_t	 pad;
 #endif
@@ -902,6 +923,7 @@
 	u_int8_t	 pfra_net;
 	u_int8_t	 pfra_not;
 	u_int8_t	 pfra_fback;
+	struct pf_addr_ether	 pfra_ether;
 };
 #define	pfra_ip4addr	pfra_u._pfra_ip4addr
 #define	pfra_ip6addr	pfra_u._pfra_ip6addr
@@ -945,6 +967,7 @@
 struct pfr_kentry {
 	struct radix_node	 pfrke_node[2];
 	union sockaddr_union	 pfrke_sa;
+	struct pf_addr_ether	 pfrke_ether;
 	u_int64_t		 pfrke_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX];
 	u_int64_t		 pfrke_bytes[PFR_DIR_MAX][PFR_OP_ADDR_MAX];
 	SLIST_ENTRY(pfr_kentry)	 pfrke_workq;
@@ -1054,6 +1077,10 @@
 	struct pf_addr	*dst;
 	struct ether_header
 			*eh;
+	struct pf_addr_ether
+			 src_ether;
+	struct pf_addr_ether
+			 dst_ether;
 	struct pf_mtag	*pf_mtag;
 	u_int16_t	*ip_sum;
 	u_int32_t	 p_len;		/* total length of payload */
@@ -1650,6 +1677,7 @@
 	    struct pf_pdesc *);
 int	pf_match_addr(u_int8_t, struct pf_addr *, struct pf_addr *,
 	    struct pf_addr *, sa_family_t);
+int	pf_match_addr_ether(struct pf_addr_ether *, struct pf_addr_ether *, int);
 int	pf_match(u_int8_t, u_int32_t, u_int32_t, u_int32_t);
 int	pf_match_port(u_int8_t, u_int16_t, u_int16_t, u_int16_t);
 int	pf_match_uid(u_int8_t, uid_t, uid_t, uid_t);
@@ -1680,6 +1708,7 @@
 #endif
 void	pfr_initialize(void);
 int	pfr_match_addr(struct pfr_ktable *, struct pf_addr *, sa_family_t);
+int	pfr_match_addr_ether(struct pfr_ktable *, struct pf_addr *, sa_family_t, struct pf_addr_ether *);
 void	pfr_update_stats(struct pfr_ktable *, struct pf_addr *, sa_family_t,
 	    u_int64_t, int, int, int);
 int	pfr_pool_get(struct pfr_ktable *, int *, struct pf_addr *,
diff -urN -x .hg -x .svn ../my/sys/net/ethernet.h ./sys/net/ethernet.h
--- ../my/sys/net/ethernet.h	2008-09-07 19:15:09.000000000 +0300
+++ ./sys/net/ethernet.h	2008-09-07 22:24:44.000000000 +0300
@@ -362,6 +362,10 @@
 } while (0)
 
 #ifdef _KERNEL
+#include <net/pfil.h>
+
+#define	MTAG_ETHER		1080579719
+#define	MTAG_ETHER_HEADER	0
 
 struct ifnet;
 struct mbuf;
@@ -383,6 +387,8 @@
 	    void *, u_int);
 struct mbuf  *ether_vlanencap(struct mbuf *, uint16_t);
 
+extern struct pfil_head ether_pfil_hook;	/* Packet filter hooks */
+
 #else /* _KERNEL */
 
 #include <sys/cdefs.h>
diff -urN -x .hg -x .svn ../my/sys/net/if.h ./sys/net/if.h
--- ../my/sys/net/if.h	2008-09-07 19:15:09.000000000 +0300
+++ ./sys/net/if.h	2008-09-07 22:24:44.000000000 +0300
@@ -150,6 +150,8 @@
 #define	IFF_MONITOR	0x40000		/* (n) user-requested monitor mode */
 #define	IFF_STATICARP	0x80000		/* (n) static ARP */
 #define	IFF_NEEDSGIANT	0x100000	/* (i) hold Giant over if_start calls */
+#define	IFF_L2FILTER	0x200000	/* (n) perform layer2 filtering on interface */
+#define	IFF_L2TAG	0x400000	/* (n) tag packets with layer2 header */
 
 /*
  * Old names for driver flags so that user space tools can continue to use
diff -urN -x .hg -x .svn ../my/sys/net/if_bridge.c ./sys/net/if_bridge.c
--- ../my/sys/net/if_bridge.c	2008-09-07 19:15:09.000000000 +0300
+++ ./sys/net/if_bridge.c	2008-09-07 22:24:44.000000000 +0300
@@ -79,6 +79,7 @@
 
 #include "opt_inet.h"
 #include "opt_inet6.h"
+#include "opt_ipfw.h"
 #include "opt_carp.h"
 
 #include <sys/param.h>
@@ -125,6 +126,7 @@
 #include <machine/in_cksum.h>
 #include <netinet/if_ether.h> /* for struct arpcom */
 #include <net/bridgestp.h>
+#include <net/ethernet.h> /* for ether_pfil_hook */
 #include <net/if_bridgevar.h>
 #include <net/if_llc.h>
 #include <net/if_vlan_var.h>
@@ -342,15 +344,14 @@
 static int pfil_onlyip = 1; /* only pass IP[46] packets when pfil is enabled */
 static int pfil_bridge = 1; /* run pfil hooks on the bridge interface */
 static int pfil_member = 1; /* run pfil hooks on the member interface */
-static int pfil_ipfw = 0;   /* layer2 filter with ipfw */
-static int pfil_ipfw_arp = 0;   /* layer2 filter with ipfw */
+static int pfil_layer2_arp = 0;   /* layer2 filter with PFIL */
 static int pfil_local_phys = 0; /* run pfil hooks on the physical interface for
                                    locally destined packets */
 static int log_stp   = 0;   /* log STP state changes */
 SYSCTL_INT(_net_link_bridge, OID_AUTO, pfil_onlyip, CTLFLAG_RW,
     &pfil_onlyip, 0, "Only pass IP packets when pfil is enabled");
-SYSCTL_INT(_net_link_bridge, OID_AUTO, ipfw_arp, CTLFLAG_RW,
-    &pfil_ipfw_arp, 0, "Filter ARP packets through IPFW layer2");
+SYSCTL_INT(_net_link_bridge, OID_AUTO, pfil_layer2_arp, CTLFLAG_RW,
+    &pfil_layer2_arp, 0, "Filter ARP packets through PFIL layer2");
 SYSCTL_INT(_net_link_bridge, OID_AUTO, pfil_bridge, CTLFLAG_RW,
     &pfil_bridge, 0, "Packet filter on the bridge interface");
 SYSCTL_INT(_net_link_bridge, OID_AUTO, pfil_member, CTLFLAG_RW,
@@ -508,39 +509,6 @@
 MODULE_DEPEND(if_bridge, bridgestp, 1, 1, 1);
 
 /*
- * handler for net.link.bridge.pfil_ipfw
- */
-static int
-sysctl_pfil_ipfw(SYSCTL_HANDLER_ARGS)
-{
-	int enable = pfil_ipfw;
-	int error;
-
-	error = sysctl_handle_int(oidp, &enable, 0, req);
-	enable = (enable) ? 1 : 0;
-
-	if (enable != pfil_ipfw) {
-		pfil_ipfw = enable;
-
-		/*
-		 * Disable pfil so that ipfw doesnt run twice, if the user
-		 * really wants both then they can re-enable pfil_bridge and/or
-		 * pfil_member. Also allow non-ip packets as ipfw can filter by
-		 * layer2 type.
-		 */
-		if (pfil_ipfw) {
-			pfil_onlyip = 0;
-			pfil_bridge = 0;
-			pfil_member = 0;
-		}
-	}
-
-	return (error);
-}
-SYSCTL_PROC(_net_link_bridge, OID_AUTO, ipfw, CTLTYPE_INT|CTLFLAG_RW,
-	    &pfil_ipfw, 0, &sysctl_pfil_ipfw, "I", "Layer2 filter with IPFW");
-
-/*
  * bridge_clone_create:
  *
  *	Create a new bridge instance.
@@ -1796,11 +1764,7 @@
 		return;
 	}
 
-	if (PFIL_HOOKED(&inet_pfil_hook)
-#ifdef INET6
-	    || PFIL_HOOKED(&inet6_pfil_hook)
-#endif
-	    ) {
+	if (PFIL_HOOKED(&ether_pfil_hook)) {
 		if (bridge_pfil(&m, sc->sc_ifp, ifp, PFIL_OUT) != 0)
 			return;
 		if (m == NULL)
@@ -2929,7 +2893,7 @@
 {
 	int snap, error, i, hlen;
 	struct ether_header *eh1, eh2;
-	struct ip_fw_args args;
+	struct m_tag *mtag_ether_header;
 	struct ip *ip;
 	struct llc llc1;
 	u_int16_t ether_type;
@@ -2942,7 +2906,7 @@
 	KASSERT(M_WRITABLE(*mp), ("%s: modifying a shared mbuf", __func__));
 #endif
 
-	if (pfil_bridge == 0 && pfil_member == 0 && pfil_ipfw == 0)
+	if (pfil_bridge == 0 && pfil_member == 0 && !(bifp != NULL && (bifp->if_flags & IFF_L2FILTER)))
 		return (0); /* filtering is disabled */
 
 	i = min((*mp)->m_pkthdr.len, max_protohdr);
@@ -2984,7 +2948,7 @@
 	switch (ether_type) {
 		case ETHERTYPE_ARP:
 		case ETHERTYPE_REVARP:
-			if (pfil_ipfw_arp == 0)
+			if (pfil_layer2_arp == 0)
 				return (0); /* Automatically pass */
 			break;
 
@@ -3003,6 +2967,12 @@
 				goto bad;
 	}
 
+	if (PFIL_HOOKED(&ether_pfil_hook) && dir == PFIL_OUT && bifp != NULL &&
+			(bifp->if_flags & IFF_L2FILTER)) {
+		if (pfil_run_hooks(&ether_pfil_hook, mp, bifp, PFIL_OUT, NULL) != 0)
+			return EACCES;
+	}
+
 	/* Strip off the Ethernet header and keep a copy. */
 	m_copydata(*mp, 0, ETHER_HDR_LEN, (caddr_t) &eh2);
 	m_adj(*mp, ETHER_HDR_LEN);
@@ -3033,47 +3003,22 @@
 			goto bad;
 	}
 
-	if (IPFW_LOADED && pfil_ipfw != 0 && dir == PFIL_OUT && ifp != NULL) {
-		error = -1;
-		args.rule = ip_dn_claim_rule(*mp);
-		if (args.rule != NULL && V_fw_one_pass)
-			goto ipfwpass; /* packet already partially processed */
-
-		args.m = *mp;
-		args.oif = ifp;
-		args.next_hop = NULL;
-		args.eh = &eh2;
-		args.inp = NULL;	/* used by ipfw uid/gid/jail rules */
-		i = ip_fw_chk_ptr(&args);
-		*mp = args.m;
-
-		if (*mp == NULL)
-			return (error);
-
-		if (DUMMYNET_LOADED && (i == IP_FW_DUMMYNET)) {
-
-			/* put the Ethernet header back on */
-			M_PREPEND(*mp, ETHER_HDR_LEN, M_DONTWAIT);
-			if (*mp == NULL)
-				return (error);
-			bcopy(&eh2, mtod(*mp, caddr_t), ETHER_HDR_LEN);
+	error = 0;
 
-			/*
-			 * Pass the pkt to dummynet, which consumes it. The
-			 * packet will return to us via bridge_dummynet().
-			 */
-			args.oif = ifp;
-			ip_dn_io_ptr(mp, DN_TO_IFB_FWD, &args);
-			return (error);
+	/* Add tag if member or bridge interface has IFF_L2TAG set */
+	if (((bifp ? bifp->if_flags : 0) | (ifp ? ifp->if_flags : 0)) & IFF_L2TAG) {
+		mtag_ether_header = m_tag_locate(*mp, MTAG_ETHER, MTAG_ETHER_HEADER,
+		    NULL);
+		if (mtag_ether_header == NULL) {
+			mtag_ether_header = m_tag_alloc(MTAG_ETHER, MTAG_ETHER_HEADER,
+					ETHER_HDR_LEN, M_NOWAIT);
+			if (mtag_ether_header != NULL) {
+				memcpy(mtag_ether_header + 1, &eh2, ETHER_HDR_LEN);
+				m_tag_prepend(*mp, mtag_ether_header);
+			}
 		}
-
-		if (i != IP_FW_PASS) /* drop */
-			goto bad;
 	}
 
-ipfwpass:
-	error = 0;
-
 	/*
 	 * Run the packet through pfil
 	 */
@@ -3110,8 +3055,25 @@
 			break;
 
 		if (pfil_bridge && dir == PFIL_IN && bifp != NULL)
+#ifdef IPFIREWALL
+	  	{
+			/* 
+			 * Mark packets as received from bridge interface.
+			 * Without this hack ipfw can't distinguish filtering
+			 * on bridge from filtering on member interface.
+			 */
+			struct ifnet *orig_rcvif;
+
+			orig_rcvif = (*mp)->m_pkthdr.rcvif;
+			(*mp)->m_pkthdr.rcvif = bifp;
+#endif
 			error = pfil_run_hooks(&inet_pfil_hook, mp, bifp,
 					dir, NULL);
+#ifdef IPFIREWALL
+			if (*mp)
+				(*mp)->m_pkthdr.rcvif = orig_rcvif;
+		}
+#endif
 
 		if (*mp == NULL || error != 0) /* filter may consume */
 			break;
@@ -3164,8 +3126,25 @@
 			break;
 
 		if (pfil_bridge && dir == PFIL_IN && bifp != NULL)
+#ifdef IPFIREWALL
+	  	{
+			/* 
+			 * Mark packets as received from bridge interface.
+			 * Without this hack ipfw can't distinguish filtering
+			 * on bridge from filtering on member interface.
+			 */
+			struct ifnet *orig_rcvif;
+
+			orig_rcvif = (*mp)->m_pkthdr.rcvif;
+			(*mp)->m_pkthdr.rcvif = bifp;
+#endif
 			error = pfil_run_hooks(&inet6_pfil_hook, mp, bifp,
 					dir, NULL);
+#ifdef IPFIREWALL
+			if (*mp)
+				(*mp)->m_pkthdr.rcvif = orig_rcvif;
+		}
+#endif
 		break;
 #endif
 	default:
diff -urN -x .hg -x .svn ../my/sys/net/if_ethersubr.c ./sys/net/if_ethersubr.c
--- ../my/sys/net/if_ethersubr.c	2008-09-07 19:15:09.000000000 +0300
+++ ./sys/net/if_ethersubr.c	2008-09-07 22:24:44.000000000 +0300
@@ -62,14 +62,13 @@
 #include <net/ethernet.h>
 #include <net/if_bridgevar.h>
 #include <net/if_vlan_var.h>
+#include <net/pfil.h>
 #include <net/pf_mtag.h>
 
 #if defined(INET) || defined(INET6)
 #include <netinet/in.h>
 #include <netinet/in_var.h>
 #include <netinet/if_ether.h>
-#include <netinet/ip_fw.h>
-#include <netinet/ip_dummynet.h>
 #endif
 #ifdef INET6
 #include <netinet6/nd6.h>
@@ -138,12 +137,7 @@
 
 #define senderr(e) do { error = (e); goto bad;} while (0)
 
-#if defined(INET) || defined(INET6)
-int
-ether_ipfw_chk(struct mbuf **m0, struct ifnet *dst,
-	struct ip_fw **rule, int shared);
-static int ether_ipfw;
-#endif
+struct pfil_head ether_pfil_hook;	/* Packet filter hooks */
 
 /*
  * Ethernet output routine.
@@ -391,20 +385,12 @@
 int
 ether_output_frame(struct ifnet *ifp, struct mbuf *m)
 {
-	int error;
-#if defined(INET) || defined(INET6)
-	struct ip_fw *rule = ip_dn_claim_rule(m);
+	int error = 0;
 
-	if (IPFW_LOADED && V_ether_ipfw != 0) {
-		if (ether_ipfw_chk(&m, ifp, &rule, 0) == 0) {
-			if (m) {
-				m_freem(m);
-				return EACCES;	/* pkt dropped */
-			} else
-				return 0;	/* consumed e.g. in a pipe */
-		}
-	}
-#endif
+	if (PFIL_HOOKED(&ether_pfil_hook) && (ifp->if_flags & IFF_L2FILTER))
+		error = pfil_run_hooks(&ether_pfil_hook, &m, ifp, PFIL_OUT, NULL);
+	if (m == NULL)
+		return 0;	/* consumed e.g. in a pipe */
 
 	/*
 	 * Queue message on interface, update output statistics if
@@ -414,102 +400,6 @@
 	return (error);
 }
 
-#if defined(INET) || defined(INET6)
-/*
- * ipfw processing for ethernet packets (in and out).
- * The second parameter is NULL from ether_demux, and ifp from
- * ether_output_frame.
- */
-int
-ether_ipfw_chk(struct mbuf **m0, struct ifnet *dst,
-	struct ip_fw **rule, int shared)
-{
-	struct ether_header *eh;
-	struct ether_header save_eh;
-	struct mbuf *m;
-	int i;
-	struct ip_fw_args args;
-
-	if (*rule != NULL && V_fw_one_pass)
-		return 1; /* dummynet packet, already partially processed */
-
-	/*
-	 * I need some amt of data to be contiguous, and in case others need
-	 * the packet (shared==1) also better be in the first mbuf.
-	 */
-	m = *m0;
-	i = min( m->m_pkthdr.len, max_protohdr);
-	if ( shared || m->m_len < i) {
-		m = m_pullup(m, i);
-		if (m == NULL) {
-			*m0 = m;
-			return 0;
-		}
-	}
-	eh = mtod(m, struct ether_header *);
-	save_eh = *eh;			/* save copy for restore below */
-	m_adj(m, ETHER_HDR_LEN);	/* strip ethernet header */
-
-	args.m = m;		/* the packet we are looking at		*/
-	args.oif = dst;		/* destination, if any			*/
-	args.rule = *rule;	/* matching rule to restart		*/
-	args.next_hop = NULL;	/* we do not support forward yet	*/
-	args.eh = &save_eh;	/* MAC header for bridged/MAC packets	*/
-	args.inp = NULL;	/* used by ipfw uid/gid/jail rules	*/
-	i = ip_fw_chk_ptr(&args);
-	m = args.m;
-	if (m != NULL) {
-		/*
-		 * Restore Ethernet header, as needed, in case the
-		 * mbuf chain was replaced by ipfw.
-		 */
-		M_PREPEND(m, ETHER_HDR_LEN, M_DONTWAIT);
-		if (m == NULL) {
-			*m0 = m;
-			return 0;
-		}
-		if (eh != mtod(m, struct ether_header *))
-			bcopy(&save_eh, mtod(m, struct ether_header *),
-				ETHER_HDR_LEN);
-	}
-	*m0 = m;
-	*rule = args.rule;
-
-	if (i == IP_FW_DENY) /* drop */
-		return 0;
-
-	KASSERT(m != NULL, ("ether_ipfw_chk: m is NULL"));
-
-	if (i == IP_FW_PASS) /* a PASS rule.  */
-		return 1;
-
-	if (DUMMYNET_LOADED && (i == IP_FW_DUMMYNET)) {
-		/*
-		 * Pass the pkt to dummynet, which consumes it.
-		 * If shared, make a copy and keep the original.
-		 */
-		if (shared) {
-			m = m_copypacket(m, M_DONTWAIT);
-			if (m == NULL)
-				return 0;
-		} else {
-			/*
-			 * Pass the original to dummynet and
-			 * nothing back to the caller
-			 */
-			*m0 = NULL ;
-		}
-		ip_dn_io_ptr(&m, dst ? DN_TO_ETH_OUT: DN_TO_ETH_DEMUX, &args);
-		return 0;
-	}
-	/*
-	 * XXX at some point add support for divert/forward actions.
-	 * If none of the above matches, we have to drop the pkt.
-	 */
-	return 0;
-}
-#endif
-
 /*
  * Process a received Ethernet packet; the packet is in the
  * mbuf chain m with the ethernet header at the front.
@@ -707,6 +597,7 @@
 ether_demux(struct ifnet *ifp, struct mbuf *m)
 {
 	struct ether_header *eh;
+	struct m_tag *mtag_ether_header;
 	int isr;
 	u_short ether_type;
 #if defined(NETATALK)
@@ -715,21 +606,15 @@
 
 	KASSERT(ifp != NULL, ("%s: NULL interface pointer", __func__));
 
-#if defined(INET) || defined(INET6)
 	/*
-	 * Allow dummynet and/or ipfw to claim the frame.
+	 * Allow pfil to claim the frame.
 	 * Do not do this for PROMISC frames in case we are re-entered.
 	 */
-	if (IPFW_LOADED && V_ether_ipfw != 0 && !(m->m_flags & M_PROMISC)) {
-		struct ip_fw *rule = ip_dn_claim_rule(m);
-
-		if (ether_ipfw_chk(&m, NULL, &rule, 0) == 0) {
-			if (m)
-				m_freem(m);	/* dropped; free mbuf chain */
-			return;			/* consumed */
-		}
+	if (PFIL_HOOKED(&ether_pfil_hook) && (ifp->if_flags & IFF_L2FILTER) &&
+			!(m->m_flags & M_PROMISC)) {
+		if (pfil_run_hooks(&ether_pfil_hook, &m, ifp, PFIL_IN, NULL) != 0)
+			return;
 	}
-#endif
 	eh = mtod(m, struct ether_header *);
 	ether_type = ntohs(eh->ether_type);
 
@@ -761,6 +646,14 @@
 		return;
 	}
 
+	if (ifp->if_flags & IFF_L2TAG) {
+		mtag_ether_header = m_tag_alloc(MTAG_ETHER, MTAG_ETHER_HEADER, ETHER_HDR_LEN, M_NOWAIT);
+		if (mtag_ether_header != NULL) {
+			memcpy(mtag_ether_header + 1, eh, ETHER_HDR_LEN);
+			m_tag_prepend(m, mtag_ether_header);
+		}
+	}
+
 	/*
 	 * Reset layer specific mbuf flags to avoid confusing upper layers.
 	 * Strip off Ethernet header.
@@ -936,10 +829,6 @@
 
 SYSCTL_DECL(_net_link);
 SYSCTL_NODE(_net_link, IFT_ETHER, ether, CTLFLAG_RW, 0, "Ethernet");
-#if defined(INET) || defined(INET6)
-SYSCTL_INT(_net_link_ether, OID_AUTO, ipfw, CTLFLAG_RW,
-	    &ether_ipfw,0,"Pass ether pkts through firewall");
-#endif
 
 #if 0
 /*
@@ -1195,10 +1084,16 @@
 static int
 ether_modevent(module_t mod, int type, void *data)
 {
+	int err;
 
 	switch (type) {
 	case MOD_LOAD:
 		if_register_com_alloc(IFT_ETHER, ether_alloc, ether_free);
+		ether_pfil_hook.ph_type = PFIL_TYPE_IFT;
+		ether_pfil_hook.ph_af = IFT_ETHER;
+		if ((err = pfil_head_register(&ether_pfil_hook)) != 0)
+			printf("%s: WARNING: unable to register pfil hook, "
+				"error %d\n", __func__, err);
 		break;
 	case MOD_UNLOAD:
 		if_deregister_com_alloc(IFT_ETHER);
diff -urN -x .hg -x .svn ../my/sys/net/pfil.h ./sys/net/pfil.h
--- ../my/sys/net/pfil.h	2008-09-07 19:15:09.000000000 +0300
+++ ./sys/net/pfil.h	2008-09-07 22:24:44.000000000 +0300
@@ -63,6 +63,7 @@
 
 #define	PFIL_TYPE_AF		1	/* key is AF_* type */
 #define	PFIL_TYPE_IFNET		2	/* key is ifnet pointer */
+#define	PFIL_TYPE_IFT		3	/* key is IFT_* type */
 
 struct pfil_head {
 	pfil_list_t	ph_in;
diff -urN -x .hg -x .svn ../my/sys/netinet/ip_fw.h ./sys/netinet/ip_fw.h
--- ../my/sys/netinet/ip_fw.h	2008-09-07 19:15:19.000000000 +0300
+++ ./sys/netinet/ip_fw.h	2008-09-07 22:24:44.000000000 +0300
@@ -72,8 +72,9 @@
 	O_IP_DSTPORT,		/* (n)port list:mask 4 byte ea	*/
 	O_PROTO,		/* arg1=protocol		*/
 
-	O_MACADDR2,		/* 2 mac addr:mask		*/
-	O_MAC_TYPE,		/* same as srcport		*/
+	O_ETHER_SRC,		/* 2 ethernet (mac) addr:mask	*/
+	O_ETHER_DST,		/* 2 ethernet (mac) addr:mask	*/
+	O_ETHER_TYPE,		/* same as srcport		*/
 
 	O_LAYER2,		/* none				*/
 	O_IN,			/* none				*/
@@ -128,7 +129,6 @@
 	O_DIVERT,		/* arg1=port number		*/
 	O_TEE,			/* arg1=port number		*/
 	O_FORWARD_IP,		/* fwd sockaddr			*/
-	O_FORWARD_MAC,		/* fwd mac			*/
 	O_NAT,                  /* nope                         */
 
 	/*
@@ -169,6 +169,15 @@
 	O_SETFIB,		/* arg1=FIB number */
 	O_FIB,			/* arg1=FIB desired fib number */
 
+	O_STATEOPTS,
+
+	/*
+	 * ARP opcodes
+	 */
+	O_ARP_OP,		/* same as srcport		*/
+	O_ARP_SRC_LOOKUP,	/* arg1=table number, u32=value	*/
+	O_ARP_DST_LOOKUP,	/* arg1=table number, u32=value	*/
+
 	O_LAST_OPCODE		/* not an opcode!		*/
 };
 
@@ -266,13 +275,21 @@
 } ipfw_insn_sa;
 
 /*
- * This is used for MAC addr-mask pairs.
+ * This is used for ethernet (MAC) addr-mask pairs.
  */
-typedef struct	_ipfw_insn_mac {
+
+#define IPFW_EA_CHECK		0x01
+#define IPFW_EA_MULTICAST	0x02
+
+typedef struct _ipfw_ether_addr {
+	u_char octet[6];
+	u_int16_t flags;
+} ipfw_ether_addr;
+
+typedef struct	_ipfw_insn_ether {
 	ipfw_insn o;
-	u_char addr[12];	/* dst[6] + src[6] */
-	u_char mask[12];	/* dst[6] + src[6] */
-} ipfw_insn_mac;
+	ipfw_ether_addr ether;
+} ipfw_insn_ether;
 
 /*
  * This is used for interface match rules (recv xx, xmit xx).
@@ -481,6 +498,8 @@
 	struct in6_addr src_ip6;
 	u_int32_t	flow_id6;
 	u_int32_t	frag_id6;
+	ipfw_ether_addr src_ether;
+	ipfw_ether_addr dst_ether;
 };
 
 #define IS_IP6_FLOW_ID(id)	((id)->addr_type == 6)
@@ -532,10 +551,16 @@
 #define	ICMP6_UNREACH_RST	0x100	/* fake ICMPv6 code (send a TCP RST) */
 
 /*
+ * Definitions for state (dynamic rule) option names.
+ */
+#define	IP_FW_STATEOPT_ETHER	0x01
+
+/*
  * These are used for lookup tables.
  */
 typedef struct	_ipfw_table_entry {
 	in_addr_t	addr;		/* network address		*/
+	ipfw_ether_addr ether_addr;	/* ethernet address		*/
 	u_int32_t	value;		/* value			*/
 	u_int16_t	tbl;		/* table number			*/
 	u_int8_t	masklen;	/* mask length			*/
@@ -586,6 +611,8 @@
        struct route_in6 ro_pmtu_or;
 };
 
+#define IP_FW_ARGS_LAYER2		0x01
+
 /*
  * Arguments for calling ipfw_chk() and dummynet_io(). We put them
  * all into a structure because this way it is easier and more
@@ -596,7 +623,8 @@
 	struct ifnet	*oif;		/* output interface		*/
 	struct sockaddr_in *next_hop;	/* forward address		*/
 	struct ip_fw	*rule;		/* matching rule		*/
-	struct ether_header *eh;	/* for bridged packets		*/
+	struct ether_header *eh;	/* for saved ethernet header	*/
+	int flags;
 
 	struct ipfw_flow_id f_id;	/* grabbed from IP header	*/
 	u_int32_t	cookie;		/* a cookie depending on rule action */
@@ -616,6 +644,8 @@
 
 int ipfw_check_in(void *, struct mbuf **, struct ifnet *, int, struct inpcb *inp);
 int ipfw_check_out(void *, struct mbuf **, struct ifnet *, int, struct inpcb *inp);
+int ipfw_ether_check_in(void *, struct mbuf **, struct ifnet *, int, struct inpcb *inp);
+int ipfw_ether_check_out(void *, struct mbuf **, struct ifnet *, int, struct inpcb *inp);
 
 int ipfw_chk(struct ip_fw_args *);
 
diff -urN -x .hg -x .svn ../my/sys/netinet/ip_fw2.c ./sys/netinet/ip_fw2.c
--- ../my/sys/netinet/ip_fw2.c	2008-09-07 19:15:19.000000000 +0300
+++ ./sys/netinet/ip_fw2.c	2008-09-07 22:24:44.000000000 +0300
@@ -66,6 +66,7 @@
 #include <sys/ucred.h>
 #include <sys/vimage.h>
 #include <net/if.h>
+#include <net/if_arp.h>
 #include <net/radix.h>
 #include <net/route.h>
 #include <net/pf_mtag.h>
@@ -150,9 +151,42 @@
 ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr;
 ipfw_nat_cfg_t *ipfw_nat_get_log_ptr;
 
+static __inline int ether_addr_allow(ipfw_ether_addr *want, 
+	ipfw_ether_addr *a)
+{
+	static ipfw_ether_addr mask = {
+		.octet = { 0xff, 0xff, 0xff, 0xff, 0xff,0xff },
+		.flags = 0
+	};
+	if ((want->flags & IPFW_EA_CHECK) == 0)
+		return (1);
+
+	if ((a->flags & IPFW_EA_CHECK) == 0)
+		return (0);
+
+	if (want->flags & IPFW_EA_MULTICAST) {
+		return (ETHER_IS_MULTICAST(a->octet));
+	}
+	
+#define EA_CMP(a) (*((u_int64_t*)(a)) & *((u_int64_t*)&mask))
+	return (EA_CMP(want) == EA_CMP(a));
+#undef EA_CMP
+}
+
+static __inline int ether_addr_allow_dyn(ipfw_ether_addr *want, ipfw_ether_addr *a)
+{
+	if ((a->flags & IPFW_EA_CHECK) == 0) {
+		if (want->flags & IPFW_EA_CHECK)
+			printf("ipfw: no tag: %6D (want %6D)\n", a->octet, ":", want->octet, ":");
+		return (1);
+	}
+	return (ether_addr_allow(want, a));
+}
+
 struct table_entry {
 	struct radix_node	rn[2];
 	struct sockaddr_in	addr, mask;
+	ipfw_ether_addr 	ether_addr;
 	u_int32_t		value;
 };
 
@@ -720,18 +754,6 @@
 		 */
 		tcp_respond(NULL, ip6, tcp, m, ack, seq, flags);
 	} else if (code != ICMP6_UNREACH_RST) { /* Send an ICMPv6 unreach. */
-#if 0
-		/*
-		 * Unlike above, the mbufs need to line up with the ip6 hdr,
-		 * as the contents are read. We need to m_adj() the
-		 * needed amount.
-		 * The mbuf will however be thrown away so we can adjust it.
-		 * Remember we did an m_pullup on it already so we
-		 * can make some assumptions about contiguousness.
-		 */
-		if (args->L3offset)
-			m_adj(m, args->L3offset);
-#endif
 		icmp6_error(m, ICMP6_DST_UNREACH, code, 0);
 	} else
 		m_freem(m);
@@ -755,7 +777,6 @@
     struct mbuf *m, struct ifnet *oif, u_short offset, uint32_t tablearg,
     struct ip *ip)
 {
-	struct ether_header *eh = args->eh;
 	char *action;
 	int limit_reached = 0;
 	char action2[40], proto[128], fragment[32];
@@ -882,7 +903,7 @@
 	}
 
 	if (hlen == 0) {	/* non-ip */
-		snprintf(SNPARGS(proto, 0), "MAC");
+		snprintf(SNPARGS(proto, 0), "ether");
 
 	} else {
 		int len;
@@ -984,13 +1005,8 @@
 #endif
 		{
 			int ip_off, ip_len;
-			if (eh != NULL) { /* layer 2 packets are as on the wire */
-				ip_off = ntohs(ip->ip_off);
-				ip_len = ntohs(ip->ip_len);
-			} else {
-				ip_off = ip->ip_off;
-				ip_len = ip->ip_len;
-			}
+			ip_off = ip->ip_off;
+			ip_len = ip->ip_len;
 			if (ip_off & (IP_MF | IP_OFFMASK))
 				snprintf(SNPARGS(fragment, 0),
 				    " (frag %d:%d@%d%s)",
@@ -1214,7 +1230,23 @@
 	if (q == NULL)
 		goto done; /* q = NULL, not found */
 
-	if ( prev != NULL) { /* found and not in front */
+	/*
+	 * Only check {src,dst}_ether if it was specified in rule and packet
+	 * mbuf has mtag_ether_header.
+	 */
+	if (dir == MATCH_NONE ||
+			!ether_addr_allow_dyn(&q->id.src_ether,
+			(dir == MATCH_FORWARD ? &pkt->src_ether : &pkt->dst_ether)) ||
+			!ether_addr_allow_dyn(&q->id.dst_ether,
+			(dir == MATCH_FORWARD ? &pkt->dst_ether : &pkt->src_ether))) {
+		printf("XXX IPFW DYN RULE: dropped by mac: %6D -> %6D\n",
+				&pkt->src_ether.octet, ":", &pkt->dst_ether.octet, ":");
+		q = NULL;
+		dir = MATCH_NONE;
+		goto done;
+	}
+
+	if (prev != NULL) { /* found and not in front */
 		prev->next = q->next;
 		q->next = V_ipfw_dyn_v[i];
 		V_ipfw_dyn_v[i] = q;
@@ -1339,7 +1371,7 @@
  * - "parent" rules for the above (O_LIMIT_PARENT).
  */
 static ipfw_dyn_rule *
-add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule)
+add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule, uint32_t stateopts)
 {
 	ipfw_dyn_rule *r;
 	int i;
@@ -1371,6 +1403,10 @@
 	}
 
 	r->id = *id;
+	if ((stateopts & IP_FW_STATEOPT_ETHER) == 0) {
+		r->id.src_ether.flags = 0;
+		r->id.dst_ether.flags = 0;
+	}
 	r->expire = time_uptime + V_dyn_syn_lifetime;
 	r->rule = rule;
 	r->dyn_type = dyn_type;
@@ -1394,7 +1430,7 @@
  * If the lookup fails, then install one.
  */
 static ipfw_dyn_rule *
-lookup_dyn_parent(struct ipfw_flow_id *pkt, struct ip_fw *rule)
+lookup_dyn_parent(struct ipfw_flow_id *pkt, struct ip_fw *rule, uint32_t stateopts)
 {
 	ipfw_dyn_rule *q;
 	int i;
@@ -1426,7 +1462,7 @@
 				return q;
 			}
 	}
-	return add_dyn_rule(pkt, O_LIMIT_PARENT, rule);
+	return add_dyn_rule(pkt, O_LIMIT_PARENT, rule, stateopts);
 }
 
 /**
@@ -1436,7 +1472,7 @@
  * session limitations are enforced.
  */
 static int
-install_state(struct ip_fw *rule, ipfw_insn_limit *cmd,
+install_state(struct ip_fw *rule, uint32_t stateopts, ipfw_insn_limit *cmd,
     struct ip_fw_args *args, uint32_t tablearg)
 {
 	static int last_log;
@@ -1483,7 +1519,7 @@
 
 	switch (cmd->o.opcode) {
 	case O_KEEP_STATE:	/* bidir rule */
-		add_dyn_rule(&args->f_id, O_KEEP_STATE, rule);
+		add_dyn_rule(&args->f_id, O_KEEP_STATE, rule, stateopts);
 		break;
 
 	case O_LIMIT: {		/* limit number of sessions */
@@ -1524,7 +1560,7 @@
 			id.src_port = args->f_id.src_port;
 		if (limit_mask & DYN_DST_PORT)
 			id.dst_port = args->f_id.dst_port;
-		if ((parent = lookup_dyn_parent(&id, rule)) == NULL) {
+		if ((parent = lookup_dyn_parent(&id, rule, stateopts)) == NULL) {
 			printf("ipfw: %s: add parent failed\n", __func__);
 			IPFW_DYN_UNLOCK();
 			return (1);
@@ -1571,7 +1607,7 @@
 				return (1);
 			}
 		}
-		add_dyn_rule(&args->f_id, O_LIMIT, (struct ip_fw *)parent);
+		add_dyn_rule(&args->f_id, O_LIMIT, (struct ip_fw *)parent, stateopts);
 		break;
 	}
 	default:
@@ -1690,22 +1726,7 @@
 send_reject(struct ip_fw_args *args, int code, int ip_len, struct ip *ip)
 {
 
-#if 0
-	/* XXX When ip is not guaranteed to be at mtod() we will
-	 * need to account for this */
-	 * The mbuf will however be thrown away so we can adjust it.
-	 * Remember we did an m_pullup on it already so we
-	 * can make some assumptions about contiguousness.
-	 */
-	if (args->L3offset)
-		m_adj(m, args->L3offset);
-#endif
 	if (code != ICMP_REJECT_RST) { /* Send an ICMP unreach */
-		/* We need the IP header in host order for icmp_error(). */
-		if (args->eh != NULL) {
-			ip->ip_len = ntohs(ip->ip_len);
-			ip->ip_off = ntohs(ip->ip_off);
-		}
 		icmp_error(args->m, ICMP_UNREACH, code, 0L, 0);
 	} else if (args->f_id.proto == IPPROTO_TCP) {
 		struct tcphdr *const tcp =
@@ -1774,7 +1795,7 @@
 
 static int
 add_table_entry(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
-    uint8_t mlen, uint32_t value)
+    uint8_t mlen, ipfw_ether_addr *ether_addr, uint32_t value)
 {
 	struct radix_node_head *rnh;
 	struct table_entry *ent;
@@ -1789,6 +1810,7 @@
 	ent->addr.sin_len = ent->mask.sin_len = 8;
 	ent->mask.sin_addr.s_addr = htonl(mlen ? ~((1 << (32 - mlen)) - 1) : 0);
 	ent->addr.sin_addr.s_addr = addr & ent->mask.sin_addr.s_addr;
+	ent->ether_addr = *ether_addr;
 	IPFW_WLOCK(&V_layer3_chain);
 	if (rnh->rnh_addaddr(&ent->addr, &ent->mask, rnh, (void *)ent) ==
 	    NULL) {
@@ -1883,7 +1905,7 @@
 
 static int
 lookup_table(struct ip_fw_chain *ch, uint16_t tbl, in_addr_t addr,
-    uint32_t *val)
+    ipfw_ether_addr *ea, uint32_t *val)
 {
 	struct radix_node_head *rnh;
 	struct table_entry *ent;
@@ -1896,6 +1918,9 @@
 	sa.sin_addr.s_addr = addr;
 	ent = (struct table_entry *)(rnh->rnh_lookup(&sa, NULL, rnh));
 	if (ent != NULL) {
+		if (ea && !ether_addr_allow(&ent->ether_addr, ea))
+			return (0);
+		/* use address to create dynamic rule */
 		*val = ent->value;
 		return (1);
 	}
@@ -1940,6 +1965,7 @@
 	else
 		ent->masklen = 33 - ffs(ntohl(n->mask.sin_addr.s_addr));
 	ent->addr = n->addr.sin_addr.s_addr;
+	ent->ether_addr = n->ether_addr;
 	ent->value = n->value;
 	tbl->cnt++;
 	return (0);
@@ -2068,10 +2094,9 @@
  * Parameters:
  *
  *	args->m	(in/out) The packet; we set to NULL when/if we nuke it.
- *		Starts with the IP header.
- *	args->eh (in)	Mac header if present, or NULL for layer3 packet.
- *	args->L3offset	Number of bytes bypassed if we came from L2.
- *			e.g. often sizeof(eh)  ** NOTYET **
+ *		Starts with the IP header or with layer2 header if IP_FW_ARGS_LAYER2
+ *		is set in args->flags.
+ *	args->eh (in)	ethernet header if present, or NULL for layer3 packet.
  *	args->oif	Outgoing interface, or NULL if packet is incoming.
  *		The incoming interface is in the mbuf. (in)
  *	args->divert_rule (in/out)
@@ -2082,6 +2107,7 @@
  *	args->next_hop	Socket we are forwarding to (out).
  *	args->f_id	Addresses grabbed from the packet (out)
  * 	args->cookie	a cookie depending on rule action
+ * 	args->flags	Flags
  *
  * Return value:
  *
@@ -2105,10 +2131,8 @@
 	 * the implementation of the various instructions to make sure
 	 * that they still work.
 	 *
-	 * args->eh	The MAC header. It is non-null for a layer2
+	 * args->eh	The ethernet header. It is non-null for a layer2
 	 *	packet, it is NULL for a layer-3 packet.
-	 * **notyet**
-	 * args->L3offset Offset in the packet to the L3 (IP or equiv.) header.
 	 *
 	 * m | args->m	Pointer to the mbuf, as received from the caller.
 	 *	It may change if ipfw_chk() does an m_pullup, or if it
@@ -2116,12 +2140,10 @@
 	 *	XXX This has to change, so that ipfw_chk() never modifies
 	 *	or consumes the buffer.
 	 * ip	is the beginning of the ip(4 or 6) header.
-	 *	Calculated by adding the L3offset to the start of data.
-	 *	(Until we start using L3offset, the packet is
 	 *	supposed to start with the ip header).
 	 */
 	struct mbuf *m = args->m;
-	struct ip *ip = mtod(m, struct ip *);
+	struct ip *ip = NULL;
 
 	/*
 	 * For rules which contain uid/gid or jail constraints, cache
@@ -2217,6 +2239,9 @@
 	if (m->m_flags & M_SKIP_FIREWALL)
 		return (IP_FW_PASS);	/* accept */
 
+	if ((args->flags & IP_FW_ARGS_LAYER2) == 0)
+		ip = mtod(m, struct ip *);
+
 	pktlen = m->m_pkthdr.len;
 	args->f_id.fib = M_GETFIB(m); /* note mbuf not altered) */
 	proto = args->f_id.proto = 0;	/* mark f_id invalid */
@@ -2242,12 +2267,22 @@
 	/*
 	 * if we have an ether header,
 	 */
-	if (args->eh)
+	if (args->eh != NULL) {
 		etype = ntohs(args->eh->ether_type);
+		memcpy(args->f_id.src_ether.octet, args->eh->ether_shost, 
+				ETHER_ADDR_LEN);
+		args->f_id.src_ether.flags = IPFW_EA_CHECK;
+		memcpy(args->f_id.dst_ether.octet, args->eh->ether_dhost, 
+				ETHER_ADDR_LEN);
+		args->f_id.dst_ether.flags = IPFW_EA_CHECK;
+	} else {
+		args->f_id.src_ether.flags = 0;
+		args->f_id.dst_ether.flags = 0;
+	}
 
 	/* Identify IP packets and fill up variables. */
 	if (pktlen >= sizeof(struct ip6_hdr) &&
-	    (args->eh == NULL || etype == ETHERTYPE_IPV6) && ip->ip_v == 6) {
+	    (args->flags & IP_FW_ARGS_LAYER2) == 0 && ip->ip_v == 6) {
 		struct ip6_hdr *ip6 = (struct ip6_hdr *)ip;
 		is_ipv6 = 1;
 		args->f_id.addr_type = 6;
@@ -2412,7 +2447,7 @@
 		args->f_id.dst_ip = 0;
 		args->f_id.flow_id6 = ntohl(ip6->ip6_flow);
 	} else if (pktlen >= sizeof(struct ip) &&
-	    (args->eh == NULL || etype == ETHERTYPE_IP) && ip->ip_v == 4) {
+	    (args->flags & IP_FW_ARGS_LAYER2) == 0 && ip->ip_v == 4) {
 	    	is_ipv4 = 1;
 		hlen = ip->ip_hl << 2;
 		args->f_id.addr_type = 4;
@@ -2423,13 +2458,8 @@
 		proto = ip->ip_p;
 		src_ip = ip->ip_src;
 		dst_ip = ip->ip_dst;
-		if (args->eh != NULL) { /* layer 2 packets are as on the wire */
-			offset = ntohs(ip->ip_off) & IP_OFFMASK;
-			ip_len = ntohs(ip->ip_len);
-		} else {
-			offset = ip->ip_off & IP_OFFMASK;
-			ip_len = ip->ip_len;
-		}
+		offset = ip->ip_off & IP_OFFMASK;
+		ip_len = ip->ip_len;
 		pktlen = ip_len < pktlen ? ip_len : pktlen;
 
 		if (offset == 0) {
@@ -2460,6 +2490,13 @@
 		ip = mtod(m, struct ip *);
 		args->f_id.src_ip = ntohl(src_ip.s_addr);
 		args->f_id.dst_ip = ntohl(dst_ip.s_addr);
+	} else if (pktlen >= ETHER_HDR_LEN && args->eh != NULL &&
+	    (args->flags & IP_FW_ARGS_LAYER2)) {
+		void *hdr;
+		switch (ntohs(args->eh->ether_type)) {
+		case ETHERTYPE_ARP:
+			PULLUP_TO(ETHER_HDR_LEN, hdr, struct arphdr);
+		}
 	}
 #undef PULLUP_TO
 	if (proto) { /* we may have port numbers, store them */
@@ -2495,7 +2532,7 @@
 		int skipto = mtag ? divert_cookie(mtag) : 0;
 
 		f = chain->rules;
-		if (args->eh == NULL && skipto != 0) {
+		if ((args->flags & IP_FW_ARGS_LAYER2) == 0 && skipto != 0) {
 			if (skipto >= IPFW_DEFAULT_RULE) {
 				IPFW_RUNLOCK(chain);
 				return (IP_FW_DENY); /* invalid */
@@ -2521,6 +2558,7 @@
 	for (; f; f = f->next) {
 		ipfw_insn *cmd;
 		uint32_t tablearg = 0;
+		uint32_t stateopts = 0;
 		int l, cmdlen, skip_or; /* skip rest of OR block */
 
 again:
@@ -2566,11 +2604,6 @@
 				match = 1;
 				break;
 
-			case O_FORWARD_MAC:
-				printf("ipfw: opcode %d unimplemented\n",
-				    cmd->opcode);
-				break;
-
 			case O_GID:
 			case O_UID:
 			case O_JAIL:
@@ -2607,23 +2640,20 @@
 				    m->m_pkthdr.rcvif, (ipfw_insn_if *)cmd);
 				break;
 
-			case O_MACADDR2:
-				if (args->eh != NULL) {	/* have MAC header */
-					u_int32_t *want = (u_int32_t *)
-						((ipfw_insn_mac *)cmd)->addr;
-					u_int32_t *mask = (u_int32_t *)
-						((ipfw_insn_mac *)cmd)->mask;
-					u_int32_t *hdr = (u_int32_t *)args->eh;
-
-					match =
-					    ( want[0] == (hdr[0] & mask[0]) &&
-					      want[1] == (hdr[1] & mask[1]) &&
-					      want[2] == (hdr[2] & mask[2]) );
+			case O_ETHER_SRC:
+			case O_ETHER_DST:
+				if (args->eh != NULL) {	/* have ethernet header */
+					ipfw_ether_addr *want = 
+						&(((ipfw_insn_ether *)cmd)->ether);
+					ipfw_ether_addr *a = (cmd->opcode == O_ETHER_SRC ?
+						&args->f_id.src_ether :
+						&args->f_id.dst_ether);
+					match = ether_addr_allow(want, a);
 				}
 				break;
 
-			case O_MAC_TYPE:
-				if (args->eh != NULL) {
+			case O_ETHER_TYPE:
+				if (args->eh != NULL) {	/* have ethernet header */
 					u_int16_t *p =
 					    ((ipfw_insn_u16 *)cmd)->ports;
 					int i;
@@ -2644,7 +2674,7 @@
 				break;
 
 			case O_LAYER2:
-				match = (args->eh != NULL);
+				match = ((args->flags & IP_FW_ARGS_LAYER2) != 0);
 				break;
 
 			case O_DIVERTED:
@@ -2671,13 +2701,17 @@
 			case O_IP_SRC_LOOKUP:
 			case O_IP_DST_LOOKUP:
 				if (is_ipv4) {
+				    ipfw_ether_addr *ea =
+					(cmd->opcode == O_IP_DST_LOOKUP ?
+					    &args->f_id.dst_ether :
+					    &args->f_id.src_ether);
 				    uint32_t a =
 					(cmd->opcode == O_IP_DST_LOOKUP) ?
 					    dst_ip.s_addr : src_ip.s_addr;
 				    uint32_t v;
 
 				    match = lookup_table(chain, cmd->arg1, a,
-					&v);
+					ea, &v);
 				    if (!match)
 					break;
 				    if (cmdlen == F_INSN_SIZE(ipfw_insn_u32))
@@ -3069,6 +3103,97 @@
 					match = 1;
 				break;
 
+			case O_STATEOPTS:
+				if ((cmd->arg1 & IP_FW_STATEOPT_ETHER)) {
+					match = (args->eh != NULL);
+					if (!match)
+						break;
+				}
+				stateopts = cmd->arg1 & 0xff;
+				break;
+
+			case O_ARP_OP:
+			case O_ARP_SRC_LOOKUP:
+			case O_ARP_DST_LOOKUP:
+				if (args->flags & IP_FW_ARGS_LAYER2 &&
+					pktlen >= ETHER_HDR_LEN && args->eh != NULL) {
+				    struct arphdr *ah;
+				    int op;
+
+				    op = ntohs(args->eh->ether_type);
+				    if (op != ETHERTYPE_ARP && op != ETHERTYPE_REVARP)
+					break;
+
+				    ah = (struct arphdr*)(mtod(m, char*) + ETHER_HDR_LEN);
+				    op = ntohs(ah->ar_op);
+
+				    if (ntohs(ah->ar_pro) != ETHERTYPE_IP ||
+					    ntohs(ah->ar_hrd) != ARPHRD_ETHER)
+					break;
+
+				    if (cmd->opcode == O_ARP_OP) {
+					u_int16_t *p =
+					    ((ipfw_insn_u16 *)cmd)->ports;
+					int i;
+
+					for (i = cmdlen - 1; !match && i > 0;
+						i--, p += 2)
+					    match = (op >= p[0] &&
+						    op <= p[1]);
+				    } else {
+					ipfw_ether_addr ha;
+					uint32_t pa, v;
+
+					/*
+					 * XXX: Drop RARP requests
+					 * Protocol addresses are undefined
+					 * and table lookup by hardware address is not supported
+					 */
+					if (op == ARPOP_REVREQUEST)
+					    break;
+
+					if (cmd->opcode == O_ARP_DST_LOOKUP) {
+					    /* 
+					     * XXX: Drop Inverse ARP requests
+					     * Target protocol address is not specified
+					     * and table lookup by hardware address is not supported
+					     */
+					    if (op == ARPOP_INVREQUEST)
+						break;
+					    pa = *(uint32_t *) ar_tpa(ah);
+
+					    /*
+					     * Ignore hardware address for requests 
+					     */
+					    if (op != ARPOP_REQUEST) {
+						memcpy(ha.octet, ar_tha(ah), ETHER_ADDR_LEN);
+						ha.flags = IPFW_EA_CHECK;
+					    } else {
+						ha.flags = 0;
+					    }
+					} else {
+					    pa = *(uint32_t *) ar_spa(ah);
+					    memcpy(ha.octet, ar_sha(ah), ETHER_ADDR_LEN);
+					    ha.flags = IPFW_EA_CHECK;
+					}
+
+					match = lookup_table(chain, cmd->arg1, pa, 
+						(ha.flags ? &ha : NULL), &v);
+					printf("ipfw: %s arp: %s: op = %d: %6D(%d) %s\n",
+						(match ? "pass" : "drop"),
+						cmd->opcode == O_ARP_DST_LOOKUP ? "dst" : "src",
+						op, ha.octet, ":", ha.flags, 
+						inet_ntoa(*(struct in_addr *)&pa));
+					if (!match)
+					    break;
+					if (cmdlen == F_INSN_SIZE(ipfw_insn_u32))
+					    match = ((ipfw_insn_u32 *)cmd)->d[0] == v;
+					else
+					    tablearg = v;
+				    }
+				}
+				break;
+
 			case O_TAGGED: {
 				uint32_t tag = (cmd->arg1 == IP_FW_TABLEARG) ?
 				    tablearg : cmd->arg1;
@@ -3142,7 +3267,7 @@
 			 */
 			case O_LIMIT:
 			case O_KEEP_STATE:
-				if (install_state(f,
+				if (install_state(f, stateopts,
 				    (ipfw_insn_limit *)cmd, args, tablearg)) {
 					retval = IP_FW_DENY;
 					goto done; /* error/limit violation */
@@ -3207,7 +3332,7 @@
 			case O_TEE: {
 				struct divert_tag *dt;
 
-				if (args->eh) /* not on layer 2 */
+				if (args->flags & IP_FW_ARGS_LAYER2) /* not valid on layer2 pkts */
 					break;
 				mtag = m_tag_get(PACKET_TAG_DIVERT,
 						sizeof(struct divert_tag),
@@ -3283,7 +3408,7 @@
 			case O_FORWARD_IP: {
 				struct sockaddr_in *sa;
 				sa = &(((ipfw_insn_sa *)cmd)->sa);
-				if (args->eh)	/* not valid on layer2 pkts */
+				if (args->flags & IP_FW_ARGS_LAYER2) /* not valid on layer2 pkts */
 					break;
 				if (!q || dyn_dir == MATCH_FORWARD) {
 					if (sa->sin_addr.s_addr == INADDR_ANY) {
@@ -3826,6 +3951,7 @@
 #endif
 		case O_IP4:
 		case O_TAG:
+		case O_STATEOPTS:
 			if (cmdlen != F_INSN_SIZE(ipfw_insn))
 				goto bad_size;
 			break;
@@ -3898,6 +4024,8 @@
 
 		case O_IP_SRC_LOOKUP:
 		case O_IP_DST_LOOKUP:
+		case O_ARP_SRC_LOOKUP:
+		case O_ARP_DST_LOOKUP:
 			if (cmd->arg1 >= IPFW_TABLES_MAX) {
 				printf("ipfw: invalid table number %d\n",
 				    cmd->arg1);
@@ -3908,8 +4036,9 @@
 				goto bad_size;
 			break;
 
-		case O_MACADDR2:
-			if (cmdlen != F_INSN_SIZE(ipfw_insn_mac))
+		case O_ETHER_SRC:
+		case O_ETHER_DST:
+			if (cmdlen != F_INSN_SIZE(ipfw_insn_ether))
 				goto bad_size;
 			break;
 
@@ -3923,9 +4052,10 @@
 				goto bad_size;
 			break;
 
-		case O_MAC_TYPE:
 		case O_IP_SRCPORT:
 		case O_IP_DSTPORT: /* XXX artificial limit, 30 port pairs */
+		case O_ETHER_TYPE:
+		case O_ARP_OP:
 			if (cmdlen < 2 || cmdlen > 31)
 				goto bad_size;
 			break;
@@ -3975,7 +4105,6 @@
 			if (cmdlen != F_INSN_SIZE(ipfw_insn_nat))
  				goto bad_size;		
  			goto check_action;
-		case O_FORWARD_MAC: /* XXX not implemented yet */
 		case O_CHECK_STATE:
 		case O_COUNT:
 		case O_ACCEPT:
@@ -4292,7 +4421,7 @@
 			if (error)
 				break;
 			error = add_table_entry(&V_layer3_chain, ent.tbl,
-			    ent.addr, ent.masklen, ent.value);
+			    ent.addr, ent.masklen, &ent.ether_addr, ent.value);
 		}
 		break;
 
diff -urN -x .hg -x .svn ../my/sys/netinet/ip_fw_pfil.c ./sys/netinet/ip_fw_pfil.c
--- ../my/sys/netinet/ip_fw_pfil.c	2008-09-07 19:15:20.000000000 +0300
+++ ./sys/netinet/ip_fw_pfil.c	2008-09-07 22:24:44.000000000 +0300
@@ -50,6 +50,7 @@
 #include <sys/vimage.h>
 
 #include <net/if.h>
+#include <net/if_types.h>
 #include <net/route.h>
 #include <net/pfil.h>
 
@@ -61,6 +62,7 @@
 #include <netinet/ip_fw.h>
 #include <netinet/ip_divert.h>
 #include <netinet/ip_dummynet.h>
+#include <netinet/if_ether.h>
 
 #include <netgraph/ng_ipfw.h>
 
@@ -93,6 +95,7 @@
 {
 	struct ip_fw_args args;
 	struct ng_ipfw_tag *ng_tag;
+	struct m_tag *tag_ether_hdr;
 	struct m_tag *dn_tag;
 	int ipfw = 0;
 	int divert;
@@ -114,6 +117,11 @@
 		m_tag_delete(*m0, (struct m_tag *)ng_tag);
 	}
 
+	tag_ether_hdr = m_tag_locate(*m0, MTAG_ETHER, MTAG_ETHER_HEADER,
+	    NULL);
+	if (tag_ether_hdr != NULL)
+		args.eh = (struct ether_header *)(tag_ether_hdr + 1);
+
 again:
 	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
 	if (dn_tag != NULL){
@@ -215,6 +223,7 @@
 {
 	struct ip_fw_args args;
 	struct ng_ipfw_tag *ng_tag;
+	struct m_tag *tag_ether_hdr;
 	struct m_tag *dn_tag;
 	int ipfw = 0;
 	int divert;
@@ -236,6 +245,11 @@
 		m_tag_delete(*m0, (struct m_tag *)ng_tag);
 	}
 
+	tag_ether_hdr = m_tag_locate(*m0, MTAG_ETHER, MTAG_ETHER_HEADER,
+	    NULL);
+	if (tag_ether_hdr != NULL)
+		args.eh = (struct ether_header *)(tag_ether_hdr + 1);
+
 again:
 	dn_tag = m_tag_find(*m0, PACKET_TAG_DUMMYNET, NULL);
 	if (dn_tag != NULL) {
@@ -422,17 +436,126 @@
 	return 1;
 }
 
+int
+ipfw_ether_check_in(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
+    struct inpcb *inp)
+{
+	struct ip_fw_args args;
+	int error;
+
+	KASSERT(dir == PFIL_IN, ("ipfw_ether_check_in wrong direction!"));
+
+	bzero(&args, sizeof(args));
+
+	args.rule = ip_dn_claim_rule(*m0);
+	if (args.rule != NULL && fw_one_pass)
+		return 0; /* packet already partially processed */
+
+	args.m = *m0;
+	args.flags = IP_FW_ARGS_LAYER2;
+	args.eh = mtod(*m0, struct ether_header *);
+	args.inp = inp;
+	error = ip_fw_chk_ptr(&args);
+	*m0 = args.m;
+#ifdef XXXGK
+	printf("IN  %6D -> %6D: %s\n", 
+			args.eh->ether_shost, ":", 
+			args.eh->ether_dhost, ":",
+			(error == IP_FW_PASS ? "passed" : "droped"));
+#endif
+
+	if (error == IP_FW_PASS)
+		return 0;
+
+	if (DUMMYNET_LOADED && (error == IP_FW_DUMMYNET)) {
+		ip_dn_io_ptr(m0, DN_TO_ETH_DEMUX, &args);
+		return 0;
+	}
+
+	/*
+	 * XXX at some point add support for divert/forward actions.
+	 * If none of the above matches, we have to drop the pkt.
+	 */
+
+	if (*m0)
+		m_freem(*m0);
+	*m0 = NULL;
+	return (EACCES);
+}
+
+int
+ipfw_ether_check_out(void *arg, struct mbuf **m0, struct ifnet *ifp, int dir,
+    struct inpcb *inp)
+{
+	struct ip_fw_args args;
+	int error;
+
+	KASSERT(dir == PFIL_OUT, ("ipfw_ether_check_out wrong direction!"));
+
+	bzero(&args, sizeof(args));
+
+	args.rule = ip_dn_claim_rule(*m0);
+	if (args.rule != NULL && fw_one_pass)
+		return 0; /* packet already partially processed */
+
+	args.m = *m0;
+	args.oif = ifp;
+	args.flags = IP_FW_ARGS_LAYER2;
+	args.eh = mtod(*m0, struct ether_header *);
+	args.inp = inp;
+	error = ip_fw_chk_ptr(&args);
+	*m0 = args.m;
+#ifdef XXXGK
+	printf("OUT %6D -> %6D: %s\n", 
+			args.eh->ether_shost, ":", 
+			args.eh->ether_dhost, ":",
+			(error == IP_FW_PASS ? "passed" : "droped"));
+#endif
+
+	if (error == IP_FW_PASS)
+		return 0;
+
+	if (DUMMYNET_LOADED && (error == IP_FW_DUMMYNET)) {
+		int dn_dir;
+
+		if (ifp->if_type == IFT_BRIDGE)
+			dn_dir = DN_TO_IFB_FWD;
+		else
+			dn_dir = DN_TO_ETH_OUT;
+		ip_dn_io_ptr(m0, dn_dir, &args);
+		return 0;
+	}
+
+	/*
+	 * XXX at some point add support for divert/forward actions.
+	 * If none of the above matches, we have to drop the pkt.
+	 */
+
+	if (*m0)
+		m_freem(*m0);
+	*m0 = NULL;
+	return (EACCES);
+}
+
 static int
 ipfw_hook(void)
 {
-	struct pfil_head *pfh_inet;
+	struct pfil_head *pfh_inet, *pfh_ether;
 
 	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
-	if (pfh_inet == NULL)
-		return ENOENT;
+	if (pfh_inet != NULL) {
+		pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
+		pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+	}
 
-	pfil_add_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
-	pfil_add_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+	pfh_ether = pfil_head_get(PFIL_TYPE_IFT, IFT_ETHER);
+	if (pfh_ether != NULL) {
+		pfil_add_hook(ipfw_ether_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_ether);
+		pfil_add_hook(ipfw_ether_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_ether);
+	}
+
+	if (pfh_inet == NULL || pfh_ether == NULL)
+		return ENOENT;
 
 	return 0;
 }
@@ -440,14 +563,22 @@
 static int
 ipfw_unhook(void)
 {
-	struct pfil_head *pfh_inet;
+	struct pfil_head *pfh_inet, *pfh_ether;
 
 	pfh_inet = pfil_head_get(PFIL_TYPE_AF, AF_INET);
-	if (pfh_inet == NULL)
-		return ENOENT;
+	if (pfh_inet != NULL) {
+		pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
+		pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+	}
+
+	pfh_ether = pfil_head_get(PFIL_TYPE_IFT, IFT_ETHER);
+	if (pfh_ether != NULL) {
+		pfil_remove_hook(ipfw_ether_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_ether);
+		pfil_remove_hook(ipfw_ether_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_ether);
+	}
 
-	pfil_remove_hook(ipfw_check_in, NULL, PFIL_IN | PFIL_WAITOK, pfh_inet);
-	pfil_remove_hook(ipfw_check_out, NULL, PFIL_OUT | PFIL_WAITOK, pfh_inet);
+	if (pfh_inet == NULL || pfh_ether == NULL)
+		return ENOENT;
 
 	return 0;
 }

--4Ckj6UjgE2iN1+kY--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20080908193020.GA37900>