Compare commits
2 commits
4f18b6c8ef
...
8d0139925e
Author | SHA1 | Date | |
---|---|---|---|
8d0139925e | |||
7f9ccf3e59 |
3 changed files with 91 additions and 20 deletions
2
pyproject.toml
Normal file
2
pyproject.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[tool.black]
|
||||
line-length = 79
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Any, Generic, Iterator, Literal, TypeVar
|
||||
|
@ -29,6 +30,30 @@ class AutoList(list[T], Generic[T]):
|
|||
return [parse_obj_as(T, value)]
|
||||
|
||||
|
||||
VARIABLES = {
|
||||
"net.len": "net.len",
|
||||
}
|
||||
|
||||
|
||||
def interpolate(string: str, ctx: Context) -> str:
|
||||
pattern = r"(?<!\\)(\$\{[_a-z][_a-z0-9.]*\})"
|
||||
|
||||
def lookup(var: str) -> str:
|
||||
try:
|
||||
return VARIABLES[var]
|
||||
except KeyError:
|
||||
return quoted(getattr(ctx, var))
|
||||
|
||||
split = re.split(pattern, string)
|
||||
parts = [
|
||||
(lookup(p[2:-1]) if re.match(pattern, p) else quoted(p))
|
||||
for p in split
|
||||
if p
|
||||
]
|
||||
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
class Proto(BaseModel):
|
||||
protos: AutoList[str]
|
||||
|
||||
|
@ -49,7 +74,28 @@ class Not(BaseModel):
|
|||
condition: Condition = Field(alias="not")
|
||||
|
||||
|
||||
Condition = Proto | Source | And | Or | Not
|
||||
class AsPathContains(BaseModel):
|
||||
contains: AutoList[int] = Field(alias="as_path.contains")
|
||||
|
||||
|
||||
class AsPathLength(BaseModel):
|
||||
length: list[int] = Field(
|
||||
ge=0, min_items=2, max_items=2, alias="as_path.len"
|
||||
)
|
||||
|
||||
|
||||
class IPv4orIPv6(BaseModel):
|
||||
ipv4: list[int] = Field(ge=0, min_items=2, max_items=2)
|
||||
ipv6: list[int] = Field(ge=0, min_items=2, max_items=2)
|
||||
|
||||
|
||||
class NetLength(BaseModel):
|
||||
length: IPv4orIPv6 = Field(alias="net.len")
|
||||
|
||||
|
||||
Condition = (
|
||||
Proto | Source | And | Or | Not | AsPathContains | AsPathLength | NetLength
|
||||
)
|
||||
|
||||
And.update_forward_refs()
|
||||
Or.update_forward_refs()
|
||||
|
@ -61,6 +107,10 @@ Accept = Literal["accept"]
|
|||
Reject = Literal["reject"]
|
||||
|
||||
|
||||
class RejectWithMsg(BaseModel):
|
||||
reject: str
|
||||
|
||||
|
||||
class PrefSrc(BaseModel):
|
||||
pref_src: AutoList[IPvAnyAddress]
|
||||
|
||||
|
@ -70,7 +120,7 @@ class Conditional(BaseModel):
|
|||
actions: AutoList[Action] = Field(alias="then")
|
||||
|
||||
|
||||
Action = Accept | Reject | PrefSrc | Conditional
|
||||
Action = Accept | Reject | RejectWithMsg | PrefSrc | Conditional
|
||||
|
||||
Conditional.update_forward_refs()
|
||||
|
||||
|
@ -144,12 +194,31 @@ def str_of_condition(condition: Condition, ctx: bool) -> str:
|
|||
sources = [str(s) for s in sources]
|
||||
return f"krt_source ~ [ {', '.join(sources)} ]"
|
||||
|
||||
case AsPathContains(contains=contains):
|
||||
return (
|
||||
f"bgp_path ~ [ {', '.join([str(asn) for asn in contains])} ]"
|
||||
)
|
||||
|
||||
case AsPathLength(length=[min_len, max_len]):
|
||||
return f"{min_len} <= bgp_path.len && bgp_path.len <= {max_len}"
|
||||
|
||||
case NetLength(
|
||||
length=IPv4orIPv6(ipv4=[min_v4, max_v4], ipv6=[min_v6, max_v6])
|
||||
):
|
||||
if ctx.ipv4:
|
||||
return f"{min_v4} <= net.len && net.len <= {max_v4}"
|
||||
else:
|
||||
return f"{min_v6} <= net.len && net.len <= {max_v6}"
|
||||
|
||||
|
||||
def lines_of_action(action: Action, ctx: Context) -> Iterable[str]:
|
||||
match action:
|
||||
case "accept" | "reject":
|
||||
yield f"{action};"
|
||||
|
||||
case RejectWithMsg(reject=reason):
|
||||
yield f"reject {interpolate(reason, ctx)};"
|
||||
|
||||
case Conditional(condition=condition, actions=actions):
|
||||
yield f"if {str_of_condition(condition, ctx)} then {'{'}"
|
||||
yield from indent(
|
||||
|
|
|
@ -27,8 +27,8 @@ protocol device {
|
|||
|
||||
{% for name, kernel in bird__kernel.items() %}
|
||||
{% for version in ["ipv4", "ipv6"] %}
|
||||
{% set ipv4 = version == "ipv4" %}
|
||||
protocol kernel {{ name | bird_name(ipv4) }} {
|
||||
{% set is_ipv4 = version == "ipv4" %}
|
||||
protocol kernel {{ name | bird_name(is_ipv4) }} {
|
||||
{% if kernel.kernel is defined %}
|
||||
kernel table {{ kernel.kernel }};
|
||||
{% endif %}
|
||||
|
@ -40,9 +40,9 @@ protocol kernel {{ name | bird_name(ipv4) }} {
|
|||
{% endif %}
|
||||
{{ version }} {
|
||||
{% if kernel.table is defined %}
|
||||
table {{ kernel.table | bird_name(ipv4) }};
|
||||
table {{ kernel.table | bird_name(is_ipv4) }};
|
||||
{% endif %}
|
||||
{{ import_export(kernel, ipv4) | indent(8) }}
|
||||
{{ import_export(kernel, is_ipv4) | indent(8) }}
|
||||
};
|
||||
}
|
||||
{% endfor %}
|
||||
|
@ -50,25 +50,25 @@ protocol kernel {{ name | bird_name(ipv4) }} {
|
|||
|
||||
{% for name, pipe in bird__pipes.items() %}
|
||||
{% for version in ["ipv4", "ipv6"] %}
|
||||
{% set ipv4 = version == "ipv4" %}
|
||||
protocol pipe {{ name | bird_name(ipv4) }} {
|
||||
table {{ pipe.table | bird_name(ipv4) }};
|
||||
peer table {{ pipe.peer_table | default("master") | bird_name(ipv4) }};
|
||||
{{ import_export(kernel, ipv4) | indent(4) }}
|
||||
{% set is_ipv4 = version == "ipv4" %}
|
||||
protocol pipe {{ name | bird_name(is_ipv4) }} {
|
||||
table {{ pipe.table | bird_name(is_ipv4) }};
|
||||
peer table {{ pipe.peer_table | default("master") | bird_name(is_ipv4) }};
|
||||
{{ import_export(kernel, is_ipv4) | indent(4) }}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% if bird__ospf is defined %}
|
||||
{% for version in ["ipv4", "ipv6"] %}
|
||||
{% set ipv4 = version == "ipv4" %}
|
||||
{% set ospf_version = "v2" if ipv4 else "v3" %}
|
||||
protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(ipv4) }} {
|
||||
{% set is_ipv4 = version == "ipv4" %}
|
||||
{% set ospf_version = "v2" if is_ipv4 else "v3" %}
|
||||
protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(is_ipv4) }} {
|
||||
{{ version }} {
|
||||
{% if bird__ospf.table is defined %}
|
||||
table {{ bird__ospf.table | bird_name(ipv4) }};
|
||||
table {{ bird__ospf.table | bird_name(is_ipv4) }};
|
||||
{% endif %}
|
||||
{{ import_export(bird__ospf, ipv4) | indent(8) }}
|
||||
{{ import_export(bird__ospf, is_ipv4) | indent(8) }}
|
||||
};
|
||||
{% for id, area in bird__ospf.areas.items() %}
|
||||
area {{ id }} {
|
||||
|
@ -92,8 +92,8 @@ protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(ipv4) }} {
|
|||
|
||||
{% for name, bgp in bird__bgp.items() %}
|
||||
{% for version in ["ipv4", "ipv6"] %}
|
||||
{% set ipv4 = version == "ipv4" %}
|
||||
protocol bgp {{ name | bird_name(ipv4) }} {
|
||||
{% set is_ipv4 = version == "ipv4" %}
|
||||
protocol bgp {{ name | bird_name(is_ipv4) }} {
|
||||
local {{ bgp.local.address
|
||||
| ansible.utils.ipaddr(version)
|
||||
| first }} as {{ bgp.local.as }};
|
||||
|
@ -106,12 +106,12 @@ protocol bgp {{ name | bird_name(ipv4) }} {
|
|||
{% endif %}
|
||||
{{ version }} {
|
||||
{% if bgp.table is defined %}
|
||||
table {{ bgp.table | bird_name(ipv4) }};
|
||||
table {{ bgp.table | bird_name(is_ipv4) }};
|
||||
{% endif %}
|
||||
{% if bgp.next_hop_self is defined %}
|
||||
next hop self;
|
||||
{% endif %}
|
||||
{{ import_export(bgp, ipv4) | indent(8) }}
|
||||
{{ import_export(bgp, is_ipv4) | indent(8) }}
|
||||
};
|
||||
}
|
||||
{% endfor %}
|
||||
|
|
Loading…
Reference in a new issue