From patchwork Wed Apr 3 02:36:28 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1075242 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="slTAqI55"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44YqwY5Lz9z9sSP for ; Wed, 3 Apr 2019 13:37:01 +1100 (AEDT) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727110AbfDCChA (ORCPT ); Tue, 2 Apr 2019 22:37:00 -0400 Received: from mail-pl1-f194.google.com ([209.85.214.194]:34946 "EHLO mail-pl1-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726890AbfDCCg6 (ORCPT ); Tue, 2 Apr 2019 22:36:58 -0400 Received: by mail-pl1-f194.google.com with SMTP id w24so844543plp.2 for ; Tue, 02 Apr 2019 19:36:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=y1X5bxrE88TW+2miYOLrGnGTqjstme05asWKbjdLXuY=; b=slTAqI55K0ct/ZbfMvkfiAZm2p5EgvxrYBaPFq/y5wW6/LLMgc1aLXO+CTIoepysQa J+8QWnKhcVvOsKg9+MiAxvd3911Vi7kA06il/reQXBGJcG9fcc8/MKXxMzZ0N7is7zYk clmWcI1+fwYE6qCS2cAi0eEANxygyFhE+lll61b3RUmEISA/9xhCC1/i034sAkTkQBs5 px0pbf/rXYUN6Iw6oOFdGOFXu/buLk7JqzlZQ0VsYYarvtCFHtlMQEGdgWP0gmYCfW7T 9QwG5EDDjlw/3XskdHhlU42oKM0SQcZNY7LqzPy0wXfjaSMkSgxB89fzhU+YaC5IjUB+ oRJg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=y1X5bxrE88TW+2miYOLrGnGTqjstme05asWKbjdLXuY=; b=cxP1FktddjFOoJWVXWaXYUZ/ezlEK4Ue4WuH/LygMNh406QXbfXn2O74BZpOxjzuxa FpGmGKkIKCWNDp9ZjX6VYt6b9PhqcrIG/0gZadZ9NaGmbg7OTCEM031uDYzDcLJ7vOgc Y3slTd0BpAxq7/YF4JcPrm6zNZq3GKq5XIaN/TkvqeseHznHHtoFu7J46mf8UAZsKkfm t6ol8t2+uBckmxgZ4NwG2HunTmESxA9sWh5yNm5SZoz7jssxdvgBwkjhieDrxsZXMfiy Xhrh0ICWsaydtOfdiPQG/7JovAGBgaooGFuBnP/kFXpuTCrurGJAF3KyO8A9pYpb7Lo6 93QQ== X-Gm-Message-State: APjAAAUD6Bcqnfh/X0UO50CUIiR4vPYZAvrRq4/NhlPDgRVsXOhSwsZt MVjZasM4pVCCuhEFSEbIzxKVWQ== X-Google-Smtp-Source: APXvYqzxSDoOayE18JTxy2n419BUwTZfJwWjDpRqzZ3Q5D194/Q9o9sAmnNNzcETjouqmiuV2l7bSQ== X-Received: by 2002:a17:902:2a29:: with SMTP id i38mr75556424plb.22.1554259016005; Tue, 02 Apr 2019 19:36:56 -0700 (PDT) Received: from localhost.localdomain (c-174-62-124-86.hsd1.ca.comcast.net. [174.62.124.86]) by smtp.gmail.com with ESMTPSA id b72sm40354896pga.86.2019.04.02.19.36.54 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 02 Apr 2019 19:36:55 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH net-next 3/3] exthdrs: Rename EH files that are IPv6 specific Date: Tue, 2 Apr 2019 19:36:28 -0700 Message-Id: <1554258988-13008-4-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1554258988-13008-1-git-send-email-tom@quantonium.net> References: <1554258988-13008-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Rename extension header files that are specific to IPv6. net/ipv6/exthdrs.c -> net/ipv6/exthdrs6.c net/ipv6/exthdrs_offload.c -> net/ipv6/exthdrs6_offload.c net/ipv6/exthdrs_options.c -> net/ipv6/exthdrs6_options.c --- net/ipv6/Makefile | 6 +- net/ipv6/exthdrs.c | 830 -------------------------------------------- net/ipv6/exthdrs6.c | 830 ++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/exthdrs6_offload.c | 41 +++ net/ipv6/exthdrs6_options.c | 201 +++++++++++ net/ipv6/exthdrs_offload.c | 41 --- net/ipv6/exthdrs_options.c | 201 ----------- 7 files changed, 1075 insertions(+), 1075 deletions(-) delete mode 100644 net/ipv6/exthdrs.c create mode 100644 net/ipv6/exthdrs6.c create mode 100644 net/ipv6/exthdrs6_offload.c create mode 100644 net/ipv6/exthdrs6_options.c delete mode 100644 net/ipv6/exthdrs_offload.c delete mode 100644 net/ipv6/exthdrs_options.c diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 72bd775..6c0e585 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -9,10 +9,10 @@ ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o \ addrlabel.o \ route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o udplite.o \ raw.o icmp.o mcast.o reassembly.o tcp_ipv6.o ping.o \ - exthdrs.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \ - udp_offload.o seg6.o fib6_notifier.o exthdrs_options.o + exthdrs6.o datagram.o ip6_flowlabel.o inet6_connection_sock.o \ + udp_offload.o seg6.o fib6_notifier.o exthdrs6_options.o -ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs_offload.o +ipv6-offload := ip6_offload.o tcpv6_offload.o exthdrs6_offload.o ipv6-$(CONFIG_SYSCTL) = sysctl_net_ipv6.o ipv6-$(CONFIG_IPV6_MROUTE) += ip6mr.o diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c deleted file mode 100644 index 6dbacf1..0000000 --- a/net/ipv6/exthdrs.c +++ /dev/null @@ -1,830 +0,0 @@ -/* - * Extension Header handling for IPv6 - * Linux INET6 implementation - * - * Authors: - * Pedro Roque - * Andi Kleen - * Alexey Kuznetsov - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - */ - -/* Changes: - * yoshfuji : ensure not to overrun while parsing - * tlv options. - * Mitsuru KANDA @USAGI and: Remove ipv6_parse_exthdrs(). - * YOSHIFUJI Hideaki @USAGI Register inbound extension header - * handlers as inet6_protocol{}. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#if IS_ENABLED(CONFIG_IPV6_MIP6) -#include -#endif -#include -#include -#ifdef CONFIG_IPV6_SEG6_HMAC -#include -#endif - -#include - -/********************* - Generic functions - *********************/ - -/* An unknown option is detected, decide what to do */ - -static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, - bool disallow_unknowns) -{ - if (disallow_unknowns) { - /* If unknown TLVs are disallowed by configuration - * then always silently drop packet. Note this also - * means no ICMP parameter problem is sent which - * could be a good property to mitigate a reflection DOS - * attack. - */ - - goto drop; - } - - switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) { - case 0: /* ignore */ - return true; - - case 1: /* drop packet */ - break; - - case 3: /* Send ICMP if not a multicast address and drop packet */ - /* Actually, it is redundant check. icmp_send - will recheck in any case. - */ - if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) - break; - /* fall through */ - case 2: /* send ICMP PARM PROB regardless and drop packet */ - icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); - return false; - } - -drop: - kfree_skb(skb); - return false; -} - -/* Parse tlv encoded option header (hop-by-hop or destination) */ - -static bool ip6_parse_tlv(const struct tlvtype_proc *procs, - struct sk_buff *skb, - int max_count) -{ - int len = (skb_transport_header(skb)[1] + 1) << 3; - const unsigned char *nh = skb_network_header(skb); - int off = skb_network_header_len(skb); - const struct tlvtype_proc *curr; - bool disallow_unknowns = false; - int tlv_count = 0; - int padlen = 0; - - if (unlikely(max_count < 0)) { - disallow_unknowns = true; - max_count = -max_count; - } - - if (skb_transport_offset(skb) + len > skb_headlen(skb)) - goto bad; - - off += 2; - len -= 2; - - while (len > 0) { - int optlen = nh[off + 1] + 2; - int i; - - switch (nh[off]) { - case IPV6_TLV_PAD1: - optlen = 1; - padlen++; - if (padlen > 7) - goto bad; - break; - - case IPV6_TLV_PADN: - /* RFC 2460 states that the purpose of PadN is - * to align the containing header to multiples - * of 8. 7 is therefore the highest valid value. - * See also RFC 4942, Section 2.1.9.5. - */ - padlen += optlen; - if (padlen > 7) - goto bad; - /* RFC 4942 recommends receiving hosts to - * actively check PadN payload to contain - * only zeroes. - */ - for (i = 2; i < optlen; i++) { - if (nh[off + i] != 0) - goto bad; - } - break; - - default: /* Other TLV code so scan list */ - if (optlen > len) - goto bad; - - tlv_count++; - if (tlv_count > max_count) - goto bad; - - for (curr = procs; curr->type >= 0; curr++) { - if (curr->type == nh[off]) { - /* type specific length/alignment - checks will be performed in the - func(). */ - if (curr->func(skb, off) == false) - return false; - break; - } - } - if (curr->type < 0 && - !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) - return false; - - padlen = 0; - break; - } - off += optlen; - len -= optlen; - } - - if (len == 0) - return true; -bad: - kfree_skb(skb); - return false; -} - -static int ipv6_destopt_rcv(struct sk_buff *skb) -{ - struct inet6_dev *idev = __in6_dev_get(skb->dev); - struct inet6_skb_parm *opt = IP6CB(skb); -#if IS_ENABLED(CONFIG_IPV6_MIP6) - __u16 dstbuf; -#endif - struct dst_entry *dst = skb_dst(skb); - struct net *net = dev_net(skb->dev); - int extlen; - - if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || - !pskb_may_pull(skb, (skb_transport_offset(skb) + - ((skb_transport_header(skb)[1] + 1) << 3)))) { - __IP6_INC_STATS(dev_net(dst->dev), idev, - IPSTATS_MIB_INHDRERRORS); -fail_and_free: - kfree_skb(skb); - return -1; - } - - extlen = (skb_transport_header(skb)[1] + 1) << 3; - if (extlen > net->ipv6.sysctl.max_dst_opts_len) - goto fail_and_free; - - opt->lastopt = opt->dst1 = skb_network_header_len(skb); -#if IS_ENABLED(CONFIG_IPV6_MIP6) - dstbuf = opt->dst1; -#endif - - if (ip6_parse_tlv(tlvprocdestopt_lst, skb, - init_net.ipv6.sysctl.max_dst_opts_cnt)) { - skb->transport_header += extlen; - opt = IP6CB(skb); -#if IS_ENABLED(CONFIG_IPV6_MIP6) - opt->nhoff = dstbuf; -#else - opt->nhoff = opt->dst1; -#endif - return 1; - } - - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - return -1; -} - -static void seg6_update_csum(struct sk_buff *skb) -{ - struct ipv6_sr_hdr *hdr; - struct in6_addr *addr; - __be32 from, to; - - /* srh is at transport offset and seg_left is already decremented - * but daddr is not yet updated with next segment - */ - - hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); - addr = hdr->segments + hdr->segments_left; - - hdr->segments_left++; - from = *(__be32 *)hdr; - - hdr->segments_left--; - to = *(__be32 *)hdr; - - /* update skb csum with diff resulting from seg_left decrement */ - - update_csum_diff4(skb, from, to); - - /* compute csum diff between current and next segment and update */ - - update_csum_diff16(skb, (__be32 *)(&ipv6_hdr(skb)->daddr), - (__be32 *)addr); -} - -static int ipv6_srh_rcv(struct sk_buff *skb) -{ - struct inet6_skb_parm *opt = IP6CB(skb); - struct net *net = dev_net(skb->dev); - struct ipv6_sr_hdr *hdr; - struct inet6_dev *idev; - struct in6_addr *addr; - int accept_seg6; - - hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); - - idev = __in6_dev_get(skb->dev); - - accept_seg6 = net->ipv6.devconf_all->seg6_enabled; - if (accept_seg6 > idev->cnf.seg6_enabled) - accept_seg6 = idev->cnf.seg6_enabled; - - if (!accept_seg6) { - kfree_skb(skb); - return -1; - } - -#ifdef CONFIG_IPV6_SEG6_HMAC - if (!seg6_hmac_validate_skb(skb)) { - kfree_skb(skb); - return -1; - } -#endif - -looped_back: - if (hdr->segments_left == 0) { - if (hdr->nexthdr == NEXTHDR_IPV6) { - int offset = (hdr->hdrlen + 1) << 3; - - skb_postpull_rcsum(skb, skb_network_header(skb), - skb_network_header_len(skb)); - - if (!pskb_pull(skb, offset)) { - kfree_skb(skb); - return -1; - } - skb_postpull_rcsum(skb, skb_transport_header(skb), - offset); - - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - skb->encapsulation = 0; - - __skb_tunnel_rx(skb, skb->dev, net); - - netif_rx(skb); - return -1; - } - - opt->srcrt = skb_network_header_len(skb); - opt->lastopt = opt->srcrt; - skb->transport_header += (hdr->hdrlen + 1) << 3; - opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb); - - return 1; - } - - if (hdr->segments_left >= (hdr->hdrlen >> 1)) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, - ((&hdr->segments_left) - - skb_network_header(skb))); - return -1; - } - - if (skb_cloned(skb)) { - if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) { - __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), - IPSTATS_MIB_OUTDISCARDS); - kfree_skb(skb); - return -1; - } - } - - hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); - - hdr->segments_left--; - addr = hdr->segments + hdr->segments_left; - - skb_push(skb, sizeof(struct ipv6hdr)); - - if (skb->ip_summed == CHECKSUM_COMPLETE) - seg6_update_csum(skb); - - ipv6_hdr(skb)->daddr = *addr; - - skb_dst_drop(skb); - - ip6_route_input(skb); - - if (skb_dst(skb)->error) { - dst_input(skb); - return -1; - } - - if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) { - if (ipv6_hdr(skb)->hop_limit <= 1) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_send(skb, ICMPV6_TIME_EXCEED, - ICMPV6_EXC_HOPLIMIT, 0); - kfree_skb(skb); - return -1; - } - ipv6_hdr(skb)->hop_limit--; - - skb_pull(skb, sizeof(struct ipv6hdr)); - goto looped_back; - } - - dst_input(skb); - - return -1; -} - -/******************************** - Routing header. - ********************************/ - -/* called with rcu_read_lock() */ -static int ipv6_rthdr_rcv(struct sk_buff *skb) -{ - struct inet6_dev *idev = __in6_dev_get(skb->dev); - struct inet6_skb_parm *opt = IP6CB(skb); - struct in6_addr *addr = NULL; - struct in6_addr daddr; - int n, i; - struct ipv6_rt_hdr *hdr; - struct rt0_hdr *rthdr; - struct net *net = dev_net(skb->dev); - int accept_source_route = net->ipv6.devconf_all->accept_source_route; - - idev = __in6_dev_get(skb->dev); - if (idev && accept_source_route > idev->cnf.accept_source_route) - accept_source_route = idev->cnf.accept_source_route; - - if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || - !pskb_may_pull(skb, (skb_transport_offset(skb) + - ((skb_transport_header(skb)[1] + 1) << 3)))) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - kfree_skb(skb); - return -1; - } - - hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb); - - if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) || - skb->pkt_type != PACKET_HOST) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); - kfree_skb(skb); - return -1; - } - - /* segment routing */ - if (hdr->type == IPV6_SRCRT_TYPE_4) - return ipv6_srh_rcv(skb); - -looped_back: - if (hdr->segments_left == 0) { - switch (hdr->type) { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - case IPV6_SRCRT_TYPE_2: - /* Silently discard type 2 header unless it was - * processed by own - */ - if (!addr) { - __IP6_INC_STATS(net, idev, - IPSTATS_MIB_INADDRERRORS); - kfree_skb(skb); - return -1; - } - break; -#endif - default: - break; - } - - opt->lastopt = opt->srcrt = skb_network_header_len(skb); - skb->transport_header += (hdr->hdrlen + 1) << 3; - opt->dst0 = opt->dst1; - opt->dst1 = 0; - opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb); - return 1; - } - - switch (hdr->type) { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - case IPV6_SRCRT_TYPE_2: - if (accept_source_route < 0) - goto unknown_rh; - /* Silently discard invalid RTH type 2 */ - if (hdr->hdrlen != 2 || hdr->segments_left != 1) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - kfree_skb(skb); - return -1; - } - break; -#endif - default: - goto unknown_rh; - } - - /* - * This is the routing header forwarding algorithm from - * RFC 2460, page 16. - */ - - n = hdr->hdrlen >> 1; - - if (hdr->segments_left > n) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, - ((&hdr->segments_left) - - skb_network_header(skb))); - return -1; - } - - /* We are about to mangle packet header. Be careful! - Do not damage packets queued somewhere. - */ - if (skb_cloned(skb)) { - /* the copy is a forwarded packet */ - if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) { - __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), - IPSTATS_MIB_OUTDISCARDS); - kfree_skb(skb); - return -1; - } - hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb); - } - - if (skb->ip_summed == CHECKSUM_COMPLETE) - skb->ip_summed = CHECKSUM_NONE; - - i = n - --hdr->segments_left; - - rthdr = (struct rt0_hdr *) hdr; - addr = rthdr->addr; - addr += i - 1; - - switch (hdr->type) { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - case IPV6_SRCRT_TYPE_2: - if (xfrm6_input_addr(skb, (xfrm_address_t *)addr, - (xfrm_address_t *)&ipv6_hdr(skb)->saddr, - IPPROTO_ROUTING) < 0) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); - kfree_skb(skb); - return -1; - } - if (!ipv6_chk_home_addr(dev_net(skb_dst(skb)->dev), addr)) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); - kfree_skb(skb); - return -1; - } - break; -#endif - default: - break; - } - - if (ipv6_addr_is_multicast(addr)) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); - kfree_skb(skb); - return -1; - } - - daddr = *addr; - *addr = ipv6_hdr(skb)->daddr; - ipv6_hdr(skb)->daddr = daddr; - - skb_dst_drop(skb); - ip6_route_input(skb); - if (skb_dst(skb)->error) { - skb_push(skb, skb->data - skb_network_header(skb)); - dst_input(skb); - return -1; - } - - if (skb_dst(skb)->dev->flags&IFF_LOOPBACK) { - if (ipv6_hdr(skb)->hop_limit <= 1) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, - 0); - kfree_skb(skb); - return -1; - } - ipv6_hdr(skb)->hop_limit--; - goto looped_back; - } - - skb_push(skb, skb->data - skb_network_header(skb)); - dst_input(skb); - return -1; - -unknown_rh: - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, - (&hdr->type) - skb_network_header(skb)); - return -1; -} - -static const struct inet6_protocol rthdr_protocol = { - .handler = ipv6_rthdr_rcv, - .flags = INET6_PROTO_NOPOLICY, -}; - -static const struct inet6_protocol destopt_protocol = { - .handler = ipv6_destopt_rcv, - .flags = INET6_PROTO_NOPOLICY, -}; - -static const struct inet6_protocol nodata_protocol = { - .handler = dst_discard, - .flags = INET6_PROTO_NOPOLICY, -}; - -int __init ipv6_exthdrs_init(void) -{ - int ret; - - ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING); - if (ret) - goto out; - - ret = inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS); - if (ret) - goto out_rthdr; - - ret = inet6_add_protocol(&nodata_protocol, IPPROTO_NONE); - if (ret) - goto out_destopt; - -out: - return ret; -out_destopt: - inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); -out_rthdr: - inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); - goto out; -}; - -void ipv6_exthdrs_exit(void) -{ - inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); - inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); - inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); -} - -int ipv6_parse_hopopts(struct sk_buff *skb) -{ - struct inet6_skb_parm *opt = IP6CB(skb); - struct net *net = dev_net(skb->dev); - int extlen; - - /* - * skb_network_header(skb) is equal to skb->data, and - * skb_network_header_len(skb) is always equal to - * sizeof(struct ipv6hdr) by definition of - * hop-by-hop options. - */ - if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || - !pskb_may_pull(skb, (sizeof(struct ipv6hdr) + - ((skb_transport_header(skb)[1] + 1) << 3)))) { -fail_and_free: - kfree_skb(skb); - return -1; - } - - extlen = (skb_transport_header(skb)[1] + 1) << 3; - if (extlen > net->ipv6.sysctl.max_hbh_opts_len) - goto fail_and_free; - - opt->flags |= IP6SKB_HOPBYHOP; - if (ip6_parse_tlv(tlvprochopopt_lst, skb, - init_net.ipv6.sysctl.max_hbh_opts_cnt)) { - skb->transport_header += extlen; - opt = IP6CB(skb); - opt->nhoff = sizeof(struct ipv6hdr); - return 1; - } - return -1; -} - -/* - * Creating outbound headers. - * - * "build" functions work when skb is filled from head to tail (datagram) - * "push" functions work when headers are added from tail to head (tcp) - * - * In both cases we assume, that caller reserved enough room - * for headers. - */ - -static void ipv6_push_rthdr0(struct sk_buff *skb, u8 *proto, - struct ipv6_rt_hdr *opt, - struct in6_addr **addr_p, struct in6_addr *saddr) -{ - struct rt0_hdr *phdr, *ihdr; - int hops; - - ihdr = (struct rt0_hdr *) opt; - - phdr = skb_push(skb, (ihdr->rt_hdr.hdrlen + 1) << 3); - memcpy(phdr, ihdr, sizeof(struct rt0_hdr)); - - hops = ihdr->rt_hdr.hdrlen >> 1; - - if (hops > 1) - memcpy(phdr->addr, ihdr->addr + 1, - (hops - 1) * sizeof(struct in6_addr)); - - phdr->addr[hops - 1] = **addr_p; - *addr_p = ihdr->addr; - - phdr->rt_hdr.nexthdr = *proto; - *proto = NEXTHDR_ROUTING; -} - -static void ipv6_push_rthdr4(struct sk_buff *skb, u8 *proto, - struct ipv6_rt_hdr *opt, - struct in6_addr **addr_p, struct in6_addr *saddr) -{ - struct ipv6_sr_hdr *sr_phdr, *sr_ihdr; - int plen, hops; - - sr_ihdr = (struct ipv6_sr_hdr *)opt; - plen = (sr_ihdr->hdrlen + 1) << 3; - - sr_phdr = skb_push(skb, plen); - memcpy(sr_phdr, sr_ihdr, sizeof(struct ipv6_sr_hdr)); - - hops = sr_ihdr->first_segment + 1; - memcpy(sr_phdr->segments + 1, sr_ihdr->segments + 1, - (hops - 1) * sizeof(struct in6_addr)); - - sr_phdr->segments[0] = **addr_p; - *addr_p = &sr_ihdr->segments[sr_ihdr->segments_left]; - - if (sr_ihdr->hdrlen > hops * 2) { - int tlvs_offset, tlvs_length; - - tlvs_offset = (1 + hops * 2) << 3; - tlvs_length = (sr_ihdr->hdrlen - hops * 2) << 3; - memcpy((char *)sr_phdr + tlvs_offset, - (char *)sr_ihdr + tlvs_offset, tlvs_length); - } - -#ifdef CONFIG_IPV6_SEG6_HMAC - if (sr_has_hmac(sr_phdr)) { - struct net *net = NULL; - - if (skb->dev) - net = dev_net(skb->dev); - else if (skb->sk) - net = sock_net(skb->sk); - - WARN_ON(!net); - - if (net) - seg6_push_hmac(net, saddr, sr_phdr); - } -#endif - - sr_phdr->nexthdr = *proto; - *proto = NEXTHDR_ROUTING; -} - -static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto, - struct ipv6_rt_hdr *opt, - struct in6_addr **addr_p, struct in6_addr *saddr) -{ - switch (opt->type) { - case IPV6_SRCRT_TYPE_0: - case IPV6_SRCRT_STRICT: - case IPV6_SRCRT_TYPE_2: - ipv6_push_rthdr0(skb, proto, opt, addr_p, saddr); - break; - case IPV6_SRCRT_TYPE_4: - ipv6_push_rthdr4(skb, proto, opt, addr_p, saddr); - break; - default: - break; - } -} - -static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv6_opt_hdr *opt) -{ - struct ipv6_opt_hdr *h = skb_push(skb, ipv6_optlen(opt)); - - memcpy(h, opt, ipv6_optlen(opt)); - h->nexthdr = *proto; - *proto = type; -} - -void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, - u8 *proto, - struct in6_addr **daddr, struct in6_addr *saddr) -{ - if (opt->srcrt) { - ipv6_push_rthdr(skb, proto, opt->srcrt, daddr, saddr); - /* - * IPV6_RTHDRDSTOPTS is ignored - * unless IPV6_RTHDR is set (RFC3542). - */ - if (opt->dst0opt) - ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst0opt); - } - if (opt->hopopt) - ipv6_push_exthdr(skb, proto, NEXTHDR_HOP, opt->hopopt); -} - -void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *proto) -{ - if (opt->dst1opt) - ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst1opt); -} -EXPORT_SYMBOL(ipv6_push_frag_opts); - -/** - * fl6_update_dst - update flowi destination address with info given - * by srcrt option, if any. - * - * @fl6: flowi6 for which daddr is to be updated - * @opt: struct ipv6_txoptions in which to look for srcrt opt - * @orig: copy of original daddr address if modified - * - * Returns NULL if no txoptions or no srcrt, otherwise returns orig - * and initial value of fl6->daddr set in orig - */ -struct in6_addr *fl6_update_dst(struct flowi6 *fl6, - const struct ipv6_txoptions *opt, - struct in6_addr *orig) -{ - if (!opt || !opt->srcrt) - return NULL; - - *orig = fl6->daddr; - - switch (opt->srcrt->type) { - case IPV6_SRCRT_TYPE_0: - case IPV6_SRCRT_STRICT: - case IPV6_SRCRT_TYPE_2: - fl6->daddr = *((struct rt0_hdr *)opt->srcrt)->addr; - break; - case IPV6_SRCRT_TYPE_4: - { - struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)opt->srcrt; - - fl6->daddr = srh->segments[srh->segments_left]; - break; - } - default: - return NULL; - } - - return orig; -} -EXPORT_SYMBOL_GPL(fl6_update_dst); diff --git a/net/ipv6/exthdrs6.c b/net/ipv6/exthdrs6.c new file mode 100644 index 0000000..6dbacf1 --- /dev/null +++ b/net/ipv6/exthdrs6.c @@ -0,0 +1,830 @@ +/* + * Extension Header handling for IPv6 + * Linux INET6 implementation + * + * Authors: + * Pedro Roque + * Andi Kleen + * Alexey Kuznetsov + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +/* Changes: + * yoshfuji : ensure not to overrun while parsing + * tlv options. + * Mitsuru KANDA @USAGI and: Remove ipv6_parse_exthdrs(). + * YOSHIFUJI Hideaki @USAGI Register inbound extension header + * handlers as inet6_protocol{}. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_IPV6_MIP6) +#include +#endif +#include +#include +#ifdef CONFIG_IPV6_SEG6_HMAC +#include +#endif + +#include + +/********************* + Generic functions + *********************/ + +/* An unknown option is detected, decide what to do */ + +static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, + bool disallow_unknowns) +{ + if (disallow_unknowns) { + /* If unknown TLVs are disallowed by configuration + * then always silently drop packet. Note this also + * means no ICMP parameter problem is sent which + * could be a good property to mitigate a reflection DOS + * attack. + */ + + goto drop; + } + + switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) { + case 0: /* ignore */ + return true; + + case 1: /* drop packet */ + break; + + case 3: /* Send ICMP if not a multicast address and drop packet */ + /* Actually, it is redundant check. icmp_send + will recheck in any case. + */ + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) + break; + /* fall through */ + case 2: /* send ICMP PARM PROB regardless and drop packet */ + icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); + return false; + } + +drop: + kfree_skb(skb); + return false; +} + +/* Parse tlv encoded option header (hop-by-hop or destination) */ + +static bool ip6_parse_tlv(const struct tlvtype_proc *procs, + struct sk_buff *skb, + int max_count) +{ + int len = (skb_transport_header(skb)[1] + 1) << 3; + const unsigned char *nh = skb_network_header(skb); + int off = skb_network_header_len(skb); + const struct tlvtype_proc *curr; + bool disallow_unknowns = false; + int tlv_count = 0; + int padlen = 0; + + if (unlikely(max_count < 0)) { + disallow_unknowns = true; + max_count = -max_count; + } + + if (skb_transport_offset(skb) + len > skb_headlen(skb)) + goto bad; + + off += 2; + len -= 2; + + while (len > 0) { + int optlen = nh[off + 1] + 2; + int i; + + switch (nh[off]) { + case IPV6_TLV_PAD1: + optlen = 1; + padlen++; + if (padlen > 7) + goto bad; + break; + + case IPV6_TLV_PADN: + /* RFC 2460 states that the purpose of PadN is + * to align the containing header to multiples + * of 8. 7 is therefore the highest valid value. + * See also RFC 4942, Section 2.1.9.5. + */ + padlen += optlen; + if (padlen > 7) + goto bad; + /* RFC 4942 recommends receiving hosts to + * actively check PadN payload to contain + * only zeroes. + */ + for (i = 2; i < optlen; i++) { + if (nh[off + i] != 0) + goto bad; + } + break; + + default: /* Other TLV code so scan list */ + if (optlen > len) + goto bad; + + tlv_count++; + if (tlv_count > max_count) + goto bad; + + for (curr = procs; curr->type >= 0; curr++) { + if (curr->type == nh[off]) { + /* type specific length/alignment + checks will be performed in the + func(). */ + if (curr->func(skb, off) == false) + return false; + break; + } + } + if (curr->type < 0 && + !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) + return false; + + padlen = 0; + break; + } + off += optlen; + len -= optlen; + } + + if (len == 0) + return true; +bad: + kfree_skb(skb); + return false; +} + +static int ipv6_destopt_rcv(struct sk_buff *skb) +{ + struct inet6_dev *idev = __in6_dev_get(skb->dev); + struct inet6_skb_parm *opt = IP6CB(skb); +#if IS_ENABLED(CONFIG_IPV6_MIP6) + __u16 dstbuf; +#endif + struct dst_entry *dst = skb_dst(skb); + struct net *net = dev_net(skb->dev); + int extlen; + + if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || + !pskb_may_pull(skb, (skb_transport_offset(skb) + + ((skb_transport_header(skb)[1] + 1) << 3)))) { + __IP6_INC_STATS(dev_net(dst->dev), idev, + IPSTATS_MIB_INHDRERRORS); +fail_and_free: + kfree_skb(skb); + return -1; + } + + extlen = (skb_transport_header(skb)[1] + 1) << 3; + if (extlen > net->ipv6.sysctl.max_dst_opts_len) + goto fail_and_free; + + opt->lastopt = opt->dst1 = skb_network_header_len(skb); +#if IS_ENABLED(CONFIG_IPV6_MIP6) + dstbuf = opt->dst1; +#endif + + if (ip6_parse_tlv(tlvprocdestopt_lst, skb, + init_net.ipv6.sysctl.max_dst_opts_cnt)) { + skb->transport_header += extlen; + opt = IP6CB(skb); +#if IS_ENABLED(CONFIG_IPV6_MIP6) + opt->nhoff = dstbuf; +#else + opt->nhoff = opt->dst1; +#endif + return 1; + } + + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + return -1; +} + +static void seg6_update_csum(struct sk_buff *skb) +{ + struct ipv6_sr_hdr *hdr; + struct in6_addr *addr; + __be32 from, to; + + /* srh is at transport offset and seg_left is already decremented + * but daddr is not yet updated with next segment + */ + + hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); + addr = hdr->segments + hdr->segments_left; + + hdr->segments_left++; + from = *(__be32 *)hdr; + + hdr->segments_left--; + to = *(__be32 *)hdr; + + /* update skb csum with diff resulting from seg_left decrement */ + + update_csum_diff4(skb, from, to); + + /* compute csum diff between current and next segment and update */ + + update_csum_diff16(skb, (__be32 *)(&ipv6_hdr(skb)->daddr), + (__be32 *)addr); +} + +static int ipv6_srh_rcv(struct sk_buff *skb) +{ + struct inet6_skb_parm *opt = IP6CB(skb); + struct net *net = dev_net(skb->dev); + struct ipv6_sr_hdr *hdr; + struct inet6_dev *idev; + struct in6_addr *addr; + int accept_seg6; + + hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); + + idev = __in6_dev_get(skb->dev); + + accept_seg6 = net->ipv6.devconf_all->seg6_enabled; + if (accept_seg6 > idev->cnf.seg6_enabled) + accept_seg6 = idev->cnf.seg6_enabled; + + if (!accept_seg6) { + kfree_skb(skb); + return -1; + } + +#ifdef CONFIG_IPV6_SEG6_HMAC + if (!seg6_hmac_validate_skb(skb)) { + kfree_skb(skb); + return -1; + } +#endif + +looped_back: + if (hdr->segments_left == 0) { + if (hdr->nexthdr == NEXTHDR_IPV6) { + int offset = (hdr->hdrlen + 1) << 3; + + skb_postpull_rcsum(skb, skb_network_header(skb), + skb_network_header_len(skb)); + + if (!pskb_pull(skb, offset)) { + kfree_skb(skb); + return -1; + } + skb_postpull_rcsum(skb, skb_transport_header(skb), + offset); + + skb_reset_network_header(skb); + skb_reset_transport_header(skb); + skb->encapsulation = 0; + + __skb_tunnel_rx(skb, skb->dev, net); + + netif_rx(skb); + return -1; + } + + opt->srcrt = skb_network_header_len(skb); + opt->lastopt = opt->srcrt; + skb->transport_header += (hdr->hdrlen + 1) << 3; + opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb); + + return 1; + } + + if (hdr->segments_left >= (hdr->hdrlen >> 1)) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, + ((&hdr->segments_left) - + skb_network_header(skb))); + return -1; + } + + if (skb_cloned(skb)) { + if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) { + __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), + IPSTATS_MIB_OUTDISCARDS); + kfree_skb(skb); + return -1; + } + } + + hdr = (struct ipv6_sr_hdr *)skb_transport_header(skb); + + hdr->segments_left--; + addr = hdr->segments + hdr->segments_left; + + skb_push(skb, sizeof(struct ipv6hdr)); + + if (skb->ip_summed == CHECKSUM_COMPLETE) + seg6_update_csum(skb); + + ipv6_hdr(skb)->daddr = *addr; + + skb_dst_drop(skb); + + ip6_route_input(skb); + + if (skb_dst(skb)->error) { + dst_input(skb); + return -1; + } + + if (skb_dst(skb)->dev->flags & IFF_LOOPBACK) { + if (ipv6_hdr(skb)->hop_limit <= 1) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_send(skb, ICMPV6_TIME_EXCEED, + ICMPV6_EXC_HOPLIMIT, 0); + kfree_skb(skb); + return -1; + } + ipv6_hdr(skb)->hop_limit--; + + skb_pull(skb, sizeof(struct ipv6hdr)); + goto looped_back; + } + + dst_input(skb); + + return -1; +} + +/******************************** + Routing header. + ********************************/ + +/* called with rcu_read_lock() */ +static int ipv6_rthdr_rcv(struct sk_buff *skb) +{ + struct inet6_dev *idev = __in6_dev_get(skb->dev); + struct inet6_skb_parm *opt = IP6CB(skb); + struct in6_addr *addr = NULL; + struct in6_addr daddr; + int n, i; + struct ipv6_rt_hdr *hdr; + struct rt0_hdr *rthdr; + struct net *net = dev_net(skb->dev); + int accept_source_route = net->ipv6.devconf_all->accept_source_route; + + idev = __in6_dev_get(skb->dev); + if (idev && accept_source_route > idev->cnf.accept_source_route) + accept_source_route = idev->cnf.accept_source_route; + + if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || + !pskb_may_pull(skb, (skb_transport_offset(skb) + + ((skb_transport_header(skb)[1] + 1) << 3)))) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + kfree_skb(skb); + return -1; + } + + hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb); + + if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) || + skb->pkt_type != PACKET_HOST) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); + kfree_skb(skb); + return -1; + } + + /* segment routing */ + if (hdr->type == IPV6_SRCRT_TYPE_4) + return ipv6_srh_rcv(skb); + +looped_back: + if (hdr->segments_left == 0) { + switch (hdr->type) { +#if IS_ENABLED(CONFIG_IPV6_MIP6) + case IPV6_SRCRT_TYPE_2: + /* Silently discard type 2 header unless it was + * processed by own + */ + if (!addr) { + __IP6_INC_STATS(net, idev, + IPSTATS_MIB_INADDRERRORS); + kfree_skb(skb); + return -1; + } + break; +#endif + default: + break; + } + + opt->lastopt = opt->srcrt = skb_network_header_len(skb); + skb->transport_header += (hdr->hdrlen + 1) << 3; + opt->dst0 = opt->dst1; + opt->dst1 = 0; + opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb); + return 1; + } + + switch (hdr->type) { +#if IS_ENABLED(CONFIG_IPV6_MIP6) + case IPV6_SRCRT_TYPE_2: + if (accept_source_route < 0) + goto unknown_rh; + /* Silently discard invalid RTH type 2 */ + if (hdr->hdrlen != 2 || hdr->segments_left != 1) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + kfree_skb(skb); + return -1; + } + break; +#endif + default: + goto unknown_rh; + } + + /* + * This is the routing header forwarding algorithm from + * RFC 2460, page 16. + */ + + n = hdr->hdrlen >> 1; + + if (hdr->segments_left > n) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, + ((&hdr->segments_left) - + skb_network_header(skb))); + return -1; + } + + /* We are about to mangle packet header. Be careful! + Do not damage packets queued somewhere. + */ + if (skb_cloned(skb)) { + /* the copy is a forwarded packet */ + if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) { + __IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), + IPSTATS_MIB_OUTDISCARDS); + kfree_skb(skb); + return -1; + } + hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb); + } + + if (skb->ip_summed == CHECKSUM_COMPLETE) + skb->ip_summed = CHECKSUM_NONE; + + i = n - --hdr->segments_left; + + rthdr = (struct rt0_hdr *) hdr; + addr = rthdr->addr; + addr += i - 1; + + switch (hdr->type) { +#if IS_ENABLED(CONFIG_IPV6_MIP6) + case IPV6_SRCRT_TYPE_2: + if (xfrm6_input_addr(skb, (xfrm_address_t *)addr, + (xfrm_address_t *)&ipv6_hdr(skb)->saddr, + IPPROTO_ROUTING) < 0) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); + kfree_skb(skb); + return -1; + } + if (!ipv6_chk_home_addr(dev_net(skb_dst(skb)->dev), addr)) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); + kfree_skb(skb); + return -1; + } + break; +#endif + default: + break; + } + + if (ipv6_addr_is_multicast(addr)) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INADDRERRORS); + kfree_skb(skb); + return -1; + } + + daddr = *addr; + *addr = ipv6_hdr(skb)->daddr; + ipv6_hdr(skb)->daddr = daddr; + + skb_dst_drop(skb); + ip6_route_input(skb); + if (skb_dst(skb)->error) { + skb_push(skb, skb->data - skb_network_header(skb)); + dst_input(skb); + return -1; + } + + if (skb_dst(skb)->dev->flags&IFF_LOOPBACK) { + if (ipv6_hdr(skb)->hop_limit <= 1) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, + 0); + kfree_skb(skb); + return -1; + } + ipv6_hdr(skb)->hop_limit--; + goto looped_back; + } + + skb_push(skb, skb->data - skb_network_header(skb)); + dst_input(skb); + return -1; + +unknown_rh: + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, + (&hdr->type) - skb_network_header(skb)); + return -1; +} + +static const struct inet6_protocol rthdr_protocol = { + .handler = ipv6_rthdr_rcv, + .flags = INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol destopt_protocol = { + .handler = ipv6_destopt_rcv, + .flags = INET6_PROTO_NOPOLICY, +}; + +static const struct inet6_protocol nodata_protocol = { + .handler = dst_discard, + .flags = INET6_PROTO_NOPOLICY, +}; + +int __init ipv6_exthdrs_init(void) +{ + int ret; + + ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING); + if (ret) + goto out; + + ret = inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS); + if (ret) + goto out_rthdr; + + ret = inet6_add_protocol(&nodata_protocol, IPPROTO_NONE); + if (ret) + goto out_destopt; + +out: + return ret; +out_destopt: + inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); +out_rthdr: + inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); + goto out; +}; + +void ipv6_exthdrs_exit(void) +{ + inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); + inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); + inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); +} + +int ipv6_parse_hopopts(struct sk_buff *skb) +{ + struct inet6_skb_parm *opt = IP6CB(skb); + struct net *net = dev_net(skb->dev); + int extlen; + + /* + * skb_network_header(skb) is equal to skb->data, and + * skb_network_header_len(skb) is always equal to + * sizeof(struct ipv6hdr) by definition of + * hop-by-hop options. + */ + if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || + !pskb_may_pull(skb, (sizeof(struct ipv6hdr) + + ((skb_transport_header(skb)[1] + 1) << 3)))) { +fail_and_free: + kfree_skb(skb); + return -1; + } + + extlen = (skb_transport_header(skb)[1] + 1) << 3; + if (extlen > net->ipv6.sysctl.max_hbh_opts_len) + goto fail_and_free; + + opt->flags |= IP6SKB_HOPBYHOP; + if (ip6_parse_tlv(tlvprochopopt_lst, skb, + init_net.ipv6.sysctl.max_hbh_opts_cnt)) { + skb->transport_header += extlen; + opt = IP6CB(skb); + opt->nhoff = sizeof(struct ipv6hdr); + return 1; + } + return -1; +} + +/* + * Creating outbound headers. + * + * "build" functions work when skb is filled from head to tail (datagram) + * "push" functions work when headers are added from tail to head (tcp) + * + * In both cases we assume, that caller reserved enough room + * for headers. + */ + +static void ipv6_push_rthdr0(struct sk_buff *skb, u8 *proto, + struct ipv6_rt_hdr *opt, + struct in6_addr **addr_p, struct in6_addr *saddr) +{ + struct rt0_hdr *phdr, *ihdr; + int hops; + + ihdr = (struct rt0_hdr *) opt; + + phdr = skb_push(skb, (ihdr->rt_hdr.hdrlen + 1) << 3); + memcpy(phdr, ihdr, sizeof(struct rt0_hdr)); + + hops = ihdr->rt_hdr.hdrlen >> 1; + + if (hops > 1) + memcpy(phdr->addr, ihdr->addr + 1, + (hops - 1) * sizeof(struct in6_addr)); + + phdr->addr[hops - 1] = **addr_p; + *addr_p = ihdr->addr; + + phdr->rt_hdr.nexthdr = *proto; + *proto = NEXTHDR_ROUTING; +} + +static void ipv6_push_rthdr4(struct sk_buff *skb, u8 *proto, + struct ipv6_rt_hdr *opt, + struct in6_addr **addr_p, struct in6_addr *saddr) +{ + struct ipv6_sr_hdr *sr_phdr, *sr_ihdr; + int plen, hops; + + sr_ihdr = (struct ipv6_sr_hdr *)opt; + plen = (sr_ihdr->hdrlen + 1) << 3; + + sr_phdr = skb_push(skb, plen); + memcpy(sr_phdr, sr_ihdr, sizeof(struct ipv6_sr_hdr)); + + hops = sr_ihdr->first_segment + 1; + memcpy(sr_phdr->segments + 1, sr_ihdr->segments + 1, + (hops - 1) * sizeof(struct in6_addr)); + + sr_phdr->segments[0] = **addr_p; + *addr_p = &sr_ihdr->segments[sr_ihdr->segments_left]; + + if (sr_ihdr->hdrlen > hops * 2) { + int tlvs_offset, tlvs_length; + + tlvs_offset = (1 + hops * 2) << 3; + tlvs_length = (sr_ihdr->hdrlen - hops * 2) << 3; + memcpy((char *)sr_phdr + tlvs_offset, + (char *)sr_ihdr + tlvs_offset, tlvs_length); + } + +#ifdef CONFIG_IPV6_SEG6_HMAC + if (sr_has_hmac(sr_phdr)) { + struct net *net = NULL; + + if (skb->dev) + net = dev_net(skb->dev); + else if (skb->sk) + net = sock_net(skb->sk); + + WARN_ON(!net); + + if (net) + seg6_push_hmac(net, saddr, sr_phdr); + } +#endif + + sr_phdr->nexthdr = *proto; + *proto = NEXTHDR_ROUTING; +} + +static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto, + struct ipv6_rt_hdr *opt, + struct in6_addr **addr_p, struct in6_addr *saddr) +{ + switch (opt->type) { + case IPV6_SRCRT_TYPE_0: + case IPV6_SRCRT_STRICT: + case IPV6_SRCRT_TYPE_2: + ipv6_push_rthdr0(skb, proto, opt, addr_p, saddr); + break; + case IPV6_SRCRT_TYPE_4: + ipv6_push_rthdr4(skb, proto, opt, addr_p, saddr); + break; + default: + break; + } +} + +static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv6_opt_hdr *opt) +{ + struct ipv6_opt_hdr *h = skb_push(skb, ipv6_optlen(opt)); + + memcpy(h, opt, ipv6_optlen(opt)); + h->nexthdr = *proto; + *proto = type; +} + +void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, + u8 *proto, + struct in6_addr **daddr, struct in6_addr *saddr) +{ + if (opt->srcrt) { + ipv6_push_rthdr(skb, proto, opt->srcrt, daddr, saddr); + /* + * IPV6_RTHDRDSTOPTS is ignored + * unless IPV6_RTHDR is set (RFC3542). + */ + if (opt->dst0opt) + ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst0opt); + } + if (opt->hopopt) + ipv6_push_exthdr(skb, proto, NEXTHDR_HOP, opt->hopopt); +} + +void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *proto) +{ + if (opt->dst1opt) + ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst1opt); +} +EXPORT_SYMBOL(ipv6_push_frag_opts); + +/** + * fl6_update_dst - update flowi destination address with info given + * by srcrt option, if any. + * + * @fl6: flowi6 for which daddr is to be updated + * @opt: struct ipv6_txoptions in which to look for srcrt opt + * @orig: copy of original daddr address if modified + * + * Returns NULL if no txoptions or no srcrt, otherwise returns orig + * and initial value of fl6->daddr set in orig + */ +struct in6_addr *fl6_update_dst(struct flowi6 *fl6, + const struct ipv6_txoptions *opt, + struct in6_addr *orig) +{ + if (!opt || !opt->srcrt) + return NULL; + + *orig = fl6->daddr; + + switch (opt->srcrt->type) { + case IPV6_SRCRT_TYPE_0: + case IPV6_SRCRT_STRICT: + case IPV6_SRCRT_TYPE_2: + fl6->daddr = *((struct rt0_hdr *)opt->srcrt)->addr; + break; + case IPV6_SRCRT_TYPE_4: + { + struct ipv6_sr_hdr *srh = (struct ipv6_sr_hdr *)opt->srcrt; + + fl6->daddr = srh->segments[srh->segments_left]; + break; + } + default: + return NULL; + } + + return orig; +} +EXPORT_SYMBOL_GPL(fl6_update_dst); diff --git a/net/ipv6/exthdrs6_offload.c b/net/ipv6/exthdrs6_offload.c new file mode 100644 index 0000000..f5e2ba1 --- /dev/null +++ b/net/ipv6/exthdrs6_offload.c @@ -0,0 +1,41 @@ +/* + * IPV6 GSO/GRO offload support + * Linux INET6 implementation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * IPV6 Extension Header GSO/GRO support + */ +#include +#include "ip6_offload.h" + +static const struct net_offload rthdr_offload = { + .flags = INET6_PROTO_GSO_EXTHDR, +}; + +static const struct net_offload dstopt_offload = { + .flags = INET6_PROTO_GSO_EXTHDR, +}; + +int __init ipv6_exthdrs_offload_init(void) +{ + int ret; + + ret = inet6_add_offload(&rthdr_offload, IPPROTO_ROUTING); + if (ret) + goto out; + + ret = inet6_add_offload(&dstopt_offload, IPPROTO_DSTOPTS); + if (ret) + goto out_rt; + +out: + return ret; + +out_rt: + inet6_del_offload(&rthdr_offload, IPPROTO_ROUTING); + goto out; +} diff --git a/net/ipv6/exthdrs6_options.c b/net/ipv6/exthdrs6_options.c new file mode 100644 index 0000000..032e072 --- /dev/null +++ b/net/ipv6/exthdrs6_options.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if IS_ENABLED(CONFIG_IPV6_MIP6) +#include +#endif + +/* Destination options header */ + +#if IS_ENABLED(CONFIG_IPV6_MIP6) +static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) +{ + struct ipv6_destopt_hao *hao; + struct inet6_skb_parm *opt = IP6CB(skb); + struct ipv6hdr *ipv6h = ipv6_hdr(skb); + int ret; + + if (opt->dsthao) { + net_dbg_ratelimited("hao duplicated\n"); + goto discard; + } + opt->dsthao = opt->dst1; + opt->dst1 = 0; + + hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff); + + if (hao->length != 16) { + net_dbg_ratelimited("hao invalid option length = %d\n", + hao->length); + goto discard; + } + + if (!(ipv6_addr_type(&hao->addr) & IPV6_ADDR_UNICAST)) { + net_dbg_ratelimited("hao is not an unicast addr: %pI6\n", + &hao->addr); + goto discard; + } + + ret = xfrm6_input_addr(skb, (xfrm_address_t *)&ipv6h->daddr, + (xfrm_address_t *)&hao->addr, IPPROTO_DSTOPTS); + if (unlikely(ret < 0)) + goto discard; + + if (skb_cloned(skb)) { + if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) + goto discard; + + /* update all variable using below by copied skbuff */ + hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + + optoff); + ipv6h = ipv6_hdr(skb); + } + + if (skb->ip_summed == CHECKSUM_COMPLETE) + skb->ip_summed = CHECKSUM_NONE; + + swap(ipv6h->saddr, hao->addr); + + if (skb->tstamp == 0) + __net_timestamp(skb); + + return true; + + discard: + kfree_skb(skb); + return false; +} +#endif + +const struct tlvtype_proc tlvprocdestopt_lst[] = { +#if IS_ENABLED(CONFIG_IPV6_MIP6) + { + .type = IPV6_TLV_HAO, + .func = ipv6_dest_hao, + }, +#endif + {-1, NULL} +}; + +/* Hop-by-hop options */ + +/* Note: we cannot rely on skb_dst(skb) before we assign it in + * ip6_route_input(). + */ +static inline struct inet6_dev *ipv6_skb_idev(struct sk_buff *skb) +{ + return skb_dst(skb) ? ip6_dst_idev(skb_dst(skb)) : + __in6_dev_get(skb->dev); +} + +static inline struct net *ipv6_skb_net(struct sk_buff *skb) +{ + return skb_dst(skb) ? dev_net(skb_dst(skb)->dev) : dev_net(skb->dev); +} + +/* Router Alert as of RFC 2711 */ + +static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + + if (nh[optoff + 1] == 2) { + IP6CB(skb)->flags |= IP6SKB_ROUTERALERT; + memcpy(&IP6CB(skb)->ra, nh + optoff + 2, + sizeof(IP6CB(skb)->ra)); + return true; + } + net_dbg_ratelimited("%s: wrong RA length %d\n", + __func__, nh[optoff + 1]); + kfree_skb(skb); + return false; +} + +/* Jumbo payload */ + +static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); + struct net *net = ipv6_skb_net(skb); + u32 pkt_len; + + if (nh[optoff + 1] != 4 || (optoff & 3) != 2) { + net_dbg_ratelimited("%s: wrong jumbo opt length/alignment %d\n", + __func__, nh[optoff + 1]); + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + goto drop; + } + + pkt_len = ntohl(*(__be32 *)(nh + optoff + 2)); + if (pkt_len <= IPV6_MAXPLEN) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff + 2); + return false; + } + if (ipv6_hdr(skb)->payload_len) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff); + return false; + } + + if (pkt_len > skb->len - sizeof(struct ipv6hdr)) { + __IP6_INC_STATS(net, idev, IPSTATS_MIB_INTRUNCATEDPKTS); + goto drop; + } + + if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) + goto drop; + + IP6CB(skb)->flags |= IP6SKB_JUMBOGRAM; + return true; + +drop: + kfree_skb(skb); + return false; +} + +/* CALIPSO RFC 5570 */ + +static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) +{ + const unsigned char *nh = skb_network_header(skb); + + if (nh[optoff + 1] < 8) + goto drop; + + if (nh[optoff + 6] * 4 + 8 > nh[optoff + 1]) + goto drop; + + if (!calipso_validate(skb, nh + optoff)) + goto drop; + + return true; + +drop: + kfree_skb(skb); + return false; +} + +const struct tlvtype_proc tlvprochopopt_lst[] = { + { + .type = IPV6_TLV_ROUTERALERT, + .func = ipv6_hop_ra, + }, + { + .type = IPV6_TLV_JUMBO, + .func = ipv6_hop_jumbo, + }, + { + .type = IPV6_TLV_CALIPSO, + .func = ipv6_hop_calipso, + }, + { -1, } +}; diff --git a/net/ipv6/exthdrs_offload.c b/net/ipv6/exthdrs_offload.c deleted file mode 100644 index f5e2ba1..0000000 --- a/net/ipv6/exthdrs_offload.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * IPV6 GSO/GRO offload support - * Linux INET6 implementation - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * - * IPV6 Extension Header GSO/GRO support - */ -#include -#include "ip6_offload.h" - -static const struct net_offload rthdr_offload = { - .flags = INET6_PROTO_GSO_EXTHDR, -}; - -static const struct net_offload dstopt_offload = { - .flags = INET6_PROTO_GSO_EXTHDR, -}; - -int __init ipv6_exthdrs_offload_init(void) -{ - int ret; - - ret = inet6_add_offload(&rthdr_offload, IPPROTO_ROUTING); - if (ret) - goto out; - - ret = inet6_add_offload(&dstopt_offload, IPPROTO_DSTOPTS); - if (ret) - goto out_rt; - -out: - return ret; - -out_rt: - inet6_del_offload(&rthdr_offload, IPPROTO_ROUTING); - goto out; -} diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c deleted file mode 100644 index 032e072..0000000 --- a/net/ipv6/exthdrs_options.c +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if IS_ENABLED(CONFIG_IPV6_MIP6) -#include -#endif - -/* Destination options header */ - -#if IS_ENABLED(CONFIG_IPV6_MIP6) -static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) -{ - struct ipv6_destopt_hao *hao; - struct inet6_skb_parm *opt = IP6CB(skb); - struct ipv6hdr *ipv6h = ipv6_hdr(skb); - int ret; - - if (opt->dsthao) { - net_dbg_ratelimited("hao duplicated\n"); - goto discard; - } - opt->dsthao = opt->dst1; - opt->dst1 = 0; - - hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff); - - if (hao->length != 16) { - net_dbg_ratelimited("hao invalid option length = %d\n", - hao->length); - goto discard; - } - - if (!(ipv6_addr_type(&hao->addr) & IPV6_ADDR_UNICAST)) { - net_dbg_ratelimited("hao is not an unicast addr: %pI6\n", - &hao->addr); - goto discard; - } - - ret = xfrm6_input_addr(skb, (xfrm_address_t *)&ipv6h->daddr, - (xfrm_address_t *)&hao->addr, IPPROTO_DSTOPTS); - if (unlikely(ret < 0)) - goto discard; - - if (skb_cloned(skb)) { - if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) - goto discard; - - /* update all variable using below by copied skbuff */ - hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + - optoff); - ipv6h = ipv6_hdr(skb); - } - - if (skb->ip_summed == CHECKSUM_COMPLETE) - skb->ip_summed = CHECKSUM_NONE; - - swap(ipv6h->saddr, hao->addr); - - if (skb->tstamp == 0) - __net_timestamp(skb); - - return true; - - discard: - kfree_skb(skb); - return false; -} -#endif - -const struct tlvtype_proc tlvprocdestopt_lst[] = { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - { - .type = IPV6_TLV_HAO, - .func = ipv6_dest_hao, - }, -#endif - {-1, NULL} -}; - -/* Hop-by-hop options */ - -/* Note: we cannot rely on skb_dst(skb) before we assign it in - * ip6_route_input(). - */ -static inline struct inet6_dev *ipv6_skb_idev(struct sk_buff *skb) -{ - return skb_dst(skb) ? ip6_dst_idev(skb_dst(skb)) : - __in6_dev_get(skb->dev); -} - -static inline struct net *ipv6_skb_net(struct sk_buff *skb) -{ - return skb_dst(skb) ? dev_net(skb_dst(skb)->dev) : dev_net(skb->dev); -} - -/* Router Alert as of RFC 2711 */ - -static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - - if (nh[optoff + 1] == 2) { - IP6CB(skb)->flags |= IP6SKB_ROUTERALERT; - memcpy(&IP6CB(skb)->ra, nh + optoff + 2, - sizeof(IP6CB(skb)->ra)); - return true; - } - net_dbg_ratelimited("%s: wrong RA length %d\n", - __func__, nh[optoff + 1]); - kfree_skb(skb); - return false; -} - -/* Jumbo payload */ - -static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); - struct net *net = ipv6_skb_net(skb); - u32 pkt_len; - - if (nh[optoff + 1] != 4 || (optoff & 3) != 2) { - net_dbg_ratelimited("%s: wrong jumbo opt length/alignment %d\n", - __func__, nh[optoff + 1]); - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - goto drop; - } - - pkt_len = ntohl(*(__be32 *)(nh + optoff + 2)); - if (pkt_len <= IPV6_MAXPLEN) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff + 2); - return false; - } - if (ipv6_hdr(skb)->payload_len) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INHDRERRORS); - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff); - return false; - } - - if (pkt_len > skb->len - sizeof(struct ipv6hdr)) { - __IP6_INC_STATS(net, idev, IPSTATS_MIB_INTRUNCATEDPKTS); - goto drop; - } - - if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) - goto drop; - - IP6CB(skb)->flags |= IP6SKB_JUMBOGRAM; - return true; - -drop: - kfree_skb(skb); - return false; -} - -/* CALIPSO RFC 5570 */ - -static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) -{ - const unsigned char *nh = skb_network_header(skb); - - if (nh[optoff + 1] < 8) - goto drop; - - if (nh[optoff + 6] * 4 + 8 > nh[optoff + 1]) - goto drop; - - if (!calipso_validate(skb, nh + optoff)) - goto drop; - - return true; - -drop: - kfree_skb(skb); - return false; -} - -const struct tlvtype_proc tlvprochopopt_lst[] = { - { - .type = IPV6_TLV_ROUTERALERT, - .func = ipv6_hop_ra, - }, - { - .type = IPV6_TLV_JUMBO, - .func = ipv6_hop_jumbo, - }, - { - .type = IPV6_TLV_CALIPSO, - .func = ipv6_hop_calipso, - }, - { -1, } -};