blob: 59d8492f2de1138349343074dcb58175bf8b042b [file]
/*
* Embedded Linux library
* Copyright (C) 2011-2014 Intel Corporation
* Copyright (C) 2020 Daniel Wagner <dwagner@suse.de>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <asm/types.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/neighbour.h>
#include <linux/icmpv6.h>
#include <linux/if.h>
#include <sys/socket.h>
#include <assert.h>
#include <limits.h>
#include <ell/ell.h>
#include "ell/netlink-private.h"
#include "ell/rtnl-private.h"
#include "ell/useful.h"
static size_t rta_add_u8(void *rta_buf, unsigned short type, uint8_t value)
{
struct rtattr *rta = rta_buf;
rta->rta_len = RTA_LENGTH(sizeof(uint8_t));
rta->rta_type = type;
*((uint8_t *) RTA_DATA(rta)) = value;
return RTA_SPACE(sizeof(uint8_t));
}
static size_t rta_add_u32(void *rta_buf, unsigned short type, uint32_t value)
{
struct rtattr *rta = rta_buf;
rta->rta_len = RTA_LENGTH(sizeof(uint32_t));
rta->rta_type = type;
*((uint32_t *) RTA_DATA(rta)) = value;
return RTA_SPACE(sizeof(uint32_t));
}
static size_t rta_add_data(void *rta_buf, unsigned short type, const void *data,
size_t data_len)
{
struct rtattr *rta = rta_buf;
rta->rta_len = RTA_LENGTH(data_len);
rta->rta_type = type;
memcpy(RTA_DATA(rta), data, data_len);
return RTA_SPACE(data_len);
}
static size_t rta_add_address(void *rta_buf, unsigned short type,
uint8_t family,
const struct in6_addr *v6,
const struct in_addr *v4)
{
struct rtattr *rta = rta_buf;
rta->rta_type = type;
switch (family) {
case AF_INET6:
rta->rta_len = RTA_LENGTH(sizeof(struct in6_addr));
memcpy(RTA_DATA(rta), v6, sizeof(struct in6_addr));
return RTA_SPACE(sizeof(struct in6_addr));
case AF_INET:
rta->rta_len = RTA_LENGTH(sizeof(struct in_addr));
memcpy(RTA_DATA(rta), v4, sizeof(struct in_addr));
return RTA_SPACE(sizeof(struct in_addr));
}
return 0;
}
static int address_is_null(int family, const struct in_addr *v4,
const struct in6_addr *v6)
{
switch (family) {
case AF_INET:
return v4->s_addr == 0;
case AF_INET6:
return IN6_IS_ADDR_UNSPECIFIED(v6);
}
return -EAFNOSUPPORT;
}
static bool messages_equal(const struct l_netlink_message *nlm,
void *other_message, size_t len)
{
if (len != nlm->hdr->nlmsg_len - NLMSG_HDRLEN)
return false;
return !memcmp(nlm->data + NLMSG_HDRLEN, other_message, len);
}
static const struct l_rtnl_route route = {
.family = AF_INET6,
.scope = RT_SCOPE_UNIVERSE,
.protocol = RTPROT_UNSPEC,
.gw = { .in6_addr = IN6ADDR_LOOPBACK_INIT },
.dst = { .in6_addr = IN6ADDR_LOOPBACK_INIT },
.dst_prefix_len = 32,
.prefsrc = { .in6_addr = IN6ADDR_LOOPBACK_INIT },
.lifetime = INT_MAX,
.expiry_time = 0, /* math performed, will cause messages to differ */
.mtu = 8096,
.priority = 300,
.preference = ICMPV6_ROUTER_PREF_LOW,
};
static struct rtmsg *build_rtmsg(const struct l_rtnl_route *rt, int ifindex,
size_t *out_len)
{
struct rtmsg *rtmmsg;
size_t bufsize;
void *rta_buf;
uint64_t now = l_time_now();
bufsize = NLMSG_ALIGN(sizeof(struct rtmsg)) +
RTA_SPACE(sizeof(uint32_t)) + /* RTA_OIF */
RTA_SPACE(sizeof(uint32_t)) + /* RTA_PRIORITY */
RTA_SPACE(sizeof(struct in6_addr)) + /* RTA_GATEWAY */
RTA_SPACE(sizeof(struct in6_addr)) + /* RTA_DST */
RTA_SPACE(sizeof(struct in6_addr)) + /* RTA_PREFSRC */
256 + /* RTA_METRICS */
RTA_SPACE(sizeof(uint8_t)) + /* RTA_PREF */
RTA_SPACE(sizeof(uint32_t)); /* RTA_EXPIRES */
rtmmsg = l_malloc(bufsize);
memset(rtmmsg, 0, bufsize);
rtmmsg->rtm_family = rt->family;
rtmmsg->rtm_table = RT_TABLE_MAIN;
rtmmsg->rtm_protocol = rt->protocol;
rtmmsg->rtm_type = RTN_UNICAST;
rtmmsg->rtm_scope = rt->scope;
rta_buf = (void *) rtmmsg + NLMSG_ALIGN(sizeof(struct rtmsg));
rta_buf += rta_add_u32(rta_buf, RTA_OIF, ifindex);
if (rt->priority)
rta_buf += rta_add_u32(rta_buf, RTA_PRIORITY,
rt->priority + ifindex);
if (!address_is_null(rt->family, &rt->gw.in_addr, &rt->gw.in6_addr))
rta_buf += rta_add_address(rta_buf, RTA_GATEWAY, rt->family,
&rt->gw.in6_addr, &rt->gw.in_addr);
if (rt->dst_prefix_len) {
rtmmsg->rtm_dst_len = rt->dst_prefix_len;
rta_buf += rta_add_address(rta_buf, RTA_DST, rt->family,
&rt->dst.in6_addr, &rt->dst.in_addr);
}
if (!address_is_null(rt->family, &rt->prefsrc.in_addr,
&rt->prefsrc.in6_addr))
rta_buf += rta_add_address(rta_buf, RTA_PREFSRC, rt->family,
&rt->prefsrc.in6_addr,
&rt->prefsrc.in_addr);
if (rt->mtu) {
uint8_t buf[256];
size_t written = rta_add_u32(buf, RTAX_MTU, rt->mtu);
rta_buf += rta_add_data(rta_buf, RTA_METRICS | NLA_F_NESTED,
buf, written);
}
if (rt->preference)
rta_buf += rta_add_u8(rta_buf, RTA_PREF, rt->preference);
if (rt->expiry_time > now)
rta_buf += rta_add_u32(rta_buf, RTA_EXPIRES,
l_time_to_secs(rt->expiry_time - now));
*out_len = rta_buf - (void *) rtmmsg;
return rtmmsg;
}
static void test_route(const void *data)
{
static const int ifindex = 3;
size_t rtmsg_len;
_auto_(l_free) void *rtmsg = build_rtmsg(&route, ifindex, &rtmsg_len);
struct l_netlink_message *nlm =
rtnl_message_from_route(RTM_NEWROUTE,
NLM_F_CREATE | NLM_F_REPLACE,
ifindex, &route);
assert(messages_equal(nlm, rtmsg, rtmsg_len));
l_netlink_message_unref(nlm);
}
static const struct l_rtnl_address address = {
.family = AF_INET6,
.prefix_len = 31,
.scope = RT_SCOPE_UNIVERSE,
.in6_addr = IN6ADDR_LOOPBACK_INIT,
.label = { 'f', 'o', 'o', 'b', 'a', 'r', '\0' },
.flags = IFA_F_PERMANENT | IFA_F_NOPREFIXROUTE,
};
static struct ifaddrmsg *build_ifaddrmsg(const struct l_rtnl_address *addr,
int ifindex, size_t *out_len)
{
struct ifaddrmsg *ifamsg;
void *buf;
size_t bufsize;
uint64_t now = l_time_now();
bufsize = NLMSG_ALIGN(sizeof(struct ifaddrmsg)) +
RTA_SPACE(sizeof(struct in6_addr)) +
RTA_SPACE(sizeof(struct in_addr)) +
RTA_SPACE(sizeof(uint32_t)) +
RTA_SPACE(IFNAMSIZ) +
RTA_SPACE(sizeof(struct ifa_cacheinfo));
ifamsg = l_malloc(bufsize);
memset(ifamsg, 0, bufsize);
ifamsg->ifa_index = ifindex;
ifamsg->ifa_family = addr->family;
ifamsg->ifa_scope = addr->scope;
ifamsg->ifa_prefixlen = addr->prefix_len;
/* Kernel ignores legacy flags in IFA_FLAGS, so set them here */
ifamsg->ifa_flags = addr->flags & 0xff;
buf = (void *) ifamsg + NLMSG_ALIGN(sizeof(struct ifaddrmsg));
if (addr->family == AF_INET) {
buf += rta_add_data(buf, IFA_LOCAL, &addr->in_addr,
sizeof(struct in_addr));
buf += rta_add_data(buf, IFA_BROADCAST, &addr->broadcast,
sizeof(struct in_addr));
} else
buf += rta_add_data(buf, IFA_LOCAL, &addr->in6_addr,
sizeof(struct in6_addr));
if (addr->flags & 0xffffff00)
buf += rta_add_u32(buf, IFA_FLAGS, addr->flags & 0xffffff00);
if (addr->label[0])
buf += rta_add_data(buf, IFA_LABEL,
addr->label, strlen(addr->label) + 1);
if (addr->preferred_expiry_time > now ||
addr->valid_expiry_time > now) {
struct ifa_cacheinfo cinfo;
memset(&cinfo, 0, sizeof(cinfo));
cinfo.ifa_prefered = addr->preferred_expiry_time > now ?
l_time_to_secs(addr->preferred_expiry_time - now) : 0;
cinfo.ifa_valid = addr->valid_expiry_time > now ?
l_time_to_secs(addr->valid_expiry_time - now) : 0;
buf += rta_add_data(buf, IFA_CACHEINFO, &cinfo, sizeof(cinfo));
}
*out_len = buf - (void *) ifamsg;
return ifamsg;
}
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Warray-bounds\"")
static void test_address(const void *data)
{
static const int ifindex = 3;
size_t ifamsg_len;
_auto_(l_free) void *ifamsg =
build_ifaddrmsg(&address, ifindex, &ifamsg_len);
struct l_netlink_message *nlm =
rtnl_message_from_address(RTM_NEWADDR,
NLM_F_CREATE | NLM_F_REPLACE,
ifindex, &address);
assert(messages_equal(nlm, ifamsg, ifamsg_len));
l_netlink_message_unref(nlm);
}
_Pragma("GCC diagnostic pop")
static void signal_handler(uint32_t signo, void *user_data)
{
switch (signo) {
case SIGINT:
case SIGTERM:
l_info("Terminate");
l_main_quit();
break;
}
}
static struct l_netlink *rtnl;
struct rtnl_test {
const char *name;
void (*start)(struct l_netlink *rtnl, void *);
void *data;
};
static bool success;
static struct l_queue *tests;
static const struct l_queue_entry *current;
static void test_add(const char *name,
void (*start)(struct l_netlink *rtnl, void *),
void *user_data)
{
struct rtnl_test *test = l_new(struct rtnl_test, 1);
test->name = name;
test->start = start;
test->data = user_data;
if (!tests)
tests = l_queue_new();
l_queue_push_tail(tests, test);
}
static void test_next()
{
struct rtnl_test *test;
if (current)
current = current->next;
else
current = l_queue_get_entries(tests);
if (!current) {
success = true;
l_main_quit();
return;
}
test = current->data;
l_info("TEST: %s", test->name);
test->start(rtnl, test->data);
}
#define test_assert(cond) \
do { \
if (!(cond)) { \
l_info("TEST FAILED in %s at %s:%i: %s", \
__func__, __FILE__, __LINE__, \
L_STRINGIFY(cond)); \
l_main_quit(); \
return; \
} \
} while (0)
static void route4_dump_cb(int error,
uint16_t type, const void *data,
uint32_t len, void *user_data)
{
const struct rtmsg *rtmsg = data;
char *dst = NULL, *gateway = NULL, *src = NULL;
uint32_t table, ifindex;
test_assert(!error);
test_assert(type == RTM_NEWROUTE);
l_rtnl_route4_extract(rtmsg, len, &table, &ifindex,
&dst, &gateway, &src);
l_info("table %d ifindex %d dst %s gateway %s src %s",
table, ifindex, dst, gateway, src);
l_free(dst);
l_free(gateway);
l_free(src);
}
static void route4_dump_destroy_cb(void *user_data)
{
test_next();
}
static void test_route4_dump(struct l_netlink *rtnl, void *user_data)
{
test_assert(l_rtnl_route4_dump(rtnl, route4_dump_cb,
NULL, route4_dump_destroy_cb));
}
static void route6_dump_cb(int error,
uint16_t type, const void *data,
uint32_t len, void *user_data)
{
const struct rtmsg *rtmsg = data;
char *dst = NULL, *gateway = NULL, *src = NULL;
uint32_t table = 0, ifindex = 0;
test_assert(!error);
test_assert(type == RTM_NEWROUTE);
l_rtnl_route6_extract(rtmsg, len, &table, &ifindex,
&dst, &gateway, &src);
l_info("table %d ifindex %d dst %s gateway %s src %s",
table, ifindex, dst, gateway, src);
l_free(dst);
l_free(gateway);
l_free(src);
}
static void route6_dump_destroy_cb(void *user_data)
{
test_next();
}
static void test_route6_dump(struct l_netlink *rtnl, void *user_data)
{
test_assert(l_rtnl_route6_dump(rtnl, route6_dump_cb,
NULL, route6_dump_destroy_cb));
}
static void ifaddr4_dump_cb(int error,
uint16_t type, const void *data,
uint32_t len, void *user_data)
{
const struct ifaddrmsg *ifa = data;
char *label = NULL, *ip = NULL, *broadcast = NULL;
test_assert(!error);
test_assert(type == RTM_NEWADDR);
l_rtnl_ifaddr4_extract(ifa, len, &label, &ip, &broadcast);
l_info("label %s ip %s broadcast %s", label, ip, broadcast);
l_free(label);
l_free(ip);
l_free(broadcast);
}
static void ifaddr4_dump_destroy_cb(void *user_data)
{
test_next();
}
static void test_ifaddr4_dump(struct l_netlink *rntl, void *user_data)
{
test_assert(l_rtnl_ifaddr4_dump(rtnl, ifaddr4_dump_cb,
NULL, ifaddr4_dump_destroy_cb));
}
static void ifaddr6_dump_cb(int error,
uint16_t type, const void *data,
uint32_t len, void *user_data)
{
const struct ifaddrmsg *ifa = data;
char *ip = NULL;
test_assert(!error);
test_assert(type == RTM_NEWADDR);
l_rtnl_ifaddr6_extract(ifa, len, &ip);
l_info("ip %s", ip);
l_free(ip);
}
static void ifaddr6_dump_destroy_cb(void *user_data)
{
test_next();
}
static void test_ifaddr6_dump(struct l_netlink *rntl, void *user_data)
{
test_assert(l_rtnl_ifaddr6_dump(rtnl, ifaddr6_dump_cb,
NULL, ifaddr6_dump_destroy_cb));
}
static void test_run(void)
{
success = false;
l_idle_oneshot(test_next, NULL, NULL);
l_main_run_with_signal(signal_handler, NULL);
}
int main(int argc, char *argv[])
{
if (!l_main_init())
return -1;
/* Run the the tests not requiring the main event loop first */
l_test_init(&argc, &argv);
l_test_add("route", test_route, NULL);
l_test_add("address", test_address, NULL);
l_test_run();
test_add("Dump IPv4 routing table", test_route4_dump, NULL);
test_add("Dump IPv6 routing table", test_route6_dump, NULL);
test_add("Dump IPv4 addresses", test_ifaddr4_dump, NULL);
test_add("Dump IPv6 addresses", test_ifaddr6_dump, NULL);
l_log_set_stderr();
rtnl = l_netlink_new(NETLINK_ROUTE);
if (!rtnl)
goto done;
test_run();
l_netlink_destroy(rtnl);
done:
l_queue_destroy(tests, l_free);
l_main_exit();
if (!success)
abort();
return 0;
}