On Fri, Nov 06, 2020 at 06:16:46PM +0000, Tom Parkin wrote:
> 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;

I think we shouldn't modify ->bridge if it's already set or if the
channel is already part of of a PPP unit  (that is, if pch->ppp or
pch->bridge is not NULL).

This also means that we have to use appropriate locking.

> +                             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);

The bridged channel might reside in a different network namespace.
So it seems that skb_scrub_packet() is needed before sending the
packet.

> +             ppp_channel_push(pch->bridge);

I'm not sure if the skb_queue_tail()/ppp_channel_push() sequence really
is necessary. We're not going through a PPP unit, so we have no risk of
recursion here. Also, if ->start_xmit() fails, I see no reason for
requeuing the skb, like __ppp_channel_push() does. I'd have to think
more about it, but I believe that we could call the channel's
->start_xmit() function directly (respecting the locking constraints
of course).

> +             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
> 

Reply via email to