blob: 2eda85e0382aa3adb51ff720305d8ea4a06e7015 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "shill/http_proxy.h"
#include <errno.h>
#include <netinet/in.h>
#include <linux/if.h> // Needs definitions from netinet/in.h
#include <stdio.h>
#include <time.h>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "shill/async_connection.h"
#include "shill/connection.h"
#include "shill/dns_client.h"
#include "shill/event_dispatcher.h"
#include "shill/ip_address.h"
#include "shill/logging.h"
#include "shill/sockets.h"
using base::Bind;
using base::StringPrintf;
using std::string;
using std::vector;
namespace shill {
const int HTTPProxy::kClientHeaderTimeoutSeconds = 1;
const int HTTPProxy::kConnectTimeoutSeconds = 10;
const int HTTPProxy::kDNSTimeoutSeconds = 5;
const int HTTPProxy::kDefaultServerPort = 80;
const int HTTPProxy::kInputTimeoutSeconds = 30;
const size_t HTTPProxy::kMaxClientQueue = 10;
const size_t HTTPProxy::kMaxHeaderCount = 128;
const size_t HTTPProxy::kMaxHeaderSize = 2048;
const int HTTPProxy::kTransactionTimeoutSeconds = 600;
const char HTTPProxy::kHTTPMethodConnect[] = "connect";
const char HTTPProxy::kHTTPMethodTerminator[] = " ";
const char HTTPProxy::kHTTPURLDelimiters[] = " /#?";
const char HTTPProxy::kHTTPURLPrefix[] = "http://";
const char HTTPProxy::kHTTPVersionPrefix[] = " HTTP/1";
const char HTTPProxy::kInternalErrorMsg[] = "Proxy Failed: Internal Error";
HTTPProxy::HTTPProxy(ConnectionRefPtr connection)
: state_(kStateIdle),
connection_(connection),
weak_ptr_factory_(this),
accept_callback_(Bind(&HTTPProxy::AcceptClient,
weak_ptr_factory_.GetWeakPtr())),
connect_completion_callback_(Bind(&HTTPProxy::OnConnectCompletion,
weak_ptr_factory_.GetWeakPtr())),
dns_client_callback_(Bind(&HTTPProxy::GetDNSResult,
weak_ptr_factory_.GetWeakPtr())),
read_client_callback_(Bind(&HTTPProxy::ReadFromClient,
weak_ptr_factory_.GetWeakPtr())),
read_server_callback_(Bind(&HTTPProxy::ReadFromServer,
weak_ptr_factory_.GetWeakPtr())),
write_client_callback_(Bind(&HTTPProxy::WriteToClient,
weak_ptr_factory_.GetWeakPtr())),
write_server_callback_(Bind(&HTTPProxy::WriteToServer,
weak_ptr_factory_.GetWeakPtr())),
dispatcher_(NULL),
proxy_port_(-1),
proxy_socket_(-1),
sockets_(NULL),
client_socket_(-1),
server_port_(kDefaultServerPort),
server_socket_(-1),
is_route_requested_(false) { }
HTTPProxy::~HTTPProxy() {
Stop();
}
bool HTTPProxy::Start(EventDispatcher *dispatcher,
Sockets *sockets) {
SLOG(HTTPProxy, 3) << "In " << __func__;
if (sockets_) {
// We are already running.
return true;
}
proxy_socket_ = sockets->Socket(PF_INET, SOCK_STREAM, 0);
if (proxy_socket_ < 0) {
PLOG(ERROR) << "Failed to open proxy socket";
return false;
}
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (sockets->Bind(proxy_socket_,
reinterpret_cast<struct sockaddr *>(&addr),
sizeof(addr)) < 0 ||
sockets->GetSockName(proxy_socket_,
reinterpret_cast<struct sockaddr *>(&addr),
&addrlen) < 0 ||
sockets->SetNonBlocking(proxy_socket_) < 0 ||
sockets->Listen(proxy_socket_, kMaxClientQueue) < 0) {
sockets->Close(proxy_socket_);
proxy_socket_ = -1;
PLOG(ERROR) << "HTTPProxy socket setup failed";
return false;
}
accept_handler_.reset(
dispatcher->CreateReadyHandler(proxy_socket_, IOHandler::kModeInput,
accept_callback_));
dispatcher_ = dispatcher;
dns_client_.reset(new DNSClient(IPAddress::kFamilyIPv4,
connection_->interface_name(),
connection_->dns_servers(),
kDNSTimeoutSeconds * 1000,
dispatcher,
dns_client_callback_));
proxy_port_ = ntohs(addr.sin_port);
server_async_connection_.reset(
new AsyncConnection(connection_->interface_name(), dispatcher, sockets,
connect_completion_callback_));
sockets_ = sockets;
state_ = kStateWaitConnection;
return true;
}
void HTTPProxy::Stop() {
SLOG(HTTPProxy, 3) << "In " << __func__;
if (!sockets_) {
return;
}
StopClient();
accept_handler_.reset();
dispatcher_ = NULL;
dns_client_.reset();
proxy_port_ = -1;
server_async_connection_.reset();
sockets_->Close(proxy_socket_);
proxy_socket_ = -1;
sockets_ = NULL;
state_ = kStateIdle;
}
// IOReadyHandler callback routine fired when a client connects to the
// proxy's socket. We Accept() the client and start reading a request
// from it.
void HTTPProxy::AcceptClient(int fd) {
SLOG(HTTPProxy, 3) << "In " << __func__;
int client_fd = sockets_->Accept(fd, NULL, NULL);
if (client_fd < 0) {
PLOG(ERROR) << "Client accept failed";
return;
}
accept_handler_->Stop();
client_socket_ = client_fd;
sockets_->SetNonBlocking(client_socket_);
read_client_handler_.reset(dispatcher_->CreateInputHandler(
client_socket_,
read_client_callback_,
Bind(&HTTPProxy::OnReadError, weak_ptr_factory_.GetWeakPtr())));
// Overall transaction timeout.
transaction_timeout_.Reset(Bind(&HTTPProxy::StopClient,
weak_ptr_factory_.GetWeakPtr()));
dispatcher_->PostDelayedTask(transaction_timeout_.callback(),
kTransactionTimeoutSeconds * 1000);
state_ = kStateReadClientHeader;
StartIdleTimeout();
}
bool HTTPProxy::ConnectServer(const IPAddress &address, int port) {
state_ = kStateConnectServer;
if (!server_async_connection_->Start(address, port)) {
SendClientError(500, "Could not create socket to connect to server");
return false;
}
StartIdleTimeout();
return true;
}
// DNSClient callback that fires when the DNS request completes.
void HTTPProxy::GetDNSResult(const Error &error, const IPAddress &address) {
if (!error.IsSuccess()) {
SendClientError(502, string("Could not resolve hostname: ") +
error.message());
return;
}
ConnectServer(address, server_port_);
}
// IOReadyHandler callback routine which fires when the asynchronous Connect()
// to the remote server completes (or fails).
void HTTPProxy::OnConnectCompletion(bool success, int fd) {
if (!success) {
SendClientError(500, string("Socket connection delayed failure: ") +
server_async_connection_->error());
return;
}
server_socket_ = fd;
state_ = kStateTunnelData;
// If this was a "CONNECT" request, notify the client that the connection
// has been established by sending an "OK" response.
if (LowerCaseEqualsASCII(client_method_, kHTTPMethodConnect)) {
SetClientResponse(200, "OK", "", "");
StartReceive();
}
StartTransmit();
}
void HTTPProxy::OnReadError(const Error &error) {
StopClient();
}
// Read through the header lines from the client, modifying or adding
// lines as necessary. Perform final determination of the hostname/port
// we should connect to and either start a DNS request or connect to a
// numeric address.
bool HTTPProxy::ParseClientRequest() {
SLOG(HTTPProxy, 3) << "In " << __func__;
string host;
bool found_via = false;
bool found_connection = false;
for (auto &header : client_headers_) {
if (StartsWithASCII(header, "Host:", false)) {
host = header.substr(5);
} else if (StartsWithASCII(header, "Via:", false)) {
found_via = true;
header.append(StringPrintf(", %s shill-proxy", client_version_.c_str()));
} else if (StartsWithASCII(header, "Connection:", false)) {
found_connection = true;
header.assign("Connection: close");
} else if (StartsWithASCII(header, "Proxy-Connection:", false)) {
header.assign("Proxy-Connection: close");
}
}
if (!found_connection) {
client_headers_.push_back("Connection: close");
}
if (!found_via) {
client_headers_.push_back(
StringPrintf("Via: %s shill-proxy", client_version_.c_str()));
}
// Assemble the request as it will be sent to the server.
client_data_.Clear();
if (!LowerCaseEqualsASCII(client_method_, kHTTPMethodConnect)) {
for (const auto &header : client_headers_) {
client_data_.Append(ByteString(header + "\r\n", false));
}
client_data_.Append(ByteString(string("\r\n"), false));
}
base::TrimWhitespaceASCII(host, base::TRIM_ALL, &host);
if (host.empty()) {
// Revert to using the hostname in the URL if no "Host:" header exists.
host = server_hostname_;
}
if (host.empty()) {
SendClientError(400, "I don't know what host you want me to connect to");
return false;
}
server_port_ = 80;
vector<string> host_parts;
base::SplitString(host, ':', &host_parts);
if (host_parts.size() > 2) {
SendClientError(400, "Too many colons in hostname");
return false;
} else if (host_parts.size() == 2) {
server_hostname_ = host_parts[0];
if (!base::StringToInt(host_parts[1], &server_port_)) {
SendClientError(400, "Could not parse port number");
return false;
}
} else {
server_hostname_ = host;
}
connection_->RequestRouting();
is_route_requested_ = true;
IPAddress addr(IPAddress::kFamilyIPv4);
if (addr.SetAddressFromString(server_hostname_)) {
if (!ConnectServer(addr, server_port_)) {
return false;
}
} else {
SLOG(HTTPProxy, 3) << "Looking up host: " << server_hostname_;
Error error;
if (!dns_client_->Start(server_hostname_, &error)) {
SendClientError(502, "Could not resolve hostname: " + error.message());
return false;
}
state_ = kStateLookupServer;
}
return true;
}
// Accept a new line into the client headers. Returns false if a parse
// error occurs.
bool HTTPProxy::ProcessLastHeaderLine() {
string *header = &client_headers_.back();
base::TrimString(*header, "\r", header);
if (header->empty()) {
// Empty line terminates client headers.
client_headers_.pop_back();
if (!ParseClientRequest()) {
return false;
}
}
// Is this is the first header line?
if (client_headers_.size() == 1) {
if (!ReadClientHTTPMethod(header) ||
!ReadClientHTTPVersion(header) ||
!ReadClientHostname(header)) {
return false;
}
}
if (client_headers_.size() >= kMaxHeaderCount) {
SendClientError(500, kInternalErrorMsg);
return false;
}
return true;
}
// Split input from client into header lines, and consume parsed lines
// from InputData. The passed in |data| is modified to indicate the
// characters consumed.
bool HTTPProxy::ReadClientHeaders(InputData *data) {
unsigned char *ptr = data->buf;
unsigned char *end = ptr + data->len;
if (client_headers_.empty()) {
client_headers_.push_back(string());
}
for (; ptr < end && state_ == kStateReadClientHeader; ++ptr) {
if (*ptr == '\n') {
if (!ProcessLastHeaderLine()) {
return false;
}
// Start a new line. New chararacters we receive will be appended there.
client_headers_.push_back(string());
continue;
}
string *header = &client_headers_.back();
// Is the first character of the header line a space or tab character?
if (header->empty() && (*ptr == ' ' || *ptr == '\t') &&
client_headers_.size() > 1) {
// Line Continuation: Add this character to the previous header line.
// This way, all of the data (including newlines and line continuation
// characters) related to a specific header will be contained within
// a single element of |client_headers_|, and manipulation of headers
// such as appending will be simpler. This is accomplished by removing
// the empty line we started, and instead appending the whitespace
// and following characters to the previous line.
client_headers_.pop_back();
header = &client_headers_.back();
header->append("\r\n");
}
if (header->length() >= kMaxHeaderSize) {
SendClientError(500, kInternalErrorMsg);
return false;
}
header->push_back(*ptr);
}
// Return the remaining data to the caller -- this could be POST data
// or other non-header data sent with the client request.
data->buf = ptr;
data->len = end - ptr;
return true;
}
// Finds the URL in the first line of an HTTP client header, and extracts
// and removes the hostname (and port) from the URL. Returns false if a
// parse error occurs, and true otherwise (whether or not the hostname was
// found).
bool HTTPProxy::ReadClientHostname(string *header) {
const string http_url_prefix(kHTTPURLPrefix);
size_t url_idx = header->find(http_url_prefix);
if (url_idx != string::npos) {
size_t host_start = url_idx + http_url_prefix.length();
size_t host_end =
header->find_first_of(kHTTPURLDelimiters, host_start);
if (host_end != string::npos) {
server_hostname_ = header->substr(host_start,
host_end - host_start);
// Modify the URL passed upstream to remove "http://<hostname>".
header->erase(url_idx, host_end - url_idx);
if ((*header)[url_idx] != '/') {
header->insert(url_idx, "/");
}
} else {
LOG(ERROR) << "Could not find end of hostname in request. Line was: "
<< *header;
SendClientError(500, kInternalErrorMsg);
return false;
}
}
return true;
}
bool HTTPProxy::ReadClientHTTPMethod(string *header) {
size_t method_end = header->find(kHTTPMethodTerminator);
if (method_end == string::npos || method_end == 0) {
LOG(ERROR) << "Could not parse HTTP method. Line was: " << *header;
SendClientError(501, "Server could not parse HTTP method");
return false;
}
client_method_ = header->substr(0, method_end);
return true;
}
// Extract the HTTP version number from the first line of the client headers.
// Returns true if found.
bool HTTPProxy::ReadClientHTTPVersion(string *header) {
const string http_version_prefix(kHTTPVersionPrefix);
size_t http_ver_pos = header->find(http_version_prefix);
if (http_ver_pos != string::npos) {
client_version_ =
header->substr(http_ver_pos + http_version_prefix.length() - 1);
} else {
SendClientError(501, "Server only accepts HTTP/1.x requests");
return false;
}
return true;
}
// IOInputHandler callback that fires when data is read from the client.
// This could be header data, or perhaps POST data that follows the headers.
void HTTPProxy::ReadFromClient(InputData *data) {
SLOG(HTTPProxy, 3) << "In " << __func__ << " length " << data->len;
if (data->len == 0) {
// EOF from client.
StopClient();
return;
}
if (state_ == kStateReadClientHeader) {
if (!ReadClientHeaders(data)) {
return;
}
if (state_ == kStateReadClientHeader) {
// Still consuming client headers; restart the input timer.
StartIdleTimeout();
return;
}
}
// Check data->len again since ReadClientHeaders() may have consumed some
// part of it.
if (data->len != 0) {
// The client sent some information after its headers. Buffer the client
// input and temporarily disable input events from the client.
client_data_.Append(ByteString(data->buf, data->len));
read_client_handler_->Stop();
StartTransmit();
}
}
// IOInputHandler callback which fires when data has been read from the
// server.
void HTTPProxy::ReadFromServer(InputData *data) {
SLOG(HTTPProxy, 3) << "In " << __func__ << " length " << data->len;
if (data->len == 0) {
// Server closed connection.
if (server_data_.IsEmpty()) {
StopClient();
return;
}
state_ = kStateFlushResponse;
} else {
read_server_handler_->Stop();
}
server_data_.Append(ByteString(data->buf, data->len));
StartTransmit();
}
// Return an HTTP error message back to the client.
void HTTPProxy::SendClientError(int code, const string &error) {
SLOG(HTTPProxy, 3) << "In " << __func__;
LOG(ERROR) << "Sending error " << error;
SetClientResponse(code, "ERROR", "text/plain", error);
state_ = kStateFlushResponse;
StartTransmit();
}
// Create an HTTP response message to be sent to the client.
void HTTPProxy::SetClientResponse(int code, const string &type,
const string &content_type,
const string &message) {
string content_line;
if (!message.empty() && !content_type.empty()) {
content_line = StringPrintf("Content-Type: %s\r\n", content_type.c_str());
}
string response = StringPrintf("HTTP/1.1 %d %s\r\n"
"%s\r\n"
"%s", code, type.c_str(),
content_line.c_str(),
message.c_str());
server_data_ = ByteString(response, false);
}
// Start a timeout for "the next event". This timeout augments the overall
// transaction timeout to make sure there is some activity occurring at
// reasonable intervals.
void HTTPProxy::StartIdleTimeout() {
int timeout_seconds = 0;
switch (state_) {
case kStateReadClientHeader:
timeout_seconds = kClientHeaderTimeoutSeconds;
break;
case kStateConnectServer:
timeout_seconds = kConnectTimeoutSeconds;
break;
case kStateLookupServer:
// DNSClient has its own internal timeout, so we need not set one here.
timeout_seconds = 0;
break;
default:
timeout_seconds = kInputTimeoutSeconds;
break;
}
idle_timeout_.Cancel();
if (timeout_seconds != 0) {
idle_timeout_.Reset(Bind(&HTTPProxy::StopClient,
weak_ptr_factory_.GetWeakPtr()));
dispatcher_->PostDelayedTask(idle_timeout_.callback(),
timeout_seconds * 1000);
}
}
// Start the various input handlers. Listen for new data only if we have
// completely written the last data we've received to the other end.
void HTTPProxy::StartReceive() {
if (state_ == kStateTunnelData && client_data_.IsEmpty()) {
read_client_handler_->Start();
}
if (server_data_.IsEmpty()) {
if (state_ == kStateTunnelData) {
if (read_server_handler_.get()) {
read_server_handler_->Start();
} else {
read_server_handler_.reset(dispatcher_->CreateInputHandler(
server_socket_,
read_server_callback_,
Bind(&HTTPProxy::OnReadError, weak_ptr_factory_.GetWeakPtr())));
}
} else if (state_ == kStateFlushResponse) {
StopClient();
return;
}
}
StartIdleTimeout();
}
// Start the various output-ready handlers for the endpoints we have
// data waiting for.
void HTTPProxy::StartTransmit() {
if (state_ == kStateTunnelData && !client_data_.IsEmpty()) {
if (write_server_handler_.get()) {
write_server_handler_->Start();
} else {
write_server_handler_.reset(
dispatcher_->CreateReadyHandler(server_socket_,
IOHandler::kModeOutput,
write_server_callback_));
}
}
if ((state_ == kStateFlushResponse || state_ == kStateTunnelData) &&
!server_data_.IsEmpty()) {
if (write_client_handler_.get()) {
write_client_handler_->Start();
} else {
write_client_handler_.reset(
dispatcher_->CreateReadyHandler(client_socket_,
IOHandler::kModeOutput,
write_client_callback_));
}
}
StartIdleTimeout();
}
// End the transaction with the current client, restart the IOHandler
// which alerts us to new clients connecting. This function is called
// during various error conditions and is a callback for all timeouts.
void HTTPProxy::StopClient() {
SLOG(HTTPProxy, 3) << "In " << __func__;
if (is_route_requested_) {
connection_->ReleaseRouting();
is_route_requested_ = false;
}
write_client_handler_.reset();
read_client_handler_.reset();
if (client_socket_ != -1) {
sockets_->Close(client_socket_);
client_socket_ = -1;
}
client_headers_.clear();
client_method_.clear();
client_version_.clear();
server_port_ = kDefaultServerPort;
write_server_handler_.reset();
read_server_handler_.reset();
if (server_socket_ != -1) {
sockets_->Close(server_socket_);
server_socket_ = -1;
}
server_hostname_.clear();
client_data_.Clear();
server_data_.Clear();
dns_client_->Stop();
server_async_connection_->Stop();
idle_timeout_.Cancel();
transaction_timeout_.Cancel();
accept_handler_->Start();
state_ = kStateWaitConnection;
}
// Output ReadyHandler callback which fires when the client socket is
// ready for data to be sent to it.
void HTTPProxy::WriteToClient(int fd) {
CHECK_EQ(client_socket_, fd);
int ret = sockets_->Send(fd, server_data_.GetConstData(),
server_data_.GetLength(), 0);
SLOG(HTTPProxy, 3) << "In " << __func__ << " wrote " << ret << " of "
<< server_data_.GetLength();
if (ret < 0) {
LOG(ERROR) << "Server write failed";
StopClient();
return;
}
server_data_ = ByteString(server_data_.GetConstData() + ret,
server_data_.GetLength() - ret);
if (server_data_.IsEmpty()) {
write_client_handler_->Stop();
}
StartReceive();
}
// Output ReadyHandler callback which fires when the server socket is
// ready for data to be sent to it.
void HTTPProxy::WriteToServer(int fd) {
CHECK_EQ(server_socket_, fd);
int ret = sockets_->Send(fd, client_data_.GetConstData(),
client_data_.GetLength(), 0);
SLOG(HTTPProxy, 3) << "In " << __func__ << " wrote " << ret << " of "
<< client_data_.GetLength();
if (ret < 0) {
LOG(ERROR) << "Client write failed";
StopClient();
return;
}
client_data_ = ByteString(client_data_.GetConstData() + ret,
client_data_.GetLength() - ret);
if (client_data_.IsEmpty()) {
write_server_handler_->Stop();
}
StartReceive();
}
} // namespace shill