#!/usr/bin/env python3 import unittest import scapy.compat from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q, GRE from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6 from scapy.volatile import RandMAC, RandIP from framework import tag_fixme_vpp_workers from framework import VppTestCase, VppTestRunner from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint from vpp_gre_interface import VppGreInterface from vpp_teib import VppTeib from vpp_ip import DpoProto from vpp_ip_route import ( VppIpRoute, VppRoutePath, VppIpTable, FibPathProto, VppMplsLabel, ) from vpp_mpls_tunnel_interface import VppMPLSTunnelInterface from util import ppp, ppc from vpp_papi import VppEnum @tag_fixme_vpp_workers class TestGREInputNodes(VppTestCase): """GRE Input Nodes Test Case""" def setUp(self): super(TestGREInputNodes, self).setUp() # create 3 pg interfaces - set one in a non-default table. self.create_pg_interfaces(range(1)) for i in self.pg_interfaces: i.admin_up() i.config_ip4() def tearDown(self): for i in self.pg_interfaces: i.unconfig_ip4() i.admin_down() super(TestGREInputNodes, self).tearDown() def test_gre_input_node(self): """GRE gre input nodes not registerd unless configured""" pkt = ( Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / GRE() ) self.pg0.add_stream(pkt) self.pg_start() # no tunnel created, gre-input not registered err = self.statistics.get_counter("/err/ip4-local/unknown_protocol")[0] self.assertEqual(err, 1) err_count = err # create gre tunnel gre_if = VppGreInterface(self, self.pg0.local_ip4, "1.1.1.2") gre_if.add_vpp_config() self.pg0.add_stream(pkt) self.pg_start() # tunnel created, gre-input registered err = self.statistics.get_counter("/err/ip4-local/unknown_protocol")[0] # expect no new errors self.assertEqual(err, err_count) class TestGRE(VppTestCase): """GRE Test Case""" @classmethod def setUpClass(cls): super(TestGRE, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestGRE, cls).tearDownClass() def setUp(self): super(TestGRE, self).setUp() # create 3 pg interfaces - set one in a non-default table. self.create_pg_interfaces(range(5)) self.tbl = VppIpTable(self, 1) self.tbl.add_vpp_config() self.pg1.set_table_ip4(1) for i in self.pg_interfaces: i.admin_up() self.pg0.config_ip4() self.pg0.resolve_arp() self.pg1.config_ip4() self.pg1.resolve_arp() self.pg2.config_ip6() self.pg2.resolve_ndp() self.pg3.config_ip4() self.pg3.resolve_arp() self.pg4.config_ip4() self.pg4.resolve_arp() def tearDown(self): for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.admin_down() self.pg1.set_table_ip4(0) super(TestGRE, self).tearDown() def create_stream_ip4(self, src_if, src_ip, dst_ip, dscp=0, ecn=0): pkts = [] tos = (dscp << 2) | ecn for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=src_ip, dst=dst_ip, tos=tos) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_stream_ip6(self, src_if, src_ip, dst_ip, dscp=0, ecn=0): pkts = [] tc = (dscp << 2) | ecn for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=src_ip, dst=dst_ip, tc=tc) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_tunnel_stream_4o4(self, src_if, tunnel_src, tunnel_dst, src_ip, dst_ip): pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / GRE() / IP(src=src_ip, dst=dst_ip) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_tunnel_stream_6o4(self, src_if, tunnel_src, tunnel_dst, src_ip, dst_ip): pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / GRE() / IPv6(src=src_ip, dst=dst_ip) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_tunnel_stream_6o6(self, src_if, tunnel_src, tunnel_dst, src_ip, dst_ip): pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IPv6(src=tunnel_src, dst=tunnel_dst) / GRE() / IPv6(src=src_ip, dst=dst_ip) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_tunnel_stream_l2o4(self, src_if, tunnel_src, tunnel_dst): pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / GRE() / Ether(dst=RandMAC("*:*:*:*:*:*"), src=RandMAC("*:*:*:*:*:*")) / IP(src=scapy.compat.raw(RandIP()), dst=scapy.compat.raw(RandIP())) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def create_tunnel_stream_vlano4(self, src_if, tunnel_src, tunnel_dst, vlan): pkts = [] for i in range(0, 257): info = self.create_packet_info(src_if, src_if) payload = self.info_to_payload(info) p = ( Ether(dst=src_if.local_mac, src=src_if.remote_mac) / IP(src=tunnel_src, dst=tunnel_dst) / GRE() / Ether(dst=RandMAC("*:*:*:*:*:*"), src=RandMAC("*:*:*:*:*:*")) / Dot1Q(vlan=vlan) / IP(src=scapy.compat.raw(RandIP()), dst=scapy.compat.raw(RandIP())) / UDP(sport=1234, dport=1234) / Raw(payload) ) info.data = p.copy() pkts.append(p) return pkts def verify_tunneled_4o4( self, src_if, capture, sent, tunnel_src, tunnel_dst, dscp=0, ecn=0 ): self.assertEqual(len(capture), len(sent)) tos = (dscp << 2) | ecn for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) self.assertEqual(rx_ip.tos, tos) self.assertEqual(rx_ip.len, len(rx_ip)) rx_gre = rx[GRE] rx_ip = rx_gre[IP] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_tunneled_6o6( self, src_if, capture, sent, tunnel_src, tunnel_dst, dscp=0, ecn=0 ): self.assertEqual(len(capture), len(sent)) tc = (dscp << 2) | ecn for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IPv6] rx_ip = rx[IPv6] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) self.assertEqual(rx_ip.tc, tc) rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload)) self.assertEqual(rx_ip.plen, len(rx_gre)) rx_ip = rx_gre[IPv6] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_tunneled_4o6(self, src_if, capture, sent, tunnel_src, tunnel_dst): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] rx_ip = rx[IPv6] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) rx_gre = GRE(scapy.compat.raw(rx_ip[IPv6].payload)) self.assertEqual(rx_ip.plen, len(rx_gre)) tx_ip = tx[IP] rx_ip = rx_gre[IP] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_tunneled_6o4(self, src_if, capture, sent, tunnel_src, tunnel_dst): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] rx_ip = rx[IP] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) self.assertEqual(rx_ip.len, len(rx_ip)) rx_gre = GRE(scapy.compat.raw(rx_ip[IP].payload)) rx_ip = rx_gre[IPv6] tx_ip = tx[IPv6] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_tunneled_l2o4(self, src_if, capture, sent, tunnel_src, tunnel_dst): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) self.assertEqual(rx_ip.len, len(rx_ip)) rx_gre = rx[GRE] rx_l2 = rx_gre[Ether] rx_ip = rx_l2[IP] tx_gre = tx[GRE] tx_l2 = tx_gre[Ether] tx_ip = tx_l2[IP] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) # bridged, not L3 forwarded, so no TTL decrement self.assertEqual(rx_ip.ttl, tx_ip.ttl) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_tunneled_vlano4( self, src_if, capture, sent, tunnel_src, tunnel_dst, vlan ): try: self.assertEqual(len(capture), len(sent)) except: ppc("Unexpected packets captured:", capture) raise for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] self.assertEqual(rx_ip.src, tunnel_src) self.assertEqual(rx_ip.dst, tunnel_dst) rx_gre = rx[GRE] rx_l2 = rx_gre[Ether] rx_vlan = rx_l2[Dot1Q] rx_ip = rx_l2[IP] self.assertEqual(rx_vlan.vlan, vlan) tx_gre = tx[GRE] tx_l2 = tx_gre[Ether] tx_ip = tx_l2[IP] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) # bridged, not L3 forwarded, so no TTL decrement self.assertEqual(rx_ip.ttl, tx_ip.ttl) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_decapped_4o4(self, src_if, capture, sent): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IP] tx_gre = tx[GRE] tx_ip = tx_gre[IP] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) # IP processing post pop has decremented the TTL self.assertEqual(rx_ip.ttl + 1, tx_ip.ttl) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_decapped_6o4(self, src_if, capture, sent): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IP] rx_ip = rx[IPv6] tx_gre = tx[GRE] tx_ip = tx_gre[IPv6] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def verify_decapped_6o6(self, src_if, capture, sent): self.assertEqual(len(capture), len(sent)) for i in range(len(capture)): try: tx = sent[i] rx = capture[i] tx_ip = tx[IPv6] rx_ip = rx[IPv6] tx_gre = tx[GRE] tx_ip = tx_gre[IPv6] self.assertEqual(rx_ip.src, tx_ip.src) self.assertEqual(rx_ip.dst, tx_ip.dst) self.assertEqual(rx_ip.hlim + 1, tx_ip.hlim) except: self.logger.error(ppp("Rx:", rx)) self.logger.error(ppp("Tx:", tx)) raise def test_gre(self): """GRE IPv4 tunnel Tests""" # # Create an L3 GRE tunnel. # - set it admin up # - assign an IP Addres # - Add a route via the tunnel # gre_if = VppGreInterface(self, self.pg0.local_ip4, "1.1.1.2") gre_if.add_vpp_config() # # The double create (create the same tunnel twice) should fail, # and we should still be able to use the original # try: gre_if.add_vpp_config() except Exception: pass else: self.fail("Double GRE tunnel add does not fail") gre_if.admin_up() gre_if.config_ip4() route_via_tun = VppIpRoute( self, "4.4.4.4", 32, [VppRoutePath("0.0.0.0", gre_if.sw_if_index)] ) route_via_tun.add_vpp_config() # # Send a packet stream that is routed into the tunnel # - they are all dropped since the tunnel's destintation IP # is unresolved - or resolves via the default route - which # which is a drop. # tx = self.create_stream_ip4(self.pg0, "5.5.5.5", "4.4.4.4") self.send_and_assert_no_replies(self.pg0, tx) # # Add a route that resolves
# BFD module    {#bfd_doc}

