diff mbox series

[mptcp-next] mptcp: add ADD_ADDR IPv6 support

Message ID 7d1d38ff0d05925e96ddb0782f8c5e3e1206590f.1600438707.git.geliangtang@gmail.com
State Superseded, archived
Headers show
Series [mptcp-next] mptcp: add ADD_ADDR IPv6 support | expand

Commit Message

Geliang Tang Sept. 18, 2020, 2:19 p.m. UTC
When ADD_ADDR suboption include an IPv6 address, the size is 28 octets.
It will not fit when other options are included, e.g. TCP Timestamps. So
here we send out a mptcp dedicated packet to carry only mptcp options.

Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/55
Signed-off-by: Geliang Tang <geliangtang@gmail.com>
---
 include/linux/tcp.h                           |  1 +
 net/ipv4/tcp_output.c                         | 14 ++++++++
 net/mptcp/options.c                           |  5 +--
 net/mptcp/pm.c                                | 22 +++++++++++--
 net/mptcp/protocol.h                          |  3 +-
 .../testing/selftests/net/mptcp/mptcp_join.sh | 33 ++++++++++++++++++-
 6 files changed, 72 insertions(+), 6 deletions(-)

Comments

Paolo Abeni Sept. 18, 2020, 2:54 p.m. UTC | #1
On Fri, 2020-09-18 at 22:19 +0800, Geliang Tang wrote:
> When ADD_ADDR suboption include an IPv6 address, the size is 28 octets.
> It will not fit when other options are included, e.g. TCP Timestamps. So
> here we send out a mptcp dedicated packet to carry only mptcp options.
> 
> Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/55
> Signed-off-by: Geliang Tang <geliangtang@gmail.com>
> ---
>  include/linux/tcp.h                           |  1 +
>  net/ipv4/tcp_output.c                         | 14 ++++++++
>  net/mptcp/options.c                           |  5 +--
>  net/mptcp/pm.c                                | 22 +++++++++++--
>  net/mptcp/protocol.h                          |  3 +-
>  .../testing/selftests/net/mptcp/mptcp_join.sh | 33 ++++++++++++++++++-
>  6 files changed, 72 insertions(+), 6 deletions(-)
> 
> diff --git a/include/linux/tcp.h b/include/linux/tcp.h
> index 2f87377e9af7..847442ac5b26 100644
> --- a/include/linux/tcp.h
> +++ b/include/linux/tcp.h
> @@ -392,6 +392,7 @@ struct tcp_sock {
>  			   */
>  #if IS_ENABLED(CONFIG_MPTCP)
>  	bool	is_mptcp;
> +	bool	mptcp_dedicated;
>  #endif

Uhmm... I would try to avoid adding more fields to tcp_sock, if
possible. Also additional hooking should be avoided, still if possible.

I think we could: 
1) change mptcp_established_options*() functions to accept 
  "struct tcp_out_options*" as the last argument, instead of
  "struct mptcp_out_options*".

2) add an 'skb' argument to mptcp_established_options_addr()

3) in mptcp_established_options_addr(), if the packet is a pure ack and
   the address to be announced is AF_INET6, drop the OPTION_TS bit from
   opts->options

Overall should be quite similar to this patch -
specifically, mptcp_pm_add_addr_signal will still send a pure ack
packet.

Side note, the above would offer the possibility of an interesting
follow-up: we could change the "tcp_out_options" struct "mptcp" field
from:
	struct mptcp_out_options mptcp;

to:
	struct mptcp_out_options *mptcp;

and init the pointer in mptcp_established_options() - the data could be
embedded in the subflow context structure, so no dynamic allocation.

That would remove the last misurable overhead we have on MPTCP enabled
build for plain TCP sockets, as tcp_out_options are zeroed for every
packet and 'struct mptcp_out_options' is quite big. (issue/15)


