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