blob: f5df26514e9e745a81da61ee00dac3a017e629ac [file] [log] [blame]
/*
* Copyright (c) 2021 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_virtual_ipip, CONFIG_NET_L2_IPIP_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <errno.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/virtual.h>
#include "ipv4.h"
#include "ipv6.h"
#include "net_private.h"
#if defined(CONFIG_NET_L2_IPIP_TXRX_DEBUG)
#define DEBUG_TX 1
#define DEBUG_RX 1
#else
#define DEBUG_TX 0
#define DEBUG_RX 0
#endif
#define IPIPV4_MTU NET_IPV4_MTU
#define IPIPV6_MTU NET_IPV6_MTU
#define PKT_ALLOC_TIME K_MSEC(50)
struct ipip_context {
struct net_if *iface;
struct net_if *attached_to;
union {
sa_family_t family;
struct net_addr peer;
};
union {
const struct in_addr *my4addr;
const struct in6_addr *my6addr;
};
bool status;
bool init_done;
};
static int ipip_init(const struct device *dev)
{
ARG_UNUSED(dev);
return 0;
}
static void iface_init(struct net_if *iface)
{
struct ipip_context *ctx = net_if_get_device(iface)->data;
if (ctx->init_done) {
return;
}
ctx->iface = iface;
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
net_if_flag_set(iface, NET_IF_POINTOPOINT);
(void)net_virtual_set_flags(iface, NET_L2_POINT_TO_POINT);
ctx->init_done = true;
}
static enum virtual_interface_caps get_capabilities(struct net_if *iface)
{
ARG_UNUSED(iface);
return VIRTUAL_INTERFACE_IPIP;
}
static int interface_start(const struct device *dev)
{
struct ipip_context *ctx = dev->data;
int ret = 0;
if (ctx->status) {
return -EALREADY;
}
ctx->status = true;
NET_DBG("Starting iface %d", net_if_get_by_iface(ctx->iface));
return ret;
}
static int interface_stop(const struct device *dev)
{
struct ipip_context *ctx = dev->data;
if (!ctx->status) {
return -EALREADY;
}
ctx->status = false;
NET_DBG("Stopping iface %d", net_if_get_by_iface(ctx->iface));
return 0;
}
static uint8_t ipv4_get_tos(struct net_pkt *pkt)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);
struct net_ipv4_hdr *ipv4_hdr;
ipv4_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &ipv4_access);
if (!ipv4_hdr) {
return 0;
}
return ipv4_hdr->tos;
}
static int interface_send(struct net_if *iface, struct net_pkt *pkt)
{
struct ipip_context *ctx = net_if_get_device(iface)->data;
struct net_pkt *tmp = NULL;
uint8_t nexthdr;
uint8_t tos = 0;
int ret;
if (ctx->attached_to == NULL) {
return -ENOENT;
}
if (net_pkt_family(pkt) == AF_INET) {
nexthdr = IPPROTO_IPIP;
tos = ipv4_get_tos(pkt);
} else if (net_pkt_family(pkt) == AF_INET6) {
nexthdr = IPPROTO_IPV6;
} else {
return -EINVAL;
}
/* Add new IP header */
if (IS_ENABLED(CONFIG_NET_IPV6) && ctx->family == AF_INET6) {
tmp = net_pkt_alloc_with_buffer(iface,
sizeof(struct net_ipv6_hdr),
AF_INET6, IPPROTO_IPV6,
PKT_ALLOC_TIME);
if (tmp == NULL) {
return -ENOMEM;
}
if (ctx->my6addr == NULL) {
ctx->my6addr = net_if_ipv6_select_src_addr(
ctx->attached_to,
&ctx->peer.in6_addr);
}
ret = net_ipv6_create(tmp, ctx->my6addr, &ctx->peer.in6_addr);
if (ret < 0) {
goto out;
}
net_buf_frag_add(tmp->buffer, pkt->buffer);
pkt->buffer = tmp->buffer;
tmp->buffer = NULL;
net_pkt_unref(tmp);
tmp = NULL;
net_pkt_cursor_init(pkt);
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv6_hdr));
net_pkt_set_ipv6_ext_opt_len(pkt, 0);
net_pkt_set_iface(pkt, ctx->attached_to);
ret = net_ipv6_finalize(pkt, nexthdr);
if (ret < 0) {
goto out;
}
net_pkt_set_family(pkt, AF_INET6);
} else if (IS_ENABLED(CONFIG_NET_IPV4) && ctx->family == AF_INET) {
tmp = net_pkt_alloc_with_buffer(iface,
sizeof(struct net_ipv4_hdr),
AF_INET, IPPROTO_IP,
PKT_ALLOC_TIME);
if (tmp == NULL) {
return -ENOMEM;
}
if (ctx->my4addr == NULL) {
ctx->my4addr = net_if_ipv4_select_src_addr(
ctx->attached_to,
&ctx->peer.in_addr);
}
if (net_if_ipv4_get_ttl(ctx->attached_to) == 0) {
NET_WARN("Interface %d TTL set to 0",
net_if_get_by_iface(ctx->attached_to));
return -EINVAL;
}
net_pkt_set_ipv4_ttl(tmp,
net_if_ipv4_get_ttl(ctx->attached_to));
/* RFC2003 chapter 3.1 */
ret = net_ipv4_create_full(tmp, ctx->my4addr,
&ctx->peer.in_addr,
tos, 0U, NET_IPV4_DF,
0U, net_pkt_ipv4_ttl(tmp));
if (ret < 0) {
goto out;
}
net_buf_frag_add(tmp->buffer, pkt->buffer);
pkt->buffer = tmp->buffer;
tmp->buffer = NULL;
net_pkt_unref(tmp);
tmp = NULL;
net_pkt_cursor_init(pkt);
net_pkt_set_ip_hdr_len(pkt, sizeof(struct net_ipv4_hdr));
net_pkt_set_ipv4_opts_len(pkt, 0);
net_pkt_set_iface(pkt, ctx->attached_to);
ret = net_ipv4_finalize(pkt, nexthdr);
if (ret < 0) {
goto out;
}
net_pkt_set_family(pkt, AF_INET);
}
if (DEBUG_TX) {
char str[sizeof("TX iface xx")];
snprintk(str, sizeof(str), "TX iface %d",
net_if_get_by_iface(net_pkt_iface(pkt)));
net_pkt_hexdump(pkt, str);
}
return net_send_data(pkt);
out:
if (tmp) {
net_pkt_unref(tmp);
}
return ret;
}
static enum net_verdict interface_recv(struct net_if *iface,
struct net_pkt *pkt)
{
if (DEBUG_RX) {
char str[sizeof("RX iface xx")];
snprintk(str, sizeof(str), "RX iface %d",
net_if_get_by_iface(iface));
net_pkt_hexdump(pkt, str);
}
return NET_CONTINUE;
}
static bool verify_remote_addr(struct ipip_context *ctx,
struct net_addr *remote_addr)
{
if (ctx->family != remote_addr->family) {
return false;
}
if (ctx->family == AF_INET) {
if (memcmp(&ctx->peer.in_addr, &remote_addr->in_addr,
sizeof(struct in_addr)) == 0) {
return true;
}
} else {
if (memcmp(&ctx->peer.in6_addr, &remote_addr->in6_addr,
sizeof(struct in6_addr)) == 0) {
return true;
}
}
return false;
}
static enum net_verdict interface_input(struct net_if *input_iface,
struct net_if *virtual_iface,
struct net_addr *remote_addr,
struct net_pkt *pkt)
{
struct ipip_context *ctx = net_if_get_device(virtual_iface)->data;
struct net_if *iface;
/* Make sure we are receiving data from remote end of the
* tunnel. See RFC4213 chapter 4 for details.
*/
if (!verify_remote_addr(ctx, remote_addr)) {
NET_DBG("DROP: remote address unknown");
return NET_DROP;
}
/* net_pkt cursor must point to correct place so that we can fetch
* the network header.
*/
if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
NET_PKT_DATA_ACCESS_DEFINE(access, struct net_ipv6_hdr);
struct net_ipv6_hdr *hdr;
hdr = (struct net_ipv6_hdr *)net_pkt_get_data(pkt, &access);
if (!hdr) {
return NET_DROP;
}
/* RFC4213 chapter 3.6 */
iface = net_if_ipv6_select_src_iface((struct in6_addr *)hdr->dst);
if (iface == NULL) {
NET_DBG("DROP: not for me (dst %s)",
net_sprint_ipv6_addr(&hdr->dst));
return NET_DROP;
}
if (!net_if_is_up(iface)) {
NET_DBG("DROP: interface %d down",
net_if_get_by_iface(iface));
return NET_DROP;
}
if (input_iface != ctx->attached_to ||
virtual_iface != iface) {
NET_DBG("DROP: wrong interface");
return NET_DROP;
}
/* Hop limit fields is decremented, RFC2473 chapter 3.1 and
* RFC4213 chapter 3.3
*/
hdr->hop_limit--;
(void)net_pkt_set_data(pkt, &access);
net_pkt_set_iface(pkt, iface);
return net_if_recv_data(iface, pkt);
}
if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
NET_PKT_DATA_ACCESS_DEFINE(access, struct net_ipv4_hdr);
struct net_ipv4_hdr *hdr;
hdr = (struct net_ipv4_hdr *)net_pkt_get_data(pkt, &access);
if (!hdr) {
return NET_DROP;
}
iface = net_if_ipv4_select_src_iface((struct in_addr *)hdr->dst);
if (iface == NULL) {
NET_DBG("DROP: not for me (dst %s)",
net_sprint_ipv4_addr(&hdr->dst));
return NET_DROP;
}
if (!net_if_is_up(iface)) {
NET_DBG("DROP: interface %d down",
net_if_get_by_iface(iface));
return NET_DROP;
}
/* TTL fields is decremented, RFC2003 chapter 3.1 */
hdr->ttl--;
(void)net_pkt_set_data(pkt, &access);
net_pkt_set_iface(pkt, iface);
return net_if_recv_data(iface, pkt);
}
return NET_CONTINUE;
}
static int interface_attach(struct net_if *iface, struct net_if *lower_iface)
{
struct ipip_context *ctx;
if (net_if_get_by_iface(iface) < 0) {
return -ENOENT;
}
ctx = net_if_get_device(iface)->data;
ctx->attached_to = lower_iface;
if (IS_ENABLED(CONFIG_NET_IPV6) && ctx->family == AF_INET6) {
struct net_if_addr *ifaddr;
struct in6_addr iid;
/* RFC4213 chapter 3.7 */
net_ipv6_addr_create_iid(&iid, net_if_get_link_addr(iface));
ifaddr = net_if_ipv6_addr_add(iface, &iid, NET_ADDR_AUTOCONF,
0);
if (!ifaddr) {
NET_ERR("Cannot add %s address to interface %p",
net_sprint_ipv6_addr(&iid),
iface);
}
}
return 0;
}
static int interface_set_config(struct net_if *iface,
enum virtual_interface_config_type type,
const struct virtual_interface_config *config)
{
struct ipip_context *ctx = net_if_get_device(iface)->data;
switch (type) {
case VIRTUAL_INTERFACE_CONFIG_TYPE_PEER_ADDRESS:
if (IS_ENABLED(CONFIG_NET_IPV4) && config->family == AF_INET) {
char peer[INET_ADDRSTRLEN];
char *addr_str;
net_ipaddr_copy(&ctx->peer.in_addr, &config->peer4addr);
addr_str = net_addr_ntop(AF_INET, &ctx->peer.in_addr,
peer, sizeof(peer));
ctx->family = AF_INET;
net_virtual_set_name(iface, "IPv4 tunnel");
if (ctx->attached_to == NULL) {
(void)net_virtual_interface_attach(iface,
net_if_ipv4_select_src_iface(
&ctx->peer.in_addr));
}
if (ctx->attached_to) {
net_if_ipv4_set_ttl(iface,
net_if_ipv4_get_ttl(ctx->attached_to));
}
NET_DBG("Interface %d peer address %s attached to %d",
net_if_get_by_iface(iface),
addr_str,
net_if_get_by_iface(ctx->attached_to));
ctx->my4addr = NULL;
} else if (IS_ENABLED(CONFIG_NET_IPV6) &&
config->family == AF_INET6) {
char peer[INET6_ADDRSTRLEN];
char *addr_str;
net_ipaddr_copy(&ctx->peer.in6_addr,
&config->peer6addr);
addr_str = net_addr_ntop(AF_INET6, &ctx->peer.in6_addr,
peer, sizeof(peer));
ctx->family = AF_INET6;
net_virtual_set_name(iface, "IPv6 tunnel");
net_ipv6_set_hop_limit(iface, 64);
if (ctx->attached_to == NULL) {
(void)net_virtual_interface_attach(iface,
net_if_ipv6_select_src_iface(
&ctx->peer.in6_addr));
}
NET_DBG("Interface %d peer address %s attached to %d",
net_if_get_by_iface(iface),
addr_str,
net_if_get_by_iface(ctx->attached_to));
ctx->my6addr = NULL;
} else {
return -EINVAL;
}
return 0;
case VIRTUAL_INTERFACE_CONFIG_TYPE_MTU:
NET_DBG("Interface %d MTU set to %d",
net_if_get_by_iface(iface), config->mtu);
net_if_set_mtu(iface, config->mtu);
return 0;
default:
break;
}
return -ENOTSUP;
}
static int interface_get_config(struct net_if *iface,
enum virtual_interface_config_type type,
struct virtual_interface_config *config)
{
struct ipip_context *ctx = net_if_get_device(iface)->data;
switch (type) {
case VIRTUAL_INTERFACE_CONFIG_TYPE_PEER_ADDRESS:
if (IS_ENABLED(CONFIG_NET_IPV6) &&
ctx->family == AF_INET6) {
net_ipaddr_copy(&config->peer6addr,
&ctx->peer.in6_addr);
} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
ctx->family == AF_INET) {
net_ipaddr_copy(&config->peer4addr,
&ctx->peer.in_addr);
} else {
return -EINVAL;
}
config->family = ctx->family;
return 0;
case VIRTUAL_INTERFACE_CONFIG_TYPE_MTU:
config->mtu = net_if_get_mtu(iface);
return 0;
default:
break;
}
return -ENOTSUP;
}
static const struct virtual_interface_api ipip_iface_api = {
.iface_api.init = iface_init,
.get_capabilities = get_capabilities,
.start = interface_start,
.stop = interface_stop,
.send = interface_send,
.recv = interface_recv,
.input = interface_input,
.attach = interface_attach,
.set_config = interface_set_config,
.get_config = interface_get_config,
};
#define NET_IPIP_DATA(x, _) \
static struct ipip_context ipip_context_data_##x = { \
}
#define NET_IPIP_INTERFACE_INIT(x, _) \
NET_VIRTUAL_INTERFACE_INIT(ipip##x, "IP_TUNNEL" #x, ipip_init, \
NULL, &ipip_context_data_##x, NULL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&ipip_iface_api, IPIPV4_MTU)
LISTIFY(CONFIG_NET_L2_IPIP_TUNNEL_COUNT, NET_IPIP_DATA, (;), _);
LISTIFY(CONFIG_NET_L2_IPIP_TUNNEL_COUNT, NET_IPIP_INTERFACE_INIT, (;), _);