blob: 0910896101d5e43037d0c0e931f79d3abf7ea70a [file] [log] [blame]
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +02001/* GPRS SGSN CDR dumper */
2
3/* (C) 2015 by Holger Hans Peter Freyther
4 * All Rights Reserved
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21#include <openbsc/sgsn.h>
22#include <openbsc/signal.h>
23#include <openbsc/gprs_utils.h>
24#include <openbsc/debug.h>
25
26#include <openbsc/vty.h>
27
28#include <gtp.h>
29#include <pdp.h>
30
31#include <arpa/inet.h>
32
33#include <time.h>
34
35#include <stdio.h>
36#include <inttypes.h>
37
38/* TODO...avoid going through a global */
39extern struct sgsn_instance *sgsn;
40
41/**
42 * The CDR module will generate an entry like:
43 *
44 * IMSI, # Subscriber IMSI
45 * IMEI, # Subscriber IMEI
46 * MSISDN, # Subscriber MISDN
47 * Charging_Timestamp, # Event start Time
48 * Charging_UTC, # Time zone of event start time
49 * Duration, # Session DURATION
50 * Cell_Id, # CELL_ID
51 * Location_Area, # LAC
52 * GGSN_ADDR, # GGSN_ADDR
53 * SGSN_ADDR, # SGSN_ADDR
54 * APNI, # APNI
55 * PDP_ADDR, # PDP_ADDR
56 * VOL_IN, # VOL_IN in Bytes
57 * VOL_OUT, # VOL_OUT in Bytes
58 * CAUSE_FOR_TERM, # CAUSE_FOR_TERM
59 */
60
61
62static void maybe_print_header(FILE *cdr_file)
63{
64 if (ftell(cdr_file) != 0)
65 return;
66
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020067 fprintf(cdr_file, "timestamp,imsi,imei,msisdn,cell_id,lac,hlr,event,pdp_duration,ggsn_addr,sgsn_addr,apni,eua_addr,vol_in,vol_out,charging_id\n");
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020068}
69
70static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
71 struct sgsn_mm_ctx *mmctx)
72{
73 FILE *cdr_file;
74 struct tm tm;
75 struct timeval tv;
76
77 if (!inst->cfg.cdr.filename)
78 return;
79
80 cdr_file = fopen(inst->cfg.cdr.filename, "a");
81 if (!cdr_file) {
82 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
83 inst->cfg.cdr.filename);
84 return;
85 }
86
87 maybe_print_header(cdr_file);
88 gettimeofday(&tv, NULL);
89 gmtime_r(&tv.tv_sec, &tm);
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020090 fprintf(cdr_file, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s\n",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020091 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
92 tm.tm_hour, tm.tm_min, tm.tm_sec,
93 (int)(tv.tv_usec / 1000),
94 mmctx->imsi,
95 mmctx->imei,
96 mmctx->msisdn,
Harald Weltef97ee042015-12-25 19:12:21 +010097 mmctx->gb.cell_id,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020098 mmctx->ra.lac,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020099 mmctx->hlr,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200100 ev);
101
102 fclose(cdr_file);
103}
104
105static void extract_eua(struct ul66_t *eua, char *eua_addr)
106{
107 if (eua->l < 2)
108 return;
109
110 /* there is no addr for ETSI/PPP */
111 if ((eua->v[0] & 0x0F) != 1) {
112 strcpy(eua_addr, "ETSI");
113 return;
114 }
115
116 if (eua->v[1] == 0x21 && eua->l == 6)
117 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
118 else if (eua->v[1] == 0x57 && eua->l == 18)
119 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
120 else {
121 /* e.g. both IPv4 and IPv6 */
122 strcpy(eua_addr, "Unknown address");
123 }
124}
125
126static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
127 struct sgsn_pdp_ctx *pdp)
128{
129 FILE *cdr_file;
130 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
131 char ggsn_addr[INET_ADDRSTRLEN + 1];
132 char sgsn_addr[INET_ADDRSTRLEN + 1];
133 char eua_addr[INET6_ADDRSTRLEN + 1];
134 struct tm tm;
135 struct timeval tv;
136 time_t duration;
137 struct timespec tp;
138
139 if (!inst->cfg.cdr.filename)
140 return;
141
142 memset(apni, 0, sizeof(apni));
143 memset(ggsn_addr, 0, sizeof(ggsn_addr));
144 memset(eua_addr, 0, sizeof(eua_addr));
145
146
147 if (pdp->lib) {
148 gprs_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
149 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
150 extract_eua(&pdp->lib->eua, eua_addr);
151 }
152
153 if (pdp->ggsn)
154 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
155
156 cdr_file = fopen(inst->cfg.cdr.filename, "a");
157 if (!cdr_file) {
158 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
159 inst->cfg.cdr.filename);
160 return;
161 }
162
163 maybe_print_header(cdr_file);
164
165 clock_gettime(CLOCK_MONOTONIC, &tp);
166 gettimeofday(&tv, NULL);
167
168 /* convert the timestamp to UTC */
169 gmtime_r(&tv.tv_sec, &tm);
170
171 /* Check the duration of the PDP context */
172 duration = tp.tv_sec - pdp->cdr_start.tv_sec;
173
174 fprintf(cdr_file,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200175 "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u\n",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200176 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
177 tm.tm_hour, tm.tm_min, tm.tm_sec,
178 (int)(tv.tv_usec / 1000),
179 pdp->mm ? pdp->mm->imsi : "N/A",
180 pdp->mm ? pdp->mm->imei : "N/A",
181 pdp->mm ? pdp->mm->msisdn : "N/A",
Harald Weltef97ee042015-12-25 19:12:21 +0100182 pdp->mm ? pdp->mm->gb.cell_id : -1,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200183 pdp->mm ? pdp->mm->ra.lac : -1,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200184 pdp->mm ? pdp->mm->hlr : "N/A",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200185 ev,
186 (unsigned long ) duration,
187 ggsn_addr,
188 sgsn_addr,
189 apni,
190 eua_addr,
191 pdp->cdr_bytes_in,
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200192 pdp->cdr_bytes_out,
193 pdp->cdr_charging_id);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200194 fclose(cdr_file);
195}
196
197static void cdr_pdp_timeout(void *_data)
198{
199 struct sgsn_pdp_ctx *pdp = _data;
200 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
201 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
202}
203
204static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
205 void *handler_data, void *_signal_data)
206{
207 struct sgsn_signal_data *signal_data = _signal_data;
208 struct sgsn_instance *inst = handler_data;
209
210 if (subsys != SS_SGSN)
211 return 0;
212
213 switch (signal) {
214 case S_SGSN_ATTACH:
215 cdr_log_mm(inst, "attach", signal_data->mm);
216 break;
217 case S_SGSN_UPDATE:
218 cdr_log_mm(inst, "update", signal_data->mm);
219 break;
220 case S_SGSN_DETACH:
221 cdr_log_mm(inst, "detach", signal_data->mm);
222 break;
223 case S_SGSN_MM_FREE:
224 cdr_log_mm(inst, "free", signal_data->mm);
225 break;
226 case S_SGSN_PDP_ACT:
227 clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200228 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200229 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200230 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
231 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200232 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
233 break;
234 case S_SGSN_PDP_DEACT:
235 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
236 osmo_timer_del(&signal_data->pdp->cdr_timer);
237 break;
238 case S_SGSN_PDP_TERMINATE:
239 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
240 osmo_timer_del(&signal_data->pdp->cdr_timer);
241 break;
242 case S_SGSN_PDP_FREE:
243 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
244 osmo_timer_del(&signal_data->pdp->cdr_timer);
245 break;
246 }
247
248 return 0;
249}
250
251int sgsn_cdr_init(struct sgsn_instance *sgsn)
252{
253 /* register for CDR related events */
254 sgsn->cfg.cdr.interval = 10 * 60;
255 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
256
257 return 0;
258}