>  #if IS_ENABLED(CONFIG_SMC)
>  	bool	syn_smc;	/* SYN includes SMC */
> diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
> index 88f6872751d3..0089ed3969a0 100644
> --- a/net/ipv4/tcp_output.c
> +++ b/net/ipv4/tcp_output.c
> @@ -921,6 +921,20 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
>  	opts->options = 0;
>  
>  	*md5 = NULL;
> +
> +	if (sk_is_mptcp(sk) && tp->mptcp_dedicated) {
> +		unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
> +		unsigned int opt_size = 0;
> +
> +		if (mptcp_established_options(sk, skb, &opt_size, remaining,
> +					      &opts->mptcp)) {
> +			opts->options |= OPTION_MPTCP;
> +			size += opt_size;
> +		}
> +
> +		return size;
> +	}
> +
>  #ifdef CONFIG_TCP_MD5SIG
>  	if (static_branch_unlikely(&tcp_md5_needed) &&
>  	    rcu_access_pointer(tp->md5sig_info)) {
> diff --git a/net/mptcp/options.c b/net/mptcp/options.c
> index 171039cbe9c4..f0ca3ed4dba8 100644
> --- a/net/mptcp/options.c
> +++ b/net/mptcp/options.c
> @@ -243,7 +243,8 @@ static void mptcp_parse_option(const struct sk_buff *skb,
>  		mp_opt->add_addr = 1;
>  		mp_opt->port = 0;
>  		mp_opt->addr_id = *ptr++;
> -		pr_debug("ADD_ADDR: id=%d, echo=%d", mp_opt->addr_id, mp_opt->echo);
> +		pr_debug("ADD_ADDR%s: id=%d, echo=%d", (mp_opt->family == 6) ? "6" : "",
> +			 mp_opt->addr_id, mp_opt->echo);
>  		if (mp_opt->family == MPTCP_ADDR_IPVERSION_4) {
>  			memcpy((u8 *)&mp_opt->addr.s_addr, (u8 *)ptr, 4);
>  			ptr += 4;
> @@ -584,7 +585,7 @@ static bool mptcp_established_options_add_addr(struct sock *sk,
>  	int len;
>  
>  	if (!mptcp_pm_should_add_signal(msk) ||
> -	    !(mptcp_pm_add_addr_signal(msk, remaining, &saddr, &echo)))
> +	    !(mptcp_pm_add_addr_signal(msk, sk, remaining, &saddr, &echo)))
>  		return false;
>  
>  	len = mptcp_add_addr_len(saddr.family);
> diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c
> index 6ca88422e774..0b015f8bdd34 100644
> --- a/net/mptcp/pm.c
> +++ b/net/mptcp/pm.c
> @@ -170,11 +170,22 @@ void mptcp_pm_rm_addr_received(struct mptcp_sock *msk, u8 rm_id)
>  	spin_unlock_bh(&pm->lock);
>  }
>  
> +static void mptcp_send_dedicated_ack(struct sock *sk)
> +{
> +	struct tcp_sock *tp = tcp_sk(sk);
> +
> +	tp->mptcp_dedicated = true;
> +	tcp_send_ack(sk);
> +	tp->mptcp_dedicated = false;
> +}
> +
>  /* path manager helpers */
>  
> -bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> +bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *ssk,
> +			      unsigned int remaining,
>  			      struct mptcp_addr_info *saddr, bool *echo)
>  {
> +	bool add_addr6 = false;
>  	int ret = false;
>  
>  	spin_lock_bh(&msk->pm.lock);
> @@ -183,8 +194,11 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
>  	if (!mptcp_pm_should_add_signal(msk))
>  		goto out_unlock;
>  
> -	if (remaining < mptcp_add_addr_len(msk->pm.local.family))
> +	if (remaining < mptcp_add_addr_len(msk->pm.local.family)) {
> +		if (msk->pm.local.family == AF_INET6)
> +			add_addr6 = true;
>  		goto out_unlock;
> +	}
>  
>  	*saddr = msk->pm.local;
>  	*echo = READ_ONCE(msk->pm.add_addr_echo);
> @@ -193,6 +207,10 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
>  
>  out_unlock:
>  	spin_unlock_bh(&msk->pm.lock);
> +
> +	if (add_addr6)
> +		mptcp_send_dedicated_ack(ssk);
> +
>  	return ret;
>  }
>  
> diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
> index db1e5de2fee7..da662a436722 100644
> --- a/net/mptcp/protocol.h
> +++ b/net/mptcp/protocol.h
> @@ -468,7 +468,8 @@ static inline unsigned int mptcp_add_addr_len(int family)
>  	return TCPOLEN_MPTCP_ADD_ADDR6;
>  }
>  
> -bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> +bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *sk,
> +			      unsigned int remaining,
>  			      struct mptcp_addr_info *saddr, bool *echo);
>  bool mptcp_pm_rm_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
>  			     u8 *rm_id);
> diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
> index 08f53d86dedc..b37e63b04772 100755
> --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
> +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
> @@ -126,6 +126,12 @@ do_ping()
>  	fi
>  }
>  
> +# $1: IP address
> +is_v6()
> +{
> +	[ -z "${1##*:*}" ]
> +}
> +
>  do_transfer()
>  {
>  	listener_ns="$1"
> @@ -165,7 +171,15 @@ do_transfer()
>  		mptcp_connect="./mptcp_connect -r"
>  	fi
>  
> -	ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} 0.0.0.0 < "$sin" > "$sout" &
> +	local local_addr
> +	if is_v6 "${connect_addr}"; then
> +		local_addr="::"
> +		mptcp_connect="./mptcp_connect -r"
> +	else
> +		local_addr="0.0.0.0"
> +	fi
> +
> +	ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} ${local_addr} < "$sin" > "$sout" &
>  	spid=$!
>  
>  	sleep 1
> @@ -491,6 +505,23 @@ run_tests $ns1 $ns2 10.0.1.1
>  chk_join_nr "multiple subflows and signal" 3 3 3
>  chk_add_nr 1 1
>  
> +# subflow IPv6
> +reset
> +ip netns exec $ns1 ./pm_nl_ctl limits 0 1
> +ip netns exec $ns2 ./pm_nl_ctl limits 0 1
> +ip netns exec $ns2 ./pm_nl_ctl add dead:beef:3::2 flags subflow
> +run_tests $ns1 $ns2 dead:beef:1::1
> +chk_join_nr "single subflow IPv6" 1 1 1
> +
> +# signal address IPv6
> +reset
> +ip netns exec $ns1 ./pm_nl_ctl limits 0 1
> +ip netns exec $ns1 ./pm_nl_ctl add dead:beef:2::1 flags signal
> +ip netns exec $ns2 ./pm_nl_ctl limits 1 1
> +run_tests $ns1 $ns2 dead:beef:1::1
> +chk_join_nr "single address IPv6" 1 1 1
> +chk_add_nr 1 1

