Coverage for birdplan/bird_config/sections/protocols/rip/__init__.py: 97%
192 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 03:27 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 03:27 +0000
1#
2# SPDX-License-Identifier: GPL-3.0-or-later
3#
4# Copyright (c) 2019-2024, AllWorldIT
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
19"""BIRD RIP protocol configuration."""
21from typing import Dict, List, Union
23from .....exceptions import BirdPlanError
24from ....globals import BirdConfigGlobals
25from ...constants import SectionConstants
26from ...functions import SectionFunctions
27from ...tables import SectionTables
28from ..base import SectionProtocolBase
29from ..direct import ProtocolDirect
30from ..pipe import ProtocolPipe, ProtocolPipeFilterType
31from .rip_attributes import RIPAttributes, RIPRoutePolicyAccept, RIPRoutePolicyRedistribute
32from .rip_functions import RIPFunctions
34__all__ = ["ProtocolRIP"]
37RIPInterfaceConfig = Union[bool, Dict[str, str]]
38RIPInterfaces = Dict[str, RIPInterfaceConfig]
41class ProtocolRIP(SectionProtocolBase):
42 """BIRD RIP protocol configuration."""
44 _rip_interfaces: RIPInterfaces
46 _rip_attributes: RIPAttributes
47 # RIP functions
48 _rip_functions: RIPFunctions
50 def __init__(
51 self, birdconfig_globals: BirdConfigGlobals, constants: SectionConstants, functions: SectionFunctions, tables: SectionTables
52 ) -> None:
53 """Initialize the object."""
54 super().__init__(birdconfig_globals, constants, functions, tables)
56 # Set section header
57 self._section = "RIP Protocol"
59 # Interfaces
60 self._rip_interfaces = {}
62 self._rip_attributes = RIPAttributes()
63 # Setup RIP functions
64 self._rip_functions = RIPFunctions(self.birdconfig_globals, self.functions)
66 def configure(self) -> None:
67 """Configure the RIP protocol."""
68 super().configure()
70 # If we don't have any configuration, just abort
71 if not self.interfaces:
72 return
74 self.functions.conf.append(self.rip_functions, deferred=True)
76 self.tables.conf.append("# RIP Tables")
77 self.tables.conf.append("ipv4 table t_rip4;")
78 self.tables.conf.append("ipv6 table t_rip6;")
79 self.tables.conf.append("")
81 self._rip_export_filter()
82 self._rip_import_filter()
83 self._rip_to_master_export_filter()
84 self._rip_to_master_import_filter()
86 # Setup the protocol
87 self._setup_protocol("4")
88 self._setup_protocol("6")
90 # Configure pipe from RIP to the master routing table
91 rip_master_pipe = ProtocolPipe(
92 birdconfig_globals=self.birdconfig_globals,
93 table_from="rip",
94 table_to="master",
95 export_filter_type=ProtocolPipeFilterType.UNVERSIONED,
96 import_filter_type=ProtocolPipeFilterType.UNVERSIONED,
97 )
98 self.conf.add(rip_master_pipe)
100 # Check if we're redistributing connected routes, if we are, create the protocol and pipe
101 if self.route_policy_redistribute.connected:
102 # Create an interface list to feed to our routing table
103 interfaces: List[str] = []
104 if isinstance(self.route_policy_redistribute.connected, list):
105 interfaces = self.route_policy_redistribute.connected
106 # Add direct protocol for redistribution of connected routes
107 rip_direct_protocol = ProtocolDirect(
108 constants=self.constants,
109 functions=self.functions,
110 tables=self.tables,
111 birdconfig_globals=self.birdconfig_globals,
112 name="rip",
113 interfaces=interfaces,
114 )
115 self.conf.add(rip_direct_protocol)
116 # Add pipe
117 rip_direct_pipe = ProtocolPipe(
118 birdconfig_globals=self.birdconfig_globals,
119 name="rip",
120 table_from="rip",
121 table_to="direct",
122 table_export="none",
123 table_import="all",
124 )
125 self.conf.add(rip_direct_pipe)
127 def add_interface(self, interface_name: str, interface_config: RIPInterfaceConfig) -> None:
128 """Add interface to RIP."""
129 # Make sure the interface exists
130 if interface_name not in self.interfaces:
131 self.interfaces[interface_name] = {}
132 # Grab the config so its easier to work with below
133 config = self.interfaces[interface_name]
134 # If the interface is just a boolean, we can return...
135 if isinstance(interface_config, bool):
136 config = interface_config
137 return
138 if not isinstance(config, dict):
139 raise BirdPlanError(f"Conflict RIP config for interface '{interface_name}'")
140 # Work through supported configuration
141 for key, value in interface_config.items():
142 # Make sure key is valid
143 if key not in ("metric", "update-time"):
144 raise BirdPlanError(f"The RIP config for interface '{interface_name}' item '{key}' hasnt been added")
145 # Set the config item
146 config[key] = value
148 def _interface_config(self) -> List[str]:
149 """Generate interface configuration."""
151 interface_lines = []
152 # Loop with interfaces
153 for interface_name in sorted(self.interfaces.keys()):
154 # Set "interface" so things are easier to work with below
155 interface = self.interfaces[interface_name]
156 # If the config is a boolean and its false, skip
157 if isinstance(interface, bool) and not interface:
158 continue
159 # Output interface
160 interface_lines.append(f' interface "{interface_name}" {{')
161 # If its not a bollean we have additional configuration to write out
162 if isinstance(interface, dict):
163 # Loop with config items
164 for key, value in interface.items():
165 if (key == "update-time") and value:
166 interface_lines.append(f" update time {value};")
167 else:
168 interface_lines.append(f" {key} {value};")
169 interface_lines.append(" };")
171 return interface_lines
173 def _setup_protocol(self, ipv: str) -> None:
174 """Set up RIP protocol."""
175 if ipv == "4":
176 self.conf.add(f"protocol rip rip{ipv} {{")
177 elif ipv == "6":
178 self.conf.add(f"protocol rip ng rip{ipv} {{")
179 self.conf.add(f' description "RIP protocol for IPv{ipv}";')
180 self.conf.add("")
181 self.conf.add(f" vrf {self.birdconfig_globals.vrf};")
182 self.conf.add("")
183 self.conf.add(f" ipv{ipv} {{")
184 self.conf.add(f" table t_rip{ipv};")
185 self.conf.add("")
186 self.conf.add(" export filter f_rip_export;")
187 self.conf.add(" import filter f_rip_import;")
188 self.conf.add("")
189 self.conf.add(" };")
190 self.conf.add("")
191 self.conf.add(self._interface_config())
192 self.conf.add("};")
194 def _rip_export_filter(self) -> None:
195 """RIP export filter setup."""
197 # Set our filter name
198 filter_name = "f_rip_export"
200 # Configure RIP export filter
201 self.conf.add("# RIP export filter")
202 self.conf.add(f"filter {filter_name}")
203 self.conf.add("string filter_name;")
204 self.conf.add("{")
205 self.conf.add(f' filter_name = "{filter_name}";')
206 # Redistribute connected
207 if self.route_policy_redistribute.connected:
208 self.conf.add(f" {self.rip_functions.redistribute_connected()};")
209 # Redistribute kernel routes
210 if self.route_policy_redistribute.kernel:
211 self.conf.add(f" {self.functions.redistribute_kernel()};")
212 # Redistribute kernel routes
213 if self.route_policy_redistribute.kernel_default:
214 self.conf.add(f" {self.functions.redistribute_kernel_default()};")
215 # Redistribute RIP routes
216 if self.route_policy_redistribute.rip:
217 self.conf.add(f" {self.rip_functions.redistribute_rip()};")
218 # Redistribute RIP default routes
219 if self.route_policy_redistribute.rip_default:
220 self.conf.add(f" {self.rip_functions.redistribute_rip_default()};")
221 # Redistribute static routes
222 if self.route_policy_redistribute.static:
223 self.conf.add(f" {self.functions.redistribute_static()};")
224 # Redistribute static default routes
225 if self.route_policy_redistribute.static_default:
226 self.conf.add(f" {self.functions.redistribute_static_default()};")
227 # Else reject
228 self.conf.add(" # Reject by default")
229 self.conf.add(" if DEBUG then")
230 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_rip export (fallthrough)";')
231 self.conf.add(" reject;")
232 self.conf.add("};")
233 self.conf.add("")
235 def _rip_import_filter(self) -> None:
236 """RIP import filter setup."""
237 # Set our filter name
238 filter_name = "f_rip_import"
240 # Configure RIP import filter
241 self.conf.add("# RIP import filter")
242 self.conf.add(f"filter {filter_name}")
243 self.conf.add("string filter_name;")
244 self.conf.add("{")
245 self.conf.add(f' filter_name = "{filter_name}";')
246 # Accept all inbound routes into the table
247 self.conf.add(" # Import all RIP routes by default")
248 self.conf.add(" if DEBUG then")
249 self.conf.add(f' print "[{filter_name}] Accepting ", net, " from t_rip import (fallthrough)";')
250 self.conf.add(" accept;")
251 self.conf.add("};")
252 self.conf.add("")
254 def _rip_to_master_export_filter(self) -> None:
255 """RIP to master export filter setup."""
256 # Set our filter name
257 filter_name = "f_rip_master_export"
259 # Configure export filter to master table
260 self.conf.add("# RIP export filter to master table")
261 self.conf.add(f"filter {filter_name}")
262 self.conf.add("string filter_name;")
263 self.conf.add("{")
264 self.conf.add(f' filter_name = "{filter_name}";')
265 # Accept only RIP routes into the master table
266 self.conf.add(" # Export RIP routes to the master table by default")
267 self.conf.add(f" {self.rip_functions.accept_rip()};")
268 # Check if we accept the default route
269 if self.route_policy_accept.default:
270 self.conf.add(" # Export default route to master (accept:rip_default is set)")
271 self.conf.add(f" {self.rip_functions.accept_rip_default()};")
272 # Default to reject
273 self.conf.add(" # Reject by default")
274 self.conf.add(" if DEBUG then")
275 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_rip to master (fallthrough)";')
276 self.conf.add(" reject;")
277 self.conf.add("};")
278 self.conf.add("")
280 def _rip_to_master_import_filter(self) -> None:
281 """RIP import filter from master table."""
282 # Set our filter name
283 filter_name = "f_rip_master_import"
285 # Configure import filter from master table
286 self.conf.add("# RIP import filter from master table")
287 self.conf.add(f"filter {filter_name}")
288 self.conf.add("string filter_name;")
289 self.conf.add("{")
290 self.conf.add(f' filter_name = "{filter_name}";')
291 # Redistribute connected
292 if self.route_policy_redistribute.connected:
293 self.conf.add(f" {self.rip_functions.accept_connected()};")
294 # Redistribute static routes
295 if self.route_policy_redistribute.static:
296 self.conf.add(f" {self.functions.accept_static()};")
297 # Redistribute static default routes
298 if self.route_policy_redistribute.static_default:
299 self.conf.add(f" {self.functions.accept_static_default()};")
300 # Redistribute kernel routes
301 if self.route_policy_redistribute.kernel:
302 self.conf.add(f" {self.functions.accept_kernel()};")
303 # Redistribute kernel default routes
304 if self.route_policy_redistribute.kernel_default:
305 self.conf.add(f" {self.functions.accept_kernel_default()};")
306 # Else accept
307 self.conf.add(" # Reject by default")
308 self.conf.add(" if DEBUG then")
309 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from master to t_rip (fallthrough)";')
310 self.conf.add(" reject;")
311 self.conf.add("};")
312 self.conf.add("")
314 @property
315 def interfaces(self) -> RIPInterfaces:
316 """Return RIP interfaces."""
317 return self._rip_interfaces
319 @property
320 def rip_attributes(self) -> RIPAttributes:
321 """Return our RIP protocol attributes."""
322 return self._rip_attributes
324 @property
325 def rip_functions(self) -> RIPFunctions:
326 """Return our RIP protocol functions."""
327 return self._rip_functions
329 @property
330 def route_policy_accept(self) -> RIPRoutePolicyAccept:
331 """Return our route policy for accepting of routes from peers into the master table."""
332 return self.rip_attributes.route_policy_accept
334 @property
335 def route_policy_redistribute(self) -> RIPRoutePolicyRedistribute:
336 """Return our route policy for redistributing of routes to the main RIP table."""
337 return self.rip_attributes.route_policy_redistribute