Sunday, February 24, 2013

Remote Command

I know, this screenshotting thing isn't really my thing... Or GUIs, rather. I like to focus on the functionality, and do UIs later. If I ever need to.

Anyway, this is a Unix server, remotely controlled by an Android client app over TCP/IP. You give the server a list of commands, and use the client to pick a command to execute. The server then executes the command, and returns an exit value. Quite simple stuff. The screenshots gives you an idea of how it works.

Server configuration file
Server startup
Server help screen
Server list of commands

Server after execution of a few commands
tensecs script
Client startup
Client setup


Client after refresh
Client command list

Client after a few executions
First, you configure the server by creating a configuration file. By default it is named server.cfg and is found in the same directory as the server is run. Alternatively you use the -c flag to specify a different name and location.

The configuration file has a simple layout that is frequently seen on Unix systems (see screenshot). Blank lines (that is, empty lines or lines containing only whitespaces) are ignored, as are comments that start with a hash (#) symbol. Otherwise you use the very common key-and-value syntax, where a key in this case is either 'ip', 'port' or 'command', and values are specified by double quotes. ip and port are mandatory; if they are not found, the server will not start. It's quite self-explanatory, but the ip and port specify what address and port to listen to, and if they cannot be bound to a socket, the server will close down. command specify a command that you will want the client to be able to execute. Naturally, you can enter as many commands as you would like.

By pressing 'h' you get some help messages (as seen in the screenshot), and by pressing 'l' (see another screenshot) you get the active list of commands. Here you can also choose to execute commands locally if you wish, perhaps for debugging purposes.

And so, the client. First, press 'setup' to configure the server IP address and port, then go back to the main window, by pressing save, and in the main window press 'refresh'. You should now see the very same log in the 'Remote' window on the client as you see in the 'Log' window on the server.

This function (refresh) is what you need to use every time you want to see whether something has occur on the server. The connection to the server closes down after each refresh is made, so the server has no means of informing the client that something has happened; it is something you need to find out for yourself, by pressing 'refresh'.

After having pressed 'refresh', you can choose a command to execute by pressing 'command'. When doing so, the client just asks the server to run the command. It doesn't even receive a reply. You need to press 'refresh' again to see what happened on the server, if the command was indeed executed, and if so, returned with what exit value.

I chose this somewhat unsophisticated networking approach this time, partly due to the simplicity of the code but also for practical purposes. I didn't want to stay connected for longer than necessary, since the client is run on a mobile device where a longer time and more data transfer means higher costs for the user, and it might indeed take long before an execution of a command returns. It's perfectly conceivable that another server is subsequently started by this server, and so might not return for an extended period of time.

The execution of commands on the server is threaded. That is, even if a command doesn't return immediately, the server continues to be usable for other requests. This is illustrated on the last screenshot, where a command named './tensecs' is executed and only returned after a few other commands have been executed and returned in between.

However, the networking is not threaded. I didn't think that was necessary due to the very short time that a connection stays active.

So, where to go from here?

Naturally, it could be extended in a million ways. The first thing that comes to mind would be a nice way for commands to return an output string back to the debug window on the server. Don't know exactly how this should be done, but maybe one could use a Unix pipe? This is probably the first thing that should be improved later.

Maybe, also, the networking should be redesigned. One could have the option of letting the socket stay connected until the app closes down or the screen goes blank, or something similar. Encryption should definitely be implemented. As I understand it, SSL encryption is not very hard to do in C or Java/Android, so that could be a good fix for that.

I had a friend of mine suggesting that the client should be made a plugin for a popular Android app called Tasker. That should also be looked at. A better-looking GUI would be nice too, I suppose.

Oh, and I have to whine about something too! I had a memory leak during the development of the server that took some time to track down. (Thanks Elvis for the help with that!) During the debugging I discovered that ncurses apparently does not free all the memory it allocates! Making a test application doing only a initscr() and endwin(), and I saw that there's still memory that could be free()'d! The FAQ of ncurses apparently states that this is normal and notes "That makes it hard to analyze curses applications for memory leaks." Yeah, no shit! But you could recompile the library with some debugging options in order to force it to free its resources. I dunno, but to me that seems like something that should be a default option...

Anyway, for now it the way I needed it to. All I wanted was to be able to have a simple means of controlling my Linux server from my Android phone, and that's exactly what this is!

And again, the source code for both the server and client will be published on my website shortly!

David

No comments: