summaryrefslogtreecommitdiff
path: root/multinetwork
diff options
context:
space:
mode:
authorErik Kline <ek@google.com>2016-01-22 09:07:44 +0900
committerErik Kline <ek@google.com>2016-05-30 17:56:18 +0900
commit871e63d9b7e584bc398dd4aa983cf561a5e8394e (patch)
treea3208bbc5055f235241938a6fd1238b2bd522197 /multinetwork
parent27879586fff26d5ac1864f57bf441a39f8ab9315 (diff)
downloadextras-871e63d9b7e584bc398dd4aa983cf561a5e8394e.tar.gz
Add multinetwork debugging tools, dnschk and httpurl
Bug: 19537384 Bug: 27199751 Bug: 28719525 Change-Id: Ie983ec12ac6c550fa76c89cd44343220688a99b4
Diffstat (limited to 'multinetwork')
-rw-r--r--multinetwork/Android.mk24
-rw-r--r--multinetwork/common.cpp135
-rw-r--r--multinetwork/common.h69
-rw-r--r--multinetwork/dnschk.cpp83
-rw-r--r--multinetwork/httpurl.cpp245
-rwxr-xr-xmultinetwork/quick_test.sh48
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, &parameters)) { 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