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

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 RIP protocol configuration.""" 

20 

21from typing import Dict, List, Union 

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 .rip_attributes import RIPAttributes, RIPRoutePolicyAccept, RIPRoutePolicyRedistribute 

32from .rip_functions import RIPFunctions 

33 

34__all__ = ["ProtocolRIP"] 

35 

36 

37RIPInterfaceConfig = Union[bool, Dict[str, str]] 

38RIPInterfaces = Dict[str, RIPInterfaceConfig] 

39 

40 

41class ProtocolRIP(SectionProtocolBase): 

42 """BIRD RIP protocol configuration.""" 

43 

44 _rip_interfaces: RIPInterfaces 

45 

46 _rip_attributes: RIPAttributes 

47 # RIP functions 

48 _rip_functions: RIPFunctions 

49 

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) 

55 

56 # Set section header 

57 self._section = "RIP Protocol" 

58 

59 # Interfaces 

60 self._rip_interfaces = {} 

61 

62 self._rip_attributes = RIPAttributes() 

63 # Setup RIP functions 

64 self._rip_functions = RIPFunctions(self.birdconfig_globals, self.functions) 

65 

66 def configure(self) -> None: 

67 """Configure the RIP protocol.""" 

68 super().configure() 

69 

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

71 if not self.interfaces: 

72 return 

73 

74 self.functions.conf.append(self.rip_functions, deferred=True) 

75 

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

80 

81 self._rip_export_filter() 

82 self._rip_import_filter() 

83 self._rip_to_master_export_filter() 

84 self._rip_to_master_import_filter() 

85 

86 # Setup the protocol 

87 self._setup_protocol("4") 

88 self._setup_protocol("6") 

89 

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) 

99 

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) 

126 

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 

147 

148 def _interface_config(self) -> List[str]: 

149 """Generate interface configuration.""" 

150 

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

170 

171 return interface_lines 

172 

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

193 

194 def _rip_export_filter(self) -> None: 

195 """RIP export filter setup.""" 

196 

197 # Set our filter name 

198 filter_name = "f_rip_export" 

199 

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

234 

235 def _rip_import_filter(self) -> None: 

236 """RIP import filter setup.""" 

237 # Set our filter name 

238 filter_name = "f_rip_import" 

239 

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

253 

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" 

258 

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

279 

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" 

284 

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

313 

314 @property 

315 def interfaces(self) -> RIPInterfaces: 

316 """Return RIP interfaces.""" 

317 return self._rip_interfaces 

318 

319 @property 

320 def rip_attributes(self) -> RIPAttributes: 

321 """Return our RIP protocol attributes.""" 

322 return self._rip_attributes 

323 

324 @property 

325 def rip_functions(self) -> RIPFunctions: 

326 """Return our RIP protocol functions.""" 

327 return self._rip_functions 

328 

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 

333 

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