Page MenuHomePhabricator

Efl.net
Updated 873 Days AgoPublic

Introduction

Efl.Net classes implements networking features using new Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer, so they are transparent to the user of such interfaces like the Efl.Io.Copier described in Efl.Io Wiki Page.

For each protocol the core of communication is Efl.Net.Socket, that is used to represent a connection between two peers. It's the one that implements Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer interfaces. One shouldn't create Efl.Net.Socket instances directly, rather use a dialer or a server:

  • Efl.Net.Dialer represents a client 'dialing' to a server. It is the 'client side' of the usage, but the name dialer is used to avoid confusion with client handles generated by the server. These are usually Efl.Net.Socket of the given transport with added functionality that can be set when you create it, like the address to connect to.
  • Efl.Net.Server represents the server that will take client requests. It does not implement the Efl.Io interfaces per-se, they will emit objects that do in client,add event. These client handles are subclass of Efl.Net.Socket and thus can read, write and close. Important: if you're handling a client delivered on client,add, add your reference to it using efl_ref(), otherwise the client will be automatically deleted by the server. To finish a client, call Efl.Io.Closer.close() on it.

For ease of use addresses in these classes are strings encoded per-transport. For IP, it's in the form IP:PORT, with IPv6 encoded within braces such as [::1]:9999 (Local host, ::1, at port 9999), while IPv4 is the traditional 4 numbers separated by dots 127.0.0.1:9999. Both Efl.Net.Server.serve(address) an Efl.Net.Dialer.dial(address) will do name resolution whenever that applies to the protocol, as in IP (TCP, UDP, HTTP, WebSocket) they will also accept host and port names, such as localhost:http to get [::1]:80 as returned by getaddrinfo(). Whenever ports are omitted, a default or random port will be used (great for servers, not so useful for clients). Efl.Net.Server exposes address property with the actual value in use. This can be used to determine the port that is being used when no port was specified. Efl.Net.Socket exposes address_remote and address_local with actual IP numbers and port. Efl.Net.Dialer exposes address_dial with the address using in Efl.Net.Dialer.dial(address), which may be different from address_remote if it contained names instead of numbers.

Since name resolution can take a while, it's done asynchronously and the server is only ready when Efl.Net.Server event serving is emitted. Analogously, Efl.Net.Dialer event connected states the socket is ready for use.

The process of receiving and sending data is the same as described in Efl.Io Wiki Page: wait for Efl.Io.Reader event can_read,changed to report can_read == true, then call Efl.Io.Reader.read() which is a synchronous call that shouldn't block if called in when can_read == true. Analogously, when Efl.Io.Writer event can_write,changed reports can_write == true, one may call Efl.Io.Writer.write() on the socket without blocking.

To finish a dialer or a server client, simply close them using Efl.Io.Closer.close and everything is handled for you.

Extending Efl.Net with your own classes

Efl.Net.Socket

Efl.Net is based on a core component Efl.Net.Socket, that is a minimal set of interfaces requiring Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer with minimal methods such as local and remote addresses (all encoded to strings). Then before proceeding, take a look at Efl.Io Developer's Point of View.

The basic behavior of a socket is to report when there is data to read or when data can be written, by Efl.Io.Reader and Efl.Io.Writer interfaces. Once they are ready, calls to Efl.Io.Reader.read() and Efl.Io.Writer.write() could be done without blocking -- but EAGAIN may be returned even in such cases, for example when a protocol "wraps" another as done by SSL -- the underlying layer may report data to read, but SSL may deliver you nothing and thus returns EAGAIN.

If you protocol is based on POSIX-like socket, then you may benefit from extending Efl.Net.Socket.Fd, which will call recv(), send() and closesocket() (on Windows, regular close on POSIX). You may override Efl.Io.Reader.read() and Efl.Io.Writer.write() to provide something else, like wrapping/framing contents.

In cases where you don't have access to the underlying socket, don't worry, it's not that difficult either. This is the case with Efl.Net.Dialer.Http and Efl.Net.Dialer.WebSocket as they are based on cURL. Since the code is quite complex due a number of factors, I'll explain the essence:

  • if the library notifies you of data to read/write and you must explicitly call the operation, it matches perfectly with existing interfaces and it's trivial to create your class. Whenever it notifies you, reflect it in Efl.Io.Reader.can_read and Efl.Io.Writer.can_write, with related events "can_read,changed" and "can_write,changed". Map the Efl.Io.Reader.read() and Efl.Io.Writer.write() to the library functions.
  • if the library gives or requests immediate data, as in cURL, then it's more complex. You should store the received or "to be sent" data in your instance, likely using eina_binbuf, and once the library sends you more data you eina_binbuf_append() to the receive queue, which is consumed by Efl.Io.Reader.read(). When the library requests data you eina_binbuf_remove() from pending queue, which is populated by Efl.Io.Writer.write(). Most libraries allow you to pause them, in such case do it after every operation until user consumed all data, otherwise your receive buffer may grow too big. Likewise, when user data "drains" (empty send queue), pause the library so it don't keep asking for more.

Note that some libraries allows you to override their "Basic Input/Output", as is the case with OpenSSL/LibreSSL (BIO) and GnuTLS (gnutls_transport_*). In such case you can more easily integrate, as done with Efl.Net.Socket.Ssl.

Efl.Net.Dialer

If you want to create a dialer, that is, something that access a remote server, then you create an Efl.Net.Dialer.My_Class, usually based on Efl.Net.Socket.My_Class if you pretend to share the socket with your server implementation. If there is no server to be implemented or able to reuse, then just create Efl.Net.Dialer.My_Class based on Efl.Loop_User or Efl.Object, depending if you need a main loop or not.

