blob: 0e00d58ab06dd929ec0c0904d5ed1284b6bb327f [file] [log] [blame]
/* Handling of actual UMA/GAN protocol as per TS 44.318 */
/* (C) 2012 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <arpa/inet.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gsm/protocol/gsm_44_318.h>
#include <osmocom/gsm/gan.h>
#include <osmocom/gsm/gsm48.h>
#include "conn.h"
#include "ganc_data.h"
static void push_rc_csr_hdr(struct msgb *msg, uint8_t pdisc, uint8_t msgt)
{
struct gan_rc_csr_hdr *gh =
(struct gan_rc_csr_hdr *) msgb_push(msg, sizeof(*gh));
gh->pdisc = pdisc;
gh->msg_type = msgt;
}
/* Find the matching gan_peer from the specified IMSI, if any */
static struct gan_peer *gan_peer_by_imsi_f(const char *imsi, uint32_t flag)
{
struct gan_peer *peer;
llist_for_each_entry(peer, &g_ganc_bts->net->peers, entry) {
if (strlen(peer->imsi) && !strcmp(peer->imsi, imsi)) {
if (!flag || (peer->flags & flag))
return peer;
}
}
return NULL;
}
/* destroy a peer, including anything that may hang off it */
static void gan_peer_destroy(struct gan_peer *peer)
{
struct osmo_conn *conn = peer->conn;
if (!peer)
return;
osmo_timer_del(&peer->keepalive_timer);
llist_del(&peer->entry);
talloc_free(peer);
/* we can only free conn after peer, as peer is a sub-object of
* ocnn in the talloc hierarchical allocator */
if (conn)
osmo_conn_close(conn);
}
static struct msgb *unc_msgb_alloc(void)
{
return msgb_alloc_headroom(1024+128, 128, "GANC Tx");
}
static int unc_peer_tx(struct gan_peer *peer, struct msgb *msg)
{
struct gan_rc_csr_hdr *gh = (struct gan_rc_csr_hdr*) msg->data;
/* compute and fill-in length */
msg->l2h = msg->data;
gh->len = htons(msgb_l2len(msg)-2);
if (peer->gan_release == 0) {
/* shomehow old pre-GAN UMA doesn't have RC */
if (gh->pdisc == GA_PDISC_RC)
gh->pdisc = GA_PDISC_CSR;
}
return osmo_conn_enqueue(peer->conn, msg);
}
static uint8_t *msgb_tlv_be16_put(struct msgb *msg, uint8_t tag, uint16_t val)
{
uint16_t _val = htons(val);
return msgb_tlv_put(msg, tag, 2, (uint8_t *) &_val);
}
static void build_gan_cch_desc(struct gan_cch_desc_ie *ie, struct ganc_bts *bts)
{
struct ganc_net *net = bts->net;
/* properly initialize to zero */
memset(ie, 0, sizeof(*ie));
ie->ecmc = 0; /* Early Classmark allowed */
ie->nmo = 0; /* NMO 1 */
if (net->gprs.mode != 0)
ie->gprs = 0;
else
ie->gprs = 1; /* No GPRS */
ie->dtm = 0; /* No Dual Transfer Mode */
ie->att = 1; /* IMSI attach/detach shall be used */
#if 0
ie->mscr = 1; /* Release 99 onwards */
#else
ie->mscr = 0; /* To avoid problems with 2-bit sequence number */
#endif
ie->t3212 = net->timer[T3212];
ie->rac = bts->routing_area_code;
ie->sgsnr = 1; /* Release 99 onwards */
ie->ecmp = net->emergency_gan_preferred;
ie->re = 1; /* No call re-esetablishment */
ie->pfcfm = 0; /* No PFC */
ie->tgecs = 1; /* Permit UTRAN classmark change */
if (!net->emergency_gan_preferred)
ie->access_class[0] |= 0x4; /* No emergency calls */
}
/* FIXME: move to libosmocore */
static void gsm48_cell_desc(struct gsm48_cell_desc *cd, uint8_t bsic, uint16_t arfcn)
{
cd->ncc = (bsic >> 3) & 0x07;
cd->bcc = bsic & 0x07;
cd->arfcn_hi = arfcn >> 8;
cd->arfcn_lo = arfcn & 0xff;
}
/* 10.1.6: GA-RC REGISTER ACCEPT */
static int tx_unc_reg_acc(struct gan_peer *peer)
{
struct msgb *msg = unc_msgb_alloc();
struct gsm48_loc_area_id lai;
struct gsm48_cell_desc cd;
struct gan_cch_desc_ie ie;
struct ganc_bts *bts = peer->bts;
struct ganc_net *net = bts->net;
uint8_t gan_band = 0x02; /* GSM 1800 */
printf("<- GA-RC REGISTER ACCEPT\n");
if (!msg)
return -ENOMEM;
gsm48_cell_desc(&cd, bts->bsic, bts->arfcn);
gsm48_generate_lai(&lai, net->country_code, net->network_code,
bts->location_area_code);
build_gan_cch_desc(&ie, bts);
push_rc_csr_hdr(msg, GA_PDISC_RC, GA_MT_RC_REGISTER_ACCEPT);
msgb_tlv_be16_put(msg, GA_IE_GERAN_CELL_ID, bts->cell_identity);
msgb_tlv_put(msg, GA_IE_LAC, sizeof(lai), (uint8_t *) &lai);
msgb_tlv_put(msg, GA_IE_GANC_CTRL_CH_DESC, sizeof(ie), (uint8_t *) &ie);
msgb_tlv_be16_put(msg, GA_IE_TU3910_TIMER, net->timer[TU3910]);
msgb_tlv_be16_put(msg, GA_IE_TU3906_TIMER, net->timer[TU3906]);
msgb_tlv_put(msg, GA_IE_GAN_BAND, 1, &gan_band);
msgb_tlv_be16_put(msg, GA_IE_TU3920_TIMER, net->timer[TU3920]);
msgb_tlv_put(msg, GA_IE_GANC_CELL_DESC, sizeof(cd), (uint8_t *) &cd);
if (net->gprs.mode != 0) {
msgb_tlv_be16_put(msg, GA_IE_TU4001_TIMER, net->timer[TU4001]);
msgb_tlv_be16_put(msg, GA_IE_TU4003_TIMER, net->timer[TU4003]);
}
return unc_peer_tx(peer, msg);
}
static int push_fqdn_or_ip(struct msgb *msg, const char *host,
uint8_t fqdn_att, uint8_t ip_att)
{
struct in_addr ia;
int rc;
rc = inet_aton(host, &ia);
if (rc == 0) {
/* it is not an IP address */
msgb_tlv_put(msg, fqdn_att, strlen(host)+1,
(uint8_t *) host);
} else {
uint8_t buf[5];
buf[0] = 0x21; /* Type: IPv4 */
memcpy(buf+1, &ia, 4);
msgb_tlv_put(msg, ip_att, sizeof(buf), buf);
}
return rc;
}
static struct ganc_bts *select_bts(struct gan_peer *peer)
{
/* FIXME: we need to select the virtual BTS based on MAC address
* and/or ESSID of the AP */
return g_ganc_bts;
}
/* 10.1.3: GA-RC DISCOVERY ACCEPT */
static int tx_unc_disco_acc(struct gan_peer *peer, const char *segw_host,
const char *ganc_host)
{
struct msgb *msg = unc_msgb_alloc();
printf("<- GA-RC DISCOVERY ACCEPT\n");
if (!msg)
return -ENOMEM;
push_rc_csr_hdr(msg, GA_PDISC_RC, GA_MT_RC_DISCOVERY_ACCEPT);
push_fqdn_or_ip(msg, segw_host,
GA_IE_DEF_SEGW_FQDN, GA_IE_DEF_SEGW_IP);
push_fqdn_or_ip(msg, ganc_host,
GA_IE_DEF_GANC_FQDN, GA_IE_DEF_GANC_IP);
return unc_peer_tx(peer, msg);
}
/* 10.1.25 GA-CSR DOWNLINK DIRECT TRANSFER */
static int tx_csr_dl_direct_xfer(struct gan_peer *peer, struct msgb *msg)
{
printf("<- GA-CSR DL DIRECT TRANSFER\n");
/* tag and length of L3 info */
msgb_vtvl_gan_push(msg, GA_IE_L3_MSG, msgb_l3len(msg));
push_rc_csr_hdr(msg, GA_PDISC_CSR, GA_MT_CSR_DL_DIRECT_XFER);
return unc_peer_tx(peer, msg);
}
/* 10.1.19 GA-CSR RELEASE */
static int tx_csr_release(struct gan_peer *peer, uint8_t cause)
{
struct msgb *msg = unc_msgb_alloc();
printf("<- GA-CSR RELEASE\n");
if (!msg)
return -ENOMEM;
push_rc_csr_hdr(msg, GA_PDISC_CSR, GA_MT_CSR_RELEASE);
msgb_tlv_put(msg, GA_IE_RR_CAUSE, 1, &cause);
return unc_peer_tx(peer, msg);
}
/* 10.1.21 GA-CSR PAGING REQUEST */
static int tx_csr_paging_req(struct gan_peer *peer, uint8_t mi_len,
uint8_t *mi, uint8_t chan_needed)
{
struct msgb *msg = unc_msgb_alloc();
printf("<- GA-CSR PAGING REQ\n");
if (!msg)
return -ENOMEM;
push_rc_csr_hdr(msg, GA_PDISC_CSR, GA_MT_CSR_PAGING_REQ);
msgb_tlv_put(msg, GA_IE_CHAN_NEEDED, 1, &chan_needed);
msgb_tlv_put(msg, GA_IE_MI, mi_len, mi);
return unc_peer_tx(peer, msg);
}
/* 10.1.13 GA-CSR REQUEST ACCEPT */
static int tx_csr_request_acc(struct gan_peer *peer)
{
struct msgb *msg = unc_msgb_alloc();
printf("<- GA-CSR REQUEST ACCEPT\n");
if (!msg)
return -ENOMEM;
push_rc_csr_hdr(msg, GA_PDISC_CSR, GA_MT_CSR_REQUEST_ACCEPT);
peer->csr_state = GA_S_CSR_DEDICATED;
return unc_peer_tx(peer, msg);
}
/* 10.1.2 GA-RC DISCOVERY REQUEST */
static int rx_rc_discovery_req(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
struct ganc_bts *bts;
if (TLVP_PRESENT(tp, GA_IE_MI)) {
gsm48_mi_to_string(peer->imsi, sizeof(peer->imsi),
TLVP_VAL(tp, GA_IE_MI), TLVP_LEN(tp, GA_IE_MI));
printf("\tfrom %s\n", peer->imsi);
}
if (TLVP_PRESENT(tp, GA_IE_GAN_RELEASE_IND))
peer->gan_release = *TLVP_VAL(tp, GA_IE_GAN_RELEASE_IND);
if (TLVP_PRESENT(tp, GA_IE_GAN_CM) && TLVP_LEN(tp, GA_IE_GAN_CM) >=2)
memcpy(peer->gan_classmark, TLVP_VAL(tp, GA_IE_GAN_CM), 2);
bts = select_bts(peer);
osmo_timer_schedule(&peer->keepalive_timer,
bts->net->timer[TU3906]*2, 0);
return tx_unc_disco_acc(peer, bts->segw_host, bts->ganc_host);
}
/* 10.1.5 GA-RC REGISTER REQUEST */
static int rx_rc_register_req(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
uint8_t *cur;
if (TLVP_PRESENT(tp, GA_IE_MI)) {
struct gan_peer *stale_peer;
char imsi[sizeof(peer->imsi)];
memset(imsi, 0, sizeof(imsi));
gsm48_mi_to_string(imsi, sizeof(imsi),
TLVP_VAL(tp, GA_IE_MI), TLVP_LEN(tp, GA_IE_MI));
printf("\tfrom %s\n", imsi);
/* find any old/stale peer for the same imsi */
stale_peer = gan_peer_by_imsi_f(imsi, GAN_PF_REGISTERED);
if (stale_peer) {
printf("\t destroying stale old gan_peer\n");
gan_peer_destroy(stale_peer);
}
memcpy(peer->imsi, imsi, sizeof(peer->imsi));
}
if (TLVP_PRESENT(tp, GA_IE_GAN_RELEASE_IND))
peer->gan_release = *TLVP_VAL(tp, GA_IE_GAN_RELEASE_IND);
if (TLVP_PRESENT(tp, GA_IE_GAN_CM) && TLVP_LEN(tp, GA_IE_GAN_CM) >=2)
memcpy(peer->gan_classmark, TLVP_VAL(tp, GA_IE_GAN_CM), 2);
if (TLVP_PRESENT(tp, GA_IE_RADIO_IE) &&
TLVP_LEN(tp, GA_IE_RADIO_IE) >= 7 &&
(*TLVP_VAL(tp, GA_IE_RADIO_IE) & 0x0F) == 0x00) {
if (peer->ms_radio_id)
talloc_free(peer->ms_radio_id);
peer->ap_radio_id = talloc_memdup(peer,
TLVP_VAL(tp, GA_IE_RADIO_IE)+1,
TLVP_LEN(tp, GA_IE_RADIO_IE)-1);
}
if (TLVP_PRESENT(tp, GA_IE_MS_RADIO_ID) &&
TLVP_LEN(tp, GA_IE_MS_RADIO_ID) >= 7 &&
(*TLVP_VAL(tp, GA_IE_MS_RADIO_ID) & 0x0F) == 0x00) {
if (peer->ms_radio_id)
talloc_free(peer->ms_radio_id);
peer->ms_radio_id = talloc_memdup(peer,
TLVP_VAL(tp, GA_IE_MS_RADIO_ID)+1,
TLVP_LEN(tp, GA_IE_MS_RADIO_ID)-1);
}
if (TLVP_PRESENT(tp, GA_IE_AP_SERV_NAME) &&
TLVP_LEN(tp, GA_IE_AP_SERV_NAME) >= 1) {
if (peer->ap_serv_name)
talloc_free(peer->ap_serv_name);
/* strndup copies len bytes + adds zero */
peer->ap_serv_name = talloc_strndup(peer,
TLVP_VAL(tp, GA_IE_AP_SERV_NAME)+1,
TLVP_LEN(tp, GA_IE_AP_SERV_NAME)-1);
}
if (TLVP_PRESENT(tp, GA_IE_LAC) && TLVP_LEN(tp, GA_IE_LAC) >= 5) {
struct gsm48_loc_area_id *lai;
lai = TLVP_VAL(tp, GA_IE_LAC);
gsm48_decode_lai(lai, &peer->ra_id.mcc, &peer->ra_id.mnc,
&peer->ra_id.lac);
}
if (TLVP_PRESENT(tp, GA_IE_RAC) && TLVP_LEN(tp, GA_IE_RAC) >= 2)
peer->ra_id.rac = ntohs(TLVP_VAL(tp, GA_IE_RAC));
if (TLVP_PRESENT(tp, GA_IE_GERAN_CELL_ID) &&
TLVP_LEN(tp, GA_IE_GERAN_CELL_ID) >= 2)
peer->cell_id = ntohs(*(uint16_t *)TLVP_VAL(tp, GA_IE_GERAN_CELL_ID));
peer->flags |= GAN_PF_REGISTERED;
peer->bts = select_bts(peer);
osmo_timer_schedule(&peer->keepalive_timer,
peer->bts->net->timer[TU3906]*2, 0);
return tx_unc_reg_acc(peer);
}
/* 10.1.12 GA CSR REQUEST */
static int rx_csr_request(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
return tx_csr_request_acc(peer);
}
/* 10.1.14 GA RC KEEP ALIVE */
static int rx_rc_keepalive(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
struct ganc_net *net = peer->bts->net;
/* re-schedule the timer at twice the TU3906 value */
osmo_timer_schedule(&peer->keepalive_timer, net->timer[TU3906]*2, 0);
return 0;
}
/* 10.1.37 GA-CSR CLEAR REQUEST */
static int rx_csr_clear_req(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
/* FIXME: request core network to release all dedicated resources */
return 0;
}
/* 10.1.20 GA-CSR RELEASE COMPLETE */
static int rx_csr_rel_compl(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
peer->csr_state = GA_S_CSR_IDLE;
return 0;
}
/* 10.1.9 GA-RC DEREGISTER */
static int rx_rc_deregister(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
/* Release all resources, MS will TCP disconnect */
peer->flags &= ~GAN_PF_REGISTERED;
/* not all MS really close the TCP connection, we have to
* release the TCP connection locally by release_timer! */
return 0;
}
/* 10.1.23 GA-CSR UL DIRECT XFER */
static int rx_csr_ul_direct_xfer(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
uint8_t sapi = 0;
if (TLVP_PRESENT(tp, GA_IE_SAPI_ID))
sapi = *TLVP_VAL(tp, GA_IE_SAPI_ID) & 0x7;
if (TLVP_PRESENT(tp, GA_IE_L3_MSG))
printf("\tL3(%u): %s\n", sapi,
osmo_hexdump(TLVP_VAL(tp, GA_IE_L3_MSG),
TLVP_LEN(tp, GA_IE_L3_MSG)));
return 0;
}
/* 10.1.27 GA-CSR CLASSMARK CHANGE */
static int rx_csr_cm_chg(struct gan_peer *peer, struct msgb *msg,
struct tlv_parsed *tp)
{
if (TLVP_PRESENT(tp, GA_IE_MS_CLASSMARK2) &&
TLVP_LEN(tp, GA_IE_MS_CLASSMARK2) == 3)
memcpy(peer->cm2, TLVP_VAL(tp, GA_IE_MS_CLASSMARK2), 3);
if (TLVP_PRESENT(tp, GA_IE_MS_CLASSMARK3)) {
peer->cm3.len = TLVP_LEN(tp, GA_IE_MS_CLASSMARK3);
peer->cm3.val = talloc_memdup(peer,
TLVP_VAL(tp, GA_IE_MS_CLASSMARK3),
peer->cm3.len);
}
return 0;
}
static int rx_unc_rc_csr(struct gan_peer *peer, struct msgb *msg,
struct gan_rc_csr_hdr *gh, struct tlv_parsed *tp)
{
switch (gh->msg_type) {
case GA_MT_RC_DISCOVERY_REQUEST:
return rx_rc_discovery_req(peer, msg, tp);
case GA_MT_RC_REGISTER_REQUEST:
return rx_rc_register_req(peer, msg, tp);
case GA_MT_CSR_REQUEST:
return rx_csr_request(peer, msg, tp);
case GA_MT_CSR_CLEAR_REQ:
return rx_csr_clear_req(peer, msg, tp);
case GA_MT_CSR_RELEASE_COMPL:
return rx_csr_rel_compl(peer, msg, tp);
case GA_MT_RC_DEREGISTER:
return rx_rc_deregister(peer, msg, tp);
case GA_MT_CSR_UL_DIRECT_XFER:
return rx_csr_ul_direct_xfer(peer, msg, tp);
case GA_MT_CSR_CM_CHANGE:
return rx_csr_cm_chg(peer, msg, tp);
case GA_MT_RC_KEEPALIVE:
return rx_rc_keepalive(peer, msg, tp);
break;
default:
printf("\tunhandled!\n");
break;
}
return 0;
}
static int rx_unc_msg(struct gan_peer *peer, struct msgb *msg)
{
struct gan_rc_csr_hdr *gh = (struct gan_rc_csr_hdr *) msg->l2h;
struct tlv_parsed tp;
int len = ntohs(gh->len);
int rc;
printf("-> (%u) %s\n", gh->pdisc, get_value_string(gan_msgt_vals, gh->msg_type));
if (len > 2) {
rc = tlv_parse(&tp, &vtvlv_gan_att_def, gh->data, len - 2, 0, 0);
if (rc < 0)
fprintf(stderr, "error %d during tlv_parse\n", rc);
} else
memset(&tp, 0, sizeof(tp));
switch (gh->pdisc) {
case GA_PDISC_RC:
case GA_PDISC_CSR:
return rx_unc_rc_csr(peer, msg, gh, &tp);
case GA_PDISC_PSR:
default:
break;
}
return 0;
}
static int unc_read_cb(struct osmo_conn *conn)
{
struct msgb *msg;
int rc, len;
msg = msgb_alloc_headroom(1024 + 128, 128, "UNC Read");
if (!msg)
return -ENOMEM;
rc = read(conn->queue.bfd.fd, msg->data, 2);
if (rc <= 0) {
msgb_free(msg);
if (conn->priv)
gan_peer_destroy(conn->priv);
else
osmo_conn_close(conn);
return rc;
} else if (rc != 2) {
msgb_free(msg);
fprintf(stderr, "unable to read 2 length bytes\n");
return -EIO;
}
msg->l2h = msg->data;
msgb_put(msg, rc);
len = ntohs(*(uint16_t *)msg->data);
rc = read(conn->queue.bfd.fd, msg->data+2, len);
if (rc < 0) {
msgb_free(msg);
if (conn->priv)
gan_peer_destroy(conn->priv);
else
osmo_conn_close(conn);
return rc;
} else if (rc != len) {
msgb_free(msg);
fprintf(stderr, "unable to read %u bytes following len\n", len);
return -EIO;
}
msgb_put(msg, rc);
return rx_unc_msg(conn->priv, msg);
/* FIXME: we have to free the msgb!! */
}
static void peer_keepalive_cb(void *_peer)
{
struct gan_peer *peer = _peer;
printf("=== Timeout for Peer %s, destroying\n", peer->imsi);
gan_peer_destroy(peer);
}
static void unc_accept_cb(struct osmo_conn *conn)
{
struct gan_peer *peer = talloc_zero(conn, struct gan_peer);
printf("accepted connection\n");
peer->bts = NULL;
peer->conn = conn;
conn->priv = peer;
peer->sccp_con = NULL;
/* start keepalive timer at hard-coded 5*30 seconds until we later
* change it to a TU3906 derived value */
peer->keepalive_timer.cb = peer_keepalive_cb;
peer->keepalive_timer.data = peer;
osmo_timer_schedule(&peer->keepalive_timer, 5*30, 0);
/* TODO: remove from list when closed */
llist_add_tail(&peer->entry, &g_ganc_bts->net->peers);
}
int ganc_server_start(const char *host, uint16_t port)
{
struct osmo_link *link;
link = osmo_link_create(NULL, host, port, unc_read_cb, 10);
if (!link)
return -ENOMEM;
osmo_link_listen(link, unc_accept_cb);
return 0;
}