A server's published interface list can change over time, and needs to be updated. We've storing iface_list as a simple array, which makes it difficult to manipulate an existing list. With this change, iface_list is modified into a linked list of interfaces, which is kept sorted by speed. Also added a reference counter for an iface entry, so that each channel can maintain a backpointer to the iface and drop it easily when needed.
Unified · Split
| @@ -162,6 +162,8 @@ cifs_dump_iface(struct seq_file *m, struct cifs_server_iface *iface) · 161 unmodified lines | |||
| 162 | seq_printf(m, "\t\tIPv4: %pI4\n", &ipv4->sin_addr); | 162 | seq_printf(m, "\t\tIPv4: %pI4\n", &ipv4->sin_addr); |
| 163 | else if (iface->sockaddr.ss_family == AF_INET6) | 163 | else if (iface->sockaddr.ss_family == AF_INET6) |
| 164 | seq_printf(m, "\t\tIPv6: %pI6\n", &ipv6->sin6_addr); | 164 | seq_printf(m, "\t\tIPv6: %pI6\n", &ipv6->sin6_addr); |
| 165 | if (!iface->is_active) | ||
| 166 | seq_puts(m, "\t\t[for-cleanup]\n"); | ||
| 165 | } | 167 | } |
| 166 | 168 | ||
| 167 | static int cifs_debug_files_proc_show(struct seq_file *m, void *v) | 169 | static int cifs_debug_files_proc_show(struct seq_file *m, void *v) |
| @@ -221,6 +223,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) · 53 unmodified lines | |||
| 221 | struct TCP_Server_Info *server; | 223 | struct TCP_Server_Info *server; |
| 222 | struct cifs_ses *ses; | 224 | struct cifs_ses *ses; |
| 223 | struct cifs_tcon *tcon; | 225 | struct cifs_tcon *tcon; |
| 226 | struct cifs_server_iface *iface; | ||
| 224 | int c, i, j; | 227 | int c, i, j; |
| 225 | 228 | ||
| 226 | seq_puts(m, | 229 | seq_puts(m, |
| @@ -456,11 +459,10 @@ skip_rdma: · 229 unmodified lines | |||
| 456 | if (ses->iface_count) | 459 | if (ses->iface_count) |
| 457 | seq_printf(m, "\n\n\tServer interfaces: %zu", | 460 | seq_printf(m, "\n\n\tServer interfaces: %zu", |
| 458 | ses->iface_count); | 461 | ses->iface_count); |
| 459 | for (j = 0; j < ses->iface_count; j++) { | 462 | j = 0; |
| 460 | struct cifs_server_iface *iface; | 463 | list_for_each_entry(iface, &ses->iface_list, |
| 461 | 464 | iface_head) { | |
| 462 | iface = &ses->iface_list[j]; | 465 | seq_printf(m, "\n\t%d)", ++j); |
| 463 | seq_printf(m, "\n\t%d)", j+1); | ||
| 464 | cifs_dump_iface(m, iface); | 466 | cifs_dump_iface(m, iface); |
| 465 | if (is_ses_using_iface(ses, iface)) | 467 | if (is_ses_using_iface(ses, iface)) |
| 466 | seq_puts(m, "\t\t[CONNECTED]\n"); | 468 | seq_puts(m, "\t\t[CONNECTED]\n"); |
| @@ -933,15 +933,67 @@ static inline void cifs_set_net_ns(struct TCP_Server_Info *srv, struct net *net) · 932 unmodified lines | |||
| 933 | #endif | 933 | #endif |
| 934 | 934 | ||
| 935 | struct cifs_server_iface { | 935 | struct cifs_server_iface { |
| 936 | struct list_head iface_head; | ||
| 937 | struct kref refcount; | ||
| 936 | size_t speed; | 938 | size_t speed; |
| 937 | unsigned int rdma_capable : 1; | 939 | unsigned int rdma_capable : 1; |
| 938 | unsigned int rss_capable : 1; | 940 | unsigned int rss_capable : 1; |
| 941 | unsigned int is_active : 1; /* unset if non existent */ | ||
| 939 | struct sockaddr_storage sockaddr; | 942 | struct sockaddr_storage sockaddr; |
| 940 | }; | 943 | }; |
| 941 | 944 | ||
| 945 | /* release iface when last ref is dropped */ | ||
| 946 | static inline void | ||
| 947 | release_iface(struct kref *ref) | ||
| 948 | { | ||
| 949 | struct cifs_server_iface *iface = container_of(ref, | ||
| 950 | struct cifs_server_iface, | ||
| 951 | refcount); | ||
| 952 | list_del_init(&iface->iface_head); | ||
| 953 | kfree(iface); | ||
| 954 | } | ||
| 955 | |||
| 956 | /* | ||
| 957 | * compare two interfaces a and b | ||
| 958 | * return 0 if everything matches. | ||
| 959 | * return 1 if a has higher link speed, or rdma capable, or rss capable | ||
| 960 | * return -1 otherwise. | ||
| 961 | */ | ||
| 962 | static inline int | ||
| 963 | iface_cmp(struct cifs_server_iface *a, struct cifs_server_iface *b) | ||
| 964 | { | ||
| 965 | int cmp_ret = 0; | ||
| 966 | |||
| 967 | WARN_ON(!a || !b); | ||
| 968 | if (a->speed == b->speed) { | ||
| 969 | if (a->rdma_capable == b->rdma_capable) { | ||
| 970 | if (a->rss_capable == b->rss_capable) { | ||
| 971 | cmp_ret = memcmp(&a->sockaddr, &b->sockaddr, | ||
| 972 | sizeof(a->sockaddr)); | ||
| 973 | if (!cmp_ret) | ||
| 974 | return 0; | ||
| 975 | else if (cmp_ret > 0) | ||
| 976 | return 1; | ||
| 977 | else | ||
| 978 | return -1; | ||
| 979 | } else if (a->rss_capable > b->rss_capable) | ||
| 980 | return 1; | ||
| 981 | else | ||
| 982 | return -1; | ||
| 983 | } else if (a->rdma_capable > b->rdma_capable) | ||
| 984 | return 1; | ||
| 985 | else | ||
| 986 | return -1; | ||
| 987 | } else if (a->speed > b->speed) | ||
| 988 | return 1; | ||
| 989 | else | ||
| 990 | return -1; | ||
| 991 | } | ||
| 992 | |||
| 942 | struct cifs_chan { | 993 | struct cifs_chan { |
| 943 | unsigned int in_reconnect : 1; /* if session setup in progress for this channel */ | 994 | unsigned int in_reconnect : 1; /* if session setup in progress for this channel */ |
| 944 | struct TCP_Server_Info *server; | 995 | struct TCP_Server_Info *server; |
| 996 | struct cifs_server_iface *iface; /* interface in use */ | ||
| 945 | __u8 signkey[SMB3_SIGN_KEY_SIZE]; | 997 | __u8 signkey[SMB3_SIGN_KEY_SIZE]; |
| 946 | }; | 998 | }; |
| 947 | 999 | ||
| @@ -993,7 +1045,7 @@ struct cifs_ses { · 45 unmodified lines | |||
| 993 | */ | 1045 | */ |
| 994 | spinlock_t iface_lock; | 1046 | spinlock_t iface_lock; |
| 995 | /* ========= begin: protected by iface_lock ======== */ | 1047 | /* ========= begin: protected by iface_lock ======== */ |
| 996 | struct cifs_server_iface *iface_list; | 1048 | struct list_head iface_list; |
| 997 | size_t iface_count; | 1049 | size_t iface_count; |
| 998 | unsigned long iface_last_update; /* jiffies */ | 1050 | unsigned long iface_last_update; /* jiffies */ |
| 999 | /* ========= end: protected by iface_lock ======== */ | 1051 | /* ========= end: protected by iface_lock ======== */ |
| @@ -1894,9 +1894,11 @@ void cifs_put_smb_ses(struct cifs_ses *ses) · 1893 unmodified lines | |||
| 1894 | int i; | 1894 | int i; |
| 1895 | 1895 | ||
| 1896 | for (i = 1; i < chan_count; i++) { | 1896 | for (i = 1; i < chan_count; i++) { |
| 1897 | spin_unlock(&ses->chan_lock); | 1897 | if (ses->chans[i].iface) { |
| 1898 | kref_put(&ses->chans[i].iface->refcount, release_iface); | ||
| 1899 | ses->chans[i].iface = NULL; | ||
| 1900 | } | ||
| 1898 | cifs_put_tcp_session(ses->chans[i].server, 0); | 1901 | cifs_put_tcp_session(ses->chans[i].server, 0); |
| 1899 | spin_lock(&ses->chan_lock); | ||
| 1900 | ses->chans[i].server = NULL; | 1902 | ses->chans[i].server = NULL; |
| 1901 | } | 1903 | } |
| 1902 | } | 1904 | } |
| @@ -75,6 +75,7 @@ sesInfoAlloc(void) · 74 unmodified lines | |||
| 75 | INIT_LIST_HEAD(&ret_buf->tcon_list); | 75 | INIT_LIST_HEAD(&ret_buf->tcon_list); |
| 76 | mutex_init(&ret_buf->session_mutex); | 76 | mutex_init(&ret_buf->session_mutex); |
| 77 | spin_lock_init(&ret_buf->iface_lock); | 77 | spin_lock_init(&ret_buf->iface_lock); |
| 78 | INIT_LIST_HEAD(&ret_buf->iface_list); | ||
| 78 | spin_lock_init(&ret_buf->chan_lock); | 79 | spin_lock_init(&ret_buf->chan_lock); |
| 79 | } | 80 | } |
| 80 | return ret_buf; | 81 | return ret_buf; |
| @@ -83,6 +84,8 @@ sesInfoAlloc(void) · 2 unmodified lines | |||
| 83 | void | 84 | void |
| 84 | sesInfoFree(struct cifs_ses *buf_to_free) | 85 | sesInfoFree(struct cifs_ses *buf_to_free) |
| 85 | { | 86 | { |
| 87 | struct cifs_server_iface *iface = NULL, *niface = NULL; | ||
| 88 | |||
| 86 | if (buf_to_free == NULL) { | 89 | if (buf_to_free == NULL) { |
| 87 | cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n"); | 90 | cifs_dbg(FYI, "Null buffer passed to sesInfoFree\n"); |
| 88 | return; | 91 | return; |
| @@ -96,7 +99,11 @@ sesInfoFree(struct cifs_ses *buf_to_free) · 7 unmodified lines | |||
| 96 | kfree(buf_to_free->user_name); | 99 | kfree(buf_to_free->user_name); |
| 97 | kfree(buf_to_free->domainName); | 100 | kfree(buf_to_free->domainName); |
| 98 | kfree_sensitive(buf_to_free->auth_key.response); | 101 | kfree_sensitive(buf_to_free->auth_key.response); |
| 99 | kfree(buf_to_free->iface_list); | 102 | spin_lock(&buf_to_free->iface_lock); |
| 103 | list_for_each_entry_safe(iface, niface, &buf_to_free->iface_list, | ||
| 104 | iface_head) | ||
| 105 | kref_put(&iface->refcount, release_iface); | ||
| 106 | spin_unlock(&buf_to_free->iface_lock); | ||
| 100 | kfree_sensitive(buf_to_free); | 107 | kfree_sensitive(buf_to_free); |
| 101 | } | 108 | } |
| 102 | 109 | ||
| @@ -58,7 +58,7 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface) · 57 unmodified lines | |||
| 58 | 58 | ||
| 59 | spin_lock(&ses->chan_lock); | 59 | spin_lock(&ses->chan_lock); |
| 60 | for (i = 0; i < ses->chan_count; i++) { | 60 | for (i = 0; i < ses->chan_count; i++) { |
| 61 | if (is_server_using_iface(ses->chans[i].server, iface)) { | 61 | if (ses->chans[i].iface == iface) { |
| 62 | spin_unlock(&ses->chan_lock); | 62 | spin_unlock(&ses->chan_lock); |
| 63 | return true; | 63 | return true; |
| 64 | } | 64 | } |
| @@ -151,11 +151,9 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) · 86 unmodified lines | |||
| 151 | { | 151 | { |
| 152 | int old_chan_count, new_chan_count; | 152 | int old_chan_count, new_chan_count; |
| 153 | int left; | 153 | int left; |
| 154 | int i = 0; | ||
| 155 | int rc = 0; | 154 | int rc = 0; |
| 156 | int tries = 0; | 155 | int tries = 0; |
| 157 | struct cifs_server_iface *ifaces = NULL; | 156 | struct cifs_server_iface *iface = NULL, *niface = NULL; |
| 158 | size_t iface_count; | ||
| 159 | 157 | ||
| 160 | spin_lock(&ses->chan_lock); | 158 | spin_lock(&ses->chan_lock); |
| 161 | 159 | ||
| @@ -184,33 +182,17 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) · 22 unmodified lines | |||
| 184 | } | 182 | } |
| 185 | spin_unlock(&ses->chan_lock); | 183 | spin_unlock(&ses->chan_lock); |
| 186 | 184 | ||
| 187 | /* | ||
| 188 | * Make a copy of the iface list at the time and use that | ||
| 189 | * instead so as to not hold the iface spinlock for opening | ||
| 190 | * channels | ||
| 191 | */ | ||
| 192 | spin_lock(&ses->iface_lock); | ||
| 193 | iface_count = ses->iface_count; | ||
| 194 | if (iface_count <= 0) { | ||
| 195 | spin_unlock(&ses->iface_lock); | ||
| 196 | cifs_dbg(VFS, "no iface list available to open channels\n"); | ||
| 197 | return 0; | ||
| 198 | } | ||
| 199 | ifaces = kmemdup(ses->iface_list, iface_count*sizeof(*ifaces), | ||
| 200 | GFP_ATOMIC); | ||
| 201 | if (!ifaces) { | ||
| 202 | spin_unlock(&ses->iface_lock); | ||
| 203 | return 0; | ||
| 204 | } | ||
| 205 | spin_unlock(&ses->iface_lock); | ||
| 206 | |||
| 207 | /* | 185 | /* |
| 208 | * Keep connecting to same, fastest, iface for all channels as | 186 | * Keep connecting to same, fastest, iface for all channels as |
| 209 | * long as its RSS. Try next fastest one if not RSS or channel | 187 | * long as its RSS. Try next fastest one if not RSS or channel |
| 210 | * creation fails. | 188 | * creation fails. |
| 211 | */ | 189 | */ |
| 190 | spin_lock(&ses->iface_lock); | ||
| 191 | iface = list_first_entry(&ses->iface_list, struct cifs_server_iface, | ||
| 192 | iface_head); | ||
| 193 | spin_unlock(&ses->iface_lock); | ||
| 194 | |||
| 212 | while (left > 0) { | 195 | while (left > 0) { |
| 213 | struct cifs_server_iface *iface; | ||
| 214 | 196 | ||
| 215 | tries++; | 197 | tries++; |
| 216 | if (tries > 3*ses->chan_max) { | 198 | if (tries > 3*ses->chan_max) { |
| @@ -219,27 +201,46 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) · 2 unmodified lines | |||
| 219 | break; | 201 | break; |
| 220 | } | 202 | } |
| 221 | 203 | ||
| 222 | iface = &ifaces[i]; | 204 | spin_lock(&ses->iface_lock); |
| 223 | if (is_ses_using_iface(ses, iface) && !iface->rss_capable) { | 205 | if (!ses->iface_count) { |
| 224 | i = (i+1) % iface_count; | 206 | spin_unlock(&ses->iface_lock); |
| 225 | continue; | 207 | break; |
| 226 | } | 208 | } |
| 227 | 209 | ||
| 228 | rc = cifs_ses_add_channel(cifs_sb, ses, iface); | 210 | list_for_each_entry_safe_from(iface, niface, &ses->iface_list, |
| 229 | if (rc) { | 211 | iface_head) { |
| 230 | cifs_dbg(FYI, "failed to open extra channel on iface#%d rc=%d\n", | 212 | /* skip ifaces that are unusable */ |
| 231 | i, rc); | 213 | if (!iface->is_active || |
| 232 | i = (i+1) % iface_count; | 214 | (is_ses_using_iface(ses, iface) && |
| 233 | continue; | 215 | !iface->rss_capable)) { |
| 216 | continue; | ||
| 217 | } | ||
| 218 | |||
| 219 | /* take ref before unlock */ | ||
| 220 | kref_get(&iface->refcount); | ||
| 221 | |||
| 222 | spin_unlock(&ses->iface_lock); | ||
| 223 | rc = cifs_ses_add_channel(cifs_sb, ses, iface); | ||
| 224 | spin_lock(&ses->iface_lock); | ||
| 225 | |||
| 226 | if (rc) { | ||
| 227 | cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n", | ||
| 228 | &iface->sockaddr, | ||
| 229 | rc); | ||
| 230 | kref_put(&iface->refcount, release_iface); | ||
| 231 | continue; | ||
| 232 | } | ||
| 233 | |||
| 234 | cifs_dbg(FYI, "successfully opened new channel on iface:%pIS\n", | ||
| 235 | &iface->sockaddr); | ||
| 236 | break; | ||
| 234 | } | 237 | } |
| 238 | spin_unlock(&ses->iface_lock); | ||
| 235 | 239 | ||
| 236 | cifs_dbg(FYI, "successfully opened new channel on iface#%d\n", | ||
| 237 | i); | ||
| 238 | left--; | 240 | left--; |
| 239 | new_chan_count++; | 241 | new_chan_count++; |
| 240 | } | 242 | } |
| 241 | 243 | ||
| 242 | kfree(ifaces); | ||
| 243 | return new_chan_count - old_chan_count; | 244 | return new_chan_count - old_chan_count; |
| 244 | } | 245 | } |
| 245 | 246 | ||
| @@ -355,6 +356,7 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, · 109 unmodified lines | |||
| 355 | spin_unlock(&ses->chan_lock); | 356 | spin_unlock(&ses->chan_lock); |
| 356 | goto out; | 357 | goto out; |
| 357 | } | 358 | } |
| 359 | chan->iface = iface; | ||
| 358 | ses->chan_count++; | 360 | ses->chan_count++; |
| 359 | atomic_set(&ses->chan_seq, 0); | 361 | atomic_set(&ses->chan_seq, 0); |
| 360 | 362 | ||
| @@ -512,73 +512,41 @@ smb3_negotiate_rsize(struct cifs_tcon *tcon, struct smb3_fs_context *ctx) · 511 unmodified lines | |||
| 512 | static int | 512 | static int |
| 513 | parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, | 513 | parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, |
| 514 | size_t buf_len, | 514 | size_t buf_len, |
| 515 | struct cifs_server_iface **iface_list, | 515 | struct cifs_ses *ses) |
| 516 | size_t *iface_count) | ||
| 517 | { | 516 | { |
| 518 | struct network_interface_info_ioctl_rsp *p; | 517 | struct network_interface_info_ioctl_rsp *p; |
| 519 | struct sockaddr_in *addr4; | 518 | struct sockaddr_in *addr4; |
| 520 | struct sockaddr_in6 *addr6; | 519 | struct sockaddr_in6 *addr6; |
| 521 | struct iface_info_ipv4 *p4; | 520 | struct iface_info_ipv4 *p4; |
| 522 | struct iface_info_ipv6 *p6; | 521 | struct iface_info_ipv6 *p6; |
| 523 | struct cifs_server_iface *info; | 522 | struct cifs_server_iface *info = NULL, *iface = NULL, *niface = NULL; |
| 523 | struct cifs_server_iface tmp_iface; | ||
| 524 | ssize_t bytes_left; | 524 | ssize_t bytes_left; |
| 525 | size_t next = 0; | 525 | size_t next = 0; |
| 526 | int nb_iface = 0; | 526 | int nb_iface = 0; |
| 527 | int rc = 0; | 527 | int rc = 0, ret = 0; |
| 528 | |||
| 529 | *iface_list = NULL; | ||
| 530 | *iface_count = 0; | ||
| 531 | |||
| 532 | /* | ||
| 533 | * Fist pass: count and sanity check | ||
| 534 | */ | ||
| 535 | 528 | ||
| 536 | bytes_left = buf_len; | 529 | bytes_left = buf_len; |
| 537 | p = buf; | 530 | p = buf; |
| 538 | while (bytes_left >= sizeof(*p)) { | ||
| 539 | nb_iface++; | ||
| 540 | next = le32_to_cpu(p->Next); | ||
| 541 | if (!next) { | ||
| 542 | bytes_left -= sizeof(*p); | ||
| 543 | break; | ||
| 544 | } | ||
| 545 | p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); | ||
| 546 | bytes_left -= next; | ||
| 547 | } | ||
| 548 | |||
| 549 | if (!nb_iface) { | ||
| 550 | cifs_dbg(VFS, "%s: malformed interface info\n", __func__); | ||
| 551 | rc = -EINVAL; | ||
| 552 | goto out; | ||
| 553 | } | ||
| 554 | |||
| 555 | /* Azure rounds the buffer size up 8, to a 16 byte boundary */ | ||
| 556 | if ((bytes_left > 8) || p->Next) | ||
| 557 | cifs_dbg(VFS, "%s: incomplete interface info\n", __func__); | ||
| 558 | |||
| 559 | 531 | ||
| 532 | spin_lock(&ses->iface_lock); | ||
| 560 | /* | 533 | /* |
| 561 | * Second pass: extract info to internal structure | 534 | * Go through iface_list and do kref_put to remove |
| 535 | * any unused ifaces. ifaces in use will be removed | ||
| 536 | * when the last user calls a kref_put on it | ||
| 562 | */ | 537 | */ |
| 563 | 538 | list_for_each_entry_safe(iface, niface, &ses->iface_list, | |
| 564 | *iface_list = kcalloc(nb_iface, sizeof(**iface_list), GFP_KERNEL); | 539 | iface_head) { |
| 565 | if (!*iface_list) { | 540 | iface->is_active = 0; |
| 566 | rc = -ENOMEM; | 541 | kref_put(&iface->refcount, release_iface); |
| 567 | goto out; | ||
| 568 | } | 542 | } |
| 543 | spin_unlock(&ses->iface_lock); | ||
| 569 | 544 | ||
| 570 | info = *iface_list; | ||
| 571 | bytes_left = buf_len; | ||
| 572 | p = buf; | ||
| 573 | while (bytes_left >= sizeof(*p)) { | 545 | while (bytes_left >= sizeof(*p)) { |
| 574 | info->speed = le64_to_cpu(p->LinkSpeed); | 546 | memset(&tmp_iface, 0, sizeof(tmp_iface)); |
| 575 | info->rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE) ? 1 : 0; | 547 | tmp_iface.speed = le64_to_cpu(p->LinkSpeed); |
| 576 | info->rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE) ? 1 : 0; | 548 | tmp_iface.rdma_capable = le32_to_cpu(p->Capability & RDMA_CAPABLE) ? 1 : 0; |
| 577 | 549 | tmp_iface.rss_capable = le32_to_cpu(p->Capability & RSS_CAPABLE) ? 1 : 0; | |
| 578 | cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, *iface_count); | ||
| 579 | cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed); | ||
| 580 | cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__, | ||
| 581 | le32_to_cpu(p->Capability)); | ||
| 582 | 550 | ||
| 583 | switch (p->Family) { | 551 | switch (p->Family) { |
| 584 | /* | 552 | /* |
| @@ -587,7 +555,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, · 2 unmodified lines | |||
| 587 | * conversion explicit in case either one changes. | 555 | * conversion explicit in case either one changes. |
| 588 | */ | 556 | */ |
| 589 | case INTERNETWORK: | 557 | case INTERNETWORK: |
| 590 | addr4 = (struct sockaddr_in *)&info->sockaddr; | 558 | addr4 = (struct sockaddr_in *)&tmp_iface.sockaddr; |
| 591 | p4 = (struct iface_info_ipv4 *)p->Buffer; | 559 | p4 = (struct iface_info_ipv4 *)p->Buffer; |
| 592 | addr4->sin_family = AF_INET; | 560 | addr4->sin_family = AF_INET; |
| 593 | memcpy(&addr4->sin_addr, &p4->IPv4Address, 4); | 561 | memcpy(&addr4->sin_addr, &p4->IPv4Address, 4); |
| @@ -599,7 +567,7 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, · 5 unmodified lines | |||
| 599 | &addr4->sin_addr); | 567 | &addr4->sin_addr); |
| 600 | break; | 568 | break; |
| 601 | case INTERNETWORKV6: | 569 | case INTERNETWORKV6: |
| 602 | addr6 = (struct sockaddr_in6 *)&info->sockaddr; | 570 | addr6 = (struct sockaddr_in6 *)&tmp_iface.sockaddr; |
| 603 | p6 = (struct iface_info_ipv6 *)p->Buffer; | 571 | p6 = (struct iface_info_ipv6 *)p->Buffer; |
| 604 | addr6->sin6_family = AF_INET6; | 572 | addr6->sin6_family = AF_INET6; |
| 605 | memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16); | 573 | memcpy(&addr6->sin6_addr, &p6->IPv6Address, 16); |
| @@ -619,36 +587,88 @@ parse_server_interfaces(struct network_interface_info_ioctl_rsp *buf, · 13 unmodified lines | |||
| 619 | goto next_iface; | 587 | goto next_iface; |
| 620 | } | 588 | } |
| 621 | 589 | ||
| 622 | (*iface_count)++; | 590 | /* |
| 623 | info++; | 591 | * The iface_list is assumed to be sorted by speed. |
| 592 | * Check if the new interface exists in that list. | ||
| 593 | * NEVER change iface. it could be in use. | ||
| 594 | * Add a new one instead | ||
| 595 | */ | ||
| 596 | spin_lock(&ses->iface_lock); | ||
| 597 | iface = niface = NULL; | ||
| 598 | list_for_each_entry_safe(iface, niface, &ses->iface_list, | ||
| 599 | iface_head) { | ||
| 600 | ret = iface_cmp(iface, &tmp_iface); | ||
| 601 | if (!ret) { | ||
| 602 | /* just get a ref so that it doesn't get picked/freed */ | ||
| 603 | iface->is_active = 1; | ||
| 604 | kref_get(&iface->refcount); | ||
| 605 | spin_unlock(&ses->iface_lock); | ||
| 606 | goto next_iface; | ||
| 607 | } else if (ret < 0) { | ||
| 608 | /* all remaining ifaces are slower */ | ||
| 609 | kref_get(&iface->refcount); | ||
| 610 | break; | ||
| 611 | } | ||
| 612 | } | ||
| 613 | spin_unlock(&ses->iface_lock); | ||
| 614 | |||
| 615 | /* no match. insert the entry in the list */ | ||
| 616 | info = kmalloc(sizeof(struct cifs_server_iface), | ||
| 617 | GFP_KERNEL); | ||
| 618 | if (!info) { | ||
| 619 | rc = -ENOMEM; | ||
| 620 | goto out; | ||
| 621 | } | ||
| 622 | memcpy(info, &tmp_iface, sizeof(tmp_iface)); | ||
| 623 | |||
| 624 | /* add this new entry to the list */ | ||
| 625 | kref_init(&info->refcount); | ||
| 626 | info->is_active = 1; | ||
| 627 | |||
| 628 | cifs_dbg(FYI, "%s: adding iface %zu\n", __func__, ses->iface_count); | ||
| 629 | cifs_dbg(FYI, "%s: speed %zu bps\n", __func__, info->speed); | ||
| 630 | cifs_dbg(FYI, "%s: capabilities 0x%08x\n", __func__, | ||
| 631 | le32_to_cpu(p->Capability)); | ||
| 632 | |||
| 633 | spin_lock(&ses->iface_lock); | ||
| 634 | if (!list_entry_is_head(iface, &ses->iface_list, iface_head)) { | ||
| 635 | list_add_tail(&info->iface_head, &iface->iface_head); | ||
| 636 | kref_put(&iface->refcount, release_iface); | ||
| 637 | } else | ||
| 638 | list_add_tail(&info->iface_head, &ses->iface_list); | ||
| 639 | spin_unlock(&ses->iface_lock); | ||
| 640 | |||
| 641 | ses->iface_count++; | ||
| 642 | ses->iface_last_update = jiffies; | ||
| 624 | next_iface: | 643 | next_iface: |
| 644 | nb_iface++; | ||
| 625 | next = le32_to_cpu(p->Next); | 645 | next = le32_to_cpu(p->Next); |
| 626 | if (!next) | 646 | if (!next) { |
| 647 | bytes_left -= sizeof(*p); | ||
| 627 | break; | 648 | break; |
| 649 | } | ||
| 628 | p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); | 650 | p = (struct network_interface_info_ioctl_rsp *)((u8 *)p+next); |
| 629 | bytes_left -= next; | 651 | bytes_left -= next; |
| 630 | } | 652 | } |
| 631 | 653 | ||
| 632 | if (!*iface_count) { | 654 | if (!nb_iface) { |
| 655 | cifs_dbg(VFS, "%s: malformed interface info\n", __func__); | ||
| 633 | rc = -EINVAL; | 656 | rc = -EINVAL; |
| 634 | goto out; | 657 | goto out; |
| 635 | } | 658 | } |
| 636 | 659 | ||
| 637 | out: | 660 | /* Azure rounds the buffer size up 8, to a 16 byte boundary */ |
| 638 | if (rc) { | 661 | if ((bytes_left > 8) || p->Next) |
| 639 | kfree(*iface_list); | 662 | cifs_dbg(VFS, "%s: incomplete interface info\n", __func__); |
| 640 | *iface_count = 0; | ||
| 641 | *iface_list = NULL; | ||
| 642 | } | ||
| 643 | return rc; | ||
| 644 | } | ||
| 645 | 663 | ||
| 646 | static int compare_iface(const void *ia, const void *ib) | ||
| 647 | { | ||
| 648 | const struct cifs_server_iface *a = (struct cifs_server_iface *)ia; | ||
| 649 | const struct cifs_server_iface *b = (struct cifs_server_iface *)ib; | ||
| 650 | 664 | ||
| 651 | return a->speed == b->speed ? 0 : (a->speed > b->speed ? -1 : 1); | 665 | if (!ses->iface_count) { |
| 666 | rc = -EINVAL; | ||
| 667 | goto out; | ||
| 668 | } | ||
| 669 | |||
| 670 | out: | ||
| 671 | return rc; | ||
| 652 | } | 672 | } |
| 653 | 673 | ||
| 654 | static int | 674 | static int |
| @@ -657,8 +677,6 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) · 2 unmodified lines | |||
| 657 | int rc; | 677 | int rc; |
| 658 | unsigned int ret_data_len = 0; | 678 | unsigned int ret_data_len = 0; |
| 659 | struct network_interface_info_ioctl_rsp *out_buf = NULL; | 679 | struct network_interface_info_ioctl_rsp *out_buf = NULL; |
| 660 | struct cifs_server_iface *iface_list; | ||
| 661 | size_t iface_count; | ||
| 662 | struct cifs_ses *ses = tcon->ses; | 680 | struct cifs_ses *ses = tcon->ses; |
| 663 | 681 | ||
| 664 | rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, | 682 | rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, |
| @@ -674,21 +692,10 @@ SMB3_request_interfaces(const unsigned int xid, struct cifs_tcon *tcon) · 9 unmodified lines | |||
| 674 | goto out; | 692 | goto out; |
| 675 | } | 693 | } |
| 676 | 694 | ||
| 677 | rc = parse_server_interfaces(out_buf, ret_data_len, | 695 | rc = parse_server_interfaces(out_buf, ret_data_len, ses); |
| 678 | &iface_list, &iface_count); | ||
| 679 | if (rc) | 696 | if (rc) |
| 680 | goto out; | 697 | goto out; |
| 681 | 698 | ||
| 682 | /* sort interfaces from fastest to slowest */ | ||
| 683 | sort(iface_list, iface_count, sizeof(*iface_list), compare_iface, NULL); | ||
| 684 | |||
| 685 | spin_lock(&ses->iface_lock); | ||
| 686 | kfree(ses->iface_list); | ||
| 687 | ses->iface_list = iface_list; | ||
| 688 | ses->iface_count = iface_count; | ||
| 689 | ses->iface_last_update = jiffies; | ||
| 690 | spin_unlock(&ses->iface_lock); | ||
| 691 | |||
| 692 | out: | 699 | out: |
| 693 | kfree(out_buf); | 700 | kfree(out_buf); |
| 694 | return rc; | 701 | return rc; |