Basic IPv6 Router Advertisement daemon
Bug: 9580643 Change-Id: Iebd4d1f694699ffd4daf7d71e9b9d8f49e9b632f
This commit is contained in:
@@ -45,12 +45,19 @@ public class NetworkUtils {
|
|||||||
public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
|
public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches a socket filter that accepts ICMP6 router advertisement packets to the given socket.
|
* Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
|
||||||
* @param fd the socket's {@link FileDescriptor}.
|
* @param fd the socket's {@link FileDescriptor}.
|
||||||
* @param packetType the hardware address type, one of ARPHRD_*.
|
* @param packetType the hardware address type, one of ARPHRD_*.
|
||||||
*/
|
*/
|
||||||
public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
|
public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
|
||||||
|
* @param fd the socket's {@link FileDescriptor}.
|
||||||
|
* @param ifIndex the interface index.
|
||||||
|
*/
|
||||||
|
public native static void setupRaSocket(FileDescriptor fd, int ifIndex) throws SocketException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds the current process to the network designated by {@code netId}. All sockets created
|
* Binds the current process to the network designated by {@code netId}. All sockets created
|
||||||
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
|
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
|
||||||
|
|||||||
@@ -125,6 +125,99 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
|
||||||
|
jint ifIndex)
|
||||||
|
{
|
||||||
|
static const int kLinkLocalHopLimit = 255;
|
||||||
|
|
||||||
|
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||||
|
|
||||||
|
// Set an ICMPv6 filter that only passes Router Solicitations.
|
||||||
|
struct icmp6_filter rs_only;
|
||||||
|
ICMP6_FILTER_SETBLOCKALL(&rs_only);
|
||||||
|
ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &rs_only);
|
||||||
|
socklen_t len = sizeof(rs_only);
|
||||||
|
if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &rs_only, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(ICMP6_FILTER): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most/all of the rest of these options can be set via Java code, but
|
||||||
|
// because we're here on account of setting an icmp6_filter go ahead
|
||||||
|
// and do it all natively for now.
|
||||||
|
//
|
||||||
|
// TODO: Consider moving these out to Java.
|
||||||
|
|
||||||
|
// Set the multicast hoplimit to 255 (link-local only).
|
||||||
|
int hops = kLinkLocalHopLimit;
|
||||||
|
len = sizeof(hops);
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(IPV6_MULTICAST_HOPS): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the unicast hoplimit to 255 (link-local only).
|
||||||
|
hops = kLinkLocalHopLimit;
|
||||||
|
len = sizeof(hops);
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(IPV6_UNICAST_HOPS): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicitly disable multicast loopback.
|
||||||
|
int off = 0;
|
||||||
|
len = sizeof(off);
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(IPV6_MULTICAST_LOOP): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify the IPv6 interface to use for outbound multicast.
|
||||||
|
len = sizeof(ifIndex);
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifIndex, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(IPV6_MULTICAST_IF): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional options to be considered:
|
||||||
|
// - IPV6_TCLASS
|
||||||
|
// - IPV6_RECVPKTINFO
|
||||||
|
// - IPV6_RECVHOPLIMIT
|
||||||
|
|
||||||
|
// Bind to [::].
|
||||||
|
const struct sockaddr_in6 sin6 = {
|
||||||
|
.sin6_family = AF_INET6,
|
||||||
|
.sin6_port = 0,
|
||||||
|
.sin6_flowinfo = 0,
|
||||||
|
.sin6_addr = IN6ADDR_ANY_INIT,
|
||||||
|
.sin6_scope_id = 0,
|
||||||
|
};
|
||||||
|
auto sa = reinterpret_cast<const struct sockaddr *>(&sin6);
|
||||||
|
len = sizeof(sin6);
|
||||||
|
if (bind(fd, sa, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"bind(IN6ADDR_ANY): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the all-routers multicast group, ff02::2%index.
|
||||||
|
struct ipv6_mreq all_rtrs = {
|
||||||
|
.ipv6mr_multiaddr = {{{0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}},
|
||||||
|
.ipv6mr_interface = ifIndex,
|
||||||
|
};
|
||||||
|
len = sizeof(all_rtrs);
|
||||||
|
if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &all_rtrs, len) != 0) {
|
||||||
|
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||||
|
"setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
|
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
|
||||||
{
|
{
|
||||||
return (jboolean) !setNetworkForProcess(netId);
|
return (jboolean) !setNetworkForProcess(netId);
|
||||||
@@ -173,6 +266,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
|
|||||||
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
||||||
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
||||||
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
||||||
|
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
|
||||||
};
|
};
|
||||||
|
|
||||||
int register_android_net_NetworkUtils(JNIEnv* env)
|
int register_android_net_NetworkUtils(JNIEnv* env)
|
||||||
|
|||||||
581
services/net/java/android/net/ip/RouterAdvertisementDaemon.java
Normal file
581
services/net/java/android/net/ip/RouterAdvertisementDaemon.java
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.net.ip;
|
||||||
|
|
||||||
|
import static android.system.OsConstants.*;
|
||||||
|
|
||||||
|
import android.net.IpPrefix;
|
||||||
|
import android.net.LinkAddress;
|
||||||
|
import android.net.LinkProperties;
|
||||||
|
import android.net.NetworkUtils;
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.system.StructGroupReq;
|
||||||
|
import android.system.StructTimeval;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
import libcore.util.HexEncoding;
|
||||||
|
|
||||||
|
import java.io.FileDescriptor;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.BufferOverflowException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic IPv6 Router Advertisement Daemon.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
*
|
||||||
|
* - Rewrite using Handler (and friends) so that AlarmManager can deliver
|
||||||
|
* "kick" messages when it's time to send a multicast RA.
|
||||||
|
*
|
||||||
|
* - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty
|
||||||
|
* RAs with zero default router lifetime when transitioning from an
|
||||||
|
* advertising state to a non-advertising state.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public class RouterAdvertisementDaemon {
|
||||||
|
private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName();
|
||||||
|
private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133);
|
||||||
|
private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134);
|
||||||
|
private static final int IPV6_MIN_MTU = 1280;
|
||||||
|
private static final int MIN_RA_HEADER_SIZE = 16;
|
||||||
|
|
||||||
|
// Summary of various timers and lifetimes.
|
||||||
|
private static final int MIN_RTR_ADV_INTERVAL_SEC = 300;
|
||||||
|
private static final int MAX_RTR_ADV_INTERVAL_SEC = 600;
|
||||||
|
// In general, router, prefix, and DNS lifetimes are all advised to be
|
||||||
|
// greater than or equal to 3 * MAX_RTR_ADV_INTERVAL. Here, we double
|
||||||
|
// that to allow for multicast packet loss.
|
||||||
|
//
|
||||||
|
// This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent
|
||||||
|
// with the https://tools.ietf.org/html/rfc7772#section-4 discussion of
|
||||||
|
// "approximately 7 RAs per hour".
|
||||||
|
private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC;
|
||||||
|
// From https://tools.ietf.org/html/rfc4861#section-10 .
|
||||||
|
private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3;
|
||||||
|
// Both initial and final RAs, but also for changes in RA contents.
|
||||||
|
// From https://tools.ietf.org/html/rfc4861#section-10 .
|
||||||
|
private static final int MAX_URGENT_RTR_ADVERTISEMENTS = 5;
|
||||||
|
|
||||||
|
private static final int DAY_IN_SECONDS = 86_400;
|
||||||
|
|
||||||
|
private static final byte[] ALL_NODES = new byte[] {
|
||||||
|
(byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
private final String mIfName;
|
||||||
|
private final int mIfIndex;
|
||||||
|
private final byte[] mHwAddr;
|
||||||
|
private final InetSocketAddress mAllNodes;
|
||||||
|
|
||||||
|
// This lock is to protect the RA from being updated while being
|
||||||
|
// transmitted on another thread (multicast or unicast).
|
||||||
|
//
|
||||||
|
// TODO: This should be handled with a more RCU-like approach.
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private final byte[] mRA = new byte[IPV6_MIN_MTU];
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private int mRaLength;
|
||||||
|
|
||||||
|
private volatile FileDescriptor mSocket;
|
||||||
|
private volatile MulticastTransmitter mMulticastTransmitter;
|
||||||
|
private volatile UnicastResponder mUnicastResponder;
|
||||||
|
|
||||||
|
public static class RaParams {
|
||||||
|
public boolean hasDefaultRoute;
|
||||||
|
public int mtu;
|
||||||
|
public HashSet<IpPrefix> prefixes;
|
||||||
|
public HashSet<Inet6Address> dnses;
|
||||||
|
|
||||||
|
public RaParams() {
|
||||||
|
hasDefaultRoute = false;
|
||||||
|
mtu = IPV6_MIN_MTU;
|
||||||
|
prefixes = new HashSet<IpPrefix>();
|
||||||
|
dnses = new HashSet<Inet6Address>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaParams(RaParams other) {
|
||||||
|
hasDefaultRoute = other.hasDefaultRoute;
|
||||||
|
mtu = other.mtu;
|
||||||
|
prefixes = (HashSet) other.prefixes.clone();
|
||||||
|
dnses = (HashSet) other.dnses.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public RouterAdvertisementDaemon(String ifname, int ifindex, byte[] hwaddr) {
|
||||||
|
mIfName = ifname;
|
||||||
|
mIfIndex = ifindex;
|
||||||
|
mHwAddr = hwaddr;
|
||||||
|
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildNewRa(RaParams params) {
|
||||||
|
if (params == null || params.prefixes.isEmpty()) {
|
||||||
|
// No RA to be served at this time.
|
||||||
|
clearRa();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.mtu < IPV6_MIN_MTU) {
|
||||||
|
params.mtu = IPV6_MIN_MTU;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteBuffer ra = ByteBuffer.wrap(mRA);
|
||||||
|
ra.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
try {
|
||||||
|
putHeader(ra, params.hasDefaultRoute);
|
||||||
|
putSlla(ra, mHwAddr);
|
||||||
|
// https://tools.ietf.org/html/rfc5175#section-4 says:
|
||||||
|
//
|
||||||
|
// "MUST NOT be added to a Router Advertisement message
|
||||||
|
// if no flags in the option are set."
|
||||||
|
//
|
||||||
|
// putExpandedFlagsOption(ra);
|
||||||
|
putMtu(ra, params.mtu);
|
||||||
|
for (IpPrefix ipp : params.prefixes) {
|
||||||
|
putPio(ra, ipp);
|
||||||
|
}
|
||||||
|
if (params.dnses.size() > 0) {
|
||||||
|
putRdnss(ra, params.dnses);
|
||||||
|
}
|
||||||
|
mRaLength = ra.position();
|
||||||
|
} catch (BufferOverflowException e) {
|
||||||
|
Log.e(TAG, "Could not construct new RA: " + e);
|
||||||
|
mRaLength = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeNotifyMulticastTransmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start() {
|
||||||
|
if (!createSocket()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mMulticastTransmitter = new MulticastTransmitter();
|
||||||
|
mMulticastTransmitter.start();
|
||||||
|
|
||||||
|
mUnicastResponder = new UnicastResponder();
|
||||||
|
mUnicastResponder.start();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
closeSocket();
|
||||||
|
mMulticastTransmitter = null;
|
||||||
|
mUnicastResponder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearRa() {
|
||||||
|
boolean notifySocket;
|
||||||
|
synchronized (mLock) {
|
||||||
|
notifySocket = (mRaLength != 0);
|
||||||
|
mRaLength = 0;
|
||||||
|
}
|
||||||
|
if (notifySocket) {
|
||||||
|
maybeNotifyMulticastTransmitter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeNotifyMulticastTransmitter() {
|
||||||
|
final MulticastTransmitter m = mMulticastTransmitter;
|
||||||
|
if (m != null) {
|
||||||
|
m.hup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Inet6Address getAllNodesForScopeId(int scopeId) {
|
||||||
|
try {
|
||||||
|
return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
|
||||||
|
} catch (UnknownHostException uhe) {
|
||||||
|
Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte asByte(int value) { return (byte) value; }
|
||||||
|
private static short asShort(int value) { return (short) value; }
|
||||||
|
|
||||||
|
private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute) {
|
||||||
|
/**
|
||||||
|
Router Advertisement Message Format
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Code | Checksum |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Reachable Time |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Retrans Timer |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Options ...
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-
|
||||||
|
*/
|
||||||
|
final byte DEFAULT_HOPLIMIT = 64;
|
||||||
|
ra.put(ICMPV6_ND_ROUTER_ADVERT)
|
||||||
|
.put(asByte(0))
|
||||||
|
.putShort(asShort(0))
|
||||||
|
.put(DEFAULT_HOPLIMIT)
|
||||||
|
// RFC 4191 "high" preference, iff. advertising a default route.
|
||||||
|
.put(hasDefaultRoute ? asByte(0x08) : asByte(0))
|
||||||
|
.putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0))
|
||||||
|
.putInt(0)
|
||||||
|
.putInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putSlla(ByteBuffer ra, byte[] slla) {
|
||||||
|
/**
|
||||||
|
Source/Target Link-layer Address
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Link-Layer Address ...
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
if (slla == null || slla.length != 6) {
|
||||||
|
// Only IEEE 802.3 6-byte addresses are supported.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte ND_OPTION_SLLA = 1;
|
||||||
|
final byte SLLA_NUM_8OCTETS = 1;
|
||||||
|
ra.put(ND_OPTION_SLLA)
|
||||||
|
.put(SLLA_NUM_8OCTETS)
|
||||||
|
.put(slla);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putExpandedFlagsOption(ByteBuffer ra) {
|
||||||
|
/**
|
||||||
|
Router Advertisement Expanded Flags Option
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Bit fields available ..
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
... for assignment |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
final byte ND_OPTION_EFO = 26;
|
||||||
|
final byte EFO_NUM_8OCTETS = 1;
|
||||||
|
|
||||||
|
ra.put(ND_OPTION_EFO)
|
||||||
|
.put(EFO_NUM_8OCTETS)
|
||||||
|
.putShort(asShort(0))
|
||||||
|
.putInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putMtu(ByteBuffer ra, int mtu) {
|
||||||
|
/**
|
||||||
|
MTU
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Reserved |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| MTU |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
final byte ND_OPTION_MTU = 5;
|
||||||
|
final byte MTU_NUM_8OCTETS = 1;
|
||||||
|
ra.put(ND_OPTION_MTU)
|
||||||
|
.put(MTU_NUM_8OCTETS)
|
||||||
|
.putShort(asShort(0))
|
||||||
|
.putInt(mtu);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putPio(ByteBuffer ra, IpPrefix ipp) {
|
||||||
|
/**
|
||||||
|
Prefix Information
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Prefix Length |L|A| Reserved1 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Valid Lifetime |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Preferred Lifetime |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Reserved2 |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| |
|
||||||
|
+ +
|
||||||
|
| |
|
||||||
|
+ Prefix +
|
||||||
|
| |
|
||||||
|
+ +
|
||||||
|
| |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
final int prefixLength = ipp.getPrefixLength();
|
||||||
|
if (prefixLength != 64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte ND_OPTION_PIO = 3;
|
||||||
|
final byte PIO_NUM_8OCTETS = 4;
|
||||||
|
|
||||||
|
final byte[] addr = ipp.getAddress().getAddress();
|
||||||
|
ra.put(ND_OPTION_PIO)
|
||||||
|
.put(PIO_NUM_8OCTETS)
|
||||||
|
.put(asByte(prefixLength))
|
||||||
|
.put(asByte(0xc0)) // L&A set
|
||||||
|
.putInt(DEFAULT_LIFETIME)
|
||||||
|
.putInt(DEFAULT_LIFETIME)
|
||||||
|
.putInt(0)
|
||||||
|
.put(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putRio(ByteBuffer ra, IpPrefix ipp) {
|
||||||
|
/**
|
||||||
|
Route Information Option
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Prefix Length |Resvd|Prf|Resvd|
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Route Lifetime |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Prefix (Variable Length) |
|
||||||
|
. .
|
||||||
|
. .
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
final int prefixLength = ipp.getPrefixLength();
|
||||||
|
if (prefixLength > 64) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte ND_OPTION_RIO = 24;
|
||||||
|
final byte RIO_NUM_8OCTETS = asByte(
|
||||||
|
(prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3);
|
||||||
|
|
||||||
|
final byte[] addr = ipp.getAddress().getAddress();
|
||||||
|
ra.put(ND_OPTION_RIO)
|
||||||
|
.put(RIO_NUM_8OCTETS)
|
||||||
|
.put(asByte(prefixLength))
|
||||||
|
.put(asByte(0x18))
|
||||||
|
.putInt(DEFAULT_LIFETIME);
|
||||||
|
|
||||||
|
// Rely upon an IpPrefix's address being properly zeroed.
|
||||||
|
if (prefixLength > 0) {
|
||||||
|
ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses) {
|
||||||
|
/**
|
||||||
|
Recursive DNS Server (RDNSS) Option
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Type | Length | Reserved |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Lifetime |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| |
|
||||||
|
: Addresses of IPv6 Recursive DNS Servers :
|
||||||
|
| |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
*/
|
||||||
|
|
||||||
|
final byte ND_OPTION_RDNSS = 25;
|
||||||
|
final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1);
|
||||||
|
ra.put(ND_OPTION_RDNSS)
|
||||||
|
.put(RDNSS_NUM_8OCTETS)
|
||||||
|
.putShort(asShort(0))
|
||||||
|
.putInt(DEFAULT_LIFETIME);
|
||||||
|
|
||||||
|
for (Inet6Address dns : dnses) {
|
||||||
|
ra.put(dns.getAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean createSocket() {
|
||||||
|
final int SEND_TIMEOUT_MS = 300;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
|
||||||
|
// Setting SNDTIMEO is purely for defensive purposes.
|
||||||
|
Os.setsockoptTimeval(
|
||||||
|
mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(SEND_TIMEOUT_MS));
|
||||||
|
Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName);
|
||||||
|
NetworkUtils.protectFromVpn(mSocket);
|
||||||
|
NetworkUtils.setupRaSocket(mSocket, mIfIndex);
|
||||||
|
} catch (ErrnoException | IOException e) {
|
||||||
|
Log.e(TAG, "Failed to create RA daemon socket: " + e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSocket() {
|
||||||
|
if (mSocket != null) {
|
||||||
|
IoUtils.closeQuietly(mSocket);
|
||||||
|
}
|
||||||
|
mSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSocketValid() {
|
||||||
|
final FileDescriptor s = mSocket;
|
||||||
|
return (s != null) && s.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSuitableDestination(InetSocketAddress dest) {
|
||||||
|
if (mAllNodes.equals(dest)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final InetAddress destip = dest.getAddress();
|
||||||
|
return (destip instanceof Inet6Address) &&
|
||||||
|
destip.isLinkLocalAddress() &&
|
||||||
|
(((Inet6Address) destip).getScopeId() == mIfIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeSendRA(InetSocketAddress dest) {
|
||||||
|
if (dest == null || !isSuitableDestination(dest)) {
|
||||||
|
dest = mAllNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mRaLength < MIN_RA_HEADER_SIZE) {
|
||||||
|
// No actual RA to send.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress());
|
||||||
|
} catch (ErrnoException | SocketException e) {
|
||||||
|
if (isSocketValid()) {
|
||||||
|
Log.e(TAG, "sendto error: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class UnicastResponder extends Thread {
|
||||||
|
private final InetSocketAddress solicitor = new InetSocketAddress();
|
||||||
|
// The recycled buffer for receiving Router Solicitations from clients.
|
||||||
|
// If the RS is larger than IPV6_MIN_MTU the packets are truncated.
|
||||||
|
// This is fine since currently only byte 0 is examined anyway.
|
||||||
|
private final byte mSolication[] = new byte[IPV6_MIN_MTU];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isSocketValid()) {
|
||||||
|
try {
|
||||||
|
// Blocking receive.
|
||||||
|
final int rval = Os.recvfrom(
|
||||||
|
mSocket, mSolication, 0, mSolication.length, 0, solicitor);
|
||||||
|
// Do the least possible amount of validation.
|
||||||
|
if (rval < 1 || mSolication[0] != ICMPV6_ND_ROUTER_SOLICIT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (ErrnoException | SocketException e) {
|
||||||
|
if (isSocketValid()) {
|
||||||
|
Log.e(TAG, "recvfrom error: " + e);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeSendRA(solicitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider moving this to run on a provided Looper as a Handler,
|
||||||
|
// with WakeupMessage-style messages providing the timer driven input.
|
||||||
|
private final class MulticastTransmitter extends Thread {
|
||||||
|
private final Random mRandom = new Random();
|
||||||
|
private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isSocketValid()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(getNextMulticastTransmitDelayMs());
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
// Stop sleeping, immediately send an RA, and continue.
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeSendRA(mAllNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hup() {
|
||||||
|
// Set to one fewer that the desired number, because as soon as
|
||||||
|
// the thread interrupt is processed we immediately send an RA
|
||||||
|
// and mUrgentAnnouncements is not examined until the subsequent
|
||||||
|
// sleep interval computation (i.e. this way we send 3 and not 4).
|
||||||
|
mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1);
|
||||||
|
interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNextMulticastTransmitDelaySec() {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (mRaLength < MIN_RA_HEADER_SIZE) {
|
||||||
|
// No actual RA to send; just sleep for 1 day.
|
||||||
|
return DAY_IN_SECONDS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final int urgentPending = mUrgentAnnouncements.getAndDecrement();
|
||||||
|
if (urgentPending > 0) {
|
||||||
|
return MIN_DELAY_BETWEEN_RAS_SEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt(
|
||||||
|
MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getNextMulticastTransmitDelayMs() {
|
||||||
|
return 1000 * (long) getNextMulticastTransmitDelaySec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user