svn commit: r186069 - in projects/l2filter: contrib/pf/man contrib/pf/pfctl sbin/ifconfig sbin/ipfw share/man/man4 sys/contrib/pf/net sys/net sys/netinet

Andrew Thompson thompsa at FreeBSD.org
Sat Dec 13 16:40:15 PST 2008


Author: thompsa
Date: Sun Dec 14 00:40:14 2008
New Revision: 186069
URL: http://svn.freebsd.org/changeset/base/186069

Log:
  Add Layer2 Filtering by Gleb Kurtsou as part of the Google Summer of Code 08.
  
  Project aims to improve layer2 filtering in ipfw and pf. So far following
  project goals are achieved: pfil framework is extended to handle ethernet
  packets, ipfw layer2 filtering is greatly simplified, added l2filter and l2tag
  per interface flags. Both ipfw and pf firewalls support filtering by ethernet
  addresses, support stateful filtering with ethernet addresses and firewall's
  lookup tables are extended to contain ethernet addresses.
  
  This will need to catch up with arpv2 and route locking changes before being
  merged to head.
  
  Submitted by:	Gleb Kurtsou (gleb.kurtsou%gmail.com)

Modified:
  projects/l2filter/contrib/pf/man/pf.conf.5
  projects/l2filter/contrib/pf/pfctl/parse.y
  projects/l2filter/contrib/pf/pfctl/pf_print_state.c
  projects/l2filter/contrib/pf/pfctl/pfctl.c
  projects/l2filter/contrib/pf/pfctl/pfctl.h
  projects/l2filter/contrib/pf/pfctl/pfctl_parser.c
  projects/l2filter/contrib/pf/pfctl/pfctl_parser.h
  projects/l2filter/contrib/pf/pfctl/pfctl_radix.c
  projects/l2filter/contrib/pf/pfctl/pfctl_table.c
  projects/l2filter/sbin/ifconfig/ifconfig.8
  projects/l2filter/sbin/ifconfig/ifconfig.c
  projects/l2filter/sbin/ipfw/ipfw.8
  projects/l2filter/sbin/ipfw/ipfw2.c
  projects/l2filter/share/man/man4/if_bridge.4
  projects/l2filter/sys/contrib/pf/net/pf.c
  projects/l2filter/sys/contrib/pf/net/pf_ioctl.c
  projects/l2filter/sys/contrib/pf/net/pf_table.c
  projects/l2filter/sys/contrib/pf/net/pfvar.h
  projects/l2filter/sys/net/ethernet.h
  projects/l2filter/sys/net/if.h
  projects/l2filter/sys/net/if_bridge.c
  projects/l2filter/sys/net/if_ethersubr.c
  projects/l2filter/sys/net/pfil.h
  projects/l2filter/sys/netinet/ip_fw.h
  projects/l2filter/sys/netinet/ip_fw2.c
  projects/l2filter/sys/netinet/ip_fw_pfil.c

