Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 815c7bea authored by David Pursell's avatar David Pursell
Browse files

fastboot: implement UDP networking interface.

This CL creates a UdpSocket class that provides a simple unified
interface to send and receive UDP packets for all platforms. Nothing
uses this interface yet except for tests.

The eventual goal is to implement a UDP protocol for fastboot, but it
makes the code much simpler and more modular if we handle the low-level
networking here independently of our custom fastboot protocol.

Some of the Windows code is similar to adb. I'd like to create a
library to hold the common functionality, but it is going to be a
little delicate to separate out the features unique to adb (e.g. the
custom file descriptor system), and I don't want to risk breaking
something in adb before the holiday break, so I'm hoping to get this in
for now and merge them early next year.

Tests are included in this CL to exercise this functionality using a
loopback connection.

Bug: http://b/26154763.
Tests: `fastboot_test` loopback tests on Linux, Mac, and Windows 7.
Change-Id: I81d1b7ace8d864246b99f6c80b8e29f64b8aa375
parent 56d7d4e8
Loading
Loading
Loading
Loading
+30 −5
Original line number Diff line number Diff line
@@ -33,15 +33,15 @@ LOCAL_CFLAGS += -Wall -Wextra -Werror -Wunreachable-code

LOCAL_CFLAGS += -DFASTBOOT_REVISION='"$(fastboot_version)"'

LOCAL_SRC_FILES_linux := usb_linux.cpp util_linux.cpp
LOCAL_STATIC_LIBRARIES_linux := libselinux
LOCAL_SRC_FILES_linux := socket_unix.cpp usb_linux.cpp util_linux.cpp
LOCAL_STATIC_LIBRARIES_linux := libcutils libselinux

LOCAL_SRC_FILES_darwin := usb_osx.cpp util_osx.cpp
LOCAL_STATIC_LIBRARIES_darwin := libselinux
LOCAL_SRC_FILES_darwin := socket_unix.cpp usb_osx.cpp util_osx.cpp
LOCAL_STATIC_LIBRARIES_darwin := libcutils libselinux
LOCAL_LDLIBS_darwin := -lpthread -framework CoreFoundation -framework IOKit -framework Carbon
LOCAL_CFLAGS_darwin := -Wno-unused-parameter

LOCAL_SRC_FILES_windows := usb_windows.cpp util_windows.cpp
LOCAL_SRC_FILES_windows := socket_windows.cpp usb_windows.cpp util_windows.cpp
LOCAL_STATIC_LIBRARIES_windows := AdbWinApi
LOCAL_REQUIRED_MODULES_windows := AdbWinApi
LOCAL_LDLIBS_windows := -lws2_32
@@ -89,3 +89,28 @@ LOCAL_CFLAGS := -Werror
LOCAL_STATIC_LIBRARIES := libbase
include $(BUILD_HOST_EXECUTABLE)
endif

# fastboot_test
# =========================================================
include $(CLEAR_VARS)

LOCAL_MODULE := fastboot_test
LOCAL_MODULE_HOST_OS := darwin linux windows

LOCAL_SRC_FILES := socket_test.cpp
LOCAL_STATIC_LIBRARIES := libbase

LOCAL_CFLAGS += -Wall -Wextra -Werror -Wunreachable-code

LOCAL_SRC_FILES_linux := socket_unix.cpp
LOCAL_STATIC_LIBRARIES_linux := libcutils

LOCAL_SRC_FILES_darwin := socket_unix.cpp
LOCAL_LDLIBS_darwin := -lpthread -framework CoreFoundation -framework IOKit -framework Carbon
LOCAL_CFLAGS_darwin := -Wno-unused-parameter
LOCAL_STATIC_LIBRARIES_darwin := libcutils

LOCAL_SRC_FILES_windows := socket_windows.cpp
LOCAL_LDLIBS_windows := -lws2_32

include $(BUILD_HOST_NATIVE_TEST)

fastboot/socket.h

0 → 100644
+76 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

