blob: 3ea665752ddd72cab16842990a14bc0a7e238493 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 "AndroidSystemNatives.h"
#include "JNIHelp.h"
#include "jni.h"
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <net/if.h> // Note: Can't appear before <sys/socket.h> on OS X.
// There are several ways that an interface index might be referenced
// out of an ifreq struct, depending on the platform.
#if defined(ifr_ifindex)
// Linux: No need to do anything.
#elif defined(ifr_index)
#define ifr_ifindex ifr_index // BSD
#elif defined(ifr_intval)
#define ifr_ifindex ifr_intval // OS X
#else
#error "Unknown how to refer to an interface index on this platform."
#endif
// A smart pointer that closes the given fd on going out of scope.
// TODO: make this generally available.
class scoped_fd {
public:
explicit scoped_fd(int fd) : fd(fd) {
}
~scoped_fd() {
close(fd);
}
int get() const {
return fd;
}
private:
int fd;
};
// TODO: add a header file for shared utilities like this.
extern jobject socketAddressToInetAddress(JNIEnv* env, sockaddr_storage* sockAddress);
class NetworkInterfaceGetter {
public:
NetworkInterfaceGetter() : interfaces(NULL) {
// Initialize this so we can be responsible for deleting it.
ifc.ifc_buf = NULL;
}
~NetworkInterfaceGetter() {
delete[] ifc.ifc_buf;
}
jobjectArray getNetworkInterfaces(JNIEnv* env);
private:
jobjectArray interfaces;
ifconf ifc;
};
// TODO: move to JNIHelp?
static void jniThrowOutOfMemoryError(JNIEnv* env) {
jniThrowException(env, "java/lang/OutOfMemoryError", "native heap");
}
// TODO(enh): move to JNIHelp.h
static void jniThrowSocketException(JNIEnv* env) {
char buf[BUFSIZ];
jniThrowException(env, "java/net/SocketException",
jniStrError(errno, buf, sizeof(buf)));
}
// Creates an InetAddress[] of size 'addressCount' from the ifc_req structs
// starting at index 'startIndex' in 'ifc.ifc_req'.
static jobjectArray MakeInetAddressArray(JNIEnv* env,
const ifconf& ifc, size_t startIndex, size_t addressCount) {
jclass inetAddressClass = env->FindClass("java/net/InetAddress");
if (inetAddressClass == NULL) {
return NULL;
}
jobjectArray addresses = env->NewObjectArray(addressCount, inetAddressClass, NULL);
if (addresses == NULL) {
return NULL;
}
for (size_t i = startIndex; i < startIndex + addressCount; ++i) {
sockaddr_storage* sockAddress =
reinterpret_cast<sockaddr_storage*>(&ifc.ifc_req[i].ifr_addr);
jobject element = socketAddressToInetAddress(env, sockAddress);
if (element == NULL) {
return NULL;
}
env->SetObjectArrayElement(addresses, i - startIndex, element);
if (env->ExceptionCheck()) {
return NULL;
}
}
return addresses;
}
// Creates a NetworkInterface with the given 'name', array of 'addresses',
// and 'id'.
static jobject MakeNetworkInterface(JNIEnv* env,
jstring name, jobjectArray addresses, jint id) {
jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
if (networkInterfaceClass == NULL) {
return NULL;
}
jmethodID networkInterfaceConstructor =
env->GetMethodID(networkInterfaceClass, "<init>",
"(Ljava/lang/String;Ljava/lang/String;[Ljava/net/InetAddress;I)V");
if (networkInterfaceConstructor == NULL) {
return NULL;
}
return env->NewObject(networkInterfaceClass, networkInterfaceConstructor,
name, name, addresses, id);
}
jobjectArray NetworkInterfaceGetter::getNetworkInterfaces(JNIEnv* env) {
scoped_fd fd(socket(PF_INET, SOCK_DGRAM, 0));
if (fd.get() < 0) {
jniThrowSocketException(env);
return NULL;
}
// Get the list of interfaces.
// Keep trying larger buffers until the result fits.
int len = 32 * sizeof(ifreq);
for (;;) {
// TODO: std::vector or boost::scoped_array would make this less awful.
if (ifc.ifc_buf != NULL) {
delete[] ifc.ifc_buf;
ifc.ifc_buf = NULL;
}
char* data = new char[len];
if (data == NULL) {
jniThrowOutOfMemoryError(env);
return NULL;
}
ifc.ifc_len = len;
ifc.ifc_buf = data;
if (ioctl(fd.get(), SIOCGIFCONF, &ifc) != 0) {
jniThrowSocketException(env);
return NULL;
}
if (ifc.ifc_len < len) {
break;
}
// The returned data was likely truncated.
// Expand the buffer and try again.
len += 32 * sizeof(ifreq);
}
// Count the number of distinct interfaces.
// Multiple addresses for a given interface have the same interface name.
// This whole function assumes that all an interface's addresses will be
// listed adjacent to one another.
size_t totalAddressCount = ifc.ifc_len / sizeof(ifreq);
size_t interfaceCount = 0;
const char* lastName = NULL;
for (size_t i = 0; i < totalAddressCount; ++i) {
const char* name = ifc.ifc_req[i].ifr_name;
if (lastName == NULL || strncmp(lastName, name, IFNAMSIZ) != 0) {
++interfaceCount;
}
lastName = name;
}
// Build the NetworkInterface[]...
jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
if (networkInterfaceClass == NULL) {
return NULL;
}
interfaces = env->NewObjectArray(interfaceCount, networkInterfaceClass, NULL);
if (interfaces == NULL) {
return NULL;
}
// Fill in the NetworkInterface[].
size_t arrayIndex = 0;
for (size_t i = 0; i < totalAddressCount; ++i) {
// Get the index for this interface.
// (This is an id the kernel uses, unrelated to our array indexes.)
int id = ifc.ifc_req[i].ifr_ifindex;
// Get the name for this interface. There only seems to be one name so
// we use it for both name and the display name (as does the RI).
jstring name = env->NewStringUTF(ifc.ifc_req[i].ifr_name);
if (name == NULL) {
return NULL;
}
// Check how many addresses this interface has.
size_t addressCount = 0;
for (size_t j = i; j < totalAddressCount; ++j) {
if (strncmp(ifc.ifc_req[i].ifr_name, ifc.ifc_req[j].ifr_name, IFNAMSIZ) == 0) {
if (ifc.ifc_req[j].ifr_addr.sa_family == AF_INET) {
++addressCount;
}
} else {
break;
}
}
// Get this interface's addresses as an InetAddress[].
jobjectArray addresses = MakeInetAddressArray(env, ifc, i, addressCount);
if (addresses == NULL) {
return NULL;
}
// Create the NetworkInterface object and add it to the NetworkInterface[].
jobject interface = MakeNetworkInterface(env, name, addresses, id);
if (interface == NULL) {
return NULL;
}
env->SetObjectArrayElement(interfaces, arrayIndex++, interface);
if (env->ExceptionCheck()) {
return NULL;
}
// Skip over this interface's addresses to the next *interface*.
i += addressCount - 1;
}
return interfaces;
}
/**
* Returns an array of zero or more NetworkInterface objects, one for each
* network interface.
*/
static jobjectArray getNetworkInterfacesImpl(JNIEnv* env, jclass) {
NetworkInterfaceGetter getter;
return getter.getNetworkInterfaces(env);
}
/*
* JNI registration
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "getNetworkInterfacesImpl", "()[Ljava/net/NetworkInterface;", (void*) getNetworkInterfacesImpl },
};
int register_java_net_NetworkInterface(JNIEnv* env) {
return jniRegisterNativeMethods(env, "java/net/NetworkInterface",
gMethods, NELEM(gMethods));
}