summaryrefslogtreecommitdiffstats
path: root/src/dhcpcd.c
diff options
context:
space:
mode:
authorRoy Marples <roy@marples.name>2018-03-19 15:39:05 +0000
committerRoy Marples <roy@marples.name>2018-03-19 15:39:05 +0000
commit1437db1d25f09c5235ed792bd278ee3f0f17dd65 (patch)
treedecb2c14336f00dff8f505e394e099ec8a2c40ba /src/dhcpcd.c
parent81edfe77f1ab8c5e9d5761fda73506d2a28710fb (diff)
downloaddhcpcd-1437db1d25f09c5235ed792bd278ee3f0f17dd65.tar.xz
link: detect buffer overflow / desync and relearn interface state
It's possible for the internal kernel buffer that reports network events to overflow. On Linux and NetBSD* this is handled by ENOBUFS being returned by recv(2). On OpenBSD there is a special route(4) message RTM_DESYNC. All other OS's don't seem to report this error, so dhcpcd cannot detect it. * I will commit a patch to NetBSD soon for this and will request a pullup to NetBSD-8.
Diffstat (limited to 'src/dhcpcd.c')
-rw-r--r--src/dhcpcd.c91
1 files changed, 77 insertions, 14 deletions
diff --git a/src/dhcpcd.c b/src/dhcpcd.c
index 8b7da6ca..5b345d5f 100644
--- a/src/dhcpcd.c
+++ b/src/dhcpcd.c
@@ -955,20 +955,6 @@ dhcpcd_activateinterface(struct interface *ifp, unsigned long long options)
}
}
-static void
-dhcpcd_handlelink(void *arg)
-{
- struct dhcpcd_ctx *ctx;
-
- ctx = arg;
- if (if_handlelink(ctx) == -1) {
- logerr(__func__);
- eloop_event_delete(ctx->eloop, ctx->link_fd);
- close(ctx->link_fd);
- ctx->link_fd = -1;
- }
-}
-
int
dhcpcd_handleinterface(void *arg, int action, const char *ifname)
{
@@ -1041,6 +1027,83 @@ dhcpcd_handleinterface(void *arg, int action, const char *ifname)
return 1;
}
+static void
+dhcpcd_handlelink(void *arg)
+{
+ struct dhcpcd_ctx *ctx = arg;
+
+ if (if_handlelink(ctx) == -1) {
+ if (errno == ENOBUFS) {
+ dhcpcd_linkoverflow(ctx);
+ return;
+ }
+ logerr(__func__);
+ }
+}
+
+static void
+dhcpcd_checkcarrier(void *arg)
+{
+ struct interface *ifp = arg;
+
+ dhcpcd_handlecarrier(ifp->ctx, LINK_UNKNOWN, ifp->flags, ifp->name);
+}
+
+void
+dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx)
+{
+ struct if_head *ifaces;
+ struct ifaddrs *ifaddrs;
+ struct interface *ifp, *ifn, *ifp1;
+
+ loginfox("route socket overflowed - learning interface state");
+
+ /* Close the existing socket and open a new one.
+ * This is easier than draining the kernel buffer of an
+ * in-determinate size. */
+ eloop_event_delete(ctx->eloop, ctx->link_fd);
+ close(ctx->link_fd);
+ if_closesockets_os(ctx);
+ if (if_opensockets_os(ctx) == -1) {
+ logerr("%s: if_opensockets", __func__);
+ eloop_exit(ctx->eloop, EXIT_FAILURE);
+ return;
+ }
+ eloop_event_add(ctx->eloop, ctx->link_fd, dhcpcd_handlelink, ctx);
+
+ /* Work out the current interfaces. */
+ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv);
+
+ /* Punt departed interfaces */
+ TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) {
+ if (if_find(ifaces, ifp->name) != NULL)
+ continue;
+ dhcpcd_handleinterface(ctx, -1, ifp->name);
+ }
+
+ /* Add new interfaces */
+ TAILQ_FOREACH_SAFE(ifp, ifaces, next, ifn) {
+ ifp1 = if_find(ctx->ifaces, ifp->name);
+ if (ifp1 != NULL) {
+ /* If the interface already exists,
+ * check carrier state. */
+ eloop_timeout_add_sec(ctx->eloop, 0,
+ dhcpcd_checkcarrier, ifp1);
+ continue;
+ }
+ TAILQ_REMOVE(ifaces, ifp, next);
+ TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next);
+ if (ifp->active)
+ eloop_timeout_add_sec(ctx->eloop, 0,
+ dhcpcd_prestartinterface, ifp);
+ }
+
+ /* Update address state. */
+ if_markaddrsstale(ctx->ifaces);
+ if_learnaddrs(ctx, ctx->ifaces, &ifaddrs);
+ if_deletestaleaddrs(ctx->ifaces);
+}
+
void
dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname,
const void *hwaddr, uint8_t hwlen)