taxonomy: add dhcpnametax

Various consumer electronics devices send their serial number
as their DHCP hostname, and the serial number includes enough
information to determine the model. For example:
    NP-D0XXXXXXXXXX       Roku HD-XR
    TIVO-849XXXXXXXXXXXX  TiVo BOLT
    01AXXXXXXXXXXXXX      Nest Thermostat v1

We require both the hostname and DHCP signature to prevent
inadvertent mis-identification of unfortunately named PCs
or other devices. Both bits of information need to match
for us to identify the device.

Example usage:

dhcpnametax -l 01:23:45:67:89:ab -d 3,1,252,42,15,6,12 -h 01AA01AB23456789
name 01:23:45:67:89:ab Nest Thermostat;Nest Thermostat v1

Change-Id: Id6f03f45194dd91776f731ccc335809be729f400
diff --git a/cmds/Makefile b/cmds/Makefile
index 98d26e5..069e6ff 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -24,6 +24,7 @@
 	buttonmon \
 	chg_mod_own \
 	cpulog \
+	dhcpnametax \
 	dhcpvendortax \
 	dhcp-rogue \
 	dir-monitor \
@@ -238,6 +239,13 @@
 		--includes --output-file=modellookup.c modellookup.gperf
 modellookup.o: CFLAGS += -Wno-missing-field-initializers
 host-modellookup.o: CFLAGS += -Wno-missing-field-initializers
+dhcpnametax: dhcpnametax.o hostnamelookup.o
+host-dhcpnametax: host-dhcpnametax.o host-hostnamelookup.o
+hostnamelookup.c: hostnamelookup.gperf
+	$(GPERF) -G -C -t -T -L ANSI-C -n -c -N hostname_lookup -K name --delimiters="|" \
+		--includes --output-file=hostnamelookup.c hostnamelookup.gperf
+hostnamelookup.o: CFLAGS += -Wno-missing-field-initializers
+host-hostnamelookup.o: CFLAGS += -Wno-missing-field-initializers
 
 
 TESTS = $(wildcard test-*.sh) $(wildcard test-*.py) $(wildcard *_test.py) $(TEST_TARGETS)
