2022-06-21 17:20:08 -04:00
|
|
|
"""
|
2022-06-30 14:21:21 -04:00
|
|
|
Support for listening with both HTTPS and Foolscap on the same port.
|
|
|
|
|
|
|
|
The goal is to make the transition from Foolscap to HTTPS-based protocols as
|
|
|
|
simple as possible, with no extra configuration needed. Listening on the same
|
|
|
|
port means a user upgrading Tahoe-LAFS will automatically get HTTPS working
|
|
|
|
with no additional changes.
|
2022-06-21 17:20:08 -04:00
|
|
|
|
2022-06-30 14:26:36 -04:00
|
|
|
Use ``support_foolscap_and_https()`` to create a new subclass for a ``Tub``
|
|
|
|
instance, and then ``add_storage_server()`` on the resulting class to add the
|
|
|
|
relevant information for a storage server once it becomes available later in
|
|
|
|
the configuration process.
|
2022-06-30 14:21:21 -04:00
|
|
|
"""
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
from twisted.internet.protocol import Protocol
|
2022-06-23 12:47:33 -04:00
|
|
|
from twisted.internet.interfaces import IDelayedCall
|
2022-06-30 15:18:22 -04:00
|
|
|
from twisted.internet.ssl import CertificateOptions
|
2022-06-22 14:19:29 -04:00
|
|
|
from twisted.web.server import Site
|
|
|
|
from twisted.protocols.tls import TLSMemoryBIOFactory
|
2022-06-23 12:47:33 -04:00
|
|
|
from twisted.internet import reactor
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
from foolscap.negotiate import Negotiation
|
2022-06-30 14:26:36 -04:00
|
|
|
from foolscap.api import Tub
|
2022-06-21 17:20:08 -04:00
|
|
|
|
2022-06-22 14:19:29 -04:00
|
|
|
from .storage.http_server import HTTPServer
|
2022-06-23 07:59:43 -04:00
|
|
|
from .storage.server import StorageServer
|
2022-06-22 14:19:29 -04:00
|
|
|
|
2022-06-22 10:23:23 -04:00
|
|
|
|
2022-06-30 14:21:21 -04:00
|
|
|
class _PretendToBeNegotiation(type):
|
|
|
|
"""
|
|
|
|
Metaclass that allows ``_FoolscapOrHttps`` to pretend to be a ``Negotiation``
|
|
|
|
instance, since Foolscap has some ``assert isinstance(protocol,
|
|
|
|
Negotiation`` checks.
|
|
|
|
"""
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
def __instancecheck__(self, instance):
|
|
|
|
return (instance.__class__ == self) or isinstance(instance, Negotiation)
|
|
|
|
|
|
|
|
|
2022-06-30 14:21:21 -04:00
|
|
|
class _FoolscapOrHttps(Protocol, metaclass=_PretendToBeNegotiation):
|
2022-06-21 17:20:08 -04:00
|
|
|
"""
|
|
|
|
Based on initial query, decide whether we're talking Foolscap or HTTP.
|
|
|
|
|
2022-06-30 14:21:21 -04:00
|
|
|
Additionally, pretends to be a ``foolscap.negotiate.Negotiation`` instance,
|
|
|
|
since these are created by Foolscap's ``Tub``, by setting this to be the
|
|
|
|
tub's ``negotiationClass``.
|
|
|
|
|
|
|
|
Do not use directly; this needs to be subclassed per ``Tub``.
|
2022-06-21 17:20:08 -04:00
|
|
|
"""
|
2022-06-22 10:23:23 -04:00
|
|
|
|
2022-06-30 14:52:12 -04:00
|
|
|
# These will be set by support_foolscap_and_https() and add_storage_server().
|
|
|
|
|
2022-06-30 15:18:22 -04:00
|
|
|
# The HTTP storage server API we're exposing.
|
|
|
|
http_storage_server: HTTPServer
|
|
|
|
# The Twisted HTTPS protocol factory wrapping the storage server API:
|
|
|
|
https_factory: TLSMemoryBIOFactory
|
2022-06-30 14:52:12 -04:00
|
|
|
# The tub that created us:
|
|
|
|
tub: Tub
|
2022-06-23 07:59:43 -04:00
|
|
|
|
2022-06-30 15:18:22 -04:00
|
|
|
# This will be created by the instance in connectionMade():
|
2022-06-23 12:47:33 -04:00
|
|
|
_timeout: IDelayedCall
|
|
|
|
|
2022-06-30 14:26:36 -04:00
|
|
|
@classmethod
|
2022-06-30 15:18:22 -04:00
|
|
|
def add_storage_server(cls, storage_server: StorageServer, swissnum):
|
2022-06-30 14:26:36 -04:00
|
|
|
"""
|
2022-06-30 14:52:12 -04:00
|
|
|
Add the various storage server-related attributes needed by a
|
|
|
|
``Tub``-specific ``_FoolscapOrHttps`` subclass.
|
2022-06-30 14:26:36 -04:00
|
|
|
"""
|
2022-06-30 15:18:22 -04:00
|
|
|
# Tub.myCertificate is a twisted.internet.ssl.PrivateCertificate
|
|
|
|
# instance.
|
|
|
|
certificate_options = CertificateOptions(
|
|
|
|
privateKey=cls.tub.myCertificate.privateKey.original,
|
|
|
|
certificate=cls.tub.myCertificate.original,
|
|
|
|
)
|
|
|
|
|
|
|
|
cls.http_storage_server = HTTPServer(storage_server, swissnum)
|
|
|
|
cls.https_factory = TLSMemoryBIOFactory(
|
|
|
|
certificate_options,
|
|
|
|
False,
|
|
|
|
Site(cls.http_storage_server.get_resource()),
|
|
|
|
)
|
2022-06-30 14:26:36 -04:00
|
|
|
|
2022-06-21 17:20:08 -04:00
|
|
|
def __init__(self, *args, **kwargs):
|
2022-06-23 12:41:01 -04:00
|
|
|
self._foolscap: Negotiation = Negotiation(*args, **kwargs)
|
|
|
|
self._buffer: bytes = b""
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
def __setattr__(self, name, value):
|
2022-06-23 12:49:07 -04:00
|
|
|
if name in {"_foolscap", "_buffer", "transport", "__class__", "_timeout"}:
|
2022-06-21 17:20:08 -04:00
|
|
|
object.__setattr__(self, name, value)
|
|
|
|
else:
|
|
|
|
setattr(self._foolscap, name, value)
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
return getattr(self._foolscap, name)
|
|
|
|
|
2022-06-23 12:43:46 -04:00
|
|
|
def _convert_to_negotiation(self):
|
|
|
|
"""
|
2022-06-23 12:44:17 -04:00
|
|
|
Convert self to a ``Negotiation`` instance.
|
2022-06-23 12:43:46 -04:00
|
|
|
"""
|
2022-06-23 12:32:43 -04:00
|
|
|
self.__class__ = Negotiation # type: ignore
|
|
|
|
self.__dict__ = self._foolscap.__dict__
|
2022-06-21 17:20:08 -04:00
|
|
|
|
|
|
|
def initClient(self, *args, **kwargs):
|
2022-06-22 10:23:23 -04:00
|
|
|
# After creation, a Negotiation instance either has initClient() or
|
2022-06-23 12:43:46 -04:00
|
|
|
# initServer() called. Since this is a client, we're never going to do
|
|
|
|
# HTTP, so we can immediately become a Negotiation instance.
|
2022-06-21 17:20:08 -04:00
|
|
|
assert not self._buffer
|
2022-06-23 12:32:43 -04:00
|
|
|
self._convert_to_negotiation()
|
2022-06-22 10:23:23 -04:00
|
|
|
return self.initClient(*args, **kwargs)
|
2022-06-21 17:20:08 -04:00
|
|
|
|
2022-06-23 12:47:33 -04:00
|
|
|
def connectionMade(self):
|
|
|
|
self._timeout = reactor.callLater(30, self.transport.abortConnection)
|
|
|
|
|
2022-06-21 17:20:08 -04:00
|
|
|
def dataReceived(self, data: bytes) -> None:
|
2022-06-23 12:41:01 -04:00
|
|
|
"""Handle incoming data.
|
2022-06-21 17:20:08 -04:00
|
|
|
|
2022-06-23 12:41:01 -04:00
|
|
|
Once we've decided which protocol we are, update self.__class__, at
|
|
|
|
which point all methods will be called on the new class.
|
|
|
|
"""
|
2022-06-21 17:20:08 -04:00
|
|
|
self._buffer += data
|
|
|
|
if len(self._buffer) < 8:
|
|
|
|
return
|
|
|
|
|
2022-06-22 10:23:23 -04:00
|
|
|
# Check if it looks like a Foolscap request. If so, it can handle this
|
2022-06-23 12:47:33 -04:00
|
|
|
# and later data, otherwise assume HTTPS.
|
|
|
|
self._timeout.cancel()
|
2022-06-21 17:20:08 -04:00
|
|
|
if self._buffer.startswith(b"GET /id/"):
|
2022-06-23 12:51:07 -04:00
|
|
|
# We're a Foolscap Negotiation server protocol instance:
|
2022-06-23 12:43:46 -04:00
|
|
|
transport = self.transport
|
|
|
|
buf = self._buffer
|
|
|
|
self._convert_to_negotiation()
|
2022-06-23 12:32:43 -04:00
|
|
|
self.makeConnection(transport)
|
|
|
|
self.dataReceived(buf)
|
|
|
|
return
|
2022-06-21 17:20:08 -04:00
|
|
|
else:
|
2022-06-23 12:51:07 -04:00
|
|
|
# We're a HTTPS protocol instance, serving the storage protocol:
|
2022-06-23 12:41:47 -04:00
|
|
|
assert self.transport is not None
|
2022-06-30 15:18:22 -04:00
|
|
|
protocol = self.https_factory.buildProtocol(self.transport.getPeer())
|
2022-06-22 14:19:29 -04:00
|
|
|
protocol.makeConnection(self.transport)
|
|
|
|
protocol.dataReceived(self._buffer)
|
2022-06-23 12:41:01 -04:00
|
|
|
self.__class__ = protocol.__class__
|
|
|
|
self.__dict__ = protocol.__dict__
|
2022-06-21 17:20:08 -04:00
|
|
|
|
2022-06-23 07:59:43 -04:00
|
|
|
|
2022-06-30 14:26:36 -04:00
|
|
|
def support_foolscap_and_https(tub: Tub):
|
2022-06-30 14:21:21 -04:00
|
|
|
"""
|
|
|
|
Create a new Foolscap-or-HTTPS protocol class for a specific ``Tub``
|
|
|
|
instance.
|
|
|
|
"""
|
2022-06-30 14:52:12 -04:00
|
|
|
the_tub = tub
|
2022-06-30 14:21:21 -04:00
|
|
|
|
2022-06-30 15:18:22 -04:00
|
|
|
class FoolscapOrHttpForTub(_FoolscapOrHttps):
|
2022-06-30 14:52:12 -04:00
|
|
|
tub = the_tub
|
2022-06-23 07:59:43 -04:00
|
|
|
|
2022-06-30 15:18:22 -04:00
|
|
|
tub.negotiationClass = FoolscapOrHttpForTub # type: ignore
|