From b448dd849a96fd1b736ee183385d7a6a9ea1863e Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Sun, 3 May 2015 11:46:58 +0200 Subject: sgsn: Make the free function internal All calls should and do go through the sgsn_mm_ctx_cleanup_free function. --- openbsc/include/openbsc/gprs_sgsn.h | 1 - openbsc/src/gprs/gprs_sgsn.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h index 7a429cdab..9e855f8c4 100644 --- a/openbsc/include/openbsc/gprs_sgsn.h +++ b/openbsc/include/openbsc/gprs_sgsn.h @@ -152,7 +152,6 @@ struct sgsn_mm_ctx *sgsn_mm_ctx_by_imsi(const char *imsi); /* Allocate a new SGSN MM context */ struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli, const struct gprs_ra_id *raid); -void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm); void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *ctx); struct sgsn_ggsn_ctx *sgsn_mm_ctx_find_ggsn_ctx(struct sgsn_mm_ctx *mmctx, diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c index 711540e03..2f5d39e83 100644 --- a/openbsc/src/gprs/gprs_sgsn.c +++ b/openbsc/src/gprs/gprs_sgsn.c @@ -183,7 +183,7 @@ struct sgsn_mm_ctx *sgsn_mm_ctx_alloc(uint32_t tlli, /* this is a hard _free_ function, it doesn't clean up the PDP contexts * in libgtp! */ -void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm) +static void sgsn_mm_ctx_free(struct sgsn_mm_ctx *mm) { struct sgsn_pdp_ctx *pdp, *pdp2; -- cgit v1.2.3 From 20de3ae17cbb61f831ca8f06a4cf7436441cfdc6 Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Tue, 5 May 2015 22:52:40 +0200 Subject: sgsn: Extract the MSISDN from the subscr data structure In case there is a subscr attached to the MM context and there is an encoded MSISDN we will attempt to decode it and in case of an international number prepend a '+'. Assume that the array size of gsm_mmcc_called->number is as big as ctx->msisdn for the strncpy. --- openbsc/src/gprs/gprs_gmm.c | 34 ++++++++++++++++++++++++++++++++++ openbsc/tests/sgsn/sgsn_test.c | 4 ++++ 2 files changed, 38 insertions(+) diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c index ace0c2ec0..2a856a6cb 100644 --- a/openbsc/src/gprs/gprs_gmm.c +++ b/openbsc/src/gprs/gprs_gmm.c @@ -545,6 +545,38 @@ static int gsm48_rx_gmm_auth_ciph_resp(struct sgsn_mm_ctx *ctx, return gsm48_gmm_authorize(ctx); } +static void extract_subscr_msisdn(struct sgsn_mm_ctx *ctx) +{ + struct gsm_mncc_number called; + uint8_t msisdn[sizeof(ctx->subscr->sgsn_data->msisdn) + 1]; + + /* Convert MSISDN from encoded to string.. */ + if (!ctx->subscr) + return; + + if (ctx->subscr->sgsn_data->msisdn_len < 1) + return; + + /* prepare the data for the decoder */ + memset(&called, 0, sizeof(called)); + msisdn[0] = ctx->subscr->sgsn_data->msisdn_len; + memcpy(&msisdn[1], ctx->subscr->sgsn_data->msisdn, + ctx->subscr->sgsn_data->msisdn_len); + + /* decode the string now */ + gsm48_decode_called(&called, msisdn); + + /* Prepend a '+' for international numbers */ + if (called.plan == 1 && called.type == 1) { + ctx->msisdn[0] = '+'; + strncpy(&ctx->msisdn[1], called.number, + sizeof(ctx->msisdn) - 1); + } else { + strncpy(&ctx->msisdn[0], called.number, + sizeof(ctx->msisdn) - 1); + } +} + /* Check if we can already authorize a subscriber */ static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) { @@ -604,6 +636,8 @@ static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) "no pending request, authorization completed\n"); break; case GSM48_MT_GMM_ATTACH_REQ: + + extract_subscr_msisdn(ctx); #ifdef PTMSI_ALLOC /* Start T3350 and re-transmit up to 5 times until ATTACH COMPLETE */ mmctx_timer_start(ctx, 3350, GSM0408_T3350_SECS); diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c index d9b162dc2..879dfe36e 100644 --- a/openbsc/tests/sgsn/sgsn_test.c +++ b/openbsc/tests/sgsn/sgsn_test.c @@ -953,6 +953,8 @@ retry_attach_req: /* check that the MM context has not been removed due to a * failed authorization */ OSMO_ASSERT(ctx == sgsn_mm_ctx_by_tlli(foreign_tlli, &raid)); + if (ctx->subscr && ctx->subscr->sgsn_data->msisdn_len > 0) + OSMO_ASSERT(strcmp(ctx->msisdn, "+49166213323") == 0); } if (retry && sgsn_tx_counter == 0) @@ -1151,6 +1153,8 @@ int my_subscr_request_update_gsup_auth(struct sgsn_mm_ctx *mmctx) { 0x10, 0x01, 0x01, 0x11, 0x02, 0xf1, 0x21, /* IPv4 */ 0x12, 0x09, 0x04, 't', 'e', 's', 't', 0x03, 'a', 'p', 'n', + 0x08, 0x07, /* MSISDN 49166213323 encoded */ + 0x91, 0x94, 0x61, 0x26, 0x31, 0x23, 0xF3, }; OSMO_ASSERT(!mmctx || mmctx->subscr); -- cgit v1.2.3 From b100895557af0980d5910a3dc81903179dea615e Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Sat, 2 May 2015 19:55:38 +0200 Subject: sgsn: Add various signals consumed by CDR or other client code --- openbsc/include/openbsc/signal.h | 21 ++++++++++++++++++++- openbsc/src/gprs/gprs_gmm.c | 31 ++++++++++++++++++++++++++++++- openbsc/src/gprs/gprs_sgsn.c | 19 +++++++++++++++++++ openbsc/src/gprs/sgsn_libgtp.c | 11 +++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h index 39319f1c9..8f27b3831 100644 --- a/openbsc/include/openbsc/signal.h +++ b/openbsc/include/openbsc/signal.h @@ -1,5 +1,5 @@ /* Generic signalling/notification infrastructure */ -/* (C) 2009-2010 by Holger Hans Peter Freyther +/* (C) 2009-2010, 2015 by Holger Hans Peter Freyther * (C) 2009 by Harald Welte * (C) 2010 by On-Waves * All Rights Reserved @@ -46,6 +46,7 @@ enum signal_subsystems { SS_MSC, SS_HO, SS_CCCH, + SS_SGSN, }; /* SS_PAGING signals */ @@ -241,4 +242,22 @@ struct ccch_signal_data { uint16_t rach_access_count; }; +/* GPRS SGSN signals SS_SGSN */ +enum signal_sgsn { + S_SGSN_ATTACH, + S_SGSN_DETACH, + S_SGSN_UPDATE, + S_SGSN_PDP_ACT, + S_SGSN_PDP_DEACT, + S_SGSN_PDP_TERMINATE, + S_SGSN_PDP_FREE, + S_SGSN_MM_FREE, +}; + +struct sgsn_mm_ctx; +struct sgsn_signal_data { + struct sgsn_mm_ctx *mm; + struct sgsn_pdp_ctx *pdp; /* non-NULL for PDP_ACT, PDP_DEACT, PDP_FREE */ +}; + #endif diff --git a/openbsc/src/gprs/gprs_gmm.c b/openbsc/src/gprs/gprs_gmm.c index 2a856a6cb..8ada3d41b 100644 --- a/openbsc/src/gprs/gprs_gmm.c +++ b/openbsc/src/gprs/gprs_gmm.c @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -580,6 +581,10 @@ static void extract_subscr_msisdn(struct sgsn_mm_ctx *ctx) /* Check if we can already authorize a subscriber */ static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) { +#ifndef PTMSI_ALLOC + struct sgsn_signal_data sig_data; +#endif + /* Request IMSI and IMEI from the MS if they are unknown */ if (!strlen(ctx->imei)) { ctx->t3370_id_type = GSM_MI_TYPE_IMEI; @@ -643,6 +648,9 @@ static int gsm48_gmm_authorize(struct sgsn_mm_ctx *ctx) mmctx_timer_start(ctx, 3350, GSM0408_T3350_SECS); ctx->t3350_mode = GMM_T3350_MODE_ATT; #else + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); ctx->mm_state = GMM_REGISTERED_NORMAL; #endif @@ -976,8 +984,13 @@ static int gsm48_rx_gmm_det_req(struct sgsn_mm_ctx *ctx, struct msgb *msg) rc = gsm48_tx_gmm_det_ack_oldmsg(msg, 0); } - if (ctx) + if (ctx) { + struct sgsn_signal_data sig_data; + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = ctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_DETACH, &sig_data); mm_ctx_cleanup_free(ctx, "GPRS DETACH REQUEST"); + } return rc; } @@ -1083,6 +1096,9 @@ static void process_ms_ctx_status(struct sgsn_mm_ctx *mmctx, static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, struct gprs_llc_llme *llme) { +#ifndef PTMSI_ALLOC + struct sgsn_signal_data sig_data; +#endif struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); uint8_t *cur = gh->data; uint8_t ms_ra_acc_cap_len; @@ -1168,6 +1184,10 @@ static int gsm48_rx_gmm_ra_upd_req(struct sgsn_mm_ctx *mmctx, struct msgb *msg, #else /* Make sure we are NORMAL (i.e. not SUSPENDED anymore) */ mmctx->mm_state = GMM_REGISTERED_NORMAL; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); #endif /* Even if there is no P-TMSI allocated, the MS will switch from * foreign TLLI to local TLLI */ @@ -1217,6 +1237,7 @@ static int gsm48_rx_gmm_status(struct sgsn_mm_ctx *mmctx, struct msgb *msg) static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, struct gprs_llc_llme *llme) { + struct sgsn_signal_data sig_data; struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_gmmh(msg); int rc; @@ -1298,6 +1319,10 @@ static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, GPRS_ALGO_GEA0, NULL); mmctx->mm_state = GMM_REGISTERED_NORMAL; rc = 0; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_ATTACH, &sig_data); break; case GSM48_MT_GMM_RA_UPD_COMPL: /* only in case SGSN offered new P-TMSI */ @@ -1312,6 +1337,10 @@ static int gsm0408_rcv_gmm(struct sgsn_mm_ctx *mmctx, struct msgb *msg, GPRS_ALGO_GEA0, NULL); mmctx->mm_state = GMM_REGISTERED_NORMAL; rc = 0; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mmctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_UPDATE, &sig_data); break; case GSM48_MT_GMM_PTMSI_REALL_COMPL: LOGMMCTXP(LOGL_INFO, mmctx, "-> PTMSI REALLLICATION COMPLETE\n"); diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c index 2f5d39e83..5fe4e616e 100644 --- a/openbsc/src/gprs/gprs_sgsn.c +++ b/openbsc/src/gprs/gprs_sgsn.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "openbsc/gprs_llc.h" #include @@ -204,6 +205,7 @@ void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *mm) struct gprs_llc_llme *llme = mm->llme; uint32_t tlli = mm->tlli; struct sgsn_pdp_ctx *pdp, *pdp2; + struct sgsn_signal_data sig_data; /* delete all existing PDP contexts for this MS */ llist_for_each_entry_safe(pdp, pdp2, &mm->pdp_list, list) { @@ -217,6 +219,11 @@ void sgsn_mm_ctx_cleanup_free(struct sgsn_mm_ctx *mm) osmo_timer_del(&mm->timer); } + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.mm = mm; + osmo_signal_dispatch(SS_SGSN, S_SGSN_MM_FREE, &sig_data); + + /* Detach from subscriber which is possibly freed then */ if (mm->subscr) { struct gsm_subscriber *subscr = subscr_get(mm->subscr); @@ -289,6 +296,8 @@ struct sgsn_pdp_ctx *sgsn_pdp_ctx_alloc(struct sgsn_mm_ctx *mm, */ void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp) { + struct sgsn_signal_data sig_data; + OSMO_ASSERT(pdp->mm != NULL); /* There might still be pending callbacks in libgtp. So the parts of @@ -299,6 +308,10 @@ void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp) /* Force the deactivation of the SNDCP layer */ sndcp_sm_deactivate_ind(&pdp->mm->llme->lle[pdp->sapi], pdp->nsapi); + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pdp; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_TERMINATE, &sig_data); + /* Detach from MM context */ llist_del(&pdp->list); pdp->mm = NULL; @@ -313,6 +326,12 @@ void sgsn_pdp_ctx_terminate(struct sgsn_pdp_ctx *pdp) */ void sgsn_pdp_ctx_free(struct sgsn_pdp_ctx *pdp) { + struct sgsn_signal_data sig_data; + + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pdp; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_FREE, &sig_data); + rate_ctr_group_free(pdp->ctrg); if (pdp->mm) llist_del(&pdp->list); diff --git a/openbsc/src/gprs/sgsn_libgtp.c b/openbsc/src/gprs/sgsn_libgtp.c index eee3ef21c..af5c93de3 100644 --- a/openbsc/src/gprs/sgsn_libgtp.c +++ b/openbsc/src/gprs/sgsn_libgtp.c @@ -287,6 +287,7 @@ static const struct cause_map gtp2sm_cause_map[] = { /* The GGSN has confirmed the creation of a PDP Context */ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) { + struct sgsn_signal_data sig_data; struct sgsn_pdp_ctx *pctx = cbp; uint8_t reject_cause; @@ -322,6 +323,11 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) /* Activate the SNDCP layer */ sndcp_sm_activate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi); + /* Inform others about it */ + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_ACT, &sig_data); + /* Send PDP CTX ACT to MS */ return gsm48_tx_gsm_act_pdp_acc(pctx); @@ -349,12 +355,17 @@ reject: /* Confirmation of a PDP Context Delete */ static int delete_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) { + struct sgsn_signal_data sig_data; struct sgsn_pdp_ctx *pctx = cbp; int rc = 0; LOGPDPCTXP(LOGL_INFO, pctx, "Received DELETE PDP CTX CONF, cause=%d(%s)\n", cause, get_value_string(gtp_cause_strs, cause)); + memset(&sig_data, 0, sizeof(sig_data)); + sig_data.pdp = pctx; + osmo_signal_dispatch(SS_SGSN, S_SGSN_PDP_DEACT, &sig_data); + if (pctx->mm) { /* Deactivate the SNDCP layer */ sndcp_sm_deactivate_ind(&pctx->mm->llme->lle[pctx->sapi], pctx->nsapi); -- cgit v1.2.3 From 4f5b8237ec2182861fbe075f4cce56a9c7caf904 Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Tue, 5 May 2015 22:25:48 +0200 Subject: sgsn: Create an initial and limited CDR module This is consuming the new signals and allows to install several different CDR/observing/event/audit modules in the future. For getting the bytes in/out the code would have had to undo what the rate counter is doing and at the same time adding a "total" to the ratecounter didn't look like a good idea, the same went for making it a plain counter. Begin writing the values one by one and open/closing a new FILE for every log messages. This is not efficient but easily deals with external truncation/rotation of the file (no fstat for and checking the links and size). As usual we will wait and see if this is an issue. Add some new members to our PDP context structure to see what it is about. --- openbsc/include/openbsc/gprs_sgsn.h | 5 + openbsc/include/openbsc/sgsn.h | 14 ++ openbsc/src/gprs/Makefile.am | 2 +- openbsc/src/gprs/sgsn_cdr.c | 254 ++++++++++++++++++++++++++++++++++++ openbsc/src/gprs/sgsn_libgtp.c | 6 + openbsc/src/gprs/sgsn_main.c | 1 + 6 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 openbsc/src/gprs/sgsn_cdr.c diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h index 9e855f8c4..c88a2bbe7 100644 --- a/openbsc/include/openbsc/gprs_sgsn.h +++ b/openbsc/include/openbsc/gprs_sgsn.h @@ -207,6 +207,11 @@ struct sgsn_pdp_ctx { struct osmo_timer_list timer; unsigned int T; /* Txxxx number */ unsigned int num_T_exp; /* number of consecutive T expirations */ + + struct osmo_timer_list cdr_timer; /* CDR record wird timer */ + struct timespec cdr_start; /* The start of the CDR */ + uint64_t cdr_bytes_in; + uint64_t cdr_bytes_out; }; #define LOGPDPCTXP(level, pdp, fmt, args...) \ diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h index 7d3a68c43..0f9a59f25 100644 --- a/openbsc/include/openbsc/sgsn.h +++ b/openbsc/include/openbsc/sgsn.h @@ -16,6 +16,11 @@ enum sgsn_auth_policy { SGSN_AUTH_POLICY_REMOTE }; +struct sgsn_cdr { + char *filename; + int interval; +}; + struct sgsn_config { /* parsed from config file */ @@ -33,6 +38,9 @@ struct sgsn_config { int require_authentication; int require_update_location; + + /* CDR configuration */ + struct sgsn_cdr cdr; }; struct sgsn_instance { @@ -85,4 +93,10 @@ int sndcp_unitdata_req(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t nsapi int sndcp_llunitdata_ind(struct msgb *msg, struct gprs_llc_lle *lle, uint8_t *hdr, uint16_t len); + +/* + * CDR related functionality + */ +int sgsn_cdr_init(struct sgsn_instance *sgsn); + #endif diff --git a/openbsc/src/gprs/Makefile.am b/openbsc/src/gprs/Makefile.am index bc3e21e1b..3d0282204 100644 --- a/openbsc/src/gprs/Makefile.am +++ b/openbsc/src/gprs/Makefile.am @@ -24,7 +24,7 @@ osmo_sgsn_SOURCES = gprs_gmm.c gprs_sgsn.c gprs_sndcp.c gprs_sndcp_vty.c \ gprs_llc.c gprs_llc_parse.c gprs_llc_vty.c crc24.c \ sgsn_ctrl.c sgsn_auth.c gprs_subscriber.c \ gprs_gsup_messages.c gprs_utils.c gprs_gsup_client.c \ - gsm_04_08_gprs.c + gsm_04_08_gprs.c sgsn_cdr.c osmo_sgsn_LDADD = \ $(top_builddir)/src/libcommon/libcommon.a \ -lgtp $(OSMO_LIBS) $(LIBOSMOABIS_LIBS) -lrt diff --git a/openbsc/src/gprs/sgsn_cdr.c b/openbsc/src/gprs/sgsn_cdr.c new file mode 100644 index 000000000..0fcdd3b2a --- /dev/null +++ b/openbsc/src/gprs/sgsn_cdr.c @@ -0,0 +1,254 @@ +/* GPRS SGSN CDR dumper */ + +/* (C) 2015 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#include +#include + +/* TODO...avoid going through a global */ +extern struct sgsn_instance *sgsn; + +/** + * The CDR module will generate an entry like: + * + * IMSI, # Subscriber IMSI + * IMEI, # Subscriber IMEI + * MSISDN, # Subscriber MISDN + * Charging_Timestamp, # Event start Time + * Charging_UTC, # Time zone of event start time + * Duration, # Session DURATION + * Cell_Id, # CELL_ID + * Location_Area, # LAC + * GGSN_ADDR, # GGSN_ADDR + * SGSN_ADDR, # SGSN_ADDR + * APNI, # APNI + * PDP_ADDR, # PDP_ADDR + * VOL_IN, # VOL_IN in Bytes + * VOL_OUT, # VOL_OUT in Bytes + * CAUSE_FOR_TERM, # CAUSE_FOR_TERM + */ + + +static void maybe_print_header(FILE *cdr_file) +{ + if (ftell(cdr_file) != 0) + return; + + fprintf(cdr_file, "timestamp,imsi,imei,msisdn,cell_id,lac,event,pdp_duration,ggsn_addr,sgsn_addr,apni,eua_addr,vol_in,vol_out\n"); +} + +static void cdr_log_mm(struct sgsn_instance *inst, const char *ev, + struct sgsn_mm_ctx *mmctx) +{ + FILE *cdr_file; + struct tm tm; + struct timeval tv; + + if (!inst->cfg.cdr.filename) + return; + + cdr_file = fopen(inst->cfg.cdr.filename, "a"); + if (!cdr_file) { + LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", + inst->cfg.cdr.filename); + return; + } + + maybe_print_header(cdr_file); + gettimeofday(&tv, NULL); + gmtime_r(&tv.tv_sec, &tm); + fprintf(cdr_file, "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000), + mmctx->imsi, + mmctx->imei, + mmctx->msisdn, + mmctx->cell_id, + mmctx->ra.lac, + ev); + + fclose(cdr_file); +} + +static void extract_eua(struct ul66_t *eua, char *eua_addr) +{ + if (eua->l < 2) + return; + + /* there is no addr for ETSI/PPP */ + if ((eua->v[0] & 0x0F) != 1) { + strcpy(eua_addr, "ETSI"); + return; + } + + if (eua->v[1] == 0x21 && eua->l == 6) + inet_ntop(AF_INET, &eua->v[2], eua_addr, INET_ADDRSTRLEN); + else if (eua->v[1] == 0x57 && eua->l == 18) + inet_ntop(AF_INET6, &eua->v[2], eua_addr, INET6_ADDRSTRLEN); + else { + /* e.g. both IPv4 and IPv6 */ + strcpy(eua_addr, "Unknown address"); + } +} + +static void cdr_log_pdp(struct sgsn_instance *inst, const char *ev, + struct sgsn_pdp_ctx *pdp) +{ + FILE *cdr_file; + char apni[(pdp->lib ? pdp->lib->apn_use.l : 0) + 1]; + char ggsn_addr[INET_ADDRSTRLEN + 1]; + char sgsn_addr[INET_ADDRSTRLEN + 1]; + char eua_addr[INET6_ADDRSTRLEN + 1]; + struct tm tm; + struct timeval tv; + time_t duration; + struct timespec tp; + + if (!inst->cfg.cdr.filename) + return; + + memset(apni, 0, sizeof(apni)); + memset(ggsn_addr, 0, sizeof(ggsn_addr)); + memset(eua_addr, 0, sizeof(eua_addr)); + + + if (pdp->lib) { + gprs_apn_to_str(apni, pdp->lib->apn_use.v, pdp->lib->apn_use.l); + inet_ntop(AF_INET, &pdp->lib->hisaddr0.s_addr, ggsn_addr, sizeof(ggsn_addr)); + extract_eua(&pdp->lib->eua, eua_addr); + } + + if (pdp->ggsn) + inet_ntop(AF_INET, &pdp->ggsn->gsn->gsnc.s_addr, sgsn_addr, sizeof(sgsn_addr)); + + cdr_file = fopen(inst->cfg.cdr.filename, "a"); + if (!cdr_file) { + LOGP(DGPRS, LOGL_ERROR, "Failed to open %s\n", + inst->cfg.cdr.filename); + return; + } + + maybe_print_header(cdr_file); + + clock_gettime(CLOCK_MONOTONIC, &tp); + gettimeofday(&tv, NULL); + + /* convert the timestamp to UTC */ + gmtime_r(&tv.tv_sec, &tm); + + /* Check the duration of the PDP context */ + duration = tp.tv_sec - pdp->cdr_start.tv_sec; + + fprintf(cdr_file, + "%04d%02d%02d%02d%02d%02d%03d,%s,%s,%s,%d,%d,%s,%ld,%s,%s,%s,%s,%" PRIu64 ",%" PRIu64 "\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000), + pdp->mm ? pdp->mm->imsi : "N/A", + pdp->mm ? pdp->mm->imei : "N/A", + pdp->mm ? pdp->mm->msisdn : "N/A", + pdp->mm ? pdp->mm->cell_id : -1, + pdp->mm ? pdp->mm->ra.lac : -1, + ev, + (unsigned long ) duration, + ggsn_addr, + sgsn_addr, + apni, + eua_addr, + pdp->cdr_bytes_in, + pdp->cdr_bytes_out); + fclose(cdr_file); +} + +static void cdr_pdp_timeout(void *_data) +{ + struct sgsn_pdp_ctx *pdp = _data; + cdr_log_pdp(sgsn, "pdp-periodic", pdp); + osmo_timer_schedule(&pdp->cdr_timer, sgsn->cfg.cdr.interval, 0); +} + +static int handle_sgsn_sig(unsigned int subsys, unsigned int signal, + void *handler_data, void *_signal_data) +{ + struct sgsn_signal_data *signal_data = _signal_data; + struct sgsn_instance *inst = handler_data; + + if (subsys != SS_SGSN) + return 0; + + switch (signal) { + case S_SGSN_ATTACH: + cdr_log_mm(inst, "attach", signal_data->mm); + break; + case S_SGSN_UPDATE: + cdr_log_mm(inst, "update", signal_data->mm); + break; + case S_SGSN_DETACH: + cdr_log_mm(inst, "detach", signal_data->mm); + break; + case S_SGSN_MM_FREE: + cdr_log_mm(inst, "free", signal_data->mm); + break; + case S_SGSN_PDP_ACT: + clock_gettime(CLOCK_MONOTONIC, &signal_data->pdp->cdr_start); + cdr_log_pdp(inst, "pdp-act", signal_data->pdp); + signal_data->pdp->cdr_timer.cb = cdr_pdp_timeout; + signal_data->pdp->cdr_timer.data = signal_data->pdp; + osmo_timer_schedule(&signal_data->pdp->cdr_timer, inst->cfg.cdr.interval, 0); + break; + case S_SGSN_PDP_DEACT: + cdr_log_pdp(inst, "pdp-deact", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + case S_SGSN_PDP_TERMINATE: + cdr_log_pdp(inst, "pdp-terminate", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + case S_SGSN_PDP_FREE: + cdr_log_pdp(inst, "pdp-free", signal_data->pdp); + osmo_timer_del(&signal_data->pdp->cdr_timer); + break; + } + + return 0; +} + +int sgsn_cdr_init(struct sgsn_instance *sgsn) +{ + /* register for CDR related events */ + sgsn->cfg.cdr.interval = 10 * 60; + osmo_signal_register_handler(SS_SGSN, handle_sgsn_sig, sgsn); + + return 0; +} diff --git a/openbsc/src/gprs/sgsn_libgtp.c b/openbsc/src/gprs/sgsn_libgtp.c index af5c93de3..9972cddf3 100644 --- a/openbsc/src/gprs/sgsn_libgtp.c +++ b/openbsc/src/gprs/sgsn_libgtp.c @@ -534,6 +534,9 @@ static int cb_data_ind(struct pdp_t *lib, void *packet, unsigned int len) rate_ctr_inc(&mm->ctrg->ctr[GMM_CTR_PKTS_UDATA_OUT]); rate_ctr_add(&mm->ctrg->ctr[GMM_CTR_BYTES_UDATA_OUT], len); + /* It is easier to have a global count */ + pdp->cdr_bytes_out += len; + return sndcp_unitdata_req(msg, &mm->llme->lle[pdp->sapi], pdp->nsapi, mm); } @@ -569,6 +572,9 @@ int sgsn_rx_sndcp_ud_ind(struct gprs_ra_id *ra_id, int32_t tlli, uint8_t nsapi, rate_ctr_inc(&mmctx->ctrg->ctr[GMM_CTR_PKTS_UDATA_IN]); rate_ctr_add(&mmctx->ctrg->ctr[GMM_CTR_BYTES_UDATA_IN], npdu_len); + /* It is easier to have a global count */ + pdp->cdr_bytes_in += npdu_len; + return gtp_data_req(pdp->ggsn->gsn, pdp->lib, npdu, npdu_len); } diff --git a/openbsc/src/gprs/sgsn_main.c b/openbsc/src/gprs/sgsn_main.c index 0db90d5c3..d5f7f6588 100644 --- a/openbsc/src/gprs/sgsn_main.c +++ b/openbsc/src/gprs/sgsn_main.c @@ -344,6 +344,7 @@ int main(int argc, char **argv) gprs_llc_vty_init(); gprs_sndcp_vty_init(); sgsn_auth_init(); + sgsn_cdr_init(&sgsn_inst); /* FIXME: register signal handler for SS_L_NS */ rc = sgsn_parse_config(sgsn_inst.config_file, &sgsn_inst.cfg); -- cgit v1.2.3 From c15c61c401e6f934fbf35c0c87d0777ff857f7c6 Mon Sep 17 00:00:00 2001 From: Holger Hans Peter Freyther Date: Wed, 6 May 2015 17:46:08 +0200 Subject: sgsn: Add VTY configuration for the CDR module Make it possible to set a filename to use for the CDR. By default no CDR will be generated. Forbid to set the interval of 0 seconds as this will cause a lot of work. Add a very basic VTY test. --- openbsc/src/gprs/sgsn_vty.c | 36 ++++++++++++++++++++++++++++++++++++ openbsc/tests/vty_test_runner.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c index be575d337..8b6e3ec29 100644 --- a/openbsc/src/gprs/sgsn_vty.c +++ b/openbsc/src/gprs/sgsn_vty.c @@ -1,6 +1,7 @@ /* * (C) 2010-2013 by Harald Welte * (C) 2010 by On-Waves + * (C) 2015 by Holger Hans Peter Freyther * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -164,6 +165,12 @@ static int config_write_sgsn(struct vty *vty) actx->ggsn->id, VTY_NEWLINE); } + if (g_cfg->cdr.filename) + vty_out(vty, " cdr filename %s%s", g_cfg->cdr.filename, VTY_NEWLINE); + else + vty_out(vty, " no cdr filename%s", VTY_NEWLINE); + vty_out(vty, " cdr interval %d%s", g_cfg->cdr.interval, VTY_NEWLINE); + return CMD_SUCCESS; } @@ -811,6 +818,32 @@ DEFUN(cfg_no_apn_name, cfg_no_apn_name_cmd, return CMD_SUCCESS; } +DEFUN(cfg_cdr_filename, cfg_cdr_filename_cmd, + "cdr filename NAME", + "CDR\nSet filename\nname\n") +{ + talloc_free(g_cfg->cdr.filename); + g_cfg->cdr.filename = talloc_strdup(tall_vty_ctx, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_no_cdr_filename, cfg_no_cdr_filename_cmd, + "no cdr filename", + NO_STR "CDR\nDisable CDR generation\n") +{ + talloc_free(g_cfg->cdr.filename); + g_cfg->cdr.filename = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_cdr_interval, cfg_cdr_interval_cmd, + "cdr interval <1-2147483647>", + "CDR\nPDP periodic log interval\nSeconds\n") +{ + g_cfg->cdr.interval = atoi(argv[0]); + return CMD_SUCCESS; +} + int sgsn_vty_init(void) { install_element_ve(&show_sgsn_cmd); @@ -842,6 +875,9 @@ int sgsn_vty_init(void) install_element(SGSN_NODE, &cfg_apn_imsi_ggsn_cmd); install_element(SGSN_NODE, &cfg_apn_name_cmd); install_element(SGSN_NODE, &cfg_no_apn_name_cmd); + install_element(SGSN_NODE, &cfg_cdr_filename_cmd); + install_element(SGSN_NODE, &cfg_no_cdr_filename_cmd); + install_element(SGSN_NODE, &cfg_cdr_interval_cmd); return 0; } diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py index 3581b67c9..4cd46653e 100644 --- a/openbsc/tests/vty_test_runner.py +++ b/openbsc/tests/vty_test_runner.py @@ -891,6 +891,34 @@ class TestVTYSGSN(TestVTYGenericBSC): res = self.vty.command("show running-config") self.assertEquals(res.find("apn internet"), -1) + def testVtyCDR(self): + self.vty.enable() + self.assertTrue(self.vty.verify('configure terminal', [''])) + self.assertEquals(self.vty.node(), 'config') + self.assertTrue(self.vty.verify('sgsn', [''])) + self.assertEquals(self.vty.node(), 'config-sgsn') + + res = self.vty.command("show running-config") + self.assert_(res.find("no cdr filename") > 0) + + self.vty.command("cdr filename bla.cdr") + res = self.vty.command("show running-config") + self.assertEquals(res.find("no cdr filename"), -1) + self.assert_(res.find(" cdr filename bla.cdr") > 0) + + self.vty.command("no cdr filename") + res = self.vty.command("show running-config") + self.assert_(res.find("no cdr filename") > 0) + self.assertEquals(res.find(" cdr filename bla.cdr"), -1) + + res = self.vty.command("show running-config") + self.assert_(res.find(" cdr interval 600") > 0) + + self.vty.command("cdr interval 900") + res = self.vty.command("show running-config") + self.assert_(res.find(" cdr interval 900") > 0) + self.assertEquals(res.find(" cdr interval 600"), -1) + def add_nat_test(suite, workdir): if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc_nat/osmo-bsc_nat")): print("Skipping the NAT test") -- cgit v1.2.3