diff mbox series

[v2] btrfs: send: prepare for v2 protocol

Message ID 20211022145336.29711-1-dsterba@suse.com (mailing list archive)
State New, archived
Headers show
Series [v2] btrfs: send: prepare for v2 protocol | expand

Commit Message

David Sterba Oct. 22, 2021, 2:53 p.m. UTC
This is the infrastructure part only, without any new updates, thus safe
to be applied now and all other changes built on top of it, unless there
are further comments.

---

This is preparatory work for send protocol update to version 2 and
higher.

We have many pending protocol update requests but still don't have the
basic protocol rev in place, the first thing that must happen is to do
the actual versioning support.

The protocol version is u32 and is a new member in the send ioctl
struct. Validity of the version field is backed by a new flag bit. Old
kernels would fail when a higher version is requested. Version protocol
0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
that's also exported in sysfs.

The version is still unchanged and will be increased once we have new
incompatible commands or stream updates.

Signed-off-by: David Sterba <dsterba@suse.com>
---
 fs/btrfs/send.c            | 22 ++++++++++++++++++++++
 fs/btrfs/send.h            |  7 +++++++
 include/uapi/linux/btrfs.h | 12 ++++++++++--
 3 files changed, 39 insertions(+), 2 deletions(-)

Comments

Nikolay Borisov Oct. 25, 2021, 6:48 a.m. UTC | #1
On 22.10.21 г. 17:53, David Sterba wrote:
> This is the infrastructure part only, without any new updates, thus safe
> to be applied now and all other changes built on top of it, unless there
> are further comments.
> 
> ---
> 
> This is preparatory work for send protocol update to version 2 and
> higher.
> 
> We have many pending protocol update requests but still don't have the
> basic protocol rev in place, the first thing that must happen is to do
> the actual versioning support.
> 
> The protocol version is u32 and is a new member in the send ioctl
> struct. Validity of the version field is backed by a new flag bit. Old
> kernels would fail when a higher version is requested. Version protocol
> 0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
> that's also exported in sysfs.
> 
> The version is still unchanged and will be increased once we have new
> incompatible commands or stream updates.
> 
> Signed-off-by: David Sterba <dsterba@suse.com>


Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Graham Cobb Oct. 25, 2021, 9:34 a.m. UTC | #2
On 25/10/2021 07:48, Nikolay Borisov wrote:
> 
> 
> On 22.10.21 г. 17:53, David Sterba wrote:
>> This is the infrastructure part only, without any new updates, thus safe
>> to be applied now and all other changes built on top of it, unless there
>> are further comments.
>>
>> ---
>>
>> This is preparatory work for send protocol update to version 2 and
>> higher.
>>
>> We have many pending protocol update requests but still don't have the
>> basic protocol rev in place, the first thing that must happen is to do
>> the actual versioning support.
>>
>> The protocol version is u32 and is a new member in the send ioctl
>> struct. Validity of the version field is backed by a new flag bit. Old
>> kernels would fail when a higher version is requested. Version protocol
>> 0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
>> that's also exported in sysfs.
>>
>> The version is still unchanged and will be increased once we have new
>> incompatible commands or stream updates.
>>
>> Signed-off-by: David Sterba <dsterba@suse.com>
> 
> 
> Reviewed-by: Nikolay Borisov <nborisov@suse.com>

I have a question about how this will work from the point of view of the
sysadmin.

I have a number of different systems between which I need send and
receive working. Some are stable machines - many using distro stable
kernels. Some are new machines (most running debian testing but
occasionally with a newer kernel for some other reason). Some are
managed by other people and I don't control the kernel or progs versions
or am even told when they change.

I need to be able to generate a send stream on any system and receive it
on any other system. However, I don't want to be limited to just the
very oldest version of the protocol: for some tasks I am willing to take
into account the target system capabilities when generating a send
stream for that task.

So, I think what I need is:

1) A mechanism to query a receiving system to find out what protocol
range it supports for receive (taking into account *both* kernel and
btrfs-receive capabilities). And when I say "protocol version" I mean
feature level - the reported "version" must define not just encodings
but also what capabilities it can handle.

