Skip to content

[sonic-frr/bgpcfgd]: Add EVPN MAC synchronization over the FPM channel - #29312

Draft
tahmed-dev wants to merge 21 commits into
sonic-net:masterfrom
tahmed-dev:tahmed/fpm-mac-learning-master
Draft

[sonic-frr/bgpcfgd]: Add EVPN MAC synchronization over the FPM channel#29312
tahmed-dev wants to merge 21 commits into
sonic-net:masterfrom
tahmed-dev:tahmed/fpm-mac-learning-master

Conversation

@tahmed-dev

Copy link
Copy Markdown
Contributor

Why I did it

EVPN multihoming needs locally learned MACs and EVPN remote MACs to converge in
a single place. Today fdbsyncd reads the kernel bridge FDB and depends on a
SONiC-only NDA_PROTOCOL patch carried in sonic-linux-kernel and iproute2
to tell hardware-learned entries apart from zebra's own writes.

This series moves that synchronization onto the FPM channel, so zebra is told
directly which MACs the ASIC learned. That removes the need for the out-of-tree
kernel patch and gives EVPN-MH a single writer for the kernel bridge FDB.

The design was reviewed with Patrice Brissette; per-MAC state is [CP OR DP],
and the kernel is still programmed in fpm mode so the kernel ARP/ND state
machine is preserved.

Work item tracking

  • Microsoft ADO: 38937153 ([Octans][FRR] Reconcile Phase 1 kernel and fpmsyncd FDB updates without duplicates)
  • Parent: 38937093 ([Octans][Phase 1] EVPN multihoming for a dual-ToR frontend without hardware switchover)

How I did it

  • Split the FPM MAC decode out of dplane_fpm_sonic.c into fpm_mac.{c,h}, kept
    free of zebra/FRR headers so it is unit testable, and wired the CUnit tests
    into the package build (patch 0121).
  • Added FPM message types for MAC replay and the end-of-replay marker, pinned to
    explicit numeric values on both sides of the wire contract (patch 0077).
  • Applied FPM MACs and the replay sweep on zebra's main thread, and replaced the
    hold-down with an end-of-replay marker so a stale sweep cannot delete live
    remote MACs (patches 0118-0120).
  • Let a dataplane module observe FDB nexthop-group programming so ES-backed
    remote MACs can be carried as a nexthop group rather than a peer VTEP address
    (patch 0122).
  • Allowed a local MAC to be programmed into the kernel bridge FDB in fpm mode,
    which is what removes the kernel patch dependency (patch 0123).
  • Dropped the now-unused iproute2 bridge-FDB protocol field patch.
  • Added the FDB_SYNC YANG model plus its tests, and bgpcfgd changes to bind
    the L3VNI to its VRF and restore EVPN Ethernet Segments into FRR on restart.

How to verify it

Unit tests, run by the package build:

FRR CUnit via pytest wrapper  447 passed, 5 skipped
sonic-yang-models             yang model tests pass
bgpcfgd                       tests/test_evpn_mh.py, tests/test_vrf.py

Hardware validation on two Arista 7060X6-64PE-B (TH5) leaves in an EVPN-MH
dual-homed topology, 38/38 checks passed on both switches, covering:

  • MAC flush on port down and local-inactive marking in zebra
  • the [CP OR DP] rule, CP-only MACs still reachable through the tunnel
  • mac_sync_mode fpm -> kernel -> fpm ownership handover
  • recovery after the FPM channel drops (fpmsyncd restart)
  • single-writer: no entry carries both extern_learn and static

Dependency and merge order

This PR cannot build until sonic-swss-common #1243 merges: it references
APP_EVPN_DF_TABLE_NAME, which does not exist on master yet. Opening as a draft
for that reason.

Merge order across the companion PRs:

  1. sonic-swss-common [broadcom]: update sai to 3.0.3.3-1 #1243 - schema, blocks everything
  2. sonic-swss [Juniper][QFX5210] Fixing a few platform issues #4857 - fpmsyncd/fdbsyncd
  3. sonic-utilities [Mellanox] Add a new SKU Mellanox-SN4600C-D112C8 #4833 - config fdb mac-sync-mode CLI
  4. this PR - FRR patches and YANG
  5. sonic-linux-kernel [build]: support submodule patching for SONIC_DPKG_DEBS #619 - drops the kernel patches, must be last, since
    master still carries the fdbsyncd plumbing those patches support

