Hello,

patch below is missing piece to stuff, which I commit on n2k18 [1].
Fairly quickly people, who have PF deployed with pfsync (and are
willing to experiment), discovered a panic on PF_LOCK recursion.
The recursion is identified by stack as follows:

    login: panic: rw_enter: pf_lock locking against myself
    Stopped at      db_enter+0x12:  popq    %r11
        TID    PID    UID     PRFLAGS     PFLAGS  CPU  COMMAND
     147548  82409      0    0x100002          0    3  ld
    *192136   5192      0     0x14000      0x200    0  softnet
    db_enter() at db_enter+0x12
    panic() at panic+0x120
    _rw_enter() at _rw_enter+0x2a3
    pfsync_update_state() at pfsync_update_state+0x90
    pf_test() at pf_test+0x608
    ip_output() at ip_output+0x6cd
    pfsync_sendout() at pfsync_sendout+0x645
    pfsync_bulk_start() at pfsync_bulk_start+0x127
    pfsync_in_ureq() at pfsync_in_ureq+0x71                                     
                                  
    pfsync_input() at pfsync_input+0x3ab
    ip_deliver() at ip_deliver+0x203
    ipintr() at ipintr+0x6a
    if_netisr(ffffffff813abf00) at if_netisr+0x5a
    taskq_thread(0) at taskq_thread+0x6d

Big thanks goes to hrvoje@, who provided me with proper set up so
I could debug the patch. And also a fair amount of credit goes
to Christiano Haesbaert for idea to dispatch outbound psync update
packet to task. 

The change in patch is conservative. The pfsync behavior does not
change unless PF gets compiled with '-DWITH_PF_LOCK' option. '-DWITH_PF_LOCK'
tells pfsync to use a softnet task to transmit packet with
update (see net/if.c [2])

Also let me clarify my earlier commit message [1] as I've received
few questions about it. At the moment my changes, which unlock PF
are experimental. The code is disabled by default. In order to
test my code you need to compile kernel with '-DWITH_PF_LOCK'. The
easiest thing is to patch CONFIG.MP with change below:
--------8<---------------8<---------------8<------------------8<--------
diff --git a/sys/arch/amd64/conf/GENERIC.MP b/sys/arch/amd64/conf/GENERIC.MP
index 7f195a53092..69c62d23cf8 100644
--- a/sys/arch/amd64/conf/GENERIC.MP
+++ b/sys/arch/amd64/conf/GENERIC.MP
@@ -2,6 +2,7 @@
 
 include "arch/amd64/conf/GENERIC"
 
+option WITH_PF_LOCK
 option MULTIPROCESSOR
 #option        MP_LOCKDEBUG
 #option        WITNESS
--------8<---------------8<---------------8<------------------8<--------
Compiling kernel with -DWITH_PF_LOCK will get you PF with two rw-locks:
    pf_lock, which serializes packets with ioctls. Currently pf_lock,
    is always grabbed exclusively.

    pf_state_lock, which packet grabs as a reader to look up a state,
    if no state is found, then packet drops the pf_state_lock and proceeds
    to rules. At that point packet grabs pf_lock as a writer. If the packet
    matches a pass rule, then it must grab pf_state_lock as a writer in
    order to insert a new state to table.

The pf_state_lock allows two pf_test() function to run in parallel for
packets, which match existing state. Packets, which don't match the state,
are serialized on pf_lock.

Mentioning parallelism: there is yet another change you need to perform
in order to get more pf_test() instances running. Currently there
is only single input task, which processes inbound packets. In order
to allow more input tasks one has to change NET_TASKQ constant found
in net/if.c. Patch below tells kernel to use two input tasks:
--------8<---------------8<---------------8<------------------8<--------
diff --git a/sys/net/if.c b/sys/net/if.c
index 78d7d3f3af6..87fca3921f1 100644
--- a/sys/net/if.c
+++ b/sys/net/if.c
@@ -232,7 +232,7 @@ int ifq_congestion;

 int             netisr;

-#define        NET_TASKQ       1
+#define        NET_TASKQ       4
 struct taskq   *nettqmp[NET_TASKQ];

 struct task if_input_task_locked = TASK_INITIALIZER(if_netisr, NULL);
--------8<---------------8<---------------8<------------------8<--------