Modified: projects/l2filter/contrib/pf/man/pf.conf.5
==============================================================================
--- projects/l2filter/contrib/pf/man/pf.conf.5	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/man/pf.conf.5	Sun Dec 14 00:40:14 2008	(r186069)
@@ -123,6 +123,7 @@ and
 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 @@ The ruleset does not need to be reloaded
 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 @@ support these options, and
 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 @@ pass in on $ext_if proto tcp from any to
 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-list = tableopts-list tableopt
 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 @@ host           = [ "!" ] ( address [ "/"
 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 @@ tos            = "tos" ( "lowdelay" | "t
                  [ "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 |

Modified: projects/l2filter/contrib/pf/pfctl/parse.y
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/parse.y	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/parse.y	Sun Dec 14 00:40:14 2008	(r186069)
@@ -128,7 +128,7 @@ enum	{ PF_STATE_OPT_MAX, PF_STATE_OPT_NO
 	    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 @@ typedef struct {
 
 %}
 
-%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 @@ typedef struct {
 %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 @@ pfrule		: action dir logquick interface 
 					}
 					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 @@ host_list	: ipspec			{ $$ = $1; }
 		}
 		;
 
-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 @@ state_opt_item	: MAXIMUM number		{
 			$$->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 @@ lookup(char *s)
 		{ "drop",		DROP},
 		{ "drop-ovl",		FRAGDROP},
 		{ "dup-to",		DUPTO},
+		{ "ether",		ETHER},
 		{ "fastroute",		FASTROUTE},
 		{ "file",		FILENAME},
 		{ "fingerprints",	FINGERPRINTS},

Modified: projects/l2filter/contrib/pf/pfctl/pf_print_state.c
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pf_print_state.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pf_print_state.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -119,6 +119,26 @@ print_addr(struct pf_addr_wrap *addr, sa
 		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 @@ print_state(struct pf_state *s, int opts
 		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",

Modified: projects/l2filter/contrib/pf/pfctl/pfctl.c
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -1902,8 +1902,8 @@ pfctl_test_altqsupport(int dev, int opts
 
 	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

Modified: projects/l2filter/contrib/pf/pfctl/pfctl.h
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl.h	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl.h	Sun Dec 14 00:40:14 2008	(r186069)
@@ -117,6 +117,7 @@ struct pf_altq	*pfaltq_lookup(const char
 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);

Modified: projects/l2filter/contrib/pf/pfctl/pfctl_parser.c
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl_parser.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl_parser.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$");
 #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 @@ print_rule(struct pf_rule *r, const char
 	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 @@ print_rule(struct pf_rule *r, const char
 				    "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 @@ host(const char *s)
 }
 
 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 @@ host_dns(const char *s, int v4mask, int 
 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 @@ append_addr_host(struct pfr_buffer *b, s
 			errno = EINVAL;
 			return (-1);
 		}
+		addr.pfra_ether = n->addr.addr_ether;
 		if (pfr_buf_add(b, &addr))
 			return (-1);
 	} while ((n = n->next) != NULL);

Modified: projects/l2filter/contrib/pf/pfctl/pfctl_parser.h
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl_parser.h	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl_parser.h	Sun Dec 14 00:40:14 2008	(r186069)
@@ -296,6 +296,7 @@ void			 ifa_load(void);
 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 *,

Modified: projects/l2filter/contrib/pf/pfctl/pfctl_radix.c
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl_radix.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl_radix.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -607,12 +607,20 @@ pfr_next_token(char buf[BUF_SIZE], FILE 
 	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);
 }

Modified: projects/l2filter/contrib/pf/pfctl/pfctl_table.c
==============================================================================
--- projects/l2filter/contrib/pf/pfctl/pfctl_table.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/contrib/pf/pfctl/pfctl_table.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -438,6 +438,8 @@ print_addrx(struct pfr_addr *ad, struct 
 	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");

Modified: projects/l2filter/sbin/ifconfig/ifconfig.8
==============================================================================
--- projects/l2filter/sbin/ifconfig/ifconfig.8	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/sbin/ifconfig/ifconfig.8	Sun Dec 14 00:40:14 2008	(r186069)
@@ -240,6 +240,27 @@ and will never send any requests.
 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

Modified: projects/l2filter/sbin/ifconfig/ifconfig.c
==============================================================================
--- projects/l2filter/sbin/ifconfig/ifconfig.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/sbin/ifconfig/ifconfig.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -782,7 +782,7 @@ setifname(const char *val, int dummy __u
 #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" \
@@ -1020,6 +1020,10 @@ static struct cmd basic_cmds[] = {
 	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),

Modified: projects/l2filter/sbin/ipfw/ipfw.8
==============================================================================
--- projects/l2filter/sbin/ipfw/ipfw.8	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/sbin/ipfw/ipfw.8	Sun Dec 14 00:40:14 2008	(r186069)
@@ -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
@@ -336,9 +336,9 @@ You can use
 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
@@ -346,11 +346,12 @@ have this picture in mind in order to de
        +----------->-----------+
        ^                       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
@@ -374,13 +375,39 @@ is invoked from
 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
@@ -394,7 +421,7 @@ differentiate among the possible places.
 .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
@@ -405,7 +432,7 @@ ipfw add 10 skipto 4000 all from any to 
 .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
@@ -1123,6 +1150,19 @@ Everything following // is considered as
 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 .
@@ -1134,6 +1174,25 @@ input for delivery.
 .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.
@@ -1145,6 +1204,19 @@ Matches IP packets whose destination por
 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 .
@@ -1349,57 +1421,6 @@ of source and destination addresses and 
 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
@@ -1448,6 +1469,40 @@ interface.
 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.
@@ -1604,6 +1659,8 @@ If
 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.
@@ -1737,6 +1794,13 @@ and
 .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

Modified: projects/l2filter/sbin/ipfw/ipfw2.c
==============================================================================
--- projects/l2filter/sbin/ipfw/ipfw2.c	Sat Dec 13 23:49:09 2008	(r186068)
+++ projects/l2filter/sbin/ipfw/ipfw2.c	Sun Dec 14 00:40:14 2008	(r186069)
@@ -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 @@ static struct _s_x f_iptos[] = {
 	{ 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 @@ static struct _s_x limit_masks[] = {
  * 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 @@ static struct _s_x ether_types[] = {
 	{ "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 @@ enum tokens {
 	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 @@ enum tokens {
 
 	TOK_FIB,
 	TOK_SETFIB,
+
+	TOK_STATEOPTS,
+
+	TOK_ARP_OP,
+	TOK_ARP_SRC,
+	TOK_ARP_DST,
 };
 
 struct _s_x dummynet_params[] = {
@@ -475,9 +499,13 @@ struct _s_x rule_options[] = {
 	{ "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 @@ struct _s_x rule_options[] = {
 	{ "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 @@ print_port(int proto, uint16_t port)
 			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 @@ struct _s_x _port_name[] = {
 	{"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 @@ print_newports(ipfw_insn_u16 *cmd, int p
  * 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 @@ strtoport(char *s, char **end, int base,
 			*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 @@ print_ip(ipfw_insn_ip *cmd, char const *
 }
 
 /*
- * 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 @@ print_ext6hdr( ipfw_insn *cmd )
  * 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 @@ show_ipfw(struct ip_fw *rule, int pcwidt
 			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 @@ show_ipfw(struct ip_fw *rule, int pcwidt
 				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 @@ show_ipfw(struct ip_fw *rule, int pcwidt
 				comment = (char *)(cmd + 1);
 				break;
 

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***


More information about the svn-src-projects mailing list