[Devel] SRV module

Stéphane Alnet stephane at carrierclass.net
Thu May 10 06:12:01 CEST 2007


Hello,

This is my first attempt at a module for OpenSER. This module is aimed
at providing outbound (from OpenSER) priority/load-balancing for SRV
records. I don't have a compilation environment at hand so I haven't
tried to compile this code yet; I'm mostly looking for feedback on how
well-behaved this code looks to people on the list, and whether there
are SIP-related issues I didn't take care of.

The basic algorithm for srv_branches() is as follows:
- from the message URI, obtain the host (domain) part and use it to do
a SRV query;
- use the records returned by the SRV query to build a list of
branches, ordered by SRV priority (strict) and by somewhat-weighted
randomized priority (if weights) or randomized priority (if all
weights = 0).
In a script, a call to srv_branches() would be followed by
serialized_branches() in order to get the proper result; there is a
snippet script at the top of the srv.c file.

I can envision at least two enhancements to the module: (a) support
hash (as in the dispatcher module) instead of using random() for
target selection; (b) also support NAPTR -- but neither of these are
critical to my application at this time.

Thank you for your feedback. I am not subscribed to the list, so
please Cc: me in your replies.
Stéphane

-- 
http://carrierclass.net/
http://www.linkedin.com/in/stephalnet
-------------- next part --------------
/**
 * $Id$
 *
 * Copyright (C) 2007 Stephane Alnet  (stephane at carrierclass.net)
 *
 * This file is part of openser, a free SIP server.
 *
 * openser is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * openser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#include "../../parser/parse_uri.h"
#include "../../dset.h"
#include "../../resolve.h"
#include "../../error.h"
#include "../../mem/mem.h"

/**
 *  Usage:
 *
 *  route[]
 *  {
 *    ...
 *       setport("0");         # Force SRV resolution
 *       srv_branches();       # Resolve SRV as branches
 *       serialize_branches(); # Serialize the branches
 *       t_relay();
 *    ...
 *  }
 *
 *  failure_route[1]
 *  {
 *      ...
 *      next_branches();       # Next serialized branch
 *  }
 *
 **/


MODULE_VERSION

/** parameters */
/* int foo=4096; */

/** module functions */
static int mod_init(void);
static int child_init(int);

static int srv_branches(struct sip_msg*, char*, char*);

static int srv_branches_fixup(void** param, int param_no); 

void destroy(void);

static cmd_export_t cmds[]={
	{"srv_branches",  srv_branches,  0, srv_branches_fixup, REQUEST_ROUTE | FAILURE_ROUTE |
		ONREPLY_ROUTE | BRANCH_ROUTE},
	{0,0,0,0,0}
};


static param_export_t params[]={
	/* {"foo",     INT_PARAM, &foo}, */
	{0,0,0}
};


/** module exports */
struct module_exports exports= {
	"srv",
	cmds,
	params,
	0,          /* exported statistics */
	mod_init,   /* module initialization function */
	(response_function) 0,
	(destroy_function) destroy,
	child_init  /* per-child init function */
};

/**
 * init module function
 */
static int mod_init(void)
{
	DBG("SRV: initializing ...\n");
	return 0;
}

/**
 * Initialize children
 */
static int child_init(int rank)
{
	DBG("SRV: init_child [%d]  pid [%d]\n", rank, getpid());
	return 0;
}

/*
 * q = MIN_Q ... MAX_Q  (lower q = higher priority)
 *
 * rdata->priority = 0-65535 [RFC2782] lower value = higher priority (think MX)
 * rdata->weight   = 0-65535 [RFC2782] algorithm:
 *      To select a target to be contacted next, arrange all SRV RRs
 *      (that have not been ordered yet) in any order, except that all
 *      those with weight 0 are placed at the beginning of the list.
 *      
 *      Compute the sum of the weights of those RRs, and with each RR
 *      associate the running sum in the selected order. Then choose a
 *      uniform random number between 0 and the sum computed
 *      (inclusive), and select the RR whose running sum value is the
 *      first in the selected order which is greater than or equal to
 *      the random number selected. The target host specified in the
 *      selected SRV RR is the next one to be contacted by the client.
 *      Remove this SRV RR from the set of the unordered SRV RRs and
 *      apply the described algorithm to the unordered SRV RRs to select
 *      the next target host.  Continue the ordering process until there
 *      are no unordered SRV RRs.  This process is repeated for each
 *      Priority.
 */