I think it would be nice testing even mixed scenarios: ipv4 msk with
ipv6 subflow[s] and vice versa (not sure if that would work ;)

Cheers,

Paolo
Geliang Tang Sept. 20, 2020, 2:59 p.m. UTC | #2
Hi Paolo,

Paolo Abeni <pabeni@redhat.com> 于2020年9月18日周五 下午10:55写道:
>
> On Fri, 2020-09-18 at 22:19 +0800, Geliang Tang wrote:
> > When ADD_ADDR suboption include an IPv6 address, the size is 28 octets.
> > It will not fit when other options are included, e.g. TCP Timestamps. So
> > here we send out a mptcp dedicated packet to carry only mptcp options.
> >
> > Closes: https://github.com/multipath-tcp/mptcp_net-next/issues/55
> > Signed-off-by: Geliang Tang <geliangtang@gmail.com>
> > ---
> >  include/linux/tcp.h                           |  1 +
> >  net/ipv4/tcp_output.c                         | 14 ++++++++
> >  net/mptcp/options.c                           |  5 +--
> >  net/mptcp/pm.c                                | 22 +++++++++++--
> >  net/mptcp/protocol.h                          |  3 +-
> >  .../testing/selftests/net/mptcp/mptcp_join.sh | 33 ++++++++++++++++++-
> >  6 files changed, 72 insertions(+), 6 deletions(-)
> >
> > diff --git a/include/linux/tcp.h b/include/linux/tcp.h
> > index 2f87377e9af7..847442ac5b26 100644
> > --- a/include/linux/tcp.h
> > +++ b/include/linux/tcp.h
> > @@ -392,6 +392,7 @@ struct tcp_sock {
> >                          */
> >  #if IS_ENABLED(CONFIG_MPTCP)
> >       bool    is_mptcp;
> > +     bool    mptcp_dedicated;
> >  #endif
>
> Uhmm... I would try to avoid adding more fields to tcp_sock, if
> possible. Also additional hooking should be avoided, still if possible.
>
> I think we could:
> 1) change mptcp_established_options*() functions to accept
>   "struct tcp_out_options*" as the last argument, instead of
>   "struct mptcp_out_options*".
>
> 2) add an 'skb' argument to mptcp_established_options_addr()
>
> 3) in mptcp_established_options_addr(), if the packet is a pure ack and
>    the address to be announced is AF_INET6, drop the OPTION_TS bit from
>    opts->options
>
> Overall should be quite similar to this patch -
> specifically, mptcp_pm_add_addr_signal will still send a pure ack
> packet.