2) A mechanism to select what protocol version (in the sense above)
btrfs-send should use in the stream (again, kernel and user space).

3) **Preferably** filesystem features on the sending side which require
protocol features outside the selected range would be emulated using
features in the earlier protocols. If that cannot be done,
**definitely** abort the send and report an error. If the send completes
without error the stream must be within the specified protocol range and
work on a receiver which claims to support that range.

That way, I can check which version the receiver supports (maybe in a
script in real time or maybe as a config option I change when necessary)
and generate a send stream which it can use. And if I can't do that, I
get a clear error. I would also be able to request tools like btrbk to
use this information when taking snapshot backups.

I am not sure I can do that with the mechanisms being proposed here. Am
I missing something?

Graham
Nikolay Borisov Oct. 25, 2021, 10:18 a.m. UTC | #3
On 25.10.21 г. 12:34, Graham Cobb wrote:
> 
> On 25/10/2021 07:48, Nikolay Borisov wrote:
>>
>>
>> On 22.10.21 г. 17:53, David Sterba wrote:
>>> This is the infrastructure part only, without any new updates, thus safe
>>> to be applied now and all other changes built on top of it, unless there
>>> are further comments.
>>>
>>> ---
>>>
>>> This is preparatory work for send protocol update to version 2 and
>>> higher.
>>>
>>> We have many pending protocol update requests but still don't have the
>>> basic protocol rev in place, the first thing that must happen is to do
>>> the actual versioning support.
>>>
>>> The protocol version is u32 and is a new member in the send ioctl
>>> struct. Validity of the version field is backed by a new flag bit. Old
>>> kernels would fail when a higher version is requested. Version protocol
>>> 0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
>>> that's also exported in sysfs.
>>>
>>> The version is still unchanged and will be increased once we have new
>>> incompatible commands or stream updates.
>>>
>>> Signed-off-by: David Sterba <dsterba@suse.com>
>>
>>
>> Reviewed-by: Nikolay Borisov <nborisov@suse.com>
> 
> I have a question about how this will work from the point of view of the
> sysadmin.
> 
> I have a number of different systems between which I need send and
> receive working. Some are stable machines - many using distro stable
> kernels. Some are new machines (most running debian testing but
> occasionally with a newer kernel for some other reason). Some are
> managed by other people and I don't control the kernel or progs versions
> or am even told when they change.
> 
> I need to be able to generate a send stream on any system and receive it
> on any other system. However, I don't want to be limited to just the
> very oldest version of the protocol: for some tasks I am willing to take
> into account the target system capabilities when generating a send
> stream for that task.
> 
> So, I think what I need is:
> 
> 1) A mechanism to query a receiving system to find out what protocol
> range it supports for receive (taking into account *both* kernel and
> btrfs-receive capabilities). And when I say "protocol version" I mean
> feature level - the reported "version" must define not just encodings
> but also what capabilities it can handle.

Receiving is handled entirely in user space by btrfs-progs so that way
it's easier to upgrade the receive side. The send stream is generated by
the kernel. Regarding the report capabilities it would be an overkill to
also report capabilitiee because eventually we'd run out of usable space
in the existing ioctl and we'll have to simply add _v2. Instead I think
we should stick to version numbers and documenting the capabilities in
release notes out of band.

Having said that current proposal (that is Boris' patches in the ml) do
propose to also add flags in addition to send stream version switch
which allows for scenarios such as:

"Use v2 protocol stream which implements fallocate/setflags and
optionally encoded read/writes", so the "support encoded read/writes"
would be predicated on the protocol version AND the feature toggle.
However I'd like some rationale why we want this flexibility, because
all of this just blows the test matrix and maintenance burden.

> 
> 2) A mechanism to select what protocol version (in the sense above)
> btrfs-send should use in the stream (again, kernel and user space).

That's already there in the proposed patches which implement v2 that is
btrfs-progs is going to have a command line option to instruct which
stream version to use.

