tcpsvd,udpsvd: make them NOMMU-capable

inetd: make udp nowait work

function                                             old     new   delta
inetd_main                                          1797    2036    +239
tcpudpsvd_main                                      1839    1973    +134
xsetenv_plain                                          -      39     +39
xsetenv_proto                                         23      40     +17
bump_nofile                                          169     170      +1
sig_term_handler                                      72      69      -3
sig_child_handler                                    239     233      -6
connection_status                                     37      31      -6
parse_one_line                                      1102    1092     -10
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 4/4 up/down: 430/-25)           Total: 405 bytes
   text    data     bss     dec     hex filename
 798437     661    7428  806526   c4e7e busybox_old
 798734     661    7428  806823   c4fa7 busybox_unstripped
This commit is contained in:
Denis Vlasenko 2008-03-17 08:35:44 +00:00
parent 9f153f610f
commit aefed941c2
3 changed files with 198 additions and 140 deletions

View File

@ -1,7 +1,7 @@
#
# Automatically generated make config: don't edit
# Busybox version: 1.10.0.svn
# Thu Mar 6 20:18:05 2008
# Sat Mar 8 01:11:42 2008
#
CONFIG_HAVE_DOT_CONFIG=y
@ -844,5 +844,5 @@ CONFIG_LPQ=y
#
# ipsvd utilities
#
# CONFIG_TCPSVD is not set
# CONFIG_UDPSVD is not set
CONFIG_TCPSVD=y
CONFIG_UDPSVD=y

View File

