blob: e585fac54544f3c130c1c303adecb2b352a4d4ea [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
38#include "simple_ctrl.h"
39
Harald Welte0e9d3692018-06-04 11:52:39 +020040/***********************************************************************
41 * blocking I/O with timeout helpers
42 ***********************************************************************/
43
Harald Welte21b901c2018-06-04 14:49:49 +020044static struct timeval *timeval_from_msec(uint32_t tout_msec)
45{
46 static struct timeval tout;
47
48 if (tout_msec == 0)
49 return NULL;
50 tout.tv_sec = tout_msec/1000;
51 tout.tv_usec = (tout_msec%1000)*1000;
52
53 return &tout;
54}
55
Harald Welte0e9d3692018-06-04 11:52:39 +020056static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
57{
Harald Welte0e9d3692018-06-04 11:52:39 +020058 fd_set readset;
59 int rc;
60
61 FD_ZERO(&readset);
62 FD_SET(fd, &readset);
Harald Welte0e9d3692018-06-04 11:52:39 +020063
Harald Welte21b901c2018-06-04 14:49:49 +020064 rc = select(fd+1, &readset, NULL, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020065 if (rc < 0)
66 return rc;
67
68 if (FD_ISSET(fd, &readset))
69 return read(fd, buf, count);
70
71 return -ETIMEDOUT;
72}
73
74static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
75{
Harald Welte0e9d3692018-06-04 11:52:39 +020076 fd_set writeset;
77 int rc;
78
79 FD_ZERO(&writeset);
80 FD_SET(fd, &writeset);
Harald Welte0e9d3692018-06-04 11:52:39 +020081
Harald Welte21b901c2018-06-04 14:49:49 +020082 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
Harald Welte0e9d3692018-06-04 11:52:39 +020083 if (rc < 0)
84 return rc;
85
86 if (FD_ISSET(fd, &writeset))
87 return write(fd, buf, count);
88
89 return -ETIMEDOUT;
90}
91
92
93/***********************************************************************
94 * actual CTRL client API
95 ***********************************************************************/
96
Harald Welte3e5ab692018-06-04 04:26:20 +020097struct simple_ctrl_handle {
98 int fd;
99 uint32_t next_id;
Harald Welte0e9d3692018-06-04 11:52:39 +0200100 uint32_t tout_msec;
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{
106 struct simple_ctrl_handle *sch;
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
Harald Welted31ddde2018-06-04 14:50:20 +0200111 fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport,
112 OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
113 if (fd < 0) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200114 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
115 return NULL;
116 }
117
Harald Welted31ddde2018-06-04 14:50:20 +0200118 /* wait until connect (or timeout) happens */
119 FD_ZERO(&writeset);
120 FD_SET(fd, &writeset);
Harald Welted31ddde2018-06-04 14:50:20 +0200121 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
122 if (rc == 0) {
123 fprintf(stderr, "CTRL: timeout during connect\n");
124 goto out_close;
Harald Welte3e5ab692018-06-04 04:26:20 +0200125 }
Harald Welted31ddde2018-06-04 14:50:20 +0200126 if (rc < 0) {
127 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
128 goto out_close;
129 }
130
131 /* set FD blocking again */
132 if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
133 fprintf(stderr, "CTRL: cannot set socket blocking: %s\n", strerror(errno));
134 goto out_close;
135 }
136
137 sch = talloc_zero(ctx, struct simple_ctrl_handle);
138 if (!sch)
139 goto out_close;
140 sch->fd = fd;
Harald Welte0e9d3692018-06-04 11:52:39 +0200141 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200142 return sch;
Harald Welted31ddde2018-06-04 14:50:20 +0200143
144out_close:
145 close(fd);
146 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200147}
148
Harald Welte7c940802018-06-04 12:04:38 +0200149void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
150{
151 sch->tout_msec = tout_msec;
152}
153
Harald Welte3e5ab692018-06-04 04:26:20 +0200154void simple_ctrl_close(struct simple_ctrl_handle *sch)
155{
156 close(sch->fd);
157 talloc_free(sch);
158}
159
160static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
161{
162 struct ipaccess_head hh;
163 struct msgb *resp;
164 int rc, len;
165
Harald Welte0e9d3692018-06-04 11:52:39 +0200166 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
167 if (rc < 0) {
168 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
169 return NULL;
170 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200171 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
172 return NULL;
173 }
174 len = ntohs(hh.len);
175
176 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
177 if (!resp)
178 return NULL;
179 resp->l1h = msgb_put(resp, sizeof(hh));
180 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
181
182 resp->l2h = resp->tail;
183 rc = read(sch->fd, resp->l2h, len);
184 if (rc < len) {
185 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
186 msgb_free(resp);
187 return NULL;
188 }
189 msgb_put(resp, rc);
190
191 return resp;
192}
193
Harald Welte7c940802018-06-04 12:04:38 +0200194struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200195{
196 struct msgb *resp;
197 struct ipaccess_head *ih;
198 struct ipaccess_head_ext *ihe;
199
200 /* loop until we've received a CTRL message */
201 while (true) {
202 resp = simple_ipa_receive(sch);
203 if (!resp)
204 return NULL;
205
206 ih = (struct ipaccess_head *) resp->l1h;
207 if (ih->proto == IPAC_PROTO_OSMO)
208 resp->l2h = resp->l2h+1;
209 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
210 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
211 return resp;
212 else {
213 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
214 msgb_free(resp);
215 }
216 }
217}
218
219static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
220{
221 int rc;
222
223 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
224 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
225
Harald Welte0e9d3692018-06-04 11:52:39 +0200226 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
227 if (rc < 0) {
228 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
229 return rc;
230 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200231 fprintf(stderr, "CTRL: ERROR: short write\n");
232 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200233 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200234 } else {
235 msgb_free(msg);
236 return 0;
237 }
238}
239
240static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
241{
242 int rc;
243
244 rc = simple_ctrl_send(sch, msg);
245 if (rc < 0)
246 return NULL;
247
248 /* FIXME: ignore any TRAP */
249 /* FIXME: check string is zero-terminated */
250 return simple_ctrl_receive(sch);
251}
252
253char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
254{
255 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
256 struct msgb *resp;
257 unsigned int rx_id;
258 char *rx_var, *rx_val;
259 int rc;
260
261 if (!msg)
262 return NULL;
263
264 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
265 if (rc < 0) {
266 msgb_free(msg);
267 return NULL;
268 }
269 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200270 if (!resp)
271 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200272
273 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
274 if (rc == 3) {
275 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
276 free(rx_var);
277 msgb_free(resp);
278 return rx_val;
279 }
280 free(rx_var);
281 free(rx_val);
282 } else {
283 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
284 }
285
286 msgb_free(resp);
287 return NULL;
288}
289
290int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
291{
292 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
293 struct msgb *resp;
294 unsigned int rx_id;
295 char *rx_var, *rx_val;
296 int rc;
297
298 if (!msg)
299 return -1;
300
301 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
302 if (rc < 0) {
303 msgb_free(msg);
304 return -1;
305 }
306 resp = simple_ctrl_xceive(sch, msg);
307
308 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
309 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
310 free(rx_val);
311 free(rx_var);
312 msgb_free(resp);
313 return 0;
314 } else {
315 free(rx_val);
316 free(rx_var);
317 }
318 } else {
319 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
320 }
321
322 msgb_free(resp);
323 return -1;
324}