]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Improve IN_IFF_TENTATIVE with ip sharing.
authorRoy Marples <roy@marples.name>
Sat, 16 May 2015 20:42:05 +0000 (20:42 +0000)
committerRoy Marples <roy@marples.name>
Sat, 16 May 2015 20:42:05 +0000 (20:42 +0000)
arp.c
arp.h
dhcp.c
dhcpcd.c
ipv4.c
ipv4.h

diff --git a/arp.c b/arp.c
index d49ee1af68bf31dacd39bfe72c19a9d983ad8177..c53e4c7b0e70fad77a77e6a44209450bbc3c5cee 100644 (file)
--- a/arp.c
+++ b/arp.c
@@ -294,17 +294,19 @@ arp_probe(struct arp_state *astate)
        arp_probe1(astate);
 }
 
-static struct arp_state *
+struct arp_state *
 arp_find(struct interface *ifp, const struct in_addr *addr)
 {
        struct arp_state *astate;
        struct dhcp_state *state;
 
-       state = D_STATE(ifp);
+       if ((state = D_STATE(ifp)) == NULL)
+               goto out;
        TAILQ_FOREACH(astate, &state->arp_states, next) {
                if (astate->addr.s_addr == addr->s_addr && astate->iface == ifp)
                        return astate;
        }
+out:
        errno = ESRCH;
        return NULL;
 }
diff --git a/arp.h b/arp.h
index 83422fe1cfb997a2e119a3f8a79e98bd7c29dc84..5e513b29dee22e4c7511646bd6f67f92bdbc31b0 100644 (file)
--- a/arp.h
+++ b/arp.h
@@ -73,6 +73,7 @@ struct arp_state *arp_new(struct interface *, const struct in_addr *);
 void arp_cancel(struct arp_state *);
 void arp_free(struct arp_state *);
 void arp_free_but(struct arp_state *);
+struct arp_state *arp_find(struct interface *, const struct in_addr *);
 void arp_close(struct interface *);
 
 void arp_handleifa(int, struct interface *, const struct in_addr *, int);
diff --git a/dhcp.c b/dhcp.c
index 9280eb37744f1903bbe082b988262b3d47cee558..afcef4fb41239fa84c442382d502e014421a33ed 100644 (file)
--- a/dhcp.c
+++ b/dhcp.c
@@ -1866,6 +1866,104 @@ dhcp_rebind(void *arg)
        send_rebind(ifp);
 }
 
+static void
+dhcp_arp_probed(struct arp_state *astate)
+{
+       struct dhcp_state *state;
+       struct if_options *ifo;
+
+       /* We didn't find a profile for this
+        * address or hwaddr, so move to the next
+        * arping profile */
+       state = D_STATE(astate->iface);
+       ifo = astate->iface->options;
+       if (state->arping_index < ifo->arping_len) {
+               if (++state->arping_index < ifo->arping_len) {
+                       astate->addr.s_addr =
+                           ifo->arping[state->arping_index - 1];
+                       arp_probe(astate);
+               }
+               dhcpcd_startinterface(astate->iface);
+               return;
+       }
+       dhcp_close(astate->iface);
+       eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface);
+#ifdef IN_IFF_TENTATIVE
+       ipv4_finaliseaddr(astate->iface);
+       arp_close(astate->iface);
+#else
+       dhcp_bind(astate->iface, astate);
+#endif
+}
+
+static void
+dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
+{
+       struct dhcp_state *state;
+       struct if_options *ifo;
+
+       state = D_STATE(astate->iface);
+       ifo = astate->iface->options;
+       if (state->arping_index &&
+           state->arping_index <= ifo->arping_len &&
+           amsg &&
+           (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] ||
+           (amsg->sip.s_addr == 0 &&
+           amsg->tip.s_addr == ifo->arping[state->arping_index - 1])))
+       {
+               char buf[HWADDR_LEN * 3];
+
+               astate->failed.s_addr = ifo->arping[state->arping_index - 1];
+               arp_report_conflicted(astate, amsg);
+               hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf));
+               if (dhcpcd_selectprofile(astate->iface, buf) == -1 &&
+                   dhcpcd_selectprofile(astate->iface,
+                       inet_ntoa(astate->failed)) == -1)
+               {
+                       /* We didn't find a profile for this
+                        * address or hwaddr, so move to the next
+                        * arping profile */
+                       dhcp_arp_probed(astate);
+                       return;
+               }
+               dhcp_close(astate->iface);
+               arp_close(astate->iface);
+               eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
+                   astate->iface);
+               dhcpcd_startinterface(astate->iface);
+       }
+
+       /* RFC 2131 3.1.5, Client-server interaction
+        * NULL amsg means IN_IFF_DUPLICATED */
+       if (amsg == NULL || (state->offer &&
+           (amsg->sip.s_addr == state->offer->yiaddr ||
+           (amsg->sip.s_addr == 0 &&
+           amsg->tip.s_addr == state->offer->yiaddr))))
+       {
+#ifdef IN_IFF_DUPLICATED
+               struct ipv4_addr *ia;
+#endif
+
+               if (amsg)
+                       astate->failed.s_addr = state->offer->yiaddr;
+               else
+                       astate->failed = astate->addr;
+               arp_report_conflicted(astate, amsg);
+               unlink(state->leasefile);
+               if (!state->lease.frominfo)
+                       dhcp_decline(astate->iface);
+#ifdef IN_IFF_DUPLICATED
+               ia = ipv4_iffindaddr(astate->iface, &astate->addr, NULL);
+               if (ia)
+                       ipv4_deladdr(astate->iface, &ia->addr, &ia->net);
+#endif
+               eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
+                   astate->iface);
+               eloop_timeout_add_sec(astate->iface->ctx->eloop,
+                   DHCP_RAND_MAX, dhcp_discover, astate->iface);
+       }
+}
+
 void
 dhcp_bind(struct interface *ifp, struct arp_state *astate)
 {
@@ -1876,6 +1974,9 @@ dhcp_bind(struct interface *ifp, struct arp_state *astate)
 
        if (state->state == DHS_BOUND)
                goto applyaddr;
+#ifdef IN_IFF_TENTATIVE
+       state->added |= STATE_TENTATIVE;
+#endif
        state->reason = NULL;
        free(state->old);
        state->old = state->new;
@@ -1993,6 +2094,16 @@ dhcp_bind(struct interface *ifp, struct arp_state *astate)
                            "%s: write_lease: %m", __func__);
 
 applyaddr:
