Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ crash-*
leak-*
timeout-*
oom-*

# Test helper
test/solicit
test/.deps/
60 changes: 54 additions & 6 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,60 @@ Change Log

All relevant, user visible, changes are documented in this file.

[UNRELEASED][]
--------------
[v3.1][] - 2026-10-21
---------------------

Faster convergence and Multicast Router Discovery. `mcd` queries the
LAN as soon as a bridge port starts forwarding, can advertise itself
to snooping switches that learn router ports from MRD, and follows an
interface's own `query-interval` in all its timers. Many fixes for
robustness, found by fuzzing and static analysis in the Nix flake
contributed by Dave Seddon.

> **Note:** the router timeout and the group membership interval now
> follow an interface's own `query-interval`. With `query-interval 4`
> on a bridge, `mcd` takes over from a silent querier after 13 sec and
> drops a group without members after 18 sec, not after 255 and 260
> sec. An explicit `router-timeout` still applies to all interfaces.

### Changes
- General query when a bridge port starts forwarding, RFC 4541 §2.1.1.
Link up, STP settling and a port added to the bridge all count, for
the bridge itself and VLAN interfaces on top of it. Hosts behind
the port are back in the MDB within the response interval instead
of at the next periodic query. On by default, see `topology-query`
in mcd.conf(5)
- Multicast Router Discovery, RFC 4286, IGMP and MLD. New `iface`
keyword `mrd on` advertises `mcd` as a multicast router to the
snooping switches on the LAN, answers their solicitations, and sends
a termination on the way down. Off by default. New global keyword
`mrd-interval` sets the advertisement interval, 4 to 180 sec
- `mctl show routers` lists the multicast routers heard through MRD
on each interface, with their advertised intervals. `mctl show
interfaces` has a new MRD column
- `mctl show interfaces json` lists each interface's router timeout
as `router-timeout`. `mctl show` labels the global value as the
default
- New `mcd -t` option to check a configuration file and exit, like
`nginx -t`. Errors are reported as `FILE:LINE: message`, and it
runs as a normal user
- Shell completion for `mctl`, bash and zsh. The commands come from
the running daemon, via the new `mctl -C` option. Installed with
`make install`, see `--with-bash-completion-dir` and
`--with-zsh-completion-dir`
- Nix flake with a development shell, native and cross builds, OCI
images, static analysis, fuzzing, and the test suite in microVMs,
see `nix/README.md`. By Dave Seddon

### Fixes
- Fix router timeout (other querier present interval) ignoring the
configured `robustness`, query and response intervals. It was always
255 sec unless `router-timeout` was set, it now follows each
interface's own `query-interval`
- Fix group membership interval ignoring the interface's own
`query-interval`
- Fix `-l LEVEL` having no effect in the foreground, `mcd -n` printed
every message, debug included, whatever the level
- Fix `mctl show router-ports` and `show igmp json` missing the router
ports of bridges with a hyphen in their name, e.g. `br-0`
- Fix shell command injection through bridge names when listing router
Expand All @@ -15,9 +65,6 @@ All relevant, user visible, changes are documented in this file.
the client
- Fix `mcd` dying of `SIGPIPE` when an IPC client hangs up before
reading its reply
- Fix default router timeout (other querier present interval) ignoring
the configured `robustness`, query and response intervals, it was
always 255 sec unless `router-timeout` was set
- Fix `mcd` exiting 0 when it cannot fork to daemonize
- Fix `mctl` crashing on a daemon command of more than 32 words
- Fix timers stopping if an expiry landed exactly on a whole second
Expand Down Expand Up @@ -295,7 +342,8 @@ Initial public release.
Limited IGMPv1/v2/v3 querier with hard-coded query interval, etc. Put
interfaces in a .conf file, whitespace separated to enable querier.

