169 lines
6.7 KiB
Python
169 lines
6.7 KiB
Python
# Copyright 2018 Canonical Ltd.
|
|
#
|
|
# 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 charmhelpers.core as ch_core
|
|
import charmhelpers.contrib.network.ip as ch_net_ip
|
|
from charms import reactive
|
|
import netaddr
|
|
|
|
|
|
class BGPEndpoint(reactive.Endpoint):
|
|
def generate_asn(self):
|
|
"""
|
|
Generate unique 32-bit Private Use [RFC6996] ASn.
|
|
|
|
This is useful to automate configuration of BGP routers that is part
|
|
of a Clos Network Topology with a Layer 3-Only routed design. [RFC7938]
|
|
|
|
Assumption:
|
|
- Unit has a IPv4 address and it is unique to the deployment.
|
|
|
|
Implementation:
|
|
- A private 32-bit ASn has a range of 4 200 000 000 - 4 294 967 294
|
|
which leaves us with 94 967 294 possible endpoints.
|
|
- Within a deployment it is less likelly that the 4 most significant
|
|
bits of the IP address differ between units.
|
|
- If we cap the 4 most significant bits of the IPv4 address to be
|
|
within the range of 0 and 4, the decimal representation of the
|
|
resulting IPv4 address fits nicely into that range.
|
|
|
|
Note:
|
|
- This implementation generates ASn in the following range:
|
|
4 211 081 215 - 4 294 967 294
|
|
- Leaving the following range for any static configuration needs:
|
|
4 200 000 000 - 4 211 081 214
|
|
"""
|
|
asn_base = 4211081215
|
|
mask = netaddr.IPAddress('4.255.255.255')
|
|
unit_ip = netaddr.IPAddress(
|
|
ch_core.hookenv.unit_get('private-address'))
|
|
masked_ip = unit_ip & mask
|
|
|
|
asn = asn_base + int(masked_ip)
|
|
|
|
return asn
|
|
|
|
def generate_asn_16(self):
|
|
"""
|
|
Generate unique 16-bit Private Use [RFC6996] ASn.
|
|
|
|
This is useful to automate configuration of BGP routers that is part
|
|
of a Clos Network Topology with a Layer 3-Only routed design. [RFC7938]
|
|
|
|
Assumption:
|
|
- Unit has a IPv4 address and it is unique to the deployment.
|
|
|
|
Implementation:
|
|
- A private 16-bit ASn has a range of 64512 - 65534
|
|
which leaves us with 1022 possible endpoints.
|
|
- The 16-bit ASn space is limited and this implementation
|
|
will give you unique ASns for a /23
|
|
|
|
Note:
|
|
- This implementation generates ASn in the following range:
|
|
65023 - 65534
|
|
- Leaving the following range for any static configuration needs:
|
|
64512 - 65022
|
|
"""
|
|
asn_base = 65023
|
|
mask = netaddr.IPAddress('0.0.1.255')
|
|
unit_ip = netaddr.IPAddress(
|
|
ch_core.hookenv.unit_get('private-address'))
|
|
masked_ip = unit_ip & mask
|
|
|
|
asn = asn_base + int(masked_ip)
|
|
|
|
return asn
|
|
|
|
def publish_info(self, asn=None, passive=False, bindings=None,
|
|
use_16bit_asn=False):
|
|
"""
|
|
Publish the AS Number and IP address of any extra-bindings of this
|
|
BGP Endpoint over the relationship.
|
|
|
|
If no AS Number is provided a unique 32-bit Private Use [RFC6996] ASn
|
|
will be generated.
|
|
|
|
:param asn: AS Number to publish. Autogenerated if not provided.
|
|
:param passive: Advertise that we wish to be configured as passive
|
|
neighbour.
|
|
:param bindings: List bindings advertised as links to speak BGP on.
|
|
"""
|
|
if asn:
|
|
myasn = asn
|
|
else:
|
|
if use_16bit_asn:
|
|
myasn = self.generate_asn_16()
|
|
else:
|
|
myasn = self.generate_asn()
|
|
|
|
# network_get will return addresses for bindings regardless of them
|
|
# being bound to a network space. detect actual space bindings by
|
|
# comparing returned addresses to what we have for the relation itself.
|
|
for relation in self.relations:
|
|
rel_network = ch_core.hookenv.network_get(
|
|
self.expand_name('{endpoint_name}'),
|
|
relation_id=relation.relation_id)
|
|
rel_addrs = [a['address']
|
|
for a in
|
|
rel_network['bind-addresses'][0]['addresses']]
|
|
actual_bindings = []
|
|
if bindings is None:
|
|
continue
|
|
for binding in bindings:
|
|
bind_network = ch_core.hookenv.network_get(
|
|
binding,
|
|
relation_id=relation.relation_id)
|
|
bind_addrs = [a['address']
|
|
for a in
|
|
bind_network['bind-addresses'][0]['addresses']]
|
|
if not set(bind_addrs).issubset(set(rel_addrs)):
|
|
actual_bindings.extend(
|
|
bind_network['bind-addresses'][0]['addresses'])
|
|
relation.to_publish['asn'] = myasn
|
|
relation.to_publish['bindings'] = actual_bindings
|
|
relation.to_publish['passive'] = passive
|
|
ch_core.hookenv.log("to_publish: '{}'".format(relation.to_publish))
|
|
|
|
def get_received_info(self):
|
|
neighbors = []
|
|
for relation in self.relations:
|
|
for unit in relation.units:
|
|
if not ('asn' in unit.received and
|
|
'bindings' in unit.received):
|
|
ch_core.hookenv.log('Skip get_received_info() '
|
|
'relation incomplete...',
|
|
level=ch_core.hookenv.DEBUG)
|
|
continue
|
|
links = []
|
|
for addrinfo in unit.received['bindings']:
|
|
# filter list of networks to those we have interfaces
|
|
# configured for
|
|
ip = ch_net_ip.get_address_in_network(addrinfo['cidr'])
|
|
if ip:
|
|
links.append(
|
|
{'local': ip,
|
|
'remote': addrinfo['address'],
|
|
'cidr': addrinfo['cidr']}
|
|
)
|
|
ch_core.hookenv.log("links: '{}'".format(links))
|
|
neighbors.append({
|
|
'asn': unit.received['asn'],
|
|
'links': links,
|
|
'relation_id': relation.relation_id,
|
|
'remote_unit_name': unit.unit_name,
|
|
'passive': unit.received['passive'],
|
|
})
|
|
return neighbors
|