Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eadb764

Browse files
unfokusloulecrivain
authored andcommitted
L2VPN/VPWS: derive route distinguisher and route target
1 parent 56587a3 commit eadb764

15 files changed

+77
-66
lines changed

cosmo.example.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
fqdnSuffix: infra.example.com
2-
2+
asn: 65542
33
devices:
44
router:
55
- "router1"

cosmo/__main__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def main() -> int:
4848
with open(args.config, 'r') as cfg_file:
4949
cosmo_configuration = yaml.safe_load(cfg_file)
5050

51+
if not 'asn' in cosmo_configuration:
52+
error(f"Field 'asn' not defined in configuration file", None)
53+
return 1
54+
5155
info(f"Fetching information from Netbox, make sure VPN is enabled on your system.")
5256

5357
netbox_url = os.environ.get("NETBOX_URL")
@@ -82,7 +86,7 @@ def noop(*args, **kwargs):
8286
content = None
8387
try:
8488
if device['name'] in cosmo_configuration['devices']['router']:
85-
router_serializer = RouterSerializer(device, cosmo_data['l2vpn_list'], cosmo_data["loopbacks"])
89+
router_serializer = RouterSerializer(device, cosmo_data['l2vpn_list'], cosmo_data["loopbacks"], cosmo_configuration["asn"])
8690
content = router_serializer.serialize()
8791
elif device['name'] in cosmo_configuration['devices']['switch']:
8892
switch_serializer = SwitchSerializer(device)

