diff mbox series

[bpf-next] net-bpf: bpf_skb_change_proto() - add support for ipv6 fragments

Message ID 20211123230632.1503854-1-zenczykowski@gmail.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series [bpf-next] net-bpf: bpf_skb_change_proto() - add support for ipv6 fragments | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for bpf-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Single patches do not need cover letters
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 12480 this patch: 12480
netdev/cc_maintainers warning 7 maintainers not CCed: kafai@fb.com songliubraving@fb.com john.fastabend@gmail.com kuba@kernel.org kpsingh@kernel.org yhs@fb.com andrii@kernel.org
netdev/build_clang success Errors and warnings before: 2106 this patch: 2106
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 11646 this patch: 11646
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: please, no space before tabs
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next success VM_Test

Commit Message

Maciej Żenczykowski Nov. 23, 2021, 11:06 p.m. UTC
From: Maciej Żenczykowski <maze@google.com>

IPv4 fragments (20 byte IPv4 header) need to be translated to/from
IPv6 fragments (40 byte IPv6 header with additional 8 byte IPv6
fragmentation header).

This allows this to be done by adding an extra flag BPF_F_IPV6_FRAGMENT
to bpf_skb_change_proto().

I think this is already technically achievable via the use of
bpf_skb_adjust_room() which was added in v4.12 commit 2be7e212d541,
but this is far easier to use and eliminates the need to call two
helper functions, so it's also faster.

Cc: Lorenzo Colitti <lorenzo@google.com>
Signed-off-by: Maciej Żenczykowski <maze@google.com>
---
 include/uapi/linux/bpf.h | 24 ++++++++++++++++++++++--
 net/core/filter.c        | 19 ++++++++++---------
 2 files changed, 32 insertions(+), 11 deletions(-)

Comments

Song Liu Nov. 27, 2021, 2:32 a.m. UTC | #1
On Tue, Nov 23, 2021 at 3:06 PM Maciej Żenczykowski
<zenczykowski@gmail.com> wrote:
>
> From: Maciej Żenczykowski <maze@google.com>
>
> IPv4 fragments (20 byte IPv4 header) need to be translated to/from
> IPv6 fragments (40 byte IPv6 header with additional 8 byte IPv6
> fragmentation header).
>
> This allows this to be done by adding an extra flag BPF_F_IPV6_FRAGMENT
> to bpf_skb_change_proto().
>
> I think this is already technically achievable via the use of
> bpf_skb_adjust_room() which was added in v4.12 commit 2be7e212d541,
> but this is far easier to use and eliminates the need to call two
> helper functions, so it's also faster.
>
> Cc: Lorenzo Colitti <lorenzo@google.com>
> Signed-off-by: Maciej Żenczykowski <maze@google.com>

Please add a selftest to exercise the new flag.

Thanks,
Song
diff mbox series

Patch

diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index ba5af15e25f5..0187c2f0a4bc 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -2188,8 +2188,10 @@  union bpf_attr {
  * 		checked and segments are recalculated by the GSO/GRO engine.
  * 		The size for GSO target is adapted as well.
  *
- * 		All values for *flags* are reserved for future usage, and must
- * 		be left at zero.
+ * 		*flags* may be set to **BPF_F_IPV6_FRAGMENT** to treat ipv6 as
+ * 		a 48 byte header instead of the normal 40 (this leaves 8 bytes
+ * 		of space for the IPv6 Fragmentation Header). All other bits in
+ * 		*flags* are reserved for future usage, and must be left at zero.
  *
  * 		A call to this helper is susceptible to change the underlying
  * 		packet buffer. Therefore, at load time, all checks on pointers
@@ -5164,6 +5166,24 @@  enum {
 	BPF_F_TUNINFO_IPV6		= (1ULL << 0),
 };
 
+/* BPF_FUNC_skb_change_proto flags. */
+enum {
+	/* Bits 0-15 are reserved for possible future expansion into
+	 * a potential signed 8 bit field, which allows for corrections
+	 * to account for ipv4 options and/or additional ipv6 expansion headers,
+	 * but for now we support *only* the 8 byte ipv6 frag header.
+	 *
+	 * This is most useful, because ipv4 without options supports fragments,
+	 * while ipv6 does not, so the 20 byte ipv4-frag <-> 48 byte ipv6
+	 * conversion is not a terribly rare case (UDP DNS queries for example).
+	 *
+	 * Only use bits <16 for other purposes if we run out of >15 bits first.
+	 *
+	 * 1ULL << 3 is equal to +8 and is the ipv6 frag header size.
+	 */
+	BPF_F_IPV6_FRAGMENT		= (1ULL << 3),
+};
+
 /* flags for both BPF_FUNC_get_stackid and BPF_FUNC_get_stack. */
 enum {
 	BPF_F_SKIP_FIELD_MASK		= 0xffULL,
diff --git a/net/core/filter.c b/net/core/filter.c
index 6102f093d59a..13020368fb4a 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -3219,9 +3219,8 @@  static int bpf_skb_net_hdr_pop(struct sk_buff *skb, u32 off, u32 len)
 	return ret;
 }
 
-static int bpf_skb_proto_4_to_6(struct sk_buff *skb)
+static int bpf_skb_proto_4_to_6(struct sk_buff *skb, u32 len_diff)
 {
-	const u32 len_diff = sizeof(struct ipv6hdr) - sizeof(struct iphdr);
 	u32 off = skb_mac_header_len(skb);
 	int ret;
 
@@ -3249,9 +3248,8 @@  static int bpf_skb_proto_4_to_6(struct sk_buff *skb)
 	return 0;
 }
 
-static int bpf_skb_proto_6_to_4(struct sk_buff *skb)
+static int bpf_skb_proto_6_to_4(struct sk_buff *skb, u32 len_diff)
 {
-	const u32 len_diff = sizeof(struct ipv6hdr) - sizeof(struct iphdr);
 	u32 off = skb_mac_header_len(skb);
 	int ret;
 
@@ -3279,17 +3277,17 @@  static int bpf_skb_proto_6_to_4(struct sk_buff *skb)
 	return 0;
 }
 
-static int bpf_skb_proto_xlat(struct sk_buff *skb, __be16 to_proto)
+static int bpf_skb_proto_xlat(struct sk_buff *skb, __be16 to_proto, u32 len_diff)
 {
 	__be16 from_proto = skb->protocol;
 
 	if (from_proto == htons(ETH_P_IP) &&
 	      to_proto == htons(ETH_P_IPV6))
-		return bpf_skb_proto_4_to_6(skb);
+		return bpf_skb_proto_4_to_6(skb, len_diff);
 
 	if (from_proto == htons(ETH_P_IPV6) &&
 	      to_proto == htons(ETH_P_IP))
-		return bpf_skb_proto_6_to_4(skb);
+		return bpf_skb_proto_6_to_4(skb, len_diff);
 
 	return -ENOTSUPP;
 }
@@ -3297,9 +3295,10 @@  static int bpf_skb_proto_xlat(struct sk_buff *skb, __be16 to_proto)
 BPF_CALL_3(bpf_skb_change_proto, struct sk_buff *, skb, __be16, proto,
 	   u64, flags)
 {
+	u32 len_diff;
 	int ret;
 
-	if (unlikely(flags))
+	if (unlikely(flags & ~(BPF_F_IPV6_FRAGMENT)))
 		return -EINVAL;
 
 	/* General idea is that this helper does the basic groundwork
@@ -3319,7 +3318,9 @@  BPF_CALL_3(bpf_skb_change_proto, struct sk_buff *, skb, __be16, proto,
 	 * that. For offloads, we mark packet as dodgy, so that headers
 	 * need to be verified first.
 	 */
-	ret = bpf_skb_proto_xlat(skb, proto);
+	len_diff = sizeof(struct ipv6hdr) - sizeof(struct iphdr)
+		   + ((flags & BPF_F_IPV6_FRAGMENT) ? sizeof(struct frag_hdr) : 0);
+	ret = bpf_skb_proto_xlat(skb, proto, len_diff);
 	bpf_compute_data_pointers(skb);
 	return ret;
 }