mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Implement MSC1708 (.well-known lookups for server routing) (#4489)
This commit is contained in:
parent
2562319821
commit
99e36d5e24
@ -15,6 +15,7 @@ recursive-include docs *
|
|||||||
recursive-include scripts *
|
recursive-include scripts *
|
||||||
recursive-include scripts-dev *
|
recursive-include scripts-dev *
|
||||||
recursive-include synapse *.pyi
|
recursive-include synapse *.pyi
|
||||||
|
recursive-include tests *.pem
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
|
|
||||||
recursive-include synapse/res *
|
recursive-include synapse/res *
|
||||||
|
1
changelog.d/4408.feature
Normal file
1
changelog.d/4408.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Refactor 'sign_request' as 'build_auth_headers'
|
|
1
changelog.d/4409.feature
Normal file
1
changelog.d/4409.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Remove redundant federation connection wrapping code
|
|
1
changelog.d/4426.feature
Normal file
1
changelog.d/4426.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Remove redundant SynapseKeyClientProtocol magic
|
|
1
changelog.d/4427.feature
Normal file
1
changelog.d/4427.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Refactor and cleanup for SRV record lookup
|
|
1
changelog.d/4428.feature
Normal file
1
changelog.d/4428.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Move SRV logic into the Agent layer
|
|
1
changelog.d/4464.feature
Normal file
1
changelog.d/4464.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Move SRV logic into the Agent layer
|
|
1
changelog.d/4468.feature
Normal file
1
changelog.d/4468.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Move SRV logic into the Agent layer
|
|
1
changelog.d/4487.feature
Normal file
1
changelog.d/4487.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -1 +0,0 @@
|
|||||||
Fix idna and ipv6 literal handling in MatrixFederationAgent
|
|
1
changelog.d/4489.feature
Normal file
1
changelog.d/4489.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Implement MSC1708 (.well-known routing for server-server federation)
|
@ -12,6 +12,8 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import cgi
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
@ -20,7 +22,7 @@ from zope.interface import implementer
|
|||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
|
from twisted.internet.endpoints import HostnameEndpoint, wrapClientTLS
|
||||||
from twisted.web.client import URI, Agent, HTTPConnectionPool
|
from twisted.web.client import URI, Agent, HTTPConnectionPool, readBody
|
||||||
from twisted.web.http_headers import Headers
|
from twisted.web.http_headers import Headers
|
||||||
from twisted.web.iweb import IAgent
|
from twisted.web.iweb import IAgent
|
||||||
|
|
||||||
@ -43,13 +45,19 @@ class MatrixFederationAgent(object):
|
|||||||
tls_client_options_factory (ClientTLSOptionsFactory|None):
|
tls_client_options_factory (ClientTLSOptionsFactory|None):
|
||||||
factory to use for fetching client tls options, or none to disable TLS.
|
factory to use for fetching client tls options, or none to disable TLS.
|
||||||
|
|
||||||
|
_well_known_tls_policy (IPolicyForHTTPS|None):
|
||||||
|
TLS policy to use for fetching .well-known files. None to use a default
|
||||||
|
(browser-like) implementation.
|
||||||
|
|
||||||
srv_resolver (SrvResolver|None):
|
srv_resolver (SrvResolver|None):
|
||||||
SRVResolver impl to use for looking up SRV records. None to use a default
|
SRVResolver impl to use for looking up SRV records. None to use a default
|
||||||
implementation.
|
implementation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, reactor, tls_client_options_factory, _srv_resolver=None,
|
self, reactor, tls_client_options_factory,
|
||||||
|
_well_known_tls_policy=None,
|
||||||
|
_srv_resolver=None,
|
||||||
):
|
):
|
||||||
self._reactor = reactor
|
self._reactor = reactor
|
||||||
self._tls_client_options_factory = tls_client_options_factory
|
self._tls_client_options_factory = tls_client_options_factory
|
||||||
@ -62,6 +70,14 @@ class MatrixFederationAgent(object):
|
|||||||
self._pool.maxPersistentPerHost = 5
|
self._pool.maxPersistentPerHost = 5
|
||||||
self._pool.cachedConnectionTimeout = 2 * 60
|
self._pool.cachedConnectionTimeout = 2 * 60
|
||||||
|
|
||||||
|
agent_args = {}
|
||||||
|
if _well_known_tls_policy is not None:
|
||||||
|
# the param is called 'contextFactory', but actually passing a
|
||||||
|
# contextfactory is deprecated, and it expects an IPolicyForHTTPS.
|
||||||
|
agent_args['contextFactory'] = _well_known_tls_policy
|
||||||
|
_well_known_agent = Agent(self._reactor, pool=self._pool, **agent_args)
|
||||||
|
self._well_known_agent = _well_known_agent
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def request(self, method, uri, headers=None, bodyProducer=None):
|
def request(self, method, uri, headers=None, bodyProducer=None):
|
||||||
"""
|
"""
|
||||||
@ -114,7 +130,11 @@ class MatrixFederationAgent(object):
|
|||||||
class EndpointFactory(object):
|
class EndpointFactory(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def endpointForURI(_uri):
|
def endpointForURI(_uri):
|
||||||
logger.info("Connecting to %s:%s", res.target_host, res.target_port)
|
logger.info(
|
||||||
|
"Connecting to %s:%i",
|
||||||
|
res.target_host.decode("ascii"),
|
||||||
|
res.target_port,
|
||||||
|
)
|
||||||
ep = HostnameEndpoint(self._reactor, res.target_host, res.target_port)
|
ep = HostnameEndpoint(self._reactor, res.target_host, res.target_port)
|
||||||
if tls_options is not None:
|
if tls_options is not None:
|
||||||
ep = wrapClientTLS(tls_options, ep)
|
ep = wrapClientTLS(tls_options, ep)
|
||||||
@ -127,7 +147,7 @@ class MatrixFederationAgent(object):
|
|||||||
defer.returnValue(res)
|
defer.returnValue(res)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def _route_matrix_uri(self, parsed_uri):
|
def _route_matrix_uri(self, parsed_uri, lookup_well_known=True):
|
||||||
"""Helper for `request`: determine the routing for a Matrix URI
|
"""Helper for `request`: determine the routing for a Matrix URI
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -135,6 +155,9 @@ class MatrixFederationAgent(object):
|
|||||||
parsed with URI.fromBytes(uri, defaultPort=-1) to set the `port` to -1
|
parsed with URI.fromBytes(uri, defaultPort=-1) to set the `port` to -1
|
||||||
if there is no explicit port given.
|
if there is no explicit port given.
|
||||||
|
|
||||||
|
lookup_well_known (bool): True if we should look up the .well-known file if
|
||||||
|
there is no SRV record.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[_RoutingResult]
|
Deferred[_RoutingResult]
|
||||||
"""
|
"""
|
||||||
@ -169,6 +192,42 @@ class MatrixFederationAgent(object):
|
|||||||
service_name = b"_matrix._tcp.%s" % (parsed_uri.host,)
|
service_name = b"_matrix._tcp.%s" % (parsed_uri.host,)
|
||||||
server_list = yield self._srv_resolver.resolve_service(service_name)
|
server_list = yield self._srv_resolver.resolve_service(service_name)
|
||||||
|
|
||||||
|
if not server_list and lookup_well_known:
|
||||||
|
# try a .well-known lookup
|
||||||
|
well_known_server = yield self._get_well_known(parsed_uri.host)
|
||||||
|
|
||||||
|
if well_known_server:
|
||||||
|
# if we found a .well-known, start again, but don't do another
|
||||||
|
# .well-known lookup.
|
||||||
|
|
||||||
|
# parse the server name in the .well-known response into host/port.
|
||||||
|
# (This code is lifted from twisted.web.client.URI.fromBytes).
|
||||||
|
if b':' in well_known_server:
|
||||||
|
well_known_host, well_known_port = well_known_server.rsplit(b':', 1)
|
||||||
|
try:
|
||||||
|
well_known_port = int(well_known_port)
|
||||||
|
except ValueError:
|
||||||
|
# the part after the colon could not be parsed as an int
|
||||||
|
# - we assume it is an IPv6 literal with no port (the closing
|
||||||
|
# ']' stops it being parsed as an int)
|
||||||
|
well_known_host, well_known_port = well_known_server, -1
|
||||||
|
else:
|
||||||
|
well_known_host, well_known_port = well_known_server, -1
|
||||||
|
|
||||||
|
new_uri = URI(
|
||||||
|
scheme=parsed_uri.scheme,
|
||||||
|
netloc=well_known_server,
|
||||||
|
host=well_known_host,
|
||||||
|
port=well_known_port,
|
||||||
|
path=parsed_uri.path,
|
||||||
|
params=parsed_uri.params,
|
||||||
|
query=parsed_uri.query,
|
||||||
|
fragment=parsed_uri.fragment,
|
||||||
|
)
|
||||||
|
|
||||||
|
res = yield self._route_matrix_uri(new_uri, lookup_well_known=False)
|
||||||
|
defer.returnValue(res)
|
||||||
|
|
||||||
if not server_list:
|
if not server_list:
|
||||||
target_host = parsed_uri.host
|
target_host = parsed_uri.host
|
||||||
port = 8448
|
port = 8448
|
||||||
@ -190,6 +249,53 @@ class MatrixFederationAgent(object):
|
|||||||
target_port=port,
|
target_port=port,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _get_well_known(self, server_name):
|
||||||
|
"""Attempt to fetch and parse a .well-known file for the given server
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_name (bytes): name of the server, from the requested url
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Deferred[bytes|None]: either the new server name, from the .well-known, or
|
||||||
|
None if there was no .well-known file.
|
||||||
|
"""
|
||||||
|
# FIXME: add a cache
|
||||||
|
|
||||||
|
uri = b"https://%s/.well-known/matrix/server" % (server_name, )
|
||||||
|
logger.info("Fetching %s", uri.decode("ascii"))
|
||||||
|
try:
|
||||||
|
response = yield make_deferred_yieldable(
|
||||||
|
self._well_known_agent.request(b"GET", uri),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(
|
||||||
|
"Connection error fetching %s: %s",
|
||||||
|
uri.decode("ascii"), e,
|
||||||
|
)
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
body = yield make_deferred_yieldable(readBody(response))
|
||||||
|
|
||||||
|
if response.code != 200:
|
||||||
|
logger.info(
|
||||||
|
"Error response %i from %s: %s",
|
||||||
|
response.code, uri.decode("ascii"), body,
|
||||||
|
)
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
content_types = response.headers.getRawHeaders(u'content-type')
|
||||||
|
if content_types is None:
|
||||||
|
raise Exception("no content-type header on .well-known response")
|
||||||
|
content_type, _opts = cgi.parse_header(content_types[-1])
|
||||||
|
if content_type != 'application/json':
|
||||||
|
raise Exception("content-type not application/json on .well-known response")
|
||||||
|
parsed_body = json.loads(body.decode('utf-8'))
|
||||||
|
logger.info("Response from .well-known: %s", parsed_body)
|
||||||
|
if not isinstance(parsed_body, dict) or "m.server" not in parsed_body:
|
||||||
|
raise Exception("invalid .well-known response")
|
||||||
|
defer.returnValue(parsed_body["m.server"].encode("ascii"))
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class _RoutingResult(object):
|
class _RoutingResult(object):
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2019 New Vector Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from OpenSSL import SSL
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_cert_file():
|
||||||
|
"""get the path to the test cert"""
|
||||||
|
|
||||||
|
# the cert file itself is made with:
|
||||||
|
#
|
||||||
|
# openssl req -x509 -newkey rsa:4096 -keyout server.pem -out server.pem -days 36500 \
|
||||||
|
# -nodes -subj '/CN=testserv'
|
||||||
|
return os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
'server.pem',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ServerTLSContext(object):
|
||||||
|
"""A TLS Context which presents our test cert."""
|
||||||
|
def __init__(self):
|
||||||
|
self.filename = get_test_cert_file()
|
||||||
|
|
||||||
|
def getContext(self):
|
||||||
|
ctx = SSL.Context(SSL.TLSv1_METHOD)
|
||||||
|
ctx.use_certificate_file(self.filename)
|
||||||
|
ctx.use_privatekey_file(self.filename)
|
||||||
|
return ctx
|
@ -17,18 +17,21 @@ import logging
|
|||||||
from mock import Mock
|
from mock import Mock
|
||||||
|
|
||||||
import treq
|
import treq
|
||||||
|
from zope.interface import implementer
|
||||||
|
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
from twisted.internet._sslverify import ClientTLSOptions, OpenSSLCertificateOptions
|
||||||
from twisted.internet.protocol import Factory
|
from twisted.internet.protocol import Factory
|
||||||
from twisted.protocols.tls import TLSMemoryBIOFactory
|
from twisted.protocols.tls import TLSMemoryBIOFactory
|
||||||
from twisted.test.ssl_helpers import ServerTLSContext
|
|
||||||
from twisted.web.http import HTTPChannel
|
from twisted.web.http import HTTPChannel
|
||||||
|
from twisted.web.iweb import IPolicyForHTTPS
|
||||||
|
|
||||||
from synapse.crypto.context_factory import ClientTLSOptionsFactory
|
from synapse.crypto.context_factory import ClientTLSOptionsFactory
|
||||||
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
|
from synapse.http.federation.matrix_federation_agent import MatrixFederationAgent
|
||||||
from synapse.http.federation.srv_resolver import Server
|
from synapse.http.federation.srv_resolver import Server
|
||||||
from synapse.util.logcontext import LoggingContext
|
from synapse.util.logcontext import LoggingContext
|
||||||
|
|
||||||
|
from tests.http import ServerTLSContext
|
||||||
from tests.server import FakeTransport, ThreadedMemoryReactorClock
|
from tests.server import FakeTransport, ThreadedMemoryReactorClock
|
||||||
from tests.unittest import TestCase
|
from tests.unittest import TestCase
|
||||||
|
|
||||||
@ -44,6 +47,7 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
self.agent = MatrixFederationAgent(
|
self.agent = MatrixFederationAgent(
|
||||||
reactor=self.reactor,
|
reactor=self.reactor,
|
||||||
tls_client_options_factory=ClientTLSOptionsFactory(None),
|
tls_client_options_factory=ClientTLSOptionsFactory(None),
|
||||||
|
_well_known_tls_policy=TrustingTLSPolicyForHTTPS(),
|
||||||
_srv_resolver=self.mock_resolver,
|
_srv_resolver=self.mock_resolver,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,10 +69,14 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
# Normally this would be done by the TCP socket code in Twisted, but we are
|
# Normally this would be done by the TCP socket code in Twisted, but we are
|
||||||
# stubbing that out here.
|
# stubbing that out here.
|
||||||
client_protocol = client_factory.buildProtocol(None)
|
client_protocol = client_factory.buildProtocol(None)
|
||||||
client_protocol.makeConnection(FakeTransport(server_tls_protocol, self.reactor))
|
client_protocol.makeConnection(
|
||||||
|
FakeTransport(server_tls_protocol, self.reactor, client_protocol),
|
||||||
|
)
|
||||||
|
|
||||||
# tell the server tls protocol to send its stuff back to the client, too
|
# tell the server tls protocol to send its stuff back to the client, too
|
||||||
server_tls_protocol.makeConnection(FakeTransport(client_protocol, self.reactor))
|
server_tls_protocol.makeConnection(
|
||||||
|
FakeTransport(client_protocol, self.reactor, server_tls_protocol),
|
||||||
|
)
|
||||||
|
|
||||||
# give the reactor a pump to get the TLS juices flowing.
|
# give the reactor a pump to get the TLS juices flowing.
|
||||||
self.reactor.pump((0.1,))
|
self.reactor.pump((0.1,))
|
||||||
@ -101,9 +109,49 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
try:
|
try:
|
||||||
fetch_res = yield fetch_d
|
fetch_res = yield fetch_d
|
||||||
defer.returnValue(fetch_res)
|
defer.returnValue(fetch_res)
|
||||||
|
except Exception as e:
|
||||||
|
logger.info("Fetch of %s failed: %s", uri.decode("ascii"), e)
|
||||||
|
raise
|
||||||
finally:
|
finally:
|
||||||
_check_logcontext(context)
|
_check_logcontext(context)
|
||||||
|
|
||||||
|
def _handle_well_known_connection(self, client_factory, expected_sni, target_server):
|
||||||
|
"""Handle an outgoing HTTPs connection: wire it up to a server, check that the
|
||||||
|
request is for a .well-known, and send the response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client_factory (IProtocolFactory): outgoing connection
|
||||||
|
expected_sni (bytes): SNI that we expect the outgoing connection to send
|
||||||
|
target_server (bytes): target server that we should redirect to in the
|
||||||
|
.well-known response.
|
||||||
|
"""
|
||||||
|
# make the connection for .well-known
|
||||||
|
well_known_server = self._make_connection(
|
||||||
|
client_factory,
|
||||||
|
expected_sni=expected_sni,
|
||||||
|
)
|
||||||
|
# check the .well-known request and send a response
|
||||||
|
self.assertEqual(len(well_known_server.requests), 1)
|
||||||
|
request = well_known_server.requests[0]
|
||||||
|
self._send_well_known_response(request, target_server)
|
||||||
|
|
||||||
|
def _send_well_known_response(self, request, target_server):
|
||||||
|
"""Check that an incoming request looks like a valid .well-known request, and
|
||||||
|
send back the response.
|
||||||
|
"""
|
||||||
|
self.assertEqual(request.method, b'GET')
|
||||||
|
self.assertEqual(request.path, b'/.well-known/matrix/server')
|
||||||
|
self.assertEqual(
|
||||||
|
request.requestHeaders.getRawHeaders(b'host'),
|
||||||
|
[b'testserv'],
|
||||||
|
)
|
||||||
|
# send back a response
|
||||||
|
request.responseHeaders.setRawHeaders(b'Content-Type', [b'application/json'])
|
||||||
|
request.write(b'{ "m.server": "%s" }' % (target_server,))
|
||||||
|
request.finish()
|
||||||
|
|
||||||
|
self.reactor.pump((0.1, ))
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
"""
|
"""
|
||||||
happy-path test of a GET request with an explicit port
|
happy-path test of a GET request with an explicit port
|
||||||
@ -283,9 +331,9 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
self.reactor.pump((0.1,))
|
self.reactor.pump((0.1,))
|
||||||
self.successResultOf(test_d)
|
self.successResultOf(test_d)
|
||||||
|
|
||||||
def test_get_hostname_no_srv(self):
|
def test_get_no_srv_no_well_known(self):
|
||||||
"""
|
"""
|
||||||
Test the behaviour when the server name has no port, and no SRV record
|
Test the behaviour when the server name has no port, no SRV, and no well-known
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.mock_resolver.resolve_service.side_effect = lambda _: []
|
self.mock_resolver.resolve_service.side_effect = lambda _: []
|
||||||
@ -300,11 +348,24 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
b"_matrix._tcp.testserv",
|
b"_matrix._tcp.testserv",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure treq is trying to connect
|
# there should be an attempt to connect on port 443 for the .well-known
|
||||||
clients = self.reactor.tcpClients
|
clients = self.reactor.tcpClients
|
||||||
self.assertEqual(len(clients), 1)
|
self.assertEqual(len(clients), 1)
|
||||||
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
||||||
self.assertEqual(host, '1.2.3.4')
|
self.assertEqual(host, '1.2.3.4')
|
||||||
|
self.assertEqual(port, 443)
|
||||||
|
|
||||||
|
# fonx the connection
|
||||||
|
client_factory.clientConnectionFailed(None, Exception("nope"))
|
||||||
|
|
||||||
|
# attemptdelay on the hostnameendpoint is 0.3, so takes that long before the
|
||||||
|
# .well-known request fails.
|
||||||
|
self.reactor.pump((0.4,))
|
||||||
|
|
||||||
|
# we should fall back to a direct connection
|
||||||
|
self.assertEqual(len(clients), 2)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[1]
|
||||||
|
self.assertEqual(host, '1.2.3.4')
|
||||||
self.assertEqual(port, 8448)
|
self.assertEqual(port, 8448)
|
||||||
|
|
||||||
# make a test server, and wire up the client
|
# make a test server, and wire up the client
|
||||||
@ -327,6 +388,67 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
self.reactor.pump((0.1,))
|
self.reactor.pump((0.1,))
|
||||||
self.successResultOf(test_d)
|
self.successResultOf(test_d)
|
||||||
|
|
||||||
|
def test_get_well_known(self):
|
||||||
|
"""Test the behaviour when the server name has no port and no SRV record, but
|
||||||
|
the .well-known redirects elsewhere
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.mock_resolver.resolve_service.side_effect = lambda _: []
|
||||||
|
self.reactor.lookups["testserv"] = "1.2.3.4"
|
||||||
|
self.reactor.lookups["target-server"] = "1::f"
|
||||||
|
|
||||||
|
test_d = self._make_get_request(b"matrix://testserv/foo/bar")
|
||||||
|
|
||||||
|
# Nothing happened yet
|
||||||
|
self.assertNoResult(test_d)
|
||||||
|
|
||||||
|
self.mock_resolver.resolve_service.assert_called_once_with(
|
||||||
|
b"_matrix._tcp.testserv",
|
||||||
|
)
|
||||||
|
self.mock_resolver.resolve_service.reset_mock()
|
||||||
|
|
||||||
|
# there should be an attempt to connect on port 443 for the .well-known
|
||||||
|
clients = self.reactor.tcpClients
|
||||||
|
self.assertEqual(len(clients), 1)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
||||||
|
self.assertEqual(host, '1.2.3.4')
|
||||||
|
self.assertEqual(port, 443)
|
||||||
|
|
||||||
|
self._handle_well_known_connection(
|
||||||
|
client_factory, expected_sni=b"testserv", target_server=b"target-server",
|
||||||
|
)
|
||||||
|
|
||||||
|
# there should be another SRV lookup
|
||||||
|
self.mock_resolver.resolve_service.assert_called_once_with(
|
||||||
|
b"_matrix._tcp.target-server",
|
||||||
|
)
|
||||||
|
|
||||||
|
# now we should get a connection to the target server
|
||||||
|
self.assertEqual(len(clients), 2)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[1]
|
||||||
|
self.assertEqual(host, '1::f')
|
||||||
|
self.assertEqual(port, 8448)
|
||||||
|
|
||||||
|
# make a test server, and wire up the client
|
||||||
|
http_server = self._make_connection(
|
||||||
|
client_factory,
|
||||||
|
expected_sni=b'target-server',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(http_server.requests), 1)
|
||||||
|
request = http_server.requests[0]
|
||||||
|
self.assertEqual(request.method, b'GET')
|
||||||
|
self.assertEqual(request.path, b'/foo/bar')
|
||||||
|
self.assertEqual(
|
||||||
|
request.requestHeaders.getRawHeaders(b'host'),
|
||||||
|
[b'target-server'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# finish the request
|
||||||
|
request.finish()
|
||||||
|
self.reactor.pump((0.1,))
|
||||||
|
self.successResultOf(test_d)
|
||||||
|
|
||||||
def test_get_hostname_srv(self):
|
def test_get_hostname_srv(self):
|
||||||
"""
|
"""
|
||||||
Test the behaviour when there is a single SRV record
|
Test the behaviour when there is a single SRV record
|
||||||
@ -372,6 +494,71 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
self.reactor.pump((0.1,))
|
self.reactor.pump((0.1,))
|
||||||
self.successResultOf(test_d)
|
self.successResultOf(test_d)
|
||||||
|
|
||||||
|
def test_get_well_known_srv(self):
|
||||||
|
"""Test the behaviour when the server name has no port and no SRV record, but
|
||||||
|
the .well-known redirects to a place where there is a SRV.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.mock_resolver.resolve_service.side_effect = lambda _: []
|
||||||
|
self.reactor.lookups["testserv"] = "1.2.3.4"
|
||||||
|
self.reactor.lookups["srvtarget"] = "5.6.7.8"
|
||||||
|
|
||||||
|
test_d = self._make_get_request(b"matrix://testserv/foo/bar")
|
||||||
|
|
||||||
|
# Nothing happened yet
|
||||||
|
self.assertNoResult(test_d)
|
||||||
|
|
||||||
|
self.mock_resolver.resolve_service.assert_called_once_with(
|
||||||
|
b"_matrix._tcp.testserv",
|
||||||
|
)
|
||||||
|
self.mock_resolver.resolve_service.reset_mock()
|
||||||
|
|
||||||
|
# there should be an attempt to connect on port 443 for the .well-known
|
||||||
|
clients = self.reactor.tcpClients
|
||||||
|
self.assertEqual(len(clients), 1)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
||||||
|
self.assertEqual(host, '1.2.3.4')
|
||||||
|
self.assertEqual(port, 443)
|
||||||
|
|
||||||
|
self.mock_resolver.resolve_service.side_effect = lambda _: [
|
||||||
|
Server(host=b"srvtarget", port=8443),
|
||||||
|
]
|
||||||
|
|
||||||
|
self._handle_well_known_connection(
|
||||||
|
client_factory, expected_sni=b"testserv", target_server=b"target-server",
|
||||||
|
)
|
||||||
|
|
||||||
|
# there should be another SRV lookup
|
||||||
|
self.mock_resolver.resolve_service.assert_called_once_with(
|
||||||
|
b"_matrix._tcp.target-server",
|
||||||
|
)
|
||||||
|
|
||||||
|
# now we should get a connection to the target of the SRV record
|
||||||
|
self.assertEqual(len(clients), 2)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[1]
|
||||||
|
self.assertEqual(host, '5.6.7.8')
|
||||||
|
self.assertEqual(port, 8443)
|
||||||
|
|
||||||
|
# make a test server, and wire up the client
|
||||||
|
http_server = self._make_connection(
|
||||||
|
client_factory,
|
||||||
|
expected_sni=b'target-server',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(http_server.requests), 1)
|
||||||
|
request = http_server.requests[0]
|
||||||
|
self.assertEqual(request.method, b'GET')
|
||||||
|
self.assertEqual(request.path, b'/foo/bar')
|
||||||
|
self.assertEqual(
|
||||||
|
request.requestHeaders.getRawHeaders(b'host'),
|
||||||
|
[b'target-server'],
|
||||||
|
)
|
||||||
|
|
||||||
|
# finish the request
|
||||||
|
request.finish()
|
||||||
|
self.reactor.pump((0.1,))
|
||||||
|
self.successResultOf(test_d)
|
||||||
|
|
||||||
def test_idna_servername(self):
|
def test_idna_servername(self):
|
||||||
"""test the behaviour when the server name has idna chars in"""
|
"""test the behaviour when the server name has idna chars in"""
|
||||||
|
|
||||||
@ -390,11 +577,25 @@ class MatrixFederationAgentTests(TestCase):
|
|||||||
b"_matrix._tcp.xn--bcher-kva.com",
|
b"_matrix._tcp.xn--bcher-kva.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure treq is trying to connect
|
# there should be an attempt to connect on port 443 for the .well-known
|
||||||
clients = self.reactor.tcpClients
|
clients = self.reactor.tcpClients
|
||||||
self.assertEqual(len(clients), 1)
|
self.assertEqual(len(clients), 1)
|
||||||
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
(host, port, client_factory, _timeout, _bindAddress) = clients[0]
|
||||||
self.assertEqual(host, '1.2.3.4')
|
self.assertEqual(host, '1.2.3.4')
|
||||||
|
self.assertEqual(port, 443)
|
||||||
|
|
||||||
|
# fonx the connection
|
||||||
|
client_factory.clientConnectionFailed(None, Exception("nope"))
|
||||||
|
|
||||||
|
# attemptdelay on the hostnameendpoint is 0.3, so takes that long before the
|
||||||
|
# .well-known request fails.
|
||||||
|
self.reactor.pump((0.4,))
|
||||||
|
|
||||||
|
# We should fall back to port 8448
|
||||||
|
clients = self.reactor.tcpClients
|
||||||
|
self.assertEqual(len(clients), 2)
|
||||||
|
(host, port, client_factory, _timeout, _bindAddress) = clients[1]
|
||||||
|
self.assertEqual(host, '1.2.3.4')
|
||||||
self.assertEqual(port, 8448)
|
self.assertEqual(port, 8448)
|
||||||
|
|
||||||
# make a test server, and wire up the client
|
# make a test server, and wire up the client
|
||||||
@ -492,3 +693,11 @@ def _build_test_server():
|
|||||||
def _log_request(request):
|
def _log_request(request):
|
||||||
"""Implements Factory.log, which is expected by Request.finish"""
|
"""Implements Factory.log, which is expected by Request.finish"""
|
||||||
logger.info("Completed request %s", request)
|
logger.info("Completed request %s", request)
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(IPolicyForHTTPS)
|
||||||
|
class TrustingTLSPolicyForHTTPS(object):
|
||||||
|
"""An IPolicyForHTTPS which doesn't do any certificate verification"""
|
||||||
|
def creatorForNetloc(self, hostname, port):
|
||||||
|
certificateOptions = OpenSSLCertificateOptions()
|
||||||
|
return ClientTLSOptions(hostname, certificateOptions.getContext())
|
||||||
|
81
tests/http/server.pem
Normal file
81
tests/http/server.pem
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
KILVFsq3cQC+bEnoM4eVbjEQkGs1RQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
@ -354,6 +354,11 @@ class FakeTransport(object):
|
|||||||
:type: twisted.internet.interfaces.IReactorTime
|
:type: twisted.internet.interfaces.IReactorTime
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_protocol = attr.ib(default=None)
|
||||||
|
"""The Protocol which is producing data for this transport. Optional, but if set
|
||||||
|
will get called back for connectionLost() notifications etc.
|
||||||
|
"""
|
||||||
|
|
||||||
disconnecting = False
|
disconnecting = False
|
||||||
buffer = attr.ib(default=b'')
|
buffer = attr.ib(default=b'')
|
||||||
producer = attr.ib(default=None)
|
producer = attr.ib(default=None)
|
||||||
@ -364,8 +369,12 @@ class FakeTransport(object):
|
|||||||
def getHost(self):
|
def getHost(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def loseConnection(self):
|
def loseConnection(self, reason=None):
|
||||||
self.disconnecting = True
|
logger.info("FakeTransport: loseConnection(%s)", reason)
|
||||||
|
if not self.disconnecting:
|
||||||
|
self.disconnecting = True
|
||||||
|
if self._protocol:
|
||||||
|
self._protocol.connectionLost(reason)
|
||||||
|
|
||||||
def abortConnection(self):
|
def abortConnection(self):
|
||||||
self.disconnecting = True
|
self.disconnecting = True
|
||||||
|
Loading…
Reference in New Issue
Block a user