320 lines
7.5 KiB
C
320 lines
7.5 KiB
C
/*
|
|
* OsmocomBB <-> SDR connection bridge
|
|
* GSM L1 control socket (/tmp/osmocom_l2) handlers
|
|
*
|
|
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
|
|
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include <sys/un.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <osmocom/core/fsm.h>
|
|
#include <osmocom/core/talloc.h>
|
|
#include <osmocom/core/select.h>
|
|
#include <osmocom/core/socket.h>
|
|
#include <osmocom/core/write_queue.h>
|
|
|
|
#include "trxcon.h"
|
|
#include "logging.h"
|
|
#include "l1ctl_link.h"
|
|
#include "l1ctl.h"
|
|
|
|
static struct value_string l1ctl_evt_names[] = {
|
|
{ 0, NULL } /* no events? */
|
|
};
|
|
|
|
static struct osmo_fsm_state l1ctl_fsm_states[] = {
|
|
[L1CTL_STATE_IDLE] = {
|
|
.out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
|
|
.name = "IDLE",
|
|
},
|
|
[L1CTL_STATE_CONNECTED] = {
|
|
.out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
|
|
.name = "CONNECTED",
|
|
},
|
|
};
|
|
|
|
static struct osmo_fsm l1ctl_fsm = {
|
|
.name = "l1ctl_link_fsm",
|
|
.states = l1ctl_fsm_states,
|
|
.num_states = ARRAY_SIZE(l1ctl_fsm_states),
|
|
.log_subsys = DL1C,
|
|
.event_names = l1ctl_evt_names,
|
|
};
|
|
|
|
static int l1ctl_link_read_cb(struct osmo_fd *bfd)
|
|
{
|
|
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
|
|
struct msgb *msg;
|
|
uint16_t len;
|
|
int rc;
|
|
|
|
/* Attempt to read from socket */
|
|
rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
|
|
if (rc < L1CTL_MSG_LEN_FIELD) {
|
|
LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
|
|
if (rc >= 0)
|
|
rc = -EIO;
|
|
l1ctl_link_close_conn(l1l);
|
|
return rc;
|
|
}
|
|
|
|
/* Check message length */
|
|
len = ntohs(len);
|
|
if (len > L1CTL_LENGTH) {
|
|
LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Allocate a new msg */
|
|
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
|
|
L1CTL_HEADROOM, "l1ctl_rx_msg");
|
|
if (!msg) {
|
|
LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
msg->l1h = msgb_put(msg, len);
|
|
rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
|
|
if (rc != len) {
|
|
LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
|
|
"%s\n", len, rc, strerror(errno));
|
|
msgb_free(msg);
|
|
return rc;
|
|
}
|
|
|
|
/* Debug print */
|
|
LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
|
|
osmo_hexdump(msg->data, msg->len));
|
|
|
|
/* Call L1CTL handler */
|
|
l1ctl_rx_cb(l1l, msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
|
|
{
|
|
int len;
|
|
|
|
if (bfd->fd <= 0)
|
|
return -EINVAL;
|
|
|
|
len = write(bfd->fd, msg->data, msg->len);
|
|
if (len != msg->len) {
|
|
LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
|
|
"written (%d) < msg_len (%d)\n", len, msg->len);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Connection handler */
|
|
static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
|
|
{
|
|
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
|
|
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
|
|
struct sockaddr_un un_addr;
|
|
socklen_t len;
|
|
int cfd;
|
|
|
|
len = sizeof(un_addr);
|
|
cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
|
|
if (cfd < 0) {
|
|
LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Check if we already have an active connection */
|
|
if (conn_bfd->fd != -1) {
|
|
LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
|
|
"we already have another active\n");
|
|
close(cfd);
|
|
return 0;
|
|
}
|
|
|
|
osmo_wqueue_init(&l1l->wq, 100);
|
|
INIT_LLIST_HEAD(&conn_bfd->list);
|
|
|
|
l1l->wq.write_cb = l1ctl_link_write_cb;
|
|
l1l->wq.read_cb = l1ctl_link_read_cb;
|
|
conn_bfd->when = BSC_FD_READ;
|
|
conn_bfd->data = l1l;
|
|
conn_bfd->fd = cfd;
|
|
|
|
if (osmo_fd_register(conn_bfd) != 0) {
|
|
LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
|
|
close(conn_bfd->fd);
|
|
conn_bfd->fd = -1;
|
|
return -1;
|
|
}
|
|
|
|
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
|
|
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
|
|
|
|
LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
|
|
{
|
|
uint16_t *len;
|
|
|
|
/* Debug print */
|
|
LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
|
|
osmo_hexdump(msg->data, msg->len));
|
|
|
|
if (msg->l1h != msg->data)
|
|
LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
|
|
|
|
/* Prepend 16-bit length before sending */
|
|
len = (uint16_t *) msgb_push(msg, L1CTL_MSG_LEN_FIELD);
|
|
*len = htons(msg->len - L1CTL_MSG_LEN_FIELD);
|
|
|
|
if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
|
|
LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
|
|
msgb_free(msg);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int l1ctl_link_close_conn(struct l1ctl_link *l1l)
|
|
{
|
|
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
|
|
|
|
if (conn_bfd->fd <= 0)
|
|
return -EINVAL;
|
|
|
|
/* Close connection socket */
|
|
osmo_fd_unregister(conn_bfd);
|
|
close(conn_bfd->fd);
|
|
conn_bfd->fd = -1;
|
|
|
|
/* Clear pending messages */
|
|
osmo_wqueue_clear(&l1l->wq);
|
|
|
|
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
|
|
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct l1ctl_link *l1ctl_link_init(void *tall_ctx, const char *sock_path)
|
|
{
|
|
struct l1ctl_link *l1l;
|
|
struct osmo_fd *bfd;
|
|
int rc;
|
|
|
|
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
|
|
|
|
l1l = talloc_zero(tall_ctx, struct l1ctl_link);
|
|
if (!l1l) {
|
|
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate a new dedicated state machine */
|
|
l1l->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l,
|
|
NULL, LOGL_DEBUG, "l1ctl_link");
|
|
if (l1l->fsm == NULL) {
|
|
LOGP(DTRX, LOGL_ERROR, "Failed to allocate an instance "
|
|
"of FSM '%s'\n", l1ctl_fsm.name);
|
|
talloc_free(l1l);
|
|
return NULL;
|
|
}
|
|
|
|
/* Create a socket and bind handlers */
|
|
bfd = &l1l->listen_bfd;
|
|
rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
|
|
OSMO_SOCK_F_BIND);
|
|
if (rc < 0) {
|
|
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
|
|
strerror(errno));
|
|
osmo_fsm_inst_free(l1l->fsm);
|
|
talloc_free(l1l);
|
|
return NULL;
|
|
}
|
|
|
|
/* Bind shutdown handler */
|
|
l1l->shutdown_cb = l1ctl_shutdown_cb;
|
|
|
|
/* Bind connection handler */
|
|
bfd->cb = l1ctl_link_accept;
|
|
bfd->when = BSC_FD_READ;
|
|
bfd->data = l1l;
|
|
|
|
/**
|
|
* To be able to accept first connection and
|
|
* drop others, it should be set to -1
|
|
*/
|
|
l1l->wq.bfd.fd = -1;
|
|
|
|
return l1l;
|
|
}
|
|
|
|
void l1ctl_link_shutdown(struct l1ctl_link *l1l)
|
|
{
|
|
struct osmo_fd *listen_bfd;
|
|
|
|
/* May be unallocated due to init error */
|
|
if (!l1l)
|
|
return;
|
|
|
|
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
|
|
|
|
/* Call shutdown callback */
|
|
if (l1l->shutdown_cb != NULL)
|
|
l1l->shutdown_cb(l1l);
|
|
|
|
listen_bfd = &l1l->listen_bfd;
|
|
|
|
/* Check if we have an established connection */
|
|
if (l1l->wq.bfd.fd != -1)
|
|
l1ctl_link_close_conn(l1l);
|
|
|
|
/* Unbind listening socket */
|
|
if (listen_bfd->fd != -1) {
|
|
osmo_fd_unregister(listen_bfd);
|
|
close(listen_bfd->fd);
|
|
listen_bfd->fd = -1;
|
|
}
|
|
|
|
osmo_fsm_inst_free(l1l->fsm);
|
|
talloc_free(l1l);
|
|
}
|
|
|
|
static __attribute__((constructor)) void on_dso_load(void)
|
|
{
|
|
OSMO_ASSERT(osmo_fsm_register(&l1ctl_fsm) == 0);
|
|
}
|