Coverage for src/birdplan/bird_config/sections/protocols/rip/__init__.py: 97%
188 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 07:38 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-25 07:38 +0000
1#
2# SPDX-License-Identifier: GPL-3.0-or-later
3#
4# Copyright (c) 2019-2025, 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 .....exceptions import BirdPlanError
22from ....globals import BirdConfigGlobals
23from ...bird_attributes import SectionBirdAttributes
24from ...constants import SectionConstants
25from ...functions import SectionFunctions
26from ...tables import SectionTables
27from ..base import SectionProtocolBase
28from ..direct import ProtocolDirect
29from ..pipe import ProtocolPipe, ProtocolPipeFilterType
30from .rip_attributes import RIPAttributes, RIPRoutePolicyAccept, RIPRoutePolicyRedistribute
31from .rip_functions import RIPFunctions
33__all__ = ["ProtocolRIP"]
36RIPInterfaceConfig = bool | dict[str, str]
37RIPInterfaces = dict[str, RIPInterfaceConfig]
40class ProtocolRIP(SectionProtocolBase):
41 """BIRD RIP protocol configuration."""
43 _rip_interfaces: RIPInterfaces
45 _rip_attributes: RIPAttributes
46 # RIP functions
47 _rip_functions: RIPFunctions
49 def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments
50 self,
51 birdconfig_globals: BirdConfigGlobals,
52 birdattributes: SectionBirdAttributes,
53 constants: SectionConstants,
54 functions: SectionFunctions,
55 tables: SectionTables,
56 ) -> None:
57 """Initialize the object."""
58 super().__init__(birdconfig_globals, birdattributes, constants, functions, tables)
60 # Set section header
61 self._section = "RIP Protocol"
63 # Interfaces
64 self._rip_interfaces = {}
66 self._rip_attributes = RIPAttributes()
67 # Setup RIP functions
68 self._rip_functions = RIPFunctions(self.birdconfig_globals, self.functions)
70 def configure(self) -> None:
71 """Configure the RIP protocol."""
72 super().configure()
74 # If we don't have any configuration, just abort
75 if not self.interfaces:
76 return
78 self.functions.conf.append(self.rip_functions, deferred=True)
80 self.tables.conf.append("# RIP Tables")
81 self.tables.conf.append("ipv4 table t_rip4;")
82 self.tables.conf.append("ipv6 table t_rip6;")
83 self.tables.conf.append("")
85 self._rip_export_filter()
86 self._rip_import_filter()
87 self._rip_to_master_export_filter()
88 self._rip_to_master_import_filter()
90 # Setup the protocol
91 self._setup_protocol("4")
92 self._setup_protocol("6")
94 # Configure pipe from RIP to the master routing table
95 rip_master_pipe = ProtocolPipe(
96 birdconfig_globals=self.birdconfig_globals,
97 table_from="rip",
98 table_to="master",
99 export_filter_type=ProtocolPipeFilterType.UNVERSIONED,
100 import_filter_type=ProtocolPipeFilterType.UNVERSIONED,
101 )
102 self.conf.add(rip_master_pipe)
104 # Check if we're redistributing connected routes, if we are, create the protocol and pipe
105 if self.route_policy_redistribute.connected:
106 # Create an interface list to feed to our routing table
107 interfaces: list[str] = []
108 if isinstance(self.route_policy_redistribute.connected, list):
109 interfaces = self.route_policy_redistribute.connected
110 # Add direct protocol for redistribution of connected routes
111 rip_direct_protocol = ProtocolDirect(
112 constants=self.constants,
113 functions=self.functions,
114 tables=self.tables,
115 birdconfig_globals=self.birdconfig_globals,
116 birdattributes=self.birdattributes,
117 name="rip",
118 interfaces=interfaces,
119 )
120 self.conf.add(rip_direct_protocol)
121 # Add pipe
122 rip_direct_pipe = ProtocolPipe(
123 birdconfig_globals=self.birdconfig_globals,
124 name="rip",
125 table_from="rip",
126 table_to="direct",
127 table_export="none",
128 table_import="all",
129 )
130 self.conf.add(rip_direct_pipe)
132 def add_interface(self, interface_name: str, interface_config: RIPInterfaceConfig) -> None:
133 """Add interface to RIP."""
134 # Make sure the interface exists
135 if interface_name not in self.interfaces:
136 self.interfaces[interface_name] = {}
137 # Grab the config so its easier to work with below
138 config = self.interfaces[interface_name]
139 # If the interface is just a boolean, we can return...
140 if isinstance(interface_config, bool):
141 config = interface_config
142 return
143 if not isinstance(config, dict):
144 raise BirdPlanError(f"Conflict RIP config for interface '{interface_name}'")
145 # Work through supported configuration
146 for key, value in interface_config.items():
147 # Make sure key is valid
148 if key not in ("metric", "update-time"):
149 raise BirdPlanError(f"The RIP config for interface '{interface_name}' item '{key}' hasnt been added")
150 # Set the config item
151 config[key] = value
153 def _interface_config(self) -> list[str]:
154 """Generate interface configuration."""
156 interface_lines = []
157 # Loop with interfaces
158 for interface_name in sorted(self.interfaces.keys()):
159 # Set "interface" so things are easier to work with below
160 interface = self.interfaces[interface_name]
161 # If the config is a boolean and its false, skip
162 if isinstance(interface, bool) and not interface:
163 continue
164 # Output interface
165 interface_lines.append(f' interface "{interface_name}" {{')
166 # If its not a bollean we have additional configuration to write out
167 if isinstance(interface, dict):
168 # Loop with config items
169 for key, value in interface.items():
170 if (key == "update-time") and value:
171 interface_lines.append(f" update time {value};")
172 else:
173 interface_lines.append(f" {key} {value};")
174 interface_lines.append(" };")
176 return interface_lines
178 def _setup_protocol(self, ipv: str) -> None:
179 """Set up RIP protocol."""
180 if ipv == "4":
181 self.conf.add(f"protocol rip rip{ipv} {{")
182 elif ipv == "6":
183 self.conf.add(f"protocol rip ng rip{ipv} {{")
184 self.conf.add(f' description "RIP protocol for IPv{ipv}";')
185 self.conf.add("")
186 self.conf.add(f" vrf {self.birdconfig_globals.vrf};")
187 self.conf.add("")
188 self.conf.add(f" ipv{ipv} {{")
189 self.conf.add(f" table t_rip{ipv};")
190 self.conf.add("")
191 self.conf.add(" export filter f_rip_export;")
192 self.conf.add(" import filter f_rip_import;")
193 self.conf.add("")
194 self.conf.add(" };")
195 self.conf.add("")
196 self.conf.add(self._interface_config())
197 self.conf.add("};")
199 def _rip_export_filter(self) -> None:
200 """RIP export filter setup."""
202 # Set our filter name
203 filter_name = "f_rip_export"
205 # Configure RIP export filter
206 self.conf.add("# RIP export filter")
207 self.conf.add(f"filter {filter_name}")
208 self.conf.add("string filter_name;")
209 self.conf.add("{")
210 self.conf.add(f' filter_name = "{filter_name}";')
211 # Redistribute connected
212 if self.route_policy_redistribute.connected:
213 self.conf.add(f" {self.rip_functions.redistribute_connected()};")
214 # Redistribute kernel routes
215 if self.route_policy_redistribute.kernel:
216 self.conf.add(f" {self.functions.redistribute_kernel()};")
217 # Redistribute kernel routes
218 if self.route_policy_redistribute.kernel_default:
219 self.conf.add(f" {self.functions.redistribute_kernel_default()};")
220 # Redistribute RIP routes
221 if self.route_policy_redistribute.rip:
222 self.conf.add(f" {self.rip_functions.redistribute_rip()};")
223 # Redistribute RIP default routes
224 if self.route_policy_redistribute.rip_default:
225 self.conf.add(f" {self.rip_functions.redistribute_rip_default()};")
226 # Redistribute static routes
227 if self.route_policy_redistribute.static:
228 self.conf.add(f" {self.functions.redistribute_static()};")
229 # Redistribute static default routes
230 if self.route_policy_redistribute.static_default:
231 self.conf.add(f" {self.functions.redistribute_static_default()};")
232 # Else reject
233 self.conf.add(" # Reject by default")
234 self.conf.add(" if DEBUG then")
235 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_rip export (fallthrough)";')
236 self.conf.add(" reject;")
237 self.conf.add("};")
238 self.conf.add("")
240 def _rip_import_filter(self) -> None:
241 """RIP import filter setup."""
242 # Set our filter name
243 filter_name = "f_rip_import"
245 # Configure RIP import filter
246 self.conf.add("# RIP import filter")
247 self.conf.add(f"filter {filter_name}")
248 self.conf.add("string filter_name;")
249 self.conf.add("{")
250 self.conf.add(f' filter_name = "{filter_name}";')
251 # Accept all inbound routes into the table
252 self.conf.add(" # Import all RIP routes by default")
253 self.conf.add(" if DEBUG then")
254 self.conf.add(f' print "[{filter_name}] Accepting ", net, " from t_rip import (fallthrough)";')
255 self.conf.add(" accept;")
256 self.conf.add("};")
257 self.conf.add("")
259 def _rip_to_master_export_filter(self) -> None:
260 """RIP to master export filter setup."""
261 # Set our filter name
262 filter_name = "f_rip_master_export"
264 # Configure export filter to master table
265 self.conf.add("# RIP export filter to master table")
266 self.conf.add(f"filter {filter_name}")
267 self.conf.add("string filter_name;")
268 self.conf.add("{")
269 self.conf.add(f' filter_name = "{filter_name}";')
270 # Accept only RIP routes into the master table
271 self.conf.add(" # Export RIP routes to the master table by default")
272 self.conf.add(f" {self.rip_functions.accept_rip()};")
273 # Check if we accept the default route
274 if self.route_policy_accept.default:
275 self.conf.add(" # Export default route to master (accept:rip_default is set)")
276 self.conf.add(f" {self.rip_functions.accept_rip_default()};")
277 # Default to reject
278 self.conf.add(" # Reject by default")
279 self.conf.add(" if DEBUG then")
280 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_rip to master (fallthrough)";')
281 self.conf.add(" reject;")
282 self.conf.add("};")
283 self.conf.add("")
285 def _rip_to_master_import_filter(self) -> None:
286 """RIP import filter from master table."""
287 # Set our filter name
288 filter_name = "f_rip_master_import"
290 # Configure import filter from master table
291 self.conf.add("# RIP import filter from master table")
292 self.conf.add(f"filter {filter_name}")
293 self.conf.add("string filter_name;")
294 self.conf.add("{")
295 self.conf.add(f' filter_name = "{filter_name}";')
296 # Redistribute connected
297 if self.route_policy_redistribute.connected:
298 self.conf.add(f" {self.rip_functions.accept_connected()};")
299 # Redistribute static routes
300 if self.route_policy_redistribute.static:
301 self.conf.add(f" {self.functions.accept_static()};")
302 # Redistribute static default routes
303 if self.route_policy_redistribute.static_default:
304 self.conf.add(f" {self.functions.accept_static_default()};")
305 # Redistribute kernel routes
306 if self.route_policy_redistribute.kernel:
307 self.conf.add(f" {self.functions.accept_kernel()};")
308 # Redistribute kernel default routes
309 if self.route_policy_redistribute.kernel_default:
310 self.conf.add(f" {self.functions.accept_kernel_default()};")
311 # Else accept
312 self.conf.add(" # Reject by default")
313 self.conf.add(" if DEBUG then")
314 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from master to t_rip (fallthrough)";')
315 self.conf.add(" reject;")
316 self.conf.add("};")
317 self.conf.add("")
319 @property
320 def interfaces(self) -> RIPInterfaces:
321 """Return RIP interfaces."""
322 return self._rip_interfaces
324 @property
325 def rip_attributes(self) -> RIPAttributes:
326 """Return our RIP protocol attributes."""
327 return self._rip_attributes
329 @property
330 def rip_functions(self) -> RIPFunctions:
331 """Return our RIP protocol functions."""
332 return self._rip_functions
334 @property
335 def route_policy_accept(self) -> RIPRoutePolicyAccept:
336 """Return our route policy for accepting of routes from peers into the master table."""
337 return self.rip_attributes.route_policy_accept
339 @property
340 def route_policy_redistribute(self) -> RIPRoutePolicyRedistribute:
341 """Return our route policy for redistributing of routes to the main RIP table."""
342 return self.rip_attributes.route_policy_redistribute