Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Experimental Unix socket support #15353

Merged
merged 20 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
906acaf
Add IReactorUNIX to ISynapseReactor type hint.
realtyem Mar 27, 2023
aac7fac
Create listen_unix().
realtyem Mar 28, 2023
9abd099
Create UnixListenerConfig and wire it up.
realtyem Mar 28, 2023
43a6ee9
Refactor SynapseRequest to handle logging correctly when using a unix…
realtyem Mar 29, 2023
7b93a29
Make the 'Synapse now listening on Unix socket' log line a little pre…
realtyem Mar 29, 2023
3267581
No silent failures on generic workers when trying to use a unix socke…
realtyem Mar 29, 2023
1e0452f
Inline variables in app/_base.py
realtyem Mar 29, 2023
c0eb5b3
Update docstring for listen_unix() to remove reference to a hardcoded…
realtyem Mar 29, 2023
9fdbb5d
Disallow both a unix socket and a ip/port combo on the same listener …
realtyem Mar 29, 2023
67eecf0
Linting
realtyem Mar 29, 2023
c74ad1d
Changelog
realtyem Mar 29, 2023
2309543
Merge branch 'develop' into exp/unix-reactor
realtyem Mar 29, 2023
3703cd0
review: simplify how listen_unix returns(and get rid of a type: ignore)
realtyem Mar 30, 2023
8876780
review: fix typo from ConfigError in app/homeserver.py
realtyem Mar 30, 2023
d6b88f3
review: roll conditional for http_options.tag into get_site_tag() hel…
realtyem Mar 30, 2023
2acf0a6
review: enhance the conditionals for checking if a port or path is va…
realtyem Mar 30, 2023
2a99e49
review: Try updating comment in get_client_ip_if_available to clarify…
realtyem Mar 31, 2023
547ff5f
Pretty up how 'Synapse now listening on Unix Socket' looks by decodin…
realtyem Mar 31, 2023
d1742a4
review: In parse_listener_def(), raise ConfigError if neither socket_…
realtyem Apr 1, 2023
cfdc5ac
Merge branch 'develop' into exp/unix-reactor
realtyem Apr 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/15353.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add experimental support for Unix sockets. Contributed by Jason Little.
92 changes: 65 additions & 27 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@

