blob: 8466e5fa34d0dbb8889a41788347a11bf1a7644d [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);
121 printf("inprogress");
122 rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
123 if (rc == 0) {
124 fprintf(stderr, "CTRL: timeout during connect\n");
125 goto out_close;
Harald Welte3e5ab692018-06-04 04:26:20 +0200126 }
Harald Welted31ddde2018-06-04 14:50:20 +0200127 if (rc < 0) {
128 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
129 goto out_close;
130 }
131
132 /* set FD blocking again */
133 if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
134 fprintf(stderr, "CTRL: cannot set socket blocking: %s\n", strerror(errno));
135 goto out_close;
136 }
137
138 sch = talloc_zero(ctx, struct simple_ctrl_handle);
139 if (!sch)
140 goto out_close;
141 sch->fd = fd;
Harald Welte0e9d3692018-06-04 11:52:39 +0200142 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200143 return sch;
Harald Welted31ddde2018-06-04 14:50:20 +0200144
145out_close:
146 close(fd);
147 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200148}
149
Harald Welte7c940802018-06-04 12:04:38 +0200150void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
151{
152 sch->tout_msec = tout_msec;
153}
154
Harald Welte3e5ab692018-06-04 04:26:20 +0200155void simple_ctrl_close(struct simple_ctrl_handle *sch)
156{
157 close(sch->fd);
158 talloc_free(sch);
159}
160
161static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
162{
163 struct ipaccess_head hh;
164 struct msgb *resp;
165 int rc, len;
166
Harald Welte0e9d3692018-06-04 11:52:39 +0200167 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
168 if (rc < 0) {
169 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
170 return NULL;
171 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200172 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
173 return NULL;
174 }
175 len = ntohs(hh.len);
176
177 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
178 if (!resp)
179 return NULL;
180 resp->l1h = msgb_put(resp, sizeof(hh));
181 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
182
183 resp->l2h = resp->tail;
184 rc = read(sch->fd, resp->l2h, len);
185 if (rc < len) {
186 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
187 msgb_free(resp);
188 return NULL;
189 }
190 msgb_put(resp, rc);
191
192 return resp;
193}
194
Harald Welte7c940802018-06-04 12:04:38 +0200195struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200196{
197 struct msgb *resp;
198 struct ipaccess_head *ih;
199 struct ipaccess_head_ext *ihe;
200
201 /* loop until we've received a CTRL message */
202 while (true) {
203 resp = simple_ipa_receive(sch);
204 if (!resp)
205 return NULL;
206
207 ih = (struct ipaccess_head *) resp->l1h;
208 if (ih->proto == IPAC_PROTO_OSMO)
209 resp->l2h = resp->l2h+1;
210 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
211 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
212 return resp;
213 else {
214 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
215 msgb_free(resp);
216 }
217 }
218}
219
220static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
221{
222 int rc;
223
224 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
225 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
226
Harald Welte0e9d3692018-06-04 11:52:39 +0200227 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
228 if (rc < 0) {
229 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
230 return rc;
231 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200232 fprintf(stderr, "CTRL: ERROR: short write\n");
233 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200234 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200235 } else {
236 msgb_free(msg);
237 return 0;
238 }
239}
240
241static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
242{
243 int rc;
244
245 rc = simple_ctrl_send(sch, msg);
246 if (rc < 0)
247 return NULL;
248
249 /* FIXME: ignore any TRAP */
250 /* FIXME: check string is zero-terminated */
251 return simple_ctrl_receive(sch);
252}
253
254char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
255{
256 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
257 struct msgb *resp;
258 unsigned int rx_id;
259 char *rx_var, *rx_val;
260 int rc;
261
262 if (!msg)
263 return NULL;
264
265 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
266 if (rc < 0) {
267 msgb_free(msg);
268 return NULL;
269 }
270 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200271 if (!resp)
272 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200273
274 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
275 if (rc == 3) {
276 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
277 free(rx_var);
278 msgb_free(resp);
279 return rx_val;
280 }
281 free(rx_var);
282 free(rx_val);
283 } else {
284 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
285 }
286
287 msgb_free(resp);
288 return NULL;
289}
290
291int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
292{
293 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
294 struct msgb *resp;
295 unsigned int rx_id;
296 char *rx_var, *rx_val;
297 int rc;
298
299 if (!msg)
300 return -1;
301
302 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
303 if (rc < 0) {
304 msgb_free(msg);
305 return -1;
306 }
307 resp = simple_ctrl_xceive(sch, msg);
308
309 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
310 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
311 free(rx_val);
312 free(rx_var);
313 msgb_free(resp);
314 return 0;
315 } else {
316 free(rx_val);
317 free(rx_var);
318 }
319 } else {
320 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
321 }
322
323 msgb_free(resp);
324 return -1;
325}