diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/Kconfig | 3 | ||||
-rw-r--r-- | net/core/Makefile | 1 | ||||
-rw-r--r-- | net/core/fib_rules.c | 416 | ||||
-rw-r--r-- | net/core/rtnetlink.c | 9 |
4 files changed, 427 insertions, 2 deletions
diff --git a/net/Kconfig b/net/Kconfig index eb855b7fa642..6528a935622c 100644 --- a/net/Kconfig +++ b/net/Kconfig | |||
@@ -251,6 +251,9 @@ config WIRELESS_EXT | |||
251 | 251 | ||
252 | source "net/netlabel/Kconfig" | 252 | source "net/netlabel/Kconfig" |
253 | 253 | ||
254 | config FIB_RULES | ||
255 | bool | ||
256 | |||
254 | endif # if NET | 257 | endif # if NET |
255 | endmenu # Networking | 258 | endmenu # Networking |
256 | 259 | ||
diff --git a/net/core/Makefile b/net/core/Makefile index 2645ba428d48..119568077dab 100644 --- a/net/core/Makefile +++ b/net/core/Makefile | |||
@@ -17,3 +17,4 @@ obj-$(CONFIG_NET_PKTGEN) += pktgen.o | |||
17 | obj-$(CONFIG_WIRELESS_EXT) += wireless.o | 17 | obj-$(CONFIG_WIRELESS_EXT) += wireless.o |
18 | obj-$(CONFIG_NETPOLL) += netpoll.o | 18 | obj-$(CONFIG_NETPOLL) += netpoll.o |
19 | obj-$(CONFIG_NET_DMA) += user_dma.o | 19 | obj-$(CONFIG_NET_DMA) += user_dma.o |
20 | obj-$(CONFIG_FIB_RULES) += fib_rules.o | ||
diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c new file mode 100644 index 000000000000..6cdad24038e2 --- /dev/null +++ b/net/core/fib_rules.c | |||
@@ -0,0 +1,416 @@ | |||
1 | /* | ||
2 | * net/core/fib_rules.c Generic Routing Rules | ||
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 as | ||
6 | * published by the Free Software Foundation, version 2. | ||
7 | * | ||
8 | * Authors: Thomas Graf <tgraf@suug.ch> | ||
9 | */ | ||
10 | |||
11 | #include <linux/config.h> | ||
12 | #include <linux/types.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/list.h> | ||
15 | #include <net/fib_rules.h> | ||
16 | |||
17 | static LIST_HEAD(rules_ops); | ||
18 | static DEFINE_SPINLOCK(rules_mod_lock); | ||
19 | |||
20 | static void notify_rule_change(int event, struct fib_rule *rule, | ||
21 | struct fib_rules_ops *ops); | ||
22 | |||
23 | static struct fib_rules_ops *lookup_rules_ops(int family) | ||
24 | { | ||
25 | struct fib_rules_ops *ops; | ||
26 | |||
27 | rcu_read_lock(); | ||
28 | list_for_each_entry_rcu(ops, &rules_ops, list) { | ||
29 | if (ops->family == family) { | ||
30 | if (!try_module_get(ops->owner)) | ||
31 | ops = NULL; | ||
32 | rcu_read_unlock(); | ||
33 | return ops; | ||
34 | } | ||
35 | } | ||
36 | rcu_read_unlock(); | ||
37 | |||
38 | return NULL; | ||
39 | } | ||
40 | |||
41 | static void rules_ops_put(struct fib_rules_ops *ops) | ||
42 | { | ||
43 | if (ops) | ||
44 | module_put(ops->owner); | ||
45 | } | ||
46 | |||
47 | int fib_rules_register(struct fib_rules_ops *ops) | ||
48 | { | ||
49 | int err = -EEXIST; | ||
50 | struct fib_rules_ops *o; | ||
51 | |||
52 | if (ops->rule_size < sizeof(struct fib_rule)) | ||
53 | return -EINVAL; | ||
54 | |||
55 | if (ops->match == NULL || ops->configure == NULL || | ||
56 | ops->compare == NULL || ops->fill == NULL || | ||
57 | ops->action == NULL) | ||
58 | return -EINVAL; | ||
59 | |||
60 | spin_lock(&rules_mod_lock); | ||
61 | list_for_each_entry(o, &rules_ops, list) | ||
62 | if (ops->family == o->family) | ||
63 | goto errout; | ||
64 | |||
65 | list_add_tail_rcu(&ops->list, &rules_ops); | ||
66 | err = 0; | ||
67 | errout: | ||
68 | spin_unlock(&rules_mod_lock); | ||
69 | |||
70 | return err; | ||
71 | } | ||
72 | |||
73 | EXPORT_SYMBOL_GPL(fib_rules_register); | ||
74 | |||
75 | static void cleanup_ops(struct fib_rules_ops *ops) | ||
76 | { | ||
77 | struct fib_rule *rule, *tmp; | ||
78 | |||
79 | list_for_each_entry_safe(rule, tmp, ops->rules_list, list) { | ||
80 | list_del_rcu(&rule->list); | ||
81 | fib_rule_put(rule); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | int fib_rules_unregister(struct fib_rules_ops *ops) | ||
86 | { | ||
87 | int err = 0; | ||
88 | struct fib_rules_ops *o; | ||
89 | |||
90 | spin_lock(&rules_mod_lock); | ||
91 | list_for_each_entry(o, &rules_ops, list) { | ||
92 | if (o == ops) { | ||
93 | list_del_rcu(&o->list); | ||
94 | cleanup_ops(ops); | ||
95 | goto out; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | err = -ENOENT; | ||
100 | out: | ||
101 | spin_unlock(&rules_mod_lock); | ||
102 | |||
103 | synchronize_rcu(); | ||
104 | |||
105 | return err; | ||
106 | } | ||
107 | |||
108 | EXPORT_SYMBOL_GPL(fib_rules_unregister); | ||
109 | |||
110 | int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, | ||
111 | int flags, struct fib_lookup_arg *arg) | ||
112 | { | ||
113 | struct fib_rule *rule; | ||
114 | int err; | ||
115 | |||
116 | rcu_read_lock(); | ||
117 | |||
118 | list_for_each_entry_rcu(rule, ops->rules_list, list) { | ||
119 | if (rule->ifindex && (rule->ifindex != fl->iif)) | ||
120 | continue; | ||
121 | |||
122 | if (!ops->match(rule, fl, flags)) | ||
123 | continue; | ||
124 | |||
125 | err = ops->action(rule, fl, flags, arg); | ||
126 | if (err != -EAGAIN) { | ||
127 | fib_rule_get(rule); | ||
128 | arg->rule = rule; | ||
129 | goto out; | ||
130 | } | ||
131 | } | ||
132 | |||
133 | err = -ENETUNREACH; | ||
134 | out: | ||
135 | rcu_read_unlock(); | ||
136 | |||
137 | return err; | ||
138 | } | ||
139 | |||
140 | EXPORT_SYMBOL_GPL(fib_rules_lookup); | ||
141 | |||
142 | int fib_nl_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | ||
143 | { | ||
144 | struct fib_rule_hdr *frh = nlmsg_data(nlh); | ||
145 | struct fib_rules_ops *ops = NULL; | ||
146 | struct fib_rule *rule, *r, *last = NULL; | ||
147 | struct nlattr *tb[FRA_MAX+1]; | ||
148 | int err = -EINVAL; | ||
149 | |||
150 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) | ||
151 | goto errout; | ||
152 | |||
153 | ops = lookup_rules_ops(frh->family); | ||
154 | if (ops == NULL) { | ||
155 | err = EAFNOSUPPORT; | ||
156 | goto errout; | ||
157 | } | ||
158 | |||
159 | err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy); | ||
160 | if (err < 0) | ||
161 | goto errout; | ||
162 | |||
163 | if (tb[FRA_IFNAME] && nla_len(tb[FRA_IFNAME]) > IFNAMSIZ) | ||
164 | goto errout; | ||
165 | |||
166 | rule = kzalloc(ops->rule_size, GFP_KERNEL); | ||
167 | if (rule == NULL) { | ||
168 | err = -ENOMEM; | ||
169 | goto errout; | ||
170 | } | ||
171 | |||
172 | if (tb[FRA_PRIORITY]) | ||
173 | rule->pref = nla_get_u32(tb[FRA_PRIORITY]); | ||
174 | |||
175 | if (tb[FRA_IFNAME]) { | ||
176 | struct net_device *dev; | ||
177 | |||
178 | rule->ifindex = -1; | ||
179 | if (nla_strlcpy(rule->ifname, tb[FRA_IFNAME], | ||
180 | IFNAMSIZ) >= IFNAMSIZ) | ||
181 | goto errout_free; | ||
182 | |||
183 | dev = __dev_get_by_name(rule->ifname); | ||
184 | if (dev) | ||
185 | rule->ifindex = dev->ifindex; | ||
186 | } | ||
187 | |||
188 | rule->action = frh->action; | ||
189 | rule->flags = frh->flags; | ||
190 | rule->table = frh->table; | ||
191 | |||
192 | if (!rule->pref && ops->default_pref) | ||
193 | rule->pref = ops->default_pref(); | ||
194 | |||
195 | err = ops->configure(rule, skb, nlh, frh, tb); | ||
196 | if (err < 0) | ||
197 | goto errout_free; | ||
198 | |||
199 | list_for_each_entry(r, ops->rules_list, list) { | ||
200 | if (r->pref > rule->pref) | ||
201 | break; | ||
202 | last = r; | ||
203 | } | ||
204 | |||
205 | fib_rule_get(rule); | ||
206 | |||
207 | if (last) | ||
208 | list_add_rcu(&rule->list, &last->list); | ||
209 | else | ||
210 | list_add_rcu(&rule->list, ops->rules_list); | ||
211 | |||
212 | notify_rule_change(RTM_NEWRULE, rule, ops); | ||
213 | rules_ops_put(ops); | ||
214 | return 0; | ||
215 | |||
216 | errout_free: | ||
217 | kfree(rule); | ||
218 | errout: | ||
219 | rules_ops_put(ops); | ||
220 | return err; | ||
221 | } | ||
222 | |||
223 | int fib_nl_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) | ||
224 | { | ||
225 | struct fib_rule_hdr *frh = nlmsg_data(nlh); | ||
226 | struct fib_rules_ops *ops = NULL; | ||
227 | struct fib_rule *rule; | ||
228 | struct nlattr *tb[FRA_MAX+1]; | ||
229 | int err = -EINVAL; | ||
230 | |||
231 | if (nlh->nlmsg_len < nlmsg_msg_size(sizeof(*frh))) | ||
232 | goto errout; | ||
233 | |||
234 | ops = lookup_rules_ops(frh->family); | ||
235 | if (ops == NULL) { | ||
236 | err = EAFNOSUPPORT; | ||
237 | goto errout; | ||
238 | } | ||
239 | |||
240 | err = nlmsg_parse(nlh, sizeof(*frh), tb, FRA_MAX, ops->policy); | ||
241 | if (err < 0) | ||
242 | goto errout; | ||
243 | |||
244 | list_for_each_entry(rule, ops->rules_list, list) { | ||
245 | if (frh->action && (frh->action != rule->action)) | ||
246 | continue; | ||
247 | |||
248 | if (frh->table && (frh->table != rule->table)) | ||
249 | continue; | ||
250 | |||
251 | if (tb[FRA_PRIORITY] && | ||
252 | (rule->pref != nla_get_u32(tb[FRA_PRIORITY]))) | ||
253 | continue; | ||
254 | |||
255 | if (tb[FRA_IFNAME] && | ||
256 | nla_strcmp(tb[FRA_IFNAME], rule->ifname)) | ||
257 | continue; | ||
258 | |||
259 | if (!ops->compare(rule, frh, tb)) | ||
260 | continue; | ||
261 | |||
262 | if (rule->flags & FIB_RULE_PERMANENT) { | ||
263 | err = -EPERM; | ||
264 | goto errout; | ||
265 | } | ||
266 | |||
267 | list_del_rcu(&rule->list); | ||
268 | synchronize_rcu(); | ||
269 | notify_rule_change(RTM_DELRULE, rule, ops); | ||
270 | fib_rule_put(rule); | ||
271 | rules_ops_put(ops); | ||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | err = -ENOENT; | ||
276 | errout: | ||
277 | rules_ops_put(ops); | ||
278 | return err; | ||
279 | } | ||
280 | |||
281 | static int fib_nl_fill_rule(struct sk_buff *skb, struct fib_rule *rule, | ||
282 | u32 pid, u32 seq, int type, int flags, | ||
283 | struct fib_rules_ops *ops) | ||
284 | { | ||
285 | struct nlmsghdr *nlh; | ||
286 | struct fib_rule_hdr *frh; | ||
287 | |||
288 | nlh = nlmsg_put(skb, pid, seq, type, sizeof(*frh), flags); | ||
289 | if (nlh == NULL) | ||
290 | return -1; | ||
291 | |||
292 | frh = nlmsg_data(nlh); | ||
293 | frh->table = rule->table; | ||
294 | frh->res1 = 0; | ||
295 | frh->res2 = 0; | ||
296 | frh->action = rule->action; | ||
297 | frh->flags = rule->flags; | ||
298 | |||
299 | if (rule->ifname[0]) | ||
300 | NLA_PUT_STRING(skb, FRA_IFNAME, rule->ifname); | ||
301 | |||
302 | if (rule->pref) | ||
303 | NLA_PUT_U32(skb, FRA_PRIORITY, rule->pref); | ||
304 | |||
305 | if (ops->fill(rule, skb, nlh, frh) < 0) | ||
306 | goto nla_put_failure; | ||
307 | |||
308 | return nlmsg_end(skb, nlh); | ||
309 | |||
310 | nla_put_failure: | ||
311 | return nlmsg_cancel(skb, nlh); | ||
312 | } | ||
313 | |||
314 | int fib_rules_dump(struct sk_buff *skb, struct netlink_callback *cb, int family) | ||
315 | { | ||
316 | int idx = 0; | ||
317 | struct fib_rule *rule; | ||
318 | struct fib_rules_ops *ops; | ||
319 | |||
320 | ops = lookup_rules_ops(family); | ||
321 | if (ops == NULL) | ||
322 | return -EAFNOSUPPORT; | ||
323 | |||
324 | rcu_read_lock(); | ||
325 | list_for_each_entry(rule, ops->rules_list, list) { | ||
326 | if (idx < cb->args[0]) | ||
327 | goto skip; | ||
328 | |||
329 | if (fib_nl_fill_rule(skb, rule, NETLINK_CB(cb->skb).pid, | ||
330 | cb->nlh->nlmsg_seq, RTM_NEWRULE, | ||
331 | NLM_F_MULTI, ops) < 0) | ||
332 | break; | ||
333 | skip: | ||
334 | idx++; | ||
335 | } | ||
336 | rcu_read_unlock(); | ||
337 | cb->args[0] = idx; | ||
338 | rules_ops_put(ops); | ||
339 | |||
340 | return skb->len; | ||
341 | } | ||
342 | |||
343 | EXPORT_SYMBOL_GPL(fib_rules_dump); | ||
344 | |||
345 | static void notify_rule_change(int event, struct fib_rule *rule, | ||
346 | struct fib_rules_ops *ops) | ||
347 | { | ||
348 | int size = nlmsg_total_size(sizeof(struct fib_rule_hdr) + 128); | ||
349 | struct sk_buff *skb = alloc_skb(size, GFP_KERNEL); | ||
350 | |||
351 | if (skb == NULL) | ||
352 | netlink_set_err(rtnl, 0, ops->nlgroup, ENOBUFS); | ||
353 | else if (fib_nl_fill_rule(skb, rule, 0, 0, event, 0, ops) < 0) { | ||
354 | kfree_skb(skb); | ||
355 | netlink_set_err(rtnl, 0, ops->nlgroup, EINVAL); | ||
356 | } else | ||
357 | netlink_broadcast(rtnl, skb, 0, ops->nlgroup, GFP_KERNEL); | ||
358 | } | ||
359 | |||
360 | static void attach_rules(struct list_head *rules, struct net_device *dev) | ||
361 | { | ||
362 | struct fib_rule *rule; | ||
363 | |||
364 | list_for_each_entry(rule, rules, list) { | ||
365 | if (rule->ifindex == -1 && | ||
366 | strcmp(dev->name, rule->ifname) == 0) | ||
367 | rule->ifindex = dev->ifindex; | ||
368 | } | ||
369 | } | ||
370 | |||
371 | static void detach_rules(struct list_head *rules, struct net_device *dev) | ||
372 | { | ||
373 | struct fib_rule *rule; | ||
374 | |||
375 | list_for_each_entry(rule, rules, list) | ||
376 | if (rule->ifindex == dev->ifindex) | ||
377 | rule->ifindex = -1; | ||
378 | } | ||
379 | |||
380 | |||
381 | static int fib_rules_event(struct notifier_block *this, unsigned long event, | ||
382 | void *ptr) | ||
383 | { | ||
384 | struct net_device *dev = ptr; | ||
385 | struct fib_rules_ops *ops; | ||
386 | |||
387 | ASSERT_RTNL(); | ||
388 | rcu_read_lock(); | ||
389 | |||
390 | switch (event) { | ||
391 | case NETDEV_REGISTER: | ||
392 | list_for_each_entry(ops, &rules_ops, list) | ||
393 | attach_rules(ops->rules_list, dev); | ||
394 | break; | ||
395 | |||
396 | case NETDEV_UNREGISTER: | ||
397 | list_for_each_entry(ops, &rules_ops, list) | ||
398 | detach_rules(ops->rules_list, dev); | ||
399 | break; | ||
400 | } | ||
401 | |||
402 | rcu_read_unlock(); | ||
403 | |||
404 | return NOTIFY_DONE; | ||
405 | } | ||
406 | |||
407 | static struct notifier_block fib_rules_notifier = { | ||
408 | .notifier_call = fib_rules_event, | ||
409 | }; | ||
410 | |||
411 | static int __init fib_rules_init(void) | ||
412 | { | ||
413 | return register_netdevice_notifier(&fib_rules_notifier); | ||
414 | } | ||
415 | |||
416 | subsys_initcall(fib_rules_init); | ||
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 30cc1ba6ed5c..aa7cff2257b1 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c | |||
@@ -49,6 +49,7 @@ | |||
49 | #include <net/udp.h> | 49 | #include <net/udp.h> |
50 | #include <net/sock.h> | 50 | #include <net/sock.h> |
51 | #include <net/pkt_sched.h> | 51 | #include <net/pkt_sched.h> |
52 | #include <net/fib_rules.h> | ||
52 | #include <net/netlink.h> | 53 | #include <net/netlink.h> |
53 | #ifdef CONFIG_NET_WIRELESS_RTNETLINK | 54 | #ifdef CONFIG_NET_WIRELESS_RTNETLINK |
54 | #include <linux/wireless.h> | 55 | #include <linux/wireless.h> |
@@ -103,7 +104,7 @@ static const int rtm_min[RTM_NR_FAMILIES] = | |||
103 | [RTM_FAM(RTM_NEWADDR)] = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), | 104 | [RTM_FAM(RTM_NEWADDR)] = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), |
104 | [RTM_FAM(RTM_NEWROUTE)] = NLMSG_LENGTH(sizeof(struct rtmsg)), | 105 | [RTM_FAM(RTM_NEWROUTE)] = NLMSG_LENGTH(sizeof(struct rtmsg)), |
105 | [RTM_FAM(RTM_NEWNEIGH)] = NLMSG_LENGTH(sizeof(struct ndmsg)), | 106 | [RTM_FAM(RTM_NEWNEIGH)] = NLMSG_LENGTH(sizeof(struct ndmsg)), |
106 | [RTM_FAM(RTM_NEWRULE)] = NLMSG_LENGTH(sizeof(struct rtmsg)), | 107 | [RTM_FAM(RTM_NEWRULE)] = NLMSG_LENGTH(sizeof(struct fib_rule_hdr)), |
107 | [RTM_FAM(RTM_NEWQDISC)] = NLMSG_LENGTH(sizeof(struct tcmsg)), | 108 | [RTM_FAM(RTM_NEWQDISC)] = NLMSG_LENGTH(sizeof(struct tcmsg)), |
108 | [RTM_FAM(RTM_NEWTCLASS)] = NLMSG_LENGTH(sizeof(struct tcmsg)), | 109 | [RTM_FAM(RTM_NEWTCLASS)] = NLMSG_LENGTH(sizeof(struct tcmsg)), |
109 | [RTM_FAM(RTM_NEWTFILTER)] = NLMSG_LENGTH(sizeof(struct tcmsg)), | 110 | [RTM_FAM(RTM_NEWTFILTER)] = NLMSG_LENGTH(sizeof(struct tcmsg)), |
@@ -120,7 +121,7 @@ static const int rta_max[RTM_NR_FAMILIES] = | |||
120 | [RTM_FAM(RTM_NEWADDR)] = IFA_MAX, | 121 | [RTM_FAM(RTM_NEWADDR)] = IFA_MAX, |
121 | [RTM_FAM(RTM_NEWROUTE)] = RTA_MAX, | 122 | [RTM_FAM(RTM_NEWROUTE)] = RTA_MAX, |
122 | [RTM_FAM(RTM_NEWNEIGH)] = NDA_MAX, | 123 | [RTM_FAM(RTM_NEWNEIGH)] = NDA_MAX, |
123 | [RTM_FAM(RTM_NEWRULE)] = RTA_MAX, | 124 | [RTM_FAM(RTM_NEWRULE)] = FRA_MAX, |
124 | [RTM_FAM(RTM_NEWQDISC)] = TCA_MAX, | 125 | [RTM_FAM(RTM_NEWQDISC)] = TCA_MAX, |
125 | [RTM_FAM(RTM_NEWTCLASS)] = TCA_MAX, | 126 | [RTM_FAM(RTM_NEWTCLASS)] = TCA_MAX, |
126 | [RTM_FAM(RTM_NEWTFILTER)] = TCA_MAX, | 127 | [RTM_FAM(RTM_NEWTFILTER)] = TCA_MAX, |
@@ -757,6 +758,10 @@ static struct rtnetlink_link link_rtnetlink_table[RTM_NR_MSGTYPES] = | |||
757 | [RTM_NEWNEIGH - RTM_BASE] = { .doit = neigh_add }, | 758 | [RTM_NEWNEIGH - RTM_BASE] = { .doit = neigh_add }, |
758 | [RTM_DELNEIGH - RTM_BASE] = { .doit = neigh_delete }, | 759 | [RTM_DELNEIGH - RTM_BASE] = { .doit = neigh_delete }, |
759 | [RTM_GETNEIGH - RTM_BASE] = { .dumpit = neigh_dump_info }, | 760 | [RTM_GETNEIGH - RTM_BASE] = { .dumpit = neigh_dump_info }, |
761 | #ifdef CONFIG_FIB_RULES | ||
762 | [RTM_NEWRULE - RTM_BASE] = { .doit = fib_nl_newrule }, | ||
763 | [RTM_DELRULE - RTM_BASE] = { .doit = fib_nl_delrule }, | ||
764 | #endif | ||
760 | [RTM_GETRULE - RTM_BASE] = { .dumpit = rtnetlink_dump_all }, | 765 | [RTM_GETRULE - RTM_BASE] = { .dumpit = rtnetlink_dump_all }, |
761 | [RTM_GETNEIGHTBL - RTM_BASE] = { .dumpit = neightbl_dump_info }, | 766 | [RTM_GETNEIGHTBL - RTM_BASE] = { .dumpit = neightbl_dump_info }, |
762 | [RTM_SETNEIGHTBL - RTM_BASE] = { .doit = neightbl_set }, | 767 | [RTM_SETNEIGHTBL - RTM_BASE] = { .doit = neightbl_set }, |