Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /* AFS cell alias detection
0003  *
0004  * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
0005  * Written by David Howells (dhowells@redhat.com)
0006  */
0007 
0008 #include <linux/slab.h>
0009 #include <linux/sched.h>
0010 #include <linux/namei.h>
0011 #include <keys/rxrpc-type.h>
0012 #include "internal.h"
0013 
0014 /*
0015  * Sample a volume.
0016  */
0017 static struct afs_volume *afs_sample_volume(struct afs_cell *cell, struct key *key,
0018                         const char *name, unsigned int namelen)
0019 {
0020     struct afs_volume *volume;
0021     struct afs_fs_context fc = {
0022         .type       = 0, /* Explicitly leave it to the VLDB */
0023         .volnamesz  = namelen,
0024         .volname    = name,
0025         .net        = cell->net,
0026         .cell       = cell,
0027         .key        = key, /* This might need to be something */
0028     };
0029 
0030     volume = afs_create_volume(&fc);
0031     _leave(" = %p", volume);
0032     return volume;
0033 }
0034 
0035 /*
0036  * Compare two addresses.
0037  */
0038 static int afs_compare_addrs(const struct sockaddr_rxrpc *srx_a,
0039                  const struct sockaddr_rxrpc *srx_b)
0040 {
0041     short port_a, port_b;
0042     int addr_a, addr_b, diff;
0043 
0044     diff = (short)srx_a->transport_type - (short)srx_b->transport_type;
0045     if (diff)
0046         goto out;
0047 
0048     switch (srx_a->transport_type) {
0049     case AF_INET: {
0050         const struct sockaddr_in *a = &srx_a->transport.sin;
0051         const struct sockaddr_in *b = &srx_b->transport.sin;
0052         addr_a = ntohl(a->sin_addr.s_addr);
0053         addr_b = ntohl(b->sin_addr.s_addr);
0054         diff = addr_a - addr_b;
0055         if (diff == 0) {
0056             port_a = ntohs(a->sin_port);
0057             port_b = ntohs(b->sin_port);
0058             diff = port_a - port_b;
0059         }
0060         break;
0061     }
0062 
0063     case AF_INET6: {
0064         const struct sockaddr_in6 *a = &srx_a->transport.sin6;
0065         const struct sockaddr_in6 *b = &srx_b->transport.sin6;
0066         diff = memcmp(&a->sin6_addr, &b->sin6_addr, 16);
0067         if (diff == 0) {
0068             port_a = ntohs(a->sin6_port);
0069             port_b = ntohs(b->sin6_port);
0070             diff = port_a - port_b;
0071         }
0072         break;
0073     }
0074 
0075     default:
0076         WARN_ON(1);
0077         diff = 1;
0078     }
0079 
0080 out:
0081     return diff;
0082 }
0083 
0084 /*
0085  * Compare the address lists of a pair of fileservers.
0086  */
0087 static int afs_compare_fs_alists(const struct afs_server *server_a,
0088                  const struct afs_server *server_b)
0089 {
0090     const struct afs_addr_list *la, *lb;
0091     int a = 0, b = 0, addr_matches = 0;
0092 
0093     la = rcu_dereference(server_a->addresses);
0094     lb = rcu_dereference(server_b->addresses);
0095 
0096     while (a < la->nr_addrs && b < lb->nr_addrs) {
0097         const struct sockaddr_rxrpc *srx_a = &la->addrs[a];
0098         const struct sockaddr_rxrpc *srx_b = &lb->addrs[b];
0099         int diff = afs_compare_addrs(srx_a, srx_b);
0100 
0101         if (diff < 0) {
0102             a++;
0103         } else if (diff > 0) {
0104             b++;
0105         } else {
0106             addr_matches++;
0107             a++;
0108             b++;
0109         }
0110     }
0111 
0112     return addr_matches;
0113 }
0114 
0115 /*
0116  * Compare the fileserver lists of two volumes.  The server lists are sorted in
0117  * order of ascending UUID.
0118  */
0119 static int afs_compare_volume_slists(const struct afs_volume *vol_a,
0120                      const struct afs_volume *vol_b)
0121 {
0122     const struct afs_server_list *la, *lb;
0123     int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
0124 
0125     la = rcu_dereference(vol_a->servers);
0126     lb = rcu_dereference(vol_b->servers);
0127 
0128     for (i = 0; i < AFS_MAXTYPES; i++)
0129         if (la->vids[i] != lb->vids[i])
0130             return 0;
0131 
0132     while (a < la->nr_servers && b < lb->nr_servers) {
0133         const struct afs_server *server_a = la->servers[a].server;
0134         const struct afs_server *server_b = lb->servers[b].server;
0135         int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
0136 
0137         if (diff < 0) {
0138             a++;
0139         } else if (diff > 0) {
0140             b++;
0141         } else {
0142             uuid_matches++;
0143             addr_matches += afs_compare_fs_alists(server_a, server_b);
0144             a++;
0145             b++;
0146         }
0147     }
0148 
0149     _leave(" = %d [um %d]", addr_matches, uuid_matches);
0150     return addr_matches;
0151 }
0152 
0153 /*
0154  * Compare root.cell volumes.
0155  */
0156 static int afs_compare_cell_roots(struct afs_cell *cell)
0157 {
0158     struct afs_cell *p;
0159 
0160     _enter("");
0161 
0162     rcu_read_lock();
0163 
0164     hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
0165         if (p == cell || p->alias_of)
0166             continue;
0167         if (!p->root_volume)
0168             continue; /* Ignore cells that don't have a root.cell volume. */
0169 
0170         if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
0171             goto is_alias;
0172     }
0173 
0174     rcu_read_unlock();
0175     _leave(" = 0");
0176     return 0;
0177 
0178 is_alias:
0179     rcu_read_unlock();
0180     cell->alias_of = afs_use_cell(p, afs_cell_trace_use_alias);
0181     return 1;
0182 }
0183 
0184 /*
0185  * Query the new cell for a volume from a cell we're already using.
0186  */
0187 static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
0188                    struct afs_cell *p)
0189 {
0190     struct afs_volume *volume, *pvol = NULL;
0191     int ret;
0192 
0193     /* Arbitrarily pick a volume from the list. */
0194     read_seqlock_excl(&p->volume_lock);
0195     if (!RB_EMPTY_ROOT(&p->volumes))
0196         pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
0197                            struct afs_volume, cell_node),
0198                       afs_volume_trace_get_query_alias);
0199     read_sequnlock_excl(&p->volume_lock);
0200     if (!pvol)
0201         return 0;
0202 
0203     _enter("%s:%s", cell->name, pvol->name);
0204 
0205     /* And see if it's in the new cell. */
0206     volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
0207     if (IS_ERR(volume)) {
0208         afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
0209         if (PTR_ERR(volume) != -ENOMEDIUM)
0210             return PTR_ERR(volume);
0211         /* That volume is not in the new cell, so not an alias */
0212         return 0;
0213     }
0214 
0215     /* The new cell has a like-named volume also - compare volume ID,
0216      * server and address lists.
0217      */
0218     ret = 0;
0219     if (pvol->vid == volume->vid) {
0220         rcu_read_lock();
0221         if (afs_compare_volume_slists(volume, pvol))
0222             ret = 1;
0223         rcu_read_unlock();
0224     }
0225 
0226     afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias);
0227     afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
0228     return ret;
0229 }
0230 
0231 /*
0232  * Query the new cell for volumes we know exist in cells we're already using.
0233  */
0234 static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
0235 {
0236     struct afs_cell *p;
0237 
0238     _enter("%s", cell->name);
0239 
0240     if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
0241         return -ERESTARTSYS;
0242 
0243     hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
0244         if (p == cell || p->alias_of)
0245             continue;
0246         if (RB_EMPTY_ROOT(&p->volumes))
0247             continue;
0248         if (p->root_volume)
0249             continue; /* Ignore cells that have a root.cell volume. */
0250         afs_use_cell(p, afs_cell_trace_use_check_alias);
0251         mutex_unlock(&cell->net->proc_cells_lock);
0252 
0253         if (afs_query_for_alias_one(cell, key, p) != 0)
0254             goto is_alias;
0255 
0256         if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
0257             afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
0258             return -ERESTARTSYS;
0259         }
0260 
0261         afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
0262     }
0263 
0264     mutex_unlock(&cell->net->proc_cells_lock);
0265     _leave(" = 0");
0266     return 0;
0267 
0268 is_alias:
0269     cell->alias_of = p; /* Transfer our ref */
0270     return 1;
0271 }
0272 
0273 /*
0274  * Look up a VLDB record for a volume.
0275  */
0276 static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
0277 {
0278     struct afs_vl_cursor vc;
0279     char *cell_name = ERR_PTR(-EDESTADDRREQ);
0280     bool skipped = false, not_skipped = false;
0281     int ret;
0282 
0283     if (!afs_begin_vlserver_operation(&vc, cell, key))
0284         return ERR_PTR(-ERESTARTSYS);
0285 
0286     while (afs_select_vlserver(&vc)) {
0287         if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
0288             vc.ac.error = -EOPNOTSUPP;
0289             skipped = true;
0290             continue;
0291         }
0292         not_skipped = true;
0293         cell_name = afs_yfsvl_get_cell_name(&vc);
0294     }
0295 
0296     ret = afs_end_vlserver_operation(&vc);
0297     if (skipped && !not_skipped)
0298         ret = -EOPNOTSUPP;
0299     return ret < 0 ? ERR_PTR(ret) : cell_name;
0300 }
0301 
0302 static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
0303 {
0304     struct afs_cell *master;
0305     char *cell_name;
0306 
0307     cell_name = afs_vl_get_cell_name(cell, key);
0308     if (IS_ERR(cell_name))
0309         return PTR_ERR(cell_name);
0310 
0311     if (strcmp(cell_name, cell->name) == 0) {
0312         kfree(cell_name);
0313         return 0;
0314     }
0315 
0316     master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
0317                  NULL, false);
0318     kfree(cell_name);
0319     if (IS_ERR(master))
0320         return PTR_ERR(master);
0321 
0322     cell->alias_of = master; /* Transfer our ref */
0323     return 1;
0324 }
0325 
0326 static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
0327 {
0328     struct afs_volume *root_volume;
0329     int ret;
0330 
0331     _enter("%s", cell->name);
0332 
0333     ret = yfs_check_canonical_cell_name(cell, key);
0334     if (ret != -EOPNOTSUPP)
0335         return ret;
0336 
0337     /* Try and get the root.cell volume for comparison with other cells */
0338     root_volume = afs_sample_volume(cell, key, "root.cell", 9);
0339     if (!IS_ERR(root_volume)) {
0340         cell->root_volume = root_volume;
0341         return afs_compare_cell_roots(cell);
0342     }
0343 
0344     if (PTR_ERR(root_volume) != -ENOMEDIUM)
0345         return PTR_ERR(root_volume);
0346 
0347     /* Okay, this cell doesn't have an root.cell volume.  We need to
0348      * locate some other random volume and use that to check.
0349      */
0350     return afs_query_for_alias(cell, key);
0351 }
0352 
0353 /*
0354  * Check to see if a new cell is an alias of a cell we already have.  At this
0355  * point we have the cell's volume server list.
0356  *
0357  * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
0358  * if we had problems gathering the data required.  In the case the we did
0359  * detect an alias, cell->alias_of is set to point to the assumed master.
0360  */
0361 int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
0362 {
0363     struct afs_net *net = cell->net;
0364     int ret;
0365 
0366     if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
0367         return -ERESTARTSYS;
0368 
0369     if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
0370         ret = afs_do_cell_detect_alias(cell, key);
0371         if (ret >= 0)
0372             clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
0373     } else {
0374         ret = cell->alias_of ? 1 : 0;
0375     }
0376 
0377     mutex_unlock(&net->cells_alias_lock);
0378 
0379     if (ret == 1)
0380         pr_notice("kAFS: Cell %s is an alias of %s\n",
0381               cell->name, cell->alias_of->name);
0382     return ret;
0383 }