From 786bd5d80b729f33f0a97f331e0cdecfc8a87b49 Mon Sep 17 00:00:00 2001 From: Jose Marcial Vieira Bisneto Date: Thu, 8 Dec 2016 23:29:50 -0300 Subject: [PATCH] Initial implementation of dnslink --- dnslink/dnslink.c | 295 +++++++++++++++++++++++++++++++++ include/ipfs/dnslink/dnslink.h | 26 +++ include/ipfs/errs.h | 16 +- 3 files changed, 332 insertions(+), 5 deletions(-) create mode 100644 dnslink/dnslink.c create mode 100644 include/ipfs/dnslink/dnslink.h diff --git a/dnslink/dnslink.c b/dnslink/dnslink.c new file mode 100644 index 0000000..d80fe2c --- /dev/null +++ b/dnslink/dnslink.c @@ -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 +#include +#include +#include +#include +#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= +// 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// +// /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; +} diff --git a/include/ipfs/dnslink/dnslink.h b/include/ipfs/dnslink/dnslink.h new file mode 100644 index 0000000..f04cb7c --- /dev/null +++ b/include/ipfs/dnslink/dnslink.h @@ -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 diff --git a/include/ipfs/errs.h b/include/ipfs/errs.h index e3c0707..c300dac 100644 --- a/include/ipfs/errs.h +++ b/include/ipfs/errs.h @@ -1,5 +1,5 @@ #ifndef IPFS_ERRS_H - #define IPFS_ERRS_H + #define IPFS_ERRS_H char *Err[] = { NULL, @@ -14,14 +14,18 @@ "unrecognized validity type", "not a valid proquint string", "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 "invalid 'ipfs ref' path", // Paths after a protocol must contain at least one component "path must contain at least one component", "TODO: ErrCidDecode", 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 { @@ -36,11 +40,13 @@ ErrUnrecognizedValidity, ErrInvalidProquint, ErrInvalidDomain, - ErrInvalidDNSLink + ErrInvalidDNSLink, ErrBadPath, ErrNoComponents, ErrCidDecode, ErrNoLink, - ErrNoLinkFmt + ErrNoLinkFmt, + ErrInvalidParam, + ErrResolveLimit } ErrsIdx; #endif // IPFS_ERRS_H