ip_helper_stream.c revision 8492:345d87e0c40a
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 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#include <sys/types.h>
28#include <inet/ip.h>
29#include <inet/ip_impl.h>
30#include <inet/ipclassifier.h>
31#include <inet/proto_set.h>
32#include <sys/stream.h>
33#include <sys/strsubr.h>
34#include <sys/strsun.h>
35#include <sys/cmn_err.h>
36#include <sys/t_kuser.h>
37#include <sys/tihdr.h>
38#include <sys/pathname.h>
39#include <sys/sockio.h>
40#include <sys/vmem.h>
41#include <sys/disp.h>
42
43void ip_helper_wput(queue_t *q, mblk_t *mp);
44
45static int ip_helper_stream_close(queue_t *, int);
46
47static struct module_info ip_helper_stream_info =  {
48	0, "iphelper", IP_MOD_MINPSZ, IP_MOD_MAXPSZ, IP_MOD_HIWAT, IP_MOD_LOWAT
49};
50
51static struct qinit ip_helper_stream_rinit = {
52	NULL, NULL, NULL, ip_helper_stream_close, NULL,
53	&ip_helper_stream_info, NULL
54};
55
56static struct qinit ip_helper_stream_winit = {
57	(pfi_t)ip_helper_wput, (pfi_t)ip_wsrv, NULL, NULL, NULL,
58	&ip_helper_stream_info, NULL, NULL, NULL, STRUIOT_NONE
59};
60
61#define	IP_USE_HELPER_CACHE	(ip_helper_stream_cache != NULL)
62
63/*
64 * set the q_ptr of the 'q' to the conn_t pointer passed in
65 */
66static void
67ip_helper_share_conn(queue_t *q, mblk_t *mp, cred_t *crp)
68{
69	/*
70	 * This operation is allowed only on helper streams with kcred
71	 */
72
73	if (kcred != crp || msgdsize(mp->b_cont) != sizeof (void *)) {
74		miocnak(q, mp, 0, EINVAL);
75		return;
76	}
77
78	if (IP_USE_HELPER_CACHE) {
79		ip_helper_stream_info_t	*ip_helper_info;
80
81		ip_helper_info = *((ip_helper_stream_info_t **)
82		    mp->b_cont->b_rptr);
83		ip_helper_info->iphs_minfo = q->q_ptr;
84		ip_helper_info->iphs_rq = RD(q);
85		ip_helper_info->iphs_wq = WR(q);
86	} else {
87		conn_t *connp = *((conn_t **)mp->b_cont->b_rptr);
88
89		connp->conn_helper_info->iphs_minfo = q->q_ptr;
90		connp->conn_helper_info->iphs_rq = RD(q);
91		connp->conn_helper_info->iphs_wq = WR(q);
92		WR(q)->q_ptr = RD(q)->q_ptr = (void *)connp;
93		connp->conn_rq = RD(q);
94		connp->conn_wq = WR(q);
95	}
96	miocack(q, mp, 0, 0);
97}
98
99void
100ip_helper_wput(queue_t *q, mblk_t *mp)
101{
102	struct iocblk *iocp = (struct iocblk *)mp->b_rptr;
103	if (DB_TYPE(mp) == M_IOCTL &&
104	    iocp->ioc_cmd == SIOCSQPTR) {
105		ip_helper_share_conn(q, mp, iocp->ioc_cr);
106	} else {
107		conn_t *connp = (conn_t *)q->q_ptr;
108
109		if (connp->conn_af_isv6) {
110			ip_wput_v6(q, mp);
111		} else {
112			ip_wput(q, mp);
113		}
114	}
115}
116
117/* ARGSUSED */
118int
119ip_helper_stream_setup(queue_t *q, dev_t *devp, int flag, int sflag,
120    cred_t *credp, boolean_t isv6)
121{
122	major_t			maj;
123	ip_helper_minfo_t	*ip_minfop;
124
125	ASSERT((flag & ~(FKLYR)) == IP_HELPER_STR);
126
127	ASSERT(RD(q) == q);
128
129	ip_minfop = kmem_alloc(sizeof (ip_helper_minfo_t), KM_NOSLEEP);
130	if (ip_minfop == NULL) {
131		return (ENOMEM);
132	}
133
134	ip_minfop->ip_minfo_dev = 0;
135	ip_minfop->ip_minfo_arena = NULL;
136
137	/*
138	 * Clone the device, allocate minor device number
139	 */
140	if (ip_minor_arena_la != NULL)
141		ip_minfop->ip_minfo_dev = inet_minor_alloc(ip_minor_arena_la);
142
143	if (ip_minfop->ip_minfo_dev == 0) {
144		/*
145		 * numbers in the large arena are exhausted
146		 * Try small arena.
147		 * Or this is a 32 bit system, 32 bit systems do not have
148		 * ip_minor_arena_la
149		 */
150		ip_minfop->ip_minfo_dev = inet_minor_alloc(ip_minor_arena_sa);
151		if (ip_minfop->ip_minfo_dev == 0) {
152			return (EBUSY);
153		}
154		ip_minfop->ip_minfo_arena = ip_minor_arena_sa;
155	} else {
156		ip_minfop->ip_minfo_arena = ip_minor_arena_la;
157	}
158
159
160	ASSERT(ip_minfop->ip_minfo_dev != 0);
161	ASSERT(ip_minfop->ip_minfo_arena != NULL);
162
163	RD(q)->q_ptr = WR(q)->q_ptr = ip_minfop;
164
165	maj = getemajor(*devp);
166	*devp = makedevice(maj, (ulong_t)(ip_minfop->ip_minfo_dev));
167
168	q->q_qinfo = &ip_helper_stream_rinit;
169	WR(q)->q_qinfo = &ip_helper_stream_winit;
170	qprocson(q);
171	return (0);
172}
173
174/* ARGSUSED */
175static int
176ip_helper_stream_close(queue_t *q, int flag)
177{
178	ip_helper_minfo_t *ip_minfop;
179
180	qprocsoff(q);
181	ip_minfop = (q)->q_ptr;
182	inet_minor_free(ip_minfop->ip_minfo_arena,
183	    ip_minfop->ip_minfo_dev);
184	kmem_free(ip_minfop, sizeof (ip_helper_minfo_t));
185	RD(q)->q_ptr = NULL;
186	WR(q)->q_ptr = NULL;
187	return (0);
188}
189
190/*
191 * Public interface for creating an IP stream with shared conn_t
192 */
193/* ARGSUSED */
194int
195ip_create_helper_stream(conn_t *connp, ldi_ident_t li)
196{
197	int	error;
198	int	ret;
199
200	ASSERT(!servicing_interrupt());
201
202	error = 0;
203	if (IP_USE_HELPER_CACHE) {
204		connp->conn_helper_info = kmem_cache_alloc(
205		    ip_helper_stream_cache, KM_NOSLEEP);
206		if (connp->conn_helper_info == NULL)
207			return (EAGAIN);
208		connp->conn_rq = connp->conn_helper_info->iphs_rq;
209		connp->conn_wq = connp->conn_helper_info->iphs_wq;
210		/*
211		 * Doesn't need to hold the QLOCK for there is no one else
212		 * should have a pointer to this queue.
213		 */
214		connp->conn_rq->q_flag |= QWANTR;
215		connp->conn_wq->q_flag |= QWANTR;
216
217		connp->conn_rq->q_ptr = connp;
218		connp->conn_wq->q_ptr = connp;
219	} else {
220		ASSERT(connp->conn_helper_info == NULL);
221		connp->conn_helper_info = kmem_alloc(
222		    sizeof (ip_helper_stream_info_t), KM_SLEEP);
223		/*
224		 * open ip device via the layered interface.
225		 * pass in kcred as some threads do not have the
226		 * priviledge to open /dev/ip and the check in
227		 * secpolicy_spec_open() will fail the open
228		 */
229		error = ldi_open_by_name(connp->conn_af_isv6 ?
230		    DEV_IP6 : DEV_IP, IP_HELPER_STR,
231		    kcred, &connp->conn_helper_info->iphs_handle, li);
232
233		if (error != 0) {
234			kmem_free(connp->conn_helper_info,
235			    (sizeof (ip_helper_stream_info_t)));
236			connp->conn_helper_info = NULL;
237			return (error);
238		}
239		/*
240		 * Share connp with the helper stream
241		 */
242		error = ldi_ioctl(connp->conn_helper_info->iphs_handle,
243		    SIOCSQPTR, (intptr_t)connp, FKIOCTL, kcred, &ret);
244
245		if (error != 0) {
246			/*
247			 * Passing in a zero flag indicates that an error
248			 * occured and stream was not shared
249			 */
250			(void) ldi_close(connp->conn_helper_info->iphs_handle,
251			    0, kcred);
252			kmem_free(connp->conn_helper_info,
253			    (sizeof (ip_helper_stream_info_t)));
254			connp->conn_helper_info = NULL;
255		}
256	}
257	return (error);
258}
259
260/*
261 * Public interface for freeing IP helper stream
262 */
263/* ARGSUSED */
264void
265ip_free_helper_stream(conn_t *connp)
266{
267	ASSERT(!servicing_interrupt());
268	if (IP_USE_HELPER_CACHE) {
269
270		if (connp->conn_helper_info == NULL)
271			return;
272		ASSERT(connp->conn_helper_info->iphs_rq != NULL);
273		ASSERT(connp->conn_helper_info->iphs_wq != NULL);
274
275		/* Prevent service procedures from being called */
276		disable_svc(connp->conn_helper_info->iphs_rq);
277
278		/* Wait until service procedure of each queue is run */
279		wait_svc(connp->conn_helper_info->iphs_rq);
280
281		/* Cleanup any pending ioctls */
282		conn_ioctl_cleanup(connp);
283
284		/* Allow service procedures to be called again */
285		enable_svc(connp->conn_helper_info->iphs_rq);
286
287		/* Flush the queues */
288		flushq(connp->conn_helper_info->iphs_rq, FLUSHALL);
289		flushq(connp->conn_helper_info->iphs_wq, FLUSHALL);
290
291		connp->conn_helper_info->iphs_rq->q_ptr = NULL;
292		connp->conn_helper_info->iphs_wq->q_ptr = NULL;
293
294		kmem_cache_free(ip_helper_stream_cache,
295		    connp->conn_helper_info);
296	} else {
297		ASSERT(
298		    connp->conn_helper_info->iphs_handle != NULL);
299
300		connp->conn_helper_info->iphs_rq->q_ptr =
301		    connp->conn_helper_info->iphs_wq->q_ptr =
302		    connp->conn_helper_info->iphs_minfo;
303		(void) ldi_close(connp->conn_helper_info->iphs_handle,
304		    IP_HELPER_STR, kcred);
305		kmem_free(connp->conn_helper_info,
306		    sizeof (ip_helper_stream_info_t));
307	}
308	connp->conn_helper_info = NULL;
309}
310
311/*
312 * create a T_SVR4_OPTMGMT_REQ TPI message and send down the IP stream
313 */
314static int
315ip_send_option_request(conn_t *connp, uint_t optset_context, int level,
316    int option_name, const void *optval, t_uscalar_t optlen, cred_t *cr)
317{
318	struct T_optmgmt_req	*optmgmt_reqp;
319	struct opthdr		*ohp;
320	ssize_t			size;
321	mblk_t			*mp;
322
323	size = sizeof (struct T_optmgmt_req) + sizeof (struct opthdr) + optlen;
324	mp = allocb_cred(size, cr);
325	if (mp == NULL)
326		return (ENOMEM);
327
328	mp->b_datap->db_type = M_PROTO;
329	optmgmt_reqp = (struct T_optmgmt_req *)mp->b_wptr;
330
331	optmgmt_reqp->PRIM_type = T_SVR4_OPTMGMT_REQ;
332	optmgmt_reqp->MGMT_flags = optset_context;
333	optmgmt_reqp->OPT_length = (t_scalar_t)sizeof (struct opthdr) + optlen;
334	optmgmt_reqp->OPT_offset = (t_scalar_t)sizeof (struct T_optmgmt_req);
335
336	mp->b_wptr += sizeof (struct T_optmgmt_req);
337
338	ohp = (struct opthdr *)mp->b_wptr;
339
340	ohp->level = level;
341	ohp->name = option_name;
342	ohp->len = optlen;
343
344	mp->b_wptr += sizeof (struct opthdr);
345
346	if (optval != NULL) {
347		bcopy(optval, mp->b_wptr, optlen);
348	} else {
349		bzero(mp->b_wptr, optlen);
350	}
351	mp->b_wptr += optlen;
352
353	/*
354	 * Send down the primitive
355	 */
356	return (ldi_putmsg(connp->conn_helper_info->iphs_handle, mp));
357}
358
359/*
360 * wait/process the response to T_SVR4_OPTMGMT_REQ TPI message
361 */
362static int
363ip_get_option_response(conn_t *connp, uint_t optset_context, void *optval,
364    t_uscalar_t *optlenp)
365{
366	union T_primitives	*tpr;
367	int			error;
368	mblk_t			*mp;
369
370	mp = NULL;
371
372	ASSERT(optset_context == T_CHECK || optset_context == T_NEGOTIATE);
373	error = ldi_getmsg(connp->conn_helper_info->iphs_handle, &mp, NULL);
374	if (error != 0) {
375		return (error);
376	}
377
378	if (DB_TYPE(mp) != M_PCPROTO || MBLKL(mp) < sizeof (tpr->type)) {
379		error = EPROTO;
380		goto done;
381	}
382
383	tpr = (union T_primitives *)mp->b_rptr;
384
385	switch (tpr->type) {
386	case T_OPTMGMT_ACK:
387		if (MBLKL(mp) < TOPTMGMTACKSZ)
388			error = EPROTO;
389		break;
390	case T_ERROR_ACK:
391		if (MBLKL(mp) < TERRORACKSZ) {
392			error = EPROTO;
393			break;
394		}
395
396		if (tpr->error_ack.TLI_error == TSYSERR)
397			error = tpr->error_ack.UNIX_error;
398		else
399			error = proto_tlitosyserr(tpr->error_ack.TLI_error);
400		break;
401	default:
402		error = EPROTO;
403		break;
404	}
405
406	if ((optset_context == T_CHECK) && (error == 0)) {
407		struct opthdr		*opt_res;
408		t_uscalar_t		len;
409		t_uscalar_t		size;
410		t_uscalar_t		maxlen = *optlenp;
411		void			*option;
412		struct T_optmgmt_ack	*optmgmt_ack;
413
414		optmgmt_ack = (struct T_optmgmt_ack *)mp->b_rptr;
415		opt_res = (struct opthdr *)
416		    ((uintptr_t)mp->b_rptr +  optmgmt_ack->OPT_offset);
417		/*
418		 * Check mblk boundary
419		 */
420		if (!MBLKIN(mp, optmgmt_ack->OPT_offset,
421		    optmgmt_ack->OPT_length)) {
422			error = EPROTO;
423			goto done;
424		}
425
426		/*
427		 * Check alignment
428		 */
429		if ((((uintptr_t)opt_res) & (__TPI_ALIGN_SIZE - 1)) != 0) {
430			error = EPROTO;
431			goto done;
432		}
433
434		option = &opt_res[1];
435
436		/* check to ensure that the option is within bounds */
437		if ((((uintptr_t)option + opt_res->len) < (uintptr_t)option) ||
438		    !MBLKIN(mp, sizeof (struct opthdr), opt_res->len)) {
439			error = EPROTO;
440			goto done;
441		}
442
443		len = opt_res->len;
444		size = MIN(len, maxlen);
445
446		/*
447		 * Copy data
448		 */
449		bcopy(option, optval, size);
450		bcopy(&size, optlenp, sizeof (size));
451	}
452
453done:
454	freemsg(mp);
455	return (error);
456}
457
458/*
459 * Public interface to get socketoptions via the ip helper stream.
460 */
461int
462ip_get_options(conn_t *connp, int level, int option_name, void *optval,
463    t_uscalar_t *optlenp, cred_t *cr)
464{
465	int			error;
466
467	error = ip_send_option_request(connp, T_CHECK, level, option_name, NULL,
468	    *optlenp, cr);
469	if (error)
470		return (error);
471
472	return (ip_get_option_response(connp, T_CHECK, optval, optlenp));
473}
474
475/*
476 * Public interface to set socket options via the ip helper stream.
477 */
478int
479ip_set_options(conn_t *connp, int level, int option_name, const void *optval,
480    t_uscalar_t optlen, cred_t *cr)
481{
482
483	int	error;
484
485	error = ip_send_option_request(connp, T_NEGOTIATE, level, option_name,
486	    optval, optlen, cr);
487	if (error)
488		return (error);
489
490	return (ip_get_option_response(connp, T_NEGOTIATE, (void *)optval,
491	    &optlen));
492}
493