summaryrefslogtreecommitdiffstats
path: root/manager/knot_resolver_manager
diff options
context:
space:
mode:
authorAleš <ales.mrazek@nic.cz>2022-01-17 18:53:19 +0100
committerAleš Mrázek <ales.mrazek@nic.cz>2022-04-08 16:17:53 +0200
commit0b1a7286082d712998d8c586f86bc129407f6d38 (patch)
treefdd3536a1e56aacfdc0a6d9ffa277a1ba82c8c99 /manager/knot_resolver_manager
parentMerge branch 'manager-systemd-improvements' into 'manager' (diff)
downloadknot-resolver-0b1a7286082d712998d8c586f86bc129407f6d38.tar.xz
knot-resolver-0b1a7286082d712998d8c586f86bc129407f6d38.zip
datamodel: network: interfaces schema changed to new listen schema #703
- list of ip-address, interface and unix-socket can be used - <ip-address/interface>@<port> syntax to specify the port number - config files and unit tests updated
Diffstat (limited to 'manager/knot_resolver_manager')
-rw-r--r--manager/knot_resolver_manager/datamodel/network_schema.py85
-rw-r--r--manager/knot_resolver_manager/datamodel/templates/macros/network_macros.lua.j273
-rw-r--r--manager/knot_resolver_manager/datamodel/templates/network.lua.j28
-rw-r--r--manager/knot_resolver_manager/datamodel/types.py86
4 files changed, 195 insertions, 57 deletions
diff --git a/manager/knot_resolver_manager/datamodel/network_schema.py b/manager/knot_resolver_manager/datamodel/network_schema.py
index d905c1fc..2abb1ec5 100644
--- a/manager/knot_resolver_manager/datamodel/network_schema.py
+++ b/manager/knot_resolver_manager/datamodel/network_schema.py
@@ -1,42 +1,20 @@
-from typing import List, Optional
+from typing import List, Optional, Union
from typing_extensions import Literal
from knot_resolver_manager.datamodel.types import (
CheckedPath,
+ InterfacePort,
IPAddress,
+ IPAddressPort,
IPNetwork,
IPv4Address,
IPv6Address,
- Listen,
SizeUnit,
)
from knot_resolver_manager.utils import SchemaNode
-KindEnum = Literal["dns", "xdp", "dot", "doh"]
-
-
-class InterfaceSchema(SchemaNode):
- class Raw(SchemaNode):
- listen: Listen
- kind: KindEnum = "dns"
- freebind: bool = False
-
- _PREVIOUS_SCHEMA = Raw
-
- listen: Listen
- kind: KindEnum
- freebind: bool
-
- def _listen(self, origin: Raw) -> Listen:
- if not origin.listen.port:
- if origin.kind == "dot":
- origin.listen.port = 853
- elif origin.kind == "doh":
- origin.listen.port = 443
- else:
- origin.listen.port = 53
- return origin.listen
+KindEnum = Literal["dns", "xdp", "dot", "doh2"]
class EdnsBufferSizeSchema(SchemaNode):
@@ -64,6 +42,55 @@ class TLSSchema(SchemaNode):
raise ValueError("'padding' must be number in range<0-512>")
+class ListenSchema(SchemaNode):
+ class Raw(SchemaNode):
+ unix_socket: Union[None, CheckedPath, List[CheckedPath]] = None
+ ip_address: Union[None, IPAddressPort, List[IPAddressPort]] = None
+ interface: Union[None, InterfacePort, List[InterfacePort]] = None
+ port: Optional[int] = None
+ kind: KindEnum = "dns"
+ freebind: bool = False
+
+ _PREVIOUS_SCHEMA = Raw
+
+ unix_socket: Union[None, CheckedPath, List[CheckedPath]]
+ ip_address: Union[None, IPAddressPort, IPAddressPort, List[IPAddressPort]]
+ interface: Union[None, InterfacePort, List[InterfacePort]]
+ port: Optional[int]
+ kind: KindEnum
+ freebind: bool
+
+ def _port(self, origin: Raw) -> Optional[int]:
+ if origin.port:
+ return origin.port
+ elif origin.ip_address or origin.interface:
+ if origin.kind == "dot":
+ return 853
+ elif origin.kind == "doh2":
+ return 443
+ return 53
+ return None
+
+ def _validate(self) -> None:
+ present = {
+ "ip_address" if self.ip_address is not None else ...,
+ "unix_socket" if self.unix_socket is not None else ...,
+ "interface" if self.interface is not None else ...,
+ }
+ if not (present == {"ip_address", ...} or present == {"unix_socket", ...} or present == {"interface", ...}):
+ raise ValueError(
+ "Listen configuration contains multiple incompatible options at once. "
+ "Only one of 'ip-address', 'interface' and 'unix-socket' optionscan be configured at once."
+ )
+ if self.port and self.unix_socket:
+ raise ValueError(
+ "'unix-socket' and 'port' are not compatible options. "
+ "Port configuration can only be used with 'ip-address' or 'interface'."
+ )
+ if self.port and not 0 <= self.port <= 65_535:
+ raise ValueError(f"Port value {self.port} out of range of usual 2-byte port value")
+
+
class NetworkSchema(SchemaNode):
do_ipv4: bool = True
do_ipv6: bool = True
@@ -74,9 +101,9 @@ class NetworkSchema(SchemaNode):
edns_buffer_size: EdnsBufferSizeSchema = EdnsBufferSizeSchema()
address_renumbering: Optional[List[AddressRenumberingSchema]] = None
tls: TLSSchema = TLSSchema()
- interfaces: List[InterfaceSchema] = [
- InterfaceSchema({"listen": {"ip": "127.0.0.1", "port": 53}}),
- InterfaceSchema({"listen": {"ip": "::1", "port": 53}, "freebind": True}),
+ listen: List[ListenSchema] = [
+ ListenSchema({"ip-address": "127.0.0.1"}),
+ ListenSchema({"ip-address": "::1", "freebind": True}),
]
def _validate(self):
diff --git a/manager/knot_resolver_manager/datamodel/templates/macros/network_macros.lua.j2 b/manager/knot_resolver_manager/datamodel/templates/macros/network_macros.lua.j2
index 758fac2a..0fa31e98 100644
--- a/manager/knot_resolver_manager/datamodel/templates/macros/network_macros.lua.j2
+++ b/manager/knot_resolver_manager/datamodel/templates/macros/network_macros.lua.j2
@@ -1,17 +1,64 @@
-{% macro net_listen(interface) -%}
-net.listen(
-{%- if interface.listen.ip -%}
-'{{ interface.listen.ip|string }}',
-{%- if interface.listen.port -%}
-{{ interface.listen.port|int }},
+{% macro listen_interface(interface) -%}
+net.{{ interface }}
+{%- endmacro %}
+
+
+{% macro listen_kind(kind) -%}
+'{{ 'tls' if kind == 'dot' else kind }}'
+{%- endmacro %}
+
+
+{% macro net_listen_unix_socket(socket, kind, freebind) -%}
+net.listen('{{ socket }}',nil,{kind={{ listen_kind(kind) }},freebind={{ 'true' if freebind else 'false'}}})
+{%- endmacro %}
+
+
+{% macro net_listen_ip_address(ip_address, kind, freebind, port) -%}
+net.listen('{{ ip_address.addr }}',
+{%- if ip_address.port -%}
+{{ ip_address.port }},
+{%- else -%}
+{{ port }},
{%- endif -%}
-{%- elif interface.listen.unix_socket -%}
-'{{ interface.listen.unix_socket|string }}',nil,
-{%- elif interface.listen.interface -%}
-net.{{ interface.listen.interface|string }},
-{%- if interface.listen.port -%}
-{{ interface.listen.port|int }},
+{kind={{ listen_kind(kind) }},freebind={{ 'true' if freebind else 'false'}}})
+{%- endmacro %}
+
+
+{% macro net_listen_interface(interface, kind, freebind, port) -%}
+net.listen({{ listen_interface(interface.intrfc) }},
+{%- if interface.port -%}
+{{ interface.port }},
+{%- else -%}
+{{ port }},
{%- endif -%}
+{kind={{ listen_kind(kind) }},freebind={{ 'true' if freebind else 'false'}}})
+{%- endmacro %}
+
+
+{% macro network_listen(listen) -%}
+{%- if listen.unix_socket -%}
+ {%- if listen.unix_socket is iterable-%}
+ {% for socket in listen.unix_socket -%}
+ {{ net_listen_unix_socket(socket, listen.kind, listen.freebind) }}
+ {% endfor -%}
+ {%- else -%}
+ {{ net_listen_unix_socket(listen.unix_socket, listen.kind, listen.freebind) }}
+ {%- endif -%}
+{%- elif listen.ip_address -%}
+ {%- if listen.ip_address is iterable-%}
+ {% for address in listen.ip_address -%}
+ {{ net_listen_ip_address(address, listen.kind, listen.freebind, listen.port) }}
+ {% endfor -%}
+ {%- else -%}
+ {{ net_listen_ip_address(listen.ip_address, listen.kind, listen.freebind, listen.port) }}
+ {%- endif -%}
+{%- elif listen.interface -%}
+ {%- if listen.interface is iterable-%}
+ {% for interface in listen.interface -%}
+ {{ net_listen_interface(interface, listen.kind, listen.freebind, listen.port) }}
+ {% endfor -%}
+ {%- else -%}
+ {{ net_listen_interface(listen.interface, listen.kind, listen.freebind, listen.port) }}
+ {%- endif -%}
{%- endif -%}
-{kind='{{ 'tls' if interface.kind == 'dot' else interface.kind }}',freebind={{ 'true' if interface.freebind else 'false'}}})
{%- endmacro %} \ No newline at end of file
diff --git a/manager/knot_resolver_manager/datamodel/templates/network.lua.j2 b/manager/knot_resolver_manager/datamodel/templates/network.lua.j2
index 9bfd0dfd..edcfd177 100644
--- a/manager/knot_resolver_manager/datamodel/templates/network.lua.j2
+++ b/manager/knot_resolver_manager/datamodel/templates/network.lua.j2
@@ -1,4 +1,4 @@
-{% from 'macros/network_macros.lua.j2' import net_listen %}
+{% from 'macros/network_macros.lua.j2' import network_listen %}
-- network.do-ipv4/6
net.ipv4 = {{ 'true' if cfg.network.do_ipv4 else 'false' }}
@@ -65,7 +65,7 @@ renumber.config = {
}
{% endif %}
--- network.interfaces
-{% for interface in cfg.network.interfaces %}
-{{ net_listen(interface) }}
+-- network.listen
+{% for listen in cfg.network.listen %}
+{{ network_listen(listen) }}
{% endfor %} \ No newline at end of file
diff --git a/manager/knot_resolver_manager/datamodel/types.py b/manager/knot_resolver_manager/datamodel/types.py
index c7c3c0aa..c97f1364 100644
--- a/manager/knot_resolver_manager/datamodel/types.py
+++ b/manager/knot_resolver_manager/datamodel/types.py
@@ -353,30 +353,94 @@ class DomainName(CustomValueType):
}
+class InterfacePort(CustomValueType):
+ intrfc: str
+ port: Optional[int] = None
+
+ def __init__(self, source_value: Any, object_path: str = "/") -> None:
+ super().__init__(source_value)
+ if isinstance(source_value, str):
+ if "@" in source_value:
+ sep = source_value.split("@", 1)
+ try:
+ self.port = int(sep[1])
+ except ValueError as e:
+ raise SchemaException("Failed to parse port.", object_path) from e
+
+ if not 0 <= self.port <= 65_535:
+ raise SchemaException(
+ f"Port value '{self.port}' out of range of usual 2-byte port value", object_path
+ )
+ self.intrfc = sep[0]
+ else:
+ self.intrfc = source_value
+ self._value = source_value
+
+ else:
+ raise SchemaException(
+ f"Unexpected value for a '<interface>[@<port>]'. Expected string, got '{source_value}'"
+ f" with type '{type(source_value)}'",
+ object_path,
+ )
+
+ def to_std(self) -> str:
+ return self._value
+
+ def __str__(self) -> str:
+ return self._value
+
+ def __int__(self) -> int:
+ raise ValueError("Can't convert InterfacePort to an integer")
+
+ def __eq__(self, o: object) -> bool:
+ """
+ Two instances of InterfacePort are equal when they represent same string.
+ """
+ return isinstance(o, InterfacePort) and str(o._value) == str(self._value)
+
+ def serialize(self) -> Any:
+ return str(self._value)
+
+ @classmethod
+ def json_schema(cls: Type["InterfacePort"]) -> Dict[Any, Any]:
+ return {
+ "type": "string",
+ }
+
+
class IPAddressPort(CustomValueType):
+ addr: Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
+ port: Optional[int] = None
+
def __init__(self, source_value: Any, object_path: str = "/") -> None:
super().__init__(source_value)
if isinstance(source_value, str):
- addr = source_value
if "@" in source_value:
sep = source_value.split("@", 1)
- addr = sep[0]
try:
- port = int(sep[1])
+ self.port = int(sep[1])
except ValueError as e:
raise SchemaException("Failed to parse port.", object_path) from e
- if not 0 <= port <= 65_535:
- raise SchemaException(f"Port value '{port}' out of range of usual 2-byte port value", object_path)
- try:
- ipaddress.ip_address(addr)
- except ValueError as e:
- raise SchemaException("Failed to parse IP address.", object_path) from e
+ if not 0 <= self.port <= 65_535:
+ raise SchemaException(
+ f"Port value '{self.port}' out of range of usual 2-byte port value", object_path
+ )
+
+ try:
+ self.addr = ipaddress.ip_address(sep[0])
+ except ValueError as e:
+ raise SchemaException("Failed to parse IP address.", object_path) from e
+ else:
+ try:
+ self.addr = ipaddress.ip_address(source_value)
+ except ValueError as e:
+ raise SchemaException("Failed to parse IP address.", object_path) from e
+ self._value = source_value
- self._value: str = source_value
else:
raise SchemaException(
- f"Unexpected value for a '<ip-address>@<port>'. Expected string, got '{source_value}'"
+ f"Unexpected value for a '<ip-address>[@<port>]'. Expected string, got '{source_value}'"
f" with type '{type(source_value)}'",
object_path,
)