--- /dev/null
+/* $OpenBSD: pf_syncookies.c,v 1.1 2018/02/06 23:37:24 henning Exp $ */
+
+/* Copyright (c) 2016,2017 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2016 Alexandr Nedvedicky <sashan@openbsd.org>
+ *
+ * syncookie parts based on FreeBSD sys/netinet/tcp_syncache.c
+ *
+ * Copyright (c) 2001 McAfee, Inc.
+ * Copyright (c) 2006,2013 Andre Oppermann, Internet Business Solutions AG
+ * All rights reserved.
+ *
+ * This software was developed for the FreeBSD Project by Jonathan Lemon
+ * and McAfee Research, the Security Research Division of McAfee, Inc. under
+ * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
+ * DARPA CHATS research program. [2001 McAfee, Inc.]
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * when we're under synflood, we use syncookies to prevent state table
+ * exhaustion. Trigger for the synflood mode is the number of half-open
+ * connections in the state table.
+ * We leave synflood mode when the number of half-open states - including
+ * in-flight syncookies - drops far enough again
+ */
+
+/*
+ * syncookie enabled Initial Sequence Number:
+ * 24 bit MAC
+ * 3 bit WSCALE index
+ * 3 bit MSS index
+ * 1 bit SACK permitted
+ * 1 bit odd/even secret
+ *
+ * References:
+ * RFC4987 TCP SYN Flooding Attacks and Common Mitigations
+ * http://cr.yp.to/syncookies.html (overview)
+ * http://cr.yp.to/syncookies/archive (details)
+ */
+
+#include "pflog.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/filio.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/kernel.h>
+#include <sys/time.h>
+#include <sys/pool.h>
+#include <sys/proc.h>
+#include <sys/rwlock.h>
+#include <sys/syslog.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcp.h>
+#include <netinet/tcp_seq.h>
+#include <netinet/udp.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/in_pcb.h>
+#include <netinet/tcp_timer.h>
+#include <netinet/tcp_var.h>
+#include <netinet/tcp_fsm.h>
+#include <netinet/udp_var.h>
+#include <netinet/icmp_var.h>
+#include <netinet/ip_divert.h>
+
+#include <net/pfvar.h>
+#include <net/pfvar_priv.h>
+
+#if NPFLOG > 0
+#include <net/if_pflog.h>
+#endif /* NPFLOG > 0 */
+
+union pf_syncookie {
+ uint8_t cookie;
+ struct {
+ uint8_t oddeven:1,
+ sack_ok:1,
+ wscale_idx:3,
+ mss_idx:3;
+ } flags;
+};
+
+#define PF_SYNCOOKIE_SECRET_SIZE 16
+#define PF_SYNCOOKIE_SECRET_LIFETIME 15 /* seconds */
+
+static struct {
+ struct timeout keytimeout;
+ volatile uint oddeven;
+ uint8_t key[2][PF_SYNCOOKIE_SECRET_SIZE];
+ uint32_t hiwat; /* absolute; # of states */
+ uint32_t lowat;
+} pf_syncookie_status;
+
+void pf_syncookie_rotate(void *);
+void pf_syncookie_newkey(void);
+uint32_t pf_syncookie_mac(struct pf_pdesc *, union pf_syncookie,
+ uint32_t);
+uint32_t pf_syncookie_generate(struct pf_pdesc *, uint16_t);
+
+void
+pf_syncookies_init(void)
+{
+ timeout_set(&pf_syncookie_status.keytimeout,
+ pf_syncookie_rotate, NULL);
+ pf_syncookie_status.hiwat = PFSTATE_HIWAT/4;
+ pf_syncookie_status.lowat = PFSTATE_HIWAT/8;
+ pf_syncookies_setmode(PF_SYNCOOKIES_NEVER);
+}
+
+int
+pf_syncookies_setmode(u_int8_t mode)
+{
+ if (mode > PF_SYNCOOKIES_MODE_MAX)
+ return (EINVAL);
+
+ if (pf_status.syncookies_mode == mode)
+ return (0);
+
+ pf_status.syncookies_mode = mode;
+ if (pf_status.syncookies_mode == PF_SYNCOOKIES_ALWAYS) {
+ pf_syncookie_newkey();
+ pf_status.syncookies_active = 1;
+ }
+ return (0);
+}
+
+int
+pf_syncookies_setwats(u_int32_t hiwat, u_int32_t lowat)
+{
+ if (lowat > hiwat)
+ return (EINVAL);
+
+ pf_syncookie_status.hiwat = hiwat;
+ pf_syncookie_status.lowat = lowat;
+ return (0);
+}
+
+int
+pf_synflood_check(struct pf_pdesc *pd)
+{
+ KASSERT (pd->proto == IPPROTO_TCP);
+
+ if (pd->m && (pd->m->m_pkthdr.pf.tag & PF_TAG_SYNCOOKIE_RECREATED))
+ return (0);
+
+ if (pf_status.syncookies_mode != PF_SYNCOOKIES_ADAPTIVE)
+ return (pf_status.syncookies_mode);
+
+ if (!pf_status.syncookies_active &&
+ pf_status.states_halfopen > pf_syncookie_status.hiwat) {
+ pf_syncookie_newkey();
+ pf_status.syncookies_active = 1;
+ DPFPRINTF(LOG_WARNING,
+ "synflood detected, enabling syncookies");
+ }
+
+ return (pf_status.syncookies_active);
+}
+
+void
+pf_syncookie_send(struct pf_pdesc *pd)
+{
+ uint16_t mss;
+ uint32_t iss;
+
+ mss = max(tcp_mssdflt, pf_get_mss(pd));
+ iss = pf_syncookie_generate(pd, mss);
+ pf_send_tcp(NULL, pd->af, pd->dst, pd->src, *pd->dport, *pd->sport,
+ iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN|TH_ACK, 0, mss,
+ 0, 1, 0, pd->rdomain);
+ pf_status.syncookies_inflight[pf_syncookie_status.oddeven]++;
+}
+
+uint8_t
+pf_syncookie_validate(struct pf_pdesc *pd)
+{
+ uint32_t hash, ack, seq;
+ union pf_syncookie cookie;
+
+ KASSERT(pd->proto == IPPROTO_TCP);
+
+ seq = ntohl(pd->hdr.tcp.th_seq) - 1;
+ ack = ntohl(pd->hdr.tcp.th_ack) - 1;
+ cookie.cookie = (ack & 0xff) ^ (ack >> 24);
+ hash = pf_syncookie_mac(pd, cookie, seq);
+
+ if ((ack & ~0xff) != (hash & ~0xff))
+ return (0);
+
+ pf_status.syncookies_inflight[cookie.flags.oddeven]--;
+ return (1);
+}
+
+/*
+ * all following functions private
+ */
+void
+pf_syncookie_rotate(void *arg)
+{
+ /* do we want to disable syncookies? */
+ if (pf_status.syncookies_active &&
+ ((pf_status.syncookies_mode == PF_SYNCOOKIES_ADAPTIVE &&
+ pf_status.states_halfopen + pf_status.syncookies_inflight[0] +
+ pf_status.syncookies_inflight[1] < pf_syncookie_status.lowat) ||
+ pf_status.syncookies_mode == PF_SYNCOOKIES_NEVER)) {
+ pf_status.syncookies_active = 0;
+ DPFPRINTF(LOG_WARNING, "syncookies disabled");
+ }
+
+ /* nothing in flight any more? delete keys and return */
+ if (!pf_status.syncookies_active &&
+ pf_status.syncookies_inflight[0] == 0 &&
+ pf_status.syncookies_inflight[1] == 0) {
+ memset(pf_syncookie_status.key[0], 0, PF_SYNCOOKIE_SECRET_SIZE);
+ memset(pf_syncookie_status.key[1], 0, PF_SYNCOOKIE_SECRET_SIZE);
+ return;
+ }
+
+ /* new key, including timeout */
+ pf_syncookie_newkey();
+}
+
+void
+pf_syncookie_newkey(void)
+{
+ pf_syncookie_status.oddeven = (pf_syncookie_status.oddeven + 1) & 0x1;
+ pf_status.syncookies_inflight[pf_syncookie_status.oddeven] = 0;
+ arc4random_buf(pf_syncookie_status.key[pf_syncookie_status.oddeven],
+ PF_SYNCOOKIE_SECRET_SIZE);
+ timeout_add_sec(&pf_syncookie_status.keytimeout,
+ PF_SYNCOOKIE_SECRET_LIFETIME);
+}
+
+/*
+ * Distribution and probability of certain MSS values. Those in between are
+ * rounded down to the next lower one.
+ * [An Analysis of TCP Maximum Segment Sizes, S. Alcock and R. Nelson, 2011]
+ * .2% .3% 5% 7% 7% 20% 15% 45%
+ */
+static int pf_syncookie_msstab[] =
+ { 216, 536, 1200, 1360, 1400, 1440, 1452, 1460 };
+
+/*
+ * Distribution and probability of certain WSCALE values.
+ * The absence of the WSCALE option is encoded with index zero.
+ * [WSCALE values histograms, Allman, 2012]
+ * X 10 10 35 5 6 14 10% by host
+ * X 11 4 5 5 18 49 3% by connections
+ */
+static int pf_syncookie_wstab[] = { 0, 0, 1, 2, 4, 6, 7, 8 };
+
+uint32_t
+pf_syncookie_mac(struct pf_pdesc *pd, union pf_syncookie cookie, uint32_t seq)
+{
+ SIPHASH_CTX ctx;
+ uint32_t siphash[2];
+
+ KASSERT(pd->proto == IPPROTO_TCP);
+
+ SipHash24_Init(&ctx,
+ (SIPHASH_KEY *)&pf_syncookie_status.key[cookie.flags.oddeven]);
+
+ switch (pd->af) {
+ case AF_INET:
+ SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v4));
+ SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v4));
+ break;
+ case AF_INET6:
+ SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v6));
+ SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v6));
+ break;
+ default:
+ panic("unknown address family");
+ }
+
+ SipHash24_Update(&ctx, pd->sport, sizeof(*pd->sport));
+ SipHash24_Update(&ctx, pd->dport, sizeof(*pd->dport));
+ SipHash24_Update(&ctx, &seq, sizeof(seq));
+ SipHash24_Update(&ctx, &cookie, sizeof(cookie));
+ SipHash24_Final((uint8_t *)&siphash, &ctx);
+
+ return (siphash[0] ^ siphash[1]);
+}
+
+uint32_t
+pf_syncookie_generate(struct pf_pdesc *pd, uint16_t mss)
+{
+ uint8_t i, wscale;
+ uint32_t iss, hash;
+ union pf_syncookie cookie;
+
+ cookie.cookie = 0;
+
+ /* map MSS */
+ for (i = nitems(pf_syncookie_msstab) - 1;
+ pf_syncookie_msstab[i] > mss && i > 0; i--)
+ /* nada */;
+ cookie.flags.mss_idx = i;
+
+ /* map WSCALE */
+ wscale = pf_get_wscale(pd);
+ for (i = nitems(pf_syncookie_wstab) - 1;
+ pf_syncookie_wstab[i] > wscale && i > 0; i--)
+ /* nada */;
+ cookie.flags.wscale_idx = i;
+ cookie.flags.sack_ok = 0; /* XXX */
+
+ cookie.flags.oddeven = pf_syncookie_status.oddeven;
+ hash = pf_syncookie_mac(pd, cookie, ntohl(pd->hdr.tcp.th_seq));
+
+ /*
+ * Put the flags into the hash and XOR them to get better ISS number
+ * variance. This doesn't enhance the cryptographic strength and is
+ * done to prevent the 8 cookie bits from showing up directly on the
+ * wire.
+ */
+ iss = hash & ~0xff;
+ iss |= cookie.cookie ^ (hash >> 24);
+
+ return (iss);
+}
+
+struct mbuf *
+pf_syncookie_recreate_syn(struct pf_pdesc *pd)
+{
+ uint8_t wscale;
+ uint16_t mss;
+ uint32_t ack, seq;
+ union pf_syncookie cookie;
+
+ seq = ntohl(pd->hdr.tcp.th_seq) - 1;
+ ack = ntohl(pd->hdr.tcp.th_ack) - 1;
+ cookie.cookie = (ack & 0xff) ^ (ack >> 24);
+
+ if (cookie.flags.mss_idx >= nitems(pf_syncookie_msstab) ||
+ cookie.flags.wscale_idx >= nitems(pf_syncookie_wstab))
+ return (NULL);
+
+ mss = pf_syncookie_msstab[cookie.flags.mss_idx];
+ wscale = pf_syncookie_wstab[cookie.flags.wscale_idx];
+
+ return (pf_build_tcp(NULL, pd->af, pd->src, pd->dst, *pd->sport,
+ *pd->dport, seq, 0, TH_SYN, wscale, mss, pd->ttl, 0,
+ PF_TAG_SYNCOOKIE_RECREATED, cookie.flags.sack_ok, pd->rdomain));
+}
+
+#define PF_TCPOPTLEN_SACKPERMITTED 2
+
+int
+pf_check_sack(struct pf_pdesc *pd)
+{
+ struct tcphdr *th = &pd->hdr.tcp;
+ int hlen = (th->th_off << 2) - sizeof(*th);
+ uint8_t opts[MAX_TCPOPTLEN], *opt = opts;
+ int olen;
+
+ if (hlen < PF_TCPOPTLEN_SACKPERMITTED || hlen > MAX_TCPOPTLEN ||
+ !pf_pull_hdr(pd->m, pd->off + sizeof(*th), opts, hlen, NULL, NULL,
+ pd->af))
+ return (0);
+
+ while (hlen >= PF_TCPOPTLEN_SACKPERMITTED) {
+ olen = opt[1];
+ switch (*opt) {
+ case TCPOPT_EOL: /* FALLTHROUGH */
+ case TCPOPT_NOP:
+ opt++;
+ hlen--;
+ break;
+ case TCPOPT_SACK_PERMITTED:
+ return (1);
+ default:
+ if (olen < 2)
+ olen = 2;
+ hlen -= olen;
+ opt += olen;
+ }
+ }
+
+ return (0);
+}