blob: db326448c8b53b8109eb672e1b93e5ad9b67a664 [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
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010021#include <osmocom/ctrl/control_if.h>
22
Neels Hofmeyr396f2e62017-09-04 15:13:25 +020023#include <osmocom/sgsn/sgsn.h>
24#include <osmocom/sgsn/signal.h>
25#include <osmocom/sgsn/gprs_utils.h>
26#include <osmocom/sgsn/debug.h>
Harald Welte7e82b742017-08-12 13:43:54 +020027#include <osmocom/gsm/apn.h>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020028
Neels Hofmeyr396f2e62017-09-04 15:13:25 +020029#include <osmocom/sgsn/vty.h>
Pau Espin Pedrol5f4736a2023-01-04 21:30:28 +010030#include <osmocom/sgsn/gtp_ggsn.h>
Pau Espin Pedrol05190c32023-01-05 20:13:13 +010031#include <osmocom/sgsn/pdpctx.h>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020032
33#include <gtp.h>
34#include <pdp.h>
35
36#include <arpa/inet.h>
37
38#include <time.h>
39
40#include <stdio.h>
41#include <inttypes.h>
42
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020043/**
44 * The CDR module will generate an entry like:
45 *
46 * IMSI, # Subscriber IMSI
47 * IMEI, # Subscriber IMEI
48 * MSISDN, # Subscriber MISDN
49 * Charging_Timestamp, # Event start Time
50 * Charging_UTC, # Time zone of event start time
51 * Duration, # Session DURATION
52 * Cell_Id, # CELL_ID
53 * Location_Area, # LAC
54 * GGSN_ADDR, # GGSN_ADDR
55 * SGSN_ADDR, # SGSN_ADDR
56 * APNI, # APNI
57 * PDP_ADDR, # PDP_ADDR
58 * VOL_IN, # VOL_IN in Bytes
59 * VOL_OUT, # VOL_OUT in Bytes
60 * CAUSE_FOR_TERM, # CAUSE_FOR_TERM
61 */
62
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010063static void send_cdr_trap(char *value)
64{
Pau Espin Pedrole659f752023-01-05 17:20:37 +010065 if (ctrl_cmd_send_trap(sgsn->ctrlh, "cdr-v1", value) < 0)
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010066 LOGP(DGPRS, LOGL_ERROR, "Failed to create and send TRAP cdr-v1\n");
67}
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020068
69static void maybe_print_header(FILE *cdr_file)
70{
71 if (ftell(cdr_file) != 0)
72 return;
73
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020074 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 +020075}
76
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +010077static int cdr_snprintf_mm(char *buf, size_t size, const char *ev,
78 struct sgsn_mm_ctx *mmctx)
79{
80 struct tm tm;
81 struct timeval tv;
82 int ret;
83
84 gettimeofday(&tv, NULL);
85 gmtime_r(&tv.tv_sec, &tm);
86 ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s",
87 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
88 tm.tm_hour, tm.tm_min, tm.tm_sec,
89 (int)(tv.tv_usec / 1000),
90 mmctx->imsi,
91 mmctx->imei,
92 mmctx->msisdn,
93 mmctx->gb.cell_id,
94 mmctx->ra.lac,
95 mmctx->hlr,
96 ev);
97 return ret;
98}
99
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200100static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
101 struct sgsn_mm_ctx *mmctx)
102{
103 FILE *cdr_file;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100104 char buf[1024];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200105
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100106 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200107 return;
108
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100109 cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200110
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100111 if (inst->cfg.cdr.trap)
112 send_cdr_trap(buf);
113
114 if (inst->cfg.cdr.filename) {
115 cdr_file = fopen(inst->cfg.cdr.filename, "a");
116 if (!cdr_file) {
117 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
118 inst->cfg.cdr.filename);
119 return;
120 }
121
122 maybe_print_header(cdr_file);
123 fprintf(cdr_file, "%s\n", buf);
124
125 fclose(cdr_file);
126 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200127}
128
129static void extract_eua(struct ul66_t *eua, char *eua_addr)
130{
131 if (eua->l < 2)
132 return;
133
134 /* there is no addr for ETSI/PPP */
135 if ((eua->v[0] & 0x0F) != 1) {
136 strcpy(eua_addr, "ETSI");
137 return;
138 }
139
140 if (eua->v[1] == 0x21 && eua->l == 6)
141 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
142 else if (eua->v[1] == 0x57 && eua->l == 18)
143 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
144 else {
145 /* e.g. both IPv4 and IPv6 */
146 strcpy(eua_addr, "Unknown address");
147 }
148}
149
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100150static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200151 struct sgsn_pdp_ctx *pdp)
152{
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200153 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
Pau Espin Pedrol8b88f5f2019-08-30 18:46:39 +0200154 char ggsn_addr[INET_ADDRSTRLEN];
155 char sgsn_addr[INET_ADDRSTRLEN];
156 char eua_addr[INET6_ADDRSTRLEN];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200157 struct tm tm;
158 struct timeval tv;
159 time_t duration;
160 struct timespec tp;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100161 int ret;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200162
163 memset(apni, 0, sizeof(apni));
164 memset(ggsn_addr, 0, sizeof(ggsn_addr));
Pau Espin Pedrolc6cef692018-07-17 17:39:16 +0200165 memset(sgsn_addr, 0, sizeof(sgsn_addr));
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200166 memset(eua_addr, 0, sizeof(eua_addr));
167
168
169 if (pdp->lib) {
Harald Welte7e82b742017-08-12 13:43:54 +0200170 osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200171 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
172 extract_eua(&pdp->lib->eua, eua_addr);
173 }
174
175 if (pdp->ggsn)
176 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
177
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200178 osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200179 gettimeofday(&tv, NULL);
180
181 /* convert the timestamp to UTC */
182 gmtime_r(&tv.tv_sec, &tm);
183
184 /* Check the duration of the PDP context */
185 duration = tp.tv_sec - pdp->cdr_start.tv_sec;
186
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100187 ret = snprintf(buf, size,
188 "%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 +0200189 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
190 tm.tm_hour, tm.tm_min, tm.tm_sec,
191 (int)(tv.tv_usec / 1000),
192 pdp->mm ? pdp->mm->imsi : "N/A",
193 pdp->mm ? pdp->mm->imei : "N/A",
194 pdp->mm ? pdp->mm->msisdn : "N/A",
Harald Weltef97ee042015-12-25 19:12:21 +0100195 pdp->mm ? pdp->mm->gb.cell_id : -1,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200196 pdp->mm ? pdp->mm->ra.lac : -1,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200197 pdp->mm ? pdp->mm->hlr : "N/A",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200198 ev,
199 (unsigned long ) duration,
200 ggsn_addr,
201 sgsn_addr,
202 apni,
203 eua_addr,
204 pdp->cdr_bytes_in,
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200205 pdp->cdr_bytes_out,
206 pdp->cdr_charging_id);
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100207 return ret;
208}
209
210static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
211 struct sgsn_pdp_ctx *pdp)
212{
213 FILE *cdr_file;
214 char buf[1024];
215
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100216 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100217 return;
218
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100219 cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp);
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100220
221 if (inst->cfg.cdr.trap)
222 send_cdr_trap(buf);
223
224 if (inst->cfg.cdr.filename) {
225 cdr_file = fopen(inst->cfg.cdr.filename, "a");
226 if (!cdr_file) {
227 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
228 inst->cfg.cdr.filename);
229 return;
230 }
231
232 maybe_print_header(cdr_file);
233 fprintf(cdr_file, "%s\n", buf);
234 fclose(cdr_file);
235 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200236}
237
238static void cdr_pdp_timeout(void *_data)
239{
240 struct sgsn_pdp_ctx *pdp = _data;
241 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
242 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
243}
244
245static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
246 void *handler_data, void *_signal_data)
247{
248 struct sgsn_signal_data *signal_data = _signal_data;
249 struct sgsn_instance *inst = handler_data;
250
251 if (subsys != SS_SGSN)
252 return 0;
253
254 switch (signal) {
255 case S_SGSN_ATTACH:
256 cdr_log_mm(inst, "attach", signal_data->mm);
257 break;
258 case S_SGSN_UPDATE:
259 cdr_log_mm(inst, "update", signal_data->mm);
260 break;
261 case S_SGSN_DETACH:
262 cdr_log_mm(inst, "detach", signal_data->mm);
263 break;
264 case S_SGSN_MM_FREE:
265 cdr_log_mm(inst, "free", signal_data->mm);
266 break;
267 case S_SGSN_PDP_ACT:
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200268 osmo_clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200269 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200270 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200271 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
272 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200273 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
274 break;
275 case S_SGSN_PDP_DEACT:
276 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
277 osmo_timer_del(&signal_data->pdp->cdr_timer);
278 break;
279 case S_SGSN_PDP_TERMINATE:
280 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
281 osmo_timer_del(&signal_data->pdp->cdr_timer);
282 break;
283 case S_SGSN_PDP_FREE:
284 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
285 osmo_timer_del(&signal_data->pdp->cdr_timer);
286 break;
287 }
288
289 return 0;
290}
291
292int sgsn_cdr_init(struct sgsn_instance *sgsn)
293{
294 /* register for CDR related events */
295 sgsn->cfg.cdr.interval = 10 * 60;
296 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
297
298 return 0;
299}
Pau Espin Pedrole659f752023-01-05 17:20:37 +0100300
301void sgsn_cdr_release(struct sgsn_instance *sgsn)
302{
303 osmo_signal_unregister_handler(SS_SGSN, handle_sgsn_sig, sgsn);
304}