blob: 229078542043120d3cd265cd5bccf2e44af2446b [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
122void simple_ctrl_close(struct simple_ctrl_handle *sch)
123{
124 close(sch->fd);
125 talloc_free(sch);
126}
127
128static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
129{
130 struct ipaccess_head hh;
131 struct msgb *resp;
132 int rc, len;
133
Harald Welte0e9d3692018-06-04 11:52:39 +0200134 rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
135 if (rc < 0) {
136 fprintf(stderr, "CTRL: Error during read: %d\n", rc);
137 return NULL;
138 } else if (rc < sizeof(hh)) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200139 fprintf(stderr, "CTRL: ERROR: short read (header)\n");
140 return NULL;
141 }
142 len = ntohs(hh.len);
143
144 resp = msgb_alloc(len+sizeof(hh), "CTRL Rx");
145 if (!resp)
146 return NULL;
147 resp->l1h = msgb_put(resp, sizeof(hh));
148 memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
149
150 resp->l2h = resp->tail;
151 rc = read(sch->fd, resp->l2h, len);
152 if (rc < len) {
153 fprintf(stderr, "CTRL: ERROR: short read (payload)\n");
154 msgb_free(resp);
155 return NULL;
156 }
157 msgb_put(resp, rc);
158
159 return resp;
160}
161
162static struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
163{
164 struct msgb *resp;
165 struct ipaccess_head *ih;
166 struct ipaccess_head_ext *ihe;
167
168 /* loop until we've received a CTRL message */
169 while (true) {
170 resp = simple_ipa_receive(sch);
171 if (!resp)
172 return NULL;
173
174 ih = (struct ipaccess_head *) resp->l1h;
175 if (ih->proto == IPAC_PROTO_OSMO)
176 resp->l2h = resp->l2h+1;
177 ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
178 if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL)
179 return resp;
180 else {
181 fprintf(stderr, "unknown IPA message %s\n", msgb_hexdump(resp));
182 msgb_free(resp);
183 }
184 }
185}
186
187static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
188{
189 int rc;
190
191 ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
192 ipa_prepend_header(msg, IPAC_PROTO_OSMO);
193
Harald Welte0e9d3692018-06-04 11:52:39 +0200194 rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
195 if (rc < 0) {
196 fprintf(stderr, "CTRL: Error during write: %d\n", rc);
197 return rc;
198 } else if (rc < msg->len) {
Harald Welte3e5ab692018-06-04 04:26:20 +0200199 fprintf(stderr, "CTRL: ERROR: short write\n");
200 msgb_free(msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200201 return -1;
Harald Welte3e5ab692018-06-04 04:26:20 +0200202 } else {
203 msgb_free(msg);
204 return 0;
205 }
206}
207
208static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
209{
210 int rc;
211
212 rc = simple_ctrl_send(sch, msg);
213 if (rc < 0)
214 return NULL;
215
216 /* FIXME: ignore any TRAP */
217 /* FIXME: check string is zero-terminated */
218 return simple_ctrl_receive(sch);
219}
220
221char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
222{
223 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
224 struct msgb *resp;
225 unsigned int rx_id;
226 char *rx_var, *rx_val;
227 int rc;
228
229 if (!msg)
230 return NULL;
231
232 rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
233 if (rc < 0) {
234 msgb_free(msg);
235 return NULL;
236 }
237 resp = simple_ctrl_xceive(sch, msg);
Harald Welte0e9d3692018-06-04 11:52:39 +0200238 if (!resp)
239 return NULL;
Harald Welte3e5ab692018-06-04 04:26:20 +0200240
241 rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
242 if (rc == 3) {
243 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
244 free(rx_var);
245 msgb_free(resp);
246 return rx_val;
247 }
248 free(rx_var);
249 free(rx_val);
250 } else {
251 fprintf(stderr, "CTRL: ERROR: GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
252 }
253
254 msgb_free(resp);
255 return NULL;
256}
257
258int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
259{
260 struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
261 struct msgb *resp;
262 unsigned int rx_id;
263 char *rx_var, *rx_val;
264 int rc;
265
266 if (!msg)
267 return -1;
268
269 rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
270 if (rc < 0) {
271 msgb_free(msg);
272 return -1;
273 }
274 resp = simple_ctrl_xceive(sch, msg);
275
276 if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
277 if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
278 free(rx_val);
279 free(rx_var);
280 msgb_free(resp);
281 return 0;
282 } else {
283 free(rx_val);
284 free(rx_var);
285 }
286 } else {
287 fprintf(stderr, "CTRL: ERROR: SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
288 }
289
290 msgb_free(resp);
291 return -1;
292}