Thanks for your review, fixed in patch v2.

>
> Side note, the above would offer the possibility of an interesting
> follow-up: we could change the "tcp_out_options" struct "mptcp" field
> from:
>         struct mptcp_out_options mptcp;
>
> to:
>         struct mptcp_out_options *mptcp;
>
> and init the pointer in mptcp_established_options() - the data could be
> embedded in the subflow context structure, so no dynamic allocation.
>
> That would remove the last misurable overhead we have on MPTCP enabled
> build for plain TCP sockets, as tcp_out_options are zeroed for every
> packet and 'struct mptcp_out_options' is quite big. (issue/15)

I'll try to do this later.

>
>
> >  #if IS_ENABLED(CONFIG_SMC)
> >       bool    syn_smc;        /* SYN includes SMC */
> > diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
> > index 88f6872751d3..0089ed3969a0 100644
> > --- a/net/ipv4/tcp_output.c
> > +++ b/net/ipv4/tcp_output.c
> > @@ -921,6 +921,20 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
> >       opts->options = 0;
> >
> >       *md5 = NULL;
> > +
> > +     if (sk_is_mptcp(sk) && tp->mptcp_dedicated) {
> > +             unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
> > +             unsigned int opt_size = 0;
> > +
> > +             if (mptcp_established_options(sk, skb, &opt_size, remaining,
> > +                                           &opts->mptcp)) {
> > +                     opts->options |= OPTION_MPTCP;
> > +                     size += opt_size;
> > +             }
> > +
> > +             return size;
> > +     }
> > +
> >  #ifdef CONFIG_TCP_MD5SIG
> >       if (static_branch_unlikely(&tcp_md5_needed) &&
> >           rcu_access_pointer(tp->md5sig_info)) {
> > diff --git a/net/mptcp/options.c b/net/mptcp/options.c
> > index 171039cbe9c4..f0ca3ed4dba8 100644
> > --- a/net/mptcp/options.c
> > +++ b/net/mptcp/options.c
> > @@ -243,7 +243,8 @@ static void mptcp_parse_option(const struct sk_buff *skb,
> >               mp_opt->add_addr = 1;
> >               mp_opt->port = 0;
> >               mp_opt->addr_id = *ptr++;
> > -             pr_debug("ADD_ADDR: id=%d, echo=%d", mp_opt->addr_id, mp_opt->echo);
> > +             pr_debug("ADD_ADDR%s: id=%d, echo=%d", (mp_opt->family == 6) ? "6" : "",
> > +                      mp_opt->addr_id, mp_opt->echo);
> >               if (mp_opt->family == MPTCP_ADDR_IPVERSION_4) {
> >                       memcpy((u8 *)&mp_opt->addr.s_addr, (u8 *)ptr, 4);
> >                       ptr += 4;
> > @@ -584,7 +585,7 @@ static bool mptcp_established_options_add_addr(struct sock *sk,
> >       int len;
> >
> >       if (!mptcp_pm_should_add_signal(msk) ||
> > -         !(mptcp_pm_add_addr_signal(msk, remaining, &saddr, &echo)))
> > +         !(mptcp_pm_add_addr_signal(msk, sk, remaining, &saddr, &echo)))
> >               return false;
> >
> >       len = mptcp_add_addr_len(saddr.family);
> > diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c
> > index 6ca88422e774..0b015f8bdd34 100644
> > --- a/net/mptcp/pm.c
> > +++ b/net/mptcp/pm.c
> > @@ -170,11 +170,22 @@ void mptcp_pm_rm_addr_received(struct mptcp_sock *msk, u8 rm_id)
> >       spin_unlock_bh(&pm->lock);
> >  }
> >
> > +static void mptcp_send_dedicated_ack(struct sock *sk)
> > +{
> > +     struct tcp_sock *tp = tcp_sk(sk);
> > +
> > +     tp->mptcp_dedicated = true;
> > +     tcp_send_ack(sk);
> > +     tp->mptcp_dedicated = false;
> > +}
> > +
> >  /* path manager helpers */
> >
> > -bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> > +bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *ssk,
> > +                           unsigned int remaining,
> >                             struct mptcp_addr_info *saddr, bool *echo)
> >  {
> > +     bool add_addr6 = false;
> >       int ret = false;
> >
> >       spin_lock_bh(&msk->pm.lock);
> > @@ -183,8 +194,11 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> >       if (!mptcp_pm_should_add_signal(msk))
> >               goto out_unlock;
> >
> > -     if (remaining < mptcp_add_addr_len(msk->pm.local.family))
> > +     if (remaining < mptcp_add_addr_len(msk->pm.local.family)) {
> > +             if (msk->pm.local.family == AF_INET6)
> > +                     add_addr6 = true;
> >               goto out_unlock;
> > +     }
> >
> >       *saddr = msk->pm.local;
> >       *echo = READ_ONCE(msk->pm.add_addr_echo);
> > @@ -193,6 +207,10 @@ bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> >
> >  out_unlock:
> >       spin_unlock_bh(&msk->pm.lock);
> > +
> > +     if (add_addr6)
> > +             mptcp_send_dedicated_ack(ssk);
> > +
> >       return ret;
> >  }
> >
> > diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
> > index db1e5de2fee7..da662a436722 100644
> > --- a/net/mptcp/protocol.h
> > +++ b/net/mptcp/protocol.h
> > @@ -468,7 +468,8 @@ static inline unsigned int mptcp_add_addr_len(int family)
> >       return TCPOLEN_MPTCP_ADD_ADDR6;
> >  }
> >
> > -bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> > +bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *sk,
> > +                           unsigned int remaining,
> >                             struct mptcp_addr_info *saddr, bool *echo);
> >  bool mptcp_pm_rm_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
> >                            u8 *rm_id);
> > diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
> > index 08f53d86dedc..b37e63b04772 100755
> > --- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
> > +++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
> > @@ -126,6 +126,12 @@ do_ping()
> >       fi
> >  }
> >
> > +# $1: IP address
> > +is_v6()
> > +{
> > +     [ -z "${1##*:*}" ]
> > +}
> > +
> >  do_transfer()
> >  {
> >       listener_ns="$1"
> > @@ -165,7 +171,15 @@ do_transfer()
> >               mptcp_connect="./mptcp_connect -r"
> >       fi
> >
> > -     ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} 0.0.0.0 < "$sin" > "$sout" &
> > +     local local_addr
> > +     if is_v6 "${connect_addr}"; then
> > +             local_addr="::"
> > +             mptcp_connect="./mptcp_connect -r"
> > +     else
> > +             local_addr="0.0.0.0"
> > +     fi
> > +
> > +     ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} ${local_addr} < "$sin" > "$sout" &
> >       spid=$!
> >
> >       sleep 1
> > @@ -491,6 +505,23 @@ run_tests $ns1 $ns2 10.0.1.1
> >  chk_join_nr "multiple subflows and signal" 3 3 3
> >  chk_add_nr 1 1
> >
> > +# subflow IPv6
> > +reset
> > +ip netns exec $ns1 ./pm_nl_ctl limits 0 1
> > +ip netns exec $ns2 ./pm_nl_ctl limits 0 1
> > +ip netns exec $ns2 ./pm_nl_ctl add dead:beef:3::2 flags subflow
> > +run_tests $ns1 $ns2 dead:beef:1::1
> > +chk_join_nr "single subflow IPv6" 1 1 1
> > +
> > +# signal address IPv6
> > +reset
> > +ip netns exec $ns1 ./pm_nl_ctl limits 0 1
> > +ip netns exec $ns1 ./pm_nl_ctl add dead:beef:2::1 flags signal
> > +ip netns exec $ns2 ./pm_nl_ctl limits 1 1
> > +run_tests $ns1 $ns2 dead:beef:1::1
> > +chk_join_nr "single address IPv6" 1 1 1
> > +chk_add_nr 1 1
>
> I think it would be nice testing even mixed scenarios: ipv4 msk with
> ipv6 subflow[s] and vice versa (not sure if that would work ;)

