Hi,

IPsec packets are passed through ip_input() a second time after
they have been decrypted.  That means that all the IP header fields
are checked twice.  Also fragment reassembly is tried twice.

In pf incoming packets in tunnel mode appear twice on the enc0
interface.  Once as IP-in-IP and once as the inner packet.  In the
outgoing path we only see the inner packet.  Asymmetry is bad for
stateful filtering.

IPv6 shows that IPsec works without that.  After decrypting we can
continue with local delivery.  If we are in tunnel mode, the IP-in-IP
protocol functions do what we want.  In transport mode only pf_test()
has to be called for the enc0 device.

Introducing ip_local() means less needless processing and cleaner
pf behavior.

ok?

bluhm

Index: netinet/ip_input.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_input.c,v
retrieving revision 1.299
diff -u -p -r1.299 ip_input.c
--- netinet/ip_input.c  11 May 2017 11:36:20 -0000      1.299
+++ netinet/ip_input.c  12 May 2017 12:34:42 -0000
@@ -483,9 +483,6 @@ ip_ours(struct mbuf *m)
 
        hlen = ip->ip_hl << 2;
 
-       /* pf might have modified stuff, might have to chksum */
-       in_proto_cksum_out(m, NULL);
-
        /*
         * If offset or IP_MF are set, must reassemble.
         * Otherwise, nothing need be done.
@@ -570,11 +567,26 @@ found:
                                ip_freef(fp);
        }
 
+       ip_local(m, hlen, ip->ip_p);
+       return;
+bad:
+       m_freem(m);
+}
+
+void
+ip_local(struct mbuf *m, int off, int nxt)
+{
+       KERNEL_ASSERT_LOCKED();
+
+       /* pf might have modified stuff, might have to chksum */
+       in_proto_cksum_out(m, NULL);
+
 #ifdef IPSEC
        if (ipsec_in_use) {
-               if (ip_input_ipsec_ours_check(m, hlen) != 0) {
+               if (ip_input_ipsec_ours_check(m, off) != 0) {
                        ipstat_inc(ips_cantforward);
-                       goto bad;
+                       m_freem(m);
+                       return;
                }
        }
        /* Otherwise, just fall through and deliver the packet */
@@ -584,10 +596,7 @@ found:
         * Switch out to protocol's input routine.
         */
        ipstat_inc(ips_delivered);
-       (*inetsw[ip_protox[ip->ip_p]].pr_input)(&m, &hlen, ip->ip_p, AF_INET);
-       return;
-bad:
-       m_freem(m);
+       (*inetsw[ip_protox[nxt]].pr_input)(&m, &off, nxt, AF_INET);
 }
 
 int
Index: netinet/ip_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_var.h,v
retrieving revision 1.71
diff -u -p -r1.71 ip_var.h
--- netinet/ip_var.h    14 Apr 2017 20:46:31 -0000      1.71
+++ netinet/ip_var.h    12 May 2017 12:34:42 -0000
@@ -249,6 +249,7 @@ void         ip_savecontrol(struct inpcb *, str
            struct mbuf *);
 void    ipintr(void);
 void    ipv4_input(struct mbuf *);
+void    ip_local(struct mbuf *, int, int);
 void    ip_forward(struct mbuf *, struct ifnet *, struct rtentry *, int);
 int     rip_ctloutput(int, struct socket *, int, int, struct mbuf *);
 void    rip_init(void);
Index: netinet/ipsec_input.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ipsec_input.c,v
retrieving revision 1.149
diff -u -p -r1.149 ipsec_input.c
--- netinet/ipsec_input.c       11 May 2017 12:14:43 -0000      1.149
+++ netinet/ipsec_input.c       12 May 2017 12:51:51 -0000
@@ -579,44 +579,38 @@ ipsec_common_input_cb(struct mbuf *m, st
                return;
        }
 
+#if NPF > 0
+       /*
+        * The ip_local() shortcut avoids running through ip_input() with the
+        * same IP header twice.  Packets in transport mode have to be be
+        * passed to pf explicitly.  In tunnel mode the inner IP header will
+        * run through ip_input() and pf anyway.
+        */
+       if ((tdbp->tdb_flags & TDBF_TUNNELING) == 0) {
+               struct ifnet *ifp;
+
+               /* This is the enc0 interface unless for ipcomp. */
+               if ((ifp = if_get(m->m_pkthdr.ph_ifidx)) == NULL) {
+                       m_freem(m);
+                       return;
+               }
+               if (pf_test(af, PF_IN, ifp, &m) != PF_PASS) {
+                       if_put(ifp);
+                       m_freem(m);
+                       return;
+               }
+               if_put(ifp);
+               if (m == NULL)
+                       return;
+       }
+#endif
        /* Call the appropriate IPsec transform callback. */
        switch (af) {
        case AF_INET:
-               if (niq_enqueue(&ipintrq, m) != 0) {
-                       DPRINTF(("ipsec_common_input_cb(): dropped packet "
-                           "because of full IP queue\n"));
-                       IPSEC_ISTAT(espstat.esps_qfull, ahstat.ahs_qfull,
-                           ipcompstat.ipcomps_qfull);
-               }
+               ip_local(m, skip, prot);
                return;
 #ifdef INET6
        case AF_INET6:
-#if NPF > 0
-               /*
-                * In the IPv4 case all packets run through ipv4_input()
-                * twice.  As the ip6_local() shortcut avoids this, IPv6
-                * packets in transport mode have to be be passed to pf
-                * explicitly.  In tunnel mode the inner IP header will
-                * run through ip_input() and pf already.
-                */
-               if ((tdbp->tdb_flags & TDBF_TUNNELING) == 0) {
-                       struct ifnet *ifp;
-
-                       /* This is the enc0 interface unless for ipcomp. */
-                       if ((ifp = if_get(m->m_pkthdr.ph_ifidx)) == NULL) {
-                               m_freem(m);
-                               return;
-                       }
-                       if (pf_test(AF_INET6, PF_IN, ifp, &m) != PF_PASS) {
-                               if_put(ifp);
-                               m_freem(m);
-                               return;
-                       }
-                       if_put(ifp);
-                       if (m == NULL)
-                               return;
-               }
-#endif
                ip6_local(m, skip, prot);
                return;
 #endif /* INET6 */

Reply via email to