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:
parent
a5ef363808
commit
0e8eedbb49
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue