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