Finally let me explain few details found in my patch:

        @@ -288,6 +296,9 @@ pfsyncattach(int npfsync)
         {
                if_clone_attach(&pfsync_cloner);
                pfsynccounters = counters_alloc(pfsyncs_ncounters);
        +#ifdef WITH_PF_LOCK
        +       mq_init(&pfsync_mq, 4096, IPL_SOFTNET);
        +#endif /* WITH_PF_LOCK */
         }

    The queue limit of 4k is empirically determined by testing on
    set up provided hrvoje@. The tests were running on intel 10Gb NICs.

    Using 4k provides sufficient buffer to limit drops of pfsync
    packets to <5%. The drops, which I could see when using 4k limit,
    were coming from ip_output() here:

 680         for (off = hlen + len; off < ntohs(ip->ip_len); off += len) {
 681                 MGETHDR(m, M_DONTWAIT, MT_HEADER);
 682                 if (m == NULL) {
 683                         ipstat_inc(ips_odropped);
 684                         error = ENOBUFS;
 685                         goto sendorfree;
 686                 }

    clearly the system ran out of packet header buffers, so more tuning would
    be needed. But this is something I'd like to keep out of scope of my 
changes.

OK to pfsync change?


thanks and
regards
sashan

[1] 
https://github.com/openbsd/src/commit/ddb22b9530d7ad1b4698803d7b6dcb21c96a3f7d#diff-9517dfce4e8db974781a4536fd38cfc1

[2] https://github.com/openbsd/src/blob/master/sys/net/if.c#L261


--------8<---------------8<---------------8<------------------8<--------
diff --git a/sys/net/if_pfsync.c b/sys/net/if_pfsync.c
index 8d86f4210fd..61f2a6df4cd 100644
--- a/sys/net/if_pfsync.c
+++ b/sys/net/if_pfsync.c
@@ -276,6 +276,14 @@ void       pfsync_bulk_start(void);
 void   pfsync_bulk_status(u_int8_t);
 void   pfsync_bulk_update(void *);
 void   pfsync_bulk_fail(void *);
+#ifdef WITH_PF_LOCK
+void   pfsync_send_dispatch(void *);
+void   pfsync_send_pkt(struct mbuf *);
+
+static struct mbuf_queue       pfsync_mq;
+static struct task     pfsync_task =
+    TASK_INITIALIZER(pfsync_send_dispatch, &pfsync_mq);
+#endif /* WITH_PF_LOCK */
 
 #define PFSYNC_MAX_BULKTRIES   12
 int    pfsync_sync_ok;
@@ -288,6 +296,9 @@ pfsyncattach(int npfsync)
 {
        if_clone_attach(&pfsync_cloner);
        pfsynccounters = counters_alloc(pfsyncs_ncounters);
+#ifdef WITH_PF_LOCK
+       mq_init(&pfsync_mq, 4096, IPL_SOFTNET);
+#endif /* WITH_PF_LOCK */
 }
 
 int
@@ -1497,6 +1508,54 @@ pfsync_drop(struct pfsync_softc *sc)
        sc->sc_len = PFSYNC_MINPKT;
 }
 
+#ifdef WITH_PF_LOCK
+void
+pfsync_send_dispatch(void *xmq)
+{
+       struct mbuf_queue *mq = xmq;
+       struct pfsync_softc *sc;
+       struct mbuf *m;
+       struct mbuf_list ml;
+       int e;
+
+       mq_delist(mq, &ml);
+       if (ml_empty(&ml))
+               return;
+
+       NET_RLOCK();
+       sc = pfsyncif;
+       if (sc == NULL) {
+               ml_purge(&ml);
+               goto done;
+       }
+
+       while ((m = ml_dequeue(&ml)) != NULL) {
+               if ((e = ip_output(m, NULL, NULL, IP_RAWOUTPUT, &sc->sc_imo,
+                   NULL, 0)) == 0)
+                       pfsyncstat_inc(pfsyncs_opackets);
+               else {
+                       DPFPRINTF(LOG_DEBUG,
+                           "ip_output() @ %s failed (%d)\n", __func__, e);
+                       pfsyncstat_inc(pfsyncs_oerrors);
+               }
+       }
+done:
+       NET_RUNLOCK();
+}
+
+void
+pfsync_send_pkt(struct mbuf *m)
+{
+       if (mq_enqueue(&pfsync_mq, m) != 0) {
+               pfsyncstat_inc(pfsyncs_oerrors);
+               DPFPRINTF(LOG_DEBUG, "mq_enqueue() @ %s failed, queue full\n",
+                   __func__);
+       }
+       else
+               task_add(net_tq(0), &pfsync_task);
+}
+#endif /* WITH_PF_LOCK */
+
 void
 pfsync_sendout(void)
 {
@@ -1669,10 +1728,14 @@ pfsync_sendout(void)
 
        m->m_pkthdr.ph_rtableid = sc->sc_if.if_rdomain;
 
+#ifdef WITH_PF_LOCK
+       pfsync_send_pkt(m);
+#else  /* !WITH_PF_LOCK */
        if (ip_output(m, NULL, NULL, IP_RAWOUTPUT, &sc->sc_imo, NULL, 0) == 0)
                pfsyncstat_inc(pfsyncs_opackets);
        else
                pfsyncstat_inc(pfsyncs_oerrors);
+#endif /* WITH_PF_LOCK */
 }
 
 void

Reply via email to