diff -uNr modules/acc/Makefile modules/acc.new/Makefile --- modules/acc/Makefile 2004-02-24 11:39:45.000000000 +0100 +++ modules/acc.new/Makefile 2004-10-31 12:04:18.000000000 +0100 @@ -11,13 +11,13 @@ LIBS= # uncomment the next line if you wish to enable SQL accounting -#DEFS+=-DSQL_ACC +#DEFS+=-DSQL_ACC # uncomment the next two lines if you wish to enable RADIUS accounting #DEFS+=-DRAD_ACC -I$(LOCALBASE)/include #LIBS=-L$(LOCALBASE)/lib -lradiusclient # uncomment the next two lines if you wish to enable DIAMETER accounting -#DEFS+=-DDIAM_ACC +#DEFS+=-DDIAM_ACC include ../../Makefile.modules diff -uNr modules/acc/README modules/acc.new/README --- modules/acc/README 2004-08-24 10:58:23.000000000 +0200 +++ modules/acc.new/README 2004-10-31 11:41:05.000000000 +0100 @@ -15,36 +15,41 @@ 1.1.1. General Example - 1.2. Dependencies - 1.3. Exported Parameters - - 1.3.1. secret (string) - 1.3.2. log_level (integer) - 1.3.3. log_fmt (string) - 1.3.4. early_media (integer) - 1.3.5. failed_transactions (integer) - 1.3.6. log_flag (integer) - 1.3.7. log_missed_flag (integer) - 1.3.8. report_ack (integer) - 1.3.9. report_cancels (integer) - 1.3.10. radius_config (string) - 1.3.11. service_type (integer) - 1.3.12. radius_flag (integer) - 1.3.13. radius_missed_flag (integer) - 1.3.14. db_url (string) - 1.3.15. db_flag (integer) - 1.3.16. db_missed_flag (integer) - 1.3.17. diameter_flag (integer) - 1.3.18. diameter_missed_flag (integer) - 1.3.19. diameter_client_host (string) - 1.3.20. diameter_client_port (int) - - 1.4. Exported Functions - - 1.4.1. acc_log_request(comment) - 1.4.2. acc_db_request(comment, table) - 1.4.3. acc_rad_request(comment) - 1.4.4. acc_diam_request(comment) + 1.2. Extra accounting + 1.3. Dependencies + 1.4. Exported Parameters + + 1.4.1. secret (string) + 1.4.2. log_level (integer) + 1.4.3. log_fmt (string) + 1.4.4. early_media (integer) + 1.4.5. failed_transactions (integer) + 1.4.6. log_flag (integer) + 1.4.7. log_missed_flag (integer) + 1.4.8. report_ack (integer) + 1.4.9. report_cancels (integer) + 1.4.10. radius_config (string) + 1.4.11. service_type (integer) + 1.4.12. radius_flag (integer) + 1.4.13. radius_missed_flag (integer) + 1.4.14. db_url (string) + 1.4.15. db_flag (integer) + 1.4.16. db_missed_flag (integer) + 1.4.17. diameter_flag (integer) + 1.4.18. diameter_missed_flag (integer) + 1.4.19. diameter_client_host (string) + 1.4.20. diameter_client_port (int) + 1.4.21. log_extra (string) + 1.4.22. db_extra (string) + 1.4.23. rad_extra (string) + 1.4.24. dia_extra (string) + + 1.5. Exported Functions + + 1.5.1. acc_log_request(comment) + 1.5.2. acc_db_request(comment, table) + 1.5.3. acc_rad_request(comment) + 1.5.4. acc_diam_request(comment) 2. Developer's Guide 3. Frequently Asked Questions @@ -70,10 +75,14 @@ 1-18. diameter_missed_flag example 1-19. diameter_client_host example 1-20. diameter_client_host example - 1-21. acc_log_request usage - 1-22. acc_db_request usage - 1-23. acc_rad_request usage - 1-24. acc_diam_request usage + 1-21. log_extra example + 1-22. db_extra example + 1-23. rad_extra example + 1-24. dia_extra example + 1-25. acc_log_request usage + 1-26. acc_db_request usage + 1-27. acc_rad_request usage + 1-28. acc_diam_request usage _________________________________________________________ Chapter 1. User's Guide @@ -182,7 +191,57 @@ }; _________________________________________________________ -1.2. Dependencies +1.2. Extra accounting + + Along the static information defined via FMT-s, ACC modules + allows dynamical selection of extra information to be logged. + There are two classes of information that are accessible by + extra accounting: data from SIP messages (as headers) and + internal SER data (as AVPs). + + Selection of extra information is done via xxx_extra + parameters by specifying the names of additional headers or + AVPs you want to log. The syntax of is: + + * xxx_extra = extra_definition (';'extra_definition)* + * extra_definition = log_name '=' data_type'/'data['/'flags] + * data_type = 'hdr' | 'avp' + * data = header_name | ['s:']AVP_name | 'i:'AVP_id + * flags = 'g' + + Via log_name you define how/where the data will be logged. Its + meaning depends of the accounting support which is used: + + * LOG accounting - log_name will be just printed along with + the data in log_name=data format; + * DB accounting - log_name will be the name of the DB column + where the data will be stored. IMPORTANT: add in db acc + table the columns corresponding to each extra data; + * RADIUS accounting - log_name will be the AVP name used for + packing the data into RADIUS message. The log_name will be + translated to AVP number via the dictionary. IMPORTANT: + add in RADIUS dictionary the log_name attribute. + * DIAMETER accounting - log_name will be the AVP code used + for packing the data into DIAMETER message. the AVP code + is given directly as integer, since DIAMETER has no + dictionary support yet. IMPORTANT: log_name must be a + number. + + Data can be an header name or an AVP name/ID - depending of + the data_type. If header, the search for it into SIP messages + will be optimized by converting at start-up the header name + (string) into header type (integer) - this is not possible for + all headers (only for the the most important); otherwise case + insensitive string matching will be used. + + The only defined flag is 'g' - global - which will force + logging all values of the given header or AVP. Without this + flag, only the first found value will be logged. IMPORTANT for + DB logging, this flag is disabled from DB data structure + constraints. + _________________________________________________________ + +1.3. Dependencies The module depends on the following modules (in the other words the listed modules must be loaded before this module): @@ -191,9 +250,9 @@ * a database module -- If compiled with database support. _________________________________________________________ -1.3. Exported Parameters +1.4. Exported Parameters -1.3.1. secret (string) +1.4.1. secret (string) Default value is randomly generated string. @@ -201,7 +260,7 @@ modparam("auth", "secret", "johndoessecretphrase") _________________________________________________________ -1.3.2. log_level (integer) +1.4.2. log_level (integer) Log level at which accounting messages are issued to syslog. @@ -211,7 +270,7 @@ modparam("acc", "log_level", 2) # Set log_level to 2 _________________________________________________________ -1.3.3. log_fmt (string) +1.4.3. log_fmt (string) Defines what parts of header fields will be printed to syslog, see "overview" for list of accepted values. @@ -222,7 +281,7 @@ modparam("acc", "log_fmt", "mfs") _________________________________________________________ -1.3.4. early_media (integer) +1.4.4. early_media (integer) Should be early media (183) accounted too ? @@ -232,7 +291,7 @@ modparam("acc", "early_media", 1) _________________________________________________________ -1.3.5. failed_transactions (integer) +1.4.5. failed_transactions (integer) Should be failed transactions (status>=300) accounted too ? @@ -242,7 +301,7 @@ modparam("acc", "failed_transactions", 1) _________________________________________________________ -1.3.6. log_flag (integer) +1.4.6. log_flag (integer) Request flag which needs to be set to account a transaction. @@ -252,7 +311,7 @@ modparam("acc", "log_flag", 2) _________________________________________________________ -1.3.7. log_missed_flag (integer) +1.4.7. log_missed_flag (integer) Request flag which needs to be set to account missed calls. @@ -262,7 +321,7 @@ modparam("acc", "log_missed_flag", 3) _________________________________________________________ -1.3.8. report_ack (integer) +1.4.8. report_ack (integer) Shall acc attempt to account e2e ACKs too ? Note that this is really only an attempt, as e2e ACKs may take a different path @@ -275,7 +334,7 @@ modparam("acc", "report_ack", 0) _________________________________________________________ -1.3.9. report_cancels (integer) +1.4.9. report_cancels (integer) By default, CANCEL reporting is disabled -- most accounting applications are happy to see INVITE's cancellation status. @@ -287,7 +346,7 @@ modparam("acc", "report_cancels", 1) _________________________________________________________ -1.3.10. radius_config (string) +1.4.10. radius_config (string) This parameter is radius specific. Path to radius client configuration file, set the referred config file correctly and @@ -303,7 +362,7 @@ modparam("acc", "radius_config", "/etc/radiusclient/radiusclient.conf") _________________________________________________________ -1.3.11. service_type (integer) +1.4.11. service_type (integer) Radius service type used for accounting. @@ -313,7 +372,7 @@ modparam("acc", "service_type", 16) _________________________________________________________ -1.3.12. radius_flag (integer) +1.4.12. radius_flag (integer) Request flag which needs to be set to account a transaction -- RADIUS specific. @@ -324,7 +383,7 @@ modparam("acc", "radius_flag", 2) _________________________________________________________ -1.3.13. radius_missed_flag (integer) +1.4.13. radius_missed_flag (integer) Request flag which needs to be set to account missed calls -- RADIUS specific. @@ -335,7 +394,7 @@ modparam("acc", "radius_missed_flag", 3) _________________________________________________________ -1.3.14. db_url (string) +1.4.14. db_url (string) SQL address -- database specific. @@ -345,7 +404,7 @@ modparam("acc", "db_url", "mysql://user:password@localhost/ser") _________________________________________________________ -1.3.15. db_flag (integer) +1.4.15. db_flag (integer) Request flag which needs to be set to account a transaction -- database specific. @@ -356,7 +415,7 @@ modparam("acc", "db_flag", 2) _________________________________________________________ -1.3.16. db_missed_flag (integer) +1.4.16. db_missed_flag (integer) Request flag which needs to be set to account missed calls -- database specific. @@ -367,7 +426,7 @@ modparam("acc", "db_missed_flag", 3) _________________________________________________________ -1.3.17. diameter_flag (integer) +1.4.17. diameter_flag (integer) Request flag which needs to be set to account a transaction -- DIAMETER specific. @@ -378,7 +437,7 @@ modparam("acc", "diameter_flag", 2) _________________________________________________________ -1.3.18. diameter_missed_flag (integer) +1.4.18. diameter_missed_flag (integer) Request flag which needs to be set to account missed calls -- DIAMETER specific. @@ -389,7 +448,7 @@ modparam("acc", "diameter_missed_flag", 3) _________________________________________________________ -1.3.19. diameter_client_host (string) +1.4.19. diameter_client_host (string) Hostname of the machine where the DIAMETER Client is running -- DIAMETER specific. @@ -400,7 +459,7 @@ modparam("acc", "diameter_client_host", "iptel.org") _________________________________________________________ -1.3.20. diameter_client_port (int) +1.4.20. diameter_client_port (int) Port number where the Diameter Client is listening -- DIAMETER specific. @@ -411,9 +470,49 @@ modparam("acc", "diameter_client_port", 3000) _________________________________________________________ -1.4. Exported Functions +1.4.21. log_extra (string) + + Extra values to be logged. + + Default value is NULL. + + Example 1-21. log_extra example +modparam("acc", "log_extra", "ua=hdr/User-Agent;uuid=avp/i:123") + _________________________________________________________ + +1.4.22. db_extra (string) + + Extra values to be logged into database - DB specific. + + Default value is NULL. + + Example 1-22. db_extra example +modparam("acc", "db_extra", "ct=hdr/Content-type; email=avp/s:email") + _________________________________________________________ + +1.4.23. rad_extra (string) + + Extra values to be logged via RADIUS - RADIUS specific. + + Default value is NULL. + + Example 1-23. rad_extra example +modparam("acc", "rad_extra", "via=hdr/Via/g; email=avp/s:email") + _________________________________________________________ + +1.4.24. dia_extra (string) + + Extra values to be logged via DIAMETER - DIAMETER specific. + + Default value is NULL. + + Example 1-24. dia_extra example +modparam("acc", "dia_extra", "ct=7846/Content-type; 7847=avp/s:email") + _________________________________________________________ + +1.5. Exported Functions -1.4.1. acc_log_request(comment) +1.5.1. acc_log_request(comment) acc_request reports on a request, for example, it can be used to report on missed calls to off-line users who are replied @@ -424,13 +523,13 @@ * comment - Comment to be appended. - Example 1-21. acc_log_request usage + Example 1-25. acc_log_request usage ... acc_log_request("Some comment"); ... _________________________________________________________ -1.4.2. acc_db_request(comment, table) +1.5.2. acc_db_request(comment, table) Like acc_log_request, acc_db_request reports on a request. The report is sent to database at "db_url", in the table referred @@ -441,13 +540,13 @@ * comment - Comment to be appended. * table - Database table to be used. - Example 1-22. acc_db_request usage + Example 1-26. acc_db_request usage ... acc_log_request("Some comment", "Some table"); ... _________________________________________________________ -1.4.3. acc_rad_request(comment) +1.5.3. acc_rad_request(comment) Like acc_log_request, acc_rad_request reports on a request. It reports to radius server as configured in "radius_config". @@ -456,13 +555,13 @@ * comment - Comment to be appended. - Example 1-23. acc_rad_request usage + Example 1-27. acc_rad_request usage ... acc_rad_request("Some comment"); ... _________________________________________________________ -1.4.4. acc_diam_request(comment) +1.5.4. acc_diam_request(comment) Like acc_log_request, acc_diam_request reports on a request. It reports to Diameter server. @@ -471,7 +570,7 @@ * comment - Comment to be appended. - Example 1-24. acc_diam_request usage + Example 1-28. acc_diam_request usage ... acc_diam_request("Some comment"); ... diff -uNr modules/acc/acc.c modules/acc.new/acc.c --- modules/acc/acc.c 2004-09-14 13:44:59.000000000 +0200 +++ modules/acc.new/acc.c 2004-10-31 11:57:10.000000000 +0100 @@ -48,6 +48,7 @@ #include "../tm/t_funcs.h" #include "acc_mod.h" #include "acc.h" +#include "acc_extra.h" #include "dict.h" #ifdef RAD_ACC #include @@ -72,16 +73,20 @@ static str na={NA, NA_LEN}; +extern struct acc_extra *log_extra; + #ifdef RAD_ACC /* caution: keep these aligned to RAD_ACC_FMT !! */ static int rad_attr[] = { A_CALLING_STATION_ID, A_CALLED_STATION_ID, A_SIP_TRANSLATED_REQUEST_URI, A_ACCT_SESSION_ID, A_SIP_TO_TAG, A_SIP_FROM_TAG, A_SIP_CSEQ }; +extern struct acc_extra *rad_extra; #endif #ifdef DIAM_ACC extern char *diameter_client_host; extern int diameter_client_port; +extern struct acc_extra *dia_extra; /* caution: keep these aligned to DIAM_ACC_FMT !! */ static int diam_attr[] = { AVP_SIP_FROM_URI, AVP_SIP_TO_URI, AVP_SIP_OURI, @@ -93,6 +98,7 @@ static char* acc_db_url=0; static db_func_t acc_dbf; static db_con_t* db_handle=0; +extern struct acc_extra *db_extra; #endif @@ -319,13 +325,13 @@ int acc_log_request( struct sip_msg *rq, struct hdr_field *to, str *txt, str *phrase) { + static str* val_arr[ALL_LOG_FMT_LEN+MAX_ACC_EXTRA]; + static str atr_arr[ALL_LOG_FMT_LEN+MAX_ACC_EXTRA]; int len; char *log_msg; char *p; int attr_cnt; int attr_len; - str* val_arr[ALL_LOG_FMT_LEN]; - str atr_arr[ALL_LOG_FMT_LEN]; int i; if (skip_cancel(rq)) return 1; @@ -336,6 +342,10 @@ LOG(L_ERR, "ERROR: acc_log_request: fmt2strar failed\n"); return -1; } + attr_cnt += extra2strar( log_extra, rq, + &len, &attr_len, + atr_arr+attr_cnt, val_arr+attr_cnt); + len+=attr_len+ACC_LEN+txt->len+A_EOL_LEN +attr_cnt*(A_SEPARATOR_LEN+A_EQ_LEN)-A_SEPARATOR_LEN; log_msg=pkg_malloc(len); @@ -436,6 +446,9 @@ #ifdef SQL_ACC +/* caution: keys need to be aligned to formatting strings */ +static db_key_t keys[ALL_LOG_FMT_LEN+1+MAX_ACC_EXTRA]; + /* binds to the corresponding database module * returns 0 on success, -1 on error */ @@ -447,16 +460,41 @@ return -1; } - /* Check database capabilities */ + /* Check database capabilities */ if (!DB_CAPABILITY(acc_dbf, DB_CAP_INSERT)) { - LOG(L_ERR, "ERROR: acc_db_init: Database module does not implement insert function\n"); + LOG(L_ERR, "ERROR: acc_db_init: Database module does not implement " + "insert function\n"); return -1; } - return 0; } +void acc_db_init_keys() +{ + struct acc_extra *extra; + int i; + + /* init the static db keys */ + /* caution: keys need to be aligned to formatting strings */ + keys[0] = acc_from_uri; + keys[1] = acc_to_uri; + keys[2] = acc_sip_method_col; + keys[3] = acc_i_uri_col; + keys[4] = acc_o_uri_col; + keys[5] = acc_sip_from_col; + keys[6] = acc_sip_callid_col; + keys[7] = acc_sip_to_col; + keys[8] = acc_sip_status_col; + keys[9] = acc_user_col; + keys[10] = acc_totag_col; + keys[11] = acc_fromtag_col; + keys[12] = acc_domain_col; + /* init the extra db keys */ + for(i=0,extra=db_extra; extra && inext) + keys[13+i] = extra->name.s; +} + /* initialize the database connection * returns 0 on success, -1 on error */ @@ -469,6 +507,7 @@ "database\n"); return -1; } + acc_db_init_keys(); return 0; }else{ LOG(L_CRIT, "BUG: acc_db_init: null db url\n"); @@ -477,7 +516,6 @@ } - /* close a db connection */ void acc_db_close() { @@ -485,19 +523,12 @@ } - int acc_db_request( struct sip_msg *rq, struct hdr_field *to, str *phrase, char *table, char *fmt) { - db_val_t vals[ALL_LOG_FMT_LEN+1]; - str* val_arr[ALL_LOG_FMT_LEN+1]; - str atr_arr[ALL_LOG_FMT_LEN+1]; - /* caution: keys need to be aligned to formatting strings */ - db_key_t keys[] = {acc_from_uri, acc_to_uri, - acc_sip_method_col, acc_i_uri_col, - acc_o_uri_col, acc_sip_from_col, acc_sip_callid_col, - acc_sip_to_col, acc_sip_status_col, acc_user_col, - acc_totag_col, acc_fromtag_col, acc_domain_col, acc_time_col }; + static db_val_t vals[ALL_LOG_FMT_LEN+1+MAX_ACC_EXTRA]; + static str* val_arr[ALL_LOG_FMT_LEN+1+MAX_ACC_EXTRA]; + static str atr_arr[ALL_LOG_FMT_LEN+1+MAX_ACC_EXTRA]; struct tm *tm; time_t timep; @@ -519,6 +550,9 @@ LOG(L_ERR, "ERROR: acc_db_request: fmt2strar failed\n"); return -1; } + attr_cnt += extra2strar( db_extra, rq, + &dummy_len, &dummy_len, + atr_arr+attr_cnt, val_arr+attr_cnt); if (!acc_db_url) { LOG(L_ERR, "ERROR: can't log -- no db_url set\n"); @@ -539,6 +573,7 @@ VAL_TYPE(vals+i)=DB_STRING; VAL_NULL(vals+i)=0; VAL_STRING(vals+i)=time_s; + keys[i] = acc_time_col; if (acc_dbf.use_table(db_handle, table) < 0) { LOG(L_ERR, "ERROR: acc_request: " @@ -546,7 +581,7 @@ return -1; } - if (acc_dbf.insert(db_handle, keys, vals, i+1) < 0) { + if (acc_dbf.insert(db_handle, keys, vals, attr_cnt+1) < 0) { LOG(L_ERR, "ERROR: acc_request: " "Error while inserting to database\n"); return -1; @@ -630,9 +665,10 @@ int acc_rad_request( struct sip_msg *rq, struct hdr_field *to, str *phrase ) { - str* val_arr[ALL_LOG_FMT_LEN+1]; - str atr_arr[ALL_LOG_FMT_LEN+1]; + static str* val_arr[RAD_ACC_FMT_LEN+MAX_ACC_EXTRA]; + static str atr_arr[RAD_ACC_FMT_LEN+MAX_ACC_EXTRA]; int attr_cnt; + int extra_attr_cnt; VALUE_PAIR *send; UINT4 av_type; int i; @@ -654,10 +690,14 @@ attr_cnt=fmt2strar( RAD_ACC_FMT, rq, to, phrase, &dummy_len, &dummy_len, val_arr, atr_arr); - if (attr_cnt!=(sizeof(RAD_ACC_FMT)-1)) { + if (attr_cnt!=RAD_ACC_FMT_LEN) { LOG(L_ERR, "ERROR: acc_rad_request: fmt2strar failed\n"); goto error; } + extra_attr_cnt = extra2strar( rad_extra, rq, + &dummy_len, &dummy_len, + atr_arr+attr_cnt, val_arr+attr_cnt); + av_type=rad_status(rq, phrase); if (!rc_avpair_add(rh, &send, attrs[A_ACCT_STATUS_TYPE].v, &av_type, -1, 0)) { @@ -747,7 +787,8 @@ } } } - /* Remaining attributes from rad_attr vector */ + + /* Remaining attributes from rad_attr vector */ for(i=0; is,val_arr[i]->len, 0)) { @@ -756,7 +797,16 @@ goto error; } } - + /* add extra also */ + for(i=attr_cnt; is,val_arr[i]->len, 0)) { + LOG(L_ERR, "ERROR: acc_rad_request: rc_avpaid_add " + "failed for %s\n", atr_arr[i].s ); + goto error; + } + } + if (rc_acct(rh, SIP_PORT, send)!=OK_RC) { LOG(L_ERR, "ERROR: acc_rad_request: radius-ing failed\n"); goto error; @@ -846,9 +896,10 @@ int acc_diam_request( struct sip_msg *rq, struct hdr_field *to, str *phrase ) { - str* val_arr[ALL_LOG_FMT_LEN+1]; - str atr_arr[ALL_LOG_FMT_LEN+1]; + static str* val_arr[DIAM_ACC_FMT_LEN+MAX_ACC_EXTRA]; + static str atr_arr[DIAM_ACC_FMT_LEN+MAX_ACC_EXTRA]; int attr_cnt; + int extra_attr_cnt; AAAMessage *send = NULL; AAA_AVP *avp; int i; @@ -871,12 +922,16 @@ attr_cnt=fmt2strar( DIAM_ACC_FMT, rq, to, phrase, &dummy_len, &dummy_len, val_arr, atr_arr); - if (attr_cnt!=(sizeof(DIAM_ACC_FMT)-1)) + if (attr_cnt!=DIAM_ACC_FMT_LEN) { LOG(L_ERR, "ERROR: acc_diam_request: fmt2strar failed\n"); return -1; } + extra_attr_cnt = extra2strar( dia_extra, rq, + &dummy_len, &dummy_len, + atr_arr+attr_cnt, val_arr+attr_cnt); + if ( (send=AAAInMessage(ACCOUNTING_REQUEST, AAA_APP_NASREQ))==NULL) { LOG(L_ERR, "ERROR: acc_diam_request: new AAA message not created\n"); @@ -1048,7 +1103,23 @@ goto error; } } - + /* also the extra */ + for(i=attr_cnt; is, val_arr[i]->len, AVP_DUPLICATE_DATA)) == 0) + { + LOG(L_ERR,"ERROR: acc_diam_request: no more free memory!\n"); + goto error; + } + if( AAAAddAVPToMessage(send, avp, 0)!= AAA_ERR_SUCCESS) + { + LOG(L_ERR, "ERROR: acc_diam_request: avp not added \n"); + AAAFreeAVP(&avp); + goto error; + } + } + if (get_uri(rq, &uri) < 0) { LOG(L_ERR, "ERROR: acc_diam_request: From/To URI not found\n"); diff -uNr modules/acc/acc_extra.c modules/acc.new/acc_extra.c --- modules/acc/acc_extra.c 1970-01-01 01:00:00.000000000 +0100 +++ modules/acc.new/acc_extra.c 2004-10-31 11:58:15.000000000 +0100 @@ -0,0 +1,467 @@ +/* + * $Id$ + * + * Copyright (C) 2004 Voice Sistem SRL + * + * This file is part of SIP Express Router. + * + * ser 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. + * + * ser 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. + * + * + * History: + * --------- + * 2004-10-28 first version (ramona) + */ + + + +#include +#include +#include +#include "../../dprint.h" +#include "../../ut.h" +#include "../../usr_avp.h" +#include "../../mem/mem.h" +#include "../../parser/parse_hname2.h" +#include "acc_extra.h" + + +#define C1 '=' +#define C2 '/' +#define C3 ';' + +#define AVP_TYPE_S "avp" +#define AVP_TYPE_LEN (sizeof(AVP_TYPE_S)-1) +#define HDR_TYPE_S "hdr" +#define HDR_TYPE_LEN (sizeof(HDR_TYPE_S)-1) + + +static str na={"n/a", 3}; +/* static arrays used for faster and simpler int to str conversion */ +static char int_buf[INT2STR_MAX_LEN*MAX_ACC_EXTRA]; +static str str_buf[MAX_ACC_EXTRA]; + + + +void init_acc_extra() +{ + int i; + for (i=0; i=%.*s/%d\n",extra->flags,extra->name.s, + extra->sval.len,extra->sval.s, extra->ival); + extra = extra->next; + } +} +*/ + +#define _HDR_OPTIMIZATION +struct acc_extra *parse_acc_extra(char *extra_str, int allowed_flags) +{ + struct hdr_field hdr; + struct acc_extra *head; + struct acc_extra *extra; + char *foo; + char *s; + char bkp; + str val; + int n; + + + n = 0; + head = 0; + extra = 0; + s = extra_str; + + if (s==0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: null string received\n"); + goto error; + } + + while (*s) + { + /* skip white spaces */ + while (*s && isspace((int)*s)) s++; + if (*s==0) + goto parse_error; + if (n==MAX_ACC_EXTRA) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: too many extras -> please " + "increase the internal buffer\n"); + goto error; + } + extra = (struct acc_extra*)pkg_malloc(sizeof(struct acc_extra)); + if (extra==0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: no more pkg mem 1\n"); + goto error; + } + memset( extra, 0, sizeof(struct acc_extra)); + /* link the new extra */ + extra->next = head; + head = extra; + n++; + + /* get name */ + foo = s; + while (*s && !isspace((int)*s) && C1!=*s) s++; + if (*s==0) + goto parse_error; + if (*s==C1) + { + extra->name.len = (s++) - foo; + } else { + extra->name.len = (s++) - foo; + /* skip spaces */ + while (*s && isspace((int)*s)) s++; + if (*s!=C1) + goto parse_error; + s++; + } + extra->name.s = (char*)pkg_malloc(extra->name.len+1); + if (extra->name.s==0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: no more pkg mem 2\n"); + goto error; + } + memcpy(extra->name.s, foo, extra->name.len); + extra->name.s[extra->name.len] = 0; + + /* skip spaces */ + while (*s && isspace((int)*s)) s++; + + /* get value type */ + if (strncasecmp(s,HDR_TYPE_S,HDR_TYPE_LEN)==0) + { + extra->flags = ACC_EXTRA_HEADER; + s += HDR_TYPE_LEN; + } + else + if (strncasecmp(s,AVP_TYPE_S,AVP_TYPE_LEN)==0) + { + extra->flags = ACC_EXTRA_AVP; + s += AVP_TYPE_LEN; + } + + if (*(s++)!=C2 || !*s) + goto parse_error; + /* get the value */ + val.s = s; + for( ; *s && *s!=C2 && *s!=C3; s++ ); + if (*s==0) + val.len = strlen(val.s); + else + val.len = s - val.s; + if (val.len==0) + goto parse_error; + + /* if AVP -> parse value */ + if (extra->flags&ACC_EXTRA_AVP) + { + if ( val.s[0] && val.s[1]==':') + { + switch (val.s[0]) + { + case 's': + case 'S': + break; + case 'i': + case 'I': + extra->flags |= ACC_EXTRA_IS_INT; + break; + default: + LOG(L_ERR,"ERROR:acc:parse_acc_extra: unknown " + "avp type (%c)\n", *foo); + goto error; + } + val.s += 2; + val.len -= 2; + if (val.len == 0) + goto parse_error; + } + /* if type int, convert it also as integer */ + if (extra->flags & ACC_EXTRA_IS_INT && + str2int(&val, (unsigned int*)&extra->ival)!=0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: avp name <%.*s> is " + "not ID as type says(%d)\n", val.len, val.s, extra->flags); + goto error; + } + } else { + /* do a terrible hack just to be able to use parse_hnames */ + bkp = val.s[val.len]; + val.s[val.len] = ':'; + val.len++; + /* header name - try to parse it and see if it's a known header */ + if (parse_hname2(val.s, val.s+val.len,&hdr)==0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: bug??\n"); + goto error; + } + val.len--; + val.s[val.len] = bkp; + extra->ival = hdr.type; + if (extra->ival!=HDR_OTHER) + LOG(L_INFO,"INFO:acc:parse_acc_extra: optimazing by using " + "hdr type (%d) instead of <%.*s>\n", + extra->ival,val.len,val.s); + } + /* get the value as string */ + extra->sval.s = (char*)pkg_malloc(val.len + 1); + if (extra->sval.s==0) + { + LOG(L_ERR,"ERROR:acc:parse_acc_extra: no more pkg mem 3\n"); + goto error; + } + extra->sval.len = val.len; + memcpy(extra->sval.s, val.s, val.len); + extra->sval.s[extra->sval.len] = 0; + + /* are any flags? */ + if (*s==C2) + { + for(s++ ; *s && !isspace((int)*s) && *s!=C3 ; s++) + switch (*s) + { + case 'g': case 'G': + extra->flags|=(ACC_EXTRA_GLOBAL&allowed_flags); + break; + default: + LOG(L_ERR,"ERROR:acc:parse_acc_extra: unknown " + "flag (%c)\n",*s); + goto error; + } + } + + /* skip spaces */ + while (*s && isspace((int)*s)) s++; + if (*s && (*(s++)!=C3 || *s==0)) + goto parse_error; + } + + /* print_extras(head); */ + return head; +parse_error: + LOG(L_ERR,"ERROR:acc:parse_acc_extra: parse failed in <%s> " + "around position %d\n",extra_str, s-extra_str); +error: + LOG(L_ERR,"acc:parse_acc_extra: error\n"); + destroy_extras(head); + return 0; +} + + + +void destroy_extras( struct acc_extra *extra) +{ + struct acc_extra *foo; + + while (extra) + { + foo = extra; + extra = extra->next; + + if (foo->name.s) + pkg_free(foo->name.s); + if (foo->sval.s) + pkg_free(foo->sval.s); + pkg_free(foo); + } +} + + +/* extra name is moved as string part of an attribute; str.len will contain an + * index to the corresponding attribute + */ +void extra2attrs( struct acc_extra *extra, struct attr *attrs, int offset) +{ + int i; + + for(i=0 ; extra && inext) + { + attrs[offset+i].n = extra->name.s; + extra->name.s =0; + extra->name.len = offset + i; + } +} + + +/* converts the name of the extra from str to integer + * and stores it over str.len ; str.s is freed and made zero + */ +int extra2int( struct acc_extra *extra ) +{ + unsigned int ui; + int i; + + for( i=0 ; extra&&inext ) + { + if (str2int( &extra->name, &ui)!=0) + { + LOG(L_ERR,"ERROR:acc:extra2int: <%s> is not number\n", + extra->name.s); + return -1; + } + pkg_free( extra->name.s ); + extra->name.s = 0; + extra->name.len = (int)ui; + } + return 0; +} + + +static inline struct hdr_field* search_hdr(struct hdr_field *hdr, + struct acc_extra *extra) +{ + if (extra->ival==HDR_OTHER) + { + while (hdr) + { + if (extra->sval.len==hdr->name.len && + strncasecmp(extra->sval.s, hdr->name.s, hdr->name.len)==0) + return hdr; + hdr = hdr->next; + } + } else { + while (hdr) + { + if (extra->ival==hdr->type) + return hdr; + hdr = hdr->next; + } + } + return hdr; +} + + + +#define test_overflow( _n, _jumper) \ + do{\ + if ((_n)==MAX_ACC_EXTRA) \ + { \ + LOG(L_WARN,"WARNING:acc:extra2strar: array to short " \ + "-> ommiting extras for accounting\n"); \ + goto _jumper; \ + }\ + } while(0) + +#define set_acc( _n, _name, _vals) \ + do {\ + attr_arr[_n] = _name; \ + val_arr[_n] = _vals; \ + *attr_len += attr_arr[_n].len; \ + *val_len += val_arr[_n]->len; \ + (_n)++; \ + } while(0) + + +int extra2strar( struct acc_extra *extra, /* extra list to account */ + struct sip_msg *rq, /* accounted message */ + int *attr_len, /* total length of accounted attribute names */ + int *val_len, /* total length of accounted values */ + str *attr_arr, + str **val_arr) +{ + struct hdr_field *hdr; + struct usr_avp *avp; + unsigned short avp_type; + int_str avp_name; + int_str avp_val; + int hdr_parsed; + char *p; + int m; + int n; + + n = 0; + m = 0; + hdr_parsed = 0; + + while (extra) + { + if (extra->flags & ACC_EXTRA_AVP) + { + /* account an AVP */ + if (extra->flags&ACC_EXTRA_IS_INT) + { + avp_type = 0; + avp_name.n = extra->ival; + } else { + avp_type = AVP_NAME_STR; + avp_name.s = &(extra->sval); + } + avp = search_first_avp( avp_type, avp_name, &avp_val); + if (!avp) + { + test_overflow( n, done); + set_acc( n, extra->name, &na ); + } else { + do + { + test_overflow( n, done); + if (avp->flags & AVP_VAL_STR) + { + set_acc( n, extra->name, avp_val.s); + } else { + p = int2str((unsigned long)avp_val.n, &str_buf[m].len); + memcpy(str_buf[m].s, p, str_buf[m].len); + set_acc( n, extra->name, &str_buf[m]); + m++; + } + if (!(extra->flags&ACC_EXTRA_GLOBAL)) + break; + } while ((avp=search_next_avp(avp, &avp_val))!=0); + } + } else { + /* account a HEADER - first parse them all */ + if (!hdr_parsed && ++hdr_parsed && parse_headers(rq, HDR_EOH, 0)!=0) + { + LOG(L_ERR,"ERROR:acc:extra2strar: failed to parse headers ->" + " skipping hdr extras\n"); + } + hdr = search_hdr(rq->headers, extra); + if (!hdr) + { + test_overflow(n, done); + set_acc(n, extra->name, &na); + } else { + do + { + test_overflow(n, done); + set_acc(n, extra->name, &hdr->body); + if (!(extra->flags & ACC_EXTRA_GLOBAL)) + break; + } while ((hdr=search_hdr(hdr->next, extra))!=0); + } + } + + extra = extra->next; + } + +done: + return n; +} + diff -uNr modules/acc/acc_extra.h modules/acc.new/acc_extra.h --- modules/acc/acc_extra.h 1970-01-01 01:00:00.000000000 +0100 +++ modules/acc.new/acc_extra.h 2004-10-31 11:37:54.000000000 +0100 @@ -0,0 +1,69 @@ +/* + * $Id$ + * + * Copyright (C) 2004 Voice Sistem SRL + * + * This file is part of SIP Express Router. + * + * ser 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. + * + * ser 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. + * + * + * History: + * --------- + * 2004-10-28 first version (ramona) + */ + + +#ifndef _ACC_EXTRA_H_ +#define _ACC_EXTRA_H_ + +#include "../../str.h" +#include "../../parser/msg_parser.h" +#include "dict.h" + +struct acc_extra +{ + int flags; /* if attr is an avp or a header */ + str name; /* name (log comment/ column name) */ + /* value - hdr name or avp name */ + str sval; /* hdr or avp name */ + int ival; /* avp ID */ + struct acc_extra *next; +}; + +#define ACC_EXTRA_HEADER (1<<1) +#define ACC_EXTRA_AVP (1<<2) +#define ACC_EXTRA_IS_INT (1<<3) +#define ACC_EXTRA_GLOBAL (1<<4) + +#define MAX_ACC_EXTRA 64 + + + +void init_acc_extra(); + +struct acc_extra *parse_acc_extra(char *extra, int allowed_flags); + +void destroy_extras( struct acc_extra *extra); + +int extra2strar( struct acc_extra *extra, struct sip_msg *rq, + int *val_len, int *attr_len, str *attrs_arr, str **val_arr); + +void extra2attrs( struct acc_extra *extra, struct attr *attrs, int offset); + +int extra2int( struct acc_extra *extra ); + +#endif + diff -uNr modules/acc/acc_mod.c modules/acc.new/acc_mod.c --- modules/acc/acc_mod.c 2004-08-24 10:58:23.000000000 +0200 +++ modules/acc.new/acc_mod.c 2004-10-29 21:09:00.000000000 +0200 @@ -57,6 +57,7 @@ #include "acc_mod.h" #include "acc.h" +#include "acc_extra.h" #include "../tm/tm_load.h" #ifdef RAD_ACC @@ -106,14 +107,22 @@ /* noisiness level logging facilities are used */ int log_level=L_NOTICE; char *log_fmt=DEFAULT_LOG_FMT; +/* log extra variables */ +static char *log_extra_str = 0; +struct acc_extra *log_extra = 0; + + #ifdef RAD_ACC static char *radius_config = "/usr/local/etc/radiusclient/radiusclient.conf"; int radius_flag = 0; int radius_missed_flag = 0; static int service_type = -1; void *rh; -struct attr attrs[A_MAX]; +struct attr attrs[A_MAX+MAX_ACC_EXTRA]; struct val vals[V_MAX]; +/* rad extra variables */ +static char *rad_extra_str = 0; +struct acc_extra *rad_extra = 0; #endif /* DIAMETER */ @@ -122,6 +131,9 @@ int diameter_missed_flag = 2; char* diameter_client_host="localhost"; int diameter_client_port=3000; +/* diameter extra variables */ +static char *dia_extra_str = 0; +struct acc_extra *dia_extra = 0; #endif #ifdef SQL_ACC @@ -155,6 +167,10 @@ char* acc_user_col = "username"; char* acc_time_col = "time"; +/* db extra variables */ +static char *db_extra_str = 0; +struct acc_extra *db_extra = 0; + /* name of missed calls table, default=="missed_calls" */ char *db_table_mc="missed_calls"; #endif @@ -195,13 +211,15 @@ /* syslog specific */ {"log_flag", INT_PARAM, &log_flag }, {"log_missed_flag", INT_PARAM, &log_missed_flag }, - {"log_level", INT_PARAM, &log_level }, - {"log_fmt", STR_PARAM, &log_fmt }, + {"log_level", INT_PARAM, &log_level }, + {"log_fmt", STR_PARAM, &log_fmt }, + {"log_extra", STR_PARAM, &log_extra_str }, #ifdef RAD_ACC {"radius_config", STR_PARAM, &radius_config }, {"radius_flag", INT_PARAM, &radius_flag }, {"radius_missed_flag", INT_PARAM, &radius_missed_flag }, {"service_type", INT_PARAM, &service_type }, + {"rad_extra", STR_PARAM, &rad_extra_str }, #endif /* DIAMETER */ #ifdef DIAM_ACC @@ -209,6 +227,7 @@ {"diameter_missed_flag",INT_PARAM, &diameter_missed_flag}, {"diameter_client_host",STR_PARAM, &diameter_client_host}, {"diameter_client_port",INT_PARAM, &diameter_client_port}, + {"dia_extra", STR_PARAM, &dia_extra_str }, #endif /* db-specific */ #ifdef SQL_ACC @@ -227,11 +246,12 @@ {"acc_sip_callid_column", STR_PARAM, &acc_sip_callid_col}, {"acc_user_column", STR_PARAM, &acc_user_col }, {"acc_time_column", STR_PARAM, &acc_time_col }, - {"acc_from_uri_column", STR_PARAM, &acc_from_uri }, - {"acc_to_uri_column", STR_PARAM, &acc_to_uri }, - {"acc_totag_column", STR_PARAM, &acc_totag_col }, - {"acc_fromtag_column", STR_PARAM, &acc_fromtag_col }, - {"acc_domain_column", STR_PARAM, &acc_domain_col }, + {"acc_from_uri_column", STR_PARAM, &acc_from_uri }, + {"acc_to_uri_column", STR_PARAM, &acc_to_uri }, + {"acc_totag_column", STR_PARAM, &acc_totag_col }, + {"acc_fromtag_column", STR_PARAM, &acc_fromtag_col }, + {"acc_domain_column", STR_PARAM, &acc_domain_col }, + {"db_extra", STR_PARAM, &db_extra_str }, #endif {0,0,0} }; @@ -307,15 +327,35 @@ return -1; } + /* init the extra engine */ + init_acc_extra(); + + /* parse the extra string, if any */ + if (log_extra_str && (log_extra=parse_acc_extra(log_extra_str,-1))==0 ) { + LOG(L_ERR,"ERROR:acc:mod_init: failed to parse log_extra param\n"); + return -1; + } + #ifdef SQL_ACC if (acc_db_bind(db_url)<0){ LOG(L_ERR, "ERROR:acc_db_init: failed..." "did you load a database module?\n"); return -1; } + /* parse the extra string, if any */ + if (db_extra_str && (db_extra=parse_acc_extra(db_extra_str,0))==0 ) { + LOG(L_ERR,"ERROR:acc:mod_init: failed to parse db_extra param\n"); + return -1; + } #endif #ifdef RAD_ACC + /* parse the extra string, if any */ + if (rad_extra_str && (rad_extra=parse_acc_extra(rad_extra_str,-1))==0 ) { + LOG(L_ERR,"ERROR:acc:mod_init: failed to parse rad_extra param\n"); + return -1; + } + memset(attrs, 0, sizeof(attrs)); memset(attrs, 0, sizeof(vals)); attrs[A_CALLING_STATION_ID].n = "Calling-Station-Id"; @@ -334,6 +374,8 @@ vals[V_STATUS_STOP].n = "Stop"; vals[V_STATUS_FAILED].n = "Failed"; vals[V_SIP_SESSION].n = "Sip-Session"; + /* add extras as attributes */ + extra2attrs( rad_extra, attrs, A_MAX); /* open log */ rc_openlog("ser"); @@ -355,6 +397,19 @@ vals[V_SIP_SESSION].v = service_type; #endif +#ifdef DIAM_ACC + /* parse the extra string, if any */ + if (dia_extra_str && (dia_extra=parse_acc_extra(dia_extra_str,-1))==0 ) { + LOG(L_ERR,"ERROR:acc:mod_init: failed to parse dia_extra param\n"); + return -1; + } + + if (extra2int(dia_extra)!=0) { + LOG(L_ERR,"ERROR:acc:mod_init: extar names for DIAMTER must be " + " integer AVP codes\n"); + return -1; + } +#endif return 0; } @@ -395,11 +450,21 @@ static void destroy(void) { + if (log_extra) + destroy_extras( log_extra); #ifdef SQL_ACC acc_db_close(); + if (db_extra) + destroy_extras( db_extra); +#endif +#ifdef RAD_ACC + if (rad_extra) + destroy_extras( rad_extra); #endif #ifdef DIAM_ACC close_tcp_connection(sockfd); + if (dia_extra) + destroy_extras( dia_extra); #endif } diff -uNr modules/acc/defs.h modules/acc.new/defs.h --- modules/acc/defs.h 2004-08-24 10:58:23.000000000 +0200 +++ modules/acc.new/defs.h 2004-10-29 21:09:00.000000000 +0200 @@ -53,10 +53,12 @@ /* caution: keep consistent with definition of rad_attr */ #ifdef RAD_ACC # define RAD_ACC_FMT "FTocdrn" +# define RAD_ACC_FMT_LEN (sizeof(RAD_ACC_FMT)-1) #endif #ifdef DIAM_ACC # define DIAM_ACC_FMT "FTocdrn" +# define DIAM_ACC_FMT_LEN (sizeof(DIAM_ACC_FMT)-1) /* information needed for reading messages from tcp connection */ typedef struct rd_buf { diff -uNr modules/acc/doc/acc_user.sgml modules/acc.new/doc/acc_user.sgml --- modules/acc/doc/acc_user.sgml 2004-08-24 10:58:24.000000000 +0200 +++ modules/acc.new/doc/acc_user.sgml 2004-10-30 13:02:00.000000000 +0200 @@ -148,6 +148,81 @@
+ Extra accounting + + Along the static information defined via FMT-s, ACC modules allows + dynamical selection of extra information to be logged. There are two + classes of information that are accessible by extra accounting: data + from SIP messages (as headers) and internal SER data (as AVPs). + + + Selection of extra information is done via + xxx_extra parameters by specifying the names + of additional headers or AVPs you want to log. The syntax of is: + + + xxx_extra = extra_definition (';'extra_definition)* + + + extra_definition = log_name '=' data_type'/'data['/'flags] + + + data_type = 'hdr' | 'avp' + + + data = header_name | ['s:']AVP_name | 'i:'AVP_id + + + flags = 'g' + + + + + Via log_name you define how/where the + data will be logged. Its meaning depends of the accounting + support which is used: + + LOG accounting - log_name + will be just printed along with the data in + log_name=data format; + + DB accounting - log_name + will be the name of the DB column where the data will be stored. + IMPORTANT: add in db acc + table the columns corresponding to each extra data; + + RADIUS accounting - log_name + will be the AVP name used for packing the data into RADIUS message. + The log_name will be translated to AVP number via the dictionary. + IMPORTANT: add in RADIUS dictionary the + log_name attribute. + + DIAMETER accounting - log_name + will be the AVP code used for packing the data into DIAMETER + message. the AVP code is given directly as integer, since DIAMETER + has no dictionary support yet. + IMPORTANT: log_name + must be a number. + + + + + Data can be an header name or an AVP name/ID - + depending of the data_type. If header, the search + for it into SIP messages will be optimized by converting at start-up + the header name (string) into header type (integer) - this is not + possible for all headers (only for the the most important); otherwise + case insensitive string matching will be used. + + + The only defined flag is 'g' - global - which will + force logging all values of the given header or AVP. Without this + flag, only the first found value will be logged. + IMPORTANT for DB logging, this flag is disabled + from DB data structure constraints. + +
+
Dependencies The module depends on the following modules (in the other words the listed modules @@ -473,6 +548,66 @@
+
+ <varname>log_extra</varname> (string) + + Extra values to be logged. + + + Default value is NULL. + + + log_extra example + +modparam("acc", "log_extra", "ua=hdr/User-Agent;uuid=avp/i:123") + + +
+
+ <varname>db_extra</varname> (string) + + Extra values to be logged into database - DB specific. + + + Default value is NULL. + + + db_extra example + +modparam("acc", "db_extra", "ct=hdr/Content-type; email=avp/s:email") + + +
+
+ <varname>rad_extra</varname> (string) + + Extra values to be logged via RADIUS - RADIUS specific. + + + Default value is NULL. + + + rad_extra example + +modparam("acc", "rad_extra", "via=hdr/Via/g; email=avp/s:email") + + +
+
+ <varname>dia_extra</varname> (string) + + Extra values to be logged via DIAMETER - DIAMETER specific. + + + Default value is NULL. + + + dia_extra example + +modparam("acc", "dia_extra", "ct=7846/Content-type; 7847=avp/s:email") + + +