xref: /PHP-7.4/ext/sockets/multicast.c (revision 900bdcbd)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Gustavo Lopes    <cataphract@php.net>                       |
16    +----------------------------------------------------------------------+
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 
25 #include "php_network.h"
26 #ifdef PHP_WIN32
27 # include "windows_common.h"
28 #else
29 #include <sys/socket.h>
30 #include <sys/ioctl.h>
31 #include <net/if.h>
32 #ifdef HAVE_SYS_SOCKIO_H
33 #include <sys/sockio.h>
34 #endif
35 #include <netinet/in.h>
36 #include <arpa/inet.h>
37 #endif
38 
39 #include "php_sockets.h"
40 #include "multicast.h"
41 #include "sockaddr_conv.h"
42 #include "main/php_network.h"
43 
44 
45 enum source_op {
46 	JOIN_SOURCE,
47 	LEAVE_SOURCE,
48 	BLOCK_SOURCE,
49 	UNBLOCK_SOURCE
50 };
51 
52 static int _php_mcast_join_leave(php_socket *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index, int join);
53 #ifdef HAS_MCAST_EXT
54 static int _php_mcast_source_op(php_socket *sock, int level, struct sockaddr *group, socklen_t group_len, struct sockaddr *source, socklen_t source_len, unsigned int if_index, enum source_op sop);
55 #endif
56 
57 #ifdef RFC3678_API
58 static int _php_source_op_to_rfc3678_op(enum source_op sop);
59 #elif HAS_MCAST_EXT
60 static const char *_php_source_op_to_string(enum source_op sop);
61 static int _php_source_op_to_ipv4_op(enum source_op sop);
62 #endif
63 
php_string_to_if_index(const char * val,unsigned * out)64 int php_string_to_if_index(const char *val, unsigned *out)
65 {
66 #if HAVE_IF_NAMETOINDEX
67 	unsigned int ind;
68 
69 	ind = if_nametoindex(val);
70 	if (ind == 0) {
71 		php_error_docref(NULL, E_WARNING,
72 			"no interface with name \"%s\" could be found", val);
73 		return FAILURE;
74 	} else {
75 		*out = ind;
76 		return SUCCESS;
77 	}
78 #else
79 	php_error_docref(NULL, E_WARNING,
80 			"this platform does not support looking up an interface by "
81 			"name, an integer interface index must be supplied instead");
82 	return FAILURE;
83 #endif
84 }
85 
php_get_if_index_from_zval(zval * val,unsigned * out)86 static int php_get_if_index_from_zval(zval *val, unsigned *out)
87 {
88 	int ret;
89 
90 	if (Z_TYPE_P(val) == IS_LONG) {
91 		if (Z_LVAL_P(val) < 0 || (zend_ulong)Z_LVAL_P(val) > UINT_MAX) {
92 			php_error_docref(NULL, E_WARNING,
93 				"the interface index cannot be negative or larger than %u;"
94 				" given " ZEND_LONG_FMT, UINT_MAX, Z_LVAL_P(val));
95 			ret = FAILURE;
96 		} else {
97 			*out = Z_LVAL_P(val);
98 			ret = SUCCESS;
99 		}
100 	} else {
101 		zend_string *tmp_str;
102 		zend_string *str = zval_get_tmp_string(val, &tmp_str);
103 		ret = php_string_to_if_index(ZSTR_VAL(str), out);
104 		zend_tmp_string_release(tmp_str);
105 	}
106 
107 	return ret;
108 }
109 
110 
111 
php_get_if_index_from_array(const HashTable * ht,const char * key,php_socket * sock,unsigned int * if_index)112 static int php_get_if_index_from_array(const HashTable *ht, const char *key,
113 	php_socket *sock, unsigned int *if_index)
114 {
115 	zval *val;
116 
117 	if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) {
118 		*if_index = 0; /* default: 0 */
119 		return SUCCESS;
120 	}
121 
122 	return php_get_if_index_from_zval(val, if_index);
123 }
124 
php_get_address_from_array(const HashTable * ht,const char * key,php_socket * sock,php_sockaddr_storage * ss,socklen_t * ss_len)125 static int php_get_address_from_array(const HashTable *ht, const char *key,
126 	php_socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len)
127 {
128 	zval *val;
129 	zend_string *str, *tmp_str;
130 
131 	if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) {
132 		php_error_docref(NULL, E_WARNING, "no key \"%s\" passed in optval", key);
133 		return FAILURE;
134 	}
135 	str = zval_get_tmp_string(val, &tmp_str);
136 	if (!php_set_inet46_addr(ss, ss_len, ZSTR_VAL(str), sock)) {
137 		zend_tmp_string_release(tmp_str);
138 		return FAILURE;
139 	}
140 	zend_tmp_string_release(tmp_str);
141 	return SUCCESS;
142 }
143 
php_do_mcast_opt(php_socket * php_sock,int level,int optname,zval * arg4)144 static int php_do_mcast_opt(php_socket *php_sock, int level, int optname, zval *arg4)
145 {
146 	HashTable		 		*opt_ht;
147 	unsigned int			if_index;
148 	int						retval;
149 	int (*mcast_req_fun)(php_socket *, int, struct sockaddr *, socklen_t,
150 		unsigned);
151 #ifdef HAS_MCAST_EXT
152 	int (*mcast_sreq_fun)(php_socket *, int, struct sockaddr *, socklen_t,
153 		struct sockaddr *, socklen_t, unsigned);
154 #endif
155 
156 	switch (optname) {
157 	case PHP_MCAST_JOIN_GROUP:
158 		mcast_req_fun = &php_mcast_join;
159 		goto mcast_req_fun;
160 	case PHP_MCAST_LEAVE_GROUP:
161 		{
162 			php_sockaddr_storage	group = {0};
163 			socklen_t				glen;
164 
165 			mcast_req_fun = &php_mcast_leave;
166 mcast_req_fun:
167 			convert_to_array_ex(arg4);
168 			opt_ht = Z_ARRVAL_P(arg4);
169 
170 			if (php_get_address_from_array(opt_ht, "group", php_sock, &group,
171 				&glen) == FAILURE) {
172 					return FAILURE;
173 			}
174 			if (php_get_if_index_from_array(opt_ht, "interface", php_sock,
175 				&if_index) == FAILURE) {
176 					return FAILURE;
177 			}
178 
179 			retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group,
180 				glen, if_index);
181 			break;
182 		}
183 
184 #ifdef HAS_MCAST_EXT
185 	case PHP_MCAST_BLOCK_SOURCE:
186 		mcast_sreq_fun = &php_mcast_block_source;
187 		goto mcast_sreq_fun;
188 	case PHP_MCAST_UNBLOCK_SOURCE:
189 		mcast_sreq_fun = &php_mcast_unblock_source;
190 		goto mcast_sreq_fun;
191 	case PHP_MCAST_JOIN_SOURCE_GROUP:
192 		mcast_sreq_fun = &php_mcast_join_source;
193 		goto mcast_sreq_fun;
194 	case PHP_MCAST_LEAVE_SOURCE_GROUP:
195 		{
196 			php_sockaddr_storage	group = {0},
197 									source = {0};
198 			socklen_t				glen,
199 									slen;
200 
201 			mcast_sreq_fun = &php_mcast_leave_source;
202 		mcast_sreq_fun:
203 			convert_to_array_ex(arg4);
204 			opt_ht = Z_ARRVAL_P(arg4);
205 
206 			if (php_get_address_from_array(opt_ht, "group", php_sock, &group,
207 					&glen) == FAILURE) {
208 				return FAILURE;
209 			}
210 			if (php_get_address_from_array(opt_ht, "source", php_sock, &source,
211 					&slen) == FAILURE) {
212 				return FAILURE;
213 			}
214 			if (php_get_if_index_from_array(opt_ht, "interface", php_sock,
215 					&if_index) == FAILURE) {
216 				return FAILURE;
217 			}
218 
219 			retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group,
220 					glen, (struct sockaddr*)&source, slen, if_index);
221 			break;
222 		}
223 #endif
224 	default:
225 		php_error_docref(NULL, E_WARNING,
226 			"unexpected option in php_do_mcast_opt (level %d, option %d). "
227 			"This is a bug.", level, optname);
228 		return FAILURE;
229 	}
230 
231 	if (retval != 0) {
232 		if (retval != -2) { /* error, but message already emitted */
233 			PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
234 		}
235 		return FAILURE;
236 	}
237 	return SUCCESS;
238 }
239 
php_do_setsockopt_ip_mcast(php_socket * php_sock,int level,int optname,zval * arg4)240 int php_do_setsockopt_ip_mcast(php_socket *php_sock,
241 							   int level,
242 							   int optname,
243 							   zval *arg4)
244 {
245 	unsigned int	if_index;
246 	struct in_addr	if_addr;
247 	void 			*opt_ptr;
248 	socklen_t		optlen;
249 	unsigned char	ipv4_mcast_ttl_lback;
250 	int				retval;
251 
252 	switch (optname) {
253 	case PHP_MCAST_JOIN_GROUP:
254 	case PHP_MCAST_LEAVE_GROUP:
255 #ifdef HAS_MCAST_EXT
256 	case PHP_MCAST_BLOCK_SOURCE:
257 	case PHP_MCAST_UNBLOCK_SOURCE:
258 	case PHP_MCAST_JOIN_SOURCE_GROUP:
259 	case PHP_MCAST_LEAVE_SOURCE_GROUP:
260 #endif
261 		if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) {
262 			return FAILURE;
263 		} else {
264 			return SUCCESS;
265 		}
266 
267 	case IP_MULTICAST_IF:
268 		if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) {
269 			return FAILURE;
270 		}
271 
272 		if (php_if_index_to_addr4(if_index, php_sock, &if_addr) == FAILURE) {
273 			return FAILURE;
274 		}
275 		opt_ptr = &if_addr;
276 		optlen	= sizeof(if_addr);
277 		goto dosockopt;
278 
279 	case IP_MULTICAST_LOOP:
280 		convert_to_boolean_ex(arg4);
281 		ipv4_mcast_ttl_lback = (unsigned char) (Z_TYPE_P(arg4) == IS_TRUE);
282 		goto ipv4_loop_ttl;
283 
284 	case IP_MULTICAST_TTL:
285 		convert_to_long_ex(arg4);
286 		if (Z_LVAL_P(arg4) < 0L || Z_LVAL_P(arg4) > 255L) {
287 			php_error_docref(NULL, E_WARNING,
288 					"Expected a value between 0 and 255");
289 			return FAILURE;
290 		}
291 		ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_P(arg4);
292 ipv4_loop_ttl:
293 		opt_ptr = &ipv4_mcast_ttl_lback;
294 		optlen	= sizeof(ipv4_mcast_ttl_lback);
295 		goto dosockopt;
296 	}
297 
298 	return 1;
299 
300 dosockopt:
301 	retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
302 	if (retval != 0) {
303 		PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
304 		return FAILURE;
305 	}
306 
307 	return SUCCESS;
308 }
309 
php_do_setsockopt_ipv6_mcast(php_socket * php_sock,int level,int optname,zval * arg4)310 int php_do_setsockopt_ipv6_mcast(php_socket *php_sock,
311 								 int level,
312 								 int optname,
313 								 zval *arg4)
314 {
315 	unsigned int	if_index;
316 	void			*opt_ptr;
317 	socklen_t		optlen;
318 	int				ov;
319 	int				retval;
320 
321 	switch (optname) {
322 	case PHP_MCAST_JOIN_GROUP:
323 	case PHP_MCAST_LEAVE_GROUP:
324 #ifdef HAS_MCAST_EXT
325 	case PHP_MCAST_BLOCK_SOURCE:
326 	case PHP_MCAST_UNBLOCK_SOURCE:
327 	case PHP_MCAST_JOIN_SOURCE_GROUP:
328 	case PHP_MCAST_LEAVE_SOURCE_GROUP:
329 #endif
330 		if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) {
331 			return FAILURE;
332 		} else {
333 			return SUCCESS;
334 		}
335 
336 	case IPV6_MULTICAST_IF:
337 		if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) {
338 			return FAILURE;
339 		}
340 
341 		opt_ptr = &if_index;
342 		optlen	= sizeof(if_index);
343 		goto dosockopt;
344 
345 	case IPV6_MULTICAST_LOOP:
346 		convert_to_boolean_ex(arg4);
347 		ov = (int) Z_TYPE_P(arg4) == IS_TRUE;
348 		goto ipv6_loop_hops;
349 	case IPV6_MULTICAST_HOPS:
350 		convert_to_long_ex(arg4);
351 		if (Z_LVAL_P(arg4) < -1L || Z_LVAL_P(arg4) > 255L) {
352 			php_error_docref(NULL, E_WARNING,
353 					"Expected a value between -1 and 255");
354 			return FAILURE;
355 		}
356 		ov = (int) Z_LVAL_P(arg4);
357 ipv6_loop_hops:
358 		opt_ptr = &ov;
359 		optlen	= sizeof(ov);
360 		goto dosockopt;
361 	}
362 
363 	return 1; /* not handled */
364 
365 dosockopt:
366 	retval = setsockopt(php_sock->bsd_socket, level, optname, opt_ptr, optlen);
367 	if (retval != 0) {
368 		PHP_SOCKET_ERROR(php_sock, "unable to set socket option", errno);
369 		return FAILURE;
370 	}
371 
372 	return SUCCESS;
373 }
374 
php_mcast_join(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index)375 int php_mcast_join(
376 	php_socket *sock,
377 	int level,
378 	struct sockaddr *group,
379 	socklen_t group_len,
380 	unsigned int if_index)
381 {
382 	return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1);
383 }
384 
php_mcast_leave(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index)385 int php_mcast_leave(
386 	php_socket *sock,
387 	int level,
388 	struct sockaddr *group,
389 	socklen_t group_len,
390 	unsigned int if_index)
391 {
392 	return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0);
393 }
394 
395 #ifdef HAS_MCAST_EXT
php_mcast_join_source(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,struct sockaddr * source,socklen_t source_len,unsigned int if_index)396 int php_mcast_join_source(
397 	php_socket *sock,
398 	int level,
399 	struct sockaddr *group,
400 	socklen_t group_len,
401 	struct sockaddr *source,
402 	socklen_t source_len,
403 	unsigned int if_index)
404 {
405 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE);
406 }
407 
php_mcast_leave_source(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,struct sockaddr * source,socklen_t source_len,unsigned int if_index)408 int php_mcast_leave_source(
409 	php_socket *sock,
410 	int level,
411 	struct sockaddr *group,
412 	socklen_t group_len,
413 	struct sockaddr *source,
414 	socklen_t source_len,
415 	unsigned int if_index)
416 {
417 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE);
418 }
419 
php_mcast_block_source(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,struct sockaddr * source,socklen_t source_len,unsigned int if_index)420 int php_mcast_block_source(
421 	php_socket *sock,
422 	int level,
423 	struct sockaddr *group,
424 	socklen_t group_len,
425 	struct sockaddr *source,
426 	socklen_t source_len,
427 	unsigned int if_index)
428 {
429 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE);
430 }
431 
php_mcast_unblock_source(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,struct sockaddr * source,socklen_t source_len,unsigned int if_index)432 int php_mcast_unblock_source(
433 	php_socket *sock,
434 	int level,
435 	struct sockaddr *group,
436 	socklen_t group_len,
437 	struct sockaddr *source,
438 	socklen_t source_len,
439 	unsigned int if_index)
440 {
441 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE);
442 }
443 #endif /* HAS_MCAST_EXT */
444 
445 
_php_mcast_join_leave(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index,int join)446 static int _php_mcast_join_leave(
447 	php_socket *sock,
448 	int level,
449 	struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */
450 	socklen_t group_len,
451 	unsigned int if_index,
452 	int join)
453 {
454 #ifdef RFC3678_API
455 	struct group_req greq = {0};
456 
457 	memcpy(&greq.gr_group, group, group_len);
458 	assert(greq.gr_group.ss_family != 0); /* the caller has set this */
459 	greq.gr_interface = if_index;
460 
461 	return setsockopt(sock->bsd_socket, level,
462 			join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq,
463 			sizeof(greq));
464 #else
465 	if (sock->type == AF_INET) {
466 		struct ip_mreq mreq;
467 		struct in_addr addr;
468 		memset(&mreq, 0, sizeof(struct ip_mreq));
469 
470 		assert(group_len == sizeof(struct sockaddr_in));
471 
472 		if (if_index != 0) {
473 			if (php_if_index_to_addr4(if_index, sock, &addr) ==
474 					FAILURE)
475 				return -2; /* failure, but notice already emitted */
476 			mreq.imr_interface = addr;
477 		} else {
478 			mreq.imr_interface.s_addr = htonl(INADDR_ANY);
479 		}
480 		mreq.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;
481 		return setsockopt(sock->bsd_socket, level,
482 				join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char*)&mreq,
483 				sizeof(mreq));
484 	}
485 #if HAVE_IPV6
486 	else if (sock->type == AF_INET6) {
487 		struct ipv6_mreq mreq;
488 		memset(&mreq, 0, sizeof(struct ipv6_mreq));
489 
490 		assert(group_len == sizeof(struct sockaddr_in6));
491 
492 		mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr;
493 		mreq.ipv6mr_interface = if_index;
494 
495 		return setsockopt(sock->bsd_socket, level,
496 				join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq,
497 				sizeof(mreq));
498 	}
499 #endif
500 	else {
501 		php_error_docref(NULL, E_WARNING,
502 			"Option %s is inapplicable to this socket type",
503 			join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP");
504 		return -2;
505 	}
506 #endif
507 }
508 
509 #ifdef HAS_MCAST_EXT
_php_mcast_source_op(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,struct sockaddr * source,socklen_t source_len,unsigned int if_index,enum source_op sop)510 static int _php_mcast_source_op(
511 	php_socket *sock,
512 	int level,
513 	struct sockaddr *group,
514 	socklen_t group_len,
515 	struct sockaddr *source,
516 	socklen_t source_len,
517 	unsigned int if_index,
518 	enum source_op sop)
519 {
520 #ifdef RFC3678_API
521 	struct group_source_req gsreq = {0};
522 
523 	memcpy(&gsreq.gsr_group, group, group_len);
524 	assert(gsreq.gsr_group.ss_family != 0);
525 	memcpy(&gsreq.gsr_source, source, source_len);
526 	assert(gsreq.gsr_source.ss_family != 0);
527 	gsreq.gsr_interface = if_index;
528 
529 	return setsockopt(sock->bsd_socket, level,
530 			_php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq));
531 #else
532 	if (sock->type == AF_INET) {
533 		struct ip_mreq_source mreqs = {0};
534 		struct in_addr addr;
535 
536 		mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;
537 		mreqs.imr_sourceaddr =  ((struct sockaddr_in*)source)->sin_addr;
538 
539 		assert(group_len == sizeof(struct sockaddr_in));
540 		assert(source_len == sizeof(struct sockaddr_in));
541 
542 		if (if_index != 0) {
543 			if (php_if_index_to_addr4(if_index, sock, &addr) ==
544 					FAILURE)
545 				return -2; /* failure, but notice already emitted */
546 			mreqs.imr_interface = addr;
547 		} else {
548 			mreqs.imr_interface.s_addr = htonl(INADDR_ANY);
549 		}
550 
551 		return setsockopt(sock->bsd_socket, level,
552 				_php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs));
553 	}
554 #if HAVE_IPV6
555 	else if (sock->type == AF_INET6) {
556 		php_error_docref(NULL, E_WARNING,
557 			"This platform does not support %s for IPv6 sockets",
558 			_php_source_op_to_string(sop));
559 		return -2;
560 	}
561 #endif
562 	else {
563 		php_error_docref(NULL, E_WARNING,
564 			"Option %s is inapplicable to this socket type",
565 			_php_source_op_to_string(sop));
566 		return -2;
567 	}
568 #endif
569 }
570 
571 #if RFC3678_API
_php_source_op_to_rfc3678_op(enum source_op sop)572 static int _php_source_op_to_rfc3678_op(enum source_op sop)
573 {
574 	switch (sop) {
575 	case JOIN_SOURCE:
576 		return MCAST_JOIN_SOURCE_GROUP;
577 	case LEAVE_SOURCE:
578 		return MCAST_LEAVE_SOURCE_GROUP;
579 	case BLOCK_SOURCE:
580 		return MCAST_BLOCK_SOURCE;
581 	case UNBLOCK_SOURCE:
582 		return MCAST_UNBLOCK_SOURCE;
583 	}
584 
585 	assert(0);
586 	return 0;
587 }
588 #else
_php_source_op_to_string(enum source_op sop)589 static const char *_php_source_op_to_string(enum source_op sop)
590 {
591 	switch (sop) {
592 	case JOIN_SOURCE:
593 		return "MCAST_JOIN_SOURCE_GROUP";
594 	case LEAVE_SOURCE:
595 		return "MCAST_LEAVE_SOURCE_GROUP";
596 	case BLOCK_SOURCE:
597 		return "MCAST_BLOCK_SOURCE";
598 	case UNBLOCK_SOURCE:
599 		return "MCAST_UNBLOCK_SOURCE";
600 	}
601 
602 	assert(0);
603 	return "";
604 }
605 
_php_source_op_to_ipv4_op(enum source_op sop)606 static int _php_source_op_to_ipv4_op(enum source_op sop)
607 {
608 	switch (sop) {
609 	case JOIN_SOURCE:
610 		return IP_ADD_SOURCE_MEMBERSHIP;
611 	case LEAVE_SOURCE:
612 		return IP_DROP_SOURCE_MEMBERSHIP;
613 	case BLOCK_SOURCE:
614 		return IP_BLOCK_SOURCE;
615 	case UNBLOCK_SOURCE:
616 		return IP_UNBLOCK_SOURCE;
617 	}
618 
619 	assert(0);
620 	return 0;
621 }
622 #endif
623 
624 #endif /* HAS_MCAST_EXT */
625 
626 #ifdef PHP_WIN32
php_if_index_to_addr4(unsigned if_index,php_socket * php_sock,struct in_addr * out_addr)627 int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr)
628 {
629 	MIB_IPADDRTABLE *addr_table;
630     ULONG size;
631     DWORD retval;
632 	DWORD i;
633 
634 	(void) php_sock; /* not necessary */
635 
636 	if (if_index == 0) {
637 		out_addr->s_addr = INADDR_ANY;
638 		return SUCCESS;
639 	}
640 
641 	size = 4 * (sizeof *addr_table);
642 	addr_table = emalloc(size);
643 retry:
644 	retval = GetIpAddrTable(addr_table, &size, 0);
645 	if (retval == ERROR_INSUFFICIENT_BUFFER) {
646 		efree(addr_table);
647 		addr_table = emalloc(size);
648 		goto retry;
649 	}
650 	if (retval != NO_ERROR) {
651 		efree(addr_table);
652 		php_error_docref(NULL, E_WARNING,
653 			"GetIpAddrTable failed with error %lu", retval);
654 		return FAILURE;
655 	}
656 	for (i = 0; i < addr_table->dwNumEntries; i++) {
657 		MIB_IPADDRROW r = addr_table->table[i];
658 		if (r.dwIndex == if_index) {
659 			out_addr->s_addr = r.dwAddr;
660 			efree(addr_table);
661 			return SUCCESS;
662 		}
663 	}
664 	efree(addr_table);
665 	php_error_docref(NULL, E_WARNING,
666 		"No interface with index %u was found", if_index);
667 	return FAILURE;
668 }
669 
php_add4_to_if_index(struct in_addr * addr,php_socket * php_sock,unsigned * if_index)670 int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index)
671 {
672 	MIB_IPADDRTABLE *addr_table;
673     ULONG size;
674     DWORD retval;
675 	DWORD i;
676 
677 	(void) php_sock; /* not necessary */
678 
679 	if (addr->s_addr == INADDR_ANY) {
680 		*if_index = 0;
681 		return SUCCESS;
682 	}
683 
684 	size = 4 * (sizeof *addr_table);
685 	addr_table = emalloc(size);
686 retry:
687 	retval = GetIpAddrTable(addr_table, &size, 0);
688 	if (retval == ERROR_INSUFFICIENT_BUFFER) {
689 		efree(addr_table);
690 		addr_table = emalloc(size);
691 		goto retry;
692 	}
693 	if (retval != NO_ERROR) {
694 		efree(addr_table);
695 		php_error_docref(NULL, E_WARNING,
696 			"GetIpAddrTable failed with error %lu", retval);
697 		return FAILURE;
698 	}
699 	for (i = 0; i < addr_table->dwNumEntries; i++) {
700 		MIB_IPADDRROW r = addr_table->table[i];
701 		if (r.dwAddr == addr->s_addr) {
702 			*if_index = r.dwIndex;
703 			efree(addr_table);
704 			return SUCCESS;
705 		}
706 	}
707 	efree(addr_table);
708 
709 	{
710 		char addr_str[17] = {0};
711 		inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
712 		php_error_docref(NULL, E_WARNING,
713 			"The interface with IP address %s was not found", addr_str);
714 	}
715 	return FAILURE;
716 }
717 
718 #else
719 
php_if_index_to_addr4(unsigned if_index,php_socket * php_sock,struct in_addr * out_addr)720 int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr)
721 {
722 	struct ifreq if_req;
723 
724 	if (if_index == 0) {
725 		out_addr->s_addr = INADDR_ANY;
726 		return SUCCESS;
727 	}
728 
729 #if !defined(ifr_ifindex) && defined(ifr_index)
730 #define ifr_ifindex ifr_index
731 #endif
732 
733 #if defined(SIOCGIFNAME)
734 	if_req.ifr_ifindex = if_index;
735 	if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) {
736 #elif defined(HAVE_IF_INDEXTONAME)
737 	if (if_indextoname(if_index, if_req.ifr_name) == NULL) {
738 #else
739 #error Neither SIOCGIFNAME nor if_indextoname are available
740 #endif
741 		php_error_docref(NULL, E_WARNING,
742 			"Failed obtaining address for interface %u: error %d", if_index, errno);
743 		return FAILURE;
744 	}
745 
746 	if (ioctl(php_sock->bsd_socket, SIOCGIFADDR, &if_req) == -1) {
747 		php_error_docref(NULL, E_WARNING,
748 			"Failed obtaining address for interface %u: error %d", if_index, errno);
749 		return FAILURE;
750 	}
751 
752 	memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr,
753 		sizeof *out_addr);
754 	return SUCCESS;
755 }
756 
757 int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index)
758 {
759 	struct ifconf	if_conf = {0};
760 	char			*buf = NULL,
761 					*p;
762 	int				size = 0,
763 					lastsize = 0;
764 	size_t			entry_len;
765 
766 	if (addr->s_addr == INADDR_ANY) {
767 		*if_index = 0;
768 		return SUCCESS;
769 	}
770 
771 	for(;;) {
772 		size += 5 * sizeof(struct ifreq);
773 		buf = ecalloc(size, 1);
774 		if_conf.ifc_len = size;
775 		if_conf.ifc_buf = buf;
776 
777 		if (ioctl(php_sock->bsd_socket, SIOCGIFCONF, (char*)&if_conf) == -1 &&
778 				(errno != EINVAL || lastsize != 0)) {
779 			php_error_docref(NULL, E_WARNING,
780 				"Failed obtaining interfaces list: error %d", errno);
781 			goto err;
782 		}
783 
784 		if (if_conf.ifc_len == lastsize)
785 			/* not increasing anymore */
786 			break;
787 		else {
788 			lastsize = if_conf.ifc_len;
789 			efree(buf);
790 			buf = NULL;
791 		}
792 	}
793 
794 	for (p = if_conf.ifc_buf;
795 		 p < if_conf.ifc_buf + if_conf.ifc_len;
796 		 p += entry_len) {
797 		/* p may be misaligned on macos. */
798 		struct ifreq cur_req;
799 		memcpy(&cur_req, p, sizeof(struct ifreq));
800 
801 #ifdef HAVE_SOCKADDR_SA_LEN
802 		entry_len = cur_req.ifr_addr.sa_len + sizeof(cur_req.ifr_name);
803 #else
804 		/* if there's no sa_len, assume the ifr_addr field is a sockaddr */
805 		entry_len = sizeof(struct sockaddr) + sizeof(cur_req.ifr_name);
806 #endif
807 		entry_len = MAX(entry_len, sizeof(cur_req));
808 
809 		if ((((struct sockaddr*)&cur_req.ifr_addr)->sa_family == AF_INET) &&
810 				(((struct sockaddr_in*)&cur_req.ifr_addr)->sin_addr.s_addr ==
811 					addr->s_addr)) {
812 #if defined(SIOCGIFINDEX)
813 			if (ioctl(php_sock->bsd_socket, SIOCGIFINDEX, (char*)&cur_req)
814 					== -1) {
815 #elif defined(HAVE_IF_NAMETOINDEX)
816 			unsigned index_tmp;
817 			if ((index_tmp = if_nametoindex(cur_req.ifr_name)) == 0) {
818 #else
819 #error Neither SIOCGIFINDEX nor if_nametoindex are available
820 #endif
821 				php_error_docref(NULL, E_WARNING,
822 					"Error converting interface name to index: error %d",
823 					errno);
824 				goto err;
825 			} else {
826 #if defined(SIOCGIFINDEX)
827 				*if_index = cur_req.ifr_ifindex;
828 #else
829 				*if_index = index_tmp;
830 #endif
831 				efree(buf);
832 				return SUCCESS;
833 			}
834 		}
835 	}
836 
837 	{
838 		char addr_str[17] = {0};
839 		inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
840 		php_error_docref(NULL, E_WARNING,
841 			"The interface with IP address %s was not found", addr_str);
842 	}
843 
844 err:
845 	if (buf != NULL)
846 		efree(buf);
847 	return FAILURE;
848 }
849 #endif
850