| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/tools/quic/quic_dispatcher.h" | 5 #include "net/tools/quic/quic_dispatcher.h" |
| 6 | 6 |
| 7 #include <errno.h> | 7 #include <errno.h> |
| 8 | 8 |
| 9 #include "base/debug/stack_trace.h" | 9 #include "base/debug/stack_trace.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
| 12 #include "net/quic/quic_blocked_writer_interface.h" | 12 #include "net/quic/quic_blocked_writer_interface.h" |
| 13 #include "net/quic/quic_flags.h" | 13 #include "net/quic/quic_flags.h" |
| 14 #include "net/quic/quic_utils.h" | 14 #include "net/quic/quic_utils.h" |
| 15 #include "net/tools/quic/quic_default_packet_writer.h" | 15 #include "net/tools/quic/quic_default_packet_writer.h" |
| 16 #include "net/tools/quic/quic_epoll_connection_helper.h" | 16 #include "net/tools/quic/quic_epoll_connection_helper.h" |
| 17 #include "net/tools/quic/quic_socket_utils.h" | 17 #include "net/tools/quic/quic_socket_utils.h" |
| 18 #include "net/tools/quic/quic_time_wait_list_manager.h" | 18 #include "net/tools/quic/quic_time_wait_list_manager.h" |
| 19 | 19 |
| 20 namespace net { | 20 namespace net { |
| 21 | 21 |
| 22 namespace tools { | 22 namespace tools { |
| 23 | 23 |
| 24 using base::StringPiece; | 24 using base::StringPiece; |
| 25 using std::make_pair; | 25 using std::make_pair; |
| 26 | 26 |
| 27 class DeleteSessionsAlarm : public EpollAlarm { | 27 class DeleteSessionsAlarm : public EpollAlarm { |
| 28 public: | 28 public: |
| 29 explicit DeleteSessionsAlarm(QuicDispatcher* dispatcher) | 29 explicit DeleteSessionsAlarm(QuicDispatcher* dispatcher) |
| 30 : dispatcher_(dispatcher) { | 30 : dispatcher_(dispatcher) {} |
| 31 } | |
| 32 | 31 |
| 33 virtual int64 OnAlarm() OVERRIDE { | 32 virtual int64 OnAlarm() OVERRIDE { |
| 34 EpollAlarm::OnAlarm(); | 33 EpollAlarm::OnAlarm(); |
| 35 dispatcher_->DeleteSessions(); | 34 dispatcher_->DeleteSessions(); |
| 36 return 0; | 35 return 0; |
| 37 } | 36 } |
| 38 | 37 |
| 39 private: | 38 private: |
| 40 QuicDispatcher* dispatcher_; | 39 QuicDispatcher* dispatcher_; |
| 41 }; | 40 }; |
| 42 | 41 |
| 43 class QuicDispatcher::QuicFramerVisitor : public QuicFramerVisitorInterface { | 42 class QuicDispatcher::QuicFramerVisitor : public QuicFramerVisitorInterface { |
| 44 public: | 43 public: |
| 45 explicit QuicFramerVisitor(QuicDispatcher* dispatcher) | 44 explicit QuicFramerVisitor(QuicDispatcher* dispatcher) |
| 46 : dispatcher_(dispatcher), | 45 : dispatcher_(dispatcher), connection_id_(0) {} |
| 47 connection_id_(0) {} | |
| 48 | 46 |
| 49 // QuicFramerVisitorInterface implementation | 47 // QuicFramerVisitorInterface implementation |
| 50 virtual void OnPacket() OVERRIDE {} | 48 virtual void OnPacket() OVERRIDE {} |
| 51 virtual bool OnUnauthenticatedPublicHeader( | 49 virtual bool OnUnauthenticatedPublicHeader( |
| 52 const QuicPacketPublicHeader& header) OVERRIDE { | 50 const QuicPacketPublicHeader& header) OVERRIDE { |
| 53 connection_id_ = header.connection_id; | 51 connection_id_ = header.connection_id; |
| 54 return dispatcher_->OnUnauthenticatedPublicHeader(header); | 52 return dispatcher_->OnUnauthenticatedPublicHeader(header); |
| 55 } | 53 } |
| 56 virtual bool OnUnauthenticatedHeader( | 54 virtual bool OnUnauthenticatedHeader( |
| 57 const QuicPacketHeader& header) OVERRIDE { | 55 const QuicPacketHeader& header) OVERRIDE { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 87 const QuicVersionNegotiationPacket& /*packet*/) OVERRIDE { | 85 const QuicVersionNegotiationPacket& /*packet*/) OVERRIDE { |
| 88 DCHECK(false); | 86 DCHECK(false); |
| 89 } | 87 } |
| 90 virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE { | 88 virtual void OnDecryptedPacket(EncryptionLevel level) OVERRIDE { |
| 91 DCHECK(false); | 89 DCHECK(false); |
| 92 } | 90 } |
| 93 virtual bool OnPacketHeader(const QuicPacketHeader& /*header*/) OVERRIDE { | 91 virtual bool OnPacketHeader(const QuicPacketHeader& /*header*/) OVERRIDE { |
| 94 DCHECK(false); | 92 DCHECK(false); |
| 95 return false; | 93 return false; |
| 96 } | 94 } |
| 97 virtual void OnRevivedPacket() OVERRIDE { | 95 virtual void OnRevivedPacket() OVERRIDE { DCHECK(false); } |
| 98 DCHECK(false); | |
| 99 } | |
| 100 virtual void OnFecProtectedPayload(StringPiece /*payload*/) OVERRIDE { | 96 virtual void OnFecProtectedPayload(StringPiece /*payload*/) OVERRIDE { |
| 101 DCHECK(false); | 97 DCHECK(false); |
| 102 } | 98 } |
| 103 virtual bool OnStreamFrame(const QuicStreamFrame& /*frame*/) OVERRIDE { | 99 virtual bool OnStreamFrame(const QuicStreamFrame& /*frame*/) OVERRIDE { |
| 104 DCHECK(false); | 100 DCHECK(false); |
| 105 return false; | 101 return false; |
| 106 } | 102 } |
| 107 virtual bool OnAckFrame(const QuicAckFrame& /*frame*/) OVERRIDE { | 103 virtual bool OnAckFrame(const QuicAckFrame& /*frame*/) OVERRIDE { |
| 108 DCHECK(false); | 104 DCHECK(false); |
| 109 return false; | 105 return false; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 120 } | 116 } |
| 121 virtual bool OnPingFrame(const QuicPingFrame& /*frame*/) OVERRIDE { | 117 virtual bool OnPingFrame(const QuicPingFrame& /*frame*/) OVERRIDE { |
| 122 DCHECK(false); | 118 DCHECK(false); |
| 123 return false; | 119 return false; |
| 124 } | 120 } |
| 125 virtual bool OnRstStreamFrame(const QuicRstStreamFrame& /*frame*/) OVERRIDE { | 121 virtual bool OnRstStreamFrame(const QuicRstStreamFrame& /*frame*/) OVERRIDE { |
| 126 DCHECK(false); | 122 DCHECK(false); |
| 127 return false; | 123 return false; |
| 128 } | 124 } |
| 129 virtual bool OnConnectionCloseFrame( | 125 virtual bool OnConnectionCloseFrame( |
| 130 const QuicConnectionCloseFrame & /*frame*/) OVERRIDE { | 126 const QuicConnectionCloseFrame& /*frame*/) OVERRIDE { |
| 131 DCHECK(false); | 127 DCHECK(false); |
| 132 return false; | 128 return false; |
| 133 } | 129 } |
| 134 virtual bool OnGoAwayFrame(const QuicGoAwayFrame& /*frame*/) OVERRIDE { | 130 virtual bool OnGoAwayFrame(const QuicGoAwayFrame& /*frame*/) OVERRIDE { |
| 135 DCHECK(false); | 131 DCHECK(false); |
| 136 return false; | 132 return false; |
| 137 } | 133 } |
| 138 virtual bool OnWindowUpdateFrame(const QuicWindowUpdateFrame& /*frame*/) | 134 virtual bool OnWindowUpdateFrame( |
| 139 OVERRIDE { | 135 const QuicWindowUpdateFrame& /*frame*/) OVERRIDE { |
| 140 DCHECK(false); | 136 DCHECK(false); |
| 141 return false; | 137 return false; |
| 142 } | 138 } |
| 143 virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE { | 139 virtual bool OnBlockedFrame(const QuicBlockedFrame& frame) OVERRIDE { |
| 144 DCHECK(false); | 140 DCHECK(false); |
| 145 return false; | 141 return false; |
| 146 } | 142 } |
| 147 virtual void OnFecData(const QuicFecData& /*fec*/) OVERRIDE { | 143 virtual void OnFecData(const QuicFecData& /*fec*/) OVERRIDE { DCHECK(false); } |
| 148 DCHECK(false); | 144 virtual void OnPacketComplete() OVERRIDE { DCHECK(false); } |
| 149 } | |
| 150 virtual void OnPacketComplete() OVERRIDE { | |
| 151 DCHECK(false); | |
| 152 } | |
| 153 | 145 |
| 154 private: | 146 private: |
| 155 QuicDispatcher* dispatcher_; | 147 QuicDispatcher* dispatcher_; |
| 156 | 148 |
| 157 // Latched in OnUnauthenticatedPublicHeader for use later. | 149 // Latched in OnUnauthenticatedPublicHeader for use later. |
| 158 QuicConnectionId connection_id_; | 150 QuicConnectionId connection_id_; |
| 159 }; | 151 }; |
| 160 | 152 |
| 161 QuicDispatcher::QuicDispatcher(const QuicConfig& config, | 153 QuicDispatcher::QuicDispatcher(const QuicConfig& config, |
| 162 const QuicCryptoServerConfig& crypto_config, | 154 const QuicCryptoServerConfig& crypto_config, |
| (...skipping 21 matching lines...) Expand all Loading... |
| 184 | 176 |
| 185 void QuicDispatcher::Initialize(int fd) { | 177 void QuicDispatcher::Initialize(int fd) { |
| 186 DCHECK(writer_ == NULL); | 178 DCHECK(writer_ == NULL); |
| 187 writer_.reset(CreateWriter(fd)); | 179 writer_.reset(CreateWriter(fd)); |
| 188 time_wait_list_manager_.reset(CreateQuicTimeWaitListManager()); | 180 time_wait_list_manager_.reset(CreateQuicTimeWaitListManager()); |
| 189 | 181 |
| 190 // Remove all versions > QUIC_VERSION_16 from the | 182 // Remove all versions > QUIC_VERSION_16 from the |
| 191 // supported_versions_no_flow_control_ vector. | 183 // supported_versions_no_flow_control_ vector. |
| 192 QuicVersionVector::iterator it = | 184 QuicVersionVector::iterator it = |
| 193 find(supported_versions_no_flow_control_.begin(), | 185 find(supported_versions_no_flow_control_.begin(), |
| 194 supported_versions_no_flow_control_.end(), QUIC_VERSION_17); | 186 supported_versions_no_flow_control_.end(), |
| 187 QUIC_VERSION_17); |
| 195 if (it != supported_versions_no_flow_control_.end()) { | 188 if (it != supported_versions_no_flow_control_.end()) { |
| 196 supported_versions_no_flow_control_.erase( | 189 supported_versions_no_flow_control_.erase( |
| 197 supported_versions_no_flow_control_.begin(), it + 1); | 190 supported_versions_no_flow_control_.begin(), it + 1); |
| 198 } | 191 } |
| 199 CHECK(!supported_versions_no_flow_control_.empty()); | 192 CHECK(!supported_versions_no_flow_control_.empty()); |
| 200 } | 193 } |
| 201 | 194 |
| 202 void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address, | 195 void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address, |
| 203 const IPEndPoint& client_address, | 196 const IPEndPoint& client_address, |
| 204 const QuicEncryptedPacket& packet) { | 197 const QuicEncryptedPacket& packet) { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 224 return false; | 217 return false; |
| 225 } | 218 } |
| 226 if (time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id)) { | 219 if (time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id)) { |
| 227 return HandlePacketForTimeWait(header); | 220 return HandlePacketForTimeWait(header); |
| 228 } | 221 } |
| 229 | 222 |
| 230 // Ensure the packet has a version negotiation bit set before creating a new | 223 // Ensure the packet has a version negotiation bit set before creating a new |
| 231 // session for it. All initial packets for a new connection are required to | 224 // session for it. All initial packets for a new connection are required to |
| 232 // have the flag set. Otherwise it may be a stray packet. | 225 // have the flag set. Otherwise it may be a stray packet. |
| 233 if (header.version_flag) { | 226 if (header.version_flag) { |
| 234 session = CreateQuicSession(connection_id, current_server_address_, | 227 session = CreateQuicSession( |
| 235 current_client_address_); | 228 connection_id, current_server_address_, current_client_address_); |
| 236 } | 229 } |
| 237 | 230 |
| 238 if (session == NULL) { | 231 if (session == NULL) { |
| 239 DVLOG(1) << "Failed to create session for " << connection_id; | 232 DVLOG(1) << "Failed to create session for " << connection_id; |
| 240 // Add this connection_id fo the time-wait state, to safely reject future | 233 // Add this connection_id fo the time-wait state, to safely reject future |
| 241 // packets. | 234 // packets. |
| 242 | 235 |
| 243 if (header.version_flag && | 236 if (header.version_flag && |
| 244 !framer_.IsSupportedVersion(header.versions.front())) { | 237 !framer_.IsSupportedVersion(header.versions.front())) { |
| 245 // TODO(ianswett): Produce a no-version version negotiation packet. | 238 // TODO(ianswett): Produce a no-version version negotiation packet. |
| 246 return false; | 239 return false; |
| 247 } | 240 } |
| 248 | 241 |
| 249 // Use the version in the packet if possible, otherwise assume the latest. | 242 // Use the version in the packet if possible, otherwise assume the latest. |
| 250 QuicVersion version = header.version_flag ? header.versions.front() : | 243 QuicVersion version = header.version_flag ? header.versions.front() |
| 251 supported_versions_.front(); | 244 : supported_versions_.front(); |
| 252 time_wait_list_manager_->AddConnectionIdToTimeWait( | 245 time_wait_list_manager_->AddConnectionIdToTimeWait( |
| 253 connection_id, version, NULL); | 246 connection_id, version, NULL); |
| 254 DCHECK(time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id)); | 247 DCHECK(time_wait_list_manager_->IsConnectionIdInTimeWait(connection_id)); |
| 255 return HandlePacketForTimeWait(header); | 248 return HandlePacketForTimeWait(header); |
| 256 } | 249 } |
| 257 DVLOG(1) << "Created new session for " << connection_id; | 250 DVLOG(1) << "Created new session for " << connection_id; |
| 258 session_map_.insert(make_pair(connection_id, session)); | 251 session_map_.insert(make_pair(connection_id, session)); |
| 259 } else { | 252 } else { |
| 260 session = it->second; | 253 session = it->second; |
| 261 } | 254 } |
| (...skipping 11 matching lines...) Expand all Loading... |
| 273 time_wait_list_manager_->ProcessPacket(current_server_address_, | 266 time_wait_list_manager_->ProcessPacket(current_server_address_, |
| 274 current_client_address_, | 267 current_client_address_, |
| 275 header.public_header.connection_id, | 268 header.public_header.connection_id, |
| 276 header.packet_sequence_number, | 269 header.packet_sequence_number, |
| 277 *current_packet_); | 270 *current_packet_); |
| 278 } | 271 } |
| 279 | 272 |
| 280 void QuicDispatcher::CleanUpSession(SessionMap::iterator it) { | 273 void QuicDispatcher::CleanUpSession(SessionMap::iterator it) { |
| 281 QuicConnection* connection = it->second->connection(); | 274 QuicConnection* connection = it->second->connection(); |
| 282 QuicEncryptedPacket* connection_close_packet = | 275 QuicEncryptedPacket* connection_close_packet = |
| 283 connection->ReleaseConnectionClosePacket(); | 276 connection->ReleaseConnectionClosePacket(); |
| 284 write_blocked_list_.erase(connection); | 277 write_blocked_list_.erase(connection); |
| 285 time_wait_list_manager_->AddConnectionIdToTimeWait(it->first, | 278 time_wait_list_manager_->AddConnectionIdToTimeWait( |
| 286 connection->version(), | 279 it->first, connection->version(), connection_close_packet); |
| 287 connection_close_packet); | |
| 288 session_map_.erase(it); | 280 session_map_.erase(it); |
| 289 } | 281 } |
| 290 | 282 |
| 291 void QuicDispatcher::DeleteSessions() { | 283 void QuicDispatcher::DeleteSessions() { |
| 292 STLDeleteElements(&closed_session_list_); | 284 STLDeleteElements(&closed_session_list_); |
| 293 } | 285 } |
| 294 | 286 |
| 295 void QuicDispatcher::OnCanWrite() { | 287 void QuicDispatcher::OnCanWrite() { |
| 296 // We got an EPOLLOUT: the socket should not be blocked. | 288 // We got an EPOLLOUT: the socket should not be blocked. |
| 297 writer_->SetWritable(); | 289 writer_->SetWritable(); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 332 QuicErrorCode error) { | 324 QuicErrorCode error) { |
| 333 SessionMap::iterator it = session_map_.find(connection_id); | 325 SessionMap::iterator it = session_map_.find(connection_id); |
| 334 if (it == session_map_.end()) { | 326 if (it == session_map_.end()) { |
| 335 LOG(DFATAL) << "ConnectionId " << connection_id | 327 LOG(DFATAL) << "ConnectionId " << connection_id |
| 336 << " does not exist in the session map. " | 328 << " does not exist in the session map. " |
| 337 << "Error: " << QuicUtils::ErrorToString(error); | 329 << "Error: " << QuicUtils::ErrorToString(error); |
| 338 LOG(DFATAL) << base::debug::StackTrace().ToString(); | 330 LOG(DFATAL) << base::debug::StackTrace().ToString(); |
| 339 return; | 331 return; |
| 340 } | 332 } |
| 341 | 333 |
| 342 DLOG_IF(INFO, error != QUIC_NO_ERROR) << "Closing connection (" | 334 DLOG_IF(INFO, error != QUIC_NO_ERROR) |
| 343 << connection_id | 335 << "Closing connection (" << connection_id |
| 344 << ") due to error: " | 336 << ") due to error: " << QuicUtils::ErrorToString(error); |
| 345 << QuicUtils::ErrorToString(error); | |
| 346 | 337 |
| 347 if (closed_session_list_.empty()) { | 338 if (closed_session_list_.empty()) { |
| 348 epoll_server_->RegisterAlarmApproximateDelta( | 339 epoll_server_->RegisterAlarmApproximateDelta(0, |
| 349 0, delete_sessions_alarm_.get()); | 340 delete_sessions_alarm_.get()); |
| 350 } | 341 } |
| 351 closed_session_list_.push_back(it->second); | 342 closed_session_list_.push_back(it->second); |
| 352 CleanUpSession(it); | 343 CleanUpSession(it); |
| 353 } | 344 } |
| 354 | 345 |
| 355 void QuicDispatcher::OnWriteBlocked(QuicBlockedWriterInterface* writer) { | 346 void QuicDispatcher::OnWriteBlocked(QuicBlockedWriterInterface* writer) { |
| 356 DCHECK(writer_->IsWriteBlocked()); | 347 DCHECK(writer_->IsWriteBlocked()); |
| 357 write_blocked_list_.insert(make_pair(writer, true)); | 348 write_blocked_list_.insert(make_pair(writer, true)); |
| 358 } | 349 } |
| 359 | 350 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 377 } | 368 } |
| 378 | 369 |
| 379 QuicConnection* QuicDispatcher::CreateQuicConnection( | 370 QuicConnection* QuicDispatcher::CreateQuicConnection( |
| 380 QuicConnectionId connection_id, | 371 QuicConnectionId connection_id, |
| 381 const IPEndPoint& server_address, | 372 const IPEndPoint& server_address, |
| 382 const IPEndPoint& client_address, | 373 const IPEndPoint& client_address, |
| 383 uint32 initial_flow_control_window) { | 374 uint32 initial_flow_control_window) { |
| 384 // If we have disabled per-stream flow control, then don't allow new | 375 // If we have disabled per-stream flow control, then don't allow new |
| 385 // connections to talk QUIC_VERSION_17 or higher. | 376 // connections to talk QUIC_VERSION_17 or higher. |
| 386 if (FLAGS_enable_quic_stream_flow_control_2) { | 377 if (FLAGS_enable_quic_stream_flow_control_2) { |
| 387 return new QuicConnection(connection_id, client_address, helper_.get(), | 378 return new QuicConnection(connection_id, |
| 388 writer_.get(), true, supported_versions_, | 379 client_address, |
| 380 helper_.get(), |
| 381 writer_.get(), |
| 382 true, |
| 383 supported_versions_, |
| 389 initial_flow_control_window_bytes_); | 384 initial_flow_control_window_bytes_); |
| 390 } else { | 385 } else { |
| 391 DVLOG(1) | 386 DVLOG(1) |
| 392 << "Flow control disabled, creating QuicDispatcher WITHOUT version 17"; | 387 << "Flow control disabled, creating QuicDispatcher WITHOUT version 17"; |
| 393 return new QuicConnection(connection_id, client_address, helper_.get(), | 388 return new QuicConnection(connection_id, |
| 394 writer_.get(), true, | 389 client_address, |
| 390 helper_.get(), |
| 391 writer_.get(), |
| 392 true, |
| 395 supported_versions_no_flow_control_, | 393 supported_versions_no_flow_control_, |
| 396 initial_flow_control_window_bytes_); | 394 initial_flow_control_window_bytes_); |
| 397 } | 395 } |
| 398 } | 396 } |
| 399 | 397 |
| 400 QuicTimeWaitListManager* QuicDispatcher::CreateQuicTimeWaitListManager() { | 398 QuicTimeWaitListManager* QuicDispatcher::CreateQuicTimeWaitListManager() { |
| 401 return new QuicTimeWaitListManager( | 399 return new QuicTimeWaitListManager( |
| 402 writer_.get(), this, epoll_server(), supported_versions()); | 400 writer_.get(), this, epoll_server(), supported_versions()); |
| 403 } | 401 } |
| 404 | 402 |
| 405 bool QuicDispatcher::HandlePacketForTimeWait( | 403 bool QuicDispatcher::HandlePacketForTimeWait( |
| 406 const QuicPacketPublicHeader& header) { | 404 const QuicPacketPublicHeader& header) { |
| 407 if (header.reset_flag) { | 405 if (header.reset_flag) { |
| 408 // Public reset packets do not have sequence numbers, so ignore the packet. | 406 // Public reset packets do not have sequence numbers, so ignore the packet. |
| 409 return false; | 407 return false; |
| 410 } | 408 } |
| 411 | 409 |
| 412 // Switch the framer to the correct version, so that the sequence number can | 410 // Switch the framer to the correct version, so that the sequence number can |
| 413 // be parsed correctly. | 411 // be parsed correctly. |
| 414 framer_.set_version(time_wait_list_manager_->GetQuicVersionFromConnectionId( | 412 framer_.set_version(time_wait_list_manager_->GetQuicVersionFromConnectionId( |
| 415 header.connection_id)); | 413 header.connection_id)); |
| 416 | 414 |
| 417 // Continue parsing the packet to extract the sequence number. Then | 415 // Continue parsing the packet to extract the sequence number. Then |
| 418 // send it to the time wait manager in OnUnathenticatedHeader. | 416 // send it to the time wait manager in OnUnathenticatedHeader. |
| 419 return true; | 417 return true; |
| 420 } | 418 } |
| 421 | 419 |
| 422 } // namespace tools | 420 } // namespace tools |
| 423 } // namespace net | 421 } // namespace net |
| OLD | NEW |