Gazebo Transport

API Reference

14.3.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"
82
83namespace 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,
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>
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
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
567 {
568 std::lock_guard<std::mutex> lock(this->mutex);
569 this->disconnectionCb = _cb;
570 }
571
576 {
577 std::lock_guard<std::mutex> lock(this->mutex);
578 this->registrationCb = _cb;
579 }
580
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 {
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 {
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