xref: /illumos-gate/usr/src/uts/common/inet/tcp/tcp_sack.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include <sys/types.h>
26 #include <sys/kmem.h>
27 #include <sys/debug.h>
28 #include <netinet/tcp.h>
29 #include <sys/systm.h>
30 #include <sys/stropts.h>
31 #include <netinet/in.h>
32 #include <netinet/ip6.h>
33 #include <inet/common.h>
34 #include <inet/ip.h>
35 #include <inet/tcp.h>
36 
37 /* kmem cache for notsack_blk_t */
38 kmem_cache_t	*tcp_notsack_blk_cache;
39 
40 /*
41  * To insert a new blk to the array of SACK blk in receiver.
42  *
43  * Parameters:
44  *	sack_blk_t *head: pointer to the array of SACK blks.
45  *	tcp_seq begin: starting seq num of the new blk.
46  *	tcp_seq end: ending seq num of the new blk.
47  *	int32_t *num: (referenced) total num of SACK blks on the list.
48  */
49 void
50 tcp_sack_insert(sack_blk_t *head, tcp_seq begin, tcp_seq end, int32_t *num)
51 {
52 	int32_t	i, j, old_num, new_num;
53 	sack_blk_t tmp[MAX_SACK_BLK - 1];
54 
55 	/* The array is empty, just add the new one. */
56 	if (*num == 0) {
57 		head[0].begin = begin;
58 		head[0].end = end;
59 		*num = 1;
60 		return;
61 	}
62 
63 	/*
64 	 * Check for overlap.  There are five cases.
65 	 *
66 	 * 1. there is no overlap with any other SACK blks.
67 	 * 2. new SACK blk is completely contained in another blk.
68 	 * 3. tail part of new SACK blk overlaps with another blk.
69 	 * 4. head part of new SACK blk overlaps with another blk.
70 	 * 5. new SACK blk completely contains another blk.
71 	 *
72 	 * Use tmp to hold old SACK blks.  After the loop, copy them back
73 	 * to head.
74 	 */
75 	old_num = *num;
76 	if (old_num > MAX_SACK_BLK - 1) {
77 		old_num = MAX_SACK_BLK - 1;
78 	}
79 	new_num = old_num;
80 	j = 0;
81 	for (i = 0; i < old_num; i++) {
82 		if (SEQ_LT(end, head[i].begin) || SEQ_GT(begin, head[i].end)) {
83 			/* Case 1: continue to check. */
84 			tmp[j].begin = head[i].begin;
85 			tmp[j].end = head[i].end;
86 			j++;
87 			continue;
88 		} else if (SEQ_GEQ(begin, head[i].begin) &&
89 		    SEQ_LEQ(end, head[i].end)) {
90 			/* Case 2: re-insert the old blk to the head. */
91 			begin = head[i].begin;
92 			end = head[i].end;
93 		} else if (SEQ_LEQ(end, head[i].end) &&
94 		    SEQ_GEQ(end, head[i].begin)) {
95 			/*
96 			 * Case 3: Extend the new blk, remove the old one
97 			 * and continue to check.
98 			 */
99 			end = head[i].end;
100 		} else if (SEQ_GEQ(begin, head[i].begin) &&
101 		    SEQ_LEQ(begin, head[i].end)) {
102 			/* Case 4 */
103 			begin = head[i].begin;
104 		}
105 		/*
106 		 * Common code for all cases except the first one, which
107 		 * copies the original SACK blk into the tmp storage.  Other
108 		 * cases remove the original SACK blk by not copying into
109 		 * tmp storage.
110 		 */
111 		new_num--;
112 	}
113 
114 	head[0].begin = begin;
115 	head[0].end = end;
116 	for (i = 0; i < new_num; i++) {
117 		head[i+1].begin = tmp[i].begin;
118 		head[i+1].end = tmp[i].end;
119 	}
120 	*num = new_num + 1;
121 }
122 
123 
124 /*
125  * To remove a SACK block.
126  *
127  * Parameters:
128  *	sack_blk_t *head: pointer to the array of SACK blks.
129  *	tcp_seq end: to remove all sack blk with seq num less than end.
130  *	int32_t *num: (referenced) total num of SACK blks in the array.
131  */
132 void
133 tcp_sack_remove(sack_blk_t *head, tcp_seq end, int32_t *num)
134 {
135 	sack_blk_t tmp[MAX_SACK_BLK];
136 	int32_t i, j, old_num, new_num;
137 
138 	if (*num == 0)
139 		return;
140 
141 	old_num = *num;
142 	new_num = old_num;
143 	j = 0;
144 	/* Walk thru the whole list and copy the new list to tmp[]. */
145 	for (i = 0; i < old_num; i++) {
146 		if (SEQ_GT(end, head[i].begin)) {
147 			/*
148 			 * Check to see if the old SACK blk needs to be
149 			 * removed or updated.  If the old blk is just
150 			 * partially covered, update begin and continue.
151 			 * If the old blk is completely covered, remove it
152 			 * and continue to check.
153 			 */
154 			if (SEQ_GEQ(end, head[i].end)) {
155 				new_num--;
156 				continue;
157 			} else {
158 				tmp[j].begin = end;
159 				tmp[j].end = head[i].end;
160 			}
161 		} else {
162 			tmp[j].begin = head[i].begin;
163 			tmp[j].end = head[i].end;
164 		}
165 		j++;
166 	}
167 	/* Copy tmp[] back to the original list. */
168 	for (i = 0; i < new_num; i++) {
169 		head[i].begin = tmp[i].begin;
170 		head[i].end = tmp[i].end;
171 	}
172 	*num = new_num;
173 }
174 
175 
176 /*
177  * Use the SACK info to insert a "notsack'ed" blk.  The notsack'ed blk list
178  * contains the list of blks which have not been selectively acknowledged
179  * by the receiver.  The SACK info is a blk which is being selectively
180  * acknowledged by the receiver.
181  *
182  * Parameters:
183  *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
184  *		blks.
185  *	tcp_seq begin: starting seq num of the SACK info.
186  *	tcp_seq end: ending seq num of the SACK info.
187  *	int32_t *num: (referenced) total num of notsack'ed blk on the list.
188  *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
189  *		blks.
190  */
191 void
192 tcp_notsack_insert(notsack_blk_t **head, tcp_seq begin, tcp_seq end,
193     int32_t *num, uint32_t *sum)
194 {
195 	notsack_blk_t *prev, *tmp, *new;
196 	uint32_t tmp_sum, tmp_num;
197 
198 	if (*head == NULL) {
199 		return;
200 	}
201 
202 	tmp = *head;
203 	prev = NULL;
204 	/* Find the right place of updating the list. */
205 	while ((tmp != NULL) && SEQ_LEQ(tmp->end, begin)) {
206 		prev = tmp;
207 		(tmp->sack_cnt)++;
208 		tmp = tmp->next;
209 	}
210 
211 	/*
212 	 * This can happen only when TCP sends new data but the notsack list
213 	 * is not updated.
214 	 */
215 	if (tmp == NULL) {
216 		return;
217 	}
218 
219 	/*
220 	 * This means the new SACK info covers something that is not on
221 	 * the list anymore.
222 	 */
223 	if (SEQ_LEQ(end, tmp->begin)) {
224 		return;
225 	}
226 
227 	/* The SACK info covers up to this blk.  So just check for this blk. */
228 	if (SEQ_LEQ(end, tmp->end)) {
229 		/*
230 		 * Only this notsack'ed blk is completely covered.  Delete
231 		 * it and return.
232 		 */
233 		if (end == tmp->end && SEQ_LEQ(begin, tmp->begin)) {
234 			if (prev != NULL) {
235 				prev->next = tmp->next;
236 			} else {
237 				*head = tmp->next;
238 			}
239 			(*num)--;
240 			*sum -= tmp->end - tmp->begin;
241 			kmem_cache_free(tcp_notsack_blk_cache, tmp);
242 			return;
243 		}
244 		/* This blk is partially covered. */
245 		if (SEQ_GEQ(begin, tmp->begin)) {
246 			/* Check what needs to be updated. */
247 			if (begin == tmp->begin) {
248 				*sum -= end - tmp->begin;
249 				tmp->begin = end;
250 			} else if (end == tmp->end) {
251 				*sum -= tmp->end - begin;
252 				tmp->end = begin;
253 				(tmp->sack_cnt)++;
254 			} else {
255 				/* Split the notsack blk. */
256 				if ((new = kmem_cache_alloc(
257 				    tcp_notsack_blk_cache, KM_NOSLEEP)) ==
258 				    NULL) {
259 					return;
260 				}
261 				new->end = tmp->end;
262 				new->begin = end;
263 				new->next = tmp->next;
264 				new->sack_cnt = 0;
265 				tmp->end = begin;
266 				tmp->next = new;
267 				(tmp->sack_cnt)++;
268 				(*num)++;
269 				*sum -= end - begin;
270 			}
271 		} else {
272 			*sum -= end - tmp->begin;
273 			tmp->begin = end;
274 		}
275 		return;
276 	}
277 
278 	/* Need to check for coverage of this blk and later blks. */
279 	tmp_sum = *sum;
280 	tmp_num = *num;
281 	if (SEQ_LT(tmp->begin, begin)) {
282 		tmp_sum -= tmp->end - begin;
283 		tmp->end = begin;
284 		(tmp->sack_cnt)++;
285 		prev = tmp;
286 		tmp = tmp->next;
287 	}
288 
289 	while (tmp != NULL) {
290 		/* The coverage stops here. */
291 		if (SEQ_GT(tmp->begin, end)) {
292 			break;
293 		} else {
294 			/* Is the blk completely or partially covered? */
295 			if (SEQ_LEQ(tmp->end, end)) {
296 				tmp_num--;
297 				tmp_sum -= tmp->end - tmp->begin;
298 				if (prev != NULL) {
299 					prev->next = tmp->next;
300 					kmem_cache_free(tcp_notsack_blk_cache,
301 					    tmp);
302 					tmp = prev->next;
303 				} else {
304 					*head = tmp->next;
305 					kmem_cache_free(tcp_notsack_blk_cache,
306 					    tmp);
307 					tmp = *head;
308 				}
309 			} else {
310 				/*
311 				 * This blk is partially covered.  It also
312 				 * means it should be the end of coverage.
313 				 */
314 				tmp_sum -= end - tmp->begin;
315 				tmp->begin = end;
316 				break;
317 			}
318 		}
319 	}
320 	*num = tmp_num;
321 	*sum = tmp_sum;
322 }
323 
324 
325 /*
326  * To remove notsack'ed blks.
327  *
328  * Parameters:
329  *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
330  *		blks.
331  *	tcp_seq end: to remove all notsack'ed blk with seq num less than end.
332  *	int32_t *num: (referenced) total num of notsack'ed blks.
333  *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
334  *		blks.
335  */
336 void
337 tcp_notsack_remove(notsack_blk_t **head, tcp_seq end, int32_t *num,
338     uint32_t *sum)
339 {
340 	notsack_blk_t *prev, *tmp;
341 	uint32_t tmp_sum = *sum;
342 
343 	if (*head == NULL)
344 		return;
345 
346 	prev = NULL;
347 	tmp = *head;
348 	while (tmp != NULL) {
349 		/* There is nothing to discard. */
350 		if (SEQ_GT(tmp->begin, end)) {
351 			break;
352 		}
353 
354 		/* Is the blk completely or partially covered? */
355 		if (SEQ_GEQ(end, tmp->end)) {
356 			(*num)--;
357 			tmp_sum -= tmp->end - tmp->begin;
358 			if (prev == NULL) {
359 				*head = tmp->next;
360 				kmem_cache_free(tcp_notsack_blk_cache, tmp);
361 				tmp = *head;
362 			} else {
363 				prev->next = tmp->next;
364 				kmem_cache_free(tcp_notsack_blk_cache, tmp);
365 				tmp = prev->next;
366 			}
367 		} else {
368 			tmp_sum -= end - tmp->begin;
369 			tmp->begin = end;
370 			break;
371 		}
372 	}
373 	*sum = tmp_sum;
374 }
375 
376 
377 /*
378  * To update the notsack'ed list when new data is sent.
379  *
380  * Assumption: this should only be called when new notsack blk is to be added.
381  *
382  * Parameters:
383  *	notsack_blk_t **head: address of the pointer to the list of notsack'ed
384  *		blks.
385  *	tcp_seq begin: beginning seq num of new data.
386  *	tcp_seq end: ending seq num of new data.
387  *	int32_t *num: (referenced) total num of notsack'ed blks.
388  *	uint32_t *sum: (referenced) total num of bytes of all the notsack'ed
389  *		blks.
390  */
391 void tcp_notsack_update(notsack_blk_t **head, tcp_seq begin, tcp_seq end,
392     int32_t *num, uint32_t *sum)
393 {
394 	notsack_blk_t *tmp;
395 
396 	tmp = *head;
397 	/* If the list is empty, create a new one. */
398 	if (tmp == NULL) {
399 		if ((tmp = kmem_cache_alloc(tcp_notsack_blk_cache,
400 		    KM_NOSLEEP)) == NULL) {
401 			return;
402 		}
403 		tmp->begin = begin;
404 		tmp->end = end;
405 		tmp->next = NULL;
406 		tmp->sack_cnt = 0;
407 		*head = tmp;
408 		*num = 1;
409 		*sum = end - begin;
410 		return;
411 	}
412 
413 	/*
414 	 * Find the place to add the new blk.  This assumes that new data
415 	 * is being sent, so the place to insert the new notsack blk is at
416 	 * the end of the list.
417 	 */
418 	while (tmp->next != NULL) {
419 		tmp = tmp->next;
420 	}
421 
422 	/* Does the new blk overlap with old one? */
423 	if (SEQ_GEQ(tmp->end, begin)) {
424 		*sum += end - tmp->end;
425 		tmp->end = end;
426 	} else {
427 		/* No.  Need to create a new notsack blk. */
428 		tmp->next = kmem_cache_alloc(tcp_notsack_blk_cache, KM_NOSLEEP);
429 		if (tmp->next != NULL) {
430 			tmp = tmp->next;
431 			tmp->begin = begin;
432 			tmp->end = end;
433 			tmp->next = NULL;
434 			tmp->sack_cnt = 0;
435 			(*num)++;
436 			*sum += end - begin;
437 		}
438 	}
439 }
440