tahoe-lafs/src/allmydata/protocol_switch.py

126 lines
4.1 KiB
Python
Raw Normal View History

"""
Support for listening with both HTTP and Foolscap on the same port.
"""
2022-06-23 12:47:33 -04:00
from typing import Optional
from twisted.internet.protocol import Protocol
2022-06-23 12:47:33 -04:00
from twisted.internet.interfaces import IDelayedCall
2022-06-23 12:41:47 -04:00
from twisted.internet.ssl import CertificateOptions, PrivateCertificate
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
from foolscap.negotiate import Negotiation
from .storage.http_server import HTTPServer
2022-06-23 07:59:43 -04:00
from .storage.server import StorageServer
2022-06-22 10:23:23 -04:00
class PretendToBeNegotiation(type):
"""😱"""
def __instancecheck__(self, instance):
return (instance.__class__ == self) or isinstance(instance, Negotiation)
class FoolscapOrHttp(Protocol, metaclass=PretendToBeNegotiation):
"""
Based on initial query, decide whether we're talking Foolscap or HTTP.
Pretends to be a ``foolscap.negotiate.Negotiation`` instance.
"""
2022-06-22 10:23:23 -04:00
2022-06-23 12:43:46 -04:00
# These three will be set by a subclass in update_foolscap_or_http_class()
# below.
2022-06-23 07:59:43 -04:00
swissnum: bytes
2022-06-23 12:41:47 -04:00
certificate: PrivateCertificate
2022-06-23 07:59:43 -04:00
storage_server: StorageServer
2022-06-23 12:47:33 -04:00
_timeout: IDelayedCall
def __init__(self, *args, **kwargs):
2022-06-23 12:41:01 -04:00
self._foolscap: Negotiation = Negotiation(*args, **kwargs)
self._buffer: bytes = b""
def __setattr__(self, name, value):
2022-06-22 10:23:23 -04:00
if name in {
"_foolscap",
"_buffer",
"transport",
"__class__",
}:
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
"""
self.__class__ = Negotiation # type: ignore
self.__dict__ = self._foolscap.__dict__
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.
assert not self._buffer
self._convert_to_negotiation()
2022-06-22 10:23:23 -04:00
return self.initClient(*args, **kwargs)
2022-06-23 12:47:33 -04:00
def connectionMade(self):
self._timeout = reactor.callLater(30, self.transport.abortConnection)
def dataReceived(self, data: bytes) -> None:
2022-06-23 12:41:01 -04:00
"""Handle incoming data.
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.
"""
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()
if self._buffer.startswith(b"GET /id/"):
2022-06-23 12:43:46 -04:00
transport = self.transport
buf = self._buffer
self._convert_to_negotiation()
self.makeConnection(transport)
self.dataReceived(buf)
return
else:
certificate_options = CertificateOptions(
privateKey=self.certificate.privateKey.original,
certificate=self.certificate.original,
)
http_server = HTTPServer(self.storage_server, self.swissnum)
factory = TLSMemoryBIOFactory(
certificate_options, False, Site(http_server.get_resource())
)
2022-06-23 12:41:47 -04:00
assert self.transport is not None
protocol = factory.buildProtocol(self.transport.getPeer())
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-23 07:59:43 -04:00
def create_foolscap_or_http_class():
class FoolscapOrHttpWithCert(FoolscapOrHttp):
pass
return FoolscapOrHttpWithCert
def update_foolscap_or_http_class(cls, certificate, storage_server, swissnum):
cls.certificate = certificate
cls.storage_server = storage_server
cls.swissnum = swissnum