feat(bird): Add as_path.{contains,len}, net.len
This commit is contained in:
parent
7f9ccf3e59
commit
8d0139925e
2 changed files with 89 additions and 20 deletions
|
@ -1,6 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
from typing import Any, Generic, Iterator, Literal, TypeVar
|
from typing import Any, Generic, Iterator, Literal, TypeVar
|
||||||
|
@ -29,6 +30,30 @@ class AutoList(list[T], Generic[T]):
|
||||||
return [parse_obj_as(T, value)]
|
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):
|
class Proto(BaseModel):
|
||||||
protos: AutoList[str]
|
protos: AutoList[str]
|
||||||
|
|
||||||
|
@ -49,7 +74,28 @@ class Not(BaseModel):
|
||||||
condition: Condition = Field(alias="not")
|
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()
|
And.update_forward_refs()
|
||||||
Or.update_forward_refs()
|
Or.update_forward_refs()
|
||||||
|
@ -61,6 +107,10 @@ Accept = Literal["accept"]
|
||||||
Reject = Literal["reject"]
|
Reject = Literal["reject"]
|
||||||
|
|
||||||
|
|
||||||
|
class RejectWithMsg(BaseModel):
|
||||||
|
reject: str
|
||||||
|
|
||||||
|
|
||||||
class PrefSrc(BaseModel):
|
class PrefSrc(BaseModel):
|
||||||
pref_src: AutoList[IPvAnyAddress]
|
pref_src: AutoList[IPvAnyAddress]
|
||||||
|
|
||||||
|
@ -70,7 +120,7 @@ class Conditional(BaseModel):
|
||||||
actions: AutoList[Action] = Field(alias="then")
|
actions: AutoList[Action] = Field(alias="then")
|
||||||
|
|
||||||
|
|
||||||
Action = Accept | Reject | PrefSrc | Conditional
|
Action = Accept | Reject | RejectWithMsg | PrefSrc | Conditional
|
||||||
|
|
||||||
Conditional.update_forward_refs()
|
Conditional.update_forward_refs()
|
||||||
|
|
||||||
|
@ -144,12 +194,31 @@ def str_of_condition(condition: Condition, ctx: bool) -> str:
|
||||||
sources = [str(s) for s in sources]
|
sources = [str(s) for s in sources]
|
||||||
return f"krt_source ~ [ {', '.join(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]:
|
def lines_of_action(action: Action, ctx: Context) -> Iterable[str]:
|
||||||
match action:
|
match action:
|
||||||
case "accept" | "reject":
|
case "accept" | "reject":
|
||||||
yield f"{action};"
|
yield f"{action};"
|
||||||
|
|
||||||
|
case RejectWithMsg(reject=reason):
|
||||||
|
yield f"reject {interpolate(reason, ctx)};"
|
||||||
|
|
||||||
case Conditional(condition=condition, actions=actions):
|
case Conditional(condition=condition, actions=actions):
|
||||||
yield f"if {str_of_condition(condition, ctx)} then {'{'}"
|
yield f"if {str_of_condition(condition, ctx)} then {'{'}"
|
||||||
yield from indent(
|
yield from indent(
|
||||||
|
|
|
@ -27,8 +27,8 @@ protocol device {
|
||||||
|
|
||||||
{% for name, kernel in bird__kernel.items() %}
|
{% for name, kernel in bird__kernel.items() %}
|
||||||
{% for version in ["ipv4", "ipv6"] %}
|
{% for version in ["ipv4", "ipv6"] %}
|
||||||
{% set ipv4 = version == "ipv4" %}
|
{% set is_ipv4 = version == "ipv4" %}
|
||||||
protocol kernel {{ name | bird_name(ipv4) }} {
|
protocol kernel {{ name | bird_name(is_ipv4) }} {
|
||||||
{% if kernel.kernel is defined %}
|
{% if kernel.kernel is defined %}
|
||||||
kernel table {{ kernel.kernel }};
|
kernel table {{ kernel.kernel }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -40,9 +40,9 @@ protocol kernel {{ name | bird_name(ipv4) }} {
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ version }} {
|
{{ version }} {
|
||||||
{% if kernel.table is defined %}
|
{% if kernel.table is defined %}
|
||||||
table {{ kernel.table | bird_name(ipv4) }};
|
table {{ kernel.table | bird_name(is_ipv4) }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ import_export(kernel, ipv4) | indent(8) }}
|
{{ import_export(kernel, is_ipv4) | indent(8) }}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -50,25 +50,25 @@ protocol kernel {{ name | bird_name(ipv4) }} {
|
||||||
|
|
||||||
{% for name, pipe in bird__pipes.items() %}
|
{% for name, pipe in bird__pipes.items() %}
|
||||||
{% for version in ["ipv4", "ipv6"] %}
|
{% for version in ["ipv4", "ipv6"] %}
|
||||||
{% set ipv4 = version == "ipv4" %}
|
{% set is_ipv4 = version == "ipv4" %}
|
||||||
protocol pipe {{ name | bird_name(ipv4) }} {
|
protocol pipe {{ name | bird_name(is_ipv4) }} {
|
||||||
table {{ pipe.table | bird_name(ipv4) }};
|
table {{ pipe.table | bird_name(is_ipv4) }};
|
||||||
peer table {{ pipe.peer_table | default("master") | bird_name(ipv4) }};
|
peer table {{ pipe.peer_table | default("master") | bird_name(is_ipv4) }};
|
||||||
{{ import_export(kernel, ipv4) | indent(4) }}
|
{{ import_export(kernel, is_ipv4) | indent(4) }}
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if bird__ospf is defined %}
|
{% if bird__ospf is defined %}
|
||||||
{% for version in ["ipv4", "ipv6"] %}
|
{% for version in ["ipv4", "ipv6"] %}
|
||||||
{% set ipv4 = version == "ipv4" %}
|
{% set is_ipv4 = version == "ipv4" %}
|
||||||
{% set ospf_version = "v2" if ipv4 else "v3" %}
|
{% set ospf_version = "v2" if is_ipv4 else "v3" %}
|
||||||
protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(ipv4) }} {
|
protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(is_ipv4) }} {
|
||||||
{{ version }} {
|
{{ version }} {
|
||||||
{% if bird__ospf.table is defined %}
|
{% if bird__ospf.table is defined %}
|
||||||
table {{ bird__ospf.table | bird_name(ipv4) }};
|
table {{ bird__ospf.table | bird_name(is_ipv4) }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ import_export(bird__ospf, ipv4) | indent(8) }}
|
{{ import_export(bird__ospf, is_ipv4) | indent(8) }}
|
||||||
};
|
};
|
||||||
{% for id, area in bird__ospf.areas.items() %}
|
{% for id, area in bird__ospf.areas.items() %}
|
||||||
area {{ id }} {
|
area {{ id }} {
|
||||||
|
@ -92,8 +92,8 @@ protocol ospf {{ ospf_version }} {{ "ospf" | bird_name(ipv4) }} {
|
||||||
|
|
||||||
{% for name, bgp in bird__bgp.items() %}
|
{% for name, bgp in bird__bgp.items() %}
|
||||||
{% for version in ["ipv4", "ipv6"] %}
|
{% for version in ["ipv4", "ipv6"] %}
|
||||||
{% set ipv4 = version == "ipv4" %}
|
{% set is_ipv4 = version == "ipv4" %}
|
||||||
protocol bgp {{ name | bird_name(ipv4) }} {
|
protocol bgp {{ name | bird_name(is_ipv4) }} {
|
||||||
local {{ bgp.local.address
|
local {{ bgp.local.address
|
||||||
| ansible.utils.ipaddr(version)
|
| ansible.utils.ipaddr(version)
|
||||||
| first }} as {{ bgp.local.as }};
|
| first }} as {{ bgp.local.as }};
|
||||||
|
@ -106,12 +106,12 @@ protocol bgp {{ name | bird_name(ipv4) }} {
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ version }} {
|
{{ version }} {
|
||||||
{% if bgp.table is defined %}
|
{% if bgp.table is defined %}
|
||||||
table {{ bgp.table | bird_name(ipv4) }};
|
table {{ bgp.table | bird_name(is_ipv4) }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if bgp.next_hop_self is defined %}
|
{% if bgp.next_hop_self is defined %}
|
||||||
next hop self;
|
next hop self;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ import_export(bgp, ipv4) | indent(8) }}
|
{{ import_export(bgp, is_ipv4) | indent(8) }}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
Loading…
Reference in a new issue