1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.net;
19
20import android.system.ErrnoException;
21import java.io.File;
22import java.io.FileDescriptor;
23import java.io.IOException;
24import java.util.ArrayList;
25import java.util.Arrays;
26import java.util.Collections;
27import java.util.Enumeration;
28import java.util.LinkedList;
29import java.util.List;
30import libcore.io.IoUtils;
31import libcore.io.Libcore;
32import static android.system.OsConstants.*;
33
34/**
35 * This class is used to represent a network interface of the local device. An
36 * interface is defined by its address and a platform dependent name. The class
37 * provides methods to get all information about the available interfaces of the
38 * system or to identify the local interface of a joined multicast group.
39 */
40public final class NetworkInterface extends Object {
41    private static final File SYS_CLASS_NET = new File("/sys/class/net");
42
43    private final String name;
44    private final int interfaceIndex;
45    private final List<InterfaceAddress> interfaceAddresses;
46    private final List<InetAddress> addresses;
47
48    private final List<NetworkInterface> children = new LinkedList<NetworkInterface>();
49
50    private NetworkInterface parent = null;
51
52    private NetworkInterface(String name, int interfaceIndex,
53            List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses) {
54        this.name = name;
55        this.interfaceIndex = interfaceIndex;
56        this.addresses = addresses;
57        this.interfaceAddresses = interfaceAddresses;
58    }
59
60    static NetworkInterface forUnboundMulticastSocket() {
61        // This is what the RI returns for a MulticastSocket that hasn't been constrained
62        // to a specific interface.
63        return new NetworkInterface(null, -1,
64                Arrays.asList(Inet6Address.ANY), Collections.<InterfaceAddress>emptyList());
65    }
66
67    /**
68     * Returns the index for the network interface, or -1 if unknown.
69     * @since 1.7
70     */
71    public int getIndex() {
72        return interfaceIndex;
73    }
74
75    /**
76     * Returns the name of this network interface (such as "eth0" or "lo").
77     */
78    public String getName() {
79        return name;
80    }
81
82    /**
83     * Returns an enumeration of the addresses bound to this network interface.
84     */
85    public Enumeration<InetAddress> getInetAddresses() {
86        return Collections.enumeration(addresses);
87    }
88
89    /**
90     * Returns a human-readable name for this network interface. On Android, this is the same
91     * string as returned by {@link #getName}.
92     */
93    public String getDisplayName() {
94        return name;
95    }
96
97    /**
98     * Returns the {@code NetworkInterface} corresponding to the named network interface, or null
99     * if no interface has this name.
100     *
101     * @throws SocketException if an error occurs.
102     * @throws NullPointerException if {@code interfaceName == null}.
103     */
104    public static NetworkInterface getByName(String interfaceName) throws SocketException {
105        if (interfaceName == null) {
106            throw new NullPointerException("interfaceName == null");
107        }
108        if (!isValidInterfaceName(interfaceName)) {
109            return null;
110        }
111
112        return getByNameInternal(interfaceName, readIfInet6Lines());
113    }
114
115    /**
116     * Similar to {@link #getByName(String)} except that {@code interfaceName}
117     * is assumed to be valid.
118     */
119    private static NetworkInterface getByNameInternal(String interfaceName,
120            String[] ifInet6Lines) throws SocketException {
121        int interfaceIndex = readIntFile("/sys/class/net/" + interfaceName + "/ifindex");
122        List<InetAddress> addresses = new ArrayList<InetAddress>();
123        List<InterfaceAddress> interfaceAddresses = new ArrayList<InterfaceAddress>();
124
125        collectIpv6Addresses(interfaceName, interfaceIndex, addresses, interfaceAddresses,
126                ifInet6Lines);
127        collectIpv4Address(interfaceName, addresses, interfaceAddresses);
128
129        return new NetworkInterface(interfaceName, interfaceIndex, addresses, interfaceAddresses);
130    }
131
132    private static String[] readIfInet6Lines() throws SocketException {
133        try {
134            return IoUtils.readFileAsString("/proc/net/if_inet6").split("\n");
135        } catch (IOException ioe) {
136            throw rethrowAsSocketException(ioe);
137        }
138    }
139
140    /**
141     * Visible for testing only.
142     *
143     * @hide
144     */
145    public static void collectIpv6Addresses(String interfaceName, int interfaceIndex,
146            List<InetAddress> addresses, List<InterfaceAddress> interfaceAddresses,
147            String[] ifInet6Lines) throws SocketException {
148        // Format of /proc/net/if_inet6.
149        // All numeric fields are implicit hex,
150        // but not necessarily two-digit (http://code.google.com/p/android/issues/detail?id=34022).
151        // 1. IPv6 address
152        // 2. interface index
153        // 3. prefix length
154        // 4. scope
155        // 5. flags
156        // 6. interface name
157        // "00000000000000000000000000000001 01 80 10 80       lo"
158        // "fe800000000000000000000000000000 407 40 20 80    wlan0"
159        final String suffix = " " + interfaceName;
160        try {
161            for (String line : ifInet6Lines) {
162                if (!line.endsWith(suffix)) {
163                    continue;
164                }
165
166                // Extract the IPv6 address.
167                byte[] addressBytes = new byte[16];
168                for (int i = 0; i < addressBytes.length; ++i) {
169                    addressBytes[i] = (byte) Integer.parseInt(line.substring(2*i, 2*i + 2), 16);
170                }
171
172                // Extract the prefix length.
173                // Skip the IPv6 address and its trailing space.
174                int prefixLengthStart = 32 + 1;
175                // Skip the interface index and its trailing space.
176                prefixLengthStart = line.indexOf(' ', prefixLengthStart) + 1;
177                int prefixLengthEnd = line.indexOf(' ', prefixLengthStart);
178                short prefixLength = Short.parseShort(line.substring(prefixLengthStart, prefixLengthEnd), 16);
179
180                Inet6Address inet6Address = new Inet6Address(addressBytes, null, interfaceIndex);
181                addresses.add(inet6Address);
182                interfaceAddresses.add(new InterfaceAddress(inet6Address, prefixLength));
183            }
184        } catch (NumberFormatException ex) {
185            throw rethrowAsSocketException(ex);
186        }
187    }
188
189    private static void collectIpv4Address(String interfaceName, List<InetAddress> addresses,
190            List<InterfaceAddress> interfaceAddresses) throws SocketException {
191        FileDescriptor fd = null;
192        try {
193            fd = Libcore.os.socket(AF_INET, SOCK_DGRAM, 0);
194            InetAddress address = Libcore.os.ioctlInetAddress(fd, SIOCGIFADDR, interfaceName);
195            InetAddress broadcast = Libcore.os.ioctlInetAddress(fd, SIOCGIFBRDADDR, interfaceName);
196            InetAddress netmask = Libcore.os.ioctlInetAddress(fd, SIOCGIFNETMASK, interfaceName);
197            if (broadcast.equals(Inet4Address.ANY)) {
198                broadcast = null;
199            }
200
201            addresses.add(address);
202            interfaceAddresses.add(new InterfaceAddress((Inet4Address) address,
203                    (Inet4Address) broadcast, (Inet4Address) netmask));
204        } catch (ErrnoException errnoException) {
205            if (errnoException.errno != EADDRNOTAVAIL) {
206                // EADDRNOTAVAIL just means no IPv4 address for this interface.
207                // Anything else is a real error.
208                throw rethrowAsSocketException(errnoException);
209            }
210        } catch (Exception ex) {
211            throw rethrowAsSocketException(ex);
212        } finally {
213            IoUtils.closeQuietly(fd);
214        }
215    }
216
217    @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
218    private static boolean isValidInterfaceName(String interfaceName) {
219        final String[] interfaceList = SYS_CLASS_NET.list();
220        // We have no interfaces listed under /sys/class/net
221        if (interfaceList == null) {
222            return false;
223        }
224
225        // Don't just stat because a crafty user might have / or .. in the supposed interface name.
226        for (String validName : interfaceList) {
227            if (interfaceName.equals(validName)) {
228                return true;
229            }
230        }
231        return false;
232    }
233
234    private static int readIntFile(String path) throws SocketException {
235        try {
236            String s = IoUtils.readFileAsString(path).trim();
237            if (s.startsWith("0x")) {
238                return Integer.parseInt(s.substring(2), 16);
239            } else {
240                return Integer.parseInt(s);
241            }
242        } catch (Exception ex) {
243            throw rethrowAsSocketException(ex);
244        }
245    }
246
247    private static SocketException rethrowAsSocketException(Exception ex) throws SocketException {
248        SocketException result = new SocketException();
249        result.initCause(ex);
250        throw result;
251    }
252
253    /**
254     * Returns the {@code NetworkInterface} corresponding to the given address, or null if no
255     * interface has this address.
256     *
257     * @throws SocketException if an error occurs.
258     * @throws NullPointerException if {@code address == null}.
259     */
260    public static NetworkInterface getByInetAddress(InetAddress address) throws SocketException {
261        if (address == null) {
262            throw new NullPointerException("address == null");
263        }
264        for (NetworkInterface networkInterface : getNetworkInterfacesList()) {
265            if (networkInterface.addresses.contains(address)) {
266                return networkInterface;
267            }
268        }
269        return null;
270    }
271
272    /**
273     * Returns the NetworkInterface corresponding to the given interface index, or null if no
274     * interface has this index.
275     *
276     * @throws SocketException if an error occurs.
277     * @since 1.7
278     */
279    public static NetworkInterface getByIndex(int index) throws SocketException {
280        String name = Libcore.os.if_indextoname(index);
281        if (name == null) {
282            return null;
283        }
284        return NetworkInterface.getByName(name);
285    }
286
287    /**
288     * Gets a list of all network interfaces available on the local system or
289     * {@code null} if no interface is available.
290     *
291     * @return the list of {@code NetworkInterface} instances representing the
292     *         available interfaces.
293     * @throws SocketException
294     *             if an error occurs while getting the network interface
295     *             information.
296     */
297    public static Enumeration<NetworkInterface> getNetworkInterfaces() throws SocketException {
298        return Collections.enumeration(getNetworkInterfacesList());
299    }
300
301    @FindBugsSuppressWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME")
302    private static List<NetworkInterface> getNetworkInterfacesList() throws SocketException {
303        String[] interfaceNames = SYS_CLASS_NET.list();
304        NetworkInterface[] interfaces = new NetworkInterface[interfaceNames.length];
305        boolean[] done = new boolean[interfaces.length];
306
307        String[] ifInet6Lines = readIfInet6Lines();
308        for (int i = 0; i < interfaceNames.length; ++i) {
309            interfaces[i] = NetworkInterface.getByNameInternal(interfaceNames[i], ifInet6Lines);
310            // http://b/5833739: getByName can return null if the interface went away between our
311            // readdir(2) and our stat(2), so mark interfaces that disappeared as 'done'.
312            if (interfaces[i] == null) {
313                done[i] = true;
314            }
315        }
316
317        List<NetworkInterface> result = new ArrayList<NetworkInterface>();
318        for (int counter = 0; counter < interfaces.length; counter++) {
319            // If this interface has been dealt with already, continue.
320            if (done[counter]) {
321                continue;
322            }
323
324            // Checks whether the following interfaces are children.
325            for (int counter2 = counter; counter2 < interfaces.length; counter2++) {
326                if (done[counter2]) {
327                    continue;
328                }
329                if (interfaces[counter2].name.startsWith(interfaces[counter].name + ":")) {
330                    interfaces[counter].children.add(interfaces[counter2]);
331                    interfaces[counter2].parent = interfaces[counter];
332                    interfaces[counter].addresses.addAll(interfaces[counter2].addresses);
333                    done[counter2] = true;
334                }
335            }
336            result.add(interfaces[counter]);
337            done[counter] = true;
338        }
339        return result;
340    }
341
342    /**
343     * Compares the specified object to this {@code NetworkInterface} and
344     * returns whether they are equal or not. The object must be an instance of
345     * {@code NetworkInterface} with the same name, display name, and list
346     * of interface addresses.
347     *
348     * @param obj
349     *            the object to compare with this instance.
350     * @return {@code true} if the specified object is equal to this {@code
351     *         NetworkInterface}, {@code false} otherwise.
352     * @see #hashCode()
353     */
354    @Override
355    public boolean equals(Object obj) {
356        if (obj == this) {
357            return true;
358        }
359        if (!(obj instanceof NetworkInterface)) {
360            return false;
361        }
362        NetworkInterface rhs = (NetworkInterface) obj;
363        // TODO: should the order of the addresses matter (we use List.equals)?
364        return interfaceIndex == rhs.interfaceIndex &&
365                name.equals(rhs.name) &&
366                addresses.equals(rhs.addresses);
367    }
368
369    /**
370     * Returns the hash code for this {@code NetworkInterface}. Since the
371     * name should be unique for each network interface the hash code is
372     * generated using the name.
373     */
374    @Override public int hashCode() {
375        return name.hashCode();
376    }
377
378    /**
379     * Returns a string containing details of this network interface.
380     * The exact format is deliberately unspecified. Callers that require a specific
381     * format should build a string themselves, using this class' accessor methods.
382     */
383    @Override public String toString() {
384        StringBuilder sb = new StringBuilder(25);
385        sb.append("[");
386        sb.append(name);
387        sb.append("][");
388        sb.append(interfaceIndex);
389        sb.append("]");
390        for (InetAddress address : addresses) {
391            sb.append("[");
392            sb.append(address.toString());
393            sb.append("]");
394        }
395        return sb.toString();
396    }
397
398    /**
399     * Returns a List of the InterfaceAddresses for this network interface.
400     * @since 1.6
401     */
402    public List<InterfaceAddress> getInterfaceAddresses() {
403        return Collections.unmodifiableList(interfaceAddresses);
404    }
405
406    /**
407     * Returns an enumeration of all the sub-interfaces of this network interface.
408     * Sub-interfaces are also known as virtual interfaces.
409     *
410     * <p>For example, {@code eth0:1} would be a sub-interface of {@code eth0}.
411     *
412     * @return an Enumeration of all the sub-interfaces of this network interface
413     * @since 1.6
414     */
415    public Enumeration<NetworkInterface> getSubInterfaces() {
416        return Collections.enumeration(children);
417    }
418
419    /**
420     * Returns the parent NetworkInterface of this interface if this is a
421     * sub-interface, or null if it's a physical (non virtual) interface.
422     *
423     * @return the NetworkInterface this interface is attached to.
424     * @since 1.6
425     */
426    public NetworkInterface getParent() {
427        return parent;
428    }
429
430    /**
431     * Returns true if this network interface is up.
432     *
433     * @return true if the interface is up.
434     * @throws SocketException if an I/O error occurs.
435     * @since 1.6
436     */
437    public boolean isUp() throws SocketException {
438        return hasFlag(IFF_UP);
439    }
440
441    /**
442     * Returns true if this network interface is a loopback interface.
443     *
444     * @return true if the interface is a loopback interface.
445     * @throws SocketException if an I/O error occurs.
446     * @since 1.6
447     */
448    public boolean isLoopback() throws SocketException {
449        return hasFlag(IFF_LOOPBACK);
450    }
451
452    /**
453     * Returns true if this network interface is a point-to-point interface.
454     * (For example, a PPP connection using a modem.)
455     *
456     * @return true if the interface is point-to-point.
457     * @throws SocketException if an I/O error occurs.
458     * @since 1.6
459     */
460    public boolean isPointToPoint() throws SocketException {
461        return hasFlag(IFF_POINTOPOINT);
462    }
463
464    /**
465     * Returns true if this network interface supports multicast.
466     *
467     * @throws SocketException if an I/O error occurs.
468     * @since 1.6
469     */
470    public boolean supportsMulticast() throws SocketException {
471        return hasFlag(IFF_MULTICAST);
472    }
473
474    private boolean hasFlag(int mask) throws SocketException {
475        int flags = readIntFile("/sys/class/net/" + name + "/flags");
476        return (flags & mask) != 0;
477    }
478
479    /**
480     * Returns the hardware address of the interface, if it has one, or null otherwise.
481     *
482     * @throws SocketException if an I/O error occurs.
483     * @since 1.6
484     */
485    public byte[] getHardwareAddress() throws SocketException {
486        try {
487            // Parse colon-separated bytes with a trailing newline: "aa:bb:cc:dd:ee:ff\n".
488            String s = IoUtils.readFileAsString("/sys/class/net/" + name + "/address");
489            byte[] result = new byte[s.length()/3];
490            for (int i = 0; i < result.length; ++i) {
491                result[i] = (byte) Integer.parseInt(s.substring(3*i, 3*i + 2), 16);
492            }
493            // We only want to return non-zero hardware addresses.
494            for (int i = 0; i < result.length; ++i) {
495                if (result[i] != 0) {
496                    return result;
497                }
498            }
499            return null;
500        } catch (Exception ex) {
501            throw rethrowAsSocketException(ex);
502        }
503    }
504
505    /**
506     * Returns the Maximum Transmission Unit (MTU) of this interface.
507     *
508     * @return the value of the MTU for the interface.
509     * @throws SocketException if an I/O error occurs.
510     * @since 1.6
511     */
512    public int getMTU() throws SocketException {
513        return readIntFile("/sys/class/net/" + name + "/mtu");
514    }
515
516    /**
517     * Returns true if this interface is a virtual interface (also called
518     * a sub-interface). Virtual interfaces are, on some systems, interfaces
519     * created as a child of a physical interface and given different settings
520     * (like address or MTU). Usually the name of the interface will the name of
521     * the parent followed by a colon (:) and a number identifying the child,
522     * since there can be several virtual interfaces attached to a single
523     * physical interface.
524     *
525     * @return true if this interface is a virtual interface.
526     * @since 1.6
527     */
528    public boolean isVirtual() {
529        return parent != null;
530    }
531}
532