/* * Linux Back to back driver * Connect two network device back to back * * vim:set expandtab shiftwidth=3 softtabstop=3: * * Copyright (c) 2014 AVM GmbH * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions, and the following disclaimer, * without modification. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed and/or modified under the * terms of the GNU General Public License as published by the Free Software * Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0) #define PDE_DATA(_inode) (PDE(_inode)->data) #endif struct b2bstat { unsigned long eapol; unsigned long forward_ok; unsigned long forward_fail; unsigned long forward_cn; unsigned long forward_error; unsigned long drop; }; struct backtobacktuple { int enabled; char ifname1[IFNAMSIZ]; char ifname2[IFNAMSIZ]; struct net_device *dev1; struct net_device *dev2; struct b2bstat stats[2]; }; static struct backtobacktuple tuple[CONFIG_B2B_NUM]; /* ------------------------------------------------------------------------ */ #ifdef CONFIG_TI_DOCSIS_INPUT_DEV /* * JZ-31022: OSS11 - CM Standard MIB Counter Test. * * OSS11 Procedure 1.1 * Counter of "Ethernet CPE Interface" are wrong: * - dot1dTpPortInFrames.1 und dot1dTpPortOutFrames.1 * - ifInBroadcastPkts.1 * * Problem: * dbridge_main.c:DBridge_receive() sets skb->ti_docsis_input_dev * from skb->sk_iif if skb->ti_docsis_input_dev is NULL. * So the input device is "l2sd0", but should be "l2sd0.2". * This code is devived from br_input.c:br_handle_frame() * * Solution: * set skb->ti_docsis_input_dev if not set. * * 2017-03-12, calle */ #endif static rx_handler_result_t b2b_rxhandler1(struct sk_buff **pskb) { struct sk_buff *skb = *pskb; struct net_device *dev = skb->dev; struct backtobacktuple *p = rcu_dereference(dev->rx_handler_data); struct b2bstat *stats = &p->stats[0]; if (skb->protocol == __constant_htons(ETH_P_PAE)) { stats->eapol++; return RX_HANDLER_PASS; } if (p->dev2) { skb_push(skb, skb->data - skb_mac_header(skb)); skb_orphan(skb); nf_reset(skb); skb_set_dev(skb, p->dev2); skb->pkt_type = PACKET_OUTGOING; #ifdef CONFIG_TI_DOCSIS_INPUT_DEV if (!skb->ti_docsis_input_dev) skb->ti_docsis_input_dev = dev; #endif switch (dev_queue_xmit(skb)) { case NET_XMIT_SUCCESS: stats->forward_ok++; break; case NET_XMIT_DROP: stats->forward_fail++; break; case NET_XMIT_CN: stats->forward_cn++; break; default: stats->forward_error++; break; } return RX_HANDLER_CONSUMED; } kfree_skb(skb); stats->drop++; return RX_HANDLER_CONSUMED; } static rx_handler_result_t b2b_rxhandler2(struct sk_buff **pskb) { struct sk_buff *skb = *pskb; struct net_device *dev = skb->dev; struct backtobacktuple *p = rcu_dereference(dev->rx_handler_data); struct b2bstat *stats = &p->stats[1]; if (skb->protocol == __constant_htons(ETH_P_PAE)) { stats->eapol++; return RX_HANDLER_PASS; } if (p->dev1) { skb_push(skb, skb->data - skb_mac_header(skb)); skb_orphan(skb); nf_reset(skb); skb_set_dev(skb, p->dev1); skb->pkt_type = PACKET_OUTGOING; #ifdef CONFIG_TI_DOCSIS_INPUT_DEV if (!skb->ti_docsis_input_dev) skb->ti_docsis_input_dev = dev; #endif switch (dev_queue_xmit(skb)) { case NET_XMIT_SUCCESS: stats->forward_ok++; break; case NET_XMIT_DROP: stats->forward_fail++; break; case NET_XMIT_CN: stats->forward_cn++; break; default: stats->forward_error++; break; } return RX_HANDLER_CONSUMED; } kfree_skb(skb); stats->drop++; return RX_HANDLER_CONSUMED; } static void rx_handler_register(struct net_device **devp, struct net_device *dev, rx_handler_func_t *rx_handler, void *rx_handler_data) { if (netdev_rx_handler_register(dev, rx_handler, rx_handler_data) < 0) { rx_handler_func_t *old = rcu_dereference(dev->rx_handler); printk(KERN_ERR "B2B: %s: rx handler already registered (%pf)\n", dev->name, old); } else { *devp = dev; printk(KERN_INFO "B2B: %s: rx handler registered\n", dev->name); } } static void b2b_dev_register(struct net_device **devp, const char *ifname, rx_handler_func_t *rx_handler, void *rx_handler_data) { struct net_device *dev; if (*devp) return; if ((dev = dev_get_by_name(&init_net, ifname)) != 0) { if (dev->flags & IFF_UP) rx_handler_register(devp, dev, rx_handler, rx_handler_data); dev_put(dev); } } /* ------------------------------------------------------------------------ */ static void rx_handler_unregister(struct net_device **devp) { struct net_device *dev = *devp; if (dev) { netdev_rx_handler_unregister(dev); *devp = 0; printk(KERN_INFO "B2B: %s: rx handler unregistered\n", dev->name); } } /* ------------------------------------------------------------------------ */ static int b2b_add(const char *ifname1, const char *ifname2) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (!p->enabled) continue; if ( strncmp(p->ifname1, ifname1, IFNAMSIZ) == 0 || strncmp(p->ifname2, ifname1, IFNAMSIZ) == 0) { printk(KERN_ERR "B2B: %s already in use.\n", ifname1); return -1; } if ( strncmp(p->ifname1, ifname2, IFNAMSIZ) == 0 || strncmp(p->ifname2, ifname2, IFNAMSIZ) == 0) { printk(KERN_ERR "B2B: %s already in use.\n", ifname2); return -1; } } for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (!p->enabled) { strlcpy(p->ifname1, ifname1, IFNAMSIZ); strlcpy(p->ifname2, ifname2, IFNAMSIZ); p->enabled = 1; printk(KERN_INFO "B2B: add %s %s, done.\n", ifname1, ifname2); b2b_dev_register(&p->dev1, ifname1, b2b_rxhandler1, p); b2b_dev_register(&p->dev2, ifname2, b2b_rxhandler2, p); return 0; } } printk(KERN_ERR "B2B: add %s %s failed, table full.\n", ifname1, ifname2); return -1; } static int b2b_del(const char *ifname) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (!p->enabled) continue; if ( strncmp(p->ifname1, ifname, IFNAMSIZ) == 0 || strncmp(p->ifname2, ifname, IFNAMSIZ) == 0) { p->enabled = 0; printk(KERN_INFO "B2B: del %s %s, done.\n", p->ifname1, p->ifname2); rx_handler_unregister(&p->dev1); rx_handler_unregister(&p->dev2); return 0; } } printk(KERN_ERR "B2B: del %s failed, not found.\n", ifname); return -1; } static void b2b_delall(void) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (p->enabled) { p->enabled = 0; rx_handler_unregister(&p->dev1); rx_handler_unregister(&p->dev2); } } } /* ------------------------------------------------------------------------ */ static void b2b_dev_gone(struct net_device *dev) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (!p->enabled) continue; if (strncmp(p->ifname1, dev->name, IFNAMSIZ) == 0) { rx_handler_unregister(&p->dev1); return; } if (strncmp(p->ifname2, dev->name, IFNAMSIZ) == 0) { rx_handler_unregister(&p->dev2); return; } } } static void b2b_dev_born(struct net_device *dev) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (!p->enabled) continue; if (strncmp(p->ifname1, dev->name, IFNAMSIZ) == 0) { rx_handler_register(&p->dev1, dev, b2b_rxhandler1, p); return; } if (strncmp(p->ifname2, dev->name, IFNAMSIZ) == 0) { rx_handler_register(&p->dev2, dev, b2b_rxhandler2, p); return; } } } static int b2b_netdev_notifier_event(struct notifier_block *notifier, unsigned long event, void *ptr) { struct net_device *dev = (struct net_device *)ptr; switch (event) { case NETDEV_UNREGISTER: b2b_dev_gone(dev); break; case NETDEV_REGISTER: break; case NETDEV_UP: b2b_dev_born(dev); break; case NETDEV_DOWN: b2b_dev_gone(dev); break; } return NOTIFY_DONE; } static struct notifier_block b2b_netdev_notifier = { .notifier_call = b2b_netdev_notifier_event }; /* ------------------------------------------------------------------------ */ static void b2b_show_stats(struct seq_file *m, const char *name, struct b2bstat *stats) { seq_printf(m, " %s eapol:%lu fwok:%lu fwfail:%lu fwcn:%lu fwerr:%lu drop:%lu\n", name, stats->eapol, stats->forward_ok, stats->forward_fail, stats->forward_cn, stats->forward_error, stats->drop); } static int b2b_show(struct seq_file *m, void *v) { struct backtobacktuple *p; int i; for (i=0; i < CONFIG_B2B_NUM; i++) { p = &tuple[i]; if (p->enabled) { seq_printf(m, "%d: %s %s (%c/%c)\n", i, p->ifname1, p->ifname2, p->dev1 ? 'a' : 'd', p->dev2 ? 'a' : 'd'); b2b_show_stats(m, p->ifname1, &p->stats[0]); b2b_show_stats(m, p->ifname2, &p->stats[1]); } } return 0; } static int b2b_show_open(struct inode *inode, struct file *file) { /* * get "data" stored in proc_dir entry, * it will be assigned seq_file->private */ return single_open(file, b2b_show, PDE_DATA(inode)); } static ssize_t b2b_write_cmds(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { char cmd[100]; char* argv[10]; int argc = 0; char* ptr_cmd; char* delimitters = " \n\t"; char* ptr_next_tok; printk(KERN_ERR "B2B: write_cmds\n"); /* Validate the length of data passed. */ if (count > 100) count = 100; /* Initialize the buffer before using it. */ memset ((void *)&cmd[0], 0, sizeof(cmd)); memset ((void *)&argv[0], 0, sizeof(argv)); /* Copy from user space. */ if (copy_from_user (&cmd, buffer, count)) return -EFAULT; ptr_next_tok = &cmd[0]; ptr_cmd = strsep(&ptr_next_tok, delimitters); if (ptr_cmd == NULL) return -1; /* Parse all the commands typed. */ do { /* Extract the first command. */ argv[argc++] = ptr_cmd; /* Validate if the user entered more commands.*/ if (argc >=10) { printk(KERN_ERR "B2B: too many parameters dropping the command\n"); return -EINVAL; } /* Get the next valid command. */ ptr_cmd = strsep(&ptr_next_tok, delimitters); } while (ptr_cmd != NULL); /* We have an extra argument when strsep is used instead of strtok */ argc--; /* Display Command Handlers */ if (strcmp(argv[0], "add") == 0) { if (argv[1] && argv[2]) { rtnl_lock(); b2b_add(argv[1], argv[2]); rtnl_unlock(); } else { printk(KERN_ERR "B2B: usage: add iface iface\n"); return -EINVAL; } } else if (strcmp(argv[0], "del") == 0 || strcmp(argv[0], "rem") == 0) { if (argv[1]) { rtnl_lock(); b2b_del(argv[1]); rtnl_unlock(); } else { printk(KERN_ERR "B2B: usage: del iface\n"); return -EINVAL; } } else if (strcmp(argv[0], "delall") == 0) { rtnl_lock(); b2b_delall(); rtnl_unlock(); } else { printk(KERN_ERR "B2B: unknown command: %s\n", argv[0]); return -EINVAL; } return count; } static const struct file_operations b2b_procfs_fops = { .open = b2b_show_open, .write = b2b_write_cmds, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; /* ------------------------------------------------------------------------ */ /* module load */ static int __init b2b_init_module(void) { register_netdevice_notifier(&b2b_netdev_notifier); (void)proc_create("b2b", S_IRUGO|S_IWUGO, init_net.proc_net, &b2b_procfs_fops); printk(KERN_INFO "Backtoback driver: init done\n"); return 0; } /* module unload */ static void __exit b2b_exit_module(void) { unregister_netdevice_notifier(&b2b_netdev_notifier); rtnl_lock(); b2b_delall(); rtnl_unlock(); remove_proc_entry("b2b", init_net.proc_net); } module_init(b2b_init_module); module_exit(b2b_exit_module);