2 Hi Vitaliy,

On Tue, May 19, 2020 at 12:11 PM Vitaliy Makkoveev
<henscheltig...@yahoo.com> wrote:
> On Mon, May 04, 2020 at 10:03:40PM +0300, Sergey Ryazanov wrote:
> > Split checks from frame accepting with header removing in the common
> > PPP input function. This should fix packet capture on a PPP interfaces.
>
> Can you describe the problem you fix? As mpi@ pointed to me, reviewers
> are stupid and have no telepathy skills :)

When I tried to capture packets on a ppp (4) interface (with pipex
activated), I noticed that all the PPP CCP frames were ok, but all the
ingress PPP IP frames were mangled, and they did not contain the PPP
header at all.

Then I started digging the code and came across the fact that pipex
has two packet capture points. One place is in pipex_ip_input(), where
pure IP packets (without a PPP header) are passed to bpf, and the
other is in pipex_ppp_input(), where only CCP frames are passed to bpf
and only if the associated network interface is of PPP type.

My first thought was to add another one hook to the pipex_ppp_input()
to intercept IP frames before the PPP header stripping. But then I
spotted that pipex_ppp_input() have a common pattern in the frames
processing, but it was poorly expressed since before the proposed
change the code is looks like this (in pseudo code):

if COMP
    COMP sanity checks
    ppp header stripping        <- duplicate #1
    call MPPE handler
    exit
if CCP
    CCP sanity checks
    bpf call
    ppp header stripping        <- duplicate #2
    call MPPE handler
    exit
ppp header stripping            <- duplicate #3
packet data alignment
if IP
    IP sanity checks
    call IP handler
    exit
if IPv6
    IPv6 sanity checks
    call IPv6 handler
    exit
if unknown PPP frame type
    produce a big fat error message

As you can see for each frame type the code performs a specific sanity
check, strips the PPP header and then call a frame specific handler.
Also CCP frames on a ppp(4) interface are passed to bpf.

So I rearrange the code to make I a bit more clear:
* group sanity checks in the beginning of the function;
* place bpf call for ppp(4) interfaces after sanity checks, but
_before_ PPP header stripping;
* place the header stripping common for all types with optional data alignment;
* group frame specific handler calls in the end of the function;
* use switch/case to help a compiler and human readers :)

Now the function code is looks like this:

if COMP
    COMP sanity checks
if CCP
    CCP sanity checks
if IP
    IP sanity checks and prepare for alignment
if IPv6
    IPv6 sanity checks and prepare for alignment
if unknown PPP frame type
    produce a big fat error message
call bpf for ppp(4) interfaces while frame still has the PPP header
strip the ppp header and perform optional alignment
if COMP
    COMP sanity checks
if CCP
    CCP sanity checks
if IP
    IP sanity checks and prepare for alignment
if IPv6
    IPv6 sanity checks and prepare for alignment

