diff options
| -rw-r--r-- | drivers/net/geneve.c | 188 |
1 files changed, 183 insertions, 5 deletions
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index a4c23240d9e3..2e1f85b8af4e 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -405,6 +405,165 @@ static int geneve_hlen(const struct genevehdr *gh) return sizeof(*gh) + gh->opt_len * 4; } +/* + * Look for GRO hint in the genenve options; if not found or does not pass basic + * sanitization return 0, otherwise the offset WRT the geneve hdr start. + */ +static unsigned int +geneve_opt_gro_hint_off(const struct genevehdr *gh, __be16 *type, + unsigned int *gh_len) +{ + struct geneve_opt *opt = (void *)(gh + 1); + unsigned int id, opt_len = gh->opt_len; + struct geneve_opt_gro_hint *gro_hint; + + while (opt_len >= (GENEVE_OPT_GRO_HINT_SIZE >> 2)) { + if (opt->opt_class == htons(GENEVE_OPT_NETDEV_CLASS) && + opt->type == GENEVE_OPT_GRO_HINT_TYPE && + opt->length == GENEVE_OPT_GRO_HINT_LEN) + goto found; + + /* check for bad opt len */ + if (opt->length + 1 >= opt_len) + return 0; + + /* next opt */ + opt_len -= opt->length + 1; + opt = ((void *)opt) + ((opt->length + 1) << 2); + } + return 0; + +found: + gro_hint = (struct geneve_opt_gro_hint *)opt->opt_data; + + /* + * Sanitize the hinted hdrs: the nested transport is UDP and must fit + * the overall hinted hdr size. + */ + if (gro_hint->nested_tp_offset + sizeof(struct udphdr) > + gro_hint->nested_hdr_len) + return 0; + + if (gro_hint->nested_nh_offset + + (gro_hint->nested_is_v6 ? sizeof(struct ipv6hdr) : + sizeof(struct iphdr)) > + gro_hint->nested_tp_offset) + return 0; + + /* Allow only supported L2. */ + id = gro_hint->inner_proto_id; + if (id >= ARRAY_SIZE(proto_id_map)) + return 0; + + *type = proto_id_map[id]; + *gh_len += gro_hint->nested_hdr_len; + + return (void *)gro_hint - (void *)gh; +} + +static const struct geneve_opt_gro_hint * +geneve_opt_gro_hint(const struct genevehdr *gh, unsigned int hint_off) +{ + return (const struct geneve_opt_gro_hint *)((void *)gh + hint_off); +} + +static unsigned int +geneve_sk_gro_hint_off(const struct sock *sk, const struct genevehdr *gh, + __be16 *type, unsigned int *gh_len) +{ + const struct geneve_sock *gs = rcu_dereference_sk_user_data(sk); + + if (!gs || !gs->gro_hint) + return 0; + return geneve_opt_gro_hint_off(gh, type, gh_len); +} + +/* Validate the packet headers pointed by data WRT the provided hint */ +static bool +geneve_opt_gro_hint_validate(void *data, + const struct geneve_opt_gro_hint *gro_hint) +{ + void *nested_nh = data + gro_hint->nested_nh_offset; + struct iphdr *iph; + + if (gro_hint->nested_is_v6) { + struct ipv6hdr *ipv6h = nested_nh; + struct ipv6_opt_hdr *opth; + int offset, len; + + if (ipv6h->nexthdr == IPPROTO_UDP) + return true; + + offset = sizeof(*ipv6h) + gro_hint->nested_nh_offset; + while (offset + sizeof(*opth) <= gro_hint->nested_tp_offset) { + opth = data + offset; + + len = ipv6_optlen(opth); + if (len + offset > gro_hint->nested_tp_offset) + return false; + if (opth->nexthdr == IPPROTO_UDP) + return true; + + offset += len; + } + return false; + } + + iph = nested_nh; + if (*(u8 *)iph != 0x45 || ip_is_fragment(iph) || + iph->protocol != IPPROTO_UDP || ip_fast_csum((u8 *)iph, 5)) + return false; + + return true; +} + +/* + * Validate the skb headers following the specified geneve hdr vs the + * provided hint, including nested L4 checksum. + * The caller already ensured that the relevant amount of data is available + * in the linear part. + */ +static bool +geneve_opt_gro_hint_validate_csum(const struct sk_buff *skb, + const struct genevehdr *gh, + const struct geneve_opt_gro_hint *gro_hint) +{ + unsigned int plen, gh_len = geneve_hlen(gh); + void *nested = (void *)gh + gh_len; + struct udphdr *nested_uh; + unsigned int nested_len; + struct ipv6hdr *ipv6h; + struct iphdr *iph; + __wsum csum, psum; + + if (!geneve_opt_gro_hint_validate(nested, gro_hint)) + return false; + + /* Use GRO hints with nested csum only if the outer header has csum. */ + nested_uh = nested + gro_hint->nested_tp_offset; + if (!nested_uh->check || skb->ip_summed == CHECKSUM_PARTIAL) + return true; + + if (!NAPI_GRO_CB(skb)->csum_valid) + return false; + + /* Compute the complete checksum up to the nested transport. */ + plen = gh_len + gro_hint->nested_tp_offset; + csum = csum_sub(NAPI_GRO_CB(skb)->csum, csum_partial(gh, plen, 0)); + nested_len = skb_gro_len(skb) - plen; + + /* Compute the nested pseudo header csum. */ + ipv6h = nested + gro_hint->nested_nh_offset; + iph = (struct iphdr *)ipv6h; + psum = gro_hint->nested_is_v6 ? + ~csum_unfold(csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, + nested_len, IPPROTO_UDP, 0)) : + csum_tcpudp_nofold(iph->saddr, iph->daddr, + nested_len, IPPROTO_UDP, 0); + + return !csum_fold(csum_add(psum, csum)); +} + /* Callback from net/ipv4/udp.c to receive packets */ static int geneve_udp_encap_recv(struct sock *sk, struct sk_buff *skb) { @@ -541,13 +700,13 @@ static struct sk_buff *geneve_gro_receive(struct sock *sk, struct list_head *head, struct sk_buff *skb) { + unsigned int hlen, gh_len, off_gnv, hint_off; + const struct packet_offload *ptype; + struct genevehdr *gh, *gh2; struct sk_buff *pp = NULL; struct sk_buff *p; - struct genevehdr *gh, *gh2; - unsigned int hlen, gh_len, off_gnv; - const struct packet_offload *ptype; - __be16 type; int flush = 1; + __be16 type; off_gnv = skb_gro_offset(skb); hlen = off_gnv + sizeof(*gh); @@ -558,6 +717,7 @@ static struct sk_buff *geneve_gro_receive(struct sock *sk, if (gh->ver != GENEVE_VER || gh->oam) goto out; gh_len = geneve_hlen(gh); + type = gh->proto_type; hlen = off_gnv + gh_len; if (!skb_gro_may_pull(skb, hlen)) { @@ -566,6 +726,25 @@ static struct sk_buff *geneve_gro_receive(struct sock *sk, goto out; } + /* The GRO hint/nested hdr could use a different ethernet type. */ + hint_off = geneve_sk_gro_hint_off(sk, gh, &type, &gh_len); + if (hint_off) { + const struct geneve_opt_gro_hint *gro_hint; + + /* + * If the hint is present, and nested hdr validation fails, do + * not attempt plain GRO: it will ignore inner hdrs and cause + * OoO. + */ + gh = skb_gro_header(skb, off_gnv + gh_len, off_gnv); + if (unlikely(!gh)) + goto out; + + gro_hint = geneve_opt_gro_hint(gh, hint_off); + if (!geneve_opt_gro_hint_validate_csum(skb, gh, gro_hint)) + goto out; + } + list_for_each_entry(p, head, list) { if (!NAPI_GRO_CB(p)->same_flow) continue; @@ -580,7 +759,6 @@ static struct sk_buff *geneve_gro_receive(struct sock *sk, skb_gro_pull(skb, gh_len); skb_gro_postpull_rcsum(skb, gh, gh_len); - type = gh->proto_type; if (likely(type == htons(ETH_P_TEB))) return call_gro_receive(eth_gro_receive, head, skb); |
