nuttx/net/netdev/netdev_ipv6.c
Zhe Weng 96233e0c42 net/netdev: Support multiple IPv6 addresses per device
Compatible with previous usage, because may network drivers are using old member name to print logs, and there's no significant need to change them now.

Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
2023-11-07 19:30:36 +08:00

467 lines
13 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <stdint.h>
#include <nuttx/net/netdev.h>
#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;
}