-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
quiche: implement QUIC specific TransportSocketFactory for TLS context #8091
Conversation
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
/assign @alyssawilk |
Signed-off-by: Dan Zhang <danzh@google.com>
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.
It seems the quic filter chain is quite different...
} else { | ||
transport_socket.set_name( | ||
Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); | ||
std::vector<std::string> server_names(filter_chain.filter_chain_match().server_names().begin(), |
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.
nit: move into if (!is_quic_)
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.
done
@@ -390,13 +390,15 @@ class ListenerImpl : public Network::ListenerConfig, | |||
class ListenerFilterChainFactoryBuilder : public FilterChainFactoryBuilder { | |||
public: | |||
ListenerFilterChainFactoryBuilder( | |||
ListenerImpl& listener, Configuration::TransportSocketFactoryContextImpl& factory_context); | |||
ListenerImpl& listener, Configuration::TransportSocketFactoryContextImpl& factory_context, | |||
bool is_quic); |
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.
My impression is that TCPListenerFilterChainFactoryBuilder
and QUIC one has nothing shared... Why not derived QUIC one from FilterChainFactoryBuilder?
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.
both of them access ListenerImpl and TransportSocketFactoryContextImpl. I made QuicListenerFilterChainFactoryBuilder a stand alone class deriving from ListenerFilterChainFactoryBuilder.
if (socket_type_ == Network::Address::SocketType::Datagram) { | ||
// Needed for recvmsg to return destination address in IP header. | ||
addListenSocketOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); | ||
// Needed to return receive buffer overflown indicator. | ||
addListenSocketOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); | ||
std::string listener_name = | ||
config.has_udp_listener_config() ? config.udp_listener_config().udp_listener_name() : ""; | ||
is_quic = listener_name == UdpListenerNames::get().Quic; |
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.
nit: is_quic = config.has_udp_listener_config() && config.udp_listener_config().udp_listener_name() == UdpListenerNames::get().Quic;
Also you can create const ref of udp_listener_config here and declare listener_name const
const auto & udp_config = config.has_udp_listener_config() ? config.udp_listener_config() : envoy::api::v2::listener::UdpListenerConfig();
const auto listener_name = udp_config.udp_listener_name().empty() ? udp_config.udp_listener_name() : "";
The idea is to figure out the final listener_name at one place
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.
done
// Network::FilterChain | ||
const Network::TransportSocketFactory* transportSocketFactory() const override { return nullptr; } | ||
|
||
Ssl::ContextConfig* tlsContextConfig() const { return tls_context_config_.get(); } |
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.
Where is this one used?
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.
not used yet. Will be used to create EnvoyQuicProofSource which is a fake one now.
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.
Can this return a const reference or just a reference?
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.
Yes
@danzh2010 friendly request to please update the PR description to something more descriptive. I will take a look when the review is a bit further along. Thank you! /wait |
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
/retest |
🔨 rebuilding |
I updated PR description. It's good for another around of review. |
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.
Thank you for the update!
/assign @goaway |
include/envoy/network/filter.h
Outdated
*/ | ||
virtual const TransportSocketFactory& transportSocketFactory() const PURE; |
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.
I think envoy standard might be using optional, but I'm a fan of pointers so we'll see what Matt says ;-)
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.
an optional reference wrapper is "nicer" but I'm fine either way.
@@ -26,6 +26,8 @@ void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { | |||
} else { | |||
ASSERT(socket_type == Network::Address::SocketType::Datagram, | |||
"Only datagram/stream listener supported"); | |||
RELEASE_ASSERT(config.udpListenerFactory() != nullptr, | |||
"UDP listner factory is not initialized."); |
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.
listener
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.
Can you add a TODO to make this a proper config check?
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.
This won't be a config error, we will create UdpListenerFactory anyway in ListenerImpl(). If not specified in config, we create the raw UDP listener factory.
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.
I think you can remove this assert then? It would crash in an obvious way on the next line.
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.
done
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.
I think you didn't remove it, just converted it. Would it actually fail under createActiveUdpListener or is this assert required?
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.
ListenerConfig.udpListenerFactory() is populated to default RawUdp listener if not specified in config. So this ASSERT is to check we actually have logic to do this before reaching here. Otherwise below config.udpListenerFactory()->createActiveUdpListener(*this, dispatcher_, logger_, config);
would crash because of accessing members of nullptr.
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.
Gotcha. If we plan to keep it in, let's at least change
listner -> listener
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.
done
@@ -50,7 +50,8 @@ bool ipFamilySupported(int domain) { | |||
Api::OsSysCalls& os_sys_calls = Api::OsSysCallsSingleton::get(); | |||
const Api::SysCallIntResult result = os_sys_calls.socket(domain, SOCK_STREAM, 0); | |||
if (result.rc_ >= 0) { | |||
RELEASE_ASSERT(os_sys_calls.close(result.rc_).rc_ == 0, ""); | |||
RELEASE_ASSERT(os_sys_calls.close(result.rc_).rc_ == 0, | |||
absl::StrCat("fail to close fd ", result.rc_)); |
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.
Thanks for adding details here.
maybe -> "failed to close fd: response code " ?
since right now it looks like you're printing the fd, not the error code.
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.
done
parent_.server_.random(), parent_.server_.stats(), parent_.server_.singletonManager(), | ||
parent_.server_.threadLocal(), validation_visitor, parent_.server_.api()); | ||
factory_context.setInitManager(initManager()); | ||
ListenerFilterChainFactoryBuilderPtr builder = |
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.
if I'm not lost in braces, we'll now do work for raw UDP that we didn't used to. Is that correct, and is it intentional?
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.
Yes, we are initializing network filters for raw UDP too. I think it doesn't hurt as long as they don't config any network filters but leave them unused.
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.
Flushing out a few questions/comments from a first pass. Thank you!
/wait
include/envoy/network/filter.h
Outdated
*/ | ||
virtual const TransportSocketFactory& transportSocketFactory() const PURE; |
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.
an optional reference wrapper is "nicer" but I'm fine either way.
include/envoy/ssl/context_config.h
Outdated
|
||
virtual ContextConfigPtr | ||
createSslContextConfig(const Protobuf::Message& config, | ||
Server::Configuration::TransportSocketFactoryContext& context) PURE; |
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.
It's a little strange to create a transport socket factory context when making an SSL context. Should we just define whatever context we need in this file and then have TransportSocketFactoryContext derive from that if needed?
namespace Tls { | ||
|
||
/** | ||
* A factory to create server side Tls context config from a protobuf. |
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.
nit: Tls/TLS here and elsewhere in comments.
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.
done
const Protobuf::Message& config, | ||
Server::Configuration::TransportSocketFactoryContext& context) { | ||
return std::make_unique<ServerContextConfigImpl>( | ||
dynamic_cast<const envoy::api::v2::auth::DownstreamTlsContext&>(config), context); |
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.
If we universally do a dynamic_cast here why not just pass this type as a parameter?
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.
createSslContextConfig() is public interface which will also be used on client side which should downcast message to UpstreamTlsContext.
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.
@lizan any thoughts on this one? I would need to sit down and page in all of this code but something seems off here. Maybe this is related to your other comment.
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.
at interface level this seems fine, though at higher level I think this should just be a TransportSocketConfigFactory
@@ -26,6 +26,8 @@ void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { | |||
} else { | |||
ASSERT(socket_type == Network::Address::SocketType::Datagram, | |||
"Only datagram/stream listener supported"); | |||
RELEASE_ASSERT(config.udpListenerFactory() != nullptr, | |||
"UDP listner factory is not initialized."); |
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.
Can you add a TODO to make this a proper config check?
// Network::FilterChain | ||
const Network::TransportSocketFactory* transportSocketFactory() const override { return nullptr; } | ||
|
||
Ssl::ContextConfig* tlsContextConfig() const { return tls_context_config_.get(); } |
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.
Can this return a const reference or just a reference?
.createActiveUdpListenerFactory(config.has_udp_listener_config() | ||
? config.udp_listener_config() | ||
: envoy::api::v2::listener::UdpListenerConfig()); | ||
} catch (EnvoyException ex) { |
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.
const EnvoyException&
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.
done
? config.udp_listener_config() | ||
: envoy::api::v2::listener::UdpListenerConfig()); | ||
} catch (EnvoyException ex) { | ||
// TODO(danzh): add implementation for quic_listener and do not catch |
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.
I'm confused. Why do we need/want this fallback?
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.
Just because we don't have QuicListenerFactoryConfig registered yet. Once PR #7896 gets in, this is not needed.
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.
Can we just assert this then or wait until the other one is merged?
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.
I'll wait for the other PR merge first and change this line. It is as such now to make listener mgr test pass in a meaningful way.
const ::envoy::api::v2::listener::FilterChain& filter_chain) const { | ||
// Initialize filter chain for QUIC. | ||
if (!filter_chain.has_tls_context()) { | ||
return nullptr; |
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.
Should this be a config error?
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.
Yes. Is it okay to lead to connection close? Or what's the best way of handling such error?
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 should check this during config load and fail the config, it should be an assert at this point IMO.
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.
With removal of QuicFilterChain, this builder can be unified with tcp one too. This check no longer apply.
return nullptr; | ||
} | ||
auto& tls_context_factory = | ||
Config::Utility::getAndCheckFactory<Ssl::ContextConfigFactory>("downstream_tls_context"); |
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.
"downstream_tls_context" should be a constant somewhere.
Can you talk a bit more about why we need to load the factory on this way vs. just directly instantiating it? It's a little confusing. I suppose this is due to pluggable TLS implementations?
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.
Yes, all the registered class work in this PR is just to make envoy core code not depends on TLS extensions.
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
@@ -120,6 +120,9 @@ class ActiveUdpListenerFactory { | |||
virtual ConnectionHandler::ActiveListenerPtr | |||
createActiveUdpListener(ConnectionHandler& parent, Event::Dispatcher& disptacher, | |||
spdlog::logger& logger, Network::ListenerConfig& config) const PURE; | |||
|
|||
// @return true if the protocol above UDP doesn't form a connection. |
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.
super nitty but /** .. */ style comments
Also maybe
"if the protocol above UDP doesn't form a connection." -> "If the UDP passing through listener doesn't form stateful connections" ?
might even be nicer to invert and have isStatefulConnection() but maybe that's just me.
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.
done
@@ -26,6 +26,8 @@ void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { | |||
} else { | |||
ASSERT(socket_type == Network::Address::SocketType::Datagram, | |||
"Only datagram/stream listener supported"); | |||
RELEASE_ASSERT(config.udpListenerFactory() != nullptr, | |||
"UDP listner factory is not initialized."); |
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.
I think you didn't remove it, just converted it. Would it actually fail under createActiveUdpListener or is this assert required?
Let's merge master and then we can get back to reviewing this one. /wait |
Signed-off-by: Dan Zhang <danzh@google.com>
Done. PTAL |
Signed-off-by: Dan Zhang <danzh@google.com>
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.
Looking good. just one nit left on my end.
@@ -26,6 +26,8 @@ void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { | |||
} else { | |||
ASSERT(socket_type == Network::Address::SocketType::Datagram, | |||
"Only datagram/stream listener supported"); | |||
RELEASE_ASSERT(config.udpListenerFactory() != nullptr, | |||
"UDP listner factory is not initialized."); |
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.
Gotcha. If we plan to keep it in, let's at least change
listner -> listener
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.
Thanks, LGTM with some small comments.
/wait
// server and client. | ||
class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { | ||
public: | ||
~QuicTransportSocketFactoryBase() override = default; |
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.
nit: delete
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.
done
Ssl::ServerContextConfig& serverContextConfig() const { return *config_; } | ||
|
||
private: | ||
Ssl::ServerContextConfigPtr config_; |
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.
nit: const here and below
class QuicTransportSocketConfigFactory | ||
: public virtual Server::Configuration::TransportSocketConfigFactory { | ||
public: | ||
~QuicTransportSocketConfigFactory() override = default; |
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.
nit: delete
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.
done
@@ -31,8 +31,8 @@ void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { | |||
if (socket_type == Network::Address::SocketType::Stream) { | |||
listener = std::make_unique<ActiveTcpListener>(*this, config); | |||
} else { | |||
ASSERT(socket_type == Network::Address::SocketType::Datagram, | |||
"Only datagram/stream listener supported"); | |||
RELEASE_ASSERT(config.udpListenerFactory() != nullptr, |
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.
The previous conversation was lost, but this should never happen due to being checked via config elsewhere, right? If so can we make this a normal ASSERT?
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.
Early on ListenerImpl() will always create some sort of ActiveUdpListener, if not configured, ActiveRawUdpListener will be created. So yes here ASSERT is sufficient.
} else if (udp_listener_factory_ != nullptr && | ||
!udp_listener_factory_->isTransportConnectionless()) { | ||
for (auto& filter_chain : config.filter_chains()) { | ||
// Early fail if any filter chain doesn't have transport socket configured. |
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.
I'm a little confused by this. If the underlying filter chain builder implementation requires a transport socket (like for QUIC) can't it just fail in that code? IMO that would be more clear? WDYT?
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.
filter chain builder doesn't know if this listener is UDP or TCP, nor does it know about if it's connectionless or not. For non-Quic listener, it's empty transport_socket is allowed, and in that case a factory for RawBufferSocket will be created..
source/server/well_known_names.h
Outdated
@@ -13,6 +13,7 @@ namespace Server { | |||
class UdpListenerNameValues { | |||
public: | |||
const std::string RawUdp = "raw_udp_listener"; | |||
const std::string Quic = "quic_listener"; |
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.
revert?
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.
done
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.
LGTM, modulo Matt's comments
Ping? |
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.
Let's ship and iterate! Well done. :)
envoyproxy#8091) Signed-off-by: Dan Zhang <danzh@google.com>
envoyproxy#8091) Signed-off-by: Dan Zhang <danzh@google.com>
envoyproxy#8091) Signed-off-by: Dan Zhang <danzh@google.com>
Implement TransportSocketFactory for QUIC which carries Ssl::ContextConfig. The Ssl::ContextConfig instance is created based on envoy::api::v2::auth::DownstreamTlsContext in envoy::api::v2::listener::FilterChain::transport_socket.
QuicServer|ClientTransportSocketFactory has new interface server|clientContextConfig() to provide access toTLS context in addition to inherited TransportSocketFactory interface. QuicTransportSocketFactory::createTransportSocket() shouldn't be called in current implementation because QUICHE stack already does all the transport layer work.
When listener config socket type is Datagram and udp_listener_config.udp_listener_name is "quiche_quic_listener", envoy::api::v2::listener::FilterChain::transport_socket has to be configured with envoy::api::v2::auth::DownstreamTlsContext or envoy::api::v2::auth::UpstreamTlsContext.
ListenerImpl() will create a QuicTransportSocketFactory instance as part of filter chain in the same way as creating other transport socket factories.
Risk Level: medium, a bad config can hit RELEASE_ASSERT.
Testing: added unit tests for ListenerManagerImpl
Part of #2557