1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Witness Service client for CIFS |
4 | * |
5 | * Copyright (c) 2020 Samuel Cabrero <scabrero@suse.de> |
6 | */ |
7 | |
8 | #include <linux/kref.h> |
9 | #include <net/genetlink.h> |
10 | #include <uapi/linux/cifs/cifs_netlink.h> |
11 | |
12 | #include "cifs_swn.h" |
13 | #include "cifsglob.h" |
14 | #include "cifsproto.h" |
15 | #include "fscache.h" |
16 | #include "cifs_debug.h" |
17 | #include "netlink.h" |
18 | |
19 | static DEFINE_IDR(cifs_swnreg_idr); |
20 | static DEFINE_MUTEX(cifs_swnreg_idr_mutex); |
21 | |
22 | struct cifs_swn_reg { |
23 | int id; |
24 | struct kref ref_count; |
25 | |
26 | const char *net_name; |
27 | const char *share_name; |
28 | bool net_name_notify; |
29 | bool share_name_notify; |
30 | bool ip_notify; |
31 | |
32 | struct cifs_tcon *tcon; |
33 | }; |
34 | |
35 | static int cifs_swn_auth_info_krb(struct cifs_tcon *tcon, struct sk_buff *skb) |
36 | { |
37 | int ret; |
38 | |
39 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_KRB_AUTH); |
40 | if (ret < 0) |
41 | return ret; |
42 | |
43 | return 0; |
44 | } |
45 | |
46 | static int cifs_swn_auth_info_ntlm(struct cifs_tcon *tcon, struct sk_buff *skb) |
47 | { |
48 | int ret; |
49 | |
50 | if (tcon->ses->user_name != NULL) { |
51 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_USER_NAME, str: tcon->ses->user_name); |
52 | if (ret < 0) |
53 | return ret; |
54 | } |
55 | |
56 | if (tcon->ses->password != NULL) { |
57 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_PASSWORD, str: tcon->ses->password); |
58 | if (ret < 0) |
59 | return ret; |
60 | } |
61 | |
62 | if (tcon->ses->domainName != NULL) { |
63 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_DOMAIN_NAME, str: tcon->ses->domainName); |
64 | if (ret < 0) |
65 | return ret; |
66 | } |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | /* |
72 | * Sends a register message to the userspace daemon based on the registration. |
73 | * The authentication information to connect to the witness service is bundled |
74 | * into the message. |
75 | */ |
76 | static int cifs_swn_send_register_message(struct cifs_swn_reg *swnreg) |
77 | { |
78 | struct sk_buff *skb; |
79 | struct genlmsghdr *hdr; |
80 | enum securityEnum authtype; |
81 | struct sockaddr_storage *addr; |
82 | int ret; |
83 | |
84 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
85 | if (skb == NULL) { |
86 | ret = -ENOMEM; |
87 | goto fail; |
88 | } |
89 | |
90 | hdr = genlmsg_put(skb, portid: 0, seq: 0, family: &cifs_genl_family, flags: 0, cmd: CIFS_GENL_CMD_SWN_REGISTER); |
91 | if (hdr == NULL) { |
92 | ret = -ENOMEM; |
93 | goto nlmsg_fail; |
94 | } |
95 | |
96 | ret = nla_put_u32(skb, attrtype: CIFS_GENL_ATTR_SWN_REGISTRATION_ID, value: swnreg->id); |
97 | if (ret < 0) |
98 | goto nlmsg_fail; |
99 | |
100 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_NET_NAME, str: swnreg->net_name); |
101 | if (ret < 0) |
102 | goto nlmsg_fail; |
103 | |
104 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_SHARE_NAME, str: swnreg->share_name); |
105 | if (ret < 0) |
106 | goto nlmsg_fail; |
107 | |
108 | /* |
109 | * If there is an address stored use it instead of the server address, because we are |
110 | * in the process of reconnecting to it after a share has been moved or we have been |
111 | * told to switch to it (client move message). In these cases we unregister from the |
112 | * server address and register to the new address when we receive the notification. |
113 | */ |
114 | if (swnreg->tcon->ses->server->use_swn_dstaddr) |
115 | addr = &swnreg->tcon->ses->server->swn_dstaddr; |
116 | else |
117 | addr = &swnreg->tcon->ses->server->dstaddr; |
118 | |
119 | ret = nla_put(skb, attrtype: CIFS_GENL_ATTR_SWN_IP, attrlen: sizeof(struct sockaddr_storage), data: addr); |
120 | if (ret < 0) |
121 | goto nlmsg_fail; |
122 | |
123 | if (swnreg->net_name_notify) { |
124 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY); |
125 | if (ret < 0) |
126 | goto nlmsg_fail; |
127 | } |
128 | |
129 | if (swnreg->share_name_notify) { |
130 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY); |
131 | if (ret < 0) |
132 | goto nlmsg_fail; |
133 | } |
134 | |
135 | if (swnreg->ip_notify) { |
136 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_IP_NOTIFY); |
137 | if (ret < 0) |
138 | goto nlmsg_fail; |
139 | } |
140 | |
141 | authtype = cifs_select_sectype(swnreg->tcon->ses->server, swnreg->tcon->ses->sectype); |
142 | switch (authtype) { |
143 | case Kerberos: |
144 | ret = cifs_swn_auth_info_krb(tcon: swnreg->tcon, skb); |
145 | if (ret < 0) { |
146 | cifs_dbg(VFS, "%s: Failed to get kerberos auth info: %d\n" , __func__, ret); |
147 | goto nlmsg_fail; |
148 | } |
149 | break; |
150 | case NTLMv2: |
151 | case RawNTLMSSP: |
152 | ret = cifs_swn_auth_info_ntlm(tcon: swnreg->tcon, skb); |
153 | if (ret < 0) { |
154 | cifs_dbg(VFS, "%s: Failed to get NTLM auth info: %d\n" , __func__, ret); |
155 | goto nlmsg_fail; |
156 | } |
157 | break; |
158 | default: |
159 | cifs_dbg(VFS, "%s: secType %d not supported!\n" , __func__, authtype); |
160 | ret = -EINVAL; |
161 | goto nlmsg_fail; |
162 | } |
163 | |
164 | genlmsg_end(skb, hdr); |
165 | genlmsg_multicast(family: &cifs_genl_family, skb, portid: 0, group: CIFS_GENL_MCGRP_SWN, GFP_ATOMIC); |
166 | |
167 | cifs_dbg(FYI, "%s: Message to register for network name %s with id %d sent\n" , __func__, |
168 | swnreg->net_name, swnreg->id); |
169 | |
170 | return 0; |
171 | |
172 | nlmsg_fail: |
173 | genlmsg_cancel(skb, hdr); |
174 | nlmsg_free(skb); |
175 | fail: |
176 | return ret; |
177 | } |
178 | |
179 | /* |
180 | * Sends an uregister message to the userspace daemon based on the registration |
181 | */ |
182 | static int cifs_swn_send_unregister_message(struct cifs_swn_reg *swnreg) |
183 | { |
184 | struct sk_buff *skb; |
185 | struct genlmsghdr *hdr; |
186 | int ret; |
187 | |
188 | skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); |
189 | if (skb == NULL) |
190 | return -ENOMEM; |
191 | |
192 | hdr = genlmsg_put(skb, portid: 0, seq: 0, family: &cifs_genl_family, flags: 0, cmd: CIFS_GENL_CMD_SWN_UNREGISTER); |
193 | if (hdr == NULL) { |
194 | ret = -ENOMEM; |
195 | goto nlmsg_fail; |
196 | } |
197 | |
198 | ret = nla_put_u32(skb, attrtype: CIFS_GENL_ATTR_SWN_REGISTRATION_ID, value: swnreg->id); |
199 | if (ret < 0) |
200 | goto nlmsg_fail; |
201 | |
202 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_NET_NAME, str: swnreg->net_name); |
203 | if (ret < 0) |
204 | goto nlmsg_fail; |
205 | |
206 | ret = nla_put_string(skb, attrtype: CIFS_GENL_ATTR_SWN_SHARE_NAME, str: swnreg->share_name); |
207 | if (ret < 0) |
208 | goto nlmsg_fail; |
209 | |
210 | ret = nla_put(skb, attrtype: CIFS_GENL_ATTR_SWN_IP, attrlen: sizeof(struct sockaddr_storage), |
211 | data: &swnreg->tcon->ses->server->dstaddr); |
212 | if (ret < 0) |
213 | goto nlmsg_fail; |
214 | |
215 | if (swnreg->net_name_notify) { |
216 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_NET_NAME_NOTIFY); |
217 | if (ret < 0) |
218 | goto nlmsg_fail; |
219 | } |
220 | |
221 | if (swnreg->share_name_notify) { |
222 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_SHARE_NAME_NOTIFY); |
223 | if (ret < 0) |
224 | goto nlmsg_fail; |
225 | } |
226 | |
227 | if (swnreg->ip_notify) { |
228 | ret = nla_put_flag(skb, attrtype: CIFS_GENL_ATTR_SWN_IP_NOTIFY); |
229 | if (ret < 0) |
230 | goto nlmsg_fail; |
231 | } |
232 | |
233 | genlmsg_end(skb, hdr); |
234 | genlmsg_multicast(family: &cifs_genl_family, skb, portid: 0, group: CIFS_GENL_MCGRP_SWN, GFP_ATOMIC); |
235 | |
236 | cifs_dbg(FYI, "%s: Message to unregister for network name %s with id %d sent\n" , __func__, |
237 | swnreg->net_name, swnreg->id); |
238 | |
239 | return 0; |
240 | |
241 | nlmsg_fail: |
242 | genlmsg_cancel(skb, hdr); |
243 | nlmsg_free(skb); |
244 | return ret; |
245 | } |
246 | |
247 | /* |
248 | * Try to find a matching registration for the tcon's server name and share name. |
249 | * Calls to this function must be protected by cifs_swnreg_idr_mutex. |
250 | * TODO Try to avoid memory allocations |
251 | */ |
252 | static struct cifs_swn_reg *cifs_find_swn_reg(struct cifs_tcon *tcon) |
253 | { |
254 | struct cifs_swn_reg *swnreg; |
255 | int id; |
256 | const char *share_name; |
257 | const char *net_name; |
258 | |
259 | net_name = extract_hostname(unc: tcon->tree_name); |
260 | if (IS_ERR(ptr: net_name)) { |
261 | int ret; |
262 | |
263 | ret = PTR_ERR(ptr: net_name); |
264 | cifs_dbg(VFS, "%s: failed to extract host name from target '%s': %d\n" , |
265 | __func__, tcon->tree_name, ret); |
266 | return ERR_PTR(error: -EINVAL); |
267 | } |
268 | |
269 | share_name = extract_sharename(unc: tcon->tree_name); |
270 | if (IS_ERR(ptr: share_name)) { |
271 | int ret; |
272 | |
273 | ret = PTR_ERR(ptr: share_name); |
274 | cifs_dbg(VFS, "%s: failed to extract share name from target '%s': %d\n" , |
275 | __func__, tcon->tree_name, ret); |
276 | kfree(objp: net_name); |
277 | return ERR_PTR(error: -EINVAL); |
278 | } |
279 | |
280 | idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { |
281 | if (strcasecmp(s1: swnreg->net_name, s2: net_name) != 0 |
282 | || strcasecmp(s1: swnreg->share_name, s2: share_name) != 0) { |
283 | continue; |
284 | } |
285 | |
286 | cifs_dbg(FYI, "Existing swn registration for %s:%s found\n" , swnreg->net_name, |
287 | swnreg->share_name); |
288 | |
289 | kfree(objp: net_name); |
290 | kfree(objp: share_name); |
291 | |
292 | return swnreg; |
293 | } |
294 | |
295 | kfree(objp: net_name); |
296 | kfree(objp: share_name); |
297 | |
298 | return ERR_PTR(error: -EEXIST); |
299 | } |
300 | |
301 | /* |
302 | * Get a registration for the tcon's server and share name, allocating a new one if it does not |
303 | * exists |
304 | */ |
305 | static struct cifs_swn_reg *cifs_get_swn_reg(struct cifs_tcon *tcon) |
306 | { |
307 | struct cifs_swn_reg *reg = NULL; |
308 | int ret; |
309 | |
310 | mutex_lock(&cifs_swnreg_idr_mutex); |
311 | |
312 | /* Check if we are already registered for this network and share names */ |
313 | reg = cifs_find_swn_reg(tcon); |
314 | if (!IS_ERR(ptr: reg)) { |
315 | kref_get(kref: ®->ref_count); |
316 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
317 | return reg; |
318 | } else if (PTR_ERR(ptr: reg) != -EEXIST) { |
319 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
320 | return reg; |
321 | } |
322 | |
323 | reg = kmalloc(size: sizeof(struct cifs_swn_reg), GFP_ATOMIC); |
324 | if (reg == NULL) { |
325 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
326 | return ERR_PTR(error: -ENOMEM); |
327 | } |
328 | |
329 | kref_init(kref: ®->ref_count); |
330 | |
331 | reg->id = idr_alloc(&cifs_swnreg_idr, ptr: reg, start: 1, end: 0, GFP_ATOMIC); |
332 | if (reg->id < 0) { |
333 | cifs_dbg(FYI, "%s: failed to allocate registration id\n" , __func__); |
334 | ret = reg->id; |
335 | goto fail; |
336 | } |
337 | |
338 | reg->net_name = extract_hostname(unc: tcon->tree_name); |
339 | if (IS_ERR(ptr: reg->net_name)) { |
340 | ret = PTR_ERR(ptr: reg->net_name); |
341 | cifs_dbg(VFS, "%s: failed to extract host name from target: %d\n" , __func__, ret); |
342 | goto fail_idr; |
343 | } |
344 | |
345 | reg->share_name = extract_sharename(unc: tcon->tree_name); |
346 | if (IS_ERR(ptr: reg->share_name)) { |
347 | ret = PTR_ERR(ptr: reg->share_name); |
348 | cifs_dbg(VFS, "%s: failed to extract share name from target: %d\n" , __func__, ret); |
349 | goto fail_net_name; |
350 | } |
351 | |
352 | reg->net_name_notify = true; |
353 | reg->share_name_notify = true; |
354 | reg->ip_notify = (tcon->capabilities & SMB2_SHARE_CAP_SCALEOUT); |
355 | |
356 | reg->tcon = tcon; |
357 | |
358 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
359 | |
360 | return reg; |
361 | |
362 | fail_net_name: |
363 | kfree(objp: reg->net_name); |
364 | fail_idr: |
365 | idr_remove(&cifs_swnreg_idr, id: reg->id); |
366 | fail: |
367 | kfree(objp: reg); |
368 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
369 | return ERR_PTR(error: ret); |
370 | } |
371 | |
372 | static void cifs_swn_reg_release(struct kref *ref) |
373 | { |
374 | struct cifs_swn_reg *swnreg = container_of(ref, struct cifs_swn_reg, ref_count); |
375 | int ret; |
376 | |
377 | ret = cifs_swn_send_unregister_message(swnreg); |
378 | if (ret < 0) |
379 | cifs_dbg(VFS, "%s: Failed to send unregister message: %d\n" , __func__, ret); |
380 | |
381 | idr_remove(&cifs_swnreg_idr, id: swnreg->id); |
382 | kfree(objp: swnreg->net_name); |
383 | kfree(objp: swnreg->share_name); |
384 | kfree(objp: swnreg); |
385 | } |
386 | |
387 | static void cifs_put_swn_reg(struct cifs_swn_reg *swnreg) |
388 | { |
389 | mutex_lock(&cifs_swnreg_idr_mutex); |
390 | kref_put(kref: &swnreg->ref_count, release: cifs_swn_reg_release); |
391 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
392 | } |
393 | |
394 | static int cifs_swn_resource_state_changed(struct cifs_swn_reg *swnreg, const char *name, int state) |
395 | { |
396 | switch (state) { |
397 | case CIFS_SWN_RESOURCE_STATE_UNAVAILABLE: |
398 | cifs_dbg(FYI, "%s: resource name '%s' become unavailable\n" , __func__, name); |
399 | cifs_signal_cifsd_for_reconnect(server: swnreg->tcon->ses->server, all_channels: true); |
400 | break; |
401 | case CIFS_SWN_RESOURCE_STATE_AVAILABLE: |
402 | cifs_dbg(FYI, "%s: resource name '%s' become available\n" , __func__, name); |
403 | cifs_signal_cifsd_for_reconnect(server: swnreg->tcon->ses->server, all_channels: true); |
404 | break; |
405 | case CIFS_SWN_RESOURCE_STATE_UNKNOWN: |
406 | cifs_dbg(FYI, "%s: resource name '%s' changed to unknown state\n" , __func__, name); |
407 | break; |
408 | } |
409 | return 0; |
410 | } |
411 | |
412 | static bool cifs_sockaddr_equal(struct sockaddr_storage *addr1, struct sockaddr_storage *addr2) |
413 | { |
414 | if (addr1->ss_family != addr2->ss_family) |
415 | return false; |
416 | |
417 | if (addr1->ss_family == AF_INET) { |
418 | return (memcmp(p: &((const struct sockaddr_in *)addr1)->sin_addr, |
419 | q: &((const struct sockaddr_in *)addr2)->sin_addr, |
420 | size: sizeof(struct in_addr)) == 0); |
421 | } |
422 | |
423 | if (addr1->ss_family == AF_INET6) { |
424 | return (memcmp(p: &((const struct sockaddr_in6 *)addr1)->sin6_addr, |
425 | q: &((const struct sockaddr_in6 *)addr2)->sin6_addr, |
426 | size: sizeof(struct in6_addr)) == 0); |
427 | } |
428 | |
429 | return false; |
430 | } |
431 | |
432 | static int cifs_swn_store_swn_addr(const struct sockaddr_storage *new, |
433 | const struct sockaddr_storage *old, |
434 | struct sockaddr_storage *dst) |
435 | { |
436 | __be16 port = cpu_to_be16(CIFS_PORT); |
437 | |
438 | if (old->ss_family == AF_INET) { |
439 | struct sockaddr_in *ipv4 = (struct sockaddr_in *)old; |
440 | |
441 | port = ipv4->sin_port; |
442 | } else if (old->ss_family == AF_INET6) { |
443 | struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)old; |
444 | |
445 | port = ipv6->sin6_port; |
446 | } |
447 | |
448 | if (new->ss_family == AF_INET) { |
449 | struct sockaddr_in *ipv4 = (struct sockaddr_in *)new; |
450 | |
451 | ipv4->sin_port = port; |
452 | } else if (new->ss_family == AF_INET6) { |
453 | struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)new; |
454 | |
455 | ipv6->sin6_port = port; |
456 | } |
457 | |
458 | *dst = *new; |
459 | |
460 | return 0; |
461 | } |
462 | |
463 | static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *addr) |
464 | { |
465 | int ret = 0; |
466 | |
467 | /* Store the reconnect address */ |
468 | cifs_server_lock(server: tcon->ses->server); |
469 | if (cifs_sockaddr_equal(addr1: &tcon->ses->server->dstaddr, addr2: addr)) |
470 | goto unlock; |
471 | |
472 | ret = cifs_swn_store_swn_addr(new: addr, old: &tcon->ses->server->dstaddr, |
473 | dst: &tcon->ses->server->swn_dstaddr); |
474 | if (ret < 0) { |
475 | cifs_dbg(VFS, "%s: failed to store address: %d\n" , __func__, ret); |
476 | goto unlock; |
477 | } |
478 | tcon->ses->server->use_swn_dstaddr = true; |
479 | |
480 | /* |
481 | * Unregister to stop receiving notifications for the old IP address. |
482 | */ |
483 | ret = cifs_swn_unregister(tcon); |
484 | if (ret < 0) { |
485 | cifs_dbg(VFS, "%s: Failed to unregister for witness notifications: %d\n" , |
486 | __func__, ret); |
487 | goto unlock; |
488 | } |
489 | |
490 | /* |
491 | * And register to receive notifications for the new IP address now that we have |
492 | * stored the new address. |
493 | */ |
494 | ret = cifs_swn_register(tcon); |
495 | if (ret < 0) { |
496 | cifs_dbg(VFS, "%s: Failed to register for witness notifications: %d\n" , |
497 | __func__, ret); |
498 | goto unlock; |
499 | } |
500 | |
501 | cifs_signal_cifsd_for_reconnect(server: tcon->ses->server, all_channels: false); |
502 | |
503 | unlock: |
504 | cifs_server_unlock(server: tcon->ses->server); |
505 | |
506 | return ret; |
507 | } |
508 | |
509 | static int cifs_swn_client_move(struct cifs_swn_reg *swnreg, struct sockaddr_storage *addr) |
510 | { |
511 | struct sockaddr_in *ipv4 = (struct sockaddr_in *)addr; |
512 | struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)addr; |
513 | |
514 | if (addr->ss_family == AF_INET) |
515 | cifs_dbg(FYI, "%s: move to %pI4\n" , __func__, &ipv4->sin_addr); |
516 | else if (addr->ss_family == AF_INET6) |
517 | cifs_dbg(FYI, "%s: move to %pI6\n" , __func__, &ipv6->sin6_addr); |
518 | |
519 | return cifs_swn_reconnect(tcon: swnreg->tcon, addr); |
520 | } |
521 | |
522 | int cifs_swn_notify(struct sk_buff *skb, struct genl_info *info) |
523 | { |
524 | struct cifs_swn_reg *swnreg; |
525 | char name[256]; |
526 | int type; |
527 | |
528 | if (info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]) { |
529 | int swnreg_id; |
530 | |
531 | swnreg_id = nla_get_u32(nla: info->attrs[CIFS_GENL_ATTR_SWN_REGISTRATION_ID]); |
532 | mutex_lock(&cifs_swnreg_idr_mutex); |
533 | swnreg = idr_find(&cifs_swnreg_idr, id: swnreg_id); |
534 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
535 | if (swnreg == NULL) { |
536 | cifs_dbg(FYI, "%s: registration id %d not found\n" , __func__, swnreg_id); |
537 | return -EINVAL; |
538 | } |
539 | } else { |
540 | cifs_dbg(FYI, "%s: missing registration id attribute\n" , __func__); |
541 | return -EINVAL; |
542 | } |
543 | |
544 | if (info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]) { |
545 | type = nla_get_u32(nla: info->attrs[CIFS_GENL_ATTR_SWN_NOTIFICATION_TYPE]); |
546 | } else { |
547 | cifs_dbg(FYI, "%s: missing notification type attribute\n" , __func__); |
548 | return -EINVAL; |
549 | } |
550 | |
551 | switch (type) { |
552 | case CIFS_SWN_NOTIFICATION_RESOURCE_CHANGE: { |
553 | int state; |
554 | |
555 | if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_NAME]) { |
556 | nla_strscpy(dst: name, nla: info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_NAME], |
557 | dstsize: sizeof(name)); |
558 | } else { |
559 | cifs_dbg(FYI, "%s: missing resource name attribute\n" , __func__); |
560 | return -EINVAL; |
561 | } |
562 | if (info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]) { |
563 | state = nla_get_u32(nla: info->attrs[CIFS_GENL_ATTR_SWN_RESOURCE_STATE]); |
564 | } else { |
565 | cifs_dbg(FYI, "%s: missing resource state attribute\n" , __func__); |
566 | return -EINVAL; |
567 | } |
568 | return cifs_swn_resource_state_changed(swnreg, name, state); |
569 | } |
570 | case CIFS_SWN_NOTIFICATION_CLIENT_MOVE: { |
571 | struct sockaddr_storage addr; |
572 | |
573 | if (info->attrs[CIFS_GENL_ATTR_SWN_IP]) { |
574 | nla_memcpy(dest: &addr, src: info->attrs[CIFS_GENL_ATTR_SWN_IP], count: sizeof(addr)); |
575 | } else { |
576 | cifs_dbg(FYI, "%s: missing IP address attribute\n" , __func__); |
577 | return -EINVAL; |
578 | } |
579 | return cifs_swn_client_move(swnreg, addr: &addr); |
580 | } |
581 | default: |
582 | cifs_dbg(FYI, "%s: unknown notification type %d\n" , __func__, type); |
583 | break; |
584 | } |
585 | |
586 | return 0; |
587 | } |
588 | |
589 | int cifs_swn_register(struct cifs_tcon *tcon) |
590 | { |
591 | struct cifs_swn_reg *swnreg; |
592 | int ret; |
593 | |
594 | swnreg = cifs_get_swn_reg(tcon); |
595 | if (IS_ERR(ptr: swnreg)) |
596 | return PTR_ERR(ptr: swnreg); |
597 | |
598 | ret = cifs_swn_send_register_message(swnreg); |
599 | if (ret < 0) { |
600 | cifs_dbg(VFS, "%s: Failed to send swn register message: %d\n" , __func__, ret); |
601 | /* Do not put the swnreg or return error, the echo task will retry */ |
602 | } |
603 | |
604 | return 0; |
605 | } |
606 | |
607 | int cifs_swn_unregister(struct cifs_tcon *tcon) |
608 | { |
609 | struct cifs_swn_reg *swnreg; |
610 | |
611 | mutex_lock(&cifs_swnreg_idr_mutex); |
612 | |
613 | swnreg = cifs_find_swn_reg(tcon); |
614 | if (IS_ERR(ptr: swnreg)) { |
615 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
616 | return PTR_ERR(ptr: swnreg); |
617 | } |
618 | |
619 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
620 | |
621 | cifs_put_swn_reg(swnreg); |
622 | |
623 | return 0; |
624 | } |
625 | |
626 | void cifs_swn_dump(struct seq_file *m) |
627 | { |
628 | struct cifs_swn_reg *swnreg; |
629 | struct sockaddr_in *sa; |
630 | struct sockaddr_in6 *sa6; |
631 | int id; |
632 | |
633 | seq_puts(m, s: "Witness registrations:" ); |
634 | |
635 | mutex_lock(&cifs_swnreg_idr_mutex); |
636 | idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { |
637 | seq_printf(m, fmt: "\nId: %u Refs: %u Network name: '%s'%s Share name: '%s'%s Ip address: " , |
638 | id, kref_read(kref: &swnreg->ref_count), |
639 | swnreg->net_name, swnreg->net_name_notify ? "(y)" : "(n)" , |
640 | swnreg->share_name, swnreg->share_name_notify ? "(y)" : "(n)" ); |
641 | switch (swnreg->tcon->ses->server->dstaddr.ss_family) { |
642 | case AF_INET: |
643 | sa = (struct sockaddr_in *) &swnreg->tcon->ses->server->dstaddr; |
644 | seq_printf(m, fmt: "%pI4" , &sa->sin_addr.s_addr); |
645 | break; |
646 | case AF_INET6: |
647 | sa6 = (struct sockaddr_in6 *) &swnreg->tcon->ses->server->dstaddr; |
648 | seq_printf(m, fmt: "%pI6" , &sa6->sin6_addr.s6_addr); |
649 | if (sa6->sin6_scope_id) |
650 | seq_printf(m, fmt: "%%%u" , sa6->sin6_scope_id); |
651 | break; |
652 | default: |
653 | seq_puts(m, s: "(unknown)" ); |
654 | } |
655 | seq_printf(m, fmt: "%s" , swnreg->ip_notify ? "(y)" : "(n)" ); |
656 | } |
657 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
658 | seq_puts(m, s: "\n" ); |
659 | } |
660 | |
661 | void cifs_swn_check(void) |
662 | { |
663 | struct cifs_swn_reg *swnreg; |
664 | int id; |
665 | int ret; |
666 | |
667 | mutex_lock(&cifs_swnreg_idr_mutex); |
668 | idr_for_each_entry(&cifs_swnreg_idr, swnreg, id) { |
669 | ret = cifs_swn_send_register_message(swnreg); |
670 | if (ret < 0) |
671 | cifs_dbg(FYI, "%s: Failed to send register message: %d\n" , __func__, ret); |
672 | } |
673 | mutex_unlock(lock: &cifs_swnreg_idr_mutex); |
674 | } |
675 | |