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
8 changes: 7 additions & 1 deletion NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ https://github.com/networkupstools/nut/milestone/10
support implemented for USB connected drivers. Library API version
bumped to 2.3.0 accordingly.

- NUT CI improvements:
- upsd: Fixed conditions for "no listening interface available" diagnosis
to check how many listeners we succeeded with, not whether the first one
succeeded or not. If not all requested (non-localhost) listeners were
available, default to fail the daemon start-up attempt; support for an
`ALLOW_NOT_ALL_LISTENERS` setting was added to control this behavior. [#723]

- NUT CI improvements:
* Added publishing recipes for PyNUT client bindings for NUT, so it ends
up in the link:https://pypi.org/project/PyNUTClient[PyPI repository].
[#2158]
Expand Down
22 changes: 16 additions & 6 deletions conf/nut.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,25 @@

MODE=none

# Uncomment this to allow starting the service even if `ups.conf` has no device
# sections configured at the moment. This environment variable overrides the
# built-in "false" flag in `upsd`, and an optional same-named default flag that
# can be set in `upsd.conf`. If you want a data server always running, even if
# it initially has nothing to serve (may be live-reloaded later, when devices
# become configured), this option is for you.
# Uncomment this to allow starting the `upsd` data server even if `ups.conf`
# has no device sections configured at the moment. This environment variable
# overrides the built-in "false" flag in `upsd`, and an optional same-named
# default flag that can be set in `upsd.conf`. If you want a data server always
# running, even if it initially has nothing to serve (may be live-reloaded
# later, when devices become configured), this option is for you.
#ALLOW_NO_DEVICE=true
#export ALLOW_NO_DEVICE

# Uncomment this to allow starting the `upsd` data server even if not all
# `LISTEN` directives can be honoured at the moment. This environment variable
# overrides the built-in "false" flag in `upsd`, and an optional same-named
# default flag that can be set in `upsd.conf`. If you want a data server always
# running, even if it would potentially not serve all clients on every uptime,
# this option is for you (note you would have to restart `upsd` to pick up the
# `LISTEN`ed IP address if it appears later). Probably `LISTEN *` is better.
#ALLOW_NOT_ALL_LISTENERS=true
#export ALLOW_NOT_ALL_LISTENERS

# The optional 'UPSD_OPTIONS' allow to set upsd specific command-line options.
# It is ignored when 'MODE' above indicates that no upsd should be running.
# It may be redundant in comparison to options which can be set in `upsd.conf`.
Expand Down
19 changes: 19 additions & 0 deletions conf/upsd.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@
# Boolean values 'false', 'no', 'off' and '0' mean that the server should refuse
# to start if zero device sections were found in ups.conf. This is the default.

# =======================================================================
# ALLOW_NOT_ALL_LISTENERS <Boolean>
# ALLOW_NOT_ALL_LISTENERS true
#
# Normally upsd requires that all `LISTEN` directives can be honoured at the
# moment the daemon starts. If your LAN IP address (or host name) used in one
# of the `LISTEN` directives may be not always accessible, and for some reason
# do not want to just `LISTEN *` on the wildcard interface, but e.g. you still
# want to use `upsmon` on `localhost`, this option can help. Note you would
# have to restart `upsd` to pick up the `LISTEN`ed IP address if it appears
# later.
#
# Boolean values 'true', 'yes', 'on' and '1' mean that the server would not
# refuse to start if it can listen on at least one interface.
#
# Boolean values 'false', 'no', 'off' and '0' mean that the server should
# refuse to start if it can not LISTEN on each and every (non-localhost)
# interface found in upsd.conf. This is the default.

# =======================================================================
# STATEPATH <path>
# STATEPATH /var/run/nut
Expand Down
10 changes: 10 additions & 0 deletions docs/config-notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,16 @@ but still want the data server to run, respond and report zero devices
(e.g. on an automatically managed monitoring deployment), you can enable
the `ALLOW_NO_DEVICE true` option in the 'upsd.conf' file.

NOTE: Normally `upsd` requires that at all `LISTEN` directives defined
in the 'upsd.conf' file are honoured (except for mishaps possible with
many names of `localhost`), and refuses to start otherwise. If you want
to allow start-up in cases where at least one but possibly not all of
the `LISTEN` directives were honoured, you can enable the
`ALLOW_NOT_ALL_LISTENERS true` option in the 'upsd.conf' file.
Note you would have to restart `upsd` to pick up the `LISTEN`ed IP address
if it appears later, so probably configuring `LISTEN *` is a better choice
in such cases.

On operating systems with service management frameworks, the data server
life-cycle is managed by `nut-server` service.

Expand Down
14 changes: 14 additions & 0 deletions docs/man/nut.conf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ If you want a data server always running and responding on the network, even
if it initially has nothing to serve (may be live-reloaded later, when devices
become configured), this option is for you.

*ALLOW_NOT_ALL_LISTENERS*::
Optional, defaults to `false`. Set this to `true` to allow starting the `upsd`
NUT data server even if not all `LISTEN` directives can be honoured at the
moment. This environment variable overrides the built-in "false" flag in the
`upsd` program, and an optional same-named default flag that can be set in
`upsd.conf`.
+
If you want a data server always running, even if it would potentially not
serve all clients on every uptime, this option is for you (note you would
have to restart `upsd` to pick up the `LISTEN`ed IP address if it appears
later).
+
Probably configuring `LISTEN *` is a better choice in such cases.

*UPSD_OPTIONS*::
Optional. Set upsd specific options. See linkman:upsd[8] for more
details. It is ignored when 'MODE' above indicates that no upsd
Expand Down
20 changes: 20 additions & 0 deletions docs/man/upsd.conf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@ unless the calling environment sets a same-named variable to enforce a value
for the current run. One way this can happen is somebody un-commenting it in
the 'nut.conf' file used by init-scripts and service unit method scripts.

"ALLOW_NOT_ALL_LISTENERS 'Boolean'"::

Normally upsd requires that all `LISTEN` directives can be honoured at the
moment the daemon starts. If your LAN IP address (or host name) used in one
of the `LISTEN` directives may be not always accessible, and for some reason
do not want to just `LISTEN *` on the wildcard interface, but e.g. you still
want to use `upsmon` on `localhost`, this option can help. Note you would
have to restart `upsd` to pick up the `LISTEN`ed IP address if it appears
later.
+
Boolean values 'true', 'yes', 'on' and '1' mean that the server would not
refuse to start if it can listen on at least one interface.
+
Boolean values 'false', 'no', 'off' and '0' mean that the server should
refuse to start if it can not LISTEN on each and every (non-localhost)
interface found in upsd.conf. This is the default, unless the calling
environment sets a same-named variable to enforce a value for the current run.
One way this can happen is somebody un-commenting it in the 'nut.conf' file
used by init-scripts and service unit method scripts.

"STATEPATH 'path'"::

Tell upsd to look for the driver state sockets in 'path' rather
Expand Down
4 changes: 3 additions & 1 deletion scripts/augeas/nutupsdconf.aug.in
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ let path = word
let upsd_maxage = [ opt_spc . key "MAXAGE" . sep_spc . store num . eol ]
let upsd_trackingdelay = [ opt_spc . key "TRACKINGDELAY" . sep_spc . store num . eol ]
let upsd_allow_no_device = [ opt_spc . key "ALLOW_NO_DEVICE" . sep_spc . store num . eol ]
let upsd_allow_not_all_listeners = [ opt_spc . key "ALLOW_NOT_ALL_LISTENERS" . sep_spc . store num . eol ]
let upsd_statepath = [ opt_spc . key "STATEPATH" . sep_spc . store path . eol ]
let upsd_listen = [ opt_spc . key "LISTEN" . sep_spc
. [ label "interface" . store ip ]
Expand All @@ -53,6 +54,7 @@ let upsd_certfile = [ opt_spc . key "CERTFILE" . sep_spc . store path . eol ]
* MAXAGE seconds
* TRACKINGDELAY seconds
* ALLOW_NO_DEVICE Boolean
* ALLOW_NOT_ALL_LISTENERS Boolean
* STATEPATH path
* LISTEN interface port
* Multiple lines each with one LISTEN address (or host name) and an optional
Expand All @@ -65,7 +67,7 @@ let upsd_certfile = [ opt_spc . key "CERTFILE" . sep_spc . store path . eol ]
* LISTEN 2001:0db8:1234:08d3:1319:8a2e:0370:7344
*
*************************************************************************)
let upsd_other = upsd_maxage | upsd_trackingdelay | upsd_allow_no_device | upsd_statepath | upsd_listen_list | upsd_maxconn | upsd_certfile
let upsd_other = upsd_maxage | upsd_trackingdelay | upsd_allow_no_device | upsd_allow_not_all_listeners | upsd_statepath | upsd_listen_list | upsd_maxconn | upsd_certfile

let upsd_lns = (upsd_other|comment|empty)*

Expand Down
15 changes: 14 additions & 1 deletion server/conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ static int parse_upsd_conf_args(size_t numargs, char **arg)
}
}

