diff options
author | Florian Obser <florian@cvs.openbsd.org> | 2021-06-16 14:06:19 +0000 |
---|---|---|
committer | Florian Obser <florian@cvs.openbsd.org> | 2021-06-16 14:06:19 +0000 |
commit | 1c97918c2ae4bb55b6fd37dcfe90359ed906e220 (patch) | |
tree | fabdbfc24fe4f0b990fc5b27cdd0aca3cd1f67ea /sbin/dhcpleased | |
parent | 9c41b21bce76fe09701a476964354b99e1b548a1 (diff) |
Implement classless static routes dhcp option.
For this we need to be able to handle multiple routes being sent from
the engine to the main process as well as to the control tool.
The configuration of the various cases (default route, directly
connected routes, non-default route via a gateway) was inspired by
dhclient's set_routes() and should behave the same way.
Tested by Uwe Werler
Diffstat (limited to 'sbin/dhcpleased')
-rw-r--r-- | sbin/dhcpleased/dhcpleased.c | 165 | ||||
-rw-r--r-- | sbin/dhcpleased/dhcpleased.h | 14 | ||||
-rw-r--r-- | sbin/dhcpleased/engine.c | 109 | ||||
-rw-r--r-- | sbin/dhcpleased/engine.h | 8 |
4 files changed, 222 insertions, 74 deletions
diff --git a/sbin/dhcpleased/dhcpleased.c b/sbin/dhcpleased/dhcpleased.c index 46685012402..ad18e134147 100644 --- a/sbin/dhcpleased/dhcpleased.c +++ b/sbin/dhcpleased/dhcpleased.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpleased.c,v 1.11 2021/05/01 11:52:36 florian Exp $ */ +/* $OpenBSD: dhcpleased.c,v 1.12 2021/06/16 14:06:17 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org> @@ -77,7 +77,9 @@ void open_bpfsock(uint32_t); void configure_interface(struct imsg_configure_interface *); void deconfigure_interface(struct imsg_configure_interface *); void propose_rdns(struct imsg_propose_rdns *); -void configure_gateway(struct imsg_configure_interface *, uint8_t); +void configure_routes(uint8_t, struct imsg_configure_interface *); +void configure_route(uint8_t, uint32_t, int, struct sockaddr_in *, struct + sockaddr_in *, struct sockaddr_in *, struct sockaddr_in *, int); void read_lease_file(struct imsg_ifinfo *); static int main_imsg_send_ipc_sockets(struct imsgbuf *, struct imsgbuf *); @@ -486,6 +488,8 @@ main_dispatch_engine(int fd, short event, void *bula) 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__); configure_interface(&imsg_interface); break; } @@ -497,6 +501,8 @@ main_dispatch_engine(int fd, short event, void *bula) 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__); deconfigure_interface(&imsg_interface); main_imsg_compose_frontend(IMSG_CLOSE_UDPSOCK, -1, &imsg_interface.if_index, @@ -683,9 +689,8 @@ configure_interface(struct imsg_configure_interface *imsg) if (ioctl(ioctl_sock, SIOCAIFADDR, &ifaliasreq) == -1) fatal("SIOCAIFADDR"); - /* XXX check weird shit in dhclient/kroute.c set_routes() */ - if (imsg->router.s_addr != INADDR_ANY) - configure_gateway(imsg, RTM_ADD); + if (imsg->routes_len > 0) + configure_routes(RTM_ADD, imsg); } req_sin_addr->sin_port = ntohs(CLIENT_PORT); if ((udpsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { @@ -773,8 +778,8 @@ deconfigure_interface(struct imsg_configure_interface *imsg) memset(&ifaliasreq, 0, sizeof(ifaliasreq)); - if (imsg->router.s_addr != INADDR_ANY) - configure_gateway(imsg, RTM_DELETE); + if (imsg->routes_len > 0) + configure_routes(RTM_DELETE, imsg); if (if_indextoname(imsg->if_index, ifaliasreq.ifra_name) == NULL) { log_warnx("%s: cannot find interface %d", __func__, @@ -795,14 +800,87 @@ deconfigure_interface(struct imsg_configure_interface *imsg) } } +void +configure_routes(uint8_t rtm_type, struct imsg_configure_interface *imsg) +{ + struct sockaddr_in dst, mask, gw, ifa; + in_addr_t addrnet, gwnet; + int i; + + memset(&ifa, 0, sizeof(ifa)); + ifa.sin_family = AF_INET; + ifa.sin_len = sizeof(ifa); + ifa.sin_addr.s_addr = imsg->addr.s_addr; + + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_len = sizeof(dst); + + memset(&mask, 0, sizeof(mask)); + mask.sin_family = AF_INET; + mask.sin_len = sizeof(mask); + + memset(&gw, 0, sizeof(gw)); + gw.sin_family = AF_INET; + gw.sin_len = sizeof(gw); + + addrnet = imsg->addr.s_addr & imsg->mask.s_addr; + + for (i = 0; i < imsg->routes_len; i++) { + dst.sin_addr.s_addr = imsg->routes[i].dst.s_addr; + mask.sin_addr.s_addr = imsg->routes[i].mask.s_addr; + gw.sin_addr.s_addr = imsg->routes[i].gw.s_addr; + + if (gw.sin_addr.s_addr == INADDR_ANY) { + /* direct route */ + configure_route(rtm_type, imsg->if_index, + imsg->rdomain, &dst, &mask, &ifa, NULL, + RTF_CLONING); + } else if (mask.sin_addr.s_addr == INADDR_ANY) { + /* default route */ + gwnet = gw.sin_addr.s_addr & imsg->mask.s_addr; + if (addrnet != gwnet) { + /* + * The gateway for the default route is outside + * the configured prefix. Install a direct + * cloning route for the gateway to make the + * default route reachable. + */ + mask.sin_addr.s_addr = 0xffffffff; + configure_route(rtm_type, imsg->if_index, + imsg->rdomain, &gw, &mask, &ifa, NULL, + RTF_CLONING); + mask.sin_addr.s_addr = + imsg->routes[i].mask.s_addr; + } + + if (gw.sin_addr.s_addr == ifa.sin_addr.s_addr) { + /* directly connected default */ + configure_route(rtm_type, imsg->if_index, + imsg->rdomain, &dst, &mask, &gw, NULL, 0); + } else { + /* default route via gateway */ + configure_route(rtm_type, imsg->if_index, + imsg->rdomain, &dst, &mask, &gw, &ifa, + RTF_GATEWAY); + } + } else { + /* non-default via gateway */ + configure_route(rtm_type, imsg->if_index, imsg->rdomain, + &dst, &mask, &gw, NULL, RTF_GATEWAY); + } + } +} + #define ROUNDUP(a) \ (((a) & (sizeof(long) - 1)) ? (1 + ((a) | (sizeof(long) - 1))) : (a)) void -configure_gateway(struct imsg_configure_interface *imsg, uint8_t rtm_type) +configure_route(uint8_t rtm_type, uint32_t if_index, int rdomain, struct + sockaddr_in *dst, struct sockaddr_in *mask, struct sockaddr_in *gw, + struct sockaddr_in *ifa, int rtm_flags) { struct rt_msghdr rtm; struct sockaddr_rtlabel rl; - struct sockaddr_in dst, gw, mask, ifa; struct iovec iov[12]; long pad = 0; int iovcnt = 0, padlen; @@ -812,70 +890,59 @@ configure_gateway(struct imsg_configure_interface *imsg, uint8_t rtm_type) rtm.rtm_version = RTM_VERSION; rtm.rtm_type = rtm_type; rtm.rtm_msglen = sizeof(rtm); - rtm.rtm_tableid = imsg->rdomain; - rtm.rtm_index = imsg->if_index; + rtm.rtm_index = if_index; + rtm.rtm_tableid = rdomain; rtm.rtm_seq = ++rtm_seq; rtm.rtm_priority = RTP_NONE; - rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFA | - RTA_LABEL; - rtm.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC | RTF_MPATH; + rtm.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_LABEL; + rtm.rtm_flags = RTF_UP | RTF_STATIC | RTF_MPATH | rtm_flags; + + if (ifa) + rtm.rtm_addrs |= RTA_IFA; iov[iovcnt].iov_base = &rtm; iov[iovcnt++].iov_len = sizeof(rtm); - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_len = sizeof(struct sockaddr_in); - - iov[iovcnt].iov_base = &dst; - iov[iovcnt++].iov_len = sizeof(dst); - rtm.rtm_msglen += sizeof(dst); - padlen = ROUNDUP(sizeof(dst)) - sizeof(dst); + iov[iovcnt].iov_base = dst; + iov[iovcnt++].iov_len = dst->sin_len; + rtm.rtm_msglen += dst->sin_len; + padlen = ROUNDUP(dst->sin_len) - dst->sin_len; if (padlen > 0) { iov[iovcnt].iov_base = &pad; iov[iovcnt++].iov_len = padlen; rtm.rtm_msglen += padlen; } - memset(&gw, 0, sizeof(gw)); - memcpy(&gw.sin_addr, &imsg->router, sizeof(gw.sin_addr)); - gw.sin_family = AF_INET; - gw.sin_len = sizeof(struct sockaddr_in); - iov[iovcnt].iov_base = &gw; - iov[iovcnt++].iov_len = sizeof(gw); - rtm.rtm_msglen += sizeof(gw); - padlen = ROUNDUP(sizeof(gw)) - sizeof(gw); + iov[iovcnt].iov_base = gw; + iov[iovcnt++].iov_len = gw->sin_len; + rtm.rtm_msglen += gw->sin_len; + padlen = ROUNDUP(gw->sin_len) - gw->sin_len; if (padlen > 0) { iov[iovcnt].iov_base = &pad; iov[iovcnt++].iov_len = padlen; rtm.rtm_msglen += padlen; } - memset(&mask, 0, sizeof(mask)); - mask.sin_family = AF_INET; - mask.sin_len = sizeof(struct sockaddr_in); - iov[iovcnt].iov_base = &mask; - iov[iovcnt++].iov_len = sizeof(mask); - rtm.rtm_msglen += sizeof(mask); - padlen = ROUNDUP(sizeof(mask)) - sizeof(mask); + iov[iovcnt].iov_base = mask; + iov[iovcnt++].iov_len = mask->sin_len; + rtm.rtm_msglen += mask->sin_len; + padlen = ROUNDUP(mask->sin_len) - mask->sin_len; if (padlen > 0) { iov[iovcnt].iov_base = &pad; iov[iovcnt++].iov_len = padlen; rtm.rtm_msglen += padlen; } - memset(&ifa, 0, sizeof(ifa)); - memcpy(&ifa.sin_addr, &imsg->addr, sizeof(ifa.sin_addr)); - ifa.sin_family = AF_INET; - ifa.sin_len = sizeof(struct sockaddr_in); - iov[iovcnt].iov_base = &ifa; - iov[iovcnt++].iov_len = sizeof(ifa); - rtm.rtm_msglen += sizeof(ifa); - padlen = ROUNDUP(sizeof(ifa)) - sizeof(ifa); - if (padlen > 0) { - iov[iovcnt].iov_base = &pad; - iov[iovcnt++].iov_len = padlen; - rtm.rtm_msglen += padlen; + if (ifa) { + iov[iovcnt].iov_base = ifa; + iov[iovcnt++].iov_len = ifa->sin_len; + rtm.rtm_msglen += ifa->sin_len; + padlen = ROUNDUP(ifa->sin_len) - ifa->sin_len; + if (padlen > 0) { + iov[iovcnt].iov_base = &pad; + iov[iovcnt++].iov_len = padlen; + rtm.rtm_msglen += padlen; + } } memset(&rl, 0, sizeof(rl)); diff --git a/sbin/dhcpleased/dhcpleased.h b/sbin/dhcpleased/dhcpleased.h index d4aa71c525d..bf23ba76df5 100644 --- a/sbin/dhcpleased/dhcpleased.h +++ b/sbin/dhcpleased/dhcpleased.h @@ -1,4 +1,4 @@ -/* $OpenBSD: dhcpleased.h,v 1.4 2021/04/10 17:22:34 florian Exp $ */ +/* $OpenBSD: dhcpleased.h,v 1.5 2021/06/16 14:06:17 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org> @@ -30,6 +30,9 @@ #define DHCPLEASED_MAX_DNSSL 1025 #define MAX_RDNS_COUNT 8 /* max nameserver in a RTM_PROPOSAL */ +/* A 1500 bytes packet can hold less than 300 classless static routes */ +#define MAX_DHCP_ROUTES 256 + #define DHCP_COOKIE {99, 130, 83, 99} /* Possible values for hardware type (htype) field. */ @@ -168,6 +171,12 @@ struct imsgev { short events; }; +struct dhcp_route { + struct in_addr dst; + struct in_addr mask; + struct in_addr gw; +}; + enum imsg_type { IMSG_NONE, #ifndef SMALL @@ -207,7 +216,8 @@ struct ctl_engine_info { struct in_addr dhcp_server; /* for unicast */ struct in_addr requested_ip; struct in_addr mask; - struct in_addr router; + struct dhcp_route routes[MAX_DHCP_ROUTES]; + int routes_len; struct in_addr nameservers[MAX_RDNS_COUNT]; uint32_t lease_time; uint32_t renewal_time; diff --git a/sbin/dhcpleased/engine.c b/sbin/dhcpleased/engine.c index badd7c76709..be25cf2cf05 100644 --- a/sbin/dhcpleased/engine.c +++ b/sbin/dhcpleased/engine.c @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.c,v 1.14 2021/05/01 11:51:59 florian Exp $ */ +/* $OpenBSD: engine.c,v 1.15 2021/06/16 14:06:17 florian Exp $ */ /* * Copyright (c) 2017, 2021 Florian Obser <florian@openbsd.org> @@ -101,7 +101,8 @@ struct dhcpleased_iface { struct in_addr dhcp_server; /* for unicast */ struct in_addr requested_ip; struct in_addr mask; - struct in_addr router; + struct dhcp_route routes[MAX_DHCP_ROUTES]; + int routes_len; struct in_addr nameservers[MAX_RDNS_COUNT]; uint32_t lease_time; uint32_t renewal_time; @@ -467,7 +468,8 @@ send_interface_info(struct dhcpleased_iface *iface, pid_t pid) cei.dhcp_server.s_addr = iface->dhcp_server.s_addr; cei.requested_ip.s_addr = iface->requested_ip.s_addr; cei.mask.s_addr = iface->mask.s_addr; - cei.router.s_addr = iface->router.s_addr; + cei.routes_len = iface->routes_len; + memcpy(cei.routes, iface->routes, sizeof(cei.routes)); memcpy(cei.nameservers, iface->nameservers, sizeof(cei.nameservers)); cei.lease_time = iface->lease_time; cei.renewal_time = iface->renewal_time; @@ -599,13 +601,15 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) struct ip *ip; struct udphdr *udp; struct dhcp_hdr *dhcp_hdr; - struct in_addr server_identifier, subnet_mask, router; + struct in_addr server_identifier, subnet_mask; struct in_addr nameservers[MAX_RDNS_COUNT]; + struct dhcp_route routes[MAX_DHCP_ROUTES]; size_t rem, i; uint32_t sum, usum, lease_time = 0, renewal_time = 0; uint32_t rebinding_time = 0; uint8_t *p, dho = DHO_PAD, dho_len; uint8_t dhcp_message_type = 0; + int routes_len = 0; char from[sizeof("xx:xx:xx:xx:xx:xx")]; char to[sizeof("xx:xx:xx:xx:xx:xx")]; char hbuf_src[INET_ADDRSTRLEN]; @@ -744,7 +748,7 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) memset(&server_identifier, 0, sizeof(server_identifier)); memset(&subnet_mask, 0, sizeof(subnet_mask)); - memset(&router, 0, sizeof(router)); + memset(&routes, 0, sizeof(routes)); memset(&nameservers, 0, sizeof(nameservers)); while (rem > 0 && dho != DHO_END) { @@ -822,19 +826,30 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) rem -= dho_len; break; case DHO_ROUTERS: - if (dho_len < sizeof(router)) + if (dho_len < sizeof(routes[routes_len].gw)) goto wrong_length; - if (dho_len % sizeof(router) != 0) + if (dho_len % sizeof(routes[routes_len].gw) != 0) goto wrong_length; - /* we only use one router */ - memcpy(&router, p, sizeof(router)); - if (log_getverbose() > 1) { - log_debug("DHO_ROUTER: %s (1/%lu)", - inet_ntop(AF_INET, &router, hbuf, - sizeof(hbuf)), dho_len / sizeof(router)); + + while (routes_len < MAX_DHCP_ROUTES && dho_len > 0) { + memcpy(&routes[routes_len].gw, p, + sizeof(routes[routes_len].gw)); + if (log_getverbose() > 1) { + log_debug("DHO_ROUTER: %s", + inet_ntop(AF_INET, + &routes[routes_len].gw, hbuf, + sizeof(hbuf))); + } + p += sizeof(routes[routes_len].gw); + rem -= sizeof(routes[routes_len].gw); + dho_len -= sizeof(routes[routes_len].gw); + routes_len++; + } + if (dho_len != 0) { + /* ignore > MAX_DHCP_ROUTES routes */ + p += dho_len; + rem -= dho_len; } - p += dho_len; - rem -= dho_len; break; case DHO_DOMAIN_NAME_SERVERS: if (dho_len < sizeof(nameservers[0])) @@ -908,6 +923,58 @@ parse_dhcp(struct dhcpleased_iface *iface, struct imsg_dhcp *dhcp) p += dho_len; rem -= dho_len; break; + case DHO_CLASSLESS_STATIC_ROUTES: { + int prefixlen, compressed_prefixlen; + + while (routes_len < MAX_DHCP_ROUTES && dho_len > 0) { + prefixlen = *p; + p += 1; + rem -= 1; + dho_len -= 1; + + if (prefixlen < 0 || prefixlen > 32) { + log_warnx("%s: invalid prefixlen: %d", + __func__, prefixlen); + return; + } + + if (prefixlen > 0) + routes[routes_len].mask.s_addr = + htonl(0xffffffff << (32 - + prefixlen)); + else + routes[routes_len].mask.s_addr = + INADDR_ANY; + + compressed_prefixlen = (prefixlen + 7) / 8; + if (dho_len < compressed_prefixlen) + goto wrong_length; + + memcpy(&routes[routes_len].dst, p, + compressed_prefixlen); + p += compressed_prefixlen; + rem -= compressed_prefixlen; + dho_len -= compressed_prefixlen; + + if (dho_len < sizeof(routes[routes_len].gw)) + goto wrong_length; + + memcpy(&routes[routes_len].gw, p, + sizeof(routes[routes_len].gw)); + p += sizeof(routes[routes_len].gw); + rem -= sizeof(routes[routes_len].gw); + dho_len -= sizeof(routes[routes_len].gw); + + routes_len++; + } + + if (dho_len != 0) { + /* ignore > MAX_DHCP_ROUTES routes */ + p += dho_len; + rem -= dho_len; + } + break; + } default: if (log_getverbose() > 1) log_debug("DHO_%u, len: %u", dho, dho_len); @@ -997,7 +1064,8 @@ 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->router.s_addr = router.s_addr; + 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; @@ -1262,7 +1330,8 @@ send_configure_interface(struct dhcpleased_iface *iface) imsg.rdomain = iface->rdomain; imsg.addr.s_addr = iface->requested_ip.s_addr; imsg.mask.s_addr = iface->mask.s_addr; - imsg.router.s_addr = iface->router.s_addr; + imsg.routes_len = iface->routes_len; + memcpy(imsg.routes, iface->routes, sizeof(imsg.routes)); engine_imsg_compose_main(IMSG_CONFIGURE_INTERFACE, 0, &imsg, sizeof(imsg)); } @@ -1281,7 +1350,8 @@ send_deconfigure_interface(struct dhcpleased_iface *iface) imsg.rdomain = iface->rdomain; imsg.addr.s_addr = iface->requested_ip.s_addr; imsg.mask.s_addr = iface->mask.s_addr; - imsg.router.s_addr = iface->router.s_addr; + imsg.routes_len = iface->routes_len; + memcpy(imsg.routes, iface->routes, sizeof(imsg.routes)); engine_imsg_compose_main(IMSG_DECONFIGURE_INTERFACE, 0, &imsg, sizeof(imsg)); @@ -1289,7 +1359,8 @@ send_deconfigure_interface(struct dhcpleased_iface *iface) iface->dhcp_server.s_addr = INADDR_ANY; iface->requested_ip.s_addr = INADDR_ANY; iface->mask.s_addr = INADDR_ANY; - iface->router.s_addr = INADDR_ANY; + iface->routes_len = 0; + memset(iface->routes, 0, sizeof(iface->routes)); } void diff --git a/sbin/dhcpleased/engine.h b/sbin/dhcpleased/engine.h index 317825f2a71..c6aded13005 100644 --- a/sbin/dhcpleased/engine.h +++ b/sbin/dhcpleased/engine.h @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.h,v 1.1 2021/02/26 16:16:37 florian Exp $ */ +/* $OpenBSD: engine.h,v 1.2 2021/06/16 14:06:17 florian Exp $ */ /* * Copyright (c) 2021 Florian Obser <florian@openbsd.org> @@ -18,11 +18,11 @@ struct imsg_configure_interface { uint32_t if_index; + int rdomain; struct in_addr addr; struct in_addr mask; - struct in_addr router; - int rdomain; - + struct dhcp_route routes[MAX_DHCP_ROUTES]; + int routes_len; }; void engine(int, int); |