2020-01-23 19:03:56 +00:00
|
|
|
import abc
|
|
|
|
from typing import Any, Dict
|
|
|
|
|
|
|
|
import netaddr
|
|
|
|
|
|
|
|
from core import constants
|
|
|
|
from core.configservice.base import ConfigService, ConfigServiceMode
|
|
|
|
from core.nodes.base import CoreNodeBase
|
|
|
|
from core.nodes.interface import CoreInterface
|
|
|
|
|
|
|
|
GROUP = "Quagga"
|
|
|
|
|
|
|
|
|
|
|
|
def has_mtu_mismatch(ifc: CoreInterface) -> bool:
|
|
|
|
"""
|
|
|
|
Helper to detect MTU mismatch and add the appropriate OSPF
|
|
|
|
mtu-ignore command. This is needed when e.g. a node is linked via a
|
|
|
|
GreTap device.
|
|
|
|
"""
|
|
|
|
if ifc.mtu != 1500:
|
|
|
|
return True
|
|
|
|
if not ifc.net:
|
|
|
|
return False
|
|
|
|
for i in ifc.net.netifs():
|
|
|
|
if i.mtu != ifc.mtu:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_router_id(node: CoreNodeBase) -> str:
|
|
|
|
"""
|
|
|
|
Helper to return the first IPv4 address of a node as its router ID.
|
|
|
|
"""
|
|
|
|
for ifc in node.netifs():
|
2020-01-23 19:27:05 +00:00
|
|
|
if getattr(ifc, "control", False):
|
2020-01-23 19:03:56 +00:00
|
|
|
continue
|
|
|
|
for a in ifc.addrlist:
|
|
|
|
a = a.split("/")[0]
|
|
|
|
if netaddr.valid_ipv4(a):
|
|
|
|
return a
|
|
|
|
return "0.0.0.0"
|
|
|
|
|
|
|
|
|
|
|
|
class Zebra(ConfigService):
|
|
|
|
name = "zebra"
|
|
|
|
group = GROUP
|
|
|
|
directories = ["/usr/local/etc/quagga", "/var/run/quagga"]
|
|
|
|
files = [
|
|
|
|
"/usr/local/etc/quagga/Quagga.conf",
|
|
|
|
"quaggaboot.sh",
|
|
|
|
"/usr/local/etc/quagga/vtysh.conf",
|
|
|
|
]
|
|
|
|
executables = ["zebra"]
|
|
|
|
dependencies = []
|
|
|
|
startup = ["sh quaggaboot.sh zebra"]
|
|
|
|
validate = ["pidof zebra"]
|
|
|
|
shutdown = ["killall zebra"]
|
|
|
|
validation_mode = ConfigServiceMode.BLOCKING
|
|
|
|
default_configs = []
|
|
|
|
modes = {}
|
|
|
|
|
|
|
|
def data(self) -> Dict[str, Any]:
|
|
|
|
quagga_bin_search = self.node.session.options.get_config(
|
|
|
|
"quagga_bin_search", default="/usr/local/bin /usr/bin /usr/lib/quagga"
|
2020-01-23 21:22:47 +00:00
|
|
|
).strip('"')
|
2020-01-23 19:03:56 +00:00
|
|
|
quagga_sbin_search = self.node.session.options.get_config(
|
|
|
|
"quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga"
|
2020-01-23 21:22:47 +00:00
|
|
|
).strip('"')
|
2020-01-23 19:03:56 +00:00
|
|
|
quagga_state_dir = constants.QUAGGA_STATE_DIR
|
|
|
|
quagga_conf = self.files[0]
|
|
|
|
|
|
|
|
services = []
|
|
|
|
want_ip4 = False
|
|
|
|
want_ip6 = False
|
|
|
|
for service in self.node.config_services.values():
|
|
|
|
if self.name not in service.dependencies:
|
|
|
|
continue
|
|
|
|
if service.ipv4_routing:
|
|
|
|
want_ip4 = True
|
|
|
|
if service.ipv6_routing:
|
|
|
|
want_ip6 = True
|
|
|
|
services.append(service)
|
|
|
|
|
|
|
|
interfaces = []
|
|
|
|
for ifc in self.node.netifs():
|
|
|
|
ip4s = []
|
|
|
|
ip6s = []
|
|
|
|
for x in ifc.addrlist:
|
|
|
|
addr = x.split("/")[0]
|
|
|
|
if netaddr.valid_ipv4(addr):
|
|
|
|
ip4s.append(x)
|
|
|
|
else:
|
|
|
|
ip6s.append(x)
|
|
|
|
is_control = getattr(ifc, "control", False)
|
|
|
|
interfaces.append((ifc, ip4s, ip6s, is_control))
|
|
|
|
|
|
|
|
return dict(
|
|
|
|
quagga_bin_search=quagga_bin_search,
|
|
|
|
quagga_sbin_search=quagga_sbin_search,
|
|
|
|
quagga_state_dir=quagga_state_dir,
|
|
|
|
quagga_conf=quagga_conf,
|
|
|
|
interfaces=interfaces,
|
|
|
|
want_ip4=want_ip4,
|
|
|
|
want_ip6=want_ip6,
|
2020-01-23 21:22:47 +00:00
|
|
|
services=services,
|
2020-01-23 19:03:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class QuaggaService(abc.ABC):
|
|
|
|
group = GROUP
|
|
|
|
directories = []
|
|
|
|
files = []
|
|
|
|
executables = []
|
|
|
|
dependencies = ["zebra"]
|
|
|
|
startup = []
|
|
|
|
validate = []
|
|
|
|
shutdown = []
|
|
|
|
validation_mode = ConfigServiceMode.BLOCKING
|
|
|
|
default_configs = []
|
|
|
|
modes = {}
|
|
|
|
ipv4_routing = False
|
|
|
|
ipv6_routing = False
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def quagga_interface_config(self, ifc: CoreInterface) -> str:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def quagga_config(self) -> str:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
class Ospfv2(QuaggaService, ConfigService):
|
|
|
|
"""
|
|
|
|
The OSPFv2 service provides IPv4 routing for wired networks. It does
|
|
|
|
not build its own configuration file but has hooks for adding to the
|
|
|
|
unified Quagga.conf file.
|
|
|
|
"""
|
|
|
|
|
|
|
|
name = "OSPFv2"
|
|
|
|
validate = ["pidof ospfd"]
|
|
|
|
shutdown = ["killall ospfd"]
|
|
|
|
ipv4_routing = True
|
|
|
|
|
|
|
|
def quagga_interface_config(self, ifc: CoreInterface) -> str:
|
|
|
|
if has_mtu_mismatch(ifc):
|
|
|
|
return "ip ospf mtu-ignore"
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def quagga_config(self) -> str:
|
|
|
|
router_id = get_router_id(self.node)
|
|
|
|
addresses = []
|
|
|
|
for ifc in self.node.netifs():
|
|
|
|
if getattr(ifc, "control", False):
|
|
|
|
continue
|
|
|
|
for a in ifc.addrlist:
|
|
|
|
addr = a.split("/")[0]
|
|
|
|
if netaddr.valid_ipv4(addr):
|
|
|
|
addresses.append(a)
|
|
|
|
data = dict(router_id=router_id, addresses=addresses)
|
|
|
|
text = """
|
|
|
|
router ospf
|
|
|
|
router-id ${router_id}
|
|
|
|
% for addr in addresses:
|
|
|
|
network ${addr} area 0
|
|
|
|
% endfor
|
|
|
|
!
|
|
|
|
"""
|
|
|
|
return self.render_text(text, data)
|