< prev index next >

src/java.base/unix/native/libnet/net_util_md.c

Print this page

        

@@ -450,11 +450,289 @@
         }
         free(buf);
     }
 }
 
-#if defined(_AIX)
+#if defined(__linux__)
+
+/* following code creates a list of addresses from the kernel
+ * routing table that are routed via the loopback address.
+ * We check all destination addresses against this table
+ * and override the scope_id field to use the relevant value for "lo"
+ * in order to work-around the Linux bug that prevents packets destined
+ * for certain local addresses from being sent via a physical interface.
+ */
+
+struct loopback_route {
+    struct in6_addr addr; /* destination address */
+    int plen; /* prefix length */
+};
+
+static struct loopback_route *loRoutes = 0;
+static int nRoutes = 0; /* number of routes */
+static int loRoutes_size = 16; /* initial size */
+static int lo_scope_id = 0;
+
+static void initLoopbackRoutes();
+
+void printAddr (struct in6_addr *addr) {
+    int i;
+    for (i=0; i<16; i++) {
+        printf ("%02x", addr->s6_addr[i]);
+    }
+    printf ("\n");
+}
+
+static jboolean needsLoopbackRoute (struct in6_addr* dest_addr) {
+    int byte_count;
+    int extra_bits, i;
+    struct loopback_route *ptr;
+
+    if (loRoutes == 0) {
+        initLoopbackRoutes();
+    }
+
+    for (ptr = loRoutes, i=0; i<nRoutes; i++, ptr++) {
+        struct in6_addr *target_addr=&ptr->addr;
+        int dest_plen = ptr->plen;
+        byte_count = dest_plen >> 3;
+        extra_bits = dest_plen & 0x3;
+
+        if (byte_count > 0) {
+            if (memcmp(target_addr, dest_addr, byte_count)) {
+                continue;  /* no match */
+            }
+        }
+
+        if (extra_bits > 0) {
+            unsigned char c1 = ((unsigned char *)target_addr)[byte_count];
+            unsigned char c2 = ((unsigned char *)&dest_addr)[byte_count];
+            unsigned char mask = 0xff << (8 - extra_bits);
+            if ((c1 & mask) != (c2 & mask)) {
+                continue;
+            }
+        }
+        return JNI_TRUE;
+    }
+    return JNI_FALSE;
+}
+
+
+static void initLoopbackRoutes() {
+    FILE *f;
+    char srcp[8][5];
+    char hopp[8][5];
+    int dest_plen, src_plen, use, refcnt, metric;
+    unsigned long flags;
+    char dest_str[40];
+    struct in6_addr dest_addr;
+    char device[16];
+    struct loopback_route *loRoutesTemp;
+
+    if (loRoutes != 0) {
+        free (loRoutes);
+    }
+    loRoutes = calloc (loRoutes_size, sizeof(struct loopback_route));
+    if (loRoutes == 0) {
+        return;
+    }
+    /*
+     * Scan /proc/net/ipv6_route looking for a matching
+     * route.
+     */
+    if ((f = fopen("/proc/net/ipv6_route", "r")) == NULL) {
+        return ;
+    }
+    while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x "
+                     "%4s%4s%4s%4s%4s%4s%4s%4s %02x "
+                     "%4s%4s%4s%4s%4s%4s%4s%4s "
+                     "%08x %08x %08x %08lx %8s",
+                     dest_str, &dest_str[5], &dest_str[10], &dest_str[15],
+                     &dest_str[20], &dest_str[25], &dest_str[30], &dest_str[35],
+                     &dest_plen,
+                     srcp[0], srcp[1], srcp[2], srcp[3],
+                     srcp[4], srcp[5], srcp[6], srcp[7],
+                     &src_plen,
+                     hopp[0], hopp[1], hopp[2], hopp[3],
+                     hopp[4], hopp[5], hopp[6], hopp[7],
+                     &metric, &use, &refcnt, &flags, device) == 31) {
+
+        /*
+         * Some routes should be ignored
+         */
+        if ( (dest_plen < 0 || dest_plen > 128)  ||
+             (src_plen != 0) ||
+             (flags & (RTF_POLICY | RTF_FLOW)) ||
+             ((flags & RTF_REJECT) && dest_plen == 0) ) {
+            continue;
+        }
+
+        /*
+         * Convert the destination address
+         */
+        dest_str[4] = ':';
+        dest_str[9] = ':';
+        dest_str[14] = ':';
+        dest_str[19] = ':';
+        dest_str[24] = ':';
+        dest_str[29] = ':';
+        dest_str[34] = ':';
+        dest_str[39] = '\0';
+
+        if (inet_pton(AF_INET6, dest_str, &dest_addr) < 0) {
+            /* not an Ipv6 address */
+            continue;
+        }
+        if (strcmp(device, "lo") != 0) {
+            /* Not a loopback route */
+            continue;
+        } else {
+            if (nRoutes == loRoutes_size) {
+                loRoutesTemp = realloc (loRoutes, loRoutes_size *
+                                        sizeof (struct loopback_route) * 2);
+
+                if (loRoutesTemp == 0) {
+                    free(loRoutes);
+                    loRoutes = NULL;
+                    nRoutes = 0;
+                    fclose (f);
+                    return;
+                }
+                loRoutes=loRoutesTemp;
+                loRoutes_size *= 2;
+            }
+            memcpy (&loRoutes[nRoutes].addr,&dest_addr,sizeof(struct in6_addr));
+            loRoutes[nRoutes].plen = dest_plen;
+            nRoutes ++;
+        }
+    }
+
+    fclose (f);
+    {
+        /* now find the scope_id for "lo" */
+
+        char devname[21];
+        char addr6p[8][5];
+        int plen, scope, dad_status, if_idx;
+
+        if ((f = fopen("/proc/net/if_inet6", "r")) != NULL) {
+            while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
+                      addr6p[0], addr6p[1], addr6p[2], addr6p[3],
+                      addr6p[4], addr6p[5], addr6p[6], addr6p[7],
+                  &if_idx, &plen, &scope, &dad_status, devname) == 13) {
+
+                if (strcmp(devname, "lo") == 0) {
+                    /*
+                     * Found - so just return the index
+                     */
+                    fclose(f);
+                    lo_scope_id = if_idx;
+                    return;
+                }
+            }
+            fclose(f);
+        }
+    }
+}
+
+/*
+ * Following is used for binding to local addresses. Equivalent
+ * to code above, for bind().
+ */
+
+struct localinterface {
+    int index;
+    char localaddr [16];
+};
+
+static struct localinterface *localifs = 0;
+static int localifsSize = 0;    /* size of array */
+static int nifs = 0;            /* number of entries used in array */
+
+/* not thread safe: make sure called once from one thread */
+
+static void initLocalIfs () {
+    FILE *f;
+    unsigned char staddr [16];
+    char ifname [33];
+    struct localinterface *lif=0;
+    struct localinterface *localifsTemp;
+    int index, x1, x2, x3;
+    unsigned int u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,ua,ub,uc,ud,ue,uf;
+
+    if ((f = fopen("/proc/net/if_inet6", "r")) == NULL) {
+        return ;
+    }
+    while (fscanf (f, "%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x "
+                "%d %x %x %x %32s",&u0,&u1,&u2,&u3,&u4,&u5,&u6,&u7,
+                &u8,&u9,&ua,&ub,&uc,&ud,&ue,&uf,
+                &index, &x1, &x2, &x3, ifname) == 21) {
+        staddr[0] = (unsigned char)u0;
+        staddr[1] = (unsigned char)u1;
+        staddr[2] = (unsigned char)u2;
+        staddr[3] = (unsigned char)u3;
+        staddr[4] = (unsigned char)u4;
+        staddr[5] = (unsigned char)u5;
+        staddr[6] = (unsigned char)u6;
+        staddr[7] = (unsigned char)u7;
+        staddr[8] = (unsigned char)u8;
+        staddr[9] = (unsigned char)u9;
+        staddr[10] = (unsigned char)ua;
+        staddr[11] = (unsigned char)ub;
+        staddr[12] = (unsigned char)uc;
+        staddr[13] = (unsigned char)ud;
+        staddr[14] = (unsigned char)ue;
+        staddr[15] = (unsigned char)uf;
+        nifs ++;
+        if (nifs > localifsSize) {
+            localifsTemp = (struct localinterface *) realloc(
+                        localifs, sizeof (struct localinterface)* (localifsSize+5));
+            if (localifsTemp == 0) {
+                free(localifs);
+                localifs = 0;
+                localifsSize = 0;
+                nifs = 0;
+                fclose(f);
+                return;
+            }
+            localifs = localifsTemp;
+            lif = localifs + localifsSize;
+            localifsSize += 5;
+        } else {
+            lif ++;
+        }
+        memcpy (lif->localaddr, staddr, 16);
+        lif->index = index;
+    }
+    fclose (f);
+}
+
+/* return the scope_id (interface index) of the
+ * interface corresponding to the given address
+ * returns 0 if no match found
+ */
+
+static int getLocalScopeID (char *addr) {
+    struct localinterface *lif;
+    int i;
+    if (localifs == 0) {
+        initLocalIfs();
+    }
+    for (i=0, lif=localifs; i<nifs; i++, lif++) {
+        if (memcmp (addr, lif->localaddr, 16) == 0) {
+            return lif->index;
+        }
+    }
+    return 0;
+}
+
+void platformInit () {
+    initLoopbackRoutes();
+    initLocalIfs();
+}
+
+#elif defined(_AIX)
 
 /* Initialize stubs for blocking I/O workarounds (see src/solaris/native/java/net/linux_close.c) */
 extern void aix_close_init();
 
 void platformInit () {

@@ -536,16 +814,75 @@
         sa->sa6.sin6_family = AF_INET6;
         if (len != NULL) {
             *len = sizeof(struct sockaddr_in6);
         }
 
+#ifdef __linux__
+        /*
+         * On Linux if we are connecting to a link-local address
+         * we need to specify the interface in the scope_id (2.4 kernel only)
+         *
+         * If the scope was cached then we use the cached value. If not cached but
+         * specified in the Inet6Address we use that, but we first check if the
+         * address needs to be routed via the loopback interface. In this case,
+         * we override the specified value with that of the loopback interface.
+         * If no cached value exists and no value was specified by user, then
+         * we try to determine a value from the routing table. In all these
+         * cases the used value is cached for further use.
+         */
+        if (IN6_IS_ADDR_LINKLOCAL(&sa->sa6.sin6_addr)) {
+            unsigned int cached_scope_id = 0, scope_id = 0;
+
+            if (ia6_cachedscopeidID) {
+                cached_scope_id = (int)(*env)->GetIntField(env, iaObj, ia6_cachedscopeidID);
+                /* if cached value exists then use it. Otherwise, check
+                 * if scope is set in the address.
+                 */
+                if (!cached_scope_id) {
+                    if (ia6_scopeidID) {
+                        scope_id = getInet6Address_scopeid(env, iaObj);
+                    }
+                    if (scope_id != 0) {
+                        /* check user-specified value for loopback case
+                         * that needs to be overridden
+                         */
+                        if (kernelIsV24() && needsLoopbackRoute(&sa->sa6.sin6_addr)) {
+                            cached_scope_id = lo_scope_id;
+                            (*env)->SetIntField(env, iaObj, ia6_cachedscopeidID, cached_scope_id);
+                        }
+                    } else {
+                        /*
+                         * Otherwise consult the IPv6 routing tables to
+                         * try determine the appropriate interface.
+                         */
+                        if (kernelIsV24()) {
+                            cached_scope_id = getDefaultIPv6Interface(&sa->sa6.sin6_addr);
+                        } else {
+                            cached_scope_id = getLocalScopeID((char *)&(sa->sa6.sin6_addr));
+                            if (cached_scope_id == 0) {
+                                cached_scope_id = getDefaultIPv6Interface(&sa->sa6.sin6_addr);
+                            }
+                        }
+                        (*env)->SetIntField(env, iaObj, ia6_cachedscopeidID, cached_scope_id);
+                    }
+                }
+            }
+
+            /*
+             * If we have a scope_id use the extended form
+             * of sockaddr_in6.
+             */
+            sa->sa6.sin6_scope_id = cached_scope_id == 0 ? scope_id : cached_scope_id;
+        }
+#else
         /* handle scope_id */
         if (family != java_net_InetAddress_IPv4) {
             if (ia6_scopeidID) {
                 sa->sa6.sin6_scope_id = getInet6Address_scopeid(env, iaObj);
             }
         }
+#endif
     } else {
         jint address;
         if (family != java_net_InetAddress_IPv4) {
             JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Protocol family unavailable");
             return -1;

@@ -676,10 +1013,162 @@
     /* not found */
     return -1;
 }
 
 /*
+ * Determine the default interface for an IPv6 address.
+ *
+ * 1. Scans /proc/net/ipv6_route for a matching route
+ *    (eg: fe80::/10 or a route for the specific address).
+ *    This will tell us the interface to use (eg: "eth0").
+ *
+ * 2. Lookup /proc/net/if_inet6 to map the interface
+ *    name to an interface index.
+ *
+ * Returns :-
+ *      -1 if error
+ *       0 if no matching interface
+ *      >1 interface index to use for the link-local address.
+ */
+#if defined(__linux__)
+int getDefaultIPv6Interface(struct in6_addr *target_addr) {
+    FILE *f;
+    char srcp[8][5];
+    char hopp[8][5];
+    int dest_plen, src_plen, use, refcnt, metric;
+    unsigned long flags;
+    char dest_str[40];
+    struct in6_addr dest_addr;
+    char device[16];
+    jboolean match = JNI_FALSE;
+
+    /*
+     * Scan /proc/net/ipv6_route looking for a matching
+     * route.
+     */
+    if ((f = fopen("/proc/net/ipv6_route", "r")) == NULL) {
+        return -1;
+    }
+    while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x "
+                     "%4s%4s%4s%4s%4s%4s%4s%4s %02x "
+                     "%4s%4s%4s%4s%4s%4s%4s%4s "
+                     "%08x %08x %08x %08lx %8s",
+                     dest_str, &dest_str[5], &dest_str[10], &dest_str[15],
+                     &dest_str[20], &dest_str[25], &dest_str[30], &dest_str[35],
+                     &dest_plen,
+                     srcp[0], srcp[1], srcp[2], srcp[3],
+                     srcp[4], srcp[5], srcp[6], srcp[7],
+                     &src_plen,
+                     hopp[0], hopp[1], hopp[2], hopp[3],
+                     hopp[4], hopp[5], hopp[6], hopp[7],
+                     &metric, &use, &refcnt, &flags, device) == 31) {
+
+        /*
+         * Some routes should be ignored
+         */
+        if ( (dest_plen < 0 || dest_plen > 128)  ||
+             (src_plen != 0) ||
+             (flags & (RTF_POLICY | RTF_FLOW)) ||
+             ((flags & RTF_REJECT) && dest_plen == 0) ) {
+            continue;
+        }
+
+        /*
+         * Convert the destination address
+         */
+        dest_str[4] = ':';
+        dest_str[9] = ':';
+        dest_str[14] = ':';
+        dest_str[19] = ':';
+        dest_str[24] = ':';
+        dest_str[29] = ':';
+        dest_str[34] = ':';
+        dest_str[39] = '\0';
+
+        if (inet_pton(AF_INET6, dest_str, &dest_addr) < 0) {
+            /* not an Ipv6 address */
+            continue;
+        } else {
+            /*
+             * The prefix len (dest_plen) indicates the number of bits we
+             * need to match on.
+             *
+             * dest_plen / 8    => number of bytes to match
+             * dest_plen % 8    => number of additional bits to match
+             *
+             * eg: fe80::/10 => match 1 byte + 2 additional bits in the
+             *                  next byte.
+             */
+            int byte_count = dest_plen >> 3;
+            int extra_bits = dest_plen & 0x3;
+
+            if (byte_count > 0) {
+                if (memcmp(target_addr, &dest_addr, byte_count)) {
+                    continue;  /* no match */
+                }
+            }
+
+            if (extra_bits > 0) {
+                unsigned char c1 = ((unsigned char *)target_addr)[byte_count];
+                unsigned char c2 = ((unsigned char *)&dest_addr)[byte_count];
+                unsigned char mask = 0xff << (8 - extra_bits);
+                if ((c1 & mask) != (c2 & mask)) {
+                    continue;
+                }
+            }
+
+            /*
+             * We have a match
+             */
+            match = JNI_TRUE;
+            break;
+        }
+    }
+    fclose(f);
+
+    /*
+     * If there's a match then we lookup the interface
+     * index.
+     */
+    if (match) {
+        char devname[21];
+        char addr6p[8][5];
+        int plen, scope, dad_status, if_idx;
+
+        if ((f = fopen("/proc/net/if_inet6", "r")) != NULL) {
+            while (fscanf(f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
+                      addr6p[0], addr6p[1], addr6p[2], addr6p[3],
+                      addr6p[4], addr6p[5], addr6p[6], addr6p[7],
+                  &if_idx, &plen, &scope, &dad_status, devname) == 13) {
+
+                if (strcmp(devname, device) == 0) {
+                    /*
+                     * Found - so just return the index
+                     */
+                    fclose(f);
+                    return if_idx;
+                }
+            }
+            fclose(f);
+        } else {
+            /*
+             * Couldn't open /proc/net/if_inet6
+             */
+            return -1;
+        }
+    }
+
+    /*
+     * If we get here it means we didn't there wasn't any
+     * route or we couldn't get the index of the interface.
+     */
+    return 0;
+}
+#endif
+
+
+/*
  * Wrapper for getsockopt system routine - does any necessary
  * pre/post processing to deal with OS specific oddities :-
  *
  * On Linux the SO_SNDBUF/SO_RCVBUF values must be post-processed
  * to compensate for an incorrect value returned by the kernel.
< prev index next >