diff options
author | Jacob Erlbeck <jerlbeck@sysmocom.de> | 2013-10-24 01:33:23 +0200 |
---|---|---|
committer | Holger Hans Peter Freyther <holger@moiji-mobile.com> | 2013-10-24 10:01:18 +0200 |
commit | 5405a104bbe7d3711fe6f19ea7d0fce50ef91147 (patch) | |
tree | d0bac56348b47cb2bf698e8d794d2cb1bc2c8801 /src/gb/gprs_ns.c | |
parent | 81d7c14756ee17c2748c3d34a198c640655fd0c6 (diff) |
gb: Fix NS RESET/RESET_ACK abnormal cases
This changes the implementations for the reception of RESET and
RESET_ACK to be compatible with 3GPP TS 08.16, 7.3.1:
- Just send a RESET_ACK with correct values back to the SGSN when a
RESET with an invalid NSVCI or NSEI has been received.
- Check RESET_ACK for matching NSEI and NSVCI.
- Ignore unexpected RESET_ACKs.
In addition, use RESET_ACK from a BSS to update the BSS source
address based on the NSVCI to be tolerant with changing UDP source
addresses/ports.
Sponsored-by: On-Waves ehf
Diffstat (limited to 'src/gb/gprs_ns.c')
-rw-r--r-- | src/gb/gprs_ns.c | 271 |
1 files changed, 215 insertions, 56 deletions
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c index a4a6376f..a597c2c0 100644 --- a/src/gb/gprs_ns.c +++ b/src/gb/gprs_ns.c @@ -102,6 +102,8 @@ enum ns_ctr { NS_CTR_DEAD, NS_CTR_REPLACED, NS_CTR_NSEI_CHG, + NS_CTR_INV_VCI, + NS_CTR_INV_NSEI, }; static const struct rate_ctr_desc nsvc_ctr_description[] = { @@ -113,6 +115,8 @@ static const struct rate_ctr_desc nsvc_ctr_description[] = { { "dead", "NS-VC gone dead count " }, { "replaced", "NS-VC replaced other count" }, { "nsei-chg", "NS-VC changed NSEI " }, + { "inv-nsvci", "NS-VCI was invalid count " }, + { "inv-nsei", "NSEI was invalid count " }, }; static const struct rate_ctr_group_desc nsvc_ctrg_desc = { @@ -225,6 +229,20 @@ static void ns_osmo_signal_dispatch(struct gprs_nsvc *nsvc, unsigned int signal, osmo_signal_dispatch(SS_L_NS, signal, &nssd); } +static void ns_osmo_signal_dispatch_mismatch(struct gprs_nsvc *nsvc, + struct msgb *msg, + uint8_t pdu_type, uint8_t ie_type) +{ + struct ns_signal_data nssd = {0}; + + nssd.nsvc = nsvc; + nssd.pdu_type = pdu_type; + nssd.ie_type = ie_type; + nssd.msg = msg; + + osmo_signal_dispatch(SS_L_NS, S_NS_MISMATCH, &nssd); +} + static void ns_osmo_signal_dispatch_replaced(struct gprs_nsvc *nsvc, struct gprs_nsvc *old_nsvc) { struct ns_signal_data nssd = {0}; @@ -323,6 +341,8 @@ int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause) LOGP(DNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n", nsvc->nsei, nsvc->nsvci, gprs_ns_cause_str(cause)); + nsvc->state |= NSE_S_RESET; + msg->l2h = msgb_put(msg, sizeof(*nsh)); nsh = (struct gprs_ns_hdr *) msg->l2h; nsh->pdu_type = NS_PDUT_RESET; @@ -672,6 +692,49 @@ static int gprs_ns_rx_status(struct gprs_nsvc *nsvc, struct msgb *msg) return 0; } +/* Replace a nsvc object with another based on NSVCI. + * This function replaces looks for a NSVC with the given NSVCI and replaces it + * if possible and necessary. If replaced, the former value of *nsvc is + * returned in *old_nsvc. + * \return != 0 if *nsvc points to a matching NSVC. + */ +static int gprs_nsvc_replace_if_found(uint16_t nsvci, + struct gprs_nsvc **nsvc, + struct gprs_nsvc **old_nsvc) +{ + struct gprs_nsvc *matching_nsvc; + + if ((*nsvc)->nsvci == nsvci) { + *old_nsvc = NULL; + return 1; + } + + matching_nsvc = gprs_nsvc_by_nsvci((*nsvc)->nsi, nsvci); + + if (!matching_nsvc) + return 0; + + /* The NS-VCI is already used by this NS-VC */ + + char *old_peer; + + /* Exchange the NS-VC objects */ + *old_nsvc = *nsvc; + *nsvc = matching_nsvc; + + /* Do logging */ + old_peer = talloc_strdup(*old_nsvc, gprs_ns_ll_str(*old_nsvc)); + LOGP(DNS, LOGL_INFO, "NS-VC changed link (NSVCI=%u) from %s to %s\n", + nsvci, old_peer, gprs_ns_ll_str(*nsvc)); + + talloc_free(old_peer); + + /* Do statistics */ + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]); + + return 1; +} + /* Section 7.3 */ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) { @@ -679,7 +742,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) struct tlv_parsed tp; uint8_t cause; uint16_t nsvci, nsei; - struct gprs_nsvc *other_nsvc = NULL; + struct gprs_nsvc *orig_nsvc = NULL; int rc; rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, @@ -706,57 +769,47 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) (*nsvc)->nsvci, (*nsvc)->nsvci_is_valid ? "" : "(invalid)", nsei, nsvci, gprs_ns_cause_str(cause)); - if ((*nsvc)->nsvci_is_valid && (*nsvc)->nsvci != nsvci) { - /* NS-VCI has changed */ - other_nsvc = gprs_nsvc_by_nsvci((*nsvc)->nsi, nsvci); - - if (other_nsvc) { - /* The NS-VCI is already used by this NS-VC */ - - struct gprs_nsvc *tmp_nsvc; - char *old_peer; - - /* Exchange the NS-VC objects */ - tmp_nsvc = *nsvc; - *nsvc = other_nsvc; - other_nsvc = tmp_nsvc; - - /* Do logging */ - old_peer = talloc_strdup(other_nsvc, - gprs_ns_ll_str(other_nsvc)); - LOGP(DNS, LOGL_INFO, - "NS-VC changed link (NSVCI=%u) from %s to %s\n", - nsvci, old_peer, gprs_ns_ll_str(*nsvc)); - - talloc_free(old_peer); - - /* Do statistics */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]); + if (!(*nsvc)->nsvci_is_valid) { + /* It's a new uninitialised NS-VC, nothing to check here */ + } else if ((*nsvc)->nsvci != nsvci) { + if ((*nsvc)->remote_end_is_sgsn) { + /* The incoming RESET doesn't match the NSVCI. Send an + * appropriate RESET_ACK and ignore the RESET. + * See 3GPP TS 08.16, 7.3.1, 2nd paragraph. + */ + ns_osmo_signal_dispatch_mismatch(*nsvc, msg, + NS_PDUT_RESET, + NS_IE_VCI); + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]); + gprs_ns_tx_reset_ack(*nsvc); + return 0; } + + /* NS-VCI has changed */ + gprs_nsvc_replace_if_found(nsvci, nsvc, &orig_nsvc); + + } else if ((*nsvc)->nsei != nsei) { + /* The incoming RESET doesn't match the NSEI. Send an + * appropriate RESET_ACK and ignore the RESET. + * See 3GPP TS 08.16, 7.3.1, 3rd paragraph. + */ + ns_osmo_signal_dispatch_mismatch(*nsvc, msg, + NS_PDUT_RESET, + NS_IE_NSEI); + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]); + gprs_ns_tx_reset_ack(*nsvc); + return 0; } /* Mark NS-VC as blocked and alive */ (*nsvc)->state = NSE_S_BLOCKED | NSE_S_ALIVE; - if (other_nsvc) { - /* Check NSEI */ - if ((*nsvc)->nsei != nsei) { - LOGP(DNS, LOGL_NOTICE, - "NS-VC changed NSEI (NSVCI=%u) from %u to %u\n", - nsvci, (*nsvc)->nsei, nsei); - - /* Override old NSEI */ - (*nsvc)->nsei = nsei; - - /* Do statistics */ - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]); - } - - ns_osmo_signal_dispatch_replaced(*nsvc, other_nsvc); + if (orig_nsvc) { + ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc); /* Update the ll info fields */ - gprs_ns_ll_copy(*nsvc, other_nsvc); - gprs_ns_ll_clear(other_nsvc); + gprs_ns_ll_copy(*nsvc, orig_nsvc); + gprs_ns_ll_clear(orig_nsvc); } else { (*nsvc)->nsei = nsei; (*nsvc)->nsvci = nsvci; @@ -776,6 +829,121 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg) return rc; } +static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg) +{ + struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct tlv_parsed tp; + uint16_t nsvci, nsei; + struct gprs_nsvc *orig_nsvc = NULL; + int rc; + + rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, + msgb_l2len(msg) - sizeof(*nsh), 0, 0); + if (rc < 0) { + LOGP(DNS, LOGL_ERROR, "NSEI=%u Rx NS RESET ACK " + "Error during TLV Parse\n", (*nsvc)->nsei); + return rc; + } + + if (!TLVP_PRESENT(&tp, NS_IE_VCI) || + !TLVP_PRESENT(&tp, NS_IE_NSEI)) { + LOGP(DNS, LOGL_ERROR, "NS RESET ACK Missing mandatory IE\n"); + gprs_ns_tx_status(*nsvc, NS_CAUSE_MISSING_ESSENT_IE, 0, msg); + return -EINVAL; + } + + nsvci = ntohs(*(uint16_t *) TLVP_VAL(&tp, NS_IE_VCI)); + nsei = ntohs(*(uint16_t *) TLVP_VAL(&tp, NS_IE_NSEI)); + + LOGP(DNS, LOGL_INFO, "NSVCI=%u%s Rx NS RESET ACK (NSEI=%u, NSVCI=%u)\n", + (*nsvc)->nsvci, (*nsvc)->nsvci_is_valid ? "" : "(invalid)", + nsei, nsvci); + + if (!((*nsvc)->state & NSE_S_RESET)) { + /* Not waiting for a RESET_ACK on this NS-VC, ignore it. + * See 3GPP TS 08.16, 7.3.1, 5th paragraph. + */ + LOGP(DNS, LOGL_ERROR, + "NS RESET ACK Discarding unexpected message for " + "NS-VCI %d from SGSN NSEI=%d\n", + nsvci, nsei); + return 0; + } + + if (!(*nsvc)->nsvci_is_valid) { + LOGP(DNS, LOGL_NOTICE, + "NS RESET ACK Uninitialised NS-VC (%u) for " + "NS-VCI %d, NSEI=%d from %s\n", + (*nsvc)->nsvci, nsvci, nsei, gprs_ns_ll_str(*nsvc)); + return -EINVAL; + } + + if ((*nsvc)->nsvci != nsvci) { + /* NS-VCI has changed */ + + /* if !0, use another NSVC object that matches the NSVCI */ + int use_other_nsvc; + + /* Only do this with BSS peers */ + use_other_nsvc = !(*nsvc)->remote_end_is_sgsn; + + if (use_other_nsvc) + /* Update *nsvc to point to the right NSVC object */ + use_other_nsvc = gprs_nsvc_replace_if_found(nsvci, nsvc, + &orig_nsvc); + + if (!use_other_nsvc) { + /* The incoming RESET_ACK doesn't match the NSVCI. + * See 3GPP TS 08.16, 7.3.1, 4th paragraph. + */ + ns_osmo_signal_dispatch_mismatch(*nsvc, msg, + NS_PDUT_RESET_ACK, + NS_IE_VCI); + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]); + LOGP(DNS, LOGL_ERROR, + "NS RESET ACK Unknown NS-VCI %d (%s NSEI=%d) " + "from %s\n", + nsvci, + (*nsvc)->remote_end_is_sgsn ? "SGSN" : "BSS", + nsei, gprs_ns_ll_str(*nsvc)); + return -EINVAL; + } + + /* Notify others */ + ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc); + + /* Update the ll info fields */ + gprs_ns_ll_copy(*nsvc, orig_nsvc); + gprs_ns_ll_clear(orig_nsvc); + } else if ((*nsvc)->nsei != nsei) { + /* The incoming RESET_ACK doesn't match the NSEI. + * See 3GPP TS 08.16, 7.3.1, 4th paragraph. + */ + ns_osmo_signal_dispatch_mismatch(*nsvc, msg, + NS_PDUT_RESET_ACK, + NS_IE_NSEI); + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]); + LOGP(DNS, LOGL_ERROR, + "NS RESET ACK Unknown NSEI %d (NS-VCI=%u) from %s\n", + nsei, nsvci, gprs_ns_ll_str(*nsvc)); + return -EINVAL; + } + + /* Mark NS-VC as blocked and alive */ + (*nsvc)->state = NSE_S_BLOCKED | NSE_S_ALIVE; + (*nsvc)->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE; + rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]); + if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) { + /* stop RESET timer */ + osmo_timer_del(&(*nsvc)->timer); + } + /* Initiate TEST proc.: Send ALIVE and start timer */ + rc = gprs_ns_tx_simple(*nsvc, NS_PDUT_ALIVE); + nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST); + + return rc; +} + static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg) { struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; @@ -944,6 +1112,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg, "from %s for non-existing NS-VC\n", nsh->pdu_type, gprs_ns_ll_str(fallback_nsvc)); fallback_nsvc->nsvci = fallback_nsvc->nsei = 0xfffe; + fallback_nsvc->nsvci_is_valid = 0; fallback_nsvc->state = NSE_S_ALIVE; rc = gprs_ns_tx_status(fallback_nsvc, @@ -1057,18 +1226,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg, rc = gprs_ns_rx_reset(nsvc, msg); break; case NS_PDUT_RESET_ACK: - LOGP(DNS, LOGL_INFO, "NSEI=%u Rx NS RESET ACK\n", (*nsvc)->nsei); - /* mark NS-VC as blocked + active */ - (*nsvc)->state = NSE_S_BLOCKED | NSE_S_ALIVE; - (*nsvc)->remote_state = NSE_S_BLOCKED | NSE_S_ALIVE; - rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]); - if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) { - /* stop RESET timer */ - osmo_timer_del(&(*nsvc)->timer); - } - /* Initiate TEST proc.: Send ALIVE and start timer */ - rc = gprs_ns_tx_simple(*nsvc, NS_PDUT_ALIVE); - nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST); + rc = gprs_ns_rx_reset_ack(nsvc, msg); break; case NS_PDUT_UNBLOCK: /* Section 7.2: unblocking procedure */ @@ -1323,6 +1481,7 @@ struct gprs_nsvc *gprs_ns_nsip_connect(struct gprs_ns_inst *nsi, nsvc->ip.bts_addr = *dest; nsvc->nsei = nsei; nsvc->nsvci = nsvci; + nsvc->nsvci_is_valid = 1; nsvc->remote_end_is_sgsn = 1; gprs_nsvc_reset(nsvc, NS_CAUSE_OM_INTERVENTION); |