+ return 0;
+}
+
+/*
+ * bridge_link_event:
+ *
+ * Report a data link event on an interface
+ */
+static void
+bridge_link_event(struct ifnet *ifp, u_int32_t event_code)
+{
+ struct {
+ struct kern_event_msg header;
+ u_int32_t unit;
+ char if_name[IFNAMSIZ];
+ } event;
+
+#if BRIDGE_DEBUG
+ if (if_bridge_debug & BR_DBGF_LIFECYCLE) {
+ printf("%s: %s event_code %u - %s\n", __func__, ifp->if_xname,
+ event_code, dlil_kev_dl_code_str(event_code));
+ }
+#endif /* BRIDGE_DEBUG */
+
+ bzero(&event, sizeof(event));
+ event.header.total_size = sizeof(event);
+ event.header.vendor_code = KEV_VENDOR_APPLE;
+ event.header.kev_class = KEV_NETWORK_CLASS;
+ event.header.kev_subclass = KEV_DL_SUBCLASS;
+ event.header.event_code = event_code;
+ event.header.event_data[0] = ifnet_family(ifp);
+ event.unit = (u_int32_t)ifnet_unit(ifp);
+ strlcpy(event.if_name, ifnet_name(ifp), IFNAMSIZ);
+ ifnet_event(ifp, &event.header);
+}
+
+#define BRIDGE_HF_DROP(reason, func, line) { \
+ bridge_hostfilter_stats.reason++; \
+ if (if_bridge_debug & BR_DBGF_HOSTFILTER) \
+ printf("%s.%d" #reason, func, line); \
+ error = EINVAL; \
+}
+
+/*
+ * Make sure this is a DHCP or Bootp request that match the host filter
+ */
+static int
+bridge_dhcp_filter(struct bridge_iflist *bif, struct mbuf *m, size_t offset)
+{
+ int error = EINVAL;
+ struct dhcp dhcp;
+
+ /*
+ * Note: We use the dhcp structure because bootp structure definition
+ * is larger and some vendors do not pad the request
+ */
+ error = mbuf_copydata(m, offset, sizeof(struct dhcp), &dhcp);
+ if (error != 0) {
+ BRIDGE_HF_DROP(brhf_dhcp_too_small, __func__, __LINE__);
+ goto done;
+ }
+ if (dhcp.dp_op != BOOTREQUEST) {
+ BRIDGE_HF_DROP(brhf_dhcp_bad_op, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * The hardware address must be an exact match
+ */
+ if (dhcp.dp_htype != ARPHRD_ETHER) {
+ BRIDGE_HF_DROP(brhf_dhcp_bad_htype, __func__, __LINE__);
+ goto done;
+ }
+ if (dhcp.dp_hlen != ETHER_ADDR_LEN) {
+ BRIDGE_HF_DROP(brhf_dhcp_bad_hlen, __func__, __LINE__);
+ goto done;
+ }
+ if (bcmp(dhcp.dp_chaddr, bif->bif_hf_hwsrc,
+ ETHER_ADDR_LEN) != 0) {
+ BRIDGE_HF_DROP(brhf_dhcp_bad_chaddr, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Client address must match the host address or be not specified
+ */
+ if (dhcp.dp_ciaddr.s_addr != bif->bif_hf_ipsrc.s_addr &&
+ dhcp.dp_ciaddr.s_addr != INADDR_ANY) {
+ BRIDGE_HF_DROP(brhf_dhcp_bad_ciaddr, __func__, __LINE__);
+ goto done;
+ }
+ error = 0;
+done:
+ return error;
+}
+
+static int
+bridge_host_filter(struct bridge_iflist *bif, struct mbuf *m)
+{
+ int error = EINVAL;
+ struct ether_header *eh;
+ static struct in_addr inaddr_any = { .s_addr = INADDR_ANY };
+
+ /*
+ * Check the Ethernet header is large enough
+ */
+ if (mbuf_pkthdr_len(m) < sizeof(struct ether_header)) {
+ BRIDGE_HF_DROP(brhf_ether_too_small, __func__, __LINE__);
+ goto done;
+ }
+ if (mbuf_len(m) < sizeof(struct ether_header) &&
+ mbuf_pullup(&m, sizeof(struct ether_header)) != 0) {
+ BRIDGE_HF_DROP(brhf_ether_pullup_failed, __func__, __LINE__);
+ goto done;
+ }
+ eh = mtod(m, struct ether_header *);
+
+ /*
+ * Restrict the source hardware address
+ */
+ if ((bif->bif_flags & BIFF_HF_HWSRC) == 0 ||
+ bcmp(eh->ether_shost, bif->bif_hf_hwsrc,
+ ETHER_ADDR_LEN) != 0) {
+ BRIDGE_HF_DROP(brhf_bad_ether_srchw_addr, __func__, __LINE__);
+ goto done;
+ }
+
+ /*
+ * Restrict Ethernet protocols to ARP and IP
+ */
+ if (eh->ether_type == htons(ETHERTYPE_ARP)) {
+ struct ether_arp *ea;
+ size_t minlen = sizeof(struct ether_header) +
+ sizeof(struct ether_arp);
+
+ /*
+ * Make the Ethernet and ARP headers contiguous
+ */
+ if (mbuf_pkthdr_len(m) < minlen) {
+ BRIDGE_HF_DROP(brhf_arp_too_small, __func__, __LINE__);
+ goto done;
+ }
+ if (mbuf_len(m) < minlen && mbuf_pullup(&m, minlen) != 0) {
+ BRIDGE_HF_DROP(brhf_arp_pullup_failed,
+ __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Verify this is an ethernet/ip arp
+ */
+ eh = mtod(m, struct ether_header *);
+ ea = (struct ether_arp *)(eh + 1);
+ if (ea->arp_hrd != htons(ARPHRD_ETHER)) {
+ BRIDGE_HF_DROP(brhf_arp_bad_hw_type,
+ __func__, __LINE__);
+ goto done;
+ }
+ if (ea->arp_pro != htons(ETHERTYPE_IP)) {
+ BRIDGE_HF_DROP(brhf_arp_bad_pro_type,
+ __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Verify the address lengths are correct
+ */
+ if (ea->arp_hln != ETHER_ADDR_LEN) {
+ BRIDGE_HF_DROP(brhf_arp_bad_hw_len, __func__, __LINE__);
+ goto done;
+ }
+ if (ea->arp_pln != sizeof(struct in_addr)) {
+ BRIDGE_HF_DROP(brhf_arp_bad_pro_len,
+ __func__, __LINE__);
+ goto done;
+ }
+
+ /*
+ * Allow only ARP request or ARP reply
+ */
+ if (ea->arp_op != htons(ARPOP_REQUEST) &&
+ ea->arp_op != htons(ARPOP_REPLY)) {
+ BRIDGE_HF_DROP(brhf_arp_bad_op, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Verify source hardware address matches
+ */
+ if (bcmp(ea->arp_sha, bif->bif_hf_hwsrc,
+ ETHER_ADDR_LEN) != 0) {
+ BRIDGE_HF_DROP(brhf_arp_bad_sha, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Verify source protocol address:
+ * May be null for an ARP probe
+ */
+ if (bcmp(ea->arp_spa, &bif->bif_hf_ipsrc.s_addr,
+ sizeof(struct in_addr)) != 0 &&
+ bcmp(ea->arp_spa, &inaddr_any,
+ sizeof(struct in_addr)) != 0) {
+ BRIDGE_HF_DROP(brhf_arp_bad_spa, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ *
+ */
+ bridge_hostfilter_stats.brhf_arp_ok += 1;
+ error = 0;
+ } else if (eh->ether_type == htons(ETHERTYPE_IP)) {
+ size_t minlen = sizeof(struct ether_header) + sizeof(struct ip);
+ struct ip iphdr;
+ size_t offset;
+
+ /*
+ * Make the Ethernet and IP headers contiguous
+ */
+ if (mbuf_pkthdr_len(m) < minlen) {
+ BRIDGE_HF_DROP(brhf_ip_too_small, __func__, __LINE__);
+ goto done;
+ }
+ offset = sizeof(struct ether_header);
+ error = mbuf_copydata(m, offset, sizeof(struct ip), &iphdr);
+ if (error != 0) {
+ BRIDGE_HF_DROP(brhf_ip_too_small, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Verify the source IP address
+ */
+ if (iphdr.ip_p == IPPROTO_UDP) {
+ struct udphdr udp;
+
+ minlen += sizeof(struct udphdr);
+ if (mbuf_pkthdr_len(m) < minlen) {
+ BRIDGE_HF_DROP(brhf_ip_too_small,
+ __func__, __LINE__);
+ goto done;
+ }
+
+ /*
+ * Allow all zero addresses for DHCP requests
+ */
+ if (iphdr.ip_src.s_addr != bif->bif_hf_ipsrc.s_addr &&
+ iphdr.ip_src.s_addr != INADDR_ANY) {
+ BRIDGE_HF_DROP(brhf_ip_bad_srcaddr,
+ __func__, __LINE__);
+ goto done;
+ }
+ offset = sizeof(struct ether_header) +
+ (IP_VHL_HL(iphdr.ip_vhl) << 2);
+ error = mbuf_copydata(m, offset,
+ sizeof(struct udphdr), &udp);
+ if (error != 0) {
+ BRIDGE_HF_DROP(brhf_ip_too_small,
+ __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Either it's a Bootp/DHCP packet that we like or
+ * it's a UDP packet from the host IP as source address
+ */
+ if (udp.uh_sport == htons(IPPORT_BOOTPC) &&
+ udp.uh_dport == htons(IPPORT_BOOTPS)) {
+ minlen += sizeof(struct dhcp);
+ if (mbuf_pkthdr_len(m) < minlen) {
+ BRIDGE_HF_DROP(brhf_ip_too_small,
+ __func__, __LINE__);
+ goto done;
+ }
+ offset += sizeof(struct udphdr);
+ error = bridge_dhcp_filter(bif, m, offset);
+ if (error != 0) {
+ goto done;
+ }
+ } else if (iphdr.ip_src.s_addr == INADDR_ANY) {
+ BRIDGE_HF_DROP(brhf_ip_bad_srcaddr,
+ __func__, __LINE__);
+ goto done;
+ }
+ } else if (iphdr.ip_src.s_addr != bif->bif_hf_ipsrc.s_addr ||
+ bif->bif_hf_ipsrc.s_addr == INADDR_ANY) {
+ BRIDGE_HF_DROP(brhf_ip_bad_srcaddr, __func__, __LINE__);
+ goto done;
+ }
+ /*
+ * Allow only boring IP protocols
+ */
+ if (iphdr.ip_p != IPPROTO_TCP &&
+ iphdr.ip_p != IPPROTO_UDP &&
+ iphdr.ip_p != IPPROTO_ICMP &&
+ iphdr.ip_p != IPPROTO_ESP &&
+ iphdr.ip_p != IPPROTO_AH &&
+ iphdr.ip_p != IPPROTO_GRE) {
+ BRIDGE_HF_DROP(brhf_ip_bad_proto, __func__, __LINE__);
+ goto done;
+ }
+ bridge_hostfilter_stats.brhf_ip_ok += 1;
+ error = 0;
+ } else {
+ BRIDGE_HF_DROP(brhf_bad_ether_type, __func__, __LINE__);
+ goto done;
+ }
+done:
+ if (error != 0) {
+ if (if_bridge_debug & BR_DBGF_HOSTFILTER) {
+ if (m) {
+ printf_mbuf_data(m, 0,
+ sizeof(struct ether_header) +
+ sizeof(struct ip));
+ }
+ printf("\n");
+ }
+
+ if (m != NULL) {
+ m_freem(m);
+ }
+ }
+ return error;