| 567 | |
| 568 | |
| 569 | void LocalServer::startServers(const ServerType & server_type) |
| 570 | { |
| 571 | validateLocalServerListenType(server_type); |
| 572 | |
| 573 | const auto & config = getClientConfiguration(); |
| 574 | const Settings & settings = global_context->getSettingsRef(); |
| 575 | |
| 576 | /// The configured `tcp_port` / `http_port` is the port that the listener binds, and `createServer` |
| 577 | /// casts it to `UInt16`, so a value outside `0..65535` would silently wrap (`-1` -> `65535`, |
| 578 | /// `70000` -> `4464`) and bring the listener up on an unexpected port. `--tcp_port` / `--http_port` |
| 579 | /// are already range-checked when parsed, but a value coming from a loaded config file reaches this |
| 580 | /// path unchecked, so validate the effective value here for every protocol being started. `0` means |
| 581 | /// an OS-assigned port and is allowed. |
| 582 | auto validate_port_range = [&](const char * port_name) |
| 583 | { |
| 584 | if (!config.has(port_name)) |
| 585 | return; |
| 586 | Int64 port = config.getInt64(port_name); |
| 587 | if (port < 0 || port > 65535) |
| 588 | throw Exception( |
| 589 | ErrorCodes::BAD_ARGUMENTS, |
| 590 | "Invalid value {} for {}: a port number must be in the range 0..65535 (use 0 for an OS-assigned port)", |
| 591 | port, port_name); |
| 592 | }; |
| 593 | if (server_type.shouldStart(ServerType::Type::TCP)) |
| 594 | validate_port_range("tcp_port"); |
| 595 | if (server_type.shouldStart(ServerType::Type::HTTP)) |
| 596 | validate_port_range("http_port"); |
| 597 | |
| 598 | /// An explicit `--listen_host` on the command line is a hard override: bind only that single host |
| 599 | /// and ignore any `listen_host` entries from a loaded config file. This matters because |
| 600 | /// `getMultipleValuesFromConfig` returns the union of repeated `listen_host[...]` keys across all |
| 601 | /// configuration layers, so a plain `config.setString("listen_host", ...)` would replace only the |
| 602 | /// first entry and leave lower-priority hosts (possibly non-loopback) from the config still bound, |
| 603 | /// defeating the documented hardening path. |
| 604 | std::vector<std::string> listen_hosts; |
| 605 | if (cli_listen_host.has_value()) |
| 606 | listen_hosts.emplace_back(*cli_listen_host); |
| 607 | else |
| 608 | listen_hosts = DB::getMultipleValuesFromConfig(config, "", "listen_host"); |
| 609 | |
| 610 | const bool hosts_explicitly_configured = !listen_hosts.empty(); |
| 611 | if (listen_hosts.empty()) |
| 612 | { |
| 613 | listen_hosts.emplace_back("::1"); |
| 614 | listen_hosts.emplace_back("127.0.0.1"); |
| 615 | } |
| 616 | |
| 617 | /// Mirror `clickhouse-server`'s `getListenTry`: honor an explicit `listen_try` server setting, |
| 618 | /// and otherwise fall back to `listen_try=true` only when the user has not named any host |
| 619 | /// explicitly (so the implicit `::1` + `127.0.0.1` defaults don't fail on a host without |
| 620 | /// IPv6). When the user names hosts explicitly with `listen_try=0`, surface bind failures. |
| 621 | bool listen_try = server_settings[ServerSetting::listen_try]; |
| 622 | if (!listen_try) |
| 623 | listen_try = !hosts_explicitly_configured; |
| 624 | |
| 625 | /// `getServerPort` stores a single value per `port_name`, so an OS-assigned ephemeral |
| 626 | /// port (`port=0`) combined with multiple `listen_host` values would produce an |
nothing calls this directly
no test coverage detected