diff --git a/docs/dev/config/index.md b/docs/dev/config/index.md index d033491b16..ed0b5aa8a1 100644 --- a/docs/dev/config/index.md +++ b/docs/dev/config/index.md @@ -20,6 +20,7 @@ The following implementation notes for configuration deployment, initial device routing.md services.md srv6.md + tunnel.gre.md vlan.md vrf.md ``` diff --git a/docs/dev/config/tunnel.gre.md b/docs/dev/config/tunnel.gre.md new file mode 100644 index 0000000000..ad3756e06f --- /dev/null +++ b/docs/dev/config/tunnel.gre.md @@ -0,0 +1,220 @@ +(dev-config-tunnel-gre)= +# Configuring GRE Tunnels + +This document describes the device data model parameters one should consider when creating a GRE tunnel configuration template. GRE tunnels are implemented as a plugin (`tunnel.gre`) rather than a core module; the plugin uses shared utility functions from the `netsim/extra/tunnel` package. + +```eval_rst +.. contents:: Table of Contents + :depth: 2 + :local: + :backlinks: none +``` + +## Overview + +The `tunnel.gre` plugin supports point-to-point GRE tunnels with IPv4 or IPv6 transport encapsulation. + +**Notes:** + +* The configuration template (in Jinja2 format) should be stored in `netsim/extra/tunnel/gre/.j2` with **nos** being the value of **netlab_device_type** or **ansible_network_os** variable (see [Using Your Devices with Ansible Playbooks](../devices.md#using-your-device-with-ansible-playbooks) for more details). +* Some devices require an initial configuration template to create the tunnel interface before other modules (e.g., routing) can reference it. Those templates are stored as `.initial.j2` alongside the main template and are executed during the initial configuration phase. +* Most of the data model attributes are optional. Use `if sth is defined`, `sth|default(value)` or `if 'sth' in intf` in your Jinja2 templates. +* To use GRE tunnels, add `plugin: [ tunnel.gre ]` to the lab topology. + +## Data Model + +GRE tunnel attributes are defined in `netsim/extra/tunnel/gre/defaults.yml` within the `attributes.link.tunnel` namespace. Every link with `tunnel.mode: gre` becomes a tunnel link. + +### Link-Level Attributes (per tunnel) + +* **tunnel.mode** (required) -- must be set to `gre`. This is validated by the attribute schema and marks the link as a GRE tunnel. +* **tunnel.af** (optional, string) -- transport address family. Valid values: `ipv4` (default), `ipv6`. Controls whether the tunnel uses regular GRE (`gre`) or IPv6 GRE (`ip6gre`) encapsulation. +* **tunnel.vrf** (optional, string) -- transport VRF name. When set, the tunnel uses a specific VRF to route the GRE packets rather than the global routing table. +* **tunnel.source** (optional, dictionary) -- controls how the tunnel source interface is selected. The system performs an automatic best-effort selection when this attribute is omitted. + +### The tunnel.source Dictionary + +When `tunnel.source` is specified, it can contain: + +* **type** (string) -- source interface type. Currently, only `loopback` is supported as a valid value. +* **link.name** (string) -- select the source interface by its link name. +* **link.role** (string) -- select the source interface by its link role. +* **ifindex** (integer) -- specify the source interface ifindex directly (rarely needed). + +### Source Interface Selection + +_netlab_ automatically selects the best underlay source interface by iterating over the node's interfaces and applying these filters (in order): + +1. If `tunnel.source.type` is not set, interfaces of type `loopback` or `tunnel` are excluded. +2. If `tunnel.vrf` is not set, VRF interfaces are excluded. Otherwise, only interfaces in `tunnel.vrf` VRF are considered. +4. If `tunnel.source.link.name` is set, the interface name must match. +5. If `tunnel.source.link.role` is set, the interface or its link role must match. +6. If `tunnel.af` is set, only interfaces with a numbered address in that address family are considered. +7. Otherwise, only interfaces with at least one numbered IPv4 or IPv6 address are considered. + +The first matching interface is used as the tunnel source. + +### Transformed Attributes (set by the plugin) + +After the `post_transform` phase, each tunnel interface receives: + +* **tunnel.\_source.ifname** (string) -- name of the selected source interface. +* **tunnel.\_source.mtu** (integer) -- MTU of the source interface (defaults to node MTU, usually 1500). +* **tunnel.\_source.ipv4** / **tunnel.\_source.ipv6** (string) -- IP address of the source interface for the tunnel transport AF. +* **tunnel.\_destination.ipv4** / **tunnel.\_destination.ipv6** (string) -- IP address of the remote tunnel endpoint (copied from the neighbor's `tunnel._source`). + +The interface also gets: + +* **type** set to `tunnel` during `pre_transform`. +* **virtual_interface** set to `True` during link transformation. +* An interface description enhanced with `[GRE tunnel]` during `post_transform`. + +## Device Features + +Device support for GRE tunnels is declared in device YAML files under `features.tunnel.gre`. The feature value is a list of supported capabilities; each device advertises which transport options it supports: + +```yaml +features: + tunnel: + gre: [ vrf, ipv4, ipv6 ] # Supports all options +``` + +Common capability combinations: + +| Device | GRE Support | +|---|---| +| FRR | `[ vrf, ipv4, ipv6 ]` | +| Cisco IOS/IOS-XE | `[ vrf, ipv4, ipv6 ]` | +| Juniper vJunos Router | `[ vrf, ipv4 ]` | +| OpenBSD | `[ ipv4 ]` | + +The `check_feature` function in `netsim/extra/tunnel/__init__.py` validates device support by checking for the presence of entries like `ipv4`, `ipv6`, or `vrf` in the device's `features.tunnel.gre` list. If a device does not support the requested feature (e.g., GRE over IPv6 when only `[ipv4]` is listed), an error is reported. + +## Tunnel Interface Naming + +Tunnel interfaces get `ifindex` values starting at 20000 (the tunnel type is the second entry in `VIRTUAL_INTERFACE_TYPES`, at offset index 1, so `(1 + 1) * 10000 = 20000`). + +Each device defines a `tunnel_interface_name` template that controls the tunnel interface name format, for example: + +| Device | Interface Name Template | +|---|---| +| FRR, VyOS, Cumulus | `tun{ifindex}` | +| Cisco IOS, Arista EOS | `Tunnel{ifindex}` | +| RouterOS 7 | `tunnel{ifindex}` | +| OpenBSD | `gre{ifindex}` | +| Juniper vJunos Router | `gr-0/0/10.{ifindex}` | +| Juniper vSRX | `gr-0/0/0.{ifindex}` | +| Aruba CX | `tunnel {ifindex}` | + +## Containerlab Kernel Modules + +When GRE tunnels are used with the `clab` provider, the plugin declares the required kernel modules in `defaults.yml`: + +```yaml +providers: + clab: + kmods: + tunnel@gre: [ ip_gre, ip6_gre ] +``` + +The `@` serves as a dot replacement in module names for kmod lookup. These modules must be loaded on the container host to support GRE tunneling. + +## Transformation Process + +The plugin hooks into the topology transformation pipeline via two hooks defined in `netsim/extra/tunnel/gre/__init__.py`: + +### pre_transform + +1. **`_tunnel.set_tunnel_type(topology)`** -- marks all links with `tunnel.mode: gre` as `type: tunnel`. +2. **Validates P2P topology** -- every GRE tunnel link must have exactly two nodes. An error is reported otherwise. + +### post_transform + +1. **`_p2p.feature_check(topology, ...)`** -- iterates over all nodes, finds those with GRE tunnel interfaces, and validates device feature support (basic GRE, transport AF, and VRF). Calls `api.node_config(ndata, 'tunnel.gre')` for each node that passes all checks. +2. **`_p2p.tunnel_source(topology, node_iflist, ...)`** -- for each node with tunnel interfaces, resolves the source interface using `_tunnel.get_tunnel_source()` and populates `tunnel._source` via `_tunnel.set_tunnel_source()`. Sets `tunnel.af` to `ipv4` as default when not specified. +3. **`_p2p.tunnel_destination(topology, node_iflist, ...)`** -- for each tunnel interface, copies the neighbor's `tunnel._source` to the local `tunnel._destination`. + +If any errors are detected during source resolution, the destination resolution step is skipped. + +## Configuration Templates + +### FRR / Linux + +Creates GRE or ip6gre tunnels using the `ip tunnel` command. Selects `gre` or `ip6gre` mode based on the `tunnel.af` attribute: + +```bash +{%- for intf in netlab_interfaces if intf.tunnel.mode|default('') in ['gre'] %} +{% set af = intf.tunnel.af|default('ipv4') %} +{% set gre_mode = 'ip6gre' if af == 'ipv6' else 'gre' %} +ip tunnel add {{ intf.ifname }} mode {{ gre_mode }} \ + local {{ intf.tunnel._source[af] }} remote {{ intf.tunnel._destination[af] }} \ + dev {{ intf.tunnel._source.ifname }} ttl 255 +{{ linux_intf_sysctl(intf,role) }} +{% endfor %} +``` + +### Cisco IOS / IOS-XE + +Configures the tunnel mode (gre ip or gre ipv6), optional transport VRF, source interface, and destination address: + +```jinja +{% for intf in netlab_interfaces if intf.tunnel.mode|default('') in ['gre'] %} +interface {{ intf.ifname }} + tunnel mode {{ intf.tunnel.mode }} {{ intf.tunnel.af.replace('ipv4','ip') }} +{% if intf.tunnel.vrf is defined %} + tunnel vrf {{ intf.tunnel.vrf }} +{% endif %} + tunnel source {{ intf.tunnel._source.ifname }} + tunnel destination {{ intf.tunnel._destination[intf.tunnel.af] }} +{% endfor %} +``` + +The `tunnel.af.replace('ipv4','ip')` expression maps `ipv4` to `ip` and leaves `ipv6` as-is, matching IOS syntax. + +### VyOS + +Requires two templates: + +**Initial** (`vyos.initial.j2`) -- creates the tunnel interface with encapsulation, source-address, and remote address: + +```jinja +{% for intf in netlab_interfaces if intf.tunnel.mode|default('') in ['gre'] %} +set interfaces tunnel {{ intf.ifname }} encapsulation 'gre' +{% if intf.tunnel.af == 'ipv6' %} +set interfaces tunnel {{ intf.ifname }} encapsulation 'ip6gre' +{% endif %} +set interfaces tunnel {{ intf.ifname }} source-address {{ intf.tunnel._source[intf.tunnel.af] }} +set interfaces tunnel {{ intf.ifname }} remote {{ intf.tunnel._destination[intf.tunnel.af] }} +{% endfor %} +``` + +**Main** (`vyos.j2`) -- sets the source interface, which also controls outer VRF selection: + +```jinja +{% for intf in netlab_interfaces if intf.tunnel.mode|default('') in ['gre'] %} +set interfaces tunnel {{ intf.ifname }} source-interface {{ intf.tunnel._source.ifname }} +{% endfor %} +``` + +The `netsim/extra/tunnel/gre/vyos.initial.j2` template is included in the initial configuration template via the `extra_initial` macro: + +```jinja +{% from '_extra_initial.j2' import extra_initial with context %} +{# + Include interface-related parts of module/plugin configuration, including + VLANs, VRFs, and tunnels +#} +{{ extra_initial() }} +``` + +## Test Cases + +Integration tests in `tests/integration/tunnel/`: + +* `01-gre.yml` -- GRE tunnels over IPv4 and IPv6 transport with OSPFv2/OSPFv3 running over the tunnels. Uses FRR containers to verify OSPF adjacencies, prefix propagation, and end-to-end ping. +* `02-gre-vrf.yml` -- GRE tunnels with VRF transport underlay, verifying that GRE works over a VRF-based transport network. + +Coverage tests in `tests/coverage/`: + +* `input/gre-source.yml` -- tests automatic tunnel source interface selection with `tunnel.source.link.name` and `tunnel.source.link.role` options. +* `errors/gre-support.yml` -- tests error conditions: unsupported device, unsupported transport AF, unsupported VRF, and missing tunnel source.