Skip to content

Feature/tunnel wireguard for FRR#3569

Merged
ipspace merged 47 commits into
ipspace:devfrom
jbemmel:feature/tunnel-wireguard
Jul 8, 2026
Merged

Feature/tunnel wireguard for FRR#3569
ipspace merged 47 commits into
ipspace:devfrom
jbemmel:feature/tunnel-wireguard

Conversation

@jbemmel

@jbemmel jbemmel commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator
  • IPv4 or IPv6
  • default routing table or in VRF
  • WireGuard package dynamically installed
  • keys auto-generated if WireGuard tools are installed
  • MTU auto-derived and validated

Tested:

./device-module-test -d frr -p clab tunnel *wire*
./device-module-test -d frr -p libvirt tunnel *wire* (needed the default route fixes)

Notes:

  • PR generalizes Linux package installation to support both apk and apt-get; needed for FRR containers
  • The ipv6 link local address is needed for OSPFv3, made this a util method in routing
  • add_linux_packages is a candidate for general util method

Comment thread netsim/extra/tunnel/wireguard/__init__.py

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you (or your AI friend) copied the utils/tunnel.py code, resulting in duplicate functionality. Please fix that.

And yes, I know you'll tell me something about AFs, but we can deal with that.

Comment thread netsim/extra/tunnel.wireguard/plugin.py Outdated
def _interface_has_af(intf: Box, af: str) -> bool:
return af in intf and isinstance(intf[af],str)