[UNRELEASED]: https://github.com/kernelkit/mcd/compare/v3.0...HEAD
[UNRELEASED]: https://github.com/kernelkit/mcd/compare/v3.1...HEAD
[v3.1]: https://github.com/kernelkit/mcd/compare/v3.0...v3.1
[v3.0]: https://github.com/kernelkit/mcd/compare/v2.4...v3.0
[v2.4]: https://github.com/kernelkit/mcd/compare/v2.3...v2.4
[v2.3]: https://github.com/kernelkit/mcd/compare/v2.2...v2.3
Expand Down
9 changes: 9 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
doc_DATA = README.md ChangeLog.md LICENSE mcd.conf
EXTRA_DIST = README.md ChangeLog.md LICENSE mcd.conf doc/overview.svg doc/querier-timeline.svg doc/why.svg
EXTRA_DIST += completions/bash/mctl completions/zsh/_mctl
DISTCLEANFILES = *~ DEADJOE semantic.cache *.gdb *.elf core core.* *.d
SUBDIRS = man src

if ENABLE_TEST
SUBDIRS += test
endif

if BASHCOMP
bashcomp_DATA = completions/bash/mctl
endif

if ZSHCOMP
zshcomp_DATA = completions/zsh/_mctl
endif

## Check if tagged in git
release-hook:
@if [ "x`git tag -l v$(PACKAGE_VERSION)|grep $(PACKAGE_VERSION)`" = "x" ]; then \
Expand Down
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ Features
`robustness` queries spaced `query-interval / 4` apart, per RFC 3376
§8.6 and §8.7. A host that missed the first query gets another one
within half a minute instead of waiting a full query interval.
- Topology query. When a bridge port starts forwarding, after link
up, STP settling or being added to the bridge, `mcd` sends a general
query at once, per RFC 4541 §2.1.1. Hosts behind the port are back
in the MDB within the response interval instead of at the next
periodic query, up to two minutes later with the defaults.
- Version fallback. Starts at IGMPv3 and MLDv2, drops to the older
versions when such hosts appear, and returns when they go quiet.
- Proxy mode, a backup querier that sends from `0.0.0.0` or `::` and can
Expand All @@ -79,6 +84,9 @@ Features
- Raw VLAN mode. Run a querier directly on a VLAN of a filtering
bridge, no `vlanN` interface required.
- Static groups, joined in the kernel so they never time out.
- Multicast Router Discovery, RFC 4286. With `mrd on`, `mcd` advertises
itself to the snooping switches on the LAN, for the ones that learn
their router ports from MRD rather than from queries.
- `mctl` shows interface and querier status, group memberships, router
ports per bridge, and the bridge MDB in a readable form. JSON output
for scripts, a watch mode for humans.
Expand Down Expand Up @@ -202,24 +210,25 @@ arguments for an overview:
$ mctl
Multicast Overview
Query Interval (default): 125 sec
Router Timeout : 255
Router Timeout (default): 255 sec
Fast Leave Ports :
Router Ports : eth3
Flood Ports :

Interface VID Proto Querier State Interval Timeout Ver
vlan1 IGMP 192.168.1.1 Up 125 None 3
vlan1 MLD fe80::7a4c:9bff:fe12:3456 Up 125 None 2
vlan2 IGMP 192.168.2.1 Up 125 None 3
vlan2 MLD fe80::7a4c:9bff:fe12:3456 Up 125 None 2
br0 42 IGMP 0.0.0.0 Up 125 None 3
br0 42 MLD :: Up 125 None 2
Interface VID Proto Querier State Interval Timeout MRD Ver
vlan1 IGMP 192.168.1.1 Up 125 None on 3
vlan1 MLD fe80::7a4c:9bff:fe12:3456 Up 125 None on 2
vlan2 IGMP 192.168.2.1 Up 125 None on 3
vlan2 MLD fe80::7a4c:9bff:fe12:3456 Up 125 None on 2
br0 42 IGMP 0.0.0.0 Up 125 None off 3
br0 42 MLD :: Up 125 None off 2

Bridge VID Multicast Group Ports
br0 2 239.1.1.1 eth3

The parts are also available on their own, as `show interfaces`,
`show groups`, `show router-ports` and `show mdb`. Add `json` to any
`show groups`, `show router-ports` and `show mdb`. `show routers`
lists the multicast routers heard through MRD. Add `json` to any
`show` command for machine readable output, and `-m` to keep it
updating like `watch(1)`. All commands and options are in
[mctl(8)][].
Expand All @@ -243,10 +252,11 @@ it in the foreground with more logging:
sudo mcd -n -l info

