syncookies for pf.
authorhenning <henning@openbsd.org>
Tue, 6 Feb 2018 23:44:48 +0000 (23:44 +0000)
committerhenning <henning@openbsd.org>
Tue, 6 Feb 2018 23:44:48 +0000 (23:44 +0000)
when syncookies are on, pf will blindly answer each and every SYN with a
syncookie-SYNACK. Upon reception of the ACK completing the 3WHS, pf will
reconstruct the original SYN, shove it through pf_test, where state will
be created if the ruleset permits it. Then massage the freshly created state
(we won't see the SYNACK), set up the sequence number modulator, and call
into the existing synproxy code to start the 3WHS with the backend host.
Add an - somewhat basic for now - adaptive mode where syncookies get enabled
if a certain percentage of the state table is filled up with half-open tcp
connections. This makes pf firewalls resilient against large synflood
attacks.
syncookies are off by default until we gained more experience, considered
experimental for now.
see http://bulabula.org/papers/2017/bsdcan/ for more details.
joint work with sashan@, widely discussed and with lots of input by many

sys/conf/files
sys/net/pf.c
sys/net/pf_ioctl.c
sys/net/pfvar.h
sys/sys/mbuf.h

index e948086..f623c41 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files,v 1.656 2017/11/16 18:12:27 jcs Exp $
+#      $OpenBSD: files,v 1.657 2018/02/06 23:44:48 henning Exp $
 #      $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
 
 #      @(#)files.newconf       7.5 (Berkeley) 5/10/93
@@ -580,6 +580,7 @@ file        net/pf_table.c                  pf
 file   net/pf_osfp.c                   pf
 file   net/pf_if.c                     pf
 file   net/pf_lb.c                     pf
+file   net/pf_syncookies.c             pf
 file   net/hfsc.c                      pf
 file   net/fq_codel.c                  pf
 
index 1f8ac7d..51a9111 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pf.c,v 1.1059 2018/02/06 09:16:11 henning Exp $ */
+/*     $OpenBSD: pf.c,v 1.1060 2018/02/06 23:44:48 henning Exp $ */
 
 /*
  * Copyright (c) 2001 Daniel Hartmeier
@@ -213,7 +213,7 @@ int                  pf_tcp_track_sloppy(struct pf_pdesc *,
 static __inline int     pf_synproxy(struct pf_pdesc *, struct pf_state **,
                            u_short *);
 int                     pf_test_state(struct pf_pdesc *, struct pf_state **,
-                           u_short *);
+                           u_short *, int);
 int                     pf_icmp_state_lookup(struct pf_pdesc *,
                            struct pf_state_key_cmp *, struct pf_state **,
                            u_int16_t, u_int16_t, int, int *, int, int);
@@ -4718,7 +4718,8 @@ pf_synproxy(struct pf_pdesc *pd, struct pf_state **state, u_short *reason)
 }
 
 int
-pf_test_state(struct pf_pdesc *pd, struct pf_state **state, u_short *reason)
+pf_test_state(struct pf_pdesc *pd, struct pf_state **state, u_short *reason,
+    int syncookie)
 {
        struct pf_state_key_cmp  key;
        int                      copyback = 0;
@@ -4752,6 +4753,11 @@ pf_test_state(struct pf_pdesc *pd, struct pf_state **state, u_short *reason)
 
        switch (pd->virtual_proto) {
        case IPPROTO_TCP:
+               if (syncookie) {
+                       pf_set_protostate(*state, PF_PEER_SRC,
+                           PF_TCPS_PROXY_DST);
+                       (*state)->dst.seqhi = ntohl(pd->hdr.tcp.th_ack) - 1;
+               }
                if ((action = pf_synproxy(pd, state, reason)) != PF_PASS)
                        return (action);
                if ((pd->hdr.tcp.th_flags & (TH_SYN|TH_ACK)) == TH_SYN) {
@@ -6904,13 +6910,54 @@ pf_test(sa_family_t af, int fwdir, struct ifnet *ifp, struct mbuf **m0)
 
        default:
                if (pd.virtual_proto == IPPROTO_TCP) {
+                       if (pd.dir == PF_IN && (pd.hdr.tcp.th_flags &
+                           (TH_SYN|TH_ACK)) == TH_SYN &&
+                           pf_synflood_check(&pd)) {
+                               pf_syncookie_send(&pd);
+                               action = PF_DROP;
+                               goto done;
+                       }
                        if ((pd.hdr.tcp.th_flags & TH_ACK) && pd.p_len == 0)
                                pqid = 1;
                        action = pf_normalize_tcp(&pd);
                        if (action == PF_DROP)
                                goto unlock;
                }
-               action = pf_test_state(&pd, &s, &reason);
+               action = pf_test_state(&pd, &s, &reason, 0);
+               if (s == NULL && action != PF_PASS && action != PF_AFRT &&
+                   pd.dir == PF_IN && pd.virtual_proto == IPPROTO_TCP &&
+                   (pd.hdr.tcp.th_flags & (TH_SYN|TH_ACK|TH_RST)) == TH_ACK &&
+                   pf_syncookie_validate(&pd)) {
+                       struct mbuf     *msyn;
+                       msyn = pf_syncookie_recreate_syn(&pd);
+                       if (msyn) {
+                               PF_UNLOCK();
+                               action = pf_test(af, fwdir, ifp, &msyn);
+                               PF_LOCK();
+                               m_freem(msyn);
+                               if (action == PF_PASS || action == PF_AFRT) {
+                                       pf_test_state(&pd, &s, &reason, 1);
+                                       if (s == NULL) {
+                                               PF_UNLOCK();
+                                               return (PF_DROP);
+                                       }
+                                       s->src.seqhi =
+                                           ntohl(pd.hdr.tcp.th_ack) - 1;
+                                       s->src.seqlo =
+                                           ntohl(pd.hdr.tcp.th_seq) - 1;
+                                       pf_set_protostate(s, PF_PEER_SRC,
+                                           PF_TCPS_PROXY_DST);
+                                       action = pf_synproxy(&pd, &s, &reason);
+                                       if (action != PF_PASS) {
+                                               PF_UNLOCK();
+                                               return (action);
+                                       }
+                               }
+                       } else
+                               action = PF_DROP;
+               }
+
+
                if (action == PF_PASS || action == PF_AFRT) {
 #if NPFSYNC > 0
                        pfsync_update_state(s);
index a88a946..231ff91 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pf_ioctl.c,v 1.328 2018/02/06 09:16:11 henning Exp $ */
+/*     $OpenBSD: pf_ioctl.c,v 1.329 2018/02/06 23:44:48 henning Exp $ */
 
 /*
  * Copyright (c) 2001 Daniel Hartmeier
@@ -173,6 +173,7 @@ pfattach(int num)
        pfr_initialize();
        pfi_initialize();
        pf_osfp_initialize();
+       pf_syncookies_init();
 
        pool_sethardlimit(pf_pool_limits[PF_LIMIT_STATES].pp,
            pf_pool_limits[PF_LIMIT_STATES].limit, NULL, 0);
@@ -2645,6 +2646,20 @@ pfioctl(dev_t dev, u_long cmd, caddr_t addr, int flags, struct proc *p)
                break;
        }
 
+       case DIOCSETSYNFLWATS: {
+               struct pfioc_synflwats *io = (struct pfioc_synflwats *)addr;
+
+               error = pf_syncookies_setwats(io->hiwat, io->lowat);
+               break;
+       }
+
+       case DIOCSETSYNCOOKIES: {
+               u_int8_t        *mode = (u_int8_t *)addr;
+
+               error = pf_syncookies_setmode(*mode);
+               break;
+       }
+
        default:
                error = ENODEV;
                break;
index 13f7fa9..8cade49 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: pfvar.h,v 1.470 2017/12/29 17:05:25 bluhm Exp $ */
+/*     $OpenBSD: pfvar.h,v 1.471 2018/02/06 23:44:48 henning Exp $ */
 
 /*
  * Copyright (c) 2001 Daniel Hartmeier
@@ -1296,6 +1296,7 @@ struct pf_status {
        u_int64_t       pcounters[2][2][3];
        u_int64_t       bcounters[2][2];
        u_int64_t       stateid;
+       u_int64_t       syncookies_inflight[2]; /* unACKed SYNcookies */
        time_t          since;
        u_int32_t       running;
        u_int32_t       states;
@@ -1304,6 +1305,9 @@ struct pf_status {
        u_int32_t       debug;
        u_int32_t       hostid;
        u_int32_t       reass;                  /* reassembly */
+       u_int8_t        syncookies_active;
+       u_int8_t        syncookies_mode;        /* never/always/adaptive */
+       u_int8_t        pad[2];
        char            ifname[IFNAMSIZ];
        u_int8_t        pf_chksum[PF_MD5_DIGEST_LENGTH];
 };
@@ -1311,6 +1315,11 @@ struct pf_status {
 #define PF_REASS_ENABLED       0x01
 #define PF_REASS_NODF          0x02
 
+#define        PF_SYNCOOKIES_NEVER     0
+#define        PF_SYNCOOKIES_ALWAYS    1
+#define        PF_SYNCOOKIES_ADAPTIVE  2
+#define        PF_SYNCOOKIES_MODE_MAX  PF_SYNCOOKIES_ADAPTIVE
+
 #define PF_PRIO_ZERO           0xff            /* match "prio 0" packets */
 
 struct pf_queue_bwspec {
@@ -1564,6 +1573,10 @@ struct pfioc_iface {
        int      pfiio_flags;
 };
 
+struct pfioc_synflwats {
+       u_int32_t       hiwat;
+       u_int32_t       lowat;
+};
 
 /*
  * ioctl operations
@@ -1629,6 +1642,8 @@ struct pfioc_iface {
 #define DIOCGETQUEUES  _IOWR('D', 94, struct pfioc_queue)
 #define DIOCGETQUEUE   _IOWR('D', 95, struct pfioc_queue)
 #define DIOCGETQSTATS  _IOWR('D', 96, struct pfioc_qstats)
+#define DIOCSETSYNFLWATS       _IOWR('D', 97, struct pfioc_synflwats)
+#define DIOCSETSYNCOOKIES      _IOWR('D', 98, u_int8_t)
 
 #ifdef _KERNEL
 
@@ -1921,6 +1936,13 @@ void                      pf_send_tcp(const struct pf_rule *, sa_family_t,
                            u_int16_t, u_int16_t, u_int32_t, u_int32_t,
                            u_int8_t, u_int16_t, u_int16_t, u_int8_t, int,
                            u_int16_t, u_int);
+void                    pf_syncookies_init(void);
+int                     pf_syncookies_setmode(u_int8_t);
+int                     pf_syncookies_setwats(u_int32_t, u_int32_t);
+int                     pf_synflood_check(struct pf_pdesc *);
+void                    pf_syncookie_send(struct pf_pdesc *);
+u_int8_t                pf_syncookie_validate(struct pf_pdesc *);
+struct mbuf *           pf_syncookie_recreate_syn(struct pf_pdesc *);
 #endif /* _KERNEL */
 
 #endif /* _NET_PFVAR_H_ */
index c60f0e2..5a2dbbe 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: mbuf.h,v 1.232 2017/10/19 11:02:42 bluhm Exp $        */
+/*     $OpenBSD: mbuf.h,v 1.233 2018/02/06 23:44:48 henning Exp $      */
 /*     $NetBSD: mbuf.h,v 1.19 1996/02/09 18:25:14 christos Exp $       */
 
 /*
@@ -108,6 +108,7 @@ struct pkthdr_pf {
 
 /* pkthdr_pf.flags */
 #define        PF_TAG_GENERATED                0x01
+#define        PF_TAG_SYNCOOKIE_RECREATED      0x02
 #define        PF_TAG_TRANSLATE_LOCALHOST      0x04
 #define        PF_TAG_DIVERTED                 0x08
 #define        PF_TAG_DIVERTED_PACKET          0x10