Browse code

modules_k/acc: Implement CDR-based logging.

Full credits to Sven Knoblich, sven.knoblich@1und1.de .

Timo Reimann authored on 03/08/2011 11:17:24
Showing 7 changed files
... ...
@@ -69,6 +69,7 @@
69 69
 #define A_STATUS_LEN (sizeof(A_STATUS)-1)
70 70
 
71 71
 #define A_SEPARATOR_CHR ';'
72
+#define A_SEPARATOR_CHR_2 ' '
72 73
 #define A_EQ_CHR '='
73 74
 
74 75
 #define MAX_SYSLOG_SIZE  65536
75 76
new file mode 100644
... ...
@@ -0,0 +1,692 @@
1
+/*
2
+ * Accounting module
3
+ *
4
+ * Copyright (C) 2011 - Sven Knoblich 1&1 Internet AG
5
+ *
6
+ * This file is part of Kamailio, a free SIP server.
7
+ *
8
+ * Kamailio is free software; you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation; either version 2 of the License, or
11
+ * (at your option) any later version
12
+ *
13
+ * Kamailio is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program; if not, write to the Free Software
20
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
+ *
22
+ */
23
+
24
+/*! \file
25
+ * \ingroup acc
26
+ * \brief Acc:: File to handle CRD generation by the help of the dialog-module
27
+ *
28
+ * - Module: \ref acc
29
+ */
30
+
31
+/*! \defgroup acc ACC :: The Kamailio accounting Module
32
+ *
33
+ * The ACC module is used to account transactions information to
34
+ *  different backends like syslog, SQL, RADIUS and DIAMETER (beta
35
+ *  version).
36
+ *
37
+ */
38
+#include "../../modules/tm/tm_load.h"
39
+#include "../dialog/dlg_load.h"
40
+
41
+#include "acc_api.h"
42
+#include "acc_cdr.h"
43
+#include "acc_mod.h"
44
+#include "acc_extra.h"
45
+#include "acc.h"
46
+
47
+#include <sys/timeb.h>
48
+
49
+struct dlg_binds dlgb;
50
+struct acc_extra* cdr_extra = NULL;
51
+int cdr_facility = LOG_DAEMON;
52
+
53
+static const str start_id = { "sz", 2};
54
+static const str end_id = { "ez", 2};
55
+static const str duration_id = { "d", 1};
56
+static const str zero_duration = { "0", 1};
57
+static const struct timeb time_error = {0,0,0,0};
58
+static const char time_separator = {'.'};
59
+static const int milliseconds_max = 1000;
60
+static const unsigned int time_buffer_length = 256;
61
+static const str empty_string = { "", 0};
62
+
63
+// buffers which are used to collect the crd data for writing
64
+static str cdr_attrs[ MAX_CDR_CORE + MAX_CDR_EXTRA];
65
+static str cdr_value_array[ MAX_CDR_CORE + MAX_CDR_EXTRA];
66
+static int cdr_int_arr[ MAX_CDR_CORE + MAX_CDR_EXTRA];
67
+static char cdr_type_array[ MAX_CDR_CORE + MAX_CDR_EXTRA];
68
+
69
+extern struct tm_binds tmb;
70
+
71
+/* compare two times */
72
+static int is_time_equal( struct timeb first_time,
73
+                          struct timeb second_time)
74
+{
75
+    if( first_time.time == second_time.time &&
76
+        first_time.millitm == second_time.millitm &&
77
+        first_time.timezone == second_time.timezone &&
78
+        first_time.dstflag == second_time.dstflag )
79
+    {
80
+        return 1;
81
+    }
82
+
83
+    return 0;
84
+}
85
+
86
+/* write all basic information to buffers(e.g. start-time ...) */
87
+static int cdr_core2strar( struct dlg_cell* dlg,
88
+                           str* values,
89
+                           int* unused,
90
+                           char* types)
91
+{
92
+    str* start = NULL;
93
+    str* end = NULL;
94
+    str* duration = NULL;
95
+
96
+    if( !dlg || !values || !types)
97
+    {
98
+        LM_ERR( "invalid input parameter!\n");
99
+        return 0;
100
+    }
101
+
102
+    start = dlgb.get_dlg_var( dlg, (str*)&start_id);
103
+    end = dlgb.get_dlg_var( dlg, (str*)&end_id);
104
+    duration = dlgb.get_dlg_var( dlg, (str*)&duration_id);
105
+
106
+    values[0] = ( start != NULL ? *start : empty_string);
107
+    types[0] = ( start != NULL ? TYPE_STR : TYPE_NULL);
108
+
109
+    values[1] = ( end != NULL ? *end : empty_string);
110
+    types[1] = ( end != NULL ? TYPE_STR : TYPE_NULL);
111
+
112
+    values[2] = ( duration != NULL ? *duration : empty_string);
113
+    types[2] = ( duration != NULL ? TYPE_STR : TYPE_NULL);
114
+
115
+    return MAX_CDR_CORE;
116
+}
117
+
118
+/* collect all crd data and write it to a syslog */
119
+static int write_cdr( struct dlg_cell* dialog,
120
+                      struct sip_msg* message)
121
+{
122
+    static char cdr_message[ MAX_SYSLOG_SIZE];
123
+    static char* const cdr_message_end = cdr_message +
124
+                                         MAX_SYSLOG_SIZE -
125
+                                         2;// -2 because of the string ending '\n\0'
126
+    char* message_position = NULL;
127
+    int message_index = 0;
128
+    int counter = 0;
129
+
130
+    if( !dialog || !message)
131
+    {
132
+        LM_ERR( "dialog and/or message is/are empty!");
133
+        return -1;
134
+    }
135
+
136
+    /* get default values */
137
+    message_index = cdr_core2strar( dialog,
138
+                                    cdr_value_array,
139
+                                    cdr_int_arr,
140
+                                    cdr_type_array);
141
+
142
+    /* get extra values */
143
+    message_index += extra2strar( cdr_extra,
144
+                                  message,
145
+                                  cdr_value_array + message_index,
146
+                                  cdr_int_arr + message_index,
147
+                                  cdr_type_array + message_index);
148
+
149
+    for( counter = 0, message_position = cdr_message;
150
+         counter < message_index ;
151
+         counter++ )
152
+    {
153
+        const char* const next_message_end = message_position +
154
+                                             2 + // ', ' -> two letters
155
+                                             cdr_attrs[ counter].len +
156
+                                             1 + // '=' -> one letter
157
+                                             cdr_value_array[ counter].len;
158
+
159
+        if( next_message_end >= cdr_message_end ||
160
+            next_message_end < message_position)
161
+        {
162
+            LM_WARN("cdr message too long, truncating..\n");
163
+            message_position = cdr_message_end;
164
+            break;
165
+        }
166
+
167
+        if( counter > 0)
168
+        {
169
+            *(message_position++) = A_SEPARATOR_CHR;
170
+            *(message_position++) = A_SEPARATOR_CHR_2;
171
+        }
172
+
173
+        memcpy( message_position,
174
+                cdr_attrs[ counter].s,
175
+                cdr_attrs[ counter].len);
176
+
177
+        message_position += cdr_attrs[ counter].len;
178
+
179
+        *( message_position++) = A_EQ_CHR;
180
+
181
+        memcpy( message_position,
182
+                cdr_value_array[ counter].s,
183
+                cdr_value_array[ counter].len);
184
+
185
+        message_position += cdr_value_array[ counter].len;
186
+    }
187
+
188
+    /* terminating line */
189
+    *(message_position++) = '\n';
190
+    *(message_position++) = '\0';
191
+
192
+    LM_GEN2( cdr_facility, log_level, "%s", cdr_message);
193
+
194
+    return 0;
195
+}
196
+
197
+/* convert a string into a timeb struct */
198
+static struct timeb time_from_string( str* time_value)
199
+{
200
+    char* point_adresse = NULL;
201
+    int point_position = -1;
202
+
203
+    if( !time_value)
204
+    {
205
+        LM_ERR( "time_value is empty!");
206
+        return time_error;
207
+    }
208
+
209
+    point_adresse = strchr( time_value->s, time_separator);
210
+
211
+    if( !point_adresse)
212
+    {
213
+        LM_ERR( "failed to find separator('%c') in '%s'!\n",
214
+                time_separator,
215
+                time_value->s);
216
+        return time_error;
217
+    }
218
+
219
+    point_position = point_adresse-time_value->s + 1;
220
+
221
+    if( point_position >= strlen(time_value->s) ||
222
+        strchr(point_adresse + 1, time_separator))
223
+    {
224
+        LM_ERR( "invalid time-string '%s'\n", time_value->s);
225
+        return time_error;
226
+    }
227
+
228
+    return (struct timeb) { atoi( time_value->s),
229
+                            atoi( point_adresse + 1),
230
+                            0,
231
+                            0};
232
+}
233
+
234
+/* set the duration in the dialog struct */
235
+static int set_duration( struct dlg_cell* dialog)
236
+{
237
+    struct timeb start_timeb = time_error;
238
+    struct timeb end_timeb = time_error;
239
+    int milliseconds = -1;
240
+    int seconds = -1;
241
+    char buffer[ time_buffer_length];
242
+    int buffer_length = -1;
243
+    str duration_time = empty_string;
244
+
245
+    if( !dialog)
246
+    {
247
+        LM_ERR("dialog is empty!\n");
248
+        return -1;
249
+    }
250
+
251
+    start_timeb = time_from_string( dlgb.get_dlg_var( dialog, (str*)&start_id));
252
+    end_timeb  = time_from_string( dlgb.get_dlg_var( dialog, (str*)&end_id));
253
+
254
+    if( is_time_equal( start_timeb, time_error) ||
255
+        is_time_equal( end_timeb, time_error))
256
+    {
257
+        LM_ERR( "failed to extract time from start or/and end-time\n");
258
+        return -1;
259
+    }
260
+
261
+    if( start_timeb.millitm >= milliseconds_max ||
262
+        end_timeb.millitm >= milliseconds_max)
263
+    {
264
+        LM_ERR( "start-(%d) or/and end-time(%d) is out of the maximum of %d\n",
265
+                start_timeb.millitm,
266
+                end_timeb.millitm,
267
+                milliseconds_max);
268
+        return -1;
269
+    }
270
+
271
+    milliseconds = end_timeb.millitm < start_timeb.millitm ?
272
+                                ( milliseconds_max +
273
+                                  end_timeb.millitm -
274
+                                  start_timeb.millitm) :
275
+                                ( end_timeb.millitm - start_timeb.millitm);
276
+
277
+    seconds = end_timeb.time -
278
+              start_timeb.time -
279
+              ( end_timeb.millitm < start_timeb.millitm ? 1 : 0);
280
+
281
+    if( seconds < 0)
282
+    {
283
+        LM_ERR( "negativ seconds(%d) for duration calculated.\n", seconds);
284
+        return -1;
285
+    }
286
+
287
+    if( milliseconds < 0 || milliseconds >= milliseconds_max)
288
+    {
289
+        LM_ERR( "milliseconds %d are out of range 0 < x < %d.\n",
290
+                milliseconds,
291
+                milliseconds_max);
292
+        return -1;
293
+    }
294
+
295
+    buffer_length = snprintf( buffer,
296
+                              time_buffer_length,
297
+                              "%d%c%03d",
298
+                              seconds,
299
+                              time_separator,
300
+                              milliseconds);
301
+
302
+    if( buffer_length < 0)
303
+    {
304
+        LM_ERR( "failed to write to buffer.\n");
305
+        return -1;
306
+    }
307
+
308
+    duration_time = ( str){ buffer, buffer_length};
309
+
310
+    if( dlgb.set_dlg_var( dialog,
311
+                          (str*)&duration_id,
312
+                          (str*)&duration_time) != 0)
313
+    {
314
+        LM_ERR( "failed to set duration time");
315
+        return -1;
316
+    }
317
+
318
+    return 0;
319
+}
320
+
321
+/* set the current time as start-time in the dialog struct */
322
+static int set_start_time( struct dlg_cell* dialog)
323
+{
324
+    char buffer[ time_buffer_length];
325
+    struct timeb current_time = time_error;
326
+    int buffer_length = -1;
327
+    str start_time = empty_string;
328
+
329
+    if( !dialog)
330
+    {
331
+        LM_ERR("dialog is empty!\n");
332
+        return -1;
333
+    }
334
+
335
+    if( ftime( &current_time) < 0)
336
+    {
337
+        LM_ERR( "failed to get current time!\n");
338
+        return -1;
339
+    }
340
+
341
+    buffer_length = snprintf( buffer,
342
+                              time_buffer_length,
343
+                              "%d%c%03d",
344
+                              (int)current_time.time,
345
+                              time_separator,
346
+                              (int)current_time.millitm);
347
+
348
+    if( buffer_length < 0)
349
+    {
350
+        LM_ERR( "reach buffer size\n");
351
+        return -1;
352
+    }
353
+
354
+    start_time = (str){ buffer, buffer_length};
355
+
356
+    if( dlgb.set_dlg_var( dialog,
357
+                          (str*)&start_id,
358
+                          (str*)&start_time) != 0)
359
+    {
360
+        LM_ERR( "failed to set start time\n");
361
+        return -1;
362
+    }
363
+
364
+    if( dlgb.set_dlg_var( dialog,
365
+                          (str*)&end_id,
366
+                          (str*)&start_time) != 0)
367
+    {
368
+        LM_ERR( "failed to set initiation end time\n");
369
+        return -1;
370
+    }
371
+
372
+    if( dlgb.set_dlg_var( dialog,
373
+                          (str*)&duration_id,
374
+                          (str*)&zero_duration) != 0)
375
+    {
376
+        LM_ERR( "failed to set initiation duration time\n");
377
+        return -1;
378
+    }
379
+
380
+    return 0;
381
+}
382
+
383
+/* set the current time as end-time in the dialog struct */
384
+static int set_end_time( struct dlg_cell* dialog)
385
+{
386
+    char buffer[ time_buffer_length];
387
+    struct timeb current_time = time_error;
388
+    int buffer_length = -1;
389
+    str end_time = empty_string;
390
+
391
+    if( !dialog)
392
+    {
393
+        LM_ERR("dialog is empty!\n");
394
+        return -1;
395
+    }
396
+
397
+    if( ftime( &current_time) < 0)
398
+    {
399
+        LM_ERR( "failed to set time!\n");
400
+        return -1;
401
+    }
402
+
403
+    buffer_length = snprintf( buffer,
404
+                              time_buffer_length,
405
+                              "%d%c%03d",
406
+                              (int)current_time.time,
407
+                              time_separator,
408
+                              (int)current_time.millitm);
409
+
410
+    if( buffer_length < 0)
411
+    {
412
+        LM_ERR( "failed to write buffer\n");
413
+        return -1;
414
+    }
415
+
416
+    end_time = ( str){ buffer, buffer_length};
417
+
418
+    if( dlgb.set_dlg_var( dialog,
419
+                          (str*)&end_id,
420
+                          (str*)&end_time) != 0)
421
+    {
422
+        LM_ERR( "failed to set start time");
423
+        return -1;
424
+    }
425
+
426
+    return 0;
427
+}
428
+
429
+/* callback for a confirmed (INVITE) dialog. */
430
+static void cdr_on_start( struct dlg_cell* dialog,
431
+                          int type,
432
+                          struct dlg_cb_params* params)
433
+{
434
+    if( !dialog || !params)
435
+    {
436
+        LM_ERR("invalid values\n!");
437
+        return;
438
+    }
439
+
440
+    if( cdr_start_on_confirmed == 0)
441
+    {
442
+        return;
443
+    }
444
+
445
+    if( set_start_time( dialog) != 0)
446
+    {
447
+        LM_ERR( "failed to set start time!\n");
448
+        return;
449
+    }
450
+}
451
+
452
+/* callback for a failure during a dialog. */
453
+static void cdr_on_failed( struct dlg_cell* dialog,
454
+                           int type,
455
+                           struct dlg_cb_params* params)
456
+{
457
+    struct sip_msg* msg = 0;
458
+
459
+    if( !dialog || !params)
460
+    {
461
+        LM_ERR("invalid values\n!");
462
+        return;
463
+    }
464
+
465
+    if( params->rpl && params->rpl != FAKED_REPLY)
466
+    {
467
+        msg = params->rpl;
468
+    }
469
+    else if( params->req)
470
+    {
471
+        msg = params->req;
472
+    }
473
+    else
474
+    {
475
+        LM_ERR( "request and response are invalid!");
476
+        return;
477
+    }
478
+
479
+    if( write_cdr( dialog, msg) != 0)
480
+    {
481
+        LM_ERR( "failed to write cdr!\n");
482
+        return;
483
+    }
484
+}
485
+
486
+/* callback for the finish of a dialog (reply to BYE). */
487
+void cdr_on_end_confirmed( struct dlg_cell* dialog,
488
+                        int type,
489
+                        struct dlg_cb_params* params)
490
+{
491
+    if( !dialog || !params || !params->req)
492
+    {
493
+        LM_ERR("invalid values\n!");
494
+        return;
495
+    }
496
+
497
+    if( write_cdr( dialog, params->req) != 0)
498
+    {
499
+        LM_ERR( "failed to write cdr!\n");
500
+        return;
501
+    }
502
+}
503
+
504
+/* callback for the end of a dialog (BYE). */
505
+static void cdr_on_end( struct dlg_cell* dialog,
506
+                        int type,
507
+                        struct dlg_cb_params* params)
508
+{
509
+    if( !dialog || !params || !params->req)
510
+    {
511
+        LM_ERR("invalid values\n!");
512
+        return;
513
+    }
514
+
515
+    if( set_end_time( dialog) != 0)
516
+    {
517
+        LM_ERR( "failed to set end time!\n");
518
+        return;
519
+    }
520
+
521
+    if( set_duration( dialog) != 0)
522
+    {
523
+        LM_ERR( "failed to set duration!\n");
524
+        return;
525
+    }
526
+}
527
+
528
+/* callback for a expired dialog. */
529
+static void cdr_on_expired( struct dlg_cell* dialog,
530
+                            int type,
531
+                            struct dlg_cb_params* params)
532
+{
533
+    if( !dialog || !params)
534
+    {
535
+        LM_ERR("invalid values\n!");
536
+        return;
537
+    }
538
+
539
+    LM_DBG("dialog '%p' expired!\n", dialog);
540
+}
541
+
542
+/* callback for the cleanup of a dialog. */
543
+static void cdr_on_destroy( struct dlg_cell* dialog,
544
+                            int type,
545
+                            struct dlg_cb_params* params)
546
+{
547
+    if( !dialog || !params)
548
+    {
549
+        LM_ERR("invalid values\n!");
550
+        return;
551
+    }
552
+
553
+    LM_DBG("dialog '%p' destroyed!\n", dialog);
554
+}
555
+
556
+/* callback for the creation of a dialog. */
557
+static void cdr_on_create( struct dlg_cell* dialog,
558
+                           int type,
559
+                           struct dlg_cb_params* params)
560
+{
561
+    if( !dialog || !params || !params->req)
562
+    {
563
+        LM_ERR( "invalid values\n!");
564
+        return;
565
+    }
566
+
567
+    if( cdr_enable == 0)
568
+    {
569
+        return;
570
+    }
571
+
572
+    if( dlgb.register_dlgcb( dialog, DLGCB_CONFIRMED, cdr_on_start, 0, 0) != 0)
573
+    {
574
+        LM_ERR("can't register create dialog CONFIRM callback\n");
575
+        return;
576
+    }
577
+
578
+    if( dlgb.register_dlgcb( dialog, DLGCB_FAILED, cdr_on_failed, 0, 0) != 0)
579
+    {
580
+        LM_ERR("can't register create dialog FAILED callback\n");
581
+        return;
582
+    }
583
+
584
+    if( dlgb.register_dlgcb( dialog, DLGCB_TERMINATED, cdr_on_end, 0, 0) != 0)
585
+    {
586
+        LM_ERR("can't register create dialog TERMINATED callback\n");
587
+        return;
588
+    }
589
+
590
+    if( dlgb.register_dlgcb( dialog, DLGCB_TERMINATED_CONFIRMED, cdr_on_end_confirmed, 0, 0) != 0)
591
+    {
592
+        LM_ERR("can't register create dialog TERMINATED CONFIRMED callback\n");
593
+        return;
594
+    }
595
+
596
+    if( dlgb.register_dlgcb( dialog, DLGCB_EXPIRED, cdr_on_expired, 0, 0) != 0)
597
+    {
598
+        LM_ERR("can't register create dialog EXPIRED callback\n");
599
+        return;
600
+    }
601
+
602
+    if( dlgb.register_dlgcb( dialog, DLGCB_DESTROY, cdr_on_destroy, 0, 0) != 0)
603
+    {
604
+        LM_ERR("can't register create dialog DESTROY callback\n");
605
+        return;
606
+    }
607
+
608
+    LM_DBG("dialog '%p' created!", dialog);
609
+
610
+    if( set_start_time( dialog) != 0)
611
+    {
612
+        LM_ERR( "failed to set start time");
613
+        return;
614
+    }
615
+}
616
+/* convert the extra-data string into a list and store it */
617
+int set_cdr_extra( char* cdr_extra_value)
618
+{
619
+    struct acc_extra* extra = 0;
620
+    int counter = 0;
621
+
622
+    if( cdr_extra_value && ( cdr_extra = parse_acc_extra( cdr_extra_value))==0)
623
+    {
624
+        LM_ERR("failed to parse crd_extra param\n");
625
+        return -1;
626
+    }
627
+
628
+    /* fixed core attributes */
629
+    cdr_attrs[ counter++] = start_id;
630
+    cdr_attrs[ counter++] = end_id;
631
+    cdr_attrs[ counter++] = duration_id;
632
+
633
+    for(extra=cdr_extra; extra ; extra=extra->next)
634
+    {
635
+        cdr_attrs[ counter++] = extra->name;
636
+    }
637
+
638
+    return 0;
639
+}
640
+
641
+/* convert the facility-name string into a id and store it */
642
+int set_cdr_facility( char* cdr_facility_str)
643
+{
644
+    int facility_id = -1;
645
+
646
+    if( !cdr_facility_str)
647
+    {
648
+        LM_ERR( "facility is empty\n");
649
+        return -1;
650
+    }
651
+
652
+    facility_id = str2facility( cdr_facility_str);
653
+
654
+    if( facility_id == -1)
655
+    {
656
+        LM_ERR("invalid cdr facility configured\n");
657
+        return -1;
658
+    }
659
+
660
+    cdr_facility = facility_id;
661
+
662
+    return 0;
663
+}
664
+
665
+/* initialization of all necessary callbacks to track a dialog */
666
+int init_cdr_generation( void)
667
+{
668
+    if( load_dlg_api( &dlgb) != 0)
669
+    {
670
+        LM_ERR("can't load dialog API\n");
671
+        return -1;
672
+    }
673
+
674
+    if( dlgb.register_dlgcb( 0, DLGCB_CREATED, cdr_on_create, 0, 0) != 0)
675
+    {
676
+        LM_ERR("can't register create callback\n");
677
+        return -1;
678
+    }
679
+
680
+    return 0;
681
+}
682
+
683
+/* convert the facility-name string into a id and store it */
684
+void destroy_cdr_generation( void)
685
+{
686
+    if( !cdr_extra)
687
+    {
688
+        return;
689
+    }
690
+
691
+    destroy_extras( cdr_extra);
692
+}
0 693
new file mode 100644
... ...
@@ -0,0 +1,49 @@
1
+/*
2
+ * Accounting module
3
+ *
4
+ * Copyright (C) 2011 - Sven Knoblich 1&1 Internet AG
5
+ *
6
+ * This file is part of Kamailio, a free SIP server.
7
+ *
8
+ * Kamailio is free software; you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation; either version 2 of the License, or
11
+ * (at your option) any later version
12
+ *
13
+ * Kamailio is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program; if not, write to the Free Software
20
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
+ *
22
+ */
23
+
24
+/*! \file
25
+ * \ingroup acc
26
+ * \brief Acc:: File to handle CRD generation by the help of the dialog-module
27
+ *
28
+ * - Module: \ref acc
29
+ */
30
+
31
+/*! \defgroup acc ACC :: The Kamailio accounting Module
32
+ *
33
+ * The ACC module is used to account transactions information to
34
+ *  different backends like syslog, SQL, RADIUS and DIAMETER (beta
35
+ *  version).
36
+ *
37
+ */
38
+
39
+#define MAX_CDR_CORE 3
40
+#define MAX_CDR_EXTRA 64
41
+
42
+
43
+int set_cdr_extra( char* cdr_extra_value);
44
+int set_cdr_facility( char* cdr_facility);
45
+int init_cdr_generation( void);
46
+void destroy_cdr_generation( void);
47
+
48
+
49
+
... ...
@@ -73,6 +73,7 @@
73 73
 #include "acc_mod.h"
