noip.[c1]: New configuration feature for setting local addresses.
authorMark Wooding <mdw@distorted.org.uk>
Mon, 2 May 2016 22:03:02 +0000 (23:03 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 2 May 2016 23:14:26 +0000 (00:14 +0100)
Some servers are picky about which addresses they're willing to accept
connections from.  Unfortunately, `noip' doesn't pick sensible local
addresses when implicitly binding sockets.  It can't usefully consult
the routing table (because we may be trying to simulate an entirely
fictional network of which the kernel knows nothing).  About the best we
can do is allow the user to configure the local address selection.

Add a new `impbind' configuration command which adds an entry to a list
of rules for choosing implicit binding addresses.

noip.1
noip.c

diff --git a/noip.1 b/noip.1
index 8687958..a79a872 100644 (file)
--- a/noip.1
+++ b/noip.1
@@ -175,6 +175,43 @@ rules are appended on the end.  Currently, the rules in
 are also put at the end (before the
 .B _AFTER
 rules), though this may change later.
+.TP
+.BI "impbind " bind-rule
+Add an entry to the implicit-bind rule list.  When a program attempts to
+.BR connect (2)
+a socket without binding its local address first,
+.B noip
+consults this list to decide on the correct local address to assign.
+Each entry in the list has the form
+.RS
+.IP
+.I address-range
+.IR address | \c
+.B same
+.PP
+The rules are tried in order: if the remote address matches (in the same
+way as in an ACL entry) the address range on the left side of the rule,
+then the socket is bound to the address from the right side; if the
+address on the right is
+.B same
+then the remote address is used.
+.PP
+Three environment variables
+are consulted too:
+.BR NOIP_IMPBIND_BEFORE ,
+.BR NOIP_IMPBIND ,
+and
+.BR NOIP_IMPBIND_AFTER .
+The
+.B _BEFORE
+rules are inserted at the front of the list; the
+.B _AFTER
+rules are appended on the end.  Currently, the rules in
+.B NOIP_IMPBIND
+are also put at the end (before the
+.B _AFTER
+rules), though this may change later.
+.RE
 .PP
 (Aside: An attempt to connect to a remote host may not be a hopeless failure,
 even if a real IP socket is denied:
diff --git a/noip.c b/noip.c
index 423822d..085b0be 100644 (file)
--- a/noip.c
+++ b/noip.c
@@ -89,6 +89,14 @@ typedef struct aclnode {
   unsigned short minport, maxport;
 } aclnode;
 
+/* Implicit bind records */
+typedef struct impbind {
+  struct impbind *next;
+  int af, how;
+  ipaddr minaddr, maxaddr, bindaddr;
+} impbind;
+enum { EXPLICIT, SAME };
+
 /* A type for an address range */
 typedef struct addrrange {
   int type;
@@ -116,6 +124,7 @@ static unsigned minautoport = 16384, maxautoport = 65536;
 /* Access control lists */
 static aclnode *bind_real, **bind_tail = &bind_real;
 static aclnode *connect_real,  **connect_tail = &connect_real;
+static impbind *impbinds, **impbind_tail = &impbinds;
 
 /*----- Import the real versions of functions -----------------------------*/
 
@@ -309,6 +318,23 @@ static void ipaddr_from_sockaddr(ipaddr *a, const struct sockaddr *sa)
   }
 }
 
+/* Store the address A in SA. */
+static void ipaddr_to_sockaddr(struct sockaddr *sa, const ipaddr *a)
+{
+  switch (sa->sa_family) {
+    case AF_INET:
+      SIN(sa)->sin_addr = a->v4;
+      break;
+    case AF_INET6:
+      SIN6(sa)->sin6_addr = a->v6;
+      SIN6(sa)->sin6_scope_id = 0;
+      SIN6(sa)->sin6_flowinfo = 0;
+      break;
+    default:
+      abort();
+  }
+}
+
 /* Copy a whole socket address about. */
 static void copy_sockaddr(struct sockaddr *sa_dst,
                          const struct sockaddr *sa_src)
@@ -825,6 +851,58 @@ static int fixup_real_ip_socket(int sk, int af_hint, int *tmp)
   return (0);
 }
 