## Overview

Bidirectional Forwarding Detection in VPP currently supports single-hop UDP
transport based on RFC 5880 and RFC 5881.

## Usage

### General usage

BFD sessions are created using APIs only. The following CLIs are implemented,
which call the APIs to manipulate the BFD:

#### Show commands:

> show bfd [keys|sessions|echo-source]

Show the existing keys, sessions or echo-source.

#### Key manipulation

##### Create a new key or modify an existing key

> bfd key set conf-key-id <id> type <keyed-sha1|meticulous-keyed-sha1> secret <secret>

Parameters:

* conf-key-id     - local configuration key ID, used to uniquely identify this key
* type            - type of the key
* secret          - shared secret (hex data)

Example:

> bfd key set conf-key-id 2368880803 type meticulous-keyed-sha1 secret 69d685b0d990cdba46872706dc

Notes:

* in-use key cannot be modified

##### Delete an existing key

> bfd key del conf-key-id <id>

Parameters:

* conf-key-id     - local configuration key ID, used to uniquely identify this key

Example:

> bfd key del conf-key-id 2368880803

Notes:

*  in-use key cannot be deleted

##### Create a new (plain or authenticated) BFD session

> bfd udp session add interface <interface> local-addr <address> peer-addr <address> desired-min-tx <interval> required-min-rx <interval> detect-mult <multiplier> [ conf-key-id <ID> bfd-key-id <ID> ]

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)
* desired-min-tx  - desired minimum tx interval (microseconds)
* required-min-rx - required minimum rx interval (microseconds)
* detect-mult     - detect multiplier (must be non-zero)
* conf-key-id     - local configuration key ID
* bfd-key-id      - BFD key ID, as carried in BFD control frames

