blob: 55aa664922b78d23410af05361baf5a798e6605d [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));
167 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
Holger Hans Peter Freyther4f5b8232015-05-05 22:25:48 +0200179 clock_gettime(CLOCK_MONOTONIC, &tp);
180 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:
269 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}