+/* We found the real address SA, with length LEN; if it's a Unix-domain
+ * address corresponding to a fake socket, convert it to cover up the
+ * deception.  Whatever happens, put the result at FAKE and store its length
+ * at FAKELEN.
+ */
+static void return_fake_name(struct sockaddr *sa, socklen_t len,
+                            struct sockaddr *fake, socklen_t *fakelen)
+{
+  address addr;
+  socklen_t alen;
+
+  if (sa->sa_family == AF_UNIX &&
+      !decode_inet_addr(&addr.sa, 0, SUN(sa), len)) {
+    sa = &addr.sa;
+    len = family_socklen(addr.sa.sa_family);
+  }
+  alen = len;
+  if (len > *fakelen) len = *fakelen;
+  if (len > 0) memcpy(fake, sa, len);
+  *fakelen = alen;
+}
+
+/*----- Implicit binding --------------------------------------------------*/
+
+#ifdef DEBUG
+
+static void dump_impbind(const impbind *i)
+{
+  char buf[ADDRBUFSZ];
+
+  fprintf(stderr, "noip(%d):   ", getpid());
+  dump_addrrange(i->af, &i->minaddr, &i->maxaddr);
+  switch (i->how) {
+    case SAME: fprintf(stderr, " <self>"); break;
+    case EXPLICIT:
+      fprintf(stderr, " %s", inet_ntop(i->af, &i->bindaddr,
+                                      buf, sizeof(buf)));
+      break;
+    default: abort();
+  }
+  fputc('\n', stderr);
+}
+
+static void dump_impbind_list(void)
+{
+  const impbind *i;
+
+  for (i = impbinds; i; i = i->next) dump_impbind(i);
+}
+
+#endif
+
 /* The socket SK is about to be used to communicate with the remote address
  * SA.  Assign it a local address so that getpeername(2) does something
  * useful.
@@ -834,6 +912,8 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa,
 {
   address addr;
   socklen_t mylen = sizeof(*sun);
+  const impbind *i;
+  Dpid;
 
   if (acl_allows_p(connect_real, *sa)) {
     if (fixup_real_ip_socket(sk, (*sa)->sa_family, 0)) return (-1);
@@ -842,7 +922,20 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa,
     if (sun->sun_family == AF_UNIX) {
       if (mylen < sizeof(*sun)) ((char *)sun)[mylen] = 0;
       if (!sun->sun_path[0]) {
+       D( fprintf(stderr, "noip(%d): checking impbind list...\n", pid); )
+       for (i = impbinds; i; i = i->next) {
+         D( dump_impbind(i); )
+         if ((*sa)->sa_family == i->af &&
+             sockaddr_in_range_p(*sa, &i->minaddr, &i->maxaddr)) {
+           D( fprintf(stderr, "noip(%d): match!\n", pid); )
+           addr.sa.sa_family = (*sa)->sa_family;
+           ipaddr_to_sockaddr(&addr.sa, &i->bindaddr);
+           goto found;
+         }
+       }
+       D( fprintf(stderr, "noip(%d): no match; using wildcard\n", pid); )
        wildcard_address((*sa)->sa_family, &addr.sa);
+      found:
        encode_inet_addr(sun, &addr.sa, WANT_FRESH);
        if (real_bind(sk, SA(sun), SUN_LEN(sun))) return (-1);
       }
@@ -854,28 +947,6 @@ static int do_implicit_bind(int sk, const struct sockaddr **sa,
   return (0);
 }
 
-/* We found the real address SA, with length LEN; if it's a Unix-domain
- * address corresponding to a fake socket, convert it to cover up the
- * deception.  Whatever happens, put the result at FAKE and store its length
- * at FAKELEN.
- */
-static void return_fake_name(struct sockaddr *sa, socklen_t len,
-                            struct sockaddr *fake, socklen_t *fakelen)
-{
-  address addr;
-  socklen_t alen;
-
-  if (sa->sa_family == AF_UNIX &&
-      !decode_inet_addr(&addr.sa, 0, SUN(sa), len)) {
-    sa = &addr.sa;
-    len = family_socklen(addr.sa.sa_family);
-  }
-  alen = len;
-  if (len > *fakelen) len = *fakelen;
-  if (len > 0) memcpy(fake, sa, len);
-  *fakelen = alen;
-}
-
 /*----- Configuration -----------------------------------------------------*/
 
 /* Return the process owner's home directory. */
@@ -1159,6 +1230,82 @@ static void parse_acl_env(const char *var, aclnode ***tail)
   }
 }
 
