Index: bridge.c
===================================================================
RCS file: /home/ncvs/src/sys/net/bridge.c,v
retrieving revision 1.16
diff -u -r1.16 bridge.c
--- bridge.c	2000/02/08 14:53:55	1.16
+++ bridge.c	2000/04/28 18:33:52
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 1998-2000 Luigi Rizzo
+ * Copyright (c) 2000 Robert N. M. Watson
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -78,18 +79,25 @@
 #include <sys/malloc.h>
 #include <sys/systm.h>
 #include <sys/socket.h> /* for net/if.h */
+#include <sys/sockio.h>
 #include <sys/kernel.h>
 #include <sys/sysctl.h>
 
 #include <net/if.h>
 #include <net/if_types.h>
+#include <net/bpf.h>
 
+#define	_IP_VHL
+
 #include <netinet/in.h> /* for struct arpcom */
 #include <netinet/in_systm.h>
 #include <netinet/in_var.h>
 #include <netinet/ip.h>
 #include <netinet/if_ether.h> /* for struct arpcom */
 
+#include <machine/in_cksum.h>
+
+#include "opt_bdg.h"
 #include "opt_ipfw.h" 
 #include "opt_ipdn.h" 
 
@@ -471,6 +479,52 @@
     do_bridge=0;
 }
 
