blob: 24d75241ed46ff5f535f65c44d984f21c459410a [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>
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020031
32#include <gtp.h>
33#include <pdp.h>
34
35#include <arpa/inet.h>
36
37#include <time.h>
38
39#include <stdio.h>
40#include <inttypes.h>
41
42/* TODO...avoid going through a global */
43extern struct sgsn_instance *sgsn;
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010044extern struct ctrl_handle *g_ctrlh;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020045
46/**
47 * The CDR module will generate an entry like:
48 *
49 * IMSI, # Subscriber IMSI
50 * IMEI, # Subscriber IMEI
51 * MSISDN, # Subscriber MISDN
52 * Charging_Timestamp, # Event start Time
53 * Charging_UTC, # Time zone of event start time
54 * Duration, # Session DURATION
55 * Cell_Id, # CELL_ID
56 * Location_Area, # LAC
57 * GGSN_ADDR, # GGSN_ADDR
58 * SGSN_ADDR, # SGSN_ADDR
59 * APNI, # APNI
60 * PDP_ADDR, # PDP_ADDR
61 * VOL_IN, # VOL_IN in Bytes
62 * VOL_OUT, # VOL_OUT in Bytes
63 * CAUSE_FOR_TERM, # CAUSE_FOR_TERM
64 */
65
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +010066static void send_cdr_trap(char *value)
67{
68 if (ctrl_cmd_send_trap(g_ctrlh, "cdr-v1", value) < 0)
69 LOGP(DGPRS, LOGL_ERROR, "Failed to create and send TRAP cdr-v1\n");
70}
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020071
72static void maybe_print_header(FILE *cdr_file)
73{
74 if (ftell(cdr_file) != 0)
75 return;
76
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020077 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 +020078}
79
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +010080static int cdr_snprintf_mm(char *buf, size_t size, const char *ev,
81 struct sgsn_mm_ctx *mmctx)
82{
83 struct tm tm;
84 struct timeval tv;
85 int ret;
86
87 gettimeofday(&tv, NULL);
88 gmtime_r(&tv.tv_sec, &tm);
89 ret = snprintf(buf, size, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s",
90 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
91 tm.tm_hour, tm.tm_min, tm.tm_sec,
92 (int)(tv.tv_usec / 1000),
93 mmctx->imsi,
94 mmctx->imei,
95 mmctx->msisdn,
96 mmctx->gb.cell_id,
97 mmctx->ra.lac,
98 mmctx->hlr,
99 ev);
100 return ret;
101}
102
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200103static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
104 struct sgsn_mm_ctx *mmctx)
105{
106 FILE *cdr_file;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100107 char buf[1024];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200108
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100109 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200110 return;
111
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100112 cdr_snprintf_mm(buf, sizeof(buf), ev, mmctx);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200113
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100114 if (inst->cfg.cdr.trap)
115 send_cdr_trap(buf);
116
117 if (inst->cfg.cdr.filename) {
118 cdr_file = fopen(inst->cfg.cdr.filename, "a");
119 if (!cdr_file) {
120 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
121 inst->cfg.cdr.filename);
122 return;
123 }
124
125 maybe_print_header(cdr_file);
126 fprintf(cdr_file, "%s\n", buf);
127
128 fclose(cdr_file);
129 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200130}
131
132static void extract_eua(struct ul66_t *eua, char *eua_addr)
133{
134 if (eua->l < 2)
135 return;
136
137 /* there is no addr for ETSI/PPP */
138 if ((eua->v[0] & 0x0F) != 1) {
139 strcpy(eua_addr, "ETSI");
140 return;
141 }
142
143 if (eua->v[1] == 0x21 && eua->l == 6)
144 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
145 else if (eua->v[1] == 0x57 && eua->l == 18)
146 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
147 else {
148 /* e.g. both IPv4 and IPv6 */
149 strcpy(eua_addr, "Unknown address");
150 }
151}
152
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100153static int cdr_snprintf_pdp(char *buf, size_t size, const char *ev,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200154 struct sgsn_pdp_ctx *pdp)
155{
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200156 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
Pau Espin Pedrol8b88f5f2019-08-30 18:46:39 +0200157 char ggsn_addr[INET_ADDRSTRLEN];
158 char sgsn_addr[INET_ADDRSTRLEN];
159 char eua_addr[INET6_ADDRSTRLEN];
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200160 struct tm tm;
161 struct timeval tv;
162 time_t duration;
163 struct timespec tp;
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100164 int ret;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200165
166 memset(apni, 0, sizeof(apni));
167 memset(ggsn_addr, 0, sizeof(ggsn_addr));
Pau Espin Pedrolc6cef692018-07-17 17:39:16 +0200168 memset(sgsn_addr, 0, sizeof(sgsn_addr));
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200169 memset(eua_addr, 0, sizeof(eua_addr));
170
171
172 if (pdp->lib) {
Harald Welte7e82b742017-08-12 13:43:54 +0200173 osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200174 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
175 extract_eua(&pdp->lib->eua, eua_addr);
176 }
177
178 if (pdp->ggsn)
179 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
180
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200181 osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200182 gettimeofday(&tv, NULL);
183
184 /* convert the timestamp to UTC */
185 gmtime_r(&tv.tv_sec, &tm);
186
187 /* Check the duration of the PDP context */
188 duration = tp.tv_sec - pdp->cdr_start.tv_sec;
189
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100190 ret = snprintf(buf, size,
191 "%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 +0200192 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
193 tm.tm_hour, tm.tm_min, tm.tm_sec,
194 (int)(tv.tv_usec / 1000),
195 pdp->mm ? pdp->mm->imsi : "N/A",
196 pdp->mm ? pdp->mm->imei : "N/A",
197 pdp->mm ? pdp->mm->msisdn : "N/A",
Harald Weltef97ee042015-12-25 19:12:21 +0100198 pdp->mm ? pdp->mm->gb.cell_id : -1,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200199 pdp->mm ? pdp->mm->ra.lac : -1,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200200 pdp->mm ? pdp->mm->hlr : "N/A",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200201 ev,
202 (unsigned long ) duration,
203 ggsn_addr,
204 sgsn_addr,
205 apni,
206 eua_addr,
207 pdp->cdr_bytes_in,
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200208 pdp->cdr_bytes_out,
209 pdp->cdr_charging_id);
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100210 return ret;
211}
212
213static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
214 struct sgsn_pdp_ctx *pdp)
215{
216 FILE *cdr_file;
217 char buf[1024];
218
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100219 if (!inst->cfg.cdr.filename && !inst->cfg.cdr.trap)
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100220 return;
221
Pau Espin Pedrol291efcb2017-11-29 12:42:07 +0100222 cdr_snprintf_pdp(buf, sizeof(buf), ev, pdp);
Pau Espin Pedrol2e9ea502017-11-29 14:01:35 +0100223
224 if (inst->cfg.cdr.trap)
225 send_cdr_trap(buf);
226
227 if (inst->cfg.cdr.filename) {
228 cdr_file = fopen(inst->cfg.cdr.filename, "a");
229 if (!cdr_file) {
230 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
231 inst->cfg.cdr.filename);
232 return;
233 }
234
235 maybe_print_header(cdr_file);
236 fprintf(cdr_file, "%s\n", buf);
237 fclose(cdr_file);
238 }
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200239}
240
241static void cdr_pdp_timeout(void *_data)
242{
243 struct sgsn_pdp_ctx *pdp = _data;
244 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
245 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
246}
247
248static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
249 void *handler_data, void *_signal_data)
250{
251 struct sgsn_signal_data *signal_data = _signal_data;
252 struct sgsn_instance *inst = handler_data;
253
254 if (subsys != SS_SGSN)
255 return 0;
256
257 switch (signal) {
258 case S_SGSN_ATTACH:
259 cdr_log_mm(inst, "attach", signal_data->mm);
260 break;
261 case S_SGSN_UPDATE:
262 cdr_log_mm(inst, "update", signal_data->mm);
263 break;
264 case S_SGSN_DETACH:
265 cdr_log_mm(inst, "detach", signal_data->mm);
266 break;
267 case S_SGSN_MM_FREE:
268 cdr_log_mm(inst, "free", signal_data->mm);
269 break;
270 case S_SGSN_PDP_ACT:
Pau Espin Pedrol36abead2018-08-17 13:27:20 +0200271 osmo_clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200272 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200273 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200274 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
275 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200276 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
277 break;
278 case S_SGSN_PDP_DEACT:
279 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
280 osmo_timer_del(&signal_data->pdp->cdr_timer);
281 break;
282 case S_SGSN_PDP_TERMINATE:
283 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
284 osmo_timer_del(&signal_data->pdp->cdr_timer);
285 break;
286 case S_SGSN_PDP_FREE:
287 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
288 osmo_timer_del(&signal_data->pdp->cdr_timer);
289 break;
290 }
291
292 return 0;
293}
294
295int sgsn_cdr_init(struct sgsn_instance *sgsn)
296{
297 /* register for CDR related events */
298 sgsn->cfg.cdr.interval = 10 * 60;
299 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
300
301 return 0;
302}