On Tue, Jul 19, 2016 at 06:13:42PM +0200, Alexander Bluhm wrote:
> Hi,
>
> claudio@ suggested to have a tunable size for the syn cache hash
> array. As we are swapping between two syn caches for random reseeding
> anyway, this feature can be added easily. When the cache is empty,
> we can change the hash size.
>
> This allows an admin under SYN flood attack to tune his machine.
> sysctl net.inet.tcp.synhashsize=10000
Makes sense to me and I like this.
> ok?
ok jung@
Please, also document it, at least in sysctl(8).
> bluhm
>
> Index: netinet/tcp_input.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_input.c,v
> retrieving revision 1.324
> diff -u -p -r1.324 tcp_input.c
> --- netinet/tcp_input.c 1 Jul 2016 18:37:15 -0000 1.324
> +++ netinet/tcp_input.c 19 Jul 2016 15:02:35 -0000
> @@ -3266,7 +3266,7 @@ tcp_mss_adv(struct mbuf *m, int af)
> */
>
> /* syn hash parameters */
> -int tcp_syn_cache_size = TCP_SYN_HASH_SIZE;
> +int tcp_syn_hash_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_use_limit = 100000;
> @@ -3360,7 +3360,13 @@ syn_cache_init(void)
> int i;
>
> /* Initialize the hash buckets. */
> - for (i = 0; i < tcp_syn_cache_size; i++) {
> + tcp_syn_cache[0].scs_buckethead = mallocarray(tcp_syn_hash_size,
> + sizeof(struct syn_cache_head), M_SYNCACHE, M_WAITOK|M_ZERO);
> + tcp_syn_cache[1].scs_buckethead = mallocarray(tcp_syn_hash_size,
> + sizeof(struct syn_cache_head), M_SYNCACHE, M_WAITOK|M_ZERO);
> + tcp_syn_cache[0].scs_size = tcp_syn_hash_size;
> + tcp_syn_cache[1].scs_size = tcp_syn_hash_size;
> + for (i = 0; i < tcp_syn_hash_size; i++) {
> TAILQ_INIT(&tcp_syn_cache[0].scs_buckethead[i].sch_bucket);
> TAILQ_INIT(&tcp_syn_cache[1].scs_buckethead[i].sch_bucket);
> }
> @@ -3377,7 +3383,7 @@ syn_cache_insert(struct syn_cache *sc, s
> struct syn_cache_set *set = &tcp_syn_cache[tcp_syn_cache_active];
> struct syn_cache_head *scp;
> struct syn_cache *sc2;
> - int s;
> + int i, s;
>
> s = splsoftnet();
>
> @@ -3385,16 +3391,33 @@ syn_cache_insert(struct syn_cache *sc, s
> * If there are no entries in the hash table, reinitialize
> * the hash secrets. To avoid useless cache swaps and
> * reinitialization, use it until the limit is reached.
> + * An emtpy cache is also the oportunity to resize the hash.
> */
> if (set->scs_count == 0 && set->scs_use <= 0) {
> - arc4random_buf(set->scs_random, sizeof(set->scs_random));
> set->scs_use = tcp_syn_use_limit;
> + if (set->scs_size != tcp_syn_hash_size) {
> + scp = mallocarray(tcp_syn_hash_size, sizeof(struct
> + syn_cache_head), M_SYNCACHE, M_NOWAIT|M_ZERO);
> + if (scp == NULL) {
> + /* Try again next time. */
> + set->scs_use = 0;
> + } else {
> + free(set->scs_buckethead, M_SYNCACHE,
> + set->scs_size *
> + sizeof(struct syn_cache_head));
> + set->scs_buckethead = scp;
> + set->scs_size = tcp_syn_hash_size;
> + for (i = 0; i < tcp_syn_hash_size; i++)
> + TAILQ_INIT(&scp[i].sch_bucket);
> + }
> + }
> + 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,
> set->scs_random);
> - scp = &set->scs_buckethead[sc->sc_hash % tcp_syn_cache_size];
> + scp = &set->scs_buckethead[sc->sc_hash % set->scs_size];
> sc->sc_buckethead = scp;
>
> /*
> @@ -3437,7 +3460,7 @@ syn_cache_insert(struct syn_cache *sc, s
> */
> scp2 = scp;
> if (TAILQ_EMPTY(&scp2->sch_bucket)) {
> - sce = &set->scs_buckethead[tcp_syn_cache_size];
> + sce = &set->scs_buckethead[set->scs_size];
> for (++scp2; scp2 != scp; scp2++) {
> if (scp2 >= sce)
> scp2 = &set->scs_buckethead[0];
> @@ -3595,7 +3618,7 @@ syn_cache_lookup(struct sockaddr *src, s
> if (sets[i]->scs_count == 0)
> continue;
> SYN_HASHALL(hash, src, dst, sets[i]->scs_random);
> - scp = &sets[i]->scs_buckethead[hash % tcp_syn_cache_size];
> + scp = &sets[i]->scs_buckethead[hash % sets[i]->scs_size];
> *headp = scp;
> TAILQ_FOREACH(sc, &scp->sch_bucket, sc_bucketq) {
> if (sc->sc_hash != hash)
> Index: netinet/tcp_usrreq.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_usrreq.c,v
> retrieving revision 1.132
> diff -u -p -r1.132 tcp_usrreq.c
> --- netinet/tcp_usrreq.c 11 Jul 2016 10:35:43 -0000 1.132
> +++ netinet/tcp_usrreq.c 19 Jul 2016 15:44:59 -0000
> @@ -956,6 +956,27 @@ tcp_sysctl(name, namelen, oldp, oldlenp,
> }
> return (0);
>
> + case TCPCTL_SYN_HASH_SIZE:
> + nval = tcp_syn_hash_size;
> + error = sysctl_int(oldp, oldlenp, newp, newlen, &nval);
> + if (error)
> + return (error);
> + if (nval != tcp_syn_hash_size) {
> + if (nval < 1 || nval > 100000)
> + return (EINVAL);
> + /*
> + * If global hash size has been changed, switch sets as
> + * soon as possible. Then the actual hash array will
> + * be reallocated.
> + */
> + if (tcp_syn_cache[0].scs_size != nval)
> + tcp_syn_cache[0].scs_use = 0;
> + if (tcp_syn_cache[1].scs_size != nval)
> + tcp_syn_cache[1].scs_use = 0;
> + tcp_syn_hash_size = nval;
> + }
> + return (0);
> +
> default:
> if (name[0] < TCPCTL_MAXID)
> return (sysctl_int_arr(tcpctl_vars, name, namelen,
> Index: netinet/tcp_var.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_var.h,v
> retrieving revision 1.113
> diff -u -p -r1.113 tcp_var.h
> --- netinet/tcp_var.h 18 Jun 2016 10:36:13 -0000 1.113
> +++ netinet/tcp_var.h 19 Jul 2016 15:44:59 -0000
> @@ -316,10 +316,11 @@ struct syn_cache_head {
> };
>
> struct syn_cache_set {
> - struct syn_cache_head
> scs_buckethead[TCP_SYN_HASH_SIZE];
> - int scs_count;
> - int scs_use;
> - u_int32_t scs_random[5];
> + struct syn_cache_head *scs_buckethead;
> + int scs_size;
> + int scs_count;
> + int scs_use;
> + u_int32_t scs_random[5];
> };
>
> #endif /* _KERNEL */
> @@ -491,7 +492,8 @@ struct tcpstat {
> #define TCPCTL_ALWAYS_KEEPALIVE 22 /* assume SO_KEEPALIVE is always set
> */
> #define TCPCTL_SYN_USE_LIMIT 23 /* number of uses before reseeding
> hash */
> #define TCPCTL_ROOTONLY 24 /* return root only port bitmap */
> -#define TCPCTL_MAXID 25
> +#define TCPCTL_SYN_HASH_SIZE 25 /* number of buckets in the hash */
> +#define TCPCTL_MAXID 26
>
> #define TCPCTL_NAMES { \
> { 0, 0 }, \
> @@ -519,6 +521,7 @@ struct tcpstat {
> { "always_keepalive", CTLTYPE_INT }, \
> { "synuselimit", CTLTYPE_INT }, \
> { "rootonly", CTLTYPE_STRUCT }, \
> + { "synhashsize", CTLTYPE_INT }, \
> }
>
> #define TCPCTL_VARS { \
> @@ -546,6 +549,7 @@ struct tcpstat {
> NULL, \
> NULL, \
> NULL, \
> + NULL, \
> NULL \
> }
>
> @@ -575,6 +579,7 @@ extern int tcp_do_rfc3390; /* RFC3390 In
> extern struct pool tcpqe_pool;
> extern int tcp_reass_limit; /* max entries for tcp reass queues */
>
> +extern int tcp_syn_hash_size; /* adjustable size of the hash array */
> extern int tcp_syn_cache_limit; /* max entries for compressed state
> engine */
> extern int tcp_syn_bucket_limit;/* max entries per hash bucket */
> extern int tcp_syn_use_limit; /* number of uses before reseeding
> hash */
> Index: sys/malloc.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/sys/malloc.h,v
> retrieving revision 1.112
> diff -u -p -r1.112 malloc.h
> --- sys/malloc.h 24 Aug 2015 15:33:49 -0000 1.112
> +++ sys/malloc.h 19 Jul 2016 15:02:35 -0000
> @@ -169,7 +169,7 @@
> #define M_KEVENT 137 /* kqueue related */
>
> /* 138 free */
> - /* 139 free */
> +#define M_SYNCACHE 139 /* syn cache hash array */
>
> #define M_UDFMOUNT 140 /* UDF mount */
> #define M_UDFFENTRY 141 /* UDF file entry */
> @@ -307,7 +307,7 @@
> "NTFS vrun", /* 136 M_NTFSRUN */ \
> "kqueue", /* 137 M_KEVENT */ \
> NULL, /* 138 free */ \
> - "NULL", \
> + "SYN cache", /* 139 M_SYNCACHE */ \
> "UDF mount", /* 140 M_UDFMOUNT */ \
> "UDF file entry", /* 141 M_UDFFENTRY */ \
> "UDF file id", /* 142 M_UDFFID */ \
>