Skip to content

Commit

Permalink
✨ Feat(bgpprefixsid.py): add support for parsing the Path Attribute: …
Browse files Browse the repository at this point in the history
  • Loading branch information
yuangezhizao committed Mar 5, 2024
1 parent a043982 commit 67e1681
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 0 deletions.
1 change: 1 addition & 0 deletions yabgp/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
BGPTYPE_TUNNEL_ENCAPS_ATTR = 23 # RFC5512
BGPTYPE_LINK_STATE = 29
BGPTYPE_LARGE_COMMUNITY = 32
BGPTYPE_BGP_PREFIX_SID = 40 # RFC8669 & RFC9252
BGPTYPE_ATTRIBUTE_SET = 128

# BGP Tunnel Encapsulation Attribute Tunnel Types
Expand Down
2 changes: 2 additions & 0 deletions yabgp/message/attribute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class AttributeFlag(int):
PARTIAL = 0x20 # 32 RFC 4271
TRANSITIVE = 0x40 # 64 RFC 4271
OPTIONAL = 0x80 # 128 RFC 4271

# OPTIONAL_TRANSITIVE = 0xc0 # 192 RFC 4271

def __str_(self):
Expand Down Expand Up @@ -110,6 +111,7 @@ class AttributeID(int):
Traffic_Engineering = 0x18 # 24 [RFC5543]
IPv6_Address_Specific_Extended_Community = 0x19 # 25 [RFC5701]
LARGE_COMMUNITY = 0x20 # 32 [8092]
BGP_PREFIX_SID = 0x28 # 40 [RFC8669 & RFC9252]
LINKSTATE = 0x1d
ATTR_SET = 0x80 # 128 [RFC6368]

Expand Down
19 changes: 19 additions & 0 deletions yabgp/message/attribute/sr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2024 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from .bgpprefixsid import BGPPrefixSID # noqa
from .srv6.l3service import SRv6L3Service # noqa
from .srv6.sidinformation import SRv6SIDInformation # noqa
from .srv6.sidstructure import SRv6SIDStructure # noqa
90 changes: 90 additions & 0 deletions yabgp/message/attribute/sr/bgpprefixsid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Copyright 2024 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import struct

import binascii

from yabgp.message.attribute import Attribute, AttributeFlag, AttributeID


class BGPPrefixSID(Attribute):
"""
BGP Prefix SID
Original: https://datatracker.ietf.org/doc/html/rfc8669#section-3
Extend: https://datatracker.ietf.org/doc/html/rfc9252#section-2
"""

ID = AttributeID.BGP_PREFIX_SID
FLAG = AttributeFlag.OPTIONAL + AttributeFlag.TRANSITIVE

registered_tlvs = dict()

def __init__(self, value, hex_value=None):
self.value = value
self.hex_value = hex_value

@classmethod
def register(cls, _type=None):
"""
:param _type:
:return:
"""

def decorator(klass):
"""
:param klass:
:return:
"""
_id = klass.TYPE if _type is None else _type
if _id in cls.registered_tlvs:
raise RuntimeError('Duplicated attribute type')
cls.registered_tlvs[_id] = klass
return klass

return decorator

def dict(self):
"""
:return:
"""
return {self.ID: self.value}

@classmethod
def unpack(cls, data):
"""
:param data:
:return:
"""
tlvs = []
while data:
type_code = data[0] # Note: Type = 1 octet
length = struct.unpack('!H', data[1:3])[0] # Note: Length = 2 octet
value = data[3: 3 + length]

if type_code in cls.registered_tlvs:
tlvs.append(cls.registered_tlvs[type_code].unpack(value).dict())
else:
tlvs.append({
'type': type_code,
'value': str(binascii.b2a_hex(value))
})
data = data[3 + length:]
return cls(value=tlvs)
8 changes: 8 additions & 0 deletions yabgp/message/attribute/sr/srv6/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# +---------------+---------------------------------+----------+-----------------+
# | TLV Code | Description | Length | Reference |
# | Point | | | |
# +---------------+---------------------------------+----------+-----------------+
# | 5 | SRv6 L3 Service TLV | variable | Section 2 |
# | 1 | SRv6 SID Information Sub-TLV | variable | Section 3.1 |
# | 1 | SRv6 SID Structure Sub-Sub-TLV | 6 | Section 3.2.1 |
# +---------------+---------------------------------+----------+-----------------+
96 changes: 96 additions & 0 deletions yabgp/message/attribute/sr/srv6/l3service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright 2024 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import struct

import binascii

