| /** @file |
| * @brief ICMPv6 related functions |
| */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_NET_DEBUG_IPV6) |
| #define SYS_LOG_DOMAIN "net/ipv6" |
| #define NET_LOG_ENABLED 1 |
| |
| /* By default this prints too much data, set the value to 1 to see |
| * neighbor cache contents. |
| */ |
| #define NET_DEBUG_NBR 0 |
| #endif |
| |
| #include <errno.h> |
| #include <net/net_core.h> |
| #include <net/net_pkt.h> |
| #include <net/net_stats.h> |
| #include <net/net_context.h> |
| #include <net/net_mgmt.h> |
| #include "net_private.h" |
| #include "connection.h" |
| #include "icmpv6.h" |
| #include "udp_internal.h" |
| #include "tcp.h" |
| #include "ipv6.h" |
| #include "nbr.h" |
| #include "6lo.h" |
| #include "route.h" |
| #include "rpl.h" |
| #include "net_stats.h" |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| static void nd_reachable_timeout(struct k_work *work); |
| #endif |
| |
| #if defined(CONFIG_NET_IPV6_NBR_CACHE) |
| |
| #define MAX_MULTICAST_SOLICIT 3 |
| #define MAX_UNICAST_SOLICIT 3 |
| #define DELAY_FIRST_PROBE_TIME (5 * MSEC_PER_SEC) /* RFC 4861 ch 10 */ |
| #define RETRANS_TIMER 1000 /* in ms, RFC 4861 ch 10 */ |
| |
| extern void net_neighbor_data_remove(struct net_nbr *nbr); |
| extern void net_neighbor_table_clear(struct net_nbr_table *table); |
| |
| NET_NBR_POOL_INIT(net_neighbor_pool, |
| CONFIG_NET_IPV6_MAX_NEIGHBORS, |
| sizeof(struct net_ipv6_nbr_data), |
| net_neighbor_data_remove, |
| 0); |
| |
| NET_NBR_TABLE_INIT(NET_NBR_GLOBAL, |
| neighbor, |
| net_neighbor_pool, |
| net_neighbor_table_clear); |
| |
| const char *net_ipv6_nbr_state2str(enum net_ipv6_nbr_state state) |
| { |
| switch (state) { |
| case NET_IPV6_NBR_STATE_INCOMPLETE: |
| return "incomplete"; |
| case NET_IPV6_NBR_STATE_REACHABLE: |
| return "reachable"; |
| case NET_IPV6_NBR_STATE_STALE: |
| return "stale"; |
| case NET_IPV6_NBR_STATE_DELAY: |
| return "delay"; |
| case NET_IPV6_NBR_STATE_PROBE: |
| return "probe"; |
| case NET_IPV6_NBR_STATE_STATIC: |
| return "static"; |
| } |
| |
| return "<invalid state>"; |
| } |
| |
| static void ipv6_nbr_set_state(struct net_nbr *nbr, |
| enum net_ipv6_nbr_state new_state) |
| { |
| if (new_state == net_ipv6_nbr_data(nbr)->state || |
| net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_STATIC) { |
| return; |
| } |
| |
| NET_DBG("nbr %p %s -> %s", nbr, |
| net_ipv6_nbr_state2str(net_ipv6_nbr_data(nbr)->state), |
| net_ipv6_nbr_state2str(new_state)); |
| |
| net_ipv6_nbr_data(nbr)->state = new_state; |
| } |
| |
| static inline bool net_is_solicited(struct net_pkt *pkt) |
| { |
| struct net_icmpv6_na_hdr hdr, *na_hdr; |
| |
| na_hdr = net_icmpv6_get_na_hdr(pkt, &hdr); |
| NET_ASSERT(na_hdr); |
| |
| return na_hdr->flags & NET_ICMPV6_NA_FLAG_SOLICITED; |
| } |
| |
| static inline bool net_is_router(struct net_pkt *pkt) |
| { |
| struct net_icmpv6_na_hdr hdr, *na_hdr; |
| |
| na_hdr = net_icmpv6_get_na_hdr(pkt, &hdr); |
| NET_ASSERT(na_hdr); |
| |
| return na_hdr->flags & NET_ICMPV6_NA_FLAG_ROUTER; |
| } |
| |
| static inline bool net_is_override(struct net_pkt *pkt) |
| { |
| struct net_icmpv6_na_hdr hdr, *na_hdr; |
| |
| na_hdr = net_icmpv6_get_na_hdr(pkt, &hdr); |
| NET_ASSERT(na_hdr); |
| |
| return na_hdr->flags & NET_ICMPV6_NA_FLAG_OVERRIDE; |
| } |
| |
| static inline struct net_nbr *get_nbr(int idx) |
| { |
| return &net_neighbor_pool[idx].nbr; |
| } |
| |
| static inline struct net_nbr *get_nbr_from_data(struct net_ipv6_nbr_data *data) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (nbr->data == (u8_t *)data) { |
| return nbr; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct iface_cb_data { |
| net_nbr_cb_t cb; |
| void *user_data; |
| }; |
| |
| static void iface_cb(struct net_if *iface, void *user_data) |
| { |
| struct iface_cb_data *data = user_data; |
| int i; |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (!nbr->ref || nbr->iface != iface) { |
| continue; |
| } |
| |
| data->cb(nbr, data->user_data); |
| } |
| } |
| |
| void net_ipv6_nbr_foreach(net_nbr_cb_t cb, void *user_data) |
| { |
| struct iface_cb_data cb_data = { |
| .cb = cb, |
| .user_data = user_data, |
| }; |
| |
| /* Return the neighbors according to network interface. This makes it |
| * easier in the callback to use the neighbor information. |
| */ |
| net_if_foreach(iface_cb, &cb_data); |
| } |
| |
| #if NET_DEBUG_NBR |
| void nbr_print(void) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (!nbr->ref) { |
| continue; |
| } |
| |
| NET_DBG("[%d] %p %d/%d/%d/%d/%d pending %p iface %p idx %d " |
| "ll %s addr %s", |
| i, nbr, nbr->ref, net_ipv6_nbr_data(nbr)->ns_count, |
| net_ipv6_nbr_data(nbr)->is_router, |
| net_ipv6_nbr_data(nbr)->state, |
| net_ipv6_nbr_data(nbr)->link_metric, |
| net_ipv6_nbr_data(nbr)->pending, |
| nbr->iface, nbr->idx, |
| nbr->idx == NET_NBR_LLADDR_UNKNOWN ? "?" : |
| net_sprint_ll_addr( |
| net_nbr_get_lladdr(nbr->idx)->addr, |
| net_nbr_get_lladdr(nbr->idx)->len), |
| net_sprint_ipv6_addr(&net_ipv6_nbr_data(nbr)->addr)); |
| } |
| } |
| #else |
| #define nbr_print(...) |
| #endif |
| |
| static struct net_nbr *nbr_lookup(struct net_nbr_table *table, |
| struct net_if *iface, |
| struct in6_addr *addr) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (!nbr->ref) { |
| continue; |
| } |
| |
| if (nbr->iface == iface && |
| net_ipv6_addr_cmp(&net_ipv6_nbr_data(nbr)->addr, addr)) { |
| return nbr; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct net_ipv6_nbr_data *net_ipv6_get_nbr_by_index(u8_t idx) |
| { |
| struct net_nbr *nbr = get_nbr(idx); |
| |
| NET_ASSERT_INFO(nbr, "Invalid ll index %d", idx); |
| |
| return net_ipv6_nbr_data(nbr); |
| } |
| |
| static inline void nbr_clear_ns_pending(struct net_ipv6_nbr_data *data) |
| { |
| k_delayed_work_cancel(&data->send_ns); |
| |
| if (data->pending) { |
| net_pkt_unref(data->pending); |
| data->pending = NULL; |
| } |
| } |
| |
| static inline void nbr_free(struct net_nbr *nbr) |
| { |
| NET_DBG("nbr %p", nbr); |
| |
| nbr_clear_ns_pending(net_ipv6_nbr_data(nbr)); |
| |
| k_delayed_work_cancel(&net_ipv6_nbr_data(nbr)->reachable); |
| |
| net_nbr_unref(nbr); |
| } |
| |
| bool net_ipv6_nbr_rm(struct net_if *iface, struct in6_addr *addr) |
| { |
| struct net_nbr *nbr; |
| |
| nbr = nbr_lookup(&net_neighbor.table, iface, addr); |
| if (!nbr) { |
| return false; |
| } |
| |
| nbr_free(nbr); |
| |
| return true; |
| } |
| |
| #define NS_REPLY_TIMEOUT MSEC_PER_SEC |
| |
| static void ns_reply_timeout(struct k_work *work) |
| { |
| /* We did not receive reply to a sent NS */ |
| struct net_ipv6_nbr_data *data = CONTAINER_OF(work, |
| struct net_ipv6_nbr_data, |
| send_ns); |
| |
| struct net_nbr *nbr = get_nbr_from_data(data); |
| |
| if (!nbr) { |
| NET_DBG("NS timeout but no nbr data"); |
| return; |
| } |
| |
| if (!data->pending) { |
| /* Silently return, this is not an error as the work |
| * cannot be cancelled in certain cases. |
| */ |
| return; |
| } |
| |
| NET_DBG("NS nbr %p pending %p timeout to %s", nbr, data->pending, |
| net_sprint_ipv6_addr(&NET_IPV6_HDR(data->pending)->dst)); |
| |
| /* To unref when pending variable was set */ |
| net_pkt_unref(data->pending); |
| |
| /* To unref the original pkt allocation */ |
| net_pkt_unref(data->pending); |
| |
| data->pending = NULL; |
| |
| net_nbr_unref(nbr); |
| } |
| |
| static void nbr_init(struct net_nbr *nbr, struct net_if *iface, |
| struct in6_addr *addr, bool is_router, |
| enum net_ipv6_nbr_state state) |
| { |
| nbr->idx = NET_NBR_LLADDR_UNKNOWN; |
| nbr->iface = iface; |
| |
| net_ipaddr_copy(&net_ipv6_nbr_data(nbr)->addr, addr); |
| ipv6_nbr_set_state(nbr, state); |
| net_ipv6_nbr_data(nbr)->is_router = is_router; |
| net_ipv6_nbr_data(nbr)->pending = NULL; |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| k_delayed_work_init(&net_ipv6_nbr_data(nbr)->reachable, |
| nd_reachable_timeout); |
| #endif |
| k_delayed_work_init(&net_ipv6_nbr_data(nbr)->send_ns, |
| ns_reply_timeout); |
| } |
| |
| static struct net_nbr *nbr_new(struct net_if *iface, |
| struct in6_addr *addr, bool is_router, |
| enum net_ipv6_nbr_state state) |
| { |
| struct net_nbr *nbr = net_nbr_get(&net_neighbor.table); |
| |
| if (!nbr) { |
| return NULL; |
| } |
| |
| nbr_init(nbr, iface, addr, true, state); |
| |
| NET_DBG("nbr %p iface %p state %d IPv6 %s", |
| nbr, iface, state, net_sprint_ipv6_addr(addr)); |
| |
| return nbr; |
| } |
| |
| #if defined(CONFIG_NET_DEBUG_IPV6) |
| static inline void dbg_update_neighbor_lladdr(struct net_linkaddr *new_lladdr, |
| struct net_linkaddr_storage *old_lladdr, |
| struct in6_addr *addr) |
| { |
| char out[sizeof("xx:xx:xx:xx:xx:xx:xx:xx")]; |
| |
| snprintk(out, sizeof(out), "%s", |
| net_sprint_ll_addr(old_lladdr->addr, old_lladdr->len)); |
| |
| NET_DBG("Updating neighbor %s lladdr %s (was %s)", |
| net_sprint_ipv6_addr(addr), |
| net_sprint_ll_addr(new_lladdr->addr, new_lladdr->len), |
| out); |
| } |
| |
| static inline void dbg_update_neighbor_lladdr_raw(u8_t *new_lladdr, |
| struct net_linkaddr_storage *old_lladdr, |
| struct in6_addr *addr) |
| { |
| struct net_linkaddr lladdr = { |
| .len = old_lladdr->len, |
| .addr = new_lladdr, |
| }; |
| |
| dbg_update_neighbor_lladdr(&lladdr, old_lladdr, addr); |
| } |
| |
| #define dbg_addr(action, pkt_str, src, dst) \ |
| do { \ |
| char out[NET_IPV6_ADDR_LEN]; \ |
| \ |
| snprintk(out, sizeof(out), "%s", \ |
| net_sprint_ipv6_addr(dst)); \ |
| \ |
| NET_DBG("%s %s from %s to %s", action, \ |
| pkt_str, net_sprint_ipv6_addr(src), out); \ |
| \ |
| } while (0) |
| |
| #define dbg_addr_recv(pkt_str, src, dst) \ |
| dbg_addr("Received", pkt_str, src, dst) |
| |
| #define dbg_addr_sent(pkt_str, src, dst) \ |
| dbg_addr("Sent", pkt_str, src, dst) |
| |
| #define dbg_addr_with_tgt(action, pkt_str, src, dst, target) \ |
| do { \ |
| char out[NET_IPV6_ADDR_LEN]; \ |
| char tgt[NET_IPV6_ADDR_LEN]; \ |
| \ |
| snprintk(out, sizeof(out), "%s", \ |
| net_sprint_ipv6_addr(dst)); \ |
| snprintk(tgt, sizeof(tgt), "%s", \ |
| net_sprint_ipv6_addr(target)); \ |
| \ |
| NET_DBG("%s %s from %s to %s, target %s", action, \ |
| pkt_str, net_sprint_ipv6_addr(src), out, tgt); \ |
| \ |
| } while (0) |
| |
| #define dbg_addr_recv_tgt(pkt_str, src, dst, tgt) \ |
| dbg_addr_with_tgt("Received", pkt_str, src, dst, tgt) |
| |
| #define dbg_addr_sent_tgt(pkt_str, src, dst, tgt) \ |
| dbg_addr_with_tgt("Sent", pkt_str, src, dst, tgt) |
| #else |
| #define dbg_update_neighbor_lladdr(...) |
| #define dbg_update_neighbor_lladdr_raw(...) |
| #define dbg_addr(...) |
| #define dbg_addr_recv(...) |
| #define dbg_addr_sent(...) |
| |
| #define dbg_addr_with_tgt(...) |
| #define dbg_addr_recv_tgt(...) |
| #define dbg_addr_sent_tgt(...) |
| #endif /* CONFIG_NET_DEBUG_IPV6 */ |
| |
| struct net_nbr *net_ipv6_nbr_add(struct net_if *iface, |
| struct in6_addr *addr, |
| struct net_linkaddr *lladdr, |
| bool is_router, |
| enum net_ipv6_nbr_state state) |
| { |
| struct net_nbr *nbr; |
| |
| nbr = nbr_lookup(&net_neighbor.table, iface, addr); |
| if (!nbr) { |
| nbr = nbr_new(iface, addr, is_router, state); |
| if (!nbr) { |
| NET_ERR("Could not add router neighbor %s [%s]", |
| net_sprint_ipv6_addr(addr), |
| net_sprint_ll_addr(lladdr->addr, lladdr->len)); |
| return NULL; |
| } |
| } |
| |
| if (net_nbr_link(nbr, iface, lladdr) == -EALREADY && |
| net_ipv6_nbr_data(nbr)->state != NET_IPV6_NBR_STATE_STATIC) { |
| /* Update the lladdr if the node was already known */ |
| struct net_linkaddr_storage *cached_lladdr; |
| |
| cached_lladdr = net_nbr_get_lladdr(nbr->idx); |
| |
| if (memcmp(cached_lladdr->addr, lladdr->addr, lladdr->len)) { |
| dbg_update_neighbor_lladdr(lladdr, cached_lladdr, addr); |
| |
| net_linkaddr_set(cached_lladdr, lladdr->addr, |
| lladdr->len); |
| |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE); |
| } else if (net_ipv6_nbr_data(nbr)->state == |
| NET_IPV6_NBR_STATE_INCOMPLETE) { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE); |
| } |
| } |
| |
| if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_INCOMPLETE) { |
| /* Send NS so that we can verify that the neighbor is |
| * reachable. |
| */ |
| net_ipv6_send_ns(iface, NULL, NULL, NULL, addr, false); |
| } |
| |
| NET_DBG("[%d] nbr %p state %d router %d IPv6 %s ll %s iface %p", |
| nbr->idx, nbr, state, is_router, |
| net_sprint_ipv6_addr(addr), |
| net_sprint_ll_addr(lladdr->addr, lladdr->len), |
| nbr->iface); |
| |
| return nbr; |
| } |
| |
| static inline struct net_nbr *nbr_add(struct net_pkt *pkt, |
| struct net_linkaddr *lladdr, |
| bool is_router, |
| enum net_ipv6_nbr_state state) |
| { |
| return net_ipv6_nbr_add(net_pkt_iface(pkt), &NET_IPV6_HDR(pkt)->src, |
| lladdr, is_router, state); |
| } |
| |
| void net_neighbor_data_remove(struct net_nbr *nbr) |
| { |
| NET_DBG("Neighbor %p removed", nbr); |
| |
| return; |
| } |
| |
| void net_neighbor_table_clear(struct net_nbr_table *table) |
| { |
| NET_DBG("Neighbor table %p cleared", table); |
| } |
| |
| struct in6_addr *net_ipv6_nbr_lookup_by_index(struct net_if *iface, |
| u8_t idx) |
| { |
| int i; |
| |
| if (idx == NET_NBR_LLADDR_UNKNOWN) { |
| return NULL; |
| } |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (!nbr->ref) { |
| continue; |
| } |
| |
| if (iface && nbr->iface != iface) { |
| continue; |
| } |
| |
| if (nbr->idx == idx) { |
| return &net_ipv6_nbr_data(nbr)->addr; |
| } |
| } |
| |
| return NULL; |
| } |
| #endif /* CONFIG_NET_IPV6_NBR_CACHE */ |
| |
| int net_ipv6_find_last_ext_hdr(struct net_pkt *pkt, u16_t *next_hdr_idx, |
| u16_t *last_hdr_idx) |
| { |
| struct net_ipv6_hdr *hdr = NET_IPV6_HDR(pkt); |
| struct net_buf *frag = pkt->frags; |
| int pos = 0; |
| u16_t offset, prev, tmp; |
| u8_t next_hdr; |
| u8_t length; |
| u8_t next; |
| |
| next = hdr->nexthdr; |
| |
| /* Initial value if no extension fragments are found */ |
| *next_hdr_idx = 6; |
| |
| offset = *last_hdr_idx = sizeof(struct net_ipv6_hdr); |
| |
| /* First check the simplest case where there is no extension headers |
| * in the packet. There cannot be any extensions after the normal or |
| * typical IP protocols |
| */ |
| if (next == IPPROTO_ICMPV6 || next == IPPROTO_UDP || |
| next == IPPROTO_TCP) { |
| return 0; |
| } |
| |
| prev = pos; |
| |
| while (frag) { |
| frag = net_frag_read_u8(frag, offset, &offset, &next_hdr); |
| if (!frag && offset == 0xffff) { |
| goto fail; |
| } |
| |
| frag = net_frag_read_u8(frag, offset, &offset, &length); |
| if (!frag && offset == 0xffff) { |
| goto fail; |
| } |
| |
| length = length * 8 + 8; |
| |
| /* TODO: Add here more IPv6 extension headers to check */ |
| switch (next) { |
| case NET_IPV6_NEXTHDR_NONE: |
| *next_hdr_idx = prev; |
| *last_hdr_idx = offset - 2; |
| goto out; |
| |
| case NET_IPV6_NEXTHDR_FRAG: |
| prev = pos; |
| pos = offset - 2; |
| offset += 2 + 4; |
| break; |
| |
| case NET_IPV6_NEXTHDR_HBHO: |
| prev = pos; |
| pos = offset - 2; |
| offset += length - 2; |
| break; |
| |
| case IPPROTO_ICMPV6: |
| case IPPROTO_UDP: |
| case IPPROTO_TCP: |
| prev = pos; |
| pos = *next_hdr_idx = offset - 2; |
| goto out; |
| |
| default: |
| goto fail; |
| } |
| |
| /* Get the next header value */ |
| frag = net_frag_read_u8(frag, pos, &tmp, &next); |
| if (!frag && pos == 0xffff) { |
| goto fail; |
| } |
| } |
| |
| out: |
| *next_hdr_idx = prev; |
| *last_hdr_idx = offset - 2; |
| |
| return 0; |
| |
| fail: |
| return -EINVAL; |
| } |
| |
| const struct in6_addr *net_ipv6_unspecified_address(void) |
| { |
| static const struct in6_addr addr = IN6ADDR_ANY_INIT; |
| |
| return &addr; |
| } |
| |
| struct net_pkt *net_ipv6_create_raw(struct net_pkt *pkt, |
| const struct in6_addr *src, |
| const struct in6_addr *dst, |
| struct net_if *iface, |
| u8_t next_header) |
| { |
| struct net_buf *header; |
| |
| header = net_pkt_get_frag(pkt, K_FOREVER); |
| |
| net_pkt_frag_insert(pkt, header); |
| |
| NET_IPV6_HDR(pkt)->vtc = 0x60; |
| NET_IPV6_HDR(pkt)->tcflow = 0; |
| NET_IPV6_HDR(pkt)->flow = 0; |
| |
| NET_IPV6_HDR(pkt)->nexthdr = 0; |
| |
| /* User can tweak the default hop limit if needed */ |
| NET_IPV6_HDR(pkt)->hop_limit = net_pkt_ipv6_hop_limit(pkt); |
| if (NET_IPV6_HDR(pkt)->hop_limit == 0) { |
| NET_IPV6_HDR(pkt)->hop_limit = |
| net_if_ipv6_get_hop_limit(iface); |
| } |
| |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst); |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src); |
| |
| net_pkt_set_ipv6_ext_len(pkt, 0); |
| NET_IPV6_HDR(pkt)->nexthdr = next_header; |
| |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| net_pkt_set_family(pkt, AF_INET6); |
| |
| net_buf_add(header, sizeof(struct net_ipv6_hdr)); |
| |
| return pkt; |
| } |
| |
| struct net_pkt *net_ipv6_create(struct net_context *context, |
| struct net_pkt *pkt, |
| const struct in6_addr *src, |
| const struct in6_addr *dst) |
| { |
| NET_ASSERT(((struct sockaddr_in6_ptr *)&context->local)->sin6_addr); |
| |
| if (!src) { |
| src = ((struct sockaddr_in6_ptr *)&context->local)->sin6_addr; |
| } |
| |
| if (net_is_ipv6_addr_unspecified(src) |
| || net_is_ipv6_addr_mcast(src)) { |
| src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt), |
| (struct in6_addr *)dst); |
| } |
| |
| return net_ipv6_create_raw(pkt, |
| src, |
| dst, |
| net_context_get_iface(context), |
| net_context_get_ip_proto(context)); |
| } |
| |
| int net_ipv6_finalize_raw(struct net_pkt *pkt, u8_t next_header) |
| { |
| /* Set the length of the IPv6 header */ |
| size_t total_len; |
| |
| #if defined(CONFIG_NET_UDP) && defined(CONFIG_NET_RPL_INSERT_HBH_OPTION) |
| if (next_header != IPPROTO_TCP && next_header != IPPROTO_ICMPV6) { |
| /* Check if we need to add RPL header to sent UDP packet. */ |
| if (net_rpl_insert_header(pkt) < 0) { |
| NET_DBG("RPL HBHO insert failed"); |
| return -EINVAL; |
| } |
| } |
| #endif |
| |
| net_pkt_compact(pkt); |
| |
| total_len = net_pkt_get_len(pkt); |
| |
| total_len -= sizeof(struct net_ipv6_hdr); |
| |
| NET_IPV6_HDR(pkt)->len[0] = total_len / 256; |
| NET_IPV6_HDR(pkt)->len[1] = total_len - NET_IPV6_HDR(pkt)->len[0] * 256; |
| |
| #if defined(CONFIG_NET_UDP) |
| if (next_header == IPPROTO_UDP) { |
| net_udp_set_chksum(pkt, pkt->frags); |
| } else |
| #endif |
| |
| #if defined(CONFIG_NET_TCP) |
| if (next_header == IPPROTO_TCP) { |
| net_tcp_set_chksum(pkt, pkt->frags); |
| } else |
| #endif |
| |
| if (next_header == IPPROTO_ICMPV6) { |
| net_icmpv6_set_chksum(pkt, pkt->frags); |
| } |
| |
| return 0; |
| } |
| |
| int net_ipv6_finalize(struct net_context *context, struct net_pkt *pkt) |
| { |
| return net_ipv6_finalize_raw(pkt, net_context_get_ip_proto(context)); |
| } |
| |
| #if defined(CONFIG_NET_IPV6_DAD) |
| int net_ipv6_start_dad(struct net_if *iface, struct net_if_addr *ifaddr) |
| { |
| return net_ipv6_send_ns(iface, NULL, NULL, NULL, |
| &ifaddr->address.in6_addr, true); |
| } |
| |
| static inline bool dad_failed(struct net_if *iface, struct in6_addr *addr) |
| { |
| if (net_is_ipv6_ll_addr(addr)) { |
| NET_ERR("DAD failed, no ll IPv6 address!"); |
| return false; |
| } |
| |
| net_if_ipv6_dad_failed(iface, addr); |
| |
| return true; |
| } |
| #endif /* CONFIG_NET_IPV6_DAD */ |
| |
| #if defined(CONFIG_NET_IPV6_NBR_CACHE) |
| |
| /* If the reserve has changed, we need to adjust it accordingly in the |
| * fragment chain. This can only happen in IEEE 802.15.4 where the link |
| * layer header size can change if the destination address changes. |
| * Thus we need to check it here. Note that this cannot happen for IPv4 |
| * as 802.15.4 supports IPv6 only. |
| */ |
| static struct net_pkt *update_ll_reserve(struct net_pkt *pkt, |
| struct in6_addr *addr) |
| { |
| /* We need to go through all the fragments and adjust the |
| * fragment data size. |
| */ |
| u16_t reserve, room_len, copy_len, pos; |
| struct net_buf *orig_frag, *frag; |
| |
| /* No need to do anything if we are forwarding the packet |
| * as we already know everything about the destination of |
| * the packet. |
| */ |
| if (net_pkt_forwarding(pkt)) { |
| return pkt; |
| } |
| |
| reserve = net_if_get_ll_reserve(net_pkt_iface(pkt), addr); |
| if (reserve == net_pkt_ll_reserve(pkt)) { |
| return pkt; |
| } |
| |
| NET_DBG("Adjust reserve old %d new %d", |
| net_pkt_ll_reserve(pkt), reserve); |
| |
| net_pkt_set_ll_reserve(pkt, reserve); |
| |
| orig_frag = pkt->frags; |
| copy_len = orig_frag->len; |
| pos = 0; |
| |
| pkt->frags = NULL; |
| room_len = 0; |
| frag = NULL; |
| |
| while (orig_frag) { |
| if (!room_len) { |
| frag = net_pkt_get_frag(pkt, K_FOREVER); |
| |
| net_pkt_frag_add(pkt, frag); |
| |
| room_len = net_buf_tailroom(frag); |
| } |
| |
| if (room_len >= copy_len) { |
| memcpy(net_buf_add(frag, copy_len), |
| orig_frag->data + pos, copy_len); |
| |
| room_len -= copy_len; |
| copy_len = 0; |
| } else { |
| memcpy(net_buf_add(frag, room_len), |
| orig_frag->data + pos, room_len); |
| |
| copy_len -= room_len; |
| pos += room_len; |
| room_len = 0; |
| } |
| |
| if (!copy_len) { |
| struct net_buf *tmp = orig_frag; |
| |
| orig_frag = orig_frag->frags; |
| |
| tmp->frags = NULL; |
| net_pkt_frag_unref(tmp); |
| |
| if (!orig_frag) { |
| break; |
| } |
| |
| copy_len = orig_frag->len; |
| pos = 0; |
| } |
| } |
| |
| return pkt; |
| } |
| |
| struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) |
| { |
| struct in6_addr *nexthop = NULL; |
| struct net_if *iface = NULL; |
| struct net_nbr *nbr; |
| |
| NET_ASSERT(pkt && pkt->frags); |
| |
| #if defined(CONFIG_NET_IPV6_FRAGMENT) |
| /* If we have already fragmented the packet, the fragment id will |
| * contain a proper value and we can skip other checks. |
| */ |
| if (net_pkt_ipv6_fragment_id(pkt) == 0) { |
| size_t pkt_len = net_pkt_get_len(pkt); |
| |
| if (pkt_len > NET_IPV6_MTU) { |
| int ret; |
| |
| ret = net_ipv6_send_fragmented_pkt(net_pkt_iface(pkt), |
| pkt, pkt_len); |
| if (ret < 0) { |
| NET_DBG("Cannot fragment IPv6 pkt (%d)", ret); |
| |
| if (ret == -ENOMEM) { |
| /* Try to send the packet if we could |
| * not allocate enough network packets |
| * and hope the original large packet |
| * can be sent ok. |
| */ |
| goto ignore_frag_error; |
| } |
| } |
| |
| /* We "fake" the sending of the packet here so that |
| * tcp.c:tcp_retry_expired() will increase the ref |
| * count when re-sending the packet. This is crucial |
| * thing to do here and will cause free memory access |
| * if not done. |
| */ |
| net_pkt_set_sent(pkt, true); |
| |
| /* We need to unref here because we simulate the packet |
| * sending. |
| */ |
| net_pkt_unref(pkt); |
| |
| /* No need to continue with the sending as the packet |
| * is now split and its fragments will be sent |
| * separately to network. |
| */ |
| return NULL; |
| } |
| } |
| ignore_frag_error: |
| #endif /* CONFIG_NET_IPV6_FRAGMENT */ |
| |
| /* Workaround Linux bug, see: |
| * https://jira.zephyrproject.org/browse/ZEP-1656 |
| */ |
| if (atomic_test_bit(net_pkt_iface(pkt)->flags, NET_IF_POINTOPOINT)) { |
| /* Update RPL header */ |
| if (net_rpl_update_header(pkt, &NET_IPV6_HDR(pkt)->dst) < 0) { |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| |
| return pkt; |
| } |
| |
| if (net_pkt_ll_dst(pkt)->addr || |
| net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst)) { |
| /* Update RPL header */ |
| if (net_rpl_update_header(pkt, &NET_IPV6_HDR(pkt)->dst) < 0) { |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| |
| return update_ll_reserve(pkt, &NET_IPV6_HDR(pkt)->dst); |
| } |
| |
| if (net_if_ipv6_addr_onlink(&iface, |
| &NET_IPV6_HDR(pkt)->dst)) { |
| nexthop = &NET_IPV6_HDR(pkt)->dst; |
| net_pkt_set_iface(pkt, iface); |
| } else { |
| /* We need to figure out where the destination |
| * host is located. |
| */ |
| struct net_route_entry *route; |
| struct net_if_router *router; |
| |
| route = net_route_lookup(NULL, &NET_IPV6_HDR(pkt)->dst); |
| if (route) { |
| nexthop = net_route_get_nexthop(route); |
| if (!nexthop) { |
| net_route_del(route); |
| |
| net_rpl_global_repair(route); |
| |
| NET_DBG("No route to host %s", |
| net_sprint_ipv6_addr( |
| &NET_IPV6_HDR(pkt)->dst)); |
| |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| } else { |
| /* No specific route to this host, use the default |
| * route instead. |
| */ |
| router = net_if_ipv6_router_find_default(NULL, |
| &NET_IPV6_HDR(pkt)->dst); |
| if (!router) { |
| NET_DBG("No default route to %s", |
| net_sprint_ipv6_addr( |
| &NET_IPV6_HDR(pkt)->dst)); |
| |
| /* Try to send the packet anyway */ |
| nexthop = &NET_IPV6_HDR(pkt)->dst; |
| goto try_send; |
| } |
| |
| nexthop = &router->address.in6_addr; |
| } |
| } |
| |
| if (net_rpl_update_header(pkt, nexthop) < 0) { |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| |
| if (!iface) { |
| /* This means that the dst was not onlink, so try to |
| * figure out the interface using nexthop instead. |
| */ |
| if (net_if_ipv6_addr_onlink(&iface, nexthop)) { |
| net_pkt_set_iface(pkt, iface); |
| } |
| |
| /* If the above check returns null, we try to send |
| * the packet and hope for the best. |
| */ |
| } |
| |
| try_send: |
| nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt), nexthop); |
| |
| NET_DBG("Neighbor lookup %p (%d) iface %p addr %s state %s", nbr, |
| nbr ? nbr->idx : NET_NBR_LLADDR_UNKNOWN, |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(nexthop), |
| nbr ? net_ipv6_nbr_state2str(net_ipv6_nbr_data(nbr)->state) : |
| "-"); |
| |
| if (nbr && nbr->idx != NET_NBR_LLADDR_UNKNOWN) { |
| struct net_linkaddr_storage *lladdr; |
| |
| lladdr = net_nbr_get_lladdr(nbr->idx); |
| |
| net_pkt_ll_dst(pkt)->addr = lladdr->addr; |
| net_pkt_ll_dst(pkt)->len = lladdr->len; |
| |
| NET_DBG("Neighbor %p addr %s", nbr, |
| net_sprint_ll_addr(lladdr->addr, lladdr->len)); |
| |
| /* Start the NUD if we are in STALE state. |
| * See RFC 4861 ch 7.3.3 for details. |
| */ |
| #if defined(CONFIG_NET_IPV6_ND) |
| if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_STALE) { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_DELAY); |
| |
| k_delayed_work_submit( |
| &net_ipv6_nbr_data(nbr)->reachable, |
| DELAY_FIRST_PROBE_TIME); |
| } |
| #endif |
| |
| return update_ll_reserve(pkt, nexthop); |
| } |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| /* We need to send NS and wait for NA before sending the packet. */ |
| if (net_ipv6_send_ns(net_pkt_iface(pkt), |
| pkt, |
| &NET_IPV6_HDR(pkt)->src, |
| NULL, |
| nexthop, |
| false) < 0) { |
| /* In case of an error, the NS send function will unref |
| * the pkt. |
| */ |
| return NULL; |
| } |
| |
| NET_DBG("pkt %p (frag %p) will be sent later", pkt, pkt->frags); |
| #else |
| NET_DBG("pkt %p (frag %p) cannot be sent, dropping it.", pkt, |
| pkt->frags); |
| |
| net_pkt_unref(pkt); |
| #endif /* CONFIG_NET_IPV6_ND */ |
| |
| return NULL; |
| } |
| |
| struct net_nbr *net_ipv6_nbr_lookup(struct net_if *iface, |
| struct in6_addr *addr) |
| { |
| return nbr_lookup(&net_neighbor.table, iface, addr); |
| } |
| |
| struct net_nbr *net_ipv6_get_nbr(struct net_if *iface, u8_t idx) |
| { |
| int i; |
| |
| if (idx == NET_NBR_LLADDR_UNKNOWN) { |
| return NULL; |
| } |
| |
| for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { |
| struct net_nbr *nbr = get_nbr(i); |
| |
| if (nbr->ref) { |
| if (iface && nbr->iface != iface) { |
| continue; |
| } |
| |
| if (nbr->idx == idx) { |
| return nbr; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static inline u8_t get_llao_len(struct net_if *iface) |
| { |
| if (iface->link_addr.len == 6) { |
| return 8; |
| } else if (iface->link_addr.len == 8) { |
| return 16; |
| } |
| |
| /* What else could it be? */ |
| NET_ASSERT_INFO(0, "Invalid link address length %d", |
| iface->link_addr.len); |
| |
| return 0; |
| } |
| |
| static inline void set_llao(struct net_linkaddr *lladdr, |
| u8_t *llao, u8_t llao_len, u8_t type) |
| { |
| llao[NET_ICMPV6_OPT_TYPE_OFFSET] = type; |
| llao[NET_ICMPV6_OPT_LEN_OFFSET] = llao_len >> 3; |
| |
| memcpy(&llao[NET_ICMPV6_OPT_DATA_OFFSET], lladdr->addr, lladdr->len); |
| |
| memset(&llao[NET_ICMPV6_OPT_DATA_OFFSET + lladdr->len], 0, |
| llao_len - lladdr->len - 2); |
| } |
| |
| static void setup_headers(struct net_pkt *pkt, u8_t nd6_len, |
| u8_t icmp_type) |
| { |
| net_buf_add(pkt->frags, |
| sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr)); |
| |
| NET_IPV6_HDR(pkt)->vtc = 0x60; |
| NET_IPV6_HDR(pkt)->tcflow = 0; |
| NET_IPV6_HDR(pkt)->flow = 0; |
| NET_IPV6_HDR(pkt)->len[0] = 0; |
| NET_IPV6_HDR(pkt)->len[1] = NET_ICMPH_LEN + nd6_len; |
| |
| NET_IPV6_HDR(pkt)->nexthdr = IPPROTO_ICMPV6; |
| NET_IPV6_HDR(pkt)->hop_limit = NET_IPV6_ND_HOP_LIMIT; |
| |
| /* In this special case where we know there are no long extension |
| * headers, so we can use this header cast. |
| */ |
| net_pkt_icmp_data(pkt)->type = icmp_type; |
| net_pkt_icmp_data(pkt)->code = 0; |
| } |
| |
| static inline void handle_ns_neighbor(struct net_pkt *pkt, u8_t ll_len, |
| u16_t sllao_offset) |
| { |
| struct net_linkaddr_storage lladdr; |
| struct net_linkaddr nbr_lladdr; |
| struct net_buf *frag; |
| u16_t pos; |
| |
| lladdr.len = 8 * ll_len - 2; |
| |
| frag = net_frag_read(pkt->frags, sllao_offset, |
| &pos, lladdr.len, lladdr.addr); |
| if (!frag && pos == 0xffff) { |
| return; |
| } |
| |
| nbr_lladdr.len = lladdr.len; |
| nbr_lladdr.addr = lladdr.addr; |
| |
| /** |
| * IEEE802154 lladdress is 8 bytes long, so it requires |
| * 2 * 8 bytes - 2 - padding. |
| * The formula above needs to be adjusted. |
| */ |
| if (net_pkt_ll_src(pkt)->len < nbr_lladdr.len) { |
| nbr_lladdr.len = net_pkt_ll_src(pkt)->len; |
| } |
| |
| nbr_add(pkt, &nbr_lladdr, false, NET_IPV6_NBR_STATE_INCOMPLETE); |
| } |
| |
| int net_ipv6_send_na(struct net_if *iface, const struct in6_addr *src, |
| const struct in6_addr *dst, const struct in6_addr *tgt, |
| u8_t flags) |
| { |
| struct net_icmpv6_na_hdr hdr, *na_hdr; |
| struct net_pkt *pkt; |
| struct net_buf *frag; |
| u8_t llao_len; |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, dst), |
| K_FOREVER); |
| |
| NET_ASSERT_INFO(pkt, "Out of TX packets"); |
| |
| frag = net_pkt_get_frag(pkt, K_FOREVER); |
| |
| NET_ASSERT_INFO(frag, "Out of DATA buffers"); |
| |
| net_pkt_frag_add(pkt, frag); |
| |
| net_pkt_set_iface(pkt, iface); |
| net_pkt_set_family(pkt, AF_INET6); |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| |
| net_pkt_ll_clear(pkt); |
| |
| llao_len = get_llao_len(iface); |
| |
| net_pkt_set_ipv6_ext_len(pkt, 0); |
| |
| setup_headers(pkt, sizeof(struct net_icmpv6_na_hdr) + llao_len, |
| NET_ICMPV6_NA); |
| |
| net_buf_add(frag, sizeof(struct net_icmpv6_na_hdr) + llao_len); |
| |
| na_hdr = net_icmpv6_get_na_hdr(pkt, &hdr); |
| NET_ASSERT_INFO(na_hdr, "Too short fragment for NA"); |
| |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src); |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst); |
| net_ipaddr_copy(&na_hdr->tgt, tgt); |
| |
| set_llao(&net_pkt_iface(pkt)->link_addr, |
| (u8_t *)net_pkt_icmp_data(pkt) + sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_na_hdr), |
| llao_len, NET_ICMPV6_ND_OPT_TLLAO); |
| |
| na_hdr->flags = flags; |
| net_icmpv6_set_na_hdr(pkt, na_hdr); |
| |
| pkt->frags->len = NET_IPV6ICMPH_LEN + |
| sizeof(struct net_icmpv6_na_hdr) + llao_len; |
| |
| net_icmpv6_set_chksum(pkt, pkt->frags); |
| |
| dbg_addr_sent_tgt("Neighbor Advertisement", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst, |
| &na_hdr->tgt); |
| |
| if (net_send_data(pkt) < 0) { |
| goto drop; |
| } |
| |
| net_stats_update_ipv6_nd_sent(); |
| |
| return 0; |
| |
| drop: |
| net_pkt_unref(pkt); |
| net_stats_update_ipv6_nd_drop(); |
| |
| return -EINVAL; |
| } |
| |
| static enum net_verdict handle_ns_input(struct net_pkt *pkt) |
| { |
| u16_t total_len = net_pkt_get_len(pkt); |
| struct net_icmpv6_nd_opt_hdr ndopthdr, *nd_opt_hdr; |
| struct net_icmpv6_ns_hdr nshdr, *ns_hdr; |
| struct net_if_addr *ifaddr; |
| u8_t flags = 0, prev_opt_len = 0; |
| int ret; |
| size_t left_len; |
| |
| ns_hdr = net_icmpv6_get_ns_hdr(pkt, &nshdr); |
| NET_ASSERT(ns_hdr); |
| |
| dbg_addr_recv_tgt("Neighbor Solicitation", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst, |
| &ns_hdr->tgt); |
| |
| net_stats_update_ipv6_nd_recv(); |
| |
| if ((total_len < (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_ns_hdr))) || |
| (NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT)) { |
| if (net_is_ipv6_addr_mcast(&ns_hdr->tgt)) { |
| struct net_icmp_hdr hdr, *icmp_hdr; |
| |
| icmp_hdr = net_icmpv6_get_hdr(pkt, &hdr); |
| if (!icmp_hdr || icmp_hdr->code != 0) { |
| NET_DBG("Preliminary check failed %u/%zu, " |
| "code %u, hop %u", |
| total_len, |
| (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_ns_hdr)), |
| icmp_hdr->code, |
| NET_IPV6_HDR(pkt)->hop_limit); |
| goto drop; |
| } |
| } |
| } |
| |
| net_pkt_set_ipv6_ext_opt_len(pkt, sizeof(struct net_icmpv6_ns_hdr)); |
| |
| nd_opt_hdr = net_icmpv6_get_nd_opt_hdr(pkt, &ndopthdr); |
| NET_ASSERT(nd_opt_hdr); |
| |
| left_len = net_pkt_get_len(pkt) - (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr)); |
| |
| while (nd_opt_hdr && net_pkt_ipv6_ext_opt_len(pkt) < left_len) { |
| if (!nd_opt_hdr->len) { |
| break; |
| } |
| |
| switch (nd_opt_hdr->type) { |
| case NET_ICMPV6_ND_OPT_SLLAO: |
| if (net_is_ipv6_addr_unspecified( |
| &NET_IPV6_HDR(pkt)->src)) { |
| goto drop; |
| } |
| |
| handle_ns_neighbor(pkt, nd_opt_hdr->len, |
| net_pkt_ip_hdr_len(pkt) + |
| net_pkt_ipv6_ext_len(pkt) + |
| sizeof(struct net_icmp_hdr) + |
| net_pkt_ipv6_ext_opt_len(pkt) + |
| 1 + 1); |
| break; |
| |
| default: |
| NET_DBG("Unknown ND option 0x%x", nd_opt_hdr->type); |
| break; |
| } |
| |
| prev_opt_len = net_pkt_ipv6_ext_opt_len(pkt); |
| |
| net_pkt_set_ipv6_ext_opt_len(pkt, |
| net_pkt_ipv6_ext_opt_len(pkt) + |
| (nd_opt_hdr->len << 3)); |
| |
| if (prev_opt_len >= net_pkt_ipv6_ext_opt_len(pkt)) { |
| NET_ERR("Corrupted NS message"); |
| goto drop; |
| } |
| |
| nd_opt_hdr = net_icmpv6_get_nd_opt_hdr(pkt, &ndopthdr); |
| } |
| |
| ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), |
| &ns_hdr->tgt); |
| if (!ifaddr) { |
| NET_DBG("No such interface address %s", |
| net_sprint_ipv6_addr(&ns_hdr->tgt)); |
| goto drop; |
| } |
| |
| #if !defined(CONFIG_NET_IPV6_DAD) |
| if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) { |
| goto drop; |
| } |
| |
| #else /* CONFIG_NET_IPV6_DAD */ |
| |
| /* Do DAD */ |
| if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) { |
| |
| if (!net_is_ipv6_addr_solicited_node(&NET_IPV6_HDR(pkt)->dst)) { |
| NET_DBG("Not solicited node addr %s", |
| net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->dst)); |
| goto drop; |
| } |
| |
| if (ifaddr->addr_state == NET_ADDR_TENTATIVE) { |
| NET_DBG("DAD failed for %s iface %p", |
| net_sprint_ipv6_addr(&ifaddr->address.in6_addr), |
| net_pkt_iface(pkt)); |
| |
| dad_failed(net_pkt_iface(pkt), |
| &ifaddr->address.in6_addr); |
| goto drop; |
| } |
| |
| /* We reuse the received packet to send the NA */ |
| net_ipv6_addr_create_ll_allnodes_mcast(&NET_IPV6_HDR(pkt)->dst); |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, |
| net_if_ipv6_select_src_addr(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->dst)); |
| flags = NET_ICMPV6_NA_FLAG_OVERRIDE; |
| goto send_na; |
| } |
| #endif /* CONFIG_NET_IPV6_DAD */ |
| |
| if (net_is_my_ipv6_addr(&NET_IPV6_HDR(pkt)->src)) { |
| NET_DBG("Duplicate IPv6 %s address", |
| net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->src)); |
| goto drop; |
| } |
| |
| /* Address resolution */ |
| if (net_is_ipv6_addr_solicited_node(&NET_IPV6_HDR(pkt)->dst)) { |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, |
| &NET_IPV6_HDR(pkt)->src); |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, &ns_hdr->tgt); |
| flags = NET_ICMPV6_NA_FLAG_SOLICITED | |
| NET_ICMPV6_NA_FLAG_OVERRIDE; |
| goto send_na; |
| } |
| |
| /* Neighbor Unreachability Detection (NUD) */ |
| if (net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->dst)) { |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, |
| &NET_IPV6_HDR(pkt)->src); |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, &ns_hdr->tgt); |
| flags = NET_ICMPV6_NA_FLAG_SOLICITED | |
| NET_ICMPV6_NA_FLAG_OVERRIDE; |
| goto send_na; |
| } else { |
| NET_DBG("NUD failed"); |
| goto drop; |
| } |
| |
| send_na: |
| ret = net_ipv6_send_na(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst, |
| &ifaddr->address.in6_addr, |
| flags); |
| if (!ret) { |
| net_pkt_unref(pkt); |
| return NET_OK; |
| } |
| |
| return NET_DROP; |
| |
| drop: |
| net_stats_update_ipv6_nd_drop(); |
| |
| return NET_DROP; |
| } |
| #endif /* CONFIG_NET_IPV6_NBR_CACHE */ |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| static void nd_reachable_timeout(struct k_work *work) |
| { |
| struct net_ipv6_nbr_data *data = CONTAINER_OF(work, |
| struct net_ipv6_nbr_data, |
| reachable); |
| |
| struct net_nbr *nbr = get_nbr_from_data(data); |
| |
| if (!data || !nbr) { |
| NET_DBG("ND reachable timeout but no nbr data " |
| "(nbr %p data %p)", nbr, data); |
| return; |
| } |
| |
| switch (data->state) { |
| case NET_IPV6_NBR_STATE_STATIC: |
| NET_ASSERT_INFO(false, "Static entry shall never timeout"); |
| break; |
| |
| case NET_IPV6_NBR_STATE_INCOMPLETE: |
| if (data->ns_count >= MAX_MULTICAST_SOLICIT) { |
| nbr_free(nbr); |
| } else { |
| data->ns_count++; |
| |
| NET_DBG("nbr %p incomplete count %u", nbr, |
| data->ns_count); |
| |
| net_ipv6_send_ns(nbr->iface, NULL, NULL, NULL, |
| &data->addr, false); |
| } |
| break; |
| |
| case NET_IPV6_NBR_STATE_REACHABLE: |
| data->state = NET_IPV6_NBR_STATE_STALE; |
| |
| NET_DBG("nbr %p moving %s state to STALE (%d)", |
| nbr, net_sprint_ipv6_addr(&data->addr), data->state); |
| break; |
| |
| case NET_IPV6_NBR_STATE_STALE: |
| NET_DBG("nbr %p removing stale address %s", |
| nbr, net_sprint_ipv6_addr(&data->addr)); |
| nbr_free(nbr); |
| break; |
| |
| case NET_IPV6_NBR_STATE_DELAY: |
| data->state = NET_IPV6_NBR_STATE_PROBE; |
| data->ns_count = 0; |
| |
| NET_DBG("nbr %p moving %s state to PROBE (%d)", |
| nbr, net_sprint_ipv6_addr(&data->addr), data->state); |
| |
| /* Intentionally continuing to probe state */ |
| |
| case NET_IPV6_NBR_STATE_PROBE: |
| if (data->ns_count >= MAX_UNICAST_SOLICIT) { |
| struct net_if_router *router; |
| |
| router = net_if_ipv6_router_lookup(nbr->iface, |
| &data->addr); |
| if (router && !router->is_infinite) { |
| NET_DBG("nbr %p address %s PROBE ended (%d)", |
| nbr, net_sprint_ipv6_addr(&data->addr), |
| data->state); |
| |
| net_if_ipv6_router_rm(router); |
| nbr_free(nbr); |
| } |
| } else { |
| data->ns_count++; |
| |
| NET_DBG("nbr %p probe count %u", nbr, |
| data->ns_count); |
| |
| net_ipv6_send_ns(nbr->iface, NULL, NULL, NULL, |
| &data->addr, false); |
| |
| k_delayed_work_submit( |
| &net_ipv6_nbr_data(nbr)->reachable, |
| RETRANS_TIMER); |
| } |
| break; |
| } |
| } |
| |
| void net_ipv6_nbr_set_reachable_timer(struct net_if *iface, struct net_nbr *nbr) |
| { |
| u32_t time; |
| |
| time = net_if_ipv6_get_reachable_time(iface); |
| |
| NET_ASSERT_INFO(time, "Zero reachable timeout!"); |
| |
| NET_DBG("Starting reachable timer nbr %p data %p time %d ms", |
| nbr, net_ipv6_nbr_data(nbr), time); |
| |
| k_delayed_work_submit(&net_ipv6_nbr_data(nbr)->reachable, time); |
| } |
| #endif /* CONFIG_NET_IPV6_ND */ |
| |
| #if defined(CONFIG_NET_IPV6_NBR_CACHE) |
| static inline bool handle_na_neighbor(struct net_pkt *pkt, |
| struct net_icmpv6_na_hdr *na_hdr, |
| u16_t tllao_offset) |
| { |
| bool lladdr_changed = false; |
| struct net_nbr *nbr; |
| struct net_linkaddr_storage lladdr = { 0 }; |
| struct net_linkaddr_storage *cached_lladdr; |
| struct net_pkt *pending; |
| struct net_buf *frag; |
| u16_t pos; |
| |
| nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt), |
| &na_hdr->tgt); |
| |
| NET_DBG("Neighbor lookup %p iface %p addr %s", nbr, |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&na_hdr->tgt)); |
| |
| if (!nbr) { |
| nbr_print(); |
| |
| NET_DBG("No such neighbor found, msg discarded"); |
| return false; |
| } |
| |
| if (tllao_offset) { |
| lladdr.len = net_pkt_iface(pkt)->link_addr.len; |
| |
| frag = net_frag_read(pkt->frags, tllao_offset, |
| &pos, lladdr.len, lladdr.addr); |
| if (!frag && pos == 0xffff) { |
| return false; |
| } |
| } |
| |
| if (nbr->idx == NET_NBR_LLADDR_UNKNOWN) { |
| struct net_linkaddr nbr_lladdr; |
| |
| if (!tllao_offset) { |
| NET_DBG("No target link layer address."); |
| return false; |
| } |
| |
| nbr_lladdr.len = lladdr.len; |
| nbr_lladdr.addr = lladdr.addr; |
| |
| if (net_nbr_link(nbr, net_pkt_iface(pkt), &nbr_lladdr)) { |
| nbr_free(nbr); |
| return false; |
| } |
| |
| NET_DBG("[%d] nbr %p state %d IPv6 %s ll %s", |
| nbr->idx, nbr, net_ipv6_nbr_data(nbr)->state, |
| net_sprint_ipv6_addr(&na_hdr->tgt), |
| net_sprint_ll_addr(nbr_lladdr.addr, nbr_lladdr.len)); |
| } |
| |
| cached_lladdr = net_nbr_get_lladdr(nbr->idx); |
| if (!cached_lladdr) { |
| NET_DBG("No lladdr but index defined"); |
| return false; |
| } |
| |
| if (tllao_offset) { |
| lladdr_changed = memcmp(lladdr.addr, |
| cached_lladdr->addr, |
| cached_lladdr->len); |
| } |
| |
| /* Update the cached address if we do not yet known it */ |
| if (net_ipv6_nbr_data(nbr)->state == NET_IPV6_NBR_STATE_INCOMPLETE) { |
| if (!tllao_offset) { |
| return false; |
| } |
| |
| if (lladdr_changed) { |
| dbg_update_neighbor_lladdr_raw(lladdr.addr, |
| cached_lladdr, |
| &na_hdr->tgt); |
| |
| net_linkaddr_set(cached_lladdr, lladdr.addr, |
| cached_lladdr->len); |
| } |
| |
| if (net_is_solicited(pkt)) { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_REACHABLE); |
| net_ipv6_nbr_data(nbr)->ns_count = 0; |
| |
| /* We might have active timer from PROBE */ |
| k_delayed_work_cancel( |
| &net_ipv6_nbr_data(nbr)->reachable); |
| |
| net_ipv6_nbr_set_reachable_timer(net_pkt_iface(pkt), |
| nbr); |
| } else { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE); |
| } |
| |
| net_ipv6_nbr_data(nbr)->is_router = net_is_router(pkt); |
| |
| goto send_pending; |
| } |
| |
| /* We do not update the address if override bit is not set |
| * and we have a valid address in the cache. |
| */ |
| if (!net_is_override(pkt) && lladdr_changed) { |
| if (net_ipv6_nbr_data(nbr)->state == |
| NET_IPV6_NBR_STATE_REACHABLE) { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_STALE); |
| } |
| |
| return false; |
| } |
| |
| if (net_is_override(pkt) || |
| (!net_is_override(pkt) && tllao_offset && !lladdr_changed)) { |
| |
| if (lladdr_changed) { |
| dbg_update_neighbor_lladdr_raw( |
| lladdr.addr, cached_lladdr, &na_hdr->tgt); |
| |
| net_linkaddr_set(cached_lladdr, lladdr.addr, |
| cached_lladdr->len); |
| } |
| |
| if (net_is_solicited(pkt)) { |
| ipv6_nbr_set_state(nbr, NET_IPV6_NBR_STATE_REACHABLE); |
| |
| /* We might have active timer from PROBE */ |
| k_delayed_work_cancel( |
| &net_ipv6_nbr_data(nbr)->reachable); |
| |
| net_ipv6_nbr_set_reachable_timer(net_pkt_iface(pkt), |
| nbr); |
| } else { |
| if (lladdr_changed) { |
| ipv6_nbr_set_state(nbr, |
| NET_IPV6_NBR_STATE_STALE); |
| } |
| } |
| } |
| |
| if (net_ipv6_nbr_data(nbr)->is_router && !net_is_router(pkt)) { |
| /* Update the routing if the peer is no longer |
| * a router. |
| */ |
| /* FIXME */ |
| } |
| |
| net_ipv6_nbr_data(nbr)->is_router = net_is_router(pkt); |
| |
| send_pending: |
| /* Next send any pending messages to the peer. */ |
| pending = net_ipv6_nbr_data(nbr)->pending; |
| |
| if (pending) { |
| NET_DBG("Sending pending %p to %s lladdr %s", pending, |
| net_sprint_ipv6_addr(&NET_IPV6_HDR(pending)->dst), |
| net_sprint_ll_addr(cached_lladdr->addr, |
| cached_lladdr->len)); |
| |
| if (net_send_data(pending) < 0) { |
| nbr_clear_ns_pending(net_ipv6_nbr_data(nbr)); |
| } else { |
| net_ipv6_nbr_data(nbr)->pending = NULL; |
| } |
| |
| net_pkt_unref(pending); |
| } |
| |
| return true; |
| } |
| |
| static enum net_verdict handle_na_input(struct net_pkt *pkt) |
| { |
| u16_t total_len = net_pkt_get_len(pkt); |
| u16_t tllao_offset = 0; |
| u8_t prev_opt_len = 0; |
| struct net_icmpv6_nd_opt_hdr ndopthdr, *nd_opt_hdr; |
| struct net_icmpv6_na_hdr nahdr, *na_hdr; |
| struct net_if_addr *ifaddr; |
| size_t left_len; |
| |
| na_hdr = net_icmpv6_get_na_hdr(pkt, &nahdr); |
| NET_ASSERT(na_hdr); |
| |
| dbg_addr_recv_tgt("Neighbor Advertisement", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst, |
| &na_hdr->tgt); |
| |
| net_stats_update_ipv6_nd_recv(); |
| |
| if ((total_len < (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_na_hdr) + |
| sizeof(struct net_icmpv6_nd_opt_hdr))) || |
| (NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT) || |
| net_is_ipv6_addr_mcast(&na_hdr->tgt) || |
| (net_is_solicited(pkt) && |
| net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst))) { |
| struct net_icmp_hdr hdr, *icmp_hdr; |
| |
| icmp_hdr = net_icmpv6_get_hdr(pkt, &hdr); |
| if (!icmp_hdr || icmp_hdr->code != 0) { |
| goto drop; |
| } |
| } |
| |
| net_pkt_set_ipv6_ext_opt_len(pkt, sizeof(struct net_icmpv6_na_hdr)); |
| |
| nd_opt_hdr = net_icmpv6_get_nd_opt_hdr(pkt, &ndopthdr); |
| |
| left_len = net_pkt_get_len(pkt) - (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr)); |
| |
| while (nd_opt_hdr && net_pkt_ipv6_ext_opt_len(pkt) < left_len) { |
| if (!nd_opt_hdr->len) { |
| break; |
| } |
| |
| switch (nd_opt_hdr->type) { |
| case NET_ICMPV6_ND_OPT_TLLAO: |
| tllao_offset = net_pkt_ip_hdr_len(pkt) + |
| net_pkt_ipv6_ext_len(pkt) + |
| sizeof(struct net_icmp_hdr) + |
| net_pkt_ipv6_ext_opt_len(pkt) + 1 + 1; |
| break; |
| |
| default: |
| NET_DBG("Unknown ND option 0x%x", nd_opt_hdr->type); |
| break; |
| } |
| |
| prev_opt_len = net_pkt_ipv6_ext_opt_len(pkt); |
| |
| net_pkt_set_ipv6_ext_opt_len(pkt, |
| net_pkt_ipv6_ext_opt_len(pkt) + |
| (nd_opt_hdr->len << 3)); |
| |
| if (prev_opt_len >= net_pkt_ipv6_ext_opt_len(pkt)) { |
| NET_ERR("Corrupted NA message"); |
| goto drop; |
| } |
| |
| nd_opt_hdr = net_icmpv6_get_nd_opt_hdr(pkt, &ndopthdr); |
| } |
| |
| ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), |
| &na_hdr->tgt); |
| if (ifaddr) { |
| NET_DBG("Interface %p already has address %s", |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&na_hdr->tgt)); |
| |
| #if defined(CONFIG_NET_IPV6_DAD) |
| if (ifaddr->addr_state == NET_ADDR_TENTATIVE) { |
| dad_failed(net_pkt_iface(pkt), &na_hdr->tgt); |
| } |
| #endif /* CONFIG_NET_IPV6_DAD */ |
| |
| goto drop; |
| } |
| |
| if (!handle_na_neighbor(pkt, na_hdr, tllao_offset)) { |
| goto drop; |
| } |
| |
| net_pkt_unref(pkt); |
| |
| net_stats_update_ipv6_nd_sent(); |
| |
| return NET_OK; |
| |
| drop: |
| net_stats_update_ipv6_nd_drop(); |
| |
| return NET_DROP; |
| } |
| |
| int net_ipv6_send_ns(struct net_if *iface, |
| struct net_pkt *pending, |
| struct in6_addr *src, |
| struct in6_addr *dst, |
| struct in6_addr *tgt, |
| bool is_my_address) |
| { |
| struct net_icmpv6_ns_hdr hdr, *ns_hdr; |
| struct net_pkt *pkt; |
| struct net_buf *frag; |
| struct net_nbr *nbr; |
| u8_t llao_len; |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, dst), |
| K_FOREVER); |
| |
| NET_ASSERT_INFO(pkt, "Out of TX packets"); |
| |
| frag = net_pkt_get_frag(pkt, K_FOREVER); |
| |
| NET_ASSERT_INFO(frag, "Out of DATA buffers"); |
| |
| net_pkt_frag_add(pkt, frag); |
| |
| net_pkt_set_iface(pkt, iface); |
| net_pkt_set_family(pkt, AF_INET6); |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| net_pkt_set_ipv6_ext_len(pkt, 0); |
| |
| net_pkt_ll_clear(pkt); |
| |
| llao_len = get_llao_len(net_pkt_iface(pkt)); |
| |
| setup_headers(pkt, sizeof(struct net_icmpv6_ns_hdr) + llao_len, |
| NET_ICMPV6_NS); |
| |
| net_buf_add(frag, sizeof(struct net_icmpv6_ns_hdr)); |
| |
| ns_hdr = net_icmpv6_get_ns_hdr(pkt, &hdr); |
| NET_ASSERT(ns_hdr); |
| |
| if (!dst) { |
| net_ipv6_addr_create_solicited_node(tgt, |
| &NET_IPV6_HDR(pkt)->dst); |
| } else { |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, dst); |
| } |
| |
| net_ipaddr_copy(&ns_hdr->tgt, tgt); |
| net_icmpv6_set_ns_hdr(pkt, ns_hdr); |
| |
| if (is_my_address) { |
| /* DAD */ |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, |
| net_ipv6_unspecified_address()); |
| |
| NET_IPV6_HDR(pkt)->len[1] -= llao_len; |
| } else { |
| if (src) { |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, src); |
| } else { |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, |
| net_if_ipv6_select_src_addr( |
| net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->dst)); |
| } |
| |
| if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) { |
| NET_DBG("No source address for NS"); |
| goto drop; |
| } |
| |
| net_buf_add(frag, llao_len); |
| |
| set_llao(&net_pkt_iface(pkt)->link_addr, |
| (u8_t *)net_pkt_icmp_data(pkt) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_ns_hdr), |
| llao_len, NET_ICMPV6_ND_OPT_SLLAO); |
| } |
| |
| net_icmpv6_set_chksum(pkt, pkt->frags); |
| |
| nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt), &ns_hdr->tgt); |
| if (!nbr) { |
| nbr_print(); |
| |
| nbr = nbr_new(net_pkt_iface(pkt), &ns_hdr->tgt, false, |
| NET_IPV6_NBR_STATE_INCOMPLETE); |
| if (!nbr) { |
| NET_DBG("Could not create new neighbor %s", |
| net_sprint_ipv6_addr(&ns_hdr->tgt)); |
| goto drop; |
| } |
| } |
| |
| if (pending) { |
| if (!net_ipv6_nbr_data(nbr)->pending) { |
| net_ipv6_nbr_data(nbr)->pending = net_pkt_ref(pending); |
| } else { |
| NET_DBG("Packet %p already pending for " |
| "operation. Discarding pending %p and pkt %p", |
| net_ipv6_nbr_data(nbr)->pending, pending, pkt); |
| net_pkt_unref(pending); |
| goto drop; |
| } |
| |
| NET_DBG("Setting timeout %d for NS", NS_REPLY_TIMEOUT); |
| |
| k_delayed_work_submit(&net_ipv6_nbr_data(nbr)->send_ns, |
| NS_REPLY_TIMEOUT); |
| } |
| |
| dbg_addr_sent_tgt("Neighbor Solicitation", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst, |
| &ns_hdr->tgt); |
| |
| if (net_send_data(pkt) < 0) { |
| NET_DBG("Cannot send NS %p (pending %p)", pkt, pending); |
| |
| if (pending) { |
| nbr_clear_ns_pending(net_ipv6_nbr_data(nbr)); |
| } |
| |
| goto drop; |
| } |
| |
| net_stats_update_ipv6_nd_sent(); |
| |
| return 0; |
| |
| drop: |
| net_pkt_unref(pkt); |
| net_stats_update_ipv6_nd_drop(); |
| |
| return -EINVAL; |
| } |
| #endif /* CONFIG_NET_IPV6_NBR_CACHE */ |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| int net_ipv6_send_rs(struct net_if *iface) |
| { |
| struct net_pkt *pkt; |
| struct net_buf *frag; |
| bool unspec_src; |
| u8_t llao_len = 0; |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL), |
| K_FOREVER); |
| |
| frag = net_pkt_get_frag(pkt, K_FOREVER); |
| |
| net_pkt_frag_add(pkt, frag); |
| |
| net_pkt_set_iface(pkt, iface); |
| net_pkt_set_family(pkt, AF_INET6); |
| net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr)); |
| |
| net_pkt_ll_clear(pkt); |
| |
| net_ipv6_addr_create_ll_allnodes_mcast(&NET_IPV6_HDR(pkt)->dst); |
| |
| net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, |
| net_if_ipv6_select_src_addr(iface, |
| &NET_IPV6_HDR(pkt)->dst)); |
| |
| unspec_src = net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src); |
| if (!unspec_src) { |
| llao_len = get_llao_len(net_pkt_iface(pkt)); |
| } |
| |
| setup_headers(pkt, sizeof(struct net_icmpv6_rs_hdr) + llao_len, |
| NET_ICMPV6_RS); |
| |
| net_buf_add(frag, sizeof(struct net_icmpv6_rs_hdr)); |
| |
| if (!unspec_src) { |
| net_buf_add(frag, llao_len); |
| |
| set_llao(&net_pkt_iface(pkt)->link_addr, |
| (u8_t *)net_pkt_icmp_data(pkt) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_rs_hdr), |
| llao_len, NET_ICMPV6_ND_OPT_SLLAO); |
| } |
| |
| net_icmpv6_set_chksum(pkt, pkt->frags); |
| |
| dbg_addr_sent("Router Solicitation", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst); |
| |
| if (net_send_data(pkt) < 0) { |
| goto drop; |
| } |
| |
| net_stats_update_ipv6_nd_sent(); |
| |
| return 0; |
| |
| drop: |
| net_pkt_unref(pkt); |
| net_stats_update_ipv6_nd_drop(); |
| |
| return -EINVAL; |
| } |
| |
| int net_ipv6_start_rs(struct net_if *iface) |
| { |
| return net_ipv6_send_rs(iface); |
| } |
| |
| static inline struct net_buf *handle_ra_neighbor(struct net_pkt *pkt, |
| struct net_buf *frag, |
| u8_t len, |
| u16_t offset, u16_t *pos, |
| struct net_nbr **nbr) |
| |
| { |
| struct net_linkaddr lladdr; |
| struct net_linkaddr_storage llstorage; |
| u8_t padding; |
| |
| if (!nbr) { |
| return NULL; |
| } |
| |
| llstorage.len = NET_LINK_ADDR_MAX_LENGTH; |
| lladdr.len = NET_LINK_ADDR_MAX_LENGTH; |
| lladdr.addr = llstorage.addr; |
| if (net_pkt_ll_src(pkt)->len < lladdr.len) { |
| lladdr.len = net_pkt_ll_src(pkt)->len; |
| } |
| |
| frag = net_frag_read(frag, offset, pos, lladdr.len, lladdr.addr); |
| if (!frag && offset) { |
| return NULL; |
| } |
| |
| padding = len * 8 - 2 - lladdr.len; |
| if (padding) { |
| frag = net_frag_read(frag, *pos, pos, padding, NULL); |
| if (!frag && *pos) { |
| return NULL; |
| } |
| } |
| |
| *nbr = nbr_add(pkt, &lladdr, true, NET_IPV6_NBR_STATE_STALE); |
| |
| return frag; |
| } |
| |
| static inline void handle_prefix_onlink(struct net_pkt *pkt, |
| struct net_icmpv6_nd_opt_prefix_info *prefix_info) |
| { |
| struct net_if_ipv6_prefix *prefix; |
| |
| prefix = net_if_ipv6_prefix_lookup(net_pkt_iface(pkt), |
| &prefix_info->prefix, |
| prefix_info->prefix_len); |
| if (!prefix) { |
| if (!prefix_info->valid_lifetime) { |
| return; |
| } |
| |
| prefix = net_if_ipv6_prefix_add(net_pkt_iface(pkt), |
| &prefix_info->prefix, |
| prefix_info->prefix_len, |
| prefix_info->valid_lifetime); |
| if (prefix) { |
| NET_DBG("Interface %p add prefix %s/%d lifetime %u", |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&prefix_info->prefix), |
| prefix_info->prefix_len, |
| prefix_info->valid_lifetime); |
| } else { |
| NET_ERR("Prefix %s/%d could not be added to iface %p", |
| net_sprint_ipv6_addr(&prefix_info->prefix), |
| prefix_info->prefix_len, |
| net_pkt_iface(pkt)); |
| |
| return; |
| } |
| } |
| |
| switch (prefix_info->valid_lifetime) { |
| case 0: |
| NET_DBG("Interface %p delete prefix %s/%d", |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&prefix_info->prefix), |
| prefix_info->prefix_len); |
| |
| net_if_ipv6_prefix_rm(net_pkt_iface(pkt), |
| &prefix->prefix, |
| prefix->len); |
| break; |
| |
| case NET_IPV6_ND_INFINITE_LIFETIME: |
| NET_DBG("Interface %p prefix %s/%d infinite", |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&prefix->prefix), |
| prefix->len); |
| |
| net_if_ipv6_prefix_set_lf(prefix, true); |
| break; |
| |
| default: |
| NET_DBG("Interface %p update prefix %s/%u lifetime %u", |
| net_pkt_iface(pkt), |
| net_sprint_ipv6_addr(&prefix_info->prefix), |
| prefix_info->prefix_len, |
| prefix_info->valid_lifetime); |
| |
| net_if_ipv6_prefix_set_lf(prefix, false); |
| net_if_ipv6_prefix_set_timer(prefix, |
| prefix_info->valid_lifetime); |
| break; |
| } |
| } |
| |
| #define TWO_HOURS (2 * 60 * 60) |
| |
| static inline u32_t remaining(struct k_delayed_work *work) |
| { |
| return k_delayed_work_remaining_get(work) / MSEC_PER_SEC; |
| } |
| |
| static inline void handle_prefix_autonomous(struct net_pkt *pkt, |
| struct net_icmpv6_nd_opt_prefix_info *prefix_info) |
| { |
| struct in6_addr addr = { }; |
| struct net_if_addr *ifaddr; |
| |
| /* Create IPv6 address using the given prefix and iid. We first |
| * setup link local address, and then copy prefix over first 8 |
| * bytes of that address. |
| */ |
| net_ipv6_addr_create_iid(&addr, |
| net_if_get_link_addr(net_pkt_iface(pkt))); |
| memcpy(&addr, &prefix_info->prefix, sizeof(struct in6_addr) / 2); |
| |
| ifaddr = net_if_ipv6_addr_lookup(&addr, NULL); |
| if (ifaddr && ifaddr->addr_type == NET_ADDR_AUTOCONF) { |
| if (prefix_info->valid_lifetime == |
| NET_IPV6_ND_INFINITE_LIFETIME) { |
| net_if_addr_set_lf(ifaddr, true); |
| return; |
| } |
| |
| /* RFC 4862 ch 5.5.3 */ |
| if ((prefix_info->valid_lifetime > TWO_HOURS) || |
| (prefix_info->valid_lifetime > |
| remaining(&ifaddr->lifetime))) { |
| NET_DBG("Timer updating for address %s " |
| "long lifetime %u secs", |
| net_sprint_ipv6_addr(&addr), |
| prefix_info->valid_lifetime); |
| |
| net_if_ipv6_addr_update_lifetime(ifaddr, |
| prefix_info->valid_lifetime); |
| } else { |
| NET_DBG("Timer updating for address %s " |
| "lifetime %u secs", |
| net_sprint_ipv6_addr(&addr), TWO_HOURS); |
| |
| net_if_ipv6_addr_update_lifetime(ifaddr, TWO_HOURS); |
| } |
| |
| net_if_addr_set_lf(ifaddr, false); |
| } else { |
| if (prefix_info->valid_lifetime == |
| NET_IPV6_ND_INFINITE_LIFETIME) { |
| net_if_ipv6_addr_add(net_pkt_iface(pkt), |
| &addr, NET_ADDR_AUTOCONF, 0); |
| } else { |
| net_if_ipv6_addr_add(net_pkt_iface(pkt), |
| &addr, NET_ADDR_AUTOCONF, |
| prefix_info->valid_lifetime); |
| } |
| } |
| } |
| |
| static inline struct net_buf *handle_ra_prefix(struct net_pkt *pkt, |
| struct net_buf *frag, |
| u8_t len, |
| u16_t offset, u16_t *pos) |
| { |
| struct net_icmpv6_nd_opt_prefix_info prefix_info; |
| |
| prefix_info.type = NET_ICMPV6_ND_OPT_PREFIX_INFO; |
| prefix_info.len = len * 8 - 2; |
| |
| frag = net_frag_read(frag, offset, pos, 1, &prefix_info.prefix_len); |
| frag = net_frag_read(frag, *pos, pos, 1, &prefix_info.flags); |
| frag = net_frag_read_be32(frag, *pos, pos, &prefix_info.valid_lifetime); |
| frag = net_frag_read_be32(frag, *pos, pos, |
| &prefix_info.preferred_lifetime); |
| /* Skip reserved bytes */ |
| frag = net_frag_skip(frag, *pos, pos, 4); |
| frag = net_frag_read(frag, *pos, pos, sizeof(struct in6_addr), |
| prefix_info.prefix.s6_addr); |
| if (!frag && *pos) { |
| return NULL; |
| } |
| |
| if (prefix_info.valid_lifetime >= prefix_info.preferred_lifetime && |
| !net_is_ipv6_ll_addr(&prefix_info.prefix)) { |
| |
| if (prefix_info.flags & NET_ICMPV6_RA_FLAG_ONLINK) { |
| handle_prefix_onlink(pkt, &prefix_info); |
| } |
| |
| if ((prefix_info.flags & NET_ICMPV6_RA_FLAG_AUTONOMOUS) && |
| prefix_info.valid_lifetime && |
| (prefix_info.prefix_len == NET_IPV6_DEFAULT_PREFIX_LEN)) { |
| handle_prefix_autonomous(pkt, &prefix_info); |
| } |
| } |
| |
| return frag; |
| } |
| |
| #if defined(CONFIG_NET_6LO_CONTEXT) |
| /* 6lowpan Context Option RFC 6775, 4.2 */ |
| static inline struct net_buf *handle_ra_6co(struct net_pkt *pkt, |
| struct net_buf *frag, |
| u8_t len, |
| u16_t offset, u16_t *pos) |
| { |
| struct net_icmpv6_nd_opt_6co context; |
| |
| context.type = NET_ICMPV6_ND_OPT_6CO; |
| context.len = len * 8 - 2; |
| |
| frag = net_frag_read_u8(frag, offset, pos, &context.context_len); |
| |
| /* RFC 6775, 4.2 |
| * Context Length: 8-bit unsigned integer. The number of leading |
| * bits in the Context Prefix field that are valid. The value ranges |
| * from 0 to 128. If it is more than 64, then the Length MUST be 3. |
| */ |
| if (context.context_len > 64 && len != 3) { |
| return NULL; |
| } |
| |
| if (context.context_len <= 64 && len != 2) { |
| return NULL; |
| } |
| |
| context.context_len = context.context_len / 8; |
| frag = net_frag_read_u8(frag, *pos, pos, &context.flag); |
| |
| /* Skip reserved bytes */ |
| frag = net_frag_skip(frag, *pos, pos, 2); |
| frag = net_frag_read_be16(frag, *pos, pos, &context.lifetime); |
| |
| /* RFC 6775, 4.2 (Length field). Length can be 2 or 3 depending |
| * on the length of context prefix field. |
| */ |
| if (len == 3) { |
| frag = net_frag_read(frag, *pos, pos, sizeof(struct in6_addr), |
| context.prefix.s6_addr); |
| } else if (len == 2) { |
| /* If length is 2 means only 64 bits of context prefix |
| * is available, rest set to zeros. |
| */ |
| frag = net_frag_read(frag, *pos, pos, 8, |
| context.prefix.s6_addr); |
| } |
| |
| if (!frag && *pos) { |
| return NULL; |
| } |
| |
| /* context_len: The number of leading bits in the Context Prefix |
| * field that are valid. So set remaining data to zero. |
| */ |
| if (context.context_len != sizeof(struct in6_addr)) { |
| memset(context.prefix.s6_addr + context.context_len, 0, |
| sizeof(struct in6_addr) - context.context_len); |
| } |
| |
| net_6lo_set_context(net_pkt_iface(pkt), &context); |
| |
| return frag; |
| } |
| #endif |
| |
| static enum net_verdict handle_ra_input(struct net_pkt *pkt) |
| { |
| u16_t total_len = net_pkt_get_len(pkt); |
| struct net_nbr *nbr = NULL; |
| struct net_icmpv6_ra_hdr hdr, *ra_hdr; |
| struct net_if_router *router; |
| struct net_buf *frag; |
| u16_t router_lifetime; |
| u32_t reachable_time; |
| u32_t retrans_timer; |
| u8_t hop_limit; |
| u16_t offset; |
| u8_t length; |
| u8_t type; |
| u32_t mtu; |
| |
| dbg_addr_recv("Router Advertisement", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst); |
| |
| net_stats_update_ipv6_nd_recv(); |
| |
| if ((total_len < (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv6_ra_hdr) + |
| sizeof(struct net_icmpv6_nd_opt_hdr))) || |
| (NET_IPV6_HDR(pkt)->hop_limit != NET_IPV6_ND_HOP_LIMIT) || |
| !net_is_ipv6_ll_addr(&NET_IPV6_HDR(pkt)->src)) { |
| struct net_icmp_hdr icmphdr, *icmp_hdr; |
| |
| icmp_hdr = net_icmpv6_get_hdr(pkt, &icmphdr); |
| if (!icmp_hdr || icmp_hdr->code != 0) { |
| goto drop; |
| } |
| } |
| |
| frag = pkt->frags; |
| offset = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) + |
| sizeof(struct net_icmp_hdr); |
| |
| frag = net_frag_read_u8(frag, offset, &offset, &hop_limit); |
| frag = net_frag_skip(frag, offset, &offset, 1); /* flags */ |
| if (!frag) { |
| goto drop; |
| } |
| |
| if (hop_limit) { |
| net_ipv6_set_hop_limit(net_pkt_iface(pkt), hop_limit); |
| NET_DBG("New hop limit %d", |
| net_if_ipv6_get_hop_limit(net_pkt_iface(pkt))); |
| } |
| |
| frag = net_frag_read_be16(frag, offset, &offset, &router_lifetime); |
| frag = net_frag_read_be32(frag, offset, &offset, &reachable_time); |
| frag = net_frag_read_be32(frag, offset, &offset, &retrans_timer); |
| if (!frag) { |
| goto drop; |
| } |
| |
| ra_hdr = net_icmpv6_get_ra_hdr(pkt, &hdr); |
| NET_ASSERT(ra_hdr); |
| |
| if (reachable_time && |
| (net_if_ipv6_get_reachable_time(net_pkt_iface(pkt)) != |
| ra_hdr->reachable_time)) { |
| net_if_ipv6_set_base_reachable_time(net_pkt_iface(pkt), |
| reachable_time); |
| |
| net_if_ipv6_set_reachable_time(net_pkt_iface(pkt)); |
| } |
| |
| if (retrans_timer) { |
| net_if_ipv6_set_retrans_timer(net_pkt_iface(pkt), |
| retrans_timer); |
| } |
| |
| while (frag) { |
| frag = net_frag_read(frag, offset, &offset, 1, &type); |
| frag = net_frag_read(frag, offset, &offset, 1, &length); |
| if (!frag) { |
| goto drop; |
| } |
| |
| switch (type) { |
| case NET_ICMPV6_ND_OPT_SLLAO: |
| frag = handle_ra_neighbor(pkt, frag, length, offset, |
| &offset, &nbr); |
| if (!frag && offset) { |
| goto drop; |
| } |
| |
| break; |
| case NET_ICMPV6_ND_OPT_MTU: |
| /* MTU has reserved 2 bytes, so skip it. */ |
| frag = net_frag_skip(frag, offset, &offset, 2); |
| frag = net_frag_read_be32(frag, offset, &offset, &mtu); |
| if (!frag && offset) { |
| goto drop; |
| } |
| |
| net_if_set_mtu(net_pkt_iface(pkt), mtu); |
| |
| if (mtu > 0xffff) { |
| /* TODO: discard packet? */ |
| NET_ERR("MTU %u, max is %u", mtu, 0xffff); |
| } |
| |
| break; |
| case NET_ICMPV6_ND_OPT_PREFIX_INFO: |
| frag = handle_ra_prefix(pkt, frag, length, offset, |
| &offset); |
| if (!frag && offset) { |
| goto drop; |
| } |
| |
| break; |
| #if defined(CONFIG_NET_6LO_CONTEXT) |
| case NET_ICMPV6_ND_OPT_6CO: |
| /* RFC 6775, 4.2 (Length)*/ |
| if (!(length == 2 || length == 3)) { |
| NET_ERR("Invalid 6CO length %d", length); |
| goto drop; |
| } |
| |
| frag = handle_ra_6co(pkt, frag, length, offset, |
| &offset); |
| if (!frag && offset) { |
| goto drop; |
| } |
| |
| break; |
| #endif |
| case NET_ICMPV6_ND_OPT_ROUTE: |
| NET_DBG("Route option (0x%x) skipped", type); |
| goto skip; |
| |
| #if defined(CONFIG_NET_IPV6_RA_RDNSS) |
| case NET_ICMPV6_ND_OPT_RDNSS: |
| NET_DBG("RDNSS option (0x%x) skipped", type); |
| goto skip; |
| #endif |
| |
| case NET_ICMPV6_ND_OPT_DNSSL: |
| NET_DBG("DNSSL option (0x%x) skipped", type); |
| goto skip; |
| |
| default: |
| NET_DBG("Unknown ND option 0x%x", type); |
| skip: |
| frag = net_frag_skip(frag, offset, &offset, |
| length * 8 - 2); |
| if (!frag && offset) { |
| goto drop; |
| } |
| |
| break; |
| } |
| } |
| |
| router = net_if_ipv6_router_lookup(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->src); |
| if (router) { |
| if (!router_lifetime) { |
| /* TODO: Start rs_timer on iface if no routers |
| * at all available on iface. |
| */ |
| net_if_ipv6_router_rm(router); |
| } else { |
| if (nbr) { |
| net_ipv6_nbr_data(nbr)->is_router = true; |
| } |
| |
| net_if_ipv6_router_update_lifetime(router, |
| router_lifetime); |
| } |
| } else { |
| net_if_ipv6_router_add(net_pkt_iface(pkt), |
| &NET_IPV6_HDR(pkt)->src, |
| router_lifetime); |
| } |
| |
| if (nbr && net_ipv6_nbr_data(nbr)->pending) { |
| NET_DBG("Sending pending pkt %p to %s", |
| net_ipv6_nbr_data(nbr)->pending, |
| net_sprint_ipv6_addr(&NET_IPV6_HDR( |
| net_ipv6_nbr_data(nbr)->pending)->dst)); |
| |
| if (net_send_data(net_ipv6_nbr_data(nbr)->pending) < 0) { |
| net_pkt_unref(net_ipv6_nbr_data(nbr)->pending); |
| } |
| |
| nbr_clear_ns_pending(net_ipv6_nbr_data(nbr)); |
| } |
| |
| /* Cancel the RS timer on iface */ |
| k_delayed_work_cancel(&net_pkt_iface(pkt)->ipv6.rs_timer); |
| |
| net_pkt_unref(pkt); |
| |
| return NET_OK; |
| |
| drop: |
| net_stats_update_ipv6_nd_drop(); |
| |
| return NET_DROP; |
| } |
| #endif /* CONFIG_NET_IPV6_ND */ |
| |
| #if defined(CONFIG_NET_IPV6_MLD) |
| #define MLDv2_LEN (2 + 1 + 1 + 2 + sizeof(struct in6_addr) * 2) |
| |
| static struct net_pkt *create_mldv2(struct net_pkt *pkt, |
| const struct in6_addr *addr, |
| u16_t record_type, |
| u8_t num_sources) |
| { |
| net_pkt_append_u8(pkt, record_type); |
| net_pkt_append_u8(pkt, 0); /* aux data len */ |
| net_pkt_append_be16(pkt, num_sources); /* number of addresses */ |
| net_pkt_append_all(pkt, sizeof(struct in6_addr), addr->s6_addr, |
| K_FOREVER); |
| |
| if (num_sources > 0) { |
| /* All source addresses, RFC 3810 ch 3 */ |
| net_pkt_append_all(pkt, sizeof(struct in6_addr), |
| net_ipv6_unspecified_address()->s6_addr, |
| K_FOREVER); |
| } |
| |
| return pkt; |
| } |
| |
| static int send_mldv2_raw(struct net_if *iface, struct net_buf *frags) |
| { |
| struct net_pkt *pkt; |
| struct in6_addr dst; |
| u16_t pos; |
| int ret; |
| |
| /* Sent to all MLDv2-capable routers */ |
| net_ipv6_addr_create(&dst, 0xff02, 0, 0, 0, 0, 0, 0, 0x0016); |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, &dst), |
| K_FOREVER); |
| |
| pkt = net_ipv6_create_raw(pkt, |
| net_if_ipv6_select_src_addr(iface, &dst), |
| &dst, |
| iface, |
| NET_IPV6_NEXTHDR_HBHO); |
| |
| NET_IPV6_HDR(pkt)->hop_limit = 1; /* RFC 3810 ch 7.4 */ |
| |
| net_pkt_set_ipv6_hdr_prev(pkt, pkt->frags->len); |
| |
| /* Add hop-by-hop option and router alert option, RFC 3810 ch 5. */ |
| net_pkt_append_u8(pkt, IPPROTO_ICMPV6); |
| net_pkt_append_u8(pkt, 0); /* length (0 means 8 bytes) */ |
| |
| #define ROUTER_ALERT_LEN 8 |
| |
| /* IPv6 router alert option is described in RFC 2711. */ |
| net_pkt_append_be16(pkt, 0x0502); /* RFC 2711 ch 2.1 */ |
| net_pkt_append_be16(pkt, 0); /* pkt contains MLD msg */ |
| |
| net_pkt_append_u8(pkt, 0); /* padding */ |
| net_pkt_append_u8(pkt, 0); /* padding */ |
| |
| /* ICMPv6 header */ |
| net_pkt_append_u8(pkt, NET_ICMPV6_MLDv2); /* type */ |
| net_pkt_append_u8(pkt, 0); /* code */ |
| net_pkt_append_be16(pkt, 0); /* chksum */ |
| |
| pkt->frags->len = NET_IPV6ICMPH_LEN + ROUTER_ALERT_LEN; |
| net_pkt_set_iface(pkt, iface); |
| |
| net_pkt_append_be16(pkt, 0); /* reserved field */ |
| |
| /* Insert the actual multicast record(s) here */ |
| net_pkt_frag_add(pkt, frags); |
| |
| ret = net_ipv6_finalize_raw(pkt, NET_IPV6_NEXTHDR_HBHO); |
| if (ret < 0) { |
| goto drop; |
| } |
| |
| net_pkt_set_ipv6_ext_len(pkt, ROUTER_ALERT_LEN); |
| |
| net_pkt_write_be16(pkt, pkt->frags, |
| NET_IPV6H_LEN + ROUTER_ALERT_LEN + 2, |
| &pos, ntohs(~net_calc_chksum_icmpv6(pkt))); |
| |
| ret = net_send_data(pkt); |
| if (ret < 0) { |
| goto drop; |
| } |
| |
| net_stats_update_icmp_sent(); |
| net_stats_update_ipv6_mld_sent(); |
| |
| return 0; |
| |
| drop: |
| net_pkt_unref(pkt); |
| net_stats_update_icmp_drop(); |
| net_stats_update_ipv6_mld_drop(); |
| |
| return ret; |
| } |
| |
| static int send_mldv2(struct net_if *iface, const struct in6_addr *addr, |
| u8_t mode) |
| { |
| struct net_pkt *pkt; |
| int ret; |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL), |
| K_FOREVER); |
| |
| net_pkt_append_be16(pkt, 1); /* number of records */ |
| |
| pkt = create_mldv2(pkt, addr, mode, 1); |
| |
| ret = send_mldv2_raw(iface, pkt->frags); |
| |
| pkt->frags = NULL; |
| |
| net_pkt_unref(pkt); |
| |
| return ret; |
| } |
| |
| int net_ipv6_mld_join(struct net_if *iface, const struct in6_addr *addr) |
| { |
| struct net_if_mcast_addr *maddr; |
| int ret; |
| |
| maddr = net_if_ipv6_maddr_lookup(addr, &iface); |
| if (maddr && net_if_ipv6_maddr_is_joined(maddr)) { |
| return -EALREADY; |
| } |
| |
| if (!maddr) { |
| maddr = net_if_ipv6_maddr_add(iface, addr); |
| if (!maddr) { |
| return -ENOMEM; |
| } |
| } |
| |
| ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_EXCLUDE); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| net_if_ipv6_maddr_join(maddr); |
| |
| net_if_mcast_monitor(iface, addr, true); |
| |
| net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_JOIN, iface); |
| |
| return ret; |
| } |
| |
| int net_ipv6_mld_leave(struct net_if *iface, const struct in6_addr *addr) |
| { |
| int ret; |
| |
| if (!net_if_ipv6_maddr_rm(iface, addr)) { |
| return -EINVAL; |
| } |
| |
| ret = send_mldv2(iface, addr, NET_IPV6_MLDv2_MODE_IS_INCLUDE); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| net_if_mcast_monitor(iface, addr, false); |
| |
| net_mgmt_event_notify(NET_EVENT_IPV6_MCAST_LEAVE, iface); |
| |
| return ret; |
| } |
| |
| static void send_mld_report(struct net_if *iface) |
| { |
| struct net_pkt *pkt; |
| int i, count = 0; |
| |
| pkt = net_pkt_get_reserve_tx(net_if_get_ll_reserve(iface, NULL), |
| K_FOREVER); |
| |
| net_pkt_append_u8(pkt, 0); /* This will be the record count */ |
| |
| for (i = 0; i < NET_IF_MAX_IPV6_MADDR; i++) { |
| if (!iface->ipv6.mcast[i].is_used || |
| !iface->ipv6.mcast[i].is_joined) { |
| continue; |
| } |
| |
| pkt = create_mldv2(pkt, &iface->ipv6.mcast[i].address.in6_addr, |
| NET_IPV6_MLDv2_MODE_IS_EXCLUDE, 0); |
| count++; |
| } |
| |
| if (count > 0) { |
| u16_t pos; |
| |
| /* Write back the record count */ |
| net_pkt_write_u8(pkt, pkt->frags, 0, &pos, count); |
| |
| send_mldv2_raw(iface, pkt->frags); |
| |
| pkt->frags = NULL; |
| } |
| |
| net_pkt_unref(pkt); |
| } |
| |
| static enum net_verdict handle_mld_query(struct net_pkt *pkt) |
| { |
| u16_t total_len = net_pkt_get_len(pkt); |
| struct in6_addr mcast; |
| u16_t max_rsp_code, num_src, pkt_len; |
| u16_t offset, pos; |
| struct net_buf *frag; |
| |
| dbg_addr_recv("Multicast Listener Query", |
| &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst); |
| |
| net_stats_update_ipv6_mld_recv(); |
| |
| /* offset tells now where the ICMPv6 header is starting */ |
| frag = net_frag_get_pos(pkt, |
| net_pkt_ip_hdr_len(pkt) + |
| net_pkt_ipv6_ext_len(pkt) + |
| sizeof(struct net_icmp_hdr), |
| &offset); |
| |
| frag = net_frag_read_be16(frag, offset, &pos, &max_rsp_code); |
| frag = net_frag_skip(frag, pos, &pos, 2); /* two reserved bytes */ |
| frag = net_frag_read(frag, pos, &pos, sizeof(mcast), mcast.s6_addr); |
| frag = net_frag_skip(frag, pos, &pos, 2); /* skip S, QRV & QQIC */ |
| frag = net_frag_read_be16(pkt->frags, pos, &pos, &num_src); |
| if (!frag && pos == 0xffff) { |
| goto drop; |
| } |
| |
| pkt_len = sizeof(struct net_ipv6_hdr) + net_pkt_ipv6_ext_len(pkt) + |
| sizeof(struct net_icmp_hdr) + (2 + 2 + 16 + 2 + 2) + |
| sizeof(struct in6_addr) * num_src; |
| |
| if ((total_len < pkt_len || pkt_len > NET_IPV6_MTU || |
| (NET_IPV6_HDR(pkt)->hop_limit != 1))) { |
| struct net_icmp_hdr hdr, *icmp_hdr; |
| |
| icmp_hdr = net_icmpv6_get_hdr(pkt, &hdr); |
| if (!icmp_hdr || icmp_hdr->code != 0) { |
| NET_DBG("Preliminary check failed %u/%u, code %u, " |
| "hop %u", total_len, pkt_len, |
| icmp_hdr->code, NET_IPV6_HDR(pkt)->hop_limit); |
| goto drop; |
| } |
| } |
| |
| /* Currently we only support a unspecified address query. */ |
| if (!net_ipv6_addr_cmp(&mcast, net_ipv6_unspecified_address())) { |
| NET_DBG("Only supporting unspecified address query (%s)", |
| net_sprint_ipv6_addr(&mcast)); |
| goto drop; |
| } |
| |
| send_mld_report(net_pkt_iface(pkt)); |
| |
| drop: |
| net_stats_update_ipv6_mld_drop(); |
| |
| return NET_DROP; |
| } |
| |
| static struct net_icmpv6_handler mld_query_input_handler = { |
| .type = NET_ICMPV6_MLD_QUERY, |
| .code = 0, |
| .handler = handle_mld_query, |
| }; |
| #endif /* CONFIG_NET_IPV6_MLD */ |
| |
| #if defined(CONFIG_NET_IPV6_NBR_CACHE) |
| static struct net_icmpv6_handler ns_input_handler = { |
| .type = NET_ICMPV6_NS, |
| .code = 0, |
| .handler = handle_ns_input, |
| }; |
| |
| static struct net_icmpv6_handler na_input_handler = { |
| .type = NET_ICMPV6_NA, |
| .code = 0, |
| .handler = handle_na_input, |
| }; |
| #endif /* CONFIG_NET_IPV6_NBR_CACHE */ |
| |
| #if defined(CONFIG_NET_IPV6_ND) |
| static struct net_icmpv6_handler ra_input_handler = { |
| .type = NET_ICMPV6_RA, |
| .code = 0, |
| .handler = handle_ra_input, |
| }; |
| #endif /* CONFIG_NET_IPV6_ND */ |
| |
| #if defined(CONFIG_NET_IPV6_FRAGMENT) |
| #if defined(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT) |
| #define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(CONFIG_NET_IPV6_FRAGMENT_TIMEOUT) |
| #else |
| #define IPV6_REASSEMBLY_TIMEOUT K_SECONDS(5) |
| #endif /* CONFIG_NET_IPV6_FRAGMENT_TIMEOUT */ |
| |
| #define FRAG_BUF_WAIT 10 /* how long to max wait for a buffer */ |
| |
| static void reassembly_timeout(struct k_work *work); |
| static bool reassembly_init_done; |
| |
| static struct net_ipv6_reassembly |
| reassembly[CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT]; |
| |
| static struct net_ipv6_reassembly *reassembly_get(u32_t id, |
| struct in6_addr *src, |
| struct in6_addr *dst) |
| { |
| int i, avail = -1; |
| |
| for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { |
| |
| if (k_delayed_work_remaining_get(&reassembly[i].timer) && |
| reassembly[i].id == id && |
| net_ipv6_addr_cmp(src, &reassembly[i].src) && |
| net_ipv6_addr_cmp(dst, &reassembly[i].dst)) { |
| return &reassembly[i]; |
| } |
| |
| if (k_delayed_work_remaining_get(&reassembly[i].timer)) { |
| continue; |
| } |
| |
| if (avail < 0) { |
| avail = i; |
| } |
| } |
| |
| if (avail < 0) { |
| return NULL; |
| } |
| |
| k_delayed_work_submit(&reassembly[avail].timer, |
| IPV6_REASSEMBLY_TIMEOUT); |
| |
| net_ipaddr_copy(&reassembly[avail].src, src); |
| net_ipaddr_copy(&reassembly[avail].dst, dst); |
| |
| reassembly[avail].id = id; |
| |
| return &reassembly[avail]; |
| } |
| |
| static bool reassembly_cancel(u32_t id, |
| struct in6_addr *src, |
| struct in6_addr *dst) |
| { |
| int i, j; |
| |
| NET_DBG("Cancel 0x%x", id); |
| |
| for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { |
| s32_t remaining; |
| |
| if (reassembly[i].id != id || |
| !net_ipv6_addr_cmp(src, &reassembly[i].src) || |
| !net_ipv6_addr_cmp(dst, &reassembly[i].dst)) { |
| continue; |
| } |
| |
| remaining = k_delayed_work_remaining_get(&reassembly[i].timer); |
| if (remaining) { |
| k_delayed_work_cancel(&reassembly[i].timer); |
| } |
| |
| NET_DBG("IPv6 reassembly id 0x%x remaining %d ms", |
| reassembly[i].id, remaining); |
| |
| reassembly[i].id = 0; |
| |
| for (j = 0; j < NET_IPV6_FRAGMENTS_MAX_PKT; j++) { |
| if (!reassembly[i].pkt[j]) { |
| continue; |
| } |
| |
| NET_DBG("[%d] IPv6 reassembly pkt %p %zd bytes data", |
| j, reassembly[i].pkt[j], |
| net_pkt_get_len(reassembly[i].pkt[j])); |
| |
| net_pkt_unref(reassembly[i].pkt[j]); |
| reassembly[i].pkt[j] = NULL; |
| } |
| |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void reassembly_info(char *str, struct net_ipv6_reassembly *reass) |
| { |
| char out[NET_IPV6_ADDR_LEN]; |
| int i, len; |
| |
| snprintk(out, sizeof(out), "%s", net_sprint_ipv6_addr(&reass->dst)); |
| |
| for (i = 0, len = 0; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { |
| len += net_pkt_get_len(reass->pkt[i]); |
| } |
| |
| NET_DBG("%s id 0x%x src %s dst %s remain %d ms len %d", |
| str, reass->id, net_sprint_ipv6_addr(&reass->src), out, |
| k_delayed_work_remaining_get(&reass->timer), len); |
| } |
| |
| static void reassembly_timeout(struct k_work *work) |
| { |
| struct net_ipv6_reassembly *reass = |
| CONTAINER_OF(work, struct net_ipv6_reassembly, timer); |
| |
| reassembly_info("Reassembly cancelled", reass); |
| |
| reassembly_cancel(reass->id, &reass->src, &reass->dst); |
| } |
| |
| static void reassemble_packet(struct net_ipv6_reassembly *reass) |
| { |
| struct net_pkt *pkt; |
| struct net_buf *last; |
| u8_t next_hdr; |
| int i, len, ret; |
| u16_t pos; |
| |
| k_delayed_work_cancel(&reass->timer); |
| |
| NET_ASSERT(reass->pkt[0]); |
| |
| last = net_buf_frag_last(reass->pkt[0]->frags); |
| |
| /* We start from 2nd packet which is then appended to |
| * the first one. |
| */ |
| for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { |
| int removed_len; |
| |
| pkt = reass->pkt[i]; |
| |
| /* Get rid of IPv6 and fragment header which are at |
| * the beginning of the fragment. |
| */ |
| removed_len = net_pkt_ipv6_fragment_start(pkt) + |
| sizeof(struct net_ipv6_frag_hdr) - |
| pkt->frags->data; |
| |
| NET_DBG("Removing %d bytes from start of pkt %p", |
| removed_len, pkt->frags); |
| |
| NET_ASSERT(removed_len >= (sizeof(struct net_ipv6_hdr) + |
| sizeof(struct net_ipv6_frag_hdr))); |
| |
| net_buf_pull(pkt->frags, removed_len); |
| |
| /* Attach the data to previous pkt */ |
| last->frags = pkt->frags; |
| last = net_buf_frag_last(pkt->frags); |
| |
| pkt->frags = NULL; |
| reass->pkt[i] = NULL; |
| |
| net_pkt_unref(pkt); |
| } |
| |
| pkt = reass->pkt[0]; |
| reass->pkt[0] = NULL; |
| |
| /* Next we need to strip away the fragment header from the first packet |
| * and set the various pointers and values in packet. |
| */ |
| |
| next_hdr = net_pkt_ipv6_fragment_start(pkt)[0]; |
| |
| /* How much data we need to move in order to get rid of the |
| * fragmentation header. |
| */ |
| len = pkt->frags->len - sizeof(struct net_ipv6_frag_hdr) - |
| (net_pkt_ipv6_fragment_start(pkt) - pkt->frags->data); |
| |
| memmove(net_pkt_ipv6_fragment_start(pkt), |
| net_pkt_ipv6_fragment_start(pkt) + |
| sizeof(struct net_ipv6_frag_hdr), len); |
| |
| /* This one updates the previous header's nexthdr value */ |
| net_pkt_write_u8(pkt, pkt->frags, net_pkt_ipv6_hdr_prev(pkt), |
| &pos, next_hdr); |
| |
| pkt->frags->len -= sizeof(struct net_ipv6_frag_hdr); |
| |
| if (!net_pkt_compact(pkt)) { |
| NET_ERR("Cannot compact reassembly packet %p", pkt); |
| net_pkt_unref(pkt); |
| return; |
| } |
| |
| /* Fix the total length of the IPv6 packet. */ |
| len = net_pkt_ipv6_ext_len(pkt); |
| if (len > 0) { |
| NET_DBG("Old pkt %p IPv6 ext len is %d bytes", pkt, len); |
| net_pkt_set_ipv6_ext_len(pkt, |
| len - sizeof(struct net_ipv6_frag_hdr)); |
| } |
| |
| len = net_pkt_get_len(pkt) - sizeof(struct net_ipv6_hdr); |
| |
| NET_IPV6_HDR(pkt)->len[0] = len / 256; |
| NET_IPV6_HDR(pkt)->len[1] = len - NET_IPV6_HDR(pkt)->len[0] * 256; |
| |
| NET_DBG("New pkt %p IPv6 len is %d bytes", pkt, len); |
| |
| /* We need to use the queue when feeding the packet back into the |
| * IP stack as we might run out of stack if we call processing_data() |
| * directly. As the packet does not contain link layer header, we |
| * MUST NOT pass it to L2 so there will be a special check for that |
| * in process_data() when handling the packet. |
| */ |
| ret = net_recv_data(net_pkt_iface(pkt), pkt); |
| if (ret < 0) { |
| net_pkt_unref(pkt); |
| } |
| } |
| |
| void net_ipv6_frag_foreach(net_ipv6_frag_cb_t cb, void *user_data) |
| { |
| int i; |
| |
| for (i = 0; reassembly_init_done && |
| i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { |
| if (!k_delayed_work_remaining_get(&reassembly[i].timer)) { |
| continue; |
| } |
| |
| cb(&reassembly[i], user_data); |
| } |
| } |
| |
| /* Verify that we have all the fragments received and in correct order. |
| */ |
| static bool fragment_verify(struct net_ipv6_reassembly *reass) |
| { |
| u16_t offset; |
| int i, prev_len; |
| |
| prev_len = net_pkt_get_len(reass->pkt[0]); |
| offset = net_pkt_ipv6_fragment_offset(reass->pkt[0]); |
| |
| NET_DBG("pkt %p offset %u", reass->pkt[0], offset); |
| |
| if (offset != 0) { |
| return false; |
| } |
| |
| for (i = 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { |
| offset = net_pkt_ipv6_fragment_offset(reass->pkt[i]); |
| |
| NET_DBG("pkt %p offset %u prev_len %d", reass->pkt[i], |
| offset, prev_len); |
| |
| if (prev_len < offset) { |
| /* Something wrong with the offset value */ |
| return false; |
| } |
| |
| prev_len = net_pkt_get_len(reass->pkt[i]); |
| } |
| |
| return true; |
| } |
| |
| static int shift_packets(struct net_ipv6_reassembly *reass, int pos) |
| { |
| int i; |
| |
| for (i = pos + 1; i < NET_IPV6_FRAGMENTS_MAX_PKT; i++) { |
| if (!reass->pkt[i]) { |
| NET_DBG("Moving [%d] %p (offset 0x%x) to [%d]", |
| pos, reass->pkt[pos], |
| net_pkt_ipv6_fragment_offset(reass->pkt[pos]), |
| i); |
| |
| /* Do we have enough space in packet array to make |
| * the move? |
| */ |
| if (((i - pos) + 1) > |
| (NET_IPV6_FRAGMENTS_MAX_PKT - i)) { |
| return -ENOMEM; |
| } |
| |
| memmove(&reass->pkt[i], &reass->pkt[pos], |
| sizeof(void *) * (i - pos)); |
| |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static enum net_verdict handle_fragment_hdr(struct net_pkt *pkt, |
| struct net_buf *frag, |
| int total_len, |
| u16_t buf_offset) |
| { |
| struct net_ipv6_reassembly *reass = NULL; |
| u32_t id; |
| u16_t loc; |
| u16_t offset; |
| u16_t flag; |
| u8_t nexthdr; |
| u8_t more; |
| bool found; |
| int i; |
| |
| if (!reassembly_init_done) { |
| /* Static initializing does not work here because of the array |
| * so we must do it at runtime. |
| */ |
| for (i = 0; i < CONFIG_NET_IPV6_FRAGMENT_MAX_COUNT; i++) { |
| k_delayed_work_init(&reassembly[i].timer, |
| reassembly_timeout); |
| } |
| |
| reassembly_init_done = true; |
| } |
| |
| net_pkt_set_ipv6_fragment_start(pkt, frag->data + buf_offset); |
| |
| /* Each fragment has a fragment header. */ |
| frag = net_frag_read_u8(frag, buf_offset, &loc, &nexthdr); |
| frag = net_frag_skip(frag, loc, &loc, 1); /* reserved */ |
| frag = net_frag_read_be16(frag, loc, &loc, &flag); |
| frag = net_frag_read_be32(frag, loc, &loc, &id); |
| if (!frag && loc == 0xffff) { |
| goto drop; |
| } |
| |
| reass = reassembly_get(id, &NET_IPV6_HDR(pkt)->src, |
| &NET_IPV6_HDR(pkt)->dst); |
| if (!reass) { |
| NET_DBG("Cannot get reassembly slot, dropping pkt %p", pkt); |
| goto drop; |
| |