diff options
Diffstat (limited to 'src/gprs_bssgp_pcu.cpp')
-rw-r--r-- | src/gprs_bssgp_pcu.cpp | 223 |
1 files changed, 207 insertions, 16 deletions
diff --git a/src/gprs_bssgp_pcu.cpp b/src/gprs_bssgp_pcu.cpp index 8e81304f..44fb7006 100644 --- a/src/gprs_bssgp_pcu.cpp +++ b/src/gprs_bssgp_pcu.cpp @@ -25,15 +25,21 @@ struct sgsn_instance *sgsn; void *tall_bsc_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); + int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) { struct bssgp_ud_hdr *budh; - int tfi; + + int8_t tfi; /* must be signed */ + uint32_t tlli; int i, j; - uint8_t trx, ts; uint8_t *data; uint16_t len; struct gprs_rlcmac_tbf *tbf; @@ -78,6 +84,54 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) } imsi[j] = '\0'; } + + /* 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 + } + } + /* 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 */ @@ -90,29 +144,83 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) tbf->llc_length = len; memset(&tbf->dir.dl, 0, sizeof(tbf->dir.dl)); /* reset rlc states */ - gprs_rlcmac_trigger_downlink_assignment(tbf, 1, NULL); + 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 */ - struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue"); + /* 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 { - // Create new TBF - tfi = tfi_alloc(&trx, &ts); + uint8_t trx, ts, use_trx, first_ts, ta, ss; + 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; + first_ts = tbf->first_ts; + ta = tbf->ta; + ss = 0; + old_tbf = tbf; + } else { + use_trx = -1; + first_ts = -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, &ts, use_trx, first_ts); if (tfi < 0) { LOGP(DRLCMAC, LOGL_NOTICE, "No PDCH resource\n"); /* FIXME: send reject */ return -EBUSY; } - tbf = tbf_alloc(tfi, trx, ts); - tbf->direction = GPRS_RLCMAC_DL_TBF; + /* set number of downlink slots according to multislot class */ + tbf = tbf_alloc(tbf, GPRS_RLCMAC_DL_TBF, tfi, trx, ts, 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: %u TLLI: 0x%08x \n", tbf->tfi, tbf->tlli); + 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); @@ -122,7 +230,7 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp) * 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, 0, imsi); + gprs_rlcmac_trigger_downlink_assignment(tbf, old_tbf, imsi); } return 0; @@ -135,6 +243,9 @@ int gprs_bssgp_pcu_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, struct bssgp_ 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 */ @@ -192,6 +303,11 @@ int gprs_bssgp_pcu_rx_sign(struct msgb *msg, struct tlv_parsed *tp, struct bssgp 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_DEBUG, "rx BSSGP_PDUT_PAGING_PS\n"); @@ -213,6 +329,8 @@ int gprs_bssgp_pcu_rx_sign(struct msgb *msg, struct tlv_parsed *tp, struct bssgp 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"); @@ -259,7 +377,9 @@ int gprs_bssgp_pcu_rcvmsg(struct msgb *msg) /* 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) + if (!bctx + && pdu_type != BSSGP_PDUT_BVC_RESET_ACK + && pdu_type != BSSGP_PDUT_BVC_UNBLOCK_ACK) { LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU " "type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci, @@ -335,14 +455,22 @@ static int nsvc_signal_cb(unsigned int subsys, unsigned int signal, case S_NS_UNBLOCK: if (!nsvc_unblocked) { nsvc_unblocked = 1; - LOGP(DPCU, LOGL_NOTICE, "NS-VC is unblocked.\n"); - bssgp_tx_bvc_reset(bctx, bctx->bvci, - BSSGP_CAUSE_PROTO_ERR_UNSPEC); + 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; @@ -351,12 +479,63 @@ static int nsvc_signal_cb(unsigned int subsys, unsigned int signal, 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(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 */ @@ -366,7 +545,13 @@ int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei, LOGP(DBSSGP, LOGL_ERROR, "Failed to create NS instance\n"); return -EINVAL; } - gprs_ns_nsip_listen(bssgp_nsi); + 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); @@ -398,6 +583,9 @@ int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei, // bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_PROTO_ERR_UNSPEC); + bvc_timer.cb = bvc_timeout; + + return 0; } @@ -406,6 +594,9 @@ 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; |