Skip to content

Prepare v3.1 - #7

Merged
troglobit merged 13 commits into
mainfrom
releng
Oct 21, 2026
Merged

troglobit merged 13 commits into
mainfrom
releng

Conversation

@troglobit

Copy link
Copy Markdown
Contributor

No description provided.

There has been no way to check a configuration file without starting
the daemon.  A bad file is only found when mcd exits on it, at start
or on SIGHUP, with the old configuration gone.

Like nginx -t and sshd -t.  The file is read by the same parser,
errors are reported as FILE:LINE: message with exit status EX_DATAERR,
or EX_NOINPUT when the file cannot be opened.  The check runs before
the root check and before daemonizing, so any user can run it:

    $ mcd -t -f /etc/mcd.conf
    mcd: config file /etc/mcd.conf is ok

config_init() now returns -1 when the file is missing, which main()
turns into the exit status.  The daemon used to start in silence
without a configuration file, it now logs the missing file and waits
for SIGHUP as before.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
mctl takes its commands from the running daemon's help reply, so a
completion script with a list of its own would drift from mcd.

New hidden option, mctl -C WORD..., prints the candidates for the
last word, which may be partial, from that same list, one per line,
with the command's description after a tab when the word completes
it.  Nothing is printed when no daemon runs.  The bash and zsh scripts
build on it and hand mctl the line as typed, so -i NAME and -u FILE
reach the daemon the command will.  zsh shows the descriptions.

Installed by make install.  --with-bash-completion-dir=DIR and
--with-zsh-completion-dir=DIR override the DATADIR defaults, or skip
with =no.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
A host behind a port that just came up, or that STP just put into
forwarding, is not in the bridge MDB until it answers a query.  With
the default query interval that is up to 125 s without its streams.

RFC 4541 section 2.1.1: send a general query when a port starts
forwarding.  netlink.c follows the bridge port state in the AF_BRIDGE
link messages, seeded with a dump at start so only a change counts,
and reports a port entering forwarding for its bridge.  mcd queries on
that bridge and on the VLAN interfaces on top of it, found through the
new ifi_lower from IFLA_LINK, when it is the elected querier, followed
by the usual startup burst.  A flapping port gets one query per second.

On by default, 'no topology-query' turns it off.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Some snooping switches learn their router ports from MRD
advertisements, or from PIM, and not from IGMP/MLD queries.  Behind
such a switch the hosts' reports never reach mcd, and the switch keeps
flooding.

New iface keyword 'mrd on'.  mcd then advertises itself as a multicast
router on the interface: three advertisements shortly after it comes
up, one every 20 s with jitter after that, one in answer to a
solicitation, and a termination when the interface goes down or mcd
exits.  Both families, from the interface address and the link-local
address like the queries.  An interface in proxy mode has no address
to advertise and sends nothing.  Off by default, a router port
receives all multicast, which a box that only runs a querier may not
want.  mctl show interfaces has a new MRD column.

The sender side comes from https://github.com/troglobit/mrdisc, as
does the solicit tool the test uses.  The IGMP receive path now checks
the checksum of every message, as the MLD path already did.

Advertisements heard from other routers are only logged.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The Linux bridge runs every IGMP packet through ip_mc_check_igmp(),
which wants the full 8 byte IGMP header and drops anything shorter as
malformed.  RFC 4286 solicitations and terminations are 4 bytes, so a
Linux snooping switch never saw mcd's termination, and dropped
solicitations on their way to it.

Send both at the size of the advertisement, zero padded.  Zero words
do not change the checksum, so a receiver that sums only the 4 bytes
of the RFC still validates it.  Same for ICMPv6, and for the solicit
tool in the test suite.

New test, mrd-bridge.sh: a second, snooping bridge behind a veth pair
learns its router port from mcd's advertisements alone, for MLD and
for IGMP, with mcd's queries dropped by tc on the way in.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
mcd -n printed every message, debug included, whatever -l said.

The logit() macro in log.h sends messages to prinl() when not
logging to syslog, and prinl() has no level check.  logger() had a
level aware stderr branch, which logit() never reached.

Check the level in logit() itself, for both paths, so a filtered
message costs neither its argument evaluation nor its formatting.
The stderr branch in logger() goes, it is the syslog path only now.
The one line mcd -t prints on success goes straight to prinl(), it
should show whatever -l says.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The interval was a fixed 20 s, the RFC 4286 default, while the RFC
allows 4 to 180 s.

New global keyword:

    mrd-interval SEC

The jitter, 2.5 % of the interval, follows it.  mctl show status
lists the value.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Advertisements from other routers were only logged, so there was no
way to see which multicast routers mcd hears on a LAN, or what they
advertise.

mcd now keeps a table per interface: an advertisement adds or
refreshes a router, with its advertisement interval, query interval
and robustness, a termination removes it.  A router not heard from
within NeighborDeadInterval, 3 * (interval + jitter) per RFC 4286
section 3.5, is dropped.  Routers are heard whether or not 'mrd on'
is set, at most 32 per interface.

    mctl show routers [json]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
An interface with its own query-interval still waited the global
router timeout before taking over from a querier that went silent.
With only

    iface br0 enable query-interval 4

mcd waited 255 s, not 13 s.  The default was computed once, at load,
from global-query-interval.

RFC 3376 section 8.5 and RFC 3810 section 9.5 compute the other
querier present interval from the interface's own query interval.
Do that for every interface, falling back to global-query-interval.
An explicit router-timeout still applies to all interfaces.

Reported by the Infix OS team.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Same as the router timeout: the group membership interval was taken
from global-query-interval, so with only

    iface br0 enable query-interval 4

a group whose last member went silent stayed for 260 s, not 18 s.

Use the interface's own query interval, RFC 3376 section 8.4 and
RFC 3810 section 9.4, for the membership and compatibility mode
timers.  show compat detail lists the interface's query interval.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Since 4970500 the router timeout follows the interface's own query
interval, but mctl show still printed only the global value as
"Router Timeout", which misleads for an interface with its own
query-interval.

Label it "Router Timeout (default)", like the query interval above
it, and list each interface's value as router-timeout in mctl show
interfaces json.  The key in show status json stays as it is.

Reported by the Infix OS team.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
mcd.conf(5) did not say that MRD advertisements go out whether or not
mcd is the elected querier, which raised the question of whether that
was intended.  It is: a backup querier needs the membership reports
too, so it has a group table to take over with.

mctl(8) also lacked the MRD column in the show interfaces list.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
@troglobit
troglobit merged commit 7663208 into main Oct 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant