/* gprs_bssgp_pcu.cpp * * Copyright (C) 2012 Ivan Klyuchnikov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include struct sgsn_instance *sgsn; extern void *tall_pcu_ctx; struct bssgp_bvc_ctx *bctx = NULL; struct gprs_nsvc *nsvc = NULL; static int bvc_sig_reset = 0, bvc_reset = 0, bvc_unblocked = 0; extern uint16_t spoof_mcc, spoof_mnc; struct osmo_timer_list bvc_timer; static void bvc_timeout(void *_priv); static int parse_imsi(struct tlv_parsed *tp, char *imsi) { uint8_t imsi_len; uint8_t *bcd_imsi; int i, j; if (!TLVP_PRESENT(tp, BSSGP_IE_IMSI)) return -EINVAL; imsi_len = TLVP_LEN(tp, BSSGP_IE_IMSI); bcd_imsi = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_IMSI); if ((bcd_imsi[0] & 0x08)) imsi_len = imsi_len * 2 - 1; else imsi_len = (imsi_len - 1) * 2; for (i = 0, j = 0; j < imsi_len && j < 15; j++) { if (!(j & 1)) { imsi[j] = (bcd_imsi[i] >> 4) + '0'; i++; } else imsi[j] = (bcd_imsi[i] & 0xf) + '0'; } imsi[j] = '\0'; return 0; } int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) { struct bssgp_ud_hdr *budh; int8_t tfi; /* must be signed */ uint32_t tlli; uint8_t *data; uint16_t len; struct gprs_rlcmac_tbf *tbf; char imsi[16] = "000"; budh = (struct bssgp_ud_hdr *)msgb_bssgph(msg); tlli = ntohl(budh->tlli); /* LLC_PDU is mandatory IE */ if (!TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) { LOGP(DBSSGP, LOGL_NOTICE, "BSSGP TLLI=0x%08x Rx UL-UD missing mandatory IE\n", tlli); return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); } data = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_LLC_PDU); len = TLVP_LEN(tp, BSSGP_IE_LLC_PDU); if (len > sizeof(tbf->llc_frame)) { LOGP(DBSSGP, LOGL_NOTICE, "BSSGP TLLI=0x%08x Rx UL-UD IE_LLC_PDU too large\n", tlli); return bssgp_tx_status(BSSGP_CAUSE_COND_IE_ERR, NULL, msg); } /* read IMSI. if no IMSI exists, use first paging block (any paging), * because during attachment the IMSI might not be known, so the MS * will listen to all paging blocks. */ parse_imsi(tp, imsi); /* parse ms radio access capability */ uint8_t ms_class = 0; if (TLVP_PRESENT(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP)) { bitvec *block; uint8_t cap_len = TLVP_LEN(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP); uint8_t *cap = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP); unsigned rp = 0; block = bitvec_alloc(cap_len); bitvec_unpack(block, cap); bitvec_read_field(block, rp, 4); // Access Technology Type bitvec_read_field(block, rp, 7); // Length of Access Capabilities bitvec_read_field(block, rp, 3); // RF Power Capability if (bitvec_read_field(block, rp, 1)) // A5 Bits Present bitvec_read_field(block, rp, 7); // A5 Bits bitvec_read_field(block, rp, 1); // ES IND bitvec_read_field(block, rp, 1); // PS bitvec_read_field(block, rp, 1); // VGCS bitvec_read_field(block, rp, 1); // VBS if (bitvec_read_field(block, rp, 1)) { // Multislot Cap Present if (bitvec_read_field(block, rp, 1)) // HSCSD Present bitvec_read_field(block, rp, 5); // Class if (bitvec_read_field(block, rp, 1)) { // GPRS Present ms_class = bitvec_read_field(block, rp, 5); // Class bitvec_read_field(block, rp, 1); // Ext. } if (bitvec_read_field(block, rp, 1)) // SMS Present bitvec_read_field(block, rp, 4); // SMS Value bitvec_read_field(block, rp, 4); // SMS Value } bitvec_free(block); } /* get lifetime */ uint16_t delay_csec = 0xffff; if (TLVP_PRESENT(tp, BSSGP_IE_PDU_LIFETIME)) { uint8_t lt_len = TLVP_LEN(tp, BSSGP_IE_PDU_LIFETIME); uint16_t *lt = (uint16_t *) TLVP_VAL(tp, BSSGP_IE_PDU_LIFETIME); if (lt_len == 2) delay_csec = ntohs(*lt); else LOGP(DBSSGP, LOGL_NOTICE, "BSSGP invalid length of " "PDU_LIFETIME IE\n"); } else LOGP(DBSSGP, LOGL_NOTICE, "BSSGP missing mandatory " "PDU_LIFETIME IE\n"); LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x IMSI: %s len: %d\n", tlli, imsi, len); /* check for existing TBF */ if ((tbf = tbf_by_tlli(tlli, GPRS_RLCMAC_DL_TBF))) { LOGP(DRLCMAC, LOGL_INFO, "TBF: APPEND TFI: %u TLLI: 0x%08x\n", tbf->tfi, tbf->tlli); if (tbf->state == GPRS_RLCMAC_WAIT_RELEASE) { LOGP(DRLCMAC, LOGL_DEBUG, "TBF in WAIT RELEASE state " "(T3193), so reuse TBF\n"); memcpy(tbf->llc_frame, data, len); tbf->llc_length = len; memset(&tbf->dir.dl, 0, sizeof(tbf->dir.dl)); /* reset rlc states */ tbf->state_flags &= GPRS_RLCMAC_FLAG_TO_MASK; /* keep to flags */ tbf->state_flags &= ~(1 << GPRS_RLCMAC_FLAG_CCCH); if (!tbf->ms_class && ms_class) tbf->ms_class = ms_class; tbf_update(tbf); gprs_rlcmac_trigger_downlink_assignment(tbf, tbf, NULL); } else { /* the TBF exists, so we must write it in the queue * we prepend lifetime in front of PDU */ struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts; struct timeval *tv; struct msgb *llc_msg = msgb_alloc(len + sizeof(*tv), "llc_pdu_queue"); if (!llc_msg) return -ENOMEM; tv = (struct timeval *)msgb_put(llc_msg, sizeof(*tv)); if (bts->force_llc_lifetime) delay_csec = bts->force_llc_lifetime; /* keep timestap at 0 for infinite delay */ if (delay_csec != 0xffff) { /* calculate timestamp of timeout */ gettimeofday(tv, NULL); tv->tv_usec += (delay_csec % 100) * 10000; tv->tv_sec += delay_csec / 100; if (tv->tv_usec > 999999) { tv->tv_usec -= 1000000; tv->tv_sec++; } } memcpy(msgb_put(llc_msg, len), data, len); msgb_enqueue(&tbf->llc_queue, llc_msg); /* set ms class for updating TBF */ if (!tbf->ms_class && ms_class) tbf->ms_class = ms_class; } } else { uint8_t trx, ta, ss; int8_t use_trx; struct gprs_rlcmac_tbf *old_tbf; /* check for uplink data, so we copy our informations */ tbf = tbf_by_tlli(tlli, GPRS_RLCMAC_UL_TBF); if (tbf && tbf->dir.ul.contention_resolution_done && !tbf->dir.ul.final_ack_sent) { use_trx = tbf->trx; ta = tbf->ta; ss = 0; old_tbf = tbf; } else { use_trx = -1; ta = 0; /* FIXME: initial TA */ ss = 1; /* PCH assignment only allows one timeslot */ old_tbf = NULL; } // Create new TBF (any TRX) tfi = tfi_alloc(GPRS_RLCMAC_DL_TBF, &trx, use_trx); if (tfi < 0) { LOGP(DRLCMAC, LOGL_NOTICE, "No PDCH resource\n"); /* FIXME: send reject */ return -EBUSY; } /* set number of downlink slots according to multislot class */ tbf = tbf_alloc(tbf, GPRS_RLCMAC_DL_TBF, tfi, trx, ms_class, ss); if (!tbf) { LOGP(DRLCMAC, LOGL_NOTICE, "No PDCH ressource\n"); /* FIXME: send reject */ return -EBUSY; } tbf->tlli = tlli; tbf->tlli_valid = 1; tbf->ta = ta; LOGP(DRLCMAC, LOGL_DEBUG, "TBF: [DOWNLINK] START TFI: %d TLLI: 0x%08x \n", tbf->tfi, tbf->tlli); /* new TBF, so put first frame */ memcpy(tbf->llc_frame, data, len); tbf->llc_length = len; /* trigger downlink assignment and set state to ASSIGN. * we don't use old_downlink, so the possible uplink is used * to trigger downlink assignment. if there is no uplink, * AGCH is used. */ gprs_rlcmac_trigger_downlink_assignment(tbf, old_tbf, imsi); } /* store IMSI for debugging purpose */ strncpy(tbf->meas.imsi, imsi, sizeof(tbf->meas.imsi) - 1); return 0; } int gprs_bssgp_pcu_rx_paging_ps(struct msgb *msg, struct tlv_parsed *tp) { char imsi[16]; uint8_t *ptmsi = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_TMSI); uint16_t ptmsi_len = TLVP_LEN(tp, BSSGP_IE_TMSI); LOGP(DBSSGP, LOGL_NOTICE, " P-TMSI = "); for (int i = 0; i < ptmsi_len; i++) { LOGPC(DBSSGP, LOGL_NOTICE, "%02x", ptmsi[i]); } LOGPC(DBSSGP, LOGL_NOTICE, "\n"); if (parse_imsi(tp, imsi)) { LOGP(DBSSGP, LOGL_ERROR, "No IMSI\n"); return -EINVAL; } return gprs_rlcmac_paging_request(ptmsi, ptmsi_len, imsi); } /* Receive a BSSGP PDU from a BSS on a PTP BVCI */ int gprs_bssgp_pcu_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, struct bssgp_bvc_ctx *bctx) { struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); uint8_t pdu_type = bgph->pdu_type; unsigned rc = 0; if (!bctx) return -EINVAL; /* If traffic is received on a BVC that is marked as blocked, the * received PDU shall not be accepted and a STATUS PDU (Cause value: * BVC Blocked) shall be sent to the peer entity on the signalling BVC */ if (bctx->state & BVC_S_BLOCKED && pdu_type != BSSGP_PDUT_STATUS) { uint16_t bvci = msgb_bvci(msg); LOGP(DBSSGP, LOGL_NOTICE, "rx BVC_S_BLOCKED\n"); return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &bvci, msg); } switch (pdu_type) { case BSSGP_PDUT_DL_UNITDATA: LOGP(DBSSGP, LOGL_DEBUG, "RX: [SGSN->PCU] BSSGP_PDUT_DL_UNITDATA\n"); gprs_bssgp_pcu_rx_dl_ud(msg, tp); break; case BSSGP_PDUT_PAGING_PS: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_PAGING_PS\n"); break; case BSSGP_PDUT_PAGING_CS: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_PAGING_CS\n"); break; case BSSGP_PDUT_RA_CAPA_UPDATE_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_RA_CAPA_UPDATE_ACK\n"); break; case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_FLOW_CONTROL_BVC_ACK\n"); break; case BSSGP_PDUT_FLOW_CONTROL_MS_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_FLOW_CONTROL_MS_ACK\n"); break; default: LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u PDU type 0x%02x unknown\n", bctx->bvci, pdu_type); rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); break; } return rc; } /* Receive a BSSGP PDU from a SGSN on a SIGNALLING BVCI */ int gprs_bssgp_pcu_rx_sign(struct msgb *msg, struct tlv_parsed *tp, struct bssgp_bvc_ctx *bctx) { struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); int rc = 0; switch (bgph->pdu_type) { case BSSGP_PDUT_STATUS: /* Some exception has occurred */ DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC STATUS\n", bctx->bvci); /* FIXME: send NM_STATUS.ind to NM */ break; case BSSGP_PDUT_SUSPEND_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SUSPEND_ACK\n"); break; case BSSGP_PDUT_SUSPEND_NACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SUSPEND_NACK\n"); break; case BSSGP_PDUT_BVC_RESET_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_RESET_ACK\n"); if (!bvc_sig_reset) bvc_sig_reset = 1; else bvc_reset = 1; bvc_timeout(NULL); break; case BSSGP_PDUT_PAGING_PS: LOGP(DBSSGP, LOGL_NOTICE, "RX: [SGSN->PCU] BSSGP_PDUT_PAGING_PS\n"); gprs_bssgp_pcu_rx_paging_ps(msg, tp); break; case BSSGP_PDUT_PAGING_CS: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_PAGING_CS\n"); break; case BSSGP_PDUT_RESUME_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_RESUME_ACK\n"); break; case BSSGP_PDUT_RESUME_NACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_RESUME_NACK\n"); break; case BSSGP_PDUT_FLUSH_LL: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_FLUSH_LL\n"); break; case BSSGP_PDUT_BVC_BLOCK_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SUSPEND_ACK\n"); break; case BSSGP_PDUT_BVC_UNBLOCK_ACK: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_UNBLOCK_ACK\n"); bvc_unblocked = 1; bvc_timeout(NULL); break; case BSSGP_PDUT_SGSN_INVOKE_TRACE: LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SGSN_INVOKE_TRACE\n"); break; default: LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx PDU type 0x%02x unknown\n", bctx->bvci, bgph->pdu_type); rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); break; } return rc; } int gprs_bssgp_pcu_rcvmsg(struct msgb *msg) { struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg); struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); struct tlv_parsed tp; uint8_t pdu_type = bgph->pdu_type; uint16_t ns_bvci = msgb_bvci(msg); int data_len; int rc = 0; struct bssgp_bvc_ctx *bctx; if (pdu_type == BSSGP_PDUT_STATUS) { LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u received STATUS\n", msgb_nsei(msg), ns_bvci); return 0; } /* Identifiers from DOWN: NSEI, BVCI (both in msg->cb) */ /* UNITDATA BSSGP headers have TLLI in front */ if (pdu_type != BSSGP_PDUT_UL_UNITDATA && pdu_type != BSSGP_PDUT_DL_UNITDATA) { data_len = msgb_bssgp_len(msg) - sizeof(*bgph); rc = bssgp_tlv_parse(&tp, bgph->data, data_len); } else { data_len = msgb_bssgp_len(msg) - sizeof(*budh); rc = bssgp_tlv_parse(&tp, budh->data, data_len); } /* look-up or create the BTS context for this BVC */ bctx = btsctx_by_bvci_nsei(ns_bvci, msgb_nsei(msg)); if (!bctx && pdu_type != BSSGP_PDUT_BVC_RESET_ACK && pdu_type != BSSGP_PDUT_BVC_UNBLOCK_ACK && pdu_type != BSSGP_PDUT_PAGING_PS) { LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU " "type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci, pdu_type); return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, NULL, msg); } if (bctx) { log_set_context(BSC_CTX_BVC, bctx); rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]); rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN], msgb_bssgp_len(msg)); } if (ns_bvci == BVCI_SIGNALLING) { LOGP(DBSSGP, LOGL_DEBUG, "rx BVCI_SIGNALLING gprs_bssgp_rx_sign\n"); rc = gprs_bssgp_pcu_rx_sign(msg, &tp, bctx); } else if (ns_bvci == BVCI_PTM) { LOGP(DBSSGP, LOGL_DEBUG, "rx BVCI_PTM bssgp_tx_status\n"); rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); } else { LOGP(DBSSGP, LOGL_DEBUG, "rx BVCI_PTP gprs_bssgp_rx_ptp\n"); rc = gprs_bssgp_pcu_rx_ptp(msg, &tp, bctx); } return rc; } int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) { return 0; } static int sgsn_ns_cb(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, struct msgb *msg, uint16_t bvci) { int rc = 0; switch (event) { case GPRS_NS_EVT_UNIT_DATA: /* hand the message into the BSSGP implementation */ rc = gprs_bssgp_pcu_rcvmsg(msg); break; default: LOGP(DPCU, LOGL_NOTICE, "RLCMAC: Unknown event %u from NS\n", event); rc = -EIO; break; } return rc; } static int nsvc_unblocked = 0; static int nsvc_signal_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct ns_signal_data *nssd; if (subsys != SS_L_NS) return -EINVAL; nssd = (struct ns_signal_data *)signal_data; if (nssd->nsvc != nsvc) { LOGP(DPCU, LOGL_ERROR, "Signal received of unknown NSVC\n"); return -EINVAL; } switch (signal) { case S_NS_UNBLOCK: if (!nsvc_unblocked) { nsvc_unblocked = 1; LOGP(DPCU, LOGL_NOTICE, "NS-VC %d is unblocked.\n", nsvc->nsvci); bvc_sig_reset = 0; bvc_reset = 0; bvc_unblocked = 0; bvc_timeout(NULL); } break; case S_NS_BLOCK: if (nsvc_unblocked) { nsvc_unblocked = 0; if (osmo_timer_pending(&bvc_timer)) osmo_timer_del(&bvc_timer); bvc_sig_reset = 0; bvc_reset = 0; bvc_unblocked = 0; LOGP(DPCU, LOGL_NOTICE, "NS-VC is blocked.\n"); } break; } return 0; } int gprs_bssgp_tx_fc_bvc(void) { if (!bctx) { LOGP(DBSSGP, LOGL_ERROR, "No bctx\n"); return -EIO; } /* FIXME: use real values */ return bssgp_tx_fc_bvc(bctx, 1, 6553500, 819100, 50000, 50000, NULL, NULL); // return bssgp_tx_fc_bvc(bctx, 1, 84000, 25000, 48000, 45000, // NULL, NULL); } static void bvc_timeout(void *_priv) { struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts; if (!bvc_sig_reset) { LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI 0\n"); bssgp_tx_bvc_reset(bctx, 0, BSSGP_CAUSE_OML_INTERV); osmo_timer_schedule(&bvc_timer, 1, 0); return; } if (!bvc_reset) { LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI %d\n", bctx->bvci); bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_OML_INTERV); osmo_timer_schedule(&bvc_timer, 1, 0); return; } if (!bvc_unblocked) { LOGP(DBSSGP, LOGL_INFO, "Sending unblock on BVCI %d\n", bctx->bvci); bssgp_tx_bvc_unblock(bctx); osmo_timer_schedule(&bvc_timer, 1, 0); return; } LOGP(DBSSGP, LOGL_DEBUG, "Sending flow control info on BVCI %d\n", bctx->bvci); gprs_bssgp_tx_fc_bvc(); osmo_timer_schedule(&bvc_timer, bts->fc_interval, 0); } /* create BSSGP/NS layer instances */ int gprs_bssgp_create(uint16_t local_port, uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei, uint16_t nsvci, uint16_t bvci, uint16_t mcc, uint16_t mnc, uint16_t lac, uint16_t rac, uint16_t cell_id) { struct sockaddr_in dest; int rc; mcc = ((mcc & 0xf00) >> 8) * 100 + ((mcc & 0x0f0) >> 4) * 10 + (mcc & 0x00f); mnc = ((mnc & 0xf00) >> 8) * 100 + ((mnc & 0x0f0) >> 4) * 10 + (mnc & 0x00f); cell_id = ntohs(cell_id); if (bctx) return 0; /* if already created, must return 0: no error */ bssgp_nsi = gprs_ns_instantiate(&sgsn_ns_cb, tall_pcu_ctx); if (!bssgp_nsi) { LOGP(DBSSGP, LOGL_ERROR, "Failed to create NS instance\n"); return -EINVAL; } gprs_ns_vty_init(bssgp_nsi); bssgp_nsi->nsip.local_port = local_port; rc = gprs_ns_nsip_listen(bssgp_nsi); if (rc < 0) { LOGP(DBSSGP, LOGL_ERROR, "Failed to create socket\n"); gprs_ns_destroy(bssgp_nsi); bssgp_nsi = NULL; return -EINVAL; } dest.sin_family = AF_INET; dest.sin_port = htons(sgsn_port); dest.sin_addr.s_addr = htonl(sgsn_ip); nsvc = gprs_ns_nsip_connect(bssgp_nsi, &dest, nsei, nsvci); if (!nsvc) { LOGP(DBSSGP, LOGL_ERROR, "Failed to create NSVCt\n"); gprs_ns_destroy(bssgp_nsi); bssgp_nsi = NULL; return -EINVAL; } bctx = btsctx_alloc(bvci, nsei); if (!bctx) { LOGP(DBSSGP, LOGL_ERROR, "Failed to create BSSGP context\n"); nsvc = NULL; gprs_ns_destroy(bssgp_nsi); bssgp_nsi = NULL; return -EINVAL; } bctx->ra_id.mcc = spoof_mcc ? : mcc; bctx->ra_id.mnc = spoof_mnc ? : mnc; bctx->ra_id.lac = lac; bctx->ra_id.rac = rac; bctx->cell_id = cell_id; osmo_signal_register_handler(SS_L_NS, nsvc_signal_cb, NULL); // bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_PROTO_ERR_UNSPEC); bvc_timer.cb = bvc_timeout; return 0; } void gprs_bssgp_destroy(void) { if (!bssgp_nsi) return; if (osmo_timer_pending(&bvc_timer)) osmo_timer_del(&bvc_timer); osmo_signal_unregister_handler(SS_L_NS, nsvc_signal_cb, NULL); nsvc = NULL; /* FIXME: move this to libgb: btsctx_free() */ llist_del(&bctx->list); talloc_free(bctx); bctx = NULL; /* FIXME: blocking... */ gprs_ns_destroy(bssgp_nsi); bssgp_nsi = NULL; }