Imported Upstream version 1.35.0
[platform/upstream/grpc.git] / src / core / ext / transport / chttp2 / server / chttp2_server.cc
1 /*
2  *
3  * Copyright 2015 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18
19 #include <grpc/support/port_platform.h>
20
21 #include "src/core/ext/transport/chttp2/server/chttp2_server.h"
22
23 #include <inttypes.h>
24 #include <limits.h>
25 #include <string.h>
26 #include <vector>
27
28 #include "absl/strings/match.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_format.h"
31
32 #include <grpc/grpc.h>
33 #include <grpc/impl/codegen/grpc_types.h>
34 #include <grpc/support/alloc.h>
35 #include <grpc/support/log.h>
36 #include <grpc/support/sync.h>
37
38 #include "src/core/ext/filters/http/server/http_server_filter.h"
39 #include "src/core/ext/transport/chttp2/transport/chttp2_transport.h"
40 #include "src/core/ext/transport/chttp2/transport/internal.h"
41 #include "src/core/lib/channel/channel_args.h"
42 #include "src/core/lib/channel/handshaker.h"
43 #include "src/core/lib/channel/handshaker_registry.h"
44 #include "src/core/lib/gprpp/ref_counted.h"
45 #include "src/core/lib/gprpp/ref_counted_ptr.h"
46 #include "src/core/lib/iomgr/endpoint.h"
47 #include "src/core/lib/iomgr/resolve_address.h"
48 #include "src/core/lib/iomgr/resource_quota.h"
49 #include "src/core/lib/iomgr/sockaddr_utils.h"
50 #include "src/core/lib/iomgr/tcp_server.h"
51 #include "src/core/lib/iomgr/unix_sockets_posix.h"
52 #include "src/core/lib/slice/slice_internal.h"
53 #include "src/core/lib/surface/api_trace.h"
54 #include "src/core/lib/surface/server.h"
55
56 namespace grpc_core {
57 namespace {
58
59 const char kUnixUriPrefix[] = "unix:";
60 const char kUnixAbstractUriPrefix[] = "unix-abstract:";
61
62 class Chttp2ServerListener : public Server::ListenerInterface {
63  public:
64   static grpc_error* Create(Server* server, grpc_resolved_address* addr,
65                             grpc_channel_args* args, int* port_num);
66
67   static grpc_error* CreateWithAcceptor(Server* server, const char* name,
68                                         grpc_channel_args* args);
69
70   // Do not instantiate directly.  Use one of the factory methods above.
71   Chttp2ServerListener(Server* server, grpc_channel_args* args);
72   ~Chttp2ServerListener() override;
73
74   void Start(Server* server,
75              const std::vector<grpc_pollset*>* pollsets) override;
76
77   channelz::ListenSocketNode* channelz_listen_socket_node() const override {
78     return channelz_listen_socket_.get();
79   }
80
81   void SetOnDestroyDone(grpc_closure* on_destroy_done) override;
82
83   void Orphan() override;
84
85  private:
86   class ConfigFetcherWatcher
87       : public grpc_server_config_fetcher::WatcherInterface {
88    public:
89     explicit ConfigFetcherWatcher(Chttp2ServerListener* listener)
90         : listener_(listener) {}
91
92     void UpdateConfig(grpc_channel_args* args) override {
93       {
94         MutexLock lock(&listener_->mu_);
95         // TODO(yashykt): Fix this
96         // grpc_channel_args_destroy(listener_->args_);
97         // listener_->args_ = args;
98         if (!listener_->shutdown_) return;  // Already started listening.
99       }
100       int port_temp;
101       grpc_error* error = grpc_tcp_server_add_port(
102           listener_->tcp_server_, &listener_->resolved_address_, &port_temp);
103       if (error != GRPC_ERROR_NONE) {
104         GRPC_ERROR_UNREF(error);
105         gpr_log(GPR_ERROR, "Error adding port to server: %s",
106                 grpc_error_string(error));
107         // TODO(yashykt): We wouldn't need to assert here if we bound to the
108         // port earlier during AddPort.
109         GPR_ASSERT(0);
110       }
111       listener_->StartListening();
112     }
113
114    private:
115     Chttp2ServerListener* listener_;
116   };
117
118   class ConnectionState : public RefCounted<ConnectionState> {
119    public:
120     ConnectionState(Chttp2ServerListener* listener,
121                     grpc_pollset* accepting_pollset,
122                     grpc_tcp_server_acceptor* acceptor,
123                     RefCountedPtr<HandshakeManager> handshake_mgr,
124                     grpc_channel_args* args, grpc_endpoint* endpoint);
125
126     ~ConnectionState() override;
127
128    private:
129     static void OnTimeout(void* arg, grpc_error* error);
130     static void OnReceiveSettings(void* arg, grpc_error* error);
131     static void OnHandshakeDone(void* arg, grpc_error* error);
132
133     Chttp2ServerListener* const listener_;
134     grpc_pollset* const accepting_pollset_;
135     grpc_tcp_server_acceptor* const acceptor_;
136     RefCountedPtr<HandshakeManager> handshake_mgr_;
137     // State for enforcing handshake timeout on receiving HTTP/2 settings.
138     grpc_chttp2_transport* transport_ = nullptr;
139     grpc_millis deadline_;
140     grpc_timer timer_;
141     grpc_closure on_timeout_;
142     grpc_closure on_receive_settings_;
143     grpc_pollset_set* const interested_parties_;
144   };
145
146   void StartListening();
147
148   static void OnAccept(void* arg, grpc_endpoint* tcp,
149                        grpc_pollset* accepting_pollset,
150                        grpc_tcp_server_acceptor* acceptor);
151
152   RefCountedPtr<HandshakeManager> CreateHandshakeManager();
153
154   static void TcpServerShutdownComplete(void* arg, grpc_error* error);
155
156   static void DestroyListener(Server* /*server*/, void* arg,
157                               grpc_closure* destroy_done);
158
159   Server* const server_;
160   grpc_channel_args* const args_;
161   grpc_tcp_server* tcp_server_;
162   grpc_resolved_address resolved_address_;
163   Mutex mu_;
164   ConfigFetcherWatcher* config_fetcher_watcher_ = nullptr;
165   bool shutdown_ = true;
166   grpc_closure tcp_server_shutdown_complete_;
167   grpc_closure* on_destroy_done_ = nullptr;
168   HandshakeManager* pending_handshake_mgrs_ = nullptr;
169   RefCountedPtr<channelz::ListenSocketNode> channelz_listen_socket_;
170 };
171
172 //
173 // Chttp2ServerListener::ConnectionState
174 //
175
176 grpc_millis GetConnectionDeadline(const grpc_channel_args* args) {
177   int timeout_ms =
178       grpc_channel_args_find_integer(args, GRPC_ARG_SERVER_HANDSHAKE_TIMEOUT_MS,
179                                      {120 * GPR_MS_PER_SEC, 1, INT_MAX});
180   return ExecCtx::Get()->Now() + timeout_ms;
181 }
182
183 Chttp2ServerListener::ConnectionState::ConnectionState(
184     Chttp2ServerListener* listener, grpc_pollset* accepting_pollset,
185     grpc_tcp_server_acceptor* acceptor,
186     RefCountedPtr<HandshakeManager> handshake_mgr, grpc_channel_args* args,
187     grpc_endpoint* endpoint)
188     : listener_(listener),
189       accepting_pollset_(accepting_pollset),
190       acceptor_(acceptor),
191       handshake_mgr_(std::move(handshake_mgr)),
192       deadline_(GetConnectionDeadline(args)),
193       interested_parties_(grpc_pollset_set_create()) {
194   grpc_pollset_set_add_pollset(interested_parties_, accepting_pollset_);
195   HandshakerRegistry::AddHandshakers(HANDSHAKER_SERVER, args,
196                                      interested_parties_, handshake_mgr_.get());
197   handshake_mgr_->DoHandshake(endpoint, args, deadline_, acceptor_,
198                               OnHandshakeDone, this);
199 }
200
201 Chttp2ServerListener::ConnectionState::~ConnectionState() {
202   if (transport_ != nullptr) {
203     GRPC_CHTTP2_UNREF_TRANSPORT(transport_, "receive settings timeout");
204   }
205   grpc_pollset_set_del_pollset(interested_parties_, accepting_pollset_);
206   grpc_pollset_set_destroy(interested_parties_);
207 }
208
209 void Chttp2ServerListener::ConnectionState::OnTimeout(void* arg,
210                                                       grpc_error* error) {
211   ConnectionState* self = static_cast<ConnectionState*>(arg);
212   // Note that we may be called with GRPC_ERROR_NONE when the timer fires
213   // or with an error indicating that the timer system is being shut down.
214   if (error != GRPC_ERROR_CANCELLED) {
215     grpc_transport_op* op = grpc_make_transport_op(nullptr);
216     op->disconnect_with_error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
217         "Did not receive HTTP/2 settings before handshake timeout");
218     grpc_transport_perform_op(&self->transport_->base, op);
219   }
220   self->Unref();
221 }
222
223 void Chttp2ServerListener::ConnectionState::OnReceiveSettings(
224     void* arg, grpc_error* error) {
225   ConnectionState* self = static_cast<ConnectionState*>(arg);
226   if (error == GRPC_ERROR_NONE) {
227     grpc_timer_cancel(&self->timer_);
228   }
229   self->Unref();
230 }
231
232 void Chttp2ServerListener::ConnectionState::OnHandshakeDone(void* arg,
233                                                             grpc_error* error) {
234   auto* args = static_cast<HandshakerArgs*>(arg);
235   ConnectionState* self = static_cast<ConnectionState*>(args->user_data);
236   {
237     MutexLock lock(&self->listener_->mu_);
238     grpc_resource_user* resource_user =
239         self->listener_->server_->default_resource_user();
240     if (error != GRPC_ERROR_NONE || self->listener_->shutdown_) {
241       const char* error_str = grpc_error_string(error);
242       gpr_log(GPR_DEBUG, "Handshaking failed: %s", error_str);
243       if (resource_user != nullptr) {
244         grpc_resource_user_free(resource_user,
245                                 GRPC_RESOURCE_QUOTA_CHANNEL_SIZE);
246       }
247       if (error == GRPC_ERROR_NONE && args->endpoint != nullptr) {
248         // We were shut down after handshaking completed successfully, so
249         // destroy the endpoint here.
250         // TODO(ctiller): It is currently necessary to shutdown endpoints
251         // before destroying them, even if we know that there are no
252         // pending read/write callbacks.  This should be fixed, at which
253         // point this can be removed.
254         grpc_endpoint_shutdown(args->endpoint, GRPC_ERROR_NONE);
255         grpc_endpoint_destroy(args->endpoint);
256         grpc_channel_args_destroy(args->args);
257         grpc_slice_buffer_destroy_internal(args->read_buffer);
258         gpr_free(args->read_buffer);
259       }
260     } else {
261       // If the handshaking succeeded but there is no endpoint, then the
262       // handshaker may have handed off the connection to some external
263       // code, so we can just clean up here without creating a transport.
264       if (args->endpoint != nullptr) {
265         grpc_transport* transport = grpc_create_chttp2_transport(
266             args->args, args->endpoint, false, resource_user);
267         grpc_error* channel_init_err = self->listener_->server_->SetupTransport(
268             transport, self->accepting_pollset_, args->args,
269             grpc_chttp2_transport_get_socket_node(transport), resource_user);
270         if (channel_init_err == GRPC_ERROR_NONE) {
271           // Use notify_on_receive_settings callback to enforce the
272           // handshake deadline.
273           // Note: The reinterpret_cast<>s here are safe, because
274           // grpc_chttp2_transport is a C-style extension of
275           // grpc_transport, so this is morally equivalent of a
276           // static_cast<> to a derived class.
277           // TODO(roth): Change to static_cast<> when we C++-ify the
278           // transport API.
279           self->transport_ =
280               reinterpret_cast<grpc_chttp2_transport*>(transport);
281           self->Ref().release();  // Held by OnReceiveSettings().
282           GRPC_CLOSURE_INIT(&self->on_receive_settings_, OnReceiveSettings,
283                             self, grpc_schedule_on_exec_ctx);
284           grpc_chttp2_transport_start_reading(transport, args->read_buffer,
285                                               &self->on_receive_settings_);
286           grpc_channel_args_destroy(args->args);
287           self->Ref().release();  // Held by OnTimeout().
288           GRPC_CHTTP2_REF_TRANSPORT(
289               reinterpret_cast<grpc_chttp2_transport*>(transport),
290               "receive settings timeout");
291           GRPC_CLOSURE_INIT(&self->on_timeout_, OnTimeout, self,
292                             grpc_schedule_on_exec_ctx);
293           grpc_timer_init(&self->timer_, self->deadline_, &self->on_timeout_);
294         } else {
295           // Failed to create channel from transport. Clean up.
296           gpr_log(GPR_ERROR, "Failed to create channel: %s",
297                   grpc_error_string(channel_init_err));
298           GRPC_ERROR_UNREF(channel_init_err);
299           grpc_transport_destroy(transport);
300           grpc_slice_buffer_destroy_internal(args->read_buffer);
301           gpr_free(args->read_buffer);
302           if (resource_user != nullptr) {
303             grpc_resource_user_free(resource_user,
304                                     GRPC_RESOURCE_QUOTA_CHANNEL_SIZE);
305           }
306           grpc_channel_args_destroy(args->args);
307         }
308       } else {
309         if (resource_user != nullptr) {
310           grpc_resource_user_free(resource_user,
311                                   GRPC_RESOURCE_QUOTA_CHANNEL_SIZE);
312         }
313       }
314     }
315     self->handshake_mgr_->RemoveFromPendingMgrList(
316         &self->listener_->pending_handshake_mgrs_);
317   }
318   self->handshake_mgr_.reset();
319   gpr_free(self->acceptor_);
320   grpc_tcp_server_unref(self->listener_->tcp_server_);
321   self->Unref();
322 }
323
324 //
325 // Chttp2ServerListener
326 //
327
328 grpc_error* Chttp2ServerListener::Create(Server* server,
329                                          grpc_resolved_address* addr,
330                                          grpc_channel_args* args,
331                                          int* port_num) {
332   Chttp2ServerListener* listener = nullptr;
333   // The bulk of this method is inside of a lambda to make cleanup
334   // easier without using goto.
335   grpc_error* error = [&]() {
336     // Create Chttp2ServerListener.
337     listener = new Chttp2ServerListener(server, args);
338     error = grpc_tcp_server_create(&listener->tcp_server_shutdown_complete_,
339                                    args, &listener->tcp_server_);
340     if (error != GRPC_ERROR_NONE) return error;
341     if (server->config_fetcher() != nullptr) {
342       listener->resolved_address_ = *addr;
343       // TODO(yashykt): Consider binding so as to be able to return the port
344       // number.
345     } else {
346       error = grpc_tcp_server_add_port(listener->tcp_server_, addr, port_num);
347       if (error != GRPC_ERROR_NONE) return error;
348     }
349     // Create channelz node.
350     if (grpc_channel_args_find_bool(args, GRPC_ARG_ENABLE_CHANNELZ,
351                                     GRPC_ENABLE_CHANNELZ_DEFAULT)) {
352       std::string string_address = grpc_sockaddr_to_string(addr, false);
353       listener->channelz_listen_socket_ =
354           MakeRefCounted<channelz::ListenSocketNode>(
355               string_address.c_str(),
356               absl::StrFormat("chttp2 listener %s", string_address.c_str()));
357     }
358     // Register with the server only upon success
359     server->AddListener(OrphanablePtr<Server::ListenerInterface>(listener));
360     return GRPC_ERROR_NONE;
361   }();
362   if (error != GRPC_ERROR_NONE) {
363     if (listener != nullptr) {
364       if (listener->tcp_server_ != nullptr) {
365         // listener is deleted when tcp_server_ is shutdown.
366         grpc_tcp_server_unref(listener->tcp_server_);
367       } else {
368         delete listener;
369       }
370     } else {
371       grpc_channel_args_destroy(args);
372     }
373   }
374   return error;
375 }
376
377 grpc_error* Chttp2ServerListener::CreateWithAcceptor(Server* server,
378                                                      const char* name,
379                                                      grpc_channel_args* args) {
380   Chttp2ServerListener* listener = new Chttp2ServerListener(server, args);
381   grpc_error* error = grpc_tcp_server_create(
382       &listener->tcp_server_shutdown_complete_, args, &listener->tcp_server_);
383   if (error != GRPC_ERROR_NONE) {
384     delete listener;
385     return error;
386   }
387   // TODO(yangg) channelz
388   TcpServerFdHandler** arg_val =
389       grpc_channel_args_find_pointer<TcpServerFdHandler*>(args, name);
390   *arg_val = grpc_tcp_server_create_fd_handler(listener->tcp_server_);
391   server->AddListener(OrphanablePtr<Server::ListenerInterface>(listener));
392   return GRPC_ERROR_NONE;
393 }
394
395 Chttp2ServerListener::Chttp2ServerListener(Server* server,
396                                            grpc_channel_args* args)
397     : server_(server), args_(args) {
398   GRPC_CLOSURE_INIT(&tcp_server_shutdown_complete_, TcpServerShutdownComplete,
399                     this, grpc_schedule_on_exec_ctx);
400 }
401
402 Chttp2ServerListener::~Chttp2ServerListener() {
403   grpc_channel_args_destroy(args_);
404 }
405
406 /* Server callback: start listening on our ports */
407 void Chttp2ServerListener::Start(
408     Server* /*server*/, const std::vector<grpc_pollset*>* /* pollsets */) {
409   if (server_->config_fetcher() != nullptr) {
410     auto watcher = absl::make_unique<ConfigFetcherWatcher>(this);
411     {
412       MutexLock lock(&mu_);
413       config_fetcher_watcher_ = watcher.get();
414     }
415     server_->config_fetcher()->StartWatch(
416         grpc_sockaddr_to_string(&resolved_address_, false), std::move(watcher));
417   } else {
418     StartListening();
419   }
420 }
421
422 void Chttp2ServerListener::StartListening() {
423   grpc_tcp_server_start(tcp_server_, &server_->pollsets(), OnAccept, this);
424   MutexLock lock(&mu_);
425   shutdown_ = false;
426 }
427
428 void Chttp2ServerListener::SetOnDestroyDone(grpc_closure* on_destroy_done) {
429   MutexLock lock(&mu_);
430   on_destroy_done_ = on_destroy_done;
431 }
432
433 RefCountedPtr<HandshakeManager> Chttp2ServerListener::CreateHandshakeManager() {
434   MutexLock lock(&mu_);
435   if (shutdown_) return nullptr;
436   grpc_resource_user* resource_user = server_->default_resource_user();
437   if (resource_user != nullptr &&
438       !grpc_resource_user_safe_alloc(resource_user,
439                                      GRPC_RESOURCE_QUOTA_CHANNEL_SIZE)) {
440     gpr_log(GPR_ERROR,
441             "Memory quota exhausted, rejecting connection, no handshaking.");
442     return nullptr;
443   }
444   auto handshake_mgr = MakeRefCounted<HandshakeManager>();
445   handshake_mgr->AddToPendingMgrList(&pending_handshake_mgrs_);
446   grpc_tcp_server_ref(tcp_server_);  // Ref held by ConnectionState.
447   return handshake_mgr;
448 }
449
450 void Chttp2ServerListener::OnAccept(void* arg, grpc_endpoint* tcp,
451                                     grpc_pollset* accepting_pollset,
452                                     grpc_tcp_server_acceptor* acceptor) {
453   Chttp2ServerListener* self = static_cast<Chttp2ServerListener*>(arg);
454   RefCountedPtr<HandshakeManager> handshake_mgr =
455       self->CreateHandshakeManager();
456   if (handshake_mgr == nullptr) {
457     grpc_endpoint_shutdown(tcp, GRPC_ERROR_NONE);
458     grpc_endpoint_destroy(tcp);
459     gpr_free(acceptor);
460     return;
461   }
462   // Deletes itself when done.
463   new ConnectionState(self, accepting_pollset, acceptor,
464                       std::move(handshake_mgr), self->args_, tcp);
465 }
466
467 void Chttp2ServerListener::TcpServerShutdownComplete(void* arg,
468                                                      grpc_error* error) {
469   Chttp2ServerListener* self = static_cast<Chttp2ServerListener*>(arg);
470   /* ensure all threads have unlocked */
471   grpc_closure* destroy_done = nullptr;
472   {
473     MutexLock lock(&self->mu_);
474     destroy_done = self->on_destroy_done_;
475     GPR_ASSERT(self->shutdown_);
476     if (self->pending_handshake_mgrs_ != nullptr) {
477       self->pending_handshake_mgrs_->ShutdownAllPending(GRPC_ERROR_REF(error));
478     }
479     self->channelz_listen_socket_.reset();
480   }
481   // Flush queued work before destroying handshaker factory, since that
482   // may do a synchronous unref.
483   ExecCtx::Get()->Flush();
484   if (destroy_done != nullptr) {
485     ExecCtx::Run(DEBUG_LOCATION, destroy_done, GRPC_ERROR_REF(error));
486     ExecCtx::Get()->Flush();
487   }
488   delete self;
489 }
490
491 /* Server callback: destroy the tcp listener (so we don't generate further
492    callbacks) */
493 void Chttp2ServerListener::Orphan() {
494   // Cancel the watch before shutting down so as to avoid holding a ref to the
495   // listener in the watcher.
496   if (config_fetcher_watcher_ != nullptr) {
497     server_->config_fetcher()->CancelWatch(config_fetcher_watcher_);
498   }
499   grpc_tcp_server* tcp_server;
500   {
501     MutexLock lock(&mu_);
502     shutdown_ = true;
503     tcp_server = tcp_server_;
504   }
505   grpc_tcp_server_shutdown_listeners(tcp_server);
506   grpc_tcp_server_unref(tcp_server);
507 }
508
509 }  // namespace
510
511 //
512 // Chttp2ServerAddPort()
513 //
514
515 grpc_error* Chttp2ServerAddPort(Server* server, const char* addr,
516                                 grpc_channel_args* args, int* port_num) {
517   if (strncmp(addr, "external:", 9) == 0) {
518     return grpc_core::Chttp2ServerListener::CreateWithAcceptor(server, addr,
519                                                                args);
520   }
521   *port_num = -1;
522   grpc_resolved_addresses* resolved = nullptr;
523   std::vector<grpc_error*> error_list;
524   // Using lambda to avoid use of goto.
525   grpc_error* error = [&]() {
526     if (absl::StartsWith(addr, kUnixUriPrefix)) {
527       error = grpc_resolve_unix_domain_address(
528           addr + sizeof(kUnixUriPrefix) - 1, &resolved);
529     } else if (absl::StartsWith(addr, kUnixAbstractUriPrefix)) {
530       error = grpc_resolve_unix_abstract_domain_address(
531           addr + sizeof(kUnixAbstractUriPrefix) - 1, &resolved);
532     } else {
533       error = grpc_blocking_resolve_address(addr, "https", &resolved);
534     }
535     if (error != GRPC_ERROR_NONE) return error;
536     // Create a listener for each resolved address.
537     for (size_t i = 0; i < resolved->naddrs; i++) {
538       // If address has a wildcard port (0), use the same port as a previous
539       // listener.
540       if (*port_num != -1 && grpc_sockaddr_get_port(&resolved->addrs[i]) == 0) {
541         grpc_sockaddr_set_port(&resolved->addrs[i], *port_num);
542       }
543       int port_temp;
544       error = grpc_core::Chttp2ServerListener::Create(
545           server, &resolved->addrs[i], grpc_channel_args_copy(args),
546           &port_temp);
547       if (error != GRPC_ERROR_NONE) {
548         error_list.push_back(error);
549       } else {
550         if (*port_num == -1) {
551           *port_num = port_temp;
552         } else {
553           GPR_ASSERT(*port_num == port_temp);
554         }
555       }
556     }
557     if (error_list.size() == resolved->naddrs) {
558       std::string msg =
559           absl::StrFormat("No address added out of total %" PRIuPTR " resolved",
560                           resolved->naddrs);
561       return GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING(
562           msg.c_str(), error_list.data(), error_list.size());
563     } else if (!error_list.empty()) {
564       std::string msg = absl::StrFormat(
565           "Only %" PRIuPTR " addresses added out of total %" PRIuPTR
566           " resolved",
567           resolved->naddrs - error_list.size(), resolved->naddrs);
568       error = GRPC_ERROR_CREATE_REFERENCING_FROM_COPIED_STRING(
569           msg.c_str(), error_list.data(), error_list.size());
570       gpr_log(GPR_INFO, "WARNING: %s", grpc_error_string(error));
571       GRPC_ERROR_UNREF(error);
572       // we managed to bind some addresses: continue without error
573     }
574     return GRPC_ERROR_NONE;
575   }();  // lambda end
576   for (grpc_error* error : error_list) {
577     GRPC_ERROR_UNREF(error);
578   }
579   grpc_channel_args_destroy(args);
580   if (resolved != nullptr) {
581     grpc_resolved_addresses_destroy(resolved);
582   }
583   if (error != GRPC_ERROR_NONE) *port_num = 0;
584   return error;
585 }
586
587 }  // namespace grpc_core