mirror of https://gerrit.osmocom.org/osmo-sysmon
338 lines
7.7 KiB
C
338 lines
7.7 KiB
C
/* Simple, blocking client API against the Osmocom CTRL interface */
|
|
|
|
/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
|
|
* All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include <talloc.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <osmocom/core/msgb.h>
|
|
#include <osmocom/core/socket.h>
|
|
#include <osmocom/gsm/ipa.h>
|
|
#include <osmocom/gsm/protocol/ipaccess.h>
|
|
|
|
#include "client.h"
|
|
#include "simple_ctrl.h"
|
|
|
|
#define CTRL_ERR(sch, fmt, args...) \
|
|
fprintf(stderr, "CTRL %s error: " fmt, make_authority(sch, &sch->cfg), ##args)
|
|
|
|
/***********************************************************************
|
|
* blocking I/O with timeout helpers
|
|
***********************************************************************/
|
|
|
|
static struct timeval *timeval_from_msec(uint32_t tout_msec)
|
|
{
|
|
static struct timeval tout;
|
|
|
|
if (tout_msec == 0)
|
|
return NULL;
|
|
tout.tv_sec = tout_msec/1000;
|
|
tout.tv_usec = (tout_msec%1000)*1000;
|
|
|
|
return &tout;
|
|
}
|
|
|
|
static ssize_t read_timeout(int fd, void *buf, size_t count, uint32_t tout_msec)
|
|
{
|
|
fd_set readset;
|
|
int rc;
|
|
|
|
FD_ZERO(&readset);
|
|
FD_SET(fd, &readset);
|
|
|
|
rc = select(fd+1, &readset, NULL, NULL, timeval_from_msec(tout_msec));
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (FD_ISSET(fd, &readset))
|
|
return read(fd, buf, count);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static ssize_t write_timeout(int fd, const void *buf, size_t count, uint32_t tout_msec)
|
|
{
|
|
fd_set writeset;
|
|
int rc;
|
|
|
|
FD_ZERO(&writeset);
|
|
FD_SET(fd, &writeset);
|
|
|
|
rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (FD_ISSET(fd, &writeset))
|
|
return write(fd, buf, count);
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* actual CTRL client API
|
|
***********************************************************************/
|
|
|
|
struct simple_ctrl_handle {
|
|
int fd;
|
|
uint32_t next_id;
|
|
uint32_t tout_msec;
|
|
struct host_cfg cfg;
|
|
};
|
|
|
|
struct simple_ctrl_handle *simple_ctrl_open(void *ctx, const char *host, uint16_t dport,
|
|
uint32_t tout_msec)
|
|
{
|
|
struct simple_ctrl_handle *sch = talloc_zero(ctx, struct simple_ctrl_handle);
|
|
fd_set writeset;
|
|
int off = 0;
|
|
int rc, fd;
|
|
|
|
if (!sch)
|
|
return NULL;
|
|
|
|
sch->cfg.name = talloc_strdup(sch, "simple-ctrl");
|
|
sch->cfg.remote_host = talloc_strdup(sch, host);
|
|
sch->cfg.remote_port = dport;
|
|
|
|
fd = osmo_sock_init(AF_INET, SOCK_STREAM, IPPROTO_TCP, host, dport,
|
|
OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_NONBLOCK);
|
|
if (fd < 0) {
|
|
CTRL_ERR(sch, "connecting socket: %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
/* wait until connect (or timeout) happens */
|
|
FD_ZERO(&writeset);
|
|
FD_SET(fd, &writeset);
|
|
rc = select(fd+1, NULL, &writeset, NULL, timeval_from_msec(tout_msec));
|
|
if (rc == 0) {
|
|
CTRL_ERR(sch, "timeout during connect\n");
|
|
goto out_close;
|
|
}
|
|
if (rc < 0) {
|
|
CTRL_ERR(sch, "error connecting socket: %s\n", strerror(errno));
|
|
goto out_close;
|
|
}
|
|
|
|
/* set FD blocking again */
|
|
if (ioctl(fd, FIONBIO, (unsigned char *)&off) < 0) {
|
|
CTRL_ERR(sch, "cannot set socket blocking: %s\n", strerror(errno));
|
|
goto out_close;
|
|
}
|
|
|
|
sch->fd = fd;
|
|
sch->tout_msec = tout_msec;
|
|
return sch;
|
|
|
|
out_close:
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
void simple_ctrl_set_timeout(struct simple_ctrl_handle *sch, uint32_t tout_msec)
|
|
{
|
|
sch->tout_msec = tout_msec;
|
|
}
|
|
|
|
void simple_ctrl_close(struct simple_ctrl_handle *sch)
|
|
{
|
|
close(sch->fd);
|
|
talloc_free(sch);
|
|
}
|
|
|
|
static struct msgb *simple_ipa_receive(struct simple_ctrl_handle *sch)
|
|
{
|
|
struct ipaccess_head hh;
|
|
struct msgb *resp;
|
|
int rc, len;
|
|
|
|
rc = read_timeout(sch->fd, (uint8_t *) &hh, sizeof(hh), sch->tout_msec);
|
|
if (rc < 0) {
|
|
CTRL_ERR(sch, "read(): %d\n", rc);
|
|
return NULL;
|
|
} else if (rc < sizeof(hh)) {
|
|
CTRL_ERR(sch, "short read (header)\n");
|
|
return NULL;
|
|
}
|
|
len = ntohs(hh.len);
|
|
|
|
resp = msgb_alloc(len+sizeof(hh)+1, "CTRL Rx");
|
|
if (!resp)
|
|
return NULL;
|
|
resp->l1h = msgb_put(resp, sizeof(hh));
|
|
memcpy(resp->l1h, (uint8_t *) &hh, sizeof(hh));
|
|
|
|
resp->l2h = resp->tail;
|
|
rc = read(sch->fd, resp->l2h, len);
|
|
if (rc < len) {
|
|
CTRL_ERR(sch, "short read (payload)\n");
|
|
msgb_free(resp);
|
|
return NULL;
|
|
}
|
|
msgb_put(resp, rc);
|
|
|
|
return resp;
|
|
}
|
|
|
|
struct msgb *simple_ctrl_receive(struct simple_ctrl_handle *sch)
|
|
{
|
|
struct msgb *resp;
|
|
struct ipaccess_head *ih;
|
|
struct ipaccess_head_ext *ihe;
|
|
unsigned char *tmp;
|
|
|
|
/* loop until we've received a CTRL message */
|
|
while (true) {
|
|
resp = simple_ipa_receive(sch);
|
|
if (!resp)
|
|
return NULL;
|
|
|
|
ih = (struct ipaccess_head *) resp->l1h;
|
|
if (ih->proto == IPAC_PROTO_OSMO)
|
|
resp->l2h = resp->l2h+1;
|
|
ihe = (struct ipaccess_head_ext*) (resp->l1h + sizeof(*ih));
|
|
if (ih->proto == IPAC_PROTO_OSMO && ihe->proto == IPAC_PROTO_EXT_CTRL) {
|
|
/* Ensure data is NULL terminated */
|
|
tmp = msgb_put(resp, 1);
|
|
*tmp = '\0';
|
|
return resp;
|
|
} else {
|
|
CTRL_ERR(sch, "unknown IPA message %s\n", msgb_hexdump(resp));
|
|
msgb_free(resp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int simple_ctrl_send(struct simple_ctrl_handle *sch, struct msgb *msg)
|
|
{
|
|
int rc;
|
|
|
|
ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
|
|
ipa_prepend_header(msg, IPAC_PROTO_OSMO);
|
|
|
|
rc = write_timeout(sch->fd, msg->data, msg->len, sch->tout_msec);
|
|
if (rc < 0) {
|
|
CTRL_ERR(sch, "write(): %d\n", rc);
|
|
return rc;
|
|
} else if (rc < msg->len) {
|
|
CTRL_ERR(sch, "short write\n");
|
|
msgb_free(msg);
|
|
return -1;
|
|
} else {
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static struct msgb *simple_ctrl_xceive(struct simple_ctrl_handle *sch, struct msgb *msg)
|
|
{
|
|
int rc;
|
|
|
|
rc = simple_ctrl_send(sch, msg);
|
|
if (rc < 0)
|
|
return NULL;
|
|
|
|
/* FIXME: ignore any TRAP */
|
|
return simple_ctrl_receive(sch);
|
|
}
|
|
|
|
char *simple_ctrl_get(struct simple_ctrl_handle *sch, const char *var)
|
|
{
|
|
struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL GET");
|
|
struct msgb *resp;
|
|
unsigned int rx_id;
|
|
char *rx_var, *rx_val;
|
|
int rc;
|
|
|
|
if (!msg)
|
|
return NULL;
|
|
|
|
rc = msgb_printf(msg, "GET %u %s", sch->next_id++, var);
|
|
if (rc < 0) {
|
|
msgb_free(msg);
|
|
return NULL;
|
|
}
|
|
resp = simple_ctrl_xceive(sch, msg);
|
|
if (!resp)
|
|
return NULL;
|
|
|
|
rc = sscanf(msgb_l2(resp), "GET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val);
|
|
if ((rc == 2) || (rc == 3)) {
|
|
/* If body is empty return an empty string */
|
|
if (rc == 2)
|
|
rx_val = strdup("");
|
|
|
|
if (rx_id == sch->next_id-1 && !strcmp(var, rx_var)) {
|
|
free(rx_var);
|
|
msgb_free(resp);
|
|
return rx_val;
|
|
}
|
|
free(rx_var);
|
|
free(rx_val);
|
|
} else {
|
|
CTRL_ERR(sch, "GET(%s) results in '%s'\n", var, (char *)msgb_l2(resp));
|
|
}
|
|
|
|
msgb_free(resp);
|
|
return NULL;
|
|
}
|
|
|
|
int simple_ctrl_set(struct simple_ctrl_handle *sch, const char *var, const char *val)
|
|
{
|
|
struct msgb *msg = msgb_alloc_headroom(512+8, 8, "CTRL SET");
|
|
struct msgb *resp;
|
|
unsigned int rx_id;
|
|
char *rx_var, *rx_val;
|
|
int rc;
|
|
|
|
if (!msg)
|
|
return -1;
|
|
|
|
rc = msgb_printf(msg, "SET %u %s %s", sch->next_id++, var, val);
|
|
if (rc < 0) {
|
|
msgb_free(msg);
|
|
return -1;
|
|
}
|
|
resp = simple_ctrl_xceive(sch, msg);
|
|
if (!resp)
|
|
return -1;
|
|
|
|
if (sscanf(msgb_l2(resp), "SET_REPLY %u %ms %ms", &rx_id, &rx_var, &rx_val) == 3) {
|
|
if (rx_id == sch->next_id-1 && !strcmp(var, rx_var) && !strcmp(val, rx_val)) {
|
|
free(rx_val);
|
|
free(rx_var);
|
|
msgb_free(resp);
|
|
return 0;
|
|
} else {
|
|
free(rx_val);
|
|
free(rx_var);
|
|
}
|
|
} else {
|
|
CTRL_ERR(sch, "SET(%s=%s) results in '%s'\n", var, val, (char *) msgb_l2(resp));
|
|
}
|
|
|
|
msgb_free(resp);
|
|
return -1;
|
|
}
|