diff --git a/CMakeLists.txt b/CMakeLists.txt
index 740a8aded..815c33677 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,3 +66,12 @@ add_library(devbnd SHARED src/binder.cpp)
target_compile_options(devbnd PRIVATE -D_GNU_SOURCE -Wall -Wextra)
target_link_options(devbnd PRIVATE -nostartfiles)
target_link_libraries(devbnd dl)
+
+#################### CLI EXECUTABLE ####################
+add_executable(nr-cli src/cli.cpp)
+target_link_libraries(nr-cli pthread)
+target_compile_options(nr-cli PRIVATE -Wall -Wextra -pedantic)
+
+target_link_libraries(nr-cli app)
+target_link_libraries(nr-cli udp)
+target_link_libraries(nr-cli utils)
diff --git a/README.md b/README.md
index 667868ba9..790c78077 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
diff --git a/makefile b/makefile
index 3c971cbaa..26eb89847 100644
--- a/makefile
+++ b/makefile
@@ -13,6 +13,7 @@ build: FORCE
cp cmake-build-release/nr-gnb build/
cp cmake-build-release/nr-ue build/
+ cp cmake-build-release/nr-cli build/
cp cmake-build-release/libdevbnd.so build/
cp tools/nr-binder build/
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index 1902d7d15..25beaa69c 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -8,3 +8,4 @@ add_library(app ${HDR_FILES} ${SRC_FILES})
target_compile_options(app PRIVATE -Wall -Wextra -pedantic -Wno-unused-parameter)
target_link_libraries(app utils)
+target_link_libraries(app udp)
diff --git a/src/app/base_app.cpp b/src/app/base_app.cpp
new file mode 100644
index 000000000..9ae6e052f
--- /dev/null
+++ b/src/app/base_app.cpp
@@ -0,0 +1,56 @@
+//
+// This file is a part of UERANSIM open source project.
+// Copyright (c) 2021 ALİ GÜNGÖR.
+//
+// The software and all associated files are licensed under GPL-3.0
+// and subject to the terms and conditions defined in LICENSE file.
+//
+
+#include "base_app.hpp"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static std::atomic_int g_instanceCount{};
+static std::vector g_runAtExit{};
+static std::vector g_deleteAtExit{};
+
+extern "C" void BaseSignalHandler(int num)
+{
+ for (auto &fun : g_runAtExit)
+ fun();
+ for (auto &file : g_deleteAtExit)
+ std::remove(file.c_str());
+
+ if (num == SIGTERM || num == SIGINT)
+ exit(0);
+}
+
+namespace app
+{
+
+void Initialize()
+{
+ if (g_instanceCount++ != 0)
+ std::terminate();
+
+ std::signal(SIGTERM, BaseSignalHandler);
+ std::signal(SIGINT, BaseSignalHandler);
+}
+
+void RunAtExit(void (*fun)())
+{
+ g_runAtExit.push_back(fun);
+}
+
+void DeleteAtExit(const std::string &file)
+{
+ g_deleteAtExit.push_back(file);
+}
+
+} // namespace app
diff --git a/src/app/base_app.hpp b/src/app/base_app.hpp
new file mode 100644
index 000000000..f1a2a586f
--- /dev/null
+++ b/src/app/base_app.hpp
@@ -0,0 +1,24 @@
+//
+// This file is a part of UERANSIM open source project.
+// Copyright (c) 2021 ALİ GÜNGÖR.
+//
+// The software and all associated files are licensed under GPL-3.0
+// and subject to the terms and conditions defined in LICENSE file.
+//
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace app
+{
+
+void Initialize();
+
+void RunAtExit(void (*fun)());
+
+void DeleteAtExit(const std::string &file);
+
+} // namespace app
diff --git a/src/app/cli_base.cpp b/src/app/cli_base.cpp
new file mode 100644
index 000000000..d0673bc0d
--- /dev/null
+++ b/src/app/cli_base.cpp
@@ -0,0 +1,69 @@
+//
+// This file is a part of UERANSIM open source project.
+// Copyright (c) 2021 ALİ GÜNGÖR.
+//
+// The software and all associated files are licensed under GPL-3.0
+// and subject to the terms and conditions defined in LICENSE file.
+//
+
+#include "cli_base.hpp"
+#include
+#include
+
+#define CMD_BUFFER_SIZE 8192
+#define CMD_RCV_TIMEOUT 2500
+#define CMD_MIN_LENGTH (3 + 4 + 4 + 1)
+
+namespace app
+{
+
+InetAddress CliServer::assignedAddress() const
+{
+ return m_socket.getAddress();
+}
+
+CliMessage CliServer::receiveMessage()
+{
+ uint8_t buffer[CMD_BUFFER_SIZE] = {0};
+ InetAddress address;
+
+ int size = m_socket.receive(buffer, CMD_BUFFER_SIZE, CMD_RCV_TIMEOUT, address);
+ if (size < CMD_MIN_LENGTH || size >= CMD_BUFFER_SIZE)
+ return {};
+
+ OctetView v{buffer, static_cast(size)};
+ if (v.readI() != cons::Major)
+ return {};
+ if (v.readI() != cons::Minor)
+ return {};
+ if (v.readI() != cons::Patch)
+ return {};
+
+ CliMessage res{};
+ res.type = static_cast(v.readI());
+ int nodeNameLength = v.read4I();
+ res.nodeName = v.readUtf8String(nodeNameLength);
+ int valueLength = v.read4I();
+ res.value = v.readUtf8String(valueLength);
+ res.clientAddr = address;
+ return res;
+}
+
+void CliServer::sendMessage(const CliMessage &msg)
+{
+ OctetString stream{};
+ stream.appendOctet(cons::Major);
+ stream.appendOctet(cons::Minor);
+ stream.appendOctet(cons::Patch);
+ stream.appendOctet(static_cast(msg.type));
+ stream.appendOctet4(static_cast(msg.nodeName.size()));
+ for (char c : msg.nodeName)
+ stream.appendOctet(static_cast(c));
+ stream.appendOctet4(static_cast(msg.value.size()));
+ for (char c : msg.value)
+ stream.appendOctet(static_cast(c));
+
+ m_socket.send(msg.clientAddr, stream.data(), static_cast(stream.length()));
+}
+
+} // namespace app
diff --git a/src/app/cli_base.hpp b/src/app/cli_base.hpp
new file mode 100644
index 000000000..a10a0b7fe
--- /dev/null
+++ b/src/app/cli_base.hpp
@@ -0,0 +1,143 @@
+//
+// This file is a part of UERANSIM open source project.
+// Copyright (c) 2021 ALİ GÜNGÖR.
+//
+// The software and all associated files are licensed under GPL-3.0
+// and subject to the terms and conditions defined in LICENSE file.
+//
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace app
+{
+
+struct CliMessage
+{
+ enum class Type
+ {
+ EMPTY = 0,
+ ECHO,
+ ERROR,
+ RESULT,
+ COMMAND
+ } type{};
+
+ std::string nodeName{};
+ std::string value{};
+ InetAddress clientAddr{};
+
+ static CliMessage Error(InetAddress addr, std::string msg, std::string node = "")
+ {
+ CliMessage m{};
+ m.type = Type::ERROR;
+ m.value = std::move(msg);
+ m.nodeName = std::move(node);
+ m.clientAddr = addr;
+ return m;
+ }
+
+ static CliMessage Result(InetAddress addr, std::string msg, std::string node = "")
+ {
+ CliMessage m{};
+ m.type = Type::RESULT;
+ m.value = std::move(msg);
+ m.nodeName = std::move(node);
+ m.clientAddr = addr;
+ return m;
+ }
+
+ static CliMessage Echo(InetAddress addr, std::string msg)
+ {
+ CliMessage m{};
+ m.type = Type::ECHO;
+ m.value = std::move(msg);
+ m.nodeName = "";
+ m.clientAddr = addr;
+ return m;
+ }
+
+ static CliMessage Command(InetAddress addr, std::string msg, std::string node = "")
+ {
+ CliMessage m{};
+ m.type = Type::COMMAND;
+ m.value = std::move(msg);
+ m.nodeName = std::move(node);
+ m.clientAddr = addr;
+ return m;
+ }
+};
+
+class CliServer
+{
+ private:
+ Socket m_socket;
+
+ public:
+ explicit CliServer() : m_socket{Socket::CreateAndBindUdp({cons::CMD_SERVER_IP, 0})}
+ {
+ }
+
+ ~CliServer()
+ {
+ m_socket.close();
+ }
+
+ [[nodiscard]] InetAddress assignedAddress() const;
+
+ CliMessage receiveMessage();
+ void sendMessage(const CliMessage &msg);
+};
+
+struct NwCliSendResponse : NtsMessage
+{
+ InetAddress address{};
+ std::string output{};
+ bool isError{};
+
+ NwCliSendResponse(const InetAddress &address, std::string output, bool isError)
+ : NtsMessage(NtsMessageType::CLI_SEND_RESPONSE), address(address), output(std::move(output)), isError(isError)
+ {
+ }
+};
+
+class CliResponseTask : public NtsTask
+{
+ private:
+ app::CliServer *cliServer;
+
+ public:
+ explicit CliResponseTask(CliServer *cliServer) : cliServer(cliServer)
+ {
+ }
+
+ protected:
+ void onStart() override
+ {
+ }
+ void onLoop() override
+ {
+ auto *msg = take();
+ if (msg == nullptr)
+ return;
+ if (msg->msgType == NtsMessageType::CLI_SEND_RESPONSE)
+ {
+ auto *w = dynamic_cast(msg);
+ cliServer->sendMessage(w->isError ? CliMessage::Error(w->address, w->output)
+ : CliMessage::Result(w->address, w->output));
+ }
+ delete msg;
+ }
+ void onQuit() override
+ {
+ }
+};
+
+} // namespace app
diff --git a/src/app/cli_cmd.cpp b/src/app/cli_cmd.cpp
new file mode 100644
index 000000000..25d377cc6
--- /dev/null
+++ b/src/app/cli_cmd.cpp
@@ -0,0 +1,231 @@
+//
+// This file is a part of UERANSIM open source project.
+// Copyright (c) 2021 ALİ GÜNGÖR.
+//
+// The software and all associated files are licensed under GPL-3.0
+// and subject to the terms and conditions defined in LICENSE file.
+//
+
+#include "cli_cmd.hpp"
+#include