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.
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);
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();
retval = EXIT_SUCCESS;
ecore_main_loop_quit();
}
+ free(rw_slice.mem);
}
static void
"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"),
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 */
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)
{
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();
efl_del(dialer);
end:
+ EINA_LIST_FREE(mcast_groups, str)
+ free(str);
ecore_con_shutdown();
ecore_shutdown();
}
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);
}
}
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),
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),
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),
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
server = NULL;
end:
+ EINA_LIST_FREE(udp_mcast_groups, str)
+ free(str);
ecore_con_shutdown();
ecore_shutdown();
# include <Evil.h>
#endif
+#include <sys/ioctl.h>
+
#include "Ecore.h"
#include "ecore_private.h"
#include "Ecore_Con.h"
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);
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)
{
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)
{
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);
/**
#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
struct {
Ecore_Thread *thread;
Efl_Future *timeout;
- } connect;
+ } resolver;
Eina_Stringshare *address_dial;
Eina_Bool connected;
Eina_Bool closed;
(!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));
}
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);
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);
}
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;
_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);
}
}
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);
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
]]
# include <Evil.h>
#endif
-#include <sys/ioctl.h>
-
#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;
{
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);
Eina_Error err = 0;
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
socklen_t addrlen = addr->ai_addrlen;
+ Eina_List *node;
SOCKET fd;
int r;
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:
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)
{
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);
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);
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"
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<string>), 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 {
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)
{
@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;
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
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"
]]
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 { }
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<string>), 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;
}
}