The dialer requires all the methods in Efl.Net.Socket and few more such as address_dial, connected, timeout_dial and the most important: dial(address). The dial(address) method starts the communication with the remote server, and should be bound by timeout_dial, which can be implemented by Efl.Loop.timeout(), thus you're likely basing your class on Efl.Loop_User. Some libraries offer their own timeout settings, in such case you can go with that.

Once connected, set Efl.Net.Dialer.connected = true and emit "connected" event.

On errors, emit "error" event with pointer to Eina_Error.

Efl.Net.Server

Unlike the dialer, these do not extend Efl.Net.Socket, rather they emit events "client,add" with Efl.Net.Socket subclasses. You may use a common Efl.Net.Socket.My_Class, such as done by Efl.Net.Socket.Tcp, or create a custom socket to play your client, as done by Efl.Net.Socket.Udp. What is essential is that the new client conforms to Efl.Net.Socket interface, offering address_remote, address_local, Efl.Io.Reader, Efl.Io.Writer and Efl.Io.Closer.

The server starts accepting requests after serve(address) is called and it emits "serving" event, making Efl.Net.Server.serving = true.

When a client is accepted emit "client,add" with the client Efl.Net.Socket instance, increase the counter Efl.Net.Server.clients_count while that is alive. Monitor for live clients using events "closed" (Efl.Io.Closer) and "del" (Efl.Object). When they're gone, decrease the clients_count.

