You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
245 lines
8.0 KiB
245 lines
8.0 KiB
/* |
|
* Copyright (C) 2016 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 <errno.h> |
|
#include <fcntl.h> |
|
#include <netdb.h> |
|
#include <netinet/in.h> |
|
#include <sys/socket.h> |
|
#include <sys/types.h> |
|
#include <sys/uio.h> |
|
#include <iostream> |
|
#include <string> |
|
|
|
#include <android/multinetwork.h> |
|
#include <android-base/stringprintf.h> |
|
#include "common.h" |
|
|
|
|
|
|
|
struct Parameters { |
|
Parameters() : ss({}), port("80"), path("/") {} |
|
|
|
struct sockaddr_storage ss; |
|
std::string host; |
|
std::string hostname; |
|
std::string port; |
|
std::string path; |
|
}; |
|
|
|
|
|
bool parseUrl(const struct Arguments& args, struct Parameters* parameters) { |
|
if (parameters == nullptr) { return false; } |
|
|
|
static const char HTTP_PREFIX[] = "http://"; |
|
if (strncmp(args.arg1, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) { |
|
std::cerr << "Only " << HTTP_PREFIX << " URLs supported." << std::endl; |
|
return false; |
|
} |
|
|
|
parameters->host = std::string(args.arg1).substr(strlen(HTTP_PREFIX)); |
|
const auto first_slash = parameters->host.find_first_of("/"); |
|
if (first_slash != std::string::npos) { |
|
parameters->path = parameters->host.substr(first_slash); |
|
parameters->host.erase(first_slash); |
|
} |
|
|
|
if (parameters->host.size() == 0) { |
|
std::cerr << "Host portion cannot be empty." << std::endl; |
|
return false; |
|
} |
|
|
|
if (parameters->host[0] == '[') { |
|
const auto closing_bracket = parameters->host.find_first_of("]"); |
|
if (closing_bracket == std::string::npos) { |
|
std::cerr << "Missing closing bracket." << std::endl; |
|
return false; |
|
} |
|
parameters->hostname = parameters->host.substr(1, closing_bracket - 1); |
|
|
|
const auto colon_port = closing_bracket + 1; |
|
if (colon_port < parameters->host.size()) { |
|
if (parameters->host[colon_port] != ':') { |
|
std::cerr << "Malformed port portion." << std::endl; |
|
return false; |
|
} |
|
parameters->port = parameters->host.substr(closing_bracket + 2); |
|
} |
|
} else { |
|
const auto first_colon = parameters->host.find_first_of(":"); |
|
if (first_colon != std::string::npos) { |
|
parameters->port = parameters->host.substr(first_colon + 1); |
|
parameters->hostname = parameters->host.substr(0, first_colon); |
|
} else { |
|
parameters->hostname = parameters->host; |
|
} |
|
} |
|
|
|
// TODO: find the request portion to send (before '#...'). |
|
|
|
std::cerr << "Resolving hostname=" << parameters->hostname |
|
<< ", port=" << parameters->port |
|
<< std::endl; |
|
|
|
struct addrinfo hints = { |
|
.ai_family = args.family, |
|
.ai_socktype = SOCK_STREAM, |
|
}; |
|
struct addrinfo *result = nullptr; |
|
|
|
int rval = -1; |
|
switch (args.api_mode) { |
|
case ApiMode::EXPLICIT: |
|
rval = android_getaddrinfofornetwork(args.nethandle, |
|
parameters->hostname.c_str(), |
|
parameters->port.c_str(), |
|
&hints, &result); |
|
break; |
|
case ApiMode::PROCESS: |
|
rval = getaddrinfo(parameters->hostname.c_str(), |
|
parameters->port.c_str(), |
|
&hints, &result); |
|
break; |
|
default: |
|
// Unreachable. |
|
std::cerr << "Unknown api mode." << std::endl; |
|
return false; |
|
} |
|
|
|
if (rval != 0) { |
|
std::cerr << "DNS resolution failure; gaierror=" << rval |
|
<< " [" << gai_strerror(rval) << "]" |
|
<< std::endl; |
|
return rval; |
|
} |
|
|
|
memcpy(&(parameters->ss), result[0].ai_addr, result[0].ai_addrlen); |
|
std::cerr << "Connecting to: " |
|
<< inetSockaddrToString(result[0].ai_addr) |
|
<< std::endl; |
|
|
|
freeaddrinfo(result); |
|
return true; |
|
} |
|
|
|
|
|
int makeTcpSocket(sa_family_t address_family, net_handle_t nethandle) { |
|
int fd = socket(address_family, SOCK_STREAM, IPPROTO_TCP); |
|
if (fd < 0) { |
|
std::cerr << "failed to create TCP socket" << std::endl; |
|
return -1; |
|
} |
|
|
|
// Don't let reads or writes block indefinitely. We cannot control |
|
// connect() timeouts without nonblocking sockets and select/poll/epoll. |
|
const struct timeval timeo = { 5, 0 }; // 5 seconds |
|
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); |
|
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); |
|
|
|
if (nethandle != NETWORK_UNSPECIFIED) { |
|
if (android_setsocknetwork(nethandle, fd) != 0) { |
|
int errnum = errno; |
|
std::cerr << "android_setsocknetwork() failed;" |
|
<< " errno: " << errnum << " [" << strerror(errnum) << "]" |
|
<< std::endl; |
|
close(fd); |
|
return -1; |
|
} |
|
} |
|
return fd; |
|
} |
|
|
|
|
|
int doHttpQuery(int fd, const struct Parameters& parameters) { |
|
int rval = -1; |
|
if (connect(fd, |
|
reinterpret_cast<const struct sockaddr *>(&(parameters.ss)), |
|
(parameters.ss.ss_family == AF_INET6) |
|
? sizeof(struct sockaddr_in6) |
|
: sizeof(struct sockaddr_in)) != 0) { |
|
int errnum = errno; |
|
std::cerr << "Failed to connect; errno=" << errnum |
|
<< " [" << strerror(errnum) << "]" |
|
<< std::endl; |
|
return -1; |
|
} |
|
|
|
const std::string request(android::base::StringPrintf( |
|
"GET %s HTTP/1.1\r\n" |
|
"Host: %s\r\n" |
|
"Accept: */*\r\n" |
|
"Connection: close\r\n" |
|
"User-Agent: httpurl/0.0\r\n" |
|
"\r\n", |
|
parameters.path.c_str(), parameters.host.c_str())); |
|
const ssize_t sent = write(fd, request.c_str(), request.size()); |
|
if (sent != static_cast<ssize_t>(request.size())) { |
|
std::cerr << "Sent only " << sent << "/" << request.size() << " bytes" |
|
<< std::endl; |
|
return -1; |
|
} |
|
|
|
char buf[4*1024]; |
|
do { |
|
rval = recv(fd, buf, sizeof(buf), 0); |
|
|
|
if (rval < 0) { |
|
const int saved_errno = errno; |
|
std::cerr << "Failed to recv; errno=" << saved_errno |
|
<< " [" << strerror(saved_errno) << "]" |
|
<< std::endl; |
|
} else if (rval > 0) { |
|
std::cout.write(buf, rval); |
|
std::cout.flush(); |
|
} |
|
} while (rval > 0); |
|
std::cout << std::endl; |
|
|
|
return 0; |
|
} |
|
|
|
|
|
int main(int argc, const char* argv[]) { |
|
int rval = -1; |
|
|
|
struct Arguments args; |
|
if (!args.parseArguments(argc, argv)) { return rval; } |
|
|
|
if (args.api_mode == ApiMode::PROCESS) { |
|
rval = android_setprocnetwork(args.nethandle); |
|
if (rval != 0) { |
|
int errnum = errno; |
|
std::cerr << "android_setprocnetwork(" << args.nethandle << ") failed;" |
|
<< " errno: " << errnum << " [" << strerror(errnum) << "]" |
|
<< std::endl; |
|
return rval; |
|
} |
|
} |
|
|
|
struct Parameters parameters; |
|
if (!parseUrl(args, ¶meters)) { return -1; } |
|
|
|
// TODO: Fall back from IPv6 to IPv4 if ss.ss_family is AF_UNSPEC. |
|
// This will involve changes to parseUrl() as well. |
|
struct FdAutoCloser closer = makeTcpSocket( |
|
parameters.ss.ss_family, |
|
(args.api_mode == ApiMode::EXPLICIT) ? args.nethandle |
|
: NETWORK_UNSPECIFIED); |
|
if (closer.fd < 0) { return closer.fd; } |
|
|
|
return doHttpQuery(closer.fd, parameters); |
|
}
|
|
|