dccp: Process incoming Change feature-negotiation options
[deliverable/linux.git] / net / dccp / feat.c
index fe6d557328d696b53bdd57c5fd39c176f33ff698..283c7fb8c7dc09a44944982efba52f7c3b2f79c9 100644 (file)
@@ -237,6 +237,40 @@ static int dccp_feat_push_change(struct list_head *fn_list, u8 feat, u8 local,
        return 0;
 }
 
+/**
+ * dccp_feat_push_confirm  -  Add a Confirm entry to the FN list
+ * @fn_list: feature-negotiation list to add to
+ * @feat: one of %dccp_feature_numbers
+ * @local: whether local (1) or remote (0) @feat_num is being confirmed
+ * @fval: pointer to NN/SP value to be inserted or NULL
+ * Returns 0 on success, a Reset code for further processing otherwise.
+ */
+static int dccp_feat_push_confirm(struct list_head *fn_list, u8 feat, u8 local,
+                                 dccp_feat_val *fval)
+{
+       struct dccp_feat_entry *new = dccp_feat_entry_new(fn_list, feat, local);
+
+       if (new == NULL)
+               return DCCP_RESET_CODE_TOO_BUSY;
+
+       new->feat_num        = feat;
+       new->is_local        = local;
+       new->state           = FEAT_STABLE;     /* transition in 6.6.2 */
+       new->needs_confirm   = 1;
+       new->empty_confirm   = (fval == NULL);
+       new->val.nn          = 0;               /* zeroes the whole structure */
+       if (!new->empty_confirm)
+               new->val     = *fval;
+       new->needs_mandatory = 0;
+
+       return 0;
+}
+
+static int dccp_push_empty_confirm(struct list_head *fn_list, u8 feat, u8 local)
+{
+       return dccp_feat_push_confirm(fn_list, feat, local, NULL);
+}
+
 static inline void dccp_feat_list_pop(struct dccp_feat_entry *entry)
 {
        list_del(&entry->node);
@@ -955,7 +989,6 @@ static int dccp_feat_nn(struct sock *sk, u8 type, u8 feature, u8 *val, u8 len)
 
        return 0;
 }
-#endif /* (later) */
 
 static void dccp_feat_empty_confirm(struct dccp_minisock *dmsk,
                                    u8 type, u8 feature)
@@ -1066,6 +1099,7 @@ int dccp_feat_change_recv(struct sock *sk, u8 type, u8 feature, u8 *val, u8 len)
 }
 
 EXPORT_SYMBOL_GPL(dccp_feat_change_recv);
+#endif /* (later) */
 
 int dccp_feat_confirm_recv(struct sock *sk, u8 type, u8 feature,
                           u8 *val, u8 len)
@@ -1206,6 +1240,150 @@ out_clean:
 
 EXPORT_SYMBOL_GPL(dccp_feat_clone);
 
+/**
+ * dccp_feat_change_recv  -  Process incoming ChangeL/R options
+ * @fn: feature-negotiation list to update
+ * @is_mandatory: whether the Change was preceded by a Mandatory option
+ * @opt: %DCCPO_CHANGE_L or %DCCPO_CHANGE_R
+ * @feat: one of %dccp_feature_numbers
+ * @val: NN value or SP value/preference list
+ * @len: length of @val in bytes
+ * @server: whether this node is the server (1) or the client (0)
+ */
+static u8 dccp_feat_change_recv(struct list_head *fn, u8 is_mandatory, u8 opt,
+                               u8 feat, u8 *val, u8 len, const bool server)
+{
+       u8 defval, type = dccp_feat_type(feat);
+       const bool local = (opt == DCCPO_CHANGE_R);
+       struct dccp_feat_entry *entry;
+       dccp_feat_val fval;
+
+       if (len == 0 || type == FEAT_UNKNOWN)           /* 6.1 and 6.6.8 */
+               goto unknown_feature_or_value;
+
+       /*
+        *      Negotiation of NN features: Change R is invalid, so there is no
+        *      simultaneous negotiation; hence we do not look up in the list.
+        */
+       if (type == FEAT_NN) {
+               if (local || len > sizeof(fval.nn))
+                       goto unknown_feature_or_value;
+
+               /* 6.3.2: "The feature remote MUST accept any valid value..." */
+               fval.nn = dccp_decode_value_var(val, len);
+               if (!dccp_feat_is_valid_nn_val(feat, fval.nn))
+                       goto unknown_feature_or_value;
+
+               return dccp_feat_push_confirm(fn, feat, local, &fval);
+       }
+
+       /*
+        *      Unidirectional/simultaneous negotiation of SP features (6.3.1)
+        */
+       entry = dccp_feat_list_lookup(fn, feat, local);
+       if (entry == NULL) {
+               /*
+                * No particular preferences have been registered. We deal with
+                * this situation by assuming that all valid values are equally
+                * acceptable, and apply the following checks:
+                * - if the peer's list is a singleton, we accept a valid value;
+                * - if we are the server, we first try to see if the peer (the
+                *   client) advertises the default value. If yes, we use it,
+                *   otherwise we accept the preferred value;
+                * - else if we are the client, we use the first list element.
+                */
+               if (dccp_feat_clone_sp_val(&fval, val, 1))
+                       return DCCP_RESET_CODE_TOO_BUSY;
+
+               if (len > 1 && server) {
+                       defval = dccp_feat_default_value(feat);
+                       if (dccp_feat_preflist_match(&defval, 1, val, len) > -1)
+                               fval.sp.vec[0] = defval;
+               } else if (!dccp_feat_is_valid_sp_val(feat, fval.sp.vec[0])) {
+                       kfree(fval.sp.vec);
+                       goto unknown_feature_or_value;
+               }
+
+               /* Treat unsupported CCIDs like invalid values */
+               if (feat == DCCPF_CCID && !ccid_support_check(fval.sp.vec, 1)) {
+                       kfree(fval.sp.vec);
+                       goto not_valid_or_not_known;
+               }
+
+               return dccp_feat_push_confirm(fn, feat, local, &fval);
+
+       } else if (entry->state == FEAT_UNSTABLE) {     /* 6.6.2 */
+               return 0;
+       }
+
+       if (dccp_feat_reconcile(&entry->val, val, len, server, true)) {
+               entry->empty_confirm = 0;
+       } else if (is_mandatory) {
+               return DCCP_RESET_CODE_MANDATORY_ERROR;
+       } else if (entry->state == FEAT_INITIALISING) {
+               /*
+                * Failed simultaneous negotiation (server only): try to `save'
+                * the connection by checking whether entry contains the default
+                * value for @feat. If yes, send an empty Confirm to signal that
+                * the received Change was not understood - which implies using
+                * the default value.
+                * If this also fails, we use Reset as the last resort.
+                */
+               WARN_ON(!server);
+               defval = dccp_feat_default_value(feat);
+               if (!dccp_feat_reconcile(&entry->val, &defval, 1, server, true))
+                       return DCCP_RESET_CODE_OPTION_ERROR;
+               entry->empty_confirm = 1;
+       }
+       entry->needs_confirm   = 1;
+       entry->needs_mandatory = 0;
+       entry->state           = FEAT_STABLE;
+       return 0;
+
+unknown_feature_or_value:
+       if (!is_mandatory)
+               return dccp_push_empty_confirm(fn, feat, local);
+
+not_valid_or_not_known:
+       return is_mandatory ? DCCP_RESET_CODE_MANDATORY_ERROR
+                           : DCCP_RESET_CODE_OPTION_ERROR;
+}
+
+/**
+ * dccp_feat_parse_options  -  Process Feature-Negotiation Options
+ * @sk: for general use and used by the client during connection setup
+ * @dreq: used by the server during connection setup
+ * @mandatory: whether @opt was preceded by a Mandatory option
+ * @opt: %DCCPO_CHANGE_L | %DCCPO_CHANGE_R | %DCCPO_CONFIRM_L | %DCCPO_CONFIRM_R
+ * @feat: one of %dccp_feature_numbers
+ * @val: value contents of @opt
+ * @len: length of @val in bytes
+ * Returns 0 on success, a Reset code for ending the connection otherwise.
+ */
+int dccp_feat_parse_options(struct sock *sk, struct dccp_request_sock *dreq,
+                           u8 mandatory, u8 opt, u8 feat, u8 *val, u8 len)
+{
+       struct dccp_sock *dp = dccp_sk(sk);
+       struct list_head *fn = dreq ? &dreq->dreq_featneg : &dp->dccps_featneg;
+       bool server = false;
+
+       switch (sk->sk_state) {
+       /*
+        *      Negotiation during connection setup
+        */
+       case DCCP_LISTEN:
+               server = true;                  /* fall through */
+       case DCCP_REQUESTING:
+               switch (opt) {
+               case DCCPO_CHANGE_L:
+               case DCCPO_CHANGE_R:
+                       return dccp_feat_change_recv(fn, mandatory, opt, feat,
+                                                    val, len, server);
+               }
+       }
+       return 0;       /* ignore FN options in all other states */
+}
+
 int dccp_feat_init(struct sock *sk)
 {
        struct dccp_sock *dp = dccp_sk(sk);
This page took 0.032263 seconds and 5 git commands to generate.