Line data Source code
1 : /*
2 : * PIMv6 MLD querier
3 : * Copyright (C) 2021-2022 David Lamparter for NetDEF, Inc.
4 : *
5 : * This program is free software; you can redistribute it and/or modify it
6 : * under the terms of the GNU General Public License as published by the Free
7 : * Software Foundation; either version 2 of the License, or (at your option)
8 : * any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful, but WITHOUT
11 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 : * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 : * more details.
14 : *
15 : * You should have received a copy of the GNU General Public License along
16 : * with this program; see the file COPYING; if not, write to the Free Software
17 : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 : */
19 :
20 : #ifndef PIM6_MLD_H
21 : #define PIM6_MLD_H
22 :
23 : #include "typesafe.h"
24 : #include "pim_addr.h"
25 :
26 : struct thread;
27 : struct pim_instance;
28 : struct gm_packet_sg;
29 : struct gm_if;
30 : struct channel_oil;
31 :
32 : #define MLD_DEFAULT_VERSION 2
33 :
34 : /* see comment below on subs_negative/subs_positive */
35 : enum gm_sub_sense {
36 : /* negative/pruning: S,G in EXCLUDE */
37 : GM_SUB_NEG = 0,
38 : /* positive/joining: *,G in EXCLUDE and S,G in INCLUDE */
39 : GM_SUB_POS = 1,
40 : };
41 :
42 : enum gm_sg_state {
43 : GM_SG_NOINFO = 0,
44 : GM_SG_JOIN,
45 : GM_SG_JOIN_EXPIRING,
46 : /* remaining 3 only valid for S,G when *,G in EXCLUDE */
47 : GM_SG_PRUNE,
48 : GM_SG_NOPRUNE,
49 : GM_SG_NOPRUNE_EXPIRING,
50 : };
51 :
52 2 : static inline bool gm_sg_state_want_join(enum gm_sg_state state)
53 : {
54 2 : return state != GM_SG_NOINFO && state != GM_SG_PRUNE;
55 : }
56 :
57 : /* MLD (S,G) state (on an interface)
58 : *
59 : * group is always != ::, src is :: for (*,G) joins. sort order in RB tree is
60 : * such that sources for a particular group can be iterated by starting at the
61 : * group. For INCLUDE, no (*,G) entry exists, only (S,G).
62 : */
63 :
64 : PREDECL_RBTREE_UNIQ(gm_packet_sg_subs);
65 : PREDECL_RBTREE_UNIQ(gm_sgs);
66 : struct gm_sg {
67 : pim_sgaddr sgaddr;
68 : struct gm_if *iface;
69 : struct gm_sgs_item itm;
70 :
71 : enum gm_sg_state state;
72 : struct channel_oil *oil;
73 : bool tib_joined;
74 :
75 : struct timeval created;
76 :
77 : /* if a group- or group-and-source specific query is running
78 : * (implies we haven't received any report yet, since it's cancelled
79 : * by that)
80 : */
81 : struct thread *t_sg_expire;
82 :
83 : /* last-member-left triggered queries (group/group-source specific)
84 : *
85 : * this timer will be running even if we aren't the elected querier,
86 : * in case the election result changes midway through.
87 : */
88 : struct thread *t_sg_query;
89 :
90 : /* we must keep sending (QRV) queries even if we get a positive
91 : * response, to make sure other routers are updated. query_sbit
92 : * will be set in that case, since other routers need the *response*,
93 : * not the *query*
94 : */
95 : uint8_t n_query;
96 : bool query_sbit;
97 :
98 : /* subs_positive tracks gm_packet_sg resulting in a JOIN, i.e. for
99 : * (*,G) it has *EXCLUDE* items, for (S,G) it has *INCLUDE* items.
100 : *
101 : * subs_negative is always empty for (*,G) and tracks EXCLUDE items
102 : * for (S,G). This means that an (S,G) entry is active as a PRUNE if
103 : * len(src->subs_negative) == len(grp->subs_positive)
104 : * && len(src->subs_positive) == 0
105 : * (i.e. all receivers for the group opted to exclude this S,G and
106 : * noone did an SSM join for the S,G)
107 : */
108 : union {
109 : struct {
110 : struct gm_packet_sg_subs_head subs_negative[1];
111 : struct gm_packet_sg_subs_head subs_positive[1];
112 : };
113 : struct gm_packet_sg_subs_head subs[2];
114 : };
115 :
116 : /* If the elected querier is not ourselves, queries and reports might
117 : * get reordered in rare circumstances, i.e. the report could arrive
118 : * just a microsecond before the query kicks off the timer. This can
119 : * then result in us thinking there are no more receivers since no
120 : * report might be received during the query period.
121 : *
122 : * To avoid this, keep track of the most recent report for this (S,G)
123 : * so we can do a quick check to add just a little bit of slack.
124 : *
125 : * EXCLUDE S,Gs are never in most_recent.
126 : */
127 : struct gm_packet_sg *most_recent;
128 : };
129 :
130 : /* host tracking entry. addr will be one of:
131 : *
132 : * :: - used by hosts during address acquisition
133 : * ::1 - may show up on some OS for joins by the router itself
134 : * link-local - regular operation by MLDv2 hosts
135 : * ffff:..:ffff - MLDv1 entry (cannot be tracked due to report suppression)
136 : *
137 : * global scope IPv6 addresses can never show up here
138 : */
139 : PREDECL_HASH(gm_subscribers);
140 : PREDECL_DLIST(gm_packets);
141 : struct gm_subscriber {
142 : pim_addr addr;
143 : struct gm_subscribers_item itm;
144 :
145 : struct gm_if *iface;
146 : size_t refcount;
147 :
148 : struct gm_packets_head packets[1];
149 :
150 : struct timeval created;
151 : };
152 :
153 : /*
154 : * MLD join state is kept batched by packet. Since the timers for all items
155 : * in a packet are the same, this reduces the number of timers we're keeping
156 : * track of. It also eases tracking for EXCLUDE state groups because the
157 : * excluded sources are in the same packet. (MLD does not support splitting
158 : * that if it exceeds MTU, it's always a full replace for exclude.)
159 : *
160 : * Since packets may be partially superseded by newer packets, the "active"
161 : * field is used to track this.
162 : */
163 :
164 : /* gm_packet_sg is allocated as part of gm_packet_state, note the items[0]
165 : * array at the end of that. gm_packet_sg is NEVER directly allocated with
166 : * XMALLOC/XFREE.
167 : */
168 : struct gm_packet_sg {
169 : /* non-NULL as long as this gm_packet_sg is the most recent entry
170 : * for (subscriber,S,G). Cleared to NULL when a newer packet by the
171 : * subscriber replaces this item.
172 : *
173 : * (Old items are kept around so we don't need to realloc/resize
174 : * gm_packet_state, which would mess up a whole lot of pointers)
175 : */
176 : struct gm_sg *sg;
177 :
178 : /* gm_sg -> (subscriber, gm_packet_sg)
179 : * only on RB-tree while sg != NULL, i.e. not superseded by newer.
180 : */
181 : struct gm_packet_sg_subs_item subs_itm;
182 :
183 : bool is_src : 1; /* := (src != ::) */
184 : bool is_excl : 1;
185 :
186 : /* for getting back to struct gm_packet_state, cf.
187 : * gm_packet_sg2state() below
188 : */
189 : uint16_t offset;
190 :
191 : /* if this is a group entry in EXCLUDE state, n_exclude counts how
192 : * many sources are on the exclude list here. They follow immediately
193 : * after.
194 : */
195 : uint16_t n_exclude;
196 : };
197 :
198 : #define gm_packet_sg2state(sg) \
199 : container_of(sg, struct gm_packet_state, items[sg->offset])
200 :
201 : PREDECL_DLIST(gm_packet_expires);
202 : struct gm_packet_state {
203 : struct gm_if *iface;
204 : struct gm_subscriber *subscriber;
205 : struct gm_packets_item pkt_itm;
206 :
207 : struct timeval received;
208 : struct gm_packet_expires_item exp_itm;
209 :
210 : /* n_active starts equal to n_sg; whenever active is set to false on
211 : * an item it is decremented. When n_active == 0, the packet can be
212 : * freed.
213 : */
214 : uint16_t n_sg, n_active;
215 : struct gm_packet_sg items[0];
216 : };
217 :
218 : /* general queries are rather different from group/S,G specific queries; it's
219 : * not particularly efficient or useful to try to shoehorn them into the S,G
220 : * timers. Instead, we keep a history of recent queries and their implied
221 : * expiries.
222 : */
223 : struct gm_general_pending {
224 : struct timeval query, expiry;
225 : };
226 :
227 : /* similarly, group queries also age out S,G entries for the group, but in
228 : * this case we only keep one query for each group
229 : *
230 : * why is this not in the *,G gm_sg? There may not be one (for INCLUDE mode
231 : * groups, or groups we don't know about.) Also, malicious clients could spam
232 : * random group-specific queries to trigger resource exhaustion, so it makes
233 : * sense to limit these.
234 : */
235 : PREDECL_RBTREE_UNIQ(gm_grp_pends);
236 : struct gm_grp_pending {
237 : struct gm_grp_pends_item itm;
238 : struct gm_if *iface;
239 : pim_addr grp;
240 :
241 : struct timeval query;
242 : struct thread *t_expire;
243 : };
244 :
245 : /* guaranteed MTU for IPv6 is 1280 bytes. IPv6 header is 40 bytes, MLDv2
246 : * query header is 24 bytes, RA option is 8 bytes - leaves 1208 bytes for the
247 : * source list, which is 151 IPv6 addresses. But we may have some more IPv6
248 : * extension headers (e.g. IPsec AH), so just cap to 128
249 : */
250 : #define MLD_V2Q_MTU_MAX_SOURCES 128
251 :
252 : /* group-and-source-specific queries are bundled together, if some host joins
253 : * multiple sources it's likely to drop all at the same time.
254 : *
255 : * Unlike gm_grp_pending, this is only used for aggregation since the S,G
256 : * state is kept directly in the gm_sg structure.
257 : */
258 : PREDECL_HASH(gm_gsq_pends);
259 : struct gm_gsq_pending {
260 : struct gm_gsq_pends_item itm;
261 :
262 : struct gm_if *iface;
263 : struct thread *t_send;
264 :
265 : pim_addr grp;
266 : bool s_bit;
267 :
268 : size_t n_src;
269 : pim_addr srcs[MLD_V2Q_MTU_MAX_SOURCES];
270 : };
271 :
272 :
273 : /* The size of this history is limited by QRV, i.e. there can't be more than
274 : * 8 items here.
275 : */
276 : #define GM_MAX_PENDING 8
277 :
278 : enum gm_version {
279 : GM_NONE,
280 : GM_MLDV1,
281 : GM_MLDV2,
282 : };
283 :
284 : struct gm_if_stats {
285 : uint64_t rx_drop_csum;
286 : uint64_t rx_drop_srcaddr;
287 : uint64_t rx_drop_dstaddr;
288 : uint64_t rx_drop_ra;
289 : uint64_t rx_drop_malformed;
290 : uint64_t rx_trunc_report;
291 :
292 : /* since the types are different, this is rx_old_* not of rx_*_old */
293 : uint64_t rx_old_report;
294 : uint64_t rx_old_leave;
295 : uint64_t rx_new_report;
296 :
297 : uint64_t rx_query_new_general;
298 : uint64_t rx_query_new_group;
299 : uint64_t rx_query_new_groupsrc;
300 : uint64_t rx_query_new_sbit;
301 : uint64_t rx_query_old_general;
302 : uint64_t rx_query_old_group;
303 :
304 : uint64_t tx_query_new_general;
305 : uint64_t tx_query_new_group;
306 : uint64_t tx_query_new_groupsrc;
307 : uint64_t tx_query_old_general;
308 : uint64_t tx_query_old_group;
309 :
310 : uint64_t tx_query_fail;
311 : };
312 :
313 : struct gm_if {
314 : struct interface *ifp;
315 : struct pim_instance *pim;
316 : struct thread *t_query, *t_other_querier, *t_expire;
317 :
318 : bool stopping;
319 :
320 : uint8_t n_startup;
321 :
322 : uint8_t cur_qrv;
323 : unsigned int cur_query_intv; /* ms */
324 : unsigned int cur_query_intv_trig; /* ms */
325 : unsigned int cur_max_resp; /* ms */
326 : enum gm_version cur_version;
327 : int cur_lmqc; /* last member query count in ds */
328 :
329 : /* this value (positive, default 10ms) defines our "timing tolerance":
330 : * - added to deadlines for expiring joins
331 : * - used to look backwards in time for queries, in case a report was
332 : * reordered before the query
333 : */
334 : struct timeval cfg_timing_fuzz;
335 :
336 : /* items in pending[] are sorted by expiry, pending[0] is earliest */
337 : struct gm_general_pending pending[GM_MAX_PENDING];
338 : uint8_t n_pending;
339 : struct gm_grp_pends_head grp_pends[1];
340 : struct gm_gsq_pends_head gsq_pends[1];
341 :
342 : pim_addr querier;
343 : pim_addr cur_ll_lowest;
344 :
345 : struct gm_sgs_head sgs[1];
346 : struct gm_subscribers_head subscribers[1];
347 : struct gm_packet_expires_head expires[1];
348 :
349 : struct timeval started;
350 : struct gm_if_stats stats;
351 : };
352 :
353 : #if PIM_IPV == 6
354 : extern void gm_ifp_update(struct interface *ifp);
355 : extern void gm_ifp_teardown(struct interface *ifp);
356 : extern void gm_group_delete(struct gm_if *gm_ifp);
357 : #else
358 : static inline void gm_ifp_update(struct interface *ifp)
359 : {
360 : }
361 :
362 : static inline void gm_ifp_teardown(struct interface *ifp)
363 : {
364 : }
365 : #endif
366 :
367 : extern void gm_cli_init(void);
368 :
369 : #endif /* PIM6_MLD_H */
|