diff options
Diffstat (limited to 'pimd/pim6_mld.h')
-rw-r--r-- | pimd/pim6_mld.h | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/pimd/pim6_mld.h b/pimd/pim6_mld.h new file mode 100644 index 0000000..540d2e1 --- /dev/null +++ b/pimd/pim6_mld.h @@ -0,0 +1,369 @@ +/* + * 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 */ |