1 /*
2 * ProFTPD: mod_shaper -- a module implementing daemon-wide rate throttling
3 * via IPC
4 * Copyright (c) 2004-2017 TJ Saunders
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 *
20 * As a special exemption, TJ Saunders and other respective copyright holders
21 * give permission to link this program with OpenSSL, and distribute the
22 * resulting executable, without including the source code for OpenSSL in the
23 * source distribution.
24 *
25 * This is mod_shaper, contrib software for proftpd 1.2 and above.
26 * For more information contact TJ Saunders <tj@castaglia.org>.
27 */
28
29 #include "conf.h"
30 #include "privs.h"
31 #include "mod_ctrls.h"
32
33 #include <sys/mman.h>
34 #include <sys/ipc.h>
35 #include <sys/msg.h>
36 #include <sys/uio.h>
37
38 #define MOD_SHAPER_VERSION "mod_shaper/0.6.6"
39
40 /* Make sure the version of proftpd is as necessary. */
41 #if PROFTPD_VERSION_NUMBER < 0x0001030402
42 # error "ProFTPD 1.3.4rc2 or later required"
43 #endif
44
45 #ifndef PR_USE_CTRLS
46 # error "Controls support required (use --enable-ctrls)"
47 #endif
48
49 /* From src/main.c */
50 extern pid_t mpid;
51
52 module shaper_module;
53
54 static ctrls_acttab_t shaper_acttab[];
55 static const char *shaper_down_cmds[] = { C_RETR, NULL };
56 static const char *shaper_up_cmds[] = { C_APPE, C_STOR, C_STOU, NULL };
57 static int shaper_engine = FALSE;
58 static char *shaper_log_path = NULL;
59 static int shaper_logfd = -1;
60 static pool *shaper_pool = NULL;
61 static int shaper_qid = -1;
62 static unsigned long shaper_qmaxbytes = 0;
63 static int shaper_scrub_timer_id = -1;
64 static char *shaper_tab_path = NULL;
65 static pool *shaper_tab_pool = NULL;
66 static int shaper_tabfd = -1;
67
68 #define SHAPER_PROJ_ID 24
69
70 #if defined(FREEBSD4)
71 # define SHAPER_IOV_BASE (char *)
72 #elif defined(LINUX)
73 # define SHAPER_IOV_BASE (__ptr_t)
74 #elif defined(SOLARIS2)
75 # define SHAPER_IOV_BASE (caddr_t)
76 #else
77 # define SHAPER_IOV_BASE (void *)
78 #endif
79
80 #ifndef HAVE_FLOCK
81 # define LOCK_SH 1
82 # define LOCK_EX 2
83 # define LOCK_NB 4
84 # define LOCK_UN 8
85 #endif /* !HAVE_FLOCK */
86
87 #define SHAPER_DEFAULT_RATE -1.0
88 #define SHAPER_DEFAULT_PRIO 10
89 #define SHAPER_DEFAULT_SHARES 5
90 #define SHAPER_DEFAULT_UPSHARES SHAPER_DEFAULT_SHARES
91 #define SHAPER_DEFAULT_DOWNSHARES SHAPER_DEFAULT_SHARES
92
93 #define SHAPER_SCRUB_INTERVAL 60
94
95 #ifndef SHAPER_MAX_SEND_ATTEMPTS
96 # define SHAPER_MAX_SEND_ATTEMPTS 5
97 #endif
98
99 struct shaper_sess {
100 pid_t sess_pid;
101 unsigned int sess_prio;
102 int sess_downincr;
103 long double sess_downrate;
104 int sess_upincr;
105 long double sess_uprate;
106 };
107
108 struct {
109 int def_prio;
110 long double downrate;
111 unsigned int def_downshares;
112 long double uprate;
113 unsigned int def_upshares;
114 unsigned int nsessions;
115 array_header *sess_list;
116
117 } shaper_tab;
118
119 /* Define our own structure for messages, since one is not portably defined.
120 */
121 struct shaper_msg {
122 /* Message type */
123 long mtype;
124
125 /* Message data */
126 char mtext[1];
127 };
128
129 /* Necessary function prototypes. */
130 static void shaper_msg_clear(pid_t);
131 static int shaper_rate_alter(unsigned int, long double, long double);
132 static void shaper_sess_exit_ev(const void *, void *);
133 static void shaper_sigusr2_ev(const void *, void *);
134 static int shaper_table_send(void);
135
136 /* Support functions
137 */
138
shaper_get_key(const char * path)139 static key_t shaper_get_key(const char *path) {
140 pr_fh_t *fh;
141 struct stat st;
142
143 /* ftok() uses stat(2) on the given path, which means that it needs to exist.
144 */
145 fh = pr_fsio_open(path, O_WRONLY|O_CREAT);
146 if (fh == NULL) {
147 int xerrno = errno;
148
149 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
150 "error opening '%s': %s", path, strerror(xerrno));
151
152 errno = xerrno;
153 return -1;
154 }
155
156 if (pr_fsio_fstat(fh, &st) < 0) {
157 int xerrno = errno;
158
159 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
160 "error checking '%s': %s", path, strerror(xerrno));
161
162 pr_fsio_close(fh);
163 errno = xerrno;
164 return -1;
165 }
166
167 if (S_ISDIR(st.st_mode)) {
168 int xerrno = EISDIR;
169
170 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
171 "error using '%s': %s", path, strerror(xerrno));
172
173 pr_fsio_close(fh);
174 errno = xerrno;
175 return -1;
176 }
177
178 pr_fsio_close(fh);
179
180 return ftok(path, SHAPER_PROJ_ID);
181 }
182
shaper_get_queue(const char * path)183 static int shaper_get_queue(const char *path) {
184 int qid;
185
186 /* Obtain a key for this path. */
187 key_t key = shaper_get_key(path);
188 if (key == (key_t) -1) {
189 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
190 "unable to get key for '%s': %s", path, strerror(errno));
191 return -1;
192 }
193
194 /* Try first using IPC_CREAT|IPC_EXCL, to check if there is an existing
195 * queue for this key. If there is, try again, using a flag of zero.
196 */
197 qid = msgget(key, IPC_CREAT|IPC_EXCL|0666);
198 if (qid < 0) {
199 if (errno == EEXIST)
200 qid = msgget(key, 0);
201
202 else
203 return -1;
204 }
205
206 return qid;
207 }
208
shaper_remove_queue(void)209 static int shaper_remove_queue(void) {
210 struct msqid_ds ds;
211 int res;
212
213 memset(&ds, 0, sizeof(ds));
214
215 res = msgctl(shaper_qid, IPC_RMID, &ds);
216 if (res < 0) {
217 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
218 "error removing queue ID %d: %s", shaper_qid, strerror(errno));
219
220 } else {
221 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
222 "removed queue ID %d", shaper_qid);
223 shaper_qid = -1;
224 }
225
226 return res;
227 }
228
shaper_msg_recv(void)229 static int shaper_msg_recv(void) {
230 int nmsgs = 0;
231 ssize_t msglen = 0;
232 struct shaper_msg *msg;
233
234 /* The expected message length consists of a priority (the unsigned int),
235 * the new downrate (the long double), and the new uprate (another long
236 * double).
237 */
238 size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
239 sizeof(long double);
240
241 msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
242 if (msg == NULL) {
243 pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
244 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
245 }
246
247 msglen = msgrcv(shaper_qid, msg, msgsz, getpid(), IPC_NOWAIT|MSG_NOERROR);
248 while (msglen > 0) {
249 unsigned int prio;
250 long double downrate, uprate;
251
252 pr_signals_handle();
253 nmsgs++;
254
255 memcpy(&prio, msg->mtext, sizeof(unsigned int));
256 memcpy(&downrate, msg->mtext + sizeof(unsigned int), sizeof(long double));
257 memcpy(&uprate, msg->mtext + sizeof(unsigned int) + sizeof(long double),
258 sizeof(long double));
259
260 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
261 "received prio %u, rate %3.2Lf down, %3.2Lf up", prio, downrate,
262 uprate);
263
264 if (shaper_rate_alter(prio, downrate, uprate) < 0) {
265 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
266 "error altering rate for current session: %s", strerror(errno));
267 }
268
269 msglen = msgrcv(shaper_qid, msg, msgsz, getpid(), IPC_NOWAIT|MSG_NOERROR);
270 }
271
272 free(msg);
273
274 if (msglen < 0 &&
275 #ifdef ENOMSG
276 errno != ENOMSG &&
277 #endif /* ENOMSG */
278 errno != EAGAIN)
279 return -1;
280
281 return nmsgs;
282 }
283
shaper_msg_send(pid_t dst_pid,unsigned int prio,long double downrate,long double uprate)284 static int shaper_msg_send(pid_t dst_pid, unsigned int prio,
285 long double downrate, long double uprate) {
286 unsigned int error_count = 0;
287 int res;
288 struct shaper_msg *msg;
289 size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
290 sizeof(long double);
291
292 msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
293 if (msg == NULL) {
294 pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
295 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
296 }
297
298 msg->mtype = dst_pid;
299 memcpy(msg->mtext, &prio, sizeof(unsigned int));
300 memcpy(msg->mtext + sizeof(unsigned int), &downrate, sizeof(long double));
301 memcpy(msg->mtext + sizeof(unsigned int) + sizeof(long double), &uprate,
302 sizeof(long double));
303
304 /* Remove any old messages in the queue for the destination PID. This
305 * helps keep the queue clear and moving, more resistant to (inadvertent
306 * or not) DoS situations.
307 */
308 shaper_msg_clear(dst_pid);
309
310 while (msgsnd(shaper_qid, msg, msgsz, IPC_NOWAIT) < 0) {
311 pr_signals_handle();
312
313 if (errno != EAGAIN) {
314 free(msg);
315 return -1;
316
317 } else {
318 /* The EAGAIN error happens when there are too many bytes of messages
319 * on the queue. Check to see what the current number of messages
320 * on the queue is, and log the error.
321 *
322 * If this error is hit too many times in a loop, we may need to give
323 * up permanently. (XXX in the future, if one queue is too small for
324 * a busy daemon, look into a different queue allocation strategy.)
325 */
326 struct msqid_ds ds;
327
328 if (msgctl(shaper_qid, IPC_STAT, &ds) < 0) {
329 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
330 "error checking queue ID %d: %s", shaper_qid, strerror(errno));
331
332 } else {
333 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
334 "unable to send message to PID %lu via queue ID %d, max bytes (%lu) "
335 "reached (%lu messages of %lu bytes currently in queue)",
336 (unsigned long) dst_pid, shaper_qid, shaper_qmaxbytes,
337 (unsigned long) ds.msg_qnum, (unsigned long) ds.msg_qnum * msgsz);
338 }
339
340 error_count++;
341 if (error_count > SHAPER_MAX_SEND_ATTEMPTS) {
342 free(msg);
343
344 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
345 "unable to send message to PID %lu via queue ID %d after %u attempts "
346 "(%u max attempts allowed), failing", (unsigned long) dst_pid,
347 shaper_qid, error_count, SHAPER_MAX_SEND_ATTEMPTS);
348
349 errno = EPERM;
350 return -1;
351 }
352 }
353
354 }
355 free(msg);
356
357 /* Send SIGUSR2 to the destination process, to let it know that it should
358 * check the queue for messages.
359 */
360 PRIVS_ROOT
361 res = kill(dst_pid, SIGUSR2);
362 PRIVS_RELINQUISH
363
364 if (res < 0) {
365 if (errno == ESRCH) {
366 shaper_msg_clear(dst_pid);
367
368 } else {
369 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
370 "error sending notice: %s", strerror(errno));
371 }
372 }
373
374 #if 0
375 /* Handle our own signal, if necessary. */
376 if (getpid() == dst_pid)
377 pr_signals_handle();
378 #endif
379
380 return 0;
381 }
382
shaper_msg_clear(pid_t dst_pid)383 static void shaper_msg_clear(pid_t dst_pid) {
384 ssize_t msglen = 0;
385 struct shaper_msg *msg;
386 size_t msgsz = sizeof(unsigned int) + sizeof(long double) +
387 sizeof(long double);
388
389 msg = malloc(sizeof(struct shaper_msg) + msgsz - sizeof(msg->mtext));
390 if (msg == NULL) {
391 pr_log_pri(PR_LOG_ALERT, MOD_SHAPER_VERSION ": Out of memory!");
392 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_NOMEM, NULL);
393 }
394
395 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
396 "clearing queue ID %d of messages for process ID %lu", shaper_qid,
397 (unsigned long) dst_pid);
398 msglen = msgrcv(shaper_qid, msg, msgsz, dst_pid, IPC_NOWAIT|MSG_NOERROR);
399 while (msglen > 0) {
400 pr_signals_handle();
401
402 msglen = msgrcv(shaper_qid, msg, msgsz, dst_pid, IPC_NOWAIT|MSG_NOERROR);
403 }
404
405 free(msg);
406 }
407
shaper_remove_config(unsigned int prio)408 static void shaper_remove_config(unsigned int prio) {
409 config_rec *c;
410 register unsigned int i;
411 pool *tmp_pool = make_sub_pool(shaper_pool);
412 array_header *list = make_array(tmp_pool, 1, sizeof(config_rec *));
413
414 /* This function is very similar to remove_config(), except that we
415 * want to remove only TransferRate config_recs, and only those
416 * config_recs whose priority matches the given priority.
417 *
418 * To do this, first we'll do a recursive scan for all TransferRate
419 * config_recs that match our priority, and add them to a list.
420 * Then we'll loop through the list, removing them from the config
421 * tree. It can't be done in one loop, as we'll not be able to
422 * track which config_recs we've seen and left alone before.
423 */
424
425 c = find_config(main_server->conf, CONF_PARAM, "TransferRate", TRUE);
426 while (c) {
427 pr_signals_handle();
428
429 if (*((unsigned int *) c->argv[3]) == prio)
430 *((config_rec **) push_array(list)) = c;
431
432 c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", TRUE);
433 }
434
435 for (i = 0; i < list->nelts; i++) {
436 xaset_t *set;
437
438 c = ((config_rec **) list->elts)[i];
439 set = c->set;
440
441 xaset_remove(set, (xasetmember_t *) c);
442
443 if (!set->xas_list) {
444 if (c->parent && c->parent->subset == set)
445 c->parent->subset = NULL;
446
447 else if (main_server->conf == set)
448 main_server->conf = NULL;
449
450 destroy_pool(set->pool);
451
452 } else {
453 destroy_pool(c->pool);
454 }
455 }
456
457 destroy_pool(tmp_pool);
458 return;
459 }
460
shaper_rate_alter(unsigned int prio,long double downrate,long double uprate)461 static int shaper_rate_alter(unsigned int prio, long double downrate,
462 long double uprate) {
463 config_rec *c;
464
465 /* Remove any TransferRate entries at this same priority. */
466 shaper_remove_config(prio);
467
468 /* Create separate TransferRate entries for the download and upload
469 * rates, for now. It would be more efficient to have a single config_rec
470 * entry, but only when the downrate and uprate are the same.
471 */
472
473 if (downrate > 0.0) {
474 c = add_config_param_set(&main_server->conf, "TransferRate", 4, NULL,
475 NULL, NULL, NULL);
476 c->argv[0] = shaper_down_cmds;
477
478 c->argv[1] = pcalloc(c->pool, sizeof(long double));
479 *((long double *) c->argv[1]) = downrate;
480
481 /* No freebytes altered via mod_shaper. */
482 c->argv[2] = pcalloc(c->pool, sizeof(off_t));
483 *((off_t *) c->argv[2]) = 0;
484
485 c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
486 *((unsigned int *) c->argv[3]) = prio;
487
488 c->flags |= CF_MERGEDOWN_MULTI;
489 }
490
491 if (uprate > 0.0) {
492 c = add_config_param_set(&main_server->conf, "TransferRate", 4, NULL,
493 NULL, NULL, NULL);
494 c->argv[0] = shaper_up_cmds;
495
496 c->argv[1] = pcalloc(c->pool, sizeof(long double));
497 *((long double *) c->argv[1]) = uprate;
498
499 /* No freebytes altered via mod_shaper. */
500 c->argv[2] = pcalloc(c->pool, sizeof(off_t));
501 *((off_t *) c->argv[2]) = 0;
502
503 c->argv[3] = pcalloc(c->pool, sizeof(unsigned int));
504 *((unsigned int *) c->argv[3]) = prio;
505
506 c->flags |= CF_MERGEDOWN_MULTI;
507 }
508
509 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
510 "merging in new TransferRate entries");
511 fixup_dirs(main_server, CF_SILENT);
512
513 return 0;
514 }
515
516 /* Flush the ShaperTable out to disk. */
shaper_table_flush(void)517 static int shaper_table_flush(void) {
518 register unsigned int i;
519 int res;
520 struct iovec tab_iov[6];
521 struct shaper_sess *sess_list;
522
523 /* Seek to the start of the file. */
524 if (lseek(shaper_tabfd, 0, SEEK_SET) == (off_t) -1) {
525 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
526 "error seeking to start of ShaperTable: %s", strerror(errno));
527 return -1;
528 }
529
530 /* Write out the ShaperTable header. */
531 tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
532 tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
533
534 tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
535 tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
536
537 tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
538 tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
539
540 tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
541 tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
542
543 tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
544 tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
545
546 tab_iov[5].iov_base = SHAPER_IOV_BASE &shaper_tab.nsessions;
547 tab_iov[5].iov_len = sizeof(shaper_tab.nsessions);
548
549 res = writev(shaper_tabfd, tab_iov, 6);
550 if (res < 0) {
551 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
552 "error writing ShaperTable (%d) header: %s", shaper_tabfd,
553 strerror(errno));
554 return -1;
555 }
556
557 sess_list = shaper_tab.sess_list->elts;
558 for (i = 0; i < shaper_tab.nsessions; i++) {
559 tab_iov[0].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_pid;
560 tab_iov[0].iov_len = sizeof(pid_t);
561
562 tab_iov[1].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_prio;
563 tab_iov[1].iov_len = sizeof(unsigned int);
564
565 tab_iov[2].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_downincr;
566 tab_iov[2].iov_len = sizeof(int);
567
568 tab_iov[3].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_downrate;
569 tab_iov[3].iov_len = sizeof(long double);
570
571 tab_iov[4].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_upincr;
572 tab_iov[4].iov_len = sizeof(int);
573
574 tab_iov[5].iov_base = SHAPER_IOV_BASE &sess_list[i].sess_uprate;
575 tab_iov[5].iov_len = sizeof(long double);
576
577 res = writev(shaper_tabfd, tab_iov, 6);
578 if (res < 0)
579 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
580 "error writing ShaperTable session entry: %s", strerror(errno));
581 }
582
583 return 0;
584 }
585
586 #ifndef HAVE_FLOCK
get_lock_type(struct flock * lock)587 static const char *get_lock_type(struct flock *lock) {
588 const char *lock_type;
589
590 switch (lock->l_type) {
591 case F_RDLCK:
592 lock_type = "read";
593 break;
594
595 case F_WRLCK:
596 lock_type = "write";
597 break;
598
599 case F_UNLCK:
600 lock_type = "unlock";
601 break;
602
603 default:
604 lock_type = "[unknown]";
605 }
606
607 return lock_type;
608 }
609 #endif /* !HAVE_FLOCK */
610
shaper_table_lock(int op)611 static int shaper_table_lock(int op) {
612 static int have_lock = FALSE;
613
614 #ifndef HAVE_FLOCK
615 int flag;
616 struct flock lock;
617 #endif /* !HAVE_FLOCK */
618
619 if (have_lock &&
620 ((op & LOCK_SH) || (op & LOCK_EX))) {
621 return 0;
622 }
623
624 if (!have_lock &&
625 (op & LOCK_UN)) {
626 return 0;
627 }
628
629 #ifdef HAVE_FLOCK
630 pr_trace_msg("lock", 9, "attempting to %s ShaperTable fd %d via flock(2)",
631 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
632 while (flock(shaper_tabfd, op) < 0) {
633 int xerrno = errno;
634
635 if (xerrno == EINTR) {
636 pr_signals_handle();
637 continue;
638 }
639
640 pr_trace_msg("lock", 9, "%s of ShaperTable fd %d failed: %s",
641 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd, strerror(xerrno));
642
643 errno = xerrno;
644 return -1;
645 }
646
647 pr_trace_msg("lock", 9, "%s of ShaperTable fd %d successful",
648 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
649
650 if ((op & LOCK_SH) ||
651 (op & LOCK_EX)) {
652 have_lock = TRUE;
653
654 } else if (op & LOCK_UN) {
655 have_lock = FALSE;
656 }
657
658 return 0;
659 #else
660 flag = F_SETLKW;
661
662 lock.l_whence = 0;
663 lock.l_start = lock.l_len = 0;
664
665 if (op & LOCK_SH) {
666 lock.l_type = F_RDLCK;
667
668 } else if (op & LOCK_EX) {
669 lock.l_type = F_WRLCK;
670
671 } else if (op & LOCK_UN) {
672 lock.l_type = F_UNLCK;
673
674 } else {
675 errno = EINVAL;
676 return -1;
677 }
678
679 if (op & LOCK_NB)
680 flag = F_SETLK;
681
682 pr_trace_msg("lock", 9, "attempting to %s ShaperTable fd %d via fcntl(2)",
683 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
684 while (fcntl(shaper_tabfd, flag, &lock) < 0) {
685 int xerrno = errno;
686
687 if (xerrno == EINTR) {
688 pr_signals_handle();
689 continue;
690 }
691
692 pr_trace_msg("lock", 9, "%s of ShaperTable fd %d failed: %s",
693 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd, strerror(xerrno));
694
695 if (xerrno == EACCES) {
696 /* Get the PID of the process blocking this lock. */
697 if (fcntl(shaper_tabfd, F_GETLK, &lock) == 0) {
698 pr_trace_msg("lock", 3, "process ID %lu has blocking %s lock on "
699 "ShaperTable fd %d", (unsigned long) lock.l_pid, get_lock_type(&lock),
700 shaper_tabfd);
701 }
702 }
703
704 errno = xerrno;
705 return -1;
706 }
707
708 pr_trace_msg("lock", 9, "%s of ShaperTable fd %d successful",
709 op == LOCK_UN ? "unlock" : "lock", shaper_tabfd);
710
711 if ((op & LOCK_SH) ||
712 (op & LOCK_EX)) {
713 have_lock = TRUE;
714
715 } else if (op & LOCK_UN) {
716 have_lock = FALSE;
717 }
718
719 return 0;
720 #endif /* HAVE_FLOCK */
721 }
722
shaper_table_init(pr_fh_t * fh)723 static int shaper_table_init(pr_fh_t *fh) {
724 unsigned int nsessions = 0;
725 struct stat st;
726 struct iovec tab_iov[6];
727
728 if (pr_fsio_fstat(fh, &st) < 0) {
729 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
730 "unable to fstat ShaperTable: %s", strerror(errno));
731 errno = EINVAL;
732 return -1;
733 }
734
735 shaper_tabfd = fh->fh_fd;
736
737 /* XXX maybe add a shaper control to clear/re-init the table, in cases
738 * where the format changes?
739 */
740
741 /* If the table already exists (i.e. size > 0), return. */
742 if (st.st_size > 0) {
743 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
744 "ShaperTable '%s' has size %" PR_LU " bytes, is already initialized",
745 fh->fh_path, (pr_off_t) st.st_size);
746 return 0;
747 }
748
749 tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
750 tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
751
752 tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
753 tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
754
755 tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
756 tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
757
758 tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
759 tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
760
761 tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
762 tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
763
764 tab_iov[5].iov_base = SHAPER_IOV_BASE &nsessions;
765 tab_iov[5].iov_len = sizeof(nsessions);
766
767 if (lseek(fh->fh_fd, 0, SEEK_SET) < 0) {
768 return -1;
769 }
770
771 if (writev(fh->fh_fd, tab_iov, 6) < 0) {
772 return -1;
773 }
774
775 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
776 "initialized ShaperTable with rate %3.2Lf KB/s (down), %3.2Lf KB/s (up), "
777 "default priority %u, default shares %u down, %u up", shaper_tab.downrate,
778 shaper_tab.uprate, shaper_tab.def_prio, shaper_tab.def_downshares,
779 shaper_tab.def_upshares);
780
781 return 0;
782 }
783
784 /* Refresh the in-memory ShaperTable from disk. */
shaper_table_refresh(void)785 static int shaper_table_refresh(void) {
786 register unsigned int i;
787 int res;
788 struct iovec tab_iov[6];
789
790 if (lseek(shaper_tabfd, 0, SEEK_SET) == (off_t) -1) {
791 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
792 "error seeking to start of ShaperTable: %s", strerror(errno));
793 return -1;
794 }
795
796 /* Read the ShaperTable header. */
797
798 tab_iov[0].iov_base = SHAPER_IOV_BASE &shaper_tab.def_prio;
799 tab_iov[0].iov_len = sizeof(shaper_tab.def_prio);
800
801 tab_iov[1].iov_base = SHAPER_IOV_BASE &shaper_tab.downrate;
802 tab_iov[1].iov_len = sizeof(shaper_tab.downrate);
803
804 tab_iov[2].iov_base = SHAPER_IOV_BASE &shaper_tab.def_downshares;
805 tab_iov[2].iov_len = sizeof(shaper_tab.def_downshares);
806
807 tab_iov[3].iov_base = SHAPER_IOV_BASE &shaper_tab.uprate;
808 tab_iov[3].iov_len = sizeof(shaper_tab.uprate);
809
810 tab_iov[4].iov_base = SHAPER_IOV_BASE &shaper_tab.def_upshares;
811 tab_iov[4].iov_len = sizeof(shaper_tab.def_upshares);
812
813 tab_iov[5].iov_base = SHAPER_IOV_BASE &shaper_tab.nsessions;
814 tab_iov[5].iov_len = sizeof(shaper_tab.nsessions);
815
816 res = readv(shaper_tabfd, tab_iov, 6);
817 if (res < 0) {
818 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
819 "error reading ShaperTable header: %s", strerror(errno));
820 return -1;
821 }
822
823 /* For every session, read in its information and add it to the list.
824 * For this, we need a pool for the session list.
825 */
826
827 if (shaper_tab_pool) {
828 destroy_pool(shaper_tab_pool);
829 shaper_tab_pool = NULL;
830 shaper_tab.sess_list = NULL;
831 }
832
833 shaper_tab_pool = make_sub_pool(shaper_pool);
834 pr_pool_tag(shaper_tab_pool, MOD_SHAPER_VERSION ": ShaperTable pool");
835
836 shaper_tab.sess_list = make_array(shaper_tab_pool, 0,
837 sizeof(struct shaper_sess));
838
839 for (i = 0; i < shaper_tab.nsessions; i++) {
840 struct shaper_sess *sess = push_array(shaper_tab.sess_list);
841
842 tab_iov[0].iov_base = SHAPER_IOV_BASE &sess->sess_pid;
843 tab_iov[0].iov_len = sizeof(sess->sess_pid);
844
845 tab_iov[1].iov_base = SHAPER_IOV_BASE &sess->sess_prio;
846 tab_iov[1].iov_len = sizeof(sess->sess_prio);
847
848 tab_iov[2].iov_base = SHAPER_IOV_BASE &sess->sess_downincr;
849 tab_iov[2].iov_len = sizeof(sess->sess_downincr);
850
851 tab_iov[3].iov_base = SHAPER_IOV_BASE &sess->sess_downrate;
852 tab_iov[3].iov_len = sizeof(sess->sess_downrate);
853
854 tab_iov[4].iov_base = SHAPER_IOV_BASE &sess->sess_upincr;
855 tab_iov[4].iov_len = sizeof(sess->sess_upincr);
856
857 tab_iov[5].iov_base = SHAPER_IOV_BASE &sess->sess_uprate;
858 tab_iov[5].iov_len = sizeof(sess->sess_uprate);
859
860 res = readv(shaper_tabfd, tab_iov, 6);
861 if (res < 0) {
862 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
863 "error reading session entry %u from ShaperTable: %s", i + 1,
864 strerror(errno));
865 return -1;
866 }
867 }
868
869 return 0;
870 }
871
872 /* Scan the ShaperTable for any sessions who might have exited in a Bad Way
873 * and not cleaned up their entries.
874 */
shaper_table_scrub(void)875 static void shaper_table_scrub(void) {
876 register unsigned int i;
877 struct shaper_sess *sess_list;
878 array_header *new_sess_list;
879 int send_tab = FALSE;
880
881 if (shaper_table_lock(LOCK_EX) < 0)
882 return;
883
884 if (shaper_table_refresh() < 0) {
885 shaper_table_lock(LOCK_UN);
886 return;
887 }
888
889 if (shaper_tab.nsessions == 0) {
890 /* No sessions in the ShaperTable to be removed. */
891 shaper_table_lock(LOCK_UN);
892 return;
893 }
894
895 sess_list = shaper_tab.sess_list->elts;
896 new_sess_list = make_array(shaper_tab_pool, 0, sizeof(struct shaper_sess));
897
898 for (i = 0; i < shaper_tab.nsessions; i++) {
899
900 /* Check to see if the PID in this entry is valid. If not, erase
901 * the slot.
902 */
903 if (kill(sess_list[i].sess_pid, 0) < 0) {
904 if (errno == ESRCH) {
905
906 /* OK, the recorded PID is no longer valid. */
907 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
908 "removed dead session (pid %u) from ShaperTable",
909 (unsigned int) sess_list[i].sess_pid);
910 send_tab = TRUE;
911 }
912
913 } else {
914 struct shaper_sess *sess = push_array(new_sess_list);
915
916 sess->sess_pid = sess_list[i].sess_pid;
917 sess->sess_prio = sess_list[i].sess_prio;
918 sess->sess_downincr = sess_list[i].sess_downincr;
919 sess->sess_downrate = sess_list[i].sess_downrate;
920 sess->sess_upincr = sess_list[i].sess_upincr;
921 sess->sess_uprate = sess_list[i].sess_uprate;
922 }
923 }
924
925 /* Replace the session list. The memory pointed to by the overwritten
926 * pointer will be freed when shaper_tab_pool is freed, which will be
927 * when the table is next refreshed.
928 */
929
930 shaper_tab.nsessions = new_sess_list->nelts;
931 shaper_tab.sess_list = new_sess_list;
932
933 if (send_tab && shaper_table_send() < 0) {
934 shaper_table_lock(LOCK_UN);
935 return;
936 }
937
938 if (shaper_table_flush() < 0) {
939 shaper_table_lock(LOCK_UN);
940 return;
941 }
942
943 shaper_table_lock(LOCK_UN);
944 return;
945 }
946
shaper_table_scrub_cb(CALLBACK_FRAME)947 static int shaper_table_scrub_cb(CALLBACK_FRAME) {
948 shaper_table_scrub();
949
950 /* Always return 1, resetting the timer. */
951 return 1;
952 }
953
954 /* Scan the ShaperTable, sending messages to each session for their new rate
955 * and its priority.
956 */
shaper_table_send(void)957 static int shaper_table_send(void) {
958 register unsigned int i;
959 unsigned int total_downshares = 0, total_upshares = 0;
960 long double rate_per_downshare, rate_per_upshare;
961 struct shaper_sess *sess_list = shaper_tab.sess_list->elts;
962
963 for (i = 0; i < shaper_tab.nsessions; i++) {
964 total_downshares += (shaper_tab.def_downshares +
965 sess_list[i].sess_downincr);
966 total_upshares += (shaper_tab.def_upshares +
967 sess_list[i].sess_upincr);
968 }
969
970 if (total_downshares == 0) {
971 total_downshares = 1;
972 }
973
974 if (total_upshares == 0) {
975 total_upshares = 1;
976 }
977
978 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
979 "total session shares: %u down, %u up", total_downshares, total_upshares);
980
981 rate_per_downshare = shaper_tab.downrate / total_downshares;
982 rate_per_upshare = shaper_tab.uprate / total_upshares;
983
984 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
985 "rate per share: %3.2Lf down, %3.2Lf up", rate_per_downshare,
986 rate_per_upshare);
987
988 for (i = 0; i < shaper_tab.nsessions; i++) {
989 sess_list[i].sess_downrate = rate_per_downshare *
990 (shaper_tab.def_downshares + sess_list[i].sess_downincr);
991 sess_list[i].sess_uprate = rate_per_upshare *
992 (shaper_tab.def_upshares + sess_list[i].sess_upincr);
993
994 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
995 "pid %u has shares of %u down, %u up, sending rates of %3.2Lf down, "
996 "%3.2Lf up", (unsigned int) sess_list[i].sess_pid,
997 shaper_tab.def_downshares + sess_list[i].sess_downincr,
998 shaper_tab.def_upshares + sess_list[i].sess_upincr,
999 sess_list[i].sess_downrate, sess_list[i].sess_uprate);
1000
1001 if (shaper_msg_send(sess_list[i].sess_pid, sess_list[i].sess_prio,
1002 sess_list[i].sess_downrate, sess_list[i].sess_uprate) < 0)
1003 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1004 "error sending msg to pid %u: %s",
1005 (unsigned int) sess_list[i].sess_pid, strerror(errno));
1006 }
1007
1008 return 0;
1009 }
1010
shaper_table_sess_add(pid_t sess_pid,unsigned int prio,int downincr,int upincr)1011 static int shaper_table_sess_add(pid_t sess_pid, unsigned int prio,
1012 int downincr, int upincr) {
1013 struct shaper_sess *sess;
1014
1015 if (shaper_table_lock(LOCK_EX) < 0) {
1016 return -1;
1017 }
1018
1019 if (shaper_table_refresh() < 0) {
1020 int xerrno = errno;
1021
1022 shaper_table_lock(LOCK_UN);
1023 errno = xerrno;
1024 return -1;
1025 }
1026
1027 shaper_tab.nsessions++;
1028 sess = push_array(shaper_tab.sess_list);
1029 sess->sess_pid = sess_pid;
1030
1031 if (prio != (unsigned int) -1) {
1032 sess->sess_prio = prio;
1033
1034 } else {
1035 sess->sess_prio = shaper_tab.def_prio;
1036 }
1037
1038 sess->sess_downincr = downincr;
1039 sess->sess_downrate = 0.0;
1040 sess->sess_upincr = upincr;
1041 sess->sess_uprate = 0.0;
1042
1043 if (shaper_table_send() < 0) {
1044 int xerrno = errno;
1045
1046 shaper_table_lock(LOCK_UN);
1047 errno = xerrno;
1048 return -1;
1049 }
1050
1051 if (shaper_table_flush() < 0) {
1052 int xerrno = errno;
1053
1054 shaper_table_lock(LOCK_UN);
1055 errno = xerrno;
1056 return -1;
1057 }
1058
1059 shaper_table_lock(LOCK_UN);
1060 return 0;
1061 }
1062
shaper_table_sess_modify(pid_t sess_pid,unsigned int prio,int downincr,int upincr)1063 static int shaper_table_sess_modify(pid_t sess_pid, unsigned int prio,
1064 int downincr, int upincr) {
1065 register unsigned int i;
1066 int found = FALSE, adj_down_ok = FALSE, adj_up_ok = FALSE;
1067 struct shaper_sess *sess_list;
1068
1069 if (shaper_table_lock(LOCK_EX) < 0)
1070 return -1;
1071
1072 if (shaper_table_refresh() < 0) {
1073 int xerrno = errno;
1074
1075 shaper_table_lock(LOCK_UN);
1076 errno = xerrno;
1077 return -1;
1078 }
1079
1080 /* XXX for large ShaperTables, this linear scan will increase the time
1081 * needed for adjusting sessions.
1082 */
1083 sess_list = shaper_tab.sess_list->elts;
1084 for (i = 0; i < shaper_tab.nsessions; i++) {
1085 if (sess_list[i].sess_pid != sess_pid)
1086 continue;
1087
1088 found = TRUE;
1089
1090 if ((shaper_tab.def_downshares + sess_list[i].sess_downincr +
1091 downincr) >= 1) {
1092 adj_down_ok = TRUE;
1093 sess_list[i].sess_downincr += downincr;
1094 }
1095
1096 if ((shaper_tab.def_upshares + sess_list[i].sess_upincr +
1097 upincr) >= 1) {
1098 adj_up_ok = TRUE;
1099 sess_list[i].sess_upincr += upincr;
1100 }
1101
1102 if (prio != (unsigned int) -1)
1103 sess_list[i].sess_prio = prio;
1104
1105 break;
1106 }
1107
1108 /* If the session was not found, or if the given adjustments were not OK,
1109 * do not send the changes out, but be done now.
1110 */
1111 if (!found || (!adj_down_ok && !adj_up_ok)) {
1112 shaper_table_lock(LOCK_UN);
1113
1114 if (!found)
1115 errno = ENOENT;
1116
1117 else if (!adj_down_ok) {
1118 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1119 "error modifying session: shares increment (%s%d) will drop "
1120 "session downshares (%u) below 1", downincr > 0 ? "+" : "", downincr,
1121 shaper_tab.def_downshares);
1122 errno = EINVAL;
1123
1124 } else if (!adj_up_ok) {
1125 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1126 "error modifying session: shares increment (%s%d) will drop "
1127 "session upshares (%u) below 1", upincr > 0 ? "+" : "", upincr,
1128 shaper_tab.def_upshares);
1129 errno = EINVAL;
1130 }
1131
1132 return -1;
1133 }
1134
1135 if (shaper_table_send() < 0) {
1136 int xerrno = errno;
1137
1138 shaper_table_lock(LOCK_UN);
1139 errno = xerrno;
1140 return -1;
1141 }
1142
1143 if (shaper_table_flush() < 0) {
1144 int xerrno = errno;
1145
1146 shaper_table_lock(LOCK_UN);
1147 errno = xerrno;
1148 return -1;
1149 }
1150
1151 shaper_table_lock(LOCK_UN);
1152 return 0;
1153 }
1154
shaper_table_sess_remove(pid_t sess_pid)1155 static int shaper_table_sess_remove(pid_t sess_pid) {
1156 register unsigned int i;
1157 int found = FALSE;
1158 struct shaper_sess *sess_list;
1159 array_header *new_sess_list;
1160
1161 if (shaper_table_lock(LOCK_EX) < 0)
1162 return -1;
1163
1164 if (shaper_table_refresh() < 0) {
1165 int xerrno = errno;
1166
1167 shaper_table_lock(LOCK_UN);
1168 errno = xerrno;
1169 return -1;
1170 }
1171
1172 if (shaper_tab.nsessions == 0) {
1173 /* No sessions in the ShaperTable to be removed. */
1174 shaper_table_lock(LOCK_UN);
1175 return 0;
1176 }
1177
1178 sess_list = shaper_tab.sess_list->elts;
1179 new_sess_list = make_array(shaper_tab_pool, 0, sizeof(struct shaper_sess));
1180
1181 for (i = 0; i < shaper_tab.nsessions; i++) {
1182 if (sess_list[i].sess_pid != sess_pid) {
1183 struct shaper_sess *sess = push_array(new_sess_list);
1184
1185 sess->sess_pid = sess_list[i].sess_pid;
1186 sess->sess_prio = sess_list[i].sess_prio;
1187 sess->sess_downincr = sess_list[i].sess_downincr;
1188 sess->sess_downrate = sess_list[i].sess_downrate;
1189 sess->sess_upincr = sess_list[i].sess_upincr;
1190 sess->sess_uprate = sess_list[i].sess_uprate;
1191
1192 } else
1193 found = TRUE;
1194 }
1195
1196 if (found)
1197 shaper_tab.nsessions--;
1198
1199 /* Replace the session list. The memory pointed to by the overwritten
1200 * pointer will be freed when shaper_tab_pool is freed, which will be
1201 * when the table is next refreshed.
1202 */
1203
1204 shaper_tab.sess_list = new_sess_list;
1205
1206 if (shaper_table_send() < 0) {
1207 int xerrno = errno;
1208
1209 shaper_table_lock(LOCK_UN);
1210 errno = xerrno;
1211 return -1;
1212 }
1213
1214 if (shaper_table_flush() < 0) {
1215 int xerrno = errno;
1216
1217 shaper_table_lock(LOCK_UN);
1218 errno = xerrno;
1219 return -1;
1220 }
1221
1222 shaper_table_lock(LOCK_UN);
1223 return 0;
1224 }
1225
1226 /* Control handlers
1227 */
1228
1229 /* usage: shaper all priority|rate|downrate|uprate|shares|downshares|upshares
1230 * val
1231 */
shaper_handle_all(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1232 static int shaper_handle_all(pr_ctrls_t *ctrl, int reqargc,
1233 char **reqargv) {
1234 register int i;
1235 int send_tab = TRUE;
1236
1237 if (reqargc < 2 ||
1238 reqargc > 14 ||
1239 reqargc % 2 != 0) {
1240 pr_ctrls_add_response(ctrl, "wrong number of parameters");
1241 return -1;
1242 }
1243
1244 if (shaper_table_lock(LOCK_EX) < 0) {
1245 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1246 "error write-locking ShaperTable: %s", strerror(errno));
1247 pr_ctrls_add_response(ctrl, "error handling request");
1248 return -1;
1249 }
1250
1251 if (shaper_table_refresh() < 0) {
1252 shaper_table_lock(LOCK_UN);
1253 pr_ctrls_add_response(ctrl, "error handling request");
1254 return -1;
1255 }
1256
1257 for (i = 0; i < reqargc;) {
1258 if (strcmp(reqargv[i], "downrate") == 0) {
1259 char *tmp;
1260 long double rate;
1261
1262 rate = strtod(reqargv[i+1], &tmp);
1263
1264 if (tmp && *tmp) {
1265 pr_ctrls_add_response(ctrl, "invalid downrate value (%s)",
1266 reqargv[i+1]);
1267 send_tab = FALSE;
1268 i += 2;
1269 continue;
1270 }
1271
1272 if (rate < 0.0) {
1273 pr_ctrls_add_response(ctrl, "downrate must be greater than 0 (%3.2Lf)",
1274 rate);
1275 send_tab = FALSE;
1276 i += 2;
1277 continue;
1278 }
1279
1280 shaper_tab.downrate = rate;
1281 pr_ctrls_add_response(ctrl, "overall downrate (%3.2Lf) set",
1282 shaper_tab.downrate);
1283
1284 i += 2;
1285
1286 } else if (strcmp(reqargv[i], "downshares") == 0) {
1287 int shares = atoi(reqargv[i+1]);
1288
1289 if (shares < 1) {
1290 pr_ctrls_add_response(ctrl, "downshares (%d) must be greater than 1",
1291 shares);
1292 send_tab = FALSE;
1293 i += 2;
1294 continue;
1295 }
1296
1297 shaper_tab.def_downshares = shares;
1298 pr_ctrls_add_response(ctrl, "default downshares (%u) set",
1299 shaper_tab.def_downshares);
1300
1301 i += 2;
1302
1303 } else if (strcmp(reqargv[i], "priority") == 0) {
1304 int prio = atoi(reqargv[i+1]);
1305
1306 if (prio < 0) {
1307 pr_ctrls_add_response(ctrl, "priority (%d) must be greater than 0",
1308 prio);
1309 send_tab = FALSE;
1310 i += 2;
1311 continue;
1312 }
1313
1314 shaper_tab.def_prio = prio;
1315 pr_ctrls_add_response(ctrl, "default priority (%u) set",
1316 shaper_tab.def_prio);
1317
1318 i += 2;
1319
1320 } else if (strcmp(reqargv[i], "rate") == 0) {
1321 char *tmp;
1322 long double rate;
1323
1324 rate = strtod(reqargv[i+1], &tmp);
1325
1326 if (tmp && *tmp) {
1327 pr_ctrls_add_response(ctrl, "invalid rate value (%s)", reqargv[i+1]);
1328 send_tab = FALSE;
1329 i += 2;
1330 continue;
1331 }
1332
1333 if (rate < 0.0) {
1334 pr_ctrls_add_response(ctrl, "rate must be greater than 0 (%3.2Lf)",
1335 rate);
1336 send_tab = FALSE;
1337 i += 2;
1338 continue;
1339 }
1340
1341 shaper_tab.downrate = rate;
1342 shaper_tab.uprate = rate;
1343 pr_ctrls_add_response(ctrl, "overall rates (%3.2Lf down, %3.2Lf up) set",
1344 shaper_tab.downrate, shaper_tab.uprate);
1345
1346 i += 2;
1347
1348 } else if (strcmp(reqargv[i], "shares") == 0) {
1349 int shares = atoi(reqargv[i+1]);
1350
1351 if (shares < 1) {
1352 pr_ctrls_add_response(ctrl, "shares (%d) must be greater than 1",
1353 shares);
1354 send_tab = FALSE;
1355 i += 2;
1356 continue;
1357 }
1358
1359 shaper_tab.def_downshares = shares;
1360 shaper_tab.def_upshares = shares;
1361 pr_ctrls_add_response(ctrl, "default shares (%u down, %u up) set",
1362 shaper_tab.def_downshares, shaper_tab.def_upshares);
1363
1364 i += 2;
1365
1366 } else if (strcmp(reqargv[i], "uprate") == 0) {
1367 char *tmp;
1368 long double rate;
1369
1370 rate = strtod(reqargv[i+1], &tmp);
1371
1372 if (tmp && *tmp) {
1373 pr_ctrls_add_response(ctrl, "invalid uprate value (%s)", reqargv[i+1]);
1374 send_tab = FALSE;
1375 i += 2;
1376 continue;
1377 }
1378
1379 if (rate < 0.0) {
1380 pr_ctrls_add_response(ctrl, "uprate must be greater than 0 (%3.2Lf)",
1381 rate);
1382 send_tab = FALSE;
1383 i += 2;
1384 continue;
1385 }
1386
1387 shaper_tab.uprate = rate;
1388 pr_ctrls_add_response(ctrl, "overall uprate (%3.2Lf) set",
1389 shaper_tab.uprate);
1390
1391 i += 2;
1392
1393 } else if (strcmp(reqargv[i], "upshares") == 0) {
1394 int shares = atoi(reqargv[i+1]);
1395
1396 if (shares < 1) {
1397 pr_ctrls_add_response(ctrl, "upshares (%d) must be greater than 1",
1398 shares);
1399 send_tab = FALSE;
1400 i += 2;
1401 continue;
1402 }
1403
1404 shaper_tab.def_upshares = shares;
1405 pr_ctrls_add_response(ctrl, "default upshares (%u) set",
1406 shaper_tab.def_upshares);
1407
1408 i += 2;
1409
1410 } else {
1411 pr_ctrls_add_response(ctrl, "unknown shaper all option '%s'",
1412 reqargv[i]);
1413 send_tab = FALSE;
1414 i += 2;
1415 continue;
1416 }
1417 }
1418
1419 if (!send_tab) {
1420 shaper_table_lock(LOCK_UN);
1421 return -1;
1422 }
1423
1424 if (shaper_table_send() < 0) {
1425 shaper_table_lock(LOCK_UN);
1426 pr_ctrls_add_response(ctrl, "error handling request");
1427 return -1;
1428 }
1429
1430 if (shaper_table_flush() < 0) {
1431 shaper_table_lock(LOCK_UN);
1432 pr_ctrls_add_response(ctrl, "error handling request");
1433 return -1;
1434 }
1435
1436 shaper_table_lock(LOCK_UN);
1437 return 0;
1438 }
1439
1440 /* usage: shaper info */
shaper_handle_info(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1441 static int shaper_handle_info(pr_ctrls_t *ctrl, int reqargc,
1442 char **reqargv) {
1443 register unsigned int i;
1444 struct shaper_sess *sess_list;
1445 unsigned int total_downshares = 0, total_upshares = 0;
1446 char *downbuf = NULL, *upbuf = NULL;
1447 size_t downbufsz = 14, upbufsz = 14;
1448
1449 if (shaper_table_lock(LOCK_SH) < 0) {
1450 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1451 "unable to read-lock ShaperTable: %s", strerror(errno));
1452 pr_ctrls_add_response(ctrl, "error handling request");
1453 return -1;
1454 }
1455
1456 if (shaper_table_refresh() < 0) {
1457 int xerrno = errno;
1458 shaper_table_lock(LOCK_UN);
1459
1460 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1461 "error refreshing ShaperTable: %s", strerror(xerrno));
1462 pr_ctrls_add_response(ctrl, "error handling request");
1463 return -1;
1464 }
1465
1466 pr_ctrls_add_response(ctrl, "Overall Rates: %3.2Lf KB/s down, %3.2Lf KB/s up",
1467 shaper_tab.downrate, shaper_tab.uprate);
1468 pr_ctrls_add_response(ctrl, "Default Shares Per Session: %u down, %u up",
1469 shaper_tab.def_downshares, shaper_tab.def_upshares);
1470 pr_ctrls_add_response(ctrl, "Default Priority: %u", shaper_tab.def_prio);
1471 pr_ctrls_add_response(ctrl, "Number of Shaped Sessions: %u",
1472 shaper_tab.nsessions);
1473
1474 sess_list = shaper_tab.sess_list->elts;
1475
1476 for (i = 0; i < shaper_tab.nsessions; i++) {
1477 total_downshares += (shaper_tab.def_downshares +
1478 sess_list[i].sess_downincr);
1479 total_upshares += (shaper_tab.def_upshares +
1480 sess_list[i].sess_upincr);
1481 }
1482
1483 if (shaper_tab.nsessions) {
1484 pr_ctrls_add_response(ctrl, "%-5s %8s %-14s %11s %-14s %11s",
1485 "PID", "Priority", "DShares", "DRate (KB/s)", "UShares", "URate (KB/s)");
1486 pr_ctrls_add_response(ctrl, "----- -------- -------------- ------------ -------------- ------------");
1487 downbuf = palloc(ctrl->ctrls_tmp_pool, downbufsz);
1488 upbuf = palloc(ctrl->ctrls_tmp_pool, upbufsz);
1489 }
1490
1491 for (i = 0; i < shaper_tab.nsessions; i++) {
1492 memset(downbuf, '\0', downbufsz);
1493 memset(upbuf, '\0', upbufsz);
1494
1495 pr_snprintf(downbuf, downbufsz, "%u/%u (%s%d)",
1496 shaper_tab.def_downshares + sess_list[i].sess_downincr, total_downshares,
1497 sess_list[i].sess_downincr > 0 ? "+" : "", sess_list[i].sess_downincr);
1498 downbuf[downbufsz-1] = '\0';
1499
1500 pr_snprintf(upbuf, upbufsz, "%u/%u (%s%d)",
1501 shaper_tab.def_upshares + sess_list[i].sess_upincr, total_upshares,
1502 sess_list[i].sess_upincr > 0 ? "+" : "", sess_list[i].sess_upincr);
1503 upbuf[upbufsz-1] = '\0';
1504
1505 pr_ctrls_add_response(ctrl, "%5u %8u %14s %11.2Lf %14s %11.2Lf",
1506 (unsigned int) sess_list[i].sess_pid, sess_list[i].sess_prio,
1507 downbuf, sess_list[i].sess_downrate, upbuf, sess_list[i].sess_uprate);
1508 }
1509
1510 shaper_table_lock(LOCK_UN);
1511 return 0;
1512 }
1513
1514 /* usage: shaper sess class|host|user name [priority prio] [shares incr]
1515 * [downshares incr] [upshares incr]
1516 */
shaper_handle_sess(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1517 static int shaper_handle_sess(pr_ctrls_t *ctrl, int reqargc,
1518 char **reqargv) {
1519 register int i;
1520 int adjusted = FALSE, send_tab = TRUE;
1521 int prio = -1, downincr = 0, upincr = 0;
1522
1523 if (reqargc < 4 ||
1524 reqargc > 6 ||
1525 reqargc % 2 != 0) {
1526 pr_ctrls_add_response(ctrl, "wrong number of parameters");
1527 return -1;
1528 }
1529
1530 for (i = 2; i < reqargc;) {
1531 if (strcmp(reqargv[i], "downshares") == 0) {
1532
1533 if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1534 pr_ctrls_add_response(ctrl,
1535 "downshares (%s) must start with '+' or '-'", reqargv[i+1]);
1536 return -1;
1537 }
1538
1539 downincr = atoi(reqargv[i+1]);
1540
1541 if (downincr == 0) {
1542 pr_ctrls_add_response(ctrl, "downshares cannot be 0");
1543 send_tab = FALSE;
1544 i += 2;
1545 continue;
1546 }
1547
1548 pr_ctrls_add_response(ctrl, "adjusted session downshares by %s%d",
1549 downincr > 0 ? "+" : "", downincr);
1550
1551 i += 2;
1552
1553 } else if (strcmp(reqargv[i], "priority") == 0) {
1554 prio = atoi(reqargv[i+1]);
1555
1556 if (prio < 0) {
1557 pr_ctrls_add_response(ctrl, "priority (%d) must be greater than 0",
1558 prio);
1559 send_tab = FALSE;
1560 i += 2;
1561 continue;
1562 }
1563
1564 pr_ctrls_add_response(ctrl, "set session priority to %u", prio);
1565 i += 2;
1566
1567 } else if (strcmp(reqargv[i], "shares") == 0) {
1568 int incr;
1569
1570 if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1571 pr_ctrls_add_response(ctrl, "shares (%s) must start with '+' or '-'",
1572 reqargv[i+1]);
1573 return -1;
1574 }
1575
1576 incr = atoi(reqargv[i+1]);
1577
1578 if (incr == 0) {
1579 pr_ctrls_add_response(ctrl, "shares cannot be 0");
1580 send_tab = FALSE;
1581 i += 2;
1582 continue;
1583 }
1584
1585 pr_ctrls_add_response(ctrl,
1586 "adjusted session downshares and upshares by %s%d",
1587 incr > 0 ? "+" : "", incr);
1588
1589 downincr = upincr = incr;
1590 i += 2;
1591
1592 } else if (strcmp(reqargv[i], "upshares") == 0) {
1593
1594 if (*reqargv[i+1] != '+' && *reqargv[i+1] != '-') {
1595 pr_ctrls_add_response(ctrl,
1596 "upshares (%s) must start with '+' or '-'", reqargv[i+1]);
1597 return -1;
1598 }
1599
1600 upincr = atoi(reqargv[i+1]);
1601
1602 if (upincr == 0) {
1603 pr_ctrls_add_response(ctrl, "upshares cannot be 0");
1604 send_tab = FALSE;
1605 i += 2;
1606 continue;
1607 }
1608
1609 pr_ctrls_add_response(ctrl, "adjusted session upshares by %s%d",
1610 upincr > 0 ? "+" : "", upincr);
1611
1612 i += 2;
1613
1614 } else {
1615 pr_ctrls_add_response(ctrl, "unknown shaper session option '%s'",
1616 reqargv[i]);
1617 send_tab = FALSE;
1618 i += 2;
1619 continue;
1620 }
1621 }
1622
1623 if (!send_tab)
1624 return -1;
1625
1626 /* Sessions that are not shaped (i.e. excluded from mod_shaper) cannot be
1627 * adjusted. If exempted at login time, they cannot later be shaped.
1628 */
1629 /* XXX add ability to add non-shaped session to ShaperTable at
1630 * post-login time? And/or remove a session from ShaperTable before
1631 * session exit?
1632 */
1633
1634 if (strcmp(reqargv[0], "user") == 0) {
1635 pr_scoreboard_entry_t *score;
1636 const char *user = reqargv[1];
1637
1638 if (pr_rewind_scoreboard() < 0)
1639 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1640 "error rewinding scoreboard: %s", strerror(errno));
1641
1642 while ((score = pr_scoreboard_entry_read()) != NULL) {
1643 pr_signals_handle();
1644
1645 if (strcmp(score->sce_user, user) == 0) {
1646 if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1647 upincr) < 0) {
1648 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1649 "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1650 strerror(errno));
1651 pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1652 (unsigned int) score->sce_pid, strerror(errno));
1653
1654 } else
1655 adjusted = TRUE;
1656 }
1657 }
1658
1659 pr_restore_scoreboard();
1660
1661 } else if (strcmp(reqargv[0], "host") == 0) {
1662 pr_scoreboard_entry_t *score;
1663 const char *addr;
1664 const pr_netaddr_t *na;
1665
1666 na = pr_netaddr_get_addr(ctrl->ctrls_tmp_pool, reqargv[1], NULL);
1667 if (na == NULL) {
1668 pr_ctrls_add_response(ctrl, "error resolving '%s': %s", reqargv[1],
1669 strerror(errno));
1670 return -1;
1671 }
1672
1673 addr = pr_netaddr_get_ipstr(na);
1674
1675 if (pr_rewind_scoreboard() < 0)
1676 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1677 "error rewinding scoreboard: %s", strerror(errno));
1678
1679 while ((score = pr_scoreboard_entry_read()) != NULL) {
1680 pr_signals_handle();
1681
1682 if (strcmp(score->sce_client_addr, addr) == 0) {
1683 if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1684 upincr) < 0) {
1685 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1686 "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1687 strerror(errno));
1688 pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1689 (unsigned int) score->sce_pid, strerror(errno));
1690
1691 } else {
1692 adjusted = TRUE;
1693 }
1694 }
1695 }
1696
1697 pr_restore_scoreboard();
1698
1699 } else if (strcmp(reqargv[0], "class") == 0) {
1700 pr_scoreboard_entry_t *score;
1701 const char *class = reqargv[1];
1702
1703 if (pr_rewind_scoreboard() < 0)
1704 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1705 "error rewinding scoreboard: %s", strerror(errno));
1706
1707 while ((score = pr_scoreboard_entry_read()) != NULL) {
1708 pr_signals_handle();
1709
1710 if (strcmp(score->sce_class, class) == 0) {
1711 if (shaper_table_sess_modify(score->sce_pid, prio, downincr,
1712 upincr) < 0) {
1713 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
1714 "error adjusting pid %u: %s", (unsigned int) score->sce_pid,
1715 strerror(errno));
1716 pr_ctrls_add_response(ctrl, "error adjusting pid %u: %s",
1717 (unsigned int) score->sce_pid, strerror(errno));
1718
1719 } else {
1720 adjusted = TRUE;
1721 }
1722 }
1723 }
1724
1725 pr_restore_scoreboard();
1726
1727 } else {
1728 pr_ctrls_add_response(ctrl, "unknown shaper session target type: '%s'",
1729 reqargv[0]);
1730 return -1;
1731 }
1732
1733 if (adjusted) {
1734 pr_ctrls_add_response(ctrl, "sessions adjusted");
1735 }
1736
1737 return 0;
1738 }
1739
shaper_handle_shaper(pr_ctrls_t * ctrl,int reqargc,char ** reqargv)1740 static int shaper_handle_shaper(pr_ctrls_t *ctrl, int reqargc,
1741 char **reqargv) {
1742
1743 /* Sanity check */
1744 if (reqargc == 0 || reqargv == NULL) {
1745 pr_ctrls_add_response(ctrl, "shaper: missing required parameters");
1746 return -1;
1747 }
1748
1749 if (strcmp(reqargv[0], "all") == 0) {
1750
1751 /* Check the all ACL */
1752 if (!ctrls_check_acl(ctrl, shaper_acttab, "all")) {
1753
1754 /* Access denied */
1755 pr_ctrls_add_response(ctrl, "access denied");
1756 return -1;
1757 }
1758
1759 return shaper_handle_all(ctrl, --reqargc, ++reqargv);
1760
1761 } else if (strcmp(reqargv[0], "info") == 0) {
1762
1763 /* Check the info ACL */
1764 if (!ctrls_check_acl(ctrl, shaper_acttab, "info")) {
1765
1766 /* Access denied */
1767 pr_ctrls_add_response(ctrl, "access denied");
1768 return -1;
1769 }
1770
1771 return shaper_handle_info(ctrl, --reqargc, ++reqargv);
1772
1773 } else if (strcmp(reqargv[0], "sess") == 0) {
1774
1775 /* Check the sess ACL */
1776 if (!ctrls_check_acl(ctrl, shaper_acttab, "sess")) {
1777
1778 /* Access denied */
1779 pr_ctrls_add_response(ctrl, "access denied");
1780 return -1;
1781 }
1782
1783 return shaper_handle_sess(ctrl, --reqargc, ++reqargv);
1784 }
1785
1786 pr_ctrls_add_response(ctrl, "unknown shaper action: '%s'", reqargv[0]);
1787 return -1;
1788 }
1789
1790 /* Configuration handlers
1791 */
1792
1793 /* usage: ShaperAll [priority prio] [rate rate] [downrate rate] [uprate rate]
1794 * [shares nshares] [downshares nshares] [upshares nshares]
1795 */
set_shaperall(cmd_rec * cmd)1796 MODRET set_shaperall(cmd_rec *cmd) {
1797 register unsigned int i;
1798
1799 if (cmd->argc-1 < 2 || cmd->argc-1 > 14 || (cmd->argc-1) % 2 != 0)
1800 CONF_ERROR(cmd, "wrong number of parameters");
1801
1802 CHECK_CONF(cmd, CONF_ROOT);
1803
1804 for (i = 1; i < cmd->argc;) {
1805 if (strcmp(cmd->argv[i], "downrate") == 0) {
1806 char *tmp;
1807 long double rate;
1808
1809 rate = strtod(cmd->argv[i+1], &tmp);
1810
1811 if (tmp && *tmp)
1812 CONF_ERROR(cmd, "invalid downrate parameter");
1813
1814 if (rate < 0.0)
1815 CONF_ERROR(cmd, "downrate must be greater than 0");
1816
1817 shaper_tab.downrate = rate;
1818 i += 2;
1819
1820 } else if (strcmp(cmd->argv[i], "downshares") == 0) {
1821 int shares = atoi(cmd->argv[i+1]);
1822
1823 if (shares < 1)
1824 CONF_ERROR(cmd, "downshares must be greater than 1");
1825
1826 shaper_tab.def_downshares = shares;
1827 i += 2;
1828
1829 } else if (strcmp(cmd->argv[i], "priority") == 0) {
1830 int prio = atoi(cmd->argv[i+1]);
1831
1832 if (prio < 0)
1833 CONF_ERROR(cmd, "priority must be greater than 0");
1834
1835 shaper_tab.def_prio = prio;
1836 i += 2;
1837
1838 } else if (strcmp(cmd->argv[i], "rate") == 0) {
1839 char *tmp;
1840 long double rate;
1841
1842 rate = strtod(cmd->argv[i+1], &tmp);
1843
1844 if (tmp && *tmp)
1845 CONF_ERROR(cmd, "invalid rate parameter");
1846
1847 if (rate < 0.0)
1848 CONF_ERROR(cmd, "rate must be greater than 0");
1849
1850 shaper_tab.downrate = rate;
1851 shaper_tab.uprate = rate;
1852 i += 2;
1853
1854 } else if (strcmp(cmd->argv[i], "shares") == 0) {
1855 int shares = atoi(cmd->argv[i+1]);
1856
1857 if (shares < 1)
1858 CONF_ERROR(cmd, "shares must be greater than 1");
1859
1860 shaper_tab.def_downshares = shares;
1861 shaper_tab.def_upshares = shares;
1862 i += 2;
1863
1864 } else if (strcmp(cmd->argv[i], "uprate") == 0) {
1865 char *tmp;
1866 long double rate;
1867
1868 rate = strtod(cmd->argv[i+1], &tmp);
1869
1870 if (tmp && *tmp)
1871 CONF_ERROR(cmd, "invalid uprate parameter");
1872
1873 if (rate < 0.0)
1874 CONF_ERROR(cmd, "uprate must be greater than 0");
1875
1876 shaper_tab.uprate = rate;
1877 i += 2;
1878
1879 } else if (strcmp(cmd->argv[i], "upshares") == 0) {
1880 int shares = atoi(cmd->argv[i+1]);
1881
1882 if (shares < 1)
1883 CONF_ERROR(cmd, "upshares must be greater than 1");
1884
1885 shaper_tab.def_upshares = shares;
1886 i += 2;
1887
1888 } else
1889 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown option: '",
1890 cmd->argv[i], "'", NULL));
1891 }
1892
1893 return PR_HANDLED(cmd);
1894 }
1895
1896 /* usage: ShaperControlsACLs actions|all allow|deny user|group list */
set_shaperctrlsacls(cmd_rec * cmd)1897 MODRET set_shaperctrlsacls(cmd_rec *cmd) {
1898 char *bad_action = NULL, **actions = NULL;
1899
1900 CHECK_ARGS(cmd, 4);
1901 CHECK_CONF(cmd, CONF_ROOT);
1902
1903 actions = ctrls_parse_acl(cmd->tmp_pool, cmd->argv[1]);
1904
1905 /* Check the second parameter to make sure it is "allow" or "deny" */
1906 if (strcmp(cmd->argv[2], "allow") != 0 &&
1907 strcmp(cmd->argv[2], "deny") != 0)
1908 CONF_ERROR(cmd, "second parameter must be 'allow' or 'deny'");
1909
1910 /* Check the third parameter to make sure it is "user" or "group" */
1911 if (strcmp(cmd->argv[3], "user") != 0 &&
1912 strcmp(cmd->argv[3], "group") != 0)
1913 CONF_ERROR(cmd, "third parameter must be 'user' or 'group'");
1914
1915 bad_action = ctrls_set_module_acls(shaper_acttab, shaper_pool, actions,
1916 cmd->argv[2], cmd->argv[3], cmd->argv[4]);
1917 if (bad_action != NULL)
1918 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown shaper action: '",
1919 bad_action, "'", NULL));
1920
1921 return PR_HANDLED(cmd);
1922 }
1923
1924 /* usage: ShaperEngine on|off */
set_shaperengine(cmd_rec * cmd)1925 MODRET set_shaperengine(cmd_rec *cmd) {
1926 int bool;
1927 config_rec *c;
1928
1929 CHECK_ARGS(cmd, 1);
1930 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1931
1932 bool = get_boolean(cmd, 1);
1933 if (bool == -1)
1934 CONF_ERROR(cmd, "expected Boolean parameter");
1935
1936 c = add_config_param(cmd->argv[0], 1, NULL);
1937 c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
1938 *((unsigned int *) c->argv[0]) = bool;
1939 c->flags |= CF_MERGEDOWN;
1940
1941 return PR_HANDLED(cmd);
1942 }
1943
1944 /* usage: ShaperLog path|"none" */
set_shaperlog(cmd_rec * cmd)1945 MODRET set_shaperlog(cmd_rec *cmd) {
1946 CHECK_ARGS(cmd, 1);
1947 CHECK_CONF(cmd, CONF_ROOT);
1948
1949 if (strcasecmp(cmd->argv[0], "none") != 0 &&
1950 pr_fs_valid_path(cmd->argv[1]) < 0) {
1951 CONF_ERROR(cmd, "must be an absolute path");
1952 }
1953
1954 shaper_log_path = pstrdup(shaper_pool, cmd->argv[1]);
1955 return PR_HANDLED(cmd);
1956 }
1957
1958 /* usage: ShaperSession [priority prio] [shares num] [downshares num]
1959 * [upshares num]
1960 */
set_shapersession(cmd_rec * cmd)1961 MODRET set_shapersession(cmd_rec *cmd) {
1962 int prio = -1;
1963 int downshares = 0, upshares = 0;
1964 config_rec *c;
1965
1966 register unsigned int i;
1967
1968 if (cmd->argc-1 < 2 ||
1969 cmd->argc-1 > 8 ||
1970 (cmd->argc-1) % 2 != 0) {
1971 CONF_ERROR(cmd, "wrong number of parameters");
1972 }
1973
1974 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL|CONF_ANON);
1975
1976 for (i = 1; i < cmd->argc;) {
1977 if (strcmp(cmd->argv[i], "downshares") == 0) {
1978 char *shareno;
1979
1980 shareno = cmd->argv[i+1];
1981 if (*shareno != '+' &&
1982 *shareno != '-') {
1983 CONF_ERROR(cmd, "downshares parameter must start with '+' or '-'");
1984 }
1985
1986 downshares = atoi(shareno);
1987 i += 2;
1988
1989 } else if (strcmp(cmd->argv[i], "priority") == 0) {
1990 prio = atoi(cmd->argv[i+1]);
1991 if (prio < 0) {
1992 CONF_ERROR(cmd, "priority must be greater than 0");
1993 }
1994
1995 i += 2;
1996
1997 } else if (strcmp(cmd->argv[i], "shares") == 0) {
1998 char *shareno;
1999
2000 shareno = cmd->argv[i+1];
2001 if (*shareno != '+' &&
2002 *shareno != '-') {
2003 CONF_ERROR(cmd, "shares parameter must start with '+' or '-'");
2004 }
2005
2006 downshares = upshares = atoi(shareno);
2007 i += 2;
2008
2009 } else if (strcmp(cmd->argv[i], "upshares") == 0) {
2010 char *shareno;
2011
2012 shareno = cmd->argv[i+1];
2013 if (*shareno != '+' &&
2014 *shareno != '-') {
2015 CONF_ERROR(cmd, "upshares parameter must start with '+' or '-'");
2016 }
2017
2018 upshares = atoi(shareno);
2019 i += 2;
2020
2021 } else {
2022 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown option: '",
2023 (char *) cmd->argv[i], "'", NULL));
2024 }
2025 }
2026
2027 c = add_config_param(cmd->argv[0], 3, NULL, NULL);
2028 c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
2029 *((unsigned int *) c->argv[0]) = (unsigned int) prio;
2030 c->argv[1] = pcalloc(c->pool, sizeof(int));
2031 *((int *) c->argv[1]) = downshares;
2032 c->argv[2] = pcalloc(c->pool, sizeof(int));
2033 *((int *) c->argv[2]) = upshares;
2034 c->flags |= CF_MERGEDOWN;
2035
2036 return PR_HANDLED(cmd);
2037 }
2038
2039 /* usage: ShaperTable path */
set_shapertable(cmd_rec * cmd)2040 MODRET set_shapertable(cmd_rec *cmd) {
2041 CHECK_ARGS(cmd, 1);
2042 CHECK_CONF(cmd, CONF_ROOT);
2043
2044 if (pr_fs_valid_path(cmd->argv[1]) < 0) {
2045 CONF_ERROR(cmd, "must be an absolute path");
2046 }
2047
2048 shaper_tab_path = pstrdup(shaper_pool, cmd->argv[1]);
2049 return PR_HANDLED(cmd);
2050 }
2051
2052 /* Command handlers
2053 */
2054
shaper_pre_pass(cmd_rec * cmd)2055 MODRET shaper_pre_pass(cmd_rec *cmd) {
2056
2057 /* Make sure this session process has the ShaperTable open.
2058 *
2059 * NOTE: I'm not sure this is needed anymore, since an fd to the
2060 * ShaperTable is opened during the 'core.postparse' event handler,
2061 * in the daemon process.
2062 */
2063
2064 PRIVS_ROOT
2065 shaper_tabfd = open(shaper_tab_path, O_RDWR);
2066 PRIVS_RELINQUISH
2067
2068 if (shaper_tabfd < 0)
2069 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2070 "unable to open ShaperTable: %s", strerror(errno));
2071
2072 return PR_DECLINED(cmd);
2073 }
2074
shaper_post_pass(cmd_rec * cmd)2075 MODRET shaper_post_pass(cmd_rec *cmd) {
2076 config_rec *c;
2077 int downincr = 0, upincr = 0;
2078 unsigned int prio = -1;
2079
2080 c = find_config(TOPLEVEL_CONF, CONF_PARAM, "ShaperEngine", FALSE);
2081 if (c != NULL &&
2082 *((unsigned char *) c->argv[0]) == TRUE) {
2083 shaper_engine = TRUE;
2084
2085 } else {
2086 /* Don't need the ShaperTable open anymore. */
2087 close(shaper_tabfd);
2088 shaper_tabfd = -1;
2089
2090 return PR_DECLINED(cmd);
2091 }
2092
2093 if (!shaper_tab_path) {
2094 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2095 "ShaperTable not configured, disabling ShaperEngine");
2096 shaper_engine = FALSE;
2097 return PR_DECLINED(cmd);
2098 }
2099
2100 if (shaper_tabfd < 0) {
2101 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2102 "ShaperTable not open, disabling ShaperEngine");
2103 shaper_engine = FALSE;
2104 return PR_DECLINED(cmd);
2105 }
2106
2107 if (shaper_tab.downrate < 0.0 || shaper_tab.uprate < 0.0) {
2108 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2109 "overall rates negative or not configured, disabling ShaperEngine");
2110 shaper_engine = FALSE;
2111 return PR_DECLINED(cmd);
2112 }
2113
2114 pr_event_register(&shaper_module, "core.exit", shaper_sess_exit_ev, NULL);
2115 pr_event_register(&shaper_module, "core.signal.USR2", shaper_sigusr2_ev,
2116 NULL);
2117
2118 c = find_config(TOPLEVEL_CONF, CONF_PARAM, "ShaperSession", FALSE);
2119 if (c) {
2120 prio = *((unsigned int *) c->argv[0]);
2121 downincr = *((int *) c->argv[1]);
2122 upincr = *((int *) c->argv[2]);
2123 }
2124
2125 /* Update the ShaperTable, adding a new entry for the current session. */
2126 if (shaper_table_sess_add(getpid(), prio, downincr, upincr) < 0) {
2127 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2128 "error adding session to ShaperTable: %s", strerror(errno));
2129 }
2130
2131 return PR_DECLINED(cmd);
2132 }
2133
shaper_post_err_pass(cmd_rec * cmd)2134 MODRET shaper_post_err_pass(cmd_rec *cmd) {
2135
2136 /* Close the ShaperTable if we failed to authenticate. */
2137 close(shaper_tabfd);
2138 shaper_tabfd = -1;
2139
2140 return PR_DECLINED(cmd);
2141 }
2142
2143 /* Event handlers
2144 */
2145
shaper_shutdown_ev(const void * event_data,void * user_data)2146 static void shaper_shutdown_ev(const void *event_data, void *user_data) {
2147
2148 /* Remove the queue from the system, and delete the ShaperTable. We can
2149 * only do this reliably when the standalone daemon process exits; if it's
2150 * an inetd process, there may be other proftpd processes still running.
2151 */
2152 if (getpid() == mpid &&
2153 ServerType == SERVER_STANDALONE) {
2154
2155 if (shaper_qid >= 0) {
2156 shaper_remove_queue();
2157 }
2158
2159 if (shaper_tab_path) {
2160 if (pr_fsio_unlink(shaper_tab_path) < 0) {
2161 pr_log_debug(DEBUG9, MOD_SHAPER_VERSION
2162 ": error unlinking '%s': %s", shaper_tab_path, strerror(errno));
2163 }
2164 }
2165 }
2166
2167 return;
2168 }
2169
shaper_sess_exit_ev(const void * event_data,void * user_data)2170 static void shaper_sess_exit_ev(const void *event_data, void *user_data) {
2171
2172 /* Remove this session from the ShaperTable. */
2173 if (shaper_table_sess_remove(getpid()) < 0) {
2174 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2175 "error removing session from ShaperTable: %s", strerror(errno));
2176 }
2177
2178 /* Clear any messages for this session from the queue as well. */
2179 shaper_msg_clear(getpid());
2180
2181 return;
2182 }
2183
2184 #if defined(PR_SHARED_MODULE)
shaper_mod_unload_ev(const void * event_data,void * user_data)2185 static void shaper_mod_unload_ev(const void *event_data, void *user_data) {
2186 if (strcmp("mod_shaper.c", (const char *) event_data) == 0) {
2187 /* Unregister ourselves from all events. */
2188 pr_event_unregister(&shaper_module, NULL, NULL);
2189
2190 /* Unregister all control actions. */
2191 (void) pr_ctrls_unregister(&shaper_module, "shaper");
2192
2193 if (shaper_scrub_timer_id != -1) {
2194 (void) pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2195 shaper_scrub_timer_id = -1;
2196 }
2197
2198 if (shaper_pool) {
2199 destroy_pool(shaper_pool);
2200 shaper_pool = NULL;
2201 shaper_tab_pool = NULL;
2202 shaper_tab.sess_list = NULL;
2203 }
2204 }
2205 }
2206 #endif /* PR_SHARED_MODULE */
2207
shaper_postparse_ev(const void * event_data,void * user_data)2208 static void shaper_postparse_ev(const void *event_data, void *user_data) {
2209 if (shaper_log_path &&
2210 strcasecmp(shaper_log_path, "none") != 0 &&
2211 pr_log_openfile(shaper_log_path, &shaper_logfd, 0660) < 0) {
2212 pr_log_debug(DEBUG2, MOD_SHAPER_VERSION
2213 ": error opening ShaperLog '%s': %s", shaper_log_path, strerror(errno));
2214 shaper_logfd = -1;
2215 }
2216
2217 if (shaper_tab_path) {
2218 pr_fh_t *fh;
2219 int xerrno;
2220 struct stat st;
2221
2222 PRIVS_ROOT
2223 fh = pr_fsio_open(shaper_tab_path, O_RDWR|O_CREAT);
2224 xerrno = errno;
2225 PRIVS_RELINQUISH
2226
2227 if (fh == NULL) {
2228 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2229 "error opening ShaperTable '%s': %s", shaper_tab_path,
2230 strerror(xerrno));
2231 pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2232 ": error opening ShaperTable '%s': %s", shaper_tab_path,
2233 strerror(xerrno));
2234 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2235 NULL);
2236 }
2237
2238 if (pr_fsio_fstat(fh, &st) < 0) {
2239 xerrno = errno;
2240
2241 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2242 "error checking ShaperTable '%s': %s", shaper_tab_path,
2243 strerror(xerrno));
2244 pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2245 ": error checking ShaperTable '%s': %s", shaper_tab_path,
2246 strerror(xerrno));
2247
2248 pr_fsio_close(fh);
2249 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2250 NULL);
2251 }
2252
2253 if (S_ISDIR(st.st_mode)) {
2254 xerrno = EISDIR;
2255
2256 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2257 "error using ShaperTable '%s': %s", shaper_tab_path,
2258 strerror(xerrno));
2259 pr_log_debug(DEBUG0, MOD_SHAPER_VERSION
2260 ": error using ShaperTable '%s': %s", shaper_tab_path,
2261 strerror(xerrno));
2262
2263 pr_fsio_close(fh);
2264 pr_session_disconnect(&shaper_module, PR_SESS_DISCONNECT_BAD_CONFIG,
2265 NULL);
2266 }
2267
2268 /* Initialize ShaperTable */
2269 if (shaper_table_init(fh) < 0)
2270 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2271 "error initializing ShaperTable: %s", strerror(errno));
2272
2273 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2274 "determining queue ID for path '%s'", shaper_tab_path);
2275
2276 shaper_qid = shaper_get_queue(shaper_tab_path);
2277 if (shaper_qid < 0) {
2278 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2279 "error obtaining queue ID: %s", strerror(errno));
2280
2281 } else {
2282 struct msqid_ds ds;
2283
2284 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2285 "obtained queue ID %d", shaper_qid);
2286
2287 if (msgctl(shaper_qid, IPC_STAT, &ds) < 0) {
2288 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2289 "error checking queue ID %d: %s", shaper_qid, strerror(errno));
2290
2291 } else {
2292 shaper_qmaxbytes = ds.msg_qbytes;
2293 }
2294
2295 /* We could be being restarted, in which case we want to send our
2296 * reinitialized table to existing sessions.
2297 */
2298
2299 if (shaper_table_lock(LOCK_EX) < 0) {
2300 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2301 "error locking ShaperTable: %s", strerror(errno));
2302 return;
2303 }
2304
2305 if (shaper_table_refresh() < 0) {
2306 shaper_table_lock(LOCK_UN);
2307 return;
2308 }
2309
2310 if (shaper_table_send() < 0) {
2311 shaper_table_lock(LOCK_UN);
2312 return;
2313 }
2314
2315 if (shaper_table_flush() < 0) {
2316 shaper_table_lock(LOCK_UN);
2317 return;
2318 }
2319
2320 shaper_table_lock(LOCK_UN);
2321 }
2322
2323 if (shaper_scrub_timer_id == -1) {
2324 shaper_scrub_timer_id = pr_timer_add(SHAPER_SCRUB_INTERVAL, -1,
2325 &shaper_module, shaper_table_scrub_cb, "shaper table scrubber");
2326 }
2327
2328 } else {
2329 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2330 "no ShaperTable configured");
2331 }
2332
2333 }
2334
shaper_restart_ev(const void * event_data,void * user_data)2335 static void shaper_restart_ev(const void *event_data, void *user_data) {
2336 register unsigned int i;
2337
2338 (void) close(shaper_logfd);
2339 shaper_logfd = -1;
2340 shaper_log_path = NULL;
2341
2342 if (shaper_pool) {
2343 destroy_pool(shaper_pool);
2344
2345 /* Make sure to mark subpools as invalid now as well. */
2346 shaper_tab_pool = NULL;
2347 shaper_tab.sess_list = NULL;
2348 }
2349
2350 shaper_pool = make_sub_pool(permanent_pool);
2351 pr_pool_tag(shaper_pool, MOD_SHAPER_VERSION);
2352
2353 for (i = 0; shaper_acttab[i].act_action; i++) {
2354 shaper_acttab[i].act_acl = pcalloc(shaper_pool, sizeof(ctrls_acl_t));
2355 ctrls_init_acl(shaper_acttab[i].act_acl);
2356 }
2357
2358 if (shaper_scrub_timer_id != -1) {
2359 (void) pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2360 shaper_scrub_timer_id = -1;
2361 }
2362
2363 return;
2364 }
2365
shaper_sigusr2_ev(const void * event_data,void * user_data)2366 static void shaper_sigusr2_ev(const void *event_data, void *user_data) {
2367 int res;
2368
2369 /* Check the queue for any messages for us. */
2370 res = shaper_msg_recv();
2371
2372 switch (res) {
2373 case -1:
2374 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2375 "error receiving updates for pid %lu: %s", (unsigned long) getpid(),
2376 strerror(errno));
2377 break;
2378
2379 case 0:
2380 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2381 "received signal, no updates for pid %lu", (unsigned long) getpid());
2382 break;
2383
2384 default:
2385 (void) pr_log_writefile(shaper_logfd, MOD_SHAPER_VERSION,
2386 "received signal, read in %d %s for pid %lu", res,
2387 res == 1 ? "update" : "updates", (unsigned long) getpid());
2388 }
2389
2390 return;
2391 }
2392
2393 /* Initialization functions
2394 */
2395
shaper_init(void)2396 static int shaper_init(void) {
2397
2398 shaper_pool = make_sub_pool(permanent_pool);
2399 pr_pool_tag(shaper_pool, MOD_SHAPER_VERSION);
2400
2401 shaper_tab.def_prio = SHAPER_DEFAULT_PRIO;
2402 shaper_tab.downrate = SHAPER_DEFAULT_RATE;
2403 shaper_tab.def_downshares = SHAPER_DEFAULT_DOWNSHARES;
2404 shaper_tab.uprate = SHAPER_DEFAULT_RATE;
2405 shaper_tab.def_upshares = SHAPER_DEFAULT_UPSHARES;
2406 shaper_tab.nsessions = 0;
2407
2408 if (pr_ctrls_register(&shaper_module, "shaper", "tune mod_shaper settings",
2409 shaper_handle_shaper) < 0) {
2410 pr_log_pri(PR_LOG_NOTICE, MOD_SHAPER_VERSION
2411 ": error registering 'shaper' control: %s", strerror(errno));
2412
2413 } else {
2414 register unsigned int i;
2415
2416 for (i = 0; shaper_acttab[i].act_action; i++) {
2417 shaper_acttab[i].act_acl = pcalloc(shaper_pool, sizeof(ctrls_acl_t));
2418 ctrls_init_acl(shaper_acttab[i].act_acl);
2419 }
2420 }
2421
2422 #if defined(PR_SHARED_MODULE)
2423 pr_event_register(&shaper_module, "core.module-unload", shaper_mod_unload_ev,
2424 NULL);
2425 #endif /* PR_SHARED_MODULE */
2426 pr_event_register(&shaper_module, "core.postparse", shaper_postparse_ev,
2427 NULL);
2428 pr_event_register(&shaper_module, "core.restart", shaper_restart_ev, NULL);
2429 pr_event_register(&shaper_module, "core.shutdown", shaper_shutdown_ev, NULL);
2430
2431 return 0;
2432 }
2433
shaper_sess_init(void)2434 static int shaper_sess_init(void) {
2435
2436 /* The ShaperTable scrubbing timer should only run in the daemon. */
2437 pr_timer_remove(shaper_scrub_timer_id, &shaper_module);
2438
2439 return 0;
2440 }
2441
2442 /* Module API tables
2443 */
2444
2445 static ctrls_acttab_t shaper_acttab[] = {
2446 { "all", NULL, NULL, NULL },
2447 { "info", NULL, NULL, NULL },
2448 { "sess", NULL, NULL, NULL },
2449 { NULL, NULL, NULL, NULL }
2450 };
2451
2452 static conftable shaper_conftab[] = {
2453 { "ShaperAll", set_shaperall, NULL },
2454 { "ShaperControlsACLs", set_shaperctrlsacls, NULL },
2455 { "ShaperEngine", set_shaperengine, NULL },
2456 { "ShaperLog", set_shaperlog, NULL },
2457 { "ShaperSession", set_shapersession, NULL },
2458 { "ShaperTable", set_shapertable, NULL },
2459 { NULL }
2460 };
2461
2462 static cmdtable shaper_cmdtab[] = {
2463 { PRE_CMD, C_PASS, G_NONE, shaper_pre_pass, FALSE, FALSE },
2464 { POST_CMD, C_PASS, G_NONE, shaper_post_pass, FALSE, FALSE },
2465 { POST_CMD_ERR, C_PASS, G_NONE, shaper_post_err_pass, FALSE, FALSE },
2466 { 0, NULL }
2467 };
2468
2469 module shaper_module = {
2470 NULL, NULL,
2471
2472 /* Module API version 2.0 */
2473 0x20,
2474
2475 /* Module name */
2476 "shaper",
2477
2478 /* Module configuration handler table */
2479 shaper_conftab,
2480
2481 /* Module command handler table */
2482 shaper_cmdtab,
2483
2484 /* Module authentication handler table */
2485 NULL,
2486
2487 /* Module initialization function */
2488 shaper_init,
2489
2490 /* Session initialization function */
2491 shaper_sess_init,
2492
2493 /* Module version */
2494 MOD_SHAPER_VERSION
2495 };
2496