> 
> 3) **Preferably** filesystem features on the sending side which require
> protocol features outside the selected range would be emulated using
> features in the earlier protocols. If that cannot be done,
> **definitely** abort the send and report an error. If the send completes
> without error the stream must be within the specified protocol range and
> work on a receiver which claims to support that range.

I'm failing to parse this? We shouldn't be having fs features which are
outside of the selected protocol version, that's the whole idea of the
protocol versioning.

> 
> That way, I can check which version the receiver supports (maybe in a
> script in real time or maybe as a config option I change when necessary)
> and generate a send stream which it can use. And if I can't do that, I
> get a clear error. I would also be able to request tools like btrbk to
> use this information when taking snapshot backups.
> 
> I am not sure I can do that with the mechanisms being proposed here. Am
> I missing something?
> 
> Graham
>
Graham Cobb Oct. 25, 2021, 2:08 p.m. UTC | #4
On 25/10/2021 11:18, Nikolay Borisov wrote:
> 
> 
> On 25.10.21 г. 12:34, Graham Cobb wrote:
>>
>> On 25/10/2021 07:48, Nikolay Borisov wrote:
>>>
>>>
>>> On 22.10.21 г. 17:53, David Sterba wrote:
>>>> This is the infrastructure part only, without any new updates, thus safe
>>>> to be applied now and all other changes built on top of it, unless there
>>>> are further comments.
>>>>
>>>> ---
>>>>
>>>> This is preparatory work for send protocol update to version 2 and
>>>> higher.
>>>>
>>>> We have many pending protocol update requests but still don't have the
>>>> basic protocol rev in place, the first thing that must happen is to do
>>>> the actual versioning support.
>>>>
>>>> The protocol version is u32 and is a new member in the send ioctl
>>>> struct. Validity of the version field is backed by a new flag bit. Old
>>>> kernels would fail when a higher version is requested. Version protocol
>>>> 0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
>>>> that's also exported in sysfs.
>>>>
>>>> The version is still unchanged and will be increased once we have new
>>>> incompatible commands or stream updates.
>>>>
>>>> Signed-off-by: David Sterba <dsterba@suse.com>
>>>
>>>
>>> Reviewed-by: Nikolay Borisov <nborisov@suse.com>
>>
>> I have a question about how this will work from the point of view of the
>> sysadmin.
>>
>> I have a number of different systems between which I need send and
>> receive working. Some are stable machines - many using distro stable
>> kernels. Some are new machines (most running debian testing but
>> occasionally with a newer kernel for some other reason). Some are
>> managed by other people and I don't control the kernel or progs versions
>> or am even told when they change.
>>
>> I need to be able to generate a send stream on any system and receive it
>> on any other system. However, I don't want to be limited to just the
>> very oldest version of the protocol: for some tasks I am willing to take
>> into account the target system capabilities when generating a send
>> stream for that task.
>>
>> So, I think what I need is:
>>
>> 1) A mechanism to query a receiving system to find out what protocol
>> range it supports for receive (taking into account *both* kernel and
>> btrfs-receive capabilities). And when I say "protocol version" I mean
>> feature level - the reported "version" must define not just encodings
>> but also what capabilities it can handle.
> 
> Receiving is handled entirely in user space by btrfs-progs so that way
> it's easier to upgrade the receive side. The send stream is generated by
> the kernel. Regarding the report capabilities it would be an overkill to
> also report capabilitiee because eventually we'd run out of usable space
> in the existing ioctl and we'll have to simply add _v2. Instead I think
> we should stick to version numbers and documenting the capabilities in
> release notes out of band.

That sounds OK - if the commitment going forward is that btrfs-receive
can always handle any stream of a certain version, and all previous
versions then all I need is an option for btrfs-receive which prints
that protocol number. I need to be able to do something like:

send_version=$(ssh target btrfs receive --version)
btrfs send --version="$send_version" | ssh target btrfs receive

