summaryrefslogtreecommitdiffstats
path: root/python/knot_resolver/datamodel/network_schema.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/knot_resolver/datamodel/network_schema.py')
-rw-r--r--python/knot_resolver/datamodel/network_schema.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/python/knot_resolver/datamodel/network_schema.py b/python/knot_resolver/datamodel/network_schema.py
new file mode 100644
index 00000000..e766d499
--- /dev/null
+++ b/python/knot_resolver/datamodel/network_schema.py
@@ -0,0 +1,181 @@
+from typing import List, Optional, Union
+
+from typing_extensions import Literal
+
+from knot_resolver.datamodel.types import (
+ EscapedStr32B,
+ WritableFilePath,
+ Int0_512,
+ Int0_65535,
+ InterfaceOptionalPort,
+ IPAddress,
+ IPAddressEM,
+ IPNetwork,
+ IPv4Address,
+ IPv6Address,
+ ListOrItem,
+ PortNumber,
+ ReadableFile,
+ SizeUnit,
+)
+from knot_resolver.utils.modeling import ConfigSchema
+
+KindEnum = Literal["dns", "xdp", "dot", "doh-legacy", "doh2"]
+
+
+class EdnsBufferSizeSchema(ConfigSchema):
+ """
+ EDNS payload size advertised in DNS packets.
+
+ ---
+ upstream: Maximum EDNS upstream (towards other DNS servers) payload size.
+ downstream: Maximum EDNS downstream (towards clients) payload size for communication.
+ """
+
+ upstream: SizeUnit = SizeUnit("1232B")
+ downstream: SizeUnit = SizeUnit("1232B")
+
+
+class AddressRenumberingSchema(ConfigSchema):
+ """
+ Renumbers addresses in answers to different address space.
+
+ ---
+ source: Source subnet.
+ destination: Destination address prefix.
+ """
+
+ source: IPNetwork
+ destination: Union[IPAddressEM, IPAddress]
+
+
+class TLSSchema(ConfigSchema):
+ """
+ TLS configuration, also affects DNS over TLS and DNS over HTTPS.
+
+ ---
+ cert_file: Path to certificate file.
+ key_file: Path to certificate key file.
+ sticket_secret: Secret for TLS session resumption via tickets. (RFC 5077).
+ sticket_secret_file: Path to file with secret for TLS session resumption via tickets. (RFC 5077).
+ auto_discovery: Experimental automatic discovery of authoritative servers supporting DNS-over-TLS.
+ padding: EDNS(0) padding of queries and answers sent over an encrypted channel.
+ """
+
+ cert_file: Optional[ReadableFile] = None
+ key_file: Optional[ReadableFile] = None
+ sticket_secret: Optional[EscapedStr32B] = None
+ sticket_secret_file: Optional[ReadableFile] = None
+ auto_discovery: bool = False
+ padding: Union[bool, Int0_512] = True
+
+ def _validate(self):
+ if self.sticket_secret and self.sticket_secret_file:
+ raise ValueError("'sticket_secret' and 'sticket_secret_file' are both defined, only one can be used")
+
+
+class ListenSchema(ConfigSchema):
+ class Raw(ConfigSchema):
+ """
+ Configuration of listening interface.
+
+ ---
+ unix_socket: Path to unix domain socket to listen to.
+ interface: IP address or interface name with optional port number to listen to.
+ port: Port number to listen to.
+ kind: Specifies DNS query transport protocol.
+ freebind: Used for binding to non-local address.
+ """
+
+ interface: Optional[ListOrItem[InterfaceOptionalPort]] = None
+ unix_socket: Optional[ListOrItem[WritableFilePath]] = None
+ port: Optional[PortNumber] = None
+ kind: KindEnum = "dns"
+ freebind: bool = False
+
+ _LAYER = Raw
+
+ interface: Optional[ListOrItem[InterfaceOptionalPort]]
+ unix_socket: Optional[ListOrItem[WritableFilePath]]
+ port: Optional[PortNumber]
+ kind: KindEnum
+ freebind: bool
+
+ def _interface(self, origin: Raw) -> Optional[ListOrItem[InterfaceOptionalPort]]:
+ if origin.interface:
+ port_set: Optional[bool] = None
+ for intrfc in origin.interface: # type: ignore[attr-defined]
+ if origin.port and intrfc.port:
+ raise ValueError("The port number is defined in two places ('port' option and '@<port>' syntax).")
+ if port_set is not None and (bool(intrfc.port) != port_set):
+ raise ValueError(
+ "The '@<port>' syntax must be used either for all or none of the interface in the list."
+ )
+ port_set = bool(intrfc.port)
+ return origin.interface
+
+ def _port(self, origin: Raw) -> Optional[PortNumber]:
+ if origin.port:
+ return origin.port
+ # default port number based on kind
+ elif origin.interface:
+ if origin.kind == "dot":
+ return PortNumber(853)
+ elif origin.kind in ["doh-legacy", "doh2"]:
+ return PortNumber(443)
+ return PortNumber(53)
+ return None
+
+ def _validate(self) -> None:
+ if bool(self.unix_socket) == bool(self.interface):
+ raise ValueError("One of 'interface' or 'unix-socket' must be configured.")
+ if self.port and self.unix_socket:
+ raise ValueError(
+ "'unix-socket' and 'port' are not compatible options."
+ " Port configuration can only be used with 'interface' option."
+ )
+
+
+class ProxyProtocolSchema(ConfigSchema):
+ """
+ PROXYv2 protocol configuration.
+
+ ---
+ allow: Allow usage of the PROXYv2 protocol headers by clients on the specified addresses.
+ """
+
+ allow: List[Union[IPAddress, IPNetwork]]
+
+
+class NetworkSchema(ConfigSchema):
+ """
+ Network connections and protocols configuration.
+
+ ---
+ do_ipv4: Enable/disable using IPv4 for contacting upstream nameservers.
+ do_ipv6: Enable/disable using IPv6 for contacting upstream nameservers.
+ out_interface_v4: IPv4 address used to perform queries. Not set by default, which lets the OS choose any address.
+ out_interface_v6: IPv6 address used to perform queries. Not set by default, which lets the OS choose any address.
+ tcp_pipeline: TCP pipeline limit. The number of outstanding queries that a single client connection can make in parallel.
+ edns_tcp_keepalive: Allows clients to discover the connection timeout. (RFC 7828)
+ edns_buffer_size: Maximum EDNS payload size advertised in DNS packets. Different values can be configured for communication downstream (towards clients) and upstream (towards other DNS servers).
+ address_renumbering: Renumbers addresses in answers to different address space.
+ tls: TLS configuration, also affects DNS over TLS and DNS over HTTPS.
+ proxy_protocol: PROXYv2 protocol configuration.
+ listen: List of interfaces to listen to and its configuration.
+ """
+
+ do_ipv4: bool = True
+ do_ipv6: bool = True
+ out_interface_v4: Optional[IPv4Address] = None
+ out_interface_v6: Optional[IPv6Address] = None
+ tcp_pipeline: Int0_65535 = Int0_65535(100)
+ edns_tcp_keepalive: bool = True
+ edns_buffer_size: EdnsBufferSizeSchema = EdnsBufferSizeSchema()
+ address_renumbering: Optional[List[AddressRenumberingSchema]] = None
+ tls: TLSSchema = TLSSchema()
+ proxy_protocol: Union[Literal[False], ProxyProtocolSchema] = False
+ listen: List[ListenSchema] = [
+ ListenSchema({"interface": "127.0.0.1"}),
+ ListenSchema({"interface": "::1", "freebind": True}),
+ ]