#771696 dibbler-client: fails to register DNS server and search list properly with resolvconf

Package:
dibbler-client
Source:
dibbler
Description:
portable DHCPv6 client
Submitter:
"Fabian Pietsch \(Debian\)"
Date:
2014-12-17 23:21:05 UTC
Severity:
normal
#771696#5
Date:
2014-12-01 18:17:38 UTC
From:
To:
Dear Maintainer,

dibbler-client produces an invalid /run/resolvconf/interface/eth0 file.

  root@vm-initia:~# cat /run/resolvconf/interface/eth0
  search canvon.de

This is missing information. In the client log, everything looks fine,
though:

  root@vm-initia:~# tail -3 /var/log/dibbler/dibbler-client.log
  2014.12.01 18:29:17 Client Notice    Setting up DNS server fd94:97cc:7e28:2::15 on interface eth0/2.
  2014.12.01 18:29:17 Client Notice    Setting up Domain nebel.canvon.de on interface eth0/2.
  2014.12.01 18:29:17 Client Notice    Setting up Domain canvon.de on interface eth0/2.

Regards,
Fabian Pietsch

#771696#10
Date:
2014-12-03 23:44:59 UTC
From:
To:
Hello,

the cause seems to be that "resolvconf -a $IFACE" is called multiple
times, once for each bit of resolver information, so only the last added
information survives.

The solution would be to gather the resolver data in dns_add() and
domain_add(), and then call resolvconf -a iface once, with all
information.

BTW, it seems that other information providers set $IFACE.inet,
so the correct invocation would seem to be "resolvconf -a $IFACE.inet6".
This way IPv6 nameservers would be ordered after IPv4 nameservers
in the final resulting /etc/resolv.conf file, though.

Regards,
Fabian

#771696#15
Date:
2014-12-04 01:52:29 UTC
From:
To:
Hello,

I just had a look at dibbler git (which seems to be at the moment almost
dibbler 1.0.0), and the gathering stuff is already there: The
/etc/resolv.conf editing code. Dibbler would just need to keep a
separate resolv.conf (initially empty), perhaps in
/var/lib/dibbler/resolv.conf, and then after each change feed that
to "resolvconf -a $IFACE.inet6".

Regards,
Fabian

#771696#20
Date:
2014-12-05 01:30:35 UTC
From:
To:
Hello,

I've created a patch against dibbler git which should allow
proper resolvconf operation. A private resolv.conf.IFACE file
is kept in /var/lib/dibbler, modified again and again, and
then fed in whole to resolvconf -a IFACE.inet6.

Before patch:

| root@silencer:~# cat /etc/resolvconf/run/interface/lan
| search nebel.canvon.de

After patch:

| root@silencer:~# cat /etc/resolvconf/run/interface/lan
| cat: /etc/resolvconf/run/interface/lan: No such file or directory
| root@silencer:~# cat /etc/resolvconf/run/interface/lan.inet6
|
| nameserver fd94:97cc:7e28:1::15
| search nebel.canvon.de canvon.de

Missing is resolvconf -d. This would have to happen at a point where
definitely no resolver information for that interface is valid anymore;
e.g., when the interface goes down, or at dibbler-client exit.

Also it does not yet clear the private resolv.conf file(s) at start.

But I now have an IPv6 nameserver in my /etc/resolv.conf again.

Regards,
Fabian Pietsch

* Fabian Pietsch (Thu, 04 Dec 2014 02:52:29 +0100):

#771696#25
Date:
2014-12-17 23:16:56 UTC
From:
To:
Hello,

I've updated the patch to fix resolvconf support.

It fixes a logic error in the patched dns_add(), and chmod()s the right
file(s) in the new domain_add_file(), so /etc/resolv.conf won't
become o-r / others un-readable again.

Also, each new FOO_file() function is above the old FOO() function,
to avoid the FOO_file() function being implicitly declared.

It still does not call resolvconf -d IFACE.inet6 anywhere, and I haven't
patched the other two ports with resolvconf support code yet. But this
seems to run on my machine better than 1.0.0.

