/* * OsmocomBB <-> SDR connection bridge * GSM L1 control socket (/tmp/osmocom_l2) handlers * * (C) 2013 by Sylvain Munaut * (C) 2016-2017 by Vadim Yanitskiy * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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; /* Allocate a new msg */ msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "L1CTL"); if (!msg) { LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n"); return -ENOMEM; } /* Attempt to read from socket */ rc = read(bfd->fd, &len, sizeof(len)); if (rc < sizeof(len)) { LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n"); msgb_free(msg); 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); msgb_free(msg); return -EINVAL; } 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, sizeof(*len)); *len = htons(msg->len - sizeof(*len)); 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; } int l1ctl_link_init(struct l1ctl_link **l1l, const char *sock_path) { struct l1ctl_link *l1l_new; struct osmo_fd *bfd; int rc; LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path); l1l_new = talloc_zero(tall_trx_ctx, struct l1ctl_link); if (!l1l_new) { LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n"); return -ENOMEM; } /* Create a socket and bind handlers */ bfd = &l1l_new->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)); talloc_free(l1l_new); return rc; } /* Bind shutdown handler */ l1l_new->shutdown_cb = l1ctl_shutdown_cb; /* Bind connection handler */ bfd->cb = l1ctl_link_accept; bfd->when = BSC_FD_READ; bfd->data = l1l_new; /** * To be able to accept first connection and * drop others, it should be set to -1 */ l1l_new->wq.bfd.fd = -1; /* Allocate a new dedicated state machine */ osmo_fsm_register(&l1ctl_fsm); l1l_new->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l_new, NULL, LOGL_DEBUG, "l1ctl_link"); *l1l = l1l_new; return 0; } 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); }