diff options
author | ume <ume@FreeBSD.org> | 2002-08-07 17:03:14 +0000 |
---|---|---|
committer | ume <ume@FreeBSD.org> | 2002-08-07 17:03:14 +0000 |
commit | c9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9 (patch) | |
tree | 88891ceb8e2b942dccd7389e9b876a7080525b53 | |
parent | 919d00b11333feaf446ac8dbde0e32d08b6f19fb (diff) | |
download | FreeBSD-src-c9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9.zip FreeBSD-src-c9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9.tar.gz |
Add capability for limiting the maximum number of simultaneous
invocations of each service from a single IP address.
Requested by: matusita
Reviewed by: dwmalone
Tested by: matusita on snapshots.jp.FreeBSD.org
MFC after: 2 weeks
-rw-r--r-- | usr.sbin/inetd/inetd.8 | 15 | ||||
-rw-r--r-- | usr.sbin/inetd/inetd.c | 286 | ||||
-rw-r--r-- | usr.sbin/inetd/inetd.h | 18 |
3 files changed, 315 insertions, 4 deletions
diff --git a/usr.sbin/inetd/inetd.8 b/usr.sbin/inetd/inetd.8 index 939a534..1ad7d3a 100644 --- a/usr.sbin/inetd/inetd.8 +++ b/usr.sbin/inetd/inetd.8 @@ -50,6 +50,7 @@ .Op Fl a Ar address | hostname .Op Fl p Ar filename .Op Fl R Ar rate +.Op Fl s Ar maximum .Op Ar configuration file .Sh DESCRIPTION The @@ -102,6 +103,12 @@ May be overridden on a per-service basis with the Specify the maximum number of times a service can be invoked in one minute; the default is 256. A rate of 0 allows an unlimited number of invocations. +.It Fl s Ar maximum +Specify the default maximum number of +simultaneous invocations of each service from a single IP address; +the default is unlimited. +May be overridden on a per-service basis with the "max-child-per-ip" +parameter. .It Fl a Specify one specific IP address to bind to. Alternatively, a hostname can be specified, @@ -155,7 +162,7 @@ fields of the configuration file are as follows: service name socket type protocol -{wait|nowait}[/max-child[/max-connections-per-ip-per-minute]] +{wait|nowait}[/max-child[/max-connections-per-ip-per-minute[/max-child-per-ip]]] user[:group][/login-class] server program server program arguments @@ -396,6 +403,12 @@ outstanding child processes. Once the maximum is reached, further connections from this IP address will be dropped until the end of the minute. +In addition, you can specify the maximum number of simultaneous +invocations of each service from a single IP address by appending a +.Dq / +followed by the number to the maximum number of outstanding child +processes. Once the maximum is reached, further connections from this +IP address will be dropped. .Pp The .Em user diff --git a/usr.sbin/inetd/inetd.c b/usr.sbin/inetd/inetd.c index 163e0df..59a6dfe 100644 --- a/usr.sbin/inetd/inetd.c +++ b/usr.sbin/inetd/inetd.c @@ -196,6 +196,12 @@ __FBSDID("$FreeBSD$"); < 0 = no limit */ #endif +#ifndef MAXPERIP +#define MAXPERIP -1 /* maximum number of this service + from a single remote address, + < 0 = no limit */ +#endif + #ifndef TOOMANY #define TOOMANY 256 /* don't start more than TOOMANY */ #endif @@ -229,6 +235,16 @@ void setup(struct servtab *); void ipsecsetup(struct servtab *); #endif void unregisterrpc(register struct servtab *sep); +static struct conninfo *search_conn(struct servtab *sep, int ctrl); +static int room_conn(struct servtab *sep, struct conninfo *conn); +static void addchild_conn(struct conninfo *conn, pid_t pid); +static void reapchild_conn(pid_t pid); +static void free_conn(struct conninfo *conn); +static void resize_conn(struct servtab *sep, int maxperip); +static void free_connlist(struct servtab *sep); +static void free_proc(struct procinfo *); +static struct procinfo *search_proc(pid_t pid, int add); +static int hashval(char *p, int len); int allow_severity; int deny_severity; @@ -243,6 +259,7 @@ int timingout; int toomany = TOOMANY; int maxchild = MAXCHILD; int maxcpm = MAXCPM; +int maxperip = MAXPERIP; struct servent *sp; struct rpcent *rpc; char *hostname = NULL; @@ -269,6 +286,8 @@ const char *pid_file = _PATH_INETDPID; struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf; +static LIST_HEAD(, procinfo) proctable[PERIPSIZE]; + int getvalue(const char *arg, int *value, const char *whine) { @@ -314,10 +333,11 @@ main(int argc, char **argv) struct addrinfo hints, *res; const char *servname; int error; + struct conninfo *conn; openlog("inetd", LOG_PID | LOG_NOWAIT | LOG_PERROR, LOG_DAEMON); - while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:")) != -1) + while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1) switch(ch) { case 'd': debug = 1; @@ -344,6 +364,10 @@ main(int argc, char **argv) case 'p': pid_file = optarg; break; + case 's': + getvalue(optarg, &maxperip, + "-s %s: bad value for maximum children per source address"); + break; case 'w': wrap_ex++; break; @@ -454,6 +478,9 @@ main(int argc, char **argv) } } + for (i = 0; i < PERIPSIZE; ++i) + LIST_INIT(&proctable[i]); + if (!no_v4bind) { udpconf = getnetconfigent("udp"); tcpconf = getnetconfigent("tcp"); @@ -563,6 +590,8 @@ main(int argc, char **argv) n--; if (debug) warnx("someone wants %s", sep->se_service); + dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep); + conn = NULL; if (sep->se_accept && sep->se_socktype == SOCK_STREAM) { i = 1; if (ioctl(sep->se_fd, FIONBIO, &i) < 0) @@ -590,6 +619,12 @@ main(int argc, char **argv) close(ctrl); continue; } + if (dofork && + (conn = search_conn(sep, ctrl)) != NULL && + !room_conn(sep, conn)) { + close(ctrl); + continue; + } } else ctrl = sep->se_fd; if (log && !ISWRAP(sep)) { @@ -627,7 +662,6 @@ main(int argc, char **argv) * fork and anything we're wrapping (as wrapping might * block or use hosts_options(5) twist). */ - dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep); if (dofork) { if (sep->se_count++ == 0) (void)gettimeofday(&sep->se_time, (struct timezone *)NULL); @@ -647,6 +681,7 @@ main(int argc, char **argv) sep->se_socktype == SOCK_STREAM) close(ctrl); close_sep(sep); + free_conn(conn); sigsetmask(0L); if (!timingout) { timingout = 1; @@ -662,12 +697,15 @@ main(int argc, char **argv) if (sep->se_accept && sep->se_socktype == SOCK_STREAM) close(ctrl); + free_conn(conn); sigsetmask(0L); sleep(1); continue; } - if (pid) + if (pid) { + addchild_conn(conn, pid); addchild(sep, pid); + } sigsetmask(0L); if (pid == 0) { if (dofork) { @@ -895,6 +933,7 @@ reapchild(void) sep->se_server, pid, status); break; } + reapchild_conn(pid); } } @@ -908,6 +947,7 @@ void config(void) { struct servtab *sep, *new, **sepp; + struct conninfo *conn; long omask; int new_nomapped; @@ -970,6 +1010,8 @@ config(void) sep->se_maxchild = new->se_maxchild; sep->se_numchild = new->se_numchild; sep->se_maxcpm = new->se_maxcpm; + resize_conn(sep, new->se_maxperip); + sep->se_maxperip = new->se_maxperip; sep->se_bi = new->se_bi; /* might need to turn on or off service now */ if (sep->se_fd >= 0) { @@ -1546,6 +1588,7 @@ getconfigent(void) #ifdef INET6 int v6bind = 0; #endif + int i; more: while ((cp = nextline(fconfig)) != NULL) { @@ -1786,6 +1829,7 @@ more: } sep->se_maxchild = -1; sep->se_maxcpm = -1; + sep->se_maxperip = -1; if ((s = strchr(arg, '/')) != NULL) { char *eptr; u_long val; @@ -1804,6 +1848,8 @@ more: sep->se_maxchild = val; if (*eptr == '/') sep->se_maxcpm = strtol(eptr + 1, &eptr, 10); + if (*eptr == '/') + sep->se_maxperip = strtol(eptr + 1, &eptr, 10); /* * explicitly do not check for \0 for future expansion / * backwards compatibility @@ -1861,6 +1907,8 @@ more: sep->se_bi = bi; } else sep->se_bi = NULL; + if (sep->se_maxperip < 0) + sep->se_maxperip = maxperip; if (sep->se_maxcpm < 0) sep->se_maxcpm = maxcpm; if (sep->se_maxchild < 0) { /* apply default max-children */ @@ -1890,6 +1938,8 @@ more: } while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; + for (i = 0; i < PERIPSIZE; ++i) + LIST_INIT(&sep->se_conn[i]); #ifdef IPSEC sep->se_policy = policy ? newstr(policy) : NULL; #endif @@ -1920,6 +1970,7 @@ freeconfig(struct servtab *cp) for (i = 0; i < MAXARGV; i++) if (cp->se_argv[i]) free(cp->se_argv[i]); + free_connlist(cp); #ifdef IPSEC if (cp->se_policy) free(cp->se_policy); @@ -2253,3 +2304,232 @@ cpmip(const struct servtab *sep, int ctrl) } return(r); } + +static struct conninfo * +search_conn(struct servtab *sep, int ctrl) +{ + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + struct conninfo *conn; + int hv; + char pname[NI_MAXHOST], pname2[NI_MAXHOST]; + + if (sep->se_maxperip <= 0) + return NULL; + + /* + * If getpeername() fails, just let it through (if logging is + * enabled the condition is caught elsewhere) + */ + if (getpeername(ctrl, (struct sockaddr *)&ss, &sslen) != 0) + return NULL; + + switch (ss.ss_family) { + case AF_INET: + hv = hashval((char *)&((struct sockaddr_in *)&ss)->sin_addr, + sizeof(struct in_addr)); + break; +#ifdef INET6 + case AF_INET6: + hv = hashval((char *)&((struct sockaddr_in6 *)&ss)->sin6_addr, + sizeof(struct in6_addr)); + break; +#endif + default: + /* + * Since we only support AF_INET and AF_INET6, just + * let other than AF_INET and AF_INET6 through. + */ + return NULL; + } + + if (getnameinfo((struct sockaddr *)&ss, sslen, pname, sizeof(pname), + NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID) != 0) + return NULL; + + LIST_FOREACH(conn, &sep->se_conn[hv], co_link) { + if (getnameinfo((struct sockaddr *)&conn->co_addr, + conn->co_addr.ss_len, pname2, sizeof(pname2), NULL, 0, + NI_NUMERICHOST | NI_WITHSCOPEID) == 0 && + strcmp(pname, pname2) == 0) + break; + } + + if (conn == NULL) { + if ((conn = malloc(sizeof(struct conninfo))) == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + conn->co_proc = malloc(sep->se_maxperip * sizeof(*conn->co_proc)); + if (conn->co_proc == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + memcpy(&conn->co_addr, (struct sockaddr *)&ss, sslen); + conn->co_numchild = 0; + LIST_INSERT_HEAD(&sep->se_conn[hv], conn, co_link); + } + + /* + * Since a child process is not invoked yet, we cannot + * determine a pid of a child. So, co_proc and co_numchild + * should be filled leter. + */ + + return conn; +} + +static int +room_conn(struct servtab *sep, struct conninfo *conn) +{ + char pname[NI_MAXHOST]; + + if (conn->co_numchild >= sep->se_maxperip) { + getnameinfo((struct sockaddr *)&conn->co_addr, + conn->co_addr.ss_len, pname, sizeof(pname), NULL, 0, + NI_NUMERICHOST | NI_WITHSCOPEID); + syslog(LOG_ERR, "%s from %s exceeded counts (limit %d)", + sep->se_service, pname, sep->se_maxperip); + return 0; + } + return 1; +} + +static void +addchild_conn(struct conninfo *conn, pid_t pid) +{ + struct procinfo *proc; + + if (conn == NULL) + return; + + if ((proc = search_proc(pid, 1)) != NULL) { + if (proc->pr_conn != NULL) { + syslog(LOG_ERR, + "addchild_conn: child already on process list"); + exit(EX_OSERR); + } + proc->pr_conn = conn; + } + + conn->co_proc[conn->co_numchild++] = proc; +} + +static void +reapchild_conn(pid_t pid) +{ + struct procinfo *proc; + struct conninfo *conn; + int i; + + if ((proc = search_proc(pid, 0)) == NULL) + return; + if ((conn = proc->pr_conn) == NULL) + return; + for (i = 0; i < conn->co_numchild; ++i) + if (conn->co_proc[i] == proc) { + conn->co_proc[i] = conn->co_proc[--conn->co_numchild]; + break; + } + free_proc(proc); + free_conn(conn); +} + +static void +resize_conn(struct servtab *sep, int maxperip) +{ + struct conninfo *conn; + int i, j; + + if (sep->se_maxperip <= 0) + return; + if (maxperip <= 0) { + free_connlist(sep); + return; + } + for (i = 0; i < PERIPSIZE; ++i) { + LIST_FOREACH(conn, &sep->se_conn[i], co_link) { + for (j = maxperip; j < conn->co_numchild; ++j) + free_proc(conn->co_proc[j]); + conn->co_proc = realloc(conn->co_proc, + maxperip * sizeof(*conn->co_proc)); + if (conn->co_proc == NULL) { + syslog(LOG_ERR, "realloc: %m"); + exit(EX_OSERR); + } + if (conn->co_numchild > maxperip) + conn->co_numchild = maxperip; + } + } +} + +static void +free_connlist(struct servtab *sep) +{ + struct conninfo *conn; + int i, j; + + for (i = 0; i < PERIPSIZE; ++i) { + while ((conn = LIST_FIRST(&sep->se_conn[i])) != NULL) { + for (j = 0; j < conn->co_numchild; ++j) + free_proc(conn->co_proc[j]); + conn->co_numchild = 0; + free_conn(conn); + } + } +} + +static void +free_conn(struct conninfo *conn) +{ + if (conn == NULL) + return; + if (conn->co_numchild <= 0) { + LIST_REMOVE(conn, co_link); + free(conn->co_proc); + free(conn); + } +} + +static struct procinfo * +search_proc(pid_t pid, int add) +{ + struct procinfo *proc; + int hv; + + hv = hashval((char *)&pid, sizeof(pid)); + LIST_FOREACH(proc, &proctable[hv], pr_link) { + if (proc->pr_pid == pid) + break; + } + if (proc == NULL && add) { + if ((proc = malloc(sizeof(struct procinfo))) == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + proc->pr_pid = pid; + proc->pr_conn = NULL; + LIST_INSERT_HEAD(&proctable[hv], proc, pr_link); + } + return proc; +} + +static void +free_proc(struct procinfo *proc) +{ + if (proc == NULL) + return; + LIST_REMOVE(proc, pr_link); + free(proc); +} + +static int +hashval(char *p, int len) +{ + int i, hv = 0xABC3D20F; + + for (i = 0; i < len; ++i, ++p) + hv = (hv << 5) ^ (hv >> 23) ^ *p; + hv = (hv ^ (hv >> 16)) & (PERIPSIZE - 1); + return hv; +} diff --git a/usr.sbin/inetd/inetd.h b/usr.sbin/inetd/inetd.h index 1e0f088..3293364 100644 --- a/usr.sbin/inetd/inetd.h +++ b/usr.sbin/inetd/inetd.h @@ -36,6 +36,7 @@ #include <sys/time.h> #include <sys/socket.h> #include <sys/un.h> +#include <sys/queue.h> #include <netinet/in.h> @@ -54,6 +55,21 @@ #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) #define ISTTCP(sep) ((sep)->se_type == TTCP_TYPE) +struct procinfo { + LIST_ENTRY(procinfo) pr_link; + pid_t pr_pid; /* child pid */ + struct conninfo *pr_conn; +}; + +struct conninfo { + LIST_ENTRY(conninfo) co_link; + struct sockaddr_storage co_addr; /* source address */ + int co_numchild; /* current number of children */ + struct procinfo **co_proc; /* array of child proc entry */ +}; + +#define PERIPSIZE 256 + struct servtab { char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ @@ -105,6 +121,8 @@ struct servtab { u_int se_nomapped : 1; u_int se_reset : 1; } se_flags; + int se_maxperip; /* max number of children per src */ + LIST_HEAD(, conninfo) se_conn[PERIPSIZE]; }; #define se_nomapped se_flags.se_nomapped |