395 lines
8.1 KiB
C
395 lines
8.1 KiB
C
/*
|
|
* Copyright (c) 2021 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(net_virtual, CONFIG_NET_L2_VIRTUAL_LOG_LEVEL);
|
|
|
|
#include <zephyr/net/net_core.h>
|
|
#include <zephyr/net/net_l2.h>
|
|
#include <zephyr/net/net_if.h>
|
|
#include <zephyr/net/net_mgmt.h>
|
|
#include <zephyr/net/virtual.h>
|
|
#include <zephyr/net/virtual_mgmt.h>
|
|
#include <zephyr/random/rand32.h>
|
|
|
|
#include "net_private.h"
|
|
|
|
#define NET_BUF_TIMEOUT K_MSEC(100)
|
|
|
|
static enum net_verdict virtual_recv(struct net_if *iface,
|
|
struct net_pkt *pkt)
|
|
{
|
|
ARG_UNUSED(iface);
|
|
ARG_UNUSED(pkt);
|
|
|
|
return NET_CONTINUE;
|
|
}
|
|
|
|
static int virtual_send(struct net_if *iface, struct net_pkt *pkt)
|
|
{
|
|
const struct virtual_interface_api *api = net_if_get_device(iface)->api;
|
|
|
|
if (!api) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* As we are just passing data through, the net_pkt is not freed here.
|
|
*/
|
|
|
|
return api->send(iface, pkt);
|
|
}
|
|
|
|
static int virtual_enable(struct net_if *iface, bool state)
|
|
{
|
|
const struct virtual_interface_api *virt;
|
|
struct virtual_interface_context *ctx, *tmp, *ctx_up, *ctx_orig;
|
|
sys_slist_t *interfaces;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Propagate the status upstream */
|
|
if (ctx->virtual_iface) {
|
|
interfaces = &ctx->virtual_iface->config.virtual_interfaces;
|
|
ctx_orig = ctx;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(interfaces, ctx_up, tmp,
|
|
node) {
|
|
if (!net_if_is_up(ctx->iface)) {
|
|
continue;
|
|
}
|
|
|
|
net_if_down(ctx_up->virtual_iface);
|
|
}
|
|
|
|
if (net_if_is_up(ctx_orig->virtual_iface)) {
|
|
NET_DBG("Taking iface %d down",
|
|
net_if_get_by_iface(ctx_orig->virtual_iface));
|
|
net_if_carrier_down(ctx_orig->virtual_iface);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
linkaddr[i] = sys_rand32_get();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
const struct net_l2 *l2;
|
|
|
|
l2 = net_if_l2(ctx->virtual_iface);
|
|
if (l2 && l2->enable) {
|
|
l2->enable(ctx->virtual_iface, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
enum net_verdict net_virtual_input(struct net_if *input_iface,
|
|
struct net_addr *remote_addr,
|
|
struct net_pkt *pkt)
|
|
{
|
|
struct virtual_interface_context *ctx, *tmp;
|
|
const struct virtual_interface_api *virt;
|
|
struct net_pkt_cursor hdr_start;
|
|
enum net_verdict verdict;
|
|
sys_slist_t *interfaces;
|
|
uint8_t iptype;
|
|
|
|
net_pkt_cursor_backup(pkt, &hdr_start);
|
|
|
|
if (net_pkt_read_u8(pkt, &iptype)) {
|
|
return NET_DROP;
|
|
}
|
|
|
|
net_pkt_cursor_restore(pkt, &hdr_start);
|
|
|
|
switch (iptype & 0xf0) {
|
|
case 0x60:
|
|
net_pkt_set_family(pkt, AF_INET6);
|
|
break;
|
|
case 0x40:
|
|
net_pkt_set_family(pkt, AF_INET);
|
|
break;
|
|
default:
|
|
return NET_DROP;
|
|
}
|
|
|
|
interfaces = &input_iface->config.virtual_interfaces;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(interfaces, ctx, tmp, node) {
|
|
if (ctx->virtual_iface == NULL) {
|
|
continue;
|
|
}
|
|
|
|
virt = net_if_get_device(ctx->virtual_iface)->api;
|
|
if (!virt || virt->input == NULL) {
|
|
continue;
|
|
}
|
|
|
|
verdict = virt->input(input_iface, ctx->virtual_iface,
|
|
remote_addr, pkt);
|
|
if (verdict == NET_OK) {
|
|
continue;
|
|
}
|
|
|
|
return verdict;
|
|
}
|
|
|
|
return NET_DROP;
|
|
}
|
|
|
|
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;
|
|
}
|