Skip to content

Commit

Permalink
cifs: try opening channels after mounting
Browse files Browse the repository at this point in the history
After doing mount() successfully we call cifs_try_adding_channels()
which will open as many channels as it can.

Channels are closed when the master session is closed.

The master connection becomes the first channel.

,-------------> global cifs_tcp_ses_list <-------------------------.
|                                                                  |
'- TCP_Server_Info  <-->  TCP_Server_Info  <-->  TCP_Server_Info <-'
      (master con)           (chan#1 con)         (chan#2 con)
      |      ^                    ^                    ^
      v      '--------------------|--------------------'
   cifs_ses                       |
   - chan_count = 3               |
   - chans[] ---------------------'
   - smb3signingkey[]
      (master signing key)

Note how channel connections don't have sessions. That's because
cifs_ses can only be part of one linked list (list_head are internal
to the elements).

For signing keys, each channel has its own signing key which must be
used only after the channel has been bound. While it's binding it must
use the master session signing key.

For encryption keys, since channel connections do not have sessions
attached we must now find matching session by looping over all sessions
in smb2_get_enc_key().

Each channel is opened like a regular server connection but at the
session setup request step it must set the
SMB2_SESSION_REQ_FLAG_BINDING flag and use the session id to bind to.

Finally, while sending in compound_send_recv() for requests that
aren't negprot, ses-setup or binding related, use a channel by cycling
through the available ones (round-robin).

Signed-off-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Aurelien Aptel authored and Steve French committed Nov 25, 2019
1 parent b8f7442 commit d70e9fa
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 88 deletions.
16 changes: 16 additions & 0 deletions fs/cifs/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,8 @@ struct cifs_ses {
__u8 smb3decryptionkey[SMB3_SIGN_KEY_SIZE];
__u8 preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE];

__u8 binding_preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE];

/*
* Network interfaces available on the server this session is
* connected to.
Expand All @@ -1022,6 +1024,20 @@ struct cifs_ses {
atomic_t chan_seq; /* round robin state */
};

/*
* When binding a new channel, we need to access the channel which isn't fully
* established yet (one past the established count)
*/

static inline
struct cifs_chan *cifs_ses_binding_channel(struct cifs_ses *ses)
{
if (ses->binding)
return &ses->chans[ses->chan_count];
else
return NULL;
}

static inline
struct TCP_Server_Info *cifs_ses_server(struct cifs_ses *ses)
{
Expand Down
7 changes: 7 additions & 0 deletions fs/cifs/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ extern void cifs_add_pending_open_locked(struct cifs_fid *fid,
struct tcon_link *tlink,
struct cifs_pending_open *open);
extern void cifs_del_pending_open(struct cifs_pending_open *open);
extern struct TCP_Server_Info *cifs_get_tcp_session(struct smb_vol *vol);
extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
int from_reconnect);
extern void cifs_put_tcon(struct cifs_tcon *tcon);
Expand Down Expand Up @@ -585,6 +586,12 @@ void cifs_free_hash(struct crypto_shash **shash, struct sdesc **sdesc);

extern void rqst_page_get_length(struct smb_rqst *rqst, unsigned int page,
unsigned int *len, unsigned int *offset);
int cifs_try_adding_channels(struct cifs_ses *ses);
int cifs_ses_add_channel(struct cifs_ses *ses,
struct cifs_server_iface *iface);
bool is_server_using_iface(struct TCP_Server_Info *server,
struct cifs_server_iface *iface);
bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface);

void extract_unc_hostname(const char *unc, const char **h, size_t *len);
int copy_path_name(char *dst, const char *src);
Expand Down
48 changes: 35 additions & 13 deletions fs/cifs/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -2745,7 +2745,7 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
send_sig(SIGKILL, task, 1);
}

static struct TCP_Server_Info *
struct TCP_Server_Info *
cifs_get_tcp_session(struct smb_vol *volume_info)
{
struct TCP_Server_Info *tcp_ses = NULL;
Expand Down Expand Up @@ -3074,6 +3074,14 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
list_del_init(&ses->smb_ses_list);
spin_unlock(&cifs_tcp_ses_lock);

/* close any extra channels */
if (ses->chan_count > 1) {
int i;

for (i = 1; i < ses->chan_count; i++)
cifs_put_tcp_session(ses->chans[i].server, 0);
}

sesInfoFree(ses);
cifs_put_tcp_session(server, 0);
}
Expand Down Expand Up @@ -3320,14 +3328,25 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
ses->sectype = volume_info->sectype;
ses->sign = volume_info->sign;
mutex_lock(&ses->session_mutex);

