mirred.c 6.43 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/*
 * net/sched/mirred.c	packet mirroring and redirect actions
 *
 *		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.
 *
 * Authors:	Jamal Hadi Salim (2002-4)
 *
 * TODO: Add ingress support (and socket redirect support)
 *
 */

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <net/sock.h>
#include <net/pkt_sched.h>
#include <linux/tc_act/tc_mirred.h>
#include <net/tc_act/tc_mirred.h>

#include <linux/etherdevice.h>
#include <linux/if_arp.h>


/* use generic hash table */
#define MY_TAB_SIZE     8
#define MY_TAB_MASK     (MY_TAB_SIZE - 1)
static u32 idx_gen;
static struct tcf_mirred *tcf_mirred_ht[MY_TAB_SIZE];
static rwlock_t mirred_lock = RW_LOCK_UNLOCKED;

/* ovewrride the defaults */
52 53 54 55
#define tcf_st		tcf_mirred
#define tc_st		tc_mirred
#define tcf_t_lock	mirred_lock
#define tcf_ht		tcf_mirred_ht
56 57 58 59 60 61 62 63

#define CONFIG_NET_ACT_INIT 1
#include <net/pkt_act.h>

static inline int
tcf_mirred_release(struct tcf_mirred *p, int bind)
{
	if (p) {
64
		if (bind)
65 66 67 68 69 70 71 72 73 74 75
			p->bindcnt--;
		p->refcnt--;
		if(!p->bindcnt && p->refcnt <= 0) {
			dev_put(p->dev);
			tcf_hash_destroy(p);
			return 1;
		}
	}
	return 0;
}

76
static int
77 78
tcf_mirred_init(struct rtattr *rta, struct rtattr *est, struct tc_action *a,
                int ovr, int bind)
79 80 81 82 83
{
	struct rtattr *tb[TCA_MIRRED_MAX];
	struct tc_mirred *parm;
	struct tcf_mirred *p;
	struct net_device *dev = NULL;
84 85
	int ret = 0;
	int ok_push = 0;
86

87 88 89
	if (rta == NULL || rtattr_parse(tb, TCA_MIRRED_MAX, RTA_DATA(rta),
	                                RTA_PAYLOAD(rta)) < 0)
		return -EINVAL;
90

91 92 93 94
	if (tb[TCA_MIRRED_PARMS-1] == NULL ||
	    RTA_PAYLOAD(tb[TCA_MIRRED_PARMS-1]) < sizeof(*parm))
		return -EINVAL;
	parm = RTA_DATA(tb[TCA_MIRRED_PARMS-1]);
95 96

	if (parm->ifindex) {
97 98 99
		dev = __dev_get_by_index(parm->ifindex);
		if (dev == NULL)
			return -ENODEV;
100 101 102 103 104 105 106
		switch (dev->type) {
			case ARPHRD_TUNNEL:
			case ARPHRD_TUNNEL6:
			case ARPHRD_SIT:
			case ARPHRD_IPGRE:
			case ARPHRD_VOID:
			case ARPHRD_NONE:
107
				ok_push = 0;
108 109
				break;
			default:
110
				ok_push = 1;
111 112 113 114
				break;
		}
	}

115 116 117 118 119 120 121 122 123 124 125 126
	p = tcf_hash_check(parm->index, a, ovr, bind);
	if (p == NULL) {
		if (!parm->ifindex)
			return -EINVAL;
		p = tcf_hash_create(parm->index, est, a, sizeof(*p), ovr, bind);
		if (p == NULL)
			return -ENOMEM;
		ret = ACT_P_CREATED;
	} else {
		if (!ovr) {
			tcf_mirred_release(p, bind);
			return -EEXIST;
127 128 129
		}
	}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
	spin_lock(&p->lock);
	p->action = parm->action;
	p->eaction = parm->eaction;
	if (parm->ifindex) {
		p->ifindex = parm->ifindex;
		if (ret != ACT_P_CREATED)
			dev_put(p->dev);
		p->dev = dev;
		dev_hold(dev);
		p->ok_push = ok_push;
	}
	spin_unlock(&p->lock);
	if (ret == ACT_P_CREATED)
		tcf_hash_insert(p);

145 146 147
	DPRINTK("tcf_mirred_init index %d action %d eaction %d device %s "
	        "ifindex %d\n", parm->index, parm->action, parm->eaction,
	        dev->name, parm->ifindex);
148
	return ret;
149 150
}

151
static int
152 153
tcf_mirred_cleanup(struct tc_action *a, int bind)
{
154 155 156
	struct tcf_mirred *p = PRIV(a, mirred);

	if (p != NULL)
157 158 159 160
		return tcf_mirred_release(p, bind);
	return 0;
}

