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