diff options
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/getaddrinfo.c | 322 | ||||
-rw-r--r-- | src/network/lookup.h | 26 | ||||
-rw-r--r-- | src/network/lookup_name.c | 168 | ||||
-rw-r--r-- | src/network/lookup_serv.c | 72 |
4 files changed, 360 insertions, 228 deletions
diff --git a/src/network/getaddrinfo.c b/src/network/getaddrinfo.c index 5d45be74..70b6cfda 100644 --- a/src/network/getaddrinfo.c +++ b/src/network/getaddrinfo.c @@ -1,249 +1,115 @@ #include <stdlib.h> -#include <stdio.h> -#include <netdb.h> -#include <netinet/in.h> #include <sys/socket.h> -#include <unistd.h> +#include <netinet/in.h> +#include <netdb.h> #include <string.h> -#include <ctype.h> -#include "__dns.h" -#include "stdio_impl.h" - -static int is_valid(const char *host) -{ - const unsigned char *s; - if (strlen(host)-1 > 254 || mbstowcs(0, host, 0) > 255) return 0; - for (s=(void *)host; *s>=0x80 || *s=='.' || *s=='-' || isalnum(*s); s++); - return !*s; -} - -#if 0 -static int have_af(int family) -{ - struct sockaddr_in6 sin6 = { .sin6_family = family }; - socklen_t sl = family == AF_INET - ? sizeof(struct sockaddr_in) - : sizeof(struct sockaddr_in6); - int sock = socket(family, SOCK_STREAM, 0); - int have = !bind(sock, (void *)&sin6, sl); - close(sock); - return have; -} -#endif - -union sa { - struct sockaddr_in sin; - struct sockaddr_in6 sin6; -}; - -struct aibuf { - struct addrinfo ai; - union sa sa; -}; - -/* Extra slots needed for storing canonical name */ -#define EXTRA ((256+sizeof(struct aibuf)-1)/sizeof(struct aibuf)) +#include "lookup.h" int getaddrinfo(const char *restrict host, const char *restrict serv, const struct addrinfo *restrict hint, struct addrinfo **restrict res) { - int flags = hint ? hint->ai_flags : 0; - int family = hint ? hint->ai_family : AF_UNSPEC; - int type = hint ? hint->ai_socktype : 0; - int proto = hint ? hint->ai_protocol : 0; - unsigned long port = 0; - struct aibuf *buf; - union sa sa = {{0}}; - unsigned char reply[1024]; - int i, j; - char line[512]; - FILE *f, _f; - unsigned char _buf[1024]; - char *z; - int result; - int cnt; - - if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC) - return EAI_FAMILY; - - if (host && strlen(host)>255) return EAI_NONAME; - if (serv && strlen(serv)>32) return EAI_SERVICE; - - if (type && !proto) - proto = type==SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP; - if (!type && proto) - type = proto==IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM; - - if (serv) { - if (!*serv) return EAI_SERVICE; - port = strtoul(serv, &z, 10); - if (*z) { - size_t servlen = strlen(serv); - char *end = line; - - if (flags & AI_NUMERICSERV) return EAI_SERVICE; + struct service ports[MAXSERVS]; + struct address addrs[MAXADDRS]; + char canon[256], *outcanon; + int nservs, naddrs, nais, canon_len, i, j, k; + int family = AF_UNSPEC, flags = 0, proto = 0; + struct aibuf { + struct addrinfo ai; + union sa { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } sa; + } *out; + + if (hint) { + family = hint->ai_family; + flags = hint->ai_flags; + proto = hint->ai_protocol; + + const int mask = AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | + AI_V4MAPPED | AI_ALL | AI_ADDRCONFIG | AI_NUMERICSERV; + if ((flags & mask) != flags) + return EAI_BADFLAGS; + + switch (family) { + case AF_INET: + case AF_INET6: + case AF_UNSPEC: + break; + default: + return EAI_FAMILY; + } - f = __fopen_rb_ca("/etc/services", &_f, _buf, sizeof _buf); - if (!f) return EAI_SERVICE; - while (fgets(line, sizeof line, f)) { - if (strncmp(line, serv, servlen) || !isspace(line[servlen])) - continue; - port = strtoul(line+servlen, &end, 10); - if (strncmp(end, proto==IPPROTO_UDP ? "/udp" : "/tcp", 4)) - continue; + switch (hint->ai_socktype) { + case SOCK_STREAM: + switch (proto) { + case 0: + proto = IPPROTO_TCP; + case IPPROTO_TCP: break; + default: + return EAI_SERVICE; } - __fclose_ca(f); - if (feof(f)) return EAI_SERVICE; - } - if (port > 65535) return EAI_SERVICE; - port = htons(port); - } - - if (!host) { - if (family == AF_UNSPEC) { - cnt = 2; family = AF_INET; - } else { - cnt = 1; - } - buf = calloc(sizeof *buf, cnt); - if (!buf) return EAI_MEMORY; - for (i=0; i<cnt; i++) { - if (i) family = AF_INET6; - buf[i].ai.ai_protocol = proto; - buf[i].ai.ai_socktype = type; - buf[i].ai.ai_addr = (void *)&buf[i].sa; - buf[i].ai.ai_addrlen = family==AF_INET6 - ? sizeof sa.sin6 : sizeof sa.sin; - buf[i].ai.ai_family = family; - buf[i].sa.sin.sin_family = family; - buf[i].sa.sin.sin_port = port; - if (i+1<cnt) buf[i].ai.ai_next = &buf[i+1].ai; - if (!(flags & AI_PASSIVE)) { - if (family == AF_INET) { - 0[(uint8_t*)&buf[i].sa.sin.sin_addr.s_addr]=127; - 3[(uint8_t*)&buf[i].sa.sin.sin_addr.s_addr]=1; - } else buf[i].sa.sin6.sin6_addr.s6_addr[15] = 1; + break; + case SOCK_DGRAM: + switch (proto) { + case 0: + proto = IPPROTO_UDP; + case IPPROTO_UDP: + break; + default: + return EAI_SERVICE; } + case 0: + break; + default: + return EAI_SOCKTYPE; } - *res = &buf->ai; - return 0; - } - - if (!*host) return EAI_NONAME; - - /* Try as a numeric address */ - if (__ipparse(&sa, family, host) >= 0) { - buf = calloc(sizeof *buf, 1+EXTRA); - if (!buf) return EAI_MEMORY; - family = sa.sin.sin_family; - buf->ai.ai_protocol = proto; - buf->ai.ai_socktype = type; - buf->ai.ai_addr = (void *)&buf->sa; - buf->ai.ai_addrlen = family==AF_INET6 ? sizeof sa.sin6 : sizeof sa.sin; - buf->ai.ai_family = family; - buf->ai.ai_canonname = (char *)host; - buf->sa = sa; - buf->sa.sin.sin_port = port; - *res = &buf->ai; - return 0; } - if (flags & AI_NUMERICHOST) return EAI_NONAME; + nservs = __lookup_serv(ports, serv, proto, flags); + if (nservs < 0) return nservs; - f = __fopen_rb_ca("/etc/hosts", &_f, _buf, sizeof _buf); - if (f) while (fgets(line, sizeof line, f)) { - char *p; - size_t l = strlen(host); + naddrs = __lookup_name(addrs, canon, host, family, flags); + if (naddrs < 0) return naddrs; - if ((p=strchr(line, '#'))) *p++='\n', *p=0; - for(p=line+1; (p=strstr(p, host)) && - (!isspace(p[-1]) || !isspace(p[l])); p++); - if (!p) continue; - __fclose_ca(f); - - /* Isolate IP address to parse */ - for (p=line; *p && !isspace(*p); p++); - *p++ = 0; - if (__ipparse(&sa, family, line) < 0) return EAI_NONAME; - - /* Allocate and fill result buffer */ - buf = calloc(sizeof *buf, 1+EXTRA); - if (!buf) return EAI_MEMORY; - family = sa.sin.sin_family; - buf->ai.ai_protocol = proto; - buf->ai.ai_socktype = type; - buf->ai.ai_addr = (void *)&buf->sa; - buf->ai.ai_addrlen = family==AF_INET6 ? sizeof sa.sin6 : sizeof sa.sin; - buf->ai.ai_family = family; - buf->sa = sa; - buf->sa.sin.sin_port = port; - - /* Extract first name as canonical name */ - for (; *p && isspace(*p); p++); - buf->ai.ai_canonname = (void *)(buf+1); - snprintf(buf->ai.ai_canonname, 256, "%s", p); - for (p=buf->ai.ai_canonname; *p && !isspace(*p); p++); - *p = 0; - if (!is_valid(buf->ai.ai_canonname)) - buf->ai.ai_canonname = 0; - - *res = &buf->ai; - return 0; - } - if (f) __fclose_ca(f); + nais = nservs * naddrs; + canon_len = strlen(canon); + out = calloc(1, nais * sizeof(*out) + canon_len + 1); + if (!out) return EAI_MEMORY; -#if 0 - f = __fopen_rb_ca("/etc/resolv.conf", &_f, _buf, sizeof _buf); - if (f) while (fgets(line, sizeof line, f)) { - if (!isspace(line[10]) || (strncmp(line, "search", 6) - && strncmp(line, "domain", 6))) continue; + if (canon_len) { + outcanon = (void *)&out[nais]; + memcpy(outcanon, canon, canon_len+1); + } else { + outcanon = 0; } - if (f) __fclose_ca(f); -#endif - /* Perform one or more DNS queries for host */ - memset(reply, 0, sizeof reply); - result = __dns_query(reply, host, family, 0); - if (result < 0) return result; - - cnt = __dns_count_addrs(reply, result); - if (cnt <= 0) return EAI_NONAME; - - buf = calloc(sizeof *buf, cnt+EXTRA); - if (!buf) return EAI_MEMORY; - - i = 0; - if (family != AF_INET6) { - j = __dns_get_rr(&buf[i].sa.sin.sin_addr, sizeof *buf, 4, cnt-i, reply, RR_A, 0); - while (j--) buf[i++].sa.sin.sin_family = AF_INET; - } - if (family != AF_INET) { - j = __dns_get_rr(&buf[i].sa.sin6.sin6_addr, sizeof *buf, 16, cnt-i, reply, RR_AAAA, 0); - while (j--) buf[i++].sa.sin.sin_family = AF_INET6; - } - if (result>1) { - j = __dns_get_rr(&buf[i].sa.sin.sin_addr, sizeof *buf, 4, cnt-i, reply+512, RR_A, 0); - while (j--) buf[i++].sa.sin.sin_family = AF_INET; - j = __dns_get_rr(&buf[i].sa.sin6.sin6_addr, sizeof *buf, 16, cnt-i, reply+512, RR_AAAA, 0); - while (j--) buf[i++].sa.sin.sin_family = AF_INET6; - } - - if (__dns_get_rr((void *)&buf[cnt], 0, 256, 1, reply, RR_CNAME, 1) <= 0) - strcpy((void *)&buf[cnt], host); - - for (i=0; i<cnt; i++) { - buf[i].ai.ai_protocol = proto; - buf[i].ai.ai_socktype = type; - buf[i].ai.ai_addr = (void *)&buf[i].sa; - buf[i].ai.ai_addrlen = buf[i].sa.sin.sin_family==AF_INET6 - ? sizeof sa.sin6 : sizeof sa.sin; - buf[i].ai.ai_family = buf[i].sa.sin.sin_family; - buf[i].sa.sin.sin_port = port; - buf[i].ai.ai_next = &buf[i+1].ai; - buf[i].ai.ai_canonname = (void *)&buf[cnt]; + for (k=i=0; i<naddrs; i++) for (j=0; j<nservs; j++, k++) { + out[k].ai = (struct addrinfo){ + .ai_family = addrs[i].family, + .ai_socktype = ports[j].proto == IPPROTO_TCP + ? SOCK_STREAM : SOCK_DGRAM, + .ai_protocol = ports[j].proto, + .ai_addrlen = addrs[i].family == AF_INET + ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6), + .ai_addr = (void *)&out[k].sa, + .ai_canonname = outcanon, + .ai_next = &out[k+1].ai }; + switch (addrs[i].family) { + case AF_INET: + out[k].sa.sin.sin_family = AF_INET; + out[k].sa.sin.sin_port = htons(ports[j].port); + memcpy(&out[k].sa.sin.sin_addr, &addrs[i].addr, 4); + break; + case AF_INET6: + out[k].sa.sin6.sin6_family = AF_INET6; + out[k].sa.sin6.sin6_port = htons(ports[j].port); + memcpy(&out[k].sa.sin6.sin6_addr, &addrs[i].addr, 16); + break; + } } - buf[cnt-1].ai.ai_next = 0; - *res = &buf->ai; - + out[nais-1].ai.ai_next = 0; + *res = &out->ai; return 0; } diff --git a/src/network/lookup.h b/src/network/lookup.h new file mode 100644 index 00000000..82c969ec --- /dev/null +++ b/src/network/lookup.h @@ -0,0 +1,26 @@ +#ifndef LOOKUP_H +#define LOOKUP_H + +#include <stdint.h> + +struct address { + int family; + unsigned scopeid; + uint8_t addr[16]; +}; + +struct service { + uint16_t port; + char proto; +}; + +/* The limit of 48 results is a non-sharp bound on the number of addresses + * that can fit in one 512-byte DNS packet full of v4 results and a second + * packet full of v6 results. Due to headers, the actual limit is lower. */ +#define MAXADDRS 48 +#define MAXSERVS 2 + +int __lookup_serv(struct service buf[static MAXSERVS], const char *name, int proto, int flags); +int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family, int flags); + +#endif diff --git a/src/network/lookup_name.c b/src/network/lookup_name.c new file mode 100644 index 00000000..b1f1ffd0 --- /dev/null +++ b/src/network/lookup_name.c @@ -0,0 +1,168 @@ +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include "lookup.h" +#include "stdio_impl.h" +#include "syscall.h" +#include "__dns.h" + +static int is_valid_hostname(const char *host) +{ + const unsigned char *s; + if (strnlen(host, 255)-1 > 254 || mbstowcs(0, host, 0) > 255) return 0; + for (s=(void *)host; *s>=0x80 || *s=='.' || *s=='-' || isalnum(*s); s++); + return !*s; +} + +static int name_from_null(struct address buf[static 2], const char *name, int family, int flags) +{ + int cnt = 0; + if (name) return 0; + if (flags & AI_PASSIVE) { + if (family != AF_INET6) + buf[cnt++] = (struct address){ .family = AF_INET }; + if (family != AF_INET) + buf[cnt++] = (struct address){ .family = AF_INET6 }; + } else { + if (family != AF_INET6) + buf[cnt++] = (struct address){ .family = AF_INET, .addr = { 127,0,0,1 } }; + if (family != AF_INET) + buf[cnt++] = (struct address){ .family = AF_INET6, .addr = { [15] = 1 } }; + } + return cnt; +} + +static int name_from_numeric(struct address buf[static 1], const char *name, int family) +{ + struct in_addr a4; + struct in6_addr a6; + if (family != AF_INET6 && inet_aton(name, &a4)>0) { + memcpy(&buf[0].addr, &a4, sizeof a4); + buf[0].family = AF_INET; + return 1; + } + if (family != AF_INET && inet_pton(AF_INET6, name, &a6)>0) { + memcpy(&buf[0].addr, &a6, sizeof a6); + buf[0].family = AF_INET6; + return 1; + } + return 0; +} + +static int name_from_hosts(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family) +{ + char line[512]; + size_t l = strlen(name); + int cnt = 0; + unsigned char _buf[1032]; + FILE _f, *f = __fopen_rb_ca("/etc/hosts", &_f, _buf, sizeof _buf); + if (!f) return 0; + while (fgets(line, sizeof line, f) && cnt < MAXADDRS) { + char *p, *z; + + if ((p=strchr(line, '#'))) *p++='\n', *p=0; + for(p=line+1; (p=strstr(p, name)) && + (!isspace(p[-1]) || !isspace(p[l])); p++); + if (!p) continue; + + /* Isolate IP address to parse */ + for (p=line; *p && !isspace(*p); p++); + *p++ = 0; + if (name_from_numeric(buf+cnt, line, family)) + cnt++; + + /* Extract first name as canonical name */ + for (; *p && isspace(*p); p++); + for (z=p; *z && !isspace(*z); z++); + *z = 0; + if (is_valid_hostname(p)) memcpy(canon, p, z-p+1); + } + __fclose_ca(f); + return cnt; +} + +static int name_from_dns(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family) +{ + unsigned char reply[1024] = { 0 }, *p = reply; + char tmp[256]; + int i, cnt = 0; + + /* Perform one or more DNS queries for host */ + int result = __dns_query(reply, name, family, 0); + if (result < 0) return result; + + for (i=0; i<result; i++) { + if (family != AF_INET6) { + int j = __dns_get_rr(&buf[cnt].addr, sizeof *buf, 4, MAXADDRS-cnt, p, RR_A, 0); + while (j--) buf[cnt++].family = AF_INET; + } + if (family != AF_INET) { + int j = __dns_get_rr(&buf[cnt].addr, sizeof *buf, 16, MAXADDRS-cnt, p, RR_AAAA, 0); + while (j--) buf[cnt++].family = AF_INET6; + } + p += 512; + } + __dns_get_rr(tmp, 0, 256, 1, reply, RR_CNAME, 1); + if (is_valid_hostname(tmp)) strcpy(canon, tmp); + return cnt; +} + +int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family, int flags) +{ + int cnt = 0, i, j; + + *canon = 0; + if (name) { + size_t l; + if ((l = strnlen(name, 255))-1 > 254) + return EAI_NONAME; + memcpy(canon, name, l+1); + } + + /* Procedurally, a request for v6 addresses with the v4-mapped + * flag set is like a request for unspecified family, followed + * by filtering of the results. */ + if (flags & AI_V4MAPPED) { + if (family == AF_INET6) family = AF_UNSPEC; + else flags -= AI_V4MAPPED; + } + + /* Try each backend until there's at least one result. */ + cnt = name_from_null(buf, name, family, flags); + if (cnt<=0) cnt = name_from_numeric(buf, name, family); + if (cnt<=0 && !(flags & AI_NUMERICHOST)) { + cnt = name_from_hosts(buf, canon, name, family); + if (cnt<=0) cnt = name_from_dns(buf, canon, name, family); + } + if (cnt<=0) return cnt ? cnt : EAI_NONAME; + + /* Filter/transform results for v4-mapped lookup, if requested. */ + if (flags & AI_V4MAPPED) { + if (!(flags & AI_ALL)) { + /* If any v6 results exist, remove v4 results. */ + for (i=0; i<cnt && buf[i].family != AF_INET6; i++); + if (i<cnt) { + for (j=0; i<cnt; i++) { + if (buf[i].family == AF_INET6) + buf[j++] = buf[i]; + } + cnt = i = j; + } + } + /* Translate any remaining v4 results to v6 */ + for (i=0; i<cnt; i++) { + if (buf[i].family != AF_INET) continue; + memcpy(buf[i].addr+12, buf[i].addr, 4); + memcpy(buf[i].addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12); + buf[i].scopeid = 0; + buf[i].family = AF_INET6; + } + } + + return cnt; +} diff --git a/src/network/lookup_serv.c b/src/network/lookup_serv.c new file mode 100644 index 00000000..bf4cba09 --- /dev/null +++ b/src/network/lookup_serv.c @@ -0,0 +1,72 @@ +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <ctype.h> +#include <string.h> +#include <fcntl.h> +#include "lookup.h" +#include "stdio_impl.h" + +int __lookup_serv(struct service buf[static MAXSERVS], const char *name, int proto, int flags) +{ + char line[128]; + int cnt = 0; + char *p, *z = ""; + unsigned long port = 0; + + if (name) { + if (!*name) return EAI_SERVICE; + port = strtoul(name, &z, 10); + } + if (!*z) { + if (port > 65535) return EAI_SERVICE; + if (proto != IPPROTO_UDP) { + buf[cnt].port = port; + buf[cnt++].proto = IPPROTO_TCP; + } + if (proto != IPPROTO_TCP) { + buf[cnt].port = port; + buf[cnt++].proto = IPPROTO_UDP; + } + return cnt; + } + + if (flags & AI_NUMERICSERV) return EAI_SERVICE; + + size_t l = strlen(name); + + unsigned char _buf[1032]; + FILE _f, *f = __fopen_rb_ca("/etc/services", &_f, _buf, sizeof _buf); + if (!f) return EAI_SERVICE; + + while (fgets(line, sizeof line, f) && cnt < MAXSERVS) { + if ((p=strchr(line, '#'))) *p++='\n', *p=0; + + /* Find service name */ + for(p=line; (p=strstr(p, name)); p++) { + if (p>line && !isspace(p[-1])) continue; + if (p[l] && !isspace(p[l])) continue; + break; + } + if (!p) continue; + + /* Skip past canonical name at beginning of line */ + for (p=line; *p && !isspace(*p); p++); + if (!p) continue; + + port = strtoul(p, &z, 10); + if (port > 65535 || z==p) continue; + if (!strncmp(z, "/udp", 4)) { + if (proto == IPPROTO_TCP) continue; + buf[cnt].port = port; + buf[cnt++].proto = IPPROTO_UDP; + } + if (!strncmp(z, "/tcp", 4)) { + if (proto == IPPROTO_UDP) continue; + buf[cnt].port = port; + buf[cnt++].proto = IPPROTO_TCP; + } + } + __fclose_ca(f); + return cnt > 0 ? cnt : EAI_SERVICE; +} |