Сетевые соединения X11

wcwpqb-pcumalfntuw_9isd2jfo.jpeg


Есть две технологии в ИТ, которые казалось должны были исчезнуть на рубеже прошлого века, но их живучесть и удобство раз за разом отодвигает их уход со сцены. Речь идет об IPv4 и X11. Если первый из них практически во всех аспектах уступает IPv6, то преимущества Wayland, как технологии над X11 очевидны не всем. Wayland вовсе не универсален, как X Windows System, он намного более прост. Это дает ему ряд преимуществ по сравнению с иксами, но в этом же кроются его недостатки.

Если говорить о преимуществах, то это в первую очередь простота реализации и долгожданное избавление пользователей графической среды Linux от таких артефактов перерисовки, как разрывы изображения, a․ k․ a․ tearing. С этим особенно часто сталкиваются обладатели видео карт NVidia. Хватает и недостатков и противники замены X-сервера напирают на гибкость использования сетевых возможностей в различных сценариях.

As mentioned, X is essentially a networking protocol with graphical displaying capabilities.


На самом деле X11 сетевой протокол с графическими возможностями, а не наоборот. Из этого простого факта следует, что даже если в скором времени основные дистрибутивы Linux перейдут на протокол Wayland, многие из возможностей и примеров использования X сервера будут востребованы еще достаточно долгое время. Удаленный доступ к приложениям и сессиям X.Org — современной реализации X Window System, одна из таких ключевых особенностей системы.

▍ Сетевая структура взаимодействий X-сервера


Наиболее распространенным IPC, т․ е․ способом взаимодействия между процессами, X-клиента и X-сервера, являются сокеты. Их роль в предоставлении API связи с использование TCP/IP, а также сокетов домена Unix. Помимо сокетов клиент и сервер для коммуникации могут также использовать иные каналы IPC, например MIT Shared Memory Extension.

Доменные сокеты Unix (от англ. Unix Domain Sockets, UDS) являются POSIX-механизмом IPC, с помощью которого различные процессы ОС могут взаимодействовать друг с другом. Такие сокеты эффективнее локальных TCP/IP соединений, так как не требуют дополнительных байтов в заголовке протокола. Подобно сокетам TCP/IP, доменные сокеты Unix поддерживают надёжную потоковую передачу данных с помощью SOCK_STREAM. Они также могут работать в режиме упорядоченной (см․ SOCK_SEQPACKET) и неупорядоченной (см․ SOCK_DGRAM) передачи датаграмм.

image-loader.svg


Рис. 1 Сетевое взаимодействие между клиентом и сервером X с помощью UDS.

UDS используют файловую систему ОС в качестве адресного пространства имён (например /tmp/my_xapp), сами сокеты в ней всего лишь inode, а процессы обращаются к сокетам, так же, как к файлу. Однако обмен данными в активном соединении использует не файловую систему, а только буферы памяти ядра.

image-loader.svg


Рис. 2 Сетевое взаимодействие между клиентом и сервером X поверх TCP/IP.

Сокеты TCP/IP и UDS создаются с помощью функции CreateWellKnownSockets (). Для этого она обращается к _XSERVTransMakeAllCOTSServerListeners. Эта процедура пробегает по каждому интерфейсу транспорта, поддерживаемого сервером.

static Bool
TryCreateSocket(int num, int *partial)
{
    char port[20];
    snprintf(port, sizeof(port), "%d", num);

return (_XSERVTransMakeAllCOTSServerListeners(port, partial,
    &ListenTransCount,
    &ListenTransConns) >= 0);

}


Количество таких интерфейсов определено константой NUMTRANS в файле X11/Xtrans/Xtrans.c.

#define NUMTRANS        (sizeof(Xtransports)/sizeof(Xtransport_table))


Таблица Xtransports[] определена в том же самом файле и содержит 10 элементов, таким образом NUMTRANS ≤ 10.

|adm@redeyes:[~]> grep '{ &TRANS' /usr/include/X11/Xtrans/Xtrans.c
    { &TRANS(SocketTCPFuncs),   TRANS_SOCKET_TCP_INDEX },
    { &TRANS(SocketINET6Funcs), TRANS_SOCKET_INET6_INDEX },
    { &TRANS(SocketINETFuncs),  TRANS_SOCKET_INET_INDEX },
    { &TRANS(SocketLocalFuncs), TRANS_SOCKET_LOCAL_INDEX },
    { &TRANS(SocketUNIXFuncs),  TRANS_SOCKET_UNIX_INDEX },
    { &TRANS(LocalFuncs),       TRANS_LOCAL_LOCAL_INDEX },
    { &TRANS(PTSFuncs),         TRANS_LOCAL_PTS_INDEX },
    { &TRANS(NAMEDFuncs),       TRANS_LOCAL_NAMED_INDEX },
    { &TRANS(PIPEFuncs),        TRANS_LOCAL_PIPE_INDEX },
    { &TRANS(SCOFuncs),         TRANS_LOCAL_SCO_INDEX },


Сами же флаги, которые во время сборки определяют транспортные интерфейсы X сервера, можно найти в xc/config/cf/linux.cf.

