cifs: Handle witness client move notification
authorSamuel Cabrero <scabrero@suse.de>
Mon, 30 Nov 2020 18:02:56 +0000 (19:02 +0100)
committerSteve French <stfrench@microsoft.com>
Mon, 14 Dec 2020 15:18:55 +0000 (09:18 -0600)
This message is sent to tell a client to close its current connection
and connect to the specified address.

Signed-off-by: Samuel Cabrero <scabrero@suse.de>
Reviewed-by: Aurelien Aptel <aaptel@suse.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifs_swn.c
fs/cifs/cifsglob.h
fs/cifs/connect.c

index 642c9ee..a172769 100644 (file)
@@ -78,6 +78,7 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
        struct sk_buff *skb;
        struct genlmsghdr *hdr;
        enum securityEnum authtype;
+       struct sockaddr_storage *addr;
        int ret;
 
        skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
@@ -104,8 +105,18 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
        if (ret < 0)
                goto nlmsg_fail;
 
-       ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage),
-                       &swnreg->tcon->ses->server->dstaddr);
+       /*
+        * If there is an address stored use it instead of the server address, because we are
+        * in the process of reconnecting to it after a share has been moved or we have been
+        * told to switch to it (client move message). In these cases we unregister from the
+        * server address and register to the new address when we receive the notification.
+        */
+       if (swnreg->tcon->ses->server->use_swn_dstaddr)
+               addr = &swnreg->tcon->ses->server->swn_dstaddr;
+       else
+               addr = &swnreg->tcon->ses->server->dstaddr;
+
+       ret = nla_put(skb, CIFS_GENL_ATTR_SWN_IP, sizeof(struct sockaddr_storage), addr);
        if (ret < 0)
                goto nlmsg_fail;
 
@@ -413,6 +424,120 @@ static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const ch
        return 0;
 }
 
+static bool cifs_sockaddr_equal(struct sockaddr_storage *addr1, struct sockaddr_storage *addr2)
+{
+       if (addr1->ss_family != addr2->ss_family)
+               return false;
+
+       if (addr1->ss_family == AF_INET) {
+               return (memcmp(&((const struct sockaddr_in *)addr1)->sin_addr,
+                               &((const struct sockaddr_in *)addr2)->sin_addr,
+                               sizeof(struct in_addr)) == 0);
+       }
+
+       if (addr1->ss_family == AF_INET6) {
+               return (memcmp(&((const struct sockaddr_in6 *)addr1)->sin6_addr,
+                               &((const struct sockaddr_in6 *)addr2)->sin6_addr,
+                               sizeof(struct in6_addr)) == 0);
+       }
+
+       return false;
+}
+
+static int cifs_swn_store_swn_addr(const struct sockaddr_storage *new,
+                                  const struct sockaddr_storage *old,
+                                  struct sockaddr_storage *dst)
+{
+       __be16 port;
+
+       if (old->ss_family == AF_INET) {
+               struct sockaddr_in *ipv4 = (struct sockaddr_in *)old;
+
+               port = ipv4->sin_port;
+       }
+
+       if (old->ss_family == AF_INET6) {
+               struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)old;
+
+               port = ipv6->sin6_port;
+       }
+
+       if (new->ss_family == AF_INET) {
+               struct sockaddr_in *ipv4 = (struct sockaddr_in *)new;
+
+               ipv4->sin_port = port;
+       }
+
+       if (new->ss_family == AF_INET6) {
+               struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)new;
+
+               ipv6->sin6_port = port;
+       }
+
+       *dst = *new;
+
+       return 0;
+}
+
+static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *addr)
+{
+       /* Store the reconnect address */
+       mutex_lock(&tcon->ses->server->srv_mutex);
+       if (!cifs_sockaddr_equal(&tcon->ses->server->dstaddr, addr)) {
+               int ret;
+
+               ret = cifs_swn_store_swn_addr(addr, &tcon->ses->server->dstaddr,
+                               &tcon->ses->server->swn_dstaddr);
+               if (ret < 0) {
+                       cifs_dbg(VFS, "%s: failed to store address: %d\n", __func__, ret);
+                       return ret;
+               }
+               tcon->ses->server->use_swn_dstaddr = true;
+
+               /*
+                * Unregister to stop receiving notifications for the old IP address.
+                */
+               ret = cifs_swn_unregister(tcon);
+               if (ret < 0) {
+                       cifs_dbg(VFS, "%s: Failed to unregister for witness notifications: %d\n",
+                                       __func__, ret);
+                       return ret;
+               }
+
+               /*
+                * And register to receive notifications for the new IP address now that we have
+                * stored the new address.
+                */
+               ret = cifs_swn_register(tcon);
+               if (ret < 0) {
+                       cifs_dbg(VFS, "%s: Failed to register for witness notifications: %d\n",
+                                       __func__, ret);
+                       return ret;
+               }
+
+               spin_lock(&GlobalMid_Lock);
+               if (tcon->ses->server->tcpStatus != CifsExiting)
+                       tcon->ses->server->tcpStatus = CifsNeedReconnect;
+               spin_unlock(&GlobalMid_Lock);
+       }
+       mutex_unlock(&tcon->ses->server->srv_mutex);
+
+       return 0;
+}
+
+static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr)
+{
+       struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr;
+       struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr;
+
+       if (addr->ss_family == AF_INET)
+               cifs_dbg(FYI, "%s: move to %pI4\n", __func__, &ipv4->sin_addr);
+       else if (addr->ss_family == AF_INET6)
+               cifs_dbg(FYI, "%s: move to %pI6\n", __func__, &ipv6->sin6_addr);
+
+       return cifs_swn_reconnect(swnreg->tcon, addr);
+}
+
 int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
 {
        struct cifs_swn_reg *swnreg;
@@ -461,6 +586,17 @@ int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
                }
                return cifs_swn_resource_state_changed(swnreg, name, state);
        }
