/* * Copyright (c) 2021 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_virtual, CONFIG_NET_L2_VIRTUAL_LOG_LEVEL); #include #include #include #include #include #include #include #include "net_private.h" #include "net_stats.h" #define NET_BUF_TIMEOUT K_MSEC(100) static enum net_verdict virtual_recv(struct net_if *iface, struct net_pkt *pkt) { struct virtual_interface_context *ctx, *tmp; const struct virtual_interface_api *api; enum net_verdict verdict; sys_slist_t *interfaces; interfaces = &iface->config.virtual_interfaces; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(interfaces, ctx, tmp, node) { if (ctx->virtual_iface == NULL) { continue; } api = net_if_get_device(ctx->virtual_iface)->api; if (!api || api->recv == NULL) { continue; } if (!net_if_is_up(ctx->virtual_iface)) { NET_DBG("Interface %d is down.", net_if_get_by_iface(ctx->virtual_iface)); continue; } verdict = api->recv(ctx->virtual_iface, pkt); if (verdict == NET_CONTINUE) { continue; } if (IS_ENABLED(CONFIG_NET_STATISTICS)) { size_t pkt_len; pkt_len = net_pkt_get_len(pkt); NET_DBG("Received pkt %p len %zu", pkt, pkt_len); net_stats_update_bytes_recv(ctx->virtual_iface, pkt_len); } if (verdict == NET_DROP) { net_stats_update_processing_error(ctx->virtual_iface); } return verdict; } NET_DBG("No handler, dropping pkt %p len %zu", pkt, net_pkt_get_len(pkt)); return NET_DROP; } static int virtual_send(struct net_if *iface, struct net_pkt *pkt) { const struct virtual_interface_api *api = net_if_get_device(iface)->api; size_t pkt_len; int ret; if (!api) { return -ENOENT; } if (!net_if_is_up(iface)) { NET_DBG("Interface %d is down.", net_if_get_by_iface(iface)); return -ENETDOWN; } if (IS_ENABLED(CONFIG_NET_STATISTICS)) { pkt_len = net_pkt_get_len(pkt); } /* As we are just passing data through, the net_pkt is not freed here. */ ret = api->send(iface, pkt); if (IS_ENABLED(CONFIG_NET_STATISTICS) && ret == 0) { NET_DBG("Sent pkt %p len %zu", pkt, pkt_len); net_stats_update_bytes_sent(iface, pkt_len); } return ret; } static int virtual_enable(struct net_if *iface, bool state) { const struct virtual_interface_api *virt; struct virtual_interface_context *ctx; virt = net_if_get_device(iface)->api; if (!virt) { return -ENOENT; } ctx = net_if_l2_data(iface); if (state) { /* Take the interfaces below this interface up as * it does not make sense otherwise. */ while (ctx->iface) { if (net_if_is_up(ctx->iface)) { /* Network interfaces below this must be up too * so we can bail out at this point. */ break; } if (net_if_l2(ctx->iface) != &NET_L2_GET_NAME(VIRTUAL)) { net_if_up(ctx->iface); break; } NET_DBG("Taking iface %d up", net_if_get_by_iface(ctx->iface)); net_if_up(ctx->iface); ctx = net_if_l2_data(ctx->iface); } if (virt->start) { virt->start(net_if_get_device(iface)); } return 0; } if (virt->stop) { virt->stop(net_if_get_device(iface)); } return 0; } enum net_l2_flags virtual_flags(struct net_if *iface) { struct virtual_interface_context *ctx = net_if_l2_data(iface); return ctx->virtual_l2_flags; } NET_L2_INIT(VIRTUAL_L2, virtual_recv, virtual_send, virtual_enable, virtual_flags); static void random_linkaddr(uint8_t *linkaddr, size_t len) { sys_rand_get(linkaddr, len); linkaddr[0] |= 0x02; /* force LAA bit */ } int net_virtual_interface_attach(struct net_if *virtual_iface, struct net_if *iface) { const struct virtual_interface_api *api; struct virtual_interface_context *ctx; bool up = false; if (net_if_get_by_iface(virtual_iface) < 0 || (iface != NULL && net_if_get_by_iface(iface) < 0)) { return -EINVAL; } if (virtual_iface == iface) { return -EINVAL; } api = net_if_get_device(virtual_iface)->api; if (api->attach == NULL) { return -ENOENT; } ctx = net_if_l2_data(virtual_iface); if (ctx->iface) { if (iface != NULL) { /* We are already attached */ return -EALREADY; } /* Detaching, take the interface down */ net_if_down(virtual_iface); (void)sys_slist_find_and_remove( &ctx->iface->config.virtual_interfaces, &ctx->node); NET_DBG("Detaching %d from %d", net_if_get_by_iface(virtual_iface), net_if_get_by_iface(ctx->iface)); ctx->iface = NULL; } else { if (iface == NULL) { /* We are already detached */ return -EALREADY; } /* Attaching, take the interface up if auto start is enabled. */ ctx->iface = iface; sys_slist_append(&ctx->iface->config.virtual_interfaces, &ctx->node); NET_DBG("Attaching %d to %d", net_if_get_by_iface(virtual_iface), net_if_get_by_iface(ctx->iface)); up = true; } /* Figure out the link address for this interface. The actual link * address is randomized. This must be done before attach is called so * that the attach callback can create link local address for the * network interface (if IPv6). The actual link address is typically * not need in tunnels. */ if (iface) { random_linkaddr(ctx->lladdr.addr, sizeof(ctx->lladdr.addr)); ctx->lladdr.len = sizeof(ctx->lladdr.addr); ctx->lladdr.type = NET_LINK_UNKNOWN; net_if_set_link_addr(virtual_iface, ctx->lladdr.addr, ctx->lladdr.len, ctx->lladdr.type); } api->attach(virtual_iface, iface); if (up && !net_if_flag_is_set(virtual_iface, NET_IF_NO_AUTO_START)) { net_if_up(virtual_iface); } return 0; } void net_virtual_disable(struct net_if *iface) { struct virtual_interface_context *ctx, *tmp; sys_slist_t *interfaces; if (net_if_get_by_iface(iface) < 0) { return; } interfaces = &iface->config.virtual_interfaces; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(interfaces, ctx, tmp, node) { NET_DBG("Iface %d down, setting virtual iface %d carrier off", net_if_get_by_iface(iface), net_if_get_by_iface(ctx->virtual_iface)); net_if_carrier_off(ctx->virtual_iface); } } void net_virtual_enable(struct net_if *iface) { struct virtual_interface_context *ctx, *tmp; sys_slist_t *interfaces; if (net_if_get_by_iface(iface) < 0) { return; } interfaces = &iface->config.virtual_interfaces; SYS_SLIST_FOR_EACH_CONTAINER_SAFE(interfaces, ctx, tmp, node) { NET_DBG("Iface %d up, setting virtual iface %d carrier on", net_if_get_by_iface(iface), net_if_get_by_iface(ctx->virtual_iface)); net_if_carrier_on(ctx->virtual_iface); } } struct net_if *net_virtual_get_iface(struct net_if *iface) { struct virtual_interface_context *ctx; if (net_if_get_by_iface(iface) < 0) { return NULL; } if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { return NULL; } ctx = net_if_l2_data(iface); return ctx->iface; } char *net_virtual_get_name(struct net_if *iface, char *buf, size_t len) { struct virtual_interface_context *ctx; if (net_if_get_by_iface(iface) < 0) { return NULL; } if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { return NULL; } ctx = net_if_l2_data(iface); strncpy(buf, ctx->name, MIN(len, sizeof(ctx->name))); buf[len - 1] = '\0'; return buf; } void net_virtual_set_name(struct net_if *iface, const char *name) { struct virtual_interface_context *ctx; if (net_if_get_by_iface(iface) < 0) { return; } if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { return; } ctx = net_if_l2_data(iface); strncpy(ctx->name, name, CONFIG_NET_L2_VIRTUAL_MAX_NAME_LEN); ctx->name[CONFIG_NET_L2_VIRTUAL_MAX_NAME_LEN - 1] = '\0'; } enum net_l2_flags net_virtual_set_flags(struct net_if *iface, enum net_l2_flags flags) { struct virtual_interface_context *ctx; enum net_l2_flags old_flags; if (net_if_get_by_iface(iface) < 0) { return 0; } if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { return 0; } ctx = net_if_l2_data(iface); old_flags = ctx->virtual_l2_flags; ctx->virtual_l2_flags = flags; return old_flags; } void net_virtual_init(struct net_if *iface) { struct virtual_interface_context *ctx; sys_slist_init(&iface->config.virtual_interfaces); if (net_if_l2(iface) != &NET_L2_GET_NAME(VIRTUAL)) { return; } ctx = net_if_l2_data(iface); if (ctx->is_init) { return; } NET_DBG("Initializing virtual L2 %p for iface %d (%p)", ctx, net_if_get_by_iface(iface), iface); ctx->virtual_iface = iface; ctx->virtual_l2_flags = 0; ctx->is_init = true; }