Regards, Fabian

diff --git a/Misc/Portable.h b/Misc/Portable.h
index 8c43b80..9b89a0d 100644
--- a/Misc/Portable.h
+++ b/Misc/Portable.h
@@ -151,6 +151,7 @@ struct link_state_notify_t
 #define SRVCONF_FILE       "/etc/dibbler/server.conf"
 #define RELCONF_FILE       "/etc/dibbler/relay.conf"
 #define RESOLVCONF_FILE    "/etc/resolv.conf"
+#define PRIV_RESOLVCONF_FILE "/var/lib/dibbler/resolv.conf"
 #define NTPCONF_FILE       "/etc/ntp.conf"
 #define RADVD_FILE         "/etc/dibbler/radvd.conf"
 #define CLNTPID_FILE       "/var/lib/dibbler/client.pid"
diff --git a/Misc/Portable.h.in b/Misc/Portable.h.in
index d8bc4a9..a2fe9e6 100644
--- a/Misc/Portable.h.in
+++ b/Misc/Portable.h.in
@@ -151,6 +151,7 @@ struct link_state_notify_t
 #define SRVCONF_FILE       "/etc/dibbler/server.conf"
 #define RELCONF_FILE       "/etc/dibbler/relay.conf"
 #define RESOLVCONF_FILE    "/etc/resolv.conf"
+#define PRIV_RESOLVCONF_FILE "/var/lib/dibbler/resolv.conf"
 #define NTPCONF_FILE       "/etc/ntp.conf"
 #define RADVD_FILE         "/etc/dibbler/radvd.conf"
 #define CLNTPID_FILE       "/var/lib/dibbler/client.pid"
diff --git a/Port-linux/lowlevel-options-linux.c b/Port-linux/lowlevel-options-linux.c
index 4000290..eb0c50b 100644
--- a/Port-linux/lowlevel-options-linux.c
+++ b/Port-linux/lowlevel-options-linux.c
@@ -9,6 +9,7 @@

 #define _BSD_SOURCE
 #define _POSIX_SOURCE
+#define _GNU_SOURCE

 #include <stdio.h>
 #include <unistd.h>
@@ -42,19 +43,24 @@ extern char * Message;
  * the pipe needs to be closed by the caller
  *
  * @param arg1 first command line argument passed to resolvconf
- * @param arg2 second command line argument passed to resolvconf
+ * @param ifname interface name for which to run resolvconf
  *
  * @return file handler (pipe to resolvconf process) or NULL
  */
-FILE *resolvconf_open(const char *arg1, const char *arg2)
+FILE *resolvconf_open(const char *arg1, const char *ifname)
 {
     pid_t child;
     int pipefd[2];
+    char * ifname_;

     if (access(RESOLVCONF, X_OK) != 0)
         return NULL;
-    if (pipe(pipefd) != 0)
+    if (!asprintf(&ifname_, "%s.inet6", ifname))
         return NULL;
+    if (pipe(pipefd) != 0) {
+        free(ifname_);
+        return NULL;
+    }
     switch(child = fork()) {
       case 0: /* child */
           close(pipefd[1]);
@@ -63,19 +69,35 @@ FILE *resolvconf_open(const char *arg1, const char *arg2)
 	  close(pipefd[0]);
 	  /* double fork so init reaps the child */
 	  if (!fork()) { /* child */
-              execl(RESOLVCONF, RESOLVCONF, arg1, arg2, (char *)NULL);
+              execl(RESOLVCONF, RESOLVCONF, arg1, ifname_, (char *)NULL);
 	  } /* All other cases are meaningless here */
 	  exit(EXIT_FAILURE);
 	  break;
     case EXIT_FAILURE: /* error */
+          free(ifname_);
           return NULL;
 	  break;
     }
     /* parent */
+    free(ifname_);
     close(pipefd[0]);
     waitpid(child, NULL, 0);
     return fdopen(pipefd[1], "w");
 }
