[Dnsmasq-discuss] [PATCH v3] Support Cisco Umbrella/OpenDNS Device ID & Remote IP
Simon Kelley
simon at thekelleys.org.uk
Tue Apr 13 23:34:40 UTC 2021
Patch applied.
Cheers,
Simon.
On 09/04/2021 20:46, 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>
> ---
> 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);
>
More information about the Dnsmasq-discuss
mailing list