两台机器的 GNOME RDP 排查:xrdp 占端口 / 0x907 证书 / 白屏闪退 / 凭证丢失

八月 06, 2026 更新: 九月 11, 2026 [桌面与效率] #gnome #RDP #Linux #gdm #freerdp #debian

两台机器的 GNOME RDP 排查记录

两台机器一台 Ubuntu 24.04、一台 Debian 13,GNOME 版本不同(46 vs 48),遇到的问题也不同。

机器清单(2026-08-11 补充):

代号IP主机名系统GNOMEgrd 版本客户端状态
机器 A192.168.5.244—Ubuntu 24.04.446.046.3Windows mstsc正常(双守护进程)
机器 B192.168.7.99xiao99Debian 13 (Trixie)48.748.1Linux Remmina正常(本次修复)
douhao(对照参考)192.168.5.68debian13Debian 13(QEMU/KVM 虚拟机,跑在机器 B 上)4848.1—一直正常

术语澄清

GNOME Remote Desktop 有两个独立的守护进程,都叫 gnome-remote-desktop,但运行级别不同:

系统级(远程登录)用户级(桌面共享)
service 名gnome-remote-desktop.service (system)gnome-remote-desktop.service (user)
管理命令sudo systemctl start/stop ...systemctl --user start/stop ...
配置命令sudo grdctl --system rdp ...grdctl rdp ...
功能提供 GDM 登录界面共享已有桌面会话
客户端看到登录屏 → 新桌面直接进入当前桌面
默认端口33893390(自动避让)

--headless 不是守护进程类型,只是用户级守护进程的一个启动参数(drop-in 文件中的 ExecStart=... --headless)。加上:不依赖物理显示器,创建虚拟屏。不加:直接使用/共享已有的 Wayland 桌面。

机器 B 最终用的是用户级、非 headless——它还是一个守护进程。


机器 A:Ubuntu 24.04.4 + GNOME 46.0

环境: Ubuntu 24.04.4, GNOME 46.0, gnome-remote-desktop 46.3。客户端:Windows 10/11 mstsc。

故障

  1. 设置里「远程登录」开关一开就自动弹回
  2. Windows mstsc 连上后报 0x907「非预期的服务器身份验证证书」

现象

问题 1:开关弹回 — xrdp 占端口

服务状态和日志:

systemctl --user status gnome-remote-desktop.service
grdctl status          # 用户级守护进程(桌面共享)
grdctl --system status # 系统级守护进程(远程登录)
journalctl -u gnome-remote-desktop.service

关键日志(用户每次点开关,系统守护进程都尝试绑 3389):

RDP server started
Failed to bind port 3389 after 10 attempts   ← 重试 10 次失败
RDP server stopped                            ← 服务停止 → 开关弹回

查谁占了 3389:

ss -tlnp | grep 3389
# 0.0.0.0:3389  ← xrdp (root)
journalctl -u xrdp   # listening to port 3389 on 0.0.0.0

根因:xrdp 开机抢先绑定 0.0.0.0:3389。xrdp.service 虽 disabled,但 Requires=xrdp-sesman.service(enabled),开机被拉起,8 秒内抢走 3389。GNOME 远程桌面绑不上,系统级守护进程重试 10 次后放弃,UI 开关弹回。

附:设置页「某些设置已锁定」横幅是 polkit 动作 org.gnome.controlcenter.remote-session-helper 要求管理员认证,点「解锁」输密码即可,不是故障。

问题 2:mstsc 0x907 证书错误

服务端实测(本机 xfreerdp 一切正常):

xfreerdp /v:127.0.0.1:3389 /u:peter /p:xxx /auth-only /cert:ignore
# 认证测试通过
# 抓取服务端证书指纹 — 与配置文件一致,证书与私钥匹配,签名有效,有效期正常

但 mstsc 每次在 TLS 协商阶段中止,服务端日志:

[freerdp_tls_handshake]: BIO_do_handshake failed
CONNECTION_STATE_NEGO - rdp_server_accept_nego() fail

对照实验(同一台 Windows、同一个 mstsc):

监听 3389 的进程证书mstsc 结果
xrdp(之前)xrdp 自签名连过,Windows 缓存了该证书
grd 用户守护进程grd 自签名0x907 硬失败,无确认框
grd 系统守护进程grd 自签名正常连接

已确认:0x907 是 Windows 检测到服务器证书与本地缓存不一致时的硬性终止(防中间人设计,不弹框)。之前 3389 上跑的是 xrdp 的证书,换成 grd 的证书后指纹不同 → 0x907。在**新端口(无缓存)**上连接一次并接受新证书后,3389 也能连了。

推测:mstsc 直连用户守护进程时 NLA 凭据校验路径与预期不一致。grd 46 源码中确实有针对 mstsc 的特殊处理(use_system_credentials = is_client_mstsc && !requested_rdstls)。

机器 A 解决方案

方案 A1:同时开启「远程登录」+「桌面共享」(推荐)

方案 A2:Windows 侧清证书缓存

regedit → HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Servers
删除对应主机名的条目

或换一个从未连过的地址形式(IP ↔ 主机名)连一次,接受新证书后原地址也能连。

机器 A 总结

机器 A 后续(2026-08-12):双守护进程最终形态与 setup.rdp.sh 固化

机器 A 最终配置即上文「方案 A1」的完整落地(日常使用、改密码全部由 script/setup.rdp.sh 一键固化):

端口角色说明
3389系统级 gnome-remote-desktop.service(--system)GDM 登录界面(远程登录),用系统用户密码
3390用户级守护进程(桌面共享,SCREEN_SHARE 模式)自动避让端口,用 RDP 凭证

--handover:登录后接管连接,由 gsd-sharing 按需拉起(GNOME 46 无独立 handover unit,不影响)。

本次踩的新坑(按严重程度排序):

坑 1:系统级凭证写入后,运行中的 daemon 不重载凭证

