X-Window结构分析与应用

时间:2022-09-07 10:15:04

X-Window结构分析与应用

简介

*nix系统的图形交互使用的是x-window系统. 基于C/S架构,即可完全运行在本地,也可以让本端运行的GUI应用在远端上进行交互(在远端上显示GUI 和 接收远端上的键盘鼠标输入).

结构

X-Window结构分析与应用

组件

X-Client

  • X-Client就是GUI应用程序(如:xclock,xterm,firefox,…)
  • Linux下GUI应用程序大多是通过库Xlib(libX11)与X-Server通信
  • X-Client自身不直接输出图形到屏幕或直接读取键盘输入,这些与外设的通信都由X-Server来处理

GUI应用程序通过加载Xlib,连接到X-Server建立通信通道,最终通过这通道将显示需求发送到X-Server上 或 从这通道上接收用户的键盘鼠标输入, 从而实现GUI交互

每个终端可以配置使用指定的X-Server, 由终端当前的环境变量’DISPLAY’指定.DISPLAY字符串的格式如下:

[host]:[displayer-id].[screen-id]

意思为: 哪个主机上的哪个显示器上的哪个屏幕

其中[host]和[screen-id]可省略.host省略意为本机,screen-id省略为默认的屏幕.如:

# 本终端环境使用本地ID为0的显示器的默认屏幕作为X-Server
export DISPLAY=:0
# 使用主机(10.0.66.238)上ID为1的显示器的ID为0的屏幕
export DISPLAY=10.0.66.238:1.0

X-Server

  • 顾名思义是X-Client们的服务端
  • 负责与输入输出设备通信
  • Linux上通常使用的实现为 Xorg
  • MacOS上可使用 XQuartz
  • Windows上可使用 Xmanager 或 Xming

负责将X-Client请求显示的图形显示到屏幕上 和 将键盘鼠标的输入事件发送到相应的X-Client上.

Linux下的X-Server

  1. 已安装桌面环境

以ubuntu17为例,若安装了桌面环境,即GNOME,则已经完全具备X-Server的服务能力(基于Xorg-server). 这完整的桌面环境不止X-Server,还包括一系列的X-Client应用程序(任务栏,文件夹管理器…)和X-Window-Manager等等.

但默认的情况下,该X-Server只提供给本地的X-Client使用,没开启TCP的对外服务渠道,只开启了基于unix-socket的进程间通信的服务渠道.

开启TCP方式则需要修改配置文件:/etc/gdm3/custom.conf

[security]
DisallowTCP=false

开放给远端使用时可能会遇到X-Auth验证失败 或 NAT环境下不可用的情况,在后面的小节进行说明

下图为GNOME桌面环境下, 终端默认使用了当前GNOME桌面所在的X-Server(Displayer1), 当执行X-Client(GUI应用xclock)时就把他显示在当前GNOME桌面里了

X-Window结构分析与应用

当然也可以手动再开启一个纯净的X-Server,如接下来这小节所示

  1. 未安装桌面环境,使用最纯净的X-Server(Xorg)

先确认已安装X-Server:

rock@ubuntu17:~$ sudo apt-get xorg

手动开启一个新的X-Server(Xorg为例):

## ':1'表示该X-Server标识为ID为2的Displayer
## Displayer1显示在tty5
## '-listen tcp'显式表示要使用tcp方式
## '-retro'表示背景为斜纹,不然默认为纯黑,不好判断启动成功与否
rock@ubuntu17:~$ sudo X :2 vt5 -listen tcp -retro

此时tty5终端已经成功启动了X-Server了,显示如下斜纹背景:

X-Window结构分析与应用

ctrl+alt+F3 切换到其他未使用的终端:

rock@ubuntu17:~$ sudo lsof -nPi tcp|grep X
Xorg 3505 root 5u IPv6 37157 0t0 TCP *:6002 (LISTEN)
Xorg 3505 root 6u IPv4 37158 0t0 TCP *:6002 (LISTEN)

