Skip to content

Commit

Permalink
Support Cisco Umbrella/OpenDNS Device ID & Remote IP
Browse files Browse the repository at this point in the history
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@brianandjenny.com>
Signed-off-by: DL6ER <dl6er@dl6er.de>
  • Loading branch information
tresni authored and DL6ER committed Apr 14, 2021
1 parent bc76152 commit a27969c
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/dnsmasq/dns-protocol.h
Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion src/dnsmasq/dnsmasq.h
Expand Up @@ -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) )
Expand Down Expand Up @@ -1063,6 +1065,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;
Expand Down
63 changes: 63 additions & 0 deletions src/dnsmasq/edns0.c
Expand Up @@ -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. */
Expand All @@ -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))
{
Expand Down
59 changes: 58 additions & 1 deletion src/dnsmasq/option.c
Expand Up @@ -174,6 +174,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[] =
Expand Down Expand Up @@ -349,6 +350,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 }
};

Expand Down Expand Up @@ -531,6 +533,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 }
};

Expand Down Expand Up @@ -657,7 +660,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;

Expand All @@ -670,10 +673,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)) ||
Expand Down Expand Up @@ -2413,6 +2435,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);
Expand Down

0 comments on commit a27969c

Please sign in to comment.