On Wed, May 20, 2020 at 05:42:35PM +0300, Vitaliy Makkoveev wrote:
> I got splassert with pppx(4) and net/ifq.c rev 1.38 raised by
> NET_ASSERT_LOCKED() in netinet/ip_output.c:113 and underlaying routines.
> 
> net/ifq.c rev 1.38 is not in snapshot yet so you need to checkout and
> build kernel to reproduce.
> 
> ---- dmesg begin ----
> 
> splassert: ip_output: want 2 have 0
> Starting stack trace...
> ip_output(fffffd801f8c8800,0,0,0,0,0) at ip_output+0x8f
> pipex_l2tp_output(fffffd801f8c8800,ffff80000e787808) at
> pipex_l2tp_output+0x21d
> pipex_ppp_output(fffffd801f8c8800,ffff80000e787808,21) at
> pipex_ppp_output+0xda
> pppx_if_start(ffff80000e787268) at pppx_if_start+0x83
> if_qstart_compat(ffff80000e7874e0) at if_qstart_compat+0x2e
> ifq_serialize(ffff80000e7874e0,ffff80000e7875b0) at ifq_serialize+0x103
> taskq_thread(ffffffff81f3df30) at taskq_thread+0x4d
> end trace frame: 0x0, count: 250
> End of stack trace.
> splassert: ipsp_spd_lookup: want 2 have 0
> Starting stack trace...
> ipsp_spd_lookup(fffffd801f8c8800,2,14,ffff80000e726c9c,2,0) at
> ipsp_spd_lookup+0x80
> ip_output_ipsec_lookup(fffffd801f8c8800,14,ffff80000e726c9c,0,1) at
> ip_output_ipsec_lookup+0x4d
> ip_output(fffffd801f8c8800,0,0,0,0,0) at ip_output+0x4fa
> pipex_l2tp_output(fffffd801f8c8800,ffff80000e787808) at
> pipex_l2tp_output+0x21d
> pipex_ppp_output(fffffd801f8c8800,ffff80000e787808,21) at
> pipex_ppp_output+0xda
> pppx_if_start(ffff80000e787268) at pppx_if_start+0x83
> if_qstart_compat(ffff80000e7874e0) at if_qstart_compat+0x2e
> ifq_serialize(ffff80000e7874e0,ffff80000e7875b0) at ifq_serialize+0x103
> taskq_thread(ffffffff81f3df30) at taskq_thread+0x4d
> end trace frame: 0x0, count: 248
> End of stack trace.
> splassert: spd_table_get: want 2 have 0
> 
> ---- dmesg end ----
> 
> 1. `pxi_if' owned by struct pppx_if has IFXF_MPSAFE flag unset
>   1.1 pppx(4) sets IFQ_SET_MAXLEN(&ifp->if_snd, 1) at net/if_pppx.c:866
> 2. pppx_if_output() is called under NET_LOCK()
> 3. pppx_if_output() calls if_enqueue() at net/if_pppx.c:1123
> 4. pppx(4) doesn't set `ifp->if_enqueue' so if_enqueue() calls
>     if_enqueue_ifq() at net/if.c:709 (which is set in net/if.c:639)
> 5. if_enqueue_ifq() calls ifq_start() at net/if.c:734
> 6. ifq_start() we a still under NET_LOCK() here
> 
> 6.a. in net/ifq.c rev 1.37 ifq_start() checks "ifq_len(ifq) >=
> min(ifp->if_txmit, ifq->ifq_maxlen)" and this was always true because
> (1.1) so we always call ifq_run_start() which calls ifq_serialize().
> 
> ifq_serialize() will call if_qstart_compat() which calls
> pppx_if_start() which calls pipex_ppp_output() etc while we still
> holding NET_LOCK() so the assertions I reported above are not raised.
> 
> 6.b. net/ifq.c rev 1.38 introduce checks of IFXF_MPSAFE flag. so we are
> always going to net/ifq.c:132 where we adding out task to `systq'
> referenced by `ifq_softnet' (ifq_softnet set to `systq' at
> net/ifq.c:199).
> 
> taskq_thread() doesn't grab NET_LOCK() so after net/ifq.c rev 1.38
> ifq_serialize() and underlaying pppx_if_start() call without NET_LOCK()
> and corresponding asserts raised.
> 
> The problem I pointed is not in net/ifq.c rev 1.38 but in pppx(4).
> `if_start' routines should grab NET_LOCK() by themself if it is required
> but pppx_if_start() and pppac_start() did't do that. pppac_start() has
> no underlaying NET_ASSERT_LOCKED() so the pppx(4) is the only case were
> the problem is shown.
> 
> Since NET_LOCK() is required by pipex(4), diff below adds it to
> pppx_if_start() and pppac_start().
> 
> After net/ifq.c rev 1.38 pppx_if_start() will newer be called from
> pppx_if_output() but from `systq' only so I don't add lock/unlock
> dances around if_enqueue() at net/if_pppx.c:1123.
> 
> Diff tested for both pppx(4) and pppac(4) cases.

thanks for the detailed analysis. i wondered how the ifq change
triggered this exactly, and your mail makes it clear.

however, pppx/pppac/pipex are not the first or only drivers in the tree
that encapsulate a packet in IP from their if_start routine and send it
out with the network stack. the way this has been solved in every other
driver has been to call ip{,6}_send to transmit the packet instead
of ip{,6}_output.

can you try the following diff?

Index: pipex.c
===================================================================
RCS file: /cvs/src/sys/net/pipex.c,v
retrieving revision 1.113
diff -u -p -r1.113 pipex.c
--- pipex.c     7 Apr 2020 07:11:22 -0000       1.113
+++ pipex.c     21 May 2020 21:49:50 -0000
@@ -1453,10 +1453,7 @@ pipex_pptp_output(struct mbuf *m0, struc
        gre->flags = htons(gre->flags);
 
        m0->m_pkthdr.ph_ifidx = session->pipex_iface->ifnet_this->if_index;
-       if (ip_output(m0, NULL, NULL, 0, NULL, NULL, 0) != 0) {
-               PIPEX_DBG((session, LOG_DEBUG, "ip_output failed."));
-               goto drop;
-       }
+       ip_send(m0);
        if (len > 0) {  /* network layer only */
                /* countup statistics */
                session->stat.opackets++;
@@ -1901,11 +1898,7 @@ pipex_l2tp_output(struct mbuf *m0, struc
                ip->ip_tos = 0;
                ip->ip_off = 0;
 
-               if (ip_output(m0, NULL, NULL, 0, NULL, NULL,
-                   session->proto.l2tp.ipsecflowinfo) != 0) {
-                       PIPEX_DBG((session, LOG_DEBUG, "ip_output failed."));
-                       goto drop;
-               }
+               ip_send(m0);
                break;
 #ifdef INET6
        case AF_INET6:
@@ -1920,10 +1913,7 @@ pipex_l2tp_output(struct mbuf *m0, struc
                    &session->peer.sin6, NULL);
                /* ip6->ip6_plen will be filled in ip6_output. */
 
-               if (ip6_output(m0, NULL, NULL, 0, NULL, NULL) != 0) {
-                       PIPEX_DBG((session, LOG_DEBUG, "ip6_output failed."));
-                       goto drop;
-               }
+               ip6_send(m0);
                break;
 #endif
        }

Reply via email to