+#ifdef IN_IFF_TENTATIVE
+       if (astate == NULL) {
+               astate = arp_new(ifp, &lease->addr);
+               if (astate) {
+                       astate->probed_cb = dhcp_arp_probed;
+                       astate->conflicted_cb = dhcp_arp_conflicted;
+               }
+       }
+#endif
+
        ipv4_applyaddr(ifp);
        if (ifo->options & DHCPCD_ARP &&
            !(ifp->ctx->options & DHCPCD_FORKED))
@@ -2348,104 +2459,6 @@ whitelisted_ip(const struct if_options *ifo, in_addr_t addr)
        return 0;
 }
 
-static void
-dhcp_arp_probed(struct arp_state *astate)
-{
-       struct dhcp_state *state;
-       struct if_options *ifo;
-
-       /* We didn't find a profile for this
-        * address or hwaddr, so move to the next
-        * arping profile */
-       state = D_STATE(astate->iface);
-       ifo = astate->iface->options;
-       if (state->arping_index < ifo->arping_len) {
-               if (++state->arping_index < ifo->arping_len) {
-                       astate->addr.s_addr =
-                           ifo->arping[state->arping_index - 1];
-                       arp_probe(astate);
-               }
-               dhcpcd_startinterface(astate->iface);
-               return;
-       }
-       dhcp_close(astate->iface);
-       eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate->iface);
-#ifdef IN_IFF_TENTATIVE
-       ipv4_finaliseaddr(astate->iface);
-       arp_close(astate->iface);
-#else
-       dhcp_bind(astate->iface, astate);
-#endif
-}
-
-static void
-dhcp_arp_conflicted(struct arp_state *astate, const struct arp_msg *amsg)
-{
-       struct dhcp_state *state;
-       struct if_options *ifo;
-
-       state = D_STATE(astate->iface);
-       ifo = astate->iface->options;
-       if (state->arping_index &&
-           state->arping_index <= ifo->arping_len &&
-           amsg &&
-           (amsg->sip.s_addr == ifo->arping[state->arping_index - 1] ||
-           (amsg->sip.s_addr == 0 &&
-           amsg->tip.s_addr == ifo->arping[state->arping_index - 1])))
-       {
-               char buf[HWADDR_LEN * 3];
-
-               astate->failed.s_addr = ifo->arping[state->arping_index - 1];
-               arp_report_conflicted(astate, amsg);
-               hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf));
-               if (dhcpcd_selectprofile(astate->iface, buf) == -1 &&
-                   dhcpcd_selectprofile(astate->iface,
-                       inet_ntoa(astate->failed)) == -1)
-               {
-                       /* We didn't find a profile for this
-                        * address or hwaddr, so move to the next
-                        * arping profile */
-                       dhcp_arp_probed(astate);
-                       return;
-               }
-               dhcp_close(astate->iface);
-               arp_close(astate->iface);
-               eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
-                   astate->iface);
-               dhcpcd_startinterface(astate->iface);
-       }
-
-       /* RFC 2131 3.1.5, Client-server interaction
-        * NULL amsg means IN_IFF_DUPLICATED */
-       if (amsg == NULL || (state->offer &&
-           (amsg->sip.s_addr == state->offer->yiaddr ||
-           (amsg->sip.s_addr == 0 &&
-           amsg->tip.s_addr == state->offer->yiaddr))))
-       {
-#ifdef IN_IFF_DUPLICATED
-               struct ipv4_addr *ia;
-#endif
-
-               if (amsg)
-                       astate->failed.s_addr = state->offer->yiaddr;
-               else
-                       astate->failed = astate->addr;
-               arp_report_conflicted(astate, amsg);
-               unlink(state->leasefile);
-               if (!state->lease.frominfo)
-                       dhcp_decline(astate->iface);
-#ifdef IN_IFF_DUPLICATED
-               ia = ipv4_iffindaddr(astate->iface, &astate->addr, NULL);
-               if (ia)
-                       ipv4_deladdr(astate->iface, &ia->addr, &ia->net);
-#endif
-               eloop_timeout_delete(astate->iface->ctx->eloop, NULL,
-                   astate->iface);
-               eloop_timeout_add_sec(astate->iface->ctx->eloop,
-                   DHCP_RAND_MAX, dhcp_discover, astate->iface);
-       }
-}
-
 static void
 dhcp_handledhcp(struct interface *ifp, struct dhcp_message **dhcpp,
     const struct in_addr *from)
