ath79: do not build TP-Link tiny images by default
[oweals/openwrt.git] / package / kernel / trelay / src / trelay.c
1 /*
2  * trelay.c: Trivial Ethernet Relay
3  *
4  * Copyright (C) 2012 Felix Fietkau <nbd@nbd.name>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  */
16 #include <linux/module.h>
17 #include <linux/list.h>
18 #include <linux/mutex.h>
19 #include <linux/netdevice.h>
20 #include <linux/rtnetlink.h>
21 #include <linux/debugfs.h>
22
23 #define trelay_log(loglevel, tr, fmt, ...) \
24         printk(loglevel "trelay: %s <-> %s: " fmt "\n", \
25                 tr->dev1->name, tr->dev2->name, ##__VA_ARGS__);
26
27 static LIST_HEAD(trelay_devs);
28 static struct dentry *debugfs_dir;
29
30 struct trelay {
31         struct list_head list;
32         struct net_device *dev1, *dev2;
33         struct dentry *debugfs;
34         int to_remove;
35         char name[];
36 };
37
38 rx_handler_result_t trelay_handle_frame(struct sk_buff **pskb)
39 {
40         struct net_device *dev;
41         struct sk_buff *skb = *pskb;
42
43         dev = rcu_dereference(skb->dev->rx_handler_data);
44         if (!dev)
45                 return RX_HANDLER_PASS;
46
47         if (skb->protocol == htons(ETH_P_PAE))
48                 return RX_HANDLER_PASS;
49
50         skb_push(skb, ETH_HLEN);
51         skb->dev = dev;
52         skb_forward_csum(skb);
53         dev_queue_xmit(skb);
54
55         return RX_HANDLER_CONSUMED;
56 }
57
58 static int trelay_open(struct inode *inode, struct file *file)
59 {
60         file->private_data = inode->i_private;
61         return 0;
62 }
63
64 static int trelay_do_remove(struct trelay *tr)
65 {
66         list_del(&tr->list);
67
68         /* First and before all, ensure that the debugfs file is removed
69          * to prevent dangling pointer in file->private_data */
70         debugfs_remove_recursive(tr->debugfs);
71
72         dev_put(tr->dev1);
73         dev_put(tr->dev2);
74
75         netdev_rx_handler_unregister(tr->dev1);
76         netdev_rx_handler_unregister(tr->dev2);
77
78         trelay_log(KERN_INFO, tr, "stopped");
79
80         kfree(tr);
81
82         return 0;
83 }
84
85 static struct trelay *trelay_find(struct net_device *dev)
86 {
87         struct trelay *tr;
88
89         list_for_each_entry(tr, &trelay_devs, list) {
90                 if (tr->dev1 == dev || tr->dev2 == dev)
91                         return tr;
92         }
93         return NULL;
94 }
95
96 static int tr_device_event(struct notifier_block *unused, unsigned long event,
97                            void *ptr)
98 {
99         struct net_device *dev = netdev_notifier_info_to_dev(ptr);
100         struct trelay *tr;
101
102         if (event != NETDEV_UNREGISTER)
103                 goto out;
104
105         tr = trelay_find(dev);
106         if (!tr)
107                 goto out;
108
109         trelay_do_remove(tr);
110
111 out:
112         return NOTIFY_DONE;
113 }
114
115 static ssize_t trelay_remove_write(struct file *file, const char __user *ubuf,
116                                    size_t count, loff_t *ppos)
117 {
118         struct trelay *tr = file->private_data;
119         tr->to_remove = 1;
120
121         return count;
122 }
123
124 static int trelay_remove_release(struct inode *inode, struct file *file)
125 {
126         struct trelay *tr, *tmp;
127
128         /* This is the only file op that is called outside debugfs_use_file_*()
129          * context which means that: (1) this file can be removed and
130          * (2) file->private_data may no longer be valid */
131         rtnl_lock();
132         list_for_each_entry_safe(tr, tmp, &trelay_devs, list)
133                 if (tr->to_remove)
134                         trelay_do_remove(tr);
135         rtnl_unlock();
136
137         return 0;
138 }
139
140 static const struct file_operations fops_remove = {
141         .owner = THIS_MODULE,
142         .open = trelay_open,
143         .write = trelay_remove_write,
144         .llseek = default_llseek,
145         .release = trelay_remove_release,
146 };
147
148
149 static int trelay_do_add(char *name, char *devn1, char *devn2)
150 {
151         struct net_device *dev1, *dev2;
152         struct trelay *tr, *tr1;
153         int ret;
154
155         tr = kzalloc(sizeof(*tr) + strlen(name) + 1, GFP_KERNEL);
156         if (!tr)
157                 return -ENOMEM;
158
159         rtnl_lock();
160         rcu_read_lock();
161
162         ret = -EEXIST;
163         list_for_each_entry(tr1, &trelay_devs, list) {
164                 if (!strcmp(tr1->name, name))
165                         goto out;
166         }
167
168         ret = -ENOENT;
169         dev1 = dev_get_by_name_rcu(&init_net, devn1);
170         dev2 = dev_get_by_name_rcu(&init_net, devn2);
171         if (!dev1 || !dev2)
172                 goto out;
173
174         ret = netdev_rx_handler_register(dev1, trelay_handle_frame, dev2);
175         if (ret < 0)
176                 goto out;
177
178         ret = netdev_rx_handler_register(dev2, trelay_handle_frame, dev1);
179         if (ret < 0) {
180                 netdev_rx_handler_unregister(dev1);
181                 goto out;
182         }
183
184         dev_hold(dev1);
185         dev_hold(dev2);
186
187         strcpy(tr->name, name);
188         tr->dev1 = dev1;
189         tr->dev2 = dev2;
190         list_add_tail(&tr->list, &trelay_devs);
191
192         trelay_log(KERN_INFO, tr, "started");
193
194         tr->debugfs = debugfs_create_dir(name, debugfs_dir);
195         debugfs_create_file("remove", S_IWUSR, tr->debugfs, tr, &fops_remove);
196         ret = 0;
197
198 out:
199         rcu_read_unlock();
200         rtnl_unlock();
201         if (ret < 0)
202                 kfree(tr);
203
204         return ret;
205 }
206
207 static ssize_t trelay_add_write(struct file *file, const char __user *ubuf,
208                                 size_t count, loff_t *ppos)
209 {
210         char buf[256];
211         char *dev1, *dev2, *tmp;
212         ssize_t len, ret;
213
214         len = min(count, sizeof(buf) - 1);
215         if (copy_from_user(buf, ubuf, len))
216                 return -EFAULT;
217
218         buf[len] = 0;
219
220         if ((tmp = strchr(buf, '\n')))
221                 *tmp = 0;
222
223         dev1 = strchr(buf, ',');
224         if (!dev1)
225                 return -EINVAL;
226
227         *(dev1++) = 0;
228
229         dev2 = strchr(dev1, ',');
230         if (!dev2)
231                 return -EINVAL;
232
233         *(dev2++) = 0;
234         if (strchr(dev2, ','))
235                 return -EINVAL;
236
237         if (!strlen(buf) || !strlen(dev1) || !strlen(dev2))
238                 return -EINVAL;
239
240         ret = trelay_do_add(buf, dev1, dev2);
241         if (ret < 0)
242                 return ret;
243
244         return count;
245 }
246
247 static const struct file_operations fops_add = {
248         .owner = THIS_MODULE,
249         .write = trelay_add_write,
250         .llseek = default_llseek,
251 };
252
253 static struct notifier_block tr_dev_notifier = {
254         .notifier_call = tr_device_event
255 };
256
257 static int __init trelay_init(void)
258 {
259         int ret;
260
261         debugfs_dir = debugfs_create_dir("trelay", NULL);
262         if (!debugfs_dir)
263                 return -ENOMEM;
264
265         debugfs_create_file("add", S_IWUSR, debugfs_dir, NULL, &fops_add);
266
267         ret = register_netdevice_notifier(&tr_dev_notifier);
268         if (ret < 0)
269                 goto error;
270
271         return 0;
272
273 error:
274         debugfs_remove_recursive(debugfs_dir);
275         return ret;
276 }
277
278 static void __exit trelay_exit(void)
279 {
280         struct trelay *tr, *tmp;
281
282         unregister_netdevice_notifier(&tr_dev_notifier);
283
284         rtnl_lock();
285         list_for_each_entry_safe(tr, tmp, &trelay_devs, list)
286                 trelay_do_remove(tr);
287         rtnl_unlock();
288
289         debugfs_remove_recursive(debugfs_dir);
290 }
291
292 module_init(trelay_init);
293 module_exit(trelay_exit);
294 MODULE_LICENSE("GPL");