If the number of clients go in excess (Efl.Net.Server.clients_limit) and reject_excess == true`, then do not accept the client (or immediately delete them) and emit "client,rejected" event.

On errors, emit "error" event with pointer to Eina_Error.

Examples

A number of examples at src/examples/ecore will use Efl.Net classes, namely:

  • efl_io_copier_example.c can use dialers to receive or send data from/to remote peers.
  • efl_net_dialer_http_example.c extensive usage of HTTP dialer, adding headers, cookie jar and authentication.
  • efl_net_dialer_websocket_example.c like the HTTP sample (since WebSockets is built on top of it), shows extensive usage of HTTP dialer, adding headers, cookie jar and authentication.
  • efl_net_dialer_udp_example.c controls particular UDP features, such as join multicast groups, receive full datagrams and so on.
  • efl_net_socket_ssl_dialer_example.c shows how to manually upgrade an existing Efl.Net.Dialer to a SSL connection. This is useful for protocols that start in clear text and then negotiate to start SSL/TLS, such as the common STARTTLS in IMAP. For automatic wrap use Efl.Net.Dialer.Ssl as present in efl_io_copier_example.c
  • efl_net_socket_ssl_server_example.c shows how to manually upgrade an existing Efl.Net.Socket emited from a server "client,add" event to a SSL connection. This is useful for protocols that start in clear text and then negotiate to start SSL/TLS, such as the common STARTTLS in IMAP. For automatic wrap use Efl.Net.Server.Ssl present in efl_net_server_example.c
  • efl_net_server_example.c showcase how servers can be created and handle clients. As one can see, although transports are very different, the same API is exposed to them if all one wants to do is receive (read) and send (write) data. Few options are exposed for some protocols, such as UDP multicast.

Examples Usage

IPv4 TCP Hello World server

Start an IPv4 TCP Hello World server on all address and port 9999. All clients that connect will receive a Hello World string, then the server will read all client data until it's closed, then it will be printed out:

$ ./src/examples/ecore/efl_net_server_example tcp '0.0.0.0:9999'
INFO: serving at 0.0.0.0:9999
TCP options:
 - IPv6 only: 1
INFO: accepted client 127.0.0.1:50084
INFO: using sender buffer 0x400000009983fc03 with copier 0x40000000a183fc05 for client 127.0.0.1:50084
INFO: using receiver buffer 0x400000009d83fc04 with copier 0x40000000d983fc13 for client 127.0.0.1:50084
INFO: client 127.0.0.1:50084 can_write=1
INFO: client 127.0.0.1:50084 can_write=0
INFO: sent to 127.0.0.1:50084 12 bytes:
--BEGIN SENT DATA--
Hello World!
--END SENT DATA--
INFO: send copier done, check if should close 0x40000000a183fc05
INFO: client 127.0.0.1:50084 can_write=1
INFO: client 127.0.0.1:50084 can_read=1
INFO: client 127.0.0.1:50084 can_read=0
INFO: client 127.0.0.1:50084 can_read=1
INFO: client 127.0.0.1:50084 can_read=0
INFO: client 127.0.0.1:50084 eos.
INFO: recv from 127.0.0.1:50084 11 bytes:
--BEGIN RECV DATA--
some data


--END RECV DATA--
INFO: receive copier done, check if should close 0x40000000d983fc13
INFO: client 127.0.0.1:50084 can_write=0
INFO: client 127.0.0.1:50084 closed.

The output produced above is the result of an TCP client sending some data\n to 127.0.0.1 port 9999, such as using telnet:

$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello World!some data
^]
telnet> quit
Connection closed.
NOTE: The string - IPv6 only: 1 in the above output is irrelevant in this context since it's an IPv4 only server.

The Hello World server implementation will use 2 independent Efl.Io.Copier:

  • send_copier will use an Efl.Io.Buffer with the string Hello World as source and the Efl.Net.Socket as destination
  • recv_copier will use Efl.Net.Socket as source and an empty Efl.Io.Bufferas destination. Once finished the contents of Efl.Io.Buffer will be printed out, it's what you see within --BEGIN RECV DATA-- and --END RECV DATA-- lines.

IPv6 TCP Hello World server

Like for IPv4, but replace the 'any address' string with an IPv6 one: Start an IPv6 TCP Hello World server on all address and port 9999. All clients that connect will receive a Hello World string, then the server will read all client data until it's closed, then it will be printed out:

$ ./src/examples/ecore/efl_net_server_example tcp '[::]:9999'
INFO: serving at [::]:9999
TCP options:
 - IPv6 only: 1
INFO: accepted client [::1]:55420
INFO: using sender buffer 0x40000000995b9703 with copier 0x40000000a15b9705 for client [::1]:55420
INFO: using receiver buffer 0x400000009d5b9704 with copier 0x40000000d95b9713 for client [::1]:55420
INFO: client [::1]:55420 can_write=1
INFO: client [::1]:55420 can_write=0
INFO: sent to [::1]:55420 12 bytes:
--BEGIN SENT DATA--
Hello World!
--END SENT DATA--
INFO: send copier done, check if should close 0x40000000a15b9705
INFO: client [::1]:55420 can_write=1
INFO: client [::1]:55420 can_read=1
INFO: client [::1]:55420 can_read=0
INFO: client [::1]:55420 can_read=1
INFO: client [::1]:55420 can_read=0
INFO: client [::1]:55420 eos.
INFO: recv from [::1]:55420 11 bytes:
--BEGIN RECV DATA--
some data


--END RECV DATA--
INFO: receive copier done, check if should close 0x40000000d95b9713
INFO: client [::1]:55420 can_write=0
INFO: client [::1]:55420 closed.

The output produced above is the result of an TCP client sending some data\n to 127.0.0.1 port 9999, such as using telnet:

$ telnet ::1 9999
Trying ::1...
Connected to ::1.
Escape character is '^]'.
Hello World!some data
^]
telnet> quit
Connection closed.

You can see that trying to connect from IPv4 doesn't work since - IPv6 only: 1 is used by default:

$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

You can change that by executing the server with --ipv4-on-ipv6 comman line option, that will set Efl.Net.Server.ipv6_only = false, allowing the kernel to automatically map IPv4 to IPv6:

$ ./src/examples/ecore/efl_net_server_example --ipv4-on-ipv6 tcp '[::]:9999'
INFO: serving at [::]:9999
TCP options:
 - IPv6 only: 0
INFO: accepted client [::ffff:127.0.0.1]:50898
INFO: using sender buffer 0x400000009adcf960 with copier 0x40000000a2dcf962 for client [::ffff:127.0.0.1]:50898
INFO: using receiver buffer 0x400000009edcf961 with copier 0x40000000dadcf970 for client [::ffff:127.0.0.1]:50898
INFO: client [::ffff:127.0.0.1]:50898 can_write=1
INFO: client [::ffff:127.0.0.1]:50898 can_write=0
INFO: sent to [::ffff:127.0.0.1]:50898 12 bytes:
--BEGIN SENT DATA--
Hello World!
--END SENT DATA--
INFO: send copier done, check if should close 0x40000000a2dcf962
INFO: client [::ffff:127.0.0.1]:50898 can_write=1
INFO: client [::ffff:127.0.0.1]:50898 can_read=1
INFO: client [::ffff:127.0.0.1]:50898 can_read=0
INFO: client [::ffff:127.0.0.1]:50898 can_read=1
INFO: client [::ffff:127.0.0.1]:50898 can_read=0
INFO: client [::ffff:127.0.0.1]:50898 eos.
INFO: recv from [::ffff:127.0.0.1]:50898 11 bytes:
--BEGIN RECV DATA--
some data


--END RECV DATA--
INFO: receive copier done, check if should close 0x40000000dadcf970
INFO: client [::ffff:127.0.0.1]:50898 can_write=0
INFO: client [::ffff:127.0.0.1]:50898 closed.

The client command is:

$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Hello World!some data
^]
telnet> quit
Connection closed.

IPv6 + IPv4 TCP echo server

Building on the command lines above example, instead of always sending a Hello World string and reading for all data, change it to be an echo server, that will receive data from clients and will send it back. This is achieved by the -e or --echo switch:

$ ./src/examples/ecore/efl_net_server_example --ipv4-on-ipv6 --echo tcp '[::]:9999'
INFO: serving at [::]:9999
TCP options:
 - IPv6 only: 0
INFO: accepted client [::ffff:127.0.0.1]:51432
INFO: using an echo copier=0x4000000099c3a946 for client [::ffff:127.0.0.1]:51432
INFO: client [::ffff:127.0.0.1]:51432 can_write=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=0
INFO: client [::ffff:127.0.0.1]:51432 can_write=0
INFO: client [::ffff:127.0.0.1]:51432 can_write=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=0
INFO: client [::ffff:127.0.0.1]:51432 can_write=0
INFO: client [::ffff:127.0.0.1]:51432 can_write=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=1
INFO: client [::ffff:127.0.0.1]:51432 can_read=0
INFO: client [::ffff:127.0.0.1]:51432 eos.
INFO: echo copier done, close and del 0x4000000099c3a946
INFO: client [::ffff:127.0.0.1]:51432 can_write=0
INFO: client [::ffff:127.0.0.1]:51432 closed.

The client command is:

$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello
hello
hi
hi
^]
telnet> quit
Connection closed.

As you can see, whatever you type (hello\n and hi\n), you get a copy right away.

The echo server implementation will use a single Efl.Io.Copier with both source and destination being the Efl.Net.Socket, super-simple, huh?!

IPv6 + IPv4 UDP echo server

If all that you want is a different transport, try changing tcp by udp in that command line:

$ ./src/examples/ecore/efl_net_server_example --ipv4-on-ipv6 --echo udp '[::]:9999'
INFO: serving at [::]:9999
UDP options:
 - IPv6 only: 0
 - don't route: 0
 - multicast TTL: 1
 - multicast loopback: 1
 - multicast groups:
INFO: accepted client [::ffff:127.0.0.1]:51299
INFO: using an echo copier=0x4000000099fb29d1 for client [::ffff:127.0.0.1]:51299
INFO: client [::ffff:127.0.0.1]:51299 can_read=1
INFO: client [::ffff:127.0.0.1]:51299 can_read=0
INFO: client [::ffff:127.0.0.1]:51299 can_read=1
INFO: client [::ffff:127.0.0.1]:51299 can_read=0
INFO: client '[::ffff:127.0.0.1]:51299' timed out, delete it.
INFO: echo copier done, close and del 0x4000000099fb29d1

See that unlike TCP, UDP prints more options since it can control multicast and if the packets should be routed via a gateway or not.

NOTE: unlike TCP, UDP has no concept of connection, so there is no way to known when the client went away. Our server will detect that by using an inactivity timeout of 10.0 seconds that can be changed with --inactivity-timeout=N or -t=N, with N being the number of inactive seconds to call the client dead. This is implemented by using Efl.Io.Copier.inactivity_timeout property, 0 disables it.

Since telnet doesn't do UDP, use our own udp client:

./src/examples/ecore/efl_net_dialer_udp_example '127.0.0.1:9999'
INFO: resolved 127.0.0.1:9999 => 127.0.0.1:9999
INFO: connected to '127.0.0.1:9999' (127.0.0.1:9999)
INFO:  - local address=0.0.0.0:51299
INFO:  - read-after-write=0 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: we're done
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

This command will write Hello World an Second Write and then finish. It's not reading anything back, so UDP datagrams echoed by the server will be discarded.

It's more useful to read back the values, to do that use --read-after-write or -r switch:

$ ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write '127.0.0.1:9999'
INFO: resolved 127.0.0.1:9999 => 127.0.0.1:9999
INFO: connected to '127.0.0.1:9999' (127.0.0.1:9999)
INFO:  - local address=0.0.0.0:51800
INFO:  - read-after-write=2 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: done writing, now wait for 2 reads
INFO: can read=1 (needed reads=2)
INFO: can read=0 (needed reads=2)
INFO: read 'Hello World'
INFO: can read=1 (needed reads=1)
INFO: can read=0 (needed reads=1)
INFO: read 'Second Write'
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

As expected you received 2 lines saying read values 'Hello World' and 'Second Write', echoed by the server.

On some platforms such as Linux, one can coalesce multiple write in a single datagram by means of cork: while that property is true, all packets will be buffered and sent in a single datagram later on. If we enable cork in the above example, we'll see that we read a single Hello WorldSecond Write string as we use cork=true before sending the first packet and only make it false after sending the last packet:

$  ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write --cork '127.0.0.1:9999'
INFO: resolved 127.0.0.1:9999 => 127.0.0.1:9999
INFO: connected to '127.0.0.1:9999' (127.0.0.1:9999)
INFO:  - local address=0.0.0.0:58572
INFO:  - read-after-write=1 reads required
INFO:  - cork=1
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can read=1 (needed reads=1)
INFO: can read=0 (needed reads=1)
INFO: read 'Hello WorldSecond Write'
INFO: can write=1 (needed writes=0)
INFO: we're done
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

IPv6 UDP multicast echo server

We can have our server to join multicast groups by using --udp-multicast-group=IP@IFACE, this works for both IPv4 and IPv6. The @IFACE is optional and is family-dependent, on IPv4 it's the IP of a local interface, such as 192.168.0.123 (0.0.0.0 means all interfaces), while on IPv6 it's the interface number, such as 2 for eth0 (0 means all interfaces).

We're using a common multicast address ff02::1 (https://en.wikipedia.org/wiki/Multicast_address#IPv6) with the well known meaning "All nodes on the local network segment".

$ ./src/examples/ecore/efl_net_server_example --udp-multicast-group='[ff02::1]@0' --echo udp '[::]:9999'
INFO: serving at [::]:9999
UDP options:
 - IPv6 only: 1
 - don't route: 0
 - multicast TTL: 1
 - multicast loopback: 1
 - multicast groups:
   * [ff02::1]@0
INFO: accepted client [fe80::f68e:38ff:fee3:c364]:37547
INFO: using an echo copier=0x40000000983b956f for client [fe80::f68e:38ff:fee3:c364]:37547
INFO: client [fe80::f68e:38ff:fee3:c364]:37547 can_read=1
INFO: client [fe80::f68e:38ff:fee3:c364]:37547 can_read=0

Client command line is as simple as before, just use the multicast address:

$ ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write '[ff02::1]:9999'
INFO: resolved [ff02::1]:9999 => [ff02::1]:9999
INFO: connected to '[ff02::1]:9999' ([ff02::1]:9999)
INFO:  - local address=[::]:42388
INFO:  - read-after-write=2 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: done writing, now wait for 2 reads
INFO: can read=1 (needed reads=2)
INFO: can read=0 (needed reads=2)
INFO: read 'Hello World'
INFO: can read=1 (needed reads=1)
INFO: can read=0 (needed reads=1)
INFO: read 'Second Write'
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

UDP client receiving data

As for UDP there is no connection, it may receive data by getting packets on the address listed on its INFO: - local address= line. If you run the client as above without a running server, it will send packets to multicast and then will wait to read 2 replies (as stated in INFO: - read-after-write=2 reads required). You can then send some packets directly to it by using another client. Here one client will be called receiver, while the second is called sender:

receiver
$ ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write '[ff02::1]:9999'
INFO: resolved [ff02::1]:9999 => [ff02::1]:9999
INFO: connected to '[ff02::1]:9999' ([ff02::1]:9999)
INFO:  - local address=[::]:41165
INFO:  - read-after-write=2 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: done writing, now wait for 2 reads
INFO: can read=1 (needed reads=2)
INFO: can read=0 (needed reads=2)
INFO: read 'Hello World'
INFO: can read=1 (needed reads=1)
INFO: can read=0 (needed reads=1)
INFO: read 'Second Write'
INFO: main loop finished.
INFO: can write=0 (needed writes=0)
sender
$ ./src/examples/ecore/efl_net_dialer_udp_example '[ff02::1]:41165'
INFO: resolved [::1]:41165 => [::1]:41165
INFO: connected to '[::1]:41165' ([::1]:41165)
INFO:  - local address=[::]:60164
INFO:  - read-after-write=0 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: we're done
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

Here we're sending a datagram to multicast address ff02::1, which the receiver joined automatically due the address used in its command line. We could as well as send to ::1 (localhost) or any other address receiver will take (see it's using :: that means any address).

NOTE: the local address will change since it's using a random "ephemeral" port. One can use --bind=IP:PORT to use a fixed port, which makes examples and testing easier to use.

Using --bind=[::]:12345 we get:

receiver_bind_12345
$ ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write --bind='[::]:12345' '[ff02::1]:9999'
INFO: resolved [ff02::1]:9999 => [ff02::1]:9999
INFO: connected to '[ff02::1]:9999' ([ff02::1]:9999)
INFO:  - local address=[::]:12345
INFO:  - read-after-write=2 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: done writing, now wait for 2 reads
sender_localhost_12345
$ ./src/examples/ecore/efl_net_dialer_udp_example '[::1]:12345'
INFO: resolved [::1]:12345 => [::1]:12345
INFO: connected to '[::1]:12345' ([::1]:12345)
INFO:  - local address=[::]:42111
INFO:  - read-after-write=0 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: we're done
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

The receiver side may use another address that is not the multicast and manually join the groups, this can be done as in the following command line:

receiver_manual_join
$ ./src/examples/ecore/efl_net_dialer_udp_example --read-after-write --multicast-group='[ff02::1]' --bind='[::]:12345' '[::]:9999'
INFO: resolved [::]:9999 => [::]:9999
INFO: connected to '[::]:9999' ([::]:9999)
INFO:  - local address=[::]:12345
INFO:  - read-after-write=2 reads required
INFO:  - cork=0
INFO:  - timeout_dial=30.000000s
INFO:  - reuse address=1
INFO:  - reuse port=1
INFO:  - multicast TTL: 1
INFO:  - multicast loopback: 1
INFO:  - multicast groups:
   * [ff02::1]
INFO: can write=1 (needed writes=2)
INFO: can write=0 (needed writes=2)
INFO: wrote 'Hello World'
INFO: can write=1 (needed writes=1)
INFO: can write=0 (needed writes=1)
INFO: wrote 'Second Write'
INFO: can write=1 (needed writes=0)
INFO: done writing, now wait for 2 reads
INFO: can read=1 (needed reads=2)
INFO: can read=0 (needed reads=2)
INFO: read 'Hello World'
INFO: can read=1 (needed reads=1)
INFO: can read=0 (needed reads=1)
INFO: read 'Second Write'
INFO: main loop finished.
INFO: can write=0 (needed writes=0)

The same sender to [::1]:12345 will work.

AF_UNIX server & clients

IMPORTANT: The AF_UNIX server and dialer are not available on Windows
$ ./src/examples/ecore/efl_net_server_example --echo unix /tmp/bla.sock
INFO: serving at /tmp/bla.sock
INFO: accepted client unnamed:9
INFO: using an echo copier=0x40000000b3dbc698 for client unnamed:9
INFO: client unnamed:9 can_read=1
INFO: client unnamed:9 can_write=1
INFO: client unnamed:9 can_read=0
INFO: client unnamed:9 can_write=0
INFO: client unnamed:9 can_write=1
INFO: client 'unnamed:9' timed out, delete it.
INFO: echo copier done, close and del 0x40000000b3dbc698
INFO: client unnamed:9 can_write=0
INFO: client unnamed:9 eos.
INFO: client unnamed:9 closed.

Since telnet cannot do AF_UNIX, we offer an example client:

$ ./src/examples/ecore/efl_net_dialer_unix_example --read-after-write /tmp/bla.sock
INFO: resolved /tmp/bla.sock => /tmp/bla.sock
INFO: connected to '/tmp/bla.sock' (/tmp/bla.sock)
INFO:  - local address=unnamed:8
INFO:  - read-after-write=1
INFO: can write=1 (wanted bytes=12)
INFO: can write=0 (wanted bytes=0)
INFO: wrote 'Hello World!', still pending=0 bytes
INFO: can write=1 (wanted bytes=0)
INFO: can read=1
INFO: can read=0
INFO: read 'Hell'
INFO: can read=1
INFO: can read=0
INFO: read 'o Wo'
INFO: can read=1
INFO: can read=0
INFO: read 'rld!'
INFO: can read=1
INFO: can read=0
INFO: end of stream.
INFO: read ''
INFO: main loop finished.
INFO: can write=0 (wanted bytes=0)
INFO: end of stream.

To simulate multiple reads, we only read 4 bytes at once, thus you see Hello World! string was broken into 3 reads.

TCP + SSL client - Manual Upgrade

One can test SSL on top of TCP in two examples: efl_net_socket_ssl_dialer_example.c and efl_io_copier_example.c. For efl_io_copier_example.c it's like the example for TCP, you may just need to disable SSL verification if you're using self signed certificates using --no-ssl-verify.

NOTE: efl_io_copier_example.c will use the helper Efl.Net.Dialer.Ssl that automatically creates an internal Efl.Net.Dialer.Tcp and wraps it with SSL, while efl_net_socket_ssl_dialer_example.c shows how to do it manually. Usually people will go with the helper, however some protocols start as clear text and needs a manual upgrade, then it may aid the implementation -- as you can in the code see it's also very simple!

A more interesting usage is to simulate an HTTPS request to google using the efl_net_socket_ssl_dialer_example.c.

$ ./src/examples/ecore/efl_net_socket_ssl_dialer_example google.com:https  \
    --send="GET / HTTP/1.0" \
    --send="Host: google.com" \
    --send=""
INFO:  - certificates in use:
INFO:  - private keys in use:
INFO:  - certificate revogation lists in use:
INFO:  - certificate authorities in use:
INFO:  - verify-mode=2
INFO: resolved google.com:https => 74.125.138.139:443
INFO: connected to 'google.com:https' (74.125.138.139:443)
INFO:  - local address=172.30.42.180:49516
INFO:  - verify-mode=2
INFO:  - hostname-verify=1
INFO:  - hostname-override='<none>'
INFO: SSL can write=1 (needed writes=5 offset=0)
INFO: SSL wrote 'GET / HTTP/1.0'
INFO: SSL can write=0 (needed writes=4 offset=0)
INFO: SSL ready!
INFO: SSL can write=1 (needed writes=4 offset=0)
INFO: SSL wrote '
'
INFO: SSL can write=0 (needed writes=3 offset=0)
INFO: SSL can write=1 (needed writes=3 offset=0)
INFO: SSL wrote 'Host: google.com'
INFO: SSL can write=0 (needed writes=2 offset=0)
INFO: SSL can write=1 (needed writes=2 offset=0)
INFO: SSL wrote '
'
INFO: SSL can write=0 (needed writes=1 offset=0)
INFO: SSL can write=1 (needed writes=1 offset=0)
INFO: SSL wrote '
'
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL read 'HTTP/1.0 302 Found
Cache-Control: private
Content-Type: text/'
INFO: SSL read 'html; charset=UTF-8
Location: https://www.google.com.br/?gfe_r'
INFO: SSL read 'd=cr&ei=2oIYWLbkD6-NhAaLg6KgBA
Content-Length: 263
Date: Tue,'
INFO: SSL read ' 01 Nov 2016 11:56:10 GMT
Alt-Svc: quic=":443"; ma=2592000; v='
INFO: SSL read '"36,35,34"

<HTML><HEAD><meta http-equiv="content-type" conte'
INFO: SSL read 'nt="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><B'
INFO: SSL read 'ODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="https:/'
INFO: SSL read '/www.google.com.br/?gfe_rd=cr&amp;ei=2oIYWLbkD6-NhAaLg6KgBA">he'
INFO: SSL read 're</A>.
</BODY></HTML>
'
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL eos
INFO: SSL can read=0
INFO: SSL read ''
INFO: main loop finished.
INFO: SSL closed

The command will send the list of strings given with --send=STRING separated by the line terminator (--line-delimiter=DELIMITER), by default \r\n. This is quite useful for HTTP, just be sure to send an empty line (--send="") as the protocol requires it to consider the header done.

Unlike most examples that reads using the Efl.Io.Copier, this one manually reads only 63 bytes at once, to force multiple reads. So you will see many partial INFO: SSL read. Likewise, writes are manually done. They are not required, only done to show how to do it manually without the copier helper.

IMPORTANT: by default server certificate will be verified, including its hostname if matches any of Subject Alternative Name DNS entries, so reaching "google.com.br" and the others will work as expected, but direct access to an IP address won't. In this case you can provide the actual hostname with --hostname-override=SERVERNAME or disable that verification with --no-hostname-verify

TCP + SSL server - Automatic

The well known example documented above efl_net_server_example will use Efl.Net.Server.Ssl that automatically wraps clients into Efl.Net.Socket.Ssl once they are accepted, this allows the same server code to work with minimal changes. What is required is to give a certificate and a key, which you can generate with openssl command:

$ openssl req -new -x509 -days 30 -nodes -out server.pem -keyout server.pem
Generating a 2048 bit RSA private key
.................................................................................+++
........................................+++
writing new private key to 'server.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:OR
Locality Name (eg, city) []:Portland
Organization Name (eg, company) [Internet Widgits Pty Ltd]:XPTO Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:root@localhost

This will create both the certificate and the key inside server.pem file, if you want them in different files, use different -keyout KEYFILENAME.pem.

Then proceed with the server as usual adding the --ssl-certificate=server.pem and --ssl-private-key=server.pem:

$ ./src/examples/ecore/efl_net_server_example --ipv4-on-ipv6 ssl localhost:9999 \
          --ssl-certificate=server.pem \
          --ssl-private-key=server.pem
INFO: serving at [::1]:9999
INFO: accepted client [::1]:53722
INFO: using sender buffer 0x40000000917a752e with copier 0x40000000997a7530 for client [::1]:53722
INFO: using receiver buffer 0x40000000957a752f with copier 0x40000000d17a753e for client [::1]:53722
INFO: client [::1]:53722 can_write=1
INFO: sent to [::1]:53722 12 bytes:
--BEGIN SENT DATA--
Hello World!
--END SENT DATA--
INFO: send copier done, check if should close 0x40000000997a7530
INFO: client '[::1]:53722' timed out recv, delete it.
INFO: recv from [::1]:53722 0 bytes:
--BEGIN RECV DATA--


--END RECV DATA--
INFO: receive copier done, check if should close 0x40000000d17a753e
INFO: client [::1]:53722 eos.
INFO: client [::1]:53722 closed.

Which is the result of a connecting efl_io_copier_example.c executed as the following. Note that we disable the SSL certificate verification as this is a self-signed certificate and it would fail.

$ ./src/examples/ecore/efl_io_copier_example ssl://localhost:9999 :stdout: \
          --no-ssl-verify
INFO: copy source=0x400000007c0222fc (Efl_Net_Dialer_Ssl) to destination=0x40000000c002230d (Efl_Io_Stdout)
INFO: output can_write=1
INFO: dialer connected to 'localhost:9999' ([::1]:9999)
INFO: input can_read=1
INFO: input can_read=0
INFO: input can_read=1
Hello World!INFO: output can_write=0
INFO: data: 0x55fffd4fb570+12
INFO: read=12, written=12
INFO: input can_read=0
INFO: output can_write=1
INFO: input can_read=1
INFO: input can_read=0
INFO: input can_read=1
INFO: input can_read=0
INFO: input eos=1
INFO: done

TCP + SSL server - Manual Upgrade

While efl_net_server_example.c will use the Efl.Net.Server.Ssl wrapper, sometimes you need to negotiate the upgrade to a secure connection in another protocol, such as done in IMAP's STARTTLS.

This is very easy to do: once "client,add" event comes with a new Efl.Net.Socket, you create a Efl.Net.Socket.Ssl and call efl_net_socket_ssl_adopt(client_ssl, client_socket, ssl_context), where the last parameter is a shared Efl.Net.Ssl.Context which will contain your certificate, private keys, revogation lists and authorities. Then you can just wait for Efl.Io.Reader.can_read = true, Efl.Io.Writer.can_write = true` to start communicating on secure channel. To know more details you can monitor new events "ssl,ready" which notifies a successful handshake and "ssl,error" which notifies some problem.

