You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1844 lines
66 KiB

  1. # Copyright 2019 New Vector Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import base64
  15. import logging
  16. import os
  17. from typing import Generator, List, Optional, cast
  18. from unittest.mock import AsyncMock, call, patch
  19. import treq
  20. from netaddr import IPSet
  21. from service_identity import VerificationError
  22. from zope.interface import implementer
  23. from twisted.internet import defer
  24. from twisted.internet._sslverify import ClientTLSOptions, OpenSSLCertificateOptions
  25. from twisted.internet.defer import Deferred
  26. from twisted.internet.endpoints import _WrappingProtocol
  27. from twisted.internet.interfaces import (
  28. IOpenSSLClientConnectionCreator,
  29. IProtocolFactory,
  30. )
  31. from twisted.internet.protocol import Factory, Protocol
  32. from twisted.protocols.tls import TLSMemoryBIOFactory, TLSMemoryBIOProtocol
  33. from twisted.web._newclient import ResponseNeverReceived
  34. from twisted.web.client import Agent
  35. from twisted.web.http import HTTPChannel, Request
  36. from twisted.web.http_headers import Headers
  37. from twisted.web.iweb import IPolicyForHTTPS, IResponse
  38. from synapse.config.homeserver import HomeServerConfig
  39. from synapse.crypto.context_factory import FederationPolicyForHTTPS
  40. from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
  41. from synapse.http.federation.srv_resolver import Server, SrvResolver
  42. from synapse.http.federation.well_known_resolver import (
  43. WELL_KNOWN_MAX_SIZE,
  44. WellKnownResolver,
  45. _cache_period_from_headers,
  46. )
  47. from synapse.logging.context import (
  48. SENTINEL_CONTEXT,
  49. LoggingContext,
  50. LoggingContextOrSentinel,
  51. current_context,
  52. )
  53. from synapse.types import ISynapseReactor
  54. from synapse.util.caches.ttlcache import TTLCache
  55. from tests import unittest
  56. from tests.http import (
  57. TestServerTLSConnectionFactory,
  58. dummy_address,
  59. get_test_ca_cert_file,
  60. )
  61. from tests.server import FakeTransport, ThreadedMemoryReactorClock
  62. from tests.utils import checked_cast, default_config
  63. logger = logging.getLogger(__name__)
  64. class MatrixFederationAgentTests(unittest.TestCase):
  65. def setUp(self) -> None:
  66. self.reactor = ThreadedMemoryReactorClock()
  67. self.mock_resolver = AsyncMock(spec=SrvResolver)
  68. config_dict = default_config("test", parse=False)
  69. config_dict["federation_custom_ca_list"] = [get_test_ca_cert_file()]
  70. self._config = config = HomeServerConfig()
  71. config.parse_config_dict(config_dict, "", "")
  72. self.tls_factory = FederationPolicyForHTTPS(config)
  73. self.well_known_cache: TTLCache[bytes, Optional[bytes]] = TTLCache(
  74. "test_cache", timer=self.reactor.seconds
  75. )
  76. self.had_well_known_cache: TTLCache[bytes, bool] = TTLCache(
  77. "test_cache", timer=self.reactor.seconds
  78. )
  79. self.well_known_resolver = WellKnownResolver(
  80. self.reactor,
  81. Agent(self.reactor, contextFactory=self.tls_factory),
  82. b"test-agent",
  83. well_known_cache=self.well_known_cache,
  84. had_well_known_cache=self.had_well_known_cache,
  85. )
  86. def _make_connection(
  87. self,
  88. client_factory: IProtocolFactory,
  89. ssl: bool = True,
  90. expected_sni: Optional[bytes] = None,
  91. tls_sanlist: Optional[List[bytes]] = None,
  92. ) -> HTTPChannel:
  93. """Builds a test server, and completes the outgoing client connection
  94. Args:
  95. client_factory: the the factory that the
  96. application is trying to use to make the outbound connection. We will
  97. invoke it to build the client Protocol
  98. ssl: If true, we will expect an ssl connection and wrap
  99. server_factory with a TLSMemoryBIOFactory
  100. False is set only for when proxy expect http connection.
  101. Otherwise federation requests use always https.
  102. expected_sni: the expected SNI value
  103. tls_sanlist: list of SAN entries for the TLS cert presented by the server.
  104. Returns:
  105. the server Protocol returned by server_factory
  106. """
  107. # build the test server
  108. server_factory = _get_test_protocol_factory()
  109. if ssl:
  110. server_factory = _wrap_server_factory_for_tls(server_factory, tls_sanlist)
  111. server_protocol = server_factory.buildProtocol(dummy_address)
  112. assert server_protocol is not None
  113. # now, tell the client protocol factory to build the client protocol (it will be a
  114. # _WrappingProtocol, around a TLSMemoryBIOProtocol, around an
  115. # HTTP11ClientProtocol) and wire the output of said protocol up to the server via
  116. # a FakeTransport.
  117. #
  118. # Normally this would be done by the TCP socket code in Twisted, but we are
  119. # stubbing that out here.
  120. # NB: we use a checked_cast here to workaround https://github.com/Shoobx/mypy-zope/issues/91)
  121. client_protocol = checked_cast(
  122. _WrappingProtocol, client_factory.buildProtocol(dummy_address)
  123. )
  124. client_protocol.makeConnection(
  125. FakeTransport(server_protocol, self.reactor, client_protocol)
  126. )
  127. # tell the server protocol to send its stuff back to the client, too
  128. server_protocol.makeConnection(
  129. FakeTransport(client_protocol, self.reactor, server_protocol)
  130. )
  131. if ssl:
  132. assert isinstance(server_protocol, TLSMemoryBIOProtocol)
  133. # fish the test server back out of the server-side TLS protocol.
  134. http_protocol = server_protocol.wrappedProtocol
  135. # grab a hold of the TLS connection, in case it gets torn down
  136. tls_connection = server_protocol._tlsConnection
  137. else:
  138. http_protocol = server_protocol
  139. tls_connection = None
  140. assert isinstance(http_protocol, HTTPChannel)
  141. # give the reactor a pump to get the TLS juices flowing (if needed)
  142. self.reactor.advance(0)
  143. # check the SNI
  144. if expected_sni is not None:
  145. server_name = tls_connection.get_servername()
  146. self.assertEqual(
  147. server_name,
  148. expected_sni,
  149. f"Expected SNI {expected_sni!s} but got {server_name!s}",
  150. )
  151. return http_protocol
  152. @defer.inlineCallbacks
  153. def _make_get_request(
  154. self, uri: bytes
  155. ) -> Generator["Deferred[object]", object, IResponse]:
  156. """
  157. Sends a simple GET request via the agent, and checks its logcontext management
  158. """
  159. with LoggingContext("one") as context:
  160. fetch_d: Deferred[IResponse] = self.agent.request(b"GET", uri)
  161. # Nothing happened yet
  162. self.assertNoResult(fetch_d)
  163. # should have reset logcontext to the sentinel
  164. _check_logcontext(SENTINEL_CONTEXT)
  165. fetch_res: IResponse
  166. try:
  167. fetch_res = yield fetch_d # type: ignore[misc, assignment]
  168. return fetch_res
  169. except Exception as e:
  170. logger.info("Fetch of %s failed: %s", uri.decode("ascii"), e)
  171. raise
  172. finally:
  173. _check_logcontext(context)
  174. def _handle_well_known_connection(
  175. self,
  176. client_factory: IProtocolFactory,
  177. expected_sni: bytes,
  178. content: bytes,
  179. response_headers: Optional[dict] = None,
  180. ) -> HTTPChannel:
  181. """Handle an outgoing HTTPs connection: wire it up to a server, check that the
  182. request is for a .well-known, and send the response.
  183. Args:
  184. client_factory: outgoing connection
  185. expected_sni: SNI that we expect the outgoing connection to send
  186. content: content to send back as the .well-known
  187. Returns:
  188. server impl
  189. """
  190. # make the connection for .well-known
  191. well_known_server = self._make_connection(
  192. client_factory, expected_sni=expected_sni
  193. )
  194. # check the .well-known request and send a response
  195. self.assertEqual(len(well_known_server.requests), 1)
  196. request = well_known_server.requests[0]
  197. self.assertEqual(
  198. request.requestHeaders.getRawHeaders(b"user-agent"), [b"test-agent"]
  199. )
  200. self._send_well_known_response(request, content, headers=response_headers or {})
  201. return well_known_server
  202. def _send_well_known_response(
  203. self,
  204. request: Request,
  205. content: bytes,
  206. headers: Optional[dict] = None,
  207. ) -> None:
  208. """Check that an incoming request looks like a valid .well-known request, and
  209. send back the response.
  210. """
  211. self.assertEqual(request.method, b"GET")
  212. self.assertEqual(request.path, b"/.well-known/matrix/server")
  213. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  214. # send back a response
  215. for k, v in (headers or {}).items():
  216. request.setHeader(k, v)
  217. request.write(content)
  218. request.finish()
  219. self.reactor.pump((0.1,))
  220. def _make_agent(self) -> MatrixFederationAgent:
  221. """
  222. If a proxy server is set, the MatrixFederationAgent must be created again
  223. because it is created too early during setUp
  224. """
  225. return MatrixFederationAgent(
  226. reactor=cast(ISynapseReactor, self.reactor),
  227. tls_client_options_factory=self.tls_factory,
  228. user_agent=b"test-agent", # Note that this is unused since _well_known_resolver is provided.
  229. ip_allowlist=IPSet(),
  230. ip_blocklist=IPSet(),
  231. _srv_resolver=self.mock_resolver,
  232. _well_known_resolver=self.well_known_resolver,
  233. )
  234. def test_get(self) -> None:
  235. """happy-path test of a GET request with an explicit port"""
  236. self._do_get()
  237. @patch.dict(
  238. os.environ,
  239. {"https_proxy": "proxy.com", "no_proxy": "testserv"},
  240. )
  241. def test_get_bypass_proxy(self) -> None:
  242. """test of a GET request with an explicit port and bypass proxy"""
  243. self._do_get()
  244. def _do_get(self) -> None:
  245. """test of a GET request with an explicit port"""
  246. self.agent = self._make_agent()
  247. self.reactor.lookups["testserv"] = "1.2.3.4"
  248. test_d = self._make_get_request(b"matrix-federation://testserv:8448/foo/bar")
  249. # Nothing happened yet
  250. self.assertNoResult(test_d)
  251. # Make sure treq is trying to connect
  252. clients = self.reactor.tcpClients
  253. self.assertEqual(len(clients), 1)
  254. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  255. self.assertEqual(host, "1.2.3.4")
  256. self.assertEqual(port, 8448)
  257. # make a test server, and wire up the client
  258. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  259. self.assertEqual(len(http_server.requests), 1)
  260. request = http_server.requests[0]
  261. self.assertEqual(request.method, b"GET")
  262. self.assertEqual(request.path, b"/foo/bar")
  263. self.assertEqual(
  264. request.requestHeaders.getRawHeaders(b"host"), [b"testserv:8448"]
  265. )
  266. self.assertEqual(
  267. request.requestHeaders.getRawHeaders(b"user-agent"), [b"test-agent"]
  268. )
  269. content = request.content.read()
  270. self.assertEqual(content, b"")
  271. # Deferred is still without a result
  272. self.assertNoResult(test_d)
  273. # send the headers
  274. request.responseHeaders.setRawHeaders(b"Content-Type", [b"application/json"])
  275. request.write("")
  276. self.reactor.pump((0.1,))
  277. response = self.successResultOf(test_d)
  278. # that should give us a Response object
  279. self.assertEqual(response.code, 200)
  280. # Send the body
  281. request.write(b'{ "a": 1 }')
  282. request.finish()
  283. self.reactor.pump((0.1,))
  284. # check it can be read
  285. json = self.successResultOf(treq.json_content(response))
  286. self.assertEqual(json, {"a": 1})
  287. @patch.dict(
  288. os.environ, {"https_proxy": "http://proxy.com", "no_proxy": "unused.com"}
  289. )
  290. def test_get_via_http_proxy(self) -> None:
  291. """test for federation request through a http proxy"""
  292. self._do_get_via_proxy(expect_proxy_ssl=False, expected_auth_credentials=None)
  293. @patch.dict(
  294. os.environ,
  295. {"https_proxy": "http://user:pass@proxy.com", "no_proxy": "unused.com"},
  296. )
  297. def test_get_via_http_proxy_with_auth(self) -> None:
  298. """test for federation request through a http proxy with authentication"""
  299. self._do_get_via_proxy(
  300. expect_proxy_ssl=False, expected_auth_credentials=b"user:pass"
  301. )
  302. @patch.dict(
  303. os.environ, {"https_proxy": "https://proxy.com", "no_proxy": "unused.com"}
  304. )
  305. def test_get_via_https_proxy(self) -> None:
  306. """test for federation request through a https proxy"""
  307. self._do_get_via_proxy(expect_proxy_ssl=True, expected_auth_credentials=None)
  308. @patch.dict(
  309. os.environ,
  310. {"https_proxy": "https://user:pass@proxy.com", "no_proxy": "unused.com"},
  311. )
  312. def test_get_via_https_proxy_with_auth(self) -> None:
  313. """test for federation request through a https proxy with authentication"""
  314. self._do_get_via_proxy(
  315. expect_proxy_ssl=True, expected_auth_credentials=b"user:pass"
  316. )
  317. def _do_get_via_proxy(
  318. self,
  319. expect_proxy_ssl: bool = False,
  320. expected_auth_credentials: Optional[bytes] = None,
  321. ) -> None:
  322. """Send a https federation request via an agent and check that it is correctly
  323. received at the proxy and client. The proxy can use either http or https.
  324. Args:
  325. expect_proxy_ssl: True if we expect the request to connect to the proxy via https.
  326. expected_auth_credentials: credentials we expect to be presented to authenticate at the proxy
  327. """
  328. self.agent = self._make_agent()
  329. self.reactor.lookups["testserv"] = "1.2.3.4"
  330. self.reactor.lookups["proxy.com"] = "9.9.9.9"
  331. test_d = self._make_get_request(b"matrix-federation://testserv:8448/foo/bar")
  332. # Nothing happened yet
  333. self.assertNoResult(test_d)
  334. # Make sure treq is trying to connect
  335. clients = self.reactor.tcpClients
  336. self.assertEqual(len(clients), 1)
  337. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  338. # make sure we are connecting to the proxy
  339. self.assertEqual(host, "9.9.9.9")
  340. self.assertEqual(port, 1080)
  341. # make a test server to act as the proxy, and wire up the client
  342. proxy_server = self._make_connection(
  343. client_factory,
  344. ssl=expect_proxy_ssl,
  345. tls_sanlist=[b"DNS:proxy.com"] if expect_proxy_ssl else None,
  346. expected_sni=b"proxy.com" if expect_proxy_ssl else None,
  347. )
  348. assert isinstance(proxy_server, HTTPChannel)
  349. # now there should be a pending CONNECT request
  350. self.assertEqual(len(proxy_server.requests), 1)
  351. request = proxy_server.requests[0]
  352. self.assertEqual(request.method, b"CONNECT")
  353. self.assertEqual(request.path, b"testserv:8448")
  354. # Check whether auth credentials have been supplied to the proxy
  355. proxy_auth_header_values = request.requestHeaders.getRawHeaders(
  356. b"Proxy-Authorization"
  357. )
  358. if expected_auth_credentials is not None:
  359. # Compute the correct header value for Proxy-Authorization
  360. encoded_credentials = base64.b64encode(expected_auth_credentials)
  361. expected_header_value = b"Basic " + encoded_credentials
  362. # Validate the header's value
  363. self.assertIn(expected_header_value, proxy_auth_header_values)
  364. else:
  365. # Check that the Proxy-Authorization header has not been supplied to the proxy
  366. self.assertIsNone(proxy_auth_header_values)
  367. # tell the proxy server not to close the connection
  368. proxy_server.persistent = True
  369. request.finish()
  370. # now we make another test server to act as the upstream HTTP server.
  371. server_ssl_protocol = _wrap_server_factory_for_tls(
  372. _get_test_protocol_factory()
  373. ).buildProtocol(dummy_address)
  374. # Tell the HTTP server to send outgoing traffic back via the proxy's transport.
  375. proxy_server_transport = proxy_server.transport
  376. assert proxy_server_transport is not None
  377. server_ssl_protocol.makeConnection(proxy_server_transport)
  378. # ... and replace the protocol on the proxy's transport with the
  379. # TLSMemoryBIOProtocol for the test server, so that incoming traffic
  380. # to the proxy gets sent over to the HTTP(s) server.
  381. # See also comment at `_do_https_request_via_proxy`
  382. # in ../test_proxyagent.py for more details
  383. if expect_proxy_ssl:
  384. assert isinstance(proxy_server_transport, TLSMemoryBIOProtocol)
  385. proxy_server_transport.wrappedProtocol = server_ssl_protocol
  386. else:
  387. assert isinstance(proxy_server_transport, FakeTransport)
  388. client_protocol = proxy_server_transport.other
  389. assert isinstance(client_protocol, Protocol)
  390. c2s_transport = checked_cast(FakeTransport, client_protocol.transport)
  391. c2s_transport.other = server_ssl_protocol
  392. self.reactor.advance(0)
  393. server_name = server_ssl_protocol._tlsConnection.get_servername()
  394. expected_sni = b"testserv"
  395. self.assertEqual(
  396. server_name,
  397. expected_sni,
  398. f"Expected SNI {expected_sni!s} but got {server_name!s}",
  399. )
  400. # now there should be a pending request
  401. http_server = server_ssl_protocol.wrappedProtocol
  402. assert isinstance(http_server, HTTPChannel)
  403. self.assertEqual(len(http_server.requests), 1)
  404. request = http_server.requests[0]
  405. self.assertEqual(request.method, b"GET")
  406. self.assertEqual(request.path, b"/foo/bar")
  407. self.assertEqual(
  408. request.requestHeaders.getRawHeaders(b"host"), [b"testserv:8448"]
  409. )
  410. self.assertEqual(
  411. request.requestHeaders.getRawHeaders(b"user-agent"), [b"test-agent"]
  412. )
  413. # Check that the destination server DID NOT receive proxy credentials
  414. self.assertIsNone(request.requestHeaders.getRawHeaders(b"Proxy-Authorization"))
  415. content = request.content.read()
  416. self.assertEqual(content, b"")
  417. # Deferred is still without a result
  418. self.assertNoResult(test_d)
  419. # send the headers
  420. request.responseHeaders.setRawHeaders(b"Content-Type", [b"application/json"])
  421. request.write("")
  422. self.reactor.pump((0.1,))
  423. response = self.successResultOf(test_d)
  424. # that should give us a Response object
  425. self.assertEqual(response.code, 200)
  426. # Send the body
  427. request.write(b'{ "a": 1 }')
  428. request.finish()
  429. self.reactor.pump((0.1,))
  430. # check it can be read
  431. json = self.successResultOf(treq.json_content(response))
  432. self.assertEqual(json, {"a": 1})
  433. def test_get_ip_address(self) -> None:
  434. """
  435. Test the behaviour when the server name contains an explicit IP (with no port)
  436. """
  437. self.agent = self._make_agent()
  438. # there will be a getaddrinfo on the IP
  439. self.reactor.lookups["1.2.3.4"] = "1.2.3.4"
  440. test_d = self._make_get_request(b"matrix-federation://1.2.3.4/foo/bar")
  441. # Nothing happened yet
  442. self.assertNoResult(test_d)
  443. # Make sure treq is trying to connect
  444. clients = self.reactor.tcpClients
  445. self.assertEqual(len(clients), 1)
  446. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  447. self.assertEqual(host, "1.2.3.4")
  448. self.assertEqual(port, 8448)
  449. # make a test server, and wire up the client
  450. http_server = self._make_connection(client_factory, expected_sni=None)
  451. self.assertEqual(len(http_server.requests), 1)
  452. request = http_server.requests[0]
  453. self.assertEqual(request.method, b"GET")
  454. self.assertEqual(request.path, b"/foo/bar")
  455. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"1.2.3.4"])
  456. # finish the request
  457. request.finish()
  458. self.reactor.pump((0.1,))
  459. self.successResultOf(test_d)
  460. def test_get_ipv6_address(self) -> None:
  461. """
  462. Test the behaviour when the server name contains an explicit IPv6 address
  463. (with no port)
  464. """
  465. self.agent = self._make_agent()
  466. # there will be a getaddrinfo on the IP
  467. self.reactor.lookups["::1"] = "::1"
  468. test_d = self._make_get_request(b"matrix-federation://[::1]/foo/bar")
  469. # Nothing happened yet
  470. self.assertNoResult(test_d)
  471. # Make sure treq is trying to connect
  472. clients = self.reactor.tcpClients
  473. self.assertEqual(len(clients), 1)
  474. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  475. self.assertEqual(host, "::1")
  476. self.assertEqual(port, 8448)
  477. # make a test server, and wire up the client
  478. http_server = self._make_connection(client_factory, expected_sni=None)
  479. self.assertEqual(len(http_server.requests), 1)
  480. request = http_server.requests[0]
  481. self.assertEqual(request.method, b"GET")
  482. self.assertEqual(request.path, b"/foo/bar")
  483. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"[::1]"])
  484. # finish the request
  485. request.finish()
  486. self.reactor.pump((0.1,))
  487. self.successResultOf(test_d)
  488. def test_get_ipv6_address_with_port(self) -> None:
  489. """
  490. Test the behaviour when the server name contains an explicit IPv6 address
  491. (with explicit port)
  492. """
  493. self.agent = self._make_agent()
  494. # there will be a getaddrinfo on the IP
  495. self.reactor.lookups["::1"] = "::1"
  496. test_d = self._make_get_request(b"matrix-federation://[::1]:80/foo/bar")
  497. # Nothing happened yet
  498. self.assertNoResult(test_d)
  499. # Make sure treq is trying to connect
  500. clients = self.reactor.tcpClients
  501. self.assertEqual(len(clients), 1)
  502. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  503. self.assertEqual(host, "::1")
  504. self.assertEqual(port, 80)
  505. # make a test server, and wire up the client
  506. http_server = self._make_connection(client_factory, expected_sni=None)
  507. self.assertEqual(len(http_server.requests), 1)
  508. request = http_server.requests[0]
  509. self.assertEqual(request.method, b"GET")
  510. self.assertEqual(request.path, b"/foo/bar")
  511. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"[::1]:80"])
  512. # finish the request
  513. request.finish()
  514. self.reactor.pump((0.1,))
  515. self.successResultOf(test_d)
  516. def test_get_hostname_bad_cert(self) -> None:
  517. """
  518. Test the behaviour when the certificate on the server doesn't match the hostname
  519. """
  520. self.agent = self._make_agent()
  521. self.mock_resolver.resolve_service.return_value = []
  522. self.reactor.lookups["testserv1"] = "1.2.3.4"
  523. test_d = self._make_get_request(b"matrix-federation://testserv1/foo/bar")
  524. # Nothing happened yet
  525. self.assertNoResult(test_d)
  526. # No SRV record lookup yet
  527. self.mock_resolver.resolve_service.assert_not_called()
  528. # there should be an attempt to connect on port 443 for the .well-known
  529. clients = self.reactor.tcpClients
  530. self.assertEqual(len(clients), 1)
  531. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  532. self.assertEqual(host, "1.2.3.4")
  533. self.assertEqual(port, 443)
  534. # fonx the connection
  535. client_factory.clientConnectionFailed(None, Exception("nope"))
  536. # attemptdelay on the hostnameendpoint is 0.3, so takes that long before the
  537. # .well-known request fails.
  538. self.reactor.pump((0.4,))
  539. # now there should be two SRV lookups
  540. self.mock_resolver.resolve_service.assert_has_calls(
  541. [call(b"_matrix-fed._tcp.testserv1"), call(b"_matrix._tcp.testserv1")]
  542. )
  543. # we should fall back to a direct connection
  544. self.assertEqual(len(clients), 2)
  545. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  546. self.assertEqual(host, "1.2.3.4")
  547. self.assertEqual(port, 8448)
  548. # make a test server, and wire up the client
  549. http_server = self._make_connection(client_factory, expected_sni=b"testserv1")
  550. # there should be no requests
  551. self.assertEqual(len(http_server.requests), 0)
  552. # ... and the request should have failed
  553. e = self.failureResultOf(test_d, ResponseNeverReceived)
  554. failure_reason = e.value.reasons[0]
  555. self.assertIsInstance(failure_reason.value, VerificationError)
  556. def test_get_ip_address_bad_cert(self) -> None:
  557. """
  558. Test the behaviour when the server name contains an explicit IP, but
  559. the server cert doesn't cover it
  560. """
  561. self.agent = self._make_agent()
  562. # there will be a getaddrinfo on the IP
  563. self.reactor.lookups["1.2.3.5"] = "1.2.3.5"
  564. test_d = self._make_get_request(b"matrix-federation://1.2.3.5/foo/bar")
  565. # Nothing happened yet
  566. self.assertNoResult(test_d)
  567. # Make sure treq is trying to connect
  568. clients = self.reactor.tcpClients
  569. self.assertEqual(len(clients), 1)
  570. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  571. self.assertEqual(host, "1.2.3.5")
  572. self.assertEqual(port, 8448)
  573. # make a test server, and wire up the client
  574. http_server = self._make_connection(client_factory, expected_sni=None)
  575. # there should be no requests
  576. self.assertEqual(len(http_server.requests), 0)
  577. # ... and the request should have failed
  578. e = self.failureResultOf(test_d, ResponseNeverReceived)
  579. failure_reason = e.value.reasons[0]
  580. self.assertIsInstance(failure_reason.value, VerificationError)
  581. def test_get_no_srv_no_well_known(self) -> None:
  582. """
  583. Test the behaviour when the server name has no port, no SRV, and no well-known
  584. """
  585. self.agent = self._make_agent()
  586. self.mock_resolver.resolve_service.return_value = []
  587. self.reactor.lookups["testserv"] = "1.2.3.4"
  588. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  589. # Nothing happened yet
  590. self.assertNoResult(test_d)
  591. # No SRV record lookup yet
  592. self.mock_resolver.resolve_service.assert_not_called()
  593. # there should be an attempt to connect on port 443 for the .well-known
  594. clients = self.reactor.tcpClients
  595. self.assertEqual(len(clients), 1)
  596. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  597. self.assertEqual(host, "1.2.3.4")
  598. self.assertEqual(port, 443)
  599. # fonx the connection
  600. client_factory.clientConnectionFailed(None, Exception("nope"))
  601. # attemptdelay on the hostnameendpoint is 0.3, so takes that long before the
  602. # .well-known request fails.
  603. self.reactor.pump((0.4,))
  604. # now there should be two SRV lookups
  605. self.mock_resolver.resolve_service.assert_has_calls(
  606. [call(b"_matrix-fed._tcp.testserv"), call(b"_matrix._tcp.testserv")]
  607. )
  608. # we should fall back to a direct connection
  609. self.assertEqual(len(clients), 2)
  610. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  611. self.assertEqual(host, "1.2.3.4")
  612. self.assertEqual(port, 8448)
  613. # make a test server, and wire up the client
  614. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  615. self.assertEqual(len(http_server.requests), 1)
  616. request = http_server.requests[0]
  617. self.assertEqual(request.method, b"GET")
  618. self.assertEqual(request.path, b"/foo/bar")
  619. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  620. # finish the request
  621. request.finish()
  622. self.reactor.pump((0.1,))
  623. self.successResultOf(test_d)
  624. def test_get_well_known(self) -> None:
  625. """Test the behaviour when the .well-known delegates elsewhere"""
  626. self.agent = self._make_agent()
  627. self.mock_resolver.resolve_service.return_value = []
  628. self.reactor.lookups["testserv"] = "1.2.3.4"
  629. self.reactor.lookups["target-server"] = "1::f"
  630. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  631. # Nothing happened yet
  632. self.assertNoResult(test_d)
  633. # there should be an attempt to connect on port 443 for the .well-known
  634. clients = self.reactor.tcpClients
  635. self.assertEqual(len(clients), 1)
  636. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  637. self.assertEqual(host, "1.2.3.4")
  638. self.assertEqual(port, 443)
  639. self._handle_well_known_connection(
  640. client_factory,
  641. expected_sni=b"testserv",
  642. content=b'{ "m.server": "target-server" }',
  643. )
  644. # there should be two SRV lookups
  645. self.mock_resolver.resolve_service.assert_has_calls(
  646. [
  647. call(b"_matrix-fed._tcp.target-server"),
  648. call(b"_matrix._tcp.target-server"),
  649. ]
  650. )
  651. # now we should get a connection to the target server
  652. self.assertEqual(len(clients), 2)
  653. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  654. self.assertEqual(host, "1::f")
  655. self.assertEqual(port, 8448)
  656. # make a test server, and wire up the client
  657. http_server = self._make_connection(
  658. client_factory, expected_sni=b"target-server"
  659. )
  660. self.assertEqual(len(http_server.requests), 1)
  661. request = http_server.requests[0]
  662. self.assertEqual(request.method, b"GET")
  663. self.assertEqual(request.path, b"/foo/bar")
  664. self.assertEqual(
  665. request.requestHeaders.getRawHeaders(b"host"), [b"target-server"]
  666. )
  667. # finish the request
  668. request.finish()
  669. self.reactor.pump((0.1,))
  670. self.successResultOf(test_d)
  671. self.assertEqual(self.well_known_cache[b"testserv"], b"target-server")
  672. # check the cache expires
  673. self.reactor.pump((48 * 3600,))
  674. self.well_known_cache.expire()
  675. self.assertNotIn(b"testserv", self.well_known_cache)
  676. def test_get_well_known_redirect(self) -> None:
  677. """Test the behaviour when the server name has no port and no SRV record, but
  678. the .well-known has a 300 redirect
  679. """
  680. self.agent = self._make_agent()
  681. self.mock_resolver.resolve_service.return_value = []
  682. self.reactor.lookups["testserv"] = "1.2.3.4"
  683. self.reactor.lookups["target-server"] = "1::f"
  684. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  685. # Nothing happened yet
  686. self.assertNoResult(test_d)
  687. # there should be an attempt to connect on port 443 for the .well-known
  688. clients = self.reactor.tcpClients
  689. self.assertEqual(len(clients), 1)
  690. (host, port, client_factory, _timeout, _bindAddress) = clients.pop()
  691. self.assertEqual(host, "1.2.3.4")
  692. self.assertEqual(port, 443)
  693. redirect_server = self._make_connection(
  694. client_factory, expected_sni=b"testserv"
  695. )
  696. # send a 302 redirect
  697. self.assertEqual(len(redirect_server.requests), 1)
  698. request = redirect_server.requests[0]
  699. request.redirect(b"https://testserv/even_better_known")
  700. request.finish()
  701. self.reactor.pump((0.1,))
  702. # now there should be another connection
  703. clients = self.reactor.tcpClients
  704. self.assertEqual(len(clients), 1)
  705. (host, port, client_factory, _timeout, _bindAddress) = clients.pop()
  706. self.assertEqual(host, "1.2.3.4")
  707. self.assertEqual(port, 443)
  708. well_known_server = self._make_connection(
  709. client_factory, expected_sni=b"testserv"
  710. )
  711. self.assertEqual(len(well_known_server.requests), 1, "No request after 302")
  712. request = well_known_server.requests[0]
  713. self.assertEqual(request.method, b"GET")
  714. self.assertEqual(request.path, b"/even_better_known")
  715. request.write(b'{ "m.server": "target-server" }')
  716. request.finish()
  717. self.reactor.pump((0.1,))
  718. # there should be two SRV lookups
  719. self.mock_resolver.resolve_service.assert_has_calls(
  720. [
  721. call(b"_matrix-fed._tcp.target-server"),
  722. call(b"_matrix._tcp.target-server"),
  723. ]
  724. )
  725. # now we should get a connection to the target server
  726. self.assertEqual(len(clients), 1)
  727. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  728. self.assertEqual(host, "1::f")
  729. self.assertEqual(port, 8448)
  730. # make a test server, and wire up the client
  731. http_server = self._make_connection(
  732. client_factory, expected_sni=b"target-server"
  733. )
  734. self.assertEqual(len(http_server.requests), 1)
  735. request = http_server.requests[0]
  736. self.assertEqual(request.method, b"GET")
  737. self.assertEqual(request.path, b"/foo/bar")
  738. self.assertEqual(
  739. request.requestHeaders.getRawHeaders(b"host"), [b"target-server"]
  740. )
  741. # finish the request
  742. request.finish()
  743. self.reactor.pump((0.1,))
  744. self.successResultOf(test_d)
  745. self.assertEqual(self.well_known_cache[b"testserv"], b"target-server")
  746. # check the cache expires
  747. self.reactor.pump((48 * 3600,))
  748. self.well_known_cache.expire()
  749. self.assertNotIn(b"testserv", self.well_known_cache)
  750. def test_get_invalid_well_known(self) -> None:
  751. """
  752. Test the behaviour when the server name has an *invalid* well-known (and no SRV)
  753. """
  754. self.agent = self._make_agent()
  755. self.mock_resolver.resolve_service.return_value = []
  756. self.reactor.lookups["testserv"] = "1.2.3.4"
  757. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  758. # Nothing happened yet
  759. self.assertNoResult(test_d)
  760. # No SRV record lookup yet
  761. self.mock_resolver.resolve_service.assert_not_called()
  762. # there should be an attempt to connect on port 443 for the .well-known
  763. clients = self.reactor.tcpClients
  764. self.assertEqual(len(clients), 1)
  765. (host, port, client_factory, _timeout, _bindAddress) = clients.pop()
  766. self.assertEqual(host, "1.2.3.4")
  767. self.assertEqual(port, 443)
  768. self._handle_well_known_connection(
  769. client_factory, expected_sni=b"testserv", content=b"NOT JSON"
  770. )
  771. # now there should be two SRV lookups
  772. self.mock_resolver.resolve_service.assert_has_calls(
  773. [call(b"_matrix-fed._tcp.testserv"), call(b"_matrix._tcp.testserv")]
  774. )
  775. # we should fall back to a direct connection
  776. self.assertEqual(len(clients), 1)
  777. (host, port, client_factory, _timeout, _bindAddress) = clients.pop()
  778. self.assertEqual(host, "1.2.3.4")
  779. self.assertEqual(port, 8448)
  780. # make a test server, and wire up the client
  781. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  782. self.assertEqual(len(http_server.requests), 1)
  783. request = http_server.requests[0]
  784. self.assertEqual(request.method, b"GET")
  785. self.assertEqual(request.path, b"/foo/bar")
  786. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  787. # finish the request
  788. request.finish()
  789. self.reactor.pump((0.1,))
  790. self.successResultOf(test_d)
  791. def test_get_well_known_unsigned_cert(self) -> None:
  792. """Test the behaviour when the .well-known server presents a cert
  793. not signed by a CA
  794. """
  795. # we use the same test server as the other tests, but use an agent with
  796. # the config left to the default, which will not trust it (since the
  797. # presented cert is signed by a test CA)
  798. self.mock_resolver.resolve_service.return_value = []
  799. self.reactor.lookups["testserv"] = "1.2.3.4"
  800. config = default_config("test", parse=True)
  801. # Build a new agent and WellKnownResolver with a different tls factory
  802. tls_factory = FederationPolicyForHTTPS(config)
  803. agent = MatrixFederationAgent(
  804. reactor=self.reactor,
  805. tls_client_options_factory=tls_factory,
  806. user_agent=b"test-agent", # This is unused since _well_known_resolver is passed below.
  807. ip_allowlist=IPSet(),
  808. ip_blocklist=IPSet(),
  809. _srv_resolver=self.mock_resolver,
  810. _well_known_resolver=WellKnownResolver(
  811. cast(ISynapseReactor, self.reactor),
  812. Agent(self.reactor, contextFactory=tls_factory),
  813. b"test-agent",
  814. well_known_cache=self.well_known_cache,
  815. had_well_known_cache=self.had_well_known_cache,
  816. ),
  817. )
  818. test_d = agent.request(b"GET", b"matrix-federation://testserv/foo/bar")
  819. # Nothing happened yet
  820. self.assertNoResult(test_d)
  821. # there should be an attempt to connect on port 443 for the .well-known
  822. clients = self.reactor.tcpClients
  823. self.assertEqual(len(clients), 1)
  824. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  825. self.assertEqual(host, "1.2.3.4")
  826. self.assertEqual(port, 443)
  827. http_proto = self._make_connection(client_factory, expected_sni=b"testserv")
  828. # there should be no requests
  829. self.assertEqual(len(http_proto.requests), 0)
  830. # and there should be two SRV lookups instead
  831. self.mock_resolver.resolve_service.assert_has_calls(
  832. [call(b"_matrix-fed._tcp.testserv"), call(b"_matrix._tcp.testserv")]
  833. )
  834. def test_get_hostname_srv(self) -> None:
  835. """
  836. Test the behaviour when there is a single SRV record for _matrix-fed.
  837. """
  838. self.agent = self._make_agent()
  839. self.mock_resolver.resolve_service.return_value = [
  840. Server(host=b"srvtarget", port=8443)
  841. ]
  842. self.reactor.lookups["srvtarget"] = "1.2.3.4"
  843. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  844. # Nothing happened yet
  845. self.assertNoResult(test_d)
  846. # the request for a .well-known will have failed with a DNS lookup error.
  847. self.mock_resolver.resolve_service.assert_called_once_with(
  848. b"_matrix-fed._tcp.testserv"
  849. )
  850. # Make sure treq is trying to connect
  851. clients = self.reactor.tcpClients
  852. self.assertEqual(len(clients), 1)
  853. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  854. self.assertEqual(host, "1.2.3.4")
  855. self.assertEqual(port, 8443)
  856. # make a test server, and wire up the client
  857. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  858. self.assertEqual(len(http_server.requests), 1)
  859. request = http_server.requests[0]
  860. self.assertEqual(request.method, b"GET")
  861. self.assertEqual(request.path, b"/foo/bar")
  862. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  863. # finish the request
  864. request.finish()
  865. self.reactor.pump((0.1,))
  866. self.successResultOf(test_d)
  867. def test_get_hostname_srv_legacy(self) -> None:
  868. """
  869. Test the behaviour when there is a single SRV record for _matrix.
  870. """
  871. self.agent = self._make_agent()
  872. # Return no entries for the _matrix-fed lookup, and a response for _matrix.
  873. self.mock_resolver.resolve_service.side_effect = [
  874. [],
  875. [Server(host=b"srvtarget", port=8443)],
  876. ]
  877. self.reactor.lookups["srvtarget"] = "1.2.3.4"
  878. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  879. # Nothing happened yet
  880. self.assertNoResult(test_d)
  881. # the request for a .well-known will have failed with a DNS lookup error.
  882. self.mock_resolver.resolve_service.assert_has_calls(
  883. [call(b"_matrix-fed._tcp.testserv"), call(b"_matrix._tcp.testserv")]
  884. )
  885. # Make sure treq is trying to connect
  886. clients = self.reactor.tcpClients
  887. self.assertEqual(len(clients), 1)
  888. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  889. self.assertEqual(host, "1.2.3.4")
  890. self.assertEqual(port, 8443)
  891. # make a test server, and wire up the client
  892. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  893. self.assertEqual(len(http_server.requests), 1)
  894. request = http_server.requests[0]
  895. self.assertEqual(request.method, b"GET")
  896. self.assertEqual(request.path, b"/foo/bar")
  897. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  898. # finish the request
  899. request.finish()
  900. self.reactor.pump((0.1,))
  901. self.successResultOf(test_d)
  902. def test_get_well_known_srv(self) -> None:
  903. """Test the behaviour when the .well-known redirects to a place where there
  904. is a _matrix-fed SRV record.
  905. """
  906. self.agent = self._make_agent()
  907. self.reactor.lookups["testserv"] = "1.2.3.4"
  908. self.reactor.lookups["srvtarget"] = "5.6.7.8"
  909. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  910. # Nothing happened yet
  911. self.assertNoResult(test_d)
  912. # there should be an attempt to connect on port 443 for the .well-known
  913. clients = self.reactor.tcpClients
  914. self.assertEqual(len(clients), 1)
  915. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  916. self.assertEqual(host, "1.2.3.4")
  917. self.assertEqual(port, 443)
  918. self.mock_resolver.resolve_service.return_value = [
  919. Server(host=b"srvtarget", port=8443)
  920. ]
  921. self._handle_well_known_connection(
  922. client_factory,
  923. expected_sni=b"testserv",
  924. content=b'{ "m.server": "target-server" }',
  925. )
  926. # there should be a SRV lookup
  927. self.mock_resolver.resolve_service.assert_called_once_with(
  928. b"_matrix-fed._tcp.target-server"
  929. )
  930. # now we should get a connection to the target of the SRV record
  931. self.assertEqual(len(clients), 2)
  932. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  933. self.assertEqual(host, "5.6.7.8")
  934. self.assertEqual(port, 8443)
  935. # make a test server, and wire up the client
  936. http_server = self._make_connection(
  937. client_factory, expected_sni=b"target-server"
  938. )
  939. self.assertEqual(len(http_server.requests), 1)
  940. request = http_server.requests[0]
  941. self.assertEqual(request.method, b"GET")
  942. self.assertEqual(request.path, b"/foo/bar")
  943. self.assertEqual(
  944. request.requestHeaders.getRawHeaders(b"host"), [b"target-server"]
  945. )
  946. # finish the request
  947. request.finish()
  948. self.reactor.pump((0.1,))
  949. self.successResultOf(test_d)
  950. def test_get_well_known_srv_legacy(self) -> None:
  951. """Test the behaviour when the .well-known redirects to a place where there
  952. is a _matrix SRV record.
  953. """
  954. self.agent = self._make_agent()
  955. self.reactor.lookups["testserv"] = "1.2.3.4"
  956. self.reactor.lookups["srvtarget"] = "5.6.7.8"
  957. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  958. # Nothing happened yet
  959. self.assertNoResult(test_d)
  960. # there should be an attempt to connect on port 443 for the .well-known
  961. clients = self.reactor.tcpClients
  962. self.assertEqual(len(clients), 1)
  963. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  964. self.assertEqual(host, "1.2.3.4")
  965. self.assertEqual(port, 443)
  966. # Return no entries for the _matrix-fed lookup, and a response for _matrix.
  967. self.mock_resolver.resolve_service.side_effect = [
  968. [],
  969. [Server(host=b"srvtarget", port=8443)],
  970. ]
  971. self._handle_well_known_connection(
  972. client_factory,
  973. expected_sni=b"testserv",
  974. content=b'{ "m.server": "target-server" }',
  975. )
  976. # there should be two SRV lookups
  977. self.mock_resolver.resolve_service.assert_has_calls(
  978. [
  979. call(b"_matrix-fed._tcp.target-server"),
  980. call(b"_matrix._tcp.target-server"),
  981. ]
  982. )
  983. # now we should get a connection to the target of the SRV record
  984. self.assertEqual(len(clients), 2)
  985. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  986. self.assertEqual(host, "5.6.7.8")
  987. self.assertEqual(port, 8443)
  988. # make a test server, and wire up the client
  989. http_server = self._make_connection(
  990. client_factory, expected_sni=b"target-server"
  991. )
  992. self.assertEqual(len(http_server.requests), 1)
  993. request = http_server.requests[0]
  994. self.assertEqual(request.method, b"GET")
  995. self.assertEqual(request.path, b"/foo/bar")
  996. self.assertEqual(
  997. request.requestHeaders.getRawHeaders(b"host"), [b"target-server"]
  998. )
  999. # finish the request
  1000. request.finish()
  1001. self.reactor.pump((0.1,))
  1002. self.successResultOf(test_d)
  1003. def test_idna_servername(self) -> None:
  1004. """test the behaviour when the server name has idna chars in"""
  1005. self.agent = self._make_agent()
  1006. self.mock_resolver.resolve_service.return_value = []
  1007. # the resolver is always called with the IDNA hostname as a native string.
  1008. self.reactor.lookups["xn--bcher-kva.com"] = "1.2.3.4"
  1009. # this is idna for bücher.com
  1010. test_d = self._make_get_request(
  1011. b"matrix-federation://xn--bcher-kva.com/foo/bar"
  1012. )
  1013. # Nothing happened yet
  1014. self.assertNoResult(test_d)
  1015. # No SRV record lookup yet
  1016. self.mock_resolver.resolve_service.assert_not_called()
  1017. # there should be an attempt to connect on port 443 for the .well-known
  1018. clients = self.reactor.tcpClients
  1019. self.assertEqual(len(clients), 1)
  1020. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  1021. self.assertEqual(host, "1.2.3.4")
  1022. self.assertEqual(port, 443)
  1023. # fonx the connection
  1024. client_factory.clientConnectionFailed(None, Exception("nope"))
  1025. # attemptdelay on the hostnameendpoint is 0.3, so takes that long before the
  1026. # .well-known request fails.
  1027. self.reactor.pump((0.4,))
  1028. # now there should have been a SRV lookup
  1029. self.mock_resolver.resolve_service.assert_has_calls(
  1030. [
  1031. call(b"_matrix-fed._tcp.xn--bcher-kva.com"),
  1032. call(b"_matrix._tcp.xn--bcher-kva.com"),
  1033. ]
  1034. )
  1035. # We should fall back to port 8448
  1036. clients = self.reactor.tcpClients
  1037. self.assertEqual(len(clients), 2)
  1038. (host, port, client_factory, _timeout, _bindAddress) = clients[1]
  1039. self.assertEqual(host, "1.2.3.4")
  1040. self.assertEqual(port, 8448)
  1041. # make a test server, and wire up the client
  1042. http_server = self._make_connection(
  1043. client_factory, expected_sni=b"xn--bcher-kva.com"
  1044. )
  1045. self.assertEqual(len(http_server.requests), 1)
  1046. request = http_server.requests[0]
  1047. self.assertEqual(request.method, b"GET")
  1048. self.assertEqual(request.path, b"/foo/bar")
  1049. self.assertEqual(
  1050. request.requestHeaders.getRawHeaders(b"host"), [b"xn--bcher-kva.com"]
  1051. )
  1052. # finish the request
  1053. request.finish()
  1054. self.reactor.pump((0.1,))
  1055. self.successResultOf(test_d)
  1056. def test_idna_srv_target(self) -> None:
  1057. """test the behaviour when the target of a _matrix-fed SRV record has idna chars"""
  1058. self.agent = self._make_agent()
  1059. self.mock_resolver.resolve_service.return_value = [
  1060. Server(host=b"xn--trget-3qa.com", port=8443)
  1061. ] # târget.com
  1062. self.reactor.lookups["xn--trget-3qa.com"] = "1.2.3.4"
  1063. test_d = self._make_get_request(
  1064. b"matrix-federation://xn--bcher-kva.com/foo/bar"
  1065. )
  1066. # Nothing happened yet
  1067. self.assertNoResult(test_d)
  1068. self.mock_resolver.resolve_service.assert_called_once_with(
  1069. b"_matrix-fed._tcp.xn--bcher-kva.com"
  1070. )
  1071. # Make sure treq is trying to connect
  1072. clients = self.reactor.tcpClients
  1073. self.assertEqual(len(clients), 1)
  1074. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  1075. self.assertEqual(host, "1.2.3.4")
  1076. self.assertEqual(port, 8443)
  1077. # make a test server, and wire up the client
  1078. http_server = self._make_connection(
  1079. client_factory, expected_sni=b"xn--bcher-kva.com"
  1080. )
  1081. self.assertEqual(len(http_server.requests), 1)
  1082. request = http_server.requests[0]
  1083. self.assertEqual(request.method, b"GET")
  1084. self.assertEqual(request.path, b"/foo/bar")
  1085. self.assertEqual(
  1086. request.requestHeaders.getRawHeaders(b"host"), [b"xn--bcher-kva.com"]
  1087. )
  1088. # finish the request
  1089. request.finish()
  1090. self.reactor.pump((0.1,))
  1091. self.successResultOf(test_d)
  1092. def test_idna_srv_target_legacy(self) -> None:
  1093. """test the behaviour when the target of a _matrix SRV record has idna chars"""
  1094. self.agent = self._make_agent()
  1095. # Return no entries for the _matrix-fed lookup, and a response for _matrix.
  1096. self.mock_resolver.resolve_service.side_effect = [
  1097. [],
  1098. [Server(host=b"xn--trget-3qa.com", port=8443)],
  1099. ] # târget.com
  1100. self.reactor.lookups["xn--trget-3qa.com"] = "1.2.3.4"
  1101. test_d = self._make_get_request(
  1102. b"matrix-federation://xn--bcher-kva.com/foo/bar"
  1103. )
  1104. # Nothing happened yet
  1105. self.assertNoResult(test_d)
  1106. self.mock_resolver.resolve_service.assert_has_calls(
  1107. [
  1108. call(b"_matrix-fed._tcp.xn--bcher-kva.com"),
  1109. call(b"_matrix._tcp.xn--bcher-kva.com"),
  1110. ]
  1111. )
  1112. # Make sure treq is trying to connect
  1113. clients = self.reactor.tcpClients
  1114. self.assertEqual(len(clients), 1)
  1115. (host, port, client_factory, _timeout, _bindAddress) = clients[0]
  1116. self.assertEqual(host, "1.2.3.4")
  1117. self.assertEqual(port, 8443)
  1118. # make a test server, and wire up the client
  1119. http_server = self._make_connection(
  1120. client_factory, expected_sni=b"xn--bcher-kva.com"
  1121. )
  1122. self.assertEqual(len(http_server.requests), 1)
  1123. request = http_server.requests[0]
  1124. self.assertEqual(request.method, b"GET")
  1125. self.assertEqual(request.path, b"/foo/bar")
  1126. self.assertEqual(
  1127. request.requestHeaders.getRawHeaders(b"host"), [b"xn--bcher-kva.com"]
  1128. )
  1129. # finish the request
  1130. request.finish()
  1131. self.reactor.pump((0.1,))
  1132. self.successResultOf(test_d)
  1133. def test_well_known_cache(self) -> None:
  1134. self.reactor.lookups["testserv"] = "1.2.3.4"
  1135. fetch_d = defer.ensureDeferred(
  1136. self.well_known_resolver.get_well_known(b"testserv")
  1137. )
  1138. # there should be an attempt to connect on port 443 for the .well-known
  1139. clients = self.reactor.tcpClients
  1140. self.assertEqual(len(clients), 1)
  1141. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1142. self.assertEqual(host, "1.2.3.4")
  1143. self.assertEqual(port, 443)
  1144. well_known_server = self._handle_well_known_connection(
  1145. client_factory,
  1146. expected_sni=b"testserv",
  1147. response_headers={b"Cache-Control": b"max-age=1000"},
  1148. content=b'{ "m.server": "target-server" }',
  1149. )
  1150. r = self.successResultOf(fetch_d)
  1151. self.assertEqual(r.delegated_server, b"target-server")
  1152. # close the tcp connection
  1153. well_known_server.loseConnection()
  1154. # repeat the request: it should hit the cache
  1155. fetch_d = defer.ensureDeferred(
  1156. self.well_known_resolver.get_well_known(b"testserv")
  1157. )
  1158. r = self.successResultOf(fetch_d)
  1159. self.assertEqual(r.delegated_server, b"target-server")
  1160. # expire the cache
  1161. self.reactor.pump((1000.0,))
  1162. # now it should connect again
  1163. fetch_d = defer.ensureDeferred(
  1164. self.well_known_resolver.get_well_known(b"testserv")
  1165. )
  1166. self.assertEqual(len(clients), 1)
  1167. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1168. self.assertEqual(host, "1.2.3.4")
  1169. self.assertEqual(port, 443)
  1170. self._handle_well_known_connection(
  1171. client_factory,
  1172. expected_sni=b"testserv",
  1173. content=b'{ "m.server": "other-server" }',
  1174. )
  1175. r = self.successResultOf(fetch_d)
  1176. self.assertEqual(r.delegated_server, b"other-server")
  1177. def test_well_known_cache_with_temp_failure(self) -> None:
  1178. """Test that we refetch well-known before the cache expires, and that
  1179. it ignores transient errors.
  1180. """
  1181. self.reactor.lookups["testserv"] = "1.2.3.4"
  1182. fetch_d = defer.ensureDeferred(
  1183. self.well_known_resolver.get_well_known(b"testserv")
  1184. )
  1185. # there should be an attempt to connect on port 443 for the .well-known
  1186. clients = self.reactor.tcpClients
  1187. self.assertEqual(len(clients), 1)
  1188. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1189. self.assertEqual(host, "1.2.3.4")
  1190. self.assertEqual(port, 443)
  1191. well_known_server = self._handle_well_known_connection(
  1192. client_factory,
  1193. expected_sni=b"testserv",
  1194. response_headers={b"Cache-Control": b"max-age=1000"},
  1195. content=b'{ "m.server": "target-server" }',
  1196. )
  1197. r = self.successResultOf(fetch_d)
  1198. self.assertEqual(r.delegated_server, b"target-server")
  1199. # close the tcp connection
  1200. well_known_server.loseConnection()
  1201. # Get close to the cache expiry, this will cause the resolver to do
  1202. # another lookup.
  1203. self.reactor.pump((900.0,))
  1204. fetch_d = defer.ensureDeferred(
  1205. self.well_known_resolver.get_well_known(b"testserv")
  1206. )
  1207. # The resolver may retry a few times, so fonx all requests that come along
  1208. attempts = 0
  1209. while self.reactor.tcpClients:
  1210. clients = self.reactor.tcpClients
  1211. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1212. attempts += 1
  1213. # fonx the connection attempt, this will be treated as a temporary
  1214. # failure.
  1215. client_factory.clientConnectionFailed(None, Exception("nope"))
  1216. # There's a few sleeps involved, so we have to pump the reactor a
  1217. # bit.
  1218. self.reactor.pump((1.0, 1.0))
  1219. # We expect to see more than one attempt as there was previously a valid
  1220. # well known.
  1221. self.assertGreater(attempts, 1)
  1222. # Resolver should return cached value, despite the lookup failing.
  1223. r = self.successResultOf(fetch_d)
  1224. self.assertEqual(r.delegated_server, b"target-server")
  1225. # Expire both caches and repeat the request
  1226. self.reactor.pump((10000.0,))
  1227. # Repeat the request, this time it should fail if the lookup fails.
  1228. fetch_d = defer.ensureDeferred(
  1229. self.well_known_resolver.get_well_known(b"testserv")
  1230. )
  1231. clients = self.reactor.tcpClients
  1232. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1233. client_factory.clientConnectionFailed(None, Exception("nope"))
  1234. self.reactor.pump((0.4,))
  1235. r = self.successResultOf(fetch_d)
  1236. self.assertEqual(r.delegated_server, None)
  1237. def test_well_known_too_large(self) -> None:
  1238. """A well-known query that returns a result which is too large should be rejected."""
  1239. self.reactor.lookups["testserv"] = "1.2.3.4"
  1240. fetch_d = defer.ensureDeferred(
  1241. self.well_known_resolver.get_well_known(b"testserv")
  1242. )
  1243. # there should be an attempt to connect on port 443 for the .well-known
  1244. clients = self.reactor.tcpClients
  1245. self.assertEqual(len(clients), 1)
  1246. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1247. self.assertEqual(host, "1.2.3.4")
  1248. self.assertEqual(port, 443)
  1249. self._handle_well_known_connection(
  1250. client_factory,
  1251. expected_sni=b"testserv",
  1252. response_headers={b"Cache-Control": b"max-age=1000"},
  1253. content=b'{ "m.server": "' + (b"a" * WELL_KNOWN_MAX_SIZE) + b'" }',
  1254. )
  1255. # The result is successful, but disabled delegation.
  1256. r = self.successResultOf(fetch_d)
  1257. self.assertIsNone(r.delegated_server)
  1258. def test_srv_fallbacks(self) -> None:
  1259. """Test that other SRV results are tried if the first one fails for _matrix-fed SRV."""
  1260. self.agent = self._make_agent()
  1261. self.mock_resolver.resolve_service.return_value = [
  1262. Server(host=b"target.com", port=8443),
  1263. Server(host=b"target.com", port=8444),
  1264. ]
  1265. self.reactor.lookups["target.com"] = "1.2.3.4"
  1266. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  1267. # Nothing happened yet
  1268. self.assertNoResult(test_d)
  1269. self.mock_resolver.resolve_service.assert_called_once_with(
  1270. b"_matrix-fed._tcp.testserv"
  1271. )
  1272. # We should see an attempt to connect to the first server
  1273. clients = self.reactor.tcpClients
  1274. self.assertEqual(len(clients), 1)
  1275. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1276. self.assertEqual(host, "1.2.3.4")
  1277. self.assertEqual(port, 8443)
  1278. # Fonx the connection
  1279. client_factory.clientConnectionFailed(None, Exception("nope"))
  1280. # There's a 300ms delay in HostnameEndpoint
  1281. self.reactor.pump((0.4,))
  1282. # Hasn't failed yet
  1283. self.assertNoResult(test_d)
  1284. # We shouldnow see an attempt to connect to the second server
  1285. clients = self.reactor.tcpClients
  1286. self.assertEqual(len(clients), 1)
  1287. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1288. self.assertEqual(host, "1.2.3.4")
  1289. self.assertEqual(port, 8444)
  1290. # make a test server, and wire up the client
  1291. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  1292. self.assertEqual(len(http_server.requests), 1)
  1293. request = http_server.requests[0]
  1294. self.assertEqual(request.method, b"GET")
  1295. self.assertEqual(request.path, b"/foo/bar")
  1296. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  1297. # finish the request
  1298. request.finish()
  1299. self.reactor.pump((0.1,))
  1300. self.successResultOf(test_d)
  1301. def test_srv_fallbacks_legacy(self) -> None:
  1302. """Test that other SRV results are tried if the first one fails for _matrix SRV."""
  1303. self.agent = self._make_agent()
  1304. # Return no entries for the _matrix-fed lookup, and a response for _matrix.
  1305. self.mock_resolver.resolve_service.side_effect = [
  1306. [],
  1307. [
  1308. Server(host=b"target.com", port=8443),
  1309. Server(host=b"target.com", port=8444),
  1310. ],
  1311. ]
  1312. self.reactor.lookups["target.com"] = "1.2.3.4"
  1313. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  1314. # Nothing happened yet
  1315. self.assertNoResult(test_d)
  1316. self.mock_resolver.resolve_service.assert_has_calls(
  1317. [call(b"_matrix-fed._tcp.testserv"), call(b"_matrix._tcp.testserv")]
  1318. )
  1319. # We should see an attempt to connect to the first server
  1320. clients = self.reactor.tcpClients
  1321. self.assertEqual(len(clients), 1)
  1322. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1323. self.assertEqual(host, "1.2.3.4")
  1324. self.assertEqual(port, 8443)
  1325. # Fonx the connection
  1326. client_factory.clientConnectionFailed(None, Exception("nope"))
  1327. # There's a 300ms delay in HostnameEndpoint
  1328. self.reactor.pump((0.4,))
  1329. # Hasn't failed yet
  1330. self.assertNoResult(test_d)
  1331. # We shouldnow see an attempt to connect to the second server
  1332. clients = self.reactor.tcpClients
  1333. self.assertEqual(len(clients), 1)
  1334. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1335. self.assertEqual(host, "1.2.3.4")
  1336. self.assertEqual(port, 8444)
  1337. # make a test server, and wire up the client
  1338. http_server = self._make_connection(client_factory, expected_sni=b"testserv")
  1339. self.assertEqual(len(http_server.requests), 1)
  1340. request = http_server.requests[0]
  1341. self.assertEqual(request.method, b"GET")
  1342. self.assertEqual(request.path, b"/foo/bar")
  1343. self.assertEqual(request.requestHeaders.getRawHeaders(b"host"), [b"testserv"])
  1344. # finish the request
  1345. request.finish()
  1346. self.reactor.pump((0.1,))
  1347. self.successResultOf(test_d)
  1348. def test_srv_no_fallback_to_legacy(self) -> None:
  1349. """Test that _matrix SRV results are not tried if the _matrix-fed one fails."""
  1350. self.agent = self._make_agent()
  1351. # Return a failing entry for _matrix-fed.
  1352. self.mock_resolver.resolve_service.side_effect = [
  1353. [Server(host=b"target.com", port=8443)],
  1354. [],
  1355. ]
  1356. self.reactor.lookups["target.com"] = "1.2.3.4"
  1357. test_d = self._make_get_request(b"matrix-federation://testserv/foo/bar")
  1358. # Nothing happened yet
  1359. self.assertNoResult(test_d)
  1360. # Only the _matrix-fed is checked, _matrix is ignored.
  1361. self.mock_resolver.resolve_service.assert_called_once_with(
  1362. b"_matrix-fed._tcp.testserv"
  1363. )
  1364. # We should see an attempt to connect to the first server
  1365. clients = self.reactor.tcpClients
  1366. self.assertEqual(len(clients), 1)
  1367. (host, port, client_factory, _timeout, _bindAddress) = clients.pop(0)
  1368. self.assertEqual(host, "1.2.3.4")
  1369. self.assertEqual(port, 8443)
  1370. # Fonx the connection
  1371. client_factory.clientConnectionFailed(None, Exception("nope"))
  1372. # There's a 300ms delay in HostnameEndpoint
  1373. self.reactor.pump((0.4,))
  1374. # Failed to resolve a server.
  1375. self.assertFailure(test_d, Exception)
  1376. class TestCachePeriodFromHeaders(unittest.TestCase):
  1377. def test_cache_control(self) -> None:
  1378. # uppercase
  1379. self.assertEqual(
  1380. _cache_period_from_headers(
  1381. Headers({b"Cache-Control": [b"foo, Max-Age = 100, bar"]})
  1382. ),
  1383. 100,
  1384. )
  1385. # missing value
  1386. self.assertIsNone(
  1387. _cache_period_from_headers(Headers({b"Cache-Control": [b"max-age=, bar"]}))
  1388. )
  1389. # hackernews: bogus due to semicolon
  1390. self.assertIsNone(
  1391. _cache_period_from_headers(
  1392. Headers({b"Cache-Control": [b"private; max-age=0"]})
  1393. )
  1394. )
  1395. # github
  1396. self.assertEqual(
  1397. _cache_period_from_headers(
  1398. Headers({b"Cache-Control": [b"max-age=0, private, must-revalidate"]})
  1399. ),
  1400. 0,
  1401. )
  1402. # google
  1403. self.assertEqual(
  1404. _cache_period_from_headers(
  1405. Headers({b"cache-control": [b"private, max-age=0"]})
  1406. ),
  1407. 0,
  1408. )
  1409. def test_expires(self) -> None:
  1410. self.assertEqual(
  1411. _cache_period_from_headers(
  1412. Headers({b"Expires": [b"Wed, 30 Jan 2019 07:35:33 GMT"]}),
  1413. time_now=lambda: 1548833700,
  1414. ),
  1415. 33,
  1416. )
  1417. # cache-control overrides expires
  1418. self.assertEqual(
  1419. _cache_period_from_headers(
  1420. Headers(
  1421. {
  1422. b"cache-control": [b"max-age=10"],
  1423. b"Expires": [b"Wed, 30 Jan 2019 07:35:33 GMT"],
  1424. }
  1425. ),
  1426. time_now=lambda: 1548833700,
  1427. ),
  1428. 10,
  1429. )
  1430. # invalid expires means immediate expiry
  1431. self.assertEqual(_cache_period_from_headers(Headers({b"Expires": [b"0"]})), 0)
  1432. def _check_logcontext(context: LoggingContextOrSentinel) -> None:
  1433. current = current_context()
  1434. if current is not context:
  1435. raise AssertionError("Expected logcontext %s but was %s" % (context, current))
  1436. def _wrap_server_factory_for_tls(
  1437. factory: IProtocolFactory, sanlist: Optional[List[bytes]] = None
  1438. ) -> TLSMemoryBIOFactory:
  1439. """Wrap an existing Protocol Factory with a test TLSMemoryBIOFactory
  1440. The resultant factory will create a TLS server which presents a certificate
  1441. signed by our test CA, valid for the domains in `sanlist`
  1442. Args:
  1443. factory: protocol factory to wrap
  1444. sanlist: list of domains the cert should be valid for
  1445. Returns:
  1446. interfaces.IProtocolFactory
  1447. """
  1448. if sanlist is None:
  1449. sanlist = [
  1450. b"DNS:testserv",
  1451. b"DNS:target-server",
  1452. b"DNS:xn--bcher-kva.com",
  1453. b"IP:1.2.3.4",
  1454. b"IP:::1",
  1455. ]
  1456. connection_creator = TestServerTLSConnectionFactory(sanlist=sanlist)
  1457. return TLSMemoryBIOFactory(
  1458. connection_creator, isClient=False, wrappedFactory=factory
  1459. )
  1460. def _get_test_protocol_factory() -> IProtocolFactory:
  1461. """Get a protocol Factory which will build an HTTPChannel
  1462. Returns:
  1463. interfaces.IProtocolFactory
  1464. """
  1465. server_factory = Factory.forProtocol(HTTPChannel)
  1466. # Request.finish expects the factory to have a 'log' method.
  1467. server_factory.log = _log_request
  1468. return server_factory
  1469. def _log_request(request: str) -> None:
  1470. """Implements Factory.log, which is expected by Request.finish"""
  1471. logger.info(f"Completed request {request}")
  1472. @implementer(IPolicyForHTTPS)
  1473. class TrustingTLSPolicyForHTTPS:
  1474. """An IPolicyForHTTPS which checks that the certificate belongs to the
  1475. right server, but doesn't check the certificate chain."""
  1476. def creatorForNetloc(
  1477. self, hostname: bytes, port: int
  1478. ) -> IOpenSSLClientConnectionCreator:
  1479. certificateOptions = OpenSSLCertificateOptions()
  1480. return ClientTLSOptions(hostname, certificateOptions.getContext())