diff mbox

[RFC] ipv6: stable privacy addresses

Message ID 20131213085549.GA3685@order.stressinduktion.org
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Hannes Frederic Sowa Dec. 13, 2013, 8:55 a.m. UTC
Hi!

This is a preview for the interface I envison for IPv6 stable privacy
addresses. This patch has still issues: I experience some problems with
dev_mc_add mc-addresses not forwarded to macvtaps here on my VM.

-- >8 --

This patch implements stable privacy addresses as currently specified
as a draft by Fernando Gont:

<http://tools.ietf.org/html/draft-ietf-6man-stable-privacy-addresses-16>

One can assume this will become a RFC any time soon. Recent updates
had only minor changes. For details please have a look at the draft.

Some time ago <http://tools.ietf.org/html/draft-iesg-serno-privacy-00>
stated that
"	Protocols intended to be used over the global Internet SHOULD NOT
depend on the inclusion of hardware serial numbers.  Protocols intended
to be used only in a local IP-based network, which use hardware serial
numbers, SHOULD define a means to keep those serial numbers from escaping
into the global Internet.
".

Despite the existence of Privacy Extensions Fernando Gont tries to abandon
EUI-64 based address for link-local as well as for global address scope
as it is too easy that the old EUI-64 based addresses could leak.

This patch currently only implements the switch of the address generation
mode at compile- or boot-time (as the patch already includes documentation
on how to use it, I'll leave it out here). The stable secret will get
loaded via procfs.

The draft offers more than one version which attributes to hash to
get a stable host identifier.  I decided to pick SHA-1 as the hashing
function and use the device address as the Net_Iface identifier. I did
not implement the optional Network_ID yet.

Futher algorithms can be easily implemented and can be distinguished by
the address generation mode.

Cc: Fernando Gont <fgont@si6networks.com>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
---
 Documentation/kernel-parameters.txt    |  10 +
 Documentation/networking/ip-sysctl.txt |  16 ++
 include/linux/ipv6.h                   |   2 +
 include/net/addrconf.h                 |   4 +
 include/net/if_inet6.h                 |   1 +
 include/net/netns/ipv6.h               |   3 +
 include/uapi/linux/if_addr.h           |   1 +
 include/uapi/linux/ipv6.h              |   2 +
 kernel/x509_certificate_list           |   0
 net/ipv6/Kconfig                       |  41 ++++
 net/ipv6/addrconf.c                    | 341 +++++++++++++++++++++++++++++++--
 net/ipv6/sysctl_net_ipv6.c             |  22 +++
 12 files changed, 425 insertions(+), 18 deletions(-)
 create mode 100644 kernel/x509_certificate_list

Comments

Florent Fourcot Dec. 13, 2013, 10:33 a.m. UTC | #1
> This is a preview for the interface I envison for IPv6 stable privacy
> addresses.


Great!

> +stable_address_secret - UNSIGNED LONG
> +	If stable privacy addresses are enabled then on the first
> +	write to this file link-local addresses are generated and
> +	router solicitions are send out. The generated addresses are

s/solicitions/solicitations/

> +	based on this secret.  If the same secret is reused across
> +	reboots the generated addresses will be the same.
> +

Do you not provide a configuration via netlink for the secret?


> +idgen_delay - INTEGER
> +	Seconds to delay another attempt to generate a stable privacy address.

Perhaps s/Seconds/Milliseconds/ ?



> +config IPV6_ADDRESS_GEN_MODE_STABLE_PRIVACY_V1
> +       bool "Stable privacy address generation v1"
> +       help
> +         Stable privacy address generation mode v1. Further
> +         information can be found here:
> +	 http://tools.ietf.org/html/draft-ietf-6man-stable-privacy-addresses-16
> +
> +	 This option may need support from distributions to install
> +	 the stable secret early at boot up (otherwise link-local
> +	 addresses will be generated too late). If you don't have
> +	 services depending on link-local addresses on boot-up you can
> +	 activate this mode and install the stable secret any time
> +	 later by hand by writing it to
> +	 /proc/sys/net/ipv6/stable_privacy_secret.
> +

Here is your documentation inconsistent with before. The right file is
probably /proc/sys/net/ipv6/stable_address_secret

You configuration is for all interfaces the same, isn't? I would like a
configuration possibility per interface, to mix EUI64 and stable privacy
addresses on the same device.

In the same way, the configuration of the secret per interface could be
useful (simple example: two NIC on the same LAN).


> -static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
> +static void addrconf_dad_stop(struct inet6_ifaddr *ifp, bool dad_failed)
> 

The passage of dad_failed from int to bool is not directly connected
with this feature, can you maybe split it in another patch?


Florent.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Hannes Frederic Sowa Dec. 13, 2013, 11:50 a.m. UTC | #2
On Fri, Dec 13, 2013 at 11:33:23AM +0100, Florent Fourcot wrote:
> Do you not provide a configuration via netlink for the secret?

I definitely will provide a netlink interface. I already have a patch here but
I am not sure how the interface will work out in the end. See below.

> > +config IPV6_ADDRESS_GEN_MODE_STABLE_PRIVACY_V1
> > +       bool "Stable privacy address generation v1"
> > +       help
> > +         Stable privacy address generation mode v1. Further
> > +         information can be found here:
> > +	 http://tools.ietf.org/html/draft-ietf-6man-stable-privacy-addresses-16
> > +
> > +	 This option may need support from distributions to install
> > +	 the stable secret early at boot up (otherwise link-local
> > +	 addresses will be generated too late). If you don't have
> > +	 services depending on link-local addresses on boot-up you can
> > +	 activate this mode and install the stable secret any time
> > +	 later by hand by writing it to
> > +	 /proc/sys/net/ipv6/stable_privacy_secret.
> > +
> 
> Here is your documentation inconsistent with before. The right file is
> probably /proc/sys/net/ipv6/stable_address_secret
> 
> You configuration is for all interfaces the same, isn't? I would like a
> configuration possibility per interface, to mix EUI64 and stable privacy
> addresses on the same device.

You mean like EUI-64 link-local and privacy-stable ones for global scope?
This indeed does sound useful.

> In the same way, the configuration of the secret per interface could be
> useful (simple example: two NIC on the same LAN).

Hmm, I thought about that too, but have not found a valid reason to support
it yet, given the numerous other configuration possibilities.

My current problem is how to make this interface as robust as possible to
use so that distributions can support it.

I just started coding support for tracking DAD-errors accross reboots so
e.g. if a privstbl address has two DAD conflicts before it stabilzes,
it should get the same one after reboot. Ideally we would serialize
this information into user-space and apply it on next boot. But what key
should we use to store the dad-conflicts (MAC or interface name etc. or
make it even configurable?). Should we store information on interfaces
which are not yet registered too, or always kick link-local generation by
hand? When should we store those numbers? If we only do that at shutdown
time we may have lost some manually destroyed interfaces already.

I have a similar problem with virtual interfaces. It would be better to
identify them by name and not by mac-address. Also we should include the name
in the hash, so we could have different kind of hashes per interface.

The hash itself needs to be stable and one particular hash version must not get
touched after release, otherwise we could change people's IPv6 addresses
accross upgrades.

The combination of all those options call for a very complex interface. In the
end I hoped to get basic support into the kernel and leave complex policies to
user-space (that's out of scope for this patch but I already have some ideas).

> > -static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
> > +static void addrconf_dad_stop(struct inet6_ifaddr *ifp, bool dad_failed)
> > 
> 
> The passage of dad_failed from int to bool is not directly connected
> with this feature, can you maybe split it in another patch?

I guess I should do that. But in the end I am almost always too lazy to
submit such patches then. ;)

I'll also improve the documentation in the next versions, thanks for the
corrections! It seems I did not update it regullary enough every time I did 
change in the code.

Thanks for review,

  Hannes

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Florent Fourcot Dec. 16, 2013, 6:27 p.m. UTC | #3
>> You configuration is for all interfaces the same, isn't? I would like a
>> configuration possibility per interface, to mix EUI64 and stable privacy
>> addresses on the same device.
> 
> You mean like EUI-64 link-local and privacy-stable ones for global scope?
> This indeed does sound useful.
> 

Yes, and to mix configuration with several interfaces. One can set the
eth0 interface with EUI-64 for global scope, and wlan0 with
privacy-stable for global scope.

Regards,

Florent.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt
index 50680a5..d50f044 100644
--- a/Documentation/kernel-parameters.txt
+++ b/Documentation/kernel-parameters.txt
@@ -346,6 +346,13 @@  bytes respectively. Such letter suffixes can also be entirely omitted.
 	add_efi_memmap	[EFI; X86] Include EFI memory map in
 			kernel's map of available physical RAM.
 
+	address_generation_mode= [IPV6] Method of address
+				 generation. Following modes are
+				 currently supported:
+
+				    0	EUI-64 address generation
+				    1	stable privacy generation v1
+
 	agp=		[AGP]
 			{ off | try_unsupported }
 			off: disable AGP support
@@ -2995,6 +3002,9 @@  bytes respectively. Such letter suffixes can also be entirely omitted.
 	spia_pedr=
 	spia_peddr=
 
+	stable_privacy_address_secret= [IPV6] Secret for stable
+				       privacy address generation.
+
 	stacktrace	[FTRACE]
 			Enabled the stack tracer on boot up.
 
diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt
index 12ba2cd..db8d481 100644
--- a/Documentation/networking/ip-sysctl.txt
+++ b/Documentation/networking/ip-sysctl.txt
@@ -1084,6 +1084,13 @@  bindv6only - BOOLEAN
 
 	Default: FALSE (as specified in RFC3493)
 
+stable_address_secret - UNSIGNED LONG
+	If stable privacy addresses are enabled then on the first
+	write to this file link-local addresses are generated and
+	router solicitions are send out. The generated addresses are
+	based on this secret.  If the same secret is reused across
+	reboots the generated addresses will be the same.
+
 IPv6 Fragmentation:
 
 ip6frag_high_thresh - INTEGER
@@ -1365,6 +1372,15 @@  suppress_frag_ndisc - INTEGER
 	1 - (default) discard fragmented neighbor discovery packets
 	0 - allow fragmented neighbor discovery packets
 
+idgen_retries - INTEGER
+	Number of retries to generate a stable privacy address in case
+	of a duplicate address.
+	Default: 3
+
+idgen_delay - INTEGER
+	Seconds to delay another attempt to generate a stable privacy address.
+	Default: 1000 (1 second)
+
 icmp/*:
 ratelimit - INTEGER
 	Limit the maximal rates for sending ICMPv6 packets.
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 3fde066..e4d4b15 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -50,6 +50,8 @@  struct ipv6_devconf {
 	__s32		force_tllao;
 	__s32           ndisc_notify;
 	__s32		suppress_frag_ndisc;
+	__s32		idgen_retries;
+	__s32		idgen_delay;
 	void		*sysctl;
 };
 
diff --git a/include/net/addrconf.h b/include/net/addrconf.h
index 66c4a44..1e572f2 100644
--- a/include/net/addrconf.h
+++ b/include/net/addrconf.h
@@ -216,6 +216,10 @@  int inet6addr_notifier_call_chain(unsigned long val, void *v);
 void inet6_netconf_notify_devconf(struct net *net, int type, int ifindex,
 				  struct ipv6_devconf *devconf);
 
+int addrconf_sysctl_stbladdr_secret_change(struct ctl_table *ctl, int write,
+					   void __user *buffer, size_t *lenp,
+					   loff_t *ppos);
+
 /**
  * __in6_dev_get - get inet6_dev pointer from netdevice
  * @dev: network device
diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h
index b58c36c..25b6706 100644
--- a/include/net/if_inet6.h
+++ b/include/net/if_inet6.h
@@ -52,6 +52,7 @@  struct inet6_ifaddr {
 
 	__u32			flags;
 	__u8			dad_probes;
+	__u8			dad_retry_cnt;
 
 	__u16			scope;
 
diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h
index 0fb2401..4ac56d9 100644
--- a/include/net/netns/ipv6.h
+++ b/include/net/netns/ipv6.h
@@ -73,6 +73,9 @@  struct netns_ipv6 {
 #endif
 	atomic_t		dev_addr_genid;
 	atomic_t		rt_genid;
+
+	int			addrgen_mode;
+	unsigned long		stbladdr_secret;
 };
 
 #if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h
index cfed10b..68b94f4 100644
--- a/include/uapi/linux/if_addr.h
+++ b/include/uapi/linux/if_addr.h
@@ -49,6 +49,7 @@  enum {
 #define IFA_F_TENTATIVE		0x40
 #define IFA_F_PERMANENT		0x80
 #define IFA_F_MANAGETEMPADDR	0x100
+#define IFA_F_PRIVSTBLADDR	0x200
 
 struct ifa_cacheinfo {
 	__u32	ifa_prefered;
diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h
index 593b0e3..5238716 100644
--- a/include/uapi/linux/ipv6.h
+++ b/include/uapi/linux/ipv6.h
@@ -163,6 +163,8 @@  enum {
 	DEVCONF_MLDV1_UNSOLICITED_REPORT_INTERVAL,
 	DEVCONF_MLDV2_UNSOLICITED_REPORT_INTERVAL,
 	DEVCONF_SUPPRESS_FRAG_NDISC,
+	DEVCONF_IDGEN_RETRIES,
+	DEVCONF_IDGEN_DELAY,
 	DEVCONF_MAX
 };
 
diff --git a/kernel/x509_certificate_list b/kernel/x509_certificate_list
new file mode 100644
index 0000000..e69de29
diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig
index d92e558..6f30001 100644
--- a/net/ipv6/Kconfig
+++ b/net/ipv6/Kconfig
@@ -21,6 +21,47 @@  menuconfig IPV6
 
 if IPV6
 
+choice
+	prompt "Autoconf address generation mode"
+	default IPV6_ADDRESS_GEN_MODE_EUI64
+	help
+
+	  Selects the default generation mode for IPv6 addresses. This
+	  option specifies the compile-time default. It can be
+	  overwritten by ipv6.address_generation_mode kernel command
+	  line parameter at boot. Please see the specific options for
+	  furhter help.
+
+	  If unsure, select the EUI-64 based address generation (at
+	  least for now).
+
+config IPV6_ADDRESS_GEN_MODE_EUI64
+       bool "EUI-64 based address generation"
+       help
+         Old link-local address generation mode. Use the interface
+         hardware address for IPv6 address generation. This is
+         currently the default mode but it is advised to switch to
+         stable privacy addresses in future. As the other option may
+         need support from distributions to manage the stable secret,
+         stick to this option until it is advised to switch.
+
+config IPV6_ADDRESS_GEN_MODE_STABLE_PRIVACY_V1
+       bool "Stable privacy address generation v1"
+       help
+         Stable privacy address generation mode v1. Further
+         information can be found here:
+	 http://tools.ietf.org/html/draft-ietf-6man-stable-privacy-addresses-16
+
+	 This option may need support from distributions to install
+	 the stable secret early at boot up (otherwise link-local
+	 addresses will be generated too late). If you don't have
+	 services depending on link-local addresses on boot-up you can
+	 activate this mode and install the stable secret any time
+	 later by hand by writing it to
+	 /proc/sys/net/ipv6/stable_privacy_secret.
+
+endchoice
+
 config IPV6_ROUTER_PREF
 	bool "IPv6: Router Preference (RFC 4191) support"
 	---help---
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 3c3425e..a178389 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -43,6 +43,7 @@ 
 #include <linux/errno.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/socket.h>
 #include <linux/sockios.h>
 #include <linux/net.h>
@@ -102,6 +103,20 @@ 
 
 #define	INFINITY_LIFE_TIME	0xFFFFFFFF
 
+static int addrgen_mode_param = -1;
+
+module_param_named(address_generation_mode,
+		   addrgen_mode_param,
+		   int, 0444);
+MODULE_PARM_DESC(address_generation_mode,
+		 "Mode of address generation (0=eui-64, 1=privacy stable)");
+
+module_param_named(stable_privacy_address_secret,
+		   init_net.ipv6.stbladdr_secret,
+		   ulong, 0444);
+MODULE_PARM_DESC(stable_privacy_address_secret,
+		 "Key for stable privacy address generation (non-zero)");
+
 static inline u32 cstamp_delta(unsigned long cstamp)
 {
 	return (cstamp - INITIAL_JIFFIES) * 100UL / HZ;
@@ -163,6 +178,9 @@  static void inet6_prefix_notify(int event, struct inet6_dev *idev,
 static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
 			       struct net_device *dev);
 
+static int ipv6_generate_stblpriv_addr(struct in6_addr *addr,
+				       struct inet6_dev *idev, __u8 dad_count);
+
 static struct ipv6_devconf ipv6_devconf __read_mostly = {
 	.forwarding		= 0,
 	.hop_limit		= IPV6_DEFAULT_HOPLIMIT,
@@ -197,6 +215,8 @@  static struct ipv6_devconf ipv6_devconf __read_mostly = {
 	.disable_ipv6		= 0,
 	.accept_dad		= 1,
 	.suppress_frag_ndisc	= 1,
+	.idgen_retries		= 3,
+	.idgen_delay		= 1 * HZ,
 };
 
 static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
@@ -233,6 +253,8 @@  static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
 	.disable_ipv6		= 0,
 	.accept_dad		= 1,
 	.suppress_frag_ndisc	= 1,
+	.idgen_retries		= 3,
+	.idgen_delay		= 1 * HZ,
 };
 
 /* Check if a valid qdisc is available */
@@ -852,6 +874,7 @@  ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
 	ifa->prefered_lft = prefered_lft;
 	ifa->cstamp = ifa->tstamp = jiffies;
 	ifa->tokenized = false;
+	ifa->dad_retry_cnt = 0;
 
 	ifa->rt = rt;
 
@@ -1570,11 +1593,98 @@  struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net, const struct in6_addr *add
 	return result;
 }
 
