#include "net/base/net_log.h"
#include "net/base/net_util.h"
#include "net/cert/asn1_util.h"
+#include "net/cert/cert_verify_result.h"
#include "net/http/http_log_util.h"
#include "net/http/http_network_session.h"
#include "net/http/http_server_properties.h"
#include "net/http/http_util.h"
+#include "net/http/transport_security_state.h"
+#include "net/socket/ssl_client_socket.h"
#include "net/spdy/spdy_buffer_producer.h"
#include "net/spdy/spdy_frame_builder.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/spdy/spdy_protocol.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_stream.h"
-#include "net/ssl/server_bound_cert_service.h"
+#include "net/ssl/channel_id_service.h"
#include "net/ssl/ssl_cipher_suite_names.h"
#include "net/ssl/ssl_connection_status_flags.h"
return dict;
}
+base::Value* NetLogSpdyInitializedCallback(NetLog::Source source,
+ const NextProto protocol_version,
+ NetLog::LogLevel /* log_level */) {
+ base::DictionaryValue* dict = new base::DictionaryValue();
+ if (source.IsValid()) {
+ source.AddToEventParameters(dict);
+ }
+ dict->SetString("protocol",
+ SSLClientSocket::NextProtoToString(protocol_version));
+ return dict;
+}
+
base::Value* NetLogSpdySettingsCallback(const HostPortPair& host_port_pair,
bool clear_persisted,
NetLog::LogLevel /* log_level */) {
}
base::Value* NetLogSpdySettingCallback(SpdySettingsIds id,
+ const SpdyMajorVersion protocol_version,
SpdySettingsFlags flags,
uint32 value,
NetLog::LogLevel /* log_level */) {
base::DictionaryValue* dict = new base::DictionaryValue();
- dict->SetInteger("id", id);
+ dict->SetInteger("id",
+ SpdyConstants::SerializeSettingId(protocol_version, id));
dict->SetInteger("flags", flags);
dict->SetInteger("value", value);
return dict;
}
-base::Value* NetLogSpdySendSettingsCallback(const SettingsMap* settings,
- NetLog::LogLevel /* log_level */) {
+base::Value* NetLogSpdySendSettingsCallback(
+ const SettingsMap* settings,
+ const SpdyMajorVersion protocol_version,
+ NetLog::LogLevel /* log_level */) {
base::DictionaryValue* dict = new base::DictionaryValue();
base::ListValue* settings_list = new base::ListValue();
for (SettingsMap::const_iterator it = settings->begin();
const SpdySettingsIds id = it->first;
const SpdySettingsFlags flags = it->second.first;
const uint32 value = it->second.second;
- settings_list->Append(new base::StringValue(
- base::StringPrintf("[id:%u flags:%u value:%u]", id, flags, value)));
+ settings_list->Append(new base::StringValue(base::StringPrintf(
+ "[id:%u flags:%u value:%u]",
+ SpdyConstants::SerializeSettingId(protocol_version, id),
+ flags,
+ value)));
}
dict->Set("settings", settings_list);
return dict;
SpdySession::PushedStreamInfo::~PushedStreamInfo() {}
+// static
+bool SpdySession::CanPool(TransportSecurityState* transport_security_state,
+ const SSLInfo& ssl_info,
+ const std::string& old_hostname,
+ const std::string& new_hostname) {
+ // Pooling is prohibited if the server cert is not valid for the new domain,
+ // and for connections on which client certs were sent. It is also prohibited
+ // when channel ID was sent if the hosts are from different eTLDs+1.
+ if (IsCertStatusError(ssl_info.cert_status))
+ return false;
+
+ if (ssl_info.client_cert_sent)
+ return false;
+
+ if (ssl_info.channel_id_sent &&
+ ChannelIDService::GetDomainForHost(new_hostname) !=
+ ChannelIDService::GetDomainForHost(old_hostname)) {
+ return false;
+ }
+
+ bool unused = false;
+ if (!ssl_info.cert->VerifyNameMatch(new_hostname, &unused))
+ return false;
+
+ std::string pinning_failure_log;
+ if (!transport_security_state->CheckPublicKeyPins(
+ new_hostname,
+ ssl_info.is_issued_by_known_root,
+ ssl_info.public_key_hashes,
+ &pinning_failure_log)) {
+ return false;
+ }
+
+ return true;
+}
+
SpdySession::SpdySession(
const SpdySessionKey& spdy_session_key,
const base::WeakPtr<HttpServerProperties>& http_server_properties,
+ TransportSecurityState* transport_security_state,
bool verify_domain_authentication,
bool enable_sending_initial_data,
bool enable_compression,
spdy_session_key_(spdy_session_key),
pool_(NULL),
http_server_properties_(http_server_properties),
+ transport_security_state_(transport_security_state),
read_buffer_(new IOBuffer(kReadBufferSize)),
stream_hi_water_mark_(kFirstStreamId),
+ last_accepted_push_stream_id_(0),
+ num_pushed_streams_(0u),
+ num_active_pushed_streams_(0u),
in_flight_write_frame_type_(DATA),
in_flight_write_frame_size_(0),
is_secure_(false),
max_concurrent_streams_limit_(max_concurrent_streams_limit == 0
? kMaxConcurrentStreamLimit
: max_concurrent_streams_limit),
+ max_concurrent_pushed_streams_(kMaxConcurrentPushedStreams),
streams_initiated_count_(0),
streams_pushed_count_(0),
streams_pushed_and_claimed_count_(0),
enable_compression_));
buffered_spdy_framer_->set_visitor(this);
buffered_spdy_framer_->set_debug_visitor(this);
- UMA_HISTOGRAM_ENUMERATION("Net.SpdyVersion", protocol_, kProtoMaximumVersion);
-#if defined(SPDY_PROXY_AUTH_ORIGIN)
- UMA_HISTOGRAM_BOOLEAN("Net.SpdySessions_DataReductionProxy",
- host_port_pair().Equals(HostPortPair::FromURL(
- GURL(SPDY_PROXY_AUTH_ORIGIN))));
-#endif
+ UMA_HISTOGRAM_ENUMERATION(
+ "Net.SpdyVersion2",
+ protocol_ - kProtoSPDYMinimumVersion,
+ kProtoSPDYMaximumVersion - kProtoSPDYMinimumVersion + 1);
- net_log_.AddEvent(
- NetLog::TYPE_SPDY_SESSION_INITIALIZED,
- connection_->socket()->NetLog().source().ToEventParametersCallback());
+ net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_INITIALIZED,
+ base::Bind(&NetLogSpdyInitializedCallback,
+ connection_->socket()->NetLog().source(),
+ protocol_));
DCHECK_EQ(availability_state_, STATE_AVAILABLE);
connection_->AddHigherLayeredPool(this);
if (!GetSSLInfo(&ssl_info, &was_npn_negotiated, &protocol_negotiated))
return true; // This is not a secure session, so all domains are okay.
- bool unused = false;
- return
- !ssl_info.client_cert_sent &&
- (!ssl_info.channel_id_sent ||
- (ServerBoundCertService::GetDomainForHost(domain) ==
- ServerBoundCertService::GetDomainForHost(host_port_pair().host()))) &&
- ssl_info.cert->VerifyNameMatch(domain, &unused);
+ return CanPool(transport_security_state_, ssl_info,
+ host_port_pair().host(), domain);
}
int SpdySession::GetPushStream(
return err;
if (!max_concurrent_streams_ ||
- (active_streams_.size() + created_streams_.size() <
+ (active_streams_.size() + created_streams_.size() - num_pushed_streams_ <
max_concurrent_streams_)) {
return CreateStream(*request, stream);
}
SpdyStreamId stream_id,
RequestPriority priority,
SpdyControlFlags flags,
- const SpdyHeaderBlock& headers) {
+ const SpdyHeaderBlock& block) {
ActiveStreamMap::const_iterator it = active_streams_.find(stream_id);
CHECK(it != active_streams_.end());
CHECK_EQ(it->second.stream->stream_id(), stream_id);
DCHECK(buffered_spdy_framer_.get());
SpdyPriority spdy_priority =
ConvertRequestPriorityToSpdyPriority(priority, GetProtocolVersion());
- scoped_ptr<SpdyFrame> syn_frame(
- buffered_spdy_framer_->CreateSynStream(stream_id, 0, spdy_priority, flags,
- &headers));
+
+ scoped_ptr<SpdyFrame> syn_frame;
+ // TODO(hkhalil): Avoid copy of |block|.
+ if (GetProtocolVersion() <= SPDY3) {
+ SpdySynStreamIR syn_stream(stream_id);
+ syn_stream.set_associated_to_stream_id(0);
+ syn_stream.set_priority(spdy_priority);
+ syn_stream.set_fin((flags & CONTROL_FLAG_FIN) != 0);
+ syn_stream.set_unidirectional((flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0);
+ syn_stream.set_name_value_block(block);
+ syn_frame.reset(buffered_spdy_framer_->SerializeFrame(syn_stream));
+ } else {
+ SpdyHeadersIR headers(stream_id);
+ headers.set_priority(spdy_priority);
+ headers.set_has_priority(true);
+ headers.set_fin((flags & CONTROL_FLAG_FIN) != 0);
+ headers.set_name_value_block(block);
+ syn_frame.reset(buffered_spdy_framer_->SerializeFrame(headers));
+ }
base::StatsCounter spdy_requests("spdy.requests");
spdy_requests.Increment();
streams_initiated_count_++;
if (net_log().IsLogging()) {
- net_log().AddEvent(
- NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
- base::Bind(&NetLogSpdySynStreamSentCallback, &headers,
- (flags & CONTROL_FLAG_FIN) != 0,
- (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
- spdy_priority,
- stream_id));
+ net_log().AddEvent(NetLog::TYPE_SPDY_SESSION_SYN_STREAM,
+ base::Bind(&NetLogSpdySynStreamSentCallback,
+ &block,
+ (flags & CONTROL_FLAG_FIN) != 0,
+ (flags & CONTROL_FLAG_UNIDIRECTIONAL) != 0,
+ spdy_priority,
+ stream_id));
}
return syn_frame.Pass();
scoped_ptr<SpdyBuffer> data_buffer(new SpdyBuffer(frame.Pass()));
- if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION) {
+ // Send window size is based on payload size, so nothing to do if this is
+ // just a FIN with no payload.
+ if (flow_control_state_ == FLOW_CONTROL_STREAM_AND_SESSION &&
+ effective_len != 0) {
DecreaseSendWindowSize(static_cast<int32>(effective_len));
data_buffer->AddConsumeCallback(
base::Bind(&SpdySession::OnWriteBufferConsumed,
// probably something that we still want to support, although server
// push is hardly used. Write tests for this and fix this. (See
// http://crbug.com/261712 .)
- if (owned_stream->type() == SPDY_PUSH_STREAM)
+ if (owned_stream->type() == SPDY_PUSH_STREAM) {
unclaimed_pushed_streams_.erase(owned_stream->url());
+ num_pushed_streams_--;
+ if (!owned_stream->IsReservedRemote())
+ num_active_pushed_streams_--;
+ }
DeleteStream(owned_stream.Pass(), status);
MaybeFinishGoingAway();
err != ERR_SOCKET_NOT_CONNECTED &&
err != ERR_CONNECTION_CLOSED && err != ERR_CONNECTION_RESET) {
// Enqueue a GOAWAY to inform the peer of why we're closing the connection.
- SpdyGoAwayIR goaway_ir(0, // Last accepted stream ID.
+ SpdyGoAwayIR goaway_ir(last_accepted_push_stream_id_,
MapNetErrorToGoAwayStatus(err),
description);
EnqueueSessionWrite(HIGHEST,
received_settings_ = true;
// Log the setting.
- net_log_.AddEvent(
- NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
- base::Bind(&NetLogSpdySettingCallback,
- id, static_cast<SpdySettingsFlags>(flags), value));
+ const SpdyMajorVersion protocol_version = GetProtocolVersion();
+ net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_RECV_SETTING,
+ base::Bind(&NetLogSpdySettingCallback,
+ id,
+ protocol_version,
+ static_cast<SpdySettingsFlags>(flags),
+ value));
}
void SpdySession::OnSendCompressedFrame(
SpdyFrameType type,
size_t payload_len,
size_t frame_len) {
- if (type != SYN_STREAM)
+ if (type != SYN_STREAM && type != HEADERS)
return;
DCHECK(buffered_spdy_framer_.get());
SpdyStream* stream) {
CHECK(in_io_loop_);
SpdyStreamId stream_id = stream->stream_id();
+
+ if (stream->type() == SPDY_PUSH_STREAM) {
+ DCHECK(stream->IsReservedRemote());
+ if (max_concurrent_pushed_streams_ &&
+ num_active_pushed_streams_ >= max_concurrent_pushed_streams_) {
+ ResetStream(stream_id,
+ RST_STREAM_REFUSED_STREAM,
+ "Stream concurrency limit reached.");
+ return STATUS_CODE_REFUSED_STREAM;
+ }
+ }
+
+ if (stream->type() == SPDY_PUSH_STREAM) {
+ // Will be balanced in DeleteStream.
+ num_active_pushed_streams_++;
+ }
+
// May invalidate |stream|.
int rv = stream->OnInitialResponseHeadersReceived(
response_headers, response_time, recv_first_byte_time);
DCHECK_NE(rv, ERR_IO_PENDING);
DCHECK(active_streams_.find(stream_id) == active_streams_.end());
}
+
return rv;
}
}
}
+bool SpdySession::OnUnknownFrame(SpdyStreamId stream_id, int frame_type) {
+ // Validate stream id.
+ // Was the frame sent on a stream id that has not been used in this session?
+ if (stream_id % 2 == 1 && stream_id > stream_hi_water_mark_)
+ return false;
+
+ if (stream_id % 2 == 0 && stream_id > last_accepted_push_stream_id_)
+ return false;
+
+ return true;
+}
+
void SpdySession::OnRstStream(SpdyStreamId stream_id,
SpdyRstStreamStatus status) {
CHECK(in_io_loop_);
// Server-initiated streams should have even sequence numbers.
if ((stream_id & 0x1) != 0) {
LOG(WARNING) << "Received invalid push stream id " << stream_id;
+ if (GetProtocolVersion() > SPDY2)
+ CloseSessionOnError(ERR_SPDY_PROTOCOL_ERROR, "Odd push stream id.");
return false;
}
+ if (GetProtocolVersion() > SPDY2) {
+ if (stream_id <= last_accepted_push_stream_id_) {
+ LOG(WARNING) << "Received push stream id lesser or equal to the last "
+ << "accepted before " << stream_id;
+ CloseSessionOnError(
+ ERR_SPDY_PROTOCOL_ERROR,
+ "New push stream id must be greater than the last accepted.");
+ return false;
+ }
+ }
+
if (IsStreamActive(stream_id)) {
+ // For SPDY3 and higher we should not get here, we'll start going away
+ // earlier on |last_seen_push_stream_id_| check.
+ CHECK_GT(SPDY3, GetProtocolVersion());
LOG(WARNING) << "Received push for active stream " << stream_id;
return false;
}
+ last_accepted_push_stream_id_ = stream_id;
+
RequestPriority request_priority =
ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion());
active_it->second.stream->OnPushPromiseHeadersReceived(headers);
DCHECK(active_it->second.stream->IsReservedRemote());
+ num_pushed_streams_++;
return true;
}
kDefaultInitialRecvWindowSize - session_recv_window_size_);
}
- // Finally, notify the server about the settings they have
- // previously told us to use when communicating with them (after
- // applying them).
- const SettingsMap& server_settings_map =
- http_server_properties_->GetSpdySettings(host_port_pair());
- if (server_settings_map.empty())
- return;
+ if (protocol_ <= kProtoSPDY31) {
+ // Finally, notify the server about the settings they have
+ // previously told us to use when communicating with them (after
+ // applying them).
+ const SettingsMap& server_settings_map =
+ http_server_properties_->GetSpdySettings(host_port_pair());
+ if (server_settings_map.empty())
+ return;
- SettingsMap::const_iterator it =
- server_settings_map.find(SETTINGS_CURRENT_CWND);
- uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0;
- UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
+ SettingsMap::const_iterator it =
+ server_settings_map.find(SETTINGS_CURRENT_CWND);
+ uint32 cwnd = (it != server_settings_map.end()) ? it->second.second : 0;
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdySettingsCwndSent", cwnd, 1, 200, 100);
- for (SettingsMap::const_iterator it = server_settings_map.begin();
- it != server_settings_map.end(); ++it) {
- const SpdySettingsIds new_id = it->first;
- const uint32 new_val = it->second.second;
- HandleSetting(new_id, new_val);
- }
+ for (SettingsMap::const_iterator it = server_settings_map.begin();
+ it != server_settings_map.end(); ++it) {
+ const SpdySettingsIds new_id = it->first;
+ const uint32 new_val = it->second.second;
+ HandleSetting(new_id, new_val);
+ }
- SendSettings(server_settings_map);
+ SendSettings(server_settings_map);
+ }
}
void SpdySession::SendSettings(const SettingsMap& settings) {
+ const SpdyMajorVersion protocol_version = GetProtocolVersion();
net_log_.AddEvent(
NetLog::TYPE_SPDY_SESSION_SEND_SETTINGS,
- base::Bind(&NetLogSpdySendSettingsCallback, &settings));
-
+ base::Bind(&NetLogSpdySendSettingsCallback, &settings, protocol_version));
// Create the SETTINGS frame and send it.
DCHECK(buffered_spdy_framer_.get());
scoped_ptr<SpdyFrame> settings_frame(