Example:

> bfd udp session add interface pg0 local-addr fd01:1::1 peer-addr fd01:1::2 desired-min-tx 100000 required-min-rx 100000 detect-mult 3 conf-key-id 1029559112 bfd-key-id 13

Notes:

* if conf-key-id and bfd-key-id are not specified, session is non-authenticated
* desired-min-tx controls desired transmission rate of both control frames and echo packets

##### Modify BFD session

> bfd udp session mod interface <interface> local-addr <address> peer-addr <address> desired-min-tx <interval> required-min-rx <interval> detect-mult <multiplier>

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)
* desired-min-tx  - desired minimum tx interval (microseconds)
* required-min-rx - required minimum rx interval (microseconds)
* detect-mult     - detect multiplier (must be non-zero)

Example:

> bfd udp session mod interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 desired-min-tx 300000 required-min-rx 200000 detect-mult 12

Notes:

* desired-min-tx controls desired transmission rate of both control frames and echo packets

##### Delete an existing BFD session

> bfd udp session del interface <interface> local-addr <address> peer-addr<address>

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)

Example:

> bfd udp session del interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2

##### Set session admin-up or admin-down

> bfd udp session set-flags interface <interface> local-addr <address> peer-addr <address> admin <up|down>

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)
* admin           - up/down based on desired action