+struct __addrconf_idgen_work {
+	struct delayed_work work;
+
+	struct inet6_dev *idev;
+	struct in6_addr addr;
+	int pfxlen;
+	int scope;
+	u32 flags;
+	u32 valid_lft;
+	u32 prefered_lft;
+	int dad_retry_cnt;
+};
+
+static void __addrconf_retry_stblpriv_deferred(struct work_struct *w)
+{
+	struct inet6_ifaddr *ifp;
+	struct __addrconf_idgen_work *work =
+		container_of(w, struct __addrconf_idgen_work, work.work);
+
+	/* protect that the interface is not going to be dead */
+	rtnl_lock();
+	if (work->idev->dead)
+		goto out;
+
+	work->addr.s6_addr32[2] = 0;
+	work->addr.s6_addr32[3] = 0;
+	if (ipv6_generate_stblpriv_addr(&work->addr, work->idev,
+					work->dad_retry_cnt))
+		goto out;
+
+	ifp = ipv6_add_addr(work->idev, &work->addr, NULL, work->pfxlen,
+		      work->scope, work->flags, work->valid_lft,
+		      work->prefered_lft);
+	if (IS_ERR(ifp))
+		goto out;
+
+	spin_lock_bh(&ifp->lock);
+	ifp->dad_retry_cnt = work->dad_retry_cnt;
+	spin_unlock_bh(&ifp->lock);
+	addrconf_dad_start(ifp);
+	in6_ifa_put(ifp);
+out:
+	rtnl_unlock();
+	in6_dev_put(work->idev);
+	kfree(w);
+}
+
+/* called with ifp locked */
+static void addrconf_retry_stblpriv(struct inet6_ifaddr *ifp)
+{
+	struct __addrconf_idgen_work *work;
+
+	addrconf_del_dad_timer(ifp);
+
+	work = kmalloc(sizeof(*work), GFP_ATOMIC);
+	if (!work)
+		return;
+
+	INIT_DELAYED_WORK(&work->work, __addrconf_retry_stblpriv_deferred);
+	work->idev = ifp->idev;
+	in6_dev_hold(work->idev);
+	work->addr = ifp->addr;
+	work->pfxlen = ifp->prefix_len;
+	work->scope = ifp->scope;
+	work->flags = ifp->flags;
+	work->valid_lft = ifp->valid_lft;
+	work->prefered_lft = ifp->prefered_lft;
+	work->dad_retry_cnt = ifp->dad_retry_cnt;
+	schedule_delayed_work(&work->work,
+			      ifp->idev->cnf.idgen_delay);
+}
+
 /* Gets referenced address, destroys ifaddr */
 
