Add support for FDB activity notification control [1]. Users can use this to enable activity notifications on a new FDB entry that was learned on an ES (Ethernet Segment) peer and mark it as locally inactive: # bridge fdb add 00:11:22:33:44:55 dev bond1 master static activity_notify inactive $ bridge -d fdb get 00:11:22:33:44:55 br br1 00:11:22:33:44:55 dev bond1 activity_notify inactive master br1 static $ bridge -d -j -p fdb get 00:11:22:33:44:55 br br1 [ { "mac": "00:11:22:33:44:55", "ifname": "bond1", "activity_notify": true, "inactive": true, "flags": [ ], "master": "br1", "state": "static" } ] User space will receive a notification when the entry becomes active and the control plane will be able to mark the entry as locally active. It is also possible to enable activity notifications on an existing dynamic entry: $ bridge -d -s -j -p fdb get 00:aa:bb:cc:dd:ee br br1 [ { "mac": "00:aa:bb:cc:dd:ee", "ifname": "bond1", "used": 8, "updated": 8, "flags": [ ], "master": "br1", "state": "" } ] # bridge fdb replace 00:aa:bb:cc:dd:ee dev bond1 master static activity_notify norefresh $ bridge -d -s -j -p fdb get 00:aa:bb:cc:dd:ee br br1 [ { "mac": "00:aa:bb:cc:dd:ee", "ifname": "bond1", "activity_notify": true, "used": 3, "updated": 23, "flags": [ ], "master": "br1", "state": "static" } ] The "norefresh" keyword is used to avoid resetting the entry's last active time (i.e., "updated" time). User space will receive a notification when the entry becomes inactive and the control plane will be able to mark the entry as locally inactive. Note that the entry was converted from a dynamic entry to a static entry to prevent the kernel from automatically deleting it upon inactivity. An existing inactive entry can only be marked as active by the kernel or by disabling and enabling activity notifications: $ bridge -d fdb get 00:11:22:33:44:55 br br1 00:11:22:33:44:55 dev bond1 activity_notify inactive master br1 static # bridge fdb replace 00:11:22:33:44:55 dev bond1 master static activity_notify $ bridge -d fdb get 00:11:22:33:44:55 br br1 00:11:22:33:44:55 dev bond1 activity_notify inactive master br1 static # bridge fdb replace 00:11:22:33:44:55 dev bond1 master static # bridge fdb replace 00:11:22:33:44:55 dev bond1 master static activity_notify $ bridge -d fdb get 00:11:22:33:44:55 br br1 00:11:22:33:44:55 dev bond1 activity_notify master br1 static Marking an entry as inactive while activity notifications are disabled does not make sense and will be rejected by the kernel: # bridge fdb replace 00:11:22:33:44:55 dev bond1 master static inactive RTNETLINK answers: Invalid argument [1] https://lore.kernel.org/netdev/20200623204718.1057508-1-nikolay@cumulusnetworks.com/ Reviewed-by: Petr Machata Signed-off-by: Ido Schimmel --- I have a kernel selftest for this functionality. I will post it after this patch is accepted. --- bridge/fdb.c | 69 ++++++++++++++++++++++++++++++++++++++++++++--- man/man8/bridge.8 | 22 ++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/bridge/fdb.c b/bridge/fdb.c index 7b4443661e6b..d57b57503198 100644 --- a/bridge/fdb.c +++ b/bridge/fdb.c @@ -40,7 +40,8 @@ static void usage(void) " [ self ] [ master ] [ use ] [ router ] [ extern_learn ]\n" " [ sticky ] [ local | static | dynamic ] [ vlan VID ]\n" " { [ dst IPADDR ] [ port PORT] [ vni VNI ] | [ nhid NHID ] }\n" - " [ via DEV ] [ src_vni VNI ]\n" + " [ via DEV ] [ src_vni VNI ] [ activity_notify ]\n" + " [ inactive ] [ norefresh ]\n" " bridge fdb [ show [ br BRDEV ] [ brport DEV ] [ vlan VID ]\n" " [ state STATE ] [ dynamic ] ]\n" " bridge fdb get [ to ] LLADDR [ br BRDEV ] { brport | dev } DEV\n" @@ -142,6 +143,24 @@ static void fdb_print_stats(FILE *fp, const struct nda_cacheinfo *ci) } } +static void fdb_print_ext_attrs(struct rtattr *nfea) +{ + struct rtattr *tb[NFEA_MAX + 1]; + + parse_rtattr_nested(tb, NFEA_MAX, nfea); + + if (tb[NFEA_ACTIVITY_NOTIFY]) { + __u8 notify; + + notify = rta_getattr_u8(tb[NFEA_ACTIVITY_NOTIFY]); + if (notify & FDB_NOTIFY_BIT) + print_bool(PRINT_ANY, "activity_notify", + "activity_notify ", true); + if (notify & FDB_NOTIFY_INACTIVE_BIT) + print_bool(PRINT_ANY, "inactive", "inactive ", true); + } +} + int print_fdb(struct nlmsghdr *n, void *arg) { FILE *fp = arg; @@ -172,8 +191,9 @@ int print_fdb(struct nlmsghdr *n, void *arg) if (filter_state && !(r->ndm_state & filter_state)) return 0; - parse_rtattr(tb, NDA_MAX, NDA_RTA(r), - n->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); + parse_rtattr_flags(tb, NDA_MAX, NDA_RTA(r), + n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)), + NLA_F_NESTED); if (tb[NDA_FLAGS_EXT]) ext_flags = rta_getattr_u32(tb[NDA_FLAGS_EXT]); @@ -273,6 +293,9 @@ int print_fdb(struct nlmsghdr *n, void *arg) "linkNetNsId", "link-netnsid %d ", rta_getattr_u32(tb[NDA_LINK_NETNSID])); + if (show_details && tb[NDA_FDB_EXT_ATTRS]) + fdb_print_ext_attrs(tb[NDA_FDB_EXT_ATTRS]); + if (show_stats && tb[NDA_CACHEINFO]) fdb_print_stats(fp, RTA_DATA(tb[NDA_CACHEINFO])); @@ -399,6 +422,34 @@ static int fdb_show(int argc, char **argv) return 0; } +static void fdb_add_ext_attrs(struct nlmsghdr *n, int maxlen, + bool activity_notify, bool inactive, + bool norefresh) +{ + struct rtattr *nest; + + if (!activity_notify && !inactive && !norefresh) + return; + + nest = addattr_nest(n, maxlen, NDA_FDB_EXT_ATTRS | NLA_F_NESTED); + + if (activity_notify || inactive) { + __u8 notify = 0; + + if (activity_notify) + notify |= FDB_NOTIFY_BIT; + if (inactive) + notify |= FDB_NOTIFY_INACTIVE_BIT; + + addattr8(n, maxlen, NFEA_ACTIVITY_NOTIFY, notify); + } + + if (norefresh) + addattr_l(n, maxlen, NFEA_DONT_REFRESH, NULL, 0); + + addattr_nest_end(n, nest); +} + static int fdb_modify(int cmd, int flags, int argc, char **argv) { struct { @@ -412,6 +463,9 @@ static int fdb_modify(int cmd, int flags, int argc, char **argv) .ndm.ndm_family = PF_BRIDGE, .ndm.ndm_state = NUD_NOARP, }; + bool activity_notify = false; + bool norefresh = false; + bool inactive = false; char *addr = NULL; char *d = NULL; char abuf[ETH_ALEN]; @@ -495,6 +549,12 @@ static int fdb_modify(int cmd, int flags, int argc, char **argv) req.ndm.ndm_flags |= NTF_EXT_LEARNED; } else if (matches(*argv, "sticky") == 0) { req.ndm.ndm_flags |= NTF_STICKY; + } else if (strcmp(*argv, "activity_notify") == 0) { + activity_notify = true; + } else if (strcmp(*argv, "inactive") == 0) { + inactive = true; + } else if (strcmp(*argv, "norefresh") == 0) { + norefresh = true; } else { if (strcmp(*argv, "to") == 0) NEXT_ARG(); @@ -559,6 +619,9 @@ static int fdb_modify(int cmd, int flags, int argc, char **argv) if (!req.ndm.ndm_ifindex) return nodev(d); + fdb_add_ext_attrs(&req.n, sizeof(req), activity_notify, inactive, + norefresh); + if (rtnl_talk(&rth, &req.n, NULL) < 0) return -1; diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index 08f329c6bca6..fe800d3fe290 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -91,7 +91,8 @@ bridge \- show / manipulate bridge addresses and devices .B via .IR DEVICE " ] | " .B nhid -.IR NHID " } " +.IR NHID " } [ " +.BR activity_notify " ] [ " inactive " ] [ " norefresh " ] .ti -8 .BR "bridge fdb" " [ [ " show " ] [ " @@ -860,6 +861,25 @@ remote VXLAN tunnel endpoint. ecmp nexthop group for the VXLAN device driver to reach remote VXLAN tunnel endpoints. +.TP +.B activity_notify +enable activity notifications on an existing or a new FDB entry. This keyword +only makes sense for non-dynamic entries as dynamic entries are deleted upon +inactivity. An entry is assumed to be active unless the \fBinactive\fR keyword +is specified. + +.TP +.B inactive +mark an FDB entry as inactive. This keyword only makes sense in conjunction +with the \fBactivity_notify\fR keyword and usually only when adding a new FDB +entry as opposed to replacing an existing one. + +.TP +.B norefresh +avoid resetting an FDB entry's activity (i.e., its last updated time). This can +be useful, for example, when one wants to enable activity notifications on an +existing entry without modifying its last updated time. + .SS bridge fdb append - append a forwarding database entry This command adds a new fdb entry with an already known .IR LLADDR . -- 2.50.0