cosmo/l2vpnhelpertypes.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def __init__(self, *args, associated_l2vpn: L2VPNType, loopbacks_by_device: dict
7575
self.associated_l2vpn = associated_l2vpn
7676
self.loopbacks_by_device = loopbacks_by_device
7777
self.asn = asn
78+
# when using 32 bit ASN we have to append L to the route target prefix
79+
self.rt = str(asn) if asn <= 2**16 else f"{asn}L"
7880

7981
def __repr__(self):
8082
return f"{self.__class__.__name__}({self.associated_l2vpn})"
@@ -318,14 +320,15 @@ def processInterfaceTypeTermination(self, o: InterfaceType) -> dict | None:
318320
lambda i: i != local,
319321
parent_l2vpn.getTerminations()
320322
))
323+
router_id = o.getParent(DeviceType).getRouterID()
321324
return {
322325
self._vrf_key: {
323326
parent_l2vpn.getName().replace("WAN: ", ""): {
324327
"interfaces": [o.getName()],
325328
"description": f"VPWS: {parent_l2vpn.getName().replace('WAN: VS_', '')}",
326329
"instance_type": "evpn-vpws",
327-
"route_distinguisher": f"{self.asn}:{str(parent_l2vpn.getIdentifier())}",
328-
"vrf_target": f"target:1:{str(parent_l2vpn.getIdentifier())}",
330+
"route_distinguisher": f"{router_id}:{str(parent_l2vpn.getIdentifier())}",
331+
"vrf_target": f"target:{self.rt}:{str(parent_l2vpn.getIdentifier())}",
329332
"protocols": {
330333
"evpn": {
331334
"interfaces": {

cosmo/netbox_types.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from abc import abstractmethod
99
from ipaddress import IPv4Interface, IPv6Interface
1010

11-
from .common import without_keys, JsonOutputType
11+
from .common import without_keys, JsonOutputType, DeviceSerializationError
1212
from typing import Self, Iterator, TypeVar, NoReturn
1313

1414

@@ -235,6 +235,33 @@ def getISISIdentifier(self) -> str|None:
235235
return None
236236
return sys_id
237237

238+
def getRouterID(self) -> str:
239+
# Deriving the router ID is a bit tricky, there is no 'correct' way.
240+
# For us it's the primary loopback IPv4 address
241+
242+
# get first loopback interface in default vrf
243+
loopback = next(filter(
244+
lambda x: (x.isLoopbackChild() and x.getVRF() == None),
245+
self.getInterfaces()
246+
), None)
247+
248+
if loopback == None:
249+
raise DeviceSerializationError("Can't derive Router ID, no suitable loopback interface found.")
250+
return ""
251+
252+
# get first IPv4 of that interface
253+
address = next(filter(
254+
lambda i: type(i) is IPv4Interface,
255+
map(lambda i: i.getIPInterfaceObject(), loopback.getIPAddresses())
256+
), None)
257+
258+
if address == None:
259+
raise DeviceSerializationError("Can't derive Router ID, no suitable loopback IP address found.")
260+
return ""
261+
262+
# return that IP without subnet mask and hope for the best
263+
return str(address.ip)
264+
238265

239266
class DeviceTypeType(AbstractNetboxType):
240267
def getBasePath(self):
@@ -365,7 +392,9 @@ def getSubInterfaceParentInterfaceName(self) -> str|None:
365392
return ret
366393

367394
def getVRF(self) -> VRFType|None:
368-
return self.get("vrf")
395+
if self["vrf"]:
396+
return VRFType(self["vrf"])
397+
return None
369398

370399
def spitInterfacePathWith(self, d: dict) -> dict:
371400
"""
@@ -449,6 +478,9 @@ def getIPAddresses(self) -> list[IPAddressType]:
449478
def hasParentInterface(self) -> bool:
450479
return bool(self.get("parent"))
451480

481+
def isLoopbackChild(self):
482+
return '.' in self.getName() and self.getName().startswith("lo")
483+
452484
def getConnectedEndpoints(self) -> list[DeviceType]:
453485
return self.get("connected_endpoints", [])
454486

@@ -486,7 +518,10 @@ def getBasePath(self):
486518
return "/vpn/l2vpns/"
487519

488520
def getIdentifier(self) -> int|None:
489-
return self["identifier"]
521+
if self["identifier"]:
522+
return self["identifier"]
523+
else:
524+
return self["id"]
490525

491526
def getType(self) -> str:
492527
return self["type"]
@@ -554,10 +589,9 @@ def getPrefixes(self) -> list[IPv6Interface|IPv4Interface]:
554589
return [
555590
ipaddress.ip_interface(p["prefix"]) for p in self["ip_prefixes"]
556591
]
557-
592+
558593
def isUniqueToDevice(self) -> bool:
559594
return len(self["devices"]) == 1
560-
595+
561596
def hasIPRanges(self) -> bool:
562597
return len(self["ip_ranges"]) > 0
563-

cosmo/routerl2vpnvisitor.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ def isCompliantWANL2VPN(self, o: L2VPNType):
3232
terminations = o.getTerminations()
3333
identifier = o.getIdentifier()
3434
l2vpn_type = self.getL2VpnTypeTerminationObjectFrom(o)
35-
if l2vpn_type.needsL2VPNIdentifierAsMandatory() and identifier is None:
36-
raise L2VPNSerializationError(
37-
f"for {o.getName()}: L2VPN identifier is mandatory."
38-
)
3935
if not l2vpn_type.isValidNumberOfTerminations(len(terminations)):
4036
raise L2VPNSerializationError(
4137
f"for {o.getName()}: "

cosmo/routervisitor.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,12 @@ def _(self, o: InterfaceType):
270270
}
271271
}
272272

273-
def getRouterId(self, o: DeviceType) -> str:
274-
return str(ipaddress.ip_interface(str(self.loopbacks_by_device[o.getName()].getIpv4())).ip)
275-
276273
@accept.register
277274
def _(self, o: VRFType):
278275
parent_interface = o.getParent(InterfaceType)
279276
if not parent_interface.isSubInterface():
280277
return # guard: do not process root interface
281-
router_id = self.getRouterId(o.getParent(DeviceType))
278+
router_id = o.getParent(DeviceType).getRouterID()
282279
if o.getRouteDistinguisher():
283280
rd = router_id + ":" + o.getRouteDistinguisher()
284281
elif len(o.getExportTargets()):

cosmo/serializer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212

1313
class RouterSerializer:
14-
def __init__(self, device, l2vpn_list, loopbacks):
14+
def __init__(self, device, l2vpn_list, loopbacks, asn):
1515
self.device = device
1616
self.l2vpn_list = l2vpn_list
1717
self.loopbacks = loopbacks
18+
self.asn = asn
1819

1920
self.l2vpns = {}
2021
self.l3vpns = {}
@@ -42,7 +43,7 @@ def serialize(self) -> CosmoOutputType|Never:
4243
# breakpoint()
4344
visitor = RouterDeviceExporterVisitor(
4445
loopbacks_by_device={k: CosmoLoopbackType(v) for k, v in self.loopbacks.items()},
45-
asn=9136,
46+
asn=self.asn,
4647
)
4748
if self.allow_private_ips:
4849
visitor.allowPrivateIPs()

cosmo/tests/cosmo.devgen_ansible.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
output_format: "ansible"
2-
2+
asn: 65542
33
devices:
44
router:
55
- "TEST0001"

cosmo/tests/cosmo.devgen_nix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
output_format: "nix"
2-
2+
asn: 65542
33
devices:
44
router:
55
- "TEST0001"

cosmo/tests/test_case_bgpcpe.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,11 @@ device_list:
120120
__typename: VRFType
121121
description: ''
122122
export_targets:
123-
- name: target:9136:407
123+
- name: target:65542:407
124124
__typename: RouteTargetType
125125
id: '407'
126126
import_targets:
127-
- name: target:9136:407
127+
- name: target:65542:407
128128
__typename: RouteTargetType
129129
name: L3VPN
130130
rd: null
@@ -165,11 +165,11 @@ device_list:
165165
__typename: VRFType
166166
description: ''
167167
export_targets:
168-
- name: target:9136:407
168+
- name: target:65542:407
169169
__typename: RouteTargetType
170170
id: '407'
171171
import_targets:
172-
- name: target:9136:407
172+
- name: target:65542:407
173173
__typename: RouteTargetType
174174
name: L3VPN
175175
rd: null

0 commit comments

Comments
 (0)