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. >