forked from Mirror/frr
Merge pull request #10775 from opensourcerouting/pim6-mld-pr
pim6d: MLD code
This commit is contained in:
commit
8a8ad459af
|
@ -218,6 +218,9 @@ vrf is specified then the default vrf is assumed. Finally the special keyword
|
||||||
'all' allows you to look at all vrfs for the command. Naming a vrf 'all' will
|
'all' allows you to look at all vrfs for the command. Naming a vrf 'all' will
|
||||||
cause great confusion.
|
cause great confusion.
|
||||||
|
|
||||||
|
PIM protocol state
|
||||||
|
------------------
|
||||||
|
|
||||||
.. clicmd:: show ipv6 pim [vrf NAME] group-type [json]
|
.. clicmd:: show ipv6 pim [vrf NAME] group-type [json]
|
||||||
|
|
||||||
Display SSM group ranges.
|
Display SSM group ranges.
|
||||||
|
@ -289,6 +292,39 @@ cause great confusion.
|
||||||
|
|
||||||
Display upstream information for S,G's and the RPF data associated with them.
|
Display upstream information for S,G's and the RPF data associated with them.
|
||||||
|
|
||||||
|
|
||||||
|
MLD state
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. clicmd:: show ipv6 mld [vrf NAME] interface [IFNAME] [detail|json]
|
||||||
|
|
||||||
|
Display per-interface MLD state, elected querier and related timers. Use
|
||||||
|
the ``detail`` or ``json`` options for further information (the JSON output
|
||||||
|
always contains all details.)
|
||||||
|
|
||||||
|
.. clicmd:: show ipv6 mld [vrf NAME] statistics [interface IFNAME] [json]
|
||||||
|
|
||||||
|
Display packet and error counters for MLD interfaces. All counters are
|
||||||
|
packet counters (not bytes) and wrap at 64 bit. In some rare cases,
|
||||||
|
malformed received MLD reports may be partially processed and counted on
|
||||||
|
multiple counters.
|
||||||
|
|
||||||
|
.. clicmd:: show ipv6 mld [vrf NAME] joins [{interface IFNAME|groups X:X::X:X/M|sources X:X::X:X/M|detail}] [json]
|
||||||
|
|
||||||
|
Display joined groups tracked by MLD. ``interface``, ``groups`` and
|
||||||
|
``sources`` options may be used to limit output to a subset (note ``sources``
|
||||||
|
refers to the multicast traffic sender, not the host that joined to receive
|
||||||
|
the traffic.)
|
||||||
|
|
||||||
|
The ``detail`` option also reports which hosts have joined (subscribed) to
|
||||||
|
particular ``S,G``. This information is only available for MLDv2 hosts with
|
||||||
|
a MLDv2 querier. MLDv1 joins are recorded as "untracked" and shown in the
|
||||||
|
``NonTrkSeen`` output column.
|
||||||
|
|
||||||
|
|
||||||
|
General multicast routing state
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
.. clicmd:: show ipv6 multicast
|
.. clicmd:: show ipv6 multicast
|
||||||
|
|
||||||
Display various information about the interfaces used in this pim instance.
|
Display various information about the interfaces used in this pim instance.
|
||||||
|
|
|
@ -707,7 +707,7 @@ DEFPY (interface_ipv6_mld_query_max_response_time,
|
||||||
IPV6_STR
|
IPV6_STR
|
||||||
IFACE_MLD_STR
|
IFACE_MLD_STR
|
||||||
IFACE_MLD_QUERY_MAX_RESPONSE_TIME_STR
|
IFACE_MLD_QUERY_MAX_RESPONSE_TIME_STR
|
||||||
"Query response value in deci-seconds\n")
|
"Query response value in milliseconds\n")
|
||||||
{
|
{
|
||||||
return gm_process_query_max_response_time_cmd(vty, qmrt_str);
|
return gm_process_query_max_response_time_cmd(vty, qmrt_str);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "pim_zebra.h"
|
#include "pim_zebra.h"
|
||||||
#include "pim_nb.h"
|
#include "pim_nb.h"
|
||||||
#include "pim6_cmd.h"
|
#include "pim6_cmd.h"
|
||||||
|
#include "pim6_mld.h"
|
||||||
|
|
||||||
zebra_capabilities_t _caps_p[] = {
|
zebra_capabilities_t _caps_p[] = {
|
||||||
ZCAP_SYS_ADMIN,
|
ZCAP_SYS_ADMIN,
|
||||||
|
@ -133,7 +134,6 @@ FRR_DAEMON_INFO(pim6d, PIM6,
|
||||||
);
|
);
|
||||||
/* clang-format on */
|
/* clang-format on */
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv, char **envp)
|
int main(int argc, char **argv, char **envp)
|
||||||
{
|
{
|
||||||
static struct option longopts[] = {
|
static struct option longopts[] = {
|
||||||
|
@ -184,6 +184,8 @@ int main(int argc, char **argv, char **envp)
|
||||||
*/
|
*/
|
||||||
pim_iface_init();
|
pim_iface_init();
|
||||||
|
|
||||||
|
gm_cli_init();
|
||||||
|
|
||||||
pim_zebra_init();
|
pim_zebra_init();
|
||||||
#if 0
|
#if 0
|
||||||
pim_bfd_init();
|
pim_bfd_init();
|
||||||
|
|
3011
pimd/pim6_mld.c
Normal file
3011
pimd/pim6_mld.c
Normal file
File diff suppressed because it is too large
Load diff
367
pimd/pim6_mld.h
Normal file
367
pimd/pim6_mld.h
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
#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 */
|
125
pimd/pim6_mld_protocol.h
Normal file
125
pimd/pim6_mld_protocol.h
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* MLD protocol definitions
|
||||||
|
* Copyright (C) 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_PROTOCOL_H
|
||||||
|
#define _PIM6_MLD_PROTOCOL_H
|
||||||
|
|
||||||
|
#include <stdalign.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* There is a struct icmp6_hdr provided by OS, but it includes 4 bytes of data.
|
||||||
|
* Not helpful for us if we want to put the MLD struct after it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct icmp6_plain_hdr {
|
||||||
|
uint8_t icmp6_type;
|
||||||
|
uint8_t icmp6_code;
|
||||||
|
uint16_t icmp6_cksum;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct icmp6_plain_hdr) == 4, "struct mismatch");
|
||||||
|
static_assert(alignof(struct icmp6_plain_hdr) <= 4, "struct mismatch");
|
||||||
|
|
||||||
|
/* for MLDv1 query, report and leave all use the same packet format */
|
||||||
|
struct mld_v1_pkt {
|
||||||
|
uint16_t max_resp_code;
|
||||||
|
uint16_t rsvd0;
|
||||||
|
struct in6_addr grp;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct mld_v1_pkt) == 20, "struct mismatch");
|
||||||
|
static_assert(alignof(struct mld_v1_pkt) <= 4, "struct mismatch");
|
||||||
|
|
||||||
|
|
||||||
|
struct mld_v2_query_hdr {
|
||||||
|
uint16_t max_resp_code;
|
||||||
|
uint16_t rsvd0;
|
||||||
|
struct in6_addr grp;
|
||||||
|
uint8_t flags;
|
||||||
|
uint8_t qqic;
|
||||||
|
uint16_t n_src;
|
||||||
|
struct in6_addr srcs[0];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct mld_v2_query_hdr) == 24, "struct mismatch");
|
||||||
|
static_assert(alignof(struct mld_v2_query_hdr) <= 4, "struct mismatch");
|
||||||
|
|
||||||
|
|
||||||
|
struct mld_v2_report_hdr {
|
||||||
|
uint16_t rsvd;
|
||||||
|
uint16_t n_records;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct mld_v2_report_hdr) == 4, "struct mismatch");
|
||||||
|
static_assert(alignof(struct mld_v2_report_hdr) <= 4, "struct mismatch");
|
||||||
|
|
||||||
|
|
||||||
|
struct mld_v2_rec_hdr {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t aux_len;
|
||||||
|
uint16_t n_src;
|
||||||
|
struct in6_addr grp;
|
||||||
|
struct in6_addr srcs[0];
|
||||||
|
};
|
||||||
|
static_assert(sizeof(struct mld_v2_rec_hdr) == 20, "struct mismatch");
|
||||||
|
static_assert(alignof(struct mld_v2_rec_hdr) <= 4, "struct mismatch");
|
||||||
|
|
||||||
|
/* clang-format off */
|
||||||
|
enum icmp6_mld_type {
|
||||||
|
ICMP6_MLD_QUERY = 130,
|
||||||
|
ICMP6_MLD_V1_REPORT = 131,
|
||||||
|
ICMP6_MLD_V1_DONE = 132,
|
||||||
|
ICMP6_MLD_V2_REPORT = 143,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum mld_v2_rec_type {
|
||||||
|
MLD_RECTYPE_IS_INCLUDE = 1,
|
||||||
|
MLD_RECTYPE_IS_EXCLUDE = 2,
|
||||||
|
MLD_RECTYPE_CHANGE_TO_INCLUDE = 3,
|
||||||
|
MLD_RECTYPE_CHANGE_TO_EXCLUDE = 4,
|
||||||
|
MLD_RECTYPE_ALLOW_NEW_SOURCES = 5,
|
||||||
|
MLD_RECTYPE_BLOCK_OLD_SOURCES = 6,
|
||||||
|
};
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
/* helper functions */
|
||||||
|
|
||||||
|
static inline unsigned int mld_max_resp_decode(uint16_t wire)
|
||||||
|
{
|
||||||
|
uint16_t code = ntohs(wire);
|
||||||
|
uint8_t exp;
|
||||||
|
|
||||||
|
if (code < 0x8000)
|
||||||
|
return code;
|
||||||
|
exp = (code >> 12) & 0x7;
|
||||||
|
return ((code & 0xfff) | 0x1000) << (exp + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t mld_max_resp_encode(uint32_t value)
|
||||||
|
{
|
||||||
|
uint16_t code;
|
||||||
|
uint8_t exp;
|
||||||
|
|
||||||
|
if (value < 0x8000)
|
||||||
|
code = value;
|
||||||
|
else {
|
||||||
|
exp = 16 - __builtin_clz(value);
|
||||||
|
code = (value >> (exp + 3)) & 0xfff;
|
||||||
|
code |= 0x8000 | (exp << 12);
|
||||||
|
}
|
||||||
|
return htons(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _PIM6_MLD_PROTOCOL_H */
|
|
@ -50,6 +50,8 @@
|
||||||
#include "pim_igmp_join.h"
|
#include "pim_igmp_join.h"
|
||||||
#include "pim_vxlan.h"
|
#include "pim_vxlan.h"
|
||||||
|
|
||||||
|
#include "pim6_mld.h"
|
||||||
|
|
||||||
#if PIM_IPV == 4
|
#if PIM_IPV == 4
|
||||||
static void pim_if_igmp_join_del_all(struct interface *ifp);
|
static void pim_if_igmp_join_del_all(struct interface *ifp);
|
||||||
static int igmp_join_sock(const char *ifname, ifindex_t ifindex,
|
static int igmp_join_sock(const char *ifname, ifindex_t ifindex,
|
||||||
|
@ -127,6 +129,7 @@ struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim,
|
||||||
pim_ifp->mroute_vif_index = -1;
|
pim_ifp->mroute_vif_index = -1;
|
||||||
|
|
||||||
pim_ifp->igmp_version = IGMP_DEFAULT_VERSION;
|
pim_ifp->igmp_version = IGMP_DEFAULT_VERSION;
|
||||||
|
pim_ifp->mld_version = MLD_DEFAULT_VERSION;
|
||||||
pim_ifp->gm_default_robustness_variable =
|
pim_ifp->gm_default_robustness_variable =
|
||||||
IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
|
IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
|
||||||
pim_ifp->gm_default_query_interval = IGMP_GENERAL_QUERY_INTERVAL;
|
pim_ifp->gm_default_query_interval = IGMP_GENERAL_QUERY_INTERVAL;
|
||||||
|
@ -651,6 +654,7 @@ void pim_if_addr_add(struct connected *ifc)
|
||||||
vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
|
vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
|
||||||
pim_if_add_vif(ifp, false, vxlan_term);
|
pim_if_add_vif(ifp, false, vxlan_term);
|
||||||
}
|
}
|
||||||
|
gm_ifp_update(ifp);
|
||||||
pim_ifchannel_scan_forward_start(ifp);
|
pim_ifchannel_scan_forward_start(ifp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -763,6 +767,8 @@ void pim_if_addr_del(struct connected *ifc, int force_prim_as_any)
|
||||||
"%s: removed link-local %pI6, lowest now %pI6, highest %pI6",
|
"%s: removed link-local %pI6, lowest now %pI6, highest %pI6",
|
||||||
ifc->ifp->name, &ifc->address->u.prefix6,
|
ifc->ifp->name, &ifc->address->u.prefix6,
|
||||||
&pim_ifp->ll_lowest, &pim_ifp->ll_highest);
|
&pim_ifp->ll_lowest, &pim_ifp->ll_highest);
|
||||||
|
|
||||||
|
gm_ifp_update(ifp);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -822,6 +828,7 @@ void pim_if_addr_add_all(struct interface *ifp)
|
||||||
vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
|
vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
|
||||||
pim_if_add_vif(ifp, false, vxlan_term);
|
pim_if_add_vif(ifp, false, vxlan_term);
|
||||||
}
|
}
|
||||||
|
gm_ifp_update(ifp);
|
||||||
pim_ifchannel_scan_forward_start(ifp);
|
pim_ifchannel_scan_forward_start(ifp);
|
||||||
|
|
||||||
pim_rp_setup(pim_ifp->pim);
|
pim_rp_setup(pim_ifp->pim);
|
||||||
|
@ -1000,12 +1007,15 @@ int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term)
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaddr = pim_ifp->primary_address;
|
ifaddr = pim_ifp->primary_address;
|
||||||
|
#if PIM_IPV != 6
|
||||||
|
/* IPv6 API is always by interface index */
|
||||||
if (!ispimreg && !is_vxlan_term && pim_addr_is_any(ifaddr)) {
|
if (!ispimreg && !is_vxlan_term && pim_addr_is_any(ifaddr)) {
|
||||||
zlog_warn(
|
zlog_warn(
|
||||||
"%s: could not get address for interface %s ifindex=%d",
|
"%s: could not get address for interface %s ifindex=%d",
|
||||||
__func__, ifp->name, ifp->ifindex);
|
__func__, ifp->name, ifp->ifindex);
|
||||||
return -4;
|
return -4;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
pim_ifp->mroute_vif_index = pim_iface_next_vif_index(ifp);
|
pim_ifp->mroute_vif_index = pim_iface_next_vif_index(ifp);
|
||||||
|
|
||||||
|
@ -1030,9 +1040,10 @@ int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term)
|
||||||
|
|
||||||
pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 1;
|
pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 1;
|
||||||
|
|
||||||
|
gm_ifp_update(ifp);
|
||||||
|
|
||||||
/* if the device qualifies as pim_vxlan iif/oif update vxlan entries */
|
/* if the device qualifies as pim_vxlan iif/oif update vxlan entries */
|
||||||
pim_vxlan_add_vif(ifp);
|
pim_vxlan_add_vif(ifp);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1050,6 +1061,8 @@ int pim_if_del_vif(struct interface *ifp)
|
||||||
/* if the device was a pim_vxlan iif/oif update vxlan mroute entries */
|
/* if the device was a pim_vxlan iif/oif update vxlan mroute entries */
|
||||||
pim_vxlan_del_vif(ifp);
|
pim_vxlan_del_vif(ifp);
|
||||||
|
|
||||||
|
gm_ifp_teardown(ifp);
|
||||||
|
|
||||||
pim_mroute_del_vif(ifp);
|
pim_mroute_del_vif(ifp);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1058,7 +1071,6 @@ int pim_if_del_vif(struct interface *ifp)
|
||||||
pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 0;
|
pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 0;
|
||||||
|
|
||||||
pim_ifp->mroute_vif_index = -1;
|
pim_ifp->mroute_vif_index = -1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,8 @@ struct pim_secondary_addr {
|
||||||
enum pim_secondary_addr_flags flags;
|
enum pim_secondary_addr_flags flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct gm_if;
|
||||||
|
|
||||||
struct pim_interface {
|
struct pim_interface {
|
||||||
bool pim_enable : 1;
|
bool pim_enable : 1;
|
||||||
bool pim_can_disable_join_suppression : 1;
|
bool pim_can_disable_join_suppression : 1;
|
||||||
|
@ -90,6 +92,7 @@ struct pim_interface {
|
||||||
* address of the interface */
|
* address of the interface */
|
||||||
|
|
||||||
int igmp_version; /* IGMP version */
|
int igmp_version; /* IGMP version */
|
||||||
|
int mld_version;
|
||||||
int gm_default_robustness_variable; /* IGMP or MLD QRV */
|
int gm_default_robustness_variable; /* IGMP or MLD QRV */
|
||||||
int gm_default_query_interval; /* IGMP or MLD secs between general
|
int gm_default_query_interval; /* IGMP or MLD secs between general
|
||||||
queries */
|
queries */
|
||||||
|
@ -111,6 +114,8 @@ struct pim_interface {
|
||||||
struct list *gm_group_list; /* list of struct IGMP or MLD group */
|
struct list *gm_group_list; /* list of struct IGMP or MLD group */
|
||||||
struct hash *gm_group_hash;
|
struct hash *gm_group_hash;
|
||||||
|
|
||||||
|
struct gm_if *mld;
|
||||||
|
|
||||||
int pim_sock_fd; /* PIM socket file descriptor */
|
int pim_sock_fd; /* PIM socket file descriptor */
|
||||||
struct thread *t_pim_sock_read; /* thread for reading PIM socket */
|
struct thread *t_pim_sock_read; /* thread for reading PIM socket */
|
||||||
int64_t pim_sock_creation; /* timestamp of PIM socket creation */
|
int64_t pim_sock_creation; /* timestamp of PIM socket creation */
|
||||||
|
|
|
@ -115,6 +115,8 @@ static struct pim_instance *pim_instance_init(struct vrf *vrf)
|
||||||
|
|
||||||
pim->send_v6_secondary = 1;
|
pim->send_v6_secondary = 1;
|
||||||
|
|
||||||
|
pim->gm_socket = -1;
|
||||||
|
|
||||||
pim_rp_init(pim);
|
pim_rp_init(pim);
|
||||||
|
|
||||||
pim_bsm_proc_init(pim);
|
pim_bsm_proc_init(pim);
|
||||||
|
|
|
@ -167,6 +167,10 @@ struct pim_instance {
|
||||||
struct list *ssmpingd_list;
|
struct list *ssmpingd_list;
|
||||||
pim_addr ssmpingd_group_addr;
|
pim_addr ssmpingd_group_addr;
|
||||||
|
|
||||||
|
unsigned int gm_socket_if_count;
|
||||||
|
int gm_socket;
|
||||||
|
struct thread *t_gm_recv;
|
||||||
|
|
||||||
unsigned int igmp_group_count;
|
unsigned int igmp_group_count;
|
||||||
unsigned int igmp_watermark_limit;
|
unsigned int igmp_watermark_limit;
|
||||||
unsigned int keep_alive_time;
|
unsigned int keep_alive_time;
|
||||||
|
@ -194,6 +198,8 @@ struct pim_instance {
|
||||||
int64_t nexthop_lookups;
|
int64_t nexthop_lookups;
|
||||||
int64_t nexthop_lookups_avoided;
|
int64_t nexthop_lookups_avoided;
|
||||||
int64_t last_route_change_time;
|
int64_t last_route_change_time;
|
||||||
|
|
||||||
|
uint64_t gm_rx_drop_sys;
|
||||||
};
|
};
|
||||||
|
|
||||||
void pim_vrf_init(void);
|
void pim_vrf_init(void);
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "lib_errors.h"
|
#include "lib_errors.h"
|
||||||
#include "pim_util.h"
|
#include "pim_util.h"
|
||||||
|
#include "pim6_mld.h"
|
||||||
|
|
||||||
#if PIM_IPV == 6
|
#if PIM_IPV == 6
|
||||||
#define pim6_msdp_err(funcname, argtype) \
|
#define pim6_msdp_err(funcname, argtype) \
|
||||||
|
@ -2702,12 +2703,22 @@ int lib_interface_gmp_address_family_igmp_version_destroy(
|
||||||
int lib_interface_gmp_address_family_mld_version_modify(
|
int lib_interface_gmp_address_family_mld_version_modify(
|
||||||
struct nb_cb_modify_args *args)
|
struct nb_cb_modify_args *args)
|
||||||
{
|
{
|
||||||
|
struct interface *ifp;
|
||||||
|
struct pim_interface *pim_ifp;
|
||||||
|
|
||||||
switch (args->event) {
|
switch (args->event) {
|
||||||
case NB_EV_VALIDATE:
|
case NB_EV_VALIDATE:
|
||||||
case NB_EV_PREPARE:
|
case NB_EV_PREPARE:
|
||||||
case NB_EV_ABORT:
|
case NB_EV_ABORT:
|
||||||
|
break;
|
||||||
case NB_EV_APPLY:
|
case NB_EV_APPLY:
|
||||||
/* TBD depends on MLD data structure changes */
|
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
||||||
|
pim_ifp = ifp->info;
|
||||||
|
if (!pim_ifp)
|
||||||
|
return NB_ERR_INCONSISTENCY;
|
||||||
|
|
||||||
|
pim_ifp->mld_version = yang_dnode_get_uint8(args->dnode, NULL);
|
||||||
|
gm_ifp_update(ifp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2717,11 +2728,22 @@ int lib_interface_gmp_address_family_mld_version_modify(
|
||||||
int lib_interface_gmp_address_family_mld_version_destroy(
|
int lib_interface_gmp_address_family_mld_version_destroy(
|
||||||
struct nb_cb_destroy_args *args)
|
struct nb_cb_destroy_args *args)
|
||||||
{
|
{
|
||||||
|
struct interface *ifp;
|
||||||
|
struct pim_interface *pim_ifp;
|
||||||
|
|
||||||
switch (args->event) {
|
switch (args->event) {
|
||||||
case NB_EV_VALIDATE:
|
case NB_EV_VALIDATE:
|
||||||
case NB_EV_PREPARE:
|
case NB_EV_PREPARE:
|
||||||
case NB_EV_ABORT:
|
case NB_EV_ABORT:
|
||||||
|
break;
|
||||||
case NB_EV_APPLY:
|
case NB_EV_APPLY:
|
||||||
|
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
||||||
|
pim_ifp = ifp->info;
|
||||||
|
if (!pim_ifp)
|
||||||
|
return NB_ERR_INCONSISTENCY;
|
||||||
|
|
||||||
|
pim_ifp->mld_version = 2;
|
||||||
|
gm_ifp_update(ifp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2734,10 +2756,10 @@ int lib_interface_gmp_address_family_mld_version_destroy(
|
||||||
int lib_interface_gmp_address_family_query_interval_modify(
|
int lib_interface_gmp_address_family_query_interval_modify(
|
||||||
struct nb_cb_modify_args *args)
|
struct nb_cb_modify_args *args)
|
||||||
{
|
{
|
||||||
#if PIM_IPV == 4
|
|
||||||
struct interface *ifp;
|
struct interface *ifp;
|
||||||
int query_interval;
|
int query_interval;
|
||||||
|
|
||||||
|
#if PIM_IPV == 4
|
||||||
switch (args->event) {
|
switch (args->event) {
|
||||||
case NB_EV_VALIDATE:
|
case NB_EV_VALIDATE:
|
||||||
case NB_EV_PREPARE:
|
case NB_EV_PREPARE:
|
||||||
|
@ -2749,7 +2771,23 @@ int lib_interface_gmp_address_family_query_interval_modify(
|
||||||
change_query_interval(ifp->info, query_interval);
|
change_query_interval(ifp->info, query_interval);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
/* TBD Depends on MLD data structure changes */
|
struct pim_interface *pim_ifp;
|
||||||
|
|
||||||
|
switch (args->event) {
|
||||||
|
case NB_EV_VALIDATE:
|
||||||
|
case NB_EV_PREPARE:
|
||||||
|
case NB_EV_ABORT:
|
||||||
|
break;
|
||||||
|
case NB_EV_APPLY:
|
||||||
|
ifp = nb_running_get_entry(args->dnode, NULL, true);
|
||||||
|
pim_ifp = ifp->info;
|
||||||
|
if (!pim_ifp)
|
||||||
|
return NB_ERR_INCONSISTENCY;
|
||||||
|
|
||||||
|
query_interval = yang_dnode_get_uint16(args->dnode, NULL);
|
||||||
|
pim_ifp->gm_default_query_interval = query_interval;
|
||||||
|
gm_ifp_update(ifp);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return NB_OK;
|
return NB_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "pim_bfd.h"
|
#include "pim_bfd.h"
|
||||||
#include "pim_bsm.h"
|
#include "pim_bsm.h"
|
||||||
#include "pim_vxlan.h"
|
#include "pim_vxlan.h"
|
||||||
|
#include "pim6_mld.h"
|
||||||
|
|
||||||
int pim_debug_config_write(struct vty *vty)
|
int pim_debug_config_write(struct vty *vty)
|
||||||
{
|
{
|
||||||
|
@ -291,7 +292,7 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PIM_IPV == 4
|
#if PIM_IPV == 4
|
||||||
static int pim_igmp_config_write(struct vty *vty, int writes,
|
static int gm_config_write(struct vty *vty, int writes,
|
||||||
struct pim_interface *pim_ifp)
|
struct pim_interface *pim_ifp)
|
||||||
{
|
{
|
||||||
/* IF ip igmp */
|
/* IF ip igmp */
|
||||||
|
@ -360,6 +361,17 @@ static int pim_igmp_config_write(struct vty *vty, int writes,
|
||||||
|
|
||||||
return writes;
|
return writes;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
static int gm_config_write(struct vty *vty, int writes,
|
||||||
|
struct pim_interface *pim_ifp)
|
||||||
|
{
|
||||||
|
if (pim_ifp->mld_version != MLD_DEFAULT_VERSION)
|
||||||
|
vty_out(vty, " ipv6 mld version %d\n", pim_ifp->mld_version);
|
||||||
|
if (pim_ifp->gm_default_query_interval != IGMP_GENERAL_QUERY_INTERVAL)
|
||||||
|
vty_out(vty, " ipv6 mld query-interval %d\n",
|
||||||
|
pim_ifp->gm_default_query_interval);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int pim_config_write(struct vty *vty, int writes, struct interface *ifp,
|
int pim_config_write(struct vty *vty, int writes, struct interface *ifp,
|
||||||
|
@ -388,9 +400,7 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp,
|
||||||
++writes;
|
++writes;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if PIM_IPV == 4
|
writes += gm_config_write(vty, writes, pim_ifp);
|
||||||
writes += pim_igmp_config_write(vty, writes, pim_ifp);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* update source */
|
/* update source */
|
||||||
if (!pim_addr_is_any(pim_ifp->update_source)) {
|
if (!pim_addr_is_any(pim_ifp->update_source)) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ noinst_PROGRAMS += pimd/test_igmpv3_join
|
||||||
vtysh_scan += \
|
vtysh_scan += \
|
||||||
pimd/pim_cmd.c \
|
pimd/pim_cmd.c \
|
||||||
pimd/pim6_cmd.c \
|
pimd/pim6_cmd.c \
|
||||||
|
pimd/pim6_mld.c \
|
||||||
#end
|
#end
|
||||||
vtysh_daemons += pimd
|
vtysh_daemons += pimd
|
||||||
vtysh_daemons += pim6d
|
vtysh_daemons += pim6d
|
||||||
|
@ -89,6 +90,7 @@ nodist_pimd_pimd_SOURCES = \
|
||||||
pimd_pim6d_SOURCES = \
|
pimd_pim6d_SOURCES = \
|
||||||
$(pim_common) \
|
$(pim_common) \
|
||||||
pimd/pim6_main.c \
|
pimd/pim6_main.c \
|
||||||
|
pimd/pim6_mld.c \
|
||||||
pimd/pim6_stubs.c \
|
pimd/pim6_stubs.c \
|
||||||
pimd/pim6_cmd.c \
|
pimd/pim6_cmd.c \
|
||||||
pimd/pim6_mroute_msg.c \
|
pimd/pim6_mroute_msg.c \
|
||||||
|
@ -155,6 +157,8 @@ noinst_HEADERS += \
|
||||||
pimd/pim_vxlan.h \
|
pimd/pim_vxlan.h \
|
||||||
pimd/pim_vxlan_instance.h \
|
pimd/pim_vxlan_instance.h \
|
||||||
pimd/pimd.h \
|
pimd/pimd.h \
|
||||||
|
pimd/pim6_mld.h \
|
||||||
|
pimd/pim6_mld_protocol.h \
|
||||||
pimd/mtracebis_netlink.h \
|
pimd/mtracebis_netlink.h \
|
||||||
pimd/mtracebis_routeget.h \
|
pimd/mtracebis_routeget.h \
|
||||||
pimd/pim6_cmd.h \
|
pimd/pim6_cmd.h \
|
||||||
|
@ -163,6 +167,7 @@ noinst_HEADERS += \
|
||||||
clippy_scan += \
|
clippy_scan += \
|
||||||
pimd/pim_cmd.c \
|
pimd/pim_cmd.c \
|
||||||
pimd/pim6_cmd.c \
|
pimd/pim6_cmd.c \
|
||||||
|
pimd/pim6_mld.c \
|
||||||
# end
|
# end
|
||||||
|
|
||||||
pimd_pimd_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4
|
pimd_pimd_CFLAGS = $(AM_CFLAGS) -DPIM_IPV=4
|
||||||
|
|
|
@ -96,6 +96,7 @@ module frr-gmp {
|
||||||
type uint8 {
|
type uint8 {
|
||||||
range "1..2";
|
range "1..2";
|
||||||
}
|
}
|
||||||
|
default "2";
|
||||||
description
|
description
|
||||||
"MLD version.";
|
"MLD version.";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue