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