Assuming the certificate and its private key are stored in server.pem as done in the previous example, execution is as simple. Note this is an echo server, so we'll test with efl_net_socket_ssl_dialer_example to send some strings so we can receive them back:

$ ./src/examples/ecore/efl_net_socket_ssl_server_example \
          --certificate=server.pem \
          --private-key=server.pem \
          localhost:9999
INFO:  - certificates in use:
INFO:     * server.pem
INFO:  - private keys in use:
INFO:     * server.pem
INFO:  - certificate revogation lists in use:
INFO:  - certificate authorities in use:
INFO: serving at [::1]:9999
INFO: accepted client [::1]:54454
INFO: using an echo copier=0x400000008e7854c2 for ssl [::1]:54454
INFO: ssl [::1]:54454 can_write=1
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl [::1]:54454 can_read=1
INFO: ssl [::1]:54454 can_read=0
INFO: ssl '[::1]:54454' timed out, delete it.
INFO: echo copier done, close and del 0x400000008e7854c2
INFO: ssl [::1]:54454 eos.
INFO: ssl [::1]:54454 closed.

Unlike for efl_io_copier_example.c we have more fine grained control and must disable explicitly the SSL certificate verification (for self signed certificates) and hostname verification since one may use 127.0.0.1 or ::1 and that would fail testing. We also use \n as line delimiter so it prints better than \r\n that is the default.

