Running adb (android debug bridge) | Split ADB

This is a guide for using [adb | developer.android.com] on QubesOS. There is currently a bug that does not allow you to attach an adb device to a VM, so this solution must be used instead.

Overview

  • Set up VM interconnection
  • Set up the sys-usb template
  • Configure adb
  • Set up the client

1. Set up VM interconnection

  • Create a new policy file with the “Policy Editor”:
File 50-android-debug-bridge
qubes.ConnectTCP +5038 @anyvm sys-usb ask default_target=sys-usb

2. Set up the sys-usb template

  • Create a new AppVM based on fedora-41-xfce and name it sys-usb-dvm
  • Obtain an adb binary (either build it yourself or download it from [the release page | developer.android.com]) and copy it to sys-usb-dvm
  • sys-usb-dvm: Copy the received binary to ~/adb/ (create if needed)

3. Set up adb

  • sys-usb-dvm: Create the following files:
File ~/adb/adb.service
[Unit]
Description=Android Debug Bridge

[Service]
Type=forking
ExecStart=/usr/local/bin/adb start-server
ExecStop=/usr/local/bin/adb kill-server
Restart=on-failure
File ~/adb/adb-proxy.service
[Unit]
Description=Android Debug Bridge Proxy

[Service]
Type=simple
ExecStart=/usr/local/bin/adb-server-proxy
Restart=on-failure
File ~/adb/adb-server-proxy.py
#!/usr/bin/env python3

# ADB Client (AC) - Proxy Client (PC) --|-- Proxy Server (PS) - ADB Server (AS)
# ADB Client (AC) /                                           \ ADB Server (AS)

import socket

tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_socket.bind(("127.0.0.1", 5038))

tcp_socket.listen(1)
print("adb-server-proxy started")

while True:
    connection, client = tcp_socket.accept()

    try:
        print("Proxy Client connection:", client)
        substream_connections = dict()

        while True:
            # Proxy Client -> ADB Server
            try:
                recv_sid_raw = connection.recv(1, socket.MSG_DONTWAIT)
                if len(recv_sid_raw) == 0:
                    raise Exception("client disconnected")
                if recv_sid_raw == b'\0':
                    disconnected_substream = int.from_bytes(connection.recv(1))
                    print(f"{disconnected_substream}:", "client-initiated disconnect")
                    substream_connections[disconnected_substream].close()
                    del substream_connections[disconnected_substream]
                else:
                    recv_sid = int.from_bytes(recv_sid_raw)
                    substream_connection = substream_connections.get(recv_sid, None)
                    if substream_connection == None:
                        print("connecting new substream", recv_sid)
                        substream_connection = socket.create_connection(("127.0.0.1", 5037))
                        substream_connections[recv_sid] = substream_connection
                    substream_connection.send(connection.recv(1))
            except BlockingIOError:
                pass

            # ADB Server -> Proxy Client
            for sid, adb_connection in list(substream_connections.items()):
                try:
                    adb_byte = adb_connection.recv(1, socket.MSG_DONTWAIT)
                    if len(adb_byte) == 0:
                        print(f"{sid}:", "server-initiated disconnect")
                        connection.send(b'\0'+sid.to_bytes())
                        del substream_connections[sid]
                        continue
                    connection.send(sid.to_bytes()+adb_byte)
                except BlockingIOError:
                    pass

    except Exception as e:
        print("exception thrown in handler:", type(e).__name__, str(e))
    finally:
        print("Proxy Client disconnected")
        for adb_conn in substream_connections.values():
            adb_conn.close()
        connection.close()
  • sys-usb-dvm: Add this to the bottom of /rw/config/rc.local:
rc.local
# adb:
cp /rw/home/user/adb/adb /usr/local/bin/
chmod +x /usr/local/bin/adb
cp /rw/home/user/adb/adb-server-proxy.py /usr/local/bin/adb-server-proxy
chmod +x /usr/local/bin/adb-server-proxy
cp /rw/home/user/adb/adb.service /etc/systemd/system/
cp /rw/home/user/adb/adb-proxy.service /etc/systemd/system/

