|  | /* | 
|  | *  Copyright 2017 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #include "pc/ice_server_parsing.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <cctype>  // For std::isdigit. | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <tuple> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "api/candidate.h" | 
|  | #include "api/peer_connection_interface.h" | 
|  | #include "api/rtc_error.h" | 
|  | #include "p2p/base/p2p_constants.h" | 
|  | #include "p2p/base/port.h" | 
|  | #include "p2p/base/port_allocator.h" | 
|  | #include "p2p/base/port_interface.h" | 
|  | #include "rtc_base/arraysize.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/ip_address.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/socket_address.h" | 
|  | #include "rtc_base/string_encode.h" | 
|  | #include "rtc_base/string_to_number.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | namespace { | 
|  | // Number of tokens must be preset when TURN uri has transport param. | 
|  | const size_t kTurnTransportTokensNum = 2; | 
|  | // The default stun port. | 
|  | const int kDefaultStunPort = 3478; | 
|  | const int kDefaultStunTlsPort = 5349; | 
|  | const char kTransport[] = "transport"; | 
|  |  | 
|  | // Allowed characters in hostname per RFC 3986 Appendix A "reg-name" | 
|  | const char kRegNameCharacters[] = | 
|  | "abcdefghijklmnopqrstuvwxyz" | 
|  | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | 
|  | "0123456789" | 
|  | "-._~"          // unreserved | 
|  | "%"             // pct-encoded | 
|  | "!$&'()*+,;=";  // sub-delims | 
|  |  | 
|  | // NOTE: Must be in the same order as the ServiceType enum. | 
|  | const char* kValidIceServiceTypes[] = {"stun", "stuns", "turn", "turns"}; | 
|  |  | 
|  | // NOTE: A loop below assumes that the first value of this enum is 0 and all | 
|  | // other values are incremental. | 
|  | enum class ServiceType { | 
|  | STUN = 0,  // Indicates a STUN server. | 
|  | STUNS,     // Indicates a STUN server used with a TLS session. | 
|  | TURN,      // Indicates a TURN server | 
|  | TURNS,     // Indicates a TURN server used with a TLS session. | 
|  | INVALID,   // Unknown. | 
|  | }; | 
|  | static_assert(static_cast<size_t>(ServiceType::INVALID) == | 
|  | arraysize(kValidIceServiceTypes), | 
|  | "kValidIceServiceTypes must have as many strings as ServiceType " | 
|  | "has values."); | 
|  |  | 
|  | // `in_str` should follow of RFC 7064/7065 syntax, but with an optional | 
|  | // "?transport=" already stripped. I.e., | 
|  | // stunURI       = scheme ":" host [ ":" port ] | 
|  | // scheme        = "stun" / "stuns" / "turn" / "turns" | 
|  | // host          = IP-literal / IPv4address / reg-name | 
|  | // port          = *DIGIT | 
|  |  | 
|  | // Return tuple is service_type, host, with service_type == ServiceType::INVALID | 
|  | // on failure. | 
|  | std::tuple<ServiceType, absl::string_view> GetServiceTypeAndHostnameFromUri( | 
|  | absl::string_view in_str) { | 
|  | const auto colonpos = in_str.find(':'); | 
|  | if (colonpos == absl::string_view::npos) { | 
|  | RTC_LOG(LS_WARNING) << "Missing ':' in ICE URI: " << in_str; | 
|  | return {ServiceType::INVALID, ""}; | 
|  | } | 
|  | if ((colonpos + 1) == in_str.length()) { | 
|  | RTC_LOG(LS_WARNING) << "Empty hostname in ICE URI: " << in_str; | 
|  | return {ServiceType::INVALID, ""}; | 
|  | } | 
|  | for (size_t i = 0; i < arraysize(kValidIceServiceTypes); ++i) { | 
|  | if (in_str.compare(0, colonpos, kValidIceServiceTypes[i]) == 0) { | 
|  | return {static_cast<ServiceType>(i), in_str.substr(colonpos + 1)}; | 
|  | } | 
|  | } | 
|  | return {ServiceType::INVALID, ""}; | 
|  | } | 
|  |  | 
|  | std::optional<int> ParsePort(absl::string_view in_str) { | 
|  | // Make sure port only contains digits. StringToNumber doesn't check this. | 
|  | for (const char& c : in_str) { | 
|  | if (!std::isdigit(static_cast<unsigned char>(c))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return StringToNumber<int>(in_str); | 
|  | } | 
|  |  | 
|  | // This method parses IPv6 and IPv4 literal strings, along with hostnames in | 
|  | // standard hostname:port format. | 
|  | // Consider following formats as correct. | 
|  | // `hostname:port`, |[IPV6 address]:port|, |IPv4 address|:port, | 
|  | // `hostname`, |[IPv6 address]|, |IPv4 address|. | 
|  |  | 
|  | // Return tuple is success, host, port. | 
|  | std::tuple<bool, absl::string_view, int> ParseHostnameAndPortFromString( | 
|  | absl::string_view in_str, | 
|  | int default_port) { | 
|  | if (in_str.empty()) { | 
|  | return {false, "", 0}; | 
|  | } | 
|  | absl::string_view host; | 
|  | int port = default_port; | 
|  |  | 
|  | if (in_str.at(0) == '[') { | 
|  | // IP_literal syntax | 
|  | auto closebracket = in_str.rfind(']'); | 
|  | if (closebracket == absl::string_view::npos) { | 
|  | return {false, "", 0}; | 
|  | } | 
|  | auto colonpos = in_str.find(':', closebracket); | 
|  | if (absl::string_view::npos != colonpos) { | 
|  | if (std::optional<int> opt_port = | 
|  | ParsePort(in_str.substr(closebracket + 2))) { | 
|  | port = *opt_port; | 
|  | } else { | 
|  | return {false, "", 0}; | 
|  | } | 
|  | } | 
|  | host = in_str.substr(1, closebracket - 1); | 
|  | } else { | 
|  | // IPv4address or reg-name syntax | 
|  | auto colonpos = in_str.find(':'); | 
|  | if (absl::string_view::npos != colonpos) { | 
|  | if (std::optional<int> opt_port = | 
|  | ParsePort(in_str.substr(colonpos + 1))) { | 
|  | port = *opt_port; | 
|  | } else { | 
|  | return {false, "", 0}; | 
|  | } | 
|  | host = in_str.substr(0, colonpos); | 
|  | } else { | 
|  | host = in_str; | 
|  | } | 
|  | // RFC 3986 section 3.2.2 and Appendix A - "reg-name" syntax | 
|  | if (host.find_first_not_of(kRegNameCharacters) != absl::string_view::npos) { | 
|  | return {false, "", 0}; | 
|  | } | 
|  | } | 
|  | return {!host.empty(), host, port}; | 
|  | } | 
|  |  | 
|  | // Adds a STUN or TURN server to the appropriate list, | 
|  | // by parsing `url` and using the username/password in `server`. | 
|  | RTCError ParseIceServerUrl(const PeerConnectionInterface::IceServer& server, | 
|  | absl::string_view url, | 
|  | ServerAddresses* stun_servers, | 
|  | std::vector<RelayServerConfig>* turn_servers) { | 
|  | // RFC 7064 | 
|  | // stunURI       = scheme ":" host [ ":" port ] | 
|  | // scheme        = "stun" / "stuns" | 
|  |  | 
|  | // RFC 7065 | 
|  | // turnURI       = scheme ":" host [ ":" port ] | 
|  | //                 [ "?transport=" transport ] | 
|  | // scheme        = "turn" / "turns" | 
|  | // transport     = "udp" / "tcp" / transport-ext | 
|  | // transport-ext = 1*unreserved | 
|  |  | 
|  | // RFC 3986 | 
|  | // host     = IP-literal / IPv4address / reg-name | 
|  | // port     = *DIGIT | 
|  |  | 
|  | RTC_DCHECK(stun_servers != nullptr); | 
|  | RTC_DCHECK(turn_servers != nullptr); | 
|  | ProtocolType turn_transport_type = PROTO_UDP; | 
|  | RTC_DCHECK(!url.empty()); | 
|  | std::vector<absl::string_view> tokens = split(url, '?'); | 
|  | absl::string_view uri_without_transport = tokens[0]; | 
|  | // Let's look into transport= param, if it exists. | 
|  | if (tokens.size() == kTurnTransportTokensNum) {  // ?transport= is present. | 
|  | std::vector<absl::string_view> transport_tokens = split(tokens[1], '='); | 
|  | if (transport_tokens[0] != kTransport) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid transport parameter key."); | 
|  | } | 
|  | if (transport_tokens.size() < 2) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Transport parameter missing value."); | 
|  | } | 
|  |  | 
|  | std::optional<ProtocolType> proto = StringToProto(transport_tokens[1]); | 
|  | if (!proto || (*proto != PROTO_UDP && *proto != PROTO_TCP)) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Transport parameter should " | 
|  | "always be udp or tcp."); | 
|  | } | 
|  | turn_transport_type = *proto; | 
|  | } | 
|  |  | 
|  | auto [service_type, hoststring] = | 
|  | GetServiceTypeAndHostnameFromUri(uri_without_transport); | 
|  | if (service_type == ServiceType::INVALID) { | 
|  | RTC_LOG(LS_ERROR) << "Invalid transport parameter in ICE URI: " << url; | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid transport parameter in ICE URI"); | 
|  | } | 
|  |  | 
|  | // GetServiceTypeAndHostnameFromUri should never give an empty hoststring | 
|  | RTC_DCHECK(!hoststring.empty()); | 
|  |  | 
|  | // stun with ?transport (or any ?) is not valid. | 
|  | if ((service_type == ServiceType::STUN || | 
|  | service_type == ServiceType::STUNS) && | 
|  | tokens.size() > 1) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid stun url with query parameters"); | 
|  | } | 
|  |  | 
|  | int default_port = kDefaultStunPort; | 
|  | if (service_type == ServiceType::TURNS) { | 
|  | default_port = kDefaultStunTlsPort; | 
|  | turn_transport_type = PROTO_TLS; | 
|  | } | 
|  |  | 
|  | if (hoststring.find('@') != absl::string_view::npos) { | 
|  | RTC_LOG(LS_ERROR) << "Invalid url with long deprecated user@host syntax: " | 
|  | << uri_without_transport; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid url with long " | 
|  | "deprecated user@host syntax"); | 
|  | } | 
|  |  | 
|  | auto [success, address, port] = | 
|  | ParseHostnameAndPortFromString(hoststring, default_port); | 
|  | if (!success) { | 
|  | RTC_LOG(LS_ERROR) << "Invalid hostname format: " << uri_without_transport; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid hostname format"); | 
|  | } | 
|  |  | 
|  | if (port <= 0 || port > 0xffff) { | 
|  | RTC_LOG(LS_ERROR) << "Invalid port: " << port; | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Invalid port"); | 
|  | } | 
|  |  | 
|  | switch (service_type) { | 
|  | case ServiceType::STUN: | 
|  | case ServiceType::STUNS: | 
|  | stun_servers->insert(SocketAddress(address, port)); | 
|  | break; | 
|  | case ServiceType::TURN: | 
|  | case ServiceType::TURNS: { | 
|  | if (server.username.empty() || server.password.empty()) { | 
|  | // The WebRTC spec requires throwing an InvalidAccessError when username | 
|  | // or credential are ommitted; this is the native equivalent. | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "ICE server parsing failed: TURN server with empty " | 
|  | "username or password"); | 
|  | } | 
|  | // RFC 8489 limits the size of the STUN username field to 509 characters. | 
|  | if (server.username.size() > kMaxTurnUsernameLength) { | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "ICE server parsing failed: TURN server username is too long"); | 
|  | } | 
|  | // If the hostname field is not empty, then the server address must be | 
|  | // the resolved IP for that host, the hostname is needed later for TLS | 
|  | // handshake (SNI and Certificate verification). | 
|  | absl::string_view hostname = | 
|  | server.hostname.empty() ? address : server.hostname; | 
|  | SocketAddress socket_address(hostname, port); | 
|  | if (!server.hostname.empty()) { | 
|  | IPAddress ip; | 
|  | if (!IPFromString(address, &ip)) { | 
|  | // When hostname is set, the server address must be a | 
|  | // resolved ip address. | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INVALID_PARAMETER, | 
|  | "ICE server parsing failed: " | 
|  | "IceServer has hostname field set, but URI does not " | 
|  | "contain an IP address."); | 
|  | } | 
|  | socket_address.SetResolvedIP(ip); | 
|  | } | 
|  | RelayServerConfig config = | 
|  | RelayServerConfig(socket_address, server.username, server.password, | 
|  | turn_transport_type); | 
|  | if (server.tls_cert_policy == | 
|  | PeerConnectionInterface::kTlsCertPolicyInsecureNoCheck) { | 
|  | config.tls_cert_policy = | 
|  | TlsCertPolicy::TLS_CERT_POLICY_INSECURE_NO_CHECK; | 
|  | } | 
|  | config.tls_alpn_protocols = server.tls_alpn_protocols; | 
|  | config.tls_elliptic_curves = server.tls_elliptic_curves; | 
|  |  | 
|  | turn_servers->push_back(config); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | // We shouldn't get to this point with an invalid service_type, we should | 
|  | // have returned an error already. | 
|  | LOG_AND_RETURN_ERROR( | 
|  | RTCErrorType::INTERNAL_ERROR, | 
|  | "ICE server parsing failed: Unexpected service type"); | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | RTCError ParseIceServersOrError( | 
|  | const PeerConnectionInterface::IceServers& servers, | 
|  | ServerAddresses* stun_servers, | 
|  | std::vector<RelayServerConfig>* turn_servers) { | 
|  | for (const PeerConnectionInterface::IceServer& server : servers) { | 
|  | if (!server.urls.empty()) { | 
|  | for (const std::string& url : server.urls) { | 
|  | if (url.empty()) { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Empty uri."); | 
|  | } | 
|  | RTCError err = | 
|  | ParseIceServerUrl(server, url, stun_servers, turn_servers); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } | 
|  | } else if (!server.uri.empty()) { | 
|  | // Fallback to old .uri if new .urls isn't present. | 
|  | RTCError err = | 
|  | ParseIceServerUrl(server, server.uri, stun_servers, turn_servers); | 
|  |  | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  | } else { | 
|  | LOG_AND_RETURN_ERROR(RTCErrorType::SYNTAX_ERROR, | 
|  | "ICE server parsing failed: Empty uri."); | 
|  | } | 
|  | } | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | RTCError ParseAndValidateIceServersFromConfiguration( | 
|  | const PeerConnectionInterface::RTCConfiguration& configuration, | 
|  | ServerAddresses& stun_servers, | 
|  | std::vector<RelayServerConfig>& turn_servers) { | 
|  | RTC_DCHECK(stun_servers.empty()); | 
|  | RTC_DCHECK(turn_servers.empty()); | 
|  | RTCError err = ParseIceServersOrError(configuration.servers, &stun_servers, | 
|  | &turn_servers); | 
|  | if (!err.ok()) { | 
|  | return err; | 
|  | } | 
|  |  | 
|  | // Restrict number of TURN servers. | 
|  | if (turn_servers.size() > kMaxTurnServers) { | 
|  | RTC_LOG(LS_WARNING) << "Number of configured TURN servers is " | 
|  | << turn_servers.size() | 
|  | << " which exceeds the maximum allowed number of " | 
|  | << kMaxTurnServers; | 
|  | turn_servers.resize(kMaxTurnServers); | 
|  | } | 
|  |  | 
|  | // Add the turn logging id to all turn servers | 
|  | for (RelayServerConfig& turn_server : turn_servers) { | 
|  | turn_server.turn_logging_id = configuration.turn_logging_id; | 
|  | } | 
|  |  | 
|  | return RTCError::OK(); | 
|  | } | 
|  |  | 
|  | }  // namespace webrtc |