#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <crypto/siphash.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <netinet/tcp_timer.h>
#include <netinet/tcp_var.h>
#include <net/pfvar.h>
#include <net/pfvar_priv.h>
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 SIPHASH_KEY_LENGTH
#define PF_SYNCOOKIE_SECRET_LIFETIME 15
static struct {
struct timeout keytimeout;
volatile uint oddeven;
SIPHASH_KEY key[2];
uint32_t hiwat;
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 * PF_SYNCOOKIES_HIWATPCT/100;
pf_syncookie_status.lowat = PFSTATE_HIWAT * PF_SYNCOOKIES_LOWATPCT/100;
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_syncookies_getwats(struct pfioc_synflwats *wats)
{
wats->hiwat = pf_syncookie_status.hiwat;
wats->lowat = pf_syncookie_status.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");
pf_status.lcounters[LCNT_SYNFLOODS]++;
}
return (pf_status.syncookies_active);
}
void
pf_syncookie_send(struct pf_pdesc *pd, u_short *reason)
{
uint16_t mss, mssdflt;
uint32_t iss;
mssdflt = atomic_load_int(&tcp_mssdflt);
mss = max(pf_get_mss(pd, mssdflt), mssdflt);
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, reason);
pf_status.syncookies_inflight[pf_syncookie_status.oddeven]++;
pf_status.lcounters[LCNT_SYNCOOKIES_SENT]++;
}
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);
if (pf_status.syncookies_inflight[cookie.flags.oddeven] == 0)
return (0);
hash = pf_syncookie_mac(pd, cookie, seq);
if ((ack & ~0xff) != (hash & ~0xff))
return (0);
pf_status.syncookies_inflight[cookie.flags.oddeven]--;
pf_status.lcounters[LCNT_SYNCOOKIES_VALID]++;
return (1);
}
void
pf_syncookie_rotate(void *arg)
{
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");
}
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;
}
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);
}
static int pf_syncookie_msstab[] =
{ 216, 536, 1200, 1360, 1400, 1440, 1452, 1460 };
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, &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;
for (i = nitems(pf_syncookie_msstab) - 1;
pf_syncookie_msstab[i] > mss && i > 0; i--)
;
cookie.flags.mss_idx = i;
wscale = pf_get_wscale(pd);
for (i = nitems(pf_syncookie_wstab) - 1;
pf_syncookie_wstab[i] > wscale && i > 0; i--)
;
cookie.flags.wscale_idx = i;
cookie.flags.sack_ok = 0;
cookie.flags.oddeven = pf_syncookie_status.oddeven;
hash = pf_syncookie_mac(pd, cookie, ntohl(pd->hdr.tcp.th_seq));
iss = hash & ~0xff;
iss |= cookie.cookie ^ (hash >> 24);
return (iss);
}
struct mbuf *
pf_syncookie_recreate_syn(struct pf_pdesc *pd, u_short *reason)
{
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,
reason));
}