From d9e31d17ceba5f0736f5a34bbc236239cd42b420 Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 2 Apr 2010 06:19:26 +0000 Subject: l2tp: Add L2TP ethernet pseudowire support This driver presents a regular net_device for each L2TP ethernet pseudowire instance. These interfaces are named l2tpethN by default, though userspace can specify an alternative name when the L2TP session is created, if preferred. When the pseudowire is established, regular Linux networking utilities may be used to configure the interface, i.e. give it IP address info or add it to a bridge. Any data passed over the interface is carried over an L2TP tunnel. Signed-off-by: James Chapman Reviewed-by: Randy Dunlap Signed-off-by: David S. Miller --- net/l2tp/l2tp_eth.c | 347 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 net/l2tp/l2tp_eth.c (limited to 'net/l2tp/l2tp_eth.c') diff --git a/net/l2tp/l2tp_eth.c b/net/l2tp/l2tp_eth.c new file mode 100644 index 000000000000..755c29729b6f --- /dev/null +++ b/net/l2tp/l2tp_eth.c @@ -0,0 +1,347 @@ +/* + * L2TPv3 ethernet pseudowire driver + * + * Copyright (c) 2008,2009,2010 Katalix Systems Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l2tp_core.h" + +/* Default device name. May be overridden by name specified by user */ +#define L2TP_ETH_DEV_NAME "l2tpeth%d" + +/* via netdev_priv() */ +struct l2tp_eth { + struct net_device *dev; + struct sock *tunnel_sock; + struct l2tp_session *session; + struct list_head list; +}; + +/* via l2tp_session_priv() */ +struct l2tp_eth_sess { + struct net_device *dev; +}; + +/* per-net private data for this module */ +static unsigned int l2tp_eth_net_id; +struct l2tp_eth_net { + struct list_head l2tp_eth_dev_list; + spinlock_t l2tp_eth_lock; +}; + +static inline struct l2tp_eth_net *l2tp_eth_pernet(struct net *net) +{ + return net_generic(net, l2tp_eth_net_id); +} + +static int l2tp_eth_dev_init(struct net_device *dev) +{ + struct l2tp_eth *priv = netdev_priv(dev); + + priv->dev = dev; + random_ether_addr(dev->dev_addr); + memset(&dev->broadcast[0], 0xff, 6); + + return 0; +} + +static void l2tp_eth_dev_uninit(struct net_device *dev) +{ + struct l2tp_eth *priv = netdev_priv(dev); + struct l2tp_eth_net *pn = l2tp_eth_pernet(dev_net(dev)); + + spin_lock(&pn->l2tp_eth_lock); + list_del_init(&priv->list); + spin_unlock(&pn->l2tp_eth_lock); + dev_put(dev); +} + +static int l2tp_eth_dev_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct l2tp_eth *priv = netdev_priv(dev); + struct l2tp_session *session = priv->session; + + l2tp_xmit_skb(session, skb, session->hdr_len); + + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + + return 0; +} + +static struct net_device_ops l2tp_eth_netdev_ops = { + .ndo_init = l2tp_eth_dev_init, + .ndo_uninit = l2tp_eth_dev_uninit, + .ndo_start_xmit = l2tp_eth_dev_xmit, +}; + +static void l2tp_eth_dev_setup(struct net_device *dev) +{ + ether_setup(dev); + + dev->netdev_ops = &l2tp_eth_netdev_ops; + dev->destructor = free_netdev; +} + +static void l2tp_eth_dev_recv(struct l2tp_session *session, struct sk_buff *skb, int data_len) +{ + struct l2tp_eth_sess *spriv = l2tp_session_priv(session); + struct net_device *dev = spriv->dev; + + if (session->debug & L2TP_MSG_DATA) { + unsigned int length; + int offset; + u8 *ptr = skb->data; + + length = min(32u, skb->len); + if (!pskb_may_pull(skb, length)) + goto error; + + printk(KERN_DEBUG "%s: eth recv: ", session->name); + + offset = 0; + do { + printk(" %02X", ptr[offset]); + } while (++offset < length); + + printk("\n"); + } + + if (data_len < ETH_HLEN) + goto error; + + secpath_reset(skb); + + /* checksums verified by L2TP */ + skb->ip_summed = CHECKSUM_NONE; + + skb_dst_drop(skb); + nf_reset(skb); + + if (dev_forward_skb(dev, skb) == NET_RX_SUCCESS) { + dev->last_rx = jiffies; + dev->stats.rx_packets++; + dev->stats.rx_bytes += data_len; + } else + dev->stats.rx_errors++; + + return; + +error: + dev->stats.rx_errors++; + kfree_skb(skb); +} + +static void l2tp_eth_delete(struct l2tp_session *session) +{ + struct l2tp_eth_sess *spriv; + struct net_device *dev; + + if (session) { + spriv = l2tp_session_priv(session); + dev = spriv->dev; + if (dev) { + unregister_netdev(dev); + spriv->dev = NULL; + } + } +} + +static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg) +{ + struct net_device *dev; + char name[IFNAMSIZ]; + struct l2tp_tunnel *tunnel; + struct l2tp_session *session; + struct l2tp_eth *priv; + struct l2tp_eth_sess *spriv; + int rc; + struct l2tp_eth_net *pn; + + tunnel = l2tp_tunnel_find(net, tunnel_id); + if (!tunnel) { + rc = -ENODEV; + goto out; + } + + session = l2tp_session_find(net, tunnel, session_id); + if (session) { + rc = -EEXIST; + goto out; + } + + if (cfg->ifname) { + dev = dev_get_by_name(net, cfg->ifname); + if (dev) { + dev_put(dev); + rc = -EEXIST; + goto out; + } + strlcpy(name, cfg->ifname, IFNAMSIZ); + } else + strcpy(name, L2TP_ETH_DEV_NAME); + + session = l2tp_session_create(sizeof(*spriv), tunnel, session_id, + peer_session_id, cfg); + if (!session) { + rc = -ENOMEM; + goto out; + } + + dev = alloc_netdev(sizeof(*priv), name, l2tp_eth_dev_setup); + if (!dev) { + rc = -ENOMEM; + goto out_del_session; + } + + dev_net_set(dev, net); + if (session->mtu == 0) + session->mtu = dev->mtu - session->hdr_len; + dev->mtu = session->mtu; + dev->needed_headroom += session->hdr_len; + + priv = netdev_priv(dev); + priv->dev = dev; + priv->session = session; + INIT_LIST_HEAD(&priv->list); + + priv->tunnel_sock = tunnel->sock; + session->recv_skb = l2tp_eth_dev_recv; + session->session_close = l2tp_eth_delete; + + spriv = l2tp_session_priv(session); + spriv->dev = dev; + + rc = register_netdev(dev); + if (rc < 0) + goto out_del_dev; + + /* Must be done after register_netdev() */ + strlcpy(session->ifname, dev->name, IFNAMSIZ); + + dev_hold(dev); + pn = l2tp_eth_pernet(dev_net(dev)); + spin_lock(&pn->l2tp_eth_lock); + list_add(&priv->list, &pn->l2tp_eth_dev_list); + spin_unlock(&pn->l2tp_eth_lock); + + return 0; + +out_del_dev: + free_netdev(dev); +out_del_session: + l2tp_session_delete(session); +out: + return rc; +} + +static __net_init int l2tp_eth_init_net(struct net *net) +{ + struct l2tp_eth_net *pn; + int err; + + pn = kzalloc(sizeof(*pn), GFP_KERNEL); + if (!pn) + return -ENOMEM; + + INIT_LIST_HEAD(&pn->l2tp_eth_dev_list); + spin_lock_init(&pn->l2tp_eth_lock); + + err = net_assign_generic(net, l2tp_eth_net_id, pn); + if (err) + goto out; + + return 0; + +out: + kfree(pn); + return err; +} + +static __net_exit void l2tp_eth_exit_net(struct net *net) +{ + struct l2tp_eth_net *pn; + + pn = net_generic(net, l2tp_eth_net_id); + /* + * if someone has cached our net then + * further net_generic call will return NULL + */ + net_assign_generic(net, l2tp_eth_net_id, NULL); + kfree(pn); +} + +static __net_initdata struct pernet_operations l2tp_eth_net_ops = { + .init = l2tp_eth_init_net, + .exit = l2tp_eth_exit_net, + .id = &l2tp_eth_net_id, + .size = sizeof(struct l2tp_eth_net), +}; + + +static const struct l2tp_nl_cmd_ops l2tp_eth_nl_cmd_ops = { + .session_create = l2tp_eth_create, + .session_delete = l2tp_session_delete, +}; + + +static int __init l2tp_eth_init(void) +{ + int err = 0; + + err = l2tp_nl_register_ops(L2TP_PWTYPE_ETH, &l2tp_eth_nl_cmd_ops); + if (err) + goto out; + + err = register_pernet_device(&l2tp_eth_net_ops); + if (err) + goto out_unreg; + + printk(KERN_INFO "L2TP ethernet pseudowire support (L2TPv3)\n"); + + return 0; + +out_unreg: + l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH); +out: + return err; +} + +static void __exit l2tp_eth_exit(void) +{ + unregister_pernet_device(&l2tp_eth_net_ops); + l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH); +} + +module_init(l2tp_eth_init); +module_exit(l2tp_eth_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("James Chapman "); +MODULE_DESCRIPTION("L2TP ethernet pseudowire driver"); +MODULE_VERSION("1.0"); -- cgit v1.2.1 From 0ad6614048cf722e4d27909665b4846805357f1b Mon Sep 17 00:00:00 2001 From: James Chapman Date: Fri, 2 Apr 2010 06:19:33 +0000 Subject: l2tp: Add debugfs files for dumping l2tp debug info The existing pppol2tp driver exports debug info to /proc/net/pppol2tp. Rather than adding info to that file for the new functionality added in this patch series, we add new files in debugfs, leaving the old /proc file for backwards compatibility (L2TPv2 only). Currently only one file is provided: l2tp/tunnels, which lists internal debug info for all l2tp tunnels and sessions. More files may be added later. The info is for debug and problem analysis only - userspace apps should use netlink to obtain status about l2tp tunnels and sessions. Although debugfs does not support net namespaces, the tunnels and sessions dumped in l2tp/tunnels are only those in the net namespace of the process reading the file. Signed-off-by: James Chapman Signed-off-by: David S. Miller --- net/l2tp/l2tp_eth.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'net/l2tp/l2tp_eth.c') diff --git a/net/l2tp/l2tp_eth.c b/net/l2tp/l2tp_eth.c index 755c29729b6f..9848faa3d163 100644 --- a/net/l2tp/l2tp_eth.c +++ b/net/l2tp/l2tp_eth.c @@ -172,6 +172,17 @@ static void l2tp_eth_delete(struct l2tp_session *session) } } +#ifdef CONFIG_L2TP_DEBUGFS +static void l2tp_eth_show(struct seq_file *m, void *arg) +{ + struct l2tp_session *session = arg; + struct l2tp_eth_sess *spriv = l2tp_session_priv(session); + struct net_device *dev = spriv->dev; + + seq_printf(m, " interface %s\n", dev->name); +} +#endif + static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg) { struct net_device *dev; @@ -233,6 +244,9 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p priv->tunnel_sock = tunnel->sock; session->recv_skb = l2tp_eth_dev_recv; session->session_close = l2tp_eth_delete; +#ifdef CONFIG_L2TP_DEBUGFS + session->show = l2tp_eth_show; +#endif spriv = l2tp_session_priv(session); spriv->dev = dev; -- cgit v1.2.1 From f66ef2d0649b220874532dfb83b6b5b368f83591 Mon Sep 17 00:00:00 2001 From: "David S. Miller" Date: Sat, 3 Apr 2010 15:01:37 -0700 Subject: l2tp: Fix L2TP_DEBUGFS ifdef tests. We have to check CONFIG_L2TP_DEBUGFS_MODULE as well as CONFIG_L2TP_DEBUGFS. Signed-off-by: David S. Miller --- net/l2tp/l2tp_eth.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'net/l2tp/l2tp_eth.c') diff --git a/net/l2tp/l2tp_eth.c b/net/l2tp/l2tp_eth.c index 9848faa3d163..ca1164afeb74 100644 --- a/net/l2tp/l2tp_eth.c +++ b/net/l2tp/l2tp_eth.c @@ -172,7 +172,7 @@ static void l2tp_eth_delete(struct l2tp_session *session) } } -#ifdef CONFIG_L2TP_DEBUGFS +#if defined(CONFIG_L2TP_DEBUGFS) || defined(CONFIG_L2TP_DEBUGFS_MODULE) static void l2tp_eth_show(struct seq_file *m, void *arg) { struct l2tp_session *session = arg; @@ -244,7 +244,7 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p priv->tunnel_sock = tunnel->sock; session->recv_skb = l2tp_eth_dev_recv; session->session_close = l2tp_eth_delete; -#ifdef CONFIG_L2TP_DEBUGFS +#if defined(CONFIG_L2TP_DEBUGFS) || defined(CONFIG_L2TP_DEBUGFS_MODULE) session->show = l2tp_eth_show; #endif -- cgit v1.2.1 From 3a737028630bb3c2b9efc38b9ddef2e09b06b808 Mon Sep 17 00:00:00 2001 From: Jiri Pirko Date: Fri, 23 Apr 2010 01:01:52 +0000 Subject: l2tp_eth: fix memory allocation Since .size is set properly in "struct pernet_operations l2tp_eth_net_ops", allocating space for "struct l2tp_eth_net" by hand is not correct, even causes memory leakage. Signed-off-by: Jiri Pirko Signed-off-by: David S. Miller --- net/l2tp/l2tp_eth.c | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) (limited to 'net/l2tp/l2tp_eth.c') diff --git a/net/l2tp/l2tp_eth.c b/net/l2tp/l2tp_eth.c index ca1164afeb74..58c6c4cda73b 100644 --- a/net/l2tp/l2tp_eth.c +++ b/net/l2tp/l2tp_eth.c @@ -276,43 +276,16 @@ out: static __net_init int l2tp_eth_init_net(struct net *net) { - struct l2tp_eth_net *pn; - int err; - - pn = kzalloc(sizeof(*pn), GFP_KERNEL); - if (!pn) - return -ENOMEM; + struct l2tp_eth_net *pn = net_generic(net, l2tp_eth_net_id); INIT_LIST_HEAD(&pn->l2tp_eth_dev_list); spin_lock_init(&pn->l2tp_eth_lock); - err = net_assign_generic(net, l2tp_eth_net_id, pn); - if (err) - goto out; - return 0; - -out: - kfree(pn); - return err; -} - -static __net_exit void l2tp_eth_exit_net(struct net *net) -{ - struct l2tp_eth_net *pn; - - pn = net_generic(net, l2tp_eth_net_id); - /* - * if someone has cached our net then - * further net_generic call will return NULL - */ - net_assign_generic(net, l2tp_eth_net_id, NULL); - kfree(pn); } static __net_initdata struct pernet_operations l2tp_eth_net_ops = { .init = l2tp_eth_init_net, - .exit = l2tp_eth_exit_net, .id = &l2tp_eth_net_id, .size = sizeof(struct l2tp_eth_net), }; -- cgit v1.2.1