MikroTTyk¶
Synopsis¶
>>> /log/info
>>> =message=A nice log message
>>>
<<< !done
This is an example of how one might use MikroTikApi to build a tty client to interact with MikroTik devices.
Source¶
This page contains a thorough explanation of what’s happening and why in the
source. If you don’t care just want to source it’s available in the
example/tty subdirectory in the repository.
Prerequisites¶
This example presumes that you have the following dependencies installed and made available to whichever build system you happen to use. This could be done without most of these, but to keep unrelated code to a minimum, these dependencies are introduced.
And, of course, the MikroTikApi library.
The Code¶
CLI options¶
First we declare the command line options we want to allow the user to specify.
These are the IPv4 address to connect to, the user and the password associated
with the provided user. We use cxxopts to handle options, so this part is
not going to get more explanation: see the cxxopts GitHub page
for more information about this library.
int
main(int argc, char** argv) {
cxxopts::Options opts("mikrottyk",
"A MikroTik device tty presenting the usage of MikroTikApi.");
opts.add_options()
("u,user", "The user to log in as into the MikroTik device. [admin]",
cxxopts::value<std::string>()->default_value("admin"))
("p,password", "The password of the user to log in as into the MikroTik device. []",
cxxopts::value<std::string>()->default_value(""))
("4,ip4", "The IPv4 address of the MikroTik device. [192.168.88.1]",
cxxopts::value<std::string>()->default_value("192.168.88.1"))
("h,help", "Print this help message")
;
auto ops = opts.parse(argc, argv);
if (ops.count("help")) {
fmt::print(opts.help());
return 0;
}
std::string usr = ops["user"].as<std::string>();
std::string passwd = ops["password"].as<std::string>();
std::string ip4 = ops["ip4"].as<std::string>();
Now we have our user, password, and IPv4 address in usr, passwd,
and ip4 respectively.
Reading sentences¶
After that we need to read the sentences the user wishes to send. To follow
existing practice on the MikroTik wiki we are going to prefix read sentences
with >>> and received reply sentences with <<<.
Error handling¶
We put the whole thing in a try-catch because the library uses exceptions to signal things breaking. At the catch clause the connection is already thorn down by the destructor, so we just need to tell the user that what happened and exit:
try {
// ... see below
} catch (const std::exception& ex) {
fmt::print(ex.what());
return -1;
}
Setting up the handle¶
With this we are done with error handling, we just need the actual code to
communicate with the MikroTik device.
First we need an api_handler object. While this also presents
defaults, we already have our defaults handled by the CLI interface, so
we pass all arguments. Note that code hereafter goes in the try branch.
mikrotik::api::api_handler api(
ip4.c_str(), // this is to allow implicit construction of the actual parameter type
usr,
passwd
);
Reading user input¶
We want to run indefinitely so we now wrap everything in an infinite loop.
Use the infinite loop style of your choice; the example uses for (;;).
We also need a vector of strings to contain the words. We declare this outside
the loop because that can help us with unnecessary allocations.
Caution
This was not benchmarked and as we know unbenchmarked optimizations might as well be pessimizations but this is not production code so at the moment I can’t be bothered to benchmark it.
Now we need to read user input. A sentence is terminated by the empty string
so we are just going to leverage that knowledge and actually read until we encounter it.
Also we want a way to exit our application: this is not a immersive VR game
so it’s not fun trying to trap the users.
Since I’m a proud member of the Cult of Vi, I went with :q to allow exiting
the tty session. If you are somehow unable to use Vi commands, just use whatever
command you want.
So, to get back on topic, we read from the user until we get the empty string, or
the exit command. Then we check if we left with the exit command, and if yes, we
just break from the infinite loop and the program exits.
Putting these steps together we get this:
std::vector<std::string> words;
for (;;) {
do {
std::string inp;
fmt::print(">>> ");
std::getline(std::cin, inp);
words.push_back(inp);
} while (!(words.back().empty()
|| words.back() == ":q"));
if (words.back() == ":q")
break;
// ... read on
}
Sending sentences¶
Sending the sentence¶
Now we just need to create a sentence from the given strings and send it. This is easy:
mikrotik::api::sentence snt(words.begin(), words.end());
mikrotik::api::reply rep;
api.send(snt);
We just create a sentence and a reply, and send the sentence easy as that. The reply is going to be used in the next step.
Receiving replies¶
Receiving the reply¶
After all this we now only need two things: read the reply
and clear the vector after we are done.
The second is pretty easy. The first is also easy but requires a bit
more code:
we once again indulge in a do-while loop and read until the reply type is a
data reply, as in the first received word is !re. This is because this
way we also end the loop if we encounter a !trap or !fatal reply,
and a !re reply is always followed by something, if only a !done reply.
After reading we just print everything to the user, and we finished our job.
do {
rep = api.read();
fmt::print("<<< !{}\n", magic_enum::enum_name(rep.reply_type));
for (const auto& resp : rep.attributes) {
fmt::print("<<< {}\n", resp);
}
} while (rep.reply_type == rep.re);
words.clear();
// btw here is the end of the for (;;) loop