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

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/>. 

18 

19"""BIRD OSPF protocol configuration.""" 

20 

21from typing import Dict, List 

22 

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 

35 

36__all__ = ["ProtocolOSPF"] 

37 

38 

39OSPFAreas = Dict[str, ProtocolOSPFArea] 

40 

41 

42class ProtocolOSPF(SectionProtocolBase): 

43 """BIRD OSPF protocol configuration.""" 

44 

45 _areas: OSPFAreas 

46 

47 _v4version: str 

48 

49 _ospf_attributes: OSPFAttributes 

50 # OSPF functions 

51 _ospf_functions: OSPFFunctions 

52 

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) 

58 

59 # Set section name 

60 self._section = "OSPF Protocol" 

61 

62 # OSPF areas 

63 self._areas = {} 

64 

65 # OSPF version to use for IPv4 

66 self._v4version = "2" 

67 

68 self._ospf_attributes = OSPFAttributes() 

69 # Setup OSPF functions 

70 self._ospf_functions = OSPFFunctions(self.birdconfig_globals, self.functions) 

71 

72 def configure(self) -> None: 

73 """Configure the OSPF protocol.""" 

74 super().configure() 

75 

76 # If we don't have any configuration, just abort 

77 if not self.areas: 

78 return 

79 

80 self.functions.conf.append(self.ospf_functions, deferred=True) 

81 

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("") 

86 

87 self._ospf_export_filter() 

88 self._ospf_import_filter() 

89 self._ospf_to_master_export_filter() 

90 self._ospf_to_master_import_filter() 

91 

92 # OSPF protocol configuration 

93 self._setup_protocol("4") 

94 self._setup_protocol("6") 

95 

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) 

105 

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) 

132 

133 def add_area(self, area_name: str, area_config: OSPFAreaConfig) -> ProtocolOSPFArea: 

134 """Add area to OSPF.""" 

135 

136 # Make sure area doesn't exist 

137 if area_name in self.areas: 

138 raise BirdPlanError(f"OSPF area '{area_name}' already exists") 

139 

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 ) 

150 

151 # Add area to OSPF 

152 self.areas[area.name] = area # Use the sanitized area name from the area object 

153 

154 return area 

155 

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 

161 

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("") 

181 

182 def _ospf_export_filter(self) -> None: 

183 """OSPF export filter setup.""" 

184 # Set our filter name 

185 filter_name = "f_ospf_export" 

186 

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("") 

217 

218 def _ospf_import_filter(self) -> None: 

219 """OSPF import filter setup.""" 

220 # Set our filter name 

221 filter_name = "f_ospf_import" 

222 

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("") 

235 

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" 

240 

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("") 

261 

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" 

266 

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("") 

295 

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] 

301 

302 @property 

303 def areas(self) -> OSPFAreas: 

304 """Return OSPF areas.""" 

305 return self._areas 

306 

307 @property 

308 def v4version(self) -> str: 

309 """Return OSPF IPv4 version to use.""" 

310 return self._v4version 

311 

312 @v4version.setter 

313 def v4version(self, v4version: str) -> None: 

314 """Set the OSPF IPv4 version to use.""" 

315 self._v4version = v4version 

316 

317 @property 

318 def ospf_attributes(self) -> OSPFAttributes: 

319 """Return our OSPF protocol attributes.""" 

320 return self._ospf_attributes 

321 

322 @property 

323 def ospf_functions(self) -> OSPFFunctions: 

324 """Return our OSPF protocol functions.""" 

325 return self._ospf_functions 

326 

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 

331 

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