text: /*
   * $Id: hncp_sd.c $
   *
   * Author: Markus Stenberg <markus stenberg@iki.fi>
   *
   * Copyright (c) 2014-2015 cisco Systems, Inc.
   *
   * Created:       Tue Jan 14 14:04:22 2014 mstenber
   * Last modified: Tue Sep 15 10:57:39 2015 mstenber
   * Edit time:     696 min
   *
   */
  
  /* This module implements the HNCP-based service discovery support.
   *
   * By default, if this isn't enabled, _normal_ DNS based activity
   * across a network should still work (thanks to DNS servers being
   * transmitted as part of prefix options for delegated prefixes and
   * configured appropriately to the clients), but this module provides
   * two extra 'features':
   *
   * - dns-sd configuration for dnsmasq (both records and remote servers)
   *
   * - maintenance of running hybrid proxy on the desired interfaces
   */
  
  #include <unistd.h>
  #include <arpa/inet.h>
  #include <sys/wait.h>
  #include <libubox/md5.h>
  
  #include "hncp_sd.h"
  #include "hncp_i.h"
  #include "dns_util.h"
  #include "iface.h"
  
  #define DNS_PORT 53
  
  /* Provided to ohybridproxy */
  #define LOCAL_OHP_AF "-4"
  #define LOCAL_OHP_ADDRESS "127.0.0.2"
  #define LOCAL_OHP_PORT 54
  
  #define ARGS_MAX_LEN 4096
  #define ARGS_MAX_COUNT 256
  
  /* Different 'daemons' to be restarted/reconfigured */
  #define UPDATE_FLAG_DNSMASQ 1
  #define UPDATE_FLAG_OHP     2
  #define UPDATE_FLAG_PCP     4
  #define UPDATE_FLAG_DDZ     8
  
  /* TLVs to be updated (and possibly other internal state) */
  #define UPDATE_FLAG_LOCAL_DDZ 0x10
  #define UPDATE_FLAG_DOMAIN    0x20
  #define UPDATE_FLAG_RNAME     0x40
  
  #define UPDATE_FLAG_ALL     0x7F
  
  /* How long a timeout we schedule for the actual update (that occurs
   * in a timeout). This effectively sets an upper bound on how
   * frequently the dnsmasq/ohp scripts are called. If there's changes
   * that mutate system state, the timer gets reset. */
  #define UPDATE_TIMEOUT 3000
  
  /* As networks grow larger, there is no real guarantee this will
   * _ever_ get executed. Therefore, provide an upper bound at which
   * point we are guaranteed one update. Otherwise e.g. address
   * information may be invalid unacceptably long.*/
  #define MAXIMUM_UPDATE_DELAY 10000
  
  struct hncp_sd_struct
  {
    hncp hncp;
    dncp dncp;
  
    /* HNCP notification subscriber structure */
    dncp_subscriber_s subscriber;
  
    /* Mask of what we need to update (in a timeout or at republish(ddz)). */
    int should_update;
    struct uloop_timeout timeout;
    hnetd_time_t initial_timeout_set;
  
    /* What we were given as router name base (or just 'r' by default). */
    char router_name_base[DNS_MAX_ESCAPED_L_LEN];
    char router_name[DNS_MAX_ESCAPED_L_LEN];
    /* how many iterations we've added to routername to get our current one. */
    int router_name_iteration;
  
    /* Parameters received when created (pointers within owned by someone else) */
    hncp_sd_params_s p;
  
    /* State (md5) hashes used to keep track of what has been committed. */
    char dnsmasq_state[16];
    char ohp_state[16];
    char ddz_state[16];
    char pcp_state[16];
  
    /* Callbacks from other modules */
    struct iface_user iface;
    struct hncp_link_user link;
  };
  
  static void _should_update(hncp_sd sd, int v)
  {
    L_DEBUG("hncp_sd/should_update:%d", v);
    sd->should_update |= v;
    /* Reset the timeout. We intentionally will not configure anything
     * until the churn slows down.*/
    if (!sd->timeout.pending)
      sd->initial_timeout_set = hnetd_time();
    if ((sd->initial_timeout_set + MAXIMUM_UPDATE_DELAY) >
        (hnetd_time() + UPDATE_TIMEOUT))
      uloop_timeout_set(&sd->timeout, UPDATE_TIMEOUT);
    else
      L_DEBUG(" .. already pending, and should be run");
  }
  
  /* Convenience wrapper around MD5 hashing */
  static bool _sh_changed(md5_ctx_t *ctx, void *result)
  {
    char buf[16];
  
    md5_end(buf, ctx);
    if (memcmp(buf, result, 16))
      {
        memcpy(result, buf, 16);
        return true;
      }
    return false;
  }
  
  static int _push_reverse_ll(struct prefix *p, uint8_t *buf, int buf_len)
  {
    uint8_t *obuf = buf;
    int i;
  
    if (prefix_is_ipv4(p))
      {
        /* XXX - not sure what plen should be for IPv4 :p */
        /* We care only about last 4 bytes, and only full ones at that
         * (hopefully that will never be a problem). */
        for (i = p->plen / 8 - 1 ; i >= 12 ; i--)
          {
            unsigned char c = p->prefix.s6_addr[i];
            char tbuf[4];
            sprintf(tbuf, "%d", c);
  
            DNS_PUSH_LABEL_STRING(buf, buf_len, tbuf);
          }
        DNS_PUSH_LABEL_STRING(buf, buf_len, "in-addr");
      }
    else
      {
        for (i = p->plen / 4 - 1 ; i >= 0 ; i--)
          {
            unsigned char c = p->prefix.s6_addr[i / 2];
            char tbuf[2];
  
            if (i % 2)
              c = c & 0xF;
            else
              c = c >> 4;
            sprintf(tbuf, "%x", c);
  
            DNS_PUSH_LABEL_STRING(buf, buf_len, tbuf);
          }
        DNS_PUSH_LABEL_STRING(buf, buf_len, "ip6");
      }
    DNS_PUSH_LABEL_STRING(buf, buf_len, "arpa");
    DNS_PUSH_LABEL(buf, buf_len, (void *)1, 0);
    return buf - obuf;
  }
  
  const char *_rewrite_ifname(const char *src, char *dst)
  {
    if (!dst)
      return NULL;
    const char *s = src;
    char *d = dst;
    while (true)
      {
        char c = *s++;
        if (c && !(isalpha(c) || (s != (src + 1) && isalnum(c))))
          c = '_';
        *d++ = c;
        if (!c)
          break;
      }
    return dst;
  }
  
  #define REWRITE_IFNAME(ifname)                          \
    _rewrite_ifname(ifname, alloca(strlen(ifname)+1))
  
  void hncp_sd_dump_link_fqdn(hncp_sd sd, dncp_ep ep,
                              const char *ifname, char *buf, size_t buf_len)
  {
    if (ep && ep->dnsname[0])
      {
        if (strcmp(ep->ifname, ep->dnsname)
            && strchr(ep->dnsname, '.'))
          {
            strncpy(buf, ep->dnsname, buf_len);
            return;
          }
        ifname = ep->dnsname;
      }
    ifname = REWRITE_IFNAME(ifname);
    snprintf(buf, buf_len, "%s.%s.%s",
             ifname, sd->router_name, sd->hncp->domain);
  }
  
  static void _publish_ddz(hncp_sd sd, dncp_ep ep,
                           int flags_forward,
                           struct prefix *assigned_prefix)
  {
    hncp_t_dns_delegated_zone dh;
    unsigned char buf[sizeof(struct tlv_attr) +
                      sizeof(*dh) +
                      DNS_MAX_ESCAPED_LEN];
    char tbuf[DNS_MAX_ESCAPED_LEN];
  
    /* Forward DDZ handling (note: duplication doesn't matter) */
    dh = (void *)buf;
    memset(dh, 0, sizeof(*dh));
    struct in6_addr *a = hncp_get_ipv6_address(sd->hncp, ep->ifname);
    if (!a)
      return;
    *((struct in6_addr *)dh->address) = *a;
    hncp_sd_dump_link_fqdn(sd, ep, ep->ifname, tbuf, sizeof(tbuf));
    int r = escaped2ll(tbuf, dh->ll, DNS_MAX_ESCAPED_LEN);
    if (r < 0)
      return;
    int flen = sizeof(*dh) + r;
    dh->flags = flags_forward;
    dncp_add_tlv(sd->dncp, HNCP_T_DNS_DELEGATED_ZONE, dh, flen, 0);
  
    /* Reverse DDZ handling */
    /* (.ip6.arpa. or .in-addr.arpa.). */
    if (assigned_prefix)
      {
        r = _push_reverse_ll(assigned_prefix, dh->ll, DNS_MAX_ESCAPED_LEN);
        if (r < 0)
          return;
        flen = sizeof(*dh) + r;
        dh->flags = 0;
        dncp_add_tlv(sd->dncp, HNCP_T_DNS_DELEGATED_ZONE, dh, flen, 0);
      }
  }
  
  static void _publish_ddzs(hncp_sd sd)
  {
    dncp_tlv t;
    hncp_t_assigned_prefix_header ah;
    dncp_ep ep;
  
    if (!(sd->should_update & UPDATE_FLAG_LOCAL_DDZ))
      return;
    sd->should_update &= ~UPDATE_FLAG_LOCAL_DDZ;
    L_DEBUG("_publish_ddzs");
    (void)dncp_remove_tlvs_by_type(sd->dncp, HNCP_T_DNS_DELEGATED_ZONE);
    dncp_for_each_tlv(sd->dncp, t)
      if ((ah = hncp_tlv_ap(dncp_tlv_get_attr(t))))
        {
          ep = dncp_find_ep_by_id(sd->dncp, ah->ep_id);
  
          /* May be just race condition or whatever, silently ignore */
          if (!ep)
            continue;
  
          struct prefix p;
          p.plen = ah->prefix_length_bits;
          memcpy(&p.prefix, ah->prefix_data, ROUND_BITS_TO_BYTES(p.plen));
  
          _publish_ddz(sd, ep, HNCP_T_DNS_DELEGATED_ZONE_FLAG_BROWSE
                       | HNCP_T_DNS_DELEGATED_ZONE_FLAG_LEGACY_BROWSE, &p);
        }
  
    /*
     * Second stage: publish DDZs ALSO for any other interface, but if
     * and only if there is no corresponding DDZ already (which has
     * browse flag set -> duplicate detection would not work).
     *
     * This applies to cases where someone else publishes AP, but we
     * want old names to work (or 'all' names to work, depending on your
     * point of view).
     */
    dncp_for_each_enabled_ep(sd->dncp, ep)
      {
        bool found = false;
  
        dncp_for_each_tlv(sd->dncp, t)
          if ((ah = hncp_tlv_ap(dncp_tlv_get_attr(t))))
            {
              if (ah->ep_id == dncp_ep_get_id(ep))
                {
                  found = true;
                  break;
                }
            }
        if (found)
          continue;
        /* Not found -> produce forward DDZ only. */
        _publish_ddz(sd, ep, 0, NULL);
      }
  }
  
  bool hncp_sd_write_dnsmasq_conf(hncp_sd sd, const char *filename)
  {
    dncp_node n;
    struct tlv_attr *a;
    FILE *f = fopen(filename, "w");
    md5_ctx_t ctx;
  
    md5_begin(&ctx);
    if (!f)
      {
        L_ERR("unable to open %s for writing dnsmasq conf", filename);
        return false;
      }
    /* Basic idea: Traverse through the hncp node+tlv graph _once_,
     * producing appropriate configuration file.
     *
     * What do we need to take care of?
     * - <routername>.<domain>
     *
     * (These are all in DNS Delegated Zone TLVs)
     * - b._dns-sd._udp.<domain> => browseable domain
     * - lb._dns-sd._udp.<domain> => (legacy) browseable domain
     *
     * <subdomain>'s ~NS (remote, real IP)
     * <subdomain>'s ~NS (local, LOCAL_OHP_ADDRESS)
     */
    dncp_for_each_node(sd->dncp, n)
      {
        dncp_node_for_each_tlv_with_type(n, a, HNCP_T_NODE_NAME) {
          hncp_t_node_name rname = tlv_data(a);
          int namelen = tlv_len(a) - sizeof(hncp_t_node_name_s);
          if (namelen > 0 && namelen >= rname->name_length
              && rname->name_length && rname->name_length <= DNS_MAX_L_LEN)
            {
              md5_hash(rname, tlv_len(a), &ctx);
              fprintf(f, "host-record=%.*s.%s,%s\n",
                      rname->name_length, rname->name, sd->hncp->domain,
                      ADDR_REPR(&rname->address));
            }
        }
  
        dncp_node_for_each_tlv_with_type(n, a, HNCP_T_DNS_DELEGATED_ZONE)
          {
            /* Decode the labels */
            char buf[DNS_MAX_ESCAPED_LEN];
            char buf2[256];
            char *server;
            int port;
            hncp_t_dns_delegated_zone dh;
  
            if (tlv_len(a) < (sizeof(*dh)+1))
              continue;
  
            dh = tlv_data(a);
            if (ll2escaped(dh->ll, tlv_len(a) - sizeof(*dh),
                           buf, sizeof(buf)) < 0)
              continue;
  
            md5_hash(a, tlv_raw_len(a), &ctx);
  
            if (dh->flags & HNCP_T_DNS_DELEGATED_ZONE_FLAG_BROWSE)
              fprintf(f, "ptr-record=b._dns-sd._udp.%s,%s\n",
                      sd->hncp->domain, buf);
            if (dh->flags & HNCP_T_DNS_DELEGATED_ZONE_FLAG_LEGACY_BROWSE)
              fprintf(f, "ptr-record=lb._dns-sd._udp.%s,%s\n",
                      sd->hncp->domain, buf);
            if (dncp_node_is_self(n))
              {
                server = LOCAL_OHP_ADDRESS;
                port = LOCAL_OHP_PORT;
              }
            else
              {
                server = buf2;
                port = DNS_PORT;
                if (!inet_ntop(AF_INET6, dh->address,
                               buf2, sizeof(buf2)))
                  {
                    L_ERR("inet_ntop failed in hncp_sd_write_dnsmasq_conf");
                    continue;
                  }
              }
            fprintf(f, "server=/%s/%s#%d\n", buf, server, port);
          }
      }
    /* Default is 150. Given 0.5 second lifetime on service queries,
     * that's not much. */
    fprintf(f, "dns-forward-max=12345\n");
  
    /* RFC1918 rebinds are ok for the home domain */
    fprintf(f, "rebind-domain-ok=%s\n", sd->hncp->domain);
    fclose(f);
    return _sh_changed(&ctx, &sd->dnsmasq_state);
  }
  
  bool hncp_sd_restart_dnsmasq(hncp_sd sd)
  {
    char *args[] = { (char *)sd->p.dnsmasq_script, "restart", NULL};
  
    hncp_run(args);
    return true;
  }
  
  #define PUSH_ARG(s) do                                  \
      {                                                   \
        int _arg = narg++;                                \
        if (narg == ARGS_MAX_COUNT)                       \
          {                                               \
            L_DEBUG("too many arguments");                \
            return false;                                 \
          }                                               \
        if (strlen(s) + 1 + c > (buf + ARGS_MAX_LEN))     \
          {                                               \
            L_DEBUG("out of buffer");                     \
          }                                               \
        args[_arg] = c;                                   \
        strcpy(c, s);                                     \
        c += strlen(s) + 1;                               \
      } while(0)
  
  
  bool hncp_sd_reconfigure_ddz(hncp_sd sd)
  {
    dncp_node n;
    struct tlv_attr *a;
    char buf[ARGS_MAX_LEN];
    char *c = buf;
    char *args[ARGS_MAX_COUNT];
    int narg = 0;
    md5_ctx_t ctx;
  
    PUSH_ARG(sd->p.ddz_script);
    PUSH_ARG(sd->hncp->domain);
    md5_begin(&ctx);
    dncp_for_each_node(sd->dncp, n)
      {
        dncp_node_for_each_tlv_with_type(n, a, HNCP_T_DNS_DELEGATED_ZONE)
          {
            /* Decode the labels */
            char buf[DNS_MAX_ESCAPED_LEN];
            hncp_t_dns_delegated_zone dh = tlv_data(a);
  
            if (tlv_len(a) < (sizeof(*dh)+1))
              continue;
            if (!(dh->flags & HNCP_T_DNS_DELEGATED_ZONE_FLAG_BROWSE))
              continue;
            dh = tlv_data(a);
            if (ll2escaped(dh->ll, tlv_len(a) - sizeof(*dh),
                           buf, sizeof(buf)) < 0)
              continue;
  
            md5_hash(buf, strlen(buf), &ctx);
            PUSH_ARG(buf);
          }
      }
    if (_sh_changed(&ctx, &sd->ddz_state))
      {
        args[narg] = NULL;
        hncp_run(args);
        return true;
      }
    return false;
  }
  
  bool hncp_sd_reconfigure_ohp(hncp_sd sd)
  {
    dncp_ep ep;
    char buf[ARGS_MAX_LEN];
    char *c = buf;
    char *args[ARGS_MAX_COUNT];
    int narg = 0;
    char tbuf[DNS_MAX_ESCAPED_LEN+IFNAMSIZ+1];
    bool first = true;
    md5_ctx_t ctx;
  
    md5_begin(&ctx);
    PUSH_ARG(sd->p.ohp_script);
  
    /* ohp can _always_ listen to all interfaces that have been
     * configured. Less flapping of the binary, and it hurts nobody if
     * it is active on few more interfaces (well, fine, slight overhead
     * from mdnsresponder, but who cares). */
  
    for (struct iface *iface = iface_next(NULL); iface; iface = iface_next(iface))
      {
        if (!(iface->internal))
          continue;
  
        sprintf(tbuf, "%s=", iface->ifname);
        ep = dncp_find_ep_by_name(sd->dncp, iface->ifname);
        hncp_sd_dump_link_fqdn(sd, ep, iface->ifname,
                               tbuf+strlen(tbuf), sizeof(tbuf)-strlen(tbuf));
  
        md5_hash(tbuf, strlen(tbuf), &ctx);
        if (first)
          {
            char port[6];
            PUSH_ARG("start");
            PUSH_ARG(LOCAL_OHP_AF);
            PUSH_ARG("-a");
            PUSH_ARG(LOCAL_OHP_ADDRESS);
            PUSH_ARG("-p");
            sprintf(port, "%d", LOCAL_OHP_PORT);
            PUSH_ARG(port);
            first = false;
          }
        PUSH_ARG(tbuf);
      }
  
    if (first)
      {
        PUSH_ARG("stop");
      }
    args[narg] = NULL;
    if (_sh_changed(&ctx, &sd->ohp_state))
      {
        hncp_run(args);
        return true;
      }
    return false;
  }
  
  bool hncp_sd_reconfigure_pcp(hncp_sd sd)
  {
    char buf[ARGS_MAX_LEN];
    char *c = buf;
    char *args[ARGS_MAX_COUNT];
    int narg = 0;
    bool first = true;
    md5_ctx_t ctx;
    dncp_node n;
    struct tlv_attr *tlv, *a;
    hncp_t_node_address ra;
    hncp_t_delegated_prefix_header dp;
    char tbuf[123];
  
    md5_begin(&ctx);
    PUSH_ARG(sd->p.pcp_script);
  
    /* ohp can _always_ listen to all interfaces that have been
     * configured. Less flapping of the binary, and it hurts nobody if
     * it is active on few more interfaces (well, fine, slight overhead
     * from mdnsresponder, but who cares). */
  
    dncp_for_each_node(sd->dncp, n)
      {
        struct in6_addr *a4 = NULL, *a6 = NULL;
        dncp_node_for_each_tlv_with_type(n, tlv, HNCP_T_EXTERNAL_CONNECTION)
          {
            if (!a4 && !a6)
              {
                dncp_node_for_each_tlv(n, a)
                  {
                    if ((ra = hncp_tlv_ra(a)))
                      {
                        if (IN6_IS_ADDR_V4MAPPED(&ra->address))
                          a4 = &ra->address;
                        else
                          a6 = &ra->address;
                      }
                  }
                /* If we don't know address for real, might as well give up */
                if (!a4 && !a6)
                  {
                    L_DEBUG("no address at all found for %s",
                            DNCP_NODE_REPR(n));
                    break;
                  }
              }
            tlv_for_each_attr(a, tlv)
              {
                if ((dp = hncp_tlv_dp(a)))
                  {
                    struct prefix p = {.plen = dp->prefix_length_bits };
                    bmemcpy(&p.prefix, dp->prefix_data, 0, p.plen);
  
                    bool is_ipv4 = prefix_is_ipv4(&p);
                    struct in6_addr *sa = is_ipv4 ? a4 : a6;
                    if (!sa)
                      {
                        L_INFO("no PCP server found for %s", PREFIX_REPR(&p));
                        continue;
                      }
  
                    sprintf(tbuf, "%s=%s", PREFIX_REPR(&p),
                            dncp_node_is_self(n) ?
                            is_ipv4 ? "127.0.0.1" : "::1" :
                            ADDR_REPR(sa));
                    md5_hash(tbuf, strlen(tbuf), &ctx);
                    if (first)
                      {
                        PUSH_ARG("start");
                        first = false;
                      }
                    PUSH_ARG(tbuf);
                  }
              }
          }
      }
  
    if (first)
      PUSH_ARG("stop");
  
    args[narg] = NULL;
    if (_sh_changed(&ctx, &sd->pcp_state))
      {
        hncp_run(args);
        return true;
      }
    return false;
  }
  
  static void
  _election_cb(struct hncp_link_user *u,
               const char *ifname __unused,
               enum hncp_link_elected elected __unused)
  {
    hncp_sd sd = container_of(u, hncp_sd_s, link);
    _should_update(sd, UPDATE_FLAG_OHP | UPDATE_FLAG_LOCAL_DDZ);
  }
  
  static void
  _set_router_name(hncp_sd sd)
  {
    size_t namelen = strlen(sd->router_name);
    hncp_t_node_name rname = alloca(sizeof(*rname) + namelen);
    memcpy(rname->name, sd->router_name, namelen);
    rname->name_length = namelen;
  
    dncp_remove_tlvs_by_type(sd->dncp, HNCP_T_NODE_NAME);
  
    if (!iface_get_preferred_address(&rname->address, false, NULL))
      dncp_add_tlv(sd->dncp, HNCP_T_NODE_NAME,
                   rname, sizeof(*rname) + namelen, 0);
  
    if (!iface_get_preferred_address(&rname->address, true, NULL))
      dncp_add_tlv(sd->dncp, HNCP_T_NODE_NAME,
                   rname, sizeof(*rname) + namelen, 0);
  }
  
  static bool
  _tlv_router_name_matches(hncp_sd sd, struct tlv_attr *a)
  {
    if (tlv_id(a) == HNCP_T_NODE_NAME)
      {
        hncp_t_node_name rname = tlv_data(a);
        int namelen = tlv_len(a) - sizeof(*rname);
        if (namelen >= (int)strlen(sd->router_name)
            && rname->name_length == (int)strlen(sd->router_name)
            && memcmp(rname->name, sd->router_name, namelen) == 0)
          return true;
      }
    return false;
  }
  
  static bool
  _tlv_ddz_matches(hncp_sd sd, struct tlv_attr *a)
  {
    /* Create the buffer we want to match against. */
    char buf[DNS_MAX_ESCAPED_L_LEN];
    unsigned char tbuf[DNS_MAX_L_LEN];
    int len;
  
    sprintf(buf, "%s.%s", sd->router_name, sd->hncp->domain);
    if ((len = escaped2ll(buf, tbuf, sizeof(tbuf)))<0)
      return false;
    if (tlv_id(a) == HNCP_T_DNS_DELEGATED_ZONE)
      {
        hncp_t_dns_delegated_zone ddz = tlv_data(a);
        if (tlv_len(a) > sizeof(*ddz))
          {
            unsigned char *tbuf2 = ddz->ll;
            int len2 = tlv_len(a) - sizeof(*ddz);
            /* XXX - do we want len2 == len, or len2 >= len?  len2 >=
             * len also matches 'well behaved' routers with subdomain
             * names but no own router name TLV for some reason, so I'm
             * tempted to keep the equality to defend just the router
             * name using this logic. */
            if (len2 == len && memcmp(tbuf2 + (len2 - len), tbuf, len) == 0)
              return true;
          }
      }
    return false;
  }
  
  static dncp_node
  _find_router_name(hncp_sd sd)
  {
    dncp_node n;
    struct tlv_attr *a;
  
    dncp_for_each_node(sd->dncp, n)
      {
        dncp_node_for_each_tlv_with_type(n, a, HNCP_T_NODE_NAME)
          {
            if (_tlv_router_name_matches(sd, a))
              return n;
          }
      }
    return NULL;
  }
  
  static void
  _change_router_name(hncp_sd sd)
  {
    /* Try to look for new one. */
    while (1)
      {
        sprintf(sd->router_name, "%s%d",
                sd->router_name_base, ++sd->router_name_iteration);
        if (!_find_router_name(sd))
          {
            L_DEBUG("renamed to %s", sd->router_name);
            _set_router_name(sd);
            _should_update(sd, UPDATE_FLAG_LOCAL_DDZ);
            return;
          }
      }
  }
  
  static void _local_tlv_cb(dncp_subscriber s,
                            struct tlv_attr *tlv, bool add __unused)
  {
    hncp_sd sd = container_of(s, hncp_sd_s, subscriber);
  
    /* Note also assigned prefix changes here; they mean our published
     * zone information is no longer valid and should be republished at
     * some point. OHP configuration may also change at this point. */
    if (tlv_id(tlv) == HNCP_T_ASSIGNED_PREFIX)
      {
        _should_update(sd, UPDATE_FLAG_LOCAL_DDZ);
      }
  }
  
  static struct tlv_attr *_get_dns_domain_tlv(hncp_sd sd)
  {
    dncp_node n;
    struct tlv_attr *a, *best = NULL;
  
    dncp_for_each_node(sd->dncp, n)
      {
        dncp_node_for_each_tlv_with_type(n, a, HNCP_T_DOMAIN_NAME)
          best = a;
      }
    return best;
  }
  
  static void _get_dns_domain(hncp_sd sd, char *dest, int dest_len)
  {
    struct tlv_attr *a = _get_dns_domain_tlv(sd);
  
    if (a && ll2escaped(tlv_data(a), tlv_len(a), dest, dest_len) > 0)
      return;
    strncpy(dest, HNCP_SD_DEFAULT_DOMAIN, dest_len);
  }
  
  
  static void _refresh_domain(hncp_sd sd)
  {
    char new_domain[DNS_MAX_ESCAPED_LEN];
  
    if (!(sd->should_update & UPDATE_FLAG_DOMAIN))
      return;
    sd->should_update &= ~UPDATE_FLAG_DOMAIN;
    _get_dns_domain(sd, new_domain, sizeof(new_domain));
    L_DEBUG("_refresh_domain:%s", new_domain);
    if (strcmp(new_domain, sd->hncp->domain))
      {
        L_DEBUG("set sd domain to %s", new_domain);
        strcpy(sd->hncp->domain, new_domain);
        _should_update(sd, UPDATE_FLAG_ALL & ~UPDATE_FLAG_DOMAIN);
      }
  }
  
  static void _refresh_rname(hncp_sd sd)
  {
    if (!(sd->should_update & UPDATE_FLAG_RNAME))
      return;
    sd->should_update &= ~UPDATE_FLAG_RNAME;
    L_DEBUG("_refresh_rname");
    _set_router_name(sd);
  }
  
  static void _tlv_cb(dncp_subscriber s,
                      dncp_node n, struct tlv_attr *tlv, bool add)
  {
    hncp_sd sd = container_of(s, hncp_sd_s, subscriber);
    dncp o = sd->dncp;
  
    L_NOTICE("[sd]_tlv_cb %s %s %s",
             add ? "add" : "remove",
             dncp_node_is_self(n) ? "local" : DNCP_NODE_REPR(n),
             TLV_REPR(tlv));
    switch (tlv_id(tlv))
      {
      case HNCP_T_NODE_NAME:
        /* Handle router name collision detection; we're interested only in
         * nodes with higher router id overriding our choice. */
        if (add
            && !dncp_node_is_self(n)
            && _tlv_router_name_matches(sd, tlv))
          {
            if (dncp_node_cmp(n, dncp_get_own_node(o)) > 0)
              {
                L_DEBUG("router name conflict, we're lower, renaming");
                _change_router_name(sd);
              }
            else
              {
                L_DEBUG("router name conflict, we're higher, ignoring");
              }
          }
        /* Router name/address changes trigger dnsmasq update due to
         * synthesized <routername>.<domain> host records. */
        _should_update(sd, UPDATE_FLAG_DNSMASQ);
        break;
  
      case HNCP_T_DNS_DELEGATED_ZONE:
        /* Dnsmasq forwarder file reflects what's in published DDZ's. If
         * they change, it (could) change too. */
        _should_update(sd, UPDATE_FLAG_DNSMASQ | UPDATE_FLAG_DDZ);
  
        /* Check also if it's name matches our router name directly ->
         * rename us if it does. */
        if (_tlv_ddz_matches(sd, tlv) && !dncp_node_is_self(n))
          {
            L_DEBUG("found matching DDZ with our router name -> force rename");
            _change_router_name(sd);
          }
        break;
  
      case HNCP_T_DOMAIN_NAME:
        /* As the TLVs aren't _yet_ valid on the node, there's a race
         * condition potential here. So we just do things after a
         * timeout. */
        _should_update(sd, UPDATE_FLAG_DOMAIN);
        break;
  
      case HNCP_T_NODE_ADDRESS:
        /* Router name/address changes trigger dnsmasq update due to
         * synthesized <routername>.<domain> host records. */
        /* Addresses of where to find PCP server may also have changed */
        _should_update(sd, UPDATE_FLAG_DNSMASQ | UPDATE_FLAG_PCP);
        break;
  
      case HNCP_T_EXTERNAL_CONNECTION:
        /* Delegated prefixes may have changed -> possibly update PCP */
        _should_update(sd, UPDATE_FLAG_PCP);
        break;
  
      }
  }
  
  
  void hncp_sd_update(hncp_sd sd)
  {
    L_DEBUG("hncp_sd_update:%d", sd->should_update);
  
    /* First the always present, internal state mutating things.. */
    _refresh_rname(sd);
    _refresh_domain(sd);
    _publish_ddzs(sd);
  
    /* Then play with external scripts, if they are present */
    if (sd->should_update & UPDATE_FLAG_DNSMASQ)
      {
        sd->should_update &= ~UPDATE_FLAG_DNSMASQ;
        if (sd->p.dnsmasq_script && sd->p.dnsmasq_bonus_file)
          {
            if (hncp_sd_write_dnsmasq_conf(sd, sd->p.dnsmasq_bonus_file))
              hncp_sd_restart_dnsmasq(sd);
          }
      }
    if (sd->should_update & UPDATE_FLAG_DDZ)
      {
        sd->should_update &= ~UPDATE_FLAG_DDZ;
        if (sd->p.ddz_script)
          hncp_sd_reconfigure_ddz(sd);
      }
    if (sd->should_update & UPDATE_FLAG_OHP)
      {
        sd->should_update &= ~UPDATE_FLAG_OHP;
        if (sd->p.ohp_script)
          hncp_sd_reconfigure_ohp(sd);
      }
    if (sd->should_update & UPDATE_FLAG_PCP)
      {
        sd->should_update &= ~UPDATE_FLAG_PCP;
        if (sd->p.pcp_script)
          hncp_sd_reconfigure_pcp(sd);
      }
    if (sd->should_update)
      L_DEBUG("hncp_sd_update leftovers:%d", sd->should_update);
  }
  
  static void _timeout_cb(struct uloop_timeout *t)
  {
    hncp_sd sd = container_of(t, hncp_sd_s, timeout);
  
    hncp_sd_update(sd);
  }
  
  
  static void _republish_cb(dncp_subscriber s)
  {
    hncp_sd sd = container_of(s, hncp_sd_s, subscriber);
  
    _refresh_domain(sd);
    _publish_ddzs(sd);
  }
  
  static void _force_republish_cb(dncp_subscriber s, dncp_ep ep __unused,
                                  enum dncp_subscriber_event event __unused)
  {
    hncp_sd sd = container_of(s, hncp_sd_s, subscriber);
  
    _should_update(sd, UPDATE_FLAG_ALL);
  }
  
  static void _intaddr_cb(struct iface_user *u, __unused const char *ifname,
                          __unused const struct prefix *addr6,
                          __unused const struct prefix *addr4)
  {
    hncp_sd sd = container_of(u, hncp_sd_s, iface);
  
    _should_update(sd, UPDATE_FLAG_RNAME | UPDATE_FLAG_LOCAL_DDZ);
  }
  
  
  hncp_sd hncp_sd_create(hncp h, hncp_sd_params p, struct hncp_link *l)
  {
    hncp_sd sd = calloc(1, sizeof(*sd));
    dncp o = h->dncp;
  
    sd->hncp = h;
    sd->dncp = o;
    sd->timeout.cb = _timeout_cb;
    sd->p = *p;
    if (!sd)
      return NULL;
  
    sd->iface.cb_intaddr = _intaddr_cb;
    sd->link.cb_elected = _election_cb;
    iface_register_user(&sd->iface);
  
    if (l)
      hncp_link_register(l, &sd->link);
  
    /* Handle domain name */
    if (p->domain_name)
      {
        uint8_t ll[DNS_MAX_LL_LEN];
        int len;
        len = escaped2ll(p->domain_name, ll, sizeof(ll));
        if (len < 0)
          {
            L_ERR("invalid domain:%s", p->domain_name);
            abort();
          }
        dncp_add_tlv(o, HNCP_T_DOMAIN_NAME, ll, len, 0);
  
        strncpy(sd->hncp->domain, p->domain_name, DNS_MAX_ESCAPED_LEN);
      }
    else
      strcpy(sd->hncp->domain, HNCP_SD_DEFAULT_DOMAIN);
  
    /* Handle router name */
    if (p->router_name)
      strcpy(sd->router_name_base, p->router_name);
    else
      strcpy(sd->router_name_base, "r");
    strcpy(sd->router_name, sd->router_name_base);
    _set_router_name(sd);
  
    /* Set up the hncp subscriber */
    sd->subscriber.local_tlv_change_cb = _local_tlv_cb;
    sd->subscriber.tlv_change_cb = _tlv_cb;
    sd->subscriber.republish_cb = _republish_cb;
    sd->subscriber.ep_change_cb = _force_republish_cb;
    dncp_subscribe(o, &sd->subscriber);
  
    return sd;
  }
  
  void hncp_sd_destroy(hncp_sd sd)
  {
    iface_unregister_user(&sd->iface);
    dncp_unsubscribe(sd->dncp, &sd->subscriber);
    uloop_timeout_cancel(&sd->timeout);
    free(sd);
  }
  
  bool hncp_sd_busy(hncp_sd sd)
  {
    return sd->should_update;
  }
  