blob: 1536c135a713413bdef074b0319f3bb89b270a20 [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>
Pau Espin Pedrol58101ea2023-01-09 12:29:27 +010032#include <osmocom/sgsn/mmctx.h>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020033
34#include <gtp.h>
35#include <pdp.h>
36
37#include <arpa/inet.h>
38
39#include <time.h>
40
41#include <stdio.h>
42#include <inttypes.h>
43
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020044/**
45 * The CDR module will generate an entry like:
46 *
47 * IMSI, # Subscriber IMSI
48 * IMEI, # Subscriber IMEI
49 * MSISDN, # Subscriber MISDN
50 * Charging_Timestamp, # Event start Time
51 * Charging_UTC, # Time zone of event start time
52 * Duration, # Session DURATION
53 * Cell_Id, # CELL_ID
54 * Location_Area, # LAC
55 * GGSN_ADDR, # GGSN_ADDR
56 * SGSN_ADDR, # SGSN_ADDR
57 * APNI, # APNI
58 * PDP_ADDR, # PDP_ADDR
59 * VOL_IN, # VOL_IN in Bytes
60 * VOL_OUT, # VOL_OUT in Bytes
61 * CAUSE_FOR_TERM, # CAUSE_FOR_TERM
62 */
63
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010064static void send_cdr_trap(char *value)
65{
Pau Espin Pedrole659f752023-01-05 17:20:37 +010066 if (ctrl_cmd_send_trap(sgsn->ctrlh, "cdr-v1", value) < 0)
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010067 LOGP(DGPRS, LOGL_ERROR, "Failed to create and send TRAP cdr-v1\n");
68}
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020069
70static void maybe_print_header(FILE *cdr_file)
71{
72 if (ftell(cdr_file) != 0)
73 return;
74
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020075 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 +020076}
77
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +010078static int cdr_snprintf_mm(char *buf, size_t size, const char *ev,
79 struct sgsn_mm_ctx *mmctx)
80{
81 struct tm tm;
82 struct timeval tv;
83 int ret;
84
85 gettimeofday(&tv, NULL);
86 gmtime_r(&tv.tv_sec, &tm);
87 ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s",
88 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
89 tm.tm_hour, tm.tm_min, tm.tm_sec,
90 (int)(tv.tv_usec / 1000),
91 mmctx->imsi,
92 mmctx->imei,
93 mmctx->msisdn,
94 mmctx->gb.cell_id,
95 mmctx->ra.lac,
96 mmctx->hlr,
97 ev);
98 return ret;
99}
100
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200101static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
102 struct sgsn_mm_ctx *mmctx)
103{
104 FILE *cdr_file;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100105 char buf[1024];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200106
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100107 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200108 return;
109
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100110 cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200111
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100112 if (inst->cfg.cdr.trap)
113 send_cdr_trap(buf);
114
115 if (inst->cfg.cdr.filename) {
116 cdr_file = fopen(inst->cfg.cdr.filename, "a");
117 if (!cdr_file) {
118 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
119 inst->cfg.cdr.filename);
120 return;
121 }
122
123 maybe_print_header(cdr_file);
124 fprintf(cdr_file, "%s\n", buf);
125
126 fclose(cdr_file);
127 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200128}
129
130static void extract_eua(struct ul66_t *eua, char *eua_addr)
131{
132 if (eua->l < 2)
133 return;
134
135 /* there is no addr for ETSI/PPP */
136 if ((eua->v[0] & 0x0F) != 1) {
137 strcpy(eua_addr, "ETSI");
138 return;
139 }
140
141 if (eua->v[1] == 0x21 && eua->l == 6)
142 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
143 else if (eua->v[1] == 0x57 && eua->l == 18)
144 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
145 else {
146 /* e.g. both IPv4 and IPv6 */
147 strcpy(eua_addr, "Unknown address");
148 }
149}
150
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100151static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200152 struct sgsn_pdp_ctx *pdp)
153{
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200154 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
Pau Espin Pedrol8b88f5f2019-08-30 18:46:39 +0200155 char ggsn_addr[INET_ADDRSTRLEN];
156 char sgsn_addr[INET_ADDRSTRLEN];
157 char eua_addr[INET6_ADDRSTRLEN];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200158 struct tm tm;
159 struct timeval tv;
160 time_t duration;
161 struct timespec tp;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100162 int ret;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200163
164 memset(apni, 0, sizeof(apni));
165 memset(ggsn_addr, 0, sizeof(ggsn_addr));
Pau Espin Pedrolc6cef692018-07-17 17:39:16 +0200166 memset(sgsn_addr, 0, sizeof(sgsn_addr));
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200167 memset(eua_addr, 0, sizeof(eua_addr));
168
169
170 if (pdp->lib) {
Harald Welte7e82b742017-08-12 13:43:54 +0200171 osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200172 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
173 extract_eua(&pdp->lib->eua, eua_addr);
174 }
175
176 if (pdp->ggsn)
177 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
178
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200179 osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200180 gettimeofday(&tv, NULL);
181
182 /* convert the timestamp to UTC */
183 gmtime_r(&tv.tv_sec, &tm);
184
185 /* Check the duration of the PDP context */
186 duration = tp.tv_sec - pdp->cdr_start.tv_sec;
187
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100188 ret = snprintf(buf, size,
189 "%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 +0200190 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
191 tm.tm_hour, tm.tm_min, tm.tm_sec,
192 (int)(tv.tv_usec / 1000),
193 pdp->mm ? pdp->mm->imsi : "N/A",
194 pdp->mm ? pdp->mm->imei : "N/A",
195 pdp->mm ? pdp->mm->msisdn : "N/A",
Harald Weltef97ee042015-12-25 19:12:21 +0100196 pdp->mm ? pdp->mm->gb.cell_id : -1,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200197 pdp->mm ? pdp->mm->ra.lac : -1,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200198 pdp->mm ? pdp->mm->hlr : "N/A",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200199 ev,
200 (unsigned long ) duration,
201 ggsn_addr,
202 sgsn_addr,
203 apni,
204 eua_addr,
205 pdp->cdr_bytes_in,
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200206 pdp->cdr_bytes_out,
207 pdp->cdr_charging_id);
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100208 return ret;
209}
210
211static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
212 struct sgsn_pdp_ctx *pdp)
213{
214 FILE *cdr_file;
215 char buf[1024];
216
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100217 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100218 return;
219
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100220 cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp);
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100221
222 if (inst->cfg.cdr.trap)
223 send_cdr_trap(buf);
224
225 if (inst->cfg.cdr.filename) {
226 cdr_file = fopen(inst->cfg.cdr.filename, "a");
227 if (!cdr_file) {
228 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
229 inst->cfg.cdr.filename);
230 return;
231 }
232
233 maybe_print_header(cdr_file);
234 fprintf(cdr_file, "%s\n", buf);
235 fclose(cdr_file);
236 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200237}
238
239static void cdr_pdp_timeout(void *_data)
240{
241 struct sgsn_pdp_ctx *pdp = _data;
242 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
243 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
244}
245
246static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
247 void *handler_data, void *_signal_data)
248{
249 struct sgsn_signal_data *signal_data = _signal_data;
250 struct sgsn_instance *inst = handler_data;
251
252 if (subsys != SS_SGSN)
253 return 0;
254
255 switch (signal) {
256 case S_SGSN_ATTACH:
257 cdr_log_mm(inst, "attach", signal_data->mm);
258 break;
259 case S_SGSN_UPDATE:
260 cdr_log_mm(inst, "update", signal_data->mm);
261 break;
262 case S_SGSN_DETACH:
263 cdr_log_mm(inst, "detach", signal_data->mm);
264 break;
265 case S_SGSN_MM_FREE:
266 cdr_log_mm(inst, "free", signal_data->mm);
267 break;
268 case S_SGSN_PDP_ACT:
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200269 osmo_clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200270 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200271 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200272 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
273 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200274 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
275 break;
276 case S_SGSN_PDP_DEACT:
277 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
278 osmo_timer_del(&signal_data->pdp->cdr_timer);
279 break;
280 case S_SGSN_PDP_TERMINATE:
281 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
282 osmo_timer_del(&signal_data->pdp->cdr_timer);
283 break;
284 case S_SGSN_PDP_FREE:
285 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
286 osmo_timer_del(&signal_data->pdp->cdr_timer);
287 break;
288 }
289
290 return 0;
291}
292
293int sgsn_cdr_init(struct sgsn_instance *sgsn)
294{
295 /* register for CDR related events */
296 sgsn->cfg.cdr.interval = 10 * 60;
297 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
298
299 return 0;
300}
Pau Espin Pedrole659f752023-01-05 17:20:37 +0100301
302void sgsn_cdr_release(struct sgsn_instance *sgsn)
303{
304 osmo_signal_unregister_handler(SS_SGSN, handle_sgsn_sig, sgsn);
305}