@ -13,12 +13,12 @@
*
* Code inside "#ifdef SSLSVD" is for sslsvd and is currently unused.
*
* Output of verbose mode matches original (modulo bugs and
* unimplemented stuff). Unnatural splitting of IP and PORT
* is retained (personally I prefer one-value "IP:PORT" notation -
* it is a natural string representation of struct sockaddr_XX).
* Busybox version exports TCPLOCALADDR instead of
* TCPLOCALIP + TCPLOCALPORT pair. ADDR more closely matches reality
* (which is "struct sockaddr_XXX". Port is not a separate entity,
* it's just a part of (AF_INET[6]) sockaddr!).
*
* TCPORIGDST{IP,PORT} is busybox-specific addition
* TCPORIGDSTADDR is Busybox-specific addition.
*
* udp server is hacked up by reusing TCP code. It has the following
* limitation inherent in Unix DGRAM sockets implementation:
@ -46,6 +46,8 @@ struct globals {
unsigned cur_per_host;
unsigned cnum;
unsigned cmax;
char **env_cur;
char *env_var[1]; /* actually bigger */
};
#define G (*(struct globals*)&bb_common_bufsiz1)
#define verbose (G.verbose )
@ -53,21 +55,46 @@ struct globals {
#define cur_per_host (G.cur_per_host)
#define cnum (G.cnum )
#define cmax (G.cmax )
#define env_cur (G.env_cur )
#define env_var (G.env_var )
#define INIT_G() \
do { \
cmax = 30; \
env_cur = &env_var[0]; \
} while (0)
/* We have to be careful about leaking memory in repeated setenv's */
static void xsetenv_plain(const char *n, const char *v)
{
char *var = xasprintf("%s=%s", n, v);
*env_cur++ = var;
putenv(var);
}
static void xsetenv_proto(const char *proto, const char *n, const char *v)
{
putenv(xasprintf("%s%s=%s", proto, n, v));
char *var = xasprintf("%s%s=%s", proto, n, v);
*env_cur++ = var;
putenv(var);
}
static void undo_xsetenv(void)
{
char **pp = env_cur = &env_var[0];
while (*pp) {
char *var = *pp;
*strchrnul(var, '=') = '\0';
unsetenv(var);
free(var);
*pp++ = NULL;
}
}
static void sig_term_handler(int sig)
{
if (verbose)
printf("%s: info: sigterm received, exit\n", applet_name);
bb_error_msg("got signal %u, exit", sig);
kill_myself_with_sig(sig);
}
@ -85,7 +112,7 @@ static void print_waitstat(unsigned pid, int wstat)
cause = "signal";
e = WTERMSIG(wstat);
}
printf("%s: info: end %d %s %d\n", applet_name, pid, cause, e);
bb_error_msg("end %d %s %d", pid, cause, e);
}
/* Must match getopt32 in main! */
@ -113,7 +140,7 @@ static void connection_status(void)
{
/* "only 1 client max" desn't need this */
if (cmax > 1)
printf("%s: info: status %u/%u\n", applet_name, cnum, cmax);
bb_error_msg("status %u/%u", cnum, cmax);
}
static void sig_child_handler(int sig)
@ -145,13 +172,11 @@ int tcpudpsvd_main(int argc, char **argv)
#ifndef SSLSVD
struct bb_uidgid_t ugid;
#endif
bool need_hostnames, need_remote_ip, tcp;
bool tcp;
uint16_t local_port;
char *local_hostname = NULL;
char *remote_hostname = (char*)""; /* "" used if no -h */
char *local_addr = local_addr; /* gcc */
char *remote_addr = remote_addr; /* gcc */
char *remote_ip = remote_addr; /* gcc */
char *preset_local_hostname = NULL;
char *remote_hostname = remote_hostname; /* for compiler */
char *remote_addr = remote_addr; /* for compiler */
len_and_sockaddr *lsa;
len_and_sockaddr local, remote;
socklen_t sa_len;
@ -168,12 +193,12 @@ int tcpudpsvd_main(int argc, char **argv)
opt_complementary = "-3:i--i:ph:vv";
#ifdef SSLSVD
getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:",
&str_c, &str_C, &instructs, &instructs, &user, &local_hostname,
&str_c, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
&str_b, &str_t, &ssluser, &root, &cert, &key, &verbose
);
#else
getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v",
&str_c, &str_C, &instructs, &instructs, &user, &local_hostname,
&str_c, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
&str_b, &str_t, &verbose
);
#endif
@ -210,26 +235,21 @@ int tcpudpsvd_main(int argc, char **argv)
if (!tcp)
max_per_host = 0;
/* stdout is used for logging, don't buffer */
setlinebuf(stdout);
bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */
need_hostnames = verbose || !(option_mask32 & OPT_E);
need_remote_ip = max_per_host || need_hostnames;
#ifdef SSLSVD
sslser = user;
client = 0;
if ((getuid() == 0) && !(option_mask32 & OPT_u)) {
xfunc_exitcode = 100;
bb_error_msg_and_die("fatal: -U ssluser must be set when running as root");
bb_error_msg_and_die("-U ssluser must be set when running as root");
}
if (option_mask32 & OPT_u)
if (!uidgid_get(&sslugid, ssluser, 1)) {
if (errno) {
bb_perror_msg_and_die("fatal: cannot get user/group: %s", ssluser);
}
bb_error_msg_and_die("fatal: unknown user/group '%s'", ssluser);
bb_error_msg_and_die("unknown user/group '%s'", ssluser);
}
if (!cert) cert = "./cert.pem";
if (!key) key = cert;
@ -246,7 +266,7 @@ int tcpudpsvd_main(int argc, char **argv)
sig_block(SIGCHLD);
signal(SIGCHLD, sig_child_handler);
signal(SIGTERM, sig_term_handler);
bb_signals(BB_SIGS_FATAL, sig_term_handler);
signal(SIGPIPE, SIG_IGN);
if (max_per_host)
@ -254,6 +274,8 @@ int tcpudpsvd_main(int argc, char **argv)
local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0);
lsa = xhost2sockaddr(argv[0], local_port);
argv += 2;
sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
setsockopt_reuseaddr(sock);
sa_len = lsa->len; /* I presume sockaddr len stays the same */
@ -274,14 +296,13 @@ int tcpudpsvd_main(int argc, char **argv)
if (verbose) {
char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa);
printf("%s: info: listening on %s", applet_name, addr);
bb_error_msg("listening on %s, starting", addr);
free(addr);
#ifndef SSLSVD
if (option_mask32 & OPT_u)
printf(", uid %u, gid %u",
(unsigned)ugid.uid, (unsigned)ugid.gid);
#endif
puts(", starting");
}
/* Main accept() loop */
@ -297,14 +318,15 @@ int tcpudpsvd_main(int argc, char **argv)
close(0);
again2:
sig_unblock(SIGCHLD);
local.len = remote.len = sa_len;
if (tcp) {
remote.len = sa_len;
conn = accept(sock, &remote.u.sa, &remote.len);
} else {
/* In case recv_from_to won't be able to recover local addr.
* Also sets port - recv_from_to is unable to do it. */
local = *lsa;
conn = recv_from_to(sock, NULL, 0, MSG_PEEK, &remote.u.sa, &local.u.sa, sa_len);
conn = recv_from_to(sock, NULL, 0, MSG_DONTWAIT | MSG_PEEK,
&remote.u.sa, &local.u.sa, sa_len);
}
sig_block(SIGCHLD);
if (conn < 0) {
@ -317,19 +339,19 @@ int tcpudpsvd_main(int argc, char **argv)
if (max_per_host) {
/* Drop connection immediately if cur_per_host > max_per_host
* (minimizing load under SYN flood) */
remote_ip = xmalloc_sockaddr2dotted_noport(&remote.u.sa);
cur_per_host = ipsvd_perhost_add(remote_ip, max_per_host, &hccp);
remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa);
cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp);
if (cur_per_host > max_per_host) {
/* ipsvd_perhost_add detected that max is exceeded
* (and did not store ip in connection table) */
free(remote_ip);
free(remote_addr);
if (msg_per_host) {
/* don't block or test for errors */
ndelay_on(0);
write(0, msg_per_host, len_per_host);
send(0, msg_per_host, len_per_host, MSG_DONTWAIT);
}
goto again1;
}
/* NB: remote_addr is not leaked, it is stored in conn table */
}
if (!tcp) {
@ -372,19 +394,21 @@ int tcpudpsvd_main(int argc, char **argv)
#endif
}
pid = fork();
pid = vfork();
if (pid == -1) {
bb_perror_msg("fork");
bb_perror_msg("vfork");
goto again;
}
if (pid != 0) {
/* parent */
/* Parent */
cnum++;
if (verbose)
connection_status();
if (hccp)
hccp->pid = pid;
/* clean up changes done by vforked child */
undo_xsetenv();
goto again;
}
@ -394,78 +418,93 @@ int tcpudpsvd_main(int argc, char **argv)
if (tcp)
close(sock);
if (need_remote_ip)
remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa);
{ /* vfork alert! every xmalloc in this block should be freed! */
char *local_hostname = local_hostname; /* for compiler */
char *local_addr = NULL;
char *free_me0 = NULL;
char *free_me1 = NULL;
char *free_me2 = NULL;
if (need_hostnames) {
if (option_mask32 & OPT_h) {
remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa);
if (!remote_hostname) {
bb_error_msg("warning: cannot look up hostname for %s", remote_addr);
remote_hostname = (char*)"";
if (verbose || !(option_mask32 & OPT_E)) {
if (!max_per_host) /* remote_addr is not yet known */
free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa);
if (option_mask32 & OPT_h) {
free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa);
if (!remote_hostname) {
bb_error_msg("cannot look up hostname for %s", remote_addr);
remote_hostname = remote_addr;
}
}
/* Find out local IP peer connected to.
* Errors ignored (I'm not paranoid enough to imagine kernel
* which doesn't know local IP). */
if (tcp)
getsockname(0, &local.u.sa, &local.len);
/* else: for UDP it is done earlier by parent */
local_addr = xmalloc_sockaddr2dotted(&local.u.sa);
if (option_mask32 & OPT_h) {
local_hostname = preset_local_hostname;
if (!local_hostname) {
free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa);
if (!local_hostname)
bb_error_msg_and_die("cannot look up hostname for %s", local_addr);
}
/* else: local_hostname is not NULL, but is NOT malloced! */
}
}
/* Find out local IP peer connected to.
* Errors ignored (I'm not paranoid enough to imagine kernel
* which doesn't know local IP). */
if (tcp) {
local.len = sa_len;
getsockname(0, &local.u.sa, &local.len);
if (verbose) {
pid = getpid();
if (max_per_host) {
bb_error_msg("concurrency %s %u/%u",
remote_addr,
cur_per_host, max_per_host);
}
bb_error_msg((option_mask32 & OPT_h)
? "start %u %s-%s (%s-%s)"
: "start %u %s-%s",
pid,
local_addr, remote_addr,
local_hostname, remote_hostname);
}
local_addr = xmalloc_sockaddr2dotted(&local.u.sa);
if (!local_hostname) {
local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa);
if (!local_hostname)
bb_error_msg_and_die("warning: cannot look up hostname for %s"+9, local_addr);
if (!(option_mask32 & OPT_E)) {
/* setup ucspi env */
const char *proto = tcp ? "TCP" : "UDP";
/* Extract "original" destination addr:port
* from Linux firewall. Useful when you redirect
* an outbond connection to local handler, and it needs
* to know where it originally tried to connect */
if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) {
char *addr = xmalloc_sockaddr2dotted(&local.u.sa);
xsetenv_plain("TCPORIGDSTADDR", addr);
free(addr);
}
xsetenv_plain("PROTO", proto);
xsetenv_proto(proto, "LOCALADDR", local_addr);
xsetenv_proto(proto, "REMOTEADDR", remote_addr);
if (option_mask32 & OPT_h) {
xsetenv_proto(proto, "LOCALHOST", local_hostname);
xsetenv_proto(proto, "REMOTEHOST", remote_hostname);
}
//compat? xsetenv_proto(proto, "REMOTEINFO", "");
/* additional */
if (cur_per_host > 0) /* can not be true for udp */
xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host));
}
free(local_addr);
free(free_me0);
free(free_me1);
free(free_me2);
}
if (verbose) {
pid = getpid();
printf("%s: info: pid %u from %s\n", applet_name, pid, remote_addr);
if (max_per_host)
printf("%s: info: concurrency %u %s %u/%u\n",
applet_name, pid, remote_ip, cur_per_host, max_per_host);
printf("%s: info: start %u %s:%s :%s:%s\n",
applet_name, pid,
local_hostname, local_addr,
remote_hostname, remote_addr);
}
if (!(option_mask32 & OPT_E)) {
/* setup ucspi env */
const char *proto = tcp ? "TCP" : "UDP";
/* Extract "original" destination addr:port
* from Linux firewall. Useful when you redirect
* an outbond connection to local handler, and it needs
* to know where it originally tried to connect */
if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &lsa->u.sa, &lsa->len) == 0) {
char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa);
xsetenv("TCPORIGDSTADDR", addr);
free(addr);
}
xsetenv("PROTO", proto);
xsetenv_proto(proto, "LOCALADDR", local_addr);
xsetenv_proto(proto, "LOCALHOST", local_hostname);
xsetenv_proto(proto, "REMOTEADDR", remote_addr);
if (option_mask32 & OPT_h) {
xsetenv_proto(proto, "REMOTEHOST", remote_hostname);
}
xsetenv_proto(proto, "REMOTEINFO", "");
/* additional */
if (cur_per_host > 0) /* can not be true for udp */
xsetenv("TCPCONCURRENCY", utoa(cur_per_host));
}
dup2(0, 1);
xdup2(0, 1);
signal(SIGTERM, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
sig_unblock(SIGCHLD);
argv += 2;
#ifdef SSLSVD
strcpy(id, utoa(pid));
ssl_io(0, argv);

View File

@ -130,8 +130,13 @@
* tening service socket, and must accept at least one connection request
* before exiting. Such a server would normally accept and process incoming
* connection requests until a timeout.
*
* In short: "stream" can be "wait" or "nowait"; "dgram" must be "wait".
*/
/* Despite of above doc saying that dgram services must use "wait",
* "udp nowait" servers are implemented in busyboxed inetd.
* IPv6 addresses are also implemented. However, they may look ugly -
* ":::service..." means "address '::' (IPv6 wildcard addr)":"service"...
* You have to put "tcp6"/"udp6" in protocol field to select IPv6.
*/
/* Here's the scoop concerning the user[:group] feature:
@ -832,9 +837,6 @@ static NOINLINE servtab_t *parse_one_line(void)
if (sep->se_socktype == SOCK_DGRAM) {
if (sep->se_proto_no == IPPROTO_TCP)
goto parse_err;
/* "udp nowait" is a small fork bomb :) */
if (!sep->se_wait)
goto parse_err;
}
/* check if the hostname specifier is a comma separated list
@ -1195,7 +1197,7 @@ int inetd_main(int argc, char **argv)
for (;;) {
int ready_fd_cnt;
int ctrl, accepted_fd;
int ctrl, accepted_fd, new_udp_fd;
fd_set readable;
if (maxsock < 0)
@ -1220,12 +1222,43 @@ int inetd_main(int argc, char **argv)
ready_fd_cnt--;
ctrl = sep->se_fd;
accepted_fd = -1;
if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) {
ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL);
if (ctrl < 0) {
if (errno != EINTR)
bb_perror_msg("accept (for %s)", sep->se_service);
continue;
new_udp_fd = -1;
if (!sep->se_wait) {
if (sep->se_socktype == SOCK_STREAM) {
ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL);
if (ctrl < 0) {
if (errno != EINTR)
bb_perror_msg("accept (for %s)", sep->se_service);
continue;
}
}
/* "nowait" udp */
if (sep->se_socktype == SOCK_DGRAM
&& sep->se_family != AF_UNIX
) {
/* How udp "nowait" works:
* child peeks at (received and buffered by kernel) UDP packet,
* performs connect() on the socket so that it is linked only
* to this peer. But this also affects parent, because descriptors
* are shared after fork() a-la dup(). When parent performs
* select(), it will see this descriptor connected to the peer (!)
* and still readable, will act on it and mess things up
* (can create many copies of same child, etc).
* Parent must create and use new socket instead. */
new_udp_fd = socket(sep->se_family, SOCK_DGRAM, 0);
if (new_udp_fd < 0) { /* error: eat packet, forget about it */
udp_err:
recv(sep->se_fd, line, LINE_SIZE, MSG_DONTWAIT);
continue;
}
setsockopt_reuseaddr(new_udp_fd);
/* TODO: better do bind after vfork in parent,
* so that we don't have two wildcard bound sockets
* even for a brief moment? */
if (bind(new_udp_fd, &sep->se_lsa->u.sa, sep->se_lsa->len) < 0) {
close(new_udp_fd);
goto udp_err;
}
}
}
@ -1283,10 +1316,15 @@ int inetd_main(int argc, char **argv)
if (pid > 0) { /* parent */
if (sep->se_wait) {
/* tcp wait: we passed listening socket to child,
* will wait for child to terminate */
sep->se_wait = pid;
remove_fd_from_set(sep->se_fd);
/* we passed listening socket to child,
* will wait for child to terminate */
}
if (new_udp_fd >= 0) {
/* udp nowait: child connected the socket,
* we created and will use new, unconnected one */
xmove_fd(new_udp_fd, sep->se_fd);
}
restore_sigmask(&omask);
maybe_close(accepted_fd);
@ -1313,39 +1351,20 @@ int inetd_main(int argc, char **argv)
#endif
/* child */
setsid();
#if 0
/* This does not work.
* Actually, it _almost_ works. The idea behind it is: child
* can peek at (already received and buffered by kernel) UDP packet,
* and perform connect() on the socket so that it is linked only
* to this peer. But this also affects parent, because descriptors
* are shared after fork() a-la dup(). When parent returns to
* select(), it will see this descriptor attached to the peer (!)
* and likely still readable, will act on it and mess things up
* (can create many copies of same child, etc).
* If child will create new socket instead, then bind() and
* connect() it to peer's address, descriptor aliasing problem
* is solved, but first packet cannot be "transferred" to the new
* socket. It is not a problem if child can account for this,
* but our child will exec - and exec'ed program does not know
* about this "lost packet" problem! Pity... */
/* "nowait" udp[6]. Hmmm... */
if (!sep->se_wait
&& sep->se_socktype == SOCK_DGRAM
&& sep->se_family != AF_UNIX
) {
/* "nowait" udp */
if (new_udp_fd >= 0) {
len_and_sockaddr *lsa = xzalloc_lsa(sep->se_family);
/* peek at the packet and remember peer addr */
int r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT,
&lsa->u.sa, &lsa->len);
if (r >= 0)
/* make this socket "connected" to peer addr:
* only packets from this peer will be recv'ed,
* and bare write()/send() will work on it */
connect(ctrl, &lsa->u.sa, lsa->len);
if (r < 0)
goto do_exit1;
/* make this socket "connected" to peer addr:
* only packets from this peer will be recv'ed,
* and bare write()/send() will work on it */
connect(ctrl, &lsa->u.sa, lsa->len);
free(lsa);
}
#endif
/* prepare env and exec program */
pwd = getpwnam(sep->se_user);
if (pwd == NULL) {