/**************************************************************************** * 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 #include #include #include #include #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 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; }