blob: de931b219bd141ac6b7ab8e8e3ba1bc17d24c5ef [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>
29
30#include <netinet/in.h>
31
32#include <osmocom/core/msgb.h>
33#include <osmocom/core/socket.h>
34#include <osmocom/gsm/ipa.h>
35#include <osmocom/gsm/protocol/ipaccess.h>
36
37#include "simple_ctrl.h"
38
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;
Harald Welte3e5ab692018-06-04 04:26:20 +0200100};
101
Harald Welte0e9d3692018-06-04 11:52:39 +0200102struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
103 uint32_t tout_msec)
Harald Welte3e5ab692018-06-04 04:26:20 +0200104{
105 struct simple_ctrl_handle *sch;
106 int rc;
107
108 rc = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport, OSMO_SOCK_F_CONNECT);
109 if (rc < 0) {
110 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
111 return NULL;
112 }
113
114 sch = talloc_zero(ctx, struct simple_ctrl_handle);
115 if (!sch) {
116 close(rc);
117 return NULL;
118 }
119 sch->fd = rc;
Harald Welte0e9d3692018-06-04 11:52:39 +0200120 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200121 return sch;
122}
123
Harald Welte7c940802018-06-04 12:04:38 +0200124void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
125{
126 sch->tout_msec = tout_msec;
127}
128
Harald Welte3e5ab692018-06-04 04:26:20 +0200129void simple_ctrl_close(struct simple_ctrl_handle *sch)
130{
131 close(sch->fd);
132 talloc_free(sch);
133}
134
135static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
136{
137 struct ipaccess_head hh;
138 struct msgb *resp;
139 int rc, len;
140
Harald Welte0e9d3692018-06-04 11:52:39 +0200141 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
142 if (rc < 0) {
143 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
144 return NULL;
145 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200146 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
147 return NULL;
148 }
149 len = ntohs(hh.len);
150
151 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
152 if (!resp)
153 return NULL;
154 resp->l1h = msgb_put(resp, sizeof(hh));
155 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
156
157 resp->l2h = resp->tail;
158 rc = read(sch->fd, resp->l2h, len);
159 if (rc < len) {
160 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
161 msgb_free(resp);
162 return NULL;
163 }
164 msgb_put(resp, rc);
165
166 return resp;
167}
168
Harald Welte7c940802018-06-04 12:04:38 +0200169struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200170{
171 struct msgb *resp;
172 struct ipaccess_head *ih;
173 struct ipaccess_head_ext *ihe;
174
175 /* loop until we've received a CTRL message */
176 while (true) {
177 resp = simple_ipa_receive(sch);
178 if (!resp)
179 return NULL;
180
181 ih = (struct ipaccess_head *) resp->l1h;
182 if (ih->proto == IPAC_PROTO_OSMO)
183 resp->l2h = resp->l2h+1;
184 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
185 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
186 return resp;
187 else {
188 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
189 msgb_free(resp);
190 }
191 }
192}
193
194static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
195{
196 int rc;
197
198 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
199 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
200
Harald Welte0e9d3692018-06-04 11:52:39 +0200201 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
202 if (rc < 0) {
203 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
204 return rc;
205 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200206 fprintf(stderr, "CTRL: ERROR: short write\n");
207 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200208 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200209 } else {
210 msgb_free(msg);
211 return 0;
212 }
213}
214
215static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
216{
217 int rc;
218
219 rc = simple_ctrl_send(sch, msg);
220 if (rc < 0)
221 return NULL;
222
223 /* FIXME: ignore any TRAP */
224 /* FIXME: check string is zero-terminated */
225 return simple_ctrl_receive(sch);
226}
227
228char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
229{
230 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
231 struct msgb *resp;
232 unsigned int rx_id;
233 char *rx_var, *rx_val;
234 int rc;
235
236 if (!msg)
237 return NULL;
238
239 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
240 if (rc < 0) {
241 msgb_free(msg);
242 return NULL;
243 }
244 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200245 if (!resp)
246 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200247
248 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
249 if (rc == 3) {
250 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
251 free(rx_var);
252 msgb_free(resp);
253 return rx_val;
254 }
255 free(rx_var);
256 free(rx_val);
257 } else {
258 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
259 }
260
261 msgb_free(resp);
262 return NULL;
263}
264
265int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
266{
267 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
268 struct msgb *resp;
269 unsigned int rx_id;
270 char *rx_var, *rx_val;
271 int rc;
272
273 if (!msg)
274 return -1;
275
276 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
277 if (rc < 0) {
278 msgb_free(msg);
279 return -1;
280 }
281 resp = simple_ctrl_xceive(sch, msg);
282
283 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
284 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
285 free(rx_val);
286 free(rx_var);
287 msgb_free(resp);
288 return 0;
289 } else {
290 free(rx_val);
291 free(rx_var);
292 }
293 } else {
294 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
295 }
296
297 msgb_free(resp);
298 return -1;
299}