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