pespin has submitted this change. ( https://gerrit.osmocom.org/c/osmo-pcap/+/39201?usp=email )
Change subject: pcap-server: Introduce VTY cmd '[no] completed-path' ......................................................................
pcap-server: Introduce VTY cmd '[no] completed-path'
This VTY allows enabling feature to instruct osmo-pcap-server to move the closed files (due to ration) into a separate directory. This is useful for users who want to monitor and act on closed pcap files to eg. generate statistics, etc.
Important: completed-path must be in the same filesystem mountpoint as base-path, since osmo-pcap-server is actually not copying the files, but atomically renaming the paths through rename() syscall.
Related: SYS#7248 Change-Id: I5166d1c5d9eb45358c87c2e1c5fbf7969d1d5294 --- M doc/manuals/chapters/server.adoc M include/osmo-pcap/osmo_pcap_server.h M src/osmo_server_network.c M src/osmo_server_vty.c 4 files changed, 141 insertions(+), 0 deletions(-)
Approvals: Jenkins Builder: Verified pespin: Looks good to me, approved
diff --git a/doc/manuals/chapters/server.adoc b/doc/manuals/chapters/server.adoc index c6404ab..e0d9ff4 100644 --- a/doc/manuals/chapters/server.adoc +++ b/doc/manuals/chapters/server.adoc @@ -125,3 +125,31 @@ Saving procedure), osmo-pcap-server may end up recreating (and truncating) a previous pcap file if it is generated with the same localtime timestamp, for instance because connection from osmo-pcap-client was re-established. + +=== Completed pcap directory + +`osmo-pcap-server` creates and writes data to opened pcap files in the +`base-path` directory configured through VTY. Furthermore, if user supplies a +`completed-path` directory through the VTY, `osmo-pcap-server` will move the +file from `base-path` to `completed-path` directory once it closes the current +file due to rotation or because `osmo-pcap-client` became disconnected. The file +name is kept when moving from one directory to the other. + +This feature is useful for users willing to have an external tool to monitor a +directory for new closed pcap files and then acting on them, eg. to gather +statistics of packets. + +.Example: Move files from /tmp/recording to /tmp/done +---- +server + ... + base-path /tmp/recording <1> + completed-path /tmp/done <2> +---- +<1> Files are opened and recorded under /tmp/recording +<2> Once closed files are moved under /tmp/done + +NOTE:: `osmo-pcap-server` uses a rename() call to move the file atomically. +However, this has the drawback that both `base-path` and `completed-path` must +be placed in the same filesystem mounting point. Using directories in different +filesystem mount points will fail. diff --git a/include/osmo-pcap/osmo_pcap_server.h b/include/osmo-pcap/osmo_pcap_server.h index 50bb121..614ceca 100644 --- a/include/osmo-pcap/osmo_pcap_server.h +++ b/include/osmo-pcap/osmo_pcap_server.h @@ -140,6 +140,7 @@ bool dh_params_allocated;
char *base_path; + char *completed_path; mode_t permission_mask; off_t max_size; bool max_size_enabled; diff --git a/src/osmo_server_network.c b/src/osmo_server_network.c index 8747091..9750170 100644 --- a/src/osmo_server_network.c +++ b/src/osmo_server_network.c @@ -39,6 +39,9 @@ #include <errno.h> #include <string.h> #include <unistd.h> +#include <limits.h> +#include <stdlib.h> +#include <libgen.h>
static void pcap_zmq_send(void *publ, const void *data, size_t len, int flags) { @@ -112,6 +115,84 @@ 0); }
+/* Move pcap file from base_path to completed_path, and updates + * conn->curr_filename to point to new location. */ +void move_completed_trace_if_needed(struct osmo_pcap_conn *conn) +{ + struct osmo_pcap_server *server = conn->server; + char *curr_filename_cpy_bname = NULL; + char *curr_filename_cpy_dname = NULL; + char *bname = NULL; + char *curr_dirname = NULL; + char *new_dirname = NULL; + char *new_filename = NULL; + size_t new_filename_len; + int rc; + + if (!conn->curr_filename) + return; + + if (!server->completed_path) + return; + + /* Assumption: curr_filename is anonicalized absolute pathname. */ + + /* basename and dirname may modify input param, and return a string + * which shall not be freed, potentially pointing to the input param. */ + curr_filename_cpy_dname = talloc_strdup(conn, conn->curr_filename); + curr_filename_cpy_bname = talloc_strdup(conn, conn->curr_filename); + if (!curr_filename_cpy_dname || !curr_filename_cpy_bname) + goto ret_free1; + + curr_dirname = dirname(curr_filename_cpy_dname); + bname = basename(curr_filename_cpy_bname); + if (!curr_dirname || !bname) { + LOGP(DSERVER, LOGL_ERROR, "Failed to resolve dirname and basename for '%s'\n", + conn->curr_filename); + goto ret_free1; + } + + new_dirname = realpath(server->completed_path, NULL); + if (!new_dirname) { + LOGP(DSERVER, LOGL_ERROR, "Failed to resolve path '%s': %s\n", + server->completed_path, strerror(errno)); + goto ret_free1; + } + + new_filename_len = strlen(new_dirname) + 1 /* '/' */ + strlen(bname) + 1 /* '\0' */; + new_filename = talloc_size(conn, new_filename_len); + if (!new_filename) + goto ret_free1; + rc = snprintf(new_filename, new_filename_len, "%s/%s", new_dirname, bname); + if (rc != new_filename_len - 1) + goto ret_free2; + + LOGP(DSERVER, LOGL_INFO, "Moving completed pcap file '%s' -> '%s'\n", conn->curr_filename, new_filename); + rc = rename(conn->curr_filename, new_filename); + if (rc == -1) { + int err = errno; + LOGP(DSERVER, LOGL_ERROR, "Failed moving completed pcap file '%s' -> '%s': %s\n", + conn->curr_filename, new_filename, strerror(err)); + if (err == EXDEV) + LOGP(DSERVER, LOGL_ERROR, "Fix your config! %s and %s shall not be in different filesystems!\n", + curr_dirname, new_dirname); + goto ret_free2; + } + + /* Now replace conn->curr_filename with new path: */ + talloc_free(conn->curr_filename); + conn->curr_filename = new_filename; + /* new_filename has been assigned, so we don't want to free it, hence move to ret_free1: */ + goto ret_free1; + +ret_free2: + talloc_free(new_filename); +ret_free1: + free(new_dirname); + talloc_free(curr_filename_cpy_bname); + talloc_free(curr_filename_cpy_dname); +} + void osmo_pcap_server_close_trace(struct osmo_pcap_conn *conn) { if (conn->local_fd >= 0) { @@ -119,6 +200,8 @@ conn->local_fd = -1; }
+ move_completed_trace_if_needed(conn); + if (conn->curr_filename) { client_event(conn, "closingtracefile", conn->curr_filename); rate_ctr_inc2(conn->ctrg, PEER_CTR_PROTATE); diff --git a/src/osmo_server_vty.c b/src/osmo_server_vty.c index ee3f995..1e8e05c 100644 --- a/src/osmo_server_vty.c +++ b/src/osmo_server_vty.c @@ -97,6 +97,8 @@ vty_out(vty, "server%s", VTY_NEWLINE);
vty_out(vty, " base-path %s%s", pcap_server->base_path, VTY_NEWLINE); + if (pcap_server->completed_path) + vty_out(vty, " completed-path %s%s", pcap_server->completed_path, VTY_NEWLINE); vty_out(vty, " file-permission-mask 0%o%s", pcap_server->permission_mask, VTY_NEWLINE); if (pcap_server->addr) vty_out(vty, " server ip %s%s", pcap_server->addr, VTY_NEWLINE); @@ -159,6 +161,31 @@ return CMD_SUCCESS; }
+DEFUN(cfg_server_no_completed_path, + cfg_server_no_completed_path_cmd, + "no completed-path", + NO_STR "Base path for completed (already closed, rotated) log files. Completed files won't be moved.\n") +{ + TALLOC_FREE(pcap_server->completed_path); + return CMD_SUCCESS; +} + +DEFUN(cfg_server_completed_path, + cfg_server_completed_path_cmd, + "completed-path PATH", + "Base path for completed (already closed, rotated) log files\n" "Path\n") +{ + /* Validate we can resolve path: */ + char *tmp = realpath(argv[0], NULL); + if (!tmp) { + vty_out(vty, "%% Failed to resolve path '%s': %s%s", argv[0], strerror(errno), VTY_NEWLINE); + return CMD_WARNING; + } + free(tmp); + osmo_talloc_replace_string(pcap_server, &pcap_server->completed_path, argv[0]); + return CMD_SUCCESS; +} + DEFUN(cfg_server_file_permission_mask, cfg_server_file_permission_mask_cmd, "file-permission-mask MODE", @@ -671,6 +698,8 @@ install_node(&server_node, config_write_server);
install_element(SERVER_NODE, &cfg_server_base_cmd); + install_element(SERVER_NODE, &cfg_server_no_completed_path_cmd); + install_element(SERVER_NODE, &cfg_server_completed_path_cmd); install_element(SERVER_NODE, &cfg_server_file_permission_mask_cmd); install_element(SERVER_NODE, &cfg_server_ip_cmd); install_element(SERVER_NODE, &cfg_server_port_cmd);