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

Petr Menšík pemensik at redhat.com
Wed Apr 7 13:42:00 UTC 2021


Hi Brian,

I maintain dnsmasq on RHEL. What is primary reason organisation ID is
embedded into each DNS query? It seems to me this is necessary only to
work around network address translations on the way from dnsmasq to its
forwarder. If no NAT is in the way, just source address should work as
unique identified for the organization, right?

How does it handle cases, when umbrella is already set its client? Ie. case:

[client]-->[dnsmasq1]-->[dnsmasq2]-->[OpenDNS]

When dnsmasq1 sets some umbrella id, should dnsmasq2 keep it and mark
with own orgid just queries without the extension? Should it be
configurable? Isn't there use case for it?

I would personally use DSCP [1] option to make different organizations
from the same source IP available. It has limited 6 bit numbers of IDs
available, but should be sufficient for low count of dnsmasq caches
behind one single public IP (from the view of OpenDNS).

[dnsmasq DSCP=4]--\
[dnsmasq DSCP=5]--+--[NAT router]---[OpenDNS]

Then OpenDNS can lookup orgid from sourceIP and DSCP pair, therefore it
would not contain orgid inside EDNS, but just at IP packet level. Of
course it would require more logic at OpenDNS, but that is the part
using those metadata anyway, isn't it?

Of course it leaves just 6 bit id for orgid, which might be not enough
in some cases.

Current dnsmasq allows setting of mark from original query. But it does
not allow setting dscp value or forwarding the one from incoming queries.

But I admit I am not sure any existing implementation can use incoming
DSCP in queries. ISC BIND9 can set DSCP for outgoing queries, but I
think it cannot use it in ACL matches for incoming queries. So maybe
even this more generic approach would require specialized addon to the
server.

Does it send deviceid on each single query? I guess orgid is required,
deviceid not much so. Do they both combined create unique pair? It seems
like Client Subnets[2] reversed. It seems privacy dangerous without TLS
encapsulation. It seems to me similar results could be archieved with
some faked IPv6 address and --add-subnet.

By the way, couldn't be used --add-cpe-id? It seems to me it serves
exactly the same purpose. Search EDNS0_OPTION_NOMCPEID in src/edns0.c.
Could just --add-cpe-id="orgid:deviceid" be used?

1. https://tools.ietf.org/html/rfc2474
2. https://tools.ietf.org/html/rfc7871

On 4/7/21 4:45 AM, Brian Hartvigsen wrote:
> 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>
> ---
>  src/dns-protocol.h |  1 +
>  src/dnsmasq.h      |  6 +++++-
>  src/edns0.c        | 53 ++++++++++++++++++++++++++++++++++++++++++++++
>  src/option.c       | 23 ++++++++++++++++++++
>  4 files changed, 82 insertions(+), 1 deletion(-)
> 
> 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..d4da132 100644
> --- a/src/dnsmasq.h
> +++ b/src/dnsmasq.h
> @@ -270,7 +270,8 @@ 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_LAST           64
>  
>  #define OPTION_BITS (sizeof(unsigned int)*8)
>  #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
> @@ -1061,6 +1062,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;
> +  int umbrella_org;
> +  int umbrella_asset;
> +  char *umbrella_device;
>    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..81dd929 100644
> --- a/src/edns0.c
> +++ b/src/edns0.c
> @@ -427,6 +427,56 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
>     return 1;
>  }
>  
> +#define UMBRELLA_ASSET  0x04
> +#define UMBRELLA_ORG    0x08
> +#define UMBRELLA_IPV4   0x10
> +#define UMBRELLA_IPV6   0x20
> +#define UMBRELLA_DEVICE 0x40
> +#define UMBRELLA_DEVICESZ  8
> +
> +static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source)
> +{
> +  /* https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic2 */
> +  int len;
> +  unsigned char umbrella_data[512] = "ODNS";
> +  unsigned char *u = &umbrella_data[4];
> +  *u++ = 0; // version
> +  *u++ = 0; // flags
> +
> +  if (daemon->umbrella_org) {
> +      *u++ = UMBRELLA_ORG;
> +      int org = htonl(daemon->umbrella_org);
> +      memcpy(u, (char *)&org, sizeof(int));
> +      u += sizeof(int);
> +  }
> +
> +  int family = source->sa.sa_family;
> +  int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
> +
> +  *u++ = family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6;
> +  memcpy(u, get_addrp(source, family), size);
> +  u += size;
> +
> +  if (daemon->umbrella_device) {
> +      *u++ = UMBRELLA_DEVICE;
> +      char word[3];
> +      for (int i = 0; i < UMBRELLA_DEVICESZ; i++) {
> +          memcpy(word, &(daemon->umbrella_device[i * 2]), 2);
> +          *u++ = strtoul(word, NULL, 16);
> +      }
> +  }
> +
> +  if (daemon->umbrella_asset) {
> +    *u++ = UMBRELLA_ASSET;
> +    int assest = htonl(daemon->umbrella_asset);
> +    memcpy(u, (char *)&assest, sizeof(int));
> +    u += sizeof(int);
> +  }
> +
> +  len = u - umbrella_data; // for the header
> +  return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&umbrella_data, 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 +495,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);
>    
>    if (option_bool(OPT_CLIENT_SUBNET))
>      {
> diff --git a/src/option.c b/src/option.c
> index 6de5914..7f4613f 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 }
>  }; 
>  
> @@ -2409,6 +2412,26 @@ 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:")) {
> +          if (strlen(arg+9) != 16) ret_err(_("Invalid Umbrella device ID"));
> +          daemon->umbrella_device = opt_string_alloc(arg+9);
> +        }
> +        else if (strstr(arg, "orgid:")) {
> +          if (!atoi_check(arg+6, &daemon->umbrella_org))
> +            ret_err(_("Invalid Umbrella organization ID"));
> +        }
> +        else if (strstr(arg, "assetid:")) {
> +          if (!atoi_check(arg+8, &daemon->umbrella_asset))
> +            ret_err(_("Invalid Umbrella asset ID"));
> +        }
> +        arg = comma;
> +      }
> +      break;
> +
>      case LOPT_ADD_MAC: /* --add-mac */
>        if (!arg)
>  	set_option_bool(OPT_ADD_MAC);
> 

-- 
Petr Menšík
Software Engineer
Red Hat, http://www.redhat.com/
email: pemensik at redhat.com
PGP: DFCF908DB7C87E8E529925BC4931CA5B6C9FC5CB

-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature
Type: application/pgp-signature
Size: 495 bytes
Desc: OpenPGP digital signature
URL: <http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20210407/f6324a6a/attachment.sig>


More information about the Dnsmasq-discuss mailing list