`SIGHUP` reloads the configuration and probes the interfaces again.
To run several instances, give each its own identity with `-i NAME`,
which also names its configuration file, PID file and IPC socket, and
point `mctl -i NAME` at the one you want. The options, signals and
files are in [mcd(8)][].
To check a configuration file before loading it, run `mcd -t -f FILE`
as any user. To run several instances, give each its own identity
with `-i NAME`, which also names its configuration file, PID file and
IPC socket, and point `mctl -i NAME` at the one you want. The
options, signals and files are in [mcd(8)][].


Building
Expand All @@ -267,6 +277,7 @@ Options worth knowing:
|------------------------|------------------------------------------------------------|
| `--with-group=NAME` | Group owner of `/run/mcd.sock`, default `root`. Use e.g. `wheel` to let admins run `mctl` without `sudo` |
| `--enable-test` | Build the test suite, see [Testing](#testing) |
| `--with-bash-completion-dir=DIR`, `--with-zsh-completion-dir=DIR` | Where to install the `mctl` completion scripts, default under `DATADIR`, `no` to skip |
| `--prefix=`, `--sysconfdir=` | The usual, defaults are `/usr/local` and `/etc` |

Configure prints a summary at the end, check it before running `make`.
Expand Down
36 changes: 36 additions & 0 deletions completions/bash/mctl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# bash completion for mctl(8)
#
# The commands come from the running mcd, via mctl -C, so there is
# nothing to complete when it is down, apart from the options.

_mctl()
{
local cur=${COMP_WORDS[COMP_CWORD]}
local prev=${COMP_WORDS[COMP_CWORD-1]}
local word

case $prev in
-u|--ipc)
COMPREPLY=($(compgen -f -- "$cur"))
return
;;
-i|--ident)
return
;;
esac

if [[ $cur == -* ]]; then
COMPREPLY=($(compgen -W '-h --help -i --ident -m --monitor -p --plain
-t --no-heading -u --ipc -v --version' -- "$cur"))
return
fi

# All words so far, options included, so -i NAME and -u FILE reach
# the same daemon the command will. The last word is the partial.
COMPREPLY=()
while IFS=$'\t' read -r word _; do
COMPREPLY+=("$word")
done < <(mctl -C "${COMP_WORDS[@]:1:COMP_CWORD}" 2>/dev/null)
}

complete -F _mctl mctl
34 changes: 34 additions & 0 deletions completions/zsh/_mctl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#compdef mctl
#
# zsh completion for mctl(8). The commands come from the running
# mcd, via mctl -C, with their descriptions.

_mctl() {
# The line as typed, _arguments rewrites $words for the command
local -a typed=("${words[@]}") cands
local cur=$CURRENT line

_arguments -s \
'(-h --help)'{-h,--help}'[Show usage and the available commands]' \
'(-i --ident)'{-i,--ident}'[Talk to the mcd started with this identity]:name' \
'(-m --monitor)'{-m,--monitor}'[Run the command every two seconds]' \
'(-p --plain)'{-p,--plain}'[Plain table headings]' \
'(-t --no-heading)'{-t,--no-heading}'[Skip table headings]' \
'(-u --ipc)'{-u,--ipc}'[IPC socket]:file:_files' \
'(-v --version)'{-v,--version}'[Show version and exit]' \
'*::command:->command' && return

case $state in
command)
# All words so far, options included, so -i NAME and -u FILE
# reach the same daemon the command will. word<TAB>desc
# becomes word:desc for _describe
for line in ${(f)"$(mctl -C "${typed[@]:1:cur-1}" 2>/dev/null)"}; do
cands+=("${line/$'\t'/:}")
done
_describe -t commands 'mctl command' cands
;;
esac
}

