# Generic Routing Tools Configuration Templates

This document describes the implementation details of the [](generic-routing):

* [Platform capabilities](dev-routing-platform)
* [Prefix filter data structure](dev-routing-prefix)
* [Routing policy data structure](dev-routing-policy)

Still missing:

* BGP AS-path filter data structure
* BGP community filter data structure
* Static routing data structure

(dev-routing-platform)=
## Platform Capabilities

The routing policy/filtering capabilities of individual devices are specified in the device **features.routing** dictionary. That dictionary contains a key for every component supported by the **routing** module:

* **policy** -- routing policy capabilities
* **prefix** -- prefix filters (boolean)
* **aspath** -- AS path filters (boolean)
* **community** -- BGP community filter capabilities. **expanded** key indicates that the device can use regular expressions to match BGP communities.

The **policy** capability dictionary has two elements:

* **match** -- attributes the device can use in **match** conditions (prefix, nexthop, aspath, community)
* **set** -- attributes the device can use in **set** parameters (locpref, med, weight, prepend, community)

The **match** and **set** values could be a list of supported attributes or a dictionary of attributes in case you need to specify the capabilities of individual attributes.

When using the dictionary format, set the supported attribute values to **true**; the only **set** attribute that might need more details is the **community** attribute.

**set.community** can be a dictionary with the following values:

* **standard** -- device can change **standard** communities
* **large** -- device can change **large** communities
* **extended** -- device can change **extended** communities
* **append** -- device can append communities to a BGP route

For example, this is the capability definition for a device that can only set MED and local preference values:

```
features:
  routing:
    policy:
      set: [ locpref, med ]
```

This is the FRRouting capability definition -- it can do almost everything supported by the routing module:

```
  routing:
    policy:
      set:
        locpref: True
        med: True
        weight: True
        prepend: True
        community:
          standard: True
          large: True
          extended: True
          append: True
      match: [ prefix, nexthop, aspath, community ]
    prefix: True
    aspath: True
    community:
      expanded: True
```

(dev-routing-prefix)=
## Prefix Filters Data Structure

The [](generic-routing-prefixes) are transformed into the **routing.prefix** dictionary:

* The keys are the prefix filter names
* The values are lists of prefix filter permit/deny conditions

Each entry in a prefix filter list contains these attributes:

* **action** -- `permit` or `deny`
* **sequence** -- sequence number.
* **ipv4** (optional) -- IPv4 prefix to match
* **ipv6** (optional) -- IPv6 prefix to match
* **min** (optional) -- Minimum prefix length to match. A dictionary with optional **ipv4** and **ipv6** attributes
* **max** (optional) -- Maximum prefix length to match. A dictionary with optional **ipv4** and **ipv6** attributes

```{tip}
The prefix filter entries are sorted by their sequence numbers. If your platform does not require sequence numbers in prefix filters, you can ignore the **‌sequence** attribute.
```

For example, the following prefix filters (using named prefixes and pools)...

```
routing.prefix:
  orig_1:
  - prefix: b_orig_1
  lb_only:
  - pool: loopback
    min:
      ipv4: 32
      ipv6: 64
    max:
      ipv4: 32
      ipv6: 64
```

... are transformed into the following data structure:

```
routing.prefix:
  lb_only:
  - action: permit
    ipv4: 10.0.0.0/24
    ipv6: 2001:db8:1::/48
    max:
      ipv4: 32
      ipv6: 64
    min:
      ipv4: 32
      ipv6: 64
    sequence: 10
  orig_1:
  - action: permit
    ipv4: 172.42.42.0/24
    sequence: 10
```

Many platforms cannot match IPv4 and IPv6 prefixes in the same prefix filter. _netlab_ generates an additional **routing._prefix** dictionary for those platforms, extracting the IPv4 and IPv6 components of prefix filters into separate data structures. Here's the **routing._prefix** data for the same example:

```
routing._prefix:
  ipv4:
    lb_only:
    - action: permit
      ipv4: 10.0.0.0/24
      max: 32
      min: 32
      sequence: 10
    orig_1:
    - action: permit
      ipv4: 172.42.42.0/24
      sequence: 10
  ipv6:
    lb_only:
    - action: permit
      ipv6: 2001:db8:1::/48
      max: 64
      min: 64
      sequence: 10
    orig_1:
    - action: deny
      ipv6: ::/0
      sequence: 10
```