74 74
 #include "acc_extra.h"
75 75
 #include "acc_logic.h"
76
+#include "acc_cdr.h"
76 77
 
77 78
 #ifdef RAD_ACC
78 79
 #include "../../lib/kcore/radius.h"
... ...
@@ -110,7 +111,7 @@ struct acc_extra *leg_info = 0;
110 111
 
111 112
 
112 113
 /* ----- SYSLOG acc variables ----------- */
113
-/*! \name AccSyslogVariables  Syslog Variables */     
114
+/*! \name AccSyslogVariables  Syslog Variables */
114 115
 /*@{*/
115 116
 
116 117
 int log_flag = -1;
... ...
@@ -123,6 +124,16 @@ struct acc_extra *log_extra = 0; /*!< Log extra attributes */
123 124
 
124 125
 /*@}*/
125 126
 
127
+/* ----- CDR generation variables ------- */
128
+/*! \name AccCdrVariables  CDR Variables */
129
+/*@{*/
130
+
131
+int cdr_enable  = 0;
132
+int cdr_start_on_confirmed = 0;
133
+static char* cdr_facility_str = 0;
134
+static char* cdr_log_extra_str = 0;
135
+/*@{*/
136
+
126 137
 /* ----- RADIUS acc variables ----------- */
127 138
 /*! \name AccRadiusVariables  Radius Variables */     
