/* * Copyright (c) 2013-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_types.h" #include "string.h" #include "ns_trace.h" #include "ip_fsc.h" #include "NWK_INTERFACE/Include/protocol.h" #include "NWK_INTERFACE/Include/protocol_stats.h" #include "6LoWPAN/Bootstraps/network_lib.h" // for nwk_udp_rx_security_check #include "Common_Protocols/ipv6_constants.h" #include "Common_Protocols/icmpv6.h" #include "Common_Protocols/udp.h" #include "Core/include/socket.h" #include "common_functions.h" #define TRACE_GROUP "udp" static buffer_t *udp_checksum_check(buffer_t *buf) { uint8_t *ptr = buffer_data_pointer(buf) + 6; uint16_t check = common_read_16_bit(ptr); // We refuse checksum field 0000, as per IPv6 (RFC 2460). Would have // to accept this if handling IPv4. if (check == 0 || buffer_ipv6_fcf(buf, IPV6_NH_UDP)) { tr_err("CKSUM ERROR - src=%s", trace_ipv6(buf->src_sa.address)); protocol_stats_update(STATS_IP_CKSUM_ERROR, 1); buf = buffer_free(buf); } return buf; } void udp_checksum_write(buffer_t *buf) { uint8_t *ptr = buffer_data_pointer(buf) + 6; uint16_t check; common_write_16_bit(0, ptr); check = buffer_ipv6_fcf(buf, IPV6_NH_UDP); if (check == 0) { check = 0xffff; } common_write_16_bit(check, ptr); } buffer_t *udp_down(buffer_t *buf) { if (buf->src_sa.addr_type != ADDR_IPV6) { //tr_debug("Create Address"); // if(protocol_stack_interface_get_address_by_prefix(buf->if_index, buf->src_sa.address,buf->dst_sa.address, 0) != 0) // { tr_debug("InterFace Address Get Fail--> free Buffer"); return buffer_free(buf); // } // else // { // buf->src_sa.addr_type = ADDR_IPV6; // } } buf = buffer_headroom(buf, 8); if (buf) { uint8_t *ptr; buf->buf_ptr -= 8; ptr = buffer_data_pointer(buf); ptr = common_write_16_bit(buf->src_sa.port, ptr); ptr = common_write_16_bit(buf->dst_sa.port, ptr); ptr = common_write_16_bit(buffer_data_length(buf), ptr); udp_checksum_write(buf); buf->IPHC_NH = 0; buf->info = (buffer_info_t)(B_FROM_UDP | B_TO_IPV6 | B_DIR_DOWN); buf->options.type = IPV6_NH_UDP; buf->options.code = 0; } return (buf); } buffer_t *udp_up(buffer_t *buf) { //tr_debug("UDP UP"); const uint8_t *ip_hdr; if ((buf->info & B_FROM_MASK) == B_FROM_IPV6_FWD) { // New paths leave IP header on for us to permit ICMP response; // note the pointer and strip now. ip_hdr = buffer_data_pointer(buf); buffer_data_strip_header(buf, buf->offset); buf->offset = 0; } else { // We came from cipv6_up (or...?) - we have no real IP headers ip_hdr = NULL; } uint16_t ip_len = buffer_data_length(buf); if (ip_len < 8) { return buffer_free(buf); } const uint8_t *udp_hdr = buffer_data_pointer(buf); buf->src_sa.port = common_read_16_bit(udp_hdr + 0); buf->dst_sa.port = common_read_16_bit(udp_hdr + 2); uint16_t udp_len = common_read_16_bit(udp_hdr + 4); buf = nwk_udp_rx_security_check(buf); if (!buf) { return NULL; } if (udp_len < 8 || udp_len > ip_len) { return buffer_free(buf); } // Set UDP length - may trim the buffer buffer_data_length_set(buf, udp_len); buf = udp_checksum_check(buf); if (!buf) { return buf; } // Remove UDP header buffer_data_pointer_set(buf, udp_hdr + 8); if (buf->dst_sa.port == 0) { tr_err("UDP port 0"); protocol_stats_update(STATS_IP_RX_DROP, 1); return buffer_free(buf); } if (buf->dst_sa.port == UDP_PORT_ECHO && buf->src_sa.port != UDP_PORT_ECHO) { protocol_interface_info_entry_t *cur; tr_debug("UDP echo msg [%"PRIi16"]: %s%s", buffer_data_length(buf), trace_array( buffer_data_pointer(buf), (buffer_data_length(buf) > 64 ? 64 : buffer_data_length(buf))), (buffer_data_length(buf) > 64 ? "..." : "")); cur = buf->interface; if (addr_is_ipv6_multicast(buf->dst_sa.address)) { const uint8_t *ipv6_ptr; ipv6_ptr = addr_select_source(cur, buf->dst_sa.address, 0); if (!ipv6_ptr) { tr_debug("UDP Echo:No address"); return buffer_free(buf); } memcpy(buf->dst_sa.address, buf->src_sa.address, 16); memcpy(buf->src_sa.address, ipv6_ptr, 16); } else { memswap(buf->dst_sa.address, buf->src_sa.address, 16); } buf->dst_sa.port = buf->src_sa.port; buf->src_sa.port = UDP_PORT_ECHO; buf->info = (buffer_info_t)(B_FROM_UDP | B_TO_UDP | B_DIR_DOWN); buf->options.hop_limit = UNICAST_HOP_LIMIT_DEFAULT; buf->options.traffic_class = 0; buf->IPHC_NH = 0; return buffer_turnaround(buf); } if (ip_hdr) { /* New path generates port unreachable here, using the real IP headers * that we know the position of thanks to buf->offset. * * Old path has socket_up make port unreachable itself, creating a * fake IP header. */ if (!buf->socket) { buffer_socket_set(buf, socket_lookup_ipv6(IPV6_NH_UDP, &buf->dst_sa, &buf->src_sa, true)); } if (!buf->socket) { // Reconstruct original IP packet buffer_data_pointer_set(buf, udp_hdr); buffer_data_length_set(buf, ip_len); buffer_data_pointer_set(buf, ip_hdr); return icmpv6_error(buf, NULL, ICMPV6_TYPE_ERROR_DESTINATION_UNREACH, ICMPV6_CODE_DST_UNREACH_PORT_UNREACH, 0); } } buf->options.type = (uint8_t) SOCKET_FAMILY_IPV6; buf->options.code = IPV6_NH_UDP; buf->info = (buffer_info_t)(B_FROM_UDP | B_TO_APP | B_DIR_UP); return buf; }