artdaq_mfextensions  v1_05_00
UDP_mfPlugin.cc
1 #include "cetlib/PluginTypeDeducer.h"
2 #include "fhiclcpp/ParameterSet.h"
3 
4 #include "messagefacility/MessageService/ELdestination.h"
5 #include "messagefacility/Utilities/ELseverityLevel.h"
6 #if MESSAGEFACILITY_HEX_VERSION < 0x20201 // v2_02_01 is s67
7 #include "messagefacility/MessageService/MessageDrop.h"
8 #else
9 #include "messagefacility/MessageLogger/MessageLogger.h"
10 #endif
11 #include "cetlib/compiler_macros.h"
12 #include "messagefacility/Utilities/exception.h"
13 
14 // C/C++ includes
15 #include <arpa/inet.h>
16 #include <ifaddrs.h>
17 #include <netdb.h>
18 #include <netinet/in.h>
19 #include <algorithm>
20 #include <fstream>
21 #include <iostream>
22 #include <memory>
24 
25 #define TRACE_NAME "UDP_mfPlugin"
26 #include "trace.h"
27 
28 // Boost includes
29 #include <boost/algorithm/string.hpp>
30 
31 #if MESSAGEFACILITY_HEX_VERSION < 0x20201 // format changed to format_ for s67
32 #define format_ format
33 #endif
34 
35 namespace mfplugins {
36 using mf::ErrorObj;
37 using mf::service::ELdestination;
38 
43 class ELUDP : public ELdestination
44 {
45 public:
49  struct Config
50  {
52  fhicl::TableFragment<ELdestination::Config> elDestConfig;
55  fhicl::Atom<int> error_max = fhicl::Atom<int>{
56  fhicl::Name{"error_turnoff_threshold"},
57  fhicl::Comment{"Number of errors before turning off destination (default: 0, don't turn off)"}, 0};
59  fhicl::Atom<int> error_report = fhicl::Atom<int>{fhicl::Name{"error_report_backoff_factor"},
60  fhicl::Comment{"Print an error message every N errors"}, 100};
62  fhicl::Atom<std::string> host =
63  fhicl::Atom<std::string>{fhicl::Name{"host"}, fhicl::Comment{"Address to send messages to"}, "227.128.12.27"};
65  fhicl::Atom<int> port = fhicl::Atom<int>{fhicl::Name{"port"}, fhicl::Comment{"Port to send messages to"}, 5140};
67  fhicl::Atom<bool> multicast_enabled = fhicl::Atom<bool>{
68  fhicl::Name{"multicast_enabled"}, fhicl::Comment{"Whether messages should be sent via multicast"}, false};
71  fhicl::Atom<std::string> output_address = fhicl::Atom<std::string>{
72  fhicl::Name{"multicast_interface_ip"},
73  fhicl::Comment{"Use this hostname for multicast output(to assign to the proper NIC)"}, "0.0.0.0"};
74  };
76  using Parameters = fhicl::WrappedTable<Config>;
77 
78 public:
83  ELUDP(Parameters const& pset);
84 
90  void fillPrefix(std::ostringstream& o, const ErrorObj& msg) override;
91 
97  void fillUsrMsg(std::ostringstream& o, const ErrorObj& msg) override;
98 
102  void fillSuffix(std::ostringstream& /*unused*/, const ErrorObj& /*msg*/) override {}
103 
109  void routePayload(const std::ostringstream& o, const ErrorObj& e) override;
110 
111 private:
112  void reconnect_();
113 
114  // Parameters
115  int error_report_backoff_factor_;
116  int error_max_;
117  std::string host_;
118  int port_;
119  bool multicast_enabled_;
120  std::string multicast_out_addr_;
121 
122  int message_socket_;
123  struct sockaddr_in message_addr_;
124 
125  // Other stuff
126  int consecutive_success_count_;
127  int error_count_;
128  int next_error_report_;
129  int seqNum_;
130 
131  int64_t pid_;
132  std::string hostname_;
133  std::string hostaddr_;
134  std::string app_;
135 };
136 
137 // END DECLARATION
138 //======================================================================
139 // BEGIN IMPLEMENTATION
140 
141 //======================================================================
142 // ELUDP c'tor
143 //======================================================================
144 
146  : ELdestination(pset().elDestConfig()), error_report_backoff_factor_(pset().error_report()), error_max_(pset().error_max()), host_(pset().host()), port_(pset().port()), multicast_enabled_(pset().multicast_enabled()), multicast_out_addr_(pset().output_address()), message_socket_(-1), consecutive_success_count_(0), error_count_(0), next_error_report_(1), seqNum_(0), pid_(static_cast<int64_t>(getpid()))
147 {
148  // hostname
149  char hostname_c[1024];
150  hostname_ = (gethostname(hostname_c, 1023) == 0) ? hostname_c : "Unkonwn Host";
151 
152  // host ip address
153  hostent* host = nullptr;
154  host = gethostbyname(hostname_c);
155 
156  if (host != nullptr)
157  {
158  // ip address from hostname if the entry exists in /etc/hosts
159  char* ip = inet_ntoa(*reinterpret_cast<struct in_addr*>(host->h_addr)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-bounds-pointer-arithmetic)
160  hostaddr_ = ip;
161  }
162  else
163  {
164  // enumerate all network interfaces
165  struct ifaddrs* ifAddrStruct = nullptr;
166  struct ifaddrs* ifa = nullptr;
167  void* tmpAddrPtr = nullptr;
168 
169  if (getifaddrs(&ifAddrStruct) != 0)
170  {
171  // failed to get addr struct
172  hostaddr_ = "127.0.0.1";
173  }
174  else
175  {
176  // iterate through all interfaces
177  for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next)
178  {
179  if (ifa->ifa_addr->sa_family == AF_INET)
180  {
181  // a valid IPv4 addres
182  tmpAddrPtr = &(reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr)->sin_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
183  char addressBuffer[INET_ADDRSTRLEN];
184  inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
185  hostaddr_ = addressBuffer;
186  }
187 
188  else if (ifa->ifa_addr->sa_family == AF_INET6)
189  {
190  // a valid IPv6 address
191  tmpAddrPtr = &(reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr)->sin6_addr); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
192  char addressBuffer[INET6_ADDRSTRLEN];
193  inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
194  hostaddr_ = addressBuffer;
195  }
196 
197  // find first non-local address
198  if (!hostaddr_.empty() && (hostaddr_ != "127.0.0.1") && (hostaddr_ != "::1"))
199  {
200  break;
201  }
202  }
203 
204  if (hostaddr_.empty())
205  { // failed to find anything
206  hostaddr_ = "127.0.0.1";
207  }
208  }
209  }
210 
211 #if 0
212  // get process name from '/proc/pid/exe'
213  std::string exe;
214  std::ostringstream pid_ostr;
215  pid_ostr << "/proc/" << pid_ << "/exe";
216  exe = realpath(pid_ostr.str().c_str(), NULL);
217 
218  size_t end = exe.find('\0');
219  size_t start = exe.find_last_of('/', end);
220 
221  app_ = exe.substr(start + 1, end - start - 1);
222 #else
223  // get process name from '/proc/pid/cmdline'
224  std::stringstream ss;
225  ss << "//proc//" << pid_ << "//cmdline";
226  std::ifstream procfile{ss.str().c_str()};
227 
228  std::string procinfo;
229 
230  if (procfile.is_open())
231  {
232  procfile >> procinfo;
233  procfile.close();
234  }
235 
236  size_t end = procinfo.find('\0');
237  size_t start = procinfo.find_last_of('/', end);
238 
239  app_ = procinfo.substr(start + 1, end - start - 1);
240 #endif
241 }
242 
243 void ELUDP::reconnect_()
244 {
245  message_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
246  if (message_socket_ < 0)
247  {
248  TLOG(TLVL_ERROR) << "I failed to create the socket for sending Data messages! err=" << strerror(errno);
249  exit(1);
250  }
251  int sts = ResolveHost(host_.c_str(), port_, message_addr_);
252  if (sts == -1)
253  {
254  TLOG(TLVL_ERROR) << "Unable to resolve Data message address, err=" << strerror(errno);
255  exit(1);
256  }
257 
258  if (multicast_out_addr_ == "0.0.0.0")
259  {
260  multicast_out_addr_.reserve(HOST_NAME_MAX);
261  sts = gethostname(&multicast_out_addr_[0], HOST_NAME_MAX);
262  if (sts < 0)
263  {
264  TLOG(TLVL_ERROR) << "Could not get current hostname, err=" << strerror(errno);
265  exit(1);
266  }
267  }
268 
269  if (multicast_out_addr_ != "localhost")
270  {
271  struct in_addr addr;
272  sts = GetInterfaceForNetwork(multicast_out_addr_.c_str(), addr);
273  // sts = ResolveHost(multicast_out_addr_.c_str(), addr);
274  if (sts == -1)
275  {
276  TLOG(TLVL_ERROR) << "Unable to resolve multicast interface address, err=" << strerror(errno);
277  exit(1);
278  }
279 
280  if (setsockopt(message_socket_, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)) == -1)
281  {
282  TLOG(TLVL_ERROR) << "Cannot set outgoing interface, err=" << strerror(errno);
283  exit(1);
284  }
285  }
286  int yes = 1;
287  if (setsockopt(message_socket_, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
288  {
289  TLOG(TLVL_ERROR) << "Unable to enable port reuse on message socket, err=" << strerror(errno);
290  exit(1);
291  }
292  if (setsockopt(message_socket_, IPPROTO_IP, IP_MULTICAST_LOOP, &yes, sizeof(yes)) < 0)
293  {
294  TLOG(TLVL_ERROR) << "Unable to enable multicast loopback on message socket, err=" << strerror(errno);
295  exit(1);
296  }
297  if (setsockopt(message_socket_, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(yes)) == -1)
298  {
299  TLOG(TLVL_ERROR) << "Cannot set message socket to broadcast, err=" << strerror(errno);
300  exit(1);
301  }
302 }
303 
304 //======================================================================
305 // Message prefix filler ( overriddes ELdestination::fillPrefix )
306 //======================================================================
307 void ELUDP::fillPrefix(std::ostringstream& oss, const ErrorObj& msg)
308 {
309  const auto& xid = msg.xid();
310 
311  auto id = xid.id();
312  auto module = xid.module();
313  auto app = app_;
314  std::replace(id.begin(), id.end(), '|', '!');
315  std::replace(app.begin(), app.end(), '|', '!');
316  std::replace(module.begin(), module.end(), '|', '!');
317 
318  oss << format_.timestamp(msg.timestamp()) << "|"; // timestamp
319  oss << std::to_string(++seqNum_) << "|"; // sequence number
320  oss << hostname_ << "|"; // host name
321  oss << hostaddr_ << "|"; // host address
322  oss << xid.severity().getName() << "|"; // severity
323  oss << id << "|"; // category
324  oss << app << "|"; // application
325 #if MESSAGEFACILITY_HEX_VERSION >= 0x20201 // an indication of s67
326  oss << pid_ << "|";
327  oss << mf::GetIteration() << "|"; // run/event no
328 #else
329  oss << pid_ << "|"; // process id
330  oss << mf::MessageDrop::instance()->iteration << "|"; // run/event no
331 #endif
332  oss << module << "|"; // module name
333 #if MESSAGEFACILITY_HEX_VERSION >= 0x20201
334  oss << msg.filename() << "|" << std::to_string(msg.lineNumber()) << "|";
335 #endif
336 }
337 
338 //======================================================================
339 // Message filler ( overriddes ELdestination::fillUsrMsg )
340 //======================================================================
341 void ELUDP::fillUsrMsg(std::ostringstream& oss, const ErrorObj& msg)
342 {
343  std::ostringstream tmposs;
344  // Print the contents.
345  for (auto const& val : msg.items())
346  {
347  tmposs << val;
348  }
349 
350  // remove leading "\n" if present
351  const std::string& usrMsg = tmposs.str().compare(0, 1, "\n") == 0 ? tmposs.str().erase(0, 1) : tmposs.str();
352 
353  oss << usrMsg;
354 }
355 
356 //======================================================================
357 // Message router ( overriddes ELdestination::routePayload )
358 //======================================================================
359 void ELUDP::routePayload(const std::ostringstream& oss, const ErrorObj& /*msg*/)
360 {
361  if (message_socket_ == -1)
362  {
363  reconnect_();
364  }
365  if (error_count_ < error_max_ || error_max_ == 0)
366  {
367  char str[INET_ADDRSTRLEN];
368  inet_ntop(AF_INET, &(message_addr_.sin_addr), str, INET_ADDRSTRLEN);
369 
370  auto string = "UDPMFMESSAGE" + std::to_string(pid_) + "|" + oss.str();
371  auto sts = sendto(message_socket_, string.c_str(), string.size(), 0, reinterpret_cast<struct sockaddr*>(&message_addr_), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
372  sizeof(message_addr_));
373 
374  if (sts < 0)
375  {
376  consecutive_success_count_ = 0;
377  ++error_count_;
378  if (error_count_ == next_error_report_)
379  {
380  TLOG(TLVL_ERROR) << "Error sending message " << seqNum_ << " to " << host_ << ", errno=" << errno << " ("
381  << strerror(errno) << ")";
382  next_error_report_ *= error_report_backoff_factor_;
383  }
384  }
385  else
386  {
387  ++consecutive_success_count_;
388  if (consecutive_success_count_ >= 5)
389  {
390  error_count_ = 0;
391  next_error_report_ = 1;
392  }
393  }
394  }
395 }
396 } // end namespace mfplugins
397 
398 //======================================================================
399 //
400 // makePlugin function
401 //
402 //======================================================================
403 
404 #ifndef EXTERN_C_FUNC_DECLARE_START
405 #define EXTERN_C_FUNC_DECLARE_START extern "C" {
406 #endif
407 
408 EXTERN_C_FUNC_DECLARE_START
409 auto makePlugin(const std::string& /*unused*/, const fhicl::ParameterSet& pset)
410 {
411  return std::make_unique<mfplugins::ELUDP>(pset);
412 }
413 }
414 
415 DEFINE_BASIC_PLUGINTYPE_FUNC(mf::service::ELdestination)
ELUDP(Parameters const &pset)
ELUDP Constructor
int ResolveHost(char const *host_in, in_addr &addr)
Convert a string hostname to a in_addr suitable for socket communication.
Definition: TCPConnect.hh:42
Message Facility UDP Streamer Destination Formats messages into a delimited string and sends via UDP ...
Definition: UDP_mfPlugin.cc:43
void fillPrefix(std::ostringstream &o, const ErrorObj &msg) override
Fill the &quot;Prefix&quot; portion of the message.
int GetInterfaceForNetwork(char const *host_in, in_addr &addr)
Convert an IP address to the network address of the interface sharing the subnet mask.
Definition: TCPConnect.hh:90
fhicl::Atom< int > error_report
&quot;error_report_backoff_factor&quot; (Default: 100): Print an error message every N errors ...
Definition: UDP_mfPlugin.cc:59
fhicl::Atom< int > port
&quot;port&quot; (Default: 5140): Port to send messages to
Definition: UDP_mfPlugin.cc:65
fhicl::Atom< std::string > host
&quot;host&quot; (Default: &quot;227.128.12.27&quot;): Address to send messages to
Definition: UDP_mfPlugin.cc:62
fhicl::Atom< std::string > output_address
Definition: UDP_mfPlugin.cc:71
Configuration Parameters for ELUDP.
Definition: UDP_mfPlugin.cc:49
void routePayload(const std::ostringstream &o, const ErrorObj &e) override
Serialize a MessageFacility message to the output.
fhicl::WrappedTable< Config > Parameters
Used for ParameterSet validation.
Definition: UDP_mfPlugin.cc:76
fhicl::Atom< int > error_max
Definition: UDP_mfPlugin.cc:55
fhicl::TableFragment< ELdestination::Config > elDestConfig
ELDestination common config parameters.
Definition: UDP_mfPlugin.cc:52
void fillUsrMsg(std::ostringstream &o, const ErrorObj &msg) override
Fill the &quot;User Message&quot; portion of the message.
void fillSuffix(std::ostringstream &, const ErrorObj &) override
Fill the &quot;Suffix&quot; portion of the message (Unused)
fhicl::Atom< bool > multicast_enabled
&quot;multicast_enabled&quot; (Default: false): Whether messages should be sent via multicast ...
Definition: UDP_mfPlugin.cc:67