grdctl --system rdp set-credentials 写完后,3389 认证依然 STATUS_LOGON_FAILURE——运行中的系统 daemon 不会自动重读凭证文件,必须重启才生效(输出里的 tcti:IO failure, using GKeyFile as fallback 正是凭证走 GKeyFile 后端的回退噪音,凭证仍写入成功;原文写「不重载 grd.conf」,文件位置见后文更正):

sudo systemctl restart gnome-remote-desktop.service

前提:重启时没有活跃的 RDP greeter 会话(见坑 5,否则会把物理会话一起踢掉)。

坑 2:SCREEN_SHARE 模式改密码不生效——凭证只读 keyring

上文 Step 1 从源码得出「SCREEN_SHARE 凭证只读 keyring」的结论,机器 A 这次是反着踩的:脚本只改了 credentials.ini,3390 依旧用旧密码。改密码必须写 keyring(物理会话活跃、keyring 已解锁时写入即时生效):

grdctl rdp set-credentials peter '<新密码>'

--headless 模式(凭证读 credentials.ini)可以绕开 keyring 死结,但代价是自建虚拟屏、看不到物理屏内容——共享物理屏场景反而要留在 SCREEN_SHARE 模式并同步 keyring。

坑 3:自检命令在 SSH 环境跑不起来

xfreerdp 是 X11 客户端,无 DISPLAY 直接报 failed to open display。自检用 xvfb-run 包一层:

xvfb-run -a xfreerdp /v:127.0.0.1:3389 /u:peter /p:xxx /auth-only /cert:ignore

坑 4:「解锁后几秒又锁住」实为自动熄屏黑屏(非复锁)

loginctl unlock-session 后轮询 20 秒,LockedHint=no 稳定——会话并未复锁,是显示器自动熄灭(黑屏),SSH 解锁不会唤醒物理屏幕,看起来像"又锁了"。防锁屏判断也要小心:按 headless 服务是否 active 判断,不能按「服务文件是否存在」——Ubuntu 24.04 自带该文件,即使实际跑的是 SCREEN_SHARE 也会被跳过:

gsettings set org.gnome.desktop.screensaver lock-enabled false
gsettings set org.gnome.desktop.screensaver idle-activation-enabled false
gsettings set org.gnome.desktop.session idle-delay 0
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'

坑 5(最重):重启系统级 daemon 会连锁踢掉物理会话

现象:脚本执行 sudo systemctl restart gnome-remote-desktop.service 时,本机物理会话(seat0)直接退出,GDM 自动登录恢复新会话。

根因链(journal 实证):

其他机器连着 3389 登录界面 → GDM 有活跃 RDP greeter 会话(user=gdm, Remote=yes)
  → 重启系统 grd daemon → greeter 全被杀(GdmDisplay: Session never registered, failing)
    → GDM 显示管理连锁出错(on_display_added: assertion 'GDM_IS_REMOTE_DISPLAY (display)' failed)
      → GDM 重新拉起 greeter 到 seat → logind 结束当前物理会话(Session N logged out)

对策:默认不重启系统 daemon——只写配置,等下次 daemon 启动/开机生效;必须立即生效时,先确认所有机器都关掉 3389 登录窗口再重启。检测会话时注意:greeter 的用户名是 Debian-gdm(不是 gdm),按用户名匹配会漏;可靠做法是按 CLASS 列过滤 loginctl list-sessions --no-legend | awk '$6=="greeter"{print $1}'。用户级 daemon(3390)重启无此风险。loginctl terminate-session 手动终止 greeter 也会弄脏 GDM 状态(后续一次 on_display_added 断言失败就能踢会话),尽量让客户端自然断开。

2026-09-11 在 douhao(Debian 13 / GDM 48 / grd 48.1)复现确认:重启系统 daemon 后物理会话(seat0)确实被踢,GDM 在 seat0 拉起新 greeter(当时有一个残留的远程 greeter c2 被杀,触发连锁)。连带后果:

  • 用户级 daemon 随会话结束一起停止(gnome-remote-desktop.service / -handover.service 都 WantedBy=gnome-session.target),3390 不再监听;
  • gnome-keyring daemon 随会话退出,login keyring 重新变锁定(下次控制台登录时 PAM 会尝试用登录密码解锁);
  • tmux server 不受影响(tmux 客户端死、server 存活,重新 attach 即可)。

恢复:到虚拟机控制台在 seat0 登录界面登录,用户级服务会自动拉起;被 disable 的 headless 服务不会自己回来。所以对没有控制台访问的远程机器,重启系统 daemon 前要先准备好控制台(Proxmox / virt-manager)。

坑 6:共享模式默认 mirror-primary,extend 必须显式选择

extend 模式自建虚拟扩展屏,RDP 里看不到物理屏内容——用户连 3390 会误以为"变成 headless 了"。脚本曾做「无物理会话自动回退 extend」的探测,实测在物理会话重启的空档 loginctl 查不到 seat0 会话,把 mirror 悄悄切回 extend。最终:默认 mirror-primary,只有显式 --extend 才配置 extend。

setup.rdp.sh(2026-08-12 版用法)

bash setup.rdp.sh                           # 默认:保留现有模式/密码,不重启任何 daemon
bash setup.rdp.sh --set-password            # 改密码(提示输入新密码)
bash setup.rdp.sh --set-password --restart  # 改密码并重启 daemon 立即生效(先确认无人在 3389 登录界面)
bash setup.rdp.sh --headless                # 强制用户级 headless 模式
bash setup.rdp.sh --screenshare             # 强制 SCREEN_SHARE 模式(默认保留现有模式)
bash setup.rdp.sh --extend                  # 共享模式用虚拟扩展屏(默认 mirror-primary)

设计要点:


机器 B:Debian 13 (Trixie) + GNOME 48

环境: Debian 13 (Trixie), GNOME Shell 48.7, gnome-remote-desktop 48.1。客户端:Linux Remmina。

故障

  1. 连接 3389 白屏 + 服务端 CPU 飙升
  2. 连接 3390 闪退(NTLM 认证失败)
  3. 密码设置后始终不生效

排查过程

先检查当前状态:

