-
Notifications
You must be signed in to change notification settings - Fork 447
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement RFC6514 MCAST-VPN (incomplete)
https://datatracker.ietf.org/doc/html/rfc6514 This commit starts support for MCAST-VPN NLRI. Route Types added: + 5 - Source Active A-D route + 6 - Shared Tree Join route + 7 - Source Tree Join route
- Loading branch information
Showing
20 changed files
with
924 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
process mvpn { | ||
run ./run/api-mvpn.run; | ||
encoder json; | ||
} | ||
|
||
neighbor 127.0.0.1 { | ||
router-id 32.32.32.32; | ||
local-address 127.0.0.1; | ||
local-as 65000; | ||
peer-as 65000; | ||
group-updates false; | ||
auto-flush true; | ||
|
||
family { | ||
ipv4 mcast-vpn; | ||
ipv6 mcast-vpn; | ||
} | ||
api { | ||
processes [ mvpn ]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
neighbor 127.0.0.1 { | ||
router-id 32.32.32.32; | ||
local-address 127.0.0.1; | ||
local-as 65000; | ||
peer-as 65000; | ||
group-updates true; | ||
auto-flush true; | ||
|
||
family { | ||
ipv4 mcast-vpn; | ||
ipv6 mcast-vpn; | ||
} | ||
|
||
announce { | ||
ipv4 { | ||
mcast-vpn shared-join rp 10.99.199.1 group 239.251.255.228 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]; | ||
mcast-vpn source-join source 10.99.12.2 group 239.251.255.228 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]; | ||
mcast-vpn source-ad source 10.99.12.4 group 239.251.255.228 rd 65000:99999 next-hop 10.10.6.4 extended-community [ target:65000:99999 ]; | ||
} | ||
ipv6 { | ||
mcast-vpn shared-join rp fd00::1 group ff0e::1 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]; | ||
mcast-vpn source-join source fd12::2 group ff0e::1 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]; | ||
mcast-vpn source-ad source fd12::4 group ff0e::1 rd 65000:99999 next-hop 10.10.6.4 extended-community [ target:65000:99999 ]; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
#!/usr/bin/python3 | ||
|
||
import os | ||
import sys | ||
import time | ||
|
||
time.sleep(2) # let the EOR pass | ||
|
||
|
||
routes = [ | ||
'ipv4 mcast-vpn shared-join rp 10.99.199.1 group 239.251.255.228 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]', | ||
'ipv4 mcast-vpn source-join source 10.99.12.2 group 239.251.255.228 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]', | ||
'ipv6 mcast-vpn shared-join rp fd00::1 group ff0e::1 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]', | ||
'ipv6 mcast-vpn source-join source fd12::2 group ff0e::1 rd 65000:99999 source-as 65000 next-hop 10.10.6.3 extended-community [ target:192.168.94.12:5 ]', | ||
'ipv6 mcast-vpn source-ad source fd12::4 group ff0e::1 rd 65000:99999 next-hop 10.10.6.4 extended-community [ target:65000:99999 ]', | ||
'ipv4 mcast-vpn source-ad source 10.99.12.4 group 239.251.255.228 rd 65000:99999 next-hop 10.10.6.4 extended-community [ target:65000:99999 ]', | ||
] | ||
|
||
for r in routes: | ||
sys.stdout.write('announce ' + r + '\n') | ||
sys.stdout.flush() | ||
time.sleep(0.3) | ||
|
||
time.sleep(5) | ||
|
||
for r in routes: | ||
sys.stdout.write('withdraw ' + r + '\n') | ||
sys.stdout.flush() | ||
time.sleep(0.3) | ||
|
||
try: | ||
now = time.time() | ||
while os.getppid() != 1 and time.time() < now + 15: | ||
line = sys.stdin.readline().strip() | ||
if not line or 'shutdown' in line: | ||
break | ||
time.sleep(1) | ||
except IOError: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
api-mvpn.conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001E:02:00000007900F0003000105 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:001E:02:00000007900F0003000205 | ||
|
||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:005B:02:00000044400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E21000105040A0A06030006160000FDE80001869F0000FDE8200A63C70120EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:005B:02:00000044400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E21000105040A0A06030007160000FDE80001869F0000FDE8200A630C0220EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0073:02:0000005C400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E39000205040A0A060300062E0000FDE80001869F0000FDE880FD00000000000000000000000000000180FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0073:02:0000005C400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E39000205040A0A060300072E0000FDE80001869F0000FDE880FD12000000000000000000000000000280FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:006F:02:00000058400101004002004003040A0A060440050400000064C010080002FDE80001869F800E35000205040A0A060400052A0000FDE80001869F80FD12000000000000000000000000000480FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0057:02:00000040400101004002004003040A0A060440050400000064C010080002FDE80001869F800E1D000105040A0A06040005120000FDE80001869F200A630C0420EFFBFFE4 | ||
|
||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0055:02:0000003E400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800F1B00010506160000FDE80001869F0000FDE8200A63C70120EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0055:02:0000003E400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800F1B00010507160000FDE80001869F0000FDE8200A630C0220EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:006D:02:00000056400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800F33000205062E0000FDE80001869F0000FDE880FD00000000000000000000000000000180FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:006D:02:00000056400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800F33000205072E0000FDE80001869F0000FDE880FD12000000000000000000000000000280FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0069:02:00000052400101004002004003040A0A060440050400000064C010080002FDE80001869F800F2F000205052A0000FDE80001869F80FD12000000000000000000000000000480FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0051:02:0000003A400101004002004003040A0A060440050400000064C010080002FDE80001869F800F1700010505120000FDE80001869F200A630C0420EFFBFFE4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
conf-mvpn.conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0073:02:0000005C400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E39000105040A0A06030006160000FDE80001869F0000FDE8200A63C70120EFFBFFE407160000FDE80001869F0000FDE8200A630C0220EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:00A3:02:0000008C400101004002004003040A0A060340050400000064C010080102C0A85E0C0005800E69000205040A0A060300062E0000FDE80001869F0000FDE880FD00000000000000000000000000000180FF0E0000000000000000000000000001072E0000FDE80001869F0000FDE880FD12000000000000000000000000000280FF0E0000000000000000000000000001 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:0057:02:00000040400101004002004003040A0A060440050400000064C010080002FDE80001869F800E1D000105040A0A06040005120000FDE80001869F200A630C0420EFFBFFE4 | ||
1:raw:FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF:006F:02:00000058400101004002004003040A0A060440050400000064C010080002FDE80001869F800E35000205040A0A060400052A0000FDE80001869F80FD12000000000000000000000000000480FF0E0000000000000000000000000001 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Every MVPN should be imported from this file | ||
# as it makes sure that all the registering decorator are run | ||
|
||
# flake8: noqa: F401,E261 | ||
|
||
from exabgp.bgp.message.update.nlri.mvpn.nlri import MVPN | ||
|
||
from exabgp.bgp.message.update.nlri.mvpn.sourcead import SourceAD | ||
from exabgp.bgp.message.update.nlri.mvpn.sourcejoin import SourceJoin | ||
from exabgp.bgp.message.update.nlri.mvpn.sharedjoin import SharedJoin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
from struct import pack | ||
|
||
from exabgp.protocol.family import AFI | ||
from exabgp.protocol.family import SAFI | ||
|
||
from exabgp.bgp.message import Action | ||
|
||
from exabgp.bgp.message.update.nlri import NLRI | ||
|
||
# https://datatracker.ietf.org/doc/html/rfc6514 | ||
|
||
# +-----------------------------------+ | ||
# | Route Type (1 octet) | | ||
# +-----------------------------------+ | ||
# | Length (1 octet) | | ||
# +-----------------------------------+ | ||
# | Route Type specific (variable) | | ||
# +-----------------------------------+ | ||
|
||
# ========================================================================= MVPN | ||
|
||
|
||
@NLRI.register(AFI.ipv4, SAFI.mcast_vpn) | ||
@NLRI.register(AFI.ipv6, SAFI.mcast_vpn) | ||
class MVPN(NLRI): | ||
registered_mvpn = dict() | ||
|
||
# NEED to be defined in the subclasses | ||
CODE = -1 | ||
NAME = 'Unknown' | ||
SHORT_NAME = 'unknown' | ||
|
||
def __init__(self, afi, action=Action.UNSET, addpath=None): | ||
NLRI.__init__(self, afi=afi, safi=SAFI.mcast_vpn, action=action) | ||
self._packed = b'' | ||
|
||
def __hash__(self): | ||
return hash("%s:%s:%s:%s" % (self.afi, self.safi, self.CODE, self._packed)) | ||
|
||
def __len__(self): | ||
return len(self._packed) + 2 | ||
|
||
def __eq__(self, other): | ||
return NLRI.__eq__(self, other) and self.CODE == other.CODE | ||
|
||
def __str__(self): | ||
return "mvpn:%s:%s" % ( | ||
self.registered_mvpn.get(self.CODE, self).SHORT_NAME.lower(), | ||
'0x' + ''.join('%02x' % _ for _ in self._packed), | ||
) | ||
|
||
def __repr__(self): | ||
return str(self) | ||
|
||
def feedback(self, action): | ||
# if self.nexthop is None and action == Action.ANNOUNCE: | ||
# return 'mvpn nlri next-hop is missing' | ||
return '' | ||
|
||
def _prefix(self): | ||
return "mvpn:%s:" % (self.registered_mvpn.get(self.CODE, self).SHORT_NAME.lower()) | ||
|
||
def pack_nlri(self, negotiated=None): | ||
# XXX: addpath not supported yet | ||
return pack('!BB', self.CODE, len(self._packed)) + self._packed | ||
|
||
@classmethod | ||
def register(cls, klass): | ||
if klass.CODE in cls.registered_mvpn: | ||
raise RuntimeError('only one MVPN registration allowed') | ||
cls.registered_mvpn[klass.CODE] = klass | ||
return klass | ||
|
||
@classmethod | ||
def unpack_nlri(cls, afi, safi, bgp, action, addpath): | ||
code = bgp[0] | ||
length = bgp[1] | ||
|
||
if code in cls.registered_mvpn: | ||
klass = cls.registered_mvpn[code].unpack(bgp[2 : length + 2], afi) | ||
else: | ||
klass = GenericMVPN(afi, code, bgp[2 : length + 2]) | ||
klass.CODE = code | ||
klass.action = action | ||
klass.addpath = addpath | ||
|
||
return klass, bgp[length + 2 :] | ||
|
||
def _raw(self): | ||
return ''.join('%02X' % _ for _ in self.pack_nlri()) | ||
|
||
|
||
class GenericMVPN(MVPN): | ||
def __init__(self, afi, code, packed): | ||
MVPN.__init__(self, afi) | ||
self.CODE = code | ||
self._pack(packed) | ||
|
||
def _pack(self, packed=None): | ||
if self._packed: | ||
return self._packed | ||
|
||
if packed: | ||
self._packed = packed | ||
return packed | ||
|
||
def json(self, compact=None): | ||
return '{ "code": %d, "parsed": false, "raw": "%s" }' % (self.CODE, self._raw()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from exabgp.protocol.family import AFI | ||
from exabgp.protocol.family import SAFI | ||
|
||
from exabgp.bgp.message.update.nlri.qualifier import RouteDistinguisher | ||
from exabgp.bgp.message.update.nlri.mvpn.nlri import MVPN | ||
from exabgp.bgp.message.notification import Notify | ||
from exabgp.protocol.ip import IP | ||
from struct import pack | ||
|
||
# +-----------------------------------+ | ||
# | RD (8 octets) | | ||
# +-----------------------------------+ | ||
# | Source AS (4 octets) | | ||
# +-----------------------------------+ | ||
# | Multicast Source Length (1 octet) | | ||
# +-----------------------------------+ | ||
# | Multicast Source (variable) | | ||
# +-----------------------------------+ | ||
# | Multicast Group Length (1 octet) | | ||
# +-----------------------------------+ | ||
# | Multicast Group (variable) | | ||
# +-----------------------------------+ | ||
|
||
|
||
@MVPN.register | ||
class SharedJoin(MVPN): | ||
CODE = 6 | ||
NAME = "C-Multicast Shared Tree Join route" | ||
SHORT_NAME = "Shared-Join" | ||
|
||
def __init__(self, rd, afi, source, group, source_as, packed=None, action=None, addpath=None): | ||
MVPN.__init__(self, afi=afi, action=action, addpath=addpath) | ||
self.rd = rd | ||
self.group = group | ||
self.source = source | ||
self.source_as = source_as | ||
self._pack(packed) | ||
|
||
def __eq__(self, other): | ||
return ( | ||
isinstance(other, SharedJoin) | ||
and self.CODE == other.CODE | ||
and self.rd == other.rd | ||
and self.source == other.source | ||
and self.group == other.group | ||
) | ||
|
||
def __ne__(self, other): | ||
return not self.__eq__(other) | ||
|
||
def __str__(self): | ||
return f'{self._prefix()}:{self.rd._str()}:{str(self.source_as)}:{str(self.source)}:{str(self.group)}' | ||
|
||
def __hash__(self): | ||
return hash((self.rd, self.source, self.group, self.source_as)) | ||
|
||
def _pack(self, packed=None): | ||
if self._packed: | ||
return self._packed | ||
|
||
if packed: | ||
self._packed = packed | ||
return packed | ||
self._packed = ( | ||
self.rd.pack() | ||
+ pack('!I', self.source_as) | ||
+ bytes([len(self.source) * 8]) | ||
+ self.source.pack() | ||
+ bytes([len(self.group) * 8]) | ||
+ self.group.pack() | ||
) | ||
return self._packed | ||
|
||
@classmethod | ||
def unpack(cls, data, afi): | ||
datalen = len(data) | ||
if datalen not in (22, 46): # IPv4 or IPv6 | ||
raise Notify(3, 5, f"Invalid C-Multicast Route length ({datalen} bytes).") | ||
cursor = 0 | ||
rd = RouteDistinguisher.unpack(data[cursor:8]) | ||
cursor += 8 | ||
source_as = int.from_bytes(data[cursor : cursor + 4], "big") | ||
cursor += 4 | ||
sourceiplen = int(data[cursor] / 8) | ||
cursor += 1 | ||
if sourceiplen != 4 and sourceiplen != 16: | ||
raise Notify( | ||
3, | ||
5, | ||
f"Invalid C-Multicast Route length ({sourceiplen*8} bits). Expected 32 bits (IPv4) or 128 bits (IPv6).", | ||
) | ||
sourceip = IP.unpack(data[cursor : cursor + sourceiplen]) | ||
cursor += sourceiplen | ||
groupiplen = int(data[cursor] / 8) | ||
cursor += 1 | ||
if groupiplen != 4 and groupiplen != 16: | ||
raise Notify( | ||
3, | ||
5, | ||
f"Invalid C-Multicast Route length ({groupiplen*8} bits). Expected 32 bits (IPv4) or 128 bits (IPv6).", | ||
) | ||
groupip = IP.unpack(data[cursor : cursor + groupiplen]) | ||
return cls(afi=afi, rd=rd, source=sourceip, group=groupip, source_as=source_as, packed=data) | ||
|
||
def json(self, compact=None): | ||
content = ' "code": %d, ' % self.CODE | ||
content += '"parsed": true, ' | ||
content += '"raw": "%s", ' % self._raw() | ||
content += '"name": "%s", ' % self.NAME | ||
content += '%s, ' % self.rd.json() | ||
content += '"source-as": "%s", ' % str(self.source_as) | ||
content += '"source": "%s", ' % str(self.source) | ||
content += '"group": "%s"' % str(self.group) | ||
return '{%s}' % content |
Oops, something went wrong.