Have ConnectivityService install packet filters when possible
am: 578a76e7de
* commit '578a76e7de77492ac33e407fff4fb9a2f5550d8a':
Have ConnectivityService install packet filters when possible
This commit is contained in:
@@ -200,6 +200,14 @@ public abstract class NetworkAgent extends Handler {
|
||||
*/
|
||||
public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
|
||||
|
||||
/**
|
||||
* Sent by ConnectivityService to the NetworkAgent to install an APF program in the network
|
||||
* chipset for use to filter packets.
|
||||
*
|
||||
* obj = byte[] containing the APF program bytecode.
|
||||
*/
|
||||
public static final int CMD_PUSH_APF_PROGRAM = BASE + 16;
|
||||
|
||||
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
|
||||
NetworkCapabilities nc, LinkProperties lp, int score) {
|
||||
this(looper, context, logTag, ni, nc, lp, score, null);
|
||||
@@ -319,6 +327,10 @@ public abstract class NetworkAgent extends Handler {
|
||||
preventAutomaticReconnect();
|
||||
break;
|
||||
}
|
||||
case CMD_PUSH_APF_PROGRAM: {
|
||||
installPacketFilter((byte[]) msg.obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,6 +506,15 @@ public abstract class NetworkAgent extends Handler {
|
||||
protected void preventAutomaticReconnect() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a packet filter.
|
||||
* @param filter an APF program to filter incoming packets.
|
||||
* @return {@code true} if filter successfully installed, {@code false} otherwise.
|
||||
*/
|
||||
protected boolean installPacketFilter(byte[] filter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void log(String s) {
|
||||
Log.d(LOG_TAG, "NetworkAgent: " + s);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,22 @@ public class NetworkMisc implements Parcelable {
|
||||
*/
|
||||
public String subscriberId;
|
||||
|
||||
/**
|
||||
* Version of APF instruction set supported for packet filtering. 0 indicates no support for
|
||||
* packet filtering using APF programs.
|
||||
*/
|
||||
public int apfVersionSupported;
|
||||
|
||||
/**
|
||||
* Maximum size of APF program allowed.
|
||||
*/
|
||||
public int maximumApfProgramSize;
|
||||
|
||||
/**
|
||||
* Format of packets passed to APF filter. Should be one of ARPHRD_*
|
||||
*/
|
||||
public int apfPacketFormat;
|
||||
|
||||
public NetworkMisc() {
|
||||
}
|
||||
|
||||
@@ -65,6 +81,9 @@ public class NetworkMisc implements Parcelable {
|
||||
explicitlySelected = nm.explicitlySelected;
|
||||
acceptUnvalidated = nm.acceptUnvalidated;
|
||||
subscriberId = nm.subscriberId;
|
||||
apfVersionSupported = nm.apfVersionSupported;
|
||||
maximumApfProgramSize = nm.maximumApfProgramSize;
|
||||
apfPacketFormat = nm.apfPacketFormat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +98,9 @@ public class NetworkMisc implements Parcelable {
|
||||
out.writeInt(explicitlySelected ? 1 : 0);
|
||||
out.writeInt(acceptUnvalidated ? 1 : 0);
|
||||
out.writeString(subscriberId);
|
||||
out.writeInt(apfVersionSupported);
|
||||
out.writeInt(maximumApfProgramSize);
|
||||
out.writeInt(apfPacketFormat);
|
||||
}
|
||||
|
||||
public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
|
||||
@@ -89,6 +111,9 @@ public class NetworkMisc implements Parcelable {
|
||||
networkMisc.explicitlySelected = in.readInt() != 0;
|
||||
networkMisc.acceptUnvalidated = in.readInt() != 0;
|
||||
networkMisc.subscriberId = in.readString();
|
||||
networkMisc.apfVersionSupported = in.readInt();
|
||||
networkMisc.maximumApfProgramSize = in.readInt();
|
||||
networkMisc.apfPacketFormat = in.readInt();
|
||||
return networkMisc;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,13 @@ public class NetworkUtils {
|
||||
*/
|
||||
public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
|
||||
|
||||
/**
|
||||
* Attaches a socket filter that accepts ICMP6 router advertisement packets to the given socket.
|
||||
* @param fd the socket's {@link FileDescriptor}.
|
||||
* @param packetType the hardware address type, one of ARPHRD_*.
|
||||
*/
|
||||
public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -26,10 +26,13 @@
|
||||
#include <net/if.h>
|
||||
#include <linux/filter.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <net/if_ether.h>
|
||||
#include <netinet/icmp6.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip6.h>
|
||||
#include <netinet/udp.h>
|
||||
#include <cutils/properties.h>
|
||||
|
||||
@@ -64,10 +67,9 @@ static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
|
||||
|
||||
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
|
||||
{
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
uint32_t ip_offset = sizeof(ether_header);
|
||||
uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol);
|
||||
uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
|
||||
uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
|
||||
uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest);
|
||||
struct sock_filter filter_code[] = {
|
||||
// Check the protocol is UDP.
|
||||
@@ -94,6 +96,45 @@ static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobje
|
||||
filter_code,
|
||||
};
|
||||
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
|
||||
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||
"setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject javaFd,
|
||||
jint hardwareAddressType)
|
||||
{
|
||||
if (hardwareAddressType != ARPHRD_ETHER) {
|
||||
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||
"attachRaFilter only supports ARPHRD_ETHER");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t ipv6_offset = sizeof(ether_header);
|
||||
uint32_t ipv6_next_header_offset = ipv6_offset + offsetof(ip6_hdr, ip6_nxt);
|
||||
uint32_t icmp6_offset = ipv6_offset + sizeof(ip6_hdr);
|
||||
uint32_t icmp6_type_offset = icmp6_offset + offsetof(icmp6_hdr, icmp6_type);
|
||||
struct sock_filter filter_code[] = {
|
||||
// Check IPv6 Next Header is ICMPv6.
|
||||
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, ipv6_next_header_offset),
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
|
||||
|
||||
// Check ICMPv6 type is Router Advertisement.
|
||||
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, icmp6_type_offset),
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
|
||||
|
||||
// Accept or reject.
|
||||
BPF_STMT(BPF_RET | BPF_K, 0xffff),
|
||||
BPF_STMT(BPF_RET | BPF_K, 0)
|
||||
};
|
||||
struct sock_fprog filter = {
|
||||
sizeof(filter_code) / sizeof(filter_code[0]),
|
||||
filter_code,
|
||||
};
|
||||
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
|
||||
jniThrowExceptionFmt(env, "java/net/SocketException",
|
||||
"setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
|
||||
@@ -148,6 +189,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
|
||||
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
|
||||
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
||||
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
||||
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
||||
};
|
||||
|
||||
int register_android_net_NetworkUtils(JNIEnv* env)
|
||||
|
||||
@@ -125,6 +125,7 @@ import com.android.server.connectivity.NetworkAgentInfo;
|
||||
import com.android.server.connectivity.NetworkMonitor;
|
||||
import com.android.server.connectivity.PacManager;
|
||||
import com.android.server.connectivity.PermissionMonitor;
|
||||
import com.android.server.connectivity.ApfFilter;
|
||||
import com.android.server.connectivity.Tethering;
|
||||
import com.android.server.connectivity.Vpn;
|
||||
import com.android.server.net.BaseNetworkObserver;
|
||||
@@ -353,6 +354,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
*/
|
||||
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
|
||||
|
||||
/**
|
||||
* used to push APF program to NetworkAgent
|
||||
* replyTo = NetworkAgent message handler
|
||||
* obj = byte[] of APF program
|
||||
*/
|
||||
private static final int EVENT_PUSH_APF_PROGRAM_TO_NETWORK = 32;
|
||||
|
||||
/** Handler thread used for both of the handlers below. */
|
||||
@VisibleForTesting
|
||||
protected final HandlerThread mHandlerThread;
|
||||
@@ -2190,6 +2198,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
mKeepaliveTracker.handleStopAllKeepalives(nai,
|
||||
ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK);
|
||||
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
|
||||
if (nai.apfFilter != null) nai.apfFilter.shutdown();
|
||||
mNetworkAgentInfos.remove(msg.replyTo);
|
||||
updateClat(null, nai.linkProperties, nai);
|
||||
synchronized (mNetworkForNetId) {
|
||||
@@ -2404,6 +2413,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
accept ? 1 : 0, always ? 1: 0, network));
|
||||
}
|
||||
|
||||
public void pushApfProgramToNetwork(NetworkAgentInfo nai, byte[] program) {
|
||||
enforceConnectivityInternalPermission();
|
||||
Message msg = mHandler.obtainMessage(EVENT_PUSH_APF_PROGRAM_TO_NETWORK, program);
|
||||
msg.replyTo = nai.messenger;
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
|
||||
if (DBG) log("handleSetAcceptUnvalidated network=" + network +
|
||||
" accept=" + accept + " always=" + always);
|
||||
@@ -2553,6 +2569,16 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
handleMobileDataAlwaysOn();
|
||||
break;
|
||||
}
|
||||
case EVENT_PUSH_APF_PROGRAM_TO_NETWORK: {
|
||||
NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
|
||||
if (nai == null) {
|
||||
loge("EVENT_PUSH_APF_PROGRAM_TO_NETWORK from unknown NetworkAgent");
|
||||
} else {
|
||||
nai.asyncChannel.sendMessage(NetworkAgent.CMD_PUSH_APF_PROGRAM,
|
||||
(byte[]) msg.obj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Sent by KeepaliveTracker to process an app request on the state machine thread.
|
||||
case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
|
||||
mKeepaliveTracker.handleStartKeepalive(msg);
|
||||
@@ -4068,6 +4094,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
if (networkAgent.clatd != null) {
|
||||
networkAgent.clatd.fixupLinkProperties(oldLp);
|
||||
}
|
||||
if (networkAgent.apfFilter != null) {
|
||||
networkAgent.apfFilter.updateFilter();
|
||||
}
|
||||
|
||||
updateInterfaces(newLp, oldLp, netId);
|
||||
updateMtu(newLp, oldLp);
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* 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 com.android.server.connectivity;
|
||||
|
||||
import static android.system.OsConstants.*;
|
||||
|
||||
import android.net.NetworkUtils;
|
||||
import android.net.apf.ApfGenerator;
|
||||
import android.net.apf.ApfGenerator.IllegalInstructionException;
|
||||
import android.net.apf.ApfGenerator.Register;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.PacketSocketAddress;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
import com.android.server.ConnectivityService;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.lang.Thread;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import libcore.io.IoBridge;
|
||||
|
||||
/**
|
||||
* For networks that support packet filtering via APF programs, {@code ApfFilter}
|
||||
* listens for IPv6 ICMPv6 router advertisements (RAs) and generates APF programs to
|
||||
* filter out redundant duplicate ones.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ApfFilter {
|
||||
// Thread to listen for RAs.
|
||||
private class ReceiveThread extends Thread {
|
||||
private final byte[] mPacket = new byte[1514];
|
||||
private final FileDescriptor mSocket;
|
||||
private volatile boolean mStopped;
|
||||
|
||||
public ReceiveThread(FileDescriptor socket) {
|
||||
mSocket = socket;
|
||||
}
|
||||
|
||||
public void halt() {
|
||||
mStopped = true;
|
||||
try {
|
||||
// Interrupts the read() call the thread is blocked in.
|
||||
IoBridge.closeAndSignalBlockedThreads(mSocket);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
log("begin monitoring");
|
||||
while (!mStopped) {
|
||||
try {
|
||||
int length = Os.read(mSocket, mPacket, 0, mPacket.length);
|
||||
processRa(mPacket, length);
|
||||
} catch (IOException|ErrnoException e) {
|
||||
if (!mStopped) {
|
||||
Log.e(TAG, "Read error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "ApfFilter";
|
||||
|
||||
private final ConnectivityService mConnectivityService;
|
||||
private final NetworkAgentInfo mNai;
|
||||
private ReceiveThread mReceiveThread;
|
||||
private String mIfaceName;
|
||||
private long mUniqueCounter;
|
||||
|
||||
private ApfFilter(ConnectivityService connectivityService, NetworkAgentInfo nai) {
|
||||
mConnectivityService = connectivityService;
|
||||
mNai = nai;
|
||||
maybeStartFilter();
|
||||
}
|
||||
|
||||
private void log(String s) {
|
||||
Log.d(TAG, "(" + mNai.network.netId + "): " + s);
|
||||
}
|
||||
|
||||
private long getUniqueNumber() {
|
||||
return mUniqueCounter++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to start listening for RAs and, if RAs are received, generating and installing
|
||||
* filters to ignore useless RAs.
|
||||
*/
|
||||
private void maybeStartFilter() {
|
||||
mIfaceName = mNai.linkProperties.getInterfaceName();
|
||||
if (mIfaceName == null) return;
|
||||
FileDescriptor socket;
|
||||
try {
|
||||
socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
|
||||
PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6,
|
||||
NetworkInterface.getByName(mIfaceName).getIndex());
|
||||
Os.bind(socket, addr);
|
||||
NetworkUtils.attachRaFilter(socket, mNai.networkMisc.apfPacketFormat);
|
||||
} catch(SocketException|ErrnoException e) {
|
||||
Log.e(TAG, "Error filtering raw socket", e);
|
||||
return;
|
||||
}
|
||||
mReceiveThread = new ReceiveThread(socket);
|
||||
mReceiveThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* mNai's LinkProperties may have changed, take appropriate action.
|
||||
*/
|
||||
public void updateFilter() {
|
||||
// If we're not listening for RAs, try starting.
|
||||
if (mReceiveThread == null) {
|
||||
maybeStartFilter();
|
||||
// If interface name has changed, restart.
|
||||
} else if (!mIfaceName.equals(mNai.linkProperties.getInterfaceName())) {
|
||||
shutdown();
|
||||
maybeStartFilter();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns seconds since Unix Epoch.
|
||||
private static long curTime() {
|
||||
return System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
|
||||
// A class to hold information about an RA.
|
||||
private class Ra {
|
||||
private static final int ETH_HEADER_LEN = 14;
|
||||
|
||||
private static final int IPV6_HEADER_LEN = 40;
|
||||
|
||||
// From RFC4861:
|
||||
private static final int ICMP6_RA_HEADER_LEN = 16;
|
||||
private static final int ICMP6_RA_OPTION_OFFSET =
|
||||
ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
|
||||
private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
|
||||
ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
|
||||
private static final int ICMP6_RA_ROUTER_LIFETIME_LEN = 2;
|
||||
// Prefix information option.
|
||||
private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
|
||||
private static final int ICMP6_PREFIX_OPTION_LEN = 32;
|
||||
private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
|
||||
private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN = 4;
|
||||
private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
|
||||
private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN = 4;
|
||||
|
||||
// From RFC6106: Recursive DNS Server option
|
||||
private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
|
||||
// From RFC6106: DNS Search List option
|
||||
private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
|
||||
|
||||
// From RFC4191: Route Information option
|
||||
private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
|
||||
// Above three options all have the same format:
|
||||
private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
|
||||
private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
|
||||
|
||||
private final ByteBuffer mPacket;
|
||||
// List of binary ranges that include the whole packet except the lifetimes.
|
||||
// Pairs consist of offset and length.
|
||||
private final ArrayList<Pair<Integer, Integer>> mNonLifetimes =
|
||||
new ArrayList<Pair<Integer, Integer>>();
|
||||
// Minimum lifetime in packet
|
||||
long mMinLifetime;
|
||||
// When the packet was last captured, in seconds since Unix Epoch
|
||||
long mLastSeen;
|
||||
|
||||
/**
|
||||
* Add a binary range of the packet that does not include a lifetime to mNonLifetimes.
|
||||
* Assumes mPacket.position() is as far as we've parsed the packet.
|
||||
* @param lastNonLifetimeStart offset within packet of where the last binary range of
|
||||
* data not including a lifetime.
|
||||
* @param lifetimeOffset offset from mPacket.position() to the next lifetime data.
|
||||
* @param lifetimeLength length of the next lifetime data.
|
||||
* @return offset within packet of where the next binary range of data not including
|
||||
* a lifetime. This can be passed into the next invocation of this function
|
||||
* via {@code lastNonLifetimeStart}.
|
||||
*/
|
||||
private int addNonLifetime(int lastNonLifetimeStart, int lifetimeOffset,
|
||||
int lifetimeLength) {
|
||||
lifetimeOffset += mPacket.position();
|
||||
mNonLifetimes.add(new Pair<Integer, Integer>(lastNonLifetimeStart,
|
||||
lifetimeOffset - lastNonLifetimeStart));
|
||||
return lifetimeOffset + lifetimeLength;
|
||||
}
|
||||
|
||||
// Note that this parses RA and may throw IllegalArgumentException (from
|
||||
// Buffer.position(int) ) or IndexOutOfBoundsException (from ByteBuffer.get(int) ) if
|
||||
// parsing encounters something non-compliant with specifications.
|
||||
Ra(byte[] packet, int length) {
|
||||
mPacket = ByteBuffer.allocate(length).put(ByteBuffer.wrap(packet, 0, length));
|
||||
mPacket.clear();
|
||||
mLastSeen = curTime();
|
||||
|
||||
// Parse router lifetime
|
||||
int lastNonLifetimeStart = addNonLifetime(0, ICMP6_RA_ROUTER_LIFETIME_OFFSET,
|
||||
ICMP6_RA_ROUTER_LIFETIME_LEN);
|
||||
// Parse ICMP6 options
|
||||
mPacket.position(ICMP6_RA_OPTION_OFFSET);
|
||||
while (mPacket.hasRemaining()) {
|
||||
int optionType = ((int)mPacket.get(mPacket.position())) & 0xff;
|
||||
int optionLength = (((int)mPacket.get(mPacket.position() + 1)) & 0xff) * 8;
|
||||
switch (optionType) {
|
||||
case ICMP6_PREFIX_OPTION_TYPE:
|
||||
// Parse valid lifetime
|
||||
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
||||
ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET,
|
||||
ICMP6_PREFIX_OPTION_VALID_LIFETIME_LEN);
|
||||
// Parse preferred lifetime
|
||||
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
||||
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET,
|
||||
ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_LEN);
|
||||
break;
|
||||
// These three options have the same lifetime offset and size, so process
|
||||
// together:
|
||||
case ICMP6_ROUTE_INFO_OPTION_TYPE:
|
||||
case ICMP6_RDNSS_OPTION_TYPE:
|
||||
case ICMP6_DNSSL_OPTION_TYPE:
|
||||
// Parse lifetime
|
||||
lastNonLifetimeStart = addNonLifetime(lastNonLifetimeStart,
|
||||
ICMP6_4_BYTE_LIFETIME_OFFSET,
|
||||
ICMP6_4_BYTE_LIFETIME_LEN);
|
||||
break;
|
||||
default:
|
||||
// RFC4861 section 4.2 dictates we ignore unknown options for fowards
|
||||
// compatibility.
|
||||
break;
|
||||
}
|
||||
mPacket.position(mPacket.position() + optionLength);
|
||||
}
|
||||
// Mark non-lifetime bytes since last lifetime.
|
||||
addNonLifetime(lastNonLifetimeStart, 0, 0);
|
||||
mMinLifetime = minLifetime(packet, length);
|
||||
}
|
||||
|
||||
// Ignoring lifetimes (which may change) does {@code packet} match this RA?
|
||||
boolean matches(byte[] packet, int length) {
|
||||
if (length != mPacket.limit()) return false;
|
||||
ByteBuffer a = ByteBuffer.wrap(packet);
|
||||
ByteBuffer b = mPacket;
|
||||
for (Pair<Integer, Integer> nonLifetime : mNonLifetimes) {
|
||||
a.clear();
|
||||
b.clear();
|
||||
a.position(nonLifetime.first);
|
||||
b.position(nonLifetime.first);
|
||||
a.limit(nonLifetime.first + nonLifetime.second);
|
||||
b.limit(nonLifetime.first + nonLifetime.second);
|
||||
if (a.compareTo(b) != 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// What is the minimum of all lifetimes within {@code packet} in seconds?
|
||||
// Precondition: matches(packet, length) already returned true.
|
||||
long minLifetime(byte[] packet, int length) {
|
||||
long minLifetime = Long.MAX_VALUE;
|
||||
// Wrap packet in ByteBuffer so we can read big-endian values easily
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(packet);
|
||||
for (int i = 0; (i + 1) < mNonLifetimes.size(); i++) {
|
||||
int offset = mNonLifetimes.get(i).first + mNonLifetimes.get(i).second;
|
||||
int lifetimeLength = mNonLifetimes.get(i+1).first - offset;
|
||||
long val;
|
||||
switch (lifetimeLength) {
|
||||
case 2: val = byteBuffer.getShort(offset); break;
|
||||
case 4: val = byteBuffer.getInt(offset); break;
|
||||
default: throw new IllegalStateException("bogus lifetime size " + length);
|
||||
}
|
||||
// Mask to size, converting signed to unsigned
|
||||
val &= (1L << (lifetimeLength * 8)) - 1;
|
||||
minLifetime = Math.min(minLifetime, val);
|
||||
}
|
||||
return minLifetime;
|
||||
}
|
||||
|
||||
// How many seconds does this RA's have to live, taking into account the fact
|
||||
// that we might have seen it a while ago.
|
||||
long currentLifetime() {
|
||||
return mMinLifetime - (curTime() - mLastSeen);
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return currentLifetime() < 0;
|
||||
}
|
||||
|
||||
// Append a filter for this RA to {@code gen}. Jump to DROP_LABEL if it should be dropped.
|
||||
// Jump to the next filter if packet doesn't match this RA.
|
||||
long generateFilter(ApfGenerator gen) throws IllegalInstructionException {
|
||||
String nextFilterLabel = "Ra" + getUniqueNumber();
|
||||
// Skip if packet is not the right size
|
||||
gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
|
||||
gen.addJumpIfR0NotEquals(mPacket.limit(), nextFilterLabel);
|
||||
int filterLifetime = (int)(currentLifetime() / FRACTION_OF_LIFETIME_TO_FILTER);
|
||||
// Skip filter if expired
|
||||
gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
|
||||
gen.addJumpIfR0GreaterThan(filterLifetime, nextFilterLabel);
|
||||
for (int i = 0; i < mNonLifetimes.size(); i++) {
|
||||
// Generate code to match the packet bytes
|
||||
Pair<Integer, Integer> nonLifetime = mNonLifetimes.get(i);
|
||||
gen.addLoadImmediate(Register.R0, nonLifetime.first);
|
||||
gen.addJumpIfBytesNotEqual(Register.R0,
|
||||
Arrays.copyOfRange(mPacket.array(), nonLifetime.first,
|
||||
nonLifetime.first + nonLifetime.second),
|
||||
nextFilterLabel);
|
||||
// Generate code to test the lifetimes haven't gone down too far
|
||||
if ((i + 1) < mNonLifetimes.size()) {
|
||||
Pair<Integer, Integer> nextNonLifetime = mNonLifetimes.get(i + 1);
|
||||
int offset = nonLifetime.first + nonLifetime.second;
|
||||
int length = nextNonLifetime.first - offset;
|
||||
switch (length) {
|
||||
case 4: gen.addLoad32(Register.R0, offset); break;
|
||||
case 2: gen.addLoad16(Register.R0, offset); break;
|
||||
default: throw new IllegalStateException("bogus lifetime size " + length);
|
||||
}
|
||||
gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
|
||||
}
|
||||
}
|
||||
gen.addJump(gen.DROP_LABEL);
|
||||
gen.defineLabel(nextFilterLabel);
|
||||
return filterLifetime;
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum number of RAs to filter for.
|
||||
private static final int MAX_RAS = 10;
|
||||
private ArrayList<Ra> mRas = new ArrayList<Ra>();
|
||||
|
||||
// There is always some marginal benefit to updating the installed APF program when an RA is
|
||||
// seen because we can extend the program's lifetime slightly, but there is some cost to
|
||||
// updating the program, so don't bother unless the program is going to expire soon. This
|
||||
// constant defines "soon" in seconds.
|
||||
private static final long MAX_PROGRAM_LIFETIME_WORTH_REFRESHING = 30;
|
||||
// We don't want to filter an RA for it's whole lifetime as it'll be expired by the time we ever
|
||||
// see a refresh. Using half the lifetime might be a good idea except for the fact that
|
||||
// packets may be dropped, so let's use 6.
|
||||
private static final int FRACTION_OF_LIFETIME_TO_FILTER = 6;
|
||||
|
||||
// When did we last install a filter program? In seconds since Unix Epoch.
|
||||
private long mLastTimeInstalledProgram;
|
||||
// How long should the last installed filter program live for? In seconds.
|
||||
private long mLastInstalledProgramMinLifetime;
|
||||
|
||||
private void installNewProgram() {
|
||||
if (mRas.size() == 0) return;
|
||||
final byte[] program;
|
||||
long programMinLifetime = Long.MAX_VALUE;
|
||||
try {
|
||||
ApfGenerator gen = new ApfGenerator();
|
||||
// This is guaranteed to return true because of the check in maybeInstall.
|
||||
gen.setApfVersion(mNai.networkMisc.apfVersionSupported);
|
||||
// Step 1: Determine how many RA filters we can fit in the program.
|
||||
int ras = 0;
|
||||
for (Ra ra : mRas) {
|
||||
if (ra.isExpired()) continue;
|
||||
ra.generateFilter(gen);
|
||||
if (gen.programLengthOverEstimate() > mNai.networkMisc.maximumApfProgramSize) {
|
||||
// We went too far. Use prior number of RAs in "ras".
|
||||
break;
|
||||
} else {
|
||||
// Yay! this RA filter fits, increment "ras".
|
||||
ras++;
|
||||
}
|
||||
}
|
||||
// Step 2: Generate RA filters
|
||||
gen = new ApfGenerator();
|
||||
// This is guaranteed to return true because of the check in maybeInstall.
|
||||
gen.setApfVersion(mNai.networkMisc.apfVersionSupported);
|
||||
for (Ra ra : mRas) {
|
||||
if (ras-- == 0) break;
|
||||
if (ra.isExpired()) continue;
|
||||
programMinLifetime = Math.min(programMinLifetime, ra.generateFilter(gen));
|
||||
}
|
||||
// Execution will reach the end of the program if no filters match, which will pass the
|
||||
// packet to the AP.
|
||||
program = gen.generate();
|
||||
} catch (IllegalInstructionException e) {
|
||||
Log.e(TAG, "Program failed to generate: ", e);
|
||||
return;
|
||||
}
|
||||
mLastTimeInstalledProgram = curTime();
|
||||
mLastInstalledProgramMinLifetime = programMinLifetime;
|
||||
hexDump("Installing filter: ", program, program.length);
|
||||
mConnectivityService.pushApfProgramToNetwork(mNai, program);
|
||||
}
|
||||
|
||||
// Install a new filter program if the last installed one will die soon.
|
||||
private void maybeInstallNewProgram() {
|
||||
if (mRas.size() == 0) return;
|
||||
// If the current program doesn't expire for a while, don't bother updating.
|
||||
long expiry = mLastTimeInstalledProgram + mLastInstalledProgramMinLifetime;
|
||||
if (expiry < curTime() + MAX_PROGRAM_LIFETIME_WORTH_REFRESHING) {
|
||||
installNewProgram();
|
||||
}
|
||||
}
|
||||
|
||||
private void hexDump(String msg, byte[] packet, int length) {
|
||||
log(msg + HexDump.toHexString(packet, 0, length));
|
||||
}
|
||||
|
||||
private void processRa(byte[] packet, int length) {
|
||||
hexDump("Read packet = ", packet, length);
|
||||
|
||||
// Have we seen this RA before?
|
||||
for (int i = 0; i < mRas.size(); i++) {
|
||||
Ra ra = mRas.get(i);
|
||||
if (ra.matches(packet, length)) {
|
||||
log("matched RA");
|
||||
// Update lifetimes.
|
||||
ra.mLastSeen = curTime();
|
||||
ra.mMinLifetime = ra.minLifetime(packet, length);
|
||||
|
||||
// Keep mRas in LRU order so as to prioritize generating filters for recently seen
|
||||
// RAs. LRU prioritizes this because RA filters are generated in order from mRas
|
||||
// until the filter program exceeds the maximum filter program size allowed by the
|
||||
// chipset, so RAs appearing earlier in mRas are more likely to make it into the
|
||||
// filter program.
|
||||
// TODO: consider sorting the RAs in order of increasing expiry time as well.
|
||||
// Swap to front of array.
|
||||
mRas.add(0, mRas.remove(i));
|
||||
|
||||
maybeInstallNewProgram();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Purge expired RAs.
|
||||
for (int i = 0; i < mRas.size();) {
|
||||
if (mRas.get(i).isExpired()) {
|
||||
log("expired RA");
|
||||
mRas.remove(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
// TODO: figure out how to proceed when we've received more then MAX_RAS RAs.
|
||||
if (mRas.size() >= MAX_RAS) return;
|
||||
try {
|
||||
log("adding RA");
|
||||
mRas.add(new Ra(packet, length));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error parsing RA: " + e);
|
||||
return;
|
||||
}
|
||||
installNewProgram();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an {@link ApfFilter} on {@code nai} if {@code nai} supports packet
|
||||
* filtering using APF programs.
|
||||
*/
|
||||
public static void maybeInstall(ConnectivityService connectivityService, NetworkAgentInfo nai) {
|
||||
if (nai.networkMisc == null) return;
|
||||
if (nai.networkMisc.apfVersionSupported == 0) return;
|
||||
if (nai.networkMisc.maximumApfProgramSize < 200) {
|
||||
Log.e(TAG, "Uselessly small APF size limit: " + nai.networkMisc.maximumApfProgramSize);
|
||||
return;
|
||||
}
|
||||
// For now only support generating programs for Ethernet frames. If this restriction is
|
||||
// lifted:
|
||||
// 1. the program generator will need its offsets adjusted.
|
||||
// 2. the packet filter attached to our packet socket will need its offset adjusted.
|
||||
if (nai.networkMisc.apfPacketFormat != ARPHRD_ETHER) return;
|
||||
if (!new ApfGenerator().setApfVersion(nai.networkMisc.apfVersionSupported)) {
|
||||
Log.e(TAG, "Unsupported APF version: " + nai.networkMisc.apfVersionSupported);
|
||||
return;
|
||||
}
|
||||
nai.apfFilter = new ApfFilter(connectivityService, nai);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (mReceiveThread != null) {
|
||||
log("shuting down");
|
||||
mReceiveThread.halt(); // Also closes socket.
|
||||
mReceiveThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import android.util.SparseArray;
|
||||
import com.android.internal.util.AsyncChannel;
|
||||
import com.android.server.ConnectivityService;
|
||||
import com.android.server.connectivity.NetworkMonitor;
|
||||
import com.android.server.connectivity.ApfFilter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -163,6 +164,8 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
// Used by ConnectivityService to keep track of 464xlat.
|
||||
public Nat464Xlat clatd;
|
||||
|
||||
public ApfFilter apfFilter;
|
||||
|
||||
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
|
||||
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
|
||||
NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
|
||||
@@ -175,6 +178,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
|
||||
currentScore = score;
|
||||
networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
|
||||
networkMisc = misc;
|
||||
apfFilter.maybeInstall(connService, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user