Move ApfFilter from ConnectivityService to IpManager

There's a few advantages to having ApfFilter in IpManager:
1. If things go wrong, crashing a particular transport is less bad then
   crashing ConnectivityService.  We also don't want to use
   ConnectivityService as a dumping ground for transport-specific logic.
2. This makes implementing WifiManager.MulticastLock a lot simpler and
   safer because enabling/disabling it doesn't have to go through the
   NetworkAgent, which could risk various races (e.g. installing a filter
   into the wrong WiFi network).
3. IpManager is the ultimate source for LinkProperties for a particular
   transport and since ApfFilter uses the LinkProperties it's better to
   have it closely paired with the IpManager. Likewise, ApfFilter needs
   to know the APF capabilities of the transport, so having it in
   the transport avoids having to parcel this information through the
   NetworkAgent.

Bug: 26238573
Change-Id: I99b85f2b64972f0e7572170ec5d1926081aa3429
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 9e360e1..20c2168 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -200,14 +200,6 @@
      */
     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);
@@ -327,10 +319,6 @@
                 preventAutomaticReconnect();
                 break;
             }
-            case CMD_PUSH_APF_PROGRAM: {
-                installPacketFilter((byte[]) msg.obj);
-                break;
-            }
         }
     }
 
@@ -506,15 +494,6 @@
     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);
     }
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 748699e..5511a24 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -56,22 +56,6 @@
      */
     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() {
     }
 
@@ -81,9 +65,6 @@
             explicitlySelected = nm.explicitlySelected;
             acceptUnvalidated = nm.acceptUnvalidated;
             subscriberId = nm.subscriberId;
-            apfVersionSupported = nm.apfVersionSupported;
-            maximumApfProgramSize = nm.maximumApfProgramSize;
-            apfPacketFormat = nm.apfPacketFormat;
         }
     }
 
@@ -98,9 +79,6 @@
         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>() {
@@ -111,9 +89,6 @@
             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;
         }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d88ddfd..95d3cc3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -126,7 +126,6 @@
 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;
@@ -355,13 +354,6 @@
      */
     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;
@@ -1803,20 +1795,6 @@
         }
     }
 
-    private void dumpApf(IndentingPrintWriter pw) {
-        pw.println("APF filters:");
-        pw.increaseIndent();
-        for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-            if (nai.apfFilter != null) {
-                pw.println(nai.name() + ":");
-                pw.increaseIndent();
-                nai.apfFilter.dump(pw);
-                pw.decreaseIndent();
-            }
-        }
-        pw.decreaseIndent();
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
@@ -1834,11 +1812,6 @@
             return;
         }
 
-        if (argsContain(args, "apf")) {
-            dumpApf(pw);
-            return;
-        }
-
         pw.print("NetworkFactories for:");
         for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
             pw.print(" " + nfi.name);
@@ -1902,7 +1875,6 @@
         mKeepaliveTracker.dump(pw);
 
         pw.println();
-        dumpApf(pw);
 
         if (mInetLog != null && mInetLog.size() > 0) {
             pw.println();
@@ -2224,7 +2196,6 @@
             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) {
@@ -2439,13 +2410,6 @@
                 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);
@@ -2595,16 +2559,6 @@
                     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);
@@ -4186,9 +4140,6 @@
         if (networkAgent.clatd != null) {
             networkAgent.clatd.fixupLinkProperties(oldLp);
         }
-        if (networkAgent.apfFilter != null) {
-            networkAgent.apfFilter.updateFilter();
-        }
 
         updateInterfaces(newLp, oldLp, netId);
         updateMtu(newLp, oldLp);
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index b4c71c1..c5d38cb 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -32,7 +32,6 @@
 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;
@@ -164,8 +163,6 @@
     // 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) {
@@ -178,7 +175,6 @@
         currentScore = score;
         networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
