ANDROID: Hack to support ABI stable accept_ra_min_lft

This is an Android-only hack (ab)using padding in ipv6_devconf to
implement an ABI-stable version of accept_ra_min_lft.

accept_ra_min_lft applies to all lifetimes in an RA and ignores the
respective RA section if its lifetime is lower than the specified value.
This determines the minimum frequency at which RAs must be processed by
the kernel. Android uses hardware offloads to drop RAs for a fraction of
the minimum of all lifetimes present in the RA (some networks have very
frequent RAs (5s) with high lifetimes (2h)). Despite this, we have
encountered networks that set the router lifetime to 30s which results
in very frequent CPU wakeups. Instead of disabling IPv6 (and dropping
IPv6 ethertype in the WiFi firmware) entirely on such networks, it seems
better to ignore the misconfigured routers while still processing RAs
from other IPv6 routers on the same network (i.e.  to support IoT
applications).

This change consists of adapted versions of the following upstream
changes (which will be available in 6.5):

- net-next 1671bcfd76fd ("net: add sysctl accept_ra_min_rtr_lft")
- net-next 5027d54a9c30 ("net: change accept_ra_min_rtr_lft to affect all RA lifetimes")
- net-next 5cb249686e67 ("net: release reference to inet6_dev pointer")

Bug: 290863811
Bug: 296625638
Bug: 297704110
Test: net tests (aosp/2714695)
Signed-off-by: Patrick Rohr <prohr@google.com>
(cherry picked from https://android-review.googlesource.com/q/commit:ae0fb8d614c267b5197bf7ac1dca4a6d85b52200)
Merged-In: I9a90fd77b13d3285f4cff6391a5b50654c0001df
Change-Id: I9a90fd77b13d3285f4cff6391a5b50654c0001df
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 8011bcf..6e94d6e 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2081,6 +2081,17 @@
 
 	Default: 1
 
+accept_ra_min_lft - INTEGER
+	Minimum acceptable lifetime value in Router Advertisement.
+
+	RA sections with a lifetime less than this value shall be
+	ignored. Zero lifetimes stay unaffected.
+
+	Possible values: 0-65535
+
+	Default: 0
+
+
 accept_ra_pinfo - BOOLEAN
 	Learn Prefix Information in Router Advertisement.
 
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index ab1fff4..f44c4e6 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -4,6 +4,7 @@
 
 #include <uapi/linux/ipv6.h>
 #include <linux/android_kabi.h>
+#include <linux/build_bug.h>
 
 #define ipv6_optlen(p)  (((p)->hdrlen+1) << 3)
 #define ipv6_authlen(p) (((p)->hdrlen+2) << 2)
@@ -81,6 +82,7 @@
 	__u32		ioam6_id;
 	__u32		ioam6_id_wide;
 	__u8		ioam6_enabled;
+	/* ANDROID HACK: 2 byte padding used for __u16 accept_ra_min_lft */
 
 	struct ctl_table_header *sysctl_header;
 
@@ -90,6 +92,17 @@
 	ANDROID_KABI_RESERVE(4);
 };
 
+/* Assert that there is actually padding where accept_ra_min_lft is placed */
+static_assert(offsetof(struct ipv6_devconf, sysctl_header) -
+	      offsetof(struct ipv6_devconf, ioam6_enabled) >=
+	      sizeof(((struct ipv6_devconf *)0)->ioam6_enabled) + sizeof(__u16));
+
+/* The ACCEPT_RA_MIN_LFT macro relies on ioam6_enabled being a u8 */
+static_assert(sizeof(((struct ipv6_devconf *)0)->ioam6_enabled) == 1);
+
+#define ACCEPT_RA_MIN_LFT(cfg) \
+	(*((__u16 *)(&(cfg).ioam6_enabled + 1)))
+
 struct ipv6_params {
 	__s32 disable_ipv6;
 	__s32 autoconf;
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index ee5261af..89e0edf6 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2751,6 +2751,9 @@
 		return;
 	}
 
