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