systemctl --user status gnome-remote-desktop.service
grdctl status
ss -tlnp | grep -E '3389|3390'

初始状态:用户守护进程以 --headless 模式运行在 3389,用户名和密码均为空。不存在 xrdp 冲突。

Step 1:设置凭证(第一个坑)

grdctl rdp set-credentials peter '<your-password>'
# 无输出,看似成功

grdctl status | grep -E "Username|Password"
# Username: (hidden)
# Password: (hidden)  ← 显示正常

客户端连上后日志:

[ERROR][com.winpr.sspi.NTLM] - [ntlm_read_AuthenticateMessage]: Message Integrity Check (MIC) verification failed!
[ERROR][com.freerdp.core.transport] - [transport_accept_nla]: client authentication failure

无论怎么重设密码都不行。后来发现:

cat ~/.local/share/gnome-remote-desktop/credentials.ini
# [RDP]
# credentials={'username': <'peter'>, 'password': <'旧密码'>}
# ↑ 几个月前的旧密码,grdctl 根本没更新它!

根因:grdctl 优先写 GNOME Keyring,如果 keyring 不可用(SSH 环境无 gnome-keyring-daemon),静默回退到 credentials.ini,但回退路径不工作。直接编辑文件绕过:

vim ~/.local/share/gnome-remote-desktop/credentials.ini
# 改 password 字段为新密码
systemctl --user restart gnome-remote-desktop.service

结论修正(2026-08-11,源码核实):上面「直接编辑 credentials.ini 后重启成功」的原因不是 daemon 读了 credentials.ini——grd 48 源码(grd-credentials-libsecret.c + grd-settings.c)证明 SCREEN_SHARE(用户级无参数)模式的凭证只从 keyring(libsecret)读,lookup 失败直接返回 NULL,不存在 GKeyFile 回退。当时真正生效的是 keyring 里的凭证:8月6日 通过 3389 GDM 登录界面登录时,gdm-password 的 PAM 配置(pam_gnome_keyring.so auth + session auto_start)创建并解锁了 login.keyring(09:57 创建,379 字节),之后 grdctl 把 RDP 凭证写进了这个已解锁的 keyring。所以「vim credentials.ini + 重启」能成功的真实链路是:重启让 daemon 重新从 keyring 读到凭证(恰好那时 keyring 可读),credentials.ini 本身没被读取。credentials.ini 只对 --headless/--system 模式(GKeyFile 后端)有效。

Step 2:开启系统守护进程(引入新问题)

按照机器 A 的经验,推荐双守护进程。启用系统守护进程:

sudo grdctl --system rdp enable
sudo grdctl --system rdp set-credentials peter '<your-password>'

# 证书路径问题:系统守护进程以 gnome-remote-desktop 用户运行,
# 而非 root。正确的证书位置:
sudo mkdir -p /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop/certificates

HOSTNAME=$(hostname)
sudo openssl req -x509 -newkey rsa:4096 -sha384 -days 730 -nodes \
  -keyout /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop/certificates/rdp-tls.key \
  -out /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop/certificates/rdp-tls.crt \
  -subj "/CN=$HOSTNAME" \
  -addext "subjectAltName=DNS:$HOSTNAME" \
  -addext "keyUsage=digitalSignature,keyEncipherment" \
  -addext "extendedKeyUsage=serverAuth" \
  -addext "nsCertType=server"

sudo chown gnome-remote-desktop:gnome-remote-desktop \
  /var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop/certificates/rdp-tls.*

# grd.conf 配置文件在 /etc/gnome-remote-desktop/grd.conf

结果:

Step 3:白屏排查 — GDM Wayland 渲染异常

日志显示系统守护进程成功启动并创建了 GDM greeter 会话:

loginctl list-sessions
# session c3, c4, c5 ...  Type=wayland  Class=greeter  Remote=yes  RemoteHost=0.0.0.0

但客户端白屏。

根因:系统级和用户级使用了不同的 compositor。

两个端口背后的渲染路径完全不同:

3389(系统级)3390(用户级)
运行用户Debian-gdmpeter(实际用户)
compositorGDM 自带的轻量 greeter compositorGNOME Shell 完整 compositor
渲染方式GDM 独立启动 Wayland 进程绘制登录界面PipeWire screencast portal 抓取已有画面
是否 GPU 加速在 RDP 虚拟输出上初始化失败已在物理显示器上正常渲染,只需编码传输

GDM greeter compositor 极其精简,只负责画一个登录框,对 RDP 虚拟输出的支持不完善。而 GNOME Shell 是完整 compositor,在物理显示器上已经借助 GPU 渲染好了,3390 只是通过 PipeWire 把画面编码成 RDP 流发出去——本质上是屏幕录制,不涉及在新输出上重新渲染。

登录后进入的还是 GNOME Shell。 GDM 只是门卫——认证通过后启动正常用户会话。问题纯粹出在门卫自己的渲染上。

能否换掉 GDM? 不行。gnome-remote-desktop 系统级守护进程通过 D-Bus 直接调 GDM 来创建 gdm-launch-environment 会话,这个接口是 GDM 专有的,LightDM 等没有这些 D-Bus API,grd 也不支持其他显示管理器。要解决系统级白屏,只有三条路:

  1. 强制 GDM 用 Xorg(白屏消失,但 Wayland 洁癖不答应):

    sudo sed -i 's/^#\s*WaylandEnable=false/WaylandEnable=false/' /etc/gdm3/custom.conf
    sudo systemctl restart gdm3
    
  2. 等上游修:GDM greeter compositor 对 RDP 虚拟输出的支持是 grd/freerdp 的持续工作

  3. 绕过:不用系统级,只保留用户级独占 3389(当前方案)

Step 3b:白屏真根因(2026-08-11 结论修正,douhao 对照实测)

过时说明:上面「GDM Wayland greeter 渲染异常」的结论只描述了表象。后来用正常机器(douhao,同 Debian 13 + GNOME 48)逐项对照排查,发现白屏的真根因在登录界面渲染链路的前置环节——--handover daemon 根本没被启动,greeter 会话里从未创建虚拟显示器。修正记录如下。

