1 /* $Id$ */
2 /*
3  * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 #include <pj/sock_qos.h>
20 #include <pj/assert.h>
21 #include <pj/errno.h>
22 #include <pj/string.h>
23 #include <pj/compat/socket.h>
24 
25 /* This is the implementation of QoS with BSD socket's setsockopt(),
26  * using Darwin-specific SO_NET_SERVICE_TYPE if available, and IP_TOS/
27  * IPV6_TCLASS as fallback.
28  */
29 #if defined(PJ_QOS_IMPLEMENTATION) && PJ_QOS_IMPLEMENTATION==PJ_QOS_DARWIN
30 
31 #include <sys/socket.h>
32 
33 #ifdef SO_NET_SERVICE_TYPE
sock_set_net_service_type(pj_sock_t sock,int val)34 static pj_status_t sock_set_net_service_type(pj_sock_t sock, int val)
35 {
36     pj_status_t status;
37 
38     status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), SO_NET_SERVICE_TYPE,
39 				&val, sizeof(val));
40     if (status == PJ_STATUS_FROM_OS(OSERR_ENOPROTOOPT))
41 	status = PJ_ENOTSUP;
42 
43     return status;
44 }
45 #endif
46 
sock_set_net_service_type_type(pj_sock_t sock,pj_qos_type type)47 static pj_status_t sock_set_net_service_type_type(pj_sock_t sock,
48 						  pj_qos_type type)
49 {
50 #ifdef SO_NET_SERVICE_TYPE
51     int val = NET_SERVICE_TYPE_BE;
52 
53     switch (type) {
54 	case PJ_QOS_TYPE_BEST_EFFORT:
55 	    val = NET_SERVICE_TYPE_BE;
56 	    break;
57 	case PJ_QOS_TYPE_BACKGROUND:
58 	    val = NET_SERVICE_TYPE_BK;
59 	    break;
60 	case PJ_QOS_TYPE_SIGNALLING:
61 	    val = NET_SERVICE_TYPE_SIG;
62 	    break;
63 	case PJ_QOS_TYPE_VIDEO:
64 	    val = NET_SERVICE_TYPE_VI;
65 	    break;
66 	case PJ_QOS_TYPE_VOICE:
67 	case PJ_QOS_TYPE_CONTROL:
68 	default:
69 	    val = NET_SERVICE_TYPE_VO;
70 	    break;
71     }
72 
73     return sock_set_net_service_type(sock, val);
74 #else
75     return PJ_ENOTSUP;
76 #endif
77 }
78 
sock_set_net_service_type_params(pj_sock_t sock,pj_qos_params * param)79 static pj_status_t sock_set_net_service_type_params(pj_sock_t sock,
80 						    pj_qos_params *param)
81 {
82 #ifdef SO_NET_SERVICE_TYPE
83     pj_status_t status;
84     int val = -1;
85 
86     PJ_ASSERT_RETURN(param, PJ_EINVAL);
87 
88     /*
89      * Sources:
90      *  - IETF draft-szigeti-tsvwg-ieee-802-11e-01
91      *  - iOS 10 SDK, sys/socket.h
92      */
93     if (val == -1 && param->flags & PJ_QOS_PARAM_HAS_DSCP) {
94 	if (param->dscp_val == 0) /* DF */
95 	    val = NET_SERVICE_TYPE_BE;
96 	else if (param->dscp_val < 0x10) /* CS1, AF11, AF12, AF13 */
97 	    val = NET_SERVICE_TYPE_BK;
98 	else if (param->dscp_val == 0x10) /* CS2 */
99 	    val = NET_SERVICE_TYPE_OAM;
100 	else if (param->dscp_val < 0x18) /* AF21, AF22, AF23 */
101 	    val = NET_SERVICE_TYPE_RD;
102 	else if (param->dscp_val < 0x20) /* CS3, AF31, AF32, AF33 */
103 	    val = NET_SERVICE_TYPE_AV;
104 	else if (param->dscp_val == 0x20) /* CS4 */
105 	    val = NET_SERVICE_TYPE_RD;
106 	else if (param->dscp_val < 0x28) /* AF41, AF42, AF43 */
107 	    val = NET_SERVICE_TYPE_VI;
108 	else if (param->dscp_val == 0x28) /* CS5 */
109 	    val = NET_SERVICE_TYPE_SIG;
110 	else
111 	    val = NET_SERVICE_TYPE_VO; /* VOICE-ADMIT, EF, CS6, etc. */
112     }
113 
114     if (val == -1 && param->flags & PJ_QOS_PARAM_HAS_WMM) {
115 	switch (param->wmm_prio) {
116 	    case PJ_QOS_WMM_PRIO_BULK_EFFORT:
117 		val = NET_SERVICE_TYPE_BE;
118 		break;
119 	    case PJ_QOS_WMM_PRIO_BULK:
120 		val = NET_SERVICE_TYPE_BK;
121 		break;
122 	    case PJ_QOS_WMM_PRIO_VIDEO:
123 		val = NET_SERVICE_TYPE_VI;
124 		break;
125 	    case PJ_QOS_WMM_PRIO_VOICE:
126 		val = NET_SERVICE_TYPE_VO;
127 		break;
128 	}
129     }
130 
131     if (val == -1) {
132 	pj_qos_type type;
133 
134 	status = pj_qos_get_type(param, &type);
135 
136 	if (status == PJ_SUCCESS)
137 	    return sock_set_net_service_type_type(sock, type);
138 
139 	val = NET_SERVICE_TYPE_BE;
140     }
141 
142     return sock_set_net_service_type(sock, val);
143 #else
144     return PJ_ENOTSUP;
145 #endif
146 }
147 
sock_set_ip_ds(pj_sock_t sock,pj_qos_params * param)148 static pj_status_t sock_set_ip_ds(pj_sock_t sock, pj_qos_params *param)
149 {
150     pj_status_t status = PJ_SUCCESS;
151 
152     PJ_ASSERT_RETURN(param, PJ_EINVAL);
153 
154     if (param->flags & PJ_QOS_PARAM_HAS_DSCP) {
155 	/* We need to know if the socket is IPv4 or IPv6 */
156 	pj_sockaddr sa;
157 	int salen = sizeof(salen);
158 
159 	/* Value is dscp_val << 2 */
160 	int val = (param->dscp_val << 2);
161 
162 	status = pj_sock_getsockname(sock, &sa, &salen);
163 	if (status != PJ_SUCCESS)
164 	    return status;
165 
166 	if (sa.addr.sa_family == pj_AF_INET()) {
167 	    /* In IPv4, the DS field goes in the TOS field */
168 	    status = pj_sock_setsockopt(sock, pj_SOL_IP(), pj_IP_TOS(),
169 					&val, sizeof(val));
170 	} else if (sa.addr.sa_family == pj_AF_INET6()) {
171 	    /* In IPv6, the DS field goes in the Traffic Class field */
172 	    status = pj_sock_setsockopt(sock, pj_SOL_IPV6(),
173 					pj_IPV6_TCLASS(),
174 					&val, sizeof(val));
175 	} else
176 	    status = PJ_EINVAL;
177 
178 	if (status != PJ_SUCCESS) {
179 	    param->flags &= ~(PJ_QOS_PARAM_HAS_DSCP);
180 	}
181     }
182 
183     return status;
184 }
185 
pj_sock_set_qos_params(pj_sock_t sock,pj_qos_params * param)186 PJ_DEF(pj_status_t) pj_sock_set_qos_params(pj_sock_t sock,
187 					   pj_qos_params *param)
188 {
189     pj_status_t status;
190 
191     PJ_ASSERT_RETURN(param, PJ_EINVAL);
192 
193     /* No op? */
194     if (!param->flags)
195 	return PJ_SUCCESS;
196 
197     /* Clear prio field since we don't support it */
198     param->flags &= ~(PJ_QOS_PARAM_HAS_SO_PRIO);
199 
200     /* Try SO_NET_SERVICE_TYPE */
201     status = sock_set_net_service_type_params(sock, param);
202     if (status == PJ_SUCCESS)
203 	return status;
204 
205     if (status != PJ_ENOTSUP) {
206 	/* SO_NET_SERVICE_TYPE sets both DSCP and WMM */
207 	param->flags &= ~(PJ_QOS_PARAM_HAS_DSCP);
208 	param->flags &= ~(PJ_QOS_PARAM_HAS_WMM);
209 	return status;
210     }
211 
212     /* Fall back to IP_TOS/IPV6_TCLASS */
213     return sock_set_ip_ds(sock, param);
214 }
215 
pj_sock_set_qos_type(pj_sock_t sock,pj_qos_type type)216 PJ_DEF(pj_status_t) pj_sock_set_qos_type(pj_sock_t sock,
217 					 pj_qos_type type)
218 {
219     pj_status_t status;
220     pj_qos_params param;
221 
222     /* Try SO_NET_SERVICE_TYPE */
223     status = sock_set_net_service_type_type(sock, type);
224     if (status == PJ_SUCCESS || status != PJ_ENOTSUP)
225 	return status;
226 
227     /* Fall back to IP_TOS/IPV6_TCLASS */
228     status = pj_qos_get_params(type, &param);
229     if (status != PJ_SUCCESS)
230 	return status;
231 
232     return sock_set_ip_ds(sock, &param);
233 }
234 
235 #ifdef SO_NET_SERVICE_TYPE
sock_get_net_service_type(pj_sock_t sock,int * p_val)236 static pj_status_t sock_get_net_service_type(pj_sock_t sock, int *p_val)
237 {
238     pj_status_t status;
239     int optlen = sizeof(*p_val);
240 
241     PJ_ASSERT_RETURN(p_val, PJ_EINVAL);
242 
243     status = pj_sock_getsockopt(sock, pj_SOL_SOCKET(), SO_NET_SERVICE_TYPE,
244 				p_val, &optlen);
245     if (status == PJ_STATUS_FROM_OS(OSERR_ENOPROTOOPT))
246 	status = PJ_ENOTSUP;
247 
248     return status;
249 }
250 #endif
251 
sock_get_net_service_type_type(pj_sock_t sock,pj_qos_type * p_type)252 static pj_status_t sock_get_net_service_type_type(pj_sock_t sock,
253 						  pj_qos_type *p_type)
254 {
255 #ifdef SO_NET_SERVICE_TYPE
256     pj_status_t status;
257     int val;
258 
259     PJ_ASSERT_RETURN(p_type, PJ_EINVAL);
260 
261     status = sock_get_net_service_type(sock, &val);
262     if (status == PJ_SUCCESS) {
263 	switch (val) {
264 	    default:
265 	    case NET_SERVICE_TYPE_BE:
266 		*p_type = PJ_QOS_TYPE_BEST_EFFORT;
267 		break;
268 	    case NET_SERVICE_TYPE_BK:
269 		*p_type = PJ_QOS_TYPE_BACKGROUND;
270 		break;
271 	    case NET_SERVICE_TYPE_SIG:
272 		*p_type = PJ_QOS_TYPE_SIGNALLING;
273 		break;
274 	    case NET_SERVICE_TYPE_VI:
275 	    case NET_SERVICE_TYPE_RV:
276 	    case NET_SERVICE_TYPE_AV:
277 	    case NET_SERVICE_TYPE_OAM:
278 	    case NET_SERVICE_TYPE_RD:
279 		*p_type = PJ_QOS_TYPE_VIDEO;
280 		break;
281 	    case NET_SERVICE_TYPE_VO:
282 		*p_type = PJ_QOS_TYPE_VOICE;
283 		break;
284 	}
285     }
286 
287     return status;
288 #else
289     return PJ_ENOTSUP;
290 #endif
291 }
292 
sock_get_net_service_type_params(pj_sock_t sock,pj_qos_params * p_param)293 static pj_status_t sock_get_net_service_type_params(pj_sock_t sock,
294 						    pj_qos_params *p_param)
295 {
296 #ifdef SO_NET_SERVICE_TYPE
297     pj_status_t status;
298     int val;
299 
300     PJ_ASSERT_RETURN(p_param, PJ_EINVAL);
301 
302     status = sock_get_net_service_type(sock, &val);
303     if (status == PJ_SUCCESS) {
304 	pj_bzero(p_param, sizeof(*p_param));
305 
306 	/* Note: these are just educated guesses, chosen for symmetry with
307 	 * sock_set_net_service_type_params: we can't know the actual values
308 	 * chosen by the OS, or even if DSCP/WMM are used at all.
309 	 *
310 	 * The source for mapping DSCP to WMM is:
311 	 *  - IETF draft-szigeti-tsvwg-ieee-802-11e-01
312 	 */
313 	switch (val) {
314 	    default:
315 	    case NET_SERVICE_TYPE_BE:
316 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
317 		p_param->dscp_val = 0; /* DF */
318 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_BULK_EFFORT; /* AC_BE */
319 		break;
320 	    case NET_SERVICE_TYPE_BK:
321 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
322 		p_param->dscp_val = 0x08; /* CS1 */
323 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_BULK; /* AC_BK */
324 		break;
325 	    case NET_SERVICE_TYPE_SIG:
326 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
327 		p_param->dscp_val = 0x28; /* CS5 */
328 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VIDEO; /* AC_VI */
329 		break;
330 	    case NET_SERVICE_TYPE_VI:
331 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
332 		p_param->dscp_val = 0x22; /* AF41 */
333 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VIDEO; /* AC_VI */
334 		break;
335 	    case NET_SERVICE_TYPE_VO:
336 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
337 		p_param->dscp_val = 0x30; /* CS6 */
338 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VOICE; /* AC_VO */
339 		break;
340 	    case NET_SERVICE_TYPE_RV:
341 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
342 		p_param->dscp_val = 0x22; /* AF41 */
343 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VIDEO; /* AC_VI */
344 		break;
345 	    case NET_SERVICE_TYPE_AV:
346 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
347 		p_param->dscp_val = 0x18; /* CS3 */
348 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VIDEO; /* AC_VI */
349 		break;
350 	    case NET_SERVICE_TYPE_OAM:
351 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
352 		p_param->dscp_val = 0x10; /* CS2 */
353 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_BULK_EFFORT; /* AC_BE */
354 		break;
355 	    case NET_SERVICE_TYPE_RD:
356 		p_param->flags = PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_WMM;
357 		p_param->dscp_val = 0x20; /* CS4 */
358 		p_param->wmm_prio = PJ_QOS_WMM_PRIO_VIDEO; /* AC_VI */
359 		break;
360 	}
361     }
362 
363     return status;
364 #else
365     return PJ_ENOTSUP;
366 #endif
367 }
368 
pj_sock_get_qos_params(pj_sock_t sock,pj_qos_params * p_param)369 PJ_DEF(pj_status_t) pj_sock_get_qos_params(pj_sock_t sock,
370 					   pj_qos_params *p_param)
371 {
372     pj_status_t status;
373     int val, optlen;
374     pj_sockaddr sa;
375     int salen = sizeof(salen);
376 
377     PJ_ASSERT_RETURN(p_param, PJ_EINVAL);
378 
379     pj_bzero(p_param, sizeof(*p_param));
380 
381     /* Try SO_NET_SERVICE_TYPE */
382     status = sock_get_net_service_type_params(sock, p_param);
383     if (status != PJ_ENOTSUP)
384 	return status;
385 
386     /* Fall back to IP_TOS/IPV6_TCLASS */
387     status = pj_sock_getsockname(sock, &sa, &salen);
388     if (status != PJ_SUCCESS)
389 	return status;
390 
391     optlen = sizeof(val);
392     if (sa.addr.sa_family == pj_AF_INET()) {
393 	status = pj_sock_getsockopt(sock, pj_SOL_IP(), pj_IP_TOS(),
394 				    &val, &optlen);
395     } else if (sa.addr.sa_family == pj_AF_INET6()) {
396 	status = pj_sock_getsockopt(sock, pj_SOL_IPV6(), pj_IPV6_TCLASS(),
397 				    &val, &optlen);
398     } else
399 	status = PJ_EINVAL;
400     if (status == PJ_SUCCESS) {
401 	p_param->flags |= PJ_QOS_PARAM_HAS_DSCP;
402 	p_param->dscp_val = (pj_uint8_t)(val >> 2);
403     }
404 
405     return status;
406 }
407 
pj_sock_get_qos_type(pj_sock_t sock,pj_qos_type * p_type)408 PJ_DEF(pj_status_t) pj_sock_get_qos_type(pj_sock_t sock,
409 					 pj_qos_type *p_type)
410 {
411     pj_qos_params param;
412     pj_status_t status;
413 
414     PJ_ASSERT_RETURN(p_type, PJ_EINVAL);
415 
416     status = sock_get_net_service_type_type(sock, p_type);
417     if (status != PJ_ENOTSUP)
418 	return status;
419 
420     status = pj_sock_get_qos_params(sock, &param);
421     if (status != PJ_SUCCESS)
422 	return status;
423 
424     return pj_qos_get_type(&param, p_type);
425 }
426 
427 #endif	/* PJ_QOS_IMPLEMENTATION */
428