レナート   Wunschkonzert, Ponyhof und Abenteuerspielplatz   ﻟﻴﻨﺎﺭﺕ

Mon, 26 Sep 2011

systemd for Administrators, Part XI

Here's the eleventh installment of my ongoing series on systemd for Administrators:

Converting inetd Services

In a previous episode of this series I covered how to convert a SysV init script to a systemd unit file. In this story I hope to explain how to convert inetd services into systemd units.

Let's start with a bit of background. inetd has a long tradition as one of the classic Unix services. As a superserver it listens on an Internet socket on behalf of another service and then activate that service on an incoming connection, thus implementing an on-demand socket activation system. This allowed Unix machines with limited resources to provide a large variety of services, without the need to run processes and invest resources for all of them all of the time. Over the years a number of independent implementations of inetd have been shipped on Linux distributions. The most prominent being the ones based on BSD inetd and xinetd. While inetd used to be installed on most distributions by default, it nowadays is used only for very few selected services and the common services are all run unconditionally at boot, primarily for (perceived) performance reasons.

One of the core feature of systemd (and Apple's launchd for the matter) is socket activation, a scheme pioneered by inetd, however back then with a different focus. Systemd-style socket activation focusses on local sockets (AF_UNIX), not so much Internet sockets (AF_INET), even though both are supported. And more importantly even, socket activation in systemd is not primarily about the on-demand aspect that was key in inetd, but more on increasing parallelization (socket activation allows starting clients and servers of the socket at the same time), simplicity (since the need to configure explicit dependencies between services is removed) and robustness (since services can be restarted or may crash without loss of connectivity of the socket). However, systemd can also activate services on-demand when connections are incoming, if configured that way.

Socket activation of any kind requires support in the services themselves. systemd provides a very simple interface that services may implement to provide socket activation, built around sd_listen_fds(). As such it is already a very minimal, simple scheme. However, the traditional inetd interface is even simpler. It allows passing only a single socket to the activated service: the socket fd is simply duplicated to STDIN and STDOUT of the process spawned, and that's already it. In order to provide compatibility systemd optionally offers the same interface to processes, thus taking advantage of the many services that already support inetd-style socket activation, but not yet systemd's native activation.

Before we continue with a concrete example, let's have a look at three different schemes to make use of socket activation:

  1. Socket activation for parallelization, simplicity, robustness: sockets are bound during early boot and a singleton service instance to serve all client requests is immediately started at boot. This is useful for all services that are very likely used frequently and continously, and hence starting them early and in parallel with the rest of the system is advisable. Examples: D-Bus, Syslog.
  2. On-demand socket activation for singleton services: sockets are bound during early boot and a singleton service instance is executed on incoming traffic. This is useful for services that are seldom used, where it is advisable to save the resources and time at boot and delay activation until they are actually needed. Example: CUPS.
  3. On-demand socket activation for per-connection service instances: sockets are bound during early boot and for each incoming connection a new service instance is instantiated and the connection socket (and not the listening one) is passed to it. This is useful for services that are seldom used, and where performance is not critical, i.e. where the cost of spawning a new service process for each incoming connection is limited. Example: SSH.

The three schemes provide different performance characteristics. After the service finishes starting up the performance provided by the first two schemes is identical to a stand-alone service (i.e. one that is started without a super-server, without socket activation), since the listening socket is passed to the actual service, and code paths from then on are identical to those of a stand-alone service and all connections are processes exactly the same way as they are in a stand-alone service. On the other hand, performance of the third scheme is usually not as good: since for each connection a new service needs to be started the resource cost is much higher. However, it also has a number of advantages: for example client connections are better isolated and it is easier to develop services activated this way.

For systemd primarily the first scheme is in focus, however the other two schemes are supported as well. (In fact, the blog story I covered the necessary code changes for systemd-style socket activation in was about a service of the second type, i.e. CUPS). inetd primarily focusses on the third scheme, however the second scheme is supported too. (The first one isn't. Presumably due the focus on the third scheme inetd got its -- a bit unfair -- reputation for being "slow".)

So much about the background, let's cut to the beef now and show an inetd service can be integrated into systemd's socket activation. We'll focus on SSH, a very common service that is widely installed and used but on the vast majority of machines probably not started more often than 1/h in average (and usually even much less). SSH has supported inetd-style activation since a long time, following the third scheme mentioned above. Since it is started only every now and then and only with a limited number of connections at the same time it is a very good candidate for this scheme as the extra resource cost is negligble: if made socket-activatable SSH is basically free as long as nobody uses it. And as soon as somebody logs in via SSH it will be started and the moment he or she disconnects all its resources are freed again. Let's find out how to make SSH socket-activatable in systemd taking advantage of the provided inetd compatibility!

Here's the configuration line used to hook up SSH with classic inetd:

ssh stream tcp nowait root /usr/sbin/sshd sshd -i

And the same as xinetd configuration fragment:

service ssh {
        socket_type = stream
        protocol = tcp
        wait = no
        user = root
        server = /usr/sbin/sshd
        server_args = -i
}

Most of this should be fairly easy to understand, as these two fragments express very much the same information. The non-obvious parts: the port number (22) is not configured in inetd configuration, but indirectly via the service database in /etc/services: the service name is used as lookup key in that database and translated to a port number. This indirection via /etc/services has been part of Unix tradition though has been getting more and more out of fashion, and the newer xinetd hence optionally allows configuration with explicit port numbers. The most interesting setting here is the not very intuitively named nowait (resp. wait=no) option. It configures whether a service is of the second (wait) resp. third (nowait) scheme mentioned above. Finally the -i switch is used to enabled inetd mode in SSH.

The systemd translation of these configuration fragments are the following two units. First: sshd.socket is a unit encapsulating information about a socket to listen on:

[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target

Most of this should be self-explanatory. A few notes: Accept=yes corresponds to nowait. It's hopefully better named, referring to the fact that for nowait the superserver calls accept() on the listening socket, where for wait this is the job of the executed service process. WantedBy=sockets.target is used to ensure that when enabled this unit is activated at boot at the right time.

And here's the matching service file sshd@.service:

[Unit]
Description=SSH Per-Connection Server

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket

This too should be mostly self-explanatory. Interesting is StandardInput=socket, the option that enables inetd compatibility for this service. StandardInput= may be used to configure what STDIN of the service should be connected for this service (see the man page for details). By setting it to socket we make sure to pass the connection socket here, as expected in the simple inetd interface. Note that we do not need to explicitly configure StandardOutput= here, since by default the setting from StandardInput= is inherited if nothing else is configured. Important is the "-" in front of the binary name. This ensures that the exit status of the per-connection sshd process is forgotten by systemd. Normally, systemd will store the exit status of a all service instances that die abnormally. SSH will sometimes die abnormally with an exit code of 1 or similar, and we want to make sure that this doesn't cause systemd to keep around information for numerous previous connections that died this way (until this information is forgotten with systemctl reset-failed).

sshd@.service is an instantiated service, as described in the preceeding installment of this series. For each incoming connection systemd will instantiate a new instance of sshd@.service, with the instance identifier named after the connection credentials.

You may wonder why in systemd configuration of an inetd service requires two unit files instead of one. The reason for this is that to simplify things we want to make sure that the relation between live units and unit files is obvious, while at the same time we can order the socket unit and the service units independently in the dependency graph and control the units as independently as possible. (Think: this allows you to shutdown the socket independently from the instances, and each instance individually.)

Now, let's see how this works in real life. If we drop these files into /etc/systemd/system we are ready to enable the socket and start it:

# systemctl enable sshd.socket
ln -s '/etc/systemd/system/sshd.socket' '/etc/systemd/system/sockets.target.wants/sshd.socket'
# systemctl start sshd.socket
# systemctl status sshd.socket
sshd.socket - SSH Socket for Per-Connection Servers
	  Loaded: loaded (/etc/systemd/system/sshd.socket; enabled)
	  Active: active (listening) since Mon, 26 Sep 2011 20:24:31 +0200; 14s ago
	Accepted: 0; Connected: 0
	  CGroup: name=systemd:/system/sshd.socket

This shows that the socket is listening, and so far no connections have been made (Accepted: will show you how many connections have been made in total since the socket was started, Connected: how many connections are currently active.)

Now, let's connect to this from two different hosts, and see which services are now active:

$ systemctl --full | grep ssh
sshd@172.31.0.52:22-172.31.0.4:47779.service  loaded active running       SSH Per-Connection Server
sshd@172.31.0.52:22-172.31.0.54:52985.service loaded active running       SSH Per-Connection Server
sshd.socket                                   loaded active listening     SSH Socket for Per-Connection Servers

As expected, there are now two service instances running, for the two connections, and they are named after the source and destination address of the TCP connection as well as the port numbers. (For AF_UNIX sockets the instance identifier will carry the PID and UID of the connecting client.) This allows us to invidiually introspect or kill specific sshd instances, in case you want to terminate the session of a specific client:

# systemctl kill sshd@172.31.0.52:22-172.31.0.4:47779.service

And that's probably already most of what you need to know for hooking up inetd services with systemd and how to use them afterwards.

In the case of SSH it is probably a good suggestion for most distributions in order to save resources to default to this kind of inetd-style socket activation, but provide a stand-alone unit file to sshd as well which can be enabled optionally. I'll soon file a wishlist bug about this against our SSH package in Fedora.

A few final notes on how xinetd and systemd compare feature-wise, and whether xinetd is fully obsoleted by systemd. The short answer here is that systemd does not provide the full xinetd feature set and that is does not fully obsolete xinetd. The longer answer is a bit more complex: if you look at the multitude of options xinetd provides you'll notice that systemd does not compare. For example, systemd does not come with built-in echo, time, daytime or discard servers, and never will include those. TCPMUX is not supported, and neither are RPC services. However, you will also find that most of these are either irrelevant on today's Internet or became other way out-of-fashion. The vast majority of inetd services do not directly take advantage of these additional features. In fact, none of the xinetd services shipped on Fedora make use of these options. That said, there are a couple of useful features that systemd does not support, for example IP ACL management. However, most administrators will probably agree that firewalls are the better solution for these kinds of problems and on top of that, systemd supports ACL management via tcpwrap for those who indulge in retro technologies like this. On the other hand systemd also provides numerous features xinetd does not provide, starting with the individual control of instances shown above, or the more expressive configurability of the execution context for the instances. I believe that what systemd provides is quite comprehensive, comes with little legacy cruft but should provide you with everything you need. And if there's something systemd does not cover, xinetd will always be there to fill the void as you can easily run it in conjunction with systemd. For the majority of uses systemd should cover what is necessary, and allows you cut down on the required components to build your system from. In a way, systemd brings back the functionality of classic Unix inetd and turns it again into a center piece of a Linux system.

And that's all for now. Thanks for reading this long piece. And now, get going and convert your services over! Even better, do this work in the individual packages upstream or in your distribution!

posted at: 20:46 | path: /projects | permanent link to this entry | 23 comments


Posted by JR at Wed Sep 28 00:56:45 2011
Any inkling as to when Debian will take the plunge and adopt systemd? Or Ubuntu for that matter.

Posted by bob at Wed Sep 28 01:29:17 2011
What would IPv6 addresses look like in the systemctl --full output?

Posted by Lennart at Wed Sep 28 04:32:09 2011
JR: systemd is available in the Debian repositories, but not the default init system. Given how slow Debian decision making usually is this will take much longer before they might switch. And Ubuntu is continuing its support for Upstart.

bob: the same way as IPv4 is formatted, i.e. just inet_ntop() as usual.

Posted by Rostedt at Wed Sep 28 04:47:21 2011
wrt "Accept=yes" instead of "wait = no", I think the better answer would have been "multithread=yes", as from the xinet.conf man page:

If its value is yes,  the  service  is single-threaded; this means that xinetd will start the server and then it will stop handling requests for the service  until  the  server  dies  and that the server software will accept the connection. If the  attribute value  is no, the service is multi-threaded and xinetd will keep handling new  service  requests  and xinetd will  accept the  connection.

Posted by Lennart at Wed Sep 28 05:00:25 2011
Rostedt: "multi-threaded"? I think that is actually really confusing wording as both kinds of services may or may not use threads. I think what the xinetd man page actually wants to say is "multi-instance".

Posted by Rostedt at Wed Sep 28 05:10:14 2011
I agree that "multi-instance" is better. It is also better than "accept". I understand what you meant by using it, but when I first look at it, I think "Accept what?". Or the question can come as, "who accepts?", as Accept=yes may be the application will accept the connections, or perhaps systemd will do the accept on behalf of the application.

Posted by martin langhoff at Wed Sep 28 05:24:10 2011
xinetd has some interesting rate-limiting options (cps, max-load). Is something equivalent available under systemd?

Posted by Jonas at Wed Sep 28 09:14:36 2011
Excellent series, have been trying to follow it as closely as possible. One detail just leave me confused in this installment; with this example of an SSH-daemon combined with systemd, that keeps track of processes through cgroups, would not user processes started over a SSH-connection set up in this manner risk beeing killed by a systemctl kill? I'm thinking about screen sessions and other daemon-like processes beeing "tainted" with the cgroup of the SSH-daemon unit for the pty. If this is the case, will all SSH-users with daemonized processes still show up in a systemctl process listing?

Posted by Jonas at Wed Sep 28 09:20:05 2011
..still show up, even after logout (and the exit of the sshd process). Sorry, still trying to wake up.

Posted by Rudd-O at Wed Sep 28 12:17:52 2011
I preferred the name Rindfleischfischteich mit Bier.

Have a great day, Lennart!

Posted by Philipp Kern at Wed Sep 28 12:22:55 2011
I don't think the problem is Debian's decision making, but Debian's slowness to embrace a technology in a way that nothing breaks.  So far, if you use systemd from experimental, your system boot does not work.

Furthermore so far the strongest argument against it on Debian lists was the reluctance of upstream to support systems other than Linux, which is quite a strong argument given the new support of non-Linux ports.  I don't know if the conclusion was that patches would be merged upstream if provided, as maintaining patches locally forever isn't quite an appealing future.

Posted by Paolo Bonzini at Wed Sep 28 14:53:33 2011
I did read that this is not a bug tracker, :) but I tried setting up sshd this way (mask Fedora's sshd.service, add sshd.socket and ssh@.service to /etc/systemd/service), and I get this in my /var/log/messages when I try to use ssh:

Sep 28 14:46:07 yakj systemd[1]: sshd.socket failed to queue socket startup job: Invalid argument

Sep 28 14:46:07 yakj systemd[1]: Unit sshd.socket entered failed state.

and the socket unit goes from active/listening to failed/failed.

Posted by Lennart at Wed Sep 28 16:45:59 2011
Rostedt: With "Accept=yes" you tell systemd to accept the connection socket. I find that a very easy explanation. That said, I think that the discussion about this is mostly bike shedding, and it's too late now, anyway.

martin: all service activation is rate limited, but its currently not configurable (making it configurable is on the todo list). Watching the load I never found a convincing approach. But maybe we could be convinced by a minimal patch that implements a logic like that.

Jonas: user sessions are placed in cgroups of their own, to avoid these kind of problems.

Posted by Lennart at Wed Sep 28 16:52:19 2011
Philipp: well, there's one big mistake here: the Debian folks appear to believe that it would be feasible to port systemd to other operating system kernels. And that it is clearly not. Debian's real problem really is not that systemd's upstream wouldn't accept the patches, but much more that the porting of systemd to other kernels is simply unrealistic. (And even if it was feasible: git is an amazing tool to maintain your own patched version of an upstream project). But really: the fact that systemd is not portable is the major problem for Debian, it's absolutely not the fact that we wouldn't accept the patches, since that's only the second step, and you wouldn't make it over the first.

Paolo: that looks as if you didn't name the sshd@.service file properly. It is important that the if a socket unit is named foo.socket the per-connection socket file is named foo@.service.

Posted by Jason at Thu Sep 29 03:18:17 2011
Can systemd handle the technique performed by System::Starter on perl's CPAN? -- This has a external program who's only job is to open a socket, and listen for connections, and connect that socket to a child process that actually does the work.  There are signals that you can send to have it fork a new version, and if it's successful, move the 'listening/accept' socket to the new instance, and terminate the old instance when old connections die?

If the goal is to move connection management to one central place (in systemd), how could event-based servers (like nginx) hook into this socket activation system?

Posted by Craig at Thu Sep 29 09:14:56 2011
@Jason

systemd's goal isn't to be a central place for handling all network connections. It just replicates some of the more useful features of inetd.

Typically, sshd spends most of it's time listening and doing very little to justify it's use of memory. I run a number of small virtual servers, sometimes with as low as 64MB memory. By not running sshd when it's not in use, I save about 7MB. That may seem like "nothing" for the typical "throw more hardware at the problem" type people, but there's no reason to waste memory when socket activation provides such an elegant solution. Having more memory free when sshd isn't running (which is most of the time) means it can be used for things like disk caches.

Nginx is quite a different matter. In most instances, sshd probably sees a few hours of usage per week -- and usually only 1 connection at a time. Nginx may well see 1000s or more connection per day. Socket activation in any form just doesn't fit with that model at all. A web server should be run as a service. systemd will still supervise it's process but it will have no part in it's handling of connections.

Posted by Anonymous at Fri Sep 30 00:00:12 2011
@Craig: "Nginx may well see 1000s or more connection per day. Socket activation in any form just doesn't fit with that model at all."

That's not true. You actually do want socket activation with Accept=no to improve your availability figures.

Posted by Jason at Fri Sep 30 03:05:22 2011
@Craig

I can certainly appreciate the value of on-demand services for a system with limited memory. Especially in small VPSs or embedded systems. What's the memory footprint of systemd + it's dependencies (dbus, etc?)  Wouldn't something light weight be better for smaller vps machines? (Perhaps, the original inetd, or ucspi-tcp/ipsvd?)

Posted by Frédéric at Fri Sep 30 13:26:36 2011
@Lennart, @Rostedt, concerning the Accept/Muti-{Threaded|Instance} discussion:
Rostedt is right: "Accept" as a option name is clearly unintelligible if you do not read the documentation (accept what?), and yes, Multi-* is neither intelligible.


As you (Lennart) tell, this option is to "tell systemd to accept the connection socket" or (my addition) "you pass it to the application for it to accept()", so why not use that definition to come to more intelligible name and value for that option? Something like "ConnectionSocket=accept" or "ConnectionSocket=passed" does (in my opinion) describe exactly what happen to that connection socket (systemd accept it or it is passed to the server).

Posted by Dax Kelson at Fri Sep 30 22:21:18 2011
A major improvement in xinetd vs inetd is the rate limiting resource controls it provides.

It is trivial to DOS attack an inetd running box.

On my F15 box the xinetd.conf has this configuration by default:

cps  = 50 10
instances  = 50
per_source  = 10

Other helpful parameters are rlimit_cpu, rlimit_as, nice, and umask.

Does systemd have these useful features?

Posted by Craig at Sat Oct 1 13:51:02 2011
@Anonymous

Response speed is very important to a web server. Almost all web servers are designed to be initialised (read configs etc.) and then stay resident and listening. There's no benefit whatsoever to socket-activation in a process that is serving requests as frequently as most web servers do. It doesn't really save memory either, since it's going to be initialised so rapidly that any page caches that occupy it's freed memory will get constantly flushed before they're even used.

For a good example of why this model doesn't perform/scale very well for frequently accessed services you should just take a look at CGI. It's not really socket-activated but it operates in much the same vain and it's well known to scale terribly for the same reasons.

Posted by Craig at Sat Oct 1 14:35:00 2011
Hmm, this blog ate my comment.

@Jason - it's using 2.4MiB RSS for me - with a full Gnome desktop. I think that's pretty "lightweight".

Posted by Craig at Sat Oct 1 15:16:09 2011
@Anonymous

Oh yeah, I see your point now. I had a brain fart about Accept=no. Who names these things? ;)

Leave a Comment:

Your Name:


Your E-mail (optional):


Comment:


As a protection against comment spam, please type the following number into the field on the right:
Secret Number Image

Please note that this is neither a support forum nor a bug tracker! Support questions or bug reports posted here will be ignored and not responded to!


It should be obvious but in case it isn't: the opinions reflected here are my own. They are not the views of my employer, or Ronald McDonald, or anyone else.

Please note that I take the liberty to delete any comments posted here that I deem inappropriate, off-topic, or insulting. And I excercise this liberty quite agressively. So yes, if you comment here, I might censor you. If you don't want to be censored your are welcome to comment on your own blog instead.


Lennart Poettering <mzoybt (at) 0pointer (dot) net>
Syndicated on Planet GNOME, Planet Fedora, planet.freedesktop.org, Planet Debian Upstream. feed RSS 0.91, RSS 2.0
Archives: 2005, 2006, 2007, 2008, 2009, 2010, 2011

Valid XHTML 1.0 Strict!   Valid CSS!