blob: 83b664565c4fdfd7156982e0d5cf5c4ff53a5a90 [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
43static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
44{
45 struct timeval tout;
46 fd_set readset;
47 int rc;
48
49 FD_ZERO(&readset);
50 FD_SET(fd, &readset);
Harald Welte546573a2018-06-04 12:04:12 +020051 if (tout_msec) {
52 tout.tv_sec = tout_msec/1000;
53 tout.tv_usec = (tout_msec%1000)*1000;
54 }
Harald Welte0e9d3692018-06-04 11:52:39 +020055
Harald Welte546573a2018-06-04 12:04:12 +020056 rc = select(fd+1, &readset, NULL, NULL, tout_msec ? &tout : NULL);
Harald Welte0e9d3692018-06-04 11:52:39 +020057 if (rc < 0)
58 return rc;
59
60 if (FD_ISSET(fd, &readset))
61 return read(fd, buf, count);
62
63 return -ETIMEDOUT;
64}
65
66static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
67{
68 struct timeval tout;
69 fd_set writeset;
70 int rc;
71
72 FD_ZERO(&writeset);
73 FD_SET(fd, &writeset);
Harald Welte546573a2018-06-04 12:04:12 +020074 if (tout_msec) {
75 tout.tv_sec = tout_msec/1000;
76 tout.tv_usec = (tout_msec%1000)*1000;
77 }
Harald Welte0e9d3692018-06-04 11:52:39 +020078
Harald Welte546573a2018-06-04 12:04:12 +020079 rc = select(fd+1, NULL, &writeset, NULL, tout_msec ? &tout : NULL);
Harald Welte0e9d3692018-06-04 11:52:39 +020080 if (rc < 0)
81 return rc;
82
83 if (FD_ISSET(fd, &writeset))
84 return write(fd, buf, count);
85
86 return -ETIMEDOUT;
87}
88
89
90/***********************************************************************
91 * actual CTRL client API
92 ***********************************************************************/
93
Harald Welte3e5ab692018-06-04 04:26:20 +020094struct simple_ctrl_handle {
95 int fd;
96 uint32_t next_id;
Harald Welte0e9d3692018-06-04 11:52:39 +020097 uint32_t tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +020098};
99
Harald Welte0e9d3692018-06-04 11:52:39 +0200100struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
101 uint32_t tout_msec)
Harald Welte3e5ab692018-06-04 04:26:20 +0200102{
103 struct simple_ctrl_handle *sch;
104 int rc;
105
106 rc = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport, OSMO_SOCK_F_CONNECT);
107 if (rc < 0) {
108 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
109 return NULL;
110 }
111
112 sch = talloc_zero(ctx, struct simple_ctrl_handle);
113 if (!sch) {
114 close(rc);
115 return NULL;
116 }
117 sch->fd = rc;
Harald Welte0e9d3692018-06-04 11:52:39 +0200118 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200119 return sch;
120}
121
Harald Welte7c940802018-06-04 12:04:38 +0200122void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
123{
124 sch->tout_msec = tout_msec;
125}
126
Harald Welte3e5ab692018-06-04 04:26:20 +0200127void simple_ctrl_close(struct simple_ctrl_handle *sch)
128{
129 close(sch->fd);
130 talloc_free(sch);
131}
132
133static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
134{
135 struct ipaccess_head hh;
136 struct msgb *resp;
137 int rc, len;
138
Harald Welte0e9d3692018-06-04 11:52:39 +0200139 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
140 if (rc < 0) {
141 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
142 return NULL;
143 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200144 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
145 return NULL;
146 }
147 len = ntohs(hh.len);
148
149 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
150 if (!resp)
151 return NULL;
152 resp->l1h = msgb_put(resp, sizeof(hh));
153 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
154
155 resp->l2h = resp->tail;
156 rc = read(sch->fd, resp->l2h, len);
157 if (rc < len) {
158 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
159 msgb_free(resp);
160 return NULL;
161 }
162 msgb_put(resp, rc);
163
164 return resp;
165}
166
Harald Welte7c940802018-06-04 12:04:38 +0200167struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
Harald Welte3e5ab692018-06-04 04:26:20 +0200168{
169 struct msgb *resp;
170 struct ipaccess_head *ih;
171 struct ipaccess_head_ext *ihe;
172
173 /* loop until we've received a CTRL message */
174 while (true) {
175 resp = simple_ipa_receive(sch);
176 if (!resp)
177 return NULL;
178
179 ih = (struct ipaccess_head *) resp->l1h;
180 if (ih->proto == IPAC_PROTO_OSMO)
181 resp->l2h = resp->l2h+1;
182 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
183 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
184 return resp;
185 else {
186 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
187 msgb_free(resp);
188 }
189 }
190}
191
192static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
193{
194 int rc;
195
196 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
197 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
198
Harald Welte0e9d3692018-06-04 11:52:39 +0200199 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
200 if (rc < 0) {
201 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
202 return rc;
203 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200204 fprintf(stderr, "CTRL: ERROR: short write\n");
205 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200206 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200207 } else {
208 msgb_free(msg);
209 return 0;
210 }
211}
212
213static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
214{
215 int rc;
216
217 rc = simple_ctrl_send(sch, msg);
218 if (rc < 0)
219 return NULL;
220
221 /* FIXME: ignore any TRAP */
222 /* FIXME: check string is zero-terminated */
223 return simple_ctrl_receive(sch);
224}
225
226char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
227{
228 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
229 struct msgb *resp;
230 unsigned int rx_id;
231 char *rx_var, *rx_val;
232 int rc;
233
234 if (!msg)
235 return NULL;
236
237 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
238 if (rc < 0) {
239 msgb_free(msg);
240 return NULL;
241 }
242 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200243 if (!resp)
244 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200245
246 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
247 if (rc == 3) {
248 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
249 free(rx_var);
250 msgb_free(resp);
251 return rx_val;
252 }
253 free(rx_var);
254 free(rx_val);
255 } else {
256 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
257 }
258
259 msgb_free(resp);
260 return NULL;
261}
262
263int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
264{
265 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
266 struct msgb *resp;
267 unsigned int rx_id;
268 char *rx_var, *rx_val;
269 int rc;
270
271 if (!msg)
272 return -1;
273
274 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
275 if (rc < 0) {
276 msgb_free(msg);
277 return -1;
278 }
279 resp = simple_ctrl_xceive(sch, msg);
280
281 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
282 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
283 free(rx_val);
284 free(rx_var);
285 msgb_free(resp);
286 return 0;
287 } else {
288 free(rx_val);
289 free(rx_var);
290 }
291 } else {
292 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
293 }
294
295 msgb_free(resp);
296 return -1;
297}