osmo-bts/src/common/abis.c

530 lines
12 KiB
C
Raw Normal View History

/* Minimalistic Abis/IP interface routines, soon to be replaced by
* libosmo-abis (Pablo) */
/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2011 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 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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
2011-07-01 16:22:01 +00:00
#include <stdlib.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/oml.h>
extern char *software_version;
extern uint8_t abis_mac[6];
/*
* support
*/
#define ABIS_ALLOC_SIZE 900
/* send message to BSC */
int abis_tx(struct ipabis_link *link, struct msgb *msg)
{
if (link->state != LINK_STATE_CONNECT) {
LOGP(DABIS, LOGL_NOTICE, "Link down, dropping message.\n");
msgb_free(msg);
return -EIO;
}
msgb_enqueue(&link->tx_queue, msg);
link->bfd.when |= BSC_FD_WRITE;
return 0;
}
int abis_oml_sendmsg(struct msgb *msg)
{
struct gsm_bts *bts = msg->trx->bts;
abis_push_ipa(msg, 0xff);
if (!bts->oml_link) {
msgb_free(msg);
return 0;
}
return abis_tx((struct ipabis_link *) bts->oml_link, msg);
}
int abis_rsl_sendmsg(struct msgb *msg)
{
struct gsm_bts_trx *trx = msg->trx;
abis_push_ipa(msg, 0);
return abis_tx((struct ipabis_link *) trx->rsl_link, msg);
}
struct msgb *abis_msgb_alloc(int headroom)
{
struct msgb *nmsg;
headroom += sizeof(struct ipaccess_head);
nmsg = msgb_alloc_headroom(ABIS_ALLOC_SIZE + headroom,
headroom, "Abis/IP");
if (!nmsg)
return NULL;
return nmsg;
}
void abis_push_ipa(struct msgb *msg, uint8_t proto)
{
struct ipaccess_head *nhh;
msg->l2h = msg->data;
nhh = (struct ipaccess_head *) msgb_push(msg, sizeof(*nhh));
nhh->proto = proto;
nhh->len = htons(msgb_l2len(msg));
}
/*
* IPA related messages
*/
/* send ping/pong */
static int abis_tx_ipa_pingpong(struct ipabis_link *link, uint8_t pingpong)
{
struct msgb *nmsg;
nmsg = abis_msgb_alloc(0);
if (!nmsg)
return -ENOMEM;
*msgb_put(nmsg, 1) = pingpong;
abis_push_ipa(nmsg, IPAC_PROTO_IPACCESS);
return abis_tx(link, nmsg);
}
/* send ACK and ID RESP */
static int abis_rx_ipa_id_get(struct ipabis_link *link, uint8_t *data, int len)
{
struct gsm_bts *bts = link->bts;
struct msgb *nmsg, *nmsg2;
char str[64];
uint8_t *tag;
if (!link->bts)
bts = link->trx->bts;
LOGP(DABIS, LOGL_INFO, "Reply to ID_GET\n");
nmsg = abis_msgb_alloc(0);
if (!nmsg)
return -ENOMEM;
*msgb_put(nmsg, 1) = IPAC_MSGT_ID_RESP;
while (len) {
if (len < 2) {
LOGP(DABIS, LOGL_NOTICE,
"Short read of ipaccess tag\n");
msgb_free(nmsg);
return -EIO;
}
switch (data[1]) {
case IPAC_IDTAG_UNIT:
sprintf(str, "%u/%u/%u",
bts->ip_access.site_id,
bts->ip_access.bts_id,
(link->trx) ? link->trx->nr : 0);
break;
case IPAC_IDTAG_MACADDR:
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
abis_mac[0], abis_mac[1], abis_mac[2],
abis_mac[3], abis_mac[4], abis_mac[5]);
break;
case IPAC_IDTAG_LOCATION1:
strcpy(str, "osmoBTS");
break;
case IPAC_IDTAG_LOCATION2:
strcpy(str, "osmoBTS");
break;
case IPAC_IDTAG_EQUIPVERS:
case IPAC_IDTAG_SWVERSION:
strcpy(str, software_version);
break;
case IPAC_IDTAG_UNITNAME:
sprintf(str, "osmoBTS-%02x-%02x-%02x-%02x-%02x-%02x",
abis_mac[0], abis_mac[1], abis_mac[2],
abis_mac[3], abis_mac[4], abis_mac[5]);
break;
case IPAC_IDTAG_SERNR:
strcpy(str, "");
break;
default:
LOGP(DABIS, LOGL_NOTICE,
"Unknown ipaccess tag 0x%02x\n", *data);
msgb_free(nmsg);
return -EINVAL;
}
LOGP(DABIS, LOGL_INFO, " tag %d: %s\n", data[1], str);
tag = msgb_put(nmsg, 3 + strlen(str) + 1);
tag[0] = 0x00;
tag[1] = 1 + strlen(str) + 1;
tag[2] = data[1];
memcpy(tag + 3, str, strlen(str) + 1);
data += 2;
len -= 2;
}
abis_push_ipa(nmsg, IPAC_PROTO_IPACCESS);
nmsg2 = abis_msgb_alloc(0);
if (!nmsg2) {
msgb_free(nmsg);
return -ENOMEM;
}
*msgb_put(nmsg2, 1) = IPAC_MSGT_ID_ACK;
abis_push_ipa(nmsg2, IPAC_PROTO_IPACCESS);
link->id_resp = 1;
abis_tx(link, nmsg2);
return abis_tx(link, nmsg);
}
static int abis_rx_ipaccess(struct ipabis_link *link, struct msgb *msg)
{
uint8_t *data = msgb_l2(msg);
int len = msgb_l2len(msg);
int ret = 0;
if (len < 1) {
LOGP(DABIS, LOGL_NOTICE, "Short read of ipaccess message\n");
msgb_free(msg);
return EIO;
}
switch (*data) {
case IPAC_MSGT_PONG:
#if 0
#warning HACK
rsl_tx_chan_rqd(link->bts->trx[0]);
#endif
LOGP(DABIS, LOGL_DEBUG, "PONG\n");
link->pong = 1;
break;
case IPAC_MSGT_PING:
LOGP(DABIS, LOGL_DEBUG, "reply to ping request\n");
ret = abis_tx_ipa_pingpong(link, IPAC_MSGT_PONG);
break;
case IPAC_MSGT_ID_GET:
ret = abis_rx_ipa_id_get(link, data + 1, len - 1);
break;
case IPAC_MSGT_ID_ACK:
LOGP(DABIS, LOGL_DEBUG, "ID ACK\n");
if (link->id_resp && link->bts)
ret = bts_link_estab(link->bts);
if (link->id_resp && link->trx)
ret = trx_link_estab(link->trx);
link->id_resp = 0;
break;
default:
LOGP(DABIS, LOGL_NOTICE,
"Unknown ipaccess message type 0x%02x\n", *data);
ret = -EINVAL;
}
msgb_free(msg);
return ret;
}
/*
* A-Bis over IP implementation
*/
/* receive message from BSC */
static int abis_rx(struct ipabis_link *link, struct msgb *msg)
{
struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
int ret = 0;
switch (hh->proto) {
case IPAC_PROTO_RSL:
if (!link->trx) {
LOGP(DABIS, LOGL_NOTICE,
"Received RSL message not on RSL link\n");
msgb_free(msg);
ret = EIO;
break;
}
msg->trx = link->trx;
ret = down_rsl(link->trx, msg);
break;
case IPAC_PROTO_IPACCESS:
ret = abis_rx_ipaccess(link, msg);
break;
case IPAC_PROTO_SCCP:
LOGP(DABIS, LOGL_INFO, "Received SCCP message\n");
msgb_free(msg);
break;
case IPAC_PROTO_OML:
if (!link->bts) {
LOGP(DABIS, LOGL_NOTICE,
"Received OML message not on OML link\n");
msgb_free(msg);
ret = EIO;
break;
}
msg->trx = link->bts->c0;
ret = down_oml(link->bts, msg);
break;
default:
LOGP(DABIS, LOGL_NOTICE, "Unknown Protocol %d\n", hh->proto);
msgb_free(msg);
ret = EINVAL;
}
return ret;
}
static void abis_timeout(void *arg)
{
struct ipabis_link *link = arg;
int ret;
switch (link->state) {
case LINK_STATE_RETRYING:
ret = abis_open(link, link->ip);
if (ret <= 0)
osmo_timer_schedule(&link->timer, OML_RETRY_TIMER, 0);
break;
case LINK_STATE_CONNECT:
if (link->ping && !link->pong) {
LOGP(DABIS, LOGL_NOTICE,
"No reply to PING. Link is lost\n");
abis_close(link);
ret = abis_open(link, link->ip);
if (ret <= 0) {
osmo_timer_schedule(&link->timer,
OML_RETRY_TIMER, 0);
link->state = LINK_STATE_RETRYING;
}
break;
}
link->ping = 1;
link->pong = 0;
LOGP(DABIS, LOGL_DEBUG, "PING\n");
abis_tx_ipa_pingpong(link, IPAC_MSGT_PING);
osmo_timer_schedule(&link->timer, OML_PING_TIMER, 0);
break;
}
}
static int abis_sock_cb(struct osmo_fd *bfd, unsigned int what)
{
struct ipabis_link *link = bfd->data;
struct ipaccess_head *hh;
struct msgb *msg;
int ret = 0;
if ((what & BSC_FD_WRITE) && link->state == LINK_STATE_CONNECTING) {
if (link->bts) {
if (osmo_timer_pending(&link->timer))
osmo_timer_del(&link->timer);
// osmo_timer_schedule(&link->timer, OML_PING_TIMER, 0);
#warning HACK
osmo_timer_schedule(&link->timer, 3, 0);
link->ping = link->pong = 0;
}
LOGP(DABIS, LOGL_INFO, "Abis socket now connected.\n");
link->state = LINK_STATE_CONNECT;
}
//printf("what %d\n", what);
if ((what & BSC_FD_READ)) {
if (!link->rx_msg) {
link->rx_msg = msgb_alloc(ABIS_ALLOC_SIZE, "Abis/IP");
if (!link->rx_msg)
return -ENOMEM;
}
msg = link->rx_msg;
hh = (struct ipaccess_head *) msg->data;
if (msg->len < sizeof(*hh)) {
ret = recv(link->bfd.fd, msg->data, sizeof(*hh), 0);
if (ret <= 0) {
goto close;
}
msgb_put(msg, ret);
if (msg->len < sizeof(*hh))
return 0;
msg->l2h = msg->data + sizeof(*hh);
if (ntohs(hh->len) > msgb_tailroom(msg)) {
LOGP(DABIS, LOGL_DEBUG, "Received packet from "
"Abis socket too large.\n");
goto close;
}
}
ret = recv(link->bfd.fd, msg->tail,
ntohs(hh->len) + sizeof(*hh) - msg->len, 0);
if (ret == 0)
goto close;
if (ret < 0 && errno != EAGAIN)
goto close;
msgb_put(msg, ret);
if (ntohs(hh->len) + sizeof(*hh) > msg->len)
return 0;
link->rx_msg = NULL;
LOGP(DABIS, LOGL_DEBUG, "Received messages from Abis socket.\n");
ret = abis_rx(link, msg);
}
if ((what & BSC_FD_WRITE)) {
msg = msgb_dequeue(&link->tx_queue);
if (msg) {
LOGP(DABIS, LOGL_DEBUG, "Sending messages to Abis socket.\n");
ret = send(link->bfd.fd, msg->data, msg->len, 0);
msgb_free(msg);
if (ret < 0)
goto close;
} else
link->bfd.when &= ~BSC_FD_WRITE;
}
if ((what & BSC_FD_EXCEPT)) {
LOGP(DABIS, LOGL_NOTICE, "Abis socket received exception\n");
goto close;
}
return ret;
close:
abis_close(link);
/* RSL link will just close and BSC is notified */
if (link->trx) {
LOGP(DABIS, LOGL_NOTICE, "Connection to BSC failed\n");
return trx_link_estab(link->trx);
}
LOGP(DABIS, LOGL_NOTICE, "Connection to BSC failed, retrying in %d "
"seconds.\n", OML_RETRY_TIMER);
osmo_timer_schedule(&link->timer, OML_RETRY_TIMER, 0);
link->state = LINK_STATE_RETRYING;
return 0;
}
int abis_open(struct ipabis_link *link, uint32_t ip)
{
unsigned int on = 1;
struct sockaddr_in addr;
int sock;
int ret;
oml_init();
if (link->bfd.fd > 0)
return -EBUSY;
INIT_LLIST_HEAD(&link->tx_queue);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return sock;
ret = ioctl(sock, FIONBIO, (unsigned char *)&on);
if (ret < 0) {
close(sock);
return ret;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
if (link->bts)
addr.sin_port = htons(IPA_TCP_PORT_OML);
else
addr.sin_port = htons(IPA_TCP_PORT_RSL);
addr.sin_addr.s_addr = htonl(ip);
ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0 && errno != EINPROGRESS) {
close(sock);
return ret;
}
link->bfd.data = link;
link->bfd.when = BSC_FD_READ | BSC_FD_WRITE | BSC_FD_EXCEPT;
link->bfd.cb = abis_sock_cb;
link->bfd.fd = sock;
link->state = LINK_STATE_CONNECTING;
link->ip = ip;
link->timer.cb = abis_timeout;
link->timer.data = link;
osmo_fd_register(&link->bfd);
LOGP(DABIS, LOGL_INFO, "Abis socket trying to reach BSC.\n");
return sock;
}
void abis_close(struct ipabis_link *link)
{
struct msgb *msg;
if (link->bfd.fd <= 0)
return;
LOGP(DABIS, LOGL_NOTICE, "Abis socket closed.\n");
if (link->rx_msg) {
msgb_free(link->rx_msg);
link->rx_msg = NULL;
}
while ((msg = msgb_dequeue(&link->tx_queue)))
msgb_free(msg);
osmo_fd_unregister(&link->bfd);
close(link->bfd.fd);
link->bfd.fd = -1; /* -1 or 0 indicates: 'close' */
link->state = LINK_STATE_IDLE;
if (osmo_timer_pending(&link->timer))
osmo_timer_del(&link->timer);
/* for now, we simply terminate the program and re-spawn */
if (link->bts)
bts_shutdown(link->bts, "Abis close / OML");
else if (link->trx)
bts_shutdown(link->trx->bts, "Abis close / RSL");
2011-07-01 17:03:30 +00:00
else {
LOGP(DABIS, LOGL_FATAL, "Unable to connect to BSC\n");
exit(43);
2011-07-01 17:03:30 +00:00
}
}