Merge tag 'vfio-v5.15-rc1' of git://github.com/awilliam/linux-vfio
[linux-2.6-microblaze.git] / net / mptcp / options.c
index 7adcbc1..c41273c 100644 (file)
@@ -81,12 +81,11 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                 * is if both hosts in their SYNs set A=0."
                 */
                if (flags & MPTCP_CAP_CHECKSUM_REQD)
-                       mp_opt->csum_reqd = 1;
+                       mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
 
-               if (flags & MPTCP_CAP_DENY_JOIN_ID0)
-                       mp_opt->deny_join_id0 = 1;
+               mp_opt->deny_join_id0 = !!(flags & MPTCP_CAP_DENY_JOIN_ID0);
 
-               mp_opt->mp_capable = 1;
+               mp_opt->suboptions |= OPTIONS_MPTCP_MPC;
                if (opsize >= TCPOLEN_MPTCP_MPC_SYNACK) {
                        mp_opt->sndr_key = get_unaligned_be64(ptr);
                        ptr += 8;
@@ -101,7 +100,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                         * equivalent to those in a DSS option and can be used
                         * interchangeably."
                         */
-                       mp_opt->dss = 1;
+                       mp_opt->suboptions |= OPTION_MPTCP_DSS;
                        mp_opt->use_map = 1;
                        mp_opt->mpc_map = 1;
                        mp_opt->data_len = get_unaligned_be16(ptr);
@@ -109,7 +108,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                }
                if (opsize == TCPOLEN_MPTCP_MPC_ACK_DATA_CSUM) {
                        mp_opt->csum = (__force __sum16)get_unaligned_be16(ptr);
-                       mp_opt->csum_reqd = 1;
+                       mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
                        ptr += 2;
                }
                pr_debug("MP_CAPABLE version=%x, flags=%x, optlen=%d sndr=%llu, rcvr=%llu len=%d csum=%u",
@@ -118,7 +117,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                break;
 
        case MPTCPOPT_MP_JOIN:
-               mp_opt->mp_join = 1;
+               mp_opt->suboptions |= OPTIONS_MPTCP_MPJ;
                if (opsize == TCPOLEN_MPTCP_MPJ_SYN) {
                        mp_opt->backup = *ptr++ & MPTCPOPT_BACKUP;
                        mp_opt->join_id = *ptr++;
@@ -144,7 +143,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                        memcpy(mp_opt->hmac, ptr, MPTCPOPT_HMAC_LEN);
                        pr_debug("MP_JOIN hmac");
                } else {
-                       mp_opt->mp_join = 0;
+                       mp_opt->suboptions &= ~OPTIONS_MPTCP_MPJ;
                }
                break;
 
@@ -192,8 +191,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                    opsize != expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM)
                        break;
 
-               mp_opt->dss = 1;
-
+               mp_opt->suboptions |= OPTION_MPTCP_DSS;
                if (mp_opt->use_ack) {
                        if (mp_opt->ack64) {
                                mp_opt->data_ack = get_unaligned_be64(ptr);
@@ -222,14 +220,15 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                        ptr += 2;
 
                        if (opsize == expected_opsize + TCPOLEN_MPTCP_DSS_CHECKSUM) {
-                               mp_opt->csum_reqd = 1;
+                               mp_opt->suboptions |= OPTION_MPTCP_CSUMREQD;
                                mp_opt->csum = (__force __sum16)get_unaligned_be16(ptr);
                                ptr += 2;
                        }
 
                        pr_debug("data_seq=%llu subflow_seq=%u data_len=%u csum=%d:%u",
                                 mp_opt->data_seq, mp_opt->subflow_seq,
-                                mp_opt->data_len, mp_opt->csum_reqd, mp_opt->csum);
+                                mp_opt->data_len, !!(mp_opt->suboptions & OPTION_MPTCP_CSUMREQD),
+                                mp_opt->csum);
                }
 
                break;
@@ -260,8 +259,10 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                                break;
                }
 
-               mp_opt->add_addr = 1;
+               mp_opt->suboptions |= OPTION_MPTCP_ADD_ADDR;
                mp_opt->addr.id = *ptr++;
+               mp_opt->addr.port = 0;
+               mp_opt->ahmac = 0;
                if (mp_opt->addr.family == AF_INET) {
                        memcpy((u8 *)&mp_opt->addr.addr.s_addr, (u8 *)ptr, 4);
                        ptr += 4;
@@ -298,7 +299,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
 
                ptr++;
 
-               mp_opt->rm_addr = 1;
+               mp_opt->suboptions |= OPTION_MPTCP_RM_ADDR;
                mp_opt->rm_list.nr = opsize - TCPOLEN_MPTCP_RM_ADDR_BASE;
                for (i = 0; i < mp_opt->rm_list.nr; i++)
                        mp_opt->rm_list.ids[i] = *ptr++;
@@ -309,7 +310,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                if (opsize != TCPOLEN_MPTCP_PRIO)
                        break;
 
-               mp_opt->mp_prio = 1;
+               mp_opt->suboptions |= OPTION_MPTCP_PRIO;
                mp_opt->backup = *ptr++ & MPTCP_PRIO_BKUP;
                pr_debug("MP_PRIO: prio=%d", mp_opt->backup);
                break;
@@ -321,7 +322,7 @@ static void mptcp_parse_option(const struct sk_buff *skb,
                ptr += 2;
                mp_opt->rcvr_key = get_unaligned_be64(ptr);
                ptr += 8;
-               mp_opt->fastclose = 1;
+               mp_opt->suboptions |= OPTION_MPTCP_FASTCLOSE;
                break;
 
        case MPTCPOPT_RST:
@@ -330,12 +331,23 @@ static void mptcp_parse_option(const struct sk_buff *skb,
 
                if (!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_RST))
                        break;
-               mp_opt->reset = 1;
+
+               mp_opt->suboptions |= OPTION_MPTCP_RST;
                flags = *ptr++;
                mp_opt->reset_transient = flags & MPTCP_RST_TRANSIENT;
                mp_opt->reset_reason = *ptr;
                break;
 
+       case MPTCPOPT_MP_FAIL:
+               if (opsize != TCPOLEN_MPTCP_FAIL)
+                       break;
+
+               ptr += 2;
+               mp_opt->suboptions |= OPTION_MPTCP_FAIL;
+               mp_opt->fail_seq = get_unaligned_be64(ptr);
+               pr_debug("MP_FAIL: data_seq=%llu", mp_opt->fail_seq);
+               break;
+
        default:
                break;
        }
@@ -345,25 +357,12 @@ void mptcp_get_options(const struct sock *sk,
                       const struct sk_buff *skb,
                       struct mptcp_options_received *mp_opt)
 {
-       struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
-       struct mptcp_sock *msk = mptcp_sk(subflow->conn);
        const struct tcphdr *th = tcp_hdr(skb);
        const unsigned char *ptr;
        int length;
 
        /* initialize option status */
-       mp_opt->mp_capable = 0;
-       mp_opt->mp_join = 0;
-       mp_opt->add_addr = 0;
-       mp_opt->ahmac = 0;
-       mp_opt->fastclose = 0;
-       mp_opt->addr.port = 0;
-       mp_opt->rm_addr = 0;
-       mp_opt->dss = 0;
-       mp_opt->mp_prio = 0;
-       mp_opt->reset = 0;
-       mp_opt->csum_reqd = READ_ONCE(msk->csum_enabled);
-       mp_opt->deny_join_id0 = 0;
+       mp_opt->suboptions = 0;
 
        length = (th->doff * 4) - sizeof(struct tcphdr);
        ptr = (const unsigned char *)(th + 1);
@@ -592,6 +591,7 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb,
                dss_size = map_size;
                if (skb && snd_data_fin_enable)
                        mptcp_write_data_fin(subflow, skb, &opts->ext_copy);
+               opts->suboptions = OPTION_MPTCP_DSS;
                ret = true;
        }
 
@@ -615,6 +615,7 @@ static bool mptcp_established_options_dss(struct sock *sk, struct sk_buff *skb,
                opts->ext_copy.ack64 = 0;
        }
        opts->ext_copy.use_ack = 1;
+       opts->suboptions = OPTION_MPTCP_DSS;
        WRITE_ONCE(msk->old_wspace, __mptcp_space((struct sock *)msk));
 
        /* Add kind/length/subtype/flag overhead if mapping is not populated */
@@ -667,29 +668,34 @@ static bool mptcp_established_options_add_addr(struct sock *sk, struct sk_buff *
        bool port;
        int len;
 
-       if ((mptcp_pm_should_add_signal_ipv6(msk) ||
-            mptcp_pm_should_add_signal_port(msk) ||
-            mptcp_pm_should_add_signal_echo(msk)) &&
-           skb && skb_is_tcp_pure_ack(skb)) {
-               pr_debug("drop other suboptions");
-               opts->suboptions = 0;
-               opts->ext_copy.use_ack = 0;
-               opts->ext_copy.use_map = 0;
-               remaining += opt_size;
-               drop_other_suboptions = true;
-       }
-
+       /* add addr will strip the existing options, be sure to avoid breaking
+        * MPC/MPJ handshakes
+        */
        if (!mptcp_pm_should_add_signal(msk) ||
-           !(mptcp_pm_add_addr_signal(msk, remaining, &opts->addr, &echo, &port)))
+           (opts->suboptions & (OPTION_MPTCP_MPJ_ACK | OPTION_MPTCP_MPC_ACK)) ||
+           !mptcp_pm_add_addr_signal(msk, skb, opt_size, remaining, &opts->addr,
+                   &echo, &port, &drop_other_suboptions))
                return false;
 
+       if (drop_other_suboptions)
+               remaining += opt_size;
        len = mptcp_add_addr_len(opts->addr.family, echo, port);
        if (remaining < len)
                return false;
 
        *size = len;
-       if (drop_other_suboptions)
+       if (drop_other_suboptions) {
+               pr_debug("drop other suboptions");
+               opts->suboptions = 0;
+
+               /* note that e.g. DSS could have written into the memory
+                * aliased by ahmac, we must reset the field here
+                * to avoid appending the hmac even for ADD_ADDR echo
+                * options
+                */
+               opts->ahmac = 0;
                *size -= opt_size;
+       }
        opts->suboptions |= OPTION_MPTCP_ADD_ADDR;
        if (!echo) {
                opts->ahmac = add_addr_generate_hmac(msk->local_key,
@@ -739,7 +745,12 @@ static bool mptcp_established_options_mp_prio(struct sock *sk,
 {
        struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
 
-       if (!subflow->send_mp_prio)
+       /* can't send MP_PRIO with MPC, as they share the same option space:
+        * 'backup'. Also it makes no sense at all
+        */
+       if (!subflow->send_mp_prio ||
+           ((OPTION_MPTCP_MPC_SYN | OPTION_MPTCP_MPC_SYNACK |
+             OPTION_MPTCP_MPC_ACK) & opts->suboptions))
                return false;
 
        /* account for the trailing 'nop' option */
@@ -755,7 +766,7 @@ static bool mptcp_established_options_mp_prio(struct sock *sk,
        return true;
 }
 
-static noinline void mptcp_established_options_rst(struct sock *sk, struct sk_buff *skb,
+static noinline bool mptcp_established_options_rst(struct sock *sk, struct sk_buff *skb,
                                                   unsigned int *size,
                                                   unsigned int remaining,
                                                   struct mptcp_out_options *opts)
@@ -763,12 +774,36 @@ static noinline void mptcp_established_options_rst(struct sock *sk, struct sk_bu
        const struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
 
        if (remaining < TCPOLEN_MPTCP_RST)
-               return;
+               return false;
 
        *size = TCPOLEN_MPTCP_RST;
        opts->suboptions |= OPTION_MPTCP_RST;
        opts->reset_transient = subflow->reset_transient;
        opts->reset_reason = subflow->reset_reason;
+
+       return true;
+}
+
+static bool mptcp_established_options_mp_fail(struct sock *sk,
+                                             unsigned int *size,
+                                             unsigned int remaining,
+                                             struct mptcp_out_options *opts)
+{
+       struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk);
+
+       if (likely(!subflow->send_mp_fail))
+               return false;
+
+       if (remaining < TCPOLEN_MPTCP_FAIL)
+               return false;
+
+       *size = TCPOLEN_MPTCP_FAIL;
+       opts->suboptions |= OPTION_MPTCP_FAIL;
+       opts->fail_seq = subflow->map_seq;
+
+       pr_debug("MP_FAIL fail_seq=%llu", opts->fail_seq);
+
+       return true;
 }
 
 bool mptcp_established_options(struct sock *sk, struct sk_buff *skb,
@@ -787,15 +822,28 @@ bool mptcp_established_options(struct sock *sk, struct sk_buff *skb,
                return false;
 
        if (unlikely(skb && TCP_SKB_CB(skb)->tcp_flags & TCPHDR_RST)) {
-               mptcp_established_options_rst(sk, skb, size, remaining, opts);
+               if (mptcp_established_options_mp_fail(sk, &opt_size, remaining, opts)) {
+                       *size += opt_size;
+                       remaining -= opt_size;
+               }
+               if (mptcp_established_options_rst(sk, skb, &opt_size, remaining, opts)) {
+                       *size += opt_size;
+                       remaining -= opt_size;
+               }
                return true;
        }
 
        snd_data_fin = mptcp_data_fin_enabled(msk);
        if (mptcp_established_options_mp(sk, skb, snd_data_fin, &opt_size, remaining, opts))
                ret = true;
-       else if (mptcp_established_options_dss(sk, skb, snd_data_fin, &opt_size, remaining, opts))
+       else if (mptcp_established_options_dss(sk, skb, snd_data_fin, &opt_size, remaining, opts)) {
                ret = true;
+               if (mptcp_established_options_mp_fail(sk, &opt_size, remaining, opts)) {
+                       *size += opt_size;
+                       remaining -= opt_size;
+                       return true;
+               }
+       }
 
        /* we reserved enough space for the above options, and exceeding the
         * TCP option space would be fatal
@@ -868,7 +916,7 @@ static bool check_fully_established(struct mptcp_sock *msk, struct sock *ssk,
                 */
                if (TCP_SKB_CB(skb)->seq == subflow->ssn_offset + 1 &&
                    TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq &&
-                   subflow->mp_join && mp_opt->mp_join &&
+                   subflow->mp_join && (mp_opt->suboptions & OPTIONS_MPTCP_MPJ) &&
                    READ_ONCE(msk->pm.server_side))
                        tcp_send_ack(ssk);
                goto fully_established;
@@ -885,8 +933,8 @@ static bool check_fully_established(struct mptcp_sock *msk, struct sock *ssk,
                return subflow->mp_capable;
        }
 
-       if ((mp_opt->dss && mp_opt->use_ack) ||
-           (mp_opt->add_addr && !mp_opt->echo)) {
+       if (((mp_opt->suboptions & OPTION_MPTCP_DSS) && mp_opt->use_ack) ||
+           ((mp_opt->suboptions & OPTION_MPTCP_ADD_ADDR) && !mp_opt->echo)) {
                /* subflows are fully established as soon as we get any
                 * additional ack, including ADD_ADDR.
                 */
@@ -899,7 +947,7 @@ static bool check_fully_established(struct mptcp_sock *msk, struct sock *ssk,
         * then fallback to TCP. Fallback scenarios requires a reset for
         * MP_JOIN subflows.
         */
-       if (!mp_opt->mp_capable) {
+       if (!(mp_opt->suboptions & OPTIONS_MPTCP_MPC)) {
                if (subflow->mp_join)
                        goto reset;
                subflow->mp_capable = 0;
@@ -971,9 +1019,11 @@ static void ack_update_msk(struct mptcp_sock *msk,
        old_snd_una = msk->snd_una;
        new_snd_una = mptcp_expand_seq(old_snd_una, mp_opt->data_ack, mp_opt->ack64);
 
-       /* ACK for data not even sent yet? Ignore. */
-       if (after64(new_snd_una, snd_nxt))
-               new_snd_una = old_snd_una;
+       /* ACK for data not even sent yet and even above recovery bound? Ignore.*/
+       if (unlikely(after64(new_snd_una, snd_nxt))) {
+               if (!msk->recovery || after64(new_snd_una, msk->recovery_snd_nxt))
+                       new_snd_una = old_snd_una;
+       }
 
        new_wnd_end = new_snd_una + tcp_sk(ssk)->snd_wnd;
 
@@ -1061,48 +1111,51 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
        if (!check_fully_established(msk, sk, subflow, skb, &mp_opt))
                return sk->sk_state != TCP_CLOSE;
 
-       if (mp_opt.fastclose &&
-           msk->local_key == mp_opt.rcvr_key) {
-               WRITE_ONCE(msk->rcv_fastclose, true);
-               mptcp_schedule_work((struct sock *)msk);
-       }
+       if (unlikely(mp_opt.suboptions != OPTION_MPTCP_DSS)) {
+               if ((mp_opt.suboptions & OPTION_MPTCP_FASTCLOSE) &&
+                   msk->local_key == mp_opt.rcvr_key) {
+                       WRITE_ONCE(msk->rcv_fastclose, true);
+                       mptcp_schedule_work((struct sock *)msk);
+               }
 
-       if (mp_opt.add_addr && add_addr_hmac_valid(msk, &mp_opt)) {
-               if (!mp_opt.echo) {
-                       mptcp_pm_add_addr_received(msk, &mp_opt.addr);
-                       MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ADDADDR);
-               } else {
-                       mptcp_pm_add_addr_echoed(msk, &mp_opt.addr);
-                       mptcp_pm_del_add_timer(msk, &mp_opt.addr, true);
-                       MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ECHOADD);
+               if ((mp_opt.suboptions & OPTION_MPTCP_ADD_ADDR) &&
+                   add_addr_hmac_valid(msk, &mp_opt)) {
+                       if (!mp_opt.echo) {
+                               mptcp_pm_add_addr_received(msk, &mp_opt.addr);
+                               MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ADDADDR);
+                       } else {
+                               mptcp_pm_add_addr_echoed(msk, &mp_opt.addr);
+                               mptcp_pm_del_add_timer(msk, &mp_opt.addr, true);
+                               MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_ECHOADD);
+                       }
+
+                       if (mp_opt.addr.port)
+                               MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_PORTADD);
                }
 
-               if (mp_opt.addr.port)
-                       MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_PORTADD);
+               if (mp_opt.suboptions & OPTION_MPTCP_RM_ADDR)
+                       mptcp_pm_rm_addr_received(msk, &mp_opt.rm_list);
 
-               mp_opt.add_addr = 0;
-       }
+               if (mp_opt.suboptions & OPTION_MPTCP_PRIO) {
+                       mptcp_pm_mp_prio_received(sk, mp_opt.backup);
+                       MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPPRIORX);
+               }
 
-       if (mp_opt.rm_addr) {
-               mptcp_pm_rm_addr_received(msk, &mp_opt.rm_list);
-               mp_opt.rm_addr = 0;
-       }
+               if (mp_opt.suboptions & OPTION_MPTCP_FAIL) {
+                       mptcp_pm_mp_fail_received(sk, mp_opt.fail_seq);
+                       MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPFAILRX);
+               }
 
-       if (mp_opt.mp_prio) {
-               mptcp_pm_mp_prio_received(sk, mp_opt.backup);
-               MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPPRIORX);
-               mp_opt.mp_prio = 0;
-       }
+               if (mp_opt.suboptions & OPTION_MPTCP_RST) {
+                       subflow->reset_seen = 1;
+                       subflow->reset_reason = mp_opt.reset_reason;
+                       subflow->reset_transient = mp_opt.reset_transient;
+               }
 
-       if (mp_opt.reset) {
-               subflow->reset_seen = 1;
-               subflow->reset_reason = mp_opt.reset_reason;
-               subflow->reset_transient = mp_opt.reset_transient;
+               if (!(mp_opt.suboptions & OPTION_MPTCP_DSS))
+                       return true;
        }
 
-       if (!mp_opt.dss)
-               return true;
-
        /* we can't wait for recvmsg() to update the ack_seq, otherwise
         * monodirectional flows will stuck
         */
@@ -1129,7 +1182,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
 
        memset(mpext, 0, sizeof(*mpext));
 
-       if (mp_opt.use_map) {
+       if (likely(mp_opt.use_map)) {
                if (mp_opt.mpc_map) {
                        /* this is an MP_CAPABLE carrying MPTCP data
                         * we know this map the first chunk of data
@@ -1149,7 +1202,7 @@ bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb)
                }
                mpext->data_len = mp_opt.data_len;
                mpext->use_map = 1;
-               mpext->csum_reqd = mp_opt.csum_reqd;
+               mpext->csum_reqd = !!(mp_opt.suboptions & OPTION_MPTCP_CSUMREQD);
 
                if (mpext->csum_reqd)
                        mpext->csum = mp_opt.csum;
@@ -1196,8 +1249,88 @@ static u16 mptcp_make_csum(const struct mptcp_ext *mpext)
 void mptcp_write_options(__be32 *ptr, const struct tcp_sock *tp,
                         struct mptcp_out_options *opts)
 {
-       if ((OPTION_MPTCP_MPC_SYN | OPTION_MPTCP_MPC_SYNACK |
-            OPTION_MPTCP_MPC_ACK) & opts->suboptions) {
+       if (unlikely(OPTION_MPTCP_FAIL & opts->suboptions)) {
+               const struct sock *ssk = (const struct sock *)tp;
+               struct mptcp_subflow_context *subflow;
+
+               subflow = mptcp_subflow_ctx(ssk);
+               subflow->send_mp_fail = 0;
+
+               *ptr++ = mptcp_option(MPTCPOPT_MP_FAIL,
+                                     TCPOLEN_MPTCP_FAIL,
+                                     0, 0);
+               put_unaligned_be64(opts->fail_seq, ptr);
+               ptr += 2;
+       }
+
+       /* RST is mutually exclusive with everything else */
+       if (unlikely(OPTION_MPTCP_RST & opts->suboptions)) {
+               *ptr++ = mptcp_option(MPTCPOPT_RST,
+                                     TCPOLEN_MPTCP_RST,
+                                     opts->reset_transient,
+                                     opts->reset_reason);
+               return;
+       }
+
+       /* DSS, MPC, MPJ and ADD_ADDR are mutually exclusive, see
+        * mptcp_established_options*()
+        */
+       if (likely(OPTION_MPTCP_DSS & opts->suboptions)) {
+               struct mptcp_ext *mpext = &opts->ext_copy;
+               u8 len = TCPOLEN_MPTCP_DSS_BASE;
+               u8 flags = 0;
+
+               if (mpext->use_ack) {
+                       flags = MPTCP_DSS_HAS_ACK;
+                       if (mpext->ack64) {
+                               len += TCPOLEN_MPTCP_DSS_ACK64;
+                               flags |= MPTCP_DSS_ACK64;
+                       } else {
+                               len += TCPOLEN_MPTCP_DSS_ACK32;
+                       }
+               }
+
+               if (mpext->use_map) {
+                       len += TCPOLEN_MPTCP_DSS_MAP64;
+
+                       /* Use only 64-bit mapping flags for now, add
+                        * support for optional 32-bit mappings later.
+                        */
+                       flags |= MPTCP_DSS_HAS_MAP | MPTCP_DSS_DSN64;
+                       if (mpext->data_fin)
+                               flags |= MPTCP_DSS_DATA_FIN;
+
+                       if (opts->csum_reqd)
+                               len += TCPOLEN_MPTCP_DSS_CHECKSUM;
+               }
+
+               *ptr++ = mptcp_option(MPTCPOPT_DSS, len, 0, flags);
+
+               if (mpext->use_ack) {
+                       if (mpext->ack64) {
+                               put_unaligned_be64(mpext->data_ack, ptr);
+                               ptr += 2;
+                       } else {
+                               put_unaligned_be32(mpext->data_ack32, ptr);
+                               ptr += 1;
+                       }
+               }
+
+               if (mpext->use_map) {
+                       put_unaligned_be64(mpext->data_seq, ptr);
+                       ptr += 2;
+                       put_unaligned_be32(mpext->subflow_seq, ptr);
+                       ptr += 1;
+                       if (opts->csum_reqd) {
+                               put_unaligned_be32(mpext->data_len << 16 |
+                                                  mptcp_make_csum(mpext), ptr);
+                       } else {
+                               put_unaligned_be32(mpext->data_len << 16 |
+                                                  TCPOPT_NOP << 8 | TCPOPT_NOP, ptr);
+                       }
+               }
+       } else if ((OPTION_MPTCP_MPC_SYN | OPTION_MPTCP_MPC_SYNACK |
+                   OPTION_MPTCP_MPC_ACK) & opts->suboptions) {
                u8 len, flag = MPTCP_CAP_HMAC_SHA256;
 
                if (OPTION_MPTCP_MPC_SYN & opts->suboptions) {
@@ -1244,10 +1377,31 @@ void mptcp_write_options(__be32 *ptr, const struct tcp_sock *tp,
                                           TCPOPT_NOP << 8 | TCPOPT_NOP, ptr);
                }
                ptr += 1;
-       }
 
-mp_capable_done:
-       if (OPTION_MPTCP_ADD_ADDR & opts->suboptions) {
+               /* MPC is additionally mutually exclusive with MP_PRIO */
+               goto mp_capable_done;
+       } else if (OPTION_MPTCP_MPJ_SYN & opts->suboptions) {
+               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
+                                     TCPOLEN_MPTCP_MPJ_SYN,
+                                     opts->backup, opts->join_id);
+               put_unaligned_be32(opts->token, ptr);
+               ptr += 1;
+               put_unaligned_be32(opts->nonce, ptr);
+               ptr += 1;
+       } else if (OPTION_MPTCP_MPJ_SYNACK & opts->suboptions) {
+               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
+                                     TCPOLEN_MPTCP_MPJ_SYNACK,
+                                     opts->backup, opts->join_id);
+               put_unaligned_be64(opts->thmac, ptr);
+               ptr += 2;
+               put_unaligned_be32(opts->nonce, ptr);
+               ptr += 1;
+       } else if (OPTION_MPTCP_MPJ_ACK & opts->suboptions) {
+               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
+                                     TCPOLEN_MPTCP_MPJ_ACK, 0, 0);
+               memcpy(ptr, opts->hmac, MPTCPOPT_HMAC_LEN);
+               ptr += 5;
+       } else if (OPTION_MPTCP_ADD_ADDR & opts->suboptions) {
                u8 len = TCPOLEN_MPTCP_ADD_ADDR_BASE;
                u8 echo = MPTCP_ADDR_ECHO;
 
@@ -1305,6 +1459,19 @@ mp_capable_done:
                }
        }
 
+       if (OPTION_MPTCP_PRIO & opts->suboptions) {
+               const struct sock *ssk = (const struct sock *)tp;
+               struct mptcp_subflow_context *subflow;
+
+               subflow = mptcp_subflow_ctx(ssk);
+               subflow->send_mp_prio = 0;
+
+               *ptr++ = mptcp_option(MPTCPOPT_MP_PRIO,
+                                     TCPOLEN_MPTCP_PRIO,
+                                     opts->backup, TCPOPT_NOP);
+       }
+
+mp_capable_done:
        if (OPTION_MPTCP_RM_ADDR & opts->suboptions) {
                u8 i = 1;
 
@@ -1325,107 +1492,6 @@ mp_capable_done:
                }
        }
 
-       if (OPTION_MPTCP_PRIO & opts->suboptions) {
-               const struct sock *ssk = (const struct sock *)tp;
-               struct mptcp_subflow_context *subflow;
-
-               subflow = mptcp_subflow_ctx(ssk);
-               subflow->send_mp_prio = 0;
-
-               *ptr++ = mptcp_option(MPTCPOPT_MP_PRIO,
-                                     TCPOLEN_MPTCP_PRIO,
-                                     opts->backup, TCPOPT_NOP);
-       }
-
-       if (OPTION_MPTCP_MPJ_SYN & opts->suboptions) {
-               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
-                                     TCPOLEN_MPTCP_MPJ_SYN,
-                                     opts->backup, opts->join_id);
-               put_unaligned_be32(opts->token, ptr);
-               ptr += 1;
-               put_unaligned_be32(opts->nonce, ptr);
-               ptr += 1;
-       }
-
-       if (OPTION_MPTCP_MPJ_SYNACK & opts->suboptions) {
-               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
-                                     TCPOLEN_MPTCP_MPJ_SYNACK,
-                                     opts->backup, opts->join_id);
-               put_unaligned_be64(opts->thmac, ptr);
-               ptr += 2;
-               put_unaligned_be32(opts->nonce, ptr);
-               ptr += 1;
-       }
-
-       if (OPTION_MPTCP_MPJ_ACK & opts->suboptions) {
-               *ptr++ = mptcp_option(MPTCPOPT_MP_JOIN,
-                                     TCPOLEN_MPTCP_MPJ_ACK, 0, 0);
-               memcpy(ptr, opts->hmac, MPTCPOPT_HMAC_LEN);
-               ptr += 5;
-       }
-
-       if (OPTION_MPTCP_RST & opts->suboptions)
-               *ptr++ = mptcp_option(MPTCPOPT_RST,
-                                     TCPOLEN_MPTCP_RST,
-                                     opts->reset_transient,
-                                     opts->reset_reason);
-
-       if (opts->ext_copy.use_ack || opts->ext_copy.use_map) {
-               struct mptcp_ext *mpext = &opts->ext_copy;
-               u8 len = TCPOLEN_MPTCP_DSS_BASE;
-               u8 flags = 0;
-
-               if (mpext->use_ack) {
-                       flags = MPTCP_DSS_HAS_ACK;
-                       if (mpext->ack64) {
-                               len += TCPOLEN_MPTCP_DSS_ACK64;
-                               flags |= MPTCP_DSS_ACK64;
-                       } else {
-                               len += TCPOLEN_MPTCP_DSS_ACK32;
-                       }
-               }
-
-               if (mpext->use_map) {
-                       len += TCPOLEN_MPTCP_DSS_MAP64;
-
-                       /* Use only 64-bit mapping flags for now, add
-                        * support for optional 32-bit mappings later.
-                        */
-                       flags |= MPTCP_DSS_HAS_MAP | MPTCP_DSS_DSN64;
-                       if (mpext->data_fin)
-                               flags |= MPTCP_DSS_DATA_FIN;
-
-                       if (opts->csum_reqd)
-                               len += TCPOLEN_MPTCP_DSS_CHECKSUM;
-               }
-
-               *ptr++ = mptcp_option(MPTCPOPT_DSS, len, 0, flags);
-
-               if (mpext->use_ack) {
-                       if (mpext->ack64) {
-                               put_unaligned_be64(mpext->data_ack, ptr);
-                               ptr += 2;
-                       } else {
-                               put_unaligned_be32(mpext->data_ack32, ptr);
-                               ptr += 1;
-                       }
-               }
-
-               if (mpext->use_map) {
-                       put_unaligned_be64(mpext->data_seq, ptr);
-                       ptr += 2;
-                       put_unaligned_be32(mpext->subflow_seq, ptr);
-                       ptr += 1;
-                       if (opts->csum_reqd) {
-                               put_unaligned_be32(mpext->data_len << 16 |
-                                                  mptcp_make_csum(mpext), ptr);
-                       } else {
-                               put_unaligned_be32(mpext->data_len << 16 |
-                                                  TCPOPT_NOP << 8 | TCPOPT_NOP, ptr);
-                       }
-               }
-       }
-
        if (tp)
                mptcp_set_rwin(tp);
 }