启动后可以发现tcp6002端口已启动监听. 其实X-Server的端口号是从6000开始一一对应Displayer-ID,如:tcp6002对应Displayer2,tcp6010对应Displayer10.

在刚启动的Displayer2上运行xclock:

rock@ubuntu17:~$ export DISPLAY=:2
rock@ubuntu17:~$ xclock&
[1] 4103
rock@ubuntu17:~$

ctrl+alt+F5 :

X-Window结构分析与应用

MacOS & Windows

其他操作系统,MacOS上运行XQuartz,Windows上运行Xmanager 即可看到相应的TCP端口开启了监听,即已经开启了X-Server服务

  • XQuartz

X-Window结构分析与应用

  • Xmanager

X-Window结构分析与应用

X-Window-Manager

单一的X-Server是没有窗口管理功能的,即多个X-Client窗口会重叠在一块,无法移动任何一个窗口. X-Window-Manager就是为多窗口管理而生的

  • X-Server(xorg) without X-Window-Manager
## 接着使用上一节'纯净X-Server'的环境
rock@ubuntu17:~$ sudo X :2 vt5 -listen tcp -retro
## tty2:
rock@ubuntu17:~$ export DISPLAY=:2
## 运行GUI应用程序
rock@ubuntu17:~$ xterm&
rock@ubuntu17:~$ xeyes&

此时tty5的桌面显示为下图所示的俩重叠应用程序窗口, 鼠标无法拖动:

tty5:

X-Window结构分析与应用

  • Xorg-server(xorg) with X-Window_Manager(twm)
## 接着使用上面的环境
rock@ubuntu17:~$ sudo X :2 vt5 -listen tcp -retro
## tty2:
rock@ubuntu17:~$ export DISPLAY=:2
## 运行GUI应用程序
rock@ubuntu17:~$ xterm&
rock@ubuntu17:~$ xeyes&
## 启动twm窗口管理器
rock@ubuntu17:~$ twm&

此时tty5桌面上的俩应用程序窗口已经可以用鼠标*拖放了:

X-Window结构分析与应用

  • 所以现在应该理解了ubuntu上GNOME桌面提供的其中一个服务就是X-Window-Manager了:

X-Window结构分析与应用

  • MacOS下XQuartz自身集成了X-Window-Manager
  • Windows下XManager自身也集成了X-Window-Manager

X-Security

X-Window系统的’访问控制(access control)’功能由X-Security来定义,X-Server端可配置基于IP级别的验证和基于共享key的验证.

确认当前X-Server的’访问控制’功能已开启

xhost配置的是当前终端环境变量DISPLAY所指向的X-Server.所以配置前先确认当前环境变量中的DISPLAY值

rock@rock-ubuntu17:~$ echo $DISPLAY
:1
## 显示当前访问控制的状态,和xhost允许的X-Client的主机来源或用户
rock@rock-ubuntu17:~$ xhost
access control enabled, only authorized clients can connect
SI:localuser:rock

该输出表示访问控制功能已开启,只允许通过IP验证 或 通过共享密钥验证的才能访问X-Server服务

## 关闭访问控制. 不验证xhost和xauth
rock@rock-ubuntu17:~$ xhost +
access control disabled, clients can connect from any host
## 开启访问控制. 验证xhost和xauth
rock@rock-ubuntu17:~$ xhost -
access control enabled, only authorized clients can connect

基于IP验证

  • 一个Displayer(X-Server)对应一个xhost列表
  • 只需在X-Server端配置
  • X-Client请求接入时, X-Server验证X-Client的IP是否在放行列表中,不在则拒绝接入
  • 使用xhost维护
## 指定配置Displayer1
rock@rock-ubuntu17:~$ export DISPLAY=:1
## 允许10.211.55.2上的X-Client接入Displayer1
rock@rock-ubuntu17:~$ xhost + 10.211.55.2
10.211.55.2 being added to access control list
## 显示Displayer1上的IP放行列表
rock@rock-ubuntu17:~$ xhost
access control enabled, only authorized clients can connect
INET:10.211.55.2
SI:localuser:rock