$ ./src/examples/ecore/efl_net_socket_ssl_dialer_example \
          --send="Hello World" \
          --send="Second Write" \
          --line-delimiter="\n" \
          --no-hostname-verify \
          --verify-mode=none \
          localhost:9999
INFO:  - certificates in use:
INFO:  - private keys in use:
INFO:  - certificate revogation lists in use:
INFO:  - certificate authorities in use:
INFO:  - verify-mode=0
INFO: resolved localhost:9999 => [::1]:9999
INFO: connected to 'localhost:9999' ([::1]:9999)
INFO:  - local address=[::1]:54454
INFO:  - verify-mode=0
INFO:  - hostname-verify=0
INFO:  - hostname-override='<none>'
INFO: SSL can write=1 (needed writes=4 offset=0)
INFO: SSL wrote 'Hello World'
INFO: SSL can write=0 (needed writes=3 offset=0)
INFO: SSL ready!
INFO: SSL can write=1 (needed writes=3 offset=0)
INFO: SSL wrote '
'
INFO: SSL can write=0 (needed writes=2 offset=0)
INFO: SSL can write=1 (needed writes=2 offset=0)
INFO: SSL wrote 'Second Write'
INFO: SSL can write=0 (needed writes=1 offset=0)
INFO: SSL can write=1 (needed writes=1 offset=0)
INFO: SSL wrote '
'
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL read 'Hello World'
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL read '
'
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL read 'Second Write'
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL read '
'
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL can read=1
INFO: SSL can read=0
INFO: SSL eos
INFO: SSL read ''
INFO: main loop finished.
INFO: SSL closed