+struct add_impbind_ctx {
+  int af, how;
+  ipaddr addr;
+};
+
+static void add_impbind(int af, const ipaddr *min, const ipaddr *max,
+                       void *p)
+{
+  struct add_impbind_ctx *ctx = p;
+  impbind *i;
+
+  if (ctx->af && af != ctx->af) return;
+  NEW(i);
+  i->af = af;
+  i->how = ctx->how;
+  i->minaddr = *min; i->maxaddr = *max;
+  switch (ctx->how) {
+    case EXPLICIT: i->bindaddr = ctx->addr;
+    case SAME: break;
+    default: abort();
+  }
+  *impbind_tail = i; impbind_tail = &i->next;
+}
+
+/* Parse an implicit-bind line.  An implicit-bind entry has the form
+ * ADDR-RANGE {ADDR | same}
+ */
+static void parse_impbind_line(char **pp)
+{
+  struct add_impbind_ctx ctx;
+  char *p = *pp, *q;
+  addrrange r;
+  int del;
+
+  for (;;) {
+    if (parse_addrrange(&p, &r)) goto bad;
+    SKIPSPC;
+    if (KWMATCHP("same")) {
+      ctx.how = SAME;
+      ctx.af = 0;
+    } else {
+      ctx.how = EXPLICIT;
+      parse_nextaddr(&p, &q, &del);
+      ctx.af = guess_address_family(q);
+      if (inet_pton(ctx.af, q, &ctx.addr) < 0) goto bad;
+      RESCAN(del);
+    }
+    foreach_addrrange(&r, add_impbind, &ctx);
+    SKIPSPC;
+    if (*p != ',') break;
+    if (*p) p++;
+  }
+  if (*p) goto bad;
+  *pp = p;
+  return;
+
+bad:
+  D( fprintf(stderr, "noip(%d): bad implicit-bind spec (ignored)\n",
+            getpid()); )
+  return;
+}
+
+/* Parse implicit-bind instructions from an environment variable VAR,
+ * attaching it to the list.
+ */
+static void parse_impbind_env(const char *var)
+{
+  char *p, *q;
+
+  if ((p = getenv(var)) != 0) {
+    p = q = xstrdup(p);
+    parse_impbind_line(&q);
+    free(p);
+  }
+}
+
 /* Parse the autoports configuration directive.  Syntax is MIN - MAX. */
 static void parse_autoports(char **pp)
 {
@@ -1192,6 +1339,7 @@ static void readconfig(void)
 
   parse_acl_env("NOIP_REALBIND_BEFORE", &bind_tail);
   parse_acl_env("NOIP_REALCONNECT_BEFORE", &connect_tail);
+  parse_impbind_env("NOIP_IMPBIND_BEFORE");
   if ((p = getenv("NOIP_AUTOPORTS")) != 0) {
     p = q = xstrdup(p);
     parse_autoports(&q);
@@ -1223,22 +1371,27 @@ static void readconfig(void)
       parse_acl_line(&p, &bind_tail);
     else if (strcmp(cmd, "realconnect") == 0)
       parse_acl_line(&p, &connect_tail);
+    else if (strcmp(cmd, "impbind") == 0)
+      parse_impbind_line(&p);
     else if (strcmp(cmd, "autoports") == 0)
       parse_autoports(&p);
     else if (strcmp(cmd, "debug") == 0)
       debug = *p ? atoi(p) : 1;
     else
-      D( fprintf(stderr, "noip: bad config command %s\n", cmd); )
+      D( fprintf(stderr, "noip(%d): bad config command %s\n", pid, cmd); )
   }
   fclose(fp);
 
 done:
   parse_acl_env("NOIP_REALBIND", &bind_tail);
   parse_acl_env("NOIP_REALCONNECT", &connect_tail);
+  parse_impbind_env("NOIP_IMPBIND");
   parse_acl_env("NOIP_REALBIND_AFTER", &bind_tail);
   parse_acl_env("NOIP_REALCONNECT_AFTER", &connect_tail);
+  parse_impbind_env("NOIP_IMPBIND_AFTER");
   *bind_tail = 0;
   *connect_tail = 0;
+  *impbind_tail = 0;
   if (!sockdir) sockdir = getenv("NOIP_SOCKETDIR");
   if (!sockdir) {
     snprintf(buf, sizeof(buf), "%s/noip-%s", tmpdir(), user());
@@ -1250,7 +1403,9 @@ done:
      fprintf(stderr, "noip(%d): realbind acl:\n", pid);
      dump_acl(bind_real);
      fprintf(stderr, "noip(%d): realconnect acl:\n", pid);
-     dump_acl(connect_real); )
+     dump_acl(connect_real);
+     fprintf(stderr, "noip(%d): impbind list:\n", pid);
+     dump_impbind_list(); )
 }
 
 /*----- Overridden system calls -------------------------------------------*/