以上设置重启失效,永久设置需要写入到对应的配置文件:/etc/X[n].hosts. n为X-Server显示器ID.即:

/etc/X0.hosts: 配置Displayer0的放行列表

/etc/X1.hosts: 配置Displayer1的放行列表

基于预共享密钥验证

  • 一个X-Server关联一个Xauthority文件, 一个Xauthority文件可包含多个Displayer的预共享密钥. X-Server只使用他所关联的Xauthority文件里自身Displayer的密钥记录
  • 需要在X-Server端和X-Client端配置一致的密钥
  • X-Client接入时带上目标Displayer的密钥,X-Server验证该密钥是否与本地Xauthority文件里配置的一致,不一致则拒绝
  • 使用xauth维护

xauth默认配置的是环境变量XAUTHORITY所指向的Xauthority文件或用户家目录的.Xauthority(当环境变量’XAUTHORITY’不存在时); 可以用-f参数显式指定

在X-Server端为Displayer2生成密钥

rock@rock-ubuntu17:~$ xauth
Using authority file /run/user/1000/gdm/Xauthority
## 生成DISPLAY=:2显示器的共享key, '.'表示使用机制'MIT-MAGIC-COOKIE-1'明文共享密钥
xauth> generate :2 .
authorization id is 572
## 查看当前显示器共享key列表
xauth> list
rock-ubuntu17/unix:1 MIT-MAGIC-COOKIE-1 5d8f557af058440ef69d6aaa665c91fb
rock-ubuntu17/unix:2 MIT-MAGIC-COOKIE-1 578f3bb50f08cd4831277cc4397a2611
xauth> exit
Writing authority file /run/user/1000/gdm/Xauthority

在X-Client端配置对端Displayer2的密钥

## 添加目标Displayer的访问密钥
root@dld:~# xauth add 10.211.55.5:2 . 578f3bb50f08cd4831277cc4397a2611
## 配置当前终端使用上步骤的远端Displayer2
root@dld:~# export DISPLAY=10.211.55.5:2
root@dld:~# xclock

如果要编辑指定的Xauthority文件,则:

rock@rock-ubuntu17:~$ xauth -f /root/.Xauthority

X-Server执行时可使用’-auth [auth-file]’选项指定使用的Xauthority文件

rock@rock-ubuntu17:~$ sudo X :1 vt5 -auth /run/user/121/gdm/Xauthority

协议

NAT-sensitive(NAT敏感)

NAT敏感 通常是因为协议的应用报文中带有对端的IP信息, 对端在处理应用逻辑里需要依赖这IP信息. 然而对端在NAT环境后面(即做了DNAT, 外网IP映射为内网IP), 本端是不知道你是做了DNAT的, 所以带的对端的IP是对端的外网IP, 这时对端解释到的IP信息是外网IP, 但他应用逻辑是用内网IP的, 因此导致应用逻辑上的混乱

X-Server与X-Client之间的远程通信使用TCP方式, 在实际测试中发现X-Server和X-Client在同一个局域网内才能连接成功, X-Client连接NAT后的X-Server的话是不能成功的. 看来这协议是’NAT敏感’的. 解决方法通常是使用SSH隧道

还有一个内容本文章没提及, 那就是XDM(X-Display-Manager), 他使用的是XDMCP协议, 走UDP177端口. 以后有机会再补充这块内容

结合SSH隧道运行远端GUI应用

为解决刚说的’NAT敏感’问题. 我们常常使用SSH隧道上的端口转发功能来绕过问题, 其原理图如下:

X-Window结构分析与应用

远端上的X-Client应用程序访问其本地SSH-Server开启的转发监听端口PORT1, SSH-Server将其在Port1在监听接入的数据通过已经建立好连接的SSH隧道转发到我们本地的SSH-Client, SSH-Client再将数据转发到本地X-Server所监听的TCP端口PORT2

以PORT1为6010, PORT2为6002为例, 其SSH隧道建立及开启转发功能的连接命令为:

ssh -R 127.0.0.1:6010:127.0.0.1:6002 user@remotehost

