Accessing a local Hyper-V environment from the Android emulator

Posted by Rik Hepworth on Saturday, February 17, 2024

Not every project can host services in the cloud. If you have a local environment running on virtual machines, connecting to that from the Android emulator running on the same host can be tricky. This post details the solution I use and the tools needed to enable it.

Detailing the problem

At Black Marble we don’t just build solutions for our customers that run in the cloud. Sometimes things still need to be hosted on-premises. We have one project that involves a mobile application and a multi-tier service back end. For development we need to make sure developers don’t trip over one another, and that requires a dedicated environment, running in virtual machines that each developer can run on their computer.

In the beforetimes, when we all worked in the office, we could host the environments centrally. With remote working this is much less practical.

The Android emulator has supported using Hyper-V for some time now, but it is still self-contained and doesn’t know about the virtual switches that can be created on a Windows development machine. Instead, it connects straight to the network the host uses and there is no straightfoward way to change that, or override network settings such as DNS, and a local hosts file is a no-go.

I need to be able to connect the mobile app running on the emulator to the services running in Hyper-V. That environment is self contained with it’s own Domain Controller, DNS, Certificate Services, ADFS and more. I need the android emulator to be able to resolve the hostnames of the services I must access, and then communicate over a secure http connection with no cert trust issues.

Step 1: DNS

It’s always DNS. The emulator documentation is clear: When it starts, it looks at the DNS configuration of the host machine and uses those addresses. If I simply add the address of the DNS inside my environment then the emulator will pick it up. I can override the DNS settings if I like, providing I start it from the command line, so that might work…

That’s great, but then routing becomes the problem: My environment is deployed to an Internal Hyper-V Virtual Switch. We do this so we can control the configuration - each VM has a fixed IP address. Access to the internet is handled by the host, which uses NAT to allow outbound traffic and handle the response. The emulator is not connected to that internal switch, and the VMs aren’t directly accessible from the network to which the host is connected.

I could define several specific NAT port mappings to allow comms with a port on the host that is then routed to the VM. However, these would need to be custom ports, and that isn’t going to work with the emulator. Since all the hostnames would then need to resolve to the host, that also won’t work unless I run a separate DNS on the host itself.

DNSCrypt-Proxy

Even though I didn’t want to run a DNS server on my host laptop, that gave me a thread to pull on. I wondered if such a thing as a DNS Proxy existed. It turns out they do, and the one I’m using is from DNSCrypt.

Secure DNS is a laudable idea, but not what I’m looking for. However, the DNSCrypt-Proxy also supports ’traditional’ DNS requests on port 53. The software is well-maintained, regularly updated and extremely well documented.

DNSCrypt-Proxy allows me to configure DNS forwarding. It lets me do that through powerful rules that mean I can just forward requests for the .local domain my environment uses. It then sends all other requests to the DNS server on my network.

To enable forwarding there are two things I must do. First of all, I need to find the relevant bit of the dnscrypt-proxy.toml file that is the main source of configuration and uncomment the line about forwarding rules.

##################################################################################
#        Route queries for specific domains to a dedicated set of servers        #
##################################################################################
 
## See the `example-forwarding-rules.txt` file for an example
 
forwarding_rules = 'forwarding-rules.txt'

I then need to edit the forwarding-rules.txt file to uncomment the local domain and give it the IP address of the DNS in my environment.

## Forward *.lan, *.local, *.home, *.home.arpa, *.internal and *.localdomain to 192.168.1.1
# lan              192.168.1.1
local            192.168.254.2
# home             192.168.1.1
# home.arpa        192.168.1.1
# internal         192.168.1.1
# localdomain      192.168.1.1
# 192.in-addr.arpa 192.168.1.1

When you download the zip containing the windows release all the config files are named example-<config file>.toml or example-<config file>.txt so you need to copy or rename them to drop the ’example-'

I can simply run the dnscrypt-proxy.exe file in a console and edit my machines’s network settings to include 127.0.0.1 in the DNS servers alongside whatever is the local DNS on the network. If I the run DNS queries using nslookup or similar I should be able to resolve hostnames from the environment’s DNS as well as from elsewhere.

Step 2: HTTP

Being able to find the IP address is great, but without a way to communicate with that IP it’s not very helpful.

The Android emulator does allow us to configure a web proxy through it’s settings, so all I need to do is find one.

I started, as we probably all do, with Fiddler Classic. It’s a stalwart of developers whenever we need to monitor traffic in our app. However, no matter what I did or how I configured it, I could not get the emulator to communicate through it.

mitmproxy

After some research I found mitmproxy which is a command-line tool that you can even install from the Microsoft Store. It supports a wide range of protocols including HTTP/2 and WebSockets - both of which our app uses.

The documentation for mitmproxy is excellent. I knew that certificates would be a challenge - I need to use https and that means the proxy must decrypt and re-encrypt the traffic. I need it to trust the certs in my environment and I need to get the emulator to trust its certs.

The second part was really easy because it’s well documented: Simply point your browser in the emulator at a specific page, download the root cert for mitmproxy and install it on the emulator.

Trusting the certs in my environment I thought would be easy. Because mitmproxy runs on my local machine I can simply drop the root cert from the CA inside the environment into the Trusted Root store on my machine and that should be it. However, it wasn’t. I kept getting cert trust warnings from mitmproxy that it couldn’t verify the upstream cert and the emulator wouldn’t communicate properly when I tried accessing web pages served by the environment in a browser.

The solution turned out to be a startup switch ssl_insecure that turns off the upstream cert verification.

This did make me nervous though - I might trust my own environment, but the same proxy allows access to other sites from the emulator. I’m less trusting of those!

Once again, the documentation provides the solution. It’s possible to control what sites the proxy will handle and the Ignoring Domains section of the docs goes into this in detail. Rather than lsiting which domains to ignore, however, you can list just the domains you want to proxy using the allow-hosts switch and some regex.

To only proxy secure traffic from my internal environment’s domain and disable the certificate check, my command-line looks like this:

mitmproxy.exe --ssl-insecure --allow-hosts '^(.+\.)?mydomain.local:443$'

Pulling everything together

Now I have all the components I need, the steps I must follow are pretty straightfoward:

  1. Start the VMs in my virtual environment.
  2. Edit the DNS servers on my host’s network connection (physical or wifi) to have 127.0.0.1 as the first entry, followed by whatever was there before (most likely the IP address of my home router).
  3. Start DNSCrypt-Proxy.
  4. Start mitmproxy making sure to include the command-line switches I need.
  5. Fire up the Android Emulator.

When I’m done I can exit all the apps and shut down my environment, but I must remember to reset the DNS servers on my host.