def _interface_matches_constraints(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And why exactly did you reinvent the utility function? There's a reason I put it into utils, not into tunnel.gre

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I instructed AI to implement a Wireguard tunnel plugin similar to the GRE tunnel plugin - it took this a bit too literally

Comment thread netsim/extra/tunnel.wireguard/plugin.py Outdated
Comment thread netsim/extra/tunnel.wireguard/plugin.py Outdated
we derive a deterministic address that the initial config script assigns when
the interface is created.
'''
if intf.get('tunnel.af') != 'ipv6' or 'ipv6' not in intf:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the wireguard tunnels are not dual-stacked (I know I could check), then you have to throw an error, not just check the AF, shrug, and return. If they are dual-stacked, then I don't understand why you're checking tunnel.af (which impacts the underlay interface selection)

Comment thread netsim/extra/tunnel.wireguard/plugin.py Outdated
@jbemmel jbemmel marked this pull request as draft July 3, 2026 22:32
@jbemmel

jbemmel commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator Author

PR now uses the common get_tunnel_source proposing a minor change in the AF filtering (i.e. if tunnel.af is set, use it to filter valid interfaces) and adding the MTU of the source interface in the response

@jbemmel jbemmel marked this pull request as ready for review July 4, 2026 05:28
@ipspace

ipspace commented Jul 4, 2026

Copy link
Copy Markdown
Owner

I would suggest you use the new utility functions from #3576, which might make your code cleaner (and with less duplicate code). Also, the new source selection function should give you all the information you need to figure out AF and MTU.

@jbemmel jbemmel marked this pull request as draft July 4, 2026 15:23
@jbemmel jbemmel force-pushed the feature/tunnel-wireguard branch from 8a275f9 to e4d2c21 Compare July 4, 2026 15:26
Comment thread tests/integration/tunnel/03-wireguard.yml
@jbemmel

jbemmel commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator Author

I would suggest you use the new utility functions from #3576, which might make your code cleaner (and with less duplicate code). Also, the new source selection function should give you all the information you need to figure out AF and MTU.

ok, updated:

  • Moved IPv6 link-local address generation to a FRR device quirk (rather than the Wireguard tunnel plugin)
  • Moved tunnel device creation to FRR initial script (also for GRE -> requires initializing the full tunnel config)
  • Suggested change for tunnel_source to yield each source interface such that specific plugins can configure each interface if needed

add_linux_packages doesn't belong in the Wireguard plugin, suggested alternative:

# tunnel/wireguard/defaults.yml (plugin declares dependency)
netlab_linux_packages:
  tunnel@wireguard:
    wireguard-tools: wg

with logic in augment/devices.py to merge it per device that runs the included modules or plugins

@jbemmel jbemmel marked this pull request as ready for review July 5, 2026 01:46

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stop "fixing" imaginary problems, hard-coding stuff where include files work, and changing things for no documented reason.

There are probably other things that have to be fixed; I had enough.

Comment thread netsim/ansible/templates/initial/frr.j2 Outdated
fi
{% elif netlab_mgmt_vrf|default(True) %}
{% else %}
{% if netlab_linux_packages|default({}) %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would you install packages only in FRR container but not in FRR VM?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both clab and libvirt providers pass the integration tests, what led you to believe this would only apply to FRR container?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stupid me. We install Linux packages unconditionally in libvirt path and check whether netlab_linux_packages is defined in clab path. Maybe we should unify them once this gets merged?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the asymmetric redundant if condition that may have confused you, I'll make it the same

Comment thread netsim/devices/frr.py Outdated
Comment thread netsim/ansible/templates/initial/frr.j2 Outdated
if [ ! -e /sys/devices/virtual/net/{{ i.ifname }} ]; then
{% if i.type in ['lag','bond'] %}
{{ create_bond_dev(i,node_provider) }}
{% elif i.tunnel.mode|default('') == 'gre' %}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Why are you "fixing" a problem that does not exist, and why are you doing that in an unrelated PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested, I moved device creation to the initial template (including 'tunnel' similar to 'bond'). This in turn broke the GRE plugin, because now tunnel devices got created twice

I can refactor things and use the per-plugin include pattern instead

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about creating tunnels in the custom config templates? Why do you think you have to create it during the initial device configuration? FRR seems patient...

Comment thread netsim/ansible/templates/initial/frr.j2 Outdated
dev {{ i.tunnel._source.ifname }} ttl 255
{% else %}
ip link add {{ i.ifname }} type dummy
ip link add {{ i.ifname }} type {{ 'wireguard' if i.tunnel.mode|default('') == 'wireguard' else 'dummy' }}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We have a nice solution in #3566. Why do you think it makes sense to hard-code "wireguard" in main FRR initial template just to fix your immediate challenge?

Comment thread netsim/extra/tunnel/gre/frr.j2 Outdated
ip link set dev {{ intf.ifname }} up
{% endfor %}
exit 0
# Empty file, GRE tunnels are created during initial FRR device configuration

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stop fixing imaginary problems

Comment thread netsim/extra/tunnel/wireguard/frr.j2 Outdated

ip link set dev "${ifname}" mtu "${mtu}" up

wait_wg_peer "${ifname}" "${peer_key}"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you expect this to work between a FRR VM and a FRR container? Also, what happens if the two containers are configured in different batches?

Comment thread netsim/extra/tunnel/_p2p.py Outdated

yield ndata,intf

def tunnel_source(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this change necessary?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the Wireguard plugin I needed to customize each tunnel beyond what the generic code does.

There are many ways to do that, I could simply iterate again too. I thought this might be a nice way to do it - but if you don't like it, we can change it

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Premature optimization at its best... Let's add a generator, and a stub function, with all the accompanying docstrings, just so we don't need an extra for loop 🤦‍♂️ in code that will be executed once every other blue moon.

@ipspace

ipspace commented Jul 5, 2026

Copy link
Copy Markdown
Owner

add_linux_packages doesn't belong in the Wireguard plugin, suggested alternative:

Why not? It's either there or in the device quirks.

with logic in augment/devices.py to merge it per device that runs the included modules or plugins

So we'll modify core logic to address the needs of one device for one plugin? Well, no.

Even if it were more common, it's still applicable only to Linux and friends (FRR, BIRD).

@jbemmel

jbemmel commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@jbemmel jbemmel marked this pull request as ready for review July 6, 2026 16:22
@jbemmel jbemmel marked this pull request as draft July 7, 2026 14:08
jbemmel and others added 12 commits July 7, 2026 09:09
Install wireguard-tools during initial configuration on clab FRR nodes
before the management VRF is created, and add integration tests for
global and transport-VRF underlay scenarios.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add shared tunnel utilities, OSPFv3 link-local assignment, VRF routing
rules for WireGuard handshakes, and integration tests for IPv6 transport.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use tunnel-AF-specific fwmark rules so IPv6 transport handshakes work
over VRF underlays instead of peer-specific policy routing.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move WireGuard netdev creation out of the plugin's custom config (which runs
last) and into the initial FRR config, so the interface exists before FRR and
the routing modules are configured. A generic _linux_device_type interface
attribute tells the initial script which kind of netdev to create, and the
deterministic IPv6 link-local (_ipv6_link_local) needed for OSPFv3 is assigned
at the same time (kernel address auto-generation is impossible for the
ARPHRD_NONE WireGuard device).

Drop the optional cryptography fallback; key generation now relies solely on
wireguard-tools, and the integration tests use explicit static keys. Docs
updated accordingly.

Co-authored-by: Cursor <cursoragent@cursor.com>
The global WireGuard listening socket could not receive encrypted packets
arriving on a VRF underlay interface for IPv6 tunnels, so handshakes never
completed. net.ipv4.udp_l3mdev_accept governs l3mdev socket matching for both
IPv4 and IPv6 UDP (there is no net.ipv6 counterpart), but it was only set in
the IPv4 branch. Set it unconditionally and collapse the per-AF policy-rule
handling into a single command.

Co-authored-by: Cursor <cursoragent@cursor.com>
Default tunnel.allowed_ips to ::/0 for IPv6 tunnels (0.0.0.0/0 for IPv4) so
IPv6 tunnels work without setting it explicitly. Add an Allowed IPs section
explaining WireGuard cryptokey routing and its inbound/outbound semantics, and
trim the examples to the plain and VRF/IPv6 cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Set _default for tunnel.af, tunnel.mtu, and tunnel.persistent_keepalive in
defaults.yml instead of hard-coding them in post_transform. Only the
allowed_ips default stays in code because it depends on the resolved af.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace hardcoded default routes with a dedicated core address pool so
the static routes reference the pool rather than 0.0.0.0/0 and ::/0.

Co-authored-by: Cursor <cursoragent@cursor.com>
Schema _default values for tunnel.af, mtu, and persistent_keepalive are
materialized onto the per-node interface entries during link validation,
before link->interface propagation. They then win over an explicit
link-level override (e.g. tunnel.af: ipv6) during the interface merge,
breaking the tunnel source lookup. Resolve these defaults in
post_transform where the interface data is already merged.

Also iterate node_iflist with .items() instead of re-indexing by key.

Co-authored-by: Cursor <cursoragent@cursor.com>
Infer tunnel.af from the underlay source interface (IPv4 when available,
IPv6 for IPv6-only underlays) so IPv6-only links no longer need an explicit
tunnel.af. Derive the tunnel interface link-local address from the low 64
bits (interface identifier) of the overlay address via a new
routing.get_ipv6_link_local helper, guaranteeing distinct link-locals on
both tunnel endpoints. Drop the now-redundant tunnel.af/allowed_ips from
the IPv6 integration tests and update the docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
jbemmel and others added 8 commits July 7, 2026 09:09
…fig.

Tunnel netdevs were created in the first loop but skipped the down/sysctl/up
cycle that enables accept_ra on host interfaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
Defer link-local assignment until after the IPv6 sysctl loop and only
bring loopback interfaces up during netdev creation so tunnel down/up
does not drop addresses needed for OSPFv3.

Co-authored-by: Cursor <cursoragent@cursor.com>
Match the VyOS initial-config pattern for FRR, restore shared tunnel_source
in _p2p, and drop WireGuard peer handshake waiting that breaks partial deploys.

Co-authored-by: Cursor <cursoragent@cursor.com>
Map tunnel.gre-style config names to slash-separated plugin directories so
frr.initial.j2 templates are found during initial device configuration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Derive _ipv6_link_local in wireguard_intf_defaults instead of an FRR device
quirk, and iterate node_iflist directly in post_transform.

Co-authored-by: Cursor <cursoragent@cursor.com>
Inline tunnel AF derivation and scope af_active to the allowed_ips default block.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Restore GRE from dev and fold WireGuard netdev creation into frr.j2,
removing the frr.initial.j2 indirection from initial device config.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jbemmel jbemmel force-pushed the feature/tunnel-wireguard branch from d3fe3e1 to 8cc58f6 Compare July 7, 2026 14:14
jbemmel and others added 9 commits July 7, 2026 09:21
Keep tunnel-specific IPv6 link-local assignment in the WireGuard plugin
template instead of the shared initial device configuration.

Co-authored-by: Cursor <cursoragent@cursor.com>
Run linux/packages.j2 on containerlab nodes again so per-node packages like
wireguard-tools are installed during initial config.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use a single interface loop with inlined wg set commands instead of a
separate setup_wg_tunnel wrapper function.

Co-authored-by: Cursor <cursoragent@cursor.com>
Assume vrf_table and rule_pref are set whenever tunnel.vrf is configured.

Co-authored-by: Cursor <cursoragent@cursor.com>
Set peer_ip from the tunnel AF first, then build peer_endpoint from IP and port.

Co-authored-by: Cursor <cursoragent@cursor.com>
Compute rule_pref only when tunnel.vrf is set and inline the fwmark conditional.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Move destination variables below VRF underlay setup and use intf.tunnel.af
directly instead of a separate af alias.

Co-authored-by: Cursor <cursoragent@cursor.com>
tunnel_destination can run after interface defaults even when earlier
steps reported validation errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jbemmel

jbemmel commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away...

@jbemmel jbemmel marked this pull request as ready for review July 7, 2026 15:06

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try taking out the LLA hack. The current setup works for me without adding manual LLA to the tunnel interface.

Comment thread netsim/extra/tunnel/wireguard/frr.j2 Outdated
@jbemmel jbemmel marked this pull request as draft July 7, 2026 16:29
Drop the deterministic fe80:: derivation and restore routing.py to dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jbemmel jbemmel marked this pull request as ready for review July 7, 2026 16:32

for node in node_iflist:
ndata = topology.nodes[node]
for intf in _tunnel.interfaces(ndata,'wireguard'):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have used node_iflist[node] here, but it doesn't matter.

ipspace added a commit that referenced this pull request Jul 8, 2026

@ipspace ipspace left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works like a charm ;) Thank you!

@ipspace ipspace merged commit c24f73e into ipspace:dev Jul 8, 2026
6 checks passed
snuffy22 pushed a commit to snuffy22/netlab that referenced this pull request Jul 9, 2026
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.

3 participants