You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
15 KiB
585 lines
15 KiB
/* SPDX-License-Identifier: LGPL-2.1-only */
|
|
/*
|
|
* lib/route/netconf.c netconf
|
|
*
|
|
* 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) 2017 David Ahern <dsa@cumulusnetworks.com>
|
|
*/
|
|
|
|
/**
|
|
* @ingroup rtnl
|
|
* @defgroup netconf Netconf
|
|
* @brief
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
#include <netlink-private/netlink.h>
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/utils.h>
|
|
#include <netlink/route/netconf.h>
|
|
#include <linux/netconf.h>
|
|
#include <linux/socket.h>
|
|
#include <netlink/hashtable.h>
|
|
|
|
/** @cond SKIP */
|
|
#define NETCONF_ATTR_FAMILY 0x0001
|
|
#define NETCONF_ATTR_IFINDEX 0x0002
|
|
#define NETCONF_ATTR_RP_FILTER 0x0004
|
|
#define NETCONF_ATTR_FWDING 0x0008
|
|
#define NETCONF_ATTR_MC_FWDING 0x0010
|
|
#define NETCONF_ATTR_PROXY_NEIGH 0x0020
|
|
#define NETCONF_ATTR_IGNORE_RT_LINKDWN 0x0040
|
|
#define NETCONF_ATTR_INPUT 0x0080
|
|
|
|
struct rtnl_netconf
|
|
{
|
|
NLHDR_COMMON
|
|
|
|
int family;
|
|
int ifindex;
|
|
int rp_filter;
|
|
int forwarding;
|
|
int mc_forwarding;
|
|
int proxy_neigh;
|
|
int ignore_routes_linkdown;
|
|
int input;
|
|
};
|
|
|
|
static struct nl_cache_ops rtnl_netconf_ops;
|
|
static struct nl_object_ops netconf_obj_ops;
|
|
/** @endcond */
|
|
|
|
static struct nla_policy devconf_ipv4_policy[NETCONFA_MAX+1] = {
|
|
[NETCONFA_IFINDEX] = { .type = NLA_S32 },
|
|
[NETCONFA_FORWARDING] = { .type = NLA_S32 },
|
|
[NETCONFA_MC_FORWARDING] = { .type = NLA_S32 },
|
|
[NETCONFA_RP_FILTER] = { .type = NLA_S32 },
|
|
[NETCONFA_PROXY_NEIGH] = { .type = NLA_S32 },
|
|
[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN] = { .type = NLA_S32 },
|
|
};
|
|
|
|
static struct nla_policy devconf_ipv6_policy[NETCONFA_MAX+1] = {
|
|
[NETCONFA_IFINDEX] = { .type = NLA_S32 },
|
|
[NETCONFA_FORWARDING] = { .type = NLA_S32 },
|
|
[NETCONFA_MC_FORWARDING] = { .type = NLA_S32 },
|
|
[NETCONFA_PROXY_NEIGH] = { .type = NLA_S32 },
|
|
[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN] = { .type = NLA_S32 },
|
|
};
|
|
|
|
static struct nla_policy devconf_mpls_policy[NETCONFA_MAX+1] = {
|
|
[NETCONFA_IFINDEX] = { .type = NLA_S32 },
|
|
[NETCONFA_INPUT] = { .type = NLA_S32 },
|
|
};
|
|
|
|
static struct rtnl_netconf *rtnl_netconf_alloc(void)
|
|
{
|
|
return (struct rtnl_netconf *) nl_object_alloc(&netconf_obj_ops);
|
|
}
|
|
|
|
static int netconf_clone(struct nl_object *_dst, struct nl_object *_src)
|
|
{
|
|
struct rtnl_netconf *dst = nl_object_priv(_dst);
|
|
struct rtnl_netconf *src = nl_object_priv(_src);
|
|
|
|
*dst = *src;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int netconf_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
|
|
struct nlmsghdr *nlh, struct nl_parser_param *pp)
|
|
{
|
|
struct nlattr *tb[NETCONFA_MAX+1], *attr;
|
|
struct rtnl_netconf *nc;
|
|
struct netconfmsg *ncm;
|
|
int err;
|
|
|
|
ncm = nlmsg_data(nlh);
|
|
switch (ncm->ncm_family) {
|
|
case AF_INET:
|
|
err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
|
|
devconf_ipv4_policy);
|
|
if (err < 0)
|
|
return err;
|
|
break;
|
|
case AF_INET6:
|
|
err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
|
|
devconf_ipv6_policy);
|
|
if (err < 0)
|
|
return err;
|
|
break;
|
|
case AF_MPLS:
|
|
err = nlmsg_parse(nlh, sizeof(*ncm), tb, NETCONFA_MAX,
|
|
devconf_mpls_policy);
|
|
if (err < 0)
|
|
return err;
|
|
break;
|
|
default:
|
|
printf("unexpected netconf family: %d\n", ncm->ncm_family);
|
|
return -1;
|
|
}
|
|
|
|
if (!tb[NETCONFA_IFINDEX])
|
|
return -1;
|
|
|
|
nc = rtnl_netconf_alloc();
|
|
if (!nc)
|
|
return -NLE_NOMEM;
|
|
|
|
nc->ce_msgtype = nlh->nlmsg_type;
|
|
nc->family = ncm->ncm_family;
|
|
nc->ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]);
|
|
|
|
nc->ce_mask = NETCONF_ATTR_FAMILY | NETCONF_ATTR_IFINDEX;
|
|
|
|
|
|
if (tb[NETCONFA_RP_FILTER]) {
|
|
attr = tb[NETCONFA_RP_FILTER];
|
|
nc->rp_filter = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_RP_FILTER;
|
|
}
|
|
|
|
if (tb[NETCONFA_FORWARDING]) {
|
|
attr = tb[NETCONFA_FORWARDING];
|
|
nc->forwarding = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_FWDING;
|
|
}
|
|
|
|
if (tb[NETCONFA_MC_FORWARDING]) {
|
|
attr = tb[NETCONFA_MC_FORWARDING];
|
|
nc->mc_forwarding = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_MC_FWDING;
|
|
}
|
|
|
|
if (tb[NETCONFA_PROXY_NEIGH]) {
|
|
attr = tb[NETCONFA_PROXY_NEIGH];
|
|
nc->proxy_neigh = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_PROXY_NEIGH;
|
|
}
|
|
|
|
if (tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN]) {
|
|
attr = tb[NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN];
|
|
nc->ignore_routes_linkdown = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_IGNORE_RT_LINKDWN;
|
|
}
|
|
|
|
if (tb[NETCONFA_INPUT]) {
|
|
attr = tb[NETCONFA_INPUT];
|
|
nc->input = nla_get_s32(attr);
|
|
nc->ce_mask |= NETCONF_ATTR_INPUT;
|
|
}
|
|
|
|
err = pp->pp_cb((struct nl_object *) nc, pp);
|
|
|
|
rtnl_netconf_put(nc);
|
|
return err;
|
|
}
|
|
|
|
static int netconf_request_update(struct nl_cache *cache, struct nl_sock *sk)
|
|
{
|
|
struct netconfmsg nc = {
|
|
.ncm_family = cache->c_iarg1,
|
|
};
|
|
|
|
return nl_send_simple(sk, RTM_GETNETCONF, NLM_F_DUMP, &nc, sizeof(nc));
|
|
}
|
|
|
|
static void netconf_dump_line(struct nl_object *obj, struct nl_dump_params *p)
|
|
{
|
|
struct rtnl_netconf *nc = (struct rtnl_netconf *) obj;
|
|
struct nl_cache *link_cache;
|
|
char buf[64];
|
|
|
|
switch(nc->family) {
|
|
case AF_INET:
|
|
nl_dump(p, "ipv4 ");
|
|
break;
|
|
case AF_INET6:
|
|
nl_dump(p, "ipv6 ");
|
|
break;
|
|
case AF_MPLS:
|
|
nl_dump(p, "mpls ");
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
switch(nc->ifindex) {
|
|
case NETCONFA_IFINDEX_ALL:
|
|
nl_dump(p, "all ");
|
|
break;
|
|
case NETCONFA_IFINDEX_DEFAULT:
|
|
nl_dump(p, "default ");
|
|
break;
|
|
default:
|
|
link_cache = nl_cache_mngt_require_safe("route/link");
|
|
if (link_cache) {
|
|
nl_dump(p, "dev %s ",
|
|
rtnl_link_i2name(link_cache, nc->ifindex,
|
|
buf, sizeof(buf)));
|
|
nl_cache_put(link_cache);
|
|
} else
|
|
nl_dump(p, "dev %d ", nc->ifindex);
|
|
}
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_FWDING) {
|
|
nl_dump(p, "forwarding %s ",
|
|
nc->forwarding ? "on" : "off");
|
|
}
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_RP_FILTER) {
|
|
if (nc->rp_filter == 0)
|
|
nl_dump(p, "rp_filter off ");
|
|
else if (nc->rp_filter == 1)
|
|
nl_dump(p, "rp_filter strict ");
|
|
else if (nc->rp_filter == 2)
|
|
nl_dump(p, "rp_filter loose ");
|
|
else
|
|
nl_dump(p, "rp_filter unknown-mode ");
|
|
}
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_MC_FWDING) {
|
|
nl_dump(p, "mc_forwarding %s ",
|
|
nc->mc_forwarding ? "on" : "off");
|
|
}
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH)
|
|
nl_dump(p, "proxy_neigh %d ", nc->proxy_neigh);
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN) {
|
|
nl_dump(p, "ignore_routes_with_linkdown %s ",
|
|
nc->ignore_routes_linkdown ? "on" : "off");
|
|
}
|
|
|
|
if (nc->ce_mask & NETCONF_ATTR_INPUT)
|
|
nl_dump(p, "input %s ", nc->input ? "on" : "off");
|
|
|
|
nl_dump(p, "\n");
|
|
}
|
|
|
|
static const struct trans_tbl netconf_attrs[] = {
|
|
__ADD(NETCONF_ATTR_FAMILY, family),
|
|
__ADD(NETCONF_ATTR_IFINDEX, ifindex),
|
|
__ADD(NETCONF_ATTR_RP_FILTER, rp_filter),
|
|
__ADD(NETCONF_ATTR_FWDING, forwarding),
|
|
__ADD(NETCONF_ATTR_MC_FWDING, mc_forwarding),
|
|
__ADD(NETCONF_ATTR_PROXY_NEIGH, proxy_neigh),
|
|
__ADD(NETCONF_ATTR_IGNORE_RT_LINKDWN, ignore_routes_with_linkdown),
|
|
__ADD(NETCONF_ATTR_INPUT, input),
|
|
};
|
|
|
|
static char *netconf_attrs2str(int attrs, char *buf, size_t len)
|
|
{
|
|
return __flags2str(attrs, buf, len, netconf_attrs,
|
|
ARRAY_SIZE(netconf_attrs));
|
|
}
|
|
|
|
static void netconf_keygen(struct nl_object *obj, uint32_t *hashkey,
|
|
uint32_t table_sz)
|
|
{
|
|
struct rtnl_netconf *nc = (struct rtnl_netconf *) obj;
|
|
unsigned int nckey_sz;
|
|
struct nc_hash_key {
|
|
int nc_family;
|
|
int nc_index;
|
|
} __attribute__((packed)) nckey;
|
|
|
|
nckey_sz = sizeof(nckey);
|
|
nckey.nc_family = nc->family;
|
|
nckey.nc_index = nc->ifindex;
|
|
|
|
*hashkey = nl_hash(&nckey, nckey_sz, 0) % table_sz;
|
|
|
|
NL_DBG(5, "netconf %p key (dev %d fam %d) keysz %d, hash 0x%x\n",
|
|
nc, nckey.nc_index, nckey.nc_family, nckey_sz, *hashkey);
|
|
}
|
|
|
|
static uint64_t netconf_compare(struct nl_object *_a, struct nl_object *_b,
|
|
uint64_t attrs, int flags)
|
|
{
|
|
struct rtnl_netconf *a = (struct rtnl_netconf *) _a;
|
|
struct rtnl_netconf *b = (struct rtnl_netconf *) _b;
|
|
uint64_t diff = 0;
|
|
|
|
#define NETCONF_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, NETCONF_ATTR_##ATTR, a, b, EXPR)
|
|
|
|
diff |= NETCONF_DIFF(FAMILY, a->family != b->family);
|
|
diff |= NETCONF_DIFF(IFINDEX, a->ifindex != b->ifindex);
|
|
diff |= NETCONF_DIFF(RP_FILTER, a->rp_filter != b->rp_filter);
|
|
diff |= NETCONF_DIFF(FWDING, a->forwarding != b->forwarding);
|
|
diff |= NETCONF_DIFF(MC_FWDING, a->mc_forwarding != b->mc_forwarding);
|
|
diff |= NETCONF_DIFF(PROXY_NEIGH, a->proxy_neigh != b->proxy_neigh);
|
|
diff |= NETCONF_DIFF(IGNORE_RT_LINKDWN,
|
|
a->ignore_routes_linkdown != b->ignore_routes_linkdown);
|
|
diff |= NETCONF_DIFF(INPUT, a->input != b->input);
|
|
|
|
#undef NETCONF_DIFF
|
|
|
|
return diff;
|
|
}
|
|
|
|
static int netconf_update(struct nl_object *old_obj, struct nl_object *new_obj)
|
|
{
|
|
struct rtnl_netconf *new_nc = (struct rtnl_netconf *) new_obj;
|
|
struct rtnl_netconf *old_nc = (struct rtnl_netconf *) old_obj;
|
|
int action = new_obj->ce_msgtype;
|
|
|
|
switch(action) {
|
|
case RTM_NEWNETCONF:
|
|
if (new_nc->family != old_nc->family ||
|
|
new_nc->ifindex != old_nc->ifindex)
|
|
return -NLE_OPNOTSUPP;
|
|
|
|
if (new_nc->ce_mask & NETCONF_ATTR_RP_FILTER)
|
|
old_nc->rp_filter = new_nc->rp_filter;
|
|
if (new_nc->ce_mask & NETCONF_ATTR_FWDING)
|
|
old_nc->forwarding = new_nc->forwarding;
|
|
if (new_nc->ce_mask & NETCONF_ATTR_MC_FWDING)
|
|
old_nc->mc_forwarding = new_nc->mc_forwarding;
|
|
if (new_nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH)
|
|
old_nc->proxy_neigh = new_nc->proxy_neigh;
|
|
if (new_nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN)
|
|
old_nc->ignore_routes_linkdown = new_nc->ignore_routes_linkdown;
|
|
|
|
break;
|
|
default:
|
|
return -NLE_OPNOTSUPP;
|
|
}
|
|
|
|
return NLE_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name Cache Management
|
|
* @{
|
|
*/
|
|
|
|
int rtnl_netconf_alloc_cache(struct nl_sock *sk, struct nl_cache **result)
|
|
{
|
|
return nl_cache_alloc_and_fill(&rtnl_netconf_ops, sk, result);
|
|
}
|
|
|
|
/**
|
|
* Search netconf in cache
|
|
* @arg cache netconf cache
|
|
* @arg family Address family of interest
|
|
* @arg ifindex Interface index of interest
|
|
*
|
|
* Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
|
|
* for given index and family
|
|
*
|
|
* The reference counter is incremented before returning the netconf entry,
|
|
* therefore the reference must be given back with rtnl_netconf_put() after
|
|
* usage.
|
|
*
|
|
* @return netconf object or NULL if no match was found.
|
|
*/
|
|
struct rtnl_netconf *rtnl_netconf_get_by_idx(struct nl_cache *cache, int family,
|
|
int ifindex)
|
|
{
|
|
struct rtnl_netconf *nc;
|
|
|
|
if (!ifindex || !family || cache->c_ops != &rtnl_netconf_ops)
|
|
return NULL;
|
|
|
|
nl_list_for_each_entry(nc, &cache->c_items, ce_list) {
|
|
if (nc->ifindex == ifindex &&
|
|
nc->family == family) {
|
|
nl_object_get((struct nl_object *) nc);
|
|
return nc;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void rtnl_netconf_put(struct rtnl_netconf *nc)
|
|
{
|
|
nl_object_put((struct nl_object *) nc);
|
|
}
|
|
|
|
/**
|
|
* Search netconf in cache
|
|
* @arg cache netconf cache
|
|
* @arg family Address family of interest
|
|
*
|
|
* Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
|
|
* for "all" netconf settings for given family
|
|
*
|
|
* The reference counter is incremented before returning the netconf entry,
|
|
* therefore the reference must be given back with rtnl_netconf_put() after
|
|
* usage.
|
|
*
|
|
* @return netconf object or NULL if no match was found.
|
|
*/
|
|
struct rtnl_netconf *rtnl_netconf_get_all(struct nl_cache *cache, int family)
|
|
{
|
|
return rtnl_netconf_get_by_idx(cache, family, NETCONFA_IFINDEX_ALL);
|
|
}
|
|
|
|
/**
|
|
* Search netconf in cache
|
|
* @arg cache netconf cache
|
|
* @arg family Address family of interest
|
|
*
|
|
* Searches netconf cache previously allocated with rtnl_netconf_alloc_cache()
|
|
* for "default" netconf settings for given family
|
|
*
|
|
* The reference counter is incremented before returning the netconf entry,
|
|
* therefore the reference must be given back with rtnl_netconf_put() after
|
|
* usage.
|
|
*
|
|
* @return netconf object or NULL if no match was found.
|
|
*/
|
|
struct rtnl_netconf *rtnl_netconf_get_default(struct nl_cache *cache, int family)
|
|
{
|
|
return rtnl_netconf_get_by_idx(cache, family, NETCONFA_IFINDEX_DEFAULT);
|
|
}
|
|
|
|
/** @} */
|
|
|
|
/**
|
|
* @name Attributes
|
|
* @{
|
|
*/
|
|
|
|
int rtnl_netconf_get_family(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_FAMILY))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->family;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_ifindex(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_IFINDEX))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->ifindex;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_forwarding(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_FWDING))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->forwarding;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_mc_forwarding(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_MC_FWDING))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->mc_forwarding;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_rp_filter(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_RP_FILTER))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->rp_filter;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_proxy_neigh(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_PROXY_NEIGH))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->proxy_neigh;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_ignore_routes_linkdown(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_IGNORE_RT_LINKDWN))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->ignore_routes_linkdown;
|
|
return 0;
|
|
}
|
|
int rtnl_netconf_get_input(struct rtnl_netconf *nc, int *val)
|
|
{
|
|
if (!nc)
|
|
return -NLE_INVAL;
|
|
if (!(nc->ce_mask & NETCONF_ATTR_INPUT))
|
|
return -NLE_MISSING_ATTR;
|
|
if (val)
|
|
*val = nc->input;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** @} */
|
|
|
|
static struct nl_object_ops netconf_obj_ops = {
|
|
.oo_name = "route/netconf",
|
|
.oo_size = sizeof(struct rtnl_netconf),
|
|
.oo_clone = netconf_clone,
|
|
.oo_dump = {
|
|
[NL_DUMP_LINE] = netconf_dump_line,
|
|
[NL_DUMP_DETAILS] = netconf_dump_line,
|
|
},
|
|
.oo_compare = netconf_compare,
|
|
.oo_keygen = netconf_keygen,
|
|
.oo_update = netconf_update,
|
|
.oo_attrs2str = netconf_attrs2str,
|
|
.oo_id_attrs = (NETCONF_ATTR_FAMILY |
|
|
NETCONF_ATTR_IFINDEX)
|
|
};
|
|
|
|
static struct nl_af_group netconf_groups[] = {
|
|
{ AF_INET, RTNLGRP_IPV4_NETCONF },
|
|
{ AF_INET6, RTNLGRP_IPV6_NETCONF },
|
|
{ AF_MPLS, RTNLGRP_MPLS_NETCONF },
|
|
{ END_OF_GROUP_LIST },
|
|
};
|
|
|
|
static struct nl_cache_ops rtnl_netconf_ops = {
|
|
.co_name = "route/netconf",
|
|
.co_hdrsize = sizeof(struct netconfmsg),
|
|
.co_msgtypes = {
|
|
{ RTM_NEWNETCONF, NL_ACT_NEW, "new" },
|
|
{ RTM_DELNETCONF, NL_ACT_DEL, "del" },
|
|
{ RTM_GETNETCONF, NL_ACT_GET, "get" },
|
|
END_OF_MSGTYPES_LIST,
|
|
},
|
|
.co_protocol = NETLINK_ROUTE,
|
|
.co_groups = netconf_groups,
|
|
.co_request_update = netconf_request_update,
|
|
.co_msg_parser = netconf_msg_parser,
|
|
.co_obj_ops = &netconf_obj_ops,
|
|
};
|
|
|
|
static void __init netconf_init(void)
|
|
{
|
|
nl_cache_mngt_register(&rtnl_netconf_ops);
|
|
}
|
|
|
|
static void __exit netconf_exit(void)
|
|
{
|
|
nl_cache_mngt_unregister(&rtnl_netconf_ops);
|
|
}
|
|
|
|
/** @} */
|