diff --git a/cmds/dhcpnametax.c b/cmds/dhcpnametax.c
new file mode 100644
index 0000000..f4af92a
--- /dev/null
+++ b/cmds/dhcpnametax.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "hostnamelookup.h"
+
+void usage(const char *progname)
+{
+  fprintf(stderr, "usage: %s -d dhcpsig -h hostname -l label\n", progname);
+  fprintf(stderr, "\t-d: DHCP options signature\n");
+  fprintf(stderr, "\t-h: hostname of the station\n");
+  fprintf(stderr, "\t-l: label for this station (typically the MAC addr)\n");
+  exit(1);
+}
+
+
+struct hostname_strings *check_directv(const char *hostname)
+{
+  regex_t r_directv;
+  regmatch_t match[2];
+
+  if (regcomp(&r_directv, "DIRECTV-([^-]+)-", REG_EXTENDED | REG_ICASE)) {
+    fprintf(stderr, "%s: regcomp failed!\n", __FUNCTION__);
+    exit(1);
+  }
+
+  if (regexec(&r_directv, hostname, 2, match, 0) == 0) {
+    struct hostname_strings *h = (struct hostname_strings *)malloc(sizeof *h);
+    int len = match[1].rm_eo - match[1].rm_so;
+
+    h->genus = "DirecTV";
+    h->species = strndup(hostname + match[1].rm_so, len);
+    return h;
+  }
+
+  return NULL;
+}
+
+
+int main(int argc, char **argv)
+{
+  struct option long_options[] = {
+    {"dhcpsig",  required_argument, 0, 'd'},
+    {"hostname", required_argument, 0, 'h'},
+    {"label",    required_argument, 0, 'l'},
+    {0,          0,                 0, 0},
+  };
+  int c;
+  const char *dhcpsig = NULL;
+  const char *hostname = NULL;
+  const char *label = NULL;
+  char concatenated[256];
+  const struct hostname_strings *sn = NULL;
+
+  setlinebuf(stdout);
+  alarm(30);
+  while ((c = getopt_long(argc, argv, "d:h:l:", long_options, NULL)) != -1) {
+    switch (c) {
+    case 'd':
+      dhcpsig = optarg;
+      break;
+    case 'l':
+      label = optarg;
+      break;
+    case 'h':
+      hostname = optarg;
+      break;
+    default:
+      usage(argv[0]);
+      break;
+    }
+  }
+
+  if (optind < argc ||
+      dhcpsig == NULL || hostname == NULL || label == NULL) {
+    usage(argv[0]);
+  }
+
+  snprintf(concatenated, sizeof(concatenated), "%s%%%s", hostname, dhcpsig);
+  if ((sn = hostname_lookup(concatenated, strlen(concatenated))) == NULL) {
+    if (strcmp(dhcpsig, "1,3,6,12,15,28,40,41,42") == 0) {
+      // DIRECTV-HR24-XXXXXXXX
+      sn = check_directv(hostname);
+    } else if (strcmp(dhcpsig, "1,3,6,12,15,28,42") == 0) {
+      // DIRECTV-HR24-XXXXXXXX
+      if ((sn = check_directv(hostname)) == NULL) {
+        // Trane thermostat XL824-XXXXXXXX
+        sn = hostname_lookup(hostname, 6);
+      }
+    } else if (strcmp(dhcpsig, "1,28,2,3,15,6,12") == 0) {
+      // TIVO-###
+      sn = hostname_lookup(hostname, 8);
+    } else if (strcmp(dhcpsig, "1,3,6,15,12") == 0) {
+      // Roku NP-##
+      sn = hostname_lookup(hostname, 5);
+    } else if (strcmp(dhcpsig, "3,1,252,42,15,6,12") == 0) {
+      // Nest 0#A
+      sn = hostname_lookup(hostname, 3);
+    } else if (strcmp(dhcpsig, "1,28,2,3,15,6,119,12,44,47,26,121,42") == 0) {
+      // SleepIQ
+      sn = hostname_lookup(hostname, 11);
+    }
+  }
+
+  if (sn != NULL) {
+    printf("name %s %s;%s\n", label, sn->genus, sn->species);
+  }
+  exit(0);
+}
diff --git a/cmds/hostnamelookup.gperf b/cmds/hostnamelookup.gperf
new file mode 100644
index 0000000..bfae6fe
--- /dev/null
+++ b/cmds/hostnamelookup.gperf
@@ -0,0 +1,153 @@
+%{
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hostnamelookup.h"
+
+%}
+struct hostname_strings {
+  char *name;
+  char *genus;
+  char *species;
+};
+%%
+01A| "Nest Thermostat", "Nest Thermostat v1"
+02A| "Nest Thermostat", "Nest Thermostat v2"
+09A| "Nest Thermostat", "Nest Thermostat v3"
+500-cc04b40| "Select Comfort SleepIQ", "SleepIQ"
+EX2700%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX2700"
+EX3700%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX3700"
+EX3800%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX3800"
+EX3920%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX3920"
+EX6100%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX6100"
+EX6120%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX6120"
+EX6150%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX6150"
+EX6200%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX6200"
+EX6920%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX6920"
+EX7000%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX7000"
+EX7300%1,121,249,3,6,12,15,28,33,43| "NetGEAR Wifi Range Extender", "EX7300"
+GFPB100%1,3,6,12,15,28,42,66,125| "Google Fiber Phone Box", "GFPB100"
+HarmonyHub%1,3,6,12,15,28,42| "Logitech Harmony Hub", "Harmony Hub"
+Hopper_br%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+Hopper_br0%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+Hopper_ETH0%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+Hopper_ETH1%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+Hopper_MoCA%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+Hopper_WiFi%1,3,6,12,15,28,42| "DISH Networks Hopper", "Hopper"
+HT701%1,3,6,12,15,28,43,125| "Grandstream Voice Adaptor", "HandyTone 701"
+HT802%1,3,6,12,15,28,43,125| "Grandstream Voice Adaptor", "HandyTone 802"
+Joey_AP%1,3,6,12,15,28,42| "DISH Networks Joey", "Joey"
+Joey_ETH0%1,3,6,12,15,28,42| "DISH Networks Joey", "Joey"
+Joey_MoCA%1,3,6,12,15,28,42| "DISH Networks Joey", "Joey"
+Joey_WiFi%1,3,6,12,15,28,42| "DISH Networks Joey", "Joey"
+LGSmartTV%252,3,42,15,6,1,12| "LG Smart TV", "LG Smart TV"
+LGwebOSTV%252,3,42,15,6,1,12| "LG Smart TV", "LG Smart TV"
+MyBookLive%1,28,2,3,15,6,119,12,44,47,26,121| "WD My Book Live", "My Book Live"
+NP-20| "Roku", "Netflix Player"
+NP-C0| "Roku", "Roku HD 1100"
+NP-D0| "Roku", "Roku HD-XR 1101"
+NP-G0| "Roku", "Roku SD 1050"
+NP-H0| "Roku", "Roku 1 HD 2000"
+NP-J0| "Roku", "Roku 1 XD 2050"
+NP-K0| "Roku", "Roku 1 XD|S 2100"
+NP-N0| "Roku", "Roku 1 XD"
+NP-11| "Roku", "Roku 2 HD 3000"
+NP-12| "Roku", "Roku 2 XD 3050"
+NP-13| "Roku", "Roku 2 XS 3100"
+NP-16| "Roku", "Roku LT 2400"
+NP-18| "Roku", "Roku HD 2500"
+NP-19| "Roku", "Roku LT 2450"
+NP-1E| "Roku", "Roku Streaming stick MHL 3400X"
+NP-1G| "Roku", "Roku 3 4200X"
+NP-1P| "Roku", "Roku LT 2700"
+NP-1X| "Roku", "Roku 1 2710"
+NP-2L| "Roku", "Roku Streaming Stick 3500"
+NP-41| "Roku", "Roku 3 4200"
+NP-4E| "Roku", "Roku 3 4230RW"
+NP-5Y| "Roku", "Roku 2 4210"
+NP-63| "Roku", "Roku 3 4230"
+NP-YY| "Roku", "Roku 4 4400"
+OBi200%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi200"
+OBi202%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi202"
+OBi300%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi300"
+OBi302%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi302"
+OBi1022%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi1022"
+OBi1032%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi1032"
+OBi1062%1,3,6,12,15,28,42,66| "Obihai VoIP Adaptor", "OBi1062"
+Philips-hue%1,3,28,6| "Philips Hue", "Hue Home Automation Bridge"
+R7000%1,3,6,12,15,28,33,44,121,249| "NetGEAR Wifi AP", "Nighthawk R7000"
+R7000%1,3,6,12,15,28,33,44,121,249,212| "NetGEAR Wifi AP", "Nighthawk R7000"
+R8000%1,3,6,12,15,28,33,44,121,249| "NetGEAR Wifi AP", "Nighthawk R8000"
+R8000%1,3,6,12,15,28,33,44,121,249,212| "NetGEAR Wifi AP", "Nighthawk R8000"
+Slingbox-350%1,3,6,12,15,28,40,41,42,119| "Slingbox", "Slingbox 350"
+Slingbox350%1,3,6,12,15,28,40,41,42,119| "Slingbox", "Slingbox 350"
+Slingbox-500%1,3,6,12,15,28,42| "Slingbox", "Slingbox 500"
+Slingbox500%1,3,6,12,15,28,42| "Slingbox", "Slingbox 500"
+Slingbox-M1%1,3,6,12,15,28,40,41,42,119| "Slingbox", "Slingbox M1"
+SPA112%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA112"
+SPA-112%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA112"
+SPA122%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA122"
+SPA232D%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA232D"
+SPA504G%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA504G"
+SPA525G2%1,3,6,7,12,15,28,42,43,44,66,67,125,128,150,159,160| "Cisco VoIP Adaptor", "SPA525G2"
+steamlink%3,1,252,42,15,6,12| "Steam Link", "Steam Link"
+TIVO-000| "TiVo", "Philips HDRx12"
+TIVO-001| "DirecTV TiVo", "Philips DSR600x"
+TIVO-002| "TiVo", "Philips HDRx12"
+TIVO-010| "TiVo", "Sony SVR-2000"
+TIVO-011| "DirecTV TiVo", "Sony SAT T-60"
+TIVO-031| "DirecTV TiVo", "Hughes GXCEBOTx"
+TIVO-101| "DirecTV TiVo", "Philips DSR7000x"
+TIVO-110| "TiVo", "Sony SVR-3000"
+TIVO-121| "DirecTV TiVo", "RCA DVR39"
+TIVO-130| "AT&T TiVo", "AT&T Broadband TiVo Series 2"
+TIVO-140| "TiVo", "TiVo Series 2"
+TIVO-151| "DirecTV TiVo", "Hughes HDVR2"
+TIVO-230| "AT&T TiVo", "AT&T Broadband TiVo Series 2"
+TIVO-240| "TiVo", "TiVo Series 2"
+TIVO-264| "TiVo DVD", "Toshiba SD-H400"
+TIVO-275| "TiVo DVD", "Pioneer 810H/57H"
+TIVO-301| "DirecTV TiVo", "Philips DSR70x"
+TIVO-321| "DirecTV TiVo", "RCA DVR40/80"
+TIVO-351| "DirecTV TiVo", "Hughes SD-DVRxx"
+TIVO-357| "DirecTV TiVo", "Hughes HR10-250"
+TIVO-381| "DirecTV TiVo", "Samsung SIR-S4xxx"
+TIVO-521| "DirecTV TiVo", "DirecTV R10"
+TIVO-540| "TiVo", "TiVo Series 2 NightLight"
+TIVO-565| "TiVo DVD", "Toshiba RS-TX20/60"
+TIVO-590| "TiVo DVD", "Humax T800/2500"
+TIVO-595| "TiVo DVD", "Humax DRT400/800"
+TIVO-627| "DirecTV TiVo", "DirecTV THR22"
+TIVO-648| "TiVo", "TiVo Series 3"
+TIVO-649| "TiVo", "TiVo Series 2 Dual Tuner"
+TIVO-652| "TiVo", "TiVo Series 3 HD"
+TIVO-658| "TiVo", "TiVo Series 3 HD XL"
+TIVO-746| "TiVo", "TiVo Premiere Series 4"
+TIVO-748| "TiVo", "TiVo Premiere XL Series 4"
+TIVO-750| "TiVo", "TiVo Premiere 4 Series 4"
+TIVO-758| "TiVo", "TiVo Premiere Elite Series 4"
+TIVO-840| "TiVo", "TiVo Roamio Pro"
+TIVO-846| "TiVo", "TiVo Roamio"
+TIVO-848| "TiVo", "TiVo Roamio Plus"
+TIVO-849| "TiVo", "TiVo BOLT"
+TIVO-A92| "TiVo", "TiVo Mini"
+TIVO-A93| "TiVo", "TiVo Mini"
+TIVO-A94| "TiVo", "TiVo Stream"
+VMB3010%1,3,6,12,15,28,33,44,121,249| "NetGEAR Arlo Camera", "VMB3010"
+VMB4000%1,3,6,12,15,28,33,44,121,249| "NetGEAR Arlo Camera", "VMB4000"
+XL824-| "Trane Thermostat", "XL824"
+XL850-| "Trane Thermostat", "XL850"
+%%
diff --git a/cmds/hostnamelookup.h b/cmds/hostnamelookup.h
new file mode 100644
index 0000000..73de8e0
--- /dev/null
+++ b/cmds/hostnamelookup.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef HOSTNAMELOOKUP_H_
+#define HOSTNAMELOOKUP_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure returned by the hostname_lookup function. */
+struct hostname_strings {
+  const char *name;
+  const char *genus;
+  const char *species;
+};
+
+/* Function generated by gperf for the hostname_lookup table. */
+extern const struct hostname_strings *hostname_lookup(const char *str,
+    unsigned int len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // HOSTNAMELOOKUP_H_
diff --git a/cmds/test-dhcpnametax.sh b/cmds/test-dhcpnametax.sh
new file mode 100755
index 0000000..73be720
--- /dev/null
+++ b/cmds/test-dhcpnametax.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+. ./wvtest/wvtest.sh
+
+pid=$$
+TAX=./host-dhcpnametax
+
+WVSTART "dhcpnametax test"
+
+WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,42 -h Hopper_ETH0)" "name label DISH Networks Hopper;Hopper"
+WVPASSEQ "$($TAX -l label -d 3,1,252,42,15,6,12 -h steamlink)" "name label Steam Link;Steam Link"
+
+# Test serial numbers with special format handling
+WVPASSEQ "$($TAX -l label -d 1,3,6,15,12 -h NP-1G0123456789)" "name label Roku;Roku 3 4200X"
+WVPASSEQ "$($TAX -l label -d 1,3,6,15,12 -h NP-120123456789)" "name label Roku;Roku 2 XD 3050"
+WVPASSEQ "$($TAX -l label -d 1,28,2,3,15,6,12 -h TIVO-848XXXXXXXXXXXX)" "name label TiVo;TiVo Roamio Plus"
+WVPASSEQ "$($TAX -l label -d 1,28,2,3,15,6,12 -h TIVO-849XXXXXXXXXXXX)" "name label TiVo;TiVo BOLT"
+WVPASSEQ "$($TAX -l label -d 3,1,252,42,15,6,12 -h 01AA01AB23456789)" "name label Nest Thermostat;Nest Thermostat v1"
+WVPASSEQ "$($TAX -l label -d 3,1,252,42,15,6,12 -h 09AA01AB23456789)" "name label Nest Thermostat;Nest Thermostat v3"
+WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,42 -h XL824-XXXXXXX)" "name label Trane Thermostat;XL824"
+WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,40,41,42 -h DIRECTV-H21-01234567)" "name label DirecTV;H21"
+WVPASSEQ "$($TAX -l label -d 1,3,6,12,15,28,42 -h DIRECTV-HR22-01234567)" "name label DirecTV;HR22"
+WVPASSEQ "$($TAX -l label -d 1,28,2,3,15,6,119,12,44,47,26,121,42 -h 500-cc04b40XXXXX)" "name label Select Comfort SleepIQ;SleepIQ"
+
+# check invalid or missing arguments.
+WVFAIL $TAX
+WVFAIL $TAX -m mac
+WVFAIL $TAX -h hostname
+WVFAIL $TAX -d dhcpsig
+
+rm -f *.$pid.tmp