/* ALLOW_NO_DEVICE <seconds> */
/* ALLOW_NO_DEVICE <bool> */
if (!strcmp(arg[0], "ALLOW_NO_DEVICE")) {
if (isdigit((size_t)arg[1][0])) {
allow_no_device = (atoi(arg[1]) != 0); /* non-zero arg is true here */
Expand All @@ -215,6 +215,19 @@ static int parse_upsd_conf_args(size_t numargs, char **arg)
return 0;
}

/* ALLOW_NOT_ALL_LISTENERS <bool> */
if (!strcmp(arg[0], "ALLOW_NOT_ALL_LISTENERS")) {
if (isdigit((size_t)arg[1][0])) {
allow_not_all_listeners = (atoi(arg[1]) != 0); /* non-zero arg is true here */
return 1;
}
if (parse_boolean(arg[1], &allow_not_all_listeners))
return 1;

upslogx(LOG_ERR, "ALLOW_NOT_ALL_LISTENERS has non numeric and non boolean value (%s)!", arg[1]);
return 0;
}

/* MAXCONN <connections> */
if (!strcmp(arg[0], "MAXCONN")) {
if (isdigit((size_t)arg[1][0])) {
Expand Down
156 changes: 154 additions & 2 deletions server/upsd.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ int tracking_delay = 3600;
*/
int allow_no_device = 0;

/*
* Preloaded to ALLOW_NOT_ALL_LISTENERS from upsd.conf or environment variable
* (with higher prio for envvar); defaults to disabled for legacy compat.
*/
int allow_not_all_listeners = 0;

/* preloaded to {OPEN_MAX} in main, can be overridden via upsd.conf */
nfds_t maxconn = 0;

Expand Down Expand Up @@ -276,14 +282,21 @@ static void setuptcp(stype_t *server)
int v = 0, one = 1;

if (VALID_FD_SOCK(server->sock_fd)) {
/* Alredy bound, e.g. thanks to 'LISTEN *' handling and injection
/* Already bound, e.g. thanks to 'LISTEN *' handling and injection
* into the list we loop over */
upsdebugx(6, "setuptcp: SKIP bind to %s port %s: entry already initialized",
server->addr, server->port);
return;
}

upsdebugx(3, "setuptcp: try to bind to %s port %s", server->addr, server->port);
if (!strcmp(server->addr, "localhost")) {
/* Warn about possible surprises with IPv4 vs. IPv6 */
upsdebugx(1,
"setuptcp: WARNING: requested to LISTEN on 'localhost' "
"by name - will use the first system-resolved "
"IP address for that");
}

/* Special handling note for `LISTEN * <port>` directive with the
* literal asterisk on systems with RFC-3493 (no relation!) support
Expand Down Expand Up @@ -877,6 +890,16 @@ static void client_readline(nut_ctype_t *client)
void server_load(void)
{
stype_t *server;
size_t listenersTotal = 0, listenersValid = 0,
listenersTotalLocalhost = 0, listenersValidLocalhost = 0,
listenersLocalhostName = 0,
listenersLocalhostName6 = 0,
listenersLocalhostIPv4 = 0,
listenersLocalhostIPv6 = 0,
listenersValidLocalhostName = 0,
listenersValidLocalhostName6 = 0,
listenersValidLocalhostIPv4 = 0,
listenersValidLocalhostIPv6 = 0;

/* default behaviour if no LISTEN address has been specified */
if (!firstaddr) {
Expand All @@ -896,10 +919,115 @@ void server_load(void)
setuptcp(server);
}

/* Account separately from setuptcp() because it can edit the list,
* e.g. when handling `LISTEN *` lines.
*/
for (server = firstaddr; server; server = server->next) {
listenersTotal++;
if (VALID_FD_SOCK(server->sock_fd)) {
listenersValid++;
}

if (!strcmp(server->addr, "localhost")) {
listenersLocalhostName++;
listenersTotalLocalhost++;
if (VALID_FD_SOCK(server->sock_fd)) {
listenersValidLocalhostName++;
listenersValidLocalhost++;
}
}

if (!strcmp(server->addr, "localhost6")) {
listenersLocalhostName6++;
listenersTotalLocalhost++;
if (VALID_FD_SOCK(server->sock_fd)) {
listenersValidLocalhostName6++;
listenersValidLocalhost++;
}
}

if (!strcmp(server->addr, "127.0.0.1")) {
listenersLocalhostIPv4++;
listenersTotalLocalhost++;
if (VALID_FD_SOCK(server->sock_fd)) {
listenersValidLocalhostIPv4++;
listenersValidLocalhost++;
}
}

if (!strcmp(server->addr, "::1")) {
listenersLocalhostIPv6++;
listenersTotalLocalhost++;
if (VALID_FD_SOCK(server->sock_fd)) {
listenersValidLocalhostIPv6++;
listenersValidLocalhost++;
}
}
}

upsdebugx(1, "%s: tried to set up %" PRIuSIZE
" listening sockets, succeeded with %" PRIuSIZE,
__func__, listenersTotal, listenersValid);
upsdebugx(3, "%s: ...of those related to localhost: "
"overall: %" PRIuSIZE " tried, %" PRIuSIZE " succeeded; "
"by name: %" PRIuSIZE "T/%" PRIuSIZE "S; "
"by name(6): %" PRIuSIZE "T/%" PRIuSIZE "S; "
"by IPv4 addr: %" PRIuSIZE "T/%" PRIuSIZE "S; "
"by IPv6 addr: %" PRIuSIZE "T/%" PRIuSIZE "S",
__func__,
listenersTotalLocalhost, listenersValidLocalhost,
listenersLocalhostName, listenersValidLocalhostName,
listenersLocalhostName6, listenersValidLocalhostName6,
listenersLocalhostIPv4, listenersValidLocalhostIPv4,
listenersLocalhostIPv6, listenersValidLocalhostIPv6
);

/* check if we have at least 1 valid LISTEN interface */
if (INVALID_FD_SOCK(firstaddr->sock_fd)) {
if (!listenersValid) {
fatalx(EXIT_FAILURE, "no listening interface available");
}

/* is everything requested - handled okay? */
if (listenersTotal == listenersValid)
return;

/* check for edge cases we can let slide */
if ( (listenersTotal - listenersValid) ==
(listenersTotalLocalhost - listenersValidLocalhost)
) {
/* Note that we can also get into this situation
* when "dual-stack" IPv6 listener also handles
* IPv4 connections, and precludes successful
* setup of the IPv4 listener later.
*
* FIXME? Can we get into this situation the other
* way around - an IPv4 listener precluding the
* IPv6 one, so end-user actually lacks one of the
* requested connection types?
*/
upsdebugx(1, "%s: discrepancy corresponds to "
"addresses related to localhost; assuming "
"that it was attempted under several names "
"which resolved to same IP:PORT socket specs "
"(so only the first one of each succeeded)",
__func__);
return;
}

if (allow_not_all_listeners) {
upslogx(LOG_WARNING,
"WARNING: some listening interfaces were "
"not available, but the ALLOW_NOT_ALL_LISTENERS "
"setting is active");
} else {
upsdebugx(0,
"Reconcile available NUT server IP addresses "
"and LISTEN configuration, or consider the "
"ALLOW_NOT_ALL_LISTENERS setting!");
fatalx(EXIT_FAILURE,
"Fatal error: some listening interfaces were "
"not available");
}
}

void server_free(void)
Expand Down Expand Up @@ -2074,6 +2202,30 @@ int main(int argc, char **argv)
}
} /* scope */

{ /* scope */
/* As documented above, the ALLOW_NOT_ALL_LISTENERS can be provided via
* envvars and then has higher priority than an upsd.conf setting
*/
const char *envvar = getenv("ALLOW_NOT_ALL_LISTENERS");
if ( envvar != NULL) {
if ( (!strncasecmp("TRUE", envvar, 4)) || (!strncasecmp("YES", envvar, 3)) || (!strncasecmp("ON", envvar, 2)) || (!strncasecmp("1", envvar, 1)) ) {
/* Admins of this server expressed a desire to serve
* NUT protocol if at least one configured listener
* works (some may be missing and clients using those
* addresses would not be served!)
*/
allow_not_all_listeners = 1;
} else if ( (!strncasecmp("FALSE", envvar, 5)) || (!strncasecmp("NO", envvar, 2)) || (!strncasecmp("OFF", envvar, 3)) || (!strncasecmp("0", envvar, 1)) ) {
/* Admins of this server expressed a desire to serve
* NUT protocol only if all configured listeners work
* (default for least surprise - admins must address
* any configuration inconsistencies!)
*/
allow_not_all_listeners = 0;
}
}
} /* scope */

/* start server */
server_load();

Expand Down
Loading