diff options
Diffstat (limited to 'metis/ccnx/forwarder/metis/command-line')
6 files changed, 649 insertions, 0 deletions
diff --git a/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt new file mode 100644 index 00000000..ac383008 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(metis_control) +add_subdirectory(metis_daemon) +#add_subdirectory(test) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt new file mode 100644 index 00000000..e07d5f40 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_control/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(metis_control metisControl_main.c) +target_link_libraries(metis_control ${METIS_LINK_LIBRARIES}) + +install(TARGETS metis_control RUNTIME DESTINATION bin) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c b/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c new file mode 100644 index 00000000..067ed9c9 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_control/metisControl_main.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> + +#include <stdbool.h> +#include <stdint.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> + +#include <LongBow/runtime.h> +#include <string.h> + +#include <parc/security/parc_Security.h> +#include <parc/security/parc_IdentityFile.h> + +#include <parc/algol/parc_Memory.h> +#include <parc/algol/parc_SafeMemory.h> +#include <parc/algol/parc_List.h> +#include <parc/algol/parc_ArrayList.h> + +#include <ccnx/api/control/cpi_ManageLinks.h> + +#include <ccnx/api/control/cpi_Forwarding.h> +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> + +#include <ccnx/api/ccnx_Portal/ccnx_Portal.h> +#include <ccnx/api/ccnx_Portal/ccnx_PortalRTA.h> + +#include <ccnx/common/ccnx_KeystoreUtilities.h> +#include <ccnx/transport/common/transport_MetaMessage.h> + +#include <ccnx/forwarder/metis/metis_About.h> +#include <ccnx/forwarder/metis/config/metis_ControlState.h> +#include <ccnx/forwarder/metis/config/metisControl_Root.h> + +#include <errno.h> + +typedef struct metis_control_main_state { + KeystoreParams *keystoreParams; + CCNxPortal *controlPortal; + MetisControlState *controlState; +} MetisControlMainState; + +static void +_displayForwarderLogo(void) +{ + printf("%s\n", metisAbout_About()); + + printf(" __ __ _ _\n"); + printf(" | \\/ | ___ | |_ (_) ___\n"); + printf(" | |\\/| | / _ \\| __|| |/ __|\n"); + printf(" | | | || __/| |_ | |\\__ \\\n"); + printf(" |_| |_| \\___| \\__||_||___/\n"); + + printf("\n"); +} + +static void +_displayUsage(char *programName) +{ + printf("Usage: %s -h\n", programName); + printf(" %s [--k|--keystore <keystore file name>] [--p|--password <keystore password>] [commands]\n", programName); + printf("\n"); + printf("Metis is the CCNx 1.0 forwarder, which runs on each end system and as a software forwarder\n"); + printf("on intermediate systems. metis_control is the program to configure the forwarder, metis_daemon.\n"); + printf("\n"); + printf("Options:\n"); + printf("-h = This help screen\n"); + printf("-k | --keystore = Specify the path of the PKCS12 keystore (default ~/.ccnx/.ccnx_keystore.p12)\n"); + printf("-p | --password = keystore password (default to prompt user)\n"); + printf("commands = configuration line to send to metis (use 'help' for list)\n"); + printf("\n"); +} + +static int +_parseArgs(int argc, char *argv[], char **keystorePath, char **keystorePassword, PARCList *commandList) +{ + static struct option longFormOptions[] = { + { "help", no_argument, 0, 'h' }, + { "keystore", required_argument, 0, 'k' }, + { "password", required_argument, 0, 'p' }, + { 0, 0, 0, 0 } + }; + + int c; + + while (1) { + // getopt_long stores the option index here. + int optionIndex = 0; + + c = getopt_long(argc, argv, "hk:p:", longFormOptions, &optionIndex); + + // Detect the end of the options. + if (c == -1) { + break; + } + + switch (c) { + case 'k': + *keystorePath = optarg; + break; + + case 'p': + *keystorePassword = optarg; + break; + + case 'h': + default: + _displayUsage(argv[0]); + return 0; + } + } + + // Any remaining parameters get put in the command list. + if (optind < argc) { + while (optind < argc) { + parcList_Add(commandList, argv[optind]); + optind++; + } + } + + return 1; +} + +static CCNxMetaMessage * +_writeAndReadMessage(void *mainStatePtr, CCNxMetaMessage *msg) +{ + assertNotNull(mainStatePtr, "Parameter mainStatePtr must be non-null"); + assertNotNull(msg, "Parameter msg must be non-null"); + + MetisControlMainState *mainState = mainStatePtr; + + if (ccnxPortal_Send(mainState->controlPortal, msg, CCNxStackTimeout_Never)) { + CCNxMetaMessage *result = ccnxPortal_Receive(mainState->controlPortal, CCNxStackTimeout_Never); + assertTrue(result != NULL, "Error reading response from Portal: (%d) %s\nb", errno, strerror(errno)); + + return result; + } + + return NULL; +} + +static CCNxPortal * +_createPortalWithKeystore(const char *keystoreName, const char *keystorePassword) +{ + PARCIdentityFile *identityFile = parcIdentityFile_Create(keystoreName, keystorePassword); + PARCIdentity *identity = parcIdentity_Create(identityFile, PARCIdentityFileAsPARCIdentity); + CCNxPortalFactory *portalFactory = ccnxPortalFactory_Create(identity); + + CCNxPortal *result = ccnxPortalFactory_CreatePortal(portalFactory, ccnxPortalRTA_Message); + + ccnxPortalFactory_Release(&portalFactory); + parcIdentity_Release(&identity); + parcIdentityFile_Release(&identityFile); + + return result; +} + +static MetisControlMainState +_openKeyStore(char *keystorePath, char *keystorePassword) +{ + MetisControlMainState mainState; + + if (keystorePassword == NULL) { + keystorePassword = ccnxKeystoreUtilities_ReadPassword(); + + mainState.keystoreParams = ccnxKeystoreUtilities_OpenFile(keystorePath, keystorePassword); + parcMemory_Deallocate((void *) &keystorePassword); + } else { + mainState.keystoreParams = ccnxKeystoreUtilities_OpenFile(keystorePath, keystorePassword); + } + + return mainState; +} + +int +main(int argc, char *argv[]) +{ + _displayForwarderLogo(); + + if (argc == 2 && strcmp("-h", argv[1]) == 0) { + _displayUsage(argv[0]); + exit(EXIT_SUCCESS); + } + + char *keystorePath = NULL; + char *keystorePassword = NULL; + + PARCList *commands = parcList(parcArrayList_Create(NULL), PARCArrayListAsPARCList); + + // Extract optional keystore and password, and optional commands. + if (!_parseArgs(argc, argv, &keystorePath, &keystorePassword, commands)) { + parcList_Release(&commands); + exit(EXIT_FAILURE); + } + + if (keystorePath == NULL) { + printf("No keystore specified. Will try default.\n"); + } else { + printf("Using keystore: %s\n", keystorePath); + } + + parcSecurity_Init(); + + MetisControlMainState mainState = _openKeyStore(keystorePath, keystorePassword); + if (mainState.keystoreParams == NULL) { + printf("Could not open keystore '%s'\n", keystorePath == NULL ? "~/.ccnx/.ccnx_keystore.p12" : keystorePath); + exit(EXIT_FAILURE); + } + + mainState.controlPortal = _createPortalWithKeystore(ccnxKeystoreUtilities_GetFileName(mainState.keystoreParams), + ccnxKeystoreUtilities_GetPassword(mainState.keystoreParams)); + parcSecurity_Fini(); + + mainState.controlState = metisControlState_Create(&mainState, _writeAndReadMessage); + + metisControlState_RegisterCommand(mainState.controlState, metisControlRoot_HelpCreate(mainState.controlState)); + metisControlState_RegisterCommand(mainState.controlState, metisControlRoot_Create(mainState.controlState)); + + if (parcList_Size(commands) > 0) { + metisControlState_DispatchCommand(mainState.controlState, commands); + } else { + metisControlState_Interactive(mainState.controlState); + } + + parcList_Release(&commands); + + metisControlState_Destroy(&mainState.controlState); + + keystoreParams_Destroy(&mainState.keystoreParams); + + ccnxPortal_Release(&mainState.controlPortal); + + return EXIT_SUCCESS; +} diff --git a/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt b/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt new file mode 100644 index 00000000..957cab17 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_daemon/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(metis_daemon metis_daemon.c) +target_link_libraries(metis_daemon ${METIS_LINK_LIBRARIES}) + +install(TARGETS metis_daemon RUNTIME DESTINATION bin) diff --git a/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c b/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c new file mode 100644 index 00000000..b6fcfcf7 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/metis_daemon/metis_daemon.c @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/stat.h> +#include <stdio.h> +#include <unistd.h> + +#include <parc/algol/parc_FileOutputStream.h> +#include <parc/logging/parc_LogLevel.h> +#include <parc/logging/parc_LogReporterTextStdout.h> +#include <parc/logging/parc_LogReporterFile.h> + +#include <LongBow/runtime.h> + +#include <ccnx/forwarder/metis/core/metis_Forwarder.h> +#include <ccnx/forwarder/metis/core/metis_Dispatcher.h> +#include <ccnx/forwarder/metis/metis_About.h> + +static void +header(void) +{ + printf("%s\n", metisAbout_About()); + + printf(" __ __ _ _\n"); + printf(" | \\/ | ___ | |_ (_) ___\n"); + printf(" | |\\/| | / _ \\| __|| |/ __|\n"); + printf(" | | | || __/| |_ | |\\__ \\\n"); + printf(" |_| |_| \\___| \\__||_||___/\n"); + + printf("\n"); +} + +static void +_usage(int exitCode) +{ + printf("Usage: metis_daemon [--port port] [--daemon] [--capacity objectStoreSize] [--log facility=level] [--log-file filename] [--config file]\n"); + printf("\n"); + printf("Metis is the CCNx 1.0 forwarder, which runs on each end system and as a software forwarder\n"); + printf("on intermediate systems. metis_daemon is the program to launch Metis, either as a console program\n"); + printf("or a background daemon (detatched from console). Once running, use the program metis_control to\n"); + printf("configure Metis.\n"); + printf("\n"); + printf("The configuration file contains configuration lines as per metis_control\n"); + printf("If logging level or content store capacity is set in the configuraiton file, it overrides the command-line\n"); + printf("When a configuration file is specified, no default listeners on 'port' are setup. Only 'add listener' lines\n"); + printf("in the configuration file matter.\n"); + printf("\n"); + printf("If no configuration file is specified, metis_daemon will listen on TCP and UDP ports specified by\n"); + printf("the --port flag (or default port). It will listen on both IPv4 and IPv6 if available.\n"); + printf("\n"); + printf("Options:\n"); + printf("--port = tcp port for in-bound connections\n"); + printf("--daemon = start as daemon process\n"); + printf("--objectStoreSize = maximum number of content objects to cache\n"); + printf("--log = sets a facility to a given log level. You can have multiple of these.\n"); + printf(" facilities: all, config, core, io, message, processor\n"); + printf(" levels: debug, info, notice, warning, error, critical, alert, off\n"); + printf(" example: metis_daemon --log io=debug --log core=off\n"); + printf("--log-file = file to write log messages to (required in daemon mode)\n"); + printf("--config = configuration filename\n"); + printf("\n"); + exit(exitCode); +} + +static void +_setLogLevelToLevel(int logLevelArray[MetisLoggerFacility_END], MetisLoggerFacility facility, const char *levelString) +{ + PARCLogLevel level = parcLogLevel_FromString(levelString); + + if (level < PARCLogLevel_All) { + // we have a good facility and level + logLevelArray[facility] = level; + } else { + printf("Invalid log level string %s\n", levelString); + _usage(EXIT_FAILURE); + } +} + +/** + * string: "facility=level" + * Set the right thing in the logger + */ +static void +_setLogLevel(int logLevelArray[MetisLoggerFacility_END], const char *string) +{ + char *tofree = parcMemory_StringDuplicate(string, strlen(string)); + char *p = tofree; + + char *facilityString = strsep(&p, "="); + if (facilityString) { + char *levelString = p; + + if (strcasecmp(facilityString, "all") == 0) { + for (MetisLoggerFacility facility = 0; facility < MetisLoggerFacility_END; facility++) { + _setLogLevelToLevel(logLevelArray, facility, levelString); + } + } else { + MetisLoggerFacility facility; + for (facility = 0; facility < MetisLoggerFacility_END; facility++) { + if (strcasecmp(facilityString, metisLogger_FacilityString(facility)) == 0) { + break; + } + } + + if (facility < MetisLoggerFacility_END) { + _setLogLevelToLevel(logLevelArray, facility, levelString); + } else { + printf("Invalid facility string %s\n", facilityString); + _usage(EXIT_FAILURE); + } + } + } + + parcMemory_Deallocate((void **) &tofree); +} + +static void +_daemonize(void) +{ + if (getppid() == 1) { + // already a daemon + return; + } + + int forkReturn = fork(); + trapUnexpectedStateIf(forkReturn < 0, "Fork error"); + + if (forkReturn > 0) { + // parent exits + exit(EXIT_SUCCESS); + } + + // Child daemon detaches + printf("child continuing, pid = %u\n", getpid()); + + // get a new process group independent from old parent + setsid(); + + /* close all descriptors */ + for (int i = getdtablesize(); i >= 0; --i) { + close(i); + } + + // reset errno because it might be seg to EBADF from the close calls above + errno = 0; + + // Redirect stdin and stdout and stderr to /dev/null + const char *devnull = "/dev/null"; + int nullfile = open(devnull, O_RDWR); + assertTrue(nullfile >= 0, "Error opening file '%s': (%d) %s", devnull, errno, strerror(errno)); + + int ret; + ret = dup(nullfile); + assertTrue(ret == 1, "Error duping fd 1 got %d file: (%d) %s", ret, errno, strerror(errno)); + ret = dup(nullfile); + assertTrue(ret == 2, "Error duping fd 2, got %d file: (%d) %s", ret, errno, strerror(errno)); + + // metisForwarder will capture signals +} + +static MetisLogger * +_createLogfile(const char *logfile) +{ + int logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, S_IWUSR | S_IRUSR); + if (logfd < 0) { + fprintf(stderr, "Error opening %s for writing: (%d) %s\n", logfile, errno, strerror(errno)); + exit(EXIT_FAILURE); + } + + chmod(logfile, S_IRWXU); + + PARCFileOutputStream *fos = parcFileOutputStream_Create(logfd); + PARCOutputStream *pos = parcFileOutputStream_AsOutputStream(fos); + PARCLogReporter *reporter = parcLogReporterFile_Create(pos); + + MetisLogger *logger = metisLogger_Create(reporter, parcClock_Wallclock()); + + parcOutputStream_Release(&pos); + parcLogReporter_Release(&reporter); + + return logger; +} + +int +main(int argc, const char *argv[]) +{ + header(); + + uint16_t port = PORT_NUMBER; + uint16_t configurationPort = 2001; + bool daemon = false; + int capacity = -1; + const char *configFileName = NULL; + + char *logfile = NULL; + + if (argc == 2 && strcasecmp(argv[1], "-h") == 0) { + _usage(EXIT_SUCCESS); + } + + int logLevelArray[MetisLoggerFacility_END]; + for (int i = 0; i < MetisLoggerFacility_END; i++) { + logLevelArray[i] = -1; + } + + for (int i = 0; i < argc; i++) { + if (argv[i][0] == '-') { + if (strcmp(argv[i], "--config") == 0) { + configFileName = argv[i + 1]; + i++; + } else if (strcmp(argv[i], "--port") == 0) { + port = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--daemon") == 0) { + daemon = true; + } else if (strcmp(argv[i], "--capacity") == 0 || strcmp(argv[i], "-c") == 0) { + capacity = atoi(argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--log") == 0) { + _setLogLevel(logLevelArray, argv[i + 1]); + i++; + } else if (strcmp(argv[i], "--log-file") == 0) { + if (logfile) { + // error cannot repeat + fprintf(stderr, "Cannot specify --log-file more than once\n"); + _usage(EXIT_FAILURE); + } + + logfile = parcMemory_StringDuplicate(argv[i + 1], strlen(argv[i + 1])); + i++; + } else { + _usage(EXIT_FAILURE); + } + } + } + + // set restrictive umask, in case we create any files + umask(027); + + if (daemon && (logfile == NULL)) { + fprintf(stderr, "Must specify a logfile when running in daemon mode\n"); + _usage(EXIT_FAILURE); + } + + if (daemon) { + // inside this call, parent will EXIT_SUCCESS and child will continue + _daemonize(); + } + + MetisLogger *logger = NULL; + if (logfile) { + logger = _createLogfile(logfile); + parcMemory_Deallocate((void **) &logfile); + } else { + PARCLogReporter *stdoutReporter = parcLogReporterTextStdout_Create(); + logger = metisLogger_Create(stdoutReporter, parcClock_Wallclock()); + parcLogReporter_Release(&stdoutReporter); + } + + for (int i = 0; i < MetisLoggerFacility_END; i++) { + if (logLevelArray[i] > -1) { + metisLogger_SetLogLevel(logger, i, logLevelArray[i]); + } + } + + + // this will update the clock to the tick clock + MetisForwarder *metis = metisForwarder_Create(logger); + + MetisConfiguration *configuration = metisForwarder_GetConfiguration(metis); + + if (capacity > -1) { + metisConfiguration_SetObjectStoreSize(configuration, capacity); + } + + metisConfiguration_StartCLI(configuration, configurationPort); + + if (configFileName) { + metisForwarder_SetupFromConfigFile(metis, configFileName); + } else { + // NULL to not setup AF_UNIX + metisForwarder_SetupAllListeners(metis, port, NULL); + } + + MetisDispatcher *dispatcher = metisForwarder_GetDispatcher(metis); + + metisLogger_Log(logger, MetisLoggerFacility_Core, PARCLogLevel_Alert, "daemon", "metis running port %d configuration-port %d", port, configurationPort); + + metisDispatcher_Run(dispatcher); + + metisLogger_Log(logger, MetisLoggerFacility_Core, PARCLogLevel_Alert, "daemon", "metis exiting port %d", port); + + metisForwarder_Destroy(&metis); + + sleep(2); + + metisLogger_Release(&logger); + return 0; +} diff --git a/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c b/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c new file mode 100644 index 00000000..fd1b4110 --- /dev/null +++ b/metis/ccnx/forwarder/metis/command-line/test/test_metis-control.c @@ -0,0 +1,68 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <LongBow/unit-test.h> +#include <LongBow/debugging.h> + +// Include the file(s) containing the functions to be tested. +// This permits internal static functions to be visible to this Test Framework. +#include "../metis-control/metisControl_main.c" + +LONGBOW_TEST_RUNNER(test_metis_control) +{ + // The following Test Fixtures will run their corresponding Test Cases. + // Test Fixtures are run in the order specified, but all tests should be idempotent. + // Never rely on the execution order of tests or share state between them. + LONGBOW_RUN_TEST_FIXTURE(Global); +} + +// The Test Runner calls this function once before any Test Fixtures are run. +LONGBOW_TEST_RUNNER_SETUP(test_metis_control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +// The Test Runner calls this function once after all the Test Fixtures are run. +LONGBOW_TEST_RUNNER_TEARDOWN(test_metis_control) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE(Global) +{ + LONGBOW_RUN_TEST_CASE(Global, myTest); +} + +LONGBOW_TEST_FIXTURE_SETUP(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_FIXTURE_TEARDOWN(Global) +{ + return LONGBOW_STATUS_SUCCEEDED; +} + +LONGBOW_TEST_CASE(Global, myTest) +{ +} + +int +main(int argc, char *argv[]) +{ + LongBowRunner *testRunner = LONGBOW_TEST_RUNNER_CREATE(test_metis_control); + int exitStatus = longBowMain(argc, argv, testRunner, NULL); + longBowTestRunner_Destroy(&testRunner); + exit(exitStatus); +} |