Which release branch to backport (provide reason below if selected)

  • 201811
  • 201911
  • 202006
  • 202012
  • 202106
  • 202111
  • 202205
  • 202211
  • 202305

Tested branch (Please provide the tested image version)

  • tahmed_fpm-mac-learning-master.0-68bbddeaf3 on Arista 7060X6-64PE-B (TH5)

Description for the changelog

Add EVPN MAC synchronization over the FPM channel, removing the kernel bridge
FDB protocol patch dependency for EVPN multihoming.

Link to config_db schema for YANG module changes

sonic-fdb-sync.yang adds the FDB_SYNC table:

"FDB_SYNC": {
    "global": {
        "mac_sync_mode": "fpm"   /* "kernel" (default) or "fpm" */
    }
}

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
Prefer orchagent ZMQ publishing for EVPN-MH split-horizon and DF state, with direct Redis APP_DB fallback when ZMQ is disabled or unavailable.

Drop the unused EVPN ES backup NHG FPM/APP_DB contract and add a CUnit harness for the SONiC FRR Redis/ZMQ provider.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
…hannel

Phase 1 of moving MAC synchronization off the Linux kernel bridge FDB and
onto the FPM channel.

dplane_fpm_sonic gains an inbound MAC path: bridge FDB entries arriving on
the FPM socket are fed through zebra_vxlan_local_mac_add_update()/_del(),
the same entry point the kernel path uses, so the EVPN state machine and
its multihoming semantics are unchanged.

RTM_FPM_MAC_REPLAY_END is pinned to an explicit value. This enum is
duplicated in sonic-swss fpmsyncd/fpm/fpm.h and the two copies have
drifted, so a positional value would have resolved differently on each side
and produced a silent wire mismatch.

sonic-fdb-sync models FDB_SYNC|global with mac_sync_mode, defaulting to
kernel so behaviour is unchanged unless explicitly enabled.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
fpmsyncd stamps each replayed local MAC with a generation and closes the
replay with RTM_FPM_MAC_REPLAY_END. Record that generation on the MAC and,
on the marker, delete every FPM-learned MAC still carrying an older one.

Adds patch 0119 for the ZEBRA_MAC_FPM_LEARNED flag and the generation field
on struct zebra_mac; the walk and delete live in the SONiC-owned plugin.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
fpm_read() runs on the FPM provider's own pthread, but the MAC path called
zebra_vxlan_local_mac_add_update() directly and the end-of-replay sweep walked
the EVPN hashes, both of which the main thread owns. The route case in the same
switch marshals into a dplane context instead, and the RMAC walkers are all
scheduled on zrouter.master.

Parse the netlink message on the FPM thread and hand the MAC to the main thread,
and queue the sweep the same way; the event queue is FIFO, so a MAC replayed in
this session is stamped before the sweep inspects it.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
fpm_read_local_mac() parsed the netlink message and mutated zebra state in
the same function, so the parsing could only be exercised by running zebra
against a live FPM connection. There was no way to unit test it.

Move the decode into dplane_fpm_sonic/fpm_mac.c. That file depends only on
the kernel netlink headers, not on zebra, so it can be linked into a plain
test binary. Add a CUnit suite that covers the decode directly and register
it the same way sonic_frr_redis_interface_test is registered.

Pulling the parsing out also made two gaps in it visible, both fixed here:

  * The message family was never checked, so a non AF_BRIDGE neighbour
    message would have been treated as a MAC.
  * NDA_LLADDR and NDA_VLAN were read without validating their payload
    length, so a short attribute would have been over read.

The decode now writes the caller's struct only after the message has been
fully validated, so a caller can never act on a partially filled result.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
The FRR package build never ran a single unit test. dh_auto_test invokes
"make test", and in this tree that target only builds the pceplib helper
binaries and runs nothing, so every check program, including the SONiC ones
in tests/zebra, was silently skipped. A test that is never executed cannot
catch a regression.

Override dh_auto_test to build the check programs and run the suite through
tests/tests.xml instead.