systemd socket activation

Using systemd socket activation is easy, simply call the method Efl.Net.Server.Fd.socket_activate(address) for file-descriptor based servers (TCP, UDP, Unix) or Efl.Net.Server.Ssl.socket_activate(address). The socket to be used is the next in $LISTEN_FDS, as servers call that function, they are 'popped' from the list, thus order matters! To avoid errors, you must pass the desired address, that is validated with the actual socket and fails to load if doesn't match -- note that this is only validation, not being used to search for a socket with that address.

The well known efl_net_server_example.c offers a switch --socket-activated to first try to use the socket activated file descriptor, if that fails then it will try to do a regular Efl.Net.Server.serve(address).

IMPORTANT: The following files showcase usage and serves as test purposes but they must respect systemd rules of absolute paths for ExecStart= commands, thus you must symlink efl_net_server_example binary to /tmp: ln -sf $PWD/src/examples/ecore/efl_net_socket_ssl_server_example /tmp.

Example systemd configuration files should be placed in ~/.config/systemd/user for ease of use and systemctl --user should be used without sudo as listed below.

SSL+TCP systemd socket activation
IMPORTANT: copy server.pem to /tmp
/home/gustavo/.config/systemd/user/efl_net_server_example-ssl.socket
[Unit]
Description=efl_net_server_example using SSL+TCP

