使用 Systemd、java 和 Ubuntu 16.04 将 1000 以下的 tcp 套接字与非 root 用户绑定

使用 Systemd、java 和 Ubuntu 16.04 将 1000 以下的 tcp 套接字与非 root 用户绑定

我想在我的 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()连接,否则您仍然可以创建一个新的ServerSocketServerSocketChannel(例如,出于开发目的)。请注意,要使其正常工作,您需要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})。

答案2

使用setcap允许 Java 二进制文件本身能力绑定到特权端口,而不需要以以下身份运行root

`sudo setcap 'cap_net_bind_service=+ep' /usr/lib/jvm/oracle-java8-jdk-amd64/bin/java`

相关内容