blob: 1d3b250a99aeb564bd2dc852f985ae6af82fc9f9 [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);
51 tout.tv_sec = tout_msec/1000;
52 tout.tv_usec = (tout_msec%1000)*1000;
53
54 rc = select(fd+1, &readset, NULL, NULL, &tout);
55 if (rc < 0)
56 return rc;
57
58 if (FD_ISSET(fd, &readset))
59 return read(fd, buf, count);
60
61 return -ETIMEDOUT;
62}
63
64static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
65{
66 struct timeval tout;
67 fd_set writeset;
68 int rc;
69
70 FD_ZERO(&writeset);
71 FD_SET(fd, &writeset);
72 tout.tv_sec = tout_msec/1000;
73 tout.tv_usec = (tout_msec%1000)*1000;
74
75 rc = select(fd+1, NULL, &writeset, NULL, &tout);
76 if (rc < 0)
77 return rc;
78
79 if (FD_ISSET(fd, &writeset))
80 return write(fd, buf, count);
81
82 return -ETIMEDOUT;
83}
84
85
86/***********************************************************************
87 * actual CTRL client API
88 ***********************************************************************/
89
Harald Welte3e5ab692018-06-04 04:26:20 +020090struct simple_ctrl_handle {
91 int fd;
92 uint32_t next_id;
Harald Welte0e9d3692018-06-04 11:52:39 +020093 uint32_t tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +020094};
95
Harald Welte0e9d3692018-06-04 11:52:39 +020096struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
97 uint32_t tout_msec)
Harald Welte3e5ab692018-06-04 04:26:20 +020098{
99 struct simple_ctrl_handle *sch;
100 int rc;
101
102 rc = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport, OSMO_SOCK_F_CONNECT);
103 if (rc < 0) {
104 fprintf(stderr, "CTRL: error connecting socket: %s\n", strerror(errno));
105 return NULL;
106 }
107
108 sch = talloc_zero(ctx, struct simple_ctrl_handle);
109 if (!sch) {
110 close(rc);
111 return NULL;
112 }
113 sch->fd = rc;
Harald Welte0e9d3692018-06-04 11:52:39 +0200114 sch->tout_msec = tout_msec;
Harald Welte3e5ab692018-06-04 04:26:20 +0200115 return sch;
116}
117
118void simple_ctrl_close(struct simple_ctrl_handle *sch)
119{
120 close(sch->fd);
121 talloc_free(sch);
122}
123
124static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
125{
126 struct ipaccess_head hh;
127 struct msgb *resp;
128 int rc, len;
129
Harald Welte0e9d3692018-06-04 11:52:39 +0200130 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
131 if (rc < 0) {
132 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
133 return NULL;
134 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200135 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
136 return NULL;
137 }
138 len = ntohs(hh.len);
139
140 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
141 if (!resp)
142 return NULL;
143 resp->l1h = msgb_put(resp, sizeof(hh));
144 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
145
146 resp->l2h = resp->tail;
147 rc = read(sch->fd, resp->l2h, len);
148 if (rc < len) {
149 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
150 msgb_free(resp);
151 return NULL;
152 }
153 msgb_put(resp, rc);
154
155 return resp;
156}
157
158static struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
159{
160 struct msgb *resp;
161 struct ipaccess_head *ih;
162 struct ipaccess_head_ext *ihe;
163
164 /* loop until we've received a CTRL message */
165 while (true) {
166 resp = simple_ipa_receive(sch);
167 if (!resp)
168 return NULL;
169
170 ih = (struct ipaccess_head *) resp->l1h;
171 if (ih->proto == IPAC_PROTO_OSMO)
172 resp->l2h = resp->l2h+1;
173 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
174 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
175 return resp;
176 else {
177 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
178 msgb_free(resp);
179 }
180 }
181}
182
183static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
184{
185 int rc;
186
187 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
188 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
189
Harald Welte0e9d3692018-06-04 11:52:39 +0200190 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
191 if (rc < 0) {
192 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
193 return rc;
194 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200195 fprintf(stderr, "CTRL: ERROR: short write\n");
196 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200197 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200198 } else {
199 msgb_free(msg);
200 return 0;
201 }
202}
203
204static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
205{
206 int rc;
207
208 rc = simple_ctrl_send(sch, msg);
209 if (rc < 0)
210 return NULL;
211
212 /* FIXME: ignore any TRAP */
213 /* FIXME: check string is zero-terminated */
214 return simple_ctrl_receive(sch);
215}
216
217char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
218{
219 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
220 struct msgb *resp;
221 unsigned int rx_id;
222 char *rx_var, *rx_val;
223 int rc;
224
225 if (!msg)
226 return NULL;
227
228 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
229 if (rc < 0) {
230 msgb_free(msg);
231 return NULL;
232 }
233 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200234 if (!resp)
235 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200236
237 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
238 if (rc == 3) {
239 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
240 free(rx_var);
241 msgb_free(resp);
242 return rx_val;
243 }
244 free(rx_var);
245 free(rx_val);
246 } else {
247 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
248 }
249
250 msgb_free(resp);
251 return NULL;
252}
253
254int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
255{
256 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
257 struct msgb *resp;
258 unsigned int rx_id;
259 char *rx_var, *rx_val;
260 int rc;
261
262 if (!msg)
263 return -1;
264
265 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
266 if (rc < 0) {
267 msgb_free(msg);
268 return -1;
269 }
270 resp = simple_ctrl_xceive(sch, msg);
271
272 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
273 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
274 free(rx_val);
275 free(rx_var);
276 msgb_free(resp);
277 return 0;
278 } else {
279 free(rx_val);
280 free(rx_var);
281 }
282 } else {
283 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
284 }
285
286 msgb_free(resp);
287 return -1;
288}