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

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

18 

19"""BIRD RIP protocol configuration.""" 

20 

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 

32 

33__all__ = ["ProtocolRIP"] 

34 

35 

36RIPInterfaceConfig = bool | dict[str, str] 

37RIPInterfaces = dict[str, RIPInterfaceConfig] 

38 

39 

40class ProtocolRIP(SectionProtocolBase): 

41 """BIRD RIP protocol configuration.""" 

42 

43 _rip_interfaces: RIPInterfaces 

44 

45 _rip_attributes: RIPAttributes 

46 # RIP functions 

47 _rip_functions: RIPFunctions 

48 

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) 

59 

60 # Set section header 

61 self._section = "RIP Protocol" 

62 

63 # Interfaces 

64 self._rip_interfaces = {} 

65 

66 self._rip_attributes = RIPAttributes() 

67 # Setup RIP functions 

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

69 

70 def configure(self) -> None: 

71 """Configure the RIP protocol.""" 

72 super().configure() 

73 

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

75 if not self.interfaces: 

76 return 

77 

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

79 

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

84 

85 self._rip_export_filter() 

86 self._rip_import_filter() 

87 self._rip_to_master_export_filter() 

88 self._rip_to_master_import_filter() 

89 

90 # Setup the protocol 

91 self._setup_protocol("4") 

92 self._setup_protocol("6") 

93 

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) 

103 

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) 

131 

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 

152 

153 def _interface_config(self) -> list[str]: 

154 """Generate interface configuration.""" 

155 

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

175 

176 return interface_lines 

177 

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

198 

199 def _rip_export_filter(self) -> None: 

200 """RIP export filter setup.""" 

201 

202 # Set our filter name 

203 filter_name = "f_rip_export" 

204 

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

239 

240 def _rip_import_filter(self) -> None: 

241 """RIP import filter setup.""" 

242 # Set our filter name 

243 filter_name = "f_rip_import" 

244 

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

258 

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" 

263 

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

284 

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" 

289 

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

318 

319 @property 

320 def interfaces(self) -> RIPInterfaces: 

321 """Return RIP interfaces.""" 

322 return self._rip_interfaces 

323 

324 @property 

325 def rip_attributes(self) -> RIPAttributes: 

326 """Return our RIP protocol attributes.""" 

327 return self._rip_attributes 

328 

329 @property 

330 def rip_functions(self) -> RIPFunctions: 

331 """Return our RIP protocol functions.""" 

332 return self._rip_functions 

333 

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 

338 

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