Skip to content

Commit de0e233

Browse files
blink1073aclark4life
andauthoredApr 10, 2025··
PYTHON-5288: [v4.12] SRV hostname validation fails when resolver and resolved hostnames are identical with three domain levels (#2276)
Co-authored-by: Jeffrey A. Clark <aclark@aclark.net>
1 parent 98b656f commit de0e233

File tree

5 files changed

+60
-12
lines changed

5 files changed

+60
-12
lines changed
 

‎doc/changelog.rst

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
Changelog
22
=========
33

4+
Changes in Version 4.12.1 (XXXX/XX/XX)
5+
--------------------------------------
6+
7+
Version 4.12.1 is a bug fix release.
8+
9+
- Fixed a bug causing SRV hostname validation to fail when resolver and resolved hostnames are identical with three domain levels.
10+
11+
Issues Resolved
12+
...............
13+
14+
See the `PyMongo 4.12 release notes in JIRA`_ for the list of resolved issues
15+
in this release.
16+
17+
.. _PyMongo 4.12 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=41916
18+
.. _PYTHON-5288: https://jira.mongodb.org/browse/PYTHON-5288
19+
420
Changes in Version 4.12.0 (2025/04/08)
521
--------------------------------------
622

‎pymongo/asynchronous/srv_resolver.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def __init__(
9696
except Exception:
9797
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,)) from None
9898
self.__slen = len(self.__plist)
99+
self.nparts = len(split_fqdn)
99100

100101
async def get_options(self) -> Optional[str]:
101102
from dns import resolver
@@ -137,12 +138,13 @@ async def _get_srv_response_and_hosts(
137138

138139
# Validate hosts
139140
for node in nodes:
140-
if self.__fqdn == node[0].lower():
141+
srv_host = node[0].lower()
142+
if self.__fqdn == srv_host and self.nparts < 3:
141143
raise ConfigurationError(
142144
"Invalid SRV host: return address is identical to SRV hostname"
143145
)
144146
try:
145-
nlist = node[0].lower().split(".")[1:][-self.__slen :]
147+
nlist = srv_host.split(".")[1:][-self.__slen :]
146148
except Exception:
147149
raise ConfigurationError(f"Invalid SRV host: {node[0]}") from None
148150
if self.__plist != nlist:

‎pymongo/synchronous/srv_resolver.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def __init__(
9696
except Exception:
9797
raise ConfigurationError(_INVALID_HOST_MSG % (fqdn,)) from None
9898
self.__slen = len(self.__plist)
99+
self.nparts = len(split_fqdn)
99100

100101
def get_options(self) -> Optional[str]:
101102
from dns import resolver
@@ -137,12 +138,13 @@ def _get_srv_response_and_hosts(
137138

138139
# Validate hosts
139140
for node in nodes:
140-
if self.__fqdn == node[0].lower():
141+
srv_host = node[0].lower()
142+
if self.__fqdn == srv_host and self.nparts < 3:
141143
raise ConfigurationError(
142144
"Invalid SRV host: return address is identical to SRV hostname"
143145
)
144146
try:
145-
nlist = node[0].lower().split(".")[1:][-self.__slen :]
147+
nlist = srv_host.split(".")[1:][-self.__slen :]
146148
except Exception:
147149
raise ConfigurationError(f"Invalid SRV host: {node[0]}") from None
148150
if self.__plist != nlist:

‎test/asynchronous/test_dns.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,15 @@ async def mock_resolve(query, record_type, *args, **kwargs):
220220
mock_resolver.side_effect = mock_resolve
221221
domain = case["query"].split("._tcp.")[1]
222222
connection_string = f"mongodb+srv://{domain}"
223-
try:
223+
if "expected_error" not in case:
224224
await parse_uri(connection_string)
225-
except ConfigurationError as e:
226-
self.assertIn(case["expected_error"], str(e))
227225
else:
228-
self.fail(f"ConfigurationError was not raised for query: {case['query']}")
226+
try:
227+
await parse_uri(connection_string)
228+
except ConfigurationError as e:
229+
self.assertIn(case["expected_error"], str(e))
230+
else:
231+
self.fail(f"ConfigurationError was not raised for query: {case['query']}")
229232

230233
async def test_1_allow_srv_hosts_with_fewer_than_three_dot_separated_parts(self):
231234
with patch("dns.asyncresolver.resolve"):
@@ -289,6 +292,17 @@ async def test_4_throw_when_return_address_does_not_contain_dot_separating_share
289292
]
290293
await self.run_initial_dns_seedlist_discovery_prose_tests(test_cases)
291294

295+
async def test_5_when_srv_hostname_has_two_dot_separated_parts_it_is_valid_for_the_returned_hostname_to_be_identical(
296+
self
297+
):
298+
test_cases = [
299+
{
300+
"query": "_mongodb._tcp.blogs.mongodb.com",
301+
"mock_target": "blogs.mongodb.com",
302+
},
303+
]
304+
await self.run_initial_dns_seedlist_discovery_prose_tests(test_cases)
305+
292306

293307
if __name__ == "__main__":
294308
unittest.main()

‎test/test_dns.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,15 @@ def mock_resolve(query, record_type, *args, **kwargs):
218218
mock_resolver.side_effect = mock_resolve
219219
domain = case["query"].split("._tcp.")[1]
220220
connection_string = f"mongodb+srv://{domain}"
221-
try:
221+
if "expected_error" not in case:
222222
parse_uri(connection_string)
223-
except ConfigurationError as e:
224-
self.assertIn(case["expected_error"], str(e))
225223
else:
226-
self.fail(f"ConfigurationError was not raised for query: {case['query']}")
224+
try:
225+
parse_uri(connection_string)
226+
except ConfigurationError as e:
227+
self.assertIn(case["expected_error"], str(e))
228+
else:
229+
self.fail(f"ConfigurationError was not raised for query: {case['query']}")
227230

228231
def test_1_allow_srv_hosts_with_fewer_than_three_dot_separated_parts(self):
229232
with patch("dns.resolver.resolve"):
@@ -287,6 +290,17 @@ def test_4_throw_when_return_address_does_not_contain_dot_separating_shared_part
287290
]
288291
self.run_initial_dns_seedlist_discovery_prose_tests(test_cases)
289292

293+
def test_5_when_srv_hostname_has_two_dot_separated_parts_it_is_valid_for_the_returned_hostname_to_be_identical(
294+
self
295+
):
296+
test_cases = [
297+
{
298+
"query": "_mongodb._tcp.blogs.mongodb.com",
299+
"mock_target": "blogs.mongodb.com",
300+
},
301+
]
302+
self.run_initial_dns_seedlist_discovery_prose_tests(test_cases)
303+
290304

291305
if __name__ == "__main__":
292306
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.