Initial implementation of dnslink
This commit is contained in:
parent
73a7690725
commit
786bd5d80b
3 changed files with 332 additions and 5 deletions
295
dnslink/dnslink.c
Normal file
295
dnslink/dnslink.c
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
/*
|
||||||
|
Package dnslink implements a dns link resolver. dnslink is a basic
|
||||||
|
standard for placing traversable links in dns itself. See dnslink.info
|
||||||
|
|
||||||
|
A dnslink is a path link in a dns TXT record, like this:
|
||||||
|
|
||||||
|
dnslink=/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
> dig TXT ipfs.io
|
||||||
|
ipfs.io. 120 IN TXT dnslink=/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy
|
||||||
|
|
||||||
|
This package eases resolving and working with thse dns links. For example:
|
||||||
|
|
||||||
|
import (
|
||||||
|
dnslink "github.com/jbenet/go-dnslink"
|
||||||
|
)
|
||||||
|
|
||||||
|
link, err := dnslink.Resolve("ipfs.io")
|
||||||
|
// link = "/ipfs/QmR7tiySn6vFHcEjBeZNtYGAFh735PJHfEMdVEycj9jAPy"
|
||||||
|
|
||||||
|
It even supports recursive resolution. Suppose you have three domains with
|
||||||
|
dnslink records like these:
|
||||||
|
|
||||||
|
> dig TXT foo.com
|
||||||
|
foo.com. 120 IN TXT dnslink=/dns/bar.com/f/o/o
|
||||||
|
> dig TXT bar.com
|
||||||
|
bar.com. 120 IN TXT dnslink=/dns/long.test.baz.it/b/a/r
|
||||||
|
> dig TXT long.test.baz.it
|
||||||
|
long.test.baz.it. 120 IN TXT dnslink=/b/a/z
|
||||||
|
|
||||||
|
Expect these resolutions:
|
||||||
|
|
||||||
|
dnslink.ResolveN("long.test.baz.it", 0) // "/dns/long.test.baz.it"
|
||||||
|
dnslink.Resolve("long.test.baz.it") // "/b/a/z"
|
||||||
|
|
||||||
|
dnslink.ResolveN("bar.com", 1) // "/dns/long.test.baz.it/b/a/r"
|
||||||
|
dnslink.Resolve("bar.com") // "/b/a/z/b/a/r"
|
||||||
|
|
||||||
|
dnslink.ResolveN("foo.com", 1) // "/dns/bar.com/f/o/o/"
|
||||||
|
dnslink.ResolveN("foo.com", 2) // "/dns/long.test.baz.it/b/a/r/f/o/o/"
|
||||||
|
dnslink.Resolve("foo.com") // "/b/a/z/b/a/r/f/o/o"
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/nameser.h>
|
||||||
|
#include <resolv.h>
|
||||||
|
#include "ipfs/namesys/namesys.h"
|
||||||
|
#define IPFS_DNSLINK_C
|
||||||
|
#include "ipfs/dnslink/dnslink.h"
|
||||||
|
#include "ipfs/cid/cid.h"
|
||||||
|
#include "ipfs/path/path.h"
|
||||||
|
|
||||||
|
// ipfs_dnslink_resolve resolves the dnslink at a particular domain. It will
|
||||||
|
// recursively keep resolving until reaching the defaultDepth of Resolver. If
|
||||||
|
// the depth is reached, ipfs_dnslink_resolve will return the last value
|
||||||
|
// retrieved, and ErrResolveLimit. If TXT records are found but are not valid
|
||||||
|
// dnslink records, ipfs_dnslink_resolve will return ErrInvalidDNSLink.
|
||||||
|
// ipfs_dnslink_resolve will check every TXT record returned. If resolution
|
||||||
|
// fails otherwise, ipfs_dnslink_resolve will return ErrResolveFailed
|
||||||
|
int ipfs_dnslink_resolve (char **p, char *domain)
|
||||||
|
{
|
||||||
|
return ipfs_dnslink_resolve_n (p, domain, DefaultDepthLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipfs_dnslink_lookup_txt is a function that looks up a TXT record in some dns resolver.
|
||||||
|
// This is useful for testing or passing your own dns resolution process, which
|
||||||
|
// could take into account non-standard TLDs like .bit, .onion, .ipfs, etc.
|
||||||
|
int (*ipfs_dnslink_lookup_txt)(char ***txt, char *name) = NULL;
|
||||||
|
|
||||||
|
// ipfs_dnslink_resolve_n is just like Resolve, with the option to specify a
|
||||||
|
// maximum resolution depth.
|
||||||
|
int ipfs_dnslink_resolve_n (char **p, char *d, int depth)
|
||||||
|
{
|
||||||
|
int err, i, l;
|
||||||
|
char *rest, **link, tail[500], buf[500], domain[500];
|
||||||
|
char dns_prefix[] = "/dns/";
|
||||||
|
|
||||||
|
domain[sizeof(domain)-1] = '\0';
|
||||||
|
strncpy (domain, d, sizeof(domain) - 1);
|
||||||
|
for (i=0 ; i < depth ; i++) {
|
||||||
|
err = ipfs_dnslink_resolve_once (&link, domain);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if does not have /dns/ as a prefix, done.
|
||||||
|
if (memcmp (*link, dns_prefix, sizeof(dns_prefix) - 1)!=0) {
|
||||||
|
l = strlen(*link) + strlen(tail);
|
||||||
|
*p = malloc(l + 1);
|
||||||
|
if (!*p) {
|
||||||
|
free(*link);
|
||||||
|
free(link);
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
*p[l] = '\0';
|
||||||
|
strncpy(*p, *link, l);
|
||||||
|
free(*link);
|
||||||
|
free(link);
|
||||||
|
strncat(*p, tail, l - strlen(*p));
|
||||||
|
return 0; // done
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep resolving
|
||||||
|
err = ipfs_dnslink_parse_link_domain (&d, &rest, *link);
|
||||||
|
free (*link);
|
||||||
|
free (link);
|
||||||
|
if (err) {
|
||||||
|
*p = NULL;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy (domain, d, sizeof(domain) - 1);
|
||||||
|
free (d);
|
||||||
|
strncpy (buf, tail, sizeof(buf) - 1);
|
||||||
|
strncpy (tail, rest, sizeof(tail) - 1);
|
||||||
|
strncat (tail, buf, sizeof(tail) - 1 - strlen(tail));
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy (buf, tail, sizeof(buf) - 1);
|
||||||
|
strncpy (tail, dns_prefix, sizeof(tail) - 1);
|
||||||
|
strncat (tail, domain, sizeof(tail) - 1 - strlen(tail));
|
||||||
|
strncat (tail, buf, sizeof(tail) - 1 - strlen(tail));
|
||||||
|
return ErrResolveLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup using libresolv -lresolv
|
||||||
|
int ipfs_dnslink_resolv_lookupTXT(char ***txt, char *domain)
|
||||||
|
{
|
||||||
|
char buf[4096], *p;
|
||||||
|
int responseLength;
|
||||||
|
int i, l, n = 0;
|
||||||
|
ns_msg query_parse_msg;
|
||||||
|
ns_rr query_parse_rr;
|
||||||
|
u_char responseByte[4096];
|
||||||
|
|
||||||
|
// Use res_query from libresolv to retrieve TXT record from DNS server.
|
||||||
|
if ((responseLength = res_query(domain,C_IN,T_TXT,responseByte,sizeof(responseByte))) < 0 ||
|
||||||
|
ns_initparse(responseByte,responseLength,&query_parse_msg) < 0) {
|
||||||
|
return ErrResolveFailed;
|
||||||
|
} else {
|
||||||
|
l = sizeof (buf);
|
||||||
|
buf[--l] = '\0';
|
||||||
|
p = buf;
|
||||||
|
// save every TXT record to buffer separating with a \0
|
||||||
|
for (i=0 ; i < ns_msg_count(query_parse_msg,ns_s_an) ; i++) {
|
||||||
|
if (ns_parserr(&query_parse_msg,ns_s_an,i,&query_parse_rr)) {
|
||||||
|
return ErrResolveFailed;
|
||||||
|
} else {
|
||||||
|
char *rdata = ns_rr_rdata(query_parse_rr);
|
||||||
|
memcpy(p, rdata+1, *rdata); // first byte is record length
|
||||||
|
p += *rdata; // update pointer
|
||||||
|
*p++ = '\0'; // mark end-of-record and update pointer to next record.
|
||||||
|
n++; // update record count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// allocate array for all records + NULL pointer terminator.
|
||||||
|
*txt = calloc(n+1, sizeof(void*));
|
||||||
|
if (!*txt) {
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
l = p - buf; // length of all records in buffer.
|
||||||
|
p = malloc(l); // allocate memory that will be used as string data at *txt array.
|
||||||
|
if (!p) {
|
||||||
|
free(*txt);
|
||||||
|
*txt = NULL;
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
memcpy(p, buf, l); // transfer from buffer to allocated memory.
|
||||||
|
for (i = 0 ; i < n ; i++) {
|
||||||
|
*txt[i] = p; // save position of current record at *txt array.
|
||||||
|
p = memchr(p, '\0', l - (p - *txt[0])) + 1; // find next record position after next \0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipfs_dnslink_resolve_once implements resolver.
|
||||||
|
int ipfs_dnslink_resolve_once (char ***p, char *domain)
|
||||||
|
{
|
||||||
|
int err, i;
|
||||||
|
char **txt;
|
||||||
|
|
||||||
|
if (!p || !domain) {
|
||||||
|
return ErrInvalidParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = NULL;
|
||||||
|
|
||||||
|
if (!ipfs_isdomain_is_domain (domain)) {
|
||||||
|
return ErrInvalidDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ipfs_dnslink_lookup_txt) { // if not set
|
||||||
|
ipfs_dnslink_lookup_txt = ipfs_dnslink_resolv_lookupTXT; // use default libresolv
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ipfs_dnslink_lookup_txt (&txt, domain);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ErrResolveFailed;
|
||||||
|
for (i=0 ; txt[i] ; i++) {
|
||||||
|
err = ipfs_dnslink_parse_txt(*p, txt[i]);
|
||||||
|
if (!err) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(*txt);
|
||||||
|
free(txt);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipfs_dnslink_parse_txt parses a TXT record value for a dnslink value.
|
||||||
|
// The TXT record must follow the dnslink format:
|
||||||
|
// TXT dnslink=<path>
|
||||||
|
// TXT dnslink=/foo/bar/baz
|
||||||
|
// ipfs_dnslink_parse_txt will return ErrInvalidDNSLink if parsing fails.
|
||||||
|
int ipfs_dnslink_parse_txt (char **path, char *txt)
|
||||||
|
{
|
||||||
|
char **parts;
|
||||||
|
|
||||||
|
if (!path || !txt) {
|
||||||
|
return ErrInvalidParam;
|
||||||
|
}
|
||||||
|
parts = ipfs_path_split_n (txt, "=", 2);
|
||||||
|
if (!parts) {
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
if (ipfs_path_segments_length (parts) == 2 && strcmp(parts[0], "dnslink")==0 && memcmp(parts[1], "/", 1)==0) {
|
||||||
|
*path = ipfs_path_clean_path(parts[1]);
|
||||||
|
if (path) {
|
||||||
|
ipfs_path_free_segments (&parts);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipfs_path_free_segments (&parts);
|
||||||
|
*path = NULL;
|
||||||
|
return ErrInvalidDNSLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipfs_dnslink_parse_link_domain parses a domain from a dnslink path.
|
||||||
|
// The link path must follow the dnslink format:
|
||||||
|
// /dns/<domain>/<path>
|
||||||
|
// /dns/ipfs.io
|
||||||
|
// /dns/ipfs.io/blog/0-hello-worlds
|
||||||
|
// ipfs_dnslink_parse_link_domain will return ErrInvalidDNSLink if parsing
|
||||||
|
// fails, and ErrInvalidDomain if the domain is not valid.
|
||||||
|
int ipfs_dnslink_parse_link_domain (char **domain, char**rest, char *txt)
|
||||||
|
{
|
||||||
|
char **parts;
|
||||||
|
int parts_len;
|
||||||
|
|
||||||
|
if (!domain || !rest || !txt) {
|
||||||
|
return ErrInvalidParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
*domain = *rest = NULL;
|
||||||
|
|
||||||
|
parts = ipfs_path_split_n (txt, "/", 4);
|
||||||
|
parts_len = ipfs_path_segments_length(parts);
|
||||||
|
if (!parts || parts_len < 3 || parts[0][0]!='\0' || strcmp(parts[1], "dns") != 0) {
|
||||||
|
return ErrInvalidDNSLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! ipfs_isdomain_is_domain (parts[2])) {
|
||||||
|
ipfs_path_free_segments (&parts);
|
||||||
|
return ErrInvalidDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
*domain = malloc(strlen (parts[2]) + 1);
|
||||||
|
if (!*domain) {
|
||||||
|
ipfs_path_free_segments (&parts);
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
strcpy(*domain, parts[2]);
|
||||||
|
|
||||||
|
if (parts_len > 3) {
|
||||||
|
*rest = malloc(strlen (parts[3]) + 1);
|
||||||
|
if (!*rest) {
|
||||||
|
ipfs_path_free_segments (&parts);
|
||||||
|
free (*domain);
|
||||||
|
*domain = NULL;
|
||||||
|
return ErrAllocFailed;
|
||||||
|
}
|
||||||
|
strcpy(*rest, parts[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
26
include/ipfs/dnslink/dnslink.h
Normal file
26
include/ipfs/dnslink/dnslink.h
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef DNSLINK_H
|
||||||
|
#define DNSLINK_H
|
||||||
|
|
||||||
|
#include "ipfs/errs.h"
|
||||||
|
|
||||||
|
// DefaultDepthLimit controls how many dns links to resolve through before
|
||||||
|
// returning. Users can override this default.
|
||||||
|
#ifndef DefaultDepthLimit
|
||||||
|
#define DefaultDepthLimit 16
|
||||||
|
#endif
|
||||||
|
// MaximumDepthLimit governs the max number of recursive resolutions.
|
||||||
|
#ifndef MaximumDepthLimit
|
||||||
|
#define MaximumDepthLimit 256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef IPFS_DNSLINK_C
|
||||||
|
extern int (*ipfs_dnslink_lookup_txt)(char ***, char *);
|
||||||
|
#endif // IPFS_DNSLINK_C
|
||||||
|
|
||||||
|
int ipfs_dnslink_resolve (char **p, char *domain);
|
||||||
|
int ipfs_dnslink_resolve_n (char **p, char *d, int depth);
|
||||||
|
int ipfs_dnslink_resolv_lookupTXT(char ***txt, char *domain);
|
||||||
|
int ipfs_dnslink_resolve_once (char ***p, char *domain);
|
||||||
|
int ipfs_dnslink_parse_txt (char **path, char *txt);
|
||||||
|
int ipfs_dnslink_parse_link_domain (char **domain, char**rest, char *txt);
|
||||||
|
#endif // DNSLINK_H
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef IPFS_ERRS_H
|
#ifndef IPFS_ERRS_H
|
||||||
#define IPFS_ERRS_H
|
#define IPFS_ERRS_H
|
||||||
|
|
||||||
char *Err[] = {
|
char *Err[] = {
|
||||||
NULL,
|
NULL,
|
||||||
|
@ -14,14 +14,18 @@
|
||||||
"unrecognized validity type",
|
"unrecognized validity type",
|
||||||
"not a valid proquint string",
|
"not a valid proquint string",
|
||||||
"not a valid domain name",
|
"not a valid domain name",
|
||||||
"not a valid dnslink entry"
|
"not a valid dnslink entry",
|
||||||
// ErrBadPath is returned when a given path is incorrectly formatted
|
// ErrBadPath is returned when a given path is incorrectly formatted
|
||||||
"invalid 'ipfs ref' path",
|
"invalid 'ipfs ref' path",
|
||||||
// Paths after a protocol must contain at least one component
|
// Paths after a protocol must contain at least one component
|
||||||
"path must contain at least one component",
|
"path must contain at least one component",
|
||||||
"TODO: ErrCidDecode",
|
"TODO: ErrCidDecode",
|
||||||
NULL,
|
NULL,
|
||||||
"no link named %s under %s"
|
"no link named %s under %s",
|
||||||
|
"ErrInvalidParam",
|
||||||
|
// ErrResolveLimit is returned when a recursive resolution goes over
|
||||||
|
// the limit.
|
||||||
|
"resolve depth exceeded"
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
@ -36,11 +40,13 @@
|
||||||
ErrUnrecognizedValidity,
|
ErrUnrecognizedValidity,
|
||||||
ErrInvalidProquint,
|
ErrInvalidProquint,
|
||||||
ErrInvalidDomain,
|
ErrInvalidDomain,
|
||||||
ErrInvalidDNSLink
|
ErrInvalidDNSLink,
|
||||||
ErrBadPath,
|
ErrBadPath,
|
||||||
ErrNoComponents,
|
ErrNoComponents,
|
||||||
ErrCidDecode,
|
ErrCidDecode,
|
||||||
ErrNoLink,
|
ErrNoLink,
|
||||||
ErrNoLinkFmt
|
ErrNoLinkFmt,
|
||||||
|
ErrInvalidParam,
|
||||||
|
ErrResolveLimit
|
||||||
} ErrsIdx;
|
} ErrsIdx;
|
||||||
#endif // IPFS_ERRS_H
|
#endif // IPFS_ERRS_H
|
||||||
|
|
Loading…
Reference in a new issue