-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introducing UDP listener. #5473
Changes from 29 commits
0509ee3
eca712d
37232e4
3c7e6f6
6389f68
bd240a5
3a973f1
6803aee
eed7d49
0bcfbb8
a1afecf
c375b54
8e49e6b
0749e32
7ae3884
4c25d18
e85347d
17ba123
cd8930e
f128673
b83ff51
e8003b2
84b6e68
a84a96d
ee78778
9bcfacf
4f8de99
bc2695a
3c1f89a
ce18742
972819a
3de381d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,7 +90,7 @@ class Dispatcher { | |
* initially listen on. | ||
*/ | ||
virtual FileEventPtr createFileEvent(int fd, FileReadyCb cb, FileTriggerType trigger, | ||
uint32_t events) PURE; | ||
uint32_t events) const PURE; | ||
|
||
/** | ||
* @return Filesystem::WatcherPtr a filesystem watcher owned by the caller. | ||
|
@@ -110,6 +110,14 @@ class Dispatcher { | |
Network::ListenerCallbacks& cb, bool bind_to_port, | ||
bool hand_off_restored_destination_connections) PURE; | ||
|
||
/** | ||
* Create a logical udp listener on a specific port. | ||
* @param socket supplies the socket to listen on. | ||
* @param cb supplies the udp listener callbacks to invoke for listener events. | ||
* @return Network::ListenerPtr a new listener that is owned by the caller. | ||
*/ | ||
virtual Network::ListenerPtr createUdpListener(Network::Socket& socket, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wondering whether we will ever want to support proxying datagrams over Unix domain sockets, in addition to over UDP. (Analogous to current Envoy support for pipes vs. TCP connections.) If so, then it might be more general to name this createDatagramListener. Or maybe UDS datagrams aren't worth supporting, in which case current name SGTM. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mpwarres Does gQUIC currently use Unix domain sockets for proxying datagrams? I vaguely recall hearing about a potential experiment with them in a QUIC talk. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that I'm aware. We have used SOCK_DGRAM + shared memory to allow GFEs (speaking QUIC) to efficiently front for other (non QUIC speaking) servers, but AIUI there the datagrams were just for coordinating shared memory use, not for carrying QUIC traffic. Not sure if @ianswett may have mentioned this in one of his talks, perhaps this one, though I don't see it in the slides. The comment I posted upthread was more hypothetical--I don't have a concrete use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a strong opinion on this, though |
||
Network::UdpListenerCallbacks& cb) PURE; | ||
/** | ||
* Allocate a timer. @see Timer for docs on how to use the timer. | ||
* @param cb supplies the callback to invoke when the timer fires. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,6 +114,59 @@ class ListenerCallbacks { | |
virtual void onNewConnection(ConnectionPtr&& new_connection) PURE; | ||
}; | ||
|
||
/** | ||
* Utility struct that encapsulates the information from a udp socket's | ||
* recvfrom/recvmmsg call. | ||
* | ||
* TODO(conqerAtapple): Maybe this belongs inside the UdpListenerCallbacks | ||
* class. | ||
*/ | ||
struct UdpData { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optional naming suggestion (feel free to disregard): UdpPacket |
||
Address::InstanceConstSharedPtr local_address_; | ||
Address::InstanceConstSharedPtr peer_address_; // TODO(conquerAtapple): Fix ownership semantics. | ||
Buffer::InstancePtr buffer_; | ||
// TODO(conquerAtapple): | ||
// Add UdpReader here so that the callback handler can | ||
// then use the reader to do multiple reads(recvmmsg) once the OS notifies it | ||
// has data. We could also just return a `ReaderFactory` that returns either a | ||
// `recvfrom` reader (with peer information) or a `read/recvmmsg` reader. This | ||
// is still being flushed out (Jan, 2019). | ||
}; | ||
|
||
/** | ||
* Udp listener callbacks. | ||
*/ | ||
class UdpListenerCallbacks { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought Matt requested on the prior PR that we try to stick to listenercallbacks where possible? I'd think the current code could be a subclass of ListenerCallbacks, where onAccept was commeted as "accepting new data" rather than "accepting a new connection", we call onNewConnection where we currently do (but with a UdpConnection subclass which may be filled with TODOs, but we pass the local address and the peer address to it) and then hand onData calls to the new connection? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment as above. The current There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my comments below. |
||
public: | ||
enum class ErrorCode { SyscallError, UnknownError }; | ||
|
||
virtual ~UdpListenerCallbacks() = default; | ||
|
||
/** | ||
* Called whenever data is received by the underlying udp socket. | ||
* | ||
* @param data UdpData from the underlying socket. | ||
*/ | ||
virtual void onData(const UdpData& data) PURE; | ||
|
||
/** | ||
* Called when the underlying socket is ready for write. | ||
* | ||
* @param socket Underlying server socket for the listener. | ||
* | ||
* TODO(conqerAtapple): Maybe we need a UdpWriter here instead of Socket. | ||
*/ | ||
virtual void onWriteReady(const Socket& socket) PURE; | ||
|
||
/** | ||
* Called when there is an error event. | ||
* | ||
* @param error_code ErrorCode for the error event. | ||
* @param error_number System error number. | ||
*/ | ||
virtual void onError(const ErrorCode& error_code, int error_number) PURE; | ||
}; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like the listener might also need to be notified when the UDP socket is writable? AIUI, in the TCP case, this interaction is a little hidden: ListenerCallbacks::onNewConnection() provides a Network::Connection, and that Connection is set up with the Dispatcher to receive events when the connection socket is readable or writable: link. The "is readable" transition is already covered by UdpListenerCallbacks::onData(), but seems like there will need to be something somewhere for the "is writable" side, to let the receipient know when it can flush/write more packets. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. So I guess we need another callback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it depends on the UdpListenerCallbacks vs. ListenerCallbacks question. If you go with the already existing ListenerCallbacks interface like @alyssawilk is suggesting, then it would make sense to reflect writability inside the Connection implementation, i.e. Event::FileReadyType::Write -> UdpConnectionImpl::onWriteReady() which could flush any buffered packets that are waiting to be written, paired with a UdpConnectionImpl::write() implementation that buffers packets when the socket is not currently writable. If you go with UdpListenerCallbacks, then yeah, I guess there would need to be something like an onWriteReady(), and it would need to supply something to write to (either the Socket, or an interface for writing a packet). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure how to reuse existing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is related to my comment above about whether we are doing connection oriented or not. IMO I'm leaning towads splitting UDP into 2 types: 1) What you have now, which is geared towards datagram, and 2) One listener type which uses normal listener callbacks, and spits out connections which then supports existing filters. Thoughts? |
||
/** | ||
* An abstract socket listener. Free the listener to stop listening on the socket. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
#include "common/network/connection_impl.h" | ||
#include "common/network/dns_impl.h" | ||
#include "common/network/listener_impl.h" | ||
#include "common/network/udp_listener_impl.h" | ||
|
||
#include "event2/event.h" | ||
|
||
|
@@ -101,7 +102,7 @@ Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( | |
} | ||
|
||
FileEventPtr DispatcherImpl::createFileEvent(int fd, FileReadyCb cb, FileTriggerType trigger, | ||
uint32_t events) { | ||
uint32_t events) const { | ||
ASSERT(isThreadSafe()); | ||
return FileEventPtr{new FileEventImpl(*this, fd, cb, trigger, events)}; | ||
} | ||
|
@@ -119,6 +120,12 @@ DispatcherImpl::createListener(Network::Socket& socket, Network::ListenerCallbac | |
hand_off_restored_destination_connections)}; | ||
} | ||
|
||
Network::ListenerPtr DispatcherImpl::createUdpListener(Network::Socket& socket, | ||
Network::UdpListenerCallbacks& cb) { | ||
ASSERT(isThreadSafe()); | ||
return Network::ListenerPtr{new Network::UdpListenerImpl(*this, socket, cb)}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
} | ||
|
||
TimerPtr DispatcherImpl::createTimer(TimerCb cb) { | ||
ASSERT(isThreadSafe()); | ||
return scheduler_->createTimer(cb); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,7 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>, public Dispatcher { | |
/** | ||
* @return event_base& the libevent base. | ||
*/ | ||
event_base& base() { return *base_; } | ||
event_base& base() const { return *base_; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you revert this const change also and a couple of the other ones you added? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will remove them but I think they should be const :) |
||
|
||
// Event::Dispatcher | ||
TimeSystem& timeSystem() override { return time_system_; } | ||
|
@@ -46,11 +46,13 @@ class DispatcherImpl : Logger::Loggable<Logger::Id::main>, public Dispatcher { | |
Network::DnsResolverSharedPtr createDnsResolver( | ||
const std::vector<Network::Address::InstanceConstSharedPtr>& resolvers) override; | ||
FileEventPtr createFileEvent(int fd, FileReadyCb cb, FileTriggerType trigger, | ||
uint32_t events) override; | ||
uint32_t events) const override; | ||
Filesystem::WatcherPtr createFilesystemWatcher() override; | ||
Network::ListenerPtr createListener(Network::Socket& socket, Network::ListenerCallbacks& cb, | ||
bool bind_to_port, | ||
bool hand_off_restored_destination_connections) override; | ||
Network::ListenerPtr createUdpListener(Network::Socket& socket, | ||
Network::UdpListenerCallbacks& cb) override; | ||
TimerPtr createTimer(TimerCb cb) override; | ||
void deferredDelete(DeferredDeletablePtr&& to_delete) override; | ||
void exit() override; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#include "common/network/base_listener_impl.h" | ||
|
||
#include <sys/un.h> | ||
|
||
#include "envoy/common/exception.h" | ||
|
||
#include "common/common/assert.h" | ||
#include "common/common/empty_string.h" | ||
#include "common/common/fmt.h" | ||
#include "common/event/dispatcher_impl.h" | ||
#include "common/event/file_event_impl.h" | ||
#include "common/network/address_impl.h" | ||
|
||
#include "event2/listener.h" | ||
|
||
namespace Envoy { | ||
namespace Network { | ||
|
||
Address::InstanceConstSharedPtr BaseListenerImpl::getLocalAddress(int fd) { | ||
return Address::addressFromFd(fd); | ||
} | ||
|
||
BaseListenerImpl::BaseListenerImpl(const Event::DispatcherImpl& dispatcher, Socket& socket) | ||
: local_address_(nullptr), dispatcher_(dispatcher), socket_(socket) { | ||
const auto ip = socket.localAddress()->ip(); | ||
|
||
// Only use the listen socket's local address for new connections if it is not the all hosts | ||
// address (e.g., 0.0.0.0 for IPv4). | ||
if (!(ip && ip->isAnyAddress())) { | ||
local_address_ = socket.localAddress(); | ||
} | ||
} | ||
|
||
} // namespace Network | ||
} // namespace Envoy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#pragma once | ||
|
||
#include "envoy/network/listener.h" | ||
|
||
#include "common/event/dispatcher_impl.h" | ||
#include "common/event/libevent.h" | ||
#include "common/network/listen_socket_impl.h" | ||
|
||
#include "event2/event.h" | ||
|
||
namespace Envoy { | ||
namespace Network { | ||
|
||
/** | ||
* Base libevent implementation of Network::Listener. | ||
*/ | ||
class BaseListenerImpl : public Listener { | ||
public: | ||
BaseListenerImpl(const Event::DispatcherImpl& dispatcher, Socket& socket); | ||
|
||
protected: | ||
virtual Address::InstanceConstSharedPtr getLocalAddress(int fd); | ||
|
||
Address::InstanceConstSharedPtr local_address_; | ||
const Event::DispatcherImpl& dispatcher_; | ||
Socket& socket_; | ||
}; | ||
|
||
} // namespace Network | ||
} // namespace Envoy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are somewhat splitting hairs here, but IMO this is not a const operation on the dispatcher, it does modify its state. Instead of changing this to a const method can you pass a non-const Dispatcher to where you need it? This is what is done elsewhere.