It's not work on mixed scenarios, I'll try to fix this later.

-Geliang

>
> Cheers,
>
> Paolo
> _______________________________________________
> mptcp mailing list -- mptcp@lists.01.org
> To unsubscribe send an email to mptcp-leave@lists.01.org
diff mbox series

Patch

diff --git a/include/linux/tcp.h b/include/linux/tcp.h
index 2f87377e9af7..847442ac5b26 100644
--- a/include/linux/tcp.h
+++ b/include/linux/tcp.h
@@ -392,6 +392,7 @@  struct tcp_sock {
 			   */
 #if IS_ENABLED(CONFIG_MPTCP)
 	bool	is_mptcp;
+	bool	mptcp_dedicated;
 #endif
 #if IS_ENABLED(CONFIG_SMC)
 	bool	syn_smc;	/* SYN includes SMC */
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index 88f6872751d3..0089ed3969a0 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -921,6 +921,20 @@  static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
 	opts->options = 0;
 
 	*md5 = NULL;
+
+	if (sk_is_mptcp(sk) && tp->mptcp_dedicated) {
+		unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
+		unsigned int opt_size = 0;
+
+		if (mptcp_established_options(sk, skb, &opt_size, remaining,
+					      &opts->mptcp)) {
+			opts->options |= OPTION_MPTCP;
+			size += opt_size;
+		}
+
+		return size;
+	}
+
 #ifdef CONFIG_TCP_MD5SIG
 	if (static_branch_unlikely(&tcp_md5_needed) &&
 	    rcu_access_pointer(tp->md5sig_info)) {
diff --git a/net/mptcp/options.c b/net/mptcp/options.c
index 171039cbe9c4..f0ca3ed4dba8 100644
--- a/net/mptcp/options.c
+++ b/net/mptcp/options.c
@@ -243,7 +243,8 @@  static void mptcp_parse_option(const struct sk_buff *skb,
 		mp_opt->add_addr = 1;
 		mp_opt->port = 0;
 		mp_opt->addr_id = *ptr++;
-		pr_debug("ADD_ADDR: id=%d, echo=%d", mp_opt->addr_id, mp_opt->echo);
+		pr_debug("ADD_ADDR%s: id=%d, echo=%d", (mp_opt->family == 6) ? "6" : "",
+			 mp_opt->addr_id, mp_opt->echo);
 		if (mp_opt->family == MPTCP_ADDR_IPVERSION_4) {
 			memcpy((u8 *)&mp_opt->addr.s_addr, (u8 *)ptr, 4);
 			ptr += 4;
@@ -584,7 +585,7 @@  static bool mptcp_established_options_add_addr(struct sock *sk,
 	int len;
 
 	if (!mptcp_pm_should_add_signal(msk) ||
-	    !(mptcp_pm_add_addr_signal(msk, remaining, &saddr, &echo)))
+	    !(mptcp_pm_add_addr_signal(msk, sk, remaining, &saddr, &echo)))
 		return false;
 
 	len = mptcp_add_addr_len(saddr.family);
diff --git a/net/mptcp/pm.c b/net/mptcp/pm.c
index 6ca88422e774..0b015f8bdd34 100644
--- a/net/mptcp/pm.c
+++ b/net/mptcp/pm.c
@@ -170,11 +170,22 @@  void mptcp_pm_rm_addr_received(struct mptcp_sock *msk, u8 rm_id)
 	spin_unlock_bh(&pm->lock);
 }
 
+static void mptcp_send_dedicated_ack(struct sock *sk)
+{
+	struct tcp_sock *tp = tcp_sk(sk);
+
+	tp->mptcp_dedicated = true;
+	tcp_send_ack(sk);
+	tp->mptcp_dedicated = false;
+}
+
 /* path manager helpers */
 
-bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
+bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *ssk,
+			      unsigned int remaining,
 			      struct mptcp_addr_info *saddr, bool *echo)
 {
+	bool add_addr6 = false;
 	int ret = false;
 
 	spin_lock_bh(&msk->pm.lock);
@@ -183,8 +194,11 @@  bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
 	if (!mptcp_pm_should_add_signal(msk))
 		goto out_unlock;
 
-	if (remaining < mptcp_add_addr_len(msk->pm.local.family))
+	if (remaining < mptcp_add_addr_len(msk->pm.local.family)) {
+		if (msk->pm.local.family == AF_INET6)
+			add_addr6 = true;
 		goto out_unlock;
+	}
 
 	*saddr = msk->pm.local;
 	*echo = READ_ONCE(msk->pm.add_addr_echo);
@@ -193,6 +207,10 @@  bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
 
 out_unlock:
 	spin_unlock_bh(&msk->pm.lock);
+
+	if (add_addr6)
+		mptcp_send_dedicated_ack(ssk);
+
 	return ret;
 }
 
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index db1e5de2fee7..da662a436722 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -468,7 +468,8 @@  static inline unsigned int mptcp_add_addr_len(int family)
 	return TCPOLEN_MPTCP_ADD_ADDR6;
 }
 
-bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
+bool mptcp_pm_add_addr_signal(struct mptcp_sock *msk, struct sock *sk,
+			      unsigned int remaining,
 			      struct mptcp_addr_info *saddr, bool *echo);
 bool mptcp_pm_rm_addr_signal(struct mptcp_sock *msk, unsigned int remaining,
 			     u8 *rm_id);
diff --git a/tools/testing/selftests/net/mptcp/mptcp_join.sh b/tools/testing/selftests/net/mptcp/mptcp_join.sh
index 08f53d86dedc..b37e63b04772 100755
--- a/tools/testing/selftests/net/mptcp/mptcp_join.sh
+++ b/tools/testing/selftests/net/mptcp/mptcp_join.sh
@@ -126,6 +126,12 @@  do_ping()
 	fi
 }
 
