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 /*
23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /*
28 * Copyright 2012 David Hoeppner. All rights reserved.
29 */
30
31 /*
32 * This file contains function related to binding.
33 */
34
35 #include <sys/types.h>
36 #include <sys/stream.h>
37 #include <sys/strsun.h>
38 #include <sys/strsubr.h>
39 #include <sys/stropts.h>
40 #include <sys/strlog.h>
41 #define _SUN_TPI_VERSION 2
42 #include <sys/tihdr.h>
43 #include <sys/suntpi.h>
44 #include <sys/xti_inet.h>
45 #include <sys/squeue_impl.h>
46 #include <sys/squeue.h>
47 #include <sys/tsol/tnet.h>
48
49 #include <rpc/pmap_prot.h>
50
51 #include <inet/common.h>
52 #include <inet/dccp_impl.h>
53 #include <inet/ip.h>
54 #include <inet/proto_set.h>
55
56 #include <sys/cmn_err.h>
57
58 /* Setable in /etc/system */
59 static uint32_t dccp_random_anon_port = 1;
60
61 static int dccp_bind_select_lport(dccp_t *, in_port_t *, boolean_t,
62 cred_t *);
63 static in_port_t dccp_get_next_priv_port(const dccp_t *);
64
65 void
66 dccp_bind_hash_insert(dccp_df_t *tbf, dccp_t *dccp, int caller_holds_lock)
67 {
68 conn_t *connp = dccp->dccp_connp;
69 conn_t *connext;
70 dccp_t **dccpp;
71 dccp_t *dccpnext;
72 dccp_t *dccphash;
73
74 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bind_hash_insert");
75
76 /* XXX:DCCP */
77
78 dccpp = &tbf->df_dccp;
79 if (!caller_holds_lock) {
80 mutex_enter(&tbf->df_lock);
81 } else {
82 ASSERT(MUTEX_HELD(&tbf->df_lock));
83 }
84
85 dccphash = dccpp[0];
86 dccpnext = NULL;
87
88 if (dccphash != NULL) {
89 /* Look for an entry using the same port */
90 while ((dccphash = dccpp[0]) != NULL &&
91 connp->conn_lport != dccphash->dccp_connp->conn_lport) {
92 dccpp = &(dccphash->dccp_bind_hash);
93 }
94
95 /* The port was not found, just add to the end */
96 if (dccphash == NULL) {
97 goto insert;
98 }
99
100 dccpnext = dccphash;
101 connext = dccpnext->dccp_connp;
102 dccphash = NULL;
103 if (V6_OR_V4_INADDR_ANY(connp->conn_bound_addr_v6) &&
104 !V6_OR_V4_INADDR_ANY(connext->conn_bound_addr_v6)) {
105 while ((dccpnext = dccpp[0]) != NULL) {
106 connext = dccpnext->dccp_connp;
107 if (!V6_OR_V4_INADDR_ANY(
108 connext->conn_bound_addr_v6)) {
109 dccpp = &(dccpnext->dccp_bind_hash_port);
110 } else {
111 break;
112 }
113 }
114
115 if (dccpnext != NULL) {
116 dccpnext->dccp_ptpbhn = &dccp->dccp_bind_hash_port;
117 dccphash = dccpnext->dccp_bind_hash;
118 if (dccphash != NULL) {
119 dccphash->dccp_ptpbhn =
120 &(dccp->dccp_bind_hash);
121 dccpnext->dccp_bind_hash = NULL;
122 }
123 }
124 } else {
125 dccpnext->dccp_ptpbhn = &dccp->dccp_bind_hash_port;
126 dccphash = dccpnext->dccp_bind_hash;
127 if (dccphash != NULL) {
128 dccphash->dccp_ptpbhn =
129 &(dccp->dccp_bind_hash);
130 dccpnext->dccp_bind_hash = NULL;
131 }
132 }
133 }
134
135 insert:
136 dccp->dccp_bind_hash_port = dccpnext;
137 dccp->dccp_bind_hash = dccphash;
138 dccp->dccp_ptpbhn = dccpp;
139 dccpp[0] = dccp;
140
141 if (!caller_holds_lock) {
142 mutex_exit(&tbf->df_lock);
143 }
144 }
145
146 /*
147 * Remove bind hash.
148 */
149 void
150 dccp_bind_hash_remove(dccp_t *dccp)
151 {
152 conn_t *connp = dccp->dccp_connp;
153 dccp_t *dccpnext;
154 dccp_stack_t *dccps = dccp->dccp_dccps;
155 kmutex_t *lockp;
156
157 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bind_hash_remove");
158
159 /* Nothing to remove */
160 if (dccp->dccp_ptpbhn == NULL) {
161 return;
162 }
163
164 ASSERT(connp->conn_lport != 0);
165 lockp = &dccps->dccps_bind_fanout[DCCP_BIND_HASH(connp->conn_lport,
166 dccps->dccps_bind_fanout_size)].df_lock;
167 ASSERT(lockp != NULL);
168
169 mutex_enter(lockp);
170 if (dccp->dccp_ptpbhn) {
171 dccpnext = dccp->dccp_bind_hash_port;
172 if (dccpnext != NULL) {
173 dccp->dccp_bind_hash_port = NULL;
174 dccpnext->dccp_ptpbhn = dccp->dccp_ptpbhn;
175 dccpnext->dccp_bind_hash = dccp->dccp_bind_hash;
176 if (dccpnext->dccp_bind_hash != NULL) {
177 dccpnext->dccp_bind_hash->dccp_ptpbhn =
178 &(dccpnext->dccp_bind_hash);
179 dccp->dccp_bind_hash = NULL;
180 }
181 } else if ((dccpnext = dccp->dccp_bind_hash) != NULL) {
182 dccpnext->dccp_ptpbhn = dccp->dccp_ptpbhn;
183 dccp->dccp_bind_hash = NULL;
184 }
185 *dccp->dccp_ptpbhn = dccpnext;
186 dccp->dccp_ptpbhn = NULL;
187 }
188 mutex_exit(lockp);
189 }
190
191 /*
192 * Check for a valid address and get a local port.
193 */
194 int
195 dccp_bind_check(conn_t *connp, struct sockaddr *sa, socklen_t len, cred_t *cr,
196 boolean_t bind_to_req_port_only)
197 {
198 dccp_t *dccp = connp->conn_dccp;
199 ip_stack_t *ips = connp->conn_netstack->netstack_ip;
200 ip_xmit_attr_t *ixa = connp->conn_ixa;
201 sin_t *sin;
202 sin6_t *sin6;
203 ipaddr_t v4addr;
204 in6_addr_t v6addr;
205 ip_laddr_t laddr_type = IPVL_UNICAST_UP;
206 zoneid_t zoneid = IPCL_ZONEID(connp);
207 in_port_t requested_port;
208 uint_t scopeid = 0;
209 int error;
210
211 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bind_check");
212
213 ASSERT((uintptr_t)len <= (uintptr_t)INT_MAX);
214
215 /*
216 * We should be in a pre-listen state.
217 */
218 if (dccp->dccp_state == DCCPS_LISTEN) {
219 return (0);
220 } else if (dccp->dccp_state > DCCPS_LISTEN) {
221 if (connp->conn_debug) {
222 (void) strlog(DCCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
223 "dccp_bind: bad state, %d", dccp->dccp_state);
224 }
225 return (-TOUTSTATE);
226 }
227
228 /*
229 * Check for a valid address parameter. Then validate the
230 * addresses and copy them and the required port in.
231 */
232 ASSERT(sa != NULL && len != 0);
233
234 if (!OK_32PTR((char *)sa)) {
235 if (connp->conn_debug) {
236 (void) strlog(DCCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
237 "dccp_bind: bad address parameter, "
238 "address %p, len %d", (void *)sa, len);
239 }
240 return (-TPROTO);
241 }
242
243 error = proto_verify_ip_addr(connp->conn_family, sa, len);
244 if (error != 0) {
245 return (error);
246 }
247
248 switch (len) {
249 case sizeof (sin_t):
250 sin = (sin_t *)sa;
251 v4addr = sin->sin_addr.s_addr;
252 requested_port = ntohs(sin->sin_port);
253 IN6_IPADDR_TO_V4MAPPED(v4addr, &v6addr);
254 if (v4addr != INADDR_ANY) {
255 laddr_type = ip_laddr_verify_v4(v4addr, zoneid, ips,
256 B_FALSE);
257 }
258 break;
259
260 case sizeof (sin6_t):
261 sin6 = (sin6_t *)sa;
262 v6addr = sin6->sin6_addr;
263 requested_port = ntohs(sin6->sin6_port);
264 if (IN6_IS_ADDR_V4MAPPED(&v6addr)) {
265 if (connp->conn_ipv6_v6only) {
266 return (EADDRNOTAVAIL);
267 }
268
269 IN6_V4MAPPED_TO_IPADDR(&v6addr, v4addr);
270 if (v4addr != INADDR_ANY) {
271 laddr_type = ip_laddr_verify_v4(v4addr, zoneid,
272 ips, B_FALSE);
273 }
274 } else {
275 if (!IN6_IS_ADDR_UNSPECIFIED(&v6addr)) {
276 if (IN6_IS_ADDR_LINKSCOPE(&v6addr)) {
277 scopeid = sin6->sin6_scope_id;
278 laddr_type = ip_laddr_verify_v6(&v6addr,
279 zoneid, ips, B_FALSE, scopeid);
280 }
281 }
282 }
283 break;
284
285 default:
286 if (connp->conn_debug) {
287 (void) strlog(DCCP_MOD_ID, 0, 1, SL_ERROR|SL_TRACE,
288 "dccp_bind: bad address length, %d", len);
289 }
290 return (EAFNOSUPPORT);
291 }
292
293 if (laddr_type == IPVL_BAD) {
294 return (EADDRNOTAVAIL);
295 }
296
297 connp->conn_bound_addr_v6 = v6addr;
298 if (scopeid != 0) {
299 ixa->ixa_flags |= IXAF_SCOPEID_SET;
300 ixa->ixa_scopeid = scopeid;
301 connp->conn_incoming_ifindex = scopeid;
302 } else {
303 ixa->ixa_flags &= ~IXAF_SCOPEID_SET;
304 connp->conn_incoming_ifindex = connp->conn_bound_if;
305 }
306
307 connp->conn_laddr_v6 = v6addr;
308 connp->conn_saddr_v6 = v6addr;
309
310 bind_to_req_port_only = requested_port != 0 && bind_to_req_port_only;
311
312 error = dccp_bind_select_lport(dccp, &requested_port,
313 bind_to_req_port_only, cr);
314 if (error != 0) {
315 connp->conn_laddr_v6 = ipv6_all_zeros;
316 connp->conn_saddr_v6 = ipv6_all_zeros;
317 connp->conn_bound_addr_v6 = ipv6_all_zeros;
318 }
319
320 return (error);
321 }
322
323 /*
324 * Bind to a local port.
325 */
326 static int
327 dccp_bind_select_lport(dccp_t *dccp, in_port_t *requested_port_ptr,
328 boolean_t bind_to_req_port_only, cred_t *cr)
329 {
330 dccp_stack_t *dccps = dccp->dccp_dccps;
331 conn_t *connp = dccp->dccp_connp;
332 zone_t *zone;
333 in_port_t allocated_port;
334 in_port_t requested_port = *requested_port_ptr;
335 in_port_t mlp_port;
336 in6_addr_t v6addr = connp->conn_laddr_v6;
337 mlp_type_t addrtype;
338 mlp_type_t mlptype;
339 boolean_t user_specified;
340
341 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bind_select_lport");
342
343 ASSERT(cr != NULL);
344
345 mlptype = mlptSingle;
346 mlp_port = requested_port;
347 if (requested_port == 0) {
348 requested_port = connp->conn_anon_priv_bind ?
349 dccp_get_next_priv_port(dccp) :
350 dccp_update_next_port(dccps->dccps_next_port_to_try,
351 dccp, B_TRUE);
352 if (requested_port == 0) {
353 return (-TNOADDR);
354 }
355 user_specified = B_FALSE;
356
357 if (connp->conn_anon_mlp && is_system_labeled()) {
358 zone = crgetzone(cr);
359 addrtype = tsol_mlp_addr_type(
360 connp->conn_allzones ? ALL_ZONES : zone->zone_id,
361 IPV6_VERSION, &v6addr,
362 dccps->dccps_netstack->netstack_ip);
363 if (addrtype == mlptSingle) {
364 return (-TNOADDR);
365 }
366 mlptype = tsol_mlp_port_type(zone, IPPROTO_DCCP,
367 PMAPPORT, addrtype);
368 mlp_port = PMAPPORT;
369 }
370 } else {
371 int i;
372 boolean_t priv = B_FALSE;
373
374 if (requested_port < dccps->dccps_smallest_nonpriv_port) {
375 priv = B_TRUE;
376 } else {
377 for (i = 0; i < dccps->dccps_num_epriv_ports; i++) {
378 if (requested_port ==
379 dccps->dccps_epriv_ports[i]) {
380 priv = B_TRUE;
381 break;
382 }
383 }
384 }
385
386 if (priv) {
387 if (secpolicy_net_privaddr(cr, requested_port,
388 IPPROTO_DCCP) != 0) {
389 if (connp->conn_debug) {
390 (void) strlog(DCCP_MOD_ID, 0, 1,
391 SL_ERROR|SL_TRACE,
392 "tcp_bind: no priv for port %d",
393 requested_port);
394 }
395 return (-TACCES);
396 }
397 }
398
399 user_specified = B_TRUE;
400
401 connp = dccp->dccp_connp;
402 if (is_system_labeled()) {
403 zone = crgetzone(cr);
404 addrtype = tsol_mlp_addr_type(
405 connp->conn_allzones ? ALL_ZONES : zone->zone_id,
406 IPV6_VERSION, &v6addr,
407 dccps->dccps_netstack->netstack_ip);
408 if (addrtype == mlptSingle) {
409 return (-TNOADDR);
410 }
411 mlptype = tsol_mlp_port_type(zone, IPPROTO_DCCP,
412 requested_port, addrtype);
413 }
414 }
415
416 if (mlptype != mlptSingle) {
417 if (secpolicy_net_bindmlp(cr) != 0) {
418 if (connp->conn_debug) {
419 (void) strlog(DCCP_MOD_ID, 0, 1,
420 SL_ERROR|SL_TRACE,
421 "dccp_bind: no priv for multilevel port %d",
422 requested_port);
423 }
424 return (-TACCES);
425 }
426
427 /*
428 * If we're specifically binding a shared IP address and the
429 * port is MLP on shared addresses, then check to see if this
430 * zone actually owns the MLP. Reject if not.
431 */
432 if (mlptype == mlptShared && addrtype == mlptShared) {
433 /*
434 * No need to handle exclusive-stack zones since
435 * ALL_ZONES only applies to the shared stack.
436 */
437 zoneid_t mlpzone;
438
439 mlpzone = tsol_mlp_findzone(IPPROTO_DCCP,
440 htons(mlp_port));
441 if (connp->conn_zoneid != mlpzone) {
442 if (connp->conn_debug) {
443 (void) strlog(DCCP_MOD_ID, 0, 1,
444 SL_ERROR|SL_TRACE,
445 "dccp_bind: attempt to bind port "
446 "%d on shared addr in zone %d "
447 "(should be %d)",
448 mlp_port, connp->conn_zoneid,
449 mlpzone);
450 }
451 return (-TACCES);
452 }
453 }
454
455 if (!user_specified) {
456 int error;
457
458 error = tsol_mlp_anon(zone, mlptype, connp->conn_proto,
459 requested_port, B_TRUE);
460 if (error != 0) {
461 if (connp->conn_debug) {
462 (void) strlog(DCCP_MOD_ID, 0, 1,
463 SL_ERROR|SL_TRACE,
464 "dccp_bind: cannot establish anon "
465 "MLP for port %d",
466 requested_port);
467 }
468 return (error);
469 }
470 connp->conn_anon_port = B_TRUE;
471 }
472 connp->conn_mlp_type = mlptype;
473 }
474
475 allocated_port = dccp_bindi(dccp, requested_port, &v6addr,
476 connp->conn_reuseaddr, B_FALSE, bind_to_req_port_only,
477 user_specified);
478
479 if (allocated_port == 0) {
480 connp->conn_mlp_type = mlptSingle;
481
482 if (connp->conn_anon_port) {
483 connp->conn_anon_port = B_FALSE;
484 (void) tsol_mlp_anon(zone, mlptype, connp->conn_proto,
485 requested_port, B_FALSE);
486 }
487
488 if (bind_to_req_port_only) {
489 if (connp->conn_debug) {
490 (void) strlog(DCCP_MOD_ID, 0, 1,
491 SL_ERROR|SL_TRACE,
492 "dccp_bind: requested addr busy");
493 }
494 return (-TADDRBUSY);
495 } else {
496 /* If we are out of ports, fail the bind */
497 if (connp->conn_debug) {
498 (void) strlog(DCCP_MOD_ID, 0, 1,
499 SL_ERROR|SL_TRACE,
500 "dccp_bind: out of ports?");
501 }
502 return (-TNOADDR);
503 }
504 }
505
506 /* Pass the allocated port back */
507 *requested_port_ptr = allocated_port;
508 return (0);
509 }
510
511 in_port_t
512 dccp_bindi(dccp_t *dccp, in_port_t port, const in6_addr_t *laddr,
513 int reuseaddr, boolean_t quick_connect, boolean_t bind_to_req_port_only,
514 boolean_t user_specified)
515 {
516 dccp_stack_t *dccps = dccp->dccp_dccps;
517 conn_t *connp = dccp->dccp_connp;
518 int count = 0;
519 int loopmax;
520
521 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bindi");
522
523 if (bind_to_req_port_only) {
524 loopmax = 1;
525 } else {
526 if (connp->conn_anon_priv_bind) {
527 loopmax = IPPORT_RESERVED -
528 dccps->dccps_min_anonpriv_port;
529 } else {
530 loopmax = (dccps->dccps_largest_anon_port -
531 dccps->dccps_smallest_anon_port + 1);
532 }
533 }
534
535 do {
536 conn_t *lconnp;
537 dccp_t *ldccp;
538 dccp_df_t *ldf;
539 uint16_t lport;
540
541 lport = htons(port);
542
543 dccp_bind_hash_remove(dccp);
544 ldf = &dccps->dccps_bind_fanout[DCCP_BIND_HASH(lport,
545 dccps->dccps_bind_fanout_size)];
546 mutex_enter(&ldf->df_lock);
547 for (ldccp = ldf->df_dccp; ldccp != NULL;
548 ldccp = ldccp->dccp_bind_hash) {
549 if (lport == ldccp->dccp_connp->conn_lport) {
550 break;
551 }
552 }
553
554 if (ldccp != NULL) {
555 /* The port number is busy */
556 mutex_exit(&ldf->df_lock);
557 } else {
558 /*
559 * This port is ours. Insert in fanout and mark as
560 * bound to prevent others from getting the port
561 * number.
562 */
563 dccp->dccp_state = DCCPS_BOUND;
564 DTRACE_DCCP6(state__change, void, NULL,
565 ip_xmit_attr_t *, connp->conn_ixa,
566 void, NULL, dccp_t *, dccp, void, NULL,
567 int32_t, DCCPS_CLOSED);
568
569 connp->conn_lport = htons(port);
570
571 ASSERT(&dccps->dccps_bind_fanout[DCCP_BIND_HASH(
572 connp->conn_lport,
573 dccps->dccps_bind_fanout_size)] == ldf);
574 dccp_bind_hash_insert(ldf, dccp, 1);
575
576 mutex_exit(&ldf->df_lock);
577
578 /*
579 * We don't want tcp_next_port_to_try to "inherit"
580 * a port number supplied by the user in a bind.
581 */
582 if (user_specified) {
583 return (port);
584 }
585
586 /*
587 * This is the only place where dccp_next_port_to_try
588 * is updated. After the update, it may or may not
589 * be in the valid range.
590 */
591 if (!connp->conn_anon_priv_bind) {
592 dccps->dccps_next_port_to_try = port + 1;
593 }
594
595 return (port);
596 }
597
598 if (connp->conn_anon_priv_bind) {
599 port = dccp_get_next_priv_port(dccp);
600 } else {
601 if (count == 0 && user_specified) {
602 /*
603 * We may have to return an anonymous port. So
604 * get one to start with.
605 */
606 port =
607 dccp_update_next_port(
608 dccps->dccps_next_port_to_try,
609 dccp, B_TRUE);
610 user_specified = B_FALSE;
611 } else {
612 port = dccp_update_next_port(port + 1, dccp,
613 B_FALSE);
614 }
615 }
616
617 if (port == 0) {
618 break;
619 }
620
621 } while (++count < loopmax);
622
623 cmn_err(CE_NOTE, "dccp_bind.c: dccp_bindi exit");
624
625 return (0);
626 }
627
628 in_port_t
629 dccp_update_next_port(in_port_t port, const dccp_t *dccp, boolean_t random)
630 {
631 dccp_stack_t *dccps = dccp->dccp_dccps;
632 boolean_t restart = B_FALSE;
633 int i;
634
635 cmn_err(CE_NOTE, "dccp_bind.c: dccp_update_next_port");
636
637 if (random && dccp_random_anon_port != 0) {
638 (void) random_get_pseudo_bytes((uint8_t *)&port,
639 sizeof (in_port_t));
640
641 if (port < dccps->dccps_smallest_anon_port) {
642 port = dccps->dccps_smallest_anon_port +
643 port % (dccps->dccps_largest_anon_port -
644 dccps->dccps_smallest_anon_port);
645 }
646 }
647
648 retry:
649 if (port < dccps->dccps_smallest_anon_port) {
650 port = (in_port_t)dccps->dccps_smallest_anon_port;
651 }
652
653 if (port > dccps->dccps_largest_anon_port) {
654 if (restart) {
655 return (0);
656 }
657 restart = B_TRUE;
658 port = (in_port_t)dccps->dccps_smallest_anon_port;
659 }
660
661 if (port < dccps->dccps_smallest_nonpriv_port) {
662 port = (in_port_t)dccps->dccps_smallest_nonpriv_port;
663 }
664
665 for (i = 0; i < dccps->dccps_num_epriv_ports; i++) {
666 if (port == dccps->dccps_epriv_ports[i]) {
667 port++;
668 goto retry;
669 }
670 }
671
672 return (port);
673 }
674
675 /*
676 * Return the next anonymous port in the privileged port range for
677 * bind checking. It starts at IPPORT_RESERVED - 1 and goes
678 * downwards. This is the same behavior as documented in the userland
679 * library call rresvport(3N).
680 *
681 * TS note: skip multilevel ports.
682 */
683 static in_port_t
684 dccp_get_next_priv_port(const dccp_t *dccp)
685 {
686 static in_port_t next_priv_port = IPPORT_RESERVED - 1;
687 dccp_stack_t *dccps = dccp->dccp_dccps;
688 in_port_t nextport;
689 boolean_t restart = B_FALSE;
690
691 retry:
692 if (next_priv_port < dccps->dccps_min_anonpriv_port ||
693 next_priv_port >= IPPORT_RESERVED) {
694 next_priv_port = IPPORT_RESERVED - 1;
695 if (restart) {
696 return (0);
697 }
698 restart = B_TRUE;
699 }
700
701 if (is_system_labeled() &&
702 (nextport = tsol_next_port(crgetzone(dccp->dccp_connp->conn_cred),
703 next_priv_port, IPPROTO_DCCP, B_FALSE)) != 0) {
704 next_priv_port = nextport;
705 goto retry;
706 }
707
708 return (next_priv_port--);
709 }