Exploring Xorg connections



There is part two: Monitoring Raw X11 Communication or why Chromium opens 7 Xorg connections

Lately I have been playing around with raw X11 protocol by writing apps that work directly with Xorg by establishing a unix socket connection with it. It was pretty fun to play with and feels like easier than Xlib (but maybe I just got better). While doing all that I got interested with how other programs interact with Xorg and decided to explore how other programs do it and look at programs taht don’t have ‘perceptible’ windows but still maintain connections. Bellow is just my notes about that.

So when the system starts up on my setup there are 11 connections to the Xorg. These are when I have “zero” windows opens. Here is the list of apps that open connection with Xorg: xinit, lxsession, lxsession, openbox, lxpolkit, lxpanel, pcmanfm, lxclipboard, nm-applet, pulseaudio, at-spi2-registr.

Initially when I started with exploring Xorg and its connections I started with simple listing all unix connection with ss and then grepping for X11. This gave me a list of connections similar to the list bellow.

Netid State  Recv-Q Send-Q          Local Address:Port      Peer Address:Port  Process
--------------------------------------------------------------------------------------
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 16834                * 16187
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 17760                * 16727
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 14971                * 16724
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 14980                * 18853
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 26903                * 16783
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 2709                 * 16738
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 26836                * 24813
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 28684                * 15175
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 18866                * 16081
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 24848                * 13486
u_str ESTAB  0      0         @/tmp/.X11-unix/X0 18869                * 24841

It gives us a list of all connections opened on Xorg side with other applications in the system (minus 2 extra listening sockets). If we run ss with an extra option -p, it will give the name of the program that is sitting on one end of the connection. For example if we run ss -p | grep X11 it will show us that all of the previous 11 connections have users:((“Xorg”,pid=656,fd=43)) for the program part.

If we want to get a list of all the user programs that is connected to the to Xorg then we can get a list of all connections and then search for “Peer Port” section and relate a connection with Xorg manually. For the list above if I search for port 16187 in my list of connections I will find a single connection with 16187 as “Source Port”. In my case it is pulseaudio.

u_str ESTAB  0  0   * 16187  * 16834  users:(("pulseaudio",pid=752,fd=34))                                                                                                                                                                 

It could be find to do this manually for a one of task but if you want to continuously explore different connections this task might become too daunting. To simplify my explorations I wrote a tiny and dirty python script to do this manual task automatically. It goes all over the lines and finds all X11 connections and then finds all programs that is sitting on the other side of the connections. After the script is run the output looks like this:

  1 |  14971 - Xorg | pid:655         16724 - xinit
  2 |  17760 - Xorg | pid:681         16727 - lxsession
  3 |  26836 - Xorg | pid:681         24813 - lxsession
  4 |  18869 - Xorg | pid:709         24841 - openbox
  5 |  14980 - Xorg | pid:712         18853 - lxpolkit
  6 |  18866 - Xorg | pid:713         16081 - lxpanel
  7 |  18863 - Xorg | pid:715         25659 - pcmanfm
  8 |   2709 - Xorg | pid:718         16738 - lxclipboard
  9 |  24848 - Xorg | pid:723         13486 - nm-applet
 10 |  16834 - Xorg | pid:752         16187 - pulseaudio
 11 |  26903 - Xorg | pid:865         16783 - at-spi2-registr

From this list we can see that that is there a unix socket connection with one side being Xorg and port 16834 and the other side is pulseaudio port 16188 and pulseaudio has pid of 752.

Here is the script that used list of connected apps

#!/usr/bin/python
import sys
import os
import subprocess

all_data = {}
result = [] 

match_list = [
    '/tmp/.X11-unix/X0',
    '@/tmp/.X11-unix/X0',
]

def get_int(string):
    try:
        return int(string)
    except:
        return -1

def process_line(line):
    parts = line.split()

    if len(parts) != 10:
        return

    netid = parts[0]
    state = parts[1]
    recvq = parts[2]
    sendq = parts[3]
    local_address = parts[4]
    local_port = get_int(parts[5])
    remote_address = parts[6]
    remote_port = get_int(parts[7])
    program = parts[8]

    name, pid = get_program(program)

    all_data[local_port] = {
        "netid" : netid,
        "state" : state,
        "recvq" : recvq,
        "sendq" : sendq,
        "local_address" : local_address,
        "local_port" : local_port,
        "remote_address" : remote_address,
        "remote_port" : remote_port,
        "program" : program,
        "program_name" : name,
        "program_pid" : pid,
    }

    if local_address in match_list:
        result.append({
            'local_port' : local_port,
            'remote_port' : remote_port,
            'local_program': program,
            "program_name" : name,
            "program_pid" : pid,
        })

def get_program(string):
    try:
        parts = string.split(',')
        name = parts[0].split("\"")[1]
        pid = int(parts[1].split("=")[1])
    except:
        name = "[no name]"
        pid = "[no pid]"
    return name, pid

if __name__ == '__main__':
    system_result = subprocess.run(["ss", "-p", "-u", "-x"], capture_output=True)

    for line in system_result.stdout.split(b'\n'):
        process_line(str(line))

    if len(result) == 0:
        print("Nothing found. Maybe you forgot to give '-p' flag to 'ss'?")
    else:
        for index, item in enumerate(result):
            remote_program_str = all_data[item['remote_port']]['program']
            name, pid = get_program(remote_program_str)
            item['remote_program_name'] = name
            item['remote_program_pid'] = pid

    result = sorted(result, key=lambda x: x['remote_program_pid'])

    for index, item in enumerate(result):
        local_name = item['program_name']
        local_pid = item['program_pid']
        local_port = item['local_port']
        remote_port = item['remote_port']
        remote_name = item['remote_program_name']
        remote_pid = item['remote_program_pid']

        print("{:3}: | {:>9} - {} | pid:{:<9} {:>9} - {}".format(
            index + 1,
            local_port,
            local_name,
            remote_pid,
            remote_port,
            remote_name,
        ))

Programs that use multiple connections

At the very beginning (before I wrote that script) I noticed that programs open multiple connections to Xorg and that why I wrote a script to see what exactly is behind those connections. For example if you run skype it will open almost 8 connections to Xorg server, firefox opens 2 and telegram-desktop also opens 2 connections. From usage perspective 1 connection should be enough as you can do everything with it. But I guess these apps split them for some efficiency gains.