Skip to content

Commit

Permalink
Implement software version capability for BGP
Browse files Browse the repository at this point in the history
https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability

This is already implemented by FRR, GoBGP, freertr, Wireshark, some more.

E.g.:

```
$ vtysh -c 'show bgp neighbor 127.0.0.1 json' | jq '."127.0.0.1".neighborCapabilities.softwareVersion'
{
  "advertisedSoftwareVersion": "FRRouting/10.2-dev-MyOwnFRRVersion-g8ca262943f",
  "receivedSoftwareVersion": "ExaBGP/feature/bgp_software_version_capability-e510e8c6b4d6c8651"
}
```

Signed-off-by: Donatas Abraitis <[email protected]>
  • Loading branch information
ton31337 committed Jul 15, 2024
1 parent b7304a7 commit d21e432
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/exabgp/bgp/message/open/capability/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from exabgp.bgp.message.open.capability.refresh import EnhancedRouteRefresh
from exabgp.bgp.message.open.capability.extended import ExtendedMessage
from exabgp.bgp.message.open.capability.hostname import HostName
from exabgp.bgp.message.open.capability.software_version import SoftwareVersion

from exabgp.bgp.message.notification import Notify

Expand Down Expand Up @@ -142,6 +143,12 @@ def _extended_message(self, neighbor):
def _hostname(self, neighbor):
self[Capability.CODE.HOSTNAME] = HostName(neighbor['host-name'], neighbor['domain-name'])

def _software_version(self, neighbor):
if not neighbor['capability']['software-version']:
return

self[Capability.CODE.SOFTRWARE_VERSION] = SoftwareVersion()

def _operational(self, neighbor):
if not neighbor['capability']['operational']:
return
Expand All @@ -163,6 +170,7 @@ def new(self, neighbor, restarted):
self._operational(neighbor)
self._extended_message(neighbor)
self._hostname(neighbor) # https://datatracker.ietf.org/doc/html/draft-walton-bgp-hostname-capability-02
self._software_version(neighbor) # https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability
self._session(neighbor) # MUST be the last key added, really !?! dict is not ordered !
return self

Expand Down
3 changes: 3 additions & 0 deletions src/exabgp/bgp/message/open/capability/capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class _CapabilityCode(int):
MULTISESSION_CISCO = 0x83 # What Cisco really use for Multisession (yes this is a reserved range in prod !)

HOSTNAME = 0x49 # https://datatracker.ietf.org/doc/html/draft-walton-bgp-hostname-capability-02
SOFTWARE_VERSION = 0x4B # https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability
OPERATIONAL = 0xB9 # ExaBGP only ...

# Internal
Expand All @@ -61,6 +62,7 @@ class _CapabilityCode(int):
MULTISESSION_CISCO: 'cisco-multi-sesion',
AIGP: 'aigp',
HOSTNAME: 'hostname',
SOFTWARE_VERSION: 'software-version',
}

def __new__(cls, value):
Expand Down Expand Up @@ -105,6 +107,7 @@ class CODE(int):
ROUTE_REFRESH_CISCO = _CapabilityCode(_CapabilityCode.ROUTE_REFRESH_CISCO)
MULTISESSION_CISCO = _CapabilityCode(_CapabilityCode.MULTISESSION_CISCO)
HOSTNAME = _CapabilityCode(_CapabilityCode.HOSTNAME)
SOFTRWARE_VERSION = _CapabilityCode(_CapabilityCode.SOFTWARE_VERSION)
OPERATIONAL = _CapabilityCode(_CapabilityCode.OPERATIONAL)
AIGP = _CapabilityCode(_CapabilityCode.AIGP)
# fmt: on
Expand Down
40 changes: 40 additions & 0 deletions src/exabgp/bgp/message/open/capability/software_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# encoding: utf-8
"""
software_version.py
Copyright (c) 2024 Donatas Abraitis. All rights reserved.
License: 3-clause BSD. (See the COPYRIGHT file)
"""

# https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability


from exabgp.bgp.message.open.capability.capability import Capability
from exabgp.version import version


@Capability.register()
class SoftwareVersion(Capability):
ID = Capability.CODE.SOFTRWARE_VERSION
SOFTWARE_VERSION_MAX_LEN = 64

def __init__(self):
self.software_version = f"ExaBGP/{version}"

def __str__(self):
return f'SoftwareVersion({self.software_version})'

def json(self):
return f'{ "software-version": "{self.software_version}" }'

def extract(self):
software_version = self.software_version.encode('utf-8')
if len(software_version) > self.SOFTWARE_VERSION_MAX_LEN:
software_version = software_version[: self.SOFTWARE_VERSION_MAX_LEN]
return [bytes([len(software_version)]) + software_version]

@staticmethod
def unpack_capability(instance, data, capability=None): # pylint: disable=W0613
l1 = data[0]
instance.software_version = data[1 : l1 + 1].decode('utf-8')
return instance
5 changes: 4 additions & 1 deletion src/exabgp/bgp/neighbor.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class Capability(dict):
'route-refresh': 0,
'nexthop': None,
'aigp': None,
'software-version': None,
}

defaults = {
Expand Down Expand Up @@ -406,7 +407,7 @@ def string(self, with_changes=True):
'\tmanual-eor %s;\n'
'%s%s%s%s%s%s%s%s%s%s%s\n'
'\tcapability {\n'
'%s%s%s%s%s%s%s%s%s\t}\n'
'%s%s%s%s%s%s%s%s%s%s\t}\n'
'\tfamily {%s\n'
'\t}\n'
'\tnexthop {%s\n'
Expand Down Expand Up @@ -446,6 +447,8 @@ def string(self, with_changes=True):
'\t\troute-refresh %s;\n' % ('enable' if self['capability']['route-refresh'] else 'disable'),
'\t\tgraceful-restart %s;\n'
% (self['capability']['graceful-restart'] if self['capability']['graceful-restart'] else 'disable'),
'\t\tsoftware-version %s;\n'
% ('enable' if self['capability']['software-version'] else 'disable'),
'\t\tnexthop %s;\n' % ('enable' if self['capability']['nexthop'] else 'disable'),
'\t\tadd-path %s;\n'
% (AddPath.string[self['capability']['add-path']] if self['capability']['add-path'] else 'disable'),
Expand Down
4 changes: 4 additions & 0 deletions src/exabgp/configuration/capability.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class ParseCapability(Section):
' operational enable|disable;\n'
' refresh enable|disable;\n'
' extended-message enable|disable;\n'
' software-version enable|disable;\n'
'}\n'
)

Expand All @@ -84,6 +85,7 @@ class ParseCapability(Section):
'route-refresh': boolean,
'aigp': boolean,
'extended-message': boolean,
'software-version': boolean,
}

action = {
Expand All @@ -96,6 +98,7 @@ class ParseCapability(Section):
'route-refresh': 'set-command',
'aigp': 'set-command',
'extended-message': 'set-command',
'software-version': 'set-command',
}

default = {
Expand All @@ -107,6 +110,7 @@ class ParseCapability(Section):
'route-refresh': True,
'aigp': True,
'extended-message': True,
'software-version': False,
}

name = 'capability'
Expand Down
1 change: 1 addition & 0 deletions src/exabgp/configuration/neighbor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from exabgp.configuration.neighbor.parser import description
from exabgp.configuration.neighbor.parser import inherit
from exabgp.configuration.neighbor.parser import rate_limit
from exabgp.version import version

from exabgp.environment import getenv

Expand Down
3 changes: 2 additions & 1 deletion tests/decode_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from exabgp.bgp.message.update.nlri import NLRI

from exabgp.bgp.message.direction import Direction

from exabgp.logger import log
from exabgp.environment import getenv

Expand Down Expand Up @@ -328,6 +328,7 @@ def __init__(self):
'aigp': None,
'operational': None,
'extended-message': True,
'software-version': False,
},
})

Expand Down

0 comments on commit d21e432

Please sign in to comment.