Commit | Line | Data |
---|---|---|
8a070a96 DH |
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); | |
fed79fd7 | 31 | _leave(" = %p", volume); |
8a070a96 DH |
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: | |
9ca06525 DH |
76 | WARN_ON(1); |
77 | diff = 1; | |
8a070a96 DH |
78 | } |
79 | ||
80 | out: | |
81 | return diff; | |
82 | } | |
83 | ||
84 | /* | |
85 | * Compare the address lists of a pair of fileservers. | |
86 | */ | |
87 | static int afs_compare_fs_alists(const struct afs_server *server_a, | |
88 | const struct afs_server *server_b) | |
89 | { | |
90 | const struct afs_addr_list *la, *lb; | |
91 | int a = 0, b = 0, addr_matches = 0; | |
92 | ||
93 | la = rcu_dereference(server_a->addresses); | |
94 | lb = rcu_dereference(server_b->addresses); | |
95 | ||
96 | while (a < la->nr_addrs && b < lb->nr_addrs) { | |
97 | const struct sockaddr_rxrpc *srx_a = &la->addrs[a]; | |
98 | const struct sockaddr_rxrpc *srx_b = &lb->addrs[b]; | |
99 | int diff = afs_compare_addrs(srx_a, srx_b); | |
100 | ||
101 | if (diff < 0) { | |
102 | a++; | |
103 | } else if (diff > 0) { | |
104 | b++; | |
105 | } else { | |
106 | addr_matches++; | |
107 | a++; | |
108 | b++; | |
109 | } | |
110 | } | |
111 | ||
112 | return addr_matches; | |
113 | } | |
114 | ||
115 | /* | |
116 | * Compare the fileserver lists of two volumes. The server lists are sorted in | |
117 | * order of ascending UUID. | |
118 | */ | |
119 | static int afs_compare_volume_slists(const struct afs_volume *vol_a, | |
120 | const struct afs_volume *vol_b) | |
121 | { | |
122 | const struct afs_server_list *la, *lb; | |
123 | int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0; | |
124 | ||
125 | la = rcu_dereference(vol_a->servers); | |
126 | lb = rcu_dereference(vol_b->servers); | |
127 | ||
128 | for (i = 0; i < AFS_MAXTYPES; i++) | |
129 | if (la->vids[i] != lb->vids[i]) | |
130 | return 0; | |
131 | ||
132 | while (a < la->nr_servers && b < lb->nr_servers) { | |
133 | const struct afs_server *server_a = la->servers[a].server; | |
134 | const struct afs_server *server_b = lb->servers[b].server; | |
135 | int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t)); | |
136 | ||
137 | if (diff < 0) { | |
138 | a++; | |
139 | } else if (diff > 0) { | |
140 | b++; | |
141 | } else { | |
142 | uuid_matches++; | |
143 | addr_matches += afs_compare_fs_alists(server_a, server_b); | |
144 | a++; | |
145 | b++; | |
146 | } | |
147 | } | |
148 | ||
149 | _leave(" = %d [um %d]", addr_matches, uuid_matches); | |
150 | return addr_matches; | |
151 | } | |
152 | ||
153 | /* | |
154 | * Compare root.cell volumes. | |
155 | */ | |
156 | static int afs_compare_cell_roots(struct afs_cell *cell) | |
157 | { | |
158 | struct afs_cell *p; | |
159 | ||
160 | _enter(""); | |
161 | ||
162 | rcu_read_lock(); | |
163 | ||
164 | hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) { | |
165 | if (p == cell || p->alias_of) | |
166 | continue; | |
167 | if (!p->root_volume) | |
168 | continue; /* Ignore cells that don't have a root.cell volume. */ | |
169 | ||
170 | if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0) | |
171 | goto is_alias; | |
172 | } | |
173 | ||
174 | rcu_read_unlock(); | |
175 | _leave(" = 0"); | |
176 | return 0; | |
177 | ||
178 | is_alias: | |
179 | rcu_read_unlock(); | |
180 | cell->alias_of = afs_get_cell(p); | |
181 | return 1; | |
182 | } | |
183 | ||
6ef350b1 DH |
184 | /* |
185 | * Query the new cell for a volume from a cell we're already using. | |
186 | */ | |
187 | static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key, | |
188 | struct afs_cell *p) | |
189 | { | |
190 | struct afs_volume *volume, *pvol = NULL; | |
191 | int ret; | |
192 | ||
20325960 DH |
193 | /* Arbitrarily pick a volume from the list. */ |
194 | read_seqlock_excl(&p->volume_lock); | |
195 | if (!RB_EMPTY_ROOT(&p->volumes)) | |
196 | pvol = afs_get_volume(rb_entry(p->volumes.rb_node, | |
197 | struct afs_volume, cell_node), | |
cca37d45 | 198 | afs_volume_trace_get_query_alias); |
20325960 | 199 | read_sequnlock_excl(&p->volume_lock); |
6ef350b1 DH |
200 | if (!pvol) |
201 | return 0; | |
202 | ||
203 | _enter("%s:%s", cell->name, pvol->name); | |
204 | ||
205 | /* And see if it's in the new cell. */ | |
206 | volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len); | |
207 | if (IS_ERR(volume)) { | |
cca37d45 | 208 | afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias); |
6ef350b1 DH |
209 | if (PTR_ERR(volume) != -ENOMEDIUM) |
210 | return PTR_ERR(volume); | |
211 | /* That volume is not in the new cell, so not an alias */ | |
212 | return 0; | |
213 | } | |
214 | ||
215 | /* The new cell has a like-named volume also - compare volume ID, | |
216 | * server and address lists. | |
217 | */ | |
218 | ret = 0; | |
219 | if (pvol->vid == volume->vid) { | |
220 | rcu_read_lock(); | |
221 | if (afs_compare_volume_slists(volume, pvol)) | |
222 | ret = 1; | |
223 | rcu_read_unlock(); | |
224 | } | |
225 | ||
cca37d45 DH |
226 | afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias); |
227 | afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias); | |
6ef350b1 DH |
228 | return ret; |
229 | } | |
230 | ||
231 | /* | |
232 | * Query the new cell for volumes we know exist in cells we're already using. | |
233 | */ | |
234 | static int afs_query_for_alias(struct afs_cell *cell, struct key *key) | |
235 | { | |
236 | struct afs_cell *p; | |
237 | ||
238 | _enter("%s", cell->name); | |
239 | ||
240 | if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) | |
241 | return -ERESTARTSYS; | |
242 | ||
243 | hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) { | |
244 | if (p == cell || p->alias_of) | |
245 | continue; | |
20325960 | 246 | if (RB_EMPTY_ROOT(&p->volumes)) |
6ef350b1 DH |
247 | continue; |
248 | if (p->root_volume) | |
249 | continue; /* Ignore cells that have a root.cell volume. */ | |
250 | afs_get_cell(p); | |
251 | mutex_unlock(&cell->net->proc_cells_lock); | |
252 | ||
253 | if (afs_query_for_alias_one(cell, key, p) != 0) | |
254 | goto is_alias; | |
255 | ||
256 | if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) { | |
257 | afs_put_cell(cell->net, p); | |
258 | return -ERESTARTSYS; | |
259 | } | |
260 | ||
261 | afs_put_cell(cell->net, p); | |
262 | } | |
263 | ||
264 | mutex_unlock(&cell->net->proc_cells_lock); | |
265 | _leave(" = 0"); | |
266 | return 0; | |
267 | ||
268 | is_alias: | |
269 | cell->alias_of = p; /* Transfer our ref */ | |
270 | return 1; | |
271 | } | |
272 | ||
6dfdf536 DH |
273 | /* |
274 | * Look up a VLDB record for a volume. | |
275 | */ | |
276 | static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key) | |
277 | { | |
278 | struct afs_vl_cursor vc; | |
279 | char *cell_name = ERR_PTR(-EDESTADDRREQ); | |
280 | bool skipped = false, not_skipped = false; | |
281 | int ret; | |
282 | ||
283 | if (!afs_begin_vlserver_operation(&vc, cell, key)) | |
284 | return ERR_PTR(-ERESTARTSYS); | |
285 | ||
286 | while (afs_select_vlserver(&vc)) { | |
287 | if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) { | |
288 | vc.ac.error = -EOPNOTSUPP; | |
289 | skipped = true; | |
290 | continue; | |
291 | } | |
292 | not_skipped = true; | |
293 | cell_name = afs_yfsvl_get_cell_name(&vc); | |
294 | } | |
295 | ||
296 | ret = afs_end_vlserver_operation(&vc); | |
297 | if (skipped && !not_skipped) | |
298 | ret = -EOPNOTSUPP; | |
299 | return ret < 0 ? ERR_PTR(ret) : cell_name; | |
300 | } | |
301 | ||
302 | static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key) | |
303 | { | |
304 | struct afs_cell *master; | |
305 | char *cell_name; | |
306 | ||
307 | cell_name = afs_vl_get_cell_name(cell, key); | |
308 | if (IS_ERR(cell_name)) | |
309 | return PTR_ERR(cell_name); | |
310 | ||
311 | if (strcmp(cell_name, cell->name) == 0) { | |
312 | kfree(cell_name); | |
313 | return 0; | |
314 | } | |
315 | ||
316 | master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name), | |
317 | NULL, false); | |
318 | kfree(cell_name); | |
319 | if (IS_ERR(master)) | |
320 | return PTR_ERR(master); | |
321 | ||
322 | cell->alias_of = master; /* Transfer our ref */ | |
323 | return 1; | |
324 | } | |
325 | ||
8a070a96 DH |
326 | static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key) |
327 | { | |
328 | struct afs_volume *root_volume; | |
6dfdf536 | 329 | int ret; |
8a070a96 DH |
330 | |
331 | _enter("%s", cell->name); | |
332 | ||
6dfdf536 DH |
333 | ret = yfs_check_canonical_cell_name(cell, key); |
334 | if (ret != -EOPNOTSUPP) | |
335 | return ret; | |
336 | ||
8a070a96 DH |
337 | /* Try and get the root.cell volume for comparison with other cells */ |
338 | root_volume = afs_sample_volume(cell, key, "root.cell", 9); | |
339 | if (!IS_ERR(root_volume)) { | |
340 | cell->root_volume = root_volume; | |
341 | return afs_compare_cell_roots(cell); | |
342 | } | |
343 | ||
344 | if (PTR_ERR(root_volume) != -ENOMEDIUM) | |
345 | return PTR_ERR(root_volume); | |
346 | ||
347 | /* Okay, this cell doesn't have an root.cell volume. We need to | |
348 | * locate some other random volume and use that to check. | |
349 | */ | |
6ef350b1 | 350 | return afs_query_for_alias(cell, key); |
8a070a96 DH |
351 | } |
352 | ||
353 | /* | |
354 | * Check to see if a new cell is an alias of a cell we already have. At this | |
355 | * point we have the cell's volume server list. | |
356 | * | |
357 | * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error | |
358 | * if we had problems gathering the data required. In the case the we did | |
359 | * detect an alias, cell->alias_of is set to point to the assumed master. | |
360 | */ | |
361 | int afs_cell_detect_alias(struct afs_cell *cell, struct key *key) | |
362 | { | |
363 | struct afs_net *net = cell->net; | |
364 | int ret; | |
365 | ||
366 | if (mutex_lock_interruptible(&net->cells_alias_lock) < 0) | |
367 | return -ERESTARTSYS; | |
368 | ||
369 | if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) { | |
370 | ret = afs_do_cell_detect_alias(cell, key); | |
371 | if (ret >= 0) | |
372 | clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags); | |
373 | } else { | |
374 | ret = cell->alias_of ? 1 : 0; | |
375 | } | |
376 | ||
377 | mutex_unlock(&net->cells_alias_lock); | |
378 | ||
379 | if (ret == 1) | |
380 | pr_notice("kAFS: Cell %s is an alias of %s\n", | |
381 | cell->name, cell->alias_of->name); | |
382 | return ret; | |
383 | } |