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 #include #include /* for net/if.h */ +#include #include #include #include #include +#include +#define _IP_VHL + #include /* for struct arpcom */ #include #include #include #include /* for struct arpcom */ +#include + +#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.