blob: bc051ac81f8ebc5641322e1ef1ab6059ad38366f [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
Neels Hofmeyr396f2e62017-09-04 15:13:25 +020021#include <osmocom/sgsn/sgsn.h>
22#include <osmocom/sgsn/signal.h>
23#include <osmocom/sgsn/gprs_utils.h>
24#include <osmocom/sgsn/debug.h>
Harald Welte7e82b742017-08-12 13:43:54 +020025#include <osmocom/gsm/apn.h>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020026
Neels Hofmeyr396f2e62017-09-04 15:13:25 +020027#include <osmocom/sgsn/vty.h>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020028
29#include <gtp.h>
30#include <pdp.h>
31
32#include <arpa/inet.h>
33
34#include <time.h>
35
36#include <stdio.h>
37#include <inttypes.h>
38
39/* TODO...avoid going through a global */
40extern struct sgsn_instance *sgsn;
41
42/**
43 * The CDR module will generate an entry like:
44 *
45 * IMSI, # Subscriber IMSI
46 * IMEI, # Subscriber IMEI
47 * MSISDN, # Subscriber MISDN
48 * Charging_Timestamp, # Event start Time
49 * Charging_UTC, # Time zone of event start time
50 * Duration, # Session DURATION
51 * Cell_Id, # CELL_ID
52 * Location_Area, # LAC
53 * GGSN_ADDR, # GGSN_ADDR
54 * SGSN_ADDR, # SGSN_ADDR
55 * APNI, # APNI
56 * PDP_ADDR, # PDP_ADDR
57 * VOL_IN, # VOL_IN in Bytes
58 * VOL_OUT, # VOL_OUT in Bytes
59 * CAUSE_FOR_TERM, # CAUSE_FOR_TERM
60 */
61
62
63static void maybe_print_header(FILE *cdr_file)
64{
65 if (ftell(cdr_file) != 0)
66 return;
67
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020068 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 +020069}
70
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +010071static int cdr_snprintf_mm(char *buf, size_t size, const char *ev,
72 struct sgsn_mm_ctx *mmctx)
73{
74 struct tm tm;
75 struct timeval tv;
76 int ret;
77
78 gettimeofday(&tv, NULL);
79 gmtime_r(&tv.tv_sec, &tm);
80 ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s",
81 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
82 tm.tm_hour, tm.tm_min, tm.tm_sec,
83 (int)(tv.tv_usec / 1000),
84 mmctx->imsi,
85 mmctx->imei,
86 mmctx->msisdn,
87 mmctx->gb.cell_id,
88 mmctx->ra.lac,
89 mmctx->hlr,
90 ev);
91 return ret;
92}
93
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020094static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
95 struct sgsn_mm_ctx *mmctx)
96{
97 FILE *cdr_file;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +010098 char buf[1024];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020099
100 if (!inst->cfg.cdr.filename)
101 return;
102
103 cdr_file = fopen(inst->cfg.cdr.filename, "a");
104 if (!cdr_file) {
105 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
106 inst->cfg.cdr.filename);
107 return;
108 }
109
110 maybe_print_header(cdr_file);
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100111 cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx);
112 fprintf(cdr_file, "%s\n", buf);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200113
114 fclose(cdr_file);
115}
116
117static void extract_eua(struct ul66_t *eua, char *eua_addr)
118{
119 if (eua->l < 2)
120 return;
121
122 /* there is no addr for ETSI/PPP */
123 if ((eua->v[0] & 0x0F) != 1) {
124 strcpy(eua_addr, "ETSI");
125 return;
126 }
127
128 if (eua->v[1] == 0x21 && eua->l == 6)
129 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
130 else if (eua->v[1] == 0x57 && eua->l == 18)
131 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
132 else {
133 /* e.g. both IPv4 and IPv6 */
134 strcpy(eua_addr, "Unknown address");
135 }
136}
137
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100138static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200139 struct sgsn_pdp_ctx *pdp)
140{
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200141 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
142 char ggsn_addr[INET_ADDRSTRLEN + 1];
143 char sgsn_addr[INET_ADDRSTRLEN + 1];
144 char eua_addr[INET6_ADDRSTRLEN + 1];
145 struct tm tm;
146 struct timeval tv;
147 time_t duration;
148 struct timespec tp;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100149 int ret;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200150
151 memset(apni, 0, sizeof(apni));
152 memset(ggsn_addr, 0, sizeof(ggsn_addr));
153 memset(eua_addr, 0, sizeof(eua_addr));
154
155
156 if (pdp->lib) {
Harald Welte7e82b742017-08-12 13:43:54 +0200157 osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200158 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
159 extract_eua(&pdp->lib->eua, eua_addr);
160 }
161
162 if (pdp->ggsn)
163 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
164
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200165 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
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100174 ret = snprintf(buf, size,
175 "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u",
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);
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100194 return ret;
195}
196
197static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
198 struct sgsn_pdp_ctx *pdp)
199{
200 FILE *cdr_file;
201 char buf[1024];
202
203 if (!inst->cfg.cdr.filename)
204 return;
205
206 cdr_file = fopen(inst->cfg.cdr.filename, "a");
207 if (!cdr_file) {
208 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
209 inst->cfg.cdr.filename);
210 return;
211 }
212
213 maybe_print_header(cdr_file);
214 cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp);
215 fprintf(cdr_file, "%s\n", buf);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200216 fclose(cdr_file);
217}
218
219static void cdr_pdp_timeout(void *_data)
220{
221 struct sgsn_pdp_ctx *pdp = _data;
222 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
223 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
224}
225
226static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
227 void *handler_data, void *_signal_data)
228{
229 struct sgsn_signal_data *signal_data = _signal_data;
230 struct sgsn_instance *inst = handler_data;
231
232 if (subsys != SS_SGSN)
233 return 0;
234
235 switch (signal) {
236 case S_SGSN_ATTACH:
237 cdr_log_mm(inst, "attach", signal_data->mm);
238 break;
239 case S_SGSN_UPDATE:
240 cdr_log_mm(inst, "update", signal_data->mm);
241 break;
242 case S_SGSN_DETACH:
243 cdr_log_mm(inst, "detach", signal_data->mm);
244 break;
245 case S_SGSN_MM_FREE:
246 cdr_log_mm(inst, "free", signal_data->mm);
247 break;
248 case S_SGSN_PDP_ACT:
249 clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200250 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200251 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200252 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
253 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200254 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
255 break;
256 case S_SGSN_PDP_DEACT:
257 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
258 osmo_timer_del(&signal_data->pdp->cdr_timer);
259 break;
260 case S_SGSN_PDP_TERMINATE:
261 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
262 osmo_timer_del(&signal_data->pdp->cdr_timer);
263 break;
264 case S_SGSN_PDP_FREE:
265 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
266 osmo_timer_del(&signal_data->pdp->cdr_timer);
267 break;
268 }
269
270 return 0;
271}
272
273int sgsn_cdr_init(struct sgsn_instance *sgsn)
274{
275 /* register for CDR related events */
276 sgsn->cfg.cdr.interval = 10 * 60;
277 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
278
279 return 0;
280}