Print this page
NEX-19225 SMB client 2.1 hits redzone panic
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
NEX-14666 Need to provide SMB 2.1 Client
NEX-17187 panic in smbfs_acl_store
NEX-17231 smbfs create xattr files finds wrong file
NEX-17224 smbfs lookup EINVAL should be ENOENT
NEX-17260 SMB1 client fails to list directory after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
and: (cleanup)
NEX-16824 SMB client connection setup rework
NEX-17232 SMB client reconnect failures
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (improve debug)
NEX-16818 Add fksmbcl development tool
NEX-17264 SMB client test tp_smbutil_013 fails after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (fix ref leaks)
NEX-16805 Add smbutil discon command
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
SUP-548 Panic from NULL pointer dereference in smb_iod_disconnect

*** 33,42 **** --- 33,45 ---- */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. + * + * Portions Copyright (C) 2001 - 2013 Apple Inc. All rights reserved. + * Copyright 2018 Nexenta Systems, Inc. All rights reserved. */ #ifdef DEBUG /* See sys/queue.h */ #define QUEUEDEBUG 1
*** 65,82 **** #include <sys/sdt.h> #include <netsmb/smb_osdep.h> #include <netsmb/smb.h> #include <netsmb/smb_conn.h> #include <netsmb/smb_rq.h> #include <netsmb/smb_subr.h> #include <netsmb/smb_tran.h> #include <netsmb/smb_trantcp.h> ! int smb_iod_send_echo(smb_vc_t *); /* * This is set/cleared when smbfs loads/unloads * No locks should be necessary, because smbfs * can't unload until all the mounts are gone. */ --- 68,98 ---- #include <sys/sdt.h> #include <netsmb/smb_osdep.h> #include <netsmb/smb.h> + #include <netsmb/smb2.h> #include <netsmb/smb_conn.h> #include <netsmb/smb_rq.h> + #include <netsmb/smb2_rq.h> #include <netsmb/smb_subr.h> #include <netsmb/smb_tran.h> #include <netsmb/smb_trantcp.h> ! /* ! * SMB messages are up to 64K. Let's leave room for two. ! * If we negotiate up to SMB2, increase these. XXX todo ! */ ! static int smb_tcpsndbuf = 0x20000; ! static int smb_tcprcvbuf = 0x20000; ! static int smb_connect_timeout = 10; /* seconds */ + static int smb1_iod_process(smb_vc_t *, mblk_t *); + static int smb2_iod_process(smb_vc_t *, mblk_t *); + static int smb_iod_send_echo(smb_vc_t *, cred_t *cr); + static int smb_iod_logoff(struct smb_vc *vcp, cred_t *cr); + /* * This is set/cleared when smbfs loads/unloads * No locks should be necessary, because smbfs * can't unload until all the mounts are gone. */
*** 91,101 **** smb_iod_share_disconnected(smb_share_t *ssp) { smb_share_invalidate(ssp); ! /* smbfs_dead() */ if (fscb && fscb->fscb_disconn) { fscb->fscb_disconn(ssp); } } --- 107,120 ---- smb_iod_share_disconnected(smb_share_t *ssp) { smb_share_invalidate(ssp); ! /* ! * This is the only fscb hook smbfs currently uses. ! * Replaces smbfs_dead() from Darwin. ! */ if (fscb && fscb->fscb_disconn) { fscb->fscb_disconn(ssp); } }
*** 140,162 **** { struct smb_rq *rqp; /* * Invalidate all outstanding requests for this connection */ rw_enter(&vcp->iod_rqlock, RW_READER); TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART); } rw_exit(&vcp->iod_rqlock); } /* ! * Called by smb_vc_rele, smb_vc_kill, and by the driver ! * close entry point if the IOD closes its dev handle. * ! * Forcibly kill the connection and IOD. */ void smb_iod_disconnect(struct smb_vc *vcp) { --- 159,184 ---- { struct smb_rq *rqp; /* * Invalidate all outstanding requests for this connection + * Also wakeup iod_muxwant waiters. */ rw_enter(&vcp->iod_rqlock, RW_READER); TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART); } rw_exit(&vcp->iod_rqlock); + cv_broadcast(&vcp->iod_muxwait); } /* ! * Called by smb_vc_rele/smb_vc_kill on last ref, and by ! * the driver close function if the IOD closes its minor. ! * In those cases, the caller should be the IOD thread. * ! * Forcibly kill the connection. */ void smb_iod_disconnect(struct smb_vc *vcp) {
*** 168,383 **** smb_iod_newstate(vcp, SMBIOD_ST_DEAD); cv_broadcast(&vcp->vc_statechg); } SMB_VC_UNLOCK(vcp); - /* - * Let's be safe here and avoid doing any - * call across the network while trying to - * shut things down. If we just disconnect, - * the server will take care of the logoff. - */ SMB_TRAN_DISCONNECT(vcp); - - /* - * If we have an IOD, it should immediately notice - * that its connection has closed. But in case - * it doesn't, let's also send it a signal. - */ - SMB_VC_LOCK(vcp); - if (vcp->iod_thr != NULL && - vcp->iod_thr != curthread) { - tsignal(vcp->iod_thr, SIGKILL); - } - SMB_VC_UNLOCK(vcp); } /* * Send one request. * * Called by _addrq (for internal requests) * and _sendall (via _addrq, _multirq, _waitrq) */ ! static int ! smb_iod_sendrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; mblk_t *m; int error; ASSERT(vcp); ! ASSERT(SEMA_HELD(&vcp->vc_sendlock)); ! ASSERT(RW_READ_HELD(&vcp->iod_rqlock)); /* ! * Note: Anything special for SMBR_INTERNAL here? */ ! if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); ! return (ENOTCONN); } /* ! * On the first send, set the MID and (maybe) ! * the signing sequence numbers. The increments ! * here are serialized by vc_sendlock */ ! if (rqp->sr_sendcnt == 0) { ! rqp->sr_mid = vcp->vc_next_mid++; ! if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { /* ! * We're signing requests and verifying ! * signatures on responses. Set the ! * sequence numbers of the request and ! * response here, used in smb_rq_verify. */ ! rqp->sr_seqno = vcp->vc_next_seq++; ! rqp->sr_rseqno = vcp->vc_next_seq++; } ! /* Fill in UID, TID, MID, etc. */ ! smb_rq_fillhdr(rqp); /* ! * Sign the message now that we're finally done ! * filling in the SMB header fields, etc. */ ! if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { ! smb_rq_sign(rqp); } ! } ! if (rqp->sr_sendcnt++ >= 60/SMBSBTIMO) { /* one minute */ ! smb_iod_rqprocessed(rqp, rqp->sr_lerror, SMBR_RESTART); /* ! * If all attempts to send a request failed, then ! * something is seriously hosed. */ ! return (ENOTCONN); } /* ! * Replaced m_copym() with Solaris copymsg() which does the same ! * work when we want to do a M_COPYALL. ! * m = m_copym(rqp->sr_rq.mb_top, 0, M_COPYALL, 0); */ ! m = copymsg(rqp->sr_rq.mb_top); ! #ifdef DTRACE_PROBE ! DTRACE_PROBE2(smb_iod_sendrq, ! (smb_rq_t *), rqp, (mblk_t *), m); ! #else ! SMBIODEBUG("M:%04x, P:%04x, U:%04x, T:%04x\n", rqp->sr_mid, 0, 0, 0); ! #endif ! m_dumpm(m); ! if (m != NULL) { ! error = SMB_TRAN_SEND(vcp, m); ! m = 0; /* consumed by SEND */ ! } else ! error = ENOBUFS; rqp->sr_lerror = error; if (error == 0) { SMBRQ_LOCK(rqp); rqp->sr_flags |= SMBR_SENT; rqp->sr_state = SMBRQ_SENT; - if (rqp->sr_flags & SMBR_SENDWAIT) - cv_broadcast(&rqp->sr_cond); SMBRQ_UNLOCK(rqp); ! return (0); } /* ! * Check for fatal errors */ if (SMB_TRAN_FATAL(vcp, error)) { /* * No further attempts should be made */ SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error); ! return (ENOTCONN); } - if (error) - SMBSDEBUG("TRAN_SEND returned non-fatal error %d\n", error); - - #ifdef APPLE - /* If proc waiting on rqp was signaled... */ - if (smb_rq_intr(rqp)) - smb_iod_rqprocessed(rqp, EINTR, 0); - #endif - - return (0); } static int ! smb_iod_recv1(struct smb_vc *vcp, mblk_t **mpp) { mblk_t *m; - uchar_t *hp; int error; top: m = NULL; error = SMB_TRAN_RECV(vcp, &m); if (error == EAGAIN) goto top; if (error) return (error); ! ASSERT(m); ! m = m_pullup(m, SMB_HDRLEN); if (m == NULL) { return (ENOSR); } - /* - * Check the SMB header - */ - hp = mtod(m, uchar_t *); - if (bcmp(hp, SMB_SIGNATURE, SMB_SIGLEN) != 0) { - m_freem(m); - return (EPROTO); - } - *mpp = m; return (0); } /* * Process incoming packets * ! * This is the "reader" loop, run by the IOD thread ! * while in state SMBIOD_ST_VCACTIVE. The loop now ! * simply blocks in the socket recv until either a ! * message arrives, or a disconnect. * ! * Any non-zero error means the IOD should terminate. */ int ! smb_iod_recvall(struct smb_vc *vcp) { - struct smb_rq *rqp; mblk_t *m; - uchar_t *hp; - ushort_t mid; int error = 0; ! int etime_count = 0; /* for "server not responding", etc. */ for (;;) { /* * Check whether someone "killed" this VC, * or is asking the IOD to terminate. */ - - if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { - SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); - error = 0; - break; - } - if (vcp->iod_flags & SMBIOD_SHUTDOWN) { SMBIODEBUG("SHUTDOWN set\n"); /* This IOD thread will terminate. */ SMB_VC_LOCK(vcp); smb_iod_newstate(vcp, SMBIOD_ST_DEAD); --- 190,455 ---- smb_iod_newstate(vcp, SMBIOD_ST_DEAD); cv_broadcast(&vcp->vc_statechg); } SMB_VC_UNLOCK(vcp); SMB_TRAN_DISCONNECT(vcp); } /* * Send one request. * + * SMB1 only + * * Called by _addrq (for internal requests) * and _sendall (via _addrq, _multirq, _waitrq) + * Errors are reported via the smb_rq, using: + * smb_iod_rqprocessed(rqp, ...) */ ! static void ! smb1_iod_sendrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; mblk_t *m; int error; ASSERT(vcp); ! ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock)); ! ASSERT((vcp->vc_flags & SMBV_SMB2) == 0); /* ! * Internal requests are allowed in any state; ! * otherwise should be active. */ ! if ((rqp->sr_flags & SMBR_INTERNAL) == 0 && ! vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); ! smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART); ! return; } + /* + * Overwrite the SMB header with the assigned MID and + * (if we're signing) sign it. + */ + smb_rq_fillhdr(rqp); + if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) { + smb_rq_sign(rqp); + } /* ! * The transport send consumes the message and we'd ! * prefer to keep a copy, so dupmsg() before sending. */ ! m = dupmsg(rqp->sr_rq.mb_top); ! if (m == NULL) { ! error = ENOBUFS; ! goto fatal; ! } ! #ifdef DTRACE_PROBE2 ! DTRACE_PROBE2(iod_sendrq, ! (smb_rq_t *), rqp, (mblk_t *), m); ! #endif ! error = SMB_TRAN_SEND(vcp, m); ! m = 0; /* consumed by SEND */ ! ! rqp->sr_lerror = error; ! if (error == 0) { ! SMBRQ_LOCK(rqp); ! rqp->sr_flags |= SMBR_SENT; ! rqp->sr_state = SMBRQ_SENT; ! SMBRQ_UNLOCK(rqp); ! return; ! } /* ! * Transport send returned an error. ! * Was it a fatal one? */ ! if (SMB_TRAN_FATAL(vcp, error)) { ! /* ! * No further attempts should be made ! */ ! fatal: ! SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error); ! smb_iod_rqprocessed(rqp, error, SMBR_RESTART); ! return; } + } ! /* ! * Send one request. ! * ! * SMB2 only ! * ! * Called by _addrq (for internal requests) ! * and _sendall (via _addrq, _multirq, _waitrq) ! * Errors are reported via the smb_rq, using: ! * smb_iod_rqprocessed(rqp, ...) ! */ ! static void ! smb2_iod_sendrq(struct smb_rq *rqp) ! { ! struct smb_rq *c_rqp; /* compound */ ! struct smb_vc *vcp = rqp->sr_vc; ! mblk_t *top_m; ! mblk_t *cur_m; ! int error; + ASSERT(vcp); + ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock)); + ASSERT((vcp->vc_flags & SMBV_SMB2) != 0); + /* ! * Internal requests are allowed in any state; ! * otherwise should be active. */ ! if ((rqp->sr_flags & SMBR_INTERNAL) == 0 && ! vcp->vc_state != SMBIOD_ST_VCACTIVE) { ! SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); ! smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART); ! return; } ! /* ! * Overwrite the SMB header with the assigned MID and ! * (if we're signing) sign it. If there are compounded ! * requests after the top one, do those too. */ ! smb2_rq_fillhdr(rqp); ! if (rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) { ! smb2_rq_sign(rqp); } + c_rqp = rqp->sr2_compound_next; + while (c_rqp != NULL) { + smb2_rq_fillhdr(c_rqp); + if (c_rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) { + smb2_rq_sign(c_rqp); + } + c_rqp = c_rqp->sr2_compound_next; + } /* ! * The transport send consumes the message and we'd ! * prefer to keep a copy, so dupmsg() before sending. ! * We also need this to build the compound message ! * that we'll actually send. The message offset at ! * the start of each compounded message should be ! * eight-byte aligned. The caller preparing the ! * compounded request has to take care of that ! * before we get here and sign messages etc. */ ! top_m = dupmsg(rqp->sr_rq.mb_top); ! if (top_m == NULL) { ! error = ENOBUFS; ! goto fatal; ! } ! c_rqp = rqp->sr2_compound_next; ! while (c_rqp != NULL) { ! size_t len = msgdsize(top_m); ! ASSERT((len & 7) == 0); ! cur_m = dupmsg(c_rqp->sr_rq.mb_top); ! if (cur_m == NULL) { ! freemsg(top_m); ! error = ENOBUFS; ! goto fatal; ! } ! linkb(top_m, cur_m); ! } ! DTRACE_PROBE2(iod_sendrq, ! (smb_rq_t *), rqp, (mblk_t *), top_m); ! error = SMB_TRAN_SEND(vcp, top_m); ! top_m = 0; /* consumed by SEND */ rqp->sr_lerror = error; if (error == 0) { SMBRQ_LOCK(rqp); rqp->sr_flags |= SMBR_SENT; rqp->sr_state = SMBRQ_SENT; SMBRQ_UNLOCK(rqp); ! return; } /* ! * Transport send returned an error. ! * Was it a fatal one? */ if (SMB_TRAN_FATAL(vcp, error)) { /* * No further attempts should be made */ + fatal: SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error); ! smb_iod_rqprocessed(rqp, error, SMBR_RESTART); ! return; } } + /* + * Receive one NetBIOS (or NBT over TCP) message. If none have arrived, + * wait up to SMB_NBTIMO (15 sec.) for one to arrive, and then if still + * none have arrived, return ETIME. + */ static int ! smb_iod_recvmsg(struct smb_vc *vcp, mblk_t **mpp) { mblk_t *m; int error; top: m = NULL; error = SMB_TRAN_RECV(vcp, &m); if (error == EAGAIN) goto top; if (error) return (error); ! ASSERT(m != NULL); ! m = m_pullup(m, 4); if (m == NULL) { return (ENOSR); } *mpp = m; return (0); } /* + * How long should we keep around an unused VC (connection)? + * There's usually a good chance connections will be reused, + * so the default is to keep such connections for 5 min. + */ + #ifdef DEBUG + int smb_iod_idle_keep_time = 60; /* seconds */ + #else + int smb_iod_idle_keep_time = 300; /* seconds */ + #endif + + /* * Process incoming packets * ! * This is the "reader" loop, run by the IOD thread. Normally we're in ! * state SMBIOD_ST_VCACTIVE here, but during reconnect we're called in ! * other states with poll==TRUE * ! * A non-zero error return here causes the IOD work loop to terminate. */ int ! smb_iod_recvall(struct smb_vc *vcp, boolean_t poll) { mblk_t *m; int error = 0; ! int etime_idle = 0; /* How many 15 sec. "ticks" idle. */ ! int etime_count = 0; /* ... and when we have requests. */ for (;;) { /* * Check whether someone "killed" this VC, * or is asking the IOD to terminate. */ if (vcp->iod_flags & SMBIOD_SHUTDOWN) { SMBIODEBUG("SHUTDOWN set\n"); /* This IOD thread will terminate. */ SMB_VC_LOCK(vcp); smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
*** 386,500 **** error = EINTR; break; } m = NULL; ! error = smb_iod_recv1(vcp, &m); if (error == ETIME && vcp->iod_rqlist.tqh_first != NULL) { /* ! * Nothing received for 15 seconds and ! * we have requests in the queue. */ etime_count++; /* ! * Once, at 15 sec. notify callbacks ! * and print the warning message. */ ! if (etime_count == 1) { ! /* Was: smb_iod_notify_down(vcp); */ ! if (fscb && fscb->fscb_down) ! smb_vc_walkshares(vcp, ! fscb->fscb_down); zprintf(vcp->vc_zoneid, "SMB server %s not responding\n", vcp->vc_srvname); } - /* ! * At 30 sec. try sending an echo, and then ! * once a minute thereafter. */ ! if ((etime_count & 3) == 2) { ! (void) smb_iod_send_echo(vcp); } ! continue; ! } /* ETIME && requests in queue */ if (error == ETIME) { /* ! * If the IOD thread holds the last reference ! * to this VC, let the IOD thread terminate. */ ! if (vcp->vc_co.co_usecount > 1) continue; SMB_VC_LOCK(vcp); if (vcp->vc_co.co_usecount == 1) { ! smb_iod_newstate(vcp, SMBIOD_ST_DEAD); SMB_VC_UNLOCK(vcp); error = 0; break; } SMB_VC_UNLOCK(vcp); continue; } /* error == ETIME */ if (error) { /* ! * The recv. above returned some error ! * we can't continue from i.e. ENOTCONN. ! * It's dangerous to continue here. ! * (possible infinite loop!) ! * ! * If we have requests enqueued, next ! * state is reconnecting, else idle. */ - int state; SMB_VC_LOCK(vcp); ! state = (vcp->iod_rqlist.tqh_first != NULL) ? ! SMBIOD_ST_RECONNECT : SMBIOD_ST_IDLE; ! smb_iod_newstate(vcp, state); cv_broadcast(&vcp->vc_statechg); SMB_VC_UNLOCK(vcp); ! error = 0; break; } /* * Received something. Yea! */ - if (etime_count) { etime_count = 0; zprintf(vcp->vc_zoneid, "SMB server %s OK\n", vcp->vc_srvname); ! /* Was: smb_iod_notify_up(vcp); */ ! if (fscb && fscb->fscb_up) ! smb_vc_walkshares(vcp, fscb->fscb_up); } /* ! * Have an SMB packet. The SMB header was ! * checked in smb_iod_recv1(). ! * Find the request... */ ! hp = mtod(m, uchar_t *); ! /*LINTED*/ ! mid = letohs(SMB_HDRMID(hp)); ! SMBIODEBUG("mid %04x\n", (uint_t)mid); rw_enter(&vcp->iod_rqlock, RW_READER); TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { if (rqp->sr_mid != mid) continue; ! DTRACE_PROBE2(smb_iod_recvrq, (smb_rq_t *), rqp, (mblk_t *), m); m_dumpm(m); SMBRQ_LOCK(rqp); if (rqp->sr_rp.md_top == NULL) { --- 458,692 ---- error = EINTR; break; } m = NULL; ! error = smb_iod_recvmsg(vcp, &m); + /* + * Internal requests (reconnecting) call this in a loop + * (with poll==TRUE) until the request completes. + */ + if (error == ETIME && poll) + break; + if (error == ETIME && vcp->iod_rqlist.tqh_first != NULL) { + /* ! * Nothing received and requests waiting. ! * Increment etime_count. If we were idle, ! * skip the 1st tick, because we started ! * waiting before there were any requests. */ + if (etime_idle != 0) { + etime_idle = 0; + } else if (etime_count < INT16_MAX) { etime_count++; + } /* ! * ETIME and requests in the queue. ! * The first time (at 15 sec.) ! * Log an error (just once). */ ! if (etime_count > 0 && ! vcp->iod_noresp == B_FALSE) { ! vcp->iod_noresp = B_TRUE; zprintf(vcp->vc_zoneid, "SMB server %s not responding\n", vcp->vc_srvname); } /* ! * At 30 sec. try sending an echo, which ! * should cause some response. */ ! if (etime_count == 2) { ! SMBIODEBUG("send echo\n"); ! (void) smb_iod_send_echo(vcp, CRED()); } ! /* ! * At 45 sec. give up on the connection ! * and try to reconnect. ! */ ! if (etime_count == 3) { ! SMB_VC_LOCK(vcp); ! smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT); ! SMB_VC_UNLOCK(vcp); ! SMB_TRAN_DISCONNECT(vcp); ! break; ! } continue; ! } /* ETIME and requests in the queue */ if (error == ETIME) { /* ! * Nothing received and no active requests. ! * ! * If we've received nothing from the server for ! * smb_iod_idle_keep_time seconds, and the IOD ! * thread holds the last reference to this VC, ! * move to state IDLE and drop the TCP session. ! * The IDLE handler will destroy the VC unless ! * vc_state goes to RECONNECT before then. */ ! etime_count = 0; ! if (etime_idle < INT16_MAX) ! etime_idle++; ! if ((etime_idle * SMB_NBTIMO) < ! smb_iod_idle_keep_time) continue; SMB_VC_LOCK(vcp); if (vcp->vc_co.co_usecount == 1) { ! smb_iod_newstate(vcp, SMBIOD_ST_IDLE); SMB_VC_UNLOCK(vcp); + SMBIODEBUG("logoff & disconnect\n"); + (void) smb_iod_logoff(vcp, CRED()); + SMB_TRAN_DISCONNECT(vcp); error = 0; break; } SMB_VC_UNLOCK(vcp); continue; } /* error == ETIME */ if (error) { /* ! * The recv above returned an error indicating ! * that our TCP session is no longer usable. ! * Disconnect the session and get ready to ! * reconnect. If we have pending requests, ! * move to state reconnect immediately; ! * otherwise move to state IDLE until a ! * request is issued on this VC. */ SMB_VC_LOCK(vcp); ! if (vcp->iod_rqlist.tqh_first != NULL) ! smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT); ! else ! smb_iod_newstate(vcp, SMBIOD_ST_IDLE); cv_broadcast(&vcp->vc_statechg); SMB_VC_UNLOCK(vcp); ! SMB_TRAN_DISCONNECT(vcp); break; } /* * Received something. Yea! */ etime_count = 0; + etime_idle = 0; + /* + * If we just completed a reconnect after logging + * "SMB server %s not responding" then log OK now. + */ + if (vcp->iod_noresp) { + vcp->iod_noresp = B_FALSE; zprintf(vcp->vc_zoneid, "SMB server %s OK\n", vcp->vc_srvname); + } ! if ((vcp->vc_flags & SMBV_SMB2) != 0) { ! error = smb2_iod_process(vcp, m); ! } else { ! error = smb1_iod_process(vcp, m); } /* ! * Reconnect calls this in a loop with poll=TRUE ! * We've received a response, so break now. */ ! if (poll) { ! error = 0; ! break; ! } ! } + return (error); + } + + /* + * Have what should be an SMB1 reply. Check and parse the header, + * then use the message ID to find the request this belongs to and + * post it on that request. + * + * Returns an error if the reader should give up. + * To be safe, error if we read garbage. + */ + static int + smb1_iod_process(smb_vc_t *vcp, mblk_t *m) + { + struct mdchain md; + struct smb_rq *rqp; + uint8_t cmd, sig[4]; + uint16_t mid; + int err, skip; + + m = m_pullup(m, SMB_HDRLEN); + if (m == NULL) + return (ENOMEM); + + /* + * Note: Intentionally do NOT md_done(&md) + * because that would free the message and + * we just want to peek here. + */ + md_initm(&md, m); + + /* + * Check the SMB header version and get the MID. + * + * The header version should be SMB1 except when we're + * doing SMB1-to-SMB2 negotiation, in which case we may + * see an SMB2 header with message ID=0 (only allowed in + * vc_state == SMBIOD_ST_CONNECTED -- negotiationg). + */ + err = md_get_mem(&md, sig, 4, MB_MSYSTEM); + if (err) + return (err); + if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') { + goto bad_hdr; + } + switch (sig[0]) { + case SMB_HDR_V1: /* SMB1 */ + md_get_uint8(&md, &cmd); + /* Skip to and get the MID. At offset 5 now. */ + skip = SMB_HDR_OFF_MID - 5; + md_get_mem(&md, NULL, skip, MB_MSYSTEM); + err = md_get_uint16le(&md, &mid); + if (err) + return (err); + break; + case SMB_HDR_V2: /* SMB2+ */ + if (vcp->vc_state == SMBIOD_ST_CONNECTED) { + /* + * No need to look, can only be + * MID=0, cmd=negotiate + */ + cmd = SMB_COM_NEGOTIATE; + mid = 0; + break; + } + /* FALLTHROUGH */ + bad_hdr: + default: + SMBIODEBUG("Bad SMB hdr\n"); + m_freem(m); + return (EPROTO); + } + + /* + * Find the reqeuest and post the reply + */ rw_enter(&vcp->iod_rqlock, RW_READER); TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { if (rqp->sr_mid != mid) continue; ! DTRACE_PROBE2(iod_post_reply, (smb_rq_t *), rqp, (mblk_t *), m); m_dumpm(m); SMBRQ_LOCK(rqp); if (rqp->sr_rp.md_top == NULL) {
*** 502,535 **** } else { if (rqp->sr_flags & SMBR_MULTIPACKET) { md_append_record(&rqp->sr_rp, m); } else { SMBRQ_UNLOCK(rqp); ! SMBSDEBUG("duplicate response %d " ! "(ignored)\n", mid); break; } } smb_iod_rqprocessed_LH(rqp, 0, 0); SMBRQ_UNLOCK(rqp); break; } if (rqp == NULL) { ! int cmd = SMB_HDRCMD(hp); ! if (cmd != SMB_COM_ECHO) ! SMBSDEBUG("drop resp: mid %d, cmd %d\n", ! (uint_t)mid, cmd); ! /* smb_printrqlist(vcp); */ m_freem(m); } rw_exit(&vcp->iod_rqlock); } ! return (error); } /* * The IOD receiver thread has requests pending and * has not received anything in a while. Try to --- 694,879 ---- } else { if (rqp->sr_flags & SMBR_MULTIPACKET) { md_append_record(&rqp->sr_rp, m); } else { SMBRQ_UNLOCK(rqp); ! rqp = NULL; break; } } smb_iod_rqprocessed_LH(rqp, 0, 0); SMBRQ_UNLOCK(rqp); break; } + rw_exit(&vcp->iod_rqlock); if (rqp == NULL) { ! if (cmd != SMB_COM_ECHO) { ! SMBSDEBUG("drop resp: MID 0x%04x\n", (uint_t)mid); ! } ! m_freem(m); ! /* ! * Keep going. It's possible this reply came ! * after the request timed out and went away. ! */ ! } ! return (0); ! } ! /* ! * Have what should be an SMB2 reply. Check and parse the header, ! * then use the message ID to find the request this belongs to and ! * post it on that request. ! * ! * We also want to apply any credit grant in this reply now, ! * rather than waiting for the owner to wake up. ! */ ! static int ! smb2_iod_process(smb_vc_t *vcp, mblk_t *m) ! { ! struct mdchain md; ! struct smb_rq *rqp; ! uint8_t sig[4]; ! mblk_t *next_m = NULL; ! uint64_t message_id, async_id; ! uint32_t flags, next_cmd_off, status; ! uint16_t command, credits_granted; ! int err; ! ! top: ! m = m_pullup(m, SMB2_HDRLEN); ! if (m == NULL) ! return (ENOMEM); ! ! /* ! * Note: Intentionally do NOT md_done(&md) ! * because that would free the message and ! * we just want to peek here. ! */ ! md_initm(&md, m); ! ! /* ! * Check the SMB header. Must be SMB2 ! * (and later, could be SMB3 encrypted) ! */ ! err = md_get_mem(&md, sig, 4, MB_MSYSTEM); ! if (err) ! return (err); ! if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') { ! goto bad_hdr; ! } ! switch (sig[0]) { ! case SMB_HDR_V2: ! break; ! case SMB_HDR_V3E: ! /* ! * Todo: If encryption enabled, decrypt the message ! * and restart processing on the cleartext. ! */ ! /* FALLTHROUGH */ ! bad_hdr: ! default: ! SMBIODEBUG("Bad SMB2 hdr\n"); m_freem(m); + return (EPROTO); } + + /* + * Parse the rest of the SMB2 header, + * skipping what we don't need. + */ + md_get_uint32le(&md, NULL); /* length, credit_charge */ + md_get_uint32le(&md, &status); + md_get_uint16le(&md, &command); + md_get_uint16le(&md, &credits_granted); + md_get_uint32le(&md, &flags); + md_get_uint32le(&md, &next_cmd_off); + md_get_uint64le(&md, &message_id); + if (flags & SMB2_FLAGS_ASYNC_COMMAND) { + md_get_uint64le(&md, &async_id); + } else { + /* PID, TID (not needed) */ + async_id = 0; + } + + /* + * If this is a compound reply, split it. + * Next must be 8-byte aligned. + */ + if (next_cmd_off != 0) { + if ((next_cmd_off & 7) != 0) + SMBIODEBUG("Misaligned next cmd\n"); + else + next_m = m_split(m, next_cmd_off, 1); + } + + /* + * Apply the credit grant + */ + rw_enter(&vcp->iod_rqlock, RW_WRITER); + vcp->vc2_limit_message_id += credits_granted; + + /* + * Find the reqeuest and post the reply + */ + rw_downgrade(&vcp->iod_rqlock); + TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { + + if (rqp->sr2_messageid != message_id) + continue; + + DTRACE_PROBE2(iod_post_reply, + (smb_rq_t *), rqp, (mblk_t *), m); + m_dumpm(m); + + /* + * If this is an interim response, just save the + * async ID but don't wakup the request. + * Don't need SMBRQ_LOCK for this. + */ + if (status == NT_STATUS_PENDING && async_id != 0) { + rqp->sr2_rspasyncid = async_id; + m_freem(m); + break; + } + + SMBRQ_LOCK(rqp); + if (rqp->sr_rp.md_top == NULL) { + md_initm(&rqp->sr_rp, m); + } else { + SMBRQ_UNLOCK(rqp); + rqp = NULL; + break; + } + smb_iod_rqprocessed_LH(rqp, 0, 0); + SMBRQ_UNLOCK(rqp); + break; + } rw_exit(&vcp->iod_rqlock); + if (rqp == NULL) { + if (command != SMB2_ECHO) { + SMBSDEBUG("drop resp: MID %lld\n", + (long long)message_id); } + m_freem(m); + /* + * Keep going. It's possible this reply came + * after the request timed out and went away. + */ + } ! /* ! * If we split a compound reply, continue with the ! * next part of the compound. ! */ ! if (next_m != NULL) { ! m = next_m; ! goto top; ! } ! ! return (0); } /* * The IOD receiver thread has requests pending and * has not received anything in a while. Try to
*** 539,668 **** * Using tmo=SMBNOREPLYWAIT in the request * so smb_rq_reply will skip smb_iod_waitrq. * The smb_smb_echo call uses SMBR_INTERNAL * to avoid calling smb_iod_sendall(). */ ! int ! smb_iod_send_echo(smb_vc_t *vcp) { smb_cred_t scred; ! int err; ! smb_credinit(&scred, NULL); ! err = smb_smb_echo(vcp, &scred, SMBNOREPLYWAIT); smb_credrele(&scred); return (err); } /* ! * The IOD thread is now just a "reader", ! * so no more smb_iod_request(). Yea! */ /* ! * Place request in the queue, and send it now if possible. * Called with no locks held. */ int ! smb_iod_addrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; ! int error, save_newrq; ASSERT(rqp->sr_cred); /* ! * State should be correct after the check in ! * smb_rq_enqueue(), but we dropped locks... */ ! if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); return (ENOTCONN); } /* ! * Requests from the IOD itself are marked _INTERNAL, ! * and get some special treatment to avoid blocking ! * the reader thread (so we don't deadlock). ! * The request is not yet on the queue, so we can ! * modify it's state here without locks. ! * Only thing using this now is ECHO. */ ! rqp->sr_owner = curthread; ! if (rqp->sr_owner == vcp->iod_thr) { ! rqp->sr_flags |= SMBR_INTERNAL; ! ! /* ! * This is a request from the IOD thread. ! * Always send directly from this thread. ! * Note lock order: iod_rqlist, vc_sendlock ! */ rw_enter(&vcp->iod_rqlock, RW_WRITER); ! TAILQ_INSERT_HEAD(&vcp->iod_rqlist, rqp, sr_link); ! rw_downgrade(&vcp->iod_rqlock); /* ! * Note: iod_sendrq expects vc_sendlock, ! * so take that here, but carefully: ! * Never block the IOD thread here. */ ! if (sema_tryp(&vcp->vc_sendlock) == 0) { ! SMBIODEBUG("sendlock busy\n"); ! error = EAGAIN; ! } else { ! /* Have vc_sendlock */ ! error = smb_iod_sendrq(rqp); ! sema_v(&vcp->vc_sendlock); } rw_exit(&vcp->iod_rqlock); /* ! * In the non-error case, _removerq ! * is done by either smb_rq_reply ! * or smb_iod_waitrq. */ ! if (error) ! smb_iod_removerq(rqp); ! return (error); } rw_enter(&vcp->iod_rqlock, RW_WRITER); ! TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link); ! /* iod_rqlock/WRITER protects iod_newrq */ ! save_newrq = vcp->iod_newrq; ! vcp->iod_newrq++; rw_exit(&vcp->iod_rqlock); /* ! * Now send any requests that need to be sent, ! * including the one we just put on the list. ! * Only the thread that found iod_newrq==0 ! * needs to run the send loop. */ - if (save_newrq == 0) - smb_iod_sendall(vcp); return (0); } /* * Mark an SMBR_MULTIPACKET request as * needing another send. Similar to the ! * "normal" part of smb_iod_addrq. */ int ! smb_iod_multirq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; - int save_newrq; ASSERT(rqp->sr_flags & SMBR_MULTIPACKET); if (rqp->sr_flags & SMBR_INTERNAL) return (EINVAL); if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); --- 883,1142 ---- * Using tmo=SMBNOREPLYWAIT in the request * so smb_rq_reply will skip smb_iod_waitrq. * The smb_smb_echo call uses SMBR_INTERNAL * to avoid calling smb_iod_sendall(). */ ! static int ! smb_iod_send_echo(smb_vc_t *vcp, cred_t *cr) { smb_cred_t scred; ! int err, tmo = SMBNOREPLYWAIT; ! ASSERT(vcp->iod_thr == curthread); ! ! smb_credinit(&scred, cr); ! if ((vcp->vc_flags & SMBV_SMB2) != 0) { ! err = smb2_smb_echo(vcp, &scred, tmo); ! } else { ! err = smb_smb_echo(vcp, &scred, tmo); ! } smb_credrele(&scred); return (err); } /* ! * Helper for smb1_iod_addrq, smb2_iod_addrq ! * Returns zero if interrupted, else 1. */ + static int + smb_iod_muxwait(smb_vc_t *vcp, boolean_t sig_ok) + { + int rc; + SMB_VC_LOCK(vcp); + vcp->iod_muxwant++; + if (sig_ok) { + rc = cv_wait_sig(&vcp->iod_muxwait, &vcp->vc_lock); + } else { + cv_wait(&vcp->iod_muxwait, &vcp->vc_lock); + rc = 1; + } + vcp->iod_muxwant--; + SMB_VC_UNLOCK(vcp); + + return (rc); + } + /* ! * Place request in the queue, and send it. * Called with no locks held. + * + * Called for SMB1 only + * + * The logic for how we limit active requests differs between + * SMB1 and SMB2. With SMB1 it's a simple counter ioc_muxcnt. */ int ! smb1_iod_addrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; ! uint16_t need; ! boolean_t sig_ok = ! (rqp->sr_flags & SMBR_NOINTR_SEND) == 0; ASSERT(rqp->sr_cred); + ASSERT((vcp->vc_flags & SMBV_SMB2) == 0); + rqp->sr_owner = curthread; + + rw_enter(&vcp->iod_rqlock, RW_WRITER); + + recheck: /* ! * Internal requests can be added in any state, ! * but normal requests only in state active. */ ! if ((rqp->sr_flags & SMBR_INTERNAL) == 0 && ! vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); + rw_exit(&vcp->iod_rqlock); return (ENOTCONN); } /* ! * If we're at the limit of active requests, block until ! * enough requests complete so we can make ours active. ! * Wakeup in smb_iod_removerq(). ! * ! * Normal callers leave one slot free, so internal ! * callers can have the last slot if needed. */ ! need = 1; ! if ((rqp->sr_flags & SMBR_INTERNAL) == 0) ! need++; ! if ((vcp->iod_muxcnt + need) > vcp->vc_maxmux) { ! rw_exit(&vcp->iod_rqlock); ! if (rqp->sr_flags & SMBR_INTERNAL) ! return (EBUSY); ! if (smb_iod_muxwait(vcp, sig_ok) == 0) ! return (EINTR); rw_enter(&vcp->iod_rqlock, RW_WRITER); ! goto recheck; ! } /* ! * Add this request to the active list and send it. ! * For SMB2 we may have a sequence of compounded ! * requests, in which case we must add them all. ! * They're sent as a compound in smb2_iod_sendrq. */ ! rqp->sr_mid = vcp->vc_next_mid++; ! /* If signing, set the signing sequence numbers. */ ! if (vcp->vc_mackey != NULL && (rqp->sr_rqflags2 & ! SMB_FLAGS2_SECURITY_SIGNATURE) != 0) { ! rqp->sr_seqno = vcp->vc_next_seq++; ! rqp->sr_rseqno = vcp->vc_next_seq++; } + vcp->iod_muxcnt++; + TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link); + smb1_iod_sendrq(rqp); rw_exit(&vcp->iod_rqlock); + return (0); + } + /* + * Place request in the queue, and send it. + * Called with no locks held. + * + * Called for SMB2 only. + * + * With SMB2 we have a range of valid message IDs, and we may + * only send requests when we can assign a message ID within + * the valid range. We may need to wait here for some active + * request to finish (and update vc2_limit_message_id) before + * we can get message IDs for our new request(s). Another + * difference is that the request sequence we're waiting to + * add here may require multipe message IDs, either due to + * either compounding or multi-credit requests. Therefore + * we need to wait for the availibility of how ever many + * message IDs are required by our request sequence. + */ + int + smb2_iod_addrq(struct smb_rq *rqp) + { + struct smb_vc *vcp = rqp->sr_vc; + struct smb_rq *c_rqp; /* compound req */ + uint16_t charge; + boolean_t sig_ok = + (rqp->sr_flags & SMBR_NOINTR_SEND) == 0; + + ASSERT(rqp->sr_cred != NULL); + ASSERT((vcp->vc_flags & SMBV_SMB2) != 0); + /* ! * Figure out the credit charges ! * No multi-credit messages yet. */ ! rqp->sr2_totalcreditcharge = rqp->sr2_creditcharge; ! c_rqp = rqp->sr2_compound_next; ! while (c_rqp != NULL) { ! rqp->sr2_totalcreditcharge += c_rqp->sr2_creditcharge; ! c_rqp = c_rqp->sr2_compound_next; ! } ! /* ! * Internal request must not be compounded ! * and should use exactly one credit. ! */ ! if (rqp->sr_flags & SMBR_INTERNAL) { ! if (rqp->sr2_compound_next != NULL) { ! ASSERT(0); ! return (EINVAL); } + } + rqp->sr_owner = curthread; + rw_enter(&vcp->iod_rqlock, RW_WRITER); ! recheck: ! /* ! * Internal requests can be added in any state, ! * but normal requests only in state active. ! */ ! if ((rqp->sr_flags & SMBR_INTERNAL) == 0 && ! vcp->vc_state != SMBIOD_ST_VCACTIVE) { ! SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state); ! rw_exit(&vcp->iod_rqlock); ! return (ENOTCONN); ! } + /* + * If we're at the limit of active requests, block until + * enough requests complete so we can make ours active. + * Wakeup in smb_iod_removerq(). + * + * Normal callers leave one slot free, so internal + * callers can have the last slot if needed. + */ + charge = rqp->sr2_totalcreditcharge; + if ((rqp->sr_flags & SMBR_INTERNAL) == 0) + charge++; + if ((vcp->vc2_next_message_id + charge) > + vcp->vc2_limit_message_id) { rw_exit(&vcp->iod_rqlock); + if (rqp->sr_flags & SMBR_INTERNAL) + return (EBUSY); + if (smb_iod_muxwait(vcp, sig_ok) == 0) + return (EINTR); + rw_enter(&vcp->iod_rqlock, RW_WRITER); + goto recheck; + } /* ! * Add this request to the active list and send it. ! * For SMB2 we may have a sequence of compounded ! * requests, in which case we must add them all. ! * They're sent as a compound in smb2_iod_sendrq. */ + rqp->sr2_messageid = vcp->vc2_next_message_id; + vcp->vc2_next_message_id += rqp->sr2_creditcharge; + TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link); + + c_rqp = rqp->sr2_compound_next; + while (c_rqp != NULL) { + c_rqp->sr2_messageid = vcp->vc2_next_message_id; + vcp->vc2_next_message_id += c_rqp->sr2_creditcharge; + TAILQ_INSERT_TAIL(&vcp->iod_rqlist, c_rqp, sr_link); + c_rqp = c_rqp->sr2_compound_next; + } + smb2_iod_sendrq(rqp); + + rw_exit(&vcp->iod_rqlock); return (0); } /* * Mark an SMBR_MULTIPACKET request as * needing another send. Similar to the ! * "normal" part of smb1_iod_addrq. ! * Only used by SMB1 */ int ! smb1_iod_multirq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; ASSERT(rqp->sr_flags & SMBR_MULTIPACKET); + if (vcp->vc_flags & SMBV_SMB2) { + ASSERT("!SMB2?"); + return (EINVAL); + } + if (rqp->sr_flags & SMBR_INTERNAL) return (EINVAL); if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
*** 671,738 **** rw_enter(&vcp->iod_rqlock, RW_WRITER); /* Already on iod_rqlist, just reset state. */ rqp->sr_state = SMBRQ_NOTSENT; - /* iod_rqlock/WRITER protects iod_newrq */ - save_newrq = vcp->iod_newrq; - vcp->iod_newrq++; - rw_exit(&vcp->iod_rqlock); - /* - * Now send any requests that need to be sent, - * including the one we just marked NOTSENT. - * Only the thread that found iod_newrq==0 - * needs to run the send loop. - */ - if (save_newrq == 0) - smb_iod_sendall(vcp); - return (0); } ! void smb_iod_removerq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; rw_enter(&vcp->iod_rqlock, RW_WRITER); #ifdef QUEUEDEBUG /* * Make sure we have not already removed it. * See sys/queue.h QUEUEDEBUG_TAILQ_POSTREMOVE * XXX: Don't like the constant 1 here... */ ASSERT(rqp->sr_link.tqe_next != (void *)1L); #endif TAILQ_REMOVE(&vcp->iod_rqlist, rqp, sr_link); rw_exit(&vcp->iod_rqlock); } - - /* * Wait for a request to complete. - * - * For normal requests, we need to deal with - * ioc_muxcnt dropping below vc_maxmux by - * making arrangements to send more... */ int smb_iod_waitrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; clock_t tr, tmo1, tmo2; ! int error, rc; if (rqp->sr_flags & SMBR_INTERNAL) { ! ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0); ! smb_iod_removerq(rqp); ! return (EAGAIN); } /* * Make sure this is NOT the IOD thread, * or the wait below will stop the reader. --- 1145,1233 ---- rw_enter(&vcp->iod_rqlock, RW_WRITER); /* Already on iod_rqlist, just reset state. */ rqp->sr_state = SMBRQ_NOTSENT; + smb1_iod_sendrq(rqp); rw_exit(&vcp->iod_rqlock); return (0); } ! /* ! * Remove a request from the active list, and ! * wake up requests waiting to go active. ! * ! * Shared by SMB1 + SMB2 ! * ! * The logic for how we limit active requests differs between ! * SMB1 and SMB2. With SMB1 it's a simple counter ioc_muxcnt. ! * With SMB2 we have a range of valid message IDs, and when we ! * retire the oldest request we need to keep track of what is ! * now the oldest message ID. In both cases, after we take a ! * request out of the list here, we should be able to wake up ! * a request waiting to get in the active list. ! */ void smb_iod_removerq(struct smb_rq *rqp) { + struct smb_rq *rqp2; struct smb_vc *vcp = rqp->sr_vc; + boolean_t was_head = B_FALSE; rw_enter(&vcp->iod_rqlock, RW_WRITER); + #ifdef QUEUEDEBUG /* * Make sure we have not already removed it. * See sys/queue.h QUEUEDEBUG_TAILQ_POSTREMOVE * XXX: Don't like the constant 1 here... */ ASSERT(rqp->sr_link.tqe_next != (void *)1L); #endif + + if (TAILQ_FIRST(&vcp->iod_rqlist) == rqp) + was_head = B_TRUE; TAILQ_REMOVE(&vcp->iod_rqlist, rqp, sr_link); + if (vcp->vc_flags & SMBV_SMB2) { + rqp2 = TAILQ_FIRST(&vcp->iod_rqlist); + if (was_head && rqp2 != NULL) { + /* Do we still need this? */ + vcp->vc2_oldest_message_id = + rqp2->sr2_messageid; + } + } else { + ASSERT(vcp->iod_muxcnt > 0); + vcp->iod_muxcnt--; + } + rw_exit(&vcp->iod_rqlock); + + /* + * If there are requests waiting for "mux" slots, + * wake one. + */ + SMB_VC_LOCK(vcp); + if (vcp->iod_muxwant != 0) + cv_signal(&vcp->iod_muxwait); + SMB_VC_UNLOCK(vcp); } /* * Wait for a request to complete. */ int smb_iod_waitrq(struct smb_rq *rqp) { struct smb_vc *vcp = rqp->sr_vc; clock_t tr, tmo1, tmo2; ! int error; if (rqp->sr_flags & SMBR_INTERNAL) { ! /* XXX - Do we ever take this path now? */ ! return (smb_iod_waitrq_int(rqp)); } /* * Make sure this is NOT the IOD thread, * or the wait below will stop the reader.
*** 740,778 **** ASSERT(curthread != vcp->iod_thr); SMBRQ_LOCK(rqp); /* - * First, wait for the request to be sent. Normally the send - * has already happened by the time we get here. However, if - * we have more than maxmux entries in the request list, our - * request may not be sent until other requests complete. - * The wait in this case is due to local I/O demands, so - * we don't want the server response timeout to apply. - * - * If a request is allowed to interrupt this wait, then the - * request is cancelled and never sent OTW. Some kinds of - * requests should never be cancelled (i.e. close) and those - * are marked SMBR_NOINTR_SEND so they either go eventually, - * or a connection close will terminate them with ENOTCONN. - */ - while (rqp->sr_state == SMBRQ_NOTSENT) { - rqp->sr_flags |= SMBR_SENDWAIT; - if (rqp->sr_flags & SMBR_NOINTR_SEND) { - cv_wait(&rqp->sr_cond, &rqp->sr_lock); - rc = 1; - } else - rc = cv_wait_sig(&rqp->sr_cond, &rqp->sr_lock); - rqp->sr_flags &= ~SMBR_SENDWAIT; - if (rc == 0) { - SMBIODEBUG("EINTR in sendwait, rqp=%p\n", rqp); - error = EINTR; - goto out; - } - } - - /* * The request has been sent. Now wait for the response, * with the timeout specified for this request. * Compute all the deadlines now, so we effectively * start the timer(s) after the request is sent. */ --- 1235,1244 ----
*** 801,821 **** if (tr == 0) { error = EINTR; goto out; } if (tr < 0) { - #ifdef DTRACE_PROBE DTRACE_PROBE1(smb_iod_waitrq1, (smb_rq_t *), rqp); - #endif - #ifdef NOT_YET - /* Want this to go ONLY to the user. */ - uprintf("SMB server %s has not responded" - " to request %d after %d seconds..." - " (still waiting).\n", vcp->vc_srvname, - rqp->sr_mid, smb_timo_notice); - #endif } } /* * Keep waiting until tmo2 is expired. --- 1267,1278 ----
*** 830,850 **** if (tr == 0) { error = EINTR; goto out; } if (tr < 0) { - #ifdef DTRACE_PROBE DTRACE_PROBE1(smb_iod_waitrq2, (smb_rq_t *), rqp); - #endif - #ifdef NOT_YET - /* Want this to go ONLY to the user. */ - uprintf("SMB server %s has not responded" - " to request %d after %d seconds..." - " (giving up).\n", vcp->vc_srvname, - rqp->sr_mid, rqp->sr_timo); - #endif error = ETIME; goto out; } /* got wakeup */ } --- 1287,1298 ----
*** 859,876 **** * They may need additional responses. */ if ((rqp->sr_flags & SMBR_MULTIPACKET) == 0) smb_iod_removerq(rqp); ! /* ! * Some request has been completed. ! * If we reached the mux limit, ! * re-run the send loop... */ ! if (vcp->iod_muxfull) ! smb_iod_sendall(vcp); return (error); } /* * Shutdown all outstanding I/O requests on the specified share with --- 1307,1345 ---- * They may need additional responses. */ if ((rqp->sr_flags & SMBR_MULTIPACKET) == 0) smb_iod_removerq(rqp); ! return (error); ! } ! ! /* ! * Internal variant of smb_iod_waitrq(), for use in ! * requests run by the IOD (reader) thread itself. ! * Block only long enough to receive one reply. */ ! int ! smb_iod_waitrq_int(struct smb_rq *rqp) ! { ! struct smb_vc *vcp = rqp->sr_vc; ! int timeleft = rqp->sr_timo; ! int error; + ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0); + again: + error = smb_iod_recvall(vcp, B_TRUE); + if (error == ETIME) { + /* We waited SMB_NBTIMO sec. */ + timeleft -= SMB_NBTIMO; + if (timeleft > 0) + goto again; + } + + smb_iod_removerq(rqp); + if (rqp->sr_state != SMBRQ_NOTIFIED) + error = ETIME; + return (error); } /* * Shutdown all outstanding I/O requests on the specified share with
*** 895,1001 **** } rw_exit(&vcp->iod_rqlock); } /* ! * Send all requests that need sending. ! * Called from _addrq, _multirq, _waitrq */ ! void ! smb_iod_sendall(smb_vc_t *vcp) { ! struct smb_rq *rqp; ! int error, muxcnt; /* ! * Clear "newrq" to make sure threads adding ! * new requests will run this function again. */ ! rw_enter(&vcp->iod_rqlock, RW_WRITER); ! vcp->iod_newrq = 0; /* ! * We only read iod_rqlist, so downgrade rwlock. ! * This allows the IOD to handle responses while ! * some requesting thread may be blocked in send. */ ! rw_downgrade(&vcp->iod_rqlock); /* ! * Serialize to prevent multiple senders. ! * Note lock order: iod_rqlock, vc_sendlock */ ! sema_p(&vcp->vc_sendlock); ! /* ! * Walk the list of requests and send when possible. ! * We avoid having more than vc_maxmux requests ! * outstanding to the server by traversing only ! * vc_maxmux entries into this list. Simple! */ ! ASSERT(vcp->vc_maxmux > 0); ! error = muxcnt = 0; ! TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) { ! if (vcp->vc_state != SMBIOD_ST_VCACTIVE) { ! error = ENOTCONN; /* stop everything! */ ! break; } ! if (rqp->sr_state == SMBRQ_NOTSENT) { ! error = smb_iod_sendrq(rqp); ! if (error) ! break; } ! if (++muxcnt == vcp->vc_maxmux) { ! SMBIODEBUG("muxcnt == vc_maxmux\n"); ! break; } } /* ! * If we have vc_maxmux requests outstanding, ! * arrange for _waitrq to call _sendall as ! * requests are completed. */ ! vcp->iod_muxfull = ! (muxcnt < vcp->vc_maxmux) ? 0 : 1; ! sema_v(&vcp->vc_sendlock); ! rw_exit(&vcp->iod_rqlock); } int ! smb_iod_vc_work(struct smb_vc *vcp, cred_t *cr) { ! struct file *fp = NULL; int err = 0; /* * This is called by the one-and-only * IOD thread for this VC. */ ASSERT(vcp->iod_thr == curthread); /* ! * Get the network transport file pointer, ! * and "loan" it to our transport module. */ ! if ((fp = getf(vcp->vc_tran_fd)) == NULL) { ! err = EBADF; ! goto out; } - if ((err = SMB_TRAN_LOAN_FP(vcp, fp, cr)) != 0) - goto out; /* ! * In case of reconnect, tell any enqueued requests ! * then can GO! */ SMB_VC_LOCK(vcp); vcp->vc_genid++; /* possibly new connection */ smb_iod_newstate(vcp, SMBIOD_ST_VCACTIVE); cv_broadcast(&vcp->vc_statechg); SMB_VC_UNLOCK(vcp); --- 1364,1727 ---- } rw_exit(&vcp->iod_rqlock); } /* ! * Ioctl functions called by the user-level I/O Deamon (IOD) ! * to bring up and service a connection to some SMB server. */ ! ! /* ! * Handle ioctl SMBIOC_IOD_CONNECT ! */ ! int ! nsmb_iod_connect(struct smb_vc *vcp, cred_t *cr) { ! int err, val; + ASSERT(vcp->iod_thr == curthread); + + if (vcp->vc_state != SMBIOD_ST_RECONNECT) { + cmn_err(CE_NOTE, "iod_connect: bad state %d", vcp->vc_state); + return (EINVAL); + } + /* ! * Putting a TLI endpoint back in the right state for a new ! * connection is a bit tricky. In theory, this could be: ! * SMB_TRAN_DISCONNECT(vcp); ! * SMB_TRAN_UNBIND(vcp); ! * but that method often results in TOUTSTATE errors. ! * It's easier to just close it and open a new endpoint. */ ! SMB_VC_LOCK(vcp); ! if (vcp->vc_tdata) ! SMB_TRAN_DONE(vcp); ! err = SMB_TRAN_CREATE(vcp, cr); ! SMB_VC_UNLOCK(vcp); ! if (err != 0) ! return (err); /* ! * Set various options on this endpoint. ! * Keep going in spite of errors. */ ! val = smb_tcpsndbuf; ! err = SMB_TRAN_SETPARAM(vcp, SMBTP_SNDBUF, &val); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: setopt SNDBUF, err=%d", err); ! } ! val = smb_tcprcvbuf; ! err = SMB_TRAN_SETPARAM(vcp, SMBTP_RCVBUF, &val); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: setopt RCVBUF, err=%d", err); ! } ! val = 1; ! err = SMB_TRAN_SETPARAM(vcp, SMBTP_KEEPALIVE, &val); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: setopt KEEPALIVE, err=%d", err); ! } ! val = 1; ! err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_NODELAY, &val); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: setopt TCP_NODELAY, err=%d", err); ! } ! val = smb_connect_timeout * 1000; ! err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_CON_TMO, &val); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: setopt TCP con tmo, err=%d", err); ! } /* ! * Bind and connect */ ! err = SMB_TRAN_BIND(vcp, NULL); ! if (err != 0) { ! cmn_err(CE_NOTE, "iod_connect: t_kbind: err=%d", err); ! /* Continue on and try connect. */ ! } ! err = SMB_TRAN_CONNECT(vcp, &vcp->vc_srvaddr.sa); /* ! * No cmn_err here, as connect failures are normal, i.e. ! * when a server has multiple addresses and only some are ! * routed for us. (libsmbfs tries them all) */ ! if (err == 0) { ! SMB_VC_LOCK(vcp); ! smb_iod_newstate(vcp, SMBIOD_ST_CONNECTED); ! SMB_VC_UNLOCK(vcp); ! } /* else stay in state reconnect */ ! return (err); ! } ! ! /* ! * Handle ioctl SMBIOC_IOD_NEGOTIATE ! * Do the whole SMB1/SMB2 negotiate ! * ! * This is where we send our first request to the server. ! * If this is the first time we're talking to this server, ! * (meaning not a reconnect) then we don't know whether ! * the server supports SMB2, so we need to use the weird ! * SMB1-to-SMB2 negotiation. That's where we send an SMB1 ! * negotiate including dialect "SMB 2.???" and if the ! * server supports SMB2 we get an SMB2 reply -- Yes, an ! * SMB2 reply to an SMB1 request. A strange protocol... ! * ! * If on the other hand we already know the server supports ! * SMB2 (because this is a reconnect) or if the client side ! * has disabled SMB1 entirely, we'll skip the SMB1 part. ! */ ! int ! nsmb_iod_negotiate(struct smb_vc *vcp, cred_t *cr) ! { ! struct smb_sopt *sv = &vcp->vc_sopt; ! smb_cred_t scred; ! int err = 0; ! ! ASSERT(vcp->iod_thr == curthread); ! ! smb_credinit(&scred, cr); ! ! if (vcp->vc_state != SMBIOD_ST_CONNECTED) { ! cmn_err(CE_NOTE, "iod_negotiate: bad state %d", vcp->vc_state); ! err = EINVAL; ! goto out; } ! if (vcp->vc_maxver == 0 || vcp->vc_minver > vcp->vc_maxver) { ! err = EINVAL; ! goto out; } ! /* ! * (Re)init negotiated values ! */ ! bzero(sv, sizeof (*sv)); ! vcp->vc2_next_message_id = 0; ! vcp->vc2_limit_message_id = 1; ! vcp->vc2_session_id = 0; ! vcp->vc_next_seq = 0; ! ! /* ! * If this was reconnect, get rid of the old MAC key ! * and session key. ! */ ! SMB_VC_LOCK(vcp); ! if (vcp->vc_mackey != NULL) { ! kmem_free(vcp->vc_mackey, vcp->vc_mackeylen); ! vcp->vc_mackey = NULL; ! vcp->vc_mackeylen = 0; } + if (vcp->vc_ssnkey != NULL) { + kmem_free(vcp->vc_ssnkey, vcp->vc_ssnkeylen); + vcp->vc_ssnkey = NULL; + vcp->vc_ssnkeylen = 0; + } + SMB_VC_UNLOCK(vcp); + /* + * If this is not an SMB2 reconect (SMBV_SMB2 not set), + * and if SMB1 is enabled, do SMB1 neogotiate. Then + * if either SMB1-to-SMB2 negotiate tells us we should + * switch to SMB2, or the local configuration has + * disabled SMB1, set the SMBV_SMB2 flag. + * + * Note that vc_maxver is handled in smb_smb_negotiate + * so we never get sv_proto == SMB_DIALECT_SMB2_FF when + * the local configuration disables SMB2, and therefore + * we won't set the SMBV_SMB2 flag. + */ + if ((vcp->vc_flags & SMBV_SMB2) == 0) { + if (vcp->vc_minver < SMB2_DIALECT_BASE) { + /* + * SMB1 is enabled + */ + err = smb_smb_negotiate(vcp, &scred); + if (err != 0) + goto out; } + /* + * If SMB1-to-SMB2 negotiate told us we should + * switch to SMB2, or if the local configuration + * disables SMB1, set the SMB2 flag. + */ + if (sv->sv_proto == SMB_DIALECT_SMB2_FF || + vcp->vc_minver >= SMB2_DIALECT_BASE) { + /* + * Switch this VC to SMB2. + */ + SMB_VC_LOCK(vcp); + vcp->vc_flags |= SMBV_SMB2; + SMB_VC_UNLOCK(vcp); + } + } /* ! * If this is an SMB2 reconnect (SMBV_SMB2 was set before this ! * function was called), or SMB1-to-SMB2 negotiate indicated ! * we should switch to SMB2, or we have SMB1 disabled (both ! * cases set SMBV_SMB2 above), then do SMB2 negotiate. */ ! if ((vcp->vc_flags & SMBV_SMB2) != 0) { ! err = smb2_smb_negotiate(vcp, &scred); ! } ! out: ! if (err == 0) { ! SMB_VC_LOCK(vcp); ! smb_iod_newstate(vcp, SMBIOD_ST_NEGOTIATED); ! SMB_VC_UNLOCK(vcp); ! } ! /* ! * (else) leave state as it was. ! * User-level will either close this handle (if connecting ! * for the first time) or call rcfail and then try again. ! */ ! ! smb_credrele(&scred); ! ! return (err); } + /* + * Handle ioctl SMBIOC_IOD_SSNSETUP + * Do either SMB1 or SMB2 session setup (one call/reply) + */ int ! nsmb_iod_ssnsetup(struct smb_vc *vcp, cred_t *cr) { ! smb_cred_t scred; ! int err; ! ! ASSERT(vcp->iod_thr == curthread); ! ! switch (vcp->vc_state) { ! case SMBIOD_ST_NEGOTIATED: ! case SMBIOD_ST_AUTHCONT: ! break; ! default: ! return (EINVAL); ! } ! ! smb_credinit(&scred, cr); ! if (vcp->vc_flags & SMBV_SMB2) ! err = smb2_smb_ssnsetup(vcp, &scred); ! else ! err = smb_smb_ssnsetup(vcp, &scred); ! smb_credrele(&scred); ! ! SMB_VC_LOCK(vcp); ! switch (err) { ! case 0: ! smb_iod_newstate(vcp, SMBIOD_ST_AUTHOK); ! break; ! case EINPROGRESS: /* MORE_PROCESSING_REQUIRED */ ! smb_iod_newstate(vcp, SMBIOD_ST_AUTHCONT); ! break; ! default: ! smb_iod_newstate(vcp, SMBIOD_ST_AUTHFAIL); ! break; ! } ! SMB_VC_UNLOCK(vcp); ! ! return (err); ! } ! ! static int ! smb_iod_logoff(struct smb_vc *vcp, cred_t *cr) ! { ! smb_cred_t scred; ! int err; ! ! ASSERT(vcp->iod_thr == curthread); ! ! smb_credinit(&scred, cr); ! if (vcp->vc_flags & SMBV_SMB2) ! err = smb2_smb_logoff(vcp, &scred); ! else ! err = smb_smb_logoff(vcp, &scred); ! smb_credrele(&scred); ! ! return (err); ! } ! ! /* ! * Handle ioctl SMBIOC_IOD_WORK ! * ! * The smbiod agent calls this after authentication to become ! * the reader for this session, so long as that's possible. ! * This should only return non-zero if we want that agent to ! * give up on this VC permanently. ! */ ! /* ARGSUSED */ ! int ! smb_iod_vc_work(struct smb_vc *vcp, int flags, cred_t *cr) ! { ! smbioc_ssn_work_t *wk = &vcp->vc_work; int err = 0; /* * This is called by the one-and-only * IOD thread for this VC. */ ASSERT(vcp->iod_thr == curthread); /* ! * Should be in state... */ ! if (vcp->vc_state != SMBIOD_ST_AUTHOK) { ! cmn_err(CE_NOTE, "iod_vc_work: bad state %d", vcp->vc_state); ! return (EINVAL); } /* ! * Update the session key and initialize SMB signing. ! * ! * This implementation does not use multiple SMB sessions per ! * TCP connection (where only the first session key is used) ! * so we always have a new session key here. Sanity check the ! * length from user space. Normally 16 or 32. */ + if (wk->wk_u_ssnkey_len > 1024) { + cmn_err(CE_NOTE, "iod_vc_work: ssn key too long"); + return (EINVAL); + } + + ASSERT(vcp->vc_ssnkey == NULL); SMB_VC_LOCK(vcp); + if (wk->wk_u_ssnkey_len != 0 && + wk->wk_u_ssnkey_buf.lp_ptr != NULL) { + vcp->vc_ssnkeylen = wk->wk_u_ssnkey_len; + vcp->vc_ssnkey = kmem_alloc(vcp->vc_ssnkeylen, KM_SLEEP); + if (ddi_copyin(wk->wk_u_ssnkey_buf.lp_ptr, + vcp->vc_ssnkey, vcp->vc_ssnkeylen, flags) != 0) { + err = EFAULT; + } + } + SMB_VC_UNLOCK(vcp); + if (err) + return (err); + + /* + * If we have a session key, derive the MAC key for SMB signing. + * If this was a NULL session, we might have no session key. + */ + ASSERT(vcp->vc_mackey == NULL); + if (vcp->vc_ssnkey != NULL) { + if (vcp->vc_flags & SMBV_SMB2) + err = smb2_sign_init(vcp); + else + err = smb_sign_init(vcp); + if (err != 0) + return (err); + } + + /* + * Tell any enqueued requests they can start. + */ + SMB_VC_LOCK(vcp); vcp->vc_genid++; /* possibly new connection */ smb_iod_newstate(vcp, SMBIOD_ST_VCACTIVE); cv_broadcast(&vcp->vc_statechg); SMB_VC_UNLOCK(vcp);
*** 1008,1020 **** */ if (fscb && fscb->fscb_connect) smb_vc_walkshares(vcp, fscb->fscb_connect); /* ! * Run the "reader" loop. */ ! err = smb_iod_recvall(vcp); /* * The reader loop returned, so we must have a * new state. (disconnected or reconnecting) * --- 1734,1748 ---- */ if (fscb && fscb->fscb_connect) smb_vc_walkshares(vcp, fscb->fscb_connect); /* ! * Run the "reader" loop. An error return here is normal ! * (i.e. when we need to reconnect) so ignore errors. ! * Note: This call updates the vc_state. */ ! (void) smb_iod_recvall(vcp, B_FALSE); /* * The reader loop returned, so we must have a * new state. (disconnected or reconnecting) *
*** 1031,1091 **** * * Tell any requests to give up or restart. */ smb_iod_invrq(vcp); - out: - /* Recall the file descriptor loan. */ - (void) SMB_TRAN_LOAN_FP(vcp, NULL, cr); - if (fp != NULL) { - releasef(vcp->vc_tran_fd); - } - return (err); } /* ! * Wait around for someone to ask to use this VC. ! * If the VC has only the IOD reference, then ! * wait only a minute or so, then drop it. */ int smb_iod_vc_idle(struct smb_vc *vcp) { - clock_t tr, delta = SEC_TO_TICK(15); int err = 0; /* * This is called by the one-and-only * IOD thread for this VC. */ ASSERT(vcp->iod_thr == curthread); SMB_VC_LOCK(vcp); ! while (vcp->vc_state == SMBIOD_ST_IDLE) { ! tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock, ! delta, TR_CLOCK_TICK); ! if (tr == 0) { err = EINTR; break; } - if (tr < 0) { - /* timeout */ - if (vcp->vc_co.co_usecount == 1) { - /* Let this IOD terminate. */ - smb_iod_newstate(vcp, SMBIOD_ST_DEAD); - /* nobody to cv_broadcast */ - break; } } ! } SMB_VC_UNLOCK(vcp); return (err); } /* * After a failed reconnect attempt, smbiod will * call this to make current requests error out. */ int smb_iod_vc_rcfail(struct smb_vc *vcp) --- 1759,1837 ---- * * Tell any requests to give up or restart. */ smb_iod_invrq(vcp); return (err); } /* ! * Handle ioctl SMBIOC_IOD_IDLE ! * ! * Wait around for someone to ask to use this VC again after the ! * TCP session has closed. When one of the connected trees adds a ! * request, smb_iod_reconnect will set vc_state to RECONNECT and ! * wake this cv_wait. When a VC ref. goes away in smb_vc_rele, ! * that also signals this wait so we can re-check whether we ! * now hold the last ref. on this VC (and can destroy it). */ int smb_iod_vc_idle(struct smb_vc *vcp) { int err = 0; + boolean_t destroy = B_FALSE; /* * This is called by the one-and-only * IOD thread for this VC. */ ASSERT(vcp->iod_thr == curthread); + /* + * Should be in state... + */ + if (vcp->vc_state != SMBIOD_ST_IDLE && + vcp->vc_state != SMBIOD_ST_RECONNECT) { + cmn_err(CE_NOTE, "iod_vc_idle: bad state %d", vcp->vc_state); + return (EINVAL); + } + SMB_VC_LOCK(vcp); ! ! while (vcp->vc_state == SMBIOD_ST_IDLE && ! vcp->vc_co.co_usecount > 1) { ! if (cv_wait_sig(&vcp->iod_idle, &vcp->vc_lock) == 0) { err = EINTR; break; } } + if (vcp->vc_state == SMBIOD_ST_IDLE && + vcp->vc_co.co_usecount == 1) { + /* + * We were woken because we now have the last ref. + * Arrange for this VC to be destroyed now. + * Set the "GONE" flag while holding the lock, + * to prevent a race with new references. + * The destroy happens after unlock. + */ + vcp->vc_flags |= SMBV_GONE; + destroy = B_TRUE; } ! SMB_VC_UNLOCK(vcp); + if (destroy) { + /* This sets vc_state = DEAD */ + smb_iod_disconnect(vcp); + } + return (err); } /* + * Handle ioctl SMBIOC_IOD_RCFAIL + * * After a failed reconnect attempt, smbiod will * call this to make current requests error out. */ int smb_iod_vc_rcfail(struct smb_vc *vcp)
*** 1096,1109 **** /* * This is called by the one-and-only * IOD thread for this VC. */ ASSERT(vcp->iod_thr == curthread); - - if (vcp->vc_state != SMBIOD_ST_RECONNECT) - return (EINVAL); - SMB_VC_LOCK(vcp); smb_iod_newstate(vcp, SMBIOD_ST_RCFAILED); cv_broadcast(&vcp->vc_statechg); --- 1842,1851 ----
*** 1115,1126 **** --- 1857,1878 ---- tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock, SEC_TO_TICK(5), TR_CLOCK_TICK); if (tr == 0) err = EINTR; + /* + * Normally we'll switch to state IDLE here. However, + * if something called smb_iod_reconnect() while we were + * waiting above, we'll be in in state reconnect already. + * In that case, keep state RECONNECT, so we essentially + * skip transition through state IDLE that would normally + * happen next. + */ + if (vcp->vc_state != SMBIOD_ST_RECONNECT) { smb_iod_newstate(vcp, SMBIOD_ST_IDLE); cv_broadcast(&vcp->vc_statechg); + } SMB_VC_UNLOCK(vcp); return (err); }
*** 1137,1151 **** --- 1889,1909 ---- SMB_VC_LOCK(vcp); again: switch (vcp->vc_state) { case SMBIOD_ST_IDLE: + /* Tell the IOD thread it's no longer IDLE. */ smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT); cv_signal(&vcp->iod_idle); /* FALLTHROUGH */ case SMBIOD_ST_RECONNECT: + case SMBIOD_ST_CONNECTED: + case SMBIOD_ST_NEGOTIATED: + case SMBIOD_ST_AUTHCONT: + case SMBIOD_ST_AUTHOK: + /* Wait for the VC state to become ACTIVE. */ rv = cv_wait_sig(&vcp->vc_statechg, &vcp->vc_lock); if (rv == 0) { err = EINTR; break; }
*** 1153,1162 **** --- 1911,1921 ---- case SMBIOD_ST_VCACTIVE: err = 0; /* success! */ break; + case SMBIOD_ST_AUTHFAIL: case SMBIOD_ST_RCFAILED: case SMBIOD_ST_DEAD: default: err = ENOTCONN; break;