kea-custom-hooks
FeM custom hooks libraries for Kea DHCP
main.cpp
Go to the documentation of this file.
1#include <cassert>
2#include <chrono>
3#include <condition_variable>
4#include <iostream>
5#include <mutex>
6#include <stdexcept>
7#include <string>
8#include <system_error>
9#include <utility>
10#include <vector>
11
12extern "C" {
13#include <arpa/inet.h>
14#include <signal.h>
15}
16
17#include <boost/program_options.hpp>
18
20#include "XmlRpcServer.hpp"
23
24namespace bpo = boost::program_options;
25
26namespace
27{
28constexpr std::uint16_t DEFAULT_XMLRPC_PORT{1082};
29
30std::pair<bool, bpo::variables_map> parse_cmdline(int argc, char** argv);
31std::pair<std::string_view, int> parse_ip_port_combo(std::string_view ip_port_combo);
33aai::XmlRpcServer create_xmlrpc_server(const std::string& ip, uint16_t port);
34
35std::ostream& info()
36{
37 std::cout << "[INFO] ";
38 return std::cout;
39}
40
41std::ostream& warning()
42{
43 std::cout << "[WARN] ";
44 return std::cout;
45}
46
47std::ostream& error()
48{
49 std::cout << "[ERR] ";
50 return std::cout;
51}
52
53bool block_signals(const sigset_t& signals)
54{
55 // We haven't spawned any threads at this point
56 // NOLINTBEGIN(*-mt-unsafe)
57 int r = sigprocmask(SIG_BLOCK, &signals, nullptr);
58 if (r < 0) {
59 error() << "Failed to block signals: " << strerror(errno) << std::endl;
60 return false;
61 }
62 return true;
63 // NOLINTEND(*-mt-unsafe)
64}
65
66bool wait_for_signals(const sigset_t& signals, std::chrono::seconds timeout)
67{
68 timespec ts{timeout.count(), 0};
69 int r;
70 // NOLINTNEXTLINE(*-avoid-do-while)
71 do {
72 r = sigtimedwait(&signals, nullptr, &ts);
73 } while (r == -1 && errno == EINTR);
74 if (r == -1 && errno != EAGAIN) {
75 throw std::system_error(errno, std::system_category(), "Failed to wait for signals");
76 }
77 return r >= 0;
78}
79} // namespace
80
81int main(int argc, char** argv)
82{
83 sigset_t signal_set;
84 sigemptyset(&signal_set);
85 for (int signal : {SIGTERM, SIGINT, SIGQUIT}) {
86 sigaddset(&signal_set, signal);
87 }
88
89 if (!block_signals(signal_set)) {
90 return 1;
91 }
92
93 const auto& [cmdline_valid, cmdline_vm] = parse_cmdline(argc, argv);
94 if (!cmdline_valid) {
95 return 1;
96 }
97
98 const std::string listen_ip{
99 cmdline_vm.count("listen-ip") > 0 ? cmdline_vm["listen-ip"].as<std::string>() : ""};
100 const uint16_t listen_port{cmdline_vm["listen-port"].as<uint16_t>()};
101 const std::vector<std::string> redis_server_connstrs{
102 cmdline_vm["redis-server"].as<std::vector<std::string>>()};
103
104 std::vector<HiredisAdapter> redis_adapters;
105 redis_adapters.reserve(redis_server_connstrs.size());
106 for (const auto& connstr : redis_server_connstrs) {
107 const auto& [ip, port] = parse_ip_port_combo(connstr);
108 if (port > 0) {
109 redis_adapters.emplace_back(std::string{ip}, port);
110 } else {
111 redis_adapters.emplace_back(std::string{ip});
112 }
113 }
114
116 info() << "Connecting to redis servers\n";
117 redis.connect();
118
119 aai::ArpwatchRequestHandler request_handler{redis};
120 aai::XmlRpcServer xmlrpc_server{create_xmlrpc_server(listen_ip, listen_port)};
121 request_handler.register_method_handlers(xmlrpc_server);
122
123 info() << "Starting XML-RPC server and request handler\n";
124 xmlrpc_server.start();
125
126 while (!wait_for_signals(signal_set, std::chrono::minutes(1))) {
127 if (!redis.is_connected()) {
128 warning() << "Lost connection to Redis server. Reconnecting\n";
129 redis.disconnect();
130 redis.connect();
131 }
132 }
133
134 info() << "Stopping AdminDB ARPwatch interface\n";
135 xmlrpc_server.stop();
136 redis.disconnect();
137
138 return 0;
139}
140
141namespace
142{
143std::pair<bool, bpo::variables_map> parse_cmdline(int argc, char** argv)
144{
145 bpo::options_description options_desc{"ARPwatch XML-RPC interface for Kea DHCP servers"};
146 // clang-format off
147 options_desc.add_options()
148 ("help,h",
149 "Print this help message and exit")
150 ("listen-ip,i", bpo::value<std::string>(),
151 "IP address to listen for XML-RPC requests on")
152 ("listen-port,l", bpo::value<uint16_t>()->default_value(DEFAULT_XMLRPC_PORT),
153 "Port to listen for XML-RPC reqeuests on")
154 ("redis-server,r", bpo::value<std::vector<std::string>>(),
155 "Redis server(s) to query")
156 ;
157 // clang-format on
158
159 bpo::variables_map vm;
160 bpo::store(bpo::parse_command_line(argc, argv, options_desc), vm);
161 bpo::notify(vm);
162
163 bool retval{true};
164
165 if (vm.count("help") > 0) {
166 std::clog << options_desc << '\n';
167 // We are single-threaded at this point
168 // NOLINTNEXTLINE(*-mt-unsafe)
169 std::exit(0);
170 }
171
172 if (vm.count("redis-server") == 0) {
173 std::clog << "Missing --redis-server!\n";
174 retval = false;
175 }
176
177 return std::make_pair(retval, std::move(vm));
178}
179
180std::pair<std::string_view, int> parse_ip_port_combo(std::string_view ip_port_combo)
181{
182 const auto separator = ip_port_combo.find_last_of(':');
183
184 if (separator == std::string_view::npos) {
185 return std::make_pair(ip_port_combo, 0);
186 }
187
188 std::string_view ip = ip_port_combo.substr(0, separator);
189 std::string_view port_str = ip_port_combo.substr(separator + 1);
190
191 return std::make_pair(ip, boost::lexical_cast<int>(port_str));
192}
193
194aai::XmlRpcServer create_xmlrpc_server(const std::string& ip, uint16_t port)
195{
196 if (ip.empty()) {
197 return aai::XmlRpcServer{port};
198 }
199
200 // sockaddr expects the port in network byte order
201 port = htons(port);
202
203 struct in6_addr in6_a
204 {
205 };
206 int is_v6 = inet_pton(AF_INET6, ip.c_str(), &in6_a);
207
208 if (is_v6 != 0) {
209 const struct sockaddr_in6 sin6
210 {
211 .sin6_family = AF_INET6, .sin6_port = port, .sin6_flowinfo = 0, .sin6_addr = in6_a,
212 .sin6_scope_id = 0
213 };
214 // NOLINTNEXTLINE(*-reinterpret-cast)
215 return aai::XmlRpcServer{reinterpret_cast<const struct sockaddr*>(&sin6), sizeof(sin6)};
216 }
217
218 struct in_addr in4_a
219 {
220 };
221 int is_v4 = inet_pton(AF_INET, ip.c_str(), &in4_a);
222
223 if (is_v4 == 0) {
224 throw std::invalid_argument{"Invalid listen IP address"};
225 }
226
227 const struct sockaddr_in sin4
228 {
229 .sin_family = AF_INET, .sin_port = port, .sin_addr = in4_a, .sin_zero = {
230 0,
231 0,
232 0,
233 0,
234 0,
235 0,
236 0,
237 0
238 }
239 };
240 // NOLINTNEXTLINE(*-reinterpret-cast)
241 return aai::XmlRpcServer{reinterpret_cast<const struct sockaddr*>(&sin4), sizeof(sin4)};
242}
243} // namespace
int main(int argc, char **argv)
Definition: main.cpp:81
Wrapper around HiredisAdapter for multi-client support.
High-level handler for ARPwatch requests.
Abstraction for an XML-RPC server.
void start()
Start the XML-RPC server.
std::vector< HiredisAdapter > redis_adapters
Definition: init.cpp:27