161
static int
162 163
tcf_mirred(struct sk_buff **pskb, struct tc_action *a)
{
164
	struct tcf_mirred *p = PRIV(a, mirred);
165 166 167
	struct net_device *dev;
	struct sk_buff *skb2 = NULL;
	struct sk_buff *skb = *pskb;
168
	u32 at = G_TC_AT(skb->tc_verd);
169 170 171

	spin_lock(&p->lock);

172
	dev = p->dev;
173 174
	p->tm.lastuse = jiffies;

175
	if (!(dev->flags&IFF_UP) ) {
176 177
		if (net_ratelimit())
			printk("mirred to Houston: device %s is gone!\n",
178
			       dev->name);
179
bad_mirred:
180
		if (skb2 != NULL)
181
			kfree_skb(skb2);
182 183 184
		p->qstats.overlimits++;
		p->bstats.bytes += skb->len;
		p->bstats.packets++;
185 186
		spin_unlock(&p->lock);
		/* should we be asking for packet to be dropped?
187
		 * may make sense for redirect case only
188 189
		*/
		return TC_ACT_SHOT;
190
	}
191 192

	skb2 = skb_clone(skb, GFP_ATOMIC);
193
	if (skb2 == NULL)
194
		goto bad_mirred;
195
	if (p->eaction != TCA_EGRESS_MIRROR && p->eaction != TCA_EGRESS_REDIR) {
196
		if (net_ratelimit())
197
			printk("tcf_mirred unknown action %d\n", p->eaction);
198 199 200
		goto bad_mirred;
	}

201 202
	p->bstats.bytes += skb2->len;
	p->bstats.packets++;
203 204
	if (!(at & AT_EGRESS))
		if (p->ok_push)
205 206 207
			skb_push(skb2, skb2->dev->hard_header_len);

	/* mirror is always swallowed */
208 209
	if (p->eaction != TCA_EGRESS_MIRROR)
		skb2->tc_verd = SET_TC_FROM(skb2->tc_verd, at);
210 211 212 213 214 215 216 217

	skb2->dev = dev;
	skb2->input_dev = skb->dev;
	dev_queue_xmit(skb2);
	spin_unlock(&p->lock);
	return p->action;
}

218
static int
219
tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind, int ref)
220 221 222
{
	unsigned char *b = skb->tail;
	struct tc_mirred opt;
223
	struct tcf_mirred *p = PRIV(a, mirred);
224 225 226 227 228 229 230 231
	struct tcf_t t;

	opt.index = p->index;
	opt.action = p->action;
	opt.refcnt = p->refcnt - ref;
	opt.bindcnt = p->bindcnt - bind;
	opt.eaction = p->eaction;
	opt.ifindex = p->ifindex;
232 233 234
	DPRINTK("tcf_mirred_dump index %d action %d eaction %d ifindex %d\n",
	         p->index, p->action, p->eaction, p->ifindex);
	RTA_PUT(skb, TCA_MIRRED_PARMS, sizeof(opt), &opt);
235 236 237
	t.install = jiffies_to_clock_t(jiffies - p->tm.install);
	t.lastuse = jiffies_to_clock_t(jiffies - p->tm.lastuse);
	t.expires = jiffies_to_clock_t(p->tm.expires);
238
	RTA_PUT(skb, TCA_MIRRED_TM, sizeof(t), &t);
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
	return skb->len;

      rtattr_failure:
	skb_trim(skb, b - skb->data);
	return -1;
}

static struct tc_action_ops act_mirred_ops = {
	.kind		=	"mirred",
	.type		=	TCA_ACT_MIRRED,
	.capab		=	TCA_CAP_NONE,
	.owner		=	THIS_MODULE,
	.act		=	tcf_mirred,
	.dump		=	tcf_mirred_dump,
	.cleanup	=	tcf_mirred_cleanup,
	.lookup		=	tcf_hash_search,
	.init		=	tcf_mirred_init,
	.walk		=	tcf_generic_walker
};

MODULE_AUTHOR("Jamal Hadi Salim(2002)");
MODULE_DESCRIPTION("Device Mirror/redirect actions");
MODULE_LICENSE("GPL");

static int __init
mirred_init_module(void)
{
	printk("Mirror/redirect action on\n");
	return tcf_register_action(&act_mirred_ops);
}

static void __exit
mirred_cleanup_module(void)
{
	tcf_unregister_action(&act_mirred_ops);
}

module_init(mirred_init_module);
module_exit(mirred_cleanup_module);