forked from MirrorHub/synapse
Experimental Unix socket support (#15353)
* Add IReactorUNIX to ISynapseReactor type hint. * Create listen_unix(). Two options, 'path' to the file and 'mode' of permissions(not umask, recommend 666 as default as nginx/other reverse proxies write to it and it's setup as user www-data) For the moment, leave the option to always create a PID lockfile turned on by default * Create UnixListenerConfig and wire it up. Rename ListenerConfig to TCPListenerConfig, then Union them together into ListenerConfig. This spidered around a bit, but I think I got it all. Metrics and manhole have been placed behind a conditional in case of accidental putting them onto a unix socket. Use new helpers to get if a listener is configured for TLS, and to help create a site tag for logging. There are 2 TODO things in parse_listener_def() to finish up at a later point. * Refactor SynapseRequest to handle logging correctly when using a unix socket. This prevents an exception when an IP address can not be retrieved for a request. * Make the 'Synapse now listening on Unix socket' log line a little prettier. * No silent failures on generic workers when trying to use a unix socket with metrics or manhole. * Inline variables in app/_base.py * Update docstring for listen_unix() to remove reference to a hardcoded permission of 0o666 and add a few comments saying where the default IS declared. * Disallow both a unix socket and a ip/port combo on the same listener resource * Linting * Changelog * review: simplify how listen_unix returns(and get rid of a type: ignore) * review: fix typo from ConfigError in app/homeserver.py * review: roll conditional for http_options.tag into get_site_tag() helper(and add docstring) * review: enhance the conditionals for checking if a port or path is valid, remove a TODO line * review: Try updating comment in get_client_ip_if_available to clarify what is being retrieved and why * Pretty up how 'Synapse now listening on Unix Socket' looks by decoding the byte string. * review: In parse_listener_def(), raise ConfigError if neither socket_path nor port is declared(and fix a typo)
This commit is contained in:
parent
9b2ab506c5
commit
56efa9b167
8 changed files with 239 additions and 90 deletions
1
changelog.d/15353.misc
Normal file
1
changelog.d/15353.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Add experimental support for Unix sockets. Contributed by Jason Little.
|
|
@ -41,7 +41,12 @@ from typing_extensions import ParamSpec
|
||||||
|
|
||||||
import twisted
|
import twisted
|
||||||
from twisted.internet import defer, error, reactor as _reactor
|
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.protocol import ServerFactory
|
||||||
from twisted.internet.tcp import Port
|
from twisted.internet.tcp import Port
|
||||||
from twisted.logger import LoggingFile, LogLevel
|
from twisted.logger import LoggingFile, LogLevel
|
||||||
|
@ -56,7 +61,7 @@ from synapse.app.phone_stats_home import start_phone_stats_home
|
||||||
from synapse.config import ConfigError
|
from synapse.config import ConfigError
|
||||||
from synapse.config._base import format_config_error
|
from synapse.config._base import format_config_error
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
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.crypto import context_factory
|
||||||
from synapse.events.presence_router import load_legacy_presence_router
|
from synapse.events.presence_router import load_legacy_presence_router
|
||||||
from synapse.events.spamcheck import load_legacy_spam_checkers
|
from synapse.events.spamcheck import load_legacy_spam_checkers
|
||||||
|
@ -351,6 +356,28 @@ def listen_tcp(
|
||||||
return r # type: ignore[return-value]
|
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(
|
def listen_http(
|
||||||
listener_config: ListenerConfig,
|
listener_config: ListenerConfig,
|
||||||
root_resource: Resource,
|
root_resource: Resource,
|
||||||
|
@ -359,18 +386,13 @@ def listen_http(
|
||||||
context_factory: Optional[IOpenSSLContextFactory],
|
context_factory: Optional[IOpenSSLContextFactory],
|
||||||
reactor: ISynapseReactor = reactor,
|
reactor: ISynapseReactor = reactor,
|
||||||
) -> List[Port]:
|
) -> List[Port]:
|
||||||
port = listener_config.port
|
|
||||||
bind_addresses = listener_config.bind_addresses
|
|
||||||
tls = listener_config.tls
|
|
||||||
|
|
||||||
assert listener_config.http_options is not None
|
assert listener_config.http_options is not None
|
||||||
|
|
||||||
site_tag = listener_config.http_options.tag
|
site_tag = listener_config.get_site_tag()
|
||||||
if site_tag is None:
|
|
||||||
site_tag = str(port)
|
|
||||||
|
|
||||||
site = SynapseSite(
|
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,
|
site_tag,
|
||||||
listener_config,
|
listener_config,
|
||||||
root_resource,
|
root_resource,
|
||||||
|
@ -378,25 +400,41 @@ def listen_http(
|
||||||
max_request_body_size=max_request_body_size,
|
max_request_body_size=max_request_body_size,
|
||||||
reactor=reactor,
|
reactor=reactor,
|
||||||
)
|
)
|
||||||
if tls:
|
|
||||||
# refresh_certificate should have been called before this.
|
if isinstance(listener_config, TCPListenerConfig):
|
||||||
assert context_factory is not None
|
if listener_config.is_tls():
|
||||||
ports = listen_ssl(
|
# refresh_certificate should have been called before this.
|
||||||
bind_addresses,
|
assert context_factory is not None
|
||||||
port,
|
ports = listen_ssl(
|
||||||
site,
|
listener_config.bind_addresses,
|
||||||
context_factory,
|
listener_config.port,
|
||||||
reactor=reactor,
|
site,
|
||||||
)
|
context_factory,
|
||||||
logger.info("Synapse now listening on TCP port %d (TLS)", port)
|
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:
|
else:
|
||||||
ports = listen_tcp(
|
ports = listen_unix(
|
||||||
bind_addresses,
|
listener_config.path, listener_config.mode, site, reactor=reactor
|
||||||
port,
|
|
||||||
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
|
return ports
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ from synapse.app._base import (
|
||||||
from synapse.config._base import ConfigError
|
from synapse.config._base import ConfigError
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
from synapse.config.homeserver import HomeServerConfig
|
||||||
from synapse.config.logger import setup_logging
|
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.federation.transport.server import TransportLayerServer
|
||||||
from synapse.http.server import JsonResource, OptionsResource
|
from synapse.http.server import JsonResource, OptionsResource
|
||||||
from synapse.logging.context import LoggingContext
|
from synapse.logging.context import LoggingContext
|
||||||
|
@ -236,12 +236,18 @@ class GenericWorkerServer(HomeServer):
|
||||||
if listener.type == "http":
|
if listener.type == "http":
|
||||||
self._listen_http(listener)
|
self._listen_http(listener)
|
||||||
elif listener.type == "manhole":
|
elif listener.type == "manhole":
|
||||||
_base.listen_manhole(
|
if isinstance(listener, TCPListenerConfig):
|
||||||
listener.bind_addresses,
|
_base.listen_manhole(
|
||||||
listener.port,
|
listener.bind_addresses,
|
||||||
manhole_settings=self.config.server.manhole_settings,
|
listener.port,
|
||||||
manhole_globals={"hs": self},
|
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":
|
elif listener.type == "metrics":
|
||||||
if not self.config.metrics.enable_metrics:
|
if not self.config.metrics.enable_metrics:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -249,10 +255,16 @@ class GenericWorkerServer(HomeServer):
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(
|
if isinstance(listener, TCPListenerConfig):
|
||||||
listener.bind_addresses,
|
_base.listen_metrics(
|
||||||
listener.port,
|
listener.bind_addresses,
|
||||||
)
|
listener.port,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ConfigError(
|
||||||
|
"Can not use a unix socket for metrics at this time."
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warning("Unsupported listener type: %s", listener.type)
|
logger.warning("Unsupported listener type: %s", listener.type)
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ from synapse.app._base import (
|
||||||
)
|
)
|
||||||
from synapse.config._base import ConfigError, format_config_error
|
from synapse.config._base import ConfigError, format_config_error
|
||||||
from synapse.config.homeserver import HomeServerConfig
|
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.federation.transport.server import TransportLayerServer
|
||||||
from synapse.http.additional_resource import AdditionalResource
|
from synapse.http.additional_resource import AdditionalResource
|
||||||
from synapse.http.server import (
|
from synapse.http.server import (
|
||||||
|
@ -78,14 +78,13 @@ class SynapseHomeServer(HomeServer):
|
||||||
DATASTORE_CLASS = DataStore # type: ignore
|
DATASTORE_CLASS = DataStore # type: ignore
|
||||||
|
|
||||||
def _listener_http(
|
def _listener_http(
|
||||||
self, config: HomeServerConfig, listener_config: ListenerConfig
|
self,
|
||||||
|
config: HomeServerConfig,
|
||||||
|
listener_config: ListenerConfig,
|
||||||
) -> Iterable[Port]:
|
) -> Iterable[Port]:
|
||||||
port = listener_config.port
|
|
||||||
# Must exist since this is an HTTP listener.
|
# Must exist since this is an HTTP listener.
|
||||||
assert listener_config.http_options is not None
|
assert listener_config.http_options is not None
|
||||||
site_tag = listener_config.http_options.tag
|
site_tag = listener_config.get_site_tag()
|
||||||
if site_tag is None:
|
|
||||||
site_tag = str(port)
|
|
||||||
|
|
||||||
# We always include a health resource.
|
# We always include a health resource.
|
||||||
resources: Dict[str, Resource] = {"/health": HealthResource()}
|
resources: Dict[str, Resource] = {"/health": HealthResource()}
|
||||||
|
@ -252,12 +251,17 @@ class SynapseHomeServer(HomeServer):
|
||||||
self._listener_http(self.config, listener)
|
self._listener_http(self.config, listener)
|
||||||
)
|
)
|
||||||
elif listener.type == "manhole":
|
elif listener.type == "manhole":
|
||||||
_base.listen_manhole(
|
if isinstance(listener, TCPListenerConfig):
|
||||||
listener.bind_addresses,
|
_base.listen_manhole(
|
||||||
listener.port,
|
listener.bind_addresses,
|
||||||
manhole_settings=self.config.server.manhole_settings,
|
listener.port,
|
||||||
manhole_globals={"hs": self},
|
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":
|
elif listener.type == "metrics":
|
||||||
if not self.config.metrics.enable_metrics:
|
if not self.config.metrics.enable_metrics:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
@ -265,10 +269,16 @@ class SynapseHomeServer(HomeServer):
|
||||||
"enable_metrics is not True!"
|
"enable_metrics is not True!"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_base.listen_metrics(
|
if isinstance(listener, TCPListenerConfig):
|
||||||
listener.bind_addresses,
|
_base.listen_metrics(
|
||||||
listener.port,
|
listener.bind_addresses,
|
||||||
)
|
listener.port,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ConfigError(
|
||||||
|
"Can not use a unix socket for metrics at this time."
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# this shouldn't happen, as the listener type should have been checked
|
# this shouldn't happen, as the listener type should have been checked
|
||||||
# during parsing
|
# during parsing
|
||||||
|
|
|
@ -214,17 +214,52 @@ class HttpListenerConfig:
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
class ListenerConfig:
|
class TCPListenerConfig:
|
||||||
"""Object describing the configuration of a single listener."""
|
"""Object describing the configuration of a single TCP listener."""
|
||||||
|
|
||||||
port: int = attr.ib(validator=attr.validators.instance_of(int))
|
port: int = attr.ib(validator=attr.validators.instance_of(int))
|
||||||
bind_addresses: List[str]
|
bind_addresses: List[str] = attr.ib(validator=attr.validators.instance_of(List))
|
||||||
type: str = attr.ib(validator=attr.validators.in_(KNOWN_LISTENER_TYPES))
|
type: str = attr.ib(validator=attr.validators.in_(KNOWN_LISTENER_TYPES))
|
||||||
tls: bool = False
|
tls: bool = False
|
||||||
|
|
||||||
# http_options is only populated if type=http
|
# http_options is only populated if type=http
|
||||||
http_options: Optional[HttpListenerConfig] = None
|
http_options: Optional[HttpListenerConfig] = None
|
||||||
|
|
||||||
|
def get_site_tag(self) -> str:
|
||||||
|
"""Retrieves http_options.tag if it exists, otherwise the port number."""
|
||||||
|
if self.http_options and self.http_options.tag is not None:
|
||||||
|
return self.http_options.tag
|
||||||
|
else:
|
||||||
|
return str(self.port)
|
||||||
|
|
||||||
|
def is_tls(self) -> bool:
|
||||||
|
return self.tls
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
|
class UnixListenerConfig:
|
||||||
|
"""Object describing the configuration of a single Unix socket listener."""
|
||||||
|
|
||||||
|
# Note: unix sockets can not be tls encrypted, so HAVE to be behind a tls-handling
|
||||||
|
# reverse proxy
|
||||||
|
path: str = attr.ib()
|
||||||
|
# A default(0o666) for this is set in parse_listener_def() below
|
||||||
|
mode: int
|
||||||
|
type: str = attr.ib(validator=attr.validators.in_(KNOWN_LISTENER_TYPES))
|
||||||
|
|
||||||
|
# http_options is only populated if type=http
|
||||||
|
http_options: Optional[HttpListenerConfig] = None
|
||||||
|
|
||||||
|
def get_site_tag(self) -> str:
|
||||||
|
return "unix"
|
||||||
|
|
||||||
|
def is_tls(self) -> bool:
|
||||||
|
"""Unix sockets can't have TLS"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
ListenerConfig = Union[TCPListenerConfig, UnixListenerConfig]
|
||||||
|
|
||||||
|
|
||||||
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
@attr.s(slots=True, frozen=True, auto_attribs=True)
|
||||||
class ManholeConfig:
|
class ManholeConfig:
|
||||||
|
@ -531,12 +566,12 @@ class ServerConfig(Config):
|
||||||
|
|
||||||
self.listeners = [parse_listener_def(i, x) for i, x in enumerate(listeners)]
|
self.listeners = [parse_listener_def(i, x) for i, x in enumerate(listeners)]
|
||||||
|
|
||||||
# no_tls is not really supported any more, but let's grandfather it in
|
# no_tls is not really supported anymore, but let's grandfather it in here.
|
||||||
# here.
|
|
||||||
if config.get("no_tls", False):
|
if config.get("no_tls", False):
|
||||||
l2 = []
|
l2 = []
|
||||||
for listener in self.listeners:
|
for listener in self.listeners:
|
||||||
if listener.tls:
|
if isinstance(listener, TCPListenerConfig) and listener.tls:
|
||||||
|
# Use isinstance() as the assertion this *has* a listener.port
|
||||||
logger.info(
|
logger.info(
|
||||||
"Ignoring TLS-enabled listener on port %i due to no_tls",
|
"Ignoring TLS-enabled listener on port %i due to no_tls",
|
||||||
listener.port,
|
listener.port,
|
||||||
|
@ -577,7 +612,7 @@ class ServerConfig(Config):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.listeners.append(
|
self.listeners.append(
|
||||||
ListenerConfig(
|
TCPListenerConfig(
|
||||||
port=bind_port,
|
port=bind_port,
|
||||||
bind_addresses=[bind_host],
|
bind_addresses=[bind_host],
|
||||||
tls=True,
|
tls=True,
|
||||||
|
@ -589,7 +624,7 @@ class ServerConfig(Config):
|
||||||
unsecure_port = config.get("unsecure_port", bind_port - 400)
|
unsecure_port = config.get("unsecure_port", bind_port - 400)
|
||||||
if unsecure_port:
|
if unsecure_port:
|
||||||
self.listeners.append(
|
self.listeners.append(
|
||||||
ListenerConfig(
|
TCPListenerConfig(
|
||||||
port=unsecure_port,
|
port=unsecure_port,
|
||||||
bind_addresses=[bind_host],
|
bind_addresses=[bind_host],
|
||||||
tls=False,
|
tls=False,
|
||||||
|
@ -601,7 +636,7 @@ class ServerConfig(Config):
|
||||||
manhole = config.get("manhole")
|
manhole = config.get("manhole")
|
||||||
if manhole:
|
if manhole:
|
||||||
self.listeners.append(
|
self.listeners.append(
|
||||||
ListenerConfig(
|
TCPListenerConfig(
|
||||||
port=manhole,
|
port=manhole,
|
||||||
bind_addresses=["127.0.0.1"],
|
bind_addresses=["127.0.0.1"],
|
||||||
type="manhole",
|
type="manhole",
|
||||||
|
@ -648,7 +683,7 @@ class ServerConfig(Config):
|
||||||
logger.warning(METRICS_PORT_WARNING)
|
logger.warning(METRICS_PORT_WARNING)
|
||||||
|
|
||||||
self.listeners.append(
|
self.listeners.append(
|
||||||
ListenerConfig(
|
TCPListenerConfig(
|
||||||
port=metrics_port,
|
port=metrics_port,
|
||||||
bind_addresses=[config.get("metrics_bind_host", "127.0.0.1")],
|
bind_addresses=[config.get("metrics_bind_host", "127.0.0.1")],
|
||||||
type="http",
|
type="http",
|
||||||
|
@ -724,7 +759,7 @@ class ServerConfig(Config):
|
||||||
self.delete_stale_devices_after = None
|
self.delete_stale_devices_after = None
|
||||||
|
|
||||||
def has_tls_listener(self) -> bool:
|
def has_tls_listener(self) -> bool:
|
||||||
return any(listener.tls for listener in self.listeners)
|
return any(listener.is_tls() for listener in self.listeners)
|
||||||
|
|
||||||
def generate_config_section(
|
def generate_config_section(
|
||||||
self,
|
self,
|
||||||
|
@ -904,25 +939,25 @@ def parse_listener_def(num: int, listener: Any) -> ListenerConfig:
|
||||||
raise ConfigError(DIRECT_TCP_ERROR, ("listeners", str(num), "type"))
|
raise ConfigError(DIRECT_TCP_ERROR, ("listeners", str(num), "type"))
|
||||||
|
|
||||||
port = listener.get("port")
|
port = listener.get("port")
|
||||||
if type(port) is not int:
|
socket_path = listener.get("path")
|
||||||
|
# Either a port or a path should be declared at a minimum. Using both would be bad.
|
||||||
|
if port is not None and not isinstance(port, int):
|
||||||
raise ConfigError("Listener configuration is lacking a valid 'port' option")
|
raise ConfigError("Listener configuration is lacking a valid 'port' option")
|
||||||
|
if socket_path is not None and not isinstance(socket_path, str):
|
||||||
|
raise ConfigError("Listener configuration is lacking a valid 'path' option")
|
||||||
|
if port and socket_path:
|
||||||
|
raise ConfigError(
|
||||||
|
"Can not have both a UNIX socket and an IP/port declared for the same "
|
||||||
|
"resource!"
|
||||||
|
)
|
||||||
|
if port is None and socket_path is None:
|
||||||
|
raise ConfigError(
|
||||||
|
"Must have either a UNIX socket or an IP/port declared for a given "
|
||||||
|
"resource!"
|
||||||
|
)
|
||||||
|
|
||||||
tls = listener.get("tls", False)
|
tls = listener.get("tls", False)
|
||||||
|
|
||||||
bind_addresses = listener.get("bind_addresses", [])
|
|
||||||
bind_address = listener.get("bind_address")
|
|
||||||
# if bind_address was specified, add it to the list of addresses
|
|
||||||
if bind_address:
|
|
||||||
bind_addresses.append(bind_address)
|
|
||||||
|
|
||||||
# if we still have an empty list of addresses, use the default list
|
|
||||||
if not bind_addresses:
|
|
||||||
if listener_type == "metrics":
|
|
||||||
# the metrics listener doesn't support IPv6
|
|
||||||
bind_addresses.append("0.0.0.0")
|
|
||||||
else:
|
|
||||||
bind_addresses.extend(DEFAULT_BIND_ADDRESSES)
|
|
||||||
|
|
||||||
http_config = None
|
http_config = None
|
||||||
if listener_type == "http":
|
if listener_type == "http":
|
||||||
try:
|
try:
|
||||||
|
@ -932,8 +967,12 @@ def parse_listener_def(num: int, listener: Any) -> ListenerConfig:
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise ConfigError("Unknown listener resource") from e
|
raise ConfigError("Unknown listener resource") from e
|
||||||
|
|
||||||
|
# For a unix socket, default x_forwarded to True, as this is the only way of
|
||||||
|
# getting a client IP.
|
||||||
|
# Note: a reverse proxy is required anyway, as there is no way of exposing a
|
||||||
|
# unix socket to the internet.
|
||||||
http_config = HttpListenerConfig(
|
http_config = HttpListenerConfig(
|
||||||
x_forwarded=listener.get("x_forwarded", False),
|
x_forwarded=listener.get("x_forwarded", (True if socket_path else False)),
|
||||||
resources=resources,
|
resources=resources,
|
||||||
additional_resources=listener.get("additional_resources", {}),
|
additional_resources=listener.get("additional_resources", {}),
|
||||||
tag=listener.get("tag"),
|
tag=listener.get("tag"),
|
||||||
|
@ -941,7 +980,30 @@ def parse_listener_def(num: int, listener: Any) -> ListenerConfig:
|
||||||
experimental_cors_msc3886=listener.get("experimental_cors_msc3886", False),
|
experimental_cors_msc3886=listener.get("experimental_cors_msc3886", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
return ListenerConfig(port, bind_addresses, listener_type, tls, http_config)
|
if socket_path:
|
||||||
|
# TODO: Add in path validation, like if the directory exists and is writable?
|
||||||
|
# Set a default for the permission, in case it's left out
|
||||||
|
socket_mode = listener.get("mode", 0o666)
|
||||||
|
|
||||||
|
return UnixListenerConfig(socket_path, socket_mode, listener_type, http_config)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert port is not None
|
||||||
|
bind_addresses = listener.get("bind_addresses", [])
|
||||||
|
bind_address = listener.get("bind_address")
|
||||||
|
# if bind_address was specified, add it to the list of addresses
|
||||||
|
if bind_address:
|
||||||
|
bind_addresses.append(bind_address)
|
||||||
|
|
||||||
|
# if we still have an empty list of addresses, use the default list
|
||||||
|
if not bind_addresses:
|
||||||
|
if listener_type == "metrics":
|
||||||
|
# the metrics listener doesn't support IPv6
|
||||||
|
bind_addresses.append("0.0.0.0")
|
||||||
|
else:
|
||||||
|
bind_addresses.extend(DEFAULT_BIND_ADDRESSES)
|
||||||
|
|
||||||
|
return TCPListenerConfig(port, bind_addresses, listener_type, tls, http_config)
|
||||||
|
|
||||||
|
|
||||||
_MANHOLE_SETTINGS_SCHEMA = {
|
_MANHOLE_SETTINGS_SCHEMA = {
|
||||||
|
|
|
@ -19,15 +19,18 @@ from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
from synapse.types import JsonDict
|
from synapse.config._base import (
|
||||||
|
|
||||||
from ._base import (
|
|
||||||
Config,
|
Config,
|
||||||
ConfigError,
|
ConfigError,
|
||||||
RoutableShardedWorkerHandlingConfig,
|
RoutableShardedWorkerHandlingConfig,
|
||||||
ShardedWorkerHandlingConfig,
|
ShardedWorkerHandlingConfig,
|
||||||
)
|
)
|
||||||
from .server import DIRECT_TCP_ERROR, ListenerConfig, parse_listener_def
|
from synapse.config.server import (
|
||||||
|
DIRECT_TCP_ERROR,
|
||||||
|
TCPListenerConfig,
|
||||||
|
parse_listener_def,
|
||||||
|
)
|
||||||
|
from synapse.types import JsonDict
|
||||||
|
|
||||||
_DEPRECATED_WORKER_DUTY_OPTION_USED = """
|
_DEPRECATED_WORKER_DUTY_OPTION_USED = """
|
||||||
The '%s' configuration option is deprecated and will be removed in a future
|
The '%s' configuration option is deprecated and will be removed in a future
|
||||||
|
@ -161,7 +164,7 @@ class WorkerConfig(Config):
|
||||||
manhole = config.get("worker_manhole")
|
manhole = config.get("worker_manhole")
|
||||||
if manhole:
|
if manhole:
|
||||||
self.worker_listeners.append(
|
self.worker_listeners.append(
|
||||||
ListenerConfig(
|
TCPListenerConfig(
|
||||||
port=manhole,
|
port=manhole,
|
||||||
bind_addresses=["127.0.0.1"],
|
bind_addresses=["127.0.0.1"],
|
||||||
type="manhole",
|
type="manhole",
|
||||||
|
|
|
@ -19,6 +19,7 @@ from typing import TYPE_CHECKING, Any, Generator, Optional, Tuple, Union
|
||||||
import attr
|
import attr
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer
|
||||||
|
|
||||||
|
from twisted.internet.address import UNIXAddress
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred
|
||||||
from twisted.internet.interfaces import IAddress, IReactorTime
|
from twisted.internet.interfaces import IAddress, IReactorTime
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
@ -257,7 +258,7 @@ class SynapseRequest(Request):
|
||||||
request_id,
|
request_id,
|
||||||
request=ContextRequest(
|
request=ContextRequest(
|
||||||
request_id=request_id,
|
request_id=request_id,
|
||||||
ip_address=self.getClientAddress().host,
|
ip_address=self.get_client_ip_if_available(),
|
||||||
site_tag=self.synapse_site.site_tag,
|
site_tag=self.synapse_site.site_tag,
|
||||||
# The requester is going to be unknown at this point.
|
# The requester is going to be unknown at this point.
|
||||||
requester=None,
|
requester=None,
|
||||||
|
@ -414,7 +415,7 @@ class SynapseRequest(Request):
|
||||||
|
|
||||||
self.synapse_site.access_logger.debug(
|
self.synapse_site.access_logger.debug(
|
||||||
"%s - %s - Received request: %s %s",
|
"%s - %s - Received request: %s %s",
|
||||||
self.getClientAddress().host,
|
self.get_client_ip_if_available(),
|
||||||
self.synapse_site.site_tag,
|
self.synapse_site.site_tag,
|
||||||
self.get_method(),
|
self.get_method(),
|
||||||
self.get_redacted_uri(),
|
self.get_redacted_uri(),
|
||||||
|
@ -462,7 +463,7 @@ class SynapseRequest(Request):
|
||||||
"%s - %s - {%s}"
|
"%s - %s - {%s}"
|
||||||
" Processed request: %.3fsec/%.3fsec (%.3fsec, %.3fsec) (%.3fsec/%.3fsec/%d)"
|
" Processed request: %.3fsec/%.3fsec (%.3fsec, %.3fsec) (%.3fsec/%.3fsec/%d)"
|
||||||
' %sB %s "%s %s %s" "%s" [%d dbevts]',
|
' %sB %s "%s %s %s" "%s" [%d dbevts]',
|
||||||
self.getClientAddress().host,
|
self.get_client_ip_if_available(),
|
||||||
self.synapse_site.site_tag,
|
self.synapse_site.site_tag,
|
||||||
requester,
|
requester,
|
||||||
processing_time,
|
processing_time,
|
||||||
|
@ -500,6 +501,26 @@ class SynapseRequest(Request):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_client_ip_if_available(self) -> str:
|
||||||
|
"""Logging helper. Return something useful when a client IP is not retrievable
|
||||||
|
from a unix socket.
|
||||||
|
|
||||||
|
In practice, this returns the socket file path on a SynapseRequest if using a
|
||||||
|
unix socket and the normal IP address for TCP sockets.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# getClientAddress().host returns a proper IP address for a TCP socket. But
|
||||||
|
# unix sockets have no concept of IP addresses or ports and return a
|
||||||
|
# UNIXAddress containing a 'None' value. In order to get something usable for
|
||||||
|
# logs(where this is used) get the unix socket file. getHost() returns a
|
||||||
|
# UNIXAddress containing a value of the socket file and has an instance
|
||||||
|
# variable of 'name' encoded as a byte string containing the path we want.
|
||||||
|
# Decode to utf-8 so it looks nice.
|
||||||
|
if isinstance(self.getClientAddress(), UNIXAddress):
|
||||||
|
return self.getHost().name.decode("utf-8")
|
||||||
|
else:
|
||||||
|
return self.getClientAddress().host
|
||||||
|
|
||||||
|
|
||||||
class XForwardedForRequest(SynapseRequest):
|
class XForwardedForRequest(SynapseRequest):
|
||||||
"""Request object which honours proxy headers
|
"""Request object which honours proxy headers
|
||||||
|
|
|
@ -50,6 +50,7 @@ from twisted.internet.interfaces import (
|
||||||
IReactorTCP,
|
IReactorTCP,
|
||||||
IReactorThreads,
|
IReactorThreads,
|
||||||
IReactorTime,
|
IReactorTime,
|
||||||
|
IReactorUNIX,
|
||||||
)
|
)
|
||||||
|
|
||||||
from synapse.api.errors import Codes, SynapseError
|
from synapse.api.errors import Codes, SynapseError
|
||||||
|
@ -91,6 +92,7 @@ StrCollection = Union[Tuple[str, ...], List[str], AbstractSet[str]]
|
||||||
class ISynapseReactor(
|
class ISynapseReactor(
|
||||||
IReactorTCP,
|
IReactorTCP,
|
||||||
IReactorSSL,
|
IReactorSSL,
|
||||||
|
IReactorUNIX,
|
||||||
IReactorPluggableNameResolver,
|
IReactorPluggableNameResolver,
|
||||||
IReactorTime,
|
IReactorTime,
|
||||||
IReactorCore,
|
IReactorCore,
|
||||||
|
|
Loading…
Reference in a new issue