@@ -2763,18 +2776,7 @@ dhcp_handledhcp(struct interface *ifp, struct dhcp_message **dhcpp,
        eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
        astate = NULL;
 
-#ifdef IN_IFF_TENTATIVE
-       addr.s_addr = state->offer->yiaddr;
-       astate = arp_new(ifp, &addr);
-       if (astate) {
-               astate->probed_cb = dhcp_arp_probed;
-               astate->conflicted_cb = dhcp_arp_conflicted;
-               /* No need to start the probe as we'll
-                * listen to the kernel stating DAD or not and
-                * that action look look for our ARP state  for
-                * what to do. */
-       }
-#else
+#ifndef IN_IFF_TENTATIVE
        if (ifo->options & DHCPCD_ARP
            && state->addr.s_addr != state->offer->yiaddr)
        {
index 8403626440d39a692f765065326603183712d470..cb07e88dcb595469398398ab1ccbce94262e6f6f 100644 (file)
--- a/dhcpcd.c
+++ b/dhcpcd.c
@@ -669,7 +669,8 @@ dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags,
                        script_runreason(ifp, "NOCARRIER");
 #ifdef NOCARRIER_PRESERVE_IP
                        arp_close(ifp);
-                       ipv4_buildroutes(ifp->ctx);
+                       if_sortinterfaces(ctx);
+                       ipv4_preferanother(ifp);
                        ipv6nd_expire(ifp, 0);
 #else
                        dhcpcd_drop(ifp, 0);
diff --git a/ipv4.c b/ipv4.c
index f73e4097d2abc8f96e497224c2de952d932c5b78..9fba88a74a3d8cefd239b30f1f683eae1736fdab 100644 (file)
--- a/ipv4.c
+++ b/ipv4.c
@@ -750,6 +750,7 @@ ipv4_deladdr(struct interface *ifp,
        int r;
        struct ipv4_state *state;
        struct ipv4_addr *ap;
+       struct arp_state *astate;
 
        logger(ifp->ctx, LOG_DEBUG, "%s: deleting IP address %s/%d",
            ifp->name, inet_ntoa(*addr), inet_ntocidr(*net));
@@ -759,15 +760,8 @@ ipv4_deladdr(struct interface *ifp,
            errno != ENODEV)
                logger(ifp->ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__);
 
-       dstate = D_STATE(ifp);
-       if (dstate &&
-           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;
-       }
+       if ((astate = arp_find(ifp, addr)) != NULL)
+               arp_free(astate);
 
        state = IPV4_STATE(ifp);
        TAILQ_FOREACH(ap, &state->addrs, next) {
@@ -779,6 +773,18 @@ ipv4_deladdr(struct interface *ifp,
                        break;
                }
        }
+
+       /* Have to do this last incase the function arguments
+        * were these very pointers. */
+       dstate = D_STATE(ifp);
+       if (dstate &&
+           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;
+       }
        return r;
 }
 
@@ -822,6 +828,7 @@ ipv4_addaddr(struct interface *ifp, const struct dhcp_lease *lease)
 {
        struct ipv4_state *state;
        struct ipv4_addr *ia;
+       struct dhcp_state *dstate;
 
        if ((state = ipv4_getstate(ifp)) == NULL) {
                logger(ifp->ctx, LOG_ERR, "%s: ipv4_getstate: %m", __func__);
@@ -859,6 +866,17 @@ ipv4_addaddr(struct interface *ifp, const struct dhcp_lease *lease)
        ia->addr_flags = IN_IFF_TENTATIVE;
 #endif
        TAILQ_INSERT_TAIL(&state->addrs, ia, next);
+
+       dstate = D_STATE(ifp);
+#ifdef IN_IFF_TENTATIVE
+       dstate->added |= STATE_ADDED;
+#else
+       dstate->added = STATE_ADDED;
+#endif
+       dstate->defend = 0;
+       dstate->addr.s_addr = lease->addr.s_addr;
+       dstate->net.s_addr = lease->net.s_addr;
+
        return 0;
 }
 
@@ -891,13 +909,45 @@ ipv4_finaliseaddr(struct interface *ifp)
            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;
+       state->added &= ~(STATE_FAKE | STATE_TENTATIVE);
        ipv4_finalisert(ifp);
 }
 
+int
+ipv4_preferanother(struct interface *ifp)
+{
+       struct dhcp_state *state = D_STATE(ifp), *nstate;
+       struct interface *ifn;
+       int preferred;
+
+       preferred = 0;
+       if (!state->added)
+               goto out;
+
+       TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
+               if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0)
+                       break; /* We are already the most preferred */
+               nstate = D_STATE(ifn);
+               if (nstate && !nstate->added &&
+                   nstate->lease.addr.s_addr == state->addr.s_addr)
+               {
+                       preferred = 1;
+                       delete_address(ifp);
+                       if (ifn->options->options & DHCPCD_ARP)
+                               dhcp_bind(ifn, NULL);
+                       else {
+                               ipv4_addaddr(ifn, &nstate->lease);
+                               nstate->added = STATE_ADDED;
+                       }
+                       break;
+               }
+       }
+
+out:
+       ipv4_buildroutes(ifp->ctx);
+       return preferred;
+}
+
 void
 ipv4_applyaddr(void *arg)
 {
@@ -914,38 +964,15 @@ ipv4_applyaddr(void *arg)
        dhcp = state->new;
        lease = &state->lease;
 
+       if_sortinterfaces(ifp->ctx);
        if (dhcp == NULL) {
                if ((ifo->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
                    (DHCPCD_EXITING | DHCPCD_PERSISTENT))
                {
-                       if (state->added) {
-                               struct in_addr addr;
-
-                               addr = state->addr;
+                       if (state->added && !ipv4_preferanother(ifp)) {
                                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);
                        }
-                       ipv4_buildroutes(ifp->ctx);
                        script_runreason(ifp, state->reason);
                } else
                        ipv4_buildroutes(ifp->ctx);
@@ -953,29 +980,30 @@ ipv4_applyaddr(void *arg)
        }
 
        /* Ensure only one interface has the address */
+       r = 0;
        TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) {
-               if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0)
+               if (ifn == ifp || strcmp(ifn->name, ifp->name) == 0) {
+                       r = 1; /* past ourselves */
                        continue;
+               }
                nstate = D_STATE(ifn);
                if (nstate && nstate->added &&
                    nstate->addr.s_addr == lease->addr.s_addr)
                {
-                       if (ifn->metric <= ifp->metric) {
+                       if (r == 0) {
                                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);
+                               state->added &= ~STATE_TENTATIVE;
+                               return;
                        }
                        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;
                }
        }
diff --git a/ipv4.h b/ipv4.h
index 08b1c213b36fed20c672ae0e1191de03830b5d47..76c32c660675a479293eac648ef661631a6054dd 100644 (file)
--- a/ipv4.h
+++ b/ipv4.h
@@ -80,11 +80,13 @@ int ipv4_addrexists(struct dhcpcd_ctx *, const struct in_addr *);
 
 #define STATE_ADDED            0x01
 #define STATE_FAKE             0x02
+#define STATE_TENTATIVE                0x04
 
 void ipv4_buildroutes(struct dhcpcd_ctx *);
 void ipv4_finaliseaddr(struct interface *);
 int ipv4_deladdr(struct interface *ifp, const struct in_addr *,
     const struct in_addr *);
+int ipv4_preferanother(struct interface *ifp);
 void ipv4_applyaddr(void *);
 int ipv4_handlert(struct dhcpcd_ctx *, int, struct rt *);
 void ipv4_freerts(struct rt_head *);