/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "Netd.h" #include #include #include #include #include "cutils/properties.h" #include "android/log.h" #include "nsWhitespaceTokenizer.h" #include "nsXULAppAPI.h" #include "nsAutoPtr.h" #include "nsString.h" #include "nsThreadUtils.h" #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk", args) #define ICS_SYS_USB_RNDIS_MAC "/sys/class/android_usb/android0/f_rndis/ethaddr" #define INVALID_SOCKET -1 #define MAX_RECONNECT_TIMES 10 namespace { mozilla::RefPtr gNetdClient; mozilla::RefPtr gNetdConsumer; class StopNetdConsumer : public nsRunnable { public: NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); gNetdConsumer = nullptr; return NS_OK; } }; bool InitRndisAddress() { char mac[20]; char serialno[] = "1234567890ABCDEF"; static const int kEthernetAddressLength = 6; char address[kEthernetAddressLength]; int i = 0; int ret = 0; int length = 0; mozilla::ScopedClose fd; fd.rwget() = open(ICS_SYS_USB_RNDIS_MAC, O_WRONLY); if (fd.rwget() == -1) { LOG("Unable to open file %s.", ICS_SYS_USB_RNDIS_MAC); return false; } property_get("ro.serialno", serialno, "1234567890ABCDEF"); // First byte is 0x02 to signify a locally administered address. address[0] = 0x02; length = strlen(serialno); for (i = 0; i < length; i++) { address[i % (kEthernetAddressLength - 1) + 1] ^= serialno[i]; } sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", address[0], address[1], address[2], address[3], address[4], address[5]); length = strlen(mac); ret = write(fd.get(), mac, length); if (ret != length) { LOG("Fail to write file %s.", ICS_SYS_USB_RNDIS_MAC); return false; } return true; } } // anonymous namespace namespace mozilla { namespace ipc { NetdClient::NetdClient() : mSocket(INVALID_SOCKET) , mIOLoop(MessageLoopForIO::current()) , mCurrentWriteOffset(0) , mReceivedIndex(0) , mReConnectTimes(0) { MOZ_COUNT_CTOR(NetdClient); } NetdClient::~NetdClient() { MOZ_COUNT_DTOR(NetdClient); } bool NetdClient::OpenSocket() { mSocket.rwget() = socket_local_client("netd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); if (mSocket.rwget() < 0) { LOG("Error connecting to : netd (%s) - will retry", strerror(errno)); return false; } // Add FD_CLOEXEC flag. int flags = fcntl(mSocket.get(), F_GETFD); if (flags == -1) { LOG("Error doing fcntl with F_GETFD command(%s)", strerror(errno)); return false; } flags |= FD_CLOEXEC; if (fcntl(mSocket.get(), F_SETFD, flags) == -1) { LOG("Error doing fcntl with F_SETFD command(%s)", strerror(errno)); return false; } // Set non-blocking. if (fcntl(mSocket.get(), F_SETFL, O_NONBLOCK) == -1) { LOG("Error set non-blocking socket(%s)", strerror(errno)); return false; } if (!MessageLoopForIO::current()-> WatchFileDescriptor(mSocket.get(), true, MessageLoopForIO::WATCH_READ, &mReadWatcher, this)) { LOG("Error set socket read watcher(%s)", strerror(errno)); return false; } if (!mOutgoingQ.empty()) { MessageLoopForIO::current()-> WatchFileDescriptor(mSocket.get(), false, MessageLoopForIO::WATCH_WRITE, &mWriteWatcher, this); } LOG("Connected to netd"); return true; } void NetdClient::OnFileCanReadWithoutBlocking(int aFd) { ssize_t length = 0; MOZ_ASSERT(aFd == mSocket.get()); while (true) { errno = 0; MOZ_ASSERT(mReceivedIndex < MAX_COMMAND_SIZE); length = read(aFd, &mReceiveBuffer[mReceivedIndex], MAX_COMMAND_SIZE - mReceivedIndex); MOZ_ASSERT(length <= ssize_t(MAX_COMMAND_SIZE - mReceivedIndex)); if (length <= 0) { if (length == -1) { if (errno == EINTR) { continue; // retry system call when interrupted } if (errno == EAGAIN || errno == EWOULDBLOCK) { return; // no data available: return and re-poll } } LOG("Can't read from netd error: %d (%s) length: %d", errno, strerror(errno), length); // At this point, assume that we can't actually access // the socket anymore, and start a reconnect loop. Restart(); return; } while (length-- > 0) { MOZ_ASSERT(mReceivedIndex < MAX_COMMAND_SIZE); if (mReceiveBuffer[mReceivedIndex] == '\0') { // We found a line terminator. Each line is formatted as an // integer response code followed by the rest of the line. // Fish out the response code. errno = 0; int responseCode = strtol(mReceiveBuffer, nullptr, 10); // TODO, Bug 783966, handle InterfaceChange(600) and BandwidthControl(601). if (!errno && responseCode < 600) { NetdCommand* response = new NetdCommand(); // Passing all the response message, including the line terminator. response->mSize = mReceivedIndex + 1; memcpy(response->mData, mReceiveBuffer, mReceivedIndex + 1); gNetdConsumer->MessageReceived(response); } if (!responseCode || errno) { LOG("Can't parse netd's response: %d (%s)", errno, strerror(errno)); } // There is data in the receive buffer beyond the current line. // Shift it down to the beginning. if (length > 0) { MOZ_ASSERT(mReceivedIndex < (MAX_COMMAND_SIZE - 1)); memmove(&mReceiveBuffer[0], &mReceiveBuffer[mReceivedIndex + 1], length); } mReceivedIndex = 0; } else { mReceivedIndex++; } } } } void NetdClient::OnFileCanWriteWithoutBlocking(int aFd) { MOZ_ASSERT(aFd == mSocket.get()); WriteNetdCommand(); } void NetdClient::Restart() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); mReadWatcher.StopWatchingFileDescriptor(); mWriteWatcher.StopWatchingFileDescriptor(); mSocket.dispose(); mReceivedIndex = 0; mCurrentWriteOffset = 0; mCurrentNetdCommand = nullptr; while (!mOutgoingQ.empty()) { delete mOutgoingQ.front(); mOutgoingQ.pop(); } Start(); } // static void NetdClient::Start() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); if (!gNetdClient) { LOG("Netd Client is not initialized"); return; } if (!gNetdClient->OpenSocket()) { // Socket open failed, try again in a second. LOG("Fail to connect to Netd"); if (++gNetdClient->mReConnectTimes > MAX_RECONNECT_TIMES) { LOG("Fail to connect to Netd after retry %d times", MAX_RECONNECT_TIMES); return; } MessageLoopForIO::current()-> PostDelayedTask(FROM_HERE, NewRunnableFunction(NetdClient::Start), 1000); return; } gNetdClient->mReConnectTimes = 0; } // static void NetdClient::SendNetdCommandIOThread(NetdCommand* aMessage) { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(aMessage); if (!gNetdClient) { LOG("Netd Client is not initialized"); return; } gNetdClient->mOutgoingQ.push(aMessage); if (gNetdClient->mSocket.get() == INVALID_SOCKET) { LOG("Netd connection is not established, push the message to queue"); return; } gNetdClient->WriteNetdCommand(); } void NetdClient::WriteNetdCommand() { if (!mCurrentNetdCommand) { mCurrentWriteOffset = 0; mCurrentNetdCommand = mOutgoingQ.front(); mOutgoingQ.pop(); } while (mCurrentWriteOffset < mCurrentNetdCommand->mSize) { ssize_t write_amount = mCurrentNetdCommand->mSize - mCurrentWriteOffset; ssize_t written = write(mSocket.get(), mCurrentNetdCommand->mData + mCurrentWriteOffset, write_amount); if (written < 0) { LOG("Cannot write to network, error %d\n", (int) written); Restart(); return; } if (written > 0) { mCurrentWriteOffset += written; } if (written != write_amount) { LOG("WriteNetdCommand fail !!! Write is not completed"); break; } } if (mCurrentWriteOffset != mCurrentNetdCommand->mSize) { MessageLoopForIO::current()-> WatchFileDescriptor(mSocket.get(), false, MessageLoopForIO::WATCH_WRITE, &mWriteWatcher, this); return; } mCurrentNetdCommand = nullptr; } static void InitNetdIOThread() { bool result; char propValue[PROPERTY_VALUE_MAX]; MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); MOZ_ASSERT(!gNetdClient); property_get("ro.build.version.sdk", propValue, "0"); // Assign rndis address for usb tethering in ICS. if (atoi(propValue) >= 15) { result = InitRndisAddress(); // We don't return here because InitRnsisAddress() function is related to // usb tethering only. Others service such as wifi tethering still need // to use ipc to communicate with netd. if (!result) { LOG("fail to give rndis interface an address"); } } gNetdClient = new NetdClient(); gNetdClient->Start(); } static void ShutdownNetdIOThread() { MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop()); nsCOMPtr shutdownEvent = new StopNetdConsumer(); gNetdClient = nullptr; NS_DispatchToMainThread(shutdownEvent); } void StartNetd(NetdConsumer* aNetdConsumer) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aNetdConsumer); MOZ_ASSERT(gNetdConsumer == nullptr); gNetdConsumer = aNetdConsumer; XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(InitNetdIOThread)); } void StopNetd() { MOZ_ASSERT(NS_IsMainThread()); nsIThread* currentThread = NS_GetCurrentThread(); NS_ASSERTION(currentThread, "This should never be null!"); XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(ShutdownNetdIOThread)); while (gNetdConsumer) { if (!NS_ProcessNextEvent(currentThread)) { NS_WARNING("Something bad happened!"); break; } } } /************************************************************************** * * This function runs in net worker Thread context. The net worker thread * is created in dom/system/gonk/NetworkManager.js * **************************************************************************/ void SendNetdCommand(NetdCommand* aMessage) { MOZ_ASSERT(aMessage); XRE_GetIOMessageLoop()->PostTask( FROM_HERE, NewRunnableFunction(NetdClient::SendNetdCommandIOThread, aMessage)); } } // namespace ipc } // namespace mozilla