Coverage for birdplan/bird_config/sections/functions.py: 96%
100 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 functions configuration."""
21import textwrap
22from collections import OrderedDict
23from typing import Any, Callable, Dict, List
25from ...bird_config.globals import BirdConfigGlobals
26from .base import SectionBase
28__all__ = ["BirdVariable", "BirdFunction", "SectionFunctions"]
31class BirdVariable(str):
32 """BIRD constant class."""
35class BirdFunction: # pylint: disable=invalid-name,too-few-public-methods
36 r'''
37 BIRD function decorator class.
39 This decorator is used to decorate a BIRD function returned in Python.
41 When the decorated function is used, a reference to the function is stored and when the configuration is
42 generated the function is called and the output included in the configuration functions section.
44 An example of a decorated function is below...
45 ```
46 @BirdFunction("bgp_some_function")
47 def bgp_func(self) -> str:
48 """Test function."""
50 return """\
51 # Some BIRD function
52 function bgp_some_function(string filter_name; bool capable; int peer_asn; int type_asn) {
53 ...
54 }
55 }"""
56 ```
58 This would be used in BIRD configuration like this...
59 ```
60 conf.append(f"if {obj.bgpfunc('hello world')} then print 'hello there';")
61 ```
63 Parameters
64 ----------
65 bird_func_name : str
66 BIRD function name.
68 ''' # noqa: RST201,RST203,RST215,RST214,RST301
70 bird_func_name: str
72 def __init__(self, bird_func_name: str):
73 """Initialize object."""
74 # Lets keep track of our BIRD function name
75 self.bird_func_name = bird_func_name
77 def __call__(self, func: Callable[..., str]) -> Callable[..., str]:
78 """Return the wrapper."""
80 def wrapped_function(*args: Any, **kwargs: Any) -> str:
81 """My decorator."""
82 # Grab the parent object
83 parent_object = args[0]
84 # Make sure it has a bird_functions attribute
85 if hasattr(parent_object, "bird_functions"):
86 # Check if this function exists...
87 if self.bird_func_name not in parent_object.bird_functions:
88 # If not add it to the function list
89 parent_object.bird_functions[self.bird_func_name] = func(*args)
90 else:
91 raise RuntimeError("Decorator 'bird_function' used on a class method without a 'bird_functions' attribute")
93 bird_args: List[str] = []
94 # Check if we're not outputting the filter_name
95 needs_filter_name = not kwargs.get("no_filter_name", False)
96 if needs_filter_name:
97 bird_args.append(BirdVariable("filter_name"))
98 # Loop with python arguments and translate into BIRD arguments
99 for arg in args[1:]:
100 value = ""
101 # Check for unquaoted BIRD variables
102 if isinstance(arg, BirdVariable):
103 value = arg
104 # Check for a boolean
105 elif isinstance(arg, bool):
106 if arg:
107 value = "true"
108 else:
109 value = "false"
110 # Check for a number
111 elif isinstance(arg, int):
112 value = f"{arg}"
113 # Check if this is a string
114 elif isinstance(arg, str):
115 value = f'"{arg}"'
116 # Everything else is not implemented atm
117 else:
118 raise NotImplementedError(f"Unknown type for '{self.bird_func_name}' value '{arg}'")
119 # Add BIRD argument
120 bird_args.append(value)
122 # Build the list of BIRD arguments in a string
123 bird_args_str = ", ".join(bird_args)
124 # Return the BIRD function call
125 return f"{self.bird_func_name}({bird_args_str})"
127 # Finally return the wrapped function
128 return wrapped_function
131class SectionFunctions(SectionBase):
132 """BIRD functions configuration."""
134 bird_functions: Dict[str, str]
136 def __init__(self, birdconfig_globals: BirdConfigGlobals):
137 """Initialize the object."""
138 super().__init__(birdconfig_globals)
140 self._section = "Global Functions"
142 self.bird_functions = OrderedDict()
144 def configure(self) -> None:
145 """Configure global constants."""
146 super().configure()
148 # Check if we're adding functions
149 for _, content in self.bird_functions.items():
150 self.conf.add(textwrap.dedent(content))
151 self.conf.add("")
153 @BirdFunction("prefix_is_longer")
154 def prefix_is_longer(self, *args: Any) -> str: # pylint: disable=unused-argument
155 """BIRD prefix_is_longer function."""
157 return """\
158 # Match a prefix longer than "size"
159 function prefix_is_longer(string filter_name; int size) -> bool {
160 if (net.len > size) then {
161 if DEBUG then print filter_name,
162 " [prefix_is_longer] Matched ", net, " from ", proto, " against size ", size;
163 return true;
164 } else {
165 return false;
166 }
167 }"""
169 @BirdFunction("prefix_is_shorter")
170 def prefix_is_shorter(self, *args: Any) -> str: # pylint: disable=unused-argument
171 """BIRD prefix_is_shorter function."""
173 return """\
174 # Match a prefix shorter than "size"
175 function prefix_is_shorter(string filter_name; int size) -> bool {
176 if (net.len < size) then {
177 if DEBUG then print filter_name,
178 " [prefix_is_shorter] Matched ", net, " from ", proto, " against size ", size;
179 return true;
180 }
181 return false;
182 }"""
184 @BirdFunction("accept_kernel")
185 def accept_kernel(self, *args: Any) -> str: # pylint: disable=unused-argument
186 """BIRD accept_kernel function."""
188 return f"""\
189 # Accept kernel route
190 function accept_kernel(string filter_name) -> bool {{
191 if (!{self.is_kernel()} || {self.is_default()}) then return false;
192 if DEBUG then print filter_name,
193 " [accept_kernel] Accepting kernel route ", {self.route_info()};
194 accept;
195 }}"""
197 @BirdFunction("accept_kernel_default")
198 def accept_kernel_default(self, *args: Any) -> str: # pylint: disable=unused-argument
199 """BIRD accept_kernel_default function."""
201 return f"""\
202 # Accept kernel route
203 function accept_kernel_default(string filter_name) -> bool {{
204 if (!{self.is_kernel()} || !{self.is_default()}) then return false;
205 if DEBUG then print filter_name,
206 " [accept_kernel_default] Accepting kernel default route ", {self.route_info()};
207 accept;
208 }}"""
210 @BirdFunction("accept_static")
211 def accept_static(self, *args: Any) -> str: # pylint: disable=unused-argument
212 """BIRD accept_static function."""
214 return f"""\
215 # Accept static route
216 function accept_static(string filter_name) -> bool {{
217 if (!{self.is_static()} || {self.is_default()}) then return false;
218 if DEBUG then print filter_name,
219 " [accept_static] Accepting static route ", {self.route_info()};
220 accept;
221 }}"""
223 @BirdFunction("accept_static_default")
224 def accept_static_default(self, *args: Any) -> str: # pylint: disable=unused-argument
225 """BIRD accept_static_default function."""
227 return f"""\
228 # Accept static default route
229 function accept_static_default(string filter_name) -> bool {{
230 if (!{self.is_static()} || !{self.is_default()}) then return false;
231 if DEBUG then print filter_name,
232 " [accept_static] Accepting static default route ", {self.route_info()};
233 accept;
234 }}"""
236 @BirdFunction("is_bgp")
237 def is_bgp(self, *args: Any) -> str: # pylint: disable=unused-argument
238 """BIRD is_bgp function."""
240 return """\
241 # Match BGP routes
242 function is_bgp(string filter_name) -> bool {
243 if (source = RTS_BGP) then return true;
244 return false;
245 }"""
247 @BirdFunction("is_bogon")
248 def is_bogon(self, *args: Any) -> str: # pylint: disable=unused-argument
249 """BIRD is_bogon function."""
251 return f"""\
252 # Match on IP bogons
253 function is_bogon(string filter_name) -> bool {{
254 if ((net.type = NET_IP4 && net ~ BOGONS_V4) || (net.type = NET_IP6 && net ~ BOGONS_V6)) then {{
255 if DEBUG then print filter_name,
256 " [is_bogon] Matched ", {self.route_info()};
257 return true;
258 }}
259 return false;
260 }}"""
262 @BirdFunction("is_connected")
263 def is_connected(self, *args: Any) -> str: # pylint: disable=unused-argument
264 """BIRD is_connected function."""
266 return """\
267 # Match connected route
268 function is_connected(string filter_name) -> bool {
269 if (proto = "direct4" || proto = "direct6") then return true;
270 return false;
271 }"""
273 @BirdFunction("is_default")
274 def is_default(self, *args: Any) -> str: # pylint: disable=unused-argument
275 """BIRD is_default function."""
277 return """\
278 # Match default route
279 function is_default(string filter_name) -> bool {
280 if ((net.type = NET_IP4 && net = DEFAULT_ROUTE_V4) || (net.type = NET_IP6 && net = DEFAULT_ROUTE_V6)) then
281 return true;
282 return false;
283 }"""
285 @BirdFunction("is_kernel")
286 def is_kernel(self, *args: Any) -> str: # pylint: disable=unused-argument
287 """BIRD is_kernel function."""
289 # NK: Below we explicitly exclude krt_source = 186 as we seem to import IPv6 routes into Bird for some reason
290 return """\
291 # Match kernel route
292 function is_kernel(string filter_name) -> bool {
293 if (source = RTS_INHERIT && krt_source != 186) then return true;
294 return false;
295 }"""
297 @BirdFunction("is_static")
298 def is_static(self, *args: Any) -> str: # pylint: disable=unused-argument
299 """BIRD is_static function."""
301 return """\
302 # Match static route
303 function is_static(string filter_name) -> bool {
304 if (proto = "static4" || proto = "static6") then return true;
305 return false;
306 }"""
308 @BirdFunction("redistribute_kernel")
309 def redistribute_kernel(self, *args: Any) -> str: # pylint: disable=unused-argument
310 """BIRD redistribute_kernel function."""
312 return f"""\
313 # Accept kernel route
314 function redistribute_kernel(string filter_name) -> bool {{
315 if (!{self.is_kernel()} || {self.is_default()}) then return false;
316 if DEBUG then print filter_name,
317 " [redistribute_kernel] Accepting kernel route ", {self.route_info()};
318 accept;
319 }}"""
321 @BirdFunction("redistribute_kernel_default")
322 def redistribute_kernel_default(self, *args: Any) -> str: # pylint: disable=unused-argument
323 """BIRD redistribute_kernel_default function."""
325 return f"""\
326 # Accept kernel route
327 function redistribute_kernel_default(string filter_name) -> bool {{
328 if (!{self.is_kernel()} || !{self.is_default()}) then return false;
329 if DEBUG then print filter_name,
330 " [redistribute_kernel_default] Accepting kernel default route ", {self.route_info()};
331 accept;
332 }}"""
334 @BirdFunction("redistribute_static")
335 def redistribute_static(self, *args: Any) -> str: # pylint: disable=unused-argument
336 """BIRD redistribute_static function."""
338 return f"""\
339 # Accept static route
340 function redistribute_static(string filter_name) -> bool {{
341 if (!{self.is_static()} || {self.is_default()}) then return false;
342 if DEBUG then print filter_name,
343 " [redistribute_static] Accepting static route ", {self.route_info()};
344 accept;
345 }}"""
347 @BirdFunction("redistribute_static_default")
348 def redistribute_static_default(self, *args: Any) -> str: # pylint: disable=unused-argument
349 """BIRD redistribute_static_default function."""
351 return f"""\
352 # Accept static default route
353 function redistribute_static_default(string filter_name) -> bool {{
354 if (!{self.is_static()} || !{self.is_default()}) then return false;
355 if DEBUG then print filter_name,
356 " [redistribute_static] Accepting static default route ", {self.route_info()};
357 accept;
358 }}"""
360 def route_info(self) -> str: # pylint: disable=unused-argument
361 """BIRD route_info function."""
363 return """net, " from ", proto"""