对照实验发现的关键差异:

douhao(正常)dev(白屏)
greeter 会话(Debian-gdm)进程有 gnome-remote-desktop-daemon --handover(gsd-sharing 的直接子进程)没有 --handover 进程
greeter gnome-shell 日志Added virtual monitor Meta-0(创建了虚拟显示器)无此日志
nmcli generalconnected:fullconnected (local only): limited
NM PrimaryConnection 属性非空(ActiveConnection/2)/(空)
gsd-sharing 的 SharingStatusAVAILABLE(3)OFFLINE(0)

根因链(逐环实测确认):

手动添加的默认路由(ip route add ... proto static,无 metric)
  → NetworkManager 不认为是自己管理的路由(PrimaryConnection=/ 空,ActiveConnection.Default=false)
    → gsd-sharing 的 nm_client_get_primary_connection() 返回 NULL
      → sharing_status = GSD_SHARING_STATUS_OFFLINE
        → start_assigned_service() 第一行就 return(源码 gsd-sharing-manager.c)
          → --handover daemon 永不启动
            → greeter 会话没有虚拟显示器(缺 "Added virtual monitor" 日志)
              → 登录界面白屏 + CPU 飙升(mutter 在虚拟输出上空转渲染)

gsd-sharing 的启动逻辑(gnome-settings-daemon 48 源码 plugins/sharing/gsd-sharing-manager.c):

修复(dev 上执行,网络是 bridge 连接 br0):

# 重新激活网络连接,让 NM 接管默认路由(NM 配置里已有 ipv4.gateway)
sudo nmcli con up br0

# 验证(三处全部转好才说明修复到位):
nmcli -t -f STATE,CONNECTIVITY general
# connected:full                                  ← 原来 limited

dbus-send --system --print-reply --dest=org.freedesktop.NetworkManager \
  /org/freedesktop/NetworkManager org.freedesktop.DBus.Properties.Get \
  string:org.freedesktop.NetworkManager string:PrimaryConnection
# variant object path "/org/freedesktop/NetworkManager/ActiveConnection/5"   ← 原来 "/"

dbus-send --session --print-reply --dest=org.gnome.SettingsDaemon.Sharing \
  /org/gnome/SettingsDaemon/Sharing org.freedesktop.DBus.Properties.Get \
  string:org.gnome.SettingsDaemon.Sharing string:SharingStatus
# variant uint32 3 (AVAILABLE)                    ← 原来 0 (OFFLINE)

修复后 3389 直接显示 GDM 登录界面,登录 → handover → 桌面完整链路正常,不需要停系统级守护进程或强制 GDM Xorg。

为何之前误判:白屏期间 greeter 的 EGL/GPU 初始化日志全部正常(Obtained a high priority EGL context),且修好 /dev/dri 的 ACL 后仍白屏,所以最初归因到「greeter 渲染异常」。而 Failed to handle service change: Process org.freedesktop.systemd1 exited with status 1 这类报错是 rygel 等 configurable services 走 systemd 激活失败的噪音(org.freedesktop.systemd1.service 的 Exec=/bin/false 是系统设计如此),与白屏无关,别被误导。

排查口诀:3389 白屏 → ① nmcli general 看是不是 limited/local only → 是则 nmcli con up <连接> 重新激活;② 看 greeter 会话里有没有 gnome-remote-desktop-daemon --handover 进程和 Added virtual monitor 日志;③ 查 gsd-sharing 的 SharingStatus 是否为 0。

Step 3c:网络必须由 NetworkManager 管理(2026-08-11 补充)

结论:3389 远程登录(GDM greeter 路径)依赖 NetworkManager,用 systemd-networkd 管理网络不行。

这是 Step 3b 依赖链的另一种触发方式(同一根因,不同起因):

本质都是 NM 不认为自己有主连接(详细依赖链见 Step 3b)。

绕开路径:

  1. 网络交给 NM 管理(推荐):nmcli con up <连接> 让 NM 接管,PrimaryConnection 非空即可
  2. 不用 3389 登录界面:用户级独占 3389 直连桌面(本页机器 B 最终方案)不依赖 NM——用户级 daemon 由 systemd 管,不经过 gsd-sharing。代价是没有 GDM 登录界面,不能走 handover 登录接管。

副作用提醒:nmcli con up <网桥> 重新激活网桥时,会把挂在网桥上的虚拟机 tap 网卡(如 vnet0)踢出网桥导致虚拟机失联。修复:

sudo ip link set vnet0 master br0   # 重新挂回
ls /sys/class/net/br0/brif/         # 确认成员里有 vnet0

Step 3d:3390 直连 Unknown monitor — 用 extend 模式(2026-08-11 补充)

现象:直连 3390(用户级桌面共享)时,认证通过、通道协商成功,但随后被服务器断开:

[ERROR][com.freerdp.core.peer] - ERRINFO_RPC_INITIATED_DISCONNECT [0x00010001]
gnome-remote-de: Failed to record monitor: GDBus.Error:org.freedesktop.DBus.Error.Failed: Unknown monitor

根因:用户级 daemon 默认共享模式是 mirror-primary——镜像现有桌面的主物理显示器。当物理会话被注销(seat0 上只剩 GDM 登录界面)、目标会话是无 seat 的 headless 会话(gnome-shell 日志 No seat assigned, running headlessly)时,没有物理屏可镜像 → Unknown monitor → 拒绝连接。

修复:切换到 extend 模式(创建虚拟显示器,不依赖物理屏):

gsettings set org.gnome.desktop.remote-desktop.rdp screen-share-mode extend
systemctl --user restart gnome-remote-desktop.service

结论:extend 模式不需要物理会话登录。实测在物理会话注销、只剩 RDP 登录的无 seat 会话时,3390 直连成功、桌面正常显示。两种模式对比:

