blob: 474d41a9aeb9a1ea632c2b31d34f71ac1ab6597b [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
71static void cdr_log_mm(struct sgsn_instance *inst, const char *ev,
72 struct sgsn_mm_ctx *mmctx)
73{
74 FILE *cdr_file;
75 struct tm tm;
76 struct timeval tv;
77
78 if (!inst->cfg.cdr.filename)
79 return;
80
81 cdr_file = fopen(inst->cfg.cdr.filename, "a");
82 if (!cdr_file) {
83 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
84 inst->cfg.cdr.filename);
85 return;
86 }
87
88 maybe_print_header(cdr_file);
89 gettimeofday(&tv, NULL);
90 gmtime_r(&tv.tv_sec, &tm);
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +020091 fprintf(cdr_file, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s\n",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020092 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
93 tm.tm_hour, tm.tm_min, tm.tm_sec,
94 (int)(tv.tv_usec / 1000),
95 mmctx->imsi,
96 mmctx->imei,
97 mmctx->msisdn,
Harald Weltef97ee042015-12-25 19:12:21 +010098 mmctx->gb.cell_id,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +020099 mmctx->ra.lac,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200100 mmctx->hlr,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200101 ev);
102
103 fclose(cdr_file);
104}
105
106static void extract_eua(struct ul66_t *eua, char *eua_addr)
107{
108 if (eua->l < 2)
109 return;
110
111 /* there is no addr for ETSI/PPP */
112 if ((eua->v[0] & 0x0F) != 1) {
113 strcpy(eua_addr, "ETSI");
114 return;
115 }
116
117 if (eua->v[1] == 0x21 && eua->l == 6)
118 inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN);
119 else if (eua->v[1] == 0x57 && eua->l == 18)
120 inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN);
121 else {
122 /* e.g. both IPv4 and IPv6 */
123 strcpy(eua_addr, "Unknown address");
124 }
125}
126
127static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev,
128 struct sgsn_pdp_ctx *pdp)
129{
130 FILE *cdr_file;
131 char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1];
132 char ggsn_addr[INET_ADDRSTRLEN + 1];
133 char sgsn_addr[INET_ADDRSTRLEN + 1];
134 char eua_addr[INET6_ADDRSTRLEN + 1];
135 struct tm tm;
136 struct timeval tv;
137 time_t duration;
138 struct timespec tp;
139
140 if (!inst->cfg.cdr.filename)
141 return;
142
143 memset(apni, 0, sizeof(apni));
144 memset(ggsn_addr, 0, sizeof(ggsn_addr));
145 memset(eua_addr, 0, sizeof(eua_addr));
146
147
148 if (pdp->lib) {
Harald Welte7e82b742017-08-12 13:43:54 +0200149 osmo_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200150 inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr));
151 extract_eua(&pdp->lib->eua, eua_addr);
152 }
153
154 if (pdp->ggsn)
155 inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr));
156
157 cdr_file = fopen(inst->cfg.cdr.filename, "a");
158 if (!cdr_file) {
159 LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n",
160 inst->cfg.cdr.filename);
161 return;
162 }
163
164 maybe_print_header(cdr_file);
165
166 clock_gettime(CLOCK_MONOTONIC, &tp);
167 gettimeofday(&tv, NULL);
168
169 /* convert the timestamp to UTC */
170 gmtime_r(&tv.tv_sec, &tm);
171
172 /* Check the duration of the PDP context */
173 duration = tp.tv_sec - pdp->cdr_start.tv_sec;
174
175 fprintf(cdr_file,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200176 "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 ",%u\n",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200177 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
178 tm.tm_hour, tm.tm_min, tm.tm_sec,
179 (int)(tv.tv_usec / 1000),
180 pdp->mm ? pdp->mm->imsi : "N/A",
181 pdp->mm ? pdp->mm->imei : "N/A",
182 pdp->mm ? pdp->mm->msisdn : "N/A",
Harald Weltef97ee042015-12-25 19:12:21 +0100183 pdp->mm ? pdp->mm->gb.cell_id : -1,
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200184 pdp->mm ? pdp->mm->ra.lac : -1,
Holger Hans Peter Freyther8ee13e22015-05-18 10:00:03 +0200185 pdp->mm ? pdp->mm->hlr : "N/A",
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200186 ev,
187 (unsigned long ) duration,
188 ggsn_addr,
189 sgsn_addr,
190 apni,
191 eua_addr,
192 pdp->cdr_bytes_in,
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200193 pdp->cdr_bytes_out,
194 pdp->cdr_charging_id);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200195 fclose(cdr_file);
196}
197
198static void cdr_pdp_timeout(void *_data)
199{
200 struct sgsn_pdp_ctx *pdp = _data;
201 cdr_log_pdp(sgsn, "pdp-periodic", pdp);
202 osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0);
203}
204
205static int handle_sgsn_sig(unsigned int subsys, unsigned int signal,
206 void *handler_data, void *_signal_data)
207{
208 struct sgsn_signal_data *signal_data = _signal_data;
209 struct sgsn_instance *inst = handler_data;
210
211 if (subsys != SS_SGSN)
212 return 0;
213
214 switch (signal) {
215 case S_SGSN_ATTACH:
216 cdr_log_mm(inst, "attach", signal_data->mm);
217 break;
218 case S_SGSN_UPDATE:
219 cdr_log_mm(inst, "update", signal_data->mm);
220 break;
221 case S_SGSN_DETACH:
222 cdr_log_mm(inst, "detach", signal_data->mm);
223 break;
224 case S_SGSN_MM_FREE:
225 cdr_log_mm(inst, "free", signal_data->mm);
226 break;
227 case S_SGSN_PDP_ACT:
228 clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start);
Holger Hans Peter Freyther77ff1c42015-05-12 21:08:42 +0200229 signal_data->pdp->cdr_charging_id = signal_data->pdp->lib->cid;
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200230 cdr_log_pdp(inst, "pdp-act", signal_data->pdp);
Pablo Neira Ayuso51215762017-05-08 20:57:52 +0200231 osmo_timer_setup(&signal_data->pdp->cdr_timer, cdr_pdp_timeout,
232 signal_data->pdp);
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200233 osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0);
234 break;
235 case S_SGSN_PDP_DEACT:
236 cdr_log_pdp(inst, "pdp-deact", signal_data->pdp);
237 osmo_timer_del(&signal_data->pdp->cdr_timer);
238 break;
239 case S_SGSN_PDP_TERMINATE:
240 cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp);
241 osmo_timer_del(&signal_data->pdp->cdr_timer);
242 break;
243 case S_SGSN_PDP_FREE:
244 cdr_log_pdp(inst, "pdp-free", signal_data->pdp);
245 osmo_timer_del(&signal_data->pdp->cdr_timer);
246 break;
247 }
248
249 return 0;
250}
251
252int sgsn_cdr_init(struct sgsn_instance *sgsn)
253{
254 /* register for CDR related events */
255 sgsn->cfg.cdr.interval = 10 * 60;
256 osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn);
257
258 return 0;
259}