/* * Copyright (c) 2023 Basalte bv * * SPDX-License-Identifier: Apache-2.0 */ #include #include LOG_MODULE_DECLARE(net_coap, CONFIG_COAP_LOG_LEVEL); #include #include #include #include #include #include #include #include #if defined(CONFIG_NET_TC_THREAD_COOPERATIVE) /* Lowest priority cooperative thread */ #define THREAD_PRIORITY K_PRIO_COOP(CONFIG_NUM_COOP_PRIORITIES - 1) #else #define THREAD_PRIORITY K_PRIO_PREEMPT(CONFIG_NUM_PREEMPT_PRIORITIES - 1) #endif #define ADDRLEN(sock) \ (((struct sockaddr *)sock)->sa_family == AF_INET ? \ sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) /* Shortened defines */ #define MAX_OPTIONS CONFIG_COAP_SERVER_MESSAGE_OPTIONS #define MAX_PENDINGS CONFIG_COAP_SERVICE_PENDING_MESSAGES #define MAX_OBSERVERS CONFIG_COAP_SERVICE_OBSERVERS #define MAX_POLL_FD CONFIG_ZVFS_POLL_MAX BUILD_ASSERT(CONFIG_ZVFS_POLL_MAX > 0, "CONFIG_ZVFS_POLL_MAX can't be 0"); static K_MUTEX_DEFINE(lock); static int control_socks[2]; #if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC) K_MEM_SLAB_DEFINE_STATIC(pending_data, CONFIG_COAP_SERVER_MESSAGE_SIZE, CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC_BLOCKS, 4); #endif static inline void *coap_server_alloc(size_t len) { #if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC) void *ptr; int ret; if (len > CONFIG_COAP_SERVER_MESSAGE_SIZE) { return NULL; } ret = k_mem_slab_alloc(&pending_data, &ptr, K_NO_WAIT); if (ret < 0) { return NULL; } return ptr; #elif defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_SYSTEM_HEAP) return k_malloc(len); #else ARG_UNUSED(len); return NULL; #endif } static inline void coap_server_free(void *ptr) { #if defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_STATIC) k_mem_slab_free(&pending_data, ptr); #elif defined(CONFIG_COAP_SERVER_PENDING_ALLOCATOR_SYSTEM_HEAP) k_free(ptr); #else ARG_UNUSED(ptr); #endif } static int coap_service_remove_observer(const struct coap_service *service, struct coap_resource *resource, const struct sockaddr *addr, const uint8_t *token, uint8_t tkl) { struct coap_observer *obs; if (tkl > 0 && addr != NULL) { /* Prefer addr+token to find the observer */ obs = coap_find_observer(service->data->observers, MAX_OBSERVERS, addr, token, tkl); } else if (tkl > 0) { /* Then try to find the observer by token */ obs = coap_find_observer_by_token(service->data->observers, MAX_OBSERVERS, token, tkl); } else if (addr != NULL) { obs = coap_find_observer_by_addr(service->data->observers, MAX_OBSERVERS, addr); } else { /* Either a token or an address is required */ return -EINVAL; } if (obs == NULL) { return 0; } if (resource == NULL) { COAP_SERVICE_FOREACH_RESOURCE(service, it) { if (coap_remove_observer(it, obs)) { memset(obs, 0, sizeof(*obs)); return 1; } } } else if (coap_remove_observer(resource, obs)) { memset(obs, 0, sizeof(*obs)); return 1; } return 0; } static int coap_server_process(int sock_fd) { static uint8_t buf[CONFIG_COAP_SERVER_MESSAGE_SIZE]; struct sockaddr client_addr; socklen_t client_addr_len = sizeof(client_addr); struct coap_service *service = NULL; struct coap_packet request; struct coap_pending *pending; struct coap_option options[MAX_OPTIONS] = { 0 }; uint8_t opt_num = MAX_OPTIONS; uint8_t type; ssize_t received; int ret; received = zsock_recvfrom(sock_fd, buf, sizeof(buf), ZSOCK_MSG_DONTWAIT, &client_addr, &client_addr_len); __ASSERT_NO_MSG(received <= sizeof(buf)); if (received < 0) { if (errno == EWOULDBLOCK) { return 0; } LOG_ERR("Failed to process client request (%d)", -errno); return -errno; } ret = coap_packet_parse(&request, buf, received, options, opt_num); if (ret < 0) { LOG_ERR("Failed To parse coap message (%d)", ret); return ret; } (void)k_mutex_lock(&lock, K_FOREVER); /* Find the active service */ COAP_SERVICE_FOREACH(svc) { if (svc->data->sock_fd == sock_fd) { service = svc; break; } } if (service == NULL) { ret = -ENOENT; goto unlock; } type = coap_header_get_type(&request); pending = coap_pending_received(&request, service->data->pending, MAX_PENDINGS); if (pending) { uint8_t token[COAP_TOKEN_MAX_LEN]; uint8_t tkl; switch (type) { case COAP_TYPE_RESET: tkl = coap_header_get_token(&request, token); coap_service_remove_observer(service, NULL, &client_addr, token, tkl); __fallthrough; case COAP_TYPE_ACK: coap_server_free(pending->data); coap_pending_clear(pending); break; default: LOG_WRN("Unexpected pending type %d", type); ret = -EINVAL; goto unlock; } goto unlock; } else if (type == COAP_TYPE_ACK || type == COAP_TYPE_RESET) { LOG_WRN("Unexpected type %d without pending packet", type); ret = -EINVAL; goto unlock; } if (IS_ENABLED(CONFIG_COAP_SERVER_WELL_KNOWN_CORE) && coap_header_get_code(&request) == COAP_METHOD_GET && coap_uri_path_match(COAP_WELL_KNOWN_CORE_PATH, options, opt_num)) { uint8_t well_known_buf[CONFIG_COAP_SERVER_MESSAGE_SIZE]; struct coap_packet response; ret = coap_well_known_core_get_len(service->res_begin, COAP_SERVICE_RESOURCE_COUNT(service), &request, &response, well_known_buf, sizeof(well_known_buf)); if (ret < 0) { LOG_ERR("Failed to build well known core for %s (%d)", service->name, ret); goto unlock; } ret = coap_service_send(service, &response, &client_addr, client_addr_len, NULL); } else { ret = coap_handle_request_len(&request, service->res_begin, COAP_SERVICE_RESOURCE_COUNT(service), options, opt_num, &client_addr, client_addr_len); /* Translate errors to response codes */ switch (ret) { case -ENOENT: ret = COAP_RESPONSE_CODE_NOT_FOUND; break; case -ENOTSUP: ret = COAP_RESPONSE_CODE_BAD_REQUEST; break; case -EPERM: ret = COAP_RESPONSE_CODE_NOT_ALLOWED; break; } /* Shortcut for replying a code without a body */ if (ret > 0 && type == COAP_TYPE_CON) { /* Minimal sized ack buffer */ uint8_t ack_buf[COAP_TOKEN_MAX_LEN + 4U]; struct coap_packet ack; ret = coap_ack_init(&ack, &request, ack_buf, sizeof(ack_buf), (uint8_t)ret); if (ret < 0) { LOG_ERR("Failed to init ACK (%d)", ret); goto unlock; } ret = coap_service_send(service, &ack, &client_addr, client_addr_len, NULL); } } unlock: (void)k_mutex_unlock(&lock); return ret; } static void coap_server_retransmit(void) { struct coap_pending *pending; int64_t remaining; int64_t now = k_uptime_get(); int ret; (void)k_mutex_lock(&lock, K_FOREVER); COAP_SERVICE_FOREACH(service) { if (service->data->sock_fd < 0) { continue; } pending = coap_pending_next_to_expire(service->data->pending, MAX_PENDINGS); if (pending == NULL) { /* No work to be done */ continue; } /* Check if the pending request has expired */ remaining = pending->t0 + pending->timeout - now; if (remaining > 0) { continue; } if (coap_pending_cycle(pending)) { ret = zsock_sendto(service->data->sock_fd, pending->data, pending->len, 0, &pending->addr, ADDRLEN(&pending->addr)); if (ret < 0) { LOG_ERR("Failed to send pending retransmission for %s (%d)", service->name, ret); } __ASSERT_NO_MSG(ret == pending->len); } else { LOG_WRN("Packet retransmission failed for %s", service->name); coap_service_remove_observer(service, NULL, &pending->addr, NULL, 0U); coap_server_free(pending->data); coap_pending_clear(pending); } } (void)k_mutex_unlock(&lock); } static int coap_server_poll_timeout(void) { struct coap_pending *pending; int64_t result = INT64_MAX; int64_t remaining; int64_t now = k_uptime_get(); COAP_SERVICE_FOREACH(svc) { if (svc->data->sock_fd < -1) { continue; } pending = coap_pending_next_to_expire(svc->data->pending, MAX_PENDINGS); if (pending == NULL) { continue; } remaining = pending->t0 + pending->timeout - now; if (result > remaining) { result = remaining; } } if (result == INT64_MAX) { return -1; } return MAX(result, 0); } static void coap_server_update_services(void) { if (zsock_send(control_socks[1], &(char){0}, 1, 0) < 0) { LOG_ERR("Failed to notify server thread (%d)", errno); } } static inline bool coap_service_in_section(const struct coap_service *service) { STRUCT_SECTION_START_EXTERN(coap_service); STRUCT_SECTION_END_EXTERN(coap_service); return STRUCT_SECTION_START(coap_service) <= service && STRUCT_SECTION_END(coap_service) > service; } static inline void coap_service_raise_event(const struct coap_service *service, uint32_t mgmt_event) { #if defined(CONFIG_NET_MGMT_EVENT_INFO) const struct net_event_coap_service net_event = { .service = service, }; net_mgmt_event_notify_with_info(mgmt_event, NULL, (void *)&net_event, sizeof(net_event)); #else ARG_UNUSED(service); net_mgmt_event_notify(mgmt_event, NULL); #endif } int coap_service_start(const struct coap_service *service) { int ret; uint8_t af; socklen_t len; struct sockaddr_storage addr_storage; union { struct sockaddr *addr; struct sockaddr_in *addr4; struct sockaddr_in6 *addr6; } addr_ptrs = { .addr = (struct sockaddr *)&addr_storage, }; if (!coap_service_in_section(service)) { __ASSERT_NO_MSG(false); return -EINVAL; } k_mutex_lock(&lock, K_FOREVER); if (service->data->sock_fd >= 0) { ret = -EALREADY; goto end; } /* set the default address (in6addr_any / INADDR_ANY are all 0) */ addr_storage = (struct sockaddr_storage){0}; if (IS_ENABLED(CONFIG_NET_IPV6) && service->host != NULL && zsock_inet_pton(AF_INET6, service->host, &addr_ptrs.addr6->sin6_addr) == 1) { /* if a literal IPv6 address is provided as the host, use IPv6 */ af = AF_INET6; len = sizeof(struct sockaddr_in6); addr_ptrs.addr6->sin6_family = AF_INET6; addr_ptrs.addr6->sin6_port = htons(*service->port); } else if (IS_ENABLED(CONFIG_NET_IPV4) && service->host != NULL && zsock_inet_pton(AF_INET, service->host, &addr_ptrs.addr4->sin_addr) == 1) { /* if a literal IPv4 address is provided as the host, use IPv4 */ af = AF_INET; len = sizeof(struct sockaddr_in); addr_ptrs.addr4->sin_family = AF_INET; addr_ptrs.addr4->sin_port = htons(*service->port); } else if (IS_ENABLED(CONFIG_NET_IPV6)) { /* prefer IPv6 if both IPv6 and IPv4 are supported */ af = AF_INET6; len = sizeof(struct sockaddr_in6); addr_ptrs.addr6->sin6_family = AF_INET6; addr_ptrs.addr6->sin6_port = htons(*service->port); } else if (IS_ENABLED(CONFIG_NET_IPV4)) { af = AF_INET; len = sizeof(struct sockaddr_in); addr_ptrs.addr4->sin_family = AF_INET; addr_ptrs.addr4->sin_port = htons(*service->port); } else { ret = -ENOTSUP; goto end; } service->data->sock_fd = zsock_socket(af, SOCK_DGRAM, IPPROTO_UDP); if (service->data->sock_fd < 0) { ret = -errno; goto end; } ret = zsock_fcntl(service->data->sock_fd, F_SETFL, O_NONBLOCK); if (ret < 0) { ret = -errno; goto close; } ret = zsock_bind(service->data->sock_fd, addr_ptrs.addr, len); if (ret < 0) { ret = -errno; goto close; } if (*service->port == 0) { /* ephemeral port - read back the port number */ len = sizeof(addr_storage); ret = zsock_getsockname(service->data->sock_fd, addr_ptrs.addr, &len); if (ret < 0) { goto close; } if (af == AF_INET6) { *service->port = addr_ptrs.addr6->sin6_port; } else { *service->port = addr_ptrs.addr4->sin_port; } } end: k_mutex_unlock(&lock); coap_server_update_services(); coap_service_raise_event(service, NET_EVENT_COAP_SERVICE_STARTED); return ret; close: (void)zsock_close(service->data->sock_fd); service->data->sock_fd = -1; k_mutex_unlock(&lock); return ret; } int coap_service_stop(const struct coap_service *service) { int ret; if (!coap_service_in_section(service)) { __ASSERT_NO_MSG(false); return -EINVAL; } k_mutex_lock(&lock, K_FOREVER); if (service->data->sock_fd < 0) { k_mutex_unlock(&lock); return -EALREADY; } /* Closing a socket will trigger a poll event */ ret = zsock_close(service->data->sock_fd); service->data->sock_fd = -1; k_mutex_unlock(&lock); coap_service_raise_event(service, NET_EVENT_COAP_SERVICE_STOPPED); return ret; } int coap_service_is_running(const struct coap_service *service) { int ret; if (!coap_service_in_section(service)) { __ASSERT_NO_MSG(false); return -EINVAL; } k_mutex_lock(&lock, K_FOREVER); ret = (service->data->sock_fd < 0) ? 0 : 1; k_mutex_unlock(&lock); return ret; } int coap_service_send(const struct coap_service *service, const struct coap_packet *cpkt, const struct sockaddr *addr, socklen_t addr_len, const struct coap_transmission_parameters *params) { int ret; if (!coap_service_in_section(service)) { __ASSERT_NO_MSG(false); return -EINVAL; } (void)k_mutex_lock(&lock, K_FOREVER); if (service->data->sock_fd < 0) { (void)k_mutex_unlock(&lock); return -EBADF; } /* * Check if we should start with retransmits, if creating a pending message fails we still * try to send. */ if (coap_header_get_type(cpkt) == COAP_TYPE_CON) { struct coap_pending *pending = coap_pending_next_unused(service->data->pending, MAX_PENDINGS); if (pending == NULL) { LOG_WRN("No pending message available for %s", service->name); goto send; } ret = coap_pending_init(pending, cpkt, addr, params); if (ret < 0) { LOG_WRN("Failed to init pending message for %s (%d)", service->name, ret); goto send; } /* Replace tracked data with our allocated copy */ pending->data = coap_server_alloc(pending->len); if (pending->data == NULL) { LOG_WRN("Failed to allocate pending message data for %s", service->name); coap_pending_clear(pending); goto send; } memcpy(pending->data, cpkt->data, pending->len); coap_pending_cycle(pending); /* Trigger event in receive loop to schedule retransmit */ coap_server_update_services(); } send: (void)k_mutex_unlock(&lock); ret = zsock_sendto(service->data->sock_fd, cpkt->data, cpkt->offset, 0, addr, addr_len); if (ret < 0) { LOG_ERR("Failed to send CoAP message (%d)", ret); return ret; } __ASSERT_NO_MSG(ret == cpkt->offset); return 0; } int coap_resource_send(const struct coap_resource *resource, const struct coap_packet *cpkt, const struct sockaddr *addr, socklen_t addr_len, const struct coap_transmission_parameters *params) { /* Find owning service */ COAP_SERVICE_FOREACH(svc) { if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) { return coap_service_send(svc, cpkt, addr, addr_len, params); } } return -ENOENT; } int coap_resource_parse_observe(struct coap_resource *resource, const struct coap_packet *request, const struct sockaddr *addr) { const struct coap_service *service = NULL; int ret; uint8_t token[COAP_TOKEN_MAX_LEN]; uint8_t tkl; if (!coap_packet_is_request(request)) { return -EINVAL; } ret = coap_get_option_int(request, COAP_OPTION_OBSERVE); if (ret < 0) { return ret; } /* Find owning service */ COAP_SERVICE_FOREACH(svc) { if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) { service = svc; break; } } if (service == NULL) { return -ENOENT; } tkl = coap_header_get_token(request, token); if (tkl == 0) { return -EINVAL; } (void)k_mutex_lock(&lock, K_FOREVER); if (ret == 0) { struct coap_observer *observer; /* RFC7641 section 4.1 - Check if the current observer already exists */ observer = coap_find_observer(service->data->observers, MAX_OBSERVERS, addr, token, tkl); if (observer != NULL) { /* Client refresh */ goto unlock; } /* New client */ observer = coap_observer_next_unused(service->data->observers, MAX_OBSERVERS); if (observer == NULL) { ret = -ENOMEM; goto unlock; } coap_observer_init(observer, request, addr); coap_register_observer(resource, observer); } else if (ret == 1) { ret = coap_service_remove_observer(service, resource, addr, token, tkl); if (ret < 0) { LOG_WRN("Failed to remove observer (%d)", ret); } } unlock: (void)k_mutex_unlock(&lock); return ret; } static int coap_resource_remove_observer(struct coap_resource *resource, const struct sockaddr *addr, const uint8_t *token, uint8_t token_len) { const struct coap_service *service = NULL; int ret; /* Find owning service */ COAP_SERVICE_FOREACH(svc) { if (COAP_SERVICE_HAS_RESOURCE(svc, resource)) { service = svc; break; } } if (service == NULL) { return -ENOENT; } (void)k_mutex_lock(&lock, K_FOREVER); ret = coap_service_remove_observer(service, resource, addr, token, token_len); (void)k_mutex_unlock(&lock); if (ret == 1) { /* An observer was found and removed */ return 0; } else if (ret == 0) { /* No matching observer found */ return -ENOENT; } /* An error occurred */ return ret; } int coap_resource_remove_observer_by_addr(struct coap_resource *resource, const struct sockaddr *addr) { return coap_resource_remove_observer(resource, addr, NULL, 0); } int coap_resource_remove_observer_by_token(struct coap_resource *resource, const uint8_t *token, uint8_t token_len) { return coap_resource_remove_observer(resource, NULL, token, token_len); } static void coap_server_thread(void *p1, void *p2, void *p3) { struct zsock_pollfd sock_fds[MAX_POLL_FD]; int sock_nfds; int ret; ARG_UNUSED(p1); ARG_UNUSED(p2); ARG_UNUSED(p3); /* Create a socket pair to wake zsock_poll */ ret = zsock_socketpair(AF_UNIX, SOCK_STREAM, 0, control_socks); if (ret < 0) { LOG_ERR("Failed to create socket pair (%d)", ret); return; } for (int i = 0; i < 2; ++i) { ret = zsock_fcntl(control_socks[i], F_SETFL, O_NONBLOCK); if (ret < 0) { zsock_close(control_socks[0]); zsock_close(control_socks[1]); LOG_ERR("Failed to set socket pair [%d] non-blocking (%d)", i, ret); return; } } COAP_SERVICE_FOREACH(svc) { if (svc->flags & COAP_SERVICE_AUTOSTART) { ret = coap_service_start(svc); if (ret < 0) { LOG_ERR("Failed to autostart service %s (%d)", svc->name, ret); } } } while (true) { sock_nfds = 0; COAP_SERVICE_FOREACH(svc) { if (svc->data->sock_fd < 0) { continue; } if (sock_nfds >= MAX_POLL_FD) { LOG_ERR("Maximum active CoAP services reached (%d), " "increase CONFIG_ZVFS_POLL_MAX to support more.", MAX_POLL_FD); break; } sock_fds[sock_nfds].fd = svc->data->sock_fd; sock_fds[sock_nfds].events = ZSOCK_POLLIN; sock_fds[sock_nfds].revents = 0; sock_nfds++; } /* Add socket pair FD to allow wake up */ if (sock_nfds < MAX_POLL_FD) { sock_fds[sock_nfds].fd = control_socks[0]; sock_fds[sock_nfds].events = ZSOCK_POLLIN; sock_fds[sock_nfds].revents = 0; sock_nfds++; } __ASSERT_NO_MSG(sock_nfds > 0); ret = zsock_poll(sock_fds, sock_nfds, coap_server_poll_timeout()); if (ret < 0) { LOG_ERR("Poll error (%d)", -errno); k_msleep(10); } for (int i = 0; i < sock_nfds; ++i) { /* Check the wake up event */ if (sock_fds[i].fd == control_socks[0] && sock_fds[i].revents & ZSOCK_POLLIN) { char tmp; zsock_recv(sock_fds[i].fd, &tmp, 1, 0); continue; } /* Check if socket can receive/was closed first */ if (sock_fds[i].revents & ZSOCK_POLLIN) { coap_server_process(sock_fds[i].fd); continue; } if (sock_fds[i].revents & ZSOCK_POLLERR) { LOG_ERR("Poll error on %d", sock_fds[i].fd); } if (sock_fds[i].revents & ZSOCK_POLLHUP) { LOG_ERR("Poll hup on %d", sock_fds[i].fd); } if (sock_fds[i].revents & ZSOCK_POLLNVAL) { LOG_ERR("Poll invalid on %d", sock_fds[i].fd); } } /* Process retransmits */ coap_server_retransmit(); } } K_THREAD_DEFINE(coap_server_id, CONFIG_COAP_SERVER_STACK_SIZE, coap_server_thread, NULL, NULL, NULL, THREAD_PRIORITY, 0, 0);