Categories

Access VNC Console of a Citrix Xen VM using Xen SDK

My goal is to access the console of a Citrix Xen virtual machine. Okay, there is the XenCenter… but it’s very slow. XenServer use VNC by default for the virtual machine console: can we use any VNC viewer? more or less…

More because you can use any VNC viewer supporting the version 3.3 of RFB protocol. Less because you need to open an HTTP tunnel using the CONNECT verb in an illegal way. No standard viewer supports this type of connection. Citrix provides a Java applet which take a console URI and a session id as parameter. The URI can be found using the command:

1
xe console-list vm-name-label=myvm params=location

Which give you:

1
location ( RO)    : https://<server>/console?ref=OpaqueRef:<uuid>

The session id is more hard to find… and I didn’t find how to have one. And I don’t want to find a way because I will have to create as many HTML page as I have virtual machines… this sounds bad.

Let’s try another way: using the Xen SDK to create a simple program which connect a standard VNC viewer to the console of a virtual machine. This tools will takes the XenServer hostname (or IP), the user and password to connect to and the virtual machine name. The “Retrieving VNC consoles via the API” section from the Xen SDK Manual gives all the information to retrieve to console URI:

1.  Client to Master/443: XML-RPC:Session.login_with_password().
2. Master/443 to Client: Returns a session reference to be used with subsequent calls.
3. Client to Master/443: XML-RPC:VM.get_by_name_label().
4. Master/443 to Client: Returns a reference to a particular VM (or the “control domain” if you want to retrieve the physical host console).
5. Client to Master/443: XML-RPC:VM.get_consoles().
6. Master/443 to Client: Returns a list of console objects associated with the VM.
7. Client to Master/443: XML-RPC:VM.get_location().
8. Returns a URI describing where the requested console is located. The URIs are of the form:https://192.168.0.1/console?ref=OpaqueRef:c038533a-af99-a0ff-9095-c1159f2dc6a0 .

In Java words this gives:

1
2
3
4
5
6
7
8
9
10
11
12
13
URL url = new URL("http://" + host);
Connection connection = new Connection(url);
Session.loginWithPassword(connection, user, password, APIVersion.latest().toString());
try {
    Set<VM> vms = VM.getByNameLabel(connection, vmName);
    VM vm = vms.iterator().next();
    Set<Console> consoles = vm.getConsoles(connection);
    Console console = consoles.iterator().next();
    String location = console.getLocation(connection);
}
finally {
    Session.logout(connection);
}

The “location” variable contains the console URI. Finally to retrieves the VNC stream, we just need to do:

9. Client to 192.168.0.1: HTTP CONNECT “/console?ref=(…)”
The final HTTP CONNECT is slightly non-standard since the HTTP/1.1 RFC specifies that it should only be a host and a port, rather than a URL. Once the HTTP connect is complete, the connection can subsequently directly be used as a VNC server without any further HTTP protocol action.

And the XenServer answer by a 500 HTTP error. Huh… what’s missing?

The manual lacks explanations for this final step. I need to use the CONNECT verb (not HTTP CONNECT), followed by the path and query part of the console URI (“/console?ref=OpaqueRef:…”), followed by “HTTP/1.0″. I also need to provide some authentication information. I can use the session id by adding a “sesssion_id” parameter to the console URI or providing it as a “session_id” cookie. But I also need to find a session id… remember: I don’t know how. Another way is to use the Basic Authentication Scheme of the HTTP protocole. To do so I have to base64-encode the user name and the password as “user:password”. Then I add an Authorization header to the query.

So, the HTTP query is:

1
2
CONNECT /location?ref=OpaqueRef:<uuid> HTTP/1.0
Authorization: Basic <token>

Using Java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Socket socket = ... // Open a SSL socket to the host (port 443)
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();

StringBuilder h = new StringBuilder();
h.append("CONNECT ");
h.append(path);
h.append(" HTTP/1.0\r\n");
h.append("Host: ");
h.append(host);
String auth = Base64.encode(user + ":" + password);
// I use the Apache implementation of base64 which add a "\r\n" sequence at the end
h.append("\r\nAuthorization: Basic ");
h.append(auth);
h.append("\r\n");

byte[] b = h.toString().getBytes();
out.write(b);
out.flush();

The XenServer anwser by a 200 response code, like that:

1
HTTP/1.1 200 OK

This answer can be read by:

1
2
3
4
5
6
7
String line = readline();
if (!line.matches("^HTTP/1\\.1 200 OK\r\n$")) {
    throw new UnexpectedException("Unexpected answer : " + line);
}
while (!"\r\n".equals(line)) {
    line = readline();
}

Once the HTTP response headers read, the VNC connection is made and can be directly used by any VNC client.

Comments are closed.