Commit eec3ebe5 authored by Sebastien Decugis's avatar Sebastien Decugis
Browse files

Added a test case for the app_acct extension

parent 5a400937
......@@ -16,11 +16,13 @@ SET_SOURCE_FILES_PROPERTIES(lex.acct_conf.c acct_conf.tab.c PROPERTIES COMPILE_F
SET( APP_ACCT_SRC
app_acct.h
app_acct.c
acct_db.c
acct_records.c
)
SET( APP_ACCT_SRC_GEN
lex.acct_conf.c
acct_conf.tab.c
acct_conf.tab.h
acct_db.c
acct_records.c
)
# Compile as a module
......
......@@ -50,8 +50,12 @@ const char * diam2db_types_mapping[AVP_TYPE_MAX + 1] = {
};
static const char * stmt = "acct_db_stmt";
static PGconn *conn = NULL;
#ifndef TEST_DEBUG
static
#endif /* TEST_DEBUG */
PGconn *conn = NULL;
/* Initialize the database context: connection to the DB, prepared statement to insert new records */
int acct_db_init(void)
{
struct acct_record_list emptyrecords;
......@@ -62,7 +66,7 @@ int acct_db_init(void)
size_t p;
int idx = 0;
PGresult * res;
#define REALLOC_SIZE 1024
#define REALLOC_SIZE 1024 /* We extend the buffer by this amount */
TRACE_ENTRY();
CHECK_PARAMS( acct_config && acct_config->conninfo && acct_config->tablename );
......@@ -75,9 +79,14 @@ int acct_db_init(void)
fd_log_debug("Connection to database failed: %s\n", PQerrorMessage(conn));
acct_db_free();
return EINVAL;
} else {
TRACE_DEBUG(INFO, "Connection to database successfull: user:%s, db:%s, host:%s.", PQuser(conn), PQdb(conn), PQhost(conn));
}
if (PQprotocolVersion(conn) < 3) {
fd_log_debug("Database protocol version is too old, version 3 is required for prepared statements.\n");
acct_db_free();
return EINVAL;
}
TRACE_DEBUG(FULL, "Connection to database successful, server version %d.", PQserverVersion(conn));
/* Now, prepare the request object */
......@@ -173,21 +182,105 @@ int acct_db_init(void)
}
PQclear(res);
free(sql);
acct_rec_empty(&emptyrecords);
/* Ok, ready */
return 0;
}
/* Terminate the connection to the DB */
void acct_db_free(void)
{
if (conn)
{
if (conn) {
/* Note: the prepared statement is automatically freed when the session terminates */
PQfinish(conn);
conn = NULL;
conn = NULL;
}
}
/* When a new message has been received, insert the content of the parsed mapping into the DB (using prepared statement) */
int acct_db_insert(struct acct_record_list * records)
{
return ENOTSUP;
char **val;
int *val_len;
int *val_isbin;
int idx = 0;
PGresult *res;
struct fd_list *li;
TRACE_ENTRY("%p", records);
CHECK_PARAMS( conn && records );
/* First, check if the connection with the DB has not staled, and eventually try to fix it */
if (PQstatus(conn) != CONNECTION_OK) {
/* Attempt a reset */
PQreset(conn);
if (PQstatus(conn) != CONNECTION_OK) {
TRACE_DEBUG(INFO, "Lost connection to the database server, and attempt to reestablish it failed");
TODO("Terminate the freeDiameter instance completly?");
return ENOTCONN;
}
}
/* Alloc the arrays of parameters */
CHECK_MALLOC( val = calloc(records->nball, sizeof(const char *)) );
CHECK_MALLOC( val_len = calloc(records->nball, sizeof(const int)) );
CHECK_MALLOC( val_isbin = calloc(records->nball, sizeof(const int)) );
/* Now write all the map'd records in these arrays */
for (li = records->all.next; li != &records->all; li = li->next) {
struct acct_record_item * r = (struct acct_record_item *)(li->o);
if (r->value) {
val_isbin[idx] = 1; /* We always pass binary parameters */
switch (r->param->avptype) {
case AVP_TYPE_OCTETSTRING:
val[idx] = (void *)(r->value->os.data);
val_len[idx] = r->value->os.len;
break;
case AVP_TYPE_INTEGER32:
case AVP_TYPE_UNSIGNED32:
case AVP_TYPE_FLOAT32:
r->scalar.v32 = htonl(r->value->u32);
val[idx] = &r->scalar.c;
val_len[idx] = sizeof(uint32_t);
break;
case AVP_TYPE_INTEGER64:
case AVP_TYPE_UNSIGNED64:
case AVP_TYPE_FLOAT64:
r->scalar.v64 = htonll(r->value->u64);
val[idx] = &r->scalar.c;
val_len[idx] = sizeof(uint64_t);
break;
default:
ASSERT(0); /* detect bugs */
}
}
idx++;
}
/* OK, now execute the SQL statement */
res = PQexecPrepared(conn, stmt, records->nball, (const char * const *)val, val_len, val_isbin, 1 /* We actually don't care here */);
/* Done with the parameters */
free(val);
free(val_len);
free(val_isbin);
/* Now check the result code */
if (PQresultStatus(res) != PGRES_COMMAND_OK) {
TRACE_DEBUG(INFO, "An error occurred while INSERTing in the database: %s", PQerrorMessage(conn));
PQclear(res);
return EINVAL; /* It was probably a mistake in configuration file... */
}
PQclear(res);
/* Ok, we are done */
return 0;
}
......@@ -73,11 +73,89 @@ int acct_rec_prepare(struct acct_record_list * records)
return 0;
}
/* Find the AVPs from configuration inside a received message */
int acct_rec_map(struct acct_record_list * records, struct msg * msg)
{
struct avp * avp;
TRACE_ENTRY("%p %p", records, msg);
/* For each AVP in the message, search if we have a corresponding unmap'd record */
CHECK_FCT( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL) );
while (avp) {
struct fd_list * li;
struct dict_object * model;
CHECK_FCT( fd_msg_model(avp, &model) );
if (model != NULL) { /* we ignore the AVPs we don't recognize */
/* Search this model in the list */
for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
struct acct_record_item * r = (struct acct_record_item *)(li->o);
if (r->param->avpobj == model) {
/* It matches: save the AVP value and unlink this record from the unmap'd list */
struct avp_hdr * h;
CHECK_FCT( fd_msg_avp_hdr( avp, &h ) );
r->value = h->avp_value;
fd_list_unlink(&r->unmapd);
records->nbunmap -= 1;
break;
}
}
/* Continue only while there are some AVPs to map */
if (FD_IS_LIST_EMPTY(&records->unmaped))
break;
}
/* Go to next AVP in the message */
CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
}
return ENOTSUP;
/* Done */
return 0;
}
/* Check that a mapped list is not empty and no required AVP is missing. Free the record list in case of error */
int acct_rec_validate(struct acct_record_list * records)
{
struct fd_list * li;
TRACE_ENTRY("%p", records);
CHECK_PARAMS( records );
/* Check at least one AVP was mapped */
if (records->nball == records->nbunmap) {
fd_log_debug("The received ACR does not contain any AVP from the configuration file.\n"
"This is an invalid situation. Please fix your configuration file.\n"
"One way to ensure this does not happen is to include Session-Id in the database.\n");
acct_rec_empty(records);
return EINVAL;
}
/* Now, check there is no required AVP unmap'd */
for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
struct acct_record_item * r = (struct acct_record_item *)(li->o);
if (r->param->required && (r->index <= 1)) {
fd_log_debug("The received ACR does not contain the required AVP '%s'.\n", r->param->avpname);
acct_rec_empty(records);
return EINVAL;
}
}
/* The record list is OK */
return 0;
}
/* Free all the items in an acct_record_list returned by acct_rec_prepare */
void acct_rec_empty(struct acct_record_list * records)
{
TRACE_ENTRY("%p", records);
CHECK_PARAMS_DO( records, return );
while (!FD_IS_LIST_EMPTY(&records->all)) {
struct acct_record_item * r = (struct acct_record_item *)(records->all.next);
fd_list_unlink( &r->chain );
fd_list_unlink( &r->unmapd );
free(r);
}
}
......@@ -37,51 +37,76 @@
#include "app_acct.h"
/* Default callback for the Accounting application. */
static int acct_fallback( struct msg ** msg, struct avp * avp, struct session * sess, enum disp_action * act)
{
/* This CB should never be called */
TRACE_ENTRY("%p %p %p %p", msg, avp, sess, act);
fd_log_debug("Unexpected message received!\n");
return ENOTSUP;
}
/* Mandatory AVPs for the Accounting-Answer */
static struct {
struct dict_object * Accounting_Record_Number;
struct dict_object * Accounting_Record_Type;
} acct_dict;
/* Callback for incoming Base Accounting Accounting-Request messages */
static int acct_cb( struct msg ** msg, struct avp * avp, struct session * sess, enum disp_action * act)
{
struct msg_hdr *hdr = NULL;
struct msg *ans, *qry;
struct msg * m;
struct avp * a = NULL;
struct avp_hdr * h = NULL;
struct avp_hdr * art=NULL, *arn=NULL; /* We keep a pointer on the Accounting-Record-{Type, Number} AVPs from the query */
char * s;
struct acct_record_list rl;
TRACE_ENTRY("%p %p %p %p", msg, avp, sess, act);
if (msg == NULL)
return EINVAL;
qry = *msg;
/* Create the answer message, including the Session-Id AVP */
m = *msg;
/* Prepare a new record list */
CHECK_FCT( acct_rec_prepare( &rl ) );
/* Maps the AVPs from the query with this record list */
CHECK_FCT( acct_rec_map( &rl, m ) );
/* Check that at least one AVP was mapped */
CHECK_FCT( acct_rec_validate( &rl ) );
/* Now, save these mapped AVPs in the database */
CHECK_FCT( acct_db_insert( &rl ) );
acct_rec_empty( &rl );
/* OK, we can send a positive reply now */
/* Get Accounting-Record-{Number,Type} values */
CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Type, &a) );
if (a) {
CHECK_FCT( fd_msg_avp_hdr( a, &art ) );
}
CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Number, &a) );
if (a) {
CHECK_FCT( fd_msg_avp_hdr( a, &arn ) );
}
/* Create the answer message */
CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
ans = *msg;
m = *msg;
/* Set the Origin-Host, Origin-Realm, Result-Code AVPs */
CHECK_FCT( fd_msg_rescode_set( ans, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
fd_log_debug("--------------Received the following Accounting message:--------------\n");
CHECK_FCT( fd_sess_getsid ( sess, &s ) );
fd_log_debug("Session: %s\n", s);
/* We may also dump other data from the message, such as Accounting session Id, number of packets, ... */
fd_log_debug("----------------------------------------------------------------------\n");
CHECK_FCT( fd_msg_rescode_set( m, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
/* Add the mandatory AVPs in the ACA */
if (art) {
CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Type, 0, &a ) );
CHECK_FCT( fd_msg_avp_setvalue( a, art->avp_value ) );
CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
}
if (arn) {
CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Number, 0, &a ) );
CHECK_FCT( fd_msg_avp_setvalue( a, arn->avp_value ) );
CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
}
/* Send the answer */
CHECK_FCT( fd_msg_send( msg, NULL, NULL ) );
*act = DISP_ACT_SEND;
return 0;
}
......@@ -93,18 +118,23 @@ static int acct_entry(char * conffile)
TRACE_ENTRY("%p", conffile);
#ifndef TEST_DEBUG /* We do this differently in the test scenario */
/* Initialize the configuration and parse the file */
CHECK_FCT( acct_conf_init() );
CHECK_FCT( acct_conf_parse(conffile) );
CHECK_FCT( acct_conf_check(conffile) );
#endif /* TEST_DEBUG */
/* Now initialize the database module */
CHECK_FCT( acct_db_init() );
/* Search the AVPs we will need in this file */
CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &acct_dict.Accounting_Record_Number, ENOENT) );
CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &acct_dict.Accounting_Record_Type, ENOENT) );
/* Register the dispatch callbacks */
memset(&data, 0, sizeof(data));
CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Base Accounting", &data.app, ENOENT) );
CHECK_FCT( fd_disp_register( acct_fallback, DISP_HOW_APPID, &data, NULL ) );
CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Accounting-Request", &data.command, ENOENT) );
CHECK_FCT( fd_disp_register( acct_cb, DISP_HOW_CC, &data, NULL ) );
......
......@@ -83,6 +83,11 @@ struct acct_record_item {
struct acct_conf_avp *param; /* the AVP entry this refers to. */
unsigned index; /* in case of multi */
union avp_value *value; /* If the AVP was found in the message, this points to its value. Otherwise, NULL */
union {
uint32_t v32 /* Storage area for network byte-order copy of the AVP value */;
uint64_t v64;
char c; /* pointer that is passed to the database */
} scalar;/* for scalar AVP (all types except OCTETSTRING) we copy in this area the value in network byte order */
};
/* The sentinel for a list of acct_record_items */
......@@ -111,3 +116,5 @@ void acct_db_free(void);
/* In acct_records.c */
int acct_rec_prepare(struct acct_record_list * records);
int acct_rec_map(struct acct_record_list * records, struct msg * msg);
int acct_rec_validate(struct acct_record_list * records);
void acct_rec_empty(struct acct_record_list * records);
......@@ -152,7 +152,7 @@ int fd_ext_load()
}
/* Now unload the extensions and free the memory */
int fd_ext_fini( void )
int fd_ext_term( void )
{
TRACE_ENTRY();
......
......@@ -83,7 +83,7 @@ int fddparse(struct fd_config * conf); /* yacc generated */
int fd_ext_add( char * filename, char * conffile );
int fd_ext_load();
void fd_ext_dump(void);
int fd_ext_fini(void);
int fd_ext_term(void);
/* Messages */
int fd_msg_init(void);
......
......@@ -164,7 +164,7 @@ end:
CHECK_FCT_DO( fd_peer_fini(), /* Stop all connections */ );
CHECK_FCT_DO( fd_rtdisp_fini(), /* Stop routing threads and destroy routing queues */ );
CHECK_FCT_DO( fd_ext_fini(), /* Cleanup all extensions */ );
CHECK_FCT_DO( fd_ext_term(), /* Cleanup all extensions */ );
CHECK_FCT_DO( fd_rtdisp_cleanup(), /* destroy remaining handlers */ );
GNUTLS_TRACE( gnutls_global_deinit() );
......
......@@ -49,11 +49,59 @@ ENDFOREACH(SRC_FILE)
# Create an archive with the daemon common files (all but main)
ADD_LIBRARY(fDcore STATIC ${TEST_COMMON_SRC})
##############################
# App_acct test
IF(BUILD_APP_ACCT)
OPTION(TEST_APP_ACCT "Test app_acct extension? (Requires a configured database, see testappacct.c for details)" OFF)
IF(TEST_APP_ACCT)
OPTION(TEST_APP_ACCT_CONNINFO "The connection string to the database")
IF(TEST_APP_ACCT_CONNINFO)
ADD_DEFINITIONS(-DTEST_CONNINFO="${TEST_APP_ACCT_CONNINFO}")
ENDIF(TEST_APP_ACCT_CONNINFO)
SET(TEST_LIST ${TEST_LIST} testappacct)
# Extension dependencies
FIND_PACKAGE(PostgreSQL REQUIRED)
INCLUDE_DIRECTORIES(${POSTGRESQL_INCLUDE_DIR})
SET(testappacct_ADDITIONAL_LIB ${POSTGRESQL_LIBRARIES})
# List of source files, copied from the extension CMakeLists.
SET( APP_ACCT_SRC
app_acct.h
app_acct.c
acct_db.c
acct_records.c
)
SET( APP_ACCT_SRC_GEN
lex.acct_conf.c
acct_conf.tab.c
acct_conf.tab.h
)
# The extension headers
INCLUDE_DIRECTORIES( "../../extensions/app_acct" )
SET(testappacct_ADDITIONAL "")
FOREACH( SRC_FILE ${APP_ACCT_SRC})
SET(testappacct_ADDITIONAL ${testappacct_ADDITIONAL} "../../extensions/app_acct/${SRC_FILE}")
ENDFOREACH(SRC_FILE)
FOREACH( SRC_FILE ${APP_ACCT_SRC_GEN})
SET(testappacct_ADDITIONAL ${testappacct_ADDITIONAL} "${CMAKE_CURRENT_BINARY_DIR}/../../extensions/app_acct/${SRC_FILE}")
ENDFOREACH(SRC_FILE)
ENDIF(TEST_APP_ACCT)
ENDIF(BUILD_APP_ACCT)
#############################
# Compile each test
FOREACH( TEST ${TEST_LIST} )
ADD_EXECUTABLE(${TEST} ${TEST}.c tests.h)
TARGET_LINK_LIBRARIES(${TEST} fDcore ${FD_LIBS})
ADD_EXECUTABLE(${TEST} ${TEST}.c tests.h ${${TEST}_ADDITIONAL})
TARGET_LINK_LIBRARIES(${TEST} fDcore ${FD_LIBS} ${${TEST}_ADDITIONAL_LIB})
ADD_TEST(${TEST} ${EXECUTABLE_OUTPUT_PATH}/${TEST})
ENDFOREACH( TEST )
/*********************************************************************************************************
* Software License Agreement (BSD License) *
* Author: Sebastien Decugis <sdecugis@nict.go.jp> *
* *
* Copyright (c) 2009, WIDE Project and NICT *
* All rights reserved. *
* *
* Redistribution and use of this software in source and binary forms, with or without modification, are *
* permitted provided that the following conditions are met: *
* *
* * Redistributions of source code must retain the above *
* copyright notice, this list of conditions and the *
* following disclaimer. *
* *
* * Redistributions in binary form must reproduce the above *
* copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other *
* materials provided with the distribution. *
* *
* * Neither the name of the WIDE Project or NICT nor the *
* names of its contributors may be used to endorse or *
* promote products derived from this software without *
* specific prior written permission of WIDE Project and *
* NICT. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
*********************************************************************************************************/
#include "tests.h"
/* The connection string to the database */
#ifndef TEST_CONNINFO
#error "Please specify the conninfo information"
#endif /* TEST_CONNINFO */
/* The table used for tests. This table will receive the following instructions:
DROP TABLE <table>;
CREATE TABLE <table>
(
ts timestamp with time zone NOT NULL,
"Accounting-Record-Type" integer,
"Session-Id" bytea,
"Accounting-Record-Number" integer,
"Route-Record1" bytea,
"Route-Record2" bytea,
"Route-Record3" bytea,
"Route-Record4" bytea
);
*/
#define TABLE "incoming_test"
#include "app_acct.h"
#include <libpq-fe.h>
static int add_avp_in_conf(char * avpname, int multi)
{
struct acct_conf_avp *new;
struct dict_object * dict;
struct dict_avp_data dictdata;
/* Validate the avp name first */
CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, avpname, &dict, ENOENT) );
CHECK_FCT( fd_dict_getval( dict, &dictdata ));
/* Create a new entry */
CHECK_MALLOC( new = malloc(sizeof(struct acct_conf_avp)) );
memset(new, 0, sizeof(struct acct_conf_avp));
fd_list_init(&new->chain, NULL);
new->avpname = avpname;
new->avpobj = dict;
new->avptype = dictdata.avp_basetype;
new->multi = multi;
/* Add this new entry at the end of the list */
fd_list_insert_before( &acct_config->avps, &new->chain );
return 0;
}
/* Main test routine */
int main(int argc, char *argv[])
{
extern PGconn *conn; /* in acct_db.c */
struct msg * msg;
char * sess_bkp;
struct dict_object * session_id = NULL;
/* First, initialize the daemon modules */
INIT_FD();
fd_g_config->cnf_diamid = strdup("test.app.acct");
fd_g_config->cnf_diamid_len = strlen(fd_g_config->cnf_diamid);
fd_g_config->cnf_diamrlm = strdup("app.acct");
fd_g_config->cnf_diamrlm_len = strlen(fd_g_config->cnf_diamrlm);
CHECK( 0, fd_queues_init() );
CHECK( 0, fd_msg_init() );
CHECK( 0, fd_rtdisp_init() );
/* Initialize the extension configuration for the test */
{
CHECK( 0, acct_conf_init() );
acct_config->conninfo = strdup(TEST_CONNINFO);
acct_config->tablename = strdup(TABLE);
acct_config->tsfield = strdup("ts");
CHECK( 0, add_avp_in_conf(strdup("Session-Id"), 0) );
CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Type"), 0) );
CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Number"), 0) );