/* admin.c - OpenNHRP administrative interface implementation * * Copyright (C) 2007-2009 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 or later as * published by the Free Software Foundation. * * See http://www.gnu.org/ for details. */ #include #include #include #include #include #include #include #include #include #include #include "nhrp_common.h" #include "nhrp_peer.h" #include "nhrp_address.h" #include "nhrp_interface.h" static struct ev_io accept_io; struct admin_remote { struct ev_timer timeout; struct ev_io io; int num_read; char cmd[512]; }; static int parse_word(const char **bufptr, size_t len, char *word) { const char *buf = *bufptr; int i, pos = 0; while (isspace(buf[pos]) && buf[pos] != '\n' && buf[pos]) pos++; if (buf[pos] == '\n' || buf[pos] == 0) return FALSE; for (i = 0; i < len-1 && !isspace(buf[pos+i]); i++) word[i] = buf[pos+i]; word[i] = 0; *bufptr += i + pos; return TRUE; } static void admin_write(void *ctx, const char *format, ...) { struct admin_remote *rmt = (struct admin_remote *) ctx; char msg[1024]; va_list ap; size_t len; va_start(ap, format); len = vsnprintf(msg, sizeof(msg), format, ap); va_end(ap); if (write(rmt->io.fd, msg, len) != len) { } } static void admin_free_remote(struct admin_remote *rm) { int fd = rm->io.fd; ev_io_stop(&rm->io); ev_timer_stop(&rm->timeout); shutdown(fd, SHUT_RDWR); close(fd); free(rm); } static int admin_show_peer(void *ctx, struct nhrp_peer *peer) { char buf[512], tmp[32]; char *str; size_t len = sizeof(buf); int i = 0, rel; if (peer->interface != NULL) i += snprintf(&buf[i], len - i, "Interface: %s\n", peer->interface->name); i += snprintf(&buf[i], len - i, "Type: %s\n" "Protocol-Address: %s/%d\n", nhrp_peer_type[peer->type], nhrp_address_format(&peer->protocol_address, sizeof(tmp), tmp), peer->prefix_length); if (peer->next_hop_address.type != PF_UNSPEC) { switch (peer->type) { case NHRP_PEER_TYPE_SHORTCUT_ROUTE: case NHRP_PEER_TYPE_LOCAL_ROUTE: str = "Next-hop-Address"; break; case NHRP_PEER_TYPE_LOCAL_ADDR: str = "Alias-Address"; break; default: str = "NBMA-Address"; break; } i += snprintf(&buf[i], len - i, "%s: %s\n", str, nhrp_address_format(&peer->next_hop_address, sizeof(tmp), tmp)); } if (peer->nbma_hostname) { i += snprintf(&buf[i], len - i, "Hostname: %s\n", peer->nbma_hostname); } if (peer->next_hop_nat_oa.type != PF_UNSPEC) { i += snprintf(&buf[i], len - i, "NBMA-NAT-OA-Address: %s\n", nhrp_address_format(&peer->next_hop_nat_oa, sizeof(tmp), tmp)); } if (peer->flags & (NHRP_PEER_FLAG_USED | NHRP_PEER_FLAG_UNIQUE | NHRP_PEER_FLAG_UP | NHRP_PEER_FLAG_LOWER_UP)) { i += snprintf(&buf[i], len - i, "Flags:"); if (peer->flags & NHRP_PEER_FLAG_UNIQUE) i += snprintf(&buf[i], len - i, " unique"); if (peer->flags & NHRP_PEER_FLAG_USED) i += snprintf(&buf[i], len - i, " used"); if (peer->flags & NHRP_PEER_FLAG_UP) i += snprintf(&buf[i], len - i, " up"); else if (peer->flags & NHRP_PEER_FLAG_LOWER_UP) i += snprintf(&buf[i], len - i, " lower-up"); i += snprintf(&buf[i], len - i, "\n"); } if (peer->expire_time) { rel = (int) (peer->expire_time - ev_now()); if (rel >= 0) { i += snprintf(&buf[i], len - i, "Expires-In: %d:%02d\n", rel / 60, rel % 60); } } admin_write(ctx, "%s\n", buf); return 0; } static void admin_free_selector(struct nhrp_peer_selector *sel) { if (sel->hostname != NULL) { free((void *) sel->hostname); sel->hostname = NULL; } } static int admin_parse_selector(void *ctx, const char *cmd, struct nhrp_peer_selector *sel) { char keyword[64], tmp[64]; struct nhrp_address address; uint8_t prefix_length; while (parse_word(&cmd, sizeof(keyword), keyword)) { if (!parse_word(&cmd, sizeof(tmp), tmp)) { admin_write(ctx, "Status: failed\n" "Reason: missing-argument\n" "Near-Keyword: '%s'\n", keyword); return FALSE; } if (strcmp(keyword, "interface") == 0 || strcmp(keyword, "iface") == 0 || strcmp(keyword, "dev") == 0) { if (sel->interface != NULL) goto err_conflict; sel->interface = nhrp_interface_get_by_name(tmp, FALSE); if (sel->interface == NULL) goto err_noiface; continue; } else if (strcmp(keyword, "host") == 0 || strcmp(keyword, "hostname") == 0) { if (sel->hostname != NULL) goto err_conflict; sel->hostname = strdup(tmp); continue; } if (!nhrp_address_parse(tmp, &address, &prefix_length)) { admin_write(ctx, "Status: failed\n" "Reason: invalid-address\n" "Near-Keyword: '%s'\n", keyword); return FALSE; } if (strcmp(keyword, "protocol") == 0) { if (sel->protocol_address.type != AF_UNSPEC) goto err_conflict; sel->protocol_address = address; sel->prefix_length = prefix_length; } else if (strcmp(keyword, "nbma") == 0) { if (sel->next_hop_address.type != AF_UNSPEC) goto err_conflict; sel->type_mask &= ~BIT(NHRP_PEER_TYPE_SHORTCUT_ROUTE); sel->next_hop_address = address; } else if (strcmp(keyword, "local-protocol") == 0) { if (sel->interface != NULL) goto err_conflict; sel->interface = nhrp_interface_get_by_protocol(&address); if (sel->interface == NULL) goto err_noiface; } else if (strcmp(keyword, "local-nbma") == 0) { if (sel->interface != NULL) goto err_conflict; sel->interface = nhrp_interface_get_by_nbma(&address); if (sel->interface == NULL) goto err_noiface; } else { admin_write(ctx, "Status: failed\n" "Reason: syntax-error\n" "Near-Keyword: '%s'\n", keyword); return FALSE; } } return TRUE; err_conflict: admin_write(ctx, "Status: failed\n" "Reason: conflicting-keyword\n" "Near-Keyword: '%s'\n", keyword); goto err; err_noiface: admin_write(ctx, "Status: failed\n" "Reason: interface-not-found\n" "Near-Keyword: '%s'\n" "Argument: '%s'\n", keyword, tmp); err: admin_free_selector(sel); return FALSE; } static void admin_route_show(void *ctx, const char *cmd) { struct nhrp_peer_selector sel; memset(&sel, 0, sizeof(sel)); sel.type_mask = BIT(NHRP_PEER_TYPE_LOCAL_ROUTE); if (!admin_parse_selector(ctx, cmd, &sel)) return; admin_write(ctx, "Status: ok\n\n"); nhrp_peer_foreach(admin_show_peer, ctx, &sel); admin_free_selector(&sel); } static void admin_cache_show(void *ctx, const char *cmd) { struct nhrp_peer_selector sel; memset(&sel, 0, sizeof(sel)); sel.type_mask = NHRP_PEER_TYPEMASK_ALL & ~BIT(NHRP_PEER_TYPE_LOCAL_ROUTE); if (!admin_parse_selector(ctx, cmd, &sel)) return; admin_write(ctx, "Status: ok\n\n"); nhrp_peer_foreach(admin_show_peer, ctx, &sel); admin_free_selector(&sel); } static void admin_cache_purge(void *ctx, const char *cmd) { struct nhrp_peer_selector sel; int count = 0; memset(&sel, 0, sizeof(sel)); sel.type_mask = NHRP_PEER_TYPEMASK_PURGEABLE; if (!admin_parse_selector(ctx, cmd, &sel)) return; nhrp_peer_foreach(nhrp_peer_purge_matching, &count, &sel); admin_free_selector(&sel); admin_write(ctx, "Status: ok\n" "Entries-Affected: %d\n", count); } static void admin_cache_lower_down(void *ctx, const char *cmd) { struct nhrp_peer_selector sel; int count = 0; memset(&sel, 0, sizeof(sel)); sel.type_mask = NHRP_PEER_TYPEMASK_PURGEABLE; if (!admin_parse_selector(ctx, cmd, &sel)) return; nhrp_peer_foreach(nhrp_peer_lowerdown_matching, &count, &sel); admin_free_selector(&sel); admin_write(ctx, "Status: ok\n" "Entries-Affected: %d\n", count); } static void admin_cache_flush(void *ctx, const char *cmd) { struct nhrp_peer_selector sel; int count = 0; memset(&sel, 0, sizeof(sel)); sel.type_mask = NHRP_PEER_TYPEMASK_REMOVABLE; if (!admin_parse_selector(ctx, cmd, &sel)) return; nhrp_peer_foreach(nhrp_peer_remove_matching, &count, &sel); admin_free_selector(&sel); admin_write(ctx, "Status: ok\n" "Entries-Affected: %d\n", count); } static int admin_show_interface(void *ctx, struct nhrp_interface *iface) { char buf[512], tmp[32]; size_t len = sizeof(buf); int i = 0; i += snprintf(&buf[i], len - i, "Interface: %s\n" "Index: %d\n", iface->name, iface->index); if (iface->protocol_address.addr_len != 0) { i += snprintf(&buf[i], len - i, "Protocol-Address: %s/%d\n", nhrp_address_format(&iface->protocol_address, sizeof(tmp), tmp), iface->protocol_address_prefix); } if (iface->flags) { i += snprintf(&buf[i], len - i, "Flags:%s%s%s%s%s\n", (iface->flags & NHRP_INTERFACE_FLAG_NON_CACHING) ? " non-caching" : "", (iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT) ? " shortcut" : "", (iface->flags & NHRP_INTERFACE_FLAG_REDIRECT) ? " redirect" : "", (iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT_DEST) ? " shortcut-dest" : "", (iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED) ? " configured" : ""); } if (!(iface->flags & NHRP_INTERFACE_FLAG_CONFIGURED)) goto done; i += snprintf(&buf[i], len - i, "Holding-Time: %u\n" "Route-Table: %u\n" "GRE-Key: %u\n" "MTU: %u\n", iface->holding_time, iface->route_table, iface->gre_key, iface->mtu); if (iface->link_index) { struct nhrp_interface *link; i += snprintf(&buf[i], len - i, "Link-Index: %d\n", iface->link_index); link = nhrp_interface_get_by_index(iface->link_index, FALSE); if (link != NULL) i += snprintf(&buf[i], len - i, "Link-Name: %s\n", link->name); } if (iface->nbma_address.addr_len != 0) { i += snprintf(&buf[i], len - i, "NBMA-MTU: %u\n" "NBMA-Address: %s\n", iface->nbma_mtu, nhrp_address_format(&iface->nbma_address, sizeof(tmp), tmp)); } if (iface->nat_cie.nbma_address.addr_len != 0) { i += snprintf(&buf[i], len - i, "NBMA-NAT-OA: %s\n", nhrp_address_format(&iface->nat_cie.nbma_address, sizeof(tmp), tmp)); } done: admin_write(ctx, "%s\n", buf); return 0; } static void admin_interface_show(void *ctx, const char *cmd) { admin_write(ctx, "Status: ok\n\n"); nhrp_interface_foreach(admin_show_interface, ctx); } static void admin_redirect_purge(void *ctx, const char *cmd) { char keyword[64]; struct nhrp_address addr; uint8_t prefix; int count; nhrp_address_set_type(&addr, PF_UNSPEC); if (parse_word(&cmd, sizeof(keyword), keyword)) { if (!nhrp_address_parse(keyword, &addr, &prefix)) { admin_write(ctx, "Status: failed\n" "Reason: invalid-address\n" "Near-Keyword: '%s'\n", keyword); return; } } count = nhrp_rate_limit_clear(&addr, prefix); admin_write(ctx, "Status: ok\n" "Entries-Affected: %d\n", count); } struct update_nbma { struct nhrp_address addr; int count; }; static int update_nbma(void *ctx, struct nhrp_peer *p) { struct update_nbma *un = (struct update_nbma *) ctx; nhrp_peer_discover_nhs(p, &un->addr); un->count++; return 0; } static void admin_update_nbma(void *ctx, const char *cmd) { char keyword[64]; struct nhrp_peer_selector sel; struct update_nbma un; memset(&sel, 0, sizeof(sel)); sel.type_mask = BIT(NHRP_PEER_TYPE_DYNAMIC_NHS); if (!parse_word(&cmd, sizeof(keyword), keyword)) goto err; if (!nhrp_address_parse(keyword, &sel.next_hop_address, NULL)) goto err; if (!parse_word(&cmd, sizeof(keyword), keyword)) goto err; if (!nhrp_address_parse(keyword, &un.addr, NULL)) goto err; un.count = 0; nhrp_peer_foreach(update_nbma, &un, &sel); admin_write(ctx, "Status: ok\n" "Entries-Affected: %d\n", un.count); return; err: admin_write(ctx, "Status: failed\n" "Reason: syntax-error\n" "Near-Keyword: '%s'\n", keyword); return; } static struct { const char *command; void (*handler)(void *ctx, const char *cmd); } admin_handler[] = { { "route show", admin_route_show }, { "show", admin_cache_show }, { "cache show", admin_cache_show }, { "flush", admin_cache_flush }, { "cache flush", admin_cache_flush }, { "purge", admin_cache_purge }, { "cache purge", admin_cache_purge }, { "cache lowerdown", admin_cache_lower_down }, { "interface show", admin_interface_show }, { "redirect purge", admin_redirect_purge }, { "update nbma", admin_update_nbma }, }; static void admin_receive_cb(struct ev_io *w, int revents) { struct admin_remote *rm = container_of(w, struct admin_remote, io); int fd = rm->io.fd; ssize_t len; int i, cmdlen; len = recv(fd, rm->cmd, sizeof(rm->cmd) - rm->num_read, MSG_DONTWAIT); if (len < 0 && errno == EAGAIN) return; if (len <= 0) goto err; rm->num_read += len; if (rm->num_read >= sizeof(rm->cmd)) goto err; if (rm->cmd[rm->num_read-1] != '\n') return; rm->cmd[--rm->num_read] = 0; for (i = 0; i < ARRAY_SIZE(admin_handler); i++) { cmdlen = strlen(admin_handler[i].command); if (rm->num_read >= cmdlen && strncasecmp(rm->cmd, admin_handler[i].command, cmdlen) == 0) { nhrp_debug("Admin: %s", rm->cmd); admin_handler[i].handler(rm, &rm->cmd[cmdlen]); break; } } if (i >= ARRAY_SIZE(admin_handler)) { admin_write(rm, "Status: error\n" "Reason: unrecognized command\n"); } err: admin_free_remote(rm); } static void admin_timeout_cb(struct ev_timer *t, int revents) { admin_free_remote(container_of(t, struct admin_remote, timeout)); } static void admin_accept_cb(ev_io *w, int revents) { struct admin_remote *rm; struct sockaddr_storage from; socklen_t fromlen = sizeof(from); int cnx; cnx = accept(w->fd, (struct sockaddr *) &from, &fromlen); if (cnx < 0) return; fcntl(cnx, F_SETFD, FD_CLOEXEC); rm = calloc(1, sizeof(struct admin_remote)); ev_io_init(&rm->io, admin_receive_cb, cnx, EV_READ); ev_io_start(&rm->io); ev_timer_init(&rm->timeout, admin_timeout_cb, 10.0, 0.); ev_timer_start(&rm->timeout); } int admin_init(const char *opennhrp_socket) { struct sockaddr_un sun; int fd; memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; strncpy(sun.sun_path, opennhrp_socket, sizeof(sun.sun_path)); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return 0; fcntl(fd, F_SETFD, FD_CLOEXEC); unlink(opennhrp_socket); if (bind(fd, (struct sockaddr *) &sun, sizeof(sun)) != 0) goto err_close; if (listen(fd, 5) != 0) goto err_close; ev_io_init(&accept_io, admin_accept_cb, fd, EV_READ); ev_io_start(&accept_io); return 1; err_close: nhrp_error("Failed initialize admin socket [%s]: %s", opennhrp_socket, strerror(errno)); close(fd); return 0; }