// This file provides a class interface for cross-platform UDP functionality. The main fastboot
// engine should not be using this interface directly, but instead should use a higher-level
// interface that enforces the fastboot UDP protocol.

#ifndef SOCKET_H_
#define SOCKET_H_

#include "android-base/macros.h"

#include <memory>
#include <string>

// UdpSocket interface to be implemented for each platform.
class UdpSocket {
  public:
    // Creates a new client connection. Clients are connected to a specific hostname/port and can
    // only send to that destination.
    // On failure, |error| is filled (if non-null) and nullptr is returned.
    static std::unique_ptr<UdpSocket> NewUdpClient(const std::string& hostname, int port,
                                                   std::string* error);

    // Creates a new server bound to local |port|. This is only meant for testing, during normal
    // fastboot operation the device acts as the server.
    // The server saves sender addresses in Receive(), and uses the most recent address during
    // calls to Send().
    static std::unique_ptr<UdpSocket> NewUdpServer(int port);

    virtual ~UdpSocket() = default;

    // Sends |length| bytes of |data|. Returns the number of bytes actually sent or -1 on error.
    virtual ssize_t Send(const void* data, size_t length) = 0;

    // Waits up to |timeout_ms| to receive up to |length| bytes of data. |timout_ms| of 0 will
    // block forever. Returns the number of bytes received or -1 on error/timeout. On timeout
    // errno will be set to EAGAIN or EWOULDBLOCK.
    virtual ssize_t Receive(void* data, size_t length, int timeout_ms) = 0;

    // Closes the socket. Returns 0 on success, -1 on error.
    virtual int Close() = 0;

  protected:
    // Protected constructor to force factory function use.
    UdpSocket() = default;

    DISALLOW_COPY_AND_ASSIGN(UdpSocket);
};

#endif  // SOCKET_H_
+197 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 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.
 */

// Tests UDP functionality using loopback connections. Requires that kDefaultPort is available
// for loopback communication on the host. These tests also assume that no UDP packets are lost,
// which should be the case for loopback communication, but is not guaranteed.

#include "socket.h"

#include <errno.h>
#include <time.h>

#include <memory>
#include <string>
#include <vector>

#include <gtest/gtest.h>

enum {
    // This port must be available for loopback communication.
    kDefaultPort = 54321,

    // Don't wait forever in a unit test.
    kDefaultTimeoutMs = 3000,
};

static const char kReceiveStringError[] = "Error receiving string";

// Test fixture to provide some helper functions. Makes each test a little simpler since we can
// just check a bool for socket creation and don't have to pass hostname or port information.
class SocketTest : public ::testing::Test {
  protected:
    bool StartServer(int port = kDefaultPort) {
        server_ = UdpSocket::NewUdpServer(port);
        return server_ != nullptr;
    }

    bool StartClient(const std::string hostname = "localhost", int port = kDefaultPort) {
        client_ = UdpSocket::NewUdpClient(hostname, port, nullptr);
        return client_ != nullptr;
    }

    bool StartClient2(const std::string hostname = "localhost", int port = kDefaultPort) {
        client2_ = UdpSocket::NewUdpClient(hostname, port, nullptr);
        return client2_ != nullptr;
    }

    std::unique_ptr<UdpSocket> server_, client_, client2_;
};

// Sends a string over a UdpSocket. Returns true if the full string (without terminating char)
// was sent.
static bool SendString(UdpSocket* udp, const std::string& message) {
    return udp->Send(message.c_str(), message.length()) == static_cast<ssize_t>(message.length());
}

// Receives a string from a UdpSocket. Returns the string, or kReceiveStringError on failure.
static std::string ReceiveString(UdpSocket* udp, size_t receive_size = 128) {
    std::vector<char> buffer(receive_size);

    ssize_t result = udp->Receive(buffer.data(), buffer.size(), kDefaultTimeoutMs);
    if (result >= 0) {
        return std::string(buffer.data(), result);
    }
    return kReceiveStringError;
}

