[Dnsmasq-discuss] [PATCH v3] Support Cisco Umbrella/OpenDNS Device ID & Remote IP

Brian Hartvigsen brian.andrew at brianandjenny.com
Fri Apr 9 19:46:04 UTC 2021


This is based on the information at
https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic and
https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic2 .
Using --umbrella by itself will enable Remote IP reporting. This can not
be used for any policy filtering in Cisco Umbrella/OpenDNS. Additional
information can be supplied using specific option specifications,
multiple can be separated by a comma:

--umbrella=orgid:1234,deviceid=0123456789abcdef

Specifies that you want to report organization 1234 using device
0123456789abcdef. For Cisco Umbrella Enterprise, see "Register (Create)
a device" (https://docs.umbrella.com/umbrella-api/docs/create-a-device)
for how to get a Device ID and "Organization ID endpoint"
(https://docs.umbrella.com/umbrella-api/docs/organization-endpoint) to
get organizations ID. For OpenDNS Home Users, there is no organization,
see Registration API endpoint
(https://docs.umbrella.com/umbrella-api/docs/registration-api-endpoint2)
for how to get a Device ID. Asset ID should be ignored unless
specifically instructed to use by support.

Signed-off-by: Brian Hartvigsen <brian.andrew at brianandjenny.com>
---
 man/dnsmasq.8      |  8 +++++-
 src/dns-protocol.h |  1 +
 src/dnsmasq.h      |  7 +++++-
 src/edns0.c        | 63 ++++++++++++++++++++++++++++++++++++++++++++++
 src/option.c       | 59 ++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 135 insertions(+), 3 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index fce580f..eec77af 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -711,7 +711,13 @@ will add the /24 and /96 subnets of the requestor for IPv4 and IPv6 requestors,
 will add 1.2.3.0/24 for IPv4 requestors and ::/0 for IPv6 requestors.
 .B --add-subnet=1.2.3.4/24,1.2.3.4/24
 will add 1.2.3.0/24 for both IPv4 and IPv6 requestors.
-
+.TP
+.B --umbrella[=deviceid:<deviceid>[,orgid:<orgid>]]
+Embeds the requestor's IP address in DNS queries forwarded upstream.
+If device id or organization id are specified, the information is
+included in the forwarded queries and may be able to be used in
+filtering policies and reporting. The order of the deviceid and orgid
+attributes is irrelevant, but must be separated by a comma.
 .TP
 .B \-c, --cache-size=<cachesize>
 Set the size of dnsmasq's cache. The default is 150 names. Setting the cache size to zero disables caching. Note: huge cache size impacts performance.
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 941bea6..8ad1964 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -82,6 +82,7 @@
 #define EDNS0_OPTION_CLIENT_SUBNET  8     /* IANA */
 #define EDNS0_OPTION_NOMDEVICEID    65073 /* Nominum temporary assignment */
 #define EDNS0_OPTION_NOMCPEID       65074 /* Nominum temporary assignment */
+#define EDNS0_OPTION_UMBRELLA       20292 /* Cisco Umbrella temporary assignment */
 
 struct dns_header {
   u16 id;
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 1e21005..95dc8ae 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -270,7 +270,9 @@ struct event_desc {
 #define OPT_SINGLE_PORT    60
 #define OPT_LEASE_RENEW    61
 #define OPT_LOG_DEBUG      62
-#define OPT_LAST           63
+#define OPT_UMBRELLA       63
+#define OPT_UMBRELLA_DEVID 64
+#define OPT_LAST           65
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -1061,6 +1063,9 @@ extern struct daemon {
   int port, query_port, min_port, max_port;
   unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl;
   char *dns_client_id;
+  u32 umbrella_org;
+  u32 umbrella_asset;
+  u8 umbrella_device[8];
   struct hostsfile *addn_hosts;
   struct dhcp_context *dhcp, *dhcp6;
   struct ra_interface *ra_interfaces;
diff --git a/src/edns0.c b/src/edns0.c
index c85b318..7bd26b8 100644
--- a/src/edns0.c
+++ b/src/edns0.c
@@ -427,6 +427,66 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
    return 1;
 }
 
+/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for
+ * detailed information on packet formating.
+ */
+#define UMBRELLA_VERSION    1
+#define UMBRELLA_TYPESZ     2
+
+#define UMBRELLA_ASSET      0x0004
+#define UMBRELLA_ASSETSZ    sizeof(daemon->umbrella_asset)
+#define UMBRELLA_ORG        0x0008
+#define UMBRELLA_ORGSZ      sizeof(daemon->umbrella_org)
+#define UMBRELLA_IPV4       0x0010
+#define UMBRELLA_IPV6       0x0020
+#define UMBRELLA_DEVICE     0x0040
+#define UMBRELLA_DEVICESZ   sizeof(daemon->umbrella_device)
+
+struct umbrella_opt {
+  u8 magic[4];
+  u8 version;
+  u8 flags;
+  /* We have 4 possible fields since we'll never send both IPv4 and
+   * IPv6, so using the larger of the two to calculate max buffer size.
+   * Each field also has a type header.  So the following accounts for
+   * the type headers and each field size to get a max buffer size.
+   */
+  u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
+};
+
+static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+{
+  *cacheable = 0;
+
+  struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {}};
+  u8 *u = &opt.fields[0];
+
+  if (daemon->umbrella_org) {
+    PUTSHORT(UMBRELLA_ORG, u);
+    PUTLONG(daemon->umbrella_org, u);
+  }
+
+  int family = source->sa.sa_family;
+  PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u);
+  int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
+  memcpy(u, get_addrp(source, family), size);
+  u += size;
+
+  if (option_bool(OPT_UMBRELLA_DEVID)) {
+    PUTSHORT(UMBRELLA_DEVICE, u);
+    memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ);
+    u += UMBRELLA_DEVICESZ;
+  }
+
+  if (daemon->umbrella_asset) {
+    PUTSHORT(UMBRELLA_ASSET, u);
+    PUTLONG(daemon->umbrella_asset, u);
+  }
+
+  int len = u - &opt.magic[0];
+  return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, len, 0, 1);
+}
+
 /* Set *check_subnet if we add a client subnet option, which needs to checked 
    in the reply. Set *cacheable to zero if we add an option which the answer
    may depend on. */
