3513 lines · 90.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h>#include <linux/skbuff.h>#include <linux/udp.h>#include <linux/jhash.h>#include <linux/if_tunnel.h>#include <linux/net.h>#include <linux/igmp.h>#include <linux/workqueue.h>#include <net/flow.h>#include <net/pkt_sched.h>#include <net/net_namespace.h>#include <net/ip.h>#include <net/udp.h>#include <net/udp_tunnel.h>#include <net/icmp.h>#include <net/mld.h>#include <net/amt.h>#include <uapi/linux/amt.h>#include <linux/security.h>#include <net/gro_cells.h>#include <net/ipv6.h>#include <net/if_inet6.h>#include <net/ndisc.h>#include <net/addrconf.h>#include <net/ip6_route.h>#include <net/inet_common.h>#include <net/inet_dscp.h>#include <net/ip6_checksum.h> static struct workqueue_struct *amt_wq; static HLIST_HEAD(source_gc_list);/* Lock for source_gc_list */static spinlock_t source_gc_lock;static struct delayed_work source_gc_wq;static char *status_str[] = {	"AMT_STATUS_INIT",	"AMT_STATUS_SENT_DISCOVERY",	"AMT_STATUS_RECEIVED_DISCOVERY",	"AMT_STATUS_SENT_ADVERTISEMENT",	"AMT_STATUS_RECEIVED_ADVERTISEMENT",	"AMT_STATUS_SENT_REQUEST",	"AMT_STATUS_RECEIVED_REQUEST",	"AMT_STATUS_SENT_QUERY",	"AMT_STATUS_RECEIVED_QUERY",	"AMT_STATUS_SENT_UPDATE",	"AMT_STATUS_RECEIVED_UPDATE",}; static char *type_str[] = {	"", /* Type 0 is not defined */	"AMT_MSG_DISCOVERY",	"AMT_MSG_ADVERTISEMENT",	"AMT_MSG_REQUEST",	"AMT_MSG_MEMBERSHIP_QUERY",	"AMT_MSG_MEMBERSHIP_UPDATE",	"AMT_MSG_MULTICAST_DATA",	"AMT_MSG_TEARDOWN",}; static char *action_str[] = {	"AMT_ACT_GMI",	"AMT_ACT_GMI_ZERO",	"AMT_ACT_GT",	"AMT_ACT_STATUS_FWD_NEW",	"AMT_ACT_STATUS_D_FWD_NEW",	"AMT_ACT_STATUS_NONE_NEW",}; static struct igmpv3_grec igmpv3_zero_grec; #if IS_ENABLED(CONFIG_IPV6)#define MLD2_ALL_NODE_INIT { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }static struct in6_addr mld2_all_node = MLD2_ALL_NODE_INIT;static struct mld2_grec mldv2_zero_grec;#endif static struct amt_skb_cb *amt_skb_cb(struct sk_buff *skb){	BUILD_BUG_ON(sizeof(struct amt_skb_cb) + sizeof(struct tc_skb_cb) >		     sizeof_field(struct sk_buff, cb)); 	return (struct amt_skb_cb *)((void *)skb->cb +		sizeof(struct tc_skb_cb));} static void __amt_source_gc_work(void){	struct amt_source_node *snode;	struct hlist_head gc_list;	struct hlist_node *t; 	spin_lock_bh(&source_gc_lock);	hlist_move_list(&source_gc_list, &gc_list);	spin_unlock_bh(&source_gc_lock); 	hlist_for_each_entry_safe(snode, t, &gc_list, node) {		hlist_del_rcu(&snode->node);		kfree_rcu(snode, rcu);	}} static void amt_source_gc_work(struct work_struct *work){	__amt_source_gc_work(); 	spin_lock_bh(&source_gc_lock);	mod_delayed_work(amt_wq, &source_gc_wq,			 msecs_to_jiffies(AMT_GC_INTERVAL));	spin_unlock_bh(&source_gc_lock);} static bool amt_addr_equal(union amt_addr *a, union amt_addr *b){	return !memcmp(a, b, sizeof(union amt_addr));} static u32 amt_source_hash(struct amt_tunnel_list *tunnel, union amt_addr *src){	u32 hash = jhash(src, sizeof(*src), tunnel->amt->hash_seed); 	return reciprocal_scale(hash, tunnel->amt->hash_buckets);} static bool amt_status_filter(struct amt_source_node *snode,			      enum amt_filter filter){	bool rc = false; 	switch (filter) {	case AMT_FILTER_FWD:		if (snode->status == AMT_SOURCE_STATUS_FWD &&		    snode->flags == AMT_SOURCE_OLD)			rc = true;		break;	case AMT_FILTER_D_FWD:		if (snode->status == AMT_SOURCE_STATUS_D_FWD &&		    snode->flags == AMT_SOURCE_OLD)			rc = true;		break;	case AMT_FILTER_FWD_NEW:		if (snode->status == AMT_SOURCE_STATUS_FWD &&		    snode->flags == AMT_SOURCE_NEW)			rc = true;		break;	case AMT_FILTER_D_FWD_NEW:		if (snode->status == AMT_SOURCE_STATUS_D_FWD &&		    snode->flags == AMT_SOURCE_NEW)			rc = true;		break;	case AMT_FILTER_ALL:		rc = true;		break;	case AMT_FILTER_NONE_NEW:		if (snode->status == AMT_SOURCE_STATUS_NONE &&		    snode->flags == AMT_SOURCE_NEW)			rc = true;		break;	case AMT_FILTER_BOTH:		if ((snode->status == AMT_SOURCE_STATUS_D_FWD ||		     snode->status == AMT_SOURCE_STATUS_FWD) &&		    snode->flags == AMT_SOURCE_OLD)			rc = true;		break;	case AMT_FILTER_BOTH_NEW:		if ((snode->status == AMT_SOURCE_STATUS_D_FWD ||		     snode->status == AMT_SOURCE_STATUS_FWD) &&		    snode->flags == AMT_SOURCE_NEW)			rc = true;		break;	default:		WARN_ON_ONCE(1);		break;	} 	return rc;} static struct amt_source_node *amt_lookup_src(struct amt_tunnel_list *tunnel,					      struct amt_group_node *gnode,					      enum amt_filter filter,					      union amt_addr *src){	u32 hash = amt_source_hash(tunnel, src);	struct amt_source_node *snode; 	hlist_for_each_entry_rcu(snode, &gnode->sources[hash], node)		if (amt_status_filter(snode, filter) &&		    amt_addr_equal(&snode->source_addr, src))			return snode; 	return NULL;} static u32 amt_group_hash(struct amt_tunnel_list *tunnel, union amt_addr *group){	u32 hash = jhash(group, sizeof(*group), tunnel->amt->hash_seed); 	return reciprocal_scale(hash, tunnel->amt->hash_buckets);} static struct amt_group_node *amt_lookup_group(struct amt_tunnel_list *tunnel,					       union amt_addr *group,					       union amt_addr *host,					       bool v6){	u32 hash = amt_group_hash(tunnel, group);	struct amt_group_node *gnode; 	hlist_for_each_entry_rcu(gnode, &tunnel->groups[hash], node) {		if (amt_addr_equal(&gnode->group_addr, group) &&		    amt_addr_equal(&gnode->host_addr, host) &&		    gnode->v6 == v6)			return gnode;	} 	return NULL;} static void amt_destroy_source(struct amt_source_node *snode){	struct amt_group_node *gnode = snode->gnode;	struct amt_tunnel_list *tunnel; 	tunnel = gnode->tunnel_list; 	if (!gnode->v6) {		netdev_dbg(snode->gnode->amt->dev,			   "Delete source %pI4 from %pI4\n",			   &snode->source_addr.ip4,			   &gnode->group_addr.ip4);#if IS_ENABLED(CONFIG_IPV6)	} else {		netdev_dbg(snode->gnode->amt->dev,			   "Delete source %pI6 from %pI6\n",			   &snode->source_addr.ip6,			   &gnode->group_addr.ip6);#endif	} 	cancel_delayed_work(&snode->source_timer);	hlist_del_init_rcu(&snode->node);	tunnel->nr_sources--;	gnode->nr_sources--;	spin_lock_bh(&source_gc_lock);	hlist_add_head_rcu(&snode->node, &source_gc_list);	spin_unlock_bh(&source_gc_lock);} static void amt_del_group(struct amt_dev *amt, struct amt_group_node *gnode){	struct amt_source_node *snode;	struct hlist_node *t;	int i; 	if (cancel_delayed_work(&gnode->group_timer))		dev_put(amt->dev);	hlist_del_rcu(&gnode->node);	gnode->tunnel_list->nr_groups--; 	if (!gnode->v6)		netdev_dbg(amt->dev, "Leave group %pI4\n",			   &gnode->group_addr.ip4);#if IS_ENABLED(CONFIG_IPV6)	else		netdev_dbg(amt->dev, "Leave group %pI6\n",			   &gnode->group_addr.ip6);#endif	for (i = 0; i < amt->hash_buckets; i++)		hlist_for_each_entry_safe(snode, t, &gnode->sources[i], node)			amt_destroy_source(snode); 	/* tunnel->lock was acquired outside of amt_del_group()	 * But rcu_read_lock() was acquired too so It's safe.	 */	kfree_rcu(gnode, rcu);} /* If a source timer expires with a router filter-mode for the group of * INCLUDE, the router concludes that traffic from this particular * source is no longer desired on the attached network, and deletes the * associated source record. */static void amt_source_work(struct work_struct *work){	struct amt_source_node *snode = container_of(to_delayed_work(work),						     struct amt_source_node,						     source_timer);	struct amt_group_node *gnode = snode->gnode;	struct amt_dev *amt = gnode->amt;	struct amt_tunnel_list *tunnel; 	tunnel = gnode->tunnel_list;	spin_lock_bh(&tunnel->lock);	rcu_read_lock();	if (gnode->filter_mode == MCAST_INCLUDE) {		amt_destroy_source(snode);		if (!gnode->nr_sources)			amt_del_group(amt, gnode);	} else {		/* When a router filter-mode for a group is EXCLUDE,		 * source records are only deleted when the group timer expires		 */		snode->status = AMT_SOURCE_STATUS_D_FWD;	}	rcu_read_unlock();	spin_unlock_bh(&tunnel->lock);} static void amt_act_src(struct amt_tunnel_list *tunnel,			struct amt_group_node *gnode,			struct amt_source_node *snode,			enum amt_act act){	struct amt_dev *amt = tunnel->amt; 	switch (act) {	case AMT_ACT_GMI:		mod_delayed_work(amt_wq, &snode->source_timer,				 msecs_to_jiffies(amt_gmi(amt)));		break;	case AMT_ACT_GMI_ZERO:		cancel_delayed_work(&snode->source_timer);		break;	case AMT_ACT_GT:		mod_delayed_work(amt_wq, &snode->source_timer,				 gnode->group_timer.timer.expires);		break;	case AMT_ACT_STATUS_FWD_NEW:		snode->status = AMT_SOURCE_STATUS_FWD;		snode->flags = AMT_SOURCE_NEW;		break;	case AMT_ACT_STATUS_D_FWD_NEW:		snode->status = AMT_SOURCE_STATUS_D_FWD;		snode->flags = AMT_SOURCE_NEW;		break;	case AMT_ACT_STATUS_NONE_NEW:		cancel_delayed_work(&snode->source_timer);		snode->status = AMT_SOURCE_STATUS_NONE;		snode->flags = AMT_SOURCE_NEW;		break;	default:		WARN_ON_ONCE(1);		return;	} 	if (!gnode->v6)		netdev_dbg(amt->dev, "Source %pI4 from %pI4 Acted %s\n",			   &snode->source_addr.ip4,			   &gnode->group_addr.ip4,			   action_str[act]);#if IS_ENABLED(CONFIG_IPV6)	else		netdev_dbg(amt->dev, "Source %pI6 from %pI6 Acted %s\n",			   &snode->source_addr.ip6,			   &gnode->group_addr.ip6,			   action_str[act]);#endif} static struct amt_source_node *amt_alloc_snode(struct amt_group_node *gnode,					       union amt_addr *src){	struct amt_source_node *snode; 	snode = kzalloc_obj(*snode, GFP_ATOMIC);	if (!snode)		return NULL; 	memcpy(&snode->source_addr, src, sizeof(union amt_addr));	snode->gnode = gnode;	snode->status = AMT_SOURCE_STATUS_NONE;	snode->flags = AMT_SOURCE_NEW;	INIT_HLIST_NODE(&snode->node);	INIT_DELAYED_WORK(&snode->source_timer, amt_source_work); 	return snode;} /* RFC 3810 - 7.2.2.  Definition of Filter Timers * *  Router Mode          Filter Timer         Actions/Comments *  -----------       -----------------       ---------------- * *    INCLUDE             Not Used            All listeners in *                                            INCLUDE mode. * *    EXCLUDE             Timer > 0           At least one listener *                                            in EXCLUDE mode. * *    EXCLUDE             Timer == 0          No more listeners in *                                            EXCLUDE mode for the *                                            multicast address. *                                            If the Requested List *                                            is empty, delete *                                            Multicast Address *                                            Record.  If not, switch *                                            to INCLUDE filter mode; *                                            the sources in the *                                            Requested List are *                                            moved to the Include *                                            List, and the Exclude *                                            List is deleted. */static void amt_group_work(struct work_struct *work){	struct amt_group_node *gnode = container_of(to_delayed_work(work),						    struct amt_group_node,						    group_timer);	struct amt_tunnel_list *tunnel = gnode->tunnel_list;	struct amt_dev *amt = gnode->amt;	struct amt_source_node *snode;	bool delete_group = true;	struct hlist_node *t;	int i, buckets; 	buckets = amt->hash_buckets; 	spin_lock_bh(&tunnel->lock);	if (gnode->filter_mode == MCAST_INCLUDE) {		/* Not Used */		spin_unlock_bh(&tunnel->lock);		goto out;	} 	rcu_read_lock();	for (i = 0; i < buckets; i++) {		hlist_for_each_entry_safe(snode, t,					  &gnode->sources[i], node) {			if (!delayed_work_pending(&snode->source_timer) ||			    snode->status == AMT_SOURCE_STATUS_D_FWD) {				amt_destroy_source(snode);			} else {				delete_group = false;				snode->status = AMT_SOURCE_STATUS_FWD;			}		}	}	if (delete_group)		amt_del_group(amt, gnode);	else		gnode->filter_mode = MCAST_INCLUDE;	rcu_read_unlock();	spin_unlock_bh(&tunnel->lock);out:	dev_put(amt->dev);} /* Non-existent group is created as INCLUDE {empty}: * * RFC 3376 - 5.1. Action on Change of Interface State * * If no interface state existed for that multicast address before * the change (i.e., the change consisted of creating a new * per-interface record), or if no state exists after the change * (i.e., the change consisted of deleting a per-interface record), * then the "non-existent" state is considered to have a filter mode * of INCLUDE and an empty source list. */static struct amt_group_node *amt_add_group(struct amt_dev *amt,					    struct amt_tunnel_list *tunnel,					    union amt_addr *group,					    union amt_addr *host,					    bool v6){	struct amt_group_node *gnode;	u32 hash;	int i; 	if (tunnel->nr_groups >= amt->max_groups)		return ERR_PTR(-ENOSPC); 	gnode = kzalloc(sizeof(*gnode) +			(sizeof(struct hlist_head) * amt->hash_buckets),			GFP_ATOMIC);	if (unlikely(!gnode))		return ERR_PTR(-ENOMEM); 	gnode->amt = amt;	gnode->group_addr = *group;	gnode->host_addr = *host;	gnode->v6 = v6;	gnode->tunnel_list = tunnel;	gnode->filter_mode = MCAST_INCLUDE;	INIT_HLIST_NODE(&gnode->node);	INIT_DELAYED_WORK(&gnode->group_timer, amt_group_work);	for (i = 0; i < amt->hash_buckets; i++)		INIT_HLIST_HEAD(&gnode->sources[i]); 	hash = amt_group_hash(tunnel, group);	hlist_add_head_rcu(&gnode->node, &tunnel->groups[hash]);	tunnel->nr_groups++; 	if (!gnode->v6)		netdev_dbg(amt->dev, "Join group %pI4\n",

Lines 1–500 of 3513.