Diff is just unable to express such change in a human friendly way :(
It is better to look at the code before diff applying and after to see
the change.

The final hank just blocks stripped IP packets passing to bpf on
ppp(4) network interfaces to avoid duplicate (and mangled) packets in
a dump.

So, after the change we will get: function behaves as early, tcpdump
perfectly sniffs on a ppp interface with activated pipex, checks are
performed before expensive packet modifications, and function code is
readable and now it is clear what exactly function do for each type of
frames.

Oh, also pipex.c become shorter by the 13 lines.

> Also your diff does two different things: (1) split frames checks and
> input, and (2) modify frames passing to bpf(4). I guess you should split
> your diff by two different.

The bpf call modifications just prevent frame duplication in the dump.
So I think Its better to keep them here.

Buf if someone is against such wide modifications, then I am Ok to
send two patches: one for mangled packets dumping prevention and one
for PPP input rework.

> I did some inlined comments below.
>
> >
> > Also forbid IP/IPv6 frames (without PPP header) passing to BPF on
> > PPP interfaces to avoid mess.
> >
> > Initialy this change was made as a part of pipex(4) and ppp(4)
> > integration work. But, since this change make the core a bit more clear
> > I would like to publish it now.
> >
> > Ok?
> >
> > ---
> >  sys/net/pipex.c | 95 ++++++++++++++++++++++++++++---------------------
> >  1 file changed, 54 insertions(+), 41 deletions(-)
> >
> > diff --git sys/net/pipex.c sys/net/pipex.c
> > index c433e4beaa6..e0066a61598 100644
> > --- sys/net/pipex.c
> > +++ sys/net/pipex.c
> > @@ -970,41 +970,68 @@ drop:
> >  Static void
> >  pipex_ppp_input(struct mbuf *m0, struct pipex_session *session, int 
> > decrypted)
> >  {
> > -     int proto, hlen = 0;
> > +     int proto, hlen = 0, align = 0;
> >       struct mbuf *n;
> >
> >       KASSERT(m0->m_pkthdr.len >= PIPEX_PPPMINLEN);
> >       proto = pipex_ppp_proto(m0, session, 0, &hlen);
> > +     switch (proto) {
> > +     case PPP_IP:
> > +             if (session->ip_forward == 0)
> > +                     goto drop;
> > +             if (!decrypted && pipex_session_is_mppe_required(session))
> > +                     /*
> > +                      * if ip packet received when mppe
> > +                      * is required, discard it.
> > +                      */
> > +                     goto drop;
> > +             align = 1;
> > +             break;
> > +#ifdef INET6
> > +     case PPP_IPV6:
> > +             if (session->ip6_forward == 0)
> > +                     goto drop;
> > +             if (!decrypted && pipex_session_is_mppe_required(session))
> > +                     /*
> > +                      * if ip packet received when mppe
> > +                      * is required, discard it.
> > +                      */
> > +                     goto drop;
> > +             align = 1;
> > +             break;
> > +#endif
> >  #ifdef PIPEX_MPPE
> > -     if (proto == PPP_COMP) {
> > +     case PPP_COMP:
> >               if (decrypted)
> >                       goto drop;
> >
> >               /* checked this on ppp_common_input() already. */
> >               KASSERT(pipex_session_is_mppe_accepted(session));
> > -
> > -             m_adj(m0, hlen);
> > -             pipex_mppe_input(m0, session);
> > -             return;
> > -     }
> > -     if (proto == PPP_CCP) {
> > +             break;
> > +     case PPP_CCP:
> >               if (decrypted)
> >                       goto drop;
> > +             break;
> > +#endif
> > +     default:
> > +             if (decrypted)
> > +                     goto drop;
> > +             /* protocol must be checked on pipex_common_input() already */
> > +             KASSERT(0);
> > +             goto drop;
> > +     }
> >
> >  #if NBPFILTER > 0
> > -         {
> > +     {
> >               struct ifnet *ifp = session->pipex_iface->ifnet_this;
> > +
> >               if (ifp->if_bpf && ifp->if_type == IFT_PPP)
> >                       bpf_mtap(ifp->if_bpf, m0, BPF_DIRECTION_IN);
> > -         }
> > -#endif
> > -             m_adj(m0, hlen);
> > -             pipex_ccp_input(m0, session);
> > -             return;
> >       }
> >  #endif
>
> For INET[46] cases you can align frame after it's passed to bpf(4). Should
> this frame be aligned before passing to bpf(4)?

We definitely need to align IP packets after the PPP header stripping
to facilitate work of other networking code. But aligning of the PPP
frame could cause a double alignment: before and after the PPP header
stripping, since a PPP header is not always have a proper length. So I
think that aligning before the PPP header stripping could be too
expensive and we do not required to do it.

> > +
> >       m_adj(m0, hlen);
> > -     if (!ALIGNED_POINTER(mtod(m0, caddr_t), uint32_t)) {
> > +     if (align && !ALIGNED_POINTER(mtod(m0, caddr_t), uint32_t)) {
> >               n = m_dup_pkt(m0, 0, M_NOWAIT);
> >               if (n == NULL)
> >                       goto drop;

[skipped]

> > -     default:
> > -             if (decrypted)
> > -                     goto drop;
> > -             /* protocol must be checked on pipex_common_input() already */
> > -             KASSERT(0);
> > -             goto drop;
>
> Hipotethically, someone inncaurate can introduce memory leak here in
> future. I like to keep default case with assertion or with "goto drop".

This code was moved to the beginning of the function. So it is still
will us. Or did you mean that we should handle unknown frame types in
the second switch too?

-- 
Sergey

Reply via email to