odhcpd: remove the "filter_class" option
authorDavid Härdeman <[email protected]>
Sat, 25 Oct 2025 22:49:22 +0000 (00:49 +0200)
committerÁlvaro Fernández Rojas <[email protected]>
Thu, 6 Nov 2025 07:24:51 +0000 (08:24 +0100)
The "filter_class" option was first introduced as part of the effort to support
the Homenet Control Protocol (HNCP, RFC7368, RFC7788). At first it was
hardcoded to filter out the user class "HOMENET", and later it was made
configurable, but there appears to be no other use-cases than Homenet. There's
also legacy comments in the odhcpd code pointing at filtering out homenet user
classes (the comments are misleading since "filter_class" is not set by
default).

Homenet is effectively dead. The "hnetd" daemon last saw any code changes in
2018, doesn't compile with current versions of GCC, and was removed from
OpenWrt altogether in 2025 [1].

The OpenWrt wiki also notes [2] that HNCP is effectively dead, and that the
upstream website [3] is gone.

Searching for the Homenet Control Protocol mostly returns results pointing
either:
a) at the standards
b) at the OpenWrt/hnetd project
c) at various posts stating that it is dead

The IETF working group for Homenet is no more, and Éric Vyncke (Area Director
for the Internet Area of the IETF) mentioned wanting to avoid Homenet pitfalls
[4] (page 11) in the future of IPv6 autoconfig.

The current implementation is also contrary to the spirit of the RFCs
introducing the option (RFC3004 - DHCPv4; RFC8415, §21.15 - DHCPv6) where it is
described (example from RFC8415) as follows:

   The information contained in the data area of this option is
   contained in one or more opaque fields that represent the user class
   or classes of which the client is a member.  A server selects
   configuration information for the client based on the classes
   identified in this option.  For example, the User Class option can be
   used to configure all clients of people in the accounting department
   with a different printer than clients of people in the marketing
   department.

I.e., it's meant to be used in a manner similar to "tags" in dnsmasq.

Finally, the option has been undocumented for the whole existance of odhcpd.

So, remove this option.

[1] https://github.com/openwrt/routing/commit/85b868b3413a29da0bd6ecd3518c2d34a6ffb788
[2] https://openwrt.org/docs/guide-user/network/zeroconfig/hncp_configuration
[3] https://web.archive.org/web/20180831161552/http://homewrt.org/start
[4] https://ripe87.ripe.net/wp-content/uploads/presentations/102-20231130-RIPE-87-IPv6-from-IETF.pdf

Signed-off-by: David Härdeman <[email protected]>
Link: https://github.com/openwrt/odhcpd/pull/294
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
README.md
src/config.c
src/dhcpv4.c
src/dhcpv6.c
src/odhcpd.h

index 485fc41acc37954b2a73dd14c77b87f243c68adc..c59c039b2626d2f4355cfcd905f3b51c41d5a033 100644 (file)
--- a/README.md
+++ b/README.md
@@ -121,7 +121,6 @@ and may also receive information from ubus
 | ntp                  |list   |`<local address>`| NTP servers to announce accepts IPv4 and IPv6 |
 | ra_management                |string | -     | TBD |
 | upstream             |list   | -     | TBD |
-| filter_class         |string | -     | TBD |
 | pd_manager           |string | -     | TBD |
 | pd_cer               |string | -     | TBD |
 | ra_advrouter         |bool   | -     | TBD |