模式画面来源需要物理屏?适用场景
mirror-primary(默认)镜像会话主物理显示器是有物理登录会话,想看物理屏内容
extendmutter 创建虚拟显示器(如 Meta-0)否无头/远程登录会话,或不想暴露物理屏内容

注意:extend 模式下 RDP 会话用的是独立的虚拟显示器,看不到物理屏上的实际内容;mirror-primary 才是"看物理屏"。

补充(2026-08-11 晚):extend 设置只对 SCREEN_SHARE(无参数)模式有意义。改用 --headless 模式后 daemon 永远自建虚拟显示器,不存在 Unknown monitor 问题——用 headless 模式可以一劳永逸绕开本坑(详见下文「grd 的 4 种运行模式」)。且实测发现 dconf 的 extend 设置可能被 GUI 设置面板等重置回 mirror-primary(dev 上 20:17 被覆盖),headless 模式不受此影响。

Step 3e:物理会话 vs 远程会话 — SSH 无法创建物理会话(2026-08-11 补充)

问题:物理会话注销后,能否通过 SSH 重新创建 seat0 物理会话?

结论:不能。 loginctl unlock-session 只能解锁已存在的会话,没有创建会话的能力。物理会话(Seat=seat0 的 user 会话)只能由 GDM 在 seat0 上登录产生,路径只有三条:

  1. 物理机前键盘登录(最直接)
  2. GDM 自动登录(模拟物理登录):
    # /etc/gdm3/custom.conf
    [daemon]
    AutomaticLoginEnable = true
    AutomaticLogin = peter
    
    然后 sudo systemctl restart gdm3 生效
  3. 3389 RDP 登录:创建的是远程会话(Remote=yes、无 seat),不是物理会话——但 RDP 用起来一样

重启 gdm3 的副作用(执行前想清楚):

会话选择判断标准(loginctl list-sessions):

特征会话类型能否 unlock/lock
Seat=seat0 + Class=user物理会话(解锁目标)✅ unlock-session / lock-session
Class=user + Remote=yes + Type=waylandRDP 远程桌面会话❌ 从不锁定,无需操作
Class=greeterGDM 登录界面❌ 别碰
Class=managersystemd 管理❌ 别碰

实测案例:dev 的物理会话 803 注销后,seat0 只剩 greeter c27。此时 3390 用 extend 模式共享远程会话 4259 正常工作——物理会话对 RDP 使用不是必需的。

认证通过后如何切到用户级 daemon?重定向 + fd 传递(2026-09-11 源码核实,更正)

原文曾写「RDP Server Redirection,不是 fd 传递」「客户端重建连接到用户守护进程(3390)」,这个结论是错的。grd 48.1 源码(grd-daemon-system.c / grd-daemon-handover.c)显示实际链路是:

  1. 系统守护进程向客户端发送 RDP_SERVER_REDIRECTION 包(含 routing token、用户名、密码、证书,不含目标地址)
  2. 客户端断开后重连回同一地址的 3389,携带 routing token;系统守护进程的 RDP server 以 incoming-redirected-connection 信号识别 token(on_incoming_redirected_connection),暂存新 socket 并发 TakeClientReady
  3. 用户级 handover daemon 调 TakeClient → 系统守护进程把该 socket 的 fd 通过 D-Bus(GUnixFDList,即 SCM_RIGHTS)传给 handover daemon(on_handle_take_client)→ handover daemon 用 grd_rdp_server_notify_incoming 把连接注入自己的 RDP server
  4. handover daemon 不监听任何端口(所以 ss 里看不到它),只接收注入的连接

也就是说:既有应用层重定向(让客户端重连并携带 token),也有内核级 fd 传递(交接 socket)——「不是 fd 传递」的说法不成立。日志中仍能看到 Sending server redirection。

Step 4:闪退排查 — NTLM MIC 不是 mstsc 独有的

机器 A 的推测是「mstsc 特有」。但机器 B 用的 Remmina(Linux)同样出现此问题:

[ERROR][com.winpr.sspi.NTLM] - [ntlm_read_AuthenticateMessage]: Message Integrity Check (MIC) verification failed!
[WARN][com.winpr.sspi] - [winpr_AcceptSecurityContext]: AcceptSecurityContext status SEC_E_MESSAGE_ALTERED [0x8009030F]
[ERROR][com.freerdp.core.transport] - [transport_accept_nla]: client authentication failure

结论修正:这是 grd 46 用户守护进程与 NLA (CredSSP) 的普遍不兼容,不限于 mstsc。需要通过系统守护进程的认证路径才能绕过——但要先解决白屏。最终选择了只用用户级独占 3389。

Step 5:Session creation inhibited(第二个锁屏坑)

凭证修复后 NTLM 通过了!但出现新错误:

gnome-remote-de: Failed to start remote desktop session:
  GDBus.Error:org.freedesktop.DBus.Error.Failed: Session creation inhibited
[ERROR][com.freerdp.core.peer] - ERRINFO_RPC_INITIATED_DISCONNECT [0x00010001]

检查会话状态:

loginctl show-session 803 | grep LockedHint
# LockedHint=yes

dbus-send --session --dest=org.gnome.ScreenSaver --print-reply \
  /org/gnome/ScreenSaver org.gnome.ScreenSaver.GetActive
# boolean true

根因:物理会话锁屏了(机器闲置一周多)。GNOME Remote Desktop 在会话锁定时拒绝创建远程桌面会话。

意外发现:SSH 远程登录可以直接解锁物理会话(同用户间 polkit 信任):

# 找到物理会话的 ID
loginctl list-sessions
# SESSION  UID USER       SEAT  LEADER CLASS   TTY  IDLE SINCE
#     803 1000 peter      seat0 38320  user    tty2 yes  1 week ago
#       3 1000 peter      -     2373   user    -    no   -
#       4 1000 peter      -     2393   manager -    no   -
#     c3  110 Debian-gdm -     158722 greeter -    no   -

# 物理会话的特征:有 SEAT(seat0)、有 TTY(tty2)、CLASS=user
# SSH 等远程会话:SEAT 为 -、CLASS=user、TTY 为 -
# GDM greeter:CLASS=greeter

