[PATCH firmware-utils 1/2] ptgen: add Chromium OS kernel partition support

2022-01-15 Thread Brian Norris
Chrom{ium,e} OS (shortened as "CrOS") bootloaders use a custom GPT
partition type to locate their kernel(s), with custom attributes for
noting properties around which partition(s) should be active and how
many times they've been tried as part of their A/B in-place upgrade
system.

OpenWRT doesn't use A/B updates for upgrades (instead, just shutting
things down far enough to reprogram the necessary partitions), so all we
need to do is tell the bootloader which one is the kernel partition, and
how to use it (i.e., set the "successful" and "priority" attributes).

ptgen already supports some basic GPT partition creation, so just
add support for a '-T ' argument. Currently, this
only supports '-T cros_kernel', but it could be extended if there are
other GPT partition types needed.

For GPT attribute and GUID definitions, see the CrOS verified boot
sources:

https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/refs/heads/master/firmware/lib/cgptlib/include/cgptlib_internal.h
https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/refs/heads/master/firmware/include/gpt.h

Wikipedia (!!) even notes the GUIDs:
https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs

The GUID is also recognized in fdisk, and likely other utilities, but
creation/manipulation is typically done via the 'cgpt' utility, provided
as part of the Chromium vboot_reference project.

Signed-off-by: Brian Norris 
---
 src/ptgen.c | 43 ++-
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/src/ptgen.c b/src/ptgen.c
index 69757c1fc7dc..7220dde42b92 100644
--- a/src/ptgen.c
+++ b/src/ptgen.c
@@ -70,6 +70,10 @@ typedef struct {
GUID_INIT( 0x21686148, 0x6449, 0x6E6F, \
0x74, 0x4E, 0x65, 0x65, 0x64, 0x45, 0x46, 0x49)
 
+#define GUID_PARTITION_CHROME_OS_KERNEL \
+   GUID_INIT( 0xFE3A2A5D, 0x4F32, 0x41A7, \
+   0xB7, 0x25, 0xAC, 0xCC, 0x32, 0x85, 0xA3, 0x09)
+
 #define GUID_PARTITION_LINUX_FIT_GUID \
GUID_INIT( 0xcae9be83, 0xb15f, 0x49cc, \
0x86, 0x3f, 0x08, 0x1b, 0x74, 0x4a, 0x2d, 0x93)
@@ -116,7 +120,9 @@ struct partinfo {
int hybrid;
char *name;
short int required;
+   bool has_guid;
guid_t guid;
+   uint64_t gattr;  /* GPT partition attributes */
 };
 
 /* GPT Partition table header */
@@ -256,6 +262,23 @@ static inline int guid_parse(char *buf, guid_t *guid)
return 0;
 }
 
+/*
+ * Map GPT partition types to partition GUIDs.
+ * NB: not all GPT partition types have an equivalent MBR type.
+ */
+static inline bool parse_gpt_parttype(const char *type, struct partinfo *part)
+{
+   if (!strcmp(type, "cros_kernel")) {
+   part->has_guid = true;
+   part->guid = GUID_PARTITION_CHROME_OS_KERNEL;
+   /* Default attributes: bootable kernel. */
+   part->gattr = (1ULL << 48) |  /* priority=1 */
+ (1ULL << 56);  /* success=1 */
+   return true;
+   }
+   return false;
+}
+
 /* init an utf-16 string from utf-8 string */
 static inline void init_utf16(char *str, uint16_t *buf, unsigned bufsize)
 {
@@ -416,6 +439,7 @@ static int gen_gptable(uint32_t signature, guid_t guid, 
unsigned nr)
to_chs(sect - 1, pte[1].chs_end);
pmbr++;
}
+   gpte[i].attr = parts[i].gattr;
 
if (parts[i].name)
init_utf16(parts[i].name, (uint16_t *)gpte[i].name, 
GPT_ENTRY_NAME_SIZE / sizeof(uint16_t));
@@ -523,7 +547,9 @@ fail:
 
 static void usage(char *prog)
 {
-   fprintf(stderr, "Usage: %s [-v] [-n] [-g] -h  -s  -o 
 [-a 0..4] [-l ] [-G ] [[-t ] [-r] [-N 
] -p [@]...] \n", prog);
+   fprintf(stderr, "Usage: %s [-v] [-n] [-g] -h  -s  -o 
\n"
+   "  [-a 0..4] [-l ] [-G ]\n"
+   "  [[-t  | -T ] [-r] [-N 
] -p [@]...] \n", prog);
exit(EXIT_FAILURE);
 }
 