+# $1: IP address
+is_v6()
+{
+	[ -z "${1##*:*}" ]
+}
+
 do_transfer()
 {
 	listener_ns="$1"
@@ -165,7 +171,15 @@  do_transfer()
 		mptcp_connect="./mptcp_connect -r"
 	fi
 
-	ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} 0.0.0.0 < "$sin" > "$sout" &
+	local local_addr
+	if is_v6 "${connect_addr}"; then
+		local_addr="::"
+		mptcp_connect="./mptcp_connect -r"
+	else
+		local_addr="0.0.0.0"
+	fi
+
+	ip netns exec ${listener_ns} $mptcp_connect -t $timeout -l -p $port -s ${srv_proto} ${local_addr} < "$sin" > "$sout" &
 	spid=$!
 
 	sleep 1
@@ -491,6 +505,23 @@  run_tests $ns1 $ns2 10.0.1.1
 chk_join_nr "multiple subflows and signal" 3 3 3
 chk_add_nr 1 1
 
+# subflow IPv6
+reset
+ip netns exec $ns1 ./pm_nl_ctl limits 0 1
+ip netns exec $ns2 ./pm_nl_ctl limits 0 1
+ip netns exec $ns2 ./pm_nl_ctl add dead:beef:3::2 flags subflow
+run_tests $ns1 $ns2 dead:beef:1::1
+chk_join_nr "single subflow IPv6" 1 1 1
+
+# signal address IPv6
+reset
+ip netns exec $ns1 ./pm_nl_ctl limits 0 1
+ip netns exec $ns1 ./pm_nl_ctl add dead:beef:2::1 flags signal
+ip netns exec $ns2 ./pm_nl_ctl limits 1 1
+run_tests $ns1 $ns2 dead:beef:1::1
+chk_join_nr "single address IPv6" 1 1 1
+chk_add_nr 1 1
+
 # single subflow, remove
 reset
 ip netns exec $ns1 ./pm_nl_ctl limits 0 1