xref: /PHP-5.4/ext/sockets/multicast.c (revision c0d060f5)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2014 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 /* $Id$ */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "php.h"
26 
27 #if HAVE_SOCKETS
28 
29 #include "php_network.h"
30 #ifdef PHP_WIN32
31 # include "win32/inet.h"
32 # include <winsock2.h>
33 # include <windows.h>
34 # include <Ws2tcpip.h>
35 # include <Ws2ipdef.h>
36 # include "php_sockets.h"
37 # include "win32/sockets.h"
38 # define NTDDI_XP NTDDI_WINXP /* bug in SDK */
39 # include <IPHlpApi.h>
40 # undef NTDDI_XP
41 #else
42 #include <sys/socket.h>
43 #include <sys/ioctl.h>
44 #include <net/if.h>
45 #ifdef HAVE_SYS_SOCKIO_H
46 #include <sys/sockio.h>
47 #endif
48 #include <netinet/in.h>
49 #include <arpa/inet.h>
50 #endif
51 
52 #include "php_sockets.h"
53 #include "multicast.h"
54 #include "main/php_network.h"
55 
56 
57 enum source_op {
58 	JOIN_SOURCE,
59 	LEAVE_SOURCE,
60 	BLOCK_SOURCE,
61 	UNBLOCK_SOURCE
62 };
63 
64 static int _php_mcast_join_leave(php_socket *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index, int join TSRMLS_DC);
65 #ifdef HAS_MCAST_EXT
66 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 TSRMLS_DC);
67 #endif
68 
69 #ifdef RFC3678_API
70 static int _php_source_op_to_rfc3678_op(enum source_op sop);
71 #elif HAS_MCAST_EXT
72 static const char *_php_source_op_to_string(enum source_op sop);
73 static int _php_source_op_to_ipv4_op(enum source_op sop);
74 #endif
75 
php_mcast_join(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index TSRMLS_DC)76 int php_mcast_join(
77 	php_socket *sock,
78 	int level,
79 	struct sockaddr *group,
80 	socklen_t group_len,
81 	unsigned int if_index TSRMLS_DC)
82 {
83 	return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1 TSRMLS_CC);
84 }
85 
php_mcast_leave(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index TSRMLS_DC)86 int php_mcast_leave(
87 	php_socket *sock,
88 	int level,
89 	struct sockaddr *group,
90 	socklen_t group_len,
91 	unsigned int if_index TSRMLS_DC)
92 {
93 	return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0 TSRMLS_CC);
94 }
95 
96 #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 TSRMLS_DC)97 int php_mcast_join_source(
98 	php_socket *sock,
99 	int level,
100 	struct sockaddr *group,
101 	socklen_t group_len,
102 	struct sockaddr *source,
103 	socklen_t source_len,
104 	unsigned int if_index TSRMLS_DC)
105 {
106 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE TSRMLS_CC);
107 }
108 
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 TSRMLS_DC)109 int php_mcast_leave_source(
110 	php_socket *sock,
111 	int level,
112 	struct sockaddr *group,
113 	socklen_t group_len,
114 	struct sockaddr *source,
115 	socklen_t source_len,
116 	unsigned int if_index TSRMLS_DC)
117 {
118 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE TSRMLS_CC);
119 }
120 
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 TSRMLS_DC)121 int php_mcast_block_source(
122 	php_socket *sock,
123 	int level,
124 	struct sockaddr *group,
125 	socklen_t group_len,
126 	struct sockaddr *source,
127 	socklen_t source_len,
128 	unsigned int if_index TSRMLS_DC)
129 {
130 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE TSRMLS_CC);
131 }
132 
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 TSRMLS_DC)133 int php_mcast_unblock_source(
134 	php_socket *sock,
135 	int level,
136 	struct sockaddr *group,
137 	socklen_t group_len,
138 	struct sockaddr *source,
139 	socklen_t source_len,
140 	unsigned int if_index TSRMLS_DC)
141 {
142 	return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE TSRMLS_CC);
143 }
144 #endif /* HAS_MCAST_EXT */
145 
146 
_php_mcast_join_leave(php_socket * sock,int level,struct sockaddr * group,socklen_t group_len,unsigned int if_index,int join TSRMLS_DC)147 static int _php_mcast_join_leave(
148 	php_socket *sock,
149 	int level,
150 	struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */
151 	socklen_t group_len,
152 	unsigned int if_index,
153 	int join TSRMLS_DC)
154 {
155 #ifdef RFC3678_API
156 	struct group_req greq = {0};
157 
158 	memcpy(&greq.gr_group, group, group_len);
159 	assert(greq.gr_group.ss_family != 0); /* the caller has set this */
160 	greq.gr_interface = if_index;
161 
162 	return setsockopt(sock->bsd_socket, level,
163 			join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq,
164 			sizeof(greq));
165 #else
166 	if (sock->type == AF_INET) {
167 		struct ip_mreq mreq = {0};
168 		struct in_addr addr;
169 
170 		assert(group_len == sizeof(struct sockaddr_in));
171 
172 		if (if_index != 0) {
173 			if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) ==
174 					FAILURE)
175 				return -2; /* failure, but notice already emitted */
176 			mreq.imr_interface = addr;
177 		} else {
178 			mreq.imr_interface.s_addr = htonl(INADDR_ANY);
179 		}
180 		mreq.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;
181 		return setsockopt(sock->bsd_socket, level,
182 				join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char*)&mreq,
183 				sizeof(mreq));
184 	}
185 #if HAVE_IPV6
186 	else if (sock->type == AF_INET6) {
187 		struct ipv6_mreq mreq = {0};
188 
189 		assert(group_len == sizeof(struct sockaddr_in6));
190 
191 		mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr;
192 		mreq.ipv6mr_interface = if_index;
193 
194 		return setsockopt(sock->bsd_socket, level,
195 				join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq,
196 				sizeof(mreq));
197 	}
198 #endif
199 	else {
200 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
201 			"Option %s is inapplicable to this socket type",
202 			join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP");
203 		return -2;
204 	}
205 #endif
206 }
207 
208 #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 TSRMLS_DC)209 static int _php_mcast_source_op(
210 	php_socket *sock,
211 	int level,
212 	struct sockaddr *group,
213 	socklen_t group_len,
214 	struct sockaddr *source,
215 	socklen_t source_len,
216 	unsigned int if_index,
217 	enum source_op sop TSRMLS_DC)
218 {
219 #ifdef RFC3678_API
220 	struct group_source_req gsreq = {0};
221 
222 	memcpy(&gsreq.gsr_group, group, group_len);
223 	assert(gsreq.gsr_group.ss_family != 0);
224 	memcpy(&gsreq.gsr_source, source, source_len);
225 	assert(gsreq.gsr_source.ss_family != 0);
226 	gsreq.gsr_interface = if_index;
227 
228 	return setsockopt(sock->bsd_socket, level,
229 			_php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq));
230 #else
231 	if (sock->type == AF_INET) {
232 		struct ip_mreq_source mreqs = {0};
233 		struct in_addr addr;
234 
235 		mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr;
236 		mreqs.imr_sourceaddr =  ((struct sockaddr_in*)source)->sin_addr;
237 
238 		assert(group_len == sizeof(struct sockaddr_in));
239 		assert(source_len == sizeof(struct sockaddr_in));
240 
241 		if (if_index != 0) {
242 			if (php_if_index_to_addr4(if_index, sock, &addr TSRMLS_CC) ==
243 					FAILURE)
244 				return -2; /* failure, but notice already emitted */
245 			mreqs.imr_interface = addr;
246 		} else {
247 			mreqs.imr_interface.s_addr = htonl(INADDR_ANY);
248 		}
249 
250 		return setsockopt(sock->bsd_socket, level,
251 				_php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs));
252 	}
253 #if HAVE_IPV6
254 	else if (sock->type == AF_INET6) {
255 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
256 			"This platform does not support %s for IPv6 sockets",
257 			_php_source_op_to_string(sop));
258 		return -2;
259 	}
260 #endif
261 	else {
262 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
263 			"Option %s is inapplicable to this socket type",
264 			_php_source_op_to_string(sop));
265 		return -2;
266 	}
267 #endif
268 }
269 
270 #if RFC3678_API
_php_source_op_to_rfc3678_op(enum source_op sop)271 static int _php_source_op_to_rfc3678_op(enum source_op sop)
272 {
273 	switch (sop) {
274 	case JOIN_SOURCE:
275 		return MCAST_JOIN_SOURCE_GROUP;
276 	case LEAVE_SOURCE:
277 		return MCAST_LEAVE_SOURCE_GROUP;
278 	case BLOCK_SOURCE:
279 		return MCAST_BLOCK_SOURCE;
280 	case UNBLOCK_SOURCE:
281 		return MCAST_UNBLOCK_SOURCE;
282 	}
283 
284 	assert(0);
285 	return 0;
286 }
287 #else
_php_source_op_to_string(enum source_op sop)288 static const char *_php_source_op_to_string(enum source_op sop)
289 {
290 	switch (sop) {
291 	case JOIN_SOURCE:
292 		return "MCAST_JOIN_SOURCE_GROUP";
293 	case LEAVE_SOURCE:
294 		return "MCAST_LEAVE_SOURCE_GROUP";
295 	case BLOCK_SOURCE:
296 		return "MCAST_BLOCK_SOURCE";
297 	case UNBLOCK_SOURCE:
298 		return "MCAST_UNBLOCK_SOURCE";
299 	}
300 
301 	assert(0);
302 	return "";
303 }
304 
_php_source_op_to_ipv4_op(enum source_op sop)305 static int _php_source_op_to_ipv4_op(enum source_op sop)
306 {
307 	switch (sop) {
308 	case JOIN_SOURCE:
309 		return IP_ADD_SOURCE_MEMBERSHIP;
310 	case LEAVE_SOURCE:
311 		return IP_DROP_SOURCE_MEMBERSHIP;
312 	case BLOCK_SOURCE:
313 		return IP_BLOCK_SOURCE;
314 	case UNBLOCK_SOURCE:
315 		return IP_UNBLOCK_SOURCE;
316 	}
317 
318 	assert(0);
319 	return 0;
320 }
321 #endif
322 
323 #endif /* HAS_MCAST_EXT */
324 
325 #if PHP_WIN32
php_if_index_to_addr4(unsigned if_index,php_socket * php_sock,struct in_addr * out_addr TSRMLS_DC)326 int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC)
327 {
328 	MIB_IPADDRTABLE *addr_table;
329     ULONG size;
330     DWORD retval;
331 	DWORD i;
332 
333 	(void) php_sock; /* not necessary */
334 
335 	if (if_index == 0) {
336 		out_addr->s_addr = INADDR_ANY;
337 		return SUCCESS;
338 	}
339 
340 	size = 4 * (sizeof *addr_table);
341 	addr_table = emalloc(size);
342 retry:
343 	retval = GetIpAddrTable(addr_table, &size, 0);
344 	if (retval == ERROR_INSUFFICIENT_BUFFER) {
345 		efree(addr_table);
346 		addr_table = emalloc(size);
347 		goto retry;
348 	}
349 	if (retval != NO_ERROR) {
350 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
351 			"GetIpAddrTable failed with error %lu", retval);
352 		return FAILURE;
353 	}
354 	for (i = 0; i < addr_table->dwNumEntries; i++) {
355 		MIB_IPADDRROW r = addr_table->table[i];
356 		if (r.dwIndex == if_index) {
357 			out_addr->s_addr = r.dwAddr;
358 			return SUCCESS;
359 		}
360 	}
361 	php_error_docref(NULL TSRMLS_CC, E_WARNING,
362 		"No interface with index %u was found", if_index);
363 	return FAILURE;
364 }
365 
php_add4_to_if_index(struct in_addr * addr,php_socket * php_sock,unsigned * if_index TSRMLS_DC)366 int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC)
367 {
368 	MIB_IPADDRTABLE *addr_table;
369     ULONG size;
370     DWORD retval;
371 	DWORD i;
372 
373 	(void) php_sock; /* not necessary */
374 
375 	if (addr->s_addr == INADDR_ANY) {
376 		*if_index = 0;
377 		return SUCCESS;
378 	}
379 
380 	size = 4 * (sizeof *addr_table);
381 	addr_table = emalloc(size);
382 retry:
383 	retval = GetIpAddrTable(addr_table, &size, 0);
384 	if (retval == ERROR_INSUFFICIENT_BUFFER) {
385 		efree(addr_table);
386 		addr_table = emalloc(size);
387 		goto retry;
388 	}
389 	if (retval != NO_ERROR) {
390 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
391 			"GetIpAddrTable failed with error %lu", retval);
392 		return FAILURE;
393 	}
394 	for (i = 0; i < addr_table->dwNumEntries; i++) {
395 		MIB_IPADDRROW r = addr_table->table[i];
396 		if (r.dwAddr == addr->s_addr) {
397 			*if_index = r.dwIndex;
398 			return SUCCESS;
399 		}
400 	}
401 
402 	{
403 		char addr_str[17] = {0};
404 		inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
405 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
406 			"The interface with IP address %s was not found", addr_str);
407 	}
408 	return FAILURE;
409 }
410 
411 #else
412 
php_if_index_to_addr4(unsigned if_index,php_socket * php_sock,struct in_addr * out_addr TSRMLS_DC)413 int php_if_index_to_addr4(unsigned if_index, php_socket *php_sock, struct in_addr *out_addr TSRMLS_DC)
414 {
415 	struct ifreq if_req;
416 
417 	if (if_index == 0) {
418 		out_addr->s_addr = INADDR_ANY;
419 		return SUCCESS;
420 	}
421 
422 #if !defined(ifr_ifindex) && defined(ifr_index)
423 #define ifr_ifindex ifr_index
424 #endif
425 
426 #if defined(SIOCGIFNAME)
427 	if_req.ifr_ifindex = if_index;
428 	if (ioctl(php_sock->bsd_socket, SIOCGIFNAME, &if_req) == -1) {
429 #elif defined(HAVE_IF_INDEXTONAME)
430 	if (if_indextoname(if_index, if_req.ifr_name) == NULL) {
431 #else
432 #error Neither SIOCGIFNAME nor if_indextoname are available
433 #endif
434 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
435 			"Failed obtaining address for interface %u: error %d", if_index, errno);
436 		return FAILURE;
437 	}
438 
439 	if (ioctl(php_sock->bsd_socket, SIOCGIFADDR, &if_req) == -1) {
440 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
441 			"Failed obtaining address for interface %u: error %d", if_index, errno);
442 		return FAILURE;
443 	}
444 
445 	memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr,
446 		sizeof *out_addr);
447 	return SUCCESS;
448 }
449 
450 int php_add4_to_if_index(struct in_addr *addr, php_socket *php_sock, unsigned *if_index TSRMLS_DC)
451 {
452 	struct ifconf	if_conf = {0};
453 	char			*buf = NULL,
454 					*p;
455 	int				size = 0,
456 					lastsize = 0;
457 	size_t			entry_len;
458 
459 	if (addr->s_addr == INADDR_ANY) {
460 		*if_index = 0;
461 		return SUCCESS;
462 	}
463 
464 	for(;;) {
465 		size += 5 * sizeof(struct ifreq);
466 		buf = ecalloc(size, 1);
467 		if_conf.ifc_len = size;
468 		if_conf.ifc_buf = buf;
469 
470 		if (ioctl(php_sock->bsd_socket, SIOCGIFCONF, (char*)&if_conf) == -1 &&
471 				(errno != EINVAL || lastsize != 0)) {
472 			php_error_docref(NULL TSRMLS_CC, E_WARNING,
473 				"Failed obtaining interfaces list: error %d", errno);
474 			goto err;
475 		}
476 
477 		if (if_conf.ifc_len == lastsize)
478 			/* not increasing anymore */
479 			break;
480 		else {
481 			lastsize = if_conf.ifc_len;
482 			efree(buf);
483 			buf = NULL;
484 		}
485 	}
486 
487 	for (p = if_conf.ifc_buf;
488 		 p < if_conf.ifc_buf + if_conf.ifc_len;
489 		 p += entry_len) {
490 		struct ifreq *cur_req;
491 
492 		/* let's hope the pointer is aligned */
493 		cur_req = (struct ifreq*) p;
494 
495 #ifdef HAVE_SOCKADDR_SA_LEN
496 		entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name);
497 #else
498 		/* if there's no sa_len, assume the ifr_addr field is a sockaddr */
499 		entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name);
500 #endif
501 		entry_len = MAX(entry_len, sizeof(*cur_req));
502 
503 		if ((((struct sockaddr*)&cur_req->ifr_addr)->sa_family == AF_INET) &&
504 				(((struct sockaddr_in*)&cur_req->ifr_addr)->sin_addr.s_addr ==
505 					addr->s_addr)) {
506 #if defined(SIOCGIFINDEX)
507 			if (ioctl(php_sock->bsd_socket, SIOCGIFINDEX, (char*)cur_req)
508 					== -1) {
509 #elif defined(HAVE_IF_NAMETOINDEX)
510 			unsigned index_tmp;
511 			if ((index_tmp = if_nametoindex(cur_req->ifr_name)) == 0) {
512 #else
513 #error Neither SIOCGIFINDEX nor if_nametoindex are available
514 #endif
515 				php_error_docref(NULL TSRMLS_CC, E_WARNING,
516 					"Error converting interface name to index: error %d",
517 					errno);
518 				goto err;
519 			} else {
520 #if defined(SIOCGIFINDEX)
521 				*if_index = cur_req->ifr_ifindex;
522 #else
523 				*if_index = index_tmp;
524 #endif
525 				efree(buf);
526 				return SUCCESS;
527 			}
528 		}
529 	}
530 
531 	{
532 		char addr_str[17] = {0};
533 		inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str));
534 		php_error_docref(NULL TSRMLS_CC, E_WARNING,
535 			"The interface with IP address %s was not found", addr_str);
536 	}
537 
538 err:
539 	if (buf != NULL)
540 		efree(buf);
541 	return FAILURE;
542 }
543 #endif
544 
545 #endif /* HAVE_SOCKETS */
546