/* add server as first channel */
ses->chans[0].server = server;
ses->chan_count = 1;
ses->chan_max = volume_info->multichannel ? volume_info->max_channels:1;

rc = cifs_negotiate_protocol(xid, ses);
if (!rc)
rc = cifs_setup_session(xid, ses, volume_info->local_nls);

/* each channel uses a different signing key */
memcpy(ses->chans[0].signkey, ses->smb3signingkey,
sizeof(ses->smb3signingkey));

mutex_unlock(&ses->session_mutex);
if (rc)
goto get_ses_fail;

/* success, put it on the list */
/* success, put it on the list and add it as first channel */
spin_lock(&cifs_tcp_ses_lock);
list_add(&ses->smb_ses_list, &server->smb_ses_list);
spin_unlock(&cifs_tcp_ses_lock);
Expand Down Expand Up @@ -4940,6 +4959,7 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
cifs_autodisable_serverino(cifs_sb);
out:
free_xid(xid);
cifs_try_adding_channels(ses);
return mount_setup_tlink(cifs_sb, ses, tcon);

error:
Expand Down Expand Up @@ -5214,21 +5234,23 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
int rc = -ENOSYS;
struct TCP_Server_Info *server = cifs_ses_server(ses);

ses->capabilities = server->capabilities;
if (linuxExtEnabled == 0)
ses->capabilities &= (~server->vals->cap_unix);
if (!ses->binding) {
ses->capabilities = server->capabilities;
if (linuxExtEnabled == 0)
ses->capabilities &= (~server->vals->cap_unix);

if (ses->auth_key.response) {
cifs_dbg(FYI, "Free previous auth_key.response = %p\n",
ses->auth_key.response);
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
ses->auth_key.len = 0;
}
}

cifs_dbg(FYI, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d\n",
server->sec_mode, server->capabilities, server->timeAdj);

if (ses->auth_key.response) {
cifs_dbg(FYI, "Free previous auth_key.response = %p\n",
ses->auth_key.response);
kfree(ses->auth_key.response);
ses->auth_key.response = NULL;
ses->auth_key.len = 0;
}

if (server->ops->sess_setup)
rc = server->ops->sess_setup(xid, ses, nls_info);

Expand Down
213 changes: 213 additions & 0 deletions fs/cifs/sess.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,219 @@
#include <linux/utsname.h>
#include <linux/slab.h>
#include "cifs_spnego.h"
#include "smb2proto.h"

bool
is_server_using_iface(struct TCP_Server_Info *server,
struct cifs_server_iface *iface)
{
struct sockaddr_in *i4 = (struct sockaddr_in *)&iface->sockaddr;
struct sockaddr_in6 *i6 = (struct sockaddr_in6 *)&iface->sockaddr;
struct sockaddr_in *s4 = (struct sockaddr_in *)&server->dstaddr;
struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&server->dstaddr;

if (server->dstaddr.ss_family != iface->sockaddr.ss_family)
return false;
if (server->dstaddr.ss_family == AF_INET) {
if (s4->sin_addr.s_addr != i4->sin_addr.s_addr)
return false;
} else if (server->dstaddr.ss_family == AF_INET6) {
if (memcmp(&s6->sin6_addr, &i6->sin6_addr,
sizeof(i6->sin6_addr)) != 0)
return false;
} else {
/* unknown family.. */
return false;
}
return true;
}

bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
{
int i;

for (i = 0; i < ses->chan_count; i++) {
if (is_server_using_iface(ses->chans[i].server, iface))
return true;
}
return false;
}

/* returns number of channels added */
int cifs_try_adding_channels(struct cifs_ses *ses)
{
int old_chan_count = ses->chan_count;
int left = ses->chan_max - ses->chan_count;
int i = 0;
int rc = 0;

if (left <= 0) {
cifs_dbg(FYI,
"ses already at max_channels (%zu), nothing to open\n",
ses->chan_max);
return 0;
}

if (ses->server->dialect < SMB30_PROT_ID) {
cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
return 0;
}

/* ifaces are sorted by speed, try them in order */
for (i = 0; left > 0 && i < ses->iface_count; i++) {
struct cifs_server_iface *iface;

iface = &ses->iface_list[i];
if (is_ses_using_iface(ses, iface) && !iface->rss_capable)
continue;

rc = cifs_ses_add_channel(ses, iface);
if (rc) {
cifs_dbg(FYI, "failed to open extra channel\n");
continue;
}

cifs_dbg(FYI, "successfully opened new channel\n");
left--;
}

/*
* TODO: if we still have channels left to open try to connect
* to same RSS-capable iface multiple times
*/

return ses->chan_count - old_chan_count;
}