/*
 * Here we implement a slightly different algorithm.
 */
 
struct branch_def {
    str         uri;
    unsigned short priority; /* from SRV */
    unsigned short weight;   /* from SRV */
    unsigned order;          /* for random selection */
    /* order is either a random value, or weight*random value if weight > 0 */
};

/*
 */
int compare_branches( struct branch_def* a, struct branch_def* b )
{
    /* Lower priority = preferred */
    if( a->priority < b->priority ) return -1;
    if( a->priority > b->priority ) return 1;

    /* Elements with 0 weight should be last */
    if( a->weight == 0 && b->weight >  0 ) return 1;
    if( a->weight >  0 && b->weigth == 0 ) return -1;

    /* Either both are 0 weight or both are non-zero weight */
    if( a->order < b->order ) return -1;
    if( a->order > b->order ) return 1;
    return 0;
}

/* Maximum number of SRV records we will consider */
#define MAX_BRANCHES    20

/* Should replace this with something similar to the hash functions in dispatcher */
static unsigned long random_msg(struct sip_msg* msg)
{
    return random();
}

static int srv_branches(struct sip_msg* msg, char* frm)
{
    /* Should this be static or not? */
    static branch_def   branches[MAX_BRANCHES];
    str luri;
    struct sip_uri uri;
    str name;
    struct rdata *head;
    struct rdata *rd;
    short ncount;

    /* Select the RURI to use for SRV resolution */
    if (msg->new_uri.s)
            luri = msg->new_uri;
    else
            luri = msg->first_line.u.request.uri;

    if (luri.len > MAX_URI_SIZE - 1) {
            LOG(L_ERR, "ERROR: srv_branches: too long uri: %.*s\n",
                    luri.len, luri.s);
            return -1;
    }

    if( parse_uri(luri.s,luri.len,&uri) < 0 ) {
        LOG(L_ERR, "ERROR: srv_branches: invalid uri: %.*s\n",
                luri.len, luri.s);
        return -1;
    }

  	/* Check port == 0 */
    if( uri.port_no != 0 ) {
        LOG(L_ERR, "ERROR: srv_branches: uri %.*s port is not zero\n",
                luri.len, luri.s);
        return -1;
    }

    /* Check proto */
    if( uri.proto == PROTO_NONE ) {
        LOG(L_ERR, "ERROR: srv_branches: uri %.*s protocol is not known\n",
                luri.len, luri.s);
        return -1;
    }

	/* Get the domain name from the RURI */
    name = uri.host;
    if (name->len >= MAX_DNS_NAME) {
            LOG(L_ERR, "ERROR:srv_branches: domain name too long\n");
            return 0;
    }

    if ((name->len+SRV_MAX_PREFIX_LEN+1)>MAX_DNS_NAME) {
            LOG(L_WARN, "WARNING:srv_branches: domain name too long (%d),"
                    " unable to perform SRV lookup\n", name->len);
            return 0;
    }

    /* Build the SRV name */
    {
        char* tmp;
        tmp = pkg_malloc(MAX_DNS_NAME);
        if (!tmp){
            LOG(L_ERR, "ERROR: srv_branches: memory allocation failure\n");
            return E_OUT_OF_MEM;
        }
        memcpy(tmp, name->s, name->len);
        tmp[name->len] = '\0';

        switch (*proto) {
                case PROTO_UDP:
                        memcpy(tmp, SRV_UDP_PREFIX, SRV_UDP_PREFIX_LEN);
                        memcpy(tmp+SRV_UDP_PREFIX_LEN, name->s, name->len);
                        tmp[SRV_UDP_PREFIX_LEN + name->len] = '\0';
                        break;
#ifdef USE_TCP
                case PROTO_TCP:
                        if (tcp_disable) goto err_proto;
                        memcpy(tmp, SRV_TCP_PREFIX, SRV_TCP_PREFIX_LEN);
                        memcpy(tmp+SRV_TCP_PREFIX_LEN, name->s, name->len);
                        tmp[SRV_TCP_PREFIX_LEN + name->len] = '\0';
                        break;
#endif
#ifdef USE_TLS
                case PROTO_TLS:
                        if (tls_disable) goto err_proto;
                        memcpy(tmp, SRV_TLS_PREFIX, SRV_TLS_PREFIX_LEN);
                        memcpy(tmp+SRV_TLS_PREFIX_LEN, name->s, name->len);
                        tmp[SRV_TLS_PREFIX_LEN + name->len] = '\0';
                        break;
#endif
                default:
                    LOG(L_ERR, "ERROR:sip_resolvehost: unsupported proto %d\n",
                            *proto);
                    return 0;
        }

        /* Resolve the SRV */
        head = get_record(tmp,T_SRV);
        pkg_free(tmp);
    }
    
	ncount = 0;

    /* Build the branch records based on the SRV records */
    {
        /* Go through the SRV records list */
        for( rd=head ; rd ; rd=rd->next )
    	{
            unsigned end, len;
            struct srv_data *rdata;
            str new_host, new_port;
            str new_uri;
            char port_s[10];

    	  	if(rd->type!=T_SRV )
    		  	continue; /* should never happen */

            rdata = (struct srv_data*)(rd->rdata);

            /* New host */
            new_host.s   = rdata->name;
            new_host.len = rdata->name_len;

            /* New port (as string) */
            new_port.s   = port_s;
            new_port.len = snprintf(new_port.s,9,"%d",rdata->port);

    		/* Build new destination URI */
            new_uri.len = 0;

            new_uri.s   = pkg_malloc(MAX_URI_SIZE);
            if (!new_uri.s){
                LOG(L_ERR, "ERROR: srv_branches: memory allocation failure\n");
                pkg_free(new_port.s);
                return E_OUT_OF_MEM;
            }


            /* Inspired by the code for sethostport in action.c */
            #define APPEND(X)                                                       \
                len=strlen(X);  if(new_uri.len+len>MAX_URI_SIZE-1) goto error_uri;  \
                memcpy(new_uri.s+new_uri.len,X,len);    new_uri.len+=len;

            #define APPEND_str(X)                                                   \
                len=X.len;      if(new_uri.len+len>MAX_URI_SIZE-1) goto error_uri;  \
                memcpy(new_uri.s+new_uri.len,X.s,len);  new_uri.len+=len;

            /* Copy username, password from URI */
            APPEND("sip:")
            if(uri.user.s)
            {
                APPEND_str(uri.user)
            }
            if(uri.passwd.s)
            {
                APPEND(":")
                APPEND_str(uri.passwd)
            }
            if(uri.user.s)
            {
                APPEND("@")
            }
            /* Copy host and port from SRV */
            APPEND_str(new_host)
            APPEND(":")
            APPEND_str(new_port)
            /* Copy others from URI */
            if(uri.params.s)
            {
                APPEND(";")
                APPEND_str(uri.params)
            }
            if(uri.headers.s)
            {
                APPEND("?")
                APPEND_str(uri.headers)
            }
            new_uri.s[new_uri.len] = '\0';

            branches[ncount].uri = new_uri;

            /* For now use priority plus a non-random order */
            branches[ncount].priority = rdata->priority;
            branches[ncount].weight   = rdata->weight;

            branches[ncount].order    = random()%65536;
            if(rdata->weight != 0)
                branches[ncount].order *= rdata->weight;

            ncount++;
            continue;

        error_uri:
            pkg_free(new_uri.s);
        }
    
        free_rdata_list(head);
    }

    if( ncount == 0 )
    {
        LOG(L_ERR, "ERROR: srv_branches: no valid SRV records for %.*s\n",
            name.len, name.s);
        return -1;
    }

    /* Sort the values */
    qsort(branches,size_of(struct branch_def),MAX_BRANCHES,&compare_branches);

    /* Now we append the entries as branches. q are set in increasing order. */
    for( q = 0; q < ncount; q++ )
    {
        append_branch(msg,msg->uri,branches[q].uri,0,q+MIN_Q,0,msg->force_send_socket);
        pkg_free(branches[q].uri.s);
    }
    return 1;
    
err_proto:
    LOG(L_ERR, "ERROR:srv_branches: protocol is disabled\n");
    return 0;
}

/**
 * destroy function
 */
void destroy(void)
{
	DBG("SRV: destroy module ...\n");
}

static int srv_branches_fixup(void** param, int param_no)
{
	return 0;			
}



More information about the Devel mailing list