Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/libcmd/include/nix/cmd/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ headers = files(
'network-proxy.hh',
'repl-interacter.hh',
'repl.hh',
'unix-socket-server.hh',
)
79 changes: 79 additions & 0 deletions src/libcmd/include/nix/cmd/unix-socket-server.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#pragma once
///@file

#include "nix/util/file-descriptor.hh"

#include <filesystem>
#include <functional>
#include <optional>
#include <sys/types.h>

namespace nix::unix {

/**
* Information about the identity of the peer on a Unix domain socket connection.
*/
struct PeerInfo
{
std::optional<pid_t> pid;
std::optional<uid_t> uid;
std::optional<gid_t> gid;
};

/**
* Get the identity of the caller, if possible.
*/
PeerInfo getPeerInfo(Descriptor remote);

/**
* Callback type for handling new connections.
*
* The callback receives ownership of the connection and is responsible
* for handling it (e.g., forking a child process, spawning a thread, etc.).
*
* @param socket The accepted connection file descriptor.
* @param closeListeners A callback to close the listening sockets.
* Useful in forked child processes to release the bound sockets.
*/
using UnixSocketHandler = std::function<void(AutoCloseFD socket, std::function<void()> closeListeners)>;

/**
* Options for the serve loop.
*
* Only used if no systemd socket activation is detected.
*/
struct ServeUnixSocketOptions
{
/**
* The Unix domain socket path to create and listen on.
*/
std::filesystem::path socketPath;

/**
* Mode for the created socket file.
*/
mode_t socketMode = 0666;
};

/**
* Run a server loop that accepts connections and calls the handler for each.
*
* This function handles:
* - systemd socket activation (via LISTEN_FDS environment variable)
* - Creating and binding a Unix domain socket if no activation is detected
* - Polling for incoming connections
* - Accepting connections
*
* For each accepted connection, the handler is called with the connection
* file descriptor. The handler takes ownership of the file descriptor and
* is responsible for closing it when done.
*
* This function never returns normally. It runs until interrupted
* (e.g., via SIGINT), at which point it throws `Interrupted`.
*
* @param options Configuration for the server.
* @param handler Callback invoked for each accepted connection.
*/
[[noreturn]] void serveUnixSocket(const ServeUnixSocketOptions & options, UnixSocketHandler handler);

} // namespace nix::unix
6 changes: 6 additions & 0 deletions src/libcmd/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ sources = files(
'repl.cc',
)

if host_machine.system() != 'windows'
sources += files(
'unix/unix-socket-server.cc',
)
endif

subdir('include/nix/cmd')

subdir('nix-meson-build-support/export-all-symbols')
Expand Down
126 changes: 126 additions & 0 deletions src/libcmd/unix/unix-socket-server.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
///@file

#include "nix/cmd/unix-socket-server.hh"
#include "nix/util/environment-variables.hh"
#include "nix/util/file-system.hh"
#include "nix/util/logging.hh"
#include "nix/util/signals.hh"
#include "nix/util/strings.hh"
#include "nix/util/unix-domain-socket.hh"
#include "nix/util/util.hh"

#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>

#if defined(__APPLE__) || defined(__FreeBSD__)
# include <sys/ucred.h>
#endif

namespace nix::unix {

PeerInfo getPeerInfo(Descriptor remote)
{
PeerInfo peer;

#if defined(SO_PEERCRED)

# if defined(__OpenBSD__)
struct sockpeercred cred;
# else
ucred cred;
# endif
socklen_t credLen = sizeof(cred);
if (getsockopt(remote, SOL_SOCKET, SO_PEERCRED, &cred, &credLen) == 0) {
peer.pid = cred.pid;
peer.uid = cred.uid;
peer.gid = cred.gid;
}

#elif defined(LOCAL_PEERCRED)

# if !defined(SOL_LOCAL)
# define SOL_LOCAL 0
# endif

xucred cred;
socklen_t credLen = sizeof(cred);
if (getsockopt(remote, SOL_LOCAL, LOCAL_PEERCRED, &cred, &credLen) == 0)
peer.uid = cred.cr_uid;

#endif

return peer;
}

[[noreturn]] void serveUnixSocket(const ServeUnixSocketOptions & options, UnixSocketHandler handler)
{
std::vector<AutoCloseFD> listeningSockets;

static constexpr int SD_LISTEN_FDS_START = 3;

// Handle socket-based activation by systemd.
auto listenFds = getEnv("LISTEN_FDS");
if (listenFds) {
if (getEnv("LISTEN_PID") != std::to_string(getpid()))
throw Error("unexpected systemd environment variables");
auto count = string2Int<unsigned int>(*listenFds);
assert(count);
for (unsigned int i = 0; i < count; ++i) {
AutoCloseFD fdSocket(SD_LISTEN_FDS_START + i);
closeOnExec(fdSocket.get());
listeningSockets.push_back(std::move(fdSocket));
}
}

// Otherwise, create and bind to a Unix domain socket.
else {
createDirs(options.socketPath.parent_path());
listeningSockets.push_back(createUnixDomainSocket(options.socketPath.string(), options.socketMode));
}

std::vector<struct pollfd> fds;
for (auto & i : listeningSockets)
fds.push_back({.fd = i.get(), .events = POLLIN});

// Loop accepting connections.
while (1) {
try {
checkInterrupt();

auto count = poll(fds.data(), fds.size(), -1);
if (count == -1) {
if (errno == EINTR)
continue;
throw SysError("polling for incoming connections");
}

for (auto & fd : fds) {
if (!fd.revents)
continue;

// Accept a connection.
struct sockaddr_un remoteAddr;
socklen_t remoteAddrLen = sizeof(remoteAddr);

AutoCloseFD remote = accept(fd.fd, (struct sockaddr *) &remoteAddr, &remoteAddrLen);
checkInterrupt();
if (!remote) {
if (errno == EINTR)
continue;
throw SysError("accepting connection");
}

handler(std::move(remote), [&]() { listeningSockets.clear(); });
}

} catch (Error & error) {
auto ei = error.info();
// FIXME: add to trace?
ei.msg = HintFmt("while processing connection: %1%", ei.msg.str());
logError(ei);
}
}
}

} // namespace nix::unix
Loading
Loading