756 lines
15 KiB
C
Executable File
756 lines
15 KiB
C
Executable File
#include <u.h>
|
|
#include <sys/time.h>
|
|
#include <libc.h>
|
|
#include <ip.h>
|
|
#include <bio.h>
|
|
#include <ndb.h>
|
|
#include "dns.h"
|
|
|
|
enum
|
|
{
|
|
Maxdest= 24, /* maximum destinations for a request message */
|
|
Maxtrans= 3 /* maximum transmissions to a server */
|
|
};
|
|
|
|
static int netquery(DN*, int, RR*, Request*, int);
|
|
static RR* dnresolve1(char*, int, int, Request*, int, int);
|
|
|
|
char *LOG = "dns";
|
|
|
|
/*
|
|
* lookup 'type' info for domain name 'name'. If it doesn't exist, try
|
|
* looking it up as a canonical name.
|
|
*/
|
|
RR*
|
|
dnresolve(char *name, int class, int type, Request *req, RR **cn, int depth, int recurse, int rooted, int *status)
|
|
{
|
|
RR *rp, *nrp, *drp;
|
|
DN *dp;
|
|
int loops;
|
|
char nname[Domlen];
|
|
|
|
if(status)
|
|
*status = 0;
|
|
|
|
/*
|
|
* hack for systems that don't have resolve search
|
|
* lists. Just look up the simple name in the database.
|
|
*/
|
|
if(!rooted && strchr(name, '.') == 0){
|
|
rp = nil;
|
|
drp = domainlist(class);
|
|
for(nrp = drp; nrp != nil; nrp = nrp->next){
|
|
snprint(nname, sizeof(nname), "%s.%s", name, nrp->ptr->name);
|
|
rp = dnresolve(nname, class, type, req,cn, depth, recurse, rooted, status);
|
|
rrfreelist(rrremneg(&rp));
|
|
if(rp != nil)
|
|
break;
|
|
}
|
|
if(drp != nil)
|
|
rrfree(drp);
|
|
return rp;
|
|
}
|
|
|
|
/*
|
|
* try the name directly
|
|
*/
|
|
rp = dnresolve1(name, class, type, req, depth, recurse);
|
|
if(rp)
|
|
return randomize(rp);
|
|
|
|
/* try it as a canonical name if we weren't told the name didn't exist */
|
|
dp = dnlookup(name, class, 0);
|
|
if(type != Tptr && dp->nonexistent != Rname){
|
|
for(loops=0; rp == nil && loops < 32; loops++){
|
|
rp = dnresolve1(name, class, Tcname, req, depth, recurse);
|
|
if(rp == nil)
|
|
break;
|
|
|
|
if(rp->negative){
|
|
rrfreelist(rp);
|
|
rp = nil;
|
|
break;
|
|
}
|
|
|
|
name = rp->host->name;
|
|
if(cn)
|
|
rrcat(cn, rp);
|
|
else
|
|
rrfreelist(rp);
|
|
|
|
rp = dnresolve1(name, class, type, req, depth, recurse);
|
|
}
|
|
}
|
|
|
|
/* distinction between not found and not good */
|
|
if(rp == 0 && status != 0 && dp->nonexistent != 0)
|
|
*status = dp->nonexistent;
|
|
|
|
return randomize(rp);
|
|
}
|
|
|
|
static RR*
|
|
dnresolve1(char *name, int class, int type, Request *req, int depth, int recurse)
|
|
{
|
|
DN *dp, *nsdp;
|
|
RR *rp, *nsrp, *dbnsrp;
|
|
char *cp;
|
|
|
|
if(debug)
|
|
syslog(0, LOG, "dnresolve1 %s %d %d", name, type, class);
|
|
|
|
/* only class Cin implemented so far */
|
|
if(class != Cin)
|
|
return 0;
|
|
|
|
dp = dnlookup(name, class, 1);
|
|
|
|
/*
|
|
* Try the cache first
|
|
*/
|
|
rp = rrlookup(dp, type, OKneg);
|
|
if(rp){
|
|
if(rp->db){
|
|
/* unauthenticated db entries are hints */
|
|
if(rp->auth)
|
|
return rp;
|
|
} else {
|
|
/* cached entry must still be valid */
|
|
if(rp->ttl > now){
|
|
/* but Tall entries are special */
|
|
if(type != Tall || rp->query == Tall)
|
|
return rp;
|
|
}
|
|
}
|
|
}
|
|
rrfreelist(rp);
|
|
|
|
/*
|
|
* try the cache for a canonical name. if found punt
|
|
* since we'll find it during the canonical name search
|
|
* in dnresolve().
|
|
*/
|
|
if(type != Tcname){
|
|
rp = rrlookup(dp, Tcname, NOneg);
|
|
rrfreelist(rp);
|
|
if(rp)
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* if we're running as just a resolver, go to our
|
|
* designated name servers
|
|
*/
|
|
if(resolver){
|
|
nsrp = randomize(getdnsservers(class));
|
|
if(nsrp != nil) {
|
|
if(netquery(dp, type, nsrp, req, depth+1)){
|
|
rrfreelist(nsrp);
|
|
return rrlookup(dp, type, OKneg);
|
|
}
|
|
rrfreelist(nsrp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* walk up the domain name looking for
|
|
* a name server for the domain.
|
|
*/
|
|
for(cp = name; cp; cp = walkup(cp)){
|
|
/*
|
|
* if this is a local (served by us) domain,
|
|
* return answer
|
|
*/
|
|
dbnsrp = randomize(dblookup(cp, class, Tns, 0, 0));
|
|
if(dbnsrp && dbnsrp->local){
|
|
rp = dblookup(name, class, type, 1, dbnsrp->ttl);
|
|
rrfreelist(dbnsrp);
|
|
return rp;
|
|
}
|
|
|
|
/*
|
|
* if recursion isn't set, just accept local
|
|
* entries
|
|
*/
|
|
if(recurse == Dontrecurse){
|
|
if(dbnsrp)
|
|
rrfreelist(dbnsrp);
|
|
continue;
|
|
}
|
|
|
|
/* look for ns in cache */
|
|
nsdp = dnlookup(cp, class, 0);
|
|
nsrp = nil;
|
|
if(nsdp)
|
|
nsrp = randomize(rrlookup(nsdp, Tns, NOneg));
|
|
|
|
/* if the entry timed out, ignore it */
|
|
if(nsrp && nsrp->ttl < now){
|
|
rrfreelist(nsrp);
|
|
nsrp = nil;
|
|
}
|
|
|
|
if(nsrp){
|
|
rrfreelist(dbnsrp);
|
|
|
|
/* try the name servers found in cache */
|
|
if(netquery(dp, type, nsrp, req, depth+1)){
|
|
rrfreelist(nsrp);
|
|
return rrlookup(dp, type, OKneg);
|
|
}
|
|
rrfreelist(nsrp);
|
|
continue;
|
|
}
|
|
|
|
/* use ns from db */
|
|
if(dbnsrp){
|
|
/* try the name servers found in db */
|
|
if(netquery(dp, type, dbnsrp, req, depth+1)){
|
|
/* we got an answer */
|
|
rrfreelist(dbnsrp);
|
|
return rrlookup(dp, type, NOneg);
|
|
}
|
|
rrfreelist(dbnsrp);
|
|
}
|
|
}
|
|
|
|
/* settle for a non-authoritative answer */
|
|
rp = rrlookup(dp, type, OKneg);
|
|
if(rp)
|
|
return rp;
|
|
|
|
/* noone answered. try the database, we might have a chance. */
|
|
return dblookup(name, class, type, 0, 0);
|
|
}
|
|
|
|
/*
|
|
* walk a domain name one element to the right. return a pointer to that element.
|
|
* in other words, return a pointer to the parent domain name.
|
|
*/
|
|
char*
|
|
walkup(char *name)
|
|
{
|
|
char *cp;
|
|
|
|
cp = strchr(name, '.');
|
|
if(cp)
|
|
return cp+1;
|
|
else if(*name)
|
|
return "";
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get a udpport for requests and replies.
|
|
*/
|
|
int
|
|
udpport(void)
|
|
{
|
|
int fd;
|
|
|
|
if((fd = dial("udp!0!53", nil, nil, nil)) < 0)
|
|
warning("can't get udp port");
|
|
return fd;
|
|
}
|
|
|
|
int
|
|
mkreq(DN *dp, int type, uchar *buf, int flags, ushort reqno)
|
|
{
|
|
DNSmsg m;
|
|
int len;
|
|
Udphdr *uh = (Udphdr*)buf;
|
|
|
|
/* stuff port number into output buffer */
|
|
memset(uh, 0, sizeof(*uh));
|
|
hnputs(uh->rport, 53);
|
|
|
|
/* make request and convert it to output format */
|
|
memset(&m, 0, sizeof(m));
|
|
m.flags = flags;
|
|
m.id = reqno;
|
|
m.qd = rralloc(type);
|
|
m.qd->owner = dp;
|
|
m.qd->type = type;
|
|
len = convDNS2M(&m, &buf[Udphdrsize], Maxudp);
|
|
if(len < 0)
|
|
abort(); /* "can't convert" */;
|
|
rrfree(m.qd);
|
|
return len;
|
|
}
|
|
|
|
static void
|
|
freeanswers(DNSmsg *mp)
|
|
{
|
|
rrfreelist(mp->qd);
|
|
rrfreelist(mp->an);
|
|
rrfreelist(mp->ns);
|
|
rrfreelist(mp->ar);
|
|
}
|
|
|
|
/*
|
|
* read replies to a request. ignore any of the wrong type. wait at most 5 seconds.
|
|
*/
|
|
static int udpreadtimeout(int, Udphdr*, void*, int, int);
|
|
static int
|
|
readreply(int fd, DN *dp, int type, ushort req,
|
|
uchar *ibuf, DNSmsg *mp, ulong endtime, Request *reqp)
|
|
{
|
|
char *err;
|
|
int len;
|
|
ulong now;
|
|
RR *rp;
|
|
|
|
for(; ; freeanswers(mp)){
|
|
now = time(0);
|
|
if(now >= endtime)
|
|
return -1; /* timed out */
|
|
|
|
/* timed read */
|
|
len = udpreadtimeout(fd, (Udphdr*)ibuf, ibuf+Udphdrsize, Maxudpin, (endtime-now)*1000);
|
|
if(len < 0)
|
|
return -1; /* timed out */
|
|
|
|
/* convert into internal format */
|
|
memset(mp, 0, sizeof(*mp));
|
|
err = convM2DNS(&ibuf[Udphdrsize], len, mp);
|
|
if(err){
|
|
syslog(0, LOG, "input err %s: %I", err, ibuf);
|
|
continue;
|
|
}
|
|
if(debug)
|
|
logreply(reqp->id, ibuf, mp);
|
|
|
|
/* answering the right question? */
|
|
if(mp->id != req){
|
|
syslog(0, LOG, "%d: id %d instead of %d: %I", reqp->id,
|
|
mp->id, req, ibuf);
|
|
continue;
|
|
}
|
|
if(mp->qd == 0){
|
|
syslog(0, LOG, "%d: no question RR: %I", reqp->id, ibuf);
|
|
continue;
|
|
}
|
|
if(mp->qd->owner != dp){
|
|
syslog(0, LOG, "%d: owner %s instead of %s: %I", reqp->id,
|
|
mp->qd->owner->name, dp->name, ibuf);
|
|
continue;
|
|
}
|
|
if(mp->qd->type != type){
|
|
syslog(0, LOG, "%d: type %d instead of %d: %I", reqp->id,
|
|
mp->qd->type, type, ibuf);
|
|
continue;
|
|
}
|
|
|
|
/* remember what request this is in answer to */
|
|
for(rp = mp->an; rp; rp = rp->next)
|
|
rp->query = type;
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 0; /* never reached */
|
|
}
|
|
|
|
static int
|
|
udpreadtimeout(int fd, Udphdr *h, void *data, int n, int ms)
|
|
{
|
|
fd_set rd;
|
|
struct timeval tv;
|
|
|
|
FD_ZERO(&rd);
|
|
FD_SET(fd, &rd);
|
|
|
|
tv.tv_sec = ms/1000;
|
|
tv.tv_usec = (ms%1000)*1000;
|
|
|
|
if(select(fd+1, &rd, 0, 0, &tv) != 1)
|
|
return -1;
|
|
return udpread(fd, h, data, n);
|
|
}
|
|
|
|
/*
|
|
* return non-0 if first list includes second list
|
|
*/
|
|
int
|
|
contains(RR *rp1, RR *rp2)
|
|
{
|
|
RR *trp1, *trp2;
|
|
|
|
for(trp2 = rp2; trp2; trp2 = trp2->next){
|
|
for(trp1 = rp1; trp1; trp1 = trp1->next){
|
|
if(trp1->type == trp2->type)
|
|
if(trp1->host == trp2->host)
|
|
if(trp1->owner == trp2->owner)
|
|
break;
|
|
}
|
|
if(trp1 == 0)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
typedef struct Dest Dest;
|
|
struct Dest
|
|
{
|
|
uchar a[IPaddrlen]; /* ip address */
|
|
DN *s; /* name server */
|
|
int nx; /* number of transmissions */
|
|
int code;
|
|
};
|
|
|
|
|
|
/*
|
|
* return multicast version if any
|
|
*/
|
|
int
|
|
ipisbm(uchar *ip)
|
|
{
|
|
if(isv4(ip)){
|
|
if(ip[IPv4off] >= 0xe0 && ip[IPv4off] < 0xf0)
|
|
return 4;
|
|
if(ipcmp(ip, IPv4bcast) == 0)
|
|
return 4;
|
|
} else {
|
|
if(ip[0] == 0xff)
|
|
return 6;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get next server address
|
|
*/
|
|
static int
|
|
serveraddrs(RR *nsrp, Dest *dest, int nd, int depth, Request *reqp)
|
|
{
|
|
RR *rp, *arp, *trp;
|
|
Dest *cur;
|
|
|
|
if(nd >= Maxdest)
|
|
return 0;
|
|
|
|
/*
|
|
* look for a server whose address we already know.
|
|
* if we find one, mark it so we ignore this on
|
|
* subsequent passes.
|
|
*/
|
|
arp = 0;
|
|
for(rp = nsrp; rp; rp = rp->next){
|
|
assert(rp->magic == RRmagic);
|
|
if(rp->marker)
|
|
continue;
|
|
arp = rrlookup(rp->host, Ta, NOneg);
|
|
if(arp){
|
|
rp->marker = 1;
|
|
break;
|
|
}
|
|
arp = dblookup(rp->host->name, Cin, Ta, 0, 0);
|
|
if(arp){
|
|
rp->marker = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* if the cache and database lookup didn't find any new
|
|
* server addresses, try resolving one via the network.
|
|
* Mark any we try to resolve so we don't try a second time.
|
|
*/
|
|
if(arp == 0){
|
|
for(rp = nsrp; rp; rp = rp->next){
|
|
if(rp->marker)
|
|
continue;
|
|
rp->marker = 1;
|
|
|
|
/*
|
|
* avoid loops looking up a server under itself
|
|
*/
|
|
if(subsume(rp->owner->name, rp->host->name))
|
|
continue;
|
|
|
|
arp = dnresolve(rp->host->name, Cin, Ta, reqp, 0, depth+1, Recurse, 1, 0);
|
|
rrfreelist(rrremneg(&arp));
|
|
if(arp)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* use any addresses that we found */
|
|
for(trp = arp; trp; trp = trp->next){
|
|
if(nd >= Maxdest)
|
|
break;
|
|
cur = &dest[nd];
|
|
parseip(cur->a, trp->ip->name);
|
|
if(ipisbm(cur->a))
|
|
continue;
|
|
cur->nx = 0;
|
|
cur->s = trp->owner;
|
|
cur->code = Rtimeout;
|
|
nd++;
|
|
}
|
|
rrfreelist(arp);
|
|
return nd;
|
|
}
|
|
|
|
/*
|
|
* cache negative responses
|
|
*/
|
|
static void
|
|
cacheneg(DN *dp, int type, int rcode, RR *soarr)
|
|
{
|
|
RR *rp;
|
|
DN *soaowner;
|
|
ulong ttl;
|
|
|
|
/* no cache time specified, don' make anything up */
|
|
if(soarr != nil){
|
|
if(soarr->next != nil){
|
|
rrfreelist(soarr->next);
|
|
soarr->next = nil;
|
|
}
|
|
soaowner = soarr->owner;
|
|
} else
|
|
soaowner = nil;
|
|
|
|
/* the attach can cause soarr to be freed so mine it now */
|
|
if(soarr != nil && soarr->soa != nil)
|
|
ttl = soarr->soa->minttl+now;
|
|
else
|
|
ttl = 5*Min;
|
|
|
|
/* add soa and negative RR to the database */
|
|
rrattach(soarr, 1);
|
|
|
|
rp = rralloc(type);
|
|
rp->owner = dp;
|
|
rp->negative = 1;
|
|
rp->negsoaowner = soaowner;
|
|
rp->negrcode = rcode;
|
|
rp->ttl = ttl;
|
|
rrattach(rp, 1);
|
|
}
|
|
|
|
/*
|
|
* query name servers. If the name server returns a pointer to another
|
|
* name server, recurse.
|
|
*/
|
|
static int
|
|
netquery1(int fd, DN *dp, int type, RR *nsrp, Request *reqp, int depth, uchar *ibuf, uchar *obuf)
|
|
{
|
|
int ndest, j, len, replywaits, rv;
|
|
ushort req;
|
|
RR *tp, *soarr;
|
|
Dest *p, *l, *np;
|
|
DN *ndp;
|
|
Dest dest[Maxdest];
|
|
DNSmsg m;
|
|
ulong endtime;
|
|
Udphdr *uh;
|
|
|
|
/* pack request into a message */
|
|
req = rand();
|
|
len = mkreq(dp, type, obuf, Frecurse|Oquery, req);
|
|
|
|
/* no server addresses yet */
|
|
l = dest;
|
|
|
|
/*
|
|
* transmit requests and wait for answers.
|
|
* at most Maxtrans attempts to each address.
|
|
* each cycle send one more message than the previous.
|
|
*/
|
|
for(ndest = 1; ndest < Maxdest; ndest++){
|
|
p = dest;
|
|
|
|
endtime = time(0);
|
|
if(endtime >= reqp->aborttime)
|
|
break;
|
|
|
|
/* get a server address if we need one */
|
|
if(ndest > l - p){
|
|
j = serveraddrs(nsrp, dest, l - p, depth, reqp);
|
|
l = &dest[j];
|
|
}
|
|
|
|
/* no servers, punt */
|
|
if(l == dest)
|
|
break;
|
|
|
|
/* send to first 'ndest' destinations */
|
|
j = 0;
|
|
for(; p < &dest[ndest] && p < l; p++){
|
|
/* skip destinations we've finished with */
|
|
if(p->nx >= Maxtrans)
|
|
continue;
|
|
|
|
j++;
|
|
|
|
/* exponential backoff of requests */
|
|
if((1<<p->nx) > ndest)
|
|
continue;
|
|
|
|
memmove(obuf, p->a, sizeof(p->a));
|
|
if(debug)
|
|
logsend(reqp->id, depth, obuf, p->s->name,
|
|
dp->name, type);
|
|
uh = (Udphdr*)obuf;
|
|
fprint(2, "send %I %I %d %d\n", uh->raddr, uh->laddr, nhgets(uh->rport), nhgets(uh->lport));
|
|
if(udpwrite(fd, uh, obuf+Udphdrsize, len) < 0)
|
|
warning("sending udp msg %r");
|
|
p->nx++;
|
|
}
|
|
if(j == 0)
|
|
break; /* no destinations left */
|
|
|
|
/* wait up to 5 seconds for replies */
|
|
endtime = time(0) + 5;
|
|
if(endtime > reqp->aborttime)
|
|
endtime = reqp->aborttime;
|
|
|
|
for(replywaits = 0; replywaits < ndest; replywaits++){
|
|
if(readreply(fd, dp, type, req, ibuf, &m, endtime, reqp) < 0)
|
|
break; /* timed out */
|
|
|
|
/* find responder */
|
|
for(p = dest; p < l; p++)
|
|
if(memcmp(p->a, ibuf, sizeof(p->a)) == 0)
|
|
break;
|
|
|
|
/* remove all addrs of responding server from list */
|
|
for(np = dest; np < l; np++)
|
|
if(np->s == p->s)
|
|
p->nx = Maxtrans;
|
|
|
|
/* ignore any error replies */
|
|
if((m.flags & Rmask) == Rserver){
|
|
rrfreelist(m.qd);
|
|
rrfreelist(m.an);
|
|
rrfreelist(m.ar);
|
|
rrfreelist(m.ns);
|
|
if(p != l)
|
|
p->code = Rserver;
|
|
continue;
|
|
}
|
|
|
|
/* ignore any bad delegations */
|
|
if(m.ns && baddelegation(m.ns, nsrp, ibuf)){
|
|
rrfreelist(m.ns);
|
|
m.ns = nil;
|
|
if(m.an == nil){
|
|
rrfreelist(m.qd);
|
|
rrfreelist(m.ar);
|
|
if(p != l)
|
|
p->code = Rserver;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
/* remove any soa's from the authority section */
|
|
soarr = rrremtype(&m.ns, Tsoa);
|
|
|
|
/* incorporate answers */
|
|
if(m.an)
|
|
rrattach(m.an, (m.flags & Fauth) ? 1 : 0);
|
|
if(m.ar)
|
|
rrattach(m.ar, 0);
|
|
if(m.ns){
|
|
ndp = m.ns->owner;
|
|
rrattach(m.ns, 0);
|
|
} else
|
|
ndp = 0;
|
|
|
|
/* free the question */
|
|
if(m.qd)
|
|
rrfreelist(m.qd);
|
|
|
|
/*
|
|
* Any reply from an authoritative server,
|
|
* or a positive reply terminates the search
|
|
*/
|
|
if(m.an != nil || (m.flags & Fauth)){
|
|
if(m.an == nil && (m.flags & Rmask) == Rname)
|
|
dp->nonexistent = Rname;
|
|
else
|
|
dp->nonexistent = 0;
|
|
|
|
/*
|
|
* cache any negative responses, free soarr
|
|
*/
|
|
if((m.flags & Fauth) && m.an == nil)
|
|
cacheneg(dp, type, (m.flags & Rmask), soarr);
|
|
else
|
|
rrfreelist(soarr);
|
|
return 1;
|
|
}
|
|
rrfreelist(soarr);
|
|
|
|
/*
|
|
* if we've been given better name servers
|
|
* recurse
|
|
*/
|
|
if(m.ns){
|
|
tp = rrlookup(ndp, Tns, NOneg);
|
|
if(!contains(nsrp, tp)){
|
|
rv = netquery(dp, type, tp, reqp, depth+1);
|
|
rrfreelist(tp);
|
|
return rv;
|
|
} else
|
|
rrfreelist(tp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if all servers returned failure, propogate it */
|
|
dp->nonexistent = Rserver;
|
|
for(p = dest; p < l; p++)
|
|
if(p->code != Rserver)
|
|
dp->nonexistent = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct Qarg Qarg;
|
|
struct Qarg
|
|
{
|
|
DN *dp;
|
|
int type;
|
|
RR *nsrp;
|
|
Request *reqp;
|
|
int depth;
|
|
};
|
|
|
|
|
|
static int
|
|
netquery(DN *dp, int type, RR *nsrp, Request *reqp, int depth)
|
|
{
|
|
uchar *obuf;
|
|
uchar *ibuf;
|
|
RR *rp;
|
|
int fd, rv;
|
|
|
|
if(depth > 12)
|
|
return 0;
|
|
|
|
/* use alloced buffers rather than ones from the stack */
|
|
ibuf = emalloc(Maxudpin+Udphdrsize);
|
|
obuf = emalloc(Maxudp+Udphdrsize);
|
|
|
|
/* prepare server RR's for incremental lookup */
|
|
for(rp = nsrp; rp; rp = rp->next)
|
|
rp->marker = 0;
|
|
|
|
fd = udpport();
|
|
if(fd < 0)
|
|
return 0;
|
|
rv = netquery1(fd, dp, type, nsrp, reqp, depth, ibuf, obuf);
|
|
close(fd);
|
|
free(ibuf);
|
|
free(obuf);
|
|
|
|
return rv;
|
|
}
|