index 070aeaae48c4b89e988d7ba4d83aa41845f3f4b0..aaf56525d9568b65720c1caefca3759bf7be743c 100644 (file)
@@ -104,7 +104,6 @@ enum {
        IFACE_ATTR_DNR,
        IFACE_ATTR_DNS_SERVICE,
        IFACE_ATTR_DOMAIN,
-       IFACE_ATTR_FILTER_CLASS,
        IFACE_ATTR_DHCPV4_FORCERECONF,
        IFACE_ATTR_DHCPV6_RAW,
        IFACE_ATTR_DHCPV6_ASSIGNALL,
@@ -159,7 +158,6 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = {
        [IFACE_ATTR_DNR] = { .name = "dnr", .type = BLOBMSG_TYPE_ARRAY },
        [IFACE_ATTR_DNS_SERVICE] = { .name = "dns_service", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_DOMAIN] = { .name = "domain", .type = BLOBMSG_TYPE_ARRAY },
-       [IFACE_ATTR_FILTER_CLASS] = { .name = "filter_class", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_DHCPV4_FORCERECONF] = { .name = "dhcpv4_forcereconf", .type = BLOBMSG_TYPE_BOOL },
        [IFACE_ATTR_DHCPV6_RAW] = { .name = "dhcpv6_raw", .type = BLOBMSG_TYPE_STRING },
        [IFACE_ATTR_DHCPV6_ASSIGNALL] = { .name ="dhcpv6_assignall", .type = BLOBMSG_TYPE_BOOL },
@@ -348,7 +346,6 @@ static void clean_interface(struct interface *iface)
        free(iface->dhcpv4_router);
        free(iface->dhcpv4_dns);
        free(iface->dhcpv6_raw);
-       free(iface->filter_class);
        free(iface->dhcpv4_ntp);
        free(iface->dhcpv6_ntp);
        free(iface->dhcpv6_sntp);
@@ -1386,11 +1383,6 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
                }
        }
 
-       if ((c = tb[IFACE_ATTR_FILTER_CLASS])) {
-               iface->filter_class = realloc(iface->filter_class, blobmsg_data_len(c) + 1);
-               memcpy(iface->filter_class, blobmsg_get_string(c), blobmsg_data_len(c) + 1);
-       }
-
        if ((c = tb[IFACE_ATTR_DHCPV4_FORCERECONF]))
                iface->dhcpv4_forcereconf = blobmsg_get_bool(c);
 
index d5177c61b50c6c32bd5f8fa73874dbc4deee220b..b64b1c3a1a1e9a95cbff188c30da3e823292baec 100644 (file)
@@ -927,16 +927,6 @@ void dhcpv4_handle_msg(void *src_addr, void *data, size_t len,
                                req_opts_len = opt->len;
                        }
                        break;
-               case DHCPV4_OPT_USER_CLASS:
-                       if (iface->filter_class) {
-                               uint8_t *c = opt->data, *cend = &opt->data[opt->len];
-                               for (; c < cend && &c[*c] < cend; c = &c[1 + *c]) {
-                                       size_t elen = strlen(iface->filter_class);
-                                       if (*c == elen && !memcmp(&c[1], iface->filter_class, elen))
-                                               return; // Ignore from homenet
-                               }
-                       }
-                       break;
                case DHCPV4_OPT_LEASETIME:
                        if (opt->len == 4) {
                                memcpy(&req_leasetime, opt->data, 4);
index ab55ff6508c2bdc7b81a35410a8a61a0ec6bcd44..8b6cf6bcfefd50dc503e1b344cbcad7beb3cde58 100644 (file)
@@ -680,13 +680,6 @@ static void handle_client_request(void *addr, void *data, size_t len,
                        if (olen != ntohs(dest.serverid_length) ||
                            memcmp(odata, &dest.serverid_buf, olen))
                                return; /* Not for us */
-               } else if (iface->filter_class && otype == DHCPV6_OPT_USER_CLASS) {
-                       uint8_t *c = odata, *cend = &odata[olen];
-                       for (; &c[2] <= cend && &c[2 + (c[0] << 8) + c[1]] <= cend; c = &c[2 + (c[0] << 8) + c[1]]) {
-                               size_t elen = strlen(iface->filter_class);
-                               if (((((size_t)c[0]) << 8) | c[1]) == elen && !memcmp(&c[2], iface->filter_class, elen))
-                                       return; /* Ignore from homenet */
-                       }
                } else if (otype == DHCPV6_OPT_IA_PD) {
 #ifdef EXT_CER_ID
                        iov[IOV_CERID].iov_len = sizeof(cerid);
index 2682e4437830394f60ecbbb5519f22db4574d7b8..01c8d0f4c0258a558d7a9911daef387b264014de 100644 (file)
@@ -450,8 +450,6 @@ struct interface {
        char *upstream;
        size_t upstream_len;
 
-       char *filter_class;
-
        // NTP
        struct in_addr *dhcpv4_ntp;
        size_t dhcpv4_ntp_cnt;