128 139
 /*@{*/
... ...
@@ -230,6 +241,11 @@ static param_export_t params[] = {
230 241
 	{"log_level",            INT_PARAM, &log_level            },
231 242
 	{"log_facility",         STR_PARAM, &log_facility_str     },
232 243
 	{"log_extra",            STR_PARAM, &log_extra_str        },
244
+    /* cdr specific */
245
+    {"cdr_enable",           INT_PARAM, &cdr_enable                     },
246
+    {"cdr_start_on_confirmed", INT_PARAM, &cdr_start_on_confirmed   },
247
+    {"cdr_facility",         STR_PARAM, &cdr_facility_str                },
248
+    {"cdr_extra",            STR_PARAM, &cdr_log_extra_str              },
233 249
 #ifdef RAD_ACC
234 250
 	{"radius_config",        STR_PARAM, &radius_config        },
235 251
 	{"radius_flag",          INT_PARAM, &radius_flag          },
... ...
@@ -503,6 +519,35 @@ static int mod_init( void )
503 519
 
504 520
 	acc_log_init();
505 521
 
522
+    /* ----------- INIT CDR GENERATION ----------- */
523
+
524
+    if( cdr_enable < 0 || cdr_enable > 1)
525
+    {
526
+        LM_ERR("cdr_enable is out of rage\n");
527
+        return -1;
528
+    }
529
+
530
+    if( cdr_enable)
531
+    {
532
+        if( cdr_log_extra_str && set_cdr_extra( cdr_log_extra_str) != 0)
533
+        {
534
+            LM_ERR( "failed to set cdr extra '%s'\n", cdr_log_extra_str);
535
+            return -1;
536
+        }
537
+
538
+        if( cdr_facility_str && set_cdr_facility( cdr_facility_str) != 0)
539
+        {
540
+            LM_ERR( "failed to set cdr facility '%s'\n", cdr_facility_str);
541
+            return -1;
542
+        }
543
+
544
+        if( init_cdr_generation() != 0)
545
+        {
546
+            LM_ERR("failed to init cdr generation\n");
547
+            return -1;
548
+        }
549
+    }
550
+
506 551
 	/* ------------ SQL INIT SECTION ----------- */
507 552
 
508 553
 #ifdef SQL_ACC
... ...
@@ -55,6 +55,9 @@ extern int log_level;
55 55
 extern int log_flag;
56 56
 extern int log_missed_flag;
57 57
 
58
+extern int cdr_enable;
59
+extern int cdr_start_on_confirmed;
60
+extern int cdr_log_facility;
58 61
 
59 62
 #ifdef RAD_ACC
60 63
 extern int radius_flag;
... ...
@@ -37,6 +37,12 @@
37 37
 		<affiliation><orgname>Voice Sistem SRL</orgname></affiliation>
38 38
 		<email>bogdan@voice-system.ro</email>
39 39
 		</editor>
40
+		<editor>
41
+		<firstname>Sven</firstname>
42
+		<surname>Knoblich</surname>
43
+		<affiliation><orgname>1&amp;1 Internet AG</orgname></affiliation>
44
+		<email>sven.knoblich@1und1.de</email>
45
+		</editor>
40 46
 	</authorgroup>
41 47
 	<copyright>
42 48
 		<year>2002</year>
... ...
@@ -48,6 +54,16 @@
48 54
 		<year>2006</year>
49 55
 		<holder>Voice Sistem SRL</holder>
50 56
 	</copyright>
57
+	<copyright>
58
+		<year>2011</year>
59
+		<holder>1&amp;1 Internet AG</holder>
60
+	</copyright>
61
+	<revhistory>
62
+		<revision>
63
+		<revnumber>$Revision$</revnumber>
64
+		<date>$Date$</date>
65
+		</revision>
66
+	</revhistory>
51 67
 	</bookinfo>
52 68
 	<toc></toc>
53 69
 
... ...
@@ -84,15 +84,6 @@
84 84
 			types of routes.
85 85
 			</para>
86 86
 		</listitem>
87
-		<listitem>
88
-			<para>
89
-			There is no session/dialog accounting (yet) -- &kamailio; maintains
90
-			no sessions. If one needs to correlate INVITEs with BYEs for 
91
-			generating proper CDRs for example for purpose of billing, then 
92
-			it is better done in the entity which processes accounting 
93
-			information.
94
-			</para>
95
-		</listitem>
96 87
 		<listitem>
97 88
 			<para>
98 89
 			If a UA fails in middle of conversation, a proxy will never 
... ...
@@ -173,7 +164,7 @@ if (uri=~"sip:+40") /* calls to Romania */ {
173 164
 			the request, etc).
174 165
 			</para>
175 166
 		</section>
176
-		<section>
167
+		<section id="acc-def-syn">
177 168
 			<title>Definitions and syntax</title>
178 169
 			<para>
179 170
 			Selection of extra information is done via 
... ...
@@ -326,8 +317,127 @@ if (uri=~"sip:+40") /* calls to Romania */ {
326 317
 			</itemizedlist>
327 318
 		</section>
328 319
 	</section>
320
+	<section>
321
+		<title>Call Data Record generation</title>
322
+        <section>
323
+            <title>Overview</title>
324
+                <para>
325
+                It is possible to generate and log Call Data Records (CDRs) directly from &kamailio;
326
+                in addition to transaction-based logging. Apart from a basic set of CDR fields which
327
+                are always included (covering start time, end time, and duration), the approach allows
328
+                flexible specification of additional fields that should be taken into account using
329
+                the configuration script. This is very similar to how transaction-based logging may
330
+                be customized with the exception that CDRs rely on dialogs instead of transactions
331
+                to store relevant information during a call.
332
+                </para>
333
+
334
+                <para>
335
+                In order to set up CDR generation, you must enable the CDR switch and load the dialog
336
+                module. You probably also want to specify a set of pseudo-variables that define more
337
+                relevant CDR fields. Pseudo-variables may be assigned arbitrarily during script
338
+                execution, and the module will make sure that the variable content will be transformed
339
+                into a CDR by the end of the dialog.
340
+                </para>
341
+
342
+                <para>
343
+                To use CDR logging in a correct manner, you should only use the dialog-based
344
+                pseudo-variables (dlg_var) from the dialog module. This allows you to save values
345
+                right from the beginning through all requests and replies until termination of the
346
+                call. While not recommended, it is still possible to use other pseudo-variables as
347
+                well. Except for pseudo-variables valid in the call-final transaction, however,
348
+                information given will not be stored in the CDR as they cannot be accessed by the
349
+                end of the call when the CDR is logged.
350
+                </para>
351
+        </section>
352
+		<section id="cdr-extra-id">
353
+			<title>CDR Extra</title>
354
+					This section is similar to the <quote>LOG accounting</quote> part of
355
+					<xref linkend="ACC-extra-id"/>.
356
+			<section>
357
+				<title>Definitions and syntax</title>
358
+					<para>
359
+					Selection of extra information is done similar to the transaction extra
360
+					<xref linkend="acc-def-syn"/>.
361
+					</para>
362
+					<itemizedlist>
363
+						<listitem><para><emphasis>
364
+						cdr_log_extra = cdr_extra_definition (';'cdr_extra_definition)*
365
+						</emphasis></para></listitem>
366
+						<listitem><para><emphasis>
367
+						cdr_extra_definition = cdr_log_name '=' pseudo_variable
368
+						</emphasis></para></listitem>
369
+					</itemizedlist>
370
+					See also <xref linkend="cdr_log_extra"/>.
371
+					<para>
372
+					The full list of supported pseudo-variables in Sip-Router is
373
+					available at:
374
+					<ulink url="http://sip-router.org/wiki/cookbooks/pseudo-variables/devel">
375
+					http://sip-router.org/wiki/cookbooks/pseudo-variables/devel</ulink>
376
+					</para>
377
+			</section>
378
+		</section>
379
+		<section id="multi-cdr-call-legs">
380
+			<title>CDR with Multi Call-Legs</title>
381
+			<section>
382
+				<title>Overview</title>
383
+				<para>
384
+				As mentioned in <xref linkend="multi-call-legs"/>, a leg represents a parallel
385
+				or forwarded call. In contrast to the normal accounting the cdr logging uses dialogs
386
+				instead of transaction to log data. This may reduce the amount of information 
387
+				but it also make it possible to combine all important data in one cdr at 
388
+				once. A second mechanism to process multiple data-sets into one cdr is not further
389
+				necessary.
390
+				</para>
391
+			</section>
392
+			<section>
393
+				<title>Configuration</title>
394
+				<para>
395
+				When you route messages multiple times through your proxy (e.g. to
396
+				handle <quote>call-forwardings</quote>) you have to use detect_spirals
397
+				from the dialog modules. Otherwise the proxy can't identify and reuse existing
398
+				dialogs.
399
+				</para>
400
+				<para>
401
+				To get the correct call-forwarding-chain you have to store each cf* with the
402
+				corresponding caller and callee in a dialog based pseudo-variable (dlg_var)
403
+				(e.g. chain=B;cfa;C|C;cfnr;D). Additionally it is necessary to store the
404
+				caller and callee for each leg. All this helps to identify the involved
405
+				phone parners and forwarding chain. When you route such calls multiple times
406
+				to the same Proxy, you could store the caller and callee within an transaction
407
+				based avp and write it into the dialog based dlg_var pv during a 200 INVITE.
408
+				</para>
409
+				<section>
410
+					<title>Example for a spiraled Proxy</title>
411
+					<programlisting format="linespecific">
412
+# A calls B (transaction 1)
413
+$avp(caller)='A'
414
+$avp(callee)='B';
415
+$dlg_var(chain)='';
329 416
 
417
+# B cfa C (transaction 2)
418
+$avp(caller)='B'
419
+$avp(callee)='C';
420
+$dlg_var(chain)='B;cfu;C';
330 421
 
422
+# C cfnr D (transaction 3)
423
+$avp(caller)='C'
424
+$avp(callee)='D';
425
+$dlg_var(chain)=$dlg_var(chain) + "|" + "C;cfnr;D";
426
+
427
+# C confirms call (200 reply of transaction 2)
428
+$dlg_var(caller) = $avp(caller); #caller='B'
429
+$dlg_var(callee) = $avp(callee); #callee='C'
430
+					</programlisting>
431
+				</section>
432
+			</section>
433
+			<section>
434
+				<title>Logged data</title>
435
+				For each call, all dialog corresponding variables will be logged. After a call
436
+				is finished, the generated call data record information will be logged as string
437
+				(VAR1=xxx,VAR2=xxxx,...) to the syslog.
438
+			</section>
439
+		</section>
440
+	</section>
331 441
 	<section>
332 442
 		<title>Dependencies</title>
333 443
 		<section>
... ...
@@ -348,6 +458,11 @@ if (uri=~"sip:+40") /* calls to Romania */ {
348 458
 				<quote>detect_direction</quote> module parameter is enabled.
349 459
 				</para>
350 460
 				</listitem>
461
+				<listitem>
462
+				<para><emphasis>dialog</emphasis> -- Dialog, if
463
+				<quote>cdr_enable</quote> module parameter is enabled.
464
+				</para>
465
+				</listitem>
351 466
 			</itemizedlist>
352 467
 			</para>
353 468
 		</section>
... ...
@@ -572,7 +687,6 @@ modparam("acc", "log_facility", "LOG_DAEMON")
572 687
 </programlisting>
573 688
 		</example>
574 689
 	</section>
575
-
576 690
 	<section>
577 691
 		<title><varname>log_extra</varname> (string)</title>
578 692
 		<para>
... ...
@@ -962,6 +1076,73 @@ modparam("acc", "diameter_extra", "7846=$hdr(Content-type);7847=$avp(s:email)")
962 1076
 </programlisting>
963 1077
 		</example>
964 1078
 	</section>
1079
+	<section>
1080
+		<title><varname>cdr_enable</varname> (integer)</title>
1081
+		<para>
1082
+		Should CDR-based logging be enabled?
1083
+		</para>
1084
+		<para>
1085
+		0 - off (default)
1086
+		1 - on
1087
+		</para>
1088
+		<example>
1089
+		<title>cdr_enable example</title>
1090
+		<programlisting format="linespecific">
1091
+modparam("acc", "cdr_enable", 1)
1092
+</programlisting>
1093
+		</example>
1094
+	</section>
1095
+	<section>
1096
+		<title><varname>cdr_start_when_confirmed</varname> (integer)</title>
1097
+		<para>
1098
+		Should the start time be taken from the time when the dialog is created,
1099
+        or when the dialog is confirmed?
1100
+		</para>
1101
+		<para>
1102
+		0 - use time of dialog creation (default).
1103
+		1 - use time of dialog confirmation.
1104
+		</para>
1105
+		<example>
1106
+		<title>cdr_start_when_confirmed example</title>
1107
+		<programlisting format="linespecific">
1108
+modparam("acc", "cdr_start_when_confirmed", 1)
1109
+</programlisting>
1110
+		</example>
1111
+	</section>
1112
+	<section>
1113
+		<title><varname>cdr_log_facility</varname> (integer)</title>
1114
+		<para>
1115
+		Log facility to which CDR messages are issued to syslog.
1116
+		This allows to easily seperate CDR-specific logging from
1117
+		the other log messages.
1118
+		</para>
1119
+		<para>
1120
+		Default value is LOG_DAEMON.
1121
+		</para>
1122
+		<example>
1123
+		<title>cdr_log_facility example</title>
1124
+		<programlisting format="linespecific">
1125
+modparam("acc", "cdr_log_facility", "LOG_DAEMON")
1126
+</programlisting>
1127
+		</example>
1128
+	</section>
1129
+	<section id="cdr_log_extra">
1130
+		<title><varname>cdr_log_extra</varname> (string)</title>
1131
+		<para>
1132
+		Set of pseudo-variables defining custom CDR fields. See
1133
+        <xref linkend="cdr-extra-id"/> for more details.
1134
+		</para>
1135
+		<para>
1136
+		Default value is NULL.
1137
+		</para>
1138
+		<example>
1139
+		<title>cdr_log_extra example</title>
1140
+		<programlisting format="linespecific">
1141
+modparam("acc", "cdr_log_extra", "c1=$dlg_var(caller);c2=$dlg_var(callee)"
1142
+</programlisting>
1143
+		</example>
1144
+	</section>
1145
+
965 1146
 	</section>
966 1147
 
967 1148
 	<section>