efl_net_server support systemd socket activation.

It includes extensive verifications to avoid mistakes and usage of
incorrect sockets.
This commit is contained in:
Gustavo Sverzut Barbieri 2016-11-01 16:01:57 -02:00
parent 3ac1812a1f
commit c2630c829f
13 changed files with 575 additions and 10 deletions

View File

@ -504,6 +504,8 @@ static const Ecore_Getopt options = {
{
ECORE_GETOPT_STORE_TRUE('e', "echo",
"Behave as 'echo' server, send back to client all the data receive"),
ECORE_GETOPT_STORE_TRUE(0, "socket-activated",
"Try to use $LISTEN_FDS from systemd, if not do a regular serve()"),
ECORE_GETOPT_STORE_UINT('l', "clients-limit",
"If set will limit number of clients to accept"),
ECORE_GETOPT_STORE_TRUE('r', "clients-reject-excess",
@ -564,9 +566,11 @@ main(int argc, char **argv)
Eina_List *crls = NULL;
Eina_List *cas = NULL;
char *cipher_choice = NULL;
Eina_Bool socket_activated = EINA_FALSE;
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_BOOL(echo),
ECORE_GETOPT_VALUE_BOOL(socket_activated),
ECORE_GETOPT_VALUE_UINT(clients_limit),
ECORE_GETOPT_VALUE_BOOL(clients_reject_excess),
ECORE_GETOPT_VALUE_BOOL(ipv6_only),
@ -659,6 +663,8 @@ main(int argc, char **argv)
efl_net_server_fd_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_fd_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_fd_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
else if (cls == EFL_NET_SERVER_UDP_CLASS)
{
@ -677,6 +683,7 @@ main(int argc, char **argv)
efl_net_server_fd_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_fd_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_fd_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
else if (cls == EFL_NET_SERVER_SSL_CLASS)
{
@ -708,11 +715,13 @@ main(int argc, char **argv)
efl_net_server_ssl_close_on_exec_set(server, EINA_TRUE); /* recommended */
efl_net_server_ssl_reuse_address_set(server, EINA_TRUE); /* optional, but nice for testing */
efl_net_server_ssl_reuse_port_set(server, EINA_TRUE); /* optional, but nice for testing... not secure unless you know what you're doing */
if (socket_activated) efl_net_server_ssl_socket_activate(server, address);
}
#ifndef _WIN32
else if (cls == EFL_NET_SERVER_UNIX_CLASS)
{
efl_net_server_unix_unlink_before_bind_set(server, EINA_TRUE); /* makes testing easier */
if (socket_activated) efl_net_server_fd_socket_activate(server, address);
}
#endif
@ -721,12 +730,18 @@ main(int argc, char **argv)
* with the object to add more properties that couldn't be done
* during efl_add().
*/
err = efl_net_server_serve(server, address);
if (err)
if (!efl_net_server_serving_get(server))
{
fprintf(stderr, "ERROR: could not serve(%s): %s\n",
address, eina_error_msg_get(err));
goto end_server;
if (socket_activated)
fprintf(stderr, "WARNING: --socket-activated, but not able to use $LISTEN_FDS descriptors. Try to start the server...\n");
err = efl_net_server_serve(server, address);
if (err)
{
fprintf(stderr, "ERROR: could not serve(%s): %s\n",
address, eina_error_msg_get(err));
goto end_server;
}
}
ecore_main_loop_begin();

View File

@ -3175,6 +3175,220 @@ efl_net_ip_port_split(char *buf, const char **p_host, const char **p_port)
return EINA_TRUE;
}
#ifdef HAVE_SYSTEMD
Eina_Error
efl_net_ip_socket_activate_check(const char *address, int family, int type, Eina_Bool *listening)
{
SOCKET fd = SD_LISTEN_FDS_START + sd_fd_index;
int r;
if (sd_fd_index >= sd_fd_max) return ENOENT;
if (family == AF_UNIX)
{
char buf[sizeof(struct sockaddr_un)] = "";
const char *sun_path;
size_t len;
if (strncmp(address, "abstract:", strlen("abstract:")) == 0)
{
const char *path = address + strlen("abstract:");
if (strlen(path) + 2 > sizeof(buf))
{
ERR("abstract path is too long: %s", path);
return EINVAL;
}
buf[0] = '\0';
memcpy(buf + 1, path, strlen(path) + 1);
sun_path = buf;
len = strlen(path) + 2;
}
else
{
if (strlen(address) + 1 > sizeof(buf))
{
ERR("path is too long: %s", address);
return EINVAL;
}
sun_path = address;
len = strlen(address) + 1;
}
r = sd_is_socket_unix(fd, type, 0, sun_path, len);
if (r < 0)
{
ERR("socket %d is not of family=%d, type=%d", fd, family, type);
return EINVAL;
}
if (listening) *listening = (r == 1);
return 0;
}
else if ((family == AF_UNSPEC) || (family == AF_INET) || (family == AF_INET6))
{
char *str;
const char *host, *port;
struct sockaddr_storage sock_addr;
struct sockaddr_storage want_addr = { .ss_family = family };
socklen_t addrlen;
Eina_Error err;
int x;
r = sd_is_socket(fd, family, type, (type == SOCK_DGRAM) ? -1 : 0);
if (r < 0)
{
ERR("socket %d is not of family=%d, type=%d", fd, family, type);
return EINVAL;
}
if ((type == SOCK_DGRAM) && (listening)) *listening = EINA_FALSE;
else if (listening) *listening = (r == 1);
addrlen = sizeof(sock_addr);
if (getsockname(fd, (struct sockaddr *)&sock_addr, &addrlen) != 0)
{
err = efl_net_socket_error_get();
ERR("could not query socket=%d name: %s", fd, eina_error_msg_get(err));
return err;
}
str = strdup(address);
EINA_SAFETY_ON_NULL_RETURN_VAL(str, ENOMEM);
if (!efl_net_ip_port_split(str, &host, &port))
{
ERR("invalid IP:PORT address: %s", address);
free(str);
return EINVAL;
}
if (!port) port = "0";
if ((family == AF_UNSPEC) && (strchr(host, ':'))) family = AF_INET6;
if (family == AF_INET6)
{
struct sockaddr_in6 *a = (struct sockaddr_in6 *)&want_addr;
x = inet_pton(AF_INET6, host, &a->sin6_addr);
}
else
{
struct sockaddr_in *a = (struct sockaddr_in *)&want_addr;
x = inet_pton(AF_INET, host, &a->sin_addr);
}
/* FAST PATH: numbers were provided */
if (x == 1)
{
char *endptr;
unsigned long p;
Eina_Bool matches;
want_addr.ss_family = family;
if (want_addr.ss_family != sock_addr.ss_family)
{
ERR("socket %d family=%d differs from wanted %d", fd, sock_addr.ss_family, want_addr.ss_family);
free(str);
return EINVAL;
}
errno = 0;
p = strtoul(port, &endptr, 10);
if ((errno) || (endptr == port) || (*endptr != '\0'))
{
ERR("invalid port number '%s'", port);
free(str);
return EINVAL;
}
else if (p > UINT16_MAX)
{
ERR("invalid port number %lu (out of range)", p);
free(str);
return ERANGE;
}
if (family == AF_INET6)
{
struct sockaddr_in6 *a = (struct sockaddr_in6 *)&want_addr;
a->sin6_port = htons(p);
matches = memcmp(a, &sock_addr, sizeof(*a)) == 0;
}
else
{
struct sockaddr_in *a = (struct sockaddr_in *)&want_addr;
x = inet_pton(AF_INET, host, &a->sin_addr);
a->sin_port = htons(p);
matches = memcmp(a, &sock_addr, sizeof(*a)) == 0;
}
if (!matches)
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")] = "";
efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&sock_addr);
ERR("socket %d address %s differs from wanted %s", fd, buf, address);
free(str);
return EINVAL;
}
free(str);
return 0;
}
else
{
/*
* NOTE: this may block, but users should be using the IP:PORT
* as numbers, getting into the fast path above.
*
* This is best-try to help API to be usable, but may
* impact the main loop execution for a while. However
* people doing bind are expected to do so on a local
* address, usually resolves faster without too many DNS
* lookups.
*/
struct addrinfo hints = {
.ai_socktype = type,
.ai_family = family,
.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED,
};
struct addrinfo *results, *itr;
DBG("resolving '%s', it may block main loop! Consider using IP:PORT", address);
do
{
x = getaddrinfo(host, port, &hints, &results);
}
while ((r == EAI_AGAIN) || ((r == EAI_SYSTEM) && (errno == EINTR)));
if (x != 0)
{
ERR("couldn't resolve host='%s', port='%s': %s",
host, port, gai_strerror(x));
free(str);
return EINVAL;
}
err = EINVAL;
for (itr = results; itr != NULL; itr = itr->ai_next)
{
if (sock_addr.ss_family != itr->ai_family) continue;
if (memcmp(itr->ai_addr, &sock_addr, itr->ai_addrlen) == 0)
{
err = 0;
break;
}
}
freeaddrinfo(results);
free(str);
return err;
}
}
else
{
if (listening) *listening = EINA_FALSE;
ERR("unsupported family=%d", family);
return EINVAL;
}
}
#endif
static void
_cleanup_close(void *data)
{

View File

@ -411,6 +411,30 @@ Eina_Bool efl_net_unix_fmt(char *buf, size_t buflen, SOCKET fd, const struct soc
#endif
Eina_Bool efl_net_ip_port_fmt(char *buf, size_t buflen, const struct sockaddr *addr);
#ifdef HAVE_SYSTEMD
/**
* Checks if the next FD in the sd_fd_index:sd_fd_max is of the
* expected family, protocol and if it's listening.
*
* This is similar to sd_is_socket()/sd_is_socket_inet(), but will
* also parse address in our standard format "IP:PORT", including IPv6
* within braces, and then will validate the address with
* getsockaddr() for INET.
*
* @param address the address to validate
* @param family AF_UNIX or AF_UNSPEC for INET, in that case AF_INET
* or AF_INET6 will be inferred from @a address.
* @param type SOCK_STREAM or SOCK_DGRAM
* @param[out] listening where to return listening state, should be
* NULL for @a type SOCK_DGRAM
*
* @return 0 on success, error otherwise.
*
* @internal
*/
Eina_Error efl_net_ip_socket_activate_check(const char *address, int family, int type, Eina_Bool *listening);
#endif
/**
* @brief splits an address in the format "host:port" in two
* null-terminated strings.

View File

@ -17,6 +17,10 @@
# include <Evil.h>
#endif
#ifdef HAVE_SYSTEMD
# include <systemd/sd-daemon.h>
#endif
#define MY_CLASS EFL_NET_SERVER_FD_CLASS
typedef struct _Efl_Net_Server_Fd_Data
@ -196,6 +200,58 @@ _efl_net_server_fd_efl_net_server_serving_get(Eo *o EINA_UNUSED, Efl_Net_Server_
return pd->serving;
}
EOLIAN static Eina_Error
_efl_net_server_fd_socket_activate(Eo *o, Efl_Net_Server_Fd_Data *pd EINA_UNUSED, const char *address)
{
EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) != INVALID_SOCKET, EALREADY);
EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
#ifndef HAVE_SYSTEMD
DBG("systemd support is disabled");
return ENOENT;
#else
if (!sd_fd_max)
{
DBG("This service was not socket-activated, no $LISTEN_FDS");
return ENOENT;
}
else if (sd_fd_index >= sd_fd_max)
{
WRN("No more systemd sockets available. Configuration mismatch?");
return ENOENT;
}
else
{
SOCKET fd = SD_LISTEN_FDS_START + sd_fd_index;
int family;
socklen_t len = sizeof(family);
if (getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &family, &len) != 0)
{
WRN("socket %d failed to return family: %s", fd, eina_error_msg_get(efl_net_socket_error_get()));
return EINVAL;
}
sd_fd_index++;
efl_net_server_fd_family_set(o, family);
efl_loop_fd_set(o, fd);
if (efl_loop_fd_get(o) == INVALID_SOCKET)
{
sd_fd_index--;
WRN("socket %d could not be used by %p (%s)",
fd, o, efl_class_name_get(efl_class_get(o)));
return EINVAL;
}
/* by default they all come with close_on_exec set
* and we must apply our local conf.
*/
efl_net_server_fd_close_on_exec_set(o, pd->close_on_exec);
return 0;
}
#endif
}
EOLIAN static Eina_Bool
_efl_net_server_fd_close_on_exec_set(Eo *o, Efl_Net_Server_Fd_Data *pd, Eina_Bool close_on_exec)
{

View File

@ -5,6 +5,53 @@ class Efl.Net.Server.Fd (Efl.Loop.Fd, Efl.Net.Server) {
]]
methods {
socket_activate {
[[If this method is called use an already activated socket.
This method allows a server to use an existing socket
received from systemd or similar system.
It will replace @Efl.Net.Server.serve, thus if this is
used, that method will return EALREADY.
\@note The parameter 'address' given to this function is
only used to validate the next socket available, it
doesn't search for a socket with the given address. Thus
the socket to be used is the next unused and orders
matter is using multiple servers!
\@note subclasses must validate the socket and return
EINVAL prior to call the base class with
Efl.Object.super. They must also emit "serving" when
ready, for instance stream protocols may need to check
for listening and if not try to listen. Usually they
will also query getsockname() and set
@Efl.Net.Server.address.
Errors:
- EALREADY: already have a socket, either from
previous @.socket_activate or
@Efl.Net.Server.serve. Usually represents a
programming error.
- ENOENT: no sockets received from process manager
(ie: systemd). Usually this is not a fatal error,
just proceed by calling @Efl.Net.Server.serve
- EINVAL: the socket received is not of the correct
family, type or protocol. Usually this means a
configuration mismatch with the order of server
creation and calls to socket_activate. The
systemd.socket entries must match the order in your
application.
]]
params {
address: string; [[The address to validate the next available socket. It doesn't serve as search, only as validation!]]
}
return: Eina.Error; [[0 on success, ENOENT if no socket is available or EALREADY if already have a socket]]
}
@property family {
[[The address family (AF_*) family of this socket.

View File

@ -117,6 +117,14 @@ _efl_net_server_ssl_ssl_context_get(Eo *o EINA_UNUSED, Efl_Net_Server_Ssl_Data *
return pd->ssl_ctx;
}
EOLIAN static Eina_Error
_efl_net_server_ssl_socket_activate(Eo *o, Efl_Net_Server_Ssl_Data *pd, const char *address)
{
EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_net_server_serving_get(o), EALREADY);
EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
return efl_net_server_fd_socket_activate(pd->server, address);
}
EOLIAN static Eina_Error
_efl_net_server_ssl_efl_net_server_serve(Eo *o EINA_UNUSED, Efl_Net_Server_Ssl_Data *pd, const char *address)
{

View File

@ -18,14 +18,49 @@ class Efl.Net.Server.Ssl (Efl.Loop_User, Efl.Net.Server) {
}
}
socket_activate {
[[If this method is called use an already activated socket.
This method allows a server to use an existing socket
received from systemd or similar system.
It will replace @Efl.Net.Server.serve, thus if this is
used, that method will return EALREADY.
\@note The parameter 'address' given to this function is
only used to validate the next socket available, it
doesn't search for a socket with the given address. Thus
the socket to be used is the next unused and orders
matter is using multiple servers!
Errors:
- EALREADY: already have a socket, either from
previous @.socket_activate or
@Efl.Net.Server.serve. Usually represents a
programming error.
- ENOENT: no sockets received from process manager
(ie: systemd). Usually this is not a fatal error,
just proceed by calling @Efl.Net.Server.serve
- EINVAL: the socket received is not of the correct
family, type or protocol. Usually this means a
configuration mismatch with the order of server
creation and calls to socket_activate. The
systemd.socket entries must match the order in your
application.
]]
params {
address: string; [[The address to validate the next available socket. It doesn't serve as search, only as validation!]]
}
return: Eina.Error; [[0 on success, ENOENT if no socket is available or EALREADY if already have a socket]]
}
@property family {
[[The address family (AF_*) family of this socket.
It will be one of AF_INET (IPv4), AF_INET6 (IPv6),
AF_UNIX...
It must be set before the @Efl.Loop.Fd.fd.set is called
with a valid file descriptor.
It will be one of AF_INET (IPv4) or AF_INET6 (IPv6).
]]
get { }
values {

View File

@ -154,6 +154,64 @@ _efl_net_server_tcp_resolved(void *data, const char *host EINA_UNUSED, const cha
efl_unref(o);
}
EOLIAN static Eina_Error
_efl_net_server_tcp_efl_net_server_fd_socket_activate(Eo *o, Efl_Net_Server_Tcp_Data *pd EINA_UNUSED, const char *address)
{
EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) != INVALID_SOCKET, EALREADY);
EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
#ifndef HAVE_SYSTEMD
return efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
#else
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
Eina_Bool listening;
Eina_Error err;
struct sockaddr_storage *addr;
socklen_t addrlen;
int fd;
err = efl_net_ip_socket_activate_check(address, AF_UNSPEC, SOCK_STREAM, &listening);
if (err) return err;
err = efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
if (err) return err;
fd = efl_loop_fd_get(o);
if (!listening)
{
if (listen(fd, 0) != 0)
{
err = efl_net_socket_error_get();
DBG("listen(%d): %s", fd, eina_error_msg_get(err));
goto error;
}
}
addrlen = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
{
err = efl_net_socket_error_get();
ERR("getsockname(%d): %s", fd, eina_error_msg_get(err));
goto error;
}
else if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
efl_net_server_address_set(o, buf);
DBG("fd=%d serving at %s", fd, address);
efl_net_server_serving_set(o, EINA_TRUE);
return 0;
error:
efl_net_server_fd_family_set(o, AF_UNSPEC);
efl_loop_fd_set(o, INVALID_SOCKET);
closesocket(fd);
return err;
}
#endif
}
EOLIAN static Eina_Error
_efl_net_server_tcp_efl_net_server_serve(Eo *o, Efl_Net_Server_Tcp_Data *pd, const char *address)
{

View File

@ -39,5 +39,6 @@ class Efl.Net.Server.Tcp (Efl.Net.Server.Fd) {
Efl.Net.Server.serve;
Efl.Net.Server.Fd.client_add;
Efl.Net.Server.Fd.client_reject;
Efl.Net.Server.Fd.socket_activate;
}
}

View File

@ -201,6 +201,53 @@ _efl_net_server_udp_resolved(void *data, const char *host EINA_UNUSED, const cha
efl_unref(o);
}
EOLIAN static Eina_Error
_efl_net_server_udp_efl_net_server_fd_socket_activate(Eo *o, Efl_Net_Server_Udp_Data *pd EINA_UNUSED, const char *address)
{
EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) != INVALID_SOCKET, EALREADY);
EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
#ifndef HAVE_SYSTEMD
return efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
#else
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
Eina_Error err;
struct sockaddr_storage *addr;
socklen_t addrlen;
int fd;
err = efl_net_ip_socket_activate_check(address, AF_UNSPEC, SOCK_DGRAM, NULL);
if (err) return err;
err = efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
if (err) return err;
fd = efl_loop_fd_get(o);
addrlen = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
{
err = efl_net_socket_error_get();
ERR("getsockname(%d): %s", fd, eina_error_msg_get(err));
goto error;
}
else if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
efl_net_server_address_set(o, buf);
DBG("fd=%d serving at %s", fd, address);
efl_net_server_serving_set(o, EINA_TRUE);
return 0;
error:
efl_net_server_fd_family_set(o, AF_UNSPEC);
efl_loop_fd_set(o, INVALID_SOCKET);
closesocket(fd);
return err;
}
#endif
}
EOLIAN static Eina_Error
_efl_net_server_udp_efl_net_server_serve(Eo *o, Efl_Net_Server_Udp_Data *pd, const char *address)
{

View File

@ -125,5 +125,6 @@ class Efl.Net.Server.Udp (Efl.Net.Server.Fd) {
Efl.Object.destructor;
Efl.Net.Server.serve;
Efl.Net.Server.Fd.process_incoming_data;
Efl.Net.Server.Fd.socket_activate;
}
}

View File

@ -162,6 +162,64 @@ _efl_net_server_unix_bind_job(void *data, const Efl_Event *event EINA_UNUSED)
efl_unref(o);
}
EOLIAN static Eina_Error
_efl_net_server_unix_efl_net_server_fd_socket_activate(Eo *o, Efl_Net_Server_Unix_Data *pd EINA_UNUSED, const char *address)
{
EINA_SAFETY_ON_TRUE_RETURN_VAL(efl_loop_fd_get(o) != INVALID_SOCKET, EALREADY);
EINA_SAFETY_ON_NULL_RETURN_VAL(address, EINVAL);
#ifndef HAVE_SYSTEMD
return efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
#else
{
char buf[INET6_ADDRSTRLEN + sizeof("[]:65536")];
Eina_Bool listening;
Eina_Error err;
struct sockaddr_storage *addr;
socklen_t addrlen;
int fd;
err = efl_net_ip_socket_activate_check(address, AF_UNIX, SOCK_STREAM, &listening);
if (err) return err;
err = efl_net_server_fd_socket_activate(efl_super(o, MY_CLASS), address);
if (err) return err;
fd = efl_loop_fd_get(o);
if (!listening)
{
if (listen(fd, 0) != 0)
{
err = efl_net_socket_error_get();
DBG("listen(%d): %s", fd, eina_error_msg_get(err));
goto error;
}
}
addrlen = sizeof(addr);
if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) != 0)
{
err = efl_net_socket_error_get();
ERR("getsockname(%d): %s", fd, eina_error_msg_get(err));
goto error;
}
else if (efl_net_ip_port_fmt(buf, sizeof(buf), (struct sockaddr *)&addr))
efl_net_server_address_set(o, buf);
DBG("fd=%d serving at %s", fd, address);
efl_net_server_serving_set(o, EINA_TRUE);
return 0;
error:
efl_net_server_fd_family_set(o, AF_UNSPEC);
efl_loop_fd_set(o, INVALID_SOCKET);
closesocket(fd);
return err;
}
#endif
}
EOLIAN static Eina_Error
_efl_net_server_unix_efl_net_server_serve(Eo *o, Efl_Net_Server_Unix_Data *pd, const char *address)
{

View File

@ -24,5 +24,6 @@ class Efl.Net.Server.Unix (Efl.Net.Server.Fd) {
Efl.Net.Server.serve;
Efl.Net.Server.Fd.client_add;
Efl.Net.Server.Fd.client_reject;
Efl.Net.Server.Fd.socket_activate;
}
}