aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorDaniel Borkmann <dborkman@redhat.com>2014-02-20 14:51:06 -0500
committerDavid S. Miller <davem@davemloft.net>2014-02-22 00:26:05 -0500
commit4c47af4d5eb2c2f78f886079a3920a7078a6f0a0 (patch)
tree5b92ba00ee372f9b23000d15a244772e6639db75 /net
parentb194c1f1dbd5f2671e49e0ac801b1b78dc7de93b (diff)
net: sctp: rework multihoming retransmission path selection to rfc4960
Problem statement: 1) both paths (primary path1 and alternate path2) are up after the association has been established i.e., HB packets are normally exchanged, 2) path2 gets inactive after path_max_retrans * max_rto timed out (i.e. path2 is down completely), 3) now, if a transmission times out on the only surviving/active path1 (any ~1sec network service impact could cause this like a channel bonding failover), then the retransmitted packets are sent over the inactive path2; this happens with partial failover and without it. Besides not being optimal in the above scenario, a small failure or timeout in the only existing path has the potential to cause long delays in the retransmission (depending on RTO_MAX) until the still active path is reselected. Further, when the T3-timeout occurs, we have active_patch == retrans_path, and even though the timeout occurred on the initial transmission of data, not a retransmit, we end up updating retransmit path. RFC4960, section 6.4. "Multi-Homed SCTP Endpoints" states under 6.4.1. "Failover from an Inactive Destination Address" the following: Some of the transport addresses of a multi-homed SCTP endpoint may become inactive due to either the occurrence of certain error conditions (see Section 8.2) or adjustments from the SCTP user. When there is outbound data to send and the primary path becomes inactive (e.g., due to failures), or where the SCTP user explicitly requests to send data to an inactive destination transport address, before reporting an error to its ULP, the SCTP endpoint should try to send the data to an alternate __active__ destination transport address if one exists. When retransmitting data that timed out, if the endpoint is multihomed, it should consider each source-destination address pair in its retransmission selection policy. When retransmitting timed-out data, the endpoint should attempt to pick the most divergent source-destination pair from the original source-destination pair to which the packet was transmitted. Note: Rules for picking the most divergent source-destination pair are an implementation decision and are not specified within this document. So, we should first reconsider to take the current active retransmission transport if we cannot find an alternative active one. If all of that fails, we can still round robin through unkown, partial failover, and inactive ones in the hope to find something still suitable. Commit 4141ddc02a92 ("sctp: retran_path update bug fix") broke that behaviour by selecting the next inactive transport when no other active transport was found besides the current assoc's peer.retran_path. Before commit 4141ddc02a92, we would have traversed through the list until we reach our peer.retran_path again, and in case that is still in state SCTP_ACTIVE, we would take it and return. Only if that is not the case either, we take the next inactive transport. Besides all that, another issue is that transports in state SCTP_UNKNOWN could be preferred over transports in state SCTP_ACTIVE in case a SCTP_ACTIVE transport appears after SCTP_UNKNOWN in the transport list yielding a weaker transport state to be used in retransmission. This patch mostly reverts 4141ddc02a92, but also rewrites this function to introduce more clarity and strictness into the code. A strict priority of transport states is enforced in this patch, hence selection is active > unkown > partial failover > inactive. Fixes: 4141ddc02a92 ("sctp: retran_path update bug fix") Signed-off-by: Daniel Borkmann <dborkman@redhat.com> Cc: Gui Jianfeng <guijianfeng@cn.fujitsu.com> Acked-by: Vlad Yasevich <yasevich@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r--net/sctp/associola.c129
1 files changed, 79 insertions, 50 deletions
diff --git a/net/sctp/associola.c b/net/sctp/associola.c
index f558433537b8..ee13d28d39d1 100644
--- a/net/sctp/associola.c
+++ b/net/sctp/associola.c
@@ -1239,78 +1239,107 @@ void sctp_assoc_update(struct sctp_association *asoc,
1239} 1239}
1240 1240
1241/* Update the retran path for sending a retransmitted packet. 1241/* Update the retran path for sending a retransmitted packet.
1242 * Round-robin through the active transports, else round-robin 1242 * See also RFC4960, 6.4. Multi-Homed SCTP Endpoints:
1243 * through the inactive transports as this is the next best thing 1243 *
1244 * we can try. 1244 * When there is outbound data to send and the primary path
1245 * becomes inactive (e.g., due to failures), or where the
1246 * SCTP user explicitly requests to send data to an
1247 * inactive destination transport address, before reporting
1248 * an error to its ULP, the SCTP endpoint should try to send
1249 * the data to an alternate active destination transport
1250 * address if one exists.
1251 *
1252 * When retransmitting data that timed out, if the endpoint
1253 * is multihomed, it should consider each source-destination
1254 * address pair in its retransmission selection policy.
1255 * When retransmitting timed-out data, the endpoint should
1256 * attempt to pick the most divergent source-destination
1257 * pair from the original source-destination pair to which
1258 * the packet was transmitted.
1259 *
1260 * Note: Rules for picking the most divergent source-destination
1261 * pair are an implementation decision and are not specified
1262 * within this document.
1263 *
1264 * Our basic strategy is to round-robin transports in priorities
1265 * according to sctp_state_prio_map[] e.g., if no such
1266 * transport with state SCTP_ACTIVE exists, round-robin through
1267 * SCTP_UNKNOWN, etc. You get the picture.
1245 */ 1268 */
1246void sctp_assoc_update_retran_path(struct sctp_association *asoc) 1269static const u8 sctp_trans_state_to_prio_map[] = {
1270 [SCTP_ACTIVE] = 3, /* best case */
1271 [SCTP_UNKNOWN] = 2,
1272 [SCTP_PF] = 1,
1273 [SCTP_INACTIVE] = 0, /* worst case */
1274};
1275
1276static u8 sctp_trans_score(const struct sctp_transport *trans)
1247{ 1277{
1248 struct sctp_transport *t, *next; 1278 return sctp_trans_state_to_prio_map[trans->state];
1249 struct list_head *head = &asoc->peer.transport_addr_list; 1279}
1250 struct list_head *pos;
1251 1280
1252 if (asoc->peer.transport_count == 1) 1281static struct sctp_transport *sctp_trans_elect_best(struct sctp_transport *curr,
1253 return; 1282 struct sctp_transport *best)
1283{
1284 if (best == NULL)
1285 return curr;
1254 1286
1255 /* Find the next transport in a round-robin fashion. */ 1287 return sctp_trans_score(curr) > sctp_trans_score(best) ? curr : best;
1256 t = asoc->peer.retran_path; 1288}
1257 pos = &t->transports;
1258 next = NULL;
1259 1289
1260 while (1) { 1290void sctp_assoc_update_retran_path(struct sctp_association *asoc)
1261 /* Skip the head. */ 1291{
1262 if (pos->next == head) 1292 struct sctp_transport *trans = asoc->peer.retran_path;
1263 pos = head->next; 1293 struct sctp_transport *trans_next = NULL;
1264 else
1265 pos = pos->next;
1266 1294
1267 t = list_entry(pos, struct sctp_transport, transports); 1295 /* We're done as we only have the one and only path. */
1296 if (asoc->peer.transport_count == 1)
1297 return;
1298 /* If active_path and retran_path are the same and active,
1299 * then this is the only active path. Use it.
1300 */
1301 if (asoc->peer.active_path == asoc->peer.retran_path &&
1302 asoc->peer.active_path->state == SCTP_ACTIVE)
1303 return;
1268 1304
1269 /* We have exhausted the list, but didn't find any 1305 /* Iterate from retran_path's successor back to retran_path. */
1270 * other active transports. If so, use the next 1306 for (trans = list_next_entry(trans, transports); 1;
1271 * transport. 1307 trans = list_next_entry(trans, transports)) {
1272 */ 1308 /* Manually skip the head element. */
1273 if (t == asoc->peer.retran_path) { 1309 if (&trans->transports == &asoc->peer.transport_addr_list)
1274 t = next; 1310 continue;
1311 if (trans->state == SCTP_UNCONFIRMED)
1312 continue;
1313 trans_next = sctp_trans_elect_best(trans, trans_next);
1314 /* Active is good enough for immediate return. */
1315 if (trans_next->state == SCTP_ACTIVE)
1275 break; 1316 break;
1276 } 1317 /* We've reached the end, time to update path. */
1277 1318 if (trans == asoc->peer.retran_path)
1278 /* Try to find an active transport. */
1279
1280 if ((t->state == SCTP_ACTIVE) ||
1281 (t->state == SCTP_UNKNOWN)) {
1282 break; 1319 break;
1283 } else {
1284 /* Keep track of the next transport in case
1285 * we don't find any active transport.
1286 */
1287 if (t->state != SCTP_UNCONFIRMED && !next)
1288 next = t;
1289 }
1290 } 1320 }
1291 1321
1292 if (t) 1322 if (trans_next != NULL)
1293 asoc->peer.retran_path = t; 1323 asoc->peer.retran_path = trans_next;
1294 else
1295 t = asoc->peer.retran_path;
1296 1324
1297 pr_debug("%s: association:%p addr:%pISpc\n", __func__, asoc, 1325 pr_debug("%s: association:%p updated new path to addr:%pISpc\n",
1298 &t->ipaddr.sa); 1326 __func__, asoc, &asoc->peer.retran_path->ipaddr.sa);
1299} 1327}
1300 1328
1301/* Choose the transport for sending retransmit packet. */ 1329struct sctp_transport *
1302struct sctp_transport *sctp_assoc_choose_alter_transport( 1330sctp_assoc_choose_alter_transport(struct sctp_association *asoc,
1303 struct sctp_association *asoc, struct sctp_transport *last_sent_to) 1331 struct sctp_transport *last_sent_to)
1304{ 1332{
1305 /* If this is the first time packet is sent, use the active path, 1333 /* If this is the first time packet is sent, use the active path,
1306 * else use the retran path. If the last packet was sent over the 1334 * else use the retran path. If the last packet was sent over the
1307 * retran path, update the retran path and use it. 1335 * retran path, update the retran path and use it.
1308 */ 1336 */
1309 if (!last_sent_to) 1337 if (last_sent_to == NULL) {
1310 return asoc->peer.active_path; 1338 return asoc->peer.active_path;
1311 else { 1339 } else {
1312 if (last_sent_to == asoc->peer.retran_path) 1340 if (last_sent_to == asoc->peer.retran_path)
1313 sctp_assoc_update_retran_path(asoc); 1341 sctp_assoc_update_retran_path(asoc);
1342
1314 return asoc->peer.retran_path; 1343 return asoc->peer.retran_path;
1315 } 1344 }
1316} 1345}