Merge "Don't accept RAs in link-local only mode."
diff --git a/common/moduleutils/src/android/net/util/SharedLog.java b/common/moduleutils/src/android/net/util/SharedLog.java
index 4fabf10..9c3cbd3 100644
--- a/common/moduleutils/src/android/net/util/SharedLog.java
+++ b/common/moduleutils/src/android/net/util/SharedLog.java
@@ -19,11 +19,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.text.TextUtils;
-import android.util.LocalLog;
 import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.StringJoiner;
 
 
@@ -43,7 +45,7 @@
         ERROR,
         MARK,
         WARN,
-    };
+    }
 
     private final LocalLog mLocalLog;
     // The tag to use for output to the system log. This is not output to the
@@ -89,7 +91,7 @@
      * <p>This method may be called on any thread.
      */
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
+        mLocalLog.dump(writer);
     }
 
     //////
@@ -181,7 +183,7 @@
 
     private String record(Category category, String msg) {
         final String entry = logLine(category, msg);
-        mLocalLog.log(entry);
+        mLocalLog.append(entry);
         return entry;
     }
 
@@ -198,4 +200,33 @@
     private boolean isRootLogInstance() {
         return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
     }
+
+    private static final class LocalLog {
+        private final Deque<String> mLog;
+        private final int mMaxLines;
+
+        LocalLog(int maxLines) {
+            mMaxLines = Math.max(0, maxLines);
+            mLog = new ArrayDeque<>(mMaxLines);
+        }
+
+        synchronized void append(String logLine) {
+            if (mMaxLines <= 0) return;
+            while (mLog.size() >= mMaxLines) {
+                mLog.remove();
+            }
+            mLog.add(LocalDateTime.now() + " - " + logLine);
+        }
+
+        /**
+         * Dumps the content of local log to print writer with each log entry
+         *
+         * @param pw printer writer to write into
+         */
+        synchronized void dump(PrintWriter pw) {
+            for (final String s : mLog) {
+                pw.println(s);
+            }
+        }
+    }
 }
diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java
index ff0aafe..a94a2d6 100644
--- a/src/android/net/ip/IpClientLinkObserver.java
+++ b/src/android/net/ip/IpClientLinkObserver.java
@@ -18,8 +18,11 @@
 
 import static android.net.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION;
 import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.AF_UNSPEC;
+import static android.system.OsConstants.IFF_LOOPBACK;
 
 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.netlink.NetlinkConstants.IFF_LOWER_UP;
 
 import android.app.AlarmManager;
 import android.content.Context;
@@ -37,6 +40,8 @@
 import com.android.net.module.util.netlink.NduseroptMessage;
 import com.android.net.module.util.netlink.NetlinkConstants;
 import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.RtNetlinkLinkMessage;
+import com.android.net.module.util.netlink.StructIfinfoMsg;
 import com.android.net.module.util.netlink.StructNdOptPref64;
 import com.android.net.module.util.netlink.StructNdOptRdnss;
 import com.android.networkstack.apishim.NetworkInformationShimImpl;
@@ -137,9 +142,9 @@
         mInterfaceLinkState = true; // Assume up by default
         mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mDependencies = deps;
         mNetlinkMonitor = new MyNetlinkMonitor(h, log, mTag);
         mHandler.post(mNetlinkMonitor::start);
-        mDependencies = deps;
     }
 
     public void shutdown() {
@@ -183,12 +188,10 @@
 
     @Override
     public void onInterfaceLinkStateChanged(String iface, boolean state) {
-        if (mInterfaceName.equals(iface)) {
-            maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
-            synchronized (this) {
-                setInterfaceLinkStateLocked(state);
-            }
-        }
+        if (isNetlinkEventParsingEnabled()) return;
+        if (!mInterfaceName.equals(iface)) return;
+        maybeLog("interfaceLinkStateChanged", iface + (state ? " up" : " down"));
+        updateInterfaceLinkStateChanged(state);
     }
 
     @Override
@@ -259,11 +262,15 @@
     public void onInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
         if (isNetlinkEventParsingEnabled()) return;
         if (!mInterfaceName.equals(iface)) return;
-        maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
         updateInterfaceDnsServerInfo(lifetime, addresses);
     }
 
