artdaq_mfextensions  v1_03_03
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::ELseverityLevel;
37 using mf::ErrorObj;
38 using mf::service::ELdestination;
39 
44 class ELUDP : public ELdestination {
45  struct Config {
46  fhicl::TableFragment<ELdestination::Config> elDestConfig;
47  fhicl::Atom<int> error_max{
48  fhicl::Name{"error_turnoff_threshold"},
49  fhicl::Comment{"Number of errors before turning off destination (default: 0, don't turn off)"}, 0};
50  fhicl::Atom<int> error_report{fhicl::Name{"error_report_backoff_factor"},
51  fhicl::Comment{"Print an error message every N errors"}, 100};
52  fhicl::Atom<std::string> host{fhicl::Name{"host"}, fhicl::Comment{"Address to send messages to"}, "227.128.12.27"};
53  fhicl::Atom<int> port{fhicl::Name{"port"}, fhicl::Comment{"Port to send messages to"}, 5140};
54  fhicl::Atom<bool> multicast_enabled{fhicl::Name{"multicast_enabled"},
55  fhicl::Comment{"Whether messages should be sent via multicast"}, false};
58  fhicl::Atom<std::string> output_address{
59  fhicl::Name{"multicast_interface_ip"},
60  fhicl::Comment{"Use this hostname for multicast output(to assign to the proper NIC)"}, "0.0.0.0"};
61  };
62  using Parameters = fhicl::WrappedTable<Config>;
63 
64  public:
69  ELUDP(Parameters const& pset);
70 
76  virtual void fillPrefix(std::ostringstream& o, const ErrorObj& e) override;
77 
83  virtual void fillUsrMsg(std::ostringstream& o, const ErrorObj& e) override;
84 
88  virtual void fillSuffix(std::ostringstream&, const ErrorObj&) override {}
89 
95  virtual void routePayload(const std::ostringstream& o, const ErrorObj& e) override;
96 
97  private:
98  void reconnect_();
99 
100  // Parameters
101  int error_report_backoff_factor_;
102  int error_max_;
103  std::string host_;
104  int port_;
105  bool multicast_enabled_;
106  std::string multicast_out_addr_;
107 
108  int message_socket_;
109  struct sockaddr_in message_addr_;
110 
111  // Other stuff
112  int consecutive_success_count_;
113  int error_count_;
114  int next_error_report_;
115  int seqNum_;
116 
117  long pid_;
118  std::string hostname_;
119  std::string hostaddr_;
120  std::string app_;
121 };
122 
123 // END DECLARATION
124 //======================================================================
125 // BEGIN IMPLEMENTATION
126 
127 //======================================================================
128 // ELUDP c'tor
129 //======================================================================
130 
131 ELUDP::ELUDP(Parameters const& pset)
132  : ELdestination(pset().elDestConfig()),
133  error_report_backoff_factor_(pset().error_report()),
134  error_max_(pset().error_max()),
135  host_(pset().host()),
136  port_(pset().port()),
137  multicast_enabled_(pset().multicast_enabled()),
138  multicast_out_addr_(pset().output_address()),
139  message_socket_(-1),
140  consecutive_success_count_(0),
141  error_count_(0),
142  next_error_report_(1),
143  seqNum_(0),
144  pid_(static_cast<long>(getpid())) {
145  // hostname
146  char hostname_c[1024];
147  hostname_ = (gethostname(hostname_c, 1023) == 0) ? hostname_c : "Unkonwn Host";
148 
149  // host ip address
150  hostent* host = nullptr;
151  host = gethostbyname(hostname_c);
152 
153  if (host != nullptr) {
154  // ip address from hostname if the entry exists in /etc/hosts
155  char* ip = inet_ntoa(*(struct in_addr*)host->h_addr);
156  hostaddr_ = ip;
157  } else {
158  // enumerate all network interfaces
159  struct ifaddrs* ifAddrStruct = nullptr;
160  struct ifaddrs* ifa = nullptr;
161  void* tmpAddrPtr = nullptr;
162 
163  if (getifaddrs(&ifAddrStruct)) {
164  // failed to get addr struct
165  hostaddr_ = "127.0.0.1";
166  } else {
167  // iterate through all interfaces
168  for (ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) {
169  if (ifa->ifa_addr->sa_family == AF_INET) {
170  // a valid IPv4 addres
171  tmpAddrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr;
172  char addressBuffer[INET_ADDRSTRLEN];
173  inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
174  hostaddr_ = addressBuffer;
175  }
176 
177  else if (ifa->ifa_addr->sa_family == AF_INET6) {
178  // a valid IPv6 address
179  tmpAddrPtr = &((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr;
180  char addressBuffer[INET6_ADDRSTRLEN];
181  inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
182  hostaddr_ = addressBuffer;
183  }
184 
185  // find first non-local address
186  if (!hostaddr_.empty() && hostaddr_.compare("127.0.0.1") && hostaddr_.compare("::1")) break;
187  }
188 
189  if (hostaddr_.empty()) // failed to find anything
190  hostaddr_ = "127.0.0.1";
191  }
192  }
193 
194 #if 0
195  // get process name from '/proc/pid/exe'
196  std::string exe;
197  std::ostringstream pid_ostr;
198  pid_ostr << "/proc/" << pid_ << "/exe";
199  exe = realpath(pid_ostr.str().c_str(), NULL);
200 
201  size_t end = exe.find('\0');
202  size_t start = exe.find_last_of('/', end);
203 
204  app_ = exe.substr(start + 1, end - start - 1);
205 #else
206  // get process name from '/proc/pid/cmdline'
207  std::stringstream ss;
208  ss << "//proc//" << pid_ << "//cmdline";
209  std::ifstream procfile{ss.str().c_str()};
210 
211  std::string procinfo;
212 
213  if (procfile.is_open()) {
214  procfile >> procinfo;
215  procfile.close();
216  }
217 
218  size_t end = procinfo.find('\0');
219  size_t start = procinfo.find_last_of('/', end);
220 
221  app_ = procinfo.substr(start + 1, end - start - 1);
222 #endif
223 }
224 
225 void ELUDP::reconnect_() {
226  message_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
227  if (message_socket_ < 0) {
228  TLOG(TLVL_ERROR) << "I failed to create the socket for sending Data messages! err=" << strerror(errno);
229  exit(1);
230  }
231  int sts = ResolveHost(host_.c_str(), port_, message_addr_);
232  if (sts == -1) {
233  TLOG(TLVL_ERROR) << "Unable to resolve Data message address, err=" << strerror(errno);
234  exit(1);
235  }
236 
237  if (multicast_out_addr_ == "0.0.0.0") {
238  multicast_out_addr_.reserve(HOST_NAME_MAX);
239  sts = gethostname(&multicast_out_addr_[0], HOST_NAME_MAX);
240  if (sts < 0) {
241  TLOG(TLVL_ERROR) << "Could not get current hostname, err=" << strerror(errno);
242  exit(1);
243  }
244  }
245 
246  if (multicast_out_addr_ != "localhost") {
247  struct in_addr addr;
248  sts = GetInterfaceForNetwork(multicast_out_addr_.c_str(), addr);
249  // sts = ResolveHost(multicast_out_addr_.c_str(), addr);
250  if (sts == -1) {
251  TLOG(TLVL_ERROR) << "Unable to resolve multicast interface address, err=" << strerror(errno);
252  exit(1);
253  }
254 
255  if (setsockopt(message_socket_, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)) == -1) {
256  TLOG(TLVL_ERROR) << "Cannot set outgoing interface, err=" << strerror(errno);
257  exit(1);
258  }
259  }
260  int yes = 1;
261  if (setsockopt(message_socket_, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
262  TLOG(TLVL_ERROR) << "Unable to enable port reuse on message socket, err=" << strerror(errno);
263  exit(1);
264  }
265  if (setsockopt(message_socket_, IPPROTO_IP, IP_MULTICAST_LOOP, &yes, sizeof(yes)) < 0) {
266  TLOG(TLVL_ERROR) << "Unable to enable multicast loopback on message socket, err=" << strerror(errno);
267  exit(1);
268  }
269  if (setsockopt(message_socket_, SOL_SOCKET, SO_BROADCAST, (void*)&yes, sizeof(int)) == -1) {
270  TLOG(TLVL_ERROR) << "Cannot set message socket to broadcast, err=" << strerror(errno);
271  exit(1);
272  }
273 }
274 
275 //======================================================================
276 // Message prefix filler ( overriddes ELdestination::fillPrefix )
277 //======================================================================
278 void ELUDP::fillPrefix(std::ostringstream& oss, const ErrorObj& msg) {
279  const auto& xid = msg.xid();
280 
281  auto id = xid.id();
282  auto module = xid.module();
283  auto app = app_;
284  std::replace(id.begin(), id.end(), '|', '!');
285  std::replace(app.begin(), app.end(), '|', '!');
286  std::replace(module.begin(), module.end(), '|', '!');
287 
288  oss << format_.timestamp(msg.timestamp()) << "|"; // timestamp
289  oss << std::to_string(++seqNum_) << "|"; // sequence number
290  oss << hostname_ << "|"; // host name
291  oss << hostaddr_ << "|"; // host address
292  oss << xid.severity().getName() << "|"; // severity
293  oss << id << "|"; // category
294  oss << app << "|"; // application
295 #if MESSAGEFACILITY_HEX_VERSION >= 0x20201 // an indication of s67
296  oss << pid_ << "|";
297  oss << mf::GetIteration() << "|"; // run/event no
298 #else
299  oss << pid_ << "|"; // process id
300  oss << mf::MessageDrop::instance()->iteration << "|"; // run/event no
301 #endif
302  oss << module << "|"; // module name
303 #if MESSAGEFACILITY_HEX_VERSION >= 0x20201
304  oss << msg.filename() << "|" << std::to_string(msg.lineNumber()) << "|";
305 #endif
306 }
307 
308 //======================================================================
309 // Message filler ( overriddes ELdestination::fillUsrMsg )
310 //======================================================================
311 void ELUDP::fillUsrMsg(std::ostringstream& oss, const ErrorObj& msg) {
312  std::ostringstream tmposs;
313  // Print the contents.
314  for (auto const& val : msg.items()) {
315  tmposs << val;
316  }
317 
318  // remove leading "\n" if present
319  const std::string& usrMsg = !tmposs.str().compare(0, 1, "\n") ? tmposs.str().erase(0, 1) : tmposs.str();
320 
321  oss << usrMsg;
322 }
323 
324 //======================================================================
325 // Message router ( overriddes ELdestination::routePayload )
326 //======================================================================
327 void ELUDP::routePayload(const std::ostringstream& oss, const ErrorObj&) {
328  if (message_socket_ == -1) reconnect_();
329  if (error_count_ < error_max_ || error_max_ == 0) {
330  char str[INET_ADDRSTRLEN];
331  inet_ntop(AF_INET, &(message_addr_.sin_addr), str, INET_ADDRSTRLEN);
332 
333  auto string = "UDPMFMESSAGE" + std::to_string(pid_) + "|" + oss.str();
334  auto sts = sendto(message_socket_, string.c_str(), string.size(), 0, (struct sockaddr*)&message_addr_,
335  sizeof(message_addr_));
336 
337  if (sts < 0) {
338  consecutive_success_count_ = 0;
339  ++error_count_;
340  if (error_count_ == next_error_report_) {
341  TLOG(TLVL_ERROR) << "Error sending message " << seqNum_ << " to " << host_ << ", errno=" << errno << " ("
342  << strerror(errno) << ")";
343  next_error_report_ *= error_report_backoff_factor_;
344  }
345  } else {
346  ++consecutive_success_count_;
347  if (consecutive_success_count_ >= 5) {
348  error_count_ = 0;
349  next_error_report_ = 1;
350  }
351  }
352  }
353 }
354 } // end namespace mfplugins
355 
356 //======================================================================
357 //
358 // makePlugin function
359 //
360 //======================================================================
361 
362 #ifndef EXTERN_C_FUNC_DECLARE_START
363 #define EXTERN_C_FUNC_DECLARE_START extern "C" {
364 #endif
365 
366 EXTERN_C_FUNC_DECLARE_START
367 auto makePlugin(const std::string&, const fhicl::ParameterSet& pset) {
368  return std::make_unique<mfplugins::ELUDP>(pset);
369 }
370 }
371 
372 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:44
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:80
virtual void routePayload(const std::ostringstream &o, const ErrorObj &e) override
Serialize a MessageFacility message to the output.
virtual void fillUsrMsg(std::ostringstream &o, const ErrorObj &e) override
Fill the &quot;User Message&quot; portion of the message.
virtual void fillPrefix(std::ostringstream &o, const ErrorObj &e) override
Fill the &quot;Prefix&quot; portion of the message.
virtual void fillSuffix(std::ostringstream &, const ErrorObj &) override
Fill the &quot;Suffix&quot; portion of the message (Unused)
Definition: UDP_mfPlugin.cc:88