| /**************************************************************************** |
| * net/netdev/netdev_ipv6.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 "netdev/netdev.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_mcastmac |
| * |
| * Description: |
| * Given an IPv6 address (in network order), create a IPv6 multicast MAC |
| * address for ICMPv6 Neighbor Solicitation message. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_ICMPv6 |
| static void netdev_ipv6_mcastmac(const net_ipv6addr_t addr, FAR uint8_t *mac) |
| { |
| FAR const uint8_t *ipaddr8 = (FAR const uint8_t *)addr; |
| |
| /* For ICMPv6, we need to add the IPv6 multicast address |
| * |
| * For IPv6 multicast addresses, the Ethernet MAC is derived by |
| * the four low-order octets OR'ed with the MAC 33:33:00:00:00:00, |
| * so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map |
| * to the Ethernet MAC address 33:33:00:01:00:03. |
| * |
| * NOTES: This appears correct for the ICMPv6 Router Solicitation |
| * Message, but the ICMPv6 Neighbor Solicitation message seems to |
| * use 33:33:ff:01:00:03. |
| */ |
| |
| mac[0] = 0x33; |
| mac[1] = 0x33; |
| mac[2] = 0xff; |
| mac[3] = ipaddr8[13]; /* Bits: 104-111 */ |
| mac[4] = ipaddr8[14]; /* Bits: 112-119 */ |
| mac[5] = ipaddr8[15]; /* Bits: 120-127 */ |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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); |
| |
| netdev_ipv6_addmcastmac(dev, addr); |
| |
| 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); |
| |
| netdev_ipv6_removemcastmac(dev, addr); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: netdev_ipv6_addmcastmac/removemcastmac |
| * |
| * Description: |
| * Add / Remove an MAC address corresponds to the IPv6 address to / from |
| * the device's MAC filter table. |
| * |
| * Input Parameters: |
| * dev - The device driver structure to be modified |
| * addr - The IPv6 address whose related MAC will be added or removed |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * The caller has locked the network. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_ICMPv6 |
| void netdev_ipv6_addmcastmac(FAR struct net_driver_s *dev, |
| const net_ipv6addr_t addr) |
| { |
| uint8_t mcastmac[ETHER_ADDR_LEN]; |
| |
| if (net_ipv6addr_cmp(addr, g_ipv6_unspecaddr)) |
| { |
| return; |
| } |
| |
| if (dev->d_addmac != NULL) |
| { |
| netdev_ipv6_mcastmac(addr, mcastmac); |
| ninfo("Add IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mcastmac[0], mcastmac[1], mcastmac[2], |
| mcastmac[3], mcastmac[4], mcastmac[5]); |
| dev->d_addmac(dev, mcastmac); |
| } |
| } |
| |
| void netdev_ipv6_removemcastmac(FAR struct net_driver_s *dev, |
| const net_ipv6addr_t addr) |
| { |
| uint8_t mcastmac[ETHER_ADDR_LEN]; |
| #ifdef CONFIG_NETDEV_MULTIPLE_IPv6 |
| int i; |
| #endif |
| |
| if (net_ipv6addr_cmp(addr, g_ipv6_unspecaddr)) |
| { |
| return; |
| } |
| |
| if (dev->d_rmmac != NULL) |
| { |
| netdev_ipv6_mcastmac(addr, mcastmac); |
| |
| #ifdef CONFIG_NETDEV_MULTIPLE_IPv6 |
| /* Avoid removing mac needed by other addresses. */ |
| |
| for (i = 0; i < CONFIG_NETDEV_MAX_IPv6_ADDR; i++) |
| { |
| FAR struct netdev_ifaddr6_s *current = &dev->d_ipv6[i]; |
| uint8_t currentmac[ETHER_ADDR_LEN]; |
| |
| /* Skip empty address and target address */ |
| |
| if (net_ipv6addr_cmp(current->addr, g_ipv6_unspecaddr) || |
| net_ipv6addr_cmp(current->addr, addr)) |
| { |
| continue; |
| } |
| |
| /* Generate multicast MAC for this address. */ |
| |
| netdev_ipv6_mcastmac(current->addr, currentmac); |
| |
| /* We don't remove the MAC if any other IPv6 address needs it. */ |
| |
| if (memcmp(currentmac, mcastmac, ETHER_ADDR_LEN) == 0) |
| { |
| return; |
| } |
| } |
| #endif /* CONFIG_NETDEV_MULTIPLE_IPv6 */ |
| |
| ninfo("Remove IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mcastmac[0], mcastmac[1], mcastmac[2], |
| mcastmac[3], mcastmac[4], mcastmac[5]); |
| dev->d_rmmac(dev, mcastmac); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * 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 scopes are greater 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; |
| } |