From 1edb2d8431e47396850b3f146be2bb6fc4310472 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 9 Aug 2018 09:34:38 +0300 Subject: [PATCH 1/5] net: ipv6: Initial support for privacy extension This creates support for IPv6 privacy extensions which is described in RFC 4941. This will also add API that can be used to add IPv6 prefixes to a white or black list privacy extension filter. Fixes #9349 Signed-off-by: Jukka Rissanen --- include/net/net_if.h | 40 ++- include/net/net_ip.h | 41 ++++ subsys/net/ip/CMakeLists.txt | 2 + subsys/net/ip/Kconfig.ipv6 | 71 ++++++ subsys/net/ip/ipv6.h | 87 +++++++ subsys/net/ip/ipv6_nbr.c | 20 +- subsys/net/ip/ipv6_pe.c | 457 +++++++++++++++++++++++++++++++++++ subsys/net/ip/net_if.c | 31 ++- subsys/net/ip/net_private.h | 6 + 9 files changed, 745 insertions(+), 10 deletions(-) create mode 100644 subsys/net/ip/ipv6_pe.c diff --git a/include/net/net_if.h b/include/net/net_if.h index 6be5241ec1c1c..60b63add1f304 100644 --- a/include/net/net_if.h +++ b/include/net/net_if.h @@ -65,10 +65,29 @@ struct net_if_addr { /** What is the current state of the address */ enum net_addr_state addr_state; -#if defined(CONFIG_NET_IPV6_DAD) && defined(CONFIG_NET_NATIVE_IPV6) +#if defined(CONFIG_NET_NATIVE_IPV6) +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + /** Address creation time. This is used to determine if the maximum + * lifetime for this address is reached or not. The value is in seconds. + */ + u32_t addr_create_time; + + /** Preferred lifetime for the address in seconds. + */ + u32_t addr_preferred_lifetime; + + /** Address timeout value. This is only used if DAD needs to be redone + * for this address because of earlier DAD failure. This value is in + * seconds. + */ + s32_t addr_timeout; +#endif + +#if defined(CONFIG_NET_IPV6_DAD) /** How many times we have done DAD */ u8_t dad_count; #endif +#endif /* CONFIG_NET_NATIVE_IPV6 */ /** Is the IP address valid forever */ u8_t is_infinite : 1; @@ -79,7 +98,10 @@ struct net_if_addr { /** Is this IP address usage limited to the subnet (mesh) or not */ u8_t is_mesh_local : 1; - u8_t _unused : 5; + /** Is this IP address temporary and generated for example by + * IPv6 privacy extension (RFC 4941) + */ + u8_t is_temporary : 1; }; /** @@ -464,6 +486,20 @@ struct net_if { /** Network interface instance configuration */ struct net_if_config config; + + /** Network interface specific flags */ + /** Enable IPv6 privacy extension (RFC 4941), this is enabled + * by default if PE support is enabled in configuration. + */ + u8_t pe_enabled : 1; + + /* If PE is enabled, then this tells whether public addresses + * are preferred over temporary ones for this interface. + */ + u8_t pe_prefer_public : 1; + + u8_t _unused : 6; + } __net_if_align; /** diff --git a/include/net/net_ip.h b/include/net/net_ip.h index 8fbeb3b12bb15..78f85f5f485f1 100644 --- a/include/net/net_ip.h +++ b/include/net/net_ip.h @@ -1406,6 +1406,47 @@ static inline u8_t net_priority2vlan(enum net_priority priority) */ const char *net_family2str(sa_family_t family); +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +/** + * @brief Add IPv6 prefix as a privacy extension filter. + * + * @details Note that the filters can be either black listing or white listing. + * + * @param addr IPv6 prefix + * @param is_blacklist Tells if this filter for white listing or black listing. + * + * @return 0 if ok, <0 if error + */ +int net_ipv6_pe_add_filter(struct in6_addr *addr, bool is_blacklist); + +/** + * @brief Delete IPv6 prefix from privacy extension filter list. + * + * @param addr IPv6 prefix + * + * @return 0 if ok, <0 if error + */ +int net_ipv6_pe_del_filter(struct in6_addr *addr); + +#else /* CONFIG_NET_IPV6_PE_ENABLE */ + +static inline int net_ipv6_pe_add_filter(struct in6_addr *addr, + bool is_blacklist) +{ + ARG_UNUSED(addr); + ARG_UNUSED(is_blacklist); + + return -ENOTSUP; +} + +static inline int net_ipv6_pe_del_filter(struct in6_addr *addr) +{ + ARG_UNUSED(addr); + + return -ENOTSUP; +} +#endif /* CONFIG_NET_IPV6_PE_ENABLE */ + #ifdef __cplusplus } #endif diff --git a/subsys/net/ip/CMakeLists.txt b/subsys/net/ip/CMakeLists.txt index 72fb6eae95dbb..1f7a5823de493 100644 --- a/subsys/net/ip/CMakeLists.txt +++ b/subsys/net/ip/CMakeLists.txt @@ -30,6 +30,8 @@ zephyr_library_sources_ifdef(CONFIG_NET_IPV6 icmpv6.c nbr.c ipv6.c ipv6_nbr.c) zephyr_library_sources_ifdef(CONFIG_NET_IPV6_MLD ipv6_mld.c) zephyr_library_sources_ifdef(CONFIG_NET_IPV6_FRAGMENT ipv6_fragment.c) +zephyr_library_sources_ifdef(CONFIG_NET_IPV6_PE_ENABLE ipv6_pe.c) +zephyr_library_sources_ifdef(CONFIG_NET_MGMT_EVENT net_mgmt.c) zephyr_library_sources_ifdef(CONFIG_NET_ROUTE route.c) zephyr_library_sources_ifdef(CONFIG_NET_STATISTICS net_stats.c) zephyr_library_sources_ifdef(CONFIG_NET_TCP connection.c tcp.c) diff --git a/subsys/net/ip/Kconfig.ipv6 b/subsys/net/ip/Kconfig.ipv6 index 7730911484029..ba6224b1c2c69 100644 --- a/subsys/net/ip/Kconfig.ipv6 +++ b/subsys/net/ip/Kconfig.ipv6 @@ -119,6 +119,77 @@ config NET_IPV6_RA_RDNSS Support Router Advertisement Recursive DNS Server option. See RFC 6106 for details. The value depends on your network needs. +config NET_IPV6_PE_ENABLE + bool "Enable privacy extension (RFC 4941) support" + select MBEDTLS + help + This enables IPv6 privacy extension (RFC 4941) support. + The interface identifier is randomized and SLAAC addresses + generated from it will expire. This requires that applications are + prepared to use new IPv6 addresses when old ones will expire. + Note that you should make sure that the value of config option + CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT should be large enough so that + two PE generated IPv6 addresses can be added to the network interface + at the same time. + +if NET_IPV6_PE_ENABLE + +config NET_IPV6_PE_FILTER_PREFIX_COUNT + int "Size of the IPv6 prefix filter list" + default 0 + help + Size of the white/black filter list of IPv6 prefixes. User can + set filters at runtime and it is possible to enable or disable + privacy extension support according to this filter list. + By default no filters are enabled. + +config NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES + bool "Prefer public preferred address over temporary one" + help + Prefer public addresses over temporary addresses. + +config NET_IPV6_PE_TEMP_VALID_LIFETIME + int "Max lifetime for temporary address (in minutes)" + default 10080 + help + No temporary address should ever remain valid for longer than this + value. The value is in minutes. Default value is 1 week (7*24*60). + +config NET_IPV6_PE_TEMP_PREFERRED_LIFETIME + int "Max preferred lifetime for temporary address (in minutes)" + default 1440 + help + No temporary address should ever remain preferred for longer than this + value. The value is in minutes. Default value is 1 day (24*60). + +config NET_IPV6_PE_REGEN_ADVANCE + int "Regenerate advance time units" + default 5 + help + A temporary address is created only if the calculated Preferred + Lifetime is greater than REGEN_ADVANCE time units. + The value is in seconds. + +config NET_IPV6_PE_MAX_DESYNC_FACTOR + int "Max desync factor (in minutes)" + default 10 + help + This is the upper bound on DESYNC_FACTOR. The value is in minutes. + The value DESYNC_FACTOR is a random value (different for each client) + that ensures that clients don't synchronize with each other and + generate new addresses at exactly the same time. + +config NET_IPV6_PE_TEMP_IDGEN_RETRIES + int "Max amount of failed DAD attempts" + default 3 + help + The node MUST perform duplicate address detection (DAD) on the + generated temporary address. If after TEMP_IDGEN_RETRIES consecutive + attempts no non-unique address was generated then there will be no + attempt to generate temporary addresses for that interface. + +endif + config NET_6LO bool "Enable 6lowpan IPv6 Compression library" default y if NET_L2_IEEE802154 diff --git a/subsys/net/ip/ipv6.h b/subsys/net/ip/ipv6.h index 9e79dd5195031..e121078c6d77b 100644 --- a/subsys/net/ip/ipv6.h +++ b/subsys/net/ip/ipv6.h @@ -483,4 +483,91 @@ void net_ipv6_mld_init(void); #define net_ipv6_nbr_init(...) #endif +/** + * @brief Start IPv6 privacy extension procedure. + * + * @param iface Interface to use. + * @param prefix IPv6 prefix to use. + * @param vlifetime Lifetime of this IPv6 prefix (in seconds). + * @param preferred_lifetime Preferred lifetime of this IPv6 prefix (in seconds) + */ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +void net_ipv6_pe_start(struct net_if *iface, const struct in6_addr *prefix, + u32_t vlifetime, u32_t preferred_lifetime); + +#else +static inline void net_ipv6_pe_start(struct net_if *iface, + const struct in6_addr *prefix, + u32_t vlifetime, + u32_t preferred_lifetime) +{ + ARG_UNUSED(iface); + ARG_UNUSED(prefix); + ARG_UNUSED(vlifetime); + ARG_UNUSED(preferred_lifetime); +} +#endif /* CONFIG_NET_IPV6_PE_ENABLE */ + +/** + * @brief Check if maximum number of Duplicate Address Detection (DAD) requests + * have been done. + * + * @param count Number of DAD requests done. + * + * @return Return True if DAD can continue, False if max amount of DAD + * requests have been done. + */ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +bool net_ipv6_pe_check_dad(int count); +#else +static inline bool net_ipv6_pe_check_dad(int count) +{ + ARG_UNUSED(count); + + return false; +} +#endif /* CONFIG_NET_IPV6_PE_ENABLE */ + +/** + * @brief Initialize IPv6 privacy extension support for a network interface. + * + * @param iface Network interface + * + * @return Return 0 if ok or <0 if there is an error. + */ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +int net_ipv6_pe_init(struct net_if *iface); +#else +static inline int net_ipv6_pe_init(struct net_if *iface) +{ + iface->pe_enabled = false; + iface->pe_prefer_public = false; + + return 0; +} +#endif /* CONFIG_NET_IPV6_PE_ENABLE */ + +typedef void (*net_ipv6_pe_filter_cb_t)(struct in6_addr *prefix, + bool is_blacklist, + void *user_data); + +/** + * @brief Go through all the IPv6 privacy extension filters and call callback + * for each IPv6 prefix. + * + * @param cb User supplied callback function to call. + * @param user_data User specified data. + * + * @return Total number of filters found. + */ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +int net_ipv6_pe_filter_foreach(net_ipv6_pe_filter_cb_t cb, void *user_data); +#else +static inline int net_ipv6_pe_filter_foreach(net_ipv6_pe_filter_cb_t cb, + void *user_data) +{ + return 0; +} +#endif + #endif /* __IPV6_H */ diff --git a/subsys/net/ip/ipv6_nbr.c b/subsys/net/ip/ipv6_nbr.c index d662deb42b4d5..17ec8543acd17 100644 --- a/subsys/net/ip/ipv6_nbr.c +++ b/subsys/net/ip/ipv6_nbr.c @@ -2117,6 +2117,7 @@ static inline u32_t remaining_lifetime(struct net_if_addr *ifaddr) static inline void handle_prefix_autonomous(struct net_pkt *pkt, struct net_icmpv6_nd_opt_prefix_info *prefix_info) { + struct net_if *iface = net_pkt_iface(pkt); struct in6_addr addr = { }; struct net_if_addr *ifaddr; @@ -2124,8 +2125,7 @@ static inline void handle_prefix_autonomous(struct net_pkt *pkt, * setup link local address, and then copy prefix over first 8 * bytes of that address. */ - net_ipv6_addr_create_iid(&addr, - net_if_get_link_addr(net_pkt_iface(pkt))); + net_ipv6_addr_create_iid(&addr, net_if_get_link_addr(iface)); memcpy(&addr, &prefix_info->prefix, sizeof(struct in6_addr) / 2); ifaddr = net_if_ipv6_addr_lookup(&addr, NULL); @@ -2160,14 +2160,22 @@ static inline void handle_prefix_autonomous(struct net_pkt *pkt, } else { if (prefix_info->valid_lifetime == NET_IPV6_ND_INFINITE_LIFETIME) { - net_if_ipv6_addr_add(net_pkt_iface(pkt), - &addr, NET_ADDR_AUTOCONF, 0); + net_if_ipv6_addr_add(iface, &addr, + NET_ADDR_AUTOCONF, 0); } else { - net_if_ipv6_addr_add(net_pkt_iface(pkt), - &addr, NET_ADDR_AUTOCONF, + net_if_ipv6_addr_add(iface, &addr, NET_ADDR_AUTOCONF, prefix_info->valid_lifetime); } } + + /* If privacy extensions are enabled, then start the procedure for that + * too. + */ + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE) && iface->pe_enabled) { + net_ipv6_pe_start(iface, &prefix_info->prefix, + prefix_info->valid_lifetime, + prefix_info->preferred_lifetime); + } } static inline bool handle_ra_prefix(struct net_pkt *pkt) diff --git a/subsys/net/ip/ipv6_pe.c b/subsys/net/ip/ipv6_pe.c new file mode 100644 index 0000000000000..d0fa65a7c8142 --- /dev/null +++ b/subsys/net/ip/ipv6_pe.c @@ -0,0 +1,457 @@ +/** @file + * @brief IPv6 privacy extension related functions + */ + +/* + * Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_DECLARE(net_ipv6, CONFIG_NET_IPV6_LOG_LEVEL); + +#include +#include +#include + +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +#include +#endif + +#include +#include +#include + +#include "net_private.h" +#include "ipv6.h" + +/* IPv6 privacy extension (RFC 4941) constants. Note that the code uses + * seconds value internally for applicaple options. These are also values + * that can be changed at runtime if needed as recommended in RFC 4941 + * chapter 3.5. + */ +static u32_t TEMP_VALID_LIFETIME = CONFIG_NET_IPV6_PE_TEMP_VALID_LIFETIME * 60; +static u32_t TEMP_PREFERRED_LIFETIME = + CONFIG_NET_IPV6_PE_TEMP_PREFERRED_LIFETIME * 60; + +#define REGEN_ADVANCE CONFIG_NET_IPV6_PE_REGEN_ADVANCE +#define MAX_DESYNC_FACTOR (CONFIG_NET_IPV6_PE_MAX_DESYNC_FACTOR * 60) + +/* RFC 4941 ch 5. "DESYNC_FACTOR is a random value within the range 0 - + * MAX_DESYNC_FACTOR. It is computed once at system start (rather than + * each time it is used) and must never be greater than + * (TEMP_VALID_LIFETIME - REGEN_ADVANCE). + * + * The RFC is contradicting as if one changes TEMP_VALID_LIFETIME at runtime, + * then DESYNC_FACTOR should be recalculated because it cannot be greater than + * TEMP_VALID_LIFETIME - REGEN_ADVANCE. But the RFC also says that the + * DESYNC_FACTOR value is only computed once at system start. For a sake of + * clarity, lets assume that if TEMP_VALID_LIFETIME is changed at runtime, we + * need to re-calculate DESYNC_FACTOR accordingly. + */ +static u32_t DESYNC_FACTOR; + +#define TEMP_IDGEN_RETRIES CONFIG_NET_IPV6_PE_TEMP_IDGEN_RETRIES + +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 +/* Is this blacklisting filter or not */ +static bool ipv6_pe_blacklist; +static struct in6_addr ipv6_pe_filter[CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT]; +#endif + +static bool ipv6_pe_use_this_prefix(const struct in6_addr *prefix) +{ +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + int i, filters_found = false; + + for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { + if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { + continue; + } + + filters_found = true; + + if (net_ipv6_addr_cmp(prefix, &ipv6_pe_filter[i])) { + if (ipv6_pe_blacklist) { + return false; + } else { + return true; + } + } + } + + if (filters_found) { + /* There was no match so if we are blacklisting, then this + * address must be acceptable. + */ + if (ipv6_pe_blacklist) { + return true; + } + + return false; + } + + return true; +#else + return true; +#endif +} + +static bool ipv6_pe_prefix_already_exists(struct net_if_ipv6 *ipv6, + const struct in6_addr *prefix) +{ + int i; + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (!ipv6->unicast[i].is_used || + ipv6->unicast[i].address.family != AF_INET6 || + !ipv6->unicast[i].is_temporary) { + continue; + } + + if (net_ipv6_is_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)prefix, 64)) { + return true; + } + } + + return false; +} + +static int ipv6_pe_prefix_remove(struct net_if *iface, + struct net_if_ipv6 *ipv6, + const struct in6_addr *prefix) +{ + int count = 0; + int i; + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (ipv6->unicast[i].is_used && + ipv6->unicast[i].address.family == AF_INET6 && + ipv6->unicast[i].is_temporary && + net_ipv6_is_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)prefix, 64)) { + net_if_ipv6_addr_rm(iface, + &ipv6->unicast[i].address.in6_addr); + count++; + } + } + + return count; +} + +static bool ipv6_pe_prefix_update_lifetimes(struct net_if_ipv6 *ipv6, + const struct in6_addr *prefix, + u32_t vlifetime) +{ + s32_t addr_age, new_age; + int i; + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (!(ipv6->unicast[i].is_used && + ipv6->unicast[i].address.family == AF_INET6 && + ipv6->unicast[i].is_temporary && + ipv6->unicast[i].addr_state == NET_ADDR_PREFERRED && + net_ipv6_is_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)prefix, 64))) { + continue; + } + + addr_age = (u32_t)(k_uptime_get() / 1000) - + ipv6->unicast[i].addr_create_time; + new_age = abs(addr_age) + vlifetime; + + if ((new_age >= TEMP_VALID_LIFETIME) || + (new_age >= (TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR))) { + break; + } + + net_if_ipv6_addr_update_lifetime(&ipv6->unicast[i], vlifetime); + + /* RFC 4941 ch 3.4, "... at most one temporary address per + * prefix should be in a non-deprecated state at any given + * time on a given interface." + * Because of this there is no need to continue the loop. + */ + return true; + } + + return false; +} + +void net_ipv6_pe_start(struct net_if *iface, const struct in6_addr *prefix, + u32_t vlifetime, u32_t preferred_lifetime) +{ + u8_t link_addr[NET_LINK_ADDR_MAX_LENGTH]; + struct net_linkaddr temp_link_id; + struct net_if_addr *ifaddr; + struct net_if_ipv6 *ipv6; + struct in6_addr addr; + u8_t md5[128 / 8]; + int i; + + if (net_if_config_ipv6_get(iface, &ipv6) < 0) { + NET_WARN("Cannot do %sDAD IPv6, config is not valid.", "PE "); + return; + } + + if (!ipv6) { + return; + } + + /* Check if user agrees to use this prefix */ + if (!ipv6_pe_use_this_prefix(prefix)) { + NET_DBG("Prefix %s/64 is not to be used", + net_sprint_ipv6_addr(prefix)); + return; + } + + /* If the prefix is already added and it is still valid, then we do + * not try to add it again. + */ + if (ipv6_pe_prefix_already_exists(ipv6, prefix)) { + if (vlifetime == 0) { + i = ipv6_pe_prefix_remove(iface, ipv6, prefix); + + NET_DBG("Removed %d addresses using prefix %s/64", + i, net_sprint_ipv6_addr(prefix)); + return; + } + + ipv6_pe_prefix_update_lifetimes(ipv6, prefix, vlifetime); + return; + } + + preferred_lifetime = MIN(preferred_lifetime, + TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR); + if (preferred_lifetime == 0 || preferred_lifetime <= REGEN_ADVANCE) { + NET_DBG("Too short preferred lifetime, temp address not " + "created for prefix %s/64", + net_sprint_ipv6_addr(prefix)); + return; + } + + NET_DBG("Starting PE process for prefix %s/64", + net_sprint_ipv6_addr(prefix)); + + temp_link_id.len = net_if_get_link_addr(iface)->len; + temp_link_id.type = net_if_get_link_addr(iface)->type; + temp_link_id.addr = link_addr; + + /* TODO: remember the random address and use it to generate a new + * one. RFC 4941 ch 3.2.1 + */ + for (i = 0; i < temp_link_id.len; i++) { + link_addr[i] = sys_rand32_get(); + } + + /* Create IPv6 address using the given prefix and iid. We first + * setup link local address, and then copy prefix over first 8 + * bytes of that address. + */ + net_ipv6_addr_create_iid(&addr, &temp_link_id); + memcpy(&addr, prefix, sizeof(struct in6_addr) / 2); + + mbedtls_md5_ret(addr.s6_addr + 8, sizeof(addr) - 8, md5); + + memcpy(addr.s6_addr + 8, &md5[8], sizeof(addr) - 8); + + /* Turn OFF universal bit, RFC 4941, ch 3.2.1 bullet 3. */ + addr.s6_addr[8] ^= 0x02; + + vlifetime = MIN(TEMP_VALID_LIFETIME, vlifetime); + + ifaddr = net_if_ipv6_addr_add(iface, &addr, NET_ADDR_AUTOCONF, + vlifetime); + if (!ifaddr) { + NET_ERR("Cannot add %s address to interface %p", + net_sprint_ipv6_addr(&addr), iface); + return; + } + + ifaddr->is_temporary = true; + ifaddr->addr_timeout = vlifetime; + ifaddr->addr_create_time = (u32_t)(k_uptime_get() / 1000); + + NET_DBG("Starting DAD for %s iface %p", net_sprint_ipv6_addr(&addr), + iface); + + net_if_ipv6_start_dad(iface, ifaddr); +} + +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + +static void iface_cb(struct net_if *iface, void *user_data) +{ + bool is_new_filter_blacklist = !ipv6_pe_blacklist; + struct in6_addr *prefix = user_data; + struct net_if_ipv6 *ipv6; + int i, ret; + + if (net_if_config_ipv6_get(iface, &ipv6) < 0) { + return; + } + + if (!ipv6) { + return; + } + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (!ipv6->unicast[i].is_used || + ipv6->unicast[i].address.family != AF_INET6 || + !ipv6->unicast[i].is_temporary) { + continue; + } + + ret = net_ipv6_is_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)prefix, 64); + + /* TODO: Do this removal gracefully so that applications + * have time to cope with this change. + */ + if (is_new_filter_blacklist) { + if (ret) { + net_if_ipv6_addr_rm(iface, + &ipv6->unicast[i].address.in6_addr); + } + } else { + if (!ret) { + net_if_ipv6_addr_rm(iface, + &ipv6->unicast[i].address.in6_addr); + } + } + } +} + +/* If we change filter value, then check if existing IPv6 prefixes will + * conflict with the new filter. + */ +static void ipv6_pe_recheck_filters(bool is_blacklist) +{ + int i; + + for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { + if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { + continue; + } + + net_if_foreach(iface_cb, &ipv6_pe_filter[i]); + } +} +#endif /* CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 */ + +int net_ipv6_pe_add_filter(struct in6_addr *addr, bool is_blacklist) +{ +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + int i; + + for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { + if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { + net_ipaddr_copy(&ipv6_pe_filter[i], addr); + + if (ipv6_pe_blacklist != is_blacklist) { + ipv6_pe_recheck_filters(is_blacklist); + } + + ipv6_pe_blacklist = is_blacklist ? true : false; + + NET_DBG("Adding %s filter %s", + ipv6_pe_blacklist ? "blacklist" : "whitelist", + net_sprint_ipv6_addr(&ipv6_pe_filter[i])); + + return 0; + } + } + + return -ENOENT; +#else + return -ENOTSUP; +#endif +} + +int net_ipv6_pe_del_filter(struct in6_addr *addr) +{ +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + int i; + + for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { + if (net_ipv6_addr_cmp(&ipv6_pe_filter[i], addr)) { + NET_DBG("Removing %s filter %s", + ipv6_pe_blacklist ? "blacklist" : "whitelist", + net_sprint_ipv6_addr(&ipv6_pe_filter[i])); + + net_ipaddr_copy(&ipv6_pe_filter[i], + net_ipv6_unspecified_address()); + + return 0; + } + } + + return -ENOENT; +#else + return -ENOTSUP; +#endif +} + +bool net_ipv6_pe_check_dad(int count) +{ + if (count > TEMP_IDGEN_RETRIES) { + return false; + } + + return true; +} + +int net_ipv6_pe_filter_foreach(net_ipv6_pe_filter_cb_t cb, void *user_data) +{ +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + int i, count = 0; + + for (i = 0; i < CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT; i++) { + if (net_ipv6_is_addr_unspecified(&ipv6_pe_filter[i])) { + continue; + } + + cb(&ipv6_pe_filter[i], ipv6_pe_blacklist, user_data); + + count++; + } + + return count; +#else + return 0; +#endif +} + +int net_ipv6_pe_init(struct net_if *iface) +{ + static bool init_done; + u32_t lifetime; + + if (TEMP_VALID_LIFETIME - REGEN_ADVANCE <= 0) { + iface->pe_enabled = false; + return -EINVAL; + } + + iface->pe_enabled = true; + iface->pe_prefer_public = + IS_ENABLED(CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES) ? + true : false; + + if (!init_done) { + lifetime = TEMP_VALID_LIFETIME - REGEN_ADVANCE; + + /* RFC 4941 ch 5 */ + DESYNC_FACTOR = sys_rand32_get() % MIN(MAX_DESYNC_FACTOR, + lifetime); + init_done = true; + } + + return 0; +} + diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 14d7bcdbd221d..51267448a7825 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -13,6 +13,7 @@ LOG_MODULE_REGISTER(net_if, CONFIG_NET_IF_LOG_LEVEL); #include #include #include +#include #include #include #include @@ -275,6 +276,8 @@ static inline void init_iface(struct net_if *iface) NET_DBG("On iface %p", iface); api->init(iface); + + net_ipv6_pe_init(iface); } enum net_verdict net_if_send_data(struct net_if *iface, struct net_pkt *pkt) @@ -867,8 +870,8 @@ static void dad_timeout(struct k_work *work) } } -static void net_if_ipv6_start_dad(struct net_if *iface, - struct net_if_addr *ifaddr) +void net_if_ipv6_start_dad(struct net_if *iface, + struct net_if_addr *ifaddr) { ifaddr->addr_state = NET_ADDR_TENTATIVE; @@ -942,6 +945,7 @@ void net_if_start_dad(struct net_if *iface) void net_if_ipv6_dad_failed(struct net_if *iface, const struct in6_addr *addr) { struct net_if_addr *ifaddr; + u32_t timeout, preferred_lifetime; ifaddr = net_if_ipv6_addr_lookup(addr, &iface); if (!ifaddr) { @@ -952,11 +956,32 @@ void net_if_ipv6_dad_failed(struct net_if *iface, const struct in6_addr *addr) sys_slist_find_and_remove(&active_dad_timers, &ifaddr->dad_node); + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE)) { + ifaddr->dad_count++; + + timeout = ifaddr->addr_timeout; + preferred_lifetime = ifaddr->addr_preferred_lifetime; + + if (!net_ipv6_pe_check_dad(ifaddr->dad_count)) { + NET_ERR("Cannot generate PE address for interface %p", + iface); + iface->pe_enabled = false; + } + } + net_mgmt_event_notify_with_info(NET_EVENT_IPV6_DAD_FAILED, iface, &ifaddr->address.in6_addr, sizeof(struct in6_addr)); + /* The old address needs to be removed from the interface before we can + * start new DAD for the new PE address as the amount of address slots + * is limited. + */ net_if_ipv6_addr_rm(iface, addr); + + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE) && iface->pe_enabled) { + net_ipv6_pe_start(iface, addr, timeout, preferred_lifetime); + } } static inline void iface_ipv6_dad_init(void) @@ -1339,8 +1364,10 @@ static inline void net_if_addr_init(struct net_if_addr *ifaddr, u32_t vlifetime) { ifaddr->is_used = true; + ifaddr->is_temporary = false; ifaddr->address.family = AF_INET6; ifaddr->addr_type = addr_type; + net_ipaddr_copy(&ifaddr->address.in6_addr, addr); /* FIXME - set the mcast addr for this node */ diff --git a/subsys/net/ip/net_private.h b/subsys/net/ip/net_private.h index fea9d8b107cc6..b76c5d1425a79 100644 --- a/subsys/net/ip/net_private.h +++ b/subsys/net/ip/net_private.h @@ -90,6 +90,12 @@ int net_context_get_timestamp(struct net_context *context, struct net_ptp_time *timestamp); #endif +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +/* This is needed by ipv6_pe.c when privacy extension support is enabled */ +void net_if_ipv6_start_dad(struct net_if *iface, + struct net_if_addr *ifaddr); +#endif + #if defined(CONFIG_NET_GPTP) /** * @brief Initialize Precision Time Protocol Layer. From a7a0c3c3a2b61c92ee7d66bc35551a9620298f06 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 15 Aug 2018 17:06:17 +0300 Subject: [PATCH 2/5] net: ipv6: Expire temporary addresses periodically Have a timer which checks if there are any expired and temporary IPv6 addresses and removes them if any are found. Signed-off-by: Jukka Rissanen --- subsys/net/ip/ipv6_pe.c | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/subsys/net/ip/ipv6_pe.c b/subsys/net/ip/ipv6_pe.c index d0fa65a7c8142..6af64913f58b7 100644 --- a/subsys/net/ip/ipv6_pe.c +++ b/subsys/net/ip/ipv6_pe.c @@ -60,6 +60,9 @@ static bool ipv6_pe_blacklist; static struct in6_addr ipv6_pe_filter[CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT]; #endif +/* We need to periodically update the private address. */ +static struct k_delayed_work temp_lifetime; + static bool ipv6_pe_use_this_prefix(const struct in6_addr *prefix) { #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 @@ -192,6 +195,7 @@ void net_ipv6_pe_start(struct net_if *iface, const struct in6_addr *prefix, struct net_if_ipv6 *ipv6; struct in6_addr addr; u8_t md5[128 / 8]; + s32_t remaining; int i; if (net_if_config_ipv6_get(iface, &ipv6) < 0) { @@ -281,6 +285,13 @@ void net_ipv6_pe_start(struct net_if *iface, const struct in6_addr *prefix, iface); net_if_ipv6_start_dad(iface, ifaddr); + + remaining = k_delayed_work_remaining_get(&temp_lifetime); + if (remaining == 0 || remaining > K_SECONDS(vlifetime)) { + NET_DBG("Next check for temp addresses in %d seconds", + vlifetime); + k_delayed_work_submit(&temp_lifetime, K_SECONDS(vlifetime)); + } } #if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 @@ -428,6 +439,61 @@ int net_ipv6_pe_filter_foreach(net_ipv6_pe_filter_cb_t cb, void *user_data) #endif } +static void renewal_cb(struct net_if *iface, void *user_data) +{ + struct net_if_ipv6 *ipv6; + struct in6_addr prefix; + int i; + + if (net_if_config_ipv6_get(iface, &ipv6) < 0) { + return; + } + + if (!ipv6) { + return; + } + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + s32_t diff; + + if (!ipv6->unicast[i].is_used || + ipv6->unicast[i].address.family != AF_INET6 || + !ipv6->unicast[i].is_temporary) { + continue; + } + + /* If the address is too old, then generate a new one + * and remove the old address. + */ + diff = (s32_t)(ipv6->unicast[i].addr_create_time - + ((u32_t)(k_uptime_get() / 1000))); + diff = abs(diff); + + if (diff < (TEMP_PREFERRED_LIFETIME - + REGEN_ADVANCE - DESYNC_FACTOR)) { + continue; + } + + net_ipaddr_copy(&prefix, &ipv6->unicast[i].address.in6_addr); + + net_if_ipv6_addr_rm(iface, + &ipv6->unicast[i].address.in6_addr); + + memset(prefix.s6_addr + 8, 0, sizeof(prefix) - 8); + + net_ipv6_pe_start(iface, &prefix, + ipv6->unicast[i].addr_timeout, + ipv6->unicast[i].addr_preferred_lifetime); + } +} + +static void ipv6_pe_renew(struct k_work *work) +{ + ARG_UNUSED(work); + + net_if_foreach(renewal_cb, NULL); +} + int net_ipv6_pe_init(struct net_if *iface) { static bool init_done; @@ -452,6 +518,8 @@ int net_ipv6_pe_init(struct net_if *iface) init_done = true; } + k_delayed_work_init(&temp_lifetime, ipv6_pe_renew); + return 0; } From 2e96305ee0f8c9766ea3e6e333265ee85c435f8c Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 16 Aug 2018 14:58:33 +0300 Subject: [PATCH 3/5] net: ipv6: Select proper IPv6 source address for privacy extension If IPv6 privacy extension is enabled, then we need to select proper public or temporary IPv6 source address when sending the packet. Signed-off-by: Jukka Rissanen --- include/net/net_if.h | 2 +- subsys/net/ip/net_if.c | 69 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/include/net/net_if.h b/include/net/net_if.h index 60b63add1f304..53e096f8b6ba7 100644 --- a/include/net/net_if.h +++ b/include/net/net_if.h @@ -1114,7 +1114,7 @@ static inline void net_if_ipv6_maddr_leave(struct net_if_mcast_addr *addr) * @return Pointer to prefix, NULL if not found. */ struct net_if_ipv6_prefix *net_if_ipv6_prefix_get(struct net_if *iface, - struct in6_addr *addr); + const struct in6_addr *addr); /** * @brief Check if this IPv6 prefix belongs to this interface diff --git a/subsys/net/ip/net_if.c b/subsys/net/ip/net_if.c index 51267448a7825..7db5def898221 100644 --- a/subsys/net/ip/net_if.c +++ b/subsys/net/ip/net_if.c @@ -2006,7 +2006,7 @@ bool net_if_ipv6_prefix_rm(struct net_if *iface, struct in6_addr *addr, } struct net_if_ipv6_prefix *net_if_ipv6_prefix_get(struct net_if *iface, - struct in6_addr *addr) + const struct in6_addr *addr) { struct net_if_ipv6_prefix *prefix = NULL; struct net_if_ipv6 *ipv6; @@ -2269,25 +2269,53 @@ static inline bool is_proper_ipv6_address(struct net_if_addr *addr) return false; } +static bool use_public_address(bool prefer_public, bool is_temporary) +{ + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE)) { + if (!prefer_public && is_temporary) { + return false; + } + } + + return true; +} + static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface, const struct in6_addr *dst, + u8_t prefix_len, u8_t *best_so_far) { struct net_if_ipv6 *ipv6 = iface->config.ip.ipv6; + struct net_if_addr *public_addr = NULL; struct in6_addr *src = NULL; - u8_t len; + u8_t public_addr_len = 0; + struct in6_addr *temp_addr; + u8_t len, temp_addr_len; + bool ret; int i; if (!ipv6) { return NULL; } + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE)) { + temp_addr = NULL; + temp_addr_len = 0; + } else { + ARG_UNUSED(temp_addr); + ARG_UNUSED(temp_addr_len); + } + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { if (!is_proper_ipv6_address(&ipv6->unicast[i])) { continue; } len = get_diff_ipv6(dst, &ipv6->unicast[i].address.in6_addr); + if (len >= prefix_len) { + len = prefix_len; + } + if (len >= *best_so_far) { /* Mesh local address can only be selected for the same * subnet. @@ -2296,11 +2324,39 @@ static struct in6_addr *net_if_ipv6_get_best_match(struct net_if *iface, continue; } + ret = use_public_address(iface->pe_prefer_public, + ipv6->unicast[i].is_temporary); + if (!ret) { + temp_addr = &ipv6->unicast[i].address.in6_addr; + temp_addr_len = len; + continue; + } + + if (!ipv6->unicast[i].is_temporary) { + public_addr = &ipv6->unicast[i]; + public_addr_len = len; + } + *best_so_far = len; src = &ipv6->unicast[i].address.in6_addr; } } + if (IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE) && + !iface->pe_prefer_public && temp_addr) { + if (temp_addr_len >= *best_so_far) { + *best_so_far = temp_addr_len; + src = temp_addr; + } + } else { + /* By default prefer always public address if found */ + if (public_addr && + !net_ipv6_addr_cmp(&public_addr->address.in6_addr, src)) { + src = &public_addr->address.in6_addr; + *best_so_far = public_addr_len; + } + } + return src; } @@ -2312,6 +2368,13 @@ const struct in6_addr *net_if_ipv6_select_src_addr(struct net_if *dst_iface, struct net_if *iface; if (!net_ipv6_is_ll_addr(dst) && !net_ipv6_is_addr_mcast(dst)) { + struct net_if_ipv6_prefix *prefix; + u8_t prefix_len = 128; + + prefix = net_if_ipv6_prefix_get(dst_iface, dst); + if (prefix) { + prefix_len = prefix->len; + } for (iface = __net_if_start; !dst_iface && iface != __net_if_end; @@ -2319,6 +2382,7 @@ const struct in6_addr *net_if_ipv6_select_src_addr(struct net_if *dst_iface, struct in6_addr *addr; addr = net_if_ipv6_get_best_match(iface, dst, + prefix_len, &best_match); if (addr) { src = addr; @@ -2328,6 +2392,7 @@ const struct in6_addr *net_if_ipv6_select_src_addr(struct net_if *dst_iface, /* If caller has supplied interface, then use that */ if (dst_iface) { src = net_if_ipv6_get_best_match(dst_iface, dst, + prefix_len, &best_match); } From 1c58ae1ac5616fcca79adbe8f715e5d099802fb7 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 13 Aug 2018 14:34:07 +0300 Subject: [PATCH 4/5] tests: net: ipv6: Add privacy extension tests Add tests that will make sure IPv6 privacy extension code works as expected. Signed-off-by: Jukka Rissanen --- tests/net/all/prj.conf | 5 + tests/net/ipv6/prj.conf | 16 +++ tests/net/ipv6/src/main.c | 210 ++++++++++++++++++++++++++++++++++- tests/net/ipv6/testcase.yaml | 21 +++- 4 files changed, 247 insertions(+), 5 deletions(-) diff --git a/tests/net/all/prj.conf b/tests/net/all/prj.conf index 9760f30233f97..9115c66121ac3 100644 --- a/tests/net/all/prj.conf +++ b/tests/net/all/prj.conf @@ -181,6 +181,11 @@ CONFIG_NET_IPV6_LOG_LEVEL_DBG=y CONFIG_NET_IPV6_NBR_CACHE_LOG_LEVEL_DBG=y CONFIG_NET_ICMPV6_LOG_LEVEL_DBG=y +# IPv6 privacy extension +CONFIG_NET_IPV6_PE_ENABLE=y +CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES=n +CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT=2 + # 6lo CONFIG_NET_6LO=y CONFIG_NET_6LO_CONTEXT=y diff --git a/tests/net/ipv6/prj.conf b/tests/net/ipv6/prj.conf index 48fe041d84ffd..2747323540898 100644 --- a/tests/net/ipv6/prj.conf +++ b/tests/net/ipv6/prj.conf @@ -24,3 +24,19 @@ CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=9 CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=7 CONFIG_NET_IF_IPV6_PREFIX_COUNT=3 CONFIG_NET_UDP_CHECKSUM=n + +CONFIG_NET_IF_MAX_IPV6_COUNT=2 +CONFIG_SYS_LOG_NET_LEVEL=2 +#CONFIG_NET_DEBUG_IF=y +#CONFIG_NET_DEBUG_CORE=y +#CONFIG_NET_DEBUG_IPV6=y +#CONFIG_NET_DEBUG_ICMPV6=y +#CONFIG_NET_DEBUG_L2_ETHERNET=y +#CONFIG_NET_DEBUG_UTILS=y +#CONFIG_NET_DEBUG_NET_PKT=y +#CONFIG_NET_DEBUG_6LO=y + +#CONFIG_NET_SHELL=y +CONFIG_NET_IPV6_PE_ENABLE=y +CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT=2 +CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES=n diff --git a/tests/net/ipv6/src/main.c b/tests/net/ipv6/src/main.c index 29c9818d2111a..1ceb53f218b30 100644 --- a/tests/net/ipv6/src/main.c +++ b/tests/net/ipv6/src/main.c @@ -93,8 +93,8 @@ static const unsigned char icmpv6_ra[] = { /* MTU */ 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc, /* Prefix info*/ - 0x03, 0x04, 0x40, 0xc0, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfe, 0x05, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; @@ -1347,6 +1347,207 @@ static void test_dst_iface_scope_mcast_send(void) net_context_put(ctx); } +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +static bool is_pe_address_found(struct net_if *iface, struct in6_addr *prefix) +{ + struct net_if_ipv6 *ipv6; + int i; + + ipv6 = iface->config.ip.ipv6; + + zassert_not_null(ipv6, "IPv6 configuration is wrong for iface %p", + iface); + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (!ipv6->unicast[i].is_used || + ipv6->unicast[i].address.family != AF_INET6 || + !ipv6->unicast[i].is_temporary) { + continue; + } + + if (net_is_ipv6_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)prefix, 64)) { + return true; + } + } + + return false; +} + +static void get_pe_addresses(struct net_if *iface, + struct in6_addr **public_addr, + struct in6_addr **temp_addr) +{ + struct in6_addr prefix = { { { 0x3f, 0xfe, 0x05, 0x07, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 } } }; + struct net_if_ipv6 *ipv6; + int i; + + ipv6 = iface->config.ip.ipv6; + + zassert_not_null(ipv6, "IPv6 configuration is wrong for iface %p", + iface); + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (!ipv6->unicast[i].is_used || + ipv6->unicast[i].address.family != AF_INET6) { + continue; + } + + if (net_is_ipv6_prefix( + (u8_t *)&ipv6->unicast[i].address.in6_addr, + (u8_t *)&prefix, 64)) { + if (ipv6->unicast[i].is_temporary) { + *temp_addr = + &ipv6->unicast[i].address.in6_addr; + } else { + *public_addr = + &ipv6->unicast[i].address.in6_addr; + } + } + } +} +#endif + +static void test_privacy_extension(void) +{ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + struct in6_addr prefix = { { { 0x3f, 0xfe, 0x05, 0x07, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 } } }; + struct net_if *iface = net_if_get_default(); + bool found; + + zassert_true(iface->pe_enabled, + "Privacy extension not enabled for iface %p", iface); + + if (IS_ENABLED(CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES)) { + zassert_true(iface->pe_prefer_public, + "Prefer public flag not set correctly for " + "iface %p", iface); + } + + /* We received RA message earlier, make sure that temporary address + * is created because of that message. + */ + + found = is_pe_address_found(iface, &prefix); + zassert_true(found, "Temporary address not found for iface %p", iface); +#endif +} + +static void test_privacy_extension_filters(void) +{ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + struct in6_addr prefix1 = { { { 0x3f, 0xfe, 0x05, 0x07, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 } } }; + struct in6_addr prefix2 = { { { 0x3f, 0xfe, 0x04, 0x07, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 } } }; + struct in6_addr prefix3 = { { { 0x3f, 0xfe, 0x03, 0x07, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0 } } }; + struct net_if *iface = net_if_get_default(); + bool found; + int ret; + + /* First add blacklist filters */ + ret = net_ipv6_pe_add_filter(&prefix1, true); + zassert_equal(ret, 0, "Filter cannot be added (%d)", ret); + + ret = net_ipv6_pe_add_filter(&prefix2, true); + zassert_equal(ret, 0, "Filter cannot be added (%d)", ret); + + ret = net_ipv6_pe_add_filter(&prefix3, true); + zassert_true(ret < 0, "Filter could be added"); + + /* Then delete them */ + ret = net_ipv6_pe_del_filter(&prefix1); + zassert_equal(ret, 0, "Filter cannot be deleted (%d)", ret); + + ret = net_ipv6_pe_del_filter(&prefix2); + zassert_equal(ret, 0, "Filter cannot be deleted (%d)", ret); + + ret = net_ipv6_pe_del_filter(&prefix2); + zassert_true(ret < 0, "Filter found (%d)", ret); + + /* Then add whitelist filter */ + ret = net_ipv6_pe_add_filter(&prefix1, false); + zassert_equal(ret, 0, "Filter cannot be added (%d)", ret); + + /* Send RS again as we have now PE whitelist filter in place */ + test_rs_message(); + + /* IP stack needs to process the packet */ + k_sleep(K_MSEC(150)); + + found = is_pe_address_found(iface, &prefix1); + zassert_true(found, "Temporary address not found for iface %p", iface); + + /* Then try with blacklisted filter */ + ret = net_ipv6_pe_del_filter(&prefix1); + zassert_equal(ret, 0, "Filter cannot be deleted (%d)", ret); + + ret = net_ipv6_pe_add_filter(&prefix1, true); + zassert_equal(ret, 0, "Filter cannot be added (%d)", ret); + + k_sleep(K_MSEC(10)); + + /* Send RS again as we have now PE blacklist filter in place */ + test_rs_message(); + + k_sleep(K_MSEC(150)); + + found = is_pe_address_found(iface, &prefix1); + zassert_false(found, "Temporary address found for iface %p", iface); + + ret = net_ipv6_pe_del_filter(&prefix1); + zassert_equal(ret, 0, "Filter cannot be deleted (%d)", ret); + + /* Add the temp address back for the next tests */ + ret = net_ipv6_pe_add_filter(&prefix1, false); + zassert_equal(ret, 0, "Filter cannot be added (%d)", ret); + + k_sleep(K_MSEC(50)); + + /* Send RS again as we have now PE whitelist filter in place */ + test_rs_message(); + + k_sleep(K_MSEC(150)); + + found = is_pe_address_found(iface, &prefix1); + zassert_true(found, "Temporary address not found for iface %p", iface); +#endif +#endif +} + +static void test_privacy_extension_get_addr(void) +{ +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + struct in6_addr dst_addr = { { { 0x3f, 0xfe, 0x05, 0x07, 0, 0, 0, 1, + 0, 0, 2, 3, 4, 5, 6, 7 } } }; + struct net_if *iface = net_if_get_default(); + struct in6_addr *public_addr = NULL; + struct in6_addr *temp_addr = NULL; + const struct in6_addr *src_addr; + + get_pe_addresses(iface, &public_addr, &temp_addr); + + zassert_not_null(public_addr, "No public address found"); + zassert_not_null(temp_addr, "No temporary address found"); + + src_addr = net_if_ipv6_select_src_addr(iface, &dst_addr); + zassert_not_null(src_addr, "No suitable source address found"); + + if (iface->pe_prefer_public) { + zassert_true(net_ipv6_addr_cmp(src_addr, public_addr), + "Non public address selected"); + } else { + zassert_true(net_ipv6_addr_cmp(src_addr, temp_addr), + "Non temporary address selected"); + } +#endif +} + void test_main(void) { ztest_test_suite(test_ipv6_fn, @@ -1376,7 +1577,10 @@ void test_main(void) ztest_unit_test(test_dst_zero_scope_mcast_recv), ztest_unit_test(test_dst_site_scope_mcast_recv_drop), ztest_unit_test(test_dst_site_scope_mcast_recv_ok), - ztest_unit_test(test_dst_org_scope_mcast_recv) + ztest_unit_test(test_dst_org_scope_mcast_recv), + ztest_unit_test(test_privacy_extension), + ztest_unit_test(test_privacy_extension_filters), + ztest_unit_test(test_privacy_extension_get_addr) ); ztest_run_test_suite(test_ipv6_fn); } diff --git a/tests/net/ipv6/testcase.yaml b/tests/net/ipv6/testcase.yaml index e92430af22927..36ceb4e3a2a7f 100644 --- a/tests/net/ipv6/testcase.yaml +++ b/tests/net/ipv6/testcase.yaml @@ -1,4 +1,21 @@ +common: + tags: net ipv6 ipv6_pe + depends_on: netif tests: net.ipv6: - tags: net ipv6 - depends_on: netif + extra_configs: + - CONFIG_NET_IPV6_PE_ENABLE=n + net.ipv6.privacy_extension.prefer_public: + extra_configs: + - CONFIG_NET_IPV6_PE_ENABLE=y + - CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES=y + - CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT=2 + - CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=9 + - CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=7 + net.ipv6.privacy_extension.prefer_temporary: + extra_configs: + - CONFIG_NET_IPV6_PE_ENABLE=y + - CONFIG_NET_IPV6_PE_PREFER_PUBLIC_ADDRESSES=n + - CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT=2 + - CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=9 + - CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=7 From f25855b6e621d03db7efd598c4e29e0319bb83cc Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Fri, 10 Aug 2018 16:34:10 +0300 Subject: [PATCH 5/5] net: shell: Add information about IPv6 privacy extension When executing "net iface" command, print current status of IPv6 privacy extension if it is enabled in config file. The "net ipv6 ..." command prints IPv6 privacy extension information, and can add or delete IPv6 prefix filters. Signed-off-by: Jukka Rissanen --- subsys/net/ip/net_shell.c | 163 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 158 insertions(+), 5 deletions(-) diff --git a/subsys/net/ip/net_shell.c b/subsys/net/ip/net_shell.c index eb7f2196f28a7..2360b5794c7af 100644 --- a/subsys/net/ip/net_shell.c +++ b/subsys/net/ip/net_shell.c @@ -392,12 +392,13 @@ static void iface_cb(struct net_if *iface, void *user_data) continue; } - PR("\t%s %s %s%s%s\n", + PR("\t%s %s %s%s%s%s\n", net_sprint_ipv6_addr(&unicast->address.in6_addr), addrtype2str(unicast->addr_type), addrstate2str(unicast->addr_state), unicast->is_infinite ? " infinite" : "", - unicast->is_mesh_local ? " meshlocal" : ""); + unicast->is_mesh_local ? " meshlocal" : "", + unicast->is_temporary ? " temporary" : ""); count++; } @@ -453,6 +454,12 @@ static void iface_cb(struct net_if *iface, void *user_data) router->is_infinite ? " infinite" : ""); } +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + printk("IPv6 privacy extension : %s (preferring %s addresses)\n", + iface->pe_enabled ? "enabled" : "disabled", + iface->pe_prefer_public ? "public" : "temporary"); +#endif + if (ipv6) { PR("IPv6 hop limit : %d\n", ipv6->hop_limit); @@ -2332,6 +2339,26 @@ static u32_t time_diff(u32_t time1, u32_t time2) return (u32_t)abs((s32_t)time1 - (s32_t)time2); } +#if defined(CONFIG_NET_IPV6_PE_ENABLE) +static void ipv6_pe_filter_cb(struct in6_addr *prefix, bool is_blacklist, + void *user_data) +{ + char ipaddr[INET6_ADDRSTRLEN + 1]; + int *count = user_data; + + net_addr_ntop(AF_INET6, prefix, ipaddr, sizeof(ipaddr) - 1); + + if ((*count) == 0) { + printk("IPv6 privacy extension %s list filters :\n", + is_blacklist ? "black" : "white"); + } + + printk("[%d] %s/64\n", *count, ipaddr); + + (*count)++; +} +#endif /* CONFIG_NET_IPV6_PE_ENABLE */ + static void address_lifetime_cb(struct net_if *iface, void *user_data) { struct net_shell_user_data *data = user_data; @@ -2386,13 +2413,14 @@ static void address_lifetime_cb(struct net_if *iface, void *user_data) "%u", (u32_t)(remaining / 1000U)); } - PR("%s \t%s\t%s \t%s/%d\n", + PR("%s \t%s\t%s \t%s/%d%s\n", addrtype2str(ipv6->unicast[i].addr_type), addrstate2str(ipv6->unicast[i].addr_state), remaining_str, net_sprint_ipv6_addr( &ipv6->unicast[i].address.in6_addr), - prefix_len); + prefix_len, + ipv6->unicast[i].is_temporary ? " (temporary)" : ""); } } #endif /* CONFIG_NET_NATIVE_IPV6 */ @@ -2401,6 +2429,15 @@ static int cmd_net_ipv6(const struct shell *shell, size_t argc, char *argv[]) { #if defined(CONFIG_NET_NATIVE_IPV6) struct net_shell_user_data user_data; + int arg = 0; + +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + int ret; +#endif + + if (argc > 1) { + goto skip_summary; + } #endif PR("IPv6 support : %s\n", @@ -2440,6 +2477,20 @@ static int cmd_net_ipv6(const struct shell *shell, size_t argc, char *argv[]) "disabled"); } + PR("Privacy extension support : %s\n", + IS_ENABLED(CONFIG_NET_IPV6_PE_ENABLE) ? "enabled" : + "disabled"); + +#if defined(CONFIG_NET_IPV6_PE_ENABLE) + ret = 0; + + net_ipv6_pe_filter_foreach(ipv6_pe_filter_cb, &ret); + + PR("Max number of IPv6 privacy extension filters " + " : %d\n", + CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT); +#endif + PR("Max number of IPv6 network interfaces " "in the system : %d\n", CONFIG_NET_IF_MAX_IPV6_COUNT); @@ -2458,7 +2509,96 @@ static int cmd_net_ipv6(const struct shell *shell, size_t argc, char *argv[]) /* Print information about address lifetime */ net_if_foreach(address_lifetime_cb, &user_data); + + if (argc <= 1) { + return 0; + } + +skip_summary: + + if (strcmp(argv[arg], "pe") == 0) { +#if CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 + bool do_whitelisting = true; + struct in6_addr prefix; + bool do_add; + + arg++; + + if (!argv[arg]) { + PR("No sub-options given. See \"help net ipv6\" " + "command for details.\n"); + return 0; + } + + if (strcmp(argv[arg], "add") == 0) { + arg++; + do_add = true; + } else if (strcmp(argv[arg], "del") == 0) { + arg++; + do_add = false; + } else { + PR("Unknown sub-option \"%s\"\n", argv[arg]); + return 0; + } + + if (!argv[arg]) { + PR("No sub-options given. See \"help net ipv6\" " + "command for details.\n"); + return 0; + } + + if (strcmp(argv[arg], "white") == 0) { + arg++; + } else if (strcmp(argv[arg], "black") == 0) { + arg++; + do_whitelisting = false; + } + + if (!argv[arg]) { + PR("No sub-options given. See \"help net ipv6\" " + "command for details.\n"); + return 0; + } + + ret = net_addr_pton(AF_INET6, argv[arg], &prefix); + if (ret < 0) { + PR("Invalid prefix \"%s\"\n", argv[arg]); + if (strstr(argv[arg], "/")) { + PR("Do not add the prefix length.\n"); + } + + return 0; + } + + if (do_add) { + ret = net_ipv6_pe_add_filter(&prefix, !do_whitelisting); + } else { + ret = net_ipv6_pe_del_filter(&prefix); + } + + if (ret < 0) { + PR("Cannot %s %s %sfilter (%d)\n", + do_add ? "add" : "delete", + argv[arg], + do_add ? + (do_whitelisting ? "whitelist " : + "blacklist ") : "", + ret); + return 0; + } + + PR("%s %sfilter for %s\n", do_add ? "Added" : "Deleted", + do_add ? + (do_whitelisting ? "whitelist " : "blacklist ") : "", + argv[arg]); +#else + PR("IPv6 privacy extension filter support is disabled.\n"); + PR("Set CONFIG_NET_IPV6_PE_FILTER_PREFIX_COUNT > 0 to " + "enable it.\n"); #endif +#endif /* CONFIG_NET_NATIVE_IPV6 */ + return 0; + } return 0; } @@ -3958,6 +4098,19 @@ SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_gptp, SHELL_SUBCMD_SET_END ); +SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ipv6, + SHELL_CMD(pe, NULL, + "net ipv6 pe add [black|white] \n" + "Add IPv6 address to filter list. The black/white " + "parameter tells if this is white listed (accepted) or " + "black listed (declined) prefix. Default is to white list " + "the prefix.\n" + "ipv6 pe del \n" + "Delete IPv6 address from filter list.", + cmd_net_ipv6), + SHELL_SUBCMD_SET_END +); + #if !defined(NET_VLAN_MAX_COUNT) #define MAX_IFACE_COUNT NET_IF_MAX_CONFIGS #else @@ -4231,7 +4384,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(net_commands, SHELL_CMD(iface, &net_cmd_iface, "Print information about network interfaces.", cmd_net_iface), - SHELL_CMD(ipv6, NULL, + SHELL_CMD(ipv6, &net_cmd_ipv6, "Print information about IPv6 specific information and " "configuration.", cmd_net_ipv6),