diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-10 03:05:47 +0000 |
---|---|---|
committer | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-10 03:05:47 +0000 |
commit | 416ef5e2bf59bb2e45066a1d5d91ac677c0f48e5 (patch) | |
tree | 1c746d673dcec7a8488c6ac51db8245411034376 /src/libs6dns/s6dns_engine.c | |
download | s6-dns-416ef5e2bf59bb2e45066a1d5d91ac677c0f48e5.tar.xz |
Initial commit
Diffstat (limited to 'src/libs6dns/s6dns_engine.c')
-rw-r--r-- | src/libs6dns/s6dns_engine.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/libs6dns/s6dns_engine.c b/src/libs6dns/s6dns_engine.c new file mode 100644 index 0000000..4145ef7 --- /dev/null +++ b/src/libs6dns/s6dns_engine.c @@ -0,0 +1,372 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/error.h> +#include <skalibs/tai.h> +#include <skalibs/stralloc.h> +#include <skalibs/mininetstring.h> +#include <skalibs/socket.h> +#include <skalibs/djbunix.h> +#include <skalibs/ip46.h> +#include <skalibs/random.h> +#include <s6-dns/s6dns-constants.h> +#include <s6-dns/s6dns-message-internal.h> +#include <s6-dns/s6dns-message.h> +#include <s6-dns/s6dns-engine.h> + + + /* Utility functions */ + +static inline int qdomain_diff (char const *s1, unsigned int n1, char const *s2, unsigned int n2) +{ + return (n1 < n2) ? -1 : (n1 > n2) ? 1 : case_diffb(s1, n1, s2) ; +} + +static int relevant (char const *q, unsigned int qlen, char const *ans, unsigned int anslen, int strict) +{ + { + s6dns_message_header_t h ; + uint16 id ; + s6dns_message_header_unpack(ans, &h) ; + if (!h.qr || h.opcode || h.z || (h.counts.qd != 1)) return 0 ; + if (h.rd != (q[2] & 1)) return 0 ; + if (strict && !h.aa && !(q[2] & 1)) return 0 ; + uint16_unpack_big(q, &id) ; + if (id != h.id) return 0 ; + } + { + char buf[255] ; + unsigned int pos = 12 ; + unsigned int n = s6dns_message_get_domain_internal(buf, 255, ans, anslen, &pos) ; + if (!n) return -1 ; + if (pos + 4 > anslen) return (errno = EPROTO, -1) ; + if (qdomain_diff(buf, n, q + 12, qlen - 16)) return 0 ; + if (byte_diff(q + qlen - 4, 4, ans + pos)) return 0 ; + } + return 1 ; +} + + + /* Network core functions: transport-dependent */ + +#ifdef SKALIBS_IPV6_ENABLED +# define socketbind46(fd, ip, port, flag) ((flag) ? socket_bind6(fd, ip, port) : socket_bind4(fd, ip, port)) +# define socketudp46(flag) ((flag) ? socket_udp6() : socket_udp4()) +# define sockettcp46(flag) ((flag) ? socket_tcp6() : socket_tcp4()) +# define socketconnect46(fd, ip, port, flag) ((flag) ? socket_connect6(fd, ip, port) : socket_connect4(fd, ip, port)) +# define S6DNS_ENGINE_LOCAL0 IP6_ANY +#else +# define socketbind46(fd, ip, port, flag) ((void)(flag), socket_bind4(fd, ip, port)) +# define socketudp46(flag) socket_udp4() +# define sockettcp46(flag) socket_tcp4() +# define socketconnect46(fd, ip, port, flag) socket_connect4(fd, ip, port) +# define S6DNS_ENGINE_LOCAL0 "\0\0\0" +#endif + +static int randombind (int fd, int flag) +{ + register unsigned int i = 0 ; + for (; i < 10 ; i++) + if (socketbind46(fd, S6DNS_ENGINE_LOCAL0, 1025 + badrandom_int(64510), flag) >= 0) return 1 ; + return (socketbind46(fd, S6DNS_ENGINE_LOCAL0, 0, flag) >= 0) ; +} + +static int thisudp (s6dns_engine_t *dt, tain_t const *stamp) +{ + for (;; dt->curserver++) + { + if (dt->curserver >= S6DNS_MAX_SERVERS) + { + dt->curserver = 0 ; + if (++dt->protostate >= 4) return -1 ; + } + if (byte_diff(s6dns_ip46list_ip(&dt->servers, dt->curserver), SKALIBS_IP_SIZE, S6DNS_ENGINE_LOCAL0)) break ; + } + if (badrandom_string(dt->sa.s + 2, 2) < 2) return 0 ; /* random query id */ + dt->fd = socketudp46(s6dns_ip46list_is6(&dt->servers, dt->curserver)) ; + if (dt->fd < 0) return 0 ; + if (!randombind(dt->fd, s6dns_ip46list_is6(&dt->servers, dt->curserver))) goto err ; /* random source port */ + if ((socketconnect46(dt->fd, s6dns_ip46list_ip(&dt->servers, dt->curserver), 53, s6dns_ip46list_is6(&dt->servers, dt->curserver)) < 0) + && (errno != EINPROGRESS)) goto err ; + tain_add(&dt->localdeadline, stamp, &tain_infinite_relative) ; + dt->flagreading = 0 ; + dt->flagwriting = 1 ; + if (dt->debughook && dt->debughook->pre_send) (*dt->debughook->pre_send)(dt, dt->debughook->external) ; + return 1 ; + err: + { + register int e = errno ; + fd_close(dt->fd) ; dt->fd = -1 ; + errno = e ; + } + return 0 ; +} + +static int thistcp (s6dns_engine_t *dt, tain_t const *stamp) +{ + for (; dt->curserver < S6DNS_MAX_SERVERS ; dt->curserver++) + if (byte_diff(s6dns_ip46list_ip(&dt->servers, dt->curserver), SKALIBS_IP_SIZE, S6DNS_ENGINE_LOCAL0)) break ; + if (dt->curserver >= S6DNS_MAX_SERVERS) return -1 ; + if (badrandom_string(dt->sa.s + 2, 2) < 2) return 0 ; /* random query id */ + dt->fd = sockettcp46(s6dns_ip46list_is6(&dt->servers, dt->curserver)) ; + if (dt->fd < 0) return 0 ; + if (!randombind(dt->fd, s6dns_ip46list_is6(&dt->servers, dt->curserver))) goto err ; /* random source port */ + if ((socketconnect46(dt->fd, s6dns_ip46list_ip(&dt->servers, dt->curserver), 53, s6dns_ip46list_is6(&dt->servers, dt->curserver)) < 0) + && (errno != EINPROGRESS)) goto err ; + tain_addsec(&dt->localdeadline, stamp, 10) ; + dt->protostate = 0 ; + dt->flagtcp = dt->flagconnecting = dt->flagwriting = 1 ; + dt->flagreading = 0 ; + if (dt->debughook && dt->debughook->pre_send) (*dt->debughook->pre_send)(dt, dt->debughook->external) ; + return 1 ; + err: + { + register int e = errno ; + fd_close(dt->fd) ; dt->fd = -1 ; + errno = e ; + } + return 0 ; +} + + + /* all the rest is transport-agnostic */ + +static int s6dns_engine_prepare (s6dns_engine_t *dt, tain_t const *stamp, int istcp) +{ + for (;; dt->curserver++) + switch (istcp ? thistcp(dt, stamp) : thisudp(dt, stamp)) + { + case -1 : return (errno = ENETUNREACH, 0) ; + case 0 : break ; + case 1 : return 1 ; + default : return (errno = EDOM, 0) ; /* can't happen */ + } +} + +static void prepare_next (s6dns_engine_t *dt, tain_t const *stamp, int istcp) +{ + if (!error_isagain(errno)) + { + fd_close(dt->fd) ; + dt->curserver++ ; + if (s6dns_engine_prepare(dt, stamp, istcp)) errno = EAGAIN ; + } +} + +static int s6dns_engine_write_udp (s6dns_engine_t *dt, tain_t const *stamp) +{ + static unsigned int const s6dns_engine_udp_timeouts[4] = { 1, 3, 11, 45 } ; + if (fd_send(dt->fd, dt->sa.s + 2, dt->querylen - 2, 0) < (int)(dt->querylen - 2)) + return (prepare_next(dt, stamp, 0), 0) ; + tain_addsec(&dt->localdeadline, stamp, s6dns_engine_udp_timeouts[dt->protostate]) ; + dt->flagwriting = 0 ; + dt->flagreading = 1 ; + if (dt->debughook && dt->debughook->post_send) (*dt->debughook->post_send)(dt, dt->debughook->external) ; + return (errno = EAGAIN, 1) ; +} + +static int s6dns_engine_write_tcp (s6dns_engine_t *dt, tain_t const *stamp) +{ + unsigned int r ; + r = allwrite(dt->fd, dt->sa.s + dt->protostate, dt->querylen - dt->protostate) ; + dt->protostate += r ; + if (r) dt->flagconnecting = 0 ; + if (dt->protostate < dt->sa.len) + { + if ((errno == ECONNRESET) && dt->flagconnecting) errno = EAGAIN ; + prepare_next(dt, stamp, 1) ; + return 0 ; + } + dt->protostate = 0 ; + tain_addsec(&dt->localdeadline, stamp, 10) ; + dt->flagwriting = 0 ; + dt->flagreading = 1 ; + if (dt->debughook && dt->debughook->post_send) (*dt->debughook->post_send)(dt, dt->debughook->external) ; + return (errno = EAGAIN, 1) ; +} + +static int s6dns_engine_read_udp (s6dns_engine_t *dt, tain_t const *stamp) +{ + s6dns_message_header_t h ; + char buf[513] ; + register int r = fd_recv(dt->fd, buf, 513, 0) ; + if (r < 0) return (prepare_next(dt, stamp, 0), 0) ; + if ((r > 512) || (r < 12)) return (errno = EAGAIN, 0) ; + switch (relevant(dt->sa.s + 2, dt->querylen - 2, buf, r, dt->flagstrict)) + { + case -1 : if (!dt->flagstrict) prepare_next(dt, stamp, 0) ; return 0 ; + case 0 : return (errno = EAGAIN, 0) ; + case 1 : break ; + default : return (errno = EDOM, 0) ; /* can't happen */ + } + if (dt->debughook && dt->debughook->post_recv) + { + if (!stralloc_catb(&dt->sa, buf, r)) return 0 ; + (*dt->debughook->post_recv)(dt, dt->debughook->external) ; + dt->sa.len = dt->querylen ; + } + s6dns_message_header_unpack(buf, &h) ; + if (h.tc) + { + fd_close(dt->fd) ; + dt->curserver = 0 ; + dt->protostate = 0 ; + if (s6dns_engine_prepare(dt, stamp, 1)) errno = EAGAIN ; + return 0 ; + } + switch (h.rcode) + { + case 0 : case 3 : break ; /* normal operation */ + case 1 : case 4 : case 5 : + byte_zero(s6dns_ip46list_ip(&dt->servers, dt->curserver), SKALIBS_IP_SIZE) ; /* do not query it again */ + default : prepare_next(dt, stamp, 0) ; return 0 ; + } + if (!stralloc_copyb(&dt->sa, buf, r)) + { + register int e = errno ; + fd_close(dt->fd) ; dt->fd = -1 ; + errno = e ; + return 0 ; + } + dt->querylen = 0 ; + fd_close(dt->fd) ; dt->fd = -1 ; + dt->flagreading = 0 ; + return 1 ; +} + +static int s6dns_engine_read_tcp (s6dns_engine_t *dt, tain_t const *stamp) +{ + register int r = sanitize_read(mininetstring_read(dt->fd, &dt->sa, &dt->protostate)) ; + if (r < 0) return (prepare_next(dt, stamp, 1), 0) ; + else if (!r) return (errno = EAGAIN, 0) ; + else if ((dt->sa.len - dt->querylen) < 12) + { + errno = EPROTO ; + goto badanswer ; + } + else + { + s6dns_message_header_t h ; + switch (relevant(dt->sa.s + 2, dt->querylen - 2, dt->sa.s + dt->querylen, dt->sa.len - dt->querylen, dt->flagstrict)) + { + case -1 : if (dt->flagstrict) { dt->sa.len = dt->querylen ; return 0 ; } + case 0 : goto badanswer ; + case 1 : break ; + default : dt->sa.len = dt->querylen ; return (errno = EDOM, 0) ; /* can't happen */ + } + if (dt->debughook && dt->debughook->post_recv) (*dt->debughook->post_recv)(dt, dt->debughook->external) ; + s6dns_message_header_unpack(dt->sa.s + dt->querylen, &h) ; + if (h.tc) goto badanswer ; + switch (h.rcode) + { + case 0 : case 3 : break ; /* normal operation */ + case 1 : case 4 : case 5 : + byte_zero(s6dns_ip46list_ip(&dt->servers, dt->curserver), SKALIBS_IP_SIZE) ; /* do not query it again */ + default : goto badanswer ; + } + fd_close(dt->fd) ; dt->fd = -1 ; + dt->flagreading = 0 ; + return 1 ; + } + badanswer: + dt->sa.len = dt->querylen ; + prepare_next(dt, stamp, 1) ; + return 0 ; +} + + +void s6dns_engine_recycle (s6dns_engine_t *dt) +{ + dt->sa.len = 0 ; + dt->querylen = 0 ; + byte_zero(&dt->servers, sizeof(s6dns_ip46list_t)) ; + if (dt->fd >= 0) + { + register int e = errno ; + fd_close(dt->fd) ; + dt->fd = -1 ; + errno = e ; + } + dt->status = ECONNABORTED ; + dt->flagstrict = dt->flagtcp = dt->flagconnecting = dt->flagreading = dt->flagwriting = 0 ; +} + +int s6dns_engine_timeout (s6dns_engine_t *dt, tain_t const *stamp) +{ + if (!error_isagain(dt->status)) return (errno = EINVAL, -1) ; + else if (tain_less(&dt->deadline, stamp)) goto yes ; + else if (!tain_less(&dt->localdeadline, stamp)) return 0 ; + else if (dt->flagwriting) goto yes ; + else if (!dt->flagreading) return 0 ; + fd_close(dt->fd) ; + dt->curserver++ ; + if (!s6dns_engine_prepare(dt, stamp, dt->flagtcp)) + { + s6dns_engine_recycle(dt) ; + dt->status = errno ; + return -1 ; + } + return 0 ; + yes: + s6dns_engine_recycle(dt) ; + dt->status = ETIMEDOUT ; + return 1 ; +} + +int s6dns_engine_event (s6dns_engine_t *dt, tain_t const *stamp) +{ + if (!error_isagain(dt->status)) return (errno = EINVAL, -1) ; + if (dt->flagwriting) + dt->flagtcp ? s6dns_engine_write_tcp(dt, stamp) : s6dns_engine_write_udp(dt, stamp) ; + else if (dt->flagreading) + { + if ((dt->flagtcp) ? s6dns_engine_read_tcp(dt, stamp) : s6dns_engine_read_udp(dt, stamp)) + { + dt->status = 0 ; + return 1 ; + } + } + else return (errno = EINVAL, -1) ; + if (error_isagain(errno)) return 0 ; + s6dns_engine_recycle(dt) ; + dt->status = errno ; + return -1 ; +} + +int s6dns_engine_init_r (s6dns_engine_t *dt, s6dns_ip46list_t const *servers, uint32 options, char const *q, unsigned int qlen, uint16 qtype, s6dns_debughook_t const *dbh, tain_t const *deadline, tain_t const *stamp) +{ + s6dns_message_header_t h = S6DNS_MESSAGE_HEADER_ZERO ; + if (!stralloc_ready(&dt->sa, qlen + 18)) return 0 ; + dt->deadline = *deadline ; + dt->localdeadline = *stamp ; + dt->querylen = qlen + 18 ; + dt->sa.len = dt->querylen ; + dt->servers = *servers ; + dt->debughook = dbh ; + dt->status = EAGAIN ; + dt->flagconnecting = dt->flagreading = dt->flagwriting = 0 ; + dt->flagstrict = !!(options & S6DNS_O_STRICT) ; + h.rd = !!(options & S6DNS_O_RECURSIVE) ; + h.counts.qd = 1 ; + uint16_pack_big(dt->sa.s, qlen + 16) ; + s6dns_message_header_pack(dt->sa.s + 2, &h) ; + byte_copy(dt->sa.s + 14, qlen, q) ; + uint16_pack_big(dt->sa.s + 14 + qlen, qtype) ; + uint16_pack_big(dt->sa.s + 16 + qlen, S6DNS_C_IN) ; + if (qlen > 496) dt->flagtcp = 1 ; + else + { + dt->flagtcp = 0 ; + dt->protostate = h.rd ; + } + if (!s6dns_engine_prepare(dt, stamp, dt->flagtcp)) + { + s6dns_engine_recycle(dt) ; + return 0 ; + } + return 1 ; +} |