1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 2008-2017 The ProFTPD Project team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 *
19 * As a special exemption, The ProFTPD Project and other respective copyright
20 * holders give permission to link this program with OpenSSL, and distribute
21 * the resulting executable, without including the source code for OpenSSL in
22 * the source distribution.
23 */
24
25 /* TransferRate throttling */
26
27 #include "conf.h"
28
29 /* Transfer rate variables */
30 static long double xfer_rate_kbps = 0.0, xfer_rate_bps = 0.0;
31 static off_t xfer_rate_freebytes = 0.0;
32 static int have_xfer_rate = FALSE;
33 static unsigned int xfer_rate_scoreboard_updates = 0;
34
35 /* Very similar to the {block,unblock}_signals() function, this masks most
36 * of the same signals -- except for TERM. This allows a throttling process
37 * to be killed by the admin.
38 */
xfer_rate_sigmask(int block)39 static void xfer_rate_sigmask(int block) {
40 static sigset_t sig_set;
41
42 if (block) {
43 sigemptyset(&sig_set);
44
45 sigaddset(&sig_set, SIGCHLD);
46 sigaddset(&sig_set, SIGUSR1);
47 sigaddset(&sig_set, SIGINT);
48 sigaddset(&sig_set, SIGQUIT);
49 #ifdef SIGIO
50 sigaddset(&sig_set, SIGIO);
51 #endif /* SIGIO */
52 #ifdef SIGBUS
53 sigaddset(&sig_set, SIGBUS);
54 #endif /* SIGBUS */
55 sigaddset(&sig_set, SIGHUP);
56
57 while (sigprocmask(SIG_BLOCK, &sig_set, NULL) < 0) {
58 if (errno == EINTR) {
59 pr_signals_handle();
60 continue;
61 }
62
63 break;
64 }
65
66 } else {
67 while (sigprocmask(SIG_UNBLOCK, &sig_set, NULL) < 0) {
68 if (errno == EINTR) {
69 pr_signals_handle();
70 continue;
71 }
72
73 break;
74 }
75 }
76 }
77
78 /* Returns the difference, in milliseconds, between the given timeval and
79 * now.
80 */
xfer_rate_since(struct timeval * then)81 static long xfer_rate_since(struct timeval *then) {
82 struct timeval now;
83 gettimeofday(&now, NULL);
84
85 return (((now.tv_sec - then->tv_sec) * 1000L) +
86 ((now.tv_usec - then->tv_usec) / 1000L));
87 }
88
pr_throttle_have_rate(void)89 int pr_throttle_have_rate(void) {
90 return have_xfer_rate;
91 }
92
pr_throttle_init(cmd_rec * cmd)93 void pr_throttle_init(cmd_rec *cmd) {
94 config_rec *c = NULL;
95 char *xfer_cmd = NULL;
96 unsigned char have_user_rate = FALSE, have_group_rate = FALSE,
97 have_class_rate = FALSE;
98 unsigned int precedence = 0;
99
100 /* Make sure the variables are (re)initialized */
101 xfer_rate_kbps = xfer_rate_bps = 0.0;
102 xfer_rate_freebytes = 0;
103 xfer_rate_scoreboard_updates = 0;
104 have_xfer_rate = FALSE;
105
106 c = find_config(CURRENT_CONF, CONF_PARAM, "TransferRate", FALSE);
107
108 /* Note: need to cycle through all the matching config_recs, and using
109 * the information from the current config_rec only if it matches
110 * the target *and* has a higher precedence than any of the previously
111 * found config_recs.
112 */
113 while (c) {
114 char **cmdlist = (char **) c->argv[0];
115 int matched_cmd = FALSE;
116
117 pr_signals_handle();
118
119 /* Does this TransferRate apply to the current command? Note: this
120 * could be made more efficient by using bitmasks rather than string
121 * comparisons.
122 */
123 for (xfer_cmd = *cmdlist; xfer_cmd; xfer_cmd = *(cmdlist++)) {
124 if (strcasecmp(xfer_cmd, cmd->argv[0]) == 0) {
125 matched_cmd = TRUE;
126 break;
127 }
128 }
129
130 /* No -- continue on to the next TransferRate. */
131 if (!matched_cmd) {
132 c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE);
133 continue;
134 }
135
136 if (c->argc > 4) {
137 if (strncmp(c->argv[4], "user", 5) == 0) {
138
139 if (pr_expr_eval_user_or((char **) &c->argv[5]) == TRUE &&
140 *((unsigned int *) c->argv[3]) > precedence) {
141
142 /* Set the precedence. */
143 precedence = *((unsigned int *) c->argv[3]);
144
145 xfer_rate_kbps = *((long double *) c->argv[1]);
146 xfer_rate_freebytes = *((off_t *) c->argv[2]);
147 have_xfer_rate = TRUE;
148 have_user_rate = TRUE;
149 have_group_rate = have_class_rate = FALSE;
150 }
151
152 } else if (strncmp(c->argv[4], "group", 6) == 0) {
153
154 if (pr_expr_eval_group_and((char **) &c->argv[5]) == TRUE &&
155 *((unsigned int *) c->argv[3]) > precedence) {
156
157 /* Set the precedence. */
158 precedence = *((unsigned int *) c->argv[3]);
159
160 xfer_rate_kbps = *((long double *) c->argv[1]);
161 xfer_rate_freebytes = *((off_t *) c->argv[2]);
162 have_xfer_rate = TRUE;
163 have_group_rate = TRUE;
164 have_user_rate = have_class_rate = FALSE;
165 }
166
167 } else if (strncmp(c->argv[4], "class", 6) == 0) {
168
169 if (pr_expr_eval_class_or((char **) &c->argv[5]) == TRUE &&
170 *((unsigned int *) c->argv[3]) > precedence) {
171
172 /* Set the precedence. */
173 precedence = *((unsigned int *) c->argv[3]);
174
175 xfer_rate_kbps = *((long double *) c->argv[1]);
176 xfer_rate_freebytes = *((off_t *) c->argv[2]);
177 have_xfer_rate = TRUE;
178 have_class_rate = TRUE;
179 have_user_rate = have_group_rate = FALSE;
180 }
181 }
182
183 } else {
184
185 if (*((unsigned int *) c->argv[3]) > precedence) {
186
187 /* Set the precedence. */
188 precedence = *((unsigned int *) c->argv[3]);
189
190 xfer_rate_kbps = *((long double *) c->argv[1]);
191 xfer_rate_freebytes = *((off_t *) c->argv[2]);
192 have_xfer_rate = TRUE;
193 have_user_rate = have_group_rate = have_class_rate = FALSE;
194 }
195 }
196
197 c = find_config_next(c, c->next, CONF_PARAM, "TransferRate", FALSE);
198 }
199
200 /* Print out a helpful debugging message. */
201 if (have_xfer_rate) {
202 pr_log_debug(DEBUG3, "TransferRate (%.3Lf KB/s, %" PR_LU
203 " bytes free) in effect%s", xfer_rate_kbps,
204 (pr_off_t) xfer_rate_freebytes,
205 have_user_rate ? " for current user" :
206 have_group_rate ? " for current group" :
207 have_class_rate ? " for current class" : "");
208
209 /* Convert the configured Kbps to bytes per usec, for use later.
210 * The 1024.0 factor converts for Kbytes to bytes, and the
211 * 1000000.0 factor converts from secs to usecs.
212 */
213 xfer_rate_bps = xfer_rate_kbps * 1024.0;
214 }
215 }
216
pr_throttle_pause(off_t xferlen,int xfer_ending)217 void pr_throttle_pause(off_t xferlen, int xfer_ending) {
218 long ideal = 0, elapsed = 0;
219 off_t orig_xferlen = xferlen;
220
221 if (XFER_ABORTED) {
222 return;
223 }
224
225 /* Calculate the time interval since the transfer of data started. */
226 elapsed = xfer_rate_since(&session.xfer.start_time);
227
228 /* Perform no throttling if no throttling has been configured. */
229 if (!have_xfer_rate) {
230 xfer_rate_scoreboard_updates++;
231
232 if (xfer_ending ||
233 xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) {
234 /* Update the scoreboard. */
235 pr_scoreboard_entry_update(session.pid,
236 PR_SCORE_XFER_LEN, orig_xferlen,
237 PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
238 NULL);
239
240 xfer_rate_scoreboard_updates = 0;
241 }
242
243 return;
244 }
245
246 /* Give credit for any configured freebytes. */
247 if (xferlen > 0 &&
248 xfer_rate_freebytes > 0) {
249
250 if (xferlen > xfer_rate_freebytes) {
251 /* Decrement the number of bytes transferred by the freebytes, so that
252 * any throttling does not take into account the freebytes.
253 */
254 xferlen -= xfer_rate_freebytes;
255
256 } else {
257 xfer_rate_scoreboard_updates++;
258
259 /* The number of bytes transferred is less than the freebytes. Just
260 * update the scoreboard -- no throttling needed.
261 */
262
263 if (xfer_ending ||
264 xfer_rate_scoreboard_updates % PR_TUNABLE_XFER_SCOREBOARD_UPDATES == 0) {
265 pr_scoreboard_entry_update(session.pid,
266 PR_SCORE_XFER_LEN, orig_xferlen,
267 PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
268 NULL);
269
270 xfer_rate_scoreboard_updates = 0;
271 }
272
273 return;
274 }
275 }
276
277 ideal = xferlen * 1000L / xfer_rate_bps;
278
279 if (ideal > elapsed) {
280 struct timeval tv;
281
282 /* Setup for the select. We use select() instead of usleep() because it
283 * seems to be far more portable across platforms.
284 *
285 * ideal and elapsed are in milleconds, but tv_usec will be microseconds,
286 * so be sure to convert properly.
287 */
288 tv.tv_usec = (ideal - elapsed) * 1000;
289 tv.tv_sec = tv.tv_usec / 1000000L;
290 tv.tv_usec = tv.tv_usec % 1000000L;
291
292 pr_log_debug(DEBUG7, "transferring too fast, delaying %ld sec%s, %ld usecs",
293 (long int) tv.tv_sec, tv.tv_sec == 1 ? "" : "s", (long int) tv.tv_usec);
294
295 /* No interruptions, please... */
296 xfer_rate_sigmask(TRUE);
297
298 if (select(0, NULL, NULL, NULL, &tv) < 0) {
299 int xerrno = errno;
300
301 if (XFER_ABORTED) {
302 pr_log_pri(PR_LOG_NOTICE, "throttling interrupted, transfer aborted");
303 xfer_rate_sigmask(FALSE);
304 return;
305 }
306
307 /* At this point, we've probably been interrupted by one of the few
308 * signals not masked off, e.g. SIGTERM.
309 */
310 if (xerrno != EINTR) {
311 pr_log_debug(DEBUG0, "unable to throttle bandwidth: %s",
312 strerror(xerrno));
313 }
314 }
315
316 xfer_rate_sigmask(FALSE);
317 pr_signals_handle();
318
319 /* Update the scoreboard. */
320 pr_scoreboard_entry_update(session.pid,
321 PR_SCORE_XFER_LEN, orig_xferlen,
322 PR_SCORE_XFER_ELAPSED, (unsigned long) ideal,
323 NULL);
324
325 } else {
326
327 /* Update the scoreboard. */
328 pr_scoreboard_entry_update(session.pid,
329 PR_SCORE_XFER_LEN, orig_xferlen,
330 PR_SCORE_XFER_ELAPSED, (unsigned long) elapsed,
331 NULL);
332 }
333
334 return;
335 }
336