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