_mctl "$@"
19 changes: 18 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([mcd], [3.0], [https://github.com/kernelkit/mcd/issues])
AC_INIT([mcd], [3.1], [https://github.com/kernelkit/mcd/issues])
AC_CONFIG_AUX_DIR(.aux)
AM_INIT_AUTOMAKE([1.11 foreign])
AM_SILENT_RULES([yes])
Expand Down Expand Up @@ -65,6 +65,19 @@ AC_ARG_WITH(group,
AS_HELP_STRING([--with-group=NAME], [Group for /run/mcd.sock (mctl), default: root]),
[group=$withval], [group=root])

AC_ARG_WITH(bash-completion-dir,
AS_HELP_STRING([--with-bash-completion-dir=DIR], [Install mctl bash completion in DIR, default: DATADIR/bash-completion/completions, no to skip]),
[bashcompdir=$withval], [bashcompdir=yes])

AC_ARG_WITH(zsh-completion-dir,
AS_HELP_STRING([--with-zsh-completion-dir=DIR], [Install mctl zsh completion in DIR, default: DATADIR/zsh/site-functions, no to skip]),
[zshcompdir=$withval], [zshcompdir=yes])

AS_IF([test "x$bashcompdir" = "xyes"], [bashcompdir='$(datadir)/bash-completion/completions'])
AS_IF([test "x$zshcompdir" = "xyes"], [zshcompdir='$(datadir)/zsh/site-functions'])
AC_SUBST(bashcompdir)
AC_SUBST(zshcompdir)

# Create config.h from selected features and fallback defautls
AS_IF([test "x$with_group" != "xno"], [
AS_IF([test "x$group" = "xyes"], [
Expand All @@ -74,6 +87,8 @@ AC_DEFINE_UNQUOTED(DEFGROUP, "$group", [For /run/finit/socket])
AM_CONDITIONAL(BSD, [test "x$ac_cv_header_net_if_dl_h" = "xyes"])
AM_CONDITIONAL(LINUX, [test "x$ac_cv_header_linux_netlink_h" = "xyes"])
AM_CONDITIONAL(ENABLE_TEST, [test "x$enable_test" != "xno"])
AM_CONDITIONAL(BASHCOMP, [test "x$bashcompdir" != "xno"])
AM_CONDITIONAL(ZSHCOMP, [test "x$zshcompdir" != "xno"])

# Workaround for as-of-yet unreleased runstatedir support, planned for
# autoconf 2.70, which some major distros have backported.
Expand All @@ -95,6 +110,8 @@ cat <<EOF
Optional features:
IPC group.............: $group
Unit tests............: $enable_test
Bash completion.......: $bashcompdir
Zsh completion........: $zshcompdir

------------- Compiler version --------------
$($CC --version || true)
Expand Down
23 changes: 21 additions & 2 deletions man/mcd.8
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
.\" -*- nroff -*-
.Dd October 16, 2026
.Dd October 21, 2026
.Dt MCD 8 SMM
.Os
.Sh NAME
.Nm mcd
.Nd IGMP and MLD querier for the Linux bridge
.Sh SYNOPSIS
.Nm
.Op Fl hnsv
.Op Fl hnstv
.Op Fl f Ar FILE
.Op Fl i Ar NAME
.Op Fl l Ar LEVEL
Expand Down Expand Up @@ -48,6 +48,13 @@ queries,
.Cm query-interval
/ 4 apart, RFC 3376 \(sc8.6 and \(sc8.7.
.It
General query when a port of the bridge starts forwarding, so hosts
behind it are back in the MDB within the response interval, not at the
next query interval, RFC 4541 \(sc2.1.1. See
.Cm topology-query
in
.Xr mcd.conf 5 .
.It
Starts at IGMPv3 and MLDv2 and falls back to IGMPv2, IGMPv1 or MLDv1
for as long as older hosts are heard.
.It
Expand All @@ -59,6 +66,13 @@ VLAN interface on top.
.It
Static groups, joined in the kernel so the bridge always forwards them
to itself.
.It
Multicast Router Discovery, RFC 4286, for switches that learn their
router ports from MRD advertisements rather than from queries. Off by
default, see
.Cm mrd
in
.Xr mcd.conf 5 .
.El
.Pp
Interfaces, links and addresses are followed with netlink, so an
Expand Down Expand Up @@ -101,6 +115,11 @@ a reload completes.
Log to syslog, the default unless
.Fl n
is given.
.It Fl t , -check-config
Check the configuration file and exit without starting the daemon.
Errors are reported as
.Ar FILE:LINE: message
with a non-zero exit status. Runs as a normal user.
.It Fl u , -ipc Ns = Ns Ar FILE
IPC socket used by
.Xr mctl 8 ,
Expand Down
Loading
Loading