It's straightforward to use the **routing._prefix** data to generate IPv4 and IPv6 prefix lists:

* Iterate over the address families. Use the node-level **af** attribute to iterate over address families used by a node.
* Iterate over **routing._prefix[afm]** items
* Create **prefix-list** entries.

Here's the template used to generate prefix lists for platforms using *industry-standard*[^IOL] prefix lists:

```
{%- macro min_max(p_entry) -%}
{%-  if 'min' in p_entry %} ge {{ p_entry.min }}{% endif -%}
{%-  if 'max' in p_entry %} le {{ p_entry.max }}{% endif -%}
{%- endmacro -%}
!
{% for pf_af in af if pf_af in routing._prefix|default({}) %}
{%   for p_name,p_value in routing._prefix[pf_af].items() %}
!
{%     for p_entry in p_value %}
{%.      af_kw = 'ip' if pf_af == 'ipv4' else pf_af %}
{{ af_kw }} prefix-list {{ p_name
  }}-{{ pf_af }} seq {{ p_entry.sequence }} {{ p_entry.action
  }} {{ p_entry[pf_af] }}{{ min_max(p_entry) }}
{%     endfor %}
{%   endfor %}
{% endfor %}
```

Notes:

* The template generates two prefix lists: *name*-ipv4 and *name*-ipv6. We have to use distinct names because some platforms don't like having IPv4 and IPv6 prefix lists with the same name.
* We need the **af_kw** variable because Cisco IOS CLI uses **ip prefix-list** and **ipv6 prefix-list**
* The **min_max** macro is used to make the generation of **ge**/**le** keywords a bit more readable.

[^IOL]: An euphemism for *we copied Cisco IOS CLI, but don't want to call it that way to avoid Cisco lawyers*.

The *address family* complexity can be avoided on platforms that can match IPv4 and IPv6 prefixes in the same prefix filter. Here's the SR Linux template:

```
{% for pf_name,pf_list in routing.prefix|default({})|items %}
- path: /routing-policy/prefix-set[name={{ pf_name }}]
  value:
    prefix:
{%   for p_entry in pf_list %}{# Iterate over prefix list entries #}
{%     for p_af in af if p_af in p_entry %}{# Iterate over address families in the prefix list entry #}
    - ip-prefix: {{ p_entry[p_af] }}
{%     if p_entry.min[p_af] is defined or p_entry.max[p_af] is defined %}
      mask-length-range: {{ 
        p_entry.min[p_af]|default(p_entry[p_af]|ipaddr('prefix')) }}..{{ 
        p_entry.max[p_af]|default(32 if p_af == 'ipv4' else 128) }}
{%     else %}
      mask-length-range: exact
{%     endif %}
{%     endfor %}
{%   endfor %}
{% endfor %}
```

Notes:

* The SR Linux prefix sets don't have **sequence** or **deny** options (the lack of **deny** action is handled as a device quirk)
* SR Linux uses **ip-prefix** attribute to specify both IPv4 and IPv6 prefixes, so we have to iterate over address families to generate two list entries when a single prefix list item contains **ipv4** and **ipv6** values.
* The **mask-length-range** parameter must be in the **a..b** format, so we have to specify the default minimum and maximum lengths when they're missing.

(dev-routing-policy)=
## Routing Policy Data Structure

The [](generic-routing-policies) are transformed into the **routing.policy** dictionary:

* The keys are the routing policy (route map) names
* The values are lists of routing policy (route map) statements (match/set values)

Each entry in a routing policy list contains these attributes:

* **action** -- `permit` or `deny`
* **sequence** -- sequence number.
* **set** -- a dictionary of **set** actions
* **match** -- a dictionary of **match** conditions

```{tip}
The routing policy entries are sorted by their sequence numbers. If your platform does not require sequence numbers in route maps, you can ignore the **‌sequence** attribute.
```

The **match** conditions in a routing policy entry include:

* **prefix** -- match the route with an IPv4 or IPv6 prefix filter
* **aspath** -- match a BGP AS-path with an AS-path filter
* **nexthop** -- match the route next hop with an IPv4 or IPv6 prefix filter
* **community** -- match BGP communities with a BGP community filter

The **set** actions include:

* **locpref** -- set local preference
* **med** -- set route metric (usually used to set BGP MED attribute)
* **weight** -- set BGP weight
* **prepend** -- do BGP AS-path prepending
* **community** -- change BGP communities attached to a route