mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-03 23:47:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * PIMv6 MLD querier
 | 
						|
 * Copyright (C) 2021-2022  David Lamparter for NetDEF, Inc.
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify it
 | 
						|
 * under the terms of the GNU General Public License as published by the Free
 | 
						|
 * Software Foundation; either version 2 of the License, or (at your option)
 | 
						|
 * any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
						|
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
						|
 * more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License along
 | 
						|
 * with this program; see the file COPYING; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | 
						|
 */
 | 
						|
 | 
						|
#ifndef PIM6_MLD_H
 | 
						|
#define PIM6_MLD_H
 | 
						|
 | 
						|
#include "typesafe.h"
 | 
						|
#include "pim_addr.h"
 | 
						|
 | 
						|
struct thread;
 | 
						|
struct pim_instance;
 | 
						|
struct gm_packet_sg;
 | 
						|
struct gm_if;
 | 
						|
struct channel_oil;
 | 
						|
 | 
						|
#define MLD_DEFAULT_VERSION 2
 | 
						|
 | 
						|
/* see comment below on subs_negative/subs_positive */
 | 
						|
enum gm_sub_sense {
 | 
						|
	/* negative/pruning: S,G in EXCLUDE */
 | 
						|
	GM_SUB_NEG = 0,
 | 
						|
	/* positive/joining: *,G in EXCLUDE and S,G in INCLUDE */
 | 
						|
	GM_SUB_POS = 1,
 | 
						|
};
 | 
						|
 | 
						|
enum gm_sg_state {
 | 
						|
	GM_SG_NOINFO = 0,
 | 
						|
	GM_SG_JOIN,
 | 
						|
	GM_SG_JOIN_EXPIRING,
 | 
						|
	/* remaining 3 only valid for S,G when *,G in EXCLUDE */
 | 
						|
	GM_SG_PRUNE,
 | 
						|
	GM_SG_NOPRUNE,
 | 
						|
	GM_SG_NOPRUNE_EXPIRING,
 | 
						|
};
 | 
						|
 | 
						|
static inline bool gm_sg_state_want_join(enum gm_sg_state state)
 | 
						|
{
 | 
						|
	return state != GM_SG_NOINFO && state != GM_SG_PRUNE;
 | 
						|
}
 | 
						|
 | 
						|
/* MLD (S,G) state (on an interface)
 | 
						|
 *
 | 
						|
 * group is always != ::, src is :: for (*,G) joins.  sort order in RB tree is
 | 
						|
 * such that sources for a particular group can be iterated by starting at the
 | 
						|
 * group.  For INCLUDE, no (*,G) entry exists, only (S,G).
 | 
						|
 */
 | 
						|
 | 
						|
PREDECL_RBTREE_UNIQ(gm_packet_sg_subs);
 | 
						|
PREDECL_RBTREE_UNIQ(gm_sgs);
 | 
						|
