blob: 0644bd7530788853825e3de0375bd5b285a9d5d4 [file] [log] [blame]
/*
* lib/route/route.c Routes
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* Copyright (c) 2003-2006 Thomas Graf <tgraf@suug.ch>
*/
/**
* @ingroup rtnl
* @defgroup route Routing
* @brief
* @{
*/
#include <netlink-local.h>
#include <netlink/netlink.h>
#include <netlink/cache.h>
#include <netlink/utils.h>
#include <netlink/data.h>
#include <netlink/route/rtnl.h>
#include <netlink/route/route.h>
#include <netlink/route/link.h>
static struct nl_cache_ops rtnl_route_ops;
static struct nla_policy route_policy[RTA_MAX+1] = {
[RTA_IIF] = { .type = NLA_STRING,
.maxlen = IFNAMSIZ, },
[RTA_OIF] = { .type = NLA_U32 },
[RTA_PRIORITY] = { .type = NLA_U32 },
[RTA_FLOW] = { .type = NLA_U32 },
[RTA_MP_ALGO] = { .type = NLA_U32 },
[RTA_CACHEINFO] = { .minlen = sizeof(struct rta_cacheinfo) },
[RTA_METRICS] = { .type = NLA_NESTED },
[RTA_MULTIPATH] = { .type = NLA_NESTED },
};
static void copy_cacheinfo_into_route(struct rta_cacheinfo *ci,
struct rtnl_route *route)
{
struct rtnl_rtcacheinfo nci = {
.rtci_clntref = ci->rta_clntref,
.rtci_last_use = ci->rta_lastuse,
.rtci_expires = ci->rta_expires,
.rtci_error = ci->rta_error,
.rtci_used = ci->rta_used,
.rtci_id = ci->rta_id,
.rtci_ts = ci->rta_ts,
.rtci_tsage = ci->rta_tsage,
};
rtnl_route_set_cacheinfo(route, &nci);
}
static int route_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
struct nlmsghdr *nlh, struct nl_parser_param *pp)
{
struct rtmsg *rtm;
struct rtnl_route *route;
struct nlattr *tb[RTA_MAX + 1];
struct nl_addr *src = NULL, *dst = NULL, *addr;
int err;
route = rtnl_route_alloc();
if (!route) {
err = nl_errno(ENOMEM);
goto errout;
}
route->ce_msgtype = nlh->nlmsg_type;
err = nlmsg_parse(nlh, sizeof(struct rtmsg), tb, RTA_MAX,
route_policy);
if (err < 0)
goto errout;
rtm = nlmsg_data(nlh);
rtnl_route_set_family(route, rtm->rtm_family);
rtnl_route_set_tos(route, rtm->rtm_tos);
rtnl_route_set_table(route, rtm->rtm_table);
rtnl_route_set_type(route, rtm->rtm_type);
rtnl_route_set_scope(route, rtm->rtm_scope);
rtnl_route_set_protocol(route, rtm->rtm_protocol);
rtnl_route_set_flags(route, rtm->rtm_flags);
if (tb[RTA_DST]) {
dst = nla_get_addr(tb[RTA_DST], rtm->rtm_family);
if (dst == NULL)
goto errout_errno;
} else {
dst = nl_addr_alloc(0);
nl_addr_set_family(dst, rtm->rtm_family);
}
nl_addr_set_prefixlen(dst, rtm->rtm_dst_len);
err = rtnl_route_set_dst(route, dst);
if (err < 0)
goto errout;
nl_addr_put(dst);
if (tb[RTA_SRC]) {
src = nla_get_addr(tb[RTA_SRC], rtm->rtm_family);
if (src == NULL)
goto errout_errno;
} else if (rtm->rtm_src_len)
src = nl_addr_alloc(0);
if (src) {
nl_addr_set_prefixlen(src, rtm->rtm_src_len);
rtnl_route_set_src(route, src);
nl_addr_put(src);
}
if (tb[RTA_IIF])
rtnl_route_set_iif(route, nla_get_string(tb[RTA_IIF]));
if (tb[RTA_OIF])
rtnl_route_set_oif(route, nla_get_u32(tb[RTA_OIF]));
if (tb[RTA_GATEWAY]) {
addr = nla_get_addr(tb[RTA_GATEWAY], route->rt_family);
if (addr == NULL)
goto errout_errno;
rtnl_route_set_gateway(route, addr);
nl_addr_put(addr);
}
if (tb[RTA_PRIORITY])
rtnl_route_set_prio(route, nla_get_u32(tb[RTA_PRIORITY]));
if (tb[RTA_PREFSRC]) {
addr = nla_get_addr(tb[RTA_PREFSRC], route->rt_family);
if (addr == NULL)
goto errout_errno;
rtnl_route_set_pref_src(route, addr);
nl_addr_put(addr);
}
if (tb[RTA_METRICS]) {
struct nlattr *mtb[RTAX_MAX + 1];
int i;
err = nla_parse_nested(mtb, RTAX_MAX, tb[RTA_METRICS], NULL);
if (err < 0)
goto errout;
for (i = 1; i <= RTAX_MAX; i++) {
if (mtb[i] && nla_len(mtb[i]) >= sizeof(uint32_t)) {
uint32_t m = nla_get_u32(mtb[i]);
if (rtnl_route_set_metric(route, i, m) < 0)
goto errout_errno;
}
}
}
if (tb[RTA_MULTIPATH]) {
struct rtnl_nexthop *nh;
struct rtnexthop *rtnh = nla_data(tb[RTA_MULTIPATH]);
size_t tlen = nla_len(tb[RTA_MULTIPATH]);
while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
nh = rtnl_route_nh_alloc();
if (!nh)
goto errout;
rtnl_route_nh_set_weight(nh, rtnh->rtnh_hops);
rtnl_route_nh_set_ifindex(nh, rtnh->rtnh_ifindex);
rtnl_route_nh_set_flags(nh, rtnh->rtnh_flags);
if (rtnh->rtnh_len > sizeof(*rtnh)) {
struct nlattr *ntb[RTA_MAX + 1];
nla_parse(ntb, RTA_MAX, (struct nlattr *)
RTNH_DATA(rtnh),
rtnh->rtnh_len - sizeof(*rtnh),
route_policy);
if (ntb[RTA_GATEWAY]) {
nh->rtnh_gateway = nla_get_addr(
ntb[RTA_GATEWAY],
route->rt_family);
nh->rtnh_mask = NEXTHOP_HAS_GATEWAY;
}
}
rtnl_route_add_nexthop(route, nh);
tlen -= RTNH_ALIGN(rtnh->rtnh_len);
rtnh = RTNH_NEXT(rtnh);
}
}
if (tb[RTA_FLOW])
rtnl_route_set_realms(route, nla_get_u32(tb[RTA_FLOW]));
if (tb[RTA_CACHEINFO])
copy_cacheinfo_into_route(nla_data(tb[RTA_CACHEINFO]), route);
if (tb[RTA_MP_ALGO])
rtnl_route_set_mp_algo(route, nla_get_u32(tb[RTA_MP_ALGO]));
err = pp->pp_cb((struct nl_object *) route, pp);
if (err < 0)
goto errout;
err = P_ACCEPT;
errout:
rtnl_route_put(route);
return err;
errout_errno:
err = nl_get_errno();
goto errout;
}
static int route_request_update(struct nl_cache *c, struct nl_handle *h)
{
return nl_rtgen_request(h, RTM_GETROUTE, AF_UNSPEC, NLM_F_DUMP);
}
/**
* @name Cache Management
* @{
*/
/**
* Build a route cache holding all routes currently configured in the kernel
* @arg handle netlink handle
*
* Allocates a new cache, initializes it properly and updates it to
* contain all routes currently configured in the kernel.
*
* @note The caller is responsible for destroying and freeing the
* cache after using it.
* @return The cache or NULL if an error has occured.
*/
struct nl_cache *rtnl_route_alloc_cache(struct nl_handle *handle)
{
struct nl_cache *cache;
cache = nl_cache_alloc(&rtnl_route_ops);
if (!cache)
return NULL;
if (handle && nl_cache_refill(handle, cache) < 0) {
free(cache);
return NULL;
}
return cache;
}
/** @} */
/**
* @name Route Addition
* @{
*/
static struct nl_msg *build_route_msg(struct rtnl_route *tmpl, int cmd,
int flags)
{
struct nl_msg *msg;
struct nl_addr *addr;
int scope, i, oif, nmetrics = 0;
struct nlattr *metrics;
struct rtmsg rtmsg = {
.rtm_family = rtnl_route_get_family(tmpl),
.rtm_dst_len = rtnl_route_get_dst_len(tmpl),
.rtm_src_len = rtnl_route_get_src_len(tmpl),
.rtm_tos = rtnl_route_get_tos(tmpl),
.rtm_table = rtnl_route_get_table(tmpl),
.rtm_type = rtnl_route_get_type(tmpl),
.rtm_protocol = rtnl_route_get_protocol(tmpl),
.rtm_flags = rtnl_route_get_flags(tmpl),
};
if (rtmsg.rtm_family == AF_UNSPEC) {
nl_error(EINVAL, "Cannot build route message, address " \
"family is unknown.");
return NULL;
}
scope = rtnl_route_get_scope(tmpl);
if (scope == RT_SCOPE_NOWHERE) {
if (rtmsg.rtm_type == RTN_LOCAL)
scope = RT_SCOPE_HOST;
else {
/* XXX Change to UNIVERSE if gw || nexthops */
scope = RT_SCOPE_LINK;
}
}
rtmsg.rtm_scope = scope;
msg = nlmsg_alloc_simple(cmd, flags);
if (msg == NULL)
return NULL;
if (nlmsg_append(msg, &rtmsg, sizeof(rtmsg), NLMSG_ALIGNTO) < 0)
goto nla_put_failure;
addr = rtnl_route_get_dst(tmpl);
if (addr)
NLA_PUT_ADDR(msg, RTA_DST, addr);
addr = rtnl_route_get_src(tmpl);
if (addr)
NLA_PUT_ADDR(msg, RTA_SRC, addr);
addr = rtnl_route_get_gateway(tmpl);
if (addr)
NLA_PUT_ADDR(msg, RTA_GATEWAY, addr);
addr = rtnl_route_get_pref_src(tmpl);
if (addr)
NLA_PUT_ADDR(msg, RTA_PREFSRC, addr);
NLA_PUT_U32(msg, RTA_PRIORITY, rtnl_route_get_prio(tmpl));
oif = rtnl_route_get_oif(tmpl);
if (oif != RTNL_LINK_NOT_FOUND)
NLA_PUT_U32(msg, RTA_OIF, oif);
for (i = 1; i <= RTAX_MAX; i++)
if (rtnl_route_get_metric(tmpl, i) != UINT_MAX)
nmetrics++;
if (nmetrics > 0) {
unsigned int val;
metrics = nla_nest_start(msg, RTA_METRICS);
if (metrics == NULL)
goto nla_put_failure;
for (i = 1; i <= RTAX_MAX; i++) {
val = rtnl_route_get_metric(tmpl, i);
if (val != UINT_MAX)
NLA_PUT_U32(msg, i, val);
}
nla_nest_end(msg, metrics);
}
#if 0
RTA_IIF,
RTA_MULTIPATH,
RTA_PROTOINFO,
RTA_FLOW,
RTA_CACHEINFO,
RTA_SESSION,
RTA_MP_ALGO,
#endif
return msg;
nla_put_failure:
nlmsg_free(msg);
return NULL;
}
struct nl_msg *rtnl_route_build_add_request(struct rtnl_route *tmpl, int flags)
{
return build_route_msg(tmpl, RTM_NEWROUTE, NLM_F_CREATE | flags);
}
int rtnl_route_add(struct nl_handle *handle, struct rtnl_route *route,
int flags)
{
struct nl_msg *msg;
int err;
msg = rtnl_route_build_add_request(route, flags);
if (!msg)
return nl_get_errno();
err = nl_send_auto_complete(handle, msg);
nlmsg_free(msg);
if (err < 0)
return err;
return nl_wait_for_ack(handle);
}
struct nl_msg *rtnl_route_build_del_request(struct rtnl_route *tmpl, int flags)
{
return build_route_msg(tmpl, RTM_DELROUTE, flags);
}
int rtnl_route_del(struct nl_handle *handle, struct rtnl_route *route,
int flags)
{
struct nl_msg *msg;
int err;
msg = rtnl_route_build_del_request(route, flags);
if (!msg)
return nl_get_errno();
err = nl_send_auto_complete(handle, msg);
nlmsg_free(msg);
if (err < 0)
return err;
return nl_wait_for_ack(handle);
}
/** @} */
static struct nl_af_group route_groups[] = {
{ AF_INET, RTNLGRP_IPV4_ROUTE },
{ AF_INET6, RTNLGRP_IPV6_ROUTE },
{ AF_DECnet, RTNLGRP_DECnet_ROUTE },
{ END_OF_GROUP_LIST },
};
static struct nl_cache_ops rtnl_route_ops = {
.co_name = "route/route",
.co_hdrsize = sizeof(struct rtmsg),
.co_msgtypes = {
{ RTM_NEWROUTE, NL_ACT_NEW, "new" },
{ RTM_DELROUTE, NL_ACT_DEL, "del" },
{ RTM_GETROUTE, NL_ACT_GET, "get" },
END_OF_MSGTYPES_LIST,
},
.co_protocol = NETLINK_ROUTE,
.co_groups = route_groups,
.co_request_update = route_request_update,
.co_msg_parser = route_msg_parser,
.co_obj_ops = &route_obj_ops,
};
static void __init route_init(void)
{
nl_cache_mngt_register(&rtnl_route_ops);
}
static void __exit route_exit(void)
{
nl_cache_mngt_unregister(&rtnl_route_ops);
}
/** @} */