A plain "make check" is not usable here. It additionally runs pceplib's
*_valgrind.sh wrappers, which reference a relative path that does not
resolve in an out-of-tree build and fail with exit 127 regardless of the
code under test. tests/tests.xml is FRR's own target for the unit suite and
depends on $(check_PROGRAMS), so it builds and runs exactly what we want.

The existing nocheck handling is preserved.

Verified by breaking the FPM MAC decode on purpose: the package build fails,
the failure is reported as a test failure rather than a build error, and no
deb is produced.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
The reconnect walk covered L3VNI router MACs only. Nothing walked
zevpn->mac_table, so a remote MAC that fpmsyncd installed before it restarted
was never re-advertised, and a withdrawal for it could not be matched against
anything. fpmsyncd could neither rebuild remote state nor safely remove what
it held.

Add a second walk, chained after the router MAC walk, that re-sends every
ZEBRA_MAC_REMOTE entry. Local MACs are skipped because they originate from
fpmsyncd. Each context is built the way zebra_evpn_mac.c builds it, including
the mac->es case where the destination is a nexthop group rather than a VTEP
address, so the replay reproduces what zebra would have sent rather than an
approximation of it.

RTM_FPM_MAC_REPLAY_END then marks the end of the walk. The type already
exists for the local direction and carries no payload, so reusing it here
needs no new enum value in either copy of the message type list, which is
where these two codebases have drifted before.

The marker schedules the socket write itself. Writing into obuf is not enough:
every other sender ends with event_add_write(), and without it the marker waits
for an unrelated enqueue to flush it, which on an idle session never arrives.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
config vrf add_vrf_vni_map writes a vni into VRF|<name>, and vrfmgrd acts on
it, but nothing on the bgpcfgd path passes that vni to zebra. The zebra
template renders vni only for VNET, and no manager watches the VRF table. The
mapping therefore reaches the ASIC side while zebra never creates the L3VNI,
leaving the VNI as L2 with the VRF merely as its tenant, and the CLI reports
success throughout.

frrcfgd already implements this in vrf_handler, so the model is supported and
this is a gap in the default path rather than a missing feature. Add the
equivalent manager for runtime updates, and the template branch so the binding
survives a config reload.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
…he marker

The end-of-replay marker was treated as proof that anything not replayed no
longer exists, and the sweep deleted it on the spot. A replay is only as
complete as its sender: fpmsyncd emits the marker even when it replayed
nothing, so a short read deletes MACs that are still present, and since
nothing re-drives an unchanged STATE_DB entry they stay deleted.

Reproduced on hardware without an instrumented binary by removing the port
field from a STATE_DB FDB entry, which loadLocalMacs() skips and
processStateFdb() ignores rather than withdrawing. An fpmsyncd reconnect then
deleted a local MAC that was still in STATE_DB, and it did not come back.

Arm a hold-down instead. A refresh arriving inside the window carries the
current generation and survives; only entries still stale at expiry are
removed. A newer replay cancels the pending sweep, whose timer would otherwise
remove the MACs that replay had just refreshed. Verified on hardware both
ways: held then removed at expiry with no refresh, held then kept when a
refresh arrived inside the window.

fpmsyncd applies the same hold to the MACs it learns from zebra. The two
constants are independent and nothing in the protocol negotiates them.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
The sweep predicate sat inline in the hash callback, where nothing can reach
it without a running zebra, so the rule that decides whether a held MAC is
retired had no coverage. Move it into fpm_mac.c beside the decode and test it,
including that a MAC carrying the current generation survives, which is what a
refresh arriving inside the hold-down relies on.

Also state that the hold-down windows on the two sides are independent by
design rather than a pair that must be kept equal.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
…ming

EVPN multihoming programs its Ethernet Segment nexthops and nexthop groups
through kernel_upd_mac_nh() and kernel_upd_mac_nhg(), which reach the kernel
directly through netlink_talk() without ever building a dataplane context.
Every other FDB operation goes through the dataplane, so a provider can be
handed a MAC that refers to a group it was never told about.

