/* -*-c-*-
- *
- * $Id: socket.c,v 1.4 1999/12/22 15:44:25 mdw Exp $
*
* Socket source and target definitions
*
* (c) 1999 Straylight/Edgeware
*/
-/*----- Licensing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
*
- * This file is part of the `fw' port forwarder.
+ * This file is part of the `fwd' port forwarder.
*
- * `fw' is free software; you can redistribute it and/or modify
+ * `fwd' is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
- *
- * `fw' is distributed in the hope that it will be useful,
+ *
+ * `fwd' is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
- * along with `fw'; if not, write to the Free Software Foundation,
+ * along with `fwd'; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/*----- Revision history --------------------------------------------------*
- *
- * $Log: socket.c,v $
- * Revision 1.4 1999/12/22 15:44:25 mdw
- * Fix log message.
- *
- * Revision 1.3 1999/10/22 22:48:36 mdw
- * New connection options: unlimited concurrent connections, and one-shot
- * listening sockets.
- *
- * Revision 1.2 1999/07/27 18:30:53 mdw
- * Various minor portability fixes.
- *
- * Revision 1.1 1999/07/26 23:33:32 mdw
- * New sources and targets.
- *
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include "config.h"
-
-#include <ctype.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-
-#include <mLib/alloc.h>
-#include <mLib/conn.h>
-#include <mLib/dstr.h>
-#include <mLib/fdflags.h>
-#include <mLib/sel.h>
-#include <mLib/sub.h>
-
-#include "addr.h"
-#include "conf.h"
-#include "endpt.h"
-#include "fw.h"
-#include "scan.h"
-#include "socket.h"
-#include "target.h"
-
-#include "inet.h"
-#include "un.h"
+#include "fwd.h"
/*----- Data structures ---------------------------------------------------*/
typedef struct ssource_opts {
unsigned opt;
unsigned conn;
+ unsigned listen;
+ unsigned naccept;
} ssource_opts;
-static ssource_opts ssgo = { 256, 0 };
+static ssource_opts ssgo = { 256, 0, 5, 1 };
#define SOCKOPT_LIMIT 0u
#define SOCKOPT_NOLIMIT 1u
typedef struct stept {
endpt e;
conn c;
- const char *desc;
+ char *desc;
} stept;
/* --- Socket source endpoint --- */
ssource *s;
} ssept;
-#define SKF_CONN 16u
-#define SKF_BROKEN 32u
-
/*----- Protocol table ----------------------------------------------------*/
static addr_ops *addrs[] = { &inet_ops, &un_ops, 0 };
/*----- Other persistent variables ----------------------------------------*/
-static addr_opts gao = { 0 };
+static addr_opts gsao = { 0 }, gtao = { 0 };
/*----- Parsing address types ---------------------------------------------*/
{
ssept *ee = (ssept *)e;
- if (ee->s->o.opt == SOCKOPT_LIMIT) {
+ if ((ee->s->s.f&SF_ACTIVE) && ee->s->o.opt == SOCKOPT_LIMIT) {
ee->s->o.conn++;
if (ee->s->o.conn == 1)
ss_listen(ee->s);
{
stept *ee = (stept *)e;
- if (ee->e.f & EPF_PENDING) {
- if (ee->e.f & SKF_CONN)
- conn_kill(&ee->c);
- } else {
+ if (ee->e.f & EPF_PENDING)
+ conn_kill(&ee->c);
+ else {
REFFD_DEC(ee->e.in);
REFFD_DEC(ee->e.out);
}
+ xfree(ee->desc);
fw_dec();
DESTROY(ee);
}
-/* --- @stept_go@ --- *
+/* --- @starget_connected@ --- *
*
* Arguments: @int fd@ = file descriptor now ready for use
* @void *p@ = pointer to an endpoint structure
* Use: Handles successful connection of the target endpoint.
*/
-static void stept_go(int fd, void *p)
+void starget_connected(int fd, void *p)
{
stept *e = p;
- /* --- Complicated and subtle --- *
- *
- * This code interacts quite closely with @starget_create@, mainly through
- * flags in the endpoint block.
- *
- * If the connection failed, I log a message (that's easy enough). The
- * behaviour then depends on whether the endpoints have been joined yet.
- * If not, I set @SKF_BROKEN@ and return, so that @starget_create@ can
- * clean up the mess and return an immediate failure. If they have, I kill
- * the connection and everything ought to work.
- *
- * If the connection worked, I clear @EPF_PENDING@ (as expected, because
- * my endpoint is now ready), and @SKF_CONN@ (to let @starget_create@ know
- * that the connection is already going). Then, only if this isn't the
- * first attempt, I rejoin this endpoint to its partner.
- */
-
if (fd == -1) {
fw_log(-1, "[%s] connection failed: %s", e->desc, strerror(errno));
- e->e.f &= ~SKF_CONN;
- if (e->e.f & EPF_PENDING)
- endpt_kill(&e->e);
- else
- e->e.f |= SKF_BROKEN;
+ endpt_kill(&e->e);
} else {
reffd *r = reffd_init(fd);
+ int opt = 1;
REFFD_INC(r);
+ fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
e->e.in = e->e.out = r;
- e->e.f &= ~(EPF_PENDING | SKF_CONN);
+ e->e.f &= ~EPF_PENDING;
if (e->e.other)
- endpt_join(&e->e, e->e.other);
+ endpt_join(&e->e, e->e.other, 0);
}
}
ssource *ss = (ssource *)s;
ssource_opts *sso = ss ? &ss->o : &ssgo;
- CONF_BEGIN(sc, "socket", "socket")
+ CONF_BEGIN(sc, "socket", "socket source")
/* --- Make sure the next token is a word --- */
CONF_ACCEPT;
}
+ if (strcmp(sc->d.buf, "listen") == 0) {
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD || !isdigit((unsigned char)sc->d.buf[0]))
+ error(sc, "parse error, expected number");
+ sso->listen = atoi(sc->d.buf);
+ if (sso->listen == 0)
+ error(sc, "argument of `listen' must be positive");
+ token(sc);
+ CONF_ACCEPT;
+ }
+
+ if (strcmp(sc->d.buf, "accept") == 0 ||
+ strcmp(sc->d.buf, "accept-count") == 0) {
+ token(sc);
+ if (sc->t == '=')
+ token(sc);
+ if (sc->t != CTOK_WORD)
+ error(sc, "parse error, expected `unlimited' or number");
+ else if (isdigit((unsigned char)sc->d.buf[0])) {
+ sso->naccept = atoi(sc->d.buf);
+ if (sso->naccept == 0)
+ error(sc, "argument of `accept-count' must be positive");
+ } else {
+ sso->naccept = 0;
+ conf_enum(sc, "unlimited,infinite",
+ ENUM_ABBREV, "`accept-count' option");
+ }
+ token(sc);
+ CONF_ACCEPT;
+ }
+
if (strcmp(sc->d.buf, "logging") == 0 ||
strcmp(sc->d.buf, "log") == 0) {
- addr_opts *ao = ss ? ss->ao : &gao;
+ addr_opts *ao = ss ? ss->ao : &gsao;
token(sc);
if (sc->t == '=')
token(sc);
/* --- Pass the option around the various address types --- */
if (ss) {
- if (ss->a->ops->option && ss->a->ops->option(sc, ss ? ss->ao : 0))
+ if (ss->a->ops->option && ss->a->ops->option(sc, ss->ao, ADDR_SRC))
CONF_ACCEPT;
} else {
addr_ops **a;
for (a = addrs; *a; a++) {
- if ((*a)->option && (*a)->option(sc, 0))
+ if ((*a)->option && (*a)->option(sc, 0, ADDR_GLOBAL))
CONF_ACCEPT;
}
}
(void)(conf_prefix(sc, "socket") || conf_prefix(sc, "sk"));
ss = CREATE(ssource);
ss->s.ops = &ssource_ops;
+ ss->s.ref = 1;
+ ss->s.f = 0;
ss->s.desc = 0;
ss->t = 0;
ss->a = getaddr(sc, ADDR_SRC);
- if (ss->a->ops->initopts)
- ss->ao = ss->a->ops->initopts();
+ if (ss->a->ops->initsrcopts)
+ ss->ao = ss->a->ops->initsrcopts();
else
ss->ao = CREATE(addr_opts);
- *ss->ao = gao;
+ *ss->ao = gsao;
ss->o = ssgo;
return (&ss->s);
}
ssept *e;
endpt *ee;
reffd *r;
+ int acceptp = 1;
+ unsigned i = 0;
- /* --- Make the file descriptor --- */
-
- {
- int opt = 1;
- if ((r = ss->a->ops->accept(fd, ss->ao, ss->s.desc)) == 0)
- return;
- setsockopt(r->fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
- fdflags(r->fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
- }
+ while (acceptp) {
- /* --- Make an endpoint --- */
+ /* --- Make the file descriptor --- */
- e = CREATE(ssept);
- e->e.ops = &ssept_ops;
- e->e.other = 0;
- e->e.f = EPF_FILE;
- e->e.t = 0;
- e->e.in = e->e.out = r;
- e->s = ss;
- REFFD_INC(r);
+ {
+ int opt = 1;
+ if ((r = ss->a->ops->accept(fd, ss->ao, ss->s.desc)) == 0)
+ return;
+ setsockopt(r->fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+ fdflags(r->fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ }
- /* --- Obtain the target endpoint and let rip --- */
+ /* --- Make an endpoint --- */
- if ((ee = ss->t->ops->create(ss->t, ss->s.desc)) == 0) {
- REFFD_DEC(r);
- REFFD_DEC(r);
- DESTROY(e);
- return;
- }
- fw_inc();
+ e = CREATE(ssept);
+ e->e.ops = &ssept_ops;
+ e->e.other = 0;
+ e->e.f = EPF_FILE;
+ e->e.t = 0;
+ e->e.in = e->e.out = r;
+ e->s = ss;
+ REFFD_INC(r);
- /* --- Remove the listening socket if necessary --- */
+ /* --- Obtain the target endpoint and let rip --- */
- switch (ss->o.opt) {
- case SOCKOPT_LIMIT:
- ss->o.conn--;
- if (!ss->o.conn) {
- if (!(ss->ao->f & ADDRF_NOLOG))
- fw_log(-1, "[%s] maximum connections reached", ss->s.desc);
+ if ((ee = ss->t->ops->create(ss->t, ss->s.desc)) == 0) {
+ REFFD_DEC(r);
+ REFFD_DEC(r);
+ DESTROY(e);
+ return;
+ }
+ fw_inc();
+
+ /* --- Note that we've done one --- */
+
+ i++;
+ if (i >= ss->o.naccept)
+ acceptp = 0;
+
+ /* --- Remove the listening socket if necessary --- */
+
+ switch (ss->o.opt) {
+ case SOCKOPT_LIMIT:
+ ss->o.conn--;
+ if (!ss->o.conn) {
+ if (!(ss->ao->f & ADDRF_NOLOG))
+ fw_log(-1, "[%s] maximum connections reached", ss->s.desc);
+ sel_rmfile(&ss->r);
+ close(ss->r.fd);
+ if (ss->a->ops->unbind)
+ ss->a->ops->unbind(ss->a);
+ acceptp = 0;
+ }
+ break;
+ case SOCKOPT_NOLIMIT:
+ break;
+ case SOCKOPT_ONESHOT:
sel_rmfile(&ss->r);
close(ss->r.fd);
if (ss->a->ops->unbind)
ss->a->ops->unbind(ss->a);
- }
- break;
- case SOCKOPT_NOLIMIT:
- break;
- case SOCKOPT_ONESHOT:
- sel_rmfile(&ss->r);
- close(ss->r.fd);
- if (ss->a->ops->unbind)
- ss->a->ops->unbind(ss->a);
- ssource_destroy(&ss->s);
- break;
- }
+ source_dec(&ss->s);
+ acceptp = 0;
+ break;
+ }
- /* --- Let everything else happen --- */
+ /* --- Let everything else happen --- */
- endpt_join(&e->e, ee);
+ endpt_join(&e->e, ee, ss->s.desc);
+ }
}
/* --- @ss_listen@ --- *
static void ss_listen(ssource *ss)
{
- gen_addr *ga = (gen_addr *)ss->a;
int fd;
+ int opt = 1;
if (!(ss->ao->f & ADDRF_NOLOG))
fw_log(-1, "[%s] reattaching listener", ss->s.desc);
/* --- Make the socket --- */
- if ((fd = socket(ga->a.ops->pf, SOCK_STREAM, 0)) < 0) {
+ if ((fd = ss->a->ops->bind(ss->a, ss->ao)) < 0) {
fw_log(-1, "[%s] couldn't create socket: %s",
ss->s.desc, strerror(errno));
goto fail_0;
}
- /* --- Set it to allow address reuse --- */
-
- {
- int opt = 1;
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
- fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
- }
-
- /* --- Bind it to the right port --- */
-
- if (bind(fd, &ga->sa, ga->a.sz)) {
- fw_log(-1, "[%s] couldn't bind socket: %s", ss->s.desc, strerror(errno));
- goto fail_1;
- }
- if (ga->a.ops->bound)
- ga->a.ops->bound(&ga->a, ss->ao);
-
/* --- Set it to listen for connections --- */
- if (listen(fd, 5)) {
+ setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+ fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ if (listen(fd, ss->o.listen)) {
fw_log(-1, "[%s] couldn't listen on socket: %s",
ss->s.desc, strerror(errno));
goto fail_1;
close(fd);
fail_0:
ss->o.conn = 0;
- ssource_destroy(&ss->s);
+ source_dec(&ss->s);
}
/* --- @attach@ --- */
{
ssource *ss = (ssource *)s;
int fd;
+ int opt = 1;
- ss->t = t;
+ ss->t = t; target_inc(t);
/* --- Initialize the description string --- */
dstr_destroy(&d);
}
- /* --- Initialize the socket for listening --- */
-
- {
- gen_addr *ga = (gen_addr *)ss->a;
-
- /* --- Make the socket --- */
+ /* --- Confirm the address --- */
- if ((fd = socket(ga->a.ops->pf, SOCK_STREAM, 0)) < 0)
- error(sc, "couldn't create socket: %s", strerror(errno));
-
- /* --- Set it to allow address reuse --- */
-
- {
- int opt = 1;
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
- fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
- }
+ if (ss->a->ops->confirm)
+ ss->a->ops->confirm(ss->a, ADDR_SRC, ss->ao);
- /* --- Bind it to the right port --- */
+ /* --- Initialize the socket for listening --- */
- if (bind(fd, &ga->sa, ga->a.sz))
- error(sc, "couldn't bind to %s: %s", ss->s.desc, strerror(errno));
- if (ga->a.ops->bound)
- ga->a.ops->bound(&ga->a, ss->ao);
+ if ((fd = ss->a->ops->bind(ss->a, ss->ao)) < 0)
+ error(sc, "couldn't bind socket `%s': %s", ss->s.desc, strerror(errno));
- /* --- Set it to listen for connections --- */
+ /* --- Set it to listen for connections --- */
- if (listen(fd, 5))
- error(sc, "couldn't listen on socket: %s", strerror(errno));
+ setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
+ fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ if (listen(fd, ss->o.listen)) {
+ error(sc, "couldn't listen on socket `%s': %s",
+ ss->s.desc, strerror(errno));
}
/* --- We're ready to go now --- */
fw_inc();
}
-/* --- @destroy@ --- */
+/* --- @shutdown@ --- */
-static void ssource_destroy(source *s)
+static void ssource_shutdown(source *s)
{
ssource *ss = (ssource *)s;
- if (ss->o.conn) {
+ if (ss->o.conn || ss->o.opt != SOCKOPT_LIMIT) {
sel_rmfile(&ss->r);
close(ss->r.fd);
if (ss->a->ops->unbind)
ss->a->ops->unbind(ss->a);
}
- if (ss->a->ops->freeopts)
- ss->a->ops->freeopts(ss->ao);
+ if (ss->a->ops->freesrcopts)
+ ss->a->ops->freesrcopts(ss->ao);
else
DESTROY(ss->ao);
- /* free(ss->s.desc); */
ss->a->ops->destroy(ss->a);
- ss->t->ops->destroy(ss->t);
source_remove(&ss->s);
+ target_dec(ss->t);
+ fw_dec();
+}
+
+/* --- @destroy@ --- */
+
+static void ssource_destroy(source *s)
+{
+ ssource *ss = (ssource *)s;
+
+ xfree(ss->s.desc);
DESTROY(ss);
fw_dec();
}
source_ops ssource_ops = {
"socket",
- ssource_option, ssource_read, ssource_attach, ssource_destroy
+ ssource_option, ssource_read, ssource_attach, ssource_shutdown, ssource_destroy
};
/*----- Target definition -------------------------------------------------*/
+/* --- @options@ --- */
+
+static int starget_option(target *t, scanner *sc)
+{
+ starget *st = (starget *)t;
+
+ CONF_BEGIN(sc, "starget", "socket target")
+
+ /* --- Pass the option around the various address types --- */
+
+ if (st) {
+ if (st->a->ops->option && st->a->ops->option(sc, st->ao, ADDR_DEST))
+ CONF_ACCEPT;
+ }
+ /* We'd have done it already if it was global */
+
+ /* --- Done --- */
+
+ CONF_END;
+}
+
/* --- @read@ --- */
static target *starget_read(scanner *sc)
(void)(conf_prefix(sc, "socket") || conf_prefix(sc, "sk"));
st = CREATE(starget);
st->t.ops = &starget_ops;
+ st->t.ref = 1;
st->a = getaddr(sc, ADDR_DEST);
+ if (st->a->ops->inittargopts)
+ st->ao = st->a->ops->inittargopts();
+ else {
+ st->ao = CREATE(addr_opts);
+ *st->ao = gtao;
+ }
dstr_puts(&d, "socket.");
st->a->ops->print(st->a, ADDR_DEST, &d);
st->t.desc = xstrdup(d.buf);
return (&st->t);
}
-/* --- @create@ --- *
- *
- * Arguments: @target *t@ = pointer to target
- * @const char *desc@ = description of connection
- *
- * Returns: Pointer to a created endpoint.
- *
- * Use: Generates a target endpoint for communication.
- */
+/* --- @confirm@ --- */
+
+static void starget_confirm(target *t)
+{
+ starget *st = (starget *)t;
+
+ if (st->a->ops->confirm)
+ st->a->ops->confirm(st->a, ADDR_DEST, st->ao);
+}
+
+/* --- @create@ --- */
static endpt *starget_create(target *t, const char *desc)
{
starget *st = (starget *)t;
stept *e = CREATE(stept);
- int fd;
- gen_addr *ga = (gen_addr *)st->a;
- int opt;
- if ((fd = socket(st->a->ops->pf, SOCK_STREAM, 0)) < 0)
- return (0);
- setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
- fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
e->e.ops = &stept_ops;
e->e.other = 0;
- e->e.f = EPF_FILE | SKF_CONN;
+ e->e.f = EPF_FILE | EPF_PENDING;
e->e.t = 0;
- e->desc = desc;
-
- /* --- Pay attention --- *
- *
- * This bit is quite subtle. The connect can succeed or fail later: that's
- * fine. The problem comes if it makes its mind up right now. The flag
- * @SKF_CONN@ signifies that I'm trying to connect. I set it up to begin
- * with and @stept_go@ turns it off when it's done: @stept_close@ uses it
- * to decide whether to kill the connection. The flag @EPF_PENDING@ is
- * only set after @conn_init@ returns and @SKF_CONN@ is still set (meaning
- * that the connection is still in progress). That's used to let
- * @stept_go@ know whether to kill the other endpoint. The flag
- * @SKF_BROKEN@ is used to signify an immediate failure.
- */
-
- conn_init(&e->c, sel, fd, &ga->sa, ga->a.sz, stept_go, e);
- if (e->e.f & SKF_BROKEN) {
+ e->desc = xstrdup(desc);
+ if (st->a->ops->connect(st->a, st->ao, &e->c, &e->e)) {
+ fw_log(-1, "[%s] couldn't connect: %s", e->desc, strerror(errno));
DESTROY(e);
return (0);
}
- if (e->e.f & SKF_CONN)
- e->e.f |= EPF_PENDING;
fw_inc();
return (&e->e);
}
static void starget_destroy(target *t)
{
starget *st = (starget *)t;
+ if (st->a->ops->freetargopts)
+ st->a->ops->freetargopts(st->ao);
+ else
+ DESTROY(st->ao);
st->a->ops->destroy(st->a);
- /* free(st->t.desc); */
+ xfree(st->t.desc);
DESTROY(st);
}
target_ops starget_ops = {
"socket",
- 0, starget_read, starget_create, starget_destroy
+ starget_option, starget_read, starget_confirm,
+ starget_create, starget_destroy
};
/*----- That's all, folks -------------------------------------------------*/