// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/http_proxy.h" #include #include #include // NOLINT - Needs definitions from netinet/in.h #include #include #include #include #include #include #include #include #include #include "shill/async_connection.h" #include "shill/connection.h" #include "shill/dns_client.h" #include "shill/event_dispatcher.h" #include "shill/logging.h" #include "shill/net/ip_address.h" #include "shill/net/sockets.h" using base::Bind; using base::StringPrintf; using std::string; using std::vector; namespace shill { namespace Logging { static auto kModuleLogScope = ScopeLogger::kHTTPProxy; static string ObjectID(Connection* c) { return c->interface_name(); } } 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_(nullptr), proxy_port_(-1), proxy_socket_(-1), sockets_(nullptr), client_socket_(-1), server_port_(kDefaultServerPort), server_socket_(-1), is_route_requested_(false) { } HTTPProxy::~HTTPProxy() { Stop(); } bool HTTPProxy::Start(EventDispatcher* dispatcher, Sockets* sockets) { SLOG(connection_.get(), 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(&addr), sizeof(addr)) < 0 || sockets->GetSockName(proxy_socket_, reinterpret_cast(&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(connection_.get(), 3) << "In " << __func__; if (!sockets_) { return; } StopClient(); accept_handler_.reset(); dispatcher_ = nullptr; dns_client_.reset(); proxy_port_ = -1; server_async_connection_.reset(); sockets_->Close(proxy_socket_); proxy_socket_ = -1; sockets_ = nullptr; 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(connection_.get(), 3) << "In " << __func__; int client_fd = sockets_->Accept(fd, nullptr, nullptr); 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 (base::LowerCaseEqualsASCII(client_method_, kHTTPMethodConnect)) { SetClientResponse(200, "OK", "", ""); StartReceive(); } StartTransmit(); } void HTTPProxy::OnReadError(const string& error_msg) { 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(connection_.get(), 3) << "In " << __func__; string host; bool found_via = false; bool found_connection = false; for (auto& header : client_headers_) { if (base::StartsWith(header, "Host:", base::CompareCase::INSENSITIVE_ASCII)) { host = header.substr(5); } else if (base::StartsWith(header, "Via:", base::CompareCase::INSENSITIVE_ASCII)) { found_via = true; header.append(StringPrintf(", %s shill-proxy", client_version_.c_str())); } else if (base::StartsWith(header, "Connection:", base::CompareCase::INSENSITIVE_ASCII)) { found_connection = true; header.assign("Connection: close"); } else if (base::StartsWith(header, "Proxy-Connection:", base::CompareCase::INSENSITIVE_ASCII)) { 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 (!base::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 host_parts = base::SplitString( host, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); 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(connection_.get(), 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://". 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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