You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
8.2 KiB
401 lines
8.2 KiB
/* |
|
* Copyright (c) 2019 Intel Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <stdbool.h> |
|
#include <fcntl.h> |
|
|
|
#include <logging/log.h> |
|
LOG_MODULE_REGISTER(net_sock_mgmt, CONFIG_NET_SOCKETS_LOG_LEVEL); |
|
|
|
#include <kernel.h> |
|
#include <sys/util.h> |
|
#include <net/socket.h> |
|
#include <syscall_handler.h> |
|
#include <sys/fdtable.h> |
|
#include <net/socket_net_mgmt.h> |
|
#include <net/ethernet_mgmt.h> |
|
|
|
#include "sockets_internal.h" |
|
#include "net_private.h" |
|
|
|
#define MSG_ALLOC_TIMEOUT K_MSEC(100) |
|
|
|
__net_socket struct net_mgmt_socket { |
|
/* Network interface related to this socket */ |
|
struct net_if *iface; |
|
|
|
/* A way to separate different sockets that listen same events */ |
|
uintptr_t pid; |
|
|
|
/* net_mgmt mask */ |
|
u32_t mask; |
|
|
|
/* Message allocation timeout */ |
|
k_timeout_t alloc_timeout; |
|
|
|
/* net_mgmt event timeout */ |
|
k_timeout_t wait_timeout; |
|
|
|
/* Socket protocol */ |
|
int proto; |
|
|
|
/* Is this entry in use (true) or not (false) */ |
|
u8_t is_in_use : 1; |
|
}; |
|
|
|
static struct net_mgmt_socket |
|
mgmt_sockets[CONFIG_NET_SOCKETS_NET_MGMT_MAX_LISTENERS]; |
|
|
|
static const struct socket_op_vtable net_mgmt_sock_fd_op_vtable; |
|
|
|
int znet_mgmt_socket(int family, int type, int proto) |
|
{ |
|
struct net_mgmt_socket *mgmt = NULL; |
|
int fd, i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(mgmt_sockets); i++) { |
|
if (mgmt_sockets[i].is_in_use) { |
|
continue; |
|
} |
|
|
|
mgmt = &mgmt_sockets[i]; |
|
} |
|
|
|
if (mgmt == NULL) { |
|
errno = ENOENT; |
|
return -1; |
|
} |
|
|
|
fd = z_reserve_fd(); |
|
if (fd < 0) { |
|
errno = ENOSPC; |
|
return -1; |
|
} |
|
|
|
mgmt->is_in_use = true; |
|
mgmt->proto = proto; |
|
mgmt->alloc_timeout = MSG_ALLOC_TIMEOUT; |
|
mgmt->wait_timeout = K_FOREVER; |
|
|
|
#if defined(CONFIG_USERSPACE) |
|
/* Set net context object as initialized and grant access to the |
|
* calling thread (and only the calling thread) |
|
*/ |
|
z_object_recycle(mgmt); |
|
#endif |
|
|
|
z_finalize_fd(fd, mgmt, |
|
(const struct fd_op_vtable *)&net_mgmt_sock_fd_op_vtable); |
|
|
|
return fd; |
|
} |
|
|
|
static int znet_mgmt_bind(struct net_mgmt_socket *mgmt, |
|
const struct sockaddr *addr, |
|
socklen_t addrlen) |
|
{ |
|
struct sockaddr_nm *nm_addr = (struct sockaddr_nm *)addr; |
|
|
|
if (addrlen != sizeof(struct sockaddr_nm)) { |
|
return -EINVAL; |
|
} |
|
|
|
if (nm_addr->nm_ifindex) { |
|
mgmt->iface = net_if_get_by_index(nm_addr->nm_ifindex); |
|
if (!mgmt->iface) { |
|
errno = ENOENT; |
|
return -1; |
|
} |
|
} else { |
|
mgmt->iface = NULL; |
|
} |
|
|
|
mgmt->pid = nm_addr->nm_pid; |
|
|
|
if (mgmt->proto == NET_MGMT_EVENT_PROTO) { |
|
mgmt->mask = nm_addr->nm_mask; |
|
|
|
if (mgmt->iface) { |
|
mgmt->mask |= NET_MGMT_IFACE_BIT; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
ssize_t znet_mgmt_sendto(struct net_mgmt_socket *mgmt, |
|
const void *buf, size_t len, |
|
int flags, const struct sockaddr *dest_addr, |
|
socklen_t addrlen) |
|
{ |
|
if (mgmt->proto == NET_MGMT_EVENT_PROTO) { |
|
/* For net_mgmt events, we only listen and never send */ |
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
|
|
/* Add handling of other network management operations here when |
|
* needed. |
|
*/ |
|
|
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
static ssize_t znet_mgmt_recvfrom(struct net_mgmt_socket *mgmt, void *buf, |
|
size_t max_len, int flags, |
|
struct sockaddr *src_addr, |
|
socklen_t *addrlen) |
|
{ |
|
struct sockaddr_nm *nm_addr = (struct sockaddr_nm *)src_addr; |
|
k_timeout_t timeout = mgmt->wait_timeout; |
|
u32_t raised_event = 0; |
|
u8_t *copy_to = buf; |
|
struct net_mgmt_msghdr hdr; |
|
struct net_if *iface; |
|
const u8_t *info; |
|
size_t info_len; |
|
int ret; |
|
|
|
if (flags & ZSOCK_MSG_DONTWAIT) { |
|
timeout = K_NO_WAIT; |
|
} |
|
|
|
again: |
|
if (mgmt->iface == NULL) { |
|
ret = net_mgmt_event_wait(mgmt->mask, &raised_event, |
|
&iface, (const void **)&info, |
|
&info_len, timeout); |
|
} else { |
|
ret = net_mgmt_event_wait_on_iface(mgmt->iface, |
|
mgmt->mask, |
|
&raised_event, |
|
(const void **)&info, |
|
&info_len, |
|
timeout); |
|
iface = mgmt->iface; |
|
} |
|
|
|
if (ret == -ETIMEDOUT) { |
|
errno = EAGAIN; |
|
return -1; |
|
} |
|
|
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
if ((mgmt->mask & raised_event) != raised_event) { |
|
if (K_TIMEOUT_EQ(timeout, K_FOREVER)) { |
|
goto again; |
|
} |
|
|
|
errno = EAGAIN; |
|
return -1; |
|
} |
|
|
|
if (nm_addr) { |
|
if (iface) { |
|
nm_addr->nm_ifindex = net_if_get_by_iface(iface); |
|
} else { |
|
nm_addr->nm_ifindex = 0; |
|
} |
|
|
|
nm_addr->nm_pid = mgmt->pid; |
|
nm_addr->nm_family = AF_NET_MGMT; |
|
nm_addr->nm_mask = raised_event; |
|
} |
|
|
|
if (info) { |
|
ret = info_len + sizeof(hdr); |
|
ret = MIN(max_len, ret); |
|
memcpy(©_to[sizeof(hdr)], info, ret); |
|
} else { |
|
ret = 0; |
|
} |
|
|
|
hdr.nm_msg_version = NET_MGMT_SOCKET_VERSION_1; |
|
hdr.nm_msg_len = ret; |
|
|
|
memcpy(copy_to, &hdr, sizeof(hdr)); |
|
|
|
return ret; |
|
} |
|
|
|
static int znet_mgmt_getsockopt(struct net_mgmt_socket *mgmt, int level, |
|
int optname, void *optval, socklen_t *optlen) |
|
{ |
|
if (level != SOL_NET_MGMT_RAW || !optval || !optlen) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
if (mgmt->iface == NULL) { |
|
errno = ENOENT; |
|
return -1; |
|
} |
|
|
|
if (IS_ENABLED(CONFIG_NET_L2_ETHERNET_MGMT)) { |
|
if (optname == NET_REQUEST_ETHERNET_GET_QAV_PARAM) { |
|
int ret; |
|
|
|
ret = net_mgmt(NET_REQUEST_ETHERNET_GET_QAV_PARAM, |
|
mgmt->iface, (void *)optval, *optlen); |
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
return 0; |
|
|
|
} else { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
} |
|
|
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
|
|
static int znet_mgmt_setsockopt(struct net_mgmt_socket *mgmt, int level, |
|
int optname, const void *optval, |
|
socklen_t optlen) |
|
{ |
|
if (level != SOL_NET_MGMT_RAW || !optval || !optlen) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
if (mgmt->iface == NULL) { |
|
errno = ENOENT; |
|
return -1; |
|
} |
|
|
|
if (IS_ENABLED(CONFIG_NET_L2_ETHERNET_MGMT)) { |
|
if (optname == NET_REQUEST_ETHERNET_SET_QAV_PARAM) { |
|
int ret; |
|
|
|
ret = net_mgmt(NET_REQUEST_ETHERNET_SET_QAV_PARAM, |
|
mgmt->iface, (void *)optval, optlen); |
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} else { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
} |
|
|
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
|
|
static ssize_t net_mgmt_sock_read(void *obj, void *buffer, size_t count) |
|
{ |
|
return znet_mgmt_recvfrom(obj, buffer, count, 0, NULL, 0); |
|
} |
|
|
|
static ssize_t net_mgmt_sock_write(void *obj, const void *buffer, |
|
size_t count) |
|
{ |
|
return znet_mgmt_sendto(obj, buffer, count, 0, NULL, 0); |
|
} |
|
|
|
static int net_mgmt_sock_ioctl(void *obj, unsigned int request, |
|
va_list args) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int net_mgmt_sock_bind(void *obj, const struct sockaddr *addr, |
|
socklen_t addrlen) |
|
{ |
|
return znet_mgmt_bind(obj, addr, addrlen); |
|
} |
|
|
|
/* The connect() function is not needed */ |
|
static int net_mgmt_sock_connect(void *obj, const struct sockaddr *addr, |
|
socklen_t addrlen) |
|
{ |
|
return 0; |
|
} |
|
|
|
/* |
|
* The listen() and accept() functions are without any functionality. |
|
*/ |
|
static int net_mgmt_sock_listen(void *obj, int backlog) |
|
{ |
|
return 0; |
|
} |
|
|
|
static int net_mgmt_sock_accept(void *obj, struct sockaddr *addr, |
|
socklen_t *addrlen) |
|
{ |
|
return 0; |
|
} |
|
|
|
static ssize_t net_mgmt_sock_sendto(void *obj, const void *buf, |
|
size_t len, int flags, |
|
const struct sockaddr *dest_addr, |
|
socklen_t addrlen) |
|
{ |
|
return znet_mgmt_sendto(obj, buf, len, flags, dest_addr, addrlen); |
|
} |
|
|
|
static ssize_t net_mgmt_sock_recvfrom(void *obj, void *buf, |
|
size_t max_len, int flags, |
|
struct sockaddr *src_addr, |
|
socklen_t *addrlen) |
|
{ |
|
return znet_mgmt_recvfrom(obj, buf, max_len, flags, |
|
src_addr, addrlen); |
|
} |
|
|
|
static int net_mgmt_sock_getsockopt(void *obj, int level, int optname, |
|
void *optval, socklen_t *optlen) |
|
{ |
|
return znet_mgmt_getsockopt(obj, level, optname, optval, optlen); |
|
} |
|
|
|
static int net_mgmt_sock_setsockopt(void *obj, int level, int optname, |
|
const void *optval, socklen_t optlen) |
|
{ |
|
return znet_mgmt_setsockopt(obj, level, optname, optval, optlen); |
|
} |
|
|
|
static const struct socket_op_vtable net_mgmt_sock_fd_op_vtable = { |
|
.fd_vtable = { |
|
.read = net_mgmt_sock_read, |
|
.write = net_mgmt_sock_write, |
|
.ioctl = net_mgmt_sock_ioctl, |
|
}, |
|
.bind = net_mgmt_sock_bind, |
|
.connect = net_mgmt_sock_connect, |
|
.listen = net_mgmt_sock_listen, |
|
.accept = net_mgmt_sock_accept, |
|
.sendto = net_mgmt_sock_sendto, |
|
.recvfrom = net_mgmt_sock_recvfrom, |
|
.getsockopt = net_mgmt_sock_getsockopt, |
|
.setsockopt = net_mgmt_sock_setsockopt, |
|
}; |
|
|
|
static bool net_mgmt_is_supported(int family, int type, int proto) |
|
{ |
|
if ((type != SOCK_RAW && type != SOCK_DGRAM) || |
|
(proto != NET_MGMT_EVENT_PROTO)) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
NET_SOCKET_REGISTER(af_net_mgmt, AF_NET_MGMT, net_mgmt_is_supported, |
|
znet_mgmt_socket);
|
|
|