diff options
author | Vlad Yasevich <vladislav.yasevich@hp.com> | 2007-12-20 17:11:47 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-01-28 17:59:23 -0500 |
commit | a08de64d074b36a56ee3bb985cd171281db78e96 (patch) | |
tree | 15b3c77a7078b5dc0510a23cc7d95922f7ad420a | |
parent | ba8a06daed7d7c8785c92c343da9e202e6988fda (diff) |
[SCTP]: Update ASCONF processing to conform to spec.
The processing of the ASCONF chunks has changed a lot in the
spec. New items are:
1. A list of ASCONF-ACK chunks is now cached
2. The source of the packet is used in response.
3. New handling for unexpect ASCONF chunks.
Signed-off-by: Vlad Yasevich <vladislav.yasevich@hp.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/sctp/structs.h | 24 | ||||
-rw-r--r-- | net/sctp/associola.c | 58 | ||||
-rw-r--r-- | net/sctp/outqueue.c | 29 | ||||
-rw-r--r-- | net/sctp/sm_make_chunk.c | 12 | ||||
-rw-r--r-- | net/sctp/sm_statefuns.c | 64 |
5 files changed, 143 insertions, 44 deletions
diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h index 8a0808eab811..fa87873fee76 100644 --- a/include/net/sctp/structs.h +++ b/include/net/sctp/structs.h | |||
@@ -744,6 +744,7 @@ struct sctp_chunk { | |||
744 | __u8 tsn_missing_report; /* Data chunk missing counter. */ | 744 | __u8 tsn_missing_report; /* Data chunk missing counter. */ |
745 | __u8 data_accepted; /* At least 1 chunk in this packet accepted */ | 745 | __u8 data_accepted; /* At least 1 chunk in this packet accepted */ |
746 | __u8 auth; /* IN: was auth'ed | OUT: needs auth */ | 746 | __u8 auth; /* IN: was auth'ed | OUT: needs auth */ |
747 | __u8 has_asconf; /* IN: have seen an asconf before */ | ||
747 | }; | 748 | }; |
748 | 749 | ||
749 | void sctp_chunk_hold(struct sctp_chunk *); | 750 | void sctp_chunk_hold(struct sctp_chunk *); |
@@ -1785,20 +1786,16 @@ struct sctp_association { | |||
1785 | */ | 1786 | */ |
1786 | struct sctp_chunk *addip_last_asconf; | 1787 | struct sctp_chunk *addip_last_asconf; |
1787 | 1788 | ||
1788 | /* ADDIP Section 4.2 Upon reception of an ASCONF Chunk. | 1789 | /* ADDIP Section 5.2 Upon reception of an ASCONF Chunk. |
1789 | * | 1790 | * |
1790 | * IMPLEMENTATION NOTE: As an optimization a receiver may wish | 1791 | * This is needed to implement itmes E1 - E4 of the updated |
1791 | * to save the last ASCONF-ACK for some predetermined period | 1792 | * spec. Here is the justification: |
1792 | * of time and instead of re-processing the ASCONF (with the | ||
1793 | * same serial number) it may just re-transmit the | ||
1794 | * ASCONF-ACK. It may wish to use the arrival of a new serial | ||
1795 | * number to discard the previously saved ASCONF-ACK or any | ||
1796 | * other means it may choose to expire the saved ASCONF-ACK. | ||
1797 | * | 1793 | * |
1798 | * [This is our saved ASCONF-ACK. We invalidate it when a new | 1794 | * Since the peer may bundle multiple ASCONF chunks toward us, |
1799 | * ASCONF serial number arrives.] | 1795 | * we now need the ability to cache multiple ACKs. The section |
1796 | * describes in detail how they are cached and cleaned up. | ||
1800 | */ | 1797 | */ |
1801 | struct sctp_chunk *addip_last_asconf_ack; | 1798 | struct list_head asconf_ack_list; |
1802 | 1799 | ||
1803 | /* These ASCONF chunks are waiting to be sent. | 1800 | /* These ASCONF chunks are waiting to be sent. |
1804 | * | 1801 | * |
@@ -1947,6 +1944,11 @@ int sctp_assoc_set_bind_addr_from_cookie(struct sctp_association *, | |||
1947 | struct sctp_cookie*, | 1944 | struct sctp_cookie*, |
1948 | gfp_t gfp); | 1945 | gfp_t gfp); |
1949 | int sctp_assoc_set_id(struct sctp_association *, gfp_t); | 1946 | int sctp_assoc_set_id(struct sctp_association *, gfp_t); |
1947 | void sctp_assoc_clean_asconf_ack_cache(const struct sctp_association *asoc); | ||
1948 | struct sctp_chunk *sctp_assoc_lookup_asconf_ack( | ||
1949 | const struct sctp_association *asoc, | ||
1950 | __be32 serial); | ||
1951 | |||
1950 | 1952 | ||
1951 | int sctp_cmp_addr_exact(const union sctp_addr *ss1, | 1953 | int sctp_cmp_addr_exact(const union sctp_addr *ss1, |
1952 | const union sctp_addr *ss2); | 1954 | const union sctp_addr *ss2); |
diff --git a/net/sctp/associola.c b/net/sctp/associola.c index 61bebb9b96e6..a016e78061f4 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c | |||
@@ -61,6 +61,7 @@ | |||
61 | 61 | ||
62 | /* Forward declarations for internal functions. */ | 62 | /* Forward declarations for internal functions. */ |
63 | static void sctp_assoc_bh_rcv(struct work_struct *work); | 63 | static void sctp_assoc_bh_rcv(struct work_struct *work); |
64 | static void sctp_assoc_free_asconf_acks(struct sctp_association *asoc); | ||
64 | 65 | ||
65 | 66 | ||
66 | /* 1st Level Abstractions. */ | 67 | /* 1st Level Abstractions. */ |
@@ -242,6 +243,7 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a | |||
242 | asoc->addip_serial = asoc->c.initial_tsn; | 243 | asoc->addip_serial = asoc->c.initial_tsn; |
243 | 244 | ||
244 | INIT_LIST_HEAD(&asoc->addip_chunk_list); | 245 | INIT_LIST_HEAD(&asoc->addip_chunk_list); |
246 | INIT_LIST_HEAD(&asoc->asconf_ack_list); | ||
245 | 247 | ||
246 | /* Make an empty list of remote transport addresses. */ | 248 | /* Make an empty list of remote transport addresses. */ |
247 | INIT_LIST_HEAD(&asoc->peer.transport_addr_list); | 249 | INIT_LIST_HEAD(&asoc->peer.transport_addr_list); |
@@ -431,8 +433,7 @@ void sctp_association_free(struct sctp_association *asoc) | |||
431 | asoc->peer.transport_count = 0; | 433 | asoc->peer.transport_count = 0; |
432 | 434 | ||
433 | /* Free any cached ASCONF_ACK chunk. */ | 435 | /* Free any cached ASCONF_ACK chunk. */ |
434 | if (asoc->addip_last_asconf_ack) | 436 | sctp_assoc_free_asconf_acks(asoc); |
435 | sctp_chunk_free(asoc->addip_last_asconf_ack); | ||
436 | 437 | ||
437 | /* Free any cached ASCONF chunk. */ | 438 | /* Free any cached ASCONF chunk. */ |
438 | if (asoc->addip_last_asconf) | 439 | if (asoc->addip_last_asconf) |
@@ -1485,3 +1486,56 @@ retry: | |||
1485 | asoc->assoc_id = (sctp_assoc_t) assoc_id; | 1486 | asoc->assoc_id = (sctp_assoc_t) assoc_id; |
1486 | return error; | 1487 | return error; |
1487 | } | 1488 | } |
1489 | |||
1490 | /* Free asconf_ack cache */ | ||
1491 | static void sctp_assoc_free_asconf_acks(struct sctp_association *asoc) | ||
1492 | { | ||
1493 | struct sctp_chunk *ack; | ||
1494 | struct sctp_chunk *tmp; | ||
1495 | |||
1496 | list_for_each_entry_safe(ack, tmp, &asoc->asconf_ack_list, | ||
1497 | transmitted_list) { | ||
1498 | list_del_init(&ack->transmitted_list); | ||
1499 | sctp_chunk_free(ack); | ||
1500 | } | ||
1501 | } | ||
1502 | |||
1503 | /* Clean up the ASCONF_ACK queue */ | ||
1504 | void sctp_assoc_clean_asconf_ack_cache(const struct sctp_association *asoc) | ||
1505 | { | ||
1506 | struct sctp_chunk *ack; | ||
1507 | struct sctp_chunk *tmp; | ||
1508 | |||
1509 | /* We can remove all the entries from the queue upto | ||
1510 | * the "Peer-Sequence-Number". | ||
1511 | */ | ||
1512 | list_for_each_entry_safe(ack, tmp, &asoc->asconf_ack_list, | ||
1513 | transmitted_list) { | ||
1514 | if (ack->subh.addip_hdr->serial == | ||
1515 | htonl(asoc->peer.addip_serial)) | ||
1516 | break; | ||
1517 | |||
1518 | list_del_init(&ack->transmitted_list); | ||
1519 | sctp_chunk_free(ack); | ||
1520 | } | ||
1521 | } | ||
1522 | |||
1523 | /* Find the ASCONF_ACK whose serial number matches ASCONF */ | ||
1524 | struct sctp_chunk *sctp_assoc_lookup_asconf_ack( | ||
1525 | const struct sctp_association *asoc, | ||
1526 | __be32 serial) | ||
1527 | { | ||
1528 | struct sctp_chunk *ack = NULL; | ||
1529 | |||
1530 | /* Walk through the list of cached ASCONF-ACKs and find the | ||
1531 | * ack chunk whose serial number matches that of the request. | ||
1532 | */ | ||
1533 | list_for_each_entry(ack, &asoc->asconf_ack_list, transmitted_list) { | ||
1534 | if (ack->subh.addip_hdr->serial == serial) { | ||
1535 | sctp_chunk_hold(ack); | ||
1536 | break; | ||
1537 | } | ||
1538 | } | ||
1539 | |||
1540 | return ack; | ||
1541 | } | ||
diff --git a/net/sctp/outqueue.c b/net/sctp/outqueue.c index fa76f235169b..a42af865c2ef 100644 --- a/net/sctp/outqueue.c +++ b/net/sctp/outqueue.c | |||
@@ -716,7 +716,29 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout) | |||
716 | new_transport = chunk->transport; | 716 | new_transport = chunk->transport; |
717 | 717 | ||
718 | if (!new_transport) { | 718 | if (!new_transport) { |
719 | new_transport = asoc->peer.active_path; | 719 | /* |
720 | * If we have a prior transport pointer, see if | ||
721 | * the destination address of the chunk | ||
722 | * matches the destination address of the | ||
723 | * current transport. If not a match, then | ||
724 | * try to look up the transport with a given | ||
725 | * destination address. We do this because | ||
726 | * after processing ASCONFs, we may have new | ||
727 | * transports created. | ||
728 | */ | ||
729 | if (transport && | ||
730 | sctp_cmp_addr_exact(&chunk->dest, | ||
731 | &transport->ipaddr)) | ||
732 | new_transport = transport; | ||
733 | else | ||
734 | new_transport = sctp_assoc_lookup_paddr(asoc, | ||
735 | &chunk->dest); | ||
736 | |||
737 | /* if we still don't have a new transport, then | ||
738 | * use the current active path. | ||
739 | */ | ||
740 | if (!new_transport) | ||
741 | new_transport = asoc->peer.active_path; | ||
720 | } else if ((new_transport->state == SCTP_INACTIVE) || | 742 | } else if ((new_transport->state == SCTP_INACTIVE) || |
721 | (new_transport->state == SCTP_UNCONFIRMED)) { | 743 | (new_transport->state == SCTP_UNCONFIRMED)) { |
722 | /* If the chunk is Heartbeat or Heartbeat Ack, | 744 | /* If the chunk is Heartbeat or Heartbeat Ack, |
@@ -729,9 +751,12 @@ int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout) | |||
729 | * address of the IP datagram containing the | 751 | * address of the IP datagram containing the |
730 | * HEARTBEAT chunk to which this ack is responding. | 752 | * HEARTBEAT chunk to which this ack is responding. |
731 | * ... | 753 | * ... |
754 | * | ||
755 | * ASCONF_ACKs also must be sent to the source. | ||
732 | */ | 756 | */ |
733 | if (chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT && | 757 | if (chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT && |
734 | chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT_ACK) | 758 | chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT_ACK && |
759 | chunk->chunk_hdr->type != SCTP_CID_ASCONF_ACK) | ||
735 | new_transport = asoc->peer.active_path; | 760 | new_transport = asoc->peer.active_path; |
736 | } | 761 | } |
737 | 762 | ||
diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c index 8138bbd93210..7fd6a6b68614 100644 --- a/net/sctp/sm_make_chunk.c +++ b/net/sctp/sm_make_chunk.c | |||
@@ -1275,6 +1275,9 @@ nodata: | |||
1275 | /* Release the memory occupied by a chunk. */ | 1275 | /* Release the memory occupied by a chunk. */ |
1276 | static void sctp_chunk_destroy(struct sctp_chunk *chunk) | 1276 | static void sctp_chunk_destroy(struct sctp_chunk *chunk) |
1277 | { | 1277 | { |
1278 | BUG_ON(!list_empty(&chunk->list)); | ||
1279 | list_del_init(&chunk->transmitted_list); | ||
1280 | |||
1278 | /* Free the chunk skb data and the SCTP_chunk stub itself. */ | 1281 | /* Free the chunk skb data and the SCTP_chunk stub itself. */ |
1279 | dev_kfree_skb(chunk->skb); | 1282 | dev_kfree_skb(chunk->skb); |
1280 | 1283 | ||
@@ -1285,9 +1288,6 @@ static void sctp_chunk_destroy(struct sctp_chunk *chunk) | |||
1285 | /* Possibly, free the chunk. */ | 1288 | /* Possibly, free the chunk. */ |
1286 | void sctp_chunk_free(struct sctp_chunk *chunk) | 1289 | void sctp_chunk_free(struct sctp_chunk *chunk) |
1287 | { | 1290 | { |
1288 | BUG_ON(!list_empty(&chunk->list)); | ||
1289 | list_del_init(&chunk->transmitted_list); | ||
1290 | |||
1291 | /* Release our reference on the message tracker. */ | 1291 | /* Release our reference on the message tracker. */ |
1292 | if (chunk->msg) | 1292 | if (chunk->msg) |
1293 | sctp_datamsg_put(chunk->msg); | 1293 | sctp_datamsg_put(chunk->msg); |
@@ -2980,11 +2980,9 @@ done: | |||
2980 | * after freeing the reference to old asconf ack if any. | 2980 | * after freeing the reference to old asconf ack if any. |
2981 | */ | 2981 | */ |
2982 | if (asconf_ack) { | 2982 | if (asconf_ack) { |
2983 | if (asoc->addip_last_asconf_ack) | ||
2984 | sctp_chunk_free(asoc->addip_last_asconf_ack); | ||
2985 | |||
2986 | sctp_chunk_hold(asconf_ack); | 2983 | sctp_chunk_hold(asconf_ack); |
2987 | asoc->addip_last_asconf_ack = asconf_ack; | 2984 | list_add_tail(&asconf_ack->transmitted_list, |
2985 | &asoc->asconf_ack_list); | ||
2988 | } | 2986 | } |
2989 | 2987 | ||
2990 | return asconf_ack; | 2988 | return asconf_ack; |
diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c index b6aaa7e97d82..a1be9d93f1a8 100644 --- a/net/sctp/sm_statefuns.c +++ b/net/sctp/sm_statefuns.c | |||
@@ -3402,48 +3402,68 @@ sctp_disposition_t sctp_sf_do_asconf(const struct sctp_endpoint *ep, | |||
3402 | 3402 | ||
3403 | /* Verify the ASCONF chunk before processing it. */ | 3403 | /* Verify the ASCONF chunk before processing it. */ |
3404 | if (!sctp_verify_asconf(asoc, | 3404 | if (!sctp_verify_asconf(asoc, |
3405 | (sctp_paramhdr_t *)((void *)addr_param + length), | 3405 | (sctp_paramhdr_t *)((void *)addr_param + length), |
3406 | (void *)chunk->chunk_end, | 3406 | (void *)chunk->chunk_end, |
3407 | &err_param)) | 3407 | &err_param)) |
3408 | return sctp_sf_violation_paramlen(ep, asoc, type, | 3408 | return sctp_sf_violation_paramlen(ep, asoc, type, |
3409 | (void *)&err_param, commands); | 3409 | (void *)&err_param, commands); |
3410 | 3410 | ||
3411 | /* ADDIP 4.2 C1) Compare the value of the serial number to the value | 3411 | /* ADDIP 5.2 E1) Compare the value of the serial number to the value |
3412 | * the endpoint stored in a new association variable | 3412 | * the endpoint stored in a new association variable |
3413 | * 'Peer-Serial-Number'. | 3413 | * 'Peer-Serial-Number'. |
3414 | */ | 3414 | */ |
3415 | if (serial == asoc->peer.addip_serial + 1) { | 3415 | if (serial == asoc->peer.addip_serial + 1) { |
3416 | /* ADDIP 4.2 C2) If the value found in the serial number is | 3416 | /* If this is the first instance of ASCONF in the packet, |
3417 | * equal to the ('Peer-Serial-Number' + 1), the endpoint MUST | 3417 | * we can clean our old ASCONF-ACKs. |
3418 | * do V1-V5. | 3418 | */ |
3419 | if (!chunk->has_asconf) | ||
3420 | sctp_assoc_clean_asconf_ack_cache(asoc); | ||
3421 | |||
3422 | /* ADDIP 5.2 E4) When the Sequence Number matches the next one | ||
3423 | * expected, process the ASCONF as described below and after | ||
3424 | * processing the ASCONF Chunk, append an ASCONF-ACK Chunk to | ||
3425 | * the response packet and cache a copy of it (in the event it | ||
3426 | * later needs to be retransmitted). | ||
3427 | * | ||
3428 | * Essentially, do V1-V5. | ||
3419 | */ | 3429 | */ |
3420 | asconf_ack = sctp_process_asconf((struct sctp_association *) | 3430 | asconf_ack = sctp_process_asconf((struct sctp_association *) |
3421 | asoc, chunk); | 3431 | asoc, chunk); |
3422 | if (!asconf_ack) | 3432 | if (!asconf_ack) |
3423 | return SCTP_DISPOSITION_NOMEM; | 3433 | return SCTP_DISPOSITION_NOMEM; |
3424 | } else if (serial == asoc->peer.addip_serial) { | 3434 | } else if (serial < asoc->peer.addip_serial + 1) { |
3425 | /* ADDIP 4.2 C3) If the value found in the serial number is | 3435 | /* ADDIP 5.2 E2) |
3426 | * equal to the value stored in the 'Peer-Serial-Number' | 3436 | * If the value found in the Sequence Number is less than the |
3427 | * IMPLEMENTATION NOTE: As an optimization a receiver may wish | 3437 | * ('Peer- Sequence-Number' + 1), simply skip to the next |
3428 | * to save the last ASCONF-ACK for some predetermined period of | 3438 | * ASCONF, and include in the outbound response packet |
3429 | * time and instead of re-processing the ASCONF (with the same | 3439 | * any previously cached ASCONF-ACK response that was |
3430 | * serial number) it may just re-transmit the ASCONF-ACK. | 3440 | * sent and saved that matches the Sequence Number of the |
3441 | * ASCONF. Note: It is possible that no cached ASCONF-ACK | ||
3442 | * Chunk exists. This will occur when an older ASCONF | ||
3443 | * arrives out of order. In such a case, the receiver | ||
3444 | * should skip the ASCONF Chunk and not include ASCONF-ACK | ||
3445 | * Chunk for that chunk. | ||
3431 | */ | 3446 | */ |
3432 | if (asoc->addip_last_asconf_ack) | 3447 | asconf_ack = sctp_assoc_lookup_asconf_ack(asoc, hdr->serial); |
3433 | asconf_ack = asoc->addip_last_asconf_ack; | 3448 | if (!asconf_ack) |
3434 | else | ||
3435 | return SCTP_DISPOSITION_DISCARD; | 3449 | return SCTP_DISPOSITION_DISCARD; |
3436 | } else { | 3450 | } else { |
3437 | /* ADDIP 4.2 C4) Otherwise, the ASCONF Chunk is discarded since | 3451 | /* ADDIP 5.2 E5) Otherwise, the ASCONF Chunk is discarded since |
3438 | * it must be either a stale packet or from an attacker. | 3452 | * it must be either a stale packet or from an attacker. |
3439 | */ | 3453 | */ |
3440 | return SCTP_DISPOSITION_DISCARD; | 3454 | return SCTP_DISPOSITION_DISCARD; |
3441 | } | 3455 | } |
3442 | 3456 | ||
3443 | /* ADDIP 4.2 C5) In both cases C2 and C3 the ASCONF-ACK MUST be sent | 3457 | /* ADDIP 5.2 E6) The destination address of the SCTP packet |
3444 | * back to the source address contained in the IP header of the ASCONF | 3458 | * containing the ASCONF-ACK Chunks MUST be the source address of |
3445 | * being responded to. | 3459 | * the SCTP packet that held the ASCONF Chunks. |
3460 | * | ||
3461 | * To do this properly, we'll set the destination address of the chunk | ||
3462 | * and at the transmit time, will try look up the transport to use. | ||
3463 | * Since ASCONFs may be bundled, the correct transport may not be | ||
3464 | * created untill we process the entire packet, thus this workaround. | ||
3446 | */ | 3465 | */ |
3466 | asconf_ack->dest = chunk->source; | ||
3447 | sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack)); | 3467 | sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack)); |
3448 | 3468 | ||
3449 | return SCTP_DISPOSITION_CONSUME; | 3469 | return SCTP_DISPOSITION_CONSUME; |