# 精准定位当前登录桌面的会话 ID:
loginctl list-sessions --no-legend | awk '$4 == "seat0" {print $1}'
# → 803

# 解锁
loginctl unlock-session 803
# LockedHint=no

如何区分会话 ID:

无需物理接触。防止复锁:

# 禁止锁屏(注销/锁屏后 RDP 无法进入)
gsettings set org.gnome.desktop.screensaver lock-enabled false
# 禁止屏保空闲时自动激活(避免再次锁屏)
gsettings set org.gnome.desktop.screensaver idle-activation-enabled false
# 屏保/锁屏的空闲触发时间设为 0(永不超时)
gsettings set org.gnome.desktop.session idle-delay 0
# 接电源时永不自挂起
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing'
# 用电池时也永不自挂起
gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-type 'nothing'

安全警告:这些设置会让物理控制台一直处于已登录状态。RDP 断开后,任何能接触到物理机器的人都能直接操作桌面。

也可以写一个 systemd 服务,在 RDP 会话断开时自动锁定(见下文「自动化」)。用系统级(远程登录/GDM greeter)天然没有这个问题——注销后回到登录界面。只用用户级(桌面共享)才有这个风险。

Step 6:headless 模式取舍

机器 B 初始以 --headless 模式运行(有 drop-in 配置文件):

# ~/.config/systemd/user/gnome-remote-desktop.service.d/headless.conf
[Service]
Type=simple
ExecStart=
ExecStart=/usr/libexec/gnome-remote-desktop-daemon --headless

在 headless 模式下仍然出现 Session creation inhibited。去掉 --headless 后,守护进程直接共享已有 Wayland 会话,配合解锁,连接成功。

机器 B 最终方案

方案演进(2026-08-11):下文「用户级非 headless 独占 3389」是当时(8月5-6日)的临时方案——它依赖 keyring 里有凭证 + 物理会话可镜像,两个条件都脆弱。后来通过源码确认 SCREEN_SHARE 模式凭证只读 keyring(credentials.ini 无效),并实测 --headless 模式(凭证走 credentials.ini + 自建虚拟屏)是远程无 GUI 环境下最可靠的用户级方案。新机器推荐直接上「4 种运行模式」章节的 douhao 修复后组合(--system + --headless + --handover),本文方案仅作历史记录。

停掉系统守护进程,用户守护进程非 headless 独占 3389:

# 停系统守护进程
sudo systemctl stop gnome-remote-desktop.service
sudo systemctl disable gnome-remote-desktop.service

# 去 headless
rm ~/.config/systemd/user/gnome-remote-desktop.service.d/headless.conf
systemctl --user daemon-reload

# 确保凭证正确
vim ~/.local/share/gnome-remote-desktop/credentials.ini

# 重启
systemctl --user restart gnome-remote-desktop.service

客户端直连 3389,直接看到已有桌面,不经过 GDM 登录界面。

日常使用流程:SSH 解锁 → RDP 使用 → 主动锁屏

用用户级桌面共享时,每次使用 RDP 的完整流程:

1. SSH 登录,检查会话状态

# 查看所有会话
loginctl list-sessions

# 查看物理会话的锁定和空闲状态
loginctl show-session <ID> | grep -E "LockedHint|IdleHint|State|Type"
# LockedHint=yes   → 已锁定,需要解锁
# IdleHint=yes     → 空闲中(可能锁屏了)
# State=active     → 会话在运行

# 一行命令判断是否需要解锁
loginctl show-session $(loginctl list-sessions --no-legend | awk '$4=="seat0"{print $1}') \
  2>/dev/null | grep "LockedHint=yes" && echo "需要解锁" || echo "已解锁"

会话状态组合:

LockedHintIdleHint含义动作
yesyes已锁屏且空闲需要解锁
yesno已手动锁定需要解锁
noyes已解锁但空闲(屏保无密码)可直接连
nono已解锁且活跃可直接连

2. 解锁物理会话

SESSION_ID=$(loginctl list-sessions --no-legend | awk '$4=="seat0"{print $1}')
loginctl unlock-session "$SESSION_ID"

3. RDP 连接

用 Remmina / mstsc 连 主机:3389。

4. 使用完毕后,主动锁屏(重要!)

loginctl lock-session "$SESSION_ID"

不锁的话物理控制台会一直处于已登录状态,任何能碰到机器的人都能操作。

自动化:RDP 断开时自动锁屏

监听用户守护进程日志中的断连事件,触发自动锁定:

# 方式一:systemd path unit 监听 + shell 脚本
# ~/.local/bin/rdp-lock-on-disconnect.sh
#!/bin/bash
SESSION_ID=$(loginctl list-sessions --no-legend | awk '$4=="seat0"{print $1}')
journalctl --user -u gnome-remote-desktop.service -f --no-pager | while read line; do
    if echo "$line" | grep -q "Network or intentional disconnect"; then
        loginctl lock-session "$SESSION_ID"
    fi
done
# ~/.config/systemd/user/rdp-auto-lock.service
[Unit]
Description=Auto-lock session on RDP disconnect
After=gnome-remote-desktop.service

[Service]
ExecStart=%h/.local/bin/rdp-lock-on-disconnect.sh
Restart=on-failure

[Install]
WantedBy=default.target
chmod +x ~/.local/bin/rdp-lock-on-disconnect.sh
systemctl --user daemon-reload
systemctl --user enable --now rdp-auto-lock.service

5. 可选:事后恢复锁屏策略

如果之前为了 RDP 禁用了自动锁屏,可以在断开后恢复:

gsettings set org.gnome.desktop.screensaver lock-enabled true

或者写进上述自动脚本,RDP 断开时一起执行。


远程登录架构与会话机制(GNOME 45+ / GDM,douhao 实测)

grd 的 4 种运行模式(2026-08-11 汇总,源码 grd-daemon.c 确认)

gnome-remote-desktop 只有一个二进制 gnome-remote-desktop-daemon,4 种运行模式由启动参数决定,互斥(count_trues(3, headless, system, handover) > 1 时直接报 Invalid option: More than one runtime mode specified 退出):

