blob: 1e50dca8fc40dde6018c99ef4775b2ece1f74815 [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.
Harald Welte3e5ab692018-06-04 04:26:20 +020017 */
18
19#include <unistd.h>
20#include <stdint.h>
21#include <talloc.h>
22#include <string.h>
23#include <errno.h>
Harald Welted31ddde2018-06-04 14:50:20 +020024#include <sys/ioctl.h>
Harald Welte3e5ab692018-06-04 04:26:20 +020025
26#include <netinet/in.h>
27
28#include <osmocom/core/msgb.h>
29#include <osmocom/core/socket.h>
30#include <osmocom/gsm/ipa.h>
31#include <osmocom/gsm/protocol/ipaccess.h>
32
Max5d42b8e2019-02-07 17:28:01 +010033#include "client.h"
Harald Welte3e5ab692018-06-04 04:26:20 +020034#include "simple_ctrl.h"
35
Max5d42b8e2019-02-07 17:28:01 +010036#define CTRL_ERR(sch, fmt, args...) \
37 fprintf(stderr, "CTRL %s error: " fmt, make_authority(sch, &sch->cfg), ##args)
Max9ce5bf92018-12-28 19:28:03 +010038
Harald Welte0e9d3692018-06-04 11:52:39 +020039/***********************************************************************
40 * blocking I/O with timeout helpers
41 ***********************************************************************/
42
Harald Welte21b901c2018-06-04 14:49:49 +020043static struct timeval *timeval_from_msec(uint32_t tout_msec)
44{
45 static struct timeval tout;
46
47 if (tout_msec == 0)
48 return NULL;
49 tout.tv_sec = tout_msec/1000;
50 tout.tv_usec = (tout_msec%1000)*1000;
51
52 return &tout;
53}
54
Harald Welte0e9d3692018-06-04 11:52:39 +020055static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
56{
Harald Welte0e9d3692018-06-04 11:52:39 +020057 fd_set readset;
58 int rc;
59
60 FD_ZERO(&readset);
61 FD_SET(fd, &readset);
Harald Welte0e9d3692018-06-04 11:52:39 +020062
Harald Welte21b901c2018-06-04 14:49:49 +020063 rc = select(fd+1, &readset, NULL, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020064 if (rc < 0)
65 return rc;
66
67 if (FD_ISSET(fd, &readset))
68 return read(fd, buf, count);
69
70 return -ETIMEDOUT;
71}
72
73static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
74{
Harald Welte0e9d3692018-06-04 11:52:39 +020075 fd_set writeset;
76 int rc;
77
78 FD_ZERO(&writeset);
79 FD_SET(fd, &writeset);
Harald Welte0e9d3692018-06-04 11:52:39 +020080
Harald Welte21b901c2018-06-04 14:49:49 +020081 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020082 if (rc < 0)
83 return rc;
84
85 if (FD_ISSET(fd, &writeset))
86 return write(fd, buf, count);
87
88 return -ETIMEDOUT;
89}
90
91
92/***********************************************************************
93 * actual CTRL client API
94 ***********************************************************************/
95
Harald Welte3e5ab692018-06-04 04:26:20 +020096struct simple_ctrl_handle {
97 int fd;
98 uint32_t next_id;
Harald Welte0e9d3692018-06-04 11:52:39 +020099 uint32_t tout_msec;
Max5d42b8e2019-02-07 17:28:01 +0100100 struct host_cfg cfg;
Harald Welte3e5ab692018-06-04 04:26:20 +0200101};
102
Harald Welte0e9d3692018-06-04 11:52:39 +0200103struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
104 uint32_t tout_msec)
Harald Welte3e5ab692018-06-04 04:26:20 +0200105{
Max9ce5bf92018-12-28 19:28:03 +0100106 struct simple_ctrl_handle *sch = talloc_zero(ctx, struct simple_ctrl_handle);
Harald Welted31ddde2018-06-04 14:50:20 +0200107 fd_set writeset;
108 int off = 0;
109 int rc, fd;
Harald Welte3e5ab692018-06-04 04:26:20 +0200110
Max9ce5bf92018-12-28 19:28:03 +0100111 if (!sch)
112 return NULL;
113
114 sch->cfg.name = talloc_strdup(sch, "simple-ctrl");
115 sch->cfg.remote_host = talloc_strdup(sch, host);
116 sch->cfg.remote_port = dport;
117
Harald Welted31ddde2018-06-04 14:50:20 +0200118 fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport,
119 OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
120 if (fd < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100121 CTRL_ERR(sch, "connecting socket: %s\n", strerror(errno));
Harald Welte3e5ab692018-06-04 04:26:20 +0200122 return NULL;
123 }
124
Harald Welted31ddde2018-06-04 14:50:20 +0200125 /* wait until connect (or timeout) happens */
126 FD_ZERO(&writeset);
127 FD_SET(fd, &writeset);
Harald Welted31ddde2018-06-04 14:50:20 +0200128 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
129 if (rc == 0) {
Max5d42b8e2019-02-07 17:28:01 +0100130 CTRL_ERR(sch, "timeout during connect\n");
Harald Welted31ddde2018-06-04 14:50:20 +0200131 goto out_close;
Harald Welte3e5ab692018-06-04 04:26:20 +0200132 }
Harald Welted31ddde2018-06-04 14:50:20 +0200133 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100134 CTRL_ERR(sch, "error connecting socket: %s\n", strerror(errno));
Harald Welted31ddde2018-06-04 14:50:20 +0200135 goto out_close;
136 }
137
138 /* set FD blocking again */
139 if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100140 CTRL_ERR(sch, "cannot set socket blocking: %s\n", strerror(errno));
Harald Welted31ddde2018-06-04 14:50:20 +0200141 goto out_close;
142 }
143
Harald Welted31ddde2018-06-04 14:50:20 +0200144 sch->fd = fd;
Harald Welte0e9d3692018-06-04 11:52:39 +0200145 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200146 return sch;
Harald Welted31ddde2018-06-04 14:50:20 +0200147
148out_close:
149 close(fd);
150 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200151}
152
Harald Welte7c940802018-06-04 12:04:38 +0200153void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
154{
155 sch->tout_msec = tout_msec;
156}
157
Harald Welte3e5ab692018-06-04 04:26:20 +0200158void simple_ctrl_close(struct simple_ctrl_handle *sch)
159{
160 close(sch->fd);
161 talloc_free(sch);
162}
163
164static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
165{
166 struct ipaccess_head hh;
167 struct msgb *resp;
168 int rc, len;
169
Harald Welte0e9d3692018-06-04 11:52:39 +0200170 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
171 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100172 CTRL_ERR(sch, "read(): %d\n", rc);
Harald Welte0e9d3692018-06-04 11:52:39 +0200173 return NULL;
174 } else if (rc < sizeof(hh)) {
Max5d42b8e2019-02-07 17:28:01 +0100175 CTRL_ERR(sch, "short read (header)\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200176 return NULL;
177 }
178 len = ntohs(hh.len);
179
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200180 resp = msgb_alloc(len+sizeof(hh)+1, "CTRL Rx");
Harald Welte3e5ab692018-06-04 04:26:20 +0200181 if (!resp)
182 return NULL;
183 resp->l1h = msgb_put(resp, sizeof(hh));
184 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
185
186 resp->l2h = resp->tail;
187 rc = read(sch->fd, resp->l2h, len);
188 if (rc < len) {
Max5d42b8e2019-02-07 17:28:01 +0100189 CTRL_ERR(sch, "short read (payload)\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200190 msgb_free(resp);
191 return NULL;
192 }
193 msgb_put(resp, rc);
194
195 return resp;
196}
197
Harald Welte7c940802018-06-04 12:04:38 +0200198struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200199{
200 struct msgb *resp;
201 struct ipaccess_head *ih;
202 struct ipaccess_head_ext *ihe;
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200203 unsigned char *tmp;
Harald Welte3e5ab692018-06-04 04:26:20 +0200204
205 /* loop until we've received a CTRL message */
206 while (true) {
207 resp = simple_ipa_receive(sch);
208 if (!resp)
209 return NULL;
210
211 ih = (struct ipaccess_head *) resp->l1h;
212 if (ih->proto == IPAC_PROTO_OSMO)
213 resp->l2h = resp->l2h+1;
214 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200215 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL) {
216 /* Ensure data is NULL terminated */
217 tmp = msgb_put(resp, 1);
218 *tmp = '\0';
Harald Welte3e5ab692018-06-04 04:26:20 +0200219 return resp;
Daniel Willmann9b2d5112018-06-05 17:26:16 +0200220 } else {
Max5d42b8e2019-02-07 17:28:01 +0100221 CTRL_ERR(sch, "unknown IPA message %s\n", msgb_hexdump(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200222 msgb_free(resp);
223 }
224 }
225}
226
227static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
228{
229 int rc;
230
231 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
232 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
233
Harald Welte0e9d3692018-06-04 11:52:39 +0200234 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
235 if (rc < 0) {
Max5d42b8e2019-02-07 17:28:01 +0100236 CTRL_ERR(sch, "write(): %d\n", rc);
Harald Welte0e9d3692018-06-04 11:52:39 +0200237 return rc;
238 } else if (rc < msg->len) {
Max5d42b8e2019-02-07 17:28:01 +0100239 CTRL_ERR(sch, "short write\n");
Harald Welte3e5ab692018-06-04 04:26:20 +0200240 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200241 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200242 } else {
243 msgb_free(msg);
244 return 0;
245 }
246}
247
248static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
249{
250 int rc;
251
252 rc = simple_ctrl_send(sch, msg);
253 if (rc < 0)
254 return NULL;
255
256 /* FIXME: ignore any TRAP */
Harald Welte3e5ab692018-06-04 04:26:20 +0200257 return simple_ctrl_receive(sch);
258}
259
260char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
261{
262 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
263 struct msgb *resp;
264 unsigned int rx_id;
265 char *rx_var, *rx_val;
266 int rc;
267
268 if (!msg)
269 return NULL;
270
271 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
272 if (rc < 0) {
273 msgb_free(msg);
274 return NULL;
275 }
276 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200277 if (!resp)
278 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200279
280 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
Daniel Willmann66a83f42019-01-28 16:11:08 +0100281 if ((rc == 2) || (rc == 3)) {
282 /* If body is empty return an empty string */
283 if (rc == 2)
284 rx_val = strdup("");
285
Harald Welte3e5ab692018-06-04 04:26:20 +0200286 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
287 free(rx_var);
288 msgb_free(resp);
289 return rx_val;
290 }
291 free(rx_var);
292 free(rx_val);
293 } else {
Max5d42b8e2019-02-07 17:28:01 +0100294 CTRL_ERR(sch, "GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200295 }
296
297 msgb_free(resp);
298 return NULL;
299}
300
301int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
302{
303 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
304 struct msgb *resp;
305 unsigned int rx_id;
306 char *rx_var, *rx_val;
307 int rc;
308
309 if (!msg)
310 return -1;
311
312 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
313 if (rc < 0) {
314 msgb_free(msg);
315 return -1;
316 }
317 resp = simple_ctrl_xceive(sch, msg);
Harald Welte47fb3832018-06-05 19:08:58 +0200318 if (!resp)
319 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200320
321 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
322 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
323 free(rx_val);
324 free(rx_var);
325 msgb_free(resp);
326 return 0;
327 } else {
328 free(rx_val);
329 free(rx_var);
330 }
331 } else {
Max5d42b8e2019-02-07 17:28:01 +0100332 CTRL_ERR(sch, "SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
Harald Welte3e5ab692018-06-04 04:26:20 +0200333 }
334
335 msgb_free(resp);
336 return -1;
337}