Skip to content

Commit

Permalink
Support binding to IPv6 loopback in IE driver
Browse files Browse the repository at this point in the history
When no host is specified on the command line, and no list of whitelisted
IP addresses are specified, IEDriverServer.exe will now bind to both the
IPv4 and IPv6 loopback adapters. If no IPv6 stack is found on the OS, the
executable will retry binding only to IPv4. Note that when using
whitelisted IP addresses, only IPv4 is supported. This is a limitation of
the Civetweb HTTP server upon which IEDriverServer.exe relies. This commit
also updates the version of Civetweb to 1.10.
  • Loading branch information
jimevans committed Feb 16, 2018
1 parent b019171 commit ed4c294
Show file tree
Hide file tree
Showing 15 changed files with 23,313 additions and 8,367 deletions.
134 changes: 96 additions & 38 deletions cpp/webdriver-server/server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ inline int wd_snprintf(char* str, size_t size, const char* format, ...) {
namespace webdriver {

Server::Server(const int port) {
this->Initialize(port, "", "", "", SERVER_DEFAULT_WHITELIST);
this->Initialize(port, "", "", "", "");
}

Server::Server(const int port, const std::string& host) {
this->Initialize(port, host, "", "", SERVER_DEFAULT_WHITELIST);
this->Initialize(port, host, "", "", "");
}

Server::Server(const int port,
const std::string& host,
const std::string& log_level,
const std::string& log_file) {
this->Initialize(port, host, log_level, log_file, SERVER_DEFAULT_WHITELIST);
this->Initialize(port, host, log_level, log_file, "");
}

Server::Server(const int port,
Expand Down Expand Up @@ -89,8 +89,6 @@ void Server::Initialize(const int port,
this->host_ = host;
if (acl.size() > 0) {
this->ProcessWhitelist(acl);
} else {
this->whitelist_.push_back(SERVER_DEFAULT_WHITELIST);
}
this->PopulateCommandRepository();
}
Expand All @@ -109,54 +107,114 @@ void Server::ProcessWhitelist(const std::string& whitelist) {
}
}

int Server::OnNewHttpRequest(struct mg_connection* conn) {
mg_context* context = mg_get_context(conn);
Server* current_server = reinterpret_cast<Server*>(mg_get_user_data(context));
mg_request_info* request_info = mg_get_request_info(conn);
int handler_result_code = current_server->ProcessRequest(conn, request_info);
return handler_result_code;
}

bool Server::Start() {
LOG(TRACE) << "Entering Server::Start";
std::string Server::GetListeningPorts(const bool use_ipv6) {
std::string port_format_string = "%s:%d";
if (this->host_.size() == 0) {
// If the host name is an empty string, then we don't want the colon
// in the listening ports string. Remove it from the format string,
// and when we use printf to format, the %s will be replaced by an
// empty string.
// If the host name is an empty string, then we want to bind
// to the local loopback address on both IPv4 and IPv6 if we
// can. Using the addresses in the listening port format string
// will prevent connection from external IP addresses.
port_format_string = "%s127.0.0.1:%d";
if (use_ipv6) {
port_format_string.append(",[::1]:%d");
}
} else if (this->whitelist_.size() > 0) {
// If there are white-listed IP addresses, we can only use IPv4,
// and we don't want the colon in the listening ports string.
// Instead, we want to bind to all adapters, and use the access
// control list to determine which addresses can connect. So to
// remove the host from the format string, when we use printf to
// format, the %s will be replaced by an empty string.
port_format_string = "%s%d";
}
int formatted_string_size = snprintf(NULL,
0,
port_format_string.c_str(),
this->host_.c_str(),
this->port_,
this->port_) + 1;
char* listening_ports_buffer = new char[formatted_string_size];
snprintf(listening_ports_buffer,
std::vector<char> listening_ports_buffer(formatted_string_size);
snprintf(&listening_ports_buffer[0],
formatted_string_size,
port_format_string.c_str(),
this->host_.c_str(),
this->port_,
this->port_);
return &listening_ports_buffer[0];
}

std::string Server::GetAccessControlList() {
std::string acl = "";
if (this->whitelist_.size() > 0) {
acl = SERVER_DEFAULT_BLACKLIST;
for (std::vector<std::string>::const_iterator it = this->whitelist_.begin();
it < this->whitelist_.end();
++it) {
acl.append(",+").append(*it);
}
LOG(DEBUG) << "Civetweb ACL is " << acl;
}
return acl;
}

void Server::GenerateOptionsList(std::vector<const char*>* options) {
std::map<std::string, std::string>::const_iterator it = this->options_.begin();
for (; it != this->options_.end(); ++it) {
options->push_back(it->first.c_str());
options->push_back(it->second.c_str());
}
options->push_back(NULL);
}

int Server::OnNewHttpRequest(struct mg_connection* conn) {
mg_context* context = mg_get_context(conn);
Server* current_server = reinterpret_cast<Server*>(mg_get_user_data(context));
const mg_request_info* request_info = mg_get_request_info(conn);
int handler_result_code = current_server->ProcessRequest(conn, request_info);
return handler_result_code;
}

bool Server::Start() {
LOG(TRACE) << "Entering Server::Start";

std::string listening_port_option = this->GetListeningPorts(true);
this->options_["listening_ports"] = listening_port_option;

std::string acl = SERVER_DEFAULT_BLACKLIST;
for (std::vector<std::string>::const_iterator it = this->whitelist_.begin();
it < this->whitelist_.end();
++it) {
acl.append(",+").append(*it);
std::string acl_option = this->GetAccessControlList();
if (acl_option.size() > 0) {
this->options_["access_control_list"] = acl_option;
}
LOG(DEBUG) << "Civetweb ACL is " << acl;

const char* options[] = { "listening_ports", listening_ports_buffer,
"access_control_list", acl.c_str(),
// "enable_keep_alive", "yes",
NULL };
this->options_["enable_keep_alive"] = "yes";

std::vector<const char*> options;
this->GenerateOptionsList(&options);

mg_callbacks callbacks = {};
callbacks.begin_request = &OnNewHttpRequest;
context_ = mg_start(&callbacks, this, options);
context_ = mg_start(&callbacks, this, &options[0]);
if (context_ == NULL) {
LOG(WARN) << "Failed to start Civetweb";
return false;
std::string ipv4_port_option = this->GetListeningPorts(false);
if (listening_port_option == ipv4_port_option) {
// If the IPv4 and IPv6 versions of the port option string
// are equal, then either a host to bind to or an ACL was
// specified, so there is no need to retry.
LOG(WARN) << "Failed to start Civetweb";
return false;
} else {
// If we fail, a host and ACL aren't specified, we might not
// be able to bind to an IPv6 address. Try again to bind to
// the IPv4 loopback only.
LOG(INFO) << "Failed first attempt to start Civetweb. Attempt start with IPv4 only";
this->options_["listening_ports"] = listening_port_option;
options.clear();
this->GenerateOptionsList(&options);
context_ = mg_start(&callbacks, this, &options[0]);
if (context_ == NULL) {
LOG(WARN) << "Failed to start Civetweb";
return false;
}
}
}
return true;
}
Expand All @@ -181,25 +239,25 @@ int Server::ProcessRequest(struct mg_connection* conn,
}

LOG(TRACE) << "Process request with:"
<< " URI: " << request_info->uri
<< " URI: " << request_info->local_uri
<< " HTTP verb: " << http_verb << std::endl
<< "body: " << request_body;

if (strcmp(request_info->uri, "/") == 0) {
if (strcmp(request_info->local_uri, "/") == 0) {
this->SendHttpOk(conn,
request_info,
SERVER_DEFAULT_PAGE,
HTML_CONTENT_TYPE);
http_response_code = 0;
} else if (strcmp(request_info->uri, "/shutdown") == 0) {
} else if (strcmp(request_info->local_uri, "/shutdown") == 0) {
this->SendHttpOk(conn,
request_info,
SERVER_DEFAULT_PAGE,
HTML_CONTENT_TYPE);
http_response_code = 0;
this->ShutDown();
} else {
std::string serialized_response = this->DispatchCommand(request_info->uri,
std::string serialized_response = this->DispatchCommand(request_info->local_uri,
http_verb,
request_body);
http_response_code = this->SendResponseToClient(conn,
Expand Down
5 changes: 5 additions & 0 deletions cpp/webdriver-server/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class Server {
const std::string& acl);

void ProcessWhitelist(const std::string& whitelist);
std::string GetListeningPorts(const bool use_ipv6);
std::string GetAccessControlList(void);
void GenerateOptionsList(std::vector<const char*>* options);

std::string ListSessions(void);
std::string LookupCommand(const std::string& uri,
Expand Down Expand Up @@ -133,6 +136,8 @@ class Server {
// List of whitelisted IPv4 addresses allowed to connect
// to this server.
std::vector<std::string> whitelist_;
// Map of options for the HTTP server
std::map<std::string, std::string> options_;
// The map of all command URIs (URL and HTTP verb), and
// the corresponding numerical value of the command.
UrlMap commands_;
Expand Down
Loading

0 comments on commit ed4c294

Please sign in to comment.