Gazebo Transport

API Reference

13.6.0
Discovery.hh
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2014 Open Source Robotics Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16 */
17 
18 #ifndef GZ_TRANSPORT_DISCOVERY_HH_
19 #define GZ_TRANSPORT_DISCOVERY_HH_
20 #include <errno.h>
21 #include <string.h>
22 
23 #ifdef _WIN32
24  // For socket(), connect(), send(), and recv().
25  #include <Winsock2.h>
26  #include <Ws2def.h>
27  #include <Ws2ipdef.h>
28  #include <Ws2tcpip.h>
29  // Type used for raw data on this platform.
30  using raw_type = char;
31 #else
32  // For data types
33  #include <sys/types.h>
34  // For socket(), connect(), send(), and recv()
35  #include <sys/socket.h>
36  // For gethostbyname()
37  #include <netdb.h>
38  // For inet_addr()
39  #include <arpa/inet.h>
40  // For close()
41  #include <unistd.h>
42  // For sockaddr_in
43  #include <netinet/in.h>
44  // Type used for raw data on this platform
45  using raw_type = void;
46 #endif
47 
48 #ifdef _WIN32
49  #pragma warning(push, 0)
50 #endif
51 #ifdef _WIN32
52  #pragma warning(pop)
53  // Suppress "decorated name length exceed" warning in STL.
54  #pragma warning(disable: 4503)
55  // Suppress "depreted API warnings" in WINSOCK.
56  #pragma warning(disable: 4996)
57 #endif
58 
59 #include <gz/msgs/discovery.pb.h>
60 
61 #include <algorithm>
62 #include <condition_variable>
63 #include <limits>
64 #include <map>
65 #include <memory>
66 #include <mutex>
67 #include <string>
68 #include <thread>
69 #include <type_traits>
70 #include <utility>
71 #include <vector>
72 
73 #include <gz/msgs/Utility.hh>
74 
75 #include "gz/transport/config.hh"
76 #include "gz/transport/Export.hh"
77 #include "gz/transport/Helpers.hh"
78 #include "gz/transport/NetUtils.hh"
82 
83 namespace gz
84 {
85  namespace transport
86  {
87  // Inline bracket to help doxygen filtering.
88  inline namespace GZ_TRANSPORT_VERSION_NAMESPACE {
90  enum class DestinationType
91  {
93  UNICAST,
95  MULTICAST,
97  ALL
98  };
99 
100  //
106  bool GZ_TRANSPORT_VISIBLE pollSockets(
107  const std::vector<int> &_sockets,
108  const int _timeout);
109 
118  template<typename Pub>
119  class Discovery
120  {
127  public: Discovery(const std::string &_pUuid,
128  const std::string &_ip,
129  const int _port,
130  const bool _verbose = false)
131  : multicastGroup(_ip),
132  port(_port),
133  hostAddr(determineHost()),
134  pUuid(_pUuid),
135  silenceInterval(kDefSilenceInterval),
136  activityInterval(kDefActivityInterval),
137  heartbeatInterval(kDefHeartbeatInterval),
138  connectionCb(nullptr),
139  disconnectionCb(nullptr),
140  verbose(_verbose),
141  initialized(false),
142  numHeartbeatsUninitialized(0),
143  exit(false),
144  enabled(false)
145  {
146  std::string gzIp;
147  if (env("GZ_IP", gzIp) && !gzIp.empty())
148  {
149  this->hostInterfaces = {gzIp};
150  }
151  else
152  {
153  // Get the list of network interfaces in this host.
154  this->hostInterfaces = determineInterfaces();
155  }
156 
157 #ifdef _WIN32
158  WORD wVersionRequested;
159  WSADATA wsaData;
160 
161  // Request WinSock v2.2.
162  wVersionRequested = MAKEWORD(2, 2);
163  // Load WinSock DLL.
164  if (WSAStartup(wVersionRequested, &wsaData) != 0)
165  {
166  std::cerr << "Unable to load WinSock DLL" << std::endl;
167  return;
168  }
169 #endif
170  for (const auto &netIface : this->hostInterfaces)
171  {
172  auto succeed = this->RegisterNetIface(netIface);
173 
174  // If the IP address that we're selecting as the main IP address of
175  // the host is invalid, we change it to 127.0.0.1 .
176  // This is probably because GZ_IP is set to a wrong value.
177  if (netIface == this->hostAddr && !succeed)
178  {
179  this->RegisterNetIface("127.0.0.1");
180  std::cerr << "Did you set the environment variable GZ_IP with a "
181  << "correct IP address? " << std::endl
182  << " [" << netIface << "] seems an invalid local IP "
183  << "address." << std::endl
184  << " Using 127.0.0.1 as hostname." << std::endl;
185  this->hostAddr = "127.0.0.1";
186  }
187  }
188 
189  // Socket option: SO_REUSEADDR. This options is used only for receiving
190  // data. We can reuse the same socket for receiving multicast data from
191  // multiple interfaces. We will use the socket at position 0 for
192  // receiving data.
193  int reuseAddr = 1;
194  if (setsockopt(this->sockets.at(0), SOL_SOCKET, SO_REUSEADDR,
195  reinterpret_cast<const char *>(&reuseAddr), sizeof(reuseAddr)) != 0)
196  {
197  std::cerr << "Error setting socket option (SO_REUSEADDR)."
198  << std::endl;
199  return;
200  }
201 
202 #ifdef SO_REUSEPORT
203  // Socket option: SO_REUSEPORT. This options is used only for receiving
204  // data. We can reuse the same socket for receiving multicast data from
205  // multiple interfaces. We will use the socket at position 0 for
206  // receiving data.
207  int reusePort = 1;
208  // cppcheck-suppress ConfigurationNotChecked
209  if (setsockopt(this->sockets.at(0), SOL_SOCKET, SO_REUSEPORT,
210  reinterpret_cast<const char *>(&reusePort), sizeof(reusePort)) != 0)
211  {
212  std::cerr << "Error setting socket option (SO_REUSEPORT)."
213  << std::endl;
214  return;
215  }
216 #endif
217  // Bind the first socket to the discovery port.
218  sockaddr_in localAddr;
219  memset(&localAddr, 0, sizeof(localAddr));
220  localAddr.sin_family = AF_INET;
221  localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
222  localAddr.sin_port = htons(static_cast<u_short>(this->port));
223 
224  if (bind(this->sockets.at(0),
225  reinterpret_cast<sockaddr *>(&localAddr), sizeof(sockaddr_in)) < 0)
226  {
227  std::cerr << "Binding to a local port failed." << std::endl;
228  return;
229  }
230 
231  // Set 'mcastAddr' to the multicast discovery group.
232  memset(&this->mcastAddr, 0, sizeof(this->mcastAddr));
233  this->mcastAddr.sin_family = AF_INET;
234  this->mcastAddr.sin_addr.s_addr =
235  inet_addr(this->multicastGroup.c_str());
236  this->mcastAddr.sin_port = htons(static_cast<u_short>(this->port));
237 
239  std::string gzRelay;
240  if (env("GZ_RELAY", gzRelay) && !gzRelay.empty())
241  {
242  relays = transport::split(gzRelay, ':');
243  }
244 
245  // Register all unicast relays.
246  for (auto const &relayAddr : relays)
247  this->AddRelayAddress(relayAddr);
248 
249  if (this->verbose)
250  this->PrintCurrentState();
251  }
252 
254  public: virtual ~Discovery()
255  {
256  // Tell the service thread to terminate.
257  this->exitMutex.lock();
258  this->exit = true;
259  this->exitMutex.unlock();
260 
261  // Wait for the service threads to finish before exit.
262  if (this->threadReception.joinable())
263  this->threadReception.join();
264 
265  // Broadcast a BYE message to trigger the remote cancellation of
266  // all our advertised topics.
267  this->SendMsg(DestinationType::ALL, msgs::Discovery::BYE,
268  Publisher("", "", this->pUuid, "", AdvertiseOptions()));
269 
270  // Close sockets.
271  for (const auto &sock : this->sockets)
272  {
273 #ifdef _WIN32
274  closesocket(sock);
275  WSACleanup();
276 #else
277  close(sock);
278 #endif
279  }
280  }
281 
285  public: void Start()
286  {
287  {
288  std::lock_guard<std::mutex> lock(this->mutex);
289 
290  // The service is already running.
291  if (this->enabled)
292  return;
293 
294  this->enabled = true;
295  }
296 
297  auto now = std::chrono::steady_clock::now();
298  this->timeNextHeartbeat = now;
299  this->timeNextActivity = now;
300 
301  // Start the thread that receives discovery information.
302  this->threadReception = std::thread(&Discovery::RecvMessages, this);
303  }
304 
309  public: bool Advertise(const Pub &_publisher)
310  {
312 
313  {
314  std::lock_guard<std::mutex> lock(this->mutex);
315 
316  if (!this->enabled)
317  return false;
318 
319  // Add the addressing information (local publisher).
320  if (!this->info.AddPublisher(_publisher))
321  return false;
322 
323  cb = this->connectionCb;
324  }
325 
326  if (cb)
327  cb(_publisher);
328 
329  // Only advertise a message outside this process if the scope
330  // is not 'Process'
331  if (_publisher.Options().Scope() != Scope_t::PROCESS)
332  this->SendMsg(DestinationType::ALL, msgs::Discovery::ADVERTISE,
333  _publisher);
334 
335  return true;
336  }
337 
348  public: bool Discover(const std::string &_topic) const
349  {
351  bool found;
352  Addresses_M<Pub> addresses;
353 
354  {
355  std::lock_guard<std::mutex> lock(this->mutex);
356 
357  if (!this->enabled)
358  return false;
359 
360  cb = this->connectionCb;
361  }
362 
363  Pub pub;
364  pub.SetTopic(_topic);
365  pub.SetPUuid(this->pUuid);
366 
367  // Send a discovery request.
368  this->SendMsg(DestinationType::ALL, msgs::Discovery::SUBSCRIBE, pub);
369 
370  {
371  std::lock_guard<std::mutex> lock(this->mutex);
372  found = this->info.Publishers(_topic, addresses);
373  }
374 
375  if (found)
376  {
377  // I already have information about this topic.
378  for (const auto &proc : addresses)
379  {
380  for (const auto &node : proc.second)
381  {
382  if (cb)
383  {
384  // Execute the user's callback for a service request. Notice
385  // that we only execute one callback for preventing receive
386  // multiple service responses for a single request.
387  cb(node);
388  }
389  }
390  }
391  }
392 
393  return true;
394  }
395 
398  public: void SendSubscribersRep(const MessagePublisher &_pub) const
399  {
400  this->SendMsg(
401  DestinationType::ALL, msgs::Discovery::SUBSCRIBERS_REP, _pub);
402  }
403 
406  public: void Register(const MessagePublisher &_pub) const
407  {
408  this->SendMsg(
409  DestinationType::ALL, msgs::Discovery::NEW_CONNECTION, _pub);
410  }
411 
414  public: void Unregister(const MessagePublisher &_pub) const
415  {
416  this->SendMsg(
417  DestinationType::ALL, msgs::Discovery::END_CONNECTION, _pub);
418  }
419 
422  public: const TopicStorage<Pub> &Info() const
423  {
424  std::lock_guard<std::mutex> lock(this->mutex);
425  return this->info;
426  }
427 
432  public: bool Publishers(const std::string &_topic,
433  Addresses_M<Pub> &_publishers) const
434  {
435  std::lock_guard<std::mutex> lock(this->mutex);
436  return this->info.Publishers(_topic, _publishers);
437  }
438 
443  public: bool RemoteSubscribers(const std::string &_topic,
444  Addresses_M<Pub> &_subscribers) const
445  {
446  std::lock_guard<std::mutex> lock(this->mutex);
447  return this->remoteSubscribers.Publishers(_topic, _subscribers);
448  }
449 
457  public: bool Unadvertise(const std::string &_topic,
458  const std::string &_nUuid)
459  {
460  Pub inf;
461  {
462  std::lock_guard<std::mutex> lock(this->mutex);
463 
464  if (!this->enabled)
465  return false;
466 
467  // Don't do anything if the topic is not advertised by any of my nodes
468  if (!this->info.Publisher(_topic, this->pUuid, _nUuid, inf))
469  return true;
470 
471  // Remove the topic information.
472  this->info.DelPublisherByNode(_topic, this->pUuid, _nUuid);
473  }
474 
475  // Only unadvertise a message outside this process if the scope
476  // is not 'Process'.
477  if (inf.Options().Scope() != Scope_t::PROCESS)
478  {
479  this->SendMsg(DestinationType::ALL,
480  msgs::Discovery::UNADVERTISE, inf);
481  }
482 
483  return true;
484  }
485 
488  public: std::string HostAddr() const
489  {
490  std::lock_guard<std::mutex> lock(this->mutex);
491  return this->hostAddr;
492  }
493 
498  public: unsigned int ActivityInterval() const
499  {
500  std::lock_guard<std::mutex> lock(this->mutex);
501  return this->activityInterval;
502  }
503 
509  public: unsigned int HeartbeatInterval() const
510  {
511  std::lock_guard<std::mutex> lock(this->mutex);
512  return this->heartbeatInterval;
513  }
514 
519  public: unsigned int SilenceInterval() const
520  {
521  std::lock_guard<std::mutex> lock(this->mutex);
522  return this->silenceInterval;
523  }
524 
528  public: void SetActivityInterval(const unsigned int _ms)
529  {
530  std::lock_guard<std::mutex> lock(this->mutex);
531  this->activityInterval = _ms;
532  }
533 
537  public: void SetHeartbeatInterval(const unsigned int _ms)
538  {
539  std::lock_guard<std::mutex> lock(this->mutex);
540  this->heartbeatInterval = _ms;
541  }
542 
546  public: void SetSilenceInterval(const unsigned int _ms)
547  {
548  std::lock_guard<std::mutex> lock(this->mutex);
549  this->silenceInterval = _ms;
550  }
551 
556  public: void ConnectionsCb(const DiscoveryCallback<Pub> &_cb)
557  {
558  std::lock_guard<std::mutex> lock(this->mutex);
559  this->connectionCb = _cb;
560  }
561 
566  public: void DisconnectionsCb(const DiscoveryCallback<Pub> &_cb)
567  {
568  std::lock_guard<std::mutex> lock(this->mutex);
569  this->disconnectionCb = _cb;
570  }
571 
575  public: void RegistrationsCb(const DiscoveryCallback<Pub> &_cb)
576  {
577  std::lock_guard<std::mutex> lock(this->mutex);
578  this->registrationCb = _cb;
579  }
580 
584  public: void UnregistrationsCb(const DiscoveryCallback<Pub> &_cb)
585  {
586  std::lock_guard<std::mutex> lock(this->mutex);
587  this->unregistrationCb = _cb;
588  }
589 
593  public: void SubscribersCb(const std::function<void()> &_cb)
594  {
595  std::lock_guard<std::mutex> lock(this->mutex);
596  this->subscribersCb = _cb;
597  }
598 
600  public: void PrintCurrentState() const
601  {
602  std::lock_guard<std::mutex> lock(this->mutex);
603 
604  std::cout << "---------------" << std::endl;
605  std::cout << std::boolalpha << "Enabled: "
606  << this->enabled << std::endl;
607  std::cout << "Discovery state" << std::endl;
608  std::cout << "\tUUID: " << this->pUuid << std::endl;
609  std::cout << "Settings" << std::endl;
610  std::cout << "\tActivity: " << this->activityInterval
611  << " ms." << std::endl;
612  std::cout << "\tHeartbeat: " << this->heartbeatInterval
613  << "ms." << std::endl;
614  std::cout << "\tSilence: " << this->silenceInterval
615  << " ms." << std::endl;
616  std::cout << "Known information:" << std::endl;
617  this->info.Print();
618 
619  // Used to calculate the elapsed time.
621 
622  std::cout << "Activity" << std::endl;
623  if (this->activity.empty())
624  std::cout << "\t<empty>" << std::endl;
625  else
626  {
627  for (auto &proc : this->activity)
628  {
629  // Elapsed time since the last update from this publisher.
630  std::chrono::duration<double> elapsed = now - proc.second;
631 
632  std::cout << "\t" << proc.first << std::endl;
633  std::cout << "\t\t" << "Since: " << std::chrono::duration_cast<
634  std::chrono::milliseconds>(elapsed).count() << " ms. ago. "
635  << std::endl;
636  }
637  }
638  std::cout << "---------------" << std::endl;
639  }
640 
644  public: void TopicList(std::vector<std::string> &_topics)
645  {
646  // Request the list of subscribers. This request is only meaningful
647  // for message discovery: nothing answers it on the service
648  // discovery channel.
649  if constexpr (std::is_same_v<Pub, MessagePublisher>)
650  {
651  Publisher pub("", "", this->pUuid, "", AdvertiseOptions());
652  this->SendMsg(
653  DestinationType::ALL, msgs::Discovery::SUBSCRIBERS_REQ, pub);
654  }
655 
656  this->WaitForInit();
657  std::lock_guard<std::mutex> lock(this->mutex);
658  this->info.TopicList(_topics);
659 
660  std::vector<std::string> remoteSubs;
661  this->remoteSubscribers.TopicList(remoteSubs);
662 
663  // Add the remote subscribers
664  for (auto const &t : remoteSubs)
665  {
666  if (std::find(_topics.begin(), _topics.end(), t) == _topics.end())
667  {
668  _topics.push_back(t);
669  }
670  }
671  }
672 
675  public: void WaitForInit() const
676  {
677  std::unique_lock<std::mutex> lk(this->mutex);
678 
679  if (!this->initialized)
680  {
681  this->initializedCv.wait(lk, [this]{return this->initialized;});
682  }
683  }
684 
688  private: void UpdateActivity()
689  {
690  // The UUIDs of the processes that have expired.
692 
693  // A copy of the disconnection callback.
694  DiscoveryCallback<Pub> disconnectCb;
695 
697 
698  {
699  std::lock_guard<std::mutex> lock(this->mutex);
700 
701  if (now < this->timeNextActivity)
702  return;
703 
704  disconnectCb = this->disconnectionCb;
705 
706  for (auto it = this->activity.cbegin(); it != this->activity.cend();)
707  {
708  // Elapsed time since the last update from this publisher.
709  auto elapsed = now - it->second;
710 
711  // This publisher has expired.
712  if (std::chrono::duration_cast<std::chrono::milliseconds>
713  (elapsed).count() > this->silenceInterval)
714  {
715  // Remove all the info entries for this process UUID.
716  this->info.DelPublishersByProc(it->first);
717  this->remoteSubscribers.DelPublishersByProc(it->first);
718 
719  uuids.push_back(it->first);
720 
721  // Remove the activity entry.
722  this->activity.erase(it++);
723  }
724  else
725  ++it;
726  }
727 
728  this->timeNextActivity = std::chrono::steady_clock::now() +
729  std::chrono::milliseconds(this->activityInterval);
730  }
731 
732  if (!disconnectCb)
733  return;
734 
735  // Notify without topic information. This is useful to inform the
736  // client that a remote node is gone, even if we were not
737  // interested in its topics.
738  for (auto const &uuid : uuids)
739  {
740  Pub publisher;
741  publisher.SetPUuid(uuid);
742  disconnectCb(publisher);
743  }
744  }
745 
748  public: void AddRelayAddress(const std::string &_ip)
749  {
750  std::lock_guard<std::mutex> lock(this->mutex);
751  // Sanity check: Make sure that this IP address is not already saved.
752  for (auto const &addr : this->relayAddrs)
753  {
754  if (addr.sin_addr.s_addr == inet_addr(_ip.c_str()))
755  return;
756  }
757 
758  sockaddr_in addr;
759  memset(&addr, 0, sizeof(addr));
760  addr.sin_family = AF_INET;
761  addr.sin_addr.s_addr = inet_addr(_ip.c_str());
762  addr.sin_port = htons(static_cast<u_short>(this->port));
763 
764  this->relayAddrs.push_back(addr);
765  }
766 
767  // \brief Gets this instance's relay addresses.
768  // \return The list of relay addresses.
770  {
772 
773  std::lock_guard<std::mutex> lock(this->mutex);
774 
775  for (auto const &addr : this->relayAddrs) {
776  result.push_back(inet_ntoa(addr.sin_addr));
777  }
778 
779  return result;
780  }
781 
783  private: void UpdateHeartbeat()
784  {
786 
787  {
788  std::lock_guard<std::mutex> lock(this->mutex);
789 
790  if (now < this->timeNextHeartbeat)
791  return;
792  }
793 
794  Publisher pub("", "", this->pUuid, "", AdvertiseOptions());
795  this->SendMsg(DestinationType::ALL, msgs::Discovery::HEARTBEAT, pub);
796 
798  {
799  std::lock_guard<std::mutex> lock(this->mutex);
800 
801  // Re-advertise topics that are advertised inside this process.
802  this->info.PublishersByProc(this->pUuid, nodes);
803  }
804 
805  for (const auto &topic : nodes)
806  {
807  for (const auto &node : topic.second)
808  {
809  this->SendMsg(DestinationType::ALL,
810  msgs::Discovery::ADVERTISE, node);
811  }
812  }
813 
814  {
815  std::lock_guard<std::mutex> lock(this->mutex);
816  if (!this->initialized)
817  {
818  if (this->numHeartbeatsUninitialized == 2u)
819  {
820  // We consider discovery initialized after two heartbeat cycles.
821  this->initialized = true;
822 
823  // Notify anyone waiting for the initialization phase to finish.
824  this->initializedCv.notify_all();
825  }
826  ++this->numHeartbeatsUninitialized;
827  }
828 
829  this->timeNextHeartbeat = std::chrono::steady_clock::now() +
830  std::chrono::milliseconds(this->heartbeatInterval);
831  }
832  }
833 
843  private: int NextTimeout() const
844  {
845  auto now = std::chrono::steady_clock::now();
846  auto timeUntilNextHeartbeat = this->timeNextHeartbeat - now;
847  auto timeUntilNextActivity = this->timeNextActivity - now;
848 
849  int t = static_cast<int>(
850  std::chrono::duration_cast<std::chrono::milliseconds>
851  (std::min(timeUntilNextHeartbeat, timeUntilNextActivity)).count());
852  int t2 = std::min(t, this->kTimeout);
853  return std::max(t2, 0);
854  }
855 
857  private: void RecvMessages()
858  {
859  bool timeToExit = false;
860  while (!timeToExit)
861  {
862  // Calculate the timeout.
863  int timeout = this->NextTimeout();
864 
865  if (pollSockets(this->sockets, timeout))
866  {
867  this->RecvDiscoveryUpdate();
868 
869  if (this->verbose)
870  this->PrintCurrentState();
871  }
872 
873  this->UpdateHeartbeat();
874  this->UpdateActivity();
875 
876  // Is it time to exit?
877  {
878  std::lock_guard<std::mutex> lock(this->exitMutex);
879  if (this->exit)
880  timeToExit = true;
881  }
882  }
883  }
884 
886  private: void RecvDiscoveryUpdate()
887  {
888  char rcvStr[Discovery::kMaxRcvStr];
889  sockaddr_in clntAddr;
890  socklen_t addrLen = sizeof(clntAddr);
891 
892  int64_t received = recvfrom(this->sockets.at(0),
893  reinterpret_cast<raw_type *>(rcvStr),
894  this->kMaxRcvStr, 0,
895  reinterpret_cast<sockaddr *>(&clntAddr),
896  reinterpret_cast<socklen_t *>(&addrLen));
897  if (received > 0)
898  {
899  uint16_t len = 0;
900  memcpy(&len, &rcvStr[0], sizeof(len));
901 
902  // Gazebo Transport delimits each discovery message with a
903  // frame_delimiter that contains byte size information.
904  // A discovery message has the form:
905  //
906  // <frame_delimiter><frame_body>
907  //
908  // Gazebo Transport version < 8 sends a frame delimiter that
909  // contains the value of sizeof(frame_delimiter)
910  // + sizeof(frame_body). In other words, the frame_delimiter
911  // contains a value that represents the total size of the
912  // frame_body and frame_delimiter in bytes.
913  //
914  // Gazebo Transport version >= 8 sends a frame_delimiter
915  // that contains the value of sizeof(frame_body). In other
916  // words, the frame_delimiter contains a value that represents
917  // the total size of only the frame_body.
918  //
919  // It is possible that two incompatible versions of Gazebo
920  // Transport exist on the same network. If we receive an
921  // unexpected size, then we ignore the message.
922 
923  // If-condition for version 8+
924  if (len + sizeof(len) == static_cast<uint16_t>(received))
925  {
926  std::string srcAddr = inet_ntoa(clntAddr.sin_addr);
927  uint16_t srcPort = ntohs(clntAddr.sin_port);
928 
929  if (this->verbose)
930  {
931  std::cout << "\nReceived discovery update from "
932  << srcAddr << ": " << srcPort << std::endl;
933  }
934 
935  this->DispatchDiscoveryMsg(srcAddr, rcvStr + sizeof(len), len);
936  }
937  }
938  else if (received < 0)
939  {
940  std::cerr << "Discovery::RecvDiscoveryUpdate() recvfrom error"
941  << std::endl;
942  }
943  }
944 
949  private: void DispatchDiscoveryMsg(const std::string &_fromIp,
950  char *_msg, uint16_t _len)
951  {
952  gz::msgs::Discovery msg;
953 
954  // Parse the message, and return if parsing failed. Parsing could
955  // fail when another discovery node is publishing messages using an
956  // older (or newer) format.
957  if (!msg.ParseFromArray(_msg, _len))
958  return;
959 
960  // Discard the message if the wire protocol is different than mine.
961  if (this->Version() != msg.version())
962  return;
963 
964  std::string recvPUuid = msg.process_uuid();
965 
966  // Discard our own discovery messages.
967  if (recvPUuid == this->pUuid)
968  return;
969 
970  // Forwarding summary:
971  // - From a unicast peer -> to multicast group (with NO_RELAY flag).
972  // - From multicast group -> to unicast peers (with RELAY flag).
973 
974  // If the RELAY flag is set, this discovery message is coming via a
975  // unicast transmission. In this case, we don't process it, we just
976  // forward it to the multicast group, and it will be dispatched once
977  // received there. Note that we also unset the RELAY flag and set the
978  // NO_RELAY flag, to avoid forwarding the message anymore.
979  if (msg.has_flags() && msg.flags().relay())
980  {
981  // Unset the RELAY flag in the header and set the NO_RELAY.
982  msg.mutable_flags()->set_relay(false);
983  msg.mutable_flags()->set_no_relay(true);
984  this->SendMulticast(msg);
985 
986  // A unicast peer contacted me. I need to save its address for
987  // sending future messages in the future.
988  this->AddRelayAddress(_fromIp);
989  return;
990  }
991  // If we are receiving this discovery message via the multicast channel
992  // and the NO_RELAY flag is not set, we forward this message via unicast
993  // to all our relays. Note that this is the most common case, where we
994  // receive a regular multicast message and we forward it to any remote
995  // relays.
996  else if (!msg.has_flags() || !msg.flags().no_relay())
997  {
998  msg.mutable_flags()->set_relay(true);
999  this->SendUnicast(msg);
1000  }
1001 
1002  bool isSenderLocal = (std::find(this->hostInterfaces.begin(),
1003  this->hostInterfaces.end(), _fromIp) != this->hostInterfaces.end()) ||
1004  (_fromIp.find("127.") == 0);
1005 
1006  // Update timestamp and cache the callbacks.
1007  DiscoveryCallback<Pub> connectCb;
1008  DiscoveryCallback<Pub> disconnectCb;
1009  DiscoveryCallback<Pub> registerCb;
1010  DiscoveryCallback<Pub> unregisterCb;
1011  std::function<void()> subscribersReqCb;
1012  {
1013  std::lock_guard<std::mutex> lock(this->mutex);
1014  this->activity[recvPUuid] = std::chrono::steady_clock::now();
1015  connectCb = this->connectionCb;
1016  disconnectCb = this->disconnectionCb;
1017  registerCb = this->registrationCb;
1018  unregisterCb = this->unregistrationCb;
1019  subscribersReqCb = this->subscribersCb;
1020  }
1021 
1022  switch (msg.type())
1023  {
1024  case msgs::Discovery::ADVERTISE:
1025  {
1026  // Read the rest of the fields.
1027  Pub publisher;
1028  publisher.SetFromDiscovery(msg);
1029 
1030  // Check scope of the topic.
1031  if ((publisher.Options().Scope() == Scope_t::PROCESS) ||
1032  (publisher.Options().Scope() == Scope_t::HOST &&
1033  !isSenderLocal))
1034  {
1035  return;
1036  }
1037 
1038  // Register an advertised address for the topic.
1039  bool added;
1040  {
1041  std::lock_guard<std::mutex> lock(this->mutex);
1042  added = this->info.AddPublisher(publisher);
1043  }
1044 
1045  if (added && connectCb)
1046  {
1047  // Execute the client's callback.
1048  connectCb(publisher);
1049  }
1050 
1051  break;
1052  }
1053  case msgs::Discovery::SUBSCRIBE:
1054  {
1055  std::string recvTopic;
1056  // Read the topic information.
1057  if (msg.has_sub())
1058  {
1059  recvTopic = msg.sub().topic();
1060  }
1061  else
1062  {
1063  std::cerr << "Subscription discovery message is missing "
1064  << "Subscriber information.\n";
1065  break;
1066  }
1067 
1068  // Check if at least one of my nodes advertises the topic requested.
1069  Addresses_M<Pub> addresses;
1070  {
1071  std::lock_guard<std::mutex> lock(this->mutex);
1072  if (!this->info.HasAnyPublishers(recvTopic, this->pUuid))
1073  {
1074  break;
1075  }
1076 
1077  if (!this->info.Publishers(recvTopic, addresses))
1078  break;
1079  }
1080 
1081  for (const auto &nodeInfo : addresses[this->pUuid])
1082  {
1083  // Check scope of the topic.
1084  if ((nodeInfo.Options().Scope() == Scope_t::PROCESS) ||
1085  (nodeInfo.Options().Scope() == Scope_t::HOST &&
1086  !isSenderLocal))
1087  {
1088  continue;
1089  }
1090 
1091  // Answer an ADVERTISE message.
1092  this->SendMsg(DestinationType::ALL,
1093  msgs::Discovery::ADVERTISE, nodeInfo);
1094  }
1095 
1096  break;
1097  }
1098  case msgs::Discovery::SUBSCRIBERS_REQ:
1099  {
1100  if (subscribersReqCb)
1101  subscribersReqCb();
1102 
1103  break;
1104  }
1105  case msgs::Discovery::SUBSCRIBERS_REP:
1106  {
1107  // Save the remote subscriber.
1108  Pub publisher;
1109  publisher.SetFromDiscovery(msg);
1110 
1111  {
1112  std::lock_guard<std::mutex> lock(this->mutex);
1113  this->remoteSubscribers.AddPublisher(publisher);
1114  }
1115  break;
1116  }
1117  case msgs::Discovery::NEW_CONNECTION:
1118  {
1119  // Read the rest of the fields.
1120  Pub publisher;
1121  publisher.SetFromDiscovery(msg);
1122 
1123  if (registerCb)
1124  registerCb(publisher);
1125 
1126  break;
1127  }
1128  case msgs::Discovery::END_CONNECTION:
1129  {
1130  // Read the rest of the fields.
1131  Pub publisher;
1132  publisher.SetFromDiscovery(msg);
1133 
1134  {
1135  std::lock_guard<std::mutex> lock(this->mutex);
1136  this->remoteSubscribers.DelPublisherByNode(
1137  publisher.Topic(), publisher.PUuid(), publisher.NUuid());
1138  }
1139 
1140  if (unregisterCb)
1141  unregisterCb(publisher);
1142 
1143  break;
1144  }
1145  case msgs::Discovery::HEARTBEAT:
1146  {
1147  // The timestamp has already been updated.
1148  break;
1149  }
1150  case msgs::Discovery::BYE:
1151  {
1152  // Remove the activity entry for this publisher.
1153  {
1154  std::lock_guard<std::mutex> lock(this->mutex);
1155  this->activity.erase(recvPUuid);
1156  }
1157 
1158  if (disconnectCb)
1159  {
1160  Pub pub;
1161  pub.SetPUuid(recvPUuid);
1162  // Notify the new disconnection.
1163  disconnectCb(pub);
1164  }
1165 
1166  // Remove the address entry for this topic.
1167  {
1168  std::lock_guard<std::mutex> lock(this->mutex);
1169  this->info.DelPublishersByProc(recvPUuid);
1170  this->remoteSubscribers.DelPublishersByProc(recvPUuid);
1171  }
1172 
1173  break;
1174  }
1175  case msgs::Discovery::UNADVERTISE:
1176  {
1177  // Read the address.
1178  Pub publisher;
1179  publisher.SetFromDiscovery(msg);
1180 
1181  // Check scope of the topic.
1182  if ((publisher.Options().Scope() == Scope_t::PROCESS) ||
1183  (publisher.Options().Scope() == Scope_t::HOST &&
1184  !isSenderLocal))
1185  {
1186  return;
1187  }
1188 
1189  if (disconnectCb)
1190  {
1191  // Notify the new disconnection.
1192  disconnectCb(publisher);
1193  }
1194 
1195  // Remove the address entry for this topic.
1196  {
1197  std::lock_guard<std::mutex> lock(this->mutex);
1198  this->info.DelPublisherByNode(publisher.Topic(),
1199  publisher.PUuid(), publisher.NUuid());
1200  }
1201 
1202  break;
1203  }
1204  default:
1205  {
1206  std::cerr << "Unknown message type [" << msg.type() << "].\n";
1207  break;
1208  }
1209  }
1210  }
1211 
1218  private: template<typename T>
1219  void SendMsg(const DestinationType &_destType,
1220  const msgs::Discovery::Type _type,
1221  const T &_pub) const
1222  {
1223  gz::msgs::Discovery discoveryMsg;
1224  discoveryMsg.set_version(this->Version());
1225  discoveryMsg.set_type(_type);
1226  discoveryMsg.set_process_uuid(this->pUuid);
1227  _pub.FillDiscovery(discoveryMsg);
1228 
1229  switch (_type)
1230  {
1231  case msgs::Discovery::ADVERTISE:
1232  case msgs::Discovery::UNADVERTISE:
1233  case msgs::Discovery::NEW_CONNECTION:
1234  case msgs::Discovery::END_CONNECTION:
1235  {
1236  _pub.FillDiscovery(discoveryMsg);
1237  break;
1238  }
1239  case msgs::Discovery::SUBSCRIBE:
1240  {
1241  discoveryMsg.mutable_sub()->set_topic(_pub.Topic());
1242  break;
1243  }
1244  case msgs::Discovery::HEARTBEAT:
1245  case msgs::Discovery::BYE:
1246  case msgs::Discovery::SUBSCRIBERS_REQ:
1247  case msgs::Discovery::SUBSCRIBERS_REP:
1248  break;
1249  default:
1250  std::cerr << "Discovery::SendMsg() error: Unrecognized message"
1251  << " type [" << _type << "]" << std::endl;
1252  return;
1253  }
1254 
1255  if (_destType == DestinationType::MULTICAST ||
1256  _destType == DestinationType::ALL)
1257  {
1258  this->SendMulticast(discoveryMsg);
1259  }
1260 
1261  // Send the discovery message to the unicast relays.
1262  if (_destType == DestinationType::UNICAST ||
1263  _destType == DestinationType::ALL)
1264  {
1265  // Set the RELAY flag in the header.
1266  discoveryMsg.mutable_flags()->set_relay(true);
1267  this->SendUnicast(discoveryMsg);
1268  }
1269 
1270  if (this->verbose)
1271  {
1272  std::cout << "\t* Sending " << msgs::ToString(_type)
1273  << " msg [" << _pub.Topic() << "]" << std::endl;
1274  }
1275  }
1276 
1279  private: void SendUnicast(const msgs::Discovery &_msg) const
1280  {
1281  uint16_t msgSize;
1282 
1283 #if GOOGLE_PROTOBUF_VERSION >= 3004000
1284  size_t msgSizeFull = _msg.ByteSizeLong();
1285 #else
1286  int msgSizeFull = _msg.ByteSize();
1287 #endif
1288  if (msgSizeFull + sizeof(msgSize) > this->kMaxRcvStr)
1289  {
1290  std::cerr << "Discovery message too large to send. Discovery won't "
1291  << "work. This shouldn't happen.\n";
1292  return;
1293  }
1294  msgSize = msgSizeFull;
1295 
1296  uint16_t totalSize = sizeof(msgSize) + msgSize;
1297  char *buffer = static_cast<char *>(new char[totalSize]);
1298  memcpy(&buffer[0], &msgSize, sizeof(msgSize));
1299 
1300  if (_msg.SerializeToArray(buffer + sizeof(msgSize), msgSize))
1301  {
1302  // Send the discovery message to the unicast relays.
1303  std::lock_guard<std::mutex> lock(this->mutex);
1304 
1305  for (const auto &sockAddr : this->relayAddrs)
1306  {
1307  errno = 0;
1308  auto sent = sendto(this->sockets.at(0),
1309  reinterpret_cast<const raw_type *>(
1310  reinterpret_cast<const unsigned char*>(buffer)),
1311  totalSize, 0,
1312  reinterpret_cast<const sockaddr *>(&sockAddr),
1313  sizeof(sockAddr));
1314 
1315  if (sent != totalSize)
1316  {
1317  std::cerr << "Exception sending a unicast message:" << std::endl;
1318  std::cerr << " Return value: " << sent << std::endl;
1319  std::cerr << " Error code: " << strerror(errno) << std::endl;
1320  break;
1321  }
1322  }
1323  }
1324  else
1325  {
1326  std::cerr << "Discovery::SendUnicast: Error serializing data."
1327  << std::endl;
1328  }
1329 
1330  delete [] buffer;
1331  }
1332 
1335  private: void SendMulticast(const msgs::Discovery &_msg) const
1336  {
1337  uint16_t msgSize;
1338 
1339 #if GOOGLE_PROTOBUF_VERSION >= 3004000
1340  size_t msgSizeFull = _msg.ByteSizeLong();
1341 #else
1342  int msgSizeFull = _msg.ByteSize();
1343 #endif
1344  if (msgSizeFull + sizeof(msgSize) > this->kMaxRcvStr)
1345  {
1346  std::cerr << "Discovery message too large to send. Discovery won't "
1347  << "work. This shouldn't happen.\n";
1348  return;
1349  }
1350 
1351  msgSize = msgSizeFull;
1352  uint16_t totalSize = sizeof(msgSize) + msgSize;
1353  char *buffer = static_cast<char *>(new char[totalSize]);
1354  memcpy(&buffer[0], &msgSize, sizeof(msgSize));
1355 
1356  if (_msg.SerializeToArray(buffer + sizeof(msgSize), msgSize))
1357  {
1358  // Send the discovery message to the multicast group through all the
1359  // sockets.
1360  for (const auto &sock : this->Sockets())
1361  {
1362  errno = 0;
1363  if (sendto(sock, reinterpret_cast<const raw_type *>(
1364  reinterpret_cast<const unsigned char*>(buffer)),
1365  totalSize, 0,
1366  reinterpret_cast<const sockaddr *>(this->MulticastAddr()),
1367  sizeof(*(this->MulticastAddr()))) != totalSize)
1368  {
1369  // Ignore EPERM and ENOBUFS errors.
1370  //
1371  // See issue #106
1372  //
1373  // Rationale drawn from:
1374  //
1375  // * https://groups.google.com/forum/#!topic/comp.protocols.tcp-ip/Qou9Sfgr77E
1376  // * https://stackoverflow.com/questions/16555101/sendto-dgrams-do-not-block-for-enobufs-on-osx
1377  if (errno != EPERM && errno != ENOBUFS)
1378  {
1379  std::cerr << "Exception sending a multicast message:"
1380  << strerror(errno) << std::endl;
1381  }
1382  break;
1383  }
1384  }
1385  }
1386  else
1387  {
1388  std::cerr << "Discovery::SendMulticast: Error serializing data."
1389  << std::endl;
1390  }
1391 
1392  delete [] buffer;
1393  }
1394 
1397  private: const std::vector<int> &Sockets() const
1398  {
1399  return this->sockets;
1400  }
1401 
1404  private: const sockaddr_in *MulticastAddr() const
1405  {
1406  return &this->mcastAddr;
1407  }
1408 
1411  private: uint8_t Version() const
1412  {
1413  static std::string gzStats;
1414  static int topicStats;
1415 
1416  if (env("GZ_TRANSPORT_TOPIC_STATISTICS", gzStats) && !gzStats.empty())
1417  {
1418  topicStats = (gzStats == "1");
1419  }
1420 
1421  return this->kWireVersion + (topicStats * 100);
1422  }
1423 
1428  private: bool RegisterNetIface(const std::string &_ip)
1429  {
1430  // Make a new socket for sending discovery information.
1431  int sock = static_cast<int>(socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP));
1432  if (sock < 0)
1433  {
1434  std::cerr << "Socket creation failed." << std::endl;
1435  return false;
1436  }
1437 
1438  // Socket option: IP_MULTICAST_IF.
1439  // This socket option needs to be applied to each socket used to send
1440  // data. This option selects the source interface for outgoing messages.
1441  struct in_addr ifAddr;
1442  ifAddr.s_addr = inet_addr(_ip.c_str());
1443  if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF,
1444  reinterpret_cast<const char*>(&ifAddr), sizeof(ifAddr)) != 0)
1445  {
1446  std::cerr << "Error setting socket option (IP_MULTICAST_IF)."
1447  << std::endl;
1448  return false;
1449  }
1450 
1451  this->sockets.push_back(sock);
1452 
1453  // Join the multicast group. We have to do it for each network interface
1454  // but we can do it on the same socket. We will use the socket at
1455  // position 0 for receiving multicast information.
1456  struct ip_mreq group;
1457  group.imr_multiaddr.s_addr =
1458  inet_addr(this->multicastGroup.c_str());
1459  group.imr_interface.s_addr = inet_addr(_ip.c_str());
1460  if (setsockopt(this->sockets.at(0), IPPROTO_IP, IP_ADD_MEMBERSHIP,
1461  reinterpret_cast<const char*>(&group), sizeof(group)) != 0)
1462  {
1463  std::cerr << "Error setting socket option (IP_ADD_MEMBERSHIP)."
1464  << std::endl;
1465  return false;
1466  }
1467 
1468  return true;
1469  }
1470 
1474  private: static const unsigned int kDefActivityInterval = 100;
1475 
1479  private: static const unsigned int kDefHeartbeatInterval = 1000;
1480 
1484  private: static const unsigned int kDefSilenceInterval = 3000;
1485 
1487  private: std::string multicastGroup;
1488 
1490  private: const int kTimeout = 250;
1491 
1493  private: static const uint16_t kMaxRcvStr =
1495 
1498  private: static const uint8_t kWireVersion = 10;
1499 
1501  private: int port;
1502 
1504  private: std::string hostAddr;
1505 
1507  private: std::vector<std::string> hostInterfaces;
1508 
1510  private: std::string pUuid;
1511 
1515  private: unsigned int silenceInterval;
1516 
1520  private: unsigned int activityInterval;
1521 
1525  private: unsigned int heartbeatInterval;
1526 
1528  private: DiscoveryCallback<Pub> connectionCb;
1529 
1531  private: DiscoveryCallback<Pub> disconnectionCb;
1532 
1534  private: DiscoveryCallback<Pub> registrationCb;
1535 
1537  private: DiscoveryCallback<Pub> unregistrationCb;
1538 
1540  private: std::function<void()> subscribersCb;
1541 
1543  private: TopicStorage<Pub> info;
1544 
1546  private: TopicStorage<Pub> remoteSubscribers;
1547 
1553 
1555  private: bool verbose;
1556 
1558  private: std::vector<int> sockets;
1559 
1561  private: sockaddr_in mcastAddr;
1562 
1564  private: std::vector<sockaddr_in> relayAddrs;
1565 
1567  private: mutable std::mutex mutex;
1568 
1570  private: std::thread threadReception;
1571 
1573  private: Timestamp timeNextHeartbeat;
1574 
1576  private: Timestamp timeNextActivity;
1577 
1579  private: std::mutex exitMutex;
1580 
1585  private: bool initialized;
1586 
1588  private: unsigned int numHeartbeatsUninitialized;
1589 
1591  private: mutable std::condition_variable initializedCv;
1592 
1594  private: bool exit;
1595 
1597  private: bool enabled;
1598  };
1599 
1603 
1607  }
1608  }
1609 }
1610 
1611 #endif