zephyr/subsys/net/lib/sockets/sockets_packet.c
Florian Grandel 9695a022f4 net: core: clean up inbound packet handling
The net_core:process_data() and connection:net_conn_input() methods are
the central network packet reception pipeline which:

1) guide network packets through all network layers,
2) decode, validate and filter packages along the way and
3) distribute packages to connections/sockets on all layers.

This code seems to have grown complex and rather cluttered over time as
all protocols, layers and socket implementations meet there in one single
place.

The code also reveals its origin as a pure IP stack which makes it hard
to introduce non-IP protocols and their supporting socket infrastructure
in a modularized way.

For an outside contributor it seems almost impossible to add another
protocol, protocol layer, filter rule or socket implementation without
breaking things.

This change doesn't try to solve all issues at once. It focuses
exclusively on aspects that maintain backwards compatibility:

* Improve modularization and encapsulation on implementation level by
disentangling code that mixes up layers, protocols and socket
implementations.

* Make IP just one protocol among others by removing assymmetry in
protocol handling logic and introduce preprocessor markup so that
IP-specific code can be eliminated by the preprocessor if not needed.

* Use preprocessor markup to delineate hook points for future
modularization or expansion without introducing structural changes (as
this would almost certainly break the API).

* Reduce cyclomatic complexity, use positive rather than negative logic,
improve variable naming, replace if/elseif/else blocks with switches,
reduce variable span, introduce inline comments where code does not
speak for itself, etc. as much as possible to make the code overall
more human-friendly.

Background: These are preparative steps for the introduction of IEEE
802.15.RAW sockets, DGRAM sockets and sockets bound to PAN IDs and device
addresses similar to what the Linux kernel does.

Signed-off-by: Florian Grandel <jerico.dev@gmail.com>
2022-09-05 14:35:17 +00:00

493 lines
12 KiB
C

