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