[Socket]
ListenStream=[::]:9992
BindIPv6Only=both

[Install]
WantedBy=sockets.target
/home/gustavo/.config/systemd/user/efl_net_server_example-ssl.service
[Unit]
Description=efl_net_server_example using TCP+SSL

[Service]
Type=simple
ExecStart=/tmp/efl_net_server_example ssl [::]:9992 --socket-activated --ssl-certificate=/tmp/server.pem --ssl-private-key=/tmp/server.pem

[Install]
Also=efl_net_server_example-ssl.socket
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user start efl_net_server_example-ssl.socket

Now you're able to use efl_io_copier_example or efl_net_socket_ssl_dialer_example on localhost:9992.

TCP systemd socket activation
/home/gustavo/.config/systemd/user/efl_net_server_example-tcp.socket
[Unit]
Description=efl_net_server_example using TCP

[Socket]
ListenStream=[::]:9990
BindIPv6Only=both

[Install]
WantedBy=sockets.target
/home/gustavo/.config/systemd/user/efl_net_server_example-tcp.service
[Unit]
Description=efl_net_server_example using TCP

[Service]
Type=simple
ExecStart=/tmp/efl_net_server_example tcp [::]:9990 --socket-activated

[Install]
Also=efl_net_server_example-tcp.socket
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user start efl_net_server_example-tcp.socket

