From cd53f9bad21128bffc6bf4f6f481e2642be2d4dd Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 25 Oct 2016 10:03:34 -0200 Subject: [PATCH] efl_net_*_udp: make UDP usable, including multicast. This was a huge work, but now UDP is usable as seen in the examples. Instead of relying on 'connect()', just do 'sendto()' and 'recvfrom()' as they are universal. Multicast address can only be connected in IPv4, IPv6 wasn't working and I'm not sure the IPv4 is portable to other platforms. Dialer will auto-join multicast groups is the dialed address is one. Multicast properties such as time to live (hops) and loopback can be configured. When joining multicast groups, the local address/interface can be configured by 'IP@IFACE' format, with '@IFACE' being optional. Dialers will now auto-bind, so it can receive data as dialers are expected to be bi-directional. One can manually specify the binding address if there is such need. Since datagrams must be read in their full size, otherwise the remaining bits are dropped, expose next_datagram_size_query() in both Efl.Net.Socket.Udp and Efl.Net.Server.Udp.Client. To finalize UDP for real we need to introduce an 'Efl_Net_Ip_Address' structure to serve as both IPv4 and IPv6 and expose 'sendto()' and 'recvfrom()'. These will come later as this commit is already too big. --- src/examples/ecore/efl_net_dialer_udp_example.c | 68 ++- src/examples/ecore/efl_net_server_example.c | 39 +- src/lib/ecore_con/ecore_con.c | 316 +++++++++++- src/lib/ecore_con/ecore_con_private.h | 91 ++++ src/lib/ecore_con/efl_net_dialer_udp.c | 199 ++++++-- src/lib/ecore_con/efl_net_dialer_udp.eo | 19 + src/lib/ecore_con/efl_net_server_udp.c | 214 ++++++++- src/lib/ecore_con/efl_net_server_udp.eo | 70 +++ src/lib/ecore_con/efl_net_server_udp_client.c | 10 + src/lib/ecore_con/efl_net_server_udp_client.eo | 11 + src/lib/ecore_con/efl_net_socket_udp.c | 614 ++++++++++++++++++++++-- src/lib/ecore_con/efl_net_socket_udp.eo | 127 +++++ 12 files changed, 1642 insertions(+), 136 deletions(-) diff --git a/src/examples/ecore/efl_net_dialer_udp_example.c b/src/examples/ecore/efl_net_dialer_udp_example.c index 846a17c..dccbb5e 100644 --- a/src/examples/ecore/efl_net_dialer_udp_example.c +++ b/src/examples/ecore/efl_net_dialer_udp_example.c @@ -12,15 +12,33 @@ static int needed_reads = 0; static void _connected(void *data EINA_UNUSED, const Efl_Event *event) { - fprintf(stderr, "INFO: connected %s\n", - efl_net_dialer_address_dial_get(event->object)); + fprintf(stderr, + "INFO: connected to '%s' (%s)\n" + "INFO: - local address=%s\n" + "INFO: - read-after-write=%d reads required\n" + "INFO: - cork=%hhu\n" + "INFO: - timeout_dial=%fs\n" + "INFO: - reuse address=%hhu\n" + "INFO: - reuse port=%hhu\n" + "INFO: - multicast TTL: %u\n" + "INFO: - multicast loopback: %u\n" + "INFO: - multicast groups:\n", + efl_net_dialer_address_dial_get(event->object), + efl_net_socket_address_remote_get(event->object), + efl_net_socket_address_local_get(event->object), + needed_reads, + efl_net_socket_udp_cork_get(event->object), + efl_net_dialer_timeout_dial_get(event->object), + efl_net_socket_udp_reuse_address_get(event->object), + efl_net_socket_udp_reuse_port_get(event->object), + efl_net_socket_udp_multicast_time_to_live_get(event->object), + efl_net_socket_udp_multicast_loopback_get(event->object)); } static void _can_read(void *data EINA_UNUSED, const Efl_Event *event) { - char buf[1024]; - Eina_Rw_Slice rw_slice = {.mem = buf, .len = sizeof(buf)}; + Eina_Rw_Slice rw_slice; Eina_Error err; Eina_Bool can_read = efl_io_reader_can_read_get(event->object); @@ -33,9 +51,15 @@ _can_read(void *data EINA_UNUSED, const Efl_Event *event) if (!needed_reads) return; + rw_slice.len = efl_net_socket_udp_next_datagram_size_query(event->object); + rw_slice.mem = malloc(rw_slice.len); + EINA_SAFETY_ON_NULL_RETURN(rw_slice.mem); err = efl_io_reader_read(event->object, &rw_slice); if (err) { + free(rw_slice.mem); + if (err == EAGAIN) /* EAGAIN for spurious packets */ + return; fprintf(stderr, "ERROR: could not read: %s\n", eina_error_msg_get(err)); retval = EXIT_FAILURE; ecore_main_loop_quit(); @@ -50,6 +74,7 @@ _can_read(void *data EINA_UNUSED, const Efl_Event *event) retval = EXIT_SUCCESS; ecore_main_loop_quit(); } + free(rw_slice.mem); } static void @@ -149,10 +174,16 @@ static const Ecore_Getopt options = { "Example of Efl_Net_Dialer_Udp usage, sending a message and receiving a reply\n", EINA_FALSE, { + ECORE_GETOPT_STORE_STR('b', "bind", "Bind to a particular address in the format IP:PORT."), ECORE_GETOPT_STORE_TRUE('r', "read-after-write", "Do a read after writes are done."), ECORE_GETOPT_STORE_TRUE('c', "cork", "use UDP_CORK around messages to generate a single datagram."), ECORE_GETOPT_STORE_TRUE('R', "dont-route", "Do not route packets via a gateway."), ECORE_GETOPT_STORE_DOUBLE('t', "connect-timeout", "timeout in seconds for the connection phase"), + ECORE_GETOPT_STORE_UINT(0, "multicast-ttl", + "Multicast time to live in number of hops from 0-255. Defaults to 1 (only local network)."), + ECORE_GETOPT_STORE_FALSE(0, "multicast-noloopback", + "Disable multicast loopback."), + ECORE_GETOPT_APPEND('M', "multicast-group", "Join a multicast group in the form 'IP@INTERFACE', with optional '@INTERFACE', where INTERFACE is the IP address of the interface to join the multicast.", ECORE_GETOPT_TYPE_STR), ECORE_GETOPT_VERSION('V', "version"), ECORE_GETOPT_COPYRIGHT('C', "copyright"), ECORE_GETOPT_LICENSE('L', "license"), @@ -167,16 +198,25 @@ int main(int argc, char **argv) { char *address = NULL; + char *bind_address = NULL; + Eina_List *mcast_groups = NULL, *lst; + char *str; Eina_Bool cork = EINA_FALSE; Eina_Bool do_read = EINA_FALSE; Eina_Bool dont_route = EINA_FALSE; + unsigned mcast_ttl = 1; + Eina_Bool mcast_loopback = EINA_TRUE; Eina_Bool quit_option = EINA_FALSE; double timeout_dial = 30.0; Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_STR(bind_address), ECORE_GETOPT_VALUE_BOOL(do_read), ECORE_GETOPT_VALUE_BOOL(cork), ECORE_GETOPT_VALUE_BOOL(dont_route), ECORE_GETOPT_VALUE_DOUBLE(timeout_dial), + ECORE_GETOPT_VALUE_UINT(mcast_ttl), + ECORE_GETOPT_VALUE_BOOL(mcast_loopback), + ECORE_GETOPT_VALUE_LIST(mcast_groups), /* standard block to provide version, copyright, license and help */ ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */ @@ -218,11 +258,19 @@ main(int argc, char **argv) dialer = efl_add(EFL_NET_DIALER_UDP_CLASS, loop, efl_name_set(efl_added, "dialer"), + efl_net_socket_udp_bind_set(efl_added, bind_address), efl_net_socket_udp_cork_set(efl_added, cork), efl_net_socket_udp_dont_route_set(efl_added, dont_route), + efl_net_socket_udp_reuse_address_set(efl_added, EINA_TRUE), /* optional, but nice for testing */ + efl_net_socket_udp_reuse_port_set(efl_added, EINA_TRUE), /* optional, but nice for testing... not secure unless you know what you're doing */ + efl_net_socket_udp_multicast_time_to_live_set(efl_added, mcast_ttl), + efl_net_socket_udp_multicast_loopback_set(efl_added, mcast_loopback), efl_net_dialer_timeout_dial_set(efl_added, timeout_dial), efl_event_callback_array_add(efl_added, dialer_cbs(), NULL)); + EINA_LIST_FOREACH(mcast_groups, lst, str) + efl_net_socket_udp_multicast_join(dialer, str); + err = efl_net_dialer_dial(dialer, address); if (err != 0) { @@ -231,16 +279,6 @@ main(int argc, char **argv) goto no_mainloop; } - fprintf(stderr, - "INFO: dialed %s\n" - "INFO: - read-after-write=%hhu\n" - "INFO: - cork=%hhu\n" - "INFO: - timeout_dial=%fs\n", - efl_net_dialer_address_dial_get(dialer), - do_read, - efl_net_socket_udp_cork_get(dialer), - efl_net_dialer_timeout_dial_get(dialer)); - if (do_read) needed_reads = cork ? 1 : 2; ecore_main_loop_begin(); @@ -251,6 +289,8 @@ main(int argc, char **argv) efl_del(dialer); end: + EINA_LIST_FREE(mcast_groups, str) + free(str); ecore_con_shutdown(); ecore_shutdown(); diff --git a/src/examples/ecore/efl_net_server_example.c b/src/examples/ecore/efl_net_server_example.c index bab84da..3bec44c 100644 --- a/src/examples/ecore/efl_net_server_example.c +++ b/src/examples/ecore/efl_net_server_example.c @@ -443,12 +443,25 @@ _server_serving(void *data EINA_UNUSED, const Efl_Event *event) } else if (efl_class_get(event->object) == EFL_NET_SERVER_UDP_CLASS) { + Eina_Iterator *it; + const char *str; + fprintf(stderr, "UDP options:\n" " - IPv6 only: %u\n" - " - don't route: %u\n", + " - don't route: %u\n" + " - multicast TTL: %u\n" + " - multicast loopback: %u\n" + " - multicast groups:\n", efl_net_server_udp_ipv6_only_get(event->object), - efl_net_server_udp_dont_route_get(event->object)); + efl_net_server_udp_dont_route_get(event->object), + efl_net_server_udp_multicast_time_to_live_get(event->object), + efl_net_server_udp_multicast_loopback_get(event->object)); + + it = efl_net_server_udp_multicast_groups_get(event->object); + EINA_ITERATOR_FOREACH(it, str) + fprintf(stderr, " * %s\n", str); + eina_iterator_free(it); } } @@ -496,6 +509,11 @@ static const Ecore_Getopt options = { ECORE_GETOPT_CATEGORY("udp", "UDP options"), ECORE_GETOPT_STORE_TRUE(0, "udp-dont-route", "If true, datagrams won't be routed using a gateway, being restricted to the local network."), + ECORE_GETOPT_STORE_UINT(0, "udp-multicast-ttl", + "Multicast time to live in number of hops from 0-255. Defaults to 1 (only local network)."), + ECORE_GETOPT_STORE_FALSE(0, "udp-multicast-noloopback", + "Disable multicast loopback."), + ECORE_GETOPT_APPEND('M', "udp-multicast-group", "Join a multicast group in the form 'IP@INTERFACE', with optional '@INTERFACE', where INTERFACE is the IP address of the interface to join the multicast.", ECORE_GETOPT_TYPE_STR), ECORE_GETOPT_CHOICE_METAVAR(0, NULL, "The server protocol.", "protocol", protocols), @@ -514,10 +532,14 @@ main(int argc, char **argv) const Efl_Class *cls; char *protocol = NULL; char *address = NULL; + Eina_List *udp_mcast_groups = NULL; + char *str; unsigned int clients_limit = 0; + unsigned udp_mcast_ttl = 1; Eina_Bool clients_reject_excess = EINA_FALSE; Eina_Bool ipv6_only = EINA_TRUE; Eina_Bool udp_dont_route = EINA_FALSE; + Eina_Bool udp_mcast_loopback = EINA_TRUE; Eina_Bool quit_option = EINA_FALSE; Ecore_Getopt_Value values[] = { ECORE_GETOPT_VALUE_BOOL(echo), @@ -534,6 +556,9 @@ main(int argc, char **argv) ECORE_GETOPT_VALUE_BOOL(quit_option), /* category: udp */ ECORE_GETOPT_VALUE_BOOL(udp_dont_route), + ECORE_GETOPT_VALUE_UINT(udp_mcast_ttl), + ECORE_GETOPT_VALUE_BOOL(udp_mcast_loopback), + ECORE_GETOPT_VALUE_LIST(udp_mcast_groups), /* positional argument */ ECORE_GETOPT_VALUE_STR(protocol), @@ -600,8 +625,16 @@ main(int argc, char **argv) efl_net_server_tcp_ipv6_only_set(server, ipv6_only); else if (cls == EFL_NET_SERVER_UDP_CLASS) { + const Eina_List *lst; + efl_net_server_udp_ipv6_only_set(server, ipv6_only); efl_net_server_udp_dont_route_set(server, udp_dont_route); + + efl_net_server_udp_multicast_time_to_live_set(server, udp_mcast_ttl); + efl_net_server_udp_multicast_loopback_set(server, udp_mcast_loopback); + + EINA_LIST_FOREACH(udp_mcast_groups, lst, str) + efl_net_server_udp_multicast_join(server, str); } /* an explicit call to efl_net_server_serve() after the object is @@ -624,6 +657,8 @@ main(int argc, char **argv) server = NULL; end: + EINA_LIST_FREE(udp_mcast_groups, str) + free(str); ecore_con_shutdown(); ecore_shutdown(); diff --git a/src/lib/ecore_con/ecore_con.c b/src/lib/ecore_con/ecore_con.c index 36e6b8c..0bd23c0 100644 --- a/src/lib/ecore_con/ecore_con.c +++ b/src/lib/ecore_con/ecore_con.c @@ -42,6 +42,8 @@ # include #endif +#include + #include "Ecore.h" #include "ecore_private.h" #include "Ecore_Con.h" @@ -3319,17 +3321,6 @@ _efl_net_connect_async_run(void *data, Ecore_Thread *thread EINA_UNUSED) if (eina_log_domain_level_check(_ecore_con_log_dom, EINA_LOG_LEVEL_DBG)) efl_net_ip_port_fmt(buf, sizeof(buf), d->addr); - if ((d->type == SOCK_DGRAM) && - (d->addr->sa_family == AF_INET) && - (((const struct sockaddr_in *)d->addr)->sin_addr.s_addr == INADDR_BROADCAST)) - { - int enable = 1; - if (setsockopt(d->sockfd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable)) == 0) - DBG("enabled SO_BROADCAST for socket=%d", d->sockfd); - else - WRN("could not enable SO_BROADCAST for socket=%d: %s", d->sockfd, eina_error_msg_get(efl_net_socket_error_get())); - } - DBG("connecting fd=%d to %s", d->sockfd, buf); r = connect(d->sockfd, d->addr, d->addrlen); @@ -3510,21 +3501,6 @@ _efl_net_ip_connect(const struct addrinfo *addr, int *sockfd) DBG("connect fd=%d to %s", fd, buf); } - if ((addr->ai_socktype == SOCK_DGRAM) && - (addr->ai_family == AF_INET) && - (((const struct sockaddr_in *)addr->ai_addr)->sin_addr.s_addr == INADDR_BROADCAST)) - { -#ifdef _WIN32 - DWORD enable = 1; -#else - int enable = 1; -#endif - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable)) == 0) - DBG("enabled SO_BROADCAST for socket=%d", fd); - else - WRN("could not enable SO_BROADCAST for socket=%d: %s", fd, eina_error_msg_get(efl_net_socket_error_get())); - } - r = connect(fd, addr->ai_addr, addr->ai_addrlen); if (r == 0) { @@ -4875,6 +4851,294 @@ efl_net_ip_connect_async_new(const char *address, const char *proxy, const char return NULL; } +static Eina_Error +efl_net_multicast_address4_parse(const char *address, struct ip_mreq *mreq) +{ + char *str = NULL; + const char *iface; + Eina_Error err = 0; + int r; + + iface = strchr(address, '@'); + if (!iface) iface = "0.0.0.0"; + else + { + str = malloc(iface - address + 1); + EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM); + memcpy(str, address, iface - address); + str[iface - address] = '\0'; + address = str; + iface++; + if (iface[0] == '\0') iface = "0.0.0.0"; + } + + if (address[0] == '\0') + { + err = EINVAL; + goto end; + } + + r = inet_pton(AF_INET, address, &mreq->imr_multiaddr); + if (r != 1) + { + if (r < 0) err = efl_net_socket_error_get(); + else err = EINVAL; + goto end; + } + + r = inet_pton(AF_INET, iface, &mreq->imr_interface); + if (r != 1) + { + if (r < 0) err = efl_net_socket_error_get(); + else err = EINVAL; + goto end; + } + + end: + free(str); + return err; +} + +static Eina_Error +efl_net_multicast_address6_parse(const char *address, struct ipv6_mreq *mreq) +{ + char *str; + char *endptr; + const char *iface; + Eina_Error err = 0; + unsigned long idx; + int r; + + str = strdup(address); + EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM); + address = str; + + if (address[0] == '[') + { + address++; + + endptr = strchr(address, ']'); + if (!endptr) + { + err = EINVAL; + goto end; + } + memmove(endptr, endptr + 1, strlen(endptr + 1) + 1); + } + + iface = strchr(address, '@'); + if (!iface) iface = "0"; + else + { + str[iface - address] = '\0'; + iface++; + if (iface[0] == '\0') iface = "0"; + } + + if (address[0] == '\0') + { + err = EINVAL; + goto end; + } + + r = inet_pton(AF_INET6, address, &mreq->ipv6mr_multiaddr); + if (r != 1) + { + if (r < 0) err = efl_net_socket_error_get(); + else err = EINVAL; + goto end; + } + + errno = 0; + idx = strtoul(iface, &endptr, 10); + if (errno) + { + err = errno; + goto end; + } + else if ((iface == endptr) || (endptr[0] != '\0')) + { + errno = EINVAL; + goto end; + } + else if (idx > UINT32_MAX) + { + errno = ERANGE; + goto end; + } + + mreq->ipv6mr_interface = idx; + + end: + free(str); + return err; +} + +Eina_Error +efl_net_multicast_join(SOCKET fd, int family, const char *address) +{ + Eina_Error err; + + if (family == AF_INET) + { + struct ip_mreq mreq; + + err = efl_net_multicast_address4_parse(address, &mreq); + if (err) + return err; + + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) + return 0; + } + else if (family == AF_INET6) + { + struct ipv6_mreq mreq; + + err = efl_net_multicast_address6_parse(address, &mreq); + if (err) + return err; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) + return 0; + } + else + { + ERR("invalid socket family=%d", family); + return EINVAL; + } + + return efl_net_socket_error_get(); +} + +Eina_Error +efl_net_multicast_leave(SOCKET fd, int family, const char *address) +{ + Eina_Error err; + + if (family == AF_INET) + { + struct ip_mreq mreq; + + err = efl_net_multicast_address4_parse(address, &mreq); + if (err) + return err; + + if (setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) + return 0; + } + else if (family == AF_INET6) + { + struct ipv6_mreq mreq; + + err = efl_net_multicast_address6_parse(address, &mreq); + if (err) + return err; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) + return 0; + } + else + { + ERR("invalid socket family=%d", family); + return EINVAL; + } + + return efl_net_socket_error_get(); +} + +Eina_Error +efl_net_multicast_ttl_set(SOCKET fd, int family, uint8_t ttl) +{ + int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6; + int opt = (family == AF_INET) ? IP_MULTICAST_TTL : IPV6_MULTICAST_HOPS; +#ifdef _WIN32 + DWORD value = ttl; +#else + int value = ttl; +#endif + + if (setsockopt(fd, level, opt, &value, sizeof(value)) == 0) + return 0; + + return efl_net_socket_error_get(); +} + +Eina_Error +efl_net_multicast_ttl_get(SOCKET fd, int family, uint8_t *ttl) +{ + int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6; + int opt = (family == AF_INET) ? IP_MULTICAST_TTL : IPV6_MULTICAST_HOPS; +#ifdef _WIN32 + DWORD value; + int valuelen = sizeof(value); +#else + int value; + socklen_t valuelen = sizeof(value); +#endif + + if (getsockopt(fd, level, opt, &value, &valuelen) == 0) + { + *ttl = value; + return 0; + } + + return efl_net_socket_error_get(); +} + +Eina_Error +efl_net_multicast_loopback_set(SOCKET fd, int family, Eina_Bool loopback) +{ + int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6; + int opt = (family == AF_INET) ? IP_MULTICAST_LOOP : IPV6_MULTICAST_LOOP; +#ifdef _WIN32 + DWORD value = loopback; +#else + int value = loopback; +#endif + + if (setsockopt(fd, level, opt, &value, sizeof(value)) == 0) + return 0; + + return efl_net_socket_error_get(); +} + +Eina_Error +efl_net_multicast_loopback_get(SOCKET fd, int family, Eina_Bool *loopback) +{ + int level = (family == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6; + int opt = (family == AF_INET) ? IP_MULTICAST_LOOP : IPV6_MULTICAST_LOOP; +#ifdef _WIN32 + DWORD value; + int valuelen = sizeof(value); +#else + int value; + socklen_t valuelen = sizeof(value); +#endif + + if (getsockopt(fd, level, opt, &value, &valuelen) == 0) + { + *loopback = !!value; + return 0; + } + + return efl_net_socket_error_get(); +} + +size_t +efl_net_udp_datagram_size_query(SOCKET fd) +{ +#ifdef _WIN32 + unsigned long size; + if (ioctlsocket(fd, FIONREAD, &size) == 0) + return size; +#else + int size; + if (ioctl(fd, FIONREAD, &size) == 0) + return size; +#endif + return READBUFSIZ; +} + Eina_Bool ecore_con_libproxy_init(void) { diff --git a/src/lib/ecore_con/ecore_con_private.h b/src/lib/ecore_con/ecore_con_private.h index b8c67e6..894356f 100644 --- a/src/lib/ecore_con/ecore_con_private.h +++ b/src/lib/ecore_con/ecore_con_private.h @@ -400,6 +400,8 @@ void ecore_con_mempool_shutdown(void); void _efl_net_server_udp_client_init(Eo *client, SOCKET fd, const struct sockaddr *addr, socklen_t addrlen, const char *str); void _efl_net_server_udp_client_feed(Eo *client, Eina_Rw_Slice slice); +void _efl_net_socket_udp_init(Eo *o, const struct sockaddr *addr, socklen_t addrlen, const char *str); + Eina_Bool efl_net_ip_port_fmt(char *buf, int buflen, const struct sockaddr *addr); /** @@ -576,4 +578,93 @@ efl_net_socket_error_get(void) #endif } +/** + * Join a multicast group specified by address. + * + * Address must be an IPv4 or IPv6 depending on @a fd and will be + * parsed using inet_pton() with corresponding @a family. The address + * may contain an '@@' delimiter to specify the local interface IP + * address to use. No interface means '0.0.0.0'. + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param address the address in the format IP[@@IFACE] + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_join(SOCKET fd, int family, const char *address); + +/** + * Leave a multicast group specified by address. + * + * This reverses the effect of efl_net_multicast_join(). + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param address the address in the format IP[@@IFACE] + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_leave(SOCKET fd, int family, const char *address); + +/** + * Sets the Time-To-Live of multicast packets. <= 1 disables going + * outside of local network. + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param ttl the time-to-live in units. + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_ttl_set(SOCKET fd, int family, uint8_t ttl); + +/** + * Retrieves the current time-to-live of multicast packets. + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param[out] ttl returns the time-to-live in units. + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_ttl_get(SOCKET fd, int family, uint8_t *ttl); + +/** + * Sets if the current local address should get a copy of the packets sent. + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param loopback if #EINA_TRUE, enables receive of local copy. #EINA_FALSE means only remote peers will do. + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_loopback_set(SOCKET fd, int family, Eina_Bool loopback); + +/** + * Gets if the current local address should get a copy of the packets sent. + * + * @param fd socket to operate on. + * @param family the socket family of fd, AF_INET or AF_INET6. + * @param[out] loopback returns if #EINA_TRUE, enables receive of local copy. #EINA_FALSE means only remote peers will do. + * + * @return 0 on success, errno mapping otherwise. + * @internal + */ +Eina_Error efl_net_multicast_loopback_get(SOCKET fd, int family, Eina_Bool *loopback); + +/** + * Query the size of the next UDP datagram pending on queue. + * + * @param fd socket to operate on. + * @return the size in bytes. + * @internal + */ +size_t efl_net_udp_datagram_size_query(SOCKET fd); + #endif diff --git a/src/lib/ecore_con/efl_net_dialer_udp.c b/src/lib/ecore_con/efl_net_dialer_udp.c index 0a21232..b5fe8de 100644 --- a/src/lib/ecore_con/efl_net_dialer_udp.c +++ b/src/lib/ecore_con/efl_net_dialer_udp.c @@ -35,7 +35,7 @@ typedef struct _Efl_Net_Dialer_Udp_Data struct { Ecore_Thread *thread; Efl_Future *timeout; - } connect; + } resolver; Eina_Stringshare *address_dial; Eina_Bool connected; Eina_Bool closed; @@ -59,10 +59,10 @@ _efl_net_dialer_udp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Udp_Data *pd) (!efl_io_closer_closed_get(o))) efl_io_closer_close(o); - if (pd->connect.thread) + if (pd->resolver.thread) { - ecore_thread_cancel(pd->connect.thread); - pd->connect.thread = NULL; + ecore_thread_cancel(pd->resolver.thread); + pd->resolver.thread = NULL; } efl_destructor(efl_super(o, MY_CLASS)); @@ -71,16 +71,16 @@ _efl_net_dialer_udp_efl_object_destructor(Eo *o, Efl_Net_Dialer_Udp_Data *pd) } static void -_efl_net_dialer_udp_connect_timeout(void *data, const Efl_Event *ev EINA_UNUSED) +_efl_net_dialer_udp_resolver_timeout(void *data, const Efl_Event *ev EINA_UNUSED) { Eo *o = data; Efl_Net_Dialer_Udp_Data *pd = efl_data_scope_get(o, MY_CLASS); Eina_Error err = ETIMEDOUT; - if (pd->connect.thread) + if (pd->resolver.thread) { - ecore_thread_cancel(pd->connect.thread); - pd->connect.thread = NULL; + ecore_thread_cancel(pd->resolver.thread); + pd->resolver.thread = NULL; } efl_ref(o); @@ -89,33 +89,123 @@ _efl_net_dialer_udp_connect_timeout(void *data, const Efl_Event *ev EINA_UNUSED) efl_unref(o); } +static Eina_Error +_efl_net_dialer_udp_resolved_bind(Eo *o, Efl_Net_Dialer_Udp_Data *pd EINA_UNUSED, struct addrinfo *addr) +{ + Eina_Error err = 0; + char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")]; + SOCKET fd; + int family = addr->ai_family; + + efl_net_socket_fd_family_set(o, family); + + fd = efl_net_socket4(family, addr->ai_socktype, addr->ai_protocol, + efl_io_closer_close_on_exec_get(o)); + if (fd == INVALID_SOCKET) + { + err = efl_net_socket_error_get(); + ERR("socket(%d, %d, %d): %s", + family, addr->ai_socktype, addr->ai_protocol, + eina_error_msg_get(err)); + return err; + } + + if (!efl_net_socket_udp_bind_get(o)) + { + if (family == AF_INET) + efl_net_socket_udp_bind_set(o, "0.0.0.0:0"); + else + efl_net_socket_udp_bind_set(o, "[::]:0"); + } + + efl_loop_fd_set(o, fd); /* will also apply reuse_address et al */ + + if (family == AF_INET) + { + const struct sockaddr_in *a = (const struct sockaddr_in *)addr->ai_addr; + uint32_t ipv4 = ntohl(a->sin_addr.s_addr); + if (ipv4 == INADDR_BROADCAST) + { +#ifdef _WIN32 + DWORD enable = 1; +#else + int enable = 1; +#endif + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &enable, sizeof(enable)) == 0) + DBG("enabled SO_BROADCAST for socket=%d", fd); + else + WRN("could not enable SO_BROADCAST for socket=%d: %s", fd, eina_error_msg_get(efl_net_socket_error_get())); + } + } + else if (family == AF_INET6) + { + const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)addr->ai_addr; + if (IN6_IS_ADDR_MULTICAST(&a->sin6_addr)) + { + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = a->sin6_addr, + }; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == 0) + { + efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr); + DBG("joined multicast group %s socket=%d", buf, fd); + } + else + { + err = efl_net_socket_error_get(); + efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr); + ERR("could not join multicast group %s socket=%d: %s", buf, fd, eina_error_msg_get(err)); + goto error; + } + } + } + + if (efl_net_ip_port_fmt(buf, sizeof(buf), addr->ai_addr)) + { + _efl_net_socket_udp_init(o, addr->ai_addr, addr->ai_addrlen, buf); + efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); + } + efl_net_dialer_connected_set(o, EINA_TRUE); + return 0; + + error: + efl_net_socket_fd_family_set(o, AF_UNSPEC); + efl_loop_fd_set(o, INVALID_SOCKET); + closesocket(fd); + return err; +} + static void -_efl_net_dialer_udp_connected(void *data, const struct sockaddr *addr, socklen_t addrlen EINA_UNUSED, SOCKET sockfd, Eina_Error err) +_efl_net_dialer_udp_resolved(void *data, const char *host EINA_UNUSED, const char *port EINA_UNUSED, const struct addrinfo *hints EINA_UNUSED, struct addrinfo *result, int gai_error) { Eo *o = data; Efl_Net_Dialer_Udp_Data *pd = efl_data_scope_get(o, MY_CLASS); - char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")]; + Eina_Error err; + struct addrinfo *addr; - pd->connect.thread = NULL; + pd->resolver.thread = NULL; efl_ref(o); /* we're emitting callbacks then continuing the workflow */ + if (gai_error) + { + err = EFL_NET_DIALER_ERROR_COULDNT_RESOLVE_HOST; + goto end; + } + + for (addr = result; addr != NULL; addr = addr->ai_next) + { + err = _efl_net_dialer_udp_resolved_bind(o, pd, addr); + if (err == 0) break; + } + freeaddrinfo(result); + + end: if (err) { efl_io_reader_eos_set(o, EINA_TRUE); efl_event_callback_call(o, EFL_NET_DIALER_EVENT_ERROR, &err); } - else - { - efl_net_socket_fd_family_set(o, addr->sa_family); - efl_loop_fd_set(o, sockfd); - if (efl_net_ip_port_fmt(buf, sizeof(buf), addr)) - { - efl_net_socket_address_remote_set(o, buf); - efl_event_callback_call(o, EFL_NET_DIALER_EVENT_RESOLVED, NULL); - } - efl_net_dialer_connected_set(o, EINA_TRUE); - } efl_unref(o); } @@ -123,39 +213,52 @@ _efl_net_dialer_udp_connected(void *data, const struct sockaddr *addr, socklen_t EOLIAN static Eina_Error _efl_net_dialer_udp_efl_net_dialer_dial(Eo *o, Efl_Net_Dialer_Udp_Data *pd EINA_UNUSED, const char *address) { + char *str; + const char *host, *port; + struct addrinfo hints = { + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, + .ai_family = AF_UNSPEC, + .ai_flags = AI_ADDRCONFIG | AI_V4MAPPED, + }; + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_dialer_connected_get(o), EISCONN); EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_io_closer_closed_get(o), EBADF); EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) >= 0, EALREADY); - if (pd->connect.thread) + if (pd->resolver.thread) { - ecore_thread_cancel(pd->connect.thread); - pd->connect.thread = NULL; + ecore_thread_cancel(pd->resolver.thread); + pd->resolver.thread = NULL; } - if (pd->connect.thread) - ecore_thread_cancel(pd->connect.thread); + if (pd->resolver.thread) + ecore_thread_cancel(pd->resolver.thread); + + str = strdup(address); + if (!efl_net_ip_port_split(str, &host, &port)) + { + free(str); + return EINVAL; + } + if (!port) port = "0"; + if (strchr(host, ':')) hints.ai_family = AF_INET6; - pd->connect.thread = efl_net_ip_connect_async_new(address, - "", - NULL, - NULL, - SOCK_DGRAM, - IPPROTO_UDP, - efl_io_closer_close_on_exec_get(o), - _efl_net_dialer_udp_connected, o); - EINA_SAFETY_ON_NULL_RETURN_VAL(pd->connect.thread, EINVAL); + pd->resolver.thread = efl_net_ip_resolve_async_new(host, port, &hints, + _efl_net_dialer_udp_resolved, o); + free(str); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->resolver.thread, EINVAL); efl_net_dialer_address_dial_set(o, address); - if (pd->connect.timeout) - efl_future_cancel(pd->connect.timeout); + if (pd->resolver.timeout) + efl_future_cancel(pd->resolver.timeout); if (pd->timeout_dial > 0.0) { - efl_future_use(&pd->connect.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); - efl_future_then(pd->connect.timeout, _efl_net_dialer_udp_connect_timeout, NULL, NULL, o); - efl_future_link(o, pd->connect.timeout); + efl_future_use(&pd->resolver.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); + efl_future_then(pd->resolver.timeout, _efl_net_dialer_udp_resolver_timeout, NULL, NULL, o); + efl_future_link(o, pd->resolver.timeout); } return 0; @@ -177,13 +280,13 @@ EOLIAN static void _efl_net_dialer_udp_efl_net_dialer_timeout_dial_set(Eo *o EINA_UNUSED, Efl_Net_Dialer_Udp_Data *pd, double seconds) { pd->timeout_dial = seconds; - if (pd->connect.timeout) - efl_future_cancel(pd->connect.timeout); + if (pd->resolver.timeout) + efl_future_cancel(pd->resolver.timeout); - if ((pd->timeout_dial > 0.0) && (pd->connect.thread)) + if ((pd->timeout_dial > 0.0) && (pd->resolver.thread)) { - efl_future_use(&pd->connect.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); - efl_future_then(pd->connect.timeout, _efl_net_dialer_udp_connect_timeout, NULL, NULL, o); + efl_future_use(&pd->resolver.timeout, efl_loop_timeout(efl_loop_get(o), pd->timeout_dial, o)); + efl_future_then(pd->resolver.timeout, _efl_net_dialer_udp_resolver_timeout, NULL, NULL, o); } } @@ -196,8 +299,8 @@ _efl_net_dialer_udp_efl_net_dialer_timeout_dial_get(Eo *o EINA_UNUSED, Efl_Net_D EOLIAN static void _efl_net_dialer_udp_efl_net_dialer_connected_set(Eo *o, Efl_Net_Dialer_Udp_Data *pd, Eina_Bool connected) { - if (pd->connect.timeout) - efl_future_cancel(pd->connect.timeout); + if (pd->resolver.timeout) + efl_future_cancel(pd->resolver.timeout); if (pd->connected == connected) return; pd->connected = connected; if (connected) efl_event_callback_call(o, EFL_NET_DIALER_EVENT_CONNECTED, NULL); diff --git a/src/lib/ecore_con/efl_net_dialer_udp.eo b/src/lib/ecore_con/efl_net_dialer_udp.eo index 41ed220..1d6c8a1 100644 --- a/src/lib/ecore_con/efl_net_dialer_udp.eo +++ b/src/lib/ecore_con/efl_net_dialer_udp.eo @@ -3,6 +3,25 @@ class Efl.Net.Dialer.Udp (Efl.Net.Socket.Udp, Efl.Net.Dialer) { UDP proxies are not supported, not even using SOCKSv5. + In addition to an IP:PORT, the UDP address to dial may be an + IPv4 broadcast (255.255.255.255), 'any ip' 0.0.0.0 (IPv4) '::' + (IPv6) or a multicast of both IPv4 and IPv6, then the group will + be joined automatically. + + Any IP (0.0.0.0 or '::') and multicast addresses will allow + incoming packets on @Efl.Io.Reader.read regardless of their + source. If using a regular IP, packets that do not match the + remote peer will be dropped as spurious, returning EAGAIN. + + To allow finer control, see: + + - @Efl.Net.Socket.Udp.bind: choose the local address to bind. + - @Efl.Net.Socket.Udp.multicast_join: join other multicast groups. + - @Efl.Net.Socket.Udp.multicast_loopback: if packets should be delivered locally or only to remote peers. + - @Efl.Net.Socket.Udp.multicast_time_to_live: how far, in number of hops, the packet should go. + + I + @since 1.19 ]] diff --git a/src/lib/ecore_con/efl_net_server_udp.c b/src/lib/ecore_con/efl_net_server_udp.c index 8b5e078..3e0e951 100644 --- a/src/lib/ecore_con/efl_net_server_udp.c +++ b/src/lib/ecore_con/efl_net_server_udp.c @@ -29,14 +29,19 @@ # include #endif -#include - #define MY_CLASS EFL_NET_SERVER_UDP_CLASS typedef struct _Efl_Net_Server_Udp_Data { Ecore_Thread *resolver; Eina_Hash *clients; /* addr (string) -> client (Efl.Net.Server.Udp.Client) */ + struct { + Eina_List *groups; /* list of newly allocated strings */ + Eina_List *pending; /* list of nodes of groups pending join */ + uint8_t ttl; + Eina_Bool loopback; + Eina_Bool ttl_set; + } multicast; Eina_Bool ipv6_only; Eina_Bool dont_route; } Efl_Net_Server_Udp_Data; @@ -46,12 +51,24 @@ _efl_net_server_udp_efl_object_constructor(Eo *o, Efl_Net_Server_Udp_Data *pd) { pd->ipv6_only = 0xff; pd->clients = eina_hash_string_superfast_new(NULL); + pd->multicast.ttl = 1; + pd->multicast.ttl_set = EINA_FALSE; + pd->multicast.loopback = 0xff; return efl_constructor(efl_super(o, MY_CLASS)); } EOLIAN void _efl_net_server_udp_efl_object_destructor(Eo *o, Efl_Net_Server_Udp_Data *pd) { + if (pd->multicast.pending) + { + eina_list_free(pd->multicast.pending); + pd->multicast.pending = NULL; + } + + while (pd->multicast.groups) + efl_net_server_udp_multicast_leave(o, pd->multicast.groups->data); + if (pd->resolver) { ecore_thread_cancel(pd->resolver); @@ -72,6 +89,7 @@ _efl_net_server_udp_resolved_bind(Eo *o, Efl_Net_Server_Udp_Data *pd, const stru Eina_Error err = 0; char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")]; socklen_t addrlen = addr->ai_addrlen; + Eina_List *node; SOCKET fd; int r; @@ -121,6 +139,28 @@ _efl_net_server_udp_resolved_bind(Eo *o, Efl_Net_Server_Udp_Data *pd, const stru DBG("fd=%d serving at %s", fd, buf); efl_net_server_serving_set(o, EINA_TRUE); + + EINA_LIST_FREE(pd->multicast.pending, node) + { + const char *mcast_addr = node->data; + Eina_Error mr = efl_net_multicast_join(fd, addr->ai_family, mcast_addr); + if (mr) + { + ERR("could not join pending multicast group '%s': %s", mcast_addr, eina_error_msg_get(mr)); + efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &mr); + } + } + + if (!pd->multicast.ttl_set) + efl_net_server_udp_multicast_time_to_live_get(o); /* fetch & sync */ + else + efl_net_server_udp_multicast_time_to_live_set(o, pd->multicast.ttl); + + if (pd->multicast.loopback == 0xff) + efl_net_server_udp_multicast_loopback_get(o); /* fetch & sync */ + else + efl_net_server_udp_multicast_loopback_set(o, pd->multicast.loopback); + return 0; error: @@ -211,21 +251,6 @@ _efl_net_server_udp_client_event_closed(void *data, const Efl_Event *event) EFL_CALLBACKS_ARRAY_DEFINE(_efl_net_server_udp_client_cbs, { EFL_IO_CLOSER_EVENT_CLOSED, _efl_net_server_udp_client_event_closed }); -static size_t -_udp_datagram_size_query(SOCKET fd) -{ -#ifdef _WIN32 - unsigned long size; - if (ioctlsocket(fd, FIONREAD, &size) == 0) - return size; -#else - int size; - if (ioctl(fd, FIONREAD, &size) == 0) - return size; -#endif - return 8 * 1024; -} - EOLIAN static void _efl_net_server_udp_efl_net_server_fd_process_incoming_data(Eo *o, Efl_Net_Server_Udp_Data *pd) { @@ -242,7 +267,7 @@ _efl_net_server_udp_efl_net_server_fd_process_incoming_data(Eo *o, Efl_Net_Serve Eina_Rw_Slice slice; fd = efl_loop_fd_get(o); - buflen = _udp_datagram_size_query(fd); + buflen = efl_net_udp_datagram_size_query(fd); buf = malloc(buflen); EINA_SAFETY_ON_NULL_RETURN(buf); @@ -255,6 +280,11 @@ _efl_net_server_udp_efl_net_server_fd_process_incoming_data(Eo *o, Efl_Net_Serve efl_event_callback_call(o, EFL_NET_SERVER_EVENT_ERROR, &err); return; } + if ((size_t)r < buflen) + { + void *tmp = realloc(buf, r); + if (tmp) buf = tmp; + } slice = (Eina_Rw_Slice){.mem = buf, .len = r }; efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr); @@ -427,5 +457,153 @@ _efl_net_server_udp_dont_route_get(Eo *o, Efl_Net_Server_Udp_Data *pd) return pd->dont_route; } +static Eina_List * +_efl_net_server_udp_multicast_find(const Eina_List *lst, const char *address) +{ + const char *str; + const Eina_List *node; + + EINA_LIST_FOREACH(lst, node, str) + { + if (strcmp(str, address) == 0) + return (Eina_List *)node; + } + + return NULL; +} + +EOLIAN static Eina_Error +_efl_net_server_udp_multicast_join(Eo *o, Efl_Net_Server_Udp_Data *pd, const char *address) +{ + const Eina_List *found; + SOCKET fd = efl_loop_fd_get(o); + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + + found = _efl_net_server_udp_multicast_find(pd->multicast.groups, address); + if (found) return EEXIST; + + pd->multicast.groups = eina_list_append(pd->multicast.groups, strdup(address)); + + if (fd == INVALID_SOCKET) + { + pd->multicast.pending = eina_list_append(pd->multicast.pending, eina_list_last(pd->multicast.groups)); + return 0; + } + + return efl_net_multicast_join(fd, efl_net_server_fd_family_get(o), address); +} + +EOLIAN static Eina_Error +_efl_net_server_udp_multicast_leave(Eo *o, Efl_Net_Server_Udp_Data *pd, const char *address) +{ + Eina_List *found; + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + + found = _efl_net_server_udp_multicast_find(pd->multicast.groups, address); + if (!found) return ENOENT; + + if (fd == INVALID_SOCKET) + { + free(found->data); + pd->multicast.pending = eina_list_remove(pd->multicast.pending, found); + pd->multicast.groups = eina_list_remove_list(pd->multicast.groups, found); + return 0; + } + + err = efl_net_multicast_leave(fd, efl_net_server_fd_family_get(o), address); + + free(found->data); + pd->multicast.groups = eina_list_remove_list(pd->multicast.groups, found); + return err; +} + +EOLIAN static Eina_Iterator * +_efl_net_server_udp_multicast_groups_get(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Data *pd) +{ + return eina_list_iterator_new(pd->multicast.groups); +} + +EOLIAN static Eina_Error +_efl_net_server_udp_multicast_time_to_live_set(Eo *o, Efl_Net_Server_Udp_Data *pd, uint8_t ttl) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + uint8_t old = pd->multicast.ttl; + + pd->multicast.ttl_set = EINA_TRUE; + pd->multicast.ttl = ttl; + + if (fd == INVALID_SOCKET) return 0; + + err = efl_net_multicast_ttl_set(fd, efl_net_server_fd_family_get(o), ttl); + if (err) + { + ERR("could not set multicast time to live=%hhu: %s", ttl, eina_error_msg_get(err)); + pd->multicast.ttl = old; + } + + return err; +} + +EOLIAN static uint8_t +_efl_net_server_udp_multicast_time_to_live_get(Eo *o, Efl_Net_Server_Udp_Data *pd) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + uint8_t ttl = pd->multicast.ttl; + + if (fd == INVALID_SOCKET) return pd->multicast.ttl; + + err = efl_net_multicast_ttl_get(fd, efl_net_server_fd_family_get(o), &ttl); + if (err) + ERR("could not get multicast time to live: %s", eina_error_msg_get(err)); + else + pd->multicast.ttl = ttl; + + return pd->multicast.ttl; +} + +EOLIAN static Eina_Error +_efl_net_server_udp_multicast_loopback_set(Eo *o, Efl_Net_Server_Udp_Data *pd, Eina_Bool loopback) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + Eina_Bool old = pd->multicast.loopback; + + pd->multicast.loopback = loopback; + + if (fd == INVALID_SOCKET) return 0; + + err = efl_net_multicast_loopback_set(fd, efl_net_server_fd_family_get(o), loopback); + if (err) + { + ERR("could not set multicast loopback=%hhu: %s", loopback, eina_error_msg_get(err)); + pd->multicast.loopback = old; + } + + return err; +} + +EOLIAN static Eina_Bool +_efl_net_server_udp_multicast_loopback_get(Eo *o, Efl_Net_Server_Udp_Data *pd) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + Eina_Bool loopback = pd->multicast.loopback; + + if (fd == INVALID_SOCKET) return pd->multicast.loopback; + + err = efl_net_multicast_loopback_get(fd, efl_net_server_fd_family_get(o), &loopback); + if (err) + ERR("could not get multicast loopback: %s", eina_error_msg_get(err)); + else + pd->multicast.loopback = loopback; + + return pd->multicast.loopback; +} #include "efl_net_server_udp.eo.c" diff --git a/src/lib/ecore_con/efl_net_server_udp.eo b/src/lib/ecore_con/efl_net_server_udp.eo index 0aa4ba0..b1908d4 100644 --- a/src/lib/ecore_con/efl_net_server_udp.eo +++ b/src/lib/ecore_con/efl_net_server_udp.eo @@ -48,6 +48,76 @@ class Efl.Net.Server.Udp (Efl.Net.Server.Fd) { dont_route: bool; } } + + multicast_join { + [[Join a multicast group. + + The multicast address should be in the format: + + IP@INTERFACE + + With '\@INTERFACE' being optional, such as: + + 224.0.0.1 - use any interface (ie: 0.0.0.0) + 224.0.0.1@0.0.0.0 + 224.0.0.1@192.168.0.1 - use the interface assigned to 192.168.0.1 + ff02::1@0 - use any interface + ff02::1@1 - use loopback interface (idx=1) + ]] + params { + address: string @nonull; + } + return: Eina.Error; + } + + multicast_leave { + [[Leave a multicast group. + + This reverses the effect of @.multicast_join. + ]] + params { + address: string @nonull; + } + return: Eina.Error; + } + + multicast_groups_get { + [[Return the multicast groups this server has joined. + + The iterator is only valid until a new group is joined + or left using @.multicast_join or @.multicast_leave. + ]] + return: free(own(iterator), eina_iterator_free); + } + + @property multicast_time_to_live { + [[Controls time to live in number of hops. + + If 1 (default), packets are only delivered to the local network. + ]] + get { } + set { + return: Eina.Error; [[0 on success, error code otherwise]] + } + values { + loopback: bool; + } + } + + @property multicast_loopback { + [[Controls whenever multicast will loopback packets locally. + + If $false, then packets won't be looped back locally, + just delivered for remote peers. + ]] + get { } + set { + return: Eina.Error; [[0 on success, error code otherwise]] + } + values { + loopback: bool; + } + } } implements { diff --git a/src/lib/ecore_con/efl_net_server_udp_client.c b/src/lib/ecore_con/efl_net_server_udp_client.c index c569295..db89c3e 100644 --- a/src/lib/ecore_con/efl_net_server_udp_client.c +++ b/src/lib/ecore_con/efl_net_server_udp_client.c @@ -118,6 +118,16 @@ _efl_net_server_udp_client_feed(Eo *o, Eina_Rw_Slice slice) free(slice.mem); } +EOLIAN static size_t +_efl_net_server_udp_client_next_datagram_size_query(Eo *o EINA_UNUSED, Efl_Net_Server_Udp_Client_Data *pd) +{ + Efl_Net_Server_Udp_Client_Packet *pkt; + + if (!pd->packets) return 0; + pkt = EINA_INLIST_CONTAINER_GET(pd->packets, Efl_Net_Server_Udp_Client_Packet); + return pkt->slice.len; +} + EOLIAN static Eina_Error _efl_net_server_udp_client_efl_io_closer_close(Eo *o, Efl_Net_Server_Udp_Client_Data *pd) { diff --git a/src/lib/ecore_con/efl_net_server_udp_client.eo b/src/lib/ecore_con/efl_net_server_udp_client.eo index b0c71e2..9ef3fda 100644 --- a/src/lib/ecore_con/efl_net_server_udp_client.eo +++ b/src/lib/ecore_con/efl_net_server_udp_client.eo @@ -13,6 +13,17 @@ class Efl.Net.Server.Udp.Client (Efl.Object, Efl.Net.Socket) { @since 1.19 ]] + methods { + next_datagram_size_query { + [[Query the next datagram size. + + This will query the next pending datagram size, in + bytes. If no datagrams are pending, 0 is returned. + ]] + return: size; [[size in bytes]] + } + } + implements { Efl.Object.finalize; Efl.Object.destructor; diff --git a/src/lib/ecore_con/efl_net_socket_udp.c b/src/lib/ecore_con/efl_net_socket_udp.c index 70f91fa..325c227 100644 --- a/src/lib/ecore_con/efl_net_socket_udp.c +++ b/src/lib/ecore_con/efl_net_socket_udp.c @@ -37,48 +37,181 @@ typedef struct _Efl_Net_Socket_Udp_Data { + struct { + Eina_List *groups; /* list of newly allocated strings */ + Eina_List *pending; /* list of nodes of groups pending join */ + uint8_t ttl; + Eina_Bool loopback; + Eina_Bool ttl_set; + } multicast; + Eina_Stringshare *address_bind; + struct sockaddr *addr_remote; + socklen_t addr_remote_len; Eina_Bool cork; Eina_Bool dont_route; + Eina_Bool reuse_address; + Eina_Bool reuse_port; } Efl_Net_Socket_Udp_Data; +// TODO: once we have Efl_Net_Ip_Address, make this protected and declared in .eo +void +_efl_net_socket_udp_init(Eo *o, const struct sockaddr *addr, socklen_t addrlen, const char *str) +{ + Efl_Net_Socket_Udp_Data *pd = efl_data_scope_get(o, MY_CLASS); + + pd->addr_remote = malloc(addrlen); + EINA_SAFETY_ON_NULL_RETURN(pd->addr_remote); + memcpy(pd->addr_remote, addr, addrlen); + pd->addr_remote_len = addrlen; + efl_net_socket_address_remote_set(o, str); +} + +static Eina_Error +_efl_net_socket_udp_bind(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + const char *bhost, *bport; + struct sockaddr_in bsa4 = {.sin_family = AF_INET}; + struct sockaddr_in6 bsa6 = {.sin6_family = AF_INET6}; + char *str = NULL, *endptr; + unsigned long ul; + Eina_Error err = 0; + SOCKET fd = efl_loop_fd_get(o); + int family = efl_net_socket_fd_family_get(o); + int r; + + if (!pd->address_bind) return 0; + + str = strdup(pd->address_bind); + EINA_SAFETY_ON_NULL_GOTO(str, error_bind); + if (!efl_net_ip_port_split(str, &bhost, &bport)) + { + bhost = (family == AF_INET) ? "0.0.0.0" : "::"; + bport = "0"; + err = EINVAL; + ERR("invalid bind address '%s', using host='%s' port=%s", pd->address_bind, bhost, bport); + } + else if (!bport) bport = "0"; + + if (family == AF_INET) + r = inet_pton(AF_INET, bhost, &bsa4.sin_addr); + else + r = inet_pton(AF_INET6, bhost, &bsa6.sin6_addr); + if (r != 1) + { + if (r < 0) err = efl_net_socket_error_get(); + else err = EINVAL; + ERR("invalid host='%s': %s", bhost, eina_error_msg_get(err)); + goto error_bind; + } + + errno = 0; + ul = strtoul(bport, &endptr, 10); + if ((endptr == bport) || (endptr[0] != '\0')) + errno = EINVAL; + else if (ul > UINT16_MAX) + errno = ERANGE; + + if (errno) + { + err = errno; + ERR("invalid port numer '%s': %s", bport, strerror(errno)); + goto error_bind; + } + + if (family == AF_INET) + bsa4.sin_port = htons(ul); + else + bsa6.sin6_port = htons(ul); + + if (family == AF_INET) + r = bind(fd, (struct sockaddr *)&bsa4, sizeof(bsa4)); + else + r = bind(fd, (struct sockaddr *)&bsa6, sizeof(bsa6)); + if (r != 0) + { + err = efl_net_socket_error_get(); + ERR("could not bind to host='%s', port=%s: %s", bhost, bport, eina_error_msg_get(err)); + goto error_bind; + } + + error_bind: + free(str); + return err; +} + EOLIAN static void _efl_net_socket_udp_efl_loop_fd_fd_set(Eo *o, Efl_Net_Socket_Udp_Data *pd EINA_UNUSED, int fd) { + Eina_List *node; + struct sockaddr_storage addr; + socklen_t addrlen; + int family; + efl_loop_fd_set(efl_super(o, MY_CLASS), fd); - if (fd != INVALID_SOCKET) - { - struct sockaddr_storage addr; - socklen_t addrlen; - int family; + if (fd == INVALID_SOCKET) return; - /* apply postponed values */ - efl_net_socket_udp_cork_set(o, pd->cork); - efl_net_socket_udp_dont_route_set(o, pd->dont_route); + family = efl_net_socket_fd_family_get(o); + if (family == AF_UNSPEC) return; - family = efl_net_socket_fd_family_get(o); - if (family == AF_UNSPEC) return; + /* apply postponed values or fetch & sync */ + if (pd->cork == 0xff) + efl_net_socket_udp_cork_get(o); + else + efl_net_socket_udp_cork_set(o, pd->cork); - addrlen = sizeof(addr); - if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) - ERR("getsockname(%d): %s", fd, eina_error_msg_get(efl_net_socket_error_get())); - else - { - char str[INET6_ADDRSTRLEN + sizeof("[]:65536")]; - if (efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr)) - efl_net_socket_address_local_set(o, str); - } + if (pd->dont_route == 0xff) + efl_net_socket_udp_dont_route_get(o); + else + efl_net_socket_udp_dont_route_set(o, pd->dont_route); - addrlen = sizeof(addr); - if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) != 0) - ERR("getpeername(%d): %s", fd, eina_error_msg_get(efl_net_socket_error_get())); - else - { - char str[INET6_ADDRSTRLEN + sizeof("[]:65536")]; - if (efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr)) - efl_net_socket_address_remote_set(o, str); - } + if (pd->reuse_address == 0xff) + efl_net_socket_udp_reuse_address_get(o); + else + efl_net_socket_udp_reuse_address_set(o, pd->reuse_address); + + if (pd->reuse_port == 0xff) + efl_net_socket_udp_reuse_port_get(o); + else + efl_net_socket_udp_reuse_port_set(o, pd->reuse_port); + + _efl_net_socket_udp_bind(o, pd); + + EINA_LIST_FREE(pd->multicast.pending, node) + { + const char *mcast_addr = node->data; + Eina_Error mr = efl_net_multicast_join(fd, family, mcast_addr); + if (mr) + ERR("could not join pending multicast group '%s': %s", mcast_addr, eina_error_msg_get(mr)); } + + if (!pd->multicast.ttl_set) + efl_net_socket_udp_multicast_time_to_live_get(o); + else + efl_net_socket_udp_multicast_time_to_live_set(o, pd->multicast.ttl); + + if (pd->multicast.loopback == 0xff) + efl_net_socket_udp_multicast_loopback_get(o); + else + efl_net_socket_udp_multicast_loopback_set(o, pd->multicast.loopback); + + addrlen = sizeof(addr); + if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0) + ERR("getsockname(%d): %s", fd, eina_error_msg_get(efl_net_socket_error_get())); + else + { + char str[INET6_ADDRSTRLEN + sizeof("[]:65536")]; + if (efl_net_ip_port_fmt(str, sizeof(str), (struct sockaddr *)&addr)) + efl_net_socket_address_local_set(o, str); + } +} + +EOLIAN static size_t +_efl_net_socket_udp_next_datagram_size_query(Eo *o, Efl_Net_Socket_Udp_Data *pd EINA_UNUSED) +{ + SOCKET fd = efl_loop_fd_get(o); + if (fd == INVALID_SOCKET) return 0; + return efl_net_udp_datagram_size_query(fd); } static inline int @@ -208,4 +341,429 @@ _efl_net_socket_udp_dont_route_get(Eo *o, Efl_Net_Socket_Udp_Data *pd) return pd->dont_route; } + +EOLIAN static Eina_Bool +_efl_net_socket_udp_reuse_address_set(Eo *o, Efl_Net_Socket_Udp_Data *pd, Eina_Bool reuse_address) +{ + int value, fd; + Eina_Bool old = pd->reuse_address; + + pd->reuse_address = reuse_address; + + fd = efl_loop_fd_get(o); + if (fd == INVALID_SOCKET) return EINA_TRUE; /* postpone until fd_set() */ + + value = reuse_address; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) != 0) + { + ERR("setsockopt(%d, SOL_SOCKET, SO_REUSEADDR, %d): %s", + fd, value, eina_error_msg_get(efl_net_socket_error_get())); + pd->reuse_address = old; + return EINA_FALSE; + } + + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_efl_net_socket_udp_reuse_address_get(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + int value = 0, fd; + socklen_t valuelen; + + fd = efl_loop_fd_get(o); + if (fd == INVALID_SOCKET) return pd->reuse_address; + + /* if there is a fd, always query it directly as it may be modified + * elsewhere by nasty users. + */ + valuelen = sizeof(value); + if (getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, &valuelen) != 0) + { + ERR("getsockopt(%d, SOL_SOCKET, SO_REUSEADDR): %s", + fd, eina_error_msg_get(efl_net_socket_error_get())); + return EINA_FALSE; + } + + pd->reuse_address = !!value; /* sync */ + return pd->reuse_address; +} + +EOLIAN static Eina_Bool +_efl_net_socket_udp_reuse_port_set(Eo *o, Efl_Net_Socket_Udp_Data *pd, Eina_Bool reuse_port) +{ +#ifdef SO_REUSEPORT + int value, fd; + Eina_Bool old = pd->reuse_port; +#endif + + pd->reuse_port = reuse_port; + +#ifdef SO_REUSEPORT + fd = efl_loop_fd_get(o); + if (fd == INVALID_SOCKET) return EINA_TRUE; /* postpone until fd_set() */ + + value = reuse_port; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)) != 0) + { + ERR("setsockopt(%d, SOL_SOCKET, SO_REUSEPORT, %d): %s", + fd, value, eina_error_msg_get(efl_net_socket_error_get())); + pd->reuse_port = old; + return EINA_FALSE; + } +#endif + + return EINA_TRUE; +} + +EOLIAN static Eina_Bool +_efl_net_socket_udp_reuse_port_get(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ +#ifdef SO_REUSEPORT + int value = 0, fd; + socklen_t valuelen; + + fd = efl_loop_fd_get(o); + if (fd == INVALID_SOCKET) return pd->reuse_port; + + /* if there is a fd, always query it directly as it may be modified + * elsewhere by nasty users. + */ + valuelen = sizeof(value); + if (getsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &value, &valuelen) != 0) + { + ERR("getsockopt(%d, SOL_SOCKET, SO_REUSEPORT): %s", + fd, eina_error_msg_get(efl_net_socket_error_get())); + return EINA_FALSE; + } + + pd->reuse_port = !!value; /* sync */ +#endif + + return pd->reuse_port; +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_bind_set(Eo *o, Efl_Net_Socket_Udp_Data *pd, const char *address) +{ + SOCKET fd = efl_loop_fd_get(o); + + EINA_SAFETY_ON_TRUE_RETURN_VAL(fd != INVALID_SOCKET, EALREADY); + + eina_stringshare_replace(&pd->address_bind, address); + return 0; +} + +EOLIAN static const char * +_efl_net_socket_udp_bind_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Udp_Data *pd) +{ + return pd->address_bind; +} + +EOLIAN Efl_Object * +_efl_net_socket_udp_efl_object_constructor(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + pd->multicast.ttl = 1; + pd->multicast.ttl_set = EINA_FALSE; + pd->multicast.loopback = 0xff; + pd->cork = 0xff; + pd->dont_route = 0xff; + pd->reuse_address = 0xff; + pd->reuse_port = 0xff; + return efl_constructor(efl_super(o, MY_CLASS)); +} + +EOLIAN void +_efl_net_socket_udp_efl_object_destructor(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + if (pd->multicast.pending) + { + eina_list_free(pd->multicast.pending); + pd->multicast.pending = NULL; + } + + while (pd->multicast.groups) + efl_net_socket_udp_multicast_leave(o, pd->multicast.groups->data); + + efl_destructor(efl_super(o, MY_CLASS)); + + eina_stringshare_replace(&pd->address_bind, NULL); + + free(pd->addr_remote); + pd->addr_remote = NULL; + pd->addr_remote_len = 0; +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_efl_io_reader_read(Eo *o, Efl_Net_Socket_Udp_Data *pd, Eina_Rw_Slice *rw_slice) +{ + SOCKET fd = efl_io_reader_fd_reader_fd_get(o); + ssize_t r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(rw_slice, EINVAL); + if (fd == INVALID_SOCKET) goto error; + do + { + struct sockaddr_storage addr = {}; + socklen_t addrlen = sizeof(addr); + r = recvfrom(fd, rw_slice->mem, rw_slice->len, 0, (struct sockaddr *)&addr, &addrlen); + if (r == SOCKET_ERROR) + { + Eina_Error err = efl_net_socket_error_get(); + + if (err == EINTR) continue; + + rw_slice->len = 0; + rw_slice->mem = NULL; + + if (err == EAGAIN) efl_io_reader_can_read_set(o, EINA_FALSE); + return err; + } + + if (addr.ss_family == AF_INET) + { + const struct sockaddr_in *a = (const struct sockaddr_in *)pd->addr_remote; + uint32_t ipv4 = ntohl(a->sin_addr.s_addr); + if ((ipv4 != INADDR_BROADCAST) && (ipv4 != INADDR_ANY) && (!IN_MULTICAST(ipv4))) + { + if ((addrlen != pd->addr_remote_len) || + (memcmp(&addr, pd->addr_remote, addrlen) != 0)) + { + char buf[INET_ADDRSTRLEN + sizeof(":65536")]; + efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr); + ERR("dropping spurious datagram from %s (expected %s)", buf, efl_net_socket_address_remote_get(o)); + rw_slice->len = 0; + rw_slice->mem = NULL; + efl_io_reader_can_read_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "read" */ + return EAGAIN; + } + } + } + else + { + const struct sockaddr_in6 *a = (const struct sockaddr_in6 *)pd->addr_remote; + if ((!IN6_IS_ADDR_MULTICAST(&a->sin6_addr)) && (memcmp(&a->sin6_addr, &in6addr_any, sizeof(in6addr_any)) != 0)) + { + if ((addrlen != pd->addr_remote_len) || + (memcmp(&addr, pd->addr_remote, addrlen) != 0)) + { + char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")]; + efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr); + ERR("dropping spurious datagram from %s (expected %s)", buf, efl_net_socket_address_remote_get(o)); + rw_slice->len = 0; + rw_slice->mem = NULL; + efl_io_reader_can_read_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "read" */ + return EAGAIN; + } + } + } + } + while (r == SOCKET_ERROR); + + rw_slice->len = r; + efl_io_reader_can_read_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "read" */ + if (r == 0) + efl_io_reader_eos_set(o, EINA_TRUE); + + return 0; + + error: + rw_slice->len = 0; + rw_slice->mem = NULL; + return EINVAL; +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_efl_io_writer_write(Eo *o, Efl_Net_Socket_Udp_Data *pd, Eina_Slice *ro_slice, Eina_Slice *remaining) +{ + SOCKET fd = efl_io_writer_fd_writer_fd_get(o); + ssize_t r; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ro_slice, EINVAL); + if (fd == INVALID_SOCKET) goto error; + + do + { + r = sendto(fd, ro_slice->mem, ro_slice->len, 0, pd->addr_remote, pd->addr_remote_len); + if (r == SOCKET_ERROR) + { + Eina_Error err = efl_net_socket_error_get(); + + if (err == EINTR) continue; + + if (remaining) *remaining = *ro_slice; + ro_slice->len = 0; + ro_slice->mem = NULL; + if (err == EAGAIN) efl_io_writer_can_write_set(o, EINA_FALSE); + return err; + } + } + while (r == SOCKET_ERROR); + + if (remaining) + { + remaining->len = ro_slice->len - r; + remaining->bytes = ro_slice->bytes + r; + } + ro_slice->len = r; + efl_io_writer_can_write_set(o, EINA_FALSE); /* wait Efl.Loop.Fd "write" */ + + return 0; + + error: + if (remaining) *remaining = *ro_slice; + ro_slice->len = 0; + ro_slice->mem = NULL; + return EINVAL; +} + +static Eina_List * +_efl_net_socket_udp_multicast_find(const Eina_List *lst, const char *address) +{ + const char *str; + const Eina_List *node; + + EINA_LIST_FOREACH(lst, node, str) + { + if (strcmp(str, address) == 0) + return (Eina_List *)node; + } + + return NULL; +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_multicast_join(Eo *o, Efl_Net_Socket_Udp_Data *pd, const char *address) +{ + const Eina_List *found; + SOCKET fd = efl_loop_fd_get(o); + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + + found = _efl_net_socket_udp_multicast_find(pd->multicast.groups, address); + if (found) return EEXIST; + + pd->multicast.groups = eina_list_append(pd->multicast.groups, strdup(address)); + + if (fd == INVALID_SOCKET) + { + pd->multicast.pending = eina_list_append(pd->multicast.pending, eina_list_last(pd->multicast.groups)); + return 0; + } + + return efl_net_multicast_join(fd, efl_net_socket_fd_family_get(o), address); +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_multicast_leave(Eo *o, Efl_Net_Socket_Udp_Data *pd, const char *address) +{ + Eina_List *found; + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + + EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL); + + found = _efl_net_socket_udp_multicast_find(pd->multicast.groups, address); + if (!found) return ENOENT; + + if (fd == INVALID_SOCKET) + { + free(found->data); + pd->multicast.pending = eina_list_remove(pd->multicast.pending, found); + pd->multicast.groups = eina_list_remove_list(pd->multicast.groups, found); + return 0; + } + + err = efl_net_multicast_leave(fd, efl_net_socket_fd_family_get(o), address); + + free(found->data); + pd->multicast.groups = eina_list_remove_list(pd->multicast.groups, found); + return err; +} + +EOLIAN static Eina_Iterator * +_efl_net_socket_udp_multicast_groups_get(Eo *o EINA_UNUSED, Efl_Net_Socket_Udp_Data *pd) +{ + return eina_list_iterator_new(pd->multicast.groups); +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_multicast_time_to_live_set(Eo *o, Efl_Net_Socket_Udp_Data *pd, uint8_t ttl) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + uint8_t old = pd->multicast.ttl; + + pd->multicast.ttl_set = EINA_TRUE; + pd->multicast.ttl = ttl; + + if (fd == INVALID_SOCKET) return 0; + + err = efl_net_multicast_ttl_set(fd, efl_net_socket_fd_family_get(o), ttl); + if (err) + { + ERR("could not set multicast time to live=%hhu: %s", ttl, eina_error_msg_get(err)); + pd->multicast.ttl = old; + } + + return err; +} + +EOLIAN static uint8_t +_efl_net_socket_udp_multicast_time_to_live_get(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + uint8_t ttl = pd->multicast.ttl; + + if (fd == INVALID_SOCKET) return pd->multicast.ttl; + + err = efl_net_multicast_ttl_get(fd, efl_net_socket_fd_family_get(o), &ttl); + if (err) + ERR("could not get multicast time to live: %s", eina_error_msg_get(err)); + else + pd->multicast.ttl = ttl; + + return pd->multicast.ttl; +} + +EOLIAN static Eina_Error +_efl_net_socket_udp_multicast_loopback_set(Eo *o, Efl_Net_Socket_Udp_Data *pd, Eina_Bool loopback) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + Eina_Bool old = pd->multicast.loopback; + + pd->multicast.loopback = loopback; + + if (fd == INVALID_SOCKET) return 0; + + err = efl_net_multicast_loopback_set(fd, efl_net_socket_fd_family_get(o), loopback); + if (err) + { + ERR("could not set multicast loopback=%hhu: %s", loopback, eina_error_msg_get(err)); + pd->multicast.loopback = old; + } + + return err; +} + +EOLIAN static Eina_Bool +_efl_net_socket_udp_multicast_loopback_get(Eo *o, Efl_Net_Socket_Udp_Data *pd) +{ + SOCKET fd = efl_loop_fd_get(o); + Eina_Error err; + Eina_Bool loopback = pd->multicast.loopback; + + if (fd == INVALID_SOCKET) return pd->multicast.loopback; + + err = efl_net_multicast_loopback_get(fd, efl_net_socket_fd_family_get(o), &loopback); + if (err) + ERR("could not get multicast loopback: %s", eina_error_msg_get(err)); + else + pd->multicast.loopback = loopback; + + return pd->multicast.loopback; +} + #include "efl_net_socket_udp.eo.c" diff --git a/src/lib/ecore_con/efl_net_socket_udp.eo b/src/lib/ecore_con/efl_net_socket_udp.eo index 086d312..40e4169 100644 --- a/src/lib/ecore_con/efl_net_socket_udp.eo +++ b/src/lib/ecore_con/efl_net_socket_udp.eo @@ -12,6 +12,15 @@ class Efl.Net.Socket.Udp (Efl.Net.Socket.Fd) { ]] methods { + next_datagram_size_query { + [[Query the next datagram size. + + This will use system calls to determine the next + datagram size, in bytes. + ]] + return: size; [[size in bytes]] + } + @property cork { [[Controls UDP's cork using UDP_CORK]] get { } @@ -39,9 +48,127 @@ class Efl.Net.Socket.Udp (Efl.Net.Socket.Fd) { dont_route: bool; } } + + @property reuse_address { + [[Controls address reuse() using SO_REUSEADDR]] + get { } + set { + return: bool (false); [[$true on success]] + } + values { + reuse_address: bool; + } + } + + @property reuse_port { + [[Controls port reuse() using SO_REUSEPORT (since linux 3.9)]] + get { } + set { + return: bool (false); [[$true on success]] + } + values { + reuse_port: bool; + } + } + + multicast_join { + [[Join a multicast group. + + The multicast address should be in the format: + + IP@INTERFACE + + With '\@INTERFACE' being optional, such as: + + 224.0.0.1 - use any interface (ie: 0.0.0.0) + 224.0.0.1@0.0.0.0 + 224.0.0.1@192.168.0.1 - use the interface assigned to 192.168.0.1 + ff02::1@0 - use any interface + ff02::1@1 - use loopback interface (idx=1) + ]] + params { + address: string @nonull; + } + return: Eina.Error; + } + + multicast_leave { + [[Leave a multicast group. + + This reverses the effect of @.multicast_join. + ]] + params { + address: string @nonull; + } + return: Eina.Error; + } + + multicast_groups_get { + [[Return the multicast groups this server has joined. + + The iterator is only valid until a new group is joined + or left using @.multicast_join or @.multicast_leave. + ]] + return: free(own(iterator), eina_iterator_free); + } + + @property multicast_time_to_live { + [[Controls time to live in number of hops. + + If 1 (default), packets are only delivered to the local network. + ]] + get { } + set { + return: Eina.Error; [[0 on success, error code otherwise]] + } + values { + loopback: bool; + } + } + + @property multicast_loopback { + [[Controls whenever multicast will loopback packets locally. + + If $false, then packets won't be looped back locally, + just delivered for remote peers. + ]] + get { } + set { + return: Eina.Error; [[0 on success, error code otherwise]] + } + values { + loopback: bool; + } + } + + @property bind { + [[Force binding to a specific address. + + Some servers may request packets being sent from a + specific address, then one should bind to that address + before proceeding to dial. + + If no address is provided or bind is not called, a + random port is bound automatically to any address. + + \@note IP and PORT must be all numeric, no name + resolution is applied. + ]] + get { } + set { + return: Eina.Error; [[0 on success, error code otherwise]] + } + values { + address: string @nonull; + } + } } implements { + Efl.Object.constructor; + Efl.Object.destructor; Efl.Loop.Fd.fd.set; + Efl.Io.Reader.read; + Efl.Io.Writer.write; } } -- 2.7.4