+#ifdef BRIDGE_IF
+/*
+ * Support for bridge0 bpf-centric interface
+ */
+struct ifnet    bridge_sniff_if;
+
+static int
+bridge_if_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+
+	if (cmd == SIOCSIFFLAGS)
+		return (0);
+
+	return (EAFNOSUPPORT);
+}
+
+static int
+bridge_sniff_if_output(struct ifnet *ifp, struct mbuf *m,
+    struct sockaddr *dst, struct rtentry *rt)
+{
+
+	/* XXX alternatively, bdg_forward could be called here */
+	printf("bridge_sniff_if_output\n");
+	return (EOPNOTSUPP);
+}
+
+static void
+bridge_if_init(void)
+{
+	bridge_sniff_if.if_name = "bridge";
+	bridge_sniff_if.if_unit = 0;
+	bridge_sniff_if.if_mtu = ETHERMTU;
+	bridge_sniff_if.if_flags = IFF_UP | IFF_BROADCAST | IFF_MULTICAST;
+	bridge_sniff_if.if_ioctl = bridge_if_ioctl;
+	bridge_sniff_if.if_output = bridge_sniff_if_output;
+	bridge_sniff_if.if_type = IFT_OTHER;
+	bridge_sniff_if.if_snd.ifq_maxlen = ifqmaxlen;
+
+	if_attach(&bridge_sniff_if);
+	bpfattach(&bridge_sniff_if, DLT_EN10MB, sizeof(struct ether_header));
+}
+
+PSEUDO_SET(bridge_if_init, if_bridge);
+
+#endif /* BRIDGE_IF */
+
 /*
  * bridge_in() is invoked to perform bridging decision on input packets.
  * On Input:
@@ -580,6 +634,208 @@
     }
 }
 
+#ifdef IPFIREWALL
+/*
+ * IPFW hook for bridge code.  Passed in are the destination interface,
+ * as well as a pointer to the mbuf holding our packet.  0 is returned to
+ * indicate that IPFW has accepted the packet; non-zero indicates that
+ * it should be dropped, either because it was not permitted (EPERM), or
+ * because it has been delivered to DUMMYNET.
+ *
+ * With DUMMYNET pipe case, drop will be returned but a copy of the
+ * packet will be filed with DUMMYNET for future delivery.
+ */
+static int
+bridge_ipfirewall(struct mbuf **m0, struct ifnet *src, struct ifnet *dst)
+{
+	struct ether_header	*eh;
+	struct ip_fw_chain	*rule = NULL;
+	struct mbuf	*m = *m0;
+	struct ip	*ip;
+	u_int16_t	dummy = 0;
+	u_short	sum;
+	int	error = 0;
+	int	iscopy = 0;	/* is m our own copy of *m0 */
+	int	hlen, i;
+
+	if (!ip_fw_chk_ptr)
+		return (0);
+
+#ifdef DUMMYNET
+	if (m->m_type == MT_DUMMYNET) {
+		/*
+		 * The packet was already tagged, so part of the processing
+		 * was done already, so we can skip some syntax checking,
+		 * etc.
+		 */
+		rule = (struct ip_fw_chain *)(m->m_data);
+		(*m0) = m = m->m_next;
+
+		src = m->m_pkthdr.rcvif; /* could be NULL in output */
+
+		iscopy = 0;
+		error = 0;
+
+		/*
+		 * XXX This retains the current DUMMYNET/bridging bug that
+		 * causes a PIPE to implicitely ACCEPT.  This should not
+		 * be the case, as it is *wrong*, as well as inconsistent
+		 * with IP-layer filtering.
+		 */
+		goto done;
+	}
+#endif /* DUMMYNET */
+
+	/* administrative bridge/ipfw check */
+	if (bdg_ipfw == 0)
+		return (0);
+
+	/*
+	 * XXX Currently only packets not sourced locally are filtered.
+	 * Is this correct?
+	 */
+	/* only filter on incoming packets */
+	if (src == NULL)
+		return (0);
+
+	/*
+	 * XXX Currently, the firewall code will free the packet when
+	 * it feels like dropping it.  IPFW should be changed to let
+	 * the caller drop the packet, not do it by itself, which would
+	 * also allow for boolean rule evaluation, fewer copies, etc.
+	 *
+	 * The condition is necessitated by the dummynet code.
+	 */
+	if (!iscopy) {
+		int	needed = min(MHLEN, sizeof(struct ether_header) +
+		    max_protohdr);
+		needed = min(needed, (*m0)->m_len);
+
+		m = m_copypacket((*m0), M_DONTWAIT);
+		if (m == NULL) {
+			printf("bridge_ipfirewall: m_copypacket failed\n");
+			return (0);
+		}
+
+		m = m_pullup(m, needed);
+		if (m == NULL) {
+			printf("bridge_ipfirewall: m_pullup failed\n");
+			return (ENOBUFS);
+		}
+		iscopy = 1;
+	}
+
+	/* sanity check the IP header, etc. */
+	if ((m)->m_len < sizeof(struct ether_header) + sizeof(struct ip))
+		return (EPERM);
+
+	eh = mtod(m, struct ether_header *);
+	ip = (struct ip *)(eh + 1);
+
+	if (IP_VHL_V(ip->ip_vhl) != IPVERSION)
+		return (EPERM);
+
+	hlen = IP_VHL_HL(ip->ip_vhl) << 2;
+	if (hlen < sizeof(struct ip))
+		return (EPERM);
+
+	if ((m)->m_len < sizeof(struct ether_header) + hlen)
+		return (EPERM);
+
+	if (hlen == sizeof(struct ip))
+		sum = in_cksum_hdr(ip);
+	else
+		sum = in_cksum((u_short *) ip, hlen);
+
+	if (sum)
+		return (EPERM);
+
+	/*
+	 * Before calling the firewall, swap fields in the style of ip_input.
+	 * This assumes the packet is IP, and header is continguous.
+	 */
+	NTOHS(ip->ip_len);
+	if (ip->ip_len < hlen) {
+		error = EPERM;
+		goto done;
+	}
+
+	NTOHS(ip->ip_id);
+	NTOHS(ip->ip_off);
+
+	if (m->m_pkthdr.len < ip->ip_len + sizeof(struct ether_header)) {
+		error = EPERM;
+		goto done;
+	}
+
+	i = (*ip_fw_chk_ptr)(&ip, hlen, NULL, &dummy, &m, &rule, NULL);
+	if (m == NULL) {
+		/*
+		 * packet discarded by firewall; however, m no longer needs
+		 * freeing so fake it not being a copy
+		 */
+		iscopy = 0;
+		error = EPERM;
+		goto done;
+	}
+
+	/*
+	 * Restore original wire byte order if packet was accepted
+	 */
+	HTONS(ip->ip_len);
+	HTONS(ip->ip_id);
+	NTOHS(ip->ip_off);
+
+	if (i == 0) {
+		/*
+		 * Firewall accepts packet
+		 */
+		error = 0;
+		goto done;
+	}
+
+#ifdef DUMMYNET
+	if (i & 0x10000) {
+		/*
+		 * DummyNet rule requires this packet.
+		 */
+		dummynet_io((i & 0xffff), DN_TO_BDG_FWD, m, dst, NULL, 0, rule,
+		    0);
+
+		iscopy = 0;
+		error = EPERM;	/* XXX should be something more innocuous */
+		goto done;
+	}
+#endif
+
+	if (i != 0 && (i & IP_FW_PORT_DYNT_FLAG) == 0) {
+		/*
+		 * XXX This was not implemented in the original bridge/ipfw
+		 * code, and is not implemented here either.  Due to the
+		 * code paths, it's not all that easy to arrange, especially
+		 * if you want to avoid duplicate delivery.
+		 */
+		/* divert or tee packet */
+		printf("bridge_ipfirewall: divert port %d\n", i);
+
+	} else {
+		printf("bridge_ipfirewall: fall through case, dropping\n");
+	}
+
+	error = EPERM;
+
+done:
+	if (iscopy) {
+		/* free our local copy for the firewall */
+		m_freem(m);
+	}
+
+	if (error)
+		printf("bridge_ipfirewall: dropping with error %d\n", error);
+	return (error);
+}
+#endif /* IPFIREWALL */
+
 /*
  * Forward to dst, excluding src port and muted interfaces.
  * The packet is freed if possible (i.e. surely not of interest for
@@ -599,17 +855,16 @@
  *    *m0  -- pointer to the packet (NULL if still existent)
  */
 int
