Wednesday October 28, 2009
SASL support in libmemcached
In my previous blog entry I announced SASL support in the memcached server provided by Dustin Sallings. Support for SASL in the server might be a good thing to have for someone, but you need support for it in your driver in order to make use of it. Being a contributor to libmemcached, so I decided to add support for SASL there.
So how do the SASL code work? Well you enable it by calling memcached_set_sasl_callbacks with a number of callbacks (I'll get back to them shortly). Whenever libmemcached successfully connects to a server (and SASL is enabled) it will start to authenticate to the server, causing multiple packets to be exchanged between the client and the memcached server (this means that you should use persistent connections, but you know that already... didn't you?)
Let's skip the internals, and look at how you as a user should implement this. The first thing you need to do is to initialize libsasl, create (and initialize) an instance to libmemcached. Before you terminate your application you should also call the cleanup function in libsasl:
...
int main(int argc, char **argv)
{
if (sasl_client_init(NULL) != SASL_OK)
{
fprintf(stderr, "Failed to initialize sasl library!\n");
return 1;
}
memcached_st *memc = memcached_create(NULL);
memcached_server_st *servers = memcached_servers_parse(servers_list);
memcached_server_push(memc, servers);
memcached_server_list_free(servers);
memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
[ .... cut ... ]
sasl_done();
}
The next thing you need to do is to create a callback structure where you specify functions that libsasl can call when it wants authentication data from you (like username / password etc):
static sasl_callback_t sasl_callbacks[] = {
{
SASL_CB_USER, &get_username, NULL
}, {
SASL_CB_AUTHNAME, &get_username, NULL
}, {
SASL_CB_PASS, &get_password, NULL
}, {
SASL_CB_LIST_END, NULL, NULL
}
};
And we associate the callback structure with the memcached instance by calling:
memcached_set_sasl_callbacks(memc, sasl_callbacks);
So how does get_username and get_password look like? They may be as simple as:
static char *username = "username";
static char *passwd = "secret";
static int get_username(void *context, int id, const char **result,
unsigned int *len)
{
if (!result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME)) {
return SASL_BADPARAM;
}
*result= username;
if (len) {
*len= (username == NULL) ? 0 : (unsigned int)strlen(username);
}
return SASL_OK;
}
static int get_password(sasl_conn_t *conn, void *context, int id,
sasl_secret_t **psecret)
{
static sasl_secret_t* x;
if (!conn || ! psecret || id != SASL_CB_PASS) {
return SASL_BADPARAM;
}
if (passwd == NULL) {
*psecret = NULL;
return SASL_OK;
}
size_t len = strlen(passwd);
x = realloc(x, sizeof(sasl_secret_t) + len);
if (!x) {
return SASL_NOMEM;
}
x->len = len;
strcpy((void *)x->data, passwd);
*psecret = x;
return SASL_OK;
}
In order to try this out you need either libsasl or libsasl2 on your machine. The functionality is not merged into the development branch of libmemcached, so you will have to grab my development branch and compile from that. If you would like to follow the status for this feature you should monitor RFE 462250. You will find the branch that implements this feature at: https://code.launchpad.net/~trond-norbye/libmemcached/sasl_rfe_462250.
Happy hacking
Posted at 12:08AM Oct 28, 2009 by trond in Memcached | Comments[0]
Sunday October 25, 2009
SASL support in Memcached!
I got a merge request for adding SASL support to memcached from Dustin Sallings before the weekend. Luckily for me he had already went a couple of rounds back and forth with dormando fixing some details, so my job reviewing the code was pretty easy resulting in only one minor detail I wanted him to fix before I applied and pushed the patch. We need more documentation of the SASL support, so feel free to submit contributions!
The SASL support requires the binary protocol, so you cannot telnet to the port to test it out. If you enable SASL support, memcached will disable the ASCII protocol.
To build a memcached server with SASL support you need to pass --enable-sasl as an option to configure, and add -S as a parameter to memcached:
trond@storm > ./configure --enable-sasl [ ... cut ... ] checking sasl/sasl.h usability... yes checking sasl/sasl.h presence... yes checking for sasl/sasl.h... yes checking for library containing sasl_server_init... -lsasl2 [ ... cut ... ] trond@storm > ./memcached -S
Right now the only way to play with the SASL support is to test out a development build of the SPY memcached client, or by using the memcached-test program. I would suggest that you start bugging the maintainers of your favorite memcached driver asking for SASL support :-)
Posted at 11:08PM Oct 25, 2009 by trond in Memcached | Comments[5]
Tuesday October 20, 2009
Testing libmemcached on EC2
Someone pinged me yesterday about a problem he was seeing when he tried to run the test suite on Jaunty Ubuntu. The tests failed almost immediately in the following assertion:
value= memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE); assert(value > 0);
I guess I'm an old-school developer, because I want to use a debugger to hunt down bugs. For some reason the default setting in the shell was to disallow creation of corefiles, so I had to execute the following command to allow the corefiles to be written:
$ ulimit -c unlimited
Now that I was able to generate coredumps I wanted to create a "debug build" of libmemcached, because the optimizer may remove local variables etc. If I'm not able to reproduce the bug with a debug build, well then we have to debug the optimized binary. Why make life harder than it already is ;-) To create a debug build, simply invoke:
$ ./configure --with-debug
This didn't work however :-( Disabling the optimization (-O3) caused the compiler to spit out some new warnings, and we treat warnings as errors in libmemcached. It turns out that the gcc version installed on the machine was the old gcc 4.3.3, and not one of the more recent 4.4 series. I've been struggling with different problems with gcc lately (mostly that it generate bogus warnings on C99 struct initializers), so I cannot say I was too happy about "yet another compiler problem". The code it complained about was:
unlikely (ptr->flags & MEM_USE_UDP)
With the following warning:
error: conversion to ‘long int’ from ‘uint32_t’ may change the sign of the result [-Wsign-conversion]
MEM_USE_UDP is an enum, and that's an integer according to C99 (see section 6.4.4.3), and flags is defined as an uint32_t. So yes, we are doing a bitwise and on an unsigned and a signed 32 bit word. But we are only testing if the value is 0 or not, so the sign doesn't matter at all!!! Just for the fun of it I decided to replace unlikely with a normal if (you might have had fun with the broken ntohX-macros on Linux generating warnings all of the time, so I guessed this could be a similar problem), and guess what: The warning is gone :-) So I went ahead and replaced all occurrences of unlikely with if... Not the thing you would like to do at 1:30AM :(
With the debug build available I could return to the original problem. I had been looking at the code, and my guess was that it was failing in getsockopt in the following snippet:
int sock_size;
socklen_t sock_length= sizeof(int);
/* REFACTOR */
/* We just try the first host, and if it is down we return zero */
if ((memcached_connect(&ptr->hosts[0])) != MEMCACHED_SUCCESS)
return 0;
if (getsockopt(ptr->hosts[0].fd, SOL_SOCKET,
SO_SNDBUF, &sock_size, &sock_length))
return 0; /* Zero means error */
return (uint64_t) sock_size;
I enabled a breakpoint on the line containing return 0; (so that i could look at errno) and ran the program, but guess what: It didn't fail! So it had to be a problem with memcached_connect. It turned out that this is a race-condition in the test suite, because the test program just starts up the memcached servers and start using them immediately. The memcached servers isn't done initializing themselves (and binding to the specified port) yet, so test fails to bind to the servers.
There are a number of small bugsI am going to fix in the test framework as a result of this:
I guess it's no secret that I really prefer software development using Solaris (and all of the great tools there), so if you are planning to do development on your EC2 image I would suggest that you start off with an OpenSolaris image instead (Check out http://blogs.sun.com/ec2/). That will give you easy access to a lot of great tools I cannot live without (dtrace, dbx, cc etc), and using the right tool for the task saves a lot of time!!! As an extra bonus you can use the DTrace probes I added to memcached to collect more information on what your memcached server is doing. Matt Ingenthron took this a step further in a demo by using the output from DTrace as an input feed to a browser.. I don't remember the link, but you should be able to Google it :-)
Posted at 10:21AM Oct 20, 2009 by trond in Memcached | Comments[1]
Saturday October 17, 2009
Bazaar shared repositories and Hudson build slaves
I created a Bazaar plugin for Hudson a while back, and we have been using that with great success on our Hudson build farm to build Drizzle, libmemcached and Gearman.
If you look at our build farm you will see that we compile the same source project multiple times on the same slaves, only with a variation in the configuration (compiler, 32/64 bit etc). With the normal configuration you will check out the complete repository for each of the projects, using a lot of bandwidth and disk space. Disk is cheap these days so I don't care that much about that, but my bandwidth is limited so I would like to reduce that if possible.
If we look at the disk layout on the build slaves it perfectly suited for using Bazaar shared repositories:
$SLAVE_HOME/workspace/
So if we create a Bazaar shared repository in $SLAVE_HOME/workspace, all of our Bazaar projects will store the upstream information in $SLAVE_HOME/workspace/.bzr. Not only will this dramatically reduce the disk footprint, it will also reduce the amount of data being downloaded. So how do you set up a shared repository? It's no magic, simply log into the build slave and run the following command:
trond@storm > bzr init-repository $SLAVE_HOME/workspace
Unfortunately there is a "bug" in Hudson causing Hudson to nuke the directory every day when it is doing housekeeping of the workspace directory. Luckily for us there is a simple workaround for the issue, simply create a project named ".bzr".
Update: Hudson will also nuke the directory if it hasn't been modified on a time period, and it seems that bazaar doesn't update the modified date on the .bzr directory. A simple workaround for this is to install the following cron job:
1 1 * * 0 /usr/bin/touch /home/hudson/hudson/workspace/.bzr
Posted at 09:53AM Oct 17, 2009 by trond in OpenSolaris | Comments[0]
Wednesday October 07, 2009
memcapable, part two
Today I added support for the ASCII protocol into memcapable so that it may be used to test both the binary and the ASCII protocol. By running it on the example server I added with the protocol parser in libmemcached I discovered that it failed all tests (mostly due to incorrect handling of noreply, but that is another story). It is not merged into trunk yet, so if you want to play with it today you need to branch lp:~trond-norbye/libmemcached/bugparade.
This is the result from running it on the server in my sandbox:
trond@storm> ./memcapable ascii quit [pass] ascii version [pass] ascii verbosity [pass] ascii set [pass] ascii set noreply [pass] ascii get [pass] ascii gets [pass] ascii mget [pass] ascii flush [pass] ascii flush noreply [pass] ascii add [pass] ascii add noreply [pass] ascii replace [pass] ascii replace noreply [pass] ascii cas [pass] ascii cas noreply [pass] ascii delete [pass] ascii delete noreply [pass] ascii incr [pass] ascii incr noreply [pass] ascii decr [pass] ascii decr noreply [pass] ascii append [pass] ascii append noreply [pass] ascii prepend [pass] ascii prepend noreply [pass] ascii stat [pass] binary noop [pass] binary quit [pass] binary quitq [pass] binary set [pass] binary setq [pass] binary flush [pass] binary flushq [pass] binary add [pass] binary addq [pass] binary replace [pass] binary replaceq [pass] binary delete [pass] binary deleteq [pass] binary get [pass] binary getq [pass] binary getk [pass] binary getkq [pass] binary incr [pass] binary incrq [pass] binary decr [pass] binary decrq [pass] binary version [pass] binary append [pass] binary appendq [pass] binary prepend [pass] binary prependq [pass] binary stat [pass] binary illegal [pass] All tests passed
I just discovered while reading the spec one more time today that some of the tests are not according to the spec, so I am going to submit bug reports on the community server and fix the tests:
verbosity should always return OK, but does not if it encounter illegal number of optionsflush_all does not fail for illegal optionsdelete a b does not fail, but assumes that the second option "noreply"Do you see any other bugs in the ASCII protocol handling in the community server?
Posted at 03:40PM Oct 07, 2009 by trond in Memcached | Comments[0]