in http-common/src/connector.rs [730:824]
fn get_systemd_socket(
socket_name: Option<String>,
) -> Result<Option<std::os::unix::io::RawFd>, String> {
// Ref: <https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html>
//
// Try to find a systemd socket to match when non "fd" path has been provided.
// We consider 4 cases:
// 1. When there is only 1 socket. In this case, we can ignore the socket name. It means
// the call is made by identity service which uses only one systemd socket. So matching is simple
// 2. There are > 1 systemd sockets and a socket name is provided. It means edged is telling us to match an fd with the provided socket name.
// 3. There are > 1 systemd sockets and a socket name is provided but no LISTEN_FDNAMES. We can't match.
// 4. There are > 1 systemd sockets but no socket name is provided. In this case it means there is no corresponding systemd socket we should match
//
// >sd_listen_fds parses the number passed in the $LISTEN_FDS environment variable, then sets the FD_CLOEXEC flag
// >for the parsed number of file descriptors starting from SD_LISTEN_FDS_START. Finally, it returns the parsed number.
//
// Note that it's not possible to distinguish between fd numbers if a process requires more than one socket.
// That is why in edged's case we use the systemd socket name to know which fd the function should return
// CS/IS/KS currently only expect one socket, so this is fine; but it is not the case for iotedged (mgmt and workload sockets)
// for example.
//
// The complication with LISTEN_FDNAMES is that CentOS 7's systemd is too old and doesn't support it, which
// would mean CS/IS/KS would have to stop using systemd socket activation on CentOS 7 (just like iotedged). This creates more complications,
// because now the sockets either have to be placed in /var/lib/aziot (just like iotedged does) which means host modules need to try
// both /run/aziot and /var/lib/aziot to connect to a service, or the services continue to bind sockets under /run/aziot but have to create
// /run/aziot themselves on startup with ACLs for all three users and all three groups.
let listen_fds: std::os::unix::io::RawFd = match get_env("LISTEN_FDS")? {
Some(listen_fds) => listen_fds
.parse()
.map_err(|err| format!("could not read LISTEN_FDS env var: {err}"))?,
None => return Ok(None),
};
// If there is no socket available, no match is possible.
if listen_fds == 0 {
return Ok(None);
}
// fcntl(CLOEXEC) all the fds so that they aren't inherited by the child processes.
// Note that we want to do this for all the fds, not just the one we're looking for.
for fd in SD_LISTEN_FDS_START..(SD_LISTEN_FDS_START + listen_fds) {
if let Err(err) = nix::fcntl::fcntl(
fd,
nix::fcntl::FcntlArg::F_SETFD(nix::fcntl::FdFlag::FD_CLOEXEC),
) {
return Err(format!("could not fcntl({fd}, F_SETFD, FD_CLOEXEC): {err}"));
}
}
// If there is only one socket, we know this is the identity service which uses only one socket, so we have a match:
if listen_fds == 1 {
return Ok(Some(SD_LISTEN_FDS_START));
}
// If there is more than 1 socket and we don't have a socket name to match, this is edged telling us that there is no systemd socket we can match.
let Some(socket_name) = socket_name else {
return Ok(None);
};
// If there is more than one socket, this is edged. We can attempt to match the socket name to systemd.
// This happens when a unix Uri is provided in the config.toml. Systemd sockets get created nonetheless, so we still prefer to use them.
// If a socket name is provided but we don't see the env variable LISTEN_FDNAMES, it means we are probably on an older OS, and we can't match either.
let Some(listen_fdnames) = get_env("LISTEN_FDNAMES")? else {
return Ok(None);
};
let listen_fdnames: Vec<&str> = listen_fdnames.split(':').collect();
let len: std::os::unix::io::RawFd = listen_fdnames
.len()
.try_into()
.map_err(|_| "invalid number of sockets".to_string())?;
if listen_fds != len {
return Err(format!(
"Mismatch, there are {} fds, and {} names",
listen_fds,
listen_fdnames.len()
));
}
if let Some(index) = listen_fdnames
.iter()
.position(|fdname| (*fdname).eq(&socket_name))
{
let index: std::os::unix::io::RawFd = index
.try_into()
.map_err(|_| "invalid number of sockets".to_string())?;
Ok(Some(SD_LISTEN_FDS_START + index))
} else {
Err(format!(
"Could not find a match for {socket_name} in the fd list"
))
}
}