And simplify smb_sendv to make it process SMB2 requests. It let us send SMB2 negotiate message to the server. Signed-off-by: Pavel Shilovsky <piastry@xxxxxxxxxxx> --- fs/cifs/cifsglob.h | 6 ++ fs/cifs/cifsproto.h | 4 +- fs/cifs/smb2misc.c | 11 +-- fs/cifs/smb2proto.h | 4 +- fs/cifs/smb2transport.c | 209 ++++++++++++++++++++++++++++++++++++++++------- fs/cifs/transport.c | 75 ++++++++++------- 6 files changed, 240 insertions(+), 69 deletions(-) diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 2703b7e..4ccd893 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -239,6 +239,12 @@ struct cifs_mnt_data { int flags; }; +static inline unsigned int +get_rfc1002_length(void *buf) +{ + return be32_to_cpu(*((__be32 *)buf)); +} + struct TCP_Server_Info { struct list_head tcp_ses_list; struct list_head smb_ses_list; diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 2b3fa3b..5109925 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -36,7 +36,9 @@ extern void cifs_buf_release(void *); extern struct smb_hdr *cifs_small_buf_get(void); extern void cifs_small_buf_release(void *); extern int smb_send(struct TCP_Server_Info *, struct smb_hdr *, - unsigned int /* length */); + unsigned int /* length */); +extern int smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, + int n_vec); extern unsigned int _GetXid(void); extern void _FreeXid(unsigned int); #define GetXid() \ diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index 1a600d8..6f7afdc 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c @@ -27,19 +27,18 @@ #include "cifs_unicode.h" #include "smb2status.h" -/* -__u64 get_mid(struct tcp_srv_inf *server) +__u64 get_mid(struct TCP_Server_Info *server) { __u64 mid; if (server == NULL) return 0; - spin_lock(&SMB2_mid_lock); - mid = server->current_mid++; - spin_unlock(&SMB2_mid_lock); + spin_lock(&GlobalMid_Lock); + mid = server->current_smb2_mid++; + spin_unlock(&GlobalMid_Lock); return mid; -} */ /* BB do we eventually need an SMB2 version of this routine? BB */ +} /* BB do we eventually need an SMB2 version of this routine? BB */ static int check_smb2_hdr(struct smb2_hdr *smb, __u64 mid) diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h index c4c40bd..e826279 100644 --- a/fs/cifs/smb2proto.h +++ b/fs/cifs/smb2proto.h @@ -38,8 +38,8 @@ extern void free_rsp_buf(int resp_buftype, void *pSMB2r); extern struct smb2_hdr *smb2_buf_get(void); extern void smb2_buf_release(void *); extern struct smb2_hdr *smb2_small_buf_get(void); -extern void smb2_small_buf_release(void *); -extern __u64 get_mid(struct TCP_Server_Info *server);*/ +extern void smb2_small_buf_release(void *);*/ +extern __u64 get_mid(struct TCP_Server_Info *server); extern int small_smb2_init_no_tc(__le16 smb2_cmd, struct cifs_ses *ses, void **request_buf); diff --git a/fs/cifs/smb2transport.c b/fs/cifs/smb2transport.c index 2ca9943..adf24c4 100644 --- a/fs/cifs/smb2transport.c +++ b/fs/cifs/smb2transport.c @@ -38,22 +38,15 @@ extern mempool_t *smb2_mid_poolp; /* - * Send an (optionally, already signed) SMB2 request over a socket. - * This socket is already locked (by a mutex) by the caller so we - * won't have framing problems or mess up SMB2 signatures. + * Set message id for the request. Should be called after wait_for_free_response + * and locking srv_mutex. iov array must have at least 1 element. */ - -int smb2_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) +static inline void smb2_seq_num_into_buf(struct TCP_Server_Info *server, + struct kvec *iov) { - int rc = -EHOSTDOWN; - - cFYI(1, "function not merged yet"); /* BB fixme */ - - return rc; + ((struct smb2_hdr *)iov[0].iov_base)->MessageId = get_mid(server); } - - /* * * Send an SMB Request. No response info (other than return code) @@ -108,23 +101,6 @@ smb2_sendrcv_blocking(const unsigned int xid, struct cifs_tcon *tcon, return rc; } -/* - * sendrcv2 is passed a cifs_ses structure (rather than simply being - * passed the ses->server->socket), because it needs the creds - * contained in the cifs_ses struct in order to sign requests. - */ -int -smb2_sendrcv2(const unsigned int xid, struct cifs_ses *ses, - struct kvec *iov, int n_vec, int *presp_buftype /* ret */, - int *status /* ret SMB2 network status code */, const int flags) -{ - int rc = -EHOSTDOWN; - - cFYI(1, "function not merged yet"); /* BB fixme */ - - return rc; -} - static void wake_up_smb2_task(struct smb2_mid_entry *mid) { @@ -251,7 +227,7 @@ wait_for_smb2_response(struct TCP_Server_Info *server, { int error; - error = wait_event_killable(server->response_q, + error = wait_event_freezekillable(server->response_q, midq->mid_state != MID_REQUEST_SUBMITTED); if (error < 0) return -ERESTARTSYS; @@ -311,4 +287,177 @@ free_smb2_mid(struct smb2_mid_entry *mid) smb2_mid_entry_free(mid); } + +static int +smb2_check_receive(struct smb2_mid_entry *mid, struct TCP_Server_Info *server, + unsigned int receive_len, bool log_error) +{ + unsigned int len = get_rfc1002_length(mid->resp_buf); + + dump_smb2(mid->resp_buf, min_t(u32, 80, len)); + /* convert the length into a more usable form */ + if ((receive_len > 24) && + (server->sec_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED)) { + /* BB fixme */ + /*rc = smb2_verify_signature(mid->resp_buf, + &ses->server->mac_signing_key); + if (rc) { + cERROR(1, "Unexpected SMB signature"); + } */ + } + + return map_smb2_to_linux_error(mid->resp_buf, log_error); +} + +/* + * sendrcv2 is passed a cifs_ses structure (rather than simply being + * passed the ses->server->socket), because it needs the creds + * contained in the cifs_ses struct in order to sign requests + */ +int +smb2_sendrcv2(const unsigned int xid, struct cifs_ses *ses, + struct kvec *iov, int n_vec, int *presp_buftype /* ret */, + int *status /* ret SMB2 network status code */, const int flags) +{ + int rc = 0; + int long_op; + unsigned int receive_len; + struct smb2_mid_entry *midQ; + struct smb2_hdr *buf = iov[0].iov_base; + unsigned int credits = 1; + + if (status) + *status = STATUS_SUCCESS; + long_op = flags & CIFS_TIMEOUT_MASK; + + *presp_buftype = CIFS_NO_BUFFER; /* no response buf yet */ + + if ((ses == NULL) || (ses->server == NULL)) { + cifs_small_buf_release(buf); + cERROR(1, "Null session"); + return -EIO; + } + + if (ses->server->tcpStatus == CifsExiting) { + cifs_small_buf_release(buf); + cFYI(1, "ololo"); + return -ENOENT; + } + + rc = wait_for_free_request(ses->server, long_op); + if (rc) { + cifs_small_buf_release(buf); + return rc; + } + + /* + * Make sure that we sign in the same order that we send on this socket + * and avoid races inside tcp sendmsg code that could cause corruption + * of smb data. + */ + + mutex_lock(&ses->server->srv_mutex); + + smb2_seq_num_into_buf(ses->server, iov); + + rc = get_smb2_mid(ses, buf, &midQ); + if (rc) { + mutex_unlock(&ses->server->srv_mutex); + cifs_small_buf_release(buf); + atomic_inc(&ses->server->credits); + wake_up(&ses->server->request_q); + return rc; + } + /* rc = sign_smb2(iov, n_vec, ses->server); BB + if (rc) { + mutex_unlock(&ses->server->srv_mutex); + cifs_small_buf_release(in_buf); + goto out; + } */ + + midQ->mid_state = MID_REQUEST_SUBMITTED; + cifs_in_send_inc(ses->server); + rc = smb_sendv(ses->server, iov, n_vec); + cifs_in_send_dec(ses->server); + cifs_save_when_sent(midQ); + + mutex_unlock(&ses->server->srv_mutex); + + if (rc < 0) { + cifs_small_buf_release(buf); + goto out; + } + + if (long_op == CIFS_ASYNC_OP) { + cifs_small_buf_release(buf); + goto out; + } + + rc = wait_for_smb2_response(ses->server, midQ); + if (rc != 0) { + /* send_nt_cancel(ses->server, in_buf, midQ); BB */ + spin_lock(&GlobalMid_Lock); + if (midQ->mid_state == MID_REQUEST_SUBMITTED) { + midQ->callback = free_smb2_mid; + spin_unlock(&GlobalMid_Lock); + cifs_small_buf_release(buf); + atomic_inc(&ses->server->credits); + wake_up(&ses->server->request_q); + return rc; + } + spin_unlock(&GlobalMid_Lock); + } + + cifs_small_buf_release(buf); + + rc = sync_smb2_mid_result(midQ, ses->server); + if (rc) { + atomic_inc(&ses->server->credits); + wake_up(&ses->server->request_q); + return rc; + } + + if (!midQ->resp_buf || (midQ->mid_state != MID_RESPONSE_RECEIVED)) { + rc = -EIO; + cFYI(1, "Bad MID state?"); + goto out; + } + + buf = (struct smb2_hdr *)midQ->resp_buf; + receive_len = be32_to_cpu(buf->smb2_buf_length); + + if (receive_len > CIFSMaxBufSize + MAX_SMB2_HDR_SIZE) { + cERROR(1, "Frame too large received. Length: %d Xid: %d", + receive_len, xid); + rc = -EIO; + goto out; + } + + /* rcvd frame is ok */ + + iov[0].iov_base = (char *)buf; + iov[0].iov_len = receive_len + 4; + + if (midQ->large_buf) + *presp_buftype = CIFS_LARGE_BUFFER; + else + *presp_buftype = CIFS_SMALL_BUFFER; + + rc = smb2_check_receive(midQ, ses->server, receive_len, + flags & CIFS_LOG_ERROR); + + if (status) + *status = le32_to_cpu(buf->Status); + + if ((flags & CIFS_NO_RESP) == 0) + /* mark it so buf will not be freed by free_smb2mid */ + midQ->resp_buf = NULL; + + credits = le16_to_cpu(buf->CreditRequest); +out: + atomic_add(credits, &ses->server->credits); + free_smb2_mid(midQ); + wake_up(&ses->server->request_q); + return rc; +} /* BB add missing functions here */ diff --git a/fs/cifs/transport.c b/fs/cifs/transport.c index 25d04df..ee8a1f8 100644 --- a/fs/cifs/transport.c +++ b/fs/cifs/transport.c @@ -120,18 +120,24 @@ delete_mid(struct mid_q_entry *mid) DeleteMidQEntry(mid); } -static int +int smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) { int rc = 0; int i = 0; struct msghdr smb_msg; + __be32 *buf_length = (__be32 *)iov[0].iov_base; struct smb_hdr *smb_buffer = iov[0].iov_base; +#ifdef CONFIG_CIFS_SMB2 + struct smb2_hdr *smb2_buffer = iov[0].iov_base; +#endif unsigned int len = iov[0].iov_len; unsigned int total_len; int first_vec = 0; - unsigned int smb_buf_length = be32_to_cpu(smb_buffer->smb_buf_length); struct socket *ssocket = server->ssocket; + unsigned int smb_buf_length; + + smb_buf_length = get_rfc1002_length(iov[0].iov_base); if (ssocket == NULL) return -ENOTSOCK; /* BB eventually add reconnect code here */ @@ -150,7 +156,12 @@ smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) total_len += iov[i].iov_len; cFYI(1, "Sending smb: total_len %d", total_len); - dump_smb(smb_buffer, len); +#ifdef CONFIG_CIFS_SMB2 + if (server->is_smb2) + dump_smb2(smb2_buffer, len); + else +#endif + dump_smb(smb_buffer, len); i = 0; while (total_len) { @@ -158,24 +169,25 @@ smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) n_vec - first_vec, total_len); if ((rc == -ENOSPC) || (rc == -EAGAIN)) { i++; - /* if blocking send we try 3 times, since each can block - for 5 seconds. For nonblocking we have to try more - but wait increasing amounts of time allowing time for - socket to clear. The overall time we wait in either - case to send on the socket is about 15 seconds. - Similarly we wait for 15 seconds for - a response from the server in SendReceive[2] - for the server to send a response back for - most types of requests (except SMB Write - past end of file which can be slow, and - blocking lock operations). NFS waits slightly longer - than CIFS, but this can make it take longer for - nonresponsive servers to be detected and 15 seconds - is more than enough time for modern networks to - send a packet. In most cases if we fail to send - after the retries we will kill the socket and - reconnect which may clear the network problem. - */ + /* + * If blocking send we try 3 times, since each can block + * for 5 seconds. For nonblocking we have to try more + * but wait increasing amounts of time allowing time for + * socket to clear. The overall time we wait in either + * case to send on the socket is about 15 seconds. + * Similarly we wait for 15 seconds for + * a response from the server in SendReceive[2] + * for the server to send a response back for + * most types of requests (except SMB Write + * past end of file which can be slow, and + * blocking lock operations). NFS waits slightly longer + * than CIFS, but this can make it take longer for + * nonresponsive servers to be detected and 15 seconds + * is more than enough time for modern networks to + * send a packet. In most cases if we fail to send + * after the retries we will kill the socket and + * reconnect which may clear the network problem. + */ if ((i >= 14) || (!server->noblocksnd && (i > 2))) { cERROR(1, "sends on sock %p stuck for 15 seconds", ssocket); @@ -196,8 +208,10 @@ smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) break; } if (rc == 0) { - /* should never happen, letting socket clear before - retrying is our only obvious option here */ + /* + * Should never happen, letting socket clear before + * retrying is our only obvious option here. + */ cERROR(1, "tcp sent no data"); msleep(500); continue; @@ -223,10 +237,12 @@ smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) if ((total_len > 0) && (total_len != smb_buf_length + 4)) { cFYI(1, "partial send (%d remaining), terminating session", total_len); - /* If we have only sent part of an SMB then the next SMB - could be taken as the remainder of this one. We need - to kill the socket so the server throws away the partial - SMB */ + /* + * If we have only sent part of an SMB then the next SMB + * could be taken as the remainder of this one. We need + * to kill the socket so the server throws away the partial + * SMB. + */ server->tcpStatus = CifsNeedReconnect; } @@ -235,9 +251,8 @@ smb_sendv(struct TCP_Server_Info *server, struct kvec *iov, int n_vec) else rc = 0; - /* Don't want to modify the buffer as a - side effect of this call. */ - smb_buffer->smb_buf_length = cpu_to_be32(smb_buf_length); + /* Don't want to modify the buffer as a side effect of this call. */ + *buf_length = cpu_to_be32(smb_buf_length); return rc; } -- 1.7.1 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html