KDEConnect/Android Emulator

From KDE Community Wiki

Running KDE Connect in an Android Emulator

The Android emulator uses the default qemu User Networking (SLIRP) backend which does not allow the emulator to be directly accessible from the host nor the local network. This prevents kdeconnect running on the desktop from seeing kdeconnect running in an emulator. In order to get a fully working network in the emulator it needs to be configured to use the tap networking backend which as an added bonus offers much better performance than the default user backend.

Running the emulator with the tap backend will create a virtual network device on the host machine that can then be configured as if it is a real ethernet card allowing you to create virtually any type of network topology. The most straightforward topology that can be created is the public bridge setup. In this setup we simply put both the emulator's virtual interface and the desktop's lan interface into a bridge so they will share the same network space (e.g. single IP-subnet). Another topology that can be used is the routing with iptables setup. This involves a little more work but will work in situations where your lan device does not support bridging (e.g. some wireless drivers).

The public bridge setup

Setting up your host system and the Android emulator to use the public bridge setup requires changes on both sides. These changes are described in the following sections.

Configuring the host machine

First you will have to unconfigure your LAN interface, create the bridge, add your LAN interface to it and assign the bridge an IP address. Assuming your LAN interface is eth0 and the bridge interface you want to create is br0:

sudo ifconfig eth0 0.0.0.0
sudo ip link add br0 type bridge
sudo ip link set eth0 master br0
dhclient br0

To make the above changes permanent do the following taken from kvm docs and reboot your system:

System Changes
RedHat/Fedora
  1. Edit /etc/sysconfig/network-scripts/ifcfg-eth0
    • comment out BOOTPROTO
    • Add BRIDGE=br0
  2. Create /etc/sysconfig/network-scripts/ifcfg-br0
    • The contents should be:
DEVICE=br0
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Bridge
STP=no
ZONE=<the same zone as eth0 is in>
Debian Edit /etc/network/interfaces
# Replace old eth0 config with br0
auto eth0 br0
# Use old eth0 config for br0 and configure the bridge
iface br0 inet dhcp
    bridge_ports   eth0
    bridge_stp     off
    bridge_maxwait 0
    bridge_fd      0
SuSE
  • Start YaST
  • Got to Network Configuration
  • Add new device -> Bridge
  • Tick your existing network device
  • done


Next you have to allow qemu-bridge-helper to add tap interfaces to this bridge, using your favorite text editor:

sudo vi /etc/qemu/bridge.conf
and add the following line:
allow br0

You can now start the emulator using the following command replacing <avd_name> with the name of an existing emulator:

emulator -avd <avd_name> -qemu --device virtio-net-pci,netdev=n1 -netdev tap,id=n1,"helper=/usr/libexec/qemu-bridge-helper --br=br0"&

To list available devices to use as <avd_name> use:

emulator -list-avds

If you want to run more than one emulator at a time you will also have to provide a MAC address to be used by the emulator:

MAC=`printf '52:54:00:12:%02X:%02X\n' $((RANDOM%256)) $((RANDOM%256))`
emulator -avd <avd_name> -qemu --device virtio-net-pci,netdev=n1,mac=${MAC} -netdev tap,id=n1,"helper=/usr/libexec/qemu-bridge-helper --br=br0"&

Configuring the Android emulator

Now that we have the host system setup its time to make things work in the emulator.

First put Android in airplane mode by dragging down the notification bar and pressing the airplane icon.

Then get a root shell in the emulator:

adb -s emulator-5554 shell
su

Next start a dhcpclient on eth1 to configure the interface. For an emulator with API level > 24 issue:

daemonize dhcpclient -i eth1

For an emulator with API Level > 22 and <= 24 issue:

dhcptool eth1

For anything older issue:

mkdir /data/misc/dhcp
dhcpcd -b eth1

Make normal routing take precedent over anything that Google/qemu configured. If ip does not work use busybox ip instead

ip rule add from all lookup main pref 99

Almost there

Take android out of airplane mode and for API levels > 24 wait until wlan0 is up again.

For an emulator with API level > 24 make dns lookups work again by issuing:

ip ro add 10.0.2.3 via 192.168.232.1 dev wlan0

And for an emulator with API level <= 23 delete the default route sending everything through the SLIRP interface:

ip ro del default via 10.0.2.2

You should be able to figure out what to do if the ip command does not exist.

Congratulations you are now the proud owner of an Android emulator with proper networking.

And then there was a script to do most of the hard work for you

Because like me you probably don't want to do that every day I've created a little script to do all the hard work for you.

Warning

Be warned though - I'm a Fedora person so running this on anything else will probably require some changes.
Please feel free to contribute your changes!
#!/bin/sh

# Copyright 2018 Erik Duisters
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License or (at your option) version 3 or any later version
# accepted by the membership of KDE e.V. (or its successor approved
# by the membership of KDE e.V.), which shall act as a proxy
# defined in Section 14 of version 3 of the license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Tested with emulators API 28-15 (excl 20) 
# API 15 has ash to [[ ]] does not work but networking seems to come up like it should

if [ -z "$1" ]
then
  echo "Error: No avd specified use one of" 
  emulator -list-avds
  exit 1
fi

#https://www.linux-kvm.org/page/Networking
#https://serverfault.com/questions/308091/set-mac-address-in-qemu-kvm-properties-file
MAC=`printf '52:54:00:12:%02X:%02X\n' $((RANDOM%256)) $((RANDOM%256))`