+    private synchronized void updateInterfaceLinkStateChanged(boolean state) {
+        setInterfaceLinkStateLocked(state);
+    }
+
     private void updateInterfaceDnsServerInfo(long lifetime, final String[] addresses) {
+        maybeLog("interfaceDnsServerInfo", Arrays.toString(addresses));
         final boolean changed = mDnsServerRepository.addServers(lifetime, addresses);
         final boolean linkState;
         if (changed) {
@@ -314,14 +321,17 @@
     }
 
     /**
-     * Simple NetlinkMonitor. Currently only listens for PREF64 events.
+     * Simple NetlinkMonitor. Listen for netlink events from kernel.
      * All methods except the constructor must be called on the handler thread.
      */
     private class MyNetlinkMonitor extends NetlinkMonitor {
         private final Handler mHandler;
 
         MyNetlinkMonitor(Handler h, SharedLog log, String tag) {
-            super(h, log, tag, OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_ND_USEROPT);
+            super(h, log, tag, OsConstants.NETLINK_ROUTE,
+                    !isNetlinkEventParsingEnabled()
+                    ? NetlinkConstants.RTMGRP_ND_USEROPT
+                    : (NetlinkConstants.RTMGRP_ND_USEROPT | NetlinkConstants.RTMGRP_LINK));
             mHandler = h;
         }
 
@@ -451,10 +461,39 @@
             }
         }
 
+        private void processRtNetlinkLinkMessage(RtNetlinkLinkMessage msg) {
+            if (!isNetlinkEventParsingEnabled()) return;
+
+            final StructIfinfoMsg ifinfoMsg = msg.getIfinfoHeader();
+            if (ifinfoMsg.family != AF_UNSPEC || ifinfoMsg.index != mIfindex) return;
+            if ((ifinfoMsg.flags & IFF_LOOPBACK) != 0) return;
+
+            switch (msg.getHeader().nlmsg_type) {
+                case NetlinkConstants.RTM_NEWLINK:
+                    final boolean state = (ifinfoMsg.flags & IFF_LOWER_UP) != 0;
+                    maybeLog("interfaceLinkStateChanged", "ifindex " + mIfindex
+                            + (state ? " up" : " down"));
+                    updateInterfaceLinkStateChanged(state);
+                    break;
+
+                case NetlinkConstants.RTM_DELLINK:
+                    break;
+
+                default:
+                    Log.e(mTag, "Unknown rtnetlink link msg type " + msg.getHeader().nlmsg_type);
+                    break;
+            }
+        }
+
         @Override
         protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
-            if (!(nlMsg instanceof NduseroptMessage)) return;
-            processNduseroptMessage((NduseroptMessage) nlMsg, whenMs);
+            if (nlMsg instanceof NduseroptMessage) {
+                processNduseroptMessage((NduseroptMessage) nlMsg, whenMs);
+            } else if (nlMsg instanceof RtNetlinkLinkMessage) {
+                processRtNetlinkLinkMessage((RtNetlinkLinkMessage) nlMsg);
+            } else {
+                Log.e(mTag, "Unknown netlink message: " + nlMsg);
+            }
         }
     }
 
diff --git a/tests/unit/src/android/net/ip/IpClientTest.java b/tests/unit/src/android/net/ip/IpClientTest.java
index a37d259..173a9e1 100644
--- a/tests/unit/src/android/net/ip/IpClientTest.java
+++ b/tests/unit/src/android/net/ip/IpClientTest.java
@@ -16,6 +16,7 @@
 
 package android.net.ip;
 
+import static android.net.util.NetworkStackUtils.IPCLIENT_PARSE_NETLINK_EVENTS_VERSION;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -25,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -164,6 +166,8 @@
         when(mDependencies.getIpMemoryStore(mContext, mNetworkStackServiceManager))
                 .thenReturn(mIpMemoryStore);
         when(mDependencies.getIpConnectivityLog()).thenReturn(mMetricsLog);
+        when(mDependencies.isFeatureEnabled(eq(mContext),
+                eq(IPCLIENT_PARSE_NETLINK_EVENTS_VERSION), anyBoolean())).thenReturn(false);
 
         mIfParams = null;
     }