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