@@ -445,6 +505,9 @@ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *l
   if (daemon->dns_client_id)
     plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, 
 			    (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
+
+  if (option_bool(OPT_UMBRELLA))
+    plen = add_umbrella_opt(header, plen, limit, source, cacheable);
   
   if (option_bool(OPT_CLIENT_SUBNET))
     {
diff --git a/src/option.c b/src/option.c
index 6de5914..e8926a4 100644
--- a/src/option.c
+++ b/src/option.c
@@ -170,6 +170,7 @@ struct myoption {
 #define LOPT_PXE_VENDOR    361
 #define LOPT_DYNHOST       362
 #define LOPT_LOG_DEBUG     363
+#define LOPT_UMBRELLA	   364
  
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -345,6 +346,7 @@ static const struct myoption opts[] =
     { "dhcp-ignore-clid", 0, 0,  LOPT_IGNORE_CLID },
     { "dynamic-host", 1, 0, LOPT_DYNHOST },
     { "log-debug", 0, 0, LOPT_LOG_DEBUG },
+	{ "umbrella", 2, 0, LOPT_UMBRELLA },
     { NULL, 0, 0, 0 }
   };
 
@@ -527,6 +529,7 @@ static struct {
   { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
   { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
   { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
+  { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -653,7 +656,7 @@ static char *canonicalise_opt(char *s)
   return ret;
 }
 
-static int atoi_check(char *a, int *res)
+static int numeric_check(char *a)
 {
   char *p;
 
@@ -666,10 +669,29 @@ static int atoi_check(char *a, int *res)
      if (*p < '0' || *p > '9')
        return 0;
 
+  return 1;
+}
+
+static int atoi_check(char *a, int *res)
+{
+  if (!numeric_check(a))
+    return 0;
   *res = atoi(a);
   return 1;
 }
 
+static int strtoul_check(char *a, u32 *res)
+{
+  if (!numeric_check(a))
+    return 0;
+  *res = strtoul(a, NULL, 10);
+  if (errno == ERANGE) {
+    errno = 0;
+    return 0;
+  }
+  return 1;
+}
+
 static int atoi_check16(char *a, int *res)
 {
   if (!(atoi_check(a, res)) ||
@@ -2409,6 +2431,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	daemon->dns_client_id = opt_string_alloc(arg);
       break;
 
+    case LOPT_UMBRELLA: /* --umbrella */
+      set_option_bool(OPT_UMBRELLA);
+      while (arg) {
+        comma = split(arg);
+        if (strstr(arg, "deviceid:")) {
+          arg += 9;
+          if (strlen(arg) != 16)
+              ret_err(gen_err);
+          for (char *p = arg; *p; p++) {
+            if (!isxdigit((int)*p))
+              ret_err(gen_err);
+          }
+          set_option_bool(OPT_UMBRELLA_DEVID);
+
+          u8 *u = daemon->umbrella_device;
+          char word[3];
+          for (u8 i = 0; i < sizeof(daemon->umbrella_device); i++, arg+=2) {
+            memcpy(word, &(arg[0]), 2);
+            *u++ = strtoul(word, NULL, 16);
+          }
+        }
+        else if (strstr(arg, "orgid:")) {
+          if (!strtoul_check(arg+6, &daemon->umbrella_org)) {
+            ret_err(gen_err);
+          }
+        }
+        else if (strstr(arg, "assetid:")) {
+          if (!strtoul_check(arg+8, &daemon->umbrella_asset)) {
+            ret_err(gen_err);
+          }
+        }
+        arg = comma;
+      }
+      break;
+
     case LOPT_ADD_MAC: /* --add-mac */
       if (!arg)
 	set_option_bool(OPT_ADD_MAC);
-- 
2.29.2




More information about the Dnsmasq-discuss mailing list