struct gm_sg {
 | 
						|
	pim_sgaddr sgaddr;
 | 
						|
	struct gm_if *iface;
 | 
						|
	struct gm_sgs_item itm;
 | 
						|
 | 
						|
	enum gm_sg_state state;
 | 
						|
	struct channel_oil *oil;
 | 
						|
	bool tib_joined;
 | 
						|
 | 
						|
	struct timeval created;
 | 
						|
 | 
						|
	/* if a group- or group-and-source specific query is running
 | 
						|
	 * (implies we haven't received any report yet, since it's cancelled
 | 
						|
	 * by that)
 | 
						|
	 */
 | 
						|
	struct thread *t_sg_expire;
 | 
						|
 | 
						|
	/* last-member-left triggered queries (group/group-source specific)
 | 
						|
	 *
 | 
						|
	 * this timer will be running even if we aren't the elected querier,
 | 
						|
	 * in case the election result changes midway through.
 | 
						|
	 */
 | 
						|
	struct thread *t_sg_query;
 | 
						|
 | 
						|
	/* we must keep sending (QRV) queries even if we get a positive
 | 
						|
	 * response, to make sure other routers are updated.  query_sbit
 | 
						|
	 * will be set in that case, since other routers need the *response*,
 | 
						|
	 * not the *query*
 | 
						|
	 */
 | 
						|
	uint8_t n_query;
 | 
						|
	bool query_sbit;
 | 
						|
 | 
						|
	/* subs_positive tracks gm_packet_sg resulting in a JOIN, i.e. for
 | 
						|
	 * (*,G) it has *EXCLUDE* items, for (S,G) it has *INCLUDE* items.
 | 
						|
	 *
 | 
						|
	 * subs_negative is always empty for (*,G) and tracks EXCLUDE items
 | 
						|
	 * for (S,G).  This means that an (S,G) entry is active as a PRUNE if
 | 
						|
	 *   len(src->subs_negative) == len(grp->subs_positive)
 | 
						|
	 *   && len(src->subs_positive) == 0
 | 
						|
	 * (i.e. all receivers for the group opted to exclude this S,G and
 | 
						|
	 * noone did an SSM join for the S,G)
 | 
						|
	 */
 | 
						|
	union {
 | 
						|
		struct {
 | 
						|
			struct gm_packet_sg_subs_head subs_negative[1];
 | 
						|
			struct gm_packet_sg_subs_head subs_positive[1];
 | 
						|
		};
 | 
						|
		struct gm_packet_sg_subs_head subs[2];
 | 
						|
	};
 | 
						|
 | 
						|
	/* If the elected querier is not ourselves, queries and reports might
 | 
						|
	 * get reordered in rare circumstances, i.e. the report could arrive
 | 
						|
	 * just a microsecond before the query kicks off the timer.  This can
 | 
						|
	 * then result in us thinking there are no more receivers since no
 | 
						|
	 * report might be received during the query period.
 | 
						|
	 *
 | 
						|
	 * To avoid this, keep track of the most recent report for this (S,G)
 | 
						|
	 * so we can do a quick check to add just a little bit of slack.
 | 
						|
	 *
 | 
						|
	 * EXCLUDE S,Gs are never in most_recent.
 | 
						|
	 */
 | 
						|
	struct gm_packet_sg *most_recent;
 | 
						|
};
 | 
						|
 | 
						|
/* host tracking entry.  addr will be one of:
 | 
						|
 *
 | 
						|
 * ::		- used by hosts during address acquisition
 | 
						|
 * ::1		- may show up on some OS for joins by the router itself
 | 
						|
 * link-local	- regular operation by MLDv2 hosts
 | 
						|
 * ffff:..:ffff	- MLDv1 entry (cannot be tracked due to report suppression)
 | 
						|
 *
 | 
						|
 * global scope IPv6 addresses can never show up here
 | 
						|
 */
 | 
						|
PREDECL_HASH(gm_subscribers);
 | 
						|
PREDECL_DLIST(gm_packets);
 | 
						|
struct gm_subscriber {
 | 
						|
	pim_addr addr;
 | 
						|
	struct gm_subscribers_item itm;
 | 
						|
 | 
						|
	struct gm_if *iface;
 | 
						|
	size_t refcount;
 | 
						|
 | 
						|
	struct gm_packets_head packets[1];
 | 
						|
 | 
						|
	struct timeval created;
 | 
						|
};
 | 
						|
 | 
						|
/*
 | 
						|
 * MLD join state is kept batched by packet.  Since the timers for all items
 | 
						|
 * in a packet are the same, this reduces the number of timers we're keeping
 | 
						|
 * track of.  It also eases tracking for EXCLUDE state groups because the
 | 
						|
 * excluded sources are in the same packet.  (MLD does not support splitting
 | 
						|
 * that if it exceeds MTU, it's always a full replace for exclude.)
 | 
						|
 *
 | 
						|
 * Since packets may be partially superseded by newer packets, the "active"
 | 
						|
 * field is used to track this.
 | 
						|
 */
 | 
						|
 | 
						|
/* gm_packet_sg is allocated as part of gm_packet_state, note the items[0]
 | 
						|
 * array at the end of that.  gm_packet_sg is NEVER directly allocated with
 | 
						|
 * XMALLOC/XFREE.
 | 
						|
 */
 | 
						|
struct gm_packet_sg {
 | 
						|
	/* non-NULL as long as this gm_packet_sg is the most recent entry
 | 
						|
	 * for (subscriber,S,G).  Cleared to NULL when a newer packet by the
 | 
						|
	 * subscriber replaces this item.
 | 
						|
	 *
 | 
						|
	 * (Old items are kept around so we don't need to realloc/resize
 | 
						|
	 * gm_packet_state, which would mess up a whole lot of pointers)
 | 
						|
	 */
 | 
						|
	struct gm_sg *sg;
 | 
						|
 | 
						|
	/* gm_sg -> (subscriber, gm_packet_sg)
 | 
						|
	 * only on RB-tree while sg != NULL, i.e. not superseded by newer.
 | 
						|
	 */
 | 
						|
	struct gm_packet_sg_subs_item subs_itm;
 | 
						|
 | 
						|
	bool is_src : 1; /* := (src != ::) */
 | 
						|
	bool is_excl : 1;
 | 
						|
 | 
						|
	/* for getting back to struct gm_packet_state, cf.
 | 
						|
	 * gm_packet_sg2state() below
 | 
						|
	 */
 | 
						|
	uint16_t offset;
 | 
						|
 | 
						|
	/* if this is a group entry in EXCLUDE state, n_exclude counts how
 | 
						|
	 * many sources are on the exclude list here.  They follow immediately
 | 
						|
	 * after.
 | 
						|
	 */
 | 
						|
	uint16_t n_exclude;
 | 
						|
};
 | 
						|
 | 
						|