-        apfFilter.maybeInstall(connService, this);
     }
 
     /**
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
new file mode 100644
index 0000000..f169411
--- /dev/null
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * 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.apf;
+
+/**
+ * APF program support capabilities.
+ *
+ * @hide
+ */
+public class ApfCapabilities {
+    /**
+     * Version of APF instruction set supported for packet filtering. 0 indicates no support for
+     * packet filtering using APF programs.
+     */
+    public final int apfVersionSupported;
+
+    /**
+     * Maximum size of APF program allowed.
+     */
+    public final int maximumApfProgramSize;
+
+    /**
+     * Format of packets passed to APF filter. Should be one of ARPHRD_*
+     */
+    public final int apfPacketFormat;
+
+    ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+        this.apfVersionSupported = apfVersionSupported;
+        this.maximumApfProgramSize = maximumApfProgramSize;
+        this.apfPacketFormat = apfPacketFormat;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/connectivity/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
similarity index 92%
rename from services/core/java/com/android/server/connectivity/ApfFilter.java
rename to services/net/java/android/net/apf/ApfFilter.java
index 35f3583..ebbf991 100644
--- a/services/core/java/com/android/server/connectivity/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity;
+package android.net.apf;
 
 import static android.system.OsConstants.*;
 
@@ -22,6 +22,7 @@
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
@@ -31,7 +32,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.ConnectivityService;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -136,24 +136,27 @@
     // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
     private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
 
-    private final ConnectivityService mConnectivityService;
-    private final NetworkAgentInfo mNai;
+    private final ApfCapabilities mApfCapabilities;
+    private final IpManager.Callback mIpManagerCallback;
+    private final NetworkInterface mNetworkInterface;
+    private byte[] mHardwareAddress;
     private ReceiveThread mReceiveThread;
-    private String mIfaceName;
-    private byte[] mIfaceMac;
     @GuardedBy("this")
     private long mUniqueCounter;
     @GuardedBy("this")
     private boolean mMulticastFilter;
 
-    private ApfFilter(ConnectivityService connectivityService, NetworkAgentInfo nai) {
-        mConnectivityService = connectivityService;
-        mNai = nai;
+    private ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
+            IpManager.Callback ipManagerCallback) {
+        mApfCapabilities = apfCapabilities;
+        mIpManagerCallback = ipManagerCallback;
+        mNetworkInterface = networkInterface;
+
         maybeStartFilter();
     }
 
     private void log(String s) {
-        Log.d(TAG, "(" + mNai.network.netId + "): " + s);
+        Log.d(TAG, "(" + mNetworkInterface.getName() + "): " + s);
     }
 
     @GuardedBy("this")
@@ -166,27 +169,18 @@
      * filters to ignore useless RAs.
      */
     private void maybeStartFilter() {
-        mIfaceName = mNai.linkProperties.getInterfaceName();
-        if (mIfaceName == null) return;
         FileDescriptor socket;
         try {
-            NetworkInterface networkInterface = NetworkInterface.getByName(mIfaceName);
-            if (networkInterface == null) {
-                Log.e(TAG, "Can't find interface " + mIfaceName);
-                return;
-            }
-            mIfaceMac = networkInterface.getHardwareAddress();
-
+            mHardwareAddress = mNetworkInterface.getHardwareAddress();
             synchronized(this) {
                 // Install basic filters
                 installNewProgramLocked();
             }
-
             socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
             PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6,
-                    networkInterface.getIndex());
+                    mNetworkInterface.getIndex());
             Os.bind(socket, addr);
-            NetworkUtils.attachRaFilter(socket, mNai.networkMisc.apfPacketFormat);
+            NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
         } catch(SocketException|ErrnoException e) {
             Log.e(TAG, "Error starting filter", e);
             return;
@@ -195,20 +189,6 @@
         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;
@@ -574,7 +554,7 @@
         gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
         // NOTE: Relies on R1 containing IPv4 header offset.
         gen.addAddR1();
-        gen.addJumpIfBytesNotEqual(Register.R0, mIfaceMac, gen.DROP_LABEL);
+        gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, gen.DROP_LABEL);
 
         // Otherwise, pass
         gen.addJump(gen.PASS_LABEL);
@@ -632,7 +612,7 @@
     private ApfGenerator beginProgramLocked() throws IllegalInstructionException {
         ApfGenerator gen = new ApfGenerator();
         // This is guaranteed to return true because of the check in maybeCreate.
-        gen.setApfVersion(mNai.networkMisc.apfVersionSupported);
+        gen.setApfVersion(mApfCapabilities.apfVersionSupported);
 
         // Here's a basic summary of what the initial program does:
         //
@@ -673,7 +653,7 @@
             for (Ra ra : mRas) {
                 ra.generateFilterLocked(gen);
                 // Stop if we get too big.
-                if (gen.programLengthOverEstimate() > mNai.networkMisc.maximumApfProgramSize) break;
+                if (gen.programLengthOverEstimate() > mApfCapabilities.maximumApfProgramSize) break;
                 rasToFilter.add(ra);
             }
             // Step 2: Actually generate the program
@@ -694,7 +674,7 @@
         if (VDBG) {
             hexDump("Installing filter: ", program, program.length);
         }
-        mConnectivityService.pushApfProgramToNetwork(mNai, program);
+        mIpManagerCallback.installPacketFilter(program);
     }
 
     // Install a new filter program if the last installed one will die soon.
