dns-domain: add calls to join/split SRV/DNS-SD service domains

This adds dns_service_join() and dns_service_split() which may be used
to concatenate a DNS-SD service name, am SRV service type string, and a
domain name into a full resolvable DNS domain name string. If the
service name is specified as NULL, only the type and domain are
appended, to implement classic, non-DNS-SD SRV lookups.

The reverse is dns_service_split() which takes the full name, and split
it into the three components again.
This commit is contained in:
Lennart Poettering 2015-11-23 21:21:13 +01:00
parent a5ef363808
commit 0e8eedbb49
3 changed files with 240 additions and 18 deletions

View File

@ -751,6 +751,34 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len) {
return out - buffer;
}
static bool srv_type_label_is_valid(const char *label, size_t n) {
size_t k;
assert(label);
if (n < 2) /* Label needs to be at least 2 chars long */
return false;
if (label[0] != '_') /* First label char needs to be underscore */
return false;
/* Second char must be a letter */
if (!(label[1] >= 'A' && label[1] <= 'Z') &&
!(label[1] >= 'a' && label[1] <= 'z'))
return false;
/* Third and further chars must be alphanumeric or a hyphen */
for (k = 2; k < n; k++) {
if (!(label[k] >= 'A' && label[k] <= 'Z') &&
!(label[k] >= 'a' && label[k] <= 'z') &&
!(label[k] >= '0' && label[k] <= '9') &&
label[k] != '-')
return false;
}
return true;
}
int dns_srv_type_verify(const char *name) {
unsigned c = 0;
int r;
@ -760,7 +788,6 @@ int dns_srv_type_verify(const char *name) {
for (;;) {
char label[DNS_LABEL_MAX];
int k;
/* This more or less implements RFC 6335, Section 5.1 */
@ -770,28 +797,18 @@ int dns_srv_type_verify(const char *name) {
if (r < 0)
return r;
if (r == 0)
return c >= 2; /* At least two labels */
if (r < 2) /* Label needs to be at least 2 chars long */
return 0;
if (label[0] != '_') /* First label char needs to be underscore */
break;
if (c >= 2)
return 0;
/* Second char must be a letter */
if (!(label[1] >= 'A' && label[1] <= 'Z') &&
!(label[1] >= 'a' && label[1] <= 'z'))
if (!srv_type_label_is_valid(label, r))
return 0;
/* Third and further chars must be alphanumeric or a hyphen */
for (k = 2; k < r; k++) {
if (!(label[k] >= 'A' && label[k] <= 'Z') &&
!(label[k] >= 'a' && label[k] <= 'z') &&
!(label[k] >= '0' && label[k] <= '9') &&
label[k] != '-')
return 0;
}
c++;
}
return c == 2; /* exactly two labels */
}
bool dns_service_name_is_valid(const char *name) {
@ -816,3 +833,145 @@ bool dns_service_name_is_valid(const char *name) {
return true;
}
int dns_service_join(const char *name, const char *type, const char *domain, char **ret) {
_cleanup_free_ char *escaped = NULL, *n = NULL;
int r;
assert(type);
assert(domain);
assert(ret);
if (!dns_srv_type_verify(type))
return -EINVAL;
if (!name)
return dns_name_concat(type, domain, ret);
if (!dns_service_name_is_valid(name))
return -EINVAL;
r = dns_label_escape(name, strlen(name), &escaped);
if (r < 0)
return r;
r = dns_name_concat(type, domain, &n);
if (r < 0)
return r;
return dns_name_concat(escaped, n, ret);
}
static bool dns_service_name_label_is_valid(const char *label, size_t n) {
char *s;
assert(label);
if (memchr(label, 0, n))
return false;
s = strndupa(label, n);
return dns_service_name_is_valid(s);
}
int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) {
_cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL;
const char *p = joined, *q = NULL, *d = NULL;
char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX];
int an, bn, cn, r;
unsigned x = 0;
assert(joined);
/* Get first label from the full name */
an = dns_label_unescape(&p, a, sizeof(a));
if (an < 0)
return an;
if (an > 0) {
x++;
/* If there was a first label, try to get the second one */
bn = dns_label_unescape(&p, b, sizeof(b));
if (bn < 0)
return bn;
if (bn > 0) {
x++;
/* If there was a second label, try to get the third one */
q = p;
cn = dns_label_unescape(&p, c, sizeof(c));
if (cn < 0)
return cn;
if (cn > 0)
x++;
} else
cn = 0;
} else
an = 0;
if (x >= 2 && srv_type_label_is_valid(b, bn)) {
if (x >= 3 && srv_type_label_is_valid(c, cn)) {
if (dns_service_name_label_is_valid(a, an)) {
/* OK, got <name> . <type> . <type2> . <domain> */
name = strndup(a, an);
if (!name)
return -ENOMEM;
type = new(char, bn+1+cn+1);
if (!type)
return -ENOMEM;
strcpy(stpcpy(stpcpy(type, b), "."), c);
d = p;
goto finish;
}
} else if (srv_type_label_is_valid(a, an)) {
/* OK, got <type> . <type2> . <domain> */
name = NULL;
type = new(char, an+1+bn+1);
if (!type)
return -ENOMEM;
strcpy(stpcpy(stpcpy(type, a), "."), b);
d = q;
goto finish;
}
}
name = NULL;
type = NULL;
d = joined;
finish:
r = dns_name_normalize(d, &domain);
if (r < 0)
return r;
if (_domain) {
*_domain = domain;
domain = NULL;
}
if (_type) {
*_type = type;
type = NULL;
}
if (_name) {
*_name = name;
name = NULL;
}
return 0;
}

View File

@ -73,3 +73,6 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len);
int dns_srv_type_verify(const char *name);
bool dns_service_name_is_valid(const char *name);
int dns_service_join(const char *name, const char *type, const char *domain, char **ret);
int dns_service_split(const char *joined, char **name, char **type, char **domain);

