blob: a29a7c6a4dad1c225b55a99606b3d5556156d6cb [file] [log] [blame]
/*
* Copyright (c) 2022 Intel and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <utils/debug.h>
#include <vlibapi/api.h>
#include <vlibmemory/api.h>
#include <threading/thread.h>
#include <threading/mutex.h>
#define vl_typedefs
#define vl_endianfun
/* Include the (first) vlib-api API definition layer */
#include <vlibmemory/vl_memory_api_h.h>
/* Include the current layer (third) vpp API definition layer */
#include <vpp/api/vpe_types.api.h>
#include <vpp/api/vpe.api.h>
#include <vnet/ip-neighbor/ip_neighbor.api_enum.h>
#include <vnet/ip-neighbor/ip_neighbor.api_types.h>
#include <vnet/ip/ip.api_enum.h>
#include <vnet/ip/ip.api_types.h>
#include <vnet/interface.api_enum.h>
#include <vnet/interface.api_types.h>
#undef vl_typedefs
#undef vl_endianfun
#include "kernel_vpp_net.h"
#include "kernel_vpp_shared.h"
typedef struct private_kernel_vpp_net_t private_kernel_vpp_net_t;
/**
* Private data of kernel_vpp_net implementation.
*/
struct private_kernel_vpp_net_t
{
/**
* Public interface.
*/
kernel_vpp_net_t public;
/**
* Mutex to access interface list
*/
mutex_t *mutex;
/**
* Known interfaces, as iface_t
*/
linked_list_t *ifaces;
/**
* Inteface update thread
*/
thread_t *net_update;
/**
* TRUE if interface events enabled
*/
bool events_on;
};
/**
* Interface entry
*/
typedef struct
{
/** interface index */
uint32_t index;
/** interface name */
char if_name[64];
/** list of known addresses, as host_t */
linked_list_t *addrs;
/** TRUE if up */
bool up;
} iface_t;
/**
* Address enumerator
*/
typedef struct
{
/** implements enumerator_t */
enumerator_t public;
/** what kind of address should we enumerate? */
kernel_address_type_t which;
/** enumerator over interfaces */
enumerator_t *ifaces;
/** current enumerator over addresses, or NULL */
enumerator_t *addrs;
/** mutex to unlock on destruction */
mutex_t *mutex;
} addr_enumerator_t;
/**
* FIB path entry
*/
typedef struct
{
chunk_t next_hop;
uint32_t sw_if_index;
uint8_t preference;
} fib_path_t;
/**
* Get an iface entry for a local address
*/
static iface_t *
address2entry (private_kernel_vpp_net_t *this, host_t *ip)
{
enumerator_t *ifaces, *addrs;
iface_t *entry, *found = NULL;
host_t *host;
ifaces = this->ifaces->create_enumerator (this->ifaces);
while (!found && ifaces->enumerate (ifaces, &entry))
{
addrs = entry->addrs->create_enumerator (entry->addrs);
while (!found && addrs->enumerate (addrs, &host))
{
if (host->ip_equals (host, ip))
{
found = entry;
}
}
addrs->destroy (addrs);
}
ifaces->destroy (ifaces);
return found;
}
/**
* Add or remove a route
*/
static status_t
manage_route (private_kernel_vpp_net_t *this, bool add, chunk_t dst,
uint8_t prefixlen, host_t *gtw, char *name)
{
char *out;
int out_len;
enumerator_t *enumerator;
iface_t *entry;
vl_api_ip_route_add_del_t *mp;
vl_api_ip_route_add_del_reply_t *rmp;
vl_api_fib_path_t *apath;
bool exists = FALSE;
this->mutex->lock (this->mutex);
enumerator = this->ifaces->create_enumerator (this->ifaces);
while (enumerator->enumerate (enumerator, &entry))
{
if (streq (name, entry->if_name))
{
exists = TRUE;
break;
}
}
enumerator->destroy (enumerator);
this->mutex->unlock (this->mutex);
if (!exists)
{
DBG1 (DBG_NET, "if_name %s not found", name);
return NOT_FOUND;
}
mp = vl_msg_api_alloc (sizeof (*mp) + sizeof (*apath));
memset (mp, 0, sizeof (*mp) + sizeof (*apath));
u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_route_add_del_b8ecfe0d");
mp->_vl_msg_id = ntohs (msg_id);
mp->is_add = add;
mp->route.prefix.len = prefixlen;
mp->route.n_paths = 1;
apath = &mp->route.paths[0];
apath->sw_if_index = ntohl (entry->index);
apath->rpf_id = ~0;
apath->weight = 1;
switch (dst.len)
{
case 4:
mp->route.prefix.address.af = ntohl (ADDRESS_IP4);
memcpy (&mp->route.prefix.address.un.ip4, dst.ptr, dst.len);
if (gtw)
{
chunk_t addr = gtw->get_address (gtw);
apath->proto = ntohl (FIB_API_PATH_NH_PROTO_IP4);
memcpy (&apath->nh.address.ip4, addr.ptr, dst.len);
}
break;
case 16:
mp->route.prefix.address.af = ntohl (ADDRESS_IP6);
memcpy (&mp->route.prefix.address.un.ip6, dst.ptr, dst.len);
if (gtw)
{
chunk_t addr = gtw->get_address (gtw);
apath->proto = ntohl (FIB_API_PATH_NH_PROTO_IP6);
memcpy (&apath->nh.address.ip6, addr.ptr, dst.len);
}
break;
default:
vl_msg_api_free (mp);
return FAILED;
}
if (vac->send (vac, (char *) mp, sizeof (*mp) + sizeof (*apath), &out,
&out_len))
{
DBG1 (DBG_KNL, "vac %sing route failed", add ? "add" : "remov");
vl_msg_api_free (mp);
return FAILED;
}
rmp = (void *) out;
vl_msg_api_free (mp);
if (rmp->retval)
{
DBG1 (DBG_KNL, "%s route failed %d", add ? "add" : "delete",
ntohl (rmp->retval));
free (out);
return FAILED;
}
free (out);
return SUCCESS;
}
/**
* Check if an address or net (addr with prefix net bits) is in
* subnet (net with net_len net bits)
*/
static bool
addr_in_subnet (chunk_t addr, int prefix, chunk_t net, int net_len)
{
static const u_char mask[] = {
0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe
};
int byte = 0;
if (net_len == 0)
{ /* any address matches a /0 network */
return TRUE;
}
if (addr.len != net.len || net_len > 8 * net.len || prefix < net_len)
{
return FALSE;
}
/* scan through all bytes in network order */
while (net_len > 0)
{
if (net_len < 8)
{
return (mask[net_len] & addr.ptr[byte]) ==
(mask[net_len] & net.ptr[byte]);
}
else
{
if (addr.ptr[byte] != net.ptr[byte])
{
return FALSE;
}
byte++;
net_len -= 8;
}
}
return TRUE;
}
/**
* Get a route: If "nexthop" the nexthop is returned, source addr otherwise
*/
static host_t *
get_route (private_kernel_vpp_net_t *this, host_t *dest, int prefix,
bool nexthop, char **iface, host_t *src)
{
fib_path_t path;
char *out, *tmp;
int out_len, i, num;
vl_api_fib_path_t *fp;
host_t *addr = NULL;
enumerator_t *enumerator;
iface_t *entry;
int family;
path.sw_if_index = ~0;
path.preference = ~0;
path.next_hop = chunk_empty;
vl_api_ip_route_dump_t *mp;
vl_api_ip_route_details_t *rmp;
mp = vl_msg_api_alloc (sizeof (*mp));
clib_memset (mp, 0, sizeof (*mp));
u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_route_dump_b9d2e09e");
mp->_vl_msg_id = htons (msg_id);
mp->table.is_ip6 = dest->get_family (dest) == AF_INET6 ? 1 : 0;
if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len))
{
vl_msg_api_free (mp);
DBG2 (DBG_KNL, "send VL_API_IP_ROUTE_ADD_DEL failed");
return NULL;
}
vl_msg_api_free (mp);
if (dest->get_family (dest) == AF_INET)
{
i = 0;
family = AF_INET;
if (prefix == -1)
prefix = 32;
tmp = out;
while (tmp < (out + out_len))
{
rmp = (void *) tmp;
num = rmp->route.n_paths;
if (rmp->route.prefix.len &&
addr_in_subnet (
dest->get_address (dest), prefix,
chunk_create (rmp->route.prefix.address.un.ip4, 4),
rmp->route.prefix.len))
{
fp = rmp->route.paths;
for (i = 0; i < num; i++)
{
#define IS_IP4_ANY(a) (a[0] == 0 && a[1] == 0 && a[2] == 0 & a[3] == 0)
if (fp->type == FIB_API_PATH_TYPE_DROP)
{
fp++;
continue;
}
if ((fp->preference < path.preference) ||
(path.sw_if_index == ~0) ||
IS_IP4_ANY (path.next_hop.ptr))
{
path.sw_if_index = ntohl (fp->sw_if_index);
path.preference = fp->preference;
if (path.next_hop.ptr)
vl_msg_api_free (path.next_hop.ptr);
path.next_hop = chunk_create (fp->nh.address.ip4, 4);
}
fp++;
}
}
tmp += sizeof (*rmp) + (sizeof (*fp) * num);
}
}
else
{
DBG1 (DBG_KNL, "not yet support ip6");
return NULL;
}
if (path.next_hop.len)
{
if (nexthop)
{
if (iface)
{
*iface = NULL;
this->mutex->lock (this->mutex);
enumerator = this->ifaces->create_enumerator (this->ifaces);
while (enumerator->enumerate (enumerator, &entry))
{
if (entry->index == path.sw_if_index)
{
*iface = strdup (entry->if_name);
break;
}
}
enumerator->destroy (enumerator);
this->mutex->unlock (this->mutex);
}
addr = host_create_from_chunk (family, path.next_hop, 0);
}
else
{
if (src)
{
addr = src->clone (src);
}
}
}
free (out);
return addr;
}
METHOD (enumerator_t, addr_enumerate, bool, addr_enumerator_t *this,
va_list args)
{
iface_t *entry;
host_t **host;
VA_ARGS_VGET (args, host);
while (TRUE)
{
while (!this->addrs)
{
if (!this->ifaces->enumerate (this->ifaces, &entry))
{
return FALSE;
}
if (!entry->up && !(this->which & ADDR_TYPE_DOWN))
{
continue;
}
this->addrs = entry->addrs->create_enumerator (entry->addrs);
}
if (this->addrs->enumerate (this->addrs, host))
{
return TRUE;
}
this->addrs->destroy (this->addrs);
this->addrs = NULL;
}
}
METHOD (enumerator_t, addr_destroy, void, addr_enumerator_t *this)
{
DESTROY_IF (this->addrs);
this->ifaces->destroy (this->ifaces);
this->mutex->unlock (this->mutex);
free (this);
}
METHOD (kernel_net_t, get_interface_name, bool, private_kernel_vpp_net_t *this,
host_t *ip, char **name)
{
iface_t *entry;
this->mutex->lock (this->mutex);
entry = address2entry (this, ip);
if (entry && name)
{
*name = strdup (entry->if_name);
}
this->mutex->unlock (this->mutex);
return entry != NULL;
}
METHOD (kernel_net_t, create_address_enumerator, enumerator_t *,
private_kernel_vpp_net_t *this, kernel_address_type_t which)
{
addr_enumerator_t *enumerator;
if (!(which & ADDR_TYPE_REGULAR))
{
/* we currently have no virtual, but regular IPs only */
return enumerator_create_empty ();
}
this->mutex->lock (this->mutex);
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _addr_enumerate,
.destroy = _addr_destroy,
},
.which = which,
.ifaces = this->ifaces->create_enumerator(this->ifaces),
.mutex = this->mutex,
);
return &enumerator->public;
}
METHOD (kernel_net_t, get_source_addr, host_t *,
private_kernel_vpp_net_t *this, host_t *dest, host_t *src)
{
return get_route (this, dest, -1, FALSE, NULL, src);
}
METHOD (kernel_net_t, get_nexthop, host_t *, private_kernel_vpp_net_t *this,
host_t *dest, int prefix, host_t *src, char **iface)
{
return get_route (this, dest, prefix, TRUE, iface, src);
}
METHOD (kernel_net_t, add_ip, status_t, private_kernel_vpp_net_t *this,
host_t *virtual_ip, int prefix, char *iface_name)
{
return NOT_SUPPORTED;
}
METHOD (kernel_net_t, del_ip, status_t, private_kernel_vpp_net_t *this,
host_t *virtual_ip, int prefix, bool wait)
{
return NOT_SUPPORTED;
}
METHOD (kernel_net_t, add_route, status_t, private_kernel_vpp_net_t *this,
chunk_t dst_net, u_int8_t prefixlen, host_t *gateway, host_t *src_ip,
char *if_name)
{
return manage_route (this, TRUE, dst_net, prefixlen, gateway, if_name);
}
METHOD (kernel_net_t, del_route, status_t, private_kernel_vpp_net_t *this,
chunk_t dst_net, u_int8_t prefixlen, host_t *gateway, host_t *src_ip,
char *if_name)
{
return manage_route (this, FALSE, dst_net, prefixlen, gateway, if_name);
}
static void
iface_destroy (iface_t *this)
{
this->addrs->destroy_offset (this->addrs, offsetof (host_t, destroy));
free (this);
}
METHOD (kernel_net_t, destroy, void, private_kernel_vpp_net_t *this)
{
this->net_update->cancel (this->net_update);
this->mutex->destroy (this->mutex);
this->ifaces->destroy_function (this->ifaces, (void *) iface_destroy);
free (this);
}
/**
* Update addresses for an iface entry
*/
static void
update_addrs (private_kernel_vpp_net_t *this, iface_t *entry)
{
char *out;
int out_len, i, num;
vl_api_ip_address_dump_t *mp;
vl_api_ip_address_details_t *rmp, *tmp;
linked_list_t *addrs;
host_t *host;
mp = vl_msg_api_alloc (sizeof (*mp));
clib_memset (mp, 0, sizeof (*mp));
u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "ip_address_dump_2d033de4");
mp->_vl_msg_id = htons (msg_id);
mp->sw_if_index = htonl (entry->index);
mp->is_ipv6 = 0;
if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len))
{
DBG2 (DBG_NET, "update_addrs : send VL_API_IP_ADDRESS_DUMP ipv4 failed");
vl_msg_api_free (mp);
return;
}
num = out_len / sizeof (*rmp);
addrs = linked_list_create ();
tmp = (vl_api_ip_address_details_t *) out;
for (i = 0; i < num; i++)
{
rmp = tmp;
tmp += 1;
host = host_create_from_chunk (
AF_INET, chunk_create (rmp->prefix.address.un.ip4, 4), 0);
addrs->insert_last (addrs, host);
}
free (out);
mp->is_ipv6 = 1;
if (vac->send_dump (vac, (char *) mp, sizeof (*mp), &out, &out_len))
{
DBG2 (DBG_NET, "update_addrs : send VL_API_IP_ADDRESS_DUMP ipv6 failed");
vl_msg_api_free (mp);
return;
}
num = out_len / sizeof (*rmp);
tmp = (vl_api_ip_address_details_t *) out;
for (i = 0; i < num; i++)
{
rmp = tmp;
tmp += 1;
host = host_create_from_chunk (
AF_INET6, chunk_create (rmp->prefix.address.un.ip6, 16), 0);
addrs->insert_last (addrs, host);
}
vl_msg_api_free (mp);
free (out);
entry->addrs->destroy (entry->addrs);
entry->addrs =
linked_list_create_from_enumerator (addrs->create_enumerator (addrs));
addrs->destroy (addrs);
}
/**
* VPP API interface event callback
*/
static void
event_cb (char *data, int data_len, void *ctx)
{
private_kernel_vpp_net_t *this = ctx;
vl_api_sw_interface_event_t *event;
iface_t *entry;
enumerator_t *enumerator;
event = (void *) data;
this->mutex->lock (this->mutex);
enumerator = this->ifaces->create_enumerator (this->ifaces);
while (enumerator->enumerate (enumerator, &entry))
{
if (entry->index == ntohl (event->sw_if_index))
{
if (event->deleted)
{
this->ifaces->remove_at (this->ifaces, enumerator);
DBG2 (DBG_NET, "interface deleted %u %s", entry->index,
entry->if_name);
iface_destroy (entry);
}
else if (entry->up != (event->flags & IF_STATUS_API_FLAG_LINK_UP))
{
entry->up =
(event->flags & IF_STATUS_API_FLAG_LINK_UP) ? TRUE : FALSE;
DBG2 (DBG_NET, "interface state changed %u %s %s", entry->index,
entry->if_name, entry->up ? "UP" : "DOWN");
}
break;
}
}
enumerator->destroy (enumerator);
this->mutex->unlock (this->mutex);
free (data);
}
/**
* Inteface update thread (update interface list and interface address)
*/
static void *
net_update_thread_fn (private_kernel_vpp_net_t *this)
{
status_t rv;
while (1)
{
char *out;
int out_len;
vl_api_sw_interface_dump_t *mp;
vl_api_sw_interface_details_t *rmp;
enumerator_t *enumerator;
iface_t *entry;
mp = vl_msg_api_alloc (sizeof (*mp));
memset (mp, 0, sizeof (*mp));
u16 msg_id =
vl_msg_api_get_msg_index ((u8 *) "sw_interface_dump_aa610c27");
mp->_vl_msg_id = htons (msg_id);
mp->name_filter_valid = 0;
rv = vac->send_dump (vac, (u8 *) mp, sizeof (*mp), &out, &out_len);
if (!rv)
{
int i, num;
this->mutex->lock (this->mutex);
enumerator = this->ifaces->create_enumerator (this->ifaces);
num = out_len / sizeof (*rmp);
rmp = (vl_api_sw_interface_details_t *) out;
for (i = 0; i < num; i++)
{
bool exists = FALSE;
if (i)
rmp += 1;
while (enumerator->enumerate (enumerator, &entry))
{
if (entry->index == ntohl (rmp->sw_if_index))
{
exists = TRUE;
break;
}
}
if (!exists)
{
INIT (entry, .index = ntohl (rmp->sw_if_index),
.up = (rmp->flags & IF_STATUS_API_FLAG_LINK_UP) ?
TRUE :
FALSE,
.addrs = linked_list_create (), );
memcpy (entry->if_name, rmp->interface_name, 63);
this->ifaces->insert_last (this->ifaces, entry);
}
update_addrs (this, entry);
}
enumerator->destroy (enumerator);
this->mutex->unlock (this->mutex);
free (out);
}
vl_msg_api_free (mp);
if (!this->events_on)
{
vl_api_want_interface_events_t *emp;
api_main_t *am = vlibapi_get_main ();
emp = vl_msg_api_alloc (sizeof (*emp));
clib_memset (emp, 0, sizeof (*emp));
u16 msg_id =
vl_msg_api_get_msg_index ((u8 *) "want_interface_events_476f5a08");
emp->_vl_msg_id = ntohs (msg_id);
emp->enable_disable = 1;
emp->pid = ntohl (am->our_pid);
rv = vac->register_event (vac, (char *) emp, sizeof (*emp), event_cb,
VL_API_SW_INTERFACE_EVENT, this);
if (!rv)
this->events_on = TRUE;
}
sleep (2);
}
return NULL;
}
kernel_vpp_net_t *
kernel_vpp_net_create ()
{
private_kernel_vpp_net_t *this;
INIT(this,
.public = {
.interface = {
.get_interface = _get_interface_name,
.create_address_enumerator = _create_address_enumerator,
.get_source_addr = _get_source_addr,
.get_nexthop = _get_nexthop,
.add_ip = _add_ip,
.del_ip = _del_ip,
.add_route = _add_route,
.del_route = _del_route,
.destroy = _destroy,
},
},
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.ifaces = linked_list_create(),
.events_on = FALSE,
);
this->net_update =
thread_create ((thread_main_t) net_update_thread_fn, this);
return &this->public;
}