xref: /original-bsd/sys/netiso/tp_output.c (revision 0997b878)
1 /*-
2  * Copyright (c) 1991, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  *
7  *	@(#)tp_output.c	8.2 (Berkeley) 02/09/95
8  */
9 
10 /***********************************************************
11 		Copyright IBM Corporation 1987
12 
13                       All Rights Reserved
14 
15 Permission to use, copy, modify, and distribute this software and its
16 documentation for any purpose and without fee is hereby granted,
17 provided that the above copyright notice appear in all copies and that
18 both that copyright notice and this permission notice appear in
19 supporting documentation, and that the name of IBM not be
20 used in advertising or publicity pertaining to distribution of the
21 software without specific, written prior permission.
22 
23 IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25 IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
29 SOFTWARE.
30 
31 ******************************************************************/
32 
33 /*
34  * ARGO Project, Computer Sciences Dept., University of Wisconsin - Madison
35  */
36 /*
37  * ARGO TP
38  *
39  * $Header: tp_output.c,v 5.4 88/11/18 17:28:08 nhall Exp $
40  * $Source: /usr/argo/sys/netiso/RCS/tp_output.c,v $
41  *
42  * In here is tp_ctloutput(), the guy called by [sg]etsockopt(),
43  */
44 
45 #include <sys/param.h>
46 #include <sys/mbuf.h>
47 #include <sys/systm.h>
48 #include <sys/socket.h>
49 #include <sys/socketvar.h>
50 #include <sys/protosw.h>
51 #include <sys/errno.h>
52 #include <sys/time.h>
53 #include <sys/kernel.h>
54 
55 #include <netiso/tp_param.h>
56 #include <netiso/tp_user.h>
57 #include <netiso/tp_stat.h>
58 #include <netiso/tp_ip.h>
59 #include <netiso/tp_clnp.h>
60 #include <netiso/tp_timer.h>
61 #include <netiso/argo_debug.h>
62 #include <netiso/tp_pcb.h>
63 #include <netiso/tp_trace.h>
64 
65 #define TPDUSIZESHIFT 24
66 #define CLASSHIFT 16
67 
68 /*
69  * NAME: 	tp_consistency()
70  *
71  * CALLED FROM:
72  * 	tp_ctloutput(), tp_input()
73  *
74  * FUNCTION and ARGUMENTS:
75  * 	Checks the consistency of options and tpdusize with class,
76  *	using the parameters passed in via (param).
77  *	(cmd) may be TP_STRICT or TP_FORCE or both.
78  *  Force means it will set all the values in (tpcb) to those in
79  *  the input arguements iff no errors were encountered.
80  *  Strict means that no inconsistency will be tolerated.  If it's
81  *  not used, checksum and tpdusize inconsistencies will be tolerated.
82  *  The reason for this is that in some cases, when we're negotiating down
83  *	from class  4, these options should be changed but should not
84  *  cause negotiation to fail.
85  *
86  * RETURNS
87  *  E* or EOK
88  *  E* if the various parms aren't ok for a given class
89  *  EOK if they are ok for a given class
90  */
91 
92 int
93 tp_consistency( tpcb, cmd, param )
94 	u_int cmd;
95 	struct tp_conn_param *param;
96 	struct tp_pcb *tpcb;
97 {
98 	register int	error = EOK;
99 	int 			class_to_use  = tp_mask_to_num(param->p_class);
100 
101 	IFTRACE(D_SETPARAMS)
102 		tptrace(TPPTmisc,
103 		"tp_consist enter class_to_use dontchange param.class cmd",
104 		class_to_use, param->p_dont_change_params, param->p_class, cmd);
105 	ENDTRACE
106 	IFDEBUG(D_SETPARAMS)
107 		printf("tp_consistency %s %s\n",
108 			cmd& TP_FORCE?	"TP_FORCE":	"",
109 			cmd& TP_STRICT?	"TP_STRICT":"");
110 	ENDDEBUG
111 	if ((cmd & TP_FORCE) && (param->p_dont_change_params)) {
112 		cmd &= ~TP_FORCE;
113 	}
114 	/* can switch net services within a domain, but
115 	 * cannot switch domains
116 	 */
117 	switch( param->p_netservice) {
118 	case ISO_CONS:
119 	case ISO_CLNS:
120 	case ISO_COSNS:
121 		/* param->p_netservice in ISO DOMAIN */
122 		if(tpcb->tp_domain != AF_ISO ) {
123 			error = EINVAL; goto done;
124 		}
125 		break;
126 	case IN_CLNS:
127 		/* param->p_netservice in INET DOMAIN */
128 		if( tpcb->tp_domain != AF_INET ) {
129 			error = EINVAL; goto done;
130 		}
131 		break;
132 		/* no others not possible-> netservice is a 2-bit field! */
133 	}
134 
135 	IFDEBUG(D_SETPARAMS)
136 		printf("p_class 0x%x, class_to_use 0x%x\n",  param->p_class,
137 			class_to_use);
138 	ENDDEBUG
139 	if(param->p_netservice > TP_MAX_NETSERVICES){
140 		error = EINVAL; goto done;
141 	}
142 	if( (param->p_class & TP_CLASSES_IMPLEMENTED) == 0 ) {
143 		error = EINVAL; goto done;
144 	}
145 	IFDEBUG(D_SETPARAMS)
146 		printf("Nretrans 0x%x\n",  param->p_Nretrans );
147 	ENDDEBUG
148 	if( ( param->p_Nretrans < 1 ) ||
149 		  (param->p_cr_ticks < 1) || (param->p_cc_ticks < 1) ) {
150 			/* bad for any class because negot has to be done a la class 4 */
151 			error = EINVAL; goto done;
152 	}
153 	IFDEBUG(D_SETPARAMS)
154 		printf("use_csum 0x%x\n",  param->p_use_checksum );
155 		printf("xtd_format 0x%x\n",  param->p_xtd_format );
156 		printf("xpd_service 0x%x\n",  param->p_xpd_service );
157 		printf("tpdusize 0x%x\n",  param->p_tpdusize );
158 		printf("tpcb->flags 0x%x\n",  tpcb->tp_flags );
159 	ENDDEBUG
160 	switch( class_to_use ) {
161 
162 	case 0:
163 		/* do not use checksums, xtd format, or XPD */
164 
165 		if( param->p_use_checksum | param->p_xtd_format | param->p_xpd_service ) {
166 			if(cmd & TP_STRICT) {
167 				error = EINVAL;
168 			} else {
169 				param->p_use_checksum = 0;
170 				param->p_xtd_format = 0;
171 				param->p_xpd_service = 0;
172 			}
173 			break;
174 		}
175 
176 		if (param->p_tpdusize < TP_MIN_TPDUSIZE) {
177 			if(cmd & TP_STRICT) {
178 				error = EINVAL;
179 			} else {
180 				param->p_tpdusize = TP_MIN_TPDUSIZE;
181 			}
182 			break;
183 		}
184 		if (param->p_tpdusize > TP0_TPDUSIZE)  {
185 			if (cmd & TP_STRICT) {
186 				error = EINVAL;
187 			} else {
188 				param->p_tpdusize = TP0_TPDUSIZE;
189 			}
190 			break;
191 		}
192 
193 		/* connect/disc data not allowed for class 0 */
194 		if (tpcb->tp_ucddata) {
195 			if(cmd & TP_STRICT) {
196 				error = EINVAL;
197 			} else if(cmd & TP_FORCE) {
198 				m_freem(tpcb->tp_ucddata);
199 				tpcb->tp_ucddata = 0;
200 			}
201 		}
202 		break;
203 
204 	case 4:
205 		IFDEBUG(D_SETPARAMS)
206 			printf("dt_ticks 0x%x\n",  param->p_dt_ticks );
207 			printf("x_ticks 0x%x\n",  param->p_x_ticks );
208 			printf("dr_ticks 0x%x\n",  param->p_dr_ticks );
209 			printf("keepalive 0x%x\n",  param->p_keepalive_ticks );
210 			printf("sendack 0x%x\n",  param->p_sendack_ticks );
211 			printf("inact 0x%x\n",  param->p_inact_ticks );
212 			printf("ref 0x%x\n",  param->p_ref_ticks );
213 		ENDDEBUG
214 		if( (param->p_class & TP_CLASS_4 ) && (
215 			  (param->p_dt_ticks < 1) || (param->p_dr_ticks < 1) ||
216 			  (param->p_x_ticks < 1)	|| (param->p_keepalive_ticks < 1) ||
217 			  (param->p_sendack_ticks < 1) || (param->p_ref_ticks < 1) ||
218 			  (param->p_inact_ticks < 1) ) ) {
219 				error = EINVAL;
220 				break;
221 		}
222 		IFDEBUG(D_SETPARAMS)
223 			printf("rx_strat 0x%x\n",  param->p_rx_strat );
224 		ENDDEBUG
225 		if(param->p_rx_strat >
226 			( TPRX_USE_CW | TPRX_EACH | TPRX_FASTSTART) ) {
227 				if(cmd & TP_STRICT) {
228 					error = EINVAL;
229 				} else {
230 					param->p_rx_strat = TPRX_USE_CW;
231 				}
232 				break;
233 		}
234 		IFDEBUG(D_SETPARAMS)
235 			printf("ack_strat 0x%x\n",  param->p_ack_strat );
236 		ENDDEBUG
237 		if((param->p_ack_strat != 0) && (param->p_ack_strat != 1)) {
238 			if(cmd & TP_STRICT) {
239 				error = EINVAL;
240 			} else {
241 				param->p_ack_strat = TPACK_WINDOW;
242 			}
243 			break;
244 		}
245 		if (param->p_tpdusize < TP_MIN_TPDUSIZE) {
246 			if(cmd & TP_STRICT) {
247 				error = EINVAL;
248 			} else {
249 				param->p_tpdusize = TP_MIN_TPDUSIZE;
250 			}
251 			break;
252 		}
253 		if (param->p_tpdusize > TP_TPDUSIZE)  {
254 			if(cmd & TP_STRICT) {
255 				error = EINVAL;
256 			} else {
257 				param->p_tpdusize = TP_TPDUSIZE;
258 			}
259 			break;
260 		}
261 		break;
262 	}
263 
264 	if ((error==0) && (cmd & TP_FORCE)) {
265 		long dusize = ((long)param->p_ptpdusize) << 7;
266 		/* Enforce Negotation rules below */
267 		tpcb->tp_class = param->p_class;
268 		if (tpcb->tp_use_checksum || param->p_use_checksum)
269 			tpcb->tp_use_checksum = 1;
270 		if (!tpcb->tp_xpd_service || !param->p_xpd_service)
271 			tpcb->tp_xpd_service = 0;
272 		if (!tpcb->tp_xtd_format || !param->p_xtd_format)
273 			tpcb->tp_xtd_format = 0;
274 		if (dusize) {
275 			if (tpcb->tp_l_tpdusize > dusize)
276 				tpcb->tp_l_tpdusize = dusize;
277 			if (tpcb->tp_ptpdusize == 0 ||
278 				tpcb->tp_ptpdusize > param->p_ptpdusize)
279 				tpcb->tp_ptpdusize = param->p_ptpdusize;
280 		} else {
281 			if (param->p_tpdusize != 0 &&
282 				tpcb->tp_tpdusize > param->p_tpdusize)
283 				tpcb->tp_tpdusize = param->p_tpdusize;
284 			tpcb->tp_l_tpdusize = 1 << tpcb->tp_tpdusize;
285 		}
286 	}
287 done:
288 
289 	IFTRACE(D_CONN)
290 		tptrace(TPPTmisc, "tp_consist returns class xtdfmt cmd",
291 			error, tpcb->tp_class, tpcb->tp_xtd_format, cmd);
292 	ENDTRACE
293 	IFDEBUG(D_CONN)
294 		printf(
295 		"tp_consist rtns 0x%x class 0x%x xtd_fmt 0x%x cmd 0x%x\n",
296 			error, tpcb->tp_class, tpcb->tp_xtd_format, cmd);
297 	ENDDEBUG
298 	return error;
299 }
300 
301 /*
302  * NAME: 	tp_ctloutput()
303  *
304  * CALLED FROM:
305  * 	[sg]etsockopt(), via so[sg]etopt().
306  *
307  * FUNCTION and ARGUMENTS:
308  * 	Implements the socket options at transport level.
309  * 	(cmd) is either PRCO_SETOPT or PRCO_GETOPT (see ../sys/protosw.h).
310  * 	(so) is the socket.
311  * 	(level) is SOL_TRANSPORT (see ../sys/socket.h)
312  * 	(optname) is the particular command or option to be set.
313  * 	(**mp) is an mbuf structure.
314  *
315  * RETURN VALUE:
316  * 	ENOTSOCK if the socket hasn't got an associated tpcb
317  *  EINVAL if
318  * 		trying to set window too big
319  * 		trying to set illegal max tpdu size
320  * 		trying to set illegal credit fraction
321  * 		trying to use unknown or unimplemented class of TP
322  *		structure passed to set timer values is wrong size
323  *  	illegal combination of command/GET-SET option,
324  *			e.g., GET w/ TPOPT_CDDATA_CLEAR:
325  *  EOPNOTSUPP if the level isn't transport, or command is neither GET nor SET
326  *   or if the transport-specific command is not implemented
327  *  EISCONN if trying a command that isn't allowed after a connection
328  *   is established
329  *  ENOTCONN if trying a command that is allowed only if a connection is
330  *   established
331  *  EMSGSIZE if trying to give too much data on connect/disconnect
332  *
333  * SIDE EFFECTS:
334  *
335  * NOTES:
336  */
337 ProtoHook
338 tp_ctloutput(cmd, so, level, optname, mp)
339 	int 			cmd, level, optname;
340 	struct socket	*so;
341 	struct mbuf 	**mp;
342 {
343 	struct		tp_pcb	*tpcb = sototpcb(so);
344 	int 		s = splnet();
345 	caddr_t		value;
346 	unsigned	val_len;
347 	int			error = 0;
348 
349 	IFTRACE(D_REQUEST)
350 		tptrace(TPPTmisc, "tp_ctloutput cmd so optname mp",
351 			cmd, so, optname, mp);
352 	ENDTRACE
353 	IFDEBUG(D_REQUEST)
354 		printf(
355 	"tp_ctloutput so 0x%x cmd 0x%x optname 0x%x, mp 0x%x *mp 0x%x tpcb 0x%x\n",
356 			so, cmd, optname, mp, mp?*mp:0, tpcb);
357 	ENDDEBUG
358 	if( tpcb == (struct tp_pcb *)0 ) {
359 		error = ENOTSOCK; goto done;
360 	}
361 	if(*mp == MNULL) {
362 		register struct mbuf *m;
363 
364 		MGET(m, M_DONTWAIT, TPMT_SONAME); /* does off, type, next */
365 		if (m == NULL) {
366 			splx(s);
367 			return ENOBUFS;
368 		}
369 		m->m_len = 0;
370 		m->m_act = 0;
371 		*mp = m;
372 	}
373 
374 	/*
375 	 *	Hook so one can set network options via a tp socket.
376 	 */
377 	if ( level == SOL_NETWORK ) {
378 		if ((tpcb->tp_nlproto == NULL) || (tpcb->tp_npcb == NULL))
379 			error = ENOTSOCK;
380 		else if (tpcb->tp_nlproto->nlp_ctloutput == NULL)
381 			error = EOPNOTSUPP;
382 		else
383 			return ((tpcb->tp_nlproto->nlp_ctloutput)(cmd, optname,
384 				tpcb->tp_npcb, *mp));
385 		goto done;
386 	} else if ( level == SOL_SOCKET) {
387 		if (optname == SO_RCVBUF && cmd == PRCO_SETOPT) {
388 			u_long old_credit = tpcb->tp_maxlcredit;
389 			tp_rsyset(tpcb);
390 			if (tpcb->tp_rhiwat != so->so_rcv.sb_hiwat &&
391 			    tpcb->tp_state == TP_OPEN &&
392 			    (old_credit < tpcb->tp_maxlcredit))
393 				tp_emit(AK_TPDU_type, tpcb,
394 					tpcb->tp_rcvnxt, 0, MNULL);
395 			tpcb->tp_rhiwat = so->so_rcv.sb_hiwat;
396 		}
397 		goto done;
398 	} else if ( level !=  SOL_TRANSPORT ) {
399 		error = EOPNOTSUPP; goto done;
400 	}
401 	if (cmd != PRCO_GETOPT && cmd != PRCO_SETOPT) {
402 		error = EOPNOTSUPP; goto done;
403 	}
404 	if ( so->so_error ) {
405 		error = so->so_error; goto done;
406 	}
407 
408 	/* The only options allowed after connection is established
409 	 * are GET (anything) and SET DISC DATA and SET PERF MEAS
410 	 */
411 	if ( ((so->so_state & SS_ISCONNECTING)||(so->so_state & SS_ISCONNECTED))
412 		&&
413 		(cmd == PRCO_SETOPT  &&
414 			optname != TPOPT_DISC_DATA &&
415 			optname != TPOPT_CFRM_DATA &&
416 			optname != TPOPT_PERF_MEAS &&
417 			optname != TPOPT_CDDATA_CLEAR ) ) {
418 		error = EISCONN; goto done;
419 	}
420 	/* The only options allowed after disconnection are GET DISC DATA,
421 	 * and TPOPT_PSTATISTICS
422 	 * and they're not allowed if the ref timer has gone off, because
423 	 * the tpcb is gone
424 	 */
425 	if ((so->so_state & (SS_ISCONNECTED | SS_ISCONFIRMING)) ==  0) {
426 		if ( so->so_pcb == (caddr_t)0 ) {
427 			error = ENOTCONN; goto done;
428 		}
429 		if ( (tpcb->tp_state == TP_REFWAIT || tpcb->tp_state == TP_CLOSING) &&
430 				(optname != TPOPT_DISC_DATA && optname != TPOPT_PSTATISTICS)) {
431 			error = ENOTCONN; goto done;
432 		}
433 	}
434 
435 	value = mtod(*mp, caddr_t);  /* it's aligned, don't worry,
436 								  * but lint complains about it
437 								  */
438 	val_len = (*mp)->m_len;
439 
440 	switch (optname) {
441 
442 	case TPOPT_INTERCEPT:
443 #define INA(t) (((struct inpcb *)(t->tp_npcb))->inp_laddr.s_addr)
444 #define ISOA(t) (((struct isopcb *)(t->tp_npcb))->isop_laddr->siso_addr)
445 
446 		if ((so->so_state & SS_PRIV) == 0) {
447 			error = EPERM;
448 		} else if (cmd != PRCO_SETOPT || tpcb->tp_state != TP_CLOSED ||
449 					(tpcb->tp_flags & TPF_GENERAL_ADDR) ||
450 					tpcb->tp_next == 0)
451 			error = EINVAL;
452 		else {
453 			register struct tp_pcb *t;
454 			error = EADDRINUSE;
455 			for (t = tp_listeners; t; t = t->tp_nextlisten)
456 				if ((t->tp_flags & TPF_GENERAL_ADDR) == 0 &&
457 						t->tp_domain == tpcb->tp_domain)
458 					switch (tpcb->tp_domain) {
459 					default:
460 						goto done;
461 #ifdef	INET
462 					case AF_INET:
463 						if (INA(t) == INA(tpcb))
464 							goto done;
465 						continue;
466 #endif
467 #ifdef ISO
468 					case AF_ISO:
469 						if (bcmp(ISOA(t).isoa_genaddr, ISOA(tpcb).isoa_genaddr,
470 										ISOA(t).isoa_len) == 0)
471 							goto done;
472 						continue;
473 #endif
474 					}
475 			tpcb->tp_lsuffixlen = 0;
476 			tpcb->tp_state = TP_LISTENING;
477 			error = 0;
478 			remque(tpcb);
479 			tpcb->tp_next = tpcb->tp_prev = tpcb;
480 			tpcb->tp_nextlisten = tp_listeners;
481 			tp_listeners = tpcb;
482 		}
483 		break;
484 
485 	case TPOPT_MY_TSEL:
486 		if ( cmd == PRCO_GETOPT ) {
487 			ASSERT( tpcb->tp_lsuffixlen <= MAX_TSAP_SEL_LEN );
488 			bcopy((caddr_t)tpcb->tp_lsuffix, value, tpcb->tp_lsuffixlen);
489 			(*mp)->m_len = tpcb->tp_lsuffixlen;
490 		} else /* cmd == PRCO_SETOPT  */ {
491 			if( (val_len > MAX_TSAP_SEL_LEN) || (val_len <= 0 )) {
492 				printf("val_len 0x%x (*mp)->m_len 0x%x\n", val_len, (*mp));
493 				error = EINVAL;
494 			} else {
495 				bcopy(value, (caddr_t)tpcb->tp_lsuffix, val_len);
496 				tpcb->tp_lsuffixlen = val_len;
497 			}
498 		}
499 		break;
500 
501 	case TPOPT_PEER_TSEL:
502 		if ( cmd == PRCO_GETOPT ) {
503 			ASSERT( tpcb->tp_fsuffixlen <= MAX_TSAP_SEL_LEN );
504 			bcopy((caddr_t)tpcb->tp_fsuffix, value, tpcb->tp_fsuffixlen);
505 			(*mp)->m_len = tpcb->tp_fsuffixlen;
506 		} else /* cmd == PRCO_SETOPT  */ {
507 			if( (val_len > MAX_TSAP_SEL_LEN) || (val_len <= 0 )) {
508 				printf("val_len 0x%x (*mp)->m_len 0x%x\n", val_len, (*mp));
509 				error = EINVAL;
510 			} else {
511 				bcopy(value, (caddr_t)tpcb->tp_fsuffix, val_len);
512 				tpcb->tp_fsuffixlen = val_len;
513 			}
514 		}
515 		break;
516 
517 	case TPOPT_FLAGS:
518 		IFDEBUG(D_REQUEST)
519 			printf("%s TPOPT_FLAGS value 0x%x *value 0x%x, flags 0x%x \n",
520 				cmd==PRCO_GETOPT?"GET":"SET",
521 				value,
522 				*value,
523 				tpcb->tp_flags);
524 		ENDDEBUG
525 
526 		if ( cmd == PRCO_GETOPT ) {
527 			*(int *)value = (int)tpcb->tp_flags;
528 			(*mp)->m_len = sizeof(u_int);
529 		} else /* cmd == PRCO_SETOPT  */ {
530 			error = EINVAL; goto done;
531 		}
532 		break;
533 
534 	case TPOPT_PARAMS:
535 		/* This handles:
536 		 * timer values,
537 		 * class, use of transport expedited data,
538 		 * max tpdu size, checksum, xtd format and
539 		 * disconnect indications, and may get rid of connect/disc data
540 		 */
541 		IFDEBUG(D_SETPARAMS)
542 			printf("TPOPT_PARAMS value 0x%x, cmd %s \n", value,
543 				cmd==PRCO_GETOPT?"GET":"SET");
544 		ENDDEBUG
545 		IFDEBUG(D_REQUEST)
546 			printf("TPOPT_PARAMS value 0x%x, cmd %s \n", value,
547 				cmd==PRCO_GETOPT?"GET":"SET");
548 		ENDDEBUG
549 
550 		if ( cmd == PRCO_GETOPT ) {
551 			*(struct tp_conn_param *)value = tpcb->_tp_param;
552 			(*mp)->m_len = sizeof(tpcb->_tp_param);
553 		} else /* cmd == PRCO_SETOPT  */ {
554 			if( (error =
555 				tp_consistency(tpcb, TP_STRICT | TP_FORCE,
556 								(struct tp_conn_param *)value))==0) {
557 				/*
558 				 * tp_consistency doesn't copy the whole set of params
559 				 */
560 				tpcb->_tp_param = *(struct tp_conn_param *)value;
561 				(*mp)->m_len = sizeof(tpcb->_tp_param);
562 			}
563 		}
564 		break;
565 
566 	case TPOPT_PSTATISTICS:
567 #ifdef TP_PERF_MEAS
568 		if (cmd == PRCO_SETOPT) {
569 			error = EINVAL; goto done;
570 		}
571 		IFPERF(tpcb)
572 			if (*mp) {
573 				struct mbuf * n;
574 				do {
575 					MFREE(*mp, n);
576 					*mp = n;
577 				} while (n);
578 			}
579 			*mp = m_copym(tpcb->tp_p_mbuf, (int)M_COPYALL, M_WAITOK);
580 		ENDPERF
581 		else {
582 			error = EINVAL; goto done;
583 		}
584 		break;
585 #else
586 		error = EOPNOTSUPP;
587 		goto done;
588 #endif /* TP_PERF_MEAS */
589 
590 	case TPOPT_CDDATA_CLEAR:
591 		if (cmd == PRCO_GETOPT) {
592 			error = EINVAL;
593 		} else {
594 			if (tpcb->tp_ucddata) {
595 				m_freem(tpcb->tp_ucddata);
596 				tpcb->tp_ucddata = 0;
597 			}
598 		}
599 		break;
600 
601 	case TPOPT_CFRM_DATA:
602 	case TPOPT_DISC_DATA:
603 	case TPOPT_CONN_DATA:
604 		if( tpcb->tp_class == TP_CLASS_0 ) {
605 			error = EOPNOTSUPP;
606 			break;
607 		}
608 		IFDEBUG(D_REQUEST)
609 			printf("%s\n", optname==TPOPT_DISC_DATA?"DISC data":"CONN data");
610 			printf("m_len 0x%x, vallen 0x%x so_snd.cc 0x%x\n",
611 				(*mp)->m_len, val_len, so->so_snd.sb_cc);
612 			dump_mbuf(so->so_snd.sb_mb, "tp_ctloutput: sosnd ");
613 		ENDDEBUG
614 		if (cmd == PRCO_SETOPT) {
615 			int len = tpcb->tp_ucddata ?  tpcb->tp_ucddata->m_len : 0;
616 			/* can append connect data in several calls */
617 			if (len + val_len >
618 				(optname==TPOPT_CONN_DATA?TP_MAX_CR_DATA:TP_MAX_DR_DATA) ) {
619 				error = EMSGSIZE; goto done;
620 			}
621 			(*mp)->m_next = MNULL;
622 			(*mp)->m_act = 0;
623 			if (tpcb->tp_ucddata)
624 				m_cat(tpcb->tp_ucddata, *mp);
625 			else
626 				tpcb->tp_ucddata = *mp;
627 			IFDEBUG(D_REQUEST)
628 				dump_mbuf(tpcb->tp_ucddata, "tp_ctloutput after CONN_DATA");
629 			ENDDEBUG
630 			IFTRACE(D_REQUEST)
631 				tptrace(TPPTmisc,"C/D DATA: flags snd.sbcc val_len",
632 					tpcb->tp_flags, so->so_snd.sb_cc,val_len,0);
633 			ENDTRACE
634 			*mp = MNULL;
635 			if (optname == TPOPT_CFRM_DATA && (so->so_state & SS_ISCONFIRMING))
636 				(void) tp_confirm(tpcb);
637 		}
638 		break;
639 
640 	case TPOPT_PERF_MEAS:
641 #ifdef TP_PERF_MEAS
642 		if (cmd == PRCO_GETOPT) {
643 			*value = (u_int)tpcb->tp_perf_on;
644 			(*mp)->m_len = sizeof(u_int);
645 		} else if (cmd == PRCO_SETOPT) {
646 			(*mp)->m_len = 0;
647 			if ((*value) != 0 && (*value) != 1 )
648 				error = EINVAL;
649 			else  tpcb->tp_perf_on = (*value);
650 		}
651 		if( tpcb->tp_perf_on )
652 			error = tp_setup_perf(tpcb);
653 #else  /* TP_PERF_MEAS */
654 		error = EOPNOTSUPP;
655 #endif /* TP_PERF_MEAS */
656 		break;
657 
658 	default:
659 		error = EOPNOTSUPP;
660 	}
661 
662 done:
663 	IFDEBUG(D_REQUEST)
664 		dump_mbuf(so->so_snd.sb_mb, "tp_ctloutput sosnd at end");
665 		dump_mbuf(*mp, "tp_ctloutput *mp");
666 	ENDDEBUG
667 	/*
668 	 * sigh: getsockopt looks only at m_len : all output data must
669 	 * reside in the first mbuf
670 	 */
671 	if (*mp) {
672 		if (cmd == PRCO_SETOPT) {
673 			m_freem(*mp);
674 			*mp = MNULL;
675 		} else {
676 			ASSERT ( m_compress(*mp, mp) <= MLEN );
677 			if (error)
678 				(*mp)->m_len = 0;
679 			IFDEBUG(D_REQUEST)
680 				dump_mbuf(*mp, "tp_ctloutput *mp after compress");
681 			ENDDEBUG
682 		}
683 	}
684 	splx(s);
685 	return error;
686 }
687