nuttx/net/netdev/netdev_ipv6.c
Petteri Aimonen c3a234fe99 ipv6: Fix source address with many addresses in same network
Previously ipv6 multi-address support decided packet source
address based on its destination. This doesn't work if NuttX
device has multiple addresses within same subnet.

Instead when a packet is a response to existing connection,
the source address should be based on the destination address
used in the received packet.
2023-12-13 06:13:25 -08:00

475 lines
14 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(ifaddr->mask, preflen);
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(ifaddr->mask, preflen);
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) to use for transmitted packets.
* If we are responding to a received packet, use the destination address
* from that packet. If we are initiating communication, pick a local
* address that best matches the destination address.
*
* Input parameters:
* dev - Network device that packet is being transmitted from
* dst - Address to compare against when choosing local address.
*
* Returned Value:
* A pointer to a net_ipv6addr_t contained in net_driver_s 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;
}