Add patch 0122, which fires a hook from the four kernel_*_mac_nh* entry
points. The kernel path is byte for byte unchanged and the hook expands to
nothing when no module is registered, so the patch is idempotent and is
shaped to be offered upstream.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
In fpm mode the ASIC learns local MACs and hands them to zebra over FPM, and
fdbsyncd no longer mirrors them into the kernel. Nothing was left populating
the kernel bridge FDB for those MACs, so the kernel kept probing hosts the
dataplane could already reach.

Add patch 0123, exposing kernel_upd_local_mac()/kernel_del_local_mac(), and
call them from the inbound FPM MAC path. The entry carries NTF_EXT_LEARNED so
the bridge does not age a MAC it never learned. It is deliberately not tagged
RTPROT_HW: nothing reads the protocol back now that SONiC no longer snoops the
kernel, which is what lets the RTPROT_HW kernel patch be dropped later.

Going straight to netlink rather than through dplane_local_mac_add() is
required: a dataplane context would come back to this module as a MAC install
and be echoed to fpmsyncd as a bogus remote entry.

An externally learned update is not enough on its own. The kernel takes a
separate path for NTF_EXT_LEARNED and ignores ndm_state there, so the message
can create the entry but can never change the state of one that already exists,
and zebra is not the only writer: its Ethernet Segment sync programs the same
MAC as static. Send the update a second time without NTF_EXT_LEARNED, a flag
the entry keeps once set, so the ordinary path honours ndm_state and forces it
back to NUD_REACHABLE. Correcting the entry in place rather than deleting and
re-adding it keeps the MAC forwarding throughout, so nothing is flooded
meanwhile. Sticky MACs are left alone, NUD_NOARP being what they should carry.

Both calls end in netlink_talk(), so report their return values rather than
discarding them. A rejected write otherwise leaves no trace anywhere, and a MAC
missing from the bridge FDB looks identical to one that was never sent.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
…down

fpmsyncd builds its replay by reading STATE_DB synchronously, so the set it
sends is complete at the moment it sends the marker. Nothing is still settling
that the 120s hold-down was waiting for, and the event queue is FIFO, so every
MAC replayed in the session is already stamped by the time the marker's handler
runs.

Sweep straight from fpm_local_mac_replay_end() and drop
FPM_MAC_STALE_HOLD_SECONDS, t_mac_sweep, mac_sweep_generation and the separate
sweep handler.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
Register the patch 0122 hooks and encode what zebra would have given the
kernel: NHA_FDB to mark it an FDB nexthop, then NHA_GATEWAY for a single VTEP
or NHA_GROUP for a set of member ids. The receiver therefore decodes exactly
what it decodes from the kernel today.

Writing into fnc->obuf is not enough on its own. An Ethernet Segment can settle
with no other traffic behind it, so the write task is armed here rather than
left to the next enqueue, which on an idle box never comes.

fpmsyncd already gates publishing on fpm mode, so this is inert in kernel mode.
fdbsyncd still reads the same groups from the kernel, so both write
L2_NEXTHOP_GROUP_TABLE with identical content until that path is retired.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
…them

Ethernet Segment nexthops are programmed outside the dataplane, so nothing
re-sends them when the FPM session comes back. fpmsyncd would then hold MACs
naming groups it had never been told about, and it drops those rather than
publish a group it cannot resolve.

Walk the L2 nexthops and then the groups built from them, between the RMAC and
MAC walks. The order is load-bearing in both places: a group is only published
once every member is known, and a MAC only once its group is.

Also advances swss for the fdbsyncd retirement:
  d905e58b [fdbsyncd]: Stop publishing L2 nexthop groups in fpm mode

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
The encoding was inline in the send path, where it could only be exercised by
building an FPM session. Move it into fpm_mac.c alongside the MAC decode, which
already stays free of zebra state precisely so it can be tested directly, and
have the send path call it.

The five cases cover what fpmsyncd parses: an IPv4 and an IPv6 single VTEP, a
group and its member ids, a delete that must still carry NHA_FDB, and the
rejections. Verified by canary: dropping the NHA_FDB attribute turns the build
red, which is the failure that would otherwise have fpmsyncd hand an Ethernet
Segment to the L3 route path.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
Local MACs originate from fpmsyncd, so sending them back is a loop. It
is also unsafe: dplane_local_mac_add() sets only the type of its stack
vtep to IPADDR_NONE, and netlink_neigh_update_msg_encode() still emits a
16 byte NDA_DST built from that never-initialised union. fpmsyncd read
it as a remote VTEP and published uninitialised zebra stack into
APPL_DB. The kernel tolerates the attribute on a local entry, which is
why this only surfaced once MACs crossed the FPM channel.

