diff options
| author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
|---|---|---|
| committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
| commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
| tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/net/tools/quic | |
| download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz | |
Initial import.
Diffstat (limited to 'chromium/net/tools/quic')
60 files changed, 7335 insertions, 0 deletions
diff --git a/chromium/net/tools/quic/benchmark/run_client.py b/chromium/net/tools/quic/benchmark/run_client.py new file mode 100755 index 00000000000..7df37ab7fa0 --- /dev/null +++ b/chromium/net/tools/quic/benchmark/run_client.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python + +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import csv +import datetime +import json +import os +import shlex +import subprocess +import sys +from optparse import OptionParser + +"""Start a client to fetch web pages either using wget or using quic_client. +If --use_wget is set, it uses wget. +Usage: This invocation + run_client.py --quic_binary_dir=../../../../out/Debug \ + --address=127.0.0.1 --port=5000 --infile=test_urls.json \ + --delay_file=delay.csv --packets_file=packets.csv + fetches pages listed in test_urls.json from a quic server running at + 127.0.0.1 on port 5000 using quic binary ../../../../out/Debug/quic_client + and stores the delay in delay.csv and the max received packet number (for + QUIC) in packets.csv. + If --use_wget is present, it will fetch the URLs using wget and ignores + the flags --address, --port, --quic_binary_dir, etc. +""" + +def Timestamp(datetm=None): + """Get the timestamp in microseconds. + Args: + datetm: the date and time to be converted to timestamp. + If not set, use the current UTC time. + Returns: + The timestamp in microseconds. + """ + datetm = datetm or datetime.datetime.utcnow() + diff = datetm - datetime.datetime.utcfromtimestamp(0) + timestamp = (diff.days * 86400 + diff.seconds) * 1000000 + diff.microseconds + return timestamp + +class PageloadExperiment: + def __init__(self, use_wget, quic_binary_dir, quic_server_address, + quic_server_port): + """Initialize PageloadExperiment. + + Args: + use_wget: Whether to use wget. + quic_binary_dir: Directory for quic_binary. + quic_server_address: IP address of quic server. + quic_server_port: Port of the quic server. + """ + self.use_wget = use_wget + self.quic_binary_dir = quic_binary_dir + self.quic_server_address = quic_server_address + self.quic_server_port = quic_server_port + if not use_wget and not os.path.isfile(quic_binary_dir + '/quic_client'): + raise IOError('There is no quic_client in the given dir: %s.' + % quic_binary_dir) + + @classmethod + def ReadPages(cls, json_file): + """Return the list of URLs from the json_file. + + One entry of the list may contain a html link and multiple resources. + """ + page_list = [] + with open(json_file) as f: + data = json.load(f) + for page in data['pages']: + url = page['url'] + if 'resources' in page: + resources = page['resources'] + else: + resources = None + if not resources: + page_list.append([url]) + else: + urls = [url] + # For url http://x.com/z/y.html, url_dir is http://x.com/z + url_dir = url.rsplit('/', 1)[0] + for resource in resources: + urls.append(url_dir + '/' + resource) + page_list.append(urls) + return page_list + + def DownloadOnePage(self, urls): + """Download a page emulated by a list of urls. + + Args: + urls: list of URLs to fetch. + Returns: + A tuple (page download time, max packet number). + """ + if self.use_wget: + cmd = 'wget -O -' + else: + cmd = '%s/quic_client --port=%s --address=%s' % ( + self.quic_binary_dir, self.quic_server_port, self.quic_server_address) + cmd_in_list = shlex.split(cmd) + cmd_in_list.extend(urls) + start_time = Timestamp() + ps_proc = subprocess.Popen(cmd_in_list, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + _std_out, std_err = ps_proc.communicate() + end_time = Timestamp() + delta_time = end_time - start_time + max_packets = 0 + if not self.use_wget: + for line in std_err.splitlines(): + if line.find('Client: Got packet') >= 0: + elems = line.split() + packet_num = int(elems[4]) + max_packets = max(max_packets, packet_num) + return delta_time, max_packets + + def RunExperiment(self, infile, delay_file, packets_file=None, num_it=1): + """Run the pageload experiment. + + Args: + infile: Input json file describing the page list. + delay_file: Output file storing delay in csv format. + packets_file: Output file storing max packet number in csv format. + num_it: Number of iterations to run in this experiment. + """ + page_list = self.ReadPages(infile) + header = [urls[0].rsplit('/', 1)[1] for urls in page_list] + header0 = 'wget' if self.use_wget else 'quic' + header = [header0] + header + + plt_list = [] + packets_list = [] + for i in range(num_it): + plt_one_row = [str(i)] + packets_one_row = [str(i)] + for urls in page_list: + time_micros, num_packets = self.DownloadOnePage(urls) + time_secs = time_micros / 1000000.0 + plt_one_row.append('%6.3f' % time_secs) + packets_one_row.append('%5d' % num_packets) + plt_list.append(plt_one_row) + packets_list.append(packets_one_row) + + with open(delay_file, 'w') as f: + csv_writer = csv.writer(f, delimiter=',') + csv_writer.writerow(header) + for one_row in plt_list: + csv_writer.writerow(one_row) + if packets_file: + with open(packets_file, 'w') as f: + csv_writer = csv.writer(f, delimiter=',') + csv_writer.writerow(header) + for one_row in packets_list: + csv_writer.writerow(one_row) + + +def main(): + parser = OptionParser() + parser.add_option('--use_wget', dest='use_wget', action='store_true', + default=False) + # Note that only debug version generates the log containing packets + # information. + parser.add_option('--quic_binary_dir', dest='quic_binary_dir', + default='../../../../out/Debug') + # For whatever server address you specify, you need to run the + # quic_server on that machine and populate it with the cache containing + # the URLs requested in the --infile. + parser.add_option('--address', dest='quic_server_address', + default='127.0.0.1') + parser.add_option('--port', dest='quic_server_port', + default='5002') + parser.add_option('--delay_file', dest='delay_file', default='delay.csv') + parser.add_option('--packets_file', dest='packets_file', + default='packets.csv') + parser.add_option('--infile', dest='infile', default='test_urls.json') + (options, _) = parser.parse_args() + + exp = PageloadExperiment(options.use_wget, options.quic_binary_dir, + options.quic_server_address, + options.quic_server_port) + exp.RunExperiment(options.infile, options.delay_file, options.packets_file) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/net/tools/quic/benchmark/test_urls.json b/chromium/net/tools/quic/benchmark/test_urls.json new file mode 100644 index 00000000000..79c20cfe3d1 --- /dev/null +++ b/chromium/net/tools/quic/benchmark/test_urls.json @@ -0,0 +1,41 @@ +{ + "description": "List of pages (URLs) for measuring quic performance using standalone quic client and server. These URLs are for testing only and are provided without guarantee.", + "pages": [ + { + "url": "http://dev1.mdw.la/test/warmup.html", + "why": "A warmup page." + }, + { + "url": "http://dev1.mdw.la/test/test_1KB.jpg", + "why": "A tiny page, about 1K Bytes." + }, + { + "url": "http://dev1.mdw.la/test/test_10KB.jpg", + "why": "A small page, about 10K Bytes." + }, + { + "url": "http://dev1.mdw.la/test/test_100KB.jpg", + "why": "A medium page, about 100K Bytes." + }, + { + "url": "http://dev1.mdw.la/test/test_1MB.jpg", + "why": "A large page, about 1M Bytes." + }, + { + "url": "http://dev1.mdw.la/test/ten_img.html", + "why": "A large page, with 1 html and 10 images totaling about 1M Bytes.", + "resources": [ + "imgs/test_100KB_0.jpg", + "imgs/test_100KB_1.jpg", + "imgs/test_100KB_2.jpg", + "imgs/test_100KB_3.jpg", + "imgs/test_100KB_4.jpg", + "imgs/test_100KB_5.jpg", + "imgs/test_100KB_6.jpg", + "imgs/test_100KB_7.jpg", + "imgs/test_100KB_8.jpg", + "imgs/test_100KB_9.jpg" + ] + } + ] +} diff --git a/chromium/net/tools/quic/end_to_end_test.cc b/chromium/net/tools/quic/end_to_end_test.cc new file mode 100644 index 00000000000..3903df965ee --- /dev/null +++ b/chromium/net/tools/quic/end_to_end_test.cc @@ -0,0 +1,651 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/strings/string_number_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/simple_thread.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/aes_128_gcm_12_encrypter.h" +#include "net/quic/crypto/null_encrypter.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_packet_creator.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/test_tools/quic_connection_peer.h" +#include "net/quic/test_tools/quic_session_peer.h" +#include "net/quic/test_tools/reliable_quic_stream_peer.h" +#include "net/tools/quic/quic_epoll_connection_helper.h" +#include "net/tools/quic/quic_in_memory_cache.h" +#include "net/tools/quic/quic_server.h" +#include "net/tools/quic/quic_socket_utils.h" +#include "net/tools/quic/test_tools/http_message_test_utils.h" +#include "net/tools/quic/test_tools/quic_client_peer.h" +#include "net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h" +#include "net/tools/quic/test_tools/quic_test_client.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using base::WaitableEvent; +using net::test::QuicConnectionPeer; +using net::test::QuicSessionPeer; +using net::test::ReliableQuicStreamPeer; +using std::string; + +namespace net { +namespace tools { +namespace test { +namespace { + +const char* kFooResponseBody = "Artichoke hearts make me happy."; +const char* kBarResponseBody = "Palm hearts are pretty delicious, also."; +const size_t kCongestionFeedbackFrameSize = 25; +// If kCongestionFeedbackFrameSize increase we need to expand this string +// accordingly. +const char* kLargeRequest = + "https://www.google.com/foo/test/a/request/string/longer/than/25/bytes"; + +void GenerateBody(string* body, int length) { + body->clear(); + body->reserve(length); + for (int i = 0; i < length; ++i) { + body->append(1, static_cast<char>(32 + i % (126 - 32))); + } +} + + +// Simple wrapper class to run server in a thread. +class ServerThread : public base::SimpleThread { + public: + explicit ServerThread(IPEndPoint address, const QuicConfig& config) + : SimpleThread("server_thread"), + listening_(true, false), + quit_(true, false), + server_(config), + address_(address), + port_(0) { + } + virtual ~ServerThread() { + } + + virtual void Run() OVERRIDE { + server_.Listen(address_); + + port_lock_.Acquire(); + port_ = server_.port(); + port_lock_.Release(); + + listening_.Signal(); + while (!quit_.IsSignaled()) { + server_.WaitForEvents(); + } + server_.Shutdown(); + } + + int GetPort() { + port_lock_.Acquire(); + int rc = port_; + port_lock_.Release(); + return rc; + } + + WaitableEvent* listening() { return &listening_; } + WaitableEvent* quit() { return &quit_; } + + private: + WaitableEvent listening_; + WaitableEvent quit_; + base::Lock port_lock_; + QuicServer server_; + IPEndPoint address_; + int port_; + + DISALLOW_COPY_AND_ASSIGN(ServerThread); +}; + +class EndToEndTest : public ::testing::TestWithParam<QuicVersion> { + public: + static void SetUpTestCase() { + QuicInMemoryCache::GetInstance()->ResetForTests(); + } + + protected: + EndToEndTest() + : server_hostname_("example.com"), + server_started_(false) { + net::IPAddressNumber ip; + CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &ip)); + server_address_ = IPEndPoint(ip, 0); + client_config_.SetDefaults(); + server_config_.SetDefaults(); + + AddToCache("GET", kLargeRequest, "HTTP/1.1", "200", "OK", kFooResponseBody); + AddToCache("GET", "https://www.google.com/foo", + "HTTP/1.1", "200", "OK", kFooResponseBody); + AddToCache("GET", "https://www.google.com/bar", + "HTTP/1.1", "200", "OK", kBarResponseBody); + version_ = GetParam(); + } + + virtual QuicTestClient* CreateQuicClient() { + QuicTestClient* client = new QuicTestClient(server_address_, + server_hostname_, + false, // not secure + client_config_, + version_); + client->Connect(); + return client; + } + + virtual bool Initialize() { + // Start the server first, because CreateQuicClient() attempts + // to connect to the server. + StartServer(); + client_.reset(CreateQuicClient()); + return client_->client()->connected(); + } + + virtual void TearDown() { + StopServer(); + } + + void StartServer() { + server_thread_.reset(new ServerThread(server_address_, server_config_)); + server_thread_->Start(); + server_thread_->listening()->Wait(); + server_address_ = IPEndPoint(server_address_.address(), + server_thread_->GetPort()); + server_started_ = true; + } + + void StopServer() { + if (!server_started_) + return; + if (server_thread_.get()) { + server_thread_->quit()->Signal(); + server_thread_->Join(); + } + } + + void AddToCache(const StringPiece& method, + const StringPiece& path, + const StringPiece& version, + const StringPiece& response_code, + const StringPiece& response_detail, + const StringPiece& body) { + BalsaHeaders request_headers, response_headers; + request_headers.SetRequestFirstlineFromStringPieces(method, + path, + version); + response_headers.SetRequestFirstlineFromStringPieces(version, + response_code, + response_detail); + response_headers.AppendHeader("content-length", + base::IntToString(body.length())); + + // Check if response already exists and matches. + QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance(); + const QuicInMemoryCache::Response* cached_response = + cache->GetResponse(request_headers); + if (cached_response != NULL) { + string cached_response_headers_str, response_headers_str; + cached_response->headers().DumpToString(&cached_response_headers_str); + response_headers.DumpToString(&response_headers_str); + CHECK_EQ(cached_response_headers_str, response_headers_str); + CHECK_EQ(cached_response->body(), body); + return; + } + cache->AddResponse(request_headers, response_headers, body); + } + + IPEndPoint server_address_; + string server_hostname_; + scoped_ptr<ServerThread> server_thread_; + scoped_ptr<QuicTestClient> client_; + bool server_started_; + QuicConfig client_config_; + QuicConfig server_config_; + QuicVersion version_; +}; + +// Run all end to end tests with all supported versions. +INSTANTIATE_TEST_CASE_P(EndToEndTests, + EndToEndTest, + ::testing::ValuesIn(kSupportedQuicVersions)); + +TEST_P(EndToEndTest, SimpleRequestResponse) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +// TODO(rch): figure out how to detect missing v6 supprt (like on the linux +// try bots) and selectively disable this test. +TEST_P(EndToEndTest, DISABLED_SimpleRequestResponsev6) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + IPAddressNumber ip; + CHECK(net::ParseIPLiteralToNumber("::1", &ip)); + server_address_ = IPEndPoint(ip, server_address_.port()); + ASSERT_TRUE(Initialize()); + + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, SeparateFinPacket) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.set_has_complete_message(false); + + client_->SendMessage(request); + + client_->SendData(string(), true); + + client_->WaitForResponse(); + EXPECT_EQ(kFooResponseBody, client_->response_body()); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); + + request.AddBody("foo", true); + + client_->SendMessage(request); + client_->SendData(string(), true); + client_->WaitForResponse(); + EXPECT_EQ(kFooResponseBody, client_->response_body()); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, MultipleRequestResponse) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, MultipleClients) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + scoped_ptr<QuicTestClient> client2(CreateQuicClient()); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddHeader("content-length", "3"); + request.set_has_complete_message(false); + + client_->SendMessage(request); + client2->SendMessage(request); + + client_->SendData("bar", true); + client_->WaitForResponse(); + EXPECT_EQ(kFooResponseBody, client_->response_body()); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); + + client2->SendData("eep", true); + client2->WaitForResponse(); + EXPECT_EQ(kFooResponseBody, client2->response_body()); + EXPECT_EQ(200u, client2->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, RequestOverMultiplePackets) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + // Set things up so we have a small payload, to guarantee fragmentation. + // A congestion feedback frame can't be split into multiple packets, make sure + // that our packet have room for at least this amount after the normal headers + // are added. + + // TODO(rch) handle this better when we have different encryption options. + const size_t kStreamDataLength = 3; + const QuicStreamId kStreamId = 1u; + const QuicStreamOffset kStreamOffset = 0u; + size_t stream_payload_size = + QuicFramer::GetMinStreamFrameSize( + GetParam(), kStreamId, kStreamOffset, true) + kStreamDataLength; + size_t min_payload_size = + std::max(kCongestionFeedbackFrameSize, stream_payload_size); + size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size); + // TODO(satyashekhar): Fix this when versioning is implemented. + client_->options()->max_packet_length = + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion, + PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) + + ciphertext_size; + + // Make sure our request is too large to fit in one packet. + EXPECT_GT(strlen(kLargeRequest), min_payload_size); + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest)); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, MultipleFramesRandomOrder) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + // Set things up so we have a small payload, to guarantee fragmentation. + // A congestion feedback frame can't be split into multiple packets, make sure + // that our packet have room for at least this amount after the normal headers + // are added. + + // TODO(rch) handle this better when we have different encryption options. + const size_t kStreamDataLength = 3; + const QuicStreamId kStreamId = 1u; + const QuicStreamOffset kStreamOffset = 0u; + size_t stream_payload_size = + QuicFramer::GetMinStreamFrameSize( + GetParam(), kStreamId, kStreamOffset, true) + kStreamDataLength; + size_t min_payload_size = + std::max(kCongestionFeedbackFrameSize, stream_payload_size); + size_t ciphertext_size = NullEncrypter().GetCiphertextSize(min_payload_size); + // TODO(satyashekhar): Fix this when versioning is implemented. + client_->options()->max_packet_length = + GetPacketHeaderSize(PACKET_8BYTE_GUID, !kIncludeVersion, + PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) + + ciphertext_size; + client_->options()->random_reorder = true; + + // Make sure our request is too large to fit in one packet. + EXPECT_GT(strlen(kLargeRequest), min_payload_size); + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest(kLargeRequest)); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, PostMissingBytes) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + // Add a content length header with no body. + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddHeader("content-length", "3"); + request.set_skip_message_validation(true); + + // This should be detected as stream fin without complete request, + // triggering an error response. + client_->SendCustomSynchronousRequest(request); + EXPECT_EQ("bad", client_->response_body()); + EXPECT_EQ(500u, client_->response_headers()->parsed_response_code()); +} + +TEST_P(EndToEndTest, LargePost) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + // Connect with lower fake packet loss than we'd like to test. Until + // b/10126687 is fixed, losing handshake packets is pretty brutal. + // FLAGS_fake_packet_loss_percentage = 5; + ASSERT_TRUE(Initialize()); + + // Wait for the server SHLO before upping the packet loss. + client_->client()->WaitForCryptoHandshakeConfirmed(); + // FLAGS_fake_packet_loss_percentage = 30; + + string body; + GenerateBody(&body, 10240); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddBody(body, true); + + EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request)); +} + +// TODO(ianswett): Enable once b/9295090 is fixed. +TEST_P(EndToEndTest, DISABLED_LargePostFEC) { + // FLAGS_fake_packet_loss_percentage = 30; + ASSERT_TRUE(Initialize()); + client_->options()->max_packets_per_fec_group = 6; + + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + // FLAGS_fake_packet_loss_percentage = 30; + ASSERT_TRUE(Initialize()); + client_->options()->max_packets_per_fec_group = 6; + + string body; + GenerateBody(&body, 10240); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddBody(body, true); + + EXPECT_EQ(kFooResponseBody, client_->SendCustomSynchronousRequest(request)); +} + +/*TEST_P(EndToEndTest, PacketTooLarge) { + FLAGS_quic_allow_oversized_packets_for_test = true; + ASSERT_TRUE(Initialize()); + + string body; + GenerateBody(&body, kMaxPacketSize); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddBody(body, true); + client_->options()->max_packet_length = 20480; + + EXPECT_EQ("", client_->SendCustomSynchronousRequest(request)); + EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error()); + EXPECT_EQ(QUIC_PACKET_TOO_LARGE, client_->connection_error()); +}*/ + +TEST_P(EndToEndTest, InvalidStream) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + string body; + GenerateBody(&body, kMaxPacketSize); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddBody(body, true); + // Force the client to write with a stream ID belonging to a nonexistent + // server-side stream. + QuicSessionPeer::SetNextStreamId(client_->client()->session(), 2); + + client_->SendCustomSynchronousRequest(request); +// EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error()); + EXPECT_EQ(QUIC_PACKET_FOR_NONEXISTENT_STREAM, client_->connection_error()); +} + +// TODO(rch): this test seems to cause net_unittests timeouts :| +TEST_P(EndToEndTest, DISABLED_MultipleTermination) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + scoped_ptr<QuicTestClient> client2(CreateQuicClient()); + + HTTPMessage request(HttpConstants::HTTP_1_1, + HttpConstants::POST, "/foo"); + request.AddHeader("content-length", "3"); + request.set_has_complete_message(false); + + // Set the offset so we won't frame. Otherwise when we pick up termination + // before HTTP framing is complete, we send an error and close the stream, + // and the second write is picked up as writing on a closed stream. + QuicReliableClientStream* stream = client_->GetOrCreateStream(); + ASSERT_TRUE(stream != NULL); + ReliableQuicStreamPeer::SetStreamBytesWritten(3, stream); + + client_->SendData("bar", true); + + // By default the stream protects itself from writes after terminte is set. + // Override this to test the server handling buggy clients. + ReliableQuicStreamPeer::SetWriteSideClosed( + false, client_->GetOrCreateStream()); + +#if !defined(WIN32) && defined(GTEST_HAS_DEATH_TEST) +#if !defined(DCHECK_ALWAYS_ON) + EXPECT_DEBUG_DEATH({ + client_->SendData("eep", true); + client_->WaitForResponse(); + EXPECT_EQ(QUIC_MULTIPLE_TERMINATION_OFFSETS, client_->stream_error()); + }, + "Check failed: !fin_buffered_"); +#else + EXPECT_DEATH({ + client_->SendData("eep", true); + client_->WaitForResponse(); + EXPECT_EQ(QUIC_MULTIPLE_TERMINATION_OFFSETS, client_->stream_error()); + }, + "Check failed: !fin_buffered_"); +#endif +#endif +} + +TEST_P(EndToEndTest, Timeout) { + client_config_.set_idle_connection_state_lifetime( + QuicTime::Delta::FromMicroseconds(500), + QuicTime::Delta::FromMicroseconds(500)); + // Note: we do NOT ASSERT_TRUE: we may time out during initial handshake: + // that's enough to validate timeout in this case. + Initialize(); + while (client_->client()->connected()) { + client_->client()->WaitForEvents(); + } +} + +TEST_P(EndToEndTest, LimitMaxOpenStreams) { + // Server limits the number of max streams to 2. + server_config_.set_max_streams_per_connection(2, 2); + // Client tries to negotiate for 10. + client_config_.set_max_streams_per_connection(10, 5); + + ASSERT_TRUE(Initialize()); + client_->client()->WaitForCryptoHandshakeConfirmed(); + QuicConfig* client_negotiated_config = client_->client()->session()->config(); + EXPECT_EQ(2u, client_negotiated_config->max_streams_per_connection()); +} + +TEST_P(EndToEndTest, ResetConnection) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); + client_->ResetConnection(); + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); +} + +class WrongAddressWriter : public QuicPacketWriter { + public: + explicit WrongAddressWriter(int fd) : fd_(fd) { + IPAddressNumber ip; + CHECK(net::ParseIPLiteralToNumber("127.0.0.2", &ip)); + self_address_ = IPEndPoint(ip, 0); + } + + virtual int WritePacket(const char* buffer, size_t buf_len, + const IPAddressNumber& real_self_address, + const IPEndPoint& peer_address, + QuicBlockedWriterInterface* blocked_writer, + int* error) OVERRIDE { + return QuicSocketUtils::WritePacket(fd_, buffer, buf_len, + self_address_.address(), peer_address, + error); + } + + IPEndPoint self_address_; + int fd_; +}; + +TEST_P(EndToEndTest, ConnectionMigration) { + // TODO(rtenneti): Delete this when NSS is supported. + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + ASSERT_TRUE(Initialize()); + + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + EXPECT_EQ(200u, client_->response_headers()->parsed_response_code()); + + WrongAddressWriter writer(QuicClientPeer::GetFd(client_->client())); + QuicEpollConnectionHelper* helper = + reinterpret_cast<QuicEpollConnectionHelper*>( + QuicConnectionPeer::GetHelper( + client_->client()->session()->connection())); + QuicEpollConnectionHelperPeer::SetWriter(helper, &writer); + + client_->SendSynchronousRequest("/bar"); + QuicEpollConnectionHelperPeer::SetWriter(helper, NULL); + + EXPECT_EQ(QUIC_STREAM_CONNECTION_ERROR, client_->stream_error()); + EXPECT_EQ(QUIC_ERROR_MIGRATING_ADDRESS, client_->connection_error()); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_client.cc b/chromium/net/tools/quic/quic_client.cc new file mode 100644 index 00000000000..86fa6c48b75 --- /dev/null +++ b/chromium/net/tools/quic/quic_client.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_client.h" + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "base/logging.h" +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_connection.h" +#include "net/quic/quic_data_reader.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/flip_server/balsa_headers.h" +#include "net/tools/quic/quic_epoll_connection_helper.h" +#include "net/tools/quic/quic_reliable_client_stream.h" +#include "net/tools/quic/quic_socket_utils.h" + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +namespace net { +namespace tools { + +const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET; + +QuicClient::QuicClient(IPEndPoint server_address, + const string& server_hostname, + const QuicVersion version) + : server_address_(server_address), + server_hostname_(server_hostname), + local_port_(0), + fd_(-1), + initialized_(false), + packets_dropped_(0), + overflow_supported_(false), + version_(version) { + config_.SetDefaults(); +} + +QuicClient::QuicClient(IPEndPoint server_address, + const string& server_hostname, + const QuicConfig& config, + const QuicVersion version) + : server_address_(server_address), + server_hostname_(server_hostname), + config_(config), + local_port_(0), + fd_(-1), + initialized_(false), + packets_dropped_(0), + overflow_supported_(false), + version_(version) { +} + +QuicClient::~QuicClient() { + if (connected()) { + session()->connection()->SendConnectionClosePacket( + QUIC_PEER_GOING_AWAY, ""); + } +} + +bool QuicClient::Initialize() { + DCHECK(!initialized_); + + epoll_server_.set_timeout_in_us(50 * 1000); + crypto_config_.SetDefaults(); + + int address_family = server_address_.GetSockAddrFamily(); + fd_ = socket(address_family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); + if (fd_ < 0) { + LOG(ERROR) << "CreateSocket() failed: " << strerror(errno); + return false; + } + + int get_overflow = 1; + int rc = setsockopt(fd_, SOL_SOCKET, SO_RXQ_OVFL, &get_overflow, + sizeof(get_overflow)); + if (rc < 0) { + DLOG(WARNING) << "Socket overflow detection not supported"; + } else { + overflow_supported_ = true; + } + + int get_local_ip = 1; + if (address_family == AF_INET) { + rc = setsockopt(fd_, IPPROTO_IP, IP_PKTINFO, + &get_local_ip, sizeof(get_local_ip)); + } else { + rc = setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &get_local_ip, sizeof(get_local_ip)); + } + + if (rc < 0) { + LOG(ERROR) << "IP detection not supported" << strerror(errno); + return false; + } + + if (bind_to_address_.size() != 0) { + client_address_ = IPEndPoint(bind_to_address_, local_port_); + } else if (address_family == AF_INET) { + IPAddressNumber any4; + CHECK(net::ParseIPLiteralToNumber("0.0.0.0", &any4)); + client_address_ = IPEndPoint(any4, local_port_); + } else { + IPAddressNumber any6; + CHECK(net::ParseIPLiteralToNumber("::", &any6)); + client_address_ = IPEndPoint(any6, local_port_); + } + + sockaddr_storage raw_addr; + socklen_t raw_addr_len = sizeof(raw_addr); + CHECK(client_address_.ToSockAddr(reinterpret_cast<sockaddr*>(&raw_addr), + &raw_addr_len)); + rc = bind(fd_, + reinterpret_cast<const sockaddr*>(&raw_addr), + sizeof(raw_addr)); + if (rc < 0) { + LOG(ERROR) << "Bind failed: " << strerror(errno); + return false; + } + + SockaddrStorage storage; + if (getsockname(fd_, storage.addr, &storage.addr_len) != 0 || + !client_address_.FromSockAddr(storage.addr, storage.addr_len)) { + LOG(ERROR) << "Unable to get self address. Error: " << strerror(errno); + } + + epoll_server_.RegisterFD(fd_, this, kEpollFlags); + initialized_ = true; + return true; +} + +bool QuicClient::Connect() { + if (!StartConnect()) { + return false; + } + while (EncryptionBeingEstablished()) { + WaitForEvents(); + } + return session_->connection()->connected(); +} + +bool QuicClient::StartConnect() { + DCHECK(!connected() && initialized_); + + QuicGuid guid = QuicRandom::GetInstance()->RandUint64(); + session_.reset(new QuicClientSession( + server_hostname_, + config_, + new QuicConnection(guid, server_address_, + CreateQuicConnectionHelper(), false, + version_), + &crypto_config_)); + return session_->CryptoConnect(); +} + +bool QuicClient::EncryptionBeingEstablished() { + return !session_->IsEncryptionEstablished() && + session_->connection()->connected(); +} + +void QuicClient::Disconnect() { + DCHECK(connected()); + + session()->connection()->SendConnectionClose(QUIC_PEER_GOING_AWAY); + epoll_server_.UnregisterFD(fd_); + close(fd_); + fd_ = -1; + initialized_ = false; +} + +void QuicClient::SendRequestsAndWaitForResponse( + const CommandLine::StringVector& args) { + for (uint32_t i = 0; i < args.size(); i++) { + BalsaHeaders headers; + headers.SetRequestFirstlineFromStringPieces("GET", args[i], "HTTP/1.1"); + CreateReliableClientStream()->SendRequest(headers, "", true); + } + + while (WaitForEvents()) { } +} + +QuicReliableClientStream* QuicClient::CreateReliableClientStream() { + if (!connected()) { + return NULL; + } + + return session_->CreateOutgoingReliableStream(); +} + +void QuicClient::WaitForStreamToClose(QuicStreamId id) { + DCHECK(connected()); + + while (!session_->IsClosedStream(id)) { + epoll_server_.WaitForEventsAndExecuteCallbacks(); + } +} + +void QuicClient::WaitForCryptoHandshakeConfirmed() { + DCHECK(connected()); + + while (!session_->IsCryptoHandshakeConfirmed()) { + epoll_server_.WaitForEventsAndExecuteCallbacks(); + } +} + +bool QuicClient::WaitForEvents() { + DCHECK(connected()); + + epoll_server_.WaitForEventsAndExecuteCallbacks(); + return session_->num_active_requests() != 0; +} + +void QuicClient::OnEvent(int fd, EpollEvent* event) { + DCHECK_EQ(fd, fd_); + + if (event->in_events & EPOLLIN) { + while (connected() && ReadAndProcessPacket()) { + } + } + if (connected() && (event->in_events & EPOLLOUT)) { + session_->connection()->OnCanWrite(); + } + if (event->in_events & EPOLLERR) { + DLOG(INFO) << "Epollerr"; + } +} + +QuicPacketCreator::Options* QuicClient::options() { + if (session() == NULL) { + return NULL; + } + return session_->options(); +} + +bool QuicClient::connected() const { + return session_.get() && session_->connection() && + session_->connection()->connected(); +} + +QuicEpollConnectionHelper* QuicClient::CreateQuicConnectionHelper() { + return new QuicEpollConnectionHelper(fd_, &epoll_server_); +} + +bool QuicClient::ReadAndProcessPacket() { + // Allocate some extra space so we can send an error if the server goes over + // the limit. + char buf[2 * kMaxPacketSize]; + + IPEndPoint server_address; + IPAddressNumber client_ip; + + int bytes_read = QuicSocketUtils::ReadPacket( + fd_, buf, arraysize(buf), overflow_supported_ ? &packets_dropped_ : NULL, + &client_ip, &server_address); + + if (bytes_read < 0) { + return false; + } + + QuicEncryptedPacket packet(buf, bytes_read, false); + QuicGuid our_guid = session_->connection()->guid(); + QuicGuid packet_guid; + + if (!QuicFramer::ReadGuidFromPacket(packet, &packet_guid)) { + DLOG(INFO) << "Could not read GUID from packet"; + return true; + } + if (packet_guid != our_guid) { + DLOG(INFO) << "Ignoring packet from unexpected GUID: " + << packet_guid << " instead of " << our_guid; + return true; + } + + IPEndPoint client_address(client_ip, client_address_.port()); + session_->connection()->ProcessUdpPacket( + client_address, server_address, packet); + return true; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_client.h b/chromium/net/tools/quic/quic_client.h new file mode 100644 index 00000000000..ca20a8d2158 --- /dev/null +++ b/chromium/net/tools/quic/quic_client.h @@ -0,0 +1,203 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A toy client, which connects to a specified port and sends QUIC +// request to that endpoint. + +#ifndef NET_TOOLS_QUIC_QUIC_CLIENT_H_ +#define NET_TOOLS_QUIC_QUIC_CLIENT_H_ + +#include <string> + +#include "base/command_line.h" +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_handshake.h" +#include "net/quic/quic_config.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_packet_creator.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_client_session.h" +#include "net/tools/quic/quic_reliable_client_stream.h" + +namespace net { + +class ProofVerifier; + +namespace tools { + +class QuicEpollConnectionHelper; + +namespace test { +class QuicClientPeer; +} // namespace test + +class QuicClient : public EpollCallbackInterface { + public: + QuicClient(IPEndPoint server_address, const std::string& server_hostname, + const QuicVersion version); + QuicClient(IPEndPoint server_address, + const std::string& server_hostname, + const QuicConfig& config, + const QuicVersion version); + + virtual ~QuicClient(); + + // Initializes the client to create a connection. Should be called exactly + // once before calling StartConnect or Connect. Returns true if the + // initialization succeeds, false otherwise. + bool Initialize(); + + // "Connect" to the QUIC server, including performing synchronous crypto + // handshake. + bool Connect(); + + // Start the crypto handshake. This can be done in place of the synchronous + // Connect(), but callers are responsible for making sure the crypto handshake + // completes. + bool StartConnect(); + + // Returns true if the crypto handshake has yet to establish encryption. + // Returns false if encryption is active (even if the server hasn't confirmed + // the handshake) or if the connection has been closed. + bool EncryptionBeingEstablished(); + + // Disconnects from the QUIC server. + void Disconnect(); + + // Sends a request simple GET for each URL in arg, and then waits for + // each to complete. + void SendRequestsAndWaitForResponse(const CommandLine::StringVector& args); + + // Returns a newly created CreateReliableClientStream, owned by the + // QuicClient. + QuicReliableClientStream* CreateReliableClientStream(); + + // Wait for events until the stream with the given ID is closed. + void WaitForStreamToClose(QuicStreamId id); + + // Wait for events until the handshake is confirmed. + void WaitForCryptoHandshakeConfirmed(); + + // Wait up to 50ms, and handle any events which occur. + // Returns true if there are any outstanding requests. + bool WaitForEvents(); + + // From EpollCallbackInterface + virtual void OnRegistration( + EpollServer* eps, int fd, int event_mask) OVERRIDE {} + virtual void OnModification(int fd, int event_mask) OVERRIDE {} + virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE; + // |fd_| can be unregistered without the client being disconnected. This + // happens in b3m QuicProber where we unregister |fd_| to feed in events to + // the client from the SelectServer. + virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {} + virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {} + + QuicPacketCreator::Options* options(); + + QuicClientSession* session() { return session_.get(); } + + bool connected() const; + + int packets_dropped() { return packets_dropped_; } + + void set_bind_to_address(IPAddressNumber address) { + bind_to_address_ = address; + } + + IPAddressNumber bind_to_address() const { return bind_to_address_; } + + void set_local_port(int local_port) { local_port_ = local_port; } + + int local_port() { return local_port_; } + + const IPEndPoint& server_address() const { return server_address_; } + + const IPEndPoint& client_address() const { return client_address_; } + + EpollServer* epoll_server() { return &epoll_server_; } + + int fd() { return fd_; } + + // This should only be set before the initial Connect() + void set_server_hostname(const string& hostname) { + server_hostname_ = hostname; + } + + // SetProofVerifier sets the ProofVerifier that will be used to verify the + // server's certificate and takes ownership of |verifier|. + void SetProofVerifier(ProofVerifier* verifier) { + // TODO(rtenneti): We should set ProofVerifier in QuicClientSession. + crypto_config_.SetProofVerifier(verifier); + } + + // SetChannelIDSigner sets a ChannelIDSigner that will be called when the + // server supports channel IDs to sign a message proving possession of the + // given ChannelID. This object takes ownership of |signer|. + void SetChannelIDSigner(ChannelIDSigner* signer) { + crypto_config_.SetChannelIDSigner(signer); + } + + protected: + virtual QuicEpollConnectionHelper* CreateQuicConnectionHelper(); + + private: + friend class net::tools::test::QuicClientPeer; + + // Read a UDP packet and hand it to the framer. + bool ReadAndProcessPacket(); + + // Set of streams created (and owned) by this client + base::hash_set<QuicReliableClientStream*> streams_; + + // Address of the server. + const IPEndPoint server_address_; + + // Hostname of the server. This may be a DNS name or an IP address literal. + std::string server_hostname_; + + // config_ and crypto_config_ contain configuration and cached state about + // servers. + QuicConfig config_; + QuicCryptoClientConfig crypto_config_; + + // Address of the client if the client is connected to the server. + IPEndPoint client_address_; + + // If initialized, the address to bind to. + IPAddressNumber bind_to_address_; + // Local port to bind to. Initialize to 0. + int local_port_; + + // Session which manages streams. + scoped_ptr<QuicClientSession> session_; + // Listens for events on the client socket. + EpollServer epoll_server_; + // UDP socket. + int fd_; + + // Tracks if the client is initialized to connect. + bool initialized_; + + // If overflow_supported_ is true, this will be the number of packets dropped + // during the lifetime of the server. This may overflow if enough packets + // are dropped. + int packets_dropped_; + + // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped + // because the socket would otherwise overflow. + bool overflow_supported_; + + // Which QUIC version does this client talk? + QuicVersion version_; + + DISALLOW_COPY_AND_ASSIGN(QuicClient); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_CLIENT_H_ diff --git a/chromium/net/tools/quic/quic_client_bin.cc b/chromium/net/tools/quic/quic_client_bin.cc new file mode 100644 index 00000000000..e13bea5e8c1 --- /dev/null +++ b/chromium/net/tools/quic/quic_client_bin.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A binary wrapper for QuicClient. Connects to --hostname or --address on +// --port and requests URLs specified on the command line. +// +// For example: +// quic_client --port=6122 /index.html /favicon.ico + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/quic/quic_client.h" + +int32 FLAGS_port = 6121; +std::string FLAGS_address = "127.0.0.1"; +std::string FLAGS_hostname = "localhost"; + +int main(int argc, char *argv[]) { + CommandLine::Init(argc, argv); + CommandLine* line = CommandLine::ForCurrentProcess(); + if (line->HasSwitch("port")) { + int port; + if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) { + FLAGS_port = port; + } + } + if (line->HasSwitch("address")) { + FLAGS_address = line->GetSwitchValueASCII("address"); + } + if (line->HasSwitch("hostname")) { + FLAGS_hostname = line->GetSwitchValueASCII("hostname"); + } + LOG(INFO) << "server port: " << FLAGS_port + << " address: " << FLAGS_address + << " hostname: " << FLAGS_hostname; + + base::AtExitManager exit_manager; + + net::IPAddressNumber addr; + CHECK(net::ParseIPLiteralToNumber(FLAGS_address, &addr)); + // TODO(rjshade): Set version on command line. + net::tools::QuicClient client( + net::IPEndPoint(addr, FLAGS_port), FLAGS_hostname, net::QuicVersionMax()); + + client.Initialize(); + + if (!client.Connect()) return 1; + + client.SendRequestsAndWaitForResponse(line->GetArgs()); + return 0; +} diff --git a/chromium/net/tools/quic/quic_client_session.cc b/chromium/net/tools/quic/quic_client_session.cc new file mode 100644 index 00000000000..c41df6702b5 --- /dev/null +++ b/chromium/net/tools/quic/quic_client_session.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_client_session.h" + +#include "base/logging.h" +#include "net/quic/crypto/crypto_protocol.h" +#include "net/tools/quic/quic_reliable_client_stream.h" +#include "net/tools/quic/quic_spdy_client_stream.h" + +using std::string; + +namespace net { +namespace tools { + +QuicClientSession::QuicClientSession( + const string& server_hostname, + const QuicConfig& config, + QuicConnection* connection, + QuicCryptoClientConfig* crypto_config) + : QuicSession(connection, config, false), + crypto_stream_(server_hostname, this, crypto_config) { +} + +QuicClientSession::~QuicClientSession() { +} + +QuicReliableClientStream* QuicClientSession::CreateOutgoingReliableStream() { + if (!crypto_stream_.encryption_established()) { + DLOG(INFO) << "Encryption not active so no outgoing stream created."; + return NULL; + } + if (GetNumOpenStreams() >= get_max_open_streams()) { + DLOG(INFO) << "Failed to create a new outgoing stream. " + << "Already " << GetNumOpenStreams() << " open."; + return NULL; + } + if (goaway_received()) { + DLOG(INFO) << "Failed to create a new outgoing stream. " + << "Already received goaway."; + return NULL; + } + QuicReliableClientStream* stream + = new QuicSpdyClientStream(GetNextStreamId(), this); + ActivateStream(stream); + return stream; +} + +QuicCryptoClientStream* QuicClientSession::GetCryptoStream() { + return &crypto_stream_; +} + +bool QuicClientSession::CryptoConnect() { + return crypto_stream_.CryptoConnect(); +} + +ReliableQuicStream* QuicClientSession::CreateIncomingReliableStream( + QuicStreamId id) { + DLOG(ERROR) << "Server push not supported"; + return NULL; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_client_session.h b/chromium/net/tools/quic/quic_client_session.h new file mode 100644 index 00000000000..f51aeeaff1b --- /dev/null +++ b/chromium/net/tools/quic/quic_client_session.h @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A client specific QuicSession subclass. + +#ifndef NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_ +#define NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_ + +#include <string> + +#include "net/quic/quic_crypto_client_stream.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_session.h" +#include "net/tools/quic/quic_reliable_client_stream.h" + +namespace net { + +class QuicConnection; +class ReliableQuicStream; + +namespace tools { + +class QuicReliableClientStream; + +class QuicClientSession : public QuicSession { + public: + QuicClientSession(const std::string& server_hostname, + const QuicConfig& config, + QuicConnection* connection, + QuicCryptoClientConfig* crypto_config); + virtual ~QuicClientSession(); + + // QuicSession methods: + virtual QuicReliableClientStream* CreateOutgoingReliableStream() OVERRIDE; + virtual QuicCryptoClientStream* GetCryptoStream() OVERRIDE; + + // Performs a crypto handshake with the server. Returns true if the crypto + // handshake is started successfully. + bool CryptoConnect(); + + protected: + // QuicSession methods: + virtual ReliableQuicStream* CreateIncomingReliableStream( + QuicStreamId id) OVERRIDE; + + private: + QuicCryptoClientStream crypto_stream_; + + DISALLOW_COPY_AND_ASSIGN(QuicClientSession); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_CLIENT_SESSION_H_ diff --git a/chromium/net/tools/quic/quic_client_session_test.cc b/chromium/net/tools/quic/quic_client_session_test.cc new file mode 100644 index 00000000000..1b1ece6597e --- /dev/null +++ b/chromium/net/tools/quic/quic_client_session_test.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_client_session.h" + +#include <vector> + +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/aes_128_gcm_12_encrypter.h" +#include "net/quic/test_tools/crypto_test_utils.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/quic/quic_reliable_client_stream.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using net::test::CryptoTestUtils; +using net::test::PacketSavingConnection; + +namespace net { +namespace tools { +namespace test { +namespace { + +const char kServerHostname[] = "www.example.com"; + +class ToolsQuicClientSessionTest : public ::testing::Test { + protected: + ToolsQuicClientSessionTest() + : guid_(1), + connection_(new PacketSavingConnection(guid_, IPEndPoint(), false)) { + crypto_config_.SetDefaults(); + session_.reset(new QuicClientSession(kServerHostname, QuicConfig(), + connection_, &crypto_config_)); + session_->config()->SetDefaults(); + } + + void CompleteCryptoHandshake() { + ASSERT_TRUE(session_->CryptoConnect()); + CryptoTestUtils::HandshakeWithFakeServer( + connection_, session_->GetCryptoStream()); + } + + QuicGuid guid_; + PacketSavingConnection* connection_; + scoped_ptr<QuicClientSession> session_; + QuicCryptoClientConfig crypto_config_; +}; + +TEST_F(ToolsQuicClientSessionTest, CryptoConnect) { + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + CompleteCryptoHandshake(); +} + +TEST_F(ToolsQuicClientSessionTest, MaxNumStreams) { + session_->config()->set_max_streams_per_connection(1, 1); + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + // FLAGS_max_streams_per_connection = 1; + // Initialize crypto before the client session will create a stream. + CompleteCryptoHandshake(); + + QuicReliableClientStream* stream = + session_->CreateOutgoingReliableStream(); + ASSERT_TRUE(stream); + EXPECT_FALSE(session_->CreateOutgoingReliableStream()); + + // Close a stream and ensure I can now open a new one. + session_->CloseStream(stream->id()); + stream = session_->CreateOutgoingReliableStream(); + EXPECT_TRUE(stream); +} + +TEST_F(ToolsQuicClientSessionTest, GoAwayReceived) { + if (!Aes128Gcm12Encrypter::IsSupported()) { + LOG(INFO) << "AES GCM not supported. Test skipped."; + return; + } + + CompleteCryptoHandshake(); + + // After receiving a GoAway, I should no longer be able to create outgoing + // streams. + session_->OnGoAway(QuicGoAwayFrame(QUIC_PEER_GOING_AWAY, 1u, "Going away.")); + EXPECT_EQ(NULL, session_->CreateOutgoingReliableStream()); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_dispatcher.cc b/chromium/net/tools/quic/quic_dispatcher.cc new file mode 100644 index 00000000000..68691f7dc58 --- /dev/null +++ b/chromium/net/tools/quic/quic_dispatcher.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_dispatcher.h" + +#include <errno.h> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "net/quic/quic_blocked_writer_interface.h" +#include "net/quic/quic_utils.h" +#include "net/tools/quic/quic_epoll_connection_helper.h" +#include "net/tools/quic/quic_socket_utils.h" + +namespace net { +namespace tools { + +using std::make_pair; + +class DeleteSessionsAlarm : public EpollAlarm { + public: + explicit DeleteSessionsAlarm(QuicDispatcher* dispatcher) + : dispatcher_(dispatcher) { + } + + virtual int64 OnAlarm() OVERRIDE { + EpollAlarm::OnAlarm(); + dispatcher_->DeleteSessions(); + return 0; + } + + private: + QuicDispatcher* dispatcher_; +}; + +QuicDispatcher::QuicDispatcher(const QuicConfig& config, + const QuicCryptoServerConfig& crypto_config, + int fd, + EpollServer* epoll_server) + : config_(config), + crypto_config_(crypto_config), + time_wait_list_manager_( + new QuicTimeWaitListManager(this, epoll_server)), + delete_sessions_alarm_(new DeleteSessionsAlarm(this)), + epoll_server_(epoll_server), + fd_(fd), + write_blocked_(false) { +} + +QuicDispatcher::~QuicDispatcher() { + STLDeleteValues(&session_map_); + STLDeleteElements(&closed_session_list_); +} + +int QuicDispatcher::WritePacket(const char* buffer, size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address, + QuicBlockedWriterInterface* writer, + int* error) { + if (write_blocked_) { + write_blocked_list_.AddBlockedObject(writer); + *error = EAGAIN; + return -1; + } + + int rc = QuicSocketUtils::WritePacket(fd_, buffer, buf_len, + self_address, peer_address, + error); + if (rc == -1 && (*error == EWOULDBLOCK || *error == EAGAIN)) { + write_blocked_list_.AddBlockedObject(writer); + write_blocked_ = true; + } + return rc; +} + +void QuicDispatcher::ProcessPacket(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet) { + QuicSession* session; + + SessionMap::iterator it = session_map_.find(guid); + if (it == session_map_.end()) { + if (time_wait_list_manager_->IsGuidInTimeWait(guid)) { + time_wait_list_manager_->ProcessPacket(server_address, + client_address, + guid, + packet); + return; + } + session = CreateQuicSession(guid, client_address, fd_, epoll_server_); + + if (session == NULL) { + DLOG(INFO) << "Failed to create session for " << guid; + // Add this guid fo the time-wait state, to safely nack future packets. + // We don't know the version here, so assume latest. + time_wait_list_manager_->AddGuidToTimeWait(guid, QuicVersionMax()); + time_wait_list_manager_->ProcessPacket(server_address, + client_address, + guid, + packet); + return; + } + DLOG(INFO) << "Created new session for " << guid; + session_map_.insert(make_pair(guid, session)); + } else { + session = it->second; + } + + session->connection()->ProcessUdpPacket( + server_address, client_address, packet); +} + +void QuicDispatcher::CleanUpSession(SessionMap::iterator it) { + QuicSession* session = it->second; + write_blocked_list_.RemoveBlockedObject(session->connection()); + time_wait_list_manager_->AddGuidToTimeWait(it->first, + session->connection()->version()); + session_map_.erase(it); +} + +void QuicDispatcher::DeleteSessions() { + STLDeleteElements(&closed_session_list_); +} + +bool QuicDispatcher::OnCanWrite() { + // We got an EPOLLOUT: the socket should not be blocked. + write_blocked_ = false; + + // Give each writer one attempt to write. + int num_writers = write_blocked_list_.NumObjects(); + for (int i = 0; i < num_writers; ++i) { + if (write_blocked_list_.IsEmpty()) { + break; + } + QuicBlockedWriterInterface* writer = + write_blocked_list_.GetNextBlockedObject(); + bool can_write_more = writer->OnCanWrite(); + if (write_blocked_) { + // We were unable to write. Wait for the next EPOLLOUT. + // In this case, the session would have been added to the blocked list + // up in WritePacket. + return false; + } + // The socket is not blocked but the writer has ceded work. Add it to the + // end of the list. + if (can_write_more) { + write_blocked_list_.AddBlockedObject(writer); + } + } + + // We're not write blocked. Return true if there's more work to do. + return !write_blocked_list_.IsEmpty(); +} + +void QuicDispatcher::Shutdown() { + while (!session_map_.empty()) { + QuicSession* session = session_map_.begin()->second; + session->connection()->SendConnectionClose(QUIC_PEER_GOING_AWAY); + // Validate that the session removes itself from the session map on close. + DCHECK(session_map_.empty() || session_map_.begin()->second != session); + } + DeleteSessions(); +} + +void QuicDispatcher::OnConnectionClose(QuicGuid guid, QuicErrorCode error) { + SessionMap::iterator it = session_map_.find(guid); + if (it == session_map_.end()) { + LOG(DFATAL) << "GUID " << guid << " does not exist in the session map. " + << "Error: " << QuicUtils::ErrorToString(error); + return; + } + + DLOG_IF(INFO, error != QUIC_NO_ERROR) << "Closing connection due to error: " + << QuicUtils::ErrorToString(error); + + if (closed_session_list_.empty()) { + epoll_server_->RegisterAlarmApproximateDelta( + 0, delete_sessions_alarm_.get()); + } + closed_session_list_.push_back(it->second); + CleanUpSession(it); +} + +QuicSession* QuicDispatcher::CreateQuicSession( + QuicGuid guid, + const IPEndPoint& client_address, + int fd, + EpollServer* epoll_server) { + QuicConnectionHelperInterface* helper = + new QuicEpollConnectionHelper(this, epoll_server); + QuicServerSession* session = new QuicServerSession( + config_, new QuicConnection(guid, client_address, helper, true, + QuicVersionMax()), this); + session->InitializeSession(crypto_config_); + return session; +} + +} // namespace tools +} // namespace net + + diff --git a/chromium/net/tools/quic/quic_dispatcher.h b/chromium/net/tools/quic/quic_dispatcher.h new file mode 100644 index 00000000000..bbf8d9b4941 --- /dev/null +++ b/chromium/net/tools/quic/quic_dispatcher.h @@ -0,0 +1,147 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A server side dispatcher which dispatches a given client's data to their +// stream. + +#ifndef NET_TOOLS_QUIC_QUIC_DISPATCHER_H_ +#define NET_TOOLS_QUIC_QUIC_DISPATCHER_H_ + +#include <list> + +#include "base/containers/hash_tables.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/blocked_list.h" +#include "net/quic/quic_blocked_writer_interface.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_packet_writer.h" +#include "net/tools/quic/quic_server_session.h" +#include "net/tools/quic/quic_time_wait_list_manager.h" + +#if defined(COMPILER_GCC) +namespace BASE_HASH_NAMESPACE { +template<> +struct hash<net::QuicBlockedWriterInterface*> { + std::size_t operator()( + const net::QuicBlockedWriterInterface* ptr) const { + return hash<size_t>()(reinterpret_cast<size_t>(ptr)); + } +}; +} +#endif + +namespace net { + +class EpollServer; +class QuicConfig; +class QuicCryptoServerConfig; +class QuicSession; + +namespace tools { + +namespace test { +class QuicDispatcherPeer; +} // namespace test + +class DeleteSessionsAlarm; +class QuicDispatcher : public QuicPacketWriter, public QuicSessionOwner { + public: + typedef BlockedList<QuicBlockedWriterInterface*> WriteBlockedList; + + // Due to the way delete_sessions_closure_ is registered, the Dispatcher + // must live until epoll_server Shutdown. + QuicDispatcher(const QuicConfig& config, + const QuicCryptoServerConfig& crypto_config, + int fd, + EpollServer* epoll_server); + virtual ~QuicDispatcher(); + + // QuicPacketWriter + virtual int WritePacket(const char* buffer, size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address, + QuicBlockedWriterInterface* writer, + int* error) OVERRIDE; + + virtual void ProcessPacket(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet); + + // Called when the underyling connection becomes writable to allow + // queued writes to happen. + // + // Returns true if more writes are possible, false otherwise. + virtual bool OnCanWrite(); + + // Sends ConnectionClose frames to all connected clients. + void Shutdown(); + + // Ensure that the closed connection is cleaned up asynchronously. + virtual void OnConnectionClose(QuicGuid guid, QuicErrorCode error) OVERRIDE; + + int fd() { return fd_; } + void set_fd(int fd) { fd_ = fd; } + + typedef base::hash_map<QuicGuid, QuicSession*> SessionMap; + + virtual QuicSession* CreateQuicSession( + QuicGuid guid, + const IPEndPoint& client_address, + int fd, + EpollServer* epoll_server); + + // Deletes all sessions on the closed session list and clears the list. + void DeleteSessions(); + + const SessionMap& session_map() const { return session_map_; } + + WriteBlockedList* write_blocked_list() { return &write_blocked_list_; } + + protected: + const QuicConfig& config_; + const QuicCryptoServerConfig& crypto_config_; + + QuicTimeWaitListManager* time_wait_list_manager() { + return time_wait_list_manager_.get(); + } + + private: + friend class net::tools::test::QuicDispatcherPeer; + + // Removes the session from the session map and write blocked list, and + // adds the GUID to the time-wait list. + void CleanUpSession(SessionMap::iterator it); + + // The list of connections waiting to write. + WriteBlockedList write_blocked_list_; + + SessionMap session_map_; + + // Entity that manages guids in time wait state. + scoped_ptr<QuicTimeWaitListManager> time_wait_list_manager_; + + // An alarm which deletes closed sessions. + scoped_ptr<DeleteSessionsAlarm> delete_sessions_alarm_; + + // The list of closed but not-yet-deleted sessions. + std::list<QuicSession*> closed_session_list_; + + EpollServer* epoll_server_; // Owned by the server. + + // The connection for client-server communication + int fd_; + + // True if the session is write blocked due to the socket returning EAGAIN. + // False if we have gotten a call to OnCanWrite after the last failed write. + bool write_blocked_; + + DISALLOW_COPY_AND_ASSIGN(QuicDispatcher); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_DISPATCHER_H_ diff --git a/chromium/net/tools/quic/quic_dispatcher_test.cc b/chromium/net/tools/quic/quic_dispatcher_test.cc new file mode 100644 index 00000000000..059d8334dc0 --- /dev/null +++ b/chromium/net/tools/quic/quic_dispatcher_test.cc @@ -0,0 +1,390 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_dispatcher.h" + +#include <string> + +#include "base/strings/string_piece.h" +#include "net/quic/crypto/crypto_handshake.h" +#include "net/quic/crypto/crypto_server_config.h" +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_crypto_stream.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_time_wait_list_manager.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using net::EpollServer; +using net::test::MockSession; +using net::tools::test::MockConnection; +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::InSequence; +using testing::Return; +using testing::WithoutArgs; + +namespace net { +namespace tools { +namespace test { +class QuicDispatcherPeer { + public: + static void SetTimeWaitListManager( + QuicDispatcher* dispatcher, + QuicTimeWaitListManager* time_wait_list_manager) { + dispatcher->time_wait_list_manager_.reset(time_wait_list_manager); + } + + static void SetWriteBlocked(QuicDispatcher* dispatcher) { + dispatcher->write_blocked_ = true; + } +}; + +namespace { + +class TestDispatcher : public QuicDispatcher { + public: + explicit TestDispatcher(const QuicConfig& config, + const QuicCryptoServerConfig& crypto_config, + EpollServer* eps) + : QuicDispatcher(config, crypto_config, 1, eps) {} + + MOCK_METHOD4(CreateQuicSession, QuicSession*( + QuicGuid guid, + const IPEndPoint& client_address, + int fd, + EpollServer* eps)); + using QuicDispatcher::write_blocked_list; +}; + +// A Connection class which unregisters the session from the dispatcher +// when sending connection close. +// It'd be slightly more realistic to do this from the Session but it would +// involve a lot more mocking. +class MockServerConnection : public MockConnection { + public: + MockServerConnection(QuicGuid guid, + IPEndPoint address, + int fd, + EpollServer* eps, + QuicDispatcher* dispatcher) + : MockConnection(guid, address, fd, eps, true), + dispatcher_(dispatcher) { + } + void UnregisterOnConnectionClose() { + LOG(ERROR) << "Unregistering " << guid(); + dispatcher_->OnConnectionClose(guid(), QUIC_NO_ERROR); + } + private: + QuicDispatcher* dispatcher_; +}; + +QuicSession* CreateSession(QuicDispatcher* dispatcher, + QuicGuid guid, + const IPEndPoint& addr, + MockSession** session, + EpollServer* eps) { + MockServerConnection* connection = + new MockServerConnection(guid, addr, 0, eps, dispatcher); + *session = new MockSession(connection, true); + ON_CALL(*connection, SendConnectionClose(_)).WillByDefault( + WithoutArgs(Invoke( + connection, &MockServerConnection::UnregisterOnConnectionClose))); + EXPECT_CALL(*reinterpret_cast<MockConnection*>((*session)->connection()), + ProcessUdpPacket(_, addr, _)); + + return *session; +} + +class QuicDispatcherTest : public ::testing::Test { + public: + QuicDispatcherTest() + : crypto_config_(QuicCryptoServerConfig::TESTING, + QuicRandom::GetInstance()), + dispatcher_(config_, crypto_config_, &eps_), + session1_(NULL), + session2_(NULL) { + } + + virtual ~QuicDispatcherTest() {} + + MockConnection* connection1() { + return reinterpret_cast<MockConnection*>(session1_->connection()); + } + + MockConnection* connection2() { + return reinterpret_cast<MockConnection*>(session2_->connection()); + } + + void ProcessPacket(IPEndPoint addr, + QuicGuid guid, + const string& data) { + QuicEncryptedPacket packet(data.data(), data.length()); + dispatcher_.ProcessPacket(IPEndPoint(), addr, guid, packet); + } + + void ValidatePacket(const QuicEncryptedPacket& packet) { + EXPECT_TRUE(packet.AsStringPiece().find(data_) != StringPiece::npos); + } + + IPAddressNumber Loopback4() { + net::IPAddressNumber addr; + CHECK(net::ParseIPLiteralToNumber("127.0.0.1", &addr)); + return addr; + } + + EpollServer eps_; + QuicConfig config_; + QuicCryptoServerConfig crypto_config_; + TestDispatcher dispatcher_; + MockSession* session1_; + MockSession* session2_; + string data_; +}; + +TEST_F(QuicDispatcherTest, ProcessPackets) { + IPEndPoint addr(Loopback4(), 1); + + EXPECT_CALL(dispatcher_, CreateQuicSession(1, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, 1, addr, &session1_, &eps_))); + ProcessPacket(addr, 1, "foo"); + + EXPECT_CALL(dispatcher_, CreateQuicSession(2, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, 2, addr, &session2_, &eps_))); + ProcessPacket(addr, 2, "bar"); + + data_ = "eep"; + EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()), + ProcessUdpPacket(_, _, _)).Times(1). + WillOnce(testing::WithArgs<2>(Invoke( + this, &QuicDispatcherTest::ValidatePacket))); + ProcessPacket(addr, 1, "eep"); +} + +TEST_F(QuicDispatcherTest, Shutdown) { + IPEndPoint addr(Loopback4(), 1); + + EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, 1, addr, &session1_, &eps_))); + + ProcessPacket(addr, 1, "foo"); + + EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()), + SendConnectionClose(QUIC_PEER_GOING_AWAY)); + + dispatcher_.Shutdown(); +} + +class MockTimeWaitListManager : public QuicTimeWaitListManager { + public: + MockTimeWaitListManager(QuicPacketWriter* writer, + EpollServer* eps) + : QuicTimeWaitListManager(writer, eps) { + } + + MOCK_METHOD4(ProcessPacket, void(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet)); +}; + +TEST_F(QuicDispatcherTest, TimeWaitListManager) { + MockTimeWaitListManager* time_wait_list_manager = + new MockTimeWaitListManager(&dispatcher_, &eps_); + // dispatcher takes the ownership of time_wait_list_manager. + QuicDispatcherPeer::SetTimeWaitListManager(&dispatcher_, + time_wait_list_manager); + // Create a new session. + IPEndPoint addr(Loopback4(), 1); + QuicGuid guid = 1; + EXPECT_CALL(dispatcher_, CreateQuicSession(guid, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, guid, addr, &session1_, &eps_))); + ProcessPacket(addr, guid, "foo"); + + // Close the connection by sending public reset packet. + QuicPublicResetPacket packet; + packet.public_header.guid = guid; + packet.public_header.reset_flag = true; + packet.public_header.version_flag = false; + packet.rejected_sequence_number = 19191; + packet.nonce_proof = 132232; + scoped_ptr<QuicEncryptedPacket> encrypted( + QuicFramer::BuildPublicResetPacket(packet)); + EXPECT_CALL(*session1_, ConnectionClose(QUIC_PUBLIC_RESET, true)).Times(1) + .WillOnce(WithoutArgs(Invoke( + reinterpret_cast<MockServerConnection*>(session1_->connection()), + &MockServerConnection::UnregisterOnConnectionClose))); + EXPECT_CALL(*reinterpret_cast<MockConnection*>(session1_->connection()), + ProcessUdpPacket(_, _, _)) + .WillOnce(Invoke( + reinterpret_cast<MockConnection*>(session1_->connection()), + &MockConnection::ReallyProcessUdpPacket)); + dispatcher_.ProcessPacket(IPEndPoint(), addr, guid, *encrypted); + EXPECT_TRUE(time_wait_list_manager->IsGuidInTimeWait(guid)); + + // Dispatcher forwards subsequent packets for this guid to the time wait list + // manager. + EXPECT_CALL(*time_wait_list_manager, ProcessPacket(_, _, guid, _)).Times(1); + ProcessPacket(addr, guid, "foo"); +} + +class WriteBlockedListTest : public QuicDispatcherTest { + public: + virtual void SetUp() { + IPEndPoint addr(Loopback4(), 1); + + EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, 1, addr, &session1_, &eps_))); + ProcessPacket(addr, 1, "foo"); + + EXPECT_CALL(dispatcher_, CreateQuicSession(_, addr, _, &eps_)) + .WillOnce(testing::Return(CreateSession( + &dispatcher_, 2, addr, &session2_, &eps_))); + ProcessPacket(addr, 2, "bar"); + + blocked_list_ = dispatcher_.write_blocked_list(); + } + + virtual void TearDown() { + EXPECT_CALL(*connection1(), SendConnectionClose(QUIC_PEER_GOING_AWAY)); + EXPECT_CALL(*connection2(), SendConnectionClose(QUIC_PEER_GOING_AWAY)); + dispatcher_.Shutdown(); + } + + bool SetBlocked() { + QuicDispatcherPeer::SetWriteBlocked(&dispatcher_); + return true; + } + + protected: + QuicDispatcher::WriteBlockedList* blocked_list_; +}; + +TEST_F(WriteBlockedListTest, BasicOnCanWrite) { + // No OnCanWrite calls because no connections are blocked. + dispatcher_.OnCanWrite(); + + // Register connection 1 for events, and make sure it's nofitied. + blocked_list_->AddBlockedObject(connection1()); + EXPECT_CALL(*connection1(), OnCanWrite()); + dispatcher_.OnCanWrite(); + + // It should get only one notification. + EXPECT_CALL(*connection1(), OnCanWrite()).Times(0); + EXPECT_FALSE(dispatcher_.OnCanWrite()); +} + +TEST_F(WriteBlockedListTest, OnCanWriteOrder) { + // Make sure we handle events in order. + InSequence s; + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection2()); + EXPECT_CALL(*connection1(), OnCanWrite()); + EXPECT_CALL(*connection2(), OnCanWrite()); + dispatcher_.OnCanWrite(); + + // Check the other ordering. + blocked_list_->AddBlockedObject(connection2()); + blocked_list_->AddBlockedObject(connection1()); + EXPECT_CALL(*connection2(), OnCanWrite()); + EXPECT_CALL(*connection1(), OnCanWrite()); + dispatcher_.OnCanWrite(); +} + +TEST_F(WriteBlockedListTest, OnCanWriteRemove) { + // Add and remove one connction. + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->RemoveBlockedObject(connection1()); + EXPECT_CALL(*connection1(), OnCanWrite()).Times(0); + dispatcher_.OnCanWrite(); + + // Add and remove one connction and make sure it doesn't affect others. + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection2()); + blocked_list_->RemoveBlockedObject(connection1()); + EXPECT_CALL(*connection2(), OnCanWrite()); + dispatcher_.OnCanWrite(); + + // Add it, remove it, and add it back and make sure things are OK. + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->RemoveBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection1()); + EXPECT_CALL(*connection1(), OnCanWrite()).Times(1); + dispatcher_.OnCanWrite(); +} + +TEST_F(WriteBlockedListTest, DoubleAdd) { + // Make sure a double add does not necessitate a double remove. + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->RemoveBlockedObject(connection1()); + EXPECT_CALL(*connection1(), OnCanWrite()).Times(0); + dispatcher_.OnCanWrite(); + + // Make sure a double add does not result in two OnCanWrite calls. + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection1()); + EXPECT_CALL(*connection1(), OnCanWrite()).Times(1); + dispatcher_.OnCanWrite(); +} + +TEST_F(WriteBlockedListTest, OnCanWriteHandleBlock) { + // Finally make sure if we write block on a write call, we stop calling. + InSequence s; + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection2()); + EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce( + Invoke(this, &WriteBlockedListTest::SetBlocked)); + EXPECT_CALL(*connection2(), OnCanWrite()).Times(0); + dispatcher_.OnCanWrite(); + + // And we'll resume where we left off when we get another call. + EXPECT_CALL(*connection2(), OnCanWrite()); + dispatcher_.OnCanWrite(); +} + +TEST_F(WriteBlockedListTest, LimitedWrites) { + // Make sure we call both writers. The first will register for more writing + // but should not be immediately called due to limits. + InSequence s; + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection2()); + EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce(Return(true)); + EXPECT_CALL(*connection2(), OnCanWrite()).WillOnce(Return(false)); + dispatcher_.OnCanWrite(); + + // Now call OnCanWrite again, and connection1 should get its second chance + EXPECT_CALL(*connection1(), OnCanWrite()); + dispatcher_.OnCanWrite(); +} + +TEST_F(WriteBlockedListTest, TestWriteLimits) { + // Finally make sure if we write block on a write call, we stop calling. + InSequence s; + blocked_list_->AddBlockedObject(connection1()); + blocked_list_->AddBlockedObject(connection2()); + EXPECT_CALL(*connection1(), OnCanWrite()).WillOnce( + Invoke(this, &WriteBlockedListTest::SetBlocked)); + EXPECT_CALL(*connection2(), OnCanWrite()).Times(0); + dispatcher_.OnCanWrite(); + + // And we'll resume where we left off when we get another call. + EXPECT_CALL(*connection2(), OnCanWrite()); + dispatcher_.OnCanWrite(); +} + + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_epoll_clock.cc b/chromium/net/tools/quic/quic_epoll_clock.cc new file mode 100644 index 00000000000..2a3abf1f1f5 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_clock.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_epoll_clock.h" + +#include "net/tools/flip_server/epoll_server.h" + +namespace net { +namespace tools { + +QuicEpollClock::QuicEpollClock(EpollServer* epoll_server) + : epoll_server_(epoll_server) { +} + +QuicEpollClock::~QuicEpollClock() {} + +QuicTime QuicEpollClock::ApproximateNow() const { + return QuicTime::Zero().Add( + QuicTime::Delta::FromMicroseconds(epoll_server_->ApproximateNowInUsec())); +} + +QuicTime QuicEpollClock::Now() const { + return QuicTime::Zero().Add( + QuicTime::Delta::FromMicroseconds(epoll_server_->NowInUsec())); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_epoll_clock.h b/chromium/net/tools/quic/quic_epoll_clock.h new file mode 100644 index 00000000000..fb21354a9e0 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_clock.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_ +#define NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_ + +#include "base/compiler_specific.h" +#include "net/quic/quic_clock.h" +#include "net/quic/quic_time.h" + +namespace net { + +class EpollServer; + +namespace tools { + +// Clock to efficiently retrieve an approximately accurate time from an +// EpollServer. +class QuicEpollClock : public QuicClock { + public: + explicit QuicEpollClock(EpollServer* epoll_server); + virtual ~QuicEpollClock(); + + // Returns the approximate current time as a QuicTime object. + virtual QuicTime ApproximateNow() const OVERRIDE; + + // Returns the current time as a QuicTime object. + // Note: this use significant resources please use only if needed. + virtual QuicTime Now() const OVERRIDE; + + protected: + EpollServer* epoll_server_; +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_EPOLL_CLOCK_H_ diff --git a/chromium/net/tools/quic/quic_epoll_clock_test.cc b/chromium/net/tools/quic/quic_epoll_clock_test.cc new file mode 100644 index 00000000000..c774d0adba2 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_clock_test.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_epoll_clock.h" + +#include "net/tools/quic/test_tools/mock_epoll_server.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace net { +namespace tools { +namespace test { + +TEST(QuicEpollClockTest, ApproximateNowInUsec) { + MockEpollServer epoll_server; + QuicEpollClock clock(&epoll_server); + + epoll_server.set_now_in_usec(1000000); + EXPECT_EQ(1000000, + clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds()); + + epoll_server.AdvanceBy(5); + EXPECT_EQ(1000005, + clock.ApproximateNow().Subtract(QuicTime::Zero()).ToMicroseconds()); +} + +TEST(QuicEpollClockTest, NowInUsec) { + MockEpollServer epoll_server; + QuicEpollClock clock(&epoll_server); + + epoll_server.set_now_in_usec(1000000); + EXPECT_EQ(1000000, + clock.Now().Subtract(QuicTime::Zero()).ToMicroseconds()); + + epoll_server.AdvanceBy(5); + EXPECT_EQ(1000005, + clock.Now().Subtract(QuicTime::Zero()).ToMicroseconds()); +} + +TEST(QuicClockTest, WallNow) { + MockEpollServer epoll_server; + QuicEpollClock clock(&epoll_server); + + base::Time start = base::Time::Now(); + QuicWallTime now = clock.WallNow(); + base::Time end = base::Time::Now(); + + // If end > start, then we can check now is between start and end. + if (end > start) { + EXPECT_LE(static_cast<uint64>(start.ToTimeT()), now.ToUNIXSeconds()); + EXPECT_LE(now.ToUNIXSeconds(), static_cast<uint64>(end.ToTimeT())); + } +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper.cc b/chromium/net/tools/quic/quic_epoll_connection_helper.cc new file mode 100644 index 00000000000..4db1d4cd426 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_connection_helper.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_epoll_connection_helper.h" + +#include <errno.h> +#include <sys/socket.h> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/quic_random.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_socket_utils.h" + +namespace net { +namespace tools { + +namespace { + +class QuicEpollAlarm : public QuicAlarm { + public: + QuicEpollAlarm(EpollServer* epoll_server, + QuicAlarm::Delegate* delegate) + : QuicAlarm(delegate), + epoll_server_(epoll_server), + epoll_alarm_impl_(this) {} + + protected: + virtual void SetImpl() OVERRIDE { + DCHECK(deadline().IsInitialized()); + epoll_server_->RegisterAlarm( + deadline().Subtract(QuicTime::Zero()).ToMicroseconds(), + &epoll_alarm_impl_); + } + + virtual void CancelImpl() OVERRIDE { + DCHECK(!deadline().IsInitialized()); + epoll_alarm_impl_.UnregisterIfRegistered(); + } + + private: + class EpollAlarmImpl : public EpollAlarm { + public: + explicit EpollAlarmImpl(QuicEpollAlarm* alarm) : alarm_(alarm) {} + + virtual int64 OnAlarm() OVERRIDE { + EpollAlarm::OnAlarm(); + alarm_->Fire(); + // Fire will take care of registering the alarm, if needed. + return 0; + } + + private: + QuicEpollAlarm* alarm_; + }; + + EpollServer* epoll_server_; + EpollAlarmImpl epoll_alarm_impl_; +}; + +} // namespace + +QuicEpollConnectionHelper::QuicEpollConnectionHelper( + int fd, EpollServer* epoll_server) + : writer_(NULL), + epoll_server_(epoll_server), + fd_(fd), + connection_(NULL), + clock_(epoll_server), + random_generator_(QuicRandom::GetInstance()) { +} + +QuicEpollConnectionHelper::QuicEpollConnectionHelper(QuicPacketWriter* writer, + EpollServer* epoll_server) + : writer_(writer), + epoll_server_(epoll_server), + fd_(-1), + connection_(NULL), + clock_(epoll_server), + random_generator_(QuicRandom::GetInstance()) { +} + +QuicEpollConnectionHelper::~QuicEpollConnectionHelper() { +} + +void QuicEpollConnectionHelper::SetConnection(QuicConnection* connection) { + DCHECK(!connection_); + connection_ = connection; +} + +const QuicClock* QuicEpollConnectionHelper::GetClock() const { + return &clock_; +} + +QuicRandom* QuicEpollConnectionHelper::GetRandomGenerator() { + return random_generator_; +} + +int QuicEpollConnectionHelper::WritePacketToWire( + const QuicEncryptedPacket& packet, + int* error) { + if (connection_->ShouldSimulateLostPacket()) { + DLOG(INFO) << "Dropping packet due to fake packet loss."; + *error = 0; + return packet.length(); + } + + // If we have a writer, delgate the write to it. + if (writer_) { + return writer_->WritePacket(packet.data(), packet.length(), + connection_->self_address().address(), + connection_->peer_address(), + connection_, + error); + } else { + return QuicSocketUtils::WritePacket( + fd_, packet.data(), packet.length(), + connection_->self_address().address(), + connection_->peer_address(), + error); + } +} + +bool QuicEpollConnectionHelper::IsWriteBlockedDataBuffered() { + return false; +} + +bool QuicEpollConnectionHelper::IsWriteBlocked(int error) { + return error == EAGAIN || error == EWOULDBLOCK; +} + +QuicAlarm* QuicEpollConnectionHelper::CreateAlarm( + QuicAlarm::Delegate* delegate) { + return new QuicEpollAlarm(epoll_server_, delegate); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper.h b/chromium/net/tools/quic/quic_epoll_connection_helper.h new file mode 100644 index 00000000000..5bd526f8469 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_connection_helper.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// The Google-specific helper for QuicConnection which uses +// EpollAlarm for alarms, and used an int fd_ for writing data. + +#ifndef NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_ +#define NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_ + +#include <sys/types.h> +#include <set> + +#include "net/quic/quic_connection.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_time.h" +#include "net/tools/quic/quic_epoll_clock.h" +#include "net/tools/quic/quic_packet_writer.h" + +namespace net { + +class EpollServer; +class QuicRandom; + +namespace tools { + +class AckAlarm; +class RetransmissionAlarm; +class SendAlarm; +class TimeoutAlarm; + +namespace test { +class QuicEpollConnectionHelperPeer; +} // namespace test + +class QuicEpollConnectionHelper : public QuicConnectionHelperInterface { + public: + QuicEpollConnectionHelper(int fd, EpollServer* eps); + QuicEpollConnectionHelper(QuicPacketWriter* writer, EpollServer* eps); + virtual ~QuicEpollConnectionHelper(); + + // QuicEpollConnectionHelperInterface + virtual void SetConnection(QuicConnection* connection) OVERRIDE; + virtual const QuicClock* GetClock() const OVERRIDE; + virtual QuicRandom* GetRandomGenerator() OVERRIDE; + virtual int WritePacketToWire(const QuicEncryptedPacket& packet, + int* error) OVERRIDE; + virtual bool IsWriteBlockedDataBuffered() OVERRIDE; + virtual bool IsWriteBlocked(int error) OVERRIDE; + virtual QuicAlarm* CreateAlarm(QuicAlarm::Delegate* delegate) OVERRIDE; + + EpollServer* epoll_server() { return epoll_server_; } + + private: + friend class QuicConnectionPeer; + friend class net::tools::test::QuicEpollConnectionHelperPeer; + + QuicPacketWriter* writer_; // Not owned + EpollServer* epoll_server_; // Not owned. + int fd_; + + QuicConnection* connection_; + const QuicEpollClock clock_; + QuicRandom* random_generator_; + + DISALLOW_COPY_AND_ASSIGN(QuicEpollConnectionHelper); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_EPOLL_CONNECTION_HELPER_H_ diff --git a/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc b/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc new file mode 100644 index 00000000000..0bfaee27d07 --- /dev/null +++ b/chromium/net/tools/quic/quic_epoll_connection_helper_test.cc @@ -0,0 +1,207 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_epoll_connection_helper.h" + +#include "net/quic/crypto/crypto_protocol.h" +#include "net/quic/crypto/quic_decrypter.h" +#include "net/quic/crypto/quic_encrypter.h" +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_framer.h" +#include "net/quic/test_tools/quic_connection_peer.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/quic/test_tools/mock_epoll_server.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::GetMinStreamFrameSize; +using net::test::FramerVisitorCapturingFrames; +using net::test::MockSendAlgorithm; +using net::test::QuicConnectionPeer; +using net::test::MockConnectionVisitor; +using net::tools::test::MockEpollServer; +using testing::_; +using testing::Return; + +namespace net { +namespace tools { +namespace test { +namespace { + +const char data1[] = "foo"; +const bool kFromPeer = true; + +class TestConnectionHelper : public QuicEpollConnectionHelper { + public: + TestConnectionHelper(int fd, EpollServer* eps) + : QuicEpollConnectionHelper(fd, eps) { + } + + virtual int WritePacketToWire(const QuicEncryptedPacket& packet, + int* error) OVERRIDE { + QuicFramer framer(QuicVersionMax(), QuicTime::Zero(), true); + FramerVisitorCapturingFrames visitor; + framer.set_visitor(&visitor); + EXPECT_TRUE(framer.ProcessPacket(packet)); + header_ = *visitor.header(); + *error = 0; + return packet.length(); + } + + QuicPacketHeader* header() { return &header_; } + + private: + QuicPacketHeader header_; +}; + +class TestConnection : public QuicConnection { + public: + TestConnection(QuicGuid guid, + IPEndPoint address, + TestConnectionHelper* helper) + : QuicConnection(guid, address, helper, false, QuicVersionMax()) { + } + + void SendAck() { + QuicConnectionPeer::SendAck(this); + } + + void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm) { + QuicConnectionPeer::SetSendAlgorithm(this, send_algorithm); + } + + using QuicConnection::SendOrQueuePacket; +}; + +class QuicEpollConnectionHelperTest : public ::testing::Test { + protected: + QuicEpollConnectionHelperTest() + : guid_(42), + framer_(QuicVersionMax(), QuicTime::Zero(), false), + send_algorithm_(new testing::StrictMock<MockSendAlgorithm>), + helper_(new TestConnectionHelper(0, &epoll_server_)), + connection_(guid_, IPEndPoint(), helper_), + frame1_(1, false, 0, data1) { + connection_.set_visitor(&visitor_); + connection_.SetSendAlgorithm(send_algorithm_); + epoll_server_.set_timeout_in_us(-1); + EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, _, _, _)). + WillRepeatedly(Return(QuicTime::Delta::Zero())); + } + + QuicPacket* ConstructDataPacket(QuicPacketSequenceNumber number, + QuicFecGroupNumber fec_group) { + header_.public_header.version_flag = false; + header_.public_header.reset_flag = false; + header_.fec_flag = false; + header_.entropy_flag = false; + header_.packet_sequence_number = number; + header_.is_in_fec_group = fec_group == 0 ? NOT_IN_FEC_GROUP : IN_FEC_GROUP; + header_.fec_group = fec_group; + + QuicFrames frames; + QuicFrame frame(&frame1_); + frames.push_back(frame); + return framer_.BuildUnsizedDataPacket(header_, frames).packet; + } + + QuicGuid guid_; + QuicFramer framer_; + + MockEpollServer epoll_server_; + testing::StrictMock<MockSendAlgorithm>* send_algorithm_; + TestConnectionHelper* helper_; + TestConnection connection_; + testing::StrictMock<MockConnectionVisitor> visitor_; + + QuicPacketHeader header_; + QuicStreamFrame frame1_; +}; + +TEST_F(QuicEpollConnectionHelperTest, DISABLED_TestRetransmission) { + //FLAGS_fake_packet_loss_percentage = 100; + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( + Return(QuicTime::Delta::Zero())); + const int64 kDefaultRetransmissionTimeMs = 500; + + const char buffer[] = "foo"; + const size_t packet_size = + GetPacketHeaderSize(PACKET_8BYTE_GUID, kIncludeVersion, + PACKET_6BYTE_SEQUENCE_NUMBER, NOT_IN_FEC_GROUP) + + GetMinStreamFrameSize(framer_.version()) + arraysize(buffer) - 1; + EXPECT_CALL(*send_algorithm_, + SentPacket(_, 1, packet_size, NOT_RETRANSMISSION)); + EXPECT_CALL(*send_algorithm_, AbandoningPacket(1, packet_size)); + connection_.SendStreamData(1, buffer, 0, false); + EXPECT_EQ(1u, helper_->header()->packet_sequence_number); + EXPECT_CALL(*send_algorithm_, + SentPacket(_, 2, packet_size, IS_RETRANSMISSION)); + epoll_server_.AdvanceByAndCallCallbacks(kDefaultRetransmissionTimeMs * 1000); + + EXPECT_EQ(2u, helper_->header()->packet_sequence_number); +} + +TEST_F(QuicEpollConnectionHelperTest, InitialTimeout) { + EXPECT_TRUE(connection_.connected()); + + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer)); + epoll_server_.WaitForEventsAndExecuteCallbacks(); + EXPECT_FALSE(connection_.connected()); + EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000, epoll_server_.NowInUsec()); +} + +TEST_F(QuicEpollConnectionHelperTest, TimeoutAfterSend) { + EXPECT_TRUE(connection_.connected()); + EXPECT_EQ(0, epoll_server_.NowInUsec()); + + // When we send a packet, the timeout will change to 5000 + + // kDefaultInitialTimeoutSecs. + epoll_server_.AdvanceBy(5000); + EXPECT_EQ(5000, epoll_server_.NowInUsec()); + + // Send an ack so we don't set the retransmission alarm. + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + connection_.SendAck(); + + // The original alarm will fire. We should not time out because we had a + // network event at t=5000. The alarm will reregister. + epoll_server_.WaitForEventsAndExecuteCallbacks(); + EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000, epoll_server_.NowInUsec()); + + // This time, we should time out. + EXPECT_CALL(visitor_, ConnectionClose(QUIC_CONNECTION_TIMED_OUT, !kFromPeer)); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 2, _, NOT_RETRANSMISSION)); + epoll_server_.WaitForEventsAndExecuteCallbacks(); + EXPECT_EQ(kDefaultInitialTimeoutSecs * 1000000 + 5000, + epoll_server_.NowInUsec()); + EXPECT_FALSE(connection_.connected()); +} + +TEST_F(QuicEpollConnectionHelperTest, SendSchedulerDelayThenSend) { + // Test that if we send a packet with a delay, it ends up queued. + EXPECT_CALL(*send_algorithm_, RetransmissionDelay()).WillRepeatedly( + Return(QuicTime::Delta::Zero())); + QuicPacket* packet = ConstructDataPacket(1, 0); + EXPECT_CALL( + *send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)).WillOnce( + testing::Return(QuicTime::Delta::FromMicroseconds(1))); + connection_.SendOrQueuePacket(ENCRYPTION_NONE, 1, packet, 0, + HAS_RETRANSMITTABLE_DATA); + EXPECT_CALL(*send_algorithm_, SentPacket(_, 1, _, NOT_RETRANSMISSION)); + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + + // Advance the clock to fire the alarm, and configure the scheduler + // to permit the packet to be sent. + EXPECT_CALL(*send_algorithm_, TimeUntilSend(_, NOT_RETRANSMISSION, _, _)). + WillRepeatedly(testing::Return(QuicTime::Delta::Zero())); + EXPECT_CALL(visitor_, OnCanWrite()).WillOnce(testing::Return(true)); + epoll_server_.AdvanceByAndCallCallbacks(1); + EXPECT_EQ(0u, connection_.NumQueuedPackets()); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_in_memory_cache.cc b/chromium/net/tools/quic/quic_in_memory_cache.cc new file mode 100644 index 00000000000..b840d79370d --- /dev/null +++ b/chromium/net/tools/quic/quic_in_memory_cache.cc @@ -0,0 +1,225 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_in_memory_cache.h" + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/stl_util.h" + +using base::FilePath; +using base::StringPiece; +using std::string; + +// Specifies the directory used during QuicInMemoryCache +// construction to seed the cache. Cache directory can be +// generated using `wget -p --save-headers <url> + +namespace net { +namespace tools { + +std::string FLAGS_quic_in_memory_cache_dir = "/tmp/quic-data"; + +namespace { + +// BalsaVisitor implementation (glue) which caches response bodies. +class CachingBalsaVisitor : public BalsaVisitorInterface { + public: + CachingBalsaVisitor() : done_framing_(false) {} + virtual void ProcessBodyData(const char* input, size_t size) OVERRIDE { + AppendToBody(input, size); + } + virtual void ProcessTrailers(const BalsaHeaders& trailer) { + LOG(DFATAL) << "Trailers not supported."; + } + virtual void MessageDone() OVERRIDE { + done_framing_ = true; + } + virtual void HandleHeaderError(BalsaFrame* framer) OVERRIDE { + UnhandledError(); + } + virtual void HandleHeaderWarning(BalsaFrame* framer) OVERRIDE { + UnhandledError(); + } + virtual void HandleTrailerError(BalsaFrame* framer) { UnhandledError(); } + virtual void HandleTrailerWarning(BalsaFrame* framer) { UnhandledError(); } + virtual void HandleChunkingError(BalsaFrame* framer) OVERRIDE { + UnhandledError(); + } + virtual void HandleBodyError(BalsaFrame* framer) OVERRIDE { + UnhandledError(); + } + void UnhandledError() { + LOG(DFATAL) << "Unhandled error framing HTTP."; + } + virtual void ProcessBodyInput(const char*, size_t) OVERRIDE {} + virtual void ProcessHeaderInput(const char*, size_t) OVERRIDE {} + virtual void ProcessTrailerInput(const char*, size_t) OVERRIDE {} + virtual void ProcessHeaders(const net::BalsaHeaders&) OVERRIDE {} + virtual void ProcessRequestFirstLine( + const char*, size_t, const char*, size_t, + const char*, size_t, const char*, size_t) OVERRIDE {} + virtual void ProcessResponseFirstLine( + const char*, size_t, const char*, + size_t, const char*, size_t, const char*, size_t) OVERRIDE {} + virtual void ProcessChunkLength(size_t) OVERRIDE {} + virtual void ProcessChunkExtensions(const char*, size_t) OVERRIDE {} + virtual void HeaderDone() OVERRIDE {} + + void AppendToBody(const char* input, size_t size) { + body_.append(input, size); + } + bool done_framing() const { return done_framing_; } + const string& body() const { return body_; } + + private: + bool done_framing_; + string body_; +}; + +} // namespace + +QuicInMemoryCache* QuicInMemoryCache::GetInstance() { + return Singleton<QuicInMemoryCache>::get(); +} + +const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( + const BalsaHeaders& request_headers) const { + ResponseMap::const_iterator it = responses_.find(GetKey(request_headers)); + if (it == responses_.end()) { + return NULL; + } + return it->second; +} + +void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers, + const BalsaHeaders& response_headers, + StringPiece response_body) { + LOG(INFO) << "Adding response for: " << GetKey(request_headers); + if (ContainsKey(responses_, GetKey(request_headers))) { + LOG(DFATAL) << "Response for given request already exists!"; + } + Response* new_response = new Response(); + new_response->set_headers(response_headers); + new_response->set_body(response_body); + responses_[GetKey(request_headers)] = new_response; +} + +void QuicInMemoryCache::ResetForTests() { + STLDeleteValues(&responses_); + Initialize(); +} + +QuicInMemoryCache::QuicInMemoryCache() { + Initialize(); +} + +void QuicInMemoryCache::Initialize() { + // If there's no defined cache dir, we have no initialization to do. + if (FLAGS_quic_in_memory_cache_dir.empty()) { + LOG(WARNING) << "No cache directory found. Skipping initialization."; + return; + } + LOG(INFO) << "Attempting to initialize QuicInMemoryCache from directory: " + << FLAGS_quic_in_memory_cache_dir; + + FilePath directory(FLAGS_quic_in_memory_cache_dir); + base::FileEnumerator file_list(directory, + true, + base::FileEnumerator::FILES); + + FilePath file = file_list.Next(); + while (!file.empty()) { + // Need to skip files in .svn directories + if (file.value().find("/.svn/") != std::string::npos) { + file = file_list.Next(); + continue; + } + + BalsaHeaders request_headers, response_headers; + + string file_contents; + file_util::ReadFileToString(file, &file_contents); + + // Frame HTTP. + CachingBalsaVisitor caching_visitor; + BalsaFrame framer; + framer.set_balsa_headers(&response_headers); + framer.set_balsa_visitor(&caching_visitor); + size_t processed = 0; + while (processed < file_contents.length() && + !caching_visitor.done_framing()) { + processed += framer.ProcessInput(file_contents.c_str() + processed, + file_contents.length() - processed); + } + + string response_headers_str; + response_headers.DumpToString(&response_headers_str); + if (!caching_visitor.done_framing()) { + LOG(DFATAL) << "Did not frame entire message from file: " << file.value() + << " (" << processed << " of " << file_contents.length() + << " bytes)."; + } + if (processed < file_contents.length()) { + // Didn't frame whole file. Assume remainder is body. + // This sometimes happens as a result of incompatibilities between + // BalsaFramer and wget's serialization of HTTP sans content-length. + caching_visitor.AppendToBody(file_contents.c_str() + processed, + file_contents.length() - processed); + processed += file_contents.length(); + } + + StringPiece base = file.value(); + if (response_headers.HasHeader("X-Original-Url")) { + base = response_headers.GetHeader("X-Original-Url"); + response_headers.RemoveAllOfHeader("X-Original-Url"); + // Remove the protocol so that the string is of the form host + path, + // which is parsed properly below. + if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) { + base.remove_prefix(8); + } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) { + base.remove_prefix(7); + } + } + int path_start = base.find_first_of('/'); + DCHECK_LT(0, path_start); + StringPiece host(base.substr(0, path_start)); + StringPiece path(base.substr(path_start)); + if (path[path.length() - 1] == ',') { + path.remove_suffix(1); + } + // Set up request headers. Assume method is GET and protocol is HTTP/1.1. + request_headers.SetRequestFirstlineFromStringPieces("GET", + path, + "HTTP/1.1"); + request_headers.ReplaceOrAppendHeader("host", host); + + LOG(INFO) << "Inserting 'http://" << GetKey(request_headers) + << "' into QuicInMemoryCache."; + + AddResponse(request_headers, response_headers, caching_visitor.body()); + + file = file_list.Next(); + } +} + +QuicInMemoryCache::~QuicInMemoryCache() { + STLDeleteValues(&responses_); +} + +string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const { + StringPiece uri = request_headers.request_uri(); + StringPiece host; + if (uri[0] == '/') { + host = request_headers.GetHeader("host"); + } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) { + uri.remove_prefix(8); + } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) { + uri.remove_prefix(7); + } + return host.as_string() + uri.as_string(); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_in_memory_cache.h b/chromium/net/tools/quic/quic_in_memory_cache.h new file mode 100644 index 00000000000..6322e2da49d --- /dev/null +++ b/chromium/net/tools/quic/quic_in_memory_cache.h @@ -0,0 +1,88 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_ +#define NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_ + +#include <string> + +#include "base/containers/hash_tables.h" +#include "base/memory/singleton.h" +#include "base/strings/string_piece.h" +#include "net/tools/flip_server/balsa_frame.h" +#include "net/tools/flip_server/balsa_headers.h" + +template <typename T> struct DefaultSingletonTraits; + +namespace net { +namespace tools { + +extern std::string FLAGS_quic_in_memory_cache_dir; + +class QuicServer; + +// In-memory cache for HTTP responses. +// Reads from disk cache generated by: +// `wget -p --save_headers <url>` +class QuicInMemoryCache { + public: + // Container for response header/body pairs. + class Response { + public: + Response() {} + ~Response() {} + + const BalsaHeaders& headers() const { return headers_; } + const base::StringPiece body() const { return base::StringPiece(body_); } + + private: + friend class QuicInMemoryCache; + + void set_headers(const BalsaHeaders& headers) { + headers_.CopyFrom(headers); + } + void set_body(base::StringPiece body) { + body.CopyToString(&body_); + } + + BalsaHeaders headers_; + std::string body_; + + DISALLOW_COPY_AND_ASSIGN(Response); + }; + static QuicInMemoryCache* GetInstance(); + + // Retrieve a response from this cache for a given request. + // If no appropriate response exists, NULL is returned. + // Currently, responses are selected based on request URI only. + const Response* GetResponse(const BalsaHeaders& request_headers) const; + + // Add a response to the cache. + void AddResponse(const BalsaHeaders& request_headers, + const BalsaHeaders& response_headers, + base::StringPiece response_body); + + void ResetForTests(); + + private: + typedef base::hash_map<std::string, Response*> ResponseMap; + + + QuicInMemoryCache(); + friend struct DefaultSingletonTraits<QuicInMemoryCache>; + ~QuicInMemoryCache(); + + void Initialize(); + std::string GetKey(const BalsaHeaders& response_headers) const; + + // Cached responses. + ResponseMap responses_; + + DISALLOW_COPY_AND_ASSIGN(QuicInMemoryCache); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_IN_MEMORY_CACHE_H_ diff --git a/chromium/net/tools/quic/quic_in_memory_cache_test.cc b/chromium/net/tools/quic/quic_in_memory_cache_test.cc new file mode 100644 index 00000000000..065ecc3b437 --- /dev/null +++ b/chromium/net/tools/quic/quic_in_memory_cache_test.cc @@ -0,0 +1,137 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/files/file_path.h" +#include "base/memory/singleton.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "net/tools/flip_server/balsa_headers.h" +#include "net/tools/quic/quic_in_memory_cache.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::IntToString; +using base::StringPiece; + +namespace net { +namespace tools { +namespace test { + +class QuicInMemoryCacheTest : public ::testing::Test { + protected: + QuicInMemoryCacheTest() { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("net").AppendASCII("data") + .AppendASCII("quic_in_memory_cache_data"); + // The file path is known to be an ascii string. + FLAGS_quic_in_memory_cache_dir = path.MaybeAsASCII(); + } + + void CreateRequest(std::string host, + std::string path, + net::BalsaHeaders* headers) { + headers->SetRequestFirstlineFromStringPieces("GET", path, "HTTP/1.1"); + headers->ReplaceOrAppendHeader("host", host); + } + + virtual void SetUp() { + QuicInMemoryCache::GetInstance()->ResetForTests(); + } + + // This method was copied from end_to_end_test.cc in this directory. + void AddToCache(const StringPiece& method, + const StringPiece& path, + const StringPiece& version, + const StringPiece& response_code, + const StringPiece& response_detail, + const StringPiece& body) { + BalsaHeaders request_headers, response_headers; + request_headers.SetRequestFirstlineFromStringPieces(method, + path, + version); + response_headers.SetRequestFirstlineFromStringPieces(version, + response_code, + response_detail); + response_headers.AppendHeader("content-length", + base::IntToString(body.length())); + + // Check if response already exists and matches. + QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance(); + const QuicInMemoryCache::Response* cached_response = + cache->GetResponse(request_headers); + if (cached_response != NULL) { + std::string cached_response_headers_str, response_headers_str; + cached_response->headers().DumpToString(&cached_response_headers_str); + response_headers.DumpToString(&response_headers_str); + CHECK_EQ(cached_response_headers_str, response_headers_str); + CHECK_EQ(cached_response->body(), body); + return; + } + cache->AddResponse(request_headers, response_headers, body); + } +}; + +TEST_F(QuicInMemoryCacheTest, AddResponseGetResponse) { + std::string response_body("hello response"); + AddToCache("GET", "https://www.google.com/bar", + "HTTP/1.1", "200", "OK", response_body); + net::BalsaHeaders request_headers; + CreateRequest("www.google.com", "/bar", &request_headers); + QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance(); + const QuicInMemoryCache::Response* response = + cache->GetResponse(request_headers); + ASSERT_TRUE(response); + EXPECT_EQ("200", response->headers().response_code()); + EXPECT_EQ(response_body.size(), response->body().length()); + + CreateRequest("", "https://www.google.com/bar", &request_headers); + response = cache->GetResponse(request_headers); + ASSERT_TRUE(response); + EXPECT_EQ("200", response->headers().response_code()); + EXPECT_EQ(response_body.size(), response->body().length()); +} + +TEST_F(QuicInMemoryCacheTest, ReadsCacheDir) { + net::BalsaHeaders request_headers; + CreateRequest("quic.test.url", "/index.html", &request_headers); + + const QuicInMemoryCache::Response* response = + QuicInMemoryCache::GetInstance()->GetResponse(request_headers); + ASSERT_TRUE(response); + std::string value; + response->headers().GetAllOfHeaderAsString("Connection", &value); + EXPECT_EQ("200", response->headers().response_code()); + EXPECT_EQ("Keep-Alive", value); + EXPECT_LT(0U, response->body().length()); +} + +TEST_F(QuicInMemoryCacheTest, ReadsCacheDirHttp) { + net::BalsaHeaders request_headers; + CreateRequest("", "http://quic.test.url/index.html", &request_headers); + + const QuicInMemoryCache::Response* response = + QuicInMemoryCache::GetInstance()->GetResponse(request_headers); + ASSERT_TRUE(response); + std::string value; + response->headers().GetAllOfHeaderAsString("Connection", &value); + EXPECT_EQ("200", response->headers().response_code()); + EXPECT_EQ("Keep-Alive", value); + EXPECT_LT(0U, response->body().length()); +} + +TEST_F(QuicInMemoryCacheTest, GetResponseNoMatch) { + net::BalsaHeaders request_headers; + CreateRequest("www.google.com", "/index.html", &request_headers); + + const QuicInMemoryCache::Response* response = + QuicInMemoryCache::GetInstance()->GetResponse(request_headers); + ASSERT_FALSE(response); +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_packet_writer.h b/chromium/net/tools/quic/quic_packet_writer.h new file mode 100644 index 00000000000..b10e852d299 --- /dev/null +++ b/chromium/net/tools/quic/quic_packet_writer.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_ +#define NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_ + +#include "net/base/ip_endpoint.h" + +namespace net { + +class QuicBlockedWriterInterface; + +namespace tools { + +// An interface between writers and the entity managing the +// socket (in our case the QuicDispatcher). This allows the Dispatcher to +// control writes, and manage any writers who end up write blocked. +class QuicPacketWriter { + public: + virtual ~QuicPacketWriter() {} + + virtual int WritePacket(const char* buffer, size_t buf_len, + const net::IPAddressNumber& self_address, + const net::IPEndPoint& peer_address, + QuicBlockedWriterInterface* blocked_writer, + int* error) = 0; +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_PACKET_WRITER_H_ diff --git a/chromium/net/tools/quic/quic_reliable_client_stream.cc b/chromium/net/tools/quic/quic_reliable_client_stream.cc new file mode 100644 index 00000000000..359fec4dd90 --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_client_stream.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_reliable_client_stream.h" + +using std::string; + +namespace net { +namespace tools { + +// Sends body data to the server and returns the number of bytes sent. +ssize_t QuicReliableClientStream::SendBody(const string& data, bool fin) { + return WriteData(data, fin).bytes_consumed; +} + +bool QuicReliableClientStream::OnStreamFrame(const QuicStreamFrame& frame) { + if (!write_side_closed()) { + DLOG(INFO) << "Got a response before the request was complete. " + << "Aborting request."; + CloseWriteSide(); + } + return ReliableQuicStream::OnStreamFrame(frame); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_reliable_client_stream.h b/chromium/net/tools/quic/quic_reliable_client_stream.h new file mode 100644 index 00000000000..10b60c2f696 --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_client_stream.h @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_ +#define NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_ + +#include <sys/types.h> +#include <string> + +#include "base/strings/string_piece.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/reliable_quic_stream.h" +#include "net/tools/flip_server/balsa_frame.h" +#include "net/tools/flip_server/balsa_headers.h" + +namespace net { + +class QuicSession; + +namespace tools { + +class QuicClientSession; + +// A base class for spdy/http client streams which handles the concept +// of sending and receiving headers and bodies. +class QuicReliableClientStream : public ReliableQuicStream { + public: + QuicReliableClientStream(QuicStreamId id, QuicSession* session) + : ReliableQuicStream(id, session) { + } + + // Serializes the headers and body, sends it to the server, and + // returns the number of bytes sent. + virtual ssize_t SendRequest(const BalsaHeaders& headers, + base::StringPiece body, + bool fin) = 0; + // Sends body data to the server and returns the number of bytes sent. + virtual ssize_t SendBody(const std::string& data, bool fin); + + // Override the base class to close the Write side as soon as we get a + // response. + // SPDY/HTTP do not support bidirectional streaming. + virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE; + + // Returns the response data. + const std::string& data() { return data_; } + + // Returns whatever headers have been received for this stream. + const BalsaHeaders& headers() { return headers_; } + + protected: + std::string* mutable_data() { return &data_; } + BalsaHeaders* mutable_headers() { return &headers_; } + + private: + BalsaHeaders headers_; + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(QuicReliableClientStream); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_RELIABLE_CLIENT_STREAM_H_ diff --git a/chromium/net/tools/quic/quic_reliable_client_stream_test.cc b/chromium/net/tools/quic/quic_reliable_client_stream_test.cc new file mode 100644 index 00000000000..af0ffd58f81 --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_client_stream_test.cc @@ -0,0 +1,96 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_reliable_client_stream.h" + +#include "base/strings/string_number_conversions.h" +#include "net/quic/quic_utils.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_client_session.h" +#include "net/tools/quic/quic_spdy_client_stream.h" +#include "net/tools/quic/spdy_utils.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::TestWithParam; + +namespace net { +namespace tools { +namespace test { +namespace { + +class QuicClientStreamTest : public ::testing::Test { + public: + QuicClientStreamTest() + : session_("example.com", QuicConfig(), + new MockConnection(1, IPEndPoint(), 0, &eps_, false), + &crypto_config_), + body_("hello world") { + session_.config()->SetDefaults(); + crypto_config_.SetDefaults(); + + headers_.SetResponseFirstlineFromStringPieces("HTTP/1.1", "200", "Ok"); + headers_.ReplaceOrAppendHeader("content-length", "11"); + + headers_string_ = SpdyUtils::SerializeResponseHeaders(headers_); + stream_.reset(new QuicSpdyClientStream(3, &session_)); + } + + EpollServer eps_; + QuicClientSession session_; + scoped_ptr<QuicReliableClientStream> stream_; + BalsaHeaders headers_; + string headers_string_; + string body_; + QuicCryptoClientConfig crypto_config_; +}; + +TEST_F(QuicClientStreamTest, TestFraming) { + EXPECT_EQ(headers_string_.size(), stream_->ProcessData( + headers_string_.c_str(), headers_string_.size())); + EXPECT_EQ(body_.size(), + stream_->ProcessData(body_.c_str(), body_.size())); + EXPECT_EQ(200u, stream_->headers().parsed_response_code()); + EXPECT_EQ(body_, stream_->data()); +} + +TEST_F(QuicClientStreamTest, TestFramingOnePacket) { + string message = headers_string_ + body_; + + EXPECT_EQ(message.size(), stream_->ProcessData( + message.c_str(), message.size())); + EXPECT_EQ(200u, stream_->headers().parsed_response_code()); + EXPECT_EQ(body_, stream_->data()); +} + +TEST_F(QuicClientStreamTest, TestFramingExtraData) { + string large_body = "hello world!!!!!!"; + + EXPECT_EQ(headers_string_.size(), stream_->ProcessData( + headers_string_.c_str(), headers_string_.size())); + // The headers should parse successfully. + EXPECT_EQ(QUIC_STREAM_NO_ERROR, stream_->stream_error()); + EXPECT_EQ(200u, stream_->headers().parsed_response_code()); + + stream_->ProcessData(large_body.c_str(), large_body.size()); + stream_->TerminateFromPeer(true); + + EXPECT_NE(QUIC_STREAM_NO_ERROR, stream_->stream_error()); +} + +TEST_F(QuicClientStreamTest, TestNoBidirectionalStreaming) { + QuicStreamFrame frame(3, false, 3, "asd"); + + EXPECT_FALSE(stream_->write_side_closed()); + EXPECT_TRUE(stream_->OnStreamFrame(frame)); + EXPECT_TRUE(stream_->write_side_closed()); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net + diff --git a/chromium/net/tools/quic/quic_reliable_server_stream.cc b/chromium/net/tools/quic/quic_reliable_server_stream.cc new file mode 100644 index 00000000000..58b884a1082 --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_server_stream.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_reliable_server_stream.h" + +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "net/tools/quic/quic_in_memory_cache.h" + +using base::StringPiece; + +namespace net { +namespace tools { + +QuicReliableServerStream::QuicReliableServerStream(QuicStreamId id, + QuicSession* session) + : ReliableQuicStream(id, session) { +} + + +void QuicReliableServerStream::SendResponse() { + // Find response in cache. If not found, send error response. + const QuicInMemoryCache::Response* response = + QuicInMemoryCache::GetInstance()->GetResponse(headers_); + if (response == NULL) { + SendErrorResponse(); + return; + } + + DLOG(INFO) << "Sending response for stream " << id(); + SendHeaders(response->headers()); + WriteData(response->body(), true); +} + +void QuicReliableServerStream::SendErrorResponse() { + DLOG(INFO) << "Sending error response for stream " << id(); + BalsaHeaders headers; + headers.SetResponseFirstlineFromStringPieces( + "HTTP/1.1", "500", "Server Error"); + headers.ReplaceOrAppendHeader("content-length", "3"); + SendHeaders(headers); + WriteData("bad", true); +} + +QuicConsumedData QuicReliableServerStream::WriteData(StringPiece data, + bool fin) { + // We only support SPDY and HTTP, and neither handles bidirectional streaming. + if (!read_side_closed()) { + CloseReadSide(); + } + return ReliableQuicStream::WriteData(data, fin); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_reliable_server_stream.h b/chromium/net/tools/quic/quic_reliable_server_stream.h new file mode 100644 index 00000000000..2669f42649c --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_server_stream.h @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_ +#define NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_ + +#include <string> + +#include "net/quic/quic_protocol.h" +#include "net/quic/reliable_quic_stream.h" +#include "net/tools/flip_server/balsa_headers.h" + +namespace net { + +class QuicSession; + +namespace tools { + +namespace test { +class QuicReliableServerStreamPeer; +} // namespace test + +// A base class for spdy/http server streams which handles the concept +// of sending and receiving headers and bodies. +class QuicReliableServerStream : public ReliableQuicStream { + public: + QuicReliableServerStream(QuicStreamId id, QuicSession* session); + virtual ~QuicReliableServerStream() {} + + // Subclasses should process and frame data when this is called, returning + // how many bytes are processed. + virtual uint32 ProcessData(const char* data, uint32 data_len) = 0; + // Subclasses should implement this to serialize headers in a + // protocol-specific manner, and send it out to the client. + virtual void SendHeaders(const BalsaHeaders& response_headers) = 0; + + // Sends a basic 200 response using SendHeaders for the headers and WriteData + // for the body. + void SendResponse(); + // Sends a basic 500 response using SendHeaders for the headers and WriteData + // for the body + void SendErrorResponse(); + // Make sure that as soon as we start writing data, we stop reading. + virtual QuicConsumedData WriteData(base::StringPiece data, bool fin) OVERRIDE; + + // Returns whatever headers have been received for this stream. + const BalsaHeaders& headers() { return headers_; } + + const string& body() { return body_; } + protected: + BalsaHeaders* mutable_headers() { return &headers_; } + string* mutable_body() { return &body_; } + + private: + friend class test::QuicReliableServerStreamPeer; + + BalsaHeaders headers_; + string body_; +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_RELIABLE_SERVER_STREAM_H_ diff --git a/chromium/net/tools/quic/quic_reliable_server_stream_test.cc b/chromium/net/tools/quic/quic_reliable_server_stream_test.cc new file mode 100644 index 00000000000..b946d94cfc3 --- /dev/null +++ b/chromium/net/tools/quic/quic_reliable_server_stream_test.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_reliable_server_stream.h" + +#include "base/strings/string_number_conversions.h" +#include "net/quic/quic_spdy_compressor.h" +#include "net/quic/quic_utils.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_in_memory_cache.h" +#include "net/tools/quic/quic_spdy_server_stream.h" +#include "net/tools/quic/spdy_utils.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using net::tools::test::MockConnection; +using net::test::MockSession; +using std::string; +using testing::_; +using testing::AnyNumber; +using testing::Invoke; +using testing::InvokeArgument; +using testing::InSequence; +using testing::Return; +using testing::StrEq; +using testing::StrictMock; +using testing::WithArgs; + +namespace net { +namespace tools { +namespace test { + +class QuicReliableServerStreamPeer { + public: + static BalsaHeaders* GetMutableHeaders( + QuicReliableServerStream* stream) { + return &(stream->headers_); + } +}; + +namespace { + +class QuicReliableServerStreamTest : public ::testing::Test { + public: + QuicReliableServerStreamTest() + : session_(new MockConnection(1, IPEndPoint(), 0, &eps_, true), true), + body_("hello world") { + BalsaHeaders request_headers; + request_headers.SetRequestFirstlineFromStringPieces( + "POST", "https://www.google.com/", "HTTP/1.1"); + request_headers.ReplaceOrAppendHeader("content-length", "11"); + + headers_string_ = SpdyUtils::SerializeRequestHeaders(request_headers); + stream_.reset(new QuicSpdyServerStream(3, &session_)); + } + + QuicConsumedData ValidateHeaders(StringPiece headers) { + headers_string_ = SpdyUtils::SerializeResponseHeaders( + response_headers_); + QuicSpdyDecompressor decompressor; + TestDecompressorVisitor visitor; + + // First the header id, then the compressed data. + EXPECT_EQ(1, headers[0]); + EXPECT_EQ(0, headers[1]); + EXPECT_EQ(0, headers[2]); + EXPECT_EQ(0, headers[3]); + EXPECT_EQ(static_cast<size_t>(headers.length() - 4), + decompressor.DecompressData(headers.substr(4), &visitor)); + + EXPECT_EQ(headers_string_, visitor.data()); + + return QuicConsumedData(headers.size(), false); + } + + static void SetUpTestCase() { + QuicInMemoryCache::GetInstance()->ResetForTests(); + } + + virtual void SetUp() { + QuicInMemoryCache* cache = QuicInMemoryCache::GetInstance(); + + BalsaHeaders request_headers, response_headers; + StringPiece body("Yum"); + request_headers.SetRequestFirstlineFromStringPieces( + "GET", + "https://www.google.com/foo", + "HTTP/1.1"); + response_headers.SetRequestFirstlineFromStringPieces("HTTP/1.1", + "200", + "OK"); + response_headers.AppendHeader("content-length", + base::IntToString(body.length())); + + // Check if response already exists and matches. + const QuicInMemoryCache::Response* cached_response = + cache->GetResponse(request_headers); + if (cached_response != NULL) { + string cached_response_headers_str, response_headers_str; + cached_response->headers().DumpToString(&cached_response_headers_str); + response_headers.DumpToString(&response_headers_str); + CHECK_EQ(cached_response_headers_str, response_headers_str); + CHECK_EQ(cached_response->body(), body); + return; + } + + cache->AddResponse(request_headers, response_headers, body); + } + + BalsaHeaders response_headers_; + EpollServer eps_; + StrictMock<MockSession> session_; + scoped_ptr<QuicReliableServerStream> stream_; + string headers_string_; + string body_; +}; + +QuicConsumedData ConsumeAllData(QuicStreamId id, StringPiece data, + QuicStreamOffset offset, bool fin) { + return QuicConsumedData(data.size(), fin); +} + +TEST_F(QuicReliableServerStreamTest, TestFraming) { + EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()). + WillRepeatedly(Invoke(ConsumeAllData)); + + EXPECT_EQ(headers_string_.size(), stream_->ProcessData( + headers_string_.c_str(), headers_string_.size())); + EXPECT_EQ(body_.size(), stream_->ProcessData(body_.c_str(), body_.size())); + EXPECT_EQ(11u, stream_->headers().content_length()); + EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri()); + EXPECT_EQ("POST", stream_->headers().request_method()); + EXPECT_EQ(body_, stream_->body()); +} + +TEST_F(QuicReliableServerStreamTest, TestFramingOnePacket) { + EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(AnyNumber()). + WillRepeatedly(Invoke(ConsumeAllData)); + + string message = headers_string_ + body_; + + EXPECT_EQ(message.size(), stream_->ProcessData( + message.c_str(), message.size())); + EXPECT_EQ(11u, stream_->headers().content_length()); + EXPECT_EQ("https://www.google.com/", + stream_->headers().request_uri()); + EXPECT_EQ("POST", stream_->headers().request_method()); + EXPECT_EQ(body_, stream_->body()); +} + +TEST_F(QuicReliableServerStreamTest, TestFramingExtraData) { + string large_body = "hello world!!!!!!"; + + // We'll automatically write out an error (headers + body) + EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(2). + WillRepeatedly(Invoke(ConsumeAllData)); + + EXPECT_EQ(headers_string_.size(), stream_->ProcessData( + headers_string_.c_str(), headers_string_.size())); + // Content length is still 11. This will register as an error and we won't + // accept the bytes. + stream_->ProcessData(large_body.c_str(), large_body.size()); + stream_->TerminateFromPeer(true); + EXPECT_EQ(11u, stream_->headers().content_length()); + EXPECT_EQ("https://www.google.com/", stream_->headers().request_uri()); + EXPECT_EQ("POST", stream_->headers().request_method()); +} + +TEST_F(QuicReliableServerStreamTest, TestSendResponse) { + BalsaHeaders* request_headers = + QuicReliableServerStreamPeer::GetMutableHeaders(stream_.get()); + request_headers->SetRequestFirstlineFromStringPieces( + "GET", + "https://www.google.com/foo", + "HTTP/1.1"); + + response_headers_.SetResponseFirstlineFromStringPieces( + "HTTP/1.1", "200", "OK"); + response_headers_.ReplaceOrAppendHeader("content-length", "3"); + + InSequence s; + EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1) + .WillOnce(WithArgs<1>(Invoke( + this, &QuicReliableServerStreamTest::ValidateHeaders))); + StringPiece kBody = "Yum"; + EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1). + WillOnce(Return(QuicConsumedData(3, true))); + + stream_->SendResponse(); + EXPECT_TRUE(stream_->read_side_closed()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +TEST_F(QuicReliableServerStreamTest, TestSendErrorResponse) { + response_headers_.SetResponseFirstlineFromStringPieces( + "HTTP/1.1", "500", "Server Error"); + response_headers_.ReplaceOrAppendHeader("content-length", "3"); + + InSequence s; + EXPECT_CALL(session_, WriteData(_, _, _, _)).Times(1) + .WillOnce(WithArgs<1>(Invoke( + this, &QuicReliableServerStreamTest::ValidateHeaders))); + StringPiece kBody = "bad"; + EXPECT_CALL(session_, WriteData(_, kBody, _, _)).Times(1). + WillOnce(Return(QuicConsumedData(3, true))); + + stream_->SendErrorResponse(); + EXPECT_TRUE(stream_->read_side_closed()); + EXPECT_TRUE(stream_->write_side_closed()); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_server.cc b/chromium/net/tools/quic/quic_server.cc new file mode 100644 index 00000000000..d18525f634f --- /dev/null +++ b/chromium/net/tools/quic/quic_server.cc @@ -0,0 +1,223 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_server.h" + +#include <errno.h> +#include <features.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/socket.h> + +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_handshake.h" +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_clock.h" +#include "net/quic/quic_crypto_stream.h" +#include "net/quic/quic_data_reader.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/quic/quic_in_memory_cache.h" +#include "net/tools/quic/quic_socket_utils.h" + +#define MMSG_MORE 0 + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +const int kEpollFlags = EPOLLIN | EPOLLOUT | EPOLLET; +const int kNumPacketsPerReadCall = 5; // Arbitrary +static const char kSourceAddressTokenSecret[] = "secret"; + +namespace net { +namespace tools { + +QuicServer::QuicServer() + : port_(0), + packets_dropped_(0), + overflow_supported_(false), + use_recvmmsg_(false), + crypto_config_(kSourceAddressTokenSecret, QuicRandom::GetInstance()) { + // Use hardcoded crypto parameters for now. + config_.SetDefaults(); + Initialize(); +} + +QuicServer::QuicServer(const QuicConfig& config) + : port_(0), + packets_dropped_(0), + overflow_supported_(false), + use_recvmmsg_(false), + config_(config), + crypto_config_(kSourceAddressTokenSecret, QuicRandom::GetInstance()) { + Initialize(); +} + +void QuicServer::Initialize() { +#if MMSG_MORE + use_recvmmsg_ = true; +#endif + epoll_server_.set_timeout_in_us(50 * 1000); + // Initialize the in memory cache now. + QuicInMemoryCache::GetInstance(); + + QuicEpollClock clock(&epoll_server_); + + scoped_ptr<CryptoHandshakeMessage> scfg( + crypto_config_.AddDefaultConfig( + QuicRandom::GetInstance(), &clock, + QuicCryptoServerConfig::ConfigOptions())); +} + +QuicServer::~QuicServer() { +} + +bool QuicServer::Listen(const IPEndPoint& address) { + port_ = address.port(); + int address_family = address.GetSockAddrFamily(); + fd_ = socket(address_family, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); + if (fd_ < 0) { + LOG(ERROR) << "CreateSocket() failed: " << strerror(errno); + return false; + } + + int rc = QuicSocketUtils::SetGetAddressInfo(fd_, address_family); + + if (rc < 0) { + LOG(ERROR) << "IP detection not supported" << strerror(errno); + return false; + } + + int get_overflow = 1; + rc = setsockopt( + fd_, SOL_SOCKET, SO_RXQ_OVFL, &get_overflow, sizeof(get_overflow)); + + if (rc < 0) { + DLOG(WARNING) << "Socket overflow detection not supported"; + } else { + overflow_supported_ = true; + } + + // Enable the socket option that allows the local address to be + // returned if the socket is bound to more than on address. + int get_local_ip = 1; + rc = setsockopt(fd_, IPPROTO_IP, IP_PKTINFO, + &get_local_ip, sizeof(get_local_ip)); + if (rc == 0 && address_family == AF_INET6) { + rc = setsockopt(fd_, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &get_local_ip, sizeof(get_local_ip)); + } + if (rc != 0) { + LOG(ERROR) << "Failed to set required socket options"; + return false; + } + + sockaddr_storage raw_addr; + socklen_t raw_addr_len = sizeof(raw_addr); + CHECK(address.ToSockAddr(reinterpret_cast<sockaddr*>(&raw_addr), + &raw_addr_len)); + rc = bind(fd_, + reinterpret_cast<const sockaddr*>(&raw_addr), + sizeof(raw_addr)); + if (rc < 0) { + LOG(ERROR) << "Bind failed: " << strerror(errno); + return false; + } + + LOG(INFO) << "Listening on " << address.ToString(); + if (port_ == 0) { + SockaddrStorage storage; + IPEndPoint server_address; + if (getsockname(fd_, storage.addr, &storage.addr_len) != 0 || + !server_address.FromSockAddr(storage.addr, storage.addr_len)) { + LOG(ERROR) << "Unable to get self address. Error: " << strerror(errno); + return false; + } + port_ = server_address.port(); + LOG(INFO) << "Kernel assigned port is " << port_; + } + + epoll_server_.RegisterFD(fd_, this, kEpollFlags); + dispatcher_.reset(new QuicDispatcher(config_, crypto_config_, fd_, + &epoll_server_)); + + return true; +} + +void QuicServer::WaitForEvents() { + epoll_server_.WaitForEventsAndExecuteCallbacks(); +} + +void QuicServer::Shutdown() { + // Before we shut down the epoll server, give all active sessions a chance to + // notify clients that they're closing. + dispatcher_->Shutdown(); +} + +void QuicServer::OnEvent(int fd, EpollEvent* event) { + DCHECK_EQ(fd, fd_); + event->out_ready_mask = 0; + + if (event->in_events & EPOLLIN) { + LOG(ERROR) << "EPOLLIN"; + bool read = true; + while (read) { + read = ReadAndDispatchSinglePacket( + fd_, port_, dispatcher_.get(), + overflow_supported_ ? &packets_dropped_ : NULL); + } + } + if (event->in_events & EPOLLOUT) { + bool can_write_more = dispatcher_->OnCanWrite(); + if (can_write_more) { + event->out_ready_mask |= EPOLLOUT; + } + } + if (event->in_events & EPOLLERR) { + } +} + +/* static */ +void QuicServer::MaybeDispatchPacket(QuicDispatcher* dispatcher, + const QuicEncryptedPacket& packet, + const IPEndPoint& server_address, + const IPEndPoint& client_address) { + QuicGuid guid; + if (!QuicFramer::ReadGuidFromPacket(packet, &guid)) { + return; + } + + dispatcher->ProcessPacket(server_address, client_address, guid, packet); +} + +bool QuicServer::ReadAndDispatchSinglePacket(int fd, + int port, + QuicDispatcher* dispatcher, + int* packets_dropped) { + // Allocate some extra space so we can send an error if the client goes over + // the limit. + char buf[2 * kMaxPacketSize]; + + IPEndPoint client_address; + IPAddressNumber server_ip; + int bytes_read = + QuicSocketUtils::ReadPacket(fd, buf, arraysize(buf), + packets_dropped, + &server_ip, &client_address); + + if (bytes_read < 0) { + return false; // We failed to read. + } + + QuicEncryptedPacket packet(buf, bytes_read, false); + + IPEndPoint server_address(server_ip, port); + MaybeDispatchPacket(dispatcher, packet, server_address, client_address); + + return true; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_server.h b/chromium/net/tools/quic/quic_server.h new file mode 100644 index 00000000000..142d1d1572c --- /dev/null +++ b/chromium/net/tools/quic/quic_server.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A toy server, which listens on a specified address for QUIC traffic and +// handles incoming responses. + +#ifndef NET_TOOLS_QUIC_QUIC_SERVER_H_ +#define NET_TOOLS_QUIC_QUIC_SERVER_H_ + +#include "base/memory/scoped_ptr.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_server_config.h" +#include "net/quic/quic_config.h" +#include "net/quic/quic_framer.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_dispatcher.h" + +namespace net { + +class QuicCryptoServerConfig; + +namespace tools { + +class QuicDispatcher; + +class QuicServer : public EpollCallbackInterface { + public: + QuicServer(); + explicit QuicServer(const QuicConfig& config); + + virtual ~QuicServer(); + + // Start listening on the specified address. + bool Listen(const IPEndPoint& address); + + // Wait up to 50ms, and handle any events which occur. + void WaitForEvents(); + + // Server deletion is imminent. Start cleaning up the epoll server. + void Shutdown(); + + // From EpollCallbackInterface + virtual void OnRegistration( + EpollServer* eps, int fd, int event_mask) OVERRIDE {} + virtual void OnModification(int fd, int event_mask) OVERRIDE {} + virtual void OnEvent(int fd, EpollEvent* event) OVERRIDE; + virtual void OnUnregistration(int fd, bool replaced) OVERRIDE {} + + // Reads a packet from the given fd, and then passes it off to + // the QuicDispatcher. Returns true if a packet is read, false + // otherwise. + // If packets_dropped is non-null, the socket is configured to track + // dropped packets, and some packets are read, it will be set to the number of + // dropped packets. + static bool ReadAndDispatchSinglePacket(int fd, int port, + QuicDispatcher* dispatcher, + int* packets_dropped); + + virtual void OnShutdown(EpollServer* eps, int fd) OVERRIDE {} + + // Dispatches the given packet only if it looks like a valid QUIC packet. + // TODO(rjshade): Return a status describing why a packet was dropped, and log + // somehow. Maybe expose as a varz. + static void MaybeDispatchPacket(QuicDispatcher* dispatcher, + const QuicEncryptedPacket& packet, + const IPEndPoint& server_address, + const IPEndPoint& client_address); + + bool overflow_supported() { return overflow_supported_; } + + int packets_dropped() { return packets_dropped_; } + + int port() { return port_; } + + private: + // Initialize the internal state of the server. + void Initialize(); + + // Accepts data from the framer and demuxes clients to sessions. + scoped_ptr<QuicDispatcher> dispatcher_; + // Frames incoming packets and hands them to the dispatcher. + EpollServer epoll_server_; + + // The port the server is listening on. + int port_; + + // Listening connection. Also used for outbound client communication. + int fd_; + + // If overflow_supported_ is true this will be the number of packets dropped + // during the lifetime of the server. This may overflow if enough packets + // are dropped. + int packets_dropped_; + + // True if the kernel supports SO_RXQ_OVFL, the number of packets dropped + // because the socket would otherwise overflow. + bool overflow_supported_; + + // If true, use recvmmsg for reading. + bool use_recvmmsg_; + + // config_ contains non-crypto parameters that are negotiated in the crypto + // handshake. + QuicConfig config_; + // crypto_config_ contains crypto parameters for the handshake. + QuicCryptoServerConfig crypto_config_; + + DISALLOW_COPY_AND_ASSIGN(QuicServer); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_SERVER_H_ diff --git a/chromium/net/tools/quic/quic_server_bin.cc b/chromium/net/tools/quic/quic_server_bin.cc new file mode 100644 index 00000000000..cccf57819f5 --- /dev/null +++ b/chromium/net/tools/quic/quic_server_bin.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A binary wrapper for QuicServer. It listens forever on --port +// (default 6121) until it's killed or ctrl-cd to death. + +#include "base/at_exit.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "net/base/ip_endpoint.h" +#include "net/tools/quic/quic_in_memory_cache.h" +#include "net/tools/quic/quic_server.h" + +// The port the quic server will listen on. + +int32 FLAGS_port = 6121; + +int main(int argc, char *argv[]) { + CommandLine::Init(argc, argv); + CommandLine* line = CommandLine::ForCurrentProcess(); + if (line->HasSwitch("quic_in_memory_cache_dir")) { + net::tools::FLAGS_quic_in_memory_cache_dir = + line->GetSwitchValueASCII("quic_in_memory_cache_dir"); + } + + if (line->HasSwitch("port")) { + int port; + if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) { + FLAGS_port = port; + } + } + + base::AtExitManager exit_manager; + + net::IPAddressNumber ip; + CHECK(net::ParseIPLiteralToNumber("::", &ip)); + + net::tools::QuicServer server; + + if (!server.Listen(net::IPEndPoint(ip, FLAGS_port))) { + return 1; + } + + while (1) { + server.WaitForEvents(); + } + + return 0; +} diff --git a/chromium/net/tools/quic/quic_server_session.cc b/chromium/net/tools/quic/quic_server_session.cc new file mode 100644 index 00000000000..7ec991c4aa9 --- /dev/null +++ b/chromium/net/tools/quic/quic_server_session.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_server_session.h" + +#include "base/logging.h" +#include "net/quic/reliable_quic_stream.h" +#include "net/tools/quic/quic_spdy_server_stream.h" + +namespace net { +namespace tools { + +QuicServerSession::QuicServerSession( + const QuicConfig& config, + QuicConnection* connection, + QuicSessionOwner* owner) + : QuicSession(connection, config, true), + owner_(owner) { +} + +QuicServerSession::~QuicServerSession() { +} + +void QuicServerSession::InitializeSession( + const QuicCryptoServerConfig& crypto_config) { + crypto_stream_.reset(CreateQuicCryptoServerStream(crypto_config)); +} + +QuicCryptoServerStream* QuicServerSession::CreateQuicCryptoServerStream( + const QuicCryptoServerConfig& crypto_config) { + return new QuicCryptoServerStream(crypto_config, this); +} + +void QuicServerSession::ConnectionClose(QuicErrorCode error, bool from_peer) { + QuicSession::ConnectionClose(error, from_peer); + owner_->OnConnectionClose(connection()->guid(), error); +} + +bool QuicServerSession::ShouldCreateIncomingReliableStream(QuicStreamId id) { + if (id % 2 == 0) { + DLOG(INFO) << "Invalid incoming even stream_id:" << id; + connection()->SendConnectionClose(QUIC_INVALID_STREAM_ID); + return false; + } + if (GetNumOpenStreams() >= get_max_open_streams()) { + DLOG(INFO) << "Failed to create a new incoming stream with id:" << id + << " Already " << GetNumOpenStreams() << " open."; + connection()->SendConnectionClose(QUIC_TOO_MANY_OPEN_STREAMS); + return false; + } + return true; +} + +ReliableQuicStream* QuicServerSession::CreateIncomingReliableStream( + QuicStreamId id) { + if (!ShouldCreateIncomingReliableStream(id)) { + return NULL; + } + + return new QuicSpdyServerStream(id, this); +} + +ReliableQuicStream* QuicServerSession::CreateOutgoingReliableStream() { + DLOG(ERROR) << "Server push not yet supported"; + return NULL; +} + +QuicCryptoServerStream* QuicServerSession::GetCryptoStream() { + return crypto_stream_.get(); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_server_session.h b/chromium/net/tools/quic/quic_server_session.h new file mode 100644 index 00000000000..604b9fc0c42 --- /dev/null +++ b/chromium/net/tools/quic/quic_server_session.h @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// A server specific QuicSession subclass. + +#ifndef NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_ +#define NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_ + +#include <set> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "net/quic/quic_crypto_server_stream.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_session.h" + +namespace net { + +class QuicConfig; +class QuicConnection; +class QuicCryptoServerConfig; +class ReliableQuicStream; + +namespace tools { + +// An interface from the session to the entity owning the session. +// This lets the session notify its owner (the Dispatcher) when the connection +// is closed. +class QuicSessionOwner { + public: + virtual ~QuicSessionOwner() {} + + virtual void OnConnectionClose(QuicGuid guid, QuicErrorCode error) = 0; +}; + +class QuicServerSession : public QuicSession { + public: + QuicServerSession(const QuicConfig& config, + QuicConnection *connection, + QuicSessionOwner* owner); + + // Override the base class to notify the owner of the connection close. + virtual void ConnectionClose(QuicErrorCode error, bool from_peer) OVERRIDE; + + virtual ~QuicServerSession(); + + virtual void InitializeSession(const QuicCryptoServerConfig& crypto_config); + + const QuicCryptoServerStream* crypto_stream() { return crypto_stream_.get(); } + + protected: + // QuicSession methods: + virtual ReliableQuicStream* CreateIncomingReliableStream( + QuicStreamId id) OVERRIDE; + virtual ReliableQuicStream* CreateOutgoingReliableStream() OVERRIDE; + virtual QuicCryptoServerStream* GetCryptoStream() OVERRIDE; + + // If we should create an incoming stream, returns true. Otherwise + // does error handling, including communicating the error to the client and + // possibly closing the connection, and returns false. + virtual bool ShouldCreateIncomingReliableStream(QuicStreamId id); + + virtual QuicCryptoServerStream* CreateQuicCryptoServerStream( + const QuicCryptoServerConfig& crypto_config); + + private: + scoped_ptr<QuicCryptoServerStream> crypto_stream_; + QuicSessionOwner* owner_; + + DISALLOW_COPY_AND_ASSIGN(QuicServerSession); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_SERVER_SESSION_H_ diff --git a/chromium/net/tools/quic/quic_server_test.cc b/chromium/net/tools/quic/quic_server_test.cc new file mode 100644 index 00000000000..7d1d66939c0 --- /dev/null +++ b/chromium/net/tools/quic/quic_server_test.cc @@ -0,0 +1,74 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_server.h" + +#include "net/quic/crypto/quic_random.h" +#include "net/quic/quic_utils.h" +#include "net/tools/quic/test_tools/mock_quic_dispatcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; + +namespace net { +namespace tools { +namespace test { + +namespace { + +class QuicServerDispatchPacketTest : public ::testing::Test { + public: + QuicServerDispatchPacketTest() + : crypto_config_("blah", QuicRandom::GetInstance()), + dispatcher_(config_, crypto_config_, 1234, &eps_) {} + + + void MaybeDispatchPacket(const QuicEncryptedPacket& packet) { + IPEndPoint client_addr, server_addr; + QuicServer::MaybeDispatchPacket(&dispatcher_, packet, + client_addr, server_addr); + } + + protected: + QuicConfig config_; + QuicCryptoServerConfig crypto_config_; + EpollServer eps_; + MockQuicDispatcher dispatcher_; +}; + +TEST_F(QuicServerDispatchPacketTest, DoNotDispatchPacketWithoutGUID) { + // Packet too short to be considered valid. + unsigned char invalid_packet[] = { 0x00 }; + QuicEncryptedPacket encrypted_invalid_packet( + QuicUtils::AsChars(invalid_packet), arraysize(invalid_packet), false); + + // We expect the invalid packet to be dropped, and ProcessPacket should never + // be called. + EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _, _)).Times(0); + MaybeDispatchPacket(encrypted_invalid_packet); +} + +TEST_F(QuicServerDispatchPacketTest, DispatchValidPacket) { + unsigned char valid_packet[] = { + // public flags (8 byte guid) + 0x3C, + // guid + 0x10, 0x32, 0x54, 0x76, + 0x98, 0xBA, 0xDC, 0xFE, + // packet sequence number + 0xBC, 0x9A, 0x78, 0x56, + 0x34, 0x12, + // private flags + 0x00 }; + QuicEncryptedPacket encrypted_valid_packet(QuicUtils::AsChars(valid_packet), + arraysize(valid_packet), false); + + EXPECT_CALL(dispatcher_, ProcessPacket(_, _, _, _)).Times(1); + MaybeDispatchPacket(encrypted_valid_packet); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_socket_utils.cc b/chromium/net/tools/quic/quic_socket_utils.cc new file mode 100644 index 00000000000..4d7d3600760 --- /dev/null +++ b/chromium/net/tools/quic/quic_socket_utils.cc @@ -0,0 +1,191 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_socket_utils.h" + +#include <errno.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <string> + +#include "base/logging.h" + +#ifndef SO_RXQ_OVFL +#define SO_RXQ_OVFL 40 +#endif + +namespace net { +namespace tools { + +// static +IPAddressNumber QuicSocketUtils::GetAddressFromMsghdr(struct msghdr *hdr) { + IPAddressNumber ret; + if (hdr->msg_controllen > 0) { + for (cmsghdr* cmsg = CMSG_FIRSTHDR(hdr); + cmsg != NULL; + cmsg = CMSG_NXTHDR(hdr, cmsg)) { + const uint8* addr_data = reinterpret_cast<const uint8*>CMSG_DATA(cmsg); + int len = 0; + if (cmsg->cmsg_type == IPV6_PKTINFO) { + len = sizeof(in6_pktinfo); + } else if (cmsg->cmsg_type == IP_PKTINFO) { + len = sizeof(in_pktinfo); + } + ret.assign(addr_data, addr_data + len); + break; + } + } + return ret; +} + +// static +bool QuicSocketUtils::GetOverflowFromMsghdr(struct msghdr *hdr, + int *dropped_packets) { + if (hdr->msg_controllen > 0) { + struct cmsghdr *cmsg; + for (cmsg = CMSG_FIRSTHDR(hdr); + cmsg != NULL; + cmsg = CMSG_NXTHDR(hdr, cmsg)) { + if (cmsg->cmsg_type == SO_RXQ_OVFL) { + *dropped_packets = *(reinterpret_cast<int*>CMSG_DATA(cmsg)); + return true; + } + } + } + return false; +} + +// static +int QuicSocketUtils::SetGetAddressInfo(int fd, int address_family) { + int get_local_ip = 1; + if (address_family == AF_INET) { + return setsockopt(fd, IPPROTO_IP, IP_PKTINFO, + &get_local_ip, sizeof(get_local_ip)); + } else { + return setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &get_local_ip, sizeof(get_local_ip)); + } +} + +// static +int QuicSocketUtils::ReadPacket(int fd, char* buffer, size_t buf_len, + int* dropped_packets, + IPAddressNumber* self_address, + IPEndPoint* peer_address) { + CHECK(peer_address != NULL); + const int kSpaceForOverflowAndIp = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo)); + char cbuf[kSpaceForOverflowAndIp]; + memset(cbuf, 0, arraysize(cbuf)); + + iovec iov = {buffer, buf_len}; + struct sockaddr_storage raw_address; + msghdr hdr; + + hdr.msg_name = &raw_address; + hdr.msg_namelen = sizeof(sockaddr_storage); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_flags = 0; + + struct cmsghdr *cmsg = (struct cmsghdr *) cbuf; + cmsg->cmsg_len = arraysize(cbuf); + hdr.msg_control = cmsg; + hdr.msg_controllen = arraysize(cbuf); + + int bytes_read = recvmsg(fd, &hdr, 0); + + // Return before setting dropped packets: if we get EAGAIN, it will + // be 0. + if (bytes_read < 0 && errno != 0) { + if (errno != EAGAIN) { + LOG(ERROR) << "Error reading " << strerror(errno); + } + return -1; + } + + if (dropped_packets != NULL) { + GetOverflowFromMsghdr(&hdr, dropped_packets); + } + if (self_address != NULL) { + *self_address = QuicSocketUtils::GetAddressFromMsghdr(&hdr); + } + + if (raw_address.ss_family == AF_INET) { + CHECK(peer_address->FromSockAddr( + reinterpret_cast<const sockaddr*>(&raw_address), + sizeof(struct sockaddr_in))); + } else if (raw_address.ss_family == AF_INET6) { + CHECK(peer_address->FromSockAddr( + reinterpret_cast<const sockaddr*>(&raw_address), + sizeof(struct sockaddr_in6))); + } + + return bytes_read; +} + +// static +int QuicSocketUtils::WritePacket(int fd, const char* buffer, size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address, + int* error) { + sockaddr_storage raw_address; + socklen_t address_len = sizeof(raw_address); + CHECK(peer_address.ToSockAddr( + reinterpret_cast<struct sockaddr*>(&raw_address), + &address_len)); + iovec iov = {const_cast<char*>(buffer), buf_len}; + + msghdr hdr; + hdr.msg_name = &raw_address; + hdr.msg_namelen = address_len; + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_flags = 0; + + const int kSpaceForIpv4 = CMSG_SPACE(sizeof(in_pktinfo)); + const int kSpaceForIpv6 = CMSG_SPACE(sizeof(in6_pktinfo)); + // kSpaceForIp should be big enough to hold both IPv4 and IPv6 packet info. + const int kSpaceForIp = + (kSpaceForIpv4 < kSpaceForIpv6) ? kSpaceForIpv6 : kSpaceForIpv4; + char cbuf[kSpaceForIp]; + if (self_address.empty()) { + hdr.msg_control = 0; + hdr.msg_controllen = 0; + } else if (GetAddressFamily(self_address) == ADDRESS_FAMILY_IPV4) { + hdr.msg_control = cbuf; + hdr.msg_controllen = kSpaceForIp; + cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); + + cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg)); + memset(pktinfo, 0, sizeof(in_pktinfo)); + pktinfo->ipi_ifindex = 0; + memcpy(&pktinfo->ipi_spec_dst, &self_address[0], self_address.size()); + hdr.msg_controllen = cmsg->cmsg_len; + } else { + hdr.msg_control = cbuf; + hdr.msg_controllen = kSpaceForIp; + cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr); + + cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg)); + memset(pktinfo, 0, sizeof(in6_pktinfo)); + memcpy(&pktinfo->ipi6_addr, &self_address[0], self_address.size()); + hdr.msg_controllen = cmsg->cmsg_len; + } + + int rc = sendmsg(fd, &hdr, 0); + *error = (rc >= 0) ? 0 : errno; + return rc; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_socket_utils.h b/chromium/net/tools/quic/quic_socket_utils.h new file mode 100644 index 00000000000..bfacc6c4719 --- /dev/null +++ b/chromium/net/tools/quic/quic_socket_utils.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Some socket related helper methods for quic. + +#ifndef NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_ +#define NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_ + +#include <stddef.h> +#include <sys/socket.h> +#include <string> + +#include "net/base/ip_endpoint.h" + +namespace net { +namespace tools { + +class QuicSocketUtils { + public: + // If the msghdr contains IP_PKTINFO or IPV6_PKTINFO, this will return the + // IPAddressNumber in that header. Returns an uninitialized IPAddress on + // failure. + static IPAddressNumber GetAddressFromMsghdr(struct msghdr *hdr); + + // If the msghdr contains an SO_RXQ_OVFL entry, this will set dropped_packets + // to the correct value and return true. Otherwise it will return false. + static bool GetOverflowFromMsghdr(struct msghdr *hdr, int *dropped_packets); + + // Sets either IP_PKTINFO or IPV6_PKTINFO on the socket, based on + // address_family. Returns the return code from setsockopt. + static int SetGetAddressInfo(int fd, int address_family); + + // Reads buf_len from the socket. If reading is successful, returns bytes + // read and sets peer_address to the peer address. Otherwise returns -1. + // + // If dropped_packets is non-null, it will be set to the number of packets + // dropped on the socket since the socket was created, assuming the kernel + // supports this feature. + // + // If self_address is non-null, it will be set to the address the peer sent + // packets to, assuming a packet was read. + static int ReadPacket(int fd, char* buffer, size_t buf_len, + int* dropped_packets, + IPAddressNumber* self_address, + IPEndPoint* peer_address); + + // Writes buf_len to the socket. If writing is successful returns the number + // of bytes written otherwise returns -1 and sets error to errno. + static int WritePacket(int fd, const char* buffer, size_t buf_len, + const IPAddressNumber& self_address, + const IPEndPoint& peer_address, + int* error); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_SOCKET_UTILS_H_ diff --git a/chromium/net/tools/quic/quic_spdy_client_stream.cc b/chromium/net/tools/quic/quic_spdy_client_stream.cc new file mode 100644 index 00000000000..62949534b3d --- /dev/null +++ b/chromium/net/tools/quic/quic_spdy_client_stream.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_spdy_client_stream.h" + +#include "net/spdy/spdy_framer.h" +#include "net/tools/quic/quic_client_session.h" +#include "net/tools/quic/spdy_utils.h" + +using base::StringPiece; +using std::string; + +namespace net { +namespace tools { + +static const size_t kHeaderBufInitialSize = 4096; + +QuicSpdyClientStream::QuicSpdyClientStream(QuicStreamId id, + QuicClientSession* session) + : QuicReliableClientStream(id, session), + read_buf_(new GrowableIOBuffer()), + response_headers_received_(false) { +} + +QuicSpdyClientStream::~QuicSpdyClientStream() { +} + +uint32 QuicSpdyClientStream::ProcessData(const char* data, uint32 length) { + uint32 total_bytes_processed = 0; + + // Are we still reading the response headers. + if (!response_headers_received_) { + // Grow the read buffer if necessary. + if (read_buf_->RemainingCapacity() < (int)length) { + read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize); + } + memcpy(read_buf_->data(), data, length); + read_buf_->set_offset(read_buf_->offset() + length); + ParseResponseHeaders(); + } else { + mutable_data()->append(data + total_bytes_processed, + length - total_bytes_processed); + } + return length; +} + +void QuicSpdyClientStream::TerminateFromPeer(bool half_close) { + ReliableQuicStream::TerminateFromPeer(half_close); + if (!response_headers_received_) { + Close(QUIC_BAD_APPLICATION_PAYLOAD); + } else if ((headers().content_length_status() == + BalsaHeadersEnums::VALID_CONTENT_LENGTH) && + mutable_data()->size() != headers().content_length()) { + Close(QUIC_BAD_APPLICATION_PAYLOAD); + } +} + +ssize_t QuicSpdyClientStream::SendRequest(const BalsaHeaders& headers, + StringPiece body, + bool fin) { + SpdyHeaderBlock header_block = + SpdyUtils::RequestHeadersToSpdyHeaders(headers); + + string headers_string = + session()->compressor()->CompressHeaders(header_block); + + bool has_body = !body.empty(); + + WriteData(headers_string, fin && !has_body); // last_data + + if (has_body) { + WriteData(body, fin); + } + + return headers_string.size() + body.size(); +} + +int QuicSpdyClientStream::ParseResponseHeaders() { + size_t read_buf_len = static_cast<size_t>(read_buf_->offset()); + SpdyFramer framer(SPDY3); + SpdyHeaderBlock headers; + char* data = read_buf_->StartOfBuffer(); + size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(), + &headers); + if (len == 0) { + return -1; + } + + if (!SpdyUtils::FillBalsaResponseHeaders(headers, mutable_headers())) { + Close(QUIC_BAD_APPLICATION_PAYLOAD); + return -1; + } + + size_t delta = read_buf_len - len; + if (delta > 0) { + mutable_data()->append(data + len, delta); + } + + return len; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_spdy_client_stream.h b/chromium/net/tools/quic/quic_spdy_client_stream.h new file mode 100644 index 00000000000..ec4d25747f7 --- /dev/null +++ b/chromium/net/tools/quic/quic_spdy_client_stream.h @@ -0,0 +1,47 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_ +#define NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_ + +#include "base/strings/string_piece.h" +#include "net/base/io_buffer.h" +#include "net/tools/quic/quic_reliable_client_stream.h" + +namespace net { + +class BalsaHeaders; + +namespace tools { + +class QuicClientSession; + +// All this does right now is send an SPDY request, and aggregate the +// SPDY response. +class QuicSpdyClientStream : public QuicReliableClientStream { + public: + QuicSpdyClientStream(QuicStreamId id, QuicClientSession* session); + virtual ~QuicSpdyClientStream(); + + // ReliableQuicStream implementation called by the session when there's + // data for us. + virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE; + + virtual void TerminateFromPeer(bool half_close) OVERRIDE; + + virtual ssize_t SendRequest(const BalsaHeaders& headers, + base::StringPiece body, + bool fin) OVERRIDE; + + private: + int ParseResponseHeaders(); + + scoped_refptr<GrowableIOBuffer> read_buf_; + bool response_headers_received_; +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_SPDY_CLIENT_STREAM_H_ diff --git a/chromium/net/tools/quic/quic_spdy_server_stream.cc b/chromium/net/tools/quic/quic_spdy_server_stream.cc new file mode 100644 index 00000000000..d6f3b7590ff --- /dev/null +++ b/chromium/net/tools/quic/quic_spdy_server_stream.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_spdy_server_stream.h" + +#include "net/quic/quic_session.h" +#include "net/spdy/spdy_framer.h" +#include "net/tools/quic/spdy_utils.h" + +using std::string; + +namespace net { +namespace tools { + +static const size_t kHeaderBufInitialSize = 4096; + +QuicSpdyServerStream::QuicSpdyServerStream(QuicStreamId id, + QuicSession* session) + : QuicReliableServerStream(id, session), + read_buf_(new GrowableIOBuffer()), + request_headers_received_(false) { +} + +QuicSpdyServerStream::~QuicSpdyServerStream() { +} + +uint32 QuicSpdyServerStream::ProcessData(const char* data, uint32 length) { + uint32 total_bytes_processed = 0; + + // Are we still reading the request headers. + if (!request_headers_received_) { + // Grow the read buffer if necessary. + if (read_buf_->RemainingCapacity() < (int)length) { + read_buf_->SetCapacity(read_buf_->capacity() + kHeaderBufInitialSize); + } + memcpy(read_buf_->data(), data, length); + read_buf_->set_offset(read_buf_->offset() + length); + ParseRequestHeaders(); + } else { + mutable_body()->append(data + total_bytes_processed, + length - total_bytes_processed); + } + return length; +} + +void QuicSpdyServerStream::TerminateFromPeer(bool half_close) { + ReliableQuicStream::TerminateFromPeer(half_close); + // This is a full close: do not send a response. + if (!half_close) { + return; + } + if (write_side_closed() || fin_buffered()) { + return; + } + + if (!request_headers_received_) { + SendErrorResponse(); // We're not done writing headers. + } else if ((headers().content_length_status() == + BalsaHeadersEnums::VALID_CONTENT_LENGTH) && + mutable_body()->size() != headers().content_length()) { + SendErrorResponse(); // Invalid content length + } else { + SendResponse(); + } +} + +void QuicSpdyServerStream::SendHeaders( + const BalsaHeaders& response_headers) { + SpdyHeaderBlock header_block = + SpdyUtils::ResponseHeadersToSpdyHeaders(response_headers); + string headers = + session()->compressor()->CompressHeaders(header_block); + + WriteData(headers, false); +} + +int QuicSpdyServerStream::ParseRequestHeaders() { + size_t read_buf_len = static_cast<size_t>(read_buf_->offset()); + SpdyFramer framer(SPDY3); + SpdyHeaderBlock headers; + char* data = read_buf_->StartOfBuffer(); + size_t len = framer.ParseHeaderBlockInBuffer(data, read_buf_->offset(), + &headers); + if (len == 0) { + return -1; + } + + if (!SpdyUtils::FillBalsaRequestHeaders(headers, mutable_headers())) { + SendErrorResponse(); + return -1; + } + + size_t delta = read_buf_len - len; + if (delta > 0) { + mutable_body()->append(data + len, delta); + } + + request_headers_received_ = true; + return len; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_spdy_server_stream.h b/chromium/net/tools/quic/quic_spdy_server_stream.h new file mode 100644 index 00000000000..8ed2a8f3d65 --- /dev/null +++ b/chromium/net/tools/quic/quic_spdy_server_stream.h @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_ +#define NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_ + +#include <string> + +#include "net/base/io_buffer.h" +#include "net/tools/quic/quic_reliable_server_stream.h" + +namespace net { + +class QuicSession; + +namespace tools { + +// All this does right now is aggregate data, and on fin, send a cached +// response. +class QuicSpdyServerStream : public QuicReliableServerStream { + public: + QuicSpdyServerStream(QuicStreamId id, QuicSession* session); + virtual ~QuicSpdyServerStream(); + + // ReliableQuicStream implementation called by the session when there's + // data for us. + virtual uint32 ProcessData(const char* data, uint32 data_len) OVERRIDE; + + virtual void SendHeaders(const BalsaHeaders& response_headers) OVERRIDE; + + int ParseRequestHeaders(); + + protected: + virtual void TerminateFromPeer(bool half_close) OVERRIDE; + + // Buffer into which response header data is read. + scoped_refptr<GrowableIOBuffer> read_buf_; + bool request_headers_received_; +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_SPDY_SERVER_STREAM_H_ diff --git a/chromium/net/tools/quic/quic_spdy_server_stream_test.cc b/chromium/net/tools/quic/quic_spdy_server_stream_test.cc new file mode 100644 index 00000000000..8168fcbd4c1 --- /dev/null +++ b/chromium/net/tools/quic/quic_spdy_server_stream_test.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_spdy_server_stream.h" + +#include "base/strings/string_piece.h" +#include "net/quic/quic_connection.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/quic/test_tools/quic_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using net::tools::test::MockConnection; +using net::test::MockSession; + +namespace net { +namespace tools { +namespace test { +namespace { + +class QuicSpdyServerStreamTest : public ::testing::Test { + public: + QuicSpdyServerStreamTest() + : connection_(new MockConnection(1, IPEndPoint(), false)), + session_(connection_, true), + stream_(1, &session_) { + } + + MockConnection* connection_; + MockSession session_; + QuicSpdyServerStream stream_; +}; + +TEST_F(QuicSpdyServerStreamTest, InvalidHeadersWithFin) { + char arr[] = { + 0x00, 0x00, 0x00, 0x05, // .... + 0x00, 0x00, 0x00, 0x05, // .... + 0x3a, 0x68, 0x6f, 0x73, // :hos + 0x74, 0x00, 0x00, 0x00, // t... + 0x00, 0x00, 0x00, 0x00, // .... + 0x07, 0x3a, 0x6d, 0x65, // .:me + 0x74, 0x68, 0x6f, 0x64, // thod + 0x00, 0x00, 0x00, 0x03, // .... + 0x47, 0x45, 0x54, 0x00, // GET. + 0x00, 0x00, 0x05, 0x3a, // ...: + 0x70, 0x61, 0x74, 0x68, // path + 0x00, 0x00, 0x00, 0x04, // .... + 0x2f, 0x66, 0x6f, 0x6f, // /foo + 0x00, 0x00, 0x00, 0x07, // .... + 0x3a, 0x73, 0x63, 0x68, // :sch + 0x65, 0x6d, 0x65, 0x00, // eme. + 0x00, 0x00, 0x00, 0x00, // .... + 0x00, 0x00, 0x08, 0x3a, // ...: + 0x76, 0x65, 0x72, 0x73, // vers + '\x96', 0x6f, 0x6e, 0x00, // <i(69)>on. + 0x00, 0x00, 0x08, 0x48, // ...H + 0x54, 0x54, 0x50, 0x2f, // TTP/ + 0x31, 0x2e, 0x31, // 1.1 + }; + QuicStreamFrame frame(1, true, 0, StringPiece(arr, arraysize(arr))); + // Verify that we don't crash when we get a invalid headers in stream frame. + stream_.OnStreamFrame(frame); +} + +} // namespace +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_time_wait_list_manager.cc b/chromium/net/tools/quic/quic_time_wait_list_manager.cc new file mode 100644 index 00000000000..a2fc4f84cb6 --- /dev/null +++ b/chromium/net/tools/quic/quic_time_wait_list_manager.cc @@ -0,0 +1,322 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/quic_time_wait_list_manager.h" + +#include <errno.h> + +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_protocol.h" +#include "net/quic/crypto/quic_decrypter.h" +#include "net/quic/crypto/quic_encrypter.h" +#include "net/quic/quic_clock.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_protocol.h" +#include "net/quic/quic_utils.h" + +using std::make_pair; + +namespace net { +namespace tools { + +namespace { + +// Time period for which the guid should live in time wait state.. +const int kTimeWaitSeconds = 5; + +} // namespace + +// A very simple alarm that just informs the QuicTimeWaitListManager to clean +// up old guids. This alarm should be unregistered and deleted before the +// QuicTimeWaitListManager is deleted. +class GuidCleanUpAlarm : public EpollAlarm { + public: + explicit GuidCleanUpAlarm(QuicTimeWaitListManager* time_wait_list_manager) + : time_wait_list_manager_(time_wait_list_manager) { + } + + virtual int64 OnAlarm() OVERRIDE { + EpollAlarm::OnAlarm(); + time_wait_list_manager_->CleanUpOldGuids(); + // Let the time wait manager register the alarm at appropriate time. + return 0; + } + + private: + // Not owned. + QuicTimeWaitListManager* time_wait_list_manager_; +}; + +struct QuicTimeWaitListManager::GuidAddTime { + GuidAddTime(QuicGuid guid, const QuicTime& time) + : guid(guid), + time_added(time) { + } + + QuicGuid guid; + QuicTime time_added; +}; + +// This class stores pending public reset packets to be sent to clients. +// server_address - server address on which a packet what was received for +// a guid in time wait state. +// client_address - address of the client that sent that packet. Needed to send +// the public reset packet back to the client. +// packet - the pending public reset packet that is to be sent to the client. +// created instance takes the ownership of this packet. +class QuicTimeWaitListManager::QueuedPacket { + public: + QueuedPacket(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicEncryptedPacket* packet) + : server_address_(server_address), + client_address_(client_address), + packet_(packet) { + } + + const IPEndPoint& server_address() const { return server_address_; } + const IPEndPoint& client_address() const { return client_address_; } + QuicEncryptedPacket* packet() { return packet_.get(); } + + private: + const IPEndPoint server_address_; + const IPEndPoint client_address_; + scoped_ptr<QuicEncryptedPacket> packet_; + + DISALLOW_COPY_AND_ASSIGN(QueuedPacket); +}; + +QuicTimeWaitListManager::QuicTimeWaitListManager( + QuicPacketWriter* writer, + EpollServer* epoll_server) + : framer_(QuicVersionMax(), + QuicTime::Zero(), // unused + true), + epoll_server_(epoll_server), + kTimeWaitPeriod_(QuicTime::Delta::FromSeconds(kTimeWaitSeconds)), + guid_clean_up_alarm_(new GuidCleanUpAlarm(this)), + clock_(epoll_server), + writer_(writer), + is_write_blocked_(false) { + framer_.set_visitor(this); + SetGuidCleanUpAlarm(); +} + +QuicTimeWaitListManager::~QuicTimeWaitListManager() { + guid_clean_up_alarm_->UnregisterIfRegistered(); + STLDeleteElements(&time_ordered_guid_list_); + STLDeleteElements(&pending_packets_queue_); +} + +void QuicTimeWaitListManager::AddGuidToTimeWait(QuicGuid guid, + QuicVersion version) { + DCHECK(!IsGuidInTimeWait(guid)); + // Initialize the guid with 0 packets received. + GuidData data(0, version); + guid_map_.insert(make_pair(guid, data)); + time_ordered_guid_list_.push_back(new GuidAddTime(guid, + clock_.ApproximateNow())); +} + +bool QuicTimeWaitListManager::IsGuidInTimeWait(QuicGuid guid) const { + return guid_map_.find(guid) != guid_map_.end(); +} + +void QuicTimeWaitListManager::ProcessPacket( + const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet) { + DCHECK(IsGuidInTimeWait(guid)); + server_address_ = server_address; + client_address_ = client_address; + + // Set the framer to the appropriate version for this GUID, before processing. + QuicVersion version = GetQuicVersionFromGuid(guid); + framer_.set_version(version); + + framer_.ProcessPacket(packet); +} + +QuicVersion QuicTimeWaitListManager::GetQuicVersionFromGuid(QuicGuid guid) { + GuidMapIterator it = guid_map_.find(guid); + DCHECK(it != guid_map_.end()); + return (it->second).version; +} + +bool QuicTimeWaitListManager::OnCanWrite() { + is_write_blocked_ = false; + while (!is_write_blocked_ && !pending_packets_queue_.empty()) { + QueuedPacket* queued_packet = pending_packets_queue_.front(); + WriteToWire(queued_packet); + if (!is_write_blocked_) { + pending_packets_queue_.pop_front(); + delete queued_packet; + } + } + + return !is_write_blocked_; +} + +void QuicTimeWaitListManager::OnError(QuicFramer* framer) { + DLOG(INFO) << QuicUtils::ErrorToString(framer->error()); +} + +bool QuicTimeWaitListManager::OnProtocolVersionMismatch( + QuicVersion received_version) { + // Drop such packets whose version don't match. + return false; +} + +bool QuicTimeWaitListManager::OnStreamFrame(const QuicStreamFrame& frame) { + return false; +} + +bool QuicTimeWaitListManager::OnAckFrame(const QuicAckFrame& frame) { + return false; +} + +bool QuicTimeWaitListManager::OnCongestionFeedbackFrame( + const QuicCongestionFeedbackFrame& frame) { + return false; +} + +bool QuicTimeWaitListManager::OnRstStreamFrame( + const QuicRstStreamFrame& frame) { + return false; +} + +bool QuicTimeWaitListManager::OnConnectionCloseFrame( + const QuicConnectionCloseFrame & frame) { + return false; +} + +bool QuicTimeWaitListManager::OnGoAwayFrame(const QuicGoAwayFrame& frame) { + return false; +} + +bool QuicTimeWaitListManager::OnPacketHeader(const QuicPacketHeader& header) { + // TODO(satyamshekhar): Think about handling packets from different client + // addresses. + GuidMapIterator it = guid_map_.find(header.public_header.guid); + DCHECK(it != guid_map_.end()); + // Increment the received packet count. + ++((it->second).num_packets); + if (ShouldSendPublicReset((it->second).num_packets)) { + // We don't need the packet anymore. Just tell the client what sequence + // number we rejected. + SendPublicReset(server_address_, + client_address_, + header.public_header.guid, + header.packet_sequence_number); + } + // Never process the body of the packet in time wait state. + return false; +} + +// Returns true if the number of packets received for this guid is a power of 2 +// to throttle the number of public reset packets we send to a client. +bool QuicTimeWaitListManager::ShouldSendPublicReset(int received_packet_count) { + return (received_packet_count & (received_packet_count - 1)) == 0; +} + +void QuicTimeWaitListManager::SendPublicReset( + const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + QuicPacketSequenceNumber rejected_sequence_number) { + QuicPublicResetPacket packet; + packet.public_header.guid = guid; + packet.public_header.reset_flag = true; + packet.public_header.version_flag = false; + packet.rejected_sequence_number = rejected_sequence_number; + // TODO(satyamshekhar): generate a valid nonce for this guid. + packet.nonce_proof = 1010101; + QueuedPacket* queued_packet = new QueuedPacket( + server_address, + client_address, + framer_.BuildPublicResetPacket(packet)); + // Takes ownership of the packet. + SendOrQueuePacket(queued_packet); +} + +// Either sends the packet and deletes it or makes pending queue the +// owner of the packet. +void QuicTimeWaitListManager::SendOrQueuePacket(QueuedPacket* packet) { + if (!is_write_blocked_) { + // TODO(satyamshekhar): Handle packets that fail due to error other than + // EAGAIN or EWOULDBLOCK. + WriteToWire(packet); + } + + if (is_write_blocked_) { + // pending_packets_queue takes the ownership of the queued packet. + pending_packets_queue_.push_back(packet); + } else { + delete packet; + } +} + +void QuicTimeWaitListManager::WriteToWire(QueuedPacket* queued_packet) { + DCHECK(!is_write_blocked_); + int error; + int rc = writer_->WritePacket(queued_packet->packet()->data(), + queued_packet->packet()->length(), + queued_packet->server_address().address(), + queued_packet->client_address(), + this, + &error); + + if (rc == -1) { + if (error == EAGAIN || error == EWOULDBLOCK) { + is_write_blocked_ = true; + } else { + LOG(WARNING) << "Received unknown error while sending reset packet to " + << queued_packet->client_address().ToString() << ": " + << strerror(error); + } + } +} + +void QuicTimeWaitListManager::SetGuidCleanUpAlarm() { + guid_clean_up_alarm_->UnregisterIfRegistered(); + int64 next_alarm_interval; + if (!time_ordered_guid_list_.empty()) { + GuidAddTime* oldest_guid = time_ordered_guid_list_.front(); + QuicTime now = clock_.ApproximateNow(); + DCHECK(now.Subtract(oldest_guid->time_added) < kTimeWaitPeriod_); + next_alarm_interval = oldest_guid->time_added + .Add(kTimeWaitPeriod_) + .Subtract(now) + .ToMicroseconds(); + } else { + // No guids added so none will expire before kTimeWaitPeriod_. + next_alarm_interval = kTimeWaitPeriod_.ToMicroseconds(); + } + + epoll_server_->RegisterAlarmApproximateDelta(next_alarm_interval, + guid_clean_up_alarm_.get()); +} + +void QuicTimeWaitListManager::CleanUpOldGuids() { + QuicTime now = clock_.ApproximateNow(); + while (time_ordered_guid_list_.size() > 0) { + DCHECK_EQ(time_ordered_guid_list_.size(), guid_map_.size()); + GuidAddTime* oldest_guid = time_ordered_guid_list_.front(); + if (now.Subtract(oldest_guid->time_added) < kTimeWaitPeriod_) { + break; + } + // This guid has lived its age, retire it now. + guid_map_.erase(oldest_guid->guid); + time_ordered_guid_list_.pop_front(); + delete oldest_guid; + } + SetGuidCleanUpAlarm(); +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/quic_time_wait_list_manager.h b/chromium/net/tools/quic/quic_time_wait_list_manager.h new file mode 100644 index 00000000000..815b9d913bf --- /dev/null +++ b/chromium/net/tools/quic/quic_time_wait_list_manager.h @@ -0,0 +1,194 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Handles packets for guids in time wait state by discarding the packet and +// sending the clients a public reset packet with exponential backoff. + +#ifndef NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_ +#define NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_ + +#include <deque> + +#include "base/containers/hash_tables.h" +#include "base/strings/string_piece.h" +#include "net/quic/quic_blocked_writer_interface.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_epoll_clock.h" +#include "net/tools/quic/quic_packet_writer.h" + +namespace net { +namespace tools { + +class GuidCleanUpAlarm; + +// Maintains a list of all guids that have been recently closed. A guid lives in +// this state for kTimeWaitPeriod. All packets received for guids in this state +// are handed over to the QuicTimeWaitListManager by the QuicDispatcher. It also +// decides whether we should send a public reset packet to the client which sent +// a packet with the guid in time wait state and sends it when appropriate. +// After the guid expires its time wait period, a new connection/session will be +// created if a packet is received for this guid. +class QuicTimeWaitListManager : public QuicBlockedWriterInterface, + public QuicFramerVisitorInterface { + public: + // writer - the entity that writes to the socket. (Owned by the dispatcher) + // epoll_server - used to run clean up alarms. (Owned by the dispatcher) + QuicTimeWaitListManager(QuicPacketWriter* writer, + EpollServer* epoll_server); + virtual ~QuicTimeWaitListManager(); + + // Adds the given guid to time wait state for kTimeWaitPeriod. Henceforth, + // any packet bearing this guid should not be processed while the guid remains + // in this list. Public reset packets are sent to the clients by the time wait + // list manager that send packets to guids in this state. DCHECKs that guid is + // not already on the list. Pass in the version as well so that if a public + // reset packet needs to be sent the framer version can be set first. + void AddGuidToTimeWait(QuicGuid guid, QuicVersion version); + + // Returns true if the guid is in time wait state, false otherwise. Packets + // received for this guid should not lead to creation of new QuicSessions. + bool IsGuidInTimeWait(QuicGuid guid) const; + + // Called when a packet is received for a guid that is in time wait state. + // Sends a public reset packet to the client which sent this guid. Sending + // of the public reset packet is throttled by using exponential back off. + // DCHECKs for the guid to be in time wait state. + // virtual to override in tests. + virtual void ProcessPacket(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet); + + // Called by the dispatcher when the underlying socket becomes writable again, + // since we might need to send pending public reset packets which we didn't + // send because the underlying socket was write blocked. + virtual bool OnCanWrite() OVERRIDE; + + // Used to delete guid entries that have outlived their time wait period. + void CleanUpOldGuids(); + + // FramerVisitorInterface + virtual void OnError(QuicFramer* framer) OVERRIDE; + virtual bool OnProtocolVersionMismatch(QuicVersion received_version) OVERRIDE; + virtual bool OnPacketHeader(const QuicPacketHeader& header) OVERRIDE; + virtual void OnPacket() OVERRIDE {} + virtual void OnPublicResetPacket( + const QuicPublicResetPacket& packet) OVERRIDE {} + virtual void OnVersionNegotiationPacket( + const QuicVersionNegotiationPacket& /*packet*/) OVERRIDE {} + + virtual void OnPacketComplete() OVERRIDE {} + // The following methods should never get called because we always return + // false from OnPacketHeader(). We never need to process body of a packet. + virtual void OnRevivedPacket() OVERRIDE {} + virtual void OnFecProtectedPayload(base::StringPiece payload) OVERRIDE {} + virtual bool OnStreamFrame(const QuicStreamFrame& frame) OVERRIDE; + virtual bool OnAckFrame(const QuicAckFrame& frame) OVERRIDE; + virtual bool OnCongestionFeedbackFrame( + const QuicCongestionFeedbackFrame& frame) OVERRIDE; + virtual bool OnRstStreamFrame(const QuicRstStreamFrame& frame) OVERRIDE; + virtual bool OnConnectionCloseFrame( + const QuicConnectionCloseFrame & frame) OVERRIDE; + virtual bool OnGoAwayFrame(const QuicGoAwayFrame& frame) OVERRIDE; + virtual void OnFecData(const QuicFecData& fec) OVERRIDE {} + + QuicVersion version() const { return framer_.version(); } + + protected: + // Exposed for tests. + bool is_write_blocked() const { return is_write_blocked_; } + + // Decides if public reset packet should be sent for this guid based on the + // number of received pacekts. + bool ShouldSendPublicReset(int received_packet_count); + + // Exposed for tests. + const QuicTime::Delta time_wait_period() const { return kTimeWaitPeriod_; } + + // Given a GUID that exists in the time wait list, returns the QuicVersion + // associated with it. Used internally to set the framer version before + // writing the public reset packet. + QuicVersion GetQuicVersionFromGuid(QuicGuid guid); + + private: + // Stores the guid and the time it was added to time wait state. + struct GuidAddTime; + // Internal structure to store pending public reset packets. + class QueuedPacket; + + // Creates a public reset packet and sends it or queues it to be sent later. + void SendPublicReset(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + QuicPacketSequenceNumber rejected_sequence_number); + + // Either sends the packet and deletes it or makes pending_packets_queue_ the + // owner of the packet. + void SendOrQueuePacket(QueuedPacket* packet); + + // Should only be called when write_blocked_ == false. We only care if the + // writing was unsuccessful because the socket got blocked, which can be + // tested using write_blocked_ == true. In case of all other errors we drop + // the packet. Hence, we return void. + void WriteToWire(QueuedPacket* packet); + + // Register the alarm with the epoll server to wake up at appropriate time. + void SetGuidCleanUpAlarm(); + + // A map from a recently closed guid to the number of packets received after + // the termination of the connection bound to the guid. + struct GuidData { + GuidData(int num_packets_, QuicVersion version_) + : num_packets(num_packets_), version(version_) {} + int num_packets; + QuicVersion version; + }; + base::hash_map<QuicGuid, GuidData> guid_map_; + typedef base::hash_map<QuicGuid, GuidData>::iterator GuidMapIterator; + + // Maintains a list of GuidAddTime elements which it owns, in the + // order they should be deleted. + std::deque<GuidAddTime*> time_ordered_guid_list_; + + // Pending public reset packets that need to be sent out to the client + // when we are given a chance to write by the dispatcher. + std::deque<QueuedPacket*> pending_packets_queue_; + + // Used to parse incoming packets. + QuicFramer framer_; + + // Server and client address of the last packet processed. + IPEndPoint server_address_; + IPEndPoint client_address_; + + // Used to schedule alarms to delete old guids which have been in the list for + // too long. Owned by the dispatcher. + EpollServer* epoll_server_; + + // Time period for which guids should remain in time wait state. + const QuicTime::Delta kTimeWaitPeriod_; + + // Alarm registered with the epoll server to clean up guids that have out + // lived their duration in time wait state. + scoped_ptr<GuidCleanUpAlarm> guid_clean_up_alarm_; + + // Clock to efficiently measure approximate time from the epoll server. + QuicEpollClock clock_; + + // Interface that writes given buffer to the socket. Owned by the dispatcher. + QuicPacketWriter* writer_; + + // True if the underlying udp socket is write blocked, i.e will return EAGAIN + // on sendmsg. + bool is_write_blocked_; + + DISALLOW_COPY_AND_ASSIGN(QuicTimeWaitListManager); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_QUIC_TIME_WAIT_LIST_MANAGER_H_ diff --git a/chromium/net/tools/quic/spdy_utils.cc b/chromium/net/tools/quic/spdy_utils.cc new file mode 100644 index 00000000000..13d05e5e706 --- /dev/null +++ b/chromium/net/tools/quic/spdy_utils.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/spdy_utils.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_protocol.h" +#include "net/tools/flip_server/balsa_headers.h" +#include "url/gurl.h" + +using base::StringPiece; +using std::pair; +using std::string; + +namespace net { +namespace tools { + +const char* const kV3Host = ":host"; +const char* const kV3Path = ":path"; +const char* const kV3Scheme = ":scheme"; +const char* const kV3Status = ":status"; +const char* const kV3Method = ":method"; +const char* const kV3Version = ":version"; + +void PopulateSpdyHeaderBlock(const BalsaHeaders& headers, + SpdyHeaderBlock* block, + bool allow_empty_values) { + for (BalsaHeaders::const_header_lines_iterator hi = + headers.header_lines_begin(); + hi != headers.header_lines_end(); + ++hi) { + if ((hi->second.length() == 0) && !allow_empty_values) { + DLOG(INFO) << "Dropping empty header " << hi->first.as_string() + << " from headers"; + continue; + } + + // This unfortunately involves loads of copying, but its the simplest way + // to sort the headers and leverage the framer. + string name = hi->first.as_string(); + StringToLowerASCII(&name); + SpdyHeaderBlock::iterator it = block->find(name); + if (it != block->end()) { + it->second.reserve(it->second.size() + 1 + hi->second.size()); + it->second.append("\0", 1); + it->second.append(hi->second.data(), hi->second.size()); + } else { + block->insert(make_pair(name, hi->second.as_string())); + } + } +} + +void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders& headers, + const string& scheme, + const string& host_and_port, + const string& path, + SpdyHeaderBlock* block) { + PopulateSpdyHeaderBlock(headers, block, true); + StringPiece host_header = headers.GetHeader("Host"); + if (!host_header.empty()) { + DCHECK(host_and_port.empty() || host_header == host_and_port); + block->insert(make_pair(kV3Host, host_header.as_string())); + } else { + block->insert(make_pair(kV3Host, host_and_port)); + } + block->insert(make_pair(kV3Path, path)); + block->insert(make_pair(kV3Scheme, scheme)); + + if (!headers.request_method().empty()) { + block->insert(make_pair(kV3Method, headers.request_method().as_string())); + } + + if (!headers.request_version().empty()) { + (*block)[kV3Version] = headers.request_version().as_string(); + } +} + +void PopulateSpdyResponseHeaderBlock(const BalsaHeaders& headers, + SpdyHeaderBlock* block) { + string status = headers.response_code().as_string(); + status.append(" "); + status.append(headers.response_reason_phrase().as_string()); + (*block)[kV3Status] = status; + (*block)[kV3Version] = + headers.response_version().as_string(); + + // Empty header values are only allowed because this is spdy3. + PopulateSpdyHeaderBlock(headers, block, true); +} + +// static +SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdyHeaders( + const BalsaHeaders& request_headers) { + string scheme; + string host_and_port; + string path; + + string url = request_headers.request_uri().as_string(); + if (url.empty() || url[0] == '/') { + path = url; + } else { + GURL request_uri(url); + if (request_headers.request_method() == "CONNECT") { + path = url; + } else { + path = request_uri.path(); + if (!request_uri.query().empty()) { + path = path + "?" + request_uri.query(); + } + host_and_port = request_uri.host(); + scheme = request_uri.scheme(); + } + } + + DCHECK(!scheme.empty()); + DCHECK(!host_and_port.empty()); + DCHECK(!path.empty()); + + SpdyHeaderBlock block; + PopulateSpdy3RequestHeaderBlock( + request_headers, scheme, host_and_port, path, &block); + if (block.find("host") != block.end()) { + block.erase(block.find("host")); + } + return block; +} + +// static +string SpdyUtils::SerializeRequestHeaders(const BalsaHeaders& request_headers) { + SpdyHeaderBlock block = RequestHeadersToSpdyHeaders(request_headers); + return SerializeUncompressedHeaders(block); +} + +// static +SpdyHeaderBlock SpdyUtils::ResponseHeadersToSpdyHeaders( + const BalsaHeaders& response_headers) { + SpdyHeaderBlock block; + PopulateSpdyResponseHeaderBlock(response_headers, &block); + return block; +} + +// static +string SpdyUtils::SerializeResponseHeaders( + const BalsaHeaders& response_headers) { + SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers); + + return SerializeUncompressedHeaders(block); +} + +// static +string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) { + int length = SpdyFramer::GetSerializedLength(SPDY3, &headers); + SpdyFrameBuilder builder(length); + SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers); + scoped_ptr<SpdyFrame> block(builder.take()); + return string(block->data(), length); +} + +bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header, + BalsaHeaders* headers) { + if (header->first.empty() || header->second.empty()) { + return true; + } + const string& header_name = header->first; + return header_name.c_str()[0] == ':'; +} + +bool SpdyUtils::FillBalsaRequestHeaders( + const SpdyHeaderBlock& header_block, + BalsaHeaders* request_headers) { + typedef SpdyHeaderBlock::const_iterator BlockIt; + + BlockIt host_it = header_block.find(kV3Host); + BlockIt path_it = header_block.find(kV3Path); + BlockIt scheme_it = header_block.find(kV3Scheme); + BlockIt method_it = header_block.find(kV3Method); + BlockIt end_it = header_block.end(); + if (host_it == end_it || path_it == end_it || scheme_it == end_it || + method_it == end_it) { + return false; + } + string url = scheme_it->second; + url.append("://"); + url.append(host_it->second); + url.append(path_it->second); + request_headers->SetRequestUri(url); + request_headers->SetRequestMethod(method_it->second); + + BlockIt cl_it = header_block.find("content-length"); + if (cl_it != header_block.end()) { + int content_length; + if (!base::StringToInt(cl_it->second, &content_length)) { + return false; + } + request_headers->SetContentLength(content_length); + } + + for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { + if (!IsSpecialSpdyHeader(it, request_headers)) { + request_headers->AppendHeader(it->first, it->second); + } + } + + return true; +} + +// The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will +// fail to parse it. +bool ParseReasonAndStatus(StringPiece status_and_reason, + BalsaHeaders* headers) { + if (status_and_reason.size() < 5) + return false; + + if (status_and_reason[3] != ' ') + return false; + + const StringPiece status_str = StringPiece(status_and_reason.data(), 3); + int status; + if (!base::StringToInt(status_str, &status)) { + return false; + } + + headers->SetResponseCode(status_str); + headers->set_parsed_response_code(status); + + StringPiece reason(status_and_reason.data() + 4, + status_and_reason.length() - 4); + + headers->SetResponseReasonPhrase(reason); + return true; +} + +bool SpdyUtils::FillBalsaResponseHeaders( + const SpdyHeaderBlock& header_block, + BalsaHeaders* request_headers) { + typedef SpdyHeaderBlock::const_iterator BlockIt; + + BlockIt status_it = header_block.find(kV3Status); + BlockIt version_it = header_block.find(kV3Version); + BlockIt end_it = header_block.end(); + if (status_it == end_it || version_it == end_it) { + return false; + } + + request_headers->SetRequestVersion(version_it->second); + if (!ParseReasonAndStatus(status_it->second, request_headers)) { + return false; + } + for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { + if (!IsSpecialSpdyHeader(it, request_headers)) { + request_headers->AppendHeader(it->first, it->second); + } + } + return true; +} + +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/spdy_utils.h b/chromium/net/tools/quic/spdy_utils.h new file mode 100644 index 00000000000..44782744526 --- /dev/null +++ b/chromium/net/tools/quic/spdy_utils.h @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_SPDY_UTILS_H_ +#define NET_TOOLS_QUIC_SPDY_UTILS_H_ + +#include <string> + +#include "net/spdy/spdy_framer.h" +#include "net/spdy/spdy_header_block.h" +#include "net/spdy/spdy_protocol.h" +#include "net/tools/flip_server/balsa_headers.h" + +namespace net { +namespace tools { + +class SpdyUtils { + public: + static std::string SerializeRequestHeaders( + const BalsaHeaders& request_headers); + + static std::string SerializeResponseHeaders( + const BalsaHeaders& response_headers); + + static bool FillBalsaRequestHeaders(const SpdyHeaderBlock& header_block, + BalsaHeaders* request_headers); + + static bool FillBalsaResponseHeaders(const SpdyHeaderBlock& header_block, + BalsaHeaders* response_headers); + + static SpdyHeaderBlock RequestHeadersToSpdyHeaders( + const BalsaHeaders& request_headers); + + static SpdyHeaderBlock ResponseHeadersToSpdyHeaders( + const BalsaHeaders& response_headers); + + static std::string SerializeUncompressedHeaders( + const SpdyHeaderBlock& headers); +}; + +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_SPDY_UTILS_H_ diff --git a/chromium/net/tools/quic/test_tools/http_message_test_utils.cc b/chromium/net/tools/quic/test_tools/http_message_test_utils.cc new file mode 100644 index 00000000000..7d6df7a7649 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/http_message_test_utils.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/http_message_test_utils.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" + +using base::StringPiece; +using std::string; +using std::vector; + +namespace net { +namespace tools { +namespace test { + +namespace { + +//const char* kContentEncoding = "content-encoding"; +const char* kContentLength = "content-length"; +const char* kTransferCoding = "transfer-encoding"; + +// Both kHTTPVersionString and kMethodString arrays are constructed to match +// the enum values defined in Version and Method of HTTPMessage. +const char* kHTTPVersionString[] = { + "", + "HTTP/0.9", + "HTTP/1.0", + "HTTP/1.1" +}; + +const char* kMethodString[] = { + "", + "OPTIONS", + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "TRACE", + "CONNECT", + "MKCOL", + "UNLOCK", +}; + +// Returns true if the message represents a complete request or response. +// Messages are considered complete if: +// - Transfer-Encoding: chunked is present and message has a final chunk. +// - Content-Length header is present and matches the message body length. +// - Neither Transfer-Encoding nor Content-Length is present and message +// is tagged as complete. +bool IsCompleteMessage(const HTTPMessage& message) { + return true; + const BalsaHeaders* headers = message.headers(); + StringPiece content_length = headers->GetHeader(kContentLength); + if (!content_length.empty()) { + int parsed_content_length; + if (!base::StringToInt(content_length, &parsed_content_length)) { + return false; + } + return (message.body().size() == (uint)parsed_content_length); + } else { + // Assume messages without transfer coding or content-length are + // tagged correctly. + return message.has_complete_message(); + } +} + +} // namespace + +HTTPMessage::Method HTTPMessage::StringToMethod(StringPiece str) { + // Skip the first element of the array since it is empty string. + for (unsigned long i = 1; i < arraysize(kMethodString); ++i) { + if (strncmp(str.data(), kMethodString[i], str.length()) == 0) { + return static_cast<HTTPMessage::Method>(i); + } + } + return HttpConstants::UNKNOWN_METHOD; +} + +HTTPMessage::Version HTTPMessage::StringToVersion(StringPiece str) { + // Skip the first element of the array since it is empty string. + for (unsigned long i = 1; i < arraysize(kHTTPVersionString); ++i) { + if (strncmp(str.data(), kHTTPVersionString[i], str.length()) == 0) { + return static_cast<HTTPMessage::Version>(i); + } + } + return HttpConstants::HTTP_UNKNOWN; +} + +const char* HTTPMessage::MethodToString(Method method) { + CHECK_LT(static_cast<size_t>(method), arraysize(kMethodString)); + return kMethodString[method]; +} + +const char* HTTPMessage::VersionToString(Version version) { + CHECK_LT(static_cast<size_t>(version), arraysize(kHTTPVersionString)); + return kHTTPVersionString[version]; +} + +HTTPMessage::HTTPMessage() + : is_request_(true) { + InitializeFields(); +} + +HTTPMessage::HTTPMessage(Version ver, Method request, const string& path) + : is_request_(true) { + InitializeFields(); + if (ver != HttpConstants::HTTP_0_9) { + headers()->SetRequestVersion(VersionToString(ver)); + } + headers()->SetRequestMethod(MethodToString(request)); + headers()->SetRequestUri(path); +} + +HTTPMessage::~HTTPMessage() { +} + +void HTTPMessage::InitializeFields() { + has_complete_message_ = true; + skip_message_validation_ = false; +} + +void HTTPMessage::AddHeader(const string& header, const string& value) { + headers()->AppendHeader(header, value); +} + +void HTTPMessage::RemoveHeader(const string& header) { + headers()->RemoveAllOfHeader(header); +} + +void HTTPMessage::ReplaceHeader(const string& header, const string& value) { + headers()->ReplaceOrAppendHeader(header, value); +} + +void HTTPMessage::AddBody(const string& body, bool add_content_length) { + body_ = body; + // Remove any transfer-encoding that was left by a previous body. + RemoveHeader(kTransferCoding); + if (add_content_length) { + ReplaceHeader(kContentLength, base::IntToString(body.size())); + } else { + RemoveHeader(kContentLength); + } +} + +void HTTPMessage::ValidateMessage() const { + if (skip_message_validation_) { + return; + } + + vector<StringPiece> transfer_encodings; + headers()->GetAllOfHeader(kTransferCoding, &transfer_encodings); + CHECK_GE(1ul, transfer_encodings.size()); + for (vector<StringPiece>::iterator it = transfer_encodings.begin(); + it != transfer_encodings.end(); + ++it) { + CHECK(StringPieceUtils::EqualIgnoreCase("identity", *it) || + StringPieceUtils::EqualIgnoreCase("chunked", *it)) << *it; + } + + vector<StringPiece> content_lengths; + headers()->GetAllOfHeader(kContentLength, &content_lengths); + CHECK_GE(1ul, content_lengths.size()); + + CHECK_EQ(has_complete_message_, IsCompleteMessage(*this)); +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/http_message_test_utils.h b/chromium/net/tools/quic/test_tools/http_message_test_utils.h new file mode 100644 index 00000000000..d389e21c8f2 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/http_message_test_utils.h @@ -0,0 +1,133 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "net/tools/flip_server/balsa_enums.h" +#include "net/tools/flip_server/balsa_headers.h" + +namespace net { +namespace tools { +namespace test { + +class HttpConstants { + public: + enum Version { + HTTP_UNKNOWN = 0, + HTTP_0_9, + HTTP_1_0, + HTTP_1_1 + }; + + enum Method { + UNKNOWN_METHOD = 0, + OPTIONS, + GET, + HEAD, + POST, + PUT, + DELETE, + TRACE, + CONNECT, + + MKCOL, + UNLOCK, + }; +}; + +// Stripped down wrapper class which basically contains headers and a body. +class HTTPMessage { + public: + typedef HttpConstants::Version Version; + typedef HttpConstants::Method Method; + + // Convenient functions to map strings into enums. The string passed in is + // not assumed to be NULL-terminated. + static Version StringToVersion(base::StringPiece str); + static Method StringToMethod(base::StringPiece str); + + static const char* MethodToString(Method method); + static const char* VersionToString(Version version); + + // Default constructor makes an empty HTTP/1.1 GET request. This is typically + // used to construct a message that will be Initialize()-ed. + HTTPMessage(); + + // Build a request message + HTTPMessage(Version version, Method request, const std::string& path); + + virtual ~HTTPMessage(); + + const std::string& body() const { return body_; } + + // Adds a header line to the message. + void AddHeader(const std::string& header, const std::string& value); + + // Removes a header line from the message. + void RemoveHeader(const std::string& header); + + // A utility function which calls RemoveHeader followed by AddHeader. + void ReplaceHeader(const std::string& header, const std::string& value); + + // Adds a body and the optional content-length header field (omitted to test + // read until close test case). To generate a message that has a header field + // of 0 content-length, call AddBody("", true). + // Multiple calls to AddBody()/AddChunkedBody() has the effect of overwriting + // the previous entry without warning. + void AddBody(const std::string& body, bool add_content_length); + + bool has_complete_message() const { return has_complete_message_; } + void set_has_complete_message(bool value) { has_complete_message_ = value; } + + // Do some basic http message consistency checks like: + // - Valid transfer-encoding header + // - Valid content-length header + // - Messages we expect to be complete are complete. + // This check can be disabled by setting skip_message_validation. + void ValidateMessage() const; + + bool skip_message_validation() const { return skip_message_validation_; } + void set_skip_message_validation(bool value) { + skip_message_validation_ = value; + } + + // Allow direct access to the body string. This should be used with caution: + // it will not update the request headers like AddBody and AddChunkedBody do. + void set_body(const std::string& body) { body_ = body; } + + const BalsaHeaders* headers() const { return &headers_; } + BalsaHeaders* headers() { return &headers_; } + + protected: + BalsaHeaders headers_; + + std::string body_; // the body with chunked framing/gzip compression + + bool is_request_; + + // True if the message should be considered complete during serialization. + // Used by SPDY and Streamed RPC clients to decide wherever or not + // to include fin flags and during message validation (if enabled). + bool has_complete_message_; + + // Allows disabling message validation when creating test messages + // that are intentionally invalid. + bool skip_message_validation_; + + private: + void InitializeFields(); + + DISALLOW_COPY_AND_ASSIGN(HTTPMessage); +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_TEST_TOOLS_HTTP_MESSAGE_TEST_UTILS_H_ diff --git a/chromium/net/tools/quic/test_tools/mock_epoll_server.cc b/chromium/net/tools/quic/test_tools/mock_epoll_server.cc new file mode 100644 index 00000000000..4101c5e9ab0 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/mock_epoll_server.cc @@ -0,0 +1,68 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/mock_epoll_server.h" + +namespace net { +namespace tools { +namespace test { + +FakeTimeEpollServer::FakeTimeEpollServer(): now_in_usec_(0) { +} + +FakeTimeEpollServer::~FakeTimeEpollServer() { +} + +int64 FakeTimeEpollServer::NowInUsec() const { + return now_in_usec_; +} + +MockEpollServer::MockEpollServer() : until_in_usec_(-1) { +} + +MockEpollServer::~MockEpollServer() { +} + +int MockEpollServer::epoll_wait_impl(int epfd, + struct epoll_event* events, + int max_events, + int timeout_in_ms) { + int num_events = 0; + while (!event_queue_.empty() && + num_events < max_events && + event_queue_.begin()->first <= NowInUsec() && + ((until_in_usec_ == -1) || + (event_queue_.begin()->first < until_in_usec_)) + ) { + int64 event_time_in_usec = event_queue_.begin()->first; + events[num_events] = event_queue_.begin()->second; + if (event_time_in_usec > NowInUsec()) { + set_now_in_usec(event_time_in_usec); + } + event_queue_.erase(event_queue_.begin()); + ++num_events; + } + if (num_events == 0) { // then we'd have waited 'till the timeout. + if (until_in_usec_ < 0) { // then we don't care what the final time is. + if (timeout_in_ms > 0) { + AdvanceBy(timeout_in_ms * 1000); + } + } else { // except we assume that we don't wait for the timeout + // period if until_in_usec_ is a positive number. + set_now_in_usec(until_in_usec_); + // And reset until_in_usec_ to signal no waiting (as + // the AdvanceByExactly* stuff is meant to be one-shot, + // as are all similar EpollServer functions) + until_in_usec_ = -1; + } + } + if (until_in_usec_ >= 0) { + CHECK(until_in_usec_ >= NowInUsec()); + } + return num_events; +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/mock_epoll_server.h b/chromium/net/tools/quic/test_tools/mock_epoll_server.h new file mode 100644 index 00000000000..710d5fdf772 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/mock_epoll_server.h @@ -0,0 +1,106 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_ + +#include "base/logging.h" +#include "net/tools/flip_server/epoll_server.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace net { +namespace tools { +namespace test { + +// Unlike the full MockEpollServer, this only lies about the time but lets +// fd events operate normally. Usefully when interacting with real backends +// but wanting to skip forward in time to trigger timeouts. +class FakeTimeEpollServer : public EpollServer { + public: + FakeTimeEpollServer(); + virtual ~FakeTimeEpollServer(); + + // Replaces the EpollServer NowInUsec. + virtual int64 NowInUsec() const OVERRIDE; + + void set_now_in_usec(int64 nius) { now_in_usec_ = nius; } + + // Advances the virtual 'now' by advancement_usec. + void AdvanceBy(int64 advancement_usec) { + set_now_in_usec(NowInUsec() + advancement_usec); + } + + // Advances the virtual 'now' by advancement_usec, and + // calls WaitForEventAndExecteCallbacks. + // Note that the WaitForEventsAndExecuteCallbacks invocation + // may cause NowInUs to advance beyond what was specified here. + // If that is not desired, use the AdvanceByExactly calls. + void AdvanceByAndCallCallbacks(int64 advancement_usec) { + AdvanceBy(advancement_usec); + WaitForEventsAndExecuteCallbacks(); + } + + private: + int64 now_in_usec_; +}; + +class MockEpollServer : public FakeTimeEpollServer { + public: // type definitions + typedef base::hash_multimap<int64, struct epoll_event> EventQueue; + + MockEpollServer(); + virtual ~MockEpollServer(); + + // time_in_usec is the time at which the event specified + // by 'ee' will be delivered. Note that it -is- possible + // to add an event for a time which has already been passed.. + // .. upon the next time that the callbacks are invoked, + // all events which are in the 'past' will be delivered. + void AddEvent(int64 time_in_usec, const struct epoll_event& ee) { + event_queue_.insert(std::make_pair(time_in_usec, ee)); + } + + // Advances the virtual 'now' by advancement_usec, + // and ensure that the next invocation of + // WaitForEventsAndExecuteCallbacks goes no farther than + // advancement_usec from the current time. + void AdvanceByExactly(int64 advancement_usec) { + until_in_usec_ = NowInUsec() + advancement_usec; + set_now_in_usec(NowInUsec() + advancement_usec); + } + + // As above, except calls WaitForEventsAndExecuteCallbacks. + void AdvanceByExactlyAndCallCallbacks(int64 advancement_usec) { + AdvanceByExactly(advancement_usec); + WaitForEventsAndExecuteCallbacks(); + } + + base::hash_set<AlarmCB*>::size_type NumberOfAlarms() const { + return all_alarms_.size(); + } + + protected: // functions + // These functions do nothing here, as we're not actually + // using the epoll_* syscalls. + virtual void DelFD(int fd) const OVERRIDE { } + virtual void AddFD(int fd, int event_mask) const OVERRIDE { } + virtual void ModFD(int fd, int event_mask) const OVERRIDE { } + + // Replaces the epoll_server's epoll_wait_impl. + virtual int epoll_wait_impl(int epfd, + struct epoll_event* events, + int max_events, + int timeout_in_ms) OVERRIDE; + virtual void SetNonblocking (int fd) OVERRIDE { } + + private: // members + EventQueue event_queue_; + int64 until_in_usec_; +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_MOCK_EPOLL_SERVER_H_ diff --git a/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc new file mode 100644 index 00000000000..3a2b1d98252 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.cc @@ -0,0 +1,21 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/mock_quic_dispatcher.h" + +namespace net { +namespace tools { +namespace test { + +MockQuicDispatcher::MockQuicDispatcher( + const QuicConfig& config, + const QuicCryptoServerConfig& crypto_config, + QuicGuid guid, + EpollServer* eps) + : QuicDispatcher(config, crypto_config, guid, eps) { } +MockQuicDispatcher::~MockQuicDispatcher() {} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h new file mode 100644 index 00000000000..563ab0de5fd --- /dev/null +++ b/chromium/net/tools/quic/test_tools/mock_quic_dispatcher.h @@ -0,0 +1,38 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_ + +#include "net/base/ip_endpoint.h" +#include "net/quic/crypto/crypto_server_config.h" +#include "net/quic/quic_config.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/flip_server/epoll_server.h" +#include "net/tools/quic/quic_dispatcher.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace net { +namespace tools { +namespace test { + +class MockQuicDispatcher : public QuicDispatcher { + public: + MockQuicDispatcher(const QuicConfig& config, + const QuicCryptoServerConfig& crypto_config, + QuicGuid guid, + EpollServer* eps); + virtual ~MockQuicDispatcher(); + + MOCK_METHOD4(ProcessPacket, void(const IPEndPoint& server_address, + const IPEndPoint& client_address, + QuicGuid guid, + const QuicEncryptedPacket& packet)); +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_MOCK_QUIC_DISPATCHER_H_ diff --git a/chromium/net/tools/quic/test_tools/quic_client_peer.cc b/chromium/net/tools/quic/test_tools/quic_client_peer.cc new file mode 100644 index 00000000000..858359474c8 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_client_peer.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/quic_client_peer.h" + +#include "net/tools/quic/quic_client.h" + +namespace net { +namespace tools { +namespace test { + +// static +void QuicClientPeer::Reinitialize(QuicClient* client) { + client->initialized_ = false; + client->epoll_server_.UnregisterFD(client->fd_); + client->Initialize(); +} + +// static +int QuicClientPeer::GetFd(QuicClient* client) { + return client->fd_; +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/quic_client_peer.h b/chromium/net/tools/quic/test_tools/quic_client_peer.h new file mode 100644 index 00000000000..8eaa17e675d --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_client_peer.h @@ -0,0 +1,25 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_ + +namespace net { +namespace tools { + +class QuicClient; + +namespace test { + +class QuicClientPeer { + public: + static void Reinitialize(QuicClient* client); + static int GetFd(QuicClient* client); +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_CLIENT_PEER_H_ diff --git a/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc new file mode 100644 index 00000000000..e358273d0a2 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h" + +#include "net/tools/quic/quic_epoll_connection_helper.h" + +namespace net { +namespace tools { +namespace test { + +// static +void QuicEpollConnectionHelperPeer::SetWriter(QuicEpollConnectionHelper* helper, + QuicPacketWriter* writer) { + helper->writer_ = writer; +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h new file mode 100644 index 00000000000..72085dfde1c --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_epoll_connection_helper_peer.h @@ -0,0 +1,31 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_ + +#include "base/basictypes.h" + +namespace net { +namespace tools { + +class QuicPacketWriter; +class QuicEpollConnectionHelper; + +namespace test { + +class QuicEpollConnectionHelperPeer { + public: + static void SetWriter(QuicEpollConnectionHelper* helper, + QuicPacketWriter* writer); + + private: + DISALLOW_COPY_AND_ASSIGN(QuicEpollConnectionHelperPeer); +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_EPOLL_CONNECTION_HELPER_PEER_H_ diff --git a/chromium/net/tools/quic/test_tools/quic_test_client.cc b/chromium/net/tools/quic/test_tools/quic_test_client.cc new file mode 100644 index 00000000000..859d7a56044 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_test_client.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/quic_test_client.h" + +#include "net/base/completion_callback.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/x509_certificate.h" +#include "net/quic/crypto/proof_verifier.h" +#include "net/tools/flip_server/balsa_headers.h" +#include "net/tools/quic/quic_epoll_connection_helper.h" +#include "net/tools/quic/test_tools/http_message_test_utils.h" +#include "url/gurl.h" + +using std::string; +using std::vector; +using base::StringPiece; + +namespace { + +// RecordingProofVerifier accepts any certificate chain and records the common +// name of the leaf. +class RecordingProofVerifier : public net::ProofVerifier { + public: + // ProofVerifier interface. + virtual net::ProofVerifier::Status VerifyProof( + net::QuicVersion version, + const string& hostname, + const string& server_config, + const vector<string>& certs, + const string& signature, + string* error_details, + scoped_ptr<net::ProofVerifyDetails>* details, + net::ProofVerifierCallback* callback) OVERRIDE { + delete callback; + + common_name_.clear(); + if (certs.empty()) { + return FAILURE; + } + + // Convert certs to X509Certificate. + vector<StringPiece> cert_pieces(certs.size()); + for (unsigned i = 0; i < certs.size(); i++) { + cert_pieces[i] = StringPiece(certs[i]); + } + scoped_refptr<net::X509Certificate> cert = + net::X509Certificate::CreateFromDERCertChain(cert_pieces); + if (!cert.get()) { + return FAILURE; + } + + common_name_ = cert->subject().GetDisplayName(); + return SUCCESS; + } + + const string& common_name() const { return common_name_; } + + private: + string common_name_; +}; + +} // anonymous namespace + +namespace net { +namespace tools { +namespace test { + +BalsaHeaders* MungeHeaders(const BalsaHeaders* const_headers, + bool secure) { + StringPiece uri = const_headers->request_uri(); + if (uri.empty()) { + return NULL; + } + if (const_headers->request_method() == "CONNECT") { + return NULL; + } + BalsaHeaders* headers = new BalsaHeaders; + headers->CopyFrom(*const_headers); + if (!uri.starts_with("https://") && + !uri.starts_with("http://")) { + // If we have a relative URL, set some defaults. + string full_uri = secure ? "https://www.google.com" : + "http://www.google.com"; + full_uri.append(uri.as_string()); + headers->SetRequestUri(full_uri); + } + return headers; +} + +// A quic client which allows mocking out writes. +class QuicEpollClient : public QuicClient { + public: + typedef QuicClient Super; + + QuicEpollClient(IPEndPoint server_address, + const string& server_hostname, + const QuicVersion version) + : Super(server_address, server_hostname, version) { + } + + QuicEpollClient(IPEndPoint server_address, + const string& server_hostname, + const QuicConfig& config, + const QuicVersion version) + : Super(server_address, server_hostname, config, version) { + } + + virtual ~QuicEpollClient() { + if (connected()) { + Disconnect(); + } + } + + virtual QuicEpollConnectionHelper* CreateQuicConnectionHelper() OVERRIDE { + if (writer_.get() != NULL) { + writer_->set_fd(fd()); + return new QuicEpollConnectionHelper(writer_.get(), epoll_server()); + } else { + return Super::CreateQuicConnectionHelper(); + } + } + + void UseWriter(QuicTestWriter* writer) { writer_.reset(writer); } + + private: + scoped_ptr<QuicTestWriter> writer_; +}; + +QuicTestClient::QuicTestClient(IPEndPoint address, const string& hostname, + const QuicVersion version) + : client_(new QuicEpollClient(address, hostname, version)) { + Initialize(address, hostname, true); +} + +QuicTestClient::QuicTestClient(IPEndPoint address, + const string& hostname, + bool secure, + const QuicVersion version) + : client_(new QuicEpollClient(address, hostname, version)) { + Initialize(address, hostname, secure); +} + +QuicTestClient::QuicTestClient(IPEndPoint address, + const string& hostname, + bool secure, + const QuicConfig& config, + const QuicVersion version) + : client_(new QuicEpollClient(address, hostname, config, version)) { + Initialize(address, hostname, secure); +} + +void QuicTestClient::Initialize(IPEndPoint address, + const string& hostname, + bool secure) { + server_address_ = address; + stream_ = NULL; + stream_error_ = QUIC_STREAM_NO_ERROR; + bytes_read_ = 0; + bytes_written_= 0; + never_connected_ = true; + secure_ = secure; + auto_reconnect_ = false; + proof_verifier_ = NULL; + ExpectCertificates(secure_); +} + +QuicTestClient::~QuicTestClient() { + if (stream_) { + stream_->set_visitor(NULL); + } +} + +void QuicTestClient::ExpectCertificates(bool on) { + if (on) { + proof_verifier_ = new RecordingProofVerifier; + client_->SetProofVerifier(proof_verifier_); + } else { + proof_verifier_ = NULL; + client_->SetProofVerifier(NULL); + } +} + +ssize_t QuicTestClient::SendRequest(const string& uri) { + HTTPMessage message(HttpConstants::HTTP_1_1, HttpConstants::GET, uri); + return SendMessage(message); +} + +ssize_t QuicTestClient::SendMessage(const HTTPMessage& message) { + stream_ = NULL; // Always force creation of a stream for SendMessage. + + // If we're not connected, try to find an sni hostname. + if (!connected()) { + GURL url(message.headers()->request_uri().as_string()); + if (!url.host().empty()) { + client_->set_server_hostname(url.host()); + } + } + + QuicReliableClientStream* stream = GetOrCreateStream(); + if (!stream) { return 0; } + + scoped_ptr<BalsaHeaders> munged_headers(MungeHeaders(message.headers(), + secure_)); + return GetOrCreateStream()->SendRequest( + munged_headers.get() ? *munged_headers.get() : *message.headers(), + message.body(), + message.has_complete_message()); +} + +ssize_t QuicTestClient::SendData(string data, bool last_data) { + QuicReliableClientStream* stream = GetOrCreateStream(); + if (!stream) { return 0; } + GetOrCreateStream()->SendBody(data, last_data); + return data.length(); +} + +string QuicTestClient::SendCustomSynchronousRequest( + const HTTPMessage& message) { + SendMessage(message); + WaitForResponse(); + return response_; +} + +string QuicTestClient::SendSynchronousRequest(const string& uri) { + if (SendRequest(uri) == 0) { + DLOG(ERROR) << "Failed the request for uri:" << uri; + return ""; + } + WaitForResponse(); + return response_; +} + +QuicReliableClientStream* QuicTestClient::GetOrCreateStream() { + if (never_connected_ == true || auto_reconnect_) { + if (!connected()) { + Connect(); + } + if (!connected()) { + return NULL; + } + } + if (!stream_) { + stream_ = client_->CreateReliableClientStream(); + if (stream_ != NULL) { + stream_->set_visitor(this); + } + } + return stream_; +} + +const string& QuicTestClient::cert_common_name() const { + return reinterpret_cast<RecordingProofVerifier*>(proof_verifier_) + ->common_name(); +} + +bool QuicTestClient::connected() const { + return client_->connected(); +} + +void QuicTestClient::WaitForResponse() { + if (stream_ == NULL) { + // The client has likely disconnected. + return; + } + client_->WaitForStreamToClose(stream_->id()); +} + +void QuicTestClient::Connect() { + DCHECK(!connected()); + client_->Initialize(); + client_->Connect(); + never_connected_ = false; +} + +void QuicTestClient::ResetConnection() { + Disconnect(); + Connect(); +} + +void QuicTestClient::Disconnect() { + client_->Disconnect(); +} + +IPEndPoint QuicTestClient::LocalSocketAddress() const { + return client_->client_address(); +} + +void QuicTestClient::ClearPerRequestState() { + stream_error_ = QUIC_STREAM_NO_ERROR; + stream_ = NULL; + response_ = ""; + headers_.Clear(); + bytes_read_ = 0; + bytes_written_ = 0; +} + +void QuicTestClient::WaitForInitialResponse() { + DCHECK(stream_ != NULL); + while (stream_ && stream_->stream_bytes_read() == 0) { + client_->WaitForEvents(); + } +} + +ssize_t QuicTestClient::Send(const void *buffer, size_t size) { + return SendData(string(static_cast<const char*>(buffer), size), false); +} + +int QuicTestClient::response_size() const { + return bytes_read_; +} + +size_t QuicTestClient::bytes_read() const { + return bytes_read_; +} + +size_t QuicTestClient::bytes_written() const { + return bytes_written_; +} + +void QuicTestClient::OnClose(ReliableQuicStream* stream) { + if (stream_ != stream) { + return; + } + response_ = stream_->data(); + headers_.CopyFrom(stream_->headers()); + stream_error_ = stream_->stream_error(); + bytes_read_ = stream_->stream_bytes_read(); + bytes_written_ = stream_->stream_bytes_written(); + stream_ = NULL; +} + +void QuicTestClient::UseWriter(QuicTestWriter* writer) { + DCHECK(!connected()); + reinterpret_cast<QuicEpollClient*>(client_.get())->UseWriter(writer); +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/quic_test_client.h b/chromium/net/tools/quic/test_tools/quic_test_client.h new file mode 100644 index 00000000000..74bfc24646a --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_test_client.h @@ -0,0 +1,143 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_ +#define NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "net/quic/quic_framer.h" +#include "net/quic/quic_packet_creator.h" +#include "net/quic/quic_protocol.h" +#include "net/tools/quic/quic_client.h" +#include "net/tools/quic/quic_packet_writer.h" + +namespace net { + +class ProofVerifier; + +namespace tools { + +namespace test { + +// Allows setting a writer for the client's QuicConnectionHelper, to allow +// fine-grained control of writes. +class QuicTestWriter : public QuicPacketWriter { + public: + virtual ~QuicTestWriter() {} + virtual void set_fd(int fd) = 0; +}; + +class HTTPMessage; + +// A toy QUIC client used for testing. +class QuicTestClient : public ReliableQuicStream::Visitor { + public: + QuicTestClient(IPEndPoint server_address, const string& server_hostname, + const QuicVersion version); + QuicTestClient(IPEndPoint server_address, + const string& server_hostname, + bool secure, + const QuicVersion version); + QuicTestClient(IPEndPoint server_address, + const string& server_hostname, + bool secure, + const QuicConfig& config, + const QuicVersion version); + + virtual ~QuicTestClient(); + + // ExpectCertificates controls whether the server is expected to provide + // certificates. The certificates, if any, are not verified, but the common + // name is recorded and available with |cert_common_name()|. + void ExpectCertificates(bool on); + + // Clears any outstanding state and sends a simple GET of 'uri' to the + // server. Returns 0 if the request failed and no bytes were written. + ssize_t SendRequest(const string& uri); + ssize_t SendMessage(const HTTPMessage& message); + + string SendCustomSynchronousRequest(const HTTPMessage& message); + string SendSynchronousRequest(const string& uri); + + // Wraps data in a quic packet and sends it. + ssize_t SendData(string data, bool last_data); + + QuicPacketCreator::Options* options() { return client_->options(); } + + const BalsaHeaders *response_headers() const {return &headers_;} + + void WaitForResponse(); + + void Connect(); + void ResetConnection(); + void Disconnect(); + IPEndPoint LocalSocketAddress() const; + void ClearPerRequestState(); + void WaitForInitialResponse(); + ssize_t Send(const void *buffer, size_t size); + int response_size() const; + size_t bytes_read() const; + size_t bytes_written() const; + + // From ReliableQuicStream::Visitor + virtual void OnClose(ReliableQuicStream* stream) OVERRIDE; + + // Configures client_ to take ownership of and use the writer. + // Must be called before initial connect. + void UseWriter(QuicTestWriter* writer); + + // Returns NULL if the maximum number of streams have already been created. + QuicReliableClientStream* GetOrCreateStream(); + + QuicRstStreamErrorCode stream_error() { return stream_error_; } + QuicErrorCode connection_error() { return client()->session()->error(); } + + QuicClient* client() { return client_.get(); } + + // cert_common_name returns the common name value of the server's certificate, + // or the empty string if no certificate was presented. + const string& cert_common_name() const; + + const string& response_body() {return response_;} + bool connected() const; + + void set_auto_reconnect(bool reconnect) { auto_reconnect_ = reconnect; } + + private: + void Initialize(IPEndPoint address, const string& hostname, bool secure); + + IPEndPoint server_address_; + IPEndPoint client_address_; + scoped_ptr<QuicClient> client_; // The actual client + QuicReliableClientStream* stream_; + + QuicRstStreamErrorCode stream_error_; + + BalsaHeaders headers_; + string response_; + uint64 bytes_read_; + uint64 bytes_written_; + // True if the client has never connected before. The client will + // auto-connect exactly once before sending data. If something causes a + // connection reset, it will not automatically reconnect. + bool never_connected_; + bool secure_; + // If true, the client will always reconnect if necessary before creating a + // stream. + bool auto_reconnect_; + + // proof_verifier_ points to a RecordingProofVerifier that is owned by + // client_. + ProofVerifier* proof_verifier_; +}; + +} // namespace test + +} // namespace tools +} // namespace net + +#endif // NET_QUIC_TEST_TOOLS_QUIC_CLIENT_H_ diff --git a/chromium/net/tools/quic/test_tools/quic_test_utils.cc b/chromium/net/tools/quic/test_tools/quic_test_utils.cc new file mode 100644 index 00000000000..95f1fb215ea --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_test_utils.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/tools/quic/test_tools/quic_test_utils.h" + +#include "net/quic/test_tools/quic_test_utils.h" +#include "net/tools/quic/quic_epoll_connection_helper.h" + +using base::StringPiece; +using net::test::MockHelper; + +namespace net { +namespace tools { +namespace test { + +MockConnection::MockConnection(QuicGuid guid, + IPEndPoint address, + int fd, + EpollServer* eps, + bool is_server) + : QuicConnection(guid, address, + new QuicEpollConnectionHelper(fd, eps), is_server, + QuicVersionMax()), + has_mock_helper_(false) { +} + +MockConnection::MockConnection(QuicGuid guid, + IPEndPoint address, + bool is_server) + : QuicConnection(guid, address, new testing::NiceMock<MockHelper>(), + is_server, QuicVersionMax()), + has_mock_helper_(true) { +} + +MockConnection::MockConnection(QuicGuid guid, + IPEndPoint address, + QuicConnectionHelperInterface* helper, + bool is_server) + : QuicConnection(guid, address, helper, is_server, QuicVersionMax()), + has_mock_helper_(false) { +} + +MockConnection::~MockConnection() { +} + +void MockConnection::AdvanceTime(QuicTime::Delta delta) { + CHECK(has_mock_helper_) << "Cannot advance time unless a MockClock is being" + " used"; + static_cast<MockHelper*>(helper())->AdvanceTime(delta); +} + +bool TestDecompressorVisitor::OnDecompressedData(StringPiece data) { + data.AppendToString(&data_); + return true; +} + +void TestDecompressorVisitor::OnDecompressionError() { + error_ = true; +} + +TestSession::TestSession(QuicConnection* connection, + const QuicConfig& config, + bool is_server) + : QuicSession(connection, config, is_server), + crypto_stream_(NULL) { +} + +TestSession::~TestSession() {} + +void TestSession::SetCryptoStream(QuicCryptoStream* stream) { + crypto_stream_ = stream; +} + +QuicCryptoStream* TestSession::GetCryptoStream() { + return crypto_stream_; +} + +} // namespace test +} // namespace tools +} // namespace net diff --git a/chromium/net/tools/quic/test_tools/quic_test_utils.h b/chromium/net/tools/quic/test_tools/quic_test_utils.h new file mode 100644 index 00000000000..31ea1815e06 --- /dev/null +++ b/chromium/net/tools/quic/test_tools/quic_test_utils.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_ +#define NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_ + +#include <string> + +#include "base/strings/string_piece.h" +#include "net/quic/quic_connection.h" +#include "net/quic/quic_session.h" +#include "net/quic/quic_spdy_decompressor.h" +#include "net/spdy/spdy_framer.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace net { + +class EpollServer; +class IPEndPoint; + +namespace tools { +namespace test { + +std::string SerializeUncompressedHeaders(const SpdyHeaderBlock& headers); + +class MockConnection : public QuicConnection { + public: + // Uses a QuicConnectionHelper created with fd and eps. + MockConnection(QuicGuid guid, + IPEndPoint address, + int fd, + EpollServer* eps, + bool is_server); + // Uses a MockHelper. + MockConnection(QuicGuid guid, IPEndPoint address, bool is_server); + MockConnection(QuicGuid guid, + IPEndPoint address, + QuicConnectionHelperInterface* helper, bool is_server); + virtual ~MockConnection(); + + // If the constructor that uses a MockHelper has been used then this method + // will advance the time of the MockClock. + void AdvanceTime(QuicTime::Delta delta); + + MOCK_METHOD3(ProcessUdpPacket, void(const IPEndPoint& self_address, + const IPEndPoint& peer_address, + const QuicEncryptedPacket& packet)); + MOCK_METHOD1(SendConnectionClose, void(QuicErrorCode error)); + MOCK_METHOD2(SendConnectionCloseWithDetails, void( + QuicErrorCode error, + const std::string& details)); + MOCK_METHOD2(SendRstStream, void(QuicStreamId id, + QuicRstStreamErrorCode error)); + MOCK_METHOD3(SendGoAway, void(QuicErrorCode error, + QuicStreamId last_good_stream_id, + const std::string& reason)); + MOCK_METHOD0(OnCanWrite, bool()); + + void ReallyProcessUdpPacket(const IPEndPoint& self_address, + const IPEndPoint& peer_address, + const QuicEncryptedPacket& packet) { + return QuicConnection::ProcessUdpPacket(self_address, peer_address, packet); + } + + virtual bool OnProtocolVersionMismatch(QuicVersion version) { return false; } + + private: + const bool has_mock_helper_; + + DISALLOW_COPY_AND_ASSIGN(MockConnection); +}; + +class TestDecompressorVisitor : public QuicSpdyDecompressor::Visitor { + public: + virtual ~TestDecompressorVisitor() {} + virtual bool OnDecompressedData(base::StringPiece data) OVERRIDE; + virtual void OnDecompressionError() OVERRIDE; + + std::string data() { return data_; } + bool error() { return error_; } + + private: + std::string data_; + bool error_; +}; + +class TestSession : public QuicSession { + public: + TestSession(QuicConnection* connection, + const QuicConfig& config, + bool is_server); + virtual ~TestSession(); + + MOCK_METHOD1(CreateIncomingReliableStream, + ReliableQuicStream*(QuicStreamId id)); + MOCK_METHOD0(CreateOutgoingReliableStream, ReliableQuicStream*()); + + void SetCryptoStream(QuicCryptoStream* stream); + + virtual QuicCryptoStream* GetCryptoStream(); + + private: + QuicCryptoStream* crypto_stream_; + DISALLOW_COPY_AND_ASSIGN(TestSession); +}; + +} // namespace test +} // namespace tools +} // namespace net + +#endif // NET_TOOLS_QUIC_TEST_TOOLS_QUIC_TEST_UTILS_H_ diff --git a/chromium/net/tools/quic/test_tools/run_all_unittests.cc b/chromium/net/tools/quic/test_tools/run_all_unittests.cc new file mode 100644 index 00000000000..6d42d33eb8f --- /dev/null +++ b/chromium/net/tools/quic/test_tools/run_all_unittests.cc @@ -0,0 +1,11 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/test_suite.h" + +int main(int argc, char** argv) { + base::TestSuite test_suite(argc, argv); + + return test_suite.Run(); +} |