-bdg_forward (struct mbuf **m0, struct ifnet *dst)
+bdg_forward(struct mbuf **m0, struct ifnet *dst)
 {
-    struct ifnet *src = (*m0)->m_pkthdr.rcvif; /* could be NULL in output */
-    struct ifnet *ifp, *last = NULL ;
-    int error=0, s ;
-    int canfree = 0 ; /* can free the buf at the end if set */
-    int once = 0;      /* loop only once */
-    struct mbuf *m ;
+	struct ether_header	*eh;
+	struct ifnet	*src = (*m0)->m_pkthdr.rcvif;	/* NULL in output */
+	struct ifnet	*ifp, *last = NULL;
+	struct mbuf	*m;
+	int	error=0, s;
+	int	canfree = 0;	/* can free the buf at the end if set */
+	int	once = 0;	/* loop only once */
 
-    struct ether_header *eh = mtod(*m0, struct ether_header *); /* XXX */
-
     if (dst == BDG_DROP) { /* this should not happen */
 	printf("xx bdg_forward for BDG_DROP\n");
 	m_freem(*m0) ;
@@ -633,138 +888,55 @@
 	panic("bdg_forward: bad dst");
 
 #ifdef IPFIREWALL
-    /*
-     * do filtering in a very similar way to what is done
-     * in ip_output. Only for IP packets, and only pass/fail/dummynet
-     * is supported. The tricky thing is to make sure that enough of
-     * the packet (basically, Eth+IP+TCP/UDP headers) is contiguous
-     * so that calls to m_pullup in ip_fw_chk will not kill the
-     * ethernet header.
-     */
-    if (ip_fw_chk_ptr) {
-	u_int16_t dummy = 0 ;
-	struct ip_fw_chain *rule = NULL ;
-	int off;
-	struct ip *ip ;
-
-	m = *m0 ;
 #ifdef DUMMYNET
-	if (m->m_type == MT_DUMMYNET) {
-	    /*
-	     * the packet was already tagged, so part of the
-	     * processing was already done, and we need to go down.
-	     */
-	    rule = (struct ip_fw_chain *)(m->m_data) ;
-	    (*m0) = m = m->m_next ;
-
-	    src = m->m_pkthdr.rcvif; /* could be NULL in output */
-	    eh = mtod(m, struct ether_header *); /* XXX */
-	    canfree = 1 ; /* for sure, a copy is not needed later. */
-	    goto forward; /* HACK! I should obey the fw_one_pass */
-	}
-#endif
-	if (bdg_ipfw == 0) /* this test must be here. */
-	    goto forward ;
-	if (src == NULL)
-	    goto forward ; /* do not apply to packets from ether_output */
 	/*
-	 * In this section, canfree=1 means m is the same as *m0.
-	 * canfree==0 means m is a copy. We need to make a copy here
-	 * (to be destroyed on exit from the firewall section) because
-	 * the firewall itself might destroy the packet.
-	 * (This is not very smart... i should really change ipfw to
-	 * leave the pkt alive!)
+	 * This is serious nastiness.  DUMMYNET will insert the packet
+	 * into bdg_forward for further processing, but prefix it with
+	 * a header.  This means we can't look at the packet itself
+	 * to determine if it is an IP packet, but instead rely on the
+	 * DUMMYNET header presence to allow us to pass it into the
+	 * firewall code.  This is an abomination.
 	 */
-	if (canfree == 0 ) {
-	    /*
-	     * Need to make a copy (and for good measure, make sure that
-	     * the header is contiguous). The original is still in *m0
-	     */
-	    int needed = min(MHLEN, 14+max_protohdr) ;
-	    needed = min(needed, (*m0)->m_len ) ;
+	if ((*m0)->m_type == MT_DUMMYNET)
+		goto narstiness; 
+#endif /* DUMMYNET */
+#endif /* IPFIREWALL */
 
-	    m = m_copypacket( (*m0), M_DONTWAIT);
-	    if (m == NULL) {
-	        printf("-- bdg: m_copypacket failed.\n") ;
-		return ENOBUFS ;
-	    }
-	    m = m_pullup(m, needed) ;
-	    if ( m == NULL ) {
-		printf("-- bdg: pullup failed.\n") ;
-		return ENOBUFS ;
-	    }
-	}
+#ifdef BRIDGE_IF
+	if (bridge_sniff_if.if_bpf && src != NULL)
+		bpf_mtap(&bridge_sniff_if, *m0);
+#endif /* BRIDGE_IF */
 
-	/*
-	 * before calling the firewall, swap fields the same as IP does.
-	 * here we assume the pkt is an IP one and the header is contiguous
-	 */
-	eh = mtod(m, struct ether_header *);
-	ip = (struct ip *)(eh + 1 ) ;
-	NTOHS(ip->ip_len);
-	NTOHS(ip->ip_id);
-	NTOHS(ip->ip_off);
+	eh = mtod(*m0, struct ether_header *);
 
-	/*
-	 * The third parameter to the firewall code is the dst.  interface.
-	 * Since we apply checks only on input pkts we use NULL.
-	 */
-	off = (*ip_fw_chk_ptr)(NULL, 0, NULL, &dummy, &m, &rule, NULL) ;
+	switch(ntohs(eh->ether_type)) {
+	case ETHERTYPE_IP:
 
-	if (m == NULL) { /* pkt discarded by firewall */
-	    /*
-	     * At this point, if canfree==1, m and *m0 were the same
-	     * thing, so just clear ptr. Otherwise, leave it alone, the
-	     * upper layer might still make use of it somewhere.
-	     */
-	    if (canfree)
-		*m0 = NULL ;
-	    return 0 ;
-	}
+#ifdef IPFIREWALL
 
-	/*
-	 * If we get here, the firewall has passed the pkt, but the
-	 * mbuf pointer might have changed. Restore eh, ip, and the
-	 * fields NTOHS()'d. Then, if canfree==1, also restore *m0.
-	 */
-	eh = mtod(m, struct ether_header *);
-	ip = (struct ip *)(eh + 1 ) ;
-	HTONS(ip->ip_len);
-	HTONS(ip->ip_id);
-	HTONS(ip->ip_off);
-	if (canfree) /* m was a reference to *m0, so update *m0 */
-	    *m0 = m ;
+narstiness:
+		if (bridge_ipfirewall(m0, src, dst)) {
+			/* packet is droppable */
+			return (0);
+		}
+#endif /* IPFIREWALL */
 
-	if (off == 0) { /* a PASS rule.  */
-	    if (canfree == 0) /* destroy the copy */
-		m_freem(m);
-	    goto forward ;
+		/* Allow packet through */
+		break;
+
+	default:
+		/* Default case is to pass through without modification */
 	}
+
 #ifdef DUMMYNET
-	if (off & 0x10000) {  
-	    /*
-	     * pass the pkt to dummynet. Need to include m, dst, rule.
-	     * Dummynet consumes the packet in all cases.
-	     */
-	    dummynet_io((off & 0xffff), DN_TO_BDG_FWD, m, dst, NULL, 0, rule, 0);
-	    if (canfree) /* dummynet has consumed the original one */
-		*m0 = NULL ;
-	    return 0 ;
-	}
-#endif
 	/*
-	 * XXX add divert/forward actions...
+	 * In the case of DUMMYNET, eh may point into the MT_DUMMYNET
+	 * mbuf instead of the real packet.
 	 */
-	/* if none of the above matches, we have to drop the pkt */
-	bdg_ipfw_drops++ ;
-	m_freem(m);
-	if (canfree == 0) /* m was a copy */
-	    m_freem(*m0);
-	*m0 = NULL ;
-	return 0 ;
-    }
-forward:
-#endif /* IPFIREWALL */
+	eh = mtod(*m0, struct ether_header *);
+	src = (*m0)->m_pkthdr.rcvif;
+#endif
+
     /*
      * Now *m0 is the only pkt left. If canfree != 0 the pkt might be
      * used by the upper layers which could scramble header fields.