#if HasDECnet
# define ConnectionFlags    -DUNIXCONN -DTCPCONN -DDNETCONN
# define ExtraLibraries        -ldnet
#else
# define ConnectionFlags    -DUNIXCONN -DTCPCONN
#endif


Проследив цепочку до системного вызова socket() находим такую последовательность транспортных процедур.

TRANS(MakeAllCOTSServerListeners)
                 |
                 v               +-------------------------+
+----->TRANS(OpenCOTSServer)+--->|TRANS(SocketSelectFamily)|
|                |               |TRANS(SocketOpen)        |
|                v               +-------+-----------------+
|           TRANS(Open)                  |
|                |                       |
|                v                       v
+------+TRANS(ParseAddress)          socket()


Серверный процесс стартует с вызова функции TRANS(CreateListener) из Xtrans.c. Далее переход в состояние ожидания входящего соединения происходит в такой последовательности.

TRANS(MakeAllCOTSServerListeners)
   |
   v
TRANS(CreateListener)
   |
   v
TRANS(SocketCreateListener)
   |
   v
bind()  


В файле Xtransmit.h можно обнаружить X_TCP_PORT. Первое клиентское соединение будет использовать порт 6000, второе — 6001 и т․ д․

#define X_TCP_PORT    6000

▍ Взгляд изнутри трафика между клиентом и сервером X


Запустим простейшую иксовую программу Xclock и проверим как все это работаете на практике.

|admin@redeye:[~]> DISPLAY=tcp/192.168.10.10:0.0 strace -e trace=network -f xclock 2>& 1  |grep -A3 TCP
1. socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_TCP) = 3
2. setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
3. setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
4. connect(3, {sa_family=AF_INET, sin_port=htons(6000), sin_addr=inet_addr("192.168.10.10")}, 16) = 0


В первой строке вывода видим, что системный вызов socket() завершился успешно и вернул файловый десткриптор 3. Далее, во второй строке setsockopt() выставляет параметр TCP_NODELAY, с которым TCP фрагменты отправляются по сети как можно ранее, даже если эти фрагменты слишком малы. В третьей строке опять встречается setsockopt() на это раз для активации опции SO_KEEPALIVE. Данный параметр поддерживает соединение в активном состоянии, с помощью периодической отправки служебных сообщений. Наконец в четвертой строке системный вызов connect() соединяет клиент X с сервером, по TCP порту 6000.

Используемый TCP порт напрямую связан с количеством соединений и номером переменной $DISPLAY, если бы использовалось DISPLAY=tcp/192.168.10.10:1.0, то тогда в последней строке connect() состоялся бы по TCP порту 6001.

После рукопожатия обмен данными происходит с использованием структур X-клиента — xConnClientPrefix, и X-сервера — xConnSetupPrefix, XconnSetup, определенных в файле /usr/include/X11/Xproto.h. Размер занимаемой памяти в структур в байтах определен в #define директивах заголовочного файла.

#define sz_xConnClientPrefix 12
#define sz_xConnSetupPrefix 8
#define sz_xConnSetup 32
...
typedef struct {
    CARD8       byteOrder;
    BYTE        pad;
    CARD16      majorVersion, minorVersion;
    CARD16      nbytesAuthProto;        /* Authorization protocol */
    CARD16      nbytesAuthString;       /* Authorization string */
    CARD16      pad2;
} xConnClientPrefix;
...
typedef struct {
    CARD8          success;
    BYTE           lengthReason; /*num bytes in string following if failure */
    CARD16         majorVersion,
                   minorVersion;
    CARD16         length;       /* 1/4 additional bytes in setup info */
} xConnSetupPrefix;
image-loader.svg


Рис. 3 Установка параметров соединения в сессии X11.

Можно также пронаблюдать за процессом с помощью сетевого анализатора, клиентский запрос Initial connection request соответствует вызову xConnClientPrefix. Соответственно Initial connection reply в следующем пакете создан со стороны xConnSetupPrefix и xConnSetup. Для сбора трафика можно запустить Wireshark и слушать локальный интерфейс, либо с помощью следующей команды tcpdump.

|root@redeye:[~]> tcpdump -i lo -vv not arp -w x11_trace.pcapng


Далее, сам файл можно открыть в Wireshark и отфильтровать просмотр по протоколу x11. Обычно для сбора трафика нужны привилегии root.

image-loader.svg


Рис. 4 Сетевой след соединения X11 в WireShark.

То же самое и во всех подробностях можно увидеть в tshark в текстовом формате. Так выглядит клиентский запрос X11 в сторону сервера.

Frame 11: 134 bytes on wire (1072 bits), 134 bytes captured (1072 bits)
    Encapsulation type: Ethernet (1)
    ...
    [Protocols in frame: eth:ethertype:ipv6:tcp:x11]
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
    Destination: 00:00:00_00:00:00 (00:00:00:00:00:00)
        Address: 00:00:00_00:00:00 (00:00:00:00:00:00)
    ...
Internet Protocol Version 6, Src: ::1, Dst: ::1
    0110 .... = Version: 6
    ...
