mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
376 lines
9.5 KiB
C++
376 lines
9.5 KiB
C++
/* 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 <android/log.h>
|
|
#include <cutils/sockets.h>
|
|
#include <fcntl.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include "cutils/properties.h"
|
|
#include "android/log.h"
|
|
|
|
#include "nsWhitespaceTokenizer.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsString.h"
|
|
#include "nsThreadUtils.h"
|
|
|
|
|
|
#define CHROMIUM_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<mozilla::ipc::NetdClient> gNetdClient;
|
|
mozilla::RefPtr<mozilla::ipc::NetdConsumer> 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) {
|
|
CHROMIUM_LOG("Unable to open file %s.", ICS_SYS_USB_RNDIS_MAC);
|
|
return false;
|
|
}
|
|
|
|
property_get("ro.serialno", serialno, "1234567890ABCDEF");
|
|
|
|
memset(address, 0, sizeof(address));
|
|
// 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) {
|
|
CHROMIUM_LOG("Fail to write file %s.", ICS_SYS_USB_RNDIS_MAC);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace mozilla {
|
|
namespace ipc {
|
|
|
|
NetdClient::NetdClient()
|
|
: LineWatcher('\0', MAX_COMMAND_SIZE)
|
|
, mIOLoop(MessageLoopForIO::current())
|
|
, mSocket(INVALID_SOCKET)
|
|
, mCurrentWriteOffset(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) {
|
|
CHROMIUM_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) {
|
|
CHROMIUM_LOG("Error doing fcntl with F_GETFD command(%s)", strerror(errno));
|
|
return false;
|
|
}
|
|
flags |= FD_CLOEXEC;
|
|
if (fcntl(mSocket.get(), F_SETFD, flags) == -1) {
|
|
CHROMIUM_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) {
|
|
CHROMIUM_LOG("Error set non-blocking socket(%s)", strerror(errno));
|
|
return false;
|
|
}
|
|
if (!MessageLoopForIO::current()->
|
|
WatchFileDescriptor(mSocket.get(),
|
|
true,
|
|
MessageLoopForIO::WATCH_READ,
|
|
&mReadWatcher,
|
|
this)) {
|
|
CHROMIUM_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);
|
|
}
|
|
|
|
CHROMIUM_LOG("Connected to netd");
|
|
return true;
|
|
}
|
|
|
|
void NetdClient::OnLineRead(int aFd, nsDependentCSubstring& aMessage)
|
|
{
|
|
// Set errno to 0 first. For preventing to use the stale version of errno.
|
|
errno = 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.
|
|
int responseCode = strtol(aMessage.Data(), nullptr, 10);
|
|
if (!errno) {
|
|
NetdCommand* response = new NetdCommand();
|
|
// Passing all the response message, including the line terminator.
|
|
response->mSize = aMessage.Length();
|
|
memcpy(response->mData, aMessage.Data(), aMessage.Length());
|
|
gNetdConsumer->MessageReceived(response);
|
|
}
|
|
|
|
if (!responseCode) {
|
|
CHROMIUM_LOG("Can't parse netd's response");
|
|
}
|
|
}
|
|
|
|
void
|
|
NetdClient::OnFileCanWriteWithoutBlocking(int aFd)
|
|
{
|
|
MOZ_ASSERT(aFd == mSocket.get());
|
|
WriteNetdCommand();
|
|
}
|
|
|
|
void
|
|
NetdClient::OnError()
|
|
{
|
|
MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
|
|
|
|
mReadWatcher.StopWatchingFileDescriptor();
|
|
mWriteWatcher.StopWatchingFileDescriptor();
|
|
|
|
mSocket.dispose();
|
|
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) {
|
|
CHROMIUM_LOG("Netd Client is not initialized");
|
|
return;
|
|
}
|
|
|
|
if (!gNetdClient->OpenSocket()) {
|
|
// Socket open failed, try again in a second.
|
|
CHROMIUM_LOG("Fail to connect to Netd");
|
|
if (++gNetdClient->mReConnectTimes > MAX_RECONNECT_TIMES) {
|
|
CHROMIUM_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) {
|
|
CHROMIUM_LOG("Netd Client is not initialized");
|
|
return;
|
|
}
|
|
|
|
gNetdClient->mOutgoingQ.push(aMessage);
|
|
|
|
if (gNetdClient->mSocket.get() == INVALID_SOCKET) {
|
|
CHROMIUM_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) {
|
|
CHROMIUM_LOG("Cannot write to network, error %d\n", (int) written);
|
|
OnError();
|
|
return;
|
|
}
|
|
|
|
if (written > 0) {
|
|
mCurrentWriteOffset += written;
|
|
}
|
|
|
|
if (written != write_amount) {
|
|
CHROMIUM_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) {
|
|
CHROMIUM_LOG("fail to give rndis interface an address");
|
|
}
|
|
}
|
|
gNetdClient = new NetdClient();
|
|
gNetdClient->Start();
|
|
}
|
|
|
|
static void
|
|
ShutdownNetdIOThread()
|
|
{
|
|
MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
|
|
nsCOMPtr<nsIRunnable> 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
|