#define gm_packet_sg2state(sg)                                                 \
 | 
						|
	container_of(sg, struct gm_packet_state, items[sg->offset])
 | 
						|
 | 
						|
PREDECL_DLIST(gm_packet_expires);
 | 
						|
struct gm_packet_state {
 | 
						|
	struct gm_if *iface;
 | 
						|
	struct gm_subscriber *subscriber;
 | 
						|
	struct gm_packets_item pkt_itm;
 | 
						|
 | 
						|
	struct timeval received;
 | 
						|
	struct gm_packet_expires_item exp_itm;
 | 
						|
 | 
						|
	/* n_active starts equal to n_sg;  whenever active is set to false on
 | 
						|
	 * an item it is decremented.  When n_active == 0, the packet can be
 | 
						|
	 * freed.
 | 
						|
	 */
 | 
						|
	uint16_t n_sg, n_active;
 | 
						|
	struct gm_packet_sg items[0];
 | 
						|
};
 | 
						|
 | 
						|
/* general queries are rather different from group/S,G specific queries;  it's
 | 
						|
 * not particularly efficient or useful to try to shoehorn them into the S,G
 | 
						|
 * timers.  Instead, we keep a history of recent queries and their implied
 | 
						|
 * expiries.
 | 
						|
 */
 | 
						|
struct gm_general_pending {
 | 
						|
	struct timeval query, expiry;
 | 
						|
};
 | 
						|
 | 
						|
/* similarly, group queries also age out S,G entries for the group, but in
 | 
						|
 * this case we only keep one query for each group
 | 
						|
 *
 | 
						|
 * why is this not in the *,G gm_sg?  There may not be one (for INCLUDE mode
 | 
						|
 * groups, or groups we don't know about.)  Also, malicious clients could spam
 | 
						|
 * random group-specific queries to trigger resource exhaustion, so it makes
 | 
						|
 * sense to limit these.
 | 
						|
 */
 | 
						|
PREDECL_RBTREE_UNIQ(gm_grp_pends);
 | 
						|
struct gm_grp_pending {
 | 
						|
	struct gm_grp_pends_item itm;
 | 
						|
	struct gm_if *iface;
 | 
						|
	pim_addr grp;
 | 
						|
 | 
						|
	struct timeval query;
 | 
						|
	struct thread *t_expire;
 | 
						|
};
 | 
						|
 | 
						|
/* guaranteed MTU for IPv6 is 1280 bytes.  IPv6 header is 40 bytes, MLDv2
 | 
						|
 * query header is 24 bytes, RA option is 8 bytes - leaves 1208 bytes for the
 | 
						|
 * source list, which is 151 IPv6 addresses.  But we may have some more IPv6
 | 
						|
 * extension headers (e.g. IPsec AH), so just cap to 128
 | 
						|
 */
 | 
						|
#define MLD_V2Q_MTU_MAX_SOURCES 128
 | 
						|
 | 
						|
/* group-and-source-specific queries are bundled together, if some host joins
 | 
						|
 * multiple sources it's likely to drop all at the same time.
 | 
						|
 *
 | 
						|
 * Unlike gm_grp_pending, this is only used for aggregation since the S,G
 | 
						|
 * state is kept directly in the gm_sg structure.
 | 
						|
 */
 | 
						|
PREDECL_HASH(gm_gsq_pends);
 | 
						|