-static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
+static void addrconf_dad_stop(struct inet6_ifaddr *ifp, bool dad_failed)
 {
-	if (ifp->flags&IFA_F_PERMANENT) {
+	/* privacy stable addresses may retry DAD with newly
+	 * generated address up to idgen_retries times
+	 */
+	if (dad_failed) {
+		spin_lock_bh(&ifp->lock);
+		if (ifp->flags & IFA_F_PRIVSTBLADDR &&
+		    ifp->dad_retry_cnt < ifp->idev->cnf.idgen_retries) {
+			addrconf_retry_stblpriv(ifp);
+			spin_unlock_bh(&ifp->lock);
+			ipv6_del_addr(ifp);
+			return;
+		}
+		spin_unlock_bh(&ifp->lock);
+	}
+
+	if (ifp->flags & IFA_F_PERMANENT) {
 		spin_lock_bh(&ifp->lock);
 		addrconf_del_dad_timer(ifp);
 		ifp->flags |= IFA_F_TENTATIVE;
@@ -1584,7 +1694,7 @@  static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
 		if (dad_failed)
 			ipv6_ifa_notify(0, ifp);
 		in6_ifa_put(ifp);
-	} else if (ifp->flags&IFA_F_TEMPORARY) {
+	} else if (ifp->flags & IFA_F_TEMPORARY) {
 		struct inet6_ifaddr *ifpub;
 		spin_lock_bh(&ifp->lock);
 		ifpub = ifp->ifpub;
@@ -1597,8 +1707,9 @@  static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
 			spin_unlock_bh(&ifp->lock);
 		}
 		ipv6_del_addr(ifp);
-	} else
+	} else {
 		ipv6_del_addr(ifp);
+	}
 }
 
 static int addrconf_dad_end(struct inet6_ifaddr *ifp)
