bool rpl_data_remember_outer(buffer_t *buf) { /* We're stripping the IP header - need the HBH header for future reference */ uint8_t *hbh; rpl_data_locate_info(buf, &hbh, NULL); if (hbh) { uint8_t instance_id = hbh[3]; /* For local instances, also need to extract the DODAG ID from src/dst */ bool local = rpl_instance_id_is_local(instance_id); /* Copy the length byte and the option data (and optionally DODAG ID) */ buf->rpl_option = ns_dyn_mem_temporary_alloc(hbh[1] + 1 + (local ? 16 : 0)); if (buf->rpl_option) { memcpy(buf->rpl_option, hbh + 1, hbh[1] + 1); if (local) { uint8_t *dodagid = instance_id & RPL_INSTANCE_DEST ? buf->dst_sa.address : buf->src_sa.address; memcpy(buf->rpl_option + hbh[1] + 1, dodagid, 16); } } } if ((buf->options.ip_extflags & IPEXT_HBH_RPL) && !buf->rpl_option) { tr_warn("RPL tunnel exit HbH fail"); return false; } return true; }
uint8_t rpl_instance_list_read(uint8_t *buffer_ptr, uint8_t buffer_size) { uint8_t ret_val = 0; struct rpl_instance *instance = NULL; if (!protocol_6lowpan_rpl_domain) { return 0; } while (buffer_size) { instance = rpl_control_enumerate_instances(protocol_6lowpan_rpl_domain, instance); if (!instance) { break; } rpl_dodag_info_t dodag_info; if (rpl_control_read_dodag_info(instance, &dodag_info)) { /* First the instance ID */ *buffer_ptr++ = dodag_info.instance_id; buffer_size--; /* Then, if local, the DODAG ID */ if (rpl_instance_id_is_local(dodag_info.instance_id)) { if (buffer_size < 16) { break; } memcpy(buffer_ptr, dodag_info.dodag_id, 16); buffer_ptr += 16; buffer_size -= 16; } ret_val++; } } return ret_val; }
/* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Option Type | Opt Data Len | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |O|R|F|0|0|0|0|0| RPLInstanceID | SenderRank | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | (sub-TLVs) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Figure 1: RPL Option */ static buffer_t *rpl_data_exthdr_provider_hbh_2(buffer_t *buf, rpl_instance_t *instance, rpl_neighbour_t *neighbour, ipv6_exthdr_stage_t stage, int16_t *result) { ipv6_route_info_t *route_info = &buf->route->route_info; /* This can be called both for routes which only use HbH headers (eg DIO) * as well as one-hop DAO_SR routes which would normally use source routing * headers, if there was more than one hop. For DAO_SR, neighbour will be * NULL. */ rpl_dodag_t *dodag = rpl_instance_current_dodag(instance); if (!dodag) { *result = -1; return buf; } bool destination_in_instance = false; uint16_t ext_size = 0; if (addr_ipv6_equal(route_info->next_hop_addr, buf->dst_sa.address) || addr_ipv6_equal(buf->dst_sa.address, dodag->id)) { destination_in_instance = true; if (buf->rpl_option) { /* Forwarding an existing option - preserve it */ uint8_t opt_size = buf->rpl_option[0]; ext_size = 2 + opt_size; ext_size = (ext_size + 7) &~ 7; } else { /* Generating our own option - fixed size, no TLVs */ ext_size = 8; } } switch (stage) { case IPV6_EXTHDR_SIZE: *result = ext_size; return buf; case IPV6_EXTHDR_INSERT: { if (!destination_in_instance) { /* We don't add a header - we'll do it on the tunnel */ *result = 0; return buf; } buf = buffer_headroom(buf, ext_size); if (!buf) { return NULL; } uint8_t *ext = buffer_data_reserve_header(buf, ext_size); ext[0] = buf->options.type; buf->options.type = IPV6_NH_HOP_BY_HOP; ext[1] = ext_size / 8 - 1; uint8_t *opt = ext + 2; opt[0] = IPV6_OPTION_RPL; if (buf->rpl_option) { /* Get back the RPL option we stripped off an outer IP header */ memcpy(opt + 1, buf->rpl_option, 1 + buf->rpl_option[0]); ns_dyn_mem_free(buf->rpl_option); buf->rpl_option = NULL; } else { opt[1] = 4; // option length opt[2] = 0; // placeholder opt[3] = instance->id; /* For upwards routes we can deduce that DODAGID must be * the destination, so set the D flag. */ if (rpl_instance_id_is_local(instance->id) && !rpl_data_is_rpl_downward_route(route_info->source)) { opt[3] |= RPL_INSTANCE_DEST; } common_write_16_bit(RPL_RANK_INFINITE, opt + 4); // SenderRank (placeholder) } /* Pad HbH header if necessary. */ uint8_t pad_len = ext + ext_size - (opt + 2 + opt[1]); if (pad_len == 1) { opt[0] = IPV6_OPTION_PAD1; } else if (pad_len > 1) { opt[0] = IPV6_OPTION_PADN; opt[1] = pad_len - 2; memset(opt + 2, 0, pad_len - 2); } // don't forget to set the "RPL option present" marker buf->options.ip_extflags |= IPEXT_HBH_RPL; *result = 0; return buf; } case IPV6_EXTHDR_MODIFY: { uint8_t *opt; uint16_t sender_rank; rpl_data_locate_info(buf, &opt, NULL); if (!opt) { *result = IPV6_EXTHDR_MODIFY_TUNNEL; // Tunnel to next hop in general case, but if going to DODAGID, // it can tunnel all the way (and it HAS to if it is a local // DODAG). if (!addr_ipv6_equal(buf->dst_sa.address, dodag->id)) { memcpy(buf->dst_sa.address, route_info->next_hop_addr, 16); } buf->src_sa.addr_type = ADDR_NONE; // force auto-selection return buf; } if (buf->ip_routed_up) { /* Check for rank errors - RFC 6550 11.2.2.2. */ /* Note that RPL spec does not say that packets from nodes of * equal rank are errors, but we treat them as such to get * reliable sibling loop detection - we require sender rank to be * strictly less for Down packets and strictly greater for Up. */ sender_rank = common_read_16_bit(opt + 4); rpl_cmp_t cmp = rpl_rank_compare_dagrank_rank(dodag, sender_rank, instance->current_rank); rpl_cmp_t expected_cmp = (opt[2] & RPL_OPT_DOWN) ? RPL_CMP_LESS : RPL_CMP_GREATER; if (cmp != expected_cmp) { /* Set the Rank-Error bit; if already set, drop */ if (opt[2] & RPL_OPT_RANK_ERROR) { protocol_stats_update(STATS_RPL_ROUTELOOP, 1); tr_info("Forwarding inconsistency R"); rpl_instance_inconsistency(instance); *result = -1; return buf; } else { opt[2] |= RPL_OPT_RANK_ERROR; } } } if (buf->rpl_flag_error & RPL_OPT_FWD_ERROR) { opt[2] |= RPL_OPT_FWD_ERROR; } else if (rpl_data_is_rpl_downward_route(route_info->source)) { opt[2] |= RPL_OPT_DOWN; } else { opt[2] &= ~RPL_OPT_DOWN; } /* Set the D flag for local instances */ if (rpl_instance_id_is_local(instance->id)) { if (addr_ipv6_equal(dodag->id, buf->dst_sa.address)) { opt[3] |= RPL_INSTANCE_DEST; } else if (addr_ipv6_equal(dodag->id, buf->src_sa.address)) { opt[3] &=~ RPL_INSTANCE_DEST; } else { tr_error("Local instance invalid %s[%d]: %s -> %s", trace_ipv6(dodag->id), instance->id, trace_ipv6(buf->src_sa.address), trace_ipv6(buf->dst_sa.address)); *result = -1; return buf; } } /* RPL 11.2.2.2. says we set SenderRank to infinite when forwarding * across a version discontinuity. (Must be up - we don't know versions * of downward routes). */ if ((buf->rpl_flag_error & RPL_OPT_FWD_ERROR) || rpl_data_is_rpl_downward_route(route_info->source) || !neighbour || neighbour->dodag_version == instance->current_dodag_version) { sender_rank = nrpl_dag_rank(dodag, instance->current_rank); } else { sender_rank = RPL_RANK_INFINITE; } common_write_16_bit(sender_rank, opt + 4); *result = 0; return buf; } default: return buffer_free(buf); } }