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://docs.pyfunceble.com
Project homepage:
https://pyfunceble.github.io/
License:
::
Copyright 2017, 2018, 2019, 2020, 2022, 2023, 2024 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
https://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.
Nameservers
Provides an interface to get the right nameserver to communicate with.
Source code in PyFunceble/query/dns/nameserver.py
| 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)
@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
@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
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
def get_nameservers(self) -> Optional[List[str]]:
"""
Provides the currently set nameservers.
"""
return self.nameservers
def get_nameserver_ports(self) -> Optional[dict]:
"""
Provides the currently set nameserver_ports.
"""
return self.nameserver_ports
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
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
|
get_ip_from_nameserver(nameserver)
classmethod
Given a nameserver, this method resolve it in order to get the
IP to contact.
Parameters:
Name |
Type |
Description |
Default |
nameserver
|
str
|
|
required
|
Source code in PyFunceble/query/dns/nameserver.py
| @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
|
get_nameserver_ports()
Provides the currently set nameserver_ports.
Source code in PyFunceble/query/dns/nameserver.py
| def get_nameserver_ports(self) -> Optional[dict]:
"""
Provides the currently set nameserver_ports.
"""
return self.nameserver_ports
|
get_nameservers()
Provides the currently set nameservers.
Source code in PyFunceble/query/dns/nameserver.py
| def get_nameservers(self) -> Optional[List[str]]:
"""
Provides the currently set nameservers.
"""
return self.nameservers
|
guess_all_settings()
Try to guess all settings.
Source code in PyFunceble/query/dns/nameserver.py
| 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
|
guess_and_set_nameservers()
Try to guess and set the nameserver to use.
Source code in PyFunceble/query/dns/nameserver.py
| 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
|
set_nameservers(value)
Sets the nameserver to use.
Side Effect:
Also updates the :code:nameserver_ports
variable.
Raises:
Type |
Description |
TypeError
|
When the given :code:value is not a class:list .
|
ValueError
|
When the given :code:value is emtpy.
|
Source code in PyFunceble/query/dns/nameserver.py
| 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
|
split_nameserver_from_port(nameserver, *, default_port=53)
staticmethod
Splits the nameserver from its port.re
Parameters:
Name |
Type |
Description |
Default |
nameserver
|
str
|
The nameserver to work with.
|
required
|
default_port
|
int
|
The default port to apply, if none is found.
|
53
|
Source code in PyFunceble/query/dns/nameserver.py
| @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
|