Trond Norbye's Weblog

« Previous month (Aug 2009) | Main | Next month (Oct 2009) »

http://blogs.sun.com/trond/date/20091028 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

http://blogs.sun.com/trond/date/20091025 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 :-)

http://blogs.sun.com/trond/date/20091020 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:

  • Don't leave behind running memcached servers if the test suite fails
  • Let the memcached server choose an available port itself (so that we can run make test from multiple users at the same time)
  • Wait for the servers to initialize themselves before utilizing them

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 :-)

http://blogs.sun.com/trond/date/20091007 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 options
  • flush_all does not fail for illegal options
  • delete 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?


Valid HTML! Valid CSS!

This is a personal weblog, I do not speak for my employer.