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

от автора


Есть две технологии в ИТ, которые казалось должны были исчезнуть на рубеже прошлого века, но их живучесть и удобство раз за разом отодвигает их уход со сцены. Речь идет об 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) передачи датаграмм.

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

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

Рис. 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);  <span class="hljs-keyword" style="color: rgb(0, 0, 255);">return</span> (_XSERVTransMakeAllCOTSServerListeners(port, partial,     &ListenTransCount,     &ListenTransConns) >= <span class="hljs-number" style="color: rgb(0, 128, 0);">0</span>);  }

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

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

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

|admin@redeye:[~]> 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;

Рис. 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.

Рис. 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, который создаст файл ~/.Xauthorityy. Если все нормально, то файл уже создан и проверка показывает на наличие MAGIC COOKIE.

|admin@redeye:[~]> xauth list host/unix:0  MIT-MAGIC-COOKIE-1  00000000000000111111111111111111

  1. Имеет смысл в качестве проверки иксов выполнить xclock прежде, чем запускать JAVA installer, или мастер настроек некоей корпоративной CRM, или ERP системы. Если показалось окно xclock, то соединение с X-сервером работает как надо.

Рис. 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 остается вне конкуренции. Надеюсь так не будет продолжаться долго.

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


ссылка на оригинал статьи https://habr.com/ru/articles/574742/