> Having said that current proposal (that is Boris' patches in the ml) do
> propose to also add flags in addition to send stream version switch
> which allows for scenarios such as:
> 
> "Use v2 protocol stream which implements fallocate/setflags and
> optionally encoded read/writes", so the "support encoded read/writes"
> would be predicated on the protocol version AND the feature toggle.
> However I'd like some rationale why we want this flexibility, because
> all of this just blows the test matrix and maintenance burden.

And makes it impossible to use in practice - the sender needs to know
what capabilities to use when generating the stream and needs to know
that the receiver will be able to handle them.

BTW, my background is networking. In the networking world we do protocol
versioning and negotiation all the time. Capabilities **can** be used
but, in practice, only when there is a real-time negotiation going on
(and they are an optimisation - not necessary for correct operation). Of
course, the design of btrfs send/receive makes it impossible to do a
negotiation so we need a mechanism for the sysadmin to perform the
"negotiation" out-of-band using command options.

>> 2) A mechanism to select what protocol version (in the sense above)
>> btrfs-send should use in the stream (again, kernel and user space).
> 
> That's already there in the proposed patches which implement v2 that is
> btrfs-progs is going to have a command line option to instruct which
> stream version to use.

That is fine as long as there are no optional capabilties in that
version which the receiver might not implement.

>> 3) **Preferably** filesystem features on the sending side which require
>> protocol features outside the selected range would be emulated using
>> features in the earlier protocols. If that cannot be done,
>> **definitely** abort the send and report an error. If the send completes
>> without error the stream must be within the specified protocol range and
>> work on a receiver which claims to support that range.
> 
> I'm failing to parse this? We shouldn't be having fs features which are
> outside of the selected protocol version, that's the whole idea of the
> protocol versioning.

I am mostly thinking of capabilities. Like, for example, an option to
send compressed data still compressed or to send it uncompressed if the
receiver doesn't implement storing compressed data (or the required
compression algorithm). But also if there is some new btrfs feature in
use which cannot be expressed in the (earlier) version of the protocol
which has been selected.