+	if (valid_lft != 0 && valid_lft < ACCEPT_RA_MIN_LFT(in6_dev->cnf))
+		goto put;
+
 	/*
 	 *	Two things going on here:
 	 *	1) Add routes for on-link prefixes
@@ -6252,6 +6255,28 @@
 	return proc_dointvec_minmax(&lctl, write, buffer, lenp, ppos);
 }
 
+static int addrconf_sysctl_accept_ra_min_lft(struct ctl_table *ctl, int write,
+					     void *buffer, size_t *lenp, loff_t *ppos)
+{
+	struct ctl_table tmp = *ctl;
+	unsigned int min = 0, max = 65535U, val;
+	u16 *data = ctl->data;
+	int res;
+
+	tmp.maxlen = sizeof(val);
+	tmp.data = &val;
+	tmp.extra1 = &min;
+	tmp.extra2 = &max;
+	val = READ_ONCE(*data);
+
+	res = proc_douintvec_minmax(&tmp, write, buffer, lenp, ppos);
+	if (res)
+		return res;
+	if (write)
+		WRITE_ONCE(*data, val);
+	return 0;
+}
+
 static void dev_disable_change(struct inet6_dev *idev)
 {
 	struct netdev_notifier_info info;
@@ -6800,6 +6825,13 @@
 		.proc_handler	= proc_dointvec,
 	},
 	{
+		.procname	= "accept_ra_min_lft",
+		.data		= &ACCEPT_RA_MIN_LFT(ipv6_devconf),
+		.maxlen		= sizeof(u16),
+		.mode		= 0644,
+		.proc_handler	= addrconf_sysctl_accept_ra_min_lft,
+	},
+	{
 		.procname	= "accept_ra_pinfo",
 		.data		= &ipv6_devconf.accept_ra_pinfo,
 		.maxlen		= sizeof(int),
@@ -7251,6 +7283,10 @@
 	struct inet6_dev *idev;
 	int i, err;
 
+	/* 0 initialize slot for accept_ra_min_lft */
+	ACCEPT_RA_MIN_LFT(ipv6_devconf) = 0;
+	ACCEPT_RA_MIN_LFT(ipv6_devconf_dflt) = 0;
+
 	err = ipv6_addr_label_init();
 	if (err < 0) {
 		pr_crit("%s: cannot initialize default policy table: %d\n",
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index 8108e9a..fbfcd8c 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -1269,6 +1269,14 @@
 		goto skip_defrtr;
 	}
 
+	lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
+	if (lifetime != 0 && lifetime < ACCEPT_RA_MIN_LFT(in6_dev->cnf)) {
+		ND_PRINTK(2, info,
+			  "RA: router lifetime (%ds) is too short: %s\n",
+			  lifetime, skb->dev->name);
+		goto skip_defrtr;
+	}
+
 	/* Do not accept RA with source-addr found on local machine unless
 	 * accept_ra_from_local is set to true.
 	 */
@@ -1281,8 +1289,6 @@
 		goto skip_defrtr;
 	}
 
-	lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
-
 #ifdef CONFIG_IPV6_ROUTER_PREF
 	pref = ra_msg->icmph.icmp6_router_pref;
 	/* 10b is handled as if it were 00b (medium) */
@@ -1453,6 +1459,9 @@
 			if (ri->prefix_len == 0 &&
 			    !in6_dev->cnf.accept_ra_defrtr)
 				continue;
+			if (ri->lifetime != 0 &&
+			    ntohl(ri->lifetime) < ACCEPT_RA_MIN_LFT(in6_dev->cnf))
+				continue;
 			if (ri->prefix_len < in6_dev->cnf.accept_ra_rt_info_min_plen)
 				continue;
 			if (ri->prefix_len > in6_dev->cnf.accept_ra_rt_info_max_plen)