| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2015 Roy Marples <[email protected]> |
| * All rights reserved |
| |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. |
| */ |
| |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <net/route.h> |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "config.h" |
| #include "arp.h" |
| #include "common.h" |
| #include "dhcpcd.h" |
| #include "dhcp.h" |
| #include "if.h" |
| #include "if-options.h" |
| #include "ipv4.h" |
| #include "script.h" |
| #ifdef PASSIVE_MODE |
| #include "rpc-interface.h" |
| #endif |
| |
| #define IPV4_LOOPBACK_ROUTE |
| #if defined(__linux__) || (defined(BSD) && defined(RTF_LOCAL)) |
| /* Linux has had loopback routes in the local table since 2.2 */ |
| #undef IPV4_LOOPBACK_ROUTE |
| #endif |
| |
| uint8_t |
| inet_ntocidr(struct in_addr address) |
| { |
| uint8_t cidr = 0; |
| uint32_t mask = htonl(address.s_addr); |
| |
| while (mask) { |
| cidr++; |
| mask <<= 1; |
| } |
| return cidr; |
| } |
| |
| int |
| inet_cidrtoaddr(int cidr, struct in_addr *addr) |
| { |
| int ocets; |
| |
| if (cidr < 1 || cidr > 32) { |
| errno = EINVAL; |
| return -1; |
| } |
| ocets = (cidr + 7) / NBBY; |
| |
| addr->s_addr = 0; |
| if (ocets > 0) { |
| memset(&addr->s_addr, 255, (size_t)ocets - 1); |
| memset((unsigned char *)&addr->s_addr + (ocets - 1), |
| (256 - (1 << (32 - cidr) % NBBY)), 1); |
| } |
| |
| return 0; |
| } |
| |
| uint32_t |
| ipv4_getnetmask(uint32_t addr) |
| { |
| uint32_t dst; |
| |
| if (addr == 0) |
| return 0; |
| |
| dst = htonl(addr); |
| if (IN_CLASSA(dst)) |
| return ntohl(IN_CLASSA_NET); |
| if (IN_CLASSB(dst)) |
| return ntohl(IN_CLASSB_NET); |
| if (IN_CLASSC(dst)) |
| return ntohl(IN_CLASSC_NET); |
| |
| return 0; |
| } |
| |
| struct ipv4_addr * |
| ipv4_iffindaddr(struct interface *ifp, |
| const struct in_addr *addr, const struct in_addr *net) |
| { |
| struct ipv4_state *state; |
| struct ipv4_addr *ap; |
| |
| state = IPV4_STATE(ifp); |
| if (state) { |
| TAILQ_FOREACH(ap, &state->addrs, next) { |
| if ((addr == NULL || ap->addr.s_addr == addr->s_addr) && |
| (net == NULL || ap->net.s_addr == net->s_addr)) |
| return ap; |
| } |
| } |
| return NULL; |
| } |
| |
| struct ipv4_addr * |
| ipv4_iffindlladdr(struct interface *ifp) |
| { |
| struct ipv4_state *state; |
| struct ipv4_addr *ap; |
| |
| state = IPV4_STATE(ifp); |
| if (state) { |
| TAILQ_FOREACH(ap, &state->addrs, next) { |
| if (IN_LINKLOCAL(htonl(ap->addr.s_addr))) |
| return ap; |
| } |
| } |
| return NULL; |
| } |
| |
| struct ipv4_addr * |
| ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr) |
| { |
| struct interface *ifp; |
| struct ipv4_addr *ap; |
| |
| TAILQ_FOREACH(ifp, ctx->ifaces, next) { |
| ap = ipv4_iffindaddr(ifp, addr, NULL); |
| if (ap) |
| return ap; |
| } |
| return NULL; |
| } |
| |
| int |
| ipv4_addrexists(struct dhcpcd_ctx *ctx, const struct in_addr *addr) |
| { |
| struct interface *ifp; |
| struct dhcp_state *state; |
| |
| TAILQ_FOREACH(ifp, ctx->ifaces, next) { |
| state = D_STATE(ifp); |
| if (state) { |
| if (addr == NULL) { |
| if (state->addr.s_addr != INADDR_ANY) |
| return 1; |
| } else if (addr->s_addr == state->addr.s_addr) |
| return 1; |
| } |
| if (addr != NULL && ipv4_iffindaddr(ifp, addr, NULL)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| void |
| ipv4_freeroutes(struct rt_head *rts) |
| { |
| |
| if (rts) { |
| ipv4_freerts(rts); |
| free(rts); |
| } |
| } |
| |
| int |
| ipv4_init(struct dhcpcd_ctx *ctx) |
| { |
| |
| if (ctx->ipv4_routes == NULL) { |
| ctx->ipv4_routes = malloc(sizeof(*ctx->ipv4_routes)); |
| if (ctx->ipv4_routes == NULL) |
| return -1; |
| TAILQ_INIT(ctx->ipv4_routes); |
| } |
| if (ctx->ipv4_kroutes == NULL) { |
| ctx->ipv4_kroutes = malloc(sizeof(*ctx->ipv4_kroutes)); |
| if (ctx->ipv4_kroutes == NULL) |
| return -1; |
| TAILQ_INIT(ctx->ipv4_kroutes); |
| } |
| return 0; |
| } |
| |
| /* Interface comparer for working out ordering. */ |
| int |
| ipv4_ifcmp(const struct interface *si, const struct interface *ti) |
| { |
| const struct dhcp_state *sis, *tis; |
| |
| sis = D_CSTATE(si); |
| tis = D_CSTATE(ti); |
| if (sis && !tis) |
| return -1; |
| if (!sis && tis) |
| return 1; |
| if (!sis && !tis) |
| return 0; |
| /* If one has a lease and the other not, it takes precedence. */ |
| if (sis->new && !tis->new) |
| return -1; |
| if (!sis->new && tis->new) |
| return 1; |
| /* Always prefer proper leases */ |
| if (!(sis->added & STATE_FAKE) && (sis->added & STATE_FAKE)) |
| return -1; |
| if ((sis->added & STATE_FAKE) && !(sis->added & STATE_FAKE)) |
| return 1; |
| /* If we are either, they neither have a lease, or they both have. |
| * We need to check for IPv4LL and make it non-preferred. */ |
| if (sis->new && tis->new) { |
| int sill = (sis->new->cookie == htonl(MAGIC_COOKIE)); |
| int till = (tis->new->cookie == htonl(MAGIC_COOKIE)); |
| if (sill && !till) |
| return -1; |
| if (!sill && till) |
| return 1; |
| } |
| return 0; |
| } |
| |
| static struct rt * |
| find_route(struct rt_head *rts, const struct rt *r, const struct rt *srt) |
| { |
| struct rt *rt; |
| |
| if (rts == NULL) |
| return NULL; |
| TAILQ_FOREACH(rt, rts, next) { |
| if (rt->dest.s_addr == r->dest.s_addr && |
| #ifdef HAVE_ROUTE_METRIC |
| (srt || (r->iface == NULL || rt->iface == NULL || |
| rt->iface->metric == r->iface->metric)) && |
| #endif |
| (!srt || srt != rt) && |
| rt->net.s_addr == r->net.s_addr) |
| return rt; |
| } |
| return NULL; |
| } |
| |
| static void |
| desc_route(const char *cmd, const struct rt *rt) |
| { |
| char addr[sizeof("000.000.000.000") + 1]; |
| struct dhcpcd_ctx *ctx = rt->iface ? rt->iface->ctx : NULL; |
| const char *ifname = rt->iface ? rt->iface->name : NULL; |
| |
| strlcpy(addr, inet_ntoa(rt->dest), sizeof(addr)); |
| if (rt->net.s_addr == htonl(INADDR_BROADCAST) && |
| rt->gate.s_addr == htonl(INADDR_ANY)) |
| logger(ctx, LOG_INFO, "%s: %s host route to %s", |
| ifname, cmd, addr); |
| else if (rt->net.s_addr == htonl(INADDR_BROADCAST)) |
| logger(ctx, LOG_INFO, "%s: %s host route to %s via %s", |
| ifname, cmd, addr, inet_ntoa(rt->gate)); |
| else if (rt->gate.s_addr == htonl(INADDR_ANY)) |
| logger(ctx, LOG_INFO, "%s: %s route to %s/%d", |
| ifname, cmd, addr, inet_ntocidr(rt->net)); |
| else if (rt->dest.s_addr == htonl(INADDR_ANY) && |
| rt->net.s_addr == htonl(INADDR_ANY)) |
| logger(ctx, LOG_INFO, "%s: %s default route via %s", |
| ifname, cmd, inet_ntoa(rt->gate)); |
| else |
| logger(ctx, LOG_INFO, "%s: %s route to %s/%d via %s", |
| ifname, cmd, addr, inet_ntocidr(rt->net), |
| inet_ntoa(rt->gate)); |
| } |
| |
| static struct rt * |
| ipv4_findrt(struct dhcpcd_ctx *ctx, const struct rt *rt, int flags) |
| { |
| struct rt *r; |
| |
| if (ctx->ipv4_kroutes == NULL) |
| return NULL; |
| TAILQ_FOREACH(r, ctx->ipv4_kroutes, next) { |
| if (rt->dest.s_addr == r->dest.s_addr && |
| #ifdef HAVE_ROUTE_METRIC |
| rt->iface == r->iface && |
| (!flags || rt->metric == r->metric) && |
| #else |
| (!flags || rt->iface == r->iface) && |
| #endif |
| rt->net.s_addr == r->net.s_addr) |
| return r; |
| } |
| return NULL; |
| } |
| |
| void |
| ipv4_freerts(struct rt_head *routes) |
| { |
| struct rt *rt; |
| |
| while ((rt = TAILQ_FIRST(routes))) { |
| TAILQ_REMOVE(routes, rt, next); |
| free(rt); |
| } |
| } |
| |
| /* If something other than dhcpcd removes a route, |
| * we need to remove it from our internal table. */ |
| int |
| ipv4_handlert(struct dhcpcd_ctx *ctx, int cmd, struct rt *rt) |
| { |
| struct rt *f; |
| |
| if (ctx->ipv4_kroutes == NULL) |
| return 0; |
| |
| f = ipv4_findrt(ctx, rt, 1); |
| switch (cmd) { |
| case RTM_ADD: |
| if (f == NULL) { |
| if ((f = malloc(sizeof(*f))) == NULL) |
| return -1; |
| *f = *rt; |
| TAILQ_INSERT_TAIL(ctx->ipv4_kroutes, f, next); |
| } |
| break; |
| case RTM_DELETE: |
| if (f) { |
| TAILQ_REMOVE(ctx->ipv4_kroutes, f, next); |
| free(f); |
| } |
| |
| /* If we manage the route, remove it */ |
| if ((f = find_route(ctx->ipv4_routes, rt, NULL))) { |
| desc_route("removing", f); |
| TAILQ_REMOVE(ctx->ipv4_routes, f, next); |
| free(f); |
| } |
| break; |
| } |
| return 0; |
| } |
| |
| #define n_route(a) nc_route(NULL, a) |
| #define c_route(a, b) nc_route(a, b) |
| static int |
| nc_route(struct rt *ort, struct rt *nrt) |
| { |
| |
| /* Don't set default routes if not asked to */ |
| if (nrt->dest.s_addr == 0 && |
| nrt->net.s_addr == 0 && |
| !(nrt->iface->options->options & DHCPCD_GATEWAY)) |
| return -1; |
| |
| desc_route(ort == NULL ? "adding" : "changing", nrt); |
| |
| if (ort == NULL) { |
| ort = ipv4_findrt(nrt->iface->ctx, nrt, 0); |
| if (ort && |
| ((ort->flags & RTF_REJECT && nrt->flags & RTF_REJECT) || |
| (ort->iface == nrt->iface && |
| #ifdef HAVE_ROUTE_METRIC |
| ort->metric == nrt->metric && |
| #endif |
| ort->gate.s_addr == nrt->gate.s_addr))) |
| return 0; |
| } else if (ort->flags & STATE_FAKE && !(nrt->flags & STATE_FAKE) && |
| ort->iface == nrt->iface && |
| #ifdef HAVE_ROUTE_METRIC |
| ort->metric == nrt->metric && |
| #endif |
| ort->dest.s_addr == nrt->dest.s_addr && |
| ort->net.s_addr == nrt->net.s_addr && |
| ort->gate.s_addr == nrt->gate.s_addr) |
| return 0; |
| |
| #ifdef HAVE_ROUTE_METRIC |
| /* With route metrics, we can safely add the new route before |
| * deleting the old route. */ |
| if (if_route(RTM_ADD, nrt) == 0) { |
| if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) |
| logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m"); |
| return 0; |
| } |
| |
| /* If the kernel claims the route exists we need to rip out the |
| * old one first. */ |
| if (errno != EEXIST || ort == NULL) |
| goto logerr; |
| #endif |
| |
| /* No route metrics, we need to delete the old route before |
| * adding the new one. */ |
| if (ort && if_route(RTM_DELETE, ort) == -1 && errno != ESRCH) |
| logger(nrt->iface->ctx, LOG_ERR, "if_route (DEL): %m"); |
| if (if_route(RTM_ADD, nrt) == 0) |
| return 0; |
| #ifdef HAVE_ROUTE_METRIC |
| logerr: |
| #endif |
| logger(nrt->iface->ctx, LOG_ERR, "if_route (ADD): %m"); |
| return -1; |
| } |
| |
| static int |
| d_route(struct rt *rt) |
| { |
| int retval; |
| |
| desc_route("deleting", rt); |
| retval = if_route(RTM_DELETE, rt); |
| if (retval != 0 && errno != ENOENT && errno != ESRCH) |
| logger(rt->iface->ctx, LOG_ERR, |
| "%s: if_delroute: %m", rt->iface->name); |
| return retval; |
| } |
| |
| static struct rt_head * |
| add_subnet_route(struct rt_head *rt, const struct interface *ifp) |
| { |
| const struct dhcp_state *s; |
| struct rt *r; |
| |
| if (rt == NULL) /* earlier malloc failed */ |
| return NULL; |
| |
| s = D_CSTATE(ifp); |
| /* Don't create a subnet route for these addresses */ |
| if (s->net.s_addr == INADDR_ANY) |
| return rt; |
| #ifndef BSD |
| /* BSD adds a route in this instance */ |
| if (s->net.s_addr == INADDR_BROADCAST) |
| return rt; |
| #endif |
| |
| if ((r = malloc(sizeof(*r))) == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| ipv4_freeroutes(rt); |
| return NULL; |
| } |
| r->dest.s_addr = s->addr.s_addr & s->net.s_addr; |
| r->net.s_addr = s->net.s_addr; |
| r->gate.s_addr = INADDR_ANY; |
| |
| TAILQ_INSERT_HEAD(rt, r, next); |
| return rt; |
| } |
| |
| #ifdef IPV4_LOOPBACK_ROUTE |
| static struct rt_head * |
| add_loopback_route(struct rt_head *rt, const struct interface *ifp) |
| { |
| struct rt *r; |
| const struct dhcp_state *s; |
| |
| if (rt == NULL) /* earlier malloc failed */ |
| return NULL; |
| |
| s = D_CSTATE(ifp); |
| if (s->addr.s_addr == INADDR_ANY) |
| return rt; |
| |
| r = malloc(sizeof(*r)); |
| if (r == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| ipv4_freeroutes(rt); |
| return NULL; |
| } |
| r->dest.s_addr = s->addr.s_addr; |
| r->net.s_addr = INADDR_BROADCAST; |
| r->gate.s_addr = htonl(INADDR_LOOPBACK); |
| TAILQ_INSERT_HEAD(rt, r, next); |
| return rt; |
| } |
| #endif |
| |
| static struct rt_head * |
| get_routes(struct interface *ifp) |
| { |
| struct rt_head *nrt; |
| struct rt *rt, *r = NULL; |
| |
| if (ifp->options->routes && TAILQ_FIRST(ifp->options->routes)) { |
| nrt = malloc(sizeof(*nrt)); |
| TAILQ_INIT(nrt); |
| TAILQ_FOREACH(rt, ifp->options->routes, next) { |
| if (rt->gate.s_addr == 0) |
| break; |
| r = malloc(sizeof(*r)); |
| if (r == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| ipv4_freeroutes(nrt); |
| return NULL; |
| } |
| memcpy(r, rt, sizeof(*r)); |
| TAILQ_INSERT_TAIL(nrt, r, next); |
| } |
| return nrt; |
| } |
| |
| return get_option_routes(ifp, D_STATE(ifp)->new); |
| } |
| |
| /* Some DHCP servers add set host routes by setting the gateway |
| * to the assigned IP address or the destination address. |
| * We need to change this. */ |
| static struct rt_head * |
| massage_host_routes(struct rt_head *rt, const struct interface *ifp) |
| { |
| struct rt *r; |
| |
| if (rt) { |
| TAILQ_FOREACH(r, rt, next) { |
| if (r->gate.s_addr == D_CSTATE(ifp)->addr.s_addr || |
| r->gate.s_addr == r->dest.s_addr) |
| { |
| r->gate.s_addr = htonl(INADDR_ANY); |
| r->net.s_addr = htonl(INADDR_BROADCAST); |
| } |
| } |
| } |
| return rt; |
| } |
| |
| |
| static struct rt_head * |
| add_destination_route(struct rt_head *rt, const struct interface *ifp) |
| { |
| struct rt *r; |
| |
| if (rt == NULL || /* failed a malloc earlier probably */ |
| !(ifp->flags & IFF_POINTOPOINT) || |
| !has_option_mask(ifp->options->dstmask, DHO_ROUTER)) |
| return rt; |
| |
| r = malloc(sizeof(*r)); |
| if (r == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| ipv4_freeroutes(rt); |
| return NULL; |
| } |
| r->dest.s_addr = INADDR_ANY; |
| r->net.s_addr = INADDR_ANY; |
| r->gate.s_addr = D_CSTATE(ifp)->dst.s_addr; |
| TAILQ_INSERT_HEAD(rt, r, next); |
| return rt; |
| } |
| |
| /* We should check to ensure the routers are on the same subnet |
| * OR supply a host route. If not, warn and add a host route. */ |
| static struct rt_head * |
| add_router_host_route(struct rt_head *rt, const struct interface *ifp) |
| { |
| struct rt *rtp, *rtn; |
| const char *cp, *cp2, *cp3, *cplim; |
| struct if_options *ifo; |
| const struct dhcp_state *state; |
| |
| if (rt == NULL) /* earlier malloc failed */ |
| return NULL; |
| |
| TAILQ_FOREACH(rtp, rt, next) { |
| if (rtp->dest.s_addr != INADDR_ANY) |
| continue; |
| /* Scan for a route to match */ |
| TAILQ_FOREACH(rtn, rt, next) { |
| if (rtn == rtp) |
| break; |
| /* match host */ |
| if (rtn->dest.s_addr == rtp->gate.s_addr) |
| break; |
| /* match subnet */ |
| cp = (const char *)&rtp->gate.s_addr; |
| cp2 = (const char *)&rtn->dest.s_addr; |
| cp3 = (const char *)&rtn->net.s_addr; |
| cplim = cp3 + sizeof(rtn->net.s_addr); |
| while (cp3 < cplim) { |
| if ((*cp++ ^ *cp2++) & *cp3++) |
| break; |
| } |
| if (cp3 == cplim) |
| break; |
| } |
| if (rtn != rtp) |
| continue; |
| state = D_CSTATE(ifp); |
| ifo = ifp->options; |
| if (ifp->flags & IFF_NOARP) { |
| if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && |
| !(state->added & STATE_FAKE)) |
| { |
| ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; |
| logger(ifp->ctx, LOG_WARNING, |
| "%s: forcing router %s through interface", |
| ifp->name, inet_ntoa(rtp->gate)); |
| } |
| rtp->gate.s_addr = 0; |
| continue; |
| } |
| if (!(ifo->options & DHCPCD_ROUTER_HOST_ROUTE_WARNED) && |
| !(state->added & STATE_FAKE)) |
| { |
| ifo->options |= DHCPCD_ROUTER_HOST_ROUTE_WARNED; |
| logger(ifp->ctx, LOG_WARNING, |
| "%s: router %s requires a host route", |
| ifp->name, inet_ntoa(rtp->gate)); |
| } |
| rtn = malloc(sizeof(*rtn)); |
| if (rtn == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| ipv4_freeroutes(rt); |
| return NULL; |
| } |
| rtn->dest.s_addr = rtp->gate.s_addr; |
| rtn->net.s_addr = htonl(INADDR_BROADCAST); |
| rtn->gate.s_addr = htonl(INADDR_ANY); |
| TAILQ_INSERT_BEFORE(rtp, rtn, next); |
| } |
| return rt; |
| } |
| |
| void |
| ipv4_buildroutes(struct dhcpcd_ctx *ctx) |
| { |
| /* Do not modify route table when running in passive mode. */ |
| #ifndef PASSIVE_MODE |
| struct rt_head *nrs, *dnr; |
| struct rt *or, *rt, *rtn; |
| struct interface *ifp; |
| const struct dhcp_state *state; |
| |
| /* We need to have the interfaces in the correct order to ensure |
| * our routes are managed correctly. */ |
| if_sortinterfaces(ctx); |
| |
| nrs = malloc(sizeof(*nrs)); |
| if (nrs == NULL) { |
| logger(ctx, LOG_ERR, "%s: %m", __func__); |
| return; |
| } |
| TAILQ_INIT(nrs); |
| |
| TAILQ_FOREACH(ifp, ctx->ifaces, next) { |
| state = D_CSTATE(ifp); |
| if (state == NULL || state->new == NULL || !state->added) |
| continue; |
| dnr = get_routes(ifp); |
| dnr = massage_host_routes(dnr, ifp); |
| dnr = add_subnet_route(dnr, ifp); |
| #ifdef IPV4_LOOPBACK_ROUTE |
| dnr = add_loopback_route(dnr, ifp); |
| #endif |
| if (ifp->options->options & DHCPCD_GATEWAY) { |
| dnr = add_router_host_route(dnr, ifp); |
| dnr = add_destination_route(dnr, ifp); |
| } |
| if (dnr == NULL) /* failed to malloc all new routes */ |
| continue; |
| TAILQ_FOREACH_SAFE(rt, dnr, next, rtn) { |
| rt->iface = ifp; |
| #ifdef HAVE_ROUTE_METRIC |
| rt->metric = ifp->metric; |
| #endif |
| rt->flags = state->added & STATE_FAKE; |
| /* Is this route already in our table? */ |
| if ((find_route(nrs, rt, NULL)) != NULL) |
| continue; |
| rt->src.s_addr = state->addr.s_addr; |
| /* Do we already manage it? */ |
| if ((or = find_route(ctx->ipv4_routes, rt, NULL))) { |
| if (state->added & STATE_FAKE) |
| continue; |
| if (or->flags & STATE_FAKE || |
| or->iface != ifp || |
| #ifdef HAVE_ROUTE_METRIC |
| rt->metric != or->metric || |
| #endif |
| or->src.s_addr != state->addr.s_addr || |
| rt->gate.s_addr != or->gate.s_addr) |
| { |
| if (c_route(or, rt) != 0) |
| continue; |
| } |
| TAILQ_REMOVE(ctx->ipv4_routes, or, next); |
| free(or); |
| } else { |
| if (state->added & STATE_FAKE) { |
| or = ipv4_findrt(ctx, rt, 1); |
| if (or == NULL || |
| or->gate.s_addr != rt->gate.s_addr) |
| continue; |
| } else { |
| if (n_route(rt) != 0) |
| continue; |
| } |
| } |
| rt->flags |= STATE_ADDED; |
| TAILQ_REMOVE(dnr, rt, next); |
| TAILQ_INSERT_TAIL(nrs, rt, next); |
| } |
| ipv4_freeroutes(dnr); |
| } |
| |
| /* Remove old routes we used to manage */ |
| if (ctx->ipv4_routes) { |
| TAILQ_FOREACH(rt, ctx->ipv4_routes, next) { |
| if (find_route(nrs, rt, NULL) == NULL && |
| (rt->iface->options->options & |
| (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != |
| (DHCPCD_EXITING | DHCPCD_PERSISTENT)) |
| d_route(rt); |
| } |
| } |
| ipv4_freeroutes(ctx->ipv4_routes); |
| ctx->ipv4_routes = nrs; |
| #endif |
| } |
| |
| int |
| ipv4_deladdr(struct interface *ifp, |
| const struct in_addr *addr, const struct in_addr *net) |
| { |
| int r = 0; |
| #ifndef PASSIVE_MODE |
| struct dhcp_state *dstate; |
| struct ipv4_state *state; |
| struct ipv4_addr *ap; |
| |
| logger(ifp->ctx, LOG_DEBUG, "%s: deleting IP address %s/%d", |
| ifp->name, inet_ntoa(*addr), inet_ntocidr(*net)); |
| |
| r = if_deladdress(ifp, addr, net); |
| if (r == -1 && errno != EADDRNOTAVAIL && errno != ENXIO && |
| errno != ENODEV) |
| logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); |
| |
| dstate = D_STATE(ifp); |
| if (dstate->addr.s_addr == addr->s_addr && |
| dstate->net.s_addr == net->s_addr) |
| { |
| dstate->added = 0; |
| dstate->addr.s_addr = 0; |
| dstate->net.s_addr = 0; |
| } |
| |
| state = IPV4_STATE(ifp); |
| TAILQ_FOREACH(ap, &state->addrs, next) { |
| if (ap->addr.s_addr == addr->s_addr && |
| ap->net.s_addr == net->s_addr) |
| { |
| TAILQ_REMOVE(&state->addrs, ap, next); |
| free(ap); |
| break; |
| } |
| } |
| #endif |
| return r; |
| } |
| |
| static int |
| delete_address(struct interface *ifp) |
| { |
| int r; |
| struct if_options *ifo; |
| struct dhcp_state *state; |
| |
| state = D_STATE(ifp); |
| ifo = ifp->options; |
| if (ifo->options & DHCPCD_INFORM || |
| (ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0)) |
| return 0; |
| r = ipv4_deladdr(ifp, &state->addr, &state->net); |
| return r; |
| } |
| |
| static struct ipv4_state * |
| ipv4_getstate(struct interface *ifp) |
| { |
| struct ipv4_state *state; |
| |
| state = IPV4_STATE(ifp); |
| if (state == NULL) { |
| ifp->if_data[IF_DATA_IPV4] = malloc(sizeof(*state)); |
| state = IPV4_STATE(ifp); |
| if (state == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| return NULL; |
| } |
| TAILQ_INIT(&state->addrs); |
| TAILQ_INIT(&state->routes); |
| } |
| return state; |
| } |
| |
| static int |
| ipv4_addaddr(struct interface *ifp, const struct dhcp_lease *lease) |
| { |
| struct ipv4_state *state; |
| struct ipv4_addr *ia; |
| |
| if ((state = ipv4_getstate(ifp)) == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: ipv4_getstate: %m", __func__); |
| return -1; |
| } |
| if (ifp->options->options & DHCPCD_NOALIAS) { |
| struct ipv4_addr *ian; |
| |
| TAILQ_FOREACH_SAFE(ia, &state->addrs, next, ian) { |
| if (ia->addr.s_addr != lease->addr.s_addr) |
| ipv4_deladdr(ifp, &ia->addr, &ia->net); |
| } |
| } |
| |
| if ((ia = malloc(sizeof(*ia))) == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| return -1; |
| } |
| |
| logger(ifp->ctx, LOG_DEBUG, "%s: adding IP address %s/%d", |
| ifp->name, inet_ntoa(lease->addr), |
| inet_ntocidr(lease->net)); |
| if (if_addaddress(ifp, &lease->addr, &lease->net, &lease->brd) == -1) { |
| if (errno != EEXIST) |
| logger(ifp->ctx, LOG_ERR, "%s: if_addaddress: %m", |
| __func__); |
| free(ia); |
| return -1; |
| } |
| |
| ia->iface = ifp; |
| ia->addr = lease->addr; |
| ia->net = lease->net; |
| #ifdef IN_IFF_TENTATIVE |
| ia->addr_flags = IN_IFF_TENTATIVE; |
| #endif |
| TAILQ_INSERT_TAIL(&state->addrs, ia, next); |
| return 0; |
| } |
| |
| static void |
| ipv4_finalisert(struct interface *ifp) |
| { |
| const struct dhcp_state *state = D_CSTATE(ifp); |
| |
| /* Find any freshly added routes, such as the subnet route. |
| * We do this because we cannot rely on recieving the kernel |
| * notification right now via our link socket. */ |
| if_initrt(ifp); |
| ipv4_buildroutes(ifp->ctx); |
| script_runreason(ifp, state->reason); |
| |
| dhcpcd_daemonise(ifp->ctx); |
| } |
| |
| void |
| ipv4_finaliseaddr(struct interface *ifp) |
| { |
| #ifndef PASSIVE_MODE |
| struct dhcp_state *state = D_STATE(ifp); |
| struct dhcp_lease *lease; |
| |
| lease = &state->lease; |
| |
| /* Delete the old address if different */ |
| if (state->addr.s_addr != lease->addr.s_addr && |
| state->addr.s_addr != 0 && |
| ipv4_iffindaddr(ifp, &lease->addr, NULL)) |
| delete_address(ifp); |
| |
| state->added = STATE_ADDED; |
| state->defend = 0; |
| state->addr.s_addr = lease->addr.s_addr; |
| state->net.s_addr = lease->net.s_addr; |
| ipv4_finalisert(ifp); |
| #endif |
| } |
| |
| void |
| ipv4_applyaddr(void *arg) |
| { |
| #ifdef PASSIVE_MODE |
| rpc_update_ipv4(arg); |
| #else |
| struct interface *ifp = arg, *ifn; |
| struct dhcp_state *state = D_STATE(ifp), *nstate; |
| struct dhcp_message *dhcp; |
| struct dhcp_lease *lease; |
| struct if_options *ifo = ifp->options; |
| struct ipv4_addr *ap; |
| int r; |
| |
| if (state == NULL) |
| return; |
| dhcp = state->new; |
| lease = &state->lease; |
| |
| if (dhcp == NULL) { |
| if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != |
| (DHCPCD_EXITING | DHCPCD_PERSISTENT)) |
| { |
| if (state->added) { |
| struct in_addr addr; |
| |
| addr = state->addr; |
| delete_address(ifp); |
| TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { |
| if (ifn == ifp || |
| strcmp(ifn->name, ifp->name) == 0) |
| continue; |
| nstate = D_STATE(ifn); |
| if (nstate && !nstate->added && |
| nstate->addr.s_addr == addr.s_addr) |
| { |
| if (ifn->options->options & |
| DHCPCD_ARP) |
| { |
| dhcp_bind(ifn, NULL); |
| } else { |
| ipv4_addaddr(ifn, |
| &nstate->lease); |
| nstate->added = |
| STATE_ADDED; |
| } |
| break; |
| } |
| } |
| } |
| ipv4_buildroutes(ifp->ctx); |
| script_runreason(ifp, state->reason); |
| } else |
| ipv4_buildroutes(ifp->ctx); |
| return; |
| } |
| |
| /* Ensure only one interface has the address */ |
| TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { |
| if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0) |
| continue; |
| nstate = D_STATE(ifn); |
| if (nstate && nstate->added && |
| nstate->addr.s_addr == lease->addr.s_addr) |
| { |
| if (ifn->metric <= ifp->metric) { |
| logger(ifp->ctx, LOG_INFO, |
| "%s: preferring %s on %s", |
| ifp->name, |
| inet_ntoa(lease->addr), |
| ifn->name); |
| state->addr.s_addr = lease->addr.s_addr; |
| state->net.s_addr = lease->net.s_addr; |
| ipv4_finalisert(ifp); |
| } |
| logger(ifp->ctx, LOG_INFO, "%s: preferring %s on %s", |
| ifn->name, |
| inet_ntoa(lease->addr), |
| ifp->name); |
| ipv4_deladdr(ifn, &nstate->addr, &nstate->net); |
| nstate->added = 0; |
| break; |
| } |
| } |
| |
| /* Does another interface already have the address from a prior boot? */ |
| if (ifn == NULL) { |
| TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { |
| if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0) |
| continue; |
| ap = ipv4_iffindaddr(ifn, &lease->addr, NULL); |
| if (ap) |
| ipv4_deladdr(ifn, &ap->addr, &ap->net); |
| } |
| } |
| |
| /* If the netmask is different, delete the addresss */ |
| ap = ipv4_iffindaddr(ifp, &lease->addr, NULL); |
| if (ap && ap->net.s_addr != lease->net.s_addr) |
| ipv4_deladdr(ifp, &ap->addr, &ap->net); |
| |
| if (ipv4_iffindaddr(ifp, &lease->addr, &lease->net)) |
| logger(ifp->ctx, LOG_DEBUG, |
| "%s: IP address %s/%d already exists", |
| ifp->name, inet_ntoa(lease->addr), |
| inet_ntocidr(lease->net)); |
| else { |
| r = ipv4_addaddr(ifp, lease); |
| if (r == -1 && errno != EEXIST) |
| return; |
| } |
| |
| #ifdef IN_IFF_NOTUSEABLE |
| ap = ipv4_iffindaddr(ifp, &lease->addr, NULL); |
| if (ap == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: added address vanished", |
| ifp->name); |
| return; |
| } else if (ap->addr_flags & IN_IFF_NOTUSEABLE) |
| return; |
| #endif |
| |
| ipv4_finaliseaddr(ifp); |
| |
| #endif /* PASSIVE_MODE */ |
| } |
| |
| void |
| ipv4_handleifa(struct dhcpcd_ctx *ctx, |
| int cmd, struct if_head *ifs, const char *ifname, |
| const struct in_addr *addr, const struct in_addr *net, |
| const struct in_addr *dst, int flags) |
| { |
| struct interface *ifp; |
| struct ipv4_state *state; |
| struct ipv4_addr *ap; |
| |
| if (ifs == NULL) |
| ifs = ctx->ifaces; |
| if (ifs == NULL) { |
| errno = ESRCH; |
| return; |
| } |
| if (addr->s_addr == INADDR_ANY) { |
| errno = EINVAL; |
| return; |
| } |
| if ((ifp = if_find(ifs, ifname)) == NULL) |
| return; |
| if ((state = ipv4_getstate(ifp)) == NULL) { |
| errno = ENOENT; |
| return; |
| } |
| |
| ap = ipv4_iffindaddr(ifp, addr, net); |
| if (cmd == RTM_NEWADDR) { |
| if (ap == NULL) { |
| if ((ap = malloc(sizeof(*ap))) == NULL) { |
| logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); |
| return; |
| } |
| ap->iface = ifp; |
| ap->addr = *addr; |
| ap->net = *net; |
| if (dst) |
| ap->dst.s_addr = dst->s_addr; |
| else |
| ap->dst.s_addr = INADDR_ANY; |
| TAILQ_INSERT_TAIL(&state->addrs, ap, next); |
| } |
| ap->addr_flags = flags; |
| } else if (cmd == RTM_DELADDR) { |
| if (ap) { |
| TAILQ_REMOVE(&state->addrs, ap, next); |
| free(ap); |
| } |
| } |
| |
| dhcp_handleifa(cmd, ifp, addr, net, dst, flags); |
| arp_handleifa(cmd, ifp, addr, flags); |
| } |
| |
| void |
| ipv4_free(struct interface *ifp) |
| { |
| struct ipv4_state *state; |
| struct ipv4_addr *addr; |
| |
| if (ifp) { |
| state = IPV4_STATE(ifp); |
| if (state) { |
| while ((addr = TAILQ_FIRST(&state->addrs))) { |
| TAILQ_REMOVE(&state->addrs, addr, next); |
| free(addr); |
| } |
| ipv4_freerts(&state->routes); |
| free(state); |
| } |
| } |
| } |
| |
| void |
| ipv4_ctxfree(struct dhcpcd_ctx *ctx) |
| { |
| |
| ipv4_freeroutes(ctx->ipv4_routes); |
| ipv4_freeroutes(ctx->ipv4_kroutes); |
| } |