Source code for PyFunceble.query.dns.nameserver

"""
The tool to check the availability or syntax of domain, IP or URL.

::


    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

Provides a way to get or guess the nameserver to use.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.github.io/#/special-thanks

Contributors:
    https://pyfunceble.github.io/#/contributors

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/dev/

Project homepage:
    https://pyfunceble.github.io/

License:
::


    Copyright 2017, 2018, 2019, 2020, 2022, 2023 Nissar Chababy

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

from typing import List, Optional, Tuple

import dns.exception
import dns.resolver

import PyFunceble.facility
import PyFunceble.storage
from PyFunceble.checker.syntax.domain import DomainSyntaxChecker
from PyFunceble.checker.syntax.url import URLSyntaxChecker
from PyFunceble.converter.url2netloc import Url2Netloc


[docs]class Nameservers: """ Provides an interface to get the right nameserver to communicate with. """ DEFAULT_NAMESERVERS: List[str] = [ "9.9.9.10", "149.112.112.10", "2620:fe::10", "2620:fe::fe:10", ] nameservers: Optional[List[str]] = None nameserver_ports: Optional[dict] = None protocol: Optional[str] = None domain_syntax_checker: DomainSyntaxChecker = DomainSyntaxChecker() url_syntax_checker: URLSyntaxChecker = URLSyntaxChecker() url2netloc: Url2Netloc = Url2Netloc() def __init__( self, nameserver: Optional[List[str]] = None, protocol: str = "TCP" ) -> None: self.protocol = protocol if nameserver is not None: self.set_nameservers(nameserver)
[docs] @staticmethod def split_nameserver_from_port( nameserver: str, *, default_port: int = 53, ) -> Tuple[str, int]: """ Splits the nameserver from its port.re :param nameserver: The nameserver to work with. :param default_port: The default port to apply, if none is found. """ if ":" in nameserver: if nameserver.count(":") > 1: if "]" not in nameserver: return nameserver, default_port nameserver, port = nameserver.split("]:", 1) if port.isdigit(): return nameserver[1:], int(port) return nameserver[1:], default_port splitted = nameserver.rsplit(":") if splitted[-1].isdigit(): return ":".join(splitted[:-1]), int(splitted[-1]) return ":".join(splitted[:-1]), default_port return nameserver, default_port
[docs] @classmethod def get_ip_from_nameserver(cls, nameserver: str) -> List[str]: """ Given a nameserver, this method resolve it in order to get the IP to contact. :param nameserver: The name to resolve. """ PyFunceble.facility.Logger.info( "Started to get ip from nameserver (%r)", nameserver ) result = [] if cls.domain_syntax_checker.set_subject(nameserver).is_valid(): try: result.extend( [ x.address for x in dns.resolver.Resolver().resolve(nameserver, "A") ] ) except dns.exception.DNSException: pass try: result.extend( [ x.address for x in dns.resolver.Resolver().resolve(nameserver, "AAAA") ] ) except dns.exception.DNSException: pass else: result.append(nameserver) PyFunceble.facility.Logger.debug( "IP from nameserver (%r):\n%r", nameserver, result ) PyFunceble.facility.Logger.info( "Finished to get ip from nameserver (%r)", nameserver ) return result
[docs] def set_nameservers(self, value: List[str]) -> "Nameservers": """ Sets the nameserver to use. Side Effect: Also updates the :code:`nameserver_ports` variable. :raise TypeError: When the given :code:`value` is not a :py:class:`list`. :raise ValueError: When the given :code:`value` is emtpy. """ if not isinstance(value, list): raise TypeError(f"<value> should be {list}, {type(value)} given.") if not value: raise ValueError("<value> should not be empty.") self.nameserver_ports = {} self.nameservers = [] for nameserver in value: if self.protocol.lower() == "https": if not nameserver.startswith("https://"): netloc = self.url2netloc.set_data_to_convert( nameserver ).get_converted() if "/" in nameserver: path = nameserver[nameserver.find("/") :] else: path = "" self.nameservers.append( "https://" # pylint: disable=line-too-long f"{netloc}{path}" ) else: self.nameservers.append(nameserver) # 443 is because it's more likely to be for DOH. self.nameserver_ports.update({self.nameservers[-1]: 443}) continue server, port = self.split_nameserver_from_port(nameserver) for dns_ip in self.get_ip_from_nameserver(server): self.nameservers.append(dns_ip) self.nameserver_ports.update({dns_ip: port}) return self
[docs] def get_nameservers(self) -> Optional[List[str]]: """ Provides the currently set nameservers. """ return self.nameservers
[docs] def get_nameserver_ports(self) -> Optional[dict]: """ Provides the currently set nameserver_ports. """ return self.nameserver_ports
[docs] def guess_and_set_nameservers(self) -> "Nameservers": """ Try to guess and set the nameserver to use. """ if PyFunceble.facility.ConfigLoader.is_already_loaded(): if PyFunceble.storage.CONFIGURATION.dns.server: if isinstance(PyFunceble.storage.CONFIGURATION.dns.server, list): self.set_nameservers(PyFunceble.storage.CONFIGURATION.dns.server) else: self.set_nameservers([PyFunceble.storage.CONFIGURATION.dns.server]) else: # pragma: no cover try: ## Well, I don't like playing with the default resolver. self.set_nameservers( dns.resolver.get_default_resolver().nameservers ) except dns.resolver.NoResolverConfiguration: self.set_nameservers(self.DEFAULT_NAMESERVERS) else: # pragma: no cover try: ## Well, I don't like playing with the default resolver. self.set_nameservers(dns.resolver.get_default_resolver().nameservers) except dns.resolver.NoResolverConfiguration: self.set_nameservers(self.DEFAULT_NAMESERVERS) return self
[docs] def guess_all_settings( self, ) -> "Nameservers": # pragma: no cover ## Method themselves are more important """ Try to guess all settings. """ to_ignore = ["guess_all_settings"] for method in dir(self): if method in to_ignore or not method.startswith("guess_"): continue getattr(self, method)() return self