struct gm_gsq_pending {
 | 
						|
	struct gm_gsq_pends_item itm;
 | 
						|
 | 
						|
	struct gm_if *iface;
 | 
						|
	struct thread *t_send;
 | 
						|
 | 
						|
	pim_addr grp;
 | 
						|
	bool s_bit;
 | 
						|
 | 
						|
	size_t n_src;
 | 
						|
	pim_addr srcs[MLD_V2Q_MTU_MAX_SOURCES];
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
/* The size of this history is limited by QRV, i.e. there can't be more than
 | 
						|
 * 8 items here.
 | 
						|
 */
 | 
						|
#define GM_MAX_PENDING 8
 | 
						|
 | 
						|
enum gm_version {
 | 
						|
	GM_NONE,
 | 
						|
	GM_MLDV1,
 | 
						|
	GM_MLDV2,
 | 
						|
};
 | 
						|
 | 
						|
struct gm_if_stats {
 | 
						|
	uint64_t rx_drop_csum;
 | 
						|
	uint64_t rx_drop_srcaddr;
 | 
						|
	uint64_t rx_drop_dstaddr;
 | 
						|
	uint64_t rx_drop_ra;
 | 
						|
	uint64_t rx_drop_malformed;
 | 
						|
	uint64_t rx_trunc_report;
 | 
						|
 | 
						|
	/* since the types are different, this is rx_old_* not of rx_*_old */
 | 
						|
	uint64_t rx_old_report;
 | 
						|
	uint64_t rx_old_leave;
 | 
						|
	uint64_t rx_new_report;
 | 
						|
 | 
						|
	uint64_t rx_query_new_general;
 | 
						|
	uint64_t rx_query_new_group;
 | 
						|
	uint64_t rx_query_new_groupsrc;
 | 
						|
	uint64_t rx_query_new_sbit;
 | 
						|
	uint64_t rx_query_old_general;
 | 
						|
	uint64_t rx_query_old_group;
 | 
						|
 | 
						|
	uint64_t tx_query_new_general;
 | 
						|
	uint64_t tx_query_new_group;
 | 
						|
	uint64_t tx_query_new_groupsrc;
 | 
						|
	uint64_t tx_query_old_general;
 | 
						|
	uint64_t tx_query_old_group;
 | 
						|
 | 
						|
	uint64_t tx_query_fail;
 | 
						|
};
 | 
						|
 | 
						|
struct gm_if {
 | 
						|
	struct interface *ifp;
 | 
						|
	struct pim_instance *pim;
 | 
						|
	struct thread *t_query, *t_other_querier, *t_expire;
 | 
						|
 | 
						|
	bool stopping;
 | 
						|
 | 
						|
	uint8_t n_startup;
 | 
						|
 | 
						|
	uint8_t cur_qrv;
 | 
						|
	unsigned int cur_query_intv;	  /* ms */
 | 
						|
	unsigned int cur_query_intv_trig; /* ms */
 | 
						|
	unsigned int cur_max_resp;	  /* ms */
 | 
						|
	enum gm_version cur_version;
 | 
						|
	int cur_lmqc; /* last member query count in ds */
 | 
						|
 | 
						|
	/* this value (positive, default 10ms) defines our "timing tolerance":
 | 
						|
	 * - added to deadlines for expiring joins
 | 
						|
	 * - used to look backwards in time for queries, in case a report was
 | 
						|
	 *   reordered before the query
 | 
						|
	 */
 | 
						|
	struct timeval cfg_timing_fuzz;
 | 
						|
 | 
						|
	/* items in pending[] are sorted by expiry, pending[0] is earliest */
 | 
						|
	struct gm_general_pending pending[GM_MAX_PENDING];
 | 
						|
	uint8_t n_pending;
 | 
						|
	struct gm_grp_pends_head grp_pends[1];
 | 
						|
	struct gm_gsq_pends_head gsq_pends[1];
 | 
						|
 | 
						|
	pim_addr querier;
 | 
						|
	pim_addr cur_ll_lowest;
 | 
						|
 | 
						|
	struct gm_sgs_head sgs[1];
 | 
						|
	struct gm_subscribers_head subscribers[1];
 | 
						|
	struct gm_packet_expires_head expires[1];
 | 
						|
 | 
						|
	struct timeval started;
 | 
						|
	struct gm_if_stats stats;
 | 
						|
};
 | 
						|
 | 
						|
#if PIM_IPV == 6
 | 
						|
extern void gm_ifp_update(struct interface *ifp);
 | 
						|
extern void gm_ifp_teardown(struct interface *ifp);
 | 
						|
extern void gm_group_delete(struct gm_if *gm_ifp);
 | 
						|
#else
 | 
						|
static inline void gm_ifp_update(struct interface *ifp)
 | 
						|
{
 | 
						|
}
 | 
						|
 | 
						|
static inline void gm_ifp_teardown(struct interface *ifp)
 | 
						|
{
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
extern void gm_cli_init(void);
 | 
						|
 | 
						|
#endif /* PIM6_MLD_H */
 |