From bad4ca4666ef82a3119d1b9fb6d19eeb78983ce7 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 18 Nov 2016 12:38:54 -0800 Subject: [PATCH] Add event for network interfaces change. --- include/cscore_c.h | 3 +- include/cscore_cpp.h | 4 +- java/src/edu/wpi/cscore/VideoEvent.java | 4 +- src/NetworkListener.cpp | 135 ++++++++++++++++++++++++ src/NetworkListener.h | 38 +++++++ src/Notifier.cpp | 8 ++ src/Notifier.h | 1 + src/cscore_cpp.cpp | 5 + 8 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 src/NetworkListener.cpp create mode 100644 src/NetworkListener.h diff --git a/include/cscore_c.h b/include/cscore_c.h index ab87d605b4..4ba849774c 100644 --- a/include/cscore_c.h +++ b/include/cscore_c.h @@ -121,7 +121,8 @@ enum CS_EventKind { CS_SINK_CREATED = 0x0400, CS_SINK_DESTROYED = 0x0800, CS_SINK_ENABLED = 0x1000, - CS_SINK_DISABLED = 0x2000 + CS_SINK_DISABLED = 0x2000, + CS_NETWORK_INTERFACES_CHANGED = 0x4000 }; // diff --git a/include/cscore_cpp.h b/include/cscore_cpp.h index fad70a6f9f..d4a5abf80c 100644 --- a/include/cscore_cpp.h +++ b/include/cscore_cpp.h @@ -81,10 +81,12 @@ struct RawEvent { kSinkCreated = CS_SINK_CREATED, kSinkDestroyed = CS_SINK_DESTROYED, kSinkEnabled = CS_SINK_ENABLED, - kSinkDisabled = CS_SINK_DISABLED + kSinkDisabled = CS_SINK_DISABLED, + kNetworkInterfacesChanged = CS_NETWORK_INTERFACES_CHANGED }; RawEvent() = default; + RawEvent(RawEvent::Kind kind_) : kind{kind_} {} RawEvent(llvm::StringRef name_, CS_Handle handle_, RawEvent::Kind kind_) : kind{kind_}, name{name_} { if (kind_ == kSinkCreated || kind_ == kSinkDestroyed || diff --git a/java/src/edu/wpi/cscore/VideoEvent.java b/java/src/edu/wpi/cscore/VideoEvent.java index 16617b4c5d..36f2e15809 100644 --- a/java/src/edu/wpi/cscore/VideoEvent.java +++ b/java/src/edu/wpi/cscore/VideoEvent.java @@ -24,7 +24,8 @@ public class VideoEvent { kSinkCreated(0x0400), kSinkDestroyed(0x0800), kSinkEnabled(0x1000), - kSinkDisabled(0x2000); + kSinkDisabled(0x2000), + kNetworkInterfacesChanged(0x4000); private int value; @@ -53,6 +54,7 @@ public class VideoEvent { case 0x0800: return Kind.kSinkDestroyed; case 0x1000: return Kind.kSinkEnabled; case 0x2000: return Kind.kSinkDisabled; + case 0x4000: return Kind.kNetworkInterfacesChanged; default: return Kind.kUnknown; } } diff --git a/src/NetworkListener.cpp b/src/NetworkListener.cpp new file mode 100644 index 0000000000..1f3bc2729d --- /dev/null +++ b/src/NetworkListener.cpp @@ -0,0 +1,135 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015-2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "NetworkListener.h" + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "Log.h" +#include "Notifier.h" + +using namespace cs; + +ATOMIC_STATIC_INIT(NetworkListener) + +class NetworkListener::Thread : public wpi::SafeThread { + public: + void Main(); + +#ifdef __linux__ + int m_command_fd = -1; +#endif +}; + +NetworkListener::~NetworkListener() { Stop(); } + +void NetworkListener::Start() { + auto thr = m_owner.GetThread(); + if (!thr) m_owner.Start(); +} + +void NetworkListener::Stop() { + // Wake up thread + if (auto thr = m_owner.GetThread()) { + thr->m_active = false; +#ifdef __linux__ + if (thr->m_command_fd >= 0) eventfd_write(thr->m_command_fd, 1); +#endif + } + m_owner.Stop(); +} + +void NetworkListener::Thread::Main() { +#ifdef __linux__ + // Create event socket so we can be shut down + m_command_fd = ::eventfd(0, 0); + if (m_command_fd < 0) { + ERROR("NetworkListener: could not create eventfd: " << strerror(errno)); + return; + } + + // Create netlink socket + int sd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sd < 0) { + ERROR("NetworkListener: could not create socket: " << strerror(errno)); + ::close(m_command_fd); + m_command_fd = -1; + return; + } + + // Bind to netlink socket + struct sockaddr_nl addr; + std::memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR; + if (bind(sd, reinterpret_cast(&addr), sizeof(addr)) < 0) { + ERROR("NetworkListener: could not create socket: " << strerror(errno)); + ::close(sd); + ::close(m_command_fd); + m_command_fd = -1; + return; + } + + char buf[4096]; + + while (m_active) { + // select timeout + struct timeval tv; + tv.tv_sec = 10; + tv.tv_usec = 0; + + // select on applicable read descriptors + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(m_command_fd, &readfds); + FD_SET(sd, &readfds); + int nfds = std::max(m_command_fd, sd) + 1; + + if (::select(nfds, &readfds, nullptr, nullptr, &tv) < 0) { + ERROR("NetworkListener: select(): " << strerror(errno)); + break; // XXX: is this the right thing to do here? + } + + // Double-check to see if we're shutting down + if (!m_active) break; + + if (!FD_ISSET(sd, &readfds)) continue; + + std::memset(&addr, 0, sizeof(addr)); + struct iovec iov = {buf, sizeof(buf)}; + struct msghdr msg = {&addr, sizeof(addr), &iov, 1, nullptr, 0, 0}; + int len = ::recvmsg(sd, &msg, 0); + if (len < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN) continue; + ERROR("NetworkListener: could not read netlink: " << strerror(errno)); + break; // XXX: is this the right thing to do here? + } + if (len == 0) continue; // EOF? + for (struct nlmsghdr* nh = reinterpret_cast(buf); + NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { + if (nh->nlmsg_type == NLMSG_DONE) break; + if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK || + nh->nlmsg_type == RTM_NEWADDR || nh->nlmsg_type == RTM_DELADDR) { + Notifier::GetInstance().NotifyNetworkInterfacesChanged(); + } + } + } + ::close(sd); + ::close(m_command_fd); + m_command_fd = -1; +#endif +} diff --git a/src/NetworkListener.h b/src/NetworkListener.h new file mode 100644 index 0000000000..9e0b0cea82 --- /dev/null +++ b/src/NetworkListener.h @@ -0,0 +1,38 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015-2016. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef CS_NETWORKLISTENER_H_ +#define CS_NETWORKLISTENER_H_ + +#include "support/atomic_static.h" +#include "support/SafeThread.h" + +namespace cs { + +class NetworkListener { + public: + static NetworkListener& GetInstance() { + ATOMIC_STATIC(NetworkListener, instance); + return instance; + } + ~NetworkListener(); + + void Start(); + void Stop(); + + private: + NetworkListener() = default; + + class Thread; + wpi::SafeThreadOwner m_owner; + + ATOMIC_STATIC_DECL(NetworkListener) +}; + +} // namespace cs + +#endif // CS_NETWORKLISTENER_H_ diff --git a/src/Notifier.cpp b/src/Notifier.cpp index 7933246ae7..de81fa300a 100644 --- a/src/Notifier.cpp +++ b/src/Notifier.cpp @@ -216,3 +216,11 @@ void Notifier::NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink, thr->m_notifications.emplace(std::move(event)); thr->m_cond.notify_one(); } + +void Notifier::NotifyNetworkInterfacesChanged() { + auto thr = m_owner.GetThread(); + if (!thr) return; + + thr->m_notifications.emplace(RawEvent::kNetworkInterfacesChanged); + thr->m_cond.notify_one(); +} diff --git a/src/Notifier.h b/src/Notifier.h index e8696ece7b..f0a88a8b45 100644 --- a/src/Notifier.h +++ b/src/Notifier.h @@ -52,6 +52,7 @@ class Notifier { void NotifySink(const SinkImpl& sink, CS_EventKind kind); void NotifySinkSourceChanged(llvm::StringRef name, CS_Sink sink, CS_Source source); + void NotifyNetworkInterfacesChanged(); private: Notifier(); diff --git a/src/cscore_cpp.cpp b/src/cscore_cpp.cpp index 30a8b273d7..8ad0f5e096 100644 --- a/src/cscore_cpp.cpp +++ b/src/cscore_cpp.cpp @@ -16,6 +16,7 @@ #include "llvm/SmallString.h" +#include "NetworkListener.h" #include "Notifier.h" #include "SinkImpl.h" #include "SourceImpl.h" @@ -487,6 +488,10 @@ CS_Listener AddListener(std::function callback, int eventMask, bool immediateNotify, CS_Status* status) { int uid = Notifier::GetInstance().AddListener(callback, eventMask); + if ((eventMask & CS_NETWORK_INTERFACES_CHANGED) != 0) { + // start network interface event listener + NetworkListener::GetInstance().Start(); + } if (immediateNotify) { // TODO }