Hi,
To prevent attacks on the hash buckets of the syn cache, our TCP
stack reseeds the hash function every time the cache is empty.
Unfortunatly the attacker can prevent the reseeding by sending
unanswered SYN packes periodically.
I fix this by having an active syn cache that gets new entries and
a passive one that is idling out. When the passive one is empty
it becomes active with a new random hash seed.
The drawback is that the the cache lookup has to be done in two syn
caches when an ACK arrives.
Perhaps the tcps_sc_seedrandom counter with a netstat -s line should
be commited anyway to show the problem.
I think this reseeding fix is independent from a decision if the
change the hash algorithm to sip hash. I also have a diff for that.
comments?
bluhm
Index: netinet/tcp_input.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_input.c,v
retrieving revision 1.314
diff -u -p -r1.314 tcp_input.c
--- netinet/tcp_input.c 7 Mar 2016 18:44:00 -0000 1.314
+++ netinet/tcp_input.c 19 Mar 2016 20:58:34 -0000
@@ -3260,40 +3260,44 @@ tcp_mss_adv(struct mbuf *m, int af)
int tcp_syn_cache_size = TCP_SYN_HASH_SIZE;
int tcp_syn_cache_limit = TCP_SYN_HASH_SIZE*TCP_SYN_BUCKET_SIZE;
int tcp_syn_bucket_limit = 3*TCP_SYN_BUCKET_SIZE;
-int tcp_syn_cache_count;
-struct syn_cache_head tcp_syn_cache[TCP_SYN_HASH_SIZE];
-u_int32_t tcp_syn_hash[5];
-
-#define SYN_HASH(sa, sp, dp) \
- (((sa)->s_addr ^ tcp_syn_hash[0]) * \
- (((((u_int32_t)(dp))<<16) + ((u_int32_t)(sp))) ^ tcp_syn_hash[4]))
+
+struct syn_cache_set {
+ struct syn_cache_head scs_buckethead[TCP_SYN_HASH_SIZE];
+ int scs_count;
+ u_int32_t scs_random[5];
+} tcp_syn_cache[2];
+int tcp_syn_cache_active;
+
+#define SYN_HASH(sa, sp, dp, rand) \
+ (((sa)->s_addr ^ (rand)[0]) * \
+ (((((u_int32_t)(dp))<<16) + ((u_int32_t)(sp))) ^ (rand)[4]))
#ifndef INET6
-#define SYN_HASHALL(hash, src, dst) \
+#define SYN_HASHALL(hash, src, dst, rand) \
do { \
hash = SYN_HASH(&satosin(src)->sin_addr, \
satosin(src)->sin_port, \
- satosin(dst)->sin_port); \
+ satosin(dst)->sin_port, (rand)); \
} while (/*CONSTCOND*/ 0)
#else
-#define SYN_HASH6(sa, sp, dp) \
- (((sa)->s6_addr32[0] ^ tcp_syn_hash[0]) * \
- ((sa)->s6_addr32[1] ^ tcp_syn_hash[1]) * \
- ((sa)->s6_addr32[2] ^ tcp_syn_hash[2]) * \
- ((sa)->s6_addr32[3] ^ tcp_syn_hash[3]) * \
- (((((u_int32_t)(dp))<<16) + ((u_int32_t)(sp))) ^ tcp_syn_hash[4]))
+#define SYN_HASH6(sa, sp, dp, rand) \
+ (((sa)->s6_addr32[0] ^ (rand)[0]) * \
+ ((sa)->s6_addr32[1] ^ (rand)[1]) * \
+ ((sa)->s6_addr32[2] ^ (rand)[2]) * \
+ ((sa)->s6_addr32[3] ^ (rand)[3]) * \
+ (((((u_int32_t)(dp))<<16) + ((u_int32_t)(sp))) ^ (rand)[4]))
-#define SYN_HASHALL(hash, src, dst) \
+#define SYN_HASHALL(hash, src, dst, rand) \
do { \
switch ((src)->sa_family) { \
case AF_INET: \
hash = SYN_HASH(&satosin(src)->sin_addr, \
satosin(src)->sin_port, \
- satosin(dst)->sin_port); \
+ satosin(dst)->sin_port, (rand)); \
break; \
case AF_INET6: \
hash = SYN_HASH6(&satosin6(src)->sin6_addr, \
satosin6(src)->sin6_port, \
- satosin6(dst)->sin6_port); \
+ satosin6(dst)->sin6_port, (rand)); \
break; \
default: \
hash = 0; \
@@ -3305,13 +3309,12 @@ void
syn_cache_rm(struct syn_cache *sc)
{
sc->sc_flags |= SCF_DEAD;
- TAILQ_REMOVE(&tcp_syn_cache[sc->sc_bucketidx].sch_bucket,
- sc, sc_bucketq);
+ TAILQ_REMOVE(&sc->sc_buckethead->sch_bucket, sc, sc_bucketq);
sc->sc_tp = NULL;
LIST_REMOVE(sc, sc_tpq);
- tcp_syn_cache[sc->sc_bucketidx].sch_length--;
+ sc->sc_buckethead->sch_length--;
timeout_del(&sc->sc_timer);
- tcp_syn_cache_count--;
+ sc->sc_set->scs_count--;
}
void
@@ -3351,8 +3354,10 @@ syn_cache_init(void)
int i;
/* Initialize the hash buckets. */
- for (i = 0; i < tcp_syn_cache_size; i++)
- TAILQ_INIT(&tcp_syn_cache[i].sch_bucket);
+ for (i = 0; i < tcp_syn_cache_size; i++) {
+ TAILQ_INIT(&tcp_syn_cache[0].scs_buckethead[i].sch_bucket);
+ TAILQ_INIT(&tcp_syn_cache[1].scs_buckethead[i].sch_bucket);
+ }
/* Initialize the syn cache pool. */
pool_init(&syn_cache_pool, sizeof(struct syn_cache), 0, 0, 0,
@@ -3363,26 +3368,31 @@ syn_cache_init(void)
void
syn_cache_insert(struct syn_cache *sc, struct tcpcb *tp)
{
+ struct syn_cache_set *set = &tcp_syn_cache[tcp_syn_cache_active];
struct syn_cache_head *scp;
struct syn_cache *sc2;
int s;
+ s = splsoftnet();
+
/*
* If there are no entries in the hash table, reinitialize
* the hash secrets.
*/
- if (tcp_syn_cache_count == 0)
- arc4random_buf(tcp_syn_hash, sizeof(tcp_syn_hash));
+ if (set->scs_count == 0) {
+ arc4random_buf(set->scs_random, sizeof(set->scs_random));
+ tcpstat.tcps_sc_seedrandom++;
+ }
- SYN_HASHALL(sc->sc_hash, &sc->sc_src.sa, &sc->sc_dst.sa);
- sc->sc_bucketidx = sc->sc_hash % tcp_syn_cache_size;
- scp = &tcp_syn_cache[sc->sc_bucketidx];
+ SYN_HASHALL(sc->sc_hash, &sc->sc_src.sa, &sc->sc_dst.sa,
+ set->scs_random);
+ scp = &set->scs_buckethead[sc->sc_hash % tcp_syn_cache_size];
+ sc->sc_buckethead = scp;
/*
* Make sure that we don't overflow the per-bucket
* limit or the total cache size limit.
*/
- s = splsoftnet();
if (scp->sch_length >= tcp_syn_bucket_limit) {
tcpstat.tcps_sc_bucketoverflow++;
/*
@@ -3400,7 +3410,7 @@ syn_cache_insert(struct syn_cache *sc, s
#endif
syn_cache_rm(sc2);
syn_cache_put(sc2);
- } else if (tcp_syn_cache_count >= tcp_syn_cache_limit) {
+ } else if (set->scs_count >= tcp_syn_cache_limit) {
struct syn_cache_head *scp2, *sce;
tcpstat.tcps_sc_overflowed++;
@@ -3414,10 +3424,10 @@ syn_cache_insert(struct syn_cache *sc, s
*/
scp2 = scp;
if (TAILQ_EMPTY(&scp2->sch_bucket)) {
- sce = &tcp_syn_cache[tcp_syn_cache_size];
+ sce = &set->scs_buckethead[tcp_syn_cache_size];
for (++scp2; scp2 != scp; scp2++) {
if (scp2 >= sce)
- scp2 = &tcp_syn_cache[0];
+ scp2 = &set->scs_buckethead[0];
if (! TAILQ_EMPTY(&scp2->sch_bucket))
break;
}
@@ -3449,9 +3459,15 @@ syn_cache_insert(struct syn_cache *sc, s
/* Put it into the bucket. */
TAILQ_INSERT_TAIL(&scp->sch_bucket, sc, sc_bucketq);
scp->sch_length++;
- tcp_syn_cache_count++;
+ sc->sc_set = set;
+ set->scs_count++;
tcpstat.tcps_sc_added++;
+
+ /* If alternative syn cache is empty, switch. */
+ if (tcp_syn_cache[!tcp_syn_cache_active].scs_count == 0)
+ tcp_syn_cache_active = !tcp_syn_cache_active;
+
splx(s);
}
@@ -3546,25 +3562,29 @@ struct syn_cache *
syn_cache_lookup(struct sockaddr *src, struct sockaddr *dst,
struct syn_cache_head **headp, u_int rtableid)
{
+ struct syn_cache_set *set;
struct syn_cache *sc;
struct syn_cache_head *scp;
u_int32_t hash;
+ int i;
splsoftassert(IPL_SOFTNET);
- if (tcp_syn_cache_count == 0)
- return (NULL);
-
- SYN_HASHALL(hash, src, dst);
- scp = &tcp_syn_cache[hash % tcp_syn_cache_size];
- *headp = scp;
- TAILQ_FOREACH(sc, &scp->sch_bucket, sc_bucketq) {
- if (sc->sc_hash != hash)
+ for (i = 0; i < 2; i++) {
+ set = &tcp_syn_cache[i];
+ if (set->scs_count == 0)
continue;
- if (!bcmp(&sc->sc_src, src, src->sa_len) &&
- !bcmp(&sc->sc_dst, dst, dst->sa_len) &&
- rtable_l2(rtableid) == rtable_l2(sc->sc_rtableid))
- return (sc);
+ SYN_HASHALL(hash, src, dst, set->scs_random);
+ scp = &set->scs_buckethead[hash % tcp_syn_cache_size];
+ *headp = scp;
+ TAILQ_FOREACH(sc, &scp->sch_bucket, sc_bucketq) {
+ if (sc->sc_hash != hash)
+ continue;
+ if (!bcmp(&sc->sc_src, src, src->sa_len) &&
+ !bcmp(&sc->sc_dst, dst, dst->sa_len) &&
+ rtable_l2(rtableid) == rtable_l2(sc->sc_rtableid))
+ return (sc);
+ }
}
return (NULL);
}
Index: netinet/tcp_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_var.h,v
retrieving revision 1.109
diff -u -p -r1.109 tcp_var.h
--- netinet/tcp_var.h 27 Aug 2015 20:56:16 -0000 1.109
+++ netinet/tcp_var.h 19 Mar 2016 20:58:34 -0000
@@ -271,7 +271,8 @@ struct syn_cache {
#define sc_route6 sc_route_u.route6
#endif
long sc_win; /* advertised window */
- int sc_bucketidx; /* our bucket index */
+ struct syn_cache_head *sc_buckethead; /* our bucket index */
+ struct syn_cache_set *sc_set; /* our syn cache set */
u_int32_t sc_hash;
u_int32_t sc_timestamp; /* timestamp from SYN */
u_int32_t sc_modulate; /* our timestamp modulator */
@@ -440,6 +441,7 @@ struct tcpstat {
u_int64_t tcps_sc_dropped; /* # of SYNs dropped (no route/mem) */
u_int64_t tcps_sc_collisions; /* # of hash collisions */
u_int64_t tcps_sc_retransmitted;/* # of retransmissions */
+ u_int64_t tcps_sc_seedrandom; /* # of syn cache seeds with random */
u_int64_t tcps_conndrained; /* # of connections drained */