ssh有个简洁的-X参数能自动根据环境配置以上转发参数及远端SSH终端的DISPLAY环境变量. 可以自行翻阅文档查看详情, 这功能需要在SSH-Server开启’X11 Forwarding’选项

这样一来就避开了’NAT敏感’的问题, 从X-Client看来他只是访问本地的X-Server, 从X-Server看来他只是接入本地的X-Client, 所有X层面上看到的IP都是127.0.0.1

同时这个做法避开了X-Security的f远程主机验证,因为都认为是本地主机的访问,只需允许本地访问即可

应用案例

本地MAC端运行远端GUI应用

下面演示使用XQuartz结合SSH的’Forwarding’运行远端GUI

  1. 先在本地运行XQuartz:

    • 配置允许网络客户端接入(开启使用TCP方式):

    X-Window结构分析与应用

    • 关闭XQuartz, 重新打开XQuartz. 此时已发现tcp6000端口已经在监听:

    X-Window结构分析与应用

    • 允许本地主机访问XQuartz. XQuartz默认没允许本机IP访问

      xhost + 127.0.0.1

      X-Window结构分析与应用

  2. 用ssh的’-R’选项在SSH会话上开启隧道端口转发功能:

    意思为将本地XQuartz的X-Server端口tcp6000映射到远端本地上的tcp6010

    root@test:~# ssh -R 127.0.0.1:6010:127.0.0.1:6000 user@remotehost

    X-Window结构分析与应用

  3. 配置远程终端的DISPLAY环境变量

    root@test:~# export DISPLAY=:10

    如步骤2截图

  4. 执行GUI

    root@test:~# xterm&
    root@test:~# xeyes

    如步骤2截图

使用X11-Forwarding(即’ssh -X user@host’)操作会比较简单, 其实他自动做的事的步骤就是以上说的

本地Ubuntu端运行远端GUI应用

下面演示使用Xorg结合SSH的’Forwarding’运行远端GUI

  1. 先在本地的tty5运行Xorg

    当前在tty2:

    rock@ubuntu17:~$ sudo X :2 vt5 -listen tcp -retro&

    X-Window结构分析与应用

    tty5:

    X-Window结构分析与应用

    ctrl+alt+F2回到tty2, 确认已经在本地开启Displayer2对应是tcp6002监听:

    X-Window结构分析与应用

  2. 用ssh的’-R’选项在SSH会话上开启隧道端口转发功能:

    ssh -R 127.0.0.1:6010:127.0.0.1:6002 user@remotehost

    确认SSH-Server已为我们启动端口转发功能:已开启tcp6010监听

    X-Window结构分析与应用

  3. 配置远程终端的DISPLAY环境变量, 执行GUI

    配置DISPLAY=:10指向SSH启动的转发端口6010:

    X-Window结构分析与应用

    ctrl+alt+F5 :

    最终已转发到我们本地的6002端口:

    X-Window结构分析与应用

使用X11-Forwarding(即’ssh -X user@host’)操作会比较简单, 其实他自动做的事的步骤就是以上说的

本地Windows端运行远端GUI应用

下面演示使用Xmanager套件结合SSH的’X11 Forwarding’运行远端GUI

  1. 先在本地运行X-Server:

X-Window结构分析与应用

X-Window结构分析与应用

X-Window结构分析与应用

现在X-Server已启动, 为Displayer0, 监听tcp6000

  1. 接下来运行Xshell, 配置远端会话属性, 开启SSH隧道中的’转发X11’选项:

注意: SSH服务端也需要开启’X11 Forwarding’选项. 不开的话Xshell配置隧道中用’TCP/IP转移’也行, 原理就是前面所说的

X-Window结构分析与应用

  1. 登录SSH,并执行GUI, 相应的UI已经在windows本地显示出来:

可看到’X11 Forwarding’已经帮我们终端配置好DISPLAY环境变量了

远端的SSH Server在本地也开启的相应的tcp6015监听, 用于隧道上的端口转发

X-Window结构分析与应用