In short, the "version number" (actually it should be a range - like
"2-5" - in case V1 support has to be dropped by some receivers later on)
reported by a command line option on btrfs-receive must provide a
guarantee that any (valid) stream of that version number can be received
(not dependent on other things - like kernel version, compression
modules loaded, mount options, etc).
Anand Jain Oct. 27, 2021, 7:17 a.m. UTC | #5
On 22/10/2021 22:53, David Sterba wrote:
> This is the infrastructure part only, without any new updates, thus safe
> to be applied now and all other changes built on top of it, unless there
> are further comments.
> 
> ---
> 
> This is preparatory work for send protocol update to version 2 and
> higher.
> 
> We have many pending protocol update requests but still don't have the
> basic protocol rev in place, the first thing that must happen is to do
> the actual versioning support.
> 
> The protocol version is u32 and is a new member in the send ioctl
> struct. Validity of the version field is backed by a new flag bit. Old
> kernels would fail when a higher version is requested. Version protocol
> 0 will pick the highest supported version, BTRFS_SEND_STREAM_VERSION,
> that's also exported in sysfs.
> 
> The version is still unchanged and will be increased once we have new
> incompatible commands or stream updates.
> 
> Signed-off-by: David Sterba <dsterba@suse.com>
> ---
>   fs/btrfs/send.c            | 22 ++++++++++++++++++++++
>   fs/btrfs/send.h            |  7 +++++++
>   include/uapi/linux/btrfs.h | 12 ++++++++++--
>   3 files changed, 39 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
> index afdcbe7844e0..28a26980245d 100644
> --- a/fs/btrfs/send.c
> +++ b/fs/btrfs/send.c
> @@ -84,6 +84,8 @@ struct send_ctx {
>   	u64 total_send_size;
>   	u64 cmd_send_size[BTRFS_SEND_C_MAX + 1];
>   	u64 flags;	/* 'flags' member of btrfs_ioctl_send_args is u64 */
> +	/* Protocol version compatibility requested */
> +	u32 proto;
>   
>   	struct btrfs_root *send_root;
>   	struct btrfs_root *parent_root;
> @@ -312,6 +314,15 @@ static void inconsistent_snapshot_error(struct send_ctx *sctx,
>   		   sctx->parent_root->root_key.objectid : 0));
>   }
>   
> +static bool proto_cmd_ok(const struct send_ctx *sctx, int cmd)
> +{
> +	switch (sctx->proto) {
> +	case 1:	 return cmd < __BTRFS_SEND_C_MAX_V1;
> +	case 2:	 return cmd < __BTRFS_SEND_C_MAX_V2;
> +	default: return false;
> +	}
> +}
> +
>   static int is_waiting_for_move(struct send_ctx *sctx, u64 ino);
>   
>   static struct waiting_dir_move *
> @@ -7269,6 +7280,17 @@ long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg)
>   
>   	sctx->flags = arg->flags;
>   
> +	if (arg->flags & BTRFS_SEND_FLAG_VERSION) {
> +		if (arg->version > BTRFS_SEND_STREAM_VERSION) {
> +			ret = -EPROTO;
> +			goto out;
> +		}
> +		/* Zero means "use the highest version" */
> +		sctx->proto = arg->version ?: BTRFS_SEND_STREAM_VERSION;
> +	} else {
> +		sctx->proto = 1;
> +	}
> +
>   	sctx->send_filp = fget(arg->send_fd);
>   	if (!sctx->send_filp) {
>   		ret = -EBADF;
> diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
> index de91488b7cd0..23bcefc84e49 100644
> --- a/fs/btrfs/send.h
> +++ b/fs/btrfs/send.h
> @@ -48,6 +48,7 @@ struct btrfs_tlv_header {
>   enum btrfs_send_cmd {
>   	BTRFS_SEND_C_UNSPEC,
>   
> +	/* Version 1 */
>   	BTRFS_SEND_C_SUBVOL,
>   	BTRFS_SEND_C_SNAPSHOT,
>   
> @@ -76,6 +77,12 @@ enum btrfs_send_cmd {
>   
>   	BTRFS_SEND_C_END,
>   	BTRFS_SEND_C_UPDATE_EXTENT,
> +	__BTRFS_SEND_C_MAX_V1,
> +
> +	/* Version 2 */
> +	__BTRFS_SEND_C_MAX_V2,
> +

  Isn't this has to be in tandem with BTRFS_SEND_STREAM_VERSION == 2?

  The changelog doesn't explain the plan of __BTRFS_SEND_C_MAX_Vx use case.

Thanks, Anand


> +	/* End */
>   	__BTRFS_SEND_C_MAX,
>   };
>   #define BTRFS_SEND_C_MAX (__BTRFS_SEND_C_MAX - 1)
> diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
> index d7d3cfead056..c1a665d87f61 100644
> --- a/include/uapi/linux/btrfs.h
> +++ b/include/uapi/linux/btrfs.h
> @@ -771,10 +771,16 @@ struct btrfs_ioctl_received_subvol_args {
>    */
>   #define BTRFS_SEND_FLAG_OMIT_END_CMD		0x4
>   
> +/*
> + * Read the protocol version in the structure
> + */
> +#define BTRFS_SEND_FLAG_VERSION			0x8
> +
>   #define BTRFS_SEND_FLAG_MASK \
>   	(BTRFS_SEND_FLAG_NO_FILE_DATA | \
>   	 BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
> -	 BTRFS_SEND_FLAG_OMIT_END_CMD)
> +	 BTRFS_SEND_FLAG_OMIT_END_CMD | \
> +	 BTRFS_SEND_FLAG_VERSION)
>   
>   struct btrfs_ioctl_send_args {
>   	__s64 send_fd;			/* in */
> @@ -782,7 +788,9 @@ struct btrfs_ioctl_send_args {
>   	__u64 __user *clone_sources;	/* in */
>   	__u64 parent_root;		/* in */
>   	__u64 flags;			/* in */
> -	__u64 reserved[4];		/* in */
> +	__u32 version;			/* in */
> +	__u32 reserved32;
> +	__u64 reserved[3];		/* in */
>   };
>   
>   /*
>
David Sterba Oct. 29, 2021, 4:03 p.m. UTC | #6
On Wed, Oct 27, 2021 at 03:17:46PM +0800, Anand Jain wrote:
> On 22/10/2021 22:53, David Sterba wrote:
> > --- a/fs/btrfs/send.h
> > +++ b/fs/btrfs/send.h
> > @@ -48,6 +48,7 @@ struct btrfs_tlv_header {
> >   enum btrfs_send_cmd {
> >   	BTRFS_SEND_C_UNSPEC,
> >   
> > +	/* Version 1 */
> >   	BTRFS_SEND_C_SUBVOL,
> >   	BTRFS_SEND_C_SNAPSHOT,
> >   
> > @@ -76,6 +77,12 @@ enum btrfs_send_cmd {
> >   
> >   	BTRFS_SEND_C_END,
> >   	BTRFS_SEND_C_UPDATE_EXTENT,
> > +	__BTRFS_SEND_C_MAX_V1,
> > +
> > +	/* Version 2 */
> > +	__BTRFS_SEND_C_MAX_V2,
> > +
> 
>   Isn't this has to be in tandem with BTRFS_SEND_STREAM_VERSION == 2?

The patch is only preparatory and adds the base code for the real
protocol updates where the STREAM_VERSION will be increased, but does
not do that yet so we don't falsely advertise v2 support when there's no
such thing.

>   The changelog doesn't explain the plan of __BTRFS_SEND_C_MAX_Vx use case.

It's supposed to prevent doubts and questions "what should I do when I
want to add v2 command" and make the code ready for anybody working on
the v2 update.
Anand Jain Oct. 30, 2021, 1:11 a.m. UTC | #7
> @@ -312,6 +314,15 @@ static void inconsistent_snapshot_error(struct send_ctx *sctx,
>   		   sctx->parent_root->root_key.objectid : 0));
>   }
>   
> +static bool proto_cmd_ok(const struct send_ctx *sctx, int cmd)

  (Now I understand the use of this function).

  2nd arg can be-  enum btrfs_send_cmd cmd


> @@ -771,10 +771,16 @@ struct btrfs_ioctl_received_subvol_args {
>    */
>   #define BTRFS_SEND_FLAG_OMIT_END_CMD		0x4
>   
> +/*
> + * Read the protocol version in the structure
> + */
> +#define BTRFS_SEND_FLAG_VERSION			0x8
> +
>   #define BTRFS_SEND_FLAG_MASK \
>   	(BTRFS_SEND_FLAG_NO_FILE_DATA | \
>   	 BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
> -	 BTRFS_SEND_FLAG_OMIT_END_CMD)
> +	 BTRFS_SEND_FLAG_OMIT_END_CMD | \
> +	 BTRFS_SEND_FLAG_VERSION)
>   


Kernel use BTRFS_SEND_FLAG_MASK like this:

long btrfs_ioctl_send()

<snip>

  if (arg->flags & ~BTRFS_SEND_FLAG_MASK) {
  ret = -EINVAL;
  goto out;
  }


So the newer btrfs-progs that sets BTRFS_SEND_FLAG_VERSION on an older 
kernel will fail.

So long we have had good backward compatibility of btrfs-progs with the 
older kernels, could we pls maintain that?

How about creating a new send IOCTL so that when the older kernel 
returns -ENOTTY and the newer-progs can fail back to the older send ioctl.

That is similar to what we did with

BTRFS_IOC_SNAP_CREATE_V2:
BTRFS_IOC_SUBVOL_CREATE_V2:
BTRFS_IOC_RM_DEV_V2:
BTRFS_IOC_TREE_SEARCH_V2:
BTRFS_IOC_LOGICAL_INO_V2:
BTRFS_IOC_BALANCE_V2:


Thanks, Anand
diff mbox series

Patch

diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c
index afdcbe7844e0..28a26980245d 100644
--- a/fs/btrfs/send.c
+++ b/fs/btrfs/send.c
@@ -84,6 +84,8 @@  struct send_ctx {
 	u64 total_send_size;
 	u64 cmd_send_size[BTRFS_SEND_C_MAX + 1];
 	u64 flags;	/* 'flags' member of btrfs_ioctl_send_args is u64 */
+	/* Protocol version compatibility requested */
+	u32 proto;
 
 	struct btrfs_root *send_root;
 	struct btrfs_root *parent_root;
@@ -312,6 +314,15 @@  static void inconsistent_snapshot_error(struct send_ctx *sctx,
 		   sctx->parent_root->root_key.objectid : 0));
 }
 
+static bool proto_cmd_ok(const struct send_ctx *sctx, int cmd)
+{
+	switch (sctx->proto) {
+	case 1:	 return cmd < __BTRFS_SEND_C_MAX_V1;
+	case 2:	 return cmd < __BTRFS_SEND_C_MAX_V2;
+	default: return false;
+	}
+}
+
 static int is_waiting_for_move(struct send_ctx *sctx, u64 ino);
 
 static struct waiting_dir_move *
@@ -7269,6 +7280,17 @@  long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg)
 
 	sctx->flags = arg->flags;
 
+	if (arg->flags & BTRFS_SEND_FLAG_VERSION) {
+		if (arg->version > BTRFS_SEND_STREAM_VERSION) {
+			ret = -EPROTO;
+			goto out;
+		}
+		/* Zero means "use the highest version" */
+		sctx->proto = arg->version ?: BTRFS_SEND_STREAM_VERSION;
+	} else {
+		sctx->proto = 1;
+	}
+
 	sctx->send_filp = fget(arg->send_fd);
 	if (!sctx->send_filp) {
 		ret = -EBADF;
diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h
index de91488b7cd0..23bcefc84e49 100644
--- a/fs/btrfs/send.h
+++ b/fs/btrfs/send.h
@@ -48,6 +48,7 @@  struct btrfs_tlv_header {
 enum btrfs_send_cmd {
 	BTRFS_SEND_C_UNSPEC,
 
+	/* Version 1 */
 	BTRFS_SEND_C_SUBVOL,
 	BTRFS_SEND_C_SNAPSHOT,
 
@@ -76,6 +77,12 @@  enum btrfs_send_cmd {
 
 	BTRFS_SEND_C_END,
 	BTRFS_SEND_C_UPDATE_EXTENT,
+	__BTRFS_SEND_C_MAX_V1,
+
+	/* Version 2 */
+	__BTRFS_SEND_C_MAX_V2,
+
+	/* End */
 	__BTRFS_SEND_C_MAX,
 };
 #define BTRFS_SEND_C_MAX (__BTRFS_SEND_C_MAX - 1)
diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index d7d3cfead056..c1a665d87f61 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -771,10 +771,16 @@  struct btrfs_ioctl_received_subvol_args {
  */
 #define BTRFS_SEND_FLAG_OMIT_END_CMD		0x4
 
+/*
+ * Read the protocol version in the structure
+ */
+#define BTRFS_SEND_FLAG_VERSION			0x8
+
 #define BTRFS_SEND_FLAG_MASK \
 	(BTRFS_SEND_FLAG_NO_FILE_DATA | \
 	 BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \
-	 BTRFS_SEND_FLAG_OMIT_END_CMD)
+	 BTRFS_SEND_FLAG_OMIT_END_CMD | \
+	 BTRFS_SEND_FLAG_VERSION)
 
 struct btrfs_ioctl_send_args {
 	__s64 send_fd;			/* in */
@@ -782,7 +788,9 @@  struct btrfs_ioctl_send_args {
 	__u64 __user *clone_sources;	/* in */
 	__u64 parent_root;		/* in */
 	__u64 flags;			/* in */
-	__u64 reserved[4];		/* in */
+	__u32 version;			/* in */
+	__u32 reserved32;
+	__u64 reserved[3];		/* in */
 };
 
 /*