Example:

> bfd udp session set-flags admin down interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2

##### Activate/change authentication for existing session

> bfd udp session auth activate interface <interface> local-addr <address> peer-addr <address> conf-key-id <ID> bfd-key-id <ID> [ delayed <yes|no> ]

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)
* conf-key-id     - local configuration key ID
* bfd-key-id      - BFD key ID, as carried in BFD control frames
* delayed         - is yes then this action is delayed until the peer performs the same action

Example:

> bfd udp session auth activate interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2 conf-key-id 540928695 bfd-key-id 239 delayed yes

Notes:

* see [Delayed option] for more information

##### Deactivate authentication for existing session

> bfd udp session auth deactivate interface <interface> local-addr <address> peer-addr <address> [ delayed <yes|no> ]

Parameters:

* interface       - interface to which this session is tied to
* local-addr      - local address (ipv4 or ipv6)
* peer-addr       - peer address (ipv4 or ipv6, must match local-addr family)
* delayed         - is yes then this action is delayed until the peer performs the same action

Example:

> bfd udp session auth deactivate interface pg0 local-addr 172.16.1.1 peer-addr 172.16.1.2

Notes:

* see [Delayed option] for more information

##### Set echo-source interface

> bfd udp echo-source set interface <interface>

Parameters:

* interface       - interface used for getting source address for echo packets

Example:

> bfd udp echo-source set interface loop0

##### Delete echo-source interface

> bfd udp echo-source del

Example:

> bfd udp echo-source del

### Authentication

BFD sessions should be authenticated for security purposes. SHA1 and meticulous
SHA1 authentication is supported by VPP. First, authentication keys are
configured in VPP and afterwards they can be used by sessions.

There are two key IDs in the scope of BFD session:

* configuration key ID is the internal unique key ID inside VPP and is never
  communicated to any peer, it serves only the purpose of identifying the key
* BFD key ID is the key ID carried in BFD control frames and is used for
  verifying authentication

#### Turning auth on/off

Authentication can be turned on or off at any time. Care must be taken however,
to either synchronize the authentication manipulation with peer's actions
to avoid the session going down.

##### Delayed option

Delayed option is useful for synchronizing authentication changes with a peer.
If it's specified, then authentication change is not performed immediately.
In this case, VPP continues to transmit packets using the old authentication
method (unauthenticated or using old sha1 key). If a packet is received, which
does not pass the current authentication, then VPP tries to authenticate it
using the new method (which might be none, if deactivating authentication)
and if it passes, then the new authentication method is put in use.

The recommended procedure for enabling/changing/disabling session 
authentication is:

1. perform authentication change on vpp's side with delayed option set to yes
2. perform authentication change on peer's side (without delayed option)

Notes:

* if both peers use delayed option at the same time, the change will never
  be carried out, since none of the peers will see any packet with the new
  authentication which could trigger the change
* remote peer does not need to support or even be aware of this mechanism
  for it to work properly


### Echo function

