aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2018-03-10 19:01:48 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2018-03-10 19:41:54 +0100
commit39bf15ba3e5d5a0f04eab05d4675241961bb333d (patch)
tree495982b0a6c4d327e1061c146e3825952e66e07a
parent37277a23fe48b13313f5d96084d890ed21d5fd8b (diff)
downloadbusybox-w32-39bf15ba3e5d5a0f04eab05d4675241961bb333d.tar.gz
busybox-w32-39bf15ba3e5d5a0f04eab05d4675241961bb333d.tar.bz2
busybox-w32-39bf15ba3e5d5a0f04eab05d4675241961bb333d.zip
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 <vda.linux@googlemail.com>
-rwxr-xr-xexamples/var_service/dhcpd_if/run4
-rw-r--r--examples/var_service/dhcpd_if/udhcpd.conf (renamed from examples/var_service/dhcpd_if/udhcpc.conf)0
-rw-r--r--networking/udhcp/common.h2
-rw-r--r--networking/udhcp/d6_dhcpc.c9
-rw-r--r--networking/udhcp/dhcpc.c9
-rw-r--r--networking/udhcp/dhcpd.c34
-rw-r--r--networking/udhcp/signalpipe.c11
7 files changed, 35 insertions, 34 deletions
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"
11ip link set dev $if up 11ip link set dev $if up
12 12
13>>udhcpd.leases 13>>udhcpd.leases
14sed 's/^interface.*$/interface '"$if/" -i udhcpc.conf 14sed 's/^interface.*$/interface '"$if/" -i udhcpd.conf
15 15
16echo "* Starting udhcpd" 16echo "* Starting udhcpd"
17exec \ 17exec \
18env - PATH="$PATH" \ 18env - PATH="$PATH" \
19softlimit \ 19softlimit \
20setuidgid root \ 20setuidgid root \
21udhcpd -f -vv udhcpc.conf 21udhcpd -f -vv udhcpd.conf
22 22
23exit $? 23exit $?
diff --git a/examples/var_service/dhcpd_if/udhcpc.conf b/examples/var_service/dhcpd_if/udhcpd.conf
index a81925970..a81925970 100644
--- a/examples/var_service/dhcpd_if/udhcpc.conf
+++ b/examples/var_service/dhcpd_if/udhcpd.conf
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index 04939e707..13059f106 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -314,7 +314,7 @@ int udhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
314 314
315void udhcp_sp_setup(void) FAST_FUNC; 315void udhcp_sp_setup(void) FAST_FUNC;
316void udhcp_sp_fd_set(struct pollfd *pfds, int extra_fd) FAST_FUNC; 316void udhcp_sp_fd_set(struct pollfd *pfds, int extra_fd) FAST_FUNC;
317int udhcp_sp_read(struct pollfd *pfds) FAST_FUNC; 317int udhcp_sp_read(void) FAST_FUNC;
318 318
319int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *nip, uint8_t *mac) FAST_FUNC; 319int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *nip, uint8_t *mac) FAST_FUNC;
320 320
diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c
index 65ff5deab..28fc7fb83 100644
--- a/networking/udhcp/d6_dhcpc.c
+++ b/networking/udhcp/d6_dhcpc.c
@@ -1378,13 +1378,12 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
1378 /* yah, I know, *you* say it would never happen */ 1378 /* yah, I know, *you* say it would never happen */
1379 timeout = INT_MAX; 1379 timeout = INT_MAX;
1380 continue; /* back to main loop */ 1380 continue; /* back to main loop */
1381 } /* if select timed out */ 1381 } /* if poll timed out */
1382 1382
1383 /* select() didn't timeout, something happened */ 1383 /* poll() didn't timeout, something happened */
1384 1384
1385 /* Is it a signal? */ 1385 /* Is it a signal? */
1386 /* note: udhcp_sp_read checks poll result before reading */ 1386 switch (udhcp_sp_read()) {
1387 switch (udhcp_sp_read(pfds)) {
1388 case SIGUSR1: 1387 case SIGUSR1:
1389 client_config.first_secs = 0; /* make secs field count from 0 */ 1388 client_config.first_secs = 0; /* make secs field count from 0 */
1390 already_waited_sec = 0; 1389 already_waited_sec = 0;
@@ -1419,7 +1418,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
1419 } 1418 }
1420 1419
1421 /* Is it a packet? */ 1420 /* Is it a packet? */
1422 if (listen_mode == LISTEN_NONE || !pfds[1].revents) 1421 if (!pfds[1].revents)
1423 continue; /* no */ 1422 continue; /* no */
1424 1423
1425 { 1424 {
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index 55f21c187..fd18325c1 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -1576,13 +1576,12 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
1576 /* yah, I know, *you* say it would never happen */ 1576 /* yah, I know, *you* say it would never happen */
1577 timeout = INT_MAX; 1577 timeout = INT_MAX;
1578 continue; /* back to main loop */ 1578 continue; /* back to main loop */
1579 } /* if select timed out */ 1579 } /* if poll timed out */
1580 1580
1581 /* select() didn't timeout, something happened */ 1581 /* poll() didn't timeout, something happened */
1582 1582
1583 /* Is it a signal? */ 1583 /* Is it a signal? */
1584 /* note: udhcp_sp_read checks poll result before reading */ 1584 switch (udhcp_sp_read()) {
1585 switch (udhcp_sp_read(pfds)) {
1586 case SIGUSR1: 1585 case SIGUSR1:
1587 client_config.first_secs = 0; /* make secs field count from 0 */ 1586 client_config.first_secs = 0; /* make secs field count from 0 */
1588 already_waited_sec = 0; 1587 already_waited_sec = 0;
@@ -1617,7 +1616,7 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv)
1617 } 1616 }
1618 1617
1619 /* Is it a packet? */ 1618 /* Is it a packet? */
1620 if (listen_mode == LISTEN_NONE || !pfds[1].revents) 1619 if (!pfds[1].revents)
1621 continue; /* no */ 1620 continue; /* no */
1622 1621
1623 { 1622 {
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c
index 238542bb0..f1368cc4d 100644
--- a/networking/udhcp/dhcpd.c
+++ b/networking/udhcp/dhcpd.c
@@ -915,20 +915,18 @@ int udhcpd_main(int argc UNUSED_PARAM, char **argv)
915 915
916 udhcp_sp_fd_set(pfds, server_socket); 916 udhcp_sp_fd_set(pfds, server_socket);
917 tv = timeout_end - monotonic_sec(); 917 tv = timeout_end - monotonic_sec();
918 retval = 0; 918 /* Block here waiting for either signal or packet */
919 if (!server_config.auto_time || tv > 0) { 919 retval = safe_poll(pfds, 2, server_config.auto_time ? tv * 1000 : -1);
920 retval = poll(pfds, 2, server_config.auto_time ? tv * 1000 : -1); 920 if (retval <= 0) {
921 } 921 if (retval == 0) {
922 if (retval == 0) { 922 write_leases();
923 write_leases(); 923 goto continue_with_autotime;
924 goto continue_with_autotime; 924 }
925 } 925 /* < 0 and not EINTR: should not happen */
926 if (retval < 0 && errno != EINTR) { 926 bb_perror_msg_and_die("poll");
927 log1("error on select");
928 continue;
929 } 927 }
930 928
931 switch (udhcp_sp_read(pfds)) { 929 if (pfds[0].revents) switch (udhcp_sp_read()) {
932 case SIGUSR1: 930 case SIGUSR1:
933 bb_error_msg("received %s", "SIGUSR1"); 931 bb_error_msg("received %s", "SIGUSR1");
934 write_leases(); 932 write_leases();
@@ -938,12 +936,16 @@ int udhcpd_main(int argc UNUSED_PARAM, char **argv)
938 bb_error_msg("received %s", "SIGTERM"); 936 bb_error_msg("received %s", "SIGTERM");
939 write_leases(); 937 write_leases();
940 goto ret0; 938 goto ret0;
941 case 0: /* no signal: read a packet */
942 break;
943 default: /* signal or error (probably EINTR): back to select */
944 continue;
945 } 939 }
946 940
941 /* Is it a packet? */
942 if (!pfds[1].revents)
943 continue; /* no */
944
945 /* Note: we do not block here, we block on poll() instead.
946 * Blocking here would prevent SIGTERM from working:
947 * socket read inside this call is restarted on caught signals.
948 */
947 bytes = udhcp_recv_kernel_packet(&packet, server_socket); 949 bytes = udhcp_recv_kernel_packet(&packet, server_socket);
948 if (bytes < 0) { 950 if (bytes < 0) {
949 /* bytes can also be -2 ("bad packet data") */ 951 /* bytes can also be -2 ("bad packet data") */
diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c
index b101b4ce4..2ff78f0f2 100644
--- a/networking/udhcp/signalpipe.c
+++ b/networking/udhcp/signalpipe.c
@@ -40,6 +40,7 @@ void FAST_FUNC udhcp_sp_setup(void)
40 xpiped_pair(signal_pipe); 40 xpiped_pair(signal_pipe);
41 close_on_exec_on(signal_pipe.rd); 41 close_on_exec_on(signal_pipe.rd);
42 close_on_exec_on(signal_pipe.wr); 42 close_on_exec_on(signal_pipe.wr);
43 ndelay_on(signal_pipe.rd);
43 ndelay_on(signal_pipe.wr); 44 ndelay_on(signal_pipe.wr);
44 bb_signals(0 45 bb_signals(0
45 + (1 << SIGUSR1) 46 + (1 << SIGUSR1)
@@ -61,20 +62,20 @@ void FAST_FUNC udhcp_sp_fd_set(struct pollfd pfds[2], int extra_fd)
61 pfds[1].fd = extra_fd; 62 pfds[1].fd = extra_fd;
62 pfds[1].events = POLLIN; 63 pfds[1].events = POLLIN;
63 } 64 }
65 /* this simplifies "is extra_fd ready?" tests elsewhere: */
66 pfds[1].revents = 0;
64} 67}
65 68
66/* Read a signal from the signal pipe. Returns 0 if there is 69/* Read a signal from the signal pipe. Returns 0 if there is
67 * no signal, -1 on error (and sets errno appropriately), and 70 * no signal, -1 on error (and sets errno appropriately), and
68 * your signal on success */ 71 * your signal on success */
69int FAST_FUNC udhcp_sp_read(struct pollfd pfds[2]) 72int FAST_FUNC udhcp_sp_read(void)
70{ 73{
71 unsigned char sig; 74 unsigned char sig;
72 75
73 if (!pfds[0].revents) 76 /* Can't block here, fd is in nonblocking mode */
74 return 0;
75
76 if (safe_read(signal_pipe.rd, &sig, 1) != 1) 77 if (safe_read(signal_pipe.rd, &sig, 1) != 1)
77 return -1; 78 return 0;
78 79
79 return sig; 80 return sig;
80} 81}