// Calls Receive() on the UdpSocket with the given timeout. Returns true if the call timed out.
static bool ReceiveTimeout(UdpSocket* udp, int timeout_ms) {
    char buffer[1];

    errno = 0;
    return udp->Receive(buffer, 1, timeout_ms) == -1 && (errno == EAGAIN || errno == EWOULDBLOCK);
}

// Tests sending packets client -> server, then server -> client.
TEST_F(SocketTest, SendAndReceive) {
    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient());

    EXPECT_TRUE(SendString(client_.get(), "foo"));
    EXPECT_EQ("foo", ReceiveString(server_.get()));

    EXPECT_TRUE(SendString(server_.get(), "bar baz"));
    EXPECT_EQ("bar baz", ReceiveString(client_.get()));
}

// Tests sending and receiving large packets.
TEST_F(SocketTest, LargePackets) {
    std::string message(512, '\0');

    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient());

    // Run through the test a few times.
    for (int i = 0; i < 10; ++i) {
        // Use a different message each iteration to prevent false positives.
        for (size_t j = 0; j < message.length(); ++j) {
            message[j] = static_cast<char>(i + j);
        }

        EXPECT_TRUE(SendString(client_.get(), message));
        EXPECT_EQ(message, ReceiveString(server_.get(), message.length()));
    }
}

// Tests IPv4 client/server.
TEST_F(SocketTest, IPv4) {
    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient("127.0.0.1"));

    EXPECT_TRUE(SendString(client_.get(), "foo"));
    EXPECT_EQ("foo", ReceiveString(server_.get()));

    EXPECT_TRUE(SendString(server_.get(), "bar"));
    EXPECT_EQ("bar", ReceiveString(client_.get()));
}

// Tests IPv6 client/server.
TEST_F(SocketTest, IPv6) {
    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient("::1"));

    EXPECT_TRUE(SendString(client_.get(), "foo"));
    EXPECT_EQ("foo", ReceiveString(server_.get()));

    EXPECT_TRUE(SendString(server_.get(), "bar"));
    EXPECT_EQ("bar", ReceiveString(client_.get()));
}

// Tests receive timeout. The timing verification logic must be very coarse to make sure different
// systems running different loads can all pass these tests.
TEST_F(SocketTest, ReceiveTimeout) {
    time_t start_time;

    ASSERT_TRUE(StartServer());

    // Make sure a 20ms timeout completes in 1 second or less.
    start_time = time(nullptr);
    EXPECT_TRUE(ReceiveTimeout(server_.get(), 20));
    EXPECT_LE(difftime(time(nullptr), start_time), 1.0);

    // Make sure a 1250ms timeout takes 1 second or more.
    start_time = time(nullptr);
    EXPECT_TRUE(ReceiveTimeout(server_.get(), 1250));
    EXPECT_LE(1.0, difftime(time(nullptr), start_time));
}

// Tests receive overflow (the UDP packet is larger than the receive buffer).
TEST_F(SocketTest, ReceiveOverflow) {
    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient());

    EXPECT_TRUE(SendString(client_.get(), "1234567890"));

    // This behaves differently on different systems; some give us a truncated UDP packet, others
    // will error out and not return anything at all.
    std::string rx_string = ReceiveString(server_.get(), 5);

    // If we didn't get an error then the packet should have been truncated.
    if (rx_string != kReceiveStringError) {
        EXPECT_EQ("12345", rx_string);
    }
}

// Tests multiple clients sending to the same server.
TEST_F(SocketTest, MultipleClients) {
    ASSERT_TRUE(StartServer());
    ASSERT_TRUE(StartClient());
    ASSERT_TRUE(StartClient2());

    EXPECT_TRUE(SendString(client_.get(), "client"));
    EXPECT_TRUE(SendString(client2_.get(), "client2"));

    // Receive the packets and send a response for each (note that packets may be received
    // out-of-order).
    for (int i = 0; i < 2; ++i) {
        std::string received = ReceiveString(server_.get());
        EXPECT_TRUE(SendString(server_.get(), received + " response"));
    }

    EXPECT_EQ("client response", ReceiveString(client_.get()));
    EXPECT_EQ("client2 response", ReceiveString(client2_.get()));
}
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "socket.h"

