diff --git a/include/nuttx/net/netdev.h b/include/nuttx/net/netdev.h index 9e279a0fe01..cbfa19fb44c 100644 --- a/include/nuttx/net/netdev.h +++ b/include/nuttx/net/netdev.h @@ -168,6 +168,12 @@ #define IPv4BUF ((FAR struct ipv4_hdr_s *)IPBUF(0)) #define IPv6BUF ((FAR struct ipv6_hdr_s *)IPBUF(0)) +#ifdef CONFIG_NET_IPv6 +# ifndef CONFIG_NETDEV_MAX_IPv6_ADDR +# define CONFIG_NETDEV_MAX_IPv6_ADDR 1 +# endif +#endif + /**************************************************************************** * Public Types ****************************************************************************/ @@ -230,6 +236,14 @@ struct netdev_varaddr_s }; #endif +#ifdef CONFIG_NET_IPv6 +struct netdev_ifaddr6_s +{ + net_ipv6addr_t addr; /* Host IPv6 address */ + net_ipv6addr_t mask; /* Network IPv6 subnet mask */ +}; +#endif + /* This structure collects information that is specific to a specific network * interface driver. If the hardware platform supports only a single * instance of this structure. @@ -302,10 +316,30 @@ struct net_driver_s #endif #ifdef CONFIG_NET_IPv6 - net_ipv6addr_t d_ipv6addr; /* Host IPv6 address assigned to the network interface */ + /* Host IPv6 addresses assigned to the network interface. + * For historical reason, we keep the old name d_ipv6addr and d_ipv6netmask + * for backward compatibility. Please use d_ipv6 for new drivers. + */ + +# if defined(CONFIG_HAVE_ANONYMOUS_STRUCT) && \ + defined(CONFIG_HAVE_ANONYMOUS_UNION) + union /* Try to limit the scope of backward compatibility alias. */ + { + struct netdev_ifaddr6_s d_ipv6[CONFIG_NETDEV_MAX_IPv6_ADDR]; + struct + { + net_ipv6addr_t d_ipv6addr; /* Compatible with previous usage */ + net_ipv6addr_t d_ipv6netmask; /* Compatible with previous usage */ + }; + }; +# else /* Without anonymous union/struct support, we can only use macros. */ + struct netdev_ifaddr6_s d_ipv6[CONFIG_NETDEV_MAX_IPv6_ADDR]; +# define d_ipv6addr d_ipv6[0].addr /* Compatible with previous usage */ +# define d_ipv6netmask d_ipv6[0].mask /* Compatible with previous usage */ +# endif /* CONFIG_HAVE_ANONYMOUS_STRUCT && CONFIG_HAVE_ANONYMOUS_UNION */ + net_ipv6addr_t d_ipv6draddr; /* Default router IPv6 address */ - net_ipv6addr_t d_ipv6netmask; /* Network IPv6 subnet mask */ -#endif +#endif /* CONFIG_NET_IPv6 */ /* This is a new design that uses d_iob as packets input and output * buffer which used by some NICs such as celluler net driver. Case for * data input, note that d_iob maybe a linked chain only when using @@ -445,6 +479,9 @@ struct net_driver_s }; typedef CODE int (*devif_poll_callback_t)(FAR struct net_driver_s *dev); +typedef CODE int (*devif_ipv6_callback_t)(FAR struct net_driver_s *dev, + FAR struct netdev_ifaddr6_s *addr, + FAR void *arg); /**************************************************************************** * Public Function Prototypes @@ -979,4 +1016,125 @@ void netdev_iob_clear(FAR struct net_driver_s *dev); void netdev_iob_release(FAR struct net_driver_s *dev); +/**************************************************************************** + * Name: netdev_ipv6_add/del + * + * Description: + * Add or delete an IPv6 address on the network device + * + * Returned Value: + * OK - Success + * -EINVAL - Invalid prefix length + * -EADDRNOTAVAIL - Delete on non-existent address + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +int netdev_ipv6_add(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + unsigned int preflen); +int netdev_ipv6_del(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + unsigned int preflen); +#endif + +/**************************************************************************** + * Name: netdev_ipv6_srcaddr/srcifaddr + * + * Description: + * Get the source IPv6 address (RFC6724). + * + * Returned Value: + * A pointer to the IPv6 address is returned on success. It will never be + * NULL, but can be an address containing g_ipv6_unspecaddr. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +FAR const uint16_t *netdev_ipv6_srcaddr(FAR struct net_driver_s *dev, + const net_ipv6addr_t dst); +FAR const struct netdev_ifaddr6_s * +netdev_ipv6_srcifaddr(FAR struct net_driver_s *dev, + const net_ipv6addr_t dst); +#endif + +/**************************************************************************** + * Name: netdev_ipv6_lladdr + * + * Description: + * Get the link-local address of the network device. + * + * Returned Value: + * A pointer to the link-local address is returned on success. + * NULL is returned if the address is not found on the device. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +FAR const uint16_t *netdev_ipv6_lladdr(FAR struct net_driver_s *dev); +#endif + +/**************************************************************************** + * Name: netdev_ipv6_lookup + * + * Description: + * Look up an IPv6 address in the network device's IPv6 addresses + * + * Input Parameters: + * dev - The network device to use in the lookup + * addr - The IPv6 address to be looked up + * maskcmp - If true, then the IPv6 address is compared to the network + * device's IPv6 addresses with mask compare. + * If false, then the IPv6 address should be exactly the same as + * the network device's IPv6 address. + * + * Returned Value: + * A pointer to the matching IPv6 address entry is returned on success. + * NULL is returned if the IPv6 address is not found in the device. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +FAR struct netdev_ifaddr6_s * +netdev_ipv6_lookup(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + bool maskcmp); +#endif + +/**************************************************************************** + * Name: netdev_ipv6_foreach + * + * Description: + * Enumerate each IPv6 address on a network device. This function will + * terminate when either (1) all addresses have been enumerated or (2) when + * a callback returns any non-zero value. + * + * Input Parameters: + * dev - The network device + * callback - Will be called for each IPv6 address + * arg - Opaque user argument passed to callback() + * + * Returned Value: + * Zero: Enumeration completed + * Non-zero: Enumeration terminated early by callback + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +int netdev_ipv6_foreach(FAR struct net_driver_s *dev, + devif_ipv6_callback_t callback, FAR void *arg); +#endif + #endif /* __INCLUDE_NUTTX_NET_NETDEV_H */ diff --git a/net/netdev/CMakeLists.txt b/net/netdev/CMakeLists.txt index a20df4d684a..53c9254bac1 100644 --- a/net/netdev/CMakeLists.txt +++ b/net/netdev/CMakeLists.txt @@ -42,4 +42,8 @@ if(CONFIG_NETDOWN_NOTIFIER) list(APPEND SRCS netdown_notifier.c) endif() +if(CONFIG_NET_IPv6) + list(APPEND SRCS netdev_ipv6.c) +endif() + target_sources(net PRIVATE ${SRCS}) diff --git a/net/netdev/Kconfig b/net/netdev/Kconfig index 6aa4699b517..27ba60c5e83 100644 --- a/net/netdev/Kconfig +++ b/net/netdev/Kconfig @@ -61,6 +61,24 @@ config NETDEV_IFINDEX When enabled, these option also enables the user interfaces: if_nametoindex() and if_indextoname(). +config NETDEV_MULTIPLE_IPv6 + bool "Enable multiple IPv6 addresses support" + default n + select NETDEV_IFINDEX + depends on NET_IPv6 + ---help--- + Enable support for multiple IPv6 addresses per network device. + +config NETDEV_MAX_IPv6_ADDR + int "Maximum number of IPv6 addresses per network device" + range 2 255 + default 2 + depends on NETDEV_MULTIPLE_IPv6 + ---help--- + Maximum number of IPv6 addresses that can be assigned to a single + network device. Normally a link-local address and a global address + are needed. + config NETDOWN_NOTIFIER bool "Support network down notifications" default n diff --git a/net/netdev/Make.defs b/net/netdev/Make.defs index ace44637da7..a273c5d6a9f 100644 --- a/net/netdev/Make.defs +++ b/net/netdev/Make.defs @@ -34,6 +34,10 @@ ifeq ($(CONFIG_NETDOWN_NOTIFIER),y) SOCK_CSRCS += netdown_notifier.c endif +ifeq ($(CONFIG_NET_IPv6),y) +NETDEV_CSRCS += netdev_ipv6.c +endif + # Include netdev build support DEPPATH += --dep-path netdev diff --git a/net/netdev/netdev_ipv6.c b/net/netdev/netdev_ipv6.c new file mode 100644 index 00000000000..0692d32305b --- /dev/null +++ b/net/netdev/netdev_ipv6.c @@ -0,0 +1,466 @@ +/**************************************************************************** + * net/netdev/netdev_ipv6.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +#include "inet/inet.h" +#include "utils/utils.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Defined in Section 2.7 of RFC4291 */ + +#define IPv6_SCOPE_INTERFACE_LOCAL 0x1 +#define IPv6_SCOPE_LINK_LOCAL 0x2 +#define IPv6_SCOPE_ADMIN_LOCAL 0x4 +#define IPv6_SCOPE_SITE_LOCAL 0x5 +#define IPv6_SCOPE_ORGANIZATION_LOCAL 0x8 +#define IPv6_SCOPE_GLOBAL 0xe + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: netdev_ipv6_get_scope + ****************************************************************************/ + +#ifdef CONFIG_NETDEV_MULTIPLE_IPv6 +static uint8_t netdev_ipv6_get_scope(const net_ipv6addr_t addr) +{ + if (net_is_addr_mcast(addr)) + { + /* As defined in Section 2.7 of RFC4291: + * | 8 | 4 | 4 | 112 bits | + * +------ -+----+----+---------------------------------------------+ + * |11111111|flgs|scop| group ID | + * +--------+----+----+---------------------------------------------+ + */ + + return NTOHS(addr[0]) & 0x000f; + } + + if (net_is_addr_linklocal(addr)) + { + return IPv6_SCOPE_LINK_LOCAL; + } + + if (net_is_addr_sitelocal(addr)) + { + return IPv6_SCOPE_SITE_LOCAL; + } + + return IPv6_SCOPE_GLOBAL; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: netdev_ipv6_add/del + * + * Description: + * Add or delete an IPv6 address on the network device + * + * Returned Value: + * OK - Success + * -EINVAL - Invalid prefix length + * -EADDRNOTAVAIL - Delete on non-existent address + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +int netdev_ipv6_add(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + unsigned int preflen) +{ + FAR struct netdev_ifaddr6_s *ifaddr = &dev->d_ipv6[0]; +#ifdef CONFIG_NETDEV_MULTIPLE_IPv6 + uint8_t scope; + int i; +#endif + + /* Verify the prefix length */ + + if (preflen > 128) + { + return -EINVAL; + } + +#ifdef CONFIG_NETDEV_MULTIPLE_IPv6 + /* Avoid duplicate address. */ + + ifaddr = netdev_ipv6_lookup(dev, addr, false); + if (ifaddr != NULL) + { + /* Check if net mask is the same. */ + + if (net_ipv6_mask2pref(ifaddr->mask) == preflen) + { + nwarn("WARNING: Trying to add same IPv6 address on net device! " + "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x/%d\n", + NTOHS(addr[0]), NTOHS(addr[1]), NTOHS(addr[2]), + NTOHS(addr[3]), NTOHS(addr[4]), NTOHS(addr[5]), + NTOHS(addr[6]), NTOHS(addr[7]), preflen); + return -EEXIST; + } + + /* Not exactly the same, update the net mask. + * REVISIT: Currently try to keep logic same as previous, which always + * allows to override the address. But not sure if it's good. + */ + + net_ipv6_pref2mask(preflen, ifaddr->mask); + return OK; + } + + /* Now we start to find a proper slot to put this address. */ + + ifaddr = &dev->d_ipv6[0]; /* Set default to a valid address. */ + scope = netdev_ipv6_get_scope(addr); + + for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) + { + FAR struct netdev_ifaddr6_s *current = &dev->d_ipv6[i]; + + /* Select empty address. */ + + if (net_ipv6addr_cmp(current->addr, g_ipv6_unspecaddr)) + { + ifaddr = current; + break; + } + + /* Select address with same scope. */ + + if (netdev_ipv6_get_scope(current->addr) == scope) + { + ifaddr = current; + continue; /* Good slot, but maybe we have empty slot later. */ + } + } +#endif /* CONFIG_NETDEV_MULTIPLE_IPv6 */ + + net_ipv6addr_copy(ifaddr->addr, addr); + net_ipv6_pref2mask(preflen, ifaddr->mask); + + return OK; +} + +int netdev_ipv6_del(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + unsigned int preflen) +{ + FAR struct netdev_ifaddr6_s *ifaddr; + + /* Verify the prefix length */ + + if (preflen > 128) + { + return -EINVAL; + } + + /* Find the matching address entry */ + + ifaddr = netdev_ipv6_lookup(dev, addr, false); + if (ifaddr == NULL) + { + /* The address does not exist on the device */ + + return -EADDRNOTAVAIL; + } + + if (net_ipv6_mask2pref(ifaddr->mask) != preflen) + { + /* Prefix length does not match, regard as not found (same as Linux) */ + + return -EADDRNOTAVAIL; + } + + /* Delete the address */ + + net_ipv6addr_copy(ifaddr->addr, g_ipv6_unspecaddr); + net_ipv6addr_copy(ifaddr->mask, g_ipv6_unspecaddr); + + return OK; +} + +/**************************************************************************** + * Name: netdev_ipv6_srcaddr/srcifaddr + * + * Description: + * Get the source IPv6 address (RFC6724). + * + * Returned Value: + * A pointer to the IPv6 address is returned on success. It will never be + * NULL, but can be an address containing g_ipv6_unspecaddr. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +FAR const uint16_t *netdev_ipv6_srcaddr(FAR struct net_driver_s *dev, + const net_ipv6addr_t dst) +{ + return netdev_ipv6_srcifaddr(dev, dst)->addr; +} + +FAR const struct netdev_ifaddr6_s * +netdev_ipv6_srcifaddr(FAR struct net_driver_s *dev, const net_ipv6addr_t dst) +{ + FAR struct netdev_ifaddr6_s *best = &dev->d_ipv6[0]; /* Don't be NULL */ +#ifdef CONFIG_NETDEV_MULTIPLE_IPv6 + uint8_t scope_dst = netdev_ipv6_get_scope(dst); + uint8_t scope_best = 0; /* All scope is larget than 0 */ + uint8_t pref_best = 0; + int i; + + for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) + { + FAR struct netdev_ifaddr6_s *current = &dev->d_ipv6[i]; + uint8_t scope_cur; + uint8_t pref_cur; + + /* Skip empty address */ + + if (net_ipv6addr_cmp(current->addr, g_ipv6_unspecaddr)) + { + continue; + } + + /* Rule 1: Prefer same address */ + + if (net_ipv6addr_cmp(dst, current->addr)) + { + best = current; + break; + } + + scope_cur = netdev_ipv6_get_scope(current->addr); + pref_cur = net_ipv6_common_pref(current->addr, dst); + + /* Rule 2: Prefer appropriate scope */ + + if (scope_cur != scope_best) + { + /* According to RFC6724: + * If Scope(SA) < Scope(SB): + * If Scope(SA) < Scope(D), then prefer SB and otherwise prefer SA + * If Scope(SB) < Scope(SA): + * If Scope(SB) < Scope(D), then prefer SA and otherwise prefer SB + * Let Scope(SA)->Scope(cur), Scope(SB)->Scope(best) in our case. + */ + + if ((scope_cur < scope_best && scope_cur >= scope_dst) || + (scope_best < scope_cur && scope_best < scope_dst)) + { + best = current; + scope_best = scope_cur; + pref_best = pref_cur; + } + + continue; + } + + /* Rule 3: Avoid deprecated and optimistic addresses + * [Not implemented: Need DAD & address type support] + * Rule 4: Prefer home address + * [Not implemented: Need MIP6] + * Rule 5: Prefer outgoing interface + * [Already satisfied: We already have the device] + * Rule 6: Prefer matching label + * [Not implemented: Need policy table support] + * [Note: Neither lwIP nor Zephyr supports policy table yet] + * Rule 7: Prefer temporary addresses + * [Not implemented: Need DAD & temporary addresses support] + */ + + /* Rule 8: Use longest matching prefix */ + + if (pref_cur > pref_best) + { + best = current; + scope_best = scope_cur; + pref_best = pref_cur; + } + } +#endif /* CONFIG_NETDEV_MULTIPLE_IPv6 */ + + return best; +} + +/**************************************************************************** + * Name: netdev_ipv6_lladdr + * + * Description: + * Get the link-local address of the network device. + * + * Returned Value: + * A pointer to the link-local address is returned on success. + * NULL is returned if the address is not found on the device. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +FAR const uint16_t *netdev_ipv6_lladdr(FAR struct net_driver_s *dev) +{ + int i; + + for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) + { + FAR struct netdev_ifaddr6_s *ifaddr = &dev->d_ipv6[i]; + + if (net_is_addr_linklocal(ifaddr->addr)) + { + return ifaddr->addr; + } + } + + return NULL; +} + +/**************************************************************************** + * Name: netdev_ipv6_lookup + * + * Description: + * Look up an IPv6 address in the network device's IPv6 addresses + * + * Input Parameters: + * dev - The network device to use in the lookup + * addr - The IPv6 address to be looked up + * maskcmp - If true, then the IPv6 address is compared to the network + * device's IPv6 addresses with mask compare. + * If false, then the IPv6 address should be exactly the same as + * the network device's IPv6 address. + * + * Returned Value: + * A pointer to the matching IPv6 address entry is returned on success. + * NULL is returned if the IPv6 address is not found in the device. + * + * Assumptions: + * The caller has locked the network. + * + ****************************************************************************/ + +FAR struct netdev_ifaddr6_s * +netdev_ipv6_lookup(FAR struct net_driver_s *dev, const net_ipv6addr_t addr, + bool maskcmp) +{ + int i; + + for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) + { + FAR struct netdev_ifaddr6_s *ifaddr = &dev->d_ipv6[i]; + + /* Skip empty address */ + + if (net_ipv6addr_cmp(ifaddr->addr, g_ipv6_unspecaddr)) + { + continue; + } + + /* Check if the address matches */ + + if (maskcmp) + { + if (net_ipv6addr_maskcmp(addr, ifaddr->addr, ifaddr->mask)) + { + return ifaddr; + } + } + else + { + if (net_ipv6addr_cmp(addr, ifaddr->addr)) + { + return ifaddr; + } + } + } + + /* No match found */ + + return NULL; +} + +/**************************************************************************** + * Name: netdev_ipv6_foreach + * + * Description: + * Enumerate each IPv6 address on a network device. This function will + * terminate when either (1) all addresses have been enumerated or (2) when + * a callback returns any non-zero value. + * + * Input Parameters: + * dev - The network device + * callback - Will be called for each IPv6 address + * arg - Opaque user argument passed to callback() + * + * Returned Value: + * Zero: Enumeration completed + * Non-zero: Enumeration terminated early by callback + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +int netdev_ipv6_foreach(FAR struct net_driver_s *dev, + devif_ipv6_callback_t callback, FAR void *arg) +{ + int i; + + if (callback == NULL) + { + return OK; + } + + for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) + { + FAR struct netdev_ifaddr6_s *ifaddr = &dev->d_ipv6[i]; + + if (!net_ipv6addr_cmp(ifaddr->addr, g_ipv6_unspecaddr)) + { + int ret = callback(dev, ifaddr, arg); + if (ret != 0) /* Stop on any error and return it */ + { + return ret; + } + } + } + + return OK; +} diff --git a/net/utils/net_mask2pref.c b/net/utils/net_mask2pref.c index 2c06a6607e4..252c1f6b727 100644 --- a/net/utils/net_mask2pref.c +++ b/net/utils/net_mask2pref.c @@ -207,4 +207,42 @@ uint8_t net_ipv6_mask2pref(FAR const uint16_t *mask) return preflen; } +/**************************************************************************** + * Name: net_ipv6_common_pref + * + * Description: + * Calculate the common prefix length of two IPv6 addresses. + * + * Input Parameters: + * a1,a2 Points to IPv6 addresses in the form of uint16_t[8] + * + * Returned Value: + * The common prefix length, range 0-128 on success; This function will + * not fail. + * + ****************************************************************************/ + +uint8_t net_ipv6_common_pref(FAR const uint16_t *a1, FAR const uint16_t *a2) +{ + uint8_t preflen; + int i; + + /* Count the leading same 16-bit groups */ + + for (i = 0, preflen = 0; i < 8 && a1[i] == a2[i]; i++, preflen += 16); + + /* Now i either, (1) indexes past the end of the mask, or (2) is the index + * to the first half-word that is not equal between the addresses. + */ + + if (i < 8) + { + preflen += net_msbits16(NTOHS(~(a1[i] ^ a2[i]))); + } + + /* Return the prefix length */ + + return preflen; +} + #endif /* CONFIG_NET_IPv6 */ diff --git a/net/utils/utils.h b/net/utils/utils.h index 9688cac7fda..757e0580b9e 100644 --- a/net/utils/utils.h +++ b/net/utils/utils.h @@ -187,6 +187,25 @@ void net_getrandom(FAR void *bytes, size_t nbytes); uint8_t net_ipv4_mask2pref(in_addr_t mask); #endif +/**************************************************************************** + * Name: net_ipv6_common_pref + * + * Description: + * Calculate the common prefix length of two IPv6 addresses. + * + * Input Parameters: + * a1,a2 Points to IPv6 addresses in the form of uint16_t[8] + * + * Returned Value: + * The common prefix length, range 0-128 on success; This function will + * not fail. + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +uint8_t net_ipv6_common_pref(FAR const uint16_t *a1, FAR const uint16_t *a2); +#endif + /**************************************************************************** * Name: net_ipv6_mask2pref *