blob: 3ad43c00637894b169b8c450214c1f67522a5316 [file] [log] [blame]
// 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/dial/dial_udp_server.h"
#if defined(STARBOARD)
#include "starboard/client_porting/poem/inet_poem.h"
#else
#include <arpa/inet.h>
#endif
#include <memory>
#include <utility>
#include <vector>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/hash.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_string_util.h"
#include "net/dial/dial_system_config.h"
#include "net/dial/dial_udp_socket_factory.h"
#include "net/server/http_server.h"
#include "net/server/http_server_request_info.h"
#include "starboard/types.h"
namespace net {
namespace { // anonymous
const char* kDialStRequest = "urn:dial-multiscreen-org:service:dial:1";
const int kReadBufferSize = 500 * 1024;
// Get the INADDR_ANY address.
IPEndPoint GetAddressForAllInterfaces(unsigned short port) {
#if defined(STARBOARD)
return IPEndPoint::GetForAllInterfaces(port);
#else
SockaddrStorage any_addr;
struct sockaddr_in* in = (struct sockaddr_in*)any_addr.addr;
in->sin_family = AF_INET;
in->sin_port = htons(port);
in->sin_addr.s_addr = INADDR_ANY;
IPEndPoint addr;
ignore_result(addr.FromSockAddr(any_addr.addr, any_addr.addr_len));
return addr;
#endif // !defined(STARBOARD)
}
} // namespace
DialUdpServer::DialUdpServer(const std::string& location_url,
const std::string& server_agent)
: factory_(new DialUdpSocketFactory()),
location_url_(location_url),
server_agent_(server_agent),
thread_("dial_udp_server"),
is_running_(false),
read_buf_(new IOBuffer(kReadBufferSize)) {
DCHECK(!location_url_.empty());
DETACH_FROM_THREAD(thread_checker_);
thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
Start();
}
DialUdpServer::~DialUdpServer() {
Stop();
}
void DialUdpServer::CreateAndBind() {
DCHECK_EQ(thread_.message_loop(), base::MessageLoop::current());
// The thread_checker_ will bind to thread_ from this point on.
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
socket_ = factory_->CreateAndBind(GetAddressForAllInterfaces(1900));
if (!socket_) {
LOG(WARNING) << "Failed to bind socket for DIAL UDP Server";
return;
}
AcceptAndProcessConnection();
}
void DialUdpServer::Shutdown() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
location_url_.clear();
socket_.reset();
factory_.reset();
is_running_ = false;
}
void DialUdpServer::Start() {
DCHECK(!is_running_);
is_running_ = true;
thread_.message_loop()->task_runner()->PostTask(
FROM_HERE,
base::Bind(&DialUdpServer::CreateAndBind, base::Unretained(this)));
}
void DialUdpServer::Stop() {
DCHECK(is_running_);
thread_.message_loop()->task_runner()->PostBlockingTask(
FROM_HERE, base::Bind(&DialUdpServer::Shutdown, base::Unretained(this)));
thread_.Stop();
}
void DialUdpServer::AcceptAndProcessConnection() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!is_running_ || !socket_.get()) {
return;
}
auto err_code = socket_->RecvFrom(
read_buf_.get(), kReadBufferSize, &client_address_,
base::Bind(&DialUdpServer::DidRead, base::Unretained(this)));
if (err_code > 0) {
// RecvFrom can also return the number of received bytes right away as well.
DidRead(err_code);
} else if (err_code != ERR_IO_PENDING) {
DCHECK(err_code == OK) << "RecvFrom returned bad error code: " << err_code;
}
// otherwise, RecvFrom returned -1 and will execute DidRead when any data is
// received.
}
void DialUdpServer::DidClose(UDPSocket* server) {}
void DialUdpServer::DidRead(int bytes_read) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!socket_ || !read_buf_.get() || !read_buf_->data()) {
return;
}
if (bytes_read <= 0) {
LOG(WARNING) << "Dial server socket read error: " << bytes_read;
return;
}
// If M-Search request was valid, send response. Else, keep quiet.
if (ParseSearchRequest(std::string(read_buf_->data()))) {
auto response = std::make_unique<std::string>();
*response = std::move(ConstructSearchResponse());
// Using the fake IOBuffer to avoid another copy.
scoped_refptr<WrappedIOBuffer> fake_buffer =
new WrappedIOBuffer(response->data());
// After optimization, some compiler will dereference and get response size
// later than passing response.
auto response_size = response->size();
int result = socket_->SendTo(
fake_buffer.get(), response_size, client_address_,
base::Bind(&DialUdpServer::WriteComplete, base::Unretained(this),
fake_buffer, base::Passed(&response)));
if (result == ERR_IO_PENDING) {
// WriteComplete is responsible for posting the next callback to accept
// connection.
return;
} else if (result < 0) {
LOG(ERROR) << "UDPSocket SendTo error: " << result;
}
}
// Register a watcher on the message loop and wait for the next dial message.
// If we call AcceptAndProcessConnection directly, the function could call
// DidRead and quickly increase stack size or even loop infinitely if the
// socket can always provide messages through RecvFrom.
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&DialUdpServer::AcceptAndProcessConnection,
base::Unretained(this)));
}
void DialUdpServer::WriteComplete(scoped_refptr<WrappedIOBuffer>,
std::unique_ptr<std::string>,
int rv) {
if (rv < 0) {
LOG(ERROR) << "UDPSocket completion callback error: " << rv;
}
thread_.task_runner()->PostTask(
FROM_HERE, base::Bind(&DialUdpServer::AcceptAndProcessConnection,
base::Unretained(this)));
}
// Parse a request to make sure it is a M-Search.
bool DialUdpServer::ParseSearchRequest(const std::string& request) {
HttpServerRequestInfo info;
if (!HttpServer::ParseHeaders(request, &info)) {
DVLOG(1) << "Failed parsing SSDP headers: " << request;
return false;
}
if (!IsValidMSearchRequest(info)) {
return false;
}
// TODO[Starboard]: verify that this st header is present in dial search
// request. The header name used to be "ST".
std::string st_request = info.GetHeaderValue("st");
ignore_result(TrimWhitespaceASCII(st_request, base::TRIM_ALL, &st_request));
if (st_request != kDialStRequest) {
DVLOG(1) << "Received incorrect ST headers: " << st_request;
return false;
}
DVLOG(1) << "Dial User-Agent: " << info.GetHeaderValue("user-agent");
return true;
}
bool DialUdpServer::IsValidMSearchRequest(const HttpServerRequestInfo& info) {
if (info.method != "M-SEARCH") {
DVLOG(1) << "Invalid M-Search: SSDP method incorrect. Received method: "
<< info.method;
return false;
}
if (info.path != "*") {
DVLOG(1) << "Invalid M-Search: SSDP path incorrect.";
return false;
}
if (!info.data.empty()) {
DVLOG(1) << "Invalid M-Search: SSDP request contains a body.";
return false;
}
return true;
}
// Since we are constructing a response from user-generated string,
// ensure all user-generated strings pass through StringPrintf.
std::string DialUdpServer::ConstructSearchResponse() const {
DCHECK(!location_url_.empty());
std::string ret("HTTP/1.1 200 OK\r\n");
ret.append(base::StringPrintf("LOCATION: %s\r\n", location_url_.c_str()));
ret.append("CACHE-CONTROL: max-age=1800\r\n");
ret.append("EXT:\r\n");
ret.append("BOOTID.UPNP.ORG: 1\r\n");
// CONFIGID represents the state of the device description. Can be any
// non-negative integer from 0 to 2^24 - 1.
// DIAL clients like the Cast extension will cache the response based on this,
// so we ensure each location change has a different config id.
// This way when the app restarts with a new IP or port, the client updates
// its cache of DIAL devices accordingly.
uint32 location_hash = base::Hash(location_url_) >> 8;
ret.append(base::StringPrintf("CONFIGID.UPNP.ORG: %u\r\n", location_hash));
ret.append(base::StringPrintf("SERVER: %s\r\n", server_agent_.c_str()));
ret.append(base::StringPrintf("ST: %s\r\n", kDialStRequest));
ret.append(base::StringPrintf("USN: uuid:%s::%s\r\n",
DialSystemConfig::GetInstance()->model_uuid(),
kDialStRequest));
ret.append("\r\n");
LOG_ONCE(INFO) << "In-App DIAL Discovery response : " << ret;
return std::move(ret);
}
} // namespace net