From 9e304df01a150e6bf0bdf7628b3c692762e59949 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Feb 2022 04:43:50 +0100 Subject: [PATCH 01/10] Separate the DNS SRV lookup into a staticmethod --- mcstatus/server.py | 48 +++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index d8f8b059..6d943bda 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -1,4 +1,7 @@ import re +from typing import Tuple + +import dns.resolver from mcstatus.pinger import PingResponse, ServerPinger, AsyncServerPinger from mcstatus.protocol.connection import ( @@ -11,7 +14,6 @@ from mcstatus.bedrock_status import BedrockServerStatus, BedrockStatusResponse from mcstatus.scripts.address_tools import parse_address from mcstatus.utils import retry -import dns.resolver from dns.exception import DNSException VALID_HOSTNAME_REGEX = re.compile( @@ -48,6 +50,22 @@ def __init__(self, host: str, port: int = 25565, timeout: float = 3): self.port = port self.timeout = timeout + @staticmethod + def dns_srv_lookup(address: str) -> Tuple[str, int]: + """Perform a DNS resolution for SRV record pointing to the Java Server. + + :param str address: The address to resolve for. + :return: A tuple of host string and port number + :raises: dns.resolver.NXDOMAIN if the record wasn't found + """ + answers = dns.resolver.resolve("_minecraft._tcp." + address, "SRV") + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + host = str(answer.target).rstrip(".") + port = int(answer.port) + return host, port + @classmethod def lookup(cls, address: str, timeout: float = 3): """Parses the given address and checks DNS records for an SRV record that points to the Minecraft server. @@ -59,18 +77,22 @@ def lookup(cls, address: str, timeout: float = 3): """ host, port = parse_address(address) - if port is None: - port = 25565 - try: - answers = dns.resolver.resolve("_minecraft._tcp." + host, "SRV") - if len(answers): - answer = answers[0] - host = str(answer.target).rstrip(".") - port = int(answer.port) - except Exception: - pass - - return cls(host, port, timeout) + + # If we have a port, no DNS resolution is needed, just make the instance, we know where to connect + if port is not None: + return cls(host, port, timeout=timeout) + + # Try to look for an SRV DNS record. If present, make the instance with host and port from it. + try: + host, port = cls.dns_srv_lookup(host) + except dns.resolver.NXDOMAIN: + # The DNS record doesn't exist, this doesn't necessarily mean the server doesn't exist though + # SRV record is optional and some servers don't expose it. So we simply use the host from the + # address and fall back to the default port + return cls(host, timeout=timeout) + + # We have the host and port from the SRV record, use it to make the instance + return cls(host, port, timeout=timeout) def ping(self, **kwargs) -> float: """Checks the latency between a Minecraft Java Edition server and the client (you). From 10cd7ab97f88708673671139ca8055d01f0ab3de Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Feb 2022 04:44:36 +0100 Subject: [PATCH 02/10] Separate the DNS A lookup into a staticmethod --- mcstatus/server.py | 54 ++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index 6d943bda..f2a8037c 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -14,7 +14,6 @@ from mcstatus.bedrock_status import BedrockServerStatus, BedrockStatusResponse from mcstatus.scripts.address_tools import parse_address from mcstatus.utils import retry -from dns.exception import DNSException VALID_HOSTNAME_REGEX = re.compile( r"(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])" @@ -66,6 +65,21 @@ def dns_srv_lookup(address: str) -> Tuple[str, int]: port = int(answer.port) return host, port + @staticmethod + def dns_a_lookup(hostname: str) -> str: + """Perform a DNS resolution for an A record to given hostname + + :param str address: The address to resolve for. + :return: A context manager + :raises: dns.resolver.NXDOMAIN if the record wasn't found + """ + answers = dns.resolver.resolve(hostname, "A") + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + hostname = str(answer).rstrip(".") + return hostname + @classmethod def lookup(cls, address: str, timeout: float = 3): """Parses the given address and checks DNS records for an SRV record that points to the Minecraft server. @@ -175,20 +189,19 @@ def query(self) -> QueryResponse: :return: Query status information in a `QueryResponse` instance. :rtype: QueryResponse """ - host = self.host try: - answers = dns.resolver.resolve(host, "A") - if len(answers): - answer = answers[0] - host = str(answer).rstrip(".") - except DNSException: - pass + ip = self.dns_a_lookup(self.host) + except dns.resolver.NXDOMAIN: + # The A record lookup can fail here since the host could already be an IP, not a hostname + # However it can also fail if the hostname is just invalid and doesn't have any MC server + # attached to it, in which case we'll get an error after connecting with the socket. + ip = self.host - return self._retry_query(host) + return self._retry_query(ip) @retry(tries=3) - def _retry_query(self, host: str) -> QueryResponse: - connection = UDPSocketConnection((host, self.port), self.timeout) + def _retry_query(self, ip: str) -> QueryResponse: + connection = UDPSocketConnection((ip, self.port), self.timeout) querier = ServerQuerier(connection) querier.handshake() return querier.read_query() @@ -199,21 +212,20 @@ async def async_query(self) -> QueryResponse: :return: Query status information in a `QueryResponse` instance. :rtype: QueryResponse """ - host = self.host try: - answers = dns.resolver.resolve(host, "A") - if len(answers): - answer = answers[0] - host = str(answer).rstrip(".") - except DNSException: - pass + ip = self.dns_a_lookup(self.host) + except dns.resolver.NXDOMAIN: + # The A record lookup can fail here since the host could already be an IP, not a hostname + # However it can also fail if the hostname is just invalid and doesn't have any MC server + # attached to it, in which case we'll get an error after connecting with the socket. + ip = self.host - return await self._retry_async_query(host) + return await self._retry_async_query(ip) @retry(tries=3) - async def _retry_async_query(self, host) -> QueryResponse: + async def _retry_async_query(self, ip: str) -> QueryResponse: connection = UDPAsyncSocketConnection() - await connection.connect((host, self.port), self.timeout) + await connection.connect((ip, self.port), self.timeout) querier = AsyncServerQuerier(connection) await querier.handshake() return await querier.read_query() From 24674287843e242686eb2cbfba038e5699c5c970 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Feb 2022 04:53:26 +0100 Subject: [PATCH 03/10] Fix docstring --- mcstatus/server.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index f2a8037c..319d0d93 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -70,7 +70,7 @@ def dns_a_lookup(hostname: str) -> str: """Perform a DNS resolution for an A record to given hostname :param str address: The address to resolve for. - :return: A context manager + :return: The resolved IP address from the A record :raises: dns.resolver.NXDOMAIN if the record wasn't found """ answers = dns.resolver.resolve(hostname, "A") @@ -81,15 +81,13 @@ def dns_a_lookup(hostname: str) -> str: return hostname @classmethod - def lookup(cls, address: str, timeout: float = 3): - """Parses the given address and checks DNS records for an SRV record that points to the Minecraft server. + def lookup(cls, address: str, *, timeout: float = 3): + """Parse the given address and check DNS records for an SRV record that points to the Minecraft java server. - :param str address: The address of the Minecraft server, like `example.com:25565`. + :param str address: The address of the Minecraft server, like `example.com:25565` :param float timeout: The timeout in seconds before failing to connect. :return: A `MinecraftServer` instance. - :rtype: MinecraftServer """ - host, port = parse_address(address) # If we have a port, no DNS resolution is needed, just make the instance, we know where to connect From a9f55e0cb045a9f62664dfc5d8eee9cce14e0aad Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 2 Feb 2022 04:54:37 +0100 Subject: [PATCH 04/10] Don't make timeout keyword-only arg - I've accidentally included something I wanted to introduce later that changes the timeout to being keyword only argument, however that isn't a part of this PR and so this removes that change --- mcstatus/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index 319d0d93..fe6663bc 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -81,7 +81,7 @@ def dns_a_lookup(hostname: str) -> str: return hostname @classmethod - def lookup(cls, address: str, *, timeout: float = 3): + def lookup(cls, address: str, timeout: float = 3): """Parse the given address and check DNS records for an SRV record that points to the Minecraft java server. :param str address: The address of the Minecraft server, like `example.com:25565` From 243ddf792f4c1bcfd24acd9fdb9290a040d82843 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 13 Feb 2022 05:25:34 +0100 Subject: [PATCH 05/10] Make DNS lookup functions private - Since python doesn't actually have truly private functions, this only renames these functions and prefixes them with "_" which is a convention for "this is only used internally and we don't expect you to mess with it, use/alter at your own risk" --- mcstatus/server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index 6d676cc4..0d57cd7b 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -48,7 +48,7 @@ def __init__(self, host: str, port: int = 25565, timeout: float = 3): self.timeout = timeout @staticmethod - def dns_srv_lookup(address: str) -> Tuple[str, int]: + def _dns_srv_lookup(address: str) -> Tuple[str, int]: """Perform a DNS resolution for SRV record pointing to the Java Server. :param str address: The address to resolve for. @@ -64,7 +64,7 @@ def dns_srv_lookup(address: str) -> Tuple[str, int]: return host, port @staticmethod - def dns_a_lookup(hostname: str) -> str: + def _dns_a_lookup(hostname: str) -> str: """Perform a DNS resolution for an A record to given hostname :param str address: The address to resolve for. @@ -94,7 +94,7 @@ def lookup(cls, address: str, timeout: float = 3) -> Self: # Try to look for an SRV DNS record. If present, make the instance with host and port from it. try: - host, port = cls.dns_srv_lookup(host) + host, port = cls._dns_srv_lookup(host) except dns.resolver.NXDOMAIN: # The DNS record doesn't exist, this doesn't necessarily mean the server doesn't exist though # SRV record is optional and some servers don't expose it. So we simply use the host from the @@ -186,7 +186,7 @@ def query(self) -> QueryResponse: :rtype: QueryResponse """ try: - ip = self.dns_a_lookup(self.host) + ip = self._dns_a_lookup(self.host) except dns.resolver.NXDOMAIN: # The A record lookup can fail here since the host could already be an IP, not a hostname # However it can also fail if the hostname is just invalid and doesn't have any MC server @@ -209,7 +209,7 @@ async def async_query(self) -> QueryResponse: :rtype: QueryResponse """ try: - ip = self.dns_a_lookup(self.host) + ip = self._dns_a_lookup(self.host) except dns.resolver.NXDOMAIN: # The A record lookup can fail here since the host could already be an IP, not a hostname # However it can also fail if the hostname is just invalid and doesn't have any MC server From 55e84b57e8259dd7aa695921cd9677c8d2769934 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 13 Feb 2022 05:32:00 +0100 Subject: [PATCH 06/10] Document raise possibilities for DNS functions --- mcstatus/server.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mcstatus/server.py b/mcstatus/server.py index 0d57cd7b..7d34d3fb 100644 --- a/mcstatus/server.py +++ b/mcstatus/server.py @@ -53,7 +53,10 @@ def _dns_srv_lookup(address: str) -> Tuple[str, int]: :param str address: The address to resolve for. :return: A tuple of host string and port number - :raises: dns.resolver.NXDOMAIN if the record wasn't found + :raises: dns.resolver.NXDOMAIN if the SRV record doesn't exist + :raises: dns.resolver.YXDOMAIN if the SRV record name is too long + :raises: dns.resolver.Timeout if the SRV record wasn't found in time + :raises: Other dns.resolver.resolve exception pointing to a deeper issue """ answers = dns.resolver.resolve("_minecraft._tcp." + address, "SRV") # There should only be one answer here, though in case the server @@ -70,6 +73,9 @@ def _dns_a_lookup(hostname: str) -> str: :param str address: The address to resolve for. :return: The resolved IP address from the A record :raises: dns.resolver.NXDOMAIN if the record wasn't found + :raises: dns.resolver.YXDOMAIN if the record name was too long + :raises: dns.resolver.Timeout if the record wasn't found in time + :raises: Other dns.resolver.resolve exception pointing to a deeper issue """ answers = dns.resolver.resolve(hostname, "A") # There should only be one answer here, though in case the server From c26004674490401ba0256cc65f0a492a2ddffe74 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 13 Feb 2022 05:32:49 +0100 Subject: [PATCH 07/10] Fix SRV test cases - For some reason, the original no_srv test was assuming that if the SRV record wasn't present an empty list would be returned. However this isn't the case at all. If anything, `dns.resolver.NoAnswer` would get raised, however even that isn't actually true. This exception is here for cases when the DNS record is present, however there's no valid data/answer that we could return, this can't happen with an SRV record, since if the record isn't present at all, we would get an `dns.resolver.NXDOMAIN` exception. - For the invalid_srv, originally it blidly raised a generic `Exception`, we should be specific here, however the only reasonable exception that could be raised is `dns.resolver.NXDOMAIN`, the behavior of which is already being tested with the new no_srv test. The other possible exceptions like `YXDOMAIN`, `Timeout` or some other `dns.resolver` exceptions don't really need to be tested since we expect them to not get catched at all, they aren't something where we should simply fall back to default port and same host, they should fail loudly and testing that is like testing all functions to make sure that they don't accidentally capture an error they shouldn't be capturing. Therefore I just removed that test case. --- mcstatus/tests/test_server.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/mcstatus/tests/test_server.py b/mcstatus/tests/test_server.py index a58d789a..da1a7b43 100644 --- a/mcstatus/tests/test_server.py +++ b/mcstatus/tests/test_server.py @@ -1,6 +1,7 @@ import asyncio import sys +import dns.resolver from mock import patch, Mock import pytest @@ -176,15 +177,7 @@ def test_query_retry(self): def test_by_address_no_srv(self): with patch("dns.resolver.resolve") as resolve: - resolve.return_value = [] - self.server = MinecraftServer.lookup("example.org") - resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") - assert self.server.host == "example.org" - assert self.server.port == 25565 - - def test_by_address_invalid_srv(self): - with patch("dns.resolver.resolve") as resolve: - resolve.side_effect = [Exception] + resolve.side_effect = [dns.resolver.NXDOMAIN] self.server = MinecraftServer.lookup("example.org") resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") assert self.server.host == "example.org" From e1ccfcfdac0a590f135b13584424998fb000a6bc Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Wed, 9 Mar 2022 12:00:54 +0100 Subject: [PATCH 08/10] Fix failing test after merging --- mcstatus/tests/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcstatus/tests/test_server.py b/mcstatus/tests/test_server.py index 077297d2..c213ee5c 100644 --- a/mcstatus/tests/test_server.py +++ b/mcstatus/tests/test_server.py @@ -180,7 +180,7 @@ def test_by_address_no_srv(self): with patch("dns.resolver.resolve") as resolve: resolve.side_effect = [dns.resolver.NXDOMAIN] self.server = JavaServer.lookup("example.org") - resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") + resolve.assert_called_once_with("_minecraft._tcp.example.org", RdataType.SRV) assert self.server.host == "example.org" assert self.server.port == 25565 From c47b5c6466861ae173c9baf22cafd87b6ab78837 Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Tue, 22 Mar 2022 13:27:41 +0100 Subject: [PATCH 09/10] Add new implementation of cache method separation Since the previous implementation no longer made sense after the Address class was added, a complete rewrite of this was necessary. This separates the DNS handling methods into a separate file instead of relying on methods in server class. It also adds support for asynchronous DNS resolving which Address implemented already. --- mcstatus/address.py | 30 +++------------ mcstatus/dns.py | 90 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 mcstatus/dns.py diff --git a/mcstatus/address.py b/mcstatus/address.py index 6ef01215..ec3c71ea 100644 --- a/mcstatus/address.py +++ b/mcstatus/address.py @@ -5,9 +5,9 @@ from typing import NamedTuple, Optional, TYPE_CHECKING, Tuple, Union from urllib.parse import urlparse -import dns.asyncresolver import dns.resolver -from dns.rdatatype import RdataType + +import mcstatus.dns if TYPE_CHECKING: from typing_extensions import Self @@ -126,11 +126,7 @@ def resolve_ip(self, lifetime: Optional[float] = None) -> Union[ipaddress.IPv4Ad # ValueError is raised if the given address wasn't valid # this means it's a hostname and we should try to resolve # the A record - answers = dns.resolver.resolve(self.host, RdataType.A, lifetime=lifetime) - # There should only be one answer here, though in case the server - # does actually point to multiple IPs, we just pick the first one - answer = answers[0] - ip_addr = str(answer).rstrip(".") + ip_addr = mcstatus.dns.resolve_a_record(self.host, lifetime=lifetime) ip = ipaddress.ip_address(ip_addr) self._cached_ip = ip @@ -151,11 +147,7 @@ async def async_resolve_ip(self, lifetime: Optional[float] = None) -> Union[ipad # ValueError is raised if the given address wasn't valid # this means it's a hostname and we should try to resolve # the A record - answers = await dns.asyncresolver.resolve(self.host, RdataType.A, lifetime=lifetime) - # There should only be one answer here, though in case the server - # does actually point to multiple IPs, we just pick the first one - answer = answers[0] - ip_addr = str(answer).rstrip(".") + ip_addr = await mcstatus.dns.async_resolve_a_record(self.host, lifetime=lifetime) ip = ipaddress.ip_address(ip_addr) self._cached_ip = ip @@ -195,7 +187,7 @@ def minecraft_srv_address_lookup( # port which we should use. If there's no such record, fall back # to the default_port (if it's defined). try: - answers = dns.resolver.resolve("_minecraft._tcp." + host, RdataType.SRV, lifetime=lifetime) + host, port = mcstatus.dns.resolve_mc_srv(host, lifetime=lifetime) except dns.resolver.NXDOMAIN: if default_port is None: raise ValueError( @@ -203,11 +195,6 @@ def minecraft_srv_address_lookup( " and default_port wasn't specified, can't parse." ) port = default_port - else: - # The record was found, use it instead - answer = answers[0] - host = str(answer.target).rstrip(".") - port = int(answer.port) return Address(host, port) @@ -232,7 +219,7 @@ async def async_minecraft_srv_address_lookup( # port which we should use. If there's no such record, fall back # to the default_port (if it's defined). try: - answers = await dns.asyncresolver.resolve("_minecraft._tcp." + host, RdataType.SRV, lifetime=lifetime) + host, port = await mcstatus.dns.async_resolve_mc_srv(host, lifetime=lifetime) except dns.resolver.NXDOMAIN: if default_port is None: raise ValueError( @@ -240,10 +227,5 @@ async def async_minecraft_srv_address_lookup( " and default_port wasn't specified, can't parse." ) port = default_port - else: - # The record was found, use it instead - answer = answers[0] - host = str(answer.target).rstrip(".") - port = int(answer.port) return Address(host, port) diff --git a/mcstatus/dns.py b/mcstatus/dns.py new file mode 100644 index 00000000..45188701 --- /dev/null +++ b/mcstatus/dns.py @@ -0,0 +1,90 @@ +from typing import Optional, Tuple + +import dns.asyncresolver +import dns.resolver +from dns.rdatatype import RdataType + + +def resolve_a_record(hostname: str, lifetime: Optional[float] = None) -> str: + """Perform a DNS resolution for an A record to given hostname + + :param str hostname: The address to resolve for. + :return: The resolved IP address from the A record + :raises dns.exception.DNSException: + One of the exceptions possibly raised by dns.resolver.resolve + Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + """ + answers = dns.resolver.resolve(hostname, RdataType.A, lifetime=lifetime) + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + hostname = str(answer).rstrip(".") + return hostname + + +async def async_resolve_a_record(hostname: str, lifetime: Optional[float] = None) -> str: + """Asynchronous alternative to resolve_a_record. + + For more details, check the docstring of resolve_a_record function. + """ + answers = await dns.asyncresolver.resolve(hostname, RdataType.A, lifetime=lifetime) + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + hostname = str(answer).rstrip(".") + return hostname + + +def resolve_srv_record(query_name: str, lifetime: Optional[float] = None) -> Tuple[str, int]: + """Perform a DNS resolution for SRV record pointing to the Java Server. + + :param str address: The address to resolve for. + :return: A tuple of host string and port number + :raises dns.exception.DNSException: + One of the exceptions possibly raised by dns.resolver.resolve + Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + """ + answers = dns.resolver.resolve(query_name, RdataType.SRV, lifetime=lifetime) + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + host = str(answer.target).rstrip(".") + port = int(answer.port) + return host, port + + +async def async_resolve_srv_record(query_name: str, lifetime: Optional[float] = None) -> Tuple[str, int]: + """Asynchronous alternative to resolve_srv_record. + + For more details, check the docstring of resolve_srv_record function. + """ + answers = await dns.asyncresolver.resolve(query_name, RdataType.SRV, lifetime=lifetime) + # There should only be one answer here, though in case the server + # does actually point to multiple IPs, we just pick the first one + answer = answers[0] + host = str(answer.target).rstrip(".") + port = int(answer.port) + return host, port + + +def resolve_mc_srv(hostname: str, lifetime: Optional[float] = None) -> Tuple[str, int]: + """Resolve SRV record for a minecraft server on given hostname. + + :param str address: The address, without port, on which an SRV record is present. + :return: Obtained target and port from the SRV record, on which the server should live on. + :raises dns.exception.DNSException: + One of the exceptions possibly raised by dns.resolver.resolve + Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + + Returns obtained target and port from the SRV record, on which + the minecraft server should live on. + """ + return resolve_srv_record("_minecraft._tcp." + hostname, lifetime=lifetime) + + +async def async_resolve_mc_srv(hostname: str, lifetime: Optional[float] = None) -> Tuple[str, int]: + """Asynchronous alternative to resolve_mc_srv. + + For more details, check the docstring of resolve_mc_srv function. + """ + return await async_resolve_srv_record("_minecraft._tcp." + hostname, lifetime=lifetime) From 58f0c0fff634736311c99aeefd9824c429830f8a Mon Sep 17 00:00:00 2001 From: ItsDrike Date: Sun, 8 May 2022 13:52:16 +0200 Subject: [PATCH 10/10] Mention dns.resolver.NoAnswer as possible exception --- mcstatus/dns.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mcstatus/dns.py b/mcstatus/dns.py index 45188701..f9e2f909 100644 --- a/mcstatus/dns.py +++ b/mcstatus/dns.py @@ -12,14 +12,14 @@ def resolve_a_record(hostname: str, lifetime: Optional[float] = None) -> str: :return: The resolved IP address from the A record :raises dns.exception.DNSException: One of the exceptions possibly raised by dns.resolver.resolve - Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + Most notably this will be `dns.exception.Timeout`, `dns.resolver.NXDOMAIN` and `dns.resolver.NoAnswer` """ answers = dns.resolver.resolve(hostname, RdataType.A, lifetime=lifetime) # There should only be one answer here, though in case the server # does actually point to multiple IPs, we just pick the first one answer = answers[0] - hostname = str(answer).rstrip(".") - return hostname + ip = str(answer).rstrip(".") + return ip async def async_resolve_a_record(hostname: str, lifetime: Optional[float] = None) -> str: @@ -31,8 +31,8 @@ async def async_resolve_a_record(hostname: str, lifetime: Optional[float] = None # There should only be one answer here, though in case the server # does actually point to multiple IPs, we just pick the first one answer = answers[0] - hostname = str(answer).rstrip(".") - return hostname + ip = str(answer).rstrip(".") + return ip def resolve_srv_record(query_name: str, lifetime: Optional[float] = None) -> Tuple[str, int]: @@ -42,7 +42,7 @@ def resolve_srv_record(query_name: str, lifetime: Optional[float] = None) -> Tup :return: A tuple of host string and port number :raises dns.exception.DNSException: One of the exceptions possibly raised by dns.resolver.resolve - Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + Most notably this will be `dns.exception.Timeout`, `dns.resolver.NXDOMAIN` and `dns.resolver.NoAnswer` """ answers = dns.resolver.resolve(query_name, RdataType.SRV, lifetime=lifetime) # There should only be one answer here, though in case the server @@ -74,7 +74,7 @@ def resolve_mc_srv(hostname: str, lifetime: Optional[float] = None) -> Tuple[str :return: Obtained target and port from the SRV record, on which the server should live on. :raises dns.exception.DNSException: One of the exceptions possibly raised by dns.resolver.resolve - Most notably this will be `dns.exception.Timeout` and `dns.resolver.NXDOMAIN` + Most notably this will be `dns.exception.Timeout`, `dns.resolver.NXDOMAIN` and `dns.resolver.NoAnswer` Returns obtained target and port from the SRV record, on which the minecraft server should live on.