/* * Copyright (c) 2008-2015, 2017-2021, Pelion 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. */ /** * \file socket.c * \brief Socket API * * The socket API functions */ #include "nsconfig.h" #include "ns_trace.h" #include <stdio.h> #include "string.h" #include "ns_types.h" #include "randLIB.h" #include "eventOS_event.h" #include "eventOS_scheduler.h" #include "nsdynmemLIB.h" #include "Core/include/ns_socket.h" #include "socket_api.h" #include "NWK_INTERFACE/Include/protocol.h" #include "Common_Protocols/tcp.h" #include "Common_Protocols/ipv6.h" #include "Common_Protocols/icmpv6.h" #include "ip6string.h" #include "common_functions.h" #define TRACE_GROUP "sck" #define RANDOM_PORT_NUMBER_START 49152 #define RANDOM_PORT_NUMBER_END 65535 #define RANDOM_PORT_NUMBER_COUNT (RANDOM_PORT_NUMBER_END - RANDOM_PORT_NUMBER_START + 1) #define RANDOM_PORT_NUMBER_MAX_STEP 500 static bool socket_reference_limit(socket_t *socket_ptr); static uint16_t port_counter; static socket_t *socket_instance[SOCKETS_MAX]; typedef struct socket_cb_data_t { int8_t tasklet; int8_t net_interface_id; socket_t *socket; buffer_t *buf; } socket_cb_data_t; typedef struct socket_cb_event_t { socket_t *socket; uint8_t socket_event; int8_t interface_id; uint16_t length; void *session_ptr; } socket_cb_event_t; socket_list_t socket_list = NS_LIST_INIT(socket_list); static int8_t socket_event_handler = -1; static uint8_t last_allocated_socket = SOCKETS_MAX - 1; /** * Generate random port number between RANDOM_PORT_NUMBER_START and RANDOM_PORT_NUMBER_END. * * \return random port number */ uint16_t socket_generate_random_port(uint8_t protocol) { uint16_t count = RANDOM_PORT_NUMBER_COUNT; do { port_counter += randLIB_get_random_in_range(1, RANDOM_PORT_NUMBER_MAX_STEP); while (port_counter >= RANDOM_PORT_NUMBER_COUNT) { port_counter -= RANDOM_PORT_NUMBER_COUNT; } uint16_t port = RANDOM_PORT_NUMBER_START + port_counter; if (socket_port_validate(port, protocol) == eOK) { return port; } } while (--count > 0); return 0; } int8_t socket_event_handler_id_get(void) { return socket_event_handler; } socket_t *socket_pointer_get(int8_t socket) { if (socket < 0 || socket >= SOCKETS_MAX) { return NULL; } if (!socket_instance[socket] || socket_instance[socket]->family == SOCKET_FAMILY_NONE) { return NULL; } return socket_instance[socket]; } static void socket_data_event_push(buffer_t *buf) { buf->socket = socket_reference(buf->socket); arm_event_s event = { .receiver = socket_event_handler, .sender = 0, .event_type = ARM_SOCKET_DATA_CB, .data_ptr = buf, .priority = ARM_LIB_HIGH_PRIORITY_EVENT, }; if (eventOS_event_send(&event) != 0) { buffer_free(buf); } } bool socket_data_queued_event_push(socket_t *socket) { arm_event_s event = { .receiver = socket_event_handler, .sender = 0, .event_type = ARM_SOCKET_DATA_QUEUED_CB, .data_ptr = socket_reference(socket), .priority = ARM_LIB_HIGH_PRIORITY_EVENT, }; if (eventOS_event_send(&event) != 0) { socket_dereference(socket); return false; } return true; } static void socket_cb_event_run(const socket_cb_event_t *event) { if (event->socket->id != -1) { eventOS_scheduler_set_active_tasklet(event->socket->tasklet); if (event->socket->flags & SOCKET_BUFFER_CB) { static socket_buffer_callback_t socket_cb_event_buffer; socket_cb_event_buffer.session_ptr = event->session_ptr; socket_cb_event_buffer.event_type = event->socket_event; socket_cb_event_buffer.socket_id = event->socket->id; socket_cb_event_buffer.buf = 0; socket_cb_event_buffer.interface_id = event->interface_id; event->socket->u.live.fptr(&socket_cb_event_buffer); socket_cb_event_buffer.session_ptr = 0; } else { static socket_callback_t socket_cb_event_stucture; socket_cb_event_stucture.event_type = event->socket_event; socket_cb_event_stucture.socket_id = event->socket->id; socket_cb_event_stucture.d_len = event->length; socket_cb_event_stucture.interface_id = event->interface_id; event->socket->u.live.fptr(&socket_cb_event_stucture); } } } static void socket_buffer_cb_run(socket_t *socket, buffer_t *buffer) { eventOS_scheduler_set_active_tasklet(socket->tasklet); static socket_buffer_callback_t socket_cb_buffer; socket_cb_buffer.event_type = SOCKET_DATA; socket_cb_buffer.socket_id = socket->id; socket_cb_buffer.interface_id = buffer->interface->id; socket_cb_buffer.buf = buffer; socket_cb_buffer.session_ptr = NULL; socket->u.live.fptr(&socket_cb_buffer); } void socket_cb_run(socket_t *socket) { if (socket->id == -1 || !socket->u.live.fptr) { return; } buffer_t *buf = ns_list_get_first(&socket->rcvq.bufs); eventOS_scheduler_set_active_tasklet(socket->tasklet); static socket_callback_t socket_cb_structure; socket_cb_structure.event_type = SOCKET_DATA; socket_cb_structure.socket_id = socket->id; socket_cb_structure.interface_id = buf ? buf->interface->id : 0; socket_cb_structure.LINK_LQI = buf ? buf->options.lqi : 0; socket_cb_structure.d_len = socket->rcvq.data_bytes; socket->u.live.fptr(&socket_cb_structure); } void socket_tasklet_event_handler(arm_event_s *event) { switch (event->event_type) { case ARM_SOCKET_INIT: tr_debug("Socket Tasklet Generated"); break; case ARM_SOCKET_EVENT_CB: { socket_cb_event_t *cb_event_ptr = event->data_ptr; socket_cb_event_run(cb_event_ptr); socket_dereference(cb_event_ptr->socket); ns_dyn_mem_free(cb_event_ptr); break; } case ARM_SOCKET_DATA_CB: { buffer_t *buf = event->data_ptr; if (!buf || !buf->socket) { tr_error("Socket CB: Buf or Socket pointer NULL"); buffer_free(buf); break; } socket_t *socket = buf->socket; if (socket->id == -1 || !socket->u.live.fptr) { //Socket is released Free just Buffer buffer_free(buf); } else if (socket->flags & SOCKET_BUFFER_CB) { // They just take ownership of the buffer. No read calls. socket_buffer_cb_run(socket, buf); } else { // Emulate old one-callback-per-buffer behavior. Make sure // the recv queue is empty, then put this buffer on it, then // make the callback. Their read calls then read from the // recv queue. After the callback, flush the receive queue again. sockbuf_flush(&socket->rcvq); sockbuf_append(&socket->rcvq, buf); socket_cb_run(socket); sockbuf_flush(&socket->rcvq); } /* Dereference the socket here*/ socket_dereference(socket); break; } case ARM_SOCKET_DATA_QUEUED_CB: { socket_t *socket = event->data_ptr; if (socket) { socket_cb_run(socket); } socket_dereference(socket); break; } case ARM_SOCKET_TCP_TIMER_CB: tcp_handle_time_event((uint16_t)event->event_data); break; default: break; } } /** * Initialize API */ void socket_init(void) { if (socket_event_handler == -1) { socket_event_handler = eventOS_event_handler_create(&socket_tasklet_event_handler, ARM_SOCKET_INIT); } port_counter = randLIB_get_random_in_range(0, RANDOM_PORT_NUMBER_COUNT - 1); } /** * Release a socket (detaching from application) * * We are either releasing directly from an application close (ID being freed, * reference being removed from ID table), or indirectly - a pending socket * attached to a listening socket being closed. * * \param socket socket pointer */ void socket_release(socket_t *socket) { socket->flags |= SOCKET_FLAG_CLOSED | SOCKET_FLAG_SHUT_WR | SOCKET_FLAG_CANT_RECV_MORE; if (socket_is_ipv6(socket)) { // Do TCP cleanup first, while we have one reference count of our own, // so it can't make socket vanish. // Take care never to put tcp_info in local variable - these calls can delete it if (tcp_info(socket->inet_pcb)) { /* This may trigger a reset if pending data. Do it first so you * get just the reset, rather than a FIN. */ tcp_error sock_status = tcp_session_shutdown_read(tcp_info(socket->inet_pcb)); /* This can also cause TCP deletion */ if (tcp_info(socket->inet_pcb)) { sock_status = tcp_session_close(tcp_info(socket->inet_pcb)); } if (tcp_info(socket->inet_pcb)) { tcp_socket_released(tcp_info(socket->inet_pcb)); } // prevent warning "statement with no effect" when TCP is disabled (void) sock_status; } else { /* Unbind the internet control block - ensures users are not prevented * from binding a new socket to the same port if the socket lingers * on due to pending events/buffers. (And also means the new socket * gets any such packets, with this getting none). */ memcpy(socket->inet_pcb->local_address, ns_in6addr_any, 16); memcpy(socket->inet_pcb->remote_address, ns_in6addr_any, 16); socket->inet_pcb->local_port = 0; socket->inet_pcb->remote_port = 0; socket->inet_pcb->protocol = 0; } sockbuf_flush(&socket->rcvq); } if (!(socket->flags & SOCKET_FLAG_PENDING)) { // Release any pending sockets in a listening socket's queue ns_list_foreach_safe(socket_t, pending, &socket->u.live.queue) { // Release of a pending socket will make it non-pending and // hence take it off our queue (see immediately below) making // this loop work. (And maybe it will free it too). socket_release(pending); } } else { // This may make socket vanish, as leaving pending decrements count socket_leave_pending_state(socket, NULL); } } static void socket_free(socket_t *socket) { if (socket->refcount != 0) { tr_err("free refed"); } if (socket->flags & SOCKET_FLAG_PENDING) { tr_err("free pending"); } ns_list_remove(&socket_list, socket); sockbuf_flush(&socket->rcvq); sockbuf_flush(&socket->sndq); socket_inet_pcb_free(socket->inet_pcb); ns_dyn_mem_free(socket); } socket_error_t socket_port_validate(uint16_t port, uint8_t protocol) { ns_list_foreach(socket_t, socket, &socket_list) { if (!socket_is_ipv6(socket)) { continue; } if (socket->inet_pcb->local_port == port && socket->inet_pcb->protocol == protocol) { return eFALSE; } } return eOK; } int8_t socket_id_assign_and_attach(socket_t *socket) { int pos = last_allocated_socket + 1; for (int i = 0; i < SOCKETS_MAX; i++, pos++) { if (pos >= SOCKETS_MAX) { pos = 0; } if (socket_instance[pos] == NULL) { socket->id = pos; socket_instance[pos] = socket_reference(socket); last_allocated_socket = pos; return pos; } } return -1; } void socket_id_detach(int8_t sid) { socket_t *socket = socket_instance[sid]; socket->id = -1; socket_release(socket); socket_instance[sid] = socket_dereference(socket); } inet_pcb_t *socket_inet_pcb_allocate(void) { inet_pcb_t *inet_pcb = ns_dyn_mem_alloc(sizeof(inet_pcb_t)); if (!inet_pcb) { return NULL; } memset(inet_pcb, 0, sizeof(inet_pcb_t)); #ifndef NO_TCP inet_pcb->session = NULL; #endif inet_pcb->unicast_hop_limit = -1; inet_pcb->multicast_hop_limit = 1; inet_pcb->multicast_if = 0; inet_pcb->multicast_loop = true; inet_pcb->socket = NULL; inet_pcb->addr_preferences = 0; inet_pcb->recvhoplimit = false; inet_pcb->recvpktinfo = false; inet_pcb->recvtclass = false; inet_pcb->edfe_mode = false; inet_pcb->link_layer_security = -1; #ifndef NO_IPV6_PMTUD inet_pcb->use_min_mtu = -1; #endif #ifndef NO_IP_FRAGMENT_TX inet_pcb->dontfrag = 0; #endif inet_pcb->tclass = SOCKET_IPV6_TCLASS_DEFAULT; inet_pcb->flow_label = IPV6_FLOW_UNSPECIFIED; ns_list_init(&inet_pcb->mc_groups); return inet_pcb; } inet_pcb_t *socket_inet_pcb_clone(const inet_pcb_t *orig) { inet_pcb_t *inet_pcb = ns_dyn_mem_alloc(sizeof(inet_pcb_t)); if (!inet_pcb) { return NULL; } *inet_pcb = *orig; inet_pcb->socket = NULL; #ifndef NO_TCP inet_pcb->session = NULL; #endif return inet_pcb; } socket_t *socket_allocate(socket_type_t type) { socket_t *socket = ns_dyn_mem_alloc(sizeof(socket_t)); if (!socket) { return NULL; } memset(socket, 0, sizeof * socket); socket->id = -1; socket->type = type; socket->family = SOCKET_FAMILY_NONE; sockbuf_init(&socket->rcvq); sockbuf_init(&socket->sndq); if (type == SOCKET_TYPE_STREAM) { socket->sndq.low_water_mark = SOCKET_DEFAULT_STREAM_SNDLOWAT; sockbuf_reserve(&socket->rcvq, SOCKET_DEFAULT_STREAM_RCVBUF); sockbuf_reserve(&socket->sndq, SOCKET_DEFAULT_STREAM_SNDBUF); } ns_list_add_to_end(&socket_list, socket); return socket; } static bool socket_reference_limit(socket_t *socket_ptr) { if (socket_ptr && socket_ptr->refcount < SOCKET_DEFAULT_REFERENCE_LIMIT) { return false; } return true; } /* Increase reference counter on socket, returning now-owned pointer */ socket_t *socket_reference(socket_t *socket_ptr) { if (!socket_ptr) { return NULL; } socket_ptr->refcount++; if (socket_ptr->refcount == 0) { socket_ptr->refcount--; tr_error("ref overflow"); } return socket_ptr; } /* Decrease reference counter on socket, releasing if it hits zero */ /* Always returns NULL to indicate caller no longer owns it */ socket_t *socket_dereference(socket_t *socket_ptr) { if (!socket_ptr) { return NULL; } if (socket_ptr->refcount == 0) { tr_error("Socket %d ref underflow", socket_ptr->id); return NULL; } if (--socket_ptr->refcount == 0) { socket_free(socket_ptr); } return NULL; } /** * Allocate a socket * * \param sid pointer to socket ID which will contain socket ID * \param port listen port for socket * * \return eOK socket opened * \return eFALSE no free sockets * \return eBUSY port reserved */ socket_error_t socket_create(socket_family_t family, socket_type_t type, uint8_t protocol, int8_t *sid, uint16_t port, void (*passed_fptr)(void *), bool buffer_type) { if (sid) { *sid = -1; } if (passed_fptr == 0) { return eFALSE; } /* Note that IPv6 socket lookup implementation assumes we don't have raw * TCP or UDP sockets, thus we ensure protocol=UDP iff type=DGRAM, * and protocol=TCP iff type=STREAM. */ if (family == SOCKET_FAMILY_IPV6) { switch (type) { case SOCKET_TYPE_DGRAM: protocol = IPV6_NH_UDP; break; case SOCKET_TYPE_STREAM: protocol = IPV6_NH_TCP; break; default: if (protocol == 0 || protocol == IPV6_NH_TCP || protocol == IPV6_NH_UDP) { return eFALSE; } port = 0xFFFF; break; } } else { /* SOCKET_FAMILY_LOCAL still has inet_pcb - give it 0 protocol */ protocol = 0; port = 0xFFFF; } if (port != 0 && socket_port_validate(port, protocol) == eFALSE) { return eBUSY; } socket_t *socket = socket_allocate(type); if (socket == NULL) { tr_error("Socket allocation fail"); return eFALSE; } inet_pcb_t *inet_pcb = socket_inet_pcb_allocate(); if (inet_pcb == NULL) { socket_free(socket); // don't leave half initialized sockets behind tr_error("inet_pcb allocation fail"); return eFALSE; } socket->inet_pcb = inet_pcb; *sid = socket_id_assign_and_attach(socket); if (*sid == -1) { tr_error("Socket IDs exhausted"); socket_free(socket); return eFALSE; } socket->flags = 0; tr_debug("Socket id %d allocated", socket->id); socket->tasklet = eventOS_scheduler_get_active_tasklet(); socket->family = family; socket->u.live.fptr = passed_fptr; ns_list_init(&socket->u.live.queue); socket->default_interface_id = -1; socket->broadcast_pan = 0; if (buffer_type) { socket->flags |= SOCKET_BUFFER_CB; } // Fill Internet protocol control block inet_pcb->protocol = protocol; inet_pcb->local_port = port; inet_pcb->socket = socket; return eOK; } socket_t *socket_new_incoming_connection(socket_t *listen_socket) { if (!socket_validate_listen_backlog(listen_socket)) { return NULL; } socket_t *new_socket = socket_allocate(listen_socket->type); if (!new_socket) { return NULL; } inet_pcb_t *inet_pcb = socket_inet_pcb_clone(listen_socket->inet_pcb); if (!inet_pcb) { socket_free(new_socket); return NULL; } inet_pcb->socket = new_socket; // They can fill this in on return new_socket->inet_pcb = inet_pcb; new_socket->flags = SOCKET_FLAG_PENDING | SOCKET_FLAG_CONNECTING; new_socket->family = listen_socket->family; new_socket->type = listen_socket->type; new_socket->default_interface_id = listen_socket->default_interface_id; new_socket->broadcast_pan = listen_socket->broadcast_pan; new_socket->tasklet = listen_socket->tasklet; new_socket->u.pending.listen_head = listen_socket; ns_list_add_to_end(&listen_socket->u.live.queue, socket_reference(new_socket)); return new_socket; } /* Connection did not complete or was abandoned - notification from transport */ void socket_connection_abandoned(socket_t *socket, int8_t interface_id, uint8_t reason) { if (!(socket->flags & (SOCKET_FLAG_CONNECTING | SOCKET_FLAG_CONNECTED))) { tr_err("abandoned: not connecting/connected"); return; } socket->flags &= ~ SOCKET_FLAG_CONNECTING; // leaving CONNECTED flag set prevents weirdness like reconnecting. Good idea? socket->flags |= SOCKET_FLAG_SHUT_WR | SOCKET_FLAG_CANT_RECV_MORE | SOCKET_FLAG_CONNECTED; sockbuf_flush(&socket->sndq); if (socket->flags & SOCKET_FLAG_PENDING) { // By leaving pending state, without any remaining reference, // we just let the failed connection go away, without notifying user socket_leave_pending_state(socket, NULL); // Hard to say if this is right if it was fully connected, but seems the best // we can do for the minute - we can't queue up the reset/whatever event. } else if (reason != 0) { socket_event_push(reason, socket, interface_id, NULL, 0); } } /* Connection has completed - notification from transport */ void socket_connection_complete(socket_t *socket, int8_t interface_id) { if (!(socket->flags & SOCKET_FLAG_CONNECTING)) { tr_err("complete: not connecting"); return; } if (socket->flags & SOCKET_FLAG_CONNECTED) { tr_err("complete: already connected"); return; } socket->flags &= ~ SOCKET_FLAG_CONNECTING; socket->flags |= SOCKET_FLAG_CONNECTED; if (socket->flags & SOCKET_FLAG_PENDING) { socket_event_push(SOCKET_INCOMING_CONNECTION, socket->u.pending.listen_head, interface_id, NULL, 0); } else { socket_event_push(SOCKET_CONNECT_DONE, socket, interface_id, NULL, 0); } } /* Socket is to leave pending state - either being accepted or released */ void socket_leave_pending_state(socket_t *socket, void (*fptr)(void *)) { if (!(socket->flags & SOCKET_FLAG_PENDING)) { tr_err("not pending"); return; } ns_list_remove(&socket->u.pending.listen_head->u.live.queue, socket); socket->flags &= ~SOCKET_FLAG_PENDING; ns_list_init(&socket->u.live.queue); socket->u.live.fptr = fptr; socket_dereference(socket); } void socket_cant_recv_more(socket_t *socket, int8_t interface_id) { socket->flags |= SOCKET_FLAG_CANT_RECV_MORE; socket_event_push(SOCKET_DATA, socket, interface_id, 0, 0); } socket_t *socket_lookup_ipv6(uint8_t protocol, const sockaddr_t *local_addr, const sockaddr_t *remote_addr, bool allow_wildcards) { //tr_debug("socket_lookup() local=%s [%d] remote=%s [%d]", trace_ipv6(local_addr->address), local_addr->port, trace_ipv6(remote_addr->address), remote_addr->port); ns_list_foreach(socket_t, socket, &socket_list) { if (!socket_is_ipv6(cur)) { continue; } inet_pcb_t *cur = socket->inet_pcb; //tr_debug("cur local=%s [%d] remote=%s [%d]", trace_ipv6(cur->local_address), cur->local_port, trace_ipv6(cur->remote_address), cur->remote_port); /* Protocol must match */ if (cur->protocol != protocol) { continue; } /* For TCP and UDP only, ports must match */ if (protocol == IPV6_NH_UDP || protocol == IPV6_NH_TCP) { if (cur->local_port == 0 || cur->local_port != local_addr->port) { continue; } if (!(allow_wildcards && cur->remote_port == 0) && cur->remote_port != remote_addr->port) { continue; } } if (!(allow_wildcards && addr_ipv6_equal(cur->local_address, ns_in6addr_any))) { if (!addr_ipv6_equal(cur->local_address, local_addr->address)) { continue; } } if (!(allow_wildcards && addr_ipv6_equal(cur->remote_address, ns_in6addr_any))) { if (!addr_ipv6_equal(cur->remote_address, remote_addr->address)) { continue; } } return socket; } return NULL; } /* Write address + port neatly to out, returning characters written */ /* Maximum output length is 48, including the terminator, assuming ip6tos limited to 39 */ static int sprint_addr(char *out, const uint8_t addr[static 16], uint16_t port) { char *init_out = out; if (addr_is_ipv6_unspecified(addr)) { *out++ = '*'; } else { *out++ = '['; out += ip6tos(addr, out); *out++ = ']'; } *out++ = ':'; if (port == 0) { *out++ = '*'; } else { out += sprintf(out, "%"PRIu16, port); } *out = '\0'; return (int)(out - init_out); } static void socket_print(const socket_t *socket, int lwidth, int rwidth, route_print_fn_t *print_fn, char sep) { if (!socket_is_ipv6(socket)) { return; } const inet_pcb_t *pcb = socket->inet_pcb; char remote_str[48]; char local_str[48]; char proto_buf[4]; const char *proto_str; switch (pcb->protocol) { case IPV6_NH_TCP: proto_str = "TCP"; break; case IPV6_NH_UDP: proto_str = "UDP"; break; case IPV6_NH_ICMPV6: proto_str = "ICMP6"; break; default: sprintf(proto_buf, "%"PRIu8, pcb->protocol); proto_str = proto_buf; break; } sprint_addr(local_str, pcb->local_address, pcb->local_port); sprint_addr(remote_str, pcb->remote_address, pcb->remote_port); const char *state = ""; if (pcb->protocol == IPV6_NH_TCP) { if (tcp_info(pcb)) { state = tcp_state_name(tcp_info(pcb)); } else if (socket->flags & SOCKET_LISTEN_STATE) { state = "LISTEN"; } else { state = "CLOSED"; } } print_fn("%3.*"PRId8"%c%3u%c%6"PRId32/*"%c%6"PRId32*/"%c%6"PRId32"%c%-5.5s%c%-*s%c%-*s%c%s", socket->id >= 0 ? 1 : 0, socket->id >= 0 ? socket->id : 0, sep, socket->refcount, sep, socket->rcvq.data_bytes, sep, /*socket->rcvq.buf_overhead_bytes, sep,*/ socket->sndq.data_bytes, sep, proto_str, sep, lwidth, local_str, sep, rwidth, remote_str, sep, state); } void socket_list_print(route_print_fn_t *print_fn, char sep) { int lwidth = 18, rwidth = 18; ns_list_foreach(const socket_t, socket, &socket_list) { if (!socket_is_ipv6(socket)) { continue; } const inet_pcb_t *pcb = socket->inet_pcb; char addr[48]; int len; len = sprint_addr(addr, pcb->local_address, pcb->local_port); if (lwidth < len) { lwidth = len; } len = sprint_addr(addr, pcb->remote_address, pcb->remote_port); if (rwidth < len) { rwidth = len; } } print_fn("Sck%cRef%cRecv-Q%c"/*"Recv-B%c"*/"Send-Q%cProto%c%-*s%c%-*s%c(state)", sep, sep, sep, /*sep,*/ sep, sep, lwidth, "Local Address", sep, rwidth, "Remote Address", sep); ns_list_foreach(const socket_t, socket, &socket_list) { socket_print(socket, lwidth, rwidth, print_fn, sep); } /* Chuck in a consistency check */ for (int i = 0; i < SOCKETS_MAX; i++) { if (socket_instance[i] && socket_instance[i]->id != i) { tr_err("ID %d points to %p with id %d", i, (void *)socket_instance[i], socket_instance[i]->id); } } ns_list_foreach(socket_t, socket, &socket_list) { if (socket->id != -1 && socket_pointer_get(socket->id) != socket) { tr_err("Socket %p has invalid ID %d", (void *)socket, socket->id); } sockbuf_check(&socket->rcvq); sockbuf_check(&socket->sndq); } } socket_t *socket_lookup(socket_family_t family, uint8_t protocol, const sockaddr_t *local_addr, const sockaddr_t *remote_addr) { switch (family) { case SOCKET_FAMILY_IPV6: return socket_lookup_ipv6(protocol, local_addr, remote_addr, true); default: return NULL; } } /** * Push a buffer to a socket * * Used by the protocol core to push buffers to sockets. * * To determine the socket to send to, transport layer must ensure either: * a) buf->socket_id already filled in, or * b) buf->options.type is socket family (IPv6/Local) * buf->options.code is protocol (IPv6=NH,Local=n/a) * * \param buf buffer to push * * \return eOK socket opened * \return eFALSE no socket found * \return eBUSY socket full */ socket_error_t socket_up(buffer_t *buf) { socket_t *socket = buf->socket; if (!socket) { socket = socket_lookup((socket_family_t) buf->options.type, buf->options.code, &buf->dst_sa, &buf->src_sa); if (!socket) { //tr_debug("Socket:Drop"); goto drop; } buffer_socket_set(buf, socket); } if ((socket->flags & (SOCKET_FLAG_PENDING | SOCKET_FLAG_CLOSED)) || socket->id == -1) { goto drop; } //Limit here if (socket_reference_limit(socket)) { tr_error("Socket reference limit drop RX %u", socket->refcount); goto drop; } if (socket->rcvq.data_byte_limit == 0) { // Old-style one event per buffer socket_data_event_push(buf); } else { // Queuing enabled - new-style events if (sockbuf_space(&socket->rcvq) < buffer_data_length(buf)) { tr_debug("Socket %d Recv-Q full", socket->id); goto drop; } bool pushed = socket_data_queued_event_push(socket); // if this failed, it means total memory exhaustion - safest to drop the packet if (!pushed) { goto drop; } sockbuf_append(&socket->rcvq, buf); } return eOK; drop: buffer_free(buf); return eFALSE; } void socket_event_push(uint8_t sock_event, socket_t *socket, int8_t interface_id, void *session_ptr, uint16_t length) { if (socket->flags & (SOCKET_FLAG_PENDING | SOCKET_FLAG_CLOSED)) { return; } socket_cb_event_t *cb_event = ns_dyn_mem_temporary_alloc(sizeof(socket_cb_event_t)); if (cb_event) { cb_event->socket = socket_reference(socket); cb_event->socket_event = sock_event; cb_event->session_ptr = session_ptr; cb_event->interface_id = interface_id; cb_event->length = length; arm_event_s event = { .receiver = socket_event_handler, .sender = 0, .data_ptr = cb_event, .event_type = ARM_SOCKET_EVENT_CB, .priority = ARM_LIB_HIGH_PRIORITY_EVENT, }; if (eventOS_event_send(&event) != 0) { socket_dereference(socket); ns_dyn_mem_free(cb_event); } } } /* Fill in a buffer's hop limit, based on the user's socket options */ void socket_inet_pcb_set_buffer_hop_limit(const inet_pcb_t *inet_pcb, buffer_t *buf, const int16_t *msg_hoplimit) { /* If hop limit specified by ancillary data, obey that over socket options */ if (msg_hoplimit) { if (*msg_hoplimit != -1) { /* Use the value they gave for this message */ buf->options.hop_limit = *msg_hoplimit; return; } /* If -1, we will go with the default, ignoring socket options */ } else if (addr_is_ipv6_multicast(buf->dst_sa.address)) { /* Multicasts have a fixed default - multicast_hop_limit will be user-specified, or that default */ buf->options.hop_limit = inet_pcb->multicast_hop_limit; return; } else if (inet_pcb->unicast_hop_limit != -1) { /* If the user specified a unicast hop limit, use it */ buf->options.hop_limit = inet_pcb->unicast_hop_limit; return; } /* User didn't specify a unicast hop limit, get the per-interface default */ /* At the moment we can only rely on if_index in the down direction, not id */ protocol_interface_info_entry_t *cur = buf->interface; buf->options.hop_limit = cur->cur_hop_limit; } /** * Calculate message payload length and validate message header iov vectors */ bool socket_message_validate_iov(const ns_msghdr_t *msg, uint16_t *length_out) { if (msg->msg_iovlen != 0 && !msg->msg_iov) { return false; } uint16_t msg_length = 0; ns_iovec_t *msg_iov = msg->msg_iov; for (uint_fast16_t i = 0; i < msg->msg_iovlen; i++) { if (msg_iov->iov_len != 0 && !msg_iov->iov_base) { return false; } msg_length += msg_iov->iov_len; if (msg_length < msg_iov->iov_len) { return false; } msg_iov++; } if (length_out) { *length_out = msg_length; } return true; } /** * Write a data buffer to a socket * * Used by the application to send data with meta data from msg * * Data will be taken either from buf if non-NULL else from msg->msg_iov. * * Using buf is not permitted for stream sockets. * * \param sid socket id * \param buf pointer to buffer is plain buffer with payload only - all metadata ignored * \param msg pointer to message-specific data message name define destination address and control message define ancillary data * * \return 0 Ok done * \return -1 Unsupported socket or message parameter * \return -5 Socket not properly connected * \return -6 Packet too short */ int16_t socket_buffer_sendmsg(int8_t sid, buffer_t *buf, const struct ns_msghdr *msg, int flags) { int8_t ret_val; int8_t interface_id = -1; const int16_t *msg_hoplimit = NULL; uint16_t payload_length; //Validate socket id socket_t *socket_ptr = socket_pointer_get(sid); if (!socket_ptr || !socket_ptr->inet_pcb) { tr_warn("Socket Id %i", sid); ret_val = -1; goto fail; } inet_pcb_t *inet_pcb = socket_ptr->inet_pcb; if (buf) { payload_length = buffer_data_length(buf); } else { if (!socket_message_validate_iov(msg, &payload_length)) { return -1; } } #ifndef NO_TCP /* Special handling for stream - basically no advanced features of sendmsg operate. * Can't set address, can't set ancilliary data. * Don't want to fill in other sticky options into buffer - TCP must handle this itself, * as it must do it for all packets, not just those coming from sendmsg. */ if (socket_ptr->type == SOCKET_TYPE_STREAM) { /* Stream sockets must be connected and not shutdown for write */ if ((socket_ptr->flags & (SOCKET_FLAG_CONNECTED | SOCKET_FLAG_SHUT_WR)) != SOCKET_FLAG_CONNECTED) { ret_val = -5; goto fail; } /* Stream sockets cannot have address specified (POSIX "EISCONN") */ if (msg && msg->msg_name && msg->msg_namelen) { ret_val = -5; goto fail; } if (payload_length == 0) { goto success; // Sending nothing is a no-op } // Figure out how much we can actually take, and reduce // payload_length if necessary (but if fed a buffer, we always just // accept the whole thing). if (!buf) { int32_t space = sockbuf_space(&socket_ptr->sndq); if (space < payload_length) { if (space < (int32_t) socket_ptr->sndq.low_water_mark) { ret_val = NS_EWOULDBLOCK; goto fail; } if (flags & NS_MSG_LEGACY0) { // Can't do a partial write, as we can't actually // indicate that we've done so. For compatibility, // accept a write of any size if there is some buffer // space available. This could make us massively go // over the buffer size limit. } else { payload_length = space; } } } } #endif // NO_TCP // Now copy (some of) the data into a buffer if (!buf) { buf = buffer_get(payload_length); if (!buf) { ret_val = -2; goto fail; } const ns_iovec_t *msg_iov = msg->msg_iov; uint16_t to_copy = payload_length; for (uint_fast16_t i = 0; i < msg->msg_iovlen; i++, msg_iov++) { uint16_t len = msg_iov->iov_len < to_copy ? msg_iov->iov_len : to_copy; buffer_data_add(buf, msg_iov->iov_base, len); to_copy -= len; if (to_copy == 0) { break; } } } buf->payload_length = payload_length; #ifndef NO_TCP if (socket_ptr->type == SOCKET_TYPE_STREAM) { tcp_session_t *tcp_info = inet_pcb->session; if (!tcp_info) { tr_warn("No TCP session for cur Socket"); ret_val = -3; goto fail; } switch (tcp_session_send(tcp_info, buf)) { case TCP_ERROR_NO_ERROR: break; case TCP_ERROR_WRONG_STATE: default: ret_val = -3; goto fail; } goto success; } // TCP system has taken ownership of buffer, or we've failed // Everything below this point is non-TCP #endif //NO_TCP if (socket_reference_limit(socket_ptr)) { tr_error("Socket reference limit drop TX %u", socket_ptr->refcount); ret_val = -1; goto fail; } /** * Mark Socket id to buffer meta data */ buffer_socket_set(buf, socket_ptr); /** * Allocate random port if port is not defined. */ if (inet_pcb->local_port == 0) { inet_pcb->local_port = socket_generate_random_port(inet_pcb->protocol); if (inet_pcb->local_port == 0) { ret_val = -2; goto fail; } } /** * Set socket configured parameters */ buf->options.traffic_class = inet_pcb->tclass; buf->options.ipv6_use_min_mtu = inet_pcb->use_min_mtu; buf->options.ipv6_dontfrag = inet_pcb->dontfrag; buf->options.multicast_loop = inet_pcb->multicast_loop; buf->options.edfe_mode = inet_pcb->edfe_mode; /* Set default remote address from PCB */ if (inet_pcb->remote_port != 0 && !addr_ipv6_equal(inet_pcb->remote_address, ns_in6addr_any)) { memcpy(buf->dst_sa.address, inet_pcb->remote_address, 16); buf->dst_sa.port = inet_pcb->remote_port; buf->dst_sa.addr_type = ADDR_IPV6; } else { buf->dst_sa.addr_type = ADDR_NONE; } /** * validate Message name and Parse Ancillary Data when Message header is shared. */ if (msg) { /** * Validate message name (destination) and set address to buffer */ if (msg->msg_namelen) { if (!msg->msg_name || msg->msg_namelen != sizeof(ns_address_t)) { ret_val = -1; goto fail; } ns_address_t *address = msg->msg_name; buf->dst_sa.port = address->identifier; buf->dst_sa.addr_type = ADDR_IPV6; memcpy(buf->dst_sa.address, address->address, 16); } ns_cmsghdr_t *cmsg = NS_CMSG_FIRSTHDR(msg); //Stay at while when full ancillary data is analyzed while (cmsg) { if (cmsg->cmsg_level == SOCKET_IPPROTO_IPV6) { const void *data_ptr = NS_CMSG_DATA(cmsg); switch (cmsg->cmsg_type) { case SOCKET_IPV6_PKTINFO: { if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(ns_in6_pktinfo_t))) { ret_val = -1; goto fail; } const ns_in6_pktinfo_t *packetinfo = data_ptr; memcpy(buf->src_sa.address, packetinfo->ipi6_addr, 16); buf->src_sa.addr_type = addr_ipv6_equal(packetinfo->ipi6_addr, ns_in6addr_any) ? ADDR_NONE : ADDR_IPV6; /** * Set interface id when it is configured >0 */ if (packetinfo->ipi6_ifindex > 0) { interface_id = packetinfo->ipi6_ifindex; } break; } case SOCKET_IPV6_HOPLIMIT: if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(int16_t))) { ret_val = -1; goto fail; } msg_hoplimit = data_ptr; if (*msg_hoplimit < -1 || *msg_hoplimit > 255) { ret_val = -1; goto fail; } break; case SOCKET_IPV6_TCLASS: { if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(int16_t))) { ret_val = -1; goto fail; } int16_t tclass = *(const int16_t *)data_ptr; if (tclass == -1) { buf->options.traffic_class = SOCKET_IPV6_TCLASS_DEFAULT; } else if (tclass >= 0 && tclass <= 255) { buf->options.traffic_class = tclass; } else { ret_val = -1; goto fail; } break; } #ifndef NO_IPV6_PMTUD case SOCKET_IPV6_USE_MIN_MTU: { if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(int8_t))) { ret_val = -1; goto fail; } int8_t use_min_mtu = *(const int8_t *) data_ptr; if (use_min_mtu < -1 || use_min_mtu > 1) { ret_val = -1; goto fail; } buf->options.ipv6_use_min_mtu = use_min_mtu; break; } #endif #ifndef NO_IP_FRAGMENT_TX case SOCKET_IPV6_DONTFRAG: { if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(int8_t))) { ret_val = -1; goto fail; } int8_t dont_frag = *(const int8_t *) data_ptr; if (dont_frag < 0 || dont_frag > 1) { ret_val = -1; goto fail; } buf->options.ipv6_dontfrag = dont_frag; break; } #endif case SOCKET_IPV6_MULTICAST_LOOP: { if (cmsg->cmsg_len != NS_CMSG_LEN(sizeof(bool))) { ret_val = -1; goto fail; } buf->options.multicast_loop = *(const bool *) data_ptr; break; } default: break; } } cmsg = NS_CMSG_NXTHDR(msg, cmsg); } } /* Should have destination address by now */ if (buf->dst_sa.addr_type == ADDR_NONE) { return -5; } switch (socket_ptr->type) { case SOCKET_TYPE_DGRAM: buf->info = (buffer_info_t)(B_DIR_DOWN + B_FROM_APP + B_TO_UDP); break; case SOCKET_TYPE_RAW: if (inet_pcb->protocol == IPV6_NH_ICMPV6) { if (buffer_data_length(buf) < 4) { ret_val = -6; goto fail; } uint8_t *ptr = buffer_data_pointer(buf); buf->options.type = ptr[0]; buf->options.code = ptr[1]; buffer_data_strip_header(buf, 4); buf->info = (buffer_info_t)(B_DIR_DOWN + B_FROM_APP + B_TO_ICMP); } else { buf->options.type = inet_pcb->protocol; buf->info = (buffer_info_t)(B_DIR_DOWN + B_FROM_APP + B_TO_IPV6); } break; default: ret_val = -1; goto fail; } /* If no address in packet info, use bound address, if any. Set before interface selection - * socket_interface_determine could otherwise auto-select source as a side-effect. */ if (buf->src_sa.addr_type == ADDR_NONE && !addr_ipv6_equal(inet_pcb->local_address, ns_in6addr_any)) { memcpy(buf->src_sa.address, inet_pcb->local_address, 16); buf->src_sa.addr_type = ADDR_IPV6; } /* If address is specified manually we must validate that the * address is valid in one of the available interfaces * */ if (buf->src_sa.addr_type == ADDR_IPV6 && protocol_interface_address_compare(buf->src_sa.address) != 0) { tr_warn("Specified source address %s is not valid", trace_ipv6(buf->src_sa.address)); ret_val = -3; goto fail; } /** * Select outgoing interface for this message */ protocol_interface_info_entry_t *cur_interface; if (interface_id > 0) { cur_interface = protocol_stack_interface_info_get_by_id(interface_id); } else { cur_interface = socket_interface_determine(socket_ptr, buf); } if (!cur_interface) { tr_warn("sendto:No interface"); ret_val = -4; goto fail; } buf->interface = cur_interface; /** * Link-specific configuration */ buf->options.ll_security_bypass_tx = (inet_pcb->link_layer_security == 0); if (socket_ptr->broadcast_pan > 0) { buf->link_specific.ieee802_15_4.useDefaultPanId = false; buf->link_specific.ieee802_15_4.dstPanId = 0xffff; } /** * Set Hop Limit */ socket_inet_pcb_set_buffer_hop_limit(inet_pcb, buf, msg_hoplimit); /** * Flow label set */ buf->options.flow_label = inet_pcb->flow_label; /** * Select source address for this message */ if (buf->src_sa.addr_type == ADDR_NONE) { const uint8_t *ipv6_ptr = addr_select_source(cur_interface, buf->dst_sa.address, inet_pcb->addr_preferences); if (!ipv6_ptr) { tr_warn("No address"); ret_val = -3; goto fail; } memcpy(buf->src_sa.address, ipv6_ptr, 16); buf->src_sa.addr_type = ADDR_IPV6; } buf->src_sa.port = inet_pcb->local_port; protocol_push(buf); #ifndef NO_TCP /* TCP jumps back to here */ success: #endif if (flags & NS_MSG_LEGACY0) { return 0; } else { return payload_length; } fail: tr_warn("fail: socket_buffer_sendmsg"); if (buf) { buffer_free(buf); } return ret_val; } void socket_tx_buffer_event_and_free(buffer_t *buf, uint8_t status) { buf = socket_tx_buffer_event(buf, status); if (buf) { buffer_free(buf); } } buffer_t *socket_tx_buffer_event(buffer_t *buf, uint8_t status) { /* TODO here - if not a locally generated packet, and it's a TX_FAIL * (when we finally implement it), should probably generate ICMP * address unreachable. (Kind of needed if address resolution was skipped * and we mapped straight to MAC address). */ if (buf->ack_receive_cb) { buf->ack_receive_cb(buf, status); } if (status == SOCKET_BUSY) { //SOCKET_BUSY shuold not be forward further and switched back orginal behaviour status = SOCKET_TX_FAIL; } /* Suppress events once socket orphaned */ if (!buf->socket || (buf->socket->flags & (SOCKET_FLAG_PENDING | SOCKET_FLAG_CLOSED))) { return buf; } protocol_interface_info_entry_t *cur_interface = buf->interface; socket_event_push(status, buf->socket, cur_interface ? cur_interface->id : -1, buf->session_ptr, buf->payload_length); return buf; } static void mc_group_free(inet_pcb_t *inet_pcb, inet_group_t *mc) { protocol_interface_info_entry_t *cur_interface = protocol_stack_interface_info_get_by_id(mc->interface_id); if (cur_interface) { addr_delete_group(cur_interface, mc->group_addr); } ns_list_remove(&inet_pcb->mc_groups, mc); ns_dyn_mem_free(mc); } /* * validate that socket backlog allows new connection. * Incoming socket needs to be listening socket. * * \return true if incoming connection can proceed * */ bool socket_validate_listen_backlog(const socket_t *socket_ptr) { if (!(socket_ptr->flags & SOCKET_LISTEN_STATE)) { return false; } return ns_list_count(&socket_ptr->u.live.queue) < socket_ptr->listen_backlog; } inet_pcb_t *socket_inet_pcb_free(inet_pcb_t *inet_pcb) { if (inet_pcb) { ns_list_foreach_safe(inet_group_t, mc, &inet_pcb->mc_groups) { mc_group_free(inet_pcb, mc); } ns_dyn_mem_free(inet_pcb); } return NULL; } int8_t socket_inet_pcb_join_group(inet_pcb_t *inet_pcb, int8_t interface_id, const uint8_t group[static 16]) { ns_list_foreach(inet_group_t, mc, &inet_pcb->mc_groups) { if ((interface_id == 0 || mc->interface_id == interface_id) && addr_ipv6_equal(mc->group_addr, group)) { return -3; } } if (interface_id == 0) { ipv6_route_t *route = ipv6_route_choose_next_hop(group, -1, NULL); if (!route) { return -3; } interface_id = route->info.interface_id; } protocol_interface_info_entry_t *cur_interface = protocol_stack_interface_info_get_by_id(interface_id); if (!cur_interface) { return -3; } inet_group_t *mc = ns_dyn_mem_alloc(sizeof * mc); if (!mc) { return -3; } memcpy(mc->group_addr, group, 16); mc->interface_id = interface_id; if (!addr_add_group(cur_interface, group)) { /* This could also happen due to being an implicit group member, eg ff02::1 */ /* In that case, doesn't seem unreasonable to return the same error as "already a member on this socket" */ ns_dyn_mem_free(mc); return -3; } ns_list_add_to_end(&inet_pcb->mc_groups, mc); return 0; } int8_t socket_inet_pcb_leave_group(inet_pcb_t *inet_pcb, int8_t interface_id, const uint8_t group[static 16]) { inet_group_t *mc = NULL; ns_list_foreach(inet_group_t, cur, &inet_pcb->mc_groups) { if ((interface_id == 0 || cur->interface_id == interface_id) && addr_ipv6_equal(cur->group_addr, group)) { mc = cur; break; } } if (!mc) { return -3; } mc_group_free(inet_pcb, mc); return 0; } struct protocol_interface_info_entry *socket_interface_determine(const socket_t *socket_ptr, buffer_t *buf) { protocol_interface_info_entry_t *cur_interface; /* Link or realm-local scope uses default interface id if set (as if dest scope id), else multicast if, else choose 6LoWPAN, else IPv6(Ethernet) */ /* Also for packets addressed to ourself (not needed any more - have loopback routes? */ if (addr_ipv6_scope(buf->dst_sa.address, NULL) <= IPV6_SCOPE_REALM_LOCAL) { if (socket_ptr->default_interface_id != -1) { cur_interface = protocol_stack_interface_info_get_by_id(socket_ptr->default_interface_id); if (cur_interface) { return cur_interface; } } } /* Sockets can specify an interface for multicast packets */ if (addr_is_ipv6_multicast(buf->dst_sa.address) && socket_ptr->inet_pcb->multicast_if > 0) { cur_interface = protocol_stack_interface_info_get_by_id(socket_ptr->inet_pcb->multicast_if); if (cur_interface && ipv6_buffer_route_to(buf, buf->dst_sa.address, cur_interface)) { return cur_interface; } else { return NULL; } } /* For greater-than-realm scope, use default interface if a default interface ID */ /* has been set (e.g. using setsockopt), else try a routing table entry */ if (addr_ipv6_scope(buf->dst_sa.address, NULL) > IPV6_SCOPE_REALM_LOCAL) { if (socket_ptr->default_interface_id != -1) { cur_interface = protocol_stack_interface_info_get_by_id(socket_ptr->default_interface_id); if (cur_interface) { return cur_interface; } else { return NULL; } } if (ipv6_buffer_route(buf)) { return buf->interface; } } /* Realm-local scope or lower now chooses an interface - 6LoWPAN default */ if (addr_ipv6_scope(buf->dst_sa.address, NULL) <= IPV6_SCOPE_REALM_LOCAL) { buf->interface = protocol_stack_interface_info_get(IF_6LoWPAN); if (buf->interface) { return buf->interface; } buf->interface = protocol_stack_interface_info_get(IF_IPV6); if (buf->interface) { return buf->interface; } return NULL; } return NULL; }