diff options
Diffstat (limited to 'net/dccp/feat.c')
-rw-r--r-- | net/dccp/feat.c | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/net/dccp/feat.c b/net/dccp/feat.c new file mode 100644 index 000000000000..e3dd30d36c8a --- /dev/null +++ b/net/dccp/feat.c | |||
@@ -0,0 +1,586 @@ | |||
1 | /* | ||
2 | * net/dccp/feat.c | ||
3 | * | ||
4 | * An implementation of the DCCP protocol | ||
5 | * Andrea Bittau <a.bittau@cs.ucl.ac.uk> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public License | ||
9 | * as published by the Free Software Foundation; either version | ||
10 | * 2 of the License, or (at your option) any later version. | ||
11 | */ | ||
12 | |||
13 | #include <linux/config.h> | ||
14 | #include <linux/module.h> | ||
15 | |||
16 | #include "dccp.h" | ||
17 | #include "ccid.h" | ||
18 | #include "feat.h" | ||
19 | |||
20 | #define DCCP_FEAT_SP_NOAGREE (-123) | ||
21 | |||
22 | int dccp_feat_change(struct dccp_minisock *dmsk, u8 type, u8 feature, | ||
23 | u8 *val, u8 len, gfp_t gfp) | ||
24 | { | ||
25 | struct dccp_opt_pend *opt; | ||
26 | |||
27 | dccp_pr_debug("feat change type=%d feat=%d\n", type, feature); | ||
28 | |||
29 | /* XXX sanity check feat change request */ | ||
30 | |||
31 | /* check if that feature is already being negotiated */ | ||
32 | list_for_each_entry(opt, &dmsk->dccpms_pending, dccpop_node) { | ||
33 | /* ok we found a negotiation for this option already */ | ||
34 | if (opt->dccpop_feat == feature && opt->dccpop_type == type) { | ||
35 | dccp_pr_debug("Replacing old\n"); | ||
36 | /* replace */ | ||
37 | BUG_ON(opt->dccpop_val == NULL); | ||
38 | kfree(opt->dccpop_val); | ||
39 | opt->dccpop_val = val; | ||
40 | opt->dccpop_len = len; | ||
41 | opt->dccpop_conf = 0; | ||
42 | return 0; | ||
43 | } | ||
44 | } | ||
45 | |||
46 | /* negotiation for a new feature */ | ||
47 | opt = kmalloc(sizeof(*opt), gfp); | ||
48 | if (opt == NULL) | ||
49 | return -ENOMEM; | ||
50 | |||
51 | opt->dccpop_type = type; | ||
52 | opt->dccpop_feat = feature; | ||
53 | opt->dccpop_len = len; | ||
54 | opt->dccpop_val = val; | ||
55 | opt->dccpop_conf = 0; | ||
56 | opt->dccpop_sc = NULL; | ||
57 | |||
58 | BUG_ON(opt->dccpop_val == NULL); | ||
59 | |||
60 | list_add_tail(&opt->dccpop_node, &dmsk->dccpms_pending); | ||
61 | return 0; | ||
62 | } | ||
63 | |||
64 | EXPORT_SYMBOL_GPL(dccp_feat_change); | ||
65 | |||
66 | static int dccp_feat_update_ccid(struct sock *sk, u8 type, u8 new_ccid_nr) | ||
67 | { | ||
68 | struct dccp_sock *dp = dccp_sk(sk); | ||
69 | struct dccp_minisock *dmsk = dccp_msk(sk); | ||
70 | /* figure out if we are changing our CCID or the peer's */ | ||
71 | const int rx = type == DCCPO_CHANGE_R; | ||
72 | const u8 ccid_nr = rx ? dmsk->dccpms_rx_ccid : dmsk->dccpms_tx_ccid; | ||
73 | struct ccid *new_ccid; | ||
74 | |||
75 | /* Check if nothing is being changed. */ | ||
76 | if (ccid_nr == new_ccid_nr) | ||
77 | return 0; | ||
78 | |||
79 | new_ccid = ccid_new(new_ccid_nr, sk, rx, GFP_ATOMIC); | ||
80 | if (new_ccid == NULL) | ||
81 | return -ENOMEM; | ||
82 | |||
83 | if (rx) { | ||
84 | ccid_hc_rx_delete(dp->dccps_hc_rx_ccid, sk); | ||
85 | dp->dccps_hc_rx_ccid = new_ccid; | ||
86 | dmsk->dccpms_rx_ccid = new_ccid_nr; | ||
87 | } else { | ||
88 | ccid_hc_tx_delete(dp->dccps_hc_tx_ccid, sk); | ||
89 | dp->dccps_hc_tx_ccid = new_ccid; | ||
90 | dmsk->dccpms_tx_ccid = new_ccid_nr; | ||
91 | } | ||
92 | |||
93 | return 0; | ||
94 | } | ||
95 | |||
96 | /* XXX taking only u8 vals */ | ||
97 | static int dccp_feat_update(struct sock *sk, u8 type, u8 feat, u8 val) | ||
98 | { | ||
99 | dccp_pr_debug("changing [%d] feat %d to %d\n", type, feat, val); | ||
100 | |||
101 | switch (feat) { | ||
102 | case DCCPF_CCID: | ||
103 | return dccp_feat_update_ccid(sk, type, val); | ||
104 | default: | ||
105 | dccp_pr_debug("IMPLEMENT changing [%d] feat %d to %d\n", | ||
106 | type, feat, val); | ||
107 | break; | ||
108 | } | ||
109 | return 0; | ||
110 | } | ||
111 | |||
112 | static int dccp_feat_reconcile(struct sock *sk, struct dccp_opt_pend *opt, | ||
113 | u8 *rpref, u8 rlen) | ||
114 | { | ||
115 | struct dccp_sock *dp = dccp_sk(sk); | ||
116 | u8 *spref, slen, *res = NULL; | ||
117 | int i, j, rc, agree = 1; | ||
118 | |||
119 | BUG_ON(rpref == NULL); | ||
120 | |||
121 | /* check if we are the black sheep */ | ||
122 | if (dp->dccps_role == DCCP_ROLE_CLIENT) { | ||
123 | spref = rpref; | ||
124 | slen = rlen; | ||
125 | rpref = opt->dccpop_val; | ||
126 | rlen = opt->dccpop_len; | ||
127 | } else { | ||
128 | spref = opt->dccpop_val; | ||
129 | slen = opt->dccpop_len; | ||
130 | } | ||
131 | /* | ||
132 | * Now we have server preference list in spref and client preference in | ||
133 | * rpref | ||
134 | */ | ||
135 | BUG_ON(spref == NULL); | ||
136 | BUG_ON(rpref == NULL); | ||
137 | |||
138 | /* FIXME sanity check vals */ | ||
139 | |||
140 | /* Are values in any order? XXX Lame "algorithm" here */ | ||
141 | /* XXX assume values are 1 byte */ | ||
142 | for (i = 0; i < slen; i++) { | ||
143 | for (j = 0; j < rlen; j++) { | ||
144 | if (spref[i] == rpref[j]) { | ||
145 | res = &spref[i]; | ||
146 | break; | ||
147 | } | ||
148 | } | ||
149 | if (res) | ||
150 | break; | ||
151 | } | ||
152 | |||
153 | /* we didn't agree on anything */ | ||
154 | if (res == NULL) { | ||
155 | /* confirm previous value */ | ||
156 | switch (opt->dccpop_feat) { | ||
157 | case DCCPF_CCID: | ||
158 | /* XXX did i get this right? =P */ | ||
159 | if (opt->dccpop_type == DCCPO_CHANGE_L) | ||
160 | res = &dccp_msk(sk)->dccpms_tx_ccid; | ||
161 | else | ||
162 | res = &dccp_msk(sk)->dccpms_rx_ccid; | ||
163 | break; | ||
164 | |||
165 | default: | ||
166 | WARN_ON(1); /* XXX implement res */ | ||
167 | return -EFAULT; | ||
168 | } | ||
169 | |||
170 | dccp_pr_debug("Don't agree... reconfirming %d\n", *res); | ||
171 | agree = 0; /* this is used for mandatory options... */ | ||
172 | } | ||
173 | |||
174 | /* need to put result and our preference list */ | ||
175 | /* XXX assume 1 byte vals */ | ||
176 | rlen = 1 + opt->dccpop_len; | ||
177 | rpref = kmalloc(rlen, GFP_ATOMIC); | ||
178 | if (rpref == NULL) | ||
179 | return -ENOMEM; | ||
180 | |||
181 | *rpref = *res; | ||
182 | memcpy(&rpref[1], opt->dccpop_val, opt->dccpop_len); | ||
183 | |||
184 | /* put it in the "confirm queue" */ | ||
185 | if (opt->dccpop_sc == NULL) { | ||
186 | opt->dccpop_sc = kmalloc(sizeof(*opt->dccpop_sc), GFP_ATOMIC); | ||
187 | if (opt->dccpop_sc == NULL) { | ||
188 | kfree(rpref); | ||
189 | return -ENOMEM; | ||
190 | } | ||
191 | } else { | ||
192 | /* recycle the confirm slot */ | ||
193 | BUG_ON(opt->dccpop_sc->dccpoc_val == NULL); | ||
194 | kfree(opt->dccpop_sc->dccpoc_val); | ||
195 | dccp_pr_debug("recycling confirm slot\n"); | ||
196 | } | ||
197 | memset(opt->dccpop_sc, 0, sizeof(*opt->dccpop_sc)); | ||
198 | |||
199 | opt->dccpop_sc->dccpoc_val = rpref; | ||
200 | opt->dccpop_sc->dccpoc_len = rlen; | ||
201 | |||
202 | /* update the option on our side [we are about to send the confirm] */ | ||
203 | rc = dccp_feat_update(sk, opt->dccpop_type, opt->dccpop_feat, *res); | ||
204 | if (rc) { | ||
205 | kfree(opt->dccpop_sc->dccpoc_val); | ||
206 | kfree(opt->dccpop_sc); | ||
207 | opt->dccpop_sc = 0; | ||
208 | return rc; | ||
209 | } | ||
210 | |||
211 | dccp_pr_debug("Will confirm %d\n", *rpref); | ||
212 | |||
213 | /* say we want to change to X but we just got a confirm X, suppress our | ||
214 | * change | ||
215 | */ | ||
216 | if (!opt->dccpop_conf) { | ||
217 | if (*opt->dccpop_val == *res) | ||
218 | opt->dccpop_conf = 1; | ||
219 | dccp_pr_debug("won't ask for change of same feature\n"); | ||
220 | } | ||
221 | |||
222 | return agree ? 0 : DCCP_FEAT_SP_NOAGREE; /* used for mandatory opts */ | ||
223 | } | ||
224 | |||
225 | static int dccp_feat_sp(struct sock *sk, u8 type, u8 feature, u8 *val, u8 len) | ||
226 | { | ||
227 | struct dccp_minisock *dmsk = dccp_msk(sk); | ||
228 | struct dccp_opt_pend *opt; | ||
229 | int rc = 1; | ||
230 | u8 t; | ||
231 | |||
232 | /* | ||
233 | * We received a CHANGE. We gotta match it against our own preference | ||
234 | * list. If we got a CHANGE_R it means it's a change for us, so we need | ||
235 | * to compare our CHANGE_L list. | ||
236 | */ | ||
237 | if (type == DCCPO_CHANGE_L) | ||
238 | t = DCCPO_CHANGE_R; | ||
239 | else | ||
240 | t = DCCPO_CHANGE_L; | ||
241 | |||
242 | /* find our preference list for this feature */ | ||
243 | list_for_each_entry(opt, &dmsk->dccpms_pending, dccpop_node) { | ||
244 | if (opt->dccpop_type != t || opt->dccpop_feat != feature) | ||
245 | continue; | ||
246 | |||
247 | /* find the winner from the two preference lists */ | ||
248 | rc = dccp_feat_reconcile(sk, opt, val, len); | ||
249 | break; | ||
250 | } | ||
251 | |||
252 | /* We didn't deal with the change. This can happen if we have no | ||
253 | * preference list for the feature. In fact, it just shouldn't | ||
254 | * happen---if we understand a feature, we should have a preference list | ||
255 | * with at least the default value. | ||
256 | */ | ||
257 | BUG_ON(rc == 1); | ||
258 | |||
259 | return rc; | ||
260 | } | ||
261 | |||
262 | static int dccp_feat_nn(struct sock *sk, u8 type, u8 feature, u8 *val, u8 len) | ||
263 | { | ||
264 | struct dccp_opt_pend *opt; | ||
265 | struct dccp_minisock *dmsk = dccp_msk(sk); | ||
266 | u8 *copy; | ||
267 | int rc; | ||
268 | |||
269 | /* NN features must be change L */ | ||
270 | if (type == DCCPO_CHANGE_R) { | ||
271 | dccp_pr_debug("received CHANGE_R %d for NN feat %d\n", | ||
272 | type, feature); | ||
273 | return -EFAULT; | ||
274 | } | ||
275 | |||
276 | /* XXX sanity check opt val */ | ||
277 | |||
278 | /* copy option so we can confirm it */ | ||
279 | opt = kzalloc(sizeof(*opt), GFP_ATOMIC); | ||
280 | if (opt == NULL) | ||
281 | return -ENOMEM; | ||
282 | |||
283 | copy = kmalloc(len, GFP_ATOMIC); | ||
284 | if (copy == NULL) { | ||
285 | kfree(opt); | ||
286 | return -ENOMEM; | ||
287 | } | ||
288 | memcpy(copy, val, len); | ||
289 | |||
290 | opt->dccpop_type = DCCPO_CONFIRM_R; /* NN can only confirm R */ | ||
291 | opt->dccpop_feat = feature; | ||
292 | opt->dccpop_val = copy; | ||
293 | opt->dccpop_len = len; | ||
294 | |||
295 | /* change feature */ | ||
296 | rc = dccp_feat_update(sk, type, feature, *val); | ||
297 | if (rc) { | ||
298 | kfree(opt->dccpop_val); | ||
299 | kfree(opt); | ||
300 | return rc; | ||
301 | } | ||
302 | |||
303 | dccp_pr_debug("Confirming NN feature %d (val=%d)\n", feature, *copy); | ||
304 | list_add_tail(&opt->dccpop_node, &dmsk->dccpms_conf); | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static void dccp_feat_empty_confirm(struct dccp_minisock *dmsk, | ||
310 | u8 type, u8 feature) | ||
311 | { | ||
312 | /* XXX check if other confirms for that are queued and recycle slot */ | ||
313 | struct dccp_opt_pend *opt = kzalloc(sizeof(*opt), GFP_ATOMIC); | ||
314 | |||
315 | if (opt == NULL) { | ||
316 | /* XXX what do we do? Ignoring should be fine. It's a change | ||
317 | * after all =P | ||
318 | */ | ||
319 | return; | ||
320 | } | ||
321 | |||
322 | opt->dccpop_type = type == DCCPO_CHANGE_L ? DCCPO_CONFIRM_R : | ||
323 | DCCPO_CONFIRM_L; | ||
324 | opt->dccpop_feat = feature; | ||
325 | opt->dccpop_val = 0; | ||
326 | opt->dccpop_len = 0; | ||
327 | |||
328 | /* change feature */ | ||
329 | dccp_pr_debug("Empty confirm feature %d type %d\n", feature, type); | ||
330 | list_add_tail(&opt->dccpop_node, &dmsk->dccpms_conf); | ||
331 | } | ||
332 | |||
333 | static void dccp_feat_flush_confirm(struct sock *sk) | ||
334 | { | ||
335 | struct dccp_minisock *dmsk = dccp_msk(sk); | ||
336 | /* Check if there is anything to confirm in the first place */ | ||
337 | int yes = !list_empty(&dmsk->dccpms_conf); | ||
338 | |||
339 | if (!yes) { | ||
340 | struct dccp_opt_pend *opt; | ||
341 | |||
342 | list_for_each_entry(opt, &dmsk->dccpms_pending, dccpop_node) { | ||
343 | if (opt->dccpop_conf) { | ||
344 | yes = 1; | ||
345 | break; | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
350 | if (!yes) | ||
351 | return; | ||
352 | |||
353 | /* OK there is something to confirm... */ | ||
354 | /* XXX check if packet is in flight? Send delayed ack?? */ | ||
355 | if (sk->sk_state == DCCP_OPEN) | ||
356 | dccp_send_ack(sk); | ||
357 | } | ||
358 | |||
359 | int dccp_feat_change_recv(struct sock *sk, u8 type, u8 feature, u8 *val, u8 len) | ||
360 | { | ||
361 | int rc; | ||
362 | |||
363 | dccp_pr_debug("got feat change type=%d feat=%d\n", type, feature); | ||
364 | |||
365 | /* figure out if it's SP or NN feature */ | ||
366 | switch (feature) { | ||
367 | /* deal with SP features */ | ||
368 | case DCCPF_CCID: | ||
369 | rc = dccp_feat_sp(sk, type, feature, val, len); | ||
370 | break; | ||
371 | |||
372 | /* deal with NN features */ | ||
373 | case DCCPF_ACK_RATIO: | ||
374 | rc = dccp_feat_nn(sk, type, feature, val, len); | ||
375 | break; | ||
376 | |||
377 | /* XXX implement other features */ | ||
378 | default: | ||
379 | rc = -EFAULT; | ||
380 | break; | ||
381 | } | ||
382 | |||
383 | /* check if there were problems changing features */ | ||
384 | if (rc) { | ||
385 | /* If we don't agree on SP, we sent a confirm for old value. | ||
386 | * However we propagate rc to caller in case option was | ||
387 | * mandatory | ||
388 | */ | ||
389 | if (rc != DCCP_FEAT_SP_NOAGREE) | ||
390 | dccp_feat_empty_confirm(dccp_msk(sk), type, feature); | ||
391 | } | ||
392 | |||
393 | /* generate the confirm [if required] */ | ||
394 | dccp_feat_flush_confirm(sk); | ||
395 | |||
396 | return rc; | ||
397 | } | ||
398 | |||
399 | EXPORT_SYMBOL_GPL(dccp_feat_change_recv); | ||
400 | |||
401 | int dccp_feat_confirm_recv(struct sock *sk, u8 type, u8 feature, | ||
402 | u8 *val, u8 len) | ||
403 | { | ||
404 | u8 t; | ||
405 | struct dccp_opt_pend *opt; | ||
406 | struct dccp_minisock *dmsk = dccp_msk(sk); | ||
407 | int rc = 1; | ||
408 | int all_confirmed = 1; | ||
409 | |||
410 | dccp_pr_debug("got feat confirm type=%d feat=%d\n", type, feature); | ||
411 | |||
412 | /* XXX sanity check type & feat */ | ||
413 | |||
414 | /* locate our change request */ | ||
415 | t = type == DCCPO_CONFIRM_L ? DCCPO_CHANGE_R : DCCPO_CHANGE_L; | ||
416 | |||
417 | list_for_each_entry(opt, &dmsk->dccpms_pending, dccpop_node) { | ||
418 | if (!opt->dccpop_conf && opt->dccpop_type == t && | ||
419 | opt->dccpop_feat == feature) { | ||
420 | /* we found it */ | ||
421 | /* XXX do sanity check */ | ||
422 | |||
423 | opt->dccpop_conf = 1; | ||
424 | |||
425 | /* We got a confirmation---change the option */ | ||
426 | dccp_feat_update(sk, opt->dccpop_type, | ||
427 | opt->dccpop_feat, *val); | ||
428 | |||
429 | dccp_pr_debug("feat %d type %d confirmed %d\n", | ||
430 | feature, type, *val); | ||
431 | rc = 0; | ||
432 | break; | ||
433 | } | ||
434 | |||
435 | if (!opt->dccpop_conf) | ||
436 | all_confirmed = 0; | ||
437 | } | ||
438 | |||
439 | /* fix re-transmit timer */ | ||
440 | /* XXX gotta make sure that no option negotiation occurs during | ||
441 | * connection shutdown. Consider that the CLOSEREQ is sent and timer is | ||
442 | * on. if all options are confirmed it might kill timer which should | ||
443 | * remain alive until close is received. | ||
444 | */ | ||
445 | if (all_confirmed) { | ||
446 | dccp_pr_debug("clear feat negotiation timer %p\n", sk); | ||
447 | inet_csk_clear_xmit_timer(sk, ICSK_TIME_RETRANS); | ||
448 | } | ||
449 | |||
450 | if (rc) | ||
451 | dccp_pr_debug("feat %d type %d never requested\n", | ||
452 | feature, type); | ||
453 | return 0; | ||
454 | } | ||
455 | |||
456 | EXPORT_SYMBOL_GPL(dccp_feat_confirm_recv); | ||
457 | |||
458 | void dccp_feat_clean(struct dccp_minisock *dmsk) | ||
459 | { | ||
460 | struct dccp_opt_pend *opt, *next; | ||
461 | |||
462 | list_for_each_entry_safe(opt, next, &dmsk->dccpms_pending, | ||
463 | dccpop_node) { | ||
464 | BUG_ON(opt->dccpop_val == NULL); | ||
465 | kfree(opt->dccpop_val); | ||
466 | |||
467 | if (opt->dccpop_sc != NULL) { | ||
468 | BUG_ON(opt->dccpop_sc->dccpoc_val == NULL); | ||
469 | kfree(opt->dccpop_sc->dccpoc_val); | ||
470 | kfree(opt->dccpop_sc); | ||
471 | } | ||
472 | |||
473 | kfree(opt); | ||
474 | } | ||
475 | INIT_LIST_HEAD(&dmsk->dccpms_pending); | ||
476 | |||
477 | list_for_each_entry_safe(opt, next, &dmsk->dccpms_conf, dccpop_node) { | ||
478 | BUG_ON(opt == NULL); | ||
479 | if (opt->dccpop_val != NULL) | ||
480 | kfree(opt->dccpop_val); | ||
481 | kfree(opt); | ||
482 | } | ||
483 | INIT_LIST_HEAD(&dmsk->dccpms_conf); | ||
484 | } | ||
485 | |||
486 | EXPORT_SYMBOL_GPL(dccp_feat_clean); | ||
487 | |||
488 | /* this is to be called only when a listening sock creates its child. It is | ||
489 | * assumed by the function---the confirm is not duplicated, but rather it is | ||
490 | * "passed on". | ||
491 | */ | ||
492 | int dccp_feat_clone(struct sock *oldsk, struct sock *newsk) | ||
493 | { | ||
494 | struct dccp_minisock *olddmsk = dccp_msk(oldsk); | ||
495 | struct dccp_minisock *newdmsk = dccp_msk(newsk); | ||
496 | struct dccp_opt_pend *opt; | ||
497 | int rc = 0; | ||
498 | |||
499 | INIT_LIST_HEAD(&newdmsk->dccpms_pending); | ||
500 | INIT_LIST_HEAD(&newdmsk->dccpms_conf); | ||
501 | |||
502 | list_for_each_entry(opt, &olddmsk->dccpms_pending, dccpop_node) { | ||
503 | struct dccp_opt_pend *newopt; | ||
504 | /* copy the value of the option */ | ||
505 | u8 *val = kmalloc(opt->dccpop_len, GFP_ATOMIC); | ||
506 | |||
507 | if (val == NULL) | ||
508 | goto out_clean; | ||
509 | memcpy(val, opt->dccpop_val, opt->dccpop_len); | ||
510 | |||
511 | newopt = kmalloc(sizeof(*newopt), GFP_ATOMIC); | ||
512 | if (newopt == NULL) { | ||
513 | kfree(val); | ||
514 | goto out_clean; | ||
515 | } | ||
516 | |||
517 | /* insert the option */ | ||
518 | memcpy(newopt, opt, sizeof(*newopt)); | ||
519 | newopt->dccpop_val = val; | ||
520 | list_add_tail(&newopt->dccpop_node, &newdmsk->dccpms_pending); | ||
521 | |||
522 | /* XXX what happens with backlogs and multiple connections at | ||
523 | * once... | ||
524 | */ | ||
525 | /* the master socket no longer needs to worry about confirms */ | ||
526 | opt->dccpop_sc = 0; /* it's not a memleak---new socket has it */ | ||
527 | |||
528 | /* reset state for a new socket */ | ||
529 | opt->dccpop_conf = 0; | ||
530 | } | ||
531 | |||
532 | /* XXX not doing anything about the conf queue */ | ||
533 | |||
534 | out: | ||
535 | return rc; | ||
536 | |||
537 | out_clean: | ||
538 | dccp_feat_clean(newdmsk); | ||
539 | rc = -ENOMEM; | ||
540 | goto out; | ||
541 | } | ||
542 | |||
543 | EXPORT_SYMBOL_GPL(dccp_feat_clone); | ||
544 | |||
545 | static int __dccp_feat_init(struct dccp_minisock *dmsk, u8 type, u8 feat, | ||
546 | u8 *val, u8 len) | ||
547 | { | ||
548 | int rc = -ENOMEM; | ||
549 | u8 *copy = kmalloc(len, GFP_KERNEL); | ||
550 | |||
551 | if (copy != NULL) { | ||
552 | memcpy(copy, val, len); | ||
553 | rc = dccp_feat_change(dmsk, type, feat, copy, len, GFP_KERNEL); | ||
554 | if (rc) | ||
555 | kfree(copy); | ||
556 | } | ||
557 | return rc; | ||
558 | } | ||
559 | |||
560 | int dccp_feat_init(struct dccp_minisock *dmsk) | ||
561 | { | ||
562 | int rc; | ||
563 | |||
564 | INIT_LIST_HEAD(&dmsk->dccpms_pending); | ||
565 | INIT_LIST_HEAD(&dmsk->dccpms_conf); | ||
566 | |||
567 | /* CCID L */ | ||
568 | rc = __dccp_feat_init(dmsk, DCCPO_CHANGE_L, DCCPF_CCID, | ||
569 | &dmsk->dccpms_tx_ccid, 1); | ||
570 | if (rc) | ||
571 | goto out; | ||
572 | |||
573 | /* CCID R */ | ||
574 | rc = __dccp_feat_init(dmsk, DCCPO_CHANGE_R, DCCPF_CCID, | ||
575 | &dmsk->dccpms_rx_ccid, 1); | ||
576 | if (rc) | ||
577 | goto out; | ||
578 | |||
579 | /* Ack ratio */ | ||
580 | rc = __dccp_feat_init(dmsk, DCCPO_CHANGE_L, DCCPF_ACK_RATIO, | ||
581 | &dmsk->dccpms_ack_ratio, 1); | ||
582 | out: | ||
583 | return rc; | ||
584 | } | ||
585 | |||
586 | EXPORT_SYMBOL_GPL(dccp_feat_init); | ||