参数运行模式daemon 类角色端口凭证来源
无参数SCREEN_SHARE用户级 grd_daemon_user_new共享现有桌面(录制已有画面)3389(可避让)只读 keyring
--headlessHEADLESS用户级 grd_daemon_user_new无头共享(自建虚拟显示器,不依赖物理屏)3389(可避让)读 credentials.ini
--systemSYSTEM系统级 grd_daemon_system_newGDM 远程登录(登录界面 → handover)3389设置读 grd.conf;凭证走 GKeyFile credentials.ini(见下方更正)
--handoverHANDOVER用户级 grd_daemon_handover_new登录接管(等 --system 移交连接)不监听one-time(system 传)

凭证位置更正(2026-09-11,grd 48.1 源码核实):--system 的凭证不在 grd.conf,而是和 --headless 一样走 GKeyFile 后端(无 TPM 时),位于运行用户 gnome-remote-desktop 的数据目录:/var/lib/gnome-remote-desktop/.local/share/gnome-remote-desktop/credentials.ini(grd-settings.c 的 create_credentials() 里 SYSTEM 与 HEADLESS 共用 create_headless_credentials())。grd.conf 只存 enabled/tls-cert 等设置。「写入后运行中的 daemon 不重载、需重启」的行为不变(文件在内存中缓存)。

对应 4 个 systemd unit(归属固定,不能混用):

unit归属运行用户ExecStart
gnome-remote-desktop.servicesystem(sudo systemctl)gnome-remote-desktop 用户--system
gnome-remote-desktop.serviceuser(systemctl --user)当前用户(无参数)SCREEN_SHARE
gnome-remote-desktop-headless.serviceuser当前用户--headless
gnome-remote-desktop-handover.serviceuser当前用户--handover

常见疑问(已实测):

"远程登录"和"桌面共享"不是并列的两种服务,而是流程的两段:

远程登录(--system, 3389)→ 看到 GDM 登录界面 → 输密码 → handover(--handover)→ 进入你的桌面
桌面共享(无参数 或 --headless, 3390)→ 直接看到你的桌面画面(不需要经过登录界面)

本博客各次配置实际用的模式对照:

配置场景--system无参数--headless--handover说明
机器 A 方案 A1(推荐)✅ 3389✅ 3390—✅标准双守护进程
机器 B 初始(headless 试错)——✅ 3389—遇到 Session inhibited
机器 B 最终方案(用户级独占)—✅ 3389——共享 803 物理会话
douhao(正常参照)✅ 3389✅ 3390—✅一直正常
dev 当前(修复后)✅ 3389✅ 3390—✅白屏修复后
douhao 修复后(本次)✅ 3389—✅ 3390✅绕开 keyring 死结
douhao 现状(2026-09-11)✅ 3389—✅ 3390 SCREEN_SHARE✅改回共享物理桌面(mirror-primary),见下方更新

为什么 douhao 修复后改用 --headless:SCREEN_SHARE 模式的凭证只从 keyring 读,而 keyring 在无显示环境(SSH)锁定/创建时必然挂起——3390 永远 Credentials are not set。--headless 模式读 credentials.ini,完全不碰 keyring,是远程无 GUI 环境下唯一可靠的 3390 路径(实测 3390 认证通过、画面正常)。

2026-09-11 更新(douhao 现状):douhao 的 3390 已改回 SCREEN_SHARE(mirror-primary,共享物理桌面)——需求从「无头共享」变成「看到物理屏」。代价是回到 keyring 路径,但并非不可用:物理会话活跃且 login keyring 已解锁时,SSH 里带上 XDG_RUNTIME_DIR/DBUS_SESSION_BUS_ADDRESS(GDBus 会自动回退到 $XDG_RUNTIME_DIR/bus)执行 grdctl rdp set-credentials 可正常写入,且写入即时生效(daemon 每次连接现查 keyring,无需重启);headless 读 credentials.ini 才需要重启 daemon。注意物理会话被踢后 keyring 会随会话内 daemon 退出而重新锁定,需登录后解锁(keyring 密码=创建时的登录密码)。另外:screen-share-mode 是 GSettings 绑定的活配置,改完对新连接立即生效,同样不需要重启 daemon。

双守护进程 + handover

douhao 上系统级与用户级守护进程并存,各管一段:

进程角色
gnome-remote-desktop-daemon --system(系统级)管理 GDM 登录界面(greeter) 的 RDP——"远程登录"端
gnome-remote-desktop-daemon + --handover(用户级)登录后接管连接,共享用户桌面

完整链路:RDP 连 3389 → 看到 GDM 登录界面 → 输密码 → 登录瞬间 handover 把连接交给用户级 daemon → 无缝进入真实桌面。

会话列表解读(loginctl,实测会话)

c8   Debian-gdm greeter active  wayland  local   seat0 tty1   ← 本地登录界面,永远挂着
c9   Debian-gdm greeter active  wayland  remote  -      -     ← 第一次 RDP 连接的 greeter
c10  Debian-gdm greeter active  wayland  remote  -      -     ← 第二次 RDP 连接的 greeter
3670 peter      user    active  wayland  remote  -      -     ← peter 已登录的 GUI 会话

渲染链路:服务器端 Wayland 渲染

远程登录界面不是 X11 转发,而是服务器端完整渲染:

RDP 客户端 ←── RDP(H.264 帧编码 + 输入事件) ──→ grd 系统守护进程
                                                        │
                      GDM 按需拉起无 seat 的 greeter 会话(c9/c10)
                                                        │
                                 里面跑 Mutter(Wayland 合成器),渲染进虚拟输出
                                                        │
                    grd 用 Mutter ScreenCast API 捕获帧 → 编码 → 推给客户端
                    客户端鼠标键盘 → RemoteDesktop API 反向注入合成器

登录后同理:handover 后 grd 用户守护进程捕获用户会话合成器的画面。

虚拟输出的底层技术(douhao 实测)

