// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /****************************************************************************** * * Copyright (c) 2020 MaxLinear, Inc. * Copyright (c) 2018 Intel Corporation * ******************************************************************************/ /* This intercepts all packets transferred on any bridge in the system and * drops the IPv4 IGMP and the IPv6 MLD packets. */ #include #include #include #include #include #include #include #include #include /* max number of encapsulated vlan tags */ #define MAX_VLAN_TAGS 3 static struct net_device *dp_mcast_filter_bridge; void _dp_mcast_filter_active(struct net_device *bridge) { ASSERT_RTNL(); WRITE_ONCE(dp_mcast_filter_bridge, bridge); } EXPORT_SYMBOL(_dp_mcast_filter_active); static bool dp_mcast_filter_is_active(struct net_device *bridge) { return bridge == READ_ONCE(dp_mcast_filter_bridge); } /* Iterates over the VLAN tags and increases nhoff for each VLAN tag. * returns 0 on success and -EINVAL in case of an parsing error. */ static int dp_mcast_vlan_iter(struct sk_buff *skb, __be16 *proto, int *nhoff) { const struct vlan_hdr *vlan = NULL; struct vlan_hdr _vlan; int i; for (i = 0; i < MAX_VLAN_TAGS; i++) { vlan = skb_header_pointer(skb, *nhoff, sizeof(_vlan), &_vlan); if (!vlan) return -EINVAL; *proto = vlan->h_vlan_encapsulated_proto; *nhoff += sizeof(*vlan); if (!eth_type_vlan(*proto)) break; } return 0; } static unsigned int dp_mcast_br_hook_ipv4(struct sk_buff *skb, int nhoff) { struct iphdr *iph; if (!pskb_network_may_pull(skb, nhoff + sizeof(struct iphdr))) return NF_ACCEPT; iph = (struct iphdr *)(skb_network_header(skb) + nhoff); if (!iph || iph->ihl * 4 < sizeof(struct iphdr)) { pr_debug("IPv4 packet too short, accepted"); return NF_ACCEPT; } if (iph->protocol == IPPROTO_IGMP) { pr_debug("IPv4 IGMP packet dropped"); return NF_DROP; } return NF_ACCEPT; } static unsigned int dp_mcast_br_hook_ipv6(struct sk_buff *skb, int nhoff) { const struct ipv6hdr *iph; struct ipv6hdr _iph; __u8 nexthdr; __be16 frag_off; iph = skb_header_pointer(skb, nhoff, sizeof(_iph), &_iph); if (!iph) { pr_debug("IPv6 packet too short"); return NF_ACCEPT; } nexthdr = iph->nexthdr; nhoff = ipv6_skip_exthdr(skb, nhoff + sizeof(_iph), &nexthdr, &frag_off); if (nhoff < 0) { pr_debug("IPv6 packet too short, accepted"); return NF_ACCEPT; } if (ipv6_is_mld(skb, nexthdr, nhoff)) { pr_debug("IPv6 MLD packet dropped"); return NF_DROP; } return NF_ACCEPT; } static unsigned int dp_mcast_br_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { __be16 proto = skb->protocol; int nhoff = skb_network_offset(skb); struct net_device *upper; unsigned int ret; int err; if (eth_type_vlan(proto)) { err = dp_mcast_vlan_iter(skb, &proto, &nhoff); if (err) { pr_debug("VLAN header broken, accepted"); return NF_ACCEPT; } } switch (proto) { case htons(ETH_P_IP): ret = dp_mcast_br_hook_ipv4(skb, nhoff); break; case htons(ETH_P_IPV6): ret = dp_mcast_br_hook_ipv6(skb, nhoff); break; default: return NF_ACCEPT; } /* only run the slow path for packets we'd like to drop */ if (ret != NF_DROP) return ret; rcu_read_lock(); upper = netdev_master_upper_dev_get_rcu(state->in); rcu_read_unlock(); if (!upper) { pr_debug("Not a bridge port, accepted\n"); return NF_ACCEPT; } if (!dp_mcast_filter_is_active(upper)) { pr_debug("The multicast filter is inactive for %s, accepted\n", upper->name); return NF_ACCEPT; } pr_debug("The multicast filter is active for %s, dropped\n", upper->name); return NF_DROP; } static struct nf_hook_ops dp_mcast_ops = { .hook = dp_mcast_br_hook, .hooknum = NF_BR_PRE_ROUTING, .pf = NFPROTO_BRIDGE, .priority = NF_BR_PRI_BRNF, }; static int dp_mcast_netdev_event(struct notifier_block *block __maybe_unused, unsigned long event, void *info) { struct net_device *ndev = netdev_notifier_info_to_dev(info); ASSERT_RTNL(); if (event == NETDEV_UNREGISTER && dp_mcast_filter_is_active(ndev)) _dp_mcast_filter_active(NULL); return NOTIFY_DONE; } static struct notifier_block netdev_notifier = { .notifier_call = dp_mcast_netdev_event, }; static int __init dp_mcast_init(void) { int ret; ret = register_netdevice_notifier(&netdev_notifier); if (ret) { pr_err("%s: failed to register the netdevice notifier", __func__); return ret; } return nf_register_hook(&dp_mcast_ops); } static void __exit dp_mcast_fini(void) { unregister_netdevice_notifier(&netdev_notifier); nf_unregister_hook(&dp_mcast_ops); } module_init(dp_mcast_init); module_exit(dp_mcast_fini); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("PON multicast filter Driver");