/* Handling of actual UMA/GAN protocol as per TS 44.318 */ /* (C) 2012 by Harald Welte * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }