通过具有套接字激活的 systemd 用户单元的按需 SSH Socks 代理不会按预期重新启动

Ale*_*ies 19 ssh proxy systemd socks socket-activation

为了访问隔离网络,我使用了 -D

为了避免每次将详细信息添加到~/.ssh/config

$ awk '/Host socks-proxy/' RS= ~/.ssh/config
Host socks-proxy
  Hostname pcit
  BatchMode yes
  RequestTTY no
  Compression yes
  DynamicForward localhost:9118
Run Code Online (Sandbox Code Playgroud)

然后我创建了一个服务单元定义文件:

$ cat ~/.config/systemd/user/SocksProxy.service 
[Unit]
Description=SocksProxy Over Bridge Host

[Service]
ExecStart=/usr/bin/ssh -Nk socks-proxy

[Install]
WantedBy=default.target
Run Code Online (Sandbox Code Playgroud)

我让守护进程重新加载新的服务定义,启用新服务,启动它,检查它的状态,并验证它正在侦听:

$ systemctl --user daemon-reload
$ systemctl --user list-unit-files | grep SocksP
SocksProxy.service   disabled

$ systemctl --user enable SocksProxy.service
Created symlink from ~/.config/systemd/user/default.target.wants/SocksProxy.service to ~/.config/systemd/user/SocksProxy.service.

$ systemctl --user start SocksProxy.service 
$ systemctl --user status SocksProxy.service 
? SocksProxy.service - SocksProxy Over Bridge Host
   Loaded: loaded (/home/alex/.config/systemd/user/SocksProxy.service; enabled)
   Active: active (running) since Thu 2017-08-03 10:45:29 CEST; 2s ago
 Main PID: 26490 (ssh)
   CGroup: /user.slice/user-1000.slice/user@1000.service/SocksProxy.service
           ??26490 /usr/bin/ssh -Nk socks-proxy

$ netstat -tnlp | grep 118
tcp     0    0 127.0.0.1:9118        0.0.0.0:*             LISTEN     
tcp6    0    0 ::1:9118              :::*                  LISTEN
Run Code Online (Sandbox Code Playgroud)

这按预期工作。然后,我想通过使用进行按需(重新)生成,避免手动启动服务,或使用永久运行它。那不起作用,我认为(我的版本)无法接收套接字文件描述符。 ssh

我找到了文档 ( 1 , 2 ),以及使用-tool 创建 2 个“包装器”服务、一个“服务”和一个“套​​接字”的示例systemd-socket-proxyd

$ cat ~/.config/systemd/user/SocksProxyHelper.socket 
[Unit]
Description=On Demand Socks proxy into Work

[Socket]
ListenStream=8118
#BindToDevice=lo
#Accept=yes

[Install]
WantedBy=sockets.target

$ cat ~/.config/systemd/user/SocksProxyHelper.service 
[Unit]
Description=On demand Work Socks tunnel
After=network.target SocksProxyHelper.socket
Requires=SocksProxyHelper.socket SocksProxy.service
After=SocksProxy.service

[Service]
#Type=simple
#Accept=false
ExecStart=/lib/systemd/systemd-socket-proxyd 127.0.0.1:9118
TimeoutStopSec=5

[Install]
WantedBy=multi-user.target

$ systemctl --user daemon-reload
Run Code Online (Sandbox Code Playgroud)

似乎有效,直到ssh死亡或被杀。然后它不会在下一次连接尝试时重新生成。

问题:

  1. /usr/bin/ssh 真的不能接受 systemd 传递的套接字吗?还是只有较新的版本?我的是up2date Debian 8.9 中的那个。
  2. 只能以根为单位使用该BindTodevice选项吗?
  3. 为什么我的代理服务在旧隧道关闭后无法在第一个新连接上正确重生?
  4. 这是设置“按需 ssh 袜子代理”的正确方法吗?如果不是,你怎么做?

Vla*_*eev 8

  • /usr/bin/ssh 真的不能接受 systemd 传递的套接字吗?

我认为这并不奇怪,考虑到:

  • OpenSSH 是一个 OpenBSD 项目
  • systemd 只支持 Linux 内核
  • systemd 支持需要明确添加到 OpenSSH,作为可选/构建时依赖项,因此它可能很难销售。

  • 只能以根为单位使用该BindTodevice选项吗?

用户 systemd 实例通常非常孤立,例如无法与主 pid-0 实例通信。像依赖用户单元文件中的系统单元这样的事情是不可能的。

BindToDevice提及的文档:

请注意,设置此参数可能会导致向单元添加额外的依赖项(见上文)。

由于上述限制,我们可以暗示该选项不适用于用户 systemd 实例。


  • 为什么我的代理服务在旧隧道关闭后无法在第一个新连接上正确重生?

