Я хотел попробовать активацию сокета с помощью Systemd и Java на моем сервере ubuntu 16.04. Моя идея состоит в том, чтобы моя программа могла напрямую открывать стандартный номер сокета с пользователем, который не является root.
В настоящее время я использую правила iptable NAT, но я хотел убрать их после прочтения статья о ликвидации и этот из Пид Эйнс,
Моя тестовая среда довольно проста. У меня есть следующая Java-программа:
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();
}
}
}
Я могу вызвать это через следующую командную строку: java -cp target/bindTCP-1.0-SNAPSHOT.jar com.test.bindTCP.App 60606
Он повторяет все команды, отправленные через tcp, он может одновременно обслуживать только одного клиента. Я тестировал "nc", и он правильно работает на сокетах выше 1000.
Чтобы установить мой systemd deamon, я написал следующие файлы конфигурации:
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
Затем я перезагружаю конфигурацию systemctl: sudo systemctl daemon-reload
Я перезапускаю rsyslog deamon: sudo systemctl restart rsyslog
А потом я пытаюсь запустить свой собственный сервис sudo systemctl start bindTCP
Но это не работает, в моем журнале (/var/log/bindTCP.log
), Я нашел следующую трассировку стека java
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)
Есть идеи, как правильно настроить мою службу?
РЕДАКТИРОВАТЬ: Решение на основе authbind работает. Предлагаемый с помощью setcap 'cap_net_bind_service = + ep' ничего не меняет. Очевидно, что systemd может справиться с этой проблемой, поэтому я все еще ищу решение, на 100% основанное на Systemd. Думаю, безопаснее.
Фактически вы не используете сокет, предоставленный systemd, в вашем демоне. Вместо создания нового ServerSocket
, используйте System.inheritedChannel()
и проверьте, является ли это ServerSocketChannel
. Если да, вы унаследовали сокет от systemd и можете начать accept()
соединения на этом, иначе вы все еще можете создать новый ServerSocket
или ServerSocketChannel
(например, в целях разработки). Обратите внимание, что для этого вам необходимо установить StandardInput=socket
в служебном модуле: Java ожидает, что унаследованный канал будет дескриптором файла 0 (стиль inetd), но systemd по умолчанию добавляет сокеты, начиная с дескриптора файла 3.
Кроме того, вы можете добавить это в свой служебный файл (в Service
раздел):
AmbientCapabilities=CAP_NET_BIND_SERVICE
Это позволит Java самой выделять зарезервированные номера портов. Тем не менее, использование блока розеток определенно является лучшим выбором. (Если вы хотите использовать AmbientCapabilities
, вам придется отключить модуль сокета, потому что systemd и ваша служба не могут одновременно связываться с одним и тем же портом. Наверное, поэтому setcap
предложение не сработало.)
Боковое примечание: поскольку ваши файлы модулей управляются вами, системным администратором, а не менеджером пакетов, они принадлежат /etc
, не в /lib
(то есть, /etc/systemd/system/bindTCP.{service,socket}
).
Использовать setcap
чтобы позволить самому двоичному файлу java способность для привязки к привилегированным портам, без необходимости работать как root
:
`sudo setcap 'cap_net_bind_service=+ep' /usr/lib/jvm/oracle-java8-jdk-amd64/bin/java`