Browse Source

Binding to IPv6 addresses

Sergey Lyubka 12 years ago
parent
commit
e53d05637a
2 changed files with 74 additions and 29 deletions
  1. 36 25
      mongoose.c
  2. 38 4
      test/unit_test.c

+ 36 - 25
mongoose.c

@@ -4499,41 +4499,50 @@ static void close_all_listening_sockets(struct mg_context *ctx) {
   free(ctx->listening_sockets);
 }
 
+static int is_valid_port(unsigned int port) {
+  return port > 0 && port < 0xffff;
+}
+
 // Valid listening port specification is: [ip_address:]port[s]
 // Examples: 80, 443s, 127.0.0.1:3128, 1.2.3.4:8080s
 // TODO(lsm): add parsing of the IPv6 address
 static int parse_port_string(const struct vec *vec, struct socket *so) {
-  int a, b, c, d, port, len;
+  unsigned int a, b, c, d, ch, len, port;
+#if defined(USE_IPV6)
+  char buf[100];
+#endif
 
   // MacOS needs that. If we do not zero it, subsequent bind() will fail.
   // Also, all-zeroes in the socket address means binding to all addresses
   // for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).
   memset(so, 0, sizeof(*so));
+  so->lsa.sin.sin_family = AF_INET;
 
-  if (sscanf(vec->ptr, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &len) == 5) {
-    // Bind to a specific IPv4 address
+  if (sscanf(vec->ptr, "%u.%u.%u.%u:%u%n", &a, &b, &c, &d, &port, &len) == 5) {
+    // Bind to a specific IPv4 address, e.g. 192.168.1.5:8080
     so->lsa.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);
-  } else if (sscanf(vec->ptr, "%d%n", &port, &len) != 1 ||
-             len <= 0 ||
-             len > (int) vec->len ||
-             port < 1 ||
-             port > 65535 ||
-             (vec->ptr[len] && vec->ptr[len] != 's' &&
-              vec->ptr[len] != 'r' && vec->ptr[len] != ',')) {
-    return 0;
-  }
-
-  so->is_ssl = vec->ptr[len] == 's';
-  so->ssl_redir = vec->ptr[len] == 'r';
+    so->lsa.sin.sin_port = htons((uint16_t) port);
 #if defined(USE_IPV6)
-  so->lsa.sin6.sin6_family = AF_INET6;
-  so->lsa.sin6.sin6_port = htons((uint16_t) port);
-#else
-  so->lsa.sin.sin_family = AF_INET;
-  so->lsa.sin.sin_port = htons((uint16_t) port);
+  } else if (sscanf(vec->ptr, "[%49[^]]]:%d%n", buf, &port, &len) == 2 &&
+             inet_pton(AF_INET6, buf, &so->lsa.sin6.sin6_addr)) {
+    // IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080
+    so->lsa.sin6.sin6_family = AF_INET6;
+    so->lsa.sin6.sin6_port = htons((uint16_t) port);
 #endif
+  } else if (sscanf(vec->ptr, "%u%n", &port, &len) == 1) {
+    // If only port is specified, bind to IPv4, INADDR_ANY
+    so->lsa.sin.sin_port = htons((uint16_t) port);
+  } else {
+    port = len = 0;   // Parsing failure. Make port invalid.
+  }
 
-  return 1;
+  ch = vec->ptr[len];  // Next character after the port number
+  so->is_ssl = ch == 's';
+  so->ssl_redir = ch == 'r';
+
+  // Make sure the port is valid and vector ends with 's', 'r' or ','
+  return is_valid_port(port) &&
+    (ch == '\0' || ch == 's' || ch == 'r' || ch == ',');
 }
 
 static int set_ports_option(struct mg_context *ctx) {
@@ -4548,7 +4557,7 @@ static int set_ports_option(struct mg_context *ctx) {
   while (success && (list = next_option(list, &vec, NULL)) != NULL) {
     if (!parse_port_string(&vec, &so)) {
       cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",
-          __func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]");
+          __func__, (int) vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|r]");
       success = 0;
     } else if (so.is_ssl && ctx->ssl_ctx == NULL) {
       cry(fc(ctx), "Cannot add SSL socket, is -ssl_certificate option set?");
@@ -4560,10 +4569,12 @@ static int set_ports_option(struct mg_context *ctx) {
                setsockopt(so.sock, SOL_SOCKET, SO_REUSEADDR,
                           (void *) &on, sizeof(on)) != 0 ||
 #if defined(USE_IPV6)
-               setsockopt(so.sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &off,
-                          sizeof(off)) != 0 ||
+               (so.lsa.sa.sa_family == AF_INET6 &&
+                setsockopt(so.sock, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &off,
+                           sizeof(off)) != 0) ||
 #endif
-               bind(so.sock, &so.lsa.sa, sizeof(so.lsa)) != 0 ||
+               bind(so.sock, &so.lsa.sa, so.lsa.sa.sa_family == AF_INET ?
+                    sizeof(so.lsa.sin) : sizeof(so.lsa)) != 0 ||
                listen(so.sock, SOMAXCONN) != 0) {
       cry(fc(ctx), "%s: cannot bind to %.*s: %d", __func__,
           (int) vec.len, vec.ptr, ERRNO);

+ 38 - 4
test/unit_test.c

@@ -22,6 +22,14 @@
 
 #define USE_WEBSOCKET
 #define USE_LUA
+
+#ifndef _WIN32
+#define __cdecl
+#define USE_IPV6
+#endif
+
+// USE_* definitions must be made before #include "mongoose.c" !
+
 #include "mongoose.c"
 
 static int s_total_tests = 0;
@@ -45,10 +53,6 @@ static int s_failed_tests = 0;
   ",127.0.0.1:" HTTPS_PORT "s"  \
   ",127.0.0.1:" HTTP_PORT2
 
-#ifndef _WIN32
-#define __cdecl
-#endif
-
 static void test_parse_http_message() {
   struct mg_request_info ri;
   char req1[] = "GET / HTTP/1.1\r\n\r\n";
@@ -719,7 +723,37 @@ static void test_strtoll(void) {
   ASSERT(strtoll("3566626116", NULL, 10) == 3566626116);
 }
 
+static void test_parse_port_string(void) {
+  static const char *valid[] = {
+    "1", "1s", "1r", "1.2.3.4:1", "1.2.3.4:1s", "1.2.3.4:1r",
+#if defined(USE_IPV6)
+    "[::1]:123", "[3ffe:2a00:100:7031::1]:900",
+#endif
+    NULL
+  };
+  static const char *invalid[] = {
+    "0", "99999", "1k", "1.2.3", "1.2.3.4:", "1.2.3.4:2p",
+    NULL
+  };
+  struct socket so;
+  struct vec vec;
+  int i;
+
+  for (i = 0; valid[i] != NULL; i++) {
+    vec.ptr = valid[i];
+    vec.len = strlen(vec.ptr);
+    ASSERT(parse_port_string(&vec, &so) != 0);
+  }
+
+  for (i = 0; invalid[i] != NULL; i++) {
+    vec.ptr = invalid[i];
+    vec.len = strlen(vec.ptr);
+    ASSERT(parse_port_string(&vec, &so) == 0);
+  }
+}
+
 int __cdecl main(void) {
+  test_parse_port_string();
   test_mg_strcasestr();
   test_alloc_vprintf();
   test_base64_encode();