xref: /openbmc/linux/fs/afs/vl_alias.c (revision e31cf2f4)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* AFS cell alias detection
3  *
4  * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
5  * Written by David Howells (dhowells@redhat.com)
6  */
7 
8 #include <linux/slab.h>
9 #include <linux/sched.h>
10 #include <linux/namei.h>
11 #include <keys/rxrpc-type.h>
12 #include "internal.h"
13 
14 /*
15  * Sample a volume.
16  */
17 static struct afs_volume *afs_sample_volume(struct afs_cell *cell, struct key *key,
18 					    const char *name, unsigned int namelen)
19 {
20 	struct afs_volume *volume;
21 	struct afs_fs_context fc = {
22 		.type		= 0, /* Explicitly leave it to the VLDB */
23 		.volnamesz	= namelen,
24 		.volname	= name,
25 		.net		= cell->net,
26 		.cell		= cell,
27 		.key		= key, /* This might need to be something */
28 	};
29 
30 	volume = afs_create_volume(&fc);
31 	_leave(" = %px", volume);
32 	return volume;
33 }
34 
35 /*
36  * Compare two addresses.
37  */
38 static int afs_compare_addrs(const struct sockaddr_rxrpc *srx_a,
39 			     const struct sockaddr_rxrpc *srx_b)
40 {
41 	short port_a, port_b;
42 	int addr_a, addr_b, diff;
43 
44 	diff = (short)srx_a->transport_type - (short)srx_b->transport_type;
45 	if (diff)
46 		goto out;
47 
48 	switch (srx_a->transport_type) {
49 	case AF_INET: {
50 		const struct sockaddr_in *a = &srx_a->transport.sin;
51 		const struct sockaddr_in *b = &srx_b->transport.sin;
52 		addr_a = ntohl(a->sin_addr.s_addr);
53 		addr_b = ntohl(b->sin_addr.s_addr);
54 		diff = addr_a - addr_b;
55 		if (diff == 0) {
56 			port_a = ntohs(a->sin_port);
57 			port_b = ntohs(b->sin_port);
58 			diff = port_a - port_b;
59 		}
60 		break;
61 	}
62 
63 	case AF_INET6: {
64 		const struct sockaddr_in6 *a = &srx_a->transport.sin6;
65 		const struct sockaddr_in6 *b = &srx_b->transport.sin6;
66 		diff = memcmp(&a->sin6_addr, &b->sin6_addr, 16);
67 		if (diff == 0) {
68 			port_a = ntohs(a->sin6_port);
69 			port_b = ntohs(b->sin6_port);
70 			diff = port_a - port_b;
71 		}
72 		break;
73 	}
74 
75 	default:
76 		BUG();
77 	}
78 
79 out:
80 	return diff;
81 }
82 
83 /*
84  * Compare the address lists of a pair of fileservers.
85  */
86 static int afs_compare_fs_alists(const struct afs_server *server_a,
87 				 const struct afs_server *server_b)
88 {
89 	const struct afs_addr_list *la, *lb;
90 	int a = 0, b = 0, addr_matches = 0;
91 
92 	la = rcu_dereference(server_a->addresses);
93 	lb = rcu_dereference(server_b->addresses);
94 
95 	while (a < la->nr_addrs && b < lb->nr_addrs) {
96 		const struct sockaddr_rxrpc *srx_a = &la->addrs[a];
97 		const struct sockaddr_rxrpc *srx_b = &lb->addrs[b];
98 		int diff = afs_compare_addrs(srx_a, srx_b);
99 
100 		if (diff < 0) {
101 			a++;
102 		} else if (diff > 0) {
103 			b++;
104 		} else {
105 			addr_matches++;
106 			a++;
107 			b++;
108 		}
109 	}
110 
111 	return addr_matches;
112 }
113 
114 /*
115  * Compare the fileserver lists of two volumes.  The server lists are sorted in
116  * order of ascending UUID.
117  */
118 static int afs_compare_volume_slists(const struct afs_volume *vol_a,
119 				     const struct afs_volume *vol_b)
120 {
121 	const struct afs_server_list *la, *lb;
122 	int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
123 
124 	la = rcu_dereference(vol_a->servers);
125 	lb = rcu_dereference(vol_b->servers);
126 
127 	for (i = 0; i < AFS_MAXTYPES; i++)
128 		if (la->vids[i] != lb->vids[i])
129 			return 0;
130 
131 	while (a < la->nr_servers && b < lb->nr_servers) {
132 		const struct afs_server *server_a = la->servers[a].server;
133 		const struct afs_server *server_b = lb->servers[b].server;
134 		int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
135 
136 		if (diff < 0) {
137 			a++;
138 		} else if (diff > 0) {
139 			b++;
140 		} else {
141 			uuid_matches++;
142 			addr_matches += afs_compare_fs_alists(server_a, server_b);
143 			a++;
144 			b++;
145 		}
146 	}
147 
148 	_leave(" = %d [um %d]", addr_matches, uuid_matches);
149 	return addr_matches;
150 }
151 
152 /*
153  * Compare root.cell volumes.
154  */
155 static int afs_compare_cell_roots(struct afs_cell *cell)
156 {
157 	struct afs_cell *p;
158 
159 	_enter("");
160 
161 	rcu_read_lock();
162 
163 	hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
164 		if (p == cell || p->alias_of)
165 			continue;
166 		if (!p->root_volume)
167 			continue; /* Ignore cells that don't have a root.cell volume. */
168 
169 		if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
170 			goto is_alias;
171 	}
172 
173 	rcu_read_unlock();
174 	_leave(" = 0");
175 	return 0;
176 
177 is_alias:
178 	rcu_read_unlock();
179 	cell->alias_of = afs_get_cell(p);
180 	return 1;
181 }
182 
183 /*
184  * Query the new cell for a volume from a cell we're already using.
185  */
186 static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
187 				   struct afs_cell *p)
188 {
189 	struct afs_volume *volume, *pvol = NULL;
190 	int ret;
191 
192 	/* Arbitrarily pick a volume from the list. */
193 	read_seqlock_excl(&p->volume_lock);
194 	if (!RB_EMPTY_ROOT(&p->volumes))
195 		pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
196 					       struct afs_volume, cell_node),
197 				      afs_volume_trace_get_query_alias);
198 	read_sequnlock_excl(&p->volume_lock);
199 	if (!pvol)
200 		return 0;
201 
202 	_enter("%s:%s", cell->name, pvol->name);
203 
204 	/* And see if it's in the new cell. */
205 	volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
206 	if (IS_ERR(volume)) {
207 		afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
208 		if (PTR_ERR(volume) != -ENOMEDIUM)
209 			return PTR_ERR(volume);
210 		/* That volume is not in the new cell, so not an alias */
211 		return 0;
212 	}
213 
214 	/* The new cell has a like-named volume also - compare volume ID,
215 	 * server and address lists.
216 	 */
217 	ret = 0;
218 	if (pvol->vid == volume->vid) {
219 		rcu_read_lock();
220 		if (afs_compare_volume_slists(volume, pvol))
221 			ret = 1;
222 		rcu_read_unlock();
223 	}
224 
225 	afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias);
226 	afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
227 	return ret;
228 }
229 
230 /*
231  * Query the new cell for volumes we know exist in cells we're already using.
232  */
233 static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
234 {
235 	struct afs_cell *p;
236 
237 	_enter("%s", cell->name);
238 
239 	if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
240 		return -ERESTARTSYS;
241 
242 	hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
243 		if (p == cell || p->alias_of)
244 			continue;
245 		if (RB_EMPTY_ROOT(&p->volumes))
246 			continue;
247 		if (p->root_volume)
248 			continue; /* Ignore cells that have a root.cell volume. */
249 		afs_get_cell(p);
250 		mutex_unlock(&cell->net->proc_cells_lock);
251 
252 		if (afs_query_for_alias_one(cell, key, p) != 0)
253 			goto is_alias;
254 
255 		if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
256 			afs_put_cell(cell->net, p);
257 			return -ERESTARTSYS;
258 		}
259 
260 		afs_put_cell(cell->net, p);
261 	}
262 
263 	mutex_unlock(&cell->net->proc_cells_lock);
264 	_leave(" = 0");
265 	return 0;
266 
267 is_alias:
268 	cell->alias_of = p; /* Transfer our ref */
269 	return 1;
270 }
271 
272 /*
273  * Look up a VLDB record for a volume.
274  */
275 static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
276 {
277 	struct afs_vl_cursor vc;
278 	char *cell_name = ERR_PTR(-EDESTADDRREQ);
279 	bool skipped = false, not_skipped = false;
280 	int ret;
281 
282 	if (!afs_begin_vlserver_operation(&vc, cell, key))
283 		return ERR_PTR(-ERESTARTSYS);
284 
285 	while (afs_select_vlserver(&vc)) {
286 		if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
287 			vc.ac.error = -EOPNOTSUPP;
288 			skipped = true;
289 			continue;
290 		}
291 		not_skipped = true;
292 		cell_name = afs_yfsvl_get_cell_name(&vc);
293 	}
294 
295 	ret = afs_end_vlserver_operation(&vc);
296 	if (skipped && !not_skipped)
297 		ret = -EOPNOTSUPP;
298 	return ret < 0 ? ERR_PTR(ret) : cell_name;
299 }
300 
301 static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
302 {
303 	struct afs_cell *master;
304 	char *cell_name;
305 
306 	cell_name = afs_vl_get_cell_name(cell, key);
307 	if (IS_ERR(cell_name))
308 		return PTR_ERR(cell_name);
309 
310 	if (strcmp(cell_name, cell->name) == 0) {
311 		kfree(cell_name);
312 		return 0;
313 	}
314 
315 	master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
316 				 NULL, false);
317 	kfree(cell_name);
318 	if (IS_ERR(master))
319 		return PTR_ERR(master);
320 
321 	cell->alias_of = master; /* Transfer our ref */
322 	return 1;
323 }
324 
325 static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
326 {
327 	struct afs_volume *root_volume;
328 	int ret;
329 
330 	_enter("%s", cell->name);
331 
332 	ret = yfs_check_canonical_cell_name(cell, key);
333 	if (ret != -EOPNOTSUPP)
334 		return ret;
335 
336 	/* Try and get the root.cell volume for comparison with other cells */
337 	root_volume = afs_sample_volume(cell, key, "root.cell", 9);
338 	if (!IS_ERR(root_volume)) {
339 		cell->root_volume = root_volume;
340 		return afs_compare_cell_roots(cell);
341 	}
342 
343 	if (PTR_ERR(root_volume) != -ENOMEDIUM)
344 		return PTR_ERR(root_volume);
345 
346 	/* Okay, this cell doesn't have an root.cell volume.  We need to
347 	 * locate some other random volume and use that to check.
348 	 */
349 	return afs_query_for_alias(cell, key);
350 }
351 
352 /*
353  * Check to see if a new cell is an alias of a cell we already have.  At this
354  * point we have the cell's volume server list.
355  *
356  * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
357  * if we had problems gathering the data required.  In the case the we did
358  * detect an alias, cell->alias_of is set to point to the assumed master.
359  */
360 int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
361 {
362 	struct afs_net *net = cell->net;
363 	int ret;
364 
365 	if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
366 		return -ERESTARTSYS;
367 
368 	if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
369 		ret = afs_do_cell_detect_alias(cell, key);
370 		if (ret >= 0)
371 			clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
372 	} else {
373 		ret = cell->alias_of ? 1 : 0;
374 	}
375 
376 	mutex_unlock(&net->cells_alias_lock);
377 
378 	if (ret == 1)
379 		pr_notice("kAFS: Cell %s is an alias of %s\n",
380 			  cell->name, cell->alias_of->name);
381 	return ret;
382 }
383