Filter on DPLANE_MAC_REMOTE, which is the rule the replay walk already
applied; only the incremental path was missing it.

Seed the kernel entry before handing the MAC to zebra as well. A MAC on
an Ethernet Segment makes zebra queue its own sync install marking the
entry static, and that runs on the dplane thread, so seeding afterwards
raced it and could downgrade a static entry back to extern_learn.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
The bridge FDB protocol names where a MAC came from, not who wrote the
entry. A MAC the switch ASIC learned is hardware learnt however it
reaches the kernel, so program it with RTPROT_HW, which is the same
attribution fdbsyncd gave these MACs when it drove the kernel path
itself. Remote MACs zebra installs keep RTPROT_ZEBRA, naming zebra as
their source.

That only holds if one writer owns the entry. zebra's Ethernet Segment
sync installs the same MAC through the dataplane and would overwrite the
attribution with its own, leaving two writers racing on one entry, so a
pre-kernel dataplane provider now skips the kernel half of that write
once FPM is driving local MACs. Remote MACs are left alone because zebra
really is their source.

Ownership is taken on the end-of-replay marker rather than on the first MAC
to arrive over the channel. fpmsyncd sends the marker only in fpm mode, and
sends it even when it replayed nothing, so it is the earliest honest statement
that local MACs belong elsewhere and it covers boot, where the ASIC has learnt
nothing yet while BGP has already converged and the Ethernet Segment sync would
otherwise get an unsuppressed write in. Ownership is released when the channel
drops, because nothing is feeding local MACs while fpmsyncd is gone and zebra
has to write them itself until the next replay.

Also drop the sonic-linux-kernel NTF_EXT_VALIDATED patch, which nothing
consumes, and correct a claim in 0123 that nothing reads the protocol
back: netlink_macfdb_change() does, to keep zebra from re-learning its
own entries.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
EVPN_ETHERNET_SEGMENT reaches FRR only from the sonic-utilities CLI, at the
moment the command is typed. Nothing reconciles CONFIG_DB into FRR afterwards
and zebra.interfaces.conf.j2 renders no Ethernet Segment configuration, so the
segments are lost on every reboot while CONFIG_DB keeps them.

Both PEs then advertise the same MACs with no shared ESI. BGP reads that as a
MAC move, so the two sides bump the sequence number at each other indefinitely:
observed in the lab at sequence 11916 with both MACs flagged Duplicate and the
VXLAN FDB rewritten about twice a second, until the segments were reapplied by
hand.

Add an EvpnMhEsMgr that pushes the segment to FRR from CONFIG_DB, mirroring the
vtysh sequence the CLI already issues. It depends on PORTCHANNEL because the
system MAC is what makes the ESI identical on both PEs, so the LAG table is now
fed into the Directory as well.

That dependency is satisfied as soon as the first PortChannel reaches the
Directory, while the system MAC lookup is per interface, so a segment can still
be handled before its own PortChannel arrives. Programming it then would emit an
ESI without es-sys-mac, which the peer never matches, so the handler defers
instead by returning False; the Manager base class queues it and retries on
every dependency change, and the segment is programmed as soon as its
PortChannel is known.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
This taught the bridge command to emit and print NDA_PROTOCOL on an FDB entry.
The kernel side of that came from the sonic-linux-kernel protocol patch, which
is gone, so the attribute is now discarded on add and never returned on dump.

Nothing consumes it either. Its only caller was the fdbsyncd kernel-mode path,
which is gated off in fpm mode, and the support probe backing that path can
never succeed anyway because it tests "dev lo" and loopback is not an Ethernet
device.

Where a MAC came from now travels over the FPM channel instead, so the origin
marker no longer needs a kernel or iproute2 carrier.

Signed-off-by: Tamer Ahmed <tamerahmed@microsoft.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@mssonicbld

Copy link
Copy Markdown
Collaborator

/azp run Azure.sonic-buildimage

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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.

2 participants