|
31 | 31 | #include <linux/utsname.h>
|
32 | 32 | #include <linux/slab.h>
|
33 | 33 | #include "cifs_spnego.h"
|
| 34 | +#include "smb2proto.h" |
| 35 | + |
| 36 | +bool |
| 37 | +is_server_using_iface(struct TCP_Server_Info *server, |
| 38 | + struct cifs_server_iface *iface) |
| 39 | +{ |
| 40 | + struct sockaddr_in *i4 = (struct sockaddr_in *)&iface->sockaddr; |
| 41 | + struct sockaddr_in6 *i6 = (struct sockaddr_in6 *)&iface->sockaddr; |
| 42 | + struct sockaddr_in *s4 = (struct sockaddr_in *)&server->dstaddr; |
| 43 | + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)&server->dstaddr; |
| 44 | + |
| 45 | + if (server->dstaddr.ss_family != iface->sockaddr.ss_family) |
| 46 | + return false; |
| 47 | + if (server->dstaddr.ss_family == AF_INET) { |
| 48 | + if (s4->sin_addr.s_addr != i4->sin_addr.s_addr) |
| 49 | + return false; |
| 50 | + } else if (server->dstaddr.ss_family == AF_INET6) { |
| 51 | + if (memcmp(&s6->sin6_addr, &i6->sin6_addr, |
| 52 | + sizeof(i6->sin6_addr)) != 0) |
| 53 | + return false; |
| 54 | + } else { |
| 55 | + /* unknown family.. */ |
| 56 | + return false; |
| 57 | + } |
| 58 | + return true; |
| 59 | +} |
| 60 | + |
| 61 | +bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface) |
| 62 | +{ |
| 63 | + int i; |
| 64 | + |
| 65 | + for (i = 0; i < ses->chan_count; i++) { |
| 66 | + if (is_server_using_iface(ses->chans[i].server, iface)) |
| 67 | + return true; |
| 68 | + } |
| 69 | + return false; |
| 70 | +} |
| 71 | + |
| 72 | +/* returns number of channels added */ |
| 73 | +int cifs_try_adding_channels(struct cifs_ses *ses) |
| 74 | +{ |
| 75 | + int old_chan_count = ses->chan_count; |
| 76 | + int left = ses->chan_max - ses->chan_count; |
| 77 | + int i = 0; |
| 78 | + int rc = 0; |
| 79 | + |
| 80 | + if (left <= 0) { |
| 81 | + cifs_dbg(FYI, |
| 82 | + "ses already at max_channels (%zu), nothing to open\n", |
| 83 | + ses->chan_max); |
| 84 | + return 0; |
| 85 | + } |
| 86 | + |
| 87 | + if (ses->server->dialect < SMB30_PROT_ID) { |
| 88 | + cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n"); |
| 89 | + return 0; |
| 90 | + } |
| 91 | + |
| 92 | + /* ifaces are sorted by speed, try them in order */ |
| 93 | + for (i = 0; left > 0 && i < ses->iface_count; i++) { |
| 94 | + struct cifs_server_iface *iface; |
| 95 | + |
| 96 | + iface = &ses->iface_list[i]; |
| 97 | + if (is_ses_using_iface(ses, iface) && !iface->rss_capable) |
| 98 | + continue; |
| 99 | + |
| 100 | + rc = cifs_ses_add_channel(ses, iface); |
| 101 | + if (rc) { |
| 102 | + cifs_dbg(FYI, "failed to open extra channel\n"); |
| 103 | + continue; |
| 104 | + } |
| 105 | + |
| 106 | + cifs_dbg(FYI, "successfully opened new channel\n"); |
| 107 | + left--; |
| 108 | + } |
| 109 | + |
| 110 | + /* |
| 111 | + * TODO: if we still have channels left to open try to connect |
| 112 | + * to same RSS-capable iface multiple times |
| 113 | + */ |
| 114 | + |
| 115 | + return ses->chan_count - old_chan_count; |
| 116 | +} |
| 117 | + |
| 118 | +int |
| 119 | +cifs_ses_add_channel(struct cifs_ses *ses, struct cifs_server_iface *iface) |
| 120 | +{ |
| 121 | + struct cifs_chan *chan; |
| 122 | + struct smb_vol vol = {NULL}; |
| 123 | + static const char unc_fmt[] = "\\%s\\foo"; |
| 124 | + char unc[sizeof(unc_fmt)+SERVER_NAME_LEN_WITH_NULL] = {0}; |
| 125 | + struct sockaddr_in *ipv4 = (struct sockaddr_in *)&iface->sockaddr; |
| 126 | + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&iface->sockaddr; |
| 127 | + int rc; |
| 128 | + unsigned int xid = get_xid(); |
| 129 | + |
| 130 | + cifs_dbg(FYI, "adding channel to ses %p (speed:%zu bps rdma:%s ", |
| 131 | + ses, iface->speed, iface->rdma_capable ? "yes" : "no"); |
| 132 | + if (iface->sockaddr.ss_family == AF_INET) |
| 133 | + cifs_dbg(FYI, "ip:%pI4)\n", &ipv4->sin_addr); |
| 134 | + else |
| 135 | + cifs_dbg(FYI, "ip:%pI6)\n", &ipv6->sin6_addr); |
| 136 | + |
| 137 | + /* |
| 138 | + * Setup a smb_vol with mostly the same info as the existing |
| 139 | + * session and overwrite it with the requested iface data. |
| 140 | + * |
| 141 | + * We need to setup at least the fields used for negprot and |
| 142 | + * sesssetup. |
| 143 | + * |
| 144 | + * We only need the volume here, so we can reuse memory from |
| 145 | + * the session and server without caring about memory |
| 146 | + * management. |
| 147 | + */ |
| 148 | + |
| 149 | + /* Always make new connection for now (TODO?) */ |
| 150 | + vol.nosharesock = true; |
| 151 | + |
| 152 | + /* Auth */ |
| 153 | + vol.domainauto = ses->domainAuto; |
| 154 | + vol.domainname = ses->domainName; |
| 155 | + vol.username = ses->user_name; |
| 156 | + vol.password = ses->password; |
| 157 | + vol.sectype = ses->sectype; |
| 158 | + vol.sign = ses->sign; |
| 159 | + |
| 160 | + /* UNC and paths */ |
| 161 | + /* XXX: Use ses->server->hostname? */ |
| 162 | + sprintf(unc, unc_fmt, ses->serverName); |
| 163 | + vol.UNC = unc; |
| 164 | + vol.prepath = ""; |
| 165 | + |
| 166 | + /* Re-use same version as master connection */ |
| 167 | + vol.vals = ses->server->vals; |
| 168 | + vol.ops = ses->server->ops; |
| 169 | + |
| 170 | + vol.noblocksnd = ses->server->noblocksnd; |
| 171 | + vol.noautotune = ses->server->noautotune; |
| 172 | + vol.sockopt_tcp_nodelay = ses->server->tcp_nodelay; |
| 173 | + vol.echo_interval = ses->server->echo_interval / HZ; |
| 174 | + |
| 175 | + /* |
| 176 | + * This will be used for encoding/decoding user/domain/pw |
| 177 | + * during sess setup auth. |
| 178 | + * |
| 179 | + * XXX: We use the default for simplicity but the proper way |
| 180 | + * would be to use the one that ses used, which is not |
| 181 | + * stored. This might break when dealing with non-ascii |
| 182 | + * strings. |
| 183 | + */ |
| 184 | + vol.local_nls = load_nls_default(); |
| 185 | + |
| 186 | + /* Use RDMA if possible */ |
| 187 | + vol.rdma = iface->rdma_capable; |
| 188 | + memcpy(&vol.dstaddr, &iface->sockaddr, sizeof(struct sockaddr_storage)); |
| 189 | + |
| 190 | + /* reuse master con client guid */ |
| 191 | + memcpy(&vol.client_guid, ses->server->client_guid, |
| 192 | + SMB2_CLIENT_GUID_SIZE); |
| 193 | + vol.use_client_guid = true; |
| 194 | + |
| 195 | + mutex_lock(&ses->session_mutex); |
| 196 | + |
| 197 | + chan = &ses->chans[ses->chan_count]; |
| 198 | + chan->server = cifs_get_tcp_session(&vol); |
| 199 | + if (IS_ERR(chan->server)) { |
| 200 | + rc = PTR_ERR(chan->server); |
| 201 | + chan->server = NULL; |
| 202 | + goto out; |
| 203 | + } |
| 204 | + |
| 205 | + /* |
| 206 | + * We need to allocate the server crypto now as we will need |
| 207 | + * to sign packets before we generate the channel signing key |
| 208 | + * (we sign with the session key) |
| 209 | + */ |
| 210 | + rc = smb311_crypto_shash_allocate(chan->server); |
| 211 | + if (rc) { |
| 212 | + cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__); |
| 213 | + goto out; |
| 214 | + } |
| 215 | + |
| 216 | + ses->binding = true; |
| 217 | + rc = cifs_negotiate_protocol(xid, ses); |
| 218 | + if (rc) |
| 219 | + goto out; |
| 220 | + |
| 221 | + rc = cifs_setup_session(xid, ses, vol.local_nls); |
| 222 | + if (rc) |
| 223 | + goto out; |
| 224 | + |
| 225 | + /* success, put it on the list |
| 226 | + * XXX: sharing ses between 2 tcp server is not possible, the |
| 227 | + * way "internal" linked lists works in linux makes element |
| 228 | + * only able to belong to one list |
| 229 | + * |
| 230 | + * the binding session is already established so the rest of |
| 231 | + * the code should be able to look it up, no need to add the |
| 232 | + * ses to the new server. |
| 233 | + */ |
| 234 | + |
| 235 | + ses->chan_count++; |
| 236 | + atomic_set(&ses->chan_seq, 0); |
| 237 | +out: |
| 238 | + ses->binding = false; |
| 239 | + mutex_unlock(&ses->session_mutex); |
| 240 | + |
| 241 | + if (rc && chan->server) |
| 242 | + cifs_put_tcp_session(chan->server, 0); |
| 243 | + unload_nls(vol.local_nls); |
| 244 | + |
| 245 | + return rc; |
| 246 | +} |
34 | 247 |
|
35 | 248 | static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB)
|
36 | 249 | {
|
|
0 commit comments