@@ -768,26 +748,27 @@
     }
 
     /**
-     * Install an {@link ApfFilter} on {@code nai} if {@code nai} supports packet
+     * Create an {@link ApfFilter} if {@code apfCapabilities} indicates support for 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 < 512) {
-            Log.e(TAG, "Unacceptably small APF limit: " + nai.networkMisc.maximumApfProgramSize);
-            return;
+    public static ApfFilter maybeCreate(ApfCapabilities apfCapabilities,
+            NetworkInterface networkInterface, IpManager.Callback ipManagerCallback) {
+        if (apfCapabilities == null || networkInterface == null) return null;
+        if (apfCapabilities.apfVersionSupported == 0) return null;
+        if (apfCapabilities.maximumApfProgramSize < 512) {
+            Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
+            return null;
         }
         // 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;
+        if (apfCapabilities.apfPacketFormat != ARPHRD_ETHER) return null;
+        if (!new ApfGenerator().setApfVersion(apfCapabilities.apfVersionSupported)) {
+            Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
+            return null;
         }
-        nai.apfFilter = new ApfFilter(connectivityService, nai);
+        return new ApfFilter(apfCapabilities, networkInterface, ipManagerCallback);
     }
 
     public synchronized void shutdown() {
@@ -807,8 +788,8 @@
     }
 
     public synchronized void dump(IndentingPrintWriter pw) {
-        pw.println("APF version: " + mNai.networkMisc.apfVersionSupported);
-        pw.println("Max program size: " + mNai.networkMisc.maximumApfProgramSize);
+        pw.println("APF version: " + mApfCapabilities.apfVersionSupported);
+        pw.println("Max program size: " + mApfCapabilities.maximumApfProgramSize);
         pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
         if (mLastTimeInstalledProgram == 0) {
             pw.println("No program installed.");
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index c7c5015..66c7909 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -19,6 +19,8 @@
 import com.android.internal.util.MessageUtils;
 
 import android.content.Context;
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
 import android.net.DhcpResults;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
@@ -38,10 +40,12 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.NetlinkTracker;
 
+import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
@@ -108,6 +112,9 @@
 
         // Called when the IpManager state machine terminates.
         public void onQuit() {}
+
+        // Install an APF program to filter incoming packets.
+        public void installPacketFilter(byte[] filter) {}
     }
 
     public static class WaitForProvisioningCallback extends Callback {
@@ -179,6 +186,11 @@
                 return this;
             }
 
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                mConfig.mApfCapabilities = apfCapabilities;
+                return this;
+            }
+
             public ProvisioningConfiguration build() {
                 return new ProvisioningConfiguration(mConfig);
             }
@@ -187,6 +199,7 @@
         /* package */ boolean mUsingIpReachabilityMonitor = true;
         /* package */ boolean mRequestedPreDhcpAction;
         /* package */ StaticIpConfiguration mStaticIpConfig;
+        /* package */ ApfCapabilities mApfCapabilities;
 
         public ProvisioningConfiguration() {}
 
@@ -194,6 +207,7 @@
             mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
             mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
             mStaticIpConfig = other.mStaticIpConfig;
+            mApfCapabilities = other.mApfCapabilities;
         }
     }
 
@@ -229,7 +243,7 @@
     private final INetworkManagementService mNwService;
     private final NetlinkTracker mNetlinkTracker;
 
-    private int mInterfaceIndex;
+    private NetworkInterface mNetworkInterface;
 
     /**
      * Non-final member variables accessed only from within our StateMachine.
@@ -240,6 +254,7 @@
     private DhcpResults mDhcpResults;
     private String mTcpBufferSizes;
     private ProxyInfo mHttpProxy;
+    private ApfFilter mApfFilter;
 
     /**
      * Member variables accessed both from within the StateMachine thread
@@ -325,7 +340,7 @@
     }
 
     public void startProvisioning(ProvisioningConfiguration req) {
-        getInterfaceIndex();
+        getNetworkInterface();
         sendMessage(CMD_START, new ProvisioningConfiguration(req));
     }
 
@@ -378,6 +393,19 @@
         }
     }
 
+    public void dumpApf(PrintWriter writer) {
+        writer.println("--------------------------------------------------------------------");
+        writer.println("APF dump:");
+        // Thread-unsafe access to mApfFilter but just used for debugging.
+        ApfFilter apfFilter = mApfFilter;
+        if (apfFilter != null) {
+            apfFilter.dump(new IndentingPrintWriter(writer, "  "));
+        } else {
+            writer.println("No apf support");
+        }
+        writer.println("--------------------------------------------------------------------");
+    }
+
 
     /**
      * Internals.
@@ -392,7 +420,7 @@
     protected String getLogRecString(Message msg) {
         final String logLine = String.format(
                 "iface{%s/%d} arg1{%d} arg2{%d} obj{%s}",
-                mInterfaceName, mInterfaceIndex,
+                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
                 msg.arg1, msg.arg2, Objects.toString(msg.obj));
         if (VDBG) {
             Log.d(mTag, getWhatToString(msg.what) + " " + logLine);
@@ -400,12 +428,12 @@
         return logLine;
     }
 
-    private void getInterfaceIndex() {
+    private void getNetworkInterface() {
         try {
-            mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex();
+            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
         } catch (SocketException | NullPointerException e) {
             // TODO: throw new IllegalStateException.
-            Log.e(mTag, "ALERT: Failed to get interface index: ", e);
+            Log.e(mTag, "ALERT: Failed to get interface object: ", e);
         }
     }
 
@@ -732,6 +760,8 @@
     class StartedState extends State {
         @Override
         public void enter() {
+            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
+                    mCallback);
             // Set privacy extensions.
             try {
                 mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
@@ -788,6 +818,11 @@
                 mDhcpClient.doQuit();
             }
 
+            if (mApfFilter != null) {
+                mApfFilter.shutdown();
+                mApfFilter = null;
+            }
+
             resetLinkProperties();
         }