int
cifs_ses_add_channel(struct cifs_ses *ses, struct cifs_server_iface *iface)
{
struct cifs_chan *chan;
struct smb_vol vol = {NULL};
static const char unc_fmt[] = "\\%s\\foo";
char unc[sizeof(unc_fmt)+SERVER_NAME_LEN_WITH_NULL] = {0};
struct sockaddr_in *ipv4 = (struct sockaddr_in *)&iface->sockaddr;
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&iface->sockaddr;
int rc;
unsigned int xid = get_xid();

cifs_dbg(FYI, "adding channel to ses %p (speed:%zu bps rdma:%s ",
ses, iface->speed, iface->rdma_capable ? "yes" : "no");
if (iface->sockaddr.ss_family == AF_INET)
cifs_dbg(FYI, "ip:%pI4)\n", &ipv4->sin_addr);
else
cifs_dbg(FYI, "ip:%pI6)\n", &ipv6->sin6_addr);

/*
* Setup a smb_vol with mostly the same info as the existing
* session and overwrite it with the requested iface data.
*
* We need to setup at least the fields used for negprot and
* sesssetup.
*
* We only need the volume here, so we can reuse memory from
* the session and server without caring about memory
* management.
*/

/* Always make new connection for now (TODO?) */
vol.nosharesock = true;

/* Auth */
vol.domainauto = ses->domainAuto;
vol.domainname = ses->domainName;
vol.username = ses->user_name;
vol.password = ses->password;
vol.sectype = ses->sectype;
vol.sign = ses->sign;

/* UNC and paths */
/* XXX: Use ses->server->hostname? */
sprintf(unc, unc_fmt, ses->serverName);
vol.UNC = unc;
vol.prepath = "";

/* Re-use same version as master connection */
vol.vals = ses->server->vals;
vol.ops = ses->server->ops;

vol.noblocksnd = ses->server->noblocksnd;
vol.noautotune = ses->server->noautotune;
vol.sockopt_tcp_nodelay = ses->server->tcp_nodelay;
vol.echo_interval = ses->server->echo_interval / HZ;

/*
* This will be used for encoding/decoding user/domain/pw
* during sess setup auth.
*
* XXX: We use the default for simplicity but the proper way
* would be to use the one that ses used, which is not
* stored. This might break when dealing with non-ascii
* strings.
*/
vol.local_nls = load_nls_default();

/* Use RDMA if possible */
vol.rdma = iface->rdma_capable;
memcpy(&vol.dstaddr, &iface->sockaddr, sizeof(struct sockaddr_storage));

/* reuse master con client guid */
memcpy(&vol.client_guid, ses->server->client_guid,
SMB2_CLIENT_GUID_SIZE);
vol.use_client_guid = true;

mutex_lock(&ses->session_mutex);

chan = &ses->chans[ses->chan_count];
chan->server = cifs_get_tcp_session(&vol);
if (IS_ERR(chan->server)) {
rc = PTR_ERR(chan->server);
chan->server = NULL;
goto out;
}

/*
* We need to allocate the server crypto now as we will need
* to sign packets before we generate the channel signing key
* (we sign with the session key)
*/
rc = smb311_crypto_shash_allocate(chan->server);
if (rc) {
cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__);
goto out;
}

ses->binding = true;
rc = cifs_negotiate_protocol(xid, ses);
if (rc)
goto out;

rc = cifs_setup_session(xid, ses, vol.local_nls);
if (rc)
goto out;

/* success, put it on the list
* XXX: sharing ses between 2 tcp server is not possible, the
* way "internal" linked lists works in linux makes element
* only able to belong to one list
*
* the binding session is already established so the rest of
* the code should be able to look it up, no need to add the
* ses to the new server.
*/

ses->chan_count++;
atomic_set(&ses->chan_seq, 0);
out:
ses->binding = false;
mutex_unlock(&ses->session_mutex);

if (rc && chan->server)
cifs_put_tcp_session(chan->server, 0);
unload_nls(vol.local_nls);

return rc;
}

static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB)
{
Expand Down
Loading

0 comments on commit d70e9fa

Please sign in to comment.