emulator -avd $1 -qemu -device virtio-net-pci,netdev=n1,mac=$MAC -netdev tap,id=n1,"helper=/usr/libexec/qemu-bridge-helper --br=br0"&

sleep 5

#TODO: Need a better way to get the emulator number
EMULATOR=`adb devices | grep emulator | tail -n1 | cut -f 1`

echo "Waiting until $EMULATOR boot is complete"

adb -s $EMULATOR wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'

echo "Boot complete - Configuring bridge mode networking"

sleep 5

SDK=""

while [[ -z $SDK ]]
do
  SDK=`adb -s $EMULATOR shell getprop ro.build.version.sdk | sed 's/.*\([0-9][0-9]\).*/\1/'`
done

echo "Emulator has sdk version: ${SDK}"

IP="ip"

if (($SDK == 18))
then
  IP="busybox ip"
fi

echo "Turning airplane mode on"

adb -s $EMULATOR shell "su 0 settings put global airplane_mode_on 1"
adb -s $EMULATOR shell "su 0 am broadcast -a android.intent.action.AIRPLANE_MODE"

if (($SDK > 24))
then
  echo "Waiting for wlan0 to go down"
  while ! `adb -s emulator-5554 shell "su 0 ip link show wlan0" | grep -q "state DOWN"`; do sleep 2; done
fi

echo "Starting dhcpclient for eth1"

if (($SDK > 24))
then
  echo "using dhcpclient"
  adb -s $EMULATOR shell "su 0 daemonize dhcpclient -i eth1"
elif (($SDK > 22))
then
  echo "using dhcptool"
  adb -s $EMULATOR shell "su 0 dhcptool eth1"
else
  echo "using dhcpcd"
  if (($SDK <= 16))
  then
    mkdir /data/misc/dhcp
  fi
  adb -s $EMULATOR shell "su 0 dhcpcd -b eth1"
fi

adb -s $EMULATOR shell "su 0 ${IP} rule add from all lookup main pref 99"

sleep 2

echo "Turning airplane mode off"
adb -s $EMULATOR shell "su 0 settings put global airplane_mode_on 0"
adb -s $EMULATOR shell "su 0 am broadcast -a android.intent.action.AIRPLANE_MODE"

if (($SDK > 24))
then
  echo "Waiting for wlan0 to come UP"
  while ! `adb -s emulator-5554 shell "su 0 ip link show wlan0" | grep -q "state UP"`; do sleep 2; done

  sleep 2

  echo "Re-enabling dns lookups"

  adb -s $EMULATOR shell "su 0 ip ro add 10.0.2.3 via 192.168.232.1 dev wlan0"
elif (($SDK <= 23))
then
  adb -s $EMULATOR shell "su 0 ${IP} ro del default via 10.0.2.2"
fi

echo "Configuration complete"

The routing with iptables setup

Note

When using this setup both parties will not receive each others broadcast packets so you will have to connect by IP address.


Configuring the host machine

Since you do not want to run the emulator as the super user you will have to grant normal users permission to use tap devices.

Create a net udev rule that creates the net/tun device file with the needed permission:
sudo vi /etc/udev/rules.d/00-net-tun.rules
The contents should be:
KERNEL=="tun", GROUP="netdev", MODE="0660", OPTIONS+="static_node=net/tun"
Create a new group netdev and add yourself to it:
sudo groupadd netdev
sudo usermod -a -G netdev `whoami`
Now reboot your system so the changes take effect.

Configure the host system to support the routed setup

Create and bring up a new tap device:
sudo ip tuntap add tap0 mode tap user `whoami`
sudo ip link set tap0 up
Allow ip forwarding:
sudo sysctl -w net.ipv4.ip_forward=1
Allow proxy_arp so the host will respond to "who has" arp messages for the emulator:
sudo sh -c "echo 1 > /proc/sys/net/ipv4/conf/all/proxy_arp"
Route all traffic for the emulator through the tap device (use an address in your subnet):
sudo ip ro add 192.168.0.100 dev tap0
Allow traffic to and from the emulator (you can use tap+ instead of tap0 to allow all tap devices):
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT
sudo iptables -I FORWARD 1 -o tap0 -j ACCEPT
sudo iptables -I INPUT 1 -i tap0 -j ACCEPT
sudo iptables -I OUTPUT 1 -o tap0 -j ACCEPT
Time to start the emulator:
emulator -avd <avd-name> -qemu --device virtio-net-pci,netdev=n1 -netdev tap,id=n1,ifname=tap0,script=no,downscript=no

Configuring the Android emulator

Now that the host is configured a couple of things need to be configured in the emulator.

Start a shell in the emulator and become root:
adb -s emulator-5554 shell
su
Assign an IP address to the tap device:
ifconfig eth1 192.168.0.100
Configure the default gateway (use the address of your desktop's LAN interface here):
ip ro add default via 192.168.0.2
Make normal routing take precedent over anything that Google/qemu configured:
ip rule add from all lookup main pref 99
Make DNS lookups work again:
ip ro add 10.0.2.3 via 192.168.232.1 dev wlan0

Have fun. Remember broadcasts do not work in this setup so you will have to configure the IP address of the host machine in kdeconnect on the emulator (overflow menu->Add devices by IP).