cifs: Handle witness client move notification

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>
This commit is contained in:
Samuel Cabrero 2020-11-30 19:02:56 +01:00 committed by Steve French
parent af1e40d9ac
commit 121d947d4f
3 changed files with 162 additions and 8 deletions

View File

@ -78,6 +78,7 @@ static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg)
struct sk_buff *skb; struct sk_buff *skb;
struct genlmsghdr *hdr; struct genlmsghdr *hdr;
enum securityEnum authtype; enum securityEnum authtype;
struct sockaddr_storage *addr;
int ret; int ret;
skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 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) if (ret < 0)
goto nlmsg_fail; 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) if (ret < 0)
goto nlmsg_fail; goto nlmsg_fail;
@ -413,6 +424,120 @@ static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const ch
return 0; 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) int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info)
{ {
struct cifs_swn_reg *swnreg; 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); 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: default:
cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type); cifs_dbg(FYI, "%s: unknown notification type %d\n", __func__, type);
break; break;

View File

@ -687,6 +687,10 @@ struct TCP_Server_Info {
int nr_targets; int nr_targets;
bool noblockcnt; /* use non-blocking connect() */ bool noblockcnt; /* use non-blocking connect() */
bool is_channel; /* if a session channel */ 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 { struct cifs_credits {

View File

@ -312,13 +312,24 @@ cifs_reconnect(struct TCP_Server_Info *server)
try_to_freeze(); try_to_freeze();
mutex_lock(&server->srv_mutex); 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 #ifdef CONFIG_CIFS_DFS_UPCALL
/* /*
* Set up next DFS target server (if any) for reconnect. If DFS * Set up next DFS target server (if any) for reconnect. If DFS
* feature is disabled, then we will retry last server we * feature is disabled, then we will retry last server we
* connected to before. * connected to before.
*/ */
reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
#endif
#ifdef CONFIG_CIFS_SWN_UPCALL
}
#endif #endif
if (cifs_rdma_enabled(server)) if (cifs_rdma_enabled(server))
@ -336,6 +347,9 @@ cifs_reconnect(struct TCP_Server_Info *server)
if (server->tcpStatus != CifsExiting) if (server->tcpStatus != CifsExiting)
server->tcpStatus = CifsNeedNegotiate; server->tcpStatus = CifsNeedNegotiate;
spin_unlock(&GlobalMid_Lock); spin_unlock(&GlobalMid_Lock);
#ifdef CONFIG_CIFS_SWN_UPCALL
server->use_swn_dstaddr = false;
#endif
mutex_unlock(&server->srv_mutex); mutex_unlock(&server->srv_mutex);
} }
} while (server->tcpStatus == CifsNeedReconnect); } while (server->tcpStatus == CifsNeedReconnect);