diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2009-08-19 18:12:27 -0400 |
---|---|---|
committer | Trond Myklebust <Trond.Myklebust@netapp.com> | 2009-08-19 18:22:15 -0400 |
commit | e571cbf1a4f8d8b6cfd4898df718dae84c75a8e1 (patch) | |
tree | 0fc9da9692a1e63cff03053fc87cc807fab5e266 /fs/nfs/dns_resolve.c | |
parent | 96c61cbd0f30496bfa57ed80f7131a57aea3e4de (diff) |
NFS: Add a dns resolver for use with NFSv4 referrals and migration
The NFSv4 and NFSv4.1 protocols both allow for the redirection of a client
from one server to another in order to support filesystem migration and
replication. For full protocol support, we need to add the ability to
convert a DNS host name into an IP address that we can feed to the RPC
client.
We'll reuse the sunrpc cache, now that it has been converted to work with
rpc_pipefs.
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/dns_resolve.c')
-rw-r--r-- | fs/nfs/dns_resolve.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/fs/nfs/dns_resolve.c b/fs/nfs/dns_resolve.c new file mode 100644 index 000000000000..f4d54ba97cc6 --- /dev/null +++ b/fs/nfs/dns_resolve.c | |||
@@ -0,0 +1,335 @@ | |||
1 | /* | ||
2 | * linux/fs/nfs/dns_resolve.c | ||
3 | * | ||
4 | * Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com> | ||
5 | * | ||
6 | * Resolves DNS hostnames into valid ip addresses | ||
7 | */ | ||
8 | |||
9 | #include <linux/hash.h> | ||
10 | #include <linux/string.h> | ||
11 | #include <linux/kmod.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/socket.h> | ||
14 | #include <linux/seq_file.h> | ||
15 | #include <linux/inet.h> | ||
16 | #include <linux/sunrpc/clnt.h> | ||
17 | #include <linux/sunrpc/cache.h> | ||
18 | #include <linux/sunrpc/svcauth.h> | ||
19 | |||
20 | #include "dns_resolve.h" | ||
21 | #include "cache_lib.h" | ||
22 | |||
23 | #define NFS_DNS_HASHBITS 4 | ||
24 | #define NFS_DNS_HASHTBL_SIZE (1 << NFS_DNS_HASHBITS) | ||
25 | |||
26 | static struct cache_head *nfs_dns_table[NFS_DNS_HASHTBL_SIZE]; | ||
27 | |||
28 | struct nfs_dns_ent { | ||
29 | struct cache_head h; | ||
30 | |||
31 | char *hostname; | ||
32 | size_t namelen; | ||
33 | |||
34 | struct sockaddr_storage addr; | ||
35 | size_t addrlen; | ||
36 | }; | ||
37 | |||
38 | |||
39 | static void nfs_dns_ent_init(struct cache_head *cnew, | ||
40 | struct cache_head *ckey) | ||
41 | { | ||
42 | struct nfs_dns_ent *new; | ||
43 | struct nfs_dns_ent *key; | ||
44 | |||
45 | new = container_of(cnew, struct nfs_dns_ent, h); | ||
46 | key = container_of(ckey, struct nfs_dns_ent, h); | ||
47 | |||
48 | kfree(new->hostname); | ||
49 | new->hostname = kstrndup(key->hostname, key->namelen, GFP_KERNEL); | ||
50 | if (new->hostname) { | ||
51 | new->namelen = key->namelen; | ||
52 | memcpy(&new->addr, &key->addr, key->addrlen); | ||
53 | new->addrlen = key->addrlen; | ||
54 | } else { | ||
55 | new->namelen = 0; | ||
56 | new->addrlen = 0; | ||
57 | } | ||
58 | } | ||
59 | |||
60 | static void nfs_dns_ent_put(struct kref *ref) | ||
61 | { | ||
62 | struct nfs_dns_ent *item; | ||
63 | |||
64 | item = container_of(ref, struct nfs_dns_ent, h.ref); | ||
65 | kfree(item->hostname); | ||
66 | kfree(item); | ||
67 | } | ||
68 | |||
69 | static struct cache_head *nfs_dns_ent_alloc(void) | ||
70 | { | ||
71 | struct nfs_dns_ent *item = kmalloc(sizeof(*item), GFP_KERNEL); | ||
72 | |||
73 | if (item != NULL) { | ||
74 | item->hostname = NULL; | ||
75 | item->namelen = 0; | ||
76 | item->addrlen = 0; | ||
77 | return &item->h; | ||
78 | } | ||
79 | return NULL; | ||
80 | }; | ||
81 | |||
82 | static unsigned int nfs_dns_hash(const struct nfs_dns_ent *key) | ||
83 | { | ||
84 | return hash_str(key->hostname, NFS_DNS_HASHBITS); | ||
85 | } | ||
86 | |||
87 | static void nfs_dns_request(struct cache_detail *cd, | ||
88 | struct cache_head *ch, | ||
89 | char **bpp, int *blen) | ||
90 | { | ||
91 | struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h); | ||
92 | |||
93 | qword_add(bpp, blen, key->hostname); | ||
94 | (*bpp)[-1] = '\n'; | ||
95 | } | ||
96 | |||
97 | static int nfs_dns_upcall(struct cache_detail *cd, | ||
98 | struct cache_head *ch) | ||
99 | { | ||
100 | struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h); | ||
101 | int ret; | ||
102 | |||
103 | ret = nfs_cache_upcall(cd, key->hostname); | ||
104 | if (ret) | ||
105 | ret = sunrpc_cache_pipe_upcall(cd, ch, nfs_dns_request); | ||
106 | return ret; | ||
107 | } | ||
108 | |||
109 | static int nfs_dns_match(struct cache_head *ca, | ||
110 | struct cache_head *cb) | ||
111 | { | ||
112 | struct nfs_dns_ent *a; | ||
113 | struct nfs_dns_ent *b; | ||
114 | |||
115 | a = container_of(ca, struct nfs_dns_ent, h); | ||
116 | b = container_of(cb, struct nfs_dns_ent, h); | ||
117 | |||
118 | if (a->namelen == 0 || a->namelen != b->namelen) | ||
119 | return 0; | ||
120 | return memcmp(a->hostname, b->hostname, a->namelen) == 0; | ||
121 | } | ||
122 | |||
123 | static int nfs_dns_show(struct seq_file *m, struct cache_detail *cd, | ||
124 | struct cache_head *h) | ||
125 | { | ||
126 | struct nfs_dns_ent *item; | ||
127 | long ttl; | ||
128 | |||
129 | if (h == NULL) { | ||
130 | seq_puts(m, "# ip address hostname ttl\n"); | ||
131 | return 0; | ||
132 | } | ||
133 | item = container_of(h, struct nfs_dns_ent, h); | ||
134 | ttl = (long)item->h.expiry_time - (long)get_seconds(); | ||
135 | if (ttl < 0) | ||
136 | ttl = 0; | ||
137 | |||
138 | if (!test_bit(CACHE_NEGATIVE, &h->flags)) { | ||
139 | char buf[INET6_ADDRSTRLEN+IPV6_SCOPE_ID_LEN+1]; | ||
140 | |||
141 | rpc_ntop((struct sockaddr *)&item->addr, buf, sizeof(buf)); | ||
142 | seq_printf(m, "%15s ", buf); | ||
143 | } else | ||
144 | seq_puts(m, "<none> "); | ||
145 | seq_printf(m, "%15s %ld\n", item->hostname, ttl); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | struct nfs_dns_ent *nfs_dns_lookup(struct cache_detail *cd, | ||
150 | struct nfs_dns_ent *key) | ||
151 | { | ||
152 | struct cache_head *ch; | ||
153 | |||
154 | ch = sunrpc_cache_lookup(cd, | ||
155 | &key->h, | ||
156 | nfs_dns_hash(key)); | ||
157 | if (!ch) | ||
158 | return NULL; | ||
159 | return container_of(ch, struct nfs_dns_ent, h); | ||
160 | } | ||
161 | |||
162 | struct nfs_dns_ent *nfs_dns_update(struct cache_detail *cd, | ||
163 | struct nfs_dns_ent *new, | ||
164 | struct nfs_dns_ent *key) | ||
165 | { | ||
166 | struct cache_head *ch; | ||
167 | |||
168 | ch = sunrpc_cache_update(cd, | ||
169 | &new->h, &key->h, | ||
170 | nfs_dns_hash(key)); | ||
171 | if (!ch) | ||
172 | return NULL; | ||
173 | return container_of(ch, struct nfs_dns_ent, h); | ||
174 | } | ||
175 | |||
176 | static int nfs_dns_parse(struct cache_detail *cd, char *buf, int buflen) | ||
177 | { | ||
178 | char buf1[NFS_DNS_HOSTNAME_MAXLEN+1]; | ||
179 | struct nfs_dns_ent key, *item; | ||
180 | unsigned long ttl; | ||
181 | ssize_t len; | ||
182 | int ret = -EINVAL; | ||
183 | |||
184 | if (buf[buflen-1] != '\n') | ||
185 | goto out; | ||
186 | buf[buflen-1] = '\0'; | ||
187 | |||
188 | len = qword_get(&buf, buf1, sizeof(buf1)); | ||
189 | if (len <= 0) | ||
190 | goto out; | ||
191 | key.addrlen = rpc_pton(buf1, len, | ||
192 | (struct sockaddr *)&key.addr, | ||
193 | sizeof(key.addr)); | ||
194 | |||
195 | len = qword_get(&buf, buf1, sizeof(buf1)); | ||
196 | if (len <= 0) | ||
197 | goto out; | ||
198 | |||
199 | key.hostname = buf1; | ||
200 | key.namelen = len; | ||
201 | memset(&key.h, 0, sizeof(key.h)); | ||
202 | |||
203 | ttl = get_expiry(&buf); | ||
204 | if (ttl == 0) | ||
205 | goto out; | ||
206 | key.h.expiry_time = ttl + get_seconds(); | ||
207 | |||
208 | ret = -ENOMEM; | ||
209 | item = nfs_dns_lookup(cd, &key); | ||
210 | if (item == NULL) | ||
211 | goto out; | ||
212 | |||
213 | if (key.addrlen == 0) | ||
214 | set_bit(CACHE_NEGATIVE, &key.h.flags); | ||
215 | |||
216 | item = nfs_dns_update(cd, &key, item); | ||
217 | if (item == NULL) | ||
218 | goto out; | ||
219 | |||
220 | ret = 0; | ||
221 | cache_put(&item->h, cd); | ||
222 | out: | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | static struct cache_detail nfs_dns_resolve = { | ||
227 | .owner = THIS_MODULE, | ||
228 | .hash_size = NFS_DNS_HASHTBL_SIZE, | ||
229 | .hash_table = nfs_dns_table, | ||
230 | .name = "dns_resolve", | ||
231 | .cache_put = nfs_dns_ent_put, | ||
232 | .cache_upcall = nfs_dns_upcall, | ||
233 | .cache_parse = nfs_dns_parse, | ||
234 | .cache_show = nfs_dns_show, | ||
235 | .match = nfs_dns_match, | ||
236 | .init = nfs_dns_ent_init, | ||
237 | .update = nfs_dns_ent_init, | ||
238 | .alloc = nfs_dns_ent_alloc, | ||
239 | }; | ||
240 | |||
241 | static int do_cache_lookup(struct cache_detail *cd, | ||
242 | struct nfs_dns_ent *key, | ||
243 | struct nfs_dns_ent **item, | ||
244 | struct nfs_cache_defer_req *dreq) | ||
245 | { | ||
246 | int ret = -ENOMEM; | ||
247 | |||
248 | *item = nfs_dns_lookup(cd, key); | ||
249 | if (*item) { | ||
250 | ret = cache_check(cd, &(*item)->h, &dreq->req); | ||
251 | if (ret) | ||
252 | *item = NULL; | ||
253 | } | ||
254 | return ret; | ||
255 | } | ||
256 | |||
257 | static int do_cache_lookup_nowait(struct cache_detail *cd, | ||
258 | struct nfs_dns_ent *key, | ||
259 | struct nfs_dns_ent **item) | ||
260 | { | ||
261 | int ret = -ENOMEM; | ||
262 | |||
263 | *item = nfs_dns_lookup(cd, key); | ||
264 | if (!*item) | ||
265 | goto out_err; | ||
266 | ret = -ETIMEDOUT; | ||
267 | if (!test_bit(CACHE_VALID, &(*item)->h.flags) | ||
268 | || (*item)->h.expiry_time < get_seconds() | ||
269 | || cd->flush_time > (*item)->h.last_refresh) | ||
270 | goto out_put; | ||
271 | ret = -ENOENT; | ||
272 | if (test_bit(CACHE_NEGATIVE, &(*item)->h.flags)) | ||
273 | goto out_put; | ||
274 | return 0; | ||
275 | out_put: | ||
276 | cache_put(&(*item)->h, cd); | ||
277 | out_err: | ||
278 | *item = NULL; | ||
279 | return ret; | ||
280 | } | ||
281 | |||
282 | static int do_cache_lookup_wait(struct cache_detail *cd, | ||
283 | struct nfs_dns_ent *key, | ||
284 | struct nfs_dns_ent **item) | ||
285 | { | ||
286 | struct nfs_cache_defer_req *dreq; | ||
287 | int ret = -ENOMEM; | ||
288 | |||
289 | dreq = nfs_cache_defer_req_alloc(); | ||
290 | if (!dreq) | ||
291 | goto out; | ||
292 | ret = do_cache_lookup(cd, key, item, dreq); | ||
293 | if (ret == -EAGAIN) { | ||
294 | ret = nfs_cache_wait_for_upcall(dreq); | ||
295 | if (!ret) | ||
296 | ret = do_cache_lookup_nowait(cd, key, item); | ||
297 | } | ||
298 | nfs_cache_defer_req_put(dreq); | ||
299 | out: | ||
300 | return ret; | ||
301 | } | ||
302 | |||
303 | ssize_t nfs_dns_resolve_name(char *name, size_t namelen, | ||
304 | struct sockaddr *sa, size_t salen) | ||
305 | { | ||
306 | struct nfs_dns_ent key = { | ||
307 | .hostname = name, | ||
308 | .namelen = namelen, | ||
309 | }; | ||
310 | struct nfs_dns_ent *item = NULL; | ||
311 | ssize_t ret; | ||
312 | |||
313 | ret = do_cache_lookup_wait(&nfs_dns_resolve, &key, &item); | ||
314 | if (ret == 0) { | ||
315 | if (salen >= item->addrlen) { | ||
316 | memcpy(sa, &item->addr, item->addrlen); | ||
317 | ret = item->addrlen; | ||
318 | } else | ||
319 | ret = -EOVERFLOW; | ||
320 | cache_put(&item->h, &nfs_dns_resolve); | ||
321 | } else if (ret == -ENOENT) | ||
322 | ret = -ESRCH; | ||
323 | return ret; | ||
324 | } | ||
325 | |||
326 | int nfs_dns_resolver_init(void) | ||
327 | { | ||
328 | return nfs_cache_register(&nfs_dns_resolve); | ||
329 | } | ||
330 | |||
331 | void nfs_dns_resolver_destroy(void) | ||
332 | { | ||
333 | nfs_cache_unregister(&nfs_dns_resolve); | ||
334 | } | ||
335 | |||