1 /*
2  * ProFTPD: mod_qos -- a module for managing QoS socket options
3  *
4  * Copyright (c) 2010 Philip Prindeville
5  * Copyright (c) 2010-2014 The ProFTPD Project
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20  *
21  * As a special exemption, Philip Prindeville and other respective copyright
22  * holders give permission to link this program with OpenSSL, and distribute the
23  * resulting executable, without including the source code for OpenSSL in the
24  * source distribution.
25  *
26  * This is mod_qos, contrib software for proftpd 1.3.x and above.
27  */
28 
29 #include "conf.h"
30 
31 #define MOD_QOS_VERSION		"mod_qos/0.1"
32 
33 /* Make sure the version of proftpd is as necessary. */
34 #if PROFTPD_VERSION_NUMBER < 0x0001030401
35 # error "ProFTPD 1.3.4rc1 or later required"
36 #endif
37 
38 module qos_module;
39 
40 /* The level argument in a setsockopt(2) call for the TCP level is platform
41  * dependent.  Linux wants SOL_IP, *BSD wants IPPROTO_IP.
42  */
43 #ifdef SOL_IP
44 static int ip_level = SOL_IP;
45 #else
46 static int ip_level = IPPROTO_IP;
47 #endif /* !SOL_IP */
48 
49 /* These particular bits have yet to be widely deployed, thus the autodetection
50  * fun here.
51  *
52  * The semantics of the categories thus defined (CS0-CS7, AF11-AF43) are
53  * determined by the network configuration of each site; there are no
54  * global conventions/semantics to attach to these categories.
55  */
56 
57 #ifdef IPTOS_CLASS_CS0
58 # define QOS_CLASS_CS0	IPTOS_CLASS_CS0
59 #else
60 # define QOS_CLASS_CS0	0x00
61 #endif
62 
63 #ifdef IPTOS_CLASS_CS1
64 # define QOS_CLASS_CS1	IPTOS_CLASS_CS1
65 #else
66 # define QOS_CLASS_CS1	0x20
67 #endif
68 
69 #ifdef IPTOS_CLASS_CS2
70 # define QOS_CLASS_CS2	IPTOS_CLASS_CS2
71 #else
72 # define QOS_CLASS_CS2	0x40
73 #endif
74 
75 #ifdef IPTOS_CLASS_CS3
76 # define QOS_CLASS_CS3	IPTOS_CLASS_CS3
77 #else
78 # define QOS_CLASS_CS3	0x60
79 #endif
80 
81 #ifdef IPTOS_CLASS_CS4
82 # define QOS_CLASS_CS4	IPTOS_CLASS_CS4
83 #else
84 # define QOS_CLASS_CS4	0x80
85 #endif
86 
87 #ifdef IPTOS_CLASS_CS5
88 # define QOS_CLASS_CS5	IPTOS_CLASS_CS5
89 #else
90 # define QOS_CLASS_CS5	0xa0
91 #endif
92 
93 #ifdef IPTOS_CLASS_CS6
94 # define QOS_CLASS_CS6	IPTOS_CLASS_CS6
95 #else
96 # define QOS_CLASS_CS6	0xc0
97 #endif
98 
99 #ifdef IPTOS_CLASS_CS7
100 # define QOS_CLASS_CS7	IPTOS_CLASS_CS7
101 #else
102 # define QOS_CLASS_CS7	0xe0
103 #endif
104 
105 /* See RFC2474 for a discussion of Differentiated Services field */
106 
107 #ifdef IPTOS_DSCP_AF11
108 # define QOS_DSCP_AF11	IPTOS_DSCP_AF11
109 #else
110 # define QOS_DSCP_AF11	0x28
111 #endif
112 
113 #ifdef IPTOS_DSCP_AF12
114 # define QOS_DSCP_AF12	IPTOS_DSCP_AF12
115 #else
116 # define QOS_DSCP_AF12	0x30
117 #endif
118 
119 #ifdef IPTOS_DSCP_AF13
120 # define QOS_DSCP_AF13	IPTOS_DSCP_AF13
121 #else
122 # define QOS_DSCP_AF13	0x38
123 #endif
124 
125 #ifdef IPTOS_DSCP_AF21
126 # define QOS_DSCP_AF21	IPTOS_DSCP_AF21
127 #else
128 # define QOS_DSCP_AF21	0x48
129 #endif
130 
131 #ifdef IPTOS_DSCP_AF22
132 # define QOS_DSCP_AF22	IPTOS_DSCP_AF22
133 #else
134 # define QOS_DSCP_AF22	0x50
135 #endif
136 
137 #ifdef IPTOS_DSCP_AF23
138 # define QOS_DSCP_AF23	IPTOS_DSCP_AF23
139 #else
140 # define QOS_DSCP_AF23	0x58
141 #endif
142 
143 #ifdef IPTOS_DSCP_AF31
144 # define QOS_DSCP_AF31	IPTOS_DSCP_AF31
145 #else
146 # define QOS_DSCP_AF31	0x68
147 #endif
148 
149 #ifdef IPTOS_DSCP_AF32
150 # define QOS_DSCP_AF32	IPTOS_DSCP_AF32
151 #else
152 # define QOS_DSCP_AF32	0x70
153 #endif
154 
155 #ifdef IPTOS_DSCP_AF33
156 # define QOS_DSCP_AF33	IPTOS_DSCP_AF33
157 #else
158 # define QOS_DSCP_AF33	0x78
159 #endif
160 
161 #ifdef IPTOS_DSCP_AF41
162 # define QOS_DSCP_AF41	IPTOS_DSCP_AF41
163 #else
164 # define QOS_DSCP_AF41	0x88
165 #endif
166 
167 #ifdef IPTOS_DSCP_AF42
168 # define QOS_DSCP_AF42	IPTOS_DSCP_AF42
169 #else
170 # define QOS_DSCP_AF42	0x90
171 #endif
172 
173 #ifdef IPTOS_DSCP_AF43
174 # define QOS_DSCP_AF43	IPTOS_DSCP_AF43
175 #else
176 # define QOS_DSCP_AF43	0x98
177 #endif
178 
179 #ifdef IPTOS_DSCP_EF
180 # define QOS_DSCP_EF	IPTOS_DSCP_EF
181 #else
182 # define QOS_DSCP_EF	0xb8
183 #endif
184 
185 struct qos_rec {
186   const char *name;
187   int value;
188 };
189 
190 static struct qos_rec qos_vals[] = {
191   { "cs0",	QOS_CLASS_CS0 },
192   { "cs1",	QOS_CLASS_CS1 },
193   { "cs2",	QOS_CLASS_CS2 },
194   { "cs3",	QOS_CLASS_CS3 },
195   { "cs4",	QOS_CLASS_CS4 },
196   { "cs5",	QOS_CLASS_CS5 },
197   { "cs6",	QOS_CLASS_CS6 },
198   { "cs7",	QOS_CLASS_CS7 },
199 
200   { "af11",	QOS_DSCP_AF11 },
201   { "af12",	QOS_DSCP_AF12 },
202   { "af13",	QOS_DSCP_AF13 },
203   { "af21",	QOS_DSCP_AF21 },
204   { "af22",	QOS_DSCP_AF22 },
205   { "af23",	QOS_DSCP_AF23 },
206   { "af31",	QOS_DSCP_AF31 },
207   { "af32",	QOS_DSCP_AF32 },
208   { "af33",	QOS_DSCP_AF33 },
209   { "af41",	QOS_DSCP_AF41 },
210   { "af42",	QOS_DSCP_AF42 },
211   { "af43",	QOS_DSCP_AF43 },
212 
213   { "ef",	QOS_DSCP_EF },
214 
215   /* Some more human-readable strings */
216 #ifdef IPTOS_LOWDELAY
217   { "lowdelay",	IPTOS_LOWDELAY },
218 #endif
219 
220 #ifdef IPTOS_THROUGHPUT
221   { "throughput",IPTOS_THROUGHPUT },
222 #endif
223 
224 #ifdef IPTOS_RELIABILITY
225   { "reliability",IPTOS_RELIABILITY },
226 #endif
227 
228 #ifdef IPTOS_LOWCOST
229   { "lowcost",	IPTOS_LOWCOST },
230 #endif
231 
232 #ifdef IPTOS_MINCOST
233   { "mincost",	IPTOS_MINCOST },
234 #endif
235 
236   { NULL,	-1 }
237 };
238 
239 /* Prototypes. */
240 static int qos_sess_init(void);
241 
qos_get_int(const char * str)242 static int qos_get_int(const char *str) {
243   register unsigned int i;
244 
245   for (i = 0; qos_vals[i].name; i++) {
246     if (strcasecmp(qos_vals[i].name, str) == 0) {
247       return qos_vals[i].value;
248     }
249   }
250 
251   return -1;
252 }
253 
254 /* Configuration handlers
255  */
256 
257 /* usage: QoSOptions */
set_qosoptions(cmd_rec * cmd)258 MODRET set_qosoptions(cmd_rec *cmd) {
259   register unsigned int i;
260   config_rec *c;
261   int ctrlqos = 0, dataqos = 0;
262 
263   CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL);
264 
265   /* Make sure we have the right number of parameters. */
266   if ((cmd->argc-1) % 2 != 0) {
267    CONF_ERROR(cmd, "bad number of parameters");
268   }
269 
270   for (i = 1; i < cmd->argc; i++) {
271     if (strcasecmp(cmd->argv[i], "dataqos") == 0) {
272       dataqos = qos_get_int(cmd->argv[++i]);
273       if (dataqos == -1) {
274         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown dataqos parameter '",
275           cmd->argv[i-1], "'", NULL));
276       }
277 
278     } else if (strcasecmp(cmd->argv[i], "ctrlqos") == 0) {
279       ctrlqos = qos_get_int(cmd->argv[++i]);
280       if (ctrlqos == -1) {
281         CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown ctrlqos parameter '",
282           cmd->argv[i-1], "'", NULL));
283       }
284 
285     } else {
286       CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown QoS option: '",
287         cmd->argv[i], "'", NULL));
288     }
289   }
290 
291   c = add_config_param(cmd->argv[0], 2, NULL, NULL);
292   c->argv[0] = pcalloc(c->pool, sizeof(int));
293   *((int *) c->argv[0]) = ctrlqos;
294   c->argv[1] = pcalloc(c->pool, sizeof(int));
295   *((int *) c->argv[1]) = dataqos;
296 
297   return PR_HANDLED(cmd);
298 }
299 
300 /* Event handlers
301  */
302 
303 #ifdef IP_TOS
qos_ctrl_listen_ev(const void * event_data,void * user_data)304 static void qos_ctrl_listen_ev(const void *event_data, void *user_data) {
305   const struct socket_ctx *sc;
306 
307   sc = event_data;
308 
309   /* Only set TOS flags on IPv4 sockets; IPv6 sockets don't seem to support
310    * them.
311    */
312 
313   if (pr_netaddr_get_family(sc->addr) == AF_INET) {
314     config_rec *c;
315     c = find_config(sc->server->conf, CONF_PARAM, "QoSOptions", FALSE);
316 
317     if (c) {
318       int ctrlqos;
319 
320       ctrlqos = *((int *) c->argv[0]);
321       if (ctrlqos != 0) {
322         int res;
323 
324         res = setsockopt(sc->sockfd, ip_level, IP_TOS, (void *) &ctrlqos,
325           sizeof(ctrlqos));
326         if (res < 0) {
327           pr_log_pri(PR_LOG_NOTICE, MOD_QOS_VERSION
328             ": error setting control socket IP_TOS: %s", strerror(errno));
329         }
330       }
331     }
332   }
333 }
334 
qos_data_listen_ev(const void * event_data,void * user_data)335 static void qos_data_listen_ev(const void *event_data, void *user_data) {
336   const struct socket_ctx *sc;
337 
338   sc = event_data;
339 
340   /* Only set TOS flags on IPv4 sockets; IPv6 sockets don't seem to support
341    * them.
342    */
343   if (pr_netaddr_get_family(sc->addr) == AF_INET) {
344     config_rec *c;
345 
346     c = find_config(sc->server->conf, CONF_PARAM, "QoSOptions", FALSE);
347     if (c) {
348       int dataqos, res;
349 
350       dataqos = *((int *) c->argv[1]);
351 
352       res = setsockopt(sc->sockfd, ip_level, IP_TOS, (void *) &dataqos,
353         sizeof(dataqos));
354       if (res < 0) {
355         pr_log_pri(PR_LOG_NOTICE, MOD_QOS_VERSION
356           ": error setting data socket IP_TOS: %s", strerror(errno));
357       }
358     }
359   }
360 }
361 
qos_data_connect_ev(const void * event_data,void * user_data)362 static void qos_data_connect_ev(const void *event_data, void *user_data) {
363   const struct socket_ctx *sc;
364 
365   sc = event_data;
366 
367   /* Only set TOS flags on IPv4 sockets; IPv6 sockets don't seem to support
368    * them.
369    */
370   if (pr_netaddr_get_family(sc->addr) == AF_INET) {
371     config_rec *c;
372     c = find_config(sc->server->conf, CONF_PARAM, "QoSOptions", FALSE);
373     if (c) {
374       int dataqos, res;
375 
376       dataqos = *((int *) c->argv[1]);
377 
378       res = setsockopt(sc->sockfd, ip_level, IP_TOS, (void *) &dataqos,
379         sizeof(dataqos));
380       if (res < 0) {
381         pr_log_pri(PR_LOG_NOTICE, MOD_QOS_VERSION
382           ": error setting data socket IP_TOS: %s", strerror(errno));
383       }
384     }
385   }
386 }
387 #endif /* IP_TOS */
388 
389 #ifdef PR_SHARED_MODULE
qos_mod_unload_ev(const void * event_data,void * user_data)390 static void qos_mod_unload_ev(const void *event_data, void *user_data) {
391   if (strcmp("mod_qos.c", (const char *) event_data) == 0) {
392     pr_event_unregister(&qos_module, NULL, NULL);
393   }
394 }
395 #endif /* PR_SHARED_MODULE */
396 
qos_sess_reinit_ev(const void * event_data,void * user_data)397 static void qos_sess_reinit_ev(const void *event_data, void *user_data) {
398   int res;
399 
400   /* A HOST command changed the main_server pointer, reinitialize ourselves. */
401 
402   pr_event_unregister(&qos_module, "core.data-connect", qos_data_connect_ev);
403   pr_event_unregister(&qos_module, "core.data-listen", qos_data_listen_ev);
404   pr_event_unregister(&qos_module, "core.session-reinit", qos_sess_reinit_ev);
405 
406   res = qos_sess_init();
407   if (res < 0) {
408     pr_session_disconnect(&qos_module,
409       PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
410   }
411 }
412 
413 /* Initialization routines
414  */
415 
qos_init(void)416 static int qos_init(void) {
417 #ifdef IP_TOS
418   pr_event_register(&qos_module, "core.ctrl-listen", qos_ctrl_listen_ev, NULL);
419 #endif
420 
421 #ifdef PR_SHARED_MODULE
422   pr_event_register(&qos_module, "core.module-unload", qos_mod_unload_ev, NULL);
423 #endif
424   return 0;
425 }
426 
qos_sess_init(void)427 static int qos_sess_init(void) {
428 #ifdef IP_TOS
429   config_rec *c;
430 
431   c = find_config(main_server->conf, CONF_PARAM, "QoSOptions", FALSE);
432   if (c) {
433     int dataqos;
434 
435     dataqos = *((int *) c->argv[1]);
436     if (dataqos != 0) {
437       pr_event_register(&qos_module, "core.data-connect", qos_data_connect_ev,
438         NULL);
439       pr_event_register(&qos_module, "core.data-listen", qos_data_listen_ev,
440         NULL);
441     }
442   }
443 #endif
444 
445   pr_event_register(&qos_module, "core.session-reinit", qos_sess_reinit_ev,
446     NULL);
447 
448   return 0;
449 }
450 
451 /* Module API tables
452  */
453 
454 static conftable qos_conftab[] = {
455   { "QoSOptions",	set_qosoptions,	NULL },
456   { NULL }
457 };
458 
459 module qos_module = {
460   NULL, NULL,
461 
462   /* Module API version 2.0 */
463   0x20,
464 
465   /* Module name */
466   "qos",
467 
468   /* Module configuration handler table */
469   qos_conftab,
470 
471   /* Module command handler table */
472   NULL,
473 
474   /* Module authentication handler table */
475   NULL,
476 
477   /* Module initialization function */
478   qos_init,
479 
480   /* Session initialization function */
481   qos_sess_init,
482 
483   /* Module version */
484   MOD_QOS_VERSION
485 };
486