Hello,

Le 10/12/2020 à 16:50, Tom Parkin a écrit :
> This new ioctl pair 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 channels
> are unbridged, which can occur either explicitly on userspace calling
> the PPPIOCUNBRIDGECHAN ioctl, or implicitly when either channel in the
> bridge is unregistered.
> 
> In order to implement the channel bridge, struct channel is extended
> with a new field, 'bridge', which points to the other struct channel
> making up the bridge.
> 
> This pointer is RCU protected to avoid adding another lock to the data
> path.
> 
> To guard against concurrent writes to the pointer, the existing struct
> channel lock 'upl' coverage is extended rather than adding a new lock.
> 
> The 'upl' lock is used to protect the existing unit pointer.  Since the
> bridge effectively replaces the unit (they're mutually exclusive for a
> channel) it makes coding easier to use the same lock to cover them
> both.
> 
> Signed-off-by: Tom Parkin <tpar...@katalix.com>
> ---
>  drivers/net/ppp/ppp_generic.c  | 152 ++++++++++++++++++++++++++++++++-
>  include/uapi/linux/ppp-ioctl.h |   2 +
>  2 files changed, 151 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
> index 7d005896a0f9..09c27f7773f9 100644
> --- a/drivers/net/ppp/ppp_generic.c
> +++ b/drivers/net/ppp/ppp_generic.c
> @@ -174,7 +174,8 @@ struct channel {
>       struct ppp      *ppp;           /* ppp unit we're connected to */
>       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' */
> +     rwlock_t        upl;            /* protects `ppp' and 'bridge' */
> +     struct channel __rcu *bridge;   /* "bridged" ppp channel */
>  #ifdef CONFIG_PPP_MULTILINK
>       u8              avail;          /* flag used in multilink stuff */
>       u8              had_frag;       /* >= 1 fragments have been sent */
> @@ -606,6 +607,83 @@ static struct bpf_prog *compat_ppp_get_filter(struct 
> sock_fprog32 __user *p)
>  #endif
>  #endif
>  
> +/* Bridge one PPP channel to another.
> + * When two channels are bridged, ppp_input on one channel is redirected to
> + * the other's ops->start_xmit handler.
> + * In order to safely bridge channels we must reject channels which are 
> already
> + * part of a bridge instance, or which form part of an existing unit.
> + * Once successfully bridged, each channel holds a reference on the other
> + * to prevent it being freed while the bridge is extant.
> + */
> +static int ppp_bridge_channels(struct channel *pch, struct channel *pchb)
> +{
> +     write_lock_bh(&pch->upl);
> +     if (pch->ppp ||
> +         rcu_dereference_protected(pch->bridge, lockdep_is_held(&pch->upl))) 
> {
> +             write_unlock_bh(&pch->upl);
> +             return -EALREADY;
> +     }
> +     rcu_assign_pointer(pch->bridge, pchb);
> +     write_unlock_bh(&pch->upl);
> +
This is mostly for my own education:

I might be misunderstanding something, but is there anything
that would prevent a packet from being forwarded from pch to pchb at this
precise moment? If not, then it might be theoretically possible to have
any answer to said packet (say, a LCP ACK) to be received before the 
pchb->bridge is set?


> +     write_lock_bh(&pchb->upl);
> +     if (pchb->ppp ||
> +         rcu_dereference_protected(pchb->bridge, 
> lockdep_is_held(&pchb->upl))) {
> +             write_unlock_bh(&pchb->upl);
> +             goto err_unset;
> +     }
> +     rcu_assign_pointer(pchb->bridge, pch);
> +     write_unlock_bh(&pchb->upl);
> +
> +     refcount_inc(&pch->file.refcnt);
> +     refcount_inc(&pchb->file.refcnt);
> +
> +     return 0;
> +
> +err_unset:
> +     write_lock_bh(&pch->upl);
> +     RCU_INIT_POINTER(pch->bridge, NULL);
> +     write_unlock_bh(&pch->upl);
> +     synchronize_rcu();
> +     return -EALREADY;
> +}

Reply via email to