/*
* Copyright (c) 2019 Intel Corporation
* Copyright (c) 2021 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <fcntl.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_sock_packet, CONFIG_NET_SOCKETS_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/drivers/entropy.h>
#include <zephyr/sys/util.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/ethernet.h>
#include <zephyr/syscall_handler.h>
#include <zephyr/sys/fdtable.h>
#include "../../ip/net_stats.h"
#include "sockets_internal.h"
extern const struct socket_op_vtable sock_fd_op_vtable;
static const struct socket_op_vtable packet_sock_fd_op_vtable;
static inline int k_fifo_wait_non_empty(struct k_fifo *fifo,
k_timeout_t timeout)
{
struct k_poll_event events[] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY, fifo),
};
return k_poll(events, ARRAY_SIZE(events), timeout);
}
static int zpacket_socket(int family, int type, int proto)
{
struct net_context *ctx;
int fd;
int ret;
fd = z_reserve_fd();
if (fd < 0) {
return -1;
}
if (proto == 0) {
if (type == SOCK_RAW) {
proto = IPPROTO_RAW;
}
}
ret = net_context_get(family, type, proto, &ctx);
if (ret < 0) {
z_free_fd(fd);
errno = -ret;
return -1;
}
/* Initialize user_data, all other calls will preserve it */
ctx->user_data = NULL;
/* recv_q and accept_q are in union */
k_fifo_init(&ctx->recv_q);
z_finalize_fd(fd, ctx,
(const struct fd_op_vtable *)&packet_sock_fd_op_vtable);
return fd;
}
static void zpacket_received_cb(struct net_context *ctx,
struct net_pkt *pkt,
union net_ip_header *ip_hdr,
union net_proto_header *proto_hdr,
int status,
void *user_data)
{
NET_DBG("ctx=%p, pkt=%p, st=%d, user_data=%p", ctx, pkt, status,
user_data);
/* if pkt is NULL, EOF */
if (!pkt) {
struct net_pkt *last_pkt = k_fifo_peek_tail(&ctx->recv_q);
if (!last_pkt) {
/* If there're no packets in the queue, recv() may
* be blocked waiting on it to become non-empty,
* so cancel that wait.
*/
sock_set_eof(ctx);
k_fifo_cancel_wait(&ctx->recv_q);
NET_DBG("Marked socket %p as peer-closed", ctx);
} else {
net_pkt_set_eof(last_pkt, true);
NET_DBG("Set EOF flag on pkt %p", ctx);
}
return;
}
/* Normal packet */
net_pkt_set_eof(pkt, false);
k_fifo_put(&ctx->recv_q, pkt);
}
static int zpacket_bind_ctx(struct net_context *ctx,
const struct sockaddr *addr,
socklen_t addrlen)
{
int ret = 0;
ret = net_context_bind(ctx, addr, addrlen);
if (ret < 0) {
errno = -ret;
return -1;
}
/* For packet socket, we expect to receive packets after call
* to bind().
*/
ret = net_context_recv(ctx, zpacket_received_cb, K_NO_WAIT,
ctx->user_data);
if (ret < 0) {
errno = -ret;
return -1;
}
return 0;
}
static void zpacket_set_eth_pkttype(struct net_if *iface,
struct sockaddr_ll *addr,
struct net_linkaddr *lladdr)
{
if (net_eth_is_addr_broadcast((struct net_eth_addr *)lladdr->addr)) {
addr->sll_pkttype = PACKET_BROADCAST;
} else if (net_eth_is_addr_multicast(
(struct net_eth_addr *)lladdr->addr)) {
addr->sll_pkttype = PACKET_MULTICAST;
} else if (!net_linkaddr_cmp(net_if_get_link_addr(iface), lladdr)) {
addr->sll_pkttype = PACKET_HOST;
} else {
addr->sll_pkttype = PACKET_OTHERHOST;
}
}
static void zpacket_set_source_addr(struct net_context *ctx,
struct net_pkt *pkt,
struct sockaddr *src_addr,
socklen_t *addrlen)
{
struct sockaddr_ll addr = {0};
struct net_if *iface = net_context_get_iface(ctx);
if (iface == NULL) {
return;
}
addr.sll_family = AF_PACKET;
addr.sll_ifindex = net_if_get_by_iface(iface);
if (net_pkt_is_l2_processed(pkt)) {
/* L2 has already processed the packet - can copy information
* directly from the net_pkt structure
*/
addr.sll_halen = pkt->lladdr_src.len;
memcpy(addr.sll_addr, pkt->lladdr_src.addr,
MIN(sizeof(addr.sll_addr), pkt->lladdr_src.len));
addr.sll_protocol = net_pkt_ll_proto_type(pkt);
if (net_if_get_link_addr(iface)->type == NET_LINK_ETHERNET) {
addr.sll_hatype = ARPHRD_ETHER;
zpacket_set_eth_pkttype(iface, &addr,
net_pkt_lladdr_dst(pkt));
}
} else if (net_if_get_link_addr(iface)->type == NET_LINK_ETHERNET) {
/* Need to extract information from the L2 header. Only
* Ethernet L2 supported currently.
*/
struct net_eth_hdr *hdr;
struct net_linkaddr dst_addr;
struct net_pkt_cursor cur;
net_pkt_cursor_backup(pkt, &cur);
net_pkt_cursor_init(pkt);
hdr = NET_ETH_HDR(pkt);
if (hdr == NULL ||
pkt->buffer->len < sizeof(struct net_eth_hdr)) {
net_pkt_cursor_restore(pkt, &cur);
return;
}
addr.sll_halen = sizeof(struct net_eth_addr);
memcpy(addr.sll_addr, hdr->src.addr,
sizeof(struct net_eth_addr));
addr.sll_protocol = ntohs(hdr->type);
addr.sll_hatype = ARPHRD_ETHER;
dst_addr.addr = hdr->dst.addr;
dst_addr.len = sizeof(struct net_eth_addr);
dst_addr.type = NET_LINK_ETHERNET;
zpacket_set_eth_pkttype(iface, &addr, &dst_addr);
net_pkt_cursor_restore(pkt, &cur);
}
/* Copy the result sockaddr_ll structure into provided buffer. If the
* buffer is smaller than the structure size, it will be truncated.
*/
memcpy(src_addr, &addr, MIN(sizeof(struct sockaddr_ll), *addrlen));
*addrlen = sizeof(struct sockaddr_ll);
}
ssize_t zpacket_sendto_ctx(struct net_context *ctx, const void *buf, size_t len,
int flags, const struct sockaddr *dest_addr,
socklen_t addrlen)
{
k_timeout_t timeout = K_FOREVER;
int status;
if (!dest_addr) {
errno = EDESTADDRREQ;
return -1;
}
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
} else {
net_context_get_option(ctx, NET_OPT_SNDTIMEO, &timeout, NULL);
}
/* Register the callback before sending in order to receive the response
* from the peer.
*/
status = net_context_recv(ctx, zpacket_received_cb, K_NO_WAIT,
ctx->user_data);
if (status < 0) {
errno = -status;
return -1;
}
status = net_context_sendto(ctx, buf, len, dest_addr, addrlen,
NULL, timeout, ctx->user_data);
if (status < 0) {
errno = -status;
return -1;
}
return status;
}
ssize_t zpacket_sendmsg_ctx(struct net_context *ctx, const struct msghdr *msg,
int flags)
{
k_timeout_t timeout = K_FOREVER;
int status;
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
} else {
net_context_get_option(ctx, NET_OPT_SNDTIMEO, &timeout, NULL);
}
status = net_context_sendmsg(ctx, msg, flags, NULL, timeout, NULL);
if (status < 0) {
errno = -status;
return -1;
}
return status;
}
ssize_t zpacket_recvfrom_ctx(struct net_context *ctx, void *buf, size_t max_len,
int flags, struct sockaddr *src_addr,
socklen_t *addrlen)
{
size_t recv_len = 0;
k_timeout_t timeout = K_FOREVER;
struct net_pkt *pkt;
if ((flags & ZSOCK_MSG_DONTWAIT) || sock_is_nonblock(ctx)) {
timeout = K_NO_WAIT;
} else {
net_context_get_option(ctx, NET_OPT_RCVTIMEO, &timeout, NULL);
}
if (flags & ZSOCK_MSG_PEEK) {
int res;
res = k_fifo_wait_non_empty(&ctx->recv_q, timeout);
/* EAGAIN when timeout expired, EINTR when cancelled */
if (res && res != -EAGAIN && res != -EINTR) {
errno = -res;
return -1;
}
pkt = k_fifo_peek_head(&ctx->recv_q);
} else {
pkt = k_fifo_get(&ctx->recv_q, timeout);
}
if (!pkt) {
errno = EAGAIN;
return -1;
}
/* We do not handle any headers here,
* just pass the whole packet to caller.
*/
recv_len = net_pkt_get_len(pkt);
if (recv_len > max_len) {
recv_len = max_len;
}
if (net_pkt_read(pkt, buf, recv_len)) {
errno = ENOBUFS;
return -1;
}
if (src_addr && addrlen) {
zpacket_set_source_addr(ctx, pkt, src_addr, addrlen);
}
if (IS_ENABLED(CONFIG_NET_PKT_RXTIME_STATS) &&
!(flags & ZSOCK_MSG_PEEK)) {
net_socket_update_tc_rx_time(pkt, k_cycle_get_32());
}
if (!(flags & ZSOCK_MSG_PEEK)) {
net_pkt_unref(pkt);
} else {
net_pkt_cursor_init(pkt);
}
return recv_len;
}
int zpacket_getsockopt_ctx(struct net_context *ctx, int level, int optname,
void *optval, socklen_t *optlen)
{
if (!optval || !optlen) {
errno = EINVAL;
return -1;
}
return sock_fd_op_vtable.getsockopt(ctx, level, optname,
optval, optlen);
}
int zpacket_setsockopt_ctx(struct net_context *ctx, int level, int optname,
const void *optval, socklen_t optlen)
{
return sock_fd_op_vtable.setsockopt(ctx, level, optname,
optval, optlen);
}
static ssize_t packet_sock_read_vmeth(void *obj, void *buffer, size_t count)
{
return zpacket_recvfrom_ctx(obj, buffer, count, 0, NULL, 0);
}
static ssize_t packet_sock_write_vmeth(void *obj, const void *buffer,
size_t count)
{
return zpacket_sendto_ctx(obj, buffer, count, 0, NULL, 0);
}
static int packet_sock_ioctl_vmeth(void *obj, unsigned int request,
va_list args)
{
return sock_fd_op_vtable.fd_vtable.ioctl(obj, request, args);
}
/*
* TODO: A packet socket can be bound to a network device using SO_BINDTODEVICE.
*/
static int packet_sock_bind_vmeth(void *obj, const struct sockaddr *addr,
socklen_t addrlen)
{
return zpacket_bind_ctx(obj, addr, addrlen);
}
/* The connect() function is no longer necessary. */
static int packet_sock_connect_vmeth(void *obj, const struct sockaddr *addr,
socklen_t addrlen)
{
return -EOPNOTSUPP;
}
/*
* The listen() and accept() functions are without any functionality,
* since the client-Server-Semantic is no longer present.
* When we use packet sockets we are sending unconnected packets.
*/
static int packet_sock_listen_vmeth(void *obj, int backlog)
{
return -EOPNOTSUPP;
}
static int packet_sock_accept_vmeth(void *obj, struct sockaddr *addr,
socklen_t *addrlen)
{
return -EOPNOTSUPP;
}
static ssize_t packet_sock_sendto_vmeth(void *obj, const void *buf, size_t len,
int flags,
const struct sockaddr *dest_addr,
socklen_t addrlen)
{
return zpacket_sendto_ctx(obj, buf, len, flags, dest_addr, addrlen);
}
static ssize_t packet_sock_sendmsg_vmeth(void *obj, const struct msghdr *msg,
int flags)
{
return zpacket_sendmsg_ctx(obj, msg, flags);
}
static ssize_t packet_sock_recvfrom_vmeth(void *obj, void *buf, size_t max_len,
int flags, struct sockaddr *src_addr,
socklen_t *addrlen)
{
return zpacket_recvfrom_ctx(obj, buf, max_len, flags,
src_addr, addrlen);
}
static int packet_sock_getsockopt_vmeth(void *obj, int level, int optname,
void *optval, socklen_t *optlen)
{
return zpacket_getsockopt_ctx(obj, level, optname, optval, optlen);
}
static int packet_sock_setsockopt_vmeth(void *obj, int level, int optname,
const void *optval, socklen_t optlen)
{
return zpacket_setsockopt_ctx(obj, level, optname, optval, optlen);
}
static int packet_sock_close_vmeth(void *obj)
{
return zsock_close_ctx(obj);
}
static const struct socket_op_vtable packet_sock_fd_op_vtable = {
.fd_vtable = {
.read = packet_sock_read_vmeth,
.write = packet_sock_write_vmeth,
.close = packet_sock_close_vmeth,
.ioctl = packet_sock_ioctl_vmeth,
},
.bind = packet_sock_bind_vmeth,
.connect = packet_sock_connect_vmeth,
.listen = packet_sock_listen_vmeth,
.accept = packet_sock_accept_vmeth,
.sendto = packet_sock_sendto_vmeth,
.sendmsg = packet_sock_sendmsg_vmeth,
.recvfrom = packet_sock_recvfrom_vmeth,
.getsockopt = packet_sock_getsockopt_vmeth,
.setsockopt = packet_sock_setsockopt_vmeth,
};
static bool packet_is_supported(int family, int type, int proto)
{
switch (type) {
case SOCK_RAW:
return proto == ETH_P_ALL
|| proto == ETH_P_ECAT
|| proto == ETH_P_IEEE802154
|| proto == IPPROTO_RAW;
case SOCK_DGRAM:
return proto > 0;
default:
return false;
}
}
NET_SOCKET_REGISTER(af_packet, NET_SOCKET_DEFAULT_PRIO, AF_PACKET,
packet_is_supported, zpacket_socket);