@@ -1627,14 +1738,20 @@  void addrconf_dad_failure(struct inet6_ifaddr *ifp)
 	net_info_ratelimited("%s: IPv6 duplicate address %pI6c detected!\n",
 			     ifp->idev->dev->name, &ifp->addr);
 
+	spin_lock_bh(&ifp->lock);
+	++ifp->dad_retry_cnt;
+	spin_unlock_bh(&ifp->lock);
+
 	if (idev->cnf.accept_dad > 1 && !idev->cnf.disable_ipv6) {
 		struct in6_addr addr;
 
 		addr.s6_addr32[0] = htonl(0xfe800000);
 		addr.s6_addr32[1] = 0;
 
-		if (!ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) &&
-		    ipv6_addr_equal(&ifp->addr, &addr)) {
+		if ((!ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) &&
+		     ipv6_addr_equal(&ifp->addr, &addr)) ||
+		    (ifp->flags & IFA_F_PRIVSTBLADDR &&
+		     ifp->scope == IFA_LINK)) {
 			/* DAD failed for link-local based on MAC address */
 			idev->cnf.disable_ipv6 = 1;
 
@@ -1643,7 +1760,7 @@  void addrconf_dad_failure(struct inet6_ifaddr *ifp)
 		}
 	}
 
-	addrconf_dad_stop(ifp, 1);
+	addrconf_dad_stop(ifp, true);
 }
 
 /* Join to solicited addr multicast group. */
