Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 28 additions & 1 deletion synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,25 @@ def listen_tcp(
return r # type: ignore[return-value]


def listen_fd(
fd: int,
factory: ServerFactory,
reactor: IReactorTCP = reactor,
backlog: int = 50,
) -> List[Port]:
"""
Use an inherited file descriptor as a listening socket.

Returns:
list of twisted.internet.tcp.Port listening for TCP connections
"""
sock = socket.socket(fileno=fd)
sock.setblocking(False)
port = Port._fromListeningDescriptor(reactor, fd, sock.family, factory)
port.startListening()
return [port]


def listen_unix(
path: str,
mode: int,
Expand Down Expand Up @@ -416,7 +435,15 @@ def listen_http(
)

if isinstance(listener_config, TCPListenerConfig):
if listener_config.is_tls():
if listener_config.fd is not None:
# Use inherited socket
ports = listen_fd(
listener_config.fd,
site,
reactor=reactor,
)
logger.info("Synapse now listening on inherited fd %d", listener_config.fd)
elif listener_config.is_tls():
# refresh_certificate should have been called before this.
assert context_factory is not None
ports = listen_ssl(
Expand Down
24 changes: 16 additions & 8 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,14 @@ class TCPListenerConfig:

# http_options is only populated if type=http
http_options: Optional[HttpListenerConfig] = None
fd: Optional[int] = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(int)))

def get_site_tag(self) -> str:
"""Retrieves http_options.tag if it exists, otherwise the port number."""
"""Retrieves http_options.tag if it exists, otherwise the port number or fd."""
if self.http_options and self.http_options.tag is not None:
return self.http_options.tag
elif self.fd is not None:
return f"fd{self.fd}"
else:
return str(self.port)

Expand Down Expand Up @@ -970,20 +973,21 @@ def parse_listener_def(num: int, listener: Any) -> ListenerConfig:

port = listener.get("port")
socket_path = listener.get("path")
# Either a port or a path should be declared at a minimum. Using both would be bad.
fd = listener.get("fd")
# Either a port, fd, or a path should be declared at a minimum. Using more than one would be bad.
if port is not None and not isinstance(port, int):
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:
if fd is not None and not isinstance(fd, int):
raise ConfigError("Listener configuration is lacking a valid 'fd' option")
if sum(x is not None for x in (port, socket_path, fd)) > 1:
raise ConfigError(
"Can not have both a UNIX socket and an IP/port declared for the same "
"resource!"
"Can not have more than one of UNIX socket, IP/port, or fd declared for the same resource!"
)
if port is None and socket_path is None:
if port is None and socket_path is None and fd is None:
raise ConfigError(
"Must have either a UNIX socket or an IP/port declared for a given "
"resource!"
"Must have either a UNIX socket, an IP/port, or an fd declared for a given resource!"
)

tls = listener.get("tls", False)
Expand Down Expand Up @@ -1016,6 +1020,10 @@ def parse_listener_def(num: int, listener: Any) -> ListenerConfig:

return UnixListenerConfig(socket_path, socket_mode, listener_type, http_config)

if fd is not None:
assert not tls
return TCPListenerConfig(0, [], listener_type, False, http_config, fd=fd)

else:
assert port is not None
bind_addresses = listener.get("bind_addresses", [])
Expand Down
Loading