Echo function is used by VPP whenever a peer declares the willingness
to support it, echo-source is set and it contains a usable subnet (see below).
When echo function is switched on, the required min rx interval advertised
to peer is set to 1 second (or the configured value, if its higher).

#### Echo source address

Because echo packets are only looped back (and not processed in any way)
by a peer, it's necessary to set the source address in a way which avoids
packet drop due to spoofing protection by VPP. Per RFC, the source address
should not be in the subnet set on the interface over which the echo packets
are sent. Also, it must not be any VPP-local address, otherwise the packet
gets dropped on receipt by VPP. The solution is to create a loopback interface
with a (private) IPv4/IPv6 subnet assigned as echo-source. The BFD then picks
an unused address from the subnet by flipping the last bit and uses that as
source address in the echo packets, thus meeting RFC recommendation while
avoiding spoofing protection.

Example: if 10.10.10.3/31 is the subnet, then 10.10.10.2 will be used as
         source address in (IPv4) echo packets

### Demand mode

Demand mode is respected by VPP, but not used locally. The only scenario when
demand mode could make sense currently is when echo is active. Because echo
packets are inherently insecure against an adversary looping them back a poll
sequence would be required for slow periodic connectivity verification anyway.
It's more efficient to just ask the remote peer to send slow periodic control
frames without VPP initiating periodic poll sequences.

### Admin-down

Session may be put admin-down at any time. This immediately causes the state
to be changed to AdminDown and remain so unless the session is put admin-up.

## BFD implementation notes

Because BFD can work over different transport layers, the BFD code is separated
into core BFD functionality - main module implemented in bfd_main.c
and transport-specific code implemented in bfd_udp.c.

### Main module

Main module is responsible for handling all the BFD functionality defined
in RFC 5880.

#### Internal API

Internal APIs defined in bfd_main.h are called from transport-specific code
to create/modify/delete

#### Packet receipt

When a packet is received by the transport layer, it is forwarded to main
module (to main thread) via an RPC call. At this point, the authentication has
been verified, so the packet is consumed, session parameters are updated
accordingly and state change (if applicable). Based on these, the timeouts
are adjusted if required and an event is sent to the process node to wake up
and recalculate sleep time.

#### Packet transmit

Main module allocates a vlib_buffer_t, creates the required BFD frame (control
or echo in it), then calls the transport layer to add the transport layer.
Then a frame containing the buffer to the aprropriate node is created
and enqueued.

#### Process node

Main module implements one process node which is a simple loop. The process
node gets next timeout from the timer wheel, sleeps until the timeout expires
and then calls a timeout routine which drives the state machine for each
session which timed out. The sleep is interrupted externally via vlib event,
when a session is added or modified in a way which might require timer wheel
manipulation. In this case the caller inserts the necessary timeout to timer
wheel and then signals the process node to wake up early, handle possible
timeouts and recalculate the sleep time again.

#### State machine

Default state of BFD session when created is Down, per RFC 5880. State changes
to Init, Up or Down based on events like received state from peer and timeouts.
The session state can be set AdminDown using a binary API, which prevents it
from going to any other state, until this limitation is removed. This state
is advertised to peers in slow periodic control frames.

For each session, the following timeouts are maintained:

1. tx timeout - used for sending out control frames
2. rx timeout - used for detecting session timeout
3. echo tx timeout - used for sending out echo frames
3. echo rx timeout - used for detecting session timeout based on echo

These timeouts are maintained in cpu clocks and recalculated when appropriate
(e.g. rx timeout is bumped when a packet is received, keeping the session
alive). Only the earliest timeout is inserted into the timer wheel at a time
and timer wheel events are never deleted, rather spurious events are ignored.
This allows efficient operation, like not inserting events into timing wheel
for each packet received or ignoring left-over events in case a bfd session
gets removed and a new one is recreated with the same session index.

#### Authentication keys management

Authentication keys are managed internally in a pool, with each key tracking
it's use count. The removal/modification is only allowed if the key is not in
use.

### UDP module

UDP module is responsible for:

1. public APIs/CLIs to configure BFD over UDP.
2. support code cal