#include <errno.h>
#include <netdb.h>

#include <android-base/stringprintf.h>
#include <cutils/sockets.h>

class UnixUdpSocket : public UdpSocket {
  public:
    enum class Type { kClient, kServer };

    UnixUdpSocket(int fd, Type type);
    ~UnixUdpSocket() override;

    ssize_t Send(const void* data, size_t length) override;
    ssize_t Receive(void* data, size_t length, int timeout_ms) override;
    int Close() override;

  private:
    int fd_;
    int receive_timeout_ms_ = 0;
    std::unique_ptr<sockaddr_storage> addr_;
    socklen_t addr_size_ = 0;

    DISALLOW_COPY_AND_ASSIGN(UnixUdpSocket);
};

UnixUdpSocket::UnixUdpSocket(int fd, Type type) : fd_(fd) {
    // Only servers need to remember addresses; clients are connected to a server in NewUdpClient()
    // so will send to that server without needing to specify the address again.
    if (type == Type::kServer) {
        addr_.reset(new sockaddr_storage);
        addr_size_ = sizeof(*addr_);
        memset(addr_.get(), 0, addr_size_);
    }
}

UnixUdpSocket::~UnixUdpSocket() {
    Close();
}

ssize_t UnixUdpSocket::Send(const void* data, size_t length) {
    return TEMP_FAILURE_RETRY(
            sendto(fd_, data, length, 0, reinterpret_cast<sockaddr*>(addr_.get()), addr_size_));
}

ssize_t UnixUdpSocket::Receive(void* data, size_t length, int timeout_ms) {
    // Only set socket timeout if it's changed.
    if (receive_timeout_ms_ != timeout_ms) {
        timeval tv;
        tv.tv_sec = timeout_ms / 1000;
        tv.tv_usec = (timeout_ms % 1000) * 1000;
        if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
            return -1;
        }
        receive_timeout_ms_ = timeout_ms;
    }

    socklen_t* addr_size_ptr = nullptr;
    if (addr_ != nullptr) {
        // Reset addr_size as it may have been modified by previous recvfrom() calls.
        addr_size_ = sizeof(*addr_);
        addr_size_ptr = &addr_size_;
    }
    return TEMP_FAILURE_RETRY(recvfrom(fd_, data, length, 0,
                                       reinterpret_cast<sockaddr*>(addr_.get()), addr_size_ptr));
}

int UnixUdpSocket::Close() {
    int result = 0;
    if (fd_ != -1) {
        result = close(fd_);
        fd_ = -1;
    }
    return result;
}

std::unique_ptr<UdpSocket> UdpSocket::NewUdpClient(const std::string& host, int port,
                                                   std::string* error) {
    int getaddrinfo_error = 0;
    int fd = socket_network_client_timeout(host.c_str(), port, SOCK_DGRAM, 0, &getaddrinfo_error);
    if (fd == -1) {
        if (error) {
            *error = android::base::StringPrintf(
                    "Failed to connect to %s:%d: %s", host.c_str(), port,
                    getaddrinfo_error ? gai_strerror(getaddrinfo_error) : strerror(errno));
        }
        return nullptr;
    }

    return std::unique_ptr<UdpSocket>(new UnixUdpSocket(fd, UnixUdpSocket::Type::kClient));
}

std::unique_ptr<UdpSocket> UdpSocket::NewUdpServer(int port) {
    int fd = socket_inaddr_any_server(port, SOCK_DGRAM);
    if (fd == -1) {
        // This is just used in testing, no need for an error message.
        return nullptr;
    }

    return std::unique_ptr<UdpSocket>(new UnixUdpSocket(fd, UnixUdpSocket::Type::kServer));
}
+246 −0

File added.

Preview size limit exceeded, changes collapsed.