@@ -1845,6 +1962,101 @@  static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev)
 	return err;
 }
 
+static bool ipv6_valid_stblpriv_hostid(struct in6_addr *addr)
+{
+	/* Subnet-Router Anycast */
+	if ((addr->s6_addr32[2]|addr->s6_addr32[3]) == 0)
+		return false;
+	if (addr->s6_addr32[2] == htonl(0x02005eff)) {
+		/* Reserved IPv6 Interface Identifiers corresponding to the
+		 * IANA Ethernet Block
+		 */
+		if (addr->s6_addr32[3] >= htonl(0xfe000000) &&
+		    addr->s6_addr32[3] <= htonl(0xfe005212))
+			return false;
+		/* Proxy Mobile IPv6 */
+		if (addr->s6_addr32[3] == htonl(0xfe005213))
+			return false;
+		/* Reserved IPv6 Interface Identifiers corresponding to the
+		 * IANA Ethernet Block
+		 */
+		if (addr->s6_addr32[3] >= htonl(0xfe005214) &&
+		    addr->s6_addr32[3] <= htonl(0xfeffffff))
+			return false;
+	}
+	/* Reserved Subnet Anycast Addresses */
+	if (addr->s6_addr32[2] == htonl(0xfdffffff) &&
+	    addr->s6_addr32[3] >= htonl(0xffffff80) &&
+	    addr->s6_addr32[3] <= htonl(0xffffffff))
+		return false;
+	return true;
+}
+
+static int ipv6_generate_stblpriv_addr(struct in6_addr *addr,
+				       struct inet6_dev *idev, __u8 dad_count)
+{
+	/* move large allocations out of the stack */
+	static DEFINE_SPINLOCK(lock);
+	static __u32 workspace[SHA_WORKSPACE_WORDS];
+	static __u32 digest[SHA_DIGEST_WORDS];
+	static __u8 buffer[SHA_MESSAGE_BYTES];
+	const size_t buflen = sizeof(buffer);
+
+	struct net_device *dev = idev->dev;
+	struct net *net = dev_net(dev);
+	int version = net->ipv6.addrgen_mode;
+	int err;
+
+	spin_lock_bh(&lock);
+
+again:
+	sha_init(digest);
+	memset(workspace, 0, sizeof(workspace));
+
+	switch (version) {
+	case 1: {
+		int addr_len = min_t(int, dev->addr_len, buflen);
+
+		memcpy(buffer, addr, sizeof(*addr));
+		memset(buffer + sizeof(*addr), 0,
+		       buflen - sizeof(*addr));
+		sha_transform(digest, buffer, workspace);
+		memcpy(buffer, dev->dev_addr, addr_len);
+		memset(buffer + addr_len, 0,
+		       buflen - addr_len);
+		sha_transform(digest, buffer, workspace);
+		memcpy(buffer, &dad_count, sizeof(dad_count));
+		memset(buffer + sizeof(dad_count), 0,
+		       buflen - sizeof(dad_count));
+		sha_transform(digest, buffer, workspace);
+		memcpy(buffer, &net->ipv6.stbladdr_secret,
+		       sizeof(net->ipv6.stbladdr_secret));
+		memset(buffer + sizeof(net->ipv6.stbladdr_secret), 0,
+		       buflen - sizeof(net->ipv6.stbladdr_secret));
+		sha_transform(digest, buffer, workspace);
+		break;
+	}
+	default:
+		WARN_ON(!version);
+		pr_crit("%s: link-local generation mode %d unavailable. disabling ipv6, reboot needed.\n",
+			__func__, version);
+		idev->cnf.disable_ipv6 = 1;
+		err = -1;
+		goto out;
+	}
+
+	addr->s6_addr32[2] = htonl(digest[0]);
+	addr->s6_addr32[3] = htonl(digest[1]);
+	if (!ipv6_valid_stblpriv_hostid(addr)) {
+		++dad_count;
+		goto again;
+	}
+	err = 0;
+out:
+	spin_unlock_bh(&lock);
+	return err;
+}
+
 /* (re)generation of randomized interface identifier (RFC 3041 3.2, 3.5) */
 static void __ipv6_regen_rndid(struct inet6_dev *idev)
 {
@@ -2094,6 +2306,7 @@  void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 	int addr_type;
 	struct inet6_dev *in6_dev;
 	struct net *net = dev_net(dev);
+	u32 addr_flags = 0;
 
 	pinfo = (struct prefix_info *) opt;
 
@@ -2198,6 +2411,13 @@  void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 				       in6_dev->token.s6_addr + 8, 8);
 				read_unlock_bh(&in6_dev->lock);
 				tokenized = true;
