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>tirimbino
parent
96c61cbd0f
commit
e571cbf1a4
@ -0,0 +1,98 @@ |
||||
|
||||
The NFS client |
||||
============== |
||||
|
||||
The NFS version 2 protocol was first documented in RFC1094 (March 1989). |
||||
Since then two more major releases of NFS have been published, with NFSv3 |
||||
being documented in RFC1813 (June 1995), and NFSv4 in RFC3530 (April |
||||
2003). |
||||
|
||||
The Linux NFS client currently supports all the above published versions, |
||||
and work is in progress on adding support for minor version 1 of the NFSv4 |
||||
protocol. |
||||
|
||||
The purpose of this document is to provide information on some of the |
||||
upcall interfaces that are used in order to provide the NFS client with |
||||
some of the information that it requires in order to fully comply with |
||||
the NFS spec. |
||||
|
||||
The DNS resolver |
||||
================ |
||||
|
||||
NFSv4 allows for one server to refer the NFS client to data that has been |
||||
migrated onto another server by means of the special "fs_locations" |
||||
attribute. See |
||||
http://tools.ietf.org/html/rfc3530#section-6 |
||||
and |
||||
http://tools.ietf.org/html/draft-ietf-nfsv4-referrals-00 |
||||
|
||||
The fs_locations information can take the form of either an ip address and |
||||
a path, or a DNS hostname and a path. The latter requires the NFS client to |
||||
do a DNS lookup in order to mount the new volume, and hence the need for an |
||||
upcall to allow userland to provide this service. |
||||
|
||||
Assuming that the user has the 'rpc_pipefs' filesystem mounted in the usual |
||||
/var/lib/nfs/rpc_pipefs, the upcall consists of the following steps: |
||||
|
||||
(1) The process checks the dns_resolve cache to see if it contains a |
||||
valid entry. If so, it returns that entry and exits. |
||||
|
||||
(2) If no valid entry exists, the helper script '/sbin/nfs_cache_getent' |
||||
(may be changed using the 'nfs.cache_getent' kernel boot parameter) |
||||
is run, with two arguments: |
||||
- the cache name, "dns_resolve" |
||||
- the hostname to resolve |
||||
|
||||
(3) After looking up the corresponding ip address, the helper script |
||||
writes the result into the rpc_pipefs pseudo-file |
||||
'/var/lib/nfs/rpc_pipefs/cache/dns_resolve/channel' |
||||
in the following (text) format: |
||||
|
||||
"<ip address> <hostname> <ttl>\n" |
||||
|
||||
Where <ip address> is in the usual IPv4 (123.456.78.90) or IPv6 |
||||
(ffee:ddcc:bbaa:9988:7766:5544:3322:1100, ffee::1100, ...) format. |
||||
<hostname> is identical to the second argument of the helper |
||||
script, and <ttl> is the 'time to live' of this cache entry (in |
||||
units of seconds). |
||||
|
||||
Note: If <ip address> is invalid, say the string "0", then a negative |
||||
entry is created, which will cause the kernel to treat the hostname |
||||
as having no valid DNS translation. |
||||
|
||||
|
||||
|
||||
|
||||
A basic sample /sbin/nfs_cache_getent |
||||
===================================== |
||||
|
||||
#!/bin/bash |
||||
# |
||||
ttl=600 |
||||
# |
||||
cut=/usr/bin/cut |
||||
getent=/usr/bin/getent |
||||
rpc_pipefs=/var/lib/nfs/rpc_pipefs |
||||
# |
||||
die() |
||||
{ |
||||
echo "Usage: $0 cache_name entry_name" |
||||
exit 1 |
||||
} |
||||
|
||||
[ $# -lt 2 ] && die |
||||
cachename="$1" |
||||
cache_path=${rpc_pipefs}/cache/${cachename}/channel |
||||
|
||||
case "${cachename}" in |
||||
dns_resolve) |
||||
name="$2" |
||||
result="$(${getent} hosts ${name} | ${cut} -f1 -d\ )" |
||||
[ -z "${result}" ] && result="0" |
||||
;; |
||||
*) |
||||
die |
||||
;; |
||||
esac |
||||
echo "${result} ${name} ${ttl}" >${cache_path} |
||||
|
@ -0,0 +1,140 @@ |
||||
/*
|
||||
* linux/fs/nfs/cache_lib.c |
||||
* |
||||
* Helper routines for the NFS client caches |
||||
* |
||||
* Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com> |
||||
*/ |
||||
#include <linux/kmod.h> |
||||
#include <linux/module.h> |
||||
#include <linux/moduleparam.h> |
||||
#include <linux/mount.h> |
||||
#include <linux/namei.h> |
||||
#include <linux/sunrpc/cache.h> |
||||
#include <linux/sunrpc/rpc_pipe_fs.h> |
||||
|
||||
#include "cache_lib.h" |
||||
|
||||
#define NFS_CACHE_UPCALL_PATHLEN 256 |
||||
#define NFS_CACHE_UPCALL_TIMEOUT 15 |
||||
|
||||
static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] = |
||||
"/sbin/nfs_cache_getent"; |
||||
static unsigned long nfs_cache_getent_timeout = NFS_CACHE_UPCALL_TIMEOUT; |
||||
|
||||
module_param_string(cache_getent, nfs_cache_getent_prog, |
||||
sizeof(nfs_cache_getent_prog), 0600); |
||||
MODULE_PARM_DESC(cache_getent, "Path to the client cache upcall program"); |
||||
module_param_named(cache_getent_timeout, nfs_cache_getent_timeout, ulong, 0600); |
||||
MODULE_PARM_DESC(cache_getent_timeout, "Timeout (in seconds) after which " |
||||
"the cache upcall is assumed to have failed"); |
||||
|
||||
int nfs_cache_upcall(struct cache_detail *cd, char *entry_name) |
||||
{ |
||||
static char *envp[] = { "HOME=/", |
||||
"TERM=linux", |
||||
"PATH=/sbin:/usr/sbin:/bin:/usr/bin", |
||||
NULL |
||||
}; |
||||
char *argv[] = { |
||||
nfs_cache_getent_prog, |
||||
cd->name, |
||||
entry_name, |
||||
NULL |
||||
}; |
||||
int ret = -EACCES; |
||||
|
||||
if (nfs_cache_getent_prog[0] == '\0') |
||||
goto out; |
||||
ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); |
||||
/*
|
||||
* Disable the upcall mechanism if we're getting an ENOENT or |
||||
* EACCES error. The admin can re-enable it on the fly by using |
||||
* sysfs to set the 'cache_getent' parameter once the problem |
||||
* has been fixed. |
||||
*/ |
||||
if (ret == -ENOENT || ret == -EACCES) |
||||
nfs_cache_getent_prog[0] = '\0'; |
||||
out: |
||||
return ret > 0 ? 0 : ret; |
||||
} |
||||
|
||||
/*
|
||||
* Deferred request handling |
||||
*/ |
||||
void nfs_cache_defer_req_put(struct nfs_cache_defer_req *dreq) |
||||
{ |
||||
if (atomic_dec_and_test(&dreq->count)) |
||||
kfree(dreq); |
||||
} |
||||
|
||||
static void nfs_dns_cache_revisit(struct cache_deferred_req *d, int toomany) |
||||
{ |
||||
struct nfs_cache_defer_req *dreq; |
||||
|
||||
dreq = container_of(d, struct nfs_cache_defer_req, deferred_req); |
||||
|
||||
complete_all(&dreq->completion); |
||||
nfs_cache_defer_req_put(dreq); |
||||
} |
||||
|
||||
static struct cache_deferred_req *nfs_dns_cache_defer(struct cache_req *req) |
||||
{ |
||||
struct nfs_cache_defer_req *dreq; |
||||
|
||||
dreq = container_of(req, struct nfs_cache_defer_req, req); |
||||
dreq->deferred_req.revisit = nfs_dns_cache_revisit; |
||||
atomic_inc(&dreq->count); |
||||
|
||||
return &dreq->deferred_req; |
||||
} |
||||
|
||||
struct nfs_cache_defer_req *nfs_cache_defer_req_alloc(void) |
||||
{ |
||||
struct nfs_cache_defer_req *dreq; |
||||
|
||||
dreq = kzalloc(sizeof(*dreq), GFP_KERNEL); |
||||
if (dreq) { |
||||
init_completion(&dreq->completion); |
||||
atomic_set(&dreq->count, 1); |
||||
dreq->req.defer = nfs_dns_cache_defer; |
||||
} |
||||
return dreq; |
||||
} |
||||
|
||||
int nfs_cache_wait_for_upcall(struct nfs_cache_defer_req *dreq) |
||||
{ |
||||
if (wait_for_completion_timeout(&dreq->completion, |
||||
nfs_cache_getent_timeout * HZ) == 0) |
||||
return -ETIMEDOUT; |
||||
return 0; |
||||
} |
||||
|
||||
int nfs_cache_register(struct cache_detail *cd) |
||||
{ |
||||
struct nameidata nd; |
||||
struct vfsmount *mnt; |
||||
int ret; |
||||
|
||||
mnt = rpc_get_mount(); |
||||
if (IS_ERR(mnt)) |
||||
return PTR_ERR(mnt); |
||||
ret = vfs_path_lookup(mnt->mnt_root, mnt, "/cache", 0, &nd); |
||||
if (ret) |
||||
goto err; |
||||
ret = sunrpc_cache_register_pipefs(nd.path.dentry, |
||||
cd->name, 0600, cd); |
||||
path_put(&nd.path); |
||||
if (!ret) |
||||
return ret; |
||||
err: |
||||
rpc_put_mount(); |
||||
return ret; |
||||
} |
||||
|
||||
void nfs_cache_unregister(struct cache_detail *cd) |
||||
{ |
||||
sunrpc_cache_unregister_pipefs(cd); |
||||
rpc_put_mount(); |
||||
} |
||||
|
@ -0,0 +1,27 @@ |
||||
/*
|
||||
* Helper routines for the NFS client caches |
||||
* |
||||
* Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com> |
||||
*/ |
||||
|
||||
#include <linux/completion.h> |
||||
#include <linux/sunrpc/cache.h> |
||||
#include <asm/atomic.h> |
||||
|
||||
/*
|
||||
* Deferred request handling |
||||
*/ |
||||
struct nfs_cache_defer_req { |
||||
struct cache_req req; |
||||
struct cache_deferred_req deferred_req; |
||||
struct completion completion; |
||||
atomic_t count; |
||||
}; |
||||
|
||||
extern int nfs_cache_upcall(struct cache_detail *cd, char *entry_name); |
||||
extern struct nfs_cache_defer_req *nfs_cache_defer_req_alloc(void); |
||||
extern void nfs_cache_defer_req_put(struct nfs_cache_defer_req *dreq); |
||||
extern int nfs_cache_wait_for_upcall(struct nfs_cache_defer_req *dreq); |
||||
|
||||
extern int nfs_cache_register(struct cache_detail *cd); |
||||
extern void nfs_cache_unregister(struct cache_detail *cd); |
@ -0,0 +1,335 @@ |
||||
/*
|
||||
* linux/fs/nfs/dns_resolve.c |
||||
* |
||||
* Copyright (c) 2009 Trond Myklebust <Trond.Myklebust@netapp.com> |
||||
* |
||||
* Resolves DNS hostnames into valid ip addresses |
||||
*/ |
||||
|
||||
#include <linux/hash.h> |
||||
#include <linux/string.h> |
||||
#include <linux/kmod.h> |
||||
#include <linux/module.h> |
||||
#include <linux/socket.h> |
||||
#include <linux/seq_file.h> |
||||
#include <linux/inet.h> |
||||
#include <linux/sunrpc/clnt.h> |
||||
#include <linux/sunrpc/cache.h> |
||||
#include <linux/sunrpc/svcauth.h> |
||||
|
||||
#include "dns_resolve.h" |
||||
#include "cache_lib.h" |
||||
|
||||
#define NFS_DNS_HASHBITS 4 |
||||
#define NFS_DNS_HASHTBL_SIZE (1 << NFS_DNS_HASHBITS) |
||||
|
||||
static struct cache_head *nfs_dns_table[NFS_DNS_HASHTBL_SIZE]; |
||||
|
||||
struct nfs_dns_ent { |
||||
struct cache_head h; |
||||
|
||||
char *hostname; |
||||
size_t namelen; |
||||
|
||||
struct sockaddr_storage addr; |
||||
size_t addrlen; |
||||
}; |
||||
|
||||
|
||||
static void nfs_dns_ent_init(struct cache_head *cnew, |
||||
struct cache_head *ckey) |
||||
{ |
||||
struct nfs_dns_ent *new; |
||||
struct nfs_dns_ent *key; |
||||
|
||||
new = container_of(cnew, struct nfs_dns_ent, h); |
||||
key = container_of(ckey, struct nfs_dns_ent, h); |
||||
|
||||
kfree(new->hostname); |
||||
new->hostname = kstrndup(key->hostname, key->namelen, GFP_KERNEL); |
||||
if (new->hostname) { |
||||
new->namelen = key->namelen; |
||||
memcpy(&new->addr, &key->addr, key->addrlen); |
||||
new->addrlen = key->addrlen; |
||||
} else { |
||||
new->namelen = 0; |
||||
new->addrlen = 0; |
||||
} |
||||
} |
||||
|
||||
static void nfs_dns_ent_put(struct kref *ref) |
||||
{ |
||||
struct nfs_dns_ent *item; |
||||
|
||||
item = container_of(ref, struct nfs_dns_ent, h.ref); |
||||
kfree(item->hostname); |
||||
kfree(item); |
||||
} |
||||
|
||||
static struct cache_head *nfs_dns_ent_alloc(void) |
||||
{ |
||||
struct nfs_dns_ent *item = kmalloc(sizeof(*item), GFP_KERNEL); |
||||
|
||||
if (item != NULL) { |
||||
item->hostname = NULL; |
||||
item->namelen = 0; |
||||
item->addrlen = 0; |
||||
return &item->h; |
||||
} |
||||
return NULL; |
||||
}; |
||||
|
||||
static unsigned int nfs_dns_hash(const struct nfs_dns_ent *key) |
||||
{ |
||||
return hash_str(key->hostname, NFS_DNS_HASHBITS); |
||||
} |
||||
|
||||
static void nfs_dns_request(struct cache_detail *cd, |
||||
struct cache_head *ch, |
||||
char **bpp, int *blen) |
||||
{ |
||||
struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h); |
||||
|
||||
qword_add(bpp, blen, key->hostname); |
||||
(*bpp)[-1] = '\n'; |
||||
} |
||||
|
||||
static int nfs_dns_upcall(struct cache_detail *cd, |
||||
struct cache_head *ch) |
||||
{ |
||||
struct nfs_dns_ent *key = container_of(ch, struct nfs_dns_ent, h); |
||||
int ret; |
||||
|
||||
ret = nfs_cache_upcall(cd, key->hostname); |
||||
if (ret) |
||||
ret = sunrpc_cache_pipe_upcall(cd, ch, nfs_dns_request); |
||||
return ret; |
||||
} |
||||
|
||||
static int nfs_dns_match(struct cache_head *ca, |
||||
struct cache_head *cb) |
||||
{ |
||||
struct nfs_dns_ent *a; |
||||
struct nfs_dns_ent *b; |
||||
|
||||
a = container_of(ca, struct nfs_dns_ent, h); |
||||
b = container_of(cb, struct nfs_dns_ent, h); |
||||
|
||||
if (a->namelen == 0 || a->namelen != b->namelen) |
||||
return 0; |
||||
return memcmp(a->hostname, b->hostname, a->namelen) == 0; |
||||
} |
||||
|
||||
static int nfs_dns_show(struct seq_file *m, struct cache_detail *cd, |
||||
struct cache_head *h) |
||||
{ |
||||
struct nfs_dns_ent *item; |
||||
long ttl; |
||||
|
||||
if (h == NULL) { |
||||
seq_puts(m, "# ip address hostname ttl\n"); |
||||
return 0; |
||||
} |
||||
item = container_of(h, struct nfs_dns_ent, h); |
||||
ttl = (long)item->h.expiry_time - (long)get_seconds(); |
||||
if (ttl < 0) |
||||
ttl = 0; |
||||
|
||||
if (!test_bit(CACHE_NEGATIVE, &h->flags)) { |
||||
char buf[INET6_ADDRSTRLEN+IPV6_SCOPE_ID_LEN+1]; |
||||
|
||||
rpc_ntop((struct sockaddr *)&item->addr, buf, sizeof(buf)); |
||||
seq_printf(m, "%15s ", buf); |
||||
} else |
||||
seq_puts(m, "<none> "); |
||||
seq_printf(m, "%15s %ld\n", item->hostname, ttl); |
||||
return 0; |
||||
} |
||||
|
||||
struct nfs_dns_ent *nfs_dns_lookup(struct cache_detail *cd, |
||||
struct nfs_dns_ent *key) |
||||
{ |
||||
struct cache_head *ch; |
||||
|
||||
ch = sunrpc_cache_lookup(cd, |
||||
&key->h, |
||||
nfs_dns_hash(key)); |
||||
if (!ch) |
||||
return NULL; |
||||
return container_of(ch, struct nfs_dns_ent, h); |
||||
} |
||||
|
||||
struct nfs_dns_ent *nfs_dns_update(struct cache_detail *cd, |
||||
struct nfs_dns_ent *new, |
||||
struct nfs_dns_ent *key) |
||||
{ |
||||
struct cache_head *ch; |
||||
|
||||
ch = sunrpc_cache_update(cd, |
||||
&new->h, &key->h, |
||||
nfs_dns_hash(key)); |
||||
if (!ch) |
||||
return NULL; |
||||
return container_of(ch, struct nfs_dns_ent, h); |
||||
} |
||||
|
||||
static int nfs_dns_parse(struct cache_detail *cd, char *buf, int buflen) |
||||
{ |
||||
char buf1[NFS_DNS_HOSTNAME_MAXLEN+1]; |
||||
struct nfs_dns_ent key, *item; |
||||
unsigned long ttl; |
||||
ssize_t len; |
||||
int ret = -EINVAL; |
||||
|
||||
if (buf[buflen-1] != '\n') |
||||
goto out; |
||||
buf[buflen-1] = '\0'; |
||||
|
||||
len = qword_get(&buf, buf1, sizeof(buf1)); |
||||
if (len <= 0) |
||||
goto out; |
||||
key.addrlen = rpc_pton(buf1, len, |
||||
(struct sockaddr *)&key.addr, |
||||
sizeof(key.addr)); |
||||
|
||||
len = qword_get(&buf, buf1, sizeof(buf1)); |
||||
if (len <= 0) |
||||
goto out; |
||||
|
||||
key.hostname = buf1; |
||||
key.namelen = len; |
||||
memset(&key.h, 0, sizeof(key.h)); |
||||
|
||||
ttl = get_expiry(&buf); |
||||
if (ttl == 0) |
||||
goto out; |
||||
key.h.expiry_time = ttl + get_seconds(); |
||||
|
||||
ret = -ENOMEM; |
||||
item = nfs_dns_lookup(cd, &key); |
||||
if (item == NULL) |
||||
goto out; |
||||
|
||||
if (key.addrlen == 0) |
||||
set_bit(CACHE_NEGATIVE, &key.h.flags); |
||||
|
||||
item = nfs_dns_update(cd, &key, item); |
||||
if (item == NULL) |
||||
goto out; |
||||
|
||||
ret = 0; |
||||
cache_put(&item->h, cd); |
||||
out: |
||||
return ret; |
||||
} |
||||
|
||||
static struct cache_detail nfs_dns_resolve = { |
||||
.owner = THIS_MODULE, |
||||
.hash_size = NFS_DNS_HASHTBL_SIZE, |
||||
.hash_table = nfs_dns_table, |
||||
.name = "dns_resolve", |
||||
.cache_put = nfs_dns_ent_put, |
||||
.cache_upcall = nfs_dns_upcall, |
||||
.cache_parse = nfs_dns_parse, |
||||
.cache_show = nfs_dns_show, |
||||
.match = nfs_dns_match, |
||||
.init = nfs_dns_ent_init, |
||||
.update = nfs_dns_ent_init, |
||||
.alloc = nfs_dns_ent_alloc, |
||||
}; |
||||
|
||||
static int do_cache_lookup(struct cache_detail *cd, |
||||
struct nfs_dns_ent *key, |
||||
struct nfs_dns_ent **item, |
||||
struct nfs_cache_defer_req *dreq) |
||||
{ |
||||
int ret = -ENOMEM; |
||||
|
||||
*item = nfs_dns_lookup(cd, key); |
||||
if (*item) { |
||||
ret = cache_check(cd, &(*item)->h, &dreq->req); |
||||
if (ret) |
||||
*item = NULL; |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
static int do_cache_lookup_nowait(struct cache_detail *cd, |
||||
struct nfs_dns_ent *key, |
||||
struct nfs_dns_ent **item) |
||||
{ |
||||
int ret = -ENOMEM; |
||||
|
||||
*item = nfs_dns_lookup(cd, key); |
||||
if (!*item) |
||||
goto out_err; |
||||
ret = -ETIMEDOUT; |
||||
if (!test_bit(CACHE_VALID, &(*item)->h.flags) |
||||
|| (*item)->h.expiry_time < get_seconds() |
||||
|| cd->flush_time > (*item)->h.last_refresh) |
||||
goto out_put; |
||||
ret = -ENOENT; |
||||
if (test_bit(CACHE_NEGATIVE, &(*item)->h.flags)) |
||||
goto out_put; |
||||
return 0; |
||||
out_put: |
||||
cache_put(&(*item)->h, cd); |
||||
out_err: |
||||
*item = NULL; |
||||
return ret; |
||||
} |
||||
|
||||
static int do_cache_lookup_wait(struct cache_detail *cd, |
||||
struct nfs_dns_ent *key, |
||||
struct nfs_dns_ent **item) |
||||
{ |
||||
struct nfs_cache_defer_req *dreq; |
||||
int ret = -ENOMEM; |
||||
|
||||
dreq = nfs_cache_defer_req_alloc(); |
||||
if (!dreq) |
||||
goto out; |
||||
ret = do_cache_lookup(cd, key, item, dreq); |
||||
if (ret == -EAGAIN) { |
||||
ret = nfs_cache_wait_for_upcall(dreq); |
||||
if (!ret) |
||||
ret = do_cache_lookup_nowait(cd, key, item); |
||||
} |
||||
nfs_cache_defer_req_put(dreq); |
||||
out: |
||||
return ret; |
||||
} |
||||
|
||||
ssize_t nfs_dns_resolve_name(char *name, size_t namelen, |
||||
struct sockaddr *sa, size_t salen) |
||||
{ |
||||
struct nfs_dns_ent key = { |
||||
.hostname = name, |
||||
.namelen = namelen, |
||||
}; |
||||
struct nfs_dns_ent *item = NULL; |
||||
ssize_t ret; |
||||
|
||||
ret = do_cache_lookup_wait(&nfs_dns_resolve, &key, &item); |
||||
if (ret == 0) { |
||||
if (salen >= item->addrlen) { |
||||
memcpy(sa, &item->addr, item->addrlen); |
||||
ret = item->addrlen; |
||||
} else |
||||
ret = -EOVERFLOW; |
||||
cache_put(&item->h, &nfs_dns_resolve); |
||||
} else if (ret == -ENOENT) |
||||
ret = -ESRCH; |
||||
return ret; |
||||
} |
||||
|
||||
int nfs_dns_resolver_init(void) |
||||
{ |
||||
return nfs_cache_register(&nfs_dns_resolve); |
||||
} |
||||
|
||||
void nfs_dns_resolver_destroy(void) |
||||
{ |
||||
nfs_cache_unregister(&nfs_dns_resolve); |
||||
} |
||||
|
@ -0,0 +1,14 @@ |
||||
/*
|
||||
* Resolve DNS hostnames into valid ip addresses |
||||
*/ |
||||
#ifndef __LINUX_FS_NFS_DNS_RESOLVE_H |
||||
#define __LINUX_FS_NFS_DNS_RESOLVE_H |
||||
|
||||
#define NFS_DNS_HOSTNAME_MAXLEN (128) |
||||
|
||||
extern int nfs_dns_resolver_init(void); |
||||
extern void nfs_dns_resolver_destroy(void); |
||||
extern ssize_t nfs_dns_resolve_name(char *name, size_t namelen, |
||||
struct sockaddr *sa, size_t salen); |
||||
|
||||
#endif |
Loading…
Reference in new issue