| /* |
| * OsmocomBB <-> SDR connection bridge |
| * UNIX socket server for L1CTL |
| * |
| * (C) 2013 by Sylvain Munaut <tnt@246tNt.com> |
| * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com> |
| * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> |
| * |
| * 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. |
| * |
| */ |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <sys/un.h> |
| #include <arpa/inet.h> |
| #include <sys/socket.h> |
| |
| #include <osmocom/core/talloc.h> |
| #include <osmocom/core/select.h> |
| #include <osmocom/core/socket.h> |
| #include <osmocom/core/write_queue.h> |
| |
| #include <osmocom/bb/trxcon/logging.h> |
| #include <osmocom/bb/trxcon/l1ctl_server.h> |
| |
| #define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args) |
| |
| static int l1ctl_client_read_cb(struct osmo_fd *ofd) |
| { |
| struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; |
| struct msgb *msg; |
| uint16_t len; |
| int rc; |
| |
| /* Attempt to read from socket */ |
| rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD); |
| if (rc != L1CTL_MSG_LEN_FIELD) { |
| if (rc <= 0) { |
| LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc, |
| strerror(errno)); |
| } else { |
| LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n"); |
| rc = -EIO; |
| } |
| l1ctl_client_conn_close(client); |
| return rc; |
| } |
| |
| /* Check message length */ |
| len = ntohs(len); |
| if (len > L1CTL_LENGTH) { |
| LOGP_CLI(client, 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_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n"); |
| return -ENOMEM; |
| } |
| |
| msg->l1h = msgb_put(msg, len); |
| rc = read(ofd->fd, msg->l1h, msgb_l1len(msg)); |
| if (rc != len) { |
| LOGP_CLI(client, 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_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len)); |
| |
| /* Call L1CTL handler */ |
| client->server->cfg->conn_read_cb(client, msg); |
| |
| return 0; |
| } |
| |
| static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg) |
| { |
| struct l1ctl_client *client = (struct l1ctl_client *)ofd->data; |
| int len; |
| |
| if (ofd->fd <= 0) |
| return -EINVAL; |
| |
| len = write(ofd->fd, msg->data, msg->len); |
| if (len != msg->len) { |
| LOGP_CLI(client, 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_server_conn_cb(struct osmo_fd *sfd, unsigned int flags) |
| { |
| struct l1ctl_server *server = (struct l1ctl_server *)sfd->data; |
| struct l1ctl_client *client; |
| int rc, client_fd; |
| |
| client_fd = accept(sfd->fd, NULL, NULL); |
| if (client_fd < 0) { |
| LOGP(DL1C, LOGL_ERROR, |
| "Failed to accept() a new connection: " |
| "%s\n", |
| strerror(errno)); |
| return client_fd; |
| } |
| |
| if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ && |
| server->num_clients >= server->cfg->num_clients_max) { |
| LOGP(DL1C, LOGL_NOTICE, |
| "L1CTL server cannot accept more " |
| "than %u connection(s)\n", |
| server->cfg->num_clients_max); |
| close(client_fd); |
| return -ENOMEM; |
| } |
| |
| client = talloc_zero(server, struct l1ctl_client); |
| if (client == NULL) { |
| LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n"); |
| close(client_fd); |
| return -ENOMEM; |
| } |
| |
| /* Init the client's write queue */ |
| osmo_wqueue_init(&client->wq, 100); |
| INIT_LLIST_HEAD(&client->wq.bfd.list); |
| |
| client->wq.write_cb = &l1ctl_client_write_cb; |
| client->wq.read_cb = &l1ctl_client_read_cb; |
| osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0); |
| |
| /* Register the client's write queue */ |
| rc = osmo_fd_register(&client->wq.bfd); |
| if (rc != 0) { |
| LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n"); |
| close(client->wq.bfd.fd); |
| talloc_free(client); |
| return rc; |
| } |
| |
| llist_add_tail(&client->list, &server->clients); |
| client->id = server->next_client_id++; |
| client->server = server; |
| server->num_clients++; |
| |
| LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id); |
| |
| if (client->server->cfg->conn_accept_cb != NULL) |
| client->server->cfg->conn_accept_cb(client); |
| |
| return 0; |
| } |
| |
| int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg) |
| { |
| uint8_t *len; |
| |
| /* Debug print */ |
| LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len)); |
| |
| if (msg->l1h != msg->data) |
| LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n"); |
| |
| /* Prepend 16-bit length before sending */ |
| len = msgb_push(msg, L1CTL_MSG_LEN_FIELD); |
| osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len); |
| |
| if (osmo_wqueue_enqueue(&client->wq, msg) != 0) { |
| LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n"); |
| msgb_free(msg); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| void l1ctl_client_conn_close(struct l1ctl_client *client) |
| { |
| struct l1ctl_server *server = client->server; |
| |
| LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n"); |
| |
| if (server->cfg->conn_close_cb != NULL) |
| server->cfg->conn_close_cb(client); |
| |
| /* Close connection socket */ |
| osmo_fd_unregister(&client->wq.bfd); |
| close(client->wq.bfd.fd); |
| client->wq.bfd.fd = -1; |
| |
| /* Clear pending messages */ |
| osmo_wqueue_clear(&client->wq); |
| |
| client->server->num_clients--; |
| llist_del(&client->list); |
| talloc_free(client); |
| |
| /* If this was the last client, reset the client IDs generator to 0. |
| * This way avoid assigning huge unreadable client IDs like 26545. */ |
| if (llist_empty(&server->clients)) |
| server->next_client_id = 0; |
| } |
| |
| struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg) |
| { |
| struct l1ctl_server *server; |
| int rc; |
| |
| LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path); |
| |
| server = talloc(ctx, struct l1ctl_server); |
| OSMO_ASSERT(server != NULL); |
| |
| *server = (struct l1ctl_server){ |
| .clients = LLIST_HEAD_INIT(server->clients), |
| .cfg = cfg, |
| }; |
| |
| /* conn_read_cb shall not be NULL */ |
| OSMO_ASSERT(cfg->conn_read_cb != NULL); |
| |
| /* Bind connection handler */ |
| osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0); |
| |
| rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND); |
| if (rc < 0) { |
| LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno)); |
| talloc_free(server); |
| return NULL; |
| } |
| |
| return server; |
| } |
| |
| void l1ctl_server_free(struct l1ctl_server *server) |
| { |
| LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n"); |
| |
| /* Close all client connections */ |
| while (!llist_empty(&server->clients)) { |
| struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list); |
| l1ctl_client_conn_close(client); |
| } |
| |
| /* Unbind listening socket */ |
| if (server->ofd.fd != -1) { |
| osmo_fd_unregister(&server->ofd); |
| close(server->ofd.fd); |
| server->ofd.fd = -1; |
| } |
| |
| talloc_free(server); |
| } |