+			} else if (net->ipv6.addrgen_mode) {
+				if (ipv6_generate_stblpriv_addr(&addr,
+								in6_dev, 0)) {
+					in6_dev_put(in6_dev);
+					return;
+				}
+				addr_flags |= IFA_F_PRIVSTBLADDR;
 			} else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
 				   ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) {
 				in6_dev_put(in6_dev);
@@ -2216,12 +2436,11 @@  ok:
 
 		if (ifp == NULL && valid_lft) {
 			int max_addresses = in6_dev->cnf.max_addresses;
-			u32 addr_flags = 0;
 
 #ifdef CONFIG_IPV6_OPTIMISTIC_DAD
 			if (in6_dev->cnf.optimistic_dad &&
 			    !net->ipv6.devconf_all->forwarding && sllao)
-				addr_flags = IFA_F_OPTIMISTIC;
+				addr_flags |= IFA_F_OPTIMISTIC;
 #endif
 
 			/* Do not allow to create too much of autoconfigured
@@ -2640,10 +2859,12 @@  static void init_loopback(struct net_device *dev)
 	}
 }
 
-static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr *addr)
+static void addrconf_add_linklocal(struct inet6_dev *idev,
+				   const struct in6_addr *addr,
+				   u32 addr_flags)
 {
 	struct inet6_ifaddr *ifp;
-	u32 addr_flags = IFA_F_PERMANENT;
+	addr_flags |= IFA_F_PERMANENT;
 
 #ifdef CONFIG_IPV6_OPTIMISTIC_DAD
 	if (idev->cnf.optimistic_dad &&
@@ -2651,7 +2872,6 @@  static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr
 		addr_flags |= IFA_F_OPTIMISTIC;
 #endif
 
-
 	ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags, 0, 0);
 	if (!IS_ERR(ifp)) {
 		addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0);
@@ -2662,6 +2882,7 @@  static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr
 
 static void addrconf_dev_config(struct net_device *dev)
 {
+	struct net *net = dev_net(dev);
 	struct in6_addr addr;
 	struct inet6_dev *idev;
 
@@ -2685,8 +2906,14 @@  static void addrconf_dev_config(struct net_device *dev)
 	memset(&addr, 0, sizeof(struct in6_addr));
 	addr.s6_addr32[0] = htonl(0xFE800000);
 
-	if (ipv6_generate_eui64(addr.s6_addr + 8, dev) == 0)
-		addrconf_add_linklocal(idev, &addr);
+	if (net->ipv6.addrgen_mode) {
+		if (ipv6_generate_stblpriv_addr(&addr, idev, 0) == 0)
+			addrconf_add_linklocal(idev, &addr,
+					       IFA_F_PRIVSTBLADDR);
+	} else {
+		if (ipv6_generate_eui64(addr.s6_addr + 8, dev) == 0)
+			addrconf_add_linklocal(idev, &addr, 0);
+	}
 }
 
 #if IS_ENABLED(CONFIG_IPV6_SIT)
@@ -2712,7 +2939,7 @@  static void addrconf_sit_config(struct net_device *dev)
 
 		ipv6_addr_set(&addr,  htonl(0xFE800000), 0, 0, 0);
 		if (!ipv6_generate_eui64(addr.s6_addr + 8, dev))
-			addrconf_add_linklocal(idev, &addr);
+			addrconf_add_linklocal(idev, &addr, 0);
 		return;
 	}
 
@@ -2738,7 +2965,7 @@  static void addrconf_gre_config(struct net_device *dev)
 
 	ipv6_addr_set(&addr,  htonl(0xFE800000), 0, 0, 0);
 	if (!ipv6_generate_eui64(addr.s6_addr + 8, dev))
-		addrconf_add_linklocal(idev, &addr);
+		addrconf_add_linklocal(idev, &addr, 0);
 }
 #endif
 
@@ -2748,7 +2975,7 @@  ipv6_inherit_linklocal(struct inet6_dev *idev, struct net_device *link_dev)
 	struct in6_addr lladdr;
 
 	if (!ipv6_get_lladdr(link_dev, &lladdr, IFA_F_TENTATIVE)) {
-		addrconf_add_linklocal(idev, &lladdr);
+		addrconf_add_linklocal(idev, &lladdr, 0);
 		return 0;
 	}
 	return -1;
@@ -2784,6 +3011,13 @@  static int addrconf_notify(struct notifier_block *this, unsigned long event,
 				break;
 			}
 
+			if (dev_net(dev)->ipv6.addrgen_mode &&
+			    !dev_net(dev)->ipv6.stbladdr_secret) {
+				pr_info("ADDRCONF(NETDEV_UP): %s: no stable secret configured for stable privacy address generation\n",
+					dev->name);
+				break;
+			}
+
 			if (!idev && dev->mtu >= IPV6_MIN_MTU)
 				idev = ipv6_add_dev(dev);
 
@@ -2797,6 +3031,10 @@  static int addrconf_notify(struct notifier_block *this, unsigned long event,
 				break;
 			}
 
+			if (dev_net(dev)->ipv6.addrgen_mode &&
+			    !dev_net(dev)->ipv6.stbladdr_secret)
+				break;
+
 			if (idev) {
 				if (idev->if_flags & IF_READY)
 					/* device is already configured. */
@@ -3137,7 +3375,7 @@  static void addrconf_dad_start(struct inet6_ifaddr *ifp)
 		 * - otherwise, kill it.
 		 */
 		in6_ifa_hold(ifp);
-		addrconf_dad_stop(ifp, 0);
+		addrconf_dad_stop(ifp, false);
 		return;
 	}
 
@@ -4159,6 +4397,8 @@  static inline void ipv6_store_devconf(struct ipv6_devconf *cnf,
 	array[DEVCONF_FORCE_TLLAO] = cnf->force_tllao;
 	array[DEVCONF_NDISC_NOTIFY] = cnf->ndisc_notify;
 	array[DEVCONF_SUPPRESS_FRAG_NDISC] = cnf->suppress_frag_ndisc;
+	array[DEVCONF_IDGEN_RETRIES] = cnf->idgen_retries;
+	array[DEVCONF_IDGEN_DELAY] = cnf->idgen_delay;
 }
 
 static inline size_t inet6_ifla6_size(void)
@@ -4617,6 +4857,50 @@  static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
 
 #ifdef CONFIG_SYSCTL
 
+int addrconf_sysctl_stbladdr_secret_change(struct ctl_table *ctl, int write,
+					   void __user *buffer, size_t *lenp,
+					   loff_t *ppos)
+{
+	struct net *net = ctl->extra2;
+	struct ctl_table lctl;
+	unsigned long secret = *(unsigned long *)ctl->data;
+	loff_t pos = *ppos;
+	int ret;
+
+	if (write && *(unsigned long *)ctl->data)
+		return -EIO;
+
+	if (!net->ipv6.addrgen_mode)
+		return -EIO;
+
+	lctl = *ctl;
+	lctl.data = &secret;
+	lctl.extra2 = NULL;
+	ret = proc_doulongvec_minmax(&lctl, write, buffer, lenp, ppos);
+	if (write) {
+		struct net_device *dev;
+
+		if (!rtnl_trylock())
+			return restart_syscall();
+
+		*(unsigned long *)ctl->data = secret;
+
+		if (secret) {
+			for_each_netdev(net, dev) {
+				struct netdev_notifier_info info;
+
+				netdev_notifier_info_init(&info, dev);
+				addrconf_notify(NULL, NETDEV_UP, &info);
+			}
+		}
+
+		rtnl_unlock();
+	}
+	if (ret)
+		*ppos = pos;
+	return ret;
+}
+
 static
 int addrconf_sysctl_forward(struct ctl_table *ctl, int write,
 			   void __user *buffer, size_t *lenp, loff_t *ppos)
@@ -4978,6 +5262,20 @@  static struct addrconf_sysctl_table
 			.proc_handler	= proc_dointvec
 		},
 		{
+			.procname	= "idgen_retries",
+			.data		= &ipv6_devconf.idgen_retries,
+			.maxlen		= sizeof(int),
+			.mode		= 0644,
+			.proc_handler	= proc_dointvec,
+		},
+		{
+			.procname	= "idgen_delay",
+			.data		= &ipv6_devconf.idgen_delay,
+			.maxlen		= sizeof(int),
+			.mode		= 0644,
+			.proc_handler	= proc_dointvec_ms_jiffies,
+		},
+		{
 			/* sentinel */
 		}
 	},
