我想在我的 ubuntu 服务器 16.04 上尝试使用 Systemd 和 Java 激活套接字。我的想法是让我的程序能够使用非 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 守护进程,我编写了以下配置文件:
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 守护进程:
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 的解决方案。我认为它更安全。
答案1
您实际上并未在守护进程中使用 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}
)。