systemctl daemon-reload
systemctl enable adb
systemctl enable adb-proxy
systemctl start adb
systemctl start adb-proxy
# ===
  • Shut down sys-usb-dvm
  • dom0: Make sys-usb-dvm a disposable template (“Qube Manager” > “sys-usb-dvm” > “Settings” > “Advanced” > “Other” > “Disposable template”)
  • Change the template of sys-usb:
    • :warning: This might lock you out of the system. Find a PS/2 keyboard to prevent that
    • dom0: qvm-shutdown --wait sys-usb; qvm-prefs -s sys-usb template sys-usb-dvm; qvm-start sys-usb

4. Set up the client

  • Create the following file:
File ~/adb/adb-client-proxy.py
#!/usr/bin/env python3
import subprocess
import socket
from threading import Thread
import time

connection_proc = None

conproc_disconnected = False
conproc_stdout = []
def read_conproc():
    global conproc_disconnected
    while True:
        recv_byte = connection_proc.stdout.read(1)
        conproc_stdout.append(recv_byte)
    conproc_disconnected = True
    print("read_conproc ended")

tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_socket.bind(("127.0.0.1", 5037))

tcp_socket.listen(1)
print("adb-client-proxy started")

substream_connections = dict()

def proxier():
    global substream_connections, conproc_disconnected

    while True:
        # Proxy Server -> ADB Client
        if conproc_disconnected:
            print("server disconnected")
            exit(1)

        if len(conproc_stdout) > 1: # Only reading pairs
            recv_sid_raw = conproc_stdout.pop(0)
            if recv_sid_raw == b'\0':
                disconnected_substream = int.from_bytes(conproc_stdout.pop(0))
                print(f"{disconnected_substream}:", "server-initiated disconnect")
                substream_connections[disconnected_substream].close()
                del substream_connections[disconnected_substream]
            else:
                recv_sid = int.from_bytes(recv_sid_raw)
                substream_connection = substream_connections.get(recv_sid, None)
                if substream_connection == None:
                    print("recv to unknown substream", recv_sid)
                else:
                    substream_connection.send(conproc_stdout.pop(0))

        # ADB Client -> Proxy Server
        for sid, adb_connection in list(substream_connections.items()):
            try:
                adb_byte = adb_connection.recv(1, socket.MSG_DONTWAIT)
                if len(adb_byte) == 0:
                    print(f"{sid}:", "client-initiated disconnect")
                    connection.send(b'\0'+sid.to_bytes())
                    del substream_connections[sid]
                    continue
                connection_proc.stdin.write(sid.to_bytes()+adb_byte)
                connection_proc.stdin.flush()
            except BlockingIOError:
                continue

Thread(target=proxier).start()

EXIT_CMD = b"0009host:kill"

while True:
    connection, client = tcp_socket.accept()
    
    if connection_proc is None:
        conproc_stdout.clear()
        connection_proc = subprocess.Popen(["qrexec-client-vm", "sys-usb", "qubes.ConnectTCP+5038"], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        Thread(target=read_conproc).start()

    new_sid = 1
    while new_sid in substream_connections.keys():
        new_sid += 1

    print("connection from client", new_sid)
    substream_connections[new_sid] = connection
File ~/adb/adb-client-proxy.service
[Unit]
Description=Android Debug Bridge Client Proxy

[Service]
Type=simple
ExecStart=/usr/local/bin/adb-client-proxy
Restart=on-failure
  • Add the following to /rw/config/rc.local
rc.local
# adb client:
cp /rw/home/user/adb/adb-client-proxy.py /usr/local/bin/adb-client-proxy
chmod +x /usr/local/bin/adb-client-proxy
cp /rw/home/user/adb/adb-client-proxy.service /etc/systemd/system/

systemctl daemon-reload
systemctl enable adb-client-proxy
systemctl start adb-client-proxy
# ===

Notes

  • :warning: The proxy server can only handle one VM at a time. When adb is used in a VM, other VMs can’t use it until the proxy is released. You can release the proxy by running adb kill-server (you should get error: failed to read response from server)
  • If a command is interrupted, you can sometimes get adb: protocol fault. You can fix this by running sudo systemctl restart adb-client-proxy
  • The bandwith is very limited (~50KiB/s)
4 Likes