from yabgp.tlv import TLV
from ..bgpprefixsid import BGPPrefixSID


# 2. SRv6 Services TLVs
#
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | TLV Type | TLV Length | RESERVED |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | SRv6 Service Sub-TLVs //
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#
# Figure 1: SRv6 Service TLVs


@BGPPrefixSID.register()
class SRv6L3Service(TLV):
"""
SRv6 L3 Service
"""
TYPE = 5 # https://datatracker.ietf.org/doc/html/rfc9252.html#section-2
TYPE_STR = 'srv6_l3_service'

registered_tlvs = dict()

@classmethod
def register(cls, _type=None):
"""
:param _type:
:return:
"""

def decorator(klass):
"""
:param klass:
:return:
"""
_id = klass.TYPE if _type is None else _type
if _id in cls.registered_tlvs:
raise RuntimeError('Duplicated SRv6 Service Sub-TLV type')
cls.registered_tlvs[_id] = klass
return klass

return decorator

@classmethod
def unpack(cls, data):
"""
:param data:
:return:
"""
tlvs = []

# reserved = data[0:1] # Note: First byte is reserved
data = data[1:]
while data:
type_code = data[0] # Note: Type = 1 octet
length = struct.unpack('!H', data[1:3])[0] # Note: Length = 2 octet
value = data[3: 3 + length]

if type_code in cls.registered_tlvs:
tlvs.append(cls.registered_tlvs[type_code].unpack(value).dict())
else:
tlvs.append({
'type': type_code,
'value': str(binascii.b2a_hex(value))
})
data = data[3 + length:]
value = {
'srv6_service_sub_tlvs': tlvs
}
return cls(value=value)
111 changes: 111 additions & 0 deletions yabgp/message/attribute/sr/srv6/sidinformation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Copyright 2024 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import struct

import binascii
import netaddr

from yabgp.tlv import TLV
from .l3service import SRv6L3Service


# 3.1. SRv6 SID Information Sub-TLV
#
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | SRv6 Service | SRv6 Service | |
# | Sub-TLV | Sub-TLV | |
# | Type=1 | Length | RESERVED1 |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | SRv6 SID Value (16 octets) //
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Svc SID Flags | SRv6 Endpoint Behavior | RESERVED2 |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | SRv6 Service Data Sub-Sub-TLVs //
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

# Figure 3: SRv6 SID Information Sub-TLV


@SRv6L3Service.register()
class SRv6SIDInformation(TLV):
"""
SRv6 SID Information
"""
TYPE = 1 # https://datatracker.ietf.org/doc/html/rfc9252.html#section-3.1
TYPE_STR = 'srv6_sid_information'

registered_tlvs = dict()

@classmethod
def register(cls, _type=None):
"""
:param _type:
:return:
"""

def decorator(klass):
"""
:param klass:
:return:
"""
_id = klass.TYPE if _type is None else _type
if _id in cls.registered_tlvs:
raise RuntimeError('Duplicated SRv6 Service Data Sub-Sub-TLV type')
cls.registered_tlvs[_id] = klass
return klass

return decorator

@classmethod
def unpack(cls, data):
"""
:param data:
:return:
"""
tlvs = []

# reserved_1 = data[0:1] # Note: First byte is reserved
srv6_sid_value = str(netaddr.IPAddress(int(binascii.b2a_hex(data[1:17]), 16)))
srv6_service_sid_flags = ord(data[17:18])
srv6_endpoint_behavior = struct.unpack('!H', data[18:20])[0]
# reserved_2 = data[20:21] # Note: Also reserved
data = data[21:]
while data:
srv6_service_sub_tlv_type_code = data[0] # Note: Type = 1 octet
srv6_service_sub_tlv_length = struct.unpack('!H', data[1:3])[0] # Note: Length = 2 octet
value = data[3: 3 + srv6_service_sub_tlv_length]

if srv6_service_sub_tlv_type_code in cls.registered_tlvs:
tlvs.append(cls.registered_tlvs[srv6_service_sub_tlv_type_code].unpack(value).dict())
else:
tlvs.append({
'type': srv6_service_sub_tlv_type_code,
'value': str(binascii.b2a_hex(value))
})
data = data[3 + srv6_service_sub_tlv_length:]
value = {
'srv6_sid_value': srv6_sid_value,
'srv6_service_sid_flags': srv6_service_sid_flags,
'srv6_endpoint_behavior': srv6_endpoint_behavior,

'srv6_service_data_sub_sub_tlvs': tlvs
}
return cls(value=value)
Loading

0 comments on commit 67e1681

Please sign in to comment.