This new ioctl allows two ppp channels to be bridged together: frames arriving in one channel are transmitted in the other channel and vice versa.
The practical use for this is primarily to support the L2TP Access Concentrator use-case. The end-user session is presented as a ppp channel (typically PPPoE, although it could be e.g. PPPoA, or even PPP over a serial link) and is switched into a PPPoL2TP session for transmission to the LNS. At the LNS the PPP session is terminated in the ISP's network. When a PPP channel is bridged to another it takes a reference on the other's struct ppp_file. This reference is dropped when the channel is unregistered: if the dereference causes the bridged channel's reference count to reach zero it is destroyed at that point. --- drivers/net/ppp/ppp_generic.c | 35 +++++++++++++++++++++++++++++++++- include/uapi/linux/ppp-ioctl.h | 1 + 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index 7d005896a0f9..d893bf4470f4 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -175,6 +175,7 @@ struct channel { struct net *chan_net; /* the net channel belongs to */ struct list_head clist; /* link in list of channels per unit */ rwlock_t upl; /* protects `ppp' */ + struct channel *bridge; /* "bridged" ppp channel */ #ifdef CONFIG_PPP_MULTILINK u8 avail; /* flag used in multilink stuff */ u8 had_frag; /* >= 1 fragments have been sent */ @@ -641,8 +642,9 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } if (pf->kind == CHANNEL) { - struct channel *pch; + struct channel *pch, *pchb; struct ppp_channel *chan; + struct ppp_net *pn; pch = PF_TO_CHANNEL(pf); @@ -657,6 +659,24 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) err = ppp_disconnect_channel(pch); break; + case PPPIOCBRIDGECHAN: + if (get_user(unit, p)) + break; + err = -ENXIO; + if (pch->bridge) { + err = -EALREADY; + break; + } + pn = ppp_pernet(current->nsproxy->net_ns); + spin_lock_bh(&pn->all_channels_lock); + pchb = ppp_find_channel(pn, unit); + if (pchb) { + refcount_inc(&pchb->file.refcnt); + pch->bridge = pchb; + err = 0; + } + spin_unlock_bh(&pn->all_channels_lock); + break; default: down_read(&pch->chan_sem); chan = pch->chan; @@ -2100,6 +2120,12 @@ ppp_input(struct ppp_channel *chan, struct sk_buff *skb) return; } + if (pch->bridge) { + skb_queue_tail(&pch->bridge->file.xq, skb); + ppp_channel_push(pch->bridge); + return; + } + read_lock_bh(&pch->upl); if (!ppp_decompress_proto(skb)) { kfree_skb(skb); @@ -2791,6 +2817,13 @@ ppp_unregister_channel(struct ppp_channel *chan) up_write(&pch->chan_sem); ppp_disconnect_channel(pch); + /* Drop our reference on a bridged channel, if any */ + if (pch->bridge) { + if (refcount_dec_and_test(&pch->bridge->file.refcnt)) + ppp_destroy_channel(pch->bridge); + pch->bridge = NULL; + } + pn = ppp_pernet(pch->chan_net); spin_lock_bh(&pn->all_channels_lock); list_del(&pch->list); diff --git a/include/uapi/linux/ppp-ioctl.h b/include/uapi/linux/ppp-ioctl.h index 7bd2a5a75348..4b97ab519c19 100644 --- a/include/uapi/linux/ppp-ioctl.h +++ b/include/uapi/linux/ppp-ioctl.h @@ -115,6 +115,7 @@ struct pppol2tp_ioc_stats { #define PPPIOCATTCHAN _IOW('t', 56, int) /* attach to ppp channel */ #define PPPIOCGCHAN _IOR('t', 55, int) /* get ppp channel number */ #define PPPIOCGL2TPSTATS _IOR('t', 54, struct pppol2tp_ioc_stats) +#define PPPIOCBRIDGECHAN _IOW('t', 53, int) /* bridge one channel to another */ #define SIOCGPPPSTATS (SIOCDEVPRIVATE + 0) #define SIOCGPPPVER (SIOCDEVPRIVATE + 1) /* NEVER change this!! */ -- 2.17.1