Now you're able to use efl_io_copier_example or telnet on localhost:9990.

UDP systemd socket activation
/home/gustavo/.config/systemd/user/efl_net_server_example-udp.socket
[Unit]
Description=efl_net_server_example using UDP

[Socket]
ListenDatagram=[::]:9991
BindIPv6Only=both

[Install]
WantedBy=sockets.target
/home/gustavo/.config/systemd/user/efl_net_server_example-udp.service
[Unit]
Description=efl_net_server_example using UDP

[Service]
Type=simple
ExecStart=/tmp/efl_net_server_example udp [::]:9991 --socket-activated

[Install]
Also=efl_net_server_example-udp.socket
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user start efl_net_server_example-udp.socket

Now you're able to use efl_io_copier_example or efl_net_dialer_udp_example on localhost:9991.

AF_UNIX systemd socket activation
/home/gustavo/.config/systemd/user/efl_net_server_example-unix.socket
[Unit]
Description=efl_net_server_example using UNIX

[Socket]
ListenStream=/tmp/efl_net_server_example.af_unix-sock

[Install]
WantedBy=sockets.target
/home/gustavo/.config/systemd/user/efl_net_server_example-unix.service
[Unit]
Description=efl_net_server_example using UNIX

[Service]
Type=simple
ExecStart=/tmp/efl_net_server_example unix /tmp/efl_net_server_example.af_unix-sock --socket-activated

[Install]
Also=efl_net_server_example-unix.socket
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user start efl_net_server_example-unix.socket

Now you're able to use efl_io_copier_example or efl_net_dialer_unix_example on /tmp/efl_net_server_example.af_unix-sock.

Buggy systemd socket activation example
/home/gustavo/.config/systemd/user/efl_net_server_example-bug.socket
[Unit]
Description=efl_net_server_example that will fail to use socket activation due wrong family

[Socket]
ListenStream=/tmp/efl_net_server_example.bug-sock

[Install]
WantedBy=sockets.target
/home/gustavo/.config/systemd/user/efl_net_server_example-bug.service
[Unit]
Description=efl_net_server_example that will fail to use socket activation due wrong family

[Service]
Type=simple
ExecStart=/tmp/efl_net_server_example ssl [::]:9992 --socket-activated --ssl-certificate=/tmp/server.pem --ssl-private-key=/tmp/server.pem

[Install]
Also=efl_net_server_example-bug.socket
WantedBy=default.target
$ systemctl --user daemon-reload
$ systemctl --user start efl_net_server_example-bug.socket

This example tries to use an AF_UNIX socket to activate an SSL+TCP server, it will validate and see it doesn't match, printing and error and will try to serve the actual address given in .service file. Once you connect, journal says:

$ systemctl --user status efl_net_server_example-bug.service
...
Nov 01 16:06:26 host efl_net_server_example[8945]: ERR<8945>:ecore_con lib/ecore_con/ecore_con.c:3286 efl_net_ip_socket_activate_check() socket 3 family=1 differs from wanted 10
Nov 01 16:06:26 host efl_net_server_example[8945]: WARNING: --socket-activated, but not able to use $LISTEN_FDS descriptors. Try to start the server...
Nov 01 16:06:26 host efl_net_server_example[8945]: INFO: serving at [::]:9992

That is, an AF_INET6 family was required, but it detected AF_UNIX and did not use that.

Last Author
barbieri
Last Edited
Nov 1 2016, 12:25 PM
Projects
None
Subscribers
None