On Sun, Aug 08, 2021 at 12:37:54PM +0200, Florian Obser wrote:
> This implements ignoring of nameservers and / or routes in leases as
> well as completely ignoring servers (you cannot block rogue DHCP servers
> in pf because bpf sees packets before pf).
>
> Various people voiced the need for these features.
> Tests, OKs?
>
> diff --git dhcpleased.c dhcpleased.c
> index 36a4a21c21a..e005d7de9ae 100644
> --- dhcpleased.c
> +++ dhcpleased.c
> @@ -569,6 +569,20 @@ main_dispatch_engine(int fd, short event, void *bula)
> sizeof(imsg_interface.if_index));
> break;
> }
> + case IMSG_WITHDRAW_ROUTES: {
> + struct imsg_configure_interface imsg_interface;
> + if (IMSG_DATA_SIZE(imsg) != sizeof(imsg_interface))
> + fatalx("%s: IMSG_CONFIGURE_INTERFACE wrong "
> + "length: %lu", __func__,
> + IMSG_DATA_SIZE(imsg));
> + memcpy(&imsg_interface, imsg.data,
> + sizeof(imsg_interface));
> + if (imsg_interface.routes_len >= MAX_DHCP_ROUTES)
> + fatalx("%s: too many routes in imsg", __func__);
> + if (imsg_interface.routes_len > 0)
> + configure_routes(RTM_DELETE, &imsg_interface);
> + break;
> + }
> case IMSG_PROPOSE_RDNS: {
> struct imsg_propose_rdns rdns;
> if (IMSG_DATA_SIZE(imsg) != sizeof(rdns))
> diff --git dhcpleased.conf.5 dhcpleased.conf.5
> index 9e6846f899e..b856113bac1 100644
> --- dhcpleased.conf.5
> +++ dhcpleased.conf.5
> @@ -57,6 +57,17 @@ A list of interfaces to overwrite defaults:
> .Ic interface
> options are as follows:
> .Bl -tag -width Ds
> +.It Ic ignore dns
> +Ignore nameservers from leases on this interface.
> +The default is to not ignore nameservers.
> +.It Ic ignore routes
> +Ignore routes from leases on this interface.
> +The default is to not ignore routes.
> +.It Ic ignore Ar server-ip
> +Ignore leases from
> +.Ar server-ip .
> +This option can be listed multiple times.
> +The default is to not ignore servers.
> .It Ic send client id Ar client-id
> Send the dhcp client identifier option with a value of
> .Ar client-id .
> diff --git dhcpleased.h dhcpleased.h
> index 7f6ec87ad19..1d20b77dbd3 100644
> --- dhcpleased.h
> +++ dhcpleased.h
> @@ -151,6 +151,12 @@
> #define DHCPRELEASE 7
> #define DHCPINFORM 8
>
> +/* Ignore parts of DHCP lease */
> +#define IGN_ROUTES 1
> +#define IGN_DNS 2
> +
> +#define MAX_SERVERS 16 /* max servers that can be ignores per
> if */
Typo in comment: ignored (not ignores)
Should there be a mention of a limitation in the man page where
it states the option can be listed multiple times?
--patrick
> +
> #define IMSG_DATA_SIZE(imsg) ((imsg).hdr.len - IMSG_HEADER_SIZE)
> #define DHCP_SNAME_LEN 64
> #define DHCP_FILE_LEN 128
> @@ -216,6 +222,7 @@ enum imsg_type {
> IMSG_DECONFIGURE_INTERFACE,
> IMSG_PROPOSE_RDNS,
> IMSG_WITHDRAW_RDNS,
> + IMSG_WITHDRAW_ROUTES,
> IMSG_REPROPOSE_RDNS,
> IMSG_REQUEST_REBOOT,
> };
> @@ -246,6 +253,9 @@ struct iface_conf {
> int vc_id_len;
> uint8_t *c_id;
> int c_id_len;
> + int ignore;
> + struct in_addr ignore_servers[MAX_SERVERS];
> + int ignore_servers_len;
> };
>
> struct dhcpleased_conf {
> @@ -304,6 +314,8 @@ const char *sin_to_str(struct sockaddr_in *);
>
> /* frontend.c */
> struct iface_conf *find_iface_conf(struct iface_conf_head *, char *);
> +int *changed_ifaces(struct dhcpleased_conf *, struct
> + dhcpleased_conf *);
>
> /* printconf.c */
> void print_config(struct dhcpleased_conf *);
> diff --git engine.c engine.c
> index 076a57e9ba6..67693c358ee 100644
> --- engine.c
> +++ engine.c
> @@ -139,6 +139,7 @@ void
> send_configure_interface(struct dhcpleased_iface *);
> void send_rdns_proposal(struct dhcpleased_iface *);
> void send_deconfigure_interface(struct dhcpleased_iface *);
> void send_rdns_withdraw(struct dhcpleased_iface *);
> +void send_routes_withdraw(struct dhcpleased_iface *);
> void parse_lease(struct dhcpleased_iface *,
> struct imsg_ifinfo *);
> int engine_imsg_compose_main(int, pid_t, void *, uint16_t);
> @@ -506,13 +507,37 @@ engine_dispatch_main(int fd, short event, void *bula)
> IMSG_DATA_SIZE(imsg));
> iface_conf->c_id_len = IMSG_DATA_SIZE(imsg);
> break;
> - case IMSG_RECONF_END:
> + case IMSG_RECONF_END: {
> + struct dhcpleased_iface *iface;
> + int *ifaces;
> + int i, if_index;
> + char *if_name;
> + char ifnamebuf[IF_NAMESIZE];
> +
> if (nconf == NULL)
> fatalx("%s: IMSG_RECONF_END without "
> "IMSG_RECONF_CONF", __func__);
> + ifaces = changed_ifaces(engine_conf, nconf);
> merge_config(engine_conf, nconf);
> nconf = NULL;
> + for (i = 0; ifaces[i] != 0; i++) {
> + if_index = ifaces[i];
> + if_name = if_indextoname(if_index, ifnamebuf);
> + iface = get_dhcpleased_iface_by_id(if_index);
> + if (if_name == NULL || iface == NULL)
> + continue;
> + iface_conf = find_iface_conf(
> + &engine_conf->iface_list, if_name);
> + if (iface_conf == NULL)
> + continue;
> + if (iface_conf->ignore & IGN_DNS)
> + send_rdns_withdraw(iface);
> + if (iface_conf->ignore & IGN_ROUTES)
> + send_routes_withdraw(iface);
> + }
> + free(ifaces);
> break;
> + }
> #endif /* SMALL */
> default:
> log_debug("%s: unexpected imsg %d", __func__,
> @@ -760,6 +785,18 @@ parse_dhcp(struct dhcpleased_iface *iface, struct
> imsg_dhcp *dhcp)
> if (inet_ntop(AF_INET, &ip->ip_dst, hbuf_dst, sizeof(hbuf_dst)) == NULL)
> hbuf_dst[0] = '\0';
>
> +#ifndef SMALL
> + if (iface_conf != NULL) {
> + for (i = 0; (int)i < iface_conf->ignore_servers_len; i++) {
> + if (iface_conf->ignore_servers[i].s_addr ==
> + ip->ip_src.s_addr) {
> + log_debug("ignoring server %s", hbuf_src);
> + return;
> + }
> + }
> + }
> +#endif /* SMALL */
> +
> if (rem < sizeof(*udp))
> goto too_short;
>
> @@ -1203,13 +1240,30 @@ parse_dhcp(struct dhcpleased_iface *iface, struct
> imsg_dhcp *dhcp)
> iface->server_identifier.s_addr = server_identifier.s_addr;
> iface->requested_ip.s_addr = dhcp_hdr->yiaddr.s_addr;
> iface->mask.s_addr = subnet_mask.s_addr;
> - iface->routes_len = routes_len;
> - memcpy(iface->routes, routes, sizeof(iface->routes));
> +#ifndef SMALL
> + if (iface_conf != NULL && iface_conf->ignore & IGN_ROUTES) {
> + iface->routes_len = 0;
> + memset(iface->routes, 0, sizeof(iface->routes));
> + } else
> +#endif /* SMALL */
> + {
> + iface->routes_len = routes_len;
> + memcpy(iface->routes, routes, sizeof(iface->routes));
> + }
> iface->lease_time = lease_time;
> iface->renewal_time = renewal_time;
> iface->rebinding_time = rebinding_time;
> - memcpy(iface->nameservers, nameservers,
> - sizeof(iface->nameservers));
> +
> +#ifndef SMALL
> + if (iface_conf != NULL && iface_conf->ignore & IGN_DNS) {
> + memset(iface->nameservers, 0,
> + sizeof(iface->nameservers));
> + } else
> +#endif /* SMALL */
> + {
> + memcpy(iface->nameservers, nameservers,
> + sizeof(iface->nameservers));
> + }
>
> iface->siaddr.s_addr = dhcp_hdr->siaddr.s_addr;
>
> @@ -1520,6 +1574,28 @@ send_deconfigure_interface(struct dhcpleased_iface
> *iface)
> memset(iface->routes, 0, sizeof(iface->routes));
> }
>
> +void
> +send_routes_withdraw(struct dhcpleased_iface *iface)
> +{
> + struct imsg_configure_interface imsg;
> +
> + if (iface->requested_ip.s_addr == INADDR_ANY || iface->routes_len == 0)
> + return;
> +
> + imsg.if_index = iface->if_index;
> + imsg.rdomain = iface->rdomain;
> + imsg.addr.s_addr = iface->requested_ip.s_addr;
> + imsg.mask.s_addr = iface->mask.s_addr;
> + imsg.siaddr.s_addr = iface->siaddr.s_addr;
> + strlcpy(imsg.file, iface->file, sizeof(imsg.file));
> + strlcpy(imsg.domainname, iface->domainname, sizeof(imsg.domainname));
> + strlcpy(imsg.hostname, iface->hostname, sizeof(imsg.hostname));
> + imsg.routes_len = iface->routes_len;
> + memcpy(imsg.routes, iface->routes, sizeof(imsg.routes));
> + engine_imsg_compose_main(IMSG_WITHDRAW_ROUTES, 0, &imsg,
> + sizeof(imsg));
> +}
> +
> void
> log_rdns(struct dhcpleased_iface *iface, int withdraw)
> {
> diff --git frontend.c frontend.c
> index 6e17b9c6c62..e52cfae0b25 100644
> --- frontend.c
> +++ frontend.c
> @@ -96,8 +96,6 @@ void send_discover(struct iface *);
> void send_request(struct iface *);
> void bpf_send_packet(struct iface *, uint8_t *, ssize_t);
> void udp_send_packet(struct iface *, uint8_t *, ssize_t);
> -int *changed_ifaces(struct dhcpleased_conf *, struct
> - dhcpleased_conf *);
> int iface_conf_cmp(struct iface_conf *, struct iface_conf *);
>
> LIST_HEAD(, iface) interfaces;
> @@ -1215,6 +1213,13 @@ iface_conf_cmp(struct iface_conf *a, struct iface_conf
> *b)
> return 1;
> if (memcmp(a->c_id, b->c_id, a->c_id_len) != 0)
> return 1;
> + if (a->ignore != b->ignore)
> + return 1;
> + if (a->ignore_servers_len != b->ignore_servers_len)
> + return 1;
> + if (memcmp(a->ignore_servers, b->ignore_servers,
> + a->ignore_servers_len * sizeof (struct in_addr)) != 0)
> + return 1;
> return 0;
> }
> #endif /* SMALL */
> diff --git parse.y parse.y
> index 947ed038f95..dc6f16d4400 100644
> --- parse.y
> +++ parse.y
> @@ -108,7 +108,7 @@ typedef struct {
>
> %}
>
> -%token DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT
> +%token DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT IGNORE DNS ROUTES
>
> %token <v.string> STRING
> %token <v.number> NUMBER
> @@ -275,6 +275,31 @@ ifaceoptsl : SEND VENDOR CLASS ID STRING {
> iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER;
> iface_conf->c_id[1] = iface_conf->c_id_len - 2;
> }
> + | IGNORE ROUTES {
> + iface_conf->ignore |= IGN_ROUTES;
> + }
> + | IGNORE DNS {
> + iface_conf->ignore |= IGN_DNS;
> + }
> + | IGNORE STRING {
> + int res;
> +
> + if (iface_conf->ignore_servers_len >= MAX_SERVERS) {
> + yyerror("too many servers to ignore");
> + free($2);
> + YYERROR;
> + }
> + res = inet_pton(AF_INET, $2,
> + &iface_conf->ignore_servers[
> + iface_conf->ignore_servers_len++]);
> +
> + if (res != 1) {
> + yyerror("Invalid server IP %s", $2);
> + free($2);
> + YYERROR;
> + }
> + free($2);
> + }
> ;
> %%
>
> @@ -312,8 +337,11 @@ lookup(char *s)
> static const struct keywords keywords[] = {
> {"class", CLASS},
> {"client", CLIENT},
> + {"dns", DNS},
> {"id", ID},
> + {"ignore", IGNORE},
> {"interface", DHCP_IFACE},
> + {"routes", ROUTES},
> {"send", SEND},
> {"vendor", VENDOR},
> };
> diff --git printconf.c printconf.c
> index 16b33bbfe40..86eef19baf3 100644
> --- printconf.c
> +++ printconf.c
> @@ -106,11 +106,24 @@ void
> print_config(struct dhcpleased_conf *conf)
> {
> struct iface_conf *iface;
> + int i;
> + char hbuf[INET_ADDRSTRLEN];
>
> SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
> printf("interface %s {\n", iface->name);
> print_dhcp_options("\t", iface->c_id, iface->c_id_len);
> print_dhcp_options("\t", iface->vc_id, iface->vc_id_len);
> + if (iface->ignore & IGN_DNS)
> + printf("\tignore dns\n");
> + if (iface->ignore & IGN_ROUTES)
> + printf("\tignore routes\n");
> + for (i = 0; i < iface->ignore_servers_len; i++) {
> + if (inet_ntop(AF_INET, &iface->ignore_servers[i],
> + hbuf, sizeof(hbuf)) == NULL)
> + continue;
> + printf("\tignore %s\n", hbuf);
> +
> + }
> printf("}\n");
> }
> }
>
>
>
> --
> I'm not entirely sure you are real.
>