+
+int resolvconf_feed(FILE * pipe, const char * file) {
+    FILE * f2 = NULL;
+    unsigned int c;
+
+    if (!(f2=fopen(file, "r")))
+        return LOWLEVEL_ERROR_FILE;
+
+    while ((c = fgetc(f2)) != EOF) {
+        fputc(c, pipe);
+    }
+    fclose(f2);
+    return LOWLEVEL_NO_ERROR;
+}
 #endif

 /* in iproute.c, borrowed from iproute2 */
@@ -230,18 +252,12 @@ int cfg_file_del(const char *file, const char *keyword, const char *value) {
           -1 - unable to open temp. file
           -2 - unable to open resolv.conf file
  */
-int dns_add(const char * ifname, int ifaceid, const char * addrPlain) {
+int dns_add_file(const char * ifname, int ifaceid, const char * addrPlain, const char * file) {
     FILE * f = NULL;
     unsigned char c;

-#ifdef MOD_RESOLVCONF
-    /* try to use resolvconf */
-    f=resolvconf_open("-a", ifname);
-#endif
-
-    /* if resolvconf is not available, fallback to normal file append */
-    if (!f && !(f=fopen(RESOLVCONF_FILE, "a+")) ) {
-            return LOWLEVEL_ERROR_FILE;
+    if (!(f=fopen(file, "a+")) ) {
+        return LOWLEVEL_ERROR_FILE;
     }

     fseek(f, -1, SEEK_END);
@@ -257,46 +273,95 @@ int dns_add(const char * ifname, int ifaceid, const char * addrPlain) {
     return LOWLEVEL_NO_ERROR;
 }

+
+/*
+ * results 0 - ok
+          -1 - unable to open temp. file
+          -2 - unable to open resolv.conf file
+ */
+int dns_add(const char * ifname, int ifaceid, const char * addrPlain) {
+    FILE * f = NULL;
+    char * file;
+    int ret;
+
+#ifdef MOD_RESOLVCONF
+    /* try to use resolvconf */
+    f=resolvconf_open("-a", ifname);
+    if (f) {
+        if (!asprintf(&file, PRIV_RESOLVCONF_FILE ".%s", ifname)) {
+            fclose(f);
+            return LOWLEVEL_ERROR_UNSPEC;
+        }
+
+        /* edit private resolv.conf-like file */
+        if ((ret = dns_add_file(ifname, ifaceid, addrPlain, file))) {
+            free(file);
+            fclose(f);
+            return ret;
+        }
+
+        /* feed that file to resolvconf */
+        ret = resolvconf_feed(f, file);
+        free(file);
+        fclose(f);
+        return ret;
+    }
+
+#endif
+
+    /* if resolvconf is not available, fallback to normal file append */
+    return dns_add_file(ifname, ifaceid, addrPlain, RESOLVCONF_FILE);
+}
+
 int dns_del(const char * ifname, int ifaceid, const char *addrPlain) {

 #ifdef MOD_RESOLVCONF
-    FILE *f = NULL;
-    /* try to use resolvconf to remove config */
-    if ((f=resolvconf_open("-d", ifname))) {
+    FILE * f = NULL;
+    char * file;
+    int ret;
+    /* try to use resolvconf to update config */
+    if ((f=resolvconf_open("-a", ifname))) {
+        if (!asprintf(&file, PRIV_RESOLVCONF_FILE ".%s", ifname)) {
+            fclose(f);
+            return LOWLEVEL_ERROR_UNSPEC;
+        }
+
+        /* edit private resolv.conf-like file */
+        if ((ret = cfg_file_del(file, "nameserver", addrPlain))) {
+            free(file);
+            fclose(f);
+            return ret;
+        }
+
+        /* feed that file to resolvconf */
+        ret = resolvconf_feed(f, file);
+        free(file);
         fclose(f);
-        return LOWLEVEL_NO_ERROR;
+        return ret;
     }
 #endif

     return cfg_file_del(RESOLVCONF_FILE, "nameserver", addrPlain);
 }