From 61937cf2e1138410f97dccc85e70594781d1ac23 Mon Sep 17 00:00:00 2001 From: William Lallemand Date: Sun, 19 Oct 2025 12:51:13 +0200 Subject: [PATCH] MINOR: systemd: support for systemd socket activation via setenv-sd-listen-fds Adds the setenv-sd-listen-fds configuration option to automatically set or clear environment variables based on systemd's LISTEN_FDS and LISTEN_FDNAMES. When enabled, HAProxy will expose file descriptors using environment variable names prefixed with FD_ followed by the uppercase FileDescriptorName defined in the systemd socket unit. This feature simplifies socket activation in containerized environments and allows binding to inherited file descriptors without relying on NAT or network initialization. Default behavior is off, which ensures the environment variables are unset. --- doc/configuration.txt | 44 ++++++++++++++++++++++++++++ include/haproxy/systemd.h | 3 ++ src/cfgparse-global.c | 29 +++++++++++++++++++ src/systemd.c | 60 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+) diff --git a/doc/configuration.txt b/doc/configuration.txt index b02a8f7cb..0b0f4176f 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1775,6 +1775,7 @@ The following keywords are supported in the "global" section : - set-dumpable - set-var - setenv + - setenv-sd-listen-fds - ssl-default-bind-ciphers - ssl-default-bind-ciphersuites - ssl-default-bind-client-sigalgs @@ -3120,6 +3121,49 @@ setenv the configuration file sees the new value. See also "presetenv", "resetenv", and "unsetenv". +setenv-sd-listen-fds [ on | off ] + Sets environment variables automatically using the LISTEN_FDS and + LISTEN_FDNAMES environment variables described in the systemd documentation. + + These 2 variables are set by systemd.socket. Systemd is able to listen to a + TCP socket by itself and pass the socket to the service using the + ListenStream keyword. When using FileDescriptorName, it is possible to assign + a name to the corresponding FD which will be inherited by haproxy. + + When set to "on", environment variables are created with the prefix "FD_", + followed by the uppercase version of FileDescriptorName from systemd. + + When set to off, the FD_ environment variables derived from + FileDescriptorName are unset. + + Limitations: + - FileDescriptorName may contain alphanumerical characters and underscores. + - variables name cannot exceed 254 characters + + This feature is particularly useful when running HAProxy in a container, as + it allows you to avoid constraints from NATing or start containers without + network access. + + Example: + + haproxy.socket: + [Socket] + ListenStream=80 + FileDescriptorName=http1 + + ListenStream=443 + FileDescriptorName=https1 + + haproxy.cfg: + global + setenv-sd-listen-fds on + + frontend + bind fd@${FD_HTTP1} + bind fd@${FD_HTTPS1} ssl ... + + Default value is off. + shm-stats-file [ EXPERIMENTAL ] When this directive is set, it enables the use of shared memory for storing stats counters. is used as argument to shm_open() to open the shared diff --git a/include/haproxy/systemd.h b/include/haproxy/systemd.h index 2ad688413..241128fec 100644 --- a/include/haproxy/systemd.h +++ b/include/haproxy/systemd.h @@ -7,4 +7,7 @@ int sd_notifyf(int unset_environment, const char *format, ...); int sd_listen_fds_with_names(int unset_environment, char ***names); int sd_listen_fds(int unset_environment); +void setenv_listen_fds(); +void unsetenv_listen_fds(); + #endif diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c index 4a9bcf000..e25a1df5e 100644 --- a/src/cfgparse-global.c +++ b/src/cfgparse-global.c @@ -26,6 +26,7 @@ #include #include #include +#include #include int cluster_secret_isset; @@ -1594,6 +1595,33 @@ static int cfg_parse_global_env_opts(char **args, int section_type, return 0; } +static int cfg_parse_global_listen_fds(char **args, int section_type, + struct proxy *curpx, const struct proxy *defpx, + const char *file, int line, char **err) +{ + if (strcmp(args[0], "setenv-sd-listen-fds") == 0) { + if (too_many_args(1, args, err, NULL)) + return -1; + if (*(args[1]) == 0) { + memprintf(err, "'%s' expects a 'on' or 'off' argument.\n", + args[0]); + return -1; + } + + if (strcmp(args[1], "on") == 0) { + setenv_listen_fds(); + } else if (strcmp(args[1], "off") == 0) { + unsetenv_listen_fds(); + } else { + memprintf(err, "'%s' expects 'on' or 'off' as argument.\n", args[0]); + return -1; + } + } + + return 0; +} + + static int cfg_parse_global_shm_stats_file(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, char **err) @@ -1852,6 +1880,7 @@ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_GLOBAL, "quiet", cfg_parse_global_mode, KWF_DISCOVERY }, { CFG_GLOBAL, "resetenv", cfg_parse_global_env_opts, KWF_DISCOVERY }, { CFG_GLOBAL, "setenv", cfg_parse_global_env_opts, KWF_DISCOVERY }, + { CFG_GLOBAL, "setenv-sd-listen-fds", cfg_parse_global_listen_fds }, { CFG_GLOBAL, "shm-stats-file", cfg_parse_global_shm_stats_file }, { CFG_GLOBAL, "shm-stats-file-max-objects", cfg_parse_global_shm_stats_file_max_objects }, { CFG_GLOBAL, "stress-level", cfg_parse_global_stress_level }, diff --git a/src/systemd.c b/src/systemd.c index 262bbe6f9..5c734a4d8 100644 --- a/src/systemd.c +++ b/src/systemd.c @@ -255,3 +255,63 @@ end: return ret; } +/* + * Automatically sets environment variables based on LISTEN_FDS and LISTEN_FDNAMES. + * + * When cleanup is set, unset those environment variables instead. + */ +static void __setenv_listen_fds(int cleanup) +{ + char **names = NULL; + char **p; + char d[255] = "FD_"; + int fd = 3; + const char *fdstr = NULL; + int i; + + sd_listen_fds_with_names(0, &names); + + p = names; + while (names && *names) { + char *n; + + /* start to write after FD_ (i = 3) */ + for (n = *names, i = 3; *n && i < sizeof(d); i++, n++) { + if (!isalnum((int)*n) && *n != '_') + break; + d[i] = toupper(*n); + } + d[i] = '\0'; + + if (!cleanup) + fdstr = ultoa(fd); + + if (cleanup) + unsetenv(d); + else + setenv(d, fdstr, 1); + names++; + fd++; + } + + /* free names allocated by sd_listen_fds_with_names() */ + names = p; + while (names && *names) { + free(*names); + names++; + } + free(p); +} + + +/* Automatically sets environment variables based on LISTEN_FDS and LISTEN_FDNAMES. */ +void setenv_listen_fds() +{ + __setenv_listen_fds(0); +} + +/* Automatically UNsets environment variables based on LISTEN_FDS and LISTEN_FDNAMES. */ +void unsetenv_listen_fds() +{ + __setenv_listen_fds(1); +}