diff options
| -rw-r--r-- | fs/cifs/smb2ops.c | 170 |
1 files changed, 155 insertions, 15 deletions
diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index c3648e9b5ec7..b2390e9a6843 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c | |||
| @@ -294,34 +294,176 @@ smb2_negotiate_rsize(struct cifs_tcon *tcon, struct smb_vol *volume_info) | |||
| 294 | return rsize; | 294 | return rsize; |
| 295 | } | 295 | } |
| 296 | 296 | ||
| 297 | #ifdef CONFIG_CIFS_STATS2 | 297 | |
| 298 | static int | ||
| 299 | parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, | ||
| 300 | size_t buf_len, | ||
| 301 | struct cifs_server_iface **iface_list, | ||
| 302 | size_t *iface_count) | ||
| 303 | { | ||
| 304 | struct network_interface_info_ioctl_rsp *p; | ||
| 305 | struct sockaddr_in *addr4; | ||
| 306 | struct sockaddr_in6 *addr6; | ||
| 307 | struct iface_info_ipv4 *p4; | ||
| 308 | struct iface_info_ipv6 *p6; | ||
| 309 | struct cifs_server_iface *info; | ||
| 310 | ssize_t bytes_left; | ||
| 311 | size_t next = 0; | ||
| 312 | int nb_iface = 0; | ||
| 313 | int rc = 0; | ||
| 314 | |||
| 315 | *iface_list = NULL; | ||
| 316 | *iface_count = 0; | ||
| 317 | |||
| 318 | /* | ||
| 319 | * Fist pass: count and sanity check | ||
| 320 | */ | ||
| 321 | |||
| 322 | bytes_left = buf_len; | ||
| 323 | p = buf; | ||
| 324 | while (bytes_left >= sizeof(*p)) { | ||
| 325 | nb_iface++; | ||
| 326 | next = le32_to_cpu(p->Next); | ||
| 327 | if (!next) { | ||
| 328 | bytes_left -= sizeof(*p); | ||
| 329 | break; | ||
| 330 | } | ||
| 331 | p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); | ||
| 332 | bytes_left -= next; | ||
| 333 | } | ||
| 334 | |||
| 335 | if (!nb_iface) { | ||
| 336 | cifs_dbg(VFS, "%s: malformed interface info\n", __func__); | ||
| 337 | rc = -EINVAL; | ||
| 338 | goto out; | ||
| 339 | } | ||
| 340 | |||
| 341 | if (bytes_left || p->Next) | ||
| 342 | cifs_dbg(VFS, "%s: incomplete interface info\n", __func__); | ||
| 343 | |||
| 344 | |||
| 345 | /* | ||
| 346 | * Second pass: extract info to internal structure | ||
| 347 | */ | ||
| 348 | |||
| 349 | *iface_list = kcalloc(nb_iface, sizeof(**iface_list), GFP_KERNEL); | ||
| 350 | if (!*iface_list) { | ||
| 351 | rc = -ENOMEM; | ||
| 352 | goto out; | ||
| 353 | } | ||
| 354 | |||
| 355 | info = *iface_list; | ||
| 356 | bytes_left = buf_len; | ||
| 357 | p = buf; | ||
| 358 | while (bytes_left >= sizeof(*p)) { | ||
| 359 | info->speed = le64_to_cpu(p->LinkSpeed); | ||
| 360 | info->rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE); | ||
| 361 | info->rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE); | ||
| 362 | |||
| 363 | cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, *iface_count); | ||
| 364 | cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed); | ||
| 365 | cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__, | ||
| 366 | le32_to_cpu(p->Capability)); | ||
| 367 | |||
| 368 | switch (p->Family) { | ||
| 369 | /* | ||
| 370 | * The kernel and wire socket structures have the same | ||
| 371 | * layout and use network byte order but make the | ||
| 372 | * conversion explicit in case either one changes. | ||
| 373 | */ | ||
| 374 | case INTERNETWORK: | ||
| 375 | addr4 = (struct sockaddr_in *)&info->sockaddr; | ||
| 376 | p4 = (struct iface_info_ipv4 *)p->Buffer; | ||
| 377 | addr4->sin_family = AF_INET; | ||
| 378 | memcpy(&addr4->sin_addr, &p4->IPv4Address, 4); | ||
| 379 | |||
| 380 | /* [MS-SMB2] 2.2.32.5.1.1 Clients MUST ignore these */ | ||
| 381 | addr4->sin_port = cpu_to_be16(CIFS_PORT); | ||
| 382 | |||
| 383 | cifs_dbg(FYI, "%s: ipv4 %pI4\n", __func__, | ||
| 384 | &addr4->sin_addr); | ||
| 385 | break; | ||
| 386 | case INTERNETWORKV6: | ||
| 387 | addr6 = (struct sockaddr_in6 *)&info->sockaddr; | ||
| 388 | p6 = (struct iface_info_ipv6 *)p->Buffer; | ||
| 389 | addr6->sin6_family = AF_INET6; | ||
| 390 | memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16); | ||
| 391 | |||
| 392 | /* [MS-SMB2] 2.2.32.5.1.2 Clients MUST ignore these */ | ||
| 393 | addr6->sin6_flowinfo = 0; | ||
| 394 | addr6->sin6_scope_id = 0; | ||
| 395 | addr6->sin6_port = cpu_to_be16(CIFS_PORT); | ||
| 396 | |||
| 397 | cifs_dbg(FYI, "%s: ipv6 %pI6\n", __func__, | ||
| 398 | &addr6->sin6_addr); | ||
| 399 | break; | ||
| 400 | default: | ||
| 401 | cifs_dbg(VFS, | ||
| 402 | "%s: skipping unsupported socket family\n", | ||
| 403 | __func__); | ||
| 404 | goto next_iface; | ||
| 405 | } | ||
| 406 | |||
| 407 | (*iface_count)++; | ||
| 408 | info++; | ||
| 409 | next_iface: | ||
| 410 | next = le32_to_cpu(p->Next); | ||
| 411 | if (!next) | ||
| 412 | break; | ||
| 413 | p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); | ||
| 414 | bytes_left -= next; | ||
| 415 | } | ||
| 416 | |||
| 417 | if (!*iface_count) { | ||
| 418 | rc = -EINVAL; | ||
| 419 | goto out; | ||
| 420 | } | ||
| 421 | |||
| 422 | out: | ||
| 423 | if (rc) { | ||
| 424 | kfree(*iface_list); | ||
| 425 | *iface_count = 0; | ||
| 426 | *iface_list = NULL; | ||
| 427 | } | ||
| 428 | return rc; | ||
| 429 | } | ||
| 430 | |||
| 431 | |||
| 298 | static int | 432 | static int |
| 299 | SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) | 433 | SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) |
| 300 | { | 434 | { |
| 301 | int rc; | 435 | int rc; |
| 302 | unsigned int ret_data_len = 0; | 436 | unsigned int ret_data_len = 0; |
| 303 | struct network_interface_info_ioctl_rsp *out_buf; | 437 | struct network_interface_info_ioctl_rsp *out_buf = NULL; |
| 438 | struct cifs_server_iface *iface_list; | ||
| 439 | size_t iface_count; | ||
| 440 | struct cifs_ses *ses = tcon->ses; | ||
| 304 | 441 | ||
| 305 | rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, | 442 | rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, |
| 306 | FSCTL_QUERY_NETWORK_INTERFACE_INFO, true /* is_fsctl */, | 443 | FSCTL_QUERY_NETWORK_INTERFACE_INFO, true /* is_fsctl */, |
| 307 | NULL /* no data input */, 0 /* no data input */, | 444 | NULL /* no data input */, 0 /* no data input */, |
| 308 | (char **)&out_buf, &ret_data_len); | 445 | (char **)&out_buf, &ret_data_len); |
| 309 | if (rc != 0) | 446 | if (rc != 0) { |
| 310 | cifs_dbg(VFS, "error %d on ioctl to get interface list\n", rc); | 447 | cifs_dbg(VFS, "error %d on ioctl to get interface list\n", rc); |
| 311 | else if (ret_data_len < sizeof(struct network_interface_info_ioctl_rsp)) { | 448 | goto out; |
| 312 | cifs_dbg(VFS, "server returned bad net interface info buf\n"); | ||
| 313 | rc = -EINVAL; | ||
| 314 | } else { | ||
| 315 | /* Dump info on first interface */ | ||
| 316 | cifs_dbg(FYI, "Adapter Capability 0x%x\t", | ||
| 317 | le32_to_cpu(out_buf->Capability)); | ||
| 318 | cifs_dbg(FYI, "Link Speed %lld\n", | ||
| 319 | le64_to_cpu(out_buf->LinkSpeed)); | ||
| 320 | } | 449 | } |
| 450 | |||
| 451 | rc = parse_server_interfaces(out_buf, ret_data_len, | ||
| 452 | &iface_list, &iface_count); | ||
| 453 | if (rc) | ||
| 454 | goto out; | ||
| 455 | |||
| 456 | spin_lock(&ses->iface_lock); | ||
| 457 | kfree(ses->iface_list); | ||
| 458 | ses->iface_list = iface_list; | ||
| 459 | ses->iface_count = iface_count; | ||
| 460 | ses->iface_last_update = jiffies; | ||
| 461 | spin_unlock(&ses->iface_lock); | ||
| 462 | |||
| 463 | out: | ||
| 321 | kfree(out_buf); | 464 | kfree(out_buf); |
| 322 | return rc; | 465 | return rc; |
| 323 | } | 466 | } |
| 324 | #endif /* STATS2 */ | ||
| 325 | 467 | ||
| 326 | void | 468 | void |
| 327 | smb2_cached_lease_break(struct work_struct *work) | 469 | smb2_cached_lease_break(struct work_struct *work) |
| @@ -399,9 +541,7 @@ smb3_qfs_tcon(const unsigned int xid, struct cifs_tcon *tcon) | |||
| 399 | if (rc) | 541 | if (rc) |
| 400 | return; | 542 | return; |
| 401 | 543 | ||
| 402 | #ifdef CONFIG_CIFS_STATS2 | ||
| 403 | SMB3_request_interfaces(xid, tcon); | 544 | SMB3_request_interfaces(xid, tcon); |
| 404 | #endif /* STATS2 */ | ||
| 405 | 545 | ||
| 406 | SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid, | 546 | SMB2_QFS_attr(xid, tcon, fid.persistent_fid, fid.volatile_fid, |
| 407 | FS_ATTRIBUTE_INFORMATION); | 547 | FS_ATTRIBUTE_INFORMATION); |
