From 14b5cfe2a11e421c777a7f2324d9562f6fe8f31b Mon Sep 17 00:00:00 2001 From: michalbiesek Date: Wed, 23 Nov 2022 13:11:42 +0100 Subject: [PATCH] (#1108) IPC mechanism server side - handle IPC mechanism communication between scoped application and CLI TODO: - CLI - integration test - IPC namespace switch Closes: #1108 --- cli/cmd/inspect.go | 22 +++ cli/inspect/inspect.go | 9 ++ docs/IPC.md | 8 ++ os/linux/Makefile | 1 + os/linux/aarch64.mk | 2 +- os/linux/x86_64.mk | 2 +- os/macOS/Makefile | 2 +- src/ipc.c | 160 +++++++++++++++++++++ src/ipc.h | 25 ++++ src/wrap.c | 45 ++++++ test/execute.sh | 1 + test/ipctest.c | 306 ++++++++++++++++++++++++++++++++++++++++ test/manual/ipcclient.c | 95 +++++++++++++ 13 files changed, 675 insertions(+), 3 deletions(-) create mode 100644 cli/cmd/inspect.go create mode 100644 cli/inspect/inspect.go create mode 100644 docs/IPC.md create mode 100644 src/ipc.c create mode 100644 src/ipc.h create mode 100644 test/ipctest.c create mode 100644 test/manual/ipcclient.c diff --git a/cli/cmd/inspect.go b/cli/cmd/inspect.go new file mode 100644 index 000000000..c6c6d5e26 --- /dev/null +++ b/cli/cmd/inspect.go @@ -0,0 +1,22 @@ +package cmd + +import ( + "github.com/criblio/scope/inspect" + "github.com/spf13/cobra" +) + +// inspectCmd represents the inspect command +var inspectCmd = &cobra.Command{ + Use: "inspect [flags]", + Short: "Display scope inspect", + Long: `Outputs inspect info.`, + Example: `scope inspect`, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + inspect.Inspect() + }, +} + +func init() { + RootCmd.AddCommand(inspectCmd) +} diff --git a/cli/inspect/inspect.go b/cli/inspect/inspect.go new file mode 100644 index 000000000..f2e181212 --- /dev/null +++ b/cli/inspect/inspect.go @@ -0,0 +1,9 @@ +package inspect + +import "fmt" + +// startConfig represents a processed configuration +func Inspect() error { + fmt.Println("inspect Test") + return nil +} diff --git a/docs/IPC.md b/docs/IPC.md new file mode 100644 index 000000000..5de19ebb0 --- /dev/null +++ b/docs/IPC.md @@ -0,0 +1,8 @@ +# IPC Notes + +- For IPC communication we are using pair of message queue: + +- `scope` is responsible to create and read on `ScopeCLI` +- Scoped application is responsible to create and read `ScopeIPC.` + +TODO: Picture diff --git a/os/linux/Makefile b/os/linux/Makefile index c9b8fff9d..4e405759b 100644 --- a/os/linux/Makefile +++ b/os/linux/Makefile @@ -93,6 +93,7 @@ coretest: export USER ?= $(shell id -u -n) coretest: $(C_FILES) $(YAML_AR) $(JSON_AR) $(TEST_LIB) @echo "$${CI:+::group::}Building Tests" $(CC) -c $(TEST_CFLAGS) $(C_FILES) $(INCLUDES) $(TEST_INCLUDES) + $(CC) $(TEST_CFLAGS) -o test/$(OS)/ipctest ipctest.o ipc.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS) $(CC) $(TEST_CFLAGS) -o test/$(OS)/vdsotest vdsotest.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS) $(CC) $(TEST_CFLAGS) -o test/$(OS)/libvertest libvertest.o libver.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS) $(CC) $(TEST_CFLAGS) -o test/$(OS)/libdirtest libdirtest.o libdir.o nsfile.o libver.o scopestdlib.o dbg.o test.o $(TEST_AR) $(TEST_LD_FLAGS) diff --git a/os/linux/aarch64.mk b/os/linux/aarch64.mk index 96f8030e7..72f61e5e3 100644 --- a/os/linux/aarch64.mk +++ b/os/linux/aarch64.mk @@ -5,7 +5,7 @@ ARCH_OBJ=$(ARCH) LD_FLAGS=$(MUSL_AR) $(UNWIND_AR) $(PCRE2_AR) $(LS_HPACK_AR) $(YAML_AR) $(JSON_AR) -ldl -lpthread -lrt -lresolv -Lcontrib/build/funchook -lfunchook -Lcontrib/build/funchook/capstone_src-prefix/src/capstone_src-build -lcapstone -z noexecstack INCLUDES=-I./contrib/libyaml/include -I./contrib/cJSON -I./os/$(OS) -I./contrib/pcre2/src -I./contrib/build/pcre2 -I./contrib/funchook/capstone_src/include/ -I./contrib/jni -I./contrib/jni/linux/ -I./contrib/openssl/include -I./contrib/build/openssl/include -I./contrib/build/libunwind/include -I./contrib/libunwind/include/ -$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/scopeelf.c src/utils.c src/strset.c src/javabci.c src/javaagent.c +$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/scopeelf.c src/utils.c src/strset.c src/javabci.c src/javaagent.c src/ipc.c @$(MAKE) -C contrib funchook pcre2 openssl ls-hpack musl libyaml libunwind cJSON @echo "$${CI:+::group::}Building $@" $(CC) $(CFLAGS) \ diff --git a/os/linux/x86_64.mk b/os/linux/x86_64.mk index 57187ed53..fd9d4e704 100644 --- a/os/linux/x86_64.mk +++ b/os/linux/x86_64.mk @@ -11,7 +11,7 @@ INCLUDES=-I./contrib/libyaml/include -I./contrib/cJSON -I./os/$(OS) -I./contrib/ # objcopy -I binary -O elf64-x86-64 -B i386 ./lib/$(OS)/libscope.so ./lib/$(OS)/libscope.o && \ # rm -f ./lib/$(OS)/libscope.so -$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/sysexec.c src/gocontext.S src/scopeelf.c src/wrap_go.c src/utils.c src/strset.c src/javabci.c src/javaagent.c +$(LIBSCOPE): src/wrap.c src/state.c src/httpstate.c src/metriccapture.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/sysexec.c src/gocontext.S src/scopeelf.c src/wrap_go.c src/utils.c src/strset.c src/javabci.c src/javaagent.c src/ipc.c @$(MAKE) -C contrib funchook pcre2 openssl ls-hpack musl libyaml cJSON libunwind @echo "$${CI:+::group::}Building $@" $(CC) $(CFLAGS) $(ARCH_CFLAGS) \ diff --git a/os/macOS/Makefile b/os/macOS/Makefile index bb6a29b4d..f785db25b 100644 --- a/os/macOS/Makefile +++ b/os/macOS/Makefile @@ -25,7 +25,7 @@ $(PCRE2_AR): cd contrib/pcre2/build && cmake .. cd contrib/pcre2/build && make -libscope.so: src/wrap.c src/state.c src/httpstate.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/utils.c $(YAML_SRC) contrib/cJSON/cJSON.c +libscope.so: src/wrap.c src/state.c src/httpstate.c src/report.c src/httpagg.c src/plattime.c src/fn.c os/$(OS)/os.c src/cfgutils.c src/cfg.c src/transport.c src/log.c src/mtc.c src/circbuf.c src/linklist.c src/evtformat.c src/ctl.c src/mtcformat.c src/com.c src/scopestdlib.c src/dbg.c src/search.c src/utils.c $(YAML_SRC) contrib/cJSON/cJSON.c src/ipc.c @echo "Building libscope.so ..." make $(PCRE2_AR) $(CC) $(CFLAGS) -shared -fvisibility=hidden -DSCOPE_VER=\"$(SCOPE_VER)\" $(YAML_DEFINES) -o ./lib/$(OS)/$@ $(INCLUDES) $^ -e,prog_version $(LD_FLAGS) diff --git a/src/ipc.c b/src/ipc.c new file mode 100644 index 000000000..c158fe0b6 --- /dev/null +++ b/src/ipc.c @@ -0,0 +1,160 @@ +#define _GNU_SOURCE + +#include "ipc.h" + +/* Inter-process communication module based on the message-queue + * + * Message-queue system limits which are related to `ipcOpenConnection` + * are defined in following files: + * + * "/proc/sys/fs/mqueue/msg_max" + * - describes maximum number of messsages in a queue (QUEUE_MSG_MAX) + * + * "/proc/sys/fs/mqueue/msgsize_max" + * - describes maximum message size in a queue (QUEUE_MSG_SIZE) + * + * "/proc/sys/fs/mqueue/queues_max" + * - describes system-wide limit on the number of message queues that can be created + * + * See details in: https://man7.org/linux/man-pages/man7/mq_overview.7.html + */ + +#define QUEUE_MSG_MAX 10 +#define QUEUE_MSG_SIZE 8192 + +#define INPUT_CMD_LEN(x) (sizeof(x)-1) +#define CMD_TABLE_SIZE(x) (sizeof(x)/sizeof(x[0])) + +/* Output message function definition + * Fills up the input buffer return the size of message + * + */ +typedef size_t (*out_msg_func_t)(char *, size_t); + +static size_t +cmdGetAttach(char *buf, size_t len) { + // scope_snprintf(buf, len, "[%d] %s", g_proc.pid, g_cfg.funcs_attached ? "Attached" : "Detached"); + // Excluding the terminating null byte + return scope_snprintf(buf, len, "GetAttach"); +} + +static size_t +cmdUnknown(char *buf, size_t len) { + // Excluding the terminating null byte + return scope_snprintf(buf, len, "Unknown"); +} + +typedef struct { + const char *name; // command name string [in] + size_t nameLen; // command name string length [in] + ipc_cmd_t cmd; // command id [out] +} input_cmd_table_t; + +typedef struct { + ipc_cmd_t cmd; // command id [in] + out_msg_func_t func; // output func [out] +} output_cmd_table_t; + +static int +ipcSend(mqd_t mqdes, const char *data, size_t len) { + return scope_mq_send(mqdes, data, len, 0); +} + +static ssize_t +ipcRecv(mqd_t mqdes, char *buf, size_t len) { + return scope_mq_receive(mqdes, buf, len, 0); +} + +mqd_t +ipcCreateNonBlockReadConnection(const char *name) { + struct mq_attr attr = {.mq_flags = 0, + .mq_maxmsg = QUEUE_MSG_MAX, + .mq_msgsize = QUEUE_MSG_SIZE, + .mq_curmsgs = 0}; + return scope_mq_open(name, O_RDONLY | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, &attr); +} + +mqd_t +ipcOpenWriteConnection(const char *name) { + return scope_mq_open(name, O_WRONLY | O_NONBLOCK); +} + +int +ipcCloseConnection(mqd_t mqdes) { + return scope_mq_close(mqdes); +} + +int +ipcDestroyConnection(const char *name) { + return scope_mq_unlink(name); +} + +long +ipcInfoMsgCount(mqd_t mqdes) { + struct mq_attr attr; + int res = scope_mq_getattr(mqdes, &attr); + if (res == 0) { + return attr.mq_curmsgs; + } + return -1; +} + +bool +ipcIsActive(mqd_t mqdes) { + return mqdes != (mqd_t) -1; +} + +bool +ipcRequestMsgHandler(mqd_t mqdes, ipc_cmd_t *cmdRes) { + // TODO do this dynamically based on mqdes + char buf[QUEUE_MSG_SIZE] = {0}; + + ssize_t recvLen = ipcRecv(mqdes, buf, sizeof(buf)); + if (recvLen == -1) { + return FALSE; + } + + // The length should be inline with outputCmdTable + input_cmd_table_t inputCmdTable[] = { + {"getStatus", INPUT_CMD_LEN("getStatus"), IPC_CMD_GET_ATTACH_STATUS} + }; + + for (int i = 0; i < CMD_TABLE_SIZE(inputCmdTable); ++i) { + if ((recvLen == inputCmdTable[i].nameLen) && !scope_memcmp(inputCmdTable[i].name, buf, recvLen)) { + *cmdRes = inputCmdTable[i].cmd; + return TRUE; + } + } + *cmdRes = IPC_CMD_UNKNOWN; + return TRUE; +} + +bool +ipcResponseMsgHandler(mqd_t mqdes, ipc_cmd_t cmd) { + // TODO do this dynamically based on mqdes + char buf[QUEUE_MSG_SIZE] = {0}; + size_t len; + if (cmd > IPC_CMD_UNKNOWN) { + return FALSE; + } + + // The length should be inline with inputCmdTable + output_cmd_table_t outputCmdTable[] = { + {IPC_CMD_GET_ATTACH_STATUS, cmdGetAttach}, + {IPC_CMD_UNKNOWN, cmdUnknown} + }; + + for (int i = 0; i < CMD_TABLE_SIZE(outputCmdTable); ++i) { + if (cmd == outputCmdTable[i].cmd) { + len = outputCmdTable[i].func(buf, sizeof(buf)); + break; + } + } + + int sendRes = ipcSend(mqdes, buf, len); + if (sendRes == -1) { + return FALSE; + } + + return TRUE; +} \ No newline at end of file diff --git a/src/ipc.h b/src/ipc.h new file mode 100644 index 000000000..99eae09c5 --- /dev/null +++ b/src/ipc.h @@ -0,0 +1,25 @@ +#ifndef __IPC_H__ +#define __IPC_H__ + +#include "scopestdlib.h" +#include "scopetypes.h" + + +typedef enum { + IPC_CMD_GET_ATTACH_STATUS, // Retrieve attach status + IPC_CMD_UNKNOWN, // Unknown command (Must be last) +} ipc_cmd_t; + +// Manage Inter-process connection +mqd_t ipcCreateNonBlockReadConnection(const char *); +mqd_t ipcOpenWriteConnection(const char *); +int ipcCloseConnection(mqd_t); +int ipcDestroyConnection(const char *); +long ipcInfoMsgCount(mqd_t); +bool ipcIsActive(mqd_t); + +// IPC Request/Response Handler +bool ipcRequestMsgHandler(mqd_t, ipc_cmd_t *); +bool ipcResponseMsgHandler(mqd_t, ipc_cmd_t); + +#endif // __IPC_H__ diff --git a/src/wrap.c b/src/wrap.c index a69da0e1a..748a071ba 100644 --- a/src/wrap.c +++ b/src/wrap.c @@ -39,6 +39,7 @@ #include "runtimecfg.h" #include "javaagent.h" #include "inject.h" +#include "ipc.h" #include "scopestdlib.h" #include "../contrib/libmusl/musl.h" @@ -55,6 +56,7 @@ static const char *g_cmddir; static list_t *g_nsslist; static uint64_t reentrancy_guard = 0ULL; static rlim_t g_max_fds = 0; +static mqd_t g_receiver_msg_q = (mqd_t) -1; typedef int (*ssl_rdfunc_t)(SSL *, void *, int); typedef int (*ssl_wrfunc_t)(SSL *, const void *, int); @@ -423,6 +425,38 @@ cmdAttach(void) return TRUE; } + +static void +mqConfig(void) { + if (ipcIsActive(g_receiver_msg_q) == FALSE) { + return; + } + + long msgCount = ipcInfoMsgCount(g_receiver_msg_q); + if (msgCount <= 0) { + return; + } + + ipc_cmd_t cmd = -1; + + if (ipcRequestMsgHandler(g_receiver_msg_q, &cmd) == FALSE) { + return; + } + + // Handle response + mqd_t cliMqDes = ipcOpenWriteConnection("/ScopeCLI"); + if (ipcIsActive(cliMqDes) == FALSE) { + scopeLogError("/ScopeCLI is missing."); + return; + } + + if (ipcResponseMsgHandler(cliMqDes, cmd) == FALSE) { + scopeLogError("Error: with sending answet to /ScopeCLI for cmd %d", cmd); + } + + ipcCloseConnection(cliMqDes); +} + static void remoteConfig() { @@ -1002,6 +1036,11 @@ handleExit(void) ctlDisconnect(g_ctl, CFG_CTL); logFlush(g_log); logDisconnect(g_log); + ipcCloseConnection(g_receiver_msg_q); + g_receiver_msg_q = (mqd_t) -1; + char name[256]; + scope_snprintf(name, sizeof(name), "/ScopeIPC.%d", g_proc.pid); + ipcDestroyConnection(name); } static void * @@ -1081,6 +1120,7 @@ periodic(void *arg) } } remoteConfig(); + mqConfig(); } return NULL; @@ -1636,6 +1676,11 @@ init(void) setMachineID(g_proc.machine_id); setUUID(g_proc.uuid); + // Create IPC connection message queue + char name[256]; + scope_snprintf(name, sizeof(name), "/ScopeIPC.%d", g_proc.pid); + g_receiver_msg_q = ipcCreateNonBlockReadConnection(name); + // initEnv() will set this TRUE if it detects `scope_attach_PID.env` in // `/dev/shm` with our PID indicating we were injected into a running // process. diff --git a/test/execute.sh b/test/execute.sh index 495228225..c27f4ca57 100755 --- a/test/execute.sh +++ b/test/execute.sh @@ -63,6 +63,7 @@ fi declare -i ERR=0 run_test test/${OS}/vdsotest +run_test test/${OS}/ipctest run_test test/${OS}/libvertest run_test test/${OS}/libdirtest run_test test/${OS}/nsinfotest diff --git a/test/ipctest.c b/test/ipctest.c new file mode 100644 index 000000000..13a3eb2d7 --- /dev/null +++ b/test/ipctest.c @@ -0,0 +1,306 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include "ipc.h" +#include "test.h" + + +static void +ipcInactiveDesc(void **state) { + mqd_t mqdes = (mqd_t)-1; + bool res = ipcIsActive(mqdes); + assert_false(res); +} + +static void +ipcInfoMsgCountNonExisting(void **state) { + long msgCount = ipcInfoMsgCount((mqd_t)-1); + assert_int_equal(msgCount, -1); +} + +static void +ipcOpenNonExistingConnection(void **state) { + mqd_t mqDes = ipcOpenWriteConnection("/NonExistingConnection"); + assert_int_equal(mqDes, -1); +} + +static void +ipcCommunicationTest(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + bool res; + mqd_t mqWriteDes, mqReadDes; + long msgCount; + struct mq_attr attr; + void *buf; + ssize_t dataLen; + + // Setup read-only IPC + mqReadDes = ipcCreateNonBlockReadConnection(ipcConnName); + assert_int_not_equal(mqReadDes, -1); + res = ipcIsActive(mqReadDes); + assert_true(res); + msgCount = ipcInfoMsgCount(mqReadDes); + assert_int_equal(msgCount, 0); + + // Read-only IPC verify that is impossible to send msg to IPC + scope_errno = 0; + status = scope_mq_send(mqReadDes, "test", sizeof("test"), 0); + assert_int_equal(scope_errno, EBADF); + assert_int_equal(status, -1); + + // Setup write-only IPC + mqWriteDes = ipcOpenWriteConnection(ipcConnName); + assert_int_not_equal(mqWriteDes, -1); + res = ipcIsActive(mqWriteDes); + assert_true(res); + + // Write-only IPC verify that it is possible to send msg to IPC + status = scope_mq_send(mqWriteDes, "test", sizeof("test"), 0); + assert_int_not_equal(status, -1); + + status = scope_mq_getattr(mqWriteDes, &attr); + assert_int_equal(status, 0); + + buf = scope_malloc(attr.mq_msgsize); + assert_non_null(buf); + + // Write-only IPC verify that is impossible to read msg from IPC + scope_errno = 0; + dataLen = scope_mq_receive(mqWriteDes, buf, attr.mq_msgsize, 0); + assert_int_equal(scope_errno, EBADF); + assert_int_equal(dataLen, -1); + + scope_free(buf); + + msgCount = ipcInfoMsgCount(mqWriteDes); + assert_int_equal(msgCount, 1); + msgCount = ipcInfoMsgCount(mqReadDes); + assert_int_equal(msgCount, 1); + + status = scope_mq_getattr(mqReadDes, &attr); + assert_int_equal(status, 0); + + buf = scope_malloc(attr.mq_msgsize); + assert_non_null(buf); + + // Read-only IPC verify that it is possible to read msg from IPC + dataLen = scope_mq_receive(mqReadDes, buf, attr.mq_msgsize, 0); + assert_int_equal(dataLen, sizeof("test")); + + scope_free(buf); + + msgCount = ipcInfoMsgCount(mqWriteDes); + assert_int_equal(msgCount, 0); + msgCount = ipcInfoMsgCount(mqReadDes); + assert_int_equal(msgCount, 0); + + // Teardown IPC(s) + status = ipcCloseConnection(mqWriteDes); + assert_int_equal(status, 0); + + status = ipcCloseConnection(mqReadDes); + assert_int_equal(status, 0); + status = ipcDestroyConnection(ipcConnName); + assert_int_equal(status, 0); +} + +static void +ipcHandlerRequestEmpty(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadWriteDes; + ipc_cmd_t cmd; + bool res; + + mqReadWriteDes = scope_mq_open(ipcConnName, O_RDWR | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadWriteDes, -1); + + // Empty Message queue + res = ipcRequestMsgHandler(mqReadWriteDes, &cmd); + assert_false(res); + + status = scope_mq_close(mqReadWriteDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + +static void +ipcHandlerRequestValid(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadWriteDes; + ipc_cmd_t cmd; + bool res; + + mqReadWriteDes = scope_mq_open(ipcConnName, O_RDWR | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadWriteDes, -1); + + // Put valid message on queue + status = scope_mq_send(mqReadWriteDes, "getStatus", scope_strlen("getStatus"), 0); + assert_int_equal(status, 0); + + res = ipcRequestMsgHandler(mqReadWriteDes, &cmd); + assert_true(res); + assert_int_equal(cmd, IPC_CMD_GET_ATTACH_STATUS); + + status = scope_mq_close(mqReadWriteDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + +static void +ipcHandlerRequestUnknown(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadWriteDes; + ipc_cmd_t cmd; + bool res; + + mqReadWriteDes = scope_mq_open(ipcConnName, O_RDWR | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadWriteDes, -1); + + // Put dummy message on queue + status = scope_mq_send(mqReadWriteDes, "loremIpsum", scope_strlen("loremIpsum"), 0); + assert_int_equal(status, 0); + + res = ipcRequestMsgHandler(mqReadWriteDes, &cmd); + assert_true(res); + assert_int_equal(cmd, IPC_CMD_UNKNOWN); + + status = scope_mq_close(mqReadWriteDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + +static void +ipcHandlerResponseFail(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadDes; + bool res; + long msgCount; + + mqReadDes = scope_mq_open(ipcConnName, O_RDONLY | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadDes, -1); + + res = ipcResponseMsgHandler(mqReadDes, IPC_CMD_GET_ATTACH_STATUS); + assert_false(res); + msgCount = ipcInfoMsgCount(mqReadDes); + assert_int_equal(msgCount, 0); + + status = scope_mq_close(mqReadDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + + +static void +ipcHandlerResponseValid(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadWriteDes; + bool res; + long msgCount; + void *buf; + struct mq_attr attr; + ssize_t dataLen; + + mqReadWriteDes = scope_mq_open(ipcConnName, O_RDWR | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadWriteDes, -1); + + res = ipcResponseMsgHandler(mqReadWriteDes, IPC_CMD_GET_ATTACH_STATUS); + assert_true(res); + msgCount = ipcInfoMsgCount(mqReadWriteDes); + assert_int_equal(msgCount, 1); + + + status = scope_mq_getattr(mqReadWriteDes, &attr); + assert_int_equal(status, 0); + + buf = scope_malloc(attr.mq_msgsize); + assert_non_null(buf); + + dataLen = scope_mq_receive(mqReadWriteDes, buf, attr.mq_msgsize, 0); + assert_int_not_equal(dataLen, -1); + assert_int_equal(dataLen, sizeof("GetAttach") - 1); + + //below should reflect cmdGetAttach + status = scope_memcmp(buf, "GetAttach", dataLen); + assert_int_equal(status, 0); + + scope_free(buf); + + status = scope_mq_close(mqReadWriteDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + +static void +ipcHandlerResponseUnknown(void **state) { + const char *ipcConnName = "/testConnection"; + int status; + mqd_t mqReadWriteDes; + bool res; + long msgCount; + void *buf; + struct mq_attr attr; + ssize_t dataLen; + + mqReadWriteDes = scope_mq_open(ipcConnName, O_RDWR | O_CREAT | O_CLOEXEC | O_NONBLOCK, 0666, NULL); + assert_int_not_equal(mqReadWriteDes, -1); + + res = ipcResponseMsgHandler(mqReadWriteDes, IPC_CMD_UNKNOWN); + assert_true(res); + msgCount = ipcInfoMsgCount(mqReadWriteDes); + assert_int_equal(msgCount, 1); + + + status = scope_mq_getattr(mqReadWriteDes, &attr); + assert_int_equal(status, 0); + + buf = scope_malloc(attr.mq_msgsize); + assert_non_null(buf); + + dataLen = scope_mq_receive(mqReadWriteDes, buf, attr.mq_msgsize, 0); + assert_int_not_equal(dataLen, -1); + assert_int_equal(dataLen, sizeof("Unknown") - 1); + + //below should reflect cmdUnknown + status = scope_memcmp(buf, "Unknown", dataLen); + assert_int_equal(status, 0); + + scope_free(buf); + + status = scope_mq_close(mqReadWriteDes); + assert_int_equal(status, 0); + status = scope_mq_unlink(ipcConnName); + assert_int_equal(status, 0); +} + +int +main(int argc, char* argv[]) { + printf("running %s\n", argv[0]); + + const struct CMUnitTest tests[] = { + cmocka_unit_test(ipcInactiveDesc), + cmocka_unit_test(ipcInfoMsgCountNonExisting), + cmocka_unit_test(ipcOpenNonExistingConnection), + cmocka_unit_test(ipcCommunicationTest), + cmocka_unit_test(ipcHandlerRequestEmpty), + cmocka_unit_test(ipcHandlerRequestValid), + cmocka_unit_test(ipcHandlerRequestUnknown), + cmocka_unit_test(ipcHandlerResponseFail), + cmocka_unit_test(ipcHandlerResponseValid), + cmocka_unit_test(ipcHandlerResponseUnknown), + cmocka_unit_test(dbgHasNoUnexpectedFailures), + }; + return cmocka_run_group_tests(tests, groupSetup, groupTeardown); +} diff --git a/test/manual/ipcclient.c b/test/manual/ipcclient.c new file mode 100644 index 000000000..f063709d3 --- /dev/null +++ b/test/manual/ipcclient.c @@ -0,0 +1,95 @@ +/* + * ipcclient - IPC client + * + * A simple program to test communication between scoped process using + * gcc -g test/manual/ipcclient.c -lrt -o ipcclient + * ./ipcclient + */ + +#include +#include +#include +#include +#include + +#define MAX_MESSAGES 10 +#define MSG_BUFFER_SIZE 8192 + +static mqd_t readMqDesc; +#define READ_MQ_NAME "/ScopeCLI" + +void cleanupReadDesc(void) { + mq_close(readMqDesc); + mq_unlink(READ_MQ_NAME); +} + +int main(int argc, char **argv) { + struct mq_attr attr = {.mq_flags = 0, + .mq_maxmsg = MAX_MESSAGES, + .mq_msgsize = MSG_BUFFER_SIZE, + .mq_curmsgs = 0}; + + int res = EXIT_FAILURE; + char WriteMqName[4096] = {0}; + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + return res; + } + + snprintf(WriteMqName, sizeof(WriteMqName), "/ScopeIPC.%s", argv[1]); + + mqd_t writeMqDesc; + + // Ugly hack disable umask to handle run as a root + mode_t oldMask = umask(0); + readMqDesc = mq_open(READ_MQ_NAME, O_RDONLY | O_CREAT, 0666, &attr); + if (readMqDesc == (mqd_t)-1) { + perror("!mq_open readMqDesc failed"); + return res; + } + umask(oldMask); + + atexit(cleanupReadDesc); + + writeMqDesc = mq_open(WriteMqName, O_WRONLY); + if (writeMqDesc == (mqd_t)-1) { + perror("!mq_open writeMqDesc failed"); + return res; + } + + char Txbuf[MSG_BUFFER_SIZE] = {0}; + char RxBuf[MSG_BUFFER_SIZE] = {0}; + + printf("\nPass example message to stdin [type 'quit' to stop]\n"); + while (fgets(Txbuf, MSG_BUFFER_SIZE, stdin) != NULL) { + if(strcmp("quit\n", Txbuf) == 0) { + break; + } + + // Send message to scoped process + if (mq_send(writeMqDesc, Txbuf, strlen(Txbuf)-1, 0) == -1) { + perror("!mq_send writeMqDesc failed"); + goto end_iteration; + } + + + // Read response + if (mq_receive(readMqDesc, RxBuf, MSG_BUFFER_SIZE, NULL) == -1) { + perror("!mq_receive readMqDesc failed"); + goto end_iteration; + } + + printf("Response from pid process %s : %s", argv[1], RxBuf); + + end_iteration: + printf("\nPass example message to stdin [type 'quit' to stop]\n"); + memset(Txbuf, 0, MSG_BUFFER_SIZE); + memset(RxBuf, 0, MSG_BUFFER_SIZE); + } + + if (mq_close(writeMqDesc) == -1) { + perror ("!mq_close writeMqDesc failed"); + } + + return EXIT_SUCCESS; +}