diff options
author | Erik Kline <ek@google.com> | 2016-05-31 01:41:50 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2016-05-31 01:41:50 +0000 |
commit | 319c10a631845898c19141d27f21fc4d918839eb (patch) | |
tree | 6522af52b18a1f4d6df679d93cf9d3bf08897d65 | |
parent | 0827d1d4ee083a7ea6523593ef35bdb26d4d16c3 (diff) | |
parent | 871e63d9b7e584bc398dd4aa983cf561a5e8394e (diff) | |
download | extras-319c10a631845898c19141d27f21fc4d918839eb.tar.gz |
Add multinetwork debugging tools, dnschk and httpurl
am: 871e63d9b7
* commit '871e63d9b7e584bc398dd4aa983cf561a5e8394e':
Add multinetwork debugging tools, dnschk and httpurl
Change-Id: Iffea34208fb19daa3b1f2c015364dff3a6542498
-rw-r--r-- | multinetwork/Android.mk | 24 | ||||
-rw-r--r-- | multinetwork/common.cpp | 135 | ||||
-rw-r--r-- | multinetwork/common.h | 69 | ||||
-rw-r--r-- | multinetwork/dnschk.cpp | 83 | ||||
-rw-r--r-- | multinetwork/httpurl.cpp | 245 | ||||
-rwxr-xr-x | multinetwork/quick_test.sh | 48 |
6 files changed, 604 insertions, 0 deletions
diff --git a/multinetwork/Android.mk b/multinetwork/Android.mk new file mode 100644 index 00000000..d1a92899 --- /dev/null +++ b/multinetwork/Android.mk @@ -0,0 +1,24 @@ +LOCAL_PATH := $(call my-dir) + +# Sample util binaries. +include $(CLEAR_VARS) +LOCAL_MODULE := dnschk + +LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include +LOCAL_CPPFLAGS += -std=c++11 +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_SHARED_LIBRARIES := libandroid libbase libc++ +LOCAL_SRC_FILES := dnschk.cpp common.cpp +include $(BUILD_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_MODULE := httpurl + +LOCAL_C_INCLUDES += frameworks/native/include external/libcxx/include +LOCAL_CPPFLAGS += -std=c++11 +LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES) +LOCAL_MODULE_TAGS := debug +LOCAL_SHARED_LIBRARIES := libandroid libbase libc++ +LOCAL_SRC_FILES := httpurl.cpp common.cpp +include $(BUILD_EXECUTABLE) diff --git a/multinetwork/common.cpp b/multinetwork/common.cpp new file mode 100644 index 00000000..7a5e7be4 --- /dev/null +++ b/multinetwork/common.cpp @@ -0,0 +1,135 @@ +/* + * 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 "common.h" + +#include <android/api-level.h> +#include <arpa/inet.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <iostream> + + +namespace { + +bool strEqual(const char *a, const char *b) { + return strcmp(a, b) == 0; +} + +// Allow specifying network handles in decimal and hexadecimal. +bool parseNetworkHandle(const char *arg, net_handle_t *nethandle) { + if (arg == nullptr || !isdigit(arg[0]) || nethandle == nullptr) { + return false; + } + + net_handle_t nh; + char *end = nullptr; + + nh = strtoull(arg, &end, 0); + if (end != nullptr && *end == '\0') { + *nethandle = nh; + return true; + } + return false; +} + +} // namespace + + +void printUsage(const char *progname) { + std::cerr << "Usage: " << progname + << " [--nethandle <nethandle>]" + << " [--mode explicit|process]" + << " [--family unspec|ipv4|ipv6]" + << " <argument>" + << std::endl; + std::cerr << std::endl; + std::cerr << "Learn nethandle values from 'dumpsys connectivity --short' " + << "or 'dumpsys connectivity --diag'" + << std::endl; +} + +Arguments::~Arguments() {} + +bool Arguments::parseArguments(int argc, const char* argv[]) { + if (argc < 1 || argv == nullptr) { return false; } + + for (int i = 1; i < argc; i++) { + if (strEqual(argv[i], "--nethandle")) { + i++; + if (argc == i) break; + if (!parseNetworkHandle(argv[i], &nethandle)) { + std::cerr << "Failed to parse nethandle: '" << argv[i] << "'" + << std::endl; + break; + } + } else if (strEqual(argv[i], "--family")) { + i++; + if (argc == i) break; + if (strEqual(argv[i], "unspec")) { + family = AF_UNSPEC; + } else if (strEqual(argv[i], "ipv4")) { + family = AF_INET; + } else if (strEqual(argv[i], "ipv6")) { + family = AF_INET6; + } else { + break; + } + } else if (strEqual(argv[i], "--mode")) { + i++; + if (argc == i) break; + if (strEqual(argv[i], "explicit")) { + api_mode = ApiMode::EXPLICIT; + } else if (strEqual(argv[i], "process")) { + api_mode = ApiMode::PROCESS; + } else { + break; + } + } else if (arg1 == nullptr) { + arg1 = argv[i]; + } else { + arg1 = nullptr; + break; + } + } + + if (arg1 != nullptr) { + return true; + } + + printUsage(argv[0]); + return false; +} + + +std::string inetSockaddrToString(const sockaddr* sa) { + const bool is_ipv6 = (sa->sa_family == AF_INET6); + char host[INET6_ADDRSTRLEN]; + char port[sizeof("65535")]; + getnameinfo(sa, is_ipv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in), + host, sizeof(host), + port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV); + + if (port[0] == '0' || port[0] == '\0') { + return std::string(host); + } + return (is_ipv6 ? "[" : "") + std::string(host) + (is_ipv6 ? "]:" : ":") + std::string(port); +} diff --git a/multinetwork/common.h b/multinetwork/common.h new file mode 100644 index 00000000..f431ea99 --- /dev/null +++ b/multinetwork/common.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + + +#ifndef SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_ +#define SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_ + +#include <sys/cdefs.h> +#include <sys/socket.h> +#include <unistd.h> +#include <string> +#include <android/multinetwork.h> + +enum class ApiMode { + EXPLICIT, + PROCESS, +}; + + +struct Arguments { + Arguments() : nethandle(NETWORK_UNSPECIFIED), + api_mode(ApiMode::EXPLICIT), + family(AF_UNSPEC), + arg1(nullptr) {} + ~Arguments(); + + bool parseArguments(int argc, const char* argv[]); + + net_handle_t nethandle; + ApiMode api_mode; + sa_family_t family; + const char* arg1; +}; + + +void printUsage(const char *progname); + +// If port is non-zero returns strings of the form "192.0.2.1:port" or +// "[2001:db8::1]:port", else it returns the bare IP string literal. +std::string inetSockaddrToString(const sockaddr* sa); + + +struct FdAutoCloser { + FdAutoCloser() : fd(-1) {} + /* not explicit */ FdAutoCloser(int fd) : fd(fd) {} + ~FdAutoCloser() { + if (fd > -1) { + close(fd); + } + fd = -1; + } + + int fd; +}; + +#endif // SYSTEM_EXTRAS_MULTINETWORK_COMMON_H_ diff --git a/multinetwork/dnschk.cpp b/multinetwork/dnschk.cpp new file mode 100644 index 00000000..a2c42d4d --- /dev/null +++ b/multinetwork/dnschk.cpp @@ -0,0 +1,83 @@ +/* + * 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 <arpa/inet.h> +#include <errno.h> +#include <netdb.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/socket.h> + +#include <iostream> +#include <string> + +#include <android/multinetwork.h> +#include "common.h" + + +int main(int argc, const char* argv[]) { + int rval = -1; + + struct Arguments args; + if (!args.parseArguments(argc, argv)) { return rval; } + + const struct addrinfo hints = { + .ai_family = args.family, + .ai_socktype = SOCK_DGRAM, + }; + struct addrinfo *result = nullptr; + + std::cout << "# " << args.arg1 + << " (via nethandle " << args.nethandle << "):" + << std::endl; + + switch (args.api_mode) { + case ApiMode::EXPLICIT: + rval = android_getaddrinfofornetwork(args.nethandle, + args.arg1, nullptr, &hints, &result); + break; + case ApiMode::PROCESS: + if (args.nethandle != NETWORK_UNSPECIFIED) { + rval = android_setprocnetwork(args.nethandle); + if (rval != 0) { + std::cerr << "android_setprocnetwork returned " << rval + << std::endl; + return rval; + } + } + rval = getaddrinfo(args.arg1, nullptr, &hints, &result); + break; + default: + // Unreachable. + std::cerr << "Unknown api mode." << std::endl; + return -1; + } + + if (rval != 0) { + std::cerr << "DNS resolution failure; gaierror=" << rval + << " [" << gai_strerror(rval) << "]" + << std::endl; + return rval; + } + + for (struct addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) { + std::cout << inetSockaddrToString(rp->ai_addr) << std::endl; + } + + freeaddrinfo(result); + return 0; +} diff --git a/multinetwork/httpurl.cpp b/multinetwork/httpurl.cpp new file mode 100644 index 00000000..e079c1d4 --- /dev/null +++ b/multinetwork/httpurl.cpp @@ -0,0 +1,245 @@ +/* + * 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); +} diff --git a/multinetwork/quick_test.sh b/multinetwork/quick_test.sh new file mode 100755 index 00000000..f586bae8 --- /dev/null +++ b/multinetwork/quick_test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +nethandle=0 + +readonly TEST_HOST="connectivitycheck.gstatic.com" +readonly TEST_PATH="/generate_204" +readonly PREFIX=">>>" + +function getUrls() { + if [ ! -z $(echo "$1" | sed -e 's/[^:]//g') ]; then + echo "http://[$1]$TEST_PATH" + echo "http://[$1]:80$TEST_PATH" + else + echo "http://$1$TEST_PATH" + echo "http://$1:80$TEST_PATH" + fi +} + +function toHex() { + readonly local hexValue=$(bc -q 2>/dev/null << EOT +obase=16 +$1 +EOT +) + if [ ! -z "$hexValue" ]; then + echo "0x$hexValue" + fi +} + + +if [ ! -z "$1" ]; then + nethandle="$1" +fi +echo "$PREFIX Using nethandle $nethandle ($(toHex $nethandle))" +echo "" + +readonly IPADDRESSES=$( + adb shell /system/xbin/dnschk --nethandle $nethandle $TEST_HOST | + sed -e 's/#.*//' -e '/^$/d') + + +for host in $TEST_HOST $IPADDRESSES; do + urls=$(getUrls $host) + for url in $urls; do + echo "$PREFIX Checking $url" >&2 + adb shell /system/xbin/httpurl --nethandle $nethandle "$url" + done +done |