Google Analytics

Friday, February 21, 2014

Making VMs play nicely with hosts (by passing URLs)

I've recently moved to Linux (currently Mint) as my primary desktop at work and I need to run a Windows VM in order to use Outlook.  I'm running Outlook 2013 in Unity mode and kept getting aggravated that when I clicked links it would open the link in Chrome on the VM and not in Chrome on my host OS.

 When VMWare Tools installed, it created a registered application named "Default Host Application" that is apparently used for VMware Fusion on OS X and is useless on a Linux or Windows host.  VMware Tools would give the following error:

Make sure the virtual machine's configuration allows the guest to open host applications.

After searching high and low for a solution other than to make it go away, I gave up and wrote my own.  Here's the idea:

Set up a listener on the host OS that will just listen for a client and pass anything that it gets to Chrome so it will open a new tab.  Now we'll set up a client on the VM to forward any URLs that it receives to the listener on the host.  And finally, we'll configure our client on the VM to be the default for all HTTP/HTTPS URLs.  Pretty simple, right?  Oh, and we'll use Perl so it will work with Windows, OS X, or Linux on the host OR the guest.

Let's get started by installing Perl on your Windows VM.  OS X and Linux should have it already.

So we'll start with the server code.  I borrowed the majority of this form a few sites online and tweaked it to my requirements.

VM_URL_Passer_Server.pl
#!/usr/bin/perl
use IO::Socket::INET;

# auto-flush on socket
$| = 1;

# creating a listening socket to listen to port 7777 on all IP addresses.
my $socket = new IO::Socket::INET (
    LocalHost => '0.0.0.0',
    LocalPort => '7777',
    Proto => 'tcp',
    Listen => 5,
    Reuse => 1
);
die "cannot create socket $!\n" unless $socket;
print "Running on port 7777\n";

while(1)
{
    # waiting for a new client connection
    my $client_socket = $socket->accept();

    # get information about a newly connected client
    my $client_address = $client_socket->peerhost();
    my $client_port = $client_socket->peerport();
    #print "connection from $client_address:$client_port\n";

    # read up to 1024 characters from the connected client
    my $data = "";
    $client_socket->recv($data, 4096);
    #print "received data: $data\n";
    
    #Put your path to Chrome here!!#
    system("'/usr/bin/google-chrome' \"$data\"");

    # write response data to the connected client
    $data = "ok";
    $client_socket->send($data);

    # notify client that response has been sent
    shutdown($client_socket, 1);
}

$socket->close();

Simply change line 34 to point to Chrome.app on OS X or Chrome.exe on Windows.  Running this script will invoke Perl and it will sit listening for connections.

url_tcp_client.pl
#!/usr/bin/perl
use IO::Socket::INET;

# auto-flush on socket
$| = 1;

# create a connecting socket
my $socket = new IO::Socket::INET (
    PeerHost => '255.255.255.255',
    PeerPort => '7777',
    Proto => 'tcp',
);
die "cannot connect to the server $!\n" unless $socket;
print "connected to the server\n";

# data to send to a server
my $req = "$ARGV[0]";
my $size = $socket->send($req);
#print "sent data of length $size\n";

# notify server that request has been sent
shutdown($socket, 1);

# receive a response of up to 1024 characters from server
#my $response = "";
#$socket->recv($response, 1024);
#print "received response: $response\n";


$socket->close();

Running this script on your guest VM with any arguments will pass the string to the host and attempt to open it with Chrome.  Simply replace 255.255.255.255 on line 9 with the IP address of your host and you should be in business.  I've left some debugging code in there so if you have problems you can uncomment it and try again.


Finally, we need to register our client script as the default HTTP, HTTPS, and FTP handler in our Windows VM.  Start by saving this to install.reg, modifying it to point to the location that you've saved the client script (I used the root of the C: drive) as well as the path to Perl, and importing it.  It is safe, but you should know what you are doing if you are modifying the registry.

Install.reg
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\dhowdy\VMURLPasser\Capabilities]
"ApplicationDescription"="VM URL Passer is a Perl TCP client that sends all clicked URLs to the specified TCP Server in order to open links clicked in a VM to the host."
"ApplicationIcon"="C:\url_tcp_client.pl,0"
"ApplicationName"="VM URL Passer"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\VMURLPasser\shell\open\command]
@="\"C:\\Perl64\\bin\\perl.EXE\" \"C:\\url_tcp_client.pl\" \"%1\""


[HKEY_LOCAL_MACHINE\SOFTWARE\dhowdy\VMURLPasser\Capabilities\URLAssociations]
"ftp"="VMURLPasser"
"http"="VMURLPasser"
"https"="VMURLPasser"
"irc"="VMURLPasser"
"mailto"="VMURLPasser"
"mms"="VMURLPasser"
"news"="VMURLPasser"
"nntp"="VMURLPasser"
"sms"="VMURLPasser"
"smsto"="VMURLPasser"
"tel"="VMURLPasser"
"urn"="VMURLPasser"
"webcal"="VMURLPasser"


[HKEY_LOCAL_MACHINE\SOFTWARE\RegisteredApplications]
"VM_URL_Passer"="Software\\dhowdy\\VMURLPasser\\Capabilities"


[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\VMURLPasser]
@="VM URL Passer Document"
"URL Protocol"=""



[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\VMURLPasser\shell]


[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\VMURLPasser\shell\open]

Now that our program is "installed" we need to tell Windows to use it.  Open Control Panel > Default Programs > Set Default Programs.  Find "VM URL Passer" in the list, click "Choose defaults for this program" and check the protocols you wish to have passed to Chrome on your host.  I chose most of them, but the important ones are FTP, HTTP, and HTTPS.



Click "Save" and you should be good to go!  Try clicking a link in Outlook on your guest VM or just open Start > run > type 'http://dhowdy.blogspot.com' and press enter.  It should open in Chrome on your host!


Disclaimer:  This was a quick and dirty hack and there is no security at all.  Anyone on your network can send anything to your browser if they find that you have that port open.  Use this with caution and if anyone has suggestions for improvement, feel free to post below!