So recently I’ve been working on implementing on a driver for Windows receiver. Its basically to setup a virtual channel between the receiver and our custom App-V application launcher on the VDA. Specifically. I’ve had the idea for a long time because I’d like to port the functionality that XenApp 6.5’s ‘offline’ App-V functionality had where you can use the local/native App-V client on the receiver machine to launch the application if you’ve got the application and App-V client locally – instead of launching the application remotely via the ICA protocol, effectively running the app-v application on the VDA(remote server) and then remoting it to the client workstation via ICA and Receiver.
The idea is basically to establish a channel of communication between receiver attempting to launch the app-v application via ICA and the process that usually launches those app-v application on the VDA. When the launcher indicates that it can and should preferably offload the launching to the app-v client on the receiver – it will tell receiver this and in response, receiver will instruct the App-A client to launch locally. If the package is not available, the channel will provide the path to the app-v package and this can then be used to locate and ‘add’ the package before locally publishing and launching it locally on the receiver machine.
The cool thing is that the whole Virtual Channel SDK is freely available from Citrix, so I was able to implement a rudimentary driver and channel that will send/receive a basic XML-type protocol back and forth between the host (VDA application) and the client (receiver driver). The samples are all written in C which is great because this fits me well. The only issue I’ve noticed about this is that the documentation has said that we should be using the Visual Studio 2010 toolset which uses C 89 standard which is sucky – like you can’t do joint variable declaration and assignments in one statement and you need to declare all your variables before you use them. Sucky. But it works for now, and I’m working on moving to C++ but still use the C subset to keep the code simple:I find using C++ only constructs early on makes things unnecessarily complex, particularly when its not complex to to begin with.
The documentation is pretty good, and its a whole lot better than the Fast Connect SDK that I’ve worked on before.
On of the interesting aspects of allowing app-v applications to “stream-to-client” which is what its called really in the XenApp 6.5 world, is what if the receiver client machine does not have access to the app-v package to have it run locally? Do you fall back to the “stream-to-server” mechanism and then have the app-v application launched on the VDA and remoted as per usual? Also what if its not a good idea to launch an app-v application locally on the receiver endpoint, even if you can – some sort of security policy or something. Clearly perhaps as part of the communication between the receiver endpoint and the VDA is reading/interpreting policy information from the XenDesktop environment about what should and should not be done.
The other always interesting aspect of networking, that I found while developing the named pipe between the broker plugin and the App-V service, is how to correctly arrange for the parties involved in the communication(both endpoints) to know how much data is available to read when data is sent from one side to the receiving side. Usually, you need to send a packet upfront which gives the receiving end a clue on how much data it is to expect from the sender – so it knows when to stop reading. This is the case when implementing raw BSD sockets and using named pipes in Windows. Though, unusually with the Citrix Virtual Channel SDK, the client driver always gets all the data provided to it, when its send from the server(VDA), it doesn’t require that you code into the protocol that a certain amount of bytes is to be expected – this seems to be coded for you in the WinStation driver engine embedded in the Receiver engine. Basically in the client driver you get a call-back called with all the data send by the server:
static void WFCAPI ICADataArrival(PVD pVD, USHORT uchan, LPBYTE pBuf, USHORT Length);
So you’ll get Length amount of data in pBuf. Thats it, you just deal with that data in pBuf.
However on the server side, you don't have this luxury meaning that any data send from the client, needs to first send an indication of how much data will be sent, then send that amount of data. So when it comes to responding to the server(VDA), the driver needs to send a response packet and also a ‘data’ packet which was described by the response packet. This is so that the server knows that it should stop reading. Quite interesting about the not needing that when going in the opposite direction!
So on the client, you need to do a read the ‘prepacket’ in this case which indicates the size of the upcoming data form the client. Then once, you’ve got that, then you need to read only that amount of data. The SendReceiveBuffer will contain the upcoming data. That structure is quite interesting too – see it later.
//Read Response // A response copnsists of two packets sent from the client.(PrePacket and a SendReceiveBuffer) // The Prepacket contains the length of data in the SendReceiveBuffer that is sent next // Read prepacket rc = WFVirtualChannelRead(hVC, VC_TIMEOUT_MILLISECONDS, (PCHAR)&prepacket, sizeof(Prepacket), &ulBytesRead); if (rc != TRUE) { _tprintf(_T("Could not receive message from virtual channel.\n")); PrintMessage(GetLastError()); SafelyExit(pBuffer, hVC); return 0; } // Read actual data rc = WFVirtualChannelRead(hVC, VC_TIMEOUT_MILLISECONDS, (PCHAR)pBuffer, prepacket.length, &ulBytesRead); if (rc != TRUE) { _tprintf(_T("Could not receive message from virtual channel.\n")); PrintMessage(GetLastError()); SafelyExit(pBuffer, hVC); return 0; } _tprintf(_T("Received Message (%s) of (%lu) bytes from virtual channel.\n"), pBuffer->payload, ulBytesRead); SafelyExit(pBuffer, hVC);
The ‘packets’ looks like this:
#pragma pack(1) typedef struct Prepacket { int length; } Prepacket; /* AppV buffer format*/ typedef struct SRB { int length; char payload[1]; } SendReceiveBuffer, *PSendReceiveBuffer;
Yes, this is just illustrative and I could probably consolidate this into just a SendReceiveBuffer and ditch the PrePacket. So anyway the client sends a prepacket and a SendReceiveBuffer as a response to the ‘host’(VDA).
One thing that can happen is that the amount of data that you want to send exceeds the number that can beheld by the int length in the Prepacket. There are some interesting ways to solve this problem. Such as determining the size of your intended data to send, and then determine how many bytes it would take to represent and store that number, and then send that number to the receiving side, then the receiving side will know it should read that many bytes into a buffer(and fetch that many bytes) and then interpret that buffer as a number – now it knows how much to receive, irrespective of the the length variable and also means you can send/receive in theory any number of bytes wihout having to artificially limit the size of the data in your protocol for example.