blob: 883e92f169077c07e7335751f24dc299b76715b6 [file] [log] [blame]
Harald Welte3e5ab692018-06-04 04:26:20 +02001/* Simple, blocking client API against the Osmocom CTRL interface */
2
3/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
4 * All Rights Reserved.
5 *
6 * SPDX-License-Identifier: GPL-2.0+
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301, USA.
22 */
23
24#include <unistd.h>
25#include <stdint.h>
26#include <talloc.h>
27#include <string.h>
28#include <errno.h>
Harald Welted31ddde2018-06-04 14:50:20 +020029#include <sys/ioctl.h>
Harald Welte3e5ab692018-06-04 04:26:20 +020030
31#include <netinet/in.h>
32
33#include <osmocom/core/msgb.h>
34#include <osmocom/core/socket.h>
35#include <osmocom/gsm/ipa.h>
36#include <osmocom/gsm/protocol/ipaccess.h>
37
Max5d42b8e2019-02-07 17:28:01 +010038#include "client.h"
Harald Welte3e5ab692018-06-04 04:26:20 +020039#include "simple_ctrl.h"
40
Max5d42b8e2019-02-07 17:28:01 +010041#define CTRL_ERR(sch, fmt, args...) \
42 fprintf(stderr, "CTRL %s error: " fmt, make_authority(sch, &sch->cfg), ##args)
Max9ce5bf92018-12-28 19:28:03 +010043
Harald Welte0e9d3692018-06-04 11:52:39 +020044/***********************************************************************
45 * blocking I/O with timeout helpers
46 ***********************************************************************/
47
Harald Welte21b901c2018-06-04 14:49:49 +020048static struct timeval *timeval_from_msec(uint32_t tout_msec)
49{
50 static struct timeval tout;
51
52 if (tout_msec == 0)
53 return NULL;
54 tout.tv_sec = tout_msec/1000;
55 tout.tv_usec = (tout_msec%1000)*1000;
56
57 return &tout;
58}
59
Harald Welte0e9d3692018-06-04 11:52:39 +020060static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
61{
Harald Welte0e9d3692018-06-04 11:52:39 +020062 fd_set readset;
63 int rc;
64
65 FD_ZERO(&readset);
66 FD_SET(fd, &readset);
Harald Welte0e9d3692018-06-04 11:52:39 +020067
Harald Welte21b901c2018-06-04 14:49:49 +020068 rc = select(fd+1, &readset, NULL, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020069 if (rc < 0)
70 return rc;
71
72 if (FD_ISSET(fd, &readset))
73 return read(fd, buf, count);
74
75 return -ETIMEDOUT;
76}
77
78static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
79{
Harald Welte0e9d3692018-06-04 11:52:39 +020080 fd_set writeset;
81 int rc;
82
83 FD_ZERO(&writeset);
84 FD_SET(fd, &writeset);
Harald Welte0e9d3692018-06-04 11:52:39 +020085
Harald Welte21b901c2018-06-04 14:49:49 +020086 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020087 if (rc < 0)
88 return rc;
89
90 if (FD_ISSET(fd, &writeset))
91 return write(fd, buf, count);
92
93 return -ETIMEDOUT;
94}
95
96
97/***********************************************************************
98 * actual CTRL client API
99 ***********************************************************************/
100
Harald Welte3e5ab692018-06-04 04:26:20 +0200101struct simple_ctrl_handle {
102 int fd;
103 uint32_t next_id;
Harald Welte0e9d3692018-06-04 11:52:39 +0200104 uint32_t tout_msec;
Max5d42b8e2019-02-07 17:28:01 +0100105 struct host_cfg cfg;
Harald Welte3e5ab692018-06-04 04:26:20 +0200106};
107
Harald Welte0e9d3692018-06-04 11:52:39 +0200108struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
109 uint32_t tout_msec)
Harald Welte3e5ab692018-06-04 04:26:20 +0200110{
Max9ce5bf92018-12-28 19:28:03 +0100111 struct simple_ctrl_handle *sch = talloc_zero(ctx, struct simple_ctrl_handle);
Harald Welted31ddde2018-06-04 14:50:20 +0200112 fd_set writeset;
113 int off = 0;
114 int rc, fd;
Harald Welte3e5ab692018-06-04 04:26:20 +0200115
Max9ce5bf92018-12-28 19:28:03 +0100116 if (!sch)
117 return NULL;
118
119 sch->cfg.name = talloc_strdup(sch, "simple-ctrl");
120 sch->cfg.remote_host = talloc_strdup(sch, host);
121 sch->cfg.remote_port = dport;
122
Harald Welted31ddde2018-06-04 14:50:20 +0200123 fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport,
124 OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
125 if (fd < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100126 CTRL_ERR(sch, "connecting socket: %s\n", strerror(errno));
Harald Welte3e5ab692018-06-04 04:26:20 +0200127 return NULL;
128 }
129
Harald Welted31ddde2018-06-04 14:50:20 +0200130 /* wait until connect (or timeout) happens */
131 FD_ZERO(&writeset);
132 FD_SET(fd, &writeset);
Harald Welted31ddde2018-06-04 14:50:20 +0200133 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
134 if (rc == 0) {
Max5d42b8e2019-02-07 17:28:01 +0100135 CTRL_ERR(sch, "timeout during connect\n");
Harald Welted31ddde2018-06-04 14:50:20 +0200136 goto out_close;
Harald Welte3e5ab692018-06-04 04:26:20 +0200137 }
Harald Welted31ddde2018-06-04 14:50:20 +0200138 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100139 CTRL_ERR(sch, "error connecting socket: %s\n", strerror(errno));
Harald Welted31ddde2018-06-04 14:50:20 +0200140 goto out_close;
141 }
142
143 /* set FD blocking again */
144 if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100145 CTRL_ERR(sch, "cannot set socket blocking: %s\n", strerror(errno));
Harald Welted31ddde2018-06-04 14:50:20 +0200146 goto out_close;
147 }
148
Harald Welted31ddde2018-06-04 14:50:20 +0200149 sch->fd = fd;
Harald Welte0e9d3692018-06-04 11:52:39 +0200150 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200151 return sch;
Harald Welted31ddde2018-06-04 14:50:20 +0200152
153out_close:
154 close(fd);
155 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200156}
157
Harald Welte7c940802018-06-04 12:04:38 +0200158void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
159{
160 sch->tout_msec = tout_msec;
161}
162
Harald Welte3e5ab692018-06-04 04:26:20 +0200163void simple_ctrl_close(struct simple_ctrl_handle *sch)
164{
165 close(sch->fd);
166 talloc_free(sch);
167}
168
169static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
170{
171 struct ipaccess_head hh;
172 struct msgb *resp;
173 int rc, len;
174
Harald Welte0e9d3692018-06-04 11:52:39 +0200175 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
176 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100177 CTRL_ERR(sch, "read(): %d\n", rc);
Harald Welte0e9d3692018-06-04 11:52:39 +0200178 return NULL;
179 } else if (rc < sizeof(hh)) {
Max5d42b8e2019-02-07 17:28:01 +0100180 CTRL_ERR(sch, "short read (header)\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200181 return NULL;
182 }
183 len = ntohs(hh.len);
184
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200185 resp = msgb_alloc(len+sizeof(hh)+1, "CTRL Rx");
Harald Welte3e5ab692018-06-04 04:26:20 +0200186 if (!resp)
187 return NULL;
188 resp->l1h = msgb_put(resp, sizeof(hh));
189 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
190
191 resp->l2h = resp->tail;
192 rc = read(sch->fd, resp->l2h, len);
193 if (rc < len) {
Max5d42b8e2019-02-07 17:28:01 +0100194 CTRL_ERR(sch, "short read (payload)\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200195 msgb_free(resp);
196 return NULL;
197 }
198 msgb_put(resp, rc);
199
200 return resp;
201}
202
Harald Welte7c940802018-06-04 12:04:38 +0200203struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200204{
205 struct msgb *resp;
206 struct ipaccess_head *ih;
207 struct ipaccess_head_ext *ihe;
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200208 unsigned char *tmp;
Harald Welte3e5ab692018-06-04 04:26:20 +0200209
210 /* loop until we've received a CTRL message */
211 while (true) {
212 resp = simple_ipa_receive(sch);
213 if (!resp)
214 return NULL;
215
216 ih = (struct ipaccess_head *) resp->l1h;
217 if (ih->proto == IPAC_PROTO_OSMO)
218 resp->l2h = resp->l2h+1;
219 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200220 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL) {
221 /* Ensure data is NULL terminated */
222 tmp = msgb_put(resp, 1);
223 *tmp = '\0';
Harald Welte3e5ab692018-06-04 04:26:20 +0200224 return resp;
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200225 } else {
Max5d42b8e2019-02-07 17:28:01 +0100226 CTRL_ERR(sch, "unknown IPA message %s\n", msgb_hexdump(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200227 msgb_free(resp);
228 }
229 }
230}
231
232static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
233{
234 int rc;
235
236 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
237 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
238
Harald Welte0e9d3692018-06-04 11:52:39 +0200239 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
240 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100241 CTRL_ERR(sch, "write(): %d\n", rc);
Harald Welte0e9d3692018-06-04 11:52:39 +0200242 return rc;
243 } else if (rc < msg->len) {
Max5d42b8e2019-02-07 17:28:01 +0100244 CTRL_ERR(sch, "short write\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200245 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200246 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200247 } else {
248 msgb_free(msg);
249 return 0;
250 }
251}
252
253static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
254{
255 int rc;
256
257 rc = simple_ctrl_send(sch, msg);
258 if (rc < 0)
259 return NULL;
260
261 /* FIXME: ignore any TRAP */
Harald Welte3e5ab692018-06-04 04:26:20 +0200262 return simple_ctrl_receive(sch);
263}
264
265char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
266{
267 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
268 struct msgb *resp;
269 unsigned int rx_id;
270 char *rx_var, *rx_val;
271 int rc;
272
273 if (!msg)
274 return NULL;
275
276 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
277 if (rc < 0) {
278 msgb_free(msg);
279 return NULL;
280 }
281 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200282 if (!resp)
283 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200284
285 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
286 if (rc == 3) {
287 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
288 free(rx_var);
289 msgb_free(resp);
290 return rx_val;
291 }
292 free(rx_var);
293 free(rx_val);
294 } else {
Max5d42b8e2019-02-07 17:28:01 +0100295 CTRL_ERR(sch, "GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200296 }
297
298 msgb_free(resp);
299 return NULL;
300}
301
302int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
303{
304 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
305 struct msgb *resp;
306 unsigned int rx_id;
307 char *rx_var, *rx_val;
308 int rc;
309
310 if (!msg)
311 return -1;
312
313 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
314 if (rc < 0) {
315 msgb_free(msg);
316 return -1;
317 }
318 resp = simple_ctrl_xceive(sch, msg);
Harald Welte47fb3832018-06-05 19:08:58 +0200319 if (!resp)
320 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200321
322 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
323 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
324 free(rx_val);
325 free(rx_var);
326 msgb_free(resp);
327 return 0;
328 } else {
329 free(rx_val);
330 free(rx_var);
331 }
332 } else {
Max5d42b8e2019-02-07 17:28:01 +0100333 CTRL_ERR(sch, "SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200334 }
335
336 msgb_free(resp);
337 return -1;
338}