blob: 9e9083f77d0f0c0760675cb3585a4df1c0dd80f4 [file] [log] [blame]
Ericb7253c62022-11-28 19:21:08 +01001/*
2 * OsmocomBB <-> SDR connection bridge
3 * UNIX socket server for L1CTL
4 *
5 * (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
6 * (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
7 * (C) 2022 by by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
8 *
9 * All Rights Reserved
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 */
22
23#include <stdio.h>
24#include <errno.h>
25#include <unistd.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include <sys/un.h>
30#include <arpa/inet.h>
31#include <sys/socket.h>
32
33#include <osmocom/core/talloc.h>
34#include <osmocom/core/select.h>
35#include <osmocom/core/socket.h>
36#include <osmocom/core/write_queue.h>
37
38#include <osmocom/bb/trxcon/logging.h>
39#include <osmocom/bb/trxcon/l1ctl_server.h>
40
41#define LOGP_CLI(cli, cat, level, fmt, args...) LOGP(cat, level, "%s" fmt, (cli)->log_prefix, ##args)
42
43static int l1ctl_client_read_cb(struct osmo_fd *ofd)
44{
45 struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
46 struct msgb *msg;
47 uint16_t len;
48 int rc;
49
50 /* Attempt to read from socket */
51 rc = read(ofd->fd, &len, L1CTL_MSG_LEN_FIELD);
52 if (rc != L1CTL_MSG_LEN_FIELD) {
53 if (rc <= 0) {
54 LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: read() failed (rc=%d): %s\n", rc,
55 strerror(errno));
56 } else {
57 LOGP_CLI(client, DL1D, LOGL_NOTICE, "L1CTL connection error: short read\n");
58 rc = -EIO;
59 }
60 l1ctl_client_conn_close(client);
61 return rc;
62 }
63
64 /* Check message length */
65 len = ntohs(len);
66 if (len > L1CTL_LENGTH) {
67 LOGP_CLI(client, DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
68 return -EINVAL;
69 }
70
71 /* Allocate a new msg */
72 msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM, L1CTL_HEADROOM, "l1ctl_rx_msg");
73 if (!msg) {
74 LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to allocate msg\n");
75 return -ENOMEM;
76 }
77
78 msg->l1h = msgb_put(msg, len);
79 rc = read(ofd->fd, msg->l1h, msgb_l1len(msg));
80 if (rc != len) {
81 LOGP_CLI(client, DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: %s\n", len, rc, strerror(errno));
82 msgb_free(msg);
83 return rc;
84 }
85
86 /* Debug print */
87 LOGP_CLI(client, DL1D, LOGL_DEBUG, "RX: '%s'\n", osmo_hexdump(msg->data, msg->len));
88
89 /* Call L1CTL handler */
90 client->server->cfg->conn_read_cb(client, msg);
91
92 return 0;
93}
94
95static int l1ctl_client_write_cb(struct osmo_fd *ofd, struct msgb *msg)
96{
97 struct l1ctl_client *client = (struct l1ctl_client *)ofd->data;
98 int len;
99
100 if (ofd->fd <= 0)
101 return -EINVAL;
102
103 len = write(ofd->fd, msg->data, msg->len);
104 if (len != msg->len) {
105 LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to write data: written (%d) < msg_len (%d)\n", len,
106 msg->len);
107 return -1;
108 }
109
110 return 0;
111}
112
113/* Connection handler */
114static int l1ctl_server_conn_cb(struct osmo_fd *sfd, unsigned int flags)
115{
116 struct l1ctl_server *server = (struct l1ctl_server *)sfd->data;
117 struct l1ctl_client *client;
118 int rc, client_fd;
119
120 client_fd = accept(sfd->fd, NULL, NULL);
121 if (client_fd < 0) {
122 LOGP(DL1C, LOGL_ERROR,
123 "Failed to accept() a new connection: "
124 "%s\n",
125 strerror(errno));
126 return client_fd;
127 }
128
129 if (server->cfg->num_clients_max > 0 /* 0 means unlimited */ &&
130 server->num_clients >= server->cfg->num_clients_max) {
131 LOGP(DL1C, LOGL_NOTICE,
132 "L1CTL server cannot accept more "
133 "than %u connection(s)\n",
134 server->cfg->num_clients_max);
135 close(client_fd);
136 return -ENOMEM;
137 }
138
139 client = talloc_zero(server, struct l1ctl_client);
140 if (client == NULL) {
141 LOGP(DL1C, LOGL_ERROR, "Failed to allocate an L1CTL client\n");
142 close(client_fd);
143 return -ENOMEM;
144 }
145
146 /* Init the client's write queue */
147 osmo_wqueue_init(&client->wq, 100);
148 INIT_LLIST_HEAD(&client->wq.bfd.list);
149
150 client->wq.write_cb = &l1ctl_client_write_cb;
151 client->wq.read_cb = &l1ctl_client_read_cb;
152 osmo_fd_setup(&client->wq.bfd, client_fd, OSMO_FD_READ, &osmo_wqueue_bfd_cb, client, 0);
153
154 /* Register the client's write queue */
155 rc = osmo_fd_register(&client->wq.bfd);
156 if (rc != 0) {
157 LOGP(DL1C, LOGL_ERROR, "Failed to register a new connection fd\n");
158 close(client->wq.bfd.fd);
159 talloc_free(client);
160 return rc;
161 }
162
163 llist_add_tail(&client->list, &server->clients);
164 client->id = server->next_client_id++;
165 client->server = server;
166 server->num_clients++;
167
168 LOGP(DL1C, LOGL_NOTICE, "L1CTL server got a new connection (id=%u)\n", client->id);
169
170 if (client->server->cfg->conn_accept_cb != NULL)
171 client->server->cfg->conn_accept_cb(client);
172
173 return 0;
174}
175
176int l1ctl_client_send(struct l1ctl_client *client, struct msgb *msg)
177{
178 uint8_t *len;
179
180 /* Debug print */
181 LOGP_CLI(client, DL1D, LOGL_DEBUG, "TX: '%s'\n", osmo_hexdump(msg->data, msg->len));
182
183 if (msg->l1h != msg->data)
184 LOGP_CLI(client, DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
185
186 /* Prepend 16-bit length before sending */
187 len = msgb_push(msg, L1CTL_MSG_LEN_FIELD);
188 osmo_store16be(msg->len - L1CTL_MSG_LEN_FIELD, len);
189
190 if (osmo_wqueue_enqueue(&client->wq, msg) != 0) {
191 LOGP_CLI(client, DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
192 msgb_free(msg);
193 return -EIO;
194 }
195
196 return 0;
197}
198
199void l1ctl_client_conn_close(struct l1ctl_client *client)
200{
201 struct l1ctl_server *server = client->server;
202
203 LOGP_CLI(client, DL1C, LOGL_NOTICE, "Closing L1CTL connection\n");
204
205 if (server->cfg->conn_close_cb != NULL)
206 server->cfg->conn_close_cb(client);
207
208 /* Close connection socket */
209 osmo_fd_unregister(&client->wq.bfd);
210 close(client->wq.bfd.fd);
211 client->wq.bfd.fd = -1;
212
213 /* Clear pending messages */
214 osmo_wqueue_clear(&client->wq);
215
216 client->server->num_clients--;
217 llist_del(&client->list);
218 talloc_free(client);
219
220 /* If this was the last client, reset the client IDs generator to 0.
221 * This way avoid assigning huge unreadable client IDs like 26545. */
222 if (llist_empty(&server->clients))
223 server->next_client_id = 0;
224}
225
226struct l1ctl_server *l1ctl_server_alloc(void *ctx, const struct l1ctl_server_cfg *cfg)
227{
228 struct l1ctl_server *server;
229 int rc;
230
231 LOGP(DL1C, LOGL_NOTICE, "Init L1CTL server (sock_path=%s)\n", cfg->sock_path);
232
233 server = talloc(ctx, struct l1ctl_server);
234 OSMO_ASSERT(server != NULL);
235
236 *server = (struct l1ctl_server){
237 .clients = LLIST_HEAD_INIT(server->clients),
238 .cfg = cfg,
239 };
240
241 /* conn_read_cb shall not be NULL */
242 OSMO_ASSERT(cfg->conn_read_cb != NULL);
243
244 /* Bind connection handler */
245 osmo_fd_setup(&server->ofd, -1, OSMO_FD_READ, &l1ctl_server_conn_cb, server, 0);
246
247 rc = osmo_sock_unix_init_ofd(&server->ofd, SOCK_STREAM, 0, cfg->sock_path, OSMO_SOCK_F_BIND);
248 if (rc < 0) {
249 LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n", strerror(errno));
250 talloc_free(server);
251 return NULL;
252 }
253
254 return server;
255}
256
257void l1ctl_server_free(struct l1ctl_server *server)
258{
259 LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL server\n");
260
261 /* Close all client connections */
262 while (!llist_empty(&server->clients)) {
263 struct l1ctl_client *client = llist_entry(server->clients.next, struct l1ctl_client, list);
264 l1ctl_client_conn_close(client);
265 }
266
267 /* Unbind listening socket */
268 if (server->ofd.fd != -1) {
269 osmo_fd_unregister(&server->ofd);
270 close(server->ofd.fd);
271 server->ofd.fd = -1;
272 }
273
274 talloc_free(server);
275}