| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/quic/crypto/quic_crypto_server_config.h" | 5 #include "net/quic/crypto/quic_crypto_server_config.h" |
| 6 | 6 |
| 7 #include <stdlib.h> | 7 #include <stdlib.h> |
| 8 #include <algorithm> | 8 #include <algorithm> |
| 9 | 9 |
| 10 #include "base/stl_util.h" | 10 #include "base/stl_util.h" |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 202 delete this; | 202 delete this; |
| 203 } | 203 } |
| 204 | 204 |
| 205 QuicCryptoServerConfig::ConfigOptions::ConfigOptions() | 205 QuicCryptoServerConfig::ConfigOptions::ConfigOptions() |
| 206 : expiry_time(QuicWallTime::Zero()), | 206 : expiry_time(QuicWallTime::Zero()), |
| 207 channel_id_enabled(false), | 207 channel_id_enabled(false), |
| 208 p256(false) {} | 208 p256(false) {} |
| 209 | 209 |
| 210 QuicCryptoServerConfig::QuicCryptoServerConfig( | 210 QuicCryptoServerConfig::QuicCryptoServerConfig( |
| 211 StringPiece source_address_token_secret, | 211 StringPiece source_address_token_secret, |
| 212 QuicRandom* rand) | 212 QuicRandom* server_nonce_entropy, |
| 213 ProofSource* proof_source) |
| 213 : replay_protection_(true), | 214 : replay_protection_(true), |
| 214 configs_lock_(), | 215 configs_lock_(), |
| 215 primary_config_(nullptr), | 216 primary_config_(nullptr), |
| 216 next_config_promotion_time_(QuicWallTime::Zero()), | 217 next_config_promotion_time_(QuicWallTime::Zero()), |
| 217 server_nonce_strike_register_lock_(), | 218 server_nonce_strike_register_lock_(), |
| 219 proof_source_(proof_source), |
| 218 strike_register_no_startup_period_(false), | 220 strike_register_no_startup_period_(false), |
| 219 strike_register_max_entries_(1 << 10), | 221 strike_register_max_entries_(1 << 10), |
| 220 strike_register_window_secs_(600), | 222 strike_register_window_secs_(600), |
| 221 source_address_token_future_secs_(3600), | 223 source_address_token_future_secs_(3600), |
| 222 source_address_token_lifetime_secs_(86400), | 224 source_address_token_lifetime_secs_(86400), |
| 223 server_nonce_strike_register_max_entries_(1 << 10), | 225 server_nonce_strike_register_max_entries_(1 << 10), |
| 224 server_nonce_strike_register_window_secs_(120) { | 226 server_nonce_strike_register_window_secs_(120) { |
| 227 DCHECK(proof_source_.get()); |
| 225 default_source_address_token_boxer_.SetKey( | 228 default_source_address_token_boxer_.SetKey( |
| 226 DeriveSourceAddressTokenKey(source_address_token_secret)); | 229 DeriveSourceAddressTokenKey(source_address_token_secret)); |
| 227 | 230 |
| 228 // Generate a random key and orbit for server nonces. | 231 // Generate a random key and orbit for server nonces. |
| 229 rand->RandBytes(server_nonce_orbit_, sizeof(server_nonce_orbit_)); | 232 server_nonce_entropy->RandBytes(server_nonce_orbit_, |
| 233 sizeof(server_nonce_orbit_)); |
| 230 const size_t key_size = server_nonce_boxer_.GetKeySize(); | 234 const size_t key_size = server_nonce_boxer_.GetKeySize(); |
| 231 scoped_ptr<uint8[]> key_bytes(new uint8[key_size]); | 235 scoped_ptr<uint8[]> key_bytes(new uint8[key_size]); |
| 232 rand->RandBytes(key_bytes.get(), key_size); | 236 server_nonce_entropy->RandBytes(key_bytes.get(), key_size); |
| 233 | 237 |
| 234 server_nonce_boxer_.SetKey( | 238 server_nonce_boxer_.SetKey( |
| 235 StringPiece(reinterpret_cast<char*>(key_bytes.get()), key_size)); | 239 StringPiece(reinterpret_cast<char*>(key_bytes.get()), key_size)); |
| 236 } | 240 } |
| 237 | 241 |
| 238 QuicCryptoServerConfig::~QuicCryptoServerConfig() { | 242 QuicCryptoServerConfig::~QuicCryptoServerConfig() { |
| 239 primary_config_ = nullptr; | 243 primary_config_ = nullptr; |
| 240 } | 244 } |
| 241 | 245 |
| 242 // static | 246 // static |
| (...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 596 if (validate_chlo_result.error_code != QUIC_NO_ERROR) { | 600 if (validate_chlo_result.error_code != QUIC_NO_ERROR) { |
| 597 *error_details = validate_chlo_result.error_details; | 601 *error_details = validate_chlo_result.error_details; |
| 598 return validate_chlo_result.error_code; | 602 return validate_chlo_result.error_code; |
| 599 } | 603 } |
| 600 | 604 |
| 601 out->Clear(); | 605 out->Clear(); |
| 602 | 606 |
| 603 bool x509_supported = false; | 607 bool x509_supported = false; |
| 604 bool x509_ecdsa_supported = false; | 608 bool x509_ecdsa_supported = false; |
| 605 ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported); | 609 ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported); |
| 606 if (proof_source_.get() && !crypto_proof->certs && | 610 DCHECK(proof_source_.get()); |
| 611 if (!crypto_proof->certs && |
| 607 !proof_source_->GetProof(server_ip, info.sni.as_string(), | 612 !proof_source_->GetProof(server_ip, info.sni.as_string(), |
| 608 primary_config->serialized, x509_ecdsa_supported, | 613 primary_config->serialized, x509_ecdsa_supported, |
| 609 &crypto_proof->certs, | 614 &crypto_proof->certs, |
| 610 &crypto_proof->signature)) { | 615 &crypto_proof->signature)) { |
| 611 return QUIC_HANDSHAKE_FAILED; | 616 return QUIC_HANDSHAKE_FAILED; |
| 612 } | 617 } |
| 613 | 618 |
| 614 if (!info.reject_reasons.empty() || !requested_config.get()) { | 619 if (!info.reject_reasons.empty() || !requested_config.get()) { |
| 615 BuildRejection(*primary_config, client_hello, info, | 620 BuildRejection(*primary_config, client_hello, info, |
| 616 validate_chlo_result.cached_network_params, | 621 validate_chlo_result.cached_network_params, |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 672 hkdf_suffix.append(reinterpret_cast<char*>(&connection_id), | 677 hkdf_suffix.append(reinterpret_cast<char*>(&connection_id), |
| 673 sizeof(connection_id)); | 678 sizeof(connection_id)); |
| 674 hkdf_suffix.append(client_hello_serialized.data(), | 679 hkdf_suffix.append(client_hello_serialized.data(), |
| 675 client_hello_serialized.length()); | 680 client_hello_serialized.length()); |
| 676 hkdf_suffix.append(requested_config->serialized); | 681 hkdf_suffix.append(requested_config->serialized); |
| 677 // The addition of x509_supported in this if statement is so that an insecure | 682 // The addition of x509_supported in this if statement is so that an insecure |
| 678 // quic client talking to a secure quic server will not result in the secure | 683 // quic client talking to a secure quic server will not result in the secure |
| 679 // quic server adding the cert to the kdf. | 684 // quic server adding the cert to the kdf. |
| 680 // TODO(nharper): Should a server that is configured to be secure (i.e. one | 685 // TODO(nharper): Should a server that is configured to be secure (i.e. one |
| 681 // that has a proof_source_) be accepting responses from an insecure client? | 686 // that has a proof_source_) be accepting responses from an insecure client? |
| 682 if (version > QUIC_VERSION_25 && proof_source_.get() && x509_supported) { | 687 DCHECK(proof_source_.get()); |
| 688 if (version > QUIC_VERSION_25 && x509_supported) { |
| 683 if (crypto_proof->certs->empty()) { | 689 if (crypto_proof->certs->empty()) { |
| 684 *error_details = "Failed to get certs"; | 690 *error_details = "Failed to get certs"; |
| 685 return QUIC_CRYPTO_INTERNAL_ERROR; | 691 return QUIC_CRYPTO_INTERNAL_ERROR; |
| 686 } | 692 } |
| 687 hkdf_suffix.append(crypto_proof->certs->at(0)); | 693 hkdf_suffix.append(crypto_proof->certs->at(0)); |
| 688 } | 694 } |
| 689 | 695 |
| 690 StringPiece cetv_ciphertext; | 696 StringPiece cetv_ciphertext; |
| 691 if (requested_config->channel_id_enabled && | 697 if (requested_config->channel_id_enabled && |
| 692 client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) { | 698 client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) { |
| (...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1001 helper.ValidationComplete(QUIC_NO_ERROR, ""); | 1007 helper.ValidationComplete(QUIC_NO_ERROR, ""); |
| 1002 return; | 1008 return; |
| 1003 } | 1009 } |
| 1004 found_error = true; | 1010 found_error = true; |
| 1005 } | 1011 } |
| 1006 | 1012 |
| 1007 if (version > QUIC_VERSION_25) { | 1013 if (version > QUIC_VERSION_25) { |
| 1008 bool x509_supported = false; | 1014 bool x509_supported = false; |
| 1009 bool x509_ecdsa_supported = false; | 1015 bool x509_ecdsa_supported = false; |
| 1010 ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported); | 1016 ParseProofDemand(client_hello, &x509_supported, &x509_ecdsa_supported); |
| 1011 if (proof_source_.get() && | 1017 if (!proof_source_->GetProof(server_ip, info->sni.as_string(), |
| 1012 !proof_source_->GetProof(server_ip, info->sni.as_string(), | |
| 1013 requested_config->serialized, | 1018 requested_config->serialized, |
| 1014 x509_ecdsa_supported, &crypto_proof->certs, | 1019 x509_ecdsa_supported, &crypto_proof->certs, |
| 1015 &crypto_proof->signature)) { | 1020 &crypto_proof->signature)) { |
| 1016 found_error = true; | 1021 found_error = true; |
| 1017 info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); | 1022 info->reject_reasons.push_back(SERVER_CONFIG_UNKNOWN_CONFIG_FAILURE); |
| 1018 } | 1023 } |
| 1019 | 1024 |
| 1020 if (!ValidateExpectedLeafCertificate(client_hello, *crypto_proof)) { | 1025 if (!ValidateExpectedLeafCertificate(client_hello, *crypto_proof)) { |
| 1021 found_error = true; | 1026 found_error = true; |
| 1022 info->reject_reasons.push_back(INVALID_EXPECTED_LEAF_CERTIFICATE); | 1027 info->reject_reasons.push_back(INVALID_EXPECTED_LEAF_CERTIFICATE); |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1120 CryptoHandshakeMessage* out) const { | 1125 CryptoHandshakeMessage* out) const { |
| 1121 base::AutoLock locked(configs_lock_); | 1126 base::AutoLock locked(configs_lock_); |
| 1122 out->set_tag(kSCUP); | 1127 out->set_tag(kSCUP); |
| 1123 out->SetStringPiece(kSCFG, primary_config_->serialized); | 1128 out->SetStringPiece(kSCFG, primary_config_->serialized); |
| 1124 out->SetStringPiece( | 1129 out->SetStringPiece( |
| 1125 kSourceAddressTokenTag, | 1130 kSourceAddressTokenTag, |
| 1126 NewSourceAddressToken(*primary_config_.get(), | 1131 NewSourceAddressToken(*primary_config_.get(), |
| 1127 previous_source_address_tokens, client_ip, rand, | 1132 previous_source_address_tokens, client_ip, rand, |
| 1128 clock->WallNow(), cached_network_params)); | 1133 clock->WallNow(), cached_network_params)); |
| 1129 | 1134 |
| 1130 if (proof_source_ == nullptr) { | |
| 1131 // Insecure QUIC, can send SCFG without proof. | |
| 1132 return true; | |
| 1133 } | |
| 1134 | |
| 1135 const vector<string>* certs; | 1135 const vector<string>* certs; |
| 1136 string signature; | 1136 string signature; |
| 1137 if (!proof_source_->GetProof( | 1137 if (!proof_source_->GetProof( |
| 1138 server_ip, params.sni, primary_config_->serialized, | 1138 server_ip, params.sni, primary_config_->serialized, |
| 1139 params.x509_ecdsa_supported, &certs, &signature)) { | 1139 params.x509_ecdsa_supported, &certs, &signature)) { |
| 1140 DVLOG(1) << "Server: failed to get proof."; | 1140 DVLOG(1) << "Server: failed to get proof."; |
| 1141 return false; | 1141 return false; |
| 1142 } | 1142 } |
| 1143 | 1143 |
| 1144 const string compressed = CertCompressor::CompressChain( | 1144 const string compressed = CertCompressor::CompressChain( |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1184 out->SetVector(kRREJ, info.reject_reasons); | 1184 out->SetVector(kRREJ, info.reject_reasons); |
| 1185 | 1185 |
| 1186 // The client may have requested a certificate chain. | 1186 // The client may have requested a certificate chain. |
| 1187 bool x509_supported = false; | 1187 bool x509_supported = false; |
| 1188 ParseProofDemand(client_hello, &x509_supported, | 1188 ParseProofDemand(client_hello, &x509_supported, |
| 1189 ¶ms->x509_ecdsa_supported); | 1189 ¶ms->x509_ecdsa_supported); |
| 1190 if (!x509_supported) { | 1190 if (!x509_supported) { |
| 1191 return; | 1191 return; |
| 1192 } | 1192 } |
| 1193 | 1193 |
| 1194 if (!proof_source_.get()) { | |
| 1195 return; | |
| 1196 } | |
| 1197 | |
| 1198 StringPiece client_common_set_hashes; | 1194 StringPiece client_common_set_hashes; |
| 1199 if (client_hello.GetStringPiece(kCCS, &client_common_set_hashes)) { | 1195 if (client_hello.GetStringPiece(kCCS, &client_common_set_hashes)) { |
| 1200 params->client_common_set_hashes = client_common_set_hashes.as_string(); | 1196 params->client_common_set_hashes = client_common_set_hashes.as_string(); |
| 1201 } | 1197 } |
| 1202 | 1198 |
| 1203 StringPiece client_cached_cert_hashes; | 1199 StringPiece client_cached_cert_hashes; |
| 1204 if (client_hello.GetStringPiece(kCCRT, &client_cached_cert_hashes)) { | 1200 if (client_hello.GetStringPiece(kCCRT, &client_cached_cert_hashes)) { |
| 1205 params->client_cached_cert_hashes = client_cached_cert_hashes.as_string(); | 1201 params->client_cached_cert_hashes = client_cached_cert_hashes.as_string(); |
| 1206 } | 1202 } |
| 1207 | 1203 |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1391 return nullptr; | 1387 return nullptr; |
| 1392 } | 1388 } |
| 1393 } | 1389 } |
| 1394 | 1390 |
| 1395 config->key_exchanges.push_back(ka.release()); | 1391 config->key_exchanges.push_back(ka.release()); |
| 1396 } | 1392 } |
| 1397 | 1393 |
| 1398 return config; | 1394 return config; |
| 1399 } | 1395 } |
| 1400 | 1396 |
| 1401 void QuicCryptoServerConfig::SetProofSource(ProofSource* proof_source) { | |
| 1402 proof_source_.reset(proof_source); | |
| 1403 } | |
| 1404 | |
| 1405 void QuicCryptoServerConfig::SetEphemeralKeySource( | 1397 void QuicCryptoServerConfig::SetEphemeralKeySource( |
| 1406 EphemeralKeySource* ephemeral_key_source) { | 1398 EphemeralKeySource* ephemeral_key_source) { |
| 1407 ephemeral_key_source_.reset(ephemeral_key_source); | 1399 ephemeral_key_source_.reset(ephemeral_key_source); |
| 1408 } | 1400 } |
| 1409 | 1401 |
| 1410 void QuicCryptoServerConfig::SetStrikeRegisterClient( | 1402 void QuicCryptoServerConfig::SetStrikeRegisterClient( |
| 1411 StrikeRegisterClient* strike_register_client) { | 1403 StrikeRegisterClient* strike_register_client) { |
| 1412 base::AutoLock locker(strike_register_client_lock_); | 1404 base::AutoLock locker(strike_register_client_lock_); |
| 1413 DCHECK(!strike_register_client_.get()); | 1405 DCHECK(!strike_register_client_.get()); |
| 1414 strike_register_client_.reset(strike_register_client); | 1406 strike_register_client_.reset(strike_register_client); |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1497 continue; | 1489 continue; |
| 1498 } | 1490 } |
| 1499 | 1491 |
| 1500 *(source_address_tokens.add_tokens()) = token; | 1492 *(source_address_tokens.add_tokens()) = token; |
| 1501 } | 1493 } |
| 1502 | 1494 |
| 1503 return config.source_address_token_boxer->Box( | 1495 return config.source_address_token_boxer->Box( |
| 1504 rand, source_address_tokens.SerializeAsString()); | 1496 rand, source_address_tokens.SerializeAsString()); |
| 1505 } | 1497 } |
| 1506 | 1498 |
| 1507 bool QuicCryptoServerConfig::HasProofSource() const { | |
| 1508 return proof_source_ != nullptr; | |
| 1509 } | |
| 1510 | |
| 1511 int QuicCryptoServerConfig::NumberOfConfigs() const { | 1499 int QuicCryptoServerConfig::NumberOfConfigs() const { |
| 1512 base::AutoLock locked(configs_lock_); | 1500 base::AutoLock locked(configs_lock_); |
| 1513 return configs_.size(); | 1501 return configs_.size(); |
| 1514 } | 1502 } |
| 1515 | 1503 |
| 1516 HandshakeFailureReason QuicCryptoServerConfig::ParseSourceAddressToken( | 1504 HandshakeFailureReason QuicCryptoServerConfig::ParseSourceAddressToken( |
| 1517 const Config& config, | 1505 const Config& config, |
| 1518 StringPiece token, | 1506 StringPiece token, |
| 1519 SourceAddressTokens* tokens) const { | 1507 SourceAddressTokens* tokens) const { |
| 1520 string storage; | 1508 string storage; |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1670 case STRIKE_REGISTER_FAILURE: | 1658 case STRIKE_REGISTER_FAILURE: |
| 1671 default: | 1659 default: |
| 1672 LOG(DFATAL) << "Unexpected server nonce error: " << nonce_error; | 1660 LOG(DFATAL) << "Unexpected server nonce error: " << nonce_error; |
| 1673 return SERVER_NONCE_NOT_UNIQUE_FAILURE; | 1661 return SERVER_NONCE_NOT_UNIQUE_FAILURE; |
| 1674 } | 1662 } |
| 1675 } | 1663 } |
| 1676 | 1664 |
| 1677 bool QuicCryptoServerConfig::ValidateExpectedLeafCertificate( | 1665 bool QuicCryptoServerConfig::ValidateExpectedLeafCertificate( |
| 1678 const CryptoHandshakeMessage& client_hello, | 1666 const CryptoHandshakeMessage& client_hello, |
| 1679 const QuicCryptoProof& crypto_proof) const { | 1667 const QuicCryptoProof& crypto_proof) const { |
| 1680 // If the server doesn't use https, then the client won't send XLCT and | |
| 1681 // proof_source_ will be null, so in this case return true. | |
| 1682 if (!proof_source_.get()) { | |
| 1683 return true; | |
| 1684 } | |
| 1685 if (crypto_proof.certs->empty()) { | 1668 if (crypto_proof.certs->empty()) { |
| 1686 return false; | 1669 return false; |
| 1687 } | 1670 } |
| 1688 | 1671 |
| 1689 uint64 hash_from_client; | 1672 uint64 hash_from_client; |
| 1690 if (client_hello.GetUint64(kXLCT, &hash_from_client) != QUIC_NO_ERROR) { | 1673 if (client_hello.GetUint64(kXLCT, &hash_from_client) != QUIC_NO_ERROR) { |
| 1691 return false; | 1674 return false; |
| 1692 } | 1675 } |
| 1693 return CryptoUtils::ComputeLeafCertHash(crypto_proof.certs->at(0)) == | 1676 return CryptoUtils::ComputeLeafCertHash(crypto_proof.certs->at(0)) == |
| 1694 hash_from_client; | 1677 hash_from_client; |
| 1695 } | 1678 } |
| 1696 | 1679 |
| 1697 void QuicCryptoServerConfig::ParseProofDemand( | 1680 void QuicCryptoServerConfig::ParseProofDemand( |
| 1698 const CryptoHandshakeMessage& client_hello, | 1681 const CryptoHandshakeMessage& client_hello, |
| 1699 bool* x509_supported, | 1682 bool* x509_supported, |
| 1700 bool* x509_ecdsa_supported) const { | 1683 bool* x509_ecdsa_supported) const { |
| 1701 const QuicTag* their_proof_demands; | 1684 const QuicTag* their_proof_demands; |
| 1702 size_t num_their_proof_demands; | 1685 size_t num_their_proof_demands; |
| 1703 | 1686 |
| 1704 if (proof_source_.get() == nullptr || | 1687 if (client_hello.GetTaglist(kPDMD, &their_proof_demands, |
| 1705 client_hello.GetTaglist(kPDMD, &their_proof_demands, | |
| 1706 &num_their_proof_demands) != QUIC_NO_ERROR) { | 1688 &num_their_proof_demands) != QUIC_NO_ERROR) { |
| 1707 return; | 1689 return; |
| 1708 } | 1690 } |
| 1709 | 1691 |
| 1710 *x509_supported = false; | 1692 *x509_supported = false; |
| 1711 for (size_t i = 0; i < num_their_proof_demands; i++) { | 1693 for (size_t i = 0; i < num_their_proof_demands; i++) { |
| 1712 switch (their_proof_demands[i]) { | 1694 switch (their_proof_demands[i]) { |
| 1713 case kX509: | 1695 case kX509: |
| 1714 *x509_supported = true; | 1696 *x509_supported = true; |
| 1715 *x509_ecdsa_supported = true; | 1697 *x509_ecdsa_supported = true; |
| 1716 break; | 1698 break; |
| 1717 case kX59R: | 1699 case kX59R: |
| 1718 *x509_supported = true; | 1700 *x509_supported = true; |
| 1719 break; | 1701 break; |
| 1720 } | 1702 } |
| 1721 } | 1703 } |
| 1722 } | 1704 } |
| 1723 | 1705 |
| 1724 QuicCryptoServerConfig::Config::Config() | 1706 QuicCryptoServerConfig::Config::Config() |
| 1725 : channel_id_enabled(false), | 1707 : channel_id_enabled(false), |
| 1726 is_primary(false), | 1708 is_primary(false), |
| 1727 primary_time(QuicWallTime::Zero()), | 1709 primary_time(QuicWallTime::Zero()), |
| 1728 priority(0), | 1710 priority(0), |
| 1729 source_address_token_boxer(nullptr) {} | 1711 source_address_token_boxer(nullptr) {} |
| 1730 | 1712 |
| 1731 QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); } | 1713 QuicCryptoServerConfig::Config::~Config() { STLDeleteElements(&key_exchanges); } |
| 1732 | 1714 |
| 1733 } // namespace net | 1715 } // namespace net |
| OLD | NEW |