import twisted
from twisted.internet import defer, error, reactor as _reactor
from twisted.internet.interfaces import IOpenSSLContextFactory, IReactorSSL, IReactorTCP
from twisted.internet.interfaces import (
IOpenSSLContextFactory,
IReactorSSL,
IReactorTCP,
IReactorUNIX,
)
from twisted.internet.protocol import ServerFactory
from twisted.internet.tcp import Port
from twisted.logger import LoggingFile, LogLevel
Expand All @@ -56,7 +61,7 @@
from synapse.config import ConfigError
from synapse.config._base import format_config_error
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import ListenerConfig, ManholeConfig
from synapse.config.server import ListenerConfig, ManholeConfig, TCPListenerConfig
from synapse.crypto import context_factory
from synapse.events.presence_router import load_legacy_presence_router
from synapse.events.spamcheck import load_legacy_spam_checkers
Expand Down Expand Up @@ -351,6 +356,28 @@ def listen_tcp(
return r # type: ignore[return-value]


def listen_unix(
path: str,
mode: int,
factory: ServerFactory,
reactor: IReactorUNIX = reactor,
backlog: int = 50,
) -> List[Port]:
"""
Create a UNIX socket for a given path and 'mode' permission

Returns:
list of twisted.internet.tcp.Port listening for TCP connections
"""
wantPID = True

return [
# IReactorUNIX returns an object implementing IListeningPort from listenUNIX,
# but we know it will be a Port instance.
cast(Port, reactor.listenUNIX(path, factory, backlog, mode, wantPID))
]


def listen_http(
listener_config: ListenerConfig,
root_resource: Resource,
Expand All @@ -359,44 +386,55 @@ def listen_http(
context_factory: Optional[IOpenSSLContextFactory],
reactor: ISynapseReactor = reactor,
) -> List[Port]:
port = listener_config.port
bind_addresses = listener_config.bind_addresses
tls = listener_config.tls

assert listener_config.http_options is not None

site_tag = listener_config.http_options.tag
if site_tag is None:
site_tag = str(port)
site_tag = listener_config.get_site_tag()

site = SynapseSite(
"synapse.access.%s.%s" % ("https" if tls else "http", site_tag),
"synapse.access.%s.%s"
% ("https" if listener_config.is_tls() else "http", site_tag),
site_tag,
listener_config,
root_resource,
version_string,
max_request_body_size=max_request_body_size,
reactor=reactor,
)
if tls:
# refresh_certificate should have been called before this.
assert context_factory is not None
ports = listen_ssl(
bind_addresses,
port,
site,
context_factory,
reactor=reactor,
)
logger.info("Synapse now listening on TCP port %d (TLS)", port)

if isinstance(listener_config, TCPListenerConfig):
if listener_config.is_tls():
# refresh_certificate should have been called before this.
assert context_factory is not None
ports = listen_ssl(
listener_config.bind_addresses,
listener_config.port,
site,
context_factory,
reactor=reactor,
)
logger.info(
"Synapse now listening on TCP port %d (TLS)", listener_config.port
)
else:
ports = listen_tcp(
listener_config.bind_addresses,
listener_config.port,
site,
reactor=reactor,
)
logger.info("Synapse now listening on TCP port %d", listener_config.port)

else:
ports = listen_tcp(
bind_addresses,
port,
site,
reactor=reactor,
ports = listen_unix(
listener_config.path, listener_config.mode, site, reactor=reactor
)
logger.info("Synapse now listening on TCP port %d", port)
# getHost() returns a UNIXAddress which contains an instance variable of 'name'
# encoded as a byte string. Decode as utf-8 so pretty.
logger.info(
"Synapse now listening on Unix Socket at: "
f"{ports[0].getHost().name.decode('utf-8')}"
)

return ports


Expand Down
34 changes: 23 additions & 11 deletions synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from synapse.config._base import ConfigError
from synapse.config.homeserver import HomeServerConfig
from synapse.config.logger import setup_logging
from synapse.config.server import ListenerConfig
from synapse.config.server import ListenerConfig, TCPListenerConfig
from synapse.federation.transport.server import TransportLayerServer
from synapse.http.server import JsonResource, OptionsResource
from synapse.logging.context import LoggingContext
Expand Down Expand Up @@ -236,23 +236,35 @@ def start_listening(self) -> None:
if listener.type == "http":
self._listen_http(listener)
elif listener.type == "manhole":
_base.listen_manhole(
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
if isinstance(listener, TCPListenerConfig):
_base.listen_manhole(
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
else:
raise ConfigError(
"Can not using a unix socket for manhole at this time."
)

elif listener.type == "metrics":
if not self.config.metrics.enable_metrics:
logger.warning(
"Metrics listener configured, but "
"enable_metrics is not True!"
)
else:
_base.listen_metrics(
listener.bind_addresses,
listener.port,
)
if isinstance(listener, TCPListenerConfig):
_base.listen_metrics(
listener.bind_addresses,
listener.port,
)
else:
raise ConfigError(
"Can not use a unix socket for metrics at this time."
)

else:
logger.warning("Unsupported listener type: %s", listener.type)

Expand Down
42 changes: 26 additions & 16 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
)
from synapse.config._base import ConfigError, format_config_error
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import ListenerConfig
from synapse.config.server import ListenerConfig, TCPListenerConfig
from synapse.federation.transport.server import TransportLayerServer
from synapse.http.additional_resource import AdditionalResource
from synapse.http.server import (
Expand Down Expand Up @@ -78,14 +78,13 @@ class SynapseHomeServer(HomeServer):
DATASTORE_CLASS = DataStore # type: ignore

def _listener_http(
self, config: HomeServerConfig, listener_config: ListenerConfig
self,
config: HomeServerConfig,
listener_config: ListenerConfig,
) -> Iterable[Port]:
port = listener_config.port
# Must exist since this is an HTTP listener.
assert listener_config.http_options is not None
site_tag = listener_config.http_options.tag
if site_tag is None:
site_tag = str(port)
site_tag = listener_config.get_site_tag()

# We always include a health resource.
resources: Dict[str, Resource] = {"/health": HealthResource()}
Expand Down Expand Up @@ -252,23 +251,34 @@ def start_listening(self) -> None:
self._listener_http(self.config, listener)
)
elif listener.type == "manhole":
_base.listen_manhole(
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
if isinstance(listener, TCPListenerConfig):
_base.listen_manhole(
listener.bind_addresses,
listener.port,
manhole_settings=self.config.server.manhole_settings,
manhole_globals={"hs": self},
)
else:
raise ConfigError(
"Can not use a unix socket for manhole at this time."
)
elif listener.type == "metrics":
if not self.config.metrics.enable_metrics:
logger.warning(
"Metrics listener configured, but "
"enable_metrics is not True!"
)
else:
_base.listen_metrics(
listener.bind_addresses,
listener.port,
)
if isinstance(listener, TCPListenerConfig):
_base.listen_metrics(
listener.bind_addresses,
listener.port,
)
else:
raise ConfigError(
"Can not use a unix socket for metrics at this time."
)

else:
# this shouldn't happen, as the listener type should have been checked
# during parsing
Expand Down
Loading