/* * Copyright (c) 2014-2017, Arm Limited and affiliates. * SPDX-License-Identifier: Apache-2.0 * * Licensed 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. */ #include "nsconfig.h" #include "ns_list.h" #include "common_functions.h" #include "ns_trace.h" #include "nsdynmemLIB.h" #include #include "NWK_INTERFACE/Include/protocol.h" #include "Common_Protocols/ipv6_constants.h" #include "6LoWPAN/IPHC_Decode/cipv6.h" #define TRACE_GROUP "iphc" typedef struct iphc_compress_state { const lowpan_context_list_t * const context_list; const uint8_t *in; uint8_t *out; uint16_t len; uint16_t out_space; uint16_t consumed; uint16_t produced; const uint8_t *outer_src_iid; const uint8_t *outer_dst_iid; const bool stable_only; } iphc_compress_state_t; static bool compress_nh(uint8_t nh, iphc_compress_state_t *restrict cs); static inline bool context_ok_for_compression(const lowpan_context_t *ctx, bool stable_only) { return ctx->lifetime && ctx->compression && (!stable_only || ctx->stable); } /* Using a specified context, what's the best possible compression of addr? */ static uint_fast8_t addr_bytes_needed(const uint8_t *addr, const uint8_t *outer_iid, const uint8_t *ctx_prefix, uint_fast8_t ctx_len) { /* Quick test: context always gets priority, so all of context must match */ /* This handles intrusions into IID space, so don't have to recheck this below */ if (!bitsequal(addr, ctx_prefix, ctx_len)) { return 16; } /* Okay, potential match, but getting next bit optimal is more complex. */ /* Try each of 3 possible cases in turn */ uint8_t template[16]; /* 0-bit case: 64 bits of IID from outer header */ /* Template contains 0s, then outer IID, then context on top */ if (ctx_len < 64) { memset(template, 0, 8); } if (ctx_len < 128) { memcpy(template + 8, outer_iid, 8); } bitcopy(template, ctx_prefix, ctx_len); if (addr_ipv6_equal(template, addr)) { return 0; } /* 16-bit case: need to match top 14 bytes. Respell top 6 bytes of IID */ /* from last test to contain 0000:00ff:fe00, unless they're fully */ /* covered by context, in which case template already hold the correct */ /* context bytes */ if (ctx_len < 112) { memcpy(template + 8, ADDR_SHORT_ADR_SUFFIC, 6); if (ctx_len > 64) { bitcopy(template + 8, ctx_prefix + 8, ctx_len - 64); } } if (memcmp(template, addr, 14) == 0) { return 2; } /* 64-bit case: only need to match top 8 bytes */ if (memcmp(template, addr, 8) == 0) { return 8; } /* Had a matching context, but didn't match enough. Example: * Context = 2002:db4::/32, Addr = 2002:db4:1::1 * Matches all 32 bits of context length, but not enough to compress 64-bit */ return 16; } static uint8_t compress_mc_addr(const lowpan_context_list_t *context_list, const uint8_t *addr, uint8_t *cmp_addr_out, uint8_t *context, uint8_t *mode, bool stable_only) { *mode |= HC_MULTICAST_COMP; if (memcmp(addr, ADDR_LINK_LOCAL_ALL_NODES, 15) == 0) { // ff02:0000:0000:0000:0000:0000:0000:00XX cmp_addr_out[0] = addr[15]; *mode |= HC_8BIT_MULTICAST; return 1; } if (memcmp(addr + 2, ADDR_LINK_LOCAL_ALL_NODES + 2, 11) == 0) { // ffXX:0000:0000:0000:0000:0000:00XX:XXXX cmp_addr_out[0] = addr[1]; cmp_addr_out[1] = addr[13]; cmp_addr_out[2] = addr[14]; cmp_addr_out[3] = addr[15]; *mode |= HC_32BIT_MULTICAST; return 4; } if (memcmp(addr + 2, ADDR_LINK_LOCAL_ALL_NODES + 2, 9) == 0) { // ffXX:0000:0000:0000:0000:00XX:XXXX:XXXX cmp_addr_out[0] = addr[1]; memcpy(cmp_addr_out + 1, addr + 11, 5); *mode |= HC_48BIT_MULTICAST; return 6; } /* Looking for addresses of the form ffxx:xxLL:PPPP:PPPP:PPPP:PPPP:xxxx:xxxx */ /* Context info is padded with zeros, so the 8-byte memcmp is okay for short contexts. */ /* RFCs are ambiguous about how this should work if prefix length is > 64 bits, */ /* so don't attempt compression against long contexts. */ ns_list_foreach(lowpan_context_t, ctx, context_list) { if (context_ok_for_compression(ctx, stable_only) && ctx->length <= 64 && addr[3] == ctx->length && memcmp(addr + 4, ctx->prefix, 8) == 0) { cmp_addr_out[0] = addr[1]; cmp_addr_out[1] = addr[2]; memcpy(cmp_addr_out + 2, addr + 12, 4); *context |= ctx->cid; *mode |= HC_48BIT_CONTEXT_MULTICAST; return 6; } } memcpy(cmp_addr_out, addr, 16); *mode |= HC_128BIT_MULTICAST; return 16; } static uint8_t compress_addr(const lowpan_context_list_t *context_list, const uint8_t *addr, bool is_dst, const uint8_t *outer_iid, uint8_t *cmp_addr_out, uint8_t *context, uint8_t *mode, bool stable_only) { if (is_dst && addr[0] == 0xff) { return compress_mc_addr(context_list, addr, cmp_addr_out, context, mode, stable_only); } uint_fast8_t best_bytes = addr_bytes_needed(addr, outer_iid, ADDR_LINK_LOCAL_PREFIX, 64); lowpan_context_t *best_ctx = NULL; bool checked_ctx0 = false; if (best_bytes > 0) { ns_list_foreach(lowpan_context_t, ctx, context_list) { if (!context_ok_for_compression(ctx, stable_only)) { continue; } uint_fast8_t bytes = addr_bytes_needed(addr, outer_iid, ctx->prefix, ctx->length); if (ctx->cid == 0) { checked_ctx0 = true; } /* This context is better if: * a) it means fewer inline bytes, or * b) it needs the same inline bytes and might avoid a CID extension */ if (bytes < best_bytes || (bytes == best_bytes && ctx->cid == 0 && best_ctx && best_ctx->cid != 0)) { best_ctx = ctx; best_bytes = bytes; /* Don't need to check further if we've reached 0 bytes, and we've considered context 0 */ if (best_bytes == 0 && checked_ctx0) { break; } } } } /* If not found a 0-byte match, one more (unlikely) possibility for source - special case for "unspecified" */ if (best_bytes > 0 && !is_dst && addr_is_ipv6_unspecified(addr)) { *mode |= HC_SRCADR_COMP | HC_SRC_ADR_128_BIT; return 0; } uint8_t mode_bits; uint8_t context_bits; if (best_ctx) { context_bits = best_ctx->cid; mode_bits = HC_DSTADR_COMP; } else { context_bits = 0; mode_bits = 0; } switch (best_bytes) { default: mode_bits |= HC_DST_ADR_128_BIT; break; case 8: mode_bits |= HC_DST_ADR_64_BIT; break; case 2: mode_bits |= HC_DST_ADR_16_BIT; break; case 0: mode_bits |= HC_DST_ADR_FROM_MAC; break; } /* Convert from IPHC DST bits to SRC bits */ if (!is_dst) { mode_bits <<= 4; context_bits <<= 4; } *mode |= mode_bits; *context |= context_bits; memcpy(cmp_addr_out, addr + (16 - best_bytes), best_bytes); return best_bytes; } static bool compress_udp(iphc_compress_state_t *restrict cs) { if (cs->len < 8) { return false; } /* UDP length should be set to payload length - if it's not, we can't compress it */ uint16_t udp_len = common_read_16_bit(cs->in + 4); if (udp_len != cs->len) { return false; } uint16_t src_port = common_read_16_bit(cs->in + 0); uint16_t dst_port = common_read_16_bit(cs->in + 2); uint8_t *ptr = cs->out; if ((src_port & 0xfff0) == 0xf0b0 && (dst_port & 0xfff0) == 0xf0b0) { if (cs->out_space < 4) { return false; } *ptr++ = NHC_UDP | NHC_UDP_PORT_COMPRESS_BOTH; *ptr++ = (src_port & 0xF) << 4 | (dst_port & 0xF); } else if ((src_port & 0xff00) == 0xf000) { if (cs->out_space < 7) { return false; } *ptr++ = NHC_UDP | NHC_UDP_PORT_COMPRESS_SRC; *ptr++ = src_port & 0xff; ptr = common_write_16_bit(dst_port, ptr); } else if ((dst_port & 0xff00) == 0xf000) { if (cs->out_space < 7) { return false; } *ptr++ = NHC_UDP | NHC_UDP_PORT_COMPRESS_DST; ptr = common_write_16_bit(src_port, ptr); *ptr++ = dst_port & 0xff; } else { if (cs->out_space < 8) { return false; } *ptr++ = NHC_UDP | NHC_UDP_PORT_COMPRESS_NONE; ptr = common_write_16_bit(src_port, ptr); ptr = common_write_16_bit(dst_port, ptr); } /* Checksum */ *ptr++ = cs->in[6]; *ptr++ = cs->in[7]; uint_fast8_t outlen = ptr - cs->out; cs->consumed += 8; cs->in += 8; cs->len -= 8; cs->out_space -= outlen; cs->out += outlen; cs->produced += outlen; return true; } static bool compress_exthdr(uint8_t nhc, iphc_compress_state_t *restrict cs) { if (cs->len < 8) { return false; } const uint8_t *in = cs->in; uint8_t nh = in[0]; uint16_t hdrlen; if (nhc == NHC_EXT_FRAG) { /* For fragment header, seems sensible to only handle first fragment; * "Next header" field doesn't describe following data otherwise. * Could still "compress" this header in all cases, but no point if we don't * have following headers to compress - it takes 8 bytes either way. */ uint16_t offset = common_read_16_bit(in + 2) & 0xFFF8; if (offset != 0) { return false; } /* Second byte is reserved; it's not a length byte */ hdrlen = 8; } else { hdrlen = (in[1] + 1) * 8; } if (hdrlen > cs->len || hdrlen >= 256) { return false; } /* If Options are well-formed, parse and remove final option if it's padding */ uint8_t hc_bytes = hdrlen; if (nhc == NHC_EXT_DEST_OPT || nhc == NHC_EXT_HOP_BY_HOP) { const uint8_t *opt = in + 2, *lastopt = opt; uint8_t optrem = hdrlen - 2; while (optrem) { lastopt = opt; if (opt[0] == IPV6_OPTION_PAD1) { opt++; optrem--; continue; } if (optrem < 2 || optrem < 2 + opt[1]) { lastopt = NULL; break; } optrem -= 2 + opt[1]; opt += 2 + opt[1]; } if (lastopt && (lastopt[0] == IPV6_OPTION_PAD1 || lastopt[0] == IPV6_OPTION_PADN)) { hc_bytes = lastopt - in; } } /* Include one for "next header" - we will either need this ourselves, or * the next hdr will*/ if (cs->out_space < hc_bytes + 1) { return false; } uint8_t *out = cs->out; cs->in += hdrlen; cs->len -= hdrlen; cs->consumed += hdrlen; cs->out += hc_bytes; cs->out_space -= hc_bytes; cs->produced += hc_bytes; out[0] = nhc; if (compress_nh(nh, cs)) { out[0] |= NHC_EXT_NH; } uint8_t *ptr = out + 1; if (!(out[0] & NHC_EXT_NH)) { *ptr++ = nh; cs->out++; cs->out_space--; cs->produced++; } if (nhc == NHC_EXT_FRAG) { *ptr++ = in[1]; } else { *ptr++ = hc_bytes - 2; } memcpy(ptr, in + 2, hc_bytes - 2); return true; } static bool compress_ipv6(iphc_compress_state_t *restrict cs, bool from_nhc) { const uint8_t *const in = cs->in; uint8_t *const out = cs->out; /* Need at least 2 plus 1 for next header field or following NHC byte */ /* plus another 1 if coming from NHC (for NHC_EXT_IPV6 byte) */ if (cs->out_space < from_nhc + 3) { return false; } if (cs->len < 40 || (in[0] & 0xF0) != 0x60) { tr_debug("Not IPv6"); return false; } uint8_t iphc[2] = { LOWPAN_DISPATCH_IPHC, 0 }; uint_fast8_t iphc_bytes = from_nhc + 2; /* Payload length field must match, or we can't compress */ if (common_read_16_bit(in + 4) != cs->len - 40) { return false; } /* Compress addresses, get context byte */ uint8_t cid = 0; uint8_t cmp_src[16], cmp_dst[16], cmp_src_len, cmp_dst_len; cmp_src_len = compress_addr(cs->context_list, in + 8, false, cs->outer_src_iid, cmp_src, &cid, &iphc[1], cs->stable_only); cmp_dst_len = compress_addr(cs->context_list, in + 24, true, cs->outer_dst_iid, cmp_dst, &cid, &iphc[1], cs->stable_only); iphc_bytes += cmp_src_len + cmp_dst_len; if (cid != 0) { iphc_bytes += 1; iphc[1] |= HC_CIDE_COMP; } /* Compress Hop Limit */ switch (cs->in[7]) { case 255: iphc[0] |= HC_HOP_LIMIT_255; break; case 64: iphc[0] |= HC_HOP_LIMIT_64; break; case 1: iphc[0] |= HC_HOP_LIMIT_1; break; default: iphc[0] |= HC_HOP_LIMIT_CARRIED_IN_LINE; iphc_bytes += 1; break; } uint8_t ecn = (in[1] & 0x30) << 2; uint8_t dscp = (in[0] & 0x0F) << 2 | (in[1] & 0xC0) >> 6; uint_fast24_t flow = common_read_24_bit(in + 1) & 0xFFFFF; if (flow == 0 && ecn == 0 && dscp == 0) { iphc[0] |= HC_TF_ELIDED; } else if (flow == 0) { iphc[0] |= HC_TF_ECN_DSCP; iphc_bytes += 1; } else if (dscp == 0) { iphc[0] |= HC_TF_ECN_FLOW_LABEL; flow |= (uint_fast24_t) ecn << 16; iphc_bytes += 3; } else { iphc[0] |= HC_TF_ECN_DSCP_FLOW_LABEL; iphc_bytes += 4; } /* Include one for "next header" - we will either need this ourselves, or * the next H will*/ if (cs->out_space < iphc_bytes + 1) { return false; } /* Update state for next header */ cs->in += 40; cs->len -= 40; cs->consumed += 40; cs->out += iphc_bytes; cs->out_space -= iphc_bytes; cs->produced += iphc_bytes; cs->outer_src_iid = in + 16; cs->outer_dst_iid = in + 32; /* Check if next header can be compressed - potentially recursing */ if (compress_nh(in[6], cs)) { iphc[0] |= HC_NEXT_HEADER_MASK; } else { /* We produce one more byte for Next Header */ iphc_bytes++; cs->out++; cs->out_space--; cs->produced++; } uint8_t *ptr = out; if (from_nhc) { *ptr++ = NHC_EXT_IPV6; } *ptr++ = iphc[0]; *ptr++ = iphc[1]; if (cid != 0) { *ptr++ = cid; } if ((iphc[0] & HC_TF_MASK) == HC_TF_ECN_DSCP_FLOW_LABEL || (iphc[0] & HC_TF_MASK) == HC_TF_ECN_DSCP) { *ptr++ = (ecn | dscp); } if ((iphc[0] & HC_TF_MASK) == HC_TF_ECN_DSCP_FLOW_LABEL || (iphc[0] & HC_TF_MASK) == HC_TF_ECN_FLOW_LABEL) { ptr = common_write_24_bit(flow, ptr); // "flow" includes ecn in 3-byte case } if (!(iphc[0] & HC_NEXT_HEADER_MASK)) { *ptr++ = in[6]; } if ((iphc[0] & HC_HOP_LIMIT_MASK) == HC_HOP_LIMIT_CARRIED_IN_LINE) { *ptr++ = in[7]; } ptr = (uint8_t *) memcpy(ptr, cmp_src, cmp_src_len) + cmp_src_len; ptr = (uint8_t *) memcpy(ptr, cmp_dst, cmp_dst_len) + cmp_dst_len; if (ptr != out + iphc_bytes) { tr_debug("iphc error"); } return true; } static bool compress_nh(uint8_t nh, iphc_compress_state_t *restrict cs) { switch (nh) { uint8_t nhc; case IPV6_NH_HOP_BY_HOP: nhc = NHC_EXT_HOP_BY_HOP; goto ext_hdr; case IPV6_NH_ROUTING: nhc = NHC_EXT_ROUTING; goto ext_hdr; case IPV6_NH_FRAGMENT: nhc = NHC_EXT_FRAG; goto ext_hdr; case IPV6_NH_DEST_OPT: nhc = NHC_EXT_DEST_OPT; goto ext_hdr; case IPV6_NH_MOBILITY: nhc = NHC_EXT_MOBILITY; ext_hdr: return compress_exthdr(nhc, cs); /* no break */ case IPV6_NH_IPV6: return compress_ipv6(cs, true); case IPV6_NH_UDP: return compress_udp(cs); default: return false; } } /* Input: An IPv6 frame, with outer layer 802.15.4 MAC (or IP) addresses in src+dst */ /* Output: 6LoWPAN frame - usually compressed. */ buffer_t *iphc_compress(const lowpan_context_list_t *context_list, buffer_t *buf, uint16_t hc_space, bool stable_only) { uint8_t *ptr = buffer_data_pointer(buf); uint16_t len = buffer_data_length(buf); uint8_t src_iid[8], dst_iid[8]; if (len < 40 || (ptr[0] & 0xF0) != 0x60) { tr_debug("Not IPv6"); return buffer_free(buf); } /* No point allocating excessively */ if (hc_space > len) { hc_space = len; } /* TODO: Could actually do it in-place with more care, working backwards * in each header. */ uint8_t *hc_out = ns_dyn_mem_temporary_alloc(hc_space); if (!hc_out) { tr_debug("No mem"); return buffer_free(buf); } if (!addr_iid_from_outer(src_iid, &buf->src_sa) || !addr_iid_from_outer(dst_iid, &buf->dst_sa)) { tr_debug("Bad outer addr"); ns_dyn_mem_free(hc_out); return buffer_free(buf); } iphc_compress_state_t cs = { .context_list = context_list, .in = ptr, .len = len, .out = hc_out, .out_space = hc_space, .consumed = 0, .produced = 0, .outer_src_iid = src_iid, .outer_dst_iid = dst_iid, .stable_only = stable_only }; if (!compress_ipv6(&cs, false) || cs.produced > cs.consumed) { /* Compression failed, or made it bigger somehow (impossible?) */ /* Fall back to uncompressed, which means adding a dispatch byte */ buf = buffer_headroom(buf, 1); if (buf) { ptr = buffer_data_reserve_header(buf, 1); *ptr = LOWPAN_DISPATCH_IPV6; } ns_dyn_mem_free(hc_out); return buf; } buffer_data_strip_header(buf, cs.consumed); buffer_data_reserve_header(buf, cs.produced); /* XXX see note above - should be able to improve this to avoid the temp buffer */ memcpy(buffer_data_pointer(buf), hc_out, cs.produced); ns_dyn_mem_free(hc_out); return buf; }