From 3293bc146985c56790033409facc0ad64a471084 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 10 Mar 2018 19:01:48 +0100 Subject: udhcpd: fix "not dying on SIGTERM" Fixes: commit 52a515d18724bbb34e3ccbbb0218efcc4eccc0a8 "udhcp: use poll() instead of select()" Feb 16 2017 udhcp_sp_read() is meant to check whether signal pipe indeed has some data to read. In the above commit, it was changed as follows: - if (!FD_ISSET(signal_pipe.rd, rfds)) + if (!pfds[0].revents) return 0; The problem is, the check was working for select() purely by accident. Caught signal interrupts select()/poll() syscalls, they return with EINTR (regardless of SA_RESTART flag in sigaction). _Then_ signal handler is invoked. IOW: they can't see any changes to fd state caused by signal haldler (in our case, signal handler makes signal pipe ready to be read). For select(), it means that rfds[] bit array is unmodified, bit of signal pipe's read fd is still set, and the above check "works": it thinks select() says there is data to read. This accident does not work for poll(): .revents stays clear, and we do not try reading signal pipe as we should. In udhcpd, we fall through and block in socket read. Further SIGTERM signals simply cause socket read to be interrupted and then restarted (since SIGTERM handler has SA_RESTART=1). Fixing this as follows: remove the check altogether. Set signal pipe read fd to nonblocking mode. Always read it in udhcp_sp_read(). If read fails, assume it's EAGAIN and return 0 ("no signal seen"). udhcpd avoids reading signal pipe on every recvd packet by looping if EINTR (using safe_poll()) - thus ensuring we have correct .revents for all fds - and calling udhcp_sp_read() only if pfds[0].revents!=0. udhcpc performs much fewer reads (typically it sleeps >99.999% of the time), there is no need to optimize it: can call udhcp_sp_read() after each poll unconditionally. To robustify socket reads, unconditionally set pfds[1].revents=0 in udhcp_sp_fd_set() (which is before poll), and check it before reading network socket in udhcpd. TODO: This might still fail: if pfds[1].revents=POLLIN, socket read may still block. There are rare cases when select/poll indicates that data can be read, but then actual read still blocks (one such case is UDP packets with wrong checksum). General advise is, if you use a poll/select loop, keep all your fds nonblocking. Maybe we should also do that to our network sockets? function old new delta udhcp_sp_setup 55 65 +10 udhcp_sp_fd_set 54 60 +6 udhcp_sp_read 46 36 -10 udhcpd_main 1451 1437 -14 udhcpc_main 2723 2708 -15 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/3 up/down: 16/-39) Total: -23 bytes Signed-off-by: Denys Vlasenko --- examples/var_service/dhcpd_if/run | 4 ++-- examples/var_service/dhcpd_if/udhcpc.conf | 28 ---------------------------- examples/var_service/dhcpd_if/udhcpd.conf | 28 ++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 30 deletions(-) delete mode 100644 examples/var_service/dhcpd_if/udhcpc.conf create mode 100644 examples/var_service/dhcpd_if/udhcpd.conf (limited to 'examples') diff --git a/examples/var_service/dhcpd_if/run b/examples/var_service/dhcpd_if/run index de85dece0..a603bdc71 100755 --- a/examples/var_service/dhcpd_if/run +++ b/examples/var_service/dhcpd_if/run @@ -11,13 +11,13 @@ echo "* Upping iface $if" ip link set dev $if up >>udhcpd.leases -sed 's/^interface.*$/interface '"$if/" -i udhcpc.conf +sed 's/^interface.*$/interface '"$if/" -i udhcpd.conf echo "* Starting udhcpd" exec \ env - PATH="$PATH" \ softlimit \ setuidgid root \ -udhcpd -f -vv udhcpc.conf +udhcpd -f -vv udhcpd.conf exit $? diff --git a/examples/var_service/dhcpd_if/udhcpc.conf b/examples/var_service/dhcpd_if/udhcpc.conf deleted file mode 100644 index a81925970..000000000 --- a/examples/var_service/dhcpd_if/udhcpc.conf +++ /dev/null @@ -1,28 +0,0 @@ -# Directives with defaults: -# start 192.168.0.20 -# end 192.168.0.254 -# interface eth0 -# max_leases 235 -# auto_time 7200 -# decline_time 3600 -# conflict_time 3600 -# offer_time 60 -# min_lease 60 -# lease_file /var/lib/misc/udhcpd.leases -# pidfile /var/run/udhcpd.pid -# siaddr 0.0.0.0 -# -# Directives with no defaults (or with empty defaults): -# option/opt NAME VALUE -# notify_file /path/to/script_to_run_after_leasefile_is_written -# (it is run with $1 = lease_file_name) -# sname dhcp_packet_sname_field_contents -# boot_file dhcp_packet_bootfile_field_contents -# static_lease XX:XX:XX:XX:XX:XX IP.ADD.RE.SS - -interface if -pidfile /dev/null -lease_file udhcpd.leases -option subnet 255.255.255.0 -option lease 3600 -#option router 192.168.0.1 diff --git a/examples/var_service/dhcpd_if/udhcpd.conf b/examples/var_service/dhcpd_if/udhcpd.conf new file mode 100644 index 000000000..a81925970 --- /dev/null +++ b/examples/var_service/dhcpd_if/udhcpd.conf @@ -0,0 +1,28 @@ +# Directives with defaults: +# start 192.168.0.20 +# end 192.168.0.254 +# interface eth0 +# max_leases 235 +# auto_time 7200 +# decline_time 3600 +# conflict_time 3600 +# offer_time 60 +# min_lease 60 +# lease_file /var/lib/misc/udhcpd.leases +# pidfile /var/run/udhcpd.pid +# siaddr 0.0.0.0 +# +# Directives with no defaults (or with empty defaults): +# option/opt NAME VALUE +# notify_file /path/to/script_to_run_after_leasefile_is_written +# (it is run with $1 = lease_file_name) +# sname dhcp_packet_sname_field_contents +# boot_file dhcp_packet_bootfile_field_contents +# static_lease XX:XX:XX:XX:XX:XX IP.ADD.RE.SS + +interface if +pidfile /dev/null +lease_file udhcpd.leases +option subnet 255.255.255.0 +option lease 3600 +#option router 192.168.0.1 -- cgit v1.2.3