@@ -5120,6 +5418,13 @@  int __init addrconf_init(void)
 {
 	int i, err;
 
+	if (addrgen_mode_param != -1)
+		init_net.ipv6.addrgen_mode = addrgen_mode_param;
+	else if (IS_ENABLED(CONFIG_IPV6_ADDRESS_GEN_MODE_STABLE_PRIVACY_V1))
+		init_net.ipv6.addrgen_mode = 1;
+	else /* if (IS_ENABLED(CONFIG_IPV6_ADDRESS_GEN_MODE_EUI64)) */
+		init_net.ipv6.addrgen_mode = 0;
+
 	err = ipv6_addr_label_init();
 	if (err < 0) {
 		pr_crit("%s: cannot initialize default policy table: %d\n",
diff --git a/net/ipv6/sysctl_net_ipv6.c b/net/ipv6/sysctl_net_ipv6.c
index 107b2f1..4811b9d 100644
--- a/net/ipv6/sysctl_net_ipv6.c
+++ b/net/ipv6/sysctl_net_ipv6.c
@@ -24,6 +24,21 @@  static struct ctl_table ipv6_table_template[] = {
 		.mode		= 0644,
 		.proc_handler	= proc_dointvec
 	},
+	{
+		.procname	= "stable_address_secret",
+		.data		= &init_net.ipv6.stbladdr_secret,
+		.maxlen		= sizeof(unsigned long),
+		.mode		= 0600,
+		.proc_handler	= addrconf_sysctl_stbladdr_secret_change,
+		.extra2		= &init_net
+	},
+	{
+		.procname	= "address_generation_mode",
+		.data		= &init_net.ipv6.addrgen_mode,
+		.maxlen		= sizeof(int),
+		.mode		= 0444,
+		.proc_handler	= proc_dointvec,
+	},
 	{ }
 };
 
@@ -52,6 +67,13 @@  static int __net_init ipv6_sysctl_net_init(struct net *net)
 		goto out;
 	ipv6_table[0].data = &net->ipv6.sysctl.bindv6only;
 
+	/* new namespaces do not inherit the stbladdr_secret */
+	net->ipv6.stbladdr_secret = 0UL;
+	ipv6_table[1].data = &net->ipv6.stbladdr_secret;
+	ipv6_table[1].extra2 = net;
+
+	ipv6_table[2].data = &net->ipv6.addrgen_mode;
+
 	ipv6_route_table = ipv6_route_sysctl_init(net);
 	if (!ipv6_route_table)
 		goto out_ipv6_table;