diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4990a83..5cb214a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(fastd hkdf_sha256.c fastd.c iface.c + inotify.c lex.c log.c options.c diff --git a/src/config.c b/src/config.c index f649dec..240d74d 100644 --- a/src/config.c +++ b/src/config.c @@ -41,6 +41,7 @@ #include "method.h" #include "peer.h" #include "peer_group.h" +#include "inotify.h" #include #include @@ -223,8 +224,23 @@ static bool has_peer_group_peer_dirs(const fastd_peer_group_t *group) { return false; } +static void add_peer(const char *name, fastd_peer_group_t *group, const char *dir) { + fastd_peer_t *peer = fastd_new0(fastd_peer_t); + peer->name = fastd_strdup(name); + peer->config_source_dir = dir; + + if (!fastd_config_read(name, group, peer, 0)) { + fastd_peer_free(peer); + return; + } + + fastd_peer_add(peer); +} + /** Reads and processes all peer definitions in the current directory (which must also be supplied as the argument) */ static void read_peer_dir(fastd_peer_group_t *group, const char *dir) { + fastd_inotify_add_config_dir(dir); + DIR *dirh = opendir("."); if (dirh) { @@ -256,16 +272,7 @@ static void read_peer_dir(fastd_peer_group_t *group, const char *dir) { continue; } - fastd_peer_t *peer = fastd_new0(fastd_peer_t); - peer->name = fastd_strdup(result->d_name); - peer->config_source_dir = dir; - - if (!fastd_config_read(result->d_name, group, peer, 0)) { - fastd_peer_free(peer); - continue; - } - - fastd_peer_add(peer); + add_peer(result->d_name, group, dir); } if (closedir(dirh) < 0) @@ -639,44 +646,48 @@ static void peer_dirs_read_peer_group(fastd_peer_group_t *group) { peer_dirs_read_peer_group(child); } -/** Initializes the configured peers */ -static void configure_peers(bool dirs_only) { - ctx.has_floating = false; - ctx.max_mtu = conf.mtu; +static void configure_peer(fastd_peer_t *peer, bool dirs_only) { + pr_info("Calling configure on peer: %P", peer); + if (peer->config_state == CONFIG_STATIC) { + /* The peer hasn't been touched since the last run of configure_peers(), so its definition must have disappeared */ + fastd_peer_delete(peer); + return; + } - ssize_t i; - for (i = VECTOR_LEN(ctx.peers)-1; i >= 0; i--) { - fastd_peer_t *peer = VECTOR_INDEX(ctx.peers, i); + if (fastd_peer_is_dynamic(peer)) + return; - if (peer->config_state == CONFIG_STATIC) { - /* The peer hasn't been touched since the last run of configure_peers(), so its definition must have disappeared */ - fastd_peer_delete(peer); - continue; - } + if (peer->config_state != CONFIG_DISABLED && !conf.protocol->check_peer(peer)) + peer->config_state = CONFIG_DISABLED; - if (fastd_peer_is_dynamic(peer)) - continue; + if (peer->config_state == CONFIG_DISABLED) { + fastd_peer_reset(peer); + return; + } - if (peer->config_state != CONFIG_DISABLED && !conf.protocol->check_peer(peer)) - peer->config_state = CONFIG_DISABLED; + if (fastd_peer_is_floating(peer)) + ctx.has_floating = true; - if (peer->config_state == CONFIG_DISABLED) { - fastd_peer_reset(peer); - continue; - } + if (conf.mode != MODE_TAP && peer->mtu > ctx.max_mtu) + ctx.max_mtu = peer->mtu; - if (fastd_peer_is_floating(peer)) - ctx.has_floating = true; + peer->config_state = CONFIG_STATIC; - if (conf.mode != MODE_TAP && peer->mtu > ctx.max_mtu) - ctx.max_mtu = peer->mtu; + if (!fastd_peer_is_established(peer)) { + if (peer->config_source_dir || !dirs_only) + fastd_peer_reset(peer); + } +} - peer->config_state = CONFIG_STATIC; +/** Initializes the configured peers */ +static void configure_peers(bool dirs_only) { + ctx.has_floating = false; + ctx.max_mtu = conf.mtu; - if (!fastd_peer_is_established(peer)) { - if (peer->config_source_dir || !dirs_only) - fastd_peer_reset(peer); - } + ssize_t i; + for (i = VECTOR_LEN(ctx.peers)-1; i >= 0; i--) { + fastd_peer_t *peer = VECTOR_INDEX(ctx.peers, i); + configure_peer(peer, dirs_only); } } @@ -685,24 +696,89 @@ void fastd_configure_peers(void) { configure_peers(false); } +void reset_peer_config_state(fastd_peer_t *peer) { + if (fastd_peer_is_dynamic(peer)) + return; + + /* Reset all peers' config states */ + if (!peer->config_source_dir) + peer->config_state = CONFIG_NEW; + else if (peer->config_state == CONFIG_DISABLED) + peer->config_state = CONFIG_STATIC; + pr_info("Reset peer config state: %P", peer); +} + /** Refreshes the peer configurations from the configured peer dirs */ void fastd_config_load_peer_dirs(bool dirs_only) { size_t i; for (i = 0; i < VECTOR_LEN(ctx.peers); i++) { fastd_peer_t *peer = VECTOR_INDEX(ctx.peers, i); + reset_peer_config_state(peer); + } - if (fastd_peer_is_dynamic(peer)) - continue; + peer_dirs_read_peer_group(conf.peer_group); + configure_peers(true); +} + +static fastd_peer_group_t *find_group_from_dir(fastd_peer_group_t *group, const char *conf_dir) { + fastd_string_stack_t *dir; + for (dir = group->peer_dirs; dir; dir = dir->next) { + if (!strcmp(dir->str, conf_dir)) + return group; + } + if (group->children) { + find_group_from_dir(group->children, conf_dir); + } + if (group->next) { + find_group_from_dir(group->next, conf_dir); + } + return NULL; +} - /* Reset all peers' config states */ +static fastd_peer_t *find_peer_by_name_and_dir(const char *dir, const char *name) { + int i; + for (i = 0; i < VECTOR_LEN(ctx.peers); i++) { + fastd_peer_t *peer = VECTOR_INDEX(ctx.peers, i); if (!peer->config_source_dir) - peer->config_state = CONFIG_NEW; - else if (peer->config_state == CONFIG_DISABLED) - peer->config_state = CONFIG_STATIC; + continue; + if (!peer->name) + continue; + if (strcmp(peer->config_source_dir, dir)) + continue; + if (strcmp(peer->name, name)) + continue; + return peer; } + return NULL; +} - peer_dirs_read_peer_group(conf.peer_group); - configure_peers(dirs_only); +void fastd_config_load_peer(const char *dir, const char *name) { + fastd_peer_group_t *group = find_group_from_dir(conf.peer_group, dir); + if (!group) { + pr_warn("Unable to find group for config %s in dir %s", name, dir); + return; + } + + fastd_peer_t *peer = find_peer_by_name_and_dir(dir, name); + if (peer) + reset_peer_config_state(peer); + + char *oldcwd = get_current_dir_name(); + chdir(dir); + add_peer(name, group, dir); + if (chdir(oldcwd)) + pr_error("can't chdir to `%s': %s", oldcwd, strerror(errno)); + free(oldcwd); + + peer = find_peer_by_name_and_dir(dir, name); + if (peer) + configure_peer(peer, false); +} + +void fastd_config_delete_peer(const char *dir, const char *name) { + fastd_peer_t *peer = find_peer_by_name_and_dir(dir, name); + if (peer) + fastd_peer_delete(peer); } /** Frees all resources used by the global configuration */ diff --git a/src/config.h b/src/config.h index 9b79014..2bf2729 100644 --- a/src/config.h +++ b/src/config.h @@ -64,5 +64,7 @@ void fastd_configure(int argc, char *const argv[]); void fastd_configure_peers(void); void fastd_config_check(void); void fastd_config_load_peer_dirs(bool dirs_only); +void fastd_config_load_peer(const char *dir, const char *name); +void fastd_config_delete_peer(const char *dir, const char *name); bool fastd_config_single_iface(void); bool fastd_config_persistent_ifaces(void); diff --git a/src/fastd.c b/src/fastd.c index 3fc3836..5c88972 100644 --- a/src/fastd.c +++ b/src/fastd.c @@ -36,6 +36,7 @@ #include "fastd.h" #include "async.h" +#include "inotify.h" #include "config.h" #include "crypto.h" #include "peer.h" @@ -539,6 +540,7 @@ static inline void init(int argc, char *argv[]) { fastd_status_init(); fastd_async_init(); + fastd_inotify_init(); fastd_socket_bind_all(); diff --git a/src/fastd.h b/src/fastd.h index 3b84e1b..ba6b15d 100644 --- a/src/fastd.h +++ b/src/fastd.h @@ -323,6 +323,9 @@ struct fastd_context { pthread_attr_t detached_thread; /**< pthread_attr_t for creating detached threads */ + fastd_poll_fd_t inotify_fd; /**< The fd for the inotifier watching for config dir changes */ + VECTOR(fastd_inotify_watch_descriptor_t *) inotify_wd; /**< A map from watch descriptor number to directory name */ + #ifdef __ANDROID__ int android_ctrl_sock_fd; /**< The unix domain socket for communicating with Android GUI */ #endif diff --git a/src/inotify.c b/src/inotify.c new file mode 100644 index 0000000..7d7bfac --- /dev/null +++ b/src/inotify.c @@ -0,0 +1,158 @@ +/* + Copyright (c) 2012-2016, Matthias Schiffer + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + \file + + inotifyhronous notifications +*/ + + +#include "inotify.h" +#include "fastd.h" +#include "config.h" + +#include + +/** Initializes the inotify notification sockets */ +void fastd_inotify_init(void) { + int fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); + if (fd == -1) + exit_errno("inotify"); + + ctx.inotify_fd = FASTD_POLL_FD(POLL_TYPE_INOTFY, fd); + + fastd_poll_fd_register(&ctx.inotify_fd); +} + +/** Reads and handles a single notification from the inotify notification socket */ +void fastd_inotify_handle(void) { + /* Loop while events can be read from inotify file descriptor. */ + for (;;) { + /* Read some events. */ + /* Some systems cannot read integer variables if they are not + properly aligned. On other systems, incorrect alignment may + decrease performance. Hence, the buffer used for reading from + the inotify file descriptor should have the same alignment as + struct inotify_event. */ + char buf[4096] + __attribute__ ((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *event; + ssize_t len = read(ctx.inotify_fd.fd, buf, sizeof(buf)); + if (len == -1 && errno != EAGAIN) + exit_errno("inotify failed read"); + + /* If the nonblocking read() found no events to read, then + it returns -1 with errno set to EAGAIN. In that case, + we exit the loop. */ + + if (len <= 0) + break; + + /* Loop over all events in the buffer */ + char *ptr; + for (ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *) ptr; + + /* Print event type */ + const char *type = "unknown: "; + if (event->mask & IN_DELETE) + type = "IN_DELETE: "; + if (event->mask & IN_CLOSE_WRITE) + type = "IN_CLOSE_WRITE: "; + if (event->mask & IN_MODIFY) + type = "IN_MODIFY: "; + if (event->mask & IN_MOVED_TO) + type = "IN_MOVED_TO: "; + if (event->mask & IN_MOVED_FROM) + type = "IN_MOVED_FROM: "; + + /* Print the name of the watched directory */ + const char *dir = NULL; + int i; + for (i = 0; i < VECTOR_LEN(ctx.inotify_wd); i++) { + fastd_inotify_watch_descriptor_t *watch_descriptor = VECTOR_INDEX(ctx.inotify_wd, i); + if (watch_descriptor->wd == event->wd) { + dir = watch_descriptor->dir; + break; + } + } + if (!dir) { + pr_warn("Failed to find dir for watch descriptor: %i", event->wd); + continue; + } + + if (event->len == 0) + continue; + + const char *name = event->name; + if (name[strlen(name)-1] == '~') { + pr_verbose("ignoring file `%s' as it seems to be a backup file", name); + continue; + } + + if (event->mask & (IN_DELETE|IN_MOVED_FROM)) { + pr_info("Doing delete due to: %s for peer: %s in dir: %s", type, name, dir); + fastd_config_delete_peer(dir, name); + } + else { + pr_info("Doing reload due to: %s for peer: %s in dir: %s", type, name, dir); + fastd_config_load_peer(dir, name); + } + } + } +} + +/** + Watch a directory for config changes, don't add it again if already watching it +*/ +void fastd_inotify_add_config_dir(const char *dir) { + + int i; + for (i = 0; i < VECTOR_LEN(ctx.inotify_wd); i++) { + fastd_inotify_watch_descriptor_t *watch_descriptor = VECTOR_INDEX(ctx.inotify_wd, i); + if (!strcmp(watch_descriptor->dir, dir)) { + return; + } + } + + int wd = inotify_add_watch(ctx.inotify_fd.fd, dir, + IN_CLOSE_WRITE| // File was changed or copied into dir + IN_MODIFY| // File was possibly modified or copied into dir + IN_DELETE| // File was deleted from dir + IN_MOVED_TO| // File was moved into dir + IN_MOVED_FROM| // File was moved from dir + IN_ONLYDIR); + if( wd == -1) { + pr_error_errno("inotify_add_watch"); + return; + } + pr_verbose("Added watcher for %s wd: %i", dir, wd); + fastd_inotify_watch_descriptor_t *watch_descriptor = fastd_new0(fastd_inotify_watch_descriptor_t); + watch_descriptor->dir = fastd_strdup(dir); + watch_descriptor->wd = wd; + VECTOR_ADD(ctx.inotify_wd, watch_descriptor); +} + diff --git a/src/inotify.h b/src/inotify.h new file mode 100644 index 0000000..f2ba4f0 --- /dev/null +++ b/src/inotify.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2012-2016, Matthias Schiffer + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + \file + + Asynchronous notifications +*/ + + +#pragma once + +#include "types.h" +#include "peer.h" + + +struct fastd_inotify_watch_descriptor { + int wd; + const char *dir; +}; + +void fastd_inotify_init(void); +void fastd_inotify_handle(void); +void fastd_inotify_add_config_dir(const char *dir); diff --git a/src/log.c b/src/log.c index ad22d37..683100f 100644 --- a/src/log.c +++ b/src/log.c @@ -145,6 +145,10 @@ static int fastd_vsnprintf(char *buffer, size_t size, const char *format, va_lis buffer += snprintf_safe(buffer, buffer_end-buffer, "%%"); break; + case 'c': + buffer += snprintf_safe(buffer, buffer_end-buffer, "%c", va_arg(ap, int)); + break; + case 'i': buffer += snprintf_safe(buffer, buffer_end-buffer, "%i", va_arg(ap, int)); break; diff --git a/src/poll.c b/src/poll.c index a3326a7..11ea2ff 100644 --- a/src/poll.c +++ b/src/poll.c @@ -32,6 +32,7 @@ #include "poll.h" #include "async.h" +#include "inotify.h" #include "peer.h" #include @@ -107,6 +108,11 @@ static inline void handle_fd(fastd_poll_fd_t *fd, bool input, bool error) { break; } + case POLL_TYPE_INOTFY: + if (input) + fastd_inotify_handle(); + break; + default: exit_bug("unknown FD type"); } diff --git a/src/types.h b/src/types.h index 0078055..f6620e4 100644 --- a/src/types.h +++ b/src/types.h @@ -86,6 +86,7 @@ typedef enum fastd_poll_type { POLL_TYPE_STATUS, /**< The status socket */ POLL_TYPE_IFACE, /**< A TUN/TAP interface */ POLL_TYPE_SOCKET, /**< A network socket */ + POLL_TYPE_INOTFY, /**< A inotify fd */ } fastd_poll_type_t; /** Task types */ @@ -144,6 +145,8 @@ typedef struct fastd_string_stack fastd_string_stack_t; typedef struct fastd_shell_command fastd_shell_command_t; typedef struct fastd_shell_env fastd_shell_env_t; +typedef struct fastd_inotify_watch_descriptor fastd_inotify_watch_descriptor_t; + /** A 128-bit aligned block of data, primarily used by the cryptographic functions */ typedef union fastd_block128 {