Skip to content

Commit

Permalink
smb: client: fix DFS interlink failover
Browse files Browse the repository at this point in the history
The DFS interlinks point to different DFS namespaces so make sure to
use the correct DFS root server to chase any DFS links under it by
storing the SMB session in dfs_ref_walk structure and then using it on
every referral walk.

Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
Paulo Alcantara authored and Steve French committed Sep 25, 2024
1 parent 9190cc0 commit 4f42a8b
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 86 deletions.
3 changes: 3 additions & 0 deletions fs/smb/client/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ struct TCP_Server_Info {
* format: \\HOST\SHARE[\OPTIONAL PATH]
*/
char *leaf_fullpath;
bool dfs_conn:1;
};

static inline bool is_smb1(struct TCP_Server_Info *server)
Expand Down Expand Up @@ -1059,6 +1060,7 @@ struct cifs_ses {
struct list_head smb_ses_list;
struct list_head rlist; /* reconnect list */
struct list_head tcon_list;
struct list_head dlist; /* dfs list */
struct cifs_tcon *tcon_ipc;
spinlock_t ses_lock; /* protect anything here that is not protected */
struct mutex session_mutex;
Expand Down Expand Up @@ -1287,6 +1289,7 @@ struct cifs_tcon {
/* BB add field for back pointer to sb struct(s)? */
#ifdef CONFIG_CIFS_DFS_UPCALL
struct delayed_work dfs_cache_work;
struct list_head dfs_ses_list;
#endif
struct delayed_work query_interfaces; /* query interfaces workqueue job */
char *origin_fullpath; /* canonical copy of smb3_fs_context::source */
Expand Down
12 changes: 2 additions & 10 deletions fs/smb/client/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -724,15 +724,9 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)

int cifs_wait_for_server_reconnect(struct TCP_Server_Info *server, bool retry);

/* Put references of @ses and its children */
static inline void cifs_put_smb_ses(struct cifs_ses *ses)
{
struct cifs_ses *next;

do {
next = ses->dfs_root_ses;
__cifs_put_smb_ses(ses);
} while ((ses = next));
__cifs_put_smb_ses(ses);
}

/* Get an active reference of @ses and its children.
Expand All @@ -746,9 +740,7 @@ static inline void cifs_put_smb_ses(struct cifs_ses *ses)
static inline void cifs_smb_ses_inc_refcount(struct cifs_ses *ses)
{
lockdep_assert_held(&cifs_tcp_ses_lock);

for (; ses; ses = ses->dfs_root_ses)
ses->ses_count++;
ses->ses_count++;
}

static inline bool dfs_src_pathname_equal(const char *s1, const char *s2)
Expand Down
41 changes: 20 additions & 21 deletions fs/smb/client/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,9 @@ static int match_server(struct TCP_Server_Info *server,
if (server->nosharesock)
return 0;

if (!match_super && (ctx->dfs_conn || server->dfs_conn))
return 0;

/* If multidialect negotiation see if existing sessions match one */
if (strcmp(ctx->vals->version_string, SMB3ANY_VERSION_STRING) == 0) {
if (server->vals->protocol_id < SMB30_PROT_ID)
Expand Down Expand Up @@ -1723,6 +1726,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,

if (ctx->nosharesock)
tcp_ses->nosharesock = true;
tcp_ses->dfs_conn = ctx->dfs_conn;

tcp_ses->ops = ctx->ops;
tcp_ses->vals = ctx->vals;
Expand Down Expand Up @@ -1873,13 +1877,15 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
}

/* this function must be called with ses_lock and chan_lock held */
static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
static int match_session(struct cifs_ses *ses,
struct smb3_fs_context *ctx,
bool match_super)
{
if (ctx->sectype != Unspecified &&
ctx->sectype != ses->sectype)
return 0;

if (ctx->dfs_root_ses != ses->dfs_root_ses)
if (!match_super && ctx->dfs_root_ses != ses->dfs_root_ses)
return 0;

/*
Expand Down Expand Up @@ -1998,7 +2004,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
continue;
}
spin_lock(&ses->chan_lock);
if (match_session(ses, ctx)) {
if (match_session(ses, ctx, false)) {
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
ret = ses;
Expand Down Expand Up @@ -2382,8 +2388,6 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
* need to lock before changing something in the session.
*/
spin_lock(&cifs_tcp_ses_lock);
if (ctx->dfs_root_ses)
cifs_smb_ses_inc_refcount(ctx->dfs_root_ses);
ses->dfs_root_ses = ctx->dfs_root_ses;
list_add(&ses->smb_ses_list, &server->smb_ses_list);
spin_unlock(&cifs_tcp_ses_lock);
Expand Down Expand Up @@ -2458,6 +2462,7 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
{
unsigned int xid;
struct cifs_ses *ses;
LIST_HEAD(ses_list);

/*
* IPC tcon share the lifetime of their session and are
Expand All @@ -2482,6 +2487,9 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)

list_del_init(&tcon->tcon_list);
tcon->status = TID_EXITING;
#ifdef CONFIG_CIFS_DFS_UPCALL
list_replace_init(&tcon->dfs_ses_list, &ses_list);
#endif
spin_unlock(&tcon->tc_lock);
spin_unlock(&cifs_tcp_ses_lock);

Expand Down Expand Up @@ -2509,6 +2517,9 @@ cifs_put_tcon(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace)
cifs_fscache_release_super_cookie(tcon);
tconInfoFree(tcon, netfs_trace_tcon_ref_free);
cifs_put_smb_ses(ses);
#ifdef CONFIG_CIFS_DFS_UPCALL
dfs_put_root_smb_sessions(&ses_list);
#endif
}

/**
Expand Down Expand Up @@ -2892,7 +2903,7 @@ cifs_match_super(struct super_block *sb, void *data)
spin_lock(&ses->chan_lock);
spin_lock(&tcon->tc_lock);
if (!match_server(tcp_srv, ctx, true) ||
!match_session(ses, ctx) ||
!match_session(ses, ctx, true) ||
!match_tcon(tcon, ctx) ||
!match_prepath(sb, tcon, mnt_data)) {
rc = 0;
Expand Down Expand Up @@ -3623,13 +3634,12 @@ int cifs_is_path_remote(struct cifs_mount_ctx *mnt_ctx)
int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
{
struct cifs_mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };
bool isdfs;
int rc;

rc = dfs_mount_share(&mnt_ctx, &isdfs);
rc = dfs_mount_share(&mnt_ctx);
if (rc)
goto error;
if (!isdfs)
if (!ctx->dfs_conn)
goto out;

/*
Expand Down Expand Up @@ -4034,7 +4044,7 @@ cifs_set_vol_auth(struct smb3_fs_context *ctx, struct cifs_ses *ses)
}

static struct cifs_tcon *
__cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
{
int rc;
struct cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb);
Expand Down Expand Up @@ -4132,17 +4142,6 @@ __cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
return tcon;
}

static struct cifs_tcon *
cifs_construct_tcon(struct cifs_sb_info *cifs_sb, kuid_t fsuid)
{
struct cifs_tcon *ret;

cifs_mount_lock();
ret = __cifs_construct_tcon(cifs_sb, fsuid);
cifs_mount_unlock();
return ret;
}

struct cifs_tcon *
cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb)
{
Expand Down
73 changes: 35 additions & 38 deletions fs/smb/client/dfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
* Get an active reference of @ses so that next call to cifs_put_tcon() won't
* release it as any new DFS referrals must go through its IPC tcon.
*/
static void add_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
static void set_root_smb_session(struct cifs_mount_ctx *mnt_ctx)
{
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct cifs_ses *ses = mnt_ctx->ses;
Expand All @@ -95,7 +95,7 @@ static inline int parse_dfs_target(struct smb3_fs_context *ctx,
return rc;
}

static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
static int setup_dfs_ref(struct cifs_mount_ctx *mnt_ctx,
struct dfs_info3_param *tgt,
struct dfs_ref_walk *rw)
{
Expand All @@ -120,6 +120,7 @@ static int set_ref_paths(struct cifs_mount_ctx *mnt_ctx,
}
ref_walk_path(rw) = ref_path;
ref_walk_fpath(rw) = full_path;
ref_walk_ses(rw) = ctx->dfs_root_ses;
return 0;
}

Expand All @@ -128,11 +129,11 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
{
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct dfs_info3_param tgt = {};
bool is_refsrv;
int rc = -ENOENT;

again:
do {
ctx->dfs_root_ses = ref_walk_ses(rw);
if (ref_walk_empty(rw)) {
rc = dfs_get_referral(mnt_ctx, ref_walk_path(rw) + 1,
NULL, ref_walk_tl(rw));
Expand All @@ -158,10 +159,7 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
if (rc)
continue;

is_refsrv = tgt.server_type == DFS_TYPE_ROOT ||
DFS_INTERLINK(tgt.flags);
ref_walk_set_tgt_hint(rw);

if (tgt.flags & DFSREF_STORAGE_SERVER) {
rc = cifs_mount_get_tcon(mnt_ctx);
if (!rc)
Expand All @@ -172,12 +170,10 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
continue;
}

if (is_refsrv)
add_root_smb_session(mnt_ctx);

set_root_smb_session(mnt_ctx);
rc = ref_walk_advance(rw);
if (!rc) {
rc = set_ref_paths(mnt_ctx, &tgt, rw);
rc = setup_dfs_ref(mnt_ctx, &tgt, rw);
if (!rc) {
rc = -EREMOTE;
goto again;
Expand All @@ -193,37 +189,39 @@ static int __dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
return rc;
}

static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx)
static int dfs_referral_walk(struct cifs_mount_ctx *mnt_ctx,
struct dfs_ref_walk **rw)
{
struct dfs_ref_walk *rw;
int rc;

rw = ref_walk_alloc();
if (IS_ERR(rw))
return PTR_ERR(rw);
*rw = ref_walk_alloc();
if (IS_ERR(*rw)) {
rc = PTR_ERR(*rw);
*rw = NULL;
return rc;
}

ref_walk_init(rw);
rc = set_ref_paths(mnt_ctx, NULL, rw);
ref_walk_init(*rw);
rc = setup_dfs_ref(mnt_ctx, NULL, *rw);
if (!rc)
rc = __dfs_referral_walk(mnt_ctx, rw);
ref_walk_free(rw);
rc = __dfs_referral_walk(mnt_ctx, *rw);
return rc;
}

static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
{
struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
struct dfs_ref_walk *rw = NULL;
struct cifs_tcon *tcon;
char *origin_fullpath;
bool new_tcon = true;
int rc;

origin_fullpath = dfs_get_path(cifs_sb, ctx->source);
if (IS_ERR(origin_fullpath))
return PTR_ERR(origin_fullpath);

rc = dfs_referral_walk(mnt_ctx);
rc = dfs_referral_walk(mnt_ctx, &rw);
if (!rc) {
/*
* Prevent superblock from being created with any missing
Expand All @@ -241,21 +239,16 @@ static int __dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)

tcon = mnt_ctx->tcon;
spin_lock(&tcon->tc_lock);
if (!tcon->origin_fullpath) {
tcon->origin_fullpath = origin_fullpath;
origin_fullpath = NULL;
} else {
new_tcon = false;
}
tcon->origin_fullpath = origin_fullpath;
origin_fullpath = NULL;
ref_walk_set_tcon(rw, tcon);
spin_unlock(&tcon->tc_lock);

if (new_tcon) {
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
dfs_cache_get_ttl() * HZ);
}
queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
dfs_cache_get_ttl() * HZ);

out:
kfree(origin_fullpath);
ref_walk_free(rw);
return rc;
}

Expand All @@ -279,7 +272,7 @@ static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
return rc;
}

int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx)
{
struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
bool nodfs = ctx->nodfs;
Expand All @@ -289,7 +282,6 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
if (rc)
return rc;

*isdfs = false;
rc = get_session(mnt_ctx, NULL);
if (rc)
return rc;
Expand Down Expand Up @@ -317,10 +309,15 @@ int dfs_mount_share(struct cifs_mount_ctx *mnt_ctx, bool *isdfs)
return rc;
}

*isdfs = true;
add_root_smb_session(mnt_ctx);
rc = __dfs_mount_share(mnt_ctx);
dfs_put_root_smb_sessions(mnt_ctx);
if (!ctx->dfs_conn) {
ctx->dfs_conn = true;
cifs_mount_put_conns(mnt_ctx);
rc = get_session(mnt_ctx, NULL);
}
if (!rc) {
set_root_smb_session(mnt_ctx);
rc = __dfs_mount_share(mnt_ctx);
}
return rc;
}

Expand Down
Loading

0 comments on commit 4f42a8b

Please sign in to comment.