linux/net/ipv4/fib_rules.c
Jamal Hadi Salim b6544c0b4c [NETLINK]: Correctly set NLM_F_MULTI without checking the pid
This patch rectifies some rtnetlink message builders that derive the
flags from the pid. It is now explicit like the other cases
which get it right. Also fixes half a dozen dumpers which did not
set NLM_F_MULTI at all.

Signed-off-by: Jamal Hadi Salim <hadi@cyberus.ca>
Signed-off-by: Thomas Graf <tgraf@suug.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-06-18 22:54:12 -07:00

438 lines
11 KiB
C

/*
* INET An implementation of the TCP/IP protocol suite for the LINUX
* operating system. INET is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* IPv4 Forwarding Information Base: policy rules.
*
* Version: $Id: fib_rules.c,v 1.17 2001/10/31 21:55:54 davem Exp $
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Fixes:
* Rani Assaf : local_rule cannot be deleted
* Marc Boucher : routing by fwmark
*/
#include <linux/config.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/bitops.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/errno.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/proc_fs.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/init.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/tcp.h>
#include <net/sock.h>
#include <net/ip_fib.h>
#define FRprintk(a...)
struct fib_rule
{
struct fib_rule *r_next;
atomic_t r_clntref;
u32 r_preference;
unsigned char r_table;
unsigned char r_action;
unsigned char r_dst_len;
unsigned char r_src_len;
u32 r_src;
u32 r_srcmask;
u32 r_dst;
u32 r_dstmask;
u32 r_srcmap;
u8 r_flags;
u8 r_tos;
#ifdef CONFIG_IP_ROUTE_FWMARK
u32 r_fwmark;
#endif
int r_ifindex;
#ifdef CONFIG_NET_CLS_ROUTE
__u32 r_tclassid;
#endif
char r_ifname[IFNAMSIZ];
int r_dead;
};
static struct fib_rule default_rule = {
.r_clntref = ATOMIC_INIT(2),
.r_preference = 0x7FFF,
.r_table = RT_TABLE_DEFAULT,
.r_action = RTN_UNICAST,
};
static struct fib_rule main_rule = {
.r_next = &default_rule,
.r_clntref = ATOMIC_INIT(2),
.r_preference = 0x7FFE,
.r_table = RT_TABLE_MAIN,
.r_action = RTN_UNICAST,
};
static struct fib_rule local_rule = {
.r_next = &main_rule,
.r_clntref = ATOMIC_INIT(2),
.r_table = RT_TABLE_LOCAL,
.r_action = RTN_UNICAST,
};
static struct fib_rule *fib_rules = &local_rule;
static DEFINE_RWLOCK(fib_rules_lock);
int inet_rtm_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
{
struct rtattr **rta = arg;
struct rtmsg *rtm = NLMSG_DATA(nlh);
struct fib_rule *r, **rp;
int err = -ESRCH;
for (rp=&fib_rules; (r=*rp) != NULL; rp=&r->r_next) {
if ((!rta[RTA_SRC-1] || memcmp(RTA_DATA(rta[RTA_SRC-1]), &r->r_src, 4) == 0) &&
rtm->rtm_src_len == r->r_src_len &&
rtm->rtm_dst_len == r->r_dst_len &&
(!rta[RTA_DST-1] || memcmp(RTA_DATA(rta[RTA_DST-1]), &r->r_dst, 4) == 0) &&
rtm->rtm_tos == r->r_tos &&
#ifdef CONFIG_IP_ROUTE_FWMARK
(!rta[RTA_PROTOINFO-1] || memcmp(RTA_DATA(rta[RTA_PROTOINFO-1]), &r->r_fwmark, 4) == 0) &&
#endif
(!rtm->rtm_type || rtm->rtm_type == r->r_action) &&
(!rta[RTA_PRIORITY-1] || memcmp(RTA_DATA(rta[RTA_PRIORITY-1]), &r->r_preference, 4) == 0) &&
(!rta[RTA_IIF-1] || rtattr_strcmp(rta[RTA_IIF-1], r->r_ifname) == 0) &&
(!rtm->rtm_table || (r && rtm->rtm_table == r->r_table))) {
err = -EPERM;
if (r == &local_rule)
break;
write_lock_bh(&fib_rules_lock);
*rp = r->r_next;
r->r_dead = 1;
write_unlock_bh(&fib_rules_lock);
fib_rule_put(r);
err = 0;
break;
}
}
return err;
}
/* Allocate new unique table id */
static struct fib_table *fib_empty_table(void)
{
int id;
for (id = 1; id <= RT_TABLE_MAX; id++)
if (fib_tables[id] == NULL)
return __fib_new_table(id);
return NULL;
}
void fib_rule_put(struct fib_rule *r)
{
if (atomic_dec_and_test(&r->r_clntref)) {
if (r->r_dead)
kfree(r);
else
printk("Freeing alive rule %p\n", r);
}
}
int inet_rtm_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
{
struct rtattr **rta = arg;
struct rtmsg *rtm = NLMSG_DATA(nlh);
struct fib_rule *r, *new_r, **rp;
unsigned char table_id;
if (rtm->rtm_src_len > 32 || rtm->rtm_dst_len > 32 ||
(rtm->rtm_tos & ~IPTOS_TOS_MASK))
return -EINVAL;
if (rta[RTA_IIF-1] && RTA_PAYLOAD(rta[RTA_IIF-1]) > IFNAMSIZ)
return -EINVAL;
table_id = rtm->rtm_table;
if (table_id == RT_TABLE_UNSPEC) {
struct fib_table *table;
if (rtm->rtm_type == RTN_UNICAST) {
if ((table = fib_empty_table()) == NULL)
return -ENOBUFS;
table_id = table->tb_id;
}
}
new_r = kmalloc(sizeof(*new_r), GFP_KERNEL);
if (!new_r)
return -ENOMEM;
memset(new_r, 0, sizeof(*new_r));
if (rta[RTA_SRC-1])
memcpy(&new_r->r_src, RTA_DATA(rta[RTA_SRC-1]), 4);
if (rta[RTA_DST-1])
memcpy(&new_r->r_dst, RTA_DATA(rta[RTA_DST-1]), 4);
if (rta[RTA_GATEWAY-1])
memcpy(&new_r->r_srcmap, RTA_DATA(rta[RTA_GATEWAY-1]), 4);
new_r->r_src_len = rtm->rtm_src_len;
new_r->r_dst_len = rtm->rtm_dst_len;
new_r->r_srcmask = inet_make_mask(rtm->rtm_src_len);
new_r->r_dstmask = inet_make_mask(rtm->rtm_dst_len);
new_r->r_tos = rtm->rtm_tos;
#ifdef CONFIG_IP_ROUTE_FWMARK
if (rta[RTA_PROTOINFO-1])
memcpy(&new_r->r_fwmark, RTA_DATA(rta[RTA_PROTOINFO-1]), 4);
#endif
new_r->r_action = rtm->rtm_type;
new_r->r_flags = rtm->rtm_flags;
if (rta[RTA_PRIORITY-1])
memcpy(&new_r->r_preference, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
new_r->r_table = table_id;
if (rta[RTA_IIF-1]) {
struct net_device *dev;
rtattr_strlcpy(new_r->r_ifname, rta[RTA_IIF-1], IFNAMSIZ);
new_r->r_ifindex = -1;
dev = __dev_get_by_name(new_r->r_ifname);
if (dev)
new_r->r_ifindex = dev->ifindex;
}
#ifdef CONFIG_NET_CLS_ROUTE
if (rta[RTA_FLOW-1])
memcpy(&new_r->r_tclassid, RTA_DATA(rta[RTA_FLOW-1]), 4);
#endif
rp = &fib_rules;
if (!new_r->r_preference) {
r = fib_rules;
if (r && (r = r->r_next) != NULL) {
rp = &fib_rules->r_next;
if (r->r_preference)
new_r->r_preference = r->r_preference - 1;
}
}
while ( (r = *rp) != NULL ) {
if (r->r_preference > new_r->r_preference)
break;
rp = &r->r_next;
}
new_r->r_next = r;
atomic_inc(&new_r->r_clntref);
write_lock_bh(&fib_rules_lock);
*rp = new_r;
write_unlock_bh(&fib_rules_lock);
return 0;
}
#ifdef CONFIG_NET_CLS_ROUTE
u32 fib_rules_tclass(struct fib_result *res)
{
if (res->r)
return res->r->r_tclassid;
return 0;
}
#endif
static void fib_rules_detach(struct net_device *dev)
{
struct fib_rule *r;
for (r=fib_rules; r; r=r->r_next) {
if (r->r_ifindex == dev->ifindex) {
write_lock_bh(&fib_rules_lock);
r->r_ifindex = -1;
write_unlock_bh(&fib_rules_lock);
}
}
}
static void fib_rules_attach(struct net_device *dev)
{
struct fib_rule *r;
for (r=fib_rules; r; r=r->r_next) {
if (r->r_ifindex == -1 && strcmp(dev->name, r->r_ifname) == 0) {
write_lock_bh(&fib_rules_lock);
r->r_ifindex = dev->ifindex;
write_unlock_bh(&fib_rules_lock);
}
}
}
int fib_lookup(const struct flowi *flp, struct fib_result *res)
{
int err;
struct fib_rule *r, *policy;
struct fib_table *tb;
u32 daddr = flp->fl4_dst;
u32 saddr = flp->fl4_src;
FRprintk("Lookup: %u.%u.%u.%u <- %u.%u.%u.%u ",
NIPQUAD(flp->fl4_dst), NIPQUAD(flp->fl4_src));
read_lock(&fib_rules_lock);
for (r = fib_rules; r; r=r->r_next) {
if (((saddr^r->r_src) & r->r_srcmask) ||
((daddr^r->r_dst) & r->r_dstmask) ||
(r->r_tos && r->r_tos != flp->fl4_tos) ||
#ifdef CONFIG_IP_ROUTE_FWMARK
(r->r_fwmark && r->r_fwmark != flp->fl4_fwmark) ||
#endif
(r->r_ifindex && r->r_ifindex != flp->iif))
continue;
FRprintk("tb %d r %d ", r->r_table, r->r_action);
switch (r->r_action) {
case RTN_UNICAST:
policy = r;
break;
case RTN_UNREACHABLE:
read_unlock(&fib_rules_lock);
return -ENETUNREACH;
default:
case RTN_BLACKHOLE:
read_unlock(&fib_rules_lock);
return -EINVAL;
case RTN_PROHIBIT:
read_unlock(&fib_rules_lock);
return -EACCES;
}
if ((tb = fib_get_table(r->r_table)) == NULL)
continue;
err = tb->tb_lookup(tb, flp, res);
if (err == 0) {
res->r = policy;
if (policy)
atomic_inc(&policy->r_clntref);
read_unlock(&fib_rules_lock);
return 0;
}
if (err < 0 && err != -EAGAIN) {
read_unlock(&fib_rules_lock);
return err;
}
}
FRprintk("FAILURE\n");
read_unlock(&fib_rules_lock);
return -ENETUNREACH;
}
void fib_select_default(const struct flowi *flp, struct fib_result *res)
{
if (res->r && res->r->r_action == RTN_UNICAST &&
FIB_RES_GW(*res) && FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK) {
struct fib_table *tb;
if ((tb = fib_get_table(res->r->r_table)) != NULL)
tb->tb_select_default(tb, flp, res);
}
}
static int fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
if (event == NETDEV_UNREGISTER)
fib_rules_detach(dev);
else if (event == NETDEV_REGISTER)
fib_rules_attach(dev);
return NOTIFY_DONE;
}
static struct notifier_block fib_rules_notifier = {
.notifier_call =fib_rules_event,
};
static __inline__ int inet_fill_rule(struct sk_buff *skb,
struct fib_rule *r,
struct netlink_callback *cb,
unsigned int flags)
{
struct rtmsg *rtm;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
nlh = NLMSG_NEW_ANSWER(skb, cb, RTM_NEWRULE, sizeof(*rtm), flags);
rtm = NLMSG_DATA(nlh);
rtm->rtm_family = AF_INET;
rtm->rtm_dst_len = r->r_dst_len;
rtm->rtm_src_len = r->r_src_len;
rtm->rtm_tos = r->r_tos;
#ifdef CONFIG_IP_ROUTE_FWMARK
if (r->r_fwmark)
RTA_PUT(skb, RTA_PROTOINFO, 4, &r->r_fwmark);
#endif
rtm->rtm_table = r->r_table;
rtm->rtm_protocol = 0;
rtm->rtm_scope = 0;
rtm->rtm_type = r->r_action;
rtm->rtm_flags = r->r_flags;
if (r->r_dst_len)
RTA_PUT(skb, RTA_DST, 4, &r->r_dst);
if (r->r_src_len)
RTA_PUT(skb, RTA_SRC, 4, &r->r_src);
if (r->r_ifname[0])
RTA_PUT(skb, RTA_IIF, IFNAMSIZ, &r->r_ifname);
if (r->r_preference)
RTA_PUT(skb, RTA_PRIORITY, 4, &r->r_preference);
if (r->r_srcmap)
RTA_PUT(skb, RTA_GATEWAY, 4, &r->r_srcmap);
#ifdef CONFIG_NET_CLS_ROUTE
if (r->r_tclassid)
RTA_PUT(skb, RTA_FLOW, 4, &r->r_tclassid);
#endif
nlh->nlmsg_len = skb->tail - b;
return skb->len;
nlmsg_failure:
rtattr_failure:
skb_trim(skb, b - skb->data);
return -1;
}
int inet_dump_rules(struct sk_buff *skb, struct netlink_callback *cb)
{
int idx;
int s_idx = cb->args[0];
struct fib_rule *r;
read_lock(&fib_rules_lock);
for (r=fib_rules, idx=0; r; r = r->r_next, idx++) {
if (idx < s_idx)
continue;
if (inet_fill_rule(skb, r, cb, NLM_F_MULTI) < 0)
break;
}
read_unlock(&fib_rules_lock);
cb->args[0] = idx;
return skb->len;
}
void __init fib_rules_init(void)
{
register_netdevice_notifier(&fib_rules_notifier);
}