This is a guide for anyone using Xamarin and whose machine cannot reach a macOS build host by SSH on port 22.
In my case, I have Xamarin installed on a Windows machine and a macOS guest hosted in QEMU/KVM on a Ubuntu laptop. The Ubuntu laptop is connected to the network through its wireless adapter.
Wireless adapters cannot bridge to KVM guests, so it is not possible to SSH directly from the Windows machine to the macOS guest.
Since Xamarin needs SSH to reach macOS, I use a bag of networking tricks including a reverse SSH tunnel, NAT, and port forwarding to enable Xamarin to reach the macOS guest.
Disclaimer: Installing macOS on anything but Apple hardware violates the macOS license agreement. Proceed at your own risk.
On Host 1, install Windows 10 and Visual Studio 2019 with Xamarin
On Host 2, install Ubuntu, KVM and a macOS guest (Host 3): See here.
On Host 3, install Xcode and Visual Studio for Mac
You should now have the following topology. The IP addresses can of course be different than mine.
Host 1: Windows 10 1809 Running Visual Studio
Host 2: Ubuntu 18.04 LTS running Host 3 in KVM, connected to wifi
IP: 192.168.122.1 (NAT with Host 3)
Host 3: OS X Mojave virtual machine
IP: 192.168.122.242 (NAT with Host 2)
Xamarin communicates with Xcode over SSH, so we need to be able to SSH from Windows to the macOS guest.
On macOS, SSH is installed but not running. Navigate to System Preferences → Sharing → check Remote Login. Optionally setup public key auth. See here.
Test that macOS can reach reach Windows.
macOS => Windows: ssh firstname.lastname@example.org
Windows => macOS: Not yet (Windows cannot reach macOS behind NAT)
In macOS, create a reverse SSH tunnel from port 22 to a free port in Windows, e.g. 40322.
ssh -R 40322:localhost:22 email@example.com
Never before was I glad to encounter a PowerShell prompt.
Leave this connection open and proceed to the next step.
On Ubuntu, open the port which Windows will go through to reach macOS.
sudo ufw allow 40322
In Windows, test the connection to macOS. Notice that to get to macOS, you SSH from Windows to Windows. The tunnel we created before at port 40322 then takes you to macOS.
ssh macosuser@localhost -p 40322
If the connection succeeds, you’ll be greeted by a shell in macOS.
This was just a test. It is not necessary to leave this connection open to proceed.
The Xamarin Mac Agent can only connect on port 22, but the macOS host is on 40322.
In Windows, in an elevated command prompt or PowerShell instance, forward port 22 to 40322.
netsh interface portproxy add v4tov4 listenport=22 listenaddress=127.0.0.1 connectport=40322 connectaddress=127.0.0.1
In Windows, in Visual Studio, connect to the macOS Xcode server. You’ll have to use user/pass auth.
User: (macOS user)
Pass: (macOS password)
Xamarin may ask to install a few things on the macOS host.
Please feel free to start a discussion on GitHub.