in source/server/listener_manager_impl.cc [717:835]
bool ListenerManagerImpl::addOrUpdateListener(const envoy::api::v2::Listener& config,
const std::string& version_info, bool modifiable) {
std::string name;
if (!config.name().empty()) {
name = config.name();
} else {
name = server_.random().uuid();
}
const uint64_t hash = MessageUtil::hash(config);
ENVOY_LOG(debug, "begin add/update listener: name={} hash={}", name, hash);
auto existing_active_listener = getListenerByName(active_listeners_, name);
auto existing_warming_listener = getListenerByName(warming_listeners_, name);
// Do a quick blocked update check before going further. This check needs to be done against both
// warming and active.
if ((existing_warming_listener != warming_listeners_.end() &&
(*existing_warming_listener)->blockUpdate(hash)) ||
(existing_active_listener != active_listeners_.end() &&
(*existing_active_listener)->blockUpdate(hash))) {
ENVOY_LOG(debug, "duplicate/locked listener '{}'. no add/update", name);
return false;
}
ListenerImplPtr new_listener(
new ListenerImpl(config, version_info, *this, name, modifiable, workers_started_, hash));
ListenerImpl& new_listener_ref = *new_listener;
// We mandate that a listener with the same name must have the same configured address. This
// avoids confusion during updates and allows us to use the same bound address. Note that in
// the case of port 0 binding, the new listener will implicitly use the same bound port from
// the existing listener.
if ((existing_warming_listener != warming_listeners_.end() &&
*(*existing_warming_listener)->address() != *new_listener->address()) ||
(existing_active_listener != active_listeners_.end() &&
*(*existing_active_listener)->address() != *new_listener->address())) {
const std::string message = fmt::format(
"error updating listener: '{}' has a different address '{}' from existing listener", name,
new_listener->address()->asString());
ENVOY_LOG(warn, "{}", message);
throw EnvoyException(message);
}
bool added = false;
if (existing_warming_listener != warming_listeners_.end()) {
// In this case we can just replace inline.
ASSERT(workers_started_);
new_listener->debugLog("update warming listener");
new_listener->setSocket((*existing_warming_listener)->getSocket());
*existing_warming_listener = std::move(new_listener);
} else if (existing_active_listener != active_listeners_.end()) {
// In this case we have no warming listener, so what we do depends on whether workers
// have been started or not. Either way we get the socket from the existing listener.
new_listener->setSocket((*existing_active_listener)->getSocket());
if (workers_started_) {
new_listener->debugLog("add warming listener");
warming_listeners_.emplace_back(std::move(new_listener));
} else {
new_listener->debugLog("update active listener");
*existing_active_listener = std::move(new_listener);
}
} else {
// Typically we catch address issues when we try to bind to the same address multiple times.
// However, for listeners that do not bind we must check to make sure we are not duplicating.
// This is an edge case and nothing will explicitly break, but there is no possibility that
// two listeners that do not bind will ever be used. Only the first one will be used when
// searched for by address. Thus we block it.
if (!new_listener->bindToPort() &&
(hasListenerWithAddress(warming_listeners_, *new_listener->address()) ||
hasListenerWithAddress(active_listeners_, *new_listener->address()))) {
const std::string message =
fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener",
name, new_listener->address()->asString());
ENVOY_LOG(warn, "{}", message);
throw EnvoyException(message);
}
// We have no warming or active listener so we need to make a new one. What we do depends on
// whether workers have been started or not. Additionally, search through draining listeners
// to see if there is a listener that has a socket bound to the address we are configured for.
// This is an edge case, but may happen if a listener is removed and then added back with a same
// or different name and intended to listen on the same address. This should work and not fail.
Network::SocketSharedPtr draining_listener_socket;
auto existing_draining_listener = std::find_if(
draining_listeners_.cbegin(), draining_listeners_.cend(),
[&new_listener](const DrainingListener& listener) {
return *new_listener->address() == *listener.listener_->socket().localAddress();
});
if (existing_draining_listener != draining_listeners_.cend()) {
draining_listener_socket = existing_draining_listener->listener_->getSocket();
}
new_listener->setSocket(draining_listener_socket
? draining_listener_socket
: factory_.createListenSocket(new_listener->address(),
new_listener->socketType(),
new_listener->listenSocketOptions(),
new_listener->bindToPort()));
if (workers_started_) {
new_listener->debugLog("add warming listener");
warming_listeners_.emplace_back(std::move(new_listener));
} else {
new_listener->debugLog("add active listener");
active_listeners_.emplace_back(std::move(new_listener));
}
added = true;
}
updateWarmingActiveGauges();
if (added) {
stats_.listener_added_.inc();
} else {
stats_.listener_modified_.inc();
}
new_listener_ref.initialize();
return true;
}