1 /* 2 * net/sched/mirred.c packet mirroring and redirect actions 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 7 * 2 of the License, or (at your option) any later version. 8 * 9 * Authors: Jamal Hadi Salim (2002-4) 10 * 11 * TODO: Add ingress support (and socket redirect support) 12 * 13 */ 14 15 #include <linux/types.h> 16 #include <linux/kernel.h> 17 #include <linux/string.h> 18 #include <linux/errno.h> 19 #include <linux/skbuff.h> 20 #include <linux/rtnetlink.h> 21 #include <linux/module.h> 22 #include <linux/init.h> 23 #include <linux/gfp.h> 24 #include <net/net_namespace.h> 25 #include <net/netlink.h> 26 #include <net/pkt_sched.h> 27 #include <linux/tc_act/tc_mirred.h> 28 #include <net/tc_act/tc_mirred.h> 29 30 #include <linux/if_arp.h> 31 32 #define MIRRED_TAB_MASK 7 33 static struct tcf_common *tcf_mirred_ht[MIRRED_TAB_MASK + 1]; 34 static u32 mirred_idx_gen; 35 static DEFINE_RWLOCK(mirred_lock); 36 37 static struct tcf_hashinfo mirred_hash_info = { 38 .htab = tcf_mirred_ht, 39 .hmask = MIRRED_TAB_MASK, 40 .lock = &mirred_lock, 41 }; 42 43 static inline int tcf_mirred_release(struct tcf_mirred *m, int bind) 44 { 45 if (m) { 46 if (bind) 47 m->tcf_bindcnt--; 48 m->tcf_refcnt--; 49 if(!m->tcf_bindcnt && m->tcf_refcnt <= 0) { 50 dev_put(m->tcfm_dev); 51 tcf_hash_destroy(&m->common, &mirred_hash_info); 52 return 1; 53 } 54 } 55 return 0; 56 } 57 58 static const struct nla_policy mirred_policy[TCA_MIRRED_MAX + 1] = { 59 [TCA_MIRRED_PARMS] = { .len = sizeof(struct tc_mirred) }, 60 }; 61 62 static int tcf_mirred_init(struct nlattr *nla, struct nlattr *est, 63 struct tc_action *a, int ovr, int bind) 64 { 65 struct nlattr *tb[TCA_MIRRED_MAX + 1]; 66 struct tc_mirred *parm; 67 struct tcf_mirred *m; 68 struct tcf_common *pc; 69 struct net_device *dev; 70 int ret, ok_push = 0; 71 72 if (nla == NULL) 73 return -EINVAL; 74 ret = nla_parse_nested(tb, TCA_MIRRED_MAX, nla, mirred_policy); 75 if (ret < 0) 76 return ret; 77 if (tb[TCA_MIRRED_PARMS] == NULL) 78 return -EINVAL; 79 parm = nla_data(tb[TCA_MIRRED_PARMS]); 80 switch (parm->eaction) { 81 case TCA_EGRESS_MIRROR: 82 case TCA_EGRESS_REDIR: 83 break; 84 default: 85 return -EINVAL; 86 } 87 if (parm->ifindex) { 88 dev = __dev_get_by_index(&init_net, parm->ifindex); 89 if (dev == NULL) 90 return -ENODEV; 91 switch (dev->type) { 92 case ARPHRD_TUNNEL: 93 case ARPHRD_TUNNEL6: 94 case ARPHRD_SIT: 95 case ARPHRD_IPGRE: 96 case ARPHRD_VOID: 97 case ARPHRD_NONE: 98 ok_push = 0; 99 break; 100 default: 101 ok_push = 1; 102 break; 103 } 104 } else { 105 dev = NULL; 106 } 107 108 pc = tcf_hash_check(parm->index, a, bind, &mirred_hash_info); 109 if (!pc) { 110 if (dev == NULL) 111 return -EINVAL; 112 pc = tcf_hash_create(parm->index, est, a, sizeof(*m), bind, 113 &mirred_idx_gen, &mirred_hash_info); 114 if (IS_ERR(pc)) 115 return PTR_ERR(pc); 116 ret = ACT_P_CREATED; 117 } else { 118 if (!ovr) { 119 tcf_mirred_release(to_mirred(pc), bind); 120 return -EEXIST; 121 } 122 } 123 m = to_mirred(pc); 124 125 spin_lock_bh(&m->tcf_lock); 126 m->tcf_action = parm->action; 127 m->tcfm_eaction = parm->eaction; 128 if (dev != NULL) { 129 m->tcfm_ifindex = parm->ifindex; 130 if (ret != ACT_P_CREATED) 131 dev_put(m->tcfm_dev); 132 dev_hold(dev); 133 m->tcfm_dev = dev; 134 m->tcfm_ok_push = ok_push; 135 } 136 spin_unlock_bh(&m->tcf_lock); 137 if (ret == ACT_P_CREATED) 138 tcf_hash_insert(pc, &mirred_hash_info); 139 140 return ret; 141 } 142 143 static int tcf_mirred_cleanup(struct tc_action *a, int bind) 144 { 145 struct tcf_mirred *m = a->priv; 146 147 if (m) 148 return tcf_mirred_release(m, bind); 149 return 0; 150 } 151 152 static int tcf_mirred(struct sk_buff *skb, struct tc_action *a, 153 struct tcf_result *res) 154 { 155 struct tcf_mirred *m = a->priv; 156 struct net_device *dev; 157 struct sk_buff *skb2; 158 u32 at; 159 int retval, err = 1; 160 161 spin_lock(&m->tcf_lock); 162 m->tcf_tm.lastuse = jiffies; 163 164 dev = m->tcfm_dev; 165 if (!(dev->flags & IFF_UP)) { 166 if (net_ratelimit()) 167 pr_notice("tc mirred to Houston: device %s is gone!\n", 168 dev->name); 169 goto out; 170 } 171 172 skb2 = skb_act_clone(skb, GFP_ATOMIC); 173 if (skb2 == NULL) 174 goto out; 175 176 m->tcf_bstats.bytes += qdisc_pkt_len(skb2); 177 m->tcf_bstats.packets++; 178 at = G_TC_AT(skb->tc_verd); 179 if (!(at & AT_EGRESS)) { 180 if (m->tcfm_ok_push) 181 skb_push(skb2, skb2->dev->hard_header_len); 182 } 183 184 /* mirror is always swallowed */ 185 if (m->tcfm_eaction != TCA_EGRESS_MIRROR) 186 skb2->tc_verd = SET_TC_FROM(skb2->tc_verd, at); 187 188 skb2->dev = dev; 189 skb2->skb_iif = skb->dev->ifindex; 190 dev_queue_xmit(skb2); 191 err = 0; 192 193 out: 194 if (err) { 195 m->tcf_qstats.overlimits++; 196 m->tcf_bstats.bytes += qdisc_pkt_len(skb); 197 m->tcf_bstats.packets++; 198 /* should we be asking for packet to be dropped? 199 * may make sense for redirect case only 200 */ 201 retval = TC_ACT_SHOT; 202 } else { 203 retval = m->tcf_action; 204 } 205 spin_unlock(&m->tcf_lock); 206 207 return retval; 208 } 209 210 static int tcf_mirred_dump(struct sk_buff *skb, struct tc_action *a, int bind, int ref) 211 { 212 unsigned char *b = skb_tail_pointer(skb); 213 struct tcf_mirred *m = a->priv; 214 struct tc_mirred opt; 215 struct tcf_t t; 216 217 opt.index = m->tcf_index; 218 opt.action = m->tcf_action; 219 opt.refcnt = m->tcf_refcnt - ref; 220 opt.bindcnt = m->tcf_bindcnt - bind; 221 opt.eaction = m->tcfm_eaction; 222 opt.ifindex = m->tcfm_ifindex; 223 NLA_PUT(skb, TCA_MIRRED_PARMS, sizeof(opt), &opt); 224 t.install = jiffies_to_clock_t(jiffies - m->tcf_tm.install); 225 t.lastuse = jiffies_to_clock_t(jiffies - m->tcf_tm.lastuse); 226 t.expires = jiffies_to_clock_t(m->tcf_tm.expires); 227 NLA_PUT(skb, TCA_MIRRED_TM, sizeof(t), &t); 228 return skb->len; 229 230 nla_put_failure: 231 nlmsg_trim(skb, b); 232 return -1; 233 } 234 235 static struct tc_action_ops act_mirred_ops = { 236 .kind = "mirred", 237 .hinfo = &mirred_hash_info, 238 .type = TCA_ACT_MIRRED, 239 .capab = TCA_CAP_NONE, 240 .owner = THIS_MODULE, 241 .act = tcf_mirred, 242 .dump = tcf_mirred_dump, 243 .cleanup = tcf_mirred_cleanup, 244 .lookup = tcf_hash_search, 245 .init = tcf_mirred_init, 246 .walk = tcf_generic_walker 247 }; 248 249 MODULE_AUTHOR("Jamal Hadi Salim(2002)"); 250 MODULE_DESCRIPTION("Device Mirror/redirect actions"); 251 MODULE_LICENSE("GPL"); 252 253 static int __init mirred_init_module(void) 254 { 255 pr_info("Mirror/redirect action on\n"); 256 return tcf_register_action(&act_mirred_ops); 257 } 258 259 static void __exit mirred_cleanup_module(void) 260 { 261 tcf_unregister_action(&act_mirred_ops); 262 } 263 264 module_init(mirred_init_module); 265 module_exit(mirred_cleanup_module); 266