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-xfceand name itsys-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: Makesys-usb-dvma disposable template (“Qube Manager” > “sys-usb-dvm” > “Settings” > “Advanced” > “Other” > “Disposable template”)- Change the template of
sys-usb:
This might lock you out of the system. Find a PS/2 keyboard to prevent thatdom0: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
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 geterror: failed to read response from server)If a command is interrupted, you can sometimes getadb: protocol fault. You can fix this by runningsudo systemctl restart adb-client-proxy- The bandwith is very limited (~50KiB/s)