I wanted to try socket activation with Systemd and Java on my ubuntu server 16.04. My idea is to make my program able to open directly a standard socket number with a user that is not root.
I currently use the iptable NAT rules, but I wanted to put it away after reading the article of liquidat and this one from Pid Eins,
My test environement is pretty simple. I've got the following java program :
package com.test.bindTCP;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class App
{
private static void acceptConnection(ServerSocket serverSocket) {
while (true) {
try (
Socket clientSocket = serverSocket.accept();
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
out.println(inputLine);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main( String[] args )
{
int portNumber = Integer.parseInt(args[0]);
try (
ServerSocket serverSocket = new ServerSocket(portNumber);
) {
acceptConnection(serverSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
I can call it though the following command line :
java -cp target/bindTCP-1.0-SNAPSHOT.jar com.test.bindTCP.App 60606
It echoes all commands sent though tcp, it's able to serve only one client at time. I tested with "nc" and it work properly on socket upper than 1000.
To set my systemd deamon, I wrote the following config files :
cat /lib/systemd/system/bindTCP.socket
[Unit]
Description=bindTCP Java 23
[Socket]
ListenStream=23
cat /lib/systemd/system/bindTCP.service
[Unit]
Description=bindTCP service
Requires=bindTCP.socket
After=syslog.target
After=network.target
[Service]
User=mylogin
Group=mygroup
ExecStart=/usr/lib/jvm/oracle-java8-jdk-amd64/bin/java -cp /home/mylogin/bindTCP-1.0-SNAPSHOT.jar com.test.bindTCP.App 23
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=bindTCP
Restart=always
[Install]
WantedBy=multi-user.target
cat /etc/rsyslog.d/bindTCP.conf
$template bindTCPlog,"%msg%\n"
if $programname == 'bindTCP' then /var/log/bindTCP.log;bindTCPlog
if $programname == 'bindTCP' then stop
Then I reload my systemctl config :
sudo systemctl daemon-reload
I restart rsyslog deamon :
sudo systemctl restart rsyslog
And then, I try to start my own service
sudo systemctl start bindTCP
But it doen't work, in my log (/var/log/bindTCP.log
), I found the following java stacktrace
java.net.BindException: Permission denied (Bind failed)
at java.net.PlainSocketImpl.socketBind(Native Method)
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
at java.net.ServerSocket.bind(ServerSocket.java:375)
at java.net.ServerSocket.<init>(ServerSocket.java:237)
at java.net.ServerSocket.<init>(ServerSocket.java:128)
at com.test.bindTCP.App.main(App.java:38)
Any idea how to setup my service properly ?
EDIT: A solution based on authbind is working. The one proposed with setcap 'cap_net_bind_service=+ep' doesn't change anything. Clearly systemd is able to deal with this issue, so I'm still looking for a solution 100% based on Systemd. I'm thinking is more secure.
Best Answer
You’re not actually using the systemd-provided socket in your daemon. Instead of creating a new
ServerSocket
, useSystem.inheritedChannel()
and check if it’s aServerSocketChannel
. If yes, you inherited a socket from systemd and can start toaccept()
connections on that, otherwise you can still create a newServerSocket
orServerSocketChannel
(e. g. for development purposes). Note that for this to work, you need to setStandardInput=socket
on the service unit: Java expects the inherited channel to be file descriptor 0 (inetd style), but systemd by default adds sockets starting at file descriptor 3.Alternatively, you can add this to your service file (in the
Service
section):This will allow Java to allocate reserved port numbers itself. Using a socket unit is definitely the better choice, though. (If you want to use
AmbientCapabilities
, you’ll have to disable the socket unit, because systemd and your service can’t both bind to the same port. That’s probably why thesetcap
suggestion didn’t work.)Side note: since your unit files are managed by you, the system administrator, and not by the package manager, they belong in
/etc
, not in/lib
(that is,/etc/systemd/system/bindTCP.{service,socket}
).