Coverage for birdplan/bird_config/sections/protocols/ospf/__init__.py: 97%
180 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 OSPF protocol configuration."""
21from typing import Dict, List
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 .area import ProtocolOSPFArea
32from .area.ospf_area_types import OSPFAreaConfig
33from .ospf_attributes import OSPFAttributes, OSPFRoutePolicyAccept, OSPFRoutePolicyRedistribute
34from .ospf_functions import OSPFFunctions
36__all__ = ["ProtocolOSPF"]
39OSPFAreas = Dict[str, ProtocolOSPFArea]
42class ProtocolOSPF(SectionProtocolBase):
43 """BIRD OSPF protocol configuration."""
45 _areas: OSPFAreas
47 _v4version: str
49 _ospf_attributes: OSPFAttributes
50 # OSPF functions
51 _ospf_functions: OSPFFunctions
53 def __init__(
54 self, birdconfig_globals: BirdConfigGlobals, constants: SectionConstants, functions: SectionFunctions, tables: SectionTables
55 ):
56 """Initialize the object."""
57 super().__init__(birdconfig_globals, constants, functions, tables)
59 # Set section name
60 self._section = "OSPF Protocol"
62 # OSPF areas
63 self._areas = {}
65 # OSPF version to use for IPv4
66 self._v4version = "2"
68 self._ospf_attributes = OSPFAttributes()
69 # Setup OSPF functions
70 self._ospf_functions = OSPFFunctions(self.birdconfig_globals, self.functions)
72 def configure(self) -> None:
73 """Configure the OSPF protocol."""
74 super().configure()
76 # If we don't have any configuration, just abort
77 if not self.areas:
78 return
80 self.functions.conf.append(self.ospf_functions, deferred=True)
82 self.tables.conf.append("# OSPF Tables")
83 self.tables.conf.append("ipv4 table t_ospf4;")
84 self.tables.conf.append("ipv6 table t_ospf6;")
85 self.tables.conf.append("")
87 self._ospf_export_filter()
88 self._ospf_import_filter()
89 self._ospf_to_master_export_filter()
90 self._ospf_to_master_import_filter()
92 # OSPF protocol configuration
93 self._setup_protocol("4")
94 self._setup_protocol("6")
96 # Configure pipe from OSPF to the master routing table
97 ospf_master_pipe = ProtocolPipe(
98 birdconfig_globals=self.birdconfig_globals,
99 table_from="ospf",
100 table_to="master",
101 export_filter_type=ProtocolPipeFilterType.UNVERSIONED,
102 import_filter_type=ProtocolPipeFilterType.UNVERSIONED,
103 )
104 self.conf.add(ospf_master_pipe)
106 # Check if we're redistributing connected routes, if we are, create the protocol and pipe
107 if self.route_policy_redistribute.connected:
108 # Create an interface list to feed to our routing table
109 interfaces: List[str] = []
110 if isinstance(self.route_policy_redistribute.connected, list):
111 interfaces = self.route_policy_redistribute.connected
112 # Add direct protocol for redistribution of connected routes
113 ospf_direct_protocol = ProtocolDirect(
114 self.birdconfig_globals,
115 self.constants,
116 self.functions,
117 self.tables,
118 name="ospf",
119 interfaces=interfaces,
120 )
121 self.conf.add(ospf_direct_protocol)
122 # Add pipe
123 ospf_direct_pipe = ProtocolPipe(
124 self.birdconfig_globals,
125 name="ospf",
126 table_from="ospf",
127 table_to="direct",
128 table_export="none",
129 table_import="all",
130 )
131 self.conf.add(ospf_direct_pipe)
133 def add_area(self, area_name: str, area_config: OSPFAreaConfig) -> ProtocolOSPFArea:
134 """Add area to OSPF."""
136 # Make sure area doesn't exist
137 if area_name in self.areas:
138 raise BirdPlanError(f"OSPF area '{area_name}' already exists")
140 # Create OSPF area object
141 area = ProtocolOSPFArea(
142 self.birdconfig_globals,
143 self.constants,
144 self.functions,
145 self.tables,
146 self.ospf_attributes,
147 area_name,
148 area_config,
149 )
151 # Add area to OSPF
152 self.areas[area.name] = area # Use the sanitized area name from the area object
154 return area
156 def _setup_protocol(self, ipv: str) -> None:
157 # Work out which OSPF protocol version to use
158 protocol_version = "3"
159 if ipv == "4":
160 protocol_version = self.v4version
162 self.conf.add(f"protocol ospf v{protocol_version} ospf{ipv} {{")
163 self.conf.add(f' description "OSPF protocol for IPv{ipv}";')
164 self.conf.add("")
165 self.conf.add(f" vrf {self.birdconfig_globals.vrf};")
166 self.conf.add("")
167 self.conf.add(f" ipv{ipv} {{")
168 self.conf.add(f" table t_ospf{ipv};")
169 self.conf.add("")
170 self.conf.add(" export filter f_ospf_export;")
171 self.conf.add(" import filter f_ospf_import;")
172 self.conf.add("")
173 self.conf.add(" };")
174 self.conf.add("")
175 # Add areas
176 for _, area in sorted(self.areas.items()):
177 self.conf.add(area)
178 # Close off block
179 self.conf.add("};")
180 self.conf.add("")
182 def _ospf_export_filter(self) -> None:
183 """OSPF export filter setup."""
184 # Set our filter name
185 filter_name = "f_ospf_export"
187 # Configure OSPF export filter
188 self.conf.add("# OSPF export filter")
189 self.conf.add(f"filter {filter_name}")
190 self.conf.add("string filter_name;")
191 self.conf.add("{")
192 self.conf.add(f' filter_name = "{filter_name}";')
193 # Redistribute connected
194 if self.route_policy_redistribute.connected:
195 self.conf.add(f" {self.ospf_functions.redistribute_connected()};")
196 # Redistribute kernel routes
197 if self.route_policy_redistribute.kernel:
198 self.conf.add(f" {self.functions.redistribute_kernel()};")
199 # Redistribute kernel routes
200 if self.route_policy_redistribute.kernel_default:
201 self.conf.add(f" {self.functions.redistribute_kernel_default()};")
202 # NK: May affect inter-area routes???? removed for now
203 # # Redistribute OSPF routes
204 # self.conf.add(f" {self.functions.redistribute_ospf()};")
205 # Redistribute static routes
206 if self.route_policy_redistribute.static:
207 self.conf.add(f" {self.functions.redistribute_static()};")
208 # Redistribute static default routes
209 if self.route_policy_redistribute.static_default:
210 self.conf.add(f" {self.functions.redistribute_static_default()};")
211 # Else reject
212 self.conf.add(" if DEBUG then")
213 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_ospf export (fallthrough)";')
214 self.conf.add(" reject;")
215 self.conf.add("};")
216 self.conf.add("")
218 def _ospf_import_filter(self) -> None:
219 """OSPF import filter setup."""
220 # Set our filter name
221 filter_name = "f_ospf_import"
223 # Configure OSPF import filter
224 self.conf.add("# OSPF import filter")
225 self.conf.add(f"filter {filter_name}")
226 self.conf.add("string filter_name;")
227 self.conf.add("{")
228 # Accept all inbound routes into the table
229 self.conf.add(" # Import all OSPF routes by default")
230 self.conf.add(" if DEBUG then")
231 self.conf.add(f' print "[{filter_name}] Accepting ", net, " from t_ospf import (fallthrough)";')
232 self.conf.add(" accept;")
233 self.conf.add("};")
234 self.conf.add("")
236 def _ospf_to_master_export_filter(self) -> None:
237 """OSPF to master export filter setup."""
238 # Set our filter name
239 filter_name = "f_ospf_master_export"
241 # Configure export filter to master table
242 self.conf.add("# OSPF export filter to master table")
243 self.conf.add(f"filter {filter_name}")
244 self.conf.add("string filter_name;")
245 self.conf.add("{")
246 self.conf.add(f' filter_name = "{filter_name}";')
247 # Accept only OSPF routes into the master table
248 self.conf.add(" # Export OSPF routes to the master table by default")
249 self.conf.add(f" {self.ospf_functions.accept_ospf()};")
250 # Check if we accept the default route
251 if self.route_policy_accept.default:
252 self.conf.add(" # Export default route to master (accept:ospf_default is set)")
253 self.conf.add(f" {self.ospf_functions.accept_ospf_default()};")
254 # Default to reject
255 self.conf.add(" # Reject everything else;")
256 self.conf.add(" if DEBUG then")
257 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from t_ospf to master (fallthrough)";')
258 self.conf.add(" reject;")
259 self.conf.add("};")
260 self.conf.add("")
262 def _ospf_to_master_import_filter(self) -> None:
263 """OSPF to master import filter setup."""
264 # Set our filter name
265 filter_name = "f_ospf_master_import"
267 # Configure import filter from master table
268 self.conf.add("# OSPF import filter from master table")
269 self.conf.add(f"filter {filter_name}")
270 self.conf.add("string filter_name;")
271 self.conf.add("{")
272 self.conf.add(f' filter_name = "{filter_name}";')
273 # Redistribute connected
274 if self.route_policy_redistribute.connected:
275 self.conf.add(f" {self.ospf_functions.accept_connected()};")
276 # Redistribute static routes
277 if self.route_policy_redistribute.static:
278 self.conf.add(f" {self.functions.accept_static()};")
279 # Redistribute static default routes
280 if self.route_policy_redistribute.static_default:
281 self.conf.add(f" {self.functions.accept_static_default()};")
282 # Redistribute kernel routes
283 if self.route_policy_redistribute.kernel:
284 self.conf.add(f" {self.functions.accept_kernel()};")
285 # Redistribute kernel default routes
286 if self.route_policy_redistribute.kernel_default:
287 self.conf.add(f" {self.functions.accept_kernel_default()};")
288 # Else accept
289 self.conf.add(" # Reject by default")
290 self.conf.add(" if DEBUG then")
291 self.conf.add(f' print "[{filter_name}] Rejecting ", net, " from master to t_ospf (fallthrough)";')
292 self.conf.add(" reject;")
293 self.conf.add("};")
294 self.conf.add("")
296 def area(self, name: str) -> ProtocolOSPFArea:
297 """Return a OSPF area configuration object."""
298 if name not in self.areas:
299 raise BirdPlanError(f"Area '{name}' not found")
300 return self.areas[name]
302 @property
303 def areas(self) -> OSPFAreas:
304 """Return OSPF areas."""
305 return self._areas
307 @property
308 def v4version(self) -> str:
309 """Return OSPF IPv4 version to use."""
310 return self._v4version
312 @v4version.setter
313 def v4version(self, v4version: str) -> None:
314 """Set the OSPF IPv4 version to use."""
315 self._v4version = v4version
317 @property
318 def ospf_attributes(self) -> OSPFAttributes:
319 """Return our OSPF protocol attributes."""
320 return self._ospf_attributes
322 @property
323 def ospf_functions(self) -> OSPFFunctions:
324 """Return our OSPF protocol functions."""
325 return self._ospf_functions
327 @property
328 def route_policy_accept(self) -> OSPFRoutePolicyAccept:
329 """Return our route policy for accepting of routes from peers into the master table."""
330 return self.ospf_attributes.route_policy_accept
332 @property
333 def route_policy_redistribute(self) -> OSPFRoutePolicyRedistribute:
334 """Return our route policy for redistributing of routes to the main OSPF table."""
335 return self.ospf_attributes.route_policy_redistribute