diff options
| author | Harald Welte <laforge@gnumonks.org> | 2010-03-14 15:45:01 +0800 | 
|---|---|---|
| committer | Harald Welte <laforge@gnumonks.org> | 2010-05-04 07:20:41 +0200 | 
| commit | 9ba500559a1609fa4bd0a7a7ea7f4d73c65d9440 (patch) | |
| tree | 436cd469c5802c0047c0359b2587abcafdfaed9f | |
| parent | 20488d488125ccec85c8f348ff0080b1c4386f9b (diff) | |
Create new 'gprs-sgsn' branch on top of 'gprs-conf'
This branch contains the partial SGSN/GGSN implementation that
was originally developed as part of the gprs branch.
| -rw-r--r-- | openbsc/include/openbsc/gprs_bssgp.h | 138 | ||||
| -rw-r--r-- | openbsc/include/openbsc/gprs_ns.h | 61 | ||||
| -rw-r--r-- | openbsc/src/gprs_bssgp.c | 397 | ||||
| -rw-r--r-- | openbsc/src/gprs_ns.c | 348 | 
4 files changed, 944 insertions, 0 deletions
| diff --git a/openbsc/include/openbsc/gprs_bssgp.h b/openbsc/include/openbsc/gprs_bssgp.h new file mode 100644 index 00000000..f85ac48e --- /dev/null +++ b/openbsc/include/openbsc/gprs_bssgp.h @@ -0,0 +1,138 @@ +#ifndef _GPRS_BSSGP_H +#define _GPRS_BSSGP_H + +/* Section 11.3.26 / Table 11.27 */ +enum bssgp_pdu_type { +	/* PDUs between RL and BSSGP SAPs */ +	BSSGP_PDUT_DL_UNITDATA		= 0x00, +	BSSGP_PDUT_UL_UNITDATA		= 0x01, +	BSSGP_PDUT_RA_CAPABILITY	= 0x02, +	BSSGP_PDUT_PTM_UNITDATA		= 0x03, +	/* PDUs between GMM SAPs */ +	BSSGP_PDUT_PAGING_PS		= 0x06, +	BSSGP_PDUT_PAGING_CS		= 0x07, +	BSSGP_PDUT_RA_CAPA_UDPATE	= 0x08, +	BSSGP_PDUT_RA_CAPA_UPDATE_ACK	= 0x09, +	BSSGP_PDUT_RADIO_STATUS		= 0x0a, +	BSSGP_PDUT_SUSPEND		= 0x0b, +	BSSGP_PDUT_SUSPEND_ACK		= 0x0c, +	BSSGP_PDUT_SUSPEND_NACK		= 0x0d, +	BSSGP_PDUT_RESUME		= 0x0e, +	BSSGP_PDUT_RESUME_ACK		= 0x0f, +	BSSGP_PDUT_RESUME_NACK		= 0x10, +	/* PDus between NM SAPs */ +	BSSGP_PDUT_BVC_BLOCK		= 0x20, +	BSSGP_PDUT_BVC_BLOCK_ACK	= 0x21, +	BSSGP_PDUT_BVC_RESET		= 0x22, +	BSSGP_PDUT_BVC_RESET_ACK	= 0x23, +	BSSGP_PDUT_BVC_UNBLOCK		= 0x24, +	BSSGP_PDUT_BVC_UNBLOCK_ACK	= 0x25, +	BSSGP_PDUT_FLOW_CONTROL_BVC	= 0x26, +	BSSGP_PDUT_FLOW_CONTROL_BVC_ACK	= 0x27, +	BSSGP_PDUT_FLOW_CONTROL_MS	= 0x28, +	BSSGP_PDUT_FLOW_CONTROL_MS_ACK	= 0x29, +	BSSGP_PDUT_FLUSH_LL		= 0x2a, +	BSSGP_PDUT_FLUSH_LL_ACK		= 0x2b, +	BSSGP_PDUT_LLC_DISCARD		= 0x2c, +	BSSGP_PDUT_SGSN_INVOKE_TRACE	= 0x40, +	BSSGP_PDUT_STATUS		= 0x41, +	/* PDUs between PFM SAP's */ +	BSSGP_PDUT_DOWNLOAD_BSS_PFC	= 0x50, +	BSSGP_PDUT_CREATE_BSS_PFC	= 0x51, +	BSSGP_PDUT_CREATE_BSS_PFC_ACK	= 0x52, +	BSSGP_PDUT_CREATE_BSS_PFC_NACK	= 0x53, +	BSSGP_PDUT_MODIFY_BSS_PFC	= 0x54, +	BSSGP_PDUT_MODIFY_BSS_PFC_ACK	= 0x55, +	BSSGP_PDUT_DELETE_BSS_PFC	= 0x56, +	BSSGP_PDUT_DELETE_BSS_PFC_ACK	= 0x57, +}; + +/* Section 10.2.1 and 10.2.2 */ +struct bssgp_ud_hdr { +	u_int8_t pdu_type; +	u_int32_t tlli; +	u_int8_t qos_profile[3]; +	u_int8_t data[0];	/* TLV's */ +} __attribute__((packed)); + +struct bssgp_normal_hdr { +	u_int8_t pdu_type; +	u_int8_t data[0];	/* TLV's */ +}; + +enum bssgp_iei_type { +	BSSGP_IE_ALIGNMENT		= 0x00, +	BSSGP_IE_BMAX_DEFAULT_MS	= 0x01, +	BSSGP_IE_BSS_AREA_ID		= 0x02, +	BSSGP_IE_BUCKET_LEAK_RATE	= 0x03, +	BSSGP_IE_BVCI			= 0x04, +	BSSGP_IE_BVC_BUCKET_SIZE	= 0x05, +	BSSGP_IE_BVC_MEASUREMENT	= 0x06, +	BSSGP_IE_CAUSE			= 0x07, +	BSSGP_IE_CELL_ID		= 0x08, +	BSSGP_IE_CHAN_NEEDED		= 0x09, +	BSSGP_IE_DRX_PARAMS		= 0x0a, +	BSSGP_IE_EMLPP_PRIO		= 0x0b, +	BSSGP_IE_FLUSH_ACTION		= 0x0c, +	BSSGP_IE_IMSI			= 0x0d, +	BSSGP_IE_LLC_PDU		= 0x0e, +	BSSGP_IE_LLC_FRAMES_DISCARDED	= 0x0f, +	BSSGP_IE_LOCATION_AREA		= 0x10, +	BSSGP_IE_MOBILE_ID		= 0x11, +	BSSGP_IE_MS_BUCKET_SIZE		= 0x12, +	BSSGP_IE_MS_RADIO_ACCESS_CAP	= 0x13, +	BSSGP_IE_OMC_ID			= 0x14, +	BSSGP_IE_PDU_IN_ERROR		= 0x15, +	BSSGP_IE_PDU_LIFETIME		= 0x16, +	BSSGP_IE_PRIORITY		= 0x17, +	BSSGP_IE_QOS_PROFILE		= 0x18, +	BSSGP_IE_RADIO_CAUSE		= 0x19, +	BSSGP_IE_RA_CAP_UPD_CAUSE	= 0x1a, +	BSSGP_IE_ROUTEING_AREA		= 0x1b, +	BSSGP_IE_R_DEFAULT_MS		= 0x1c, +	BSSGP_IE_SUSPEND_REF_NR		= 0x1d, +	BSSGP_IE_TAG			= 0x1e, +	BSSGP_IE_TLLI			= 0x1f, +	BSSGP_IE_TMSI			= 0x20, +	BSSGP_IE_TRACE_REFERENC		= 0x21, +	BSSGP_IE_TRACE_TYPE		= 0x22, +	BSSGP_IE_TRANSACTION_ID		= 0x23, +	BSSGP_IE_TRIGGER_ID		= 0x24, +	BSSGP_IE_NUM_OCT_AFF		= 0x25, +	BSSGP_IE_LSA_ID_LIST		= 0x26, +	BSSGP_IE_LSA_INFORMATION	= 0x27, +	BSSGP_IE_PACKET_FLOW_ID		= 0x28, +	BSSGP_IE_PACKET_FLOW_TIMER	= 0x29, +	BSSGP_IE_AGG_BSS_QOS_PROFILE	= 0x3a, +	BSSGP_IE_FEATURE_BITMAP		= 0x3b, +	BSSGP_IE_BUCKET_FULL_RATIO	= 0x3c, +	BSSGP_IE_SERVICE_UTRAN_CCO	= 0x3d, +}; + +/* Section 11.3.8 / Table 11.10: Cause coding */ +enum gprs_bssgp_cause { +	BSSGP_CAUSE_PROC_OVERLOAD	= 0x00, +	BSSGP_CAUSE_EQUIP_FAIL		= 0x01, +	BSSGP_CAUSE_TRASIT_NET_FAIL	= 0x02, +	BSSGP_CAUSE_CAPA_GREATER_0KPBS	= 0x03, +	BSSGP_CAUSE_UNKNOWN_MS		= 0x04, +	BSSGP_CAUSE_UNKNOWN_BVCI	= 0x05, +	BSSGP_CAUSE_CELL_TRAF_CONG	= 0x06, +	BSSGP_CAUSE_SGSN_CONG		= 0x07, +	BSSGP_CAUSE_OML_INTERV		= 0x08, +	BSSGP_CAUSE_BVCI_BLOCKED	= 0x09, +	BSSGP_CAUSE_PFC_CREATE_FAIL	= 0x0a, +	BSSGP_CAUSE_SEM_INCORR_PDU	= 0x20, +	BSSGP_CAUSE_INV_MAND_INF	= 0x21, +	BSSGP_CAUSE_MISSING_MAND_IE	= 0x22, +	BSSGP_CAUSE_MISSING_COND_IE	= 0x23, +	BSSGP_CAUSE_UNEXP_COND_IE	= 0x24, +	BSSGP_CAUSE_COND_IE_ERR		= 0x25, +	BSSGP_CAUSE_PDU_INCOMP_STATE	= 0x26, +	BSSGP_CAUSE_PROTO_ERR_UNSPEC	= 0x27, +	BSSGP_CAUSE_PDU_INCOMP_FEAT	= 0x28, +}; + +extern int gprs_bssgp_rcvmsg(struct msgb *msg, u_int16_t bvci); + +#endif /* _GPRS_BSSGP_H */ diff --git a/openbsc/include/openbsc/gprs_ns.h b/openbsc/include/openbsc/gprs_ns.h new file mode 100644 index 00000000..90f1adf3 --- /dev/null +++ b/openbsc/include/openbsc/gprs_ns.h @@ -0,0 +1,61 @@ +#ifndef _GPRS_NS_H +#define _GPRS_NS_H + +struct gprs_ns_hdr { +	u_int8_t pdu_type; +	u_int8_t data[0]; +} __attribute__((packed)); + +/* TS 08.16, Section 10.3.7, Table 14 */ +enum ns_pdu_type { +	NS_PDUT_UNITDATA	= 0x00, +	NS_PDUT_RESET		= 0x02, +	NS_PDUT_RESET_ACK	= 0x03, +	NS_PDUT_BLOCK		= 0x04, +	NS_PDUT_BLOCK_ACK	= 0x05, +	NS_PDUT_UNBLOCK		= 0x06, +	NS_PDUT_UNBLOCK_ACK	= 0x07, +	NS_PDUT_STATUS		= 0x08, +	NS_PDUT_ALIVE		= 0x0a, +	NS_PDUT_ALIVE_ACK	= 0x0b, +}; + +/* TS 08.16, Section 10.3, Table 12 */ +enum ns_ctrl_ie { +	NS_IE_CAUSE		= 0x00, +	NS_IE_VCI		= 0x01, +	NS_IE_PDU		= 0x02, +	NS_IE_BVCI		= 0x03, +	NS_IE_NSEI		= 0x04, +}; + +/* TS 08.16, Section 10.3.2, Table 13 */ +enum ns_cause { +	NS_CAUSE_TRANSIT_FAIL		= 0x00, +	NS_CAUSE_OM_INTERVENTION	= 0x01, +	NS_CAUSE_EQUIP_FAIL		= 0x02, +	NS_CAUSE_NSVC_BLOCKED		= 0x03, +	NS_CAUSE_NSVC_UNKNOWN		= 0x04, +	NS_CAUSE_BVCI_UNKNOWN		= 0x05, +	NS_CAUSE_SEM_INCORR_PDU		= 0x08, +	NS_CAUSE_PDU_INCOMP_PSTATE	= 0x0a, +	NS_CAUSE_PROTO_ERR_UNSPEC	= 0x0b, +	NS_CAUSE_INVAL_ESSENT_IE	= 0x0c, +	NS_CAUSE_MISSING_ESSENT_IE	= 0x0d, +}; + +/* a layer 1 entity transporting NS frames */ +struct gprs_ns_link { +	union { +		struct { +			int fd; +		} ip; +	}; +}; + + +int gprs_ns_rcvmsg(struct msgb *msg); + +int gprs_ns_sendmsg(struct gprs_ns_link *link, u_int16_t bvci, +		    struct msgb *msg); +#endif diff --git a/openbsc/src/gprs_bssgp.c b/openbsc/src/gprs_bssgp.c new file mode 100644 index 00000000..de57d25d --- /dev/null +++ b/openbsc/src/gprs_bssgp.c @@ -0,0 +1,397 @@ +/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <errno.h> +#include <sys/types.h> + +#include <netinet/in.h> + +#include <osmocore/msgb.h> +#include <osmocore/tlv.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_04_08_gprs.h> +#include <openbsc/gprs_bssgp.h> +#include <openbsc/gprs_llc.h> +#include <openbsc/gprs_ns.h> + +/* global pointer to the gsm network data structure */ +/* FIXME: this must go! */ +extern struct gsm_network *bsc_gsmnet; + +/* Chapter 11.3.9 / Table 11.10: Cause coding */ +static const char *bssgp_cause_strings[] = { +	[BSSGP_CAUSE_PROC_OVERLOAD]	= "Processor overload", +	[BSSGP_CAUSE_EQUIP_FAIL]	= "Equipment Failure", +	[BSSGP_CAUSE_TRASIT_NET_FAIL]	= "Transit netowkr service failure", +	[BSSGP_CAUSE_CAPA_GREATER_0KPBS]= "Transmission capacity modified", +	[BSSGP_CAUSE_UNKNOWN_MS]	= "Unknown MS", +	[BSSGP_CAUSE_UNKNOWN_BVCI]	= "Unknown BVCI", +	[BSSGP_CAUSE_CELL_TRAF_CONG]	= "Cell traffic congestion", +	[BSSGP_CAUSE_SGSN_CONG]		= "SGSN congestion", +	[BSSGP_CAUSE_OML_INTERV]	= "O&M intervention", +	[BSSGP_CAUSE_BVCI_BLOCKED]	= "BVCI blocked", +	[BSSGP_CAUSE_PFC_CREATE_FAIL]	= "PFC create failure", +	[BSSGP_CAUSE_SEM_INCORR_PDU]	= "Semantically incorrect PDU", +	[BSSGP_CAUSE_INV_MAND_INF]	= "Invalid mandatory information", +	[BSSGP_CAUSE_MISSING_MAND_IE]	= "Missing mandatory IE", +	[BSSGP_CAUSE_MISSING_COND_IE]	= "Missing conditional IE", +	[BSSGP_CAUSE_UNEXP_COND_IE]	= "Unexpected conditional IE", +	[BSSGP_CAUSE_COND_IE_ERR]	= "Conditional IE error", +	[BSSGP_CAUSE_PDU_INCOMP_STATE]	= "PDU incompatible with protocol state", +	[BSSGP_CAUSE_PROTO_ERR_UNSPEC]	= "Protocol error - unspecified", +	[BSSGP_CAUSE_PDU_INCOMP_FEAT]	= "PDU not compatible with feature set", +}; + +static const char *bssgp_cause_str(enum gprs_bssgp_cause cause) +{ +	if (cause >= ARRAY_SIZE(bssgp_cause_strings)) +		return "undefined"; + +	if (bssgp_cause_strings[cause]) +		return bssgp_cause_strings[cause]; + +	return "undefined"; +} + +static inline int bssgp_tlv_parse(struct tlv_parsed *tp, u_int8_t *buf, int len) +{ +	return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0); +} + +static inline struct msgb *bssgp_msgb_alloc(void) +{ +	return msgb_alloc_headroom(4096, 128, "BSSGP"); +} + +/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */ +static int bssgp_tx_simple_bvci(u_int8_t pdu_type, u_int16_t bvci, u_int16_t ns_bvci) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); +	u_int16_t _bvci; + +	bgph->pdu_type = pdu_type; +	_bvci = htons(bvci); +	msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (u_int8_t *) &_bvci); + +	return gprs_ns_sendmsg(NULL, ns_bvci, msg); +} + +/* Chapter 10.4.5: Flow Control BVC ACK */ +static int bssgp_tx_fc_bvc_ack(u_int8_t tag, u_int16_t ns_bvci) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + +	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK; +	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); + +	return gprs_ns_sendmsg(NULL, ns_bvci, msg); +} + +/* Chapter 10.4.14: Status */ +static int bssgp_tx_status(u_int8_t cause, u_int16_t *bvci, struct msgb *orig_msg) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + +	DEBUGPC(DGPRS, "BSSGP: TX STATUS, cause=%s\n", bssgp_cause_str(cause)); + +	bgph->pdu_type = BSSGP_PDUT_STATUS; +	msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause); +	if (bvci) { +		u_int16_t _bvci = htons(*bvci); +		msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (u_int8_t *) &_bvci); +	} +	if (orig_msg) +		msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, +			      msgb_l3len(orig_msg), orig_msg->l3h); + +	return gprs_ns_sendmsg(NULL, 0, msg); +} + +/* Uplink unit-data */ +static int bssgp_rx_ul_ud(struct msgb *msg, u_int16_t bvci) +{ +	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msg->l3h; +	struct gsm_bts *bts; +	int data_len = msgb_l3len(msg) - sizeof(*budh); +	struct tlv_parsed tp; +	int rc; + +	DEBUGP(DGPRS, "BSSGP UL-UD\n"); + +	msg->tlli = ntohl(budh->tlli); +	rc = bssgp_tlv_parse(&tp, budh->data, data_len); + +	/* Cell ID and LLC_PDU are the only mandatory IE */ +	if (!TLVP_PRESENT(&tp, BSSGP_IE_CELL_ID) || +	    !TLVP_PRESENT(&tp, BSSGP_IE_LLC_PDU)) +		return -EIO; + +	/* Determine the BTS based on the Cell ID */ +	bts = gsm48_bts_by_ra_id(bsc_gsmnet, +				 TLVP_VAL(&tp, BSSGP_IE_CELL_ID), +				 TLVP_LEN(&tp, BSSGP_IE_CELL_ID)); +	if (bts) +		msg->trx = bts->c0; + +	msg->llch = TLVP_VAL(&tp, BSSGP_IE_LLC_PDU); + +	return gprs_llc_rcvmsg(msg, &tp); +} + +static int bssgp_rx_suspend(struct msgb *msg, u_int16_t bvci) +{ +	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msg->l3h; +	int data_len = msgb_l3len(msg) - sizeof(*bgph); +	struct tlv_parsed tp; +	int rc; + +	DEBUGP(DGPRS, "BSSGP SUSPEND\n"); + +	rc = bssgp_tlv_parse(&tp, bgph->data, data_len); +	if (rc < 0) +		return rc; + +	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) || +	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) +		return -EIO; + +	/* SEND SUSPEND_ACK or SUSPEND_NACK */ +	/* FIXME */ +} + +static int bssgp_rx_resume(struct msgb *msg, u_int16_t bvci) +{ +	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msg->l3h; +	int data_len = msgb_l3len(msg) - sizeof(*bgph); +	struct tlv_parsed tp; +	int rc; + +	DEBUGP(DGPRS, "BSSGP RESUME\n"); + +	rc = bssgp_tlv_parse(&tp, bgph->data, data_len); +	if (rc < 0) +		return rc; + +	if (!TLVP_PRESENT(&tp, BSSGP_IE_TLLI) || +	    !TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA) || +	    !TLVP_PRESENT(&tp, BSSGP_IE_SUSPEND_REF_NR)) +		return -EIO; + +	/* SEND RESUME_ACK or RESUME_NACK */ +	/* FIXME */ +} + +static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp, +			   u_int16_t ns_bvci) +{ + +	DEBUGP(DGPRS, "BSSGP FC BVC\n"); + +	if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) || +	    !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) +		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); + +	/* Send FLOW_CONTROL_BVC_ACK */ +	return bssgp_tx_fc_bvc_ack(*TLVP_VAL(tp, BSSGP_IE_TAG), ns_bvci); +} +/* We expect msg->l3h to point to the BSSGP header */ +int gprs_bssgp_rcvmsg(struct msgb *msg, u_int16_t ns_bvci) +{ +	struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msg->l3h; +	struct tlv_parsed tp; +	u_int8_t pdu_type = bgph->pdu_type; +	int data_len = msgb_l3len(msg) - sizeof(*bgph); +	u_int16_t bvci; +	int rc = 0; + +	if (pdu_type != BSSGP_PDUT_UL_UNITDATA && +	    pdu_type != BSSGP_PDUT_DL_UNITDATA) +		rc = bssgp_tlv_parse(&tp, bgph->data, data_len); + +	switch (pdu_type) { +	case BSSGP_PDUT_UL_UNITDATA: +		/* some LLC data from the MS */ +		rc = bssgp_rx_ul_ud(msg, ns_bvci); +		break; +	case BSSGP_PDUT_RA_CAPABILITY: +		/* BSS requests RA capability or IMSI */ +		DEBUGP(DGPRS, "BSSGP RA CAPABILITY UPDATE\n"); +		/* FIXME: send RA_CAPA_UPDATE_ACK */ +		break; +	case BSSGP_PDUT_RADIO_STATUS: +		DEBUGP(DGPRS, "BSSGP RADIO STATUS\n"); +		/* BSS informs us of some exception */ +		break; +	case BSSGP_PDUT_SUSPEND: +		/* MS wants to suspend */ +		rc = bssgp_rx_suspend(msg, ns_bvci); +		break; +	case BSSGP_PDUT_RESUME: +		/* MS wants to resume */ +		rc = bssgp_rx_resume(msg, ns_bvci); +		break; +	case BSSGP_PDUT_FLUSH_LL: +		/* BSS informs MS has moved to one cell to other cell */ +		DEBUGP(DGPRS, "BSSGP FLUSH LL\n"); +		/* Send FLUSH_LL_ACK */ +		break; +	case BSSGP_PDUT_LLC_DISCARD: +		/* BSS informs that some LLC PDU's have been discarded */ +		DEBUGP(DGPRS, "BSSGP LLC DISCARDED\n"); +		break; +	case BSSGP_PDUT_FLOW_CONTROL_BVC: +		/* BSS informs us of available bandwidth in Gb interface */ +		rc = bssgp_rx_fc_bvc(msg, &tp, ns_bvci); +		break; +	case BSSGP_PDUT_FLOW_CONTROL_MS: +		/* BSS informs us of available bandwidth to one MS */ +		DEBUGP(DGPRS, "BSSGP FC MS\n"); +		/* Send FLOW_CONTROL_MS_ACK */ +		break; +	case BSSGP_PDUT_BVC_BLOCK: +		/* BSS tells us that BVC shall be blocked */ +		DEBUGP(DGPRS, "BSSGP BVC BLOCK "); +		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) || +		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) +			goto err_mand_ie; +		bvci = ntohs(*(u_int16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); +		DEBUGPC(DGPRS, "BVCI=%u, cause=%s\n", bvci, +			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE))); +		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK, +					  bvci, ns_bvci); +		break; +	case BSSGP_PDUT_BVC_UNBLOCK: +		/* BSS tells us that BVC shall be unblocked */ +		DEBUGP(DGPRS, "BSSGP BVC UNBLOCK "); +		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) +			goto err_mand_ie; +		bvci = ntohs(*(u_int16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); +		DEBUGPC(DGPRS, "BVCI=%u\n", bvci); +		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, +					  bvci, ns_bvci); +		break; +	case BSSGP_PDUT_BVC_RESET: +		/* BSS tells us that BVC init is required */ +		DEBUGP(DGPRS, "BSSGP BVC RESET "); +		if (!TLVP_PRESENT(&tp, BSSGP_IE_BVCI) || +		    !TLVP_PRESENT(&tp, BSSGP_IE_CAUSE)) +			goto err_mand_ie; +		bvci = ntohs(*(u_int16_t *)TLVP_VAL(&tp, BSSGP_IE_BVCI)); +		DEBUGPC(DGPRS, "BVCI=%u, cause=%s\n", bvci, +			bssgp_cause_str(*TLVP_VAL(&tp, BSSGP_IE_CAUSE))); +		rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, +					  bvci, ns_bvci); +		break; +	case BSSGP_PDUT_STATUS: +		/* Some exception has occurred */ +	case BSSGP_PDUT_DOWNLOAD_BSS_PFC: +	case BSSGP_PDUT_CREATE_BSS_PFC_ACK: +	case BSSGP_PDUT_CREATE_BSS_PFC_NACK: +	case BSSGP_PDUT_MODIFY_BSS_PFC: +	case BSSGP_PDUT_DELETE_BSS_PFC_ACK: +		DEBUGP(DGPRS, "BSSGP PDU type 0x%02x not [yet] implemented\n", +			pdu_type); +		break; +	/* those only exist in the SGSN -> BSS direction */ +	case BSSGP_PDUT_DL_UNITDATA: +	case BSSGP_PDUT_PAGING_PS: +	case BSSGP_PDUT_PAGING_CS: +	case BSSGP_PDUT_RA_CAPA_UPDATE_ACK: +	case BSSGP_PDUT_SUSPEND_ACK: +	case BSSGP_PDUT_SUSPEND_NACK: +	case BSSGP_PDUT_RESUME_ACK: +	case BSSGP_PDUT_RESUME_NACK: +	case BSSGP_PDUT_FLUSH_LL_ACK: +	case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: +	case BSSGP_PDUT_FLOW_CONTROL_MS_ACK: +	case BSSGP_PDUT_BVC_BLOCK_ACK: +	case BSSGP_PDUT_BVC_UNBLOCK_ACK: +	case BSSGP_PDUT_SGSN_INVOKE_TRACE: +		DEBUGP(DGPRS, "BSSGP PDU type 0x%02x only exists in DL\n", +			pdu_type); +		rc = -EINVAL; +		break; +	default: +		DEBUGP(DGPRS, "BSSGP PDU type 0x%02x unknown\n", pdu_type); +		break; +	} + +	return rc; +err_mand_ie: +	return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +} + +int gprs_bssgp_tx_dl_ud(struct msgb *msg) +{ +	struct gsm_bts *bts; +	struct bssgp_ud_hdr *budh; +	u_int8_t llc_pdu_tlv_hdr_len = 2; +	u_int8_t *llc_pdu_tlv, *qos_profile; +	u_int16_t pdu_lifetime = 1000; /* centi-seconds */ +	u_int8_t qos_profile_default[3] = { 0x00, 0x00, 0x21 }; +	u_int16_t msg_len = msg->len; + +	if (!msg->trx) { +		DEBUGP(DGPRS, "Cannot transmit DL-UD without TRX assigned\n"); +		return -EINVAL; +	} + +	bts = msg->trx->bts; + +	if (msg->len > TVLV_MAX_ONEBYTE) +		llc_pdu_tlv_hdr_len += 1; + +	/* prepend the tag and length of the LLC-PDU TLV */ +	llc_pdu_tlv = msgb_push(msg, llc_pdu_tlv_hdr_len); +	llc_pdu_tlv[0] = BSSGP_IE_LLC_PDU; +	if (llc_pdu_tlv_hdr_len > 2) { +		llc_pdu_tlv[1] = msg_len >> 8; +		llc_pdu_tlv[2] = msg_len & 0xff; +	} else { +		llc_pdu_tlv[1] = msg_len & 0x3f; +		llc_pdu_tlv[1] |= 0x80; +	} + +	/* FIXME: optional elements */ + +	/* prepend the pdu lifetime */ +	pdu_lifetime = htons(pdu_lifetime); +	msgb_tvlv_push(msg, BSSGP_IE_PDU_LIFETIME, 2, (u_int8_t *)&pdu_lifetime); + +	/* prepend the QoS profile, TLLI and pdu type */ +	budh = (struct bssgp_ud_hdr *) msgb_push(msg, sizeof(*budh)); +	memcpy(budh->qos_profile, qos_profile_default, sizeof(qos_profile_default)); +	budh->tlli = htonl(msg->tlli); +	budh->pdu_type = BSSGP_PDUT_DL_UNITDATA; + +	return gprs_ns_sendmsg(NULL, bts->gprs.cell.bvci, msg); +} diff --git a/openbsc/src/gprs_ns.c b/openbsc/src/gprs_ns.c new file mode 100644 index 00000000..a686a22a --- /dev/null +++ b/openbsc/src/gprs_ns.c @@ -0,0 +1,348 @@ +/* GPRS Networks Service (NS) messages on the Gb interface + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/* Some introduction into NS:  NS is used typically on top of frame relay, + * but in the ip.access world it is encapsulated in UDP packets.  It serves + * as an intermediate shim betwen BSSGP and the underlying medium.  It doesn't + * do much, apart from providing congestion notification and status indication. + * + * Terms: + * 	NS		Network Service + *	NSVC		NS Virtual Connection + *	NSEI		NS Entity Identifier + *	NSVL		NS Virtual Link + *	NSVLI		NS Virtual Link Identifier + *	BVC		BSSGP Virtual Connection + *	BVCI		BSSGP Virtual Connection Identifier + *	NSVCG		NS Virtual Connection Goup + *	Blocked		NS-VC cannot be used for user traffic + *	Alive		Ability of a NS-VC to provide communication + * + *  There can be multiple BSSGP virtual connections over one (group of) NSVC's.  BSSGP will + * therefore identify the BSSGP virtual connection by a BVCI passed down to NS. + * NS then has to firgure out which NSVC's are responsible for this BVCI. + * Those mappings are administratively configured. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> + +#include <arpa/inet.h> + +#include <openbsc/gsm_data.h> +#include <osmocore/msgb.h> +#include <osmocore/tlv.h> +#include <osmocore/talloc.h> +#include <openbsc/debug.h> +#include <openbsc/gprs_ns.h> +#include <openbsc/gprs_bssgp.h> + +#define NS_ALLOC_SIZE	1024 + +static const struct tlv_definition ns_att_tlvdef = { +	.def = { +		[NS_IE_CAUSE]	= { TLV_TYPE_TvLV, 0 }, +		[NS_IE_VCI]	= { TLV_TYPE_TvLV, 0 }, +		[NS_IE_PDU]	= { TLV_TYPE_TvLV, 0 }, +		[NS_IE_BVCI]	= { TLV_TYPE_TvLV, 0 }, +		[NS_IE_NSEI]	= { TLV_TYPE_TvLV, 0 }, +	}, +}; + +#define NSE_S_BLOCKED	0x0001 +#define NSE_S_ALIVE	0x0002 + +struct gprs_nsvc { +	struct llist_head list; + +	u_int16_t nsei;		/* end-to-end significance */ +	u_int16_t nsvci;	/* uniquely identifies NS-VC at SGSN */ + +	u_int32_t state; + +	struct timer_list alive_timer; +	int timer_is_tns_alive; +	int alive_retries; +}; + +/* FIXME: dynamically search for the matching NSVC */ +static struct gprs_nsvc dummy_nsvc = { .state = NSE_S_BLOCKED | NSE_S_ALIVE }; + +/* Section 10.3.2, Table 13 */ +static const char *ns_cause_str[] = { +	[NS_CAUSE_TRANSIT_FAIL]		= "Transit network failure", +	[NS_CAUSE_OM_INTERVENTION] 	= "O&M intervention", +	[NS_CAUSE_EQUIP_FAIL]		= "Equipment failure", +	[NS_CAUSE_NSVC_BLOCKED]		= "NS-VC blocked", +	[NS_CAUSE_NSVC_UNKNOWN]		= "NS-VC unknown", +	[NS_CAUSE_BVCI_UNKNOWN]		= "BVCI unknown", +	[NS_CAUSE_SEM_INCORR_PDU]	= "Semantically incorrect PDU", +	[NS_CAUSE_PDU_INCOMP_PSTATE]	= "PDU not compatible with protocol state", +	[NS_CAUSE_PROTO_ERR_UNSPEC]	= "Protocol error, unspecified", +	[NS_CAUSE_INVAL_ESSENT_IE]	= "Invalid essential IE", +	[NS_CAUSE_MISSING_ESSENT_IE]	= "Missing essential IE", +}; + +static const char *gprs_ns_cause_str(enum ns_cause cause) +{ +	if (cause >= ARRAY_SIZE(ns_cause_str)) +		return "undefined"; + +	if (ns_cause_str[cause]) +		return ns_cause_str[cause]; + +	return "undefined"; +} + +static int gprs_ns_tx(struct msgb *msg) +{ +	return ipac_gprs_send(msg); +} + +static int gprs_ns_tx_simple(struct gprs_ns_link *link, u_int8_t pdu_type) +{ +	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS"); +	struct gprs_ns_hdr *nsh; + +	if (!msg) +		return -ENOMEM; + +	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh)); + +	nsh->pdu_type = pdu_type; + +	return gprs_ns_tx(msg); +} + +#define NS_TIMER_ALIVE	3, 0 	/* after 3 seconds without response, we retry */ +#define NS_TIMER_TEST	30, 0	/* every 10 seconds we check if the BTS is still alive */ +#define NS_ALIVE_RETRIES  10	/* after 3 failed retransmit we declare BTS as dead */ + +static void gprs_ns_alive_cb(void *data) +{ +	struct gprs_nsvc *nsvc = data; + +	if (nsvc->timer_is_tns_alive) { +		/* Tns-alive case: we expired without response ! */ +		nsvc->alive_retries++; +		if (nsvc->alive_retries > NS_ALIVE_RETRIES) { +			/* mark as dead and blocked */ +			nsvc->state = NSE_S_BLOCKED; +			DEBUGP(DGPRS, "Tns-alive more then %u retries, " +				" blocking NS-VC\n", NS_ALIVE_RETRIES); +			/* FIXME: inform higher layers */ +			return; +		} +	} else { +		/* Tns-test case: send NS-ALIVE PDU */ +		gprs_ns_tx_simple(NULL, NS_PDUT_ALIVE); +		/* start Tns-alive timer */ +		nsvc->timer_is_tns_alive = 1; +	} +	bsc_schedule_timer(&nsvc->alive_timer, NS_TIMER_ALIVE); +} + +/* Section 9.2.6 */ +static int gprs_ns_tx_reset_ack(u_int16_t nsvci, u_int16_t nsei) +{ +	struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "GPRS/NS"); +	struct gprs_ns_hdr *nsh; + +	if (!msg) +		return -ENOMEM; + +	nsvci = htons(nsvci); +	nsei = htons(nsei); + +	nsh = (struct gprs_ns_hdr *) msgb_put(msg, sizeof(*nsh)); + +	nsh->pdu_type = NS_PDUT_RESET_ACK; + +	msgb_tvlv_put(msg, NS_IE_VCI, 2, (u_int8_t *)&nsvci); +	msgb_tvlv_put(msg, NS_IE_NSEI, 2, (u_int8_t *)&nsei); + +	return gprs_ns_tx(msg); +} + +/* Section 9.2.10: transmit side */ +int gprs_ns_sendmsg(struct gprs_ns_link *link, u_int16_t bvci, +		    struct msgb *msg) +{ +	struct gprs_ns_hdr *nsh; + +	nsh = (struct gprs_ns_hdr *) msgb_push(msg, sizeof(*nsh) + 3); +	if (!nsh) { +		DEBUGP(DGPRS, "Not enough headroom for NS header\n"); +		return -EIO; +	} + +	nsh->pdu_type = NS_PDUT_UNITDATA; +	/* spare octet in data[0] */ +	nsh->data[1] = bvci >> 8; +	nsh->data[2] = bvci & 0xff; + +	return gprs_ns_tx(msg); +} + +/* Section 9.2.10: receive side */ +static int gprs_ns_rx_unitdata(struct msgb *msg) +{ +	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h; +	u_int16_t bvci; + +	/* spare octet in data[0] */ +	bvci = nsh->data[1] << 8 | nsh->data[2]; +	msg->l3h = &nsh->data[3]; + +	/* call upper layer (BSSGP) */ +	return gprs_bssgp_rcvmsg(msg, bvci); +} + +/* Section 9.2.7 */ +static int gprs_ns_rx_status(struct msgb *msg) +{ +	struct gprs_ns_hdr *nsh = msg->l2h; +	struct tlv_parsed tp; +	u_int8_t cause; +	int rc; + +	DEBUGP(DGPRS, "NS STATUS "); + +	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0); + +	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE)) { +		DEBUGPC(DGPRS, "missing cause IE\n"); +		return -EINVAL; +	} + +	cause = *TLVP_VAL(&tp, NS_IE_CAUSE); +	DEBUGPC(DGPRS, "cause=%s\n", gprs_ns_cause_str(cause)); + +	return 0; +} + +/* Section 7.3 */ +static int gprs_ns_rx_reset(struct msgb *msg) +{ +	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; +	struct gprs_nsvc *nsvc = &dummy_nsvc; +	struct tlv_parsed tp; +	u_int8_t *cause; +	u_int16_t *nsvci, *nsei; +	int rc; + +	DEBUGP(DGPRS, "NS RESET "); + +	rc = tlv_parse(&tp, &ns_att_tlvdef, nsh->data, msgb_l2len(msg), 0, 0); + +	if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) || +	    !TLVP_PRESENT(&tp, NS_IE_VCI) || +	    !TLVP_PRESENT(&tp, NS_IE_NSEI)) { +		/* FIXME: respond with NS_CAUSE_MISSING_ESSENT_IE */ +		DEBUGPC(DGPRS, "Missing mandatory IE\n"); +		return -EINVAL; +	} + +	cause = (u_int8_t *) TLVP_VAL(&tp, NS_IE_CAUSE); +	nsvci = (u_int16_t *) TLVP_VAL(&tp, NS_IE_VCI); +	nsei = (u_int16_t *) TLVP_VAL(&tp, NS_IE_NSEI); + +	*nsvci = ntohs(*nsvci); +	*nsei = ntohs(*nsei); + +	DEBUGPC(DGPRS, "cause=%s, NSVCI=%u, NSEI=%u\n", +		gprs_ns_cause_str(*cause), *nsvci, *nsei); + +	/* mark the NS-VC as blocked and alive */ +	nsvc->state = NSE_S_BLOCKED | NSE_S_ALIVE; +	nsvc->nsei = *nsei; +	nsvc->nsvci = *nsvci; + +	/* start the test procedure */ +	nsvc->alive_timer.cb = gprs_ns_alive_cb; +	nsvc->alive_timer.data = nsvc; +	bsc_schedule_timer(&nsvc->alive_timer, NS_TIMER_ALIVE); + +	return gprs_ns_tx_reset_ack(*nsvci, *nsei); +} + +/* main entry point, here incoming NS frames enter */ +int gprs_ns_rcvmsg(struct msgb *msg) +{ +	struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; +	struct gprs_nsvc *nsvc = &dummy_nsvc; +	int rc = 0; + +	switch (nsh->pdu_type) { +	case NS_PDUT_ALIVE: +		/* remote end inquires whether we're still alive, +		 * we need to respond with ALIVE_ACK */ +		rc = gprs_ns_tx_simple(NULL, NS_PDUT_ALIVE_ACK); +		break; +	case NS_PDUT_ALIVE_ACK: +		/* stop Tns-alive */ +		bsc_del_timer(&nsvc->alive_timer); +		/* start Tns-test */ +		nsvc->timer_is_tns_alive = 0; +		bsc_schedule_timer(&nsvc->alive_timer, NS_TIMER_TEST); +		break; +	case NS_PDUT_UNITDATA: +		/* actual user data */ +		rc = gprs_ns_rx_unitdata(msg); +		break; +	case NS_PDUT_STATUS: +		rc = gprs_ns_rx_status(msg); +		break; +	case NS_PDUT_RESET: +		rc = gprs_ns_rx_reset(msg); +		break; +	case NS_PDUT_RESET_ACK: +		/* FIXME: mark remote NS-VC as blocked + active */ +		break; +	case NS_PDUT_UNBLOCK: +		/* Section 7.2: unblocking procedure */ +		DEBUGP(DGPRS, "NS UNBLOCK\n"); +		nsvc->state &= ~NSE_S_BLOCKED; +		rc = gprs_ns_tx_simple(NULL, NS_PDUT_UNBLOCK_ACK); +		break; +	case NS_PDUT_UNBLOCK_ACK: +		/* FIXME: mark remote NS-VC as unblocked + active */ +		break; +	case NS_PDUT_BLOCK: +		DEBUGP(DGPRS, "NS BLOCK\n"); +		nsvc->state |= NSE_S_BLOCKED; +		rc = gprs_ns_tx_simple(NULL, NS_PDUT_UNBLOCK_ACK); +		break; +	case NS_PDUT_BLOCK_ACK: +		/* FIXME: mark remote NS-VC as blocked + active */ +		break; +	default: +		DEBUGP(DGPRS, "Unknown NS PDU type 0x%02x\n", nsh->pdu_type); +		rc = -EINVAL; +		break; +	} +	return rc; +} + | 
