| 1 | |
| 2 | |
| 3 | * IPv6 Syncookies implementation for the Linux kernel
|
| 4 | *
|
| 5 | * Authors:
|
| 6 | * Glenn Griffin <[email protected]>
|
| 7 | *
|
| 8 | * Based on IPv4 implementation by Andi Kleen
|
| 9 | * linux/net/ipv4/syncookies.c
|
| 10 |
|
| 11 |
|
| 12 | #include <linux/tcp.h>
|
| 13 | #include <linux/random.h>
|
| 14 | #include <linux/siphash.h>
|
| 15 | #include <linux/kernel.h>
|
| 16 | #include <net/secure_seq.h>
|
| 17 | #include <net/ipv6.h>
|
| 18 | #include <net/tcp.h>
|
| 19 | #include <net/tcp_ecn.h>
|
| 20 |
|
| 21 | #define COOKIEBITS 24
|
| 22 | #define COOKIEMASK (((__u32)1 << COOKIEBITS) - 1)
|
| 23 |
|
| 24 | static siphash_aligned_key_t syncookie6_secret[2];
|
| 25 |
|
| 26 | |
| 27 | * [ipv6 tcp] MSS must be computed as the maximum packet size minus 60 [..]
|
| 28 | *
|
| 29 | * Due to IPV6_MIN_MTU=1280 the lowest possible MSS is 1220, which allows
|
| 30 | * using higher values than ipv4 tcp syncookies.
|
| 31 | * The other values are chosen based on ethernet (1500 and 9k MTU), plus
|
| 32 | * one that accounts for common encap (PPPoe) overhead. Table must be sorted.
|
| 33 |
|
| 34 | static __u16 const msstab[] = {
|
| 35 | 1280 - 60,
|
| 36 | 1480 - 60,
|
| 37 | 1500 - 60,
|
| 38 | 9000 - 60,
|
| 39 | };
|
| 40 |
|
| 41 | static u32 cookie_hash(const struct in6_addr *saddr,
|
| 42 | const struct in6_addr *daddr,
|
| 43 | __be16 sport, __be16 dport, u32 count, int c)
|
| 44 | {
|
| 45 | const struct {
|
| 46 | struct in6_addr saddr;
|
| 47 | struct in6_addr daddr;
|
| 48 | u32 count;
|
| 49 | __be16 sport;
|
| 50 | __be16 dport;
|
| 51 | } __aligned(SIPHASH_ALIGNMENT) combined = {
|
| 52 | .saddr = *saddr,
|
| 53 | .daddr = *daddr,
|
| 54 | .count = count,
|
| 55 | .sport = sport,
|
| 56 | .dport = dport
|
| 57 | };
|
| 58 |
|
| 59 | net_get_random_once(syncookie6_secret, sizeof(syncookie6_secret));
|
| 60 | return siphash(&combined, offsetofend(typeof(combined), dport),
|
| 61 | &syncookie6_secret[c]);
|
| 62 | }
|
| 63 |
|
| 64 | static __u32 secure_tcp_syn_cookie(const struct in6_addr *saddr,
|
| 65 | const struct in6_addr *daddr,
|
| 66 | __be16 sport, __be16 dport, __u32 sseq,
|
| 67 | __u32 data)
|
| 68 | {
|
| 69 | u32 count = tcp_cookie_time();
|
| 70 | return (cookie_hash(saddr, daddr, sport, dport, 0, 0) +
|
| 71 | sseq + (count << COOKIEBITS) +
|
| 72 | ((cookie_hash(saddr, daddr, sport, dport, count, 1) + data)
|
| 73 | & COOKIEMASK));
|
| 74 | }
|
| 75 |
|
| 76 | static __u32 check_tcp_syn_cookie(__u32 cookie, const struct in6_addr *saddr,
|
| 77 | const struct in6_addr *daddr, __be16 sport,
|
| 78 | __be16 dport, __u32 sseq)
|
| 79 | {
|
| 80 | __u32 diff, count = tcp_cookie_time();
|
| 81 |
|
| 82 | cookie -= cookie_hash(saddr, daddr, sport, dport, 0, 0) + sseq;
|
| 83 |
|
| 84 | diff = (count - (cookie >> COOKIEBITS)) & ((__u32) -1 >> COOKIEBITS);
|
| 85 | if (diff >= MAX_SYNCOOKIE_AGE)
|
| 86 | return (__u32)-1;
|
| 87 |
|
| 88 | return (cookie -
|
| 89 | cookie_hash(saddr, daddr, sport, dport, count - diff, 1))
|
| 90 | & COOKIEMASK;
|
| 91 | }
|
| 92 |
|
| 93 | u32 __cookie_v6_init_sequence(const struct ipv6hdr *iph,
|
| 94 | const struct tcphdr *th, __u16 *mssp)
|
| 95 | {
|
| 96 | int mssind;
|
| 97 | const __u16 mss = *mssp;
|
| 98 |
|
| 99 | for (mssind = ARRAY_SIZE(msstab) - 1; mssind ; mssind--)
|
| 100 | if (mss >= msstab[mssind])
|
| 101 | break;
|
| 102 |
|
| 103 | *mssp = msstab[mssind];
|
| 104 |
|
| 105 | return secure_tcp_syn_cookie(&iph->saddr, &iph->daddr, th->source,
|
| 106 | th->dest, ntohl(th->seq), mssind);
|
| 107 | }
|
| 108 | EXPORT_SYMBOL_GPL(__cookie_v6_init_sequence);
|
| 109 |
|
| 110 | __u32 cookie_v6_init_sequence(const struct sk_buff *skb, __u16 *mssp)
|
| 111 | {
|
| 112 | const struct ipv6hdr *iph = ipv6_hdr(skb);
|
| 113 | const struct tcphdr *th = tcp_hdr(skb);
|
| 114 |
|
| 115 | return __cookie_v6_init_sequence(iph, th, mssp);
|
| 116 | }
|
| 117 |
|
| 118 | int __cookie_v6_check(const struct ipv6hdr *iph, const struct tcphdr *th)
|
| 119 | {
|
| 120 | __u32 cookie = ntohl(th->ack_seq) - 1;
|
| 121 | __u32 seq = ntohl(th->seq) - 1;
|
| 122 | __u32 mssind;
|
| 123 |
|
| 124 | mssind = check_tcp_syn_cookie(cookie, &iph->saddr, &iph->daddr,
|
| 125 | th->source, th->dest, seq);
|
| 126 |
|
| 127 | return mssind < ARRAY_SIZE(msstab) ? msstab[mssind] : 0;
|
| 128 | }
|
| 129 | EXPORT_SYMBOL_GPL(__cookie_v6_check);
|
| 130 |
|
| 131 | static struct request_sock *cookie_tcp_check(struct net *net, struct sock *sk,
|
| 132 | struct sk_buff *skb)
|
| 133 | {
|
| 134 | struct tcp_options_received tcp_opt;
|
| 135 | u32 tsoff = 0;
|
| 136 | int mss;
|
| 137 |
|
| 138 | if (tcp_synq_no_recent_overflow(sk))
|
| 139 | goto out;
|
| 140 |
|
| 141 | mss = __cookie_v6_check(ipv6_hdr(skb), tcp_hdr(skb));
|
| 142 | if (!mss) {
|
| 143 | __NET_INC_STATS(net, LINUX_MIB_SYNCOOKIESFAILED);
|
| 144 | goto out;
|
| 145 | }
|
| 146 |
|
| 147 | __NET_INC_STATS(net, LINUX_MIB_SYNCOOKIESRECV);
|
| 148 |
|
| 149 |
|
| 150 | memset(&tcp_opt, 0, sizeof(tcp_opt));
|
| 151 | tcp_parse_options(net, skb, &tcp_opt, 0, NULL);
|
| 152 |
|
| 153 | if (tcp_opt.saw_tstamp && tcp_opt.rcv_tsecr) {
|
| 154 | union tcp_seq_and_ts_off st;
|
| 155 |
|
| 156 | st = secure_tcpv6_seq_and_ts_off(net,
|
| 157 | ipv6_hdr(skb)->daddr.s6_addr32,
|
| 158 | ipv6_hdr(skb)->saddr.s6_addr32,
|
| 159 | tcp_hdr(skb)->dest,
|
| 160 | tcp_hdr(skb)->source);
|
| 161 | tsoff = st.ts_off;
|
| 162 | tcp_opt.rcv_tsecr -= tsoff;
|
| 163 | }
|
| 164 |
|
| 165 | if (!cookie_timestamp_decode(net, &tcp_opt))
|
| 166 | goto out;
|
| 167 |
|
| 168 | return cookie_tcp_reqsk_alloc(&tcp6_request_sock_ops, sk, skb,
|
| 169 | &tcp_opt, mss, tsoff);
|
| 170 | out:
|
| 171 | return ERR_PTR(-EINVAL);
|
| 172 | }
|
| 173 |
|
| 174 | struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb)
|
| 175 | {
|
| 176 | const struct tcphdr *th = tcp_hdr(skb);
|
| 177 | struct ipv6_pinfo *np = inet6_sk(sk);
|
| 178 | struct tcp_sock *tp = tcp_sk(sk);
|
| 179 | struct inet_request_sock *ireq;
|
| 180 | struct net *net = sock_net(sk);
|
| 181 | struct request_sock *req;
|
| 182 | struct dst_entry *dst;
|
| 183 | struct sock *ret = sk;
|
| 184 | __u8 rcv_wscale;
|
| 185 | int full_space;
|
| 186 | SKB_DR(reason);
|
| 187 |
|
| 188 | if (!READ_ONCE(net->ipv4.sysctl_tcp_syncookies) ||
|
| 189 | !th->ack || th->rst)
|
| 190 | goto out;
|
| 191 |
|
| 192 | if (cookie_bpf_ok(skb)) {
|
| 193 | req = cookie_bpf_check(sk, skb);
|
| 194 | } else {
|
| 195 | req = cookie_tcp_check(net, sk, skb);
|
| 196 | if (IS_ERR(req))
|
| 197 | goto out;
|
| 198 | }
|
| 199 | if (!req) {
|
| 200 | SKB_DR_SET(reason, NO_SOCKET);
|
| 201 | goto out_drop;
|
| 202 | }
|
| 203 |
|
| 204 | ireq = inet_rsk(req);
|
| 205 |
|
| 206 | ireq->ir_v6_rmt_addr = ipv6_hdr(skb)->saddr;
|
| 207 | ireq->ir_v6_loc_addr = ipv6_hdr(skb)->daddr;
|
| 208 |
|
| 209 | if (security_inet_conn_request(sk, skb, req)) {
|
| 210 | SKB_DR_SET(reason, SECURITY_HOOK);
|
| 211 | goto out_free;
|
| 212 | }
|
| 213 |
|
| 214 | if (ipv6_opt_accepted(sk, skb, &TCP_SKB_CB(skb)->header.h6) ||
|
| 215 | np->rxopt.bits.rxinfo || np->rxopt.bits.rxoinfo ||
|
| 216 | np->rxopt.bits.rxhlim || np->rxopt.bits.rxohlim) {
|
| 217 | refcount_inc(&skb->users);
|
| 218 | ireq->pktopts = skb;
|
| 219 | }
|
| 220 |
|
| 221 |
|
| 222 | if (!sk->sk_bound_dev_if &&
|
| 223 | ipv6_addr_type(&ireq->ir_v6_rmt_addr) & IPV6_ADDR_LINKLOCAL)
|
| 224 | ireq->ir_iif = tcp_v6_iif(skb);
|
| 225 |
|
| 226 | tcp_ao_syncookie(sk, skb, req, AF_INET6);
|
| 227 |
|
| 228 | |
| 229 | * We need to lookup the dst_entry to get the correct window size.
|
| 230 | * This is taken from tcp_v6_syn_recv_sock. Somebody please enlighten
|
| 231 | * me if there is a preferred way.
|
| 232 |
|
| 233 | {
|
| 234 | struct in6_addr *final_p, final;
|
| 235 | struct flowi6 fl6;
|
| 236 | memset(&fl6, 0, sizeof(fl6));
|
| 237 | fl6.flowi6_proto = IPPROTO_TCP;
|
| 238 | fl6.daddr = ireq->ir_v6_rmt_addr;
|
| 239 | final_p = fl6_update_dst(&fl6, rcu_dereference(np->opt), &final);
|
| 240 | fl6.saddr = ireq->ir_v6_loc_addr;
|
| 241 | fl6.flowi6_oif = ireq->ir_iif;
|
| 242 | fl6.flowi6_mark = ireq->ir_mark;
|
| 243 | fl6.fl6_dport = ireq->ir_rmt_port;
|
| 244 | fl6.fl6_sport = inet_sk(sk)->inet_sport;
|
| 245 | fl6.flowi6_uid = sk_uid(sk);
|
| 246 | security_req_classify_flow(req, flowi6_to_flowi_common(&fl6));
|
| 247 |
|
| 248 | ip6_ecmp_set_mp_hash(net, &fl6, tcp_rsk(req)->txhash);
|
| 249 |
|
| 250 | dst = ip6_dst_lookup_flow(net, sk, &fl6, final_p);
|
| 251 | if (IS_ERR(dst)) {
|
| 252 | SKB_DR_SET(reason, IP_OUTNOROUTES);
|
| 253 | goto out_free;
|
| 254 | }
|
| 255 | }
|
| 256 |
|
| 257 | req->rsk_window_clamp = READ_ONCE(tp->window_clamp) ? :dst_metric(dst, RTAX_WINDOW);
|
| 258 |
|
| 259 | full_space = tcp_full_space(sk);
|
| 260 | if (sk->sk_userlocks & SOCK_RCVBUF_LOCK &&
|
| 261 | (req->rsk_window_clamp > full_space || req->rsk_window_clamp == 0))
|
| 262 | req->rsk_window_clamp = full_space;
|
| 263 |
|
| 264 | tcp_select_initial_window(sk, full_space, req->mss,
|
| 265 | &req->rsk_rcv_wnd, &req->rsk_window_clamp,
|
| 266 | ireq->wscale_ok, &rcv_wscale,
|
| 267 | dst_metric(dst, RTAX_INITRWND));
|
| 268 |
|
| 269 | |
| 270 | * by BPF kfunc, then, rcv_wscale is already configured.
|
| 271 |
|
| 272 | if (!req->syncookie)
|
| 273 | ireq->rcv_wscale = rcv_wscale;
|
| 274 | ireq->ecn_ok &= cookie_ecn_ok(net, dst);
|
| 275 | tcp_rsk(req)->accecn_ok = ireq->ecn_ok && cookie_accecn_ok(th);
|
| 276 |
|
| 277 | ret = tcp_get_cookie_sock(sk, skb, req, dst);
|
| 278 | if (!ret) {
|
| 279 | SKB_DR_SET(reason, NO_SOCKET);
|
| 280 | goto out_drop;
|
| 281 | }
|
| 282 | out:
|
| 283 | return ret;
|
| 284 | out_free:
|
| 285 | reqsk_free(req);
|
| 286 | out_drop:
|
| 287 | sk_skb_reason_drop(sk, skb, reason);
|
| 288 | return NULL;
|
| 289 | }
|