core-extra/daemon/core/services/bird.py

233 lines
6.8 KiB
Python

"""
bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
"""
from typing import Optional
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService
class Bird(CoreService):
"""
Bird router support
"""
name: str = "bird"
group: str = "BIRD"
executables: tuple[str, ...] = ("bird",)
dirs: tuple[str, ...] = ("/etc/bird",)
configs: tuple[str, ...] = ("/etc/bird/bird.conf",)
startup: tuple[str, ...] = (f"bird -c {configs[0]}",)
shutdown: tuple[str, ...] = ("killall bird",)
validate: tuple[str, ...] = ("pidof bird",)
@classmethod
def generate_config(cls, node: CoreNode, filename: str) -> str:
"""
Return the bird.conf file contents.
"""
if filename == cls.configs[0]:
return cls.generate_bird_config(node)
else:
raise ValueError
@staticmethod
def router_id(node: CoreNode) -> str:
"""
Helper to return the first IPv4 address of a node as its router ID.
"""
for iface in node.get_ifaces(control=False):
ip4 = iface.get_ip4()
if ip4:
return str(ip4.ip)
return "0.0.0.0"
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
"""
Returns configuration file text. Other services that depend on bird
will have hooks that are invoked here.
"""
cfg = f"""\
/* Main configuration file for BIRD. This is ony a template,
* you will *need* to customize it according to your needs
* Beware that only double quotes \'"\' are valid. No singles. */
log "/var/log/{cls.name}.log" all;
#debug protocols all;
#debug commands 2;
router id {cls.router_id(node)}; # Mandatory for IPv6, may be automatic for IPv4
protocol kernel {{
persist; # Don\'t remove routes on BIRD shutdown
scan time 200; # Scan kernel routing table every 200 seconds
export all;
import all;
}}
protocol device {{
scan time 10; # Scan interfaces every 10 seconds
}}
"""
# generate protocol specific configurations
for s in node.services:
if cls.name not in s.dependencies:
continue
if not (isinstance(s, BirdService) or issubclass(s, BirdService)):
continue
cfg += s.generate_bird_config(node)
return cfg
class BirdService(CoreService):
"""
Parent class for Bird services. Defines properties and methods
common to Bird's routing daemons.
"""
name: Optional[str] = None
group: str = "BIRD"
executables: tuple[str, ...] = ("bird",)
dependencies: tuple[str, ...] = ("bird",)
meta: str = "The config file for this service can be found in the bird service."
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
return ""
@classmethod
def generate_bird_iface_config(cls, node: CoreNode) -> str:
"""
Use only bare interfaces descriptions in generated protocol
configurations. This has the slight advantage of being the same
everywhere.
"""
cfg = ""
for iface in node.get_ifaces(control=False):
cfg += f' interface "{iface.name}";\n'
return cfg
class BirdBgp(BirdService):
"""
BGP BIRD Service (configuration generation)
"""
name: str = "BIRD_BGP"
custom_needed: bool = True
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
return """
/* This is a sample config that should be customized with appropriate AS numbers
* and peers; add one section like this for each neighbor */
protocol bgp {
local as 65000; # Customize your AS number
neighbor 198.51.100.130 as 64496; # Customize neighbor AS number && IP
export filter { # We use non-trivial export rules
# This is an example. You should advertise only *your routes*
if (source = RTS_DEVICE) || (source = RTS_OSPF) then {
# bgp_community.add((65000,64501)); # Assign our community
accept;
}
reject;
};
import all;
}
"""
class BirdOspf(BirdService):
"""
OSPF BIRD Service (configuration generation)
"""
name: str = "BIRD_OSPFv2"
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "protocol ospf {\n"
cfg += " export filter {\n"
cfg += " if source = RTS_BGP then {\n"
cfg += " ospf_metric1 = 100;\n"
cfg += " accept;\n"
cfg += " }\n"
cfg += " accept;\n"
cfg += " };\n"
cfg += " area 0.0.0.0 {\n"
cfg += cls.generate_bird_iface_config(node)
cfg += " };\n"
cfg += "}\n\n"
return cfg
class BirdRadv(BirdService):
"""
RADV BIRD Service (configuration generation)
"""
name: str = "BIRD_RADV"
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "/* This is a sample config that must be customized */\n"
cfg += "protocol radv {\n"
cfg += " # auto configuration on all interfaces\n"
cfg += cls.generate_bird_iface_config(node)
cfg += " # Advertise DNS\n"
cfg += " rdnss {\n"
cfg += "# lifetime mult 10;\n"
cfg += "# lifetime mult 10;\n"
cfg += "# ns 2001:0DB8:1234::11;\n"
cfg += "# ns 2001:0DB8:1234::11;\n"
cfg += "# ns 2001:0DB8:1234::12;\n"
cfg += "# ns 2001:0DB8:1234::12;\n"
cfg += " };\n"
cfg += "}\n\n"
return cfg
class BirdRip(BirdService):
"""
RIP BIRD Service (configuration generation)
"""
name: str = "BIRD_RIP"
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "protocol rip {\n"
cfg += " period 10;\n"
cfg += " garbage time 60;\n"
cfg += cls.generate_bird_iface_config(node)
cfg += " honor neighbor;\n"
cfg += " authentication none;\n"
cfg += " import all;\n"
cfg += " export all;\n"
cfg += "}\n\n"
return cfg
class BirdStatic(BirdService):
"""
Static Bird Service (configuration generation)
"""
name: str = "BIRD_static"
custom_needed: bool = True
@classmethod
def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "/* This is a sample config that must be customized */\n"
cfg += "protocol static {\n"
cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n"
cfg += "# route 203.0.113.0/24 reject; # Sink route\n"
cfg += '# route 10.2.0.0/24 via "arc0"; # Secondary network\n'
cfg += "}\n\n"
return cfg