据我了解,事件链如下:

  • SocksProxyHelper.socket 开始了。
  • SOCKS 客户端连接到 localhost:8118。
  • systemd 启动SocksProxyHelper.service
  • 作为 的依赖项SocksProxyHelper.service,systemd 也会启动SocksProxy.service
  • systemd-socket-proxyd接受 systemd 套接字,并将其数据转发到 ssh.
  • ssh 死亡或被杀。
  • systemd 通知并SocksProxy.service置于非活动状态,但什么也不做。
  • SocksProxyHelper.service继续运行并接受连接,但无法连接到ssh,因为它不再运行。

解决方法是添加BindsTo=SocksProxy.serviceSocksProxyHelper.service. 引用它的文档(强调):

配置需求依赖,风格与Requires=. 但是,这种依赖类型更强:除了Requires=它声明的效果之外,如果绑定到的单元停止,则该单元也将停止。这意味着绑定到另一个突然进入非活动状态的单位的单位也将被停止。由于不同的原因,单元可能突然、意外地进入非活动状态:服务单元主进程可能会自行选择终止,设备单元的后备设备可能被拔掉,或者挂载单元的挂载点可能在没有参与的情况下被卸载。系统和服务管理器。

当与After=同一单元结合使用时, 的行为BindsTo=甚至更强。在这种情况下,严格绑定的单元必须处于活动状态,该单元也处于活动状态。这不仅是指与另一部突然进入非激活状态的单位,也是一个绑定到另一个单元都不会被跳过由于失败的状况检查(如ConditionPathExists=ConditionPathIsSymbolicLink=... -见下文)将被停止,应该的正在运行。因此,在许多情况下最好与 结合BindsTo=使用After=


  • 这是设置“按需 ssh 袜子代理”的正确方法吗?如果不是,你怎么做?

可能没有“正确的方法”。这种方法有它的优点(一切都是“按需”的)和缺点(依赖于 systemd,第一个连接没有通过,因为 ssh 还没有开始监听)。也许在 autossh 中实现 systemd 套接字激活支持将是一个更好的解决方案。


ank*_*tis 7

为了便于将来参考,我将systemd --user使用守护进程的按需 ssh 隧道的配置文件粘贴在下面systemd-socket-proxyd,其中包含各种增强功能和解释注释:

~/.config/systemd/user/ssh-tunnel-proxy.socket

[Unit]
Description=Socket-activation for SSH-tunnel

[Socket]
ListenStream=1000

[Install]
WantedBy=sockets.target
Run Code Online (Sandbox Code Playgroud)

~/.config/systemd/user/ssh-tunnel-proxy.service

[Unit]
Description=Socket-activation proxy for SSH tunnel

## Stop also when stopped listening for socket-activation.
BindsTo=ssh-tunnel-proxy.socket
After=ssh-tunnel-proxy.socket

## Stop also when ssh-tunnel stops/breaks
#  (otherwise, could not restart).
BindsTo=ssh-tunnel.service
After=ssh-tunnel.service

[Service]
ExecStart=/lib/systemd/systemd-socket-proxyd --exit-idle-time=500s localhost:1001
Run Code Online (Sandbox Code Playgroud)

~/.config/systemd/user/ssh-tunnel.service

[Unit]
Description=Tunnel to SSH server

## Stop-when-idle is controlled by `--exit-idle-time=` in proxy.service
#  (from `man systemd-socket-proxyd`)
StopWhenUnneeded=true

[Service]
Type=simple
## Prefixed with `-` not to mark service as failed on net-fails;
#  will be restarted on-demand by socket-activation.
ExecStart=-/usr/bin/ssh -kaxNT -o ExitOnForwardFailure=yes  hostname_in_ssh_config  -L 1001:localhost:2000
## Delay enough time to allow for ssh-authentication to complete
#  so tunnel has been established before proxy process attaches to it,
#  or else the first SYN request will be lost.
ExecStartPost=/bin/sleep 2
Run Code Online (Sandbox Code Playgroud)

定制

在上面的脚本中,您必须替换以下字符串:

  • 1000- (ssh-tunnel-proxy.socket文件)
    什么(主机:)端口在本地侦听隧道的套接字激活,例如模拟本地 MYSql 端口。
  • 1001-(ssh-tunnel-proxy.servicessh-tunnel.service文件)代理转发到 ssh 服务进程时使用
    什么本地(主机:)端口;
    只需选择一个未使用的端口即可。
  • hostname_in_ssh_config- (ssh-tunnel.service文件)
    要连接的主机组,在您的ssh 配置中引用 (任何 SOCKS 配置都属于此处)。
  • localhost:2000- (ssh-tunnel.service文件)
    ssh 隧道的远程主机:端口端点,例如远程 MYSql 绑定到的位置。
  • x2 延迟时间ssh-tunnel-proxy.servicessh-tunnel.service文件)
    它们在评论中进行了解释。
  • ssh-tunnel-...- (所有单元文件的前缀)
    使其描述您对隧道的需求,例如mysql-tunnel-....
  • SSH -(在Description=所有单元文件的指令中)
    使其描述您对隧道的需求,例如MYSql.

控制隧道的命令

[Unit]
Description=Socket-activation for SSH-tunnel

[Socket]
ListenStream=1000

[Install]
WantedBy=sockets.target
Run Code Online (Sandbox Code Playgroud)