diff options
-rw-r--r-- | cloud_print/gcp20/prototype/dns_packet_parser.cc | 38 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/dns_packet_parser.h | 78 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/dns_response_builder.cc | 153 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/dns_response_builder.h | 68 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/dns_sd_server.cc | 247 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/dns_sd_server.h | 76 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/gcp20_device.cc | 43 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/gcp20_device.gyp | 23 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/printer.cc | 131 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/printer.h | 42 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/service_parameters.cc | 22 | ||||
-rw-r--r-- | cloud_print/gcp20/prototype/service_parameters.h | 32 |
12 files changed, 885 insertions, 68 deletions
diff --git a/cloud_print/gcp20/prototype/dns_packet_parser.cc b/cloud_print/gcp20/prototype/dns_packet_parser.cc new file mode 100644 index 0000000..f85ffc7 --- /dev/null +++ b/cloud_print/gcp20/prototype/dns_packet_parser.cc @@ -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. + +#include "cloud_print/gcp20/prototype/dns_packet_parser.h" + +#include "net/base/big_endian.h" + +DnsPacketParser::DnsPacketParser(const char* packet, size_t length) + : packet_(packet), + length_(length), + record_parser_(packet, length, sizeof(header_)) { + net::BigEndianReader reader(packet, length); + is_header_read_ = reader.ReadU16(&header_.id) && + reader.ReadU16(&header_.flags) && + reader.ReadU16(&header_.qdcount) && + reader.ReadU16(&header_.ancount) && + reader.ReadU16(&header_.nscount) && + reader.ReadU16(&header_.arcount); +} + +bool DnsPacketParser::ReadRecord(DnsQueryRecord* out) { + DCHECK(packet_); + DnsQueryRecord result; + size_t consumed = ReadName(&result.qname); + if (!consumed) + return false; + net::BigEndianReader reader(packet_ + GetOffset() + consumed, + length_ - (GetOffset() + consumed)); + if (reader.ReadU16(&result.qtype) && reader.ReadU16(&result.qclass) && + record_parser_.SkipQuestion()) { // instead of |cur_ = reader.ptr();| + *out = result; + return true; + } + + return false; +} + diff --git a/cloud_print/gcp20/prototype/dns_packet_parser.h b/cloud_print/gcp20/prototype/dns_packet_parser.h new file mode 100644 index 0000000..88c6a03 --- /dev/null +++ b/cloud_print/gcp20/prototype/dns_packet_parser.h @@ -0,0 +1,78 @@ +// 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 CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_ +#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_ + +#include <string> + +#include "net/dns/dns_protocol.h" +#include "net/dns/dns_response.h" + +// Parsed response record. +struct DnsQueryRecord { + DnsQueryRecord() : qtype(0), qclass(0) {} + ~DnsQueryRecord() {} + + std::string qname; // in dotted form + uint16 qtype; + uint16 qclass; +}; + +// Iterator to walk over records of the DNS response packet. Encapsulates +// |DnsRecordParser| object for using its functionality. +class DnsPacketParser { + public: + // Constructs an iterator to process the |packet| of given |length|. + DnsPacketParser(const char* packet, size_t length); + + // Destroys the object. + ~DnsPacketParser() {} + + bool IsValid() const { return record_parser_.IsValid() && is_header_read_; } + + // Returns |true| if no more bytes remain in the packet. + bool AtEnd() const { return record_parser_.AtEnd(); } + + // Returns header of DNS packet. + const net::dns_protocol::Header& header() const { return header_; } + + // Parses the next query record into |record|. Returns true if succeeded. + bool ReadRecord(DnsQueryRecord* record); + + // Parses the next resource record into |record|. Returns true if succeeded. + bool ReadRecord(net::DnsResourceRecord* record) { + return record_parser_.ReadRecord(record); + } + + private: + // Returns current offset into the packet. + size_t GetOffset() const { return record_parser_.GetOffset(); } + + // Parses a (possibly compressed) DNS name from the packet starting at + // |pos|. Stores output (even partial) in |out| unless |out| is NULL. |out| + // is stored in the dotted form, e.g., "example.com". Returns number of bytes + // consumed or 0 on failure. + // This is exposed to allow parsing compressed names within RRDATA for TYPEs + // such as NS, CNAME, PTR, MX, SOA. + // See RFC 1035 section 4.1.4. + unsigned ReadName(std::string* out) const { + return record_parser_.ReadName(packet_ + GetOffset(), out); + } + + const char* packet_; + size_t length_; + + // Contents parsed header_; + net::dns_protocol::Header header_; + bool is_header_read_; + + // Encapsulated parser. + net::DnsRecordParser record_parser_; + + DISALLOW_COPY_AND_ASSIGN(DnsPacketParser); +}; + +#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_PACKET_PARSER_H_ + diff --git a/cloud_print/gcp20/prototype/dns_response_builder.cc b/cloud_print/gcp20/prototype/dns_response_builder.cc new file mode 100644 index 0000000..2bcd43f --- /dev/null +++ b/cloud_print/gcp20/prototype/dns_response_builder.cc @@ -0,0 +1,153 @@ +// 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 "cloud_print/gcp20/prototype/dns_response_builder.h" + +#include "base/logging.h" +#include "net/base/big_endian.h" +#include "net/base/dns_util.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/dns_response.h" + +namespace { + +uint16 klass = net::dns_protocol::kClassIN; + +} // namespace + +DnsResponseRecord::DnsResponseRecord() : type(0), klass(0), ttl(0) { +} + +DnsResponseRecord::~DnsResponseRecord() { +} + +DnsResponseBuilder::DnsResponseBuilder(uint16 id) { + header_.id = id; + // TODO(maksymb): Check do we need AA flag enabled. + header_.flags = net::dns_protocol::kFlagResponse | + net::dns_protocol::kFlagAA; + header_.qdcount = 0; + header_.ancount = 0; + header_.nscount = 0; + header_.arcount = 0; +} + +void DnsResponseBuilder::AppendPtr(const std::string& service_type, uint32 ttl, + const std::string& service_name) { + std::string rdata; + bool success = net::DNSDomainFromDot(service_name, &rdata); + DCHECK(success); + + AddResponse(service_type, net::dns_protocol::kTypePTR, ttl, rdata); +} + +void DnsResponseBuilder::AppendSrv(const std::string& service_name, uint32 ttl, + uint16 priority, uint16 weight, + uint16 http_port, + const std::string& service_domain_name) { + std::string domain_name; + bool success = net::DNSDomainFromDot(service_domain_name, &domain_name); + DCHECK(success); + + std::vector<uint8> rdata(2 + 2 + 2 + domain_name.size()); + + net::BigEndianWriter writer(rdata.data(), rdata.size()); + success = writer.WriteU16(priority) && + writer.WriteU16(weight) && + writer.WriteU16(http_port) && + writer.WriteBytes(domain_name.data(), domain_name.size()); + DCHECK(success); + DCHECK_EQ(writer.remaining(), 0); // For warranty of correct size allocation. + + AddResponse(service_name, net::dns_protocol::kTypeSRV, ttl, + std::string(rdata.begin(), rdata.end())); +} + +void DnsResponseBuilder::AppendA(const std::string& service_domain_name, + uint32 ttl, net::IPAddressNumber http_ipv4) { + // TODO(maksymb): IP to send must depends on interface from where query was + // received. + if (http_ipv4.empty()) { + LOG(ERROR) << "Invalid IP"; + return; + } + + AddResponse(service_domain_name, net::dns_protocol::kTypeA, ttl, + std::string(http_ipv4.begin(), http_ipv4.end())); +} + +void DnsResponseBuilder::AppendTxt(const std::string& service_name, uint32 ttl, + const std::vector<std::string>& metadata) { + std::string rdata; + for (std::vector<std::string>::const_iterator str = metadata.begin(); + str != metadata.end(); ++str) { + int len = static_cast<int>(str->size()); + DCHECK_LT(len, 256); + rdata += static_cast<char>(len); // Set length byte. + rdata += *str; + } + + AddResponse(service_name, net::dns_protocol::kTypeTXT, ttl, rdata); +} + +scoped_refptr<net::IOBufferWithSize> DnsResponseBuilder::Build() { + size_t size = sizeof(header_); + for (std::vector<DnsResponseRecord>::const_iterator iter = responses_.begin(); + iter != responses_.end(); ++iter) { + size += iter->name.size() + 2 + // Two dots: first and last. + sizeof(iter->type) + sizeof(iter->klass) + sizeof(iter->ttl) + + 2 + // sizeof(RDLENGTH) + iter->rdata.size(); + } + + if (responses_.empty()) + return NULL; // No answer. + + header_.ancount = static_cast<uint16>(responses_.size()); + + scoped_refptr<net::IOBufferWithSize> message( + new net::IOBufferWithSize(static_cast<int>(size))); + net::BigEndianWriter writer(message->data(), message->size()); + bool success = writer.WriteU16(header_.id) && + writer.WriteU16(header_.flags) && + writer.WriteU16(header_.qdcount) && + writer.WriteU16(header_.ancount) && + writer.WriteU16(header_.nscount) && + writer.WriteU16(header_.arcount); + DCHECK(success); + + std::string name_in_dns_format; + for (std::vector<DnsResponseRecord>::const_iterator iter = responses_.begin(); + iter != responses_.end(); ++iter) { + success = net::DNSDomainFromDot(iter->name, &name_in_dns_format); + DCHECK(success); + DCHECK_EQ(name_in_dns_format.size(), iter->name.size() + 2); + + success = writer.WriteBytes(name_in_dns_format.data(), + name_in_dns_format.size()) && + writer.WriteU16(iter->type) && + writer.WriteU16(iter->klass) && + writer.WriteU32(iter->ttl) && + writer.WriteU16(static_cast<uint16>(iter->rdata.size())) && + writer.WriteBytes(iter->rdata.data(), iter->rdata.size()); + DCHECK(success); + } + + DCHECK_EQ(writer.remaining(), 0); // For warranty of correct size allocation. + + return message; +} + +void DnsResponseBuilder::AddResponse(const std::string& name, uint16 type, + uint32 ttl, const std::string& rdata) { + DnsResponseRecord response; + response.name = name; + response.klass = klass; + response.ttl = ttl; + response.type = type; + response.rdata = rdata; + responses_.push_back(response); +} + diff --git a/cloud_print/gcp20/prototype/dns_response_builder.h b/cloud_print/gcp20/prototype/dns_response_builder.h new file mode 100644 index 0000000..430a7e1 --- /dev/null +++ b/cloud_print/gcp20/prototype/dns_response_builder.h @@ -0,0 +1,68 @@ +// 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 CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_ +#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "net/base/net_util.h" +#include "net/dns/dns_protocol.h" + +namespace net { + class IOBufferWithSize; +} + +// Record for storing response data. +struct DnsResponseRecord { + DnsResponseRecord(); + ~DnsResponseRecord(); + + std::string name; // in dotted form + uint16 type; + uint16 klass; + uint32 ttl; + std::string rdata; +}; + +// Class for building service-specified responses. +class DnsResponseBuilder { + public: + // Initializes builder. + explicit DnsResponseBuilder(uint16 id); + + // Destroys the object. + ~DnsResponseBuilder() {} + + // Methods for appending different types of responses to packet. + void AppendPtr(const std::string& service_type, uint32 ttl, + const std::string& service_name); + void AppendSrv(const std::string& service_name, uint32 ttl, uint16 priority, + uint16 weight, uint16 http_port, + const std::string& service_domain_name); + void AppendA(const std::string& service_domain_name, uint32 ttl, + net::IPAddressNumber http_ipv4); + void AppendTxt(const std::string& service_name, uint32 ttl, + const std::vector<std::string>& metadata); + + // Serializes packet to byte sequence. + scoped_refptr<net::IOBufferWithSize> Build(); + + private: + // Appends response to packet. + void AddResponse(const std::string& name, uint16 type, uint32 ttl, + const std::string& rdata); + + std::vector<DnsResponseRecord> responses_; + + // Header of response package. + net::dns_protocol::Header header_; + + DISALLOW_COPY_AND_ASSIGN(DnsResponseBuilder); +}; + +#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_RESPONSE_BUILDER_H_ + diff --git a/cloud_print/gcp20/prototype/dns_sd_server.cc b/cloud_print/gcp20/prototype/dns_sd_server.cc index 0ca8b43..2d2716b 100644 --- a/cloud_print/gcp20/prototype/dns_sd_server.cc +++ b/cloud_print/gcp20/prototype/dns_sd_server.cc @@ -7,7 +7,15 @@ #include <string.h> #include "base/basictypes.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop.h" +#include "base/strings/stringprintf.h" +#include "cloud_print/gcp20/prototype/dns_packet_parser.h" +#include "cloud_print/gcp20/prototype/dns_response_builder.h" #include "net/base/big_endian.h" +#include "net/base/dns_util.h" +#include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/dns/dns_protocol.h" @@ -16,53 +24,91 @@ namespace { const char* kDefaultIpAddressMulticast = "224.0.0.251"; const uint16 kDefaultPortMulticast = 5353; -// TODO(maksymb): Add possibility to set constants via command line arguments -const uint32 kDefaultTTL = 60*60; // in seconds +const double kTimeToNextAnnouncement = 0.8; // relatively to TTL +const int kDnsBufSize = 65537; + +const uint16 kSrvPriority = 0; +const uint16 kSrvWeight = 0; + +void DoNothingAfterSendToSocket(int /*val*/) { + NOTREACHED(); + // TODO(maksymb): Delete this function once empty callback for SendTo() method + // will be allowed. +} } // namespace -DnsSdServer::DnsSdServer() : is_online_(false) { - // Do nothing +DnsSdServer::DnsSdServer() + : recv_buf_(new net::IOBufferWithSize(kDnsBufSize)), + full_ttl_(0) { } DnsSdServer::~DnsSdServer() { Shutdown(); } -bool DnsSdServer::Start() { - if (is_online_) +bool DnsSdServer::Start(const ServiceParameters& serv_params, uint32 full_ttl, + const std::vector<std::string>& metadata) { + if (IsOnline()) return true; if (!CreateSocket()) return false; + // Initializing server with parameters from arguments. + serv_params_ = serv_params; + full_ttl_ = full_ttl; + metadata_ = metadata; + LOG(INFO) << "DNS server started"; + LOG(WARNING) << "DNS server does not support probing"; - SendAnnouncement(kDefaultTTL); + SendAnnouncement(full_ttl_); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&DnsSdServer::OnDatagramReceived, base::Unretained(this))); - is_online_ = true; return true; } void DnsSdServer::Update() { - if (!is_online_) + if (!IsOnline()) return; - SendAnnouncement(kDefaultTTL); + SendAnnouncement(full_ttl_); } void DnsSdServer::Shutdown() { - if (!is_online_) + if (!IsOnline()) return; - SendAnnouncement(0); // ttl is 0 + SendAnnouncement(0); // TTL is 0 socket_->Close(); - is_online_ = false; + socket_.reset(NULL); LOG(INFO) << "DNS server stopped"; } -void DnsSdServer::ProcessMessages() { - NOTIMPLEMENTED(); // implement this +void DnsSdServer::UpdateMetadata(const std::vector<std::string>& metadata) { + if (!IsOnline()) + return; + + metadata_ = metadata; + + // TODO(maksymb): If less than 20% of full TTL left before next announcement + // then send it now. + + uint32 current_ttl = GetCurrentTLL(); + if (!CommandLine::ForCurrentProcess()->HasSwitch("no-announcement")) { + DnsResponseBuilder builder(current_ttl); + + builder.AppendTxt(serv_params_.service_name_, current_ttl, metadata_); + scoped_refptr<net::IOBufferWithSize> buffer(builder.Build()); + + DCHECK(buffer.get() != NULL); + + socket_->SendTo(buffer.get(), buffer.get()->size(), multicast_address_, + base::Bind(&DoNothingAfterSendToSocket)); + } } bool DnsSdServer::CreateSocket() { @@ -77,8 +123,7 @@ bool DnsSdServer::CreateSocket() { socket_.reset(new net::UDPSocket(net::DatagramSocket::DEFAULT_BIND, - net::RandIntCallback(), - NULL, + net::RandIntCallback(), NULL, net::NetLog::Source())); net::IPEndPoint local_address = net::IPEndPoint(local_ip_any, @@ -103,35 +148,153 @@ bool DnsSdServer::CreateSocket() { return true; } -bool DnsSdServer::CheckPendingQueries() { - NOTIMPLEMENTED(); // implement this - return false; +void DnsSdServer::ProcessMessage(int len, net::IOBufferWithSize* buf) { + VLOG(1) << "Received new message with length: " << len; + + // Parse the message. + DnsPacketParser parser(buf->data(), len); + + if (!parser.IsValid()) // Was unable to parse header. + return; + + // TODO(maksymb): Handle truncated messages. + if (parser.header().flags & net::dns_protocol::kFlagResponse) // Not a query. + return; + + DnsResponseBuilder builder(parser.header().id); + + uint32 current_ttl = GetCurrentTLL(); + + DnsQueryRecord query; + // TODO(maksymb): Check known answers. + for (int query_idx = 0; query_idx < parser.header().qdcount; ++query_idx) { + bool success = parser.ReadRecord(&query); + if (success) { + ProccessQuery(current_ttl, query, &builder); + } else { // if (success) + LOG(INFO) << "Broken package"; + break; + } + } + + scoped_refptr<net::IOBufferWithSize> buffer(builder.Build()); + if (buffer.get() == NULL) + return; // No answers. + + VLOG(1) << "Current TTL for respond: " << current_ttl; + + bool multicast_respond = + CommandLine::ForCurrentProcess()->HasSwitch("multicast-respond"); + socket_->SendTo(buffer.get(), buffer.get()->size(), + multicast_respond ? multicast_address_ : recv_address_, + base::Bind(&DoNothingAfterSendToSocket)); + VLOG(1) << "Responded to " + << (multicast_respond ? multicast_address_ : recv_address_).ToString(); } -void DoNothing(int /*var*/) { - // Do nothing +void DnsSdServer::ProccessQuery(uint32 current_ttl, const DnsQueryRecord& query, + DnsResponseBuilder* builder) const { + std::string log; + bool responded = false; + switch (query.qtype) { + // TODO(maksymb): Add IPv6 support. + case net::dns_protocol::kTypePTR: + log = "Processing PTR query"; + if (query.qname == serv_params_.service_type_) { + builder->AppendPtr(serv_params_.service_type_, current_ttl, + serv_params_.service_name_); + responded = true; + } + break; + case net::dns_protocol::kTypeSRV: + log = "Processing SRV query"; + if (query.qname == serv_params_.service_name_) { + builder->AppendSrv(serv_params_.service_name_, current_ttl, + kSrvPriority, kSrvWeight, serv_params_.http_port_, + serv_params_.service_domain_name_); + responded = true; + } + break; + case net::dns_protocol::kTypeA: + log = "Processing A query"; + if (query.qname == serv_params_.service_domain_name_) { + builder->AppendA(serv_params_.service_domain_name_, current_ttl, + serv_params_.http_ipv4_); + responded = true; + } + break; + case net::dns_protocol::kTypeTXT: + log = "Processing TXT query"; + if (query.qname == serv_params_.service_name_) { + builder->AppendTxt(serv_params_.service_name_, current_ttl, metadata_); + responded = true; + } + break; + default: + base::SStringPrintf(&log, "Unknown query type (%d)", query.qtype); + } + log += responded ? ": responded" : ": ignored"; + VLOG(1) << log; +} + +void DnsSdServer::DoLoop(int rv) { + // TODO(maksymb): Check what happened if buffer will be overflowed + do { + if (rv > 0) + ProcessMessage(rv, recv_buf_); + rv = socket_->RecvFrom(recv_buf_, recv_buf_->size(), &recv_address_, + base::Bind(&DnsSdServer::DoLoop, + base::Unretained(this))); + } while (rv > 0); + + // TODO(maksymb): Add handler for errors + DCHECK(rv == net::ERR_IO_PENDING); +} + +void DnsSdServer::OnDatagramReceived() { + DoLoop(0); } void DnsSdServer::SendAnnouncement(uint32 ttl) { - // Create a message with allocated space for header. - // DNS header is temporary empty. - scoped_ptr<std::vector<uint8> > message( - new std::vector<uint8>(sizeof(net::dns_protocol::Header), 0)); // all is 0 - - // TODO(maksymb): Create and implement DnsResponse class - - // Preparing for sending - scoped_refptr<net::IOBufferWithSize> buffer = - new net::IOBufferWithSize(static_cast<int>(message.get()->size())); - memcpy(buffer.get()->data(), message.get()->data(), message.get()->size()); - - // Create empty callback (we don't need it at all) and send packet - net::CompletionCallback callback = base::Bind(DoNothing); - socket_->SendTo(buffer.get(), - buffer.get()->size(), - multicast_address_, - callback); - - LOG(INFO) << "Announcement was sent with TTL: " << ttl; + if (!CommandLine::ForCurrentProcess()->HasSwitch("no-announcement")) { + DnsResponseBuilder builder(ttl); + + builder.AppendPtr(serv_params_.service_type_, ttl, + serv_params_.service_name_); + builder.AppendSrv(serv_params_.service_name_, ttl, kSrvPriority, kSrvWeight, + serv_params_.http_port_, + serv_params_.service_domain_name_); + builder.AppendA(serv_params_.service_domain_name_, ttl, + serv_params_.http_ipv4_); + builder.AppendTxt(serv_params_.service_name_, ttl, metadata_); + scoped_refptr<net::IOBufferWithSize> buffer(builder.Build()); + + DCHECK(buffer.get() != NULL); + + socket_->SendTo(buffer.get(), buffer.get()->size(), multicast_address_, + base::Bind(&DoNothingAfterSendToSocket)); + + VLOG(1) << "Announcement was sent with TTL: " << ttl; + } + + time_until_live_ = base::Time::Now() + + base::TimeDelta::FromSeconds(full_ttl_); + + // Schedule next announcement. + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&DnsSdServer::Update, base::Unretained(this)), + base::TimeDelta::FromSeconds(static_cast<int64>( + kTimeToNextAnnouncement*full_ttl_))); +} + +uint32 DnsSdServer::GetCurrentTLL() const { + uint32 current_ttl = (time_until_live_ - base::Time::Now()).InSeconds(); + if (time_until_live_ < base::Time::Now() || current_ttl == 0) { + // This should not be reachable. But still we don't need to fail. + current_ttl = 1; // Service is still alive. + LOG(ERROR) << "|current_ttl| was equal to zero."; + } + return current_ttl; } diff --git a/cloud_print/gcp20/prototype/dns_sd_server.h b/cloud_print/gcp20/prototype/dns_sd_server.h index 9c8ed96..d5ef793 100644 --- a/cloud_print/gcp20/prototype/dns_sd_server.h +++ b/cloud_print/gcp20/prototype/dns_sd_server.h @@ -2,27 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef GCP20_PROTOTYPE_DNS_SD_H_ -#define GCP20_PROTOTYPE_DNS_SD_H_ +#ifndef CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_ +#define CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_ +#include <string> #include <vector> -#include "net/dns/dns_protocol.h" +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "cloud_print/gcp20/prototype/service_parameters.h" +#include "net/base/ip_endpoint.h" #include "net/udp/udp_socket.h" +namespace net { + class IOBufferWithSize; +} + +struct DnsQueryRecord; +class DnsResponseBuilder; + // Class for sending multicast announcements, receiving queries and answering on -// them. Client should call |ProccessMessages| periodically to make server work. +// them. +// TODO(maksymb): Implement probing. class DnsSdServer { public: - // Constructs unstarted server. + // Constructor does not start server. DnsSdServer(); - // Stops server. + // Stops the server and destroys the object. ~DnsSdServer(); // Starts the server. Returns |true| if server works. Also sends // announcement. - bool Start(); + bool Start(const ServiceParameters& serv_params, uint32 full_ttl, + const std::vector<std::string>& metadata) WARN_UNUSED_RESULT; // Sends announcement if server works. void Update(); @@ -30,24 +43,35 @@ class DnsSdServer { // Stops server with announcement. void Shutdown(); - // Process pending queries for the server. - void ProcessMessages(); - // Returns |true| if server works. - bool is_online() { return is_online_; } + bool IsOnline() { return !!socket_; } + + // Updates data for TXT respond. + void UpdateMetadata(const std::vector<std::string>& metadata); private: // Binds a socket to multicast address. Returns |true| on success. bool CreateSocket(); + // Processes single query. + void ProccessQuery(uint32 current_ttl, const DnsQueryRecord& query, + DnsResponseBuilder* builder) const; + + // Processes DNS message. + void ProcessMessage(int len, net::IOBufferWithSize* buf); + + // CompletionCallback for receiving data from DNS. + void DoLoop(int rv); + + // Function to start listening to socket (delegate to DoLoop function). + void OnDatagramReceived(); + // Sends announcement. void SendAnnouncement(uint32 ttl); - // Returns |true| if server received some questions. - bool CheckPendingQueries(); - - // Stores |true| if server was started. - bool is_online_; + // Calculates and returns current TTL (with accordance to last send + // announcement time. + uint32 GetCurrentTLL() const; // Stores socket to multicast address. scoped_ptr<net::UDPSocket> socket_; @@ -55,8 +79,26 @@ class DnsSdServer { // Stores multicast address end point. net::IPEndPoint multicast_address_; + // Stores time until last announcement is live. + base::Time time_until_live_; + + // Stores service parameters (like service-name and service-type etc.) + ServiceParameters serv_params_; + + // Stores the buffer for receiving messages. + scoped_refptr<net::IOBufferWithSize> recv_buf_; + + // Stores address from where last message was sent. + net::IPEndPoint recv_address_; + + // Stores information for TXT respond. + std::vector<std::string> metadata_; + + // TTL for announcements + uint32 full_ttl_; + DISALLOW_COPY_AND_ASSIGN(DnsSdServer); }; -#endif // GCP20_PROTOTYPE_DNS_SD_H_ +#endif // CLOUD_PRINT_GCP20_PROTOTYPE_DNS_SD_SERVER_H_ diff --git a/cloud_print/gcp20/prototype/gcp20_device.cc b/cloud_print/gcp20/prototype/gcp20_device.cc index 2ff1a2d..4eab248 100644 --- a/cloud_print/gcp20/prototype/gcp20_device.cc +++ b/cloud_print/gcp20/prototype/gcp20_device.cc @@ -2,12 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <stdio.h> +#include <signal.h> +#include "base/at_exit.h" +#include "base/bind.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/message_loop.h" +#include "base/run_loop.h" #include "base/threading/platform_thread.h" -#include "cloud_print/gcp20/prototype/dns_sd_server.h" +#include "base/time.h" +#include "cloud_print/gcp20/prototype/printer.h" + +namespace { + +Printer printer; +base::AtExitManager at_exit; + +void StopPrinter() { + printer.Stop(); +} + +void StartPrinter() { + bool success = printer.Start(); + DCHECK(success); + + at_exit.RegisterTask(base::Bind(&StopPrinter)); +} + +void OnAbort(int val) { + StopPrinter(); + base::MessageLoop::current()->Quit(); +} + +} // namespace int main(int argc, char* argv[]) { CommandLine::Init(argc, argv); @@ -16,11 +44,12 @@ int main(int argc, char* argv[]) { settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; logging::InitLogging(settings); - DnsSdServer dns_sd_server; - dns_sd_server.Start(); - base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2)); - dns_sd_server.Shutdown(); + signal(SIGINT, OnAbort); // Handle Ctrl+C signal. + + base::MessageLoop loop(base::MessageLoop::TYPE_IO); + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&StartPrinter)); + base::RunLoop runner; + runner.Run(); return 0; } - diff --git a/cloud_print/gcp20/prototype/gcp20_device.gyp b/cloud_print/gcp20/prototype/gcp20_device.gyp index 7946960..8324b8b 100644 --- a/cloud_print/gcp20/prototype/gcp20_device.gyp +++ b/cloud_print/gcp20/prototype/gcp20_device.gyp @@ -16,14 +16,33 @@ }, 'targets': [ { - 'target_name': 'gcp20_device', - 'type': 'executable', + 'target_name': 'gcp20_device_lib', + 'type': 'static_library', 'dependencies': [ '<(DEPTH)/base/base.gyp:base', '<(DEPTH)/net/net.gyp:net', ], 'sources': [ + 'dns_packet_parser.cc', + 'dns_packet_parser.h', + 'dns_response_builder.cc', + 'dns_response_builder.h', 'dns_sd_server.cc', + 'dns_sd_server.h', + 'gcp20_device.cc', + 'printer.cc', + 'printer.h', + 'service_parameters.cc', + 'service_parameters.h', + ], + }, + { + 'target_name': 'gcp20_device', + 'type': 'executable', + 'dependencies': [ + 'gcp20_device.gyp:gcp20_device_lib', + ], + 'sources': [ 'gcp20_device.cc', ], 'msvs_settings': { diff --git a/cloud_print/gcp20/prototype/printer.cc b/cloud_print/gcp20/prototype/printer.cc new file mode 100644 index 0000000..4d39fe8 --- /dev/null +++ b/cloud_print/gcp20/prototype/printer.cc @@ -0,0 +1,131 @@ +// 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 "cloud_print/gcp20/prototype/printer.h" + +#include <string> +#include <vector> + +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "cloud_print/gcp20/prototype/service_parameters.h" +#include "net/base/net_util.h" + +namespace { + +const char* kServiceType = "_privet._tcp.local"; +const char* kServiceNamePrefix = "first_gcp20_device"; +const char* kServiceDomainName = "my-privet-device.local"; + +const uint16 kDefaultTTL = 60*60; +const uint16 kDefaultHttpPort = 10101; + +uint16 ReadHttpPortFromCommandLine() { + uint32 http_port_tmp = kDefaultHttpPort; + + std::string http_port_string_tmp = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII("http-port"); + base::StringToUint(http_port_string_tmp, &http_port_tmp); + + if (http_port_tmp > kuint16max) { + LOG(ERROR) << "Port " << http_port_tmp << " is too large (maximum is " << + kDefaultHttpPort << "). Using default port."; + + http_port_tmp = kDefaultHttpPort; + } + + VLOG(1) << "HTTP port for responses: " << http_port_tmp; + return static_cast<uint16>(http_port_tmp); +} + +uint16 ReadTtlFromCommandLine() { + uint32 ttl = kDefaultTTL; + + base::StringToUint( + CommandLine::ForCurrentProcess()->GetSwitchValueASCII("ttl"), &ttl); + + VLOG(1) << "TTL for announcements: " << ttl; + return ttl; +} + +// Returns local IP address number of first interface found (except loopback). +// Return value is empty if no interface found. Possible interfaces names are +// "eth0", "wlan0" etc. If interface name is empty, function will return IP +// address of first interface found. +net::IPAddressNumber GetLocalIp(const std::string& interface_name, + bool return_ipv6_number) { + net::NetworkInterfaceList interfaces; + bool success = net::GetNetworkList(&interfaces); + DCHECK(success); + + size_t expected_address_size = return_ipv6_number ? net::kIPv6AddressSize + : net::kIPv4AddressSize; + + for (net::NetworkInterfaceList::iterator iter = interfaces.begin(); + iter != interfaces.end(); ++iter) { + if (iter->address.size() == expected_address_size && + (interface_name.empty() || interface_name == iter->name)) { + LOG(INFO) << net::IPAddressToString(iter->address); + return iter->address; + } + } + + return net::IPAddressNumber(); +} + +} // namespace + +Printer::Printer() : initialized_(false) { +} + +Printer::~Printer() { + Stop(); +} + +bool Printer::Start() { + if (initialized_) + return true; + + // TODO(maksymb): Add switch for command line to control interface name. + net::IPAddressNumber ip = GetLocalIp("", false); + if (ip.empty()) { + LOG(ERROR) << "No local IP found. Cannot start printer."; + return false; + } + VLOG(1) << "Local address: " << net::IPAddressToString(ip); + + // Starting DNS-SD server. + initialized_ = dns_server_.Start( + ServiceParameters(kServiceType, + kServiceNamePrefix, + kServiceDomainName, + ip, + ReadHttpPortFromCommandLine()), + ReadTtlFromCommandLine(), + CreateTxt()); + + return initialized_; +} + +void Printer::Stop() { + if (!initialized_) + return; + + dns_server_.Shutdown(); +} + +std::vector<std::string> Printer::CreateTxt() const { + std::vector<std::string> txt; + + txt.push_back("txtvers=1"); + txt.push_back("ty=Google Cloud Ready Printer GCP2.0 Prototype"); + txt.push_back("note=Virtual printer"); + txt.push_back("url=https://www.google.com/cloudprint"); + txt.push_back("type=printer"); + txt.push_back("id="); + txt.push_back("cs=offline"); + + return txt; +} + diff --git a/cloud_print/gcp20/prototype/printer.h b/cloud_print/gcp20/prototype/printer.h new file mode 100644 index 0000000..d2d72b6 --- /dev/null +++ b/cloud_print/gcp20/prototype/printer.h @@ -0,0 +1,42 @@ +// 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 GCP20_PROTOTYPE_PRINTER_H_ +#define GCP20_PROTOTYPE_PRINTER_H_ + +#include <string> +#include <vector> + +#include "cloud_print/gcp20/prototype/dns_sd_server.h" + +// This class will maintain work of DNS-SD server, HTTP server and others. +class Printer { + public: + // Constructs uninitialized object. + Printer(); + + // Destroys the object. + ~Printer(); + + // Starts all servers. + bool Start(); + + // Stops all servers. + void Stop(); + + private: + // Creates data for DNS TXT respond. + std::vector<std::string> CreateTxt() const; + + // Contains |true| if initialized. + bool initialized_; + + // Contains DNS-SD server. + DnsSdServer dns_server_; + + DISALLOW_COPY_AND_ASSIGN(Printer); +}; + +#endif // GCP20_PROTOTYPE_PRINTER_H_ + diff --git a/cloud_print/gcp20/prototype/service_parameters.cc b/cloud_print/gcp20/prototype/service_parameters.cc new file mode 100644 index 0000000..e32581e --- /dev/null +++ b/cloud_print/gcp20/prototype/service_parameters.cc @@ -0,0 +1,22 @@ +// 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 "cloud_print/gcp20/prototype/service_parameters.h" + +ServiceParameters::ServiceParameters() : http_port_(0) { +} + +ServiceParameters::ServiceParameters(const std::string& service_type, + const std::string& service_name_prefix, + const std::string& service_domain_name, + const net::IPAddressNumber& http_ipv4, + uint16 http_port) + : service_type_(service_type), + service_name_(service_name_prefix + "." + service_type), + service_domain_name_(service_domain_name), + http_ipv4_(http_ipv4), + http_port_(http_port) { +} + + diff --git a/cloud_print/gcp20/prototype/service_parameters.h b/cloud_print/gcp20/prototype/service_parameters.h new file mode 100644 index 0000000..79582ad --- /dev/null +++ b/cloud_print/gcp20/prototype/service_parameters.h @@ -0,0 +1,32 @@ +// 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 CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_ +#define CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_ + +#include <string> + +#include "net/base/net_util.h" + +// Stores information about service. +struct ServiceParameters { + ServiceParameters(); + + ~ServiceParameters() {} + + ServiceParameters(const std::string& service_type, + const std::string& service_name_prefix, + const std::string& service_domain_name, + const net::IPAddressNumber& http_ipv4, + uint16 http_port); + + std::string service_type_; + std::string service_name_; + std::string service_domain_name_; + net::IPAddressNumber http_ipv4_; + uint16 http_port_; +}; + +#endif // CLOUD_PRINT_GCP20_PROTOTYPE_SERVICE_PARAMETERS_H_ + |