+       case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: {
+               struct sockaddr_storage addr;
+
+               if (info->attrs[CIFS_GENL_ATTR_SWN_IP]) {
+                       nla_memcpy(&addr, info->attrs[CIFS_GENL_ATTR_SWN_IP], sizeof(addr));
+               } else {
+                       cifs_dbg(FYI, "%s: missing IP address attribute\n", __func__);
+                       return -EINVAL;
+               }
+               return cifs_swn_client_move(swnreg, &addr);
+       }
        default:
                cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type);
                break;
index 7843810..720d0f6 100644 (file)
@@ -687,6 +687,10 @@ struct TCP_Server_Info {
        int nr_targets;
        bool noblockcnt; /* use non-blocking connect() */
        bool is_channel; /* if a session channel */
+#ifdef CONFIG_CIFS_SWN_UPCALL
+       bool use_swn_dstaddr;
+       struct sockaddr_storage swn_dstaddr;
+#endif
 };
 
 struct cifs_credits {
index 1c8b08c..9f59fe2 100644 (file)
@@ -312,13 +312,24 @@ cifs_reconnect(struct TCP_Server_Info *server)
                try_to_freeze();
 
                mutex_lock(&server->srv_mutex);
+
+#ifdef CONFIG_CIFS_SWN_UPCALL
+               if (server->use_swn_dstaddr) {
+                       server->dstaddr = server->swn_dstaddr;
+               } else {
+#endif
+
 #ifdef CONFIG_CIFS_DFS_UPCALL
-               /*
-                * Set up next DFS target server (if any) for reconnect. If DFS
-                * feature is disabled, then we will retry last server we
-                * connected to before.
-                */
-               reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
+                       /*
+                        * Set up next DFS target server (if any) for reconnect. If DFS
+                        * feature is disabled, then we will retry last server we
+                        * connected to before.
+                        */
+                       reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
+#endif
+
+#ifdef CONFIG_CIFS_SWN_UPCALL
+               }
 #endif
 
                if (cifs_rdma_enabled(server))
@@ -336,6 +347,9 @@ cifs_reconnect(struct TCP_Server_Info *server)
                        if (server->tcpStatus != CifsExiting)
                                server->tcpStatus = CifsNeedNegotiate;
                        spin_unlock(&GlobalMid_Lock);
+#ifdef CONFIG_CIFS_SWN_UPCALL
+                       server->use_swn_dstaddr = false;
+#endif
                        mutex_unlock(&server->srv_mutex);
                }
        } while (server->tcpStatus == CifsNeedReconnect);