Transmission Control Protocol, Src Port: 49202, Dst Port: 6010, Seq: 1, Ack: 1, Len: 48
    Source Port: 49202
    Destination Port: 6010
    ...
X11, Request, Initial connection request
    byte-order: 0x6c (Little-endian)
    unused
    protocol-major-version: 11
    protocol-minor-version: 0
    authorization-protocol-name-length: 18
    authorization-protocol-data-length: 16
    unused
    authorization-protocol-name: MIT-MAGIC-COOKIE-1


Пакет с ответом со стороны X-сервера совсем не так лаконичен, в нем указаны все необходимые детали для форматирования графического вывода на экран.


Frame 13: 14698 bytes on wire (117584 bits), 14698 bytes captured (117584 bits)
    Encapsulation type: Ethernet (1)
    ...
    [Protocols in frame: eth:ethertype:ipv6:tcp:x11]
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
    Destination: 00:00:00_00:00:00 (00:00:00:00:00:00)
        Address: 00:00:00_00:00:00 (00:00:00:00:00:00)
    ...
Internet Protocol Version 6, Src: ::1, Dst: ::1
    0110 .... = Version: 6
    ...
Transmission Control Protocol, Src Port: 6010, Dst Port: 49202, Seq: 1, Ack: 49, Len: 14612
    Source Port: 6010
    ...
X11, Reply, Initial connection reply
    success: 1
    unused
    protocol-major-version: 11
    protocol-minor-version: 0
    replylength: 3651
    release-number: 12011000
    resource-id-base: 0x0b200000
    resource-id-mask: 0x001fffff
    motion-buffer-size: 256
    length-of-vendor: 20
    maximum-request-length: 65535
    number-of-screens-in-roots: 1
    number-of-formats-in-pixmap-formats: 7
    image-byte-order: 0x00 (LSBFirst)
    bitmap-format-bit-order: 0x00 (LSBFirst)
    bitmap-format-scanline-unit: 32
    bitmap-format-scanline-pad: 32
    min-keycode: 8
    max-keycode: 255
    unused
    vendor: The X.Org Foundation
    pixmap-format
        pixmap-format
    ...
        pixmap-format
    ...
    screen
        screen (000007a4: 1920 x 1080 x 24)
            root: 0x000007a4
            default_colormap: 0x00000020
            white_pixel: 0x00ffffff
            black_pixel: 0x00000000
            current_input_masks: 0x00fa8033
            width_in_pixels: 1920
            height_in_pixels: 1080
            width_in_millimeters: 451
            height_in_millimeters: 254
            min_installed_maps: 1
            max_installed_maps: 1
            root_visual: 0x00000021
            backing_stores: 0x01
            save_unders: False
            root_depth: 24
            allowed_depths_len: 7
            depth-detail
                depth-detail
                    depth: 24
                    unused
                    visualtypes-numbers: 576
                    unused
                    visualtype
                        visualtype
                ...
                        visualtype
                ...
                        visualtype
                ...

▍ Удаленное подключение к X-серверу по ssh


Переходя от теории сетевых взаимодействий X Window System к практике, рассмотрим, как запустить графическое приложение на уделенном X-сервере при подключении по ssh.

|admin@redeye:[~]> ssh -X my.remote.host
|admin@redeye:[~]> ssh -Y my.remote.host


  1. Подключение необходимо запускать с ключом -X, который активирует переадресацию X11. На практике, однако из-за X11 SECURITY extension это часто не работает. В таких случаях используют -Y для подключения с доверительной переадресацией X11.
  2. Убедитесь, что на удаленном сервере установлен пакет xauth, который создаст файл ~/.Xauthority. Если все нормально, то файл уже создан и проверка показывает на наличие MAGIC COOKIE.
|admin@redeye:[~]> xauth list
host/unix:0  MIT-MAGIC-COOKIE-1  00000000000000111111111111111111


  1. Общепринятым наименьшим общим делителем иксов является xclock, в качестве проверки запустите эту программу прежде, чем запускать JAVA installer, или мастер настроек некоей корпоративной CRM, или ERP системы.
image-loader.svg


Рис. 5 Собственно xclock, значит удаленные иксы настроены и работают.

  1. Если после логина по ssh была выполнена команда смены пользователя sudo su - для перехода в root, то тогда необходимо выставить переменную DISPLAY. Этого не надо делать, если используется изначальный пользователь и сеанс shell.
|admin@redeye:[~]> export DISPLAY=:0
echo $DISPLAY
:0


Я стараюсь по мере возможности тестировать сеанс Wayland при каждом новом релизе KDE Plasma, несмотря на многочисленные улучшения с каждым разом, на версии kde-apps-21.04.3 работать можно лишь на Plasma-X11. В чем бы не состояли преимущества Wayland, до тех пор пока в терминале зависает команда man, хаотично прыгают элементы ниспадающего меню и блуждает внешний монитор, старый и добрый X Window System остается вне конкуренции. Надеюсь так не будет продолжаться долго.

▍ Для дополнительной информации

image-loader.svg

© Habrahabr.ru