blob: 0e625cd7990409b4072665977c13d745959103e7 [file] [log] [blame]
Daniel Willmann1264cb42010-10-21 15:00:36 +02001/* SNMP-like status interface
2 *
3 * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de>
4 * (C) 2010-2011 by On-Waves
5 *
6 * All Rights Reserved
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 along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 */
23
24#include <errno.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <time.h>
29#include <unistd.h>
30
31#include <arpa/inet.h>
32
33#include <netinet/tcp.h>
34
35#include <sys/fcntl.h>
36#include <sys/ioctl.h>
37#include <sys/socket.h>
38#include <sys/types.h>
39
40#include <openbsc/control_cmd.h>
41#include <openbsc/debug.h>
42#include <openbsc/e1_input.h>
43#include <openbsc/gsm_data.h>
44#include <openbsc/ipaccess.h>
45#include <openbsc/socket.h>
46#include <openbsc/subchan_demux.h>
47
48#include <openbsc/abis_rsl.h>
49#include <openbsc/abis_nm.h>
50
51#include <osmocom/core/msgb.h>
Daniel Willmanne9f58942011-04-21 11:43:55 +020052#include <osmocom/core/rate_ctr.h>
Daniel Willmann1264cb42010-10-21 15:00:36 +020053#include <osmocom/core/select.h>
Daniel Willmanne9f58942011-04-21 11:43:55 +020054#include <osmocom/core/statistics.h>
Daniel Willmann1264cb42010-10-21 15:00:36 +020055#include <osmocom/core/talloc.h>
56
57#include <osmocom/gsm/tlv.h>
58
59#include <osmocom/vty/command.h>
60#include <osmocom/vty/vector.h>
61
62struct ctrl_handle {
63 struct osmo_fd listen_fd;
64 struct gsm_network *gsmnet;
65};
66
67vector ctrl_node_vec;
68
69int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd)
70{
71 int ret;
72 struct msgb *msg;
73
74 msg = ctrl_cmd_make(cmd);
75 if (!msg) {
76 LOGP(DINP, LOGL_ERROR, "Could not generate msg\n");
77 return -1;
78 }
79
80 ipaccess_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
81 ipaccess_prepend_header(msg, IPAC_PROTO_OSMO);
82
83 ret = osmo_wqueue_enqueue(queue, msg);
84 if (ret != 0) {
85 LOGP(DINP, LOGL_ERROR, "Failed to enqueue the command.\n");
86 msgb_free(msg);
87 }
88 return ret;
89}
90
91int ctrl_cmd_handle(struct ctrl_cmd *cmd, void *data)
92{
93 char *token, *request;
94 int num, i, j, ret, node;
95 struct gsm_network *gsmnet = data;
96
97 struct gsm_network *net = NULL;
98 struct gsm_bts *bts = NULL;
99 struct gsm_bts_trx *trx = NULL;
100 struct gsm_bts_trx_ts *ts = NULL;
101 vector vline, cmdvec, cmds_vec;
102
103 ret = CTRL_CMD_ERROR;
104 cmd->reply = "Someone forgot to fill in the reply.";
105 cmd->node = NULL;
106 node = CTRL_NODE_ROOT;
107
108 request = talloc_strdup(tall_bsc_ctx, cmd->variable);
109 if (!request)
110 goto err;
111
112 for (i=0;i<strlen(request);i++) {
113 if (request[i] == '.')
114 request[i] = ' ';
115 }
116
117 vline = cmd_make_strvec(request);
118 talloc_free(request);
119 if (!vline)
120 goto err;
121
122 for (i=0;i<vector_active(vline);i++) {
123 token = vector_slot(vline, i);
124 /* TODO: We need to make sure that the following chars are digits
125 * and/or use strtol to check if number conversion was successful
126 * Right now something like net.bts_stats will not work */
127 if (!strcmp(token, "net")) {
128 net = gsmnet;
129 if (!net)
130 break;
131 cmd->node = net;
132 node = CTRL_NODE_NET;
133 } else if (!strncmp(token, "bts", 3)) {
134 if (!net)
135 break;
136 num = atoi(&token[3]);
137 bts = gsm_bts_num(net, num);
138 if (!bts)
139 break;
140 cmd->node = bts;
141 node = CTRL_NODE_BTS;
142 } else if (!strncmp(token, "trx", 3)) {
143 if (!bts)
144 break;
145 num = atoi(&token[3]);
146 trx = gsm_bts_trx_num(bts, num);
147 if (!trx)
148 break;
149 cmd->node = trx;
150 node = CTRL_NODE_TRX;
151 } else if (!strncmp(token, "ts", 2)) {
152 if (!trx)
153 break;
154 num = atoi(&token[2]);
155 if ((num >= 0) && (num < TRX_NR_TS))
156 ts = &trx->ts[num];
157 if (!ts)
158 break;
159 cmd->node = ts;
160 node = CTRL_NODE_TS;
161 } else {
162 /* If we're here the rest must be the command */
163 cmdvec = vector_init(vector_active(vline)-i);
164 for (j=i; j<vector_active(vline); j++) {
165 vector_set(cmdvec, vector_slot(vline, j));
166 }
167
168 /* Get the command vector of the right node */
169 cmds_vec = vector_lookup(ctrl_node_vec, node);
170
171 if (!cmds_vec) {
172 cmd->reply = "Command not found";
173 vector_free(cmdvec);
174 break;
175 }
176
177 ret = ctrl_cmd_exec(cmdvec, cmd, cmds_vec, data);
178
179 vector_free(cmdvec);
180 break;
181 }
182 }
183
184 cmd_free_strvec(vline);
185
186err:
187 if (ret == CTRL_CMD_ERROR)
188 cmd->type = CTRL_TYPE_ERROR;
189 return ret;
190}
191
192static void control_close_conn(struct ctrl_connection *ccon)
193{
194 close(ccon->write_queue.bfd.fd);
195 osmo_fd_unregister(&ccon->write_queue.bfd);
196 if (ccon->closed_cb)
197 ccon->closed_cb(ccon);
198 talloc_free(ccon);
199}
200
201static int handle_control_read(struct osmo_fd * bfd)
202{
203 int ret = -1, error;
204 struct osmo_wqueue *queue;
205 struct ctrl_connection *ccon;
206 struct ipaccess_head *iph;
207 struct ipaccess_head_ext *iph_ext;
208 struct msgb *msg;
209 struct ctrl_cmd *cmd;
210 struct ctrl_handle *ctrl = bfd->data;
211
212 queue = container_of(bfd, struct osmo_wqueue, bfd);
213 ccon = container_of(queue, struct ctrl_connection, write_queue);
214
215 msg = ipaccess_read_msg(bfd, &error);
216
217 if (!msg) {
218 if (error == 0)
219 LOGP(DINP, LOGL_INFO, "The control connection was closed\n");
220 else
221 LOGP(DINP, LOGL_ERROR, "Failed to parse ip access message: %d\n", error);
222
223 goto err;
224 }
225
226 if (msg->len < sizeof(*iph) + sizeof(*iph_ext)) {
227 LOGP(DINP, LOGL_ERROR, "The message is too short.\n");
228 goto err;
229 }
230
231 iph = (struct ipaccess_head *) msg->data;
232 if (iph->proto != IPAC_PROTO_OSMO) {
233 LOGP(DINP, LOGL_ERROR, "Protocol mismatch. We got 0x%x\n", iph->proto);
234 goto err;
235 }
236
237 iph_ext = (struct ipaccess_head_ext *) iph->data;
238 if (iph_ext->proto != IPAC_PROTO_EXT_CTRL) {
239 LOGP(DINP, LOGL_ERROR, "Extended protocol mismatch. We got 0x%x\n", iph_ext->proto);
240 goto err;
241 }
242
243 msg->l2h = iph_ext->data;
244
245 cmd = ctrl_cmd_parse(ccon, msg);
246
247 if (cmd) {
248 cmd->ccon = ccon;
249 if (ctrl_cmd_handle(cmd, ctrl->gsmnet) != CTRL_CMD_HANDLED) {
250 ctrl_cmd_send(queue, cmd);
251 talloc_free(cmd);
252 }
253 } else {
254 cmd = talloc_zero(ccon, struct ctrl_cmd);
255 if (!cmd)
256 goto err;
257 LOGP(DINP, LOGL_ERROR, "Command parser error.\n");
258 cmd->type = CTRL_TYPE_ERROR;
259 cmd->id = "err";
260 cmd->reply = "Command parser error.";
261 ctrl_cmd_send(queue, cmd);
262 talloc_free(cmd);
263 }
264
265 msgb_free(msg);
266 return 0;
267
268err:
269 control_close_conn(ccon);
270 msgb_free(msg);
271 return ret;
272}
273
274static int control_write_cb(struct osmo_fd *bfd, struct msgb *msg)
275{
276 int rc;
277
278 rc = write(bfd->fd, msg->data, msg->len);
279 if (rc != msg->len)
280 LOGP(DINP, LOGL_ERROR, "Failed to write message to the control connection.\n");
281
282 return rc;
283}
284
285static struct ctrl_connection *ctrl_connection_alloc(void *ctx)
286{
287 struct ctrl_connection *ccon = talloc_zero(ctx, struct ctrl_connection);
288 if (!ccon)
289 return NULL;
290
291 osmo_wqueue_init(&ccon->write_queue, 100);
292 /* Error handling here? */
293
294 INIT_LLIST_HEAD(&ccon->cmds);
295 return ccon;
296}
297
298static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what)
299{
300 int ret, fd, on;
301 struct ctrl_connection *ccon;
302 struct sockaddr_in sa;
303 socklen_t sa_len = sizeof(sa);
304
305
306 if (!(what & BSC_FD_READ))
307 return 0;
308
309 fd = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
310 if (fd < 0) {
311 perror("accept");
312 return fd;
313 }
314 LOGP(DINP, LOGL_INFO, "accept()ed new control connection from %s\n",
315 inet_ntoa(sa.sin_addr));
316
317 on = 1;
318 ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
319 if (ret != 0) {
320 LOGP(DNAT, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno));
321 close(fd);
322 return ret;
323 }
324 ccon = ctrl_connection_alloc(listen_bfd->data);
325 if (!ccon) {
326 LOGP(DINP, LOGL_ERROR, "Failed to allocate.\n");
327 close(fd);
328 return -1;
329 }
330
331 ccon->write_queue.bfd.data = listen_bfd->data;
332 ccon->write_queue.bfd.fd = fd;
333 ccon->write_queue.bfd.when = BSC_FD_READ;
334 ccon->write_queue.read_cb = handle_control_read;
335 ccon->write_queue.write_cb = control_write_cb;
336
337 ret = osmo_fd_register(&ccon->write_queue.bfd);
338 if (ret < 0) {
339 LOGP(DINP, LOGL_ERROR, "Could not register FD.\n");
340 close(ccon->write_queue.bfd.fd);
341 talloc_free(ccon);
342 }
343
344 return ret;
345}
346
Daniel Willmanne9f58942011-04-21 11:43:55 +0200347static uint64_t get_rate_ctr_value(const struct rate_ctr *ctr, int intv)
348{
349 if (intv >= RATE_CTR_INTV_NUM)
350 return 0;
351
352 /* Absolute value */
353 if (intv == -1) {
354 return ctr->current;
355 } else {
356 return ctr->intv[intv].rate;
357 }
358}
359
360static char *get_all_rate_ctr_in_group(const struct rate_ctr_group *ctrg, int intv)
361{
362 int i;
363 char *counters = talloc_strdup(tall_bsc_ctx, "");
364 if (!counters)
365 return NULL;
366
367 for (i=0;i<ctrg->desc->num_ctr;i++) {
368 counters = talloc_asprintf_append(counters, "\n%s.%u.%s %lu",
369 ctrg->desc->group_name_prefix, ctrg->idx,
370 ctrg->desc->ctr_desc[i].name,
371 get_rate_ctr_value(&ctrg->ctr[i], intv));
372 if (!counters)
373 return NULL;
374 }
375 return counters;
376}
377
378static int get_rate_ctr_group(const char *ctr_group, int intv, struct ctrl_cmd *cmd)
379{
380 int i;
381 char *counters;
382 struct rate_ctr_group *ctrg;
383
384 cmd->reply = talloc_asprintf(cmd, "All counters in group %s", ctr_group);
385 if (!cmd->reply)
386 goto oom;
387
388 for (i=0;;i++) {
389 ctrg = rate_ctr_get_group_by_name_idx(ctr_group, i);
390 if (!ctrg)
391 break;
392
393 counters = get_all_rate_ctr_in_group(ctrg, intv);
394 if (!counters)
395 goto oom;
396
397 cmd->reply = talloc_asprintf_append(cmd->reply, "%s", counters);
398 talloc_free(counters);
399 if (!cmd->reply)
400 goto oom;
401 }
402
403 /* We found no counter group by that name */
404 if (i == 0) {
405 cmd->reply = talloc_asprintf(cmd, "No counter group with name %s.", ctr_group);
406 return CTRL_CMD_ERROR;
407 }
408
409 return CTRL_CMD_REPLY;
410oom:
411 cmd->reply = "OOM.";
412 return CTRL_CMD_ERROR;
413}
414
415static int get_rate_ctr_group_idx(const struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd)
416{
417 char *counters;
418
419 counters = get_all_rate_ctr_in_group(ctrg, intv);
420 if (!counters)
421 goto oom;
422
423 cmd->reply = talloc_asprintf(cmd, "All counters in %s.%u%s",
424 ctrg->desc->group_name_prefix, ctrg->idx, counters);
425 talloc_free(counters);
426 if (!cmd->reply)
427 goto oom;
428
429 return CTRL_CMD_REPLY;
430oom:
431 cmd->reply = "OOM.";
432 return CTRL_CMD_ERROR;
433}
434
435/* rate_ctr */
436CTRL_CMD_DEFINE(rate_ctr, "rate_ctr *");
437int get_rate_ctr(struct ctrl_cmd *cmd, void *data)
438{
439 int intv;
440 unsigned int idx;
441 char *ctr_group, *ctr_idx, *ctr_name, *tmp, *dup, *saveptr, *interval;
442 struct rate_ctr_group *ctrg;
443 const struct rate_ctr *ctr;
444
445 dup = talloc_strdup(cmd, cmd->variable);
446 if (!dup)
447 goto oom;
448
449 /* Skip over possible prefixes (net.) */
450 tmp = strstr(dup, "rate_ctr");
451 if (!tmp) {
452 talloc_free(dup);
453 cmd->reply = "rate_ctr not a token in rate_ctr command!";
454 goto err;
455 }
456
457 strtok_r(tmp, ".", &saveptr);
458 interval = strtok_r(NULL, ".", &saveptr);
459 if (!interval) {
460 talloc_free(dup);
461 cmd->reply = "Missing interval.";
462 goto err;
463 }
464
465 if (!strcmp(interval, "abs")) {
466 intv = -1;
467 } else if (!strcmp(interval, "per_sec")) {
468 intv = RATE_CTR_INTV_SEC;
469 } else if (!strcmp(interval, "per_min")) {
470 intv = RATE_CTR_INTV_MIN;
471 } else if (!strcmp(interval, "per_hour")) {
472 intv = RATE_CTR_INTV_HOUR;
473 } else if (!strcmp(interval, "per_day")) {
474 intv = RATE_CTR_INTV_DAY;
475 } else {
476 talloc_free(dup);
477 cmd->reply = "Wrong interval.";
478 goto err;
479 }
480
481 ctr_group = strtok_r(NULL, ".", &saveptr);
482 tmp = strtok_r(NULL, ".", &saveptr);
483 if (!ctr_group || !tmp) {
484 talloc_free(dup);
485 cmd->reply = "Counter group must be of form a.b";
486 goto err;
487 }
488 ctr_group[strlen(ctr_group)] = '.';
489
490 ctr_idx = strtok_r(NULL, ".", &saveptr);
491 if (!ctr_idx) {
492 talloc_free(dup);
493 return get_rate_ctr_group(ctr_group, intv, cmd);
494 }
495 idx = atoi(ctr_idx);
496
497 ctrg = rate_ctr_get_group_by_name_idx(ctr_group, idx);
498 if (!ctrg) {
499 talloc_free(dup);
500 cmd->reply = "Counter group not found.";
501 goto err;
502 }
503
504 ctr_name = strtok_r(NULL, "\0", &saveptr);
505 if (!ctr_name) {
506 talloc_free(dup);
507 return get_rate_ctr_group_idx(ctrg, intv, cmd);
508 }
509
510 ctr = rate_ctr_get_by_name(ctrg, ctr_name);
511 if (!ctr) {
512 cmd->reply = "Counter name not found.";
513 talloc_free(dup);
514 goto err;
515 }
516
517 talloc_free(dup);
518
519 cmd->reply = talloc_asprintf(cmd, "%lu", get_rate_ctr_value(ctr, intv));
520 if (!cmd->reply)
521 goto oom;
522
523 return CTRL_CMD_REPLY;
524oom:
525 cmd->reply = "OOM";
526err:
527 return CTRL_CMD_ERROR;
528}
529
530int set_rate_ctr(struct ctrl_cmd *cmd, void *data)
531{
532 cmd->reply = "Can't set rate counter.";
533
534 return CTRL_CMD_ERROR;
535}
536
537int verify_rate_ctr(struct ctrl_cmd *cmd, const char *value, void *data)
538{
539 return 0;
540}
541
542/* counter */
543CTRL_CMD_DEFINE(counter, "counter *");
544int get_counter(struct ctrl_cmd *cmd, void *data)
545{
546 char *ctr_name, *tmp, *dup, *saveptr;
547 struct osmo_counter *counter;
548
549 cmd->reply = "OOM";
550 dup = talloc_strdup(cmd, cmd->variable);
551 if (!dup)
552 goto err;
553
554
555 tmp = strstr(dup, "counter");
556 if (!tmp) {
557 talloc_free(dup);
558 goto err;
559 }
560
561 strtok_r(tmp, ".", &saveptr);
562 ctr_name = strtok_r(NULL, "\0", &saveptr);
563
564 if (!ctr_name)
565 goto err;
566
567 counter = osmo_counter_get_by_name(ctr_name);
568 if (!counter) {
569 cmd->reply = "Counter name not found.";
570 talloc_free(dup);
571 goto err;
572 }
573
574 talloc_free(dup);
575
576 cmd->reply = talloc_asprintf(cmd, "%lu", counter->value);
577 if (!cmd->reply) {
578 cmd->reply = "OOM";
579 goto err;
580 }
581
582 return CTRL_CMD_REPLY;
583err:
584 return CTRL_CMD_ERROR;
585}
586
587int set_counter(struct ctrl_cmd *cmd, void *data)
588{
589
590 cmd->reply = "Can't set counter.";
591
592 return CTRL_CMD_ERROR;
593}
594
595int verify_counter(struct ctrl_cmd *cmd, const char *value, void *data)
596{
597 return 0;
598}
599
Daniel Willmann1264cb42010-10-21 15:00:36 +0200600int controlif_setup(struct gsm_network *gsmnet, uint16_t port)
601{
602 int ret;
603 struct ctrl_handle *ctrl;
604
605 ctrl = talloc_zero(tall_bsc_ctx, struct ctrl_handle);
606 if (!ctrl)
607 return -ENOMEM;
608
609 ctrl->gsmnet = gsmnet;
610
611 ctrl_node_vec = vector_init(5);
612 if (!ctrl_node_vec)
613 return -ENOMEM;
614
615 /* Listen for control connections */
616 ret = make_sock(&ctrl->listen_fd, IPPROTO_TCP, 0, port,
617 0, listen_fd_cb, ctrl);
618 if (ret < 0) {
619 talloc_free(ctrl);
620 return ret;
621 }
622
Daniel Willmanne9f58942011-04-21 11:43:55 +0200623 ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rate_ctr);
624 ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_counter);
625
Daniel Willmann1264cb42010-10-21 15:00:36 +0200626 return ret;
627}