treewide: fix replace nbd@openwrt.org with nbd@nbd.name
[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 static LIST_HEAD(trelay_devs);
24 static struct dentry *debugfs_dir;
25
26 struct trelay {
27         struct list_head list;
28         struct net_device *dev1, *dev2;
29         struct dentry *debugfs;
30         char name[];
31 };
32
33 rx_handler_result_t trelay_handle_frame(struct sk_buff **pskb)
34 {
35         struct net_device *dev;
36         struct sk_buff *skb = *pskb;
37
38         dev = rcu_dereference(skb->dev->rx_handler_data);
39         if (!dev)
40                 return RX_HANDLER_PASS;
41
42         if (skb->protocol == htons(ETH_P_PAE))
43                 return RX_HANDLER_PASS;
44
45         skb_push(skb, ETH_HLEN);
46         skb->dev = dev;
47         skb_forward_csum(skb);
48         dev_queue_xmit(skb);
49
50         return RX_HANDLER_CONSUMED;
51 }
52
53 static int trelay_open(struct inode *inode, struct file *file)
54 {
55         file->private_data = inode->i_private;
56         return 0;
57 }
58
59 static int trelay_do_remove(struct trelay *tr)
60 {
61         list_del(&tr->list);
62
63         dev_put(tr->dev1);
64         dev_put(tr->dev2);
65
66         netdev_rx_handler_unregister(tr->dev1);
67         netdev_rx_handler_unregister(tr->dev2);
68
69         debugfs_remove_recursive(tr->debugfs);
70         kfree(tr);
71
72         return 0;
73 }
74
75 static struct trelay *trelay_find(struct net_device *dev)
76 {
77         struct trelay *tr;
78
79         list_for_each_entry(tr, &trelay_devs, list) {
80                 if (tr->dev1 == dev || tr->dev2 == dev)
81                         return tr;
82         }
83         return NULL;
84 }
85
86 static int tr_device_event(struct notifier_block *unused, unsigned long event,
87                            void *ptr)
88 {
89         struct net_device *dev = ptr;
90         struct trelay *tr;
91
92         if (event != NETDEV_UNREGISTER)
93                 goto out;
94
95         tr = trelay_find(dev);
96         if (!tr)
97                 goto out;
98
99         trelay_do_remove(tr);
100
101 out:
102         return NOTIFY_DONE;
103 }
104
105 static ssize_t trelay_remove_write(struct file *file, const char __user *ubuf,
106                                    size_t count, loff_t *ppos)
107 {
108         struct trelay *tr = file->private_data;
109         int ret;
110
111         rtnl_lock();
112         ret = trelay_do_remove(tr);
113         rtnl_unlock();
114
115         if (ret < 0)
116                  return ret;
117
118         return count;
119 }
120
121 static const struct file_operations fops_remove = {
122         .owner = THIS_MODULE,
123         .open = trelay_open,
124         .write = trelay_remove_write,
125         .llseek = default_llseek,
126 };
127
128
129 static int trelay_do_add(char *name, char *devn1, char *devn2)
130 {
131         struct net_device *dev1, *dev2;
132         struct trelay *tr, *tr1;
133         int ret;
134
135         tr = kzalloc(sizeof(*tr) + strlen(name) + 1, GFP_KERNEL);
136         if (!tr)
137                 return -ENOMEM;
138
139         rtnl_lock();
140         rcu_read_lock();
141
142         ret = -EEXIST;
143         list_for_each_entry(tr1, &trelay_devs, list) {
144                 if (!strcmp(tr1->name, name))
145                         goto out;
146         }
147
148         ret = -ENOENT;
149         dev1 = dev_get_by_name_rcu(&init_net, devn1);
150         dev2 = dev_get_by_name_rcu(&init_net, devn2);
151         if (!dev1 || !dev2)
152                 goto out;
153
154         ret = netdev_rx_handler_register(dev1, trelay_handle_frame, dev2);
155         if (ret < 0)
156                 goto out;
157
158         ret = netdev_rx_handler_register(dev2, trelay_handle_frame, dev1);
159         if (ret < 0) {
160                 netdev_rx_handler_unregister(dev1);
161                 goto out;
162         }
163
164         dev_hold(dev1);
165         dev_hold(dev2);
166
167         strcpy(tr->name, name);
168         tr->dev1 = dev1;
169         tr->dev2 = dev2;
170         list_add_tail(&tr->list, &trelay_devs);
171
172         tr->debugfs = debugfs_create_dir(name, debugfs_dir);
173         debugfs_create_file("remove", S_IWUSR, tr->debugfs, tr, &fops_remove);
174         ret = 0;
175
176 out:
177         rcu_read_unlock();
178         rtnl_unlock();
179         if (ret < 0)
180                 kfree(tr);
181
182         return ret;
183 }
184
185 static ssize_t trelay_add_write(struct file *file, const char __user *ubuf,
186                                 size_t count, loff_t *ppos)
187 {
188         char buf[256];
189         char *dev1, *dev2, *tmp;
190         ssize_t len, ret;
191
192         len = min(count, sizeof(buf) - 1);
193         if (copy_from_user(buf, ubuf, len))
194                 return -EFAULT;
195
196         buf[len] = 0;
197
198         if ((tmp = strchr(buf, '\n')))
199                 *tmp = 0;
200
201         dev1 = strchr(buf, ',');
202         if (!dev1)
203                 return -EINVAL;
204
205         *(dev1++) = 0;
206
207         dev2 = strchr(dev1, ',');
208         if (!dev2)
209                 return -EINVAL;
210
211         *(dev2++) = 0;
212         if (strchr(dev2, ','))
213                 return -EINVAL;
214
215         if (!strlen(buf) || !strlen(dev1) || !strlen(dev2))
216                 return -EINVAL;
217
218         ret = trelay_do_add(buf, dev1, dev2);
219         if (ret < 0)
220                 return ret;
221
222         return count;
223 }
224
225 static const struct file_operations fops_add = {
226         .owner = THIS_MODULE,
227         .write = trelay_add_write,
228         .llseek = default_llseek,
229 };
230
231 static struct notifier_block tr_dev_notifier = {
232         .notifier_call = tr_device_event
233 };
234
235 static int __init trelay_init(void)
236 {
237         int ret;
238
239         debugfs_dir = debugfs_create_dir("trelay", NULL);
240         if (!debugfs_dir)
241                 return -ENOMEM;
242
243         debugfs_create_file("add", S_IWUSR, debugfs_dir, NULL, &fops_add);
244
245         ret = register_netdevice_notifier(&tr_dev_notifier);
246         if (ret < 0)
247                 goto error;
248
249         return 0;
250
251 error:
252         debugfs_remove_recursive(debugfs_dir);
253         return ret;
254 }
255
256 static void __exit trelay_exit(void)
257 {
258         struct trelay *tr, *tmp;
259
260         unregister_netdevice_notifier(&tr_dev_notifier);
261
262         rtnl_lock();
263         list_for_each_entry_safe(tr, tmp, &trelay_devs, list)
264                 trelay_do_remove(tr);
265         rtnl_unlock();
266
267         debugfs_remove_recursive(debugfs_dir);
268 }
269
270 module_init(trelay_init);
271 module_exit(trelay_exit);
272 MODULE_LICENSE("GPL");