|
| 1 | +/* Server address list management |
| 2 | + * |
| 3 | + * Copyright (C) 2017 Red Hat, Inc. All Rights Reserved. |
| 4 | + * Written by David Howells (dhowells@redhat.com) |
| 5 | + * |
| 6 | + * This program is free software; you can redistribute it and/or |
| 7 | + * modify it under the terms of the GNU General Public Licence |
| 8 | + * as published by the Free Software Foundation; either version |
| 9 | + * 2 of the Licence, or (at your option) any later version. |
| 10 | + */ |
| 11 | + |
| 12 | +#include <linux/slab.h> |
| 13 | +#include <linux/ctype.h> |
| 14 | +#include <linux/dns_resolver.h> |
| 15 | +#include <linux/inet.h> |
| 16 | +#include <keys/rxrpc-type.h> |
| 17 | +#include "internal.h" |
| 18 | +#include "afs_fs.h" |
| 19 | + |
| 20 | +#define AFS_MAX_ADDRESSES \ |
| 21 | + ((unsigned int)((PAGE_SIZE - sizeof(struct afs_addr_list)) / \ |
| 22 | + sizeof(struct sockaddr_rxrpc))) |
| 23 | + |
| 24 | +/* |
| 25 | + * Release an address list. |
| 26 | + */ |
| 27 | +void afs_put_addrlist(struct afs_addr_list *alist) |
| 28 | +{ |
| 29 | + if (alist && refcount_dec_and_test(&alist->usage)) |
| 30 | + call_rcu(&alist->rcu, (rcu_callback_t)kfree); |
| 31 | +} |
| 32 | + |
| 33 | +/* |
| 34 | + * Allocate an address list. |
| 35 | + */ |
| 36 | +struct afs_addr_list *afs_alloc_addrlist(unsigned int nr, |
| 37 | + unsigned short service, |
| 38 | + unsigned short port) |
| 39 | +{ |
| 40 | + struct afs_addr_list *alist; |
| 41 | + unsigned int i; |
| 42 | + |
| 43 | + _enter("%u,%u,%u", nr, service, port); |
| 44 | + |
| 45 | + alist = kzalloc(sizeof(*alist) + sizeof(alist->addrs[0]) * nr, |
| 46 | + GFP_KERNEL); |
| 47 | + if (!alist) |
| 48 | + return NULL; |
| 49 | + |
| 50 | + refcount_set(&alist->usage, 1); |
| 51 | + |
| 52 | + for (i = 0; i < nr; i++) { |
| 53 | + struct sockaddr_rxrpc *srx = &alist->addrs[i]; |
| 54 | + srx->srx_family = AF_RXRPC; |
| 55 | + srx->srx_service = service; |
| 56 | + srx->transport_type = SOCK_DGRAM; |
| 57 | + srx->transport_len = sizeof(srx->transport.sin6); |
| 58 | + srx->transport.sin6.sin6_family = AF_INET6; |
| 59 | + srx->transport.sin6.sin6_port = htons(port); |
| 60 | + } |
| 61 | + |
| 62 | + return alist; |
| 63 | +} |
| 64 | + |
| 65 | +/* |
| 66 | + * Parse a text string consisting of delimited addresses. |
| 67 | + */ |
| 68 | +struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len, |
| 69 | + char delim, |
| 70 | + unsigned short service, |
| 71 | + unsigned short port) |
| 72 | +{ |
| 73 | + struct afs_addr_list *alist; |
| 74 | + const char *p, *end = text + len; |
| 75 | + unsigned int nr = 0; |
| 76 | + |
| 77 | + _enter("%*.*s,%c", (int)len, (int)len, text, delim); |
| 78 | + |
| 79 | + if (!len) |
| 80 | + return ERR_PTR(-EDESTADDRREQ); |
| 81 | + |
| 82 | + if (delim == ':' && (memchr(text, ',', len) || !memchr(text, '.', len))) |
| 83 | + delim = ','; |
| 84 | + |
| 85 | + /* Count the addresses */ |
| 86 | + p = text; |
| 87 | + do { |
| 88 | + if (!*p) |
| 89 | + return ERR_PTR(-EINVAL); |
| 90 | + if (*p == delim) |
| 91 | + continue; |
| 92 | + nr++; |
| 93 | + if (*p == '[') { |
| 94 | + p++; |
| 95 | + if (p == end) |
| 96 | + return ERR_PTR(-EINVAL); |
| 97 | + p = memchr(p, ']', end - p); |
| 98 | + if (!p) |
| 99 | + return ERR_PTR(-EINVAL); |
| 100 | + p++; |
| 101 | + if (p >= end) |
| 102 | + break; |
| 103 | + } |
| 104 | + |
| 105 | + p = memchr(p, delim, end - p); |
| 106 | + if (!p) |
| 107 | + break; |
| 108 | + p++; |
| 109 | + } while (p < end); |
| 110 | + |
| 111 | + _debug("%u/%u addresses", nr, AFS_MAX_ADDRESSES); |
| 112 | + if (nr > AFS_MAX_ADDRESSES) |
| 113 | + nr = AFS_MAX_ADDRESSES; |
| 114 | + |
| 115 | + alist = afs_alloc_addrlist(nr, service, port); |
| 116 | + if (!alist) |
| 117 | + return ERR_PTR(-ENOMEM); |
| 118 | + |
| 119 | + /* Extract the addresses */ |
| 120 | + p = text; |
| 121 | + do { |
| 122 | + struct sockaddr_rxrpc *srx = &alist->addrs[alist->nr_addrs]; |
| 123 | + char tdelim = delim; |
| 124 | + |
| 125 | + if (*p == delim) { |
| 126 | + p++; |
| 127 | + continue; |
| 128 | + } |
| 129 | + |
| 130 | + if (*p == '[') { |
| 131 | + p++; |
| 132 | + tdelim = ']'; |
| 133 | + } |
| 134 | + |
| 135 | + if (in4_pton(p, end - p, |
| 136 | + (u8 *)&srx->transport.sin6.sin6_addr.s6_addr32[3], |
| 137 | + tdelim, &p)) { |
| 138 | + srx->transport.sin6.sin6_addr.s6_addr32[0] = 0; |
| 139 | + srx->transport.sin6.sin6_addr.s6_addr32[1] = 0; |
| 140 | + srx->transport.sin6.sin6_addr.s6_addr32[2] = htonl(0xffff); |
| 141 | + } else if (in6_pton(p, end - p, |
| 142 | + srx->transport.sin6.sin6_addr.s6_addr, |
| 143 | + tdelim, &p)) { |
| 144 | + /* Nothing to do */ |
| 145 | + } else { |
| 146 | + goto bad_address; |
| 147 | + } |
| 148 | + |
| 149 | + if (tdelim == ']') { |
| 150 | + if (p == end || *p != ']') |
| 151 | + goto bad_address; |
| 152 | + p++; |
| 153 | + } |
| 154 | + |
| 155 | + if (p < end) { |
| 156 | + if (*p == '+') { |
| 157 | + /* Port number specification "+1234" */ |
| 158 | + unsigned int xport = 0; |
| 159 | + p++; |
| 160 | + if (p >= end || !isdigit(*p)) |
| 161 | + goto bad_address; |
| 162 | + do { |
| 163 | + xport *= 10; |
| 164 | + xport += *p - '0'; |
| 165 | + if (xport > 65535) |
| 166 | + goto bad_address; |
| 167 | + p++; |
| 168 | + } while (p < end && isdigit(*p)); |
| 169 | + srx->transport.sin6.sin6_port = htons(xport); |
| 170 | + } else if (*p == delim) { |
| 171 | + p++; |
| 172 | + } else { |
| 173 | + goto bad_address; |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + alist->nr_addrs++; |
| 178 | + } while (p < end && alist->nr_addrs < AFS_MAX_ADDRESSES); |
| 179 | + |
| 180 | + _leave(" = [nr %u]", alist->nr_addrs); |
| 181 | + return alist; |
| 182 | + |
| 183 | +bad_address: |
| 184 | + kfree(alist); |
| 185 | + return ERR_PTR(-EINVAL); |
| 186 | +} |
| 187 | + |
| 188 | +/* |
| 189 | + * Compare old and new address lists to see if there's been any change. |
| 190 | + * - How to do this in better than O(Nlog(N)) time? |
| 191 | + * - We don't really want to sort the address list, but would rather take the |
| 192 | + * list as we got it so as not to undo record rotation by the DNS server. |
| 193 | + */ |
| 194 | +#if 0 |
| 195 | +static int afs_cmp_addr_list(const struct afs_addr_list *a1, |
| 196 | + const struct afs_addr_list *a2) |
| 197 | +{ |
| 198 | +} |
| 199 | +#endif |
| 200 | + |
| 201 | +/* |
| 202 | + * Perform a DNS query for VL servers and build a up an address list. |
| 203 | + */ |
| 204 | +struct afs_addr_list *afs_dns_query(struct afs_cell *cell, time64_t *_expiry) |
| 205 | +{ |
| 206 | + struct afs_addr_list *alist; |
| 207 | + char *vllist = NULL; |
| 208 | + int ret; |
| 209 | + |
| 210 | + _enter("%s", cell->name); |
| 211 | + |
| 212 | + ret = dns_query("afsdb", cell->name, cell->name_len, |
| 213 | + "ipv4", &vllist, _expiry); |
| 214 | + if (ret < 0) |
| 215 | + return ERR_PTR(ret); |
| 216 | + |
| 217 | + alist = afs_parse_text_addrs(vllist, strlen(vllist), ',', |
| 218 | + VL_SERVICE, AFS_VL_PORT); |
| 219 | + if (IS_ERR(alist)) { |
| 220 | + kfree(vllist); |
| 221 | + if (alist != ERR_PTR(-ENOMEM)) |
| 222 | + pr_err("Failed to parse DNS data\n"); |
| 223 | + return alist; |
| 224 | + } |
| 225 | + |
| 226 | + kfree(vllist); |
| 227 | + return alist; |
| 228 | +} |
| 229 | + |
| 230 | +/* |
| 231 | + * Get an address to try. |
| 232 | + */ |
| 233 | +bool afs_iterate_addresses(struct afs_addr_cursor *ac) |
| 234 | +{ |
| 235 | + _enter("%hu+%hd", ac->start, (short)ac->index); |
| 236 | + |
| 237 | + if (!ac->alist) |
| 238 | + return false; |
| 239 | + |
| 240 | + if (ac->begun) { |
| 241 | + ac->index++; |
| 242 | + if (ac->index == ac->alist->nr_addrs) |
| 243 | + ac->index = 0; |
| 244 | + |
| 245 | + if (ac->index == ac->start) { |
| 246 | + ac->error = -EDESTADDRREQ; |
| 247 | + return false; |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + ac->begun = true; |
| 252 | + ac->responded = false; |
| 253 | + ac->addr = &ac->alist->addrs[ac->index]; |
| 254 | + return true; |
| 255 | +} |
| 256 | + |
| 257 | +/* |
| 258 | + * Release an address list cursor. |
| 259 | + */ |
| 260 | +int afs_end_cursor(struct afs_addr_cursor *ac) |
| 261 | +{ |
| 262 | + if (ac->responded && ac->index != ac->start) |
| 263 | + WRITE_ONCE(ac->alist->index, ac->index); |
| 264 | + |
| 265 | + afs_put_addrlist(ac->alist); |
| 266 | + ac->alist = NULL; |
| 267 | + return ac->error; |
| 268 | +} |
| 269 | + |
| 270 | +/* |
| 271 | + * Set the address cursor for iterating over VL servers. |
| 272 | + */ |
| 273 | +int afs_set_vl_cursor(struct afs_addr_cursor *ac, struct afs_cell *cell) |
| 274 | +{ |
| 275 | + struct afs_addr_list *alist; |
| 276 | + int ret; |
| 277 | + |
| 278 | + if (!rcu_access_pointer(cell->vl_addrs)) { |
| 279 | + ret = wait_on_bit(&cell->flags, AFS_CELL_FL_NO_LOOKUP_YET, |
| 280 | + TASK_INTERRUPTIBLE); |
| 281 | + if (ret < 0) |
| 282 | + return ret; |
| 283 | + |
| 284 | + if (!rcu_access_pointer(cell->vl_addrs) && |
| 285 | + ktime_get_real_seconds() < cell->dns_expiry) |
| 286 | + return cell->error; |
| 287 | + } |
| 288 | + |
| 289 | + read_lock(&cell->vl_addrs_lock); |
| 290 | + alist = rcu_dereference_protected(cell->vl_addrs, |
| 291 | + lockdep_is_held(&cell->vl_addrs_lock)); |
| 292 | + if (alist->nr_addrs > 0) |
| 293 | + afs_get_addrlist(alist); |
| 294 | + else |
| 295 | + alist = NULL; |
| 296 | + read_unlock(&cell->vl_addrs_lock); |
| 297 | + |
| 298 | + if (!alist) |
| 299 | + return -EDESTADDRREQ; |
| 300 | + |
| 301 | + ac->alist = alist; |
| 302 | + ac->addr = NULL; |
| 303 | + ac->start = READ_ONCE(alist->index); |
| 304 | + ac->index = ac->start; |
| 305 | + ac->error = 0; |
| 306 | + ac->begun = false; |
| 307 | + return 0; |
| 308 | +} |
0 commit comments