From 54a315716b6773507cc0bffa236f10f11c5d2400 Mon Sep 17 00:00:00 2001 From: Pawel Kubik Date: Mon, 14 Sep 2015 12:20:36 +0200 Subject: [PATCH 01/16] CLI bash completion fix [Feature] CLI bash completion fix [Cause] Bash completion broken after commit 4a9c93f [Solution] Bash completion script template fix [Verification] Build, install, check CLI completion Change-Id: Iff043a983d042e4181b33b0f6c4e415befcc22ed --- cli/support/vsm-completion.sh.in | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/support/vsm-completion.sh.in b/cli/support/vsm-completion.sh.in index 74a02a5..704e162 100755 --- a/cli/support/vsm-completion.sh.in +++ b/cli/support/vsm-completion.sh.in @@ -2,8 +2,13 @@ [ -z "$BASH_VERSION" ] && return __@PROJECT_NAME@_cli() { - words=`@CLI_CODENAME@ --bash-completion ${COMP_WORDS[COMP_CWORD]}` - COMPREPLY=($(compgen -W "$words" -- ${COMP_WORDS[COMP_CWORD]})) + if [ "${COMP_WORDS[COMP_CWORD]}" = "" ]; then + comp="''" + else + comp= + fi + words=`@CLI_CODENAME@ --bash-completion $(echo "${COMP_WORDS[@]:1}" | sed 's/ = /=/g') $comp` + COMPREPLY=($(compgen -W "$words" -- "${COMP_WORDS[COMP_CWORD]}")) } complete -F __@PROJECT_NAME@_cli vsm -- 2.7.4 From 3166537b1ec126be9343551b0d7801a89c08e637 Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Wed, 16 Sep 2015 11:22:22 +0200 Subject: [PATCH 02/16] lxcpp: Setting up the control terminal [Feature] Setting up the control terminal in Attach [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I6b1dced4f9a16c04e82b122679f86b90be29d3d1 --- libs/lxcpp/commands/attach.cpp | 51 +++++++++++++++++++++++++++++++++++++++++- libs/lxcpp/commands/attach.hpp | 3 +++ libs/lxcpp/container-impl.cpp | 1 + libs/lxcpp/credentials.hpp | 2 -- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/libs/lxcpp/commands/attach.cpp b/libs/lxcpp/commands/attach.cpp index 0ff856e..01a811b 100644 --- a/libs/lxcpp/commands/attach.cpp +++ b/libs/lxcpp/commands/attach.cpp @@ -31,9 +31,13 @@ #include "lxcpp/credentials.hpp" #include "utils/exception.hpp" +#include "utils/fd-utils.hpp" +#include "logger/logger.hpp" #include #include +#include +#include #include @@ -67,6 +71,35 @@ void setupMountPoints() */ } +bool setupControlTTY(const int ttyFD) +{ + if (!::isatty(ttyFD)) { + return false; + } + + if (::setsid() < 0) { + return false; + } + + if (::ioctl(ttyFD, TIOCSCTTY, NULL) < 0) { + return false; + } + + if (::dup2(ttyFD, STDIN_FILENO) < 0) { + return false; + } + + if (::dup2(ttyFD, STDOUT_FILENO) < 0) { + return false; + } + + if (::dup2(ttyFD, STDERR_FILENO) < 0) { + return false; + } + + return true; +} + int execFunction(void* call) { try { @@ -83,6 +116,7 @@ Attach::Attach(lxcpp::ContainerImpl& container, Container::AttachCall& userCall, const uid_t uid, const gid_t gid, + const std::string& ttyPath, const std::vector& supplementaryGids, const int capsToKeep, const std::string& workDirInContainer, @@ -98,10 +132,18 @@ Attach::Attach(lxcpp::ContainerImpl& container, mEnvToKeep(envToKeep), mEnvToSet(envToSet) { + mTTYFD = ::open(ttyPath.c_str(), O_RDWR | O_NOCTTY); + if (mTTYFD < 0) { + const std::string msg = "open() failed: " + + utils::getSystemErrorMessage(); + LOGE(msg); + throw BadArgument(msg); + } } Attach::~Attach() { + utils::close(mTTYFD); } void Attach::execute() @@ -113,6 +155,7 @@ void Attach::execute() mUserCall, mUid, mGid, + mTTYFD, mSupplementaryGids, mCapsToKeep, mEnvToKeep, @@ -127,13 +170,14 @@ void Attach::execute() intermChannel.setRight(); interm(intermChannel, call); intermChannel.shutdown(); - ::_exit(0); + ::_exit(EXIT_SUCCESS); } } int Attach::child(const Container::AttachCall& call, const uid_t uid, const gid_t gid, + const int ttyFD, const std::vector& supplementaryGids, const int capsToKeep, const std::vector& envToKeep, @@ -155,6 +199,11 @@ int Attach::child(const Container::AttachCall& call, lxcpp::setuid(uid); + // Set control TTY + if(!setupControlTTY(ttyFD)) { + ::_exit(EXIT_FAILURE); + } + // Run user's code return call(); } diff --git a/libs/lxcpp/commands/attach.hpp b/libs/lxcpp/commands/attach.hpp index 36c57ba..2c1f365 100644 --- a/libs/lxcpp/commands/attach.hpp +++ b/libs/lxcpp/commands/attach.hpp @@ -58,6 +58,7 @@ public: Container::AttachCall& userCall, const uid_t uid, const gid_t gid, + const std::string& ttyPath, const std::vector& supplementaryGids, const int capsToKeep, const std::string& workDirInContainer, @@ -72,6 +73,7 @@ private: const Container::AttachCall& mUserCall; const uid_t mUid; const gid_t mGid; + int mTTYFD; const std::vector& mSupplementaryGids; const int mCapsToKeep; const std::string& mWorkDirInContainer; @@ -82,6 +84,7 @@ private: static int child(const Container::AttachCall& call, const uid_t uid, const gid_t gid, + const int ttyFD, const std::vector& supplementaryGids, const int capsToKeep, const std::vector& envToKeep, diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 73e5254..8249009 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -152,6 +152,7 @@ void ContainerImpl::attach(Container::AttachCall& call, call, /*uid in container*/ 0, /*gid in container*/ 0, + "/dev/tty", /*supplementary gids in container*/ {}, /*capsToKeep*/ 0, cwdInContainer, diff --git a/libs/lxcpp/credentials.hpp b/libs/lxcpp/credentials.hpp index df00ce5..ab1a490 100644 --- a/libs/lxcpp/credentials.hpp +++ b/libs/lxcpp/credentials.hpp @@ -36,8 +36,6 @@ void setgid(const gid_t gid); void setuid(const uid_t uid); - - } // namespace lxcpp #endif // LXCPP_CREDENTIALS_HPP \ No newline at end of file -- 2.7.4 From 61e31300c53fb090f48930f4d4462e6bc9218be1 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Wed, 29 Jul 2015 13:27:25 +0200 Subject: [PATCH 03/16] lxcpp: Initial implementation of the start API call [Feature] Initial implementation of the start API call. [Verification] Build, install, run tests Significant changes related to the start API call: - Start command that daemonizes and execs guard binary - Guard process implementation that execs the container's init Additional changes in this commit supporting the Start API call: - API extension to Channel class, support close on exec and be able to survive (as a FD) exec() call - set close on exec in persistent file logger - new logger helper to setup the logger - add pid to the log format Change-Id: I2d9648e2a861add2aa1bd1d66587bff2a109cc9c --- CMakeLists.txt | 8 +- common/utils/channel.cpp | 55 ++++++++++++- common/utils/channel.hpp | 27 ++++++- libs/logger/backend-persistent-file.hpp | 9 ++- libs/logger/formatter.cpp | 5 +- libs/logger/level.hpp | 2 +- libs/logger/logger.cpp | 38 +++++++++ libs/logger/logger.hpp | 28 ++++++- libs/lxcpp/CMakeLists.txt | 10 ++- libs/lxcpp/commands/attach.cpp | 2 + libs/lxcpp/commands/start.cpp | 135 ++++++++++++++++++++++++++++++++ libs/lxcpp/commands/start.hpp | 66 ++++++++++++++++ libs/lxcpp/container-config.hpp | 111 ++++++++++++++++++++++++++ libs/lxcpp/container-impl.cpp | 12 ++- libs/lxcpp/container-impl.hpp | 28 ++----- libs/lxcpp/container.hpp | 5 ++ libs/lxcpp/exception.hpp | 5 ++ libs/lxcpp/guard/CMakeLists.txt | 37 +++++++++ libs/lxcpp/guard/guard.cpp | 100 +++++++++++++++++++++++ libs/lxcpp/guard/guard.hpp | 39 +++++++++ libs/lxcpp/guard/main.cpp | 49 ++++++++++++ libs/lxcpp/logger-config.cpp | 48 ++++++++++++ libs/lxcpp/logger-config.hpp | 65 +++++++++++++++ libs/lxcpp/utils.cpp | 122 +++++++++++++++++++++++++++++ libs/lxcpp/utils.hpp | 54 +++++++++++++ packaging/vasum.spec | 1 + 26 files changed, 1022 insertions(+), 39 deletions(-) create mode 100644 libs/lxcpp/commands/start.cpp create mode 100644 libs/lxcpp/commands/start.hpp create mode 100644 libs/lxcpp/container-config.hpp create mode 100644 libs/lxcpp/guard/CMakeLists.txt create mode 100644 libs/lxcpp/guard/guard.cpp create mode 100644 libs/lxcpp/guard/guard.hpp create mode 100644 libs/lxcpp/guard/main.cpp create mode 100644 libs/lxcpp/logger-config.cpp create mode 100644 libs/lxcpp/logger-config.hpp create mode 100644 libs/lxcpp/utils.cpp create mode 100644 libs/lxcpp/utils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b98eee..0409167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,10 +143,10 @@ ENDIF(NOT DEFINED PYTHON_SITELIB) SET(COMMON_FOLDER ${PROJECT_SOURCE_DIR}/common) SET(LIBS_FOLDER ${PROJECT_SOURCE_DIR}/libs) SET(LOGGER_FOLDER ${PROJECT_SOURCE_DIR}/libs/logger) -SET(LXCPP_FOLDER ${PROJECT_SOURCE_DIR}/libs/lxcpp) SET(DBUS_FOLDER ${PROJECT_SOURCE_DIR}/libs/dbus) SET(CONFIG_FOLDER ${PROJECT_SOURCE_DIR}/libs/config) SET(IPC_FOLDER ${PROJECT_SOURCE_DIR}/libs/ipc) +SET(LXCPP_FOLDER ${PROJECT_SOURCE_DIR}/libs/lxcpp) SET(CLIENT_FOLDER ${PROJECT_SOURCE_DIR}/client) SET(SERVER_FOLDER ${PROJECT_SOURCE_DIR}/server) SET(ZONE_SUPPORT_FOLDER ${PROJECT_SOURCE_DIR}/zone-support) @@ -180,6 +180,10 @@ IF(NOT DEFINED DATA_DIR) SET(DATA_DIR "${CMAKE_INSTALL_PREFIX}/share") ENDIF(NOT DEFINED DATA_DIR) +IF(NOT DEFINED LIBEXEC_DIR) + SET(LIBEXEC_DIR "${CMAKE_INSTALL_PREFIX}/libexec") +ENDIF(NOT DEFINED LIBEXEC_DIR) + IF(NOT DEFINED RUN_DIR) SET(RUN_DIR "/var/run") ENDIF(NOT DEFINED RUN_DIR) @@ -190,12 +194,12 @@ SET(VSM_UNIT_TESTS_IPC_SOCKET_PATH ${RUN_DIR}/vasum-ipc-unit-tests.socket) ADD_SUBDIRECTORY(${COMMON_FOLDER}) ADD_SUBDIRECTORY(${LOGGER_FOLDER}) -ADD_SUBDIRECTORY(${LXCPP_FOLDER}) IF(NOT WITHOUT_DBUS) ADD_SUBDIRECTORY(${DBUS_FOLDER}) ENDIF(NOT WITHOUT_DBUS) ADD_SUBDIRECTORY(${CONFIG_FOLDER}) ADD_SUBDIRECTORY(${IPC_FOLDER}) +ADD_SUBDIRECTORY(${LXCPP_FOLDER}) ADD_SUBDIRECTORY(${CLIENT_FOLDER}) ADD_SUBDIRECTORY(${SERVER_FOLDER}) IF(NOT WITHOUT_DBUS) diff --git a/common/utils/channel.cpp b/common/utils/channel.cpp index fbb110d..3a0dd31 100644 --- a/common/utils/channel.cpp +++ b/common/utils/channel.cpp @@ -27,6 +27,8 @@ #include "logger/logger.hpp" +#include +#include #include namespace { @@ -36,10 +38,15 @@ const int RIGHT = 1; namespace utils { -Channel::Channel() +Channel::Channel(const bool closeOnExec) : mSocketIndex(-1) { - if (::socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, mSockets) < 0) { + int flags = SOCK_STREAM; + if (closeOnExec) { + flags |= SOCK_CLOEXEC; + }; + + if (::socketpair(AF_LOCAL, flags, 0, mSockets.data()) < 0) { const std::string msg = "socketpair() failed: " + utils::getSystemErrorMessage(); LOGE(msg); @@ -47,23 +54,36 @@ Channel::Channel() } } +Channel::Channel(const int fd) + : mSocketIndex(LEFT), + mSockets{{fd, -1}} +{ + assert(fd >= 0); +} + Channel::~Channel() { closeSocket(LEFT); closeSocket(RIGHT); } +/* + * This function has to be safe in regard to signal(7) + */ void Channel::setLeft() { mSocketIndex = LEFT; - utils::close(mSockets[RIGHT]); + ::close(mSockets[RIGHT]); mSockets[RIGHT] = -1; } +/* + * This function has to be safe in regard to signal(7) + */ void Channel::setRight() { mSocketIndex = RIGHT; - utils::close(mSockets[LEFT]); + ::close(mSockets[LEFT]); mSockets[LEFT] = -1; } @@ -73,6 +93,33 @@ void Channel::shutdown() closeSocket(mSocketIndex); } +int Channel::getFD() +{ + assert(mSocketIndex != -1 && "Channel's end isn't set"); + return mSockets[mSocketIndex]; +} + +int Channel::getLeftFD() +{ + return mSockets[LEFT]; +} + +int Channel::getRightFD() +{ + return mSockets[RIGHT]; +} + +void Channel::setCloseOnExec(const bool closeOnExec) +{ + const int fd = getFD(); + + if (closeOnExec) { + ::fcntl(fd, F_SETFD, ::fcntl(fd, F_GETFD) | FD_CLOEXEC); + } else { + ::fcntl(fd, F_SETFD, ::fcntl(fd, F_GETFD) & ~FD_CLOEXEC); + } +} + void Channel::closeSocket(int socketIndex) { utils::shutdown(mSockets[socketIndex]); diff --git a/common/utils/channel.hpp b/common/utils/channel.hpp index f537121..3b60daa 100644 --- a/common/utils/channel.hpp +++ b/common/utils/channel.hpp @@ -26,6 +26,8 @@ #define COMMON_UTILS_CHANNEL_HPP #include "utils/fd-utils.hpp" + +#include #include namespace utils { @@ -35,7 +37,8 @@ namespace utils { */ class Channel { public: - Channel(); + explicit Channel(const bool closeOnExec = true); + explicit Channel(const int fd); ~Channel(); Channel(const Channel&) = delete; @@ -72,12 +75,32 @@ public: template Data read(); + /** + * Get an active file descriptor + */ + int getFD(); + + /** + * Gen the left file descriptor + */ + int getLeftFD(); + + /** + * Gen the right file descriptor + */ + int getRightFD(); + + /** + * Sets close on exec on an active fd to either true or false + */ + void setCloseOnExec(const bool closeOnExec); + private: void closeSocket(int socketIndex); int mSocketIndex; - int mSockets[2]; + std::array mSockets; }; template diff --git a/libs/logger/backend-persistent-file.hpp b/libs/logger/backend-persistent-file.hpp index 74d2def..5cbd714 100644 --- a/libs/logger/backend-persistent-file.hpp +++ b/libs/logger/backend-persistent-file.hpp @@ -27,7 +27,9 @@ #include "logger/backend.hpp" +#include #include +#include namespace logger { @@ -35,7 +37,12 @@ class PersistentFileBackend : public LogBackend { public: PersistentFileBackend(const std::string& filePath) : mfilePath(filePath), - mOut(mfilePath, std::ios::app) {} + mOut(mfilePath, std::ios::app) + { + using filebufType = __gnu_cxx::stdio_filebuf; + const int fd = static_cast(mOut.rdbuf())->fd(); + ::fcntl(fd, F_SETFD, ::fcntl(fd, F_GETFD) | FD_CLOEXEC); + } void log(LogLevel logLevel, const std::string& file, diff --git a/libs/logger/formatter.cpp b/libs/logger/formatter.cpp index 17bed53..06404f2 100644 --- a/libs/logger/formatter.cpp +++ b/libs/logger/formatter.cpp @@ -26,6 +26,7 @@ #include "logger/formatter.hpp" #include "utils/ccolor.hpp" +#include #include #include #include @@ -39,7 +40,7 @@ namespace { const int TIME_COLUMN_LENGTH = 12; const int SEVERITY_COLUMN_LENGTH = 8; -const int THREAD_COLUMN_LENGTH = 3; +const int PROCESS_COLUMN_LENGTH = 8; const int FILE_COLUMN_LENGTH = 60; std::atomic gNextThreadId(1); @@ -122,7 +123,7 @@ std::string LogFormatter::getHeader(LogLevel logLevel, std::ostringstream logLine; logLine << getCurrentTime() << ' ' << std::left << std::setw(SEVERITY_COLUMN_LENGTH) << '[' + toString(logLevel) + ']' - << std::right << std::setw(THREAD_COLUMN_LENGTH) << getCurrentThread() << ": " + << std::right << std::setw(PROCESS_COLUMN_LENGTH) << ::getpid() << "/" << getCurrentThread() << ": " << std::left << std::setw(FILE_COLUMN_LENGTH) << file + ':' + std::to_string(line) + ' ' + func + ':'; return logLine.str(); diff --git a/libs/logger/level.hpp b/libs/logger/level.hpp index fd33343..99dc235 100644 --- a/libs/logger/level.hpp +++ b/libs/logger/level.hpp @@ -33,7 +33,7 @@ namespace logger { * @brief Available log levels * @ingroup libLogger */ -enum class LogLevel { +enum class LogLevel : int { TRACE, ///< Most detailed log level DEBUG, ///< Debug logs INFO, ///< Information diff --git a/libs/logger/logger.cpp b/libs/logger/logger.cpp index ec0855b..fbb85d6 100644 --- a/libs/logger/logger.cpp +++ b/libs/logger/logger.cpp @@ -40,6 +40,44 @@ std::mutex gLogMutex; } // namespace +void setupLogger(const LogType type, + const LogLevel level, + const std::string &arg) +{ + if (type == LogType::LOG_FILE || type == LogType::LOG_PERSISTENT_FILE) { + if (arg.empty()) { + throw std::runtime_error("Path needs to be specified in the agument"); + } + } + + switch(type) { + case LogType::LOG_NULL: + Logger::setLogBackend(new NullLogger()); + break; +#ifdef HAVE_SYSTEMD + case LogType::LOG_JOURNALD: + Logger::setLogBackend(new SystemdJournalBackend()); + break; +#endif + case LogType::LOG_FILE: + Logger::setLogBackend(new FileBackend(arg)); + break; + case LogType::LOG_PERSISTENT_FILE: + Logger::setLogBackend(new PersistentFileBackend(arg)); + break; + case LogType::LOG_SYSLOG: + Logger::setLogBackend(new SyslogBackend()); + break; + case LogType::LOG_STDERR: + Logger::setLogBackend(new StderrBackend()); + break; + default: + throw std::runtime_error("Bad logger type passed"); + } + + Logger::setLogLevel(level); +} + void Logger::logMessage(LogLevel logLevel, const std::string& message, const std::string& file, diff --git a/libs/logger/logger.hpp b/libs/logger/logger.hpp index 0a1ebe6..92baee1 100644 --- a/libs/logger/logger.hpp +++ b/libs/logger/logger.hpp @@ -61,9 +61,14 @@ #define COMMON_LOGGER_LOGGER_HPP #include "logger/level.hpp" +#include "logger/backend-null.hpp" +#ifdef HAVE_SYSTEMD +#include "logger/backend-journal.hpp" +#endif #include "logger/backend-file.hpp" -#include "logger/backend-stderr.hpp" #include "logger/backend-persistent-file.hpp" +#include "logger/backend-syslog.hpp" +#include "logger/backend-stderr.hpp" #include #include @@ -74,6 +79,27 @@ namespace logger { +enum class LogType : int { + LOG_NULL, + LOG_JOURNALD, + LOG_FILE, + LOG_PERSISTENT_FILE, + LOG_SYSLOG, + LOG_STDERR +}; + +/** + * A helper function to easily and completely setup a new logger + * + * @param type logger type to be set up + * @param level maximum log level that will be logged + * @param arg an argument used by some loggers, specific to them + * (e.g. file name for file loggers) + */ +void setupLogger(const LogType type, + const LogLevel level, + const std::string &arg = ""); + class LogBackend; class Logger { diff --git a/libs/lxcpp/CMakeLists.txt b/libs/lxcpp/CMakeLists.txt index 203ddd3..8f391e8 100644 --- a/libs/lxcpp/CMakeLists.txt +++ b/libs/lxcpp/CMakeLists.txt @@ -19,6 +19,9 @@ PROJECT(lxcpp) +SET(GUARD_CODENAME "${PROJECT_NAME}-guard") +ADD_SUBDIRECTORY(guard) + MESSAGE(STATUS "") MESSAGE(STATUS "Generating makefile for the liblxcpp...") FILE(GLOB HEADERS *.hpp) @@ -37,15 +40,18 @@ ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS} ${SRCS_COMMANDS}) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES SOVERSION ${_LIB_SOVERSION_} VERSION ${_LIB_VERSION_} + COMPILE_DEFINITIONS GUARD_PATH="${LIBEXEC_DIR}/${GUARD_CODENAME}" ) ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger) ## Link libraries ############################################################## FIND_PACKAGE(Boost COMPONENTS system filesystem) +PKG_CHECK_MODULES(LXCPP_DEPS REQUIRED glib-2.0) + INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) -INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger) +INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS} ${LXCPP_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger Config) ## Generate the pc file ######################################################## CONFIGURE_FILE(${PC_FILE}.in ${CMAKE_CURRENT_BINARY_DIR}/${PC_FILE} @ONLY) diff --git a/libs/lxcpp/commands/attach.cpp b/libs/lxcpp/commands/attach.cpp index 01a811b..419f651 100644 --- a/libs/lxcpp/commands/attach.cpp +++ b/libs/lxcpp/commands/attach.cpp @@ -167,6 +167,8 @@ void Attach::execute() parent(intermChannel, interPid); intermChannel.shutdown(); } else { + // TODO: only safe functions mentioned in signal(7) should be used below. + // This is not the case now, needs fixing. intermChannel.setRight(); interm(intermChannel, call); intermChannel.shutdown(); diff --git a/libs/lxcpp/commands/start.cpp b/libs/lxcpp/commands/start.cpp new file mode 100644 index 0000000..f68654a --- /dev/null +++ b/libs/lxcpp/commands/start.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Implementation of starting a container + */ + +#include "lxcpp/commands/start.hpp" +#include "lxcpp/exception.hpp" +#include "lxcpp/process.hpp" +#include "lxcpp/utils.hpp" + +#include "logger/logger.hpp" +#include "config/manager.hpp" + +#include + + +namespace lxcpp { + + +Start::Start(ContainerConfig &config) + : mConfig(config), + mChannel(false), + mGuardPath(GUARD_PATH), + mChannelFD(std::to_string(mChannel.getRightFD())) +{ +} + +Start::~Start() +{ +} + +void Start::execute() +{ + LOGD("Forking daemonize and guard processes. Execing guard libexec binary."); + LOGD("Logging will cease now. It should be restored using some new facility in the guard process."); + const pid_t pid = lxcpp::fork(); + + if (pid > 0) { + mChannel.setLeft(); + parent(pid); + } else { + // Below this point only safe functions mentioned in signal(7) are allowed. + mChannel.setRight(); + daemonize(); + ::_exit(EXIT_FAILURE); + } +} + +void Start::parent(const pid_t pid) +{ + int status = lxcpp::waitpid(pid); + if (status != EXIT_SUCCESS) { + const std::string msg = "Problem with a daemonize process"; + LOGE(msg); + throw ProcessSetupException(msg); + } + + // Send the config to the guard process + config::saveToFD(mChannel.getFD(), mConfig); + + // Read the pids from the guard process + mConfig.mGuardPid = mChannel.read(); + mConfig.mInitPid = mChannel.read(); + + mChannel.shutdown(); + + if (mConfig.mGuardPid <= 0 || mConfig.mInitPid <= 0) { + const std::string msg = "Problem with receiving pids"; + LOGE(msg); + throw ProcessSetupException(msg); + } + + LOGD("Guard PID: " << mConfig.mGuardPid); + LOGD("Init PID: " << mConfig.mInitPid); +} + +void Start::daemonize() +{ + // Double fork() with exit() to reattach the process under the host's init + pid_t pid = ::fork(); + if (pid < 0) { + ::_exit(EXIT_FAILURE); + } + + if (pid == 0) { + // Prepare a clean environment for a guard process: + // - chdir to / so it's independent on other directories + // - null std* fds so it's properly dettached from the terminal + // - set a new session so it cannot reacquire a terminal + + if (::chdir("/") < 0) { + ::_exit(EXIT_FAILURE); + } + if (nullStdFDs() <0) { + ::_exit(EXIT_FAILURE); + } + if (::setsid() < 0) { + ::_exit(EXIT_FAILURE); + } + + // Add name and path of the container to argv. They are not used, but will + // identify the container in the process list in case setProcTitle() fails + // and will guarantee we have enough argv memory to write the title we want. + const char *argv[] = {mGuardPath.c_str(), + mChannelFD.c_str(), + mConfig.mName.c_str(), + mConfig.mRootPath.c_str(), + NULL}; + ::execve(argv[0], const_cast(argv), NULL); + ::_exit(EXIT_FAILURE); + } + + ::_exit(EXIT_SUCCESS); +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/start.hpp b/libs/lxcpp/commands/start.hpp new file mode 100644 index 0000000..758b471 --- /dev/null +++ b/libs/lxcpp/commands/start.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Headers for starting a container + */ + +#ifndef LXCPP_COMMANDS_START_HPP +#define LXCPP_COMMANDS_START_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/container-config.hpp" +#include "utils/channel.hpp" + +#include + + +namespace lxcpp { + + +class Start final: Command { +public: + /** + * Starts the container + * + * In more details it prepares an environment for a guard process, + * starts it, and passes it the configuration through a file descriptor. + * + * @param config container's config + */ + Start(ContainerConfig &config); + ~Start(); + + void execute(); + +private: + ContainerConfig &mConfig; + utils::Channel mChannel; + std::string mGuardPath; + std::string mChannelFD; + + void parent(const pid_t pid); + void daemonize(); +}; + + +} // namespace lxcpp + + +#endif // LXCPP_COMMANDS_START_HPP diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp new file mode 100644 index 0000000..9caad96 --- /dev/null +++ b/libs/lxcpp/container-config.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief A definition of a ContainerConfig struct + */ + +#ifndef LXCPP_CONTAINER_CONFIG_HPP +#define LXCPP_CONTAINER_CONFIG_HPP + +#include "lxcpp/logger-config.hpp" + +#include +#include + +#include +#include +#include + + +namespace lxcpp { + + +struct ContainerConfig { + /** + * Name of the container. + * + * Set: by constructor, cannot be changed afterwards. + * Get: getName() + */ + std::string mName; + + /** + * Path of the root directory of the container. + * + * Set: by contstructor, cannot be changed afterwards. + * Get: getRootPath() + */ + std::string mRootPath; + + /** + * Pid of the guard process. + * + * Set: automatically by the guard process itself. + * Get: getGuardPid() + */ + pid_t mGuardPid; + + /** + * Pid of the container's init process. + * + * Set: automatically by the guard process. + * Get: getInitPid() + */ + pid_t mInitPid; + + /** + * Argv of the container's init process to be executed. + * The path has to be relative to the RootPath. + * + * Set: setInit() + * Get: getInit() + */ + std::vector mInit; + + /** + * Logger to be configured inside the guard process. This logger + * reconfiguration is due to the fact that guard looses standard file + * descriptors and might loose access to other files by mount namespace + * usage. Hence an option to set some other logger that will work + * regardless. E.g. PersistentFile. + * + * Set: setLogger() + * Get: none + */ + LoggerConfig mLogger; + + ContainerConfig() : mGuardPid(-1), mInitPid(-1) {} + + CONFIG_REGISTER + ( + mName, + mRootPath, + mGuardPid, + mInitPid, + mInit, + mLogger + ) +}; + + +} + + +#endif // LXCPP_CONTAINER_CONFIG_HPP diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 8249009..cee6fa4 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -28,6 +28,7 @@ #include "lxcpp/namespace.hpp" #include "lxcpp/capability.hpp" #include "lxcpp/commands/attach.hpp" +#include "lxcpp/commands/start.hpp" #include "logger/logger.hpp" #include "utils/exception.hpp" @@ -120,9 +121,18 @@ pid_t ContainerImpl::getInitPid() const return mConfig.mInitPid; } +void ContainerImpl::setLogger(const logger::LogType type, + const logger::LogLevel level, + const std::string &arg) +{ + mConfig.mLogger.set(type, level, arg); +} + void ContainerImpl::start() { - throw NotImplementedException(); + // TODO: check config consistency and completeness somehow + Start start(mConfig); + start.execute(); } void ContainerImpl::stop() diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 5435d49..1bf44b7 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -25,37 +25,15 @@ #define LXCPP_CONTAINER_IMPL_HPP #include -#include -#include #include +#include "lxcpp/container-config.hpp" #include "lxcpp/container.hpp" #include "lxcpp/namespace.hpp" #include "lxcpp/network.hpp" -#include "utils/channel.hpp" - namespace lxcpp { -struct ContainerConfig { - std::string mName; - std::string mRootPath; - pid_t mGuardPid; - pid_t mInitPid; - std::vector mInit; - - ContainerConfig() : mGuardPid(-1), mInitPid(-1) {} - - CONFIG_REGISTER - ( - mName, - mRootPath, - mGuardPid, - mInitPid, - mInit - ) -}; - class ContainerImpl : public virtual Container { public: @@ -73,6 +51,10 @@ public: const std::vector& getInit(); void setInit(const std::vector &init); + void setLogger(const logger::LogType type, + const logger::LogLevel level, + const std::string &arg); + const std::vector& getNamespaces() const; // Execution actions diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 192f2df..88c3ff8 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -25,6 +25,7 @@ #define LXCPP_CONTAINER_HPP #include "lxcpp/network-config.hpp" +#include "lxcpp/logger-config.hpp" #include #include @@ -60,6 +61,10 @@ public: virtual const std::vector& getInit() = 0; virtual void setInit(const std::vector &init) = 0; + virtual void setLogger(const logger::LogType type, + const logger::LogLevel level, + const std::string &arg) = 0; + // Execution actions virtual void start() = 0; virtual void stop() = 0; diff --git a/libs/lxcpp/exception.hpp b/libs/lxcpp/exception.hpp index 7764282..11a6a0e 100644 --- a/libs/lxcpp/exception.hpp +++ b/libs/lxcpp/exception.hpp @@ -66,6 +66,11 @@ struct CapabilitySetupException: public Exception { : Exception(message) {} }; +struct UtilityException: public Exception { + explicit UtilityException(const std::string& message = "Error during an utility operation") + : Exception(message) {} +}; + struct BadArgument: public Exception { explicit BadArgument(const std::string& message = "Bad argument passed") : Exception(message) {} diff --git a/libs/lxcpp/guard/CMakeLists.txt b/libs/lxcpp/guard/CMakeLists.txt new file mode 100644 index 0000000..6c69300 --- /dev/null +++ b/libs/lxcpp/guard/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# @file CMakeLists.txt +# @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) +# + +MESSAGE(STATUS "") +MESSAGE(STATUS "Generating makefile for the Guard...") +FILE(GLOB guard_SRCS *.cpp *.hpp) + + +## Setup target ################################################################ +ADD_EXECUTABLE(${GUARD_CODENAME} ${guard_SRCS}) + + +## Link libraries ############################################################## +PKG_CHECK_MODULES(GUARD_DEPS REQUIRED glib-2.0) +INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) +INCLUDE_DIRECTORIES(SYSTEM ${GUARD_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(${GUARD_CODENAME} lxcpp) + + +## Install ##################################################################### +INSTALL(TARGETS ${GUARD_CODENAME} DESTINATION ${LIBEXEC_DIR}) diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp new file mode 100644 index 0000000..63c7263 --- /dev/null +++ b/libs/lxcpp/guard/guard.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief LXCPP guard process implementation + */ + +#include "lxcpp/utils.hpp" +#include "lxcpp/guard/guard.hpp" +#include "lxcpp/process.hpp" + +#include "config/manager.hpp" +#include "logger/logger.hpp" + +#include +#include + + +namespace lxcpp { + + +void startContainer(const ContainerConfig &cfg) +{ + std::vector argv; + argv.reserve(cfg.mInit.size() + 1); + for (auto const & it : cfg.mInit) { + argv.push_back(it.c_str()); + } + argv.push_back(nullptr); + + LOGD("Executing container's init: " << argv[0]); + ::execve(argv[0], const_cast(argv.data()), NULL); + ::_exit(EXIT_FAILURE); +} + +int startGuard(int channelFD) +{ + ContainerConfig cfg; + utils::Channel channel(channelFD); + channel.setCloseOnExec(true); + config::loadFromFD(channel.getFD(), cfg); + + logger::setupLogger(cfg.mLogger.getType(), + cfg.mLogger.getLevel(), + cfg.mLogger.getArg()); + + LOGD("Guard started, config & logging restored"); + + try { + LOGD("Setting the guard process title"); + const std::string title = "[LXCPP] " + cfg.mName + " " + cfg.mRootPath; + setProcTitle(title); + } catch (std::exception &e) { + // Ignore, this is optional + LOGW("Failed to set the guard process title"); + } + + // TODO: container preparation part 1 + + // TODO: switch to clone + LOGD("Forking container's init process"); + pid_t pid = lxcpp::fork(); + + if (pid == 0) { + // TODO: container preparation part 2 + + startContainer(cfg); + ::_exit(EXIT_FAILURE); + } + + cfg.mGuardPid = ::getpid(); + cfg.mInitPid = pid; + + channel.write(cfg.mGuardPid); + channel.write(cfg.mInitPid); + channel.shutdown(); + + int status = lxcpp::waitpid(pid); + LOGD("Init exited with status: " << status); + return status; +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/guard/guard.hpp b/libs/lxcpp/guard/guard.hpp new file mode 100644 index 0000000..78457ae --- /dev/null +++ b/libs/lxcpp/guard/guard.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief LXCPP guard process header + */ + +#ifndef LXCPP_GUARD_GUARD_HPP +#define LXCPP_GUARD_GUARD_HPP + + +#include "lxcpp/container-config.hpp" +#include "utils/channel.hpp" + +namespace lxcpp { + + +int startGuard(int channelFD); + + +} // namespace lxcpp + +#endif // LXCPP_GUARD_HPP diff --git a/libs/lxcpp/guard/main.cpp b/libs/lxcpp/guard/main.cpp new file mode 100644 index 0000000..c8db30b --- /dev/null +++ b/libs/lxcpp/guard/main.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Main file for the Guard process (libexec) + */ + +#include "lxcpp/guard/guard.hpp" + +#include "utils/fd-utils.hpp" + +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc == 1) { + std::cout << "This file should not be executed by hand" << std::endl; + ::_exit(EXIT_FAILURE); + } + + // NOTE: this might not be required now, but I leave it here not to forget. + // We need to investigate this with vasum and think about possibility of + // poorly written software that leaks file descriptors and might use LXCPP. +#if 0 + for(int fd = 3; fd < ::sysconf(_SC_OPEN_MAX); ++fd) { + utils::close(fd); + } +#endif + + int fd = std::stoi(argv[1]); + return lxcpp::startGuard(fd); +} diff --git a/libs/lxcpp/logger-config.cpp b/libs/lxcpp/logger-config.cpp new file mode 100644 index 0000000..3aed302 --- /dev/null +++ b/libs/lxcpp/logger-config.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsumg.com) + * @brief Logger configuration + */ + +#include "lxcpp/logger-config.hpp" +#include "lxcpp/exception.hpp" + +namespace lxcpp { + + +void LoggerConfig::set(const logger::LogType type, + const logger::LogLevel level, + const std::string &arg) +{ + if (type == logger::LogType::LOG_FILE || type == logger::LogType::LOG_PERSISTENT_FILE) { + if (arg.empty()) { + const std::string msg = "Path needs to be specified in the agument"; + LOGE(msg); + throw BadArgument(msg); + } + } + + mType = static_cast(type); + mLevel = static_cast(level); + mArg = arg; +} + + +} //namespace lxcpp diff --git a/libs/lxcpp/logger-config.hpp b/libs/lxcpp/logger-config.hpp new file mode 100644 index 0000000..8312623 --- /dev/null +++ b/libs/lxcpp/logger-config.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsumg.com) + * @brief Logger configuration + */ + +#ifndef LXCPP_LOGGER_CONFIG_HPP +#define LXCPP_LOGGER_CONFIG_HPP + +#include "config/config.hpp" +#include "config/fields.hpp" +#include "logger/logger.hpp" + + +namespace lxcpp { + + +/** + * Logger configuration + */ +struct LoggerConfig { +private: + int mType; + int mLevel; + std::string mArg; + +public: + void set(const logger::LogType type, + const logger::LogLevel level, + const std::string &arg = ""); + + logger::LogType getType() const {return static_cast(mType);} + logger::LogLevel getLevel() const {return static_cast(mLevel);} + std::string getArg() const {return mArg;} + + CONFIG_REGISTER + ( + mType, + mLevel, + mArg + ) +}; + + +} //namespace lxcpp + + +#endif // LXCPP_LOGGER_CONFIG_HPP diff --git a/libs/lxcpp/utils.cpp b/libs/lxcpp/utils.cpp new file mode 100644 index 0000000..41e76b5 --- /dev/null +++ b/libs/lxcpp/utils.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief LXCPP utils implementation + */ + +#include "lxcpp/exception.hpp" + +#include "logger/logger.hpp" +#include "utils/fd-utils.hpp" +#include "utils/exception.hpp" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include + + +namespace lxcpp { + + +/* + * This function has to be safe in regard to signal(7) + */ +int nullStdFDs() +{ + int ret = -1; + + int fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR | O_CLOEXEC)); + if (fd == -1) { + goto err; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDIN_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDOUT_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDERR_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::close(fd))) { + goto err_close; + } + + return 0; + +err_close: + TEMP_FAILURE_RETRY(::close(fd)); +err: + return ret; +} + +void setProcTitle(const std::string &title) +{ + std::ifstream f("/proc/self/stat"); + auto it = std::istream_iterator(f); + + // Skip the first 47 fields, entries 48-49 are ARG_START and ARG_END. + std::advance(it, 47); + unsigned long argStart = std::stol(*it++); + unsigned long argEnd = std::stol(*it++); + + f.close(); + + char *mem = reinterpret_cast(argStart); + ptrdiff_t oldLen = argEnd - argStart; + + // Include the null byte here, because in the calculations below we want to have room for it. + size_t newLen = title.length() + 1; + + // We shouldn't use more then we have available. Hopefully that should be enough. + if ((long)newLen > oldLen) { + newLen = oldLen; + } else { + argEnd = argStart + newLen; + } + + // Sanity check + if (argEnd < newLen || argEnd < argStart) { + std::string msg = "setProcTitle() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw UtilityException(msg); + } + + // Let's try to set the memory range properly (this requires capabilities) + if (::prctl(PR_SET_MM, PR_SET_MM_ARG_END, argEnd, 0, 0) < 0) { + // If that failed let's fall back to the poor man's version, just zero the memory we already have. + ::bzero(mem, oldLen); + } + + ::strcpy(mem, title.c_str()); +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/utils.hpp b/libs/lxcpp/utils.hpp new file mode 100644 index 0000000..2a59303 --- /dev/null +++ b/libs/lxcpp/utils.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief LXCPP utils headers + */ + +#ifndef LXCPP_UTILS_HPP +#define LXCPP_UTILS_HPP + +#include + + +namespace lxcpp { + +/** + * Nullifies all standard file descriptors (stdin, stdout, stderr) + * replacing them with file descriptor to /dev/null. Used to + * as a part of a process to detach a process from a control terminal. + * + * This function has to be safe in regard to signal(7) + * + * @returns an error code in case of failure. + */ +int nullStdFDs(); + +/** + * Changes the tittle of a current process title (e.g. visible in ps tool). + * + * @param title A new tittle to be set + */ +void setProcTitle(const std::string &title); + + +} // namespace lxcpp + + +#endif // LXCPP_START_HPP diff --git a/packaging/vasum.spec b/packaging/vasum.spec index 2cd5dd8..7709ab4 100644 --- a/packaging/vasum.spec +++ b/packaging/vasum.spec @@ -487,6 +487,7 @@ The package provides liblxcpp library. %files -n liblxcpp %defattr(644,root,root,755) %{_libdir}/liblxcpp.so.0 +%attr(755,root,root) %{_libexecdir}/lxcpp-guard %attr(755,root,root) %{_libdir}/liblxcpp.so.%{version} %package -n liblxcpp-devel -- 2.7.4 From a3495900d30400f536a89118984fca13df3187e1 Mon Sep 17 00:00:00 2001 From: Pawel Kubik Date: Tue, 15 Sep 2015 15:40:41 +0200 Subject: [PATCH 04/16] Instructions for generating code coverage report [Feature] Instructions in markdown [Cause] Need for local coverage reports [Solution] N/A [Verification] Follow instructions in the file and check report Change-Id: I69df0752268585c622b5006107ab91a59b840ea7 --- doc/coverage_report.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 doc/coverage_report.md diff --git a/doc/coverage_report.md b/doc/coverage_report.md new file mode 100644 index 0000000..cb9ef8e --- /dev/null +++ b/doc/coverage_report.md @@ -0,0 +1,40 @@ +Generating Code Coverage Report +=============================== + +Requirements +------------ + - [**gcc**](gcc.gnu.org) - provides `gcov` which generates coverage summary + - [**gcovr**](gcovr.com) - recursively runs `gcov` and generates HTML report. + [*PyPI*](pypi.python.org/pypi/gcovr) should provide the newest version. + You can use `pip` command (available in most repositories): + ```bash + sudo pip install gcovr + ``` + +Instructions +------------ + +All command should be run from within your build directory. **repodir** is a +path to your repository root. + +1. Generate your build using CCOV profile +```bash +cmake -DCMAKE_BUILD_TYPE=CCOV repodir +``` + +2. Compile, install and run tests in order to generate coverage files (.gcda extension) +```bash +make +sudo make install +sudo vsm_all_tests.py +``` + **Make sure that no other Vasum binaries are being used after installation + except from those used by tests!** + +3. Generate HTML report +```bash +gcovr -e tests -s -v -r repodir --html -o coverage.html +``` + +4. Coverage report consists of single page **coverage.html**. Find it in your + build directory and open in a web browser. -- 2.7.4 From c6006f81f49ddd8c3fc1cf59f3b54ea522f12fcc Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Thu, 27 Aug 2015 15:25:58 +0200 Subject: [PATCH 05/16] lxcpp: network implementation (part 1) [Feature] Network implementation for lxcpp (list, details) [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I2f43a2bb6d042b5aad9abf87951965e585fb4eb6 --- common/netlink/netlink-message.cpp | 22 +- common/netlink/netlink-message.hpp | 8 +- common/netlink/netlink.cpp | 2 +- common/utils/text.cpp | 43 ++++ common/utils/text.hpp | 54 +++++ libs/lxcpp/commands/netcreate.cpp | 45 ++++ libs/lxcpp/commands/netcreate.hpp | 57 +++++ libs/lxcpp/container-config.hpp | 9 + libs/lxcpp/container-impl.cpp | 40 +++- libs/lxcpp/container-impl.hpp | 18 +- libs/lxcpp/container.hpp | 20 +- libs/lxcpp/network-config.cpp | 66 +++++- libs/lxcpp/network-config.hpp | 55 ++++- libs/lxcpp/network.cpp | 398 +++++++++++++++++++++++++++++++++- libs/lxcpp/network.hpp | 78 ++++++- server/netdev.cpp | 4 +- tests/unit_tests/lxcpp/ut-network.cpp | 83 +++++++ 17 files changed, 938 insertions(+), 64 deletions(-) create mode 100644 common/utils/text.cpp create mode 100644 common/utils/text.hpp create mode 100644 libs/lxcpp/commands/netcreate.cpp create mode 100644 libs/lxcpp/commands/netcreate.hpp create mode 100644 tests/unit_tests/lxcpp/ut-network.cpp diff --git a/common/netlink/netlink-message.cpp b/common/netlink/netlink-message.cpp index cef7828..5bdda7b 100644 --- a/common/netlink/netlink-message.cpp +++ b/common/netlink/netlink-message.cpp @@ -191,7 +191,7 @@ void NetlinkResponse::skipAttribute() NetlinkResponse& NetlinkResponse::openNested(int ifla) { const rtattr *rta = asAttr(get(RTA_LENGTH(0))); - if (rta->rta_type == ifla) { + if (rta->rta_type != ifla) { const std::string msg = "Wrong attribute type, expected: " + std::to_string(ifla) + ", got: " + std::to_string(rta->rta_type); LOGE(msg); throw VasumException(msg); @@ -217,9 +217,13 @@ NetlinkResponse& NetlinkResponse::closeNested() return *this; } -NetlinkResponse& NetlinkResponse::fetch(int ifla, std::string& value, int maxSize) +NetlinkResponse& NetlinkResponse::fetch(int ifla, std::string& value, unsigned len) { - value = std::string(get(ifla, maxSize)); + const rtattr *rta = asAttr(get(RTA_LENGTH(0))); + if (len > RTA_PAYLOAD(rta)) { + len = RTA_PAYLOAD(rta); + } + value = std::string(get(ifla, -1), len); skipAttribute(); return *this; } @@ -228,12 +232,15 @@ const char* NetlinkResponse::get(int ifla, int len) const { const rtattr *rta = asAttr(get(RTA_LENGTH(len < 0 ? 0 : len))); if (rta->rta_type != ifla) { - const std::string msg = "Wrong attribute type, expected: " + std::to_string(ifla) + ", got: " + std::to_string(rta->rta_type); + const std::string msg = "Wrong attribute type, expected: " + std::to_string(ifla) + + ", got: " + std::to_string(rta->rta_type); LOGE(msg); throw VasumException(msg); } if (len >= 0 && rta->rta_len != RTA_LENGTH(len)) { - const std::string msg = "Wrong attribute length, expected: " + std::to_string(rta->rta_len) + ", got: " + std::to_string(len); + const std::string msg = "Wrong attribute " + std::to_string(ifla) + + " length, expected: " + std::to_string(rta->rta_len) + + ", got: " + std::to_string(RTA_LENGTH(len)); LOGE(msg); throw VasumException(msg); } @@ -270,6 +277,11 @@ int NetlinkResponse::getAttributeType() const return asAttr(get(RTA_LENGTH(0)))->rta_type; } +int NetlinkResponse::getAttributeLength() const +{ + return RTA_PAYLOAD(asAttr(get(RTA_LENGTH(0)))); +} + NetlinkResponse& NetlinkResponse::seek(int len) { if (size() < mPosition + len) { diff --git a/common/netlink/netlink-message.hpp b/common/netlink/netlink-message.hpp index 3379009..73e6a4c 100644 --- a/common/netlink/netlink-message.hpp +++ b/common/netlink/netlink-message.hpp @@ -25,6 +25,7 @@ #ifndef COMMON_NETLINK_NETLINK_MESSAGE_HPP #define COMMON_NETLINK_NETLINK_MESSAGE_HPP +#include #include #include #include @@ -150,7 +151,7 @@ public: /** * Fetch attribute */ - NetlinkResponse& fetch(int ifla, std::string& value, int maxSize = -1); + NetlinkResponse& fetch(int ifla, std::string& value, unsigned maxLen = std::numeric_limits::max()); template NetlinkResponse& fetch(int ifla, T& value); ///@} @@ -161,6 +162,11 @@ public: int getAttributeType() const; /** + * Get attributie length + **/ + int getAttributeLength() const; + + /** * Fetch data of type T */ template diff --git a/common/netlink/netlink.cpp b/common/netlink/netlink.cpp index 3408293..45d0697 100644 --- a/common/netlink/netlink.cpp +++ b/common/netlink/netlink.cpp @@ -100,7 +100,7 @@ void Netlink::open(int netNsPid) auto fdFactory = []{ return socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); }; assert(mFd == -1); - if (netNsPid == 0 || netNsPid == getpid()) { + if (netNsPid == 0 || netNsPid == 1 || netNsPid == ::getpid()) { mFd = fdFactory(); if (mFd == -1) { LOGE("Can't open socket: " << getSystemErrorMessage()); diff --git a/common/utils/text.cpp b/common/utils/text.cpp new file mode 100644 index 0000000..5bdf335 --- /dev/null +++ b/common/utils/text.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Text related utility + */ + +#include "utils/text.hpp" + +namespace utils { +namespace { +const char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; +} + +std::string toHexString(const void *data, unsigned len) +{ + const unsigned char *d = static_cast(data); + std::string s(len * 2, ' '); + for (unsigned i = 0; i < len; ++i) { + s[2 * i] = hexmap[(d[i] >> 4) & 0x0F]; + s[2 * i + 1] = hexmap[d[i] & 0x0F]; + } + return s; +} + +} // namespace utils diff --git a/common/utils/text.hpp b/common/utils/text.hpp new file mode 100644 index 0000000..435f421 --- /dev/null +++ b/common/utils/text.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Text related utils + */ + +#ifndef COMMON_UTILS_TEXT_HPP +#define COMMON_UTILS_TEXT_HPP + +#include + +namespace utils { + +/** + * Convert binary bytes array to hex string representation + */ +std::string toHexString(const void *data, unsigned len); + +inline bool beginsWith(std::string const &value, std::string const &part) +{ + if (part.size() > value.size()) { + return false; + } + return std::equal(part.begin(), part.end(), value.begin()); +} + +inline bool endsWith(std::string const &value, std::string const &part) +{ + if (part.size() > value.size()) { + return false; + } + return std::equal(part.rbegin(), part.rend(), value.rbegin()); +} + +} // namespace utils + +#endif // COMMON_UTILS_TEXT_HPP diff --git a/libs/lxcpp/commands/netcreate.cpp b/libs/lxcpp/commands/netcreate.cpp new file mode 100644 index 0000000..3c88d8e --- /dev/null +++ b/libs/lxcpp/commands/netcreate.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Network configuration command + */ + +#include "lxcpp/commands/netcreate.hpp" +#include "lxcpp/network.hpp" + +namespace lxcpp { + +void NetCreateAll::execute() +{ + for (const auto& i : mInterfaceConfigs) { + NetworkInterface networkInerface(mContainerPid, i.getZoneIf()); + networkInerface.create(i.getHostIf(), i.getType(), i.getMode()); + + Attrs attrs; + for (const auto& a : i.getAddrList()) { + Attr attr; + NetworkInterface::convertInetAddr2Attr(a, attr); + attrs.push_back(attr); + } + networkInerface.setAttrs(attrs); + } +} + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/netcreate.hpp b/libs/lxcpp/commands/netcreate.hpp new file mode 100644 index 0000000..5d6e512 --- /dev/null +++ b/libs/lxcpp/commands/netcreate.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Network configuration command + */ + +#ifndef LXCPP_COMMAND_NETCREATE_HPP +#define LXCPP_COMMAND_NETCREATE_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/network-config.hpp" + +#include + +namespace lxcpp { + +class NetCreateAll final: Command { +public: + /** + * Runs call in the container's context + * + * Creates and configures network interfaces in the container + * + */ + NetCreateAll(pid_t containerPid, const std::vector& iflist) : + mContainerPid(containerPid), + mInterfaceConfigs(iflist) + { + } + + void execute(); + +private: + const pid_t mContainerPid; + const std::vector& mInterfaceConfigs; +}; + +} // namespace lxcpp + +#endif // LXCPP_COMMAND_NETCREATE_HPP diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp index 9caad96..54422bf 100644 --- a/libs/lxcpp/container-config.hpp +++ b/libs/lxcpp/container-config.hpp @@ -25,6 +25,7 @@ #define LXCPP_CONTAINER_CONFIG_HPP #include "lxcpp/logger-config.hpp" +#include "lxcpp/network-config.hpp" #include #include @@ -71,6 +72,14 @@ struct ContainerConfig { pid_t mInitPid; /** + * Container network configration + * + * Set: by container network config methods + * Get: none + */ + NetworkConfig mNetwork; + + /** * Argv of the container's init process to be executed. * The path has to be relative to the RootPath. * diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index cee6fa4..a524231 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -182,22 +182,48 @@ void ContainerImpl::addInterfaceConfig(const std::string& hostif, InterfaceType type, MacVLanMode mode) { - mInterfaceConfig.push_back(NetworkInterfaceConfig(hostif,zoneif,type,mode)); + mConfig.mNetwork.addInterfaceConfig(hostif, zoneif, type, mode); } -void ContainerImpl::addAddrConfig(const std::string& /*ifname*/, const InetAddr& /*addr*/) +void ContainerImpl::addInetConfig(const std::string& ifname, const InetAddr& addr) { - throw NotImplementedException(); + mConfig.mNetwork.addInetConfig(ifname, addr); } -std::vector ContainerImpl::getInterfaces() +std::vector ContainerImpl::getInterfaces() const { return NetworkInterface::getInterfaces(getInitPid()); } -NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& /*ifname*/) +NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname) const { - throw NotImplementedException(); + NetworkInterface ni(getInitPid(), ifname); + std::vector addrs; + std::string macaddr; + InetAddr addr; + int mtu = 0, flags = 0; + Attrs attrs = ni.getAttrs(); + for (const Attr& a : attrs) { + switch (a.name) { + case AttrName::MAC: + macaddr = a.value; + break; + case AttrName::MTU: + mtu = std::stoul(a.value); + break; + case AttrName::IPV6: + case AttrName::IPV4: + NetworkInterface::convertAttr2InetAddr(a, addr); + addrs.push_back(addr); + break; + case AttrName::FLAGS: + flags = std::stoul(a.value); + break; + default: //ignore others + break; + } + } + return NetworkInterfaceInfo{ifname, ni.status(), macaddr, mtu, flags, addrs}; } void ContainerImpl::createInterface(const std::string& hostif, @@ -205,7 +231,7 @@ void ContainerImpl::createInterface(const std::string& hostif, InterfaceType type, MacVLanMode mode) { - NetworkInterface ni(*this, zoneif); + NetworkInterface ni(getInitPid(), zoneif); ni.create(hostif, type, mode); } diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 1bf44b7..3379143 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -34,7 +34,6 @@ namespace lxcpp { - class ContainerImpl : public virtual Container { public: ContainerImpl(const std::string &name, const std::string &path); @@ -69,15 +68,19 @@ public: const std::string& cwdInContainer); // Network interfaces setup/config + /** + * adds interface configration. + * throws NetworkException if zoneif name already on list + */ void addInterfaceConfig(const std::string& hostif, - const std::string& zoneif, - InterfaceType type, - MacVLanMode mode); - void addAddrConfig(const std::string& ifname, const InetAddr& addr); + const std::string& zoneif, + InterfaceType type, + MacVLanMode mode); + void addInetConfig(const std::string& ifname, const InetAddr& addr); // Network interfaces (runtime) - std::vector getInterfaces(); - NetworkInterfaceInfo getInterfaceInfo(const std::string& ifname); + std::vector getInterfaces() const; + NetworkInterfaceInfo getInterfaceInfo(const std::string& ifname) const; void createInterface(const std::string& hostif, const std::string& zoneif, InterfaceType type, @@ -93,7 +96,6 @@ private: // TODO: convert to ContainerConfig struct std::vector mNamespaces; - std::vector mInterfaceConfig; }; } // namespace lxcpp diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 88c3ff8..3e126d2 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -34,14 +34,12 @@ namespace lxcpp { -enum class NetStatus { - DOWN, - UP -}; - struct NetworkInterfaceInfo { const std::string ifname; const NetStatus status; + const std::string macaddr; + const int mtu; + const int flags; const std::vector addrs; }; @@ -78,14 +76,14 @@ public: // Network interfaces setup/config virtual void addInterfaceConfig(const std::string& hostif, - const std::string& zoneif, - InterfaceType type, - MacVLanMode mode) = 0; - virtual void addAddrConfig(const std::string& ifname, const InetAddr& addr) = 0; + const std::string& zoneif, + InterfaceType type, + MacVLanMode mode) = 0; + virtual void addInetConfig(const std::string& ifname, const InetAddr& addr) = 0; // Network interfaces (runtime) - virtual std::vector getInterfaces() = 0; - virtual NetworkInterfaceInfo getInterfaceInfo(const std::string& ifname) = 0; + virtual std::vector getInterfaces() const = 0; + virtual NetworkInterfaceInfo getInterfaceInfo(const std::string& ifname) const = 0; virtual void createInterface(const std::string& hostif, const std::string& zoneif, InterfaceType type, diff --git a/libs/lxcpp/network-config.cpp b/libs/lxcpp/network-config.cpp index 77e220a..17e1449 100644 --- a/libs/lxcpp/network-config.cpp +++ b/libs/lxcpp/network-config.cpp @@ -23,25 +23,81 @@ */ #include "lxcpp/network-config.hpp" +#include "lxcpp/network.hpp" #include "lxcpp/exception.hpp" +#include "logger/logger.hpp" #include + namespace lxcpp { -void NetworkInterfaceConfig::addNetAddr(const InetAddr& addr) +const std::string& NetworkInterfaceConfig::getHostIf() const +{ + return mHostIf; +} + +const std::string& NetworkInterfaceConfig::getZoneIf() const +{ + return mZoneIf; +} + +const InterfaceType& NetworkInterfaceConfig::getType() const +{ + return mType; +} + +const MacVLanMode& NetworkInterfaceConfig::getMode() const +{ + return mMode; +} + +const std::vector& NetworkInterfaceConfig::getAddrList() const +{ + return mIpAddrList; +} + +void NetworkInterfaceConfig::addInetAddr(const InetAddr& addr) { std::vector::iterator exists = std::find(mIpAddrList.begin(), mIpAddrList.end(), addr); if (exists != mIpAddrList.end()) { - std::string msg("Address alredy assigned"); + std::string msg("Address already assigned"); throw NetworkException(msg); } mIpAddrList.push_back(addr); } -void NetworkInterfaceConfig::delNetAddr(const InetAddr& addr) +void NetworkConfig::addInterfaceConfig(const std::string& hostif, + const std::string& zoneif, + InterfaceType type, + MacVLanMode mode) { - std::vector::iterator exists = std::find(mIpAddrList.begin(), mIpAddrList.end(), addr); - mIpAddrList.erase(exists); + auto it = std::find_if(mInterfaces.begin(), mInterfaces.end(), + [&zoneif](const NetworkInterfaceConfig& entry) { + return entry.getZoneIf() == zoneif; + } + ); + if (it != mInterfaces.end()) { + std::string msg = "Interface already exists"; + LOGE(msg); + throw NetworkException(msg); + } + mInterfaces.push_back(NetworkInterfaceConfig(hostif,zoneif,type,mode)); +} + +void NetworkConfig::addInetConfig(const std::string& ifname, const InetAddr& addr) +{ + auto it = std::find_if(mInterfaces.begin(), mInterfaces.end(), + [&ifname](const NetworkInterfaceConfig& entry) { + return entry.getZoneIf() == ifname; + } + ); + + if (it == mInterfaces.end()) { + std::string msg = "No such interface"; + LOGE(msg); + throw NetworkException(msg); + } + it->addInetAddr(addr); } } //namespace lxcpp diff --git a/libs/lxcpp/network-config.hpp b/libs/lxcpp/network-config.hpp index b4e2fe0..c94f45b 100644 --- a/libs/lxcpp/network-config.hpp +++ b/libs/lxcpp/network-config.hpp @@ -24,11 +24,15 @@ #ifndef LXCPP_NETWORK_CONFIG_HPP #define LXCPP_NETWORK_CONFIG_HPP +#include "config/config.hpp" +#include "config/fields.hpp" + #include #include #include #include +#include namespace lxcpp { @@ -60,11 +64,18 @@ enum class InetAddrType { IPV6 }; +enum class NetStatus { + DOWN, + UP +}; + + /** * Unified ip address */ struct InetAddr { InetAddrType type; + uint32_t flags; int prefix; union { struct in_addr ipv4; @@ -99,22 +110,56 @@ public: mType(type), mMode(mode) { - // TODO: Remove temporary usage - (void) mType; - (void) mMode; } - void addNetAddr(const InetAddr&); - void delNetAddr(const InetAddr&); + const std::string& getHostIf() const; + + const std::string& getZoneIf() const; + + const InterfaceType& getType() const; + + const MacVLanMode& getMode() const; + + const std::vector& getAddrList() const; + + void addInetAddr(const InetAddr&); private: const std::string mHostIf; const std::string mZoneIf; const InterfaceType mType; const MacVLanMode mMode; + //TODO mtu, macaddress, txqueue + /* + * above are interface parameters which can be read/modified: + * MTU (Maximum Transmit Unit) is maximum length of link level packet in TCP stream + * MAC address is also called hardware card address + * TXQueue is transmit queue length + * + * I think most usufull would be possibility to set MAC address, other have their + * well working defaults but can be tuned to make faster networking (especially localy) + */ std::vector mIpAddrList; }; +/** + * Network interface configuration + */ +struct NetworkConfig { + + //for convinience + void addInterfaceConfig(const std::string& hostif, + const std::string& zoneif, + InterfaceType type, + MacVLanMode mode); + void addInetConfig(const std::string& ifname, const InetAddr& addr); + + std::vector mInterfaces; + + //TODO tmporary to allow serialization of this object + CONFIG_REGISTER_EMPTY +}; + } //namespace lxcpp #endif // LXCPP_NETWORK_CONFIG_HPP diff --git a/libs/lxcpp/network.cpp b/libs/lxcpp/network.cpp index ad01f6f..965f20c 100644 --- a/libs/lxcpp/network.cpp +++ b/libs/lxcpp/network.cpp @@ -23,16 +23,172 @@ #include "lxcpp/network.hpp" +#include "lxcpp/container.hpp" #include "lxcpp/exception.hpp" #include "netlink/netlink-message.hpp" #include "utils/make-clean.hpp" +#include "utils/text.hpp" +#include "logger/logger.hpp" + +#include #include +#include + +#define CHANGE_FLAGS_DEFAULT 0xffffffff +/* from linux/if_addr.h: + * IFA_FLAGS is a u32 attribute that extends the u8 field ifa_flags. + * If present, the value from struct ifaddrmsg will be ignored. + * + * But this FLAG is available since some kernel version + * check if one of extended flags id defined + */ +#ifndef IFA_F_MANAGETEMPADDR +#define IFA_FLAGS IFA_UNSPEC +#endif using namespace vasum::netlink; namespace lxcpp { +namespace { +std::string toString(const in_addr& addr) +{ + char buf[INET_ADDRSTRLEN]; + const char* ret = ::inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); + if (ret == NULL) { + std::string msg = "Can't parse inet v4 addr"; + LOGE(msg); + throw NetworkException(msg); + } + return ret; +} + +std::string toString(const in6_addr& addr) +{ + char buf[INET6_ADDRSTRLEN]; + const char* ret = ::inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN); + if (ret == NULL) { + std::string msg = "Can't parse inet v6 addr"; + LOGE(msg); + throw NetworkException(msg); + } + return ret; +} + +uint32_t getInterfaceIndex(const std::string& name) +{ + uint32_t index = ::if_nametoindex(name.c_str()); + if (!index) { + std::string msg = "Can't find interface"; + LOGE(msg); + throw NetworkException(msg); + } + return index; +} + +uint32_t getInterfaceIndex(const std::string& name, pid_t pid) +{ + NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg infoPeer = utils::make_clean(); + infoPeer.ifi_family = AF_UNSPEC; + infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(infoPeer) + .put(IFLA_IFNAME, name); + + NetlinkResponse response = send(nlm, pid); + if (!response.hasMessage()) { + std::string msg = "Can't get interface index"; + LOGE(msg); + throw NetworkException(msg); + } + + response.fetch(infoPeer); + return infoPeer.ifi_index; +} + +void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t pid) +{ + uint32_t index = getInterfaceIndex(ifname, pid); + NetlinkMessage nlm(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + ifaddrmsg infoAddr = utils::make_clean(); + infoAddr.ifa_family = family; //test AF_PACKET to get all AF_INET* ? + nlm.put(infoAddr); + + NetlinkResponse response = send(nlm, pid); + if (!response.hasMessage()) { + return ; + } + + Attr attr; + while (response.hasMessage()) { + ifaddrmsg addrmsg; + response.fetch(addrmsg); + if (addrmsg.ifa_index == index) { + InetAddr a; + if (addrmsg.ifa_family == AF_INET6) { + a.type = InetAddrType::IPV6; + } else if (addrmsg.ifa_family == AF_INET) { + a.type = InetAddrType::IPV4; + } else { + std::string msg = "Unsupported inet family"; + LOGE(msg); + throw NetworkException(msg); + } + a.flags = addrmsg.ifa_flags; // IF_F_SECONDARY = secondary(alias) + a.prefix = addrmsg.ifa_prefixlen; + + bool hasLocal = false; + while (response.hasAttribute()) { + + int attrType = response.getAttributeType(); + switch (attrType) { + + // on each case line, int number in comment is value of the enum + case IFA_ADDRESS: //1 + if (hasLocal) { + response.skipAttribute(); + break; + } + // fall thru + case IFA_LOCAL: //2 + if (addrmsg.ifa_family == AF_INET6) { + response.fetch(attrType, a.addr.ipv6); + } else if (addrmsg.ifa_family == AF_INET) { + response.fetch(attrType, a.addr.ipv4); + } else { + LOGW("unsupported family " << addrmsg.ifa_family); + response.skipAttribute(); + } + hasLocal=true; + break; + + case IFA_FLAGS: //8 extended flags - overwrites addrmsg.ifa_flags + response.fetch(IFA_FLAGS, a.flags); + break; + + case IFA_LABEL: //3 <- should be equal to ifname + case IFA_BROADCAST://4 + case IFA_ANYCAST: //5 + case IFA_CACHEINFO://6 + case IFA_MULTICAST://7 + default: + //std::string tmp; + //response.fetch(attrType, tmp); + //std::cout << "skip(IFA) " << attrType << ":" << tmp << std::endl; + response.skipAttribute(); + break; + } + } + NetworkInterface::convertInetAddr2Attr(a, attr); + attrs.push_back(attr); + } + response.fetchNextMessage(); + } +} +} // anonymous namespace + + void NetworkInterface::create(const std::string& hostif, InterfaceType type, MacVLanMode mode) @@ -70,9 +226,21 @@ void NetworkInterface::createMacVLan(const std::string& /*hostif*/, MacVLanMode throw NotImplementedException(); } -void NetworkInterface::move(const std::string& /*hostif*/) +void NetworkInterface::move(const std::string& hostif) { - throw NotImplementedException(); + uint32_t index = getInterfaceIndex(hostif); + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg infopeer = utils::make_clean(); + infopeer.ifi_family = AF_UNSPEC; + infopeer.ifi_index = index; + nlm.put(infopeer) + .put(IFLA_NET_NS_PID, mContainerPid); + send(nlm); + + //rename to mIfname inside container + if (mIfname != hostif) { + renameFrom(hostif); + } } void NetworkInterface::destroy() @@ -85,7 +253,7 @@ NetStatus NetworkInterface::status() throw NotImplementedException(); /* //TODO get container status, if stopped return CONFIGURED - if (mContainer.getInitPid()<=0) return CONFIGURED; + if (mContainerPid<=0) return CONFIGURED; // read netlink return DOWN;*/ } @@ -101,14 +269,152 @@ void NetworkInterface::down() throw NotImplementedException(); } -void NetworkInterface::setAttrs(const Attrs& /*attrs*/) +void NetworkInterface::renameFrom(const std::string& oldif) { - throw NotImplementedException(); + NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg infoPeer = utils::make_clean(); + infoPeer.ifi_family = AF_UNSPEC; + infoPeer.ifi_index = getInterfaceIndex(oldif, mContainerPid); + infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + + nlm.put(infoPeer) + .put(IFLA_IFNAME, mIfname); + send(nlm, mContainerPid); } -const Attrs NetworkInterface::getAttrs() const +void NetworkInterface::addInetAddr(const InetAddr& addr) { - throw NotImplementedException(); + NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK); + ifaddrmsg infoAddr = utils::make_clean(); + infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); + infoAddr.ifa_family = addr.type == InetAddrType::IPV4 ? AF_INET : AF_INET6; + infoAddr.ifa_prefixlen = addr.prefix; + infoAddr.ifa_flags = addr.flags; + nlm.put(infoAddr); + + if (addr.type == InetAddrType::IPV6) { + nlm.put(IFA_ADDRESS, addr.addr.ipv6); + nlm.put(IFA_LOCAL, addr.addr.ipv6); + } else if (addr.type == InetAddrType::IPV4) { + nlm.put(IFA_ADDRESS, addr.addr.ipv4); + nlm.put(IFA_LOCAL, addr.addr.ipv4); + } + + send(nlm, mContainerPid); +} + +void NetworkInterface::setAttrs(const Attrs& attrs) +{ + //TODO check this: NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK); + unsigned mtu=0, link=0, txq=0; + ifinfomsg infoPeer = utils::make_clean(); + infoPeer.ifi_family = AF_UNSPEC; + infoPeer.ifi_index = getInterfaceIndex(mIfname, mContainerPid); + infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + + for (const auto& attr : attrs) { + if (attr.name == AttrName::FLAGS) { + infoPeer.ifi_flags = stoul(attr.value); + } else if (attr.name == AttrName::CHANGE) { + infoPeer.ifi_change = stoul(attr.value); + } else if (attr.name == AttrName::TYPE) { + infoPeer.ifi_type = stoul(attr.value); + } else if (attr.name == AttrName::MTU) { + mtu = stoul(attr.value); + } else if (attr.name == AttrName::LINK) { + link = stoul(attr.value); + } else if (attr.name == AttrName::TXQLEN) { + txq = stoul(attr.value); + } + } + nlm.put(infoPeer); + if (mtu) { + nlm.put(IFLA_MTU, mtu); + } + if (link) { + nlm.put(IFLA_LINK, link); + } + if (txq) { + nlm.put(IFLA_TXQLEN, txq); + } + + NetlinkResponse response = send(nlm, mContainerPid); + if (!response.hasMessage()) { + throw NetworkException("Can't set interface information"); + } + + // configure inet addresses + InetAddr addr; + for (const Attr& a : attrs) { + if (convertAttr2InetAddr(a, addr)) { + addInetAddr(addr); + } + } +} + +Attrs NetworkInterface::getAttrs() const +{ + NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg infoPeer = utils::make_clean(); + infoPeer.ifi_family = AF_UNSPEC; + infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(infoPeer) + .put(IFLA_IFNAME, mIfname); + + NetlinkResponse response = send(nlm, mContainerPid); + if (!response.hasMessage()) { + throw NetworkException("Can't get interface information"); + } + + Attrs attrs; + response.fetch(infoPeer); + attrs.push_back(Attr{AttrName::FLAGS, std::to_string(infoPeer.ifi_flags)}); + attrs.push_back(Attr{AttrName::TYPE, std::to_string(infoPeer.ifi_type)}); + + while (response.hasAttribute()) { + /* + * While traditional MAC addresses are all 48 bits in length, + * a few types of networks require 64-bit addresses instead. + */ + std::string mac; + uint32_t mtu, link, txq; + int attrType = response.getAttributeType(); + switch (attrType) { + case IFLA_ADDRESS: //1 + response.fetch(IFLA_ADDRESS, mac); + attrs.push_back(Attr{AttrName::MAC, utils::toHexString(mac.c_str(), mac.size())}); + break; + case IFLA_MTU: //4 + response.fetch(IFLA_MTU, mtu); + attrs.push_back(Attr{AttrName::MTU, std::to_string(mtu)}); + break; + case IFLA_LINK://5 + response.fetch(IFLA_LINK, link); + attrs.push_back(Attr{AttrName::LINK, std::to_string(link)}); + break; + case IFLA_TXQLEN://13 + response.fetch(IFLA_TXQLEN, txq); + attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(txq)}); + break; + case IFLA_BROADCAST://2 MAC broadcast + case IFLA_IFNAME: //3 + case IFLA_QDISC: //6 queue discipline + case IFLA_STATS: //7 struct net_device_stats + case IFLA_COST: //8 + case IFLA_PRIORITY: //9 + case IFLA_MASTER: //10 + case IFLA_WIRELESS: //11 + case IFLA_PROTINFO: //12 + case IFLA_MAP: //14 + default: + response.skipAttribute(); + break; + } + } + getAddressAttrs(attrs, AF_INET, mIfname, mContainerPid); + getAddressAttrs(attrs, AF_INET6, mIfname, mContainerPid); + return attrs; } std::vector NetworkInterface::getInterfaces(pid_t initpid) @@ -124,11 +430,87 @@ std::vector NetworkInterface::getInterfaces(pid_t initpid) while (response.hasMessage()) { std::string ifName; response.skip(); - response.fetch(IFLA_IFNAME, ifName); + // fetched value contains \0 terminator + int len = response.getAttributeLength(); + response.fetch(IFLA_IFNAME, ifName, len - 1); iflist.push_back(ifName); response.fetchNextMessage(); } return iflist; } +void NetworkInterface::convertInetAddr2Attr(const InetAddr& a, Attr& attr) +{ + std::string value; + + if (a.type == InetAddrType::IPV6) { + value += "ip=" + toString(a.addr.ipv6); + } else if (a.type == InetAddrType::IPV4) { + value += "ip=" + toString(a.addr.ipv4); + } else { + throw NetworkException(); + } + + value += ",pfx=" + std::to_string(a.prefix); + value += ",flags=" + std::to_string(a.flags); + + if (a.type == InetAddrType::IPV6) { + attr = Attr{AttrName::IPV6, value}; + } else { + attr = Attr{AttrName::IPV4, value}; + } +} + +bool NetworkInterface::convertAttr2InetAddr(const Attr& attr, InetAddr& addr) +{ + std::string::size_type s = 0U; + std::string::size_type e = s; + + if (attr.name != AttrName::IPV6 && attr.name != AttrName::IPV4) { + return false; //not inet attribute + } + + addr.prefix = 0; + addr.flags = 0; + + bool addrFound = false; + while (e != std::string::npos) { + e = attr.value.find(',', s); + std::string ss = attr.value.substr(s, e - s); + s = e + 1; + + std::string name; + std::string value; + std::string::size_type p = ss.find('='); + if (p != std::string::npos) { + name = ss.substr(0, p); + value = ss.substr(p + 1); + } else { + name = ss; + //value remains empty + } + + if (name == "ip") { + if (attr.name == AttrName::IPV6) { + addr.type = InetAddrType::IPV6; + if (inet_pton(AF_INET6, value.c_str(), &addr.addr.ipv6) != 1) { + throw NetworkException("Parse IPV6 address"); + } + } else if (attr.name == AttrName::IPV4) { + addr.type = InetAddrType::IPV4; + if (inet_pton(AF_INET, value.c_str(), &addr.addr.ipv4) != 1) { + throw NetworkException("Parse IPV4 address"); + } + } + addrFound = true; + } else if (name == "pfx") { + addr.prefix = stoul(value); + } else if (name == "flags") { + addr.flags = stoul(value); + } + } + + return addrFound; +} + } // namespace lxcpp diff --git a/libs/lxcpp/network.hpp b/libs/lxcpp/network.hpp index 8381611..cc0167e 100644 --- a/libs/lxcpp/network.hpp +++ b/libs/lxcpp/network.hpp @@ -24,31 +24,65 @@ #ifndef LXCPP_NETWORK_HPP #define LXCPP_NETWORK_HPP -#include "lxcpp/container.hpp" +#include "lxcpp/network-config.hpp" + +#include #include +#include namespace lxcpp { +enum class AttrName { + MAC, + FLAGS, + CHANGE, + TYPE, + MTU, + LINK, + TXQLEN, + IPV4, + IPV6, +}; + +inline std::ostream& operator<<(std::ostream& os, const AttrName& a) { + switch (a) { + case AttrName::MAC: os << "mac"; break; + case AttrName::FLAGS: os << "flags"; break; + case AttrName::CHANGE: os << "change"; break; + case AttrName::TYPE: os << "type"; break; + case AttrName::MTU: os << "mtu"; break; + case AttrName::LINK: os << "link"; break; + case AttrName::TXQLEN: os << "txq"; break; + case AttrName::IPV4: os << "ipv4"; break; + case AttrName::IPV6: os << "ipv6"; break; + } + return os; +} + struct Attr { - std::string name; + AttrName name; std::string value; }; typedef std::vector Attrs; - -/// Network operations to be performed on given container and interface -/// operates on netlink device +/** + * Network operations to be performed on given container and interface + * operates on netlink device + */ class NetworkInterface { public: - NetworkInterface(Container& c, const std::string& ifname) : - mContainer(c), + /** + * Create network interface object for the ifname in the container + */ + NetworkInterface(pid_t pid, const std::string& ifname) : + mContainerPid(pid), mIfname(ifname) { - // TODO: Remove temporary usage - (void) mContainer; } + const std::string& getName() const { return mIfname; } + //Network actions on Container void create(const std::string& hostif, InterfaceType type, MacVLanMode mode=MacVLanMode::PRIVATE); void destroy(); @@ -56,19 +90,39 @@ public: NetStatus status(); void up(); void down(); + + /** + * Rename interface name + * Equivalent to: ip link set dev $oldif name $this.mIfname + */ + void renameFrom(const std::string& oldif); + void setAttrs(const Attrs& attrs); - const Attrs getAttrs() const; + Attrs getAttrs() const; + + void setMACAddress(const std::string& macaddr); + void setMTU(int mtu); + void setTxLength(int txlen); + void addInetAddr(const InetAddr& addr); + void delInetAddr(const InetAddr& addr); static std::vector getInterfaces(pid_t initpid); + static void convertInetAddr2Attr(const InetAddr& addr, Attr& attr); + static bool convertAttr2InetAddr(const Attr& attr, InetAddr& addr); + private: void createVeth(const std::string& hostif); void createBridge(const std::string& hostif); void createMacVLan(const std::string& hostif, MacVLanMode mode); + /** + * Move interface to container + * Equivalent to: ip link set dev $hostif netns $mContainerPid + */ void move(const std::string& hostif); - Container& mContainer; ///< Container to operate on - const std::string mIfname; ///< network interface name inside zone + pid_t mContainerPid; ///< Container pid to operate on (0=kernel) + const std::string mIfname; ///< network interface name inside zone }; } // namespace lxcpp diff --git a/server/netdev.cpp b/server/netdev.cpp index dc8642c..8387c84 100644 --- a/server/netdev.cpp +++ b/server/netdev.cpp @@ -428,7 +428,9 @@ std::vector listNetdev(const pid_t& nsPid) while (response.hasMessage()) { std::string ifName; response.skip(); - response.fetch(IFLA_IFNAME, ifName); + // fetched value contains \0 terminator + int len = response.getAttributeLength(); + response.fetch(IFLA_IFNAME, ifName, len - 1); interfaces.push_back(ifName); response.fetchNextMessage(); } diff --git a/tests/unit_tests/lxcpp/ut-network.cpp b/tests/unit_tests/lxcpp/ut-network.cpp new file mode 100644 index 0000000..373ec1b --- /dev/null +++ b/tests/unit_tests/lxcpp/ut-network.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Krzysztof Dynowski (k.dynowski@samsumg.com) + * @brief Unit tests of lxcpp network helpers + */ + +#include "config.hpp" +#include "config/manager.hpp" +#include "lxcpp/network.hpp" +#include "ut.hpp" + +#include +#include + +namespace { + +struct Fixture { + Fixture() {} + ~Fixture() {} +}; + +} // namespace + +/* + * NOTE: network inerface unit tests are not finished yet + * tests are developed/added together with network interface code + * and container code development + */ + +BOOST_FIXTURE_TEST_SUITE(LxcppNetworkSuite, Fixture) + +using namespace lxcpp; + +BOOST_AUTO_TEST_CASE(ListInterfaces) +{ + std::vector iflist; + BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0);) + for (const auto& i : iflist) { + std::cout << i << std::endl; + } +} + +BOOST_AUTO_TEST_CASE(DetailedListInterfaces) +{ + std::vector iflist; + BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0);) + for (const auto& i : iflist) { + NetworkInterface ni(1,i); + std::cout << "Attrs of " << i << std::endl; + Attrs attrs; + BOOST_CHECK_NO_THROW(attrs = ni.getAttrs();) + for (const Attr& a : attrs) { + std::cout << a.name << "=" << a.value << "; "; + } + std::cout << std::endl; + } +} +BOOST_AUTO_TEST_CASE(NetworkConfigSerialization) +{ + NetworkConfig cfg; + std::string json; + BOOST_CHECK_NO_THROW(json = config::saveToJsonString(cfg);) + std::cout << json << std::endl; +} + +BOOST_AUTO_TEST_SUITE_END() -- 2.7.4 From e1624f3797d5c9a50f5f4b8bfd0ce4970d0ed5f4 Mon Sep 17 00:00:00 2001 From: Pawel Kubik Date: Fri, 18 Sep 2015 14:41:31 +0200 Subject: [PATCH 06/16] Fixed test scripts [Feature] N/A [Cause] Unit tests using fixtures can't be run separately, because their names contains bash special characters. [Solution] Put test names inside quotes. [Verification] Install, launch single test case with fixture. Change-Id: I61e426957e080b1a145bd901c155677775d571ba --- tests/scripts/vsm_launch_test.py | 5 +++++ tests/scripts/vsm_test_parser.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/scripts/vsm_launch_test.py b/tests/scripts/vsm_launch_test.py index 038ba30..dd5831f 100755 --- a/tests/scripts/vsm_launch_test.py +++ b/tests/scripts/vsm_launch_test.py @@ -5,6 +5,7 @@ from vsm_test_parser import Logger, Parser import subprocess import argparse import os +import re _defLaunchArgs = ["--report_format=XML", "--catch_system_errors=no", @@ -38,6 +39,10 @@ def launchTest(cmd=[], externalToolCmd=[], parsing=True): if externalToolCmd and not _checkIfBinExists(externalToolCmd[0]): return + cmd[1:] = ["'{0}'".format(arg) if re.search("^\s*[^']*/.*<.*>\s*$", arg) + else arg + for arg in cmd[1:]] + log.info("Starting " + cmd[0] + " ...") if parsing: diff --git a/tests/scripts/vsm_test_parser.py b/tests/scripts/vsm_test_parser.py index d0d4fa7..fc0eae6 100644 --- a/tests/scripts/vsm_test_parser.py +++ b/tests/scripts/vsm_test_parser.py @@ -80,7 +80,7 @@ class Logger(object): commandPrefix = "vsm_launch_test.py " + bin + " -t " self.infoTitle("Some tests failed. Use following command(s) to launch them explicitly:") for test in self.__failedTests: - self.error(self.__indentChar + commandPrefix + test) + self.error(self.__indentChar + commandPrefix + "'{0}'".format(test)) def terminatedBySignal(self, bin, signum): self.error("\n=========== FAILED ===========\n") -- 2.7.4 From fe7acf72de44d39b5cb434fd9119364fc2ba44e2 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Thu, 17 Sep 2015 18:18:04 +0200 Subject: [PATCH 07/16] lxcpp: Terminal preparation part 1 (host) [Feature] Prepare pseudoterminals for the container [Verification] Build, install, run tests Changes in this commit: - PrepHostTerminal command and surrounding function (openPty) - Terminal(s)Config - Changes to Start::daemonize() to do its job better - fd-utils helpers for FD flags - set CLOEXEC on FDs received through libConfig Change-Id: I432447900c189bb50669267ff4422c36860b5481 --- common/utils/channel.cpp | 6 +- common/utils/fd-utils.cpp | 32 ++++++ common/utils/fd-utils.hpp | 10 ++ libs/config/fdstore.cpp | 2 +- libs/lxcpp/CMakeLists.txt | 2 +- libs/lxcpp/commands/prep-host-terminal.cpp | 55 ++++++++++ libs/lxcpp/commands/prep-host-terminal.hpp | 57 +++++++++++ libs/lxcpp/commands/start.cpp | 60 ++++++----- libs/lxcpp/container-config.hpp | 12 ++- libs/lxcpp/container-impl.cpp | 16 +++ libs/lxcpp/container-impl.hpp | 2 + libs/lxcpp/container.hpp | 2 + libs/lxcpp/exception.hpp | 5 + libs/lxcpp/guard/main.cpp | 9 +- libs/lxcpp/terminal-config.hpp | 76 ++++++++++++++ libs/lxcpp/terminal.cpp | 155 +++++++++++++++++++++++++++++ libs/lxcpp/terminal.hpp | 59 +++++++++++ libs/lxcpp/utils.cpp | 44 +------- libs/lxcpp/utils.hpp | 10 -- 19 files changed, 526 insertions(+), 88 deletions(-) create mode 100644 libs/lxcpp/commands/prep-host-terminal.cpp create mode 100644 libs/lxcpp/commands/prep-host-terminal.hpp create mode 100644 libs/lxcpp/terminal-config.hpp create mode 100644 libs/lxcpp/terminal.cpp create mode 100644 libs/lxcpp/terminal.hpp diff --git a/common/utils/channel.cpp b/common/utils/channel.cpp index 3a0dd31..52047ee 100644 --- a/common/utils/channel.cpp +++ b/common/utils/channel.cpp @@ -113,11 +113,7 @@ void Channel::setCloseOnExec(const bool closeOnExec) { const int fd = getFD(); - if (closeOnExec) { - ::fcntl(fd, F_SETFD, ::fcntl(fd, F_GETFD) | FD_CLOEXEC); - } else { - ::fcntl(fd, F_SETFD, ::fcntl(fd, F_GETFD) & ~FD_CLOEXEC); - } + utils::setCloseOnExec(fd, closeOnExec); } void Channel::closeSocket(int socketIndex) diff --git a/common/utils/fd-utils.cpp b/common/utils/fd-utils.cpp index 3b7b998..3e0db3e 100644 --- a/common/utils/fd-utils.cpp +++ b/common/utils/fd-utils.cpp @@ -93,6 +93,28 @@ void waitForEvent(int fd, } } +void setFDFlag(const int fd, const int getOp, const int setOp, const int flag, const bool set) +{ + int ret = ::fcntl(fd, getOp); + if (ret == -1) { + std::string msg = "fcntl(): Failed to get FD flags: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + + if (set) { + ret = ::fcntl(fd, setOp, ret | flag); + } else { + ret = ::fcntl(fd, setOp, ret & ~flag); + } + + if (ret == -1) { + std::string msg = "fcntl(): Failed to set FD flag: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } +} + } // namespace void close(int fd) @@ -348,5 +370,15 @@ bool fdSend(int socket, int fd, const unsigned int timeoutMS) return true; } +void setCloseOnExec(int fd, bool closeOnExec) +{ + setFDFlag(fd, F_GETFD, F_SETFD, FD_CLOEXEC, closeOnExec); +} + +void setNonBlocking(int fd, bool nonBlocking) +{ + setFDFlag(fd, F_GETFL, F_SETFL, O_NONBLOCK, nonBlocking); +} + } // namespace utils diff --git a/common/utils/fd-utils.hpp b/common/utils/fd-utils.hpp index 56e3f41..91bbff4 100644 --- a/common/utils/fd-utils.hpp +++ b/common/utils/fd-utils.hpp @@ -86,6 +86,16 @@ bool fdSend(int socket, int fd, const unsigned int timeoutMS = 5000); */ int fdRecv(int socket, const unsigned int timeoutMS = 5000); +/** + * Set or remove CLOEXEC on a file descriptor + */ +void setCloseOnExec(int fd, bool closeOnExec); + +/** + * Set or remove NONBLOCK on a file descriptor + */ +void setNonBlocking(int fd, bool nonBlocking); + } // namespace utils #endif // COMMON_UTILS_FD_HPP diff --git a/libs/config/fdstore.cpp b/libs/config/fdstore.cpp index cc0b903..44d2418 100644 --- a/libs/config/fdstore.cpp +++ b/libs/config/fdstore.cpp @@ -262,7 +262,7 @@ int FDStore::receiveFD(const unsigned int timeoutMS) // Receive for(;;) { - ssize_t ret = ::recvmsg(mFD, &msgh, MSG_WAITALL); + ssize_t ret = ::recvmsg(mFD, &msgh, MSG_WAITALL | MSG_CMSG_CLOEXEC); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { // Neglected errors, retry diff --git a/libs/lxcpp/CMakeLists.txt b/libs/lxcpp/CMakeLists.txt index 8f391e8..d5e205b 100644 --- a/libs/lxcpp/CMakeLists.txt +++ b/libs/lxcpp/CMakeLists.txt @@ -51,7 +51,7 @@ PKG_CHECK_MODULES(LXCPP_DEPS REQUIRED glib-2.0) INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${Boost_INCLUDE_DIRS} ${LXCPP_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger Config) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} Common ${pkgs_LDFLAGS} ${Boost_LIBRARIES} Logger Config util) ## Generate the pc file ######################################################## CONFIGURE_FILE(${PC_FILE}.in ${CMAKE_CURRENT_BINARY_DIR}/${PC_FILE} @ONLY) diff --git a/libs/lxcpp/commands/prep-host-terminal.cpp b/libs/lxcpp/commands/prep-host-terminal.cpp new file mode 100644 index 0000000..f219ff9 --- /dev/null +++ b/libs/lxcpp/commands/prep-host-terminal.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Implementation of host terminal preparation + */ + +#include "lxcpp/commands/prep-host-terminal.hpp" +#include "lxcpp/terminal.hpp" + +#include "logger/logger.hpp" + + +namespace lxcpp { + + +PrepHostTerminal::PrepHostTerminal(TerminalsConfig &terminals) + : mTerminals(terminals) +{ +} + +PrepHostTerminal::~PrepHostTerminal() +{ +} + +void PrepHostTerminal::execute() +{ + LOGD("Creating " << mTerminals.count << " pseudoterminal(s) on the host side:"); + + for (int i = 0; i < mTerminals.count; ++i) + { + const auto pty = lxcpp::openPty(true); + LOGD(pty.second << " has been created"); + mTerminals.PTYs.emplace_back(pty.first, pty.second); + } +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/prep-host-terminal.hpp b/libs/lxcpp/commands/prep-host-terminal.hpp new file mode 100644 index 0000000..f7153b2 --- /dev/null +++ b/libs/lxcpp/commands/prep-host-terminal.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Headers for host terminal preparation + */ + +#ifndef LXCPP_COMMANDS_PREP_HOST_TERMINAL_HPP +#define LXCPP_COMMANDS_PREP_HOST_TERMINAL_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/terminal-config.hpp" + + +namespace lxcpp { + + +class PrepHostTerminal final: Command { +public: + /** + * Prepares the terminal on the host side. + * + * It creates a number of pseudoterminals and stores them + * to be passed to the guard and prepared for init process. + * + * @param config container's config + */ + PrepHostTerminal(TerminalsConfig &config); + ~PrepHostTerminal(); + + void execute(); + +private: + TerminalsConfig &mTerminals; +}; + + +} // namespace lxcpp + + +#endif // LXCPP_COMMANDS_PREP_HOST_TERMINAL_HPP diff --git a/libs/lxcpp/commands/start.cpp b/libs/lxcpp/commands/start.cpp index f68654a..32a3054 100644 --- a/libs/lxcpp/commands/start.cpp +++ b/libs/lxcpp/commands/start.cpp @@ -25,11 +25,14 @@ #include "lxcpp/exception.hpp" #include "lxcpp/process.hpp" #include "lxcpp/utils.hpp" +#include "lxcpp/terminal.hpp" #include "logger/logger.hpp" #include "config/manager.hpp" #include +#include +#include namespace lxcpp { @@ -94,41 +97,44 @@ void Start::parent(const pid_t pid) void Start::daemonize() { + // Prepare a clean daemonized environment for a guard process: + + // Set a new session so the process looses its control terminal + if (::setsid() < 0) { + ::_exit(EXIT_FAILURE); + } + // Double fork() with exit() to reattach the process under the host's init + // and to make sure that the child (guard) is not a process group leader + // and cannot reacquire its control terminal pid_t pid = ::fork(); - if (pid < 0) { + if (pid < 0) { // fork failed + ::_exit(EXIT_FAILURE); + } + if (pid > 0) { // exit in parent process + ::_exit(EXIT_SUCCESS); + } + + // Chdir to / so it's independent on other directories + if (::chdir("/") < 0) { ::_exit(EXIT_FAILURE); } - if (pid == 0) { - // Prepare a clean environment for a guard process: - // - chdir to / so it's independent on other directories - // - null std* fds so it's properly dettached from the terminal - // - set a new session so it cannot reacquire a terminal - - if (::chdir("/") < 0) { - ::_exit(EXIT_FAILURE); - } - if (nullStdFDs() <0) { - ::_exit(EXIT_FAILURE); - } - if (::setsid() < 0) { - ::_exit(EXIT_FAILURE); - } - - // Add name and path of the container to argv. They are not used, but will - // identify the container in the process list in case setProcTitle() fails - // and will guarantee we have enough argv memory to write the title we want. - const char *argv[] = {mGuardPath.c_str(), - mChannelFD.c_str(), - mConfig.mName.c_str(), - mConfig.mRootPath.c_str(), - NULL}; - ::execve(argv[0], const_cast(argv), NULL); + // Null std* fds so it's properly dettached from the terminal + if (nullStdFDs() < 0) { ::_exit(EXIT_FAILURE); } - ::_exit(EXIT_SUCCESS); + // Add name and path of the container to argv. They are not used, but will + // identify the container in the process list in case setProcTitle() fails + // and will guarantee we have enough argv memory to write the title we want. + const char *argv[] = {mGuardPath.c_str(), + mChannelFD.c_str(), + mConfig.mName.c_str(), + mConfig.mRootPath.c_str(), + NULL}; + ::execve(argv[0], const_cast(argv), NULL); + ::_exit(EXIT_FAILURE); } diff --git a/libs/lxcpp/container-config.hpp b/libs/lxcpp/container-config.hpp index 54422bf..c84b4ef 100644 --- a/libs/lxcpp/container-config.hpp +++ b/libs/lxcpp/container-config.hpp @@ -26,6 +26,7 @@ #include "lxcpp/logger-config.hpp" #include "lxcpp/network-config.hpp" +#include "lxcpp/terminal-config.hpp" #include #include @@ -100,6 +101,14 @@ struct ContainerConfig { */ LoggerConfig mLogger; + /** + * Configuration for terminal(s), from API point of view, only their number. + * + * Set: setTerminalCount() + * Get: none + */ + TerminalsConfig mTerminals; + ContainerConfig() : mGuardPid(-1), mInitPid(-1) {} CONFIG_REGISTER @@ -109,7 +118,8 @@ struct ContainerConfig { mGuardPid, mInitPid, mInit, - mLogger + mLogger, + mTerminals ) }; diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index a524231..715dad8 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -29,6 +29,7 @@ #include "lxcpp/capability.hpp" #include "lxcpp/commands/attach.hpp" #include "lxcpp/commands/start.hpp" +#include "lxcpp/commands/prep-host-terminal.hpp" #include "logger/logger.hpp" #include "utils/exception.hpp" @@ -128,9 +129,24 @@ void ContainerImpl::setLogger(const logger::LogType type, mConfig.mLogger.set(type, level, arg); } +void ContainerImpl::setTerminalCount(const unsigned int count) +{ + if (count == 0) { + const std::string msg = "Container needs at least one terminal"; + LOGE(msg); + throw ConfigureException(msg); + } + + mConfig.mTerminals.count = count; +} + void ContainerImpl::start() { // TODO: check config consistency and completeness somehow + + PrepHostTerminal terminal(mConfig.mTerminals); + terminal.execute(); + Start start(mConfig); start.execute(); } diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 3379143..97b9155 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -54,6 +54,8 @@ public: const logger::LogLevel level, const std::string &arg); + void setTerminalCount(const unsigned int count); + const std::vector& getNamespaces() const; // Execution actions diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 3e126d2..bf10a76 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -63,6 +63,8 @@ public: const logger::LogLevel level, const std::string &arg) = 0; + virtual void setTerminalCount(const unsigned int count) = 0; + // Execution actions virtual void start() = 0; virtual void stop() = 0; diff --git a/libs/lxcpp/exception.hpp b/libs/lxcpp/exception.hpp index 11a6a0e..387e2eb 100644 --- a/libs/lxcpp/exception.hpp +++ b/libs/lxcpp/exception.hpp @@ -71,6 +71,11 @@ struct UtilityException: public Exception { : Exception(message) {} }; +struct TerminalException: public Exception { + explicit TerminalException(const std::string& message = "Error during a terminal operation") + : Exception(message) {} +}; + struct BadArgument: public Exception { explicit BadArgument(const std::string& message = "Bad argument passed") : Exception(message) {} diff --git a/libs/lxcpp/guard/main.cpp b/libs/lxcpp/guard/main.cpp index c8db30b..1065ecb 100644 --- a/libs/lxcpp/guard/main.cpp +++ b/libs/lxcpp/guard/main.cpp @@ -35,15 +35,18 @@ int main(int argc, char *argv[]) ::_exit(EXIT_FAILURE); } + int channel = std::stoi(argv[1]); + // NOTE: this might not be required now, but I leave it here not to forget. // We need to investigate this with vasum and think about possibility of // poorly written software that leaks file descriptors and might use LXCPP. #if 0 for(int fd = 3; fd < ::sysconf(_SC_OPEN_MAX); ++fd) { - utils::close(fd); + if (fd != channel) { + utils::close(fd); + } } #endif - int fd = std::stoi(argv[1]); - return lxcpp::startGuard(fd); + return lxcpp::startGuard(channel); } diff --git a/libs/lxcpp/terminal-config.hpp b/libs/lxcpp/terminal-config.hpp new file mode 100644 index 0000000..3d6675a --- /dev/null +++ b/libs/lxcpp/terminal-config.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsumg.com) + * @brief Terminal configuration + */ + +#ifndef LXCPP_TERMINAL_CONFIG_HPP +#define LXCPP_TERMINAL_CONFIG_HPP + +#include "config/config.hpp" +#include "config/fields.hpp" + +#include +#include + + +namespace lxcpp { + + +struct TerminalConfig { + config::FileDescriptor masterFD; + std::string ptsName; + + TerminalConfig() + : masterFD(-1) + {} + + TerminalConfig(const int masterFD, const std::string &ptsName) + : masterFD(masterFD), + ptsName(ptsName) + {} + + CONFIG_REGISTER + ( + masterFD, + ptsName + ) +}; + +struct TerminalsConfig { + int count; + std::vector PTYs; + + TerminalsConfig(const int count = 1) + : count(count) + {} + + CONFIG_REGISTER + ( + count, + PTYs + ) +}; + + +} //namespace lxcpp + + +#endif // LXCPP_TERMINAL_CONFIG_HPP diff --git a/libs/lxcpp/terminal.cpp b/libs/lxcpp/terminal.cpp new file mode 100644 index 0000000..ce77cd5 --- /dev/null +++ b/libs/lxcpp/terminal.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Terminal helpers implementation + */ + +#include "lxcpp/exception.hpp" + +#include "logger/logger.hpp" +#include "utils/exception.hpp" +#include "utils/fd-utils.hpp" + +#include +#include +#include +#include + + +namespace lxcpp { + + +namespace { + +void openpty(int *master, int *slave) +{ + // Do not use other parameters, they are not 100% safe + if (-1 == ::openpty(master, slave, NULL, NULL, NULL)) { + const std::string msg = "openpty() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); + } +} + +void tcgetattr(const int fd, struct termios *termios_p) +{ + if (-1 == ::tcgetattr(fd, termios_p)) { + const std::string msg = "tcgetattr() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); + } +} + +void tcsetattr(const int fd, const int optional_actions, const struct termios *termios_p) +{ + if (-1 == ::tcsetattr(fd, optional_actions, termios_p)) { + const std::string msg = "tcsetattr() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); + } +} + +std::string ttyname_r(const int fd) +{ + char ptsName[PATH_MAX]; + int rc = ::ttyname_r(fd, ptsName, PATH_MAX); + + if (rc != 0) { + const std::string msg = "ttyname_r() failed: " + utils::getSystemErrorMessage(rc); + LOGE(msg); + throw TerminalException(msg); + } + + return ptsName; +} + +} // namespace + + +/* + * This function has to be safe in regard to signal(7) + */ +int nullStdFDs() +{ + int ret = -1; + + int fd = TEMP_FAILURE_RETRY(::open("/dev/null", O_RDWR | O_CLOEXEC)); + if (fd == -1) { + goto err; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDIN_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDOUT_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDERR_FILENO))) { + goto err_close; + } + + if (-1 == TEMP_FAILURE_RETRY(::close(fd))) { + goto err; + } + + return 0; + +err_close: + TEMP_FAILURE_RETRY(::close(fd)); +err: + return ret; +} + +std::pair openPty(bool rawMode) +{ + int master, slave; + std::string ptsName; + + try { + lxcpp::openpty(&master, &slave); + + utils::setCloseOnExec(master, true); + utils::setNonBlocking(master, true); + + if (rawMode) { + struct termios ttyAttr; + + lxcpp::tcgetattr(slave, &ttyAttr); + ::cfmakeraw(&ttyAttr); + lxcpp::tcsetattr(slave, TCSADRAIN, &ttyAttr); + } + + ptsName = lxcpp::ttyname_r(slave); + + } catch(std::exception&) { + TEMP_FAILURE_RETRY(::close(master)); + TEMP_FAILURE_RETRY(::close(slave)); + + throw; + } + + utils::close(slave); + return std::pair(master, ptsName); +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/terminal.hpp b/libs/lxcpp/terminal.hpp new file mode 100644 index 0000000..85ae6e2 --- /dev/null +++ b/libs/lxcpp/terminal.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Terminal helpers headers + */ + +#ifndef LXCPP_TERMINAL_HPP +#define LXCPP_TERMINAL_HPP + + +namespace lxcpp { + + +/** + * Nullifies all standard file descriptors (stdin, stdout, stderr) + * replacing them with file descriptor to /dev/null. Used to + * as a part of a process to detach a process from a control terminal. + * + * This function has to be safe in regard to signal(7) + * + * @returns an error code in case of failure. + */ +int nullStdFDs(); + +/** + * This function creates a new pair of virtual character devices + * using a pseudtoreminal interface. It also configures as much as it + * can so the devices are immediately usable. + * + * @param rawMode Whether to set the terminal in the raw mode (termios(2)) + * + * @returns file descriptor to the master device and the path/name of + * the pts slace device. + */ +std::pair openPty(bool rawMode); + + +} // namespace lxcpp + + + +#endif // LXCPP_TERMINAL_HPP diff --git a/libs/lxcpp/utils.cpp b/libs/lxcpp/utils.cpp index 41e76b5..685d915 100644 --- a/libs/lxcpp/utils.cpp +++ b/libs/lxcpp/utils.cpp @@ -21,16 +21,16 @@ * @brief LXCPP utils implementation */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include "lxcpp/exception.hpp" #include "logger/logger.hpp" #include "utils/fd-utils.hpp" #include "utils/exception.hpp" -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - #include #include #include @@ -41,42 +41,6 @@ namespace lxcpp { -/* - * This function has to be safe in regard to signal(7) - */ -int nullStdFDs() -{ - int ret = -1; - - int fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR | O_CLOEXEC)); - if (fd == -1) { - goto err; - } - - if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDIN_FILENO))) { - goto err_close; - } - - if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDOUT_FILENO))) { - goto err_close; - } - - if (-1 == TEMP_FAILURE_RETRY(::dup2(fd, STDERR_FILENO))) { - goto err_close; - } - - if (-1 == TEMP_FAILURE_RETRY(::close(fd))) { - goto err_close; - } - - return 0; - -err_close: - TEMP_FAILURE_RETRY(::close(fd)); -err: - return ret; -} - void setProcTitle(const std::string &title) { std::ifstream f("/proc/self/stat"); diff --git a/libs/lxcpp/utils.hpp b/libs/lxcpp/utils.hpp index 2a59303..e4c158b 100644 --- a/libs/lxcpp/utils.hpp +++ b/libs/lxcpp/utils.hpp @@ -29,16 +29,6 @@ namespace lxcpp { -/** - * Nullifies all standard file descriptors (stdin, stdout, stderr) - * replacing them with file descriptor to /dev/null. Used to - * as a part of a process to detach a process from a control terminal. - * - * This function has to be safe in regard to signal(7) - * - * @returns an error code in case of failure. - */ -int nullStdFDs(); /** * Changes the tittle of a current process title (e.g. visible in ps tool). -- 2.7.4 From 08a871f9e354e79e755402cd0a97dc6d4501ef47 Mon Sep 17 00:00:00 2001 From: Lukasz Pawelczyk Date: Fri, 18 Sep 2015 16:26:58 +0200 Subject: [PATCH 08/16] lxcpp: Terminal preparation part 2 (guest) [Feature] Make use of the created pseudoterminals in the container [Verification] Build, install, run tests Changes in this commit: - PrepGuestTerminal command - it's main function: setupIOControlTTY() - some more utils/terminal wrappers for syscalls - long overdue open(2) wrapper Change-Id: I235baec044ed82d3376cba3f2e5b4d18f5b1dd71 --- common/utils/fd-utils.cpp | 59 +++++++++++++++++++++++++- common/utils/fd-utils.hpp | 17 ++++++++ libs/lxcpp/commands/attach.cpp | 8 +--- libs/lxcpp/commands/prep-guest-terminal.cpp | 64 +++++++++++++++++++++++++++++ libs/lxcpp/commands/prep-guest-terminal.hpp | 58 ++++++++++++++++++++++++++ libs/lxcpp/commands/prep-host-terminal.cpp | 3 +- libs/lxcpp/container-impl.cpp | 34 +++++++++++++++ libs/lxcpp/credentials.cpp | 12 ++++++ libs/lxcpp/credentials.hpp | 4 +- libs/lxcpp/guard/guard.cpp | 4 ++ libs/lxcpp/guard/main.cpp | 6 +-- libs/lxcpp/process.cpp | 10 +---- libs/lxcpp/terminal.cpp | 32 +++++++++++++++ libs/lxcpp/terminal.hpp | 10 +++++ 14 files changed, 299 insertions(+), 22 deletions(-) create mode 100644 libs/lxcpp/commands/prep-guest-terminal.cpp create mode 100644 libs/lxcpp/commands/prep-guest-terminal.hpp diff --git a/common/utils/fd-utils.cpp b/common/utils/fd-utils.cpp index 3e0db3e..9bd7d29 100644 --- a/common/utils/fd-utils.cpp +++ b/common/utils/fd-utils.cpp @@ -22,6 +22,10 @@ * @brief File descriptor utility functions */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include "config.hpp" #include "utils/fd-utils.hpp" @@ -32,9 +36,11 @@ #include #include #include +#include #include #include #include +#include #include namespace fs = boost::filesystem; @@ -117,6 +123,31 @@ void setFDFlag(const int fd, const int getOp, const int setOp, const int flag, c } // namespace + +int open(const std::string &path, int flags, mode_t mode) +{ + assert(!(flags & O_CREAT || flags & O_TMPFILE) || mode >= 0); + + int fd; + + for (;;) { + fd = ::open(path.c_str(), flags, mode); + + if (-1 == fd) { + if (errno == EINTR) { + LOGT("open() interrupted by a signal, retrying"); + continue; + } + const std::string msg = "open() failed: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + break; + } + + return fd; +} + void close(int fd) { if (fd < 0) { @@ -126,7 +157,7 @@ void close(int fd) for (;;) { if (-1 == ::close(fd)) { if (errno == EINTR) { - LOGT("Close interrupted by a signal, retrying"); + LOGT("close() interrupted by a signal, retrying"); continue; } LOGE("Error in close: " << getSystemErrorMessage()); @@ -148,6 +179,32 @@ void shutdown(int fd) } } +int ioctl(int fd, unsigned long request, void *argp) +{ + int ret = ::ioctl(fd, request, argp); + if (ret == -1) { + const std::string msg = "ioctl() failed: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + return ret; +} + +int dup2(int oldFD, int newFD, bool closeOnExec) +{ + int flags = 0; + if (closeOnExec) { + flags |= O_CLOEXEC; + } + int fd = dup3(oldFD, newFD, flags); + if (fd == -1) { + const std::string msg = "dup3() failed: " + getSystemErrorMessage(); + LOGE(msg); + throw UtilsException(msg); + } + return fd; +} + void write(int fd, const void* bufferPtr, const size_t size, int timeoutMS) { chr::high_resolution_clock::time_point deadline = chr::high_resolution_clock::now() + diff --git a/common/utils/fd-utils.hpp b/common/utils/fd-utils.hpp index 91bbff4..60aed45 100644 --- a/common/utils/fd-utils.hpp +++ b/common/utils/fd-utils.hpp @@ -25,11 +25,18 @@ #ifndef COMMON_UTILS_FD_HPP #define COMMON_UTILS_FD_HPP +#include #include +#include namespace utils { /** + * Open a file. + */ +int open(const std::string &path, int flags, mode_t mode = -1); + +/** * Close the file descriptor. */ void close(int fd); @@ -40,6 +47,16 @@ void close(int fd); void shutdown(int fd); /** + * Operation on a special file + */ +int ioctl(int fd, unsigned long request, void *argp); + +/** + * Duplicate one file desciptor onto another + */ +int dup2(int olfFD, int newFD, bool closeOnExec = false); + +/** * Write to a file descriptor, throw on error. * * @param fd file descriptor diff --git a/libs/lxcpp/commands/attach.cpp b/libs/lxcpp/commands/attach.cpp index 419f651..e1bdfef 100644 --- a/libs/lxcpp/commands/attach.cpp +++ b/libs/lxcpp/commands/attach.cpp @@ -132,13 +132,7 @@ Attach::Attach(lxcpp::ContainerImpl& container, mEnvToKeep(envToKeep), mEnvToSet(envToSet) { - mTTYFD = ::open(ttyPath.c_str(), O_RDWR | O_NOCTTY); - if (mTTYFD < 0) { - const std::string msg = "open() failed: " + - utils::getSystemErrorMessage(); - LOGE(msg); - throw BadArgument(msg); - } + mTTYFD = utils::open(ttyPath, O_RDWR | O_NOCTTY); } Attach::~Attach() diff --git a/libs/lxcpp/commands/prep-guest-terminal.cpp b/libs/lxcpp/commands/prep-guest-terminal.cpp new file mode 100644 index 0000000..f496f86 --- /dev/null +++ b/libs/lxcpp/commands/prep-guest-terminal.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Implementation of guest terminal preparation + */ + +#include "lxcpp/commands/prep-guest-terminal.hpp" +#include "lxcpp/terminal.hpp" + +#include "utils/fd-utils.hpp" +#include "logger/logger.hpp" + + +namespace lxcpp { + + +PrepGuestTerminal::PrepGuestTerminal(TerminalsConfig &terminals) + : mTerminals(terminals) +{ +} + +PrepGuestTerminal::~PrepGuestTerminal() +{ +} + +void PrepGuestTerminal::execute() +{ + LOGD("Preparing " << mTerminals.count << " pseudoterminal(s) on the guest side."); + + // TODO when /dev/ is already namespaced, create: + // /dev/pts/x (N times) (or mount devpts?) + // symlink /dev/console -> first PTY + // symlink /dev/ttyY -> /dev/pts/X (N times) + //for (int i = 0; i < mTerminals.count; ++i) {} + + // Setup first PTY as a controlling terminal (/dev/console). + // This way simple programs in the container can work + // and we will be able to see the output of a container's init + // before the launch of getty processes. + int fd = utils::open(mTerminals.PTYs[0].ptsName, + O_RDWR | O_CLOEXEC | O_NOCTTY); + setupIOControlTTY(fd); + +} + + +} // namespace lxcpp diff --git a/libs/lxcpp/commands/prep-guest-terminal.hpp b/libs/lxcpp/commands/prep-guest-terminal.hpp new file mode 100644 index 0000000..1086c3b --- /dev/null +++ b/libs/lxcpp/commands/prep-guest-terminal.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@samsung.com) + * @brief Headers for guest terminal preparation + */ + +#ifndef LXCPP_COMMANDS_PREP_GUEST_TERMINAL_HPP +#define LXCPP_COMMANDS_PREP_GUEST_TERMINAL_HPP + +#include "lxcpp/commands/command.hpp" +#include "lxcpp/terminal-config.hpp" + + +namespace lxcpp { + + +class PrepGuestTerminal final: Command { +public: + /** + * Prepares the terminal on the guest side. + * + * It fills the /dev/ directory of a container with appropriate + * entries representing the created PTYs. It also takes already + * created PTYs and sets the first one as a controlling terminal. + * + * @param config container's config + */ + PrepGuestTerminal(TerminalsConfig &config); + ~PrepGuestTerminal(); + + void execute(); + +private: + TerminalsConfig &mTerminals; +}; + + +} // namespace lxcpp + + +#endif // LXCPP_COMMANDS_PREP_GUEST_TERMINAL_HPP diff --git a/libs/lxcpp/commands/prep-host-terminal.cpp b/libs/lxcpp/commands/prep-host-terminal.cpp index f219ff9..1dc4f3d 100644 --- a/libs/lxcpp/commands/prep-host-terminal.cpp +++ b/libs/lxcpp/commands/prep-host-terminal.cpp @@ -43,8 +43,7 @@ void PrepHostTerminal::execute() { LOGD("Creating " << mTerminals.count << " pseudoterminal(s) on the host side:"); - for (int i = 0; i < mTerminals.count; ++i) - { + for (int i = 0; i < mTerminals.count; ++i) { const auto pty = lxcpp::openPty(true); LOGD(pty.second << " has been created"); mTerminals.PTYs.emplace_back(pty.first, pty.second); diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 715dad8..7b715f3 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -40,6 +40,34 @@ #include #include +#include +#include +#include +#include + + +namespace { + +// TODO: UGLY: REMOVEME: +// It will be removed as soon as Container::console() will get implemented +// I need it for now to know I didn't brake anything. It will be eradicated. +void readTerminal(const lxcpp::TerminalConfig &term) +{ + char *buf = NULL; + size_t size = 0; + ssize_t ret; + + printf("%s output:\n", term.ptsName.c_str()); + usleep(10000); + + FILE *fp = fdopen(term.masterFD.value, "r"); + while((ret = getline(&buf, &size, fp)) != -1L) { + printf("%s", buf); + } + free(buf); +} + +} // namespace namespace lxcpp { @@ -149,10 +177,16 @@ void ContainerImpl::start() Start start(mConfig); start.execute(); + + // TODO: UGLY: REMOVEME: read from 1st terminal + readTerminal(mConfig.mTerminals.PTYs[0]); } void ContainerImpl::stop() { + // TODO: things to do when shuttting down the container: + // - close PTY master FDs from the config so we won't keep PTYs open + throw NotImplementedException(); } diff --git a/libs/lxcpp/credentials.cpp b/libs/lxcpp/credentials.cpp index 692be25..c127170 100644 --- a/libs/lxcpp/credentials.cpp +++ b/libs/lxcpp/credentials.cpp @@ -62,5 +62,17 @@ void setuid(const uid_t uid) } } +pid_t setsid() +{ + pid_t pid = ::setsid(); + if (pid == -1) { + const std::string msg = "setsid() failed: " + + utils::getSystemErrorMessage(); + LOGE(msg); + throw CredentialSetupException(msg); + } + return pid; +} + } // namespace lxcpp diff --git a/libs/lxcpp/credentials.hpp b/libs/lxcpp/credentials.hpp index ab1a490..aa56aca 100644 --- a/libs/lxcpp/credentials.hpp +++ b/libs/lxcpp/credentials.hpp @@ -36,6 +36,8 @@ void setgid(const gid_t gid); void setuid(const uid_t uid); +pid_t setsid(); + } // namespace lxcpp -#endif // LXCPP_CREDENTIALS_HPP \ No newline at end of file +#endif // LXCPP_CREDENTIALS_HPP diff --git a/libs/lxcpp/guard/guard.cpp b/libs/lxcpp/guard/guard.cpp index 63c7263..952cdf5 100644 --- a/libs/lxcpp/guard/guard.cpp +++ b/libs/lxcpp/guard/guard.cpp @@ -24,6 +24,7 @@ #include "lxcpp/utils.hpp" #include "lxcpp/guard/guard.hpp" #include "lxcpp/process.hpp" +#include "lxcpp/commands/prep-guest-terminal.hpp" #include "config/manager.hpp" #include "logger/logger.hpp" @@ -80,6 +81,9 @@ int startGuard(int channelFD) if (pid == 0) { // TODO: container preparation part 2 + PrepGuestTerminal terminals(cfg.mTerminals); + terminals.execute(); + startContainer(cfg); ::_exit(EXIT_FAILURE); } diff --git a/libs/lxcpp/guard/main.cpp b/libs/lxcpp/guard/main.cpp index 1065ecb..78c7476 100644 --- a/libs/lxcpp/guard/main.cpp +++ b/libs/lxcpp/guard/main.cpp @@ -35,18 +35,18 @@ int main(int argc, char *argv[]) ::_exit(EXIT_FAILURE); } - int channel = std::stoi(argv[1]); + int channelFD = std::stoi(argv[1]); // NOTE: this might not be required now, but I leave it here not to forget. // We need to investigate this with vasum and think about possibility of // poorly written software that leaks file descriptors and might use LXCPP. #if 0 for(int fd = 3; fd < ::sysconf(_SC_OPEN_MAX); ++fd) { - if (fd != channel) { + if (fd != channelFD) { utils::close(fd); } } #endif - return lxcpp::startGuard(channel); + return lxcpp::startGuard(channelFD); } diff --git a/libs/lxcpp/process.cpp b/libs/lxcpp/process.cpp index 96475d5..0353d82 100644 --- a/libs/lxcpp/process.cpp +++ b/libs/lxcpp/process.cpp @@ -79,13 +79,7 @@ pid_t clone(int (*function)(void *), void setns(const pid_t pid, const std::vector& namespaces) { - int dirFD = ::open(getNsPath(pid).c_str(), O_DIRECTORY | O_CLOEXEC); - if(dirFD < 0) { - const std::string msg = "open() failed: " + - utils::getSystemErrorMessage(); - LOGE(msg); - throw ProcessSetupException(msg); - } + int dirFD = utils::open(getNsPath(pid), O_DIRECTORY | O_CLOEXEC); // Open FDs connected with the requested namespaces std::vector fds(namespaces.size(), -1); @@ -162,4 +156,4 @@ void unshare(const Namespace ns) throw ProcessSetupException(msg); } } -} // namespace lxcpp \ No newline at end of file +} // namespace lxcpp diff --git a/libs/lxcpp/terminal.cpp b/libs/lxcpp/terminal.cpp index ce77cd5..72c57af 100644 --- a/libs/lxcpp/terminal.cpp +++ b/libs/lxcpp/terminal.cpp @@ -22,6 +22,7 @@ */ #include "lxcpp/exception.hpp" +#include "lxcpp/credentials.hpp" #include "logger/logger.hpp" #include "utils/exception.hpp" @@ -119,6 +120,37 @@ err: return ret; } +bool isatty(int fd) +{ + int ret = ::isatty(fd); + if (ret) { + return true; + } + if (errno == EINVAL || errno == ENOTTY) { + return false; + } + + const std::string msg = "isatty() failed: " + utils::getSystemErrorMessage(); + LOGE(msg); + throw TerminalException(msg); +} + +void setupIOControlTTY(const int ttyFD) +{ + if (!lxcpp::isatty(ttyFD)) { + const std::string msg = "setupIOControlTTY(): file descriptor passed is not a terminal"; + LOGE(msg); + throw TerminalException(msg); + } + + lxcpp::setsid(); + utils::ioctl(ttyFD, TIOCSCTTY, NULL); + + utils::dup2(ttyFD, STDIN_FILENO); + utils::dup2(ttyFD, STDOUT_FILENO); + utils::dup2(ttyFD, STDERR_FILENO); +} + std::pair openPty(bool rawMode) { int master, slave; diff --git a/libs/lxcpp/terminal.hpp b/libs/lxcpp/terminal.hpp index 85ae6e2..421e4ce 100644 --- a/libs/lxcpp/terminal.hpp +++ b/libs/lxcpp/terminal.hpp @@ -40,6 +40,16 @@ namespace lxcpp { int nullStdFDs(); /** + * Checks if a file descriptor is a terminal + */ +bool isatty(int fd); + +/** + * Setups the passed fd as a new control and IO (in, out, err) terminal + */ +void setupIOControlTTY(const int ttyFD); + +/** * This function creates a new pair of virtual character devices * using a pseudtoreminal interface. It also configures as much as it * can so the devices are immediately usable. -- 2.7.4 From d59013e9bc3dd7060947f2cc2ff74fd03efa8fee Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Thu, 17 Sep 2015 14:55:25 +0200 Subject: [PATCH 09/16] lxcpp: Attach process helper [Feature] N/A [Cause] Attach should work in multi-threaded programs [Solution] Moved part of Attach implementation to a helper binary [Verification] Build, install, run test Change-Id: Ibdf5e0049ed41fdbbdb67a5f11de8aa415bdb1a5 --- libs/lxcpp/CMakeLists.txt | 9 +- libs/lxcpp/attach/CMakeLists.txt | 37 +++++++ libs/lxcpp/attach/attach-config.hpp | 120 ++++++++++++++++++++++ libs/lxcpp/attach/attach-helper.cpp | 109 ++++++++++++++++++++ libs/lxcpp/attach/attach-helper.hpp | 51 ++++++++++ libs/lxcpp/attach/main.cpp | 63 ++++++++++++ libs/lxcpp/commands/attach.cpp | 195 ++++++------------------------------ libs/lxcpp/commands/attach.hpp | 36 ++----- libs/lxcpp/config.hpp | 78 +++++++++++++++ libs/lxcpp/container-impl.cpp | 6 +- libs/lxcpp/container-impl.hpp | 2 +- libs/lxcpp/container.hpp | 4 +- libs/lxcpp/utils.cpp | 64 ++++++++++++ libs/lxcpp/utils.hpp | 4 + packaging/vasum.spec | 1 + 15 files changed, 581 insertions(+), 198 deletions(-) create mode 100644 libs/lxcpp/attach/CMakeLists.txt create mode 100644 libs/lxcpp/attach/attach-config.hpp create mode 100644 libs/lxcpp/attach/attach-helper.cpp create mode 100644 libs/lxcpp/attach/attach-helper.hpp create mode 100644 libs/lxcpp/attach/main.cpp create mode 100644 libs/lxcpp/config.hpp diff --git a/libs/lxcpp/CMakeLists.txt b/libs/lxcpp/CMakeLists.txt index d5e205b..9e49843 100644 --- a/libs/lxcpp/CMakeLists.txt +++ b/libs/lxcpp/CMakeLists.txt @@ -22,6 +22,9 @@ PROJECT(lxcpp) SET(GUARD_CODENAME "${PROJECT_NAME}-guard") ADD_SUBDIRECTORY(guard) +SET(ATTACH_CODENAME "${PROJECT_NAME}-attach") +ADD_SUBDIRECTORY(attach) + MESSAGE(STATUS "") MESSAGE(STATUS "Generating makefile for the liblxcpp...") FILE(GLOB HEADERS *.hpp) @@ -40,7 +43,11 @@ ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRCS} ${SRCS_COMMANDS}) SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES SOVERSION ${_LIB_SOVERSION_} VERSION ${_LIB_VERSION_} - COMPILE_DEFINITIONS GUARD_PATH="${LIBEXEC_DIR}/${GUARD_CODENAME}" +) + +TARGET_COMPILE_DEFINITIONS(${PROJECT_NAME} + PRIVATE GUARD_PATH="${LIBEXEC_DIR}/${GUARD_CODENAME}" + PRIVATE ATTACH_PATH="${LIBEXEC_DIR}/${ATTACH_CODENAME}" ) ADD_DEPENDENCIES(${PROJECT_NAME} Common Logger) diff --git a/libs/lxcpp/attach/CMakeLists.txt b/libs/lxcpp/attach/CMakeLists.txt new file mode 100644 index 0000000..cdd40ca --- /dev/null +++ b/libs/lxcpp/attach/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +# @file CMakeLists.txt +# @author Jan Olszak (j.olszak@samsung.com) +# + +MESSAGE(STATUS "") +MESSAGE(STATUS "Generating makefile for the Attach...") +FILE(GLOB attach_SRCS *.cpp *.hpp) + + +## Setup target ################################################################ +ADD_EXECUTABLE(${ATTACH_CODENAME} ${attach_SRCS}) + + +## Link libraries ############################################################## +PKG_CHECK_MODULES(ATTACH_DEPS REQUIRED glib-2.0) +INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) +INCLUDE_DIRECTORIES(SYSTEM ${ATTACH_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(${ATTACH_CODENAME} Common lxcpp Config) + + +## Install ##################################################################### +INSTALL(TARGETS ${ATTACH_CODENAME} DESTINATION ${LIBEXEC_DIR}) diff --git a/libs/lxcpp/attach/attach-config.hpp b/libs/lxcpp/attach/attach-config.hpp new file mode 100644 index 0000000..9788abe --- /dev/null +++ b/libs/lxcpp/attach/attach-config.hpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Internal structure sent between Attach command and AttachHelper + */ + +#ifndef LXCPP_ATTACH_ATTACH_CONFIG_HPP +#define LXCPP_ATTACH_ATTACH_CONFIG_HPP + +#include "lxcpp/namespace.hpp" + +#include +#include + +#include + +#include +#include + +namespace lxcpp { + +struct AttachConfig { + + /// Arguments passed by user, argv[0] is the binary's path in container + std::vector argv; + + /// PID of the container's init process + pid_t initPid; + + /// Namespaces to which we'll attach + std::vector namespaces; + + /// User ID to set + uid_t uid; + + /// Group ID to set + gid_t gid; + + /// PTS that will become the control terminal for the attached process + int ttyFD; + + /// Supplementary groups to set + std::vector supplementaryGids; + + /// Mask of capabilities that will be available + int capsToKeep; + + /// Work directory for the attached process + std::string workDirInContainer; + + /// Environment variables that will be kept + std::vector envToKeep; + + /// Environment variables that will be set/updated for the attached process + std::vector> envToSet; + + AttachConfig() = default; + + AttachConfig(const std::vector& argv, + const pid_t initPid, + const std::vector& namespaces, + const uid_t uid, + const gid_t gid, + const std::vector& supplementaryGids, + const int capsToKeep, + const std::string& workDirInContainer, + const std::vector& envToKeep, + const std::vector>& envToSet) + : argv(argv), + initPid(initPid), + namespaces(namespaces), + uid(uid), + gid(gid), + ttyFD(-1), + supplementaryGids(supplementaryGids), + capsToKeep(capsToKeep), + workDirInContainer(workDirInContainer), + envToKeep(envToKeep), + envToSet(envToSet) + {} + + CONFIG_REGISTER + ( + //TODO: Uncomment and fix cstring serialization + // argv, + initPid, + //TODO: Uncomment and fix Namespace serialization (or remove Namespace) + // namespaces, + uid, + gid, + ttyFD, + supplementaryGids, + capsToKeep, + workDirInContainer, + envToKeep + //TODO: Uncomment and fix std::pair serialization + // envToSet + ) +}; + +} // namespace lxcpp + +#endif // LXCPP_ATTACH_ATTACH_CONFIG_HPP \ No newline at end of file diff --git a/libs/lxcpp/attach/attach-helper.cpp b/libs/lxcpp/attach/attach-helper.cpp new file mode 100644 index 0000000..321502f --- /dev/null +++ b/libs/lxcpp/attach/attach-helper.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Implementation of attaching to a container + */ + +#include "lxcpp/attach/attach-helper.hpp" +#include "lxcpp/exception.hpp" +#include "lxcpp/process.hpp" +#include "lxcpp/filesystem.hpp" +#include "lxcpp/namespace.hpp" +#include "lxcpp/capability.hpp" +#include "lxcpp/environment.hpp" +#include "lxcpp/credentials.hpp" +#include "lxcpp/utils.hpp" + +#include "utils/exception.hpp" +#include "utils/fd-utils.hpp" +#include "logger/logger.hpp" +#include "config/manager.hpp" + +#include +#include +#include + +#include + +namespace lxcpp { + +namespace { + +int child(void* data) +{ + AttachConfig& config = *static_cast(data); + + // Setup /proc /sys mount + setupMountPoints(); + + // Setup capabilities + dropCapsFromBoundingExcept(config.capsToKeep); + + // Setup environment variables + clearenvExcept(config.envToKeep); + setenv(config.envToSet); + + // Set uid/gids + lxcpp::setgid(config.gid); + setgroups(config.supplementaryGids); + lxcpp::setuid(config.uid); + + // Set control TTY + if(!setupControlTTY(config.ttyFD)) { + ::_exit(EXIT_FAILURE); + } + + // Run user's binary + ::execve(config.argv[0], const_cast(config.argv.data()), nullptr); + return EXIT_FAILURE; +} + +} // namespace + +AttachHelper::AttachHelper(const int channelFD) + : mChannel(channelFD) +{ + mChannel.setCloseOnExec(true); + config::loadFromFD(mChannel.getFD(), mConfig); +} + +AttachHelper::~AttachHelper() +{ + utils::close(mConfig.ttyFD); +} + +void AttachHelper::execute() +{ + // Move to the right namespaces + lxcpp::setns(mConfig.initPid, mConfig.namespaces); + + // Change the current work directory + // workDirInContainer is a path relative to the container's root + lxcpp::chdir(mConfig.workDirInContainer); + + // Unsharing PID namespace won't affect the returned childPid + // CLONE_PARENT: Child's PPID == Caller's PID + const pid_t childPid = lxcpp::clone(child, + &mConfig, + CLONE_PARENT); + mChannel.write(childPid); +} + +} // namespace lxcpp diff --git a/libs/lxcpp/attach/attach-helper.hpp b/libs/lxcpp/attach/attach-helper.hpp new file mode 100644 index 0000000..5362a0c --- /dev/null +++ b/libs/lxcpp/attach/attach-helper.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Implementation of attaching to a container + */ + +#ifndef LXCPP_ATTACH_ATTACH_HELPER_HPP +#define LXCPP_ATTACH_ATTACH_HELPER_HPP + +#include "lxcpp/attach/attach-config.hpp" + +#include "utils/channel.hpp" + +namespace lxcpp { + +/** + * Implementation of the Intermediate helper process. + * @see Attach + */ +class AttachHelper { +public: + AttachHelper(const int channelFD); + ~AttachHelper(); + + void execute(); + +private: + utils::Channel mChannel; + AttachConfig mConfig; +}; + +} // namespace lxcpp + +#endif // LXCPP_ATTACH_ATTACH_HELPER_HPP \ No newline at end of file diff --git a/libs/lxcpp/attach/main.cpp b/libs/lxcpp/attach/main.cpp new file mode 100644 index 0000000..d5924ec --- /dev/null +++ b/libs/lxcpp/attach/main.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 Samsung Electronics Co., Ltd All Rights Reserved + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * @author Jan Olszak (j.olszak@samsung.com) + * @brief Main file for the Intermediate process (libexec) + */ + +#include "lxcpp/attach/attach-helper.hpp" + +#include "utils/fd-utils.hpp" +#include "utils/typeinfo.hpp" + +// TODO: Implement passing logger configuration and uncomment this. +// #include "logger/logger.hpp" + +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc == 1) { + std::cout << "This file should not be executed by hand" << std::endl; + ::_exit(EXIT_FAILURE); + } + + // NOTE: this might not be required now, but I leave it here not to forget. + // We need to investigate this with vasum and think about possibility of + // poorly written software that leaks file descriptors and might use LXCPP. +#if 0 + for(int fd = 3; fd < ::sysconf(_SC_OPEN_MAX); ++fd) { + if(fd != std::stoi(argv[1])) { + utils::close(fd); + } + } +#endif + + try { + int fd = std::stoi(argv[1]); + lxcpp::AttachHelper attachHelper(fd); + attachHelper.execute(); + return EXIT_SUCCESS; + } catch(std::exception& e) { + // TODO: Implement passing logger configuration and uncomment this. + // LOGE("Unexpected: " << utils::getTypeName(e) << ": " << e.what()); + return EXIT_FAILURE; + } +} diff --git a/libs/lxcpp/commands/attach.cpp b/libs/lxcpp/commands/attach.cpp index e1bdfef..2bfa592 100644 --- a/libs/lxcpp/commands/attach.cpp +++ b/libs/lxcpp/commands/attach.cpp @@ -24,96 +24,17 @@ #include "lxcpp/commands/attach.hpp" #include "lxcpp/exception.hpp" #include "lxcpp/process.hpp" -#include "lxcpp/filesystem.hpp" -#include "lxcpp/namespace.hpp" -#include "lxcpp/capability.hpp" -#include "lxcpp/environment.hpp" -#include "lxcpp/credentials.hpp" #include "utils/exception.hpp" #include "utils/fd-utils.hpp" #include "logger/logger.hpp" #include -#include -#include -#include - -#include namespace lxcpp { -namespace { - -void setupMountPoints() -{ - /* TODO: Uncomment when preparing the final attach() version - - // TODO: This unshare should be optional only if we attach to PID/NET namespace, but not MNT. - // Otherwise container already has remounted /proc /sys - lxcpp::unshare(Namespace::MNT); - - if (isMountPointShared("/")) { - // TODO: Handle case when the container rootfs or mount location is MS_SHARED, but not '/' - lxcpp::mount(nullptr, "/", nullptr, MS_SLAVE | MS_REC, nullptr); - } - - if(isMountPoint("/proc")) { - lxcpp::umount("/proc", MNT_DETACH); - lxcpp::mount("none", "/proc", "proc", 0, nullptr); - } - - if(isMountPoint("/sys")) { - lxcpp::umount("/sys", MNT_DETACH); - lxcpp::mount("none", "/sys", "sysfs", 0, nullptr); - } - - */ -} - -bool setupControlTTY(const int ttyFD) -{ - if (!::isatty(ttyFD)) { - return false; - } - - if (::setsid() < 0) { - return false; - } - - if (::ioctl(ttyFD, TIOCSCTTY, NULL) < 0) { - return false; - } - - if (::dup2(ttyFD, STDIN_FILENO) < 0) { - return false; - } - - if (::dup2(ttyFD, STDOUT_FILENO) < 0) { - return false; - } - - if (::dup2(ttyFD, STDERR_FILENO) < 0) { - return false; - } - - return true; -} - -int execFunction(void* call) -{ - try { - return (*static_cast(call))(); - } catch(...) { - return -1; // Non-zero on failure - } - return 0; // Success -} - -} // namespace - -Attach::Attach(lxcpp::ContainerImpl& container, - Container::AttachCall& userCall, +Attach::Attach(const lxcpp::ContainerImpl& container, + const std::vector& argv, const uid_t uid, const gid_t gid, const std::string& ttyPath, @@ -122,112 +43,62 @@ Attach::Attach(lxcpp::ContainerImpl& container, const std::string& workDirInContainer, const std::vector& envToKeep, const std::vector>& envToSet) - : mContainer(container), - mUserCall(userCall), - mUid(uid), - mGid(gid), - mSupplementaryGids(supplementaryGids), - mCapsToKeep(capsToKeep), - mWorkDirInContainer(workDirInContainer), - mEnvToKeep(envToKeep), - mEnvToSet(envToSet) + : mIntermChannel(false), + mConfig(argv, + container.getInitPid(), + container.getNamespaces(), + uid, + gid, + supplementaryGids, + capsToKeep, + workDirInContainer, + envToKeep, + envToSet) { - mTTYFD = utils::open(ttyPath, O_RDWR | O_NOCTTY); + // Set TTY + if (ttyPath.empty()) { + mConfig.ttyFD = -1; + } else { + mConfig.ttyFD = utils::open(ttyPath, O_RDWR | O_NOCTTY); + } } Attach::~Attach() { - utils::close(mTTYFD); + utils::close(mConfig.ttyFD); } void Attach::execute() { - // Channels for setup synchronization - utils::Channel intermChannel; - - Call call = std::bind(&Attach::child, - mUserCall, - mUid, - mGid, - mTTYFD, - mSupplementaryGids, - mCapsToKeep, - mEnvToKeep, - mEnvToSet); + // Channels for passing configuration and synchronization + const std::string mIntermChannelFDStr = std::to_string(mIntermChannel.getRightFD()); const pid_t interPid = lxcpp::fork(); if (interPid > 0) { - intermChannel.setLeft(); - parent(intermChannel, interPid); - intermChannel.shutdown(); - } else { - // TODO: only safe functions mentioned in signal(7) should be used below. - // This is not the case now, needs fixing. - intermChannel.setRight(); - interm(intermChannel, call); - intermChannel.shutdown(); - ::_exit(EXIT_SUCCESS); - } -} - -int Attach::child(const Container::AttachCall& call, - const uid_t uid, - const gid_t gid, - const int ttyFD, - const std::vector& supplementaryGids, - const int capsToKeep, - const std::vector& envToKeep, - const std::vector>& envToSet) -{ - // Setup /proc /sys mount - setupMountPoints(); + mIntermChannel.setLeft(); - // Setup capabilities - dropCapsFromBoundingExcept(capsToKeep); + parent(interPid); - // Setup environment variables - clearenvExcept(envToKeep); - setenv(envToSet); - - // Set uid/gids - lxcpp::setgid(gid); - setgroups(supplementaryGids); - - lxcpp::setuid(uid); + } else { + mIntermChannel.setRight(); - // Set control TTY - if(!setupControlTTY(ttyFD)) { + const char *argv[] = {ATTACH_PATH, + mIntermChannelFDStr.c_str(), + NULL + }; + ::execve(argv[0], const_cast(argv), nullptr); ::_exit(EXIT_FAILURE); } - - // Run user's code - return call(); } -void Attach::parent(utils::Channel& intermChannel, const pid_t interPid) +void Attach::parent(const pid_t interPid) { // TODO: Setup cgroups etc - const pid_t childPid = intermChannel.read(); + const pid_t childPid = mIntermChannel.read(); // Wait for all processes lxcpp::waitpid(interPid); lxcpp::waitpid(childPid); } -void Attach::interm(utils::Channel& intermChannel, Call& call) -{ - lxcpp::setns(mContainer.getInitPid(), mContainer.getNamespaces()); - - // Change the current work directory - // workDirInContainer is a path relative to the container's root - lxcpp::chdir(mWorkDirInContainer); - - // PID namespace won't affect the returned pid - // CLONE_PARENT: Child's PPID == Caller's PID - const pid_t childPid = lxcpp::clone(execFunction, - &call, - CLONE_PARENT); - intermChannel.write(childPid); -} - } // namespace lxcpp diff --git a/libs/lxcpp/commands/attach.hpp b/libs/lxcpp/commands/attach.hpp index 2c1f365..ce39b83 100644 --- a/libs/lxcpp/commands/attach.hpp +++ b/libs/lxcpp/commands/attach.hpp @@ -25,6 +25,7 @@ #define LXCPP_COMMANDS_ATTACH_HPP #include "lxcpp/commands/command.hpp" +#include "lxcpp/attach/attach-config.hpp" #include "lxcpp/container-impl.hpp" #include "utils/channel.hpp" @@ -36,7 +37,6 @@ namespace lxcpp { class Attach final: Command { public: - typedef std::function Call; /** * Runs call in the container's context @@ -45,17 +45,18 @@ public: * It will not be stored for future use like most other commands. * * @param container container to which it attaches - * @param userCall user's function to run + * @param argv path and arguments for the user's executable * @param uid uid in the container * @param gid gid in the container + * @param ttyPath path of the TTY in the host * @param supplementaryGids supplementary groups in container * @param capsToKeep capabilities that will be kept * @param workDirInContainer work directory set for the new process * @param envToKeep environment variables that will be kept * @param envToSet new environment variables that will be set */ - Attach(lxcpp::ContainerImpl& container, - Container::AttachCall& userCall, + Attach(const lxcpp::ContainerImpl& container, + const std::vector& argv, const uid_t uid, const gid_t gid, const std::string& ttyPath, @@ -69,32 +70,11 @@ public: void execute(); private: - const lxcpp::ContainerImpl& mContainer; - const Container::AttachCall& mUserCall; - const uid_t mUid; - const gid_t mGid; - int mTTYFD; - const std::vector& mSupplementaryGids; - const int mCapsToKeep; - const std::string& mWorkDirInContainer; - const std::vector& mEnvToKeep; - const std::vector>& mEnvToSet; + utils::Channel mIntermChannel; + AttachConfig mConfig; - // Methods for different stages of setting up the attachment - static int child(const Container::AttachCall& call, - const uid_t uid, - const gid_t gid, - const int ttyFD, - const std::vector& supplementaryGids, - const int capsToKeep, - const std::vector& envToKeep, - const std::vector>& envToSet); + void parent(const pid_t pid); - void parent(utils::Channel& intermChannel, - const pid_t pid); - - void interm(utils::Channel& intermChannel, - Call& call); }; } // namespace lxcpp diff --git a/libs/lxcpp/config.hpp b/libs/lxcpp/config.hpp new file mode 100644 index 0000000..753430e --- /dev/null +++ b/libs/lxcpp/config.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved + * + * Contact: Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com) + * + * 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 + */ + +/** + * @file + * @author Lukasz Pawelczyk (l.pawelczyk@partner.samsung.com) + * @brief Configuration file for the code + */ + + +#ifndef COMMON_CONFIG_HPP +#define COMMON_CONFIG_HPP + + +#ifdef __clang__ +#define CLANG_VERSION (__clang__major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#endif // __clang__ + +#if defined __GNUC__ && !defined __clang__ // clang also defines GCC versions +#define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + + +#ifdef GCC_VERSION + +#if GCC_VERSION < 40800 +// GCC 4.8 is the first where those defines are not required for +// std::this_thread::sleep_for() and ::yield(). They might exist though +// in previous versions depending on the build configuration of the GCC. +#ifndef _GLIBCXX_USE_NANOSLEEP +#define _GLIBCXX_USE_NANOSLEEP +#endif // _GLIBCXX_USE_NANOSLEEP +#ifndef _GLIBCXX_USE_SCHED_YIELD +#define _GLIBCXX_USE_SCHED_YIELD +#endif // _GLIBCXX_USE_SCHED_YIELD +#endif // GCC_VERSION < 40800 + +#if GCC_VERSION < 40700 +// Those appeared in 4.7 with full c++11 support +#define final +#define override +#define thread_local __thread // use GCC extension instead of C++11 +#define steady_clock monotonic_clock +#endif // GCC_VERSION < 40700 + +#endif // GCC_VERSION + +// Variadic macros support for boost preprocessor should be enabled +// manually for clang since they are marked as untested feature +// (boost trunk if fixed but the latest 1.55 version is not, +// see boost/preprocessor/config/config.hpp) +#ifdef __clang__ +#define BOOST_PP_VARIADICS 1 +#endif + +// This has to be defined always when the boost has not been compiled +// using C++11. Headers detect that you are compiling using C++11 and +// blindly and wrongly assume that boost has been as well. +#ifndef BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_CXX11_SCOPED_ENUMS 1 +#endif + +#endif // COMMON_CONFIG_HPP diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 7b715f3..5301cb5 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -205,11 +205,11 @@ void ContainerImpl::reboot() throw NotImplementedException(); } -void ContainerImpl::attach(Container::AttachCall& call, +void ContainerImpl::attach(const std::vector& argv, const std::string& cwdInContainer) { Attach attach(*this, - call, + argv, /*uid in container*/ 0, /*gid in container*/ 0, "/dev/tty", @@ -217,7 +217,7 @@ void ContainerImpl::attach(Container::AttachCall& call, /*capsToKeep*/ 0, cwdInContainer, /*envToKeep*/ {}, - /*envInContainer*/ {{"container","lxcpp"}}); + /*envInContainer*/ {{"container","lxcpp"}}); // TODO: Env variables should agree with the ones already in the container attach.execute(); } diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index 97b9155..b0786a1 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -66,7 +66,7 @@ public: void reboot(); // Other - void attach(Container::AttachCall& attachCall, + void attach(const std::vector& argv, const std::string& cwdInContainer); // Network interfaces setup/config diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index bf10a76..96af842 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -45,8 +45,6 @@ struct NetworkInterfaceInfo { class Container { public: - typedef std::function AttachCall; - virtual ~Container() {}; // Configuration @@ -73,7 +71,7 @@ public: virtual void reboot() = 0; // Other - virtual void attach(AttachCall& attachCall, + virtual void attach(const std::vector& argv, const std::string& cwdInContainer) = 0; // Network interfaces setup/config diff --git a/libs/lxcpp/utils.cpp b/libs/lxcpp/utils.cpp index 685d915..0c731b5 100644 --- a/libs/lxcpp/utils.cpp +++ b/libs/lxcpp/utils.cpp @@ -26,6 +26,9 @@ #endif #include "lxcpp/exception.hpp" +#include "lxcpp/namespace.hpp" +#include "lxcpp/filesystem.hpp" +#include "lxcpp/process.hpp" #include "logger/logger.hpp" #include "utils/fd-utils.hpp" @@ -35,7 +38,10 @@ #include #include #include + #include +#include +#include namespace lxcpp { @@ -82,5 +88,63 @@ void setProcTitle(const std::string &title) ::strcpy(mem, title.c_str()); } +void setupMountPoints() +{ +#if 0 + // TODO: This unshare should be optional only if we attach to PID/NET namespace, but not MNT. + // Otherwise container already has remounted /proc /sys + lxcpp::unshare(Namespace::MNT); + + if (isMountPointShared("/")) { + // TODO: Handle case when the container rootfs or mount location is MS_SHARED, but not '/' + lxcpp::mount(nullptr, "/", nullptr, MS_SLAVE | MS_REC, nullptr); + } + + if(isMountPoint("/proc")) { + lxcpp::umount("/proc", MNT_DETACH); + lxcpp::mount("none", "/proc", "proc", 0, nullptr); + } + + if(isMountPoint("/sys")) { + lxcpp::umount("/sys", MNT_DETACH); + lxcpp::mount("none", "/sys", "sysfs", 0, nullptr); + } +#endif +} + +bool setupControlTTY(const int ttyFD) +{ + if (ttyFD != -1) { + return true; + } + + if (!::isatty(ttyFD)) { + return false; + } + + if (::setsid() < 0) { + return false; + } + + if (::ioctl(ttyFD, TIOCSCTTY, NULL) < 0) { + return false; + } + + if (::dup2(ttyFD, STDIN_FILENO) < 0) { + return false; + } + + if (::dup2(ttyFD, STDOUT_FILENO) < 0) { + return false; + } + + if (::dup2(ttyFD, STDERR_FILENO) < 0) { + return false; + } + + return true; +} + + } // namespace lxcpp diff --git a/libs/lxcpp/utils.hpp b/libs/lxcpp/utils.hpp index e4c158b..c4d469b 100644 --- a/libs/lxcpp/utils.hpp +++ b/libs/lxcpp/utils.hpp @@ -37,6 +37,10 @@ namespace lxcpp { */ void setProcTitle(const std::string &title); +void setupMountPoints(); + +bool setupControlTTY(const int ttyFD); + } // namespace lxcpp diff --git a/packaging/vasum.spec b/packaging/vasum.spec index 7709ab4..546c782 100644 --- a/packaging/vasum.spec +++ b/packaging/vasum.spec @@ -488,6 +488,7 @@ The package provides liblxcpp library. %defattr(644,root,root,755) %{_libdir}/liblxcpp.so.0 %attr(755,root,root) %{_libexecdir}/lxcpp-guard +%attr(755,root,root) %{_libexecdir}/lxcpp-attach %attr(755,root,root) %{_libdir}/liblxcpp.so.%{version} %package -n liblxcpp-devel -- 2.7.4 From 4753f1ecfcb0cd625156b57f9212050225eb68c0 Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Wed, 23 Sep 2015 10:54:20 +0200 Subject: [PATCH 10/16] hotfix: expression always true, use ::ioctl [Feature/Bug] mistake in assert check [Cause] compilation error, testcase failure [Solution] N/A [Verification] Build and run tests Change-Id: Ia9696593c5641a8cc5a2a8b2dc3a9c023609d864 --- common/utils/fd-utils.cpp | 2 +- server/input-monitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/utils/fd-utils.cpp b/common/utils/fd-utils.cpp index 9bd7d29..08603fa 100644 --- a/common/utils/fd-utils.cpp +++ b/common/utils/fd-utils.cpp @@ -126,7 +126,7 @@ void setFDFlag(const int fd, const int getOp, const int setOp, const int flag, c int open(const std::string &path, int flags, mode_t mode) { - assert(!(flags & O_CREAT || flags & O_TMPFILE) || mode >= 0); + assert(!(flags & O_CREAT || flags & O_TMPFILE) || mode != static_cast(-1)); int fd; diff --git a/server/input-monitor.cpp b/server/input-monitor.cpp index 45982af..5ac6e68 100644 --- a/server/input-monitor.cpp +++ b/server/input-monitor.cpp @@ -131,7 +131,7 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex, } char name[DEVICE_NAME_LENGTH]; - if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { + if (::ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) { LOGD("Failed to get the device name of: " << path); if (::close(fd) < 0) { LOGE("Error during closing file " << path); -- 2.7.4 From 19b4d6c1e2ed32f0355a8cef0d94519f298d5015 Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Wed, 23 Sep 2015 10:54:20 +0200 Subject: [PATCH 11/16] hotfix2: assert condition [Feature/Bug] wrong assert condition [Cause] testcase failure [Solution] N/A [Verification] Build and run tests Change-Id: I2ad2e674205fbc927cb85251c0cbe037bca039fb --- common/utils/fd-utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/utils/fd-utils.cpp b/common/utils/fd-utils.cpp index 08603fa..08c52fe 100644 --- a/common/utils/fd-utils.cpp +++ b/common/utils/fd-utils.cpp @@ -126,7 +126,7 @@ void setFDFlag(const int fd, const int getOp, const int setOp, const int flag, c int open(const std::string &path, int flags, mode_t mode) { - assert(!(flags & O_CREAT || flags & O_TMPFILE) || mode != static_cast(-1)); + assert(!((flags & O_CREAT) == O_CREAT || (flags & O_TMPFILE) == O_TMPFILE) || mode != static_cast(-1)); int fd; -- 2.7.4 From 583de1c43bd062edc5db45905f1c10de9de829be Mon Sep 17 00:00:00 2001 From: Pawel Kubik Date: Fri, 25 Sep 2015 14:48:50 +0200 Subject: [PATCH 12/16] Improved coverage report instructions. [Feature] More detailed per-file coverage report instructions. [Cause] N/A [Solution] N/A [Verification] Follow new instructions. Change-Id: I75c563e8627f2f2e5aa22329e231f18fc4f1841a --- doc/coverage_report.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/coverage_report.md b/doc/coverage_report.md index cb9ef8e..4fb5cbe 100644 --- a/doc/coverage_report.md +++ b/doc/coverage_report.md @@ -14,8 +14,11 @@ Requirements Instructions ------------ -All command should be run from within your build directory. **repodir** is a -path to your repository root. +All command should be run from within your build directory. Replace following expressions: + - **repodir** - with a path to your repository root. + - **covdir** - with a path you'd like to place your coverage report + +------------------------------------------------------------------------------- 1. Generate your build using CCOV profile ```bash @@ -33,8 +36,8 @@ sudo vsm_all_tests.py 3. Generate HTML report ```bash -gcovr -e tests -s -v -r repodir --html -o coverage.html +gcovr -e tests -s -v -r repodir --html --html-details -o covdir/index.html ``` -4. Coverage report consists of single page **coverage.html**. Find it in your - build directory and open in a web browser. +4. Open *index.html* in a web browser to see generated documentation. Click on any source file + to see line coverage visualization. -- 2.7.4 From d1d008881304e7b310ead5968fee2e1dd4783789 Mon Sep 17 00:00:00 2001 From: Pawel Kubik Date: Fri, 25 Sep 2015 14:10:29 +0200 Subject: [PATCH 13/16] Fixed dbus testing container configuration. [Feature] Fix [Cause] Zone stopped immediately after start. [Solution] Added sleep-loop to prevent zone from stopping. [Verification] Build (best under CCOV config), install, run: sudo vsm_launch_test.py vasum-server-unit-tests \ -t 'ZoneSuite/DbusConnection' Change-Id: I1ffc9eb047f08173314c16c60ef505f0d81c11c6 --- tests/unit_tests/configs/templates/console-dbus.conf.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/configs/templates/console-dbus.conf.in b/tests/unit_tests/configs/templates/console-dbus.conf.in index 2dee28b..8ab8b98 100644 --- a/tests/unit_tests/configs/templates/console-dbus.conf.in +++ b/tests/unit_tests/configs/templates/console-dbus.conf.in @@ -1,6 +1,6 @@ { "zoneTemplate" : "minimal.sh", - "initWithArgs" : ["/bin/bash", "-c", "trap exit SIGTERM; /bin/dbus-daemon --config-file=@VSM_TEST_CONFIG_INSTALL_DIR@/dbus/ut-dbus-system.conf --fork"], + "initWithArgs" : ["/bin/bash", "-c", "trap exit SIGTERM; /bin/dbus-daemon --config-file=@VSM_TEST_CONFIG_INSTALL_DIR@/dbus/ut-dbus-system.conf --fork; while true; do sleep 0.1; done"], "requestedState" : "running", "ipv4Gateway" : "", "ipv4" : "", -- 2.7.4 From 9aebe3b497f5c36681afb0334c8d4595638ca42e Mon Sep 17 00:00:00 2001 From: Krzysztof Dynowski Date: Thu, 3 Sep 2015 09:34:37 +0200 Subject: [PATCH 14/16] lxcpp: network implementation (part 2) [Feature] Network implementation for lxcpp (create/destroy, add/del ip addr) [Cause] N/A [Solution] N/A [Verification] Build, install, run tests Change-Id: I1286efb4893b9b77bebf20f0e40b9fd06611e48b --- common/netlink/netlink-message.hpp | 1 + common/netlink/netlink.hpp | 1 + libs/config/CMakeLists.txt | 2 +- libs/lxcpp/commands/netcreate.cpp | 45 ++- libs/lxcpp/commands/netcreate.hpp | 75 ++++- libs/lxcpp/container-impl.cpp | 47 +-- libs/lxcpp/container-impl.hpp | 7 +- libs/lxcpp/container.hpp | 6 +- libs/lxcpp/network-config.cpp | 50 +++- libs/lxcpp/network-config.hpp | 143 ++++----- libs/lxcpp/network.cpp | 532 ++++++++++++++++++++-------------- libs/lxcpp/network.hpp | 268 +++++++++++++++-- tests/unit_tests/lxcpp/ut-network.cpp | 129 +++++++-- tests/unit_tests/server/ut-zone.cpp | 2 - 14 files changed, 912 insertions(+), 396 deletions(-) diff --git a/common/netlink/netlink-message.hpp b/common/netlink/netlink-message.hpp index 73e6a4c..3ee729b 100644 --- a/common/netlink/netlink-message.hpp +++ b/common/netlink/netlink-message.hpp @@ -35,6 +35,7 @@ #include #include +//FIXME remove from namespace vasum namespace vasum { namespace netlink { diff --git a/common/netlink/netlink.hpp b/common/netlink/netlink.hpp index 8d33957..56eff0b 100644 --- a/common/netlink/netlink.hpp +++ b/common/netlink/netlink.hpp @@ -28,6 +28,7 @@ #include #include +//FIXME remove from namespace vasum namespace vasum { namespace netlink { diff --git a/libs/config/CMakeLists.txt b/libs/config/CMakeLists.txt index 160cb09..81c8bbb 100644 --- a/libs/config/CMakeLists.txt +++ b/libs/config/CMakeLists.txt @@ -40,7 +40,7 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES PKG_CHECK_MODULES(CONFIG_DEPS REQUIRED sqlite3 glib-2.0) PKG_SEARCH_MODULE(JSON_C REQUIRED json json-c) -INCLUDE_DIRECTORIES(${LIBS_FOLDER}) +INCLUDE_DIRECTORIES(${LIBS_FOLDER} ${COMMON_FOLDER}) INCLUDE_DIRECTORIES(SYSTEM ${CONFIG_DEPS_INCLUDE_DIRS} ${JSON_C_INCLUDE_DIRS}) TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${pkgs_LDFLAGS} ${CONFIG_DEPS_LIBRARIES} ${JSON_C_LIBRARIES}) diff --git a/libs/lxcpp/commands/netcreate.cpp b/libs/lxcpp/commands/netcreate.cpp index 3c88d8e..56c1e2a 100644 --- a/libs/lxcpp/commands/netcreate.cpp +++ b/libs/lxcpp/commands/netcreate.cpp @@ -28,17 +28,46 @@ namespace lxcpp { void NetCreateAll::execute() { - for (const auto& i : mInterfaceConfigs) { - NetworkInterface networkInerface(mContainerPid, i.getZoneIf()); - networkInerface.create(i.getHostIf(), i.getType(), i.getMode()); + pid_t pid = mContainer.getInitPid(); + for (const auto& interface : mNetwork.getInterfaces()) { + NetworkInterface networkInterface(interface.getZoneIf(), pid); + networkInterface.create(interface.getType(), interface.getHostIf(), interface.getMode()); Attrs attrs; - for (const auto& a : i.getAddrList()) { - Attr attr; - NetworkInterface::convertInetAddr2Attr(a, attr); - attrs.push_back(attr); + if (interface.getMTU() > 0) { + attrs.push_back(Attr{AttrName::MTU, std::to_string(interface.getMTU())}); } - networkInerface.setAttrs(attrs); + if (!interface.getMACAddress().empty()) { + attrs.push_back(Attr{AttrName::MAC, interface.getMACAddress()}); + } + if (interface.getTxLength() > 0) { + attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(interface.getTxLength())}); + } + networkInterface.setAttrs(attrs); + + for (const auto& addr : interface.getAddrList()) { + networkInterface.addInetAddr(addr); + } + } +} + +void NetInteraceCreate::execute() +{ + NetworkInterface networkInterface(mZoneIf, mContainer.getInitPid()); + networkInterface.create(mType, mHostIf, mMode); +} + +void NetInterfaceSetAttrs::execute() +{ + NetworkInterface networkInterface(mIfname, mContainer.getInitPid()); + networkInterface.setAttrs(mAttrs); +} + +void NetInterfaceAddInetAddr::execute() +{ + NetworkInterface networkInterface(mIfname, mContainer.getInitPid()); + for (const auto& a : mAddrList) { + networkInterface.addInetAddr(a); } } diff --git a/libs/lxcpp/commands/netcreate.hpp b/libs/lxcpp/commands/netcreate.hpp index 5d6e512..dec7288 100644 --- a/libs/lxcpp/commands/netcreate.hpp +++ b/libs/lxcpp/commands/netcreate.hpp @@ -26,6 +26,7 @@ #include "lxcpp/commands/command.hpp" #include "lxcpp/network-config.hpp" +#include "lxcpp/container.hpp" #include @@ -37,19 +38,81 @@ public: * Runs call in the container's context * * Creates and configures network interfaces in the container - * */ - NetCreateAll(pid_t containerPid, const std::vector& iflist) : - mContainerPid(containerPid), - mInterfaceConfigs(iflist) + NetCreateAll(const Container& container, const NetworkConfig& network) : + mContainer(container), + mNetwork(network) + { + } + + void execute(); + +private: + const Container& mContainer; + const NetworkConfig& mNetwork; +}; + +class NetInteraceCreate final: Command { +public: + NetInteraceCreate(const Container& container, + const std::string& zoneif, + const std::string& hostif, + InterfaceType type, + MacVLanMode mode=MacVLanMode::PRIVATE) : + mContainer(container), + mZoneIf(zoneif), + mHostIf(hostif), + mType(type), + mMode(mode) + { + } + + void execute(); + +private: + const Container& mContainer; + const std::string& mZoneIf; + const std::string& mHostIf; + InterfaceType mType; + MacVLanMode mMode; +}; + +class NetInterfaceSetAttrs final: Command { +public: + NetInterfaceSetAttrs(const Container& container, + const std::string& ifname, + const Attrs& attrs) : + mContainer(container), + mIfname(ifname), + mAttrs(attrs) + { + } + + void execute(); + +private: + const Container& mContainer; + const std::string& mIfname; + const Attrs& mAttrs; +}; + +class NetInterfaceAddInetAddr final: Command { +public: + NetInterfaceAddInetAddr(const Container& container, + const std::string& ifname, + const std::vector& addrList) : + mContainer(container), + mIfname(ifname), + mAddrList(addrList) { } void execute(); private: - const pid_t mContainerPid; - const std::vector& mInterfaceConfigs; + const Container& mContainer; + const std::string& mIfname; + const std::vector& mAddrList; }; } // namespace lxcpp diff --git a/libs/lxcpp/container-impl.cpp b/libs/lxcpp/container-impl.cpp index 5301cb5..c7035e4 100644 --- a/libs/lxcpp/container-impl.cpp +++ b/libs/lxcpp/container-impl.cpp @@ -230,9 +230,10 @@ const std::vector& ContainerImpl::getNamespaces() const void ContainerImpl::addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, + const std::vector& addrs, MacVLanMode mode) { - mConfig.mNetwork.addInterfaceConfig(hostif, zoneif, type, mode); + mConfig.mNetwork.addInterfaceConfig(hostif, zoneif, type, addrs, mode); } void ContainerImpl::addInetConfig(const std::string& ifname, const InetAddr& addr) @@ -247,10 +248,9 @@ std::vector ContainerImpl::getInterfaces() const NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname) const { - NetworkInterface ni(getInitPid(), ifname); + NetworkInterface ni(ifname, getInitPid()); std::vector addrs; std::string macaddr; - InetAddr addr; int mtu = 0, flags = 0; Attrs attrs = ni.getAttrs(); for (const Attr& a : attrs) { @@ -261,11 +261,6 @@ NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname) case AttrName::MTU: mtu = std::stoul(a.value); break; - case AttrName::IPV6: - case AttrName::IPV4: - NetworkInterface::convertAttr2InetAddr(a, addr); - addrs.push_back(addr); - break; case AttrName::FLAGS: flags = std::stoul(a.value); break; @@ -273,6 +268,7 @@ NetworkInterfaceInfo ContainerImpl::getInterfaceInfo(const std::string& ifname) break; } } + addrs = ni.getInetAddressList(); return NetworkInterfaceInfo{ifname, ni.status(), macaddr, mtu, flags, addrs}; } @@ -281,33 +277,44 @@ void ContainerImpl::createInterface(const std::string& hostif, InterfaceType type, MacVLanMode mode) { - NetworkInterface ni(getInitPid(), zoneif); - ni.create(hostif, type, mode); + NetworkInterface ni(zoneif, getInitPid()); + ni.create(type, hostif, mode); } -void ContainerImpl::destroyInterface(const std::string& /*ifname*/) +void ContainerImpl::destroyInterface(const std::string& ifname) { - throw NotImplementedException(); + NetworkInterface ni(ifname, getInitPid()); + ni.destroy(); } -void ContainerImpl::setUp(const std::string& /*ifname*/) +void ContainerImpl::moveInterface(const std::string& ifname) { - throw NotImplementedException(); + NetworkInterface ni(ifname); + ni.moveToContainer(getInitPid()); } -void ContainerImpl::setDown(const std::string& /*ifname*/) +void ContainerImpl::setUp(const std::string& ifname) { - throw NotImplementedException(); + NetworkInterface ni(ifname, getInitPid()); + ni.up(); } -void ContainerImpl::addAddr(const std::string& /*ifname*/, const InetAddr& /*addr*/) +void ContainerImpl::setDown(const std::string& ifname) { - throw NotImplementedException(); + NetworkInterface ni(ifname, getInitPid()); + ni.down(); } -void ContainerImpl::delAddr(const std::string& /*ifname*/, const InetAddr& /*addr*/) +void ContainerImpl::addInetAddr(const std::string& ifname, const InetAddr& addr) { - throw NotImplementedException(); + NetworkInterface ni(ifname, getInitPid()); + ni.addInetAddr(addr); +} + +void ContainerImpl::delInetAddr(const std::string& ifname, const InetAddr& addr) +{ + NetworkInterface ni(ifname, getInitPid()); + ni.delInetAddr(addr); } } // namespace lxcpp diff --git a/libs/lxcpp/container-impl.hpp b/libs/lxcpp/container-impl.hpp index b0786a1..7393974 100644 --- a/libs/lxcpp/container-impl.hpp +++ b/libs/lxcpp/container-impl.hpp @@ -30,7 +30,6 @@ #include "lxcpp/container-config.hpp" #include "lxcpp/container.hpp" #include "lxcpp/namespace.hpp" -#include "lxcpp/network.hpp" namespace lxcpp { @@ -77,6 +76,7 @@ public: void addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, + const std::vector& addrs, MacVLanMode mode); void addInetConfig(const std::string& ifname, const InetAddr& addr); @@ -87,11 +87,12 @@ public: const std::string& zoneif, InterfaceType type, MacVLanMode mode); + void moveInterface(const std::string& ifname); void destroyInterface(const std::string& ifname); void setUp(const std::string& ifname); void setDown(const std::string& ifname); - void addAddr(const std::string& ifname, const InetAddr& addr); - void delAddr(const std::string& ifname, const InetAddr& addr); + void addInetAddr(const std::string& ifname, const InetAddr& addr); + void delInetAddr(const std::string& ifname, const InetAddr& addr); private: ContainerConfig mConfig; diff --git a/libs/lxcpp/container.hpp b/libs/lxcpp/container.hpp index 96af842..51dda8c 100644 --- a/libs/lxcpp/container.hpp +++ b/libs/lxcpp/container.hpp @@ -78,6 +78,7 @@ public: virtual void addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, + const std::vector& addrs, MacVLanMode mode) = 0; virtual void addInetConfig(const std::string& ifname, const InetAddr& addr) = 0; @@ -89,10 +90,11 @@ public: InterfaceType type, MacVLanMode mode) = 0; virtual void destroyInterface(const std::string& ifname) = 0; + virtual void moveInterface(const std::string& ifname) = 0; virtual void setUp(const std::string& ifname) = 0; virtual void setDown(const std::string& ifname) = 0; - virtual void addAddr(const std::string& ifname, const InetAddr& addr) = 0; - virtual void delAddr(const std::string& ifname, const InetAddr& addr) = 0; + virtual void addInetAddr(const std::string& ifname, const InetAddr& addr) = 0; + virtual void delInetAddr(const std::string& ifname, const InetAddr& addr) = 0; }; } // namespace lxcpp diff --git a/libs/lxcpp/network-config.cpp b/libs/lxcpp/network-config.cpp index 17e1449..89019ec 100644 --- a/libs/lxcpp/network-config.cpp +++ b/libs/lxcpp/network-config.cpp @@ -23,12 +23,10 @@ */ #include "lxcpp/network-config.hpp" -#include "lxcpp/network.hpp" #include "lxcpp/exception.hpp" #include "logger/logger.hpp" #include - namespace lxcpp { const std::string& NetworkInterfaceConfig::getHostIf() const @@ -41,14 +39,44 @@ const std::string& NetworkInterfaceConfig::getZoneIf() const return mZoneIf; } -const InterfaceType& NetworkInterfaceConfig::getType() const +InterfaceType NetworkInterfaceConfig::getType() const +{ + return static_cast(mType); +} + +MacVLanMode NetworkInterfaceConfig::getMode() const +{ + return static_cast(mMode); +} + +void NetworkInterfaceConfig::setMTU(int mtu) +{ + mMtu = mtu; +} + +void NetworkInterfaceConfig::setMACAddress(const std::string& mac) { - return mType; + mMacAddress = mac; } -const MacVLanMode& NetworkInterfaceConfig::getMode() const +void NetworkInterfaceConfig::setTxLength(int txlen) { - return mMode; + mTxLength = txlen; +} + +int NetworkInterfaceConfig::getMTU() const +{ + return mMtu; +} + +const std::string& NetworkInterfaceConfig::getMACAddress() const +{ + return mMacAddress; +} + +int NetworkInterfaceConfig::getTxLength() const +{ + return mTxLength; } const std::vector& NetworkInterfaceConfig::getAddrList() const @@ -60,7 +88,7 @@ void NetworkInterfaceConfig::addInetAddr(const InetAddr& addr) { std::vector::iterator exists = std::find(mIpAddrList.begin(), mIpAddrList.end(), addr); if (exists != mIpAddrList.end()) { - std::string msg("Address already assigned"); + const std::string msg("Address already assigned"); throw NetworkException(msg); } mIpAddrList.push_back(addr); @@ -69,6 +97,7 @@ void NetworkInterfaceConfig::addInetAddr(const InetAddr& addr) void NetworkConfig::addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, + const std::vector& addrs, MacVLanMode mode) { auto it = std::find_if(mInterfaces.begin(), mInterfaces.end(), @@ -76,12 +105,13 @@ void NetworkConfig::addInterfaceConfig(const std::string& hostif, return entry.getZoneIf() == zoneif; } ); + if (it != mInterfaces.end()) { - std::string msg = "Interface already exists"; + const std::string msg = "Interface already exists"; LOGE(msg); throw NetworkException(msg); } - mInterfaces.push_back(NetworkInterfaceConfig(hostif,zoneif,type,mode)); + mInterfaces.push_back(NetworkInterfaceConfig(hostif, zoneif, type, addrs, mode)); } void NetworkConfig::addInetConfig(const std::string& ifname, const InetAddr& addr) @@ -93,7 +123,7 @@ void NetworkConfig::addInetConfig(const std::string& ifname, const InetAddr& add ); if (it == mInterfaces.end()) { - std::string msg = "No such interface"; + const std::string msg = "No such interface"; LOGE(msg); throw NetworkException(msg); } diff --git a/libs/lxcpp/network-config.hpp b/libs/lxcpp/network-config.hpp index c94f45b..aa1ee18 100644 --- a/libs/lxcpp/network-config.hpp +++ b/libs/lxcpp/network-config.hpp @@ -26,6 +26,9 @@ #include "config/config.hpp" #include "config/fields.hpp" +//#include "config/fields-union.hpp" +#include "lxcpp/network.hpp" +#include "lxcpp/exception.hpp" #include #include @@ -37,78 +40,25 @@ namespace lxcpp { /** - * Created interface type - */ -enum class InterfaceType { - VETH, - BRIDGE, - MACVLAN, - MOVE -}; - -/** - * Suported MacVLan modes - */ -enum class MacVLanMode { - PRIVATE, - VEPA, - BRIDGE, - PASSTHRU -}; - -/** - * Suported address types - */ -enum class InetAddrType { - IPV4, - IPV6 -}; - -enum class NetStatus { - DOWN, - UP -}; - - -/** - * Unified ip address - */ -struct InetAddr { - InetAddrType type; - uint32_t flags; - int prefix; - union { - struct in_addr ipv4; - struct in6_addr ipv6; - } addr; -}; - -static inline bool operator==(const InetAddr& a, const InetAddr& b) { - if (a.type == b.type && a.prefix == b.prefix) { - if (a.type == InetAddrType::IPV4) { - return ::memcmp(&a.addr.ipv4, &b.addr.ipv4, sizeof(a.addr.ipv4)) == 0; - } - if (a.type == InetAddrType::IPV6) { - return ::memcmp(&a.addr.ipv6, &b.addr.ipv6, sizeof(a.addr.ipv6)) == 0; - } - } - return false; -} - - -/** * Network interface configuration */ class NetworkInterfaceConfig { public: + NetworkInterfaceConfig() = default; // default constructor required by visitor + NetworkInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, - MacVLanMode mode = MacVLanMode::PRIVATE) : + const std::vector& addrs, + MacVLanMode mode) : mHostIf(hostif), mZoneIf(zoneif), - mType(type), - mMode(mode) + mType(static_cast(type)), + mMode(static_cast(mode)), + mMtu(0), + mMacAddress(), + mTxLength(0), + mIpAddrList(addrs) { } @@ -116,48 +66,79 @@ public: const std::string& getZoneIf() const; - const InterfaceType& getType() const; + InterfaceType getType() const; - const MacVLanMode& getMode() const; + MacVLanMode getMode() const; + + void setMTU(int mtu); + int getMTU() const; + + void setMACAddress(const std::string& mac); + const std::string& getMACAddress() const; + + void setTxLength(int txlen); + int getTxLength() const; const std::vector& getAddrList() const; - void addInetAddr(const InetAddr&); + void addInetAddr(const InetAddr& addr); + + CONFIG_REGISTER + ( + mHostIf, + mZoneIf, + mType, + mMode, + mIpAddrList + ) private: - const std::string mHostIf; - const std::string mZoneIf; - const InterfaceType mType; - const MacVLanMode mMode; - //TODO mtu, macaddress, txqueue + std::string mHostIf; + std::string mZoneIf; + int mType; + int mMode; + /* - * above are interface parameters which can be read/modified: + * interface parameters which can be set after interface is created * MTU (Maximum Transmit Unit) is maximum length of link level packet in TCP stream * MAC address is also called hardware card address * TXQueue is transmit queue length * - * I think most usufull would be possibility to set MAC address, other have their - * well working defaults but can be tuned to make faster networking (especially localy) + * I think most useful would be possibility to set MAC address, other have their + * well working defaults but can be tuned to make faster networking (especially locally) */ + int mMtu; + std::string mMacAddress; + int mTxLength; + std::vector mIpAddrList; }; /** * Network interface configuration */ -struct NetworkConfig { - - //for convinience +class NetworkConfig { +public: + /** + * adds interface configuration. + * throws NetworkException if zoneif name already on list + */ void addInterfaceConfig(const std::string& hostif, const std::string& zoneif, InterfaceType type, - MacVLanMode mode); + const std::vector& addrs = std::vector(), + MacVLanMode mode = MacVLanMode::PRIVATE); void addInetConfig(const std::string& ifname, const InetAddr& addr); - std::vector mInterfaces; + const std::vector& getInterfaces() const { return mInterfaces; } + const NetworkInterfaceConfig& getInterface(int i) const { return mInterfaces.at(i); } + + CONFIG_REGISTER( + mInterfaces + ) - //TODO tmporary to allow serialization of this object - CONFIG_REGISTER_EMPTY +private: + std::vector mInterfaces; }; } //namespace lxcpp diff --git a/libs/lxcpp/network.cpp b/libs/lxcpp/network.cpp index 965f20c..1214e5c 100644 --- a/libs/lxcpp/network.cpp +++ b/libs/lxcpp/network.cpp @@ -28,12 +28,18 @@ #include "netlink/netlink-message.hpp" #include "utils/make-clean.hpp" #include "utils/text.hpp" +#include "utils/exception.hpp" #include "logger/logger.hpp" #include -#include +#include #include +#include +#include +#include +#include +#include #define CHANGE_FLAGS_DEFAULT 0xffffffff /* from linux/if_addr.h: @@ -52,35 +58,12 @@ using namespace vasum::netlink; namespace lxcpp { namespace { -std::string toString(const in_addr& addr) -{ - char buf[INET_ADDRSTRLEN]; - const char* ret = ::inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); - if (ret == NULL) { - std::string msg = "Can't parse inet v4 addr"; - LOGE(msg); - throw NetworkException(msg); - } - return ret; -} - -std::string toString(const in6_addr& addr) -{ - char buf[INET6_ADDRSTRLEN]; - const char* ret = ::inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN); - if (ret == NULL) { - std::string msg = "Can't parse inet v6 addr"; - LOGE(msg); - throw NetworkException(msg); - } - return ret; -} uint32_t getInterfaceIndex(const std::string& name) { uint32_t index = ::if_nametoindex(name.c_str()); if (!index) { - std::string msg = "Can't find interface"; + const std::string msg = "Can't find interface " + utils::getSystemErrorMessage(); LOGE(msg); throw NetworkException(msg); } @@ -90,24 +73,35 @@ uint32_t getInterfaceIndex(const std::string& name) uint32_t getInterfaceIndex(const std::string& name, pid_t pid) { NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfomsg infoPeer = utils::make_clean(); - infoPeer.ifi_family = AF_UNSPEC; - infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; - nlm.put(infoPeer) + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) .put(IFLA_IFNAME, name); NetlinkResponse response = send(nlm, pid); if (!response.hasMessage()) { - std::string msg = "Can't get interface index"; + const std::string msg = "Can't get interface index"; LOGE(msg); throw NetworkException(msg); } - response.fetch(infoPeer); - return infoPeer.ifi_index; + response.fetch(info); + return info.ifi_index; +} + +void bridgeModify(const std::string& ifname, uint32_t masterIndex) { + NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + info.ifi_index = getInterfaceIndex(ifname); + nlm.put(info) + .put(IFLA_MASTER, masterIndex); + send(nlm); } -void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t pid) +void getAddressList(std::vector& addrs, int family, const std::string& ifname, pid_t pid) { uint32_t index = getInterfaceIndex(ifname, pid); NetlinkMessage nlm(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); @@ -116,22 +110,17 @@ void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t nlm.put(infoAddr); NetlinkResponse response = send(nlm, pid); - if (!response.hasMessage()) { - return ; - } - - Attr attr; while (response.hasMessage()) { ifaddrmsg addrmsg; response.fetch(addrmsg); if (addrmsg.ifa_index == index) { InetAddr a; if (addrmsg.ifa_family == AF_INET6) { - a.type = InetAddrType::IPV6; + a.setType(InetAddrType::IPV6); } else if (addrmsg.ifa_family == AF_INET) { - a.type = InetAddrType::IPV4; + a.setType(InetAddrType::IPV4); } else { - std::string msg = "Unsupported inet family"; + const std::string msg = "Unsupported inet family"; LOGE(msg); throw NetworkException(msg); } @@ -153,14 +142,14 @@ void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t // fall thru case IFA_LOCAL: //2 if (addrmsg.ifa_family == AF_INET6) { - response.fetch(attrType, a.addr.ipv6); + response.fetch(attrType, a.getAddr()); } else if (addrmsg.ifa_family == AF_INET) { - response.fetch(attrType, a.addr.ipv4); + response.fetch(attrType, a.getAddr()); } else { LOGW("unsupported family " << addrmsg.ifa_family); response.skipAttribute(); } - hasLocal=true; + hasLocal = true; break; case IFA_FLAGS: //8 extended flags - overwrites addrmsg.ifa_flags @@ -180,8 +169,7 @@ void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t break; } } - NetworkInterface::convertInetAddr2Attr(a, attr); - attrs.push_back(attr); + addrs.push_back(a); } response.fetchNextMessage(); } @@ -189,146 +177,256 @@ void getAddressAttrs(Attrs& attrs, int family, const std::string& ifname, pid_t } // anonymous namespace -void NetworkInterface::create(const std::string& hostif, - InterfaceType type, +std::string toString(const in_addr& addr) +{ + char buf[INET_ADDRSTRLEN]; + const char* ret = ::inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN); + if (ret == NULL) { + const std::string msg = "Wrong inet v4 addr " + utils::getSystemErrorMessage(); + LOGE(msg); + throw NetworkException(msg); + } + return ret; +} + +std::string toString(const in6_addr& addr) +{ + char buf[INET6_ADDRSTRLEN]; + const char* ret = ::inet_ntop(AF_INET6, &addr, buf, INET6_ADDRSTRLEN); + if (ret == NULL) { + const std::string msg = "Wrong inet v6 addr " + utils::getSystemErrorMessage(); + LOGE(msg); + throw NetworkException(msg); + } + return ret; +} + +void fromString(const std::string& s, in_addr& addr) +{ + if (s.empty()) { + ::memset(&addr, 0, sizeof(addr)); + } + else if (::inet_pton(AF_INET, s.c_str(), &addr) != 1) { + const std::string msg = "Can't parse inet v4 addr " + utils::getSystemErrorMessage(); + LOGE(msg); + throw NetworkException(msg); + } +} + +void fromString(const std::string& s, in6_addr& addr) +{ + if (s == ":") { + ::memset(&addr, 0, sizeof(addr)); + } + else if (::inet_pton(AF_INET6, s.c_str(), &addr) != 1) { + const std::string msg = "Can't parse inet v6 addr " + utils::getSystemErrorMessage(); + LOGE(msg); + throw NetworkException(msg); + } +} + +std::string toString(const InetAddr& a) { + std::string opts = "/" + std::to_string(a.prefix); + if (a.getType() == InetAddrType::IPV6) { + return toString(a.getAddr()) + opts; + } + if (a.getType() == InetAddrType::IPV4) { + return toString(a.getAddr()) + opts; + } + return ""; +} + +InetAddr::InetAddr(uint32_t f, int p, const std::string& a) +{ + if (a.find(":") != std::string::npos) { + setType(InetAddrType::IPV6); + fromString(a, getAddr()); + } else { + setType(InetAddrType::IPV4); + fromString(a, getAddr()); + } + flags = f; + prefix = p; +} + +void NetworkInterface::create(InterfaceType type, + const std::string& peerif, MacVLanMode mode) { switch (type) { case InterfaceType::VETH: - createVeth(hostif); + createVeth(peerif); break; case InterfaceType::BRIDGE: - createBridge(hostif); + createBridge(); break; case InterfaceType::MACVLAN: - createMacVLan(hostif, mode); - break; - case InterfaceType::MOVE: - move(hostif); + createMacVLan(peerif, mode); break; default: throw NetworkException("Unsuported interface type"); } } -void NetworkInterface::createVeth(const std::string& /*hostif*/) +void NetworkInterface::createVeth(const std::string& peerif) { - throw NotImplementedException(); + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) + .put(IFLA_IFNAME, mIfname) + .beginNested(IFLA_LINKINFO) + .put(IFLA_INFO_KIND, "veth") + .beginNested(IFLA_INFO_DATA) + .beginNested(VETH_INFO_PEER) + .put(info) + .put(IFLA_IFNAME, peerif) + .endNested() + .endNested() + .endNested(); + send(nlm); } -void NetworkInterface::createBridge(const std::string& /*hostif*/) +void NetworkInterface::createBridge() { - throw NotImplementedException(); + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) + .beginNested(IFLA_LINKINFO) + .put(IFLA_INFO_KIND, "bridge") + .beginNested(IFLA_INFO_DATA) + .beginNested(IFLA_AF_SPEC) + .put(IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_MASTER) + .endNested() + .endNested() + .endNested() + .put(IFLA_IFNAME, mIfname); //bridge name (will be created) + send(nlm); } -void NetworkInterface::createMacVLan(const std::string& /*hostif*/, MacVLanMode /*mode*/) +void NetworkInterface::createMacVLan(const std::string& maserif, MacVLanMode mode) { - throw NotImplementedException(); + uint32_t index = getInterfaceIndex(maserif); + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) + .beginNested(IFLA_LINKINFO) + .put(IFLA_INFO_KIND, "macvlan") + .beginNested(IFLA_INFO_DATA) + .put(IFLA_MACVLAN_MODE, static_cast(mode)) + .endNested() + .endNested() + .put(IFLA_LINK, index) //master index + .put(IFLA_IFNAME, mIfname); //slave name (will be created) + send(nlm); } -void NetworkInterface::move(const std::string& hostif) +void NetworkInterface::moveToContainer(pid_t pid) { - uint32_t index = getInterfaceIndex(hostif); - NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfomsg infopeer = utils::make_clean(); - infopeer.ifi_family = AF_UNSPEC; - infopeer.ifi_index = index; - nlm.put(infopeer) - .put(IFLA_NET_NS_PID, mContainerPid); - send(nlm); - - //rename to mIfname inside container - if (mIfname != hostif) { - renameFrom(hostif); - } + NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_index = getInterfaceIndex(mIfname); + nlm.put(info) + .put(IFLA_NET_NS_PID, pid); + send(nlm); + mContainerPid = pid; } void NetworkInterface::destroy() { - throw NotImplementedException(); + //uint32_t index = getInterfaceIndex(mIfname); + NetlinkMessage nlm(RTM_DELLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + info.ifi_index = getInterfaceIndex(mIfname, mContainerPid); + nlm.put(info) + .put(IFLA_IFNAME, mIfname); + send(nlm); } -NetStatus NetworkInterface::status() +NetStatus NetworkInterface::status() const { - throw NotImplementedException(); - /* - //TODO get container status, if stopped return CONFIGURED - if (mContainerPid<=0) return CONFIGURED; - // read netlink - return DOWN;*/ -} - + NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) + .put(IFLA_IFNAME, mIfname); -void NetworkInterface::up() -{ - throw NotImplementedException(); -} + NetlinkResponse response = send(nlm, mContainerPid); + if (!response.hasMessage()) { + throw NetworkException("Can't get interface information"); + } -void NetworkInterface::down() -{ - throw NotImplementedException(); + response.fetch(info); + return (info.ifi_flags & IFF_UP) != 0 ? NetStatus::UP : NetStatus::DOWN; } void NetworkInterface::renameFrom(const std::string& oldif) { NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfomsg infoPeer = utils::make_clean(); - infoPeer.ifi_family = AF_UNSPEC; - infoPeer.ifi_index = getInterfaceIndex(oldif, mContainerPid); - infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_index = getInterfaceIndex(oldif, mContainerPid); + info.ifi_change = CHANGE_FLAGS_DEFAULT; - nlm.put(infoPeer) + nlm.put(info) .put(IFLA_IFNAME, mIfname); send(nlm, mContainerPid); } -void NetworkInterface::addInetAddr(const InetAddr& addr) +void NetworkInterface::addToBridge(const std::string& bridge) { - NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK); - ifaddrmsg infoAddr = utils::make_clean(); - infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); - infoAddr.ifa_family = addr.type == InetAddrType::IPV4 ? AF_INET : AF_INET6; - infoAddr.ifa_prefixlen = addr.prefix; - infoAddr.ifa_flags = addr.flags; - nlm.put(infoAddr); - - if (addr.type == InetAddrType::IPV6) { - nlm.put(IFA_ADDRESS, addr.addr.ipv6); - nlm.put(IFA_LOCAL, addr.addr.ipv6); - } else if (addr.type == InetAddrType::IPV4) { - nlm.put(IFA_ADDRESS, addr.addr.ipv4); - nlm.put(IFA_LOCAL, addr.addr.ipv4); - } + bridgeModify(mIfname, getInterfaceIndex(bridge)); +} - send(nlm, mContainerPid); +void NetworkInterface::delFromBridge() +{ + bridgeModify(mIfname, 0); } + void NetworkInterface::setAttrs(const Attrs& attrs) { + if (attrs.empty()) { + return ; + } + //TODO check this: NetlinkMessage nlm(RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK); NetlinkMessage nlm(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK); - unsigned mtu=0, link=0, txq=0; - ifinfomsg infoPeer = utils::make_clean(); - infoPeer.ifi_family = AF_UNSPEC; - infoPeer.ifi_index = getInterfaceIndex(mIfname, mContainerPid); - infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; + ifinfomsg info = utils::make_clean(); + info.ifi_index = getInterfaceIndex(mIfname, mContainerPid); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + std::string mac; + unsigned mtu = 0, link = 0, txq = 0; for (const auto& attr : attrs) { if (attr.name == AttrName::FLAGS) { - infoPeer.ifi_flags = stoul(attr.value); + info.ifi_flags = stoul(attr.value); } else if (attr.name == AttrName::CHANGE) { - infoPeer.ifi_change = stoul(attr.value); + info.ifi_change = stoul(attr.value); } else if (attr.name == AttrName::TYPE) { - infoPeer.ifi_type = stoul(attr.value); + info.ifi_type = stoul(attr.value); } else if (attr.name == AttrName::MTU) { mtu = stoul(attr.value); } else if (attr.name == AttrName::LINK) { link = stoul(attr.value); } else if (attr.name == AttrName::TXQLEN) { txq = stoul(attr.value); + } else if (attr.name == AttrName::MAC) { + mac = attr.value; } + } - nlm.put(infoPeer); + nlm.put(info); if (mtu) { nlm.put(IFLA_MTU, mtu); } @@ -338,28 +436,23 @@ void NetworkInterface::setAttrs(const Attrs& attrs) if (txq) { nlm.put(IFLA_TXQLEN, txq); } + if (!mac.empty()) { + nlm.put(IFLA_ADDRESS, mac); + } NetlinkResponse response = send(nlm, mContainerPid); if (!response.hasMessage()) { throw NetworkException("Can't set interface information"); } - - // configure inet addresses - InetAddr addr; - for (const Attr& a : attrs) { - if (convertAttr2InetAddr(a, addr)) { - addInetAddr(addr); - } - } } Attrs NetworkInterface::getAttrs() const { NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_ACK); - ifinfomsg infoPeer = utils::make_clean(); - infoPeer.ifi_family = AF_UNSPEC; - infoPeer.ifi_change = CHANGE_FLAGS_DEFAULT; - nlm.put(infoPeer) + ifinfomsg info = utils::make_clean(); + info.ifi_family = AF_UNSPEC; + info.ifi_change = CHANGE_FLAGS_DEFAULT; + nlm.put(info) .put(IFLA_IFNAME, mIfname); NetlinkResponse response = send(nlm, mContainerPid); @@ -368,9 +461,9 @@ Attrs NetworkInterface::getAttrs() const } Attrs attrs; - response.fetch(infoPeer); - attrs.push_back(Attr{AttrName::FLAGS, std::to_string(infoPeer.ifi_flags)}); - attrs.push_back(Attr{AttrName::TYPE, std::to_string(infoPeer.ifi_type)}); + response.fetch(info); + attrs.push_back(Attr{AttrName::FLAGS, std::to_string(info.ifi_flags)}); + attrs.push_back(Attr{AttrName::TYPE, std::to_string(info.ifi_type)}); while (response.hasAttribute()) { /* @@ -412,15 +505,100 @@ Attrs NetworkInterface::getAttrs() const break; } } - getAddressAttrs(attrs, AF_INET, mIfname, mContainerPid); - getAddressAttrs(attrs, AF_INET6, mIfname, mContainerPid); return attrs; } +void NetworkInterface::addInetAddr(const InetAddr& addr) +{ + NetlinkMessage nlm(RTM_NEWADDR, NLM_F_CREATE | NLM_F_REQUEST | NLM_F_ACK); + ifaddrmsg infoAddr = utils::make_clean(); + infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); + infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6; + infoAddr.ifa_prefixlen = addr.prefix; + infoAddr.ifa_flags = addr.flags; + nlm.put(infoAddr); + + if (addr.getType() == InetAddrType::IPV6) { + nlm.put(IFA_ADDRESS, addr.getAddr()); + nlm.put(IFA_LOCAL, addr.getAddr()); + } else if (addr.getType() == InetAddrType::IPV4) { + nlm.put(IFA_ADDRESS, addr.getAddr()); + nlm.put(IFA_LOCAL, addr.getAddr()); + } + + send(nlm, mContainerPid); +} + +void NetworkInterface::delInetAddr(const InetAddr& addr) +{ + NetlinkMessage nlm(RTM_DELADDR, NLM_F_REQUEST | NLM_F_ACK); + ifaddrmsg infoAddr = utils::make_clean(); + infoAddr.ifa_index = getInterfaceIndex(mIfname, mContainerPid); + infoAddr.ifa_family = addr.getType() == InetAddrType::IPV4 ? AF_INET : AF_INET6; + infoAddr.ifa_prefixlen = addr.prefix; + infoAddr.ifa_flags = addr.flags; + nlm.put(infoAddr); + + if (addr.getType() == InetAddrType::IPV6) { + nlm.put(IFA_ADDRESS, addr.getAddr()); + nlm.put(IFA_LOCAL, addr.getAddr()); + } else if (addr.getType() == InetAddrType::IPV4) { + nlm.put(IFA_ADDRESS, addr.getAddr()); + nlm.put(IFA_LOCAL, addr.getAddr()); + } + + send(nlm, mContainerPid); +} + +std::vector NetworkInterface::getInetAddressList() const +{ + std::vector addrs; + getAddressList(addrs, AF_UNSPEC, mIfname, mContainerPid); + return addrs; +} + +void NetworkInterface::up() +{ + Attrs attrs; + attrs.push_back(Attr{AttrName::CHANGE, std::to_string(IFF_UP)}); + attrs.push_back(Attr{AttrName::FLAGS, std::to_string(IFF_UP)}); + setAttrs(attrs); +} + +void NetworkInterface::down() +{ + Attrs attrs; + attrs.push_back(Attr{AttrName::CHANGE, std::to_string(IFF_UP)}); + attrs.push_back(Attr{AttrName::FLAGS, std::to_string(0)}); + setAttrs(attrs); +} + +void NetworkInterface::setMACAddress(const std::string& macaddr) +{ + Attrs attrs; + attrs.push_back(Attr{AttrName::MAC, macaddr}); + setAttrs(attrs); +} + +void NetworkInterface::setMTU(int mtu) +{ + Attrs attrs; + attrs.push_back(Attr{AttrName::MTU, std::to_string(mtu)}); + setAttrs(attrs); +} + +void NetworkInterface::setTxLength(int txqlen) +{ + Attrs attrs; + attrs.push_back(Attr{AttrName::TXQLEN, std::to_string(txqlen)}); + setAttrs(attrs); +} + + std::vector NetworkInterface::getInterfaces(pid_t initpid) { // get interfaces seen by netlink - NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST|NLM_F_DUMP|NLM_F_ROOT); + NetlinkMessage nlm(RTM_GETLINK, NLM_F_REQUEST | NLM_F_DUMP | NLM_F_ROOT); ifinfomsg info = utils::make_clean(); info.ifi_family = AF_PACKET; nlm.put(info); @@ -439,78 +617,4 @@ std::vector NetworkInterface::getInterfaces(pid_t initpid) return iflist; } -void NetworkInterface::convertInetAddr2Attr(const InetAddr& a, Attr& attr) -{ - std::string value; - - if (a.type == InetAddrType::IPV6) { - value += "ip=" + toString(a.addr.ipv6); - } else if (a.type == InetAddrType::IPV4) { - value += "ip=" + toString(a.addr.ipv4); - } else { - throw NetworkException(); - } - - value += ",pfx=" + std::to_string(a.prefix); - value += ",flags=" + std::to_string(a.flags); - - if (a.type == InetAddrType::IPV6) { - attr = Attr{AttrName::IPV6, value}; - } else { - attr = Attr{AttrName::IPV4, value}; - } -} - -bool NetworkInterface::convertAttr2InetAddr(const Attr& attr, InetAddr& addr) -{ - std::string::size_type s = 0U; - std::string::size_type e = s; - - if (attr.name != AttrName::IPV6 && attr.name != AttrName::IPV4) { - return false; //not inet attribute - } - - addr.prefix = 0; - addr.flags = 0; - - bool addrFound = false; - while (e != std::string::npos) { - e = attr.value.find(',', s); - std::string ss = attr.value.substr(s, e - s); - s = e + 1; - - std::string name; - std::string value; - std::string::size_type p = ss.find('='); - if (p != std::string::npos) { - name = ss.substr(0, p); - value = ss.substr(p + 1); - } else { - name = ss; - //value remains empty - } - - if (name == "ip") { - if (attr.name == AttrName::IPV6) { - addr.type = InetAddrType::IPV6; - if (inet_pton(AF_INET6, value.c_str(), &addr.addr.ipv6) != 1) { - throw NetworkException("Parse IPV6 address"); - } - } else if (attr.name == AttrName::IPV4) { - addr.type = InetAddrType::IPV4; - if (inet_pton(AF_INET, value.c_str(), &addr.addr.ipv4) != 1) { - throw NetworkException("Parse IPV4 address"); - } - } - addrFound = true; - } else if (name == "pfx") { - addr.prefix = stoul(value); - } else if (name == "flags") { - addr.flags = stoul(value); - } - } - - return addrFound; -} - } // namespace lxcpp diff --git a/libs/lxcpp/network.hpp b/libs/lxcpp/network.hpp index cc0167e..a1ebf49 100644 --- a/libs/lxcpp/network.hpp +++ b/libs/lxcpp/network.hpp @@ -24,14 +24,125 @@ #ifndef LXCPP_NETWORK_HPP #define LXCPP_NETWORK_HPP -#include "lxcpp/network-config.hpp" +#include "config/fields.hpp" +#include #include #include #include +#include + namespace lxcpp { +std::string toString(const in_addr& addr); +std::string toString(const in6_addr& addr); +void fromString(const std::string& s, in_addr& addr); +void fromString(const std::string& s, in6_addr& addr); + +/** + * Suported address types + */ +enum class InetAddrType { + IPV4, + IPV6 +}; + +/** + * Unified ip address + */ +class InetAddr { +public: + InetAddr() {} + InetAddr(uint32_t flags, int prefix, const std::string& addr); + + InetAddrType getType() const { + return static_cast(type); + } + void setType(InetAddrType t) { + type = static_cast(t); + } + + template + T& getAddr() { + //FIXME return union field after fix of addr type + char *v = addr; + return *(reinterpret_cast(v)); + } + template + const T& getAddr() const { + //FIXME return union field after fix of addr type + const char *v = addr; + return *(reinterpret_cast(v)); + } + + uint32_t flags; + int prefix; + + CONFIG_REGISTER + ( + type, + flags, + prefix + //FIXME add when visitor can serialize char[SIZE] + //addr + ) + +private: + //FIXME change to union when visitor can serialize type by istream ostream operators + char addr[sizeof(in6_addr)]; + //FIXME: change to enum when visitor can serialize type by istream ostream operators + int type; +}; + +static inline bool operator==(const in_addr& a, const in_addr& b) +{ + return ::memcmp(&a, &b, sizeof(a)) == 0; +} + +static inline bool operator==(const in6_addr& a, const in6_addr& b) +{ + return ::memcmp(&a, &b, sizeof(a)) == 0; +} + +static inline bool operator==(const InetAddr& a, const InetAddr& b) +{ + if (a.getType() == b.getType() && a.prefix == b.prefix) { + if (a.getType() == InetAddrType::IPV6) + return a.getAddr() == b.getAddr(); + else + return a.getAddr() == b.getAddr(); + } + return false; +} + +std::string toString(const InetAddr& a); + +enum class RoutingTable { + UNSPEC, // also means 'any' + COMPAT, + DEFAULT, + MAIN, + LOCAL, + USER, +}; +inline std::string toString(RoutingTable rt) { + switch (rt) { + case RoutingTable::UNSPEC: + return "unspec"; + case RoutingTable::COMPAT: + return "compat"; + case RoutingTable::DEFAULT: + return "default"; + case RoutingTable::MAIN: + return "main"; + case RoutingTable::LOCAL: + return "local"; + default: + return "user"; + } +} + enum class AttrName { MAC, FLAGS, @@ -40,8 +151,6 @@ enum class AttrName { MTU, LINK, TXQLEN, - IPV4, - IPV6, }; inline std::ostream& operator<<(std::ostream& os, const AttrName& a) { @@ -53,8 +162,6 @@ inline std::ostream& operator<<(std::ostream& os, const AttrName& a) { case AttrName::MTU: os << "mtu"; break; case AttrName::LINK: os << "link"; break; case AttrName::TXQLEN: os << "txq"; break; - case AttrName::IPV4: os << "ipv4"; break; - case AttrName::IPV6: os << "ipv6"; break; } return os; } @@ -67,62 +174,165 @@ struct Attr { typedef std::vector Attrs; /** + * Created interface type + */ +enum class InterfaceType : int { + VETH, + BRIDGE, + MACVLAN +}; + +/** + * Suported MacVLan modes + */ +enum class MacVLanMode { + PRIVATE, + VEPA, + BRIDGE, + PASSTHRU +}; + +enum class NetStatus { + DOWN, + UP +}; + +std::string toString(const InetAddr& a); + +/** * Network operations to be performed on given container and interface * operates on netlink device */ class NetworkInterface { public: /** - * Create network interface object for the ifname in the container + * Create network interface object for the ifname in the container (network namespace) + * Note: pid=0 is kernel */ - NetworkInterface(pid_t pid, const std::string& ifname) : - mContainerPid(pid), - mIfname(ifname) + NetworkInterface(const std::string& ifname, pid_t pid = 0) : + mIfname(ifname), + mContainerPid(pid) { } const std::string& getName() const { return mIfname; } - //Network actions on Container - void create(const std::string& hostif, InterfaceType type, MacVLanMode mode=MacVLanMode::PRIVATE); + /** + * Retrieve network interface status (UP or DOWN) + */ + NetStatus status() const; + + /** + * Create network interface in container identified by mContainerPid + * Equivalent to: ip link add @mIfname type @type [...] + * Create pair of virtual ethernet interfaces + * ip link add @mIfname type veth peer name @peerif + * Create bridge interface + * ip link add @mIfname type bridge + * Create psedo-ethernet interface on existing one + * ip link add @mIfname type macvlan link @peerif [mode @mode] + */ + void create(InterfaceType type, const std::string& peerif, MacVLanMode mode = MacVLanMode::PRIVATE); + + /** + * Delete interface + * Equivalent to: ip link delete @mIfname + */ void destroy(); - NetStatus status(); - void up(); - void down(); + /** + * Move interface to container + * Equivalent to: ip link set dev @hostif netns @mContainerPid + */ + void moveToContainer(pid_t p); /** * Rename interface name - * Equivalent to: ip link set dev $oldif name $this.mIfname + * Equivalent to: ip link set dev @oldif name @mIfname */ void renameFrom(const std::string& oldif); + /** + * Add interface to the bridge + * Equivalent to: ip link set @mIfname master @bridge + */ + void addToBridge(const std::string& bridge); + + /** + * Remove insterface from the bridge + * Equivalent to: ip link set @mIfname nomaster + */ + void delFromBridge(); + + /** + * Set or get interface attributes in one netlink call. + * Supported attributes: see @AttrNames + */ void setAttrs(const Attrs& attrs); Attrs getAttrs() const; - void setMACAddress(const std::string& macaddr); - void setMTU(int mtu); - void setTxLength(int txlen); + /** + * Add inet address to the interface + * Equivalent to: ip addr add @addr dev @mIfname + */ void addInetAddr(const InetAddr& addr); + + /** + * Remove inet address from the interface + * Equivalent to: ip addr del @addr dev @mIfname + */ void delInetAddr(const InetAddr& addr); - static std::vector getInterfaces(pid_t initpid); + /** + * Retrieve all inet addresses for the interface + * Equivalent to: ip addr show, ip -6 addr show + */ + std::vector getInetAddressList() const; - static void convertInetAddr2Attr(const InetAddr& addr, Attr& attr); - static bool convertAttr2InetAddr(const Attr& attr, InetAddr& addr); + /** + * Set interface up + * Equivalent to: ip link set @mInface up + */ + void up(); -private: - void createVeth(const std::string& hostif); - void createBridge(const std::string& hostif); - void createMacVLan(const std::string& hostif, MacVLanMode mode); /** - * Move interface to container - * Equivalent to: ip link set dev $hostif netns $mContainerPid + * Set interface down + * Equivalent to: ip link set @mInface down */ - void move(const std::string& hostif); + void down(); + + /** + * Set MAC address attribute + * Equivalent to: ip link set @mIface address @macaddr + * @macaddr in format AA:BB:CC:DD:FF:GG + */ + void setMACAddress(const std::string& macaddr); + + /** + * Set MTU attribute + * Equivalent to: ip link set @mIface mtu @mtu + */ + void setMTU(int mtu); + + /** + * Set TxQ attribute + * Equivalent to: ip link set @mIface txqueue @txlen + */ + void setTxLength(int txlen); + + /** + * Get list of network interafece names + * Equivalent to: ip link show + */ + static std::vector getInterfaces(pid_t initpid); + +private: + void createVeth(const std::string& peerif); + void createBridge(); + void createMacVLan(const std::string& masterif, MacVLanMode mode); - pid_t mContainerPid; ///< Container pid to operate on (0=kernel) const std::string mIfname; ///< network interface name inside zone + pid_t mContainerPid; ///< Container pid to operate on (0 means kernel) }; } // namespace lxcpp diff --git a/tests/unit_tests/lxcpp/ut-network.cpp b/tests/unit_tests/lxcpp/ut-network.cpp index 373ec1b..f797a6c 100644 --- a/tests/unit_tests/lxcpp/ut-network.cpp +++ b/tests/unit_tests/lxcpp/ut-network.cpp @@ -23,17 +23,32 @@ #include "config.hpp" #include "config/manager.hpp" -#include "lxcpp/network.hpp" +#include "lxcpp/network-config.hpp" #include "ut.hpp" #include #include +#include + namespace { +using namespace lxcpp; +using namespace config; + struct Fixture { Fixture() {} ~Fixture() {} + + static std::string getUniqueName(const std::string& prefix) { + std::vector iflist = NetworkInterface::getInterfaces(0); + std::string name; + unsigned i = 0; + do { + name = prefix + std::to_string(i++); + } while (std::find(iflist.begin(), iflist.end(), name) != iflist.end()); + return name; + } }; } // namespace @@ -46,38 +61,112 @@ struct Fixture { BOOST_FIXTURE_TEST_SUITE(LxcppNetworkSuite, Fixture) -using namespace lxcpp; - -BOOST_AUTO_TEST_CASE(ListInterfaces) +BOOST_AUTO_TEST_CASE(NetworkListInterfaces) { std::vector iflist; - BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0);) - for (const auto& i : iflist) { - std::cout << i << std::endl; - } -} + BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0)); -BOOST_AUTO_TEST_CASE(DetailedListInterfaces) -{ - std::vector iflist; - BOOST_CHECK_NO_THROW(iflist = NetworkInterface::getInterfaces(0);) for (const auto& i : iflist) { - NetworkInterface ni(1,i); - std::cout << "Attrs of " << i << std::endl; + NetworkInterface ni(i); + std::cout << i << ": "; Attrs attrs; - BOOST_CHECK_NO_THROW(attrs = ni.getAttrs();) + std::vector addrs; + BOOST_CHECK_NO_THROW(attrs = ni.getAttrs()); for (const Attr& a : attrs) { - std::cout << a.name << "=" << a.value << "; "; + std::cout << a.name << "=" << a.value; + if (a.name == AttrName::FLAGS) { + uint32_t f = stoul(a.value); + std::cout << "("; + std::cout << ((f & IFF_UP) !=0 ? "UP" : "DONW"); + std::cout << ")"; + } + std::cout << "; "; } std::cout << std::endl; + + BOOST_CHECK_NO_THROW(addrs = ni.getInetAddressList()); + for (const InetAddr& a : addrs) { + std::cout << " " << toString(a) << std::endl; + } } } + BOOST_AUTO_TEST_CASE(NetworkConfigSerialization) { + std::string tmpConfigFile = "/tmp/netconfig.conf"; NetworkConfig cfg; - std::string json; - BOOST_CHECK_NO_THROW(json = config::saveToJsonString(cfg);) - std::cout << json << std::endl; + BOOST_CHECK_NO_THROW(config::saveToJsonString(cfg)); + + cfg.addInterfaceConfig("host-veth0", "zone-eth0", InterfaceType::VETH); + cfg.addInterfaceConfig("host-veth1", "zone-eth1", InterfaceType::BRIDGE); + cfg.addInterfaceConfig("host-veth2", "zone-eth2", InterfaceType::MACVLAN); + + InetAddr addr(0, 24, "1.2.3.4"); + cfg.addInetConfig("zone-eth0", addr); + + config::saveToJsonFile(tmpConfigFile, cfg); + + NetworkConfig cfg2; + config::loadFromJsonFile(tmpConfigFile, cfg2); + + int ifnum = cfg.getInterfaces().size(); + for (int i = 0; i < ifnum; ++i) { + const NetworkInterfaceConfig& ni1 = cfg.getInterface(i); + const NetworkInterfaceConfig& ni2 = cfg2.getInterface(i); + + BOOST_CHECK_EQUAL(ni1.getHostIf(), ni2.getHostIf()); + BOOST_CHECK_EQUAL(ni1.getZoneIf(), ni2.getZoneIf()); + BOOST_CHECK(ni1.getType() == ni2.getType()); + BOOST_CHECK(ni1.getMode() == ni2.getMode()); + } +} +BOOST_AUTO_TEST_CASE(NetworkBridgeCreateDestroy) +{ + std::string name = getUniqueName("lolo"); + NetworkInterface ni(name); + InetAddr myip(0, 32, "10.100.1.1"); + + BOOST_CHECK_NO_THROW(ni.create(InterfaceType::BRIDGE, "")); + BOOST_CHECK_NO_THROW(ni.addInetAddr(myip);) + + std::vector iflist = NetworkInterface::getInterfaces(0); + BOOST_CHECK(std::find(iflist.begin(), iflist.end(), name) != iflist.end()); + + std::vector addrs = ni.getInetAddressList(); + BOOST_CHECK(std::find(addrs.begin(), addrs.end(), myip) != addrs.end()); + for (const auto& i : iflist) { + std::cout << " " << i; + } + std::cout << std::endl; + for (const InetAddr& a : addrs) { + std::cout << " " << toString(a) << std::endl; + } + + BOOST_CHECK_NO_THROW(ni.delInetAddr(myip)); + BOOST_CHECK_NO_THROW(ni.destroy()); +} + +BOOST_AUTO_TEST_CASE(NetworkMacVLanCreateDestroy) +{ + std::string masterif; + std::vector iflist = NetworkInterface::getInterfaces(0); + for (const auto& ifn : iflist) { + if (ifn == "lo") continue; + NetworkInterface n(ifn); + if (n.status() == NetStatus::UP) { + masterif = ifn; + break; + } + } + + NetworkInterface ni(getUniqueName("lolo")); + std::cout << " creating MACVLAN on " << masterif << std::endl; + BOOST_CHECK_NO_THROW(ni.create(InterfaceType::MACVLAN, masterif, MacVLanMode::VEPA)); + + iflist = NetworkInterface::getInterfaces(0); + BOOST_CHECK(std::find(iflist.begin(), iflist.end(), ni.getName()) != iflist.end()); + + BOOST_CHECK_NO_THROW(ni.destroy()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/unit_tests/server/ut-zone.cpp b/tests/unit_tests/server/ut-zone.cpp index aefb29a..c7740e9 100644 --- a/tests/unit_tests/server/ut-zone.cpp +++ b/tests/unit_tests/server/ut-zone.cpp @@ -42,8 +42,6 @@ #include #include #include -#include -#include #include using namespace utils; -- 2.7.4 From e85426229880fbc634cfde33f35f1bce2b1eb32c Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Mon, 28 Sep 2015 18:19:07 +0200 Subject: [PATCH 15/16] config: Cstring serialization. [Feature] Serialization of null-terminated strings [Cause] N/A [Solution] Memory for serialized data is allocated with new, user should delete[] the field in the destructor or when it's not used. [Verification] Build, install, run tests. Change-Id: Iff49e14ab95410bd7a80ee23abf04e9d92e65a55 --- libs/config/from-fdstore-visitor.hpp | 10 +++++++ libs/config/from-gvariant-visitor.hpp | 12 +++++++++ libs/config/from-json-visitor.hpp | 11 ++++++++ libs/config/from-kvjson-visitor.hpp | 10 +++++++ libs/config/kvstore.cpp | 37 ++++++++++++++++++++++++-- libs/config/kvstore.hpp | 2 ++ libs/config/to-fdstore-visitor.hpp | 8 ++++++ libs/config/to-gvariant-visitor.hpp | 1 + libs/config/to-json-visitor.hpp | 5 ++++ tests/unit_tests/config/testconfig-example.hpp | 4 +++ tests/unit_tests/config/ut-configuration.cpp | 1 + 11 files changed, 99 insertions(+), 2 deletions(-) diff --git a/libs/config/from-fdstore-visitor.hpp b/libs/config/from-fdstore-visitor.hpp index 3863f7f..a948b3b 100644 --- a/libs/config/from-fdstore-visitor.hpp +++ b/libs/config/from-fdstore-visitor.hpp @@ -61,6 +61,16 @@ private: mStore.read(&value.front(), size); } + void readInternal(char* &value) + { + size_t size; + readInternal(size); + + value = new char[size + 1]; + mStore.read(value, size); + value[size] = '\0'; + } + void readInternal(config::FileDescriptor& fd) { fd = mStore.receiveFD(); diff --git a/libs/config/from-gvariant-visitor.hpp b/libs/config/from-gvariant-visitor.hpp index e648625..a4134b6 100644 --- a/libs/config/from-gvariant-visitor.hpp +++ b/libs/config/from-gvariant-visitor.hpp @@ -127,6 +127,18 @@ private: value = g_variant_get_string(object, NULL); } + static void fromGVariant(GVariant* object, char* &value) + { + checkType(object, G_VARIANT_TYPE_STRING); + + const char* source = g_variant_get_string(object, NULL); + size_t len = std::strlen(source); + + value = new char[len + 1]; + std::strncpy(value, source, len); + value[len] = '\0'; + } + static void fromGVariant(GVariant* object, config::FileDescriptor& value) { checkType(object, G_VARIANT_TYPE_INT32); diff --git a/libs/config/from-json-visitor.hpp b/libs/config/from-json-visitor.hpp index 74d309b..bc7864a 100644 --- a/libs/config/from-json-visitor.hpp +++ b/libs/config/from-json-visitor.hpp @@ -30,6 +30,7 @@ #include #include +#include #include namespace config { @@ -137,6 +138,16 @@ private: value = json_object_get_string(object); } + static void fromJsonObject(json_object* object, char* &value) + { + checkType(object, json_type_string); + + int len = json_object_get_string_len(object); + value = new char[len + 1]; + std::strncpy(value, json_object_get_string(object), len); + value[len] = '\0'; + } + template static void fromJsonObject(json_object* object, std::vector& value) { diff --git a/libs/config/from-kvjson-visitor.hpp b/libs/config/from-kvjson-visitor.hpp index eea975c..cc6e14a 100644 --- a/libs/config/from-kvjson-visitor.hpp +++ b/libs/config/from-kvjson-visitor.hpp @@ -273,6 +273,16 @@ private: checkType(object, json_type_string); value = json_object_get_string(object); } + + static void fromJsonObject(json_object* object, char* &value) + { + checkType(object, json_type_string); + + int len = json_object_get_string_len(object); + value = new char[len + 1]; + std::strncpy(value, json_object_get_string(object), len); + value[len] = '\0'; + } }; } // namespace config diff --git a/libs/config/kvstore.cpp b/libs/config/kvstore.cpp index b6d922e..25febf6 100644 --- a/libs/config/kvstore.cpp +++ b/libs/config/kvstore.cpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace config { @@ -231,13 +232,13 @@ void KVStore::remove(const std::string& key) transaction.commit(); } -void KVStore::setInternal(const std::string& key, const std::string& value) +void KVStore::setInternal(const std::string& key, const char* value) { Transaction transaction(*this); ScopedReset scopedReset(mSetValueStmt); ::sqlite3_bind_text(mSetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC); - ::sqlite3_bind_text(mSetValueStmt->get(), 2, value.c_str(), AUTO_DETERM_SIZE, SQLITE_STATIC); + ::sqlite3_bind_text(mSetValueStmt->get(), 2, value, AUTO_DETERM_SIZE, SQLITE_STATIC); if (::sqlite3_step(mSetValueStmt->get()) != SQLITE_DONE) { @@ -246,6 +247,11 @@ void KVStore::setInternal(const std::string& key, const std::string& value) transaction.commit(); } +void KVStore::setInternal(const std::string& key, const std::string& value) +{ + setInternal(key, value.c_str()); +} + void KVStore::setInternal(const std::string& key, const std::initializer_list& values) { setInternal(key, std::vector(values)); @@ -294,6 +300,33 @@ std::string KVStore::getInternal(const std::string& key, std::string*) return value; } +char* KVStore::getInternal(const std::string& key, char**) +{ + Transaction transaction(*this); + ScopedReset scopedReset(mGetValueStmt); + + ::sqlite3_bind_text(mGetValueStmt->get(), 1, key.c_str(), AUTO_DETERM_SIZE, SQLITE_TRANSIENT); + + int ret = ::sqlite3_step(mGetValueStmt->get()); + if (ret == SQLITE_DONE) { + throw ConfigException("No value corresponding to the key: " + key + "@" + mPath); + } + if (ret != SQLITE_ROW) { + throw ConfigException("Error during stepping: " + mConn.getErrorMessage()); + } + + const char* source = reinterpret_cast(sqlite3_column_text(mGetValueStmt->get(), FIRST_COLUMN)); + + size_t length = std::strlen(source); + char* value = new char[length + 1]; + + std::strncpy(value, source, length); + value[length] = '\0'; + + transaction.commit(); + return value; +} + std::vector KVStore::getInternal(const std::string& key, std::vector*) { Transaction transaction(*this); diff --git a/libs/config/kvstore.hpp b/libs/config/kvstore.hpp index eaf568b..583d534 100644 --- a/libs/config/kvstore.hpp +++ b/libs/config/kvstore.hpp @@ -134,6 +134,7 @@ private: bool mIsTransactionCommited; void setInternal(const std::string& key, const std::string& value); + void setInternal(const std::string& key, const char* value); void setInternal(const std::string& key, const std::initializer_list& values); void setInternal(const std::string& key, const std::vector& values); template @@ -142,6 +143,7 @@ private: void setInternal(const std::string& key, const std::vector& values); std::string getInternal(const std::string& key, std::string*); + char* getInternal(const std::string& key, char**); std::vector getInternal(const std::string& key, std::vector*); template T getInternal(const std::string& key, T*); diff --git a/libs/config/to-fdstore-visitor.hpp b/libs/config/to-fdstore-visitor.hpp index 1eedddb..7da3d76 100644 --- a/libs/config/to-fdstore-visitor.hpp +++ b/libs/config/to-fdstore-visitor.hpp @@ -30,6 +30,7 @@ #include "config/types.hpp" #include +#include namespace config { @@ -60,6 +61,13 @@ private: mStore.write(value.c_str(), value.size()); } + void writeInternal(const char* &value) + { + size_t size = std::strlen(value); + writeInternal(size); + mStore.write(value, size); + } + void writeInternal(const config::FileDescriptor& fd) { mStore.sendFD(fd.value); diff --git a/libs/config/to-gvariant-visitor.hpp b/libs/config/to-gvariant-visitor.hpp index 047aa17..40c002d 100644 --- a/libs/config/to-gvariant-visitor.hpp +++ b/libs/config/to-gvariant-visitor.hpp @@ -83,6 +83,7 @@ private: void writeInternal(bool value) { add("b", value); }; void writeInternal(double value) { add("d", value); }; void writeInternal(const std::string& value) { add("s", value.c_str()); }; + void writeInternal(const char* value) { add("s", value); }; void writeInternal(const config::FileDescriptor& value) { add("h", value.value); }; template diff --git a/libs/config/to-json-visitor.hpp b/libs/config/to-json-visitor.hpp index 4ab4a4b..717b952 100644 --- a/libs/config/to-json-visitor.hpp +++ b/libs/config/to-json-visitor.hpp @@ -120,6 +120,11 @@ private: return json_object_new_string(value.c_str()); } + static json_object* toJsonObject(char* value) + { + return json_object_new_string(value); + } + template static json_object* toJsonObject(const std::vector& value) { diff --git a/tests/unit_tests/config/testconfig-example.hpp b/tests/unit_tests/config/testconfig-example.hpp index 549985b..949239d 100644 --- a/tests/unit_tests/config/testconfig-example.hpp +++ b/tests/unit_tests/config/testconfig-example.hpp @@ -88,6 +88,7 @@ struct TestConfig { std::uint32_t uint32Val; std::uint64_t uint64Val; std::string stringVal; + char* cstringVal; double doubleVal; bool boolVal; @@ -110,6 +111,7 @@ struct TestConfig { uint32Val, uint64Val, stringVal, + cstringVal, doubleVal, boolVal, @@ -150,6 +152,7 @@ const std::string jsonTestString = "\"uint32Val\": 123456, " "\"uint64Val\": 1234567890123456789, " "\"stringVal\": \"blah\", " + "\"cstringVal\": \"blah\", " "\"doubleVal\": -1.234000, " "\"boolVal\": true, " "\"emptyIntVector\": [ ], " @@ -173,6 +176,7 @@ const std::string jsonEmptyTestString = "\"uint32Val\": 0, " "\"uint64Val\": 0, " "\"stringVal\": \"\", " + "\"cstringVal\": \"\", " "\"boolVal\": false, " "\"emptyIntVector\": [ ], " "\"intVector\": [ ], " diff --git a/tests/unit_tests/config/ut-configuration.cpp b/tests/unit_tests/config/ut-configuration.cpp index 88b6a4e..68311eb 100644 --- a/tests/unit_tests/config/ut-configuration.cpp +++ b/tests/unit_tests/config/ut-configuration.cpp @@ -64,6 +64,7 @@ BOOST_AUTO_TEST_CASE(FromJsonString) BOOST_CHECK_EQUAL(123456, testConfig.uint32Val); BOOST_CHECK_EQUAL(1234567890123456789ll, testConfig.uint64Val); BOOST_CHECK_EQUAL("blah", testConfig.stringVal); + BOOST_CHECK_EQUAL("blah", testConfig.cstringVal); BOOST_CHECK_CLOSE(-1.234, testConfig.doubleVal, TOLERANCE); BOOST_CHECK_EQUAL(true, testConfig.boolVal); -- 2.7.4 From 1ce1ce12ca76938b17b1b83f5f6443be417156da Mon Sep 17 00:00:00 2001 From: Jan Olszak Date: Tue, 29 Sep 2015 17:41:18 +0200 Subject: [PATCH 16/16] config: std::array serialization. [Feature] Serialization of std::array [Cause] N/A [Solution] N/A [Verification] Build, install, run tests. Change-Id: I3e2f890d07ef6c0818135566eadb11a025438fd7 --- libs/config/from-fdstore-visitor.hpp | 10 ++++++++ libs/config/from-gvariant-visitor.hpp | 15 ++++++++++++ libs/config/from-json-visitor.hpp | 17 ++++++++++--- libs/config/from-kvjson-visitor.hpp | 34 ++++++++++++++++++++++++++ libs/config/kvstore.hpp | 31 +++++++++++++++++++++++ libs/config/to-fdstore-visitor.hpp | 14 +++++++++-- libs/config/to-gvariant-visitor.hpp | 15 ++++++++++++ libs/config/to-json-visitor.hpp | 10 ++++++++ tests/unit_tests/config/testconfig-example.hpp | 6 +++++ tests/unit_tests/config/ut-configuration.cpp | 10 +++++--- 10 files changed, 154 insertions(+), 8 deletions(-) diff --git a/libs/config/from-fdstore-visitor.hpp b/libs/config/from-fdstore-visitor.hpp index a948b3b..1013a61 100644 --- a/libs/config/from-fdstore-visitor.hpp +++ b/libs/config/from-fdstore-visitor.hpp @@ -29,7 +29,9 @@ #include "config/fdstore.hpp" #include "config/types.hpp" +#include #include +#include namespace config { @@ -100,6 +102,14 @@ private: readInternal(value); } } + + template + void readInternal(std::array& values) + { + for (T& value : values) { + readInternal(value); + } + } }; } // namespace config diff --git a/libs/config/from-gvariant-visitor.hpp b/libs/config/from-gvariant-visitor.hpp index a4134b6..213f917 100644 --- a/libs/config/from-gvariant-visitor.hpp +++ b/libs/config/from-gvariant-visitor.hpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -160,6 +161,20 @@ private: } } + template + static void fromGVariant(GVariant* object, std::array& values) + { + checkType(object, G_VARIANT_TYPE_ARRAY); + + GVariantIter iter; + g_variant_iter_init(&iter, object); + + for (T& value: values) { + auto child = makeUnique(g_variant_iter_next_value(&iter)); + fromGVariant(child.get(), value); + } + } + template static typename std::enable_if::value>::type fromGVariant(GVariant* object, T& value) diff --git a/libs/config/from-json-visitor.hpp b/libs/config/from-json-visitor.hpp index bc7864a..041027e 100644 --- a/libs/config/from-json-visitor.hpp +++ b/libs/config/from-json-visitor.hpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace config { @@ -149,13 +150,23 @@ private: } template - static void fromJsonObject(json_object* object, std::vector& value) + static void fromJsonObject(json_object* object, std::vector& values) { checkType(object, json_type_array); int length = json_object_array_length(object); - value.resize(static_cast(length)); + values.resize(static_cast(length)); for (int i = 0; i < length; ++i) { - fromJsonObject(json_object_array_get_idx(object, i), value[static_cast(i)]); + fromJsonObject(json_object_array_get_idx(object, i), values[static_cast(i)]); + } + } + + template + static void fromJsonObject(json_object* object, std::array& values) + { + checkType(object, json_type_array); + + for (std::size_t i = 0; i < N; ++i) { + fromJsonObject(json_object_array_get_idx(object, i), values[i]); } } diff --git a/libs/config/from-kvjson-visitor.hpp b/libs/config/from-kvjson-visitor.hpp index cc6e14a..96f3888 100644 --- a/libs/config/from-kvjson-visitor.hpp +++ b/libs/config/from-kvjson-visitor.hpp @@ -167,6 +167,25 @@ private: } } + template + void getValue(const std::string& name, std::array& values) + { + json_object* object = nullptr; + if (mObject && json_object_object_get_ex(mObject, name.c_str(), &object)) { + checkType(object, json_type_array); + } + + std::string k = key(mKeyPrefix, name); + FromKVJsonVisitor visitor(*this, name, false); + if (mStore.exists(k)) { + json_object_put(visitor.mObject); + visitor.mObject = nullptr; + } + for (std::size_t i = 0; i < N; ++i) { + visitor.getValue(i, values[i]); + } + } + template::value, int>::type = 0> void getValue(int i, T& t) { @@ -213,6 +232,21 @@ private: } } + template + void getValue(int i, std::array& values) + { + std::string k = key(mKeyPrefix, std::to_string(i)); + + FromKVJsonVisitor visitor(*this, i, false); + if (mStore.exists(k)) { + json_object_put(visitor.mObject); + visitor.mObject = nullptr; + } + for (std::size_t idx = 0; idx < N; ++idx) { + visitor.getValue(idx, values[idx]); + } + } + static void checkType(json_object* object, json_type type) { if (type != json_object_get_type(object)) { diff --git a/libs/config/kvstore.hpp b/libs/config/kvstore.hpp index 583d534..fe30e21 100644 --- a/libs/config/kvstore.hpp +++ b/libs/config/kvstore.hpp @@ -141,6 +141,8 @@ private: void setInternal(const std::string& key, const T& value); template void setInternal(const std::string& key, const std::vector& values); + template + void setInternal(const std::string& key, const std::array& values); std::string getInternal(const std::string& key, std::string*); char* getInternal(const std::string& key, char**); @@ -149,6 +151,8 @@ private: T getInternal(const std::string& key, T*); template std::vector getInternal(const std::string& key, std::vector*); + template + std::array getInternal(const std::string& key, std::array*); std::string mPath; sqlite3::Connection mConn; @@ -204,12 +208,39 @@ void KVStore::setInternal(const std::string& key, const std::vector& values) setInternal(key, strValues); } +template +void KVStore::setInternal(const std::string& key, const std::array& values) +{ + std::vector strValues(N); + + std::transform(values.begin(), + values.end(), + strValues.begin(), + toString); + + setInternal(key, strValues); +} + template T KVStore::getInternal(const std::string& key, T*) { return fromString(getInternal(key, static_cast(nullptr))); } +template +std::array KVStore::getInternal(const std::string& key, std::array*) +{ + std::vector strValues = getInternal(key, static_cast*>(nullptr)); + std::array values; + + std::transform(strValues.begin(), + strValues.end(), + values.begin(), + fromString); + + return values; +} + template std::vector KVStore::getInternal(const std::string& key, std::vector*) { diff --git a/libs/config/to-fdstore-visitor.hpp b/libs/config/to-fdstore-visitor.hpp index 7da3d76..7ce8058 100644 --- a/libs/config/to-fdstore-visitor.hpp +++ b/libs/config/to-fdstore-visitor.hpp @@ -29,8 +29,10 @@ #include "config/fdstore.hpp" #include "config/types.hpp" -#include +#include #include +#include +#include namespace config { @@ -90,7 +92,15 @@ private: void writeInternal(const std::vector& values) { writeInternal(values.size()); - for (const T& value : values) { + for (const T& value: values) { + writeInternal(value); + } + } + + template + void writeInternal(const std::array& values) + { + for (const T& value: values) { writeInternal(value); } } diff --git a/libs/config/to-gvariant-visitor.hpp b/libs/config/to-gvariant-visitor.hpp index 40c002d..1bd1375 100644 --- a/libs/config/to-gvariant-visitor.hpp +++ b/libs/config/to-gvariant-visitor.hpp @@ -29,6 +29,7 @@ #include "config/is-union.hpp" #include "config/types.hpp" +#include #include #include #include @@ -100,6 +101,20 @@ private: } } + template + void writeInternal(const std::array& values) + { + if (!values.empty()) { + g_variant_builder_open(mBuilder, G_VARIANT_TYPE_ARRAY); + for (const T& v : values) { + writeInternal(v); + } + g_variant_builder_close(mBuilder); + } else { + g_variant_builder_add(mBuilder, "as", NULL); + } + } + template typename std::enable_if::value && !isUnion::value>::type writeInternal(const T& value) diff --git a/libs/config/to-json-visitor.hpp b/libs/config/to-json-visitor.hpp index 717b952..3c746d2 100644 --- a/libs/config/to-json-visitor.hpp +++ b/libs/config/to-json-visitor.hpp @@ -135,6 +135,16 @@ private: return array; } + template + static json_object* toJsonObject(const std::array& values) + { + json_object* array = json_object_new_array(); + for (const T& v : values) { + json_object_array_add(array, toJsonObject(v)); + } + return array; + } + template::value>::type> static json_object* toJsonObject(const T& value) { diff --git a/tests/unit_tests/config/testconfig-example.hpp b/tests/unit_tests/config/testconfig-example.hpp index 949239d..c6f8476 100644 --- a/tests/unit_tests/config/testconfig-example.hpp +++ b/tests/unit_tests/config/testconfig-example.hpp @@ -97,6 +97,8 @@ struct TestConfig { std::vector stringVector; std::vector doubleVector; + std::array intArray; + SubConfig subObj; std::vector subVector; @@ -120,6 +122,8 @@ struct TestConfig { stringVector, doubleVector, + intArray, + subObj, subVector, @@ -159,6 +163,7 @@ const std::string jsonTestString = "\"intVector\": [ 1, 2, 3 ], " "\"stringVector\": [ \"a\", \"b\" ], " "\"doubleVector\": [ 0.000000, 1.000000, 2.000000 ], " + "\"intArray\": [ 0, 1 ], " "\"subObj\": { \"intVal\": 54321, \"intVector\": [ 1, 2 ], \"subSubObj\": { \"intVal\": 234 } }, " "\"subVector\": [ { \"intVal\": 123, \"intVector\": [ 3, 4 ], \"subSubObj\": { \"intVal\": 345 } }, " "{ \"intVal\": 456, \"intVector\": [ 5, 6 ], \"subSubObj\": { \"intVal\": 567 } } ], " @@ -182,6 +187,7 @@ const std::string jsonEmptyTestString = "\"intVector\": [ ], " "\"stringVector\": [ ], " "\"doubleVector\": [ ], " + "\"intArray\": [ ], " "\"subObj\": { \"intVal\": 0, \"intVector\": [ ], \"subSubObj\": { \"intVal\": 0 } }, " "\"subVector\": [ ], " "\"union1\": { \"type\": \"int\", \"value\": 0 }, " diff --git a/tests/unit_tests/config/ut-configuration.cpp b/tests/unit_tests/config/ut-configuration.cpp index 68311eb..a134b7b 100644 --- a/tests/unit_tests/config/ut-configuration.cpp +++ b/tests/unit_tests/config/ut-configuration.cpp @@ -71,9 +71,9 @@ BOOST_AUTO_TEST_CASE(FromJsonString) BOOST_REQUIRE_EQUAL(0, testConfig.emptyIntVector.size()); BOOST_REQUIRE_EQUAL(3, testConfig.intVector.size()); - BOOST_CHECK_EQUAL(1, testConfig.intVector[0]); - BOOST_CHECK_EQUAL(2, testConfig.intVector[1]); - BOOST_CHECK_EQUAL(3, testConfig.intVector[2]); + BOOST_CHECK_EQUAL(1, testConfig.intVector[0]); + BOOST_CHECK_EQUAL(2, testConfig.intVector[1]); + BOOST_CHECK_EQUAL(3, testConfig.intVector[2]); BOOST_REQUIRE_EQUAL(2, testConfig.stringVector.size()); BOOST_CHECK_EQUAL("a", testConfig.stringVector[0]); @@ -84,6 +84,10 @@ BOOST_AUTO_TEST_CASE(FromJsonString) BOOST_CHECK_CLOSE(1.0, testConfig.doubleVector[1], TOLERANCE); BOOST_CHECK_CLOSE(2.0, testConfig.doubleVector[2], TOLERANCE); + BOOST_REQUIRE_EQUAL(2, testConfig.intArray.size()); + BOOST_CHECK_EQUAL(0, testConfig.intArray[0]); + BOOST_CHECK_EQUAL(1, testConfig.intArray[1]); + BOOST_CHECK_EQUAL(54321, testConfig.subObj.intVal); BOOST_CHECK_EQUAL(2, testConfig.subObj.intVector.size()); BOOST_CHECK_EQUAL(1, testConfig.subObj.intVector[0]); -- 2.7.4