OSDN Git Service

Add multinetwork debugging tools, dnschk and httpurl
authorErik Kline <ek@google.com>
Fri, 22 Jan 2016 00:07:44 +0000 (09:07 +0900)
committerErik Kline <ek@google.com>
Mon, 30 May 2016 08:56:18 +0000 (17:56 +0900)
Bug: 19537384
Bug: 27199751
Bug: 28719525
Change-Id: Ie983ec12ac6c550fa76c89cd44343220688a99b4

multinetwork/Android.mk [new file with mode: 0644]
multinetwork/common.cpp [new file with mode: 0644]
multinetwork/common.h [new file with mode: 0644]
multinetwork/dnschk.cpp [new file with mode: 0644]
multinetwork/httpurl.cpp [new file with mode: 0644]
multinetwork/quick_test.sh [new file with mode: 0755]

diff --git a/multinetwork/Android.mk b/multinetwork/Android.mk
new file mode 100644 (file)
index 0000000..d1a9289
--- /dev/null
@@ -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 (file)
index 0000000..7a5e7be
--- /dev/null
@@ -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 (file)
index 0000000..f431ea9
--- /dev/null
@@ -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 (file)
index 0000000..a2c42d4
--- /dev/null
@@ -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 (file)
index 0000000..e079c1d
--- /dev/null
@@ -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 (executable)
index 0000000..f586bae
--- /dev/null
@@ -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