View File

@ -332,7 +332,6 @@ static void test_dns_srv_type_verify(void) {
assert_se(dns_srv_type_verify("_http._tcp") > 0);
assert_se(dns_srv_type_verify("_foo-bar._tcp") > 0);
assert_se(dns_srv_type_verify("_w._udp") > 0);
assert_se(dns_srv_type_verify("_piep._sub._w._udp") > 0);
assert_se(dns_srv_type_verify("_a800._tcp") > 0);
assert_se(dns_srv_type_verify("_a-800._tcp") > 0);
@ -348,6 +347,65 @@ static void test_dns_srv_type_verify(void) {
assert_se(dns_srv_type_verify("_800._tcp") == 0);
assert_se(dns_srv_type_verify("_-800._tcp") == 0);
assert_se(dns_srv_type_verify("_-foo._tcp") == 0);
assert_se(dns_srv_type_verify("_piep._foo._udp") == 0);
}
static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) {
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL;
assert_se(dns_service_join(a, b, c, &t) == r);
assert_se(streq_ptr(t, d));
if (r < 0)
return;
assert_se(dns_service_split(t, &x, &y, &z) >= 0);
assert_se(streq_ptr(a, x));
assert_se(streq_ptr(b, y));
assert_se(streq_ptr(c, z));
}
static void test_dns_service_join(void) {
test_dns_service_join_one("", "", "", -EINVAL, NULL);
test_dns_service_join_one("", "_http._tcp", "", -EINVAL, NULL);
test_dns_service_join_one("", "_http._tcp", "foo", -EINVAL, NULL);
test_dns_service_join_one("foo", "", "foo", -EINVAL, NULL);
test_dns_service_join_one("foo", "foo", "foo", -EINVAL, NULL);
test_dns_service_join_one("foo", "_http._tcp", "", 0, "foo._http._tcp");
test_dns_service_join_one(NULL, "_http._tcp", "", 0, "_http._tcp");
test_dns_service_join_one("foo", "_http._tcp", "foo", 0, "foo._http._tcp.foo");
test_dns_service_join_one(NULL, "_http._tcp", "foo", 0, "_http._tcp.foo");
test_dns_service_join_one("Lennart's PC", "_pc._tcp", "foo.bar.com", 0, "Lennart\\039s\\032PC._pc._tcp.foo.bar.com");
test_dns_service_join_one(NULL, "_pc._tcp", "foo.bar.com", 0, "_pc._tcp.foo.bar.com");
}
static void test_dns_service_split_one(const char *joined, const char *a, const char *b, const char *c, int r) {
_cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *t = NULL;
assert_se(dns_service_split(joined, &x, &y, &z) == r);
assert_se(streq_ptr(x, a));
assert_se(streq_ptr(y, b));
assert_se(streq_ptr(z, c));
if (r < 0)
return;
if (y) {
assert_se(dns_service_join(x, y, z, &t) == 0);
assert_se(streq_ptr(joined, t));
} else
assert_se(!x && streq_ptr(z, joined));
}
static void test_dns_service_split(void) {
test_dns_service_split_one("", NULL, NULL, "", 0);
test_dns_service_split_one("foo", NULL, NULL, "foo", 0);
test_dns_service_split_one("foo.bar", NULL, NULL, "foo.bar", 0);
test_dns_service_split_one("_foo.bar", NULL, NULL, "_foo.bar", 0);
test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", "", 0);
test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", "", 0);
test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0);
}
int main(int argc, char *argv[]) {
@ -367,6 +425,8 @@ int main(int argc, char *argv[]) {
test_dns_name_to_wire_format();
test_dns_service_name_is_valid();
test_dns_srv_type_verify();
test_dns_service_join();
test_dns_service_split();
return 0;
}