Download | Plain Text | No Line Numbers


  1. --- /dev/null 1970-01-01 01:00:00.000000000 +0100
  2. +++ CHANGES.tcpserver-limits-patch 2006-01-26 18:59:34.000000000 +0100
  3. @@ -0,0 +1,28 @@
  4. +20060126 Added support for "always reject" (setting MAXCONNIP and/or
  5. +MAXCONNC to 0) and fixing a bug when sometimes DIEMSG would not be
  6. +shown (by Mark Powell)
  7. +
  8. +20050903 Added support for Solaris (by Jorge Valdes).
  9. +Moved MAXLOAD code to getprocla().
  10. +Modified documentation a little to accommodate recent changes.
  11. +
  12. +20050130 reinstated /proc/loadavg support for those compiling on Linux
  13. +with dietlibc (see #define NO_GETLOADAVG at top of tcpserver.c).
  14. +Also, we now compile on 64bit platforms (we avoid including unistd.h if
  15. +using getloadavg(3), so we don't conflict with readwrite.h header file)
  16. +Needed if your compile was breaking with:
  17. +readwrite.h:4: error: syntax error before "read"
  18. +readwrite.h:4: warning: data definition has no type or storage class
  19. +SUMMARY: If 20040725 worked for you, there is no reason to upgrade
  20. +(no new features of bugfixes)
  21. +
  22. +20040725 adds a sleep(1) before terminating (to prevent too high load from
  23. +many rapid fork()/exit() calls. It also changes the method for checking
  24. +system load to getloadavg(3) instead of parsing /proc/loadavg, therefore
  25. +making it working on *BSD and other non-Linux systems in addition to Linux.
  26. +It also adds DIEMSG="xxx" support.
  27. +
  28. +20040327 fixes a bug in 20040124 related to MAXLOAD (it would not work
  29. +correctly when load was higher than 10.00)
  30. +
  31. +
  32. --- /dev/null 1970-01-01 01:00:00.000000000 +0100
  33. +++ README.tcpserver-limits-patch 2006-01-26 18:56:02.000000000 +0100
  34. @@ -0,0 +1,136 @@
  35. +See CHANGES.tcpserver-limits-patch for changes summary.
  36. +
  37. +:::COMPILING:::
  38. +
  39. + For MAXLOAD variable to have effect, you have 3 options:
  40. +
  41. +(1) By default the patch assumes that you have working getloadavg(3)
  42. + (most modern UN*Xoids have, including Linux and FreeBSD). No changes
  43. + are needed to standard ucspi-tcp compilation procedures.
  44. +
  45. +(2) If you have Solaris system:
  46. + - conf-cc needs to be modified to include "-DSOLARIS"
  47. + - Makefile needs to be modified so that tcpserver links with "-lkstat"
  48. + (add "-lkstat" to the end of line 748 after "`cat socket.lib`")
  49. +
  50. +(3) If you have a non-Solaris system without getloadavg(3), but with
  51. + readable '/proc/loadavg' (in linux-2.4.x/2.6.x syntax); for example
  52. + if you're compiling on Linux system with dietlibc:
  53. + - conf-cc needs to be modified to include "-DNO_GETLOADAVG"
  54. +
  55. +
  56. +:::USING:::
  57. +
  58. +This patch (20060126) makes tcpserver from DJB's ucspi-tcp-0.88 package (see
  59. +http://cr.yp.to/ucspi-tcp.html) to modify its behavior if some environment
  60. +variables are present.
  61. +
  62. +The variables can be preset before starting tcpserver (thus acting as
  63. +default for all connections), or, if you use 'tcpserver -x xxx.cdb', they
  64. +can be set (or overridden) from xxx.cdb. If none of the variables are set,
  65. +tcpserver behaves same as non patched version (except for negligible
  66. +performance loss). Any or all variables can be set, as soon as first limit
  67. +is reached the connection is dropped. I'd recommend using .cdb files
  68. +exclusively though, as you can then modify configuration without killing
  69. +tcpserver.
  70. +
  71. +The variables are:
  72. +
  73. +(1) MAXLOAD
  74. + maximum 1-minute load average * 100. For example, if you have line
  75. + :allow,MAXLOAD="350"
  76. + in your rules file from which you created .cdb, the connection will be
  77. + accepted only if load average is below 3.50
  78. +
  79. + See COMPILING instructions above for info on supported systems.
  80. +
  81. +(2) MAXCONNIP
  82. + maximum connections from one IP address. tcpserver's -c flag defines
  83. + maximum number of allowed connections, but it can be abused if
  84. + just one host goes wild and eats all the connections - no other host
  85. + would be able to connect then. If you created your .cdb with:
  86. + :allow,MAXCONNIP="5"
  87. + and run tcpserver -c 50, then each IP address would be able to have at
  88. + most 5 concurrent connections, while there still could connect 50
  89. + clients total.
  90. + 0 is valid value and means 'always reject'
  91. +
  92. +(3) MAXCONNC
  93. +
  94. + maximum connections from whole C-class (256 addresses). Extension of
  95. + MAXCONNIP, as sometimes the problematic client has a whole farm of
  96. + client machines with different IP addresses instead of just one IP
  97. + address, and they all try to connect. It might have been more useful to
  98. + be able to specify CIDR block than C-class, but I've decided to KISS.
  99. +
  100. + for example tcpserver -c 200, and .cdb with:
  101. + :allow,MAXCONNC="15"
  102. + will allow at most 15 host from any x.y.z.0/24 address block, while
  103. + still allowing up to 200 total connections.
  104. + 0 is valid value and means 'always reject'
  105. +
  106. +(4) DIEMSG
  107. +
  108. + if set and one of the above limits is exceeded, this is the message
  109. + to be sent to client (CRLF is always added to the text) before terminating
  110. + connection. If unset, the connection simply terminates (after 1 sec delay)
  111. + if limit is exceeded.
  112. +
  113. + For example:
  114. + DIEMSG="421 example.com Service temporarily not available, closing
  115. + transmission channel"
  116. +
  117. +Notes:
  118. +
  119. +- if a connection is dropped due to some of those variables set, it will be
  120. + flagged (if you run tcpserver -v) with "LOAD:", "MAXCONNIP:" or
  121. + "MAXCONNC:" at the end of the "tcpserver: deny" line. If that bothers you
  122. + (eg. you have a strict log parsers), don't apply that chunk of the patch.
  123. +
  124. +- the idea for this patch came from my previous experience with xinetd, and
  125. + need to limit incoming bursts of virus/spam SMTP connections, since I was
  126. + running qmail-scanner to scan incoming and outgoing messages for viruses
  127. + and spam.
  128. +
  129. +When you make changes, please check that they work as expected.
  130. +
  131. +Examples (for tcprules created .cdb)
  132. +(a) 192.168.:allow,MAXLOAD="1000"
  133. + :allow,MAXCONNIP="3"
  134. +
  135. + this would allow any connection from your local LAN (192.168.*.*
  136. + addresses) if system load is less than 10.00. non-LAN connections would
  137. + be accepted only if clients from that IP address have not already opened
  138. + more than 2 connections (as your connection would be last allowed -- 3rd)
  139. +
  140. +(b) 192.168.:allow
  141. + 5.6.7.8:allow,MAXCONNIP="3"
  142. + 1.2.:allow,MAXLOAD="500",MAXCONNIP="1",MAXCONNC="5"
  143. + :allow,MAXLOAD="1000",MAXCONNIP="3",DIEMSG="421 example.com unavailable"
  144. +
  145. + if client connects from 192.168.*.* (ex: your LAN), it is allowed.
  146. + if it connects from 5.6.7.8 (ex: little abusive customer of yours),
  147. + it is allowed unless there are already 3active connections from 5.6.7.8
  148. + to this service
  149. + if it connects from 1.2.*.* (ex: some problematic networks which caused
  150. + you grief in the past) it will connect only if load is less than 5.0,
  151. + there is less than 5 active connections from whole C class
  152. + (1.2.*.0/24), and if that specific IP address does not already have
  153. + connection open.
  154. + in all other cases, the client will be permitted to connect if load is
  155. + less than 10.00 and client has 2 or less connections open. If load is
  156. + higher than 10.00 or there are 3 or more connections open from this
  157. + client, the message "421 example.com unavailable" will be returned to
  158. + the client and connection terminated.
  159. +
  160. +
  161. +Any bugs introduced are my own, do not bother DJB with them.
  162. +If you find any, or have neat ideas, or better documentation, or whatever,
  163. +contact me.
  164. +
  165. +the latest version of the patch can be found at:
  166. +http://linux.voyager.hr/ucspi-tcp/
  167. +
  168. +Enjoy,
  169. +Matija Nalis,
  170. +mnalis-tcpserver _at_ voyager.hr
  171. --- tcpserver.c 2006-09-12 15:54:11.000000000 +0200
  172. +++ tcpserver.c 2006-09-12 17:59:43.000000000 +0200
  173. @@ -1,3 +1,11 @@
  174. +#ifdef __dietlibc__
  175. +#define NO_GETLOADAVG
  176. +#endif
  177. +
  178. +#include <stdlib.h>
  179. +#ifdef NO_GETLOADAVG
  180. +#include <unistd.h>
  181. +#endif
  182. #include <sys/types.h>
  183. #include <sys/param.h>
  184. #include <netdb.h>
  185. @@ -59,11 +67,29 @@
  186. static stralloc tmp;
  187. static stralloc fqdn;
  188. static stralloc addresses;
  189. +static stralloc diemsg_buf;
  190. +
  191. +#ifdef SOLARIS
  192. +#include <kstat.h>
  193. +static kstat_ctl_t *kc;
  194. +#ifndef FSCALE
  195. +#define FSHIFT 8 /* bits to right of fixed binary point */
  196. +#define FSCALE (1<<FSHIFT)
  197. +#endif /* FSCALE */
  198. +
  199. +#define loaddouble(la) ((double)(la) / FSCALE)
  200. +#endif
  201.  
  202. char bspace[16];
  203. buffer b;
  204.  
  205. +typedef struct
  206. +{
  207. + char ip[4];
  208. + pid_t pid;
  209. +} baby;
  210.  
  211. +baby *child;
  212.  
  213. /* ---------------------------- child */
  214.  
  215. @@ -72,6 +98,10 @@
  216. int flagdeny = 0;
  217. int flagallownorules = 0;
  218. char *fnrules = 0;
  219. +unsigned long maxload = 0;
  220. +long maxconnip = -1;
  221. +long maxconnc = -1;
  222. +char *diemsg = "";
  223.  
  224. void drop_nomem(void)
  225. {
  226. @@ -110,6 +140,8 @@
  227. strerr_die4sys(111,DROP,"unable to read ",fnrules,": ");
  228. }
  229.  
  230. +unsigned long limit = 40;
  231. +
  232. void found(char *data,unsigned int datalen)
  233. {
  234. unsigned int next0;
  235. @@ -125,5 +157,13 @@
  236. if (data[1 + split] == '=') {
  237. data[1 + split] = 0;
  238. env(data + 1,data + 1 + split + 1);
  239. + if (str_diff(data+1, "MAXLOAD") == 0) scan_ulong(data+1+split+1,&maxload);
  240. + if (str_diff(data+1, "MAXCONNIP") == 0) scan_ulong(data+1+split+1,&maxconnip);
  241. + if (str_diff(data+1, "MAXCONNC") == 0) scan_ulong(data+1+split+1,&maxconnc);
  242. + if (str_diff(data+1, "DIEMSG") == 0) {
  243. + if (!stralloc_copys(&diemsg_buf,data+1+split+1)) drop_nomem();
  244. + if (!stralloc_0(&diemsg_buf)) drop_nomem();
  245. + diemsg = diemsg_buf.s;
  246. + }
  247. }
  248. break;
  249. @@ -133,6 +173,49 @@
  250. }
  251. }
  252.  
  253. +unsigned long getprocla(void)
  254. +{
  255. +#ifdef SOLARIS
  256. + kstat_t *ksp;
  257. + kstat_named_t *knp;
  258. + double lavg;
  259. + kstat_chain_update(kc);
  260. + ksp = kstat_lookup(kc, "unix", 0, "system_misc");
  261. + kstat_read(kc,ksp,NULL);
  262. + knp = kstat_data_lookup(ksp,"avenrun_1min");
  263. + lavg = loaddouble(knp->value.ui32);
  264. + return (unsigned long)(lavg * 100);
  265. +#else
  266. +#ifdef NO_GETLOADAVG
  267. + int lret;
  268. + int i;
  269. + unsigned long u1, u2;
  270. + char *s;
  271. + static stralloc loadavg_data = {0};
  272. +
  273. + lret = openreadclose("/proc/loadavg", &loadavg_data, 10);
  274. + if (lret != -1) {
  275. + /* /proc/loadavg format is:
  276. + * 13.08 3.04 1.00 34/170 14190 */
  277. + s = loadavg_data.s;
  278. + i = scan_ulong (s, &u1); s+=i;
  279. + if ((i>0) && (i<5) && (*s == '.')) { /* load should be < 10000 */
  280. + i = scan_ulong (s+1,&u2);
  281. + if (i==2) { /* we require two decimal places */
  282. + return (u1 * 100 + u2);
  283. + }
  284. + return (u1 * 100);
  285. + }
  286. + }
  287. +#else
  288. + double result;
  289. + if (getloadavg(&result, 1) == 1) {
  290. + return (result * 100);
  291. + }
  292. +#endif
  293. +#endif
  294. +}
  295. +
  296. void doit(int t)
  297. {
  298. int j;
  299. @@ -211,6 +294,24 @@
  300. }
  301. }
  302.  
  303. + unsigned long curload = 0;
  304. + if (maxload) {
  305. + curload = getprocla();
  306. + if (curload > maxload) flagdeny = 2;
  307. + }
  308. +
  309. + if (!flagdeny && (maxconnip != -1 || maxconnc != -1)) {
  310. + unsigned long u;
  311. + long c1=0, cc=0;
  312. + for (u=0; u < limit; u++)
  313. + if (child[u].pid != 0) {
  314. + if (byte_equal(child[u].ip, 3, remoteip)) cc++;
  315. + if (byte_equal(child[u].ip, 4, remoteip)) c1++;
  316. + }
  317. + if (maxconnc != -1 && (cc >= maxconnc)) flagdeny = 4;
  318. + if (maxconnip != -1 && (c1 >= maxconnip)) flagdeny = 3;
  319. + }
  320. +
  321. if (verbosity >= 2) {
  322. strnum[fmt_ulong(strnum,getpid())] = 0;
  323. if (!stralloc_copys(&tmp,"tcpserver: ")) drop_nomem();
  324. @@ -223,11 +324,35 @@
  325. cats(":"); safecats(remoteipstr);
  326. cats(":"); if (flagremoteinfo) safecats(tcpremoteinfo.s);
  327. cats(":"); safecats(remoteportstr);
  328. + if (flagdeny == 2) {
  329. + char curloadstr[FMT_ULONG];
  330. + curloadstr[fmt_ulong(curloadstr,curload)] = 0;
  331. + cats(" "); safecats ("LOAD"); cats(":"); safecats(curloadstr);
  332. + }
  333. + if (flagdeny == 3) {
  334. + char maxconstr[FMT_ULONG];
  335. + maxconstr[fmt_ulong(maxconstr,maxconnip)] = 0;
  336. + cats(" "); safecats ("MAXCONNIP"); cats(":"); safecats(maxconstr);
  337. + }
  338. + if (flagdeny == 4) {
  339. + char maxconstr[FMT_ULONG];
  340. + maxconstr[fmt_ulong(maxconstr,maxconnc)] = 0;
  341. + cats(" "); safecats ("MAXCONNC"); cats(":"); safecats(maxconstr);
  342. + }
  343. cats("\n");
  344. buffer_putflush(buffer_2,tmp.s,tmp.len);
  345. }
  346.  
  347. - if (flagdeny) _exit(100);
  348. + if (flagdeny) {
  349. + if (*diemsg) {
  350. + buffer_init(&b,write,t,bspace,sizeof bspace);
  351. + buffer_puts(&b,diemsg);
  352. + if (buffer_putsflush(&b,"\r\n") == -1)
  353. + strerr_die2sys(111,DROP,"unable to print diemsg: ");
  354. + }
  355. + sleep(1);
  356. + _exit(100);
  357. + }
  358. }
  359.  
  360.  
  361. @@ -253,7 +378,6 @@
  362. _exit(100);
  363. }
  364.  
  365. -unsigned long limit = 40;
  366. unsigned long numchildren = 0;
  367.  
  368. int flag1 = 0;
  369. @@ -278,6 +402,7 @@
  370. {
  371. int wstat;
  372. int pid;
  373. + unsigned long u;
  374.  
  375. while ((pid = wait_nohang(&wstat)) > 0) {
  376. if (verbosity >= 2) {
  377. @@ -286,11 +411,14 @@
  378. strerr_warn4("tcpserver: end ",strnum," status ",strnum2,0);
  379. }
  380. if (numchildren) --numchildren; printstatus();
  381. + for (u=0; u < limit; u++) if (child[u].pid == pid) { child[u].pid = 0; break; }
  382. + if (u == limit) strerr_die1x(111,"tcpserver: ERROR: dead child not found?!"); /* never happens */
  383. }
  384. }
  385.  
  386. main(int argc,char **argv)
  387. {
  388. + pid_t pid;
  389. char *hostname;
  390. char *portname;
  391. int opt;
  392. @@ -331,6 +459,10 @@
  393. }
  394. argc -= optind;
  395. argv += optind;
  396. + x = env_get("MAXLOAD"); if (x) scan_ulong(x,&maxload);
  397. + x = env_get("MAXCONNIP"); if (x) scan_ulong(x,&maxconnip);
  398. + x = env_get("MAXCONNC"); if (x) scan_ulong(x,&maxconnc);
  399. + x = env_get("DIEMSG"); if (x) diemsg = x;
  400.  
  401. if (!verbosity)
  402. buffer_2->fd = -1;
  403. @@ -352,6 +484,10 @@
  404. }
  405.  
  406. if (!*argv) usage();
  407. +
  408. + child = calloc(sizeof(baby),limit);
  409. + if (!child)
  410. + strerr_die2x(111,FATAL,"out of memory for MAXCONNIP tracking");
  411.  
  412. sig_block(sig_child);
  413. sig_catch(sig_child,sigchld);
  414. @@ -393,6 +529,9 @@
  415.  
  416. close(0);
  417. close(1);
  418. + #ifdef SOLARIS
  419. + kc = kstat_open();
  420. + #endif
  421. printstatus();
  422.  
  423. for (;;) {
  424. @@ -405,7 +544,7 @@
  425. if (t == -1) continue;
  426. ++numchildren; printstatus();
  427.  
  428. - switch(fork()) {
  429. + switch(pid=fork()) {
  430. case 0:
  431. close(s);
  432. doit(t);
  433. @@ -420,6 +559,16 @@
  434. case -1:
  435. strerr_warn2(DROP,"unable to fork: ",&strerr_sys);
  436. --numchildren; printstatus();
  437. + break;
  438. + default:
  439. + for (u=0; u < limit; u++)
  440. + if (child[u].pid == 0) {
  441. + byte_copy(child[u].ip,4,remoteip);
  442. + child[u].pid = pid;
  443. + break;
  444. + }
  445. + if (u == limit)
  446. + strerr_die1x(111,"tcpserver: ERROR: no empty space for new child?!"); /* never happens */
  447. }
  448. close(t);
  449. }
  450.