Skip to content

Commit 61b7b9f

Browse files
authored
listener: fixed a bug where the addresses cannot be updated partially (#38601)
1 parent eb398e9 commit 61b7b9f

File tree

7 files changed

+157
-42
lines changed

7 files changed

+157
-42
lines changed

api/envoy/config/listener/v3/listener.proto

+3-3
Original file line numberDiff line numberDiff line change
@@ -247,10 +247,10 @@ message Listener {
247247
google.protobuf.BoolValue freebind = 11;
248248

249249
// Additional socket options that may not be present in Envoy source code or
250-
// precompiled binaries. The socket options can be updated for a listener when
250+
// precompiled binaries.
251+
// It is not allowed to update the socket options for any existing address if
251252
// :ref:`enable_reuse_port <envoy_v3_api_field_config.listener.v3.Listener.enable_reuse_port>`
252-
// is ``true``. Otherwise, if socket options change during a listener update the update will be rejected
253-
// to make it clear that the options were not updated.
253+
// is ``false`` to avoid the conflict when creating new sockets for the listener.
254254
repeated core.v3.SocketOption socket_options = 13;
255255

256256
// Whether the listener should accept TCP Fast Open (TFO) connections.

changelogs/current.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ bug_fixes:
8080
Before the :scheme header was always 'http'.
8181
This behavioral change can be temporarily reverted by setting runtime guard
8282
``envoy.reloadable_features.jwt_fetcher_use_scheme_from_uri`` to false.
83+
- area: listener
84+
change: |
85+
Fixed a bug where the addresses cannot be updated partially even if the reuse port is enabled.
8386
8487
removed_config_or_runtime:
8588
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`

source/common/listener_manager/listener_impl.cc

+6-13
Original file line numberDiff line numberDiff line change
@@ -1128,15 +1128,13 @@ bool ListenerImpl::getReusePortOrDefault(Server::Instance& server,
11281128
return initial_reuse_port_value;
11291129
}
11301130

1131-
bool ListenerImpl::socketOptionsEqual(const ListenerImpl& other) const {
1132-
return ListenerMessageUtil::socketOptionsEqual(config(), other.config());
1133-
}
1134-
11351131
bool ListenerImpl::hasCompatibleAddress(const ListenerImpl& other) const {
1136-
if ((socket_type_ != other.socket_type_) || (addresses_.size() != other.addresses().size())) {
1132+
// First, check if the listener has the same socket type and the same number of addresses.
1133+
if (socket_type_ != other.socket_type_ || addresses_.size() != other.addresses().size()) {
11371134
return false;
11381135
}
11391136

1137+
// Second, check if the listener has the same addresses.
11401138
// The listener support listening on the zero port address for test. Multiple zero
11411139
// port addresses are also supported. For comparing two listeners with multiple
11421140
// zero port addresses, only need to ensure there are the same number of zero
@@ -1153,17 +1151,12 @@ bool ListenerImpl::hasCompatibleAddress(const ListenerImpl& other) const {
11531151
}
11541152
other_addresses.erase(iter);
11551153
}
1156-
return true;
1154+
1155+
// Third, check if the listener has the same socket options.
1156+
return ListenerMessageUtil::socketOptionsEqual(config(), other.config());
11571157
}
11581158

11591159
bool ListenerImpl::hasDuplicatedAddress(const ListenerImpl& other) const {
1160-
// Skip the duplicate address check if this is the case of a listener update with new socket
1161-
// options.
1162-
if ((name_ == other.name_) &&
1163-
!ListenerMessageUtil::socketOptionsEqual(config(), other.config())) {
1164-
return false;
1165-
}
1166-
11671160
if (socket_type_ != other.socket_type_) {
11681161
return false;
11691162
}

source/common/listener_manager/listener_impl.h

-2
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,6 @@ class ListenerImpl final : public Network::ListenerConfig,
271271
const envoy::config::listener::v3::Listener& config,
272272
Network::Socket::Type socket_type);
273273

274-
// Compare whether two listeners have different socket options.
275-
bool socketOptionsEqual(const ListenerImpl& other) const;
276274
// Check whether a new listener can share sockets with this listener.
277275
bool hasCompatibleAddress(const ListenerImpl& other) const;
278276
// Check whether a new listener has duplicated listening address this listener.

source/common/listener_manager/listener_manager_impl.cc

+21-13
Original file line numberDiff line numberDiff line change
@@ -512,21 +512,12 @@ ListenerManagerImpl::addOrUpdateListener(const envoy::config::listener::v3::List
512512
absl::Status
513513
ListenerManagerImpl::setupSocketFactoryForListener(ListenerImpl& new_listener,
514514
const ListenerImpl& existing_listener) {
515-
bool same_socket_options = true;
516515
if (new_listener.reusePort() != existing_listener.reusePort()) {
517516
return absl::InvalidArgumentError(fmt::format(
518517
"Listener {}: reuse port cannot be changed during an update", new_listener.name()));
519518
}
520519

521-
same_socket_options = existing_listener.socketOptionsEqual(new_listener);
522-
if (!same_socket_options && new_listener.reusePort() == false) {
523-
return absl::InvalidArgumentError(
524-
fmt::format("Listener {}: doesn't support update any socket options "
525-
"when the reuse port isn't enabled",
526-
new_listener.name()));
527-
}
528-
529-
if (!(existing_listener.hasCompatibleAddress(new_listener) && same_socket_options)) {
520+
if (!existing_listener.hasCompatibleAddress(new_listener)) {
530521
RETURN_IF_NOT_OK(setNewOrDrainingSocketFactory(new_listener.name(), new_listener));
531522
} else {
532523
RETURN_IF_NOT_OK(new_listener.cloneSocketFactoryFrom(existing_listener));
@@ -639,9 +630,21 @@ absl::StatusOr<bool> ListenerManagerImpl::addOrUpdateListenerInternal(
639630
return true;
640631
}
641632

642-
bool ListenerManagerImpl::hasListenerWithDuplicatedAddress(const ListenerList& list,
633+
bool ListenerManagerImpl::hasListenerWithDuplicatedAddress(const ListenerList& listener_list,
643634
const ListenerImpl& listener) {
644-
for (const auto& existing_listener : list) {
635+
// This is new listener or new version of existing listener but with different addresses
636+
// or different socket options.
637+
// Check if the listener has duplicated address with existing listeners.
638+
639+
for (const auto& existing_listener : listener_list) {
640+
if (listener.reusePort() && existing_listener->name() == listener.name()) {
641+
// If reuse port is enabled, we can skip the check between different versions of the
642+
// same listener as they can create their own sockets anyway.
643+
// If reuse port is disabled, the duplicated addresses check is necessary to to avoid
644+
// attempting to bind to the same address when creating new sockets for the listener.
645+
continue;
646+
}
647+
645648
if (existing_listener->hasDuplicatedAddress(listener)) {
646649
return true;
647650
}
@@ -1148,7 +1151,9 @@ absl::Status ListenerManagerImpl::setNewOrDrainingSocketFactory(const std::strin
11481151
if (hasListenerWithDuplicatedAddress(warming_listeners_, listener) ||
11491152
hasListenerWithDuplicatedAddress(active_listeners_, listener)) {
11501153
const std::string message =
1151-
fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener",
1154+
fmt::format("error adding listener: '{}' has duplicate address '{}' as existing listener, "
1155+
"to check if the listener has duplicated addresses with other listeners or "
1156+
"'enable_reuse_port' is set to 'false' for the listener",
11521157
name, absl::StrJoin(listener.addresses(), ",", Network::AddressStrFormatter()));
11531158
ENVOY_LOG(warn, "{}", message);
11541159
return absl::InvalidArgumentError(message);
@@ -1188,6 +1193,9 @@ absl::Status ListenerManagerImpl::setNewOrDrainingSocketFactory(const std::strin
11881193
}
11891194
}
11901195

1196+
// TODO(wbpcode): if we cannot clone the socket factory from the draining listener, we should
1197+
// check the duplicated addresses again the draining listeners to avoid the creation failure
1198+
// of the sockets.
11911199
if (draining_listener_ptr != nullptr) {
11921200
RETURN_IF_NOT_OK(listener.cloneSocketFactoryFrom(*draining_listener_ptr));
11931201
} else {

test/common/listener_manager/listener_manager_impl_test.cc

+115-9
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@ name: bar
251251
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
252252
EXPECT_THROW_WITH_MESSAGE(
253253
addOrUpdateListener(parseListenerFromV3Yaml(yaml2)), EnvoyException,
254-
"error adding listener: 'bar' has duplicate address '127.0.0.1:1234' as existing listener");
254+
"error adding listener: 'bar' has duplicate address '127.0.0.1:1234' as existing listener, "
255+
"to check if the listener has duplicated addresses with other listeners or "
256+
"'enable_reuse_port' is set to 'false' for the listener");
255257
}
256258

257259
TEST_P(ListenerManagerImplWithRealFiltersTest, DuplicateNonIPAddressNotAllowed) {
@@ -278,7 +280,9 @@ name: bar
278280
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
279281
EXPECT_THROW_WITH_MESSAGE(
280282
addOrUpdateListener(parseListenerFromV3Yaml(yaml2)), EnvoyException,
281-
"error adding listener: 'bar' has duplicate address '/path' as existing listener");
283+
"error adding listener: 'bar' has duplicate address '/path' as existing listener, to check "
284+
"if the listener has duplicated addresses with other listeners or 'enable_reuse_port' is set "
285+
"to 'false' for the listener");
282286
}
283287

284288
TEST_P(ListenerManagerImplWithRealFiltersTest, MultipleAddressesDuplicatePortNotAllowed) {
@@ -318,7 +322,9 @@ name: bar
318322
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
319323
EXPECT_THROW_WITH_MESSAGE(addOrUpdateListener(parseListenerFromV3Yaml(yaml2)), EnvoyException,
320324
"error adding listener: 'bar' has duplicate address "
321-
"'127.0.0.1:1234,127.0.0.3:1234' as existing listener");
325+
"'127.0.0.1:1234,127.0.0.3:1234' as existing listener, to check if the "
326+
"listener has duplicated addresses with other listeners or "
327+
"'enable_reuse_port' is set to 'false' for the listener");
322328
}
323329

324330
TEST_P(ListenerManagerImplWithRealFiltersTest,
@@ -358,9 +364,11 @@ bind_to_port: false
358364
)EOF";
359365

360366
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
361-
EXPECT_THROW_WITH_MESSAGE(addOrUpdateListener(parseListenerFromV3Yaml(yaml2)), EnvoyException,
362-
"error adding listener: 'bar' has duplicate address "
363-
"'127.0.0.1:0,127.0.0.3:0' as existing listener");
367+
EXPECT_THROW_WITH_MESSAGE(
368+
addOrUpdateListener(parseListenerFromV3Yaml(yaml2)), EnvoyException,
369+
"error adding listener: 'bar' has duplicate address "
370+
"'127.0.0.1:0,127.0.0.3:0' as existing listener, to check if the listener has duplicated "
371+
"addresses with other listeners or 'enable_reuse_port' is set to 'false' for the listener");
364372
}
365373

366374
TEST_P(ListenerManagerImplWithRealFiltersTest, AllowCreateListenerWithMutipleZeroPorts) {
@@ -402,6 +410,98 @@ name: bar
402410
addOrUpdateListener(parseListenerFromV3Yaml(yaml2));
403411
}
404412

413+
TEST_P(ListenerManagerImplWithRealFiltersTest, AllowAddressesUpdatePartially) {
414+
// Update one of the addresses from '127.0.0.2:1000' to '127.0.0.3:2000'.
415+
const std::string yaml1 = R"EOF(
416+
name: foo
417+
address:
418+
socket_address:
419+
address: 127.0.0.1
420+
port_value: 1000
421+
additional_addresses:
422+
- address:
423+
socket_address:
424+
address: 127.0.0.2
425+
port_value: 1000
426+
filter_chains:
427+
- filters: []
428+
name: foo
429+
)EOF";
430+
431+
const std::string yaml2 = R"EOF(
432+
name: foo
433+
address:
434+
socket_address:
435+
address: 127.0.0.1
436+
port_value: 1000
437+
additional_addresses:
438+
- address:
439+
socket_address:
440+
address: 127.0.0.3
441+
port_value: 2000
442+
filter_chains:
443+
- filters: []
444+
name: foo
445+
)EOF";
446+
447+
EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, default_bind_type, _, 0)).Times(2);
448+
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
449+
EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, default_bind_type, _, 0)).Times(2);
450+
addOrUpdateListener(parseListenerFromV3Yaml(yaml2));
451+
}
452+
453+
TEST_P(ListenerManagerImplWithRealFiltersTest,
454+
AllowUpdateSocketOptionsIfNotDuplicatedEvenReusePortIsDisabled) {
455+
// All addresses are different, so it should be allowed to update socket options even
456+
// reuse port is disabled.
457+
const std::string yaml1 = R"EOF(
458+
name: foo
459+
address:
460+
socket_address:
461+
address: 127.0.0.1
462+
port_value: 1000
463+
additional_addresses:
464+
- address:
465+
socket_address:
466+
address: 127.0.0.2
467+
port_value: 1000
468+
enable_reuse_port: false
469+
filter_chains:
470+
- filters: []
471+
name: foo
472+
)EOF";
473+
474+
const std::string yaml2 = R"EOF(
475+
name: foo
476+
address:
477+
socket_address:
478+
address: 127.0.0.4
479+
port_value: 1000
480+
additional_addresses:
481+
- address:
482+
socket_address:
483+
address: 127.0.0.3
484+
port_value: 2000
485+
enable_reuse_port: false
486+
socket_options:
487+
- level: 1
488+
name: 9
489+
int_value: 1
490+
filter_chains:
491+
- filters: []
492+
name: foo
493+
)EOF";
494+
495+
EXPECT_CALL(listener_factory_,
496+
createListenSocket(_, _, _, ListenerComponentFactory::BindType::NoReusePort, _, 0))
497+
.Times(2);
498+
addOrUpdateListener(parseListenerFromV3Yaml(yaml1));
499+
EXPECT_CALL(listener_factory_,
500+
createListenSocket(_, _, _, ListenerComponentFactory::BindType::NoReusePort, _, 0))
501+
.Times(2);
502+
addOrUpdateListener(parseListenerFromV3Yaml(yaml2));
503+
}
504+
405505
TEST_P(ListenerManagerImplWithRealFiltersTest, SetListenerPerConnectionBufferLimit) {
406506
const std::string yaml = R"EOF(
407507
address:
@@ -2298,7 +2398,9 @@ name: bar
22982398
)EOF";
22992399

23002400
const std::string expected_error_message =
2301-
"error adding listener: 'bar' has duplicate address '127.0.0.1:1234' as existing listener";
2401+
"error adding listener: 'bar' has duplicate address '127.0.0.1:1234' as existing listener, "
2402+
"to check if the listener has duplicated addresses with other listeners or "
2403+
"'enable_reuse_port' is set to 'false' for the listener";
23022404
testListenerUpdateWithSocketOptionsChangeRejected(listener_origin, listener_updated,
23032405
expected_error_message);
23042406
}
@@ -3343,7 +3445,9 @@ bind_to_port: false
33433445
EXPECT_CALL(*listener_bar, onDestroy());
33443446
EXPECT_THROW_WITH_MESSAGE(
33453447
addOrUpdateListener(parseListenerFromV3Yaml(listener_bar_yaml)), EnvoyException,
3346-
"error adding listener: 'bar' has duplicate address '0.0.0.0:1234' as existing listener");
3448+
"error adding listener: 'bar' has duplicate address '0.0.0.0:1234' as existing listener, to "
3449+
"check if the listener has duplicated addresses with other listeners or 'enable_reuse_port' "
3450+
"is set to 'false' for the listener");
33473451

33483452
// Move foo to active and then try to add again. This should still fail.
33493453
EXPECT_CALL(*worker_, addListener(_, _, _, _, _));
@@ -3354,7 +3458,9 @@ bind_to_port: false
33543458
EXPECT_CALL(*listener_bar, onDestroy());
33553459
EXPECT_THROW_WITH_MESSAGE(
33563460
addOrUpdateListener(parseListenerFromV3Yaml(listener_bar_yaml)), EnvoyException,
3357-
"error adding listener: 'bar' has duplicate address '0.0.0.0:1234' as existing listener");
3461+
"error adding listener: 'bar' has duplicate address '0.0.0.0:1234' as existing listener, to "
3462+
"check if the listener has duplicated addresses with other listeners or 'enable_reuse_port' "
3463+
"is set to 'false' for the listener");
33583464

33593465
EXPECT_CALL(*listener_foo, onDestroy());
33603466
}

test/integration/listener_lds_integration_test.cc

+9-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "envoy/network/connection.h"
99
#include "envoy/service/discovery/v3/discovery.pb.h"
1010

11+
#include "source/common/common/random_generator.h"
1112
#include "source/common/config/api_version.h"
1213

1314
#include "test/common/grpc/grpc_client_integration.h"
@@ -1639,6 +1640,12 @@ TEST_P(ListenerFilterIntegrationTest,
16391640
listener_config_.set_name("listener_foo");
16401641
listener_config_.set_stat_prefix("listener_stat");
16411642
listener_config_.mutable_enable_reuse_port()->set_value(false);
1643+
1644+
// Set random port manually for test. 0 port means the kernel will generate a random port
1645+
// and envoy will skip the port conflict check.
1646+
const uint32_t random_port = Random::RandomUtility::random() % 10000 + 10000;
1647+
listener_config_.mutable_address()->mutable_socket_address()->set_port_value(random_port);
1648+
16421649
ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString());
16431650
bootstrap.mutable_static_resources()->mutable_listeners()->Clear();
16441651
auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config();
@@ -1682,8 +1689,8 @@ TEST_P(ListenerFilterIntegrationTest,
16821689
EXPECT_LOG_CONTAINS(
16831690
"warning",
16841691
"gRPC config for type.googleapis.com/envoy.config.listener.v3.Listener rejected: Error "
1685-
"adding/updating listener(s) listener_foo: Listener listener_foo: doesn't support update any "
1686-
"socket options when the reuse port isn't enabled",
1692+
"adding/updating listener(s) listener_foo: error adding listener: 'listener_foo' has "
1693+
"duplicate address",
16871694
{
16881695
sendLdsResponse({MessageUtil::getYamlStringFromMessage(listener_config_)}, "2");
16891696
test_server_->waitForCounterGe("listener_manager.lds.update_rejected", 1);

0 commit comments

Comments
 (0)