@@ -559,9 +585,8 @@ int main (int argc, char **argv)
uint32_t signature = 0x5452574F; /* 'OWRT' */
guid_t guid = GUID_INIT( signature, 0x2211, 0x4433, \
0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0x00);
-   guid_t part_guid = GUID_PARTITION_BASIC_DATA;
 
-   while ((ch = getopt(argc, argv, "h:s:p:a:t:o:vnHN:gl:rS:G:")) != -1) {
+   while ((ch = getopt(argc, argv, "h:s:p:a:t:T:o:vnHN:gl:rS:G:")) != -1) {
switch (ch) {
case 'o':
filename = optarg;
@@ -594,12 +619,12 @@ int main (int argc, char **argv)
*(p++) = 0;
parts[part].start = to_kbytes(p);
}
-   part_guid = type_to_guid_and_name(type, );
+   if (!parts[part].has_guid)
+   parts[part].guid = 

[PATCH firmware-utils 2/2] cros-vbutil: add Chrome OS vboot kernel-signing utility

2022-01-15 Thread Brian Norris
Chrom{ium,e} OS based devices use a Coreboot+Depthcharge-based firmware,
which verifies and loads a kernel packed in a verified-boot payload. The
verification tooling (both for creating and verifying payloads) is
implemented here:

https://chromium.googlesource.com/chromiumos/platform/vboot_reference

Devices running such bootloaders also tend to support a "developer
mode," where a device can be unlocked to run arbitrary kernel payloads,
using the same verified-boot format plus well-known developer keys. More
information can be found here:

https://chromium.googlesource.com/chromiumos/docs/+/master/developer_mode.md

Rather than build and package the vboot_reference utilities as part of
the base OpenWRT tools, I chose to reimplement just the portion that's
required for signing payloads. I also embed the developer key directly
in the source for convenience, though it's certainly possible to
provide other keys too, if one were to build their own firmware that
accepts it.

This tool is essentially the same as running something like this, using
the Chromium OS tooling:

  vbutil_kernel --pack kernel_partition.bin \
   --keyblock /usr/share/vboot/devkeys/kernel.keyblock \
   --signprivate /usr/share/vboot/devkeys/kernel_data_key.vbprivk \
   --version 1 \
   --vmlinuz kernel.bin \
   --bootloader zero.txt \
   --config commnad_line.txt \
   --arch ${ARCH}

I have also packaged the Chromium OS vboot_reference tooling for the
packages feed, as it can be useful beyond simply creating a bootable
image (e.g., manipulating Chromium OS specific GPT attributes, handling
other NVRAM attributes, vboot packing/unpacking/verifying):
https://github.com/openwrt/packages/pull/12829

The vboot_reference tools are released by Google under a BSD 3-clause
license. I've provided the original license text as well as a GPL-2
notice for my modifications (essentially just borrowing the data
structures and rewriting everything else).

Signed-off-by: Brian Norris 
---
 CMakeLists.txt|   1 +
 src/cros-vbutil.c | 608 ++
 2 files changed, 609 insertions(+)
 create mode 100644 src/cros-vbutil.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f406520aa87e..9ac64ff51033 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,6 +34,7 @@ FW_UTIL(bcm4908kernel "" "" "")
 FW_UTIL(buffalo-enc src/buffalo-lib.c "" "")
 FW_UTIL(buffalo-tag src/buffalo-lib.c "" "")
 FW_UTIL(buffalo-tftp src/buffalo-lib.c "" "")
+FW_UTIL(cros-vbutil "" "" "${OPENSSL_CRYPTO_LIBRARIES}")
 FW_UTIL(dgfirmware "" "" "")
 FW_UTIL(dgn3500sum "" "" "")
 FW_UTIL(dns313-header "" "" "")
diff --git a/src/cros-vbutil.c b/src/cros-vbutil.c
new file mode 100644
index ..a29ee700f1d4
--- /dev/null
+++ b/src/cros-vbutil.c
@@ -0,0 +1,608 @@
+/*
+ * cros-vbutil - Tool for signing kernels using the Chromium OS verified boot
+ * format, using widely-shared "developer" keys. The output of this tool is
+ * intended to be written to a GPT partition of type "Chrome OS Kernel", such
+ * that a Chromium OS bootloader can find it.
+ *
+ * Much of this is adapted from Google's vboot_reference project found here:
+ *   https://chromium.googlesource.com/chromiumos/platform/vboot_reference
+ *
+ * Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ ** Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ ** Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ ** Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Rewritten:
+ * Copyright (c) 2021 Brian Norris 
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public 

[PATCH] iprule: add support for uidrange

2022-01-15 Thread Matthew Hagan
Allow for per-user routing policies via the uidrange iprule option.
Option allows for a single UID or range of UIDs.

Signed-off-by: Matthew Hagan 
---
 iprule.c   | 13 -
 iprule.h   |  5 +
 system-linux.c |  8 
 3 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/iprule.c b/iprule.c
index b9e16a5..57976b1 100644
--- a/iprule.c
+++ b/iprule.c
@@ -44,6 +44,7 @@ enum {
RULE_ACTION,
RULE_GOTO,
RULE_SUP_PREFIXLEN,
+   RULE_UIDRANGE,
RULE_DISABLED,
__RULE_MAX
 };
@@ -59,6 +60,7 @@ static const struct blobmsg_policy rule_attr[__RULE_MAX] = {
[RULE_FWMARK] = { .name = "mark", .type = BLOBMSG_TYPE_STRING },
[RULE_LOOKUP] = { .name = "lookup", .type = BLOBMSG_TYPE_STRING },
[RULE_SUP_PREFIXLEN] = { .name = "suppress_prefixlength", .type = 
BLOBMSG_TYPE_INT32 },
+   [RULE_UIDRANGE] = { .name = "uidrange", .type = BLOBMSG_TYPE_STRING },
[RULE_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING },
[RULE_GOTO]   = { .name = "goto", .type = BLOBMSG_TYPE_INT32 },
[RULE_DISABLED] = { .name = "disabled", .type = BLOBMSG_TYPE_BOOL },
@@ -201,7 +203,7 @@ iprule_add(struct blob_attr *attr, bool v6)
struct blob_attr *tb[__RULE_MAX], *cur;
struct iprule *rule;
char *iface_name;
-   int af = v6 ? AF_INET6 : AF_INET;
+   int af = v6 ? AF_INET6 : AF_INET, ret;
 
blobmsg_parse(rule_attr, __RULE_MAX, tb, blobmsg_data(attr), 
blobmsg_data_len(attr));
 
@@ -282,6 +284,15 @@ iprule_add(struct blob_attr *attr, bool v6)
rule->flags |= IPRULE_SUP_PREFIXLEN;
}
 
+   if ((cur = tb[RULE_UIDRANGE]) != NULL) {
+   ret = sscanf(blobmsg_get_string(cur), "%u-%u", 
>uidrange_start, >uidrange_end);
+   if (ret == 1)
+   rule->uidrange_end = rule->uidrange_start;
+   else if (ret != 2)
+   DPRINTF("Failed to parse UID range: %s\n", (char *) 
blobmsg_data(cur));
+   rule->flags |= IPRULE_UIDRANGE;
+   }
+
if ((cur = tb[RULE_ACTION]) != NULL) {
if (!system_resolve_iprule_action(blobmsg_data(cur), 
>action)) {
DPRINTF("Failed to parse rule action: %s\n", (char *) 
blobmsg_data(cur));
diff --git a/iprule.h b/iprule.h
index 89b94b4..6d91d06 100644
--- a/iprule.h
+++ b/iprule.h
@@ -63,6 +63,9 @@ enum iprule_flags {
 
/* rule suppresses results by prefix length */
IPRULE_SUP_PREFIXLEN= (1 << 13),
+
+   /* rule specifies uidrange */
+   IPRULE_UIDRANGE = (1 << 14),
 };
 
 struct iprule {
@@ -102,6 +105,8 @@ struct iprule {
 
unsigned int lookup;
unsigned int sup_prefixlen;
+   unsigned int uidrange_start;
+   unsigned int uidrange_end;
unsigned int action;
unsigned int gotoid;
 };
diff --git a/system-linux.c b/system-linux.c
index 654f2ac..a14d779 100644
--- a/system-linux.c
+++ b/system-linux.c
@@ -2954,6 +2954,14 @@ static int system_iprule(struct iprule *rule, int cmd)
if (rule->flags & IPRULE_SUP_PREFIXLEN)
nla_put_u32(msg, FRA_SUPPRESS_PREFIXLEN, rule->sup_prefixlen);
 
+   if (rule->flags & IPRULE_UIDRANGE) {
+   struct fib_rule_uid_range uidrange = {
+   rule->uidrange_start,
+   rule->uidrange_end
+   };
+   nla_put(msg, FRA_UID_RANGE, sizeof(uidrange), );
+   }
+
if (rule->flags & IPRULE_GOTO)
nla_put_u32(msg, FRA_GOTO, rule->gotoid);
 
-- 
2.27.0


___
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel


Re: Support for Google Onhub devices

2022-01-15 Thread Sungbo Eo

Hi,

On 2022-01-13 02:52, Brian Norris wrote:

On Wed, Jan 12, 2022 at 8:43 AM Thomas Deselaers  wrote:


Hey folks,


Hi!


I have an Onhub router and some Google Wifi repeaters. Google recently
announced that they are going to shut down the support for Onhub
(effectively bricking them) by end of 2022.

I have done a bit of research and it seems like there was some
preliminary support for OpenWRT on these devices at some point. I
guess there will be a lot of Onhubs that would be pretty good OpenwRT
routers in about a year - which otherwise, are probably going to the
trash.

While I am a developer, I have only very little openWRT experience and
I have been wondering what it would take to bring up support for these
devices.


It's not likely to be a great "first hacking on OpenWRT" experience,
but it should technically be possible...

FWIW, I've been hacking on Google WiFi, the successor of the OnHub. I
have it working, and documented pretty much everything here:

https://openwrt.org/inbox/toh/google/google_wifi

For the OnHub, you could leverage the same partition formatting (OnHub
is also running a similar Chrome OS bootloader), but you'd have to
bring up a different SoC (OnHub uses ipq8064, while Google WiFi is
ipq4019). OnHub also doesn't have as easy of serial-console access for
hacking. And I don't see documentation for Developer Mode on OnHub,
although I know it's possible.

But, I'm interested, and if you really have the stamina to hack on it,
I can be a sounding board!


I've also been thinking of porting OpenWrt to OnHub since I ran into it 
a few years ago, but I couldn't make enough time to actually play with it...


Exploitee.rs already did great groundwork for it, they found out the way 
to enable developer mode (as mentioned by Alberto), and they also 
succeeded in getting serial access to ASUS OnHub [1].
I could not find any reference/discussion on serial access to TP-Link 
OnHub, but after rummaging through the internal photo from iFixit 
teardown [2], I strongly suspect JP2 connector (next to the QCA8337 
Ethernet switch) is the Yoshi debug header [3] which can be also found 
in some old Chromebook mainboards. (JP6 might be connected to Zigbee 
chip instead, otherwise Exploitee.rs guys wouldn't have missed it...) 
Comparing the schematic of Yoshi flex cable with the connector pads, I 
guess we can at least get access to UART2, JTAG, SPI2, DEV_MODE pins. 
That's why I fetched OnHub myself and put it into the cabinet before 
taking it apart and forgot about it. :/


Brian did a great work on Google WiFi support and I believe most of it 
can be applied to OnHub as well. (I really appreciate your work! I never 
thought Chrome OS stuff would be another hassle...) I hope I could find 
some time to start working on it someday...


Cheers,
Sungbo

[1] https://www.exploitee.rs/index.php/Asus_OnHub
[2] https://ko.ifixit.com/Teardown/OnHub+Teardown/48129
[3] 
https://chromium.googlesource.com/chromiumos/third_party/hdctools/+/refs/heads/master/docs/servo_v2.md#connecting-servo-v2


___
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel


[PATCH] ath79: Add support for Ubiquiti NanoBeam AC Gen1 XC

2022-01-15 Thread Daniel González Cabanelas
The Ubiquiti NanoBeam AC Gen1 XC (NBE-5AC-19) is an outdoor 802.11ac CPE
with a waterproof casing (ultrasonically welded) and bulb shaped.

Hardware:
 - SoC: Qualcomm Atheros QCA9558
 - RAM: 128 MB DDR2
 - Flash: 16 MB SPI NOR
 - Ethernet: 1x GbE, AR8033 phy connected via SGMII
 - PSU: 24 Vdc passive PoE
 - WiFi 5 GHz: Qualcomm Atheros QCA988X
 - Buttons: 1x reset
 - LEDs: 1x power, 1x Ethernet, 4x RSSI, all blue

Not working:
 - Fast Ethernet: 100 Mbps link speed detected, interface up but
   without ping.

Installation from stock airOS firmware:
 - Follow instructions for XC-type Ubiquiti devices on OpenWrt wiki at
   https://openwrt.org/toh/ubiquiti/common

Signed-off-by: Daniel González Cabanelas 
---
 .../ath79/dts/qca9558_ubnt_nanobeam-ac-xc.dts | 109 ++
 .../generic/base-files/etc/board.d/01_leds|   1 +
 .../generic/base-files/etc/board.d/02_network |   2 +
 .../etc/hotplug.d/firmware/11-ath10k-caldata  |   1 +
 target/linux/ath79/image/generic-ubnt.mk  |   9 ++
 5 files changed, 122 insertions(+)
 create mode 100644 target/linux/ath79/dts/qca9558_ubnt_nanobeam-ac-xc.dts

diff --git a/target/linux/ath79/dts/qca9558_ubnt_nanobeam-ac-xc.dts 
b/target/linux/ath79/dts/qca9558_ubnt_nanobeam-ac-xc.dts
new file mode 100644
index 00..9db81d7790
--- /dev/null
+++ b/target/linux/ath79/dts/qca9558_ubnt_nanobeam-ac-xc.dts
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Device Tree file for Ubiquiti NBE-5AC-19
+ *
+ * Copyright (C) 2022 Daniel González Cabanelas 
+ * based on device tree from qca9558_ubnt_powerbeam-5ac-500.dts
+ */
+ 
+#include "qca955x_ubnt_xc.dtsi"
+
+/ {
+   compatible = "ubnt,nanobeam-ac-xc", "ubnt,xc", "qca,qca9558";
+   model = "Ubiquiti NanoBeam AC Gen1 (XC)";
+
+   aliases {
+   led-boot = _power;
+   led-failsafe = _power;
+   led-running = _power;
+   led-upgrade = _power;
+   };
+
+   keys {
+   compatible = "gpio-keys";
+
+   reset {
+   label = "Reset button";
+   linux,code = ;
+   gpios = < 19 GPIO_ACTIVE_LOW>;
+   debounce-interval = <60>;
+   };
+   };
+
+   led_spi {
+   compatible = "spi-gpio";
+   #address-cells = <1>;
+   #size-cells = <0>;
+
+   sck-gpios  = < 0 GPIO_ACTIVE_HIGH>;
+   mosi-gpios = < 1 GPIO_ACTIVE_HIGH>;
+   cs-gpios   = < 3 GPIO_ACTIVE_HIGH>;
+   num-chipselects = <1>;
+
+   led_gpio: led_gpio@0 {
+   compatible = "fairchild,74hc595";
+   reg = <0>;
+   gpio-controller;
+   #gpio-cells = <2>;
+   registers-number = <1>;
+   spi-max-frequency = <1000>;
+   enable-gpios = < 18 GPIO_ACTIVE_LOW>;
+   };
+   };
+
+   leds {
+   compatible = "gpio-leds";
+
+   rssi0 {
+   label = "blue:rssi0";
+   gpios = <_gpio 0 GPIO_ACTIVE_LOW>;
+   };
+   rssi1 {
+   label = "blue:rssi1";
+   gpios = <_gpio 1 GPIO_ACTIVE_LOW>;
+   };
+   rssi2 {
+   label = "blue:rssi2";
+   gpios = <_gpio 2 GPIO_ACTIVE_LOW>;
+   };
+   rssi3 {
+   label = "blue:rssi3";
+   gpios = <_gpio 3 GPIO_ACTIVE_LOW>;
+   };
+   led_power: power {
+   label = "blue:power";
+   gpios = <_gpio 4 GPIO_ACTIVE_LOW>;
+   default-state = "on";
+   };
+   };
+};
+
+ {
+   status = "okay";
+
+   phy-mask = <4>;
+   phy4: ethernet-phy@4 {
+   phy-mode = "sgmii";
+   reg = <4>;
+   at803x-override-sgmii-link-check;
+   };
+};
+
+ {
+   status = "okay";
+
+   nvmem-cells = <_art_0>;
+   nvmem-cell-names = "mac-address";
+   phy-mode = "sgmii";
+   phy-handle = <>;
+};
+
+ {
+   compatible = "nvmem-cells";
+   #address-cells = <1>;
+   #size-cells = <1>;
+
+   macaddr_art_0: macaddr@0 {
+   reg = <0x0 0x6>;
+   };
+};
diff --git a/target/linux/ath79/generic/base-files/etc/board.d/01_leds 
b/target/linux/ath79/generic/base-files/etc/board.d/01_leds
index 652b54092e..9a9837abbb 100644
--- a/target/linux/ath79/generic/base-files/etc/board.d/01_leds
+++ b/target/linux/ath79/generic/base-files/etc/board.d/01_leds
@@ -409,6 +409,7 @@ trendnet,tew-823dru)
 ubnt,bullet-ac|\
 ubnt,nanobeam-ac|\
 ubnt,nanobeam-ac-gen2|\
+ubnt,nanobeam-ac-xc|\
 ubnt,nanostation-ac|\
 ubnt,powerbeam-5ac-gen2)
ucidef_set_rssimon "wlan0" "20" "1"
diff --git