douhao 的 card0-Virtual-1 不是 VKMS,而是 virtio-gpu——douhao 本身是 QEMU/KVM 虚拟机:

/sys/class/drm/card0/device/uevent: DRIVER=virtio-pci, PCI_ID=1AF4:1050(virtio-gpu)
lsmod: virtio_gpu
技术本质场景
VKMS(drm/vkms)纯内核虚拟 KMS,无 PCI 设备无头服务器测试 KMS 路径
virtio-gpu(douhao 用)半虚拟化 GPU,宿主提供真实帧缓冲,自带虚拟 EDID虚拟机标准虚拟显卡
Mutter 虚拟显示器合成器内部软件虚拟输出,不进内核 DRMgnome-remote-desktop 无头会话

640x480 之谜:virtio-gpu 的 Virtual-1 连接器自带 EDID,支持 5120x2160 等大模式——但 RDP greeter 画面可能只有 640x480。原因是远程 greeter 的合成器走 Mutter 内置虚拟显示器(软件路径),其尺寸由 RDP 客户端连接时请求的桌面尺寸决定(grd 系统守护进程默认 EXTEND 模式,用客户端 Client Core Data 的 DesktopWidth/Height 建虚拟屏;Mutter 按 PipeWire 协商尺寸创建/调整虚拟显示器,见 meta-screen-cast-virtual-stream-src.c 的 create_virtual_monitor/ensure_virtual_monitor),不经过 KMS 连接器的模式列表。更正(2026-09-11):原文写「Mutter 默认 640x480」不准确——实测客户端请求 /size:1000x700 时服务端渲染内容就是 1000x700,尺寸由客户端请求驱动;greeter 显示 640x480 是因为客户端请求了 640x480(把客户端分辨率调大即可)。两条渲染路径不冲突:连接器支持大分辨率,不代表 greeter 会用它。


多显示器与显示器热插拔(2026-08-18 补充)

共享哪个屏:mirror-primary 只镜像主显示器

客户端侧选项(Remmina 1.4.43 实测)

Remmina 没有 mstsc 那种字面叫"使用所有显示器"的选项,等价物在 Basic(基本)标签页下半段("智能卡 PIN"一组之后、"分辨率"之前,需向下滚动):

选项含义
Enable multi monitor每个本地显示器映射为一个独立 RDP monitor(≈ mstsc 的"使用所有显示器",全屏生效)
Force multi monitor when toggled强制多显示器模式
Span screen over multiple monitors多屏拼接成一个虚拟大桌面(单一逻辑 monitor,窗口可跨屏拖动)
List monitor IDs逗号分隔指定共享哪些显示器(如 0,1),可精确排除某个屏

服务端(grd 0.4.x / GNOME 46+)侧本身支持多显示器:每个物理显示器独立 ScreenCast 流 → RDP 多 monitor。两端都要开启才生效。

显示器移除(热插拔)对共享的影响


踩坑总表

问题现象机器根因解法
xrdp 占端口开关弹回Axrdp-sesman 开机抢先绑 3389systemctl stop/disable xrdp xrdp-sesman
0x907 证书mstsc 硬拒绝AWindows 缓存了旧证书指纹清注册表 / 换地址重连
GDM 白屏 + CPU 飙升(greeter 无虚拟显示器)3389 连上白屏、无渲染错误B手动默认路由 → NM PrimaryConnection 空 → gsd-sharing 判 OFFLINE → --handover 不启动 → greeter 无虚拟显示器nmcli con up <连接> 让 NM 接管默认路由(见 Step 3b);systemd-networkd 同理(见 Step 3c)
NTLM MIC 失败Remmina 闪退B用户守护进程 NLA 普遍不兼容用系统守护进程 / 3389 登录路径
Session inhibited认证通过后断开B物理会话锁屏loginctl unlock-session
凭证不持久化NTLM 永远失败Bgrdctl 写 keyring 失败(SSH 无 keyring daemon),回退 credentials.ini 的写入路径不工作通过 3389 GDM 登录一次解锁 keyring 后写凭证,或改用 --headless 模式(凭证走 credentials.ini)
SSH 可远程解锁—B同用户会话间 polkit 信任loginctl unlock-session <id>
3390 Unknown monitor认证通过后 RPC 断开Bmirror-primary 模式无物理屏可镜像(物理会话注销后)短期:screen-share-mode extend;根治:改用 --headless 模式(永远自建虚拟屏,见 Step 3d)
3390 凭证死结认证被拒 Credentials are not set,grdctl 写 keyring 挂起B/douhaoSCREEN_SHARE 模式凭证只读 keyring;keyring 锁定/无显示环境无法写入改用 --headless 模式(读 credentials.ini,不碰 keyring)
keyring 解锁弹窗密码错RDP 登录后弹解锁框,输登录密码报错B8月6日 排查期间重建的 login.keyring 密码与当前登录密码不一致(GDM 登录创建的 keyring 密码=当时登录密码,改密码不同步)备份后删除 login.keyring,下次 GUI 登录以当前密码重建(丢其中存的少量密码)
系统级凭证不重载3389 改密码后认证仍 STATUS_LOGON_FAILUREA运行中的系统 daemon 不重读 grd.conf写凭证后 systemctl restart gnome-remote-desktop.service(先确认无活跃 gdm 会话,见下行)
重启系统 daemon 踢物理会话脚本跑着跑着物理桌面退出、自动登录恢复A活跃 RDP greeter 被杀 → GDM on_display_added 断言失败 → seat 会话被踢默认不重启系统 daemon(--restart 显式才重启,先清空 user=gdm 会话)
SCREEN_SHARE 改密码不生效3390 还是旧密码ASCREEN_SHARE 凭证只读 keyring,credentials.ini 无效grdctl rdp set-credentials 写 keyring(物理会话活跃时即时生效)
共享